summaryrefslogtreecommitdiff
path: root/spec/ruby/library/datetime/strftime_spec.rb
AgeCommit message (Collapse)Author
2021-09-28Followed up ruby/spec examples for date.Hiroshi SHIBATA
https://github.com/ruby/ruby/commit/f9f7f3a75ec5af4a70e3332f8f5aa300c13432e2
2021-06-02Update to ruby/spec@a0b7d0dBenoit Daloze
2019-07-27Update to ruby/spec@875a09eBenoit Daloze
2018-03-04Update to ruby/spec@c1b568beregon
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62656 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2017-09-20Move spec/rubyspec to spec/ruby for consistencyeregon
* Other ruby implementations use the spec/ruby directory. [Misc #13792] [ruby-core:82287] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@59979 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
'none' style='width: 98.2%;'/> -rw-r--r--spec/bundler/bundler/cli_spec.rb262
-rw-r--r--spec/bundler/bundler/compact_index_client/updater_spec.rb59
-rw-r--r--spec/bundler/bundler/definition_spec.rb292
-rw-r--r--spec/bundler/bundler/dependency_spec.rb157
-rw-r--r--spec/bundler/bundler/digest_spec.rb24
-rw-r--r--spec/bundler/bundler/dsl_spec.rb308
-rw-r--r--spec/bundler/bundler/endpoint_specification_spec.rb83
-rw-r--r--spec/bundler/bundler/env_spec.rb236
-rw-r--r--spec/bundler/bundler/environment_preserver_spec.rb79
-rw-r--r--spec/bundler/bundler/fetcher/base_spec.rb76
-rw-r--r--spec/bundler/bundler/fetcher/compact_index_spec.rb109
-rw-r--r--spec/bundler/bundler/fetcher/dependency_spec.rb283
-rw-r--r--spec/bundler/bundler/fetcher/downloader_spec.rb255
-rw-r--r--spec/bundler/bundler/fetcher/index_spec.rb82
-rw-r--r--spec/bundler/bundler/fetcher_spec.rb192
-rw-r--r--spec/bundler/bundler/friendly_errors_spec.rb234
-rw-r--r--spec/bundler/bundler/gem_helper_spec.rb451
-rw-r--r--spec/bundler/bundler/gem_version_promoter_spec.rb163
-rw-r--r--spec/bundler/bundler/index_spec.rb36
-rw-r--r--spec/bundler/bundler/installer/gem_installer_spec.rb50
-rw-r--r--spec/bundler/bundler/installer/parallel_installer_spec.rb46
-rw-r--r--spec/bundler/bundler/installer/spec_installation_spec.rb66
-rw-r--r--spec/bundler/bundler/lockfile_parser_spec.rb153
-rw-r--r--spec/bundler/bundler/mirror_spec.rb331
-rw-r--r--spec/bundler/bundler/plugin/api/source_spec.rb88
-rw-r--r--spec/bundler/bundler/plugin/api_spec.rb83
-rw-r--r--spec/bundler/bundler/plugin/dsl_spec.rb38
-rw-r--r--spec/bundler/bundler/plugin/events_spec.rb22
-rw-r--r--spec/bundler/bundler/plugin/index_spec.rb204
-rw-r--r--spec/bundler/bundler/plugin/installer_spec.rb131
-rw-r--r--spec/bundler/bundler/plugin/source_list_spec.rb25
-rw-r--r--spec/bundler/bundler/plugin_spec.rb337
-rw-r--r--spec/bundler/bundler/remote_specification_spec.rb187
-rw-r--r--spec/bundler/bundler/resolver/candidate_spec.rb21
-rw-r--r--spec/bundler/bundler/retry_spec.rb81
-rw-r--r--spec/bundler/bundler/ruby_dsl_spec.rb120
-rw-r--r--spec/bundler/bundler/ruby_version_spec.rb500
-rw-r--r--spec/bundler/bundler/rubygems_integration_spec.rb104
-rw-r--r--spec/bundler/bundler/settings/validator_spec.rb111
-rw-r--r--spec/bundler/bundler/settings_spec.rb337
-rw-r--r--spec/bundler/bundler/shared_helpers_spec.rb506
-rw-r--r--spec/bundler/bundler/source/git/git_proxy_spec.rb150
-rw-r--r--spec/bundler/bundler/source/git_spec.rb73
-rw-r--r--spec/bundler/bundler/source/path_spec.rb31
-rw-r--r--spec/bundler/bundler/source/rubygems/remote_spec.rb172
-rw-r--r--spec/bundler/bundler/source/rubygems_spec.rb47
-rw-r--r--spec/bundler/bundler/source_list_spec.rb459
-rw-r--r--spec/bundler/bundler/source_spec.rb174
-rw-r--r--spec/bundler/bundler/spec_set_spec.rb77
-rw-r--r--spec/bundler/bundler/stub_specification_spec.rb47
-rw-r--r--spec/bundler/bundler/ui/shell_spec.rb60
-rw-r--r--spec/bundler/bundler/ui_spec.rb41
-rw-r--r--spec/bundler/bundler/uri_credentials_filter_spec.rb127
-rw-r--r--spec/bundler/bundler/worker_spec.rb69
-rw-r--r--spec/bundler/bundler/yaml_serializer_spec.rb194
-rw-r--r--spec/bundler/cache/cache_path_spec.rb32
-rw-r--r--spec/bundler/cache/gems_spec.rb327
-rw-r--r--spec/bundler/cache/git_spec.rb276
-rw-r--r--spec/bundler/cache/path_spec.rb169
-rw-r--r--spec/bundler/cache/platform_spec.rb49
-rw-r--r--spec/bundler/commands/add_spec.rb305
-rw-r--r--spec/bundler/commands/binstubs_spec.rb556
-rw-r--r--spec/bundler/commands/cache_spec.rb428
-rw-r--r--spec/bundler/commands/check_spec.rb554
-rw-r--r--spec/bundler/commands/clean_spec.rb916
-rw-r--r--spec/bundler/commands/config_spec.rb580
-rw-r--r--spec/bundler/commands/console_spec.rb141
-rw-r--r--spec/bundler/commands/doctor_spec.rb146
-rw-r--r--spec/bundler/commands/exec_spec.rb1254
-rw-r--r--spec/bundler/commands/fund_spec.rb82
-rw-r--r--spec/bundler/commands/help_spec.rb90
-rw-r--r--spec/bundler/commands/info_spec.rb249
-rw-r--r--spec/bundler/commands/init_spec.rb207
-rw-r--r--spec/bundler/commands/inject_spec.rb117
-rw-r--r--spec/bundler/commands/install_spec.rb1106
-rw-r--r--spec/bundler/commands/issue_spec.rb16
-rw-r--r--spec/bundler/commands/licenses_spec.rb37
-rw-r--r--spec/bundler/commands/list_spec.rb195
-rw-r--r--spec/bundler/commands/lock_spec.rb1273
-rw-r--r--spec/bundler/commands/newgem_spec.rb1662
-rw-r--r--spec/bundler/commands/open_spec.rb176
-rw-r--r--spec/bundler/commands/outdated_spec.rb1368
-rw-r--r--spec/bundler/commands/platform_spec.rb1307
-rw-r--r--spec/bundler/commands/post_bundle_message_spec.rb205
-rw-r--r--spec/bundler/commands/pristine_spec.rb221
-rw-r--r--spec/bundler/commands/remove_spec.rb732
-rw-r--r--spec/bundler/commands/show_spec.rb224
-rw-r--r--spec/bundler/commands/update_spec.rb1715
-rw-r--r--spec/bundler/commands/version_spec.rb47
-rw-r--r--spec/bundler/commands/viz_spec.rb144
-rw-r--r--spec/bundler/install/allow_offline_install_spec.rb99
-rw-r--r--spec/bundler/install/binstubs_spec.rb49
-rw-r--r--spec/bundler/install/bundler_spec.rb268
-rw-r--r--spec/bundler/install/deploy_spec.rb550
-rw-r--r--spec/bundler/install/failure_spec.rb51
-rw-r--r--spec/bundler/install/gemfile/eval_gemfile_spec.rb122
-rw-r--r--spec/bundler/install/gemfile/force_ruby_platform_spec.rb118
-rw-r--r--spec/bundler/install/gemfile/gemspec_spec.rb697
-rw-r--r--spec/bundler/install/gemfile/git_spec.rb1630
-rw-r--r--spec/bundler/install/gemfile/groups_spec.rb403
-rw-r--r--spec/bundler/install/gemfile/install_if_spec.rb44
-rw-r--r--spec/bundler/install/gemfile/lockfile_spec.rb48
-rw-r--r--spec/bundler/install/gemfile/path_spec.rb984
-rw-r--r--spec/bundler/install/gemfile/platform_spec.rb618
-rw-r--r--spec/bundler/install/gemfile/ruby_spec.rb123
-rw-r--r--spec/bundler/install/gemfile/sources_spec.rb1672
-rw-r--r--spec/bundler/install/gemfile/specific_platform_spec.rb971
-rw-r--r--spec/bundler/install/gemfile_spec.rb118
-rw-r--r--spec/bundler/install/gems/compact_index_spec.rb954
-rw-r--r--spec/bundler/install/gems/dependency_api_spec.rb759
-rw-r--r--spec/bundler/install/gems/env_spec.rb107
-rw-r--r--spec/bundler/install/gems/flex_spec.rb370
-rw-r--r--spec/bundler/install/gems/fund_spec.rb164
-rw-r--r--spec/bundler/install/gems/mirror_spec.rb39
-rw-r--r--spec/bundler/install/gems/native_extensions_spec.rb188
-rw-r--r--spec/bundler/install/gems/post_install_spec.rb150
-rw-r--r--spec/bundler/install/gems/resolving_spec.rb600
-rw-r--r--spec/bundler/install/gems/standalone_spec.rb515
-rw-r--r--spec/bundler/install/gems/win32_spec.rb25
-rw-r--r--spec/bundler/install/gemspecs_spec.rb161
-rw-r--r--spec/bundler/install/git_spec.rb174
-rw-r--r--spec/bundler/install/global_cache_spec.rb254
-rw-r--r--spec/bundler/install/path_spec.rb226
-rw-r--r--spec/bundler/install/prereleases_spec.rb54
-rw-r--r--spec/bundler/install/process_lock_spec.rb57
-rw-r--r--spec/bundler/install/redownload_spec.rb91
-rw-r--r--spec/bundler/install/security_policy_spec.rb72
-rw-r--r--spec/bundler/install/yanked_spec.rb233
-rw-r--r--spec/bundler/lock/git_spec.rb162
-rw-r--r--spec/bundler/lock/lockfile_spec.rb1608
-rw-r--r--spec/bundler/other/cli_dispatch_spec.rb20
-rw-r--r--spec/bundler/other/ext_spec.rb65
-rw-r--r--spec/bundler/other/major_deprecation_spec.rb649
-rw-r--r--spec/bundler/plugins/command_spec.rb78
-rw-r--r--spec/bundler/plugins/hook_spec.rb109
-rw-r--r--spec/bundler/plugins/install_spec.rb381
-rw-r--r--spec/bundler/plugins/list_spec.rb60
-rw-r--r--spec/bundler/plugins/source/example_spec.rb452
-rw-r--r--spec/bundler/plugins/source_spec.rb111
-rw-r--r--spec/bundler/plugins/uninstall_spec.rb49
-rw-r--r--spec/bundler/quality_es_spec.rb61
-rw-r--r--spec/bundler/quality_spec.rb245
-rw-r--r--spec/bundler/realworld/dependency_api_spec.rb46
-rw-r--r--spec/bundler/realworld/double_check_spec.rb40
-rw-r--r--spec/bundler/realworld/edgecases_spec.rb516
-rw-r--r--spec/bundler/realworld/ffi_spec.rb57
-rw-r--r--spec/bundler/realworld/fixtures/warbler/.gitignore1
-rw-r--r--spec/bundler/realworld/fixtures/warbler/Gemfile7
-rw-r--r--spec/bundler/realworld/fixtures/warbler/Gemfile.lock30
-rw-r--r--spec/bundler/realworld/fixtures/warbler/bin/warbler-example.rb3
-rw-r--r--spec/bundler/realworld/fixtures/warbler/demo/demo.gemspec10
-rw-r--r--spec/bundler/realworld/gemfile_source_header_spec.rb53
-rw-r--r--spec/bundler/realworld/git_spec.rb11
-rw-r--r--spec/bundler/realworld/mirror_probe_spec.rb131
-rw-r--r--spec/bundler/realworld/parallel_spec.rb66
-rw-r--r--spec/bundler/realworld/slow_perf_spec.rb33
-rw-r--r--spec/bundler/resolver/basic_spec.rb350
-rw-r--r--spec/bundler/resolver/platform_spec.rb427
-rw-r--r--spec/bundler/runtime/executable_spec.rb169
-rw-r--r--spec/bundler/runtime/gem_tasks_spec.rb106
-rw-r--r--spec/bundler/runtime/inline_spec.rb626
-rw-r--r--spec/bundler/runtime/load_spec.rb113
-rw-r--r--spec/bundler/runtime/platform_spec.rb464
-rw-r--r--spec/bundler/runtime/require_spec.rb465
-rw-r--r--spec/bundler/runtime/self_management_spec.rb126
-rw-r--r--spec/bundler/runtime/setup_spec.rb1557
-rw-r--r--spec/bundler/runtime/with_unbundled_env_spec.rb302
-rw-r--r--spec/bundler/spec_helper.rb119
-rw-r--r--spec/bundler/support/artifice/compact_index.rb6
-rw-r--r--spec/bundler/support/artifice/compact_index_api_missing.rb13
-rw-r--r--spec/bundler/support/artifice/compact_index_basic_authentication.rb15
-rw-r--r--spec/bundler/support/artifice/compact_index_checksum_mismatch.rb16
-rw-r--r--spec/bundler/support/artifice/compact_index_concurrent_download.rb32
-rw-r--r--spec/bundler/support/artifice/compact_index_creds_diff_host.rb39
-rw-r--r--spec/bundler/support/artifice/compact_index_extra.rb6
-rw-r--r--spec/bundler/support/artifice/compact_index_extra_api.rb6
-rw-r--r--spec/bundler/support/artifice/compact_index_extra_api_missing.rb17
-rw-r--r--spec/bundler/support/artifice/compact_index_extra_missing.rb17
-rw-r--r--spec/bundler/support/artifice/compact_index_forbidden.rb13
-rw-r--r--spec/bundler/support/artifice/compact_index_host_redirect.rb21
-rw-r--r--spec/bundler/support/artifice/compact_index_no_gem.rb13
-rw-r--r--spec/bundler/support/artifice/compact_index_partial_update.rb38
-rw-r--r--spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb40
-rw-r--r--spec/bundler/support/artifice/compact_index_precompiled_before.rb25
-rw-r--r--spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb34
-rw-r--r--spec/bundler/support/artifice/compact_index_rate_limited.rb48
-rw-r--r--spec/bundler/support/artifice/compact_index_redirects.rb21
-rw-r--r--spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb20
-rw-r--r--spec/bundler/support/artifice/compact_index_wrong_dependencies.rb17
-rw-r--r--spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb20
-rw-r--r--spec/bundler/support/artifice/endpoint.rb6
-rw-r--r--spec/bundler/support/artifice/endpoint_500.rb17
-rw-r--r--spec/bundler/support/artifice/endpoint_api_forbidden.rb13
-rw-r--r--spec/bundler/support/artifice/endpoint_basic_authentication.rb15
-rw-r--r--spec/bundler/support/artifice/endpoint_creds_diff_host.rb39
-rw-r--r--spec/bundler/support/artifice/endpoint_extra.rb33
-rw-r--r--spec/bundler/support/artifice/endpoint_extra_api.rb34
-rw-r--r--spec/bundler/support/artifice/endpoint_extra_missing.rb17
-rw-r--r--spec/bundler/support/artifice/endpoint_fallback.rb19
-rw-r--r--spec/bundler/support/artifice/endpoint_host_redirect.rb17
-rw-r--r--spec/bundler/support/artifice/endpoint_marshal_fail.rb6
-rw-r--r--spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb15
-rw-r--r--spec/bundler/support/artifice/endpoint_mirror_source.rb17
-rw-r--r--spec/bundler/support/artifice/endpoint_redirect.rb17
-rw-r--r--spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb20
-rw-r--r--spec/bundler/support/artifice/endpoint_timeout.rb15
-rw-r--r--spec/bundler/support/artifice/fail.rb29
-rw-r--r--spec/bundler/support/artifice/helpers/artifice.rb30
-rw-r--r--spec/bundler/support/artifice/helpers/compact_index.rb118
-rw-r--r--spec/bundler/support/artifice/helpers/compact_index_extra.rb33
-rw-r--r--spec/bundler/support/artifice/helpers/compact_index_extra_api.rb48
-rw-r--r--spec/bundler/support/artifice/helpers/endpoint.rb112
-rw-r--r--spec/bundler/support/artifice/helpers/endpoint_extra.rb29
-rw-r--r--spec/bundler/support/artifice/helpers/endpoint_fallback.rb15
-rw-r--r--spec/bundler/support/artifice/helpers/endpoint_marshal_fail.rb9
-rw-r--r--spec/bundler/support/artifice/helpers/rack_request.rb100
-rw-r--r--spec/bundler/support/artifice/used_cassettes.txt20908
-rw-r--r--spec/bundler/support/artifice/vcr.rb152
-rw-r--r--spec/bundler/support/artifice/windows.rb45
-rw-r--r--spec/bundler/support/build_metadata.rb49
-rw-r--r--spec/bundler/support/builders.rb677
-rw-r--r--spec/bundler/support/bundle.rb10
-rw-r--r--spec/bundler/support/command_execution.rb33
-rw-r--r--spec/bundler/support/filters.rb38
-rw-r--r--spec/bundler/support/hax.rb53
-rw-r--r--spec/bundler/support/helpers.rb597
-rw-r--r--spec/bundler/support/indexes.rb419
-rw-r--r--spec/bundler/support/matchers.rb237
-rw-r--r--spec/bundler/support/path.rb311
-rw-r--r--spec/bundler/support/permissions.rb12
-rw-r--r--spec/bundler/support/platforms.rb106
-rw-r--r--spec/bundler/support/rubygems_ext.rb177
-rw-r--r--spec/bundler/support/rubygems_version_manager.rb120
-rw-r--r--spec/bundler/support/silent_logger.rb10
-rw-r--r--spec/bundler/support/switch_rubygems.rb4
-rw-r--r--spec/bundler/support/the_bundle.rb35
-rw-r--r--spec/bundler/update/gemfile_spec.rb47
-rw-r--r--spec/bundler/update/gems/fund_spec.rb50
-rw-r--r--spec/bundler/update/gems/post_install_spec.rb76
-rw-r--r--spec/bundler/update/git_spec.rb336
-rw-r--r--spec/bundler/update/path_spec.rb19
-rw-r--r--spec/bundler/update/redownload_spec.rb34
-rw-r--r--spec/default.mspec74
-rw-r--r--spec/mspec/.rspec1
-rw-r--r--spec/mspec/Gemfile4
-rw-r--r--spec/mspec/Gemfile.lock26
-rw-r--r--spec/mspec/LICENSE22
-rw-r--r--spec/mspec/README.md84
-rw-r--r--spec/mspec/Rakefile6
-rwxr-xr-xspec/mspec/bin/mkspec7
-rwxr-xr-xspec/mspec/bin/mkspec.bat1
-rwxr-xr-xspec/mspec/bin/mspec7
-rwxr-xr-xspec/mspec/bin/mspec-ci7
-rwxr-xr-xspec/mspec/bin/mspec-ci.bat1
-rwxr-xr-xspec/mspec/bin/mspec-run7
-rwxr-xr-xspec/mspec/bin/mspec-run.bat1
-rwxr-xr-xspec/mspec/bin/mspec-tag7
-rwxr-xr-xspec/mspec/bin/mspec-tag.bat1
-rwxr-xr-xspec/mspec/bin/mspec.bat1
-rw-r--r--spec/mspec/lib/mspec.rb8
-rwxr-xr-xspec/mspec/lib/mspec/commands/mkspec.rb145
-rw-r--r--spec/mspec/lib/mspec/commands/mspec-ci.rb79
-rw-r--r--spec/mspec/lib/mspec/commands/mspec-run.rb87
-rw-r--r--spec/mspec/lib/mspec/commands/mspec-tag.rb133
-rwxr-xr-xspec/mspec/lib/mspec/commands/mspec.rb120
-rw-r--r--spec/mspec/lib/mspec/expectations.rb2
-rw-r--r--spec/mspec/lib/mspec/expectations/expectations.rb39
-rw-r--r--spec/mspec/lib/mspec/expectations/should.rb41
-rw-r--r--spec/mspec/lib/mspec/guards.rb11
-rw-r--r--spec/mspec/lib/mspec/guards/block_device.rb16
-rw-r--r--spec/mspec/lib/mspec/guards/bug.rb29
-rw-r--r--spec/mspec/lib/mspec/guards/conflict.rb23
-rw-r--r--spec/mspec/lib/mspec/guards/endian.rb25
-rw-r--r--spec/mspec/lib/mspec/guards/feature.rb45
-rw-r--r--spec/mspec/lib/mspec/guards/guard.rb141
-rw-r--r--spec/mspec/lib/mspec/guards/platform.rb104
-rw-r--r--spec/mspec/lib/mspec/guards/quarantine.rb11
-rw-r--r--spec/mspec/lib/mspec/guards/superuser.rb25
-rw-r--r--spec/mspec/lib/mspec/guards/support.rb14
-rw-r--r--spec/mspec/lib/mspec/guards/version.rb72
-rw-r--r--spec/mspec/lib/mspec/helpers.rb13
-rw-r--r--spec/mspec/lib/mspec/helpers/argf.rb35
-rw-r--r--spec/mspec/lib/mspec/helpers/argv.rb44
-rw-r--r--spec/mspec/lib/mspec/helpers/datetime.rb48
-rw-r--r--spec/mspec/lib/mspec/helpers/fixture.rb24
-rw-r--r--spec/mspec/lib/mspec/helpers/flunk.rb3
-rw-r--r--spec/mspec/lib/mspec/helpers/fs.rb64
-rw-r--r--spec/mspec/lib/mspec/helpers/io.rb87
-rw-r--r--spec/mspec/lib/mspec/helpers/mock_to_path.rb6
-rw-r--r--spec/mspec/lib/mspec/helpers/numeric.rb80
-rw-r--r--spec/mspec/lib/mspec/helpers/ruby_exe.rb189
-rw-r--r--spec/mspec/lib/mspec/helpers/scratch.rb21
-rw-r--r--spec/mspec/lib/mspec/helpers/tmp.rb48
-rw-r--r--spec/mspec/lib/mspec/helpers/warning.rb21
-rw-r--r--spec/mspec/lib/mspec/matchers.rb37
-rw-r--r--spec/mspec/lib/mspec/matchers/base.rb79
-rw-r--r--spec/mspec/lib/mspec/matchers/be_an_instance_of.rb26
-rw-r--r--spec/mspec/lib/mspec/matchers/be_ancestor_of.rb24
-rw-r--r--spec/mspec/lib/mspec/matchers/be_close.rb29
-rw-r--r--spec/mspec/lib/mspec/matchers/be_computed_by.rb37
-rw-r--r--spec/mspec/lib/mspec/matchers/be_empty.rb20
-rw-r--r--spec/mspec/lib/mspec/matchers/be_false.rb20
-rw-r--r--spec/mspec/lib/mspec/matchers/be_kind_of.rb24
-rw-r--r--spec/mspec/lib/mspec/matchers/be_nan.rb20
-rw-r--r--spec/mspec/lib/mspec/matchers/be_nil.rb20
-rw-r--r--spec/mspec/lib/mspec/matchers/be_true.rb20
-rw-r--r--spec/mspec/lib/mspec/matchers/be_true_or_false.rb20
-rw-r--r--spec/mspec/lib/mspec/matchers/block_caller.rb37
-rw-r--r--spec/mspec/lib/mspec/matchers/complain.rb71
-rw-r--r--spec/mspec/lib/mspec/matchers/eql.rb26
-rw-r--r--spec/mspec/lib/mspec/matchers/equal.rb26
-rw-r--r--spec/mspec/lib/mspec/matchers/equal_element.rb78
-rw-r--r--spec/mspec/lib/mspec/matchers/have_class_variable.rb12
-rw-r--r--spec/mspec/lib/mspec/matchers/have_constant.rb12
-rw-r--r--spec/mspec/lib/mspec/matchers/have_instance_method.rb24
-rw-r--r--spec/mspec/lib/mspec/matchers/have_instance_variable.rb12
-rw-r--r--spec/mspec/lib/mspec/matchers/have_method.rb24
-rw-r--r--spec/mspec/lib/mspec/matchers/have_private_instance_method.rb24
-rw-r--r--spec/mspec/lib/mspec/matchers/have_private_method.rb24
-rw-r--r--spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb24
-rw-r--r--spec/mspec/lib/mspec/matchers/have_public_instance_method.rb24
-rw-r--r--spec/mspec/lib/mspec/matchers/have_singleton_method.rb24
-rw-r--r--spec/mspec/lib/mspec/matchers/include.rb31
-rw-r--r--spec/mspec/lib/mspec/matchers/include_any_of.rb29
-rw-r--r--spec/mspec/lib/mspec/matchers/infinity.rb28
-rw-r--r--spec/mspec/lib/mspec/matchers/match_yaml.rb50
-rw-r--r--spec/mspec/lib/mspec/matchers/method.rb10
-rw-r--r--spec/mspec/lib/mspec/matchers/output.rb67
-rw-r--r--spec/mspec/lib/mspec/matchers/output_to_fd.rb71
-rw-r--r--spec/mspec/lib/mspec/matchers/raise_error.rb93
-rw-r--r--spec/mspec/lib/mspec/matchers/respond_to.rb24
-rw-r--r--spec/mspec/lib/mspec/matchers/signed_zero.rb28
-rw-r--r--spec/mspec/lib/mspec/matchers/skip.rb5
-rw-r--r--spec/mspec/lib/mspec/matchers/variable.rb24
-rw-r--r--spec/mspec/lib/mspec/mocks.rb3
-rw-r--r--spec/mspec/lib/mspec/mocks/mock.rb212
-rw-r--r--spec/mspec/lib/mspec/mocks/object.rb28
-rw-r--r--spec/mspec/lib/mspec/mocks/proxy.rb186
-rw-r--r--spec/mspec/lib/mspec/runner.rb12
-rw-r--r--spec/mspec/lib/mspec/runner/actions.rb6
-rw-r--r--spec/mspec/lib/mspec/runner/actions/constants_leak_checker.rb84
-rw-r--r--spec/mspec/lib/mspec/runner/actions/filter.rb40
-rw-r--r--spec/mspec/lib/mspec/runner/actions/leakchecker.rb319
-rw-r--r--spec/mspec/lib/mspec/runner/actions/profile.rb60
-rw-r--r--spec/mspec/lib/mspec/runner/actions/tag.rb133
-rw-r--r--spec/mspec/lib/mspec/runner/actions/taglist.rb56
-rw-r--r--spec/mspec/lib/mspec/runner/actions/tagpurge.rb56
-rw-r--r--spec/mspec/lib/mspec/runner/actions/tally.rb133
-rw-r--r--spec/mspec/lib/mspec/runner/actions/timeout.rb93
-rw-r--r--spec/mspec/lib/mspec/runner/actions/timer.rb22
-rw-r--r--spec/mspec/lib/mspec/runner/context.rb237
-rw-r--r--spec/mspec/lib/mspec/runner/evaluate.rb54
-rw-r--r--spec/mspec/lib/mspec/runner/example.rb34
-rw-r--r--spec/mspec/lib/mspec/runner/exception.rb54
-rw-r--r--spec/mspec/lib/mspec/runner/filters.rb4
-rw-r--r--spec/mspec/lib/mspec/runner/filters/match.rb18
-rw-r--r--spec/mspec/lib/mspec/runner/filters/profile.rb54
-rw-r--r--spec/mspec/lib/mspec/runner/filters/regexp.rb23
-rw-r--r--spec/mspec/lib/mspec/runner/filters/tag.rb29
-rw-r--r--spec/mspec/lib/mspec/runner/formatters.rb13
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/base.rb144
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/describe.rb23
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/dotted.rb23
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/file.rb24
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/html.rb81
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/junit.rb87
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/method.rb95
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/multi.rb47
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/profile.rb18
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/specdoc.rb41
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/spinner.rb111
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/stats.rb57
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/summary.rb4
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/unit.rb20
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/yaml.rb38
-rw-r--r--spec/mspec/lib/mspec/runner/mspec.rb422
-rw-r--r--spec/mspec/lib/mspec/runner/object.rb26
-rw-r--r--spec/mspec/lib/mspec/runner/parallel.rb98
-rw-r--r--spec/mspec/lib/mspec/runner/shared.rb14
-rw-r--r--spec/mspec/lib/mspec/runner/tag.rb38
-rw-r--r--spec/mspec/lib/mspec/utils/deprecate.rb6
-rw-r--r--spec/mspec/lib/mspec/utils/format.rb24
-rw-r--r--spec/mspec/lib/mspec/utils/name_map.rb126
-rw-r--r--spec/mspec/lib/mspec/utils/options.rb504
-rw-r--r--spec/mspec/lib/mspec/utils/script.rb299
-rw-r--r--spec/mspec/lib/mspec/utils/version.rb52
-rw-r--r--spec/mspec/lib/mspec/utils/warnings.rb53
-rw-r--r--spec/mspec/lib/mspec/version.rb5
-rw-r--r--spec/mspec/spec/commands/fixtures/four.txt0
-rw-r--r--spec/mspec/spec/commands/fixtures/level2/three_spec.rb1
-rw-r--r--spec/mspec/spec/commands/fixtures/one_spec.rb1
-rw-r--r--spec/mspec/spec/commands/fixtures/three.rb1
-rw-r--r--spec/mspec/spec/commands/fixtures/two_spec.rb1
-rw-r--r--spec/mspec/spec/commands/mkspec_spec.rb363
-rw-r--r--spec/mspec/spec/commands/mspec_ci_spec.rb150
-rw-r--r--spec/mspec/spec/commands/mspec_run_spec.rb173
-rw-r--r--spec/mspec/spec/commands/mspec_spec.rb207
-rw-r--r--spec/mspec/spec/commands/mspec_tag_spec.rb414
-rw-r--r--spec/mspec/spec/expectations/expectations_spec.rb29
-rw-r--r--spec/mspec/spec/expectations/should_spec.rb61
-rw-r--r--spec/mspec/spec/fixtures/a_spec.rb15
-rw-r--r--spec/mspec/spec/fixtures/b_spec.rb7
-rw-r--r--spec/mspec/spec/fixtures/chatty_spec.rb8
-rw-r--r--spec/mspec/spec/fixtures/config.mspec8
-rw-r--r--spec/mspec/spec/fixtures/die_spec.rb7
-rwxr-xr-xspec/mspec/spec/fixtures/my_ruby4
-rw-r--r--spec/mspec/spec/fixtures/object_methods_spec.rb8
-rw-r--r--spec/mspec/spec/fixtures/print_interpreter_spec.rb4
-rw-r--r--spec/mspec/spec/fixtures/should.rb75
-rw-r--r--spec/mspec/spec/fixtures/tagging_spec.rb16
-rw-r--r--spec/mspec/spec/guards/block_device_spec.rb46
-rw-r--r--spec/mspec/spec/guards/bug_spec.rb151
-rw-r--r--spec/mspec/spec/guards/conflict_spec.rb53
-rw-r--r--spec/mspec/spec/guards/endian_spec.rb55
-rw-r--r--spec/mspec/spec/guards/feature_spec.rb120
-rw-r--r--spec/mspec/spec/guards/guard_spec.rb421
-rw-r--r--spec/mspec/spec/guards/platform_spec.rb337
-rw-r--r--spec/mspec/spec/guards/quarantine_spec.rb35
-rw-r--r--spec/mspec/spec/guards/superuser_spec.rb35
-rw-r--r--spec/mspec/spec/guards/support_spec.rb54
-rw-r--r--spec/mspec/spec/guards/user_spec.rb20
-rw-r--r--spec/mspec/spec/guards/version_spec.rb112
-rw-r--r--spec/mspec/spec/helpers/argf_spec.rb37
-rw-r--r--spec/mspec/spec/helpers/argv_spec.rb27
-rw-r--r--spec/mspec/spec/helpers/datetime_spec.rb44
-rw-r--r--spec/mspec/spec/helpers/fixture_spec.rb25
-rw-r--r--spec/mspec/spec/helpers/flunk_spec.rb20
-rw-r--r--spec/mspec/spec/helpers/fs_spec.rb195
-rw-r--r--spec/mspec/spec/helpers/io_spec.rb136
-rw-r--r--spec/mspec/spec/helpers/mock_to_path_spec.rb23
-rw-r--r--spec/mspec/spec/helpers/numeric_spec.rb31
-rw-r--r--spec/mspec/spec/helpers/ruby_exe_spec.rb256
-rw-r--r--spec/mspec/spec/helpers/scratch_spec.rb24
-rw-r--r--spec/mspec/spec/helpers/suppress_warning_spec.rb19
-rw-r--r--spec/mspec/spec/helpers/tmp_spec.rb27
-rw-r--r--spec/mspec/spec/integration/interpreter_spec.rb18
-rw-r--r--spec/mspec/spec/integration/object_methods_spec.rb18
-rw-r--r--spec/mspec/spec/integration/run_spec.rb71
-rw-r--r--spec/mspec/spec/integration/tag_spec.rb59
-rw-r--r--spec/mspec/spec/matchers/base_spec.rb228
-rw-r--r--spec/mspec/spec/matchers/be_an_instance_of_spec.rb50
-rw-r--r--spec/mspec/spec/matchers/be_ancestor_of_spec.rb28
-rw-r--r--spec/mspec/spec/matchers/be_close_spec.rb48
-rw-r--r--spec/mspec/spec/matchers/be_computed_by_spec.rb42
-rw-r--r--spec/mspec/spec/matchers/be_empty_spec.rb26
-rw-r--r--spec/mspec/spec/matchers/be_false_spec.rb28
-rw-r--r--spec/mspec/spec/matchers/be_kind_of_spec.rb31
-rw-r--r--spec/mspec/spec/matchers/be_nan_spec.rb28
-rw-r--r--spec/mspec/spec/matchers/be_nil_spec.rb27
-rw-r--r--spec/mspec/spec/matchers/be_true_or_false_spec.rb19
-rw-r--r--spec/mspec/spec/matchers/be_true_spec.rb28
-rw-r--r--spec/mspec/spec/matchers/block_caller_spec.rb13
-rw-r--r--spec/mspec/spec/matchers/complain_spec.rb102
-rw-r--r--spec/mspec/spec/matchers/eql_spec.rb33
-rw-r--r--spec/mspec/spec/matchers/equal_element_spec.rb75
-rw-r--r--spec/mspec/spec/matchers/equal_spec.rb32
-rw-r--r--spec/mspec/spec/matchers/have_class_variable_spec.rb49
-rw-r--r--spec/mspec/spec/matchers/have_constant_spec.rb37
-rw-r--r--spec/mspec/spec/matchers/have_instance_method_spec.rb53
-rw-r--r--spec/mspec/spec/matchers/have_instance_variable_spec.rb50
-rw-r--r--spec/mspec/spec/matchers/have_method_spec.rb55
-rw-r--r--spec/mspec/spec/matchers/have_private_instance_method_spec.rb57
-rw-r--r--spec/mspec/spec/matchers/have_private_method_spec.rb44
-rw-r--r--spec/mspec/spec/matchers/have_protected_instance_method_spec.rb57
-rw-r--r--spec/mspec/spec/matchers/have_public_instance_method_spec.rb53
-rw-r--r--spec/mspec/spec/matchers/have_singleton_method_spec.rb45
-rw-r--r--spec/mspec/spec/matchers/include_any_of_spec.rb42
-rw-r--r--spec/mspec/spec/matchers/include_spec.rb37
-rw-r--r--spec/mspec/spec/matchers/infinity_spec.rb34
-rw-r--r--spec/mspec/spec/matchers/match_yaml_spec.rb39
-rw-r--r--spec/mspec/spec/matchers/output_spec.rb84
-rw-r--r--spec/mspec/spec/matchers/output_to_fd_spec.rb44
-rw-r--r--spec/mspec/spec/matchers/raise_error_spec.rb183
-rw-r--r--spec/mspec/spec/matchers/respond_to_spec.rb33
-rw-r--r--spec/mspec/spec/matchers/signed_zero_spec.rb32
-rw-r--r--spec/mspec/spec/mocks/mock_spec.rb530
-rw-r--r--spec/mspec/spec/mocks/proxy_spec.rb405
-rw-r--r--spec/mspec/spec/runner/actions/filter_spec.rb84
-rw-r--r--spec/mspec/spec/runner/actions/tag_spec.rb313
-rw-r--r--spec/mspec/spec/runner/actions/taglist_spec.rb152
-rw-r--r--spec/mspec/spec/runner/actions/tagpurge_spec.rb154
-rw-r--r--spec/mspec/spec/runner/actions/tally_spec.rb355
-rw-r--r--spec/mspec/spec/runner/actions/timer_spec.rb44
-rw-r--r--spec/mspec/spec/runner/context_spec.rb1028
-rw-r--r--spec/mspec/spec/runner/example_spec.rb117
-rw-r--r--spec/mspec/spec/runner/exception_spec.rb146
-rw-r--r--spec/mspec/spec/runner/filters/a.yaml4
-rw-r--r--spec/mspec/spec/runner/filters/b.yaml11
-rw-r--r--spec/mspec/spec/runner/filters/match_spec.rb34
-rw-r--r--spec/mspec/spec/runner/filters/profile_spec.rb117
-rw-r--r--spec/mspec/spec/runner/filters/regexp_spec.rb31
-rw-r--r--spec/mspec/spec/runner/filters/tag_spec.rb92
-rw-r--r--spec/mspec/spec/runner/formatters/describe_spec.rb67
-rw-r--r--spec/mspec/spec/runner/formatters/dotted_spec.rb284
-rw-r--r--spec/mspec/spec/runner/formatters/file_spec.rb84
-rw-r--r--spec/mspec/spec/runner/formatters/html_spec.rb220
-rw-r--r--spec/mspec/spec/runner/formatters/junit_spec.rb159
-rw-r--r--spec/mspec/spec/runner/formatters/method_spec.rb177
-rw-r--r--spec/mspec/spec/runner/formatters/multi_spec.rb68
-rw-r--r--spec/mspec/spec/runner/formatters/specdoc_spec.rb106
-rw-r--r--spec/mspec/spec/runner/formatters/spinner_spec.rb83
-rw-r--r--spec/mspec/spec/runner/formatters/summary_spec.rb26
-rw-r--r--spec/mspec/spec/runner/formatters/unit_spec.rb73
-rw-r--r--spec/mspec/spec/runner/formatters/yaml_spec.rb134
-rw-r--r--spec/mspec/spec/runner/mspec_spec.rb597
-rw-r--r--spec/mspec/spec/runner/shared_spec.rb90
-rw-r--r--spec/mspec/spec/runner/tag_spec.rb123
-rw-r--r--spec/mspec/spec/runner/tags.txt4
-rw-r--r--spec/mspec/spec/spec_helper.rb68
-rw-r--r--spec/mspec/spec/utils/deprecate_spec.rb17
-rw-r--r--spec/mspec/spec/utils/name_map_spec.rb175
-rw-r--r--spec/mspec/spec/utils/options_spec.rb1302
-rw-r--r--spec/mspec/spec/utils/script_spec.rb470
-rw-r--r--spec/mspec/spec/utils/version_spec.rb45
-rwxr-xr-xspec/mspec/tool/find.rb10
-rwxr-xr-xspec/mspec/tool/pull-latest-mspec-spec26
-rw-r--r--spec/mspec/tool/remove_old_guards.rb70
-rw-r--r--spec/mspec/tool/sync/.gitignore4
-rw-r--r--spec/mspec/tool/sync/sync-rubyspec.rb254
-rwxr-xr-xspec/mspec/tool/tag_from_output.rb63
-rwxr-xr-xspec/mspec/tool/wrap_with_guard.rb28
-rw-r--r--spec/ruby/.gitignore5
-rw-r--r--spec/ruby/.mspec.constants234
-rw-r--r--spec/ruby/.rubocop.yml185
-rw-r--r--spec/ruby/.rubocop_todo.yml137
-rw-r--r--spec/ruby/CONTRIBUTING.md295
-rw-r--r--spec/ruby/LICENSE22
-rw-r--r--spec/ruby/README.md157
-rw-r--r--spec/ruby/TODO8
-rw-r--r--spec/ruby/command_line/backtrace_limit_spec.rb48
-rw-r--r--spec/ruby/command_line/dash_a_spec.rb19
-rw-r--r--spec/ruby/command_line/dash_c_spec.rb13
-rw-r--r--spec/ruby/command_line/dash_d_spec.rb22
-rw-r--r--spec/ruby/command_line/dash_e_spec.rb41
-rw-r--r--spec/ruby/command_line/dash_encoding_spec.rb36
-rw-r--r--spec/ruby/command_line/dash_external_encoding_spec.rb15
-rw-r--r--spec/ruby/command_line/dash_internal_encoding_spec.rb15
-rw-r--r--spec/ruby/command_line/dash_l_spec.rb31
-rw-r--r--spec/ruby/command_line/dash_n_spec.rb36
-rw-r--r--spec/ruby/command_line/dash_p_spec.rb19
-rw-r--r--spec/ruby/command_line/dash_r_spec.rb28
-rw-r--r--spec/ruby/command_line/dash_s_spec.rb52
-rw-r--r--spec/ruby/command_line/dash_upper_c_spec.rb6
-rw-r--r--spec/ruby/command_line/dash_upper_e_spec.rb37
-rw-r--r--spec/ruby/command_line/dash_upper_f_spec.rb13
-rw-r--r--spec/ruby/command_line/dash_upper_i_spec.rb51
-rw-r--r--spec/ruby/command_line/dash_upper_k_spec.rb65
-rw-r--r--spec/ruby/command_line/dash_upper_s_spec.rb29
-rw-r--r--spec/ruby/command_line/dash_upper_u_spec.rb45
-rw-r--r--spec/ruby/command_line/dash_upper_w_spec.rb44
-rw-r--r--spec/ruby/command_line/dash_upper_x_spec.rb6
-rw-r--r--spec/ruby/command_line/dash_v_spec.rb13
-rw-r--r--spec/ruby/command_line/dash_w_spec.rb10
-rw-r--r--spec/ruby/command_line/dash_x_spec.rb21
-rw-r--r--spec/ruby/command_line/error_message_spec.rb11
-rw-r--r--spec/ruby/command_line/feature_spec.rb71
-rw-r--r--spec/ruby/command_line/fixtures/backtrace.rb35
-rw-r--r--spec/ruby/command_line/fixtures/bad_syntax.rb1
-rw-r--r--spec/ruby/command_line/fixtures/bin/bad_embedded_ruby.txt3
-rw-r--r--spec/ruby/command_line/fixtures/bin/dash_s_fail1
-rw-r--r--spec/ruby/command_line/fixtures/bin/embedded_ruby.txt3
-rw-r--r--spec/ruby/command_line/fixtures/bin/hybrid_launcher.sh4
-rwxr-xr-xspec/ruby/command_line/fixtures/bin/launcher.rb2
-rw-r--r--spec/ruby/command_line/fixtures/change_directory_script.rb1
-rw-r--r--spec/ruby/command_line/fixtures/conditional_range.txt5
-rw-r--r--spec/ruby/command_line/fixtures/dash_s_script.rb12
-rw-r--r--spec/ruby/command_line/fixtures/debug.rb10
-rw-r--r--spec/ruby/command_line/fixtures/debug_info.rb11
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_across_files.rb3
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_across_files_diff_enc.rb3
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb2
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_required.rb1
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rbbin0 -> 121 bytes-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_two_literals.rb1
-rw-r--r--spec/ruby/command_line/fixtures/full_names.txt3
-rw-r--r--spec/ruby/command_line/fixtures/loadpath.rb1
-rw-r--r--spec/ruby/command_line/fixtures/names.txt3
-rw-r--r--spec/ruby/command_line/fixtures/passwd_file.txt3
-rw-r--r--spec/ruby/command_line/fixtures/require.rb1
-rw-r--r--spec/ruby/command_line/fixtures/rubyopt.rb1
-rw-r--r--spec/ruby/command_line/fixtures/test_file.rb1
-rw-r--r--spec/ruby/command_line/fixtures/verbose.rb1
-rw-r--r--spec/ruby/command_line/frozen_strings_spec.rb29
-rw-r--r--spec/ruby/command_line/rubylib_spec.rb69
-rw-r--r--spec/ruby/command_line/rubyopt_spec.rb185
-rw-r--r--spec/ruby/command_line/shared/change_directory.rb21
-rw-r--r--spec/ruby/command_line/shared/verbose.rb9
-rw-r--r--spec/ruby/command_line/syntax_error_spec.rb13
-rw-r--r--spec/ruby/core/argf/argf_spec.rb11
-rw-r--r--spec/ruby/core/argf/argv_spec.rb19
-rw-r--r--spec/ruby/core/argf/binmode_spec.rb43
-rw-r--r--spec/ruby/core/argf/bytes_spec.rb16
-rw-r--r--spec/ruby/core/argf/chars_spec.rb16
-rw-r--r--spec/ruby/core/argf/close_spec.rb35
-rw-r--r--spec/ruby/core/argf/closed_spec.rb18
-rw-r--r--spec/ruby/core/argf/codepoints_spec.rb16
-rw-r--r--spec/ruby/core/argf/each_byte_spec.rb6
-rw-r--r--spec/ruby/core/argf/each_char_spec.rb6
-rw-r--r--spec/ruby/core/argf/each_codepoint_spec.rb6
-rw-r--r--spec/ruby/core/argf/each_line_spec.rb6
-rw-r--r--spec/ruby/core/argf/each_spec.rb6
-rw-r--r--spec/ruby/core/argf/eof_spec.rb10
-rw-r--r--spec/ruby/core/argf/file_spec.rb21
-rw-r--r--spec/ruby/core/argf/filename_spec.rb6
-rw-r--r--spec/ruby/core/argf/fileno_spec.rb6
-rw-r--r--spec/ruby/core/argf/fixtures/bin_file.txt2
-rw-r--r--spec/ruby/core/argf/fixtures/file1.txt2
-rw-r--r--spec/ruby/core/argf/fixtures/file2.txt2
-rw-r--r--spec/ruby/core/argf/fixtures/filename.rb3
-rw-r--r--spec/ruby/core/argf/fixtures/lineno.rb5
-rw-r--r--spec/ruby/core/argf/fixtures/rewind.rb5
-rw-r--r--spec/ruby/core/argf/fixtures/stdin.txt2
-rw-r--r--spec/ruby/core/argf/getc_spec.rb20
-rw-r--r--spec/ruby/core/argf/gets_spec.rb49
-rw-r--r--spec/ruby/core/argf/lineno_spec.rb30
-rw-r--r--spec/ruby/core/argf/lines_spec.rb16
-rw-r--r--spec/ruby/core/argf/path_spec.rb6
-rw-r--r--spec/ruby/core/argf/pos_spec.rb38
-rw-r--r--spec/ruby/core/argf/read_nonblock_spec.rb80
-rw-r--r--spec/ruby/core/argf/read_spec.rb85
-rw-r--r--spec/ruby/core/argf/readchar_spec.rb19
-rw-r--r--spec/ruby/core/argf/readline_spec.rb23
-rw-r--r--spec/ruby/core/argf/readlines_spec.rb6
-rw-r--r--spec/ruby/core/argf/readpartial_spec.rb75
-rw-r--r--spec/ruby/core/argf/rewind_spec.rb39
-rw-r--r--spec/ruby/core/argf/seek_spec.rb63
-rw-r--r--spec/ruby/core/argf/set_encoding_spec.rb41
-rw-r--r--spec/ruby/core/argf/shared/each_byte.rb58
-rw-r--r--spec/ruby/core/argf/shared/each_char.rb58
-rw-r--r--spec/ruby/core/argf/shared/each_codepoint.rb58
-rw-r--r--spec/ruby/core/argf/shared/each_line.rb62
-rw-r--r--spec/ruby/core/argf/shared/eof.rb24
-rw-r--r--spec/ruby/core/argf/shared/filename.rb28
-rw-r--r--spec/ruby/core/argf/shared/fileno.rb24
-rw-r--r--spec/ruby/core/argf/shared/getc.rb17
-rw-r--r--spec/ruby/core/argf/shared/gets.rb99
-rw-r--r--spec/ruby/core/argf/shared/pos.rb31
-rw-r--r--spec/ruby/core/argf/shared/read.rb58
-rw-r--r--spec/ruby/core/argf/shared/readlines.rb22
-rw-r--r--spec/ruby/core/argf/skip_spec.rb42
-rw-r--r--spec/ruby/core/argf/tell_spec.rb6
-rw-r--r--spec/ruby/core/argf/to_a_spec.rb6
-rw-r--r--spec/ruby/core/argf/to_i_spec.rb6
-rw-r--r--spec/ruby/core/argf/to_io_spec.rb23
-rw-r--r--spec/ruby/core/argf/to_s_spec.rb14
-rw-r--r--spec/ruby/core/array/allocate_spec.rb19
-rw-r--r--spec/ruby/core/array/any_spec.rb37
-rw-r--r--spec/ruby/core/array/append_spec.rb40
-rw-r--r--spec/ruby/core/array/array_spec.rb7
-rw-r--r--spec/ruby/core/array/assoc_spec.rb40
-rw-r--r--spec/ruby/core/array/at_spec.rb56
-rw-r--r--spec/ruby/core/array/bsearch_index_spec.rb85
-rw-r--r--spec/ruby/core/array/bsearch_spec.rb84
-rw-r--r--spec/ruby/core/array/clear_spec.rb32
-rw-r--r--spec/ruby/core/array/clone_spec.rb31
-rw-r--r--spec/ruby/core/array/collect_spec.rb11
-rw-r--r--spec/ruby/core/array/combination_spec.rb74
-rw-r--r--spec/ruby/core/array/compact_spec.rb51
-rw-r--r--spec/ruby/core/array/comparison_spec.rb97
-rw-r--r--spec/ruby/core/array/concat_spec.rb74
-rw-r--r--spec/ruby/core/array/constructor_spec.rb24
-rw-r--r--spec/ruby/core/array/count_spec.rb15
-rw-r--r--spec/ruby/core/array/cycle_spec.rb101
-rw-r--r--spec/ruby/core/array/deconstruct_spec.rb9
-rw-r--r--spec/ruby/core/array/delete_at_spec.rb41
-rw-r--r--spec/ruby/core/array/delete_if_spec.rb52
-rw-r--r--spec/ruby/core/array/delete_spec.rb46
-rw-r--r--spec/ruby/core/array/difference_spec.rb22
-rw-r--r--spec/ruby/core/array/dig_spec.rb52
-rw-r--r--spec/ruby/core/array/drop_spec.rb64
-rw-r--r--spec/ruby/core/array/drop_while_spec.rb28
-rw-r--r--spec/ruby/core/array/dup_spec.rb31
-rw-r--r--spec/ruby/core/array/each_index_spec.rb42
-rw-r--r--spec/ruby/core/array/each_spec.rb77
-rw-r--r--spec/ruby/core/array/element_reference_spec.rb50
-rw-r--r--spec/ruby/core/array/element_set_spec.rb537
-rw-r--r--spec/ruby/core/array/empty_spec.rb10
-rw-r--r--spec/ruby/core/array/eql_spec.rb19
-rw-r--r--spec/ruby/core/array/equal_value_spec.rb51
-rw-r--r--spec/ruby/core/array/fetch_spec.rb55
-rw-r--r--spec/ruby/core/array/fill_spec.rb336
-rw-r--r--spec/ruby/core/array/filter_spec.rb14
-rw-r--r--spec/ruby/core/array/find_index_spec.rb6
-rw-r--r--spec/ruby/core/array/first_spec.rb93
-rw-r--r--spec/ruby/core/array/fixtures/classes.rb584
-rw-r--r--spec/ruby/core/array/fixtures/encoded_strings.rb69
-rw-r--r--spec/ruby/core/array/flatten_spec.rb278
-rw-r--r--spec/ruby/core/array/frozen_spec.rb16
-rw-r--r--spec/ruby/core/array/hash_spec.rb83
-rw-r--r--spec/ruby/core/array/include_spec.rb33
-rw-r--r--spec/ruby/core/array/index_spec.rb6
-rw-r--r--spec/ruby/core/array/initialize_spec.rb156
-rw-r--r--spec/ruby/core/array/insert_spec.rb78
-rw-r--r--spec/ruby/core/array/inspect_spec.rb7
-rw-r--r--spec/ruby/core/array/intersect_spec.rb17
-rw-r--r--spec/ruby/core/array/intersection_spec.rb19
-rw-r--r--spec/ruby/core/array/join_spec.rb50
-rw-r--r--spec/ruby/core/array/keep_if_spec.rb11
-rw-r--r--spec/ruby/core/array/last_spec.rb87
-rw-r--r--spec/ruby/core/array/length_spec.rb7
-rw-r--r--spec/ruby/core/array/map_spec.rb11
-rw-r--r--spec/ruby/core/array/max_spec.rb116
-rw-r--r--spec/ruby/core/array/min_spec.rb121
-rw-r--r--spec/ruby/core/array/minmax_spec.rb14
-rw-r--r--spec/ruby/core/array/minus_spec.rb7
-rw-r--r--spec/ruby/core/array/multiply_spec.rb104
-rw-r--r--spec/ruby/core/array/new_spec.rb122
-rw-r--r--spec/ruby/core/array/pack/a_spec.rb73
-rw-r--r--spec/ruby/core/array/pack/at_spec.rb30
-rw-r--r--spec/ruby/core/array/pack/b_spec.rb113
-rw-r--r--spec/ruby/core/array/pack/buffer_spec.rb50
-rw-r--r--spec/ruby/core/array/pack/c_spec.rb85
-rw-r--r--spec/ruby/core/array/pack/comment_spec.rb25
-rw-r--r--spec/ruby/core/array/pack/d_spec.rb39
-rw-r--r--spec/ruby/core/array/pack/e_spec.rb25
-rw-r--r--spec/ruby/core/array/pack/empty_spec.rb11
-rw-r--r--spec/ruby/core/array/pack/f_spec.rb39
-rw-r--r--spec/ruby/core/array/pack/g_spec.rb25
-rw-r--r--spec/ruby/core/array/pack/h_spec.rb205
-rw-r--r--spec/ruby/core/array/pack/i_spec.rb133
-rw-r--r--spec/ruby/core/array/pack/j_spec.rb217
-rw-r--r--spec/ruby/core/array/pack/l_spec.rb221
-rw-r--r--spec/ruby/core/array/pack/m_spec.rb317
-rw-r--r--spec/ruby/core/array/pack/n_spec.rb25
-rw-r--r--spec/ruby/core/array/pack/p_spec.rb38
-rw-r--r--spec/ruby/core/array/pack/percent_spec.rb7
-rw-r--r--spec/ruby/core/array/pack/q_spec.rb61
-rw-r--r--spec/ruby/core/array/pack/s_spec.rb133
-rw-r--r--spec/ruby/core/array/pack/shared/basic.rb97
-rw-r--r--spec/ruby/core/array/pack/shared/encodings.rb16
-rw-r--r--spec/ruby/core/array/pack/shared/float.rb287
-rw-r--r--spec/ruby/core/array/pack/shared/integer.rb441
-rw-r--r--spec/ruby/core/array/pack/shared/numeric_basic.rb50
-rw-r--r--spec/ruby/core/array/pack/shared/string.rb48
-rw-r--r--spec/ruby/core/array/pack/shared/taint.rb2
-rw-r--r--spec/ruby/core/array/pack/shared/unicode.rb104
-rw-r--r--spec/ruby/core/array/pack/u_spec.rb140
-rw-r--r--spec/ruby/core/array/pack/v_spec.rb25
-rw-r--r--spec/ruby/core/array/pack/w_spec.rb52
-rw-r--r--spec/ruby/core/array/pack/x_spec.rb65
-rw-r--r--spec/ruby/core/array/pack/z_spec.rb44
-rw-r--r--spec/ruby/core/array/partition_spec.rb43
-rw-r--r--spec/ruby/core/array/permutation_spec.rb138
-rw-r--r--spec/ruby/core/array/plus_spec.rb43
-rw-r--r--spec/ruby/core/array/pop_spec.rb124
-rw-r--r--spec/ruby/core/array/prepend_spec.rb7
-rw-r--r--spec/ruby/core/array/product_spec.rb68
-rw-r--r--spec/ruby/core/array/push_spec.rb7
-rw-r--r--spec/ruby/core/array/rassoc_spec.rb38
-rw-r--r--spec/ruby/core/array/reject_spec.rb143
-rw-r--r--spec/ruby/core/array/repeated_combination_spec.rb84
-rw-r--r--spec/ruby/core/array/repeated_permutation_spec.rb94
-rw-r--r--spec/ruby/core/array/replace_spec.rb7
-rw-r--r--spec/ruby/core/array/reverse_each_spec.rb43
-rw-r--r--spec/ruby/core/array/reverse_spec.rb42
-rw-r--r--spec/ruby/core/array/rindex_spec.rb80
-rw-r--r--spec/ruby/core/array/rotate_spec.rb129
-rw-r--r--spec/ruby/core/array/sample_spec.rb144
-rw-r--r--spec/ruby/core/array/select_spec.rb14
-rw-r--r--spec/ruby/core/array/shared/clone.rb20
-rw-r--r--spec/ruby/core/array/shared/collect.rb109
-rw-r--r--spec/ruby/core/array/shared/delete_if.rb13
-rw-r--r--spec/ruby/core/array/shared/difference.rb78
-rw-r--r--spec/ruby/core/array/shared/enumeratorize.rb5
-rw-r--r--spec/ruby/core/array/shared/eql.rb92
-rw-r--r--spec/ruby/core/array/shared/index.rb37
-rw-r--r--spec/ruby/core/array/shared/inspect.rb107
-rw-r--r--spec/ruby/core/array/shared/intersection.rb84
-rw-r--r--spec/ruby/core/array/shared/join.rb112
-rw-r--r--spec/ruby/core/array/shared/keep_if.rb60
-rw-r--r--spec/ruby/core/array/shared/length.rb11
-rw-r--r--spec/ruby/core/array/shared/push.rb33
-rw-r--r--spec/ruby/core/array/shared/replace.rb60
-rw-r--r--spec/ruby/core/array/shared/select.rb32
-rw-r--r--spec/ruby/core/array/shared/slice.rb889
-rw-r--r--spec/ruby/core/array/shared/union.rb79
-rw-r--r--spec/ruby/core/array/shared/unshift.rb64
-rw-r--r--spec/ruby/core/array/shift_spec.rb120
-rw-r--r--spec/ruby/core/array/shuffle_spec.rb96
-rw-r--r--spec/ruby/core/array/size_spec.rb7
-rw-r--r--spec/ruby/core/array/slice_spec.rb246
-rw-r--r--spec/ruby/core/array/sort_by_spec.rb52
-rw-r--r--spec/ruby/core/array/sort_spec.rb252
-rw-r--r--spec/ruby/core/array/sum_spec.rb71
-rw-r--r--spec/ruby/core/array/take_spec.rb40
-rw-r--r--spec/ruby/core/array/take_while_spec.rb28
-rw-r--r--spec/ruby/core/array/to_a_spec.rb24
-rw-r--r--spec/ruby/core/array/to_ary_spec.rb20
-rw-r--r--spec/ruby/core/array/to_h_spec.rb79
-rw-r--r--spec/ruby/core/array/to_s_spec.rb8
-rw-r--r--spec/ruby/core/array/transpose_spec.rb53
-rw-r--r--spec/ruby/core/array/try_convert_spec.rb50
-rw-r--r--spec/ruby/core/array/union_spec.rb25
-rw-r--r--spec/ruby/core/array/uniq_spec.rb217
-rw-r--r--spec/ruby/core/array/unshift_spec.rb7
-rw-r--r--spec/ruby/core/array/values_at_spec.rb74
-rw-r--r--spec/ruby/core/array/zip_spec.rb71
-rw-r--r--spec/ruby/core/basicobject/__id__spec.rb6
-rw-r--r--spec/ruby/core/basicobject/__send___spec.rb10
-rw-r--r--spec/ruby/core/basicobject/basicobject_spec.rb91
-rw-r--r--spec/ruby/core/basicobject/equal_spec.rb52
-rw-r--r--spec/ruby/core/basicobject/equal_value_spec.rb10
-rw-r--r--spec/ruby/core/basicobject/fixtures/classes.rb33
-rw-r--r--spec/ruby/core/basicobject/fixtures/common.rb9
-rw-r--r--spec/ruby/core/basicobject/fixtures/remove_method_missing.rb9
-rw-r--r--spec/ruby/core/basicobject/fixtures/singleton_method.rb10
-rw-r--r--spec/ruby/core/basicobject/initialize_spec.rb13
-rw-r--r--spec/ruby/core/basicobject/instance_eval_spec.rb248
-rw-r--r--spec/ruby/core/basicobject/instance_exec_spec.rb107
-rw-r--r--spec/ruby/core/basicobject/method_missing_spec.rb39
-rw-r--r--spec/ruby/core/basicobject/not_equal_spec.rb53
-rw-r--r--spec/ruby/core/basicobject/not_spec.rb11
-rw-r--r--spec/ruby/core/basicobject/singleton_method_added_spec.rb145
-rw-r--r--spec/ruby/core/basicobject/singleton_method_removed_spec.rb24
-rw-r--r--spec/ruby/core/basicobject/singleton_method_undefined_spec.rb24
-rw-r--r--spec/ruby/core/binding/clone_spec.rb7
-rw-r--r--spec/ruby/core/binding/dup_spec.rb7
-rw-r--r--spec/ruby/core/binding/eval_spec.rb152
-rw-r--r--spec/ruby/core/binding/fixtures/classes.rb66
-rw-r--r--spec/ruby/core/binding/fixtures/irb.rb3
-rw-r--r--spec/ruby/core/binding/fixtures/irbrc1
-rw-r--r--spec/ruby/core/binding/fixtures/location.rb6
-rw-r--r--spec/ruby/core/binding/irb_spec.rb16
-rw-r--r--spec/ruby/core/binding/local_variable_defined_spec.rb46
-rw-r--r--spec/ruby/core/binding/local_variable_get_spec.rb56
-rw-r--r--spec/ruby/core/binding/local_variable_set_spec.rb71
-rw-r--r--spec/ruby/core/binding/local_variables_spec.rb35
-rw-r--r--spec/ruby/core/binding/receiver_spec.rb11
-rw-r--r--spec/ruby/core/binding/shared/clone.rb34
-rw-r--r--spec/ruby/core/binding/source_location_spec.rb9
-rw-r--r--spec/ruby/core/builtin_constants/builtin_constants_spec.rb49
-rw-r--r--spec/ruby/core/class/allocate_spec.rb41
-rw-r--r--spec/ruby/core/class/attached_object_spec.rb31
-rw-r--r--spec/ruby/core/class/dup_spec.rb64
-rw-r--r--spec/ruby/core/class/fixtures/classes.rb47
-rw-r--r--spec/ruby/core/class/inherited_spec.rb101
-rw-r--r--spec/ruby/core/class/initialize_spec.rb34
-rw-r--r--spec/ruby/core/class/new_spec.rb155
-rw-r--r--spec/ruby/core/class/subclasses_spec.rb60
-rw-r--r--spec/ruby/core/class/superclass_spec.rb27
-rw-r--r--spec/ruby/core/comparable/between_spec.rb25
-rw-r--r--spec/ruby/core/comparable/clamp_spec.rb78
-rw-r--r--spec/ruby/core/comparable/equal_value_spec.rb114
-rw-r--r--spec/ruby/core/comparable/fixtures/classes.rb36
-rw-r--r--spec/ruby/core/comparable/gt_spec.rb43
-rw-r--r--spec/ruby/core/comparable/gte_spec.rb47
-rw-r--r--spec/ruby/core/comparable/lt_spec.rb49
-rw-r--r--spec/ruby/core/comparable/lte_spec.rb46
-rw-r--r--spec/ruby/core/complex/abs2_spec.rb9
-rw-r--r--spec/ruby/core/complex/abs_spec.rb6
-rw-r--r--spec/ruby/core/complex/angle_spec.rb6
-rw-r--r--spec/ruby/core/complex/arg_spec.rb6
-rw-r--r--spec/ruby/core/complex/coerce_spec.rb70
-rw-r--r--spec/ruby/core/complex/comparison_spec.rb25
-rw-r--r--spec/ruby/core/complex/conj_spec.rb6
-rw-r--r--spec/ruby/core/complex/conjugate_spec.rb6
-rw-r--r--spec/ruby/core/complex/constants_spec.rb7
-rw-r--r--spec/ruby/core/complex/denominator_spec.rb13
-rw-r--r--spec/ruby/core/complex/divide_spec.rb6
-rw-r--r--spec/ruby/core/complex/eql_spec.rb31
-rw-r--r--spec/ruby/core/complex/equal_value_spec.rb93
-rw-r--r--spec/ruby/core/complex/exponent_spec.rb61
-rw-r--r--spec/ruby/core/complex/fdiv_spec.rb129
-rw-r--r--spec/ruby/core/complex/finite_spec.rb32
-rw-r--r--spec/ruby/core/complex/hash_spec.rb16
-rw-r--r--spec/ruby/core/complex/imag_spec.rb6
-rw-r--r--spec/ruby/core/complex/imaginary_spec.rb6
-rw-r--r--spec/ruby/core/complex/infinite_spec.rb32
-rw-r--r--spec/ruby/core/complex/inspect_spec.rb16
-rw-r--r--spec/ruby/core/complex/integer_spec.rb11
-rw-r--r--spec/ruby/core/complex/magnitude_spec.rb6
-rw-r--r--spec/ruby/core/complex/marshal_dump_spec.rb11
-rw-r--r--spec/ruby/core/complex/minus_spec.rb45
-rw-r--r--spec/ruby/core/complex/multiply_spec.rb49
-rw-r--r--spec/ruby/core/complex/negative_spec.rb13
-rw-r--r--spec/ruby/core/complex/numerator_spec.rb19
-rw-r--r--spec/ruby/core/complex/phase_spec.rb6
-rw-r--r--spec/ruby/core/complex/plus_spec.rb45
-rw-r--r--spec/ruby/core/complex/polar_spec.rb43
-rw-r--r--spec/ruby/core/complex/positive_spec.rb13
-rw-r--r--spec/ruby/core/complex/quo_spec.rb6
-rw-r--r--spec/ruby/core/complex/rationalize_spec.rb31
-rw-r--r--spec/ruby/core/complex/real_spec.rb28
-rw-r--r--spec/ruby/core/complex/rect_spec.rb10
-rw-r--r--spec/ruby/core/complex/rectangular_spec.rb10
-rw-r--r--spec/ruby/core/complex/shared/abs.rb10
-rw-r--r--spec/ruby/core/complex/shared/arg.rb9
-rw-r--r--spec/ruby/core/complex/shared/conjugate.rb8
-rw-r--r--spec/ruby/core/complex/shared/divide.rb82
-rw-r--r--spec/ruby/core/complex/shared/image.rb8
-rw-r--r--spec/ruby/core/complex/shared/rect.rb94
-rw-r--r--spec/ruby/core/complex/to_c_spec.rb12
-rw-r--r--spec/ruby/core/complex/to_f_spec.rb41
-rw-r--r--spec/ruby/core/complex/to_i_spec.rb41
-rw-r--r--spec/ruby/core/complex/to_r_spec.rb41
-rw-r--r--spec/ruby/core/complex/to_s_spec.rb44
-rw-r--r--spec/ruby/core/complex/uminus_spec.rb11
-rw-r--r--spec/ruby/core/conditionvariable/broadcast_spec.rb40
-rw-r--r--spec/ruby/core/conditionvariable/marshal_dump_spec.rb9
-rw-r--r--spec/ruby/core/conditionvariable/signal_spec.rb77
-rw-r--r--spec/ruby/core/conditionvariable/wait_spec.rb175
-rw-r--r--spec/ruby/core/data/constants_spec.rb35
-rw-r--r--spec/ruby/core/dir/chdir_spec.rb124
-rw-r--r--spec/ruby/core/dir/children_spec.rb134
-rw-r--r--spec/ruby/core/dir/chroot_spec.rb47
-rw-r--r--spec/ruby/core/dir/close_spec.rb19
-rw-r--r--spec/ruby/core/dir/delete_spec.rb15
-rw-r--r--spec/ruby/core/dir/dir_spec.rb7
-rw-r--r--spec/ruby/core/dir/each_child_spec.rb106
-rw-r--r--spec/ruby/core/dir/each_spec.rb64
-rw-r--r--spec/ruby/core/dir/element_reference_spec.rb33
-rw-r--r--spec/ruby/core/dir/empty_spec.rb31
-rw-r--r--spec/ruby/core/dir/entries_spec.rb70
-rw-r--r--spec/ruby/core/dir/exist_spec.rb15
-rw-r--r--spec/ruby/core/dir/fileno_spec.rb37
-rw-r--r--spec/ruby/core/dir/fixtures/common.rb204
-rw-r--r--spec/ruby/core/dir/foreach_spec.rb68
-rw-r--r--spec/ruby/core/dir/getwd_spec.rb15
-rw-r--r--spec/ruby/core/dir/glob_spec.rb253
-rw-r--r--spec/ruby/core/dir/home_spec.rb88
-rw-r--r--spec/ruby/core/dir/initialize_spec.rb23
-rw-r--r--spec/ruby/core/dir/inspect_spec.rb24
-rw-r--r--spec/ruby/core/dir/mkdir_spec.rb107
-rw-r--r--spec/ruby/core/dir/open_spec.rb15
-rw-r--r--spec/ruby/core/dir/path_spec.rb15
-rw-r--r--spec/ruby/core/dir/pos_spec.rb40
-rw-r--r--spec/ruby/core/dir/pwd_spec.rb39
-rw-r--r--spec/ruby/core/dir/read_spec.rb76
-rw-r--r--spec/ruby/core/dir/rewind_spec.rb36
-rw-r--r--spec/ruby/core/dir/rmdir_spec.rb15
-rw-r--r--spec/ruby/core/dir/seek_spec.rb19
-rw-r--r--spec/ruby/core/dir/shared/chroot.rb44
-rw-r--r--spec/ruby/core/dir/shared/closed.rb9
-rw-r--r--spec/ruby/core/dir/shared/delete.rb63
-rw-r--r--spec/ruby/core/dir/shared/exist.rb56
-rw-r--r--spec/ruby/core/dir/shared/glob.rb484
-rw-r--r--spec/ruby/core/dir/shared/open.rb73
-rw-r--r--spec/ruby/core/dir/shared/path.rb30
-rw-r--r--spec/ruby/core/dir/shared/pos.rb51
-rw-r--r--spec/ruby/core/dir/shared/pwd.rb45
-rw-r--r--spec/ruby/core/dir/tell_spec.rb18
-rw-r--r--spec/ruby/core/dir/to_path_spec.rb15
-rw-r--r--spec/ruby/core/dir/unlink_spec.rb15
-rw-r--r--spec/ruby/core/encoding/_dump_spec.rb5
-rw-r--r--spec/ruby/core/encoding/_load_spec.rb5
-rw-r--r--spec/ruby/core/encoding/aliases_spec.rb43
-rw-r--r--spec/ruby/core/encoding/ascii_compatible_spec.rb11
-rw-r--r--spec/ruby/core/encoding/compatible_spec.rb379
-rw-r--r--spec/ruby/core/encoding/converter/asciicompat_encoding_spec.rb37
-rw-r--r--spec/ruby/core/encoding/converter/constants_spec.rb131
-rw-r--r--spec/ruby/core/encoding/converter/convert_spec.rb45
-rw-r--r--spec/ruby/core/encoding/converter/convpath_spec.rb24
-rw-r--r--spec/ruby/core/encoding/converter/destination_encoding_spec.rb11
-rw-r--r--spec/ruby/core/encoding/converter/finish_spec.rb36
-rw-r--r--spec/ruby/core/encoding/converter/insert_output_spec.rb5
-rw-r--r--spec/ruby/core/encoding/converter/inspect_spec.rb13
-rw-r--r--spec/ruby/core/encoding/converter/last_error_spec.rb91
-rw-r--r--spec/ruby/core/encoding/converter/new_spec.rb119
-rw-r--r--spec/ruby/core/encoding/converter/primitive_convert_spec.rb211
-rw-r--r--spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb68
-rw-r--r--spec/ruby/core/encoding/converter/putback_spec.rb56
-rw-r--r--spec/ruby/core/encoding/converter/replacement_spec.rb72
-rw-r--r--spec/ruby/core/encoding/converter/search_convpath_spec.rb30
-rw-r--r--spec/ruby/core/encoding/converter/source_encoding_spec.rb11
-rw-r--r--spec/ruby/core/encoding/default_external_spec.rb71
-rw-r--r--spec/ruby/core/encoding/default_internal_spec.rb74
-rw-r--r--spec/ruby/core/encoding/dummy_spec.rb14
-rw-r--r--spec/ruby/core/encoding/find_spec.rb82
-rw-r--r--spec/ruby/core/encoding/fixtures/classes.rb49
-rw-r--r--spec/ruby/core/encoding/inspect_spec.rb19
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_name_spec.rb18
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_spec.rb18
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb30
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb28
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb30
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_name_spec.rb28
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_spec.rb33
-rw-r--r--spec/ruby/core/encoding/list_spec.rb49
-rw-r--r--spec/ruby/core/encoding/locale_charmap_spec.rb56
-rw-r--r--spec/ruby/core/encoding/name_list_spec.rb23
-rw-r--r--spec/ruby/core/encoding/name_spec.rb5
-rw-r--r--spec/ruby/core/encoding/names_spec.rb35
-rw-r--r--spec/ruby/core/encoding/replicate_spec.rb75
-rw-r--r--spec/ruby/core/encoding/shared/name.rb15
-rw-r--r--spec/ruby/core/encoding/to_s_spec.rb5
-rw-r--r--spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_name_spec.rb15
-rw-r--r--spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_spec.rb15
-rw-r--r--spec/ruby/core/encoding/undefined_conversion_error/error_char_spec.rb27
-rw-r--r--spec/ruby/core/encoding/undefined_conversion_error/source_encoding_name_spec.rb28
-rw-r--r--spec/ruby/core/encoding/undefined_conversion_error/source_encoding_spec.rb29
-rw-r--r--spec/ruby/core/enumerable/all_spec.rb181
-rw-r--r--spec/ruby/core/enumerable/any_spec.rb194
-rw-r--r--spec/ruby/core/enumerable/chain_spec.rb23
-rw-r--r--spec/ruby/core/enumerable/chunk_spec.rb72
-rw-r--r--spec/ruby/core/enumerable/chunk_while_spec.rb42
-rw-r--r--spec/ruby/core/enumerable/collect_concat_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/collect_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/compact_spec.rb11
-rw-r--r--spec/ruby/core/enumerable/count_spec.rb59
-rw-r--r--spec/ruby/core/enumerable/cycle_spec.rb104
-rw-r--r--spec/ruby/core/enumerable/detect_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/drop_spec.rb43
-rw-r--r--spec/ruby/core/enumerable/drop_while_spec.rb50
-rw-r--r--spec/ruby/core/enumerable/each_cons_spec.rb105
-rw-r--r--spec/ruby/core/enumerable/each_entry_spec.rb41
-rw-r--r--spec/ruby/core/enumerable/each_slice_spec.rb107
-rw-r--r--spec/ruby/core/enumerable/each_with_index_spec.rb53
-rw-r--r--spec/ruby/core/enumerable/each_with_object_spec.rb41
-rw-r--r--spec/ruby/core/enumerable/entries_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/filter_map_spec.rb24
-rw-r--r--spec/ruby/core/enumerable/filter_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/find_all_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/find_index_spec.rb89
-rw-r--r--spec/ruby/core/enumerable/find_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/first_spec.rb28
-rw-r--r--spec/ruby/core/enumerable/fixtures/classes.rb345
-rw-r--r--spec/ruby/core/enumerable/flat_map_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/grep_spec.rb102
-rw-r--r--spec/ruby/core/enumerable/grep_v_spec.rb91
-rw-r--r--spec/ruby/core/enumerable/group_by_spec.rb37
-rw-r--r--spec/ruby/core/enumerable/include_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/inject_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/lazy_spec.rb10
-rw-r--r--spec/ruby/core/enumerable/map_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/max_by_spec.rb81
-rw-r--r--spec/ruby/core/enumerable/max_spec.rb119
-rw-r--r--spec/ruby/core/enumerable/member_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/min_by_spec.rb81
-rw-r--r--spec/ruby/core/enumerable/min_spec.rb123
-rw-r--r--spec/ruby/core/enumerable/minmax_by_spec.rb44
-rw-r--r--spec/ruby/core/enumerable/minmax_spec.rb20
-rw-r--r--spec/ruby/core/enumerable/none_spec.rb147
-rw-r--r--spec/ruby/core/enumerable/one_spec.rb149
-rw-r--r--spec/ruby/core/enumerable/partition_spec.rb20
-rw-r--r--spec/ruby/core/enumerable/reduce_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/reject_spec.rb25
-rw-r--r--spec/ruby/core/enumerable/reverse_each_spec.rb26
-rw-r--r--spec/ruby/core/enumerable/select_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/shared/collect.rb107
-rw-r--r--spec/ruby/core/enumerable/shared/collect_concat.rb54
-rw-r--r--spec/ruby/core/enumerable/shared/entries.rb16
-rw-r--r--spec/ruby/core/enumerable/shared/enumerable_enumeratorized.rb33
-rw-r--r--spec/ruby/core/enumerable/shared/enumeratorized.rb42
-rw-r--r--spec/ruby/core/enumerable/shared/find.rb77
-rw-r--r--spec/ruby/core/enumerable/shared/find_all.rb31
-rw-r--r--spec/ruby/core/enumerable/shared/include.rb34
-rw-r--r--spec/ruby/core/enumerable/shared/inject.rb77
-rw-r--r--spec/ruby/core/enumerable/shared/take.rb63
-rw-r--r--spec/ruby/core/enumerable/slice_after_spec.rb61
-rw-r--r--spec/ruby/core/enumerable/slice_before_spec.rb64
-rw-r--r--spec/ruby/core/enumerable/slice_when_spec.rb54
-rw-r--r--spec/ruby/core/enumerable/sort_by_spec.rb43
-rw-r--r--spec/ruby/core/enumerable/sort_spec.rb54
-rw-r--r--spec/ruby/core/enumerable/sum_spec.rb50
-rw-r--r--spec/ruby/core/enumerable/take_spec.rb13
-rw-r--r--spec/ruby/core/enumerable/take_while_spec.rb51
-rw-r--r--spec/ruby/core/enumerable/tally_spec.rb80
-rw-r--r--spec/ruby/core/enumerable/to_a_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/to_h_spec.rb88
-rw-r--r--spec/ruby/core/enumerable/uniq_spec.rb78
-rw-r--r--spec/ruby/core/enumerable/zip_spec.rb46
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb16
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb17
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb16
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb18
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb17
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb9
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb20
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb20
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb9
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb17
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb17
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb11
-rw-r--r--spec/ruby/core/enumerator/chain/each_spec.rb15
-rw-r--r--spec/ruby/core/enumerator/chain/initialize_spec.rb31
-rw-r--r--spec/ruby/core/enumerator/chain/inspect_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/chain/rewind_spec.rb51
-rw-r--r--spec/ruby/core/enumerator/chain/size_spec.rb22
-rw-r--r--spec/ruby/core/enumerator/each_spec.rb89
-rw-r--r--spec/ruby/core/enumerator/each_with_index_spec.rb36
-rw-r--r--spec/ruby/core/enumerator/each_with_object_spec.rb6
-rw-r--r--spec/ruby/core/enumerator/enum_for_spec.rb6
-rw-r--r--spec/ruby/core/enumerator/enumerator_spec.rb7
-rw-r--r--spec/ruby/core/enumerator/feed_spec.rb52
-rw-r--r--spec/ruby/core/enumerator/first_spec.rb7
-rw-r--r--spec/ruby/core/enumerator/fixtures/common.rb9
-rw-r--r--spec/ruby/core/enumerator/generator/each_spec.rb40
-rw-r--r--spec/ruby/core/enumerator/generator/initialize_spec.rb26
-rw-r--r--spec/ruby/core/enumerator/initialize_spec.rb65
-rw-r--r--spec/ruby/core/enumerator/inspect_spec.rb17
-rw-r--r--spec/ruby/core/enumerator/lazy/chunk_spec.rb67
-rw-r--r--spec/ruby/core/enumerator/lazy/chunk_while_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/collect_concat_spec.rb8
-rw-r--r--spec/ruby/core/enumerator/lazy/collect_spec.rb8
-rw-r--r--spec/ruby/core/enumerator/lazy/compact_spec.rb11
-rw-r--r--spec/ruby/core/enumerator/lazy/drop_spec.rb58
-rw-r--r--spec/ruby/core/enumerator/lazy/drop_while_spec.rb66
-rw-r--r--spec/ruby/core/enumerator/lazy/eager_spec.rb27
-rw-r--r--spec/ruby/core/enumerator/lazy/enum_for_spec.rb8
-rw-r--r--spec/ruby/core/enumerator/lazy/filter_map_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/filter_spec.rb6
-rw-r--r--spec/ruby/core/enumerator/lazy/find_all_spec.rb8
-rw-r--r--spec/ruby/core/enumerator/lazy/fixtures/classes.rb54
-rw-r--r--spec/ruby/core/enumerator/lazy/flat_map_spec.rb16
-rw-r--r--spec/ruby/core/enumerator/lazy/force_spec.rb36
-rw-r--r--spec/ruby/core/enumerator/lazy/grep_spec.rb121
-rw-r--r--spec/ruby/core/enumerator/lazy/grep_v_spec.rb123
-rw-r--r--spec/ruby/core/enumerator/lazy/initialize_spec.rb63
-rw-r--r--spec/ruby/core/enumerator/lazy/lazy_spec.rb32
-rw-r--r--spec/ruby/core/enumerator/lazy/map_spec.rb12
-rw-r--r--spec/ruby/core/enumerator/lazy/reject_spec.rb78
-rw-r--r--spec/ruby/core/enumerator/lazy/select_spec.rb47
-rw-r--r--spec/ruby/core/enumerator/lazy/shared/collect.rb62
-rw-r--r--spec/ruby/core/enumerator/lazy/shared/collect_concat.rb78
-rw-r--r--spec/ruby/core/enumerator/lazy/shared/select.rb66
-rw-r--r--spec/ruby/core/enumerator/lazy/shared/to_enum.rb55
-rw-r--r--spec/ruby/core/enumerator/lazy/slice_after_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/slice_before_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/slice_when_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/take_spec.rb66
-rw-r--r--spec/ruby/core/enumerator/lazy/take_while_spec.rb60
-rw-r--r--spec/ruby/core/enumerator/lazy/to_enum_spec.rb8
-rw-r--r--spec/ruby/core/enumerator/lazy/uniq_spec.rb74
-rw-r--r--spec/ruby/core/enumerator/lazy/with_index_spec.rb36
-rw-r--r--spec/ruby/core/enumerator/lazy/zip_spec.rb86
-rw-r--r--spec/ruby/core/enumerator/new_spec.rb119
-rw-r--r--spec/ruby/core/enumerator/next_spec.rb38
-rw-r--r--spec/ruby/core/enumerator/next_values_spec.rb55
-rw-r--r--spec/ruby/core/enumerator/peek_spec.rb36
-rw-r--r--spec/ruby/core/enumerator/peek_values_spec.rb57
-rw-r--r--spec/ruby/core/enumerator/plus_spec.rb33
-rw-r--r--spec/ruby/core/enumerator/produce_spec.rb34
-rw-r--r--spec/ruby/core/enumerator/rewind_spec.rb70
-rw-r--r--spec/ruby/core/enumerator/size_spec.rb26
-rw-r--r--spec/ruby/core/enumerator/to_enum_spec.rb6
-rw-r--r--spec/ruby/core/enumerator/with_index_spec.rb72
-rw-r--r--spec/ruby/core/enumerator/with_object_spec.rb6
-rw-r--r--spec/ruby/core/enumerator/yielder/append_spec.rb35
-rw-r--r--spec/ruby/core/enumerator/yielder/initialize_spec.rb18
-rw-r--r--spec/ruby/core/enumerator/yielder/to_proc_spec.rb16
-rw-r--r--spec/ruby/core/enumerator/yielder/yield_spec.rb33
-rw-r--r--spec/ruby/core/env/assoc_spec.rb31
-rw-r--r--spec/ruby/core/env/clear_spec.rb20
-rw-r--r--spec/ruby/core/env/delete_if_spec.rb54
-rw-r--r--spec/ruby/core/env/delete_spec.rb49
-rw-r--r--spec/ruby/core/env/each_key_spec.rb34
-rw-r--r--spec/ruby/core/env/each_pair_spec.rb6
-rw-r--r--spec/ruby/core/env/each_spec.rb6
-rw-r--r--spec/ruby/core/env/each_value_spec.rb34
-rw-r--r--spec/ruby/core/env/element_reference_spec.rb76
-rw-r--r--spec/ruby/core/env/element_set_spec.rb6
-rw-r--r--spec/ruby/core/env/empty_spec.rb23
-rw-r--r--spec/ruby/core/env/except_spec.rb36
-rw-r--r--spec/ruby/core/env/fetch_spec.rb63
-rw-r--r--spec/ruby/core/env/filter_spec.rb13
-rw-r--r--spec/ruby/core/env/fixtures/common.rb9
-rw-r--r--spec/ruby/core/env/has_key_spec.rb6
-rw-r--r--spec/ruby/core/env/has_value_spec.rb6
-rw-r--r--spec/ruby/core/env/include_spec.rb6
-rw-r--r--spec/ruby/core/env/index_spec.rb14
-rw-r--r--spec/ruby/core/env/indexes_spec.rb1
-rw-r--r--spec/ruby/core/env/indices_spec.rb1
-rw-r--r--spec/ruby/core/env/inspect_spec.rb11
-rw-r--r--spec/ruby/core/env/invert_spec.rb16
-rw-r--r--spec/ruby/core/env/keep_if_spec.rb54
-rw-r--r--spec/ruby/core/env/key_spec.rb11
-rw-r--r--spec/ruby/core/env/keys_spec.rb14
-rw-r--r--spec/ruby/core/env/length_spec.rb6
-rw-r--r--spec/ruby/core/env/member_spec.rb6
-rw-r--r--spec/ruby/core/env/merge_spec.rb6
-rw-r--r--spec/ruby/core/env/rassoc_spec.rb42
-rw-r--r--spec/ruby/core/env/rehash_spec.rb7
-rw-r--r--spec/ruby/core/env/reject_spec.rb101
-rw-r--r--spec/ruby/core/env/replace_spec.rb51
-rw-r--r--spec/ruby/core/env/select_spec.rb13
-rw-r--r--spec/ruby/core/env/shared/each.rb65
-rw-r--r--spec/ruby/core/env/shared/include.rb23
-rw-r--r--spec/ruby/core/env/shared/key.rb31
-rw-r--r--spec/ruby/core/env/shared/length.rb13
-rw-r--r--spec/ruby/core/env/shared/select.rb61
-rw-r--r--spec/ruby/core/env/shared/store.rb60
-rw-r--r--spec/ruby/core/env/shared/to_hash.rb33
-rw-r--r--spec/ruby/core/env/shared/update.rb106
-rw-r--r--spec/ruby/core/env/shared/value.rb22
-rw-r--r--spec/ruby/core/env/shift_spec.rb47
-rw-r--r--spec/ruby/core/env/size_spec.rb6
-rw-r--r--spec/ruby/core/env/slice_spec.rb27
-rw-r--r--spec/ruby/core/env/spec_helper.rb26
-rw-r--r--spec/ruby/core/env/store_spec.rb6
-rw-r--r--spec/ruby/core/env/to_a_spec.rb18
-rw-r--r--spec/ruby/core/env/to_h_spec.rb58
-rw-r--r--spec/ruby/core/env/to_hash_spec.rb6
-rw-r--r--spec/ruby/core/env/to_s_spec.rb7
-rw-r--r--spec/ruby/core/env/update_spec.rb6
-rw-r--r--spec/ruby/core/env/value_spec.rb6
-rw-r--r--spec/ruby/core/env/values_at_spec.rb38
-rw-r--r--spec/ruby/core/env/values_spec.rb14
-rw-r--r--spec/ruby/core/exception/backtrace_locations_spec.rb39
-rw-r--r--spec/ruby/core/exception/backtrace_spec.rb93
-rw-r--r--spec/ruby/core/exception/case_compare_spec.rb39
-rw-r--r--spec/ruby/core/exception/cause_spec.rb56
-rw-r--r--spec/ruby/core/exception/dup_spec.rb74
-rw-r--r--spec/ruby/core/exception/equal_value_spec.rb68
-rw-r--r--spec/ruby/core/exception/errno_spec.rb67
-rw-r--r--spec/ruby/core/exception/exception_spec.rb69
-rw-r--r--spec/ruby/core/exception/exit_value_spec.rb13
-rw-r--r--spec/ruby/core/exception/fixtures/common.rb95
-rw-r--r--spec/ruby/core/exception/frozen_error_spec.rb22
-rw-r--r--spec/ruby/core/exception/full_message_spec.rb106
-rw-r--r--spec/ruby/core/exception/hierarchy_spec.rb62
-rw-r--r--spec/ruby/core/exception/inspect_spec.rb24
-rw-r--r--spec/ruby/core/exception/interrupt_spec.rb60
-rw-r--r--spec/ruby/core/exception/io_error_spec.rb45
-rw-r--r--spec/ruby/core/exception/key_error_spec.rb19
-rw-r--r--spec/ruby/core/exception/load_error_spec.rb21
-rw-r--r--spec/ruby/core/exception/message_spec.rb27
-rw-r--r--spec/ruby/core/exception/name_error_spec.rb28
-rw-r--r--spec/ruby/core/exception/name_spec.rb43
-rw-r--r--spec/ruby/core/exception/new_spec.rb7
-rw-r--r--spec/ruby/core/exception/no_method_error_spec.rb136
-rw-r--r--spec/ruby/core/exception/reason_spec.rb13
-rw-r--r--spec/ruby/core/exception/receiver_spec.rb58
-rw-r--r--spec/ruby/core/exception/result_spec.rb21
-rw-r--r--spec/ruby/core/exception/set_backtrace_spec.rb56
-rw-r--r--spec/ruby/core/exception/shared/new.rb18
-rw-r--r--spec/ruby/core/exception/signal_exception_spec.rb123
-rw-r--r--spec/ruby/core/exception/signm_spec.rb9
-rw-r--r--spec/ruby/core/exception/signo_spec.rb9
-rw-r--r--spec/ruby/core/exception/standard_error_spec.rb23
-rw-r--r--spec/ruby/core/exception/status_spec.rb9
-rw-r--r--spec/ruby/core/exception/success_spec.rb15
-rw-r--r--spec/ruby/core/exception/system_call_error_spec.rb143
-rw-r--r--spec/ruby/core/exception/system_exit_spec.rb59
-rw-r--r--spec/ruby/core/exception/to_s_spec.rb37
-rw-r--r--spec/ruby/core/exception/top_level_spec.rb45
-rw-r--r--spec/ruby/core/exception/uncaught_throw_error_spec.rb12
-rw-r--r--spec/ruby/core/false/and_spec.rb11
-rw-r--r--spec/ruby/core/false/case_compare_spec.rb14
-rw-r--r--spec/ruby/core/false/dup_spec.rb7
-rw-r--r--spec/ruby/core/false/falseclass_spec.rb15
-rw-r--r--spec/ruby/core/false/inspect_spec.rb7
-rw-r--r--spec/ruby/core/false/or_spec.rb11
-rw-r--r--spec/ruby/core/false/to_s_spec.rb15
-rw-r--r--spec/ruby/core/false/xor_spec.rb11
-rw-r--r--spec/ruby/core/fiber/blocking_spec.rb79
-rw-r--r--spec/ruby/core/fiber/fixtures/classes.rb12
-rw-r--r--spec/ruby/core/fiber/new_spec.rb39
-rw-r--r--spec/ruby/core/fiber/raise_spec.rb119
-rw-r--r--spec/ruby/core/fiber/resume_spec.rb79
-rw-r--r--spec/ruby/core/fiber/shared/blocking.rb41
-rw-r--r--spec/ruby/core/fiber/storage_spec.rb117
-rw-r--r--spec/ruby/core/fiber/yield_spec.rb49
-rw-r--r--spec/ruby/core/file/absolute_path_spec.rb94
-rw-r--r--spec/ruby/core/file/atime_spec.rb57
-rw-r--r--spec/ruby/core/file/basename_spec.rb183
-rw-r--r--spec/ruby/core/file/birthtime_spec.rb60
-rw-r--r--spec/ruby/core/file/blockdev_spec.rb6
-rw-r--r--spec/ruby/core/file/chardev_spec.rb6
-rw-r--r--spec/ruby/core/file/chmod_spec.rb185
-rw-r--r--spec/ruby/core/file/chown_spec.rb144
-rw-r--r--spec/ruby/core/file/constants/constants_spec.rb31
-rw-r--r--spec/ruby/core/file/constants_spec.rb141
-rw-r--r--spec/ruby/core/file/ctime_spec.rb51
-rw-r--r--spec/ruby/core/file/delete_spec.rb6
-rw-r--r--spec/ruby/core/file/directory_spec.rb10
-rw-r--r--spec/ruby/core/file/dirname_spec.rb124
-rw-r--r--spec/ruby/core/file/empty_spec.rb13
-rw-r--r--spec/ruby/core/file/executable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/executable_spec.rb7
-rw-r--r--spec/ruby/core/file/exist_spec.rb6
-rw-r--r--spec/ruby/core/file/expand_path_spec.rb265
-rw-r--r--spec/ruby/core/file/extname_spec.rb76
-rw-r--r--spec/ruby/core/file/file_spec.rb16
-rw-r--r--spec/ruby/core/file/fixtures/common.rb22
-rw-r--r--spec/ruby/core/file/fixtures/do_not_remove1
-rw-r--r--spec/ruby/core/file/fixtures/file_types.rb66
-rw-r--r--spec/ruby/core/file/flock_spec.rb106
-rw-r--r--spec/ruby/core/file/fnmatch_spec.rb10
-rw-r--r--spec/ruby/core/file/ftype_spec.rb82
-rw-r--r--spec/ruby/core/file/grpowned_spec.rb10
-rw-r--r--spec/ruby/core/file/identical_spec.rb6
-rw-r--r--spec/ruby/core/file/initialize_spec.rb19
-rw-r--r--spec/ruby/core/file/inspect_spec.rb17
-rw-r--r--spec/ruby/core/file/join_spec.rb148
-rw-r--r--spec/ruby/core/file/lchmod_spec.rb32
-rw-r--r--spec/ruby/core/file/lchown_spec.rb59
-rw-r--r--spec/ruby/core/file/link_spec.rb39
-rw-r--r--spec/ruby/core/file/lstat_spec.rb33
-rw-r--r--spec/ruby/core/file/lutime_spec.rb38
-rw-r--r--spec/ruby/core/file/mkfifo_spec.rb51
-rw-r--r--spec/ruby/core/file/mtime_spec.rb53
-rw-r--r--spec/ruby/core/file/new_spec.rb162
-rw-r--r--spec/ruby/core/file/null_spec.rb15
-rw-r--r--spec/ruby/core/file/open_spec.rb704
-rw-r--r--spec/ruby/core/file/owned_spec.rb35
-rw-r--r--spec/ruby/core/file/path_spec.rb40
-rw-r--r--spec/ruby/core/file/pipe_spec.rb32
-rw-r--r--spec/ruby/core/file/printf_spec.rb18
-rw-r--r--spec/ruby/core/file/read_spec.rb6
-rw-r--r--spec/ruby/core/file/readable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/readable_spec.rb7
-rw-r--r--spec/ruby/core/file/readlink_spec.rb86
-rw-r--r--spec/ruby/core/file/realdirpath_spec.rb104
-rw-r--r--spec/ruby/core/file/realpath_spec.rb94
-rw-r--r--spec/ruby/core/file/rename_spec.rb37
-rw-r--r--spec/ruby/core/file/reopen_spec.rb32
-rw-r--r--spec/ruby/core/file/setgid_spec.rb36
-rw-r--r--spec/ruby/core/file/setuid_spec.rb38
-rw-r--r--spec/ruby/core/file/shared/fnmatch.rb249
-rw-r--r--spec/ruby/core/file/shared/open.rb12
-rw-r--r--spec/ruby/core/file/shared/path.rb94
-rw-r--r--spec/ruby/core/file/shared/read.rb15
-rw-r--r--spec/ruby/core/file/shared/stat.rb32
-rw-r--r--spec/ruby/core/file/shared/unlink.rb61
-rw-r--r--spec/ruby/core/file/size_spec.rb119
-rw-r--r--spec/ruby/core/file/socket_spec.rb42
-rw-r--r--spec/ruby/core/file/split_spec.rb64
-rw-r--r--spec/ruby/core/file/stat/atime_spec.rb18
-rw-r--r--spec/ruby/core/file/stat/birthtime_spec.rb27
-rw-r--r--spec/ruby/core/file/stat/blksize_spec.rb27
-rw-r--r--spec/ruby/core/file/stat/blockdev_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/blocks_spec.rb27
-rw-r--r--spec/ruby/core/file/stat/chardev_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/comparison_spec.rb66
-rw-r--r--spec/ruby/core/file/stat/ctime_spec.rb18
-rw-r--r--spec/ruby/core/file/stat/dev_major_spec.rb23
-rw-r--r--spec/ruby/core/file/stat/dev_minor_spec.rb23
-rw-r--r--spec/ruby/core/file/stat/dev_spec.rb15
-rw-r--r--spec/ruby/core/file/stat/directory_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/executable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/executable_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/file_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/fixtures/classes.rb5
-rw-r--r--spec/ruby/core/file/stat/ftype_spec.rb64
-rw-r--r--spec/ruby/core/file/stat/gid_spec.rb19
-rw-r--r--spec/ruby/core/file/stat/grpowned_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/ino_spec.rb28
-rw-r--r--spec/ruby/core/file/stat/inspect_spec.rb26
-rw-r--r--spec/ruby/core/file/stat/mode_spec.rb19
-rw-r--r--spec/ruby/core/file/stat/mtime_spec.rb18
-rw-r--r--spec/ruby/core/file/stat/new_spec.rb32
-rw-r--r--spec/ruby/core/file/stat/nlink_spec.rb21
-rw-r--r--spec/ruby/core/file/stat/owned_spec.rb33
-rw-r--r--spec/ruby/core/file/stat/pipe_spec.rb32
-rw-r--r--spec/ruby/core/file/stat/rdev_major_spec.rb31
-rw-r--r--spec/ruby/core/file/stat/rdev_minor_spec.rb31
-rw-r--r--spec/ruby/core/file/stat/rdev_spec.rb15
-rw-r--r--spec/ruby/core/file/stat/readable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/readable_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/setgid_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/setuid_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/size_spec.rb21
-rw-r--r--spec/ruby/core/file/stat/socket_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/sticky_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/symlink_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/uid_spec.rb18
-rw-r--r--spec/ruby/core/file/stat/world_readable_spec.rb11
-rw-r--r--spec/ruby/core/file/stat/world_writable_spec.rb11
-rw-r--r--spec/ruby/core/file/stat/writable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/writable_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/zero_spec.rb7
-rw-r--r--spec/ruby/core/file/stat_spec.rb55
-rw-r--r--spec/ruby/core/file/sticky_spec.rb50
-rw-r--r--spec/ruby/core/file/symlink_spec.rb57
-rw-r--r--spec/ruby/core/file/to_path_spec.rb6
-rw-r--r--spec/ruby/core/file/truncate_spec.rb177
-rw-r--r--spec/ruby/core/file/umask_spec.rb57
-rw-r--r--spec/ruby/core/file/unlink_spec.rb6
-rw-r--r--spec/ruby/core/file/utime_spec.rb104
-rw-r--r--spec/ruby/core/file/world_readable_spec.rb12
-rw-r--r--spec/ruby/core/file/world_writable_spec.rb12
-rw-r--r--spec/ruby/core/file/writable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/writable_spec.rb7
-rw-r--r--spec/ruby/core/file/zero_spec.rb13
-rw-r--r--spec/ruby/core/filetest/blockdev_spec.rb6
-rw-r--r--spec/ruby/core/filetest/chardev_spec.rb6
-rw-r--r--spec/ruby/core/filetest/directory_spec.rb10
-rw-r--r--spec/ruby/core/filetest/executable_real_spec.rb7
-rw-r--r--spec/ruby/core/filetest/executable_spec.rb7
-rw-r--r--spec/ruby/core/filetest/exist_spec.rb6
-rw-r--r--spec/ruby/core/filetest/file_spec.rb10
-rw-r--r--spec/ruby/core/filetest/grpowned_spec.rb10
-rw-r--r--spec/ruby/core/filetest/identical_spec.rb6
-rw-r--r--spec/ruby/core/filetest/owned_spec.rb6
-rw-r--r--spec/ruby/core/filetest/pipe_spec.rb6
-rw-r--r--spec/ruby/core/filetest/readable_real_spec.rb7
-rw-r--r--spec/ruby/core/filetest/readable_spec.rb7
-rw-r--r--spec/ruby/core/filetest/setgid_spec.rb6
-rw-r--r--spec/ruby/core/filetest/setuid_spec.rb6
-rw-r--r--spec/ruby/core/filetest/size_spec.rb34
-rw-r--r--spec/ruby/core/filetest/socket_spec.rb6
-rw-r--r--spec/ruby/core/filetest/sticky_spec.rb7
-rw-r--r--spec/ruby/core/filetest/symlink_spec.rb10
-rw-r--r--spec/ruby/core/filetest/world_readable_spec.rb5
-rw-r--r--spec/ruby/core/filetest/world_writable_spec.rb5
-rw-r--r--spec/ruby/core/filetest/writable_real_spec.rb7
-rw-r--r--spec/ruby/core/filetest/writable_spec.rb7
-rw-r--r--spec/ruby/core/filetest/zero_spec.rb13
-rw-r--r--spec/ruby/core/float/abs_spec.rb6
-rw-r--r--spec/ruby/core/float/angle_spec.rb6
-rw-r--r--spec/ruby/core/float/arg_spec.rb6
-rw-r--r--spec/ruby/core/float/case_compare_spec.rb6
-rw-r--r--spec/ruby/core/float/ceil_spec.rb21
-rw-r--r--spec/ruby/core/float/coerce_spec.rb18
-rw-r--r--spec/ruby/core/float/comparison_spec.rb113
-rw-r--r--spec/ruby/core/float/constants_spec.rb55
-rw-r--r--spec/ruby/core/float/denominator_spec.rb29
-rw-r--r--spec/ruby/core/float/divide_spec.rb43
-rw-r--r--spec/ruby/core/float/divmod_spec.rb43
-rw-r--r--spec/ruby/core/float/dup_spec.rb8
-rw-r--r--spec/ruby/core/float/eql_spec.rb16
-rw-r--r--spec/ruby/core/float/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/float/exponent_spec.rb15
-rw-r--r--spec/ruby/core/float/fdiv_spec.rb6
-rw-r--r--spec/ruby/core/float/finite_spec.rb19
-rw-r--r--spec/ruby/core/float/fixtures/classes.rb4
-rw-r--r--spec/ruby/core/float/fixtures/coerce.rb15
-rw-r--r--spec/ruby/core/float/float_spec.rb19
-rw-r--r--spec/ruby/core/float/floor_spec.rb21
-rw-r--r--spec/ruby/core/float/gt_spec.rb38
-rw-r--r--spec/ruby/core/float/gte_spec.rb38
-rw-r--r--spec/ruby/core/float/hash_spec.rb11
-rw-r--r--spec/ruby/core/float/infinite_spec.rb19
-rw-r--r--spec/ruby/core/float/inspect_spec.rb6
-rw-r--r--spec/ruby/core/float/lt_spec.rb38
-rw-r--r--spec/ruby/core/float/lte_spec.rb39
-rw-r--r--spec/ruby/core/float/magnitude_spec.rb5
-rw-r--r--spec/ruby/core/float/minus_spec.rb12
-rw-r--r--spec/ruby/core/float/modulo_spec.rb10
-rw-r--r--spec/ruby/core/float/multiply_spec.rb17
-rw-r--r--spec/ruby/core/float/nan_spec.rb9
-rw-r--r--spec/ruby/core/float/negative_spec.rb33
-rw-r--r--spec/ruby/core/float/next_float_spec.rb49
-rw-r--r--spec/ruby/core/float/numerator_spec.rb39
-rw-r--r--spec/ruby/core/float/phase_spec.rb6
-rw-r--r--spec/ruby/core/float/plus_spec.rb12
-rw-r--r--spec/ruby/core/float/positive_spec.rb33
-rw-r--r--spec/ruby/core/float/prev_float_spec.rb49
-rw-r--r--spec/ruby/core/float/quo_spec.rb6
-rw-r--r--spec/ruby/core/float/rationalize_spec.rb43
-rw-r--r--spec/ruby/core/float/round_spec.rb194
-rw-r--r--spec/ruby/core/float/shared/abs.rb21
-rw-r--r--spec/ruby/core/float/shared/arg.rb36
-rw-r--r--spec/ruby/core/float/shared/arithmetic_exception_in_coerce.rb11
-rw-r--r--spec/ruby/core/float/shared/comparison_exception_in_coerce.rb11
-rw-r--r--spec/ruby/core/float/shared/equal.rb38
-rw-r--r--spec/ruby/core/float/shared/modulo.rb48
-rw-r--r--spec/ruby/core/float/shared/quo.rb59
-rw-r--r--spec/ruby/core/float/shared/to_i.rb14
-rw-r--r--spec/ruby/core/float/shared/to_s.rb308
-rw-r--r--spec/ruby/core/float/to_f_spec.rb9
-rw-r--r--spec/ruby/core/float/to_i_spec.rb6
-rw-r--r--spec/ruby/core/float/to_int_spec.rb6
-rw-r--r--spec/ruby/core/float/to_r_spec.rb5
-rw-r--r--spec/ruby/core/float/to_s_spec.rb6
-rw-r--r--spec/ruby/core/float/truncate_spec.rb14
-rw-r--r--spec/ruby/core/float/uminus_spec.rb28
-rw-r--r--spec/ruby/core/float/uplus_spec.rb9
-rw-r--r--spec/ruby/core/float/zero_spec.rb9
-rw-r--r--spec/ruby/core/gc/auto_compact_spec.rb26
-rw-r--r--spec/ruby/core/gc/count_spec.rb17
-rw-r--r--spec/ruby/core/gc/disable_spec.rb18
-rw-r--r--spec/ruby/core/gc/enable_spec.rb13
-rw-r--r--spec/ruby/core/gc/garbage_collect_spec.rb15
-rw-r--r--spec/ruby/core/gc/measure_total_time_spec.rb19
-rw-r--r--spec/ruby/core/gc/profiler/clear_spec.rb5
-rw-r--r--spec/ruby/core/gc/profiler/disable_spec.rb16
-rw-r--r--spec/ruby/core/gc/profiler/enable_spec.rb17
-rw-r--r--spec/ruby/core/gc/profiler/enabled_spec.rb21
-rw-r--r--spec/ruby/core/gc/profiler/report_spec.rb5
-rw-r--r--spec/ruby/core/gc/profiler/result_spec.rb7
-rw-r--r--spec/ruby/core/gc/profiler/total_time_spec.rb7
-rw-r--r--spec/ruby/core/gc/start_spec.rb12
-rw-r--r--spec/ruby/core/gc/stat_spec.rb62
-rw-r--r--spec/ruby/core/gc/stress_spec.rb27
-rw-r--r--spec/ruby/core/gc/total_time_spec.rb15
-rw-r--r--spec/ruby/core/hash/allocate_spec.rb15
-rw-r--r--spec/ruby/core/hash/any_spec.rb30
-rw-r--r--spec/ruby/core/hash/assoc_spec.rb50
-rw-r--r--spec/ruby/core/hash/clear_spec.rb32
-rw-r--r--spec/ruby/core/hash/clone_spec.rb12
-rw-r--r--spec/ruby/core/hash/compact_spec.rb59
-rw-r--r--spec/ruby/core/hash/compare_by_identity_spec.rb138
-rw-r--r--spec/ruby/core/hash/constructor_spec.rb110
-rw-r--r--spec/ruby/core/hash/deconstruct_keys_spec.rb23
-rw-r--r--spec/ruby/core/hash/default_proc_spec.rb80
-rw-r--r--spec/ruby/core/hash/default_spec.rb46
-rw-r--r--spec/ruby/core/hash/delete_if_spec.rb44
-rw-r--r--spec/ruby/core/hash/delete_spec.rb44
-rw-r--r--spec/ruby/core/hash/dig_spec.rb66
-rw-r--r--spec/ruby/core/hash/each_key_spec.rb23
-rw-r--r--spec/ruby/core/hash/each_pair_spec.rb11
-rw-r--r--spec/ruby/core/hash/each_spec.rb11
-rw-r--r--spec/ruby/core/hash/each_value_spec.rb23
-rw-r--r--spec/ruby/core/hash/element_reference_spec.rb134
-rw-r--r--spec/ruby/core/hash/element_set_spec.rb7
-rw-r--r--spec/ruby/core/hash/empty_spec.rb15
-rw-r--r--spec/ruby/core/hash/eql_spec.rb9
-rw-r--r--spec/ruby/core/hash/equal_value_spec.rb18
-rw-r--r--spec/ruby/core/hash/except_spec.rb34
-rw-r--r--spec/ruby/core/hash/fetch_spec.rb44
-rw-r--r--spec/ruby/core/hash/fetch_values_spec.rb35
-rw-r--r--spec/ruby/core/hash/filter_spec.rb10
-rw-r--r--spec/ruby/core/hash/fixtures/classes.rb75
-rw-r--r--spec/ruby/core/hash/fixtures/name.rb30
-rw-r--r--spec/ruby/core/hash/flatten_spec.rb62
-rw-r--r--spec/ruby/core/hash/gt_spec.rb42
-rw-r--r--spec/ruby/core/hash/gte_spec.rb42
-rw-r--r--spec/ruby/core/hash/has_key_spec.rb7
-rw-r--r--spec/ruby/core/hash/has_value_spec.rb7
-rw-r--r--spec/ruby/core/hash/hash_spec.rb53
-rw-r--r--spec/ruby/core/hash/include_spec.rb7
-rw-r--r--spec/ruby/core/hash/index_spec.rb9
-rw-r--r--spec/ruby/core/hash/initialize_spec.rb61
-rw-r--r--spec/ruby/core/hash/inspect_spec.rb7
-rw-r--r--spec/ruby/core/hash/invert_spec.rb27
-rw-r--r--spec/ruby/core/hash/keep_if_spec.rb37
-rw-r--r--spec/ruby/core/hash/key_spec.rb12
-rw-r--r--spec/ruby/core/hash/keys_spec.rb23
-rw-r--r--spec/ruby/core/hash/length_spec.rb7
-rw-r--r--spec/ruby/core/hash/lt_spec.rb42
-rw-r--r--spec/ruby/core/hash/lte_spec.rb42
-rw-r--r--spec/ruby/core/hash/member_spec.rb7
-rw-r--r--spec/ruby/core/hash/merge_spec.rb100
-rw-r--r--spec/ruby/core/hash/new_spec.rb36
-rw-r--r--spec/ruby/core/hash/rassoc_spec.rb42
-rw-r--r--spec/ruby/core/hash/rehash_spec.rb84
-rw-r--r--spec/ruby/core/hash/reject_spec.rb95
-rw-r--r--spec/ruby/core/hash/replace_spec.rb7
-rw-r--r--spec/ruby/core/hash/ruby2_keywords_hash_spec.rb59
-rw-r--r--spec/ruby/core/hash/select_spec.rb10
-rw-r--r--spec/ruby/core/hash/shared/comparison.rb15
-rw-r--r--spec/ruby/core/hash/shared/each.rb124
-rw-r--r--spec/ruby/core/hash/shared/eql.rb204
-rw-r--r--spec/ruby/core/hash/shared/equal.rb90
-rw-r--r--spec/ruby/core/hash/shared/greater_than.rb23
-rw-r--r--spec/ruby/core/hash/shared/index.rb37
-rw-r--r--spec/ruby/core/hash/shared/iteration.rb19
-rw-r--r--spec/ruby/core/hash/shared/key.rb38
-rw-r--r--spec/ruby/core/hash/shared/length.rb12
-rw-r--r--spec/ruby/core/hash/shared/less_than.rb23
-rw-r--r--spec/ruby/core/hash/shared/replace.rb51
-rw-r--r--spec/ruby/core/hash/shared/select.rb91
-rw-r--r--spec/ruby/core/hash/shared/store.rb115
-rw-r--r--spec/ruby/core/hash/shared/to_s.rb89
-rw-r--r--spec/ruby/core/hash/shared/update.rb76
-rw-r--r--spec/ruby/core/hash/shared/value.rb14
-rw-r--r--spec/ruby/core/hash/shared/values_at.rb9
-rw-r--r--spec/ruby/core/hash/shift_spec.rb101
-rw-r--r--spec/ruby/core/hash/size_spec.rb7
-rw-r--r--spec/ruby/core/hash/slice_spec.rb53
-rw-r--r--spec/ruby/core/hash/sort_spec.rb17
-rw-r--r--spec/ruby/core/hash/store_spec.rb7
-rw-r--r--spec/ruby/core/hash/to_a_spec.rb39
-rw-r--r--spec/ruby/core/hash/to_h_spec.rb72
-rw-r--r--spec/ruby/core/hash/to_hash_spec.rb14
-rw-r--r--spec/ruby/core/hash/to_proc_spec.rb99
-rw-r--r--spec/ruby/core/hash/to_s_spec.rb7
-rw-r--r--spec/ruby/core/hash/transform_keys_spec.rb147
-rw-r--r--spec/ruby/core/hash/transform_values_spec.rb97
-rw-r--r--spec/ruby/core/hash/try_convert_spec.rb50
-rw-r--r--spec/ruby/core/hash/update_spec.rb7
-rw-r--r--spec/ruby/core/hash/value_spec.rb7
-rw-r--r--spec/ruby/core/hash/values_at_spec.rb7
-rw-r--r--spec/ruby/core/hash/values_spec.rb10
-rw-r--r--spec/ruby/core/integer/abs_spec.rb6
-rw-r--r--spec/ruby/core/integer/allbits_spec.rb37
-rw-r--r--spec/ruby/core/integer/anybits_spec.rb36
-rw-r--r--spec/ruby/core/integer/bit_and_spec.rb97
-rw-r--r--spec/ruby/core/integer/bit_length_spec.rb76
-rw-r--r--spec/ruby/core/integer/bit_or_spec.rb89
-rw-r--r--spec/ruby/core/integer/bit_xor_spec.rb93
-rw-r--r--spec/ruby/core/integer/case_compare_spec.rb6
-rw-r--r--spec/ruby/core/integer/ceil_spec.rb19
-rw-r--r--spec/ruby/core/integer/chr_spec.rb257
-rw-r--r--spec/ruby/core/integer/coerce_spec.rb104
-rw-r--r--spec/ruby/core/integer/comparison_spec.rb177
-rw-r--r--spec/ruby/core/integer/complement_spec.rb20
-rw-r--r--spec/ruby/core/integer/constants_spec.rb41
-rw-r--r--spec/ruby/core/integer/denominator_spec.rb20
-rw-r--r--spec/ruby/core/integer/digits_spec.rb41
-rw-r--r--spec/ruby/core/integer/div_spec.rb146
-rw-r--r--spec/ruby/core/integer/divide_spec.rb89
-rw-r--r--spec/ruby/core/integer/divmod_spec.rb117
-rw-r--r--spec/ruby/core/integer/downto_spec.rb69
-rw-r--r--spec/ruby/core/integer/dup_spec.rb13
-rw-r--r--spec/ruby/core/integer/element_reference_spec.rb188
-rw-r--r--spec/ruby/core/integer/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/integer/even_spec.rb40
-rw-r--r--spec/ruby/core/integer/exponent_spec.rb7
-rw-r--r--spec/ruby/core/integer/fdiv_spec.rb100
-rw-r--r--spec/ruby/core/integer/fixtures/classes.rb4
-rw-r--r--spec/ruby/core/integer/floor_spec.rb19
-rw-r--r--spec/ruby/core/integer/gcd_spec.rb69
-rw-r--r--spec/ruby/core/integer/gcdlcm_spec.rb53
-rw-r--r--spec/ruby/core/integer/gt_spec.rb43
-rw-r--r--spec/ruby/core/integer/gte_spec.rb43
-rw-r--r--spec/ruby/core/integer/integer_spec.rb20
-rw-r--r--spec/ruby/core/integer/lcm_spec.rb58
-rw-r--r--spec/ruby/core/integer/left_shift_spec.rb211
-rw-r--r--spec/ruby/core/integer/lt_spec.rb45
-rw-r--r--spec/ruby/core/integer/lte_spec.rb53
-rw-r--r--spec/ruby/core/integer/magnitude_spec.rb6
-rw-r--r--spec/ruby/core/integer/minus_spec.rb43
-rw-r--r--spec/ruby/core/integer/modulo_spec.rb10
-rw-r--r--spec/ruby/core/integer/multiply_spec.rb45
-rw-r--r--spec/ruby/core/integer/next_spec.rb6
-rw-r--r--spec/ruby/core/integer/nobits_spec.rb36
-rw-r--r--spec/ruby/core/integer/numerator_spec.rb18
-rw-r--r--spec/ruby/core/integer/odd_spec.rb38
-rw-r--r--spec/ruby/core/integer/ord_spec.rb17
-rw-r--r--spec/ruby/core/integer/plus_spec.rb58
-rw-r--r--spec/ruby/core/integer/pow_spec.rb51
-rw-r--r--spec/ruby/core/integer/pred_spec.rb11
-rw-r--r--spec/ruby/core/integer/rationalize_spec.rb39
-rw-r--r--spec/ruby/core/integer/remainder_spec.rb51
-rw-r--r--spec/ruby/core/integer/right_shift_spec.rb233
-rw-r--r--spec/ruby/core/integer/round_spec.rb83
-rw-r--r--spec/ruby/core/integer/shared/abs.rb18
-rw-r--r--spec/ruby/core/integer/shared/arithmetic_coerce.rb31
-rw-r--r--spec/ruby/core/integer/shared/comparison_coerce.rb11
-rw-r--r--spec/ruby/core/integer/shared/equal.rb58
-rw-r--r--spec/ruby/core/integer/shared/exponent.rb126
-rw-r--r--spec/ruby/core/integer/shared/integer_rounding.rb19
-rw-r--r--spec/ruby/core/integer/shared/modulo.rb74
-rw-r--r--spec/ruby/core/integer/shared/next.rb25
-rw-r--r--spec/ruby/core/integer/shared/to_i.rb8
-rw-r--r--spec/ruby/core/integer/size_spec.rb34
-rw-r--r--spec/ruby/core/integer/sqrt_spec.rb31
-rw-r--r--spec/ruby/core/integer/succ_spec.rb6
-rw-r--r--spec/ruby/core/integer/times_spec.rb79
-rw-r--r--spec/ruby/core/integer/to_f_spec.rb23
-rw-r--r--spec/ruby/core/integer/to_i_spec.rb6
-rw-r--r--spec/ruby/core/integer/to_int_spec.rb6
-rw-r--r--spec/ruby/core/integer/to_r_spec.rb26
-rw-r--r--spec/ruby/core/integer/to_s_spec.rb95
-rw-r--r--spec/ruby/core/integer/truncate_spec.rb19
-rw-r--r--spec/ruby/core/integer/try_convert_spec.rb40
-rw-r--r--spec/ruby/core/integer/uminus_spec.rb30
-rw-r--r--spec/ruby/core/integer/upto_spec.rb69
-rw-r--r--spec/ruby/core/integer/zero_spec.rb21
-rw-r--r--spec/ruby/core/io/advise_spec.rb86
-rw-r--r--spec/ruby/core/io/binmode_spec.rb64
-rw-r--r--spec/ruby/core/io/binread_spec.rb47
-rw-r--r--spec/ruby/core/io/binwrite_spec.rb6
-rw-r--r--spec/ruby/core/io/bytes_spec.rb47
-rw-r--r--spec/ruby/core/io/chars_spec.rb30
-rw-r--r--spec/ruby/core/io/close_on_exec_spec.rb76
-rw-r--r--spec/ruby/core/io/close_read_spec.rb60
-rw-r--r--spec/ruby/core/io/close_spec.rb118
-rw-r--r--spec/ruby/core/io/close_write_spec.rb64
-rw-r--r--spec/ruby/core/io/closed_spec.rb20
-rw-r--r--spec/ruby/core/io/codepoints_spec.rb38
-rw-r--r--spec/ruby/core/io/constants_spec.rb19
-rw-r--r--spec/ruby/core/io/copy_stream_spec.rb322
-rw-r--r--spec/ruby/core/io/dup_spec.rb106
-rw-r--r--spec/ruby/core/io/each_byte_spec.rb57
-rw-r--r--spec/ruby/core/io/each_char_spec.rb12
-rw-r--r--spec/ruby/core/io/each_codepoint_spec.rb43
-rw-r--r--spec/ruby/core/io/each_line_spec.rb11
-rw-r--r--spec/ruby/core/io/each_spec.rb11
-rw-r--r--spec/ruby/core/io/eof_spec.rb107
-rw-r--r--spec/ruby/core/io/external_encoding_spec.rb225
-rw-r--r--spec/ruby/core/io/fcntl_spec.rb8
-rw-r--r--spec/ruby/core/io/fdatasync_spec.rb5
-rw-r--r--spec/ruby/core/io/fileno_spec.rb12
-rw-r--r--spec/ruby/core/io/fixtures/bom_UTF-16BE.txtbin0 -> 20 bytes-rw-r--r--spec/ruby/core/io/fixtures/bom_UTF-16LE.txtbin0 -> 20 bytes-rw-r--r--spec/ruby/core/io/fixtures/bom_UTF-32BE.txtbin0 -> 40 bytes-rw-r--r--spec/ruby/core/io/fixtures/bom_UTF-32LE.txtbin0 -> 40 bytes-rw-r--r--spec/ruby/core/io/fixtures/bom_UTF-8.txt1
-rw-r--r--spec/ruby/core/io/fixtures/classes.rb218
-rw-r--r--spec/ruby/core/io/fixtures/copy_in_out.rb2
-rw-r--r--spec/ruby/core/io/fixtures/copy_stream.txt6
-rw-r--r--spec/ruby/core/io/fixtures/empty.txt0
-rw-r--r--spec/ruby/core/io/fixtures/incomplete.txt1
-rw-r--r--spec/ruby/core/io/fixtures/lines.txt9
-rw-r--r--spec/ruby/core/io/fixtures/no_bom_UTF-8.txt1
-rw-r--r--spec/ruby/core/io/fixtures/numbered_lines.txt5
-rw-r--r--spec/ruby/core/io/fixtures/one_byte.txt1
-rw-r--r--spec/ruby/core/io/fixtures/read_binary.txt1
-rw-r--r--spec/ruby/core/io/fixtures/read_euc_jp.txt1
-rw-r--r--spec/ruby/core/io/fixtures/read_text.txt1
-rw-r--r--spec/ruby/core/io/fixtures/reopen_stdout.rb3
-rw-r--r--spec/ruby/core/io/flush_spec.rb37
-rw-r--r--spec/ruby/core/io/for_fd_spec.rb10
-rw-r--r--spec/ruby/core/io/foreach_spec.rb81
-rw-r--r--spec/ruby/core/io/fsync_spec.rb24
-rw-r--r--spec/ruby/core/io/getbyte_spec.rb42
-rw-r--r--spec/ruby/core/io/getc_spec.rb42
-rw-r--r--spec/ruby/core/io/gets_spec.rb315
-rw-r--r--spec/ruby/core/io/initialize_spec.rb49
-rw-r--r--spec/ruby/core/io/inspect_spec.rb23
-rw-r--r--spec/ruby/core/io/internal_encoding_spec.rb147
-rw-r--r--spec/ruby/core/io/io_spec.rb11
-rw-r--r--spec/ruby/core/io/ioctl_spec.rb32
-rw-r--r--spec/ruby/core/io/isatty_spec.rb6
-rw-r--r--spec/ruby/core/io/lineno_spec.rb136
-rw-r--r--spec/ruby/core/io/lines_spec.rb46
-rw-r--r--spec/ruby/core/io/new_spec.rb12
-rw-r--r--spec/ruby/core/io/nonblock_spec.rb70
-rw-r--r--spec/ruby/core/io/open_spec.rb86
-rw-r--r--spec/ruby/core/io/output_spec.rb27
-rw-r--r--spec/ruby/core/io/path_spec.rb14
-rw-r--r--spec/ruby/core/io/pid_spec.rb35
-rw-r--r--spec/ruby/core/io/pipe_spec.rb225
-rw-r--r--spec/ruby/core/io/popen_spec.rb271
-rw-r--r--spec/ruby/core/io/pos_spec.rb11
-rw-r--r--spec/ruby/core/io/pread_spec.rb50
-rw-r--r--spec/ruby/core/io/print_spec.rb66
-rw-r--r--spec/ruby/core/io/printf_spec.rb32
-rw-r--r--spec/ruby/core/io/putc_spec.rb11
-rw-r--r--spec/ruby/core/io/puts_spec.rb139
-rw-r--r--spec/ruby/core/io/pwrite_spec.rb43
-rw-r--r--spec/ruby/core/io/read_nonblock_spec.rb148
-rw-r--r--spec/ruby/core/io/read_spec.rb627
-rw-r--r--spec/ruby/core/io/readbyte_spec.rb24
-rw-r--r--spec/ruby/core/io/readchar_spec.rb110
-rw-r--r--spec/ruby/core/io/readline_spec.rb86
-rw-r--r--spec/ruby/core/io/readlines_spec.rb236
-rw-r--r--spec/ruby/core/io/readpartial_spec.rb111
-rw-r--r--spec/ruby/core/io/reopen_spec.rb313
-rw-r--r--spec/ruby/core/io/rewind_spec.rb53
-rw-r--r--spec/ruby/core/io/seek_spec.rb79
-rw-r--r--spec/ruby/core/io/select_spec.rb120
-rw-r--r--spec/ruby/core/io/set_encoding_by_bom_spec.rb262
-rw-r--r--spec/ruby/core/io/set_encoding_spec.rb238
-rw-r--r--spec/ruby/core/io/shared/binwrite.rb78
-rw-r--r--spec/ruby/core/io/shared/chars.rb73
-rw-r--r--spec/ruby/core/io/shared/codepoints.rb54
-rw-r--r--spec/ruby/core/io/shared/each.rb267
-rw-r--r--spec/ruby/core/io/shared/gets_ascii.rb19
-rw-r--r--spec/ruby/core/io/shared/new.rb404
-rw-r--r--spec/ruby/core/io/shared/pos.rb78
-rw-r--r--spec/ruby/core/io/shared/readlines.rb263
-rw-r--r--spec/ruby/core/io/shared/tty.rb24
-rw-r--r--spec/ruby/core/io/shared/write.rb99
-rw-r--r--spec/ruby/core/io/stat_spec.rb24
-rw-r--r--spec/ruby/core/io/sync_spec.rb64
-rw-r--r--spec/ruby/core/io/sysopen_spec.rb50
-rw-r--r--spec/ruby/core/io/sysread_spec.rb132
-rw-r--r--spec/ruby/core/io/sysseek_spec.rb49
-rw-r--r--spec/ruby/core/io/syswrite_spec.rb81
-rw-r--r--spec/ruby/core/io/tell_spec.rb7
-rw-r--r--spec/ruby/core/io/to_i_spec.rb12
-rw-r--r--spec/ruby/core/io/to_io_spec.rb21
-rw-r--r--spec/ruby/core/io/try_convert_spec.rb49
-rw-r--r--spec/ruby/core/io/tty_spec.rb6
-rw-r--r--spec/ruby/core/io/ungetbyte_spec.rb54
-rw-r--r--spec/ruby/core/io/ungetc_spec.rb148
-rw-r--r--spec/ruby/core/io/write_nonblock_spec.rb95
-rw-r--r--spec/ruby/core/io/write_spec.rb204
-rw-r--r--spec/ruby/core/kernel/Array_spec.rb97
-rw-r--r--spec/ruby/core/kernel/Complex_spec.rb272
-rw-r--r--spec/ruby/core/kernel/Float_spec.rb345
-rw-r--r--spec/ruby/core/kernel/Hash_spec.rb63
-rw-r--r--spec/ruby/core/kernel/Integer_spec.rb801
-rw-r--r--spec/ruby/core/kernel/Rational_spec.rb6
-rw-r--r--spec/ruby/core/kernel/String_spec.rb106
-rw-r--r--spec/ruby/core/kernel/__callee___spec.rb48
-rw-r--r--spec/ruby/core/kernel/__dir___spec.rb37
-rw-r--r--spec/ruby/core/kernel/__method___spec.rb40
-rw-r--r--spec/ruby/core/kernel/abort_spec.rb15
-rw-r--r--spec/ruby/core/kernel/at_exit_spec.rb70
-rw-r--r--spec/ruby/core/kernel/autoload_spec.rb175
-rw-r--r--spec/ruby/core/kernel/backtick_spec.rb84
-rw-r--r--spec/ruby/core/kernel/binding_spec.rb51
-rw-r--r--spec/ruby/core/kernel/block_given_spec.rb38
-rw-r--r--spec/ruby/core/kernel/caller_locations_spec.rb84
-rw-r--r--spec/ruby/core/kernel/caller_spec.rb70
-rw-r--r--spec/ruby/core/kernel/case_compare_spec.rb135
-rw-r--r--spec/ruby/core/kernel/catch_spec.rb127
-rw-r--r--spec/ruby/core/kernel/chomp_spec.rb65
-rw-r--r--spec/ruby/core/kernel/chop_spec.rb53
-rw-r--r--spec/ruby/core/kernel/class_spec.rb26
-rw-r--r--spec/ruby/core/kernel/clone_spec.rb209
-rw-r--r--spec/ruby/core/kernel/comparison_spec.rb31
-rw-r--r--spec/ruby/core/kernel/define_singleton_method_spec.rb114
-rw-r--r--spec/ruby/core/kernel/display_spec.rb6
-rw-r--r--spec/ruby/core/kernel/dup_spec.rb67
-rw-r--r--spec/ruby/core/kernel/enum_for_spec.rb5
-rw-r--r--spec/ruby/core/kernel/eql_spec.rb10
-rw-r--r--spec/ruby/core/kernel/equal_value_spec.rb15
-rw-r--r--spec/ruby/core/kernel/eval_spec.rb439
-rw-r--r--spec/ruby/core/kernel/exec_spec.rb18
-rw-r--r--spec/ruby/core/kernel/exit_spec.rb27
-rw-r--r--spec/ruby/core/kernel/extend_spec.rb79
-rw-r--r--spec/ruby/core/kernel/fail_spec.rb42
-rw-r--r--spec/ruby/core/kernel/fixtures/Complex.rb5
-rw-r--r--spec/ruby/core/kernel/fixtures/__callee__.rb34
-rw-r--r--spec/ruby/core/kernel/fixtures/__dir__.rb2
-rw-r--r--spec/ruby/core/kernel/fixtures/__method__.rb34
-rw-r--r--spec/ruby/core/kernel/fixtures/at_exit.rb3
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_b.rb5
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_d.rb5
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_from_included_module.rb9
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_from_included_module2.rb9
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_frozen.rb7
-rw-r--r--spec/ruby/core/kernel/fixtures/caller.rb7
-rw-r--r--spec/ruby/core/kernel/fixtures/caller_at_exit.rb7
-rw-r--r--spec/ruby/core/kernel/fixtures/caller_locations.rb7
-rw-r--r--spec/ruby/core/kernel/fixtures/chomp.rb4
-rw-r--r--spec/ruby/core/kernel/fixtures/chomp_f.rb4
-rw-r--r--spec/ruby/core/kernel/fixtures/chop.rb4
-rw-r--r--spec/ruby/core/kernel/fixtures/chop_f.rb4
-rw-r--r--spec/ruby/core/kernel/fixtures/classes.rb504
-rw-r--r--spec/ruby/core/kernel/fixtures/eval_locals.rb6
-rw-r--r--spec/ruby/core/kernel/fixtures/eval_return_with_lambda.rb12
-rw-r--r--spec/ruby/core/kernel/fixtures/eval_return_without_lambda.rb14
-rw-r--r--spec/ruby/core/kernel/fixtures/singleton_methods.rb13
-rw-r--r--spec/ruby/core/kernel/fixtures/test.rb362
-rw-r--r--spec/ruby/core/kernel/fixtures/warn_core_method.rb14
-rw-r--r--spec/ruby/core/kernel/fixtures/warn_require.rb1
-rw-r--r--spec/ruby/core/kernel/fixtures/warn_require_caller.rb2
-rw-r--r--spec/ruby/core/kernel/fork_spec.rb15
-rw-r--r--spec/ruby/core/kernel/format_spec.rb15
-rw-r--r--spec/ruby/core/kernel/freeze_spec.rb91
-rw-r--r--spec/ruby/core/kernel/frozen_spec.rb76
-rw-r--r--spec/ruby/core/kernel/gets_spec.rb17
-rw-r--r--spec/ruby/core/kernel/global_variables_spec.rb26
-rw-r--r--spec/ruby/core/kernel/gsub_spec.rb96
-rw-r--r--spec/ruby/core/kernel/initialize_clone_spec.rb28
-rw-r--r--spec/ruby/core/kernel/initialize_copy_spec.rb29
-rw-r--r--spec/ruby/core/kernel/initialize_dup_spec.rb20
-rw-r--r--spec/ruby/core/kernel/inspect_spec.rb31
-rw-r--r--spec/ruby/core/kernel/instance_of_spec.rb40
-rw-r--r--spec/ruby/core/kernel/instance_variable_defined_spec.rb41
-rw-r--r--spec/ruby/core/kernel/instance_variable_get_spec.rb111
-rw-r--r--spec/ruby/core/kernel/instance_variable_set_spec.rb105
-rw-r--r--spec/ruby/core/kernel/instance_variables_spec.rb40
-rw-r--r--spec/ruby/core/kernel/is_a_spec.rb6
-rw-r--r--spec/ruby/core/kernel/iterator_spec.rb14
-rw-r--r--spec/ruby/core/kernel/itself_spec.rb9
-rw-r--r--spec/ruby/core/kernel/kind_of_spec.rb6
-rw-r--r--spec/ruby/core/kernel/lambda_spec.rb150
-rw-r--r--spec/ruby/core/kernel/load_spec.rb40
-rw-r--r--spec/ruby/core/kernel/local_variables_spec.rb48
-rw-r--r--spec/ruby/core/kernel/loop_spec.rb79
-rw-r--r--spec/ruby/core/kernel/match_spec.rb30
-rw-r--r--spec/ruby/core/kernel/method_spec.rb61
-rw-r--r--spec/ruby/core/kernel/methods_spec.rb101
-rw-r--r--spec/ruby/core/kernel/nil_spec.rb12
-rw-r--r--spec/ruby/core/kernel/not_match_spec.rb21
-rw-r--r--spec/ruby/core/kernel/object_id_spec.rb6
-rw-r--r--spec/ruby/core/kernel/open_spec.rb167
-rw-r--r--spec/ruby/core/kernel/p_spec.rb85
-rw-r--r--spec/ruby/core/kernel/pp_spec.rb9
-rw-r--r--spec/ruby/core/kernel/print_spec.rb24
-rw-r--r--spec/ruby/core/kernel/printf_spec.rb63
-rw-r--r--spec/ruby/core/kernel/private_methods_spec.rb69
-rw-r--r--spec/ruby/core/kernel/proc_spec.rb58
-rw-r--r--spec/ruby/core/kernel/protected_methods_spec.rb69
-rw-r--r--spec/ruby/core/kernel/public_method_spec.rb32
-rw-r--r--spec/ruby/core/kernel/public_methods_spec.rb76
-rw-r--r--spec/ruby/core/kernel/public_send_spec.rb116
-rw-r--r--spec/ruby/core/kernel/putc_spec.rb39
-rw-r--r--spec/ruby/core/kernel/puts_spec.rb29
-rw-r--r--spec/ruby/core/kernel/raise_spec.rb57
-rw-r--r--spec/ruby/core/kernel/rand_spec.rb197
-rw-r--r--spec/ruby/core/kernel/readline_spec.rb12
-rw-r--r--spec/ruby/core/kernel/readlines_spec.rb12
-rw-r--r--spec/ruby/core/kernel/remove_instance_variable_spec.rb72
-rw-r--r--spec/ruby/core/kernel/require_relative_spec.rb437
-rw-r--r--spec/ruby/core/kernel/require_spec.rb34
-rw-r--r--spec/ruby/core/kernel/respond_to_missing_spec.rb100
-rw-r--r--spec/ruby/core/kernel/respond_to_spec.rb72
-rw-r--r--spec/ruby/core/kernel/select_spec.rb18
-rw-r--r--spec/ruby/core/kernel/send_spec.rb68
-rw-r--r--spec/ruby/core/kernel/set_trace_func_spec.rb12
-rw-r--r--spec/ruby/core/kernel/shared/dup_clone.rb91
-rw-r--r--spec/ruby/core/kernel/shared/kind_of.rb55
-rw-r--r--spec/ruby/core/kernel/shared/lambda.rb11
-rw-r--r--spec/ruby/core/kernel/shared/load.rb207
-rw-r--r--spec/ruby/core/kernel/shared/method.rb56
-rw-r--r--spec/ruby/core/kernel/shared/require.rb808
-rw-r--r--spec/ruby/core/kernel/shared/sprintf.rb993
-rw-r--r--spec/ruby/core/kernel/shared/sprintf_encoding.rb67
-rw-r--r--spec/ruby/core/kernel/shared/then.rb20
-rw-r--r--spec/ruby/core/kernel/singleton_class_spec.rb29
-rw-r--r--spec/ruby/core/kernel/singleton_method_spec.rb41
-rw-r--r--spec/ruby/core/kernel/singleton_methods_spec.rb192
-rw-r--r--spec/ruby/core/kernel/sleep_spec.rb62
-rw-r--r--spec/ruby/core/kernel/spawn_spec.rb25
-rw-r--r--spec/ruby/core/kernel/sprintf_spec.rb24
-rw-r--r--spec/ruby/core/kernel/srand_spec.rb73
-rw-r--r--spec/ruby/core/kernel/sub_spec.rb26
-rw-r--r--spec/ruby/core/kernel/syscall_spec.rb12
-rw-r--r--spec/ruby/core/kernel/system_spec.rb115
-rw-r--r--spec/ruby/core/kernel/taint_spec.rb19
-rw-r--r--spec/ruby/core/kernel/tainted_spec.rb21
-rw-r--r--spec/ruby/core/kernel/tap_spec.rb13
-rw-r--r--spec/ruby/core/kernel/test_spec.rb109
-rw-r--r--spec/ruby/core/kernel/then_spec.rb6
-rw-r--r--spec/ruby/core/kernel/throw_spec.rb80
-rw-r--r--spec/ruby/core/kernel/to_enum_spec.rb5
-rw-r--r--spec/ruby/core/kernel/to_s_spec.rb8
-rw-r--r--spec/ruby/core/kernel/trace_var_spec.rb54
-rw-r--r--spec/ruby/core/kernel/trap_spec.rb9
-rw-r--r--spec/ruby/core/kernel/trust_spec.rb20
-rw-r--r--spec/ruby/core/kernel/untaint_spec.rb20
-rw-r--r--spec/ruby/core/kernel/untrace_var_spec.rb12
-rw-r--r--spec/ruby/core/kernel/untrust_spec.rb19
-rw-r--r--spec/ruby/core/kernel/untrusted_spec.rb20
-rw-r--r--spec/ruby/core/kernel/warn_spec.rb309
-rw-r--r--spec/ruby/core/kernel/yield_self_spec.rb6
-rw-r--r--spec/ruby/core/main/define_method_spec.rb28
-rw-r--r--spec/ruby/core/main/fixtures/classes.rb26
-rw-r--r--spec/ruby/core/main/fixtures/string_refinement.rb7
-rw-r--r--spec/ruby/core/main/fixtures/string_refinement_user.rb11
-rw-r--r--spec/ruby/core/main/fixtures/using.rb1
-rw-r--r--spec/ruby/core/main/fixtures/using_in_main.rb5
-rw-r--r--spec/ruby/core/main/fixtures/using_in_method.rb5
-rw-r--r--spec/ruby/core/main/fixtures/wrapped_include.rb1
-rw-r--r--spec/ruby/core/main/include_spec.rb16
-rw-r--r--spec/ruby/core/main/private_spec.rb52
-rw-r--r--spec/ruby/core/main/public_spec.rb53
-rw-r--r--spec/ruby/core/main/ruby2_keywords_spec.rb9
-rw-r--r--spec/ruby/core/main/to_s_spec.rb7
-rw-r--r--spec/ruby/core/main/using_spec.rb152
-rw-r--r--spec/ruby/core/marshal/dump_spec.rb650
-rw-r--r--spec/ruby/core/marshal/fixtures/classes.rb4
-rw-r--r--spec/ruby/core/marshal/fixtures/marshal_data.rb420
-rw-r--r--spec/ruby/core/marshal/fixtures/random.dumpbin0 -> 2520 bytes-rw-r--r--spec/ruby/core/marshal/float_spec.rb77
-rw-r--r--spec/ruby/core/marshal/load_spec.rb6
-rw-r--r--spec/ruby/core/marshal/major_version_spec.rb7
-rw-r--r--spec/ruby/core/marshal/minor_version_spec.rb7
-rw-r--r--spec/ruby/core/marshal/restore_spec.rb6
-rw-r--r--spec/ruby/core/marshal/shared/load.rb987
-rw-r--r--spec/ruby/core/matchdata/allocate_spec.rb8
-rw-r--r--spec/ruby/core/matchdata/begin_spec.rb104
-rw-r--r--spec/ruby/core/matchdata/captures_spec.rb15
-rw-r--r--spec/ruby/core/matchdata/dup_spec.rb14
-rw-r--r--spec/ruby/core/matchdata/element_reference_spec.rb116
-rw-r--r--spec/ruby/core/matchdata/end_spec.rb104
-rw-r--r--spec/ruby/core/matchdata/eql_spec.rb6
-rw-r--r--spec/ruby/core/matchdata/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/matchdata/fixtures/classes.rb3
-rw-r--r--spec/ruby/core/matchdata/hash_spec.rb5
-rw-r--r--spec/ruby/core/matchdata/inspect_spec.rb23
-rw-r--r--spec/ruby/core/matchdata/length_spec.rb6
-rw-r--r--spec/ruby/core/matchdata/match_length_spec.rb34
-rw-r--r--spec/ruby/core/matchdata/match_spec.rb34
-rw-r--r--spec/ruby/core/matchdata/named_captures_spec.rb15
-rw-r--r--spec/ruby/core/matchdata/names_spec.rb33
-rw-r--r--spec/ruby/core/matchdata/offset_spec.rb30
-rw-r--r--spec/ruby/core/matchdata/post_match_spec.rb26
-rw-r--r--spec/ruby/core/matchdata/pre_match_spec.rb26
-rw-r--r--spec/ruby/core/matchdata/regexp_spec.rb24
-rw-r--r--spec/ruby/core/matchdata/shared/eql.rb26
-rw-r--r--spec/ruby/core/matchdata/shared/length.rb5
-rw-r--r--spec/ruby/core/matchdata/size_spec.rb6
-rw-r--r--spec/ruby/core/matchdata/string_spec.rb25
-rw-r--r--spec/ruby/core/matchdata/to_a_spec.rb15
-rw-r--r--spec/ruby/core/matchdata/to_s_spec.rb15
-rw-r--r--spec/ruby/core/matchdata/values_at_spec.rb76
-rw-r--r--spec/ruby/core/math/acos_spec.rb56
-rw-r--r--spec/ruby/core/math/acosh_spec.rb41
-rw-r--r--spec/ruby/core/math/asin_spec.rb48
-rw-r--r--spec/ruby/core/math/asinh_spec.rb42
-rw-r--r--spec/ruby/core/math/atan2_spec.rb54
-rw-r--r--spec/ruby/core/math/atan_spec.rb40
-rw-r--r--spec/ruby/core/math/atanh_spec.rb14
-rw-r--r--spec/ruby/core/math/cbrt_spec.rb27
-rw-r--r--spec/ruby/core/math/constants_spec.rb22
-rw-r--r--spec/ruby/core/math/cos_spec.rb42
-rw-r--r--spec/ruby/core/math/cosh_spec.rb37
-rw-r--r--spec/ruby/core/math/erf_spec.rb44
-rw-r--r--spec/ruby/core/math/erfc_spec.rb43
-rw-r--r--spec/ruby/core/math/exp_spec.rb37
-rw-r--r--spec/ruby/core/math/fixtures/classes.rb28
-rw-r--r--spec/ruby/core/math/frexp_spec.rb37
-rw-r--r--spec/ruby/core/math/gamma_spec.rb69
-rw-r--r--spec/ruby/core/math/hypot_spec.rb41
-rw-r--r--spec/ruby/core/math/ldexp_spec.rb60
-rw-r--r--spec/ruby/core/math/lgamma_spec.rb54
-rw-r--r--spec/ruby/core/math/log10_spec.rb43
-rw-r--r--spec/ruby/core/math/log2_spec.rb41
-rw-r--r--spec/ruby/core/math/log_spec.rb57
-rw-r--r--spec/ruby/core/math/sin_spec.rb39
-rw-r--r--spec/ruby/core/math/sinh_spec.rb37
-rw-r--r--spec/ruby/core/math/sqrt_spec.rb40
-rw-r--r--spec/ruby/core/math/tan_spec.rb42
-rw-r--r--spec/ruby/core/math/tanh_spec.rb39
-rw-r--r--spec/ruby/core/method/arity_spec.rb222
-rw-r--r--spec/ruby/core/method/call_spec.rb7
-rw-r--r--spec/ruby/core/method/case_compare_spec.rb7
-rw-r--r--spec/ruby/core/method/clone_spec.rb14
-rw-r--r--spec/ruby/core/method/compose_spec.rb100
-rw-r--r--spec/ruby/core/method/curry_spec.rb36
-rw-r--r--spec/ruby/core/method/element_reference_spec.rb7
-rw-r--r--spec/ruby/core/method/eql_spec.rb6
-rw-r--r--spec/ruby/core/method/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/method/fixtures/classes.rb246
-rw-r--r--spec/ruby/core/method/hash_spec.rb15
-rw-r--r--spec/ruby/core/method/inspect_spec.rb6
-rw-r--r--spec/ruby/core/method/name_spec.rb22
-rw-r--r--spec/ruby/core/method/original_name_spec.rb22
-rw-r--r--spec/ruby/core/method/owner_spec.rb32
-rw-r--r--spec/ruby/core/method/parameters_spec.rb270
-rw-r--r--spec/ruby/core/method/private_spec.rb21
-rw-r--r--spec/ruby/core/method/protected_spec.rb21
-rw-r--r--spec/ruby/core/method/public_spec.rb21
-rw-r--r--spec/ruby/core/method/receiver_spec.rb22
-rw-r--r--spec/ruby/core/method/shared/call.rb51
-rw-r--r--spec/ruby/core/method/shared/eql.rb94
-rw-r--r--spec/ruby/core/method/shared/to_s.rb85
-rw-r--r--spec/ruby/core/method/source_location_spec.rb113
-rw-r--r--spec/ruby/core/method/super_method_spec.rb66
-rw-r--r--spec/ruby/core/method/to_proc_spec.rb104
-rw-r--r--spec/ruby/core/method/to_s_spec.rb6
-rw-r--r--spec/ruby/core/method/unbind_spec.rb54
-rw-r--r--spec/ruby/core/module/alias_method_spec.rb173
-rw-r--r--spec/ruby/core/module/ancestors_spec.rb70
-rw-r--r--spec/ruby/core/module/append_features_spec.rb61
-rw-r--r--spec/ruby/core/module/attr_accessor_spec.rb119
-rw-r--r--spec/ruby/core/module/attr_reader_spec.rb80
-rw-r--r--spec/ruby/core/module/attr_spec.rb168
-rw-r--r--spec/ruby/core/module/attr_writer_spec.rb90
-rw-r--r--spec/ruby/core/module/autoload_spec.rb1012
-rw-r--r--spec/ruby/core/module/case_compare_spec.rb31
-rw-r--r--spec/ruby/core/module/class_eval_spec.rb7
-rw-r--r--spec/ruby/core/module/class_exec_spec.rb7
-rw-r--r--spec/ruby/core/module/class_variable_defined_spec.rb72
-rw-r--r--spec/ruby/core/module/class_variable_get_spec.rb76
-rw-r--r--spec/ruby/core/module/class_variable_set_spec.rb62
-rw-r--r--spec/ruby/core/module/class_variables_spec.rb34
-rw-r--r--spec/ruby/core/module/comparison_spec.rb36
-rw-r--r--spec/ruby/core/module/const_added_spec.rb125
-rw-r--r--spec/ruby/core/module/const_defined_spec.rb154
-rw-r--r--spec/ruby/core/module/const_get_spec.rb251
-rw-r--r--spec/ruby/core/module/const_missing_spec.rb36
-rw-r--r--spec/ruby/core/module/const_set_spec.rb142
-rw-r--r--spec/ruby/core/module/const_source_location_spec.rb225
-rw-r--r--spec/ruby/core/module/constants_spec.rb97
-rw-r--r--spec/ruby/core/module/define_method_spec.rb805
-rw-r--r--spec/ruby/core/module/define_singleton_method_spec.rb15
-rw-r--r--spec/ruby/core/module/deprecate_constant_spec.rb61
-rw-r--r--spec/ruby/core/module/eql_spec.rb7
-rw-r--r--spec/ruby/core/module/equal_spec.rb7
-rw-r--r--spec/ruby/core/module/equal_value_spec.rb7
-rw-r--r--spec/ruby/core/module/extend_object_spec.rb56
-rw-r--r--spec/ruby/core/module/extended_spec.rb44
-rw-r--r--spec/ruby/core/module/fixtures/autoload.rb1
-rw-r--r--spec/ruby/core/module/fixtures/autoload_abc.rb11
-rw-r--r--spec/ruby/core/module/fixtures/autoload_c.rb11
-rw-r--r--spec/ruby/core/module/fixtures/autoload_callback.rb2
-rw-r--r--spec/ruby/core/module/fixtures/autoload_concur.rb9
-rw-r--r--spec/ruby/core/module/fixtures/autoload_d.rb11
-rw-r--r--spec/ruby/core/module/fixtures/autoload_during_autoload.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_during_require.rb4
-rw-r--r--spec/ruby/core/module/fixtures/autoload_during_require_current_file.rb5
-rw-r--r--spec/ruby/core/module/fixtures/autoload_e.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_empty.rb1
-rw-r--r--spec/ruby/core/module/fixtures/autoload_ex1.rb16
-rw-r--r--spec/ruby/core/module/fixtures/autoload_exception.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_f.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_g.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_h.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_i.rb5
-rw-r--r--spec/ruby/core/module/fixtures/autoload_j.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_k.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_lm.rb4
-rw-r--r--spec/ruby/core/module/fixtures/autoload_location.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_nested.rb8
-rw-r--r--spec/ruby/core/module/fixtures/autoload_never_set.rb1
-rw-r--r--spec/ruby/core/module/fixtures/autoload_o.rb2
-rw-r--r--spec/ruby/core/module/fixtures/autoload_overridden.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_r.rb4
-rw-r--r--spec/ruby/core/module/fixtures/autoload_raise.rb2
-rw-r--r--spec/ruby/core/module/fixtures/autoload_required_directly.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_required_directly_nested.rb1
-rw-r--r--spec/ruby/core/module/fixtures/autoload_required_directly_no_constant.rb2
-rw-r--r--spec/ruby/core/module/fixtures/autoload_s.rb5
-rw-r--r--spec/ruby/core/module/fixtures/autoload_self_during_require.rb5
-rw-r--r--spec/ruby/core/module/fixtures/autoload_subclass.rb11
-rw-r--r--spec/ruby/core/module/fixtures/autoload_t.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_v.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_w.rb2
-rw-r--r--spec/ruby/core/module/fixtures/autoload_w2.rb1
-rw-r--r--spec/ruby/core/module/fixtures/autoload_x.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_z.rb5
-rw-r--r--spec/ruby/core/module/fixtures/classes.rb627
-rw-r--r--spec/ruby/core/module/fixtures/constant_unicode.rb5
-rw-r--r--spec/ruby/core/module/fixtures/constants_autoload.rb6
-rw-r--r--spec/ruby/core/module/fixtures/constants_autoload_a.rb2
-rw-r--r--spec/ruby/core/module/fixtures/constants_autoload_b.rb2
-rw-r--r--spec/ruby/core/module/fixtures/constants_autoload_c.rb3
-rw-r--r--spec/ruby/core/module/fixtures/constants_autoload_d.rb4
-rw-r--r--spec/ruby/core/module/fixtures/module.rb4
-rw-r--r--spec/ruby/core/module/fixtures/multi/foo.rb6
-rw-r--r--spec/ruby/core/module/fixtures/multi/foo/bar_baz.rb11
-rw-r--r--spec/ruby/core/module/fixtures/name.rb10
-rw-r--r--spec/ruby/core/module/fixtures/path1/load_path.rb9
-rw-r--r--spec/ruby/core/module/fixtures/path2/load_path.rb1
-rw-r--r--spec/ruby/core/module/fixtures/refine.rb25
-rw-r--r--spec/ruby/core/module/fixtures/repeated_concurrent_autoload.rb8
-rw-r--r--spec/ruby/core/module/freeze_spec.rb6
-rw-r--r--spec/ruby/core/module/gt_spec.rb36
-rw-r--r--spec/ruby/core/module/gte_spec.rb33
-rw-r--r--spec/ruby/core/module/include_spec.rb577
-rw-r--r--spec/ruby/core/module/included_modules_spec.rb14
-rw-r--r--spec/ruby/core/module/included_spec.rb44
-rw-r--r--spec/ruby/core/module/initialize_copy_spec.rb18
-rw-r--r--spec/ruby/core/module/initialize_spec.rb18
-rw-r--r--spec/ruby/core/module/instance_method_spec.rb111
-rw-r--r--spec/ruby/core/module/instance_methods_spec.rb61
-rw-r--r--spec/ruby/core/module/lt_spec.rb36
-rw-r--r--spec/ruby/core/module/lte_spec.rb33
-rw-r--r--spec/ruby/core/module/method_added_spec.rb83
-rw-r--r--spec/ruby/core/module/method_defined_spec.rb98
-rw-r--r--spec/ruby/core/module/method_removed_spec.rb33
-rw-r--r--spec/ruby/core/module/method_undefined_spec.rb33
-rw-r--r--spec/ruby/core/module/module_eval_spec.rb7
-rw-r--r--spec/ruby/core/module/module_exec_spec.rb7
-rw-r--r--spec/ruby/core/module/module_function_spec.rb285
-rw-r--r--spec/ruby/core/module/name_spec.rb130
-rw-r--r--spec/ruby/core/module/nesting_spec.rb31
-rw-r--r--spec/ruby/core/module/new_spec.rb31
-rw-r--r--spec/ruby/core/module/prepend_features_spec.rb64
-rw-r--r--spec/ruby/core/module/prepend_spec.rb761
-rw-r--r--spec/ruby/core/module/prepended_spec.rb25
-rw-r--r--spec/ruby/core/module/private_class_method_spec.rb93
-rw-r--r--spec/ruby/core/module/private_constant_spec.rb32
-rw-r--r--spec/ruby/core/module/private_instance_methods_spec.rb54
-rw-r--r--spec/ruby/core/module/private_method_defined_spec.rb120
-rw-r--r--spec/ruby/core/module/private_spec.rb107
-rw-r--r--spec/ruby/core/module/protected_instance_methods_spec.rb57
-rw-r--r--spec/ruby/core/module/protected_method_defined_spec.rb120
-rw-r--r--spec/ruby/core/module/protected_spec.rb69
-rw-r--r--spec/ruby/core/module/public_class_method_spec.rb96
-rw-r--r--spec/ruby/core/module/public_constant_spec.rb38
-rw-r--r--spec/ruby/core/module/public_instance_method_spec.rb65
-rw-r--r--spec/ruby/core/module/public_instance_methods_spec.rb61
-rw-r--r--spec/ruby/core/module/public_method_defined_spec.rb72
-rw-r--r--spec/ruby/core/module/public_spec.rb57
-rw-r--r--spec/ruby/core/module/refine_spec.rb1051
-rw-r--r--spec/ruby/core/module/remove_class_variable_spec.rb44
-rw-r--r--spec/ruby/core/module/remove_const_spec.rb105
-rw-r--r--spec/ruby/core/module/remove_method_spec.rb131
-rw-r--r--spec/ruby/core/module/ruby2_keywords_spec.rb319
-rw-r--r--spec/ruby/core/module/shared/class_eval.rb168
-rw-r--r--spec/ruby/core/module/shared/class_exec.rb29
-rw-r--r--spec/ruby/core/module/shared/equal_value.rb14
-rw-r--r--spec/ruby/core/module/shared/set_visibility.rb186
-rw-r--r--spec/ruby/core/module/singleton_class_spec.rb27
-rw-r--r--spec/ruby/core/module/to_s_spec.rb68
-rw-r--r--spec/ruby/core/module/undef_method_spec.rb181
-rw-r--r--spec/ruby/core/module/using_spec.rb377
-rw-r--r--spec/ruby/core/mutex/lock_spec.rb34
-rw-r--r--spec/ruby/core/mutex/locked_spec.rb36
-rw-r--r--spec/ruby/core/mutex/owned_spec.rb55
-rw-r--r--spec/ruby/core/mutex/sleep_spec.rb103
-rw-r--r--spec/ruby/core/mutex/synchronize_spec.rb66
-rw-r--r--spec/ruby/core/mutex/try_lock_spec.rb32
-rw-r--r--spec/ruby/core/mutex/unlock_spec.rb38
-rw-r--r--spec/ruby/core/nil/and_spec.rb11
-rw-r--r--spec/ruby/core/nil/case_compare_spec.rb13
-rw-r--r--spec/ruby/core/nil/dup_spec.rb7
-rw-r--r--spec/ruby/core/nil/inspect_spec.rb7
-rw-r--r--spec/ruby/core/nil/match_spec.rb21
-rw-r--r--spec/ruby/core/nil/nil_spec.rb7
-rw-r--r--spec/ruby/core/nil/nilclass_spec.rb15
-rw-r--r--spec/ruby/core/nil/or_spec.rb11
-rw-r--r--spec/ruby/core/nil/rationalize_spec.rb16
-rw-r--r--spec/ruby/core/nil/to_a_spec.rb7
-rw-r--r--spec/ruby/core/nil/to_c_spec.rb7
-rw-r--r--spec/ruby/core/nil/to_f_spec.rb11
-rw-r--r--spec/ruby/core/nil/to_h_spec.rb8
-rw-r--r--spec/ruby/core/nil/to_i_spec.rb11
-rw-r--r--spec/ruby/core/nil/to_r_spec.rb7
-rw-r--r--spec/ruby/core/nil/to_s_spec.rb15
-rw-r--r--spec/ruby/core/nil/xor_spec.rb11
-rw-r--r--spec/ruby/core/numeric/abs2_spec.rb34
-rw-r--r--spec/ruby/core/numeric/abs_spec.rb6
-rw-r--r--spec/ruby/core/numeric/angle_spec.rb6
-rw-r--r--spec/ruby/core/numeric/arg_spec.rb6
-rw-r--r--spec/ruby/core/numeric/ceil_spec.rb15
-rw-r--r--spec/ruby/core/numeric/clone_spec.rb32
-rw-r--r--spec/ruby/core/numeric/coerce_spec.rb59
-rw-r--r--spec/ruby/core/numeric/comparison_spec.rb48
-rw-r--r--spec/ruby/core/numeric/conj_spec.rb6
-rw-r--r--spec/ruby/core/numeric/conjugate_spec.rb6
-rw-r--r--spec/ruby/core/numeric/denominator_spec.rb24
-rw-r--r--spec/ruby/core/numeric/div_spec.rb22
-rw-r--r--spec/ruby/core/numeric/divmod_spec.rb15
-rw-r--r--spec/ruby/core/numeric/dup_spec.rb16
-rw-r--r--spec/ruby/core/numeric/eql_spec.rb22
-rw-r--r--spec/ruby/core/numeric/fdiv_spec.rb32
-rw-r--r--spec/ruby/core/numeric/finite_spec.rb8
-rw-r--r--spec/ruby/core/numeric/fixtures/classes.rb17
-rw-r--r--spec/ruby/core/numeric/floor_spec.rb14
-rw-r--r--spec/ruby/core/numeric/i_spec.rb15
-rw-r--r--spec/ruby/core/numeric/imag_spec.rb6
-rw-r--r--spec/ruby/core/numeric/imaginary_spec.rb6
-rw-r--r--spec/ruby/core/numeric/infinite_spec.rb8
-rw-r--r--spec/ruby/core/numeric/integer_spec.rb8
-rw-r--r--spec/ruby/core/numeric/magnitude_spec.rb5
-rw-r--r--spec/ruby/core/numeric/modulo_spec.rb24
-rw-r--r--spec/ruby/core/numeric/negative_spec.rb41
-rw-r--r--spec/ruby/core/numeric/nonzero_spec.rb18
-rw-r--r--spec/ruby/core/numeric/numerator_spec.rb33
-rw-r--r--spec/ruby/core/numeric/numeric_spec.rb7
-rw-r--r--spec/ruby/core/numeric/phase_spec.rb6
-rw-r--r--spec/ruby/core/numeric/polar_spec.rb50
-rw-r--r--spec/ruby/core/numeric/positive_spec.rb41
-rw-r--r--spec/ruby/core/numeric/quo_spec.rb64
-rw-r--r--spec/ruby/core/numeric/real_spec.rb37
-rw-r--r--spec/ruby/core/numeric/rect_spec.rb6
-rw-r--r--spec/ruby/core/numeric/rectangular_spec.rb6
-rw-r--r--spec/ruby/core/numeric/remainder_spec.rb67
-rw-r--r--spec/ruby/core/numeric/round_spec.rb14
-rw-r--r--spec/ruby/core/numeric/shared/abs.rb19
-rw-r--r--spec/ruby/core/numeric/shared/arg.rb38
-rw-r--r--spec/ruby/core/numeric/shared/conj.rb20
-rw-r--r--spec/ruby/core/numeric/shared/imag.rb26
-rw-r--r--spec/ruby/core/numeric/shared/quo.rb7
-rw-r--r--spec/ruby/core/numeric/shared/rect.rb48
-rw-r--r--spec/ruby/core/numeric/shared/step.rb416
-rw-r--r--spec/ruby/core/numeric/singleton_method_added_spec.rb41
-rw-r--r--spec/ruby/core/numeric/step_spec.rb198
-rw-r--r--spec/ruby/core/numeric/to_c_spec.rb45
-rw-r--r--spec/ruby/core/numeric/to_int_spec.rb10
-rw-r--r--spec/ruby/core/numeric/truncate_spec.rb14
-rw-r--r--spec/ruby/core/numeric/uminus_spec.rb31
-rw-r--r--spec/ruby/core/numeric/uplus_spec.rb9
-rw-r--r--spec/ruby/core/numeric/zero_spec.rb18
-rw-r--r--spec/ruby/core/objectspace/_id2ref_spec.rb52
-rw-r--r--spec/ruby/core/objectspace/add_finalizer_spec.rb5
-rw-r--r--spec/ruby/core/objectspace/call_finalizer_spec.rb5
-rw-r--r--spec/ruby/core/objectspace/count_objects_spec.rb5
-rw-r--r--spec/ruby/core/objectspace/define_finalizer_spec.rb194
-rw-r--r--spec/ruby/core/objectspace/each_object_spec.rb213
-rw-r--r--spec/ruby/core/objectspace/finalizers_spec.rb5
-rw-r--r--spec/ruby/core/objectspace/fixtures/classes.rb64
-rw-r--r--spec/ruby/core/objectspace/garbage_collect_spec.rb22
-rw-r--r--spec/ruby/core/objectspace/remove_finalizer_spec.rb5
-rw-r--r--spec/ruby/core/objectspace/undefine_finalizer_spec.rb5
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_key_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_pair_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_value_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/element_reference_spec.rb24
-rw-r--r--spec/ruby/core/objectspace/weakmap/element_set_spec.rb38
-rw-r--r--spec/ruby/core/objectspace/weakmap/include_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/inspect_spec.rb25
-rw-r--r--spec/ruby/core/objectspace/weakmap/key_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/keys_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/length_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/member_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/each.rb10
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/include.rb30
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/members.rb14
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/size.rb14
-rw-r--r--spec/ruby/core/objectspace/weakmap/size_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/values_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap_spec.rb12
-rw-r--r--spec/ruby/core/proc/allocate_spec.rb9
-rw-r--r--spec/ruby/core/proc/arity_spec.rb640
-rw-r--r--spec/ruby/core/proc/binding_spec.rb21
-rw-r--r--spec/ruby/core/proc/block_pass_spec.rb21
-rw-r--r--spec/ruby/core/proc/call_spec.rb16
-rw-r--r--spec/ruby/core/proc/case_compare_spec.rb16
-rw-r--r--spec/ruby/core/proc/clone_spec.rb6
-rw-r--r--spec/ruby/core/proc/compose_spec.rb162
-rw-r--r--spec/ruby/core/proc/curry_spec.rb180
-rw-r--r--spec/ruby/core/proc/dup_spec.rb6
-rw-r--r--spec/ruby/core/proc/element_reference_spec.rb27
-rw-r--r--spec/ruby/core/proc/eql_spec.rb12
-rw-r--r--spec/ruby/core/proc/equal_value_spec.rb12
-rw-r--r--spec/ruby/core/proc/fixtures/common.rb51
-rw-r--r--spec/ruby/core/proc/fixtures/proc_aref.rb9
-rw-r--r--spec/ruby/core/proc/fixtures/proc_aref_frozen.rb10
-rw-r--r--spec/ruby/core/proc/fixtures/source_location.rb55
-rw-r--r--spec/ruby/core/proc/hash_spec.rb17
-rw-r--r--spec/ruby/core/proc/inspect_spec.rb6
-rw-r--r--spec/ruby/core/proc/lambda_spec.rb60
-rw-r--r--spec/ruby/core/proc/new_spec.rb201
-rw-r--r--spec/ruby/core/proc/parameters_spec.rb118
-rw-r--r--spec/ruby/core/proc/ruby2_keywords_spec.rb78
-rw-r--r--spec/ruby/core/proc/shared/call.rb99
-rw-r--r--spec/ruby/core/proc/shared/call_arguments.rb29
-rw-r--r--spec/ruby/core/proc/shared/compose.rb22
-rw-r--r--spec/ruby/core/proc/shared/dup.rb10
-rw-r--r--spec/ruby/core/proc/shared/equal.rb100
-rw-r--r--spec/ruby/core/proc/shared/to_s.rb60
-rw-r--r--spec/ruby/core/proc/source_location_spec.rb86
-rw-r--r--spec/ruby/core/proc/to_proc_spec.rb9
-rw-r--r--spec/ruby/core/proc/to_s_spec.rb6
-rw-r--r--spec/ruby/core/proc/yield_spec.rb16
-rw-r--r--spec/ruby/core/process/_fork_spec.rb24
-rw-r--r--spec/ruby/core/process/abort_spec.rb6
-rw-r--r--spec/ruby/core/process/clock_getres_spec.rb33
-rw-r--r--spec/ruby/core/process/clock_gettime_spec.rb152
-rw-r--r--spec/ruby/core/process/constants_spec.rb86
-rw-r--r--spec/ruby/core/process/daemon_spec.rb118
-rw-r--r--spec/ruby/core/process/detach_spec.rb75
-rw-r--r--spec/ruby/core/process/egid_spec.rb58
-rw-r--r--spec/ruby/core/process/euid_spec.rb56
-rw-r--r--spec/ruby/core/process/exec_spec.rb241
-rw-r--r--spec/ruby/core/process/exit_spec.rb10
-rw-r--r--spec/ruby/core/process/fixtures/clocks.rb18
-rw-r--r--spec/ruby/core/process/fixtures/common.rb88
-rw-r--r--spec/ruby/core/process/fixtures/daemon.rb111
-rw-r--r--spec/ruby/core/process/fixtures/in.txt1
-rw-r--r--spec/ruby/core/process/fixtures/kill.rb45
-rw-r--r--spec/ruby/core/process/fixtures/map_fd.rb9
-rw-r--r--spec/ruby/core/process/fixtures/setpriority.rb12
-rw-r--r--spec/ruby/core/process/fork_spec.rb6
-rw-r--r--spec/ruby/core/process/getpgid_spec.rb17
-rw-r--r--spec/ruby/core/process/getpgrp_spec.rb7
-rw-r--r--spec/ruby/core/process/getpriority_spec.rb23
-rw-r--r--spec/ruby/core/process/getrlimit_spec.rb100
-rw-r--r--spec/ruby/core/process/gid/change_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/eid_spec.rb9
-rw-r--r--spec/ruby/core/process/gid/grant_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/re_exchange_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/re_exchangeable_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/rid_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/sid_available_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/switch_spec.rb5
-rw-r--r--spec/ruby/core/process/gid_spec.rb22
-rw-r--r--spec/ruby/core/process/groups_spec.rb67
-rw-r--r--spec/ruby/core/process/initgroups_spec.rb22
-rw-r--r--spec/ruby/core/process/kill_spec.rb132
-rw-r--r--spec/ruby/core/process/last_status_spec.rb18
-rw-r--r--spec/ruby/core/process/maxgroups_spec.rb19
-rw-r--r--spec/ruby/core/process/pid_spec.rb9
-rw-r--r--spec/ruby/core/process/ppid_spec.rb9
-rw-r--r--spec/ruby/core/process/set_proctitle_spec.rb23
-rw-r--r--spec/ruby/core/process/setpgid_spec.rb29
-rw-r--r--spec/ruby/core/process/setpgrp_spec.rb37
-rw-r--r--spec/ruby/core/process/setpriority_spec.rb60
-rw-r--r--spec/ruby/core/process/setrlimit_spec.rb241
-rw-r--r--spec/ruby/core/process/setsid_spec.rb16
-rw-r--r--spec/ruby/core/process/spawn_spec.rb756
-rw-r--r--spec/ruby/core/process/status/bit_and_spec.rb5
-rw-r--r--spec/ruby/core/process/status/coredump_spec.rb5
-rw-r--r--spec/ruby/core/process/status/equal_value_spec.rb15
-rw-r--r--spec/ruby/core/process/status/exited_spec.rb32
-rw-r--r--spec/ruby/core/process/status/exitstatus_spec.rb25
-rw-r--r--spec/ruby/core/process/status/inspect_spec.rb5
-rw-r--r--spec/ruby/core/process/status/pid_spec.rb15
-rw-r--r--spec/ruby/core/process/status/right_shift_spec.rb5
-rw-r--r--spec/ruby/core/process/status/signaled_spec.rb31
-rw-r--r--spec/ruby/core/process/status/stopped_spec.rb5
-rw-r--r--spec/ruby/core/process/status/stopsig_spec.rb5
-rw-r--r--spec/ruby/core/process/status/success_spec.rb41
-rw-r--r--spec/ruby/core/process/status/termsig_spec.rb43
-rw-r--r--spec/ruby/core/process/status/to_i_spec.rb13
-rw-r--r--spec/ruby/core/process/status/to_int_spec.rb5
-rw-r--r--spec/ruby/core/process/status/to_s_spec.rb5
-rw-r--r--spec/ruby/core/process/status/wait_spec.rb102
-rw-r--r--spec/ruby/core/process/sys/getegid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/geteuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/getgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/getuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/issetugid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setegid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/seteuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setregid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setresgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setresuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setreuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setrgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setruid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setuid_spec.rb5
-rw-r--r--spec/ruby/core/process/times_spec.rb39
-rw-r--r--spec/ruby/core/process/uid/change_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/eid_spec.rb9
-rw-r--r--spec/ruby/core/process/uid/grant_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/re_exchange_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/re_exchangeable_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/rid_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/sid_available_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/switch_spec.rb5
-rw-r--r--spec/ruby/core/process/uid_spec.rb57
-rw-r--r--spec/ruby/core/process/wait2_spec.rb45
-rw-r--r--spec/ruby/core/process/wait_spec.rb91
-rw-r--r--spec/ruby/core/process/waitall_spec.rb48
-rw-r--r--spec/ruby/core/process/waitpid2_spec.rb5
-rw-r--r--spec/ruby/core/process/waitpid_spec.rb13
-rw-r--r--spec/ruby/core/queue/append_spec.rb6
-rw-r--r--spec/ruby/core/queue/clear_spec.rb6
-rw-r--r--spec/ruby/core/queue/close_spec.rb6
-rw-r--r--spec/ruby/core/queue/closed_spec.rb6
-rw-r--r--spec/ruby/core/queue/deq_spec.rb6
-rw-r--r--spec/ruby/core/queue/empty_spec.rb6
-rw-r--r--spec/ruby/core/queue/enq_spec.rb6
-rw-r--r--spec/ruby/core/queue/initialize_spec.rb49
-rw-r--r--spec/ruby/core/queue/length_spec.rb6
-rw-r--r--spec/ruby/core/queue/num_waiting_spec.rb6
-rw-r--r--spec/ruby/core/queue/pop_spec.rb6
-rw-r--r--spec/ruby/core/queue/push_spec.rb6
-rw-r--r--spec/ruby/core/queue/shift_spec.rb6
-rw-r--r--spec/ruby/core/queue/size_spec.rb6
-rw-r--r--spec/ruby/core/random/bytes_spec.rb30
-rw-r--r--spec/ruby/core/random/default_spec.rb45
-rw-r--r--spec/ruby/core/random/equal_value_spec.rb37
-rw-r--r--spec/ruby/core/random/fixtures/classes.rb15
-rw-r--r--spec/ruby/core/random/new_seed_spec.rb24
-rw-r--r--spec/ruby/core/random/new_spec.rb37
-rw-r--r--spec/ruby/core/random/rand_spec.rb224
-rw-r--r--spec/ruby/core/random/random_number_spec.rb8
-rw-r--r--spec/ruby/core/random/seed_spec.rb29
-rw-r--r--spec/ruby/core/random/shared/bytes.rb17
-rw-r--r--spec/ruby/core/random/shared/rand.rb9
-rw-r--r--spec/ruby/core/random/srand_spec.rb39
-rw-r--r--spec/ruby/core/random/urandom_spec.rb25
-rw-r--r--spec/ruby/core/range/begin_spec.rb6
-rw-r--r--spec/ruby/core/range/bsearch_spec.rb436
-rw-r--r--spec/ruby/core/range/case_compare_spec.rb19
-rw-r--r--spec/ruby/core/range/clone_spec.rb26
-rw-r--r--spec/ruby/core/range/count_spec.rb12
-rw-r--r--spec/ruby/core/range/cover_spec.rb10
-rw-r--r--spec/ruby/core/range/dup_spec.rb23
-rw-r--r--spec/ruby/core/range/each_spec.rb114
-rw-r--r--spec/ruby/core/range/end_spec.rb6
-rw-r--r--spec/ruby/core/range/eql_spec.rb10
-rw-r--r--spec/ruby/core/range/equal_value_spec.rb18
-rw-r--r--spec/ruby/core/range/exclude_end_spec.rb19
-rw-r--r--spec/ruby/core/range/first_spec.rb53
-rw-r--r--spec/ruby/core/range/fixtures/classes.rb90
-rw-r--r--spec/ruby/core/range/frozen_spec.rb27
-rw-r--r--spec/ruby/core/range/hash_spec.rb24
-rw-r--r--spec/ruby/core/range/include_spec.rb10
-rw-r--r--spec/ruby/core/range/initialize_spec.rb50
-rw-r--r--spec/ruby/core/range/inspect_spec.rb29
-rw-r--r--spec/ruby/core/range/last_spec.rb59
-rw-r--r--spec/ruby/core/range/max_spec.rb103
-rw-r--r--spec/ruby/core/range/member_spec.rb10
-rw-r--r--spec/ruby/core/range/min_spec.rb88
-rw-r--r--spec/ruby/core/range/minmax_spec.rb132
-rw-r--r--spec/ruby/core/range/new_spec.rb79
-rw-r--r--spec/ruby/core/range/percent_spec.rb16
-rw-r--r--spec/ruby/core/range/range_spec.rb7
-rw-r--r--spec/ruby/core/range/shared/begin.rb10
-rw-r--r--spec/ruby/core/range/shared/cover.rb193
-rw-r--r--spec/ruby/core/range/shared/cover_and_include.rb76
-rw-r--r--spec/ruby/core/range/shared/end.rb10
-rw-r--r--spec/ruby/core/range/shared/equal_value.rb51
-rw-r--r--spec/ruby/core/range/shared/include.rb91
-rw-r--r--spec/ruby/core/range/size_spec.rb65
-rw-r--r--spec/ruby/core/range/step_spec.rb514
-rw-r--r--spec/ruby/core/range/to_a_spec.rb39
-rw-r--r--spec/ruby/core/range/to_s_spec.rb23
-rw-r--r--spec/ruby/core/rational/abs_spec.rb5
-rw-r--r--spec/ruby/core/rational/ceil_spec.rb5
-rw-r--r--spec/ruby/core/rational/coerce_spec.rb5
-rw-r--r--spec/ruby/core/rational/comparison_spec.rb22
-rw-r--r--spec/ruby/core/rational/denominator_spec.rb5
-rw-r--r--spec/ruby/core/rational/div_spec.rb17
-rw-r--r--spec/ruby/core/rational/divide_spec.rb19
-rw-r--r--spec/ruby/core/rational/divmod_spec.rb13
-rw-r--r--spec/ruby/core/rational/equal_value_spec.rb17
-rw-r--r--spec/ruby/core/rational/exponent_spec.rb5
-rw-r--r--spec/ruby/core/rational/fdiv_spec.rb5
-rw-r--r--spec/ruby/core/rational/floor_spec.rb5
-rw-r--r--spec/ruby/core/rational/hash_spec.rb5
-rw-r--r--spec/ruby/core/rational/inspect_spec.rb5
-rw-r--r--spec/ruby/core/rational/integer_spec.rb12
-rw-r--r--spec/ruby/core/rational/magnitude_spec.rb5
-rw-r--r--spec/ruby/core/rational/marshal_dump_spec.rb11
-rw-r--r--spec/ruby/core/rational/minus_spec.rb51
-rw-r--r--spec/ruby/core/rational/modulo_spec.rb5
-rw-r--r--spec/ruby/core/rational/multiply_spec.rb19
-rw-r--r--spec/ruby/core/rational/numerator_spec.rb5
-rw-r--r--spec/ruby/core/rational/plus_spec.rb18
-rw-r--r--spec/ruby/core/rational/quo_spec.rb5
-rw-r--r--spec/ruby/core/rational/rational_spec.rb11
-rw-r--r--spec/ruby/core/rational/rationalize_spec.rb36
-rw-r--r--spec/ruby/core/rational/remainder_spec.rb5
-rw-r--r--spec/ruby/core/rational/round_spec.rb6
-rw-r--r--spec/ruby/core/rational/to_f_spec.rb5
-rw-r--r--spec/ruby/core/rational/to_i_spec.rb5
-rw-r--r--spec/ruby/core/rational/to_r_spec.rb20
-rw-r--r--spec/ruby/core/rational/to_s_spec.rb5
-rw-r--r--spec/ruby/core/rational/truncate_spec.rb5
-rw-r--r--spec/ruby/core/rational/zero_spec.rb13
-rw-r--r--spec/ruby/core/refinement/append_features_spec.rb21
-rw-r--r--spec/ruby/core/refinement/extend_object_spec.rb21
-rw-r--r--spec/ruby/core/refinement/import_methods_spec.rb34
-rw-r--r--spec/ruby/core/refinement/include_spec.rb27
-rw-r--r--spec/ruby/core/refinement/prepend_features_spec.rb21
-rw-r--r--spec/ruby/core/refinement/prepend_spec.rb27
-rw-r--r--spec/ruby/core/regexp/case_compare_spec.rb35
-rw-r--r--spec/ruby/core/regexp/casefold_spec.rb8
-rw-r--r--spec/ruby/core/regexp/compile_spec.rb19
-rw-r--r--spec/ruby/core/regexp/encoding_spec.rb62
-rw-r--r--spec/ruby/core/regexp/eql_spec.rb6
-rw-r--r--spec/ruby/core/regexp/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/regexp/escape_spec.rb6
-rw-r--r--spec/ruby/core/regexp/fixed_encoding_spec.rb36
-rw-r--r--spec/ruby/core/regexp/hash_spec.rb20
-rw-r--r--spec/ruby/core/regexp/initialize_spec.rb23
-rw-r--r--spec/ruby/core/regexp/inspect_spec.rb44
-rw-r--r--spec/ruby/core/regexp/last_match_spec.rb56
-rw-r--r--spec/ruby/core/regexp/match_spec.rb146
-rw-r--r--spec/ruby/core/regexp/named_captures_spec.rb35
-rw-r--r--spec/ruby/core/regexp/names_spec.rb29
-rw-r--r--spec/ruby/core/regexp/new_spec.rb19
-rw-r--r--spec/ruby/core/regexp/options_spec.rb54
-rw-r--r--spec/ruby/core/regexp/quote_spec.rb6
-rw-r--r--spec/ruby/core/regexp/shared/equal_value.rb31
-rw-r--r--spec/ruby/core/regexp/shared/new.rb609
-rw-r--r--spec/ruby/core/regexp/shared/quote.rb41
-rw-r--r--spec/ruby/core/regexp/source_spec.rb47
-rw-r--r--spec/ruby/core/regexp/timeout_spec.rb35
-rw-r--r--spec/ruby/core/regexp/to_s_spec.rb62
-rw-r--r--spec/ruby/core/regexp/try_convert_spec.rb21
-rw-r--r--spec/ruby/core/regexp/union_spec.rb159
-rw-r--r--spec/ruby/core/signal/fixtures/trap_all.rb15
-rw-r--r--spec/ruby/core/signal/list_spec.rb68
-rw-r--r--spec/ruby/core/signal/signame_spec.rb22
-rw-r--r--spec/ruby/core/signal/trap_spec.rb293
-rw-r--r--spec/ruby/core/sizedqueue/append_spec.rb11
-rw-r--r--spec/ruby/core/sizedqueue/clear_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/close_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/closed_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/deq_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/empty_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/enq_spec.rb11
-rw-r--r--spec/ruby/core/sizedqueue/length_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/max_spec.rb10
-rw-r--r--spec/ruby/core/sizedqueue/new_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/num_waiting_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/pop_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/push_spec.rb11
-rw-r--r--spec/ruby/core/sizedqueue/shift_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/size_spec.rb6
-rw-r--r--spec/ruby/core/string/allocate_spec.rb19
-rw-r--r--spec/ruby/core/string/append_spec.rb13
-rw-r--r--spec/ruby/core/string/ascii_only_spec.rb83
-rw-r--r--spec/ruby/core/string/b_spec.rb15
-rw-r--r--spec/ruby/core/string/byteindex_spec.rb16
-rw-r--r--spec/ruby/core/string/bytes_spec.rb55
-rw-r--r--spec/ruby/core/string/bytesize_spec.rb33
-rw-r--r--spec/ruby/core/string/byteslice_spec.rb33
-rw-r--r--spec/ruby/core/string/capitalize_spec.rb216
-rw-r--r--spec/ruby/core/string/case_compare_spec.rb8
-rw-r--r--spec/ruby/core/string/casecmp_spec.rb194
-rw-r--r--spec/ruby/core/string/center_spec.rb130
-rw-r--r--spec/ruby/core/string/chars_spec.rb15
-rw-r--r--spec/ruby/core/string/chomp_spec.rb374
-rw-r--r--spec/ruby/core/string/chop_spec.rb126
-rw-r--r--spec/ruby/core/string/chr_spec.rb42
-rw-r--r--spec/ruby/core/string/clear_spec.rb37
-rw-r--r--spec/ruby/core/string/clone_spec.rb61
-rw-r--r--spec/ruby/core/string/codepoints_spec.rb18
-rw-r--r--spec/ruby/core/string/comparison_spec.rb112
-rw-r--r--spec/ruby/core/string/concat_spec.rb26
-rw-r--r--spec/ruby/core/string/count_spec.rb105
-rw-r--r--spec/ruby/core/string/crypt_spec.rb92
-rw-r--r--spec/ruby/core/string/dedup_spec.rb8
-rw-r--r--spec/ruby/core/string/delete_prefix_spec.rb91
-rw-r--r--spec/ruby/core/string/delete_spec.rb124
-rw-r--r--spec/ruby/core/string/delete_suffix_spec.rb91
-rw-r--r--spec/ruby/core/string/downcase_spec.rb202
-rw-r--r--spec/ruby/core/string/dump_spec.rb404
-rw-r--r--spec/ruby/core/string/dup_spec.rb65
-rw-r--r--spec/ruby/core/string/each_byte_spec.rb61
-rw-r--r--spec/ruby/core/string/each_char_spec.rb7
-rw-r--r--spec/ruby/core/string/each_codepoint_spec.rb8
-rw-r--r--spec/ruby/core/string/each_grapheme_cluster_spec.rb17
-rw-r--r--spec/ruby/core/string/each_line_spec.rb9
-rw-r--r--spec/ruby/core/string/element_reference_spec.rb35
-rw-r--r--spec/ruby/core/string/element_set_spec.rb588
-rw-r--r--spec/ruby/core/string/empty_spec.rb12
-rw-r--r--spec/ruby/core/string/encode_spec.rb226
-rw-r--r--spec/ruby/core/string/encoding_spec.rb188
-rw-r--r--spec/ruby/core/string/end_with_spec.rb8
-rw-r--r--spec/ruby/core/string/eql_spec.rb21
-rw-r--r--spec/ruby/core/string/equal_value_spec.rb8
-rw-r--r--spec/ruby/core/string/fixtures/classes.rb60
-rw-r--r--spec/ruby/core/string/fixtures/freeze_magic_comment.rb3
-rw-r--r--spec/ruby/core/string/fixtures/iso-8859-9-encoding.rb9
-rw-r--r--spec/ruby/core/string/fixtures/to_c.rb5
-rw-r--r--spec/ruby/core/string/fixtures/utf-8-encoding.rb7
-rw-r--r--spec/ruby/core/string/force_encoding_spec.rb71
-rw-r--r--spec/ruby/core/string/freeze_spec.rb17
-rw-r--r--spec/ruby/core/string/getbyte_spec.rb69
-rw-r--r--spec/ruby/core/string/grapheme_clusters_spec.rb13
-rw-r--r--spec/ruby/core/string/gsub_spec.rb625
-rw-r--r--spec/ruby/core/string/hash_spec.rb9
-rw-r--r--spec/ruby/core/string/hex_spec.rb49
-rw-r--r--spec/ruby/core/string/include_spec.rb49
-rw-r--r--spec/ruby/core/string/index_spec.rb321
-rw-r--r--spec/ruby/core/string/initialize_spec.rb26
-rw-r--r--spec/ruby/core/string/insert_spec.rb81
-rw-r--r--spec/ruby/core/string/inspect_spec.rb520
-rw-r--r--spec/ruby/core/string/intern_spec.rb7
-rw-r--r--spec/ruby/core/string/length_spec.rb7
-rw-r--r--spec/ruby/core/string/lines_spec.rb19
-rw-r--r--spec/ruby/core/string/ljust_spec.rb113
-rw-r--r--spec/ruby/core/string/lstrip_spec.rb77
-rw-r--r--spec/ruby/core/string/match_spec.rb167
-rw-r--r--spec/ruby/core/string/modulo_spec.rb778
-rw-r--r--spec/ruby/core/string/multiply_spec.rb7
-rw-r--r--spec/ruby/core/string/new_spec.rb61
-rw-r--r--spec/ruby/core/string/next_spec.rb11
-rw-r--r--spec/ruby/core/string/oct_spec.rb88
-rw-r--r--spec/ruby/core/string/ord_spec.rb33
-rw-r--r--spec/ruby/core/string/partition_spec.rb63
-rw-r--r--spec/ruby/core/string/plus_spec.rb36
-rw-r--r--spec/ruby/core/string/prepend_spec.rb54
-rw-r--r--spec/ruby/core/string/replace_spec.rb7
-rw-r--r--spec/ruby/core/string/reverse_spec.rb79
-rw-r--r--spec/ruby/core/string/rindex_spec.rb387
-rw-r--r--spec/ruby/core/string/rjust_spec.rb113
-rw-r--r--spec/ruby/core/string/rpartition_spec.rb71
-rw-r--r--spec/ruby/core/string/rstrip_spec.rb95
-rw-r--r--spec/ruby/core/string/scan_spec.rb175
-rw-r--r--spec/ruby/core/string/scrub_spec.rb175
-rw-r--r--spec/ruby/core/string/setbyte_spec.rb111
-rw-r--r--spec/ruby/core/string/shared/chars.rb66
-rw-r--r--spec/ruby/core/string/shared/codepoints.rb62
-rw-r--r--spec/ruby/core/string/shared/concat.rb150
-rw-r--r--spec/ruby/core/string/shared/dedup.rb57
-rw-r--r--spec/ruby/core/string/shared/each_char_without_block.rb26
-rw-r--r--spec/ruby/core/string/shared/each_codepoint_without_block.rb33
-rw-r--r--spec/ruby/core/string/shared/each_line.rb172
-rw-r--r--spec/ruby/core/string/shared/each_line_without_block.rb17
-rw-r--r--spec/ruby/core/string/shared/encode.rb247
-rw-r--r--spec/ruby/core/string/shared/eql.rb38
-rw-r--r--spec/ruby/core/string/shared/equal_value.rb29
-rw-r--r--spec/ruby/core/string/shared/grapheme_clusters.rb16
-rw-r--r--spec/ruby/core/string/shared/length.rb55
-rw-r--r--spec/ruby/core/string/shared/partition.rb51
-rw-r--r--spec/ruby/core/string/shared/replace.rb47
-rw-r--r--spec/ruby/core/string/shared/slice.rb562
-rw-r--r--spec/ruby/core/string/shared/strip.rb24
-rw-r--r--spec/ruby/core/string/shared/succ.rb96
-rw-r--r--spec/ruby/core/string/shared/to_a.rb9
-rw-r--r--spec/ruby/core/string/shared/to_s.rb13
-rw-r--r--spec/ruby/core/string/shared/to_sym.rb72
-rw-r--r--spec/ruby/core/string/size_spec.rb7
-rw-r--r--spec/ruby/core/string/slice_spec.rb441
-rw-r--r--spec/ruby/core/string/split_spec.rb614
-rw-r--r--spec/ruby/core/string/squeeze_spec.rb118
-rw-r--r--spec/ruby/core/string/start_with_spec.rb18
-rw-r--r--spec/ruby/core/string/string_spec.rb7
-rw-r--r--spec/ruby/core/string/strip_spec.rb61
-rw-r--r--spec/ruby/core/string/sub_spec.rb522
-rw-r--r--spec/ruby/core/string/succ_spec.rb11
-rw-r--r--spec/ruby/core/string/sum_spec.rb22
-rw-r--r--spec/ruby/core/string/swapcase_spec.rb201
-rw-r--r--spec/ruby/core/string/to_c_spec.rb41
-rw-r--r--spec/ruby/core/string/to_f_spec.rb70
-rw-r--r--spec/ruby/core/string/to_i_spec.rb337
-rw-r--r--spec/ruby/core/string/to_r_spec.rb58
-rw-r--r--spec/ruby/core/string/to_s_spec.rb7
-rw-r--r--spec/ruby/core/string/to_str_spec.rb7
-rw-r--r--spec/ruby/core/string/to_sym_spec.rb7
-rw-r--r--spec/ruby/core/string/tr_s_spec.rb131
-rw-r--r--spec/ruby/core/string/tr_spec.rb126
-rw-r--r--spec/ruby/core/string/try_convert_spec.rb50
-rw-r--r--spec/ruby/core/string/uminus_spec.rb6
-rw-r--r--spec/ruby/core/string/undump_spec.rb441
-rw-r--r--spec/ruby/core/string/unicode_normalize_spec.rb115
-rw-r--r--spec/ruby/core/string/unicode_normalized_spec.rb74
-rw-r--r--spec/ruby/core/string/unpack/a_spec.rb66
-rw-r--r--spec/ruby/core/string/unpack/at_spec.rb29
-rw-r--r--spec/ruby/core/string/unpack/b_spec.rb217
-rw-r--r--spec/ruby/core/string/unpack/c_spec.rb73
-rw-r--r--spec/ruby/core/string/unpack/comment_spec.rb25
-rw-r--r--spec/ruby/core/string/unpack/d_spec.rb28
-rw-r--r--spec/ruby/core/string/unpack/e_spec.rb14
-rw-r--r--spec/ruby/core/string/unpack/f_spec.rb28
-rw-r--r--spec/ruby/core/string/unpack/g_spec.rb14
-rw-r--r--spec/ruby/core/string/unpack/h_spec.rb155
-rw-r--r--spec/ruby/core/string/unpack/i_spec.rb152
-rw-r--r--spec/ruby/core/string/unpack/j_spec.rb272
-rw-r--r--spec/ruby/core/string/unpack/l_spec.rb265
-rw-r--r--spec/ruby/core/string/unpack/m_spec.rb192
-rw-r--r--spec/ruby/core/string/unpack/n_spec.rb18
-rw-r--r--spec/ruby/core/string/unpack/p_spec.rb44
-rw-r--r--spec/ruby/core/string/unpack/percent_spec.rb7
-rw-r--r--spec/ruby/core/string/unpack/q_spec.rb64
-rw-r--r--spec/ruby/core/string/unpack/s_spec.rb152
-rw-r--r--spec/ruby/core/string/unpack/shared/basic.rb21
-rw-r--r--spec/ruby/core/string/unpack/shared/float.rb311
-rw-r--r--spec/ruby/core/string/unpack/shared/integer.rb399
-rw-r--r--spec/ruby/core/string/unpack/shared/string.rb51
-rw-r--r--spec/ruby/core/string/unpack/shared/taint.rb2
-rw-r--r--spec/ruby/core/string/unpack/shared/unicode.rb70
-rw-r--r--spec/ruby/core/string/unpack/u_spec.rb97
-rw-r--r--spec/ruby/core/string/unpack/v_spec.rb18
-rw-r--r--spec/ruby/core/string/unpack/w_spec.rb45
-rw-r--r--spec/ruby/core/string/unpack/x_spec.rb62
-rw-r--r--spec/ruby/core/string/unpack/z_spec.rb28
-rw-r--r--spec/ruby/core/string/unpack1_spec.rb36
-rw-r--r--spec/ruby/core/string/unpack_spec.rb34
-rw-r--r--spec/ruby/core/string/upcase_spec.rb194
-rw-r--r--spec/ruby/core/string/uplus_spec.rb22
-rw-r--r--spec/ruby/core/string/upto_spec.rb104
-rw-r--r--spec/ruby/core/string/valid_encoding/utf_8_spec.rb214
-rw-r--r--spec/ruby/core/string/valid_encoding_spec.rb135
-rw-r--r--spec/ruby/core/struct/clone_spec.rb7
-rw-r--r--spec/ruby/core/struct/deconstruct_keys_spec.rb76
-rw-r--r--spec/ruby/core/struct/deconstruct_spec.rb10
-rw-r--r--spec/ruby/core/struct/dig_spec.rb52
-rw-r--r--spec/ruby/core/struct/dup_spec.rb23
-rw-r--r--spec/ruby/core/struct/each_pair_spec.rb33
-rw-r--r--spec/ruby/core/struct/each_spec.rb27
-rw-r--r--spec/ruby/core/struct/element_reference_spec.rb52
-rw-r--r--spec/ruby/core/struct/element_set_spec.rb29
-rw-r--r--spec/ruby/core/struct/eql_spec.rb13
-rw-r--r--spec/ruby/core/struct/equal_value_spec.rb7
-rw-r--r--spec/ruby/core/struct/filter_spec.rb10
-rw-r--r--spec/ruby/core/struct/fixtures/classes.rb26
-rw-r--r--spec/ruby/core/struct/hash_spec.rb64
-rw-r--r--spec/ruby/core/struct/initialize_spec.rb51
-rw-r--r--spec/ruby/core/struct/inspect_spec.rb12
-rw-r--r--spec/ruby/core/struct/instance_variable_get_spec.rb16
-rw-r--r--spec/ruby/core/struct/instance_variables_spec.rb16
-rw-r--r--spec/ruby/core/struct/keyword_init_spec.rb21
-rw-r--r--spec/ruby/core/struct/length_spec.rb12
-rw-r--r--spec/ruby/core/struct/members_spec.rb13
-rw-r--r--spec/ruby/core/struct/new_spec.rb234
-rw-r--r--spec/ruby/core/struct/select_spec.rb10
-rw-r--r--spec/ruby/core/struct/shared/accessor.rb7
-rw-r--r--spec/ruby/core/struct/shared/dup.rb9
-rw-r--r--spec/ruby/core/struct/shared/equal_value.rb37
-rw-r--r--spec/ruby/core/struct/shared/inspect.rb5
-rw-r--r--spec/ruby/core/struct/shared/select.rb26
-rw-r--r--spec/ruby/core/struct/size_spec.rb11
-rw-r--r--spec/ruby/core/struct/struct_spec.rb43
-rw-r--r--spec/ruby/core/struct/to_a_spec.rb12
-rw-r--r--spec/ruby/core/struct/to_h_spec.rb56
-rw-r--r--spec/ruby/core/struct/to_s_spec.rb12
-rw-r--r--spec/ruby/core/struct/values_at_spec.rb59
-rw-r--r--spec/ruby/core/struct/values_spec.rb11
-rw-r--r--spec/ruby/core/symbol/all_symbols_spec.rb19
-rw-r--r--spec/ruby/core/symbol/capitalize_spec.rb41
-rw-r--r--spec/ruby/core/symbol/case_compare_spec.rb11
-rw-r--r--spec/ruby/core/symbol/casecmp_spec.rb144
-rw-r--r--spec/ruby/core/symbol/comparison_spec.rb51
-rw-r--r--spec/ruby/core/symbol/downcase_spec.rb25
-rw-r--r--spec/ruby/core/symbol/dup_spec.rb7
-rw-r--r--spec/ruby/core/symbol/element_reference_spec.rb6
-rw-r--r--spec/ruby/core/symbol/empty_spec.rb11
-rw-r--r--spec/ruby/core/symbol/encoding_spec.rb23
-rw-r--r--spec/ruby/core/symbol/end_with_spec.rb8
-rw-r--r--spec/ruby/core/symbol/equal_value_spec.rb14
-rw-r--r--spec/ruby/core/symbol/fixtures/classes.rb3
-rw-r--r--spec/ruby/core/symbol/id2name_spec.rb6
-rw-r--r--spec/ruby/core/symbol/inspect_spec.rb105
-rw-r--r--spec/ruby/core/symbol/intern_spec.rb11
-rw-r--r--spec/ruby/core/symbol/length_spec.rb6
-rw-r--r--spec/ruby/core/symbol/match_spec.rb77
-rw-r--r--spec/ruby/core/symbol/name_spec.rb19
-rw-r--r--spec/ruby/core/symbol/next_spec.rb6
-rw-r--r--spec/ruby/core/symbol/shared/id2name.rb16
-rw-r--r--spec/ruby/core/symbol/shared/length.rb23
-rw-r--r--spec/ruby/core/symbol/shared/slice.rb262
-rw-r--r--spec/ruby/core/symbol/shared/succ.rb18
-rw-r--r--spec/ruby/core/symbol/size_spec.rb6
-rw-r--r--spec/ruby/core/symbol/slice_spec.rb6
-rw-r--r--spec/ruby/core/symbol/start_with_spec.rb8
-rw-r--r--spec/ruby/core/symbol/succ_spec.rb6
-rw-r--r--spec/ruby/core/symbol/swapcase_spec.rb29
-rw-r--r--spec/ruby/core/symbol/symbol_spec.rb19
-rw-r--r--spec/ruby/core/symbol/to_proc_spec.rb99
-rw-r--r--spec/ruby/core/symbol/to_s_spec.rb6
-rw-r--r--spec/ruby/core/symbol/to_sym_spec.rb9
-rw-r--r--spec/ruby/core/symbol/upcase_spec.rb21
-rw-r--r--spec/ruby/core/systemexit/initialize_spec.rb26
-rw-r--r--spec/ruby/core/systemexit/success_spec.rb13
-rw-r--r--spec/ruby/core/thread/abort_on_exception_spec.rb106
-rw-r--r--spec/ruby/core/thread/add_trace_func_spec.rb5
-rw-r--r--spec/ruby/core/thread/alive_spec.rb58
-rw-r--r--spec/ruby/core/thread/allocate_spec.rb9
-rw-r--r--spec/ruby/core/thread/backtrace/limit_spec.rb15
-rw-r--r--spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb102
-rw-r--r--spec/ruby/core/thread/backtrace/location/base_label_spec.rb49
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb4
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb2
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_method_added.rb10
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/classes.rb35
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/locations_in_main.rb5
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/locations_in_required.rb3
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/main.rb5
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/path.rb2
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb11
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb1
-rw-r--r--spec/ruby/core/thread/backtrace/location/inspect_spec.rb13
-rw-r--r--spec/ruby/core/thread/backtrace/location/label_spec.rb37
-rw-r--r--spec/ruby/core/thread/backtrace/location/lineno_spec.rb23
-rw-r--r--spec/ruby/core/thread/backtrace/location/path_spec.rb124
-rw-r--r--spec/ruby/core/thread/backtrace/location/to_s_spec.rb13
-rw-r--r--spec/ruby/core/thread/backtrace_locations_spec.rb79
-rw-r--r--spec/ruby/core/thread/backtrace_spec.rb69
-rw-r--r--spec/ruby/core/thread/current_spec.rb31
-rw-r--r--spec/ruby/core/thread/element_reference_spec.rb44
-rw-r--r--spec/ruby/core/thread/element_set_spec.rb51
-rw-r--r--spec/ruby/core/thread/exclusive_spec.rb49
-rw-r--r--spec/ruby/core/thread/exit_spec.rb15
-rw-r--r--spec/ruby/core/thread/fetch_spec.rb36
-rw-r--r--spec/ruby/core/thread/fixtures/classes.rb297
-rw-r--r--spec/ruby/core/thread/fork_spec.rb9
-rw-r--r--spec/ruby/core/thread/group_spec.rb5
-rw-r--r--spec/ruby/core/thread/handle_interrupt_spec.rb125
-rw-r--r--spec/ruby/core/thread/ignore_deadlock_spec.rb21
-rw-r--r--spec/ruby/core/thread/initialize_spec.rb27
-rw-r--r--spec/ruby/core/thread/inspect_spec.rb6
-rw-r--r--spec/ruby/core/thread/join_spec.rb70
-rw-r--r--spec/ruby/core/thread/key_spec.rb53
-rw-r--r--spec/ruby/core/thread/keys_spec.rb44
-rw-r--r--spec/ruby/core/thread/kill_spec.rb25
-rw-r--r--spec/ruby/core/thread/list_spec.rb55
-rw-r--r--spec/ruby/core/thread/main_spec.rb10
-rw-r--r--spec/ruby/core/thread/name_spec.rb54
-rw-r--r--spec/ruby/core/thread/native_thread_id_spec.rb17
-rw-r--r--spec/ruby/core/thread/new_spec.rb83
-rw-r--r--spec/ruby/core/thread/pass_spec.rb8
-rw-r--r--spec/ruby/core/thread/pending_interrupt_spec.rb32
-rw-r--r--spec/ruby/core/thread/priority_spec.rb72
-rw-r--r--spec/ruby/core/thread/raise_spec.rb232
-rw-r--r--spec/ruby/core/thread/report_on_exception_spec.rb157
-rw-r--r--spec/ruby/core/thread/run_spec.rb8
-rw-r--r--spec/ruby/core/thread/set_trace_func_spec.rb5
-rw-r--r--spec/ruby/core/thread/shared/exit.rb200
-rw-r--r--spec/ruby/core/thread/shared/start.rb41
-rw-r--r--spec/ruby/core/thread/shared/to_s.rb53
-rw-r--r--spec/ruby/core/thread/shared/wakeup.rb62
-rw-r--r--spec/ruby/core/thread/start_spec.rb9
-rw-r--r--spec/ruby/core/thread/status_spec.rb60
-rw-r--r--spec/ruby/core/thread/stop_spec.rb54
-rw-r--r--spec/ruby/core/thread/terminate_spec.rb7
-rw-r--r--spec/ruby/core/thread/thread_variable_get_spec.rb25
-rw-r--r--spec/ruby/core/thread/thread_variable_set_spec.rb26
-rw-r--r--spec/ruby/core/thread/thread_variable_spec.rb21
-rw-r--r--spec/ruby/core/thread/thread_variables_spec.rb29
-rw-r--r--spec/ruby/core/thread/to_s_spec.rb6
-rw-r--r--spec/ruby/core/thread/value_spec.rb31
-rw-r--r--spec/ruby/core/thread/wakeup_spec.rb7
-rw-r--r--spec/ruby/core/threadgroup/add_spec.rb39
-rw-r--r--spec/ruby/core/threadgroup/default_spec.rb11
-rw-r--r--spec/ruby/core/threadgroup/enclose_spec.rb24
-rw-r--r--spec/ruby/core/threadgroup/enclosed_spec.rb14
-rw-r--r--spec/ruby/core/threadgroup/list_spec.rb23
-rw-r--r--spec/ruby/core/time/_dump_spec.rb55
-rw-r--r--spec/ruby/core/time/_load_spec.rb52
-rw-r--r--spec/ruby/core/time/asctime_spec.rb6
-rw-r--r--spec/ruby/core/time/at_spec.rb291
-rw-r--r--spec/ruby/core/time/ceil_spec.rb44
-rw-r--r--spec/ruby/core/time/comparison_spec.rb104
-rw-r--r--spec/ruby/core/time/ctime_spec.rb6
-rw-r--r--spec/ruby/core/time/day_spec.rb6
-rw-r--r--spec/ruby/core/time/dst_spec.rb6
-rw-r--r--spec/ruby/core/time/dup_spec.rb46
-rw-r--r--spec/ruby/core/time/eql_spec.rb29
-rw-r--r--spec/ruby/core/time/fixtures/classes.rb106
-rw-r--r--spec/ruby/core/time/floor_spec.rb36
-rw-r--r--spec/ruby/core/time/friday_spec.rb11
-rw-r--r--spec/ruby/core/time/getgm_spec.rb6
-rw-r--r--spec/ruby/core/time/getlocal_spec.rb167
-rw-r--r--spec/ruby/core/time/getutc_spec.rb6
-rw-r--r--spec/ruby/core/time/gm_spec.rb10
-rw-r--r--spec/ruby/core/time/gmt_offset_spec.rb6
-rw-r--r--spec/ruby/core/time/gmt_spec.rb8
-rw-r--r--spec/ruby/core/time/gmtime_spec.rb6
-rw-r--r--spec/ruby/core/time/gmtoff_spec.rb6
-rw-r--r--spec/ruby/core/time/hash_spec.rb11
-rw-r--r--spec/ruby/core/time/hour_spec.rb17
-rw-r--r--spec/ruby/core/time/inspect_spec.rb33
-rw-r--r--spec/ruby/core/time/isdst_spec.rb6
-rw-r--r--spec/ruby/core/time/local_spec.rb11
-rw-r--r--spec/ruby/core/time/localtime_spec.rb152
-rw-r--r--spec/ruby/core/time/mday_spec.rb6
-rw-r--r--spec/ruby/core/time/min_spec.rb17
-rw-r--r--spec/ruby/core/time/minus_spec.rb121
-rw-r--r--spec/ruby/core/time/mktime_spec.rb11
-rw-r--r--spec/ruby/core/time/mon_spec.rb6
-rw-r--r--spec/ruby/core/time/monday_spec.rb11
-rw-r--r--spec/ruby/core/time/month_spec.rb6
-rw-r--r--spec/ruby/core/time/new_spec.rb458
-rw-r--r--spec/ruby/core/time/now_spec.rb51
-rw-r--r--spec/ruby/core/time/nsec_spec.rb31
-rw-r--r--spec/ruby/core/time/plus_spec.rb118
-rw-r--r--spec/ruby/core/time/round_spec.rb35
-rw-r--r--spec/ruby/core/time/saturday_spec.rb11
-rw-r--r--spec/ruby/core/time/sec_spec.rb7
-rw-r--r--spec/ruby/core/time/shared/asctime.rb6
-rw-r--r--spec/ruby/core/time/shared/day.rb15
-rw-r--r--spec/ruby/core/time/shared/getgm.rb9
-rw-r--r--spec/ruby/core/time/shared/gm.rb70
-rw-r--r--spec/ruby/core/time/shared/gmt_offset.rb59
-rw-r--r--spec/ruby/core/time/shared/gmtime.rb33
-rw-r--r--spec/ruby/core/time/shared/inspect.rb21
-rw-r--r--spec/ruby/core/time/shared/isdst.rb8
-rw-r--r--spec/ruby/core/time/shared/local.rb42
-rw-r--r--spec/ruby/core/time/shared/month.rb15
-rw-r--r--spec/ruby/core/time/shared/now.rb33
-rw-r--r--spec/ruby/core/time/shared/time_params.rb267
-rw-r--r--spec/ruby/core/time/shared/to_i.rb16
-rw-r--r--spec/ruby/core/time/strftime_spec.rb93
-rw-r--r--spec/ruby/core/time/subsec_spec.rb27
-rw-r--r--spec/ruby/core/time/succ_spec.rb39
-rw-r--r--spec/ruby/core/time/sunday_spec.rb11
-rw-r--r--spec/ruby/core/time/thursday_spec.rb11
-rw-r--r--spec/ruby/core/time/time_spec.rb7
-rw-r--r--spec/ruby/core/time/to_a_spec.rb12
-rw-r--r--spec/ruby/core/time/to_f_spec.rb7
-rw-r--r--spec/ruby/core/time/to_i_spec.rb6
-rw-r--r--spec/ruby/core/time/to_r_spec.rb11
-rw-r--r--spec/ruby/core/time/to_s_spec.rb6
-rw-r--r--spec/ruby/core/time/tuesday_spec.rb11
-rw-r--r--spec/ruby/core/time/tv_nsec_spec.rb5
-rw-r--r--spec/ruby/core/time/tv_sec_spec.rb6
-rw-r--r--spec/ruby/core/time/tv_usec_spec.rb5
-rw-r--r--spec/ruby/core/time/usec_spec.rb43
-rw-r--r--spec/ruby/core/time/utc_offset_spec.rb6
-rw-r--r--spec/ruby/core/time/utc_spec.rb58
-rw-r--r--spec/ruby/core/time/wday_spec.rb9
-rw-r--r--spec/ruby/core/time/wednesday_spec.rb11
-rw-r--r--spec/ruby/core/time/yday_spec.rb21
-rw-r--r--spec/ruby/core/time/year_spec.rb17
-rw-r--r--spec/ruby/core/time/zone_spec.rb104
-rw-r--r--spec/ruby/core/tracepoint/allow_reentry_spec.rb32
-rw-r--r--spec/ruby/core/tracepoint/binding_spec.rb21
-rw-r--r--spec/ruby/core/tracepoint/callee_id_spec.rb18
-rw-r--r--spec/ruby/core/tracepoint/defined_class_spec.rb27
-rw-r--r--spec/ruby/core/tracepoint/disable_spec.rb76
-rw-r--r--spec/ruby/core/tracepoint/enable_spec.rb574
-rw-r--r--spec/ruby/core/tracepoint/enabled_spec.rb15
-rw-r--r--spec/ruby/core/tracepoint/eval_script_spec.rb23
-rw-r--r--spec/ruby/core/tracepoint/event_spec.rb22
-rw-r--r--spec/ruby/core/tracepoint/fixtures/classes.rb40
-rw-r--r--spec/ruby/core/tracepoint/inspect_spec.rb134
-rw-r--r--spec/ruby/core/tracepoint/lineno_spec.rb20
-rw-r--r--spec/ruby/core/tracepoint/method_id_spec.rb15
-rw-r--r--spec/ruby/core/tracepoint/new_spec.rb72
-rw-r--r--spec/ruby/core/tracepoint/parameters_spec.rb28
-rw-r--r--spec/ruby/core/tracepoint/path_spec.rb26
-rw-r--r--spec/ruby/core/tracepoint/raised_exception_spec.rb20
-rw-r--r--spec/ruby/core/tracepoint/return_value_spec.rb17
-rw-r--r--spec/ruby/core/tracepoint/self_spec.rb26
-rw-r--r--spec/ruby/core/tracepoint/trace_spec.rb10
-rw-r--r--spec/ruby/core/true/and_spec.rb11
-rw-r--r--spec/ruby/core/true/case_compare_spec.rb13
-rw-r--r--spec/ruby/core/true/dup_spec.rb7
-rw-r--r--spec/ruby/core/true/inspect_spec.rb7
-rw-r--r--spec/ruby/core/true/or_spec.rb11
-rw-r--r--spec/ruby/core/true/to_s_spec.rb15
-rw-r--r--spec/ruby/core/true/trueclass_spec.rb15
-rw-r--r--spec/ruby/core/true/xor_spec.rb11
-rw-r--r--spec/ruby/core/unboundmethod/arity_spec.rb207
-rw-r--r--spec/ruby/core/unboundmethod/bind_call_spec.rb50
-rw-r--r--spec/ruby/core/unboundmethod/bind_spec.rb61
-rw-r--r--spec/ruby/core/unboundmethod/clone_spec.rb12
-rw-r--r--spec/ruby/core/unboundmethod/eql_spec.rb5
-rw-r--r--spec/ruby/core/unboundmethod/equal_value_spec.rb157
-rw-r--r--spec/ruby/core/unboundmethod/fixtures/classes.rb103
-rw-r--r--spec/ruby/core/unboundmethod/hash_spec.rb22
-rw-r--r--spec/ruby/core/unboundmethod/inspect_spec.rb7
-rw-r--r--spec/ruby/core/unboundmethod/name_spec.rb15
-rw-r--r--spec/ruby/core/unboundmethod/original_name_spec.rb22
-rw-r--r--spec/ruby/core/unboundmethod/owner_spec.rb33
-rw-r--r--spec/ruby/core/unboundmethod/parameters_spec.rb5
-rw-r--r--spec/ruby/core/unboundmethod/private_spec.rb21
-rw-r--r--spec/ruby/core/unboundmethod/protected_spec.rb21
-rw-r--r--spec/ruby/core/unboundmethod/public_spec.rb21
-rw-r--r--spec/ruby/core/unboundmethod/shared/to_s.rb44
-rw-r--r--spec/ruby/core/unboundmethod/source_location_spec.rb52
-rw-r--r--spec/ruby/core/unboundmethod/super_method_spec.rb51
-rw-r--r--spec/ruby/core/unboundmethod/to_s_spec.rb7
-rw-r--r--spec/ruby/core/warning/element_reference_spec.rb20
-rw-r--r--spec/ruby/core/warning/element_set_spec.rb35
-rw-r--r--spec/ruby/core/warning/warn_spec.rb90
-rw-r--r--spec/ruby/default.mspec50
-rw-r--r--spec/ruby/fixtures/basicobject/method_missing.rb55
-rw-r--r--spec/ruby/fixtures/class.rb142
-rw-r--r--spec/ruby/fixtures/class_variables.rb58
-rw-r--r--spec/ruby/fixtures/code/a/load_fixture.bundle1
-rw-r--r--spec/ruby/fixtures/code/a/load_fixture.dll1
-rw-r--r--spec/ruby/fixtures/code/a/load_fixture.dylib1
-rw-r--r--spec/ruby/fixtures/code/a/load_fixture.so1
-rw-r--r--spec/ruby/fixtures/code/b/load_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/c/load_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/concurrent.rb12
-rw-r--r--spec/ruby/fixtures/code/concurrent2.rb8
-rw-r--r--spec/ruby/fixtures/code/concurrent3.rb2
-rw-r--r--spec/ruby/fixtures/code/concurrent_require_fixture.rb4
-rw-r--r--spec/ruby/fixtures/code/file_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/gem/load_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/line_fixture.rb5
-rw-r--r--spec/ruby/fixtures/code/load_ext_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/load_fixture1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.bundle1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.dll1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.dylib1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext.bundle1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext.dll1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext.dylib1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext.rb1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext.so1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.so1
-rw-r--r--spec/ruby/fixtures/code/load_fixture_and__FILE__.rb1
-rw-r--r--spec/ruby/fixtures/code/load_wrap_fixture.rb12
-rw-r--r--spec/ruby/fixtures/code/load_wrap_method_fixture.rb9
-rw-r--r--spec/ruby/fixtures/code/methods_fixture.rb364
-rw-r--r--spec/ruby/fixtures/code/raise_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/recursive_load_fixture.rb5
-rw-r--r--spec/ruby/fixtures/code/recursive_require_fixture.rb3
-rw-r--r--spec/ruby/fixtures/code/symlink/symlink1.rb1
-rw-r--r--spec/ruby/fixtures/code/symlink/symlink2/symlink2.rb1
-rw-r--r--spec/ruby/fixtures/code_loading.rb41
-rw-r--r--spec/ruby/fixtures/constants.rb323
-rw-r--r--spec/ruby/fixtures/enumerator/classes.rb15
-rw-r--r--spec/ruby/fixtures/math/common.rb3
-rw-r--r--spec/ruby/fixtures/rational.rb14
-rw-r--r--spec/ruby/fixtures/reflection.rb352
-rw-r--r--spec/ruby/language/BEGIN_spec.rb41
-rw-r--r--spec/ruby/language/END_spec.rb15
-rw-r--r--spec/ruby/language/README30
-rw-r--r--spec/ruby/language/alias_spec.rb276
-rw-r--r--spec/ruby/language/and_spec.rb80
-rw-r--r--spec/ruby/language/array_spec.rb162
-rw-r--r--spec/ruby/language/block_spec.rb1102
-rw-r--r--spec/ruby/language/break_spec.rb383
-rw-r--r--spec/ruby/language/case_spec.rb445
-rw-r--r--spec/ruby/language/class_spec.rb363
-rw-r--r--spec/ruby/language/class_variable_spec.rb116
-rw-r--r--spec/ruby/language/comment_spec.rb13
-rw-r--r--spec/ruby/language/constants_spec.rb750
-rw-r--r--spec/ruby/language/def_spec.rb798
-rw-r--r--spec/ruby/language/defined_spec.rb1171
-rw-r--r--spec/ruby/language/delegation_spec.rb65
-rw-r--r--spec/ruby/language/encoding_spec.rb36
-rw-r--r--spec/ruby/language/ensure_spec.rb331
-rw-r--r--spec/ruby/language/execution_spec.rb15
-rw-r--r--spec/ruby/language/file_spec.rb21
-rw-r--r--spec/ruby/language/fixtures/argv_encoding.rb1
-rw-r--r--spec/ruby/language/fixtures/array.rb32
-rw-r--r--spec/ruby/language/fixtures/begin_file.rb3
-rw-r--r--spec/ruby/language/fixtures/binary_symbol.rb4
-rw-r--r--spec/ruby/language/fixtures/block.rb61
-rw-r--r--spec/ruby/language/fixtures/break.rb291
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel.rb9
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel_block.rb23
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel_method.rb17
-rw-r--r--spec/ruby/language/fixtures/bytes_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/case_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/classes.rb31
-rw-r--r--spec/ruby/language/fixtures/coding_us_ascii.rb11
-rw-r--r--spec/ruby/language/fixtures/coding_utf_8.rb11
-rw-r--r--spec/ruby/language/fixtures/constant_visibility.rb114
-rw-r--r--spec/ruby/language/fixtures/constants_sclass.rb54
-rw-r--r--spec/ruby/language/fixtures/def.rb14
-rw-r--r--spec/ruby/language/fixtures/defined.rb306
-rw-r--r--spec/ruby/language/fixtures/delegation.rb11
-rw-r--r--spec/ruby/language/fixtures/dollar_zero.rb6
-rw-r--r--spec/ruby/language/fixtures/emacs_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/ensure.rb121
-rw-r--r--spec/ruby/language/fixtures/file.rb1
-rw-r--r--spec/ruby/language/fixtures/for_scope.rb15
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb5
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb5
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb5
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb4
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_required.rb3
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rbbin0 -> 181 bytes-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb1
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb3
-rw-r--r--spec/ruby/language/fixtures/hash_strings_binary.rb7
-rw-r--r--spec/ruby/language/fixtures/hash_strings_usascii.rb7
-rw-r--r--spec/ruby/language/fixtures/hash_strings_utf8.rb7
-rw-r--r--spec/ruby/language/fixtures/magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/match_operators.rb9
-rw-r--r--spec/ruby/language/fixtures/metaclass.rb33
-rw-r--r--spec/ruby/language/fixtures/module.rb24
-rw-r--r--spec/ruby/language/fixtures/next.rb134
-rw-r--r--spec/ruby/language/fixtures/no_magic_comment.rb1
-rw-r--r--spec/ruby/language/fixtures/precedence.rb16
-rw-r--r--spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb3
-rw-r--r--spec/ruby/language/fixtures/private.rb59
-rw-r--r--spec/ruby/language/fixtures/rescue.rb67
-rw-r--r--spec/ruby/language/fixtures/rescue_captures.rb107
-rw-r--r--spec/ruby/language/fixtures/return.rb135
-rw-r--r--spec/ruby/language/fixtures/second_line_magic_comment.rb3
-rw-r--r--spec/ruby/language/fixtures/second_token_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/send.rb141
-rwxr-xr-xspec/ruby/language/fixtures/shebang_magic_comment.rb3
-rw-r--r--spec/ruby/language/fixtures/squiggly_heredoc.rb71
-rw-r--r--spec/ruby/language/fixtures/super.rb742
-rw-r--r--spec/ruby/language/fixtures/utf16-be-nobom.rbbin0 -> 68 bytes-rw-r--r--spec/ruby/language/fixtures/utf16-le-nobom.rbbin0 -> 69 bytes-rw-r--r--spec/ruby/language/fixtures/utf8-bom.rb2
-rw-r--r--spec/ruby/language/fixtures/utf8-nobom.rb2
-rw-r--r--spec/ruby/language/fixtures/variables.rb85
-rw-r--r--spec/ruby/language/fixtures/vim_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/yield.rb41
-rw-r--r--spec/ruby/language/for_spec.rb182
-rw-r--r--spec/ruby/language/hash_spec.rb258
-rw-r--r--spec/ruby/language/heredoc_spec.rb109
-rw-r--r--spec/ruby/language/if_spec.rb371
-rw-r--r--spec/ruby/language/keyword_arguments_spec.rb397
-rw-r--r--spec/ruby/language/lambda_spec.rb620
-rw-r--r--spec/ruby/language/line_spec.rb45
-rw-r--r--spec/ruby/language/loop_spec.rb67
-rw-r--r--spec/ruby/language/magic_comment_spec.rb92
-rw-r--r--spec/ruby/language/match_spec.rb81
-rw-r--r--spec/ruby/language/metaclass_spec.rb143
-rw-r--r--spec/ruby/language/method_spec.rb1856
-rw-r--r--spec/ruby/language/module_spec.rb110
-rw-r--r--spec/ruby/language/next_spec.rb410
-rw-r--r--spec/ruby/language/not_spec.rb51
-rw-r--r--spec/ruby/language/numbered_parameters_spec.rb118
-rw-r--r--spec/ruby/language/numbers_spec.rb105
-rw-r--r--spec/ruby/language/optional_assignments_spec.rb496
-rw-r--r--spec/ruby/language/or_spec.rb90
-rw-r--r--spec/ruby/language/order_spec.rb75
-rw-r--r--spec/ruby/language/pattern_matching_spec.rb1403
-rw-r--r--spec/ruby/language/precedence_spec.rb445
-rw-r--r--spec/ruby/language/predefined/data_spec.rb48
-rw-r--r--spec/ruby/language/predefined/fixtures/data1.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/data2.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/data3.rb6
-rw-r--r--spec/ruby/language/predefined/fixtures/data4.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/data5.rb5
-rw-r--r--spec/ruby/language/predefined/fixtures/data_offset.rb12
-rw-r--r--spec/ruby/language/predefined/fixtures/data_only.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/empty_data.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/print_data.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb1
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb9
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb2
-rw-r--r--spec/ruby/language/predefined/toplevel_binding_spec.rb34
-rw-r--r--spec/ruby/language/predefined_spec.rb1394
-rw-r--r--spec/ruby/language/private_spec.rb67
-rw-r--r--spec/ruby/language/proc_spec.rb247
-rw-r--r--spec/ruby/language/range_spec.rb30
-rw-r--r--spec/ruby/language/redo_spec.rb66
-rw-r--r--spec/ruby/language/regexp/anchors_spec.rb179
-rw-r--r--spec/ruby/language/regexp/back-references_spec.rb140
-rw-r--r--spec/ruby/language/regexp/character_classes_spec.rb642
-rw-r--r--spec/ruby/language/regexp/empty_checks_spec.rb135
-rw-r--r--spec/ruby/language/regexp/encoding_spec.rb148
-rw-r--r--spec/ruby/language/regexp/escapes_spec.rb169
-rw-r--r--spec/ruby/language/regexp/grouping_spec.rb63
-rw-r--r--spec/ruby/language/regexp/interpolation_spec.rb58
-rw-r--r--spec/ruby/language/regexp/modifiers_spec.rb115
-rw-r--r--spec/ruby/language/regexp/repetition_spec.rb142
-rw-r--r--spec/ruby/language/regexp/subexpression_call_spec.rb50
-rw-r--r--spec/ruby/language/regexp_spec.rb169
-rw-r--r--spec/ruby/language/rescue_spec.rb515
-rw-r--r--spec/ruby/language/retry_spec.rb52
-rw-r--r--spec/ruby/language/return_spec.rb490
-rw-r--r--spec/ruby/language/safe_navigator_spec.rb99
-rw-r--r--spec/ruby/language/safe_spec.rb27
-rw-r--r--spec/ruby/language/send_spec.rb570
-rw-r--r--spec/ruby/language/shared/__FILE__.rb23
-rw-r--r--spec/ruby/language/shared/__LINE__.rb15
-rw-r--r--spec/ruby/language/singleton_class_spec.rb293
-rw-r--r--spec/ruby/language/source_encoding_spec.rb61
-rw-r--r--spec/ruby/language/string_spec.rb299
-rw-r--r--spec/ruby/language/super_spec.rb434
-rw-r--r--spec/ruby/language/symbol_spec.rb106
-rw-r--r--spec/ruby/language/throw_spec.rb81
-rw-r--r--spec/ruby/language/undef_spec.rb72
-rw-r--r--spec/ruby/language/unless_spec.rb43
-rw-r--r--spec/ruby/language/until_spec.rb234
-rw-r--r--spec/ruby/language/variables_spec.rb870
-rw-r--r--spec/ruby/language/while_spec.rb344
-rw-r--r--spec/ruby/language/yield_spec.rb225
-rw-r--r--spec/ruby/library/English/English_spec.rb171
-rw-r--r--spec/ruby/library/English/alias_spec.rb14
-rw-r--r--spec/ruby/library/abbrev/abbrev_spec.rb31
-rw-r--r--spec/ruby/library/base64/decode64_spec.rb29
-rw-r--r--spec/ruby/library/base64/encode64_spec.rb23
-rw-r--r--spec/ruby/library/base64/strict_decode64_spec.rb41
-rw-r--r--spec/ruby/library/base64/strict_encode64_spec.rb19
-rw-r--r--spec/ruby/library/base64/urlsafe_decode64_spec.rb19
-rw-r--r--spec/ruby/library/base64/urlsafe_encode64_spec.rb20
-rw-r--r--spec/ruby/library/bigdecimal/BigDecimal_spec.rb269
-rw-r--r--spec/ruby/library/bigdecimal/abs_spec.rb50
-rw-r--r--spec/ruby/library/bigdecimal/add_spec.rb193
-rw-r--r--spec/ruby/library/bigdecimal/case_compare_spec.rb7
-rw-r--r--spec/ruby/library/bigdecimal/ceil_spec.rb104
-rw-r--r--spec/ruby/library/bigdecimal/clone_spec.rb6
-rw-r--r--spec/ruby/library/bigdecimal/coerce_spec.rb26
-rw-r--r--spec/ruby/library/bigdecimal/comparison_spec.rb81
-rw-r--r--spec/ruby/library/bigdecimal/constants_spec.rb70
-rw-r--r--spec/ruby/library/bigdecimal/div_spec.rb110
-rw-r--r--spec/ruby/library/bigdecimal/divide_spec.rb17
-rw-r--r--spec/ruby/library/bigdecimal/divmod_spec.rb180
-rw-r--r--spec/ruby/library/bigdecimal/double_fig_spec.rb9
-rw-r--r--spec/ruby/library/bigdecimal/dup_spec.rb6
-rw-r--r--spec/ruby/library/bigdecimal/eql_spec.rb6
-rw-r--r--spec/ruby/library/bigdecimal/equal_value_spec.rb7
-rw-r--r--spec/ruby/library/bigdecimal/exponent_spec.rb27
-rw-r--r--spec/ruby/library/bigdecimal/finite_spec.rb34
-rw-r--r--spec/ruby/library/bigdecimal/fix_spec.rb57
-rw-r--r--spec/ruby/library/bigdecimal/fixtures/classes.rb17
-rw-r--r--spec/ruby/library/bigdecimal/floor_spec.rb100
-rw-r--r--spec/ruby/library/bigdecimal/frac_spec.rb48
-rw-r--r--spec/ruby/library/bigdecimal/gt_spec.rb96
-rw-r--r--spec/ruby/library/bigdecimal/gte_spec.rb100
-rw-r--r--spec/ruby/library/bigdecimal/hash_spec.rb30
-rw-r--r--spec/ruby/library/bigdecimal/infinite_spec.rb32
-rw-r--r--spec/ruby/library/bigdecimal/inspect_spec.rb30
-rw-r--r--spec/ruby/library/bigdecimal/limit_spec.rb55
-rw-r--r--spec/ruby/library/bigdecimal/lt_spec.rb94
-rw-r--r--spec/ruby/library/bigdecimal/lte_spec.rb100
-rw-r--r--spec/ruby/library/bigdecimal/minus_spec.rb66
-rw-r--r--spec/ruby/library/bigdecimal/mode_spec.rb36
-rw-r--r--spec/ruby/library/bigdecimal/modulo_spec.rb12
-rw-r--r--spec/ruby/library/bigdecimal/mult_spec.rb32
-rw-r--r--spec/ruby/library/bigdecimal/multiply_spec.rb41
-rw-r--r--spec/ruby/library/bigdecimal/nan_spec.rb23
-rw-r--r--spec/ruby/library/bigdecimal/nonzero_spec.rb29
-rw-r--r--spec/ruby/library/bigdecimal/plus_spec.rb54
-rw-r--r--spec/ruby/library/bigdecimal/power_spec.rb6
-rw-r--r--spec/ruby/library/bigdecimal/precs_spec.rb55
-rw-r--r--spec/ruby/library/bigdecimal/quo_spec.rb12
-rw-r--r--spec/ruby/library/bigdecimal/remainder_spec.rb92
-rw-r--r--spec/ruby/library/bigdecimal/round_spec.rb242
-rw-r--r--spec/ruby/library/bigdecimal/shared/clone.rb13
-rw-r--r--spec/ruby/library/bigdecimal/shared/eql.rb61
-rw-r--r--spec/ruby/library/bigdecimal/shared/modulo.rb125
-rw-r--r--spec/ruby/library/bigdecimal/shared/mult.rb97
-rw-r--r--spec/ruby/library/bigdecimal/shared/power.rb72
-rw-r--r--spec/ruby/library/bigdecimal/shared/quo.rb67
-rw-r--r--spec/ruby/library/bigdecimal/shared/to_int.rb16
-rw-r--r--spec/ruby/library/bigdecimal/sign_spec.rb46
-rw-r--r--spec/ruby/library/bigdecimal/split_spec.rb86
-rw-r--r--spec/ruby/library/bigdecimal/sqrt_spec.rb112
-rw-r--r--spec/ruby/library/bigdecimal/sub_spec.rb70
-rw-r--r--spec/ruby/library/bigdecimal/to_d_spec.rb10
-rw-r--r--spec/ruby/library/bigdecimal/to_f_spec.rb54
-rw-r--r--spec/ruby/library/bigdecimal/to_i_spec.rb7
-rw-r--r--spec/ruby/library/bigdecimal/to_int_spec.rb8
-rw-r--r--spec/ruby/library/bigdecimal/to_r_spec.rb28
-rw-r--r--spec/ruby/library/bigdecimal/to_s_spec.rb97
-rw-r--r--spec/ruby/library/bigdecimal/truncate_spec.rb81
-rw-r--r--spec/ruby/library/bigdecimal/uminus_spec.rb58
-rw-r--r--spec/ruby/library/bigdecimal/uplus_spec.rb17
-rw-r--r--spec/ruby/library/bigdecimal/util_spec.rb40
-rw-r--r--spec/ruby/library/bigdecimal/zero_spec.rb27
-rw-r--r--spec/ruby/library/bigmath/log_spec.rb10
-rw-r--r--spec/ruby/library/cgi/cookie/domain_spec.rb23
-rw-r--r--spec/ruby/library/cgi/cookie/expires_spec.rb23
-rw-r--r--spec/ruby/library/cgi/cookie/initialize_spec.rb147
-rw-r--r--spec/ruby/library/cgi/cookie/name_spec.rb23
-rw-r--r--spec/ruby/library/cgi/cookie/parse_spec.rb26
-rw-r--r--spec/ruby/library/cgi/cookie/path_spec.rb23
-rw-r--r--spec/ruby/library/cgi/cookie/secure_spec.rb70
-rw-r--r--spec/ruby/library/cgi/cookie/to_s_spec.rb33
-rw-r--r--spec/ruby/library/cgi/cookie/value_spec.rb76
-rw-r--r--spec/ruby/library/cgi/escapeElement_spec.rb20
-rw-r--r--spec/ruby/library/cgi/escapeHTML_spec.rb17
-rw-r--r--spec/ruby/library/cgi/escape_spec.rb18
-rw-r--r--spec/ruby/library/cgi/htmlextension/a_spec.rb49
-rw-r--r--spec/ruby/library/cgi/htmlextension/base_spec.rb33
-rw-r--r--spec/ruby/library/cgi/htmlextension/blockquote_spec.rb33
-rw-r--r--spec/ruby/library/cgi/htmlextension/br_spec.rb22
-rw-r--r--spec/ruby/library/cgi/htmlextension/caption_spec.rb33
-rw-r--r--spec/ruby/library/cgi/htmlextension/checkbox_group_spec.rb76
-rw-r--r--spec/ruby/library/cgi/htmlextension/checkbox_spec.rb77
-rw-r--r--spec/ruby/library/cgi/htmlextension/doctype_spec.rb27
-rw-r--r--spec/ruby/library/cgi/htmlextension/file_field_spec.rb72
-rw-r--r--spec/ruby/library/cgi/htmlextension/fixtures/common.rb15
-rw-r--r--spec/ruby/library/cgi/htmlextension/form_spec.rb58
-rw-r--r--spec/ruby/library/cgi/htmlextension/frame_spec.rb14
-rw-r--r--spec/ruby/library/cgi/htmlextension/frameset_spec.rb14
-rw-r--r--spec/ruby/library/cgi/htmlextension/hidden_spec.rb59
-rw-r--r--spec/ruby/library/cgi/htmlextension/html_spec.rb66
-rw-r--r--spec/ruby/library/cgi/htmlextension/image_button_spec.rb69
-rw-r--r--spec/ruby/library/cgi/htmlextension/img_spec.rb83
-rw-r--r--spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb64
-rw-r--r--spec/ruby/library/cgi/htmlextension/password_field_spec.rb84
-rw-r--r--spec/ruby/library/cgi/htmlextension/popup_menu_spec.rb8
-rw-r--r--spec/ruby/library/cgi/htmlextension/radio_button_spec.rb77
-rw-r--r--spec/ruby/library/cgi/htmlextension/radio_group_spec.rb77
-rw-r--r--spec/ruby/library/cgi/htmlextension/reset_spec.rb57
-rw-r--r--spec/ruby/library/cgi/htmlextension/scrolling_list_spec.rb8
-rw-r--r--spec/ruby/library/cgi/htmlextension/shared/popup_menu.rb94
-rw-r--r--spec/ruby/library/cgi/htmlextension/submit_spec.rb57
-rw-r--r--spec/ruby/library/cgi/htmlextension/text_field_spec.rb84
-rw-r--r--spec/ruby/library/cgi/htmlextension/textarea_spec.rb73
-rw-r--r--spec/ruby/library/cgi/http_header_spec.rb8
-rw-r--r--spec/ruby/library/cgi/initialize_spec.rb133
-rw-r--r--spec/ruby/library/cgi/out_spec.rb51
-rw-r--r--spec/ruby/library/cgi/parse_spec.rb24
-rw-r--r--spec/ruby/library/cgi/pretty_spec.rb24
-rw-r--r--spec/ruby/library/cgi/print_spec.rb26
-rw-r--r--spec/ruby/library/cgi/queryextension/accept_charset_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/accept_encoding_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/accept_language_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/accept_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/auth_type_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/cache_control_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/content_length_spec.rb26
-rw-r--r--spec/ruby/library/cgi/queryextension/content_type_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/cookies_spec.rb10
-rw-r--r--spec/ruby/library/cgi/queryextension/element_reference_spec.rb27
-rw-r--r--spec/ruby/library/cgi/queryextension/from_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/gateway_interface_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/has_key_spec.rb7
-rw-r--r--spec/ruby/library/cgi/queryextension/host_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/include_spec.rb7
-rw-r--r--spec/ruby/library/cgi/queryextension/key_spec.rb7
-rw-r--r--spec/ruby/library/cgi/queryextension/keys_spec.rb20
-rw-r--r--spec/ruby/library/cgi/queryextension/multipart_spec.rb40
-rw-r--r--spec/ruby/library/cgi/queryextension/negotiate_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/params_spec.rb37
-rw-r--r--spec/ruby/library/cgi/queryextension/path_info_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/path_translated_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/pragma_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/query_string_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/raw_cookie2_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/raw_cookie_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/referer_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/remote_addr_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/remote_host_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/remote_ident_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/remote_user_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/request_method_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/script_name_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/server_name_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/server_port_spec.rb26
-rw-r--r--spec/ruby/library/cgi/queryextension/server_protocol_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/server_software_spec.rb22
-rw-r--r--spec/ruby/library/cgi/queryextension/shared/has_key.rb19
-rw-r--r--spec/ruby/library/cgi/queryextension/user_agent_spec.rb22
-rw-r--r--spec/ruby/library/cgi/rfc1123_date_spec.rb10
-rw-r--r--spec/ruby/library/cgi/shared/http_header.rb112
-rw-r--r--spec/ruby/library/cgi/unescapeElement_spec.rb20
-rw-r--r--spec/ruby/library/cgi/unescapeHTML_spec.rb44
-rw-r--r--spec/ruby/library/cgi/unescape_spec.rb15
-rw-r--r--spec/ruby/library/coverage/fixtures/eval_code.rb11
-rw-r--r--spec/ruby/library/coverage/fixtures/second_class.rb5
-rw-r--r--spec/ruby/library/coverage/fixtures/some_class.rb16
-rw-r--r--spec/ruby/library/coverage/fixtures/start_coverage.rb3
-rw-r--r--spec/ruby/library/coverage/peek_result_spec.rb64
-rw-r--r--spec/ruby/library/coverage/result_spec.rb141
-rw-r--r--spec/ruby/library/coverage/running_spec.rb20
-rw-r--r--spec/ruby/library/coverage/start_spec.rb12
-rw-r--r--spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/basicwriter/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/basicwriter/terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/cell/data_spec.rb6
-rw-r--r--spec/ruby/library/csv/cell/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/fixtures/one_line.csv1
-rw-r--r--spec/ruby/library/csv/foreach_spec.rb6
-rw-r--r--spec/ruby/library/csv/generate_line_spec.rb30
-rw-r--r--spec/ruby/library/csv/generate_row_spec.rb6
-rw-r--r--spec/ruby/library/csv/generate_spec.rb32
-rw-r--r--spec/ruby/library/csv/iobuf/close_spec.rb6
-rw-r--r--spec/ruby/library/csv/iobuf/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/iobuf/read_spec.rb6
-rw-r--r--spec/ruby/library/csv/iobuf/terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/ioreader/get_row_spec.rb6
-rw-r--r--spec/ruby/library/csv/ioreader/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/ioreader/terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/liberal_parsing_spec.rb19
-rw-r--r--spec/ruby/library/csv/open_spec.rb6
-rw-r--r--spec/ruby/library/csv/parse_spec.rb93
-rw-r--r--spec/ruby/library/csv/read_spec.rb6
-rw-r--r--spec/ruby/library/csv/readlines_spec.rb35
-rw-r--r--spec/ruby/library/csv/streambuf/add_buf_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/buf_size_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/drop_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/element_reference_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/get_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/is_eos_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/read_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/rel_buf_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/stringreader/get_row_spec.rb6
-rw-r--r--spec/ruby/library/csv/stringreader/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/add_row_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/append_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/close_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/create_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/generate_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/terminate_spec.rb6
-rw-r--r--spec/ruby/library/date/accessor_spec.rb91
-rw-r--r--spec/ruby/library/date/add_month_spec.rb38
-rw-r--r--spec/ruby/library/date/add_spec.rb30
-rw-r--r--spec/ruby/library/date/ajd_spec.rb6
-rw-r--r--spec/ruby/library/date/ajd_to_amjd_spec.rb6
-rw-r--r--spec/ruby/library/date/ajd_to_jd_spec.rb6
-rw-r--r--spec/ruby/library/date/amjd_spec.rb6
-rw-r--r--spec/ruby/library/date/amjd_to_ajd_spec.rb6
-rw-r--r--spec/ruby/library/date/append_spec.rb6
-rw-r--r--spec/ruby/library/date/asctime_spec.rb6
-rw-r--r--spec/ruby/library/date/boat_spec.rb24
-rw-r--r--spec/ruby/library/date/case_compare_spec.rb6
-rw-r--r--spec/ruby/library/date/civil_spec.rb7
-rw-r--r--spec/ruby/library/date/commercial_spec.rb17
-rw-r--r--spec/ruby/library/date/commercial_to_jd_spec.rb6
-rw-r--r--spec/ruby/library/date/comparison_spec.rb6
-rw-r--r--spec/ruby/library/date/constants_spec.rb48
-rw-r--r--spec/ruby/library/date/conversions_spec.rb43
-rw-r--r--spec/ruby/library/date/ctime_spec.rb6
-rw-r--r--spec/ruby/library/date/cwday_spec.rb6
-rw-r--r--spec/ruby/library/date/cweek_spec.rb6
-rw-r--r--spec/ruby/library/date/cwyear_spec.rb6
-rw-r--r--spec/ruby/library/date/day_fraction_spec.rb6
-rw-r--r--spec/ruby/library/date/day_fraction_to_time_spec.rb6
-rw-r--r--spec/ruby/library/date/day_spec.rb9
-rw-r--r--spec/ruby/library/date/downto_spec.rb18
-rw-r--r--spec/ruby/library/date/england_spec.rb6
-rw-r--r--spec/ruby/library/date/eql_spec.rb12
-rw-r--r--spec/ruby/library/date/format/bag/method_missing_spec.rb6
-rw-r--r--spec/ruby/library/date/format/bag/to_hash_spec.rb6
-rw-r--r--spec/ruby/library/date/friday_spec.rb12
-rw-r--r--spec/ruby/library/date/gregorian_leap_spec.rb15
-rw-r--r--spec/ruby/library/date/gregorian_spec.rb16
-rw-r--r--spec/ruby/library/date/hash_spec.rb8
-rw-r--r--spec/ruby/library/date/infinity/abs_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/coerce_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/comparison_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/d_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/finite_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/infinite_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/nan_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/uminus_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/uplus_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/zero_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity_spec.rb67
-rw-r--r--spec/ruby/library/date/inspect_spec.rb6
-rw-r--r--spec/ruby/library/date/iso8601_spec.rb35
-rw-r--r--spec/ruby/library/date/italy_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_spec.rb15
-rw-r--r--spec/ruby/library/date/jd_to_ajd_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_civil_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_commercial_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_ld_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_mjd_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_ordinal_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_wday_spec.rb6
-rw-r--r--spec/ruby/library/date/julian_leap_spec.rb15
-rw-r--r--spec/ruby/library/date/julian_spec.rb16
-rw-r--r--spec/ruby/library/date/ld_spec.rb6
-rw-r--r--spec/ruby/library/date/ld_to_jd_spec.rb6
-rw-r--r--spec/ruby/library/date/leap_spec.rb10
-rw-r--r--spec/ruby/library/date/mday_spec.rb6
-rw-r--r--spec/ruby/library/date/minus_month_spec.rb23
-rw-r--r--spec/ruby/library/date/minus_spec.rb30
-rw-r--r--spec/ruby/library/date/mjd_spec.rb6
-rw-r--r--spec/ruby/library/date/mjd_to_jd_spec.rb6
-rw-r--r--spec/ruby/library/date/mon_spec.rb6
-rw-r--r--spec/ruby/library/date/monday_spec.rb8
-rw-r--r--spec/ruby/library/date/month_spec.rb9
-rw-r--r--spec/ruby/library/date/new_spec.rb8
-rw-r--r--spec/ruby/library/date/new_start_spec.rb6
-rw-r--r--spec/ruby/library/date/next_day_spec.rb14
-rw-r--r--spec/ruby/library/date/next_month_spec.rb29
-rw-r--r--spec/ruby/library/date/next_spec.rb6
-rw-r--r--spec/ruby/library/date/next_year_spec.rb12
-rw-r--r--spec/ruby/library/date/ordinal_spec.rb7
-rw-r--r--spec/ruby/library/date/ordinal_to_jd_spec.rb6
-rw-r--r--spec/ruby/library/date/parse_spec.rb159
-rw-r--r--spec/ruby/library/date/plus_spec.rb20
-rw-r--r--spec/ruby/library/date/prev_day_spec.rb14
-rw-r--r--spec/ruby/library/date/prev_month_spec.rb29
-rw-r--r--spec/ruby/library/date/prev_year_spec.rb12
-rw-r--r--spec/ruby/library/date/relationship_spec.rb20
-rw-r--r--spec/ruby/library/date/rfc3339_spec.rb13
-rw-r--r--spec/ruby/library/date/right_shift_spec.rb6
-rw-r--r--spec/ruby/library/date/saturday_spec.rb8
-rw-r--r--spec/ruby/library/date/shared/civil.rb57
-rw-r--r--spec/ruby/library/date/shared/commercial.rb39
-rw-r--r--spec/ruby/library/date/shared/jd.rb14
-rw-r--r--spec/ruby/library/date/shared/new_bang.rb14
-rw-r--r--spec/ruby/library/date/shared/ordinal.rb22
-rw-r--r--spec/ruby/library/date/shared/parse.rb54
-rw-r--r--spec/ruby/library/date/shared/parse_eu.rb37
-rw-r--r--spec/ruby/library/date/shared/parse_us.rb36
-rw-r--r--spec/ruby/library/date/shared/valid_civil.rb36
-rw-r--r--spec/ruby/library/date/shared/valid_commercial.rb34
-rw-r--r--spec/ruby/library/date/shared/valid_jd.rb20
-rw-r--r--spec/ruby/library/date/shared/valid_ordinal.rb26
-rw-r--r--spec/ruby/library/date/start_spec.rb6
-rw-r--r--spec/ruby/library/date/step_spec.rb56
-rw-r--r--spec/ruby/library/date/strftime_spec.rb49
-rw-r--r--spec/ruby/library/date/strptime_spec.rb149
-rw-r--r--spec/ruby/library/date/succ_spec.rb6
-rw-r--r--spec/ruby/library/date/sunday_spec.rb8
-rw-r--r--spec/ruby/library/date/thursday_spec.rb8
-rw-r--r--spec/ruby/library/date/time_to_day_fraction_spec.rb6
-rw-r--r--spec/ruby/library/date/to_s_spec.rb6
-rw-r--r--spec/ruby/library/date/today_spec.rb14
-rw-r--r--spec/ruby/library/date/tuesday_spec.rb8
-rw-r--r--spec/ruby/library/date/upto_spec.rb16
-rw-r--r--spec/ruby/library/date/valid_civil_spec.rb9
-rw-r--r--spec/ruby/library/date/valid_commercial_spec.rb8
-rw-r--r--spec/ruby/library/date/valid_date_spec.rb7
-rw-r--r--spec/ruby/library/date/valid_jd_spec.rb9
-rw-r--r--spec/ruby/library/date/valid_ordinal_spec.rb9
-rw-r--r--spec/ruby/library/date/valid_time_spec.rb6
-rw-r--r--spec/ruby/library/date/wday_spec.rb9
-rw-r--r--spec/ruby/library/date/wednesday_spec.rb8
-rw-r--r--spec/ruby/library/date/yday_spec.rb6
-rw-r--r--spec/ruby/library/date/year_spec.rb9
-rw-r--r--spec/ruby/library/date/zone_to_diff_spec.rb6
-rw-r--r--spec/ruby/library/datetime/_strptime_spec.rb6
-rw-r--r--spec/ruby/library/datetime/add_spec.rb9
-rw-r--r--spec/ruby/library/datetime/civil_spec.rb6
-rw-r--r--spec/ruby/library/datetime/commercial_spec.rb6
-rw-r--r--spec/ruby/library/datetime/hour_spec.rb47
-rw-r--r--spec/ruby/library/datetime/httpdate_spec.rb6
-rw-r--r--spec/ruby/library/datetime/iso8601_spec.rb10
-rw-r--r--spec/ruby/library/datetime/jd_spec.rb6
-rw-r--r--spec/ruby/library/datetime/jisx0301_spec.rb10
-rw-r--r--spec/ruby/library/datetime/min_spec.rb6
-rw-r--r--spec/ruby/library/datetime/minute_spec.rb6
-rw-r--r--spec/ruby/library/datetime/new_offset_spec.rb6
-rw-r--r--spec/ruby/library/datetime/new_spec.rb52
-rw-r--r--spec/ruby/library/datetime/now_spec.rb25
-rw-r--r--spec/ruby/library/datetime/offset_spec.rb6
-rw-r--r--spec/ruby/library/datetime/ordinal_spec.rb6
-rw-r--r--spec/ruby/library/datetime/parse_spec.rb127
-rw-r--r--spec/ruby/library/datetime/rfc2822_spec.rb6
-rw-r--r--spec/ruby/library/datetime/rfc3339_spec.rb10
-rw-r--r--spec/ruby/library/datetime/rfc822_spec.rb6
-rw-r--r--spec/ruby/library/datetime/sec_fraction_spec.rb6
-rw-r--r--spec/ruby/library/datetime/sec_spec.rb6
-rw-r--r--spec/ruby/library/datetime/second_fraction_spec.rb6
-rw-r--r--spec/ruby/library/datetime/second_spec.rb6
-rw-r--r--spec/ruby/library/datetime/shared/min.rb40
-rw-r--r--spec/ruby/library/datetime/shared/sec.rb45
-rw-r--r--spec/ruby/library/datetime/strftime_spec.rb61
-rw-r--r--spec/ruby/library/datetime/strptime_spec.rb6
-rw-r--r--spec/ruby/library/datetime/subtract_spec.rb19
-rw-r--r--spec/ruby/library/datetime/to_date_spec.rb37
-rw-r--r--spec/ruby/library/datetime/to_datetime_spec.rb9
-rw-r--r--spec/ruby/library/datetime/to_s_spec.rb17
-rw-r--r--spec/ruby/library/datetime/to_time_spec.rb50
-rw-r--r--spec/ruby/library/datetime/xmlschema_spec.rb10
-rw-r--r--spec/ruby/library/datetime/zone_spec.rb6
-rw-r--r--spec/ruby/library/delegate/delegate_class/instance_method_spec.rb52
-rw-r--r--spec/ruby/library/delegate/delegate_class/instance_methods_spec.rb26
-rw-r--r--spec/ruby/library/delegate/delegate_class/private_instance_methods_spec.rb23
-rw-r--r--spec/ruby/library/delegate/delegate_class/protected_instance_methods_spec.rb29
-rw-r--r--spec/ruby/library/delegate/delegate_class/public_instance_methods_spec.rb25
-rw-r--r--spec/ruby/library/delegate/delegate_class/respond_to_missing_spec.rb23
-rw-r--r--spec/ruby/library/delegate/delegator/case_compare_spec.rb11
-rw-r--r--spec/ruby/library/delegate/delegator/compare_spec.rb11
-rw-r--r--spec/ruby/library/delegate/delegator/complement_spec.rb11
-rw-r--r--spec/ruby/library/delegate/delegator/eql_spec.rb35
-rw-r--r--spec/ruby/library/delegate/delegator/equal_spec.rb13
-rw-r--r--spec/ruby/library/delegate/delegator/equal_value_spec.rb24
-rw-r--r--spec/ruby/library/delegate/delegator/frozen_spec.rb39
-rw-r--r--spec/ruby/library/delegate/delegator/hash_spec.rb11
-rw-r--r--spec/ruby/library/delegate/delegator/marshal_spec.rb21
-rw-r--r--spec/ruby/library/delegate/delegator/method_spec.rb69
-rw-r--r--spec/ruby/library/delegate/delegator/methods_spec.rb37
-rw-r--r--spec/ruby/library/delegate/delegator/not_equal_spec.rb24
-rw-r--r--spec/ruby/library/delegate/delegator/not_spec.rb11
-rw-r--r--spec/ruby/library/delegate/delegator/private_methods_spec.rb20
-rw-r--r--spec/ruby/library/delegate/delegator/protected_methods_spec.rb18
-rw-r--r--spec/ruby/library/delegate/delegator/public_methods_spec.rb18
-rw-r--r--spec/ruby/library/delegate/delegator/send_spec.rb26
-rw-r--r--spec/ruby/library/delegate/delegator/taint_spec.rb8
-rw-r--r--spec/ruby/library/delegate/delegator/tap_spec.rb16
-rw-r--r--spec/ruby/library/delegate/delegator/trust_spec.rb8
-rw-r--r--spec/ruby/library/delegate/delegator/untaint_spec.rb8
-rw-r--r--spec/ruby/library/delegate/delegator/untrust_spec.rb8
-rw-r--r--spec/ruby/library/delegate/fixtures/classes.rb60
-rw-r--r--spec/ruby/library/digest/bubblebabble_spec.rb29
-rw-r--r--spec/ruby/library/digest/hexencode_spec.rb31
-rw-r--r--spec/ruby/library/digest/instance/append_spec.rb7
-rw-r--r--spec/ruby/library/digest/instance/new_spec.rb19
-rw-r--r--spec/ruby/library/digest/instance/shared/update.rb8
-rw-r--r--spec/ruby/library/digest/instance/update_spec.rb7
-rw-r--r--spec/ruby/library/digest/md5/append_spec.rb7
-rw-r--r--spec/ruby/library/digest/md5/block_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/md5/digest_bang_spec.rb13
-rw-r--r--spec/ruby/library/digest/md5/digest_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/md5/digest_spec.rb32
-rw-r--r--spec/ruby/library/digest/md5/equal_spec.rb37
-rw-r--r--spec/ruby/library/digest/md5/file_spec.rb43
-rw-r--r--spec/ruby/library/digest/md5/hexdigest_bang_spec.rb14
-rw-r--r--spec/ruby/library/digest/md5/hexdigest_spec.rb32
-rw-r--r--spec/ruby/library/digest/md5/inspect_spec.rb11
-rw-r--r--spec/ruby/library/digest/md5/length_spec.rb7
-rw-r--r--spec/ruby/library/digest/md5/reset_spec.rb14
-rw-r--r--spec/ruby/library/digest/md5/shared/constants.rb17
-rw-r--r--spec/ruby/library/digest/md5/shared/length.rb8
-rw-r--r--spec/ruby/library/digest/md5/shared/sample.rb17
-rw-r--r--spec/ruby/library/digest/md5/shared/update.rb7
-rw-r--r--spec/ruby/library/digest/md5/size_spec.rb7
-rw-r--r--spec/ruby/library/digest/md5/to_s_spec.rb24
-rw-r--r--spec/ruby/library/digest/md5/update_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha1/digest_spec.rb20
-rw-r--r--spec/ruby/library/digest/sha1/file_spec.rb43
-rw-r--r--spec/ruby/library/digest/sha1/shared/constants.rb18
-rw-r--r--spec/ruby/library/digest/sha2/hexdigest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha256/append_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha256/block_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha256/digest_bang_spec.rb13
-rw-r--r--spec/ruby/library/digest/sha256/digest_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha256/digest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha256/equal_spec.rb36
-rw-r--r--spec/ruby/library/digest/sha256/file_spec.rb47
-rw-r--r--spec/ruby/library/digest/sha256/hexdigest_bang_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha256/hexdigest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha256/inspect_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha256/length_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha256/reset_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha256/shared/constants.rb18
-rw-r--r--spec/ruby/library/digest/sha256/shared/length.rb8
-rw-r--r--spec/ruby/library/digest/sha256/shared/update.rb7
-rw-r--r--spec/ruby/library/digest/sha256/size_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha256/to_s_spec.rb21
-rw-r--r--spec/ruby/library/digest/sha256/update_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha384/append_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha384/block_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha384/digest_bang_spec.rb13
-rw-r--r--spec/ruby/library/digest/sha384/digest_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha384/digest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha384/equal_spec.rb36
-rw-r--r--spec/ruby/library/digest/sha384/file_spec.rb43
-rw-r--r--spec/ruby/library/digest/sha384/hexdigest_bang_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha384/hexdigest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha384/inspect_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha384/length_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha384/reset_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha384/shared/constants.rb19
-rw-r--r--spec/ruby/library/digest/sha384/shared/length.rb8
-rw-r--r--spec/ruby/library/digest/sha384/shared/update.rb7
-rw-r--r--spec/ruby/library/digest/sha384/size_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha384/to_s_spec.rb21
-rw-r--r--spec/ruby/library/digest/sha384/update_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha512/append_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha512/block_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha512/digest_bang_spec.rb13
-rw-r--r--spec/ruby/library/digest/sha512/digest_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha512/digest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha512/equal_spec.rb36
-rw-r--r--spec/ruby/library/digest/sha512/file_spec.rb43
-rw-r--r--spec/ruby/library/digest/sha512/hexdigest_bang_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha512/hexdigest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha512/inspect_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha512/length_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha512/reset_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha512/shared/constants.rb18
-rw-r--r--spec/ruby/library/digest/sha512/shared/length.rb8
-rw-r--r--spec/ruby/library/digest/sha512/shared/update.rb7
-rw-r--r--spec/ruby/library/digest/sha512/size_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha512/to_s_spec.rb21
-rw-r--r--spec/ruby/library/digest/sha512/update_spec.rb7
-rw-r--r--spec/ruby/library/drb/fixtures/test_server.rb8
-rw-r--r--spec/ruby/library/drb/start_service_spec.rb28
-rw-r--r--spec/ruby/library/erb/def_class_spec.rb29
-rw-r--r--spec/ruby/library/erb/def_method_spec.rb26
-rw-r--r--spec/ruby/library/erb/def_module_spec.rb27
-rw-r--r--spec/ruby/library/erb/defmethod/def_erb_method_spec.rb64
-rw-r--r--spec/ruby/library/erb/filename_spec.rb40
-rw-r--r--spec/ruby/library/erb/fixtures/classes.rb5
-rw-r--r--spec/ruby/library/erb/new_spec.rb157
-rw-r--r--spec/ruby/library/erb/result_spec.rb86
-rw-r--r--spec/ruby/library/erb/run_spec.rb96
-rw-r--r--spec/ruby/library/erb/src_spec.rb33
-rw-r--r--spec/ruby/library/erb/util/h_spec.rb7
-rw-r--r--spec/ruby/library/erb/util/html_escape_spec.rb7
-rw-r--r--spec/ruby/library/erb/util/shared/html_escape.rb42
-rw-r--r--spec/ruby/library/erb/util/shared/url_encode.rb42
-rw-r--r--spec/ruby/library/erb/util/u_spec.rb7
-rw-r--r--spec/ruby/library/erb/util/url_encode_spec.rb7
-rw-r--r--spec/ruby/library/etc/confstr_spec.rb14
-rw-r--r--spec/ruby/library/etc/endgrent_spec.rb7
-rw-r--r--spec/ruby/library/etc/endpwent_spec.rb7
-rw-r--r--spec/ruby/library/etc/getgrent_spec.rb7
-rw-r--r--spec/ruby/library/etc/getgrgid_spec.rb69
-rw-r--r--spec/ruby/library/etc/getgrnam_spec.rb30
-rw-r--r--spec/ruby/library/etc/getlogin_spec.rb43
-rw-r--r--spec/ruby/library/etc/getpwent_spec.rb7
-rw-r--r--spec/ruby/library/etc/getpwnam_spec.rb28
-rw-r--r--spec/ruby/library/etc/getpwuid_spec.rb36
-rw-r--r--spec/ruby/library/etc/group_spec.rb27
-rw-r--r--spec/ruby/library/etc/nprocessors_spec.rb9
-rw-r--r--spec/ruby/library/etc/passwd_spec.rb15
-rw-r--r--spec/ruby/library/etc/shared/windows.rb7
-rw-r--r--spec/ruby/library/etc/struct_group_spec.rb35
-rw-r--r--spec/ruby/library/etc/struct_passwd_spec.rb43
-rw-r--r--spec/ruby/library/etc/sysconf_spec.rb22
-rw-r--r--spec/ruby/library/etc/sysconfdir_spec.rb8
-rw-r--r--spec/ruby/library/etc/systmpdir_spec.rb8
-rw-r--r--spec/ruby/library/expect/expect_spec.rb62
-rw-r--r--spec/ruby/library/fiber/alive_spec.rb46
-rw-r--r--spec/ruby/library/fiber/current_spec.rb63
-rw-r--r--spec/ruby/library/fiber/resume_spec.rb35
-rw-r--r--spec/ruby/library/fiber/transfer_spec.rb128
-rw-r--r--spec/ruby/library/fiddle/handle/initialize_spec.rb10
-rw-r--r--spec/ruby/library/find/find_spec.rb30
-rw-r--r--spec/ruby/library/find/fixtures/common.rb174
-rw-r--r--spec/ruby/library/find/prune_spec.rb12
-rw-r--r--spec/ruby/library/getoptlong/each_option_spec.rb7
-rw-r--r--spec/ruby/library/getoptlong/each_spec.rb7
-rw-r--r--spec/ruby/library/getoptlong/error_message_spec.rb23
-rw-r--r--spec/ruby/library/getoptlong/get_option_spec.rb7
-rw-r--r--spec/ruby/library/getoptlong/get_spec.rb7
-rw-r--r--spec/ruby/library/getoptlong/initialize_spec.rb28
-rw-r--r--spec/ruby/library/getoptlong/ordering_spec.rb38
-rw-r--r--spec/ruby/library/getoptlong/set_options_spec.rb98
-rw-r--r--spec/ruby/library/getoptlong/shared/each.rb18
-rw-r--r--spec/ruby/library/getoptlong/shared/get.rb62
-rw-r--r--spec/ruby/library/getoptlong/terminate_spec.rb30
-rw-r--r--spec/ruby/library/getoptlong/terminated_spec.rb17
-rw-r--r--spec/ruby/library/io-wait/wait_readable_spec.rb27
-rw-r--r--spec/ruby/library/io-wait/wait_writable_spec.rb20
-rw-r--r--spec/ruby/library/ipaddr/hton_spec.rb30
-rw-r--r--spec/ruby/library/ipaddr/ipv4_conversion_spec.rb44
-rw-r--r--spec/ruby/library/ipaddr/new_spec.rb110
-rw-r--r--spec/ruby/library/ipaddr/operator_spec.rb82
-rw-r--r--spec/ruby/library/ipaddr/reverse_spec.rb27
-rw-r--r--spec/ruby/library/ipaddr/to_s_spec.rb20
-rw-r--r--spec/ruby/library/logger/device/close_spec.rb31
-rw-r--r--spec/ruby/library/logger/device/new_spec.rb47
-rw-r--r--spec/ruby/library/logger/device/write_spec.rb51
-rw-r--r--spec/ruby/library/logger/fixtures/common.rb9
-rw-r--r--spec/ruby/library/logger/logger/add_spec.rb81
-rw-r--r--spec/ruby/library/logger/logger/close_spec.rb20
-rw-r--r--spec/ruby/library/logger/logger/datetime_format_spec.rb60
-rw-r--r--spec/ruby/library/logger/logger/debug_spec.rb52
-rw-r--r--spec/ruby/library/logger/logger/error_spec.rb53
-rw-r--r--spec/ruby/library/logger/logger/fatal_spec.rb53
-rw-r--r--spec/ruby/library/logger/logger/info_spec.rb53
-rw-r--r--spec/ruby/library/logger/logger/new_spec.rb118
-rw-r--r--spec/ruby/library/logger/logger/unknown_spec.rb36
-rw-r--r--spec/ruby/library/logger/logger/warn_spec.rb53
-rw-r--r--spec/ruby/library/logger/severity_spec.rb13
-rw-r--r--spec/ruby/library/matrix/I_spec.rb9
-rw-r--r--spec/ruby/library/matrix/antisymmetric_spec.rb38
-rw-r--r--spec/ruby/library/matrix/build_spec.rb76
-rw-r--r--spec/ruby/library/matrix/clone_spec.rb28
-rw-r--r--spec/ruby/library/matrix/coerce_spec.rb11
-rw-r--r--spec/ruby/library/matrix/collect_spec.rb9
-rw-r--r--spec/ruby/library/matrix/column_size_spec.rb16
-rw-r--r--spec/ruby/library/matrix/column_spec.rb38
-rw-r--r--spec/ruby/library/matrix/column_vector_spec.rb28
-rw-r--r--spec/ruby/library/matrix/column_vectors_spec.rb29
-rw-r--r--spec/ruby/library/matrix/columns_spec.rb45
-rw-r--r--spec/ruby/library/matrix/conj_spec.rb9
-rw-r--r--spec/ruby/library/matrix/conjugate_spec.rb9
-rw-r--r--spec/ruby/library/matrix/constructor_spec.rb68
-rw-r--r--spec/ruby/library/matrix/det_spec.rb10
-rw-r--r--spec/ruby/library/matrix/determinant_spec.rb10
-rw-r--r--spec/ruby/library/matrix/diagonal_spec.rb75
-rw-r--r--spec/ruby/library/matrix/divide_spec.rb57
-rw-r--r--spec/ruby/library/matrix/each_spec.rb77
-rw-r--r--spec/ruby/library/matrix/each_with_index_spec.rb84
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalue_matrix_spec.rb12
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalues_spec.rb25
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/eigenvector_matrix_spec.rb23
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/eigenvectors_spec.rb25
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/initialize_spec.rb27
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/to_a_spec.rb21
-rw-r--r--spec/ruby/library/matrix/element_reference_spec.rb26
-rw-r--r--spec/ruby/library/matrix/empty_spec.rb71
-rw-r--r--spec/ruby/library/matrix/eql_spec.rb14
-rw-r--r--spec/ruby/library/matrix/equal_value_spec.rb14
-rw-r--r--spec/ruby/library/matrix/exponent_spec.rb67
-rw-r--r--spec/ruby/library/matrix/find_index_spec.rb149
-rw-r--r--spec/ruby/library/matrix/fixtures/classes.rb7
-rw-r--r--spec/ruby/library/matrix/hash_spec.rb18
-rw-r--r--spec/ruby/library/matrix/hermitian_spec.rb37
-rw-r--r--spec/ruby/library/matrix/identity_spec.rb9
-rw-r--r--spec/ruby/library/matrix/imag_spec.rb9
-rw-r--r--spec/ruby/library/matrix/imaginary_spec.rb9
-rw-r--r--spec/ruby/library/matrix/inspect_spec.rb30
-rw-r--r--spec/ruby/library/matrix/inv_spec.rb10
-rw-r--r--spec/ruby/library/matrix/inverse_from_spec.rb9
-rw-r--r--spec/ruby/library/matrix/inverse_spec.rb10
-rw-r--r--spec/ruby/library/matrix/lower_triangular_spec.rb27
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/determinant_spec.rb24
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/initialize_spec.rb16
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/l_spec.rb21
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/p_spec.rb21
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/solve_spec.rb56
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/to_a_spec.rb36
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/u_spec.rb21
-rw-r--r--spec/ruby/library/matrix/map_spec.rb9
-rw-r--r--spec/ruby/library/matrix/minor_spec.rb88
-rw-r--r--spec/ruby/library/matrix/minus_spec.rb45
-rw-r--r--spec/ruby/library/matrix/multiply_spec.rb71
-rw-r--r--spec/ruby/library/matrix/new_spec.rb11
-rw-r--r--spec/ruby/library/matrix/normal_spec.rb29
-rw-r--r--spec/ruby/library/matrix/orthogonal_spec.rb29
-rw-r--r--spec/ruby/library/matrix/permutation_spec.rb35
-rw-r--r--spec/ruby/library/matrix/plus_spec.rb45
-rw-r--r--spec/ruby/library/matrix/rank_spec.rb22
-rw-r--r--spec/ruby/library/matrix/real_spec.rb46
-rw-r--r--spec/ruby/library/matrix/rect_spec.rb9
-rw-r--r--spec/ruby/library/matrix/rectangular_spec.rb9
-rw-r--r--spec/ruby/library/matrix/regular_spec.rb34
-rw-r--r--spec/ruby/library/matrix/round_spec.rb24
-rw-r--r--spec/ruby/library/matrix/row_size_spec.rb16
-rw-r--r--spec/ruby/library/matrix/row_spec.rb39
-rw-r--r--spec/ruby/library/matrix/row_vector_spec.rb27
-rw-r--r--spec/ruby/library/matrix/row_vectors_spec.rb29
-rw-r--r--spec/ruby/library/matrix/rows_spec.rb44
-rw-r--r--spec/ruby/library/matrix/scalar/Fail_spec.rb9
-rw-r--r--spec/ruby/library/matrix/scalar/Raise_spec.rb9
-rw-r--r--spec/ruby/library/matrix/scalar/divide_spec.rb9
-rw-r--r--spec/ruby/library/matrix/scalar/exponent_spec.rb9
-rw-r--r--spec/ruby/library/matrix/scalar/included_spec.rb9
-rw-r--r--spec/ruby/library/matrix/scalar/initialize_spec.rb9
-rw-r--r--spec/ruby/library/matrix/scalar/minus_spec.rb9
-rw-r--r--spec/ruby/library/matrix/scalar/multiply_spec.rb9
-rw-r--r--spec/ruby/library/matrix/scalar/plus_spec.rb9
-rw-r--r--spec/ruby/library/matrix/scalar_spec.rb70
-rw-r--r--spec/ruby/library/matrix/shared/collect.rb26
-rw-r--r--spec/ruby/library/matrix/shared/conjugate.rb20
-rw-r--r--spec/ruby/library/matrix/shared/determinant.rb38
-rw-r--r--spec/ruby/library/matrix/shared/equal_value.rb33
-rw-r--r--spec/ruby/library/matrix/shared/identity.rb19
-rw-r--r--spec/ruby/library/matrix/shared/imaginary.rb20
-rw-r--r--spec/ruby/library/matrix/shared/inverse.rb38
-rw-r--r--spec/ruby/library/matrix/shared/rectangular.rb18
-rw-r--r--spec/ruby/library/matrix/shared/trace.rb12
-rw-r--r--spec/ruby/library/matrix/shared/transpose.rb19
-rw-r--r--spec/ruby/library/matrix/singular_spec.rb34
-rw-r--r--spec/ruby/library/matrix/spec_helper.rb35
-rw-r--r--spec/ruby/library/matrix/square_spec.rb31
-rw-r--r--spec/ruby/library/matrix/symmetric_spec.rb32
-rw-r--r--spec/ruby/library/matrix/t_spec.rb9
-rw-r--r--spec/ruby/library/matrix/to_a_spec.rb14
-rw-r--r--spec/ruby/library/matrix/to_s_spec.rb9
-rw-r--r--spec/ruby/library/matrix/tr_spec.rb10
-rw-r--r--spec/ruby/library/matrix/trace_spec.rb10
-rw-r--r--spec/ruby/library/matrix/transpose_spec.rb9
-rw-r--r--spec/ruby/library/matrix/unit_spec.rb9
-rw-r--r--spec/ruby/library/matrix/unitary_spec.rb36
-rw-r--r--spec/ruby/library/matrix/upper_triangular_spec.rb27
-rw-r--r--spec/ruby/library/matrix/vector/cross_product_spec.rb17
-rw-r--r--spec/ruby/library/matrix/vector/each2_spec.rb52
-rw-r--r--spec/ruby/library/matrix/vector/eql_spec.rb19
-rw-r--r--spec/ruby/library/matrix/vector/inner_product_spec.rb25
-rw-r--r--spec/ruby/library/matrix/vector/normalize_spec.rb21
-rw-r--r--spec/ruby/library/matrix/zero_spec.rb55
-rw-r--r--spec/ruby/library/mkmf/mkmf_spec.rb7
-rw-r--r--spec/ruby/library/monitor/enter_spec.rb28
-rw-r--r--spec/ruby/library/monitor/mon_initialize_spec.rb31
-rw-r--r--spec/ruby/library/monitor/new_cond_spec.rb88
-rw-r--r--spec/ruby/library/monitor/synchronize_spec.rb41
-rw-r--r--spec/ruby/library/monitor/try_enter_spec.rb39
-rw-r--r--spec/ruby/library/net/FTPError_spec.rb11
-rw-r--r--spec/ruby/library/net/FTPPermError_spec.rb15
-rw-r--r--spec/ruby/library/net/FTPProtoError_spec.rb15
-rw-r--r--spec/ruby/library/net/FTPReplyError_spec.rb15
-rw-r--r--spec/ruby/library/net/FTPTempError_spec.rb15
-rw-r--r--spec/ruby/library/net/ftp/abort_spec.rb65
-rw-r--r--spec/ruby/library/net/ftp/acct_spec.rb61
-rw-r--r--spec/ruby/library/net/ftp/binary_spec.rb27
-rw-r--r--spec/ruby/library/net/ftp/chdir_spec.rb102
-rw-r--r--spec/ruby/library/net/ftp/close_spec.rb33
-rw-r--r--spec/ruby/library/net/ftp/closed_spec.rb24
-rw-r--r--spec/ruby/library/net/ftp/connect_spec.rb52
-rw-r--r--spec/ruby/library/net/ftp/debug_mode_spec.rb26
-rw-r--r--spec/ruby/library/net/ftp/default_passive_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/delete_spec.rb62
-rw-r--r--spec/ruby/library/net/ftp/dir_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/fixtures/default_passive.rb3
-rw-r--r--spec/ruby/library/net/ftp/fixtures/passive.rb2
-rw-r--r--spec/ruby/library/net/ftp/fixtures/putbinaryfile3
-rw-r--r--spec/ruby/library/net/ftp/fixtures/puttextfile3
-rw-r--r--spec/ruby/library/net/ftp/fixtures/server.rb277
-rw-r--r--spec/ruby/library/net/ftp/get_spec.rb24
-rw-r--r--spec/ruby/library/net/ftp/getbinaryfile_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/getdir_spec.rb10
-rw-r--r--spec/ruby/library/net/ftp/gettextfile_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/help_spec.rb69
-rw-r--r--spec/ruby/library/net/ftp/initialize_spec.rb408
-rw-r--r--spec/ruby/library/net/ftp/last_response_code_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/last_response_spec.rb28
-rw-r--r--spec/ruby/library/net/ftp/lastresp_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/list_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/login_spec.rb198
-rw-r--r--spec/ruby/library/net/ftp/ls_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/mdtm_spec.rb41
-rw-r--r--spec/ruby/library/net/ftp/mkdir_spec.rb64
-rw-r--r--spec/ruby/library/net/ftp/mtime_spec.rb53
-rw-r--r--spec/ruby/library/net/ftp/nlst_spec.rb95
-rw-r--r--spec/ruby/library/net/ftp/noop_spec.rb41
-rw-r--r--spec/ruby/library/net/ftp/open_spec.rb58
-rw-r--r--spec/ruby/library/net/ftp/passive_spec.rb31
-rw-r--r--spec/ruby/library/net/ftp/put_spec.rb24
-rw-r--r--spec/ruby/library/net/ftp/putbinaryfile_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/puttextfile_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/pwd_spec.rb56
-rw-r--r--spec/ruby/library/net/ftp/quit_spec.rb36
-rw-r--r--spec/ruby/library/net/ftp/rename_spec.rb97
-rw-r--r--spec/ruby/library/net/ftp/resume_spec.rb26
-rw-r--r--spec/ruby/library/net/ftp/retrbinary_spec.rb33
-rw-r--r--spec/ruby/library/net/ftp/retrlines_spec.rb37
-rw-r--r--spec/ruby/library/net/ftp/return_code_spec.rb27
-rw-r--r--spec/ruby/library/net/ftp/rmdir_spec.rb61
-rw-r--r--spec/ruby/library/net/ftp/sendcmd_spec.rb57
-rw-r--r--spec/ruby/library/net/ftp/set_socket_spec.rb11
-rw-r--r--spec/ruby/library/net/ftp/shared/getbinaryfile.rb150
-rw-r--r--spec/ruby/library/net/ftp/shared/gettextfile.rb100
-rw-r--r--spec/ruby/library/net/ftp/shared/last_response_code.rb25
-rw-r--r--spec/ruby/library/net/ftp/shared/list.rb104
-rw-r--r--spec/ruby/library/net/ftp/shared/putbinaryfile.rb167
-rw-r--r--spec/ruby/library/net/ftp/shared/puttextfile.rb120
-rw-r--r--spec/ruby/library/net/ftp/shared/pwd.rb3
-rw-r--r--spec/ruby/library/net/ftp/site_spec.rb56
-rw-r--r--spec/ruby/library/net/ftp/size_spec.rb51
-rw-r--r--spec/ruby/library/net/ftp/spec_helper.rb5
-rw-r--r--spec/ruby/library/net/ftp/status_spec.rb70
-rw-r--r--spec/ruby/library/net/ftp/storbinary_spec.rb51
-rw-r--r--spec/ruby/library/net/ftp/storlines_spec.rb46
-rw-r--r--spec/ruby/library/net/ftp/system_spec.rb51
-rw-r--r--spec/ruby/library/net/ftp/voidcmd_spec.rb57
-rw-r--r--spec/ruby/library/net/ftp/welcome_spec.rb28
-rw-r--r--spec/ruby/library/net/http/HTTPBadResponse_spec.rb8
-rw-r--r--spec/ruby/library/net/http/HTTPClientExcepton_spec.rb12
-rw-r--r--spec/ruby/library/net/http/HTTPError_spec.rb12
-rw-r--r--spec/ruby/library/net/http/HTTPFatalError_spec.rb12
-rw-r--r--spec/ruby/library/net/http/HTTPHeaderSyntaxError_spec.rb8
-rw-r--r--spec/ruby/library/net/http/HTTPRetriableError_spec.rb12
-rw-r--r--spec/ruby/library/net/http/HTTPServerException_spec.rb12
-rw-r--r--spec/ruby/library/net/http/http/Proxy_spec.rb35
-rw-r--r--spec/ruby/library/net/http/http/active_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/address_spec.rb9
-rw-r--r--spec/ruby/library/net/http/http/close_on_empty_response_spec.rb10
-rw-r--r--spec/ruby/library/net/http/http/copy_spec.rb21
-rw-r--r--spec/ruby/library/net/http/http/default_port_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/delete_spec.rb21
-rw-r--r--spec/ruby/library/net/http/http/finish_spec.rb29
-rw-r--r--spec/ruby/library/net/http/http/fixtures/http_server.rb123
-rw-r--r--spec/ruby/library/net/http/http/get2_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/get_print_spec.rb30
-rw-r--r--spec/ruby/library/net/http/http/get_response_spec.rb30
-rw-r--r--spec/ruby/library/net/http/http/get_spec.rb96
-rw-r--r--spec/ruby/library/net/http/http/head2_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/head_spec.rb25
-rw-r--r--spec/ruby/library/net/http/http/http_default_port_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/https_default_port_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/initialize_spec.rb46
-rw-r--r--spec/ruby/library/net/http/http/inspect_spec.rb24
-rw-r--r--spec/ruby/library/net/http/http/is_version_1_1_spec.rb7
-rw-r--r--spec/ruby/library/net/http/http/is_version_1_2_spec.rb7
-rw-r--r--spec/ruby/library/net/http/http/lock_spec.rb21
-rw-r--r--spec/ruby/library/net/http/http/mkcol_spec.rb21
-rw-r--r--spec/ruby/library/net/http/http/move_spec.rb25
-rw-r--r--spec/ruby/library/net/http/http/new_spec.rb86
-rw-r--r--spec/ruby/library/net/http/http/newobj_spec.rb48
-rw-r--r--spec/ruby/library/net/http/http/open_timeout_spec.rb24
-rw-r--r--spec/ruby/library/net/http/http/options_spec.rb25
-rw-r--r--spec/ruby/library/net/http/http/port_spec.rb9
-rw-r--r--spec/ruby/library/net/http/http/post2_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/post_form_spec.rb22
-rw-r--r--spec/ruby/library/net/http/http/post_spec.rb74
-rw-r--r--spec/ruby/library/net/http/http/propfind_spec.rb24
-rw-r--r--spec/ruby/library/net/http/http/proppatch_spec.rb24
-rw-r--r--spec/ruby/library/net/http/http/proxy_address_spec.rb31
-rw-r--r--spec/ruby/library/net/http/http/proxy_class_spec.rb9
-rw-r--r--spec/ruby/library/net/http/http/proxy_pass_spec.rb39
-rw-r--r--spec/ruby/library/net/http/http/proxy_port_spec.rb39
-rw-r--r--spec/ruby/library/net/http/http/proxy_user_spec.rb39
-rw-r--r--spec/ruby/library/net/http/http/put2_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/put_spec.rb24
-rw-r--r--spec/ruby/library/net/http/http/read_timeout_spec.rb24
-rw-r--r--spec/ruby/library/net/http/http/request_get_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/request_head_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/request_post_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/request_put_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/request_spec.rb109
-rw-r--r--spec/ruby/library/net/http/http/request_types_spec.rb254
-rw-r--r--spec/ruby/library/net/http/http/send_request_spec.rb61
-rw-r--r--spec/ruby/library/net/http/http/set_debug_output_spec.rb33
-rw-r--r--spec/ruby/library/net/http/http/shared/request_get.rb41
-rw-r--r--spec/ruby/library/net/http/http/shared/request_head.rb41
-rw-r--r--spec/ruby/library/net/http/http/shared/request_post.rb41
-rw-r--r--spec/ruby/library/net/http/http/shared/request_put.rb41
-rw-r--r--spec/ruby/library/net/http/http/shared/started.rb26
-rw-r--r--spec/ruby/library/net/http/http/shared/version_1_1.rb6
-rw-r--r--spec/ruby/library/net/http/http/shared/version_1_2.rb6
-rw-r--r--spec/ruby/library/net/http/http/socket_type_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/start_spec.rb111
-rw-r--r--spec/ruby/library/net/http/http/started_spec.rb8
-rw-r--r--spec/ruby/library/net/http/http/trace_spec.rb24
-rw-r--r--spec/ruby/library/net/http/http/unlock_spec.rb24
-rw-r--r--spec/ruby/library/net/http/http/use_ssl_spec.rb9
-rw-r--r--spec/ruby/library/net/http/http/version_1_1_spec.rb7
-rw-r--r--spec/ruby/library/net/http/http/version_1_2_spec.rb20
-rw-r--r--spec/ruby/library/net/http/httpexceptions/fixtures/classes.rb5
-rw-r--r--spec/ruby/library/net/http/httpexceptions/initialize_spec.rb17
-rw-r--r--spec/ruby/library/net/http/httpexceptions/response_spec.rb10
-rw-r--r--spec/ruby/library/net/http/httpgenericrequest/body_exist_spec.rb21
-rw-r--r--spec/ruby/library/net/http/httpgenericrequest/body_spec.rb30
-rw-r--r--spec/ruby/library/net/http/httpgenericrequest/body_stream_spec.rb32
-rw-r--r--spec/ruby/library/net/http/httpgenericrequest/exec_spec.rb131
-rw-r--r--spec/ruby/library/net/http/httpgenericrequest/inspect_spec.rb25
-rw-r--r--spec/ruby/library/net/http/httpgenericrequest/method_spec.rb15
-rw-r--r--spec/ruby/library/net/http/httpgenericrequest/path_spec.rb12
-rw-r--r--spec/ruby/library/net/http/httpgenericrequest/request_body_permitted_spec.rb12
-rw-r--r--spec/ruby/library/net/http/httpgenericrequest/response_body_permitted_spec.rb12
-rw-r--r--spec/ruby/library/net/http/httpgenericrequest/set_body_internal_spec.rb21
-rw-r--r--spec/ruby/library/net/http/httpheader/add_field_spec.rb31
-rw-r--r--spec/ruby/library/net/http/httpheader/basic_auth_spec.rb14
-rw-r--r--spec/ruby/library/net/http/httpheader/canonical_each_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/chunked_spec.rb22
-rw-r--r--spec/ruby/library/net/http/httpheader/content_length_spec.rb54
-rw-r--r--spec/ruby/library/net/http/httpheader/content_range_spec.rb32
-rw-r--r--spec/ruby/library/net/http/httpheader/content_type_spec.rb26
-rw-r--r--spec/ruby/library/net/http/httpheader/delete_spec.rb30
-rw-r--r--spec/ruby/library/net/http/httpheader/each_capitalized_name_spec.rb35
-rw-r--r--spec/ruby/library/net/http/httpheader/each_capitalized_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/each_header_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/each_key_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/each_name_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/each_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/each_value_spec.rb35
-rw-r--r--spec/ruby/library/net/http/httpheader/element_reference_spec.rb39
-rw-r--r--spec/ruby/library/net/http/httpheader/element_set_spec.rb41
-rw-r--r--spec/ruby/library/net/http/httpheader/fetch_spec.rb68
-rw-r--r--spec/ruby/library/net/http/httpheader/fixtures/classes.rb11
-rw-r--r--spec/ruby/library/net/http/httpheader/form_data_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/get_fields_spec.rb39
-rw-r--r--spec/ruby/library/net/http/httpheader/initialize_http_header_spec.rb21
-rw-r--r--spec/ruby/library/net/http/httpheader/key_spec.rb21
-rw-r--r--spec/ruby/library/net/http/httpheader/length_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/main_type_spec.rb24
-rw-r--r--spec/ruby/library/net/http/httpheader/proxy_basic_auth_spec.rb14
-rw-r--r--spec/ruby/library/net/http/httpheader/range_length_spec.rb32
-rw-r--r--spec/ruby/library/net/http/httpheader/range_spec.rb48
-rw-r--r--spec/ruby/library/net/http/httpheader/set_content_type_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/set_form_data_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/set_range_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/shared/each_capitalized.rb31
-rw-r--r--spec/ruby/library/net/http/httpheader/shared/each_header.rb31
-rw-r--r--spec/ruby/library/net/http/httpheader/shared/each_name.rb31
-rw-r--r--spec/ruby/library/net/http/httpheader/shared/set_content_type.rb18
-rw-r--r--spec/ruby/library/net/http/httpheader/shared/set_form_data.rb27
-rw-r--r--spec/ruby/library/net/http/httpheader/shared/set_range.rb89
-rw-r--r--spec/ruby/library/net/http/httpheader/shared/size.rb18
-rw-r--r--spec/ruby/library/net/http/httpheader/size_spec.rb8
-rw-r--r--spec/ruby/library/net/http/httpheader/sub_type_spec.rb32
-rw-r--r--spec/ruby/library/net/http/httpheader/to_hash_spec.rb25
-rw-r--r--spec/ruby/library/net/http/httpheader/type_params_spec.rb24
-rw-r--r--spec/ruby/library/net/http/httprequest/initialize_spec.rb45
-rw-r--r--spec/ruby/library/net/http/httpresponse/body_permitted_spec.rb13
-rw-r--r--spec/ruby/library/net/http/httpresponse/body_spec.rb7
-rw-r--r--spec/ruby/library/net/http/httpresponse/code_spec.rb24
-rw-r--r--spec/ruby/library/net/http/httpresponse/code_type_spec.rb24
-rw-r--r--spec/ruby/library/net/http/httpresponse/entity_spec.rb7
-rw-r--r--spec/ruby/library/net/http/httpresponse/error_spec.rb24
-rw-r--r--spec/ruby/library/net/http/httpresponse/error_type_spec.rb24
-rw-r--r--spec/ruby/library/net/http/httpresponse/exception_type_spec.rb13
-rw-r--r--spec/ruby/library/net/http/httpresponse/header_spec.rb9
-rw-r--r--spec/ruby/library/net/http/httpresponse/http_version_spec.rb12
-rw-r--r--spec/ruby/library/net/http/httpresponse/initialize_spec.rb11
-rw-r--r--spec/ruby/library/net/http/httpresponse/inspect_spec.rb15
-rw-r--r--spec/ruby/library/net/http/httpresponse/message_spec.rb9
-rw-r--r--spec/ruby/library/net/http/httpresponse/msg_spec.rb9
-rw-r--r--spec/ruby/library/net/http/httpresponse/read_body_spec.rb86
-rw-r--r--spec/ruby/library/net/http/httpresponse/read_header_spec.rb9
-rw-r--r--spec/ruby/library/net/http/httpresponse/read_new_spec.rb23
-rw-r--r--spec/ruby/library/net/http/httpresponse/reading_body_spec.rb58
-rw-r--r--spec/ruby/library/net/http/httpresponse/response_spec.rb9
-rw-r--r--spec/ruby/library/net/http/httpresponse/shared/body.rb20
-rw-r--r--spec/ruby/library/net/http/httpresponse/value_spec.rb24
-rw-r--r--spec/ruby/library/objectspace/fixtures/trace.rb5
-rw-r--r--spec/ruby/library/objectspace/memsize_of_all_spec.rb22
-rw-r--r--spec/ruby/library/objectspace/memsize_of_spec.rb34
-rw-r--r--spec/ruby/library/objectspace/reachable_objects_from_spec.rb61
-rw-r--r--spec/ruby/library/objectspace/trace_object_allocations_spec.rb149
-rw-r--r--spec/ruby/library/objectspace/trace_spec.rb15
-rw-r--r--spec/ruby/library/observer/add_observer_spec.rb23
-rw-r--r--spec/ruby/library/observer/count_observers_spec.rb23
-rw-r--r--spec/ruby/library/observer/delete_observer_spec.rb19
-rw-r--r--spec/ruby/library/observer/delete_observers_spec.rb19
-rw-r--r--spec/ruby/library/observer/fixtures/classes.rb17
-rw-r--r--spec/ruby/library/observer/notify_observers_spec.rb31
-rw-r--r--spec/ruby/library/open3/capture2_spec.rb6
-rw-r--r--spec/ruby/library/open3/capture2e_spec.rb6
-rw-r--r--spec/ruby/library/open3/capture3_spec.rb6
-rw-r--r--spec/ruby/library/open3/pipeline_r_spec.rb6
-rw-r--r--spec/ruby/library/open3/pipeline_rw_spec.rb6
-rw-r--r--spec/ruby/library/open3/pipeline_spec.rb6
-rw-r--r--spec/ruby/library/open3/pipeline_start_spec.rb6
-rw-r--r--spec/ruby/library/open3/pipeline_w_spec.rb6
-rw-r--r--spec/ruby/library/open3/popen2_spec.rb6
-rw-r--r--spec/ruby/library/open3/popen2e_spec.rb6
-rw-r--r--spec/ruby/library/open3/popen3_spec.rb41
-rw-r--r--spec/ruby/library/openssl/cipher_spec.rb9
-rw-r--r--spec/ruby/library/openssl/config/freeze_spec.rb22
-rw-r--r--spec/ruby/library/openssl/digest_spec.rb63
-rw-r--r--spec/ruby/library/openssl/hmac/digest_spec.rb16
-rw-r--r--spec/ruby/library/openssl/hmac/hexdigest_spec.rb16
-rw-r--r--spec/ruby/library/openssl/random/pseudo_bytes_spec.rb8
-rw-r--r--spec/ruby/library/openssl/random/random_bytes_spec.rb6
-rw-r--r--spec/ruby/library/openssl/random/shared/random_bytes.rb29
-rw-r--r--spec/ruby/library/openssl/shared/constants.rb11
-rw-r--r--spec/ruby/library/openssl/x509/name/parse_spec.rb48
-rw-r--r--spec/ruby/library/openssl/x509/name/verify_spec.rb78
-rw-r--r--spec/ruby/library/openstruct/delete_field_spec.rb19
-rw-r--r--spec/ruby/library/openstruct/element_reference_spec.rb13
-rw-r--r--spec/ruby/library/openstruct/element_set_spec.rb13
-rw-r--r--spec/ruby/library/openstruct/equal_value_spec.rb28
-rw-r--r--spec/ruby/library/openstruct/fixtures/classes.rb4
-rw-r--r--spec/ruby/library/openstruct/frozen_spec.rb40
-rw-r--r--spec/ruby/library/openstruct/initialize_spec.rb8
-rw-r--r--spec/ruby/library/openstruct/inspect_spec.rb8
-rw-r--r--spec/ruby/library/openstruct/marshal_dump_spec.rb9
-rw-r--r--spec/ruby/library/openstruct/marshal_load_spec.rb12
-rw-r--r--spec/ruby/library/openstruct/method_missing_spec.rb24
-rw-r--r--spec/ruby/library/openstruct/new_spec.rb20
-rw-r--r--spec/ruby/library/openstruct/shared/inspect.rb20
-rw-r--r--spec/ruby/library/openstruct/to_h_spec.rb68
-rw-r--r--spec/ruby/library/openstruct/to_s_spec.rb8
-rw-r--r--spec/ruby/library/optionparser/order_spec.rb28
-rw-r--r--spec/ruby/library/optionparser/parse_spec.rb28
-rw-r--r--spec/ruby/library/pathname/absolute_spec.rb22
-rw-r--r--spec/ruby/library/pathname/birthtime_spec.rb16
-rw-r--r--spec/ruby/library/pathname/divide_spec.rb6
-rw-r--r--spec/ruby/library/pathname/empty_spec.rb32
-rw-r--r--spec/ruby/library/pathname/equal_value_spec.rb14
-rw-r--r--spec/ruby/library/pathname/glob_spec.rb84
-rw-r--r--spec/ruby/library/pathname/hash_spec.rb14
-rw-r--r--spec/ruby/library/pathname/inspect_spec.rb10
-rw-r--r--spec/ruby/library/pathname/join_spec.rb40
-rw-r--r--spec/ruby/library/pathname/new_spec.rb23
-rw-r--r--spec/ruby/library/pathname/parent_spec.rb18
-rw-r--r--spec/ruby/library/pathname/pathname_spec.rb19
-rw-r--r--spec/ruby/library/pathname/plus_spec.rb6
-rw-r--r--spec/ruby/library/pathname/realdirpath_spec.rb10
-rw-r--r--spec/ruby/library/pathname/realpath_spec.rb10
-rw-r--r--spec/ruby/library/pathname/relative_path_from_spec.rb51
-rw-r--r--spec/ruby/library/pathname/relative_spec.rb22
-rw-r--r--spec/ruby/library/pathname/root_spec.rb26
-rw-r--r--spec/ruby/library/pathname/shared/plus.rb8
-rw-r--r--spec/ruby/library/pathname/sub_spec.rb15
-rw-r--r--spec/ruby/library/pp/pp_spec.rb30
-rw-r--r--spec/ruby/library/prime/each_spec.rb170
-rw-r--r--spec/ruby/library/prime/instance_spec.rb24
-rw-r--r--spec/ruby/library/prime/int_from_prime_division_spec.rb16
-rw-r--r--spec/ruby/library/prime/integer/each_prime_spec.rb16
-rw-r--r--spec/ruby/library/prime/integer/from_prime_division_spec.rb16
-rw-r--r--spec/ruby/library/prime/integer/prime_division_spec.rb22
-rw-r--r--spec/ruby/library/prime/integer/prime_spec.rb20
-rw-r--r--spec/ruby/library/prime/next_spec.rb10
-rw-r--r--spec/ruby/library/prime/prime_division_spec.rb28
-rw-r--r--spec/ruby/library/prime/prime_spec.rb20
-rw-r--r--spec/ruby/library/prime/shared/next.rb8
-rw-r--r--spec/ruby/library/prime/succ_spec.rb10
-rw-r--r--spec/ruby/library/rbconfig/rbconfig_spec.rb101
-rw-r--r--spec/ruby/library/rbconfig/sizeof/limits_spec.rb40
-rw-r--r--spec/ruby/library/rbconfig/sizeof/sizeof_spec.rb30
-rw-r--r--spec/ruby/library/rbconfig/unicode_emoji_version_spec.rb23
-rw-r--r--spec/ruby/library/rbconfig/unicode_version_spec.rb23
-rw-r--r--spec/ruby/library/readline/basic_quote_characters_spec.rb18
-rw-r--r--spec/ruby/library/readline/basic_word_break_characters_spec.rb16
-rw-r--r--spec/ruby/library/readline/completer_quote_characters_spec.rb16
-rw-r--r--spec/ruby/library/readline/completer_word_break_characters_spec.rb16
-rw-r--r--spec/ruby/library/readline/completion_append_character_spec.rb16
-rw-r--r--spec/ruby/library/readline/completion_case_fold_spec.rb18
-rw-r--r--spec/ruby/library/readline/completion_proc_spec.rb22
-rw-r--r--spec/ruby/library/readline/constants_spec.rb18
-rw-r--r--spec/ruby/library/readline/emacs_editing_mode_spec.rb11
-rw-r--r--spec/ruby/library/readline/filename_quote_characters_spec.rb18
-rw-r--r--spec/ruby/library/readline/history/append_spec.rb28
-rw-r--r--spec/ruby/library/readline/history/delete_at_spec.rb38
-rw-r--r--spec/ruby/library/readline/history/each_spec.rb23
-rw-r--r--spec/ruby/library/readline/history/element_reference_spec.rb35
-rw-r--r--spec/ruby/library/readline/history/element_set_spec.rb35
-rw-r--r--spec/ruby/library/readline/history/empty_spec.rb13
-rw-r--r--spec/ruby/library/readline/history/history_spec.rb9
-rw-r--r--spec/ruby/library/readline/history/length_spec.rb9
-rw-r--r--spec/ruby/library/readline/history/pop_spec.rb23
-rw-r--r--spec/ruby/library/readline/history/push_spec.rb26
-rw-r--r--spec/ruby/library/readline/history/shared/size.rb14
-rw-r--r--spec/ruby/library/readline/history/shift_spec.rb23
-rw-r--r--spec/ruby/library/readline/history/size_spec.rb9
-rw-r--r--spec/ruby/library/readline/history/to_s_spec.rb9
-rw-r--r--spec/ruby/library/readline/readline_spec.rb26
-rw-r--r--spec/ruby/library/readline/spec_helper.rb11
-rw-r--r--spec/ruby/library/readline/vi_editing_mode_spec.rb11
-rw-r--r--spec/ruby/library/resolv/fixtures/hosts1
-rw-r--r--spec/ruby/library/resolv/get_address_spec.rb19
-rw-r--r--spec/ruby/library/resolv/get_addresses_spec.rb12
-rw-r--r--spec/ruby/library/resolv/get_name_spec.rb18
-rw-r--r--spec/ruby/library/resolv/get_names_spec.rb11
-rw-r--r--spec/ruby/library/rexml/attribute/clone_spec.rb14
-rw-r--r--spec/ruby/library/rexml/attribute/element_spec.rb26
-rw-r--r--spec/ruby/library/rexml/attribute/equal_value_spec.rb21
-rw-r--r--spec/ruby/library/rexml/attribute/hash_spec.rb16
-rw-r--r--spec/ruby/library/rexml/attribute/initialize_spec.rb32
-rw-r--r--spec/ruby/library/rexml/attribute/inspect_spec.rb22
-rw-r--r--spec/ruby/library/rexml/attribute/namespace_spec.rb27
-rw-r--r--spec/ruby/library/rexml/attribute/node_type_spec.rb13
-rw-r--r--spec/ruby/library/rexml/attribute/prefix_spec.rb21
-rw-r--r--spec/ruby/library/rexml/attribute/remove_spec.rb23
-rw-r--r--spec/ruby/library/rexml/attribute/to_s_spec.rb17
-rw-r--r--spec/ruby/library/rexml/attribute/to_string_spec.rb17
-rw-r--r--spec/ruby/library/rexml/attribute/value_spec.rb17
-rw-r--r--spec/ruby/library/rexml/attribute/write_spec.rb26
-rw-r--r--spec/ruby/library/rexml/attribute/xpath_spec.rb22
-rw-r--r--spec/ruby/library/rexml/attributes/add_spec.rb10
-rw-r--r--spec/ruby/library/rexml/attributes/append_spec.rb10
-rw-r--r--spec/ruby/library/rexml/attributes/delete_all_spec.rb34
-rw-r--r--spec/ruby/library/rexml/attributes/delete_spec.rb30
-rw-r--r--spec/ruby/library/rexml/attributes/each_attribute_spec.rb25
-rw-r--r--spec/ruby/library/rexml/attributes/each_spec.rb26
-rw-r--r--spec/ruby/library/rexml/attributes/element_reference_spec.rb21
-rw-r--r--spec/ruby/library/rexml/attributes/element_set_spec.rb28
-rw-r--r--spec/ruby/library/rexml/attributes/get_attribute_ns_spec.rb17
-rw-r--r--spec/ruby/library/rexml/attributes/get_attribute_spec.rb32
-rw-r--r--spec/ruby/library/rexml/attributes/initialize_spec.rb21
-rw-r--r--spec/ruby/library/rexml/attributes/length_spec.rb10
-rw-r--r--spec/ruby/library/rexml/attributes/namespaces_spec.rb9
-rw-r--r--spec/ruby/library/rexml/attributes/prefixes_spec.rb27
-rw-r--r--spec/ruby/library/rexml/attributes/shared/add.rb17
-rw-r--r--spec/ruby/library/rexml/attributes/shared/length.rb13
-rw-r--r--spec/ruby/library/rexml/attributes/size_spec.rb10
-rw-r--r--spec/ruby/library/rexml/attributes/to_a_spec.rb22
-rw-r--r--spec/ruby/library/rexml/cdata/clone_spec.rb13
-rw-r--r--spec/ruby/library/rexml/cdata/initialize_spec.rb27
-rw-r--r--spec/ruby/library/rexml/cdata/shared/to_s.rb11
-rw-r--r--spec/ruby/library/rexml/cdata/to_s_spec.rb10
-rw-r--r--spec/ruby/library/rexml/cdata/value_spec.rb10
-rw-r--r--spec/ruby/library/rexml/document/add_element_spec.rb34
-rw-r--r--spec/ruby/library/rexml/document/add_spec.rb60
-rw-r--r--spec/ruby/library/rexml/document/clone_spec.rb23
-rw-r--r--spec/ruby/library/rexml/document/doctype_spec.rb18
-rw-r--r--spec/ruby/library/rexml/document/encoding_spec.rb25
-rw-r--r--spec/ruby/library/rexml/document/expanded_name_spec.rb19
-rw-r--r--spec/ruby/library/rexml/document/new_spec.rb39
-rw-r--r--spec/ruby/library/rexml/document/node_type_spec.rb11
-rw-r--r--spec/ruby/library/rexml/document/root_spec.rb15
-rw-r--r--spec/ruby/library/rexml/document/stand_alone_spec.rb22
-rw-r--r--spec/ruby/library/rexml/document/version_spec.rb17
-rw-r--r--spec/ruby/library/rexml/document/write_spec.rb38
-rw-r--r--spec/ruby/library/rexml/document/xml_decl_spec.rb18
-rw-r--r--spec/ruby/library/rexml/element/add_attribute_spec.rb44
-rw-r--r--spec/ruby/library/rexml/element/add_attributes_spec.rb25
-rw-r--r--spec/ruby/library/rexml/element/add_element_spec.rb41
-rw-r--r--spec/ruby/library/rexml/element/add_namespace_spec.rb26
-rw-r--r--spec/ruby/library/rexml/element/add_text_spec.rb27
-rw-r--r--spec/ruby/library/rexml/element/attribute_spec.rb20
-rw-r--r--spec/ruby/library/rexml/element/attributes_spec.rb22
-rw-r--r--spec/ruby/library/rexml/element/cdatas_spec.rb27
-rw-r--r--spec/ruby/library/rexml/element/clone_spec.rb32
-rw-r--r--spec/ruby/library/rexml/element/comments_spec.rb23
-rw-r--r--spec/ruby/library/rexml/element/delete_attribute_spec.rb42
-rw-r--r--spec/ruby/library/rexml/element/delete_element_spec.rb52
-rw-r--r--spec/ruby/library/rexml/element/delete_namespace_spec.rb28
-rw-r--r--spec/ruby/library/rexml/element/document_spec.rb19
-rw-r--r--spec/ruby/library/rexml/element/each_element_with_attribute_spec.rb38
-rw-r--r--spec/ruby/library/rexml/element/each_element_with_text_spec.rb34
-rw-r--r--spec/ruby/library/rexml/element/element_reference_spec.rb23
-rw-r--r--spec/ruby/library/rexml/element/get_text_spec.rb21
-rw-r--r--spec/ruby/library/rexml/element/has_attributes_spec.rb20
-rw-r--r--spec/ruby/library/rexml/element/has_elements_spec.rb21
-rw-r--r--spec/ruby/library/rexml/element/has_text_spec.rb19
-rw-r--r--spec/ruby/library/rexml/element/inspect_spec.rb30
-rw-r--r--spec/ruby/library/rexml/element/instructions_spec.rb24
-rw-r--r--spec/ruby/library/rexml/element/namespace_spec.rb30
-rw-r--r--spec/ruby/library/rexml/element/namespaces_spec.rb35
-rw-r--r--spec/ruby/library/rexml/element/new_spec.rb38
-rw-r--r--spec/ruby/library/rexml/element/next_element_spec.rb22
-rw-r--r--spec/ruby/library/rexml/element/node_type_spec.rb11
-rw-r--r--spec/ruby/library/rexml/element/prefixes_spec.rb26
-rw-r--r--spec/ruby/library/rexml/element/previous_element_spec.rb23
-rw-r--r--spec/ruby/library/rexml/element/raw_spec.rb27
-rw-r--r--spec/ruby/library/rexml/element/root_spec.rb31
-rw-r--r--spec/ruby/library/rexml/element/text_spec.rb49
-rw-r--r--spec/ruby/library/rexml/element/texts_spec.rb19
-rw-r--r--spec/ruby/library/rexml/element/whitespace_spec.rb26
-rw-r--r--spec/ruby/library/rexml/node/each_recursive_spec.rb24
-rw-r--r--spec/ruby/library/rexml/node/find_first_recursive_spec.rb28
-rw-r--r--spec/ruby/library/rexml/node/index_in_parent_spec.rb18
-rw-r--r--spec/ruby/library/rexml/node/next_sibling_node_spec.rb24
-rw-r--r--spec/ruby/library/rexml/node/parent_spec.rb23
-rw-r--r--spec/ruby/library/rexml/node/previous_sibling_node_spec.rb24
-rw-r--r--spec/ruby/library/rexml/shared/each_element.rb36
-rw-r--r--spec/ruby/library/rexml/shared/elements_to_a.rb34
-rw-r--r--spec/ruby/library/rexml/text/append_spec.rb13
-rw-r--r--spec/ruby/library/rexml/text/clone_spec.rb13
-rw-r--r--spec/ruby/library/rexml/text/comparison_spec.rb28
-rw-r--r--spec/ruby/library/rexml/text/empty_spec.rb15
-rw-r--r--spec/ruby/library/rexml/text/indent_text_spec.rb26
-rw-r--r--spec/ruby/library/rexml/text/inspect_spec.rb11
-rw-r--r--spec/ruby/library/rexml/text/new_spec.rb51
-rw-r--r--spec/ruby/library/rexml/text/node_type_spec.rb11
-rw-r--r--spec/ruby/library/rexml/text/normalize_spec.rb11
-rw-r--r--spec/ruby/library/rexml/text/read_with_substitution_spec.rb15
-rw-r--r--spec/ruby/library/rexml/text/to_s_spec.rb20
-rw-r--r--spec/ruby/library/rexml/text/unnormalize_spec.rb11
-rw-r--r--spec/ruby/library/rexml/text/value_spec.rb40
-rw-r--r--spec/ruby/library/rexml/text/wrap_spec.rb23
-rw-r--r--spec/ruby/library/rexml/text/write_with_substitution_spec.rb36
-rw-r--r--spec/ruby/library/ripper/lex_spec.rb23
-rw-r--r--spec/ruby/library/ripper/sexp_spec.rb13
-rw-r--r--spec/ruby/library/rubygems/gem/bin_path_spec.rb34
-rw-r--r--spec/ruby/library/rubygems/gem/load_path_insert_index_spec.rb10
-rw-r--r--spec/ruby/library/securerandom/base64_spec.rb55
-rw-r--r--spec/ruby/library/securerandom/bytes_spec.rb8
-rw-r--r--spec/ruby/library/securerandom/hex_spec.rb54
-rw-r--r--spec/ruby/library/securerandom/random_bytes_spec.rb53
-rw-r--r--spec/ruby/library/securerandom/random_number_spec.rb97
-rw-r--r--spec/ruby/library/set/add_spec.rb27
-rw-r--r--spec/ruby/library/set/append_spec.rb7
-rw-r--r--spec/ruby/library/set/case_compare_spec.rb12
-rw-r--r--spec/ruby/library/set/case_equality_spec.rb7
-rw-r--r--spec/ruby/library/set/classify_spec.rb27
-rw-r--r--spec/ruby/library/set/clear_spec.rb17
-rw-r--r--spec/ruby/library/set/collect_spec.rb7
-rw-r--r--spec/ruby/library/set/compare_by_identity_spec.rb143
-rw-r--r--spec/ruby/library/set/comparison_spec.rb29
-rw-r--r--spec/ruby/library/set/constructor_spec.rb15
-rw-r--r--spec/ruby/library/set/delete_if_spec.rb38
-rw-r--r--spec/ruby/library/set/delete_spec.rb37
-rw-r--r--spec/ruby/library/set/difference_spec.rb7
-rw-r--r--spec/ruby/library/set/disjoint_spec.rb23
-rw-r--r--spec/ruby/library/set/divide_spec.rb34
-rw-r--r--spec/ruby/library/set/each_spec.rb26
-rw-r--r--spec/ruby/library/set/empty_spec.rb10
-rw-r--r--spec/ruby/library/set/enumerable/to_set_spec.rb21
-rw-r--r--spec/ruby/library/set/eql_spec.rb15
-rw-r--r--spec/ruby/library/set/equal_value_spec.rb33
-rw-r--r--spec/ruby/library/set/exclusion_spec.rb18
-rw-r--r--spec/ruby/library/set/filter_spec.rb6
-rw-r--r--spec/ruby/library/set/fixtures/set_like.rb31
-rw-r--r--spec/ruby/library/set/flatten_merge_spec.rb23
-rw-r--r--spec/ruby/library/set/flatten_spec.rb53
-rw-r--r--spec/ruby/library/set/hash_spec.rb13
-rw-r--r--spec/ruby/library/set/include_spec.rb7
-rw-r--r--spec/ruby/library/set/initialize_clone_spec.rb18
-rw-r--r--spec/ruby/library/set/initialize_spec.rb73
-rw-r--r--spec/ruby/library/set/inspect_spec.rb7
-rw-r--r--spec/ruby/library/set/intersect_spec.rb23
-rw-r--r--spec/ruby/library/set/intersection_spec.rb11
-rw-r--r--spec/ruby/library/set/join_spec.rb31
-rw-r--r--spec/ruby/library/set/keep_if_spec.rb38
-rw-r--r--spec/ruby/library/set/length_spec.rb7
-rw-r--r--spec/ruby/library/set/map_spec.rb7
-rw-r--r--spec/ruby/library/set/member_spec.rb7
-rw-r--r--spec/ruby/library/set/merge_spec.rb19
-rw-r--r--spec/ruby/library/set/minus_spec.rb7
-rw-r--r--spec/ruby/library/set/plus_spec.rb7
-rw-r--r--spec/ruby/library/set/pretty_print_cycle_spec.rb10
-rw-r--r--spec/ruby/library/set/pretty_print_spec.rb19
-rw-r--r--spec/ruby/library/set/proper_subset_spec.rb41
-rw-r--r--spec/ruby/library/set/proper_superset_spec.rb41
-rw-r--r--spec/ruby/library/set/reject_spec.rb42
-rw-r--r--spec/ruby/library/set/replace_spec.rb17
-rw-r--r--spec/ruby/library/set/select_spec.rb6
-rw-r--r--spec/ruby/library/set/shared/add.rb14
-rw-r--r--spec/ruby/library/set/shared/collect.rb20
-rw-r--r--spec/ruby/library/set/shared/difference.rb15
-rw-r--r--spec/ruby/library/set/shared/include.rb29
-rw-r--r--spec/ruby/library/set/shared/inspect.rb15
-rw-r--r--spec/ruby/library/set/shared/intersection.rb15
-rw-r--r--spec/ruby/library/set/shared/length.rb6
-rw-r--r--spec/ruby/library/set/shared/select.rb42
-rw-r--r--spec/ruby/library/set/shared/union.rb15
-rw-r--r--spec/ruby/library/set/size_spec.rb7
-rw-r--r--spec/ruby/library/set/sortedset/add_spec.rb42
-rw-r--r--spec/ruby/library/set/sortedset/append_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/case_equality_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/classify_spec.rb30
-rw-r--r--spec/ruby/library/set/sortedset/clear_spec.rb20
-rw-r--r--spec/ruby/library/set/sortedset/collect_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/constructor_spec.rb18
-rw-r--r--spec/ruby/library/set/sortedset/delete_if_spec.rb41
-rw-r--r--spec/ruby/library/set/sortedset/delete_spec.rb40
-rw-r--r--spec/ruby/library/set/sortedset/difference_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/divide_spec.rb37
-rw-r--r--spec/ruby/library/set/sortedset/each_spec.rb29
-rw-r--r--spec/ruby/library/set/sortedset/empty_spec.rb13
-rw-r--r--spec/ruby/library/set/sortedset/eql_spec.rb19
-rw-r--r--spec/ruby/library/set/sortedset/equal_value_spec.rb16
-rw-r--r--spec/ruby/library/set/sortedset/exclusion_spec.rb21
-rw-r--r--spec/ruby/library/set/sortedset/filter_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/flatten_merge_spec.rb11
-rw-r--r--spec/ruby/library/set/sortedset/flatten_spec.rb47
-rw-r--r--spec/ruby/library/set/sortedset/hash_spec.rb16
-rw-r--r--spec/ruby/library/set/sortedset/include_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/initialize_spec.rb33
-rw-r--r--spec/ruby/library/set/sortedset/inspect_spec.rb13
-rw-r--r--spec/ruby/library/set/sortedset/intersection_spec.rb14
-rw-r--r--spec/ruby/library/set/sortedset/keep_if_spec.rb34
-rw-r--r--spec/ruby/library/set/sortedset/length_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/map_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/member_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/merge_spec.rb22
-rw-r--r--spec/ruby/library/set/sortedset/minus_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/plus_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/pretty_print_cycle_spec.rb13
-rw-r--r--spec/ruby/library/set/sortedset/pretty_print_spec.rb20
-rw-r--r--spec/ruby/library/set/sortedset/proper_subset_spec.rb36
-rw-r--r--spec/ruby/library/set/sortedset/proper_superset_spec.rb36
-rw-r--r--spec/ruby/library/set/sortedset/reject_spec.rb45
-rw-r--r--spec/ruby/library/set/sortedset/replace_spec.rb20
-rw-r--r--spec/ruby/library/set/sortedset/select_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/shared/add.rb14
-rw-r--r--spec/ruby/library/set/sortedset/shared/collect.rb20
-rw-r--r--spec/ruby/library/set/sortedset/shared/difference.rb15
-rw-r--r--spec/ruby/library/set/sortedset/shared/include.rb7
-rw-r--r--spec/ruby/library/set/sortedset/shared/intersection.rb15
-rw-r--r--spec/ruby/library/set/sortedset/shared/length.rb6
-rw-r--r--spec/ruby/library/set/sortedset/shared/select.rb35
-rw-r--r--spec/ruby/library/set/sortedset/shared/union.rb15
-rw-r--r--spec/ruby/library/set/sortedset/size_spec.rb10
-rw-r--r--spec/ruby/library/set/sortedset/sortedset_spec.rb22
-rw-r--r--spec/ruby/library/set/sortedset/subset_spec.rb36
-rw-r--r--spec/ruby/library/set/sortedset/subtract_spec.rb20
-rw-r--r--spec/ruby/library/set/sortedset/superset_spec.rb36
-rw-r--r--spec/ruby/library/set/sortedset/to_a_spec.rb20
-rw-r--r--spec/ruby/library/set/sortedset/union_spec.rb14
-rw-r--r--spec/ruby/library/set/subset_spec.rb41
-rw-r--r--spec/ruby/library/set/subtract_spec.rb17
-rw-r--r--spec/ruby/library/set/superset_spec.rb41
-rw-r--r--spec/ruby/library/set/to_a_spec.rb8
-rw-r--r--spec/ruby/library/set/to_s_spec.rb11
-rw-r--r--spec/ruby/library/set/union_spec.rb11
-rw-r--r--spec/ruby/library/shellwords/shellwords_spec.rb34
-rw-r--r--spec/ruby/library/singleton/allocate_spec.rb8
-rw-r--r--spec/ruby/library/singleton/clone_spec.rb8
-rw-r--r--spec/ruby/library/singleton/dump_spec.rb14
-rw-r--r--spec/ruby/library/singleton/dup_spec.rb8
-rw-r--r--spec/ruby/library/singleton/fixtures/classes.rb18
-rw-r--r--spec/ruby/library/singleton/instance_spec.rb30
-rw-r--r--spec/ruby/library/singleton/load_spec.rb21
-rw-r--r--spec/ruby/library/singleton/new_spec.rb8
-rw-r--r--spec/ruby/library/socket/addrinfo/afamily_spec.rb37
-rw-r--r--spec/ruby/library/socket/addrinfo/bind_spec.rb28
-rw-r--r--spec/ruby/library/socket/addrinfo/canonname_spec.rb27
-rw-r--r--spec/ruby/library/socket/addrinfo/connect_from_spec.rb75
-rw-r--r--spec/ruby/library/socket/addrinfo/connect_spec.rb35
-rw-r--r--spec/ruby/library/socket/addrinfo/connect_to_spec.rb75
-rw-r--r--spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb115
-rw-r--r--spec/ruby/library/socket/addrinfo/foreach_spec.rb9
-rw-r--r--spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb91
-rw-r--r--spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb42
-rw-r--r--spec/ruby/library/socket/addrinfo/initialize_spec.rb591
-rw-r--r--spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb50
-rw-r--r--spec/ruby/library/socket/addrinfo/inspect_spec.rb65
-rw-r--r--spec/ruby/library/socket/addrinfo/ip_address_spec.rb66
-rw-r--r--spec/ruby/library/socket/addrinfo/ip_port_spec.rb35
-rw-r--r--spec/ruby/library/socket/addrinfo/ip_spec.rb64
-rw-r--r--spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb35
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb43
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb29
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb47
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv4_spec.rb35
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb23
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb45
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb20
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb19
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb18
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb18
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb18
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb48
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb23
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_spec.rb35
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb71
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb18
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb15
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb20
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb20
-rw-r--r--spec/ruby/library/socket/addrinfo/listen_spec.rb34
-rw-r--r--spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb84
-rw-r--r--spec/ruby/library/socket/addrinfo/marshal_load_spec.rb35
-rw-r--r--spec/ruby/library/socket/addrinfo/pfamily_spec.rb43
-rw-r--r--spec/ruby/library/socket/addrinfo/protocol_spec.rb24
-rw-r--r--spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb51
-rw-r--r--spec/ruby/library/socket/addrinfo/socktype_spec.rb23
-rw-r--r--spec/ruby/library/socket/addrinfo/tcp_spec.rb34
-rw-r--r--spec/ruby/library/socket/addrinfo/to_s_spec.rb6
-rw-r--r--spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb6
-rw-r--r--spec/ruby/library/socket/addrinfo/udp_spec.rb36
-rw-r--r--spec/ruby/library/socket/addrinfo/unix_path_spec.rb37
-rw-r--r--spec/ruby/library/socket/addrinfo/unix_spec.rb71
-rw-r--r--spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb33
-rw-r--r--spec/ruby/library/socket/ancillarydata/data_spec.rb9
-rw-r--r--spec/ruby/library/socket/ancillarydata/family_spec.rb9
-rw-r--r--spec/ruby/library/socket/ancillarydata/initialize_spec.rb284
-rw-r--r--spec/ruby/library/socket/ancillarydata/int_spec.rb43
-rw-r--r--spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb145
-rw-r--r--spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb11
-rw-r--r--spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb11
-rw-r--r--spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb89
-rw-r--r--spec/ruby/library/socket/ancillarydata/level_spec.rb9
-rw-r--r--spec/ruby/library/socket/ancillarydata/type_spec.rb9
-rw-r--r--spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb61
-rw-r--r--spec/ruby/library/socket/basicsocket/close_read_spec.rb43
-rw-r--r--spec/ruby/library/socket/basicsocket/close_write_spec.rb48
-rw-r--r--spec/ruby/library/socket/basicsocket/connect_address_spec.rb154
-rw-r--r--spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb103
-rw-r--r--spec/ruby/library/socket/basicsocket/for_fd_spec.rb38
-rw-r--r--spec/ruby/library/socket/basicsocket/getpeereid_spec.rb36
-rw-r--r--spec/ruby/library/socket/basicsocket/getpeername_spec.rb25
-rw-r--r--spec/ruby/library/socket/basicsocket/getsockname_spec.rb28
-rw-r--r--spec/ruby/library/socket/basicsocket/getsockopt_spec.rb188
-rw-r--r--spec/ruby/library/socket/basicsocket/ioctl_spec.rb42
-rw-r--r--spec/ruby/library/socket/basicsocket/local_address_spec.rb10
-rw-r--r--spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb44
-rw-r--r--spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb91
-rw-r--r--spec/ruby/library/socket/basicsocket/recv_spec.rb159
-rw-r--r--spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb224
-rw-r--r--spec/ruby/library/socket/basicsocket/recvmsg_spec.rb197
-rw-r--r--spec/ruby/library/socket/basicsocket/remote_address_spec.rb10
-rw-r--r--spec/ruby/library/socket/basicsocket/send_spec.rb220
-rw-r--r--spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb118
-rw-r--r--spec/ruby/library/socket/basicsocket/sendmsg_spec.rb111
-rw-r--r--spec/ruby/library/socket/basicsocket/setsockopt_spec.rb336
-rw-r--r--spec/ruby/library/socket/basicsocket/shutdown_spec.rb155
-rw-r--r--spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb43
-rw-r--r--spec/ruby/library/socket/constants/constants_spec.rb108
-rw-r--r--spec/ruby/library/socket/fixtures/classes.rb164
-rw-r--r--spec/ruby/library/socket/fixtures/send_io.txt1
-rw-r--r--spec/ruby/library/socket/ipsocket/addr_spec.rb105
-rw-r--r--spec/ruby/library/socket/ipsocket/getaddress_spec.rb25
-rw-r--r--spec/ruby/library/socket/ipsocket/peeraddr_spec.rb117
-rw-r--r--spec/ruby/library/socket/ipsocket/recvfrom_spec.rb123
-rw-r--r--spec/ruby/library/socket/option/bool_spec.rb27
-rw-r--r--spec/ruby/library/socket/option/initialize_spec.rb83
-rw-r--r--spec/ruby/library/socket/option/inspect_spec.rb19
-rw-r--r--spec/ruby/library/socket/option/int_spec.rb43
-rw-r--r--spec/ruby/library/socket/option/linger_spec.rb76
-rw-r--r--spec/ruby/library/socket/option/new_spec.rb35
-rw-r--r--spec/ruby/library/socket/shared/address.rb249
-rw-r--r--spec/ruby/library/socket/shared/pack_sockaddr.rb106
-rw-r--r--spec/ruby/library/socket/shared/partially_closable_sockets.rb13
-rw-r--r--spec/ruby/library/socket/shared/socketpair.rb138
-rw-r--r--spec/ruby/library/socket/socket/accept_loop_spec.rb84
-rw-r--r--spec/ruby/library/socket/socket/accept_nonblock_spec.rb141
-rw-r--r--spec/ruby/library/socket/socket/accept_spec.rb121
-rw-r--r--spec/ruby/library/socket/socket/bind_spec.rb150
-rw-r--r--spec/ruby/library/socket/socket/connect_nonblock_spec.rb149
-rw-r--r--spec/ruby/library/socket/socket/connect_spec.rb56
-rw-r--r--spec/ruby/library/socket/socket/for_fd_spec.rb30
-rw-r--r--spec/ruby/library/socket/socket/getaddrinfo_spec.rb373
-rw-r--r--spec/ruby/library/socket/socket/gethostbyaddr_spec.rb124
-rw-r--r--spec/ruby/library/socket/socket/gethostbyname_spec.rb135
-rw-r--r--spec/ruby/library/socket/socket/gethostname_spec.rb8
-rw-r--r--spec/ruby/library/socket/socket/getifaddrs_spec.rb117
-rw-r--r--spec/ruby/library/socket/socket/getnameinfo_spec.rb147
-rw-r--r--spec/ruby/library/socket/socket/getservbyname_spec.rb32
-rw-r--r--spec/ruby/library/socket/socket/getservbyport_spec.rb23
-rw-r--r--spec/ruby/library/socket/socket/initialize_spec.rb87
-rw-r--r--spec/ruby/library/socket/socket/ip_address_list_spec.rb50
-rw-r--r--spec/ruby/library/socket/socket/ipv6only_bang_spec.rb20
-rw-r--r--spec/ruby/library/socket/socket/listen_spec.rb66
-rw-r--r--spec/ruby/library/socket/socket/local_address_spec.rb43
-rw-r--r--spec/ruby/library/socket/socket/new_spec.rb2
-rw-r--r--spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/pair_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb118
-rw-r--r--spec/ruby/library/socket/socket/recvfrom_spec.rb92
-rw-r--r--spec/ruby/library/socket/socket/remote_address_spec.rb54
-rw-r--r--spec/ruby/library/socket/socket/sockaddr_in_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/sockaddr_un_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/socket_spec.rb38
-rw-r--r--spec/ruby/library/socket/socket/socketpair_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/sysaccept_spec.rb91
-rw-r--r--spec/ruby/library/socket/socket/tcp_server_loop_spec.rb54
-rw-r--r--spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb39
-rw-r--r--spec/ruby/library/socket/socket/tcp_spec.rb70
-rw-r--r--spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb47
-rw-r--r--spec/ruby/library/socket/socket/udp_server_loop_spec.rb59
-rw-r--r--spec/ruby/library/socket/socket/udp_server_recv_spec.rb35
-rw-r--r--spec/ruby/library/socket/socket/udp_server_sockets_spec.rb39
-rw-r--r--spec/ruby/library/socket/socket/unix_server_loop_spec.rb58
-rw-r--r--spec/ruby/library/socket/socket/unix_server_socket_spec.rb48
-rw-r--r--spec/ruby/library/socket/socket/unix_spec.rb45
-rw-r--r--spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb46
-rw-r--r--spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb26
-rw-r--r--spec/ruby/library/socket/spec_helper.rb13
-rw-r--r--spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb85
-rw-r--r--spec/ruby/library/socket/tcpserver/accept_spec.rb119
-rw-r--r--spec/ruby/library/socket/tcpserver/gets_spec.rb16
-rw-r--r--spec/ruby/library/socket/tcpserver/initialize_spec.rb101
-rw-r--r--spec/ruby/library/socket/tcpserver/listen_spec.rb22
-rw-r--r--spec/ruby/library/socket/tcpserver/new_spec.rb131
-rw-r--r--spec/ruby/library/socket/tcpserver/sysaccept_spec.rb66
-rw-r--r--spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb119
-rw-r--r--spec/ruby/library/socket/tcpsocket/initialize_spec.rb66
-rw-r--r--spec/ruby/library/socket/tcpsocket/local_address_spec.rb73
-rw-r--r--spec/ruby/library/socket/tcpsocket/open_spec.rb5
-rw-r--r--spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb21
-rw-r--r--spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb48
-rw-r--r--spec/ruby/library/socket/tcpsocket/recv_spec.rb28
-rw-r--r--spec/ruby/library/socket/tcpsocket/remote_address_spec.rb72
-rw-r--r--spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb45
-rw-r--r--spec/ruby/library/socket/tcpsocket/shared/new.rb102
-rw-r--r--spec/ruby/library/socket/udpsocket/bind_spec.rb83
-rw-r--r--spec/ruby/library/socket/udpsocket/connect_spec.rb35
-rw-r--r--spec/ruby/library/socket/udpsocket/initialize_spec.rb40
-rw-r--r--spec/ruby/library/socket/udpsocket/inspect_spec.rb17
-rw-r--r--spec/ruby/library/socket/udpsocket/local_address_spec.rb80
-rw-r--r--spec/ruby/library/socket/udpsocket/new_spec.rb34
-rw-r--r--spec/ruby/library/socket/udpsocket/open_spec.rb13
-rw-r--r--spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb102
-rw-r--r--spec/ruby/library/socket/udpsocket/remote_address_spec.rb79
-rw-r--r--spec/ruby/library/socket/udpsocket/send_spec.rb154
-rw-r--r--spec/ruby/library/socket/udpsocket/write_spec.rb21
-rw-r--r--spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb92
-rw-r--r--spec/ruby/library/socket/unixserver/accept_spec.rb117
-rw-r--r--spec/ruby/library/socket/unixserver/for_fd_spec.rb23
-rw-r--r--spec/ruby/library/socket/unixserver/initialize_spec.rb28
-rw-r--r--spec/ruby/library/socket/unixserver/listen_spec.rb21
-rw-r--r--spec/ruby/library/socket/unixserver/new_spec.rb6
-rw-r--r--spec/ruby/library/socket/unixserver/open_spec.rb26
-rw-r--r--spec/ruby/library/socket/unixserver/shared/new.rb22
-rw-r--r--spec/ruby/library/socket/unixserver/sysaccept_spec.rb52
-rw-r--r--spec/ruby/library/socket/unixsocket/addr_spec.rb36
-rw-r--r--spec/ruby/library/socket/unixsocket/initialize_spec.rb38
-rw-r--r--spec/ruby/library/socket/unixsocket/inspect_spec.rb17
-rw-r--r--spec/ruby/library/socket/unixsocket/local_address_spec.rb96
-rw-r--r--spec/ruby/library/socket/unixsocket/new_spec.rb6
-rw-r--r--spec/ruby/library/socket/unixsocket/open_spec.rb28
-rw-r--r--spec/ruby/library/socket/unixsocket/pair_spec.rb39
-rw-r--r--spec/ruby/library/socket/unixsocket/partially_closable_spec.rb25
-rw-r--r--spec/ruby/library/socket/unixsocket/path_spec.rb28
-rw-r--r--spec/ruby/library/socket/unixsocket/peeraddr_spec.rb30
-rw-r--r--spec/ruby/library/socket/unixsocket/recv_io_spec.rb87
-rw-r--r--spec/ruby/library/socket/unixsocket/recvfrom_spec.rb98
-rw-r--r--spec/ruby/library/socket/unixsocket/remote_address_spec.rb45
-rw-r--r--spec/ruby/library/socket/unixsocket/send_io_spec.rb58
-rw-r--r--spec/ruby/library/socket/unixsocket/shared/new.rb24
-rw-r--r--spec/ruby/library/socket/unixsocket/socketpair_spec.rb40
-rw-r--r--spec/ruby/library/stringio/append_spec.rb81
-rw-r--r--spec/ruby/library/stringio/binmode_spec.rb23
-rw-r--r--spec/ruby/library/stringio/bytes_spec.rb29
-rw-r--r--spec/ruby/library/stringio/chars_spec.rb29
-rw-r--r--spec/ruby/library/stringio/close_read_spec.rb31
-rw-r--r--spec/ruby/library/stringio/close_spec.rb23
-rw-r--r--spec/ruby/library/stringio/close_write_spec.rb31
-rw-r--r--spec/ruby/library/stringio/closed_read_spec.rb12
-rw-r--r--spec/ruby/library/stringio/closed_spec.rb16
-rw-r--r--spec/ruby/library/stringio/closed_write_spec.rb12
-rw-r--r--spec/ruby/library/stringio/codepoints_spec.rb19
-rw-r--r--spec/ruby/library/stringio/each_byte_spec.rb11
-rw-r--r--spec/ruby/library/stringio/each_char_spec.rb11
-rw-r--r--spec/ruby/library/stringio/each_codepoint_spec.rb9
-rw-r--r--spec/ruby/library/stringio/each_line_spec.rb23
-rw-r--r--spec/ruby/library/stringio/each_spec.rb27
-rw-r--r--spec/ruby/library/stringio/eof_spec.rb11
-rw-r--r--spec/ruby/library/stringio/external_encoding_spec.rb25
-rw-r--r--spec/ruby/library/stringio/fcntl_spec.rb8
-rw-r--r--spec/ruby/library/stringio/fileno_spec.rb9
-rw-r--r--spec/ruby/library/stringio/fixtures/classes.rb15
-rw-r--r--spec/ruby/library/stringio/flush_spec.rb9
-rw-r--r--spec/ruby/library/stringio/fsync_spec.rb9
-rw-r--r--spec/ruby/library/stringio/getbyte_spec.rb19
-rw-r--r--spec/ruby/library/stringio/getc_spec.rb19
-rw-r--r--spec/ruby/library/stringio/getch_spec.rb44
-rw-r--r--spec/ruby/library/stringio/getpass_spec.rb11
-rw-r--r--spec/ruby/library/stringio/gets_spec.rb250
-rw-r--r--spec/ruby/library/stringio/initialize_spec.rb307
-rw-r--r--spec/ruby/library/stringio/inspect_spec.rb19
-rw-r--r--spec/ruby/library/stringio/internal_encoding_spec.rb10
-rw-r--r--spec/ruby/library/stringio/isatty_spec.rb7
-rw-r--r--spec/ruby/library/stringio/length_spec.rb7
-rw-r--r--spec/ruby/library/stringio/lineno_spec.rb30
-rw-r--r--spec/ruby/library/stringio/lines_spec.rb53
-rw-r--r--spec/ruby/library/stringio/new_spec.rb8
-rw-r--r--spec/ruby/library/stringio/open_spec.rb215
-rw-r--r--spec/ruby/library/stringio/path_spec.rb8
-rw-r--r--spec/ruby/library/stringio/pid_spec.rb8
-rw-r--r--spec/ruby/library/stringio/pos_spec.rb28
-rw-r--r--spec/ruby/library/stringio/print_spec.rb102
-rw-r--r--spec/ruby/library/stringio/printf_spec.rb91
-rw-r--r--spec/ruby/library/stringio/putc_spec.rb103
-rw-r--r--spec/ruby/library/stringio/puts_spec.rb184
-rw-r--r--spec/ruby/library/stringio/read_nonblock_spec.rb53
-rw-r--r--spec/ruby/library/stringio/read_spec.rb62
-rw-r--r--spec/ruby/library/stringio/readbyte_spec.rb20
-rw-r--r--spec/ruby/library/stringio/readchar_spec.rb20
-rw-r--r--spec/ruby/library/stringio/readline_spec.rb150
-rw-r--r--spec/ruby/library/stringio/readlines_spec.rb118
-rw-r--r--spec/ruby/library/stringio/readpartial_spec.rb80
-rw-r--r--spec/ruby/library/stringio/reopen_spec.rb281
-rw-r--r--spec/ruby/library/stringio/rewind_spec.rb24
-rw-r--r--spec/ruby/library/stringio/seek_spec.rb67
-rw-r--r--spec/ruby/library/stringio/set_encoding_spec.rb20
-rw-r--r--spec/ruby/library/stringio/shared/codepoints.rb45
-rw-r--r--spec/ruby/library/stringio/shared/each.rb163
-rw-r--r--spec/ruby/library/stringio/shared/each_byte.rb48
-rw-r--r--spec/ruby/library/stringio/shared/each_char.rb36
-rw-r--r--spec/ruby/library/stringio/shared/eof.rb24
-rw-r--r--spec/ruby/library/stringio/shared/getc.rb43
-rw-r--r--spec/ruby/library/stringio/shared/isatty.rb5
-rw-r--r--spec/ruby/library/stringio/shared/length.rb5
-rw-r--r--spec/ruby/library/stringio/shared/read.rb127
-rw-r--r--spec/ruby/library/stringio/shared/readchar.rb29
-rw-r--r--spec/ruby/library/stringio/shared/sysread.rb15
-rw-r--r--spec/ruby/library/stringio/shared/tell.rb12
-rw-r--r--spec/ruby/library/stringio/shared/write.rb121
-rw-r--r--spec/ruby/library/stringio/size_spec.rb7
-rw-r--r--spec/ruby/library/stringio/string_spec.rb50
-rw-r--r--spec/ruby/library/stringio/stringio_spec.rb8
-rw-r--r--spec/ruby/library/stringio/sync_spec.rb19
-rw-r--r--spec/ruby/library/stringio/sysread_spec.rb48
-rw-r--r--spec/ruby/library/stringio/syswrite_spec.rb19
-rw-r--r--spec/ruby/library/stringio/tell_spec.rb7
-rw-r--r--spec/ruby/library/stringio/truncate_spec.rb62
-rw-r--r--spec/ruby/library/stringio/tty_spec.rb7
-rw-r--r--spec/ruby/library/stringio/ungetbyte_spec.rb42
-rw-r--r--spec/ruby/library/stringio/ungetc_spec.rb72
-rw-r--r--spec/ruby/library/stringio/write_nonblock_spec.rb25
-rw-r--r--spec/ruby/library/stringio/write_spec.rb19
-rw-r--r--spec/ruby/library/stringscanner/append_spec.rb11
-rw-r--r--spec/ruby/library/stringscanner/beginning_of_line_spec.rb7
-rw-r--r--spec/ruby/library/stringscanner/bol_spec.rb7
-rw-r--r--spec/ruby/library/stringscanner/check_spec.rb25
-rw-r--r--spec/ruby/library/stringscanner/check_until_spec.rb21
-rw-r--r--spec/ruby/library/stringscanner/clear_spec.rb18
-rw-r--r--spec/ruby/library/stringscanner/concat_spec.rb11
-rw-r--r--spec/ruby/library/stringscanner/dup_spec.rb39
-rw-r--r--spec/ruby/library/stringscanner/element_reference_spec.rb60
-rw-r--r--spec/ruby/library/stringscanner/empty_spec.rb18
-rw-r--r--spec/ruby/library/stringscanner/eos_spec.rb7
-rw-r--r--spec/ruby/library/stringscanner/exist_spec.rb30
-rw-r--r--spec/ruby/library/stringscanner/get_byte_spec.rb7
-rw-r--r--spec/ruby/library/stringscanner/getbyte_spec.rb21
-rw-r--r--spec/ruby/library/stringscanner/getch_spec.rb35
-rw-r--r--spec/ruby/library/stringscanner/initialize_spec.rb27
-rw-r--r--spec/ruby/library/stringscanner/inspect_spec.rb20
-rw-r--r--spec/ruby/library/stringscanner/match_spec.rb28
-rw-r--r--spec/ruby/library/stringscanner/matched_size_spec.rb24
-rw-r--r--spec/ruby/library/stringscanner/matched_spec.rb41
-rw-r--r--spec/ruby/library/stringscanner/must_C_version_spec.rb8
-rw-r--r--spec/ruby/library/stringscanner/peek_spec.rb7
-rw-r--r--spec/ruby/library/stringscanner/peep_spec.rb18
-rw-r--r--spec/ruby/library/stringscanner/pointer_spec.rb11
-rw-r--r--spec/ruby/library/stringscanner/pos_spec.rb11
-rw-r--r--spec/ruby/library/stringscanner/post_match_spec.rb28
-rw-r--r--spec/ruby/library/stringscanner/pre_match_spec.rb41
-rw-r--r--spec/ruby/library/stringscanner/reset_spec.rb15
-rw-r--r--spec/ruby/library/stringscanner/rest_size_spec.rb7
-rw-r--r--spec/ruby/library/stringscanner/rest_spec.rb48
-rw-r--r--spec/ruby/library/stringscanner/restsize_spec.rb18
-rw-r--r--spec/ruby/library/stringscanner/scan_full_spec.rb30
-rw-r--r--spec/ruby/library/stringscanner/scan_spec.rb87
-rw-r--r--spec/ruby/library/stringscanner/scan_until_spec.rb29
-rw-r--r--spec/ruby/library/stringscanner/search_full_spec.rb36
-rw-r--r--spec/ruby/library/stringscanner/shared/bol.rb25
-rw-r--r--spec/ruby/library/stringscanner/shared/concat.rb30
-rw-r--r--spec/ruby/library/stringscanner/shared/eos.rb17
-rw-r--r--spec/ruby/library/stringscanner/shared/extract_range.rb11
-rw-r--r--spec/ruby/library/stringscanner/shared/extract_range_matched.rb13
-rw-r--r--spec/ruby/library/stringscanner/shared/get_byte.rb29
-rw-r--r--spec/ruby/library/stringscanner/shared/peek.rb39
-rw-r--r--spec/ruby/library/stringscanner/shared/pos.rb52
-rw-r--r--spec/ruby/library/stringscanner/shared/rest_size.rb18
-rw-r--r--spec/ruby/library/stringscanner/shared/terminate.rb8
-rw-r--r--spec/ruby/library/stringscanner/size_spec.rb17
-rw-r--r--spec/ruby/library/stringscanner/skip_spec.rb18
-rw-r--r--spec/ruby/library/stringscanner/skip_until_spec.rb24
-rw-r--r--spec/ruby/library/stringscanner/string_spec.rb40
-rw-r--r--spec/ruby/library/stringscanner/terminate_spec.rb7
-rw-r--r--spec/ruby/library/stringscanner/unscan_spec.rb28
-rw-r--r--spec/ruby/library/syslog/alert_spec.rb10
-rw-r--r--spec/ruby/library/syslog/close_spec.rb58
-rw-r--r--spec/ruby/library/syslog/constants_spec.rb41
-rw-r--r--spec/ruby/library/syslog/crit_spec.rb10
-rw-r--r--spec/ruby/library/syslog/debug_spec.rb10
-rw-r--r--spec/ruby/library/syslog/emerg_spec.rb16
-rw-r--r--spec/ruby/library/syslog/err_spec.rb10
-rw-r--r--spec/ruby/library/syslog/facility_spec.rb48
-rw-r--r--spec/ruby/library/syslog/ident_spec.rb35
-rw-r--r--spec/ruby/library/syslog/info_spec.rb10
-rw-r--r--spec/ruby/library/syslog/inspect_spec.rb39
-rw-r--r--spec/ruby/library/syslog/instance_spec.rb13
-rw-r--r--spec/ruby/library/syslog/log_spec.rb56
-rw-r--r--spec/ruby/library/syslog/mask_spec.rb113
-rw-r--r--spec/ruby/library/syslog/notice_spec.rb10
-rw-r--r--spec/ruby/library/syslog/open_spec.rb92
-rw-r--r--spec/ruby/library/syslog/opened_spec.rb39
-rw-r--r--spec/ruby/library/syslog/options_spec.rb48
-rw-r--r--spec/ruby/library/syslog/reopen_spec.rb10
-rw-r--r--spec/ruby/library/syslog/shared/log.rb39
-rw-r--r--spec/ruby/library/syslog/shared/reopen.rb40
-rw-r--r--spec/ruby/library/syslog/warning_spec.rb10
-rw-r--r--spec/ruby/library/tempfile/_close_spec.rb21
-rw-r--r--spec/ruby/library/tempfile/callback_spec.rb6
-rw-r--r--spec/ruby/library/tempfile/close_spec.rb57
-rw-r--r--spec/ruby/library/tempfile/delete_spec.rb7
-rw-r--r--spec/ruby/library/tempfile/initialize_spec.rb46
-rw-r--r--spec/ruby/library/tempfile/length_spec.rb7
-rw-r--r--spec/ruby/library/tempfile/open_spec.rb97
-rw-r--r--spec/ruby/library/tempfile/path_spec.rb26
-rw-r--r--spec/ruby/library/tempfile/shared/length.rb21
-rw-r--r--spec/ruby/library/tempfile/shared/unlink.rb12
-rw-r--r--spec/ruby/library/tempfile/size_spec.rb7
-rw-r--r--spec/ruby/library/tempfile/unlink_spec.rb7
-rw-r--r--spec/ruby/library/thread/queue_spec.rb8
-rw-r--r--spec/ruby/library/thread/sizedqueue_spec.rb8
-rw-r--r--spec/ruby/library/time/httpdate_spec.rb21
-rw-r--r--spec/ruby/library/time/iso8601_spec.rb7
-rw-r--r--spec/ruby/library/time/rfc2822_spec.rb7
-rw-r--r--spec/ruby/library/time/rfc822_spec.rb7
-rw-r--r--spec/ruby/library/time/shared/rfc2822.rb65
-rw-r--r--spec/ruby/library/time/shared/xmlschema.rb53
-rw-r--r--spec/ruby/library/time/to_date_spec.rb42
-rw-r--r--spec/ruby/library/time/to_datetime_spec.rb41
-rw-r--r--spec/ruby/library/time/to_time_spec.rb15
-rw-r--r--spec/ruby/library/time/xmlschema_spec.rb7
-rw-r--r--spec/ruby/library/timeout/error_spec.rb8
-rw-r--r--spec/ruby/library/timeout/timeout_spec.rb42
-rw-r--r--spec/ruby/library/tmpdir/dir/mktmpdir_spec.rb117
-rw-r--r--spec/ruby/library/tmpdir/dir/tmpdir_spec.rb10
-rw-r--r--spec/ruby/library/uri/decode_www_form_component_spec.rb6
-rw-r--r--spec/ruby/library/uri/decode_www_form_spec.rb6
-rw-r--r--spec/ruby/library/uri/encode_www_form_component_spec.rb6
-rw-r--r--spec/ruby/library/uri/encode_www_form_spec.rb6
-rw-r--r--spec/ruby/library/uri/eql_spec.rb10
-rw-r--r--spec/ruby/library/uri/equality_spec.rb46
-rw-r--r--spec/ruby/library/uri/escape/decode_spec.rb6
-rw-r--r--spec/ruby/library/uri/escape/encode_spec.rb6
-rw-r--r--spec/ruby/library/uri/escape/escape_spec.rb6
-rw-r--r--spec/ruby/library/uri/escape/unescape_spec.rb6
-rw-r--r--spec/ruby/library/uri/extract_spec.rb86
-rw-r--r--spec/ruby/library/uri/fixtures/classes.rb11
-rw-r--r--spec/ruby/library/uri/fixtures/normalization.rb54
-rw-r--r--spec/ruby/library/uri/ftp/build_spec.rb6
-rw-r--r--spec/ruby/library/uri/ftp/merge_spec.rb6
-rw-r--r--spec/ruby/library/uri/ftp/new2_spec.rb6
-rw-r--r--spec/ruby/library/uri/ftp/path_spec.rb26
-rw-r--r--spec/ruby/library/uri/ftp/set_typecode_spec.rb6
-rw-r--r--spec/ruby/library/uri/ftp/to_s_spec.rb15
-rw-r--r--spec/ruby/library/uri/ftp/typecode_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/absolute_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/build2_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/build_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/coerce_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/component_ary_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/component_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/default_port_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/eql_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/equal_value_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/fragment_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/hash_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/hierarchical_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/host_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/inspect_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/merge_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/minus_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/normalize_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/opaque_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/password_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/path_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/plus_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/port_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/query_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/registry_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/relative_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/route_from_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/route_to_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/scheme_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/select_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_fragment_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_host_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_opaque_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_password_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_path_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_port_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_query_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_registry_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_scheme_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_user_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_userinfo_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/to_s_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/use_registry_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/user_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/userinfo_spec.rb10
-rw-r--r--spec/ruby/library/uri/http/build_spec.rb6
-rw-r--r--spec/ruby/library/uri/http/request_uri_spec.rb16
-rw-r--r--spec/ruby/library/uri/join_spec.rb59
-rw-r--r--spec/ruby/library/uri/ldap/attributes_spec.rb10
-rw-r--r--spec/ruby/library/uri/ldap/build_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/dn_spec.rb10
-rw-r--r--spec/ruby/library/uri/ldap/extensions_spec.rb10
-rw-r--r--spec/ruby/library/uri/ldap/filter_spec.rb10
-rw-r--r--spec/ruby/library/uri/ldap/hierarchical_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/scope_spec.rb10
-rw-r--r--spec/ruby/library/uri/ldap/set_attributes_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/set_dn_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/set_extensions_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/set_filter_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/set_scope_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/build_spec.rb92
-rw-r--r--spec/ruby/library/uri/mailto/headers_spec.rb10
-rw-r--r--spec/ruby/library/uri/mailto/set_headers_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/set_to_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/to_mailtext_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/to_rfc822text_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/to_s_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/to_spec.rb10
-rw-r--r--spec/ruby/library/uri/merge_spec.rb20
-rw-r--r--spec/ruby/library/uri/normalize_spec.rb35
-rw-r--r--spec/ruby/library/uri/parse_spec.rb203
-rw-r--r--spec/ruby/library/uri/parser/escape_spec.rb6
-rw-r--r--spec/ruby/library/uri/parser/extract_spec.rb7
-rw-r--r--spec/ruby/library/uri/parser/inspect_spec.rb6
-rw-r--r--spec/ruby/library/uri/parser/join_spec.rb7
-rw-r--r--spec/ruby/library/uri/parser/make_regexp_spec.rb6
-rw-r--r--spec/ruby/library/uri/parser/parse_spec.rb7
-rw-r--r--spec/ruby/library/uri/parser/split_spec.rb6
-rw-r--r--spec/ruby/library/uri/parser/unescape_spec.rb6
-rw-r--r--spec/ruby/library/uri/plus_spec.rb459
-rw-r--r--spec/ruby/library/uri/regexp_spec.rb18
-rw-r--r--spec/ruby/library/uri/route_from_spec.rb23
-rw-r--r--spec/ruby/library/uri/route_to_spec.rb26
-rw-r--r--spec/ruby/library/uri/select_spec.rb27
-rw-r--r--spec/ruby/library/uri/set_component_spec.rb45
-rw-r--r--spec/ruby/library/uri/shared/eql.rb17
-rw-r--r--spec/ruby/library/uri/shared/extract.rb83
-rw-r--r--spec/ruby/library/uri/shared/join.rb56
-rw-r--r--spec/ruby/library/uri/shared/parse.rb199
-rw-r--r--spec/ruby/library/uri/split_spec.rb6
-rw-r--r--spec/ruby/library/uri/uri_spec.rb29
-rw-r--r--spec/ruby/library/uri/util/make_components_hash_spec.rb6
-rw-r--r--spec/ruby/library/weakref/__getobj___spec.rb17
-rw-r--r--spec/ruby/library/weakref/allocate_spec.rb8
-rw-r--r--spec/ruby/library/weakref/fixtures/classes.rb26
-rw-r--r--spec/ruby/library/weakref/new_spec.rb13
-rw-r--r--spec/ruby/library/weakref/send_spec.rb37
-rw-r--r--spec/ruby/library/weakref/weakref_alive_spec.rb15
-rw-r--r--spec/ruby/library/win32ole/fixtures/classes.rb22
-rw-r--r--spec/ruby/library/win32ole/fixtures/event.xml4
-rw-r--r--spec/ruby/library/win32ole/win32ole/_getproperty_spec.rb14
-rw-r--r--spec/ruby/library/win32ole/win32ole/_invoke_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole/codepage_spec.rb13
-rw-r--r--spec/ruby/library/win32ole/win32ole/connect_spec.rb15
-rw-r--r--spec/ruby/library/win32ole/win32ole/const_load_spec.rb32
-rw-r--r--spec/ruby/library/win32ole/win32ole/constants_spec.rb42
-rw-r--r--spec/ruby/library/win32ole/win32ole/create_guid_spec.rb9
-rw-r--r--spec/ruby/library/win32ole/win32ole/invoke_spec.rb14
-rw-r--r--spec/ruby/library/win32ole/win32ole/locale_spec.rb29
-rw-r--r--spec/ruby/library/win32ole/win32ole/new_spec.rb25
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb16
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_method_help_spec.rb10
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_method_spec.rb10
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole/setproperty_spec.rb10
-rw-r--r--spec/ruby/library/win32ole/win32ole/shared/ole_method.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole/shared/setproperty.rb23
-rw-r--r--spec/ruby/library/win32ole/win32ole_event/new_spec.rb33
-rw-r--r--spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb70
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb28
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/event_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb26
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/name_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/new_spec.rb33
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/params_spec.rb28
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/shared/name.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/visible_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/default_spec.rb31
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/input_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/name_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/optional_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/retval_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/shared/name.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/guid_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/name_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/new_spec.rb40
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/progid_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/progids_spec.rb14
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/shared/name.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/variables_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/visible_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/name_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/shared/name.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/to_s_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/value_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb18
-rw-r--r--spec/ruby/library/yaml/dump_spec.rb56
-rw-r--r--spec/ruby/library/yaml/dump_stream_spec.rb8
-rw-r--r--spec/ruby/library/yaml/fixtures/common.rb4
-rw-r--r--spec/ruby/library/yaml/fixtures/example_class.rb7
-rw-r--r--spec/ruby/library/yaml/fixtures/strings.rb36
-rw-r--r--spec/ruby/library/yaml/fixtures/test_yaml.yml2
-rw-r--r--spec/ruby/library/yaml/load_file_spec.rb13
-rw-r--r--spec/ruby/library/yaml/load_spec.rb10
-rw-r--r--spec/ruby/library/yaml/load_stream_spec.rb8
-rw-r--r--spec/ruby/library/yaml/parse_file_spec.rb8
-rw-r--r--spec/ruby/library/yaml/parse_spec.rb22
-rw-r--r--spec/ruby/library/yaml/shared/each_document.rb18
-rw-r--r--spec/ruby/library/yaml/shared/load.rb136
-rw-r--r--spec/ruby/library/yaml/to_yaml_spec.rb98
-rw-r--r--spec/ruby/library/yaml/unsafe_load_spec.rb9
-rw-r--r--spec/ruby/library/zlib/adler32_spec.rb46
-rw-r--r--spec/ruby/library/zlib/crc32_spec.rb54
-rw-r--r--spec/ruby/library/zlib/crc_table_spec.rb80
-rw-r--r--spec/ruby/library/zlib/deflate/deflate_spec.rb133
-rw-r--r--spec/ruby/library/zlib/deflate/new_spec.rb1
-rw-r--r--spec/ruby/library/zlib/deflate/params_spec.rb17
-rw-r--r--spec/ruby/library/zlib/deflate/set_dictionary_spec.rb14
-rw-r--r--spec/ruby/library/zlib/deflate_spec.rb8
-rw-r--r--spec/ruby/library/zlib/gunzip_spec.rb14
-rw-r--r--spec/ruby/library/zlib/gzip_spec.rb15
-rw-r--r--spec/ruby/library/zlib/gzipfile/close_spec.rb21
-rw-r--r--spec/ruby/library/zlib/gzipfile/closed_spec.rb16
-rw-r--r--spec/ruby/library/zlib/gzipfile/comment_spec.rb26
-rw-r--r--spec/ruby/library/zlib/gzipfile/orig_name_spec.rb26
-rw-r--r--spec/ruby/library/zlib/gzipreader/each_byte_spec.rb51
-rw-r--r--spec/ruby/library/zlib/gzipreader/each_line_spec.rb5
-rw-r--r--spec/ruby/library/zlib/gzipreader/each_spec.rb5
-rw-r--r--spec/ruby/library/zlib/gzipreader/eof_spec.rb54
-rw-r--r--spec/ruby/library/zlib/gzipreader/getc_spec.rb39
-rw-r--r--spec/ruby/library/zlib/gzipreader/gets_spec.rb22
-rw-r--r--spec/ruby/library/zlib/gzipreader/new_spec.rb1
-rw-r--r--spec/ruby/library/zlib/gzipreader/pos_spec.rb24
-rw-r--r--spec/ruby/library/zlib/gzipreader/read_spec.rb66
-rw-r--r--spec/ruby/library/zlib/gzipreader/readpartial_spec.rb17
-rw-r--r--spec/ruby/library/zlib/gzipreader/rewind_spec.rb47
-rw-r--r--spec/ruby/library/zlib/gzipreader/shared/each.rb49
-rw-r--r--spec/ruby/library/zlib/gzipreader/ungetbyte_spec.rb120
-rw-r--r--spec/ruby/library/zlib/gzipreader/ungetc_spec.rb284
-rw-r--r--spec/ruby/library/zlib/gzipwriter/append_spec.rb15
-rw-r--r--spec/ruby/library/zlib/gzipwriter/mtime_spec.rb38
-rw-r--r--spec/ruby/library/zlib/gzipwriter/write_spec.rb36
-rw-r--r--spec/ruby/library/zlib/inflate/append_spec.rb60
-rw-r--r--spec/ruby/library/zlib/inflate/finish_spec.rb28
-rw-r--r--spec/ruby/library/zlib/inflate/inflate_spec.rb159
-rw-r--r--spec/ruby/library/zlib/inflate/new_spec.rb1
-rw-r--r--spec/ruby/library/zlib/inflate/set_dictionary_spec.rb20
-rw-r--r--spec/ruby/library/zlib/inflate_spec.rb8
-rw-r--r--spec/ruby/library/zlib/zlib_version_spec.rb8
-rw-r--r--spec/ruby/library/zlib/zstream/adler_spec.rb11
-rw-r--r--spec/ruby/library/zlib/zstream/avail_in_spec.rb9
-rw-r--r--spec/ruby/library/zlib/zstream/avail_out_spec.rb9
-rw-r--r--spec/ruby/library/zlib/zstream/data_type_spec.rb9
-rw-r--r--spec/ruby/library/zlib/zstream/flush_next_out_spec.rb14
-rw-r--r--spec/ruby/optional/capi/README13
-rw-r--r--spec/ruby/optional/capi/array_spec.rb497
-rw-r--r--spec/ruby/optional/capi/basic_object_spec.rb24
-rw-r--r--spec/ruby/optional/capi/bignum_spec.rb224
-rw-r--r--spec/ruby/optional/capi/binding_spec.rb28
-rw-r--r--spec/ruby/optional/capi/boolean_spec.rb33
-rw-r--r--spec/ruby/optional/capi/class_spec.rb492
-rw-r--r--spec/ruby/optional/capi/complex_spec.rb45
-rw-r--r--spec/ruby/optional/capi/constants_spec.rb325
-rw-r--r--spec/ruby/optional/capi/data_spec.rb52
-rw-r--r--spec/ruby/optional/capi/debug_spec.rb66
-rw-r--r--spec/ruby/optional/capi/encoding_spec.rb694
-rw-r--r--spec/ruby/optional/capi/enumerator_spec.rb66
-rw-r--r--spec/ruby/optional/capi/exception_spec.rb147
-rw-r--r--spec/ruby/optional/capi/ext/.gitignore9
-rw-r--r--spec/ruby/optional/capi/ext/array_spec.c297
-rw-r--r--spec/ruby/optional/capi/ext/basic_object_spec.c19
-rw-r--r--spec/ruby/optional/capi/ext/bignum_spec.c106
-rw-r--r--spec/ruby/optional/capi/ext/binding_spec.c19
-rw-r--r--spec/ruby/optional/capi/ext/boolean_spec.c33
-rw-r--r--spec/ruby/optional/capi/ext/class_id_under_autoload_spec.c13
-rw-r--r--spec/ruby/optional/capi/ext/class_spec.c179
-rw-r--r--spec/ruby/optional/capi/ext/class_under_autoload_spec.c13
-rw-r--r--spec/ruby/optional/capi/ext/complex_spec.c45
-rw-r--r--spec/ruby/optional/capi/ext/constants_spec.c178
-rw-r--r--spec/ruby/optional/capi/ext/data_spec.c89
-rw-r--r--spec/ruby/optional/capi/ext/debug_spec.c93
-rw-r--r--spec/ruby/optional/capi/ext/encoding_spec.c371
-rw-r--r--spec/ruby/optional/capi/ext/enumerator_spec.c32
-rw-r--r--spec/ruby/optional/capi/ext/exception_spec.c59
-rw-r--r--spec/ruby/optional/capi/ext/fiber_spec.c69
-rw-r--r--spec/ruby/optional/capi/ext/file_spec.c29
-rw-r--r--spec/ruby/optional/capi/ext/fixnum_spec.c26
-rw-r--r--spec/ruby/optional/capi/ext/float_spec.c47
-rw-r--r--spec/ruby/optional/capi/ext/gc_spec.c97
-rw-r--r--spec/ruby/optional/capi/ext/globals_spec.c161
-rw-r--r--spec/ruby/optional/capi/ext/hash_spec.c160
-rw-r--r--spec/ruby/optional/capi/ext/integer_spec.c41
-rw-r--r--spec/ruby/optional/capi/ext/io_spec.c312
-rw-r--r--spec/ruby/optional/capi/ext/kernel_spec.c405
-rw-r--r--spec/ruby/optional/capi/ext/language_spec.c42
-rw-r--r--spec/ruby/optional/capi/ext/marshal_spec.c24
-rw-r--r--spec/ruby/optional/capi/ext/module_spec.c176
-rw-r--r--spec/ruby/optional/capi/ext/module_under_autoload_spec.c15
-rw-r--r--spec/ruby/optional/capi/ext/mutex_spec.c55
-rw-r--r--spec/ruby/optional/capi/ext/numeric_spec.c130
-rw-r--r--spec/ruby/optional/capi/ext/object_spec.c528
-rw-r--r--spec/ruby/optional/capi/ext/proc_spec.c132
-rw-r--r--spec/ruby/optional/capi/ext/range_spec.c50
-rw-r--r--spec/ruby/optional/capi/ext/rational_spec.c54
-rw-r--r--spec/ruby/optional/capi/ext/rbasic_spec.c100
-rw-r--r--spec/ruby/optional/capi/ext/regexp_spec.c74
-rw-r--r--spec/ruby/optional/capi/ext/rubyspec.h37
-rw-r--r--spec/ruby/optional/capi/ext/st_spec.c83
-rw-r--r--spec/ruby/optional/capi/ext/string_spec.c719
-rw-r--r--spec/ruby/optional/capi/ext/struct_spec.c85
-rw-r--r--spec/ruby/optional/capi/ext/symbol_spec.c116
-rw-r--r--spec/ruby/optional/capi/ext/thread_spec.c188
-rw-r--r--spec/ruby/optional/capi/ext/time_spec.c81
-rw-r--r--spec/ruby/optional/capi/ext/tracepoint_spec.c49
-rw-r--r--spec/ruby/optional/capi/ext/typed_data_spec.c189
-rw-r--r--spec/ruby/optional/capi/ext/util_spec.c126
-rw-r--r--spec/ruby/optional/capi/fiber_spec.rb89
-rw-r--r--spec/ruby/optional/capi/file_spec.rb89
-rw-r--r--spec/ruby/optional/capi/fixnum_spec.rb101
-rw-r--r--spec/ruby/optional/capi/fixtures/class.rb104
-rw-r--r--spec/ruby/optional/capi/fixtures/const_get.rb5
-rw-r--r--spec/ruby/optional/capi/fixtures/const_get_at.rb5
-rw-r--r--spec/ruby/optional/capi/fixtures/const_get_from.rb5
-rw-r--r--spec/ruby/optional/capi/fixtures/const_get_object.rb3
-rw-r--r--spec/ruby/optional/capi/fixtures/encoding.rb3
-rw-r--r--spec/ruby/optional/capi/fixtures/foo.rb1
-rw-r--r--spec/ruby/optional/capi/fixtures/module.rb39
-rw-r--r--spec/ruby/optional/capi/fixtures/module_autoload.rb4
-rw-r--r--spec/ruby/optional/capi/fixtures/object.rb29
-rw-r--r--spec/ruby/optional/capi/fixtures/path_to_class.rb6
-rw-r--r--spec/ruby/optional/capi/fixtures/proc.rb20
-rw-r--r--spec/ruby/optional/capi/fixtures/read.txt1
-rw-r--r--spec/ruby/optional/capi/float_spec.rb43
-rw-r--r--spec/ruby/optional/capi/gc_spec.rb105
-rw-r--r--spec/ruby/optional/capi/globals_spec.rb293
-rw-r--r--spec/ruby/optional/capi/hash_spec.rb274
-rw-r--r--spec/ruby/optional/capi/integer_spec.rb290
-rw-r--r--spec/ruby/optional/capi/io_spec.rb418
-rw-r--r--spec/ruby/optional/capi/kernel_spec.rb696
-rw-r--r--spec/ruby/optional/capi/language_spec.rb37
-rw-r--r--spec/ruby/optional/capi/marshal_spec.rb46
-rw-r--r--spec/ruby/optional/capi/module_spec.rb431
-rw-r--r--spec/ruby/optional/capi/mutex_spec.rb89
-rw-r--r--spec/ruby/optional/capi/numeric_spec.rb495
-rw-r--r--spec/ruby/optional/capi/object_spec.rb1007
-rw-r--r--spec/ruby/optional/capi/proc_spec.rb135
-rw-r--r--spec/ruby/optional/capi/rake_helper.rb22
-rw-r--r--spec/ruby/optional/capi/range_spec.rb95
-rw-r--r--spec/ruby/optional/capi/rational_spec.rb57
-rw-r--r--spec/ruby/optional/capi/rbasic_spec.rb42
-rw-r--r--spec/ruby/optional/capi/regexp_spec.rb128
-rw-r--r--spec/ruby/optional/capi/shared/rbasic.rb29
-rw-r--r--spec/ruby/optional/capi/spec_helper.rb147
-rw-r--r--spec/ruby/optional/capi/st_spec.rb41
-rw-r--r--spec/ruby/optional/capi/string_spec.rb1293
-rw-r--r--spec/ruby/optional/capi/struct_spec.rb211
-rw-r--r--spec/ruby/optional/capi/symbol_spec.rb180
-rw-r--r--spec/ruby/optional/capi/thread_spec.rb189
-rw-r--r--spec/ruby/optional/capi/time_spec.rb296
-rw-r--r--spec/ruby/optional/capi/tracepoint_spec.rb56
-rw-r--r--spec/ruby/optional/capi/typed_data_spec.rb88
-rw-r--r--spec/ruby/optional/capi/util_spec.rb326
-rw-r--r--spec/ruby/security/cve_2010_1330_spec.rb19
-rw-r--r--spec/ruby/security/cve_2011_4815_spec.rb47
-rw-r--r--spec/ruby/security/cve_2013_4164_spec.rb15
-rw-r--r--spec/ruby/security/cve_2014_8080_spec.rb34
-rw-r--r--spec/ruby/security/cve_2017_17742_spec.rb37
-rw-r--r--spec/ruby/security/cve_2018_16396_spec.rb7
-rw-r--r--spec/ruby/security/cve_2018_6914_spec.rb55
-rw-r--r--spec/ruby/security/cve_2018_8778_spec.rb10
-rw-r--r--spec/ruby/security/cve_2018_8779_spec.rb30
-rw-r--r--spec/ruby/security/cve_2018_8780_spec.rb43
-rw-r--r--spec/ruby/security/cve_2019_8321_spec.rb20
-rw-r--r--spec/ruby/security/cve_2019_8322_spec.rb21
-rw-r--r--spec/ruby/security/cve_2019_8323_spec.rb36
-rw-r--r--spec/ruby/security/cve_2019_8325_spec.rb46
-rw-r--r--spec/ruby/security/cve_2020_10663_spec.rb39
-rw-r--r--spec/ruby/shared/basicobject/method_missing.rb124
-rw-r--r--spec/ruby/shared/basicobject/send.rb128
-rw-r--r--spec/ruby/shared/enumerable/minmax.rb24
-rw-r--r--spec/ruby/shared/enumerator/enum_for.rb57
-rw-r--r--spec/ruby/shared/enumerator/with_index.rb33
-rw-r--r--spec/ruby/shared/enumerator/with_object.rb42
-rw-r--r--spec/ruby/shared/fiber/resume.rb58
-rw-r--r--spec/ruby/shared/file/blockdev.rb9
-rw-r--r--spec/ruby/shared/file/chardev.rb9
-rw-r--r--spec/ruby/shared/file/directory.rb66
-rw-r--r--spec/ruby/shared/file/executable.rb83
-rw-r--r--spec/ruby/shared/file/executable_real.rb81
-rw-r--r--spec/ruby/shared/file/exist.rb24
-rw-r--r--spec/ruby/shared/file/file.rb45
-rw-r--r--spec/ruby/shared/file/grpowned.rb39
-rw-r--r--spec/ruby/shared/file/identical.rb51
-rw-r--r--spec/ruby/shared/file/owned.rb3
-rw-r--r--spec/ruby/shared/file/pipe.rb3
-rw-r--r--spec/ruby/shared/file/readable.rb49
-rw-r--r--spec/ruby/shared/file/readable_real.rb39
-rw-r--r--spec/ruby/shared/file/setgid.rb2
-rw-r--r--spec/ruby/shared/file/setuid.rb2
-rw-r--r--spec/ruby/shared/file/size.rb124
-rw-r--r--spec/ruby/shared/file/socket.rb3
-rw-r--r--spec/ruby/shared/file/sticky.rb29
-rw-r--r--spec/ruby/shared/file/symlink.rb46
-rw-r--r--spec/ruby/shared/file/world_readable.rb49
-rw-r--r--spec/ruby/shared/file/world_writable.rb49
-rw-r--r--spec/ruby/shared/file/writable.rb44
-rw-r--r--spec/ruby/shared/file/writable_real.rb49
-rw-r--r--spec/ruby/shared/file/zero.rb68
-rw-r--r--spec/ruby/shared/hash/key_error.rb23
-rw-r--r--spec/ruby/shared/io/putc.rb57
-rw-r--r--spec/ruby/shared/kernel/complex.rb133
-rw-r--r--spec/ruby/shared/kernel/equal.rb54
-rw-r--r--spec/ruby/shared/kernel/object_id.rb80
-rw-r--r--spec/ruby/shared/kernel/raise.rb149
-rw-r--r--spec/ruby/shared/math/atanh.rb44
-rw-r--r--spec/ruby/shared/process/abort.rb36
-rw-r--r--spec/ruby/shared/process/exit.rb120
-rw-r--r--spec/ruby/shared/process/fork.rb90
-rw-r--r--spec/ruby/shared/queue/clear.rb12
-rw-r--r--spec/ruby/shared/queue/close.rb14
-rw-r--r--spec/ruby/shared/queue/closed.rb12
-rw-r--r--spec/ruby/shared/queue/deque.rb147
-rw-r--r--spec/ruby/shared/queue/empty.rb12
-rw-r--r--spec/ruby/shared/queue/enque.rb18
-rw-r--r--spec/ruby/shared/queue/length.rb9
-rw-r--r--spec/ruby/shared/queue/num_waiting.rb16
-rw-r--r--spec/ruby/shared/rational/Rational.rb141
-rw-r--r--spec/ruby/shared/rational/abs.rb11
-rw-r--r--spec/ruby/shared/rational/arithmetic_exception_in_coerce.rb11
-rw-r--r--spec/ruby/shared/rational/ceil.rb45
-rw-r--r--spec/ruby/shared/rational/coerce.rb34
-rw-r--r--spec/ruby/shared/rational/comparison.rb95
-rw-r--r--spec/ruby/shared/rational/denominator.rb14
-rw-r--r--spec/ruby/shared/rational/div.rb54
-rw-r--r--spec/ruby/shared/rational/divide.rb71
-rw-r--r--spec/ruby/shared/rational/divmod.rb42
-rw-r--r--spec/ruby/shared/rational/equal_value.rb39
-rw-r--r--spec/ruby/shared/rational/exponent.rb196
-rw-r--r--spec/ruby/shared/rational/fdiv.rb5
-rw-r--r--spec/ruby/shared/rational/floor.rb45
-rw-r--r--spec/ruby/shared/rational/hash.rb9
-rw-r--r--spec/ruby/shared/rational/inspect.rb14
-rw-r--r--spec/ruby/shared/rational/marshal_dump.rb5
-rw-r--r--spec/ruby/shared/rational/marshal_load.rb5
-rw-r--r--spec/ruby/shared/rational/modulo.rb43
-rw-r--r--spec/ruby/shared/rational/multiply.rb62
-rw-r--r--spec/ruby/shared/rational/numerator.rb10
-rw-r--r--spec/ruby/shared/rational/plus.rb48
-rw-r--r--spec/ruby/shared/rational/quo.rb5
-rw-r--r--spec/ruby/shared/rational/remainder.rb5
-rw-r--r--spec/ruby/shared/rational/round.rb106
-rw-r--r--spec/ruby/shared/rational/to_f.rb16
-rw-r--r--spec/ruby/shared/rational/to_i.rb12
-rw-r--r--spec/ruby/shared/rational/to_r.rb11
-rw-r--r--spec/ruby/shared/rational/to_s.rb14
-rw-r--r--spec/ruby/shared/rational/truncate.rb45
-rw-r--r--spec/ruby/shared/sizedqueue/enque.rb113
-rw-r--r--spec/ruby/shared/sizedqueue/max.rb47
-rw-r--r--spec/ruby/shared/sizedqueue/new.rb23
-rw-r--r--spec/ruby/shared/sizedqueue/num_waiting.rb12
-rw-r--r--spec/ruby/shared/string/end_with.rb61
-rw-r--r--spec/ruby/shared/string/start_with.rb76
-rw-r--r--spec/ruby/shared/string/times.rb68
-rw-r--r--spec/ruby/shared/time/strftime_for_date.rb273
-rw-r--r--spec/ruby/shared/time/strftime_for_time.rb181
-rw-r--r--spec/ruby/spec_helper.rb38
-rw-r--r--spec/syntax_suggest/fixtures/derailed_require_tree.rb.txt74
-rwxr-xr-xspec/syntax_suggest/fixtures/rexe.rb.txt569
-rw-r--r--spec/syntax_suggest/fixtures/routes.rb.txt121
-rw-r--r--spec/syntax_suggest/fixtures/ruby_buildpack.rb.txt1344
-rw-r--r--spec/syntax_suggest/fixtures/syntax_tree.rb.txt9234
-rw-r--r--spec/syntax_suggest/fixtures/this_project_extra_def.rb.txt64
-rw-r--r--spec/syntax_suggest/fixtures/webmock.rb.txt35
-rw-r--r--spec/syntax_suggest/integration/exe_cli_spec.rb27
-rw-r--r--spec/syntax_suggest/integration/ruby_command_line_spec.rb193
-rw-r--r--spec/syntax_suggest/integration/syntax_suggest_spec.rb239
-rw-r--r--spec/syntax_suggest/spec_helper.rb104
-rw-r--r--spec/syntax_suggest/unit/api_spec.rb108
-rw-r--r--spec/syntax_suggest/unit/around_block_scan_spec.rb165
-rw-r--r--spec/syntax_suggest/unit/block_expand_spec.rb230
-rw-r--r--spec/syntax_suggest/unit/capture/before_after_keyword_ends_spec.rb47
-rw-r--r--spec/syntax_suggest/unit/capture/falling_indent_lines_spec.rb44
-rw-r--r--spec/syntax_suggest/unit/capture_code_context_spec.rb229
-rw-r--r--spec/syntax_suggest/unit/clean_document_spec.rb260
-rw-r--r--spec/syntax_suggest/unit/cli_spec.rb224
-rw-r--r--spec/syntax_suggest/unit/code_block_spec.rb77
-rw-r--r--spec/syntax_suggest/unit/code_frontier_spec.rb135
-rw-r--r--spec/syntax_suggest/unit/code_line_spec.rb165
-rw-r--r--spec/syntax_suggest/unit/code_search_spec.rb505
-rw-r--r--spec/syntax_suggest/unit/core_ext_spec.rb34
-rw-r--r--spec/syntax_suggest/unit/display_invalid_blocks_spec.rb174
-rw-r--r--spec/syntax_suggest/unit/explain_syntax_spec.rb255
-rw-r--r--spec/syntax_suggest/unit/lex_all_spec.rb29
-rw-r--r--spec/syntax_suggest/unit/pathname_from_message_spec.rb56
-rw-r--r--spec/syntax_suggest/unit/priority_queue_spec.rb95
-rw-r--r--spec/syntax_suggest/unit/scan_history_spec.rb114
5320 files changed, 356555 insertions, 47 deletions
diff --git a/spec/README b/spec/README
deleted file mode 100644
index 9821404697..0000000000
--- a/spec/README
+++ /dev/null
@@ -1,31 +0,0 @@
-= RubySpec
-
-RubySpec (http://rubyspec.org) provides the annotation of the Ruby
-implementation in an executable format. The make task
-`update-rubyspec' retrieves the specification and puts it into this
-directory.
-
-== Directory structure
- spec
- +-- mspec driver library for executing the specification.
- +-- rubyspec
- +-- core specification for core libraries
- | +-- array
- | +-- bignum
- | +-- ...
- |
- +-- fixtures example classes for writing specs
- +-- language specification for Ruby language itself
- +-- library specification for standard libraries
- +-- addrev
- +-- ...
-
-== How to run
-:make target
- verifies all specs.
- $ make test-rubyspec
-:mspec command
- verifies the specified spec.
- $ mspec {language|core|library}
- or
- $ mspec spec/path/to/some_spec.rb
diff --git a/spec/README.md b/spec/README.md
new file mode 100644
index 0000000000..4fcf090759
--- /dev/null
+++ b/spec/README.md
@@ -0,0 +1,160 @@
+# spec/bundler
+
+spec/bundler is rspec examples for bundler library (`lib/bundler.rb`, `lib/bundler/*`).
+
+## Running spec/bundler
+
+To run rspec for bundler:
+
+```bash
+make test-bundler
+```
+
+or run rspec with parallel execution:
+
+```bash
+make test-bundler-parallel
+```
+
+If you specify `BUNDLER_SPECS=foo/bar_spec.rb` then only `spec/bundler/foo/bar_spec.rb` will be run.
+
+# spec/ruby
+
+ruby/spec (https://github.com/ruby/spec/) is
+a test suite for the Ruby language.
+
+Once a month, @eregon merges the in-tree copy under spec/ruby
+with the upstream repository, preserving the commits and history.
+The same happens for other implementations such as JRuby and TruffleRuby.
+
+Feel welcome to modify the in-tree spec/ruby.
+This is the purpose of the in-tree copy,
+to facilitate contributions to ruby/spec for MRI developers.
+
+New features, additional tests for existing features and
+regressions tests are all welcome in ruby/spec.
+There is very little behavior that is implementation-specific,
+as in the end user programs tend to rely on every behavior MRI exhibits.
+In other words: If adding a spec might reveal a bug in
+another implementation, then it is worth adding it.
+Currently, the only module which is MRI-specific is `RubyVM`.
+
+## Changing behavior and versions guards
+
+Version guards (`ruby_version_is`) must be added for new features or features
+which change behavior or are removed. This is necessary for other Ruby implementations
+to still be able to run the specs and contribute new specs.
+
+For example, change:
+
+```ruby
+describe "Some spec" do
+ it "some example" do
+ # Old behavior for Ruby < 2.7
+ end
+end
+```
+
+to:
+
+```ruby
+describe "Some spec" do
+ ruby_version_is ""..."2.7" do
+ it "some example" do
+ # Old behavior for Ruby < 2.7
+ end
+ end
+
+ ruby_version_is "2.7" do
+ it "some example" do
+ # New behavior for Ruby >= 2.7
+ end
+ end
+end
+```
+
+See `spec/ruby/CONTRIBUTING.md` for more documentation about guards.
+
+To verify specs are compatible with older Ruby versions:
+
+```bash
+cd spec/ruby
+$RUBY_MANAGER use 2.4.9
+../mspec/bin/mspec -j
+```
+
+## Running ruby/spec
+
+To run all specs:
+
+```bash
+make test-spec
+```
+
+Extra arguments can be added via `MSPECOPT`.
+For instance, to show the help:
+
+```bash
+make test-spec MSPECOPT=-h
+```
+
+You can also run the specs in parallel, which is currently experimental.
+It takes around 10s instead of 60s on a quad-core laptop.
+
+```bash
+make test-spec MSPECOPT=-j
+```
+
+To run a specific test, add its path to the command:
+
+```bash
+make test-spec MSPECOPT=spec/ruby/language/for_spec.rb
+```
+
+If ruby trunk is your current `ruby` in `$PATH`, you can also run `mspec` directly:
+
+```bash
+# change ruby to trunk
+ruby -v # => trunk
+spec/mspec/bin/mspec spec/ruby/language/for_spec.rb
+```
+
+## ruby/spec and test/
+
+The main difference between a "spec" under `spec/ruby/` and
+a test under `test/` is that specs are documenting what they test.
+This is extremely valuable when reading these tests, as it
+helps to quickly understand what specific behavior is tested,
+and how a method should behave. Basic English is fine for spec descriptions.
+Specs also tend to have few expectations (assertions) per spec,
+as they specify one aspect of the behavior and not everything at once.
+Beyond that, the syntax is slightly different but it does the same thing:
+`assert_equal 3, 1+2` is just `(1+2).should == 3`.
+
+Example:
+
+```ruby
+describe "The for expression" do
+ it "iterates over an Enumerable passing each element to the block" do
+ j = 0
+ for i in 1..3
+ j += i
+ end
+ j.should == 6
+ end
+end
+```
+
+For more details, see `spec/ruby/CONTRIBUTING.md`.
+
+# spec/syntax_suggest
+
+## Running spec/syntax_suggest
+
+To run rspec for syntax_suggest:
+
+```bash
+make test-syntax-suggest
+```
+
+If you specify `SYNTAX_SUGGEST_SPECS=foo/bar_spec.rb` then only `spec/syntax_suggest/foo/bar_spec.rb` will be run.
diff --git a/spec/bundler/bundler/build_metadata_spec.rb b/spec/bundler/bundler/build_metadata_spec.rb
new file mode 100644
index 0000000000..afa2d1716f
--- /dev/null
+++ b/spec/bundler/bundler/build_metadata_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require "bundler"
+require "bundler/build_metadata"
+
+RSpec.describe Bundler::BuildMetadata do
+ before do
+ allow(Time).to receive(:now).and_return(Time.at(0))
+ Bundler::BuildMetadata.instance_variable_set(:@built_at, nil)
+ end
+
+ describe "#built_at" do
+ it "returns %Y-%m-%d formatted time" do
+ expect(Bundler::BuildMetadata.built_at).to eq "1970-01-01"
+ end
+ end
+
+ describe "#release?" do
+ it "returns false as default" do
+ expect(Bundler::BuildMetadata.release?).to be_falsey
+ end
+ end
+
+ describe "#git_commit_sha" do
+ context "if instance valuable is defined" do
+ before do
+ Bundler::BuildMetadata.instance_variable_set(:@git_commit_sha, "foo")
+ end
+
+ after do
+ Bundler::BuildMetadata.remove_instance_variable(:@git_commit_sha)
+ end
+
+ it "returns set value" do
+ expect(Bundler::BuildMetadata.git_commit_sha).to eq "foo"
+ end
+ end
+ end
+
+ describe "#to_h" do
+ subject { Bundler::BuildMetadata.to_h }
+
+ it "returns a hash includes Built At, Git SHA and Released Version" do
+ expect(subject["Built At"]).to eq "1970-01-01"
+ expect(subject["Git SHA"]).to be_instance_of(String)
+ expect(subject["Released Version"]).to be_falsey
+ end
+ end
+end
diff --git a/spec/bundler/bundler/bundler_spec.rb b/spec/bundler/bundler/bundler_spec.rb
new file mode 100644
index 0000000000..54fedc8568
--- /dev/null
+++ b/spec/bundler/bundler/bundler_spec.rb
@@ -0,0 +1,378 @@
+# frozen_string_literal: true
+
+require "bundler"
+require "tmpdir"
+
+RSpec.describe Bundler do
+ describe "#load_marshal" do
+ it "is a private method and raises an error" do
+ data = Marshal.dump(Bundler)
+ expect { Bundler.load_marshal(data) }.to raise_error(NoMethodError, /private method `load_marshal' called/)
+ end
+
+ it "loads any data" do
+ data = Marshal.dump(Bundler)
+ expect(Bundler.send(:load_marshal, data)).to eq(Bundler)
+ end
+ end
+
+ describe "#safe_load_marshal" do
+ it "fails on unexpected class" do
+ data = Marshal.dump(Bundler)
+ expect { Bundler.safe_load_marshal(data) }.to raise_error(Bundler::MarshalError)
+ end
+
+ it "loads simple structure" do
+ simple_structure = { "name" => [:abc] }
+ data = Marshal.dump(simple_structure)
+ expect(Bundler.safe_load_marshal(data)).to eq(simple_structure)
+ end
+
+ it "loads Gem::Specification" do
+ gem_spec = Gem::Specification.new do |s|
+ s.name = "bundler"
+ s.version = Gem::Version.new("2.4.7")
+ s.installed_by_version = Gem::Version.new("0")
+ s.authors = ["André Arko",
+ "Samuel Giddins",
+ "Colby Swandale",
+ "Hiroshi Shibata",
+ "David Rodríguez",
+ "Grey Baker",
+ "Stephanie Morillo",
+ "Chris Morris",
+ "James Wen",
+ "Tim Moore",
+ "André Medeiros",
+ "Jessica Lynn Suttles",
+ "Terence Lee",
+ "Carl Lerche",
+ "Yehuda Katz"]
+ s.date = Time.utc(2023, 2, 15)
+ s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably"
+ s.email = ["team@bundler.io"]
+ s.homepage = "https://bundler.io"
+ s.metadata = { "bug_tracker_uri" => "https://github.com/rubygems/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler",
+ "changelog_uri" => "https://github.com/rubygems/rubygems/blob/master/bundler/CHANGELOG.md",
+ "homepage_uri" => "https://bundler.io/",
+ "source_code_uri" => "https://github.com/rubygems/rubygems/tree/master/bundler" }
+ s.require_paths = ["lib"]
+ s.required_ruby_version = Gem::Requirement.new([">= 2.6.0"])
+ s.required_rubygems_version = Gem::Requirement.new([">= 3.0.1"])
+ s.rubygems_version = "3.4.7"
+ s.specification_version = 4
+ s.summary = "The best way to manage your application's dependencies"
+ s.license = false
+ end
+ data = Marshal.dump(gem_spec)
+ expect(Bundler.safe_load_marshal(data)).to eq(gem_spec)
+ end
+ end
+
+ describe "#load_gemspec_uncached" do
+ let(:app_gemspec_path) { tmp("test.gemspec") }
+ subject { Bundler.load_gemspec_uncached(app_gemspec_path) }
+
+ context "with incorrect YAML file" do
+ before do
+ File.open(app_gemspec_path, "wb") do |f|
+ f.write strip_whitespace(<<-GEMSPEC)
+ ---
+ {:!00 ao=gu\g1= 7~f
+ GEMSPEC
+ end
+ end
+
+ it "catches YAML syntax errors" do
+ expect { subject }.to raise_error(Bundler::GemspecError, /error while loading `test.gemspec`/)
+ end
+ end
+
+ context "with correct YAML file", :if => defined?(Encoding) do
+ it "can load a gemspec with unicode characters with default ruby encoding" do
+ # spec_helper forces the external encoding to UTF-8 but that's not the
+ # default until Ruby 2.0
+ verbose = $VERBOSE
+ $VERBOSE = false
+ encoding = Encoding.default_external
+ Encoding.default_external = "ASCII"
+ $VERBOSE = verbose
+
+ File.open(app_gemspec_path, "wb") do |file|
+ file.puts <<-GEMSPEC.gsub(/^\s+/, "")
+ # -*- encoding: utf-8 -*-
+ Gem::Specification.new do |gem|
+ gem.author = "André the Giant"
+ end
+ GEMSPEC
+ end
+
+ expect(subject.author).to eq("André the Giant")
+
+ verbose = $VERBOSE
+ $VERBOSE = false
+ Encoding.default_external = encoding
+ $VERBOSE = verbose
+ end
+ end
+
+ it "sets loaded_from" do
+ app_gemspec_path.open("w") do |f|
+ f.puts <<-GEMSPEC
+ Gem::Specification.new do |gem|
+ gem.name = "validated"
+ end
+ GEMSPEC
+ end
+
+ expect(subject.loaded_from).to eq(app_gemspec_path.expand_path.to_s)
+ end
+
+ context "validate is true" do
+ subject { Bundler.load_gemspec_uncached(app_gemspec_path, true) }
+
+ it "validates the specification" do
+ app_gemspec_path.open("w") do |f|
+ f.puts <<-GEMSPEC
+ Gem::Specification.new do |gem|
+ gem.name = "validated"
+ end
+ GEMSPEC
+ end
+ expect(Bundler.rubygems).to receive(:validate).with have_attributes(:name => "validated")
+ subject
+ end
+ end
+
+ context "with gemspec containing local variables" do
+ before do
+ File.open(app_gemspec_path, "wb") do |f|
+ f.write strip_whitespace(<<-GEMSPEC)
+ must_not_leak = true
+ Gem::Specification.new do |gem|
+ gem.name = "leak check"
+ end
+ GEMSPEC
+ end
+ end
+
+ it "should not pollute the TOPLEVEL_BINDING" do
+ subject
+ expect(TOPLEVEL_BINDING.eval("local_variables")).to_not include(:must_not_leak)
+ end
+ end
+ end
+
+ describe "#which" do
+ let(:executable) { "executable" }
+
+ let(:path) do
+ if Gem.win_platform?
+ %w[C:/a C:/b C:/c C:/../d C:/e]
+ else
+ %w[/a /b c ../d /e]
+ end
+ end
+
+ let(:expected) { "executable" }
+
+ before do
+ ENV["PATH"] = path.join(File::PATH_SEPARATOR)
+
+ allow(File).to receive(:file?).and_return(false)
+ allow(File).to receive(:executable?).and_return(false)
+ if expected
+ expect(File).to receive(:file?).with(expected).and_return(true)
+ expect(File).to receive(:executable?).with(expected).and_return(true)
+ end
+ end
+
+ subject { described_class.which(executable) }
+
+ shared_examples_for "it returns the correct executable" do
+ it "returns the expected file" do
+ expect(subject).to eq(expected)
+ end
+ end
+
+ it_behaves_like "it returns the correct executable"
+
+ context "when the executable in inside a quoted path" do
+ let(:expected) do
+ if Gem.win_platform?
+ "C:/e/executable"
+ else
+ "/e/executable"
+ end
+ end
+ it_behaves_like "it returns the correct executable"
+ end
+
+ context "when the executable is not found" do
+ let(:expected) { nil }
+ it_behaves_like "it returns the correct executable"
+ end
+ end
+
+ describe "configuration" do
+ context "disable_shared_gems" do
+ it "should unset GEM_PATH with empty string" do
+ expect(Bundler).to receive(:use_system_gems?).and_return(false)
+ Bundler.send(:configure_gem_path)
+ expect(ENV["GEM_PATH"]).to eq ""
+ end
+ end
+ end
+
+ describe "#rm_rf" do
+ context "the directory is world writable" do
+ let(:bundler_ui) { Bundler.ui }
+ it "should raise a friendly error" do
+ allow(File).to receive(:exist?).and_return(true)
+ allow(::Bundler::FileUtils).to receive(:remove_entry_secure).and_raise(ArgumentError)
+ allow(File).to receive(:world_writable?).and_return(true)
+ message = <<EOF
+It is a security vulnerability to allow your home directory to be world-writable, and bundler cannot continue.
+You should probably consider fixing this issue by running `chmod o-w ~` on *nix.
+Please refer to https://ruby-doc.org/stdlib-3.1.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-remove_entry_secure for details.
+EOF
+ expect(bundler_ui).to receive(:warn).with(message)
+ expect { Bundler.send(:rm_rf, bundled_app) }.to raise_error(Bundler::PathError)
+ end
+ end
+ end
+
+ describe "#mkdir_p" do
+ it "creates a folder at the given path" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ allow(Bundler).to receive(:root).and_return(bundled_app)
+
+ Bundler.mkdir_p(bundled_app.join("foo", "bar"))
+ expect(bundled_app.join("foo", "bar")).to exist
+ end
+ end
+
+ describe "#user_home" do
+ context "home directory is set" do
+ it "should return the user home" do
+ path = "/home/oggy"
+ allow(Bundler.rubygems).to receive(:user_home).and_return(path)
+ allow(File).to receive(:directory?).with(path).and_return true
+ allow(File).to receive(:writable?).with(path).and_return true
+ expect(Bundler.user_home).to eq(Pathname(path))
+ end
+
+ context "is not a directory" do
+ it "should issue a warning and return a temporary user home" do
+ path = "/home/oggy"
+ allow(Bundler.rubygems).to receive(:user_home).and_return(path)
+ allow(File).to receive(:directory?).with(path).and_return false
+ allow(Bundler).to receive(:tmp).and_return(Pathname.new("/tmp/trulyrandom"))
+ expect(Bundler.ui).to receive(:warn).with("`/home/oggy` is not a directory.\n")
+ expect(Bundler.ui).to receive(:warn).with("Bundler will use `/tmp/trulyrandom' as your home directory temporarily.\n")
+ expect(Bundler.user_home).to eq(Pathname("/tmp/trulyrandom"))
+ end
+ end
+
+ context "is not writable" do
+ let(:path) { "/home/oggy" }
+ let(:dotbundle) { "/home/oggy/.bundle" }
+
+ it "should issue a warning and return a temporary user home" do
+ allow(Bundler.rubygems).to receive(:user_home).and_return(path)
+ allow(File).to receive(:directory?).with(path).and_return true
+ allow(File).to receive(:writable?).with(path).and_return false
+ allow(File).to receive(:directory?).with(dotbundle).and_return false
+ allow(Bundler).to receive(:tmp).and_return(Pathname.new("/tmp/trulyrandom"))
+ expect(Bundler.ui).to receive(:warn).with("`/home/oggy` is not writable.\n")
+ expect(Bundler.ui).to receive(:warn).with("Bundler will use `/tmp/trulyrandom' as your home directory temporarily.\n")
+ expect(Bundler.user_home).to eq(Pathname("/tmp/trulyrandom"))
+ end
+
+ context ".bundle exists and have correct permissions" do
+ it "should return the user home" do
+ allow(Bundler.rubygems).to receive(:user_home).and_return(path)
+ allow(File).to receive(:directory?).with(path).and_return true
+ allow(File).to receive(:writable?).with(path).and_return false
+ allow(File).to receive(:directory?).with(dotbundle).and_return true
+ allow(File).to receive(:writable?).with(dotbundle).and_return true
+ expect(Bundler.user_home).to eq(Pathname(path))
+ end
+ end
+ end
+ end
+
+ context "home directory is not set" do
+ it "should issue warning and return a temporary user home" do
+ allow(Bundler.rubygems).to receive(:user_home).and_return(nil)
+ allow(Bundler).to receive(:tmp).and_return(Pathname.new("/tmp/trulyrandom"))
+ expect(Bundler.ui).to receive(:warn).with("Your home directory is not set.\n")
+ expect(Bundler.ui).to receive(:warn).with("Bundler will use `/tmp/trulyrandom' as your home directory temporarily.\n")
+ expect(Bundler.user_home).to eq(Pathname("/tmp/trulyrandom"))
+ end
+ end
+ end
+
+ context "user cache dir" do
+ let(:home_path) { Pathname.new(ENV["HOME"]) }
+
+ let(:xdg_data_home) { home_path.join(".local") }
+ let(:xdg_cache_home) { home_path.join(".cache") }
+ let(:xdg_config_home) { home_path.join(".config") }
+
+ let(:bundle_user_home_default) { home_path.join(".bundle") }
+ let(:bundle_user_home_custom) { xdg_data_home.join("bundle") }
+
+ let(:bundle_user_cache_default) { bundle_user_home_default.join("cache") }
+ let(:bundle_user_cache_custom) { xdg_cache_home.join("bundle") }
+
+ let(:bundle_user_config_default) { bundle_user_home_default.join("config") }
+ let(:bundle_user_config_custom) { xdg_config_home.join("bundle") }
+
+ let(:bundle_user_plugin_default) { bundle_user_home_default.join("plugin") }
+ let(:bundle_user_plugin_custom) { xdg_data_home.join("bundle").join("plugin") }
+
+ describe "#user_bundle_path" do
+ before do
+ allow(Bundler.rubygems).to receive(:user_home).and_return(home_path)
+ end
+
+ it "should use the default home path" do
+ expect(Bundler.user_bundle_path).to eq(bundle_user_home_default)
+ expect(Bundler.user_bundle_path("home")).to eq(bundle_user_home_default)
+ expect(Bundler.user_bundle_path("cache")).to eq(bundle_user_cache_default)
+ expect(Bundler.user_cache).to eq(bundle_user_cache_default)
+ expect(Bundler.user_bundle_path("config")).to eq(bundle_user_config_default)
+ expect(Bundler.user_bundle_path("plugin")).to eq(bundle_user_plugin_default)
+ end
+
+ it "should use custom home path as root for other paths" do
+ ENV["BUNDLE_USER_HOME"] = bundle_user_home_custom.to_s
+ allow(Bundler.rubygems).to receive(:user_home).and_raise
+ expect(Bundler.user_bundle_path).to eq(bundle_user_home_custom)
+ expect(Bundler.user_bundle_path("home")).to eq(bundle_user_home_custom)
+ expect(Bundler.user_bundle_path("cache")).to eq(bundle_user_home_custom.join("cache"))
+ expect(Bundler.user_cache).to eq(bundle_user_home_custom.join("cache"))
+ expect(Bundler.user_bundle_path("config")).to eq(bundle_user_home_custom.join("config"))
+ expect(Bundler.user_bundle_path("plugin")).to eq(bundle_user_home_custom.join("plugin"))
+ end
+
+ it "should use all custom paths, except home" do
+ ENV.delete("BUNDLE_USER_HOME")
+ ENV["BUNDLE_USER_CACHE"] = bundle_user_cache_custom.to_s
+ ENV["BUNDLE_USER_CONFIG"] = bundle_user_config_custom.to_s
+ ENV["BUNDLE_USER_PLUGIN"] = bundle_user_plugin_custom.to_s
+ expect(Bundler.user_bundle_path).to eq(bundle_user_home_default)
+ expect(Bundler.user_bundle_path("home")).to eq(bundle_user_home_default)
+ expect(Bundler.user_bundle_path("cache")).to eq(bundle_user_cache_custom)
+ expect(Bundler.user_cache).to eq(bundle_user_cache_custom)
+ expect(Bundler.user_bundle_path("config")).to eq(bundle_user_config_custom)
+ expect(Bundler.user_bundle_path("plugin")).to eq(bundle_user_plugin_custom)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb
new file mode 100644
index 0000000000..b752cd7e70
--- /dev/null
+++ b/spec/bundler/bundler/cli_spec.rb
@@ -0,0 +1,262 @@
+# frozen_string_literal: true
+
+require "bundler/cli"
+
+RSpec.describe "bundle executable" do
+ it "returns non-zero exit status when passed unrecognized options" do
+ bundle "--invalid_argument", :raise_on_error => false
+ expect(exitstatus).to_not be_zero
+ end
+
+ it "returns non-zero exit status when passed unrecognized task" do
+ bundle "unrecognized-task", :raise_on_error => false
+ expect(exitstatus).to_not be_zero
+ end
+
+ it "looks for a binary and executes it if it's named bundler-<task>" do
+ skip "Could not find command testtasks, probably because not a windows friendly executable" if Gem.win_platform?
+
+ File.open(tmp("bundler-testtasks"), "w", 0o755) do |f|
+ ruby = ENV["RUBY"] || "/usr/bin/env ruby"
+ f.puts "#!#{ruby}\nputs 'Hello, world'\n"
+ end
+
+ with_path_added(tmp) do
+ bundle "testtasks"
+ end
+
+ expect(out).to eq("Hello, world")
+ end
+
+ describe "aliases" do
+ it "aliases e to exec" do
+ bundle "e --help"
+
+ expect(out_with_macos_man_workaround).to include("bundle-exec")
+ end
+
+ it "aliases ex to exec" do
+ bundle "ex --help"
+
+ expect(out_with_macos_man_workaround).to include("bundle-exec")
+ end
+
+ it "aliases exe to exec" do
+ bundle "exe --help"
+
+ expect(out_with_macos_man_workaround).to include("bundle-exec")
+ end
+
+ it "aliases c to check" do
+ bundle "c --help"
+
+ expect(out_with_macos_man_workaround).to include("bundle-check")
+ end
+
+ it "aliases i to install" do
+ bundle "i --help"
+
+ expect(out_with_macos_man_workaround).to include("bundle-install")
+ end
+
+ it "aliases ls to list" do
+ bundle "ls --help"
+
+ expect(out_with_macos_man_workaround).to include("bundle-list")
+ end
+
+ it "aliases package to cache" do
+ bundle "package --help"
+
+ expect(out_with_macos_man_workaround).to include("bundle-cache")
+ end
+
+ it "aliases pack to cache" do
+ bundle "pack --help"
+
+ expect(out_with_macos_man_workaround).to include("bundle-cache")
+ end
+
+ private
+
+ # Some `man` (e.g., on macOS) always highlights the output even to
+ # non-tty.
+ def out_with_macos_man_workaround
+ out.gsub(/.[\b]/, "")
+ end
+ end
+
+ context "with no arguments" do
+ it "prints a concise help message", :bundler => "3" do
+ bundle ""
+ expect(err).to be_empty
+ expect(out).to include("Bundler version #{Bundler::VERSION}").
+ and include("\n\nBundler commands:\n\n").
+ and include("\n\n Primary commands:\n").
+ and include("\n\n Utilities:\n").
+ and include("\n\nOptions:\n")
+ end
+ end
+
+ context "when ENV['BUNDLE_GEMFILE'] is set to an empty string" do
+ it "ignores it" do
+ gemfile bundled_app_gemfile, <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ bundle :install, :env => { "BUNDLE_GEMFILE" => "" }
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with --verbose" do
+ it "prints the running command" do
+ gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ bundle "info bundler", :verbose => true
+ expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION}")
+ end
+
+ it "doesn't print defaults" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", :verbose => true
+ expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}")
+ end
+
+ it "doesn't print defaults" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", :verbose => true
+ expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}")
+ end
+ end
+
+ describe "bundle outdated" do
+ let(:run_command) do
+ bundle "install"
+
+ bundle "outdated #{flags}", :raise_on_error => false
+ end
+
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", '0.9.1'
+ G
+ end
+
+ context "with --groups flag" do
+ let(:flags) { "--groups" }
+
+ it "prints a message when there are outdated gems" do
+ run_command
+
+ expect(out).to include("Gem Current Latest Requested Groups")
+ expect(out).to include("rack 0.9.1 1.0.0 = 0.9.1 default")
+ end
+ end
+
+ context "with --parseable" do
+ let(:flags) { "--parseable" }
+
+ it "prints a message when there are outdated gems" do
+ run_command
+
+ expect(out).to include("rack (newest 1.0.0, installed 0.9.1, requested = 0.9.1)")
+ end
+ end
+
+ context "with --groups and --parseable" do
+ let(:flags) { "--groups --parseable" }
+
+ it "prints a simplified message when there are outdated gems" do
+ run_command
+
+ expect(out).to include("rack (newest 1.0.0, installed 0.9.1, requested = 0.9.1)")
+ end
+ end
+ end
+
+ describe "printing the outdated warning" do
+ shared_examples_for "no warning" do
+ it "prints no warning" do
+ bundle "fail", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false
+ expect(last_command.stdboth).to eq("Could not find command \"fail\".")
+ end
+ end
+
+ let(:bundler_version) { "2.0" }
+ let(:latest_version) { nil }
+ before do
+ bundle "config set --global disable_version_check false"
+
+ pristine_system_gems "bundler-#{bundler_version}"
+ if latest_version
+ info_path = home(".bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/bundler")
+ info_path.parent.mkpath
+ info_path.open("w") {|f| f.write "#{latest_version}\n" }
+ end
+ end
+
+ context "when there is no latest version" do
+ include_examples "no warning"
+ end
+
+ context "when the latest version is equal to the current version" do
+ let(:latest_version) { bundler_version }
+ include_examples "no warning"
+ end
+
+ context "when the latest version is less than the current version" do
+ let(:latest_version) { "0.9" }
+ include_examples "no warning"
+ end
+
+ context "when the latest version is greater than the current version" do
+ let(:latest_version) { "222.0" }
+ it "prints the version warning" do
+ bundle "fail", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false
+ expect(err).to start_with(<<-EOS.strip)
+The latest bundler is #{latest_version}, but you are currently running #{bundler_version}.
+To update to the most recent version, run `bundle update --bundler`
+ EOS
+ end
+
+ context "and disable_version_check is set" do
+ before { bundle "config set disable_version_check true", :env => { "BUNDLER_VERSION" => bundler_version } }
+ include_examples "no warning"
+ end
+
+ context "running a parseable command" do
+ it "prints no warning" do
+ bundle "config get --parseable foo", :env => { "BUNDLER_VERSION" => bundler_version }
+ expect(last_command.stdboth).to eq ""
+
+ bundle "platform --ruby", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false
+ expect(last_command.stdboth).to eq "Could not locate Gemfile"
+ end
+ end
+
+ context "and is a pre-release" do
+ let(:latest_version) { "222.0.0.pre.4" }
+ it "prints the version warning" do
+ bundle "fail", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false
+ expect(err).to start_with(<<-EOS.strip)
+The latest bundler is #{latest_version}, but you are currently running #{bundler_version}.
+To update to the most recent version, run `bundle update --bundler`
+ EOS
+ end
+ end
+ end
+ end
+end
+
+RSpec.describe "bundler executable" do
+ it "shows the bundler version just as the `bundle` executable does", :bundler => "< 3" do
+ bundler "--version"
+ expect(out).to eq("Bundler version #{Bundler::VERSION}")
+ end
+
+ it "shows the bundler version just as the `bundle` executable does", :bundler => "3" do
+ bundler "--version"
+ expect(out).to eq(Bundler::VERSION)
+ end
+end
diff --git a/spec/bundler/bundler/compact_index_client/updater_spec.rb b/spec/bundler/bundler/compact_index_client/updater_spec.rb
new file mode 100644
index 0000000000..fe417e3920
--- /dev/null
+++ b/spec/bundler/bundler/compact_index_client/updater_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require "net/http"
+require "bundler/compact_index_client"
+require "bundler/compact_index_client/updater"
+require "tmpdir"
+
+RSpec.describe Bundler::CompactIndexClient::Updater do
+ let(:fetcher) { double(:fetcher) }
+ let(:local_path) { Pathname.new Dir.mktmpdir("localpath") }
+ let(:remote_path) { double(:remote_path) }
+
+ let!(:updater) { described_class.new(fetcher) }
+
+ context "when the ETag header is missing" do
+ # Regression test for https://github.com/rubygems/bundler/issues/5463
+ let(:response) { double(:response, :body => "abc123") }
+
+ it "treats the response as an update" do
+ expect(response).to receive(:[]).with("ETag") { nil }
+ expect(fetcher).to receive(:call) { response }
+
+ updater.update(local_path, remote_path)
+ end
+ end
+
+ context "when the download is corrupt" do
+ let(:response) { double(:response, :body => "") }
+
+ it "raises HTTPError" do
+ expect(fetcher).to receive(:call).and_raise(Zlib::GzipFile::Error)
+
+ expect do
+ updater.update(local_path, remote_path)
+ end.to raise_error(Bundler::HTTPError)
+ end
+ end
+
+ context "when receiving non UTF-8 data and default internal encoding set to ASCII" do
+ let(:response) { double(:response, :body => "\x8B".b) }
+
+ it "works just fine" do
+ old_verbose = $VERBOSE
+ previous_internal_encoding = Encoding.default_internal
+
+ begin
+ $VERBOSE = false
+ Encoding.default_internal = "ASCII"
+ expect(response).to receive(:[]).with("ETag") { nil }
+ expect(fetcher).to receive(:call) { response }
+
+ updater.update(local_path, remote_path)
+ ensure
+ Encoding.default_internal = previous_internal_encoding
+ $VERBOSE = old_verbose
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb
new file mode 100644
index 0000000000..59b958ae42
--- /dev/null
+++ b/spec/bundler/bundler/definition_spec.rb
@@ -0,0 +1,292 @@
+# frozen_string_literal: true
+
+require "bundler/definition"
+
+RSpec.describe Bundler::Definition do
+ describe "#lock" do
+ before do
+ allow(Bundler).to receive(:settings) { Bundler::Settings.new(".") }
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") }
+ allow(Bundler).to receive(:ui) { double("UI", :info => "", :debug => "") }
+ end
+ context "when it's not possible to write to the file" do
+ subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) }
+
+ it "raises an PermissionError with explanation" do
+ allow(File).to receive(:open).and_call_original
+ expect(File).to receive(:open).with("Gemfile.lock", "wb").
+ and_raise(Errno::EACCES)
+ expect { subject.lock("Gemfile.lock") }.
+ to raise_error(Bundler::PermissionError, /Gemfile\.lock/)
+ end
+ end
+ context "when a temporary resource access issue occurs" do
+ subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) }
+
+ it "raises a TemporaryResourceError with explanation" do
+ allow(File).to receive(:open).and_call_original
+ expect(File).to receive(:open).with("Gemfile.lock", "wb").
+ and_raise(Errno::EAGAIN)
+ expect { subject.lock("Gemfile.lock") }.
+ to raise_error(Bundler::TemporaryResourceError, /temporarily unavailable/)
+ end
+ end
+ context "when Bundler::Definition.no_lock is set to true" do
+ subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) }
+ before { Bundler::Definition.no_lock = true }
+ after { Bundler::Definition.no_lock = false }
+
+ it "does not create a lock file" do
+ subject.lock("Gemfile.lock")
+ expect(File.file?("Gemfile.lock")).to eq false
+ end
+ end
+ end
+
+ describe "detects changes" do
+ it "for a path gem with changes" do
+ build_lib "foo", "1.0", :path => lib_path("foo")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "1.0"
+ end
+
+ bundle :install, :env => { "DEBUG" => "1" }
+
+ expect(out).to match(/re-resolving dependencies/)
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack (= 1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "with an explicit update" do
+ build_repo4 do
+ build_gem("ffi", "1.9.23") {|s| s.platform = "java" }
+ build_gem("ffi", "1.9.23")
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "ffi"
+ G
+
+ bundle "lock --add-platform java"
+
+ bundle "update ffi", :env => { "DEBUG" => "1" }
+
+ expect(out).to match(/because bundler is unlocking gems: \(ffi\)/)
+ end
+
+ it "for a path gem with deps and no changes" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "1.0"
+ s.add_development_dependency "net-ssh", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ bundle :check, :env => { "DEBUG" => "1" }
+
+ expect(out).to match(/using resolution from the lockfile/)
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack (= 1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "for a locked gem for another platform" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "only_java", platform: :jruby
+ G
+
+ bundle "lock --add-platform java"
+ bundle :check, :env => { "DEBUG" => "1" }
+
+ expect(out).to match(/using resolution from the lockfile/)
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ only_java (1.1-java)
+
+ PLATFORMS
+ #{lockfile_platforms("java")}
+
+ DEPENDENCIES
+ only_java
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "for a rubygems gem" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo"
+ G
+
+ bundle :check, :env => { "DEBUG" => "1" }
+
+ expect(out).to match(/using resolution from the lockfile/)
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+ end
+
+ describe "initialize" do
+ context "gem version promoter" do
+ context "eager unlock" do
+ let(:source_list) do
+ Bundler::SourceList.new.tap do |source_list|
+ source_list.add_global_rubygems_remote(file_uri_for(gem_repo4))
+ end
+ end
+
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'isolated_owner'
+
+ gem 'shared_owner_a'
+ gem 'shared_owner_b'
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}
+ specs:
+ isolated_dep (2.0.1)
+ isolated_owner (1.0.1)
+ isolated_dep (~> 2.0)
+ shared_dep (5.0.1)
+ shared_owner_a (3.0.1)
+ shared_dep (~> 5.0)
+ shared_owner_b (4.0.1)
+ shared_dep (~> 5.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ shared_owner_a
+ shared_owner_b
+ isolated_owner
+
+ BUNDLED WITH
+ 1.13.0
+ L
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ end
+
+ it "should not eagerly unlock shared dependency with bundle install conservative updating behavior" do
+ updated_deps_in_gemfile = [Bundler::Dependency.new("isolated_owner", ">= 0"),
+ Bundler::Dependency.new("shared_owner_a", "3.0.2"),
+ Bundler::Dependency.new("shared_owner_b", ">= 0")]
+ unlock_hash_for_bundle_install = {}
+ definition = Bundler::Definition.new(
+ bundled_app_lock,
+ updated_deps_in_gemfile,
+ source_list,
+ unlock_hash_for_bundle_install
+ )
+ locked = definition.send(:converge_locked_specs).map(&:name)
+ expect(locked).to include "shared_dep"
+ end
+
+ it "should not eagerly unlock shared dependency with bundle update conservative updating behavior" do
+ updated_deps_in_gemfile = [Bundler::Dependency.new("isolated_owner", ">= 0"),
+ Bundler::Dependency.new("shared_owner_a", ">= 0"),
+ Bundler::Dependency.new("shared_owner_b", ">= 0")]
+ definition = Bundler::Definition.new(
+ bundled_app_lock,
+ updated_deps_in_gemfile,
+ source_list,
+ :gems => ["shared_owner_a"], :conservative => true
+ )
+ locked = definition.send(:converge_locked_specs).map(&:name)
+ expect(locked).to eq %w[isolated_dep isolated_owner shared_dep shared_owner_b]
+ expect(locked.include?("shared_dep")).to be_truthy
+ end
+ end
+ end
+ end
+
+ def mock_source_list
+ Class.new do
+ def all_sources
+ []
+ end
+
+ def path_sources
+ []
+ end
+
+ def rubygems_remotes
+ []
+ end
+
+ def replace_sources!(arg)
+ nil
+ end
+ end.new
+ end
+end
diff --git a/spec/bundler/bundler/dependency_spec.rb b/spec/bundler/bundler/dependency_spec.rb
new file mode 100644
index 0000000000..6e346c36c1
--- /dev/null
+++ b/spec/bundler/bundler/dependency_spec.rb
@@ -0,0 +1,157 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Dependency do
+ let(:options) do
+ {}
+ end
+ let(:dependency) do
+ described_class.new(
+ "test_gem",
+ "1.0.0",
+ options
+ )
+ end
+
+ describe "to_lock" do
+ it "returns formatted string" do
+ expect(dependency.to_lock).to eq(" test_gem (= 1.0.0)")
+ end
+
+ it "matches format of Gem::Dependency#to_lock" do
+ gem_dependency = Gem::Dependency.new("test_gem", "1.0.0")
+ expect(dependency.to_lock).to eq(gem_dependency.to_lock)
+ end
+
+ context "when source is passed" do
+ let(:options) do
+ {
+ "source" => Bundler::Source::Git.new({}),
+ }
+ end
+
+ it "returns formatted string with exclamation mark" do
+ expect(dependency.to_lock).to eq(" test_gem (= 1.0.0)!")
+ end
+ end
+ end
+
+ describe "PLATFORM_MAP" do
+ subject { described_class::PLATFORM_MAP }
+
+ # rubocop:disable Naming/VariableNumber
+ let(:platforms) do
+ { :ruby => Gem::Platform::RUBY,
+ :ruby_18 => Gem::Platform::RUBY,
+ :ruby_19 => Gem::Platform::RUBY,
+ :ruby_20 => Gem::Platform::RUBY,
+ :ruby_21 => Gem::Platform::RUBY,
+ :ruby_22 => Gem::Platform::RUBY,
+ :ruby_23 => Gem::Platform::RUBY,
+ :ruby_24 => Gem::Platform::RUBY,
+ :ruby_25 => Gem::Platform::RUBY,
+ :ruby_26 => Gem::Platform::RUBY,
+ :ruby_27 => Gem::Platform::RUBY,
+ :ruby_30 => Gem::Platform::RUBY,
+ :ruby_31 => Gem::Platform::RUBY,
+ :ruby_32 => Gem::Platform::RUBY,
+ :ruby_33 => Gem::Platform::RUBY,
+ :mri => Gem::Platform::RUBY,
+ :mri_18 => Gem::Platform::RUBY,
+ :mri_19 => Gem::Platform::RUBY,
+ :mri_20 => Gem::Platform::RUBY,
+ :mri_21 => Gem::Platform::RUBY,
+ :mri_22 => Gem::Platform::RUBY,
+ :mri_23 => Gem::Platform::RUBY,
+ :mri_24 => Gem::Platform::RUBY,
+ :mri_25 => Gem::Platform::RUBY,
+ :mri_26 => Gem::Platform::RUBY,
+ :mri_27 => Gem::Platform::RUBY,
+ :mri_30 => Gem::Platform::RUBY,
+ :mri_31 => Gem::Platform::RUBY,
+ :mri_32 => Gem::Platform::RUBY,
+ :mri_33 => Gem::Platform::RUBY,
+ :rbx => Gem::Platform::RUBY,
+ :truffleruby => Gem::Platform::RUBY,
+ :jruby => Gem::Platform::JAVA,
+ :jruby_18 => Gem::Platform::JAVA,
+ :jruby_19 => Gem::Platform::JAVA,
+ :windows => Gem::Platform::WINDOWS,
+ :windows_18 => Gem::Platform::WINDOWS,
+ :windows_19 => Gem::Platform::WINDOWS,
+ :windows_20 => Gem::Platform::WINDOWS,
+ :windows_21 => Gem::Platform::WINDOWS,
+ :windows_22 => Gem::Platform::WINDOWS,
+ :windows_23 => Gem::Platform::WINDOWS,
+ :windows_24 => Gem::Platform::WINDOWS,
+ :windows_25 => Gem::Platform::WINDOWS,
+ :windows_26 => Gem::Platform::WINDOWS,
+ :windows_27 => Gem::Platform::WINDOWS,
+ :windows_30 => Gem::Platform::WINDOWS,
+ :windows_31 => Gem::Platform::WINDOWS,
+ :windows_32 => Gem::Platform::WINDOWS,
+ :windows_33 => Gem::Platform::WINDOWS,
+ :mswin => Gem::Platform::MSWIN,
+ :mswin_18 => Gem::Platform::MSWIN,
+ :mswin_19 => Gem::Platform::MSWIN,
+ :mswin_20 => Gem::Platform::MSWIN,
+ :mswin_21 => Gem::Platform::MSWIN,
+ :mswin_22 => Gem::Platform::MSWIN,
+ :mswin_23 => Gem::Platform::MSWIN,
+ :mswin_24 => Gem::Platform::MSWIN,
+ :mswin_25 => Gem::Platform::MSWIN,
+ :mswin_26 => Gem::Platform::MSWIN,
+ :mswin_27 => Gem::Platform::MSWIN,
+ :mswin_30 => Gem::Platform::MSWIN,
+ :mswin_31 => Gem::Platform::MSWIN,
+ :mswin_32 => Gem::Platform::MSWIN,
+ :mswin_33 => Gem::Platform::MSWIN,
+ :mswin64 => Gem::Platform::MSWIN64,
+ :mswin64_19 => Gem::Platform::MSWIN64,
+ :mswin64_20 => Gem::Platform::MSWIN64,
+ :mswin64_21 => Gem::Platform::MSWIN64,
+ :mswin64_22 => Gem::Platform::MSWIN64,
+ :mswin64_23 => Gem::Platform::MSWIN64,
+ :mswin64_24 => Gem::Platform::MSWIN64,
+ :mswin64_25 => Gem::Platform::MSWIN64,
+ :mswin64_26 => Gem::Platform::MSWIN64,
+ :mswin64_27 => Gem::Platform::MSWIN64,
+ :mswin64_30 => Gem::Platform::MSWIN64,
+ :mswin64_31 => Gem::Platform::MSWIN64,
+ :mswin64_32 => Gem::Platform::MSWIN64,
+ :mswin64_33 => Gem::Platform::MSWIN64,
+ :mingw => Gem::Platform::MINGW,
+ :mingw_18 => Gem::Platform::MINGW,
+ :mingw_19 => Gem::Platform::MINGW,
+ :mingw_20 => Gem::Platform::MINGW,
+ :mingw_21 => Gem::Platform::MINGW,
+ :mingw_22 => Gem::Platform::MINGW,
+ :mingw_23 => Gem::Platform::MINGW,
+ :mingw_24 => Gem::Platform::MINGW,
+ :mingw_25 => Gem::Platform::MINGW,
+ :mingw_26 => Gem::Platform::MINGW,
+ :mingw_27 => Gem::Platform::MINGW,
+ :mingw_30 => Gem::Platform::MINGW,
+ :mingw_31 => Gem::Platform::MINGW,
+ :mingw_32 => Gem::Platform::MINGW,
+ :mingw_33 => Gem::Platform::MINGW,
+ :x64_mingw => Gem::Platform::X64_MINGW,
+ :x64_mingw_20 => Gem::Platform::X64_MINGW,
+ :x64_mingw_21 => Gem::Platform::X64_MINGW,
+ :x64_mingw_22 => Gem::Platform::X64_MINGW,
+ :x64_mingw_23 => Gem::Platform::X64_MINGW,
+ :x64_mingw_24 => Gem::Platform::X64_MINGW,
+ :x64_mingw_25 => Gem::Platform::X64_MINGW,
+ :x64_mingw_26 => Gem::Platform::X64_MINGW,
+ :x64_mingw_27 => Gem::Platform::X64_MINGW,
+ :x64_mingw_30 => Gem::Platform::X64_MINGW,
+ :x64_mingw_31 => Gem::Platform::X64_MINGW,
+ :x64_mingw_32 => Gem::Platform::X64_MINGW,
+ :x64_mingw_33 => Gem::Platform::X64_MINGW }
+ end
+ # rubocop:enable Naming/VariableNumber
+
+ it "includes all platforms" do
+ expect(subject).to eq(platforms)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/digest_spec.rb b/spec/bundler/bundler/digest_spec.rb
new file mode 100644
index 0000000000..841cc0259e
--- /dev/null
+++ b/spec/bundler/bundler/digest_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require "digest"
+require "bundler/digest"
+
+RSpec.describe Bundler::Digest do
+ context "SHA1" do
+ subject { Bundler::Digest }
+ let(:stdlib) { ::Digest::SHA1 }
+
+ it "is compatible with stdlib" do
+ random_strings = ["foo", "skfjsdlkfjsdf", "3924m", "ldskfj"]
+
+ # https://datatracker.ietf.org/doc/html/rfc3174#section-7.3
+ rfc3174_test_cases = ["abc", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "a", "01234567" * 8]
+
+ (random_strings + rfc3174_test_cases).each do |payload|
+ sha1 = subject.sha1(payload)
+ sha1_stdlib = stdlib.hexdigest(payload)
+ expect(sha1).to be == sha1_stdlib, "#{payload}'s sha1 digest (#{sha1}) did not match stlib's result (#{sha1_stdlib})"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb
new file mode 100644
index 0000000000..8b5bf930f2
--- /dev/null
+++ b/spec/bundler/bundler/dsl_spec.rb
@@ -0,0 +1,308 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Dsl do
+ before do
+ @rubygems = double("rubygems")
+ allow(Bundler::Source::Rubygems).to receive(:new) { @rubygems }
+ end
+
+ describe "#git_source" do
+ it "registers custom hosts" do
+ subject.git_source(:example) {|repo_name| "git@git.example.com:#{repo_name}.git" }
+ subject.git_source(:foobar) {|repo_name| "git@foobar.com:#{repo_name}.git" }
+ subject.gem("dobry-pies", :example => "strzalek/dobry-pies")
+ example_uri = "git@git.example.com:strzalek/dobry-pies.git"
+ expect(subject.dependencies.first.source.uri).to eq(example_uri)
+ end
+
+ it "raises exception on invalid hostname" do
+ expect do
+ subject.git_source(:group) {|repo_name| "git@git.example.com:#{repo_name}.git" }
+ end.to raise_error(Bundler::InvalidOption)
+ end
+
+ it "expects block passed" do
+ expect { subject.git_source(:example) }.to raise_error(Bundler::InvalidOption)
+ end
+
+ it "converts :github PR to URI using https" do
+ subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5")
+ github_uri = "https://github.com/indirect/sparks.git"
+ expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ expect(subject.dependencies.first.source.ref).to eq("refs/pull/5/head")
+ end
+
+ it "rejects :github PR URI with a branch, ref or tag" do
+ expect do
+ subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5", :branch => "foo")
+ end.to raise_error(
+ Bundler::GemfileError,
+ %(The :branch option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`),
+ )
+
+ expect do
+ subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5", :ref => "foo")
+ end.to raise_error(
+ Bundler::GemfileError,
+ %(The :ref option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`),
+ )
+
+ expect do
+ subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5", :tag => "foo")
+ end.to raise_error(
+ Bundler::GemfileError,
+ %(The :tag option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`),
+ )
+ end
+
+ it "rejects :github with :git" do
+ expect do
+ subject.gem("sparks", :github => "indirect/sparks", :git => "https://github.com/indirect/sparks.git")
+ end.to raise_error(
+ Bundler::GemfileError,
+ %(The :git option can't be used with `github: "indirect/sparks"`),
+ )
+ end
+
+ context "default hosts", :bundler => "< 3" do
+ it "converts :github to URI using https" do
+ subject.gem("sparks", :github => "indirect/sparks")
+ github_uri = "https://github.com/indirect/sparks.git"
+ expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ end
+
+ it "converts :github shortcut to URI using https" do
+ subject.gem("sparks", :github => "rails")
+ github_uri = "https://github.com/rails/rails.git"
+ expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ end
+
+ it "converts numeric :gist to :git" do
+ subject.gem("not-really-a-gem", :gist => 2_859_988)
+ github_uri = "https://gist.github.com/2859988.git"
+ expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ end
+
+ it "converts :gist to :git" do
+ subject.gem("not-really-a-gem", :gist => "2859988")
+ github_uri = "https://gist.github.com/2859988.git"
+ expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ end
+
+ it "converts :bitbucket to :git" do
+ subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails")
+ bitbucket_uri = "https://mcorp@bitbucket.org/mcorp/flatlab-rails.git"
+ expect(subject.dependencies.first.source.uri).to eq(bitbucket_uri)
+ end
+
+ it "converts 'mcorp' to 'mcorp/mcorp'" do
+ subject.gem("not-really-a-gem", :bitbucket => "mcorp")
+ bitbucket_uri = "https://mcorp@bitbucket.org/mcorp/mcorp.git"
+ expect(subject.dependencies.first.source.uri).to eq(bitbucket_uri)
+ end
+ end
+
+ context "default git sources" do
+ it "has bitbucket, gist, and github" do
+ expect(subject.instance_variable_get(:@git_sources).keys.sort).to eq(%w[bitbucket gist github])
+ end
+ end
+ end
+
+ describe "#method_missing" do
+ it "raises an error for unknown DSL methods" do
+ expect(Bundler).to receive(:read_file).with(source_root.join("Gemfile").to_s).
+ and_return("unknown")
+
+ error_msg = "There was an error parsing `Gemfile`: Undefined local variable or method `unknown' for Gemfile. Bundler cannot continue."
+ expect { subject.eval_gemfile("Gemfile") }.
+ to raise_error(Bundler::GemfileError, Regexp.new(error_msg))
+ end
+ end
+
+ describe "#eval_gemfile" do
+ it "handles syntax errors with a useful message" do
+ expect(Bundler).to receive(:read_file).with(source_root.join("Gemfile").to_s).and_return("}")
+ expect { subject.eval_gemfile("Gemfile") }.
+ to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`: (syntax error, unexpected tSTRING_DEND|(compile error - )?syntax error, unexpected '\}'). Bundler cannot continue./)
+ end
+
+ it "distinguishes syntax errors from evaluation errors" do
+ expect(Bundler).to receive(:read_file).with(source_root.join("Gemfile").to_s).and_return(
+ "ruby '2.1.5', :engine => 'ruby', :engine_version => '1.2.4'"
+ )
+ expect { subject.eval_gemfile("Gemfile") }.
+ to raise_error(Bundler::GemfileError, /There was an error evaluating `Gemfile`: ruby_version must match the :engine_version for MRI/)
+ end
+ end
+
+ describe "#gem" do
+ # rubocop:disable Naming/VariableNumber
+ [:ruby, :ruby_18, :ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24, :ruby_25, :ruby_26, :ruby_27,
+ :ruby_30, :ruby_31, :ruby_32, :ruby_33, :mri, :mri_18, :mri_19, :mri_20, :mri_21, :mri_22, :mri_23, :mri_24,
+ :mri_25, :mri_26, :mri_27, :mri_30, :mri_31, :mri_32, :mri_33, :jruby, :rbx, :truffleruby].each do |platform|
+ it "allows #{platform} as a valid platform" do
+ subject.gem("foo", :platform => platform)
+ end
+ end
+ # rubocop:enable Naming/VariableNumber
+
+ it "allows platforms matching the running Ruby version" do
+ platform = "ruby_#{RbConfig::CONFIG["MAJOR"]}#{RbConfig::CONFIG["MINOR"]}"
+ subject.gem("foo", :platform => platform)
+ end
+
+ it "rejects invalid platforms" do
+ expect { subject.gem("foo", :platform => :bogus) }.
+ to raise_error(Bundler::GemfileError, /is not a valid platform/)
+ end
+
+ it "rejects empty gem name" do
+ expect { subject.gem("") }.
+ to raise_error(Bundler::GemfileError, /an empty gem name is not valid/)
+ end
+
+ it "rejects with a leading space in the name" do
+ expect { subject.gem(" foo") }.
+ to raise_error(Bundler::GemfileError, /' foo' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a trailing space in the name" do
+ expect { subject.gem("foo ") }.
+ to raise_error(Bundler::GemfileError, /'foo ' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a space in the gem name" do
+ expect { subject.gem("fo o") }.
+ to raise_error(Bundler::GemfileError, /'fo o' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a tab in the gem name" do
+ expect { subject.gem("fo\to") }.
+ to raise_error(Bundler::GemfileError, /'fo\to' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a newline in the gem name" do
+ expect { subject.gem("fo\no") }.
+ to raise_error(Bundler::GemfileError, /'fo\no' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a carriage return in the gem name" do
+ expect { subject.gem("fo\ro") }.
+ to raise_error(Bundler::GemfileError, /'fo\ro' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a form feed in the gem name" do
+ expect { subject.gem("fo\fo") }.
+ to raise_error(Bundler::GemfileError, /'fo\fo' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects symbols as gem name" do
+ expect { subject.gem(:foo) }.
+ to raise_error(Bundler::GemfileError, /You need to specify gem names as Strings. Use 'gem "foo"' instead/)
+ end
+
+ it "rejects branch option on non-git gems" do
+ expect { subject.gem("foo", :branch => "test") }.
+ to raise_error(Bundler::GemfileError, /The `branch` option for `gem 'foo'` is not allowed. Only gems with a git source can specify a branch/)
+ end
+
+ it "allows specifying a branch on git gems" do
+ subject.gem("foo", :branch => "test", :git => "http://mytestrepo")
+ dep = subject.dependencies.last
+ expect(dep.name).to eq "foo"
+ end
+
+ it "allows specifying a branch on git gems with a git_source" do
+ subject.git_source(:test_source) {|n| "https://github.com/#{n}" }
+ subject.gem("foo", :branch => "test", :test_source => "bundler/bundler")
+ dep = subject.dependencies.last
+ expect(dep.name).to eq "foo"
+ end
+ end
+
+ context "can bundle groups of gems with" do
+ # git "https://github.com/rails/rails.git" do
+ # gem "railties"
+ # gem "action_pack"
+ # gem "active_model"
+ # end
+ describe "#git" do
+ it "from a single repo" do
+ rails_gems = %w[railties action_pack active_model]
+ subject.git "https://github.com/rails/rails.git" do
+ rails_gems.each {|rails_gem| subject.send :gem, rails_gem }
+ end
+ expect(subject.dependencies.map(&:name)).to match_array rails_gems
+ end
+ end
+
+ # github 'spree' do
+ # gem 'spree_core'
+ # gem 'spree_api'
+ # gem 'spree_backend'
+ # end
+ describe "#github" do
+ it "from github" do
+ spree_gems = %w[spree_core spree_api spree_backend]
+ subject.github "spree" do
+ spree_gems.each {|spree_gem| subject.send :gem, spree_gem }
+ end
+
+ subject.dependencies.each do |d|
+ expect(d.source.uri).to eq("https://github.com/spree/spree.git")
+ end
+ end
+ end
+ end
+
+ describe "syntax errors" do
+ it "will raise a Bundler::GemfileError" do
+ gemfile "gem 'foo', :path => /unquoted/string/syntax/error"
+ expect { Bundler::Dsl.evaluate(bundled_app_gemfile, nil, true) }.
+ to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`:( compile error -)? unknown regexp options - trg.+ Bundler cannot continue./)
+ end
+ end
+
+ describe "Runtime errors" do
+ it "will raise a Bundler::GemfileError" do
+ gemfile "raise RuntimeError, 'foo'"
+ expect { Bundler::Dsl.evaluate(bundled_app_gemfile, nil, true) }.
+ to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`: foo. Bundler cannot continue./i)
+ end
+ end
+
+ describe "#with_source" do
+ context "if there was a rubygem source already defined" do
+ it "restores it after it's done" do
+ other_source = double("other-source")
+ allow(Bundler::Source::Rubygems).to receive(:new).and_return(other_source)
+ allow(Bundler).to receive(:default_gemfile).and_return(Pathname.new("./Gemfile"))
+
+ subject.source("https://other-source.org") do
+ subject.gem("dobry-pies", :path => "foo")
+ subject.gem("foo")
+ end
+
+ expect(subject.dependencies.last.source).to eq(other_source)
+ end
+ end
+ end
+
+ describe "#check_primary_source_safety" do
+ context "when a global source is not defined implicitly" do
+ it "will raise a major deprecation warning" do
+ not_a_global_source = double("not-a-global-source", :no_remotes? => true)
+ allow(Bundler::Source::Rubygems).to receive(:new).and_return(not_a_global_source)
+
+ warning = "This Gemfile does not include an explicit global source. " \
+ "Not using an explicit global source may result in a different lockfile being generated depending on " \
+ "the gems you have installed locally before bundler is run. " \
+ "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\"."
+ expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, warning)
+
+ subject.check_primary_source_safety
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/endpoint_specification_spec.rb b/spec/bundler/bundler/endpoint_specification_spec.rb
new file mode 100644
index 0000000000..7dd6925007
--- /dev/null
+++ b/spec/bundler/bundler/endpoint_specification_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::EndpointSpecification do
+ let(:name) { "foo" }
+ let(:version) { "1.0.0" }
+ let(:platform) { Gem::Platform::RUBY }
+ let(:dependencies) { [] }
+ let(:spec_fetcher) { double(:spec_fetcher) }
+ let(:metadata) { nil }
+
+ subject(:spec) { described_class.new(name, version, platform, spec_fetcher, dependencies, metadata) }
+
+ describe "#build_dependency" do
+ let(:name) { "foo" }
+ let(:requirement1) { "~> 1.1" }
+ let(:requirement2) { ">= 1.1.7" }
+
+ it "should return a Gem::Dependency" do
+ expect(subject.send(:build_dependency, name, [requirement1, requirement2])).
+ to eq(Gem::Dependency.new(name, requirement1, requirement2))
+ end
+
+ context "when an ArgumentError occurs" do
+ before do
+ allow(Gem::Dependency).to receive(:new).with(name, [requirement1, requirement2]) {
+ raise ArgumentError.new("Some error occurred")
+ }
+ end
+
+ it "should raise the original error" do
+ expect { subject.send(:build_dependency, name, [requirement1, requirement2]) }.to raise_error(
+ ArgumentError, "Some error occurred"
+ )
+ end
+ end
+ end
+
+ describe "#parse_metadata" do
+ context "when the metadata has malformed requirements" do
+ let(:metadata) { { "rubygems" => ">\n" } }
+ it "raises a helpful error message" do
+ expect { subject }.to raise_error(
+ Bundler::GemspecError,
+ a_string_including("There was an error parsing the metadata for the gem foo (1.0.0)").
+ and(a_string_including('The metadata was {"rubygems"=>">\n"}'))
+ )
+ end
+ end
+ end
+
+ describe "#required_ruby_version" do
+ context "required_ruby_version is already set on endpoint specification" do
+ existing_value = "already set value"
+ let(:required_ruby_version) { existing_value }
+
+ it "should return the current value when already set on endpoint specification" do
+ expect(spec.required_ruby_version). eql?(existing_value)
+ end
+ end
+
+ it "should return the remote spec value when not set on endpoint specification and remote spec has one" do
+ remote_value = "remote_value"
+ remote_spec = double(:remote_spec, :required_ruby_version => remote_value, :required_rubygems_version => nil)
+ allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec)
+
+ expect(spec.required_ruby_version). eql?(remote_value)
+ end
+
+ it "should use the default Gem Requirement value when not set on endpoint specification and not set on remote spec" do
+ remote_spec = double(:remote_spec, :required_ruby_version => nil, :required_rubygems_version => nil)
+ allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec)
+ expect(spec.required_ruby_version). eql?(Gem::Requirement.default)
+ end
+ end
+
+ it "supports equality comparison" do
+ remote_spec = double(:remote_spec, :required_ruby_version => nil, :required_rubygems_version => nil)
+ allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec)
+ other_spec = described_class.new("bar", version, platform, spec_fetcher, dependencies, metadata)
+ expect(spec).to eql(spec)
+ expect(spec).to_not eql(other_spec)
+ end
+end
diff --git a/spec/bundler/bundler/env_spec.rb b/spec/bundler/bundler/env_spec.rb
new file mode 100644
index 0000000000..a00489d0e5
--- /dev/null
+++ b/spec/bundler/bundler/env_spec.rb
@@ -0,0 +1,236 @@
+# frozen_string_literal: true
+
+require "bundler/settings"
+require "openssl"
+
+RSpec.describe Bundler::Env do
+ let(:git_proxy_stub) { Bundler::Source::Git::GitProxy.new(nil, nil) }
+
+ describe "#report" do
+ it "prints the environment" do
+ out = described_class.report
+
+ expect(out).to include("Environment")
+ expect(out).to include(Bundler::VERSION)
+ expect(out).to include(Gem::VERSION)
+ expect(out).to include(described_class.send(:ruby_version))
+ expect(out).to include(described_class.send(:git_version))
+ expect(out).to include(OpenSSL::OPENSSL_VERSION)
+ end
+
+ describe "rubygems paths" do
+ it "prints gem home" do
+ with_clear_paths("GEM_HOME", "/a/b/c") do
+ out = described_class.report
+ expect(out).to include("Gem Home /a/b/c")
+ end
+ end
+
+ it "prints gem path" do
+ with_clear_paths("GEM_PATH", "/a/b/c#{File::PATH_SEPARATOR}d/e/f") do
+ out = described_class.report
+ expect(out).to include("Gem Path /a/b/c#{File::PATH_SEPARATOR}d/e/f")
+ end
+ end
+
+ it "prints user home" do
+ with_clear_paths("HOME", "/a/b/c") do
+ out = described_class.report
+ expect(out).to include("User Home /a/b/c")
+ end
+ end
+
+ it "prints user path" do
+ with_clear_paths("HOME", "/a/b/c") do
+ allow(File).to receive(:exist?)
+ allow(File).to receive(:exist?).with("/a/b/c/.gem").and_return(true)
+ out = described_class.report
+ expect(out).to include("User Path /a/b/c/.gem")
+ end
+ end
+
+ it "prints bin dir" do
+ with_clear_paths("GEM_HOME", "/a/b/c") do
+ out = described_class.report
+ expect(out).to include("Bin Dir /a/b/c/bin")
+ end
+ end
+
+ private
+
+ def with_clear_paths(env_var, env_value)
+ old_env_var = ENV[env_var]
+ ENV[env_var] = env_value
+ Gem.clear_paths
+ yield
+ ensure
+ ENV[env_var] = old_env_var
+ end
+ end
+
+ context "when there is a Gemfile and a lockfile and print_gemfile is true" do
+ before do
+ gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem 'rack', '1.0.0'"
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (1.0.0)
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 1.10.0
+ L
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ end
+
+ let(:output) { described_class.report(:print_gemfile => true) }
+
+ it "prints the Gemfile" do
+ expect(output).to include("Gemfile")
+ expect(output).to include("'rack', '1.0.0'")
+ end
+
+ it "prints the lockfile" do
+ expect(output).to include("Gemfile.lock")
+ expect(output).to include("rack (1.0.0)")
+ end
+ end
+
+ context "when there no Gemfile and print_gemfile is true" do
+ let(:output) { described_class.report(:print_gemfile => true) }
+
+ it "prints the environment" do
+ expect(output).to start_with("## Environment")
+ end
+ end
+
+ context "when there's bundler config with credentials" do
+ before do
+ bundle "config set https://localgemserver.test/ user:pass"
+ end
+
+ let(:output) { described_class.report(:print_gemfile => true) }
+
+ it "prints the config with redacted values" do
+ expect(output).to include("https://localgemserver.test")
+ expect(output).to include("user:[REDACTED]")
+ expect(output).to_not include("user:pass")
+ end
+ end
+
+ context "when there's bundler config with OAuth token credentials" do
+ before do
+ bundle "config set https://localgemserver.test/ api_token:x-oauth-basic"
+ end
+
+ let(:output) { described_class.report(:print_gemfile => true) }
+
+ it "prints the config with redacted values" do
+ expect(output).to include("https://localgemserver.test")
+ expect(output).to include("[REDACTED]:x-oauth-basic")
+ expect(output).to_not include("api_token:x-oauth-basic")
+ end
+ end
+
+ context "when Gemfile contains a gemspec and print_gemspecs is true" do
+ let(:gemspec) do
+ strip_whitespace(<<-GEMSPEC)
+ Gem::Specification.new do |gem|
+ gem.name = "foo"
+ gem.author = "Fumofu"
+ end
+ GEMSPEC
+ end
+
+ before do
+ gemfile("source \"#{file_uri_for(gem_repo1)}\"; gemspec")
+
+ File.open(bundled_app.join("foo.gemspec"), "wb") do |f|
+ f.write(gemspec)
+ end
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ end
+
+ it "prints the gemspec" do
+ output = described_class.report(:print_gemspecs => true)
+
+ expect(output).to include("foo.gemspec")
+ expect(output).to include(gemspec)
+ end
+ end
+
+ context "when eval_gemfile is used" do
+ it "prints all gemfiles" do
+ create_file bundled_app("other/Gemfile-other"), "gem 'rack'"
+ create_file bundled_app("other/Gemfile"), "eval_gemfile 'Gemfile-other'"
+ create_file bundled_app("Gemfile-alt"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ eval_gemfile "other/Gemfile"
+ G
+ gemfile "eval_gemfile #{bundled_app("Gemfile-alt").to_s.dump}"
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ allow(Bundler::SharedHelpers).to receive(:pwd).and_return(bundled_app)
+
+ output = described_class.report(:print_gemspecs => true)
+ expect(output).to include(strip_whitespace(<<-ENV))
+ ## Gemfile
+
+ ### Gemfile
+
+ ```ruby
+ eval_gemfile #{bundled_app("Gemfile-alt").to_s.dump}
+ ```
+
+ ### Gemfile-alt
+
+ ```ruby
+ source "#{file_uri_for(gem_repo1)}"
+ eval_gemfile "other/Gemfile"
+ ```
+
+ ### other/Gemfile
+
+ ```ruby
+ eval_gemfile 'Gemfile-other'
+ ```
+
+ ### other/Gemfile-other
+
+ ```ruby
+ gem 'rack'
+ ```
+
+ ### Gemfile.lock
+
+ ```
+ <No #{bundled_app_lock} found>
+ ```
+ ENV
+ end
+ end
+
+ context "when the git version is OS specific" do
+ it "includes OS specific information with the version number" do
+ expect(git_proxy_stub).to receive(:git_local).with("--version").
+ and_return("git version 1.2.3 (Apple Git-BS)")
+ expect(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub)
+
+ expect(described_class.report).to include("Git 1.2.3 (Apple Git-BS)")
+ end
+ end
+ end
+
+ describe ".version_of" do
+ let(:parsed_version) { described_class.send(:version_of, "ruby") }
+
+ it "strips version of new line characters" do
+ expect(parsed_version).to_not end_with("\n")
+ end
+ end
+end
diff --git a/spec/bundler/bundler/environment_preserver_spec.rb b/spec/bundler/bundler/environment_preserver_spec.rb
new file mode 100644
index 0000000000..530ca6f835
--- /dev/null
+++ b/spec/bundler/bundler/environment_preserver_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::EnvironmentPreserver do
+ let(:preserver) { described_class.new(env, ["foo"]) }
+
+ describe "#backup" do
+ let(:env) { { "foo" => "my-foo", "bar" => "my-bar" } }
+ subject { preserver.backup }
+
+ it "should create backup entries" do
+ expect(subject["BUNDLER_ORIG_foo"]).to eq("my-foo")
+ end
+
+ it "should keep the original entry" do
+ expect(subject["foo"]).to eq("my-foo")
+ end
+
+ it "should not create backup entries for unspecified keys" do
+ expect(subject.key?("BUNDLER_ORIG_bar")).to eq(false)
+ end
+
+ it "should not affect the original env" do
+ subject
+ expect(env.keys.sort).to eq(%w[bar foo])
+ end
+
+ context "when a key is empty" do
+ let(:env) { { "foo" => "" } }
+
+ it "should not create backup entries" do
+ expect(subject).not_to have_key "BUNDLER_ORIG_foo"
+ end
+ end
+
+ context "when an original key is set" do
+ let(:env) { { "foo" => "my-foo", "BUNDLER_ORIG_foo" => "orig-foo" } }
+
+ it "should keep the original value in the BUNDLER_ORIG_ variable" do
+ expect(subject["BUNDLER_ORIG_foo"]).to eq("orig-foo")
+ end
+
+ it "should keep the variable" do
+ expect(subject["foo"]).to eq("my-foo")
+ end
+ end
+ end
+
+ describe "#restore" do
+ subject { preserver.restore }
+
+ context "when an original key is set" do
+ let(:env) { { "foo" => "my-foo", "BUNDLER_ORIG_foo" => "orig-foo" } }
+
+ it "should restore the original value" do
+ expect(subject["foo"]).to eq("orig-foo")
+ end
+
+ it "should delete the backup value" do
+ expect(subject.key?("BUNDLER_ORIG_foo")).to eq(false)
+ end
+ end
+
+ context "when no original key is set" do
+ let(:env) { { "foo" => "my-foo" } }
+
+ it "should keep the current value" do
+ expect(subject["foo"]).to eq("my-foo")
+ end
+ end
+
+ context "when the original key is empty" do
+ let(:env) { { "foo" => "my-foo", "BUNDLER_ORIG_foo" => "" } }
+
+ it "should keep the current value" do
+ expect(subject["foo"]).to eq("my-foo")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/base_spec.rb b/spec/bundler/bundler/fetcher/base_spec.rb
new file mode 100644
index 0000000000..02506591f3
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/base_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Fetcher::Base do
+ let(:downloader) { double(:downloader) }
+ let(:remote) { double(:remote) }
+ let(:display_uri) { "http://sample_uri.com" }
+
+ class TestClass < described_class; end
+
+ subject { TestClass.new(downloader, remote, display_uri) }
+
+ describe "#initialize" do
+ context "with the abstract Base class" do
+ it "should raise an error" do
+ expect { described_class.new(downloader, remote, display_uri) }.to raise_error(RuntimeError, "Abstract class")
+ end
+ end
+
+ context "with a class that inherits the Base class" do
+ it "should set the passed attributes" do
+ expect(subject.downloader).to eq(downloader)
+ expect(subject.remote).to eq(remote)
+ expect(subject.display_uri).to eq("http://sample_uri.com")
+ end
+ end
+ end
+
+ describe "#remote_uri" do
+ let(:remote_uri_obj) { double(:remote_uri_obj) }
+
+ before { allow(remote).to receive(:uri).and_return(remote_uri_obj) }
+
+ it "should return the remote's uri" do
+ expect(subject.remote_uri).to eq(remote_uri_obj)
+ end
+ end
+
+ describe "#fetch_uri" do
+ let(:remote_uri_obj) { Bundler::URI("http://rubygems.org") }
+
+ before { allow(subject).to receive(:remote_uri).and_return(remote_uri_obj) }
+
+ context "when the remote uri's host is rubygems.org" do
+ it "should create a copy of the remote uri with index.rubygems.org as the host" do
+ fetched_uri = subject.fetch_uri
+ expect(fetched_uri.host).to eq("index.rubygems.org")
+ expect(fetched_uri).to_not be(remote_uri_obj)
+ end
+ end
+
+ context "when the remote uri's host is not rubygems.org" do
+ let(:remote_uri_obj) { Bundler::URI("http://otherhost.org") }
+
+ it "should return the remote uri" do
+ expect(subject.fetch_uri).to eq(Bundler::URI("http://otherhost.org"))
+ end
+ end
+
+ it "memoizes the fetched uri" do
+ expect(remote_uri_obj).to receive(:host).once
+ 2.times { subject.fetch_uri }
+ end
+ end
+
+ describe "#available?" do
+ it "should return whether the api is available" do
+ expect(subject.available?).to be_truthy
+ end
+ end
+
+ describe "#api_fetcher?" do
+ it "should return false" do
+ expect(subject.api_fetcher?).to be_falsey
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/compact_index_spec.rb b/spec/bundler/bundler/fetcher/compact_index_spec.rb
new file mode 100644
index 0000000000..00eb27edea
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/compact_index_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+# load CompactIndexClient upfront to prevent thread safety issues during parallel specs
+require "bundler/compact_index_client"
+
+RSpec.describe Bundler::Fetcher::CompactIndex do
+ let(:downloader) { double(:downloader) }
+ let(:display_uri) { Bundler::URI("http://sampleuri.com") }
+ let(:remote) { double(:remote, :cache_slug => "lsjdf", :uri => display_uri) }
+ let(:compact_index) { described_class.new(downloader, remote, display_uri) }
+
+ before do
+ allow(compact_index).to receive(:log_specs) {}
+ end
+
+ describe "#specs_for_names" do
+ let(:thread_list) { Thread.list.select {|thread| thread.status == "run" } }
+ let(:thread_inspection) { thread_list.map {|th| " * #{th}:\n #{th.backtrace_locations.join("\n ")}" }.join("\n") }
+
+ it "has only one thread open at the end of the run" do
+ compact_index.specs_for_names(["lskdjf"])
+
+ thread_count = thread_list.count
+ expect(thread_count).to eq(1), "Expected 1 active thread after `#specs_for_names`, but found #{thread_count}. In particular, found:\n#{thread_inspection}"
+ end
+
+ it "calls worker#stop during the run" do
+ expect_any_instance_of(Bundler::Worker).to receive(:stop).at_least(:once).and_call_original
+
+ compact_index.specs_for_names(["lskdjf"])
+ end
+
+ describe "#available?" do
+ before do
+ allow(compact_index).to receive(:compact_index_client).
+ and_return(double(:compact_index_client, :update_and_parse_checksums! => true))
+ end
+
+ it "returns true" do
+ expect(compact_index).to be_available
+ end
+
+ context "when OpenSSL is not available" do
+ before do
+ allow(compact_index).to receive(:require).with("openssl").and_raise(LoadError)
+ end
+
+ it "returns true" do
+ expect(compact_index).to be_available
+ end
+ end
+
+ context "when OpenSSL is FIPS-enabled" do
+ def remove_cached_md5_availability
+ return unless Bundler::SharedHelpers.instance_variable_defined?(:@md5_available)
+ Bundler::SharedHelpers.remove_instance_variable(:@md5_available)
+ end
+
+ before do
+ remove_cached_md5_availability
+ stub_const("OpenSSL::OPENSSL_FIPS", true)
+ end
+
+ after { remove_cached_md5_availability }
+
+ context "when FIPS-mode is active" do
+ before do
+ allow(OpenSSL::Digest).to receive(:digest).with("MD5", "").
+ and_raise(OpenSSL::Digest::DigestError)
+ end
+
+ it "returns false" do
+ expect(compact_index).to_not be_available
+ end
+ end
+
+ it "returns true" do
+ expect(compact_index).to be_available
+ end
+ end
+ end
+
+ context "logging" do
+ before { allow(compact_index).to receive(:log_specs).and_call_original }
+
+ context "with debug on" do
+ before do
+ allow(Bundler).to receive_message_chain(:ui, :debug?).and_return(true)
+ end
+
+ it "should log at info level" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with('Looking up gems ["lskdjf"]')
+ compact_index.specs_for_names(["lskdjf"])
+ end
+ end
+
+ context "with debug off" do
+ before do
+ allow(Bundler).to receive_message_chain(:ui, :debug?).and_return(false)
+ end
+
+ it "should log at info level" do
+ expect(Bundler).to receive_message_chain(:ui, :info).with(".", false)
+ compact_index.specs_for_names(["lskdjf"])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/dependency_spec.rb b/spec/bundler/bundler/fetcher/dependency_spec.rb
new file mode 100644
index 0000000000..20307f9c99
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/dependency_spec.rb
@@ -0,0 +1,283 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Fetcher::Dependency do
+ let(:downloader) { double(:downloader) }
+ let(:remote) { double(:remote, :uri => Bundler::URI("http://localhost:5000")) }
+ let(:display_uri) { "http://sample_uri.com" }
+
+ subject { described_class.new(downloader, remote, display_uri) }
+
+ describe "#available?" do
+ let(:dependency_api_uri) { double(:dependency_api_uri) }
+ let(:fetched_spec) { double(:fetched_spec) }
+
+ before do
+ allow(subject).to receive(:dependency_api_uri).and_return(dependency_api_uri)
+ allow(downloader).to receive(:fetch).with(dependency_api_uri).and_return(fetched_spec)
+ end
+
+ it "should be truthy" do
+ expect(subject.available?).to be_truthy
+ end
+
+ context "when there is no network access" do
+ before do
+ allow(downloader).to receive(:fetch).with(dependency_api_uri) {
+ raise Bundler::Fetcher::NetworkDownError.new("Network Down Message")
+ }
+ end
+
+ it "should raise an HTTPError with the original message" do
+ expect { subject.available? }.to raise_error(Bundler::HTTPError, "Network Down Message")
+ end
+ end
+
+ context "when authentication is required" do
+ let(:remote_uri) { "http://remote_uri.org" }
+
+ before do
+ allow(downloader).to receive(:fetch).with(dependency_api_uri) {
+ raise Bundler::Fetcher::AuthenticationRequiredError.new(remote_uri)
+ }
+ end
+
+ it "should raise the original error" do
+ expect { subject.available? }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError,
+ %r{Authentication is required for http://remote_uri.org})
+ end
+ end
+
+ context "when there is an http error" do
+ before { allow(downloader).to receive(:fetch).with(dependency_api_uri) { raise Bundler::HTTPError.new } }
+
+ it "should be falsey" do
+ expect(subject.available?).to be_falsey
+ end
+ end
+ end
+
+ describe "#api_fetcher?" do
+ it "should return true" do
+ expect(subject.api_fetcher?).to be_truthy
+ end
+ end
+
+ describe "#specs" do
+ let(:gem_names) { %w[foo bar] }
+ let(:full_dependency_list) { ["bar"] }
+ let(:last_spec_list) { [["boulder", gem_version1, "ruby", resque]] }
+ let(:fail_errors) { double(:fail_errors) }
+ let(:bundler_retry) { double(:bundler_retry) }
+ let(:gem_version1) { double(:gem_version1) }
+ let(:resque) { double(:resque) }
+ let(:remote_uri) { "http://remote-uri.org" }
+
+ before do
+ stub_const("Bundler::Fetcher::FAIL_ERRORS", fail_errors)
+ allow(Bundler::Retry).to receive(:new).with("dependency api", fail_errors).and_return(bundler_retry)
+ allow(bundler_retry).to receive(:attempts) {|&block| block.call }
+ allow(subject).to receive(:log_specs) {}
+ allow(subject).to receive(:remote_uri).and_return(remote_uri)
+ allow(Bundler).to receive_message_chain(:ui, :debug?)
+ allow(Bundler).to receive_message_chain(:ui, :info)
+ allow(Bundler).to receive_message_chain(:ui, :debug)
+ end
+
+ context "when there are given gem names that are not in the full dependency list" do
+ let(:spec_list) { [["top", gem_version2, "ruby", faraday]] }
+ let(:deps_list) { [] }
+ let(:dependency_specs) { [spec_list, deps_list] }
+ let(:gem_version2) { double(:gem_version2) }
+ let(:faraday) { double(:faraday) }
+
+ before { allow(subject).to receive(:dependency_specs).with(["foo"]).and_return(dependency_specs) }
+
+ it "should return a hash with the remote_uri and the list of specs" do
+ expect(subject.specs(gem_names, full_dependency_list, last_spec_list)).to eq([
+ ["top", gem_version2, "ruby", faraday],
+ ["boulder", gem_version1, "ruby", resque],
+ ])
+ end
+ end
+
+ context "when all given gem names are in the full dependency list" do
+ let(:gem_names) { ["foo"] }
+ let(:full_dependency_list) { %w[foo bar] }
+ let(:last_spec_list) { ["boulder"] }
+
+ it "should return a hash with the remote_uri and the last spec list" do
+ expect(subject.specs(gem_names, full_dependency_list, last_spec_list)).to eq(["boulder"])
+ end
+ end
+
+ context "logging" do
+ before { allow(subject).to receive(:log_specs).and_call_original }
+
+ context "with debug on" do
+ before do
+ allow(Bundler).to receive_message_chain(:ui, :debug?).and_return(true)
+ allow(subject).to receive(:dependency_specs).with(["foo"]).and_return([[], []])
+ end
+
+ it "should log the query list at debug level" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("Query List: [\"foo\"]")
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("Query List: []")
+ subject.specs(gem_names, full_dependency_list, last_spec_list)
+ end
+ end
+
+ context "with debug off" do
+ before do
+ allow(Bundler).to receive_message_chain(:ui, :debug?).and_return(false)
+ allow(subject).to receive(:dependency_specs).with(["foo"]).and_return([[], []])
+ end
+
+ it "should log at info level" do
+ expect(Bundler).to receive_message_chain(:ui, :info).with(".", false)
+ expect(Bundler).to receive_message_chain(:ui, :info).with(".", false)
+ subject.specs(gem_names, full_dependency_list, last_spec_list)
+ end
+ end
+ end
+
+ shared_examples_for "the error is properly handled" do
+ it "should return nil" do
+ expect(subject.specs(gem_names, full_dependency_list, last_spec_list)).to be_nil
+ end
+
+ context "debug logging is not on" do
+ before { allow(Bundler).to receive_message_chain(:ui, :debug?).and_return(false) }
+
+ it "should log a new line to info" do
+ expect(Bundler).to receive_message_chain(:ui, :info).with("")
+ subject.specs(gem_names, full_dependency_list, last_spec_list)
+ end
+ end
+ end
+
+ shared_examples_for "the error is logged" do
+ it "should log the inability to fetch from API at debug level, and mention retrying" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("could not fetch from the dependency API, trying the full index")
+ subject.specs(gem_names, full_dependency_list, last_spec_list)
+ end
+ end
+
+ context "when an HTTPError occurs" do
+ before { allow(subject).to receive(:dependency_specs) { raise Bundler::HTTPError.new } }
+
+ it_behaves_like "the error is properly handled"
+ it_behaves_like "the error is logged"
+ end
+
+ context "when a GemspecError occurs" do
+ before { allow(subject).to receive(:dependency_specs) { raise Bundler::GemspecError.new } }
+
+ it_behaves_like "the error is properly handled"
+ it_behaves_like "the error is logged"
+ end
+
+ context "when a MarshalError occurs" do
+ before { allow(subject).to receive(:dependency_specs) { raise Bundler::MarshalError.new } }
+
+ it_behaves_like "the error is properly handled"
+ it_behaves_like "the error is logged"
+ end
+ end
+
+ describe "#dependency_specs" do
+ let(:gem_names) { [%w[foo bar], %w[bundler rubocop]] }
+ let(:gem_list) { double(:gem_list) }
+ let(:formatted_specs_and_deps) { double(:formatted_specs_and_deps) }
+
+ before do
+ allow(subject).to receive(:unmarshalled_dep_gems).with(gem_names).and_return(gem_list)
+ allow(subject).to receive(:get_formatted_specs_and_deps).with(gem_list).and_return(formatted_specs_and_deps)
+ end
+
+ it "should log the query list at debug level" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with(
+ "Query Gemcutter Dependency Endpoint API: foo,bar,bundler,rubocop"
+ )
+ subject.dependency_specs(gem_names)
+ end
+
+ it "should return formatted specs and a unique list of dependencies" do
+ expect(subject.dependency_specs(gem_names)).to eq(formatted_specs_and_deps)
+ end
+ end
+
+ describe "#unmarshalled_dep_gems" do
+ let(:gem_names) { [%w[foo bar], %w[bundler rubocop]] }
+ let(:dep_api_uri) { double(:dep_api_uri) }
+ let(:unmarshalled_gems) { double(:unmarshalled_gems) }
+ let(:fetch_response) { double(:fetch_response, :body => double(:body)) }
+ let(:rubygems_limit) { 50 }
+
+ before { allow(subject).to receive(:dependency_api_uri).with(gem_names).and_return(dep_api_uri) }
+
+ it "should fetch dependencies from RubyGems and unmarshal them" do
+ expect(gem_names).to receive(:each_slice).with(rubygems_limit).and_call_original
+ expect(downloader).to receive(:fetch).with(dep_api_uri).and_return(fetch_response)
+ expect(Bundler).to receive(:safe_load_marshal).with(fetch_response.body).and_return([unmarshalled_gems])
+ expect(subject.unmarshalled_dep_gems(gem_names)).to eq([unmarshalled_gems])
+ end
+ end
+
+ describe "#get_formatted_specs_and_deps" do
+ let(:gem_list) do
+ [
+ {
+ :dependencies => {
+ "resque" => "req3,req4",
+ },
+ :name => "typhoeus",
+ :number => "1.0.1",
+ :platform => "ruby",
+ },
+ {
+ :dependencies => {
+ "faraday" => "req1,req2",
+ },
+ :name => "grape",
+ :number => "2.0.2",
+ :platform => "jruby",
+ },
+ ]
+ end
+
+ it "should return formatted specs and a unique list of dependencies" do
+ spec_list, deps_list = subject.get_formatted_specs_and_deps(gem_list)
+ expect(spec_list).to eq([["typhoeus", "1.0.1", "ruby", [["resque", ["req3,req4"]]]],
+ ["grape", "2.0.2", "jruby", [["faraday", ["req1,req2"]]]]])
+ expect(deps_list).to eq(%w[resque faraday])
+ end
+ end
+
+ describe "#dependency_api_uri" do
+ let(:uri) { Bundler::URI("http://gem-api.com") }
+
+ context "with gem names" do
+ let(:gem_names) { %w[foo bar bundler rubocop] }
+
+ before { allow(subject).to receive(:fetch_uri).and_return(uri) }
+
+ it "should return an api calling uri with the gems in the query" do
+ expect(subject.dependency_api_uri(gem_names).to_s).to eq(
+ "http://gem-api.com/api/v1/dependencies?gems=bar%2Cbundler%2Cfoo%2Crubocop"
+ )
+ end
+ end
+
+ context "with no gem names" do
+ let(:gem_names) { [] }
+
+ before { allow(subject).to receive(:fetch_uri).and_return(uri) }
+
+ it "should return an api calling uri with no query" do
+ expect(subject.dependency_api_uri(gem_names).to_s).to eq(
+ "http://gem-api.com/api/v1/dependencies"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb
new file mode 100644
index 0000000000..22aa3a8b0c
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/downloader_spec.rb
@@ -0,0 +1,255 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Fetcher::Downloader do
+ let(:connection) { double(:connection) }
+ let(:redirect_limit) { 5 }
+ let(:uri) { Bundler::URI("http://www.uri-to-fetch.com/api/v2/endpoint") }
+ let(:options) { double(:options) }
+
+ subject { described_class.new(connection, redirect_limit) }
+
+ describe "fetch" do
+ let(:counter) { 0 }
+ let(:httpv) { "1.1" }
+ let(:http_response) { double(:response) }
+
+ before do
+ allow(subject).to receive(:request).with(uri, options).and_return(http_response)
+ allow(http_response).to receive(:body).and_return("Body with info")
+ end
+
+ context "when the # requests counter is greater than the redirect limit" do
+ let(:counter) { redirect_limit + 1 }
+
+ it "should raise a Bundler::HTTPError specifying too many redirects" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::HTTPError, "Too many redirects")
+ end
+ end
+
+ context "logging" do
+ let(:http_response) { Net::HTTPSuccess.new("1.1", 200, "Success") }
+
+ it "should log the HTTP response code and message to debug" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP 200 Success #{uri}")
+ subject.fetch(uri, options, counter)
+ end
+ end
+
+ context "when the request response is a Net::HTTPRedirection" do
+ let(:http_response) { Net::HTTPRedirection.new(httpv, 308, "Moved") }
+
+ before { http_response["location"] = "http://www.redirect-uri.com/api/v2/endpoint" }
+
+ it "should try to fetch the redirect uri and iterate the # requests counter" do
+ expect(subject).to receive(:fetch).with(Bundler::URI("http://www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original
+ expect(subject).to receive(:fetch).with(Bundler::URI("http://www.redirect-uri.com/api/v2/endpoint"), options, 1)
+ subject.fetch(uri, options, counter)
+ end
+
+ context "when the redirect uri and original uri are the same" do
+ let(:uri) { Bundler::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint") }
+
+ before { http_response["location"] = "ssh://www.uri-to-fetch.com/api/v1/endpoint" }
+
+ it "should set the same user and password for the redirect uri" do
+ expect(subject).to receive(:fetch).with(Bundler::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original
+ expect(subject).to receive(:fetch).with(Bundler::URI("ssh://username:password@www.uri-to-fetch.com/api/v1/endpoint"), options, 1)
+ subject.fetch(uri, options, counter)
+ end
+ end
+ end
+
+ context "when the request response is a Net::HTTPSuccess" do
+ let(:http_response) { Net::HTTPSuccess.new("1.1", 200, "Success") }
+
+ it "should return the response body" do
+ expect(subject.fetch(uri, options, counter)).to eq(http_response)
+ end
+ end
+
+ context "when the request response is a Net::HTTPRequestEntityTooLarge" do
+ let(:http_response) { Net::HTTPRequestEntityTooLarge.new("1.1", 413, "Too Big") }
+
+ it "should raise a Bundler::Fetcher::FallbackError with the response body" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::FallbackError, "Body with info")
+ end
+ end
+
+ context "when the request response is a Net::HTTPUnauthorized" do
+ let(:http_response) { Net::HTTPUnauthorized.new("1.1", 401, "Unauthorized") }
+
+ it "should raise a Bundler::Fetcher::AuthenticationRequiredError with the uri host" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError,
+ /Authentication is required for www.uri-to-fetch.com/)
+ end
+
+ it "should raise a Bundler::Fetcher::AuthenticationRequiredError with advices" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError,
+ /`bundle config set --global www\.uri-to-fetch\.com username:password`.*`BUNDLE_WWW__URI___TO___FETCH__COM`/m)
+ end
+
+ context "when the there are credentials provided in the request" do
+ let(:uri) { Bundler::URI("http://user:password@www.uri-to-fetch.com") }
+
+ it "should raise a Bundler::Fetcher::BadAuthenticationError that doesn't contain the password" do
+ expect { subject.fetch(uri, options, counter) }.
+ to raise_error(Bundler::Fetcher::BadAuthenticationError, /Bad username or password for www.uri-to-fetch.com/)
+ end
+ end
+ end
+
+ context "when the request response is a Net::HTTPForbidden" do
+ let(:http_response) { Net::HTTPForbidden.new("1.1", 403, "Forbidden") }
+ let(:uri) { Bundler::URI("http://user:password@www.uri-to-fetch.com") }
+
+ it "should raise a Bundler::Fetcher::AuthenticationForbiddenError with the uri host" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::AuthenticationForbiddenError,
+ /Access token could not be authenticated for www.uri-to-fetch.com/)
+ end
+ end
+
+ context "when the request response is a Net::HTTPNotFound" do
+ let(:http_response) { Net::HTTPNotFound.new("1.1", 404, "Not Found") }
+
+ it "should raise a Bundler::Fetcher::FallbackError with Net::HTTPNotFound" do
+ expect { subject.fetch(uri, options, counter) }.
+ to raise_error(Bundler::Fetcher::FallbackError, "Net::HTTPNotFound: http://www.uri-to-fetch.com/api/v2/endpoint")
+ end
+
+ context "when the there are credentials provided in the request" do
+ let(:uri) { Bundler::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") }
+
+ it "should raise a Bundler::Fetcher::FallbackError that doesn't contain the password" do
+ expect { subject.fetch(uri, options, counter) }.
+ to raise_error(Bundler::Fetcher::FallbackError, "Net::HTTPNotFound: http://username@www.uri-to-fetch.com/api/v2/endpoint")
+ end
+ end
+ end
+
+ context "when the request response is some other type" do
+ let(:http_response) { Net::HTTPBadGateway.new("1.1", 500, "Fatal Error") }
+
+ it "should raise a Bundler::HTTPError with the response class and body" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::HTTPError, "Net::HTTPBadGateway: Body with info")
+ end
+ end
+ end
+
+ describe "request" do
+ let(:net_http_get) { double(:net_http_get) }
+ let(:response) { double(:response) }
+
+ before do
+ allow(Net::HTTP::Get).to receive(:new).with("/api/v2/endpoint", options).and_return(net_http_get)
+ allow(connection).to receive(:request).with(uri, net_http_get).and_return(response)
+ end
+
+ it "should log the HTTP GET request to debug" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP GET http://www.uri-to-fetch.com/api/v2/endpoint")
+ subject.request(uri, options)
+ end
+
+ context "when there is a user provided in the request" do
+ context "and there is also a password provided" do
+ context "that contains cgi escaped characters" do
+ let(:uri) { Bundler::URI("http://username:password%24@www.uri-to-fetch.com/api/v2/endpoint") }
+
+ it "should request basic authentication with the username and password" do
+ expect(net_http_get).to receive(:basic_auth).with("username", "password$")
+ subject.request(uri, options)
+ end
+ end
+
+ context "that is all unescaped characters" do
+ let(:uri) { Bundler::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") }
+ it "should request basic authentication with the username and proper cgi compliant password" do
+ expect(net_http_get).to receive(:basic_auth).with("username", "password")
+ subject.request(uri, options)
+ end
+ end
+ end
+
+ context "and there is no password provided" do
+ let(:uri) { Bundler::URI("http://username@www.uri-to-fetch.com/api/v2/endpoint") }
+
+ it "should request basic authentication with just the user" do
+ expect(net_http_get).to receive(:basic_auth).with("username", nil)
+ subject.request(uri, options)
+ end
+ end
+
+ context "that contains cgi escaped characters" do
+ let(:uri) { Bundler::URI("http://username%24@www.uri-to-fetch.com/api/v2/endpoint") }
+
+ it "should request basic authentication with the proper cgi compliant password user" do
+ expect(net_http_get).to receive(:basic_auth).with("username$", nil)
+ subject.request(uri, options)
+ end
+ end
+ end
+
+ context "when the request response causes a OpenSSL::SSL::SSLError" do
+ before { allow(connection).to receive(:request).with(uri, net_http_get) { raise OpenSSL::SSL::SSLError.new } }
+
+ it "should raise a LoadError about openssl" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::CertificateFailureError,
+ %r{Could not verify the SSL certificate for http://www.uri-to-fetch.com/api/v2/endpoint})
+ end
+ end
+
+ context "when the request response causes an error included in HTTP_ERRORS" do
+ let(:message) { nil }
+ let(:error) { RuntimeError.new(message) }
+
+ before do
+ stub_const("Bundler::Fetcher::HTTP_ERRORS", [RuntimeError])
+ allow(connection).to receive(:request).with(uri, net_http_get) { raise error }
+ end
+
+ it "should trace log the error" do
+ allow(Bundler).to receive_message_chain(:ui, :debug)
+ expect(Bundler).to receive_message_chain(:ui, :trace).with(error)
+ expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError)
+ end
+
+ context "when error message is about the host being down" do
+ let(:message) { "host down: http://www.uri-to-fetch.com" }
+
+ it "should raise a Bundler::Fetcher::NetworkDownError" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError,
+ /Could not reach host www.uri-to-fetch.com/)
+ end
+ end
+
+ context "when error message is not about host down" do
+ let(:message) { "other error about network" }
+
+ it "should raise a Bundler::HTTPError" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError,
+ "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (other error about network)")
+ end
+
+ context "when the there are credentials provided in the request" do
+ let(:uri) { Bundler::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") }
+ before do
+ allow(net_http_get).to receive(:basic_auth).with("username", "password")
+ end
+
+ it "should raise a Bundler::HTTPError that doesn't contain the password" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError,
+ "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (other error about network)")
+ end
+ end
+ end
+
+ context "when error message is about no route to host" do
+ let(:message) { "Failed to open TCP connection to www.uri-to-fetch.com:443 " }
+
+ it "should raise a Bundler::Fetcher::HTTPError" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError,
+ "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (#{message})")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/index_spec.rb b/spec/bundler/bundler/fetcher/index_spec.rb
new file mode 100644
index 0000000000..971b64ce8f
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/index_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require "rubygems/remote_fetcher"
+
+RSpec.describe Bundler::Fetcher::Index do
+ let(:downloader) { nil }
+ let(:remote) { nil }
+ let(:display_uri) { "http://sample_uri.com" }
+ let(:rubygems) { double(:rubygems) }
+ let(:gem_names) { %w[foo bar] }
+
+ subject { described_class.new(downloader, remote, display_uri) }
+
+ before { allow(Bundler).to receive(:rubygems).and_return(rubygems) }
+
+ it "fetches and returns the list of remote specs" do
+ expect(rubygems).to receive(:fetch_all_remote_specs) { nil }
+ subject.specs(gem_names)
+ end
+
+ context "error handling" do
+ let(:remote_uri) { Bundler::URI("http://remote-uri.org") }
+ before do
+ allow(rubygems).to receive(:fetch_all_remote_specs) { raise Gem::RemoteFetcher::FetchError.new(error_message, display_uri) }
+ allow(subject).to receive(:remote_uri).and_return(remote_uri)
+ end
+
+ context "when certificate verify failed" do
+ let(:error_message) { "certificate verify failed" }
+
+ it "should raise a Bundler::Fetcher::CertificateFailureError" do
+ expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::CertificateFailureError,
+ %r{Could not verify the SSL certificate for http://sample_uri.com})
+ end
+ end
+
+ context "when a 401 response occurs" do
+ let(:error_message) { "401" }
+
+ before do
+ allow(remote_uri).to receive(:userinfo).and_return(userinfo)
+ end
+
+ context "and there was userinfo" do
+ let(:userinfo) { double(:userinfo) }
+
+ it "should raise a Bundler::Fetcher::BadAuthenticationError" do
+ expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::BadAuthenticationError,
+ %r{Bad username or password for http://remote-uri.org})
+ end
+ end
+
+ context "and there was no userinfo" do
+ let(:userinfo) { nil }
+
+ it "should raise a Bundler::Fetcher::AuthenticationRequiredError" do
+ expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError,
+ %r{Authentication is required for http://remote-uri.org})
+ end
+ end
+ end
+
+ context "when a 403 response occurs" do
+ let(:error_message) { "403" }
+
+ it "should raise a Bundler::Fetcher::AuthenticationForbiddenError" do
+ expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::AuthenticationForbiddenError,
+ %r{Access token could not be authenticated for http://remote-uri.org})
+ end
+ end
+
+ context "any other message is returned" do
+ let(:error_message) { "You get an error, you get an error!" }
+
+ before { allow(Bundler).to receive(:ui).and_return(double(:trace => nil)) }
+
+ it "should raise a Bundler::HTTPError" do
+ expect { subject.specs(gem_names) }.to raise_error(Bundler::HTTPError, "Could not fetch specs from http://sample_uri.com due to underlying error <You get an error, you get an error! (http://sample_uri.com)>")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher_spec.rb b/spec/bundler/bundler/fetcher_spec.rb
new file mode 100644
index 0000000000..27a63c476d
--- /dev/null
+++ b/spec/bundler/bundler/fetcher_spec.rb
@@ -0,0 +1,192 @@
+# frozen_string_literal: true
+
+require "bundler/fetcher"
+
+RSpec.describe Bundler::Fetcher do
+ let(:uri) { Bundler::URI("https://example.com") }
+ let(:remote) { double("remote", :uri => uri, :original_uri => nil) }
+
+ subject(:fetcher) { Bundler::Fetcher.new(remote) }
+
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new("root") }
+ end
+
+ describe "#connection" do
+ context "when Gem.configuration doesn't specify http_proxy" do
+ it "specify no http_proxy" do
+ expect(fetcher.http_proxy).to be_nil
+ end
+ it "consider environment vars when determine proxy" do
+ with_env_vars("HTTP_PROXY" => "http://proxy-example.com") do
+ expect(fetcher.http_proxy).to match("http://proxy-example.com")
+ end
+ end
+ end
+ context "when Gem.configuration specifies http_proxy " do
+ let(:proxy) { "http://proxy-example2.com" }
+ before do
+ allow(Gem.configuration).to receive(:[]).with(:http_proxy).and_return(proxy)
+ end
+ it "consider Gem.configuration when determine proxy" do
+ expect(fetcher.http_proxy).to match("http://proxy-example2.com")
+ end
+ it "consider Gem.configuration when determine proxy" do
+ with_env_vars("HTTP_PROXY" => "http://proxy-example.com") do
+ expect(fetcher.http_proxy).to match("http://proxy-example2.com")
+ end
+ end
+ context "when the proxy is :no_proxy" do
+ let(:proxy) { :no_proxy }
+ it "does not set a proxy" do
+ expect(fetcher.http_proxy).to be_nil
+ end
+ end
+ end
+
+ context "when a rubygems source mirror is set" do
+ let(:orig_uri) { Bundler::URI("http://zombo.com") }
+ let(:remote_with_mirror) do
+ double("remote", :uri => uri, :original_uri => orig_uri, :anonymized_uri => uri)
+ end
+
+ let(:fetcher) { Bundler::Fetcher.new(remote_with_mirror) }
+
+ it "sets the 'X-Gemfile-Source' header containing the original source" do
+ expect(
+ fetcher.send(:connection).override_headers["X-Gemfile-Source"]
+ ).to eq("http://zombo.com")
+ end
+ end
+
+ context "when there is no rubygems source mirror set" do
+ let(:remote_no_mirror) do
+ double("remote", :uri => uri, :original_uri => nil, :anonymized_uri => uri)
+ end
+
+ let(:fetcher) { Bundler::Fetcher.new(remote_no_mirror) }
+
+ it "does not set the 'X-Gemfile-Source' header" do
+ expect(fetcher.send(:connection).override_headers["X-Gemfile-Source"]).to be_nil
+ end
+ end
+
+ context "when there are proxy environment variable(s) set" do
+ it "consider http_proxy" do
+ with_env_vars("HTTP_PROXY" => "http://proxy-example3.com") do
+ expect(fetcher.http_proxy).to match("http://proxy-example3.com")
+ end
+ end
+ it "consider no_proxy" do
+ with_env_vars("HTTP_PROXY" => "http://proxy-example4.com", "NO_PROXY" => ".example.com,.example.net") do
+ expect(
+ fetcher.send(:connection).no_proxy
+ ).to eq([".example.com", ".example.net"])
+ end
+ end
+ end
+
+ context "when no ssl configuration is set" do
+ it "no cert" do
+ expect(fetcher.send(:connection).cert).to be_nil
+ expect(fetcher.send(:connection).key).to be_nil
+ end
+ end
+
+ context "when bunder ssl ssl configuration is set" do
+ before do
+ cert = File.join(Spec::Path.tmpdir, "cert")
+ File.open(cert, "w") {|f| f.write "PEM" }
+ allow(Bundler.settings).to receive(:[]).and_return(nil)
+ allow(Bundler.settings).to receive(:[]).with(:ssl_client_cert).and_return(cert)
+ expect(OpenSSL::X509::Certificate).to receive(:new).with("PEM").and_return("cert")
+ expect(OpenSSL::PKey::RSA).to receive(:new).with("PEM").and_return("key")
+ end
+ after do
+ FileUtils.rm File.join(Spec::Path.tmpdir, "cert")
+ end
+ it "use bundler configuration" do
+ expect(fetcher.send(:connection).cert).to eq("cert")
+ expect(fetcher.send(:connection).key).to eq("key")
+ end
+ end
+
+ context "when gem ssl configuration is set" do
+ before do
+ allow(Gem.configuration).to receive_messages(
+ :http_proxy => nil,
+ :ssl_client_cert => "cert",
+ :ssl_ca_cert => "ca"
+ )
+ expect(File).to receive(:read).and_return("")
+ expect(OpenSSL::X509::Certificate).to receive(:new).and_return("cert")
+ expect(OpenSSL::PKey::RSA).to receive(:new).and_return("key")
+ store = double("ca store")
+ expect(store).to receive(:add_file)
+ expect(OpenSSL::X509::Store).to receive(:new).and_return(store)
+ end
+ it "use gem configuration" do
+ expect(fetcher.send(:connection).cert).to eq("cert")
+ expect(fetcher.send(:connection).key).to eq("key")
+ end
+ end
+ end
+
+ describe "#user_agent" do
+ it "builds user_agent with current ruby version and Bundler settings" do
+ allow(Bundler.settings).to receive(:all).and_return(%w[foo bar])
+ expect(fetcher.user_agent).to match(%r{bundler/(\d.)})
+ expect(fetcher.user_agent).to match(%r{rubygems/(\d.)})
+ expect(fetcher.user_agent).to match(%r{ruby/(\d.)})
+ expect(fetcher.user_agent).to match(%r{options/foo,bar})
+ end
+
+ describe "include CI information" do
+ it "from one CI" do
+ with_env_vars("JENKINS_URL" => "foo") do
+ ci_part = fetcher.user_agent.split(" ").find {|x| x.start_with?("ci/") }
+ expect(ci_part).to match("jenkins")
+ end
+ end
+
+ it "from many CI" do
+ with_env_vars("TRAVIS" => "foo", "GITLAB_CI" => "gitlab", "CI_NAME" => "my_ci") do
+ ci_part = fetcher.user_agent.split(" ").find {|x| x.start_with?("ci/") }
+ expect(ci_part).to match("travis")
+ expect(ci_part).to match("gitlab")
+ expect(ci_part).to match("my_ci")
+ end
+ end
+ end
+ end
+
+ describe "#fetch_spec" do
+ let(:name) { "name" }
+ let(:version) { "1.3.17" }
+ let(:platform) { "platform" }
+ let(:downloader) { double("downloader") }
+ let(:body) { double(Net::HTTP::Get, :body => downloaded_data) }
+
+ context "when attempting to load a Gem::Specification" do
+ let(:spec) { Gem::Specification.new(name, version) }
+ let(:downloaded_data) { Zlib::Deflate.deflate(Marshal.dump(spec)) }
+
+ it "returns the spec" do
+ expect(Bundler::Fetcher::Downloader).to receive(:new).and_return(downloader)
+ expect(downloader).to receive(:fetch).once.and_return(body)
+ result = fetcher.fetch_spec([name, version, platform])
+ expect(result).to eq(spec)
+ end
+ end
+
+ context "when attempting to load an unexpected class" do
+ let(:downloaded_data) { Zlib::Deflate.deflate(Marshal.dump(3)) }
+
+ it "raises a HTTPError error" do
+ expect(Bundler::Fetcher::Downloader).to receive(:new).and_return(downloader)
+ expect(downloader).to receive(:fetch).once.and_return(body)
+ expect { fetcher.fetch_spec([name, version, platform]) }.to raise_error(Bundler::HTTPError, /Gemspec .* contained invalid data/i)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/friendly_errors_spec.rb b/spec/bundler/bundler/friendly_errors_spec.rb
new file mode 100644
index 0000000000..37afe488f3
--- /dev/null
+++ b/spec/bundler/bundler/friendly_errors_spec.rb
@@ -0,0 +1,234 @@
+# frozen_string_literal: true
+
+require "bundler"
+require "bundler/friendly_errors"
+require "cgi"
+
+RSpec.describe Bundler, "friendly errors" do
+ context "with invalid YAML in .gemrc" do
+ before do
+ File.open(home(".gemrc"), "w") do |f|
+ f.write "invalid: yaml: hah"
+ end
+ end
+
+ after do
+ FileUtils.rm(home(".gemrc"))
+ end
+
+ it "reports a relevant friendly error message" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle :install, :env => { "DEBUG" => "true" }
+
+ expect(err).to include("Failed to load #{home(".gemrc")}")
+ end
+ end
+
+ it "calls log_error in case of exception" do
+ exception = Exception.new
+ expect(Bundler::FriendlyErrors).to receive(:exit_status).with(exception).and_return(1)
+ expect do
+ Bundler.with_friendly_errors do
+ raise exception
+ end
+ end.to raise_error(SystemExit)
+ end
+
+ it "calls exit_status on exception" do
+ exception = Exception.new
+ expect(Bundler::FriendlyErrors).to receive(:log_error).with(exception)
+ expect do
+ Bundler.with_friendly_errors do
+ raise exception
+ end
+ end.to raise_error(SystemExit)
+ end
+
+ describe "#log_error" do
+ shared_examples "Bundler.ui receive error" do |error, message|
+ it "" do
+ expect(Bundler.ui).to receive(:error).with(message || error.message)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ shared_examples "Bundler.ui receive trace" do |error|
+ it "" do
+ expect(Bundler.ui).to receive(:trace).with(error)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ context "YamlSyntaxError" do
+ it_behaves_like "Bundler.ui receive error", Bundler::YamlSyntaxError.new(StandardError.new, "sample_message")
+
+ it "Bundler.ui receive trace" do
+ std_error = StandardError.new
+ exception = Bundler::YamlSyntaxError.new(std_error, "sample_message")
+ expect(Bundler.ui).to receive(:trace).with(std_error)
+ Bundler::FriendlyErrors.log_error(exception)
+ end
+ end
+
+ context "Dsl::DSLError, GemspecError" do
+ it_behaves_like "Bundler.ui receive error", Bundler::Dsl::DSLError.new("description", "dsl_path", "backtrace")
+ it_behaves_like "Bundler.ui receive error", Bundler::GemspecError.new
+ end
+
+ context "GemRequireError" do
+ let(:orig_error) { StandardError.new }
+ let(:error) { Bundler::GemRequireError.new(orig_error, "sample_message") }
+
+ before do
+ allow(orig_error).to receive(:backtrace).and_return([])
+ end
+
+ it "Bundler.ui receive error" do
+ expect(Bundler.ui).to receive(:error).with(error.message)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+
+ it "writes to Bundler.ui.trace" do
+ expect(Bundler.ui).to receive(:trace).with(orig_error)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ context "BundlerError" do
+ it "Bundler.ui receive error" do
+ error = Bundler::BundlerError.new
+ expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ context "Thor::Error" do
+ it_behaves_like "Bundler.ui receive error", Bundler::Thor::Error.new
+ end
+
+ context "Interrupt" do
+ it "Bundler.ui receive error" do
+ expect(Bundler.ui).to receive(:error).with("\nQuitting...")
+ Bundler::FriendlyErrors.log_error(Interrupt.new)
+ end
+ it_behaves_like "Bundler.ui receive trace", Interrupt.new
+ end
+
+ context "Gem::InvalidSpecificationException" do
+ it "Bundler.ui receive error" do
+ error = Gem::InvalidSpecificationException.new
+ expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ context "SystemExit" do
+ # Does nothing
+ end
+
+ context "Java::JavaLang::OutOfMemoryError" do
+ module Java
+ module JavaLang
+ class OutOfMemoryError < StandardError; end
+ end
+ end
+
+ it "Bundler.ui receive error" do
+ error = Java::JavaLang::OutOfMemoryError.new
+ expect(Bundler.ui).to receive(:error).with(/JVM has run out of memory/)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ context "unexpected error" do
+ it "calls request_issue_report_for with error" do
+ error = StandardError.new
+ expect(Bundler::FriendlyErrors).to receive(:request_issue_report_for).with(error)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+ end
+
+ describe "#exit_status" do
+ it "calls status_code for BundlerError" do
+ error = Bundler::BundlerError.new
+ expect(error).to receive(:status_code).and_return("sample_status_code")
+ expect(Bundler::FriendlyErrors.exit_status(error)).to eq("sample_status_code")
+ end
+
+ it "returns 15 for Thor::Error" do
+ error = Bundler::Thor::Error.new
+ expect(Bundler::FriendlyErrors.exit_status(error)).to eq(15)
+ end
+
+ it "calls status for SystemExit" do
+ error = SystemExit.new
+ expect(error).to receive(:status).and_return("sample_status")
+ expect(Bundler::FriendlyErrors.exit_status(error)).to eq("sample_status")
+ end
+
+ it "returns 1 in other cases" do
+ error = StandardError.new
+ expect(Bundler::FriendlyErrors.exit_status(error)).to eq(1)
+ end
+ end
+
+ describe "#request_issue_report_for" do
+ it "calls relevant methods for Bundler.ui" do
+ expect(Bundler.ui).not_to receive(:info)
+ expect(Bundler.ui).to receive(:error).exactly(3).times
+ expect(Bundler.ui).not_to receive(:warn)
+ Bundler::FriendlyErrors.request_issue_report_for(StandardError.new)
+ end
+
+ it "includes error class, message and backlog" do
+ error = StandardError.new
+ allow(Bundler::FriendlyErrors).to receive(:issues_url).and_return("")
+
+ expect(error).to receive(:class).at_least(:once)
+ expect(error).to receive(:message).at_least(:once)
+ expect(error).to receive(:backtrace).at_least(:once)
+ Bundler::FriendlyErrors.request_issue_report_for(error)
+ end
+ end
+
+ describe "#issues_url" do
+ it "generates a search URL for the exception message" do
+ exception = Exception.new("Exception message")
+
+ expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/rubygems/rubygems/search?q=Exception+message&type=Issues")
+ end
+
+ it "generates a search URL for only the first line of a multi-line exception message" do
+ exception = Exception.new(<<END)
+First line of the exception message
+Second line of the exception message
+END
+
+ expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/rubygems/rubygems/search?q=First+line+of+the+exception+message&type=Issues")
+ end
+
+ it "generates the url without colons" do
+ exception = Exception.new(<<END)
+Exception ::: with ::: colons :::
+END
+ issues_url = Bundler::FriendlyErrors.issues_url(exception)
+ expect(issues_url).not_to include("%3A")
+ expect(issues_url).to eq("https://github.com/rubygems/rubygems/search?q=#{CGI.escape("Exception with colons ")}&type=Issues")
+ end
+
+ it "removes information after - for Errono::EACCES" do
+ exception = Exception.new(<<END)
+Errno::EACCES: Permission denied @ dir_s_mkdir - /Users/foo/bar/
+END
+ allow(exception).to receive(:is_a?).with(Errno).and_return(true)
+ issues_url = Bundler::FriendlyErrors.issues_url(exception)
+ expect(issues_url).not_to include("/Users/foo/bar")
+ expect(issues_url).to eq("https://github.com/rubygems/rubygems/search?q=#{CGI.escape("Errno EACCES Permission denied @ dir_s_mkdir ")}&type=Issues")
+ end
+ end
+end
diff --git a/spec/bundler/bundler/gem_helper_spec.rb b/spec/bundler/bundler/gem_helper_spec.rb
new file mode 100644
index 0000000000..7d955007ab
--- /dev/null
+++ b/spec/bundler/bundler/gem_helper_spec.rb
@@ -0,0 +1,451 @@
+# frozen_string_literal: true
+
+require "rake"
+require "bundler/gem_helper"
+
+RSpec.describe Bundler::GemHelper do
+ let(:app_name) { "lorem__ipsum" }
+ let(:app_path) { bundled_app app_name }
+ let(:app_gemspec_path) { app_path.join("#{app_name}.gemspec") }
+
+ before(:each) do
+ global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__LINTER" => "false",
+ "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__CHANGELOG" => "false"
+ sys_exec("git config --global init.defaultBranch main")
+ bundle "gem #{app_name}"
+ prepare_gemspec(app_gemspec_path)
+ end
+
+ context "determining gemspec" do
+ subject { Bundler::GemHelper.new(app_path) }
+
+ context "fails" do
+ it "when there is no gemspec" do
+ FileUtils.rm app_gemspec_path
+ expect { subject }.to raise_error(/Unable to determine name/)
+ end
+
+ it "when there are two gemspecs and the name isn't specified" do
+ FileUtils.touch app_path.join("#{app_name}-2.gemspec")
+ expect { subject }.to raise_error(/Unable to determine name/)
+ end
+ end
+
+ context "interpolates the name" do
+ it "when there is only one gemspec" do
+ expect(subject.gemspec.name).to eq(app_name)
+ end
+
+ it "for a hidden gemspec" do
+ FileUtils.mv app_gemspec_path, app_path.join(".gemspec")
+ expect(subject.gemspec.name).to eq(app_name)
+ end
+ end
+
+ it "handles namespaces and converts them to CamelCase" do
+ bundle "gem #{app_name}-foo_bar"
+ underscore_path = bundled_app "#{app_name}-foo_bar"
+
+ lib = underscore_path.join("lib/#{app_name}/foo_bar.rb").read
+ expect(lib).to include("module LoremIpsum")
+ expect(lib).to include("module FooBar")
+ end
+ end
+
+ context "gem management" do
+ def mock_confirm_message(message)
+ expect(Bundler.ui).to receive(:confirm).with(message)
+ end
+
+ def mock_build_message(name, version)
+ message = "#{name} #{version} built to pkg/#{name}-#{version}.gem."
+ mock_confirm_message message
+ end
+
+ def mock_checksum_message(name, version)
+ message = "#{name} #{version} checksum written to checksums/#{name}-#{version}.gem.sha512."
+ mock_confirm_message message
+ end
+
+ def sha512_hexdigest(path)
+ Digest::SHA512.file(path).hexdigest
+ end
+
+ subject! { Bundler::GemHelper.new(app_path) }
+ let(:app_version) { "0.1.0" }
+ let(:app_gem_dir) { app_path.join("pkg") }
+ let(:app_gem_path) { app_gem_dir.join("#{app_name}-#{app_version}.gem") }
+ let(:app_sha_path) { app_path.join("checksums", "#{app_name}-#{app_version}.gem.sha512") }
+ let(:app_gemspec_content) { File.read(app_gemspec_path) }
+
+ before(:each) do
+ content = app_gemspec_content.gsub("TODO: ", "")
+ content.sub!(/homepage\s+= ".*"/, 'homepage = ""')
+ content.gsub!(/spec\.metadata.+\n/, "")
+ File.open(app_gemspec_path, "w") {|file| file << content }
+ end
+
+ it "uses a shell UI for output" do
+ expect(Bundler.ui).to be_a(Bundler::UI::Shell)
+ end
+
+ describe "#install" do
+ let!(:rake_application) { Rake.application }
+
+ before(:each) do
+ Rake.application = Rake::Application.new
+ end
+
+ after(:each) do
+ Rake.application = rake_application
+ end
+
+ context "defines Rake tasks" do
+ let(:task_names) do
+ %w[build install release release:guard_clean
+ release:source_control_push release:rubygem_push]
+ end
+
+ context "before installation" do
+ it "raises an error with appropriate message" do
+ task_names.each do |name|
+ skip "Rake::FileTask '#{name}' exists" if File.exist?(name)
+ expect { Rake.application[name] }.
+ to raise_error(/^Don't know how to build task '#{name}'/)
+ end
+ end
+ end
+
+ context "after installation" do
+ before do
+ subject.install
+ end
+
+ it "adds Rake tasks successfully" do
+ task_names.each do |name|
+ expect { Rake.application[name] }.not_to raise_error
+ expect(Rake.application[name]).to be_instance_of Rake::Task
+ end
+ end
+
+ it "provides a way to access the gemspec object" do
+ expect(subject.gemspec.name).to eq(app_name)
+ end
+ end
+ end
+ end
+
+ describe "#build_gem" do
+ context "when build failed" do
+ it "raises an error with appropriate message" do
+ # break the gemspec by adding back the TODOs
+ File.open(app_gemspec_path, "w") {|file| file << app_gemspec_content }
+ expect { subject.build_gem }.to raise_error(/TODO/)
+ end
+ end
+
+ context "when build was successful" do
+ it "creates .gem file" do
+ mock_build_message app_name, app_version
+ subject.build_gem
+ expect(app_gem_path).to exist
+ end
+ end
+
+ context "when building in the current working directory" do
+ it "creates .gem file" do
+ mock_build_message app_name, app_version
+ Dir.chdir app_path do
+ Bundler::GemHelper.new.build_gem
+ end
+ expect(app_gem_path).to exist
+ end
+ end
+
+ context "when building in a location relative to the current working directory" do
+ it "creates .gem file" do
+ mock_build_message app_name, app_version
+ Dir.chdir File.dirname(app_path) do
+ Bundler::GemHelper.new(File.basename(app_path)).build_gem
+ end
+ expect(app_gem_path).to exist
+ end
+ end
+ end
+
+ describe "#build_checksum" do
+ it "calculates SHA512 of the content" do
+ FileUtils.mkdir_p(app_gem_dir)
+ File.write(app_gem_path, "")
+ mock_checksum_message app_name, app_version
+ subject.build_checksum(app_gem_path)
+ expect(File.read(app_sha_path).chomp).to eql(Digest::SHA512.hexdigest(""))
+ end
+
+ context "when build was successful" do
+ it "creates .sha512 file" do
+ mock_build_message app_name, app_version
+ mock_checksum_message app_name, app_version
+ subject.build_checksum
+ expect(app_sha_path).to exist
+ expect(File.read(app_sha_path).chomp).to eql(sha512_hexdigest(app_gem_path))
+ end
+ end
+ context "when building in the current working directory" do
+ it "creates a .sha512 file" do
+ mock_build_message app_name, app_version
+ mock_checksum_message app_name, app_version
+ Dir.chdir app_path do
+ Bundler::GemHelper.new.build_checksum
+ end
+ expect(app_sha_path).to exist
+ expect(File.read(app_sha_path).chomp).to eql(sha512_hexdigest(app_gem_path))
+ end
+ end
+ context "when building in a location relative to the current working directory" do
+ it "creates a .sha512 file" do
+ mock_build_message app_name, app_version
+ mock_checksum_message app_name, app_version
+ Dir.chdir File.dirname(app_path) do
+ Bundler::GemHelper.new(File.basename(app_path)).build_checksum
+ end
+ expect(app_sha_path).to exist
+ expect(File.read(app_sha_path).chomp).to eql(sha512_hexdigest(app_gem_path))
+ end
+ end
+ end
+
+ describe "#install_gem" do
+ context "when installation was successful" do
+ it "gem is installed" do
+ mock_build_message app_name, app_version
+ mock_confirm_message "#{app_name} (#{app_version}) installed."
+ subject.install_gem(nil, :local)
+ expect(app_gem_path).to exist
+ gem_command :list
+ expect(out).to include("#{app_name} (#{app_version})")
+ end
+ end
+
+ context "when installation fails" do
+ it "raises an error with appropriate message" do
+ # create empty gem file in order to simulate install failure
+ allow(subject).to receive(:build_gem) do
+ FileUtils.mkdir_p(app_gem_dir)
+ FileUtils.touch app_gem_path
+ app_gem_path
+ end
+ expect { subject.install_gem }.to raise_error(/Running `#{gem_bin} install #{app_gem_path}` failed/)
+ end
+ end
+ end
+
+ describe "rake release" do
+ let!(:rake_application) { Rake.application }
+
+ before(:each) do
+ Rake.application = Rake::Application.new
+ subject.install
+ end
+
+ after(:each) do
+ Rake.application = rake_application
+ end
+
+ before do
+ sys_exec("git init", :dir => app_path)
+ sys_exec("git config user.email \"you@example.com\"", :dir => app_path)
+ sys_exec("git config user.name \"name\"", :dir => app_path)
+ sys_exec("git config commit.gpgsign false", :dir => app_path)
+ sys_exec("git config push.default simple", :dir => app_path)
+
+ # silence messages
+ allow(Bundler.ui).to receive(:confirm)
+ allow(Bundler.ui).to receive(:error)
+ end
+
+ context "fails" do
+ it "when there are unstaged files" do
+ expect { Rake.application["release"].invoke }.
+ to raise_error("There are files that need to be committed first.")
+ end
+
+ it "when there are uncommitted files" do
+ sys_exec("git add .", :dir => app_path)
+ expect { Rake.application["release"].invoke }.
+ to raise_error("There are files that need to be committed first.")
+ end
+
+ it "when there is no git remote" do
+ sys_exec("git commit -a -m \"initial commit\"", :dir => app_path)
+ expect { Rake.application["release"].invoke }.to raise_error(RuntimeError)
+ end
+ end
+
+ context "succeeds" do
+ let(:repo) { build_git("foo", :bare => true) }
+
+ before do
+ sys_exec("git remote add origin #{file_uri_for(repo.path)}", :dir => app_path)
+ sys_exec('git commit -a -m "initial commit"', :dir => app_path)
+ end
+
+ context "on releasing" do
+ before do
+ mock_build_message app_name, app_version
+ mock_confirm_message "Tagged v#{app_version}."
+ mock_confirm_message "Pushed git commits and release tag."
+
+ sys_exec("git push -u origin main", :dir => app_path)
+ end
+
+ it "calls rubygem_push with proper arguments" do
+ expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
+
+ Rake.application["release"].invoke
+ end
+
+ it "uses Kernel.system" do
+ cmd = gem_bin.shellsplit
+ expect(Kernel).to receive(:system).with(*cmd, "push", app_gem_path.to_s, "--host", "http://example.org").and_return(true)
+
+ Rake.application["release"].invoke
+ end
+
+ it "also works when releasing from an ambiguous reference" do
+ # Create a branch with the same name as the tag
+ sys_exec("git checkout -b v#{app_version}", :dir => app_path)
+ sys_exec("git push -u origin v#{app_version}", :dir => app_path)
+
+ expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
+
+ Rake.application["release"].invoke
+ end
+
+ it "also works with releasing from a branch not yet pushed" do
+ sys_exec("git checkout -b module_function", :dir => app_path)
+
+ expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
+
+ Rake.application["release"].invoke
+ end
+ end
+
+ context "on releasing with a custom tag prefix" do
+ before do
+ Bundler::GemHelper.tag_prefix = "foo-"
+ mock_build_message app_name, app_version
+ mock_confirm_message "Pushed git commits and release tag."
+
+ sys_exec("git push -u origin main", :dir => app_path)
+ expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
+ end
+
+ it "prepends the custom prefix to the tag" do
+ mock_confirm_message "Tagged foo-v#{app_version}."
+
+ Rake.application["release"].invoke
+ end
+ end
+
+ it "even if tag already exists" do
+ mock_build_message app_name, app_version
+ mock_confirm_message "Tag v#{app_version} has already been created."
+ expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
+
+ sys_exec("git tag -a -m \"Version #{app_version}\" v#{app_version}", :dir => app_path)
+
+ Rake.application["release"].invoke
+ end
+ end
+ end
+
+ describe "release:rubygem_push" do
+ let!(:rake_application) { Rake.application }
+
+ before(:each) do
+ Rake.application = Rake::Application.new
+ subject.install
+ allow(subject).to receive(:sh_with_input)
+ end
+
+ after(:each) do
+ Rake.application = rake_application
+ end
+
+ before do
+ sys_exec("git init", :dir => app_path)
+ sys_exec("git config user.email \"you@example.com\"", :dir => app_path)
+ sys_exec("git config user.name \"name\"", :dir => app_path)
+ sys_exec("git config push.gpgsign simple", :dir => app_path)
+
+ # silence messages
+ allow(Bundler.ui).to receive(:confirm)
+ allow(Bundler.ui).to receive(:error)
+
+ credentials = double("credentials", "file?" => true)
+ allow(Bundler.user_home).to receive(:join).
+ with(".gem/credentials").and_return(credentials)
+ end
+
+ describe "success messaging" do
+ context "No allowed_push_host set" do
+ before do
+ allow(subject).to receive(:allowed_push_host).and_return(nil)
+ end
+
+ around do |example|
+ orig_host = ENV["RUBYGEMS_HOST"]
+ ENV["RUBYGEMS_HOST"] = rubygems_host_env
+
+ example.run
+
+ ENV["RUBYGEMS_HOST"] = orig_host
+ end
+
+ context "RUBYGEMS_HOST env var is set" do
+ let(:rubygems_host_env) { "https://custom.env.gemhost.com" }
+
+ it "should report successful push to the host from the environment" do
+ mock_confirm_message "Pushed #{app_name} #{app_version} to #{rubygems_host_env}"
+
+ Rake.application["release:rubygem_push"].invoke
+ end
+ end
+
+ context "RUBYGEMS_HOST env var is not set" do
+ let(:rubygems_host_env) { nil }
+
+ it "should report successful push to rubygems.org" do
+ mock_confirm_message "Pushed #{app_name} #{app_version} to rubygems.org"
+
+ Rake.application["release:rubygem_push"].invoke
+ end
+ end
+
+ context "RUBYGEMS_HOST env var is an empty string" do
+ let(:rubygems_host_env) { "" }
+
+ it "should report successful push to rubygems.org" do
+ mock_confirm_message "Pushed #{app_name} #{app_version} to rubygems.org"
+
+ Rake.application["release:rubygem_push"].invoke
+ end
+ end
+ end
+
+ context "allowed_push_host set in gemspec" do
+ before do
+ allow(subject).to receive(:allowed_push_host).and_return("https://my.gemhost.com")
+ end
+
+ it "should report successful push to the allowed gem host" do
+ mock_confirm_message "Pushed #{app_name} #{app_version} to https://my.gemhost.com"
+
+ Rake.application["release:rubygem_push"].invoke
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/gem_version_promoter_spec.rb b/spec/bundler/bundler/gem_version_promoter_spec.rb
new file mode 100644
index 0000000000..8058412f32
--- /dev/null
+++ b/spec/bundler/bundler/gem_version_promoter_spec.rb
@@ -0,0 +1,163 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::GemVersionPromoter do
+ let(:gvp) { described_class.new }
+
+ # Rightmost (highest array index) in result is most preferred.
+ # Leftmost (lowest array index) in result is least preferred.
+ # `build_candidates` has all versions of gem in index.
+ # `build_spec` is the version currently in the .lock file.
+ #
+ # In default (not strict) mode, all versions in the index will
+ # be returned, allowing Bundler the best chance to resolve all
+ # dependencies, but sometimes resulting in upgrades that some
+ # would not consider conservative.
+
+ describe "#sort_versions" do
+ def build_candidates(versions)
+ versions.map do |v|
+ Bundler::Resolver::Candidate.new(v)
+ end
+ end
+
+ def build_package(name, version, locked = [])
+ Bundler::Resolver::Package.new(name, [], :locked_specs => Bundler::SpecSet.new(build_spec(name, version)), :unlock => locked)
+ end
+
+ def sorted_versions(candidates:, current:, name: "foo", locked: [])
+ gvp.sort_versions(
+ build_package(name, current, locked),
+ build_candidates(candidates)
+ ).flatten.map(&:version).map(&:to_s)
+ end
+
+ it "numerically sorts versions" do
+ versions = sorted_versions(:candidates => %w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0], :current => "1.7.8")
+ expect(versions).to eq %w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0]
+ end
+
+ context "with no options" do
+ it "defaults to level=:major, strict=false, pre=false" do
+ versions = sorted_versions(:candidates => %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], :current => "0.3.0")
+ expect(versions).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0]
+ end
+ end
+
+ context "when strict" do
+ before { gvp.strict = true }
+
+ context "when level is major" do
+ before { gvp.level = :major }
+
+ it "keeps downgrades" do
+ versions = sorted_versions(:candidates => %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], :current => "0.3.0")
+ expect(versions).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0]
+ end
+ end
+
+ context "when level is minor" do
+ before { gvp.level = :minor }
+
+ it "removes downgrades and major upgrades" do
+ versions = sorted_versions(:candidates => %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], :current => "0.3.0")
+ expect(versions).to eq %w[0.3.0 0.3.1 0.9.0]
+ end
+ end
+
+ context "when level is patch" do
+ before { gvp.level = :patch }
+
+ it "removes downgrades and major and minor upgrades" do
+ versions = sorted_versions(:candidates => %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], :current => "0.3.0")
+ expect(versions).to eq %w[0.3.0 0.3.1]
+ end
+ end
+ end
+
+ context "when not strict" do
+ before { gvp.strict = false }
+
+ context "when level is major" do
+ before { gvp.level = :major }
+
+ it "orders by version" do
+ versions = sorted_versions(:candidates => %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], :current => "0.3.0")
+ expect(versions).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0]
+ end
+ end
+
+ context "when level is minor" do
+ before { gvp.level = :minor }
+
+ it "favors downgrades, then upgrades by major descending, minor ascending, patch ascending" do
+ versions = sorted_versions(:candidates => %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], :current => "0.3.0")
+ expect(versions).to eq %w[0.2.0 2.0.1 2.1.0 1.0.0 0.3.0 0.3.1 0.9.0]
+ end
+ end
+
+ context "when level is patch" do
+ before { gvp.level = :patch }
+
+ it "favors downgrades, then upgrades by major descending, minor descending, patch ascending" do
+ versions = sorted_versions(:candidates => %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], :current => "0.3.0")
+ expect(versions).to eq %w[0.2.0 2.1.0 2.0.1 1.0.0 0.9.0 0.3.0 0.3.1]
+ end
+ end
+ end
+
+ context "when pre" do
+ before { gvp.pre = true }
+
+ it "sorts regardless of prerelease status" do
+ versions = sorted_versions(:candidates => %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0], :current => "1.8.0")
+ expect(versions).to eq %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0]
+ end
+ end
+
+ context "when not pre" do
+ before { gvp.pre = false }
+
+ it "deprioritizes prerelease gems" do
+ versions = sorted_versions(:candidates => %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0], :current => "1.8.0")
+ expect(versions).to eq %w[1.7.7.pre 1.8.1.pre 2.0.0.pre 1.8.0 1.8.1 2.0.0]
+ end
+ end
+
+ context "when locking and not major" do
+ before { gvp.level = :minor }
+
+ it "keeps the current version last" do
+ versions = sorted_versions(:candidates => %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.1.0 2.0.1], :current => "0.3.0", :locked => ["bar"])
+ expect(versions.last).to eq("0.3.0")
+ end
+ end
+ end
+
+ describe "#level=" do
+ subject { described_class.new }
+
+ it "should raise if not major, minor or patch is passed" do
+ expect { subject.level = :minjor }.to raise_error ArgumentError
+ end
+
+ it "should raise if invalid classes passed" do
+ [123, nil].each do |value|
+ expect { subject.level = value }.to raise_error ArgumentError
+ end
+ end
+
+ it "should accept major, minor patch symbols" do
+ [:major, :minor, :patch].each do |value|
+ subject.level = value
+ expect(subject.level).to eq value
+ end
+ end
+
+ it "should accept major, minor patch strings" do
+ %w[major minor patch].each do |value|
+ subject.level = value
+ expect(subject.level).to eq value.to_sym
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/index_spec.rb b/spec/bundler/bundler/index_spec.rb
new file mode 100644
index 0000000000..0f3f6e4944
--- /dev/null
+++ b/spec/bundler/bundler/index_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Index do
+ let(:specs) { [] }
+ subject { described_class.build {|i| i.use(specs) } }
+
+ context "specs with a nil platform" do
+ let(:spec) do
+ Gem::Specification.new do |s|
+ s.name = "json"
+ s.version = "1.8.3"
+ allow(s).to receive(:platform).and_return(nil)
+ end
+ end
+ let(:specs) { [spec] }
+
+ describe "#search_by_spec" do
+ it "finds the spec when a nil platform is specified" do
+ expect(subject.search(spec)).to eq([spec])
+ end
+
+ it "finds the spec when a ruby platform is specified" do
+ query = spec.dup.tap {|s| s.platform = "ruby" }
+ expect(subject.search(query)).to eq([spec])
+ end
+ end
+ end
+
+ context "with specs that include development dependencies" do
+ let(:specs) { [*build_spec("a", "1.0.0") {|s| s.development("b", "~> 1.0") }] }
+
+ it "does not include b in #dependency_names" do
+ expect(subject.dependency_names).not_to include("b")
+ end
+ end
+end
diff --git a/spec/bundler/bundler/installer/gem_installer_spec.rb b/spec/bundler/bundler/installer/gem_installer_spec.rb
new file mode 100644
index 0000000000..e86bdf009a
--- /dev/null
+++ b/spec/bundler/bundler/installer/gem_installer_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require "bundler/installer/gem_installer"
+
+RSpec.describe Bundler::GemInstaller do
+ let(:definition) { instance_double("Definition", :locked_gems => nil) }
+ let(:installer) { instance_double("Installer", :definition => definition) }
+ let(:spec_source) { instance_double("SpecSource") }
+ let(:spec) { instance_double("Specification", :name => "dummy", :version => "0.0.1", :loaded_from => "dummy", :source => spec_source) }
+
+ subject { described_class.new(spec, installer) }
+
+ context "spec_settings is nil" do
+ it "invokes install method with empty build_args" do
+ allow(spec_source).to receive(:install).with(
+ spec,
+ { :force => false, :ensure_builtin_gems_cached => false, :build_args => [], :previous_spec => nil }
+ )
+ subject.install_from_spec
+ end
+ end
+
+ context "spec_settings is build option" do
+ it "invokes install method with build_args" do
+ allow(Bundler.settings).to receive(:[]).with(:bin)
+ allow(Bundler.settings).to receive(:[]).with(:inline)
+ allow(Bundler.settings).to receive(:[]).with(:forget_cli_options)
+ allow(Bundler.settings).to receive(:[]).with("build.dummy").and_return("--with-dummy-config=dummy")
+ expect(spec_source).to receive(:install).with(
+ spec,
+ { :force => false, :ensure_builtin_gems_cached => false, :build_args => ["--with-dummy-config=dummy"], :previous_spec => nil }
+ )
+ subject.install_from_spec
+ end
+ end
+
+ context "spec_settings is build option with spaces" do
+ it "invokes install method with build_args" do
+ allow(Bundler.settings).to receive(:[]).with(:bin)
+ allow(Bundler.settings).to receive(:[]).with(:inline)
+ allow(Bundler.settings).to receive(:[]).with(:forget_cli_options)
+ allow(Bundler.settings).to receive(:[]).with("build.dummy").and_return("--with-dummy-config=dummy --with-another-dummy-config")
+ expect(spec_source).to receive(:install).with(
+ spec,
+ { :force => false, :ensure_builtin_gems_cached => false, :build_args => ["--with-dummy-config=dummy", "--with-another-dummy-config"], :previous_spec => nil }
+ )
+ subject.install_from_spec
+ end
+ end
+end
diff --git a/spec/bundler/bundler/installer/parallel_installer_spec.rb b/spec/bundler/bundler/installer/parallel_installer_spec.rb
new file mode 100644
index 0000000000..c8403a2e38
--- /dev/null
+++ b/spec/bundler/bundler/installer/parallel_installer_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require "bundler/installer/parallel_installer"
+
+RSpec.describe Bundler::ParallelInstaller do
+ let(:installer) { instance_double("Installer") }
+ let(:all_specs) { [] }
+ let(:size) { 1 }
+ let(:standalone) { false }
+ let(:force) { false }
+
+ subject { described_class.new(installer, all_specs, size, standalone, force) }
+
+ context "when the spec set is not a valid resolution" do
+ let(:all_specs) do
+ [
+ build_spec("cucumber", "4.1.0") {|s| s.runtime "diff-lcs", "< 1.4" },
+ build_spec("diff-lcs", "1.4.4"),
+ ].flatten
+ end
+
+ it "prints a warning" do
+ expect(Bundler.ui).to receive(:warn).with(<<-W.strip)
+Your lockfile doesn't include a valid resolution.
+You can fix this by regenerating your lockfile or manually editing the bad locked gems to a version that satisfies all dependencies.
+The unmet dependencies are:
+* diff-lcs (< 1.4), dependency of cucumber-4.1.0, unsatisfied by diff-lcs-1.4.4
+ W
+ subject.check_for_unmet_dependencies
+ end
+ end
+
+ context "when the spec set is a valid resolution" do
+ let(:all_specs) do
+ [
+ build_spec("cucumber", "4.1.0") {|s| s.runtime "diff-lcs", "< 1.4" },
+ build_spec("diff-lcs", "1.3"),
+ ].flatten
+ end
+
+ it "doesn't print a warning" do
+ expect(Bundler.ui).not_to receive(:warn)
+ subject.check_for_unmet_dependencies
+ end
+ end
+end
diff --git a/spec/bundler/bundler/installer/spec_installation_spec.rb b/spec/bundler/bundler/installer/spec_installation_spec.rb
new file mode 100644
index 0000000000..e63ef26cb3
--- /dev/null
+++ b/spec/bundler/bundler/installer/spec_installation_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require "bundler/installer/parallel_installer"
+
+RSpec.describe Bundler::ParallelInstaller::SpecInstallation do
+ let!(:dep) do
+ a_spec = Object.new
+ def a_spec.name
+ "I like tests"
+ end
+
+ def a_spec.full_name
+ "I really like tests"
+ end
+ a_spec
+ end
+
+ describe "#ready_to_enqueue?" do
+ context "when in enqueued state" do
+ it "is falsey" do
+ spec = described_class.new(dep)
+ spec.state = :enqueued
+ expect(spec.ready_to_enqueue?).to be_falsey
+ end
+ end
+
+ context "when in installed state" do
+ it "returns falsey" do
+ spec = described_class.new(dep)
+ spec.state = :installed
+ expect(spec.ready_to_enqueue?).to be_falsey
+ end
+ end
+
+ it "returns truthy" do
+ spec = described_class.new(dep)
+ expect(spec.ready_to_enqueue?).to be_truthy
+ end
+ end
+
+ describe "#dependencies_installed?" do
+ context "when all dependencies are installed" do
+ it "returns true" do
+ dependencies = []
+ dependencies << instance_double("SpecInstallation", :spec => "alpha", :name => "alpha", :installed? => true, :all_dependencies => [], :type => :production)
+ dependencies << instance_double("SpecInstallation", :spec => "beta", :name => "beta", :installed? => true, :all_dependencies => [], :type => :production)
+ all_specs = dependencies + [instance_double("SpecInstallation", :spec => "gamma", :name => "gamma", :installed? => false, :all_dependencies => [], :type => :production)]
+ spec = described_class.new(dep)
+ allow(spec).to receive(:all_dependencies).and_return(dependencies)
+ expect(spec.dependencies_installed?(all_specs)).to be_truthy
+ end
+ end
+
+ context "when all dependencies are not installed" do
+ it "returns false" do
+ dependencies = []
+ dependencies << instance_double("SpecInstallation", :spec => "alpha", :name => "alpha", :installed? => false, :all_dependencies => [], :type => :production)
+ dependencies << instance_double("SpecInstallation", :spec => "beta", :name => "beta", :installed? => true, :all_dependencies => [], :type => :production)
+ all_specs = dependencies + [instance_double("SpecInstallation", :spec => "gamma", :name => "gamma", :installed? => false, :all_dependencies => [], :type => :production)]
+ spec = described_class.new(dep)
+ allow(spec).to receive(:all_dependencies).and_return(dependencies)
+ expect(spec.dependencies_installed?(all_specs)).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/lockfile_parser_spec.rb b/spec/bundler/bundler/lockfile_parser_spec.rb
new file mode 100644
index 0000000000..3a6d61336f
--- /dev/null
+++ b/spec/bundler/bundler/lockfile_parser_spec.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require "bundler/lockfile_parser"
+
+RSpec.describe Bundler::LockfileParser do
+ let(:lockfile_contents) { strip_whitespace(<<-L) }
+ GIT
+ remote: https://github.com/alloy/peiji-san.git
+ revision: eca485d8dc95f12aaec1a434b49d295c7e91844b
+ specs:
+ peiji-san (1.2.0)
+
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ rake (10.3.2)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ peiji-san!
+ rake
+
+ RUBY VERSION
+ ruby 2.1.3p242
+
+ BUNDLED WITH
+ 1.12.0.rc.2
+ L
+
+ describe ".sections_in_lockfile" do
+ it "returns the attributes" do
+ attributes = described_class.sections_in_lockfile(lockfile_contents)
+ expect(attributes).to contain_exactly(
+ "BUNDLED WITH", "DEPENDENCIES", "GEM", "GIT", "PLATFORMS", "RUBY VERSION"
+ )
+ end
+ end
+
+ describe ".unknown_sections_in_lockfile" do
+ let(:lockfile_contents) { strip_whitespace(<<-L) }
+ UNKNOWN ATTR
+
+ UNKNOWN ATTR 2
+ random contents
+ L
+
+ it "returns the unknown attributes" do
+ attributes = described_class.unknown_sections_in_lockfile(lockfile_contents)
+ expect(attributes).to contain_exactly("UNKNOWN ATTR", "UNKNOWN ATTR 2")
+ end
+ end
+
+ describe ".sections_to_ignore" do
+ subject { described_class.sections_to_ignore(base_version) }
+
+ context "with a nil base version" do
+ let(:base_version) { nil }
+
+ it "returns the same as > 1.0" do
+ expect(subject).to contain_exactly(
+ described_class::BUNDLED, described_class::RUBY, described_class::PLUGIN
+ )
+ end
+ end
+
+ context "with a prerelease base version" do
+ let(:base_version) { Gem::Version.create("1.11.0.rc.1") }
+
+ it "returns the same as for the release version" do
+ expect(subject).to contain_exactly(
+ described_class::RUBY, described_class::PLUGIN
+ )
+ end
+ end
+
+ context "with a current version" do
+ let(:base_version) { Gem::Version.create(Bundler::VERSION) }
+
+ it "returns an empty array" do
+ expect(subject).to eq([])
+ end
+ end
+
+ context "with a future version" do
+ let(:base_version) { Gem::Version.create("5.5.5") }
+
+ it "returns an empty array" do
+ expect(subject).to eq([])
+ end
+ end
+ end
+
+ describe "#initialize" do
+ before { allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app("gems.rb")) }
+ subject { described_class.new(lockfile_contents) }
+
+ let(:sources) do
+ [Bundler::Source::Git.new("uri" => "https://github.com/alloy/peiji-san.git", "revision" => "eca485d8dc95f12aaec1a434b49d295c7e91844b"),
+ Bundler::Source::Rubygems.new("remotes" => ["https://rubygems.org"])]
+ end
+ let(:dependencies) do
+ {
+ "peiji-san" => Bundler::Dependency.new("peiji-san", ">= 0"),
+ "rake" => Bundler::Dependency.new("rake", ">= 0"),
+ }
+ end
+ let(:specs) do
+ [
+ Bundler::LazySpecification.new("peiji-san", v("1.2.0"), rb),
+ Bundler::LazySpecification.new("rake", v("10.3.2"), rb),
+ ]
+ end
+ let(:platforms) { [rb] }
+ let(:bundler_version) { Gem::Version.new("1.12.0.rc.2") }
+ let(:ruby_version) { "ruby 2.1.3p242" }
+
+ shared_examples_for "parsing" do
+ it "parses correctly" do
+ expect(subject.sources).to eq sources
+ expect(subject.dependencies).to eq dependencies
+ expect(subject.specs).to eq specs
+ expect(Hash[subject.specs.map {|s| [s, s.dependencies] }]).to eq Hash[subject.specs.map {|s| [s, s.dependencies] }]
+ expect(subject.platforms).to eq platforms
+ expect(subject.bundler_version).to eq bundler_version
+ expect(subject.ruby_version).to eq ruby_version
+ end
+ end
+
+ include_examples "parsing"
+
+ context "when an extra section is at the end" do
+ let(:lockfile_contents) { super() + "\n\nFOO BAR\n baz\n baa\n qux\n" }
+ include_examples "parsing"
+ end
+
+ context "when an extra section is at the start" do
+ let(:lockfile_contents) { "FOO BAR\n baz\n baa\n qux\n\n" + super() }
+ include_examples "parsing"
+ end
+
+ context "when an extra section is in the middle" do
+ let(:lockfile_contents) { super().split(/(?=GEM)/).insert(1, "FOO BAR\n baz\n baa\n qux\n\n").join }
+ include_examples "parsing"
+ end
+
+ context "when a dependency has options" do
+ let(:lockfile_contents) { super().sub("peiji-san!", "peiji-san!\n foo: bar") }
+ include_examples "parsing"
+ end
+ end
+end
diff --git a/spec/bundler/bundler/mirror_spec.rb b/spec/bundler/bundler/mirror_spec.rb
new file mode 100644
index 0000000000..1eaf1e9a8e
--- /dev/null
+++ b/spec/bundler/bundler/mirror_spec.rb
@@ -0,0 +1,331 @@
+# frozen_string_literal: true
+
+require "bundler/mirror"
+
+RSpec.describe Bundler::Settings::Mirror do
+ let(:mirror) { Bundler::Settings::Mirror.new }
+
+ it "returns zero when fallback_timeout is not set" do
+ expect(mirror.fallback_timeout).to eq(0)
+ end
+
+ it "takes a number as a fallback_timeout" do
+ mirror.fallback_timeout = 1
+ expect(mirror.fallback_timeout).to eq(1)
+ end
+
+ it "takes truthy as a default fallback timeout" do
+ mirror.fallback_timeout = true
+ expect(mirror.fallback_timeout).to eq(0.1)
+ end
+
+ it "takes falsey as a zero fallback timeout" do
+ mirror.fallback_timeout = false
+ expect(mirror.fallback_timeout).to eq(0)
+ end
+
+ it "takes a string with 'true' as a default fallback timeout" do
+ mirror.fallback_timeout = "true"
+ expect(mirror.fallback_timeout).to eq(0.1)
+ end
+
+ it "takes a string with 'false' as a zero fallback timeout" do
+ mirror.fallback_timeout = "false"
+ expect(mirror.fallback_timeout).to eq(0)
+ end
+
+ it "takes a string for the uri but returns an uri object" do
+ mirror.uri = "http://localhost:9292"
+ expect(mirror.uri).to eq(Bundler::URI("http://localhost:9292"))
+ end
+
+ it "takes an uri object for the uri" do
+ mirror.uri = Bundler::URI("http://localhost:9293")
+ expect(mirror.uri).to eq(Bundler::URI("http://localhost:9293"))
+ end
+
+ context "without a uri" do
+ it "invalidates the mirror" do
+ mirror.validate!
+ expect(mirror.valid?).to be_falsey
+ end
+ end
+
+ context "with an uri" do
+ before { mirror.uri = "http://localhost:9292" }
+
+ context "without a fallback timeout" do
+ it "is not valid by default" do
+ expect(mirror.valid?).to be_falsey
+ end
+
+ context "when probed" do
+ let(:probe) { double }
+
+ context "with a replying mirror" do
+ before do
+ allow(probe).to receive(:replies?).and_return(true)
+ mirror.validate!(probe)
+ end
+
+ it "is valid" do
+ expect(mirror.valid?).to be_truthy
+ end
+ end
+
+ context "with a non replying mirror" do
+ before do
+ allow(probe).to receive(:replies?).and_return(false)
+ mirror.validate!(probe)
+ end
+
+ it "is still valid" do
+ expect(mirror.valid?).to be_truthy
+ end
+ end
+ end
+ end
+
+ context "with a fallback timeout" do
+ before { mirror.fallback_timeout = 1 }
+
+ it "is not valid by default" do
+ expect(mirror.valid?).to be_falsey
+ end
+
+ context "when probed" do
+ let(:probe) { double }
+
+ context "with a replying mirror" do
+ before do
+ allow(probe).to receive(:replies?).and_return(true)
+ mirror.validate!(probe)
+ end
+
+ it "is valid" do
+ expect(mirror.valid?).to be_truthy
+ end
+
+ it "is validated only once" do
+ allow(probe).to receive(:replies?).and_raise("Only once!")
+ mirror.validate!(probe)
+ expect(mirror.valid?).to be_truthy
+ end
+ end
+
+ context "with a non replying mirror" do
+ before do
+ allow(probe).to receive(:replies?).and_return(false)
+ mirror.validate!(probe)
+ end
+
+ it "is not valid" do
+ expect(mirror.valid?).to be_falsey
+ end
+
+ it "is validated only once" do
+ allow(probe).to receive(:replies?).and_raise("Only once!")
+ mirror.validate!(probe)
+ expect(mirror.valid?).to be_falsey
+ end
+ end
+ end
+ end
+
+ describe "#==" do
+ it "returns true if uri and fallback timeout are the same" do
+ uri = "https://ruby.taobao.org"
+ mirror = Bundler::Settings::Mirror.new(uri, 1)
+ another_mirror = Bundler::Settings::Mirror.new(uri, 1)
+
+ expect(mirror == another_mirror).to be true
+ end
+ end
+ end
+end
+
+RSpec.describe Bundler::Settings::Mirrors do
+ let(:localhost_uri) { Bundler::URI("http://localhost:9292") }
+
+ context "with a just created mirror" do
+ let(:mirrors) do
+ probe = double
+ allow(probe).to receive(:replies?).and_return(true)
+ Bundler::Settings::Mirrors.new(probe)
+ end
+
+ it "returns a mirror that contains the source uri for an unknown uri" do
+ mirror = mirrors.for("http://rubygems.org/")
+ expect(mirror).to eq(Bundler::Settings::Mirror.new("http://rubygems.org/"))
+ end
+
+ it "parses a mirror key and returns a mirror for the parsed uri" do
+ mirrors.parse("mirror.http://rubygems.org/", localhost_uri)
+ expect(mirrors.for("http://rubygems.org/").uri).to eq(localhost_uri)
+ end
+
+ it "parses a relative mirror key and returns a mirror for the parsed http uri" do
+ mirrors.parse("mirror.rubygems.org", localhost_uri)
+ expect(mirrors.for("http://rubygems.org/").uri).to eq(localhost_uri)
+ end
+
+ it "parses a relative mirror key and returns a mirror for the parsed https uri" do
+ mirrors.parse("mirror.rubygems.org", localhost_uri)
+ expect(mirrors.for("https://rubygems.org/").uri).to eq(localhost_uri)
+ end
+
+ context "with a uri parsed already" do
+ before { mirrors.parse("mirror.http://rubygems.org/", localhost_uri) }
+
+ it "takes a mirror fallback_timeout and assigns the timeout" do
+ mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "2")
+ expect(mirrors.for("http://rubygems.org/").fallback_timeout).to eq(2)
+ end
+
+ it "parses a 'true' fallback timeout and sets the default timeout" do
+ mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "true")
+ expect(mirrors.for("http://rubygems.org/").fallback_timeout).to eq(0.1)
+ end
+
+ it "parses a 'false' fallback timeout and sets it to zero" do
+ mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "false")
+ expect(mirrors.for("http://rubygems.org/").fallback_timeout).to eq(0)
+ end
+ end
+ end
+
+ context "with a mirror prober that replies on time" do
+ let(:mirrors) do
+ probe = double
+ allow(probe).to receive(:replies?).and_return(true)
+ Bundler::Settings::Mirrors.new(probe)
+ end
+
+ context "with a default fallback_timeout for rubygems.org" do
+ before do
+ mirrors.parse("mirror.http://rubygems.org/", localhost_uri)
+ mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "true")
+ end
+
+ it "returns localhost" do
+ expect(mirrors.for("http://rubygems.org").uri).to eq(localhost_uri)
+ end
+ end
+
+ context "with a mirror for all" do
+ before do
+ mirrors.parse("mirror.all", localhost_uri)
+ end
+
+ context "without a fallback timeout" do
+ it "returns localhost uri for rubygems" do
+ expect(mirrors.for("http://rubygems.org").uri).to eq(localhost_uri)
+ end
+
+ it "returns localhost for any other url" do
+ expect(mirrors.for("http://whatever.com/").uri).to eq(localhost_uri)
+ end
+ end
+ context "with a fallback timeout" do
+ before { mirrors.parse("mirror.all.fallback_timeout", "1") }
+
+ it "returns localhost uri for rubygems" do
+ expect(mirrors.for("http://rubygems.org").uri).to eq(localhost_uri)
+ end
+
+ it "returns localhost for any other url" do
+ expect(mirrors.for("http://whatever.com/").uri).to eq(localhost_uri)
+ end
+ end
+ end
+ end
+
+ context "with a mirror prober that does not reply on time" do
+ let(:mirrors) do
+ probe = double
+ allow(probe).to receive(:replies?).and_return(false)
+ Bundler::Settings::Mirrors.new(probe)
+ end
+
+ context "with a localhost mirror for all" do
+ before { mirrors.parse("mirror.all", localhost_uri) }
+
+ context "without a fallback timeout" do
+ it "returns localhost" do
+ expect(mirrors.for("http://whatever.com").uri).to eq(localhost_uri)
+ end
+ end
+
+ context "with a fallback timeout" do
+ before { mirrors.parse("mirror.all.fallback_timeout", "true") }
+
+ it "returns the source uri, not localhost" do
+ expect(mirrors.for("http://whatever.com").uri).to eq(Bundler::URI("http://whatever.com/"))
+ end
+ end
+ end
+
+ context "with localhost as a mirror for rubygems.org" do
+ before { mirrors.parse("mirror.http://rubygems.org/", localhost_uri) }
+
+ context "without a fallback timeout" do
+ it "returns the uri that is not mirrored" do
+ expect(mirrors.for("http://whatever.com").uri).to eq(Bundler::URI("http://whatever.com/"))
+ end
+
+ it "returns localhost for rubygems.org" do
+ expect(mirrors.for("http://rubygems.org/").uri).to eq(localhost_uri)
+ end
+ end
+
+ context "with a fallback timeout" do
+ before { mirrors.parse("mirror.http://rubygems.org/.fallback_timeout", "true") }
+
+ it "returns the uri that is not mirrored" do
+ expect(mirrors.for("http://whatever.com").uri).to eq(Bundler::URI("http://whatever.com/"))
+ end
+
+ it "returns rubygems.org for rubygems.org" do
+ expect(mirrors.for("http://rubygems.org/").uri).to eq(Bundler::URI("http://rubygems.org/"))
+ end
+ end
+ end
+ end
+end
+
+RSpec.describe Bundler::Settings::TCPSocketProbe do
+ let(:probe) { Bundler::Settings::TCPSocketProbe.new }
+
+ context "with a listening TCP Server" do
+ def with_server_and_mirror
+ server = TCPServer.new("0.0.0.0", 0)
+ mirror = Bundler::Settings::Mirror.new("http://0.0.0.0:#{server.addr[1]}", 1)
+ yield server, mirror
+ server.close unless server.closed?
+ end
+
+ it "probes the server correctly" do
+ skip "obscure error" if Gem.win_platform?
+
+ with_server_and_mirror do |server, mirror|
+ expect(server.closed?).to be_falsey
+ expect(probe.replies?(mirror)).to be_truthy
+ end
+ end
+
+ it "probes falsey when the server is down" do
+ with_server_and_mirror do |server, mirror|
+ server.close
+ expect(probe.replies?(mirror)).to be_falsey
+ end
+ end
+ end
+
+ context "with an invalid mirror" do
+ let(:mirror) { Bundler::Settings::Mirror.new("http://127.0.0.127:9292", true) }
+
+ it "fails with a timeout when there is nothing to tcp handshake" do
+ expect(probe.replies?(mirror)).to be_falsey
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/api/source_spec.rb b/spec/bundler/bundler/plugin/api/source_spec.rb
new file mode 100644
index 0000000000..428ceb220a
--- /dev/null
+++ b/spec/bundler/bundler/plugin/api/source_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::API::Source do
+ let(:uri) { "uri://to/test" }
+ let(:type) { "spec_type" }
+
+ subject(:source) do
+ klass = Class.new
+ klass.send :include, Bundler::Plugin::API::Source
+ klass.new("uri" => uri, "type" => type)
+ end
+
+ describe "attributes" do
+ it "allows access to uri" do
+ expect(source.uri).to eq("uri://to/test")
+ end
+
+ it "allows access to name" do
+ expect(source.name).to eq("spec_type at uri://to/test")
+ end
+ end
+
+ context "post_install" do
+ let(:installer) { double(:installer) }
+
+ before do
+ allow(Bundler::Source::Path::Installer).to receive(:new) { installer }
+ end
+
+ it "calls Path::Installer's post_install" do
+ expect(installer).to receive(:post_install).once
+
+ source.post_install(double(:spec))
+ end
+ end
+
+ context "install_path" do
+ let(:uri) { "uri://to/a/repository-name" }
+ let(:hash) { Digest(:SHA1).hexdigest(uri) }
+ let(:install_path) { Pathname.new "/bundler/install/path" }
+
+ before do
+ allow(Bundler).to receive(:install_path) { install_path }
+ end
+
+ it "returns basename with uri_hash" do
+ expected = Pathname.new "#{install_path}/repository-name-#{hash[0..11]}"
+ expect(source.install_path).to eq(expected)
+ end
+ end
+
+ context "to_lock" do
+ it "returns the string with remote and type" do
+ expected = strip_whitespace <<-L
+ PLUGIN SOURCE
+ remote: #{uri}
+ type: #{type}
+ specs:
+ L
+
+ expect(source.to_lock).to eq(expected)
+ end
+
+ context "with additional options to lock" do
+ before do
+ allow(source).to receive(:options_to_lock) { { "first" => "option" } }
+ end
+
+ it "includes them" do
+ expected = strip_whitespace <<-L
+ PLUGIN SOURCE
+ remote: #{uri}
+ type: #{type}
+ first: option
+ specs:
+ L
+
+ expect(source.to_lock).to eq(expected)
+ end
+ end
+ end
+
+ describe "to_s" do
+ it "returns the string with type and uri" do
+ expect(source.to_s).to eq("plugin source for spec_type with uri uri://to/test")
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/api_spec.rb b/spec/bundler/bundler/plugin/api_spec.rb
new file mode 100644
index 0000000000..58fb908572
--- /dev/null
+++ b/spec/bundler/bundler/plugin/api_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::API do
+ context "plugin declarations" do
+ before do
+ stub_const "UserPluginClass", Class.new(Bundler::Plugin::API)
+ end
+
+ describe "#command" do
+ it "declares a command plugin with same class as handler" do
+ expect(Bundler::Plugin).
+ to receive(:add_command).with("meh", UserPluginClass).once
+
+ UserPluginClass.command "meh"
+ end
+
+ it "accepts another class as argument that handles the command" do
+ stub_const "NewClass", Class.new
+ expect(Bundler::Plugin).to receive(:add_command).with("meh", NewClass).once
+
+ UserPluginClass.command "meh", NewClass
+ end
+ end
+
+ describe "#source" do
+ it "declares a source plugin with same class as handler" do
+ expect(Bundler::Plugin).
+ to receive(:add_source).with("a_source", UserPluginClass).once
+
+ UserPluginClass.source "a_source"
+ end
+
+ it "accepts another class as argument that handles the command" do
+ stub_const "NewClass", Class.new
+ expect(Bundler::Plugin).to receive(:add_source).with("a_source", NewClass).once
+
+ UserPluginClass.source "a_source", NewClass
+ end
+ end
+
+ describe "#hook" do
+ it "accepts a block and passes it to Plugin module" do
+ foo = double("tester")
+ expect(foo).to receive(:called)
+
+ expect(Bundler::Plugin).to receive(:add_hook).with("post-foo").and_yield
+
+ Bundler::Plugin::API.hook("post-foo") { foo.called }
+ end
+ end
+ end
+
+ context "bundler interfaces provided" do
+ before do
+ stub_const "UserPluginClass", Class.new(Bundler::Plugin::API)
+ end
+
+ subject(:api) { UserPluginClass.new }
+
+ # A test of delegation
+ it "provides the Bundler's functions" do
+ expect(Bundler).to receive(:an_unknown_function).once
+
+ api.an_unknown_function
+ end
+
+ it "includes Bundler::SharedHelpers' functions" do
+ expect(Bundler::SharedHelpers).to receive(:an_unknown_helper).once
+
+ api.an_unknown_helper
+ end
+
+ context "#tmp" do
+ it "provides a tmp dir" do
+ expect(api.tmp("mytmp")).to be_directory
+ end
+
+ it "accepts multiple names for suffix" do
+ expect(api.tmp("myplugin", "download")).to be_directory
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/dsl_spec.rb b/spec/bundler/bundler/plugin/dsl_spec.rb
new file mode 100644
index 0000000000..00e39dca69
--- /dev/null
+++ b/spec/bundler/bundler/plugin/dsl_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::DSL do
+ DSL = Bundler::Plugin::DSL
+
+ subject(:dsl) { Bundler::Plugin::DSL.new }
+
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new "/" }
+ end
+
+ describe "it ignores only the methods defined in Bundler::Dsl" do
+ it "doesn't raises error for Dsl methods" do
+ expect { dsl.install_if }.not_to raise_error
+ end
+
+ it "raises error for other methods" do
+ expect { dsl.no_method }.to raise_error(DSL::PluginGemfileError)
+ end
+ end
+
+ describe "source block" do
+ it "adds #source with :type to list and also inferred_plugins list" do
+ expect(dsl).to receive(:plugin).with("bundler-source-news").once
+
+ dsl.source("some_random_url", :type => "news") {}
+
+ expect(dsl.inferred_plugins).to eq(["bundler-source-news"])
+ end
+
+ it "registers a source type plugin only once for multiple declarations" do
+ expect(dsl).to receive(:plugin).with("bundler-source-news").and_call_original.once
+
+ dsl.source("some_random_url", :type => "news") {}
+ dsl.source("another_random_url", :type => "news") {}
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/events_spec.rb b/spec/bundler/bundler/plugin/events_spec.rb
new file mode 100644
index 0000000000..28d70c6fdd
--- /dev/null
+++ b/spec/bundler/bundler/plugin/events_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::Events do
+ context "plugin events" do
+ before { Bundler::Plugin::Events.send :reset }
+
+ describe "#define" do
+ it "raises when redefining a constant" do
+ Bundler::Plugin::Events.send(:define, :TEST_EVENT, "foo")
+
+ expect do
+ Bundler::Plugin::Events.send(:define, :TEST_EVENT, "bar")
+ end.to raise_error(ArgumentError)
+ end
+
+ it "can define a new constant" do
+ Bundler::Plugin::Events.send(:define, :NEW_CONSTANT, "value")
+ expect(Bundler::Plugin::Events::NEW_CONSTANT).to eq("value")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/index_spec.rb b/spec/bundler/bundler/plugin/index_spec.rb
new file mode 100644
index 0000000000..5a7047459f
--- /dev/null
+++ b/spec/bundler/bundler/plugin/index_spec.rb
@@ -0,0 +1,204 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::Index do
+ Index = Bundler::Plugin::Index
+
+ before do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ path = lib_path(plugin_name)
+ index.register_plugin("new-plugin", path.to_s, [path.join("lib").to_s], commands, sources, hooks)
+ end
+
+ let(:plugin_name) { "new-plugin" }
+ let(:commands) { [] }
+ let(:sources) { [] }
+ let(:hooks) { [] }
+
+ subject(:index) { Index.new }
+
+ describe "#register plugin" do
+ it "is available for retrieval" do
+ expect(index.plugin_path(plugin_name)).to eq(lib_path(plugin_name))
+ end
+
+ it "load_paths is available for retrieval" do
+ expect(index.load_paths(plugin_name)).to eq([lib_path(plugin_name).join("lib").to_s])
+ end
+
+ it "is persistent" do
+ new_index = Index.new
+ expect(new_index.plugin_path(plugin_name)).to eq(lib_path(plugin_name))
+ end
+
+ it "load_paths are persistent" do
+ new_index = Index.new
+ expect(new_index.load_paths(plugin_name)).to eq([lib_path(plugin_name).join("lib").to_s])
+ end
+ end
+
+ describe "commands" do
+ let(:commands) { ["newco"] }
+
+ it "returns the plugins name on query" do
+ expect(index.command_plugin("newco")).to eq(plugin_name)
+ end
+
+ it "raises error on conflict" do
+ expect do
+ index.register_plugin("aplugin", lib_path("aplugin").to_s, lib_path("aplugin").join("lib").to_s, ["newco"], [], [])
+ end.to raise_error(Index::CommandConflict)
+ end
+
+ it "is persistent" do
+ new_index = Index.new
+ expect(new_index.command_plugin("newco")).to eq(plugin_name)
+ end
+ end
+
+ describe "source" do
+ let(:sources) { ["new_source"] }
+
+ it "returns the plugins name on query" do
+ expect(index.source_plugin("new_source")).to eq(plugin_name)
+ end
+
+ it "raises error on conflict" do
+ expect do
+ index.register_plugin("aplugin", lib_path("aplugin").to_s, lib_path("aplugin").join("lib").to_s, [], ["new_source"], [])
+ end.to raise_error(Index::SourceConflict)
+ end
+
+ it "is persistent" do
+ new_index = Index.new
+ expect(new_index.source_plugin("new_source")).to eq(plugin_name)
+ end
+ end
+
+ describe "hook" do
+ let(:hooks) { ["after-bar"] }
+
+ it "returns the plugins name on query" do
+ expect(index.hook_plugins("after-bar")).to include(plugin_name)
+ end
+
+ it "is persistent" do
+ new_index = Index.new
+ expect(new_index.hook_plugins("after-bar")).to eq([plugin_name])
+ end
+
+ it "only registers a gem once for an event" do
+ path = lib_path(plugin_name)
+ index.register_plugin(plugin_name,
+ path.to_s,
+ [path.join("lib").to_s],
+ commands,
+ sources,
+ hooks + hooks)
+ expect(index.hook_plugins("after-bar")).to eq([plugin_name])
+ end
+
+ it "is gone after unregistration" do
+ expect(index.index_file.read).to include("after-bar:\n - \"new-plugin\"\n")
+ index.unregister_plugin(plugin_name)
+ expect(index.index_file.read).to_not include("after-bar:\n - \n")
+ end
+
+ context "that are not registered" do
+ let(:file) { double("index-file") }
+
+ before do
+ index.hook_plugins("not-there")
+ allow(File).to receive(:open).and_yield(file)
+ end
+
+ it "should not save it with next registered hook" do
+ expect(file).to receive(:puts) do |content|
+ expect(content).not_to include("not-there")
+ end
+
+ index.register_plugin("aplugin", lib_path("aplugin").to_s, lib_path("aplugin").join("lib").to_s, [], [], [])
+ end
+ end
+ end
+
+ describe "global index" do
+ before do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(nil)
+
+ Bundler::Plugin.reset!
+ path = lib_path("gplugin")
+ index.register_plugin("gplugin", path.to_s, [path.join("lib").to_s], [], ["glb_source"], [])
+ end
+
+ it "skips sources" do
+ new_index = Index.new
+ expect(new_index.source_plugin("glb_source")).to be_falsy
+ end
+ end
+
+ describe "after conflict" do
+ let(:commands) { ["foo"] }
+ let(:sources) { ["bar"] }
+ let(:hooks) { ["thehook"] }
+
+ shared_examples "it cleans up" do
+ it "the path" do
+ expect(index.installed?("cplugin")).to be_falsy
+ end
+
+ it "the command" do
+ expect(index.command_plugin("xfoo")).to be_falsy
+ end
+
+ it "the source" do
+ expect(index.source_plugin("xbar")).to be_falsy
+ end
+
+ it "the hook" do
+ expect(index.hook_plugins("xthehook")).to be_empty
+ end
+ end
+
+ context "on command conflict it cleans up" do
+ before do
+ expect do
+ path = lib_path("cplugin")
+ index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["xbar"], ["xthehook"])
+ end.to raise_error(Index::CommandConflict)
+ end
+
+ include_examples "it cleans up"
+ end
+
+ context "on source conflict it cleans up" do
+ before do
+ expect do
+ path = lib_path("cplugin")
+ index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["xfoo"], ["bar"], ["xthehook"])
+ end.to raise_error(Index::SourceConflict)
+ end
+
+ include_examples "it cleans up"
+ end
+
+ context "on command and source conflict it cleans up" do
+ before do
+ expect do
+ path = lib_path("cplugin")
+ index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["bar"], ["xthehook"])
+ end.to raise_error(Index::CommandConflict)
+ end
+
+ include_examples "it cleans up"
+ end
+ end
+
+ describe "readonly disk without home" do
+ it "ignores being unable to create temp home dir" do
+ expect_any_instance_of(Bundler::Plugin::Index).to receive(:global_index_file).
+ and_raise(Bundler::GenericSystemCallError.new("foo", "bar"))
+ Bundler::Plugin::Index.new
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/installer_spec.rb b/spec/bundler/bundler/plugin/installer_spec.rb
new file mode 100644
index 0000000000..2c50ee5afc
--- /dev/null
+++ b/spec/bundler/bundler/plugin/installer_spec.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::Installer do
+ subject(:installer) { Bundler::Plugin::Installer.new }
+
+ describe "cli install" do
+ it "uses Gem.sources when non of the source is provided" do
+ sources = double(:sources)
+ Bundler.settings # initialize it before we have to touch rubygems.ext_lock
+ allow(Gem).to receive(:sources) { sources }
+
+ allow(installer).to receive(:install_rubygems).
+ with("new-plugin", [">= 0"], sources).once
+
+ installer.install("new-plugin", {})
+ end
+
+ describe "with mocked installers" do
+ let(:spec) { double(:spec) }
+ it "returns the installed spec after installing git plugins" do
+ allow(installer).to receive(:install_git).
+ and_return("new-plugin" => spec)
+
+ expect(installer.install(["new-plugin"], :git => "https://some.ran/dom")).
+ to eq("new-plugin" => spec)
+ end
+
+ it "returns the installed spec after installing local git plugins" do
+ allow(installer).to receive(:install_local_git).
+ and_return("new-plugin" => spec)
+
+ expect(installer.install(["new-plugin"], :local_git => "/phony/path/repo")).
+ to eq("new-plugin" => spec)
+ end
+
+ it "returns the installed spec after installing rubygems plugins" do
+ allow(installer).to receive(:install_rubygems).
+ and_return("new-plugin" => spec)
+
+ expect(installer.install(["new-plugin"], :source => "https://some.ran/dom")).
+ to eq("new-plugin" => spec)
+ end
+ end
+
+ describe "with actual installers" do
+ before do
+ build_repo2 do
+ build_plugin "re-plugin"
+ build_plugin "ma-plugin"
+ end
+ end
+
+ context "git plugins" do
+ before do
+ build_git "ga-plugin", :path => lib_path("ga-plugin") do |s|
+ s.write "plugins.rb"
+ end
+ end
+
+ let(:result) do
+ installer.install(["ga-plugin"], :git => file_uri_for(lib_path("ga-plugin")))
+ end
+
+ it "returns the installed spec after installing" do
+ spec = result["ga-plugin"]
+ expect(spec.full_name).to eq "ga-plugin-1.0"
+ end
+
+ it "has expected full_gem_path" do
+ rev = revision_for(lib_path("ga-plugin"))
+ expect(result["ga-plugin"].full_gem_path).
+ to eq(Bundler::Plugin.root.join("bundler", "gems", "ga-plugin-#{rev[0..11]}").to_s)
+ end
+ end
+
+ context "local git plugins" do
+ before do
+ build_git "ga-plugin", :path => lib_path("ga-plugin") do |s|
+ s.write "plugins.rb"
+ end
+ end
+
+ let(:result) do
+ installer.install(["ga-plugin"], :local_git => lib_path("ga-plugin").to_s)
+ end
+
+ it "returns the installed spec after installing" do
+ spec = result["ga-plugin"]
+ expect(spec.full_name).to eq "ga-plugin-1.0"
+ end
+
+ it "has expected full_gem_path" do
+ rev = revision_for(lib_path("ga-plugin"))
+ expect(result["ga-plugin"].full_gem_path).
+ to eq(Bundler::Plugin.root.join("bundler", "gems", "ga-plugin-#{rev[0..11]}").to_s)
+ end
+ end
+
+ context "rubygems plugins" do
+ let(:result) do
+ installer.install(["re-plugin"], :source => file_uri_for(gem_repo2))
+ end
+
+ it "returns the installed spec after installing " do
+ expect(result["re-plugin"]).to be_kind_of(Bundler::RemoteSpecification)
+ end
+
+ it "has expected full_gem_path" do
+ expect(result["re-plugin"].full_gem_path).
+ to eq(global_plugin_gem("re-plugin-1.0").to_s)
+ end
+ end
+
+ context "multiple plugins" do
+ let(:result) do
+ installer.install(["re-plugin", "ma-plugin"], :source => file_uri_for(gem_repo2))
+ end
+
+ it "returns the installed spec after installing " do
+ expect(result["re-plugin"]).to be_kind_of(Bundler::RemoteSpecification)
+ expect(result["ma-plugin"]).to be_kind_of(Bundler::RemoteSpecification)
+ end
+
+ it "has expected full_gem_path" do
+ expect(result["re-plugin"].full_gem_path).to eq(global_plugin_gem("re-plugin-1.0").to_s)
+ expect(result["ma-plugin"].full_gem_path).to eq(global_plugin_gem("ma-plugin-1.0").to_s)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/source_list_spec.rb b/spec/bundler/bundler/plugin/source_list_spec.rb
new file mode 100644
index 0000000000..64a1233dd1
--- /dev/null
+++ b/spec/bundler/bundler/plugin/source_list_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::SourceList do
+ SourceList = Bundler::Plugin::SourceList
+
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new "/" }
+ end
+
+ subject(:source_list) { SourceList.new }
+
+ describe "adding sources uses classes for plugin" do
+ it "uses Plugin::Installer::Rubygems for rubygems sources" do
+ source = source_list.
+ add_rubygems_source("remotes" => ["https://existing-rubygems.org"])
+ expect(source).to be_instance_of(Bundler::Plugin::Installer::Rubygems)
+ end
+
+ it "uses Plugin::Installer::Git for git sources" do
+ source = source_list.
+ add_git_source("uri" => "git://existing-git.org/path.git")
+ expect(source).to be_instance_of(Bundler::Plugin::Installer::Git)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb
new file mode 100644
index 0000000000..d28479cf31
--- /dev/null
+++ b/spec/bundler/bundler/plugin_spec.rb
@@ -0,0 +1,337 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin do
+ Plugin = Bundler::Plugin
+
+ let(:installer) { double(:installer) }
+ let(:index) { double(:index) }
+ let(:spec) { double(:spec) }
+ let(:spec2) { double(:spec2) }
+
+ before do
+ build_lib "new-plugin", :path => lib_path("new-plugin") do |s|
+ s.write "plugins.rb"
+ end
+
+ build_lib "another-plugin", :path => lib_path("another-plugin") do |s|
+ s.write "plugins.rb"
+ end
+
+ allow(spec).to receive(:full_gem_path).
+ and_return(lib_path("new-plugin").to_s)
+ allow(spec).to receive(:load_paths).
+ and_return([lib_path("new-plugin").join("lib").to_s])
+
+ allow(spec2).to receive(:full_gem_path).
+ and_return(lib_path("another-plugin").to_s)
+ allow(spec2).to receive(:load_paths).
+ and_return([lib_path("another-plugin").join("lib").to_s])
+
+ allow(Plugin::Installer).to receive(:new) { installer }
+ allow(Plugin).to receive(:index) { index }
+ allow(index).to receive(:register_plugin)
+ end
+
+ describe "list command" do
+ context "when no plugins are installed" do
+ before { allow(index).to receive(:installed_plugins) { [] } }
+ it "outputs no plugins installed" do
+ expect(Bundler.ui).to receive(:info).with("No plugins installed")
+ subject.list
+ end
+ end
+
+ context "with installed plugins" do
+ before do
+ allow(index).to receive(:installed_plugins) { %w[plug1 plug2] }
+ allow(index).to receive(:plugin_commands).with("plug1") { %w[c11 c12] }
+ allow(index).to receive(:plugin_commands).with("plug2") { %w[c21 c22] }
+ end
+ it "list plugins followed by commands" do
+ expected_output = "plug1\n-----\n c11\n c12\n\nplug2\n-----\n c21\n c22\n\n"
+ expect(Bundler.ui).to receive(:info).with(expected_output)
+ subject.list
+ end
+ end
+ end
+
+ describe "install command" do
+ let(:opts) { { "version" => "~> 1.0", "source" => "foo" } }
+
+ before do
+ allow(installer).to receive(:install).with(["new-plugin"], opts) do
+ { "new-plugin" => spec }
+ end
+ end
+
+ it "passes the name and options to installer" do
+ allow(index).to receive(:installed?).
+ with("new-plugin")
+ allow(installer).to receive(:install).with(["new-plugin"], opts) do
+ { "new-plugin" => spec }
+ end.once
+
+ subject.install ["new-plugin"], opts
+ end
+
+ it "validates the installed plugin" do
+ allow(index).to receive(:installed?).
+ with("new-plugin")
+ allow(subject).
+ to receive(:validate_plugin!).with(lib_path("new-plugin")).once
+
+ subject.install ["new-plugin"], opts
+ end
+
+ it "registers the plugin with index" do
+ allow(index).to receive(:installed?).
+ with("new-plugin")
+ allow(index).to receive(:register_plugin).
+ with("new-plugin", lib_path("new-plugin").to_s, [lib_path("new-plugin").join("lib").to_s], []).once
+ subject.install ["new-plugin"], opts
+ end
+
+ context "multiple plugins" do
+ it do
+ allow(installer).to receive(:install).
+ with(["new-plugin", "another-plugin"], opts) do
+ {
+ "new-plugin" => spec,
+ "another-plugin" => spec2,
+ }
+ end.once
+
+ allow(subject).to receive(:validate_plugin!).twice
+ allow(index).to receive(:installed?).twice
+ allow(index).to receive(:register_plugin).twice
+ subject.install ["new-plugin", "another-plugin"], opts
+ end
+ end
+ end
+
+ describe "evaluate gemfile for plugins" do
+ let(:definition) { double("definition") }
+ let(:builder) { double("builder") }
+ let(:gemfile) { bundled_app_gemfile }
+
+ before do
+ allow(Plugin::DSL).to receive(:new) { builder }
+ allow(builder).to receive(:eval_gemfile).with(gemfile)
+ allow(builder).to receive(:check_primary_source_safety)
+ allow(builder).to receive(:to_definition) { definition }
+ allow(builder).to receive(:inferred_plugins) { [] }
+ end
+
+ it "doesn't calls installer without any plugins" do
+ allow(definition).to receive(:dependencies) { [] }
+ allow(installer).to receive(:install_definition).never
+
+ subject.gemfile_install(gemfile)
+ end
+
+ context "with dependencies" do
+ let(:plugin_specs) do
+ {
+ "new-plugin" => spec,
+ "another-plugin" => spec2,
+ }
+ end
+
+ before do
+ allow(index).to receive(:installed?) { nil }
+ allow(definition).to receive(:dependencies) { [Bundler::Dependency.new("new-plugin", ">=0"), Bundler::Dependency.new("another-plugin", ">=0")] }
+ allow(installer).to receive(:install_definition) { plugin_specs }
+ end
+
+ it "should validate and register the plugins" do
+ expect(subject).to receive(:validate_plugin!).twice
+ expect(subject).to receive(:register_plugin).twice
+
+ subject.gemfile_install(gemfile)
+ end
+
+ it "should pass the optional plugins to #register_plugin" do
+ allow(builder).to receive(:inferred_plugins) { ["another-plugin"] }
+
+ expect(subject).to receive(:register_plugin).
+ with("new-plugin", spec, false).once
+
+ expect(subject).to receive(:register_plugin).
+ with("another-plugin", spec2, true).once
+
+ subject.gemfile_install(gemfile)
+ end
+ end
+ end
+
+ describe "#command?" do
+ it "returns true value for commands in index" do
+ allow(index).
+ to receive(:command_plugin).with("newcommand") { "my-plugin" }
+ result = subject.command? "newcommand"
+ expect(result).to be_truthy
+ end
+
+ it "returns false value for commands not in index" do
+ allow(index).to receive(:command_plugin).with("newcommand") { nil }
+ result = subject.command? "newcommand"
+ expect(result).to be_falsy
+ end
+ end
+
+ describe "#exec_command" do
+ it "raises UndefinedCommandError when command is not found" do
+ allow(index).to receive(:command_plugin).with("newcommand") { nil }
+ expect { subject.exec_command("newcommand", []) }.
+ to raise_error(Plugin::UndefinedCommandError)
+ end
+ end
+
+ describe "#source?" do
+ it "returns true value for sources in index" do
+ allow(index).
+ to receive(:command_plugin).with("foo-source") { "my-plugin" }
+ result = subject.command? "foo-source"
+ expect(result).to be_truthy
+ end
+
+ it "returns false value for source not in index" do
+ allow(index).to receive(:command_plugin).with("foo-source") { nil }
+ result = subject.command? "foo-source"
+ expect(result).to be_falsy
+ end
+ end
+
+ describe "#source" do
+ it "raises UnknownSourceError when source is not found" do
+ allow(index).to receive(:source_plugin).with("bar") { nil }
+ expect { subject.source("bar") }.
+ to raise_error(Plugin::UnknownSourceError)
+ end
+
+ it "loads the plugin, if not loaded" do
+ allow(index).to receive(:source_plugin).with("foo-bar") { "plugin_name" }
+
+ expect(subject).to receive(:load_plugin).with("plugin_name")
+ subject.source("foo-bar")
+ end
+
+ it "returns the class registered with #add_source" do
+ allow(index).to receive(:source_plugin).with("foo") { "plugin_name" }
+ stub_const "NewClass", Class.new
+
+ subject.add_source("foo", NewClass)
+ expect(subject.source("foo")).to be(NewClass)
+ end
+ end
+
+ describe "#source_from_lock" do
+ it "returns instance of registered class initialized with locked opts" do
+ opts = { "type" => "l_source", "remote" => "xyz", "other" => "random" }
+ allow(index).to receive(:source_plugin).with("l_source") { "plugin_name" }
+
+ stub_const "SClass", Class.new
+ s_instance = double(:s_instance)
+ subject.add_source("l_source", SClass)
+
+ expect(SClass).to receive(:new).
+ with(hash_including("type" => "l_source", "uri" => "xyz", "other" => "random")) { s_instance }
+ expect(subject.source_from_lock(opts)).to be(s_instance)
+ end
+ end
+
+ describe "#root" do
+ context "in app dir" do
+ before do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ end
+
+ it "returns plugin dir in app .bundle path" do
+ expect(subject.root).to eq(bundled_app.join(".bundle/plugin"))
+ end
+ end
+
+ context "outside app dir" do
+ before do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(nil)
+ end
+
+ it "returns plugin dir in global bundle path" do
+ expect(subject.root).to eq(home.join(".bundle/plugin"))
+ end
+ end
+ end
+
+ describe "#add_hook" do
+ it "raises an ArgumentError on an unregistered event" do
+ ran = false
+ expect do
+ Plugin.add_hook("unregistered-hook") { ran = true }
+ end.to raise_error(ArgumentError)
+ expect(ran).to be(false)
+ end
+ end
+
+ describe "#hook" do
+ before do
+ path = lib_path("foo-plugin")
+ build_lib "foo-plugin", :path => path do |s|
+ s.write "plugins.rb", code
+ end
+
+ Bundler::Plugin::Events.send(:reset)
+ Bundler::Plugin::Events.send(:define, :EVENT1, "event-1")
+ Bundler::Plugin::Events.send(:define, :EVENT2, "event-2")
+
+ allow(index).to receive(:hook_plugins).with(Bundler::Plugin::Events::EVENT1).
+ and_return(["foo-plugin", "", nil])
+ allow(index).to receive(:hook_plugins).with(Bundler::Plugin::Events::EVENT2).
+ and_return(["foo-plugin"])
+ allow(index).to receive(:plugin_path).with("foo-plugin").and_return(path)
+ allow(index).to receive(:load_paths).with("foo-plugin").and_return([])
+ end
+
+ let(:code) { <<-RUBY }
+ Bundler::Plugin::API.hook("event-1") { puts "hook for event 1" }
+ RUBY
+
+ it "raises an ArgumentError on an unregistered event" do
+ expect do
+ Plugin.hook("unregistered-hook")
+ end.to raise_error(ArgumentError)
+ end
+
+ it "executes the hook" do
+ expect do
+ Plugin.hook(Bundler::Plugin::Events::EVENT1)
+ end.to output("hook for event 1\n").to_stdout
+ end
+
+ context "single plugin declaring more than one hook" do
+ let(:code) { <<-RUBY }
+ Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT1) {}
+ Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT2) {}
+ puts "loaded"
+ RUBY
+
+ it "evals plugins.rb once" do
+ expect do
+ Plugin.hook(Bundler::Plugin::Events::EVENT1)
+ Plugin.hook(Bundler::Plugin::Events::EVENT2)
+ end.to output("loaded\n").to_stdout
+ end
+ end
+
+ context "a block is passed" do
+ let(:code) { <<-RUBY }
+ Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT1) { |&blk| blk.call }
+ RUBY
+
+ it "is passed to the hook" do
+ expect do
+ Plugin.hook(Bundler::Plugin::Events::EVENT1) { puts "win" }
+ end.to output("win\n").to_stdout
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/remote_specification_spec.rb b/spec/bundler/bundler/remote_specification_spec.rb
new file mode 100644
index 0000000000..921a47a2d3
--- /dev/null
+++ b/spec/bundler/bundler/remote_specification_spec.rb
@@ -0,0 +1,187 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::RemoteSpecification do
+ let(:name) { "foo" }
+ let(:version) { Gem::Version.new("1.0.0") }
+ let(:platform) { Gem::Platform::RUBY }
+ let(:spec_fetcher) { double(:spec_fetcher) }
+
+ subject { described_class.new(name, version, platform, spec_fetcher) }
+
+ it "is Comparable" do
+ expect(described_class.ancestors).to include(Comparable)
+ end
+
+ it "can match platforms" do
+ expect(described_class.ancestors).to include(Bundler::MatchPlatform)
+ end
+
+ describe "#fetch_platform" do
+ let(:remote_spec) { double(:remote_spec, :platform => "jruby") }
+
+ before { allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) }
+
+ it "should return the spec platform" do
+ expect(subject.fetch_platform).to eq("jruby")
+ end
+ end
+
+ describe "#full_name" do
+ context "when platform is ruby" do
+ it "should return the spec name and version" do
+ expect(subject.full_name).to eq("foo-1.0.0")
+ end
+ end
+
+ context "when platform is nil" do
+ let(:platform) { nil }
+
+ it "should return the spec name and version" do
+ expect(subject.full_name).to eq("foo-1.0.0")
+ end
+ end
+
+ context "when platform is a non-ruby platform" do
+ let(:platform) { "jruby" }
+
+ it "should return the spec name, version, and platform" do
+ expect(subject.full_name).to eq("foo-1.0.0-java")
+ end
+ end
+ end
+
+ describe "#<=>" do
+ let(:other_name) { name }
+ let(:other_version) { version }
+ let(:other_platform) { platform }
+ let(:other_spec_fetcher) { spec_fetcher }
+
+ shared_examples_for "a comparison" do
+ context "which exactly matches" do
+ it "returns 0" do
+ expect(subject <=> other).to eq(0)
+ end
+ end
+
+ context "which is different by name" do
+ let(:other_name) { "a" }
+ it "returns 1" do
+ expect(subject <=> other).to eq(1)
+ end
+ end
+
+ context "which has a lower version" do
+ let(:other_version) { Gem::Version.new("0.9.0") }
+ it "returns 1" do
+ expect(subject <=> other).to eq(1)
+ end
+ end
+
+ context "which has a higher version" do
+ let(:other_version) { Gem::Version.new("1.1.0") }
+ it "returns -1" do
+ expect(subject <=> other).to eq(-1)
+ end
+ end
+
+ context "which has a different platform" do
+ let(:other_platform) { Gem::Platform.new("x86-mswin32") }
+ it "returns -1" do
+ expect(subject <=> other).to eq(-1)
+ end
+ end
+ end
+
+ context "comparing another Bundler::RemoteSpecification" do
+ let(:other) do
+ Bundler::RemoteSpecification.new(other_name, other_version,
+ other_platform, nil)
+ end
+
+ it_should_behave_like "a comparison"
+ end
+
+ context "comparing a Gem::Specification" do
+ let(:other) do
+ Gem::Specification.new(other_name, other_version).tap do |s|
+ s.platform = other_platform
+ end
+ end
+
+ it_should_behave_like "a comparison"
+ end
+
+ context "comparing a non sortable object" do
+ let(:other) { Object.new }
+ let(:remote_spec) { double(:remote_spec, :platform => "jruby") }
+
+ before do
+ allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec)
+ allow(remote_spec).to receive(:<=>).and_return(nil)
+ end
+
+ it "should use default object comparison" do
+ expect(subject <=> other).to eq(nil)
+ end
+ end
+ end
+
+ describe "#__swap__" do
+ let(:spec) { double(:spec, :dependencies => []) }
+ let(:new_spec) { double(:new_spec, :dependencies => [], :runtime_dependencies => []) }
+
+ before { subject.instance_variable_set(:@_remote_specification, spec) }
+
+ it "should replace remote specification with the passed spec" do
+ expect(subject.instance_variable_get(:@_remote_specification)).to be(spec)
+ subject.__swap__(new_spec)
+ expect(subject.instance_variable_get(:@_remote_specification)).to be(new_spec)
+ end
+ end
+
+ describe "#sort_obj" do
+ context "when platform is ruby" do
+ it "should return a sorting delegate array with name, version, and -1" do
+ expect(subject.sort_obj).to match_array(["foo", version, -1])
+ end
+ end
+
+ context "when platform is not ruby" do
+ let(:platform) { "jruby" }
+
+ it "should return a sorting delegate array with name, version, and 1" do
+ expect(subject.sort_obj).to match_array(["foo", version, 1])
+ end
+ end
+ end
+
+ describe "method missing" do
+ context "and is present in Gem::Specification" do
+ let(:remote_spec) { double(:remote_spec, :authors => "abcd") }
+
+ before do
+ allow(subject).to receive(:_remote_specification).and_return(remote_spec)
+ expect(subject.methods.map(&:to_sym)).not_to include(:authors)
+ end
+
+ it "should send through to Gem::Specification" do
+ expect(subject.authors).to eq("abcd")
+ end
+ end
+ end
+
+ describe "respond to missing?" do
+ context "and is present in Gem::Specification" do
+ let(:remote_spec) { double(:remote_spec, :authors => "abcd") }
+
+ before do
+ allow(subject).to receive(:_remote_specification).and_return(remote_spec)
+ expect(subject.methods.map(&:to_sym)).not_to include(:authors)
+ end
+
+ it "should send through to Gem::Specification" do
+ expect(subject.respond_to?(:authors)).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/resolver/candidate_spec.rb b/spec/bundler/bundler/resolver/candidate_spec.rb
new file mode 100644
index 0000000000..cd52c867c4
--- /dev/null
+++ b/spec/bundler/bundler/resolver/candidate_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Resolver::Candidate do
+ it "compares fine" do
+ version1 = described_class.new("1.12.5", :specs => [Gem::Specification.new("foo", "1.12.5") {|s| s.platform = Gem::Platform::RUBY }])
+ version2 = described_class.new("1.12.5") # passing no specs creates a platform specific candidate, so sorts higher
+
+ expect(version2 >= version1).to be true
+
+ expect(version1.generic! == version2.generic!).to be true
+ expect(version1.platform_specific! == version2.platform_specific!).to be true
+
+ expect(version1.platform_specific! >= version2.generic!).to be true
+ expect(version2.platform_specific! >= version1.generic!).to be true
+
+ version1 = described_class.new("1.12.5", :specs => [Gem::Specification.new("foo", "1.12.5") {|s| s.platform = Gem::Platform::RUBY }])
+ version2 = described_class.new("1.12.5", :specs => [Gem::Specification.new("foo", "1.12.5") {|s| s.platform = Gem::Platform::X64_LINUX }])
+
+ expect(version2 >= version1).to be true
+ end
+end
diff --git a/spec/bundler/bundler/retry_spec.rb b/spec/bundler/bundler/retry_spec.rb
new file mode 100644
index 0000000000..b893580d72
--- /dev/null
+++ b/spec/bundler/bundler/retry_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Retry do
+ it "return successful result if no errors" do
+ attempts = 0
+ result = Bundler::Retry.new(nil, nil, 3).attempt do
+ attempts += 1
+ :success
+ end
+ expect(result).to eq(:success)
+ expect(attempts).to eq(1)
+ end
+
+ it "returns the first valid result" do
+ jobs = [proc { raise "foo" }, proc { :bar }, proc { raise "foo" }]
+ attempts = 0
+ result = Bundler::Retry.new(nil, nil, 3).attempt do
+ attempts += 1
+ jobs.shift.call
+ end
+ expect(result).to eq(:bar)
+ expect(attempts).to eq(2)
+ end
+
+ it "raises the last error" do
+ errors = [StandardError, StandardError, StandardError, Bundler::GemfileNotFound]
+ attempts = 0
+ expect do
+ Bundler::Retry.new(nil, nil, 3).attempt do
+ attempts += 1
+ raise errors.shift
+ end
+ end.to raise_error(Bundler::GemfileNotFound)
+ expect(attempts).to eq(4)
+ end
+
+ it "raises exceptions" do
+ error = Bundler::GemfileNotFound
+ attempts = 0
+ expect do
+ Bundler::Retry.new(nil, error).attempt do
+ attempts += 1
+ raise error
+ end
+ end.to raise_error(error)
+ expect(attempts).to eq(1)
+ end
+
+ context "logging" do
+ let(:error) { Bundler::GemfileNotFound }
+ let(:failure_message) { "Retrying test due to error (2/2): #{error} #{error}" }
+
+ context "with debugging on" do
+ it "print error message with newline" do
+ allow(Bundler.ui).to receive(:debug?).and_return(true)
+ expect(Bundler.ui).to_not receive(:info)
+ expect(Bundler.ui).to receive(:warn).with(failure_message, true)
+
+ expect do
+ Bundler::Retry.new("test", [], 1).attempt do
+ raise error
+ end
+ end.to raise_error(error)
+ end
+ end
+
+ context "with debugging off" do
+ it "print error message with newlines" do
+ allow(Bundler.ui).to receive(:debug?).and_return(false)
+ expect(Bundler.ui).to receive(:info).with("").twice
+ expect(Bundler.ui).to receive(:warn).with(failure_message, false)
+
+ expect do
+ Bundler::Retry.new("test", [], 1).attempt do
+ raise error
+ end
+ end.to raise_error(error)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/ruby_dsl_spec.rb b/spec/bundler/bundler/ruby_dsl_spec.rb
new file mode 100644
index 0000000000..0ba55e949f
--- /dev/null
+++ b/spec/bundler/bundler/ruby_dsl_spec.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+require "bundler/ruby_dsl"
+
+RSpec.describe Bundler::RubyDsl do
+ class MockDSL
+ include Bundler::RubyDsl
+
+ attr_reader :ruby_version
+ end
+
+ let(:dsl) { MockDSL.new }
+ let(:ruby_version) { "2.0.0" }
+ let(:ruby_version_arg) { ruby_version }
+ let(:version) { "2.0.0" }
+ let(:engine) { "jruby" }
+ let(:engine_version) { "9000" }
+ let(:patchlevel) { "100" }
+ let(:options) do
+ { :patchlevel => patchlevel,
+ :engine => engine,
+ :engine_version => engine_version }
+ end
+
+ let(:invoke) do
+ proc do
+ args = []
+ args << Array(ruby_version_arg) if ruby_version_arg
+ args << options
+
+ dsl.ruby(*args)
+ end
+ end
+
+ subject do
+ invoke.call
+ dsl.ruby_version
+ end
+
+ describe "#ruby_version" do
+ shared_examples_for "it stores the ruby version" do
+ it "stores the version" do
+ expect(subject.versions).to eq(Array(ruby_version))
+ expect(subject.gem_version.version).to eq(version)
+ end
+
+ it "stores the engine details" do
+ expect(subject.engine).to eq(engine)
+ expect(subject.engine_versions).to eq(Array(engine_version))
+ end
+
+ it "stores the patchlevel" do
+ expect(subject.patchlevel).to eq(patchlevel)
+ end
+ end
+
+ context "with a plain version" do
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with a single requirement" do
+ let(:ruby_version) { ">= 2.0.0" }
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with two requirements in the same string" do
+ let(:ruby_version) { ">= 2.0.0, < 3.0" }
+ it "raises an error" do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context "with two requirements" do
+ let(:ruby_version) { ["~> 2.0.0", "> 2.0.1"] }
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with multiple engine versions" do
+ let(:engine_version) { ["> 200", "< 300"] }
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with no options hash" do
+ let(:invoke) { proc { dsl.ruby(ruby_version) } }
+
+ let(:patchlevel) { nil }
+ let(:engine) { "ruby" }
+ let(:engine_version) { version }
+
+ it_behaves_like "it stores the ruby version"
+
+ context "and with multiple requirements" do
+ let(:ruby_version) { ["~> 2.0.0", "> 2.0.1"] }
+ let(:engine_version) { ruby_version }
+ it_behaves_like "it stores the ruby version"
+ end
+ end
+
+ context "with a file option" do
+ let(:options) { { :file => "foo" } }
+ let(:version) { "3.2.2" }
+ let(:ruby_version) { "3.2.2" }
+ let(:ruby_version_arg) { nil }
+ let(:engine_version) { version }
+ let(:patchlevel) { nil }
+ let(:engine) { "ruby" }
+ before { allow(Bundler).to receive(:read_file).with("foo").and_return("#{version}\n") }
+
+ it_behaves_like "it stores the ruby version"
+
+ context "and a version" do
+ let(:ruby_version_arg) { "2.0.0" }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(Bundler::GemfileError, "Cannot specify version when using the file option")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/ruby_version_spec.rb b/spec/bundler/bundler/ruby_version_spec.rb
new file mode 100644
index 0000000000..39d0571361
--- /dev/null
+++ b/spec/bundler/bundler/ruby_version_spec.rb
@@ -0,0 +1,500 @@
+# frozen_string_literal: true
+
+require "bundler/ruby_version"
+
+RSpec.describe "Bundler::RubyVersion and its subclasses" do
+ let(:version) { "2.0.0" }
+ let(:patchlevel) { "645" }
+ let(:engine) { "jruby" }
+ let(:engine_version) { "2.0.1" }
+
+ describe Bundler::RubyVersion do
+ subject { Bundler::RubyVersion.new(version, patchlevel, engine, engine_version) }
+
+ let(:ruby_version) { subject }
+ let(:other_version) { version }
+ let(:other_patchlevel) { patchlevel }
+ let(:other_engine) { engine }
+ let(:other_engine_version) { engine_version }
+ let(:other_ruby_version) { Bundler::RubyVersion.new(other_version, other_patchlevel, other_engine, other_engine_version) }
+
+ describe "#initialize" do
+ context "no engine is passed" do
+ let(:engine) { nil }
+
+ it "should set ruby as the engine" do
+ expect(subject.engine).to eq("ruby")
+ end
+ end
+
+ context "no engine_version is passed" do
+ let(:engine_version) { nil }
+
+ it "should set engine version as the passed version" do
+ expect(subject.engine_versions).to eq(["2.0.0"])
+ end
+ end
+
+ context "with engine in symbol" do
+ let(:engine) { :jruby }
+
+ it "should coerce engine to string" do
+ expect(subject.engine).to eq("jruby")
+ end
+ end
+
+ context "is called with multiple requirements" do
+ let(:version) { ["<= 2.0.0", "> 1.9.3"] }
+ let(:engine_version) { nil }
+
+ it "sets the versions" do
+ expect(subject.versions).to eq(version)
+ end
+
+ it "sets the engine versions" do
+ expect(subject.engine_versions).to eq(version)
+ end
+ end
+
+ context "is called with multiple engine requirements" do
+ let(:engine_version) { [">= 2.0", "< 2.3"] }
+
+ it "sets the engine versions" do
+ expect(subject.engine_versions).to eq(engine_version)
+ end
+ end
+ end
+
+ describe ".from_string" do
+ shared_examples_for "returning" do
+ it "returns the original RubyVersion" do
+ expect(described_class.from_string(subject.to_s)).to eq(subject)
+ end
+ end
+
+ include_examples "returning"
+
+ context "no patchlevel" do
+ let(:patchlevel) { nil }
+
+ include_examples "returning"
+ end
+
+ context "engine is ruby" do
+ let(:engine) { "ruby" }
+ let(:engine_version) { version }
+
+ include_examples "returning"
+ end
+
+ context "with multiple requirements" do
+ let(:engine_version) { ["> 9", "< 11"] }
+ let(:version) { ["> 8", "< 10"] }
+ let(:patchlevel) { nil }
+
+ it "returns nil" do
+ expect(described_class.from_string(subject.to_s)).to be_nil
+ end
+ end
+ end
+
+ describe "#to_s" do
+ it "should return info string with the ruby version, patchlevel, engine, and engine version" do
+ expect(subject.to_s).to eq("ruby 2.0.0p645 (jruby 2.0.1)")
+ end
+
+ context "no patchlevel" do
+ let(:patchlevel) { nil }
+
+ it "should return info string with the version, engine, and engine version" do
+ expect(subject.to_s).to eq("ruby 2.0.0 (jruby 2.0.1)")
+ end
+ end
+
+ context "engine is ruby" do
+ let(:engine) { "ruby" }
+
+ it "should return info string with the ruby version and patchlevel" do
+ expect(subject.to_s).to eq("ruby 2.0.0p645")
+ end
+ end
+
+ context "with multiple requirements" do
+ let(:engine_version) { ["> 9", "< 11"] }
+ let(:version) { ["> 8", "< 10"] }
+ let(:patchlevel) { nil }
+
+ it "should return info string with all requirements" do
+ expect(subject.to_s).to eq("ruby > 8, < 10 (jruby > 9, < 11)")
+ end
+ end
+ end
+
+ describe "#==" do
+ shared_examples_for "two ruby versions are not equal" do
+ it "should return false" do
+ expect(subject).to_not eq(other_ruby_version)
+ end
+ end
+
+ context "the versions, pathlevels, engines, and engine_versions match" do
+ it "should return true" do
+ expect(subject).to eq(other_ruby_version)
+ end
+ end
+
+ context "the versions do not match" do
+ let(:other_version) { "1.21.6" }
+
+ it_behaves_like "two ruby versions are not equal"
+ end
+
+ context "the patchlevels do not match" do
+ let(:other_patchlevel) { "21" }
+
+ it_behaves_like "two ruby versions are not equal"
+ end
+
+ context "the engines do not match" do
+ let(:other_engine) { "ruby" }
+
+ it_behaves_like "two ruby versions are not equal"
+ end
+
+ context "the engine versions do not match" do
+ let(:other_engine_version) { "1.11.2" }
+
+ it_behaves_like "two ruby versions are not equal"
+ end
+ end
+
+ describe "#host" do
+ before do
+ allow(RbConfig::CONFIG).to receive(:[]).with("host_cpu").and_return("x86_64")
+ allow(RbConfig::CONFIG).to receive(:[]).with("host_vendor").and_return("apple")
+ allow(RbConfig::CONFIG).to receive(:[]).with("host_os").and_return("darwin14.5.0")
+ end
+
+ it "should return an info string with the host cpu, vendor, and os" do
+ expect(subject.host).to eq("x86_64-apple-darwin14.5.0")
+ end
+
+ it "memoizes the info string with the host cpu, vendor, and os" do
+ expect(RbConfig::CONFIG).to receive(:[]).with("host_cpu").once.and_call_original
+ expect(RbConfig::CONFIG).to receive(:[]).with("host_vendor").once.and_call_original
+ expect(RbConfig::CONFIG).to receive(:[]).with("host_os").once.and_call_original
+ 2.times { ruby_version.host }
+ end
+ end
+
+ describe "#gem_version" do
+ let(:gem_version) { "2.0.0" }
+ let(:gem_version_obj) { Gem::Version.new(gem_version) }
+
+ shared_examples_for "it parses the version from the requirement string" do |version|
+ let(:version) { version }
+ it "should return the underlying version" do
+ expect(ruby_version.gem_version).to eq(gem_version_obj)
+ expect(ruby_version.gem_version.version).to eq(gem_version)
+ end
+ end
+
+ it_behaves_like "it parses the version from the requirement string", "2.0.0"
+ it_behaves_like "it parses the version from the requirement string", ">= 2.0.0"
+ it_behaves_like "it parses the version from the requirement string", "~> 2.0.0"
+ it_behaves_like "it parses the version from the requirement string", "< 2.0.0"
+ it_behaves_like "it parses the version from the requirement string", "= 2.0.0"
+ it_behaves_like "it parses the version from the requirement string", ["> 2.0.0", "< 2.4.5"]
+ end
+
+ describe "#diff" do
+ let(:engine) { "ruby" }
+
+ shared_examples_for "there is a difference in the engines" do
+ it "should return a tuple with :engine and the two different engines" do
+ expect(ruby_version.diff(other_ruby_version)).to eq([:engine, engine, other_engine])
+ end
+ end
+
+ shared_examples_for "there is a difference in the versions" do
+ it "should return a tuple with :version and the two different versions" do
+ expect(ruby_version.diff(other_ruby_version)).to eq([:version, Array(version).join(", "), Array(other_version).join(", ")])
+ end
+ end
+
+ shared_examples_for "there is a difference in the engine versions" do
+ it "should return a tuple with :engine_version and the two different engine versions" do
+ expect(ruby_version.diff(other_ruby_version)).to eq([:engine_version, Array(engine_version).join(", "), Array(other_engine_version).join(", ")])
+ end
+ end
+
+ shared_examples_for "there is a difference in the patchlevels" do
+ it "should return a tuple with :patchlevel and the two different patchlevels" do
+ expect(ruby_version.diff(other_ruby_version)).to eq([:patchlevel, patchlevel, other_patchlevel])
+ end
+ end
+
+ shared_examples_for "there are no differences" do
+ it "should return nil" do
+ expect(ruby_version.diff(other_ruby_version)).to be_nil
+ end
+ end
+
+ context "all things match exactly" do
+ it_behaves_like "there are no differences"
+ end
+
+ context "detects engine discrepancies first" do
+ let(:other_version) { "2.0.1" }
+ let(:other_patchlevel) { "643" }
+ let(:other_engine) { "rbx" }
+ let(:other_engine_version) { "2.0.0" }
+
+ it_behaves_like "there is a difference in the engines"
+ end
+
+ context "detects version discrepancies second" do
+ let(:other_version) { "2.0.1" }
+ let(:other_patchlevel) { "643" }
+ let(:other_engine_version) { "2.0.0" }
+
+ it_behaves_like "there is a difference in the versions"
+ end
+
+ context "detects version discrepancies with multiple requirements second" do
+ let(:other_version) { "2.0.1" }
+ let(:other_patchlevel) { "643" }
+ let(:other_engine_version) { "2.0.0" }
+
+ let(:version) { ["> 2.0.0", "< 1.0.0"] }
+
+ it_behaves_like "there is a difference in the versions"
+ end
+
+ context "detects engine version discrepancies third" do
+ let(:other_patchlevel) { "643" }
+ let(:other_engine_version) { "2.0.0" }
+
+ it_behaves_like "there is a difference in the engine versions"
+ end
+
+ context "detects engine version discrepancies with multiple requirements third" do
+ let(:other_patchlevel) { "643" }
+ let(:other_engine_version) { "2.0.0" }
+
+ let(:engine_version) { ["> 2.0.0", "< 1.0.0"] }
+
+ it_behaves_like "there is a difference in the engine versions"
+ end
+
+ context "detects patchlevel discrepancies last" do
+ let(:other_patchlevel) { "643" }
+
+ it_behaves_like "there is a difference in the patchlevels"
+ end
+
+ context "successfully matches gem requirements" do
+ let(:version) { ">= 2.0.0" }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { "2.0.0" }
+ let(:other_patchlevel) { "642" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.0.5" }
+
+ it_behaves_like "there are no differences"
+ end
+
+ context "successfully matches multiple gem requirements" do
+ let(:version) { [">= 2.0.0", "< 2.4.5"] }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { ["~> 2.0.1", "< 2.4.5"] }
+ let(:other_version) { "2.0.0" }
+ let(:other_patchlevel) { "642" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.0.5" }
+
+ it_behaves_like "there are no differences"
+ end
+
+ context "successfully detects bad gem requirements with versions with multiple requirements" do
+ let(:version) { ["~> 2.0.0", "< 2.0.5"] }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { "2.0.5" }
+ let(:other_patchlevel) { "642" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.0.5" }
+
+ it_behaves_like "there is a difference in the versions"
+ end
+
+ context "successfully detects bad gem requirements with versions" do
+ let(:version) { "~> 2.0.0" }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { "2.1.0" }
+ let(:other_patchlevel) { "642" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.0.5" }
+
+ it_behaves_like "there is a difference in the versions"
+ end
+
+ context "successfully detects bad gem requirements with patchlevels" do
+ let(:version) { ">= 2.0.0" }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { "2.0.0" }
+ let(:other_patchlevel) { "645" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.0.5" }
+
+ it_behaves_like "there is a difference in the patchlevels"
+ end
+
+ context "successfully detects bad gem requirements with engine versions" do
+ let(:version) { ">= 2.0.0" }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { "2.0.0" }
+ let(:other_patchlevel) { "642" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.1.0" }
+
+ it_behaves_like "there is a difference in the engine versions"
+ end
+
+ context "with a patchlevel of -1" do
+ let(:version) { ">= 2.0.0" }
+ let(:patchlevel) { "-1" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { version }
+ let(:other_engine) { engine }
+ let(:other_engine_version) { engine_version }
+
+ context "and comparing with another patchlevel of -1" do
+ let(:other_patchlevel) { patchlevel }
+
+ it_behaves_like "there are no differences"
+ end
+
+ context "and comparing with a patchlevel that is not -1" do
+ let(:other_patchlevel) { "642" }
+
+ it_behaves_like "there is a difference in the patchlevels"
+ end
+ end
+ end
+
+ describe "#system" do
+ subject { Bundler::RubyVersion.system }
+
+ let(:bundler_system_ruby_version) { subject }
+
+ around do |example|
+ if Bundler::RubyVersion.instance_variable_defined?("@system")
+ begin
+ old_ruby_version = Bundler::RubyVersion.instance_variable_get("@system")
+ Bundler::RubyVersion.remove_instance_variable("@system")
+ example.run
+ ensure
+ Bundler::RubyVersion.instance_variable_set("@system", old_ruby_version)
+ end
+ else
+ begin
+ example.run
+ ensure
+ Bundler::RubyVersion.remove_instance_variable("@system")
+ end
+ end
+ end
+
+ it "should return an instance of Bundler::RubyVersion" do
+ expect(subject).to be_kind_of(Bundler::RubyVersion)
+ end
+
+ it "memoizes the instance of Bundler::RubyVersion" do
+ expect(Bundler::RubyVersion).to receive(:new).once.and_call_original
+ 2.times { subject }
+ end
+
+ describe "#version" do
+ it "should return the value of Gem.ruby_version as a string" do
+ expect(subject.versions).to eq([Gem.ruby_version.to_s])
+ end
+ end
+
+ describe "#engine" do
+ before { stub_const("RUBY_ENGINE", "jruby") }
+ before { stub_const("RUBY_ENGINE_VERSION", "2.1.1") }
+
+ it "should return a copy of the value of RUBY_ENGINE" do
+ expect(subject.engine).to eq("jruby")
+ expect(subject.engine).to_not be(RUBY_ENGINE)
+ end
+ end
+
+ describe "#engine_version" do
+ context "engine is ruby" do
+ before do
+ allow(Gem).to receive(:ruby_version).and_return(Gem::Version.new("2.2.4"))
+ stub_const("RUBY_ENGINE", "ruby")
+ end
+
+ it "should return the value of Gem.ruby_version as a string" do
+ expect(bundler_system_ruby_version.engine_versions).to eq(["2.2.4"])
+ end
+ end
+
+ context "engine is rbx" do
+ before do
+ stub_const("RUBY_ENGINE", "rbx")
+ stub_const("RUBY_ENGINE_VERSION", "2.0.0")
+ end
+
+ it "should return a copy of the value of RUBY_ENGINE_VERSION" do
+ expect(bundler_system_ruby_version.engine_versions).to eq(["2.0.0"])
+ expect(bundler_system_ruby_version.engine_versions.first).to_not be(RUBY_ENGINE_VERSION)
+ end
+ end
+
+ context "engine is jruby" do
+ before do
+ stub_const("RUBY_ENGINE", "jruby")
+ stub_const("RUBY_ENGINE_VERSION", "2.1.1")
+ end
+
+ it "should return a copy of the value of RUBY_ENGINE_VERSION" do
+ expect(subject.engine_versions).to eq(["2.1.1"])
+ expect(bundler_system_ruby_version.engine_versions.first).to_not be(RUBY_ENGINE_VERSION)
+ end
+ end
+
+ context "engine is some other ruby engine" do
+ before do
+ stub_const("RUBY_ENGINE", "not_supported_ruby_engine")
+ stub_const("RUBY_ENGINE_VERSION", "1.2.3")
+ end
+
+ it "returns RUBY_ENGINE_VERSION" do
+ expect(bundler_system_ruby_version.engine_versions).to eq(["1.2.3"])
+ end
+ end
+ end
+
+ describe "#patchlevel" do
+ it "should return a string with the value of RUBY_PATCHLEVEL" do
+ expect(subject.patchlevel).to eq(RUBY_PATCHLEVEL.to_s)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/rubygems_integration_spec.rb b/spec/bundler/bundler/rubygems_integration_spec.rb
new file mode 100644
index 0000000000..182aa3646a
--- /dev/null
+++ b/spec/bundler/bundler/rubygems_integration_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::RubygemsIntegration do
+ it "uses the same chdir lock as rubygems" do
+ expect(Bundler.rubygems.ext_lock).to eq(Gem::Ext::Builder::CHDIR_MONITOR)
+ end
+
+ context "#validate" do
+ let(:spec) do
+ Gem::Specification.new do |s|
+ s.name = "to-validate"
+ s.version = "1.0.0"
+ s.loaded_from = __FILE__
+ end
+ end
+ subject { Bundler.rubygems.validate(spec) }
+
+ it "validates with packaging mode disabled" do
+ expect(spec).to receive(:validate).with(false)
+ subject
+ end
+
+ context "with an invalid spec" do
+ before do
+ expect(spec).to receive(:validate).with(false).
+ and_raise(Gem::InvalidSpecificationException.new("TODO is not an author"))
+ end
+
+ it "should raise a Gem::InvalidSpecificationException and produce a helpful warning message" do
+ expect { subject }.to raise_error(Gem::InvalidSpecificationException,
+ "The gemspec at #{__FILE__} is not valid. "\
+ "Please fix this gemspec.\nThe validation error was 'TODO is not an author'\n")
+ end
+ end
+ end
+
+ describe "#download_gem" do
+ let(:bundler_retry) { double(Bundler::Retry) }
+ let(:uri) { Bundler::URI.parse("https://foo.bar") }
+ let(:cache_dir) { "#{Gem.path.first}/cache" }
+ let(:spec) do
+ spec = Gem::Specification.new("Foo", Gem::Version.new("2.5.2"))
+ spec.remote = Bundler::Source::Rubygems::Remote.new(uri.to_s)
+ spec
+ end
+ let(:fetcher) { double("gem_remote_fetcher") }
+
+ it "successfully downloads gem with retries" do
+ expect(Bundler.rubygems).to receive(:gem_remote_fetcher).and_return(fetcher)
+ expect(fetcher).to receive(:headers=).with({ "X-Gemfile-Source" => "https://foo.bar" })
+ expect(Bundler::Retry).to receive(:new).with("download gem from #{uri}/").
+ and_return(bundler_retry)
+ expect(bundler_retry).to receive(:attempts).and_yield
+ expect(fetcher).to receive(:cache_update_path)
+
+ Bundler.rubygems.download_gem(spec, uri, cache_dir)
+ end
+ end
+
+ describe "#fetch_all_remote_specs" do
+ let(:uri) { "https://example.com" }
+ let(:fetcher) { double("gem_remote_fetcher") }
+ let(:specs_response) { Marshal.dump(["specs"]) }
+ let(:prerelease_specs_response) { Marshal.dump(["prerelease_specs"]) }
+
+ context "when a rubygems source mirror is set" do
+ let(:orig_uri) { Bundler::URI("http://zombo.com") }
+ let(:remote_with_mirror) { double("remote", :uri => uri, :original_uri => orig_uri) }
+
+ it "sets the 'X-Gemfile-Source' header containing the original source" do
+ expect(Bundler.rubygems).to receive(:gem_remote_fetcher).twice.and_return(fetcher)
+ expect(fetcher).to receive(:headers=).with({ "X-Gemfile-Source" => "http://zombo.com" }).twice
+ expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(specs_response)
+ expect(fetcher).to receive(:fetch_path).with(uri + "prerelease_specs.4.8.gz").and_return(prerelease_specs_response)
+ result = Bundler.rubygems.fetch_all_remote_specs(remote_with_mirror)
+ expect(result).to eq(%w[specs prerelease_specs])
+ end
+ end
+
+ context "when there is no rubygems source mirror set" do
+ let(:remote_no_mirror) { double("remote", :uri => uri, :original_uri => nil) }
+
+ it "does not set the 'X-Gemfile-Source' header" do
+ expect(Bundler.rubygems).to receive(:gem_remote_fetcher).twice.and_return(fetcher)
+ expect(fetcher).to_not receive(:headers=)
+ expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(specs_response)
+ expect(fetcher).to receive(:fetch_path).with(uri + "prerelease_specs.4.8.gz").and_return(prerelease_specs_response)
+ result = Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror)
+ expect(result).to eq(%w[specs prerelease_specs])
+ end
+ end
+
+ context "when loading an unexpected class" do
+ let(:remote_no_mirror) { double("remote", :uri => uri, :original_uri => nil) }
+ let(:unexpected_specs_response) { Marshal.dump(3) }
+
+ it "raises a MarshalError error" do
+ expect(Bundler.rubygems).to receive(:gem_remote_fetcher).once.and_return(fetcher)
+ expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(unexpected_specs_response)
+ expect { Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror) }.to raise_error(Bundler::MarshalError, /unexpected class/i)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/settings/validator_spec.rb b/spec/bundler/bundler/settings/validator_spec.rb
new file mode 100644
index 0000000000..e4ffd89435
--- /dev/null
+++ b/spec/bundler/bundler/settings/validator_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Settings::Validator do
+ describe ".validate!" do
+ def validate!(key, value, settings)
+ transformed_key = Bundler.settings.key_for(key)
+ if value.nil?
+ settings.delete(transformed_key)
+ else
+ settings[transformed_key] = value
+ end
+ described_class.validate!(key, value, settings)
+ settings
+ end
+
+ it "path and path.system are mutually exclusive" do
+ expect(validate!("path", "bundle", {})).to eq("BUNDLE_PATH" => "bundle")
+ expect(validate!("path", "bundle", "BUNDLE_PATH__SYSTEM" => false)).to eq("BUNDLE_PATH" => "bundle")
+ expect(validate!("path", "bundle", "BUNDLE_PATH__SYSTEM" => true)).to eq("BUNDLE_PATH" => "bundle")
+ expect(validate!("path", nil, "BUNDLE_PATH__SYSTEM" => true)).to eq("BUNDLE_PATH__SYSTEM" => true)
+ expect(validate!("path", nil, "BUNDLE_PATH__SYSTEM" => false)).to eq("BUNDLE_PATH__SYSTEM" => false)
+ expect(validate!("path", nil, {})).to eq({})
+
+ expect(validate!("path.system", true, "BUNDLE_PATH" => "bundle")).to eq("BUNDLE_PATH__SYSTEM" => true)
+ expect(validate!("path.system", false, "BUNDLE_PATH" => "bundle")).to eq("BUNDLE_PATH" => "bundle", "BUNDLE_PATH__SYSTEM" => false)
+ expect(validate!("path.system", nil, "BUNDLE_PATH" => "bundle")).to eq("BUNDLE_PATH" => "bundle")
+ expect(validate!("path.system", true, {})).to eq("BUNDLE_PATH__SYSTEM" => true)
+ expect(validate!("path.system", false, {})).to eq("BUNDLE_PATH__SYSTEM" => false)
+ expect(validate!("path.system", nil, {})).to eq({})
+ end
+
+ it "a group cannot be in both `with` & `without` simultaneously" do
+ expect do
+ validate!("with", "", {})
+ validate!("with", nil, {})
+ validate!("with", "", "BUNDLE_WITHOUT" => "a")
+ validate!("with", nil, "BUNDLE_WITHOUT" => "a")
+ validate!("with", "b:c", "BUNDLE_WITHOUT" => "a")
+
+ validate!("without", "", {})
+ validate!("without", nil, {})
+ validate!("without", "", "BUNDLE_WITH" => "a")
+ validate!("without", nil, "BUNDLE_WITH" => "a")
+ validate!("without", "b:c", "BUNDLE_WITH" => "a")
+ end.not_to raise_error
+
+ expect { validate!("with", "b:c", "BUNDLE_WITHOUT" => "c:d") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip
+ Setting `with` to "b:c" failed:
+ - a group cannot be in both `with` & `without` simultaneously
+ - `without` is current set to [:c, :d]
+ - the `c` groups conflict
+ EOS
+
+ expect { validate!("without", "b:c", "BUNDLE_WITH" => "c:d") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip
+ Setting `without` to "b:c" failed:
+ - a group cannot be in both `with` & `without` simultaneously
+ - `with` is current set to [:c, :d]
+ - the `c` groups conflict
+ EOS
+ end
+ end
+
+ describe described_class::Rule do
+ let(:keys) { %w[key] }
+ let(:description) { "rule description" }
+ let(:validate) { proc { raise "validate called!" } }
+ subject(:rule) { described_class.new(keys, description, &validate) }
+
+ describe "#validate!" do
+ it "calls the block" do
+ expect { rule.validate!("key", nil, {}) }.to raise_error(RuntimeError, /validate called!/)
+ end
+ end
+
+ describe "#fail!" do
+ it "raises with a helpful message" do
+ expect { subject.fail!("key", "value", "reason1", "reason2") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip
+ Setting `key` to "value" failed:
+ - rule description
+ - reason1
+ - reason2
+ EOS
+ end
+ end
+
+ describe "#set" do
+ it "works when the value has not changed" do
+ allow(Bundler.ui).to receive(:info).never
+
+ subject.set({}, "key", nil)
+ subject.set({ "BUNDLE_KEY" => "value" }, "key", "value")
+ end
+
+ it "prints out when the value is changing" do
+ settings = {}
+
+ expect(Bundler.ui).to receive(:info).with("Setting `key` to \"value\", since rule description, reason1")
+ subject.set(settings, "key", "value", "reason1")
+ expect(settings).to eq("BUNDLE_KEY" => "value")
+
+ expect(Bundler.ui).to receive(:info).with("Setting `key` to \"value2\", since rule description, reason2")
+ subject.set(settings, "key", "value2", "reason2")
+ expect(settings).to eq("BUNDLE_KEY" => "value2")
+
+ expect(Bundler.ui).to receive(:info).with("Setting `key` to nil, since rule description, reason3")
+ subject.set(settings, "key", nil, "reason3")
+ expect(settings).to eq({})
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/settings_spec.rb b/spec/bundler/bundler/settings_spec.rb
new file mode 100644
index 0000000000..4636993d9f
--- /dev/null
+++ b/spec/bundler/bundler/settings_spec.rb
@@ -0,0 +1,337 @@
+# frozen_string_literal: true
+
+require "bundler/settings"
+
+RSpec.describe Bundler::Settings do
+ subject(:settings) { described_class.new(bundled_app) }
+
+ describe "#set_local" do
+ context "when the local config file is not found" do
+ subject(:settings) { described_class.new(nil) }
+
+ it "raises a GemfileNotFound error with explanation" do
+ expect { subject.set_local("foo", "bar") }.
+ to raise_error(Bundler::GemfileNotFound, "Could not locate Gemfile")
+ end
+ end
+ end
+
+ describe "load_config" do
+ let(:hash) do
+ {
+ "build.thrift" => "--with-cppflags=-D_FORTIFY_SOURCE=0",
+ "build.libv8" => "--with-system-v8",
+ "build.therubyracer" => "--with-v8-dir",
+ "build.pg" => "--with-pg-config=/usr/local/Cellar/postgresql92/9.2.8_1/bin/pg_config",
+ "gem.coc" => "false",
+ "gem.mit" => "false",
+ "gem.test" => "minitest",
+ "thingy" => <<-EOS.tr("\n", " "),
+--asdf --fdsa --ty=oh man i hope this doesn't break bundler because
+that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
+--very-important-option=DontDeleteRoo
+--very-important-option=DontDeleteRoo
+--very-important-option=DontDeleteRoo
+--very-important-option=DontDeleteRoo
+ EOS
+ "xyz" => "zyx",
+ }
+ end
+
+ before do
+ hash.each do |key, value|
+ settings.set_local key, value
+ end
+ end
+
+ it "can load the config" do
+ loaded = settings.send(:load_config, bundled_app("config"))
+ expected = Hash[hash.map do |k, v|
+ [settings.send(:key_for, k), v.to_s]
+ end]
+ expect(loaded).to eq(expected)
+ end
+
+ context "when BUNDLE_IGNORE_CONFIG is set" do
+ before { ENV["BUNDLE_IGNORE_CONFIG"] = "TRUE" }
+
+ it "ignores the config" do
+ loaded = settings.send(:load_config, bundled_app("config"))
+ expect(loaded).to eq({})
+ end
+ end
+ end
+
+ describe "#global_config_file" do
+ context "when $HOME is not accessible" do
+ it "does not raise" do
+ expect(Bundler.rubygems).to receive(:user_home).twice.and_return(nil)
+
+ expect(subject.send(:global_config_file)).to be_nil
+ end
+ end
+ end
+
+ describe "#[]" do
+ context "when the local config file is not found" do
+ subject(:settings) { described_class.new }
+
+ it "does not raise" do
+ expect do
+ subject["foo"]
+ end.not_to raise_error
+ end
+ end
+
+ context "when not set" do
+ context "when default value present" do
+ it "retrieves value" do
+ expect(settings[:retry]).to be 3
+ end
+ end
+
+ it "returns nil" do
+ expect(settings[:buttermilk]).to be nil
+ end
+ end
+
+ context "when is boolean" do
+ it "returns a boolean" do
+ settings.set_local :frozen, "true"
+ expect(settings[:frozen]).to be true
+ end
+ context "when specific gem is configured" do
+ it "returns a boolean" do
+ settings.set_local "ignore_messages.foobar", "true"
+ expect(settings["ignore_messages.foobar"]).to be true
+ end
+ end
+ end
+
+ context "when is number" do
+ it "returns a number" do
+ settings.set_local :ssl_verify_mode, "1"
+ expect(settings[:ssl_verify_mode]).to be 1
+ end
+ end
+
+ context "when it's not possible to write to the file" do
+ it "raises an PermissionError with explanation" do
+ expect(::Bundler::FileUtils).to receive(:mkdir_p).with(settings.send(:local_config_file).dirname).
+ and_raise(Errno::EACCES)
+ expect { settings.set_local :frozen, "1" }.
+ to raise_error(Bundler::PermissionError, /config/)
+ end
+ end
+ end
+
+ describe "#temporary" do
+ it "reset after used" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+
+ Bundler.settings.set_command_option :no_install, true
+
+ Bundler.settings.temporary(:no_install => false) do
+ expect(Bundler.settings[:no_install]).to eq false
+ end
+
+ expect(Bundler.settings[:no_install]).to eq true
+
+ Bundler.settings.set_command_option :no_install, nil
+ end
+
+ it "returns the return value of the block" do
+ ret = Bundler.settings.temporary({}) { :ret }
+ expect(ret).to eq :ret
+ end
+
+ context "when called without a block" do
+ it "leaves the setting changed" do
+ Bundler.settings.temporary(:foo => :random)
+ expect(Bundler.settings[:foo]).to eq "random"
+ end
+
+ it "returns nil" do
+ expect(Bundler.settings.temporary(:foo => :bar)).to be_nil
+ end
+ end
+ end
+
+ describe "#set_global" do
+ context "when it's not possible to write to the file" do
+ it "raises an PermissionError with explanation" do
+ expect(::Bundler::FileUtils).to receive(:mkdir_p).with(settings.send(:global_config_file).dirname).
+ and_raise(Errno::EACCES)
+ expect { settings.set_global(:frozen, "1") }.
+ to raise_error(Bundler::PermissionError, %r{\.bundle/config})
+ end
+ end
+ end
+
+ describe "#pretty_values_for" do
+ it "prints the converted value rather than the raw string" do
+ bool_key = described_class::BOOL_KEYS.first
+ settings.set_local(bool_key, "false")
+ expect(subject.pretty_values_for(bool_key)).to eq [
+ "Set for your local app (#{bundled_app("config")}): false",
+ ]
+ end
+ end
+
+ describe "#mirror_for" do
+ let(:uri) { Bundler::URI("https://rubygems.org/") }
+
+ context "with no configured mirror" do
+ it "returns the original URI" do
+ expect(settings.mirror_for(uri)).to eq(uri)
+ end
+
+ it "converts a string parameter to a URI" do
+ expect(settings.mirror_for("https://rubygems.org/")).to eq(uri)
+ end
+ end
+
+ context "with a configured mirror" do
+ let(:mirror_uri) { Bundler::URI("https://rubygems-mirror.org/") }
+
+ before { settings.set_local "mirror.https://rubygems.org/", mirror_uri.to_s }
+
+ it "returns the mirror URI" do
+ expect(settings.mirror_for(uri)).to eq(mirror_uri)
+ end
+
+ it "converts a string parameter to a URI" do
+ expect(settings.mirror_for("https://rubygems.org/")).to eq(mirror_uri)
+ end
+
+ it "normalizes the URI" do
+ expect(settings.mirror_for("https://rubygems.org")).to eq(mirror_uri)
+ end
+
+ it "is case insensitive" do
+ expect(settings.mirror_for("HTTPS://RUBYGEMS.ORG/")).to eq(mirror_uri)
+ end
+
+ context "with a file URI" do
+ let(:mirror_uri) { Bundler::URI("file:/foo/BAR/baz/qUx/") }
+
+ it "returns the mirror URI" do
+ expect(settings.mirror_for(uri)).to eq(mirror_uri)
+ end
+
+ it "converts a string parameter to a URI" do
+ expect(settings.mirror_for("file:/foo/BAR/baz/qUx/")).to eq(mirror_uri)
+ end
+
+ it "normalizes the URI" do
+ expect(settings.mirror_for("file:/foo/BAR/baz/qUx")).to eq(mirror_uri)
+ end
+ end
+ end
+ end
+
+ describe "#credentials_for" do
+ let(:uri) { Bundler::URI("https://gemserver.example.org/") }
+ let(:credentials) { "username:password" }
+
+ context "with no configured credentials" do
+ it "returns nil" do
+ expect(settings.credentials_for(uri)).to be_nil
+ end
+ end
+
+ context "with credentials configured by URL" do
+ before { settings.set_local "https://gemserver.example.org/", credentials }
+
+ it "returns the configured credentials" do
+ expect(settings.credentials_for(uri)).to eq(credentials)
+ end
+ end
+
+ context "with credentials configured by hostname" do
+ before { settings.set_local "gemserver.example.org", credentials }
+
+ it "returns the configured credentials" do
+ expect(settings.credentials_for(uri)).to eq(credentials)
+ end
+ end
+ end
+
+ describe "URI normalization" do
+ it "normalizes HTTP URIs in credentials configuration" do
+ settings.set_local "http://gemserver.example.org", "username:password"
+ expect(settings.all).to include("http://gemserver.example.org/")
+ end
+
+ it "normalizes HTTPS URIs in credentials configuration" do
+ settings.set_local "https://gemserver.example.org", "username:password"
+ expect(settings.all).to include("https://gemserver.example.org/")
+ end
+
+ it "normalizes HTTP URIs in mirror configuration" do
+ settings.set_local "mirror.http://rubygems.org", "http://rubygems-mirror.org"
+ expect(settings.all).to include("mirror.http://rubygems.org/")
+ end
+
+ it "normalizes HTTPS URIs in mirror configuration" do
+ settings.set_local "mirror.https://rubygems.org", "http://rubygems-mirror.org"
+ expect(settings.all).to include("mirror.https://rubygems.org/")
+ end
+
+ it "does not normalize other config keys that happen to contain 'http'" do
+ settings.set_local "local.httparty", home("httparty")
+ expect(settings.all).to include("local.httparty")
+ end
+
+ it "does not normalize other config keys that happen to contain 'https'" do
+ settings.set_local "local.httpsmarty", home("httpsmarty")
+ expect(settings.all).to include("local.httpsmarty")
+ end
+
+ it "reads older keys without trailing slashes" do
+ settings.set_local "mirror.https://rubygems.org", "http://rubygems-mirror.org"
+ expect(settings.mirror_for("https://rubygems.org/")).to eq(
+ Bundler::URI("http://rubygems-mirror.org/")
+ )
+ end
+
+ it "normalizes URIs with a fallback_timeout option" do
+ settings.set_local "mirror.https://rubygems.org/.fallback_timeout", "true"
+ expect(settings.all).to include("mirror.https://rubygems.org/.fallback_timeout")
+ end
+
+ it "normalizes URIs with a fallback_timeout option without a trailing slash" do
+ settings.set_local "mirror.https://rubygems.org.fallback_timeout", "true"
+ expect(settings.all).to include("mirror.https://rubygems.org/.fallback_timeout")
+ end
+ end
+
+ describe "BUNDLE_ keys format" do
+ let(:settings) { described_class.new(bundled_app(".bundle")) }
+
+ it "converts older keys without double underscore" do
+ config("BUNDLE_MY__PERSONAL.RACK" => "~/Work/git/rack")
+ expect(settings["my.personal.rack"]).to eq("~/Work/git/rack")
+ end
+
+ it "converts older keys without trailing slashes and double underscore" do
+ config("BUNDLE_MIRROR__HTTPS://RUBYGEMS.ORG" => "http://rubygems-mirror.org")
+ expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org")
+ end
+
+ it "converts older keys with dashes" do
+ config("BUNDLE_MY-PERSONAL-SERVER__ORG" => "my-personal-server.org")
+ expect(Bundler.ui).to receive(:warn).with(
+ "Your #{bundled_app(".bundle/config")} config includes `BUNDLE_MY-PERSONAL-SERVER__ORG`, which contains the dash character (`-`).\n" \
+ "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \
+ "Please edit #{bundled_app(".bundle/config")} and replace any dashes in configuration keys with a triple underscore (`___`)."
+ )
+ expect(settings["my-personal-server.org"]).to eq("my-personal-server.org")
+ end
+
+ it "reads newer keys format properly" do
+ config("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://rubygems-mirror.org")
+ expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org")
+ end
+ end
+end
diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb
new file mode 100644
index 0000000000..3c6536c4eb
--- /dev/null
+++ b/spec/bundler/bundler/shared_helpers_spec.rb
@@ -0,0 +1,506 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::SharedHelpers do
+ let(:ext_lock_double) { double(:ext_lock) }
+
+ before do
+ pwd_stub
+ allow(Bundler.rubygems).to receive(:ext_lock).and_return(ext_lock_double)
+ allow(ext_lock_double).to receive(:synchronize) {|&block| block.call }
+ end
+
+ let(:pwd_stub) { allow(subject).to receive(:pwd).and_return(bundled_app) }
+
+ subject { Bundler::SharedHelpers }
+
+ describe "#default_gemfile" do
+ before { ENV["BUNDLE_GEMFILE"] = "/path/Gemfile" }
+
+ context "Gemfile is present" do
+ let(:expected_gemfile_path) { Pathname.new("/path/Gemfile").expand_path }
+
+ it "returns the Gemfile path" do
+ expect(subject.default_gemfile).to eq(expected_gemfile_path)
+ end
+ end
+
+ context "Gemfile is not present" do
+ before { ENV["BUNDLE_GEMFILE"] = nil }
+
+ it "raises a GemfileNotFound error" do
+ expect { subject.default_gemfile }.to raise_error(
+ Bundler::GemfileNotFound, "Could not locate Gemfile"
+ )
+ end
+ end
+
+ context "Gemfile is not an absolute path" do
+ before { ENV["BUNDLE_GEMFILE"] = "Gemfile" }
+
+ let(:expected_gemfile_path) { Pathname.new("Gemfile").expand_path }
+
+ it "returns the Gemfile path" do
+ expect(subject.default_gemfile).to eq(expected_gemfile_path)
+ end
+ end
+ end
+
+ describe "#default_lockfile" do
+ context "gemfile is gems.rb" do
+ let(:gemfile_path) { Pathname.new("/path/gems.rb") }
+ let(:expected_lockfile_path) { Pathname.new("/path/gems.locked") }
+
+ before { allow(subject).to receive(:default_gemfile).and_return(gemfile_path) }
+
+ it "returns the gems.locked path" do
+ expect(subject.default_lockfile).to eq(expected_lockfile_path)
+ end
+ end
+
+ context "is a regular Gemfile" do
+ let(:gemfile_path) { Pathname.new("/path/Gemfile") }
+ let(:expected_lockfile_path) { Pathname.new("/path/Gemfile.lock") }
+
+ before { allow(subject).to receive(:default_gemfile).and_return(gemfile_path) }
+
+ it "returns the lock file path" do
+ expect(subject.default_lockfile).to eq(expected_lockfile_path)
+ end
+ end
+ end
+
+ describe "#default_bundle_dir" do
+ context ".bundle does not exist" do
+ it "returns nil" do
+ expect(subject.default_bundle_dir).to be_nil
+ end
+ end
+
+ context ".bundle is global .bundle" do
+ let(:global_rubygems_dir) { Pathname.new(bundled_app) }
+
+ before do
+ Dir.mkdir bundled_app(".bundle")
+ allow(Bundler.rubygems).to receive(:user_home).and_return(global_rubygems_dir)
+ end
+
+ it "returns nil" do
+ expect(subject.default_bundle_dir).to be_nil
+ end
+ end
+
+ context ".bundle is not global .bundle" do
+ let(:global_rubygems_dir) { Pathname.new("/path/rubygems") }
+ let(:expected_bundle_dir_path) { Pathname.new("#{bundled_app}/.bundle") }
+
+ before do
+ Dir.mkdir bundled_app(".bundle")
+ allow(Bundler.rubygems).to receive(:user_home).and_return(global_rubygems_dir)
+ end
+
+ it "returns the .bundle path" do
+ expect(subject.default_bundle_dir).to eq(expected_bundle_dir_path)
+ end
+ end
+ end
+
+ describe "#in_bundle?" do
+ it "calls the find_gemfile method" do
+ expect(subject).to receive(:find_gemfile)
+ subject.in_bundle?
+ end
+
+ shared_examples_for "correctly determines whether to return a Gemfile path" do
+ context "currently in directory with a Gemfile" do
+ before { FileUtils.touch(bundled_app_gemfile) }
+ after { FileUtils.rm(bundled_app_gemfile) }
+
+ it "returns path of the bundle Gemfile" do
+ expect(subject.in_bundle?).to eq("#{bundled_app}/Gemfile")
+ end
+ end
+
+ context "currently in directory without a Gemfile" do
+ it "returns nil" do
+ expect(subject.in_bundle?).to be_nil
+ end
+ end
+ end
+
+ context "ENV['BUNDLE_GEMFILE'] set" do
+ before { ENV["BUNDLE_GEMFILE"] = "/path/Gemfile" }
+
+ it "returns ENV['BUNDLE_GEMFILE']" do
+ expect(subject.in_bundle?).to eq("/path/Gemfile")
+ end
+ end
+
+ context "ENV['BUNDLE_GEMFILE'] not set" do
+ before { ENV["BUNDLE_GEMFILE"] = nil }
+
+ it_behaves_like "correctly determines whether to return a Gemfile path"
+ end
+
+ context "ENV['BUNDLE_GEMFILE'] is blank" do
+ before { ENV["BUNDLE_GEMFILE"] = "" }
+
+ it_behaves_like "correctly determines whether to return a Gemfile path"
+ end
+ end
+
+ describe "#chdir" do
+ let(:op_block) { proc { Dir.mkdir "nested_dir" } }
+
+ before { Dir.mkdir bundled_app("chdir_test_dir") }
+
+ it "executes the passed block while in the specified directory" do
+ subject.chdir(bundled_app("chdir_test_dir"), &op_block)
+ expect(bundled_app("chdir_test_dir/nested_dir")).to exist
+ end
+ end
+
+ describe "#pwd" do
+ let(:pwd_stub) { nil }
+
+ it "returns the current absolute path" do
+ expect(subject.pwd).to eq(source_root)
+ end
+ end
+
+ describe "#with_clean_git_env" do
+ let(:with_clean_git_env_block) { proc { Dir.mkdir bundled_app("with_clean_git_env_test_dir") } }
+
+ before do
+ ENV["GIT_DIR"] = "ORIGINAL_ENV_GIT_DIR"
+ ENV["GIT_WORK_TREE"] = "ORIGINAL_ENV_GIT_WORK_TREE"
+ end
+
+ it "executes the passed block" do
+ subject.with_clean_git_env(&with_clean_git_env_block)
+ expect(bundled_app("with_clean_git_env_test_dir")).to exist
+ end
+
+ context "when a block is passed" do
+ let(:with_clean_git_env_block) do
+ proc do
+ Dir.mkdir bundled_app("git_dir_test_dir") unless ENV["GIT_DIR"].nil?
+ Dir.mkdir bundled_app("git_work_tree_test_dir") unless ENV["GIT_WORK_TREE"].nil?
+ end end
+
+ it "uses a fresh git env for execution" do
+ subject.with_clean_git_env(&with_clean_git_env_block)
+ expect(bundled_app("git_dir_test_dir")).to_not exist
+ expect(bundled_app("git_work_tree_test_dir")).to_not exist
+ end
+ end
+
+ context "passed block does not throw errors" do
+ let(:with_clean_git_env_block) do
+ proc do
+ ENV["GIT_DIR"] = "NEW_ENV_GIT_DIR"
+ ENV["GIT_WORK_TREE"] = "NEW_ENV_GIT_WORK_TREE"
+ end end
+
+ it "restores the git env after" do
+ subject.with_clean_git_env(&with_clean_git_env_block)
+ expect(ENV["GIT_DIR"]).to eq("ORIGINAL_ENV_GIT_DIR")
+ expect(ENV["GIT_WORK_TREE"]).to eq("ORIGINAL_ENV_GIT_WORK_TREE")
+ end
+ end
+
+ context "passed block throws errors" do
+ let(:with_clean_git_env_block) do
+ proc do
+ ENV["GIT_DIR"] = "NEW_ENV_GIT_DIR"
+ ENV["GIT_WORK_TREE"] = "NEW_ENV_GIT_WORK_TREE"
+ raise RuntimeError.new
+ end end
+
+ it "restores the git env after" do
+ expect { subject.with_clean_git_env(&with_clean_git_env_block) }.to raise_error(RuntimeError)
+ expect(ENV["GIT_DIR"]).to eq("ORIGINAL_ENV_GIT_DIR")
+ expect(ENV["GIT_WORK_TREE"]).to eq("ORIGINAL_ENV_GIT_WORK_TREE")
+ end
+ end
+ end
+
+ describe "#set_bundle_environment" do
+ before do
+ ENV["BUNDLE_GEMFILE"] = "Gemfile"
+ end
+
+ shared_examples_for "ENV['PATH'] gets set correctly" do
+ before { Dir.mkdir bundled_app(".bundle") }
+
+ it "ensures bundle bin path is in ENV['PATH']" do
+ subject.set_bundle_environment
+ paths = ENV["PATH"].split(File::PATH_SEPARATOR)
+ expect(paths).to include("#{Bundler.bundle_path}/bin")
+ end
+ end
+
+ shared_examples_for "ENV['RUBYOPT'] gets set correctly" do
+ it "ensures -rbundler/setup is at the beginning of ENV['RUBYOPT']" do
+ subject.set_bundle_environment
+ expect(ENV["RUBYOPT"].split(" ")).to start_with("-r#{source_lib_dir}/bundler/setup")
+ end
+ end
+
+ shared_examples_for "ENV['BUNDLER_SETUP'] gets set correctly" do
+ it "ensures bundler/setup is set in ENV['BUNDLER_SETUP']" do
+ skip "Does not play well with DidYouMean being a bundled gem instead of a default gem in Ruby 2.6" if RUBY_VERSION < "2.7"
+
+ subject.set_bundle_environment
+ expect(ENV["BUNDLER_SETUP"]).to eq("#{source_lib_dir}/bundler/setup")
+ end
+ end
+
+ shared_examples_for "ENV['RUBYLIB'] gets set correctly" do
+ let(:ruby_lib_path) { "stubbed_ruby_lib_dir" }
+
+ before do
+ allow(subject).to receive(:bundler_ruby_lib).and_return(ruby_lib_path)
+ end
+
+ it "ensures bundler's ruby version lib path is in ENV['RUBYLIB']" do
+ subject.set_bundle_environment
+ paths = (ENV["RUBYLIB"]).split(File::PATH_SEPARATOR)
+ expect(paths).to include(ruby_lib_path)
+ end
+ end
+
+ it "calls the appropriate set methods" do
+ expect(subject).to receive(:set_bundle_variables)
+ expect(subject).to receive(:set_path)
+ expect(subject).to receive(:set_rubyopt)
+ expect(subject).to receive(:set_rubylib)
+ subject.set_bundle_environment
+ end
+
+ it "ignores if bundler_ruby_lib is same as rubylibdir" do
+ allow(subject).to receive(:bundler_ruby_lib).and_return(RbConfig::CONFIG["rubylibdir"])
+
+ subject.set_bundle_environment
+
+ paths = (ENV["RUBYLIB"]).split(File::PATH_SEPARATOR)
+ expect(paths.count(RbConfig::CONFIG["rubylibdir"])).to eq(0)
+ end
+
+ it "exits if bundle path contains the unix-like path separator" do
+ if Gem.respond_to?(:path_separator)
+ allow(Gem).to receive(:path_separator).and_return(":")
+ else
+ stub_const("File::PATH_SEPARATOR", ":")
+ end
+ allow(Bundler).to receive(:bundle_path) { Pathname.new("so:me/dir/bin") }
+ expect { subject.send(:validate_bundle_path) }.to raise_error(
+ Bundler::PathError,
+ "Your bundle path contains text matching \":\", which is the " \
+ "path separator for your system. Bundler cannot " \
+ "function correctly when the Bundle path contains the " \
+ "system's PATH separator. Please change your " \
+ "bundle path to not match \":\".\nYour current bundle " \
+ "path is '#{Bundler.bundle_path}'."
+ )
+ end
+
+ context "with a jruby path_separator regex" do
+ # In versions of jruby that supported ruby 1.8, the path separator was the standard File::PATH_SEPARATOR
+ let(:regex) { Regexp.new("(?<!jar:file|jar|file|classpath|uri:classloader|uri|http|https):") }
+ it "does not exit if bundle path is the standard uri path" do
+ allow(Bundler.rubygems).to receive(:path_separator).and_return(regex)
+ allow(Bundler).to receive(:bundle_path) { Pathname.new("uri:classloader:/WEB-INF/gems") }
+ expect { subject.send(:validate_bundle_path) }.not_to raise_error
+ end
+
+ it "exits if bundle path contains another directory" do
+ allow(Bundler.rubygems).to receive(:path_separator).and_return(regex)
+ allow(Bundler).to receive(:bundle_path) {
+ Pathname.new("uri:classloader:/WEB-INF/gems:other/dir")
+ }
+
+ expect { subject.send(:validate_bundle_path) }.to raise_error(
+ Bundler::PathError,
+ "Your bundle path contains text matching " \
+ "/(?<!jar:file|jar|file|classpath|uri:classloader|uri|http|https):/, which is the " \
+ "path separator for your system. Bundler cannot " \
+ "function correctly when the Bundle path contains the " \
+ "system's PATH separator. Please change your " \
+ "bundle path to not match " \
+ "/(?<!jar:file|jar|file|classpath|uri:classloader|uri|http|https):/." \
+ "\nYour current bundle path is '#{Bundler.bundle_path}'."
+ )
+ end
+ end
+
+ context "ENV['PATH'] does not exist" do
+ before { ENV.delete("PATH") }
+
+ it_behaves_like "ENV['PATH'] gets set correctly"
+ end
+
+ context "ENV['PATH'] is empty" do
+ before { ENV["PATH"] = "" }
+
+ it_behaves_like "ENV['PATH'] gets set correctly"
+ end
+
+ context "ENV['PATH'] exists" do
+ before { ENV["PATH"] = "/some_path/bin" }
+
+ it_behaves_like "ENV['PATH'] gets set correctly"
+ end
+
+ context "ENV['PATH'] already contains the bundle bin path" do
+ let(:bundle_path) { "#{Bundler.bundle_path}/bin" }
+
+ before do
+ ENV["PATH"] = bundle_path
+ end
+
+ it_behaves_like "ENV['PATH'] gets set correctly"
+
+ it "ENV['PATH'] should only contain one instance of bundle bin path" do
+ subject.set_bundle_environment
+ paths = (ENV["PATH"]).split(File::PATH_SEPARATOR)
+ expect(paths.count(bundle_path)).to eq(1)
+ end
+ end
+
+ context "ENV['RUBYOPT'] does not exist" do
+ before { ENV.delete("RUBYOPT") }
+
+ it_behaves_like "ENV['RUBYOPT'] gets set correctly"
+ end
+
+ context "ENV['RUBYOPT'] exists without -rbundler/setup" do
+ before { ENV["RUBYOPT"] = "-I/some_app_path/lib" }
+
+ it_behaves_like "ENV['RUBYOPT'] gets set correctly"
+ end
+
+ context "ENV['RUBYOPT'] exists and contains -rbundler/setup" do
+ before { ENV["RUBYOPT"] = "-rbundler/setup" }
+
+ it_behaves_like "ENV['RUBYOPT'] gets set correctly"
+ end
+
+ context "ENV['RUBYLIB'] does not exist" do
+ before { ENV.delete("RUBYLIB") }
+
+ it_behaves_like "ENV['RUBYLIB'] gets set correctly"
+ end
+
+ context "ENV['RUBYLIB'] is empty" do
+ before { ENV["PATH"] = "" }
+
+ it_behaves_like "ENV['RUBYLIB'] gets set correctly"
+ end
+
+ context "ENV['RUBYLIB'] exists" do
+ before { ENV["PATH"] = "/some_path/bin" }
+
+ it_behaves_like "ENV['RUBYLIB'] gets set correctly"
+ end
+
+ context "bundle executable in ENV['BUNDLE_BIN_PATH'] does not exist" do
+ before { ENV["BUNDLE_BIN_PATH"] = "/does/not/exist" }
+ before { Bundler.rubygems.replace_bin_path [] }
+
+ it "sets BUNDLE_BIN_PATH to the bundle executable file" do
+ subject.set_bundle_environment
+ bin_path = ENV["BUNDLE_BIN_PATH"]
+ expect(bin_path).to eq(bindir.join("bundle").to_s)
+ expect(File.exist?(bin_path)).to be true
+ end
+ end
+
+ context "ENV['RUBYLIB'] already contains the bundler's ruby version lib path" do
+ let(:ruby_lib_path) { "stubbed_ruby_lib_dir" }
+
+ before do
+ ENV["RUBYLIB"] = ruby_lib_path
+ end
+
+ it_behaves_like "ENV['RUBYLIB'] gets set correctly"
+
+ it "ENV['RUBYLIB'] should only contain one instance of bundler's ruby version lib path" do
+ subject.set_bundle_environment
+ paths = (ENV["RUBYLIB"]).split(File::PATH_SEPARATOR)
+ expect(paths.count(ruby_lib_path)).to eq(1)
+ end
+ end
+ end
+
+ describe "#filesystem_access" do
+ context "system has proper permission access" do
+ let(:file_op_block) { proc {|path| FileUtils.mkdir_p(path) } }
+
+ it "performs the operation in the passed block" do
+ subject.filesystem_access(bundled_app("test_dir"), &file_op_block)
+ expect(bundled_app("test_dir")).to exist
+ end
+ end
+
+ context "system throws Errno::EACESS" do
+ let(:file_op_block) { proc {|_path| raise Errno::EACCES } }
+
+ it "raises a PermissionError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::PermissionError
+ )
+ end
+ end
+
+ context "system throws Errno::EAGAIN" do
+ let(:file_op_block) { proc {|_path| raise Errno::EAGAIN } }
+
+ it "raises a TemporaryResourceError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::TemporaryResourceError
+ )
+ end
+ end
+
+ context "system throws Errno::EPROTO" do
+ let(:file_op_block) { proc {|_path| raise Errno::EPROTO } }
+
+ it "raises a VirtualProtocolError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::VirtualProtocolError
+ )
+ end
+ end
+
+ context "system throws Errno::ENOTSUP" do
+ let(:file_op_block) { proc {|_path| raise Errno::ENOTSUP } }
+
+ it "raises a OperationNotSupportedError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::OperationNotSupportedError
+ )
+ end
+ end
+
+ context "system throws Errno::ENOSPC" do
+ let(:file_op_block) { proc {|_path| raise Errno::ENOSPC } }
+
+ it "raises a NoSpaceOnDeviceError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::NoSpaceOnDeviceError
+ )
+ end
+ end
+
+ context "system throws an unhandled SystemCallError" do
+ let(:error) { SystemCallError.new("Shields down", 1337) }
+ let(:file_op_block) { proc {|_path| raise error } }
+
+ it "raises a GenericSystemCallError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::GenericSystemCallError, /error accessing.+underlying.+Shields down/m
+ )
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source/git/git_proxy_spec.rb b/spec/bundler/bundler/source/git/git_proxy_spec.rb
new file mode 100644
index 0000000000..c79dd8ff71
--- /dev/null
+++ b/spec/bundler/bundler/source/git/git_proxy_spec.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Source::Git::GitProxy do
+ let(:path) { Pathname("path") }
+ let(:uri) { "https://github.com/rubygems/rubygems.git" }
+ let(:ref) { "HEAD" }
+ let(:revision) { nil }
+ let(:git_source) { nil }
+ let(:clone_result) { double(Process::Status, :success? => true) }
+ let(:base_clone_args) { ["clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--depth", "1", "--single-branch"] }
+ subject { described_class.new(path, uri, ref, revision, git_source) }
+
+ context "with configured credentials" do
+ it "adds username and password to URI" do
+ Bundler.settings.temporary(uri => "u:p") do
+ allow(subject).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(subject).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s], nil).and_return(["", "", clone_result])
+ subject.checkout
+ end
+ end
+
+ it "adds username and password to URI for host" do
+ Bundler.settings.temporary("github.com" => "u:p") do
+ allow(subject).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(subject).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s], nil).and_return(["", "", clone_result])
+ subject.checkout
+ end
+ end
+
+ it "does not add username and password to mismatched URI" do
+ Bundler.settings.temporary("https://u:p@github.com/rubygems/rubygems-mismatch.git" => "u:p") do
+ allow(subject).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(subject).to receive(:capture).with([*base_clone_args, "--", uri, path.to_s], nil).and_return(["", "", clone_result])
+ subject.checkout
+ end
+ end
+
+ it "keeps original userinfo" do
+ Bundler.settings.temporary("github.com" => "u:p") do
+ original = "https://orig:info@github.com/rubygems/rubygems.git"
+ subject = described_class.new(Pathname("path"), original, "HEAD")
+ allow(subject).to receive(:git_local).with("--version").and_return("git version 2.14.0")
+ expect(subject).to receive(:capture).with([*base_clone_args, "--", original, path.to_s], nil).and_return(["", "", clone_result])
+ subject.checkout
+ end
+ end
+ end
+
+ describe "#version" do
+ context "with a normal version number" do
+ before do
+ expect(subject).to receive(:git_local).with("--version").
+ and_return("git version 1.2.3")
+ end
+
+ it "returns the git version number" do
+ expect(subject.version).to eq("1.2.3")
+ end
+
+ it "does not raise an error when passed into Gem::Version.create" do
+ expect { Gem::Version.create subject.version }.not_to raise_error
+ end
+ end
+
+ context "with a OSX version number" do
+ before do
+ expect(subject).to receive(:git_local).with("--version").
+ and_return("git version 1.2.3 (Apple Git-BS)")
+ end
+
+ it "strips out OSX specific additions in the version string" do
+ expect(subject.version).to eq("1.2.3")
+ end
+
+ it "does not raise an error when passed into Gem::Version.create" do
+ expect { Gem::Version.create subject.version }.not_to raise_error
+ end
+ end
+
+ context "with a msysgit version number" do
+ before do
+ expect(subject).to receive(:git_local).with("--version").
+ and_return("git version 1.2.3.msysgit.0")
+ end
+
+ it "strips out msysgit specific additions in the version string" do
+ expect(subject.version).to eq("1.2.3")
+ end
+
+ it "does not raise an error when passed into Gem::Version.create" do
+ expect { Gem::Version.create subject.version }.not_to raise_error
+ end
+ end
+ end
+
+ describe "#full_version" do
+ context "with a normal version number" do
+ before do
+ expect(subject).to receive(:git_local).with("--version").
+ and_return("git version 1.2.3")
+ end
+
+ it "returns the git version number" do
+ expect(subject.full_version).to eq("1.2.3")
+ end
+ end
+
+ context "with a OSX version number" do
+ before do
+ expect(subject).to receive(:git_local).with("--version").
+ and_return("git version 1.2.3 (Apple Git-BS)")
+ end
+
+ it "does not strip out OSX specific additions in the version string" do
+ expect(subject.full_version).to eq("1.2.3 (Apple Git-BS)")
+ end
+ end
+
+ context "with a msysgit version number" do
+ before do
+ expect(subject).to receive(:git_local).with("--version").
+ and_return("git version 1.2.3.msysgit.0")
+ end
+
+ it "does not strip out msysgit specific additions in the version string" do
+ expect(subject.full_version).to eq("1.2.3.msysgit.0")
+ end
+ end
+ end
+
+ it "doesn't allow arbitrary code execution through Gemfile uris with a leading dash" do
+ gemfile <<~G
+ gem "poc", git: "-u./pay:load.sh"
+ G
+
+ file = bundled_app("pay:load.sh")
+
+ create_file file, <<~RUBY
+ #!/bin/sh
+
+ touch #{bundled_app("canary")}
+ RUBY
+
+ FileUtils.chmod("+x", file)
+
+ bundle :lock, :raise_on_error => false
+
+ expect(Pathname.new(bundled_app("canary"))).not_to exist
+ end
+end
diff --git a/spec/bundler/bundler/source/git_spec.rb b/spec/bundler/bundler/source/git_spec.rb
new file mode 100644
index 0000000000..ed6dc3cd29
--- /dev/null
+++ b/spec/bundler/bundler/source/git_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Source::Git do
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new("root") }
+ end
+
+ let(:uri) { "https://github.com/foo/bar.git" }
+ let(:options) do
+ { "uri" => uri }
+ end
+
+ subject { described_class.new(options) }
+
+ describe "#to_s" do
+ it "returns a description" do
+ expect(subject.to_s).to eq "https://github.com/foo/bar.git"
+ end
+
+ context "when the URI contains credentials" do
+ let(:uri) { "https://my-secret-token:x-oauth-basic@github.com/foo/bar.git" }
+
+ it "filters credentials" do
+ expect(subject.to_s).to eq "https://x-oauth-basic@github.com/foo/bar.git"
+ end
+ end
+
+ context "when the source has a glob specifier" do
+ let(:glob) { "bar/baz/*.gemspec" }
+ let(:options) do
+ { "uri" => uri, "glob" => glob }
+ end
+
+ it "includes it" do
+ expect(subject.to_s).to eq "https://github.com/foo/bar.git (glob: bar/baz/*.gemspec)"
+ end
+ end
+
+ context "when the source has a reference" do
+ let(:git_proxy_stub) do
+ instance_double(Bundler::Source::Git::GitProxy, :revision => "123abc", :branch => "v1.0.0")
+ end
+ let(:options) do
+ { "uri" => uri, "ref" => "v1.0.0" }
+ end
+
+ before do
+ allow(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub)
+ end
+
+ it "includes it" do
+ expect(subject.to_s).to eq "https://github.com/foo/bar.git (at v1.0.0@123abc)"
+ end
+ end
+
+ context "when the source has both reference and glob specifiers" do
+ let(:git_proxy_stub) do
+ instance_double(Bundler::Source::Git::GitProxy, :revision => "123abc", :branch => "v1.0.0")
+ end
+ let(:options) do
+ { "uri" => uri, "ref" => "v1.0.0", "glob" => "gems/foo/*.gemspec" }
+ end
+
+ before do
+ allow(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub)
+ end
+
+ it "includes both" do
+ expect(subject.to_s).to eq "https://github.com/foo/bar.git (at v1.0.0@123abc, glob: gems/foo/*.gemspec)"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source/path_spec.rb b/spec/bundler/bundler/source/path_spec.rb
new file mode 100644
index 0000000000..1d13e03ec1
--- /dev/null
+++ b/spec/bundler/bundler/source/path_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Source::Path do
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new("root") }
+ end
+
+ describe "#eql?" do
+ subject { described_class.new("path" => "gems/a") }
+
+ context "with two equivalent relative paths from different roots" do
+ let(:a_gem_opts) { { "path" => "../gems/a", "root_path" => Bundler.root.join("nested") } }
+ let(:a_gem) { described_class.new a_gem_opts }
+
+ it "returns true" do
+ expect(subject).to eq a_gem
+ end
+ end
+
+ context "with the same (but not equivalent) relative path from different roots" do
+ subject { described_class.new("path" => "gems/a") }
+
+ let(:a_gem_opts) { { "path" => "gems/a", "root_path" => Bundler.root.join("nested") } }
+ let(:a_gem) { described_class.new a_gem_opts }
+
+ it "returns false" do
+ expect(subject).to_not eq a_gem
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source/rubygems/remote_spec.rb b/spec/bundler/bundler/source/rubygems/remote_spec.rb
new file mode 100644
index 0000000000..07ce4f968e
--- /dev/null
+++ b/spec/bundler/bundler/source/rubygems/remote_spec.rb
@@ -0,0 +1,172 @@
+# frozen_string_literal: true
+
+require "bundler/source/rubygems/remote"
+
+RSpec.describe Bundler::Source::Rubygems::Remote do
+ def remote(uri)
+ Bundler::Source::Rubygems::Remote.new(uri)
+ end
+
+ before do
+ allow(Digest(:MD5)).to receive(:hexdigest).with(duck_type(:to_s)) {|string| "MD5HEX(#{string})" }
+ end
+
+ let(:uri_no_auth) { Bundler::URI("https://gems.example.com") }
+ let(:uri_with_auth) { Bundler::URI("https://#{credentials}@gems.example.com") }
+ let(:credentials) { "username:password" }
+
+ context "when the original URI has no credentials" do
+ describe "#uri" do
+ it "returns the original URI" do
+ expect(remote(uri_no_auth).uri).to eq(uri_no_auth)
+ end
+
+ it "applies configured credentials" do
+ Bundler.settings.temporary(uri_no_auth.to_s => credentials) do
+ expect(remote(uri_no_auth).uri).to eq(uri_with_auth)
+ end
+ end
+ end
+
+ describe "#anonymized_uri" do
+ it "returns the original URI" do
+ expect(remote(uri_no_auth).anonymized_uri).to eq(uri_no_auth)
+ end
+
+ it "does not apply given credentials" do
+ Bundler.settings.temporary(uri_no_auth.to_s => credentials) do
+ expect(remote(uri_no_auth).anonymized_uri).to eq(uri_no_auth)
+ end
+ end
+ end
+
+ describe "#cache_slug" do
+ it "returns the correct slug" do
+ expect(remote(uri_no_auth).cache_slug).to eq("gems.example.com.443.MD5HEX(gems.example.com.443./)")
+ end
+
+ it "only applies the given user" do
+ Bundler.settings.temporary(uri_no_auth.to_s => credentials) do
+ expect(remote(uri_no_auth).cache_slug).to eq("gems.example.com.username.443.MD5HEX(gems.example.com.username.443./)")
+ end
+ end
+ end
+ end
+
+ context "when the original URI has a username and password" do
+ describe "#uri" do
+ it "returns the original URI" do
+ expect(remote(uri_with_auth).uri).to eq(uri_with_auth)
+ end
+
+ it "does not apply configured credentials" do
+ Bundler.settings.temporary(uri_no_auth.to_s => "other:stuff")
+ expect(remote(uri_with_auth).uri).to eq(uri_with_auth)
+ end
+ end
+
+ describe "#anonymized_uri" do
+ it "returns the URI without username and password" do
+ expect(remote(uri_with_auth).anonymized_uri).to eq(uri_no_auth)
+ end
+
+ it "does not apply given credentials" do
+ Bundler.settings.temporary(uri_no_auth.to_s => "other:stuff")
+ expect(remote(uri_with_auth).anonymized_uri).to eq(uri_no_auth)
+ end
+ end
+
+ describe "#cache_slug" do
+ it "returns the correct slug" do
+ expect(remote(uri_with_auth).cache_slug).to eq("gems.example.com.username.443.MD5HEX(gems.example.com.username.443./)")
+ end
+
+ it "does not apply given credentials" do
+ Bundler.settings.temporary(uri_with_auth.to_s => credentials)
+ expect(remote(uri_with_auth).cache_slug).to eq("gems.example.com.username.443.MD5HEX(gems.example.com.username.443./)")
+ end
+ end
+ end
+
+ context "when the original URI has only a username" do
+ let(:uri) { Bundler::URI("https://SeCrEt-ToKeN@gem.fury.io/me/") }
+
+ describe "#anonymized_uri" do
+ it "returns the URI without username and password" do
+ expect(remote(uri).anonymized_uri).to eq(Bundler::URI("https://gem.fury.io/me/"))
+ end
+ end
+
+ describe "#cache_slug" do
+ it "returns the correct slug" do
+ expect(remote(uri).cache_slug).to eq("gem.fury.io.SeCrEt-ToKeN.443.MD5HEX(gem.fury.io.SeCrEt-ToKeN.443./me/)")
+ end
+ end
+ end
+
+ context "when a mirror with inline credentials is configured for the URI" do
+ let(:uri) { Bundler::URI("https://rubygems.org/") }
+ let(:mirror_uri_with_auth) { Bundler::URI("https://username:password@rubygems-mirror.org/") }
+ let(:mirror_uri_no_auth) { Bundler::URI("https://rubygems-mirror.org/") }
+
+ before { Bundler.settings.temporary("mirror.https://rubygems.org/" => mirror_uri_with_auth.to_s) }
+
+ after { Bundler.settings.temporary("mirror.https://rubygems.org/" => nil) }
+
+ specify "#uri returns the mirror URI with credentials" do
+ expect(remote(uri).uri).to eq(mirror_uri_with_auth)
+ end
+
+ specify "#anonymized_uri returns the mirror URI without credentials" do
+ expect(remote(uri).anonymized_uri).to eq(mirror_uri_no_auth)
+ end
+
+ specify "#original_uri returns the original source" do
+ expect(remote(uri).original_uri).to eq(uri)
+ end
+
+ specify "#cache_slug returns the correct slug" do
+ expect(remote(uri).cache_slug).to eq("rubygems.org.443.MD5HEX(rubygems.org.443./)")
+ end
+ end
+
+ context "when a mirror with configured credentials is configured for the URI" do
+ let(:uri) { Bundler::URI("https://rubygems.org/") }
+ let(:mirror_uri_with_auth) { Bundler::URI("https://#{credentials}@rubygems-mirror.org/") }
+ let(:mirror_uri_no_auth) { Bundler::URI("https://rubygems-mirror.org/") }
+
+ before do
+ Bundler.settings.temporary("mirror.https://rubygems.org/" => mirror_uri_no_auth.to_s)
+ Bundler.settings.temporary(mirror_uri_no_auth.to_s => credentials)
+ end
+
+ after do
+ Bundler.settings.temporary("mirror.https://rubygems.org/" => nil)
+ Bundler.settings.temporary(mirror_uri_no_auth.to_s => nil)
+ end
+
+ specify "#uri returns the mirror URI with credentials" do
+ expect(remote(uri).uri).to eq(mirror_uri_with_auth)
+ end
+
+ specify "#anonymized_uri returns the mirror URI without credentials" do
+ expect(remote(uri).anonymized_uri).to eq(mirror_uri_no_auth)
+ end
+
+ specify "#original_uri returns the original source" do
+ expect(remote(uri).original_uri).to eq(uri)
+ end
+
+ specify "#cache_slug returns the original source" do
+ expect(remote(uri).cache_slug).to eq("rubygems.org.443.MD5HEX(rubygems.org.443./)")
+ end
+ end
+
+ context "when there is no mirror set" do
+ describe "#original_uri" do
+ it "is not set" do
+ expect(remote(uri_no_auth).original_uri).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source/rubygems_spec.rb b/spec/bundler/bundler/source/rubygems_spec.rb
new file mode 100644
index 0000000000..884fa81046
--- /dev/null
+++ b/spec/bundler/bundler/source/rubygems_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Source::Rubygems do
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new("root") }
+ end
+
+ describe "caches" do
+ it "includes Bundler.app_cache" do
+ expect(subject.caches).to include(Bundler.app_cache)
+ end
+
+ it "includes GEM_PATH entries" do
+ Gem.path.each do |path|
+ expect(subject.caches).to include(File.expand_path("#{path}/cache"))
+ end
+ end
+
+ it "is an array of strings or pathnames" do
+ subject.caches.each do |cache|
+ expect([String, Pathname]).to include(cache.class)
+ end
+ end
+ end
+
+ describe "#add_remote" do
+ context "when the source is an HTTP(s) URI with no host" do
+ it "raises error" do
+ expect { subject.add_remote("https:rubygems.org") }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe "#no_remotes?" do
+ context "when no remote provided" do
+ it "returns a truthy value" do
+ expect(described_class.new("remotes" => []).no_remotes?).to be_truthy
+ end
+ end
+
+ context "when a remote provided" do
+ it "returns a falsey value" do
+ expect(described_class.new("remotes" => ["https://rubygems.org"]).no_remotes?).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source_list_spec.rb b/spec/bundler/bundler/source_list_spec.rb
new file mode 100644
index 0000000000..f860e9ff58
--- /dev/null
+++ b/spec/bundler/bundler/source_list_spec.rb
@@ -0,0 +1,459 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::SourceList do
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new "./tmp/bundled_app" }
+
+ stub_const "ASourcePlugin", Class.new(Bundler::Plugin::API)
+ ASourcePlugin.source "new_source"
+ allow(Bundler::Plugin).to receive(:source?).with("new_source").and_return(true)
+ end
+
+ subject(:source_list) { Bundler::SourceList.new }
+
+ let(:rubygems_aggregate) { Bundler::Source::Rubygems.new }
+ let(:metadata_source) { Bundler::Source::Metadata.new }
+
+ describe "adding sources" do
+ before do
+ source_list.add_path_source("path" => "/existing/path/to/gem")
+ source_list.add_git_source("uri" => "git://existing-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://existing-rubygems.org"])
+ source_list.add_plugin_source("new_source", "uri" => "https://some.url/a")
+ end
+
+ describe "#add_path_source" do
+ before do
+ @duplicate = source_list.add_path_source("path" => "/path/to/gem")
+ @new_source = source_list.add_path_source("path" => "/path/to/gem")
+ end
+
+ it "returns the new path source" do
+ expect(@new_source).to be_instance_of(Bundler::Source::Path)
+ end
+
+ it "passes the provided options to the new source" do
+ expect(@new_source.options).to eq("path" => "/path/to/gem")
+ end
+
+ it "adds the source to the beginning of path_sources" do
+ expect(source_list.path_sources.first).to equal(@new_source)
+ end
+
+ it "removes existing duplicates" do
+ expect(source_list.path_sources).not_to include equal(@duplicate)
+ end
+ end
+
+ describe "#add_git_source" do
+ before do
+ @duplicate = source_list.add_git_source("uri" => "git://host/path.git")
+ @new_source = source_list.add_git_source("uri" => "git://host/path.git")
+ end
+
+ it "returns the new git source" do
+ expect(@new_source).to be_instance_of(Bundler::Source::Git)
+ end
+
+ it "passes the provided options to the new source" do
+ @new_source = source_list.add_git_source("uri" => "git://host/path.git")
+ expect(@new_source.options).to eq("uri" => "git://host/path.git")
+ end
+
+ it "adds the source to the beginning of git_sources" do
+ @new_source = source_list.add_git_source("uri" => "git://host/path.git")
+ expect(source_list.git_sources.first).to equal(@new_source)
+ end
+
+ it "removes existing duplicates" do
+ @duplicate = source_list.add_git_source("uri" => "git://host/path.git")
+ @new_source = source_list.add_git_source("uri" => "git://host/path.git")
+ expect(source_list.git_sources).not_to include equal(@duplicate)
+ end
+
+ context "with the git: protocol" do
+ let(:msg) do
+ "The git source `git://existing-git.org/path.git` " \
+ "uses the `git` protocol, which transmits data without encryption. " \
+ "Disable this warning with `bundle config set --local git.allow_insecure true`, " \
+ "or switch to the `https` protocol to keep your data secure."
+ end
+
+ it "warns about git protocols" do
+ expect(Bundler.ui).to receive(:warn).with(msg)
+ source_list.add_git_source("uri" => "git://existing-git.org/path.git")
+ end
+
+ it "ignores git protocols on request" do
+ Bundler.settings.temporary(:"git.allow_insecure" => true)
+ expect(Bundler.ui).to_not receive(:warn).with(msg)
+ source_list.add_git_source("uri" => "git://existing-git.org/path.git")
+ end
+ end
+ end
+
+ describe "#add_rubygems_source" do
+ before do
+ @duplicate = source_list.add_rubygems_source("remotes" => ["https://rubygems.org/"])
+ @new_source = source_list.add_rubygems_source("remotes" => ["https://rubygems.org/"])
+ end
+
+ it "returns the new rubygems source" do
+ expect(@new_source).to be_instance_of(Bundler::Source::Rubygems)
+ end
+
+ it "passes the provided options to the new source" do
+ expect(@new_source.options).to eq("remotes" => ["https://rubygems.org/"])
+ end
+
+ it "adds the source to the beginning of rubygems_sources" do
+ expect(source_list.rubygems_sources.first).to equal(@new_source)
+ end
+
+ it "removes duplicates" do
+ expect(source_list.rubygems_sources).not_to include equal(@duplicate)
+ end
+ end
+
+ describe "#add_global_rubygems_remote" do
+ let!(:returned_source) { source_list.add_global_rubygems_remote("https://rubygems.org/") }
+
+ it "returns the aggregate rubygems source" do
+ expect(returned_source).to be_instance_of(Bundler::Source::Rubygems)
+ end
+
+ it "adds the provided remote to the beginning of the aggregate source" do
+ source_list.add_global_rubygems_remote("https://othersource.org")
+ expect(returned_source.remotes).to eq [
+ Bundler::URI("https://othersource.org/"),
+ Bundler::URI("https://rubygems.org/"),
+ ]
+ end
+ end
+
+ describe "#add_plugin_source" do
+ before do
+ @duplicate = source_list.add_plugin_source("new_source", "uri" => "http://host/path.")
+ @new_source = source_list.add_plugin_source("new_source", "uri" => "http://host/path.")
+ end
+
+ it "returns the new plugin source" do
+ expect(@new_source).to be_a(Bundler::Plugin::API::Source)
+ end
+
+ it "passes the provided options to the new source" do
+ expect(@new_source.options).to eq("uri" => "http://host/path.")
+ end
+
+ it "adds the source to the beginning of git_sources" do
+ expect(source_list.plugin_sources.first).to equal(@new_source)
+ end
+
+ it "removes existing duplicates" do
+ expect(source_list.plugin_sources).not_to include equal(@duplicate)
+ end
+ end
+ end
+
+ describe "#all_sources" do
+ it "includes the aggregate rubygems source when rubygems sources have been added" do
+ source_list.add_git_source("uri" => "git://host/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://rubygems.org"])
+ source_list.add_path_source("path" => "/path/to/gem")
+ source_list.add_plugin_source("new_source", "uri" => "https://some.url/a")
+
+ expect(source_list.all_sources).to include rubygems_aggregate
+ end
+
+ it "includes the aggregate rubygems source when no rubygems sources have been added" do
+ source_list.add_git_source("uri" => "git://host/path.git")
+ source_list.add_path_source("path" => "/path/to/gem")
+ source_list.add_plugin_source("new_source", "uri" => "https://some.url/a")
+
+ expect(source_list.all_sources).to include rubygems_aggregate
+ end
+
+ it "returns sources of the same type in the reverse order that they were added" do
+ source_list.add_git_source("uri" => "git://third-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://fifth-rubygems.org"])
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_plugin_source("new_source", "uri" => "https://some.url/b")
+ source_list.add_rubygems_source("remotes" => ["https://fourth-rubygems.org"])
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://third-rubygems.org"])
+ source_list.add_plugin_source("new_source", "uri" => "https://some.o.url/")
+ source_list.add_git_source("uri" => "git://second-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://second-rubygems.org"])
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_plugin_source("new_source", "uri" => "https://some.url/c")
+ source_list.add_rubygems_source("remotes" => ["https://first-rubygems.org"])
+ source_list.add_git_source("uri" => "git://first-git.org/path.git")
+
+ expect(source_list.all_sources).to eq [
+ Bundler::Source::Path.new("path" => "/first/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/second/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/third/path/to/gem"),
+ Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://third-git.org/path.git"),
+ ASourcePlugin.new("uri" => "https://some.url/c"),
+ ASourcePlugin.new("uri" => "https://some.o.url/"),
+ ASourcePlugin.new("uri" => "https://some.url/b"),
+ Bundler::Source::Rubygems.new("remotes" => ["https://first-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://second-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://third-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://fourth-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://fifth-rubygems.org"]),
+ rubygems_aggregate,
+ metadata_source,
+ ]
+ end
+ end
+
+ describe "#path_sources" do
+ it "returns an empty array when no path sources have been added" do
+ source_list.add_global_rubygems_remote("https://rubygems.org")
+ source_list.add_git_source("uri" => "git://host/path.git")
+ expect(source_list.path_sources).to be_empty
+ end
+
+ it "returns path sources in the reverse order that they were added" do
+ source_list.add_git_source("uri" => "git://third-git.org/path.git")
+ source_list.add_global_rubygems_remote("https://fifth-rubygems.org")
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_global_rubygems_remote("https://fourth-rubygems.org")
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_global_rubygems_remote("https://third-rubygems.org")
+ source_list.add_git_source("uri" => "git://second-git.org/path.git")
+ source_list.add_global_rubygems_remote("https://second-rubygems.org")
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_global_rubygems_remote("https://first-rubygems.org")
+ source_list.add_git_source("uri" => "git://first-git.org/path.git")
+
+ expect(source_list.path_sources).to eq [
+ Bundler::Source::Path.new("path" => "/first/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/second/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/third/path/to/gem"),
+ ]
+ end
+ end
+
+ describe "#git_sources" do
+ it "returns an empty array when no git sources have been added" do
+ source_list.add_global_rubygems_remote("https://rubygems.org")
+ source_list.add_path_source("path" => "/path/to/gem")
+
+ expect(source_list.git_sources).to be_empty
+ end
+
+ it "returns git sources in the reverse order that they were added" do
+ source_list.add_git_source("uri" => "git://third-git.org/path.git")
+ source_list.add_global_rubygems_remote("https://fifth-rubygems.org")
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_global_rubygems_remote("https://fourth-rubygems.org")
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_global_rubygems_remote("https://third-rubygems.org")
+ source_list.add_git_source("uri" => "git://second-git.org/path.git")
+ source_list.add_global_rubygems_remote("https://second-rubygems.org")
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_global_rubygems_remote("https://first-rubygems.org")
+ source_list.add_git_source("uri" => "git://first-git.org/path.git")
+
+ expect(source_list.git_sources).to eq [
+ Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://third-git.org/path.git"),
+ ]
+ end
+ end
+
+ describe "#plugin_sources" do
+ it "returns an empty array when no plugin sources have been added" do
+ source_list.add_global_rubygems_remote("https://rubygems.org")
+ source_list.add_path_source("path" => "/path/to/gem")
+
+ expect(source_list.plugin_sources).to be_empty
+ end
+
+ it "returns plugin sources in the reverse order that they were added" do
+ source_list.add_plugin_source("new_source", "uri" => "https://third-git.org/path.git")
+ source_list.add_git_source("https://new-git.org")
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_global_rubygems_remote("https://fourth-rubygems.org")
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_global_rubygems_remote("https://third-rubygems.org")
+ source_list.add_plugin_source("new_source", "uri" => "git://second-git.org/path.git")
+ source_list.add_global_rubygems_remote("https://second-rubygems.org")
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_global_rubygems_remote("https://first-rubygems.org")
+ source_list.add_plugin_source("new_source", "uri" => "git://first-git.org/path.git")
+
+ expect(source_list.plugin_sources).to eq [
+ ASourcePlugin.new("uri" => "git://first-git.org/path.git"),
+ ASourcePlugin.new("uri" => "git://second-git.org/path.git"),
+ ASourcePlugin.new("uri" => "https://third-git.org/path.git"),
+ ]
+ end
+ end
+
+ describe "#rubygems_sources" do
+ it "includes the aggregate rubygems source when rubygems sources have been added" do
+ source_list.add_git_source("uri" => "git://host/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://rubygems.org"])
+ source_list.add_path_source("path" => "/path/to/gem")
+
+ expect(source_list.rubygems_sources).to include rubygems_aggregate
+ end
+
+ it "returns only the aggregate rubygems source when no rubygems sources have been added" do
+ source_list.add_git_source("uri" => "git://host/path.git")
+ source_list.add_path_source("path" => "/path/to/gem")
+
+ expect(source_list.rubygems_sources).to eq [rubygems_aggregate]
+ end
+
+ it "returns rubygems sources in the reverse order that they were added" do
+ source_list.add_git_source("uri" => "git://third-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://fifth-rubygems.org"])
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://fourth-rubygems.org"])
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://third-rubygems.org"])
+ source_list.add_git_source("uri" => "git://second-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://second-rubygems.org"])
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://first-rubygems.org"])
+ source_list.add_git_source("uri" => "git://first-git.org/path.git")
+
+ expect(source_list.rubygems_sources).to eq [
+ Bundler::Source::Rubygems.new("remotes" => ["https://first-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://second-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://third-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://fourth-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://fifth-rubygems.org"]),
+ rubygems_aggregate,
+ ]
+ end
+ end
+
+ describe "#get" do
+ context "when it includes an equal source" do
+ let(:rubygems_source) { Bundler::Source::Rubygems.new("remotes" => ["https://rubygems.org"]) }
+ before { @equal_source = source_list.add_global_rubygems_remote("https://rubygems.org") }
+
+ it "returns the equal source" do
+ expect(source_list.get(rubygems_source)).to be @equal_source
+ end
+ end
+
+ context "when it does not include an equal source" do
+ let(:path_source) { Bundler::Source::Path.new("path" => "/path/to/gem") }
+
+ it "returns nil" do
+ expect(source_list.get(path_source)).to be_nil
+ end
+ end
+ end
+
+ describe "#lock_sources" do
+ before do
+ source_list.add_git_source("uri" => "git://third-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://duplicate-rubygems.org"])
+ source_list.add_plugin_source("new_source", "uri" => "https://third-bar.org/foo")
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://third-rubygems.org"])
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://second-rubygems.org"])
+ source_list.add_git_source("uri" => "git://second-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://first-rubygems.org"])
+ source_list.add_plugin_source("new_source", "uri" => "https://second-plugin.org/random")
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://duplicate-rubygems.org"])
+ source_list.add_git_source("uri" => "git://first-git.org/path.git")
+ end
+
+ it "returns all sources, without combining rubygems sources" do
+ expect(source_list.lock_sources).to eq [
+ Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://third-git.org/path.git"),
+ ASourcePlugin.new("uri" => "https://second-plugin.org/random"),
+ ASourcePlugin.new("uri" => "https://third-bar.org/foo"),
+ Bundler::Source::Path.new("path" => "/first/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/second/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/third/path/to/gem"),
+ Bundler::Source::Rubygems.new,
+ Bundler::Source::Rubygems.new("remotes" => ["https://duplicate-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://first-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://second-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://third-rubygems.org"]),
+ ]
+ end
+ end
+
+ describe "replace_sources!" do
+ let(:existing_locked_source) { Bundler::Source::Path.new("path" => "/existing/path") }
+ let(:removed_locked_source) { Bundler::Source::Path.new("path" => "/removed/path") }
+
+ let(:locked_sources) { [existing_locked_source, removed_locked_source] }
+
+ before do
+ @existing_source = source_list.add_path_source("path" => "/existing/path")
+ @new_source = source_list.add_path_source("path" => "/new/path")
+ source_list.replace_sources!(locked_sources)
+ end
+
+ it "maintains the order and number of sources" do
+ expect(source_list.path_sources).to eq [@new_source, @existing_source]
+ end
+
+ it "retains the same instance of the new source" do
+ expect(source_list.path_sources[0]).to be @new_source
+ end
+
+ it "replaces the instance of the existing source" do
+ expect(source_list.path_sources[1]).to be existing_locked_source
+ end
+ end
+
+ describe "#cached!" do
+ let(:rubygems_source) { source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) }
+ let(:git_source) { source_list.add_git_source("uri" => "git://host/path.git") }
+ let(:path_source) { source_list.add_path_source("path" => "/path/to/gem") }
+
+ it "calls #cached! on all the sources" do
+ expect(rubygems_source).to receive(:cached!)
+ expect(git_source).to receive(:cached!)
+ expect(path_source).to receive(:cached!)
+ source_list.cached!
+ end
+ end
+
+ describe "#remote!" do
+ let(:rubygems_source) { source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) }
+ let(:git_source) { source_list.add_git_source("uri" => "git://host/path.git") }
+ let(:path_source) { source_list.add_path_source("path" => "/path/to/gem") }
+
+ it "calls #remote! on all the sources" do
+ expect(rubygems_source).to receive(:remote!)
+ expect(git_source).to receive(:remote!)
+ expect(path_source).to receive(:remote!)
+ source_list.remote!
+ end
+ end
+
+ describe "implicit_global_source?" do
+ context "when a global rubygem source provided" do
+ it "returns a falsy value" do
+ source_list.add_global_rubygems_remote("https://rubygems.org")
+
+ expect(source_list.implicit_global_source?).to be_falsey
+ end
+ end
+ context "when no global rubygem source provided" do
+ it "returns a truthy value" do
+ expect(source_list.implicit_global_source?).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source_spec.rb b/spec/bundler/bundler/source_spec.rb
new file mode 100644
index 0000000000..ceb369ecdb
--- /dev/null
+++ b/spec/bundler/bundler/source_spec.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Source do
+ class ExampleSource < Bundler::Source
+ end
+
+ subject { ExampleSource.new }
+
+ describe "#unmet_deps" do
+ let(:specs) { double(:specs) }
+ let(:unmet_dependency_names) { double(:unmet_dependency_names) }
+
+ before do
+ allow(subject).to receive(:specs).and_return(specs)
+ allow(specs).to receive(:unmet_dependency_names).and_return(unmet_dependency_names)
+ end
+
+ it "should return the names of unmet dependencies" do
+ expect(subject.unmet_deps).to eq(unmet_dependency_names)
+ end
+ end
+
+ describe "#version_message" do
+ let(:spec) { double(:spec, :name => "nokogiri", :version => ">= 1.6", :platform => rb) }
+
+ shared_examples_for "the lockfile specs are not relevant" do
+ it "should return a string with the spec name and version" do
+ expect(subject.version_message(spec)).to eq("nokogiri >= 1.6")
+ end
+ end
+
+ context "when there are locked gems" do
+ context "that contain the relevant gem spec" do
+ context "without a version" do
+ let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => nil) }
+
+ it_behaves_like "the lockfile specs are not relevant"
+ end
+
+ context "with the same version" do
+ let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => ">= 1.6") }
+
+ it_behaves_like "the lockfile specs are not relevant"
+ end
+
+ context "with a different version" do
+ let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "< 1.5") }
+
+ context "with color", :no_color_tty do
+ before do
+ allow($stdout).to receive(:tty?).and_return(true)
+ end
+
+ it "should return a string with the spec name and version and locked spec version" do
+ expect(subject.version_message(spec, locked_gem)).to eq("nokogiri >= 1.6\e[32m (was < 1.5)\e[0m")
+ end
+ end
+
+ context "without color" do
+ around do |example|
+ with_ui(Bundler::UI::Shell.new("no-color" => true)) do
+ example.run
+ end
+ end
+
+ it "should return a string with the spec name and version and locked spec version" do
+ expect(subject.version_message(spec, locked_gem)).to eq("nokogiri >= 1.6 (was < 1.5)")
+ end
+ end
+ end
+
+ context "with a more recent version" do
+ let(:spec) { double(:spec, :name => "nokogiri", :version => "1.6.1", :platform => rb) }
+ let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "1.7.0") }
+
+ context "with color", :no_color_tty do
+ before do
+ allow($stdout).to receive(:tty?).and_return(true)
+ end
+
+ it "should return a string with the locked spec version in yellow" do
+ expect(subject.version_message(spec, locked_gem)).to eq("nokogiri 1.6.1\e[33m (was 1.7.0)\e[0m")
+ end
+ end
+
+ context "without color" do
+ around do |example|
+ with_ui(Bundler::UI::Shell.new("no-color" => true)) do
+ example.run
+ end
+ end
+
+ it "should return a string with the locked spec version in yellow" do
+ expect(subject.version_message(spec, locked_gem)).to eq("nokogiri 1.6.1 (was 1.7.0)")
+ end
+ end
+ end
+
+ context "with an older version" do
+ let(:spec) { double(:spec, :name => "nokogiri", :version => "1.7.1", :platform => rb) }
+ let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "1.7.0") }
+
+ context "with color", :no_color_tty do
+ before do
+ allow($stdout).to receive(:tty?).and_return(true)
+ end
+
+ it "should return a string with the locked spec version in green" do
+ expect(subject.version_message(spec, locked_gem)).to eq("nokogiri 1.7.1\e[32m (was 1.7.0)\e[0m")
+ end
+ end
+
+ context "without color" do
+ around do |example|
+ with_ui(Bundler::UI::Shell.new("no-color" => true)) do
+ example.run
+ end
+ end
+
+ it "should return a string with the locked spec version in yellow" do
+ expect(subject.version_message(spec, locked_gem)).to eq("nokogiri 1.7.1 (was 1.7.0)")
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe "#can_lock?" do
+ context "when the passed spec's source is equivalent" do
+ let(:spec) { double(:spec, :source => subject) }
+
+ it "should return true" do
+ expect(subject.can_lock?(spec)).to be_truthy
+ end
+ end
+
+ context "when the passed spec's source is not equivalent" do
+ let(:spec) { double(:spec, :source => double(:other_source)) }
+
+ it "should return false" do
+ expect(subject.can_lock?(spec)).to be_falsey
+ end
+ end
+ end
+
+ describe "#include?" do
+ context "when the passed source is equivalent" do
+ let(:source) { subject }
+
+ it "should return true" do
+ expect(subject).to include(source)
+ end
+ end
+
+ context "when the passed source is not equivalent" do
+ let(:source) { double(:source) }
+
+ it "should return false" do
+ expect(subject).to_not include(source)
+ end
+ end
+ end
+
+ private
+
+ def with_ui(ui)
+ old_ui = Bundler.ui
+ Bundler.ui = ui
+ yield
+ ensure
+ Bundler.ui = old_ui
+ end
+end
diff --git a/spec/bundler/bundler/spec_set_spec.rb b/spec/bundler/bundler/spec_set_spec.rb
new file mode 100644
index 0000000000..6fedd38b50
--- /dev/null
+++ b/spec/bundler/bundler/spec_set_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::SpecSet do
+ let(:specs) do
+ [
+ build_spec("a", "1.0"),
+ build_spec("b", "1.0"),
+ build_spec("c", "1.1") do |s|
+ s.dep "a", "< 2.0"
+ s.dep "e", "> 0"
+ end,
+ build_spec("d", "2.0") do |s|
+ s.dep "a", "1.0"
+ s.dep "c", "~> 1.0"
+ end,
+ build_spec("e", "1.0.0.pre.1"),
+ ].flatten
+ end
+
+ subject { described_class.new(specs) }
+
+ context "enumerable methods" do
+ it "has a length" do
+ expect(subject.length).to eq(5)
+ end
+
+ it "has a size" do
+ expect(subject.size).to eq(5)
+ end
+ end
+
+ describe "#find_by_name_and_platform" do
+ let(:platform) { Gem::Platform.new("universal-darwin-64") }
+ let(:platform_spec) { build_spec("b", "2.0", platform).first }
+ let(:specs) do
+ [
+ build_spec("a", "1.0"),
+ platform_spec,
+ ].flatten
+ end
+
+ it "finds spec with given name and platform" do
+ spec = described_class.new(specs).find_by_name_and_platform("b", platform)
+ expect(spec).to eq platform_spec
+ end
+ end
+
+ describe "#merge" do
+ let(:other_specs) do
+ [
+ build_spec("f", "1.0"),
+ build_spec("g", "2.0"),
+ ].flatten
+ end
+
+ let(:other_spec_set) { described_class.new(other_specs) }
+
+ it "merges the items in each gemspec" do
+ new_spec_set = subject.merge(other_spec_set)
+ specs = new_spec_set.to_a.map(&:full_name)
+ expect(specs).to include("a-1.0")
+ expect(specs).to include("f-1.0")
+ end
+ end
+
+ describe "#to_a" do
+ it "returns the specs in order" do
+ expect(subject.to_a.map(&:full_name)).to eq %w[
+ a-1.0
+ b-1.0
+ e-1.0.0.pre.1
+ c-1.1
+ d-2.0
+ ]
+ end
+ end
+end
diff --git a/spec/bundler/bundler/stub_specification_spec.rb b/spec/bundler/bundler/stub_specification_spec.rb
new file mode 100644
index 0000000000..fb612813c2
--- /dev/null
+++ b/spec/bundler/bundler/stub_specification_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::StubSpecification do
+ let(:with_bundler_stub_spec) do
+ gemspec = Gem::Specification.new do |s|
+ s.name = "gemname"
+ s.version = "1.0.0"
+ s.loaded_from = __FILE__
+ s.extensions = "ext/gemname"
+ end
+
+ described_class.from_stub(gemspec)
+ end
+
+ describe "#from_stub" do
+ it "returns the same stub if already a Bundler::StubSpecification" do
+ stub = described_class.from_stub(with_bundler_stub_spec)
+ expect(stub).to be(with_bundler_stub_spec)
+ end
+ end
+
+ describe "#manually_installed?" do
+ it "returns true if installed_by_version is nil or 0" do
+ stub = described_class.from_stub(with_bundler_stub_spec)
+ expect(stub.manually_installed?).to be true
+ end
+
+ it "returns false if installed_by_version is greater than 0" do
+ stub = described_class.from_stub(with_bundler_stub_spec)
+ stub.installed_by_version = Gem::Version.new(1)
+ expect(stub.manually_installed?).to be false
+ end
+ end
+
+ describe "#missing_extensions?" do
+ it "returns false if manually_installed?" do
+ stub = described_class.from_stub(with_bundler_stub_spec)
+ expect(stub.missing_extensions?).to be false
+ end
+
+ it "returns true if not manually_installed?" do
+ stub = described_class.from_stub(with_bundler_stub_spec)
+ stub.installed_by_version = Gem::Version.new(1)
+ expect(stub.missing_extensions?).to be true
+ end
+ end
+end
diff --git a/spec/bundler/bundler/ui/shell_spec.rb b/spec/bundler/bundler/ui/shell_spec.rb
new file mode 100644
index 0000000000..15120a8a41
--- /dev/null
+++ b/spec/bundler/bundler/ui/shell_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::UI::Shell do
+ subject { described_class.new }
+
+ before { subject.level = "debug" }
+
+ describe "#info" do
+ before { subject.level = "info" }
+ it "prints to stdout" do
+ expect { subject.info("info") }.to output("info\n").to_stdout
+ end
+ end
+
+ describe "#confirm" do
+ before { subject.level = "confirm" }
+ it "prints to stdout" do
+ expect { subject.confirm("confirm") }.to output("confirm\n").to_stdout
+ end
+ end
+
+ describe "#warn" do
+ before { subject.level = "warn" }
+ it "prints to stderr" do
+ expect { subject.warn("warning") }.to output("warning\n").to_stderr
+ end
+ end
+
+ describe "#debug" do
+ it "prints to stdout" do
+ expect { subject.debug("debug") }.to output("debug\n").to_stdout
+ end
+ end
+
+ describe "#error" do
+ before { subject.level = "error" }
+
+ it "prints to stderr" do
+ expect { subject.error("error!!!") }.to output("error!!!\n").to_stderr
+ end
+
+ context "when stderr is closed" do
+ it "doesn't report anything" do
+ output = begin
+ result = StringIO.new
+ result.close
+
+ $stderr = result
+
+ subject.error("Something went wrong")
+
+ result.string
+ ensure
+ $stderr = STDERR
+ end
+ expect(output).to_not eq("Something went wrong")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/ui_spec.rb b/spec/bundler/bundler/ui_spec.rb
new file mode 100644
index 0000000000..6df0d2e290
--- /dev/null
+++ b/spec/bundler/bundler/ui_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::UI do
+ describe Bundler::UI::Silent do
+ it "has the same instance methods as Shell" do
+ shell = Bundler::UI::Shell
+ methods = proc do |cls|
+ cls.instance_methods.map do |i|
+ m = shell.instance_method(i)
+ [i, m.parameters]
+ end.sort_by(&:first)
+ end
+ expect(methods.call(described_class)).to eq(methods.call(shell))
+ end
+
+ it "has the same instance class as Shell" do
+ shell = Bundler::UI::Shell
+ methods = proc do |cls|
+ cls.methods.map do |i|
+ m = shell.method(i)
+ [i, m.parameters]
+ end.sort_by(&:first)
+ end
+ expect(methods.call(described_class)).to eq(methods.call(shell))
+ end
+ end
+
+ describe Bundler::UI::Shell do
+ let(:options) { {} }
+ subject { described_class.new(options) }
+ describe "debug?" do
+ it "returns a boolean" do
+ subject.level = :debug
+ expect(subject.debug?).to eq(true)
+
+ subject.level = :error
+ expect(subject.debug?).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/uri_credentials_filter_spec.rb b/spec/bundler/bundler/uri_credentials_filter_spec.rb
new file mode 100644
index 0000000000..466c1b8594
--- /dev/null
+++ b/spec/bundler/bundler/uri_credentials_filter_spec.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::URICredentialsFilter do
+ subject { described_class }
+
+ describe "#credential_filtered_uri" do
+ shared_examples_for "original type of uri is maintained" do
+ it "maintains same type for return value as uri input type" do
+ expect(subject.credential_filtered_uri(uri)).to be_kind_of(uri.class)
+ end
+ end
+
+ shared_examples_for "sensitive credentials in uri are filtered out" do
+ context "authentication using oauth credentials" do
+ context "specified via 'x-oauth-basic'" do
+ let(:credentials) { "oauth_token:x-oauth-basic@" }
+
+ it "returns the uri without the oauth token" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(Bundler::URI("https://x-oauth-basic@github.com/company/private-repo").to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+
+ context "specified via 'x'" do
+ let(:credentials) { "oauth_token:x@" }
+
+ it "returns the uri without the oauth token" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(Bundler::URI("https://x@github.com/company/private-repo").to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+ end
+
+ context "authentication using login credentials" do
+ let(:credentials) { "username1:hunter3@" }
+
+ it "returns the uri without the password" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(Bundler::URI("https://username1@github.com/company/private-repo").to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+
+ context "authentication without credentials" do
+ let(:credentials) { "" }
+
+ it "returns the same uri" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(uri.to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+ end
+
+ context "uri is a uri object" do
+ let(:uri) { Bundler::URI("https://#{credentials}github.com/company/private-repo") }
+
+ it_behaves_like "sensitive credentials in uri are filtered out"
+ end
+
+ context "uri is a uri string" do
+ let(:uri) { "https://#{credentials}github.com/company/private-repo" }
+
+ it_behaves_like "sensitive credentials in uri are filtered out"
+ end
+
+ context "uri is a non-uri format string (ex. path)" do
+ let(:uri) { "/path/to/repo" }
+
+ it "returns the same uri" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(uri.to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+
+ context "uri is nil" do
+ let(:uri) { nil }
+
+ it "returns nil" do
+ expect(subject.credential_filtered_uri(uri)).to be_nil
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+ end
+
+ describe "#credential_filtered_string" do
+ let(:str_to_filter) { "This is a git message containing a uri #{uri}!" }
+ let(:credentials) { "" }
+ let(:uri) { Bundler::URI("https://#{credentials}github.com/company/private-repo") }
+
+ context "with a uri that contains credentials" do
+ let(:credentials) { "oauth_token:x-oauth-basic@" }
+
+ it "returns the string without the sensitive credentials" do
+ expect(subject.credential_filtered_string(str_to_filter, uri)).to eq(
+ "This is a git message containing a uri https://x-oauth-basic@github.com/company/private-repo!"
+ )
+ end
+ end
+
+ context "that does not contains credentials" do
+ it "returns the same string" do
+ expect(subject.credential_filtered_string(str_to_filter, uri)).to eq(str_to_filter)
+ end
+ end
+
+ context "string to filter is nil" do
+ let(:str_to_filter) { nil }
+
+ it "returns nil" do
+ expect(subject.credential_filtered_string(str_to_filter, uri)).to be_nil
+ end
+ end
+
+ context "uri to filter out is nil" do
+ let(:uri) { nil }
+
+ it "returns the same string" do
+ expect(subject.credential_filtered_string(str_to_filter, uri)).to eq(str_to_filter)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/worker_spec.rb b/spec/bundler/bundler/worker_spec.rb
new file mode 100644
index 0000000000..e4ebbd2932
--- /dev/null
+++ b/spec/bundler/bundler/worker_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require "bundler/worker"
+
+RSpec.describe Bundler::Worker do
+ let(:size) { 5 }
+ let(:name) { "Spec Worker" }
+ let(:function) { proc {|object, worker_number| [object, worker_number] } }
+ subject { described_class.new(size, name, function) }
+
+ after { subject.stop }
+
+ describe "#initialize" do
+ context "when Thread.start raises ThreadError" do
+ it "raises when no threads can be created" do
+ allow(Thread).to receive(:start).and_raise(ThreadError, "error creating thread")
+
+ expect { subject.enq "a" }.to raise_error(Bundler::ThreadCreationError, "Failed to create threads for the Spec Worker worker: error creating thread")
+ end
+ end
+ end
+
+ describe "handling interrupts" do
+ let(:status) do
+ pid = Process.fork do
+ $stderr.reopen File.new("/dev/null", "w")
+ Signal.trap "INT", previous_interrupt_handler
+ subject.enq "a"
+ subject.stop unless interrupt_before_stopping
+ Process.kill "INT", Process.pid
+ end
+
+ Process.wait2(pid).last
+ end
+
+ before do
+ skip "requires Process.fork" unless Process.respond_to?(:fork)
+ end
+
+ context "when interrupted before stopping" do
+ let(:interrupt_before_stopping) { true }
+ let(:previous_interrupt_handler) { ->(*) { exit 0 } }
+
+ it "aborts" do
+ expect(status.exitstatus).to eq(1)
+ end
+ end
+
+ context "when interrupted after stopping" do
+ let(:interrupt_before_stopping) { false }
+
+ context "when the previous interrupt handler was the default" do
+ let(:previous_interrupt_handler) { "DEFAULT" }
+
+ it "uses the default interrupt handler" do
+ expect(status).to be_signaled
+ end
+ end
+
+ context "when the previous interrupt handler was customized" do
+ let(:previous_interrupt_handler) { ->(*) { exit 42 } }
+
+ it "restores the custom interrupt handler after stopping" do
+ expect(status.exitstatus).to eq(42)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/yaml_serializer_spec.rb b/spec/bundler/bundler/yaml_serializer_spec.rb
new file mode 100644
index 0000000000..1241c74bbf
--- /dev/null
+++ b/spec/bundler/bundler/yaml_serializer_spec.rb
@@ -0,0 +1,194 @@
+# frozen_string_literal: true
+
+require "bundler/yaml_serializer"
+
+RSpec.describe Bundler::YAMLSerializer do
+ subject(:serializer) { Bundler::YAMLSerializer }
+
+ describe "#dump" do
+ it "works for simple hash" do
+ hash = { "Q" => "Where does Thursday come before Wednesday? In the dictionary. :P" }
+
+ expected = strip_whitespace <<-YAML
+ ---
+ Q: "Where does Thursday come before Wednesday? In the dictionary. :P"
+ YAML
+
+ expect(serializer.dump(hash)).to eq(expected)
+ end
+
+ it "handles nested hash" do
+ hash = {
+ "nice-one" => {
+ "read_ahead" => "All generalizations are false, including this one",
+ },
+ }
+
+ expected = strip_whitespace <<-YAML
+ ---
+ nice-one:
+ read_ahead: "All generalizations are false, including this one"
+ YAML
+
+ expect(serializer.dump(hash)).to eq(expected)
+ end
+
+ it "array inside an hash" do
+ hash = {
+ "nested_hash" => {
+ "contains_array" => [
+ "Jack and Jill went up the hill",
+ "To fetch a pail of water.",
+ "Jack fell down and broke his crown,",
+ "And Jill came tumbling after.",
+ ],
+ },
+ }
+
+ expected = strip_whitespace <<-YAML
+ ---
+ nested_hash:
+ contains_array:
+ - "Jack and Jill went up the hill"
+ - "To fetch a pail of water."
+ - "Jack fell down and broke his crown,"
+ - "And Jill came tumbling after."
+ YAML
+
+ expect(serializer.dump(hash)).to eq(expected)
+ end
+ end
+
+ describe "#load" do
+ it "works for simple hash" do
+ yaml = strip_whitespace <<-YAML
+ ---
+ Jon: "Air is free dude!"
+ Jack: "Yes.. until you buy a bag of chips!"
+ YAML
+
+ hash = {
+ "Jon" => "Air is free dude!",
+ "Jack" => "Yes.. until you buy a bag of chips!",
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
+
+ it "works for nested hash" do
+ yaml = strip_whitespace <<-YAML
+ ---
+ baa:
+ baa: "black sheep"
+ have: "you any wool?"
+ yes: "merry have I"
+ three: "bags full"
+ YAML
+
+ hash = {
+ "baa" => {
+ "baa" => "black sheep",
+ "have" => "you any wool?",
+ "yes" => "merry have I",
+ },
+ "three" => "bags full",
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
+
+ it "handles colon in key/value" do
+ yaml = strip_whitespace <<-YAML
+ BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/: http://rubygems-mirror.org
+ YAML
+
+ expect(serializer.load(yaml)).to eq("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://rubygems-mirror.org")
+ end
+
+ it "handles arrays inside hashes" do
+ yaml = strip_whitespace <<-YAML
+ ---
+ nested_hash:
+ contains_array:
+ - "Why shouldn't you write with a broken pencil?"
+ - "Because it's pointless!"
+ YAML
+
+ hash = {
+ "nested_hash" => {
+ "contains_array" => [
+ "Why shouldn't you write with a broken pencil?",
+ "Because it's pointless!",
+ ],
+ },
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
+
+ it "handles windows-style CRLF line endings" do
+ yaml = strip_whitespace(<<-YAML).gsub("\n", "\r\n")
+ ---
+ nested_hash:
+ contains_array:
+ - "Why shouldn't you write with a broken pencil?"
+ - "Because it's pointless!"
+ - oh so silly
+ YAML
+
+ hash = {
+ "nested_hash" => {
+ "contains_array" => [
+ "Why shouldn't you write with a broken pencil?",
+ "Because it's pointless!",
+ "oh so silly",
+ ],
+ },
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
+ end
+
+ describe "against yaml lib" do
+ let(:hash) do
+ {
+ "a_joke" => {
+ "my-stand" => "I can totally keep secrets",
+ "but" => "The people I tell them to can't :P",
+ "wouldn't it be funny if this string were empty?" => "",
+ },
+ "more" => {
+ "first" => [
+ "Can a kangaroo jump higher than a house?",
+ "Of course, a house doesn't jump at all.",
+ ],
+ "second" => [
+ "What did the sea say to the sand?",
+ "Nothing, it simply waved.",
+ ],
+ "array with empty string" => [""],
+ },
+ "sales" => {
+ "item" => "A Parachute",
+ "description" => "Only used once, never opened.",
+ },
+ "one-more" => "I'd tell you a chemistry joke but I know I wouldn't get a reaction.",
+ }
+ end
+
+ context "#load" do
+ it "retrieves the original hash" do
+ require "yaml"
+ expect(serializer.load(YAML.dump(hash))).to eq(hash)
+ end
+ end
+
+ context "#dump" do
+ it "retrieves the original hash" do
+ require "yaml"
+ expect(YAML.load(serializer.dump(hash))).to eq(hash)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/cache/cache_path_spec.rb b/spec/bundler/cache/cache_path_spec.rb
new file mode 100644
index 0000000000..12385427b1
--- /dev/null
+++ b/spec/bundler/cache/cache_path_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle package" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ context "with --cache-path" do
+ it "caches gems at given path" do
+ bundle :cache, "cache-path" => "vendor/cache-foo"
+ expect(bundled_app("vendor/cache-foo/rack-1.0.0.gem")).to exist
+ end
+ end
+
+ context "with config cache_path" do
+ it "caches gems at given path" do
+ bundle "config set cache_path vendor/cache-foo"
+ bundle :cache
+ expect(bundled_app("vendor/cache-foo/rack-1.0.0.gem")).to exist
+ end
+ end
+
+ context "with absolute --cache-path" do
+ it "caches gems at given path" do
+ bundle :cache, "cache-path" => "/tmp/cache-foo"
+ expect(bundled_app("/tmp/cache-foo/rack-1.0.0.gem")).to exist
+ end
+ end
+end
diff --git a/spec/bundler/cache/gems_spec.rb b/spec/bundler/cache/gems_spec.rb
new file mode 100644
index 0000000000..63c00eba01
--- /dev/null
+++ b/spec/bundler/cache/gems_spec.rb
@@ -0,0 +1,327 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle cache" do
+ shared_examples_for "when there are only gemsources" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ system_gems "rack-1.0.0", :path => path
+ bundle :cache
+ end
+
+ it "copies the .gem file to vendor/cache" do
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "uses the cache as a source when installing gems" do
+ build_gem "omg", :path => bundled_app("vendor/cache")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "omg"
+ G
+
+ expect(the_bundle).to include_gems "omg 1.0.0"
+ end
+
+ it "uses the cache as a source when installing gems with --local" do
+ system_gems [], :path => default_bundle_path
+ bundle "install --local"
+
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+
+ it "does not reinstall gems from the cache if they exist on the system" do
+ build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+
+ it "does not reinstall gems from the cache if they exist in the bundle" do
+ system_gems "rack-1.0.0", :path => default_bundle_path
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+
+ bundle :install, :local => true
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+
+ it "creates a lockfile" do
+ cache_gems "rack-1.0.0"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "cache"
+
+ expect(bundled_app_lock).to exist
+ end
+ end
+
+ context "using system gems" do
+ before { bundle "config set path.system true" }
+ let(:path) { system_gem_path }
+ it_behaves_like "when there are only gemsources"
+ end
+
+ context "installing into a local path" do
+ before { bundle "config set path ./.bundle" }
+ let(:path) { local_gem_path }
+ it_behaves_like "when there are only gemsources"
+ end
+
+ describe "when there is a built-in gem", :ruby_repo do
+ let(:default_json_version) { ruby "gem 'json'; require 'json'; puts JSON::VERSION" }
+
+ before :each do
+ build_repo2 do
+ build_gem "json", default_json_version
+ end
+
+ build_gem "json", default_json_version, :to_system => true, :default => true
+ end
+
+ it "uses builtin gems when installing to system gems" do
+ bundle "config set path.system true"
+ install_gemfile %(source "#{file_uri_for(gem_repo1)}"; gem 'json', '#{default_json_version}'), :verbose => true
+ expect(out).to include("Using json #{default_json_version}")
+ end
+
+ it "caches remote and builtin gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'json', '#{default_json_version}'
+ gem 'rack', '1.0.0'
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist
+ end
+
+ it "caches builtin gems when cache_all_platforms is set" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "json"
+ G
+
+ bundle "config set cache_all_platforms true"
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist
+ end
+
+ it "doesn't make remote request after caching the gem" do
+ build_gem "builtin_gem_2", "1.0.2", :path => bundled_app("vendor/cache") do |s|
+ s.summary = "This builtin_gem is bundled with Ruby"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'builtin_gem_2', '1.0.2'
+ G
+
+ bundle "install --local"
+ expect(the_bundle).to include_gems("builtin_gem_2 1.0.2")
+ end
+
+ it "errors if the builtin gem isn't available to cache" do
+ bundle "config set path.system true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'json', '#{default_json_version}'
+ G
+
+ bundle :cache, :raise_on_error => false
+ expect(exitstatus).to_not eq(0)
+ expect(err).to include("json-#{default_json_version} is built in to Ruby, and can't be cached")
+ end
+ end
+
+ describe "when there are also git sources" do
+ before do
+ build_git "foo"
+ system_gems "rack-1.0.0"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ gem 'rack'
+ G
+ end
+
+ it "still works" do
+ bundle :cache
+
+ system_gems []
+ bundle "install --local"
+
+ expect(the_bundle).to include_gems("rack 1.0.0", "foo 1.0")
+ end
+
+ it "should not explode if the lockfile is not present" do
+ FileUtils.rm(bundled_app_lock)
+
+ bundle :cache
+
+ expect(bundled_app_lock).to exist
+ end
+ end
+
+ describe "when previously cached" do
+ before :each do
+ build_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ gem "actionpack"
+ G
+ bundle :cache
+ expect(cached_gem("rack-1.0.0")).to exist
+ expect(cached_gem("actionpack-2.3.2")).to exist
+ expect(cached_gem("activesupport-2.3.2")).to exist
+ end
+
+ it "re-caches during install" do
+ cached_gem("rack-1.0.0").rmtree
+ bundle :install
+ expect(out).to include("Updating files in vendor/cache")
+ expect(cached_gem("rack-1.0.0")).to exist
+ end
+
+ it "adds and removes when gems are updated" do
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ bundle "update", :all => true
+ expect(cached_gem("rack-1.2")).to exist
+ expect(cached_gem("rack-1.0.0")).not_to exist
+ end
+
+ it "adds new gems and dependencies" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails"
+ G
+ expect(cached_gem("rails-2.3.2")).to exist
+ expect(cached_gem("activerecord-2.3.2")).to exist
+ end
+
+ it "removes .gems for removed gems and dependencies" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ G
+ expect(cached_gem("rack-1.0.0")).to exist
+ expect(cached_gem("actionpack-2.3.2")).not_to exist
+ expect(cached_gem("activesupport-2.3.2")).not_to exist
+ end
+
+ it "removes .gems when gem changes to git source" do
+ build_git "rack"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack", :git => "#{lib_path("rack-1.0")}"
+ gem "actionpack"
+ G
+ expect(cached_gem("rack-1.0.0")).not_to exist
+ expect(cached_gem("actionpack-2.3.2")).to exist
+ expect(cached_gem("activesupport-2.3.2")).to exist
+ end
+
+ it "doesn't remove gems that are for another platform" do
+ simulate_platform "java" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+
+ bundle :cache
+ expect(cached_gem("platform_specific-1.0-java")).to exist
+ end
+
+ simulate_new_machine
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+
+ expect(cached_gem("platform_specific-1.0-#{Bundler.local_platform}")).to exist
+ expect(cached_gem("platform_specific-1.0-java")).to exist
+ end
+
+ it "doesn't remove gems with mismatched :rubygems_version or :date" do
+ cached_gem("rack-1.0.0").rmtree
+ build_gem "rack", "1.0.0",
+ :path => bundled_app("vendor/cache"),
+ :rubygems_version => "1.3.2"
+ simulate_new_machine
+
+ bundle :install
+ expect(cached_gem("rack-1.0.0")).to exist
+ end
+
+ it "handles directories and non .gem files in the cache" do
+ bundled_app("vendor/cache/foo").mkdir
+ File.open(bundled_app("vendor/cache/bar"), "w") {|f| f.write("not a gem") }
+ bundle :cache
+ end
+
+ it "does not say that it is removing gems when it isn't actually doing so" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ bundle "cache"
+ bundle "install"
+ expect(out).not_to match(/removing/i)
+ end
+
+ it "does not warn about all if it doesn't have any git/path dependency" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ bundle "cache"
+ expect(out).not_to match(/\-\-all/)
+ end
+
+ it "should install gems with the name bundler in them (that aren't bundler)" do
+ build_gem "foo-bundler", "1.0",
+ :path => bundled_app("vendor/cache")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo-bundler"
+ G
+
+ expect(the_bundle).to include_gems "foo-bundler 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/cache/git_spec.rb b/spec/bundler/cache/git_spec.rb
new file mode 100644
index 0000000000..890ba88605
--- /dev/null
+++ b/spec/bundler/cache/git_spec.rb
@@ -0,0 +1,276 @@
+# frozen_string_literal: true
+
+RSpec.describe "git base name" do
+ it "base_name should strip private repo uris" do
+ source = Bundler::Source::Git.new("uri" => "git@github.com:bundler.git")
+ expect(source.send(:base_name)).to eq("bundler")
+ end
+
+ it "base_name should strip network share paths" do
+ source = Bundler::Source::Git.new("uri" => "//MachineName/ShareFolder")
+ expect(source.send(:base_name)).to eq("ShareFolder")
+ end
+end
+
+RSpec.describe "bundle cache with git" do
+ it "copies repository to vendor cache and uses it" do
+ git = build_git "foo"
+ ref = git.ref_for("main", 11)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.bundlecache")).to be_file
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "copies repository to vendor cache and uses it even when configured with `path`" do
+ git = build_git "foo"
+ ref = git.ref_for("main", 11)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config set --local path vendor/bundle"
+ bundle "install"
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "runs twice without exploding" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+ bundle :cache
+
+ expect(out).to include "Updating files in vendor/cache"
+ FileUtils.rm_rf lib_path("foo-1.0")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "tracks updates" do
+ git = build_git "foo"
+ old_ref = git.ref_for("main", 11)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+
+ update_git "foo" do |s|
+ s.write "lib/foo.rb", "puts :CACHE"
+ end
+
+ ref = git.ref_for("main", 11)
+ expect(ref).not_to eq(old_ref)
+
+ bundle "update", :all => true
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{old_ref}")).not_to exist
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ run "require 'foo'"
+ expect(out).to eq("CACHE")
+ end
+
+ it "tracks updates when specifying the gem" do
+ git = build_git "foo"
+ old_ref = git.ref_for("main", 11)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+
+ update_git "foo" do |s|
+ s.write "lib/foo.rb", "puts :CACHE"
+ end
+
+ ref = git.ref_for("main", 11)
+ expect(ref).not_to eq(old_ref)
+
+ bundle "update foo"
+
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{old_ref}")).not_to exist
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ run "require 'foo'"
+ expect(out).to eq("CACHE")
+ end
+
+ it "uses the local repository to generate the cache" do
+ git = build_git "foo"
+ ref = git.ref_for("main", 11)
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => '#{lib_path("foo-invalid")}', :branch => :main
+ G
+
+ bundle %(config set local.foo #{lib_path("foo-1.0")})
+ bundle "install"
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/foo-invalid-#{ref}")).to exist
+
+ # Updating the local still uses the local.
+ update_git "foo" do |s|
+ s.write "lib/foo.rb", "puts :LOCAL"
+ end
+
+ run "require 'foo'"
+ expect(out).to eq("LOCAL")
+ end
+
+ it "copies repository to vendor cache, including submodules" do
+ # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
+ system(*%W[git config --global protocol.file.allow always])
+
+ build_git "submodule", "1.0"
+
+ git = build_git "has_submodule", "1.0" do |s|
+ s.add_dependency "submodule"
+ end
+
+ sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0")
+ sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("has_submodule-1.0")}", :submodules => true do
+ gem "has_submodule"
+ end
+ G
+
+ ref = git.ref_for("main", 11)
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/has_submodule-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/has_submodule-1.0-#{ref}/submodule-1.0")).to exist
+ expect(the_bundle).to include_gems "has_submodule 1.0"
+ end
+
+ it "caches pre-evaluated gemspecs" do
+ git = build_git "foo"
+
+ # Insert a gemspec method that shells out
+ spec_lines = lib_path("foo-1.0/foo.gemspec").read.split("\n")
+ spec_lines.insert(-2, "s.description = `echo bob`")
+ update_git("foo") {|s| s.write "foo.gemspec", spec_lines.join("\n") }
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle "config set cache_all true"
+ bundle :cache
+
+ ref = git.ref_for("main", 11)
+ gemspec = bundled_app("vendor/cache/foo-1.0-#{ref}/foo.gemspec").read
+ expect(gemspec).to_not match("`echo bob`")
+ end
+
+ it "can install after bundle cache with git not installed" do
+ build_git "foo"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle "config set cache_all true"
+ bundle :cache, "all-platforms" => true, :install => false
+
+ simulate_new_machine
+ with_path_as "" do
+ bundle "config set deployment true"
+ bundle :install, :local => true
+ expect(the_bundle).to include_gem "foo 1.0"
+ end
+ end
+
+ it "respects the --no-install flag" do
+ git = build_git "foo", &:add_c_extension
+ ref = git.ref_for("main", 11)
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle "config set cache_all true"
+
+ # The algorithm for the cache location for a git checkout is
+ # in Bundle::Source::Git#cache_path
+ cache_path_name = "foo-1.0-#{Digest(:SHA1).hexdigest(lib_path("foo-1.0").to_s)}"
+
+ # Run this test twice. This is because materially different codepaths
+ # will get hit the second time around.
+ # The first time, Bundler::Sources::Git#install_path is set to the system
+ # wide cache directory bundler/gems; the second time, it's set to the
+ # vendor/cache directory. We don't want the native extension to appear in
+ # either of these places, so run the `bundle cache` command twice.
+ 2.times do
+ bundle :cache, "all-platforms" => true, :install => false
+
+ # it did _NOT_ actually install the gem - neither in $GEM_HOME (bundler 2 mode),
+ # nor in .bundle (bundler 3 mode)
+ expect(Pathname.new(File.join(default_bundle_path, "gems/foo-1.0-#{ref}"))).to_not exist
+ # it _did_ cache the gem in vendor/
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ # it did _NOT_ build the gems extensions in the vendor/ dir
+ expect(Dir[bundled_app("vendor/cache/foo-1.0-#{ref}/lib/foo_c*")]).to be_empty
+ # it _did_ cache the git checkout
+ expect(default_cache_path("git", cache_path_name)).to exist
+ # And the checkout is a bare checkout
+ expect(default_cache_path("git", cache_path_name, "HEAD")).to exist
+ end
+
+ # Subsequently installing the gem should compile it.
+ # _currently_, the gem gets compiled in vendor/cache, and vendor/cache is added
+ # to the $LOAD_PATH for git extensions, so it all kind of "works". However, in the
+ # future we would like to stop adding vendor/cache to the $LOAD_PATH for git extensions
+ # and instead treat them identically to normal gems (where the gem install location,
+ # not the cache location, is added to $LOAD_PATH).
+ # Verify that the compilation worked and the result is in $LOAD_PATH by simply attempting
+ # to require it; that should make sure this spec does not break if the load path behaviour
+ # is changed.
+ bundle :install, :local => true
+ ruby <<~R, :raise_on_error => false
+ require 'bundler/setup'
+ require 'foo_c'
+ R
+ expect(last_command).to_not be_failure
+ end
+end
diff --git a/spec/bundler/cache/path_spec.rb b/spec/bundler/cache/path_spec.rb
new file mode 100644
index 0000000000..2ad136a008
--- /dev/null
+++ b/spec/bundler/cache/path_spec.rb
@@ -0,0 +1,169 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle cache with path" do
+ it "is no-op when the path is within the bundle" do
+ build_lib "foo", :path => bundled_app("lib/foo")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => '#{bundled_app("lib/foo")}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "copies when the path is outside the bundle " do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo-1.0")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0/.bundlecache")).to be_file
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "copies when the path is outside the bundle and the paths intersect" do
+ libname = File.basename(bundled_app) + "_gem"
+ libpath = File.join(File.dirname(bundled_app), libname)
+
+ build_lib libname, :path => libpath
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "#{libname}", :path => '#{libpath}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+ expect(bundled_app("vendor/cache/#{libname}")).to exist
+ expect(bundled_app("vendor/cache/#{libname}/.bundlecache")).to be_file
+
+ expect(the_bundle).to include_gems "#{libname} 1.0"
+ end
+
+ it "updates the path on each cache" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+
+ build_lib "foo" do |s|
+ s.write "lib/foo.rb", "puts :CACHE"
+ end
+
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/foo-1.0")).to exist
+
+ run "require 'foo'"
+ expect(out).to eq("CACHE")
+ end
+
+ it "removes stale entries cache" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/foo-1.0")).to exist
+
+ build_lib "bar"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bar", :path => '#{lib_path("bar-1.0")}'
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
+ end
+
+ it "does not cache path gems by default", :bundler => "< 3" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :cache
+ expect(err).to be_empty
+ expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
+ end
+
+ it "caches path gems by default", :bundler => "3" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :cache
+ expect(err).to be_empty
+ expect(bundled_app("vendor/cache/foo-1.0")).to exist
+ end
+
+ it "stores the given flag" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+ build_lib "bar"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ gem "bar", :path => '#{lib_path("bar-1.0")}'
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/bar-1.0")).to exist
+ end
+
+ it "can rewind chosen configuration" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+ build_lib "baz"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ gem "baz", :path => '#{lib_path("baz-1.0")}'
+ G
+
+ bundle "cache --no-all", :raise_on_error => false
+ expect(bundled_app("vendor/cache/baz-1.0")).not_to exist
+ end
+end
diff --git a/spec/bundler/cache/platform_spec.rb b/spec/bundler/cache/platform_spec.rb
new file mode 100644
index 0000000000..128278956c
--- /dev/null
+++ b/spec/bundler/cache/platform_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle cache with multiple platforms" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ platforms :mri, :rbx do
+ gem "rack", "1.0.0"
+ end
+
+ platforms :jruby do
+ gem "activesupport", "2.3.5"
+ end
+ G
+
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (1.0.0)
+ activesupport (2.3.5)
+
+ PLATFORMS
+ ruby
+ java
+
+ DEPENDENCIES
+ rack (1.0.0)
+ activesupport (2.3.5)
+ G
+
+ cache_gems "rack-1.0.0", "activesupport-2.3.5"
+ end
+
+ it "ensures that a successful bundle install does not delete gems for other platforms" do
+ bundle "install"
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/activesupport-2.3.5.gem")).to exist
+ end
+
+ it "ensures that a successful bundle update does not delete gems for other platforms" do
+ bundle "update", :all => true
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/activesupport-2.3.5.gem")).to exist
+ end
+end
diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb
new file mode 100644
index 0000000000..5a5b534e8d
--- /dev/null
+++ b/spec/bundler/commands/add_spec.rb
@@ -0,0 +1,305 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle add" do
+ before :each do
+ build_repo2 do
+ build_gem "foo", "1.1"
+ build_gem "foo", "2.0"
+ build_gem "baz", "1.2.3"
+ build_gem "bar", "0.12.3"
+ build_gem "cat", "0.12.3.pre"
+ build_gem "dog", "1.1.3.pre"
+ end
+
+ build_git "foo", "2.0"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "weakling", "~> 0.0.1"
+ G
+ end
+
+ context "when no gems are specified" do
+ it "shows error" do
+ bundle "add", :raise_on_error => false
+
+ expect(err).to include("Please specify gems to add")
+ end
+ end
+
+ describe "without version specified" do
+ it "version requirement becomes ~> major.minor.patch when resolved version is < 1.0" do
+ bundle "add 'bar'"
+ expect(bundled_app_gemfile.read).to match(/gem "bar", "~> 0.12.3"/)
+ expect(the_bundle).to include_gems "bar 0.12.3"
+ end
+
+ it "version requirement becomes ~> major.minor when resolved version is > 1.0" do
+ bundle "add 'baz'"
+ expect(bundled_app_gemfile.read).to match(/gem "baz", "~> 1.2"/)
+ expect(the_bundle).to include_gems "baz 1.2.3"
+ end
+
+ it "version requirement becomes ~> major.minor.patch.pre when resolved version is < 1.0" do
+ bundle "add 'cat'"
+ expect(bundled_app_gemfile.read).to match(/gem "cat", "~> 0.12.3.pre"/)
+ expect(the_bundle).to include_gems "cat 0.12.3.pre"
+ end
+
+ it "version requirement becomes ~> major.minor.pre when resolved version is > 1.0.pre" do
+ bundle "add 'dog'"
+ expect(bundled_app_gemfile.read).to match(/gem "dog", "~> 1.1.pre"/)
+ expect(the_bundle).to include_gems "dog 1.1.3.pre"
+ end
+ end
+
+ describe "with --version" do
+ it "adds dependency of specified version and runs install" do
+ bundle "add 'foo' --version='~> 1.0'"
+ expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 1.0"/)
+ expect(the_bundle).to include_gems "foo 1.1"
+ end
+
+ it "adds multiple version constraints when specified" do
+ requirements = ["< 3.0", "> 1.0"]
+ bundle "add 'foo' --version='#{requirements.join(", ")}'"
+ expect(bundled_app_gemfile.read).to match(/gem "foo", #{Gem::Requirement.new(requirements).as_list.map(&:dump).join(', ')}/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --require" do
+ it "adds the require param for the gem" do
+ bundle "add 'foo' --require=foo/engine"
+ expect(bundled_app_gemfile.read).to match(%r{gem "foo",(?: .*,) :require => "foo\/engine"})
+ end
+
+ it "converts false to a boolean" do
+ bundle "add 'foo' --require=false"
+ expect(bundled_app_gemfile.read).to match(/gem "foo",(?: .*,) :require => false/)
+ end
+ end
+
+ describe "with --group" do
+ it "adds dependency for the specified group" do
+ bundle "add 'foo' --group='development'"
+ expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :group => :development/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+
+ it "adds dependency to more than one group" do
+ bundle "add 'foo' --group='development, test'"
+ expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :groups => \[:development, :test\]/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --source" do
+ it "adds dependency with specified source" do
+ bundle "add 'foo' --source='#{file_uri_for(gem_repo2)}'"
+
+ expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :source => "#{file_uri_for(gem_repo2)}"/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --path" do
+ it "adds dependency with specified path" do
+ bundle "add 'foo' --path='#{lib_path("foo-2.0")}'"
+
+ expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :path => "#{lib_path("foo-2.0")}"/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --git" do
+ it "adds dependency with specified git source" do
+ bundle "add foo --git=#{lib_path("foo-2.0")}"
+
+ expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}"/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --git and --branch" do
+ before do
+ update_git "foo", "2.0", :branch => "test"
+ end
+
+ it "adds dependency with specified git source and branch" do
+ bundle "add foo --git=#{lib_path("foo-2.0")} --branch=test"
+
+ expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :branch => "test"/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --git and --ref" do
+ it "adds dependency with specified git source and branch" do
+ bundle "add foo --git=#{lib_path("foo-2.0")} --ref=#{revision_for(lib_path("foo-2.0"))}"
+
+ expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2\.0", :git => "#{lib_path("foo-2.0")}", :ref => "#{revision_for(lib_path("foo-2.0"))}"/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --github" do
+ it "adds dependency with specified github source", :realworld do
+ bundle "add rake --github=ruby/rake"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.0", :github => "ruby\/rake"})
+ end
+ end
+
+ describe "with --github and --branch" do
+ it "adds dependency with specified github source and branch", :realworld do
+ bundle "add rake --github=ruby/rake --branch=master"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.0", :github => "ruby\/rake", :branch => "master"})
+ end
+ end
+
+ describe "with --github and --ref" do
+ it "adds dependency with specified github source and ref", :realworld do
+ bundle "add rake --github=ruby/rake --ref=5c60da8"
+
+ expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.0", :github => "ruby\/rake", :ref => "5c60da8"})
+ end
+ end
+
+ describe "with --skip-install" do
+ it "adds gem to Gemfile but is not installed" do
+ bundle "add foo --skip-install --version=2.0"
+
+ expect(bundled_app_gemfile.read).to match(/gem "foo", "= 2.0"/)
+ expect(the_bundle).to_not include_gems "foo 2.0"
+ end
+ end
+
+ it "using combination of short form options works like long form" do
+ bundle "add 'foo' -s='#{file_uri_for(gem_repo2)}' -g='development' -v='~>1.0'"
+ expect(bundled_app_gemfile.read).to include %(gem "foo", "~> 1.0", :group => :development, :source => "#{file_uri_for(gem_repo2)}")
+ expect(the_bundle).to include_gems "foo 1.1"
+ end
+
+ it "shows error message when version is not formatted correctly" do
+ bundle "add 'foo' -v='~>1 . 0'", :raise_on_error => false
+ expect(err).to match("Invalid gem requirement pattern '~>1 . 0'")
+ end
+
+ it "shows error message when gem cannot be found" do
+ bundle "config set force_ruby_platform true"
+ bundle "add 'werk_it'", :raise_on_error => false
+ expect(err).to match("Could not find gem 'werk_it' in")
+
+ bundle "add 'werk_it' -s='#{file_uri_for(gem_repo2)}'", :raise_on_error => false
+ expect(err).to match("Could not find gem 'werk_it' in rubygems repository")
+ end
+
+ it "shows error message when source cannot be reached" do
+ bundle "add 'baz' --source='http://badhostasdf'", :raise_on_error => false
+ expect(err).to include("Could not reach host badhostasdf. Check your network connection and try again.")
+
+ bundle "add 'baz' --source='file://does/not/exist'", :raise_on_error => false
+ expect(err).to include("Could not fetch specs from file://does/not/exist/")
+ end
+
+ describe "with --optimistic" do
+ it "adds optimistic version" do
+ bundle "add 'foo' --optimistic"
+ expect(bundled_app_gemfile.read).to include %(gem "foo", ">= 2.0")
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --strict option" do
+ it "adds strict version" do
+ bundle "add 'foo' --strict"
+ expect(bundled_app_gemfile.read).to include %(gem "foo", "= 2.0")
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with no option" do
+ it "adds pessimistic version" do
+ bundle "add 'foo'"
+ expect(bundled_app_gemfile.read).to include %(gem "foo", "~> 2.0")
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --optimistic and --strict" do
+ it "throws error" do
+ bundle "add 'foo' --strict --optimistic", :raise_on_error => false
+
+ expect(err).to include("You can not specify `--strict` and `--optimistic` at the same time")
+ end
+ end
+
+ context "multiple gems" do
+ it "adds multiple gems to gemfile" do
+ bundle "add bar baz"
+
+ expect(bundled_app_gemfile.read).to match(/gem "bar", "~> 0.12.3"/)
+ expect(bundled_app_gemfile.read).to match(/gem "baz", "~> 1.2"/)
+ end
+
+ it "throws error if any of the specified gems are present in the gemfile with different version" do
+ bundle "add weakling bar", :raise_on_error => false
+
+ expect(err).to include("You cannot specify the same gem twice with different version requirements")
+ expect(err).to include("You specified: weakling (~> 0.0.1) and weakling (>= 0).")
+ end
+ end
+
+ describe "when a gem is added which is already specified in Gemfile with version" do
+ it "shows an error when added with different version requirement" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack", "1.0"
+ G
+
+ bundle "add 'rack' --version=1.1", :raise_on_error => false
+
+ expect(err).to include("You cannot specify the same gem twice with different version requirements")
+ expect(err).to include("If you want to update the gem version, run `bundle update rack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive")
+ end
+
+ it "shows error when added without version requirements" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack", "1.0"
+ G
+
+ bundle "add 'rack'", :raise_on_error => false
+
+ expect(err).to include("Gem already added.")
+ expect(err).to include("You cannot specify the same gem twice with different version requirements")
+ expect(err).not_to include("If you want to update the gem version, run `bundle update rack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive")
+ end
+ end
+
+ describe "when a gem is added which is already specified in Gemfile without version" do
+ it "shows an error when added with different version requirement" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ G
+
+ bundle "add 'rack' --version=1.1", :raise_on_error => false
+
+ expect(err).to include("You cannot specify the same gem twice with different version requirements")
+ expect(err).to include("If you want to update the gem version, run `bundle update rack`.")
+ expect(err).not_to include("You may also need to change the version requirement specified in the Gemfile if it's too restrictive")
+ end
+ end
+
+ describe "when a gem is added and cache exists" do
+ it "caches all new dependencies added for the specified gem" do
+ bundle :cache
+
+ bundle "add 'rack' --version=1.0.0"
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+ end
+end
diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb
new file mode 100644
index 0000000000..bfbef58181
--- /dev/null
+++ b/spec/bundler/commands/binstubs_spec.rb
@@ -0,0 +1,556 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle binstubs <gem>" do
+ context "when the gem exists in the lockfile" do
+ it "sets up the binstub" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack"
+
+ expect(bundled_app("bin/rackup")).to exist
+ end
+
+ it "does not install other binstubs" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rails"
+ G
+
+ bundle "binstubs rails"
+
+ expect(bundled_app("bin/rackup")).not_to exist
+ expect(bundled_app("bin/rails")).to exist
+ end
+
+ it "does install multiple binstubs" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rails"
+ G
+
+ bundle "binstubs rails rack"
+
+ expect(bundled_app("bin/rackup")).to exist
+ expect(bundled_app("bin/rails")).to exist
+ end
+
+ it "allows installing all binstubs" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ bundle :binstubs, :all => true
+
+ expect(bundled_app("bin/rails")).to exist
+ expect(bundled_app("bin/rake")).to exist
+ end
+
+ it "allows installing binstubs for all platforms" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack --all-platforms"
+
+ expect(bundled_app("bin/rackup")).to exist
+ expect(bundled_app("bin/rackup.cmd")).to exist
+ end
+
+ it "displays an error when used without any gem" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "binstubs", :raise_on_error => false
+ expect(exitstatus).to eq(1)
+ expect(err).to include("`bundle binstubs` needs at least one gem to run.")
+ end
+
+ it "displays an error when used with --all and gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack", :all => true, :raise_on_error => false
+ expect(last_command).to be_failure
+ expect(err).to include("Cannot specify --all with specific gems")
+ end
+
+ context "when generating bundle binstub outside bundler" do
+ it "should abort" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack"
+
+ File.open(bundled_app("bin/bundle"), "wb") do |file|
+ file.print "OMG"
+ end
+
+ sys_exec "bin/rackup", :raise_on_error => false
+
+ expect(err).to include("was not generated by Bundler")
+ end
+ end
+
+ context "the bundle binstub" do
+ before do
+ pristine_system_gems "bundler-#{system_bundler_version}"
+ build_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "prints_loaded_gems", "1.0" do |s|
+ s.executables = "print_loaded_gems"
+ s.bindir = "exe"
+ s.write "exe/print_loaded_gems", <<-R
+ specs = Gem.loaded_specs.values.reject {|s| s.default_gem? }
+ puts specs.map(&:full_name).sort.inspect
+ R
+ end
+ end
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ gem "prints_loaded_gems"
+ G
+ bundle "binstubs bundler rack prints_loaded_gems"
+ end
+
+ let(:system_bundler_version) { Bundler::VERSION }
+
+ it "runs bundler" do
+ sys_exec "bin/bundle install", :env => { "DEBUG" => "1" }
+ expect(out).to include %(Using bundler #{system_bundler_version}\n)
+ end
+
+ context "when BUNDLER_VERSION is set" do
+ it "runs the correct version of bundler" do
+ sys_exec "bin/bundle install", :env => { "BUNDLER_VERSION" => "999.999.999" }, :raise_on_error => false
+ expect(exitstatus).to eq(42)
+ expect(err).to include("Activating bundler (999.999.999) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`")
+ end
+
+ it "runs the correct version of bundler even if a higher version is installed" do
+ system_gems "bundler-999.999.998", "bundler-999.999.999"
+
+ sys_exec "bin/bundle install", :env => { "BUNDLER_VERSION" => "999.999.998", "DEBUG" => "1" }, :raise_on_error => false
+ expect(out).to include %(Using bundler 999.999.998\n)
+ end
+ end
+
+ context "when a lockfile exists with a locked bundler version" do
+ context "and the version is newer" do
+ before do
+ lockfile lockfile.gsub(system_bundler_version, "999.999")
+ end
+
+ it "runs the correct version of bundler" do
+ sys_exec "bin/bundle install", :raise_on_error => false
+ expect(exitstatus).to eq(42)
+ expect(err).to include("Activating bundler (~> 999.999) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`")
+ end
+ end
+
+ context "and the version is newer when given `gems.rb` and `gems.locked`" do
+ before do
+ gemfile bundled_app("gems.rb"), gemfile
+ lockfile bundled_app("gems.locked"), lockfile.gsub(system_bundler_version, "999.999")
+ end
+
+ it "runs the correct version of bundler" do
+ sys_exec "bin/bundle install", :env => { "BUNDLE_GEMFILE" => "gems.rb" }, :raise_on_error => false
+
+ expect(exitstatus).to eq(42)
+ expect(err).to include("Activating bundler (~> 999.999) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`")
+ end
+ end
+
+ context "and the version is older and a different major" do
+ let(:system_bundler_version) { "55" }
+
+ before do
+ lockfile lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 44.0")
+ end
+
+ it "runs the correct version of bundler" do
+ sys_exec "bin/bundle install", :raise_on_error => false
+ expect(exitstatus).to eq(42)
+ expect(err).to include("Activating bundler (~> 44.0) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 44.0'`")
+ end
+ end
+
+ context "and the version is older and a different major when given `gems.rb` and `gems.locked`" do
+ let(:system_bundler_version) { "55" }
+
+ before do
+ gemfile bundled_app("gems.rb"), gemfile
+ lockfile bundled_app("gems.locked"), lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 44.0")
+ end
+
+ it "runs the correct version of bundler" do
+ sys_exec "bin/bundle install", :env => { "BUNDLE_GEMFILE" => "gems.rb" }, :raise_on_error => false
+ expect(exitstatus).to eq(42)
+ expect(err).to include("Activating bundler (~> 44.0) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 44.0'`")
+ end
+ end
+
+ context "and the version is older and the same major" do
+ let(:system_bundler_version) { "2.999.999" }
+
+ before do
+ lockfile lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 2.3.0")
+ end
+
+ it "installs and runs the exact version of bundler", :rubygems => ">= 3.3.0.dev", :realworld => true do
+ sys_exec "bin/bundle install --verbose", :artifice => "vcr"
+ expect(exitstatus).not_to eq(42)
+ expect(out).to include("Bundler 2.999.999 is running, but your lockfile was generated with 2.3.0. Installing Bundler 2.3.0 and restarting using that version.")
+ expect(out).to include("Using bundler 2.3.0")
+ expect(err).not_to include("Activating bundler (~> 2.3.0) failed:")
+ end
+
+ it "runs the available version of bundler", :rubygems => "< 3.3.0.dev" do
+ sys_exec "bin/bundle install --verbose"
+ expect(exitstatus).not_to eq(42)
+ expect(out).not_to include("Bundler 2.999.999 is running, but your lockfile was generated with 2.3.0. Installing Bundler 2.3.0 and restarting using that version.")
+ expect(out).to include("Using bundler 2.999.999")
+ expect(err).not_to include("Activating bundler (~> 2.3.0) failed:")
+ end
+ end
+
+ context "and the version is a pre-releaser" do
+ let(:system_bundler_version) { "55" }
+
+ before do
+ lockfile lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 2.12.0.a")
+ end
+
+ it "runs the correct version of bundler when the version is a pre-release" do
+ sys_exec "bin/bundle install", :raise_on_error => false
+ expect(exitstatus).to eq(42)
+ expect(err).to include("Activating bundler (~> 2.12.a) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 2.12.a'`")
+ end
+ end
+ end
+
+ context "when update --bundler is called" do
+ before { lockfile.gsub(system_bundler_version, "1.1.1") }
+
+ it "calls through to the latest bundler version", :realworld do
+ sys_exec "bin/bundle update --bundler", :env => { "DEBUG" => "1" }
+ using_bundler_line = /Using bundler ([\w\.]+)\n/.match(out)
+ expect(using_bundler_line).to_not be_nil
+ latest_version = using_bundler_line[1]
+ expect(Gem::Version.new(latest_version)).to be >= Gem::Version.new(system_bundler_version)
+ end
+
+ it "calls through to the explicit bundler version" do
+ sys_exec "bin/bundle update --bundler=999.999.999", :raise_on_error => false
+ expect(exitstatus).to eq(42)
+ expect(err).to include("Activating bundler (999.999.999) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`")
+ end
+ end
+
+ context "without a lockfile" do
+ it "falls back to the latest installed bundler" do
+ FileUtils.rm bundled_app_lock
+ sys_exec "bin/bundle install", :env => { "DEBUG" => "1" }
+ expect(out).to include "Using bundler #{system_bundler_version}\n"
+ end
+ end
+
+ context "using another binstub" do
+ it "loads all gems" do
+ sys_exec bundled_app("bin/print_loaded_gems").to_s
+ expect(out).to eq %(["bundler-#{Bundler::VERSION}", "prints_loaded_gems-1.0", "rack-1.2"])
+ end
+
+ context "when requesting a different bundler version" do
+ before { lockfile lockfile.gsub(Bundler::VERSION, "999.999.999") }
+
+ it "attempts to load that version" do
+ sys_exec bundled_app("bin/rackup").to_s, :raise_on_error => false
+ expect(exitstatus).to eq(42)
+ expect(err).to include("Activating bundler (~> 999.999) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`")
+ end
+ end
+ end
+ end
+
+ it "installs binstubs from git gems" do
+ FileUtils.mkdir_p(lib_path("foo/bin"))
+ FileUtils.touch(lib_path("foo/bin/foo"))
+ build_git "foo", "1.0", :path => lib_path("foo") do |s|
+ s.executables = %w[foo]
+ end
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ G
+
+ bundle "binstubs foo"
+
+ expect(bundled_app("bin/foo")).to exist
+ end
+
+ it "installs binstubs from path gems" do
+ FileUtils.mkdir_p(lib_path("foo/bin"))
+ FileUtils.touch(lib_path("foo/bin/foo"))
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.executables = %w[foo]
+ end
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ bundle "binstubs foo"
+
+ expect(bundled_app("bin/foo")).to exist
+ end
+
+ it "sets correct permissions for binstubs" do
+ with_umask(0o002) do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack"
+ binary = bundled_app("bin/rackup")
+ expect(File.stat(binary).mode.to_s(8)).to eq(Gem.win_platform? ? "100644" : "100775")
+ end
+ end
+
+ context "when using --shebang" do
+ it "sets the specified shebang for the binstub" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack --shebang jruby"
+ expect(File.readlines(bundled_app("bin/rackup")).first).to eq("#!/usr/bin/env jruby\n")
+ end
+ end
+ end
+
+ context "when the gem doesn't exist" do
+ it "displays an error with correct status" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ bundle "binstubs doesnt_exist", :raise_on_error => false
+
+ expect(exitstatus).to eq(7)
+ expect(err).to include("Could not find gem 'doesnt_exist'.")
+ end
+ end
+
+ context "--path" do
+ it "sets the binstubs dir" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack --path exec"
+
+ expect(bundled_app("exec/rackup")).to exist
+ end
+
+ it "setting is saved for bundle install", :bundler => "< 3" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rails"
+ G
+
+ bundle "binstubs rack", :path => "exec"
+ bundle :install
+
+ expect(bundled_app("exec/rails")).to exist
+ end
+ end
+
+ context "with --standalone option" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rails"
+ G
+ end
+
+ it "generates a standalone binstub" do
+ bundle "binstubs rack --standalone"
+ expect(bundled_app("bin/rackup")).to exist
+ end
+
+ it "generates a binstub that does not depend on rubygems or bundler" do
+ bundle "binstubs rack --standalone"
+ expect(File.read(bundled_app("bin/rackup"))).to_not include("Gem.bin_path")
+ end
+
+ context "when specified --path option" do
+ it "generates a standalone binstub at the given path" do
+ bundle "binstubs rack --standalone --path foo"
+ expect(bundled_app("foo/rackup")).to exist
+ end
+ end
+
+ context "when specified --all-platforms option" do
+ it "generates standalone binstubs for all platforms" do
+ bundle "binstubs rack --standalone --all-platforms"
+ expect(bundled_app("bin/rackup")).to exist
+ expect(bundled_app("bin/rackup.cmd")).to exist
+ end
+ end
+
+ context "when the gem is bundler" do
+ it "warns without generating a standalone binstub" do
+ bundle "binstubs bundler --standalone"
+ expect(bundled_app("bin/bundle")).not_to exist
+ expect(bundled_app("bin/bundler")).not_to exist
+ expect(err).to include("Sorry, Bundler can only be run via RubyGems.")
+ end
+ end
+
+ context "when specified --all option" do
+ it "generates standalone binstubs for all gems except bundler" do
+ bundle "binstubs --standalone --all"
+ expect(bundled_app("bin/rackup")).to exist
+ expect(bundled_app("bin/rails")).to exist
+ expect(bundled_app("bin/bundle")).not_to exist
+ expect(bundled_app("bin/bundler")).not_to exist
+ expect(err).not_to include("Sorry, Bundler can only be run via RubyGems.")
+ end
+ end
+ end
+
+ context "when the bin already exists" do
+ it "doesn't overwrite and warns" do
+ FileUtils.mkdir_p(bundled_app("bin"))
+ File.open(bundled_app("bin/rackup"), "wb") do |file|
+ file.print "OMG"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack"
+
+ expect(bundled_app("bin/rackup")).to exist
+ expect(File.read(bundled_app("bin/rackup"))).to eq("OMG")
+ expect(err).to include("Skipped rackup")
+ expect(err).to include("overwrite skipped stubs, use --force")
+ end
+
+ context "when using --force" do
+ it "overwrites the binstub" do
+ FileUtils.mkdir_p(bundled_app("bin"))
+ File.open(bundled_app("bin/rackup"), "wb") do |file|
+ file.print "OMG"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack --force"
+
+ expect(bundled_app("bin/rackup")).to exist
+ expect(File.read(bundled_app("bin/rackup"))).not_to eq("OMG")
+ end
+ end
+ end
+
+ context "when the gem has no bins" do
+ it "suggests child gems if they have bins" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack-obama"
+ G
+
+ bundle "binstubs rack-obama"
+ expect(err).to include("rack-obama has no executables")
+ expect(err).to include("rack has: rackup")
+ end
+
+ it "works if child gems don't have bins" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "actionpack"
+ G
+
+ bundle "binstubs actionpack"
+ expect(err).to include("no executables for the gem actionpack")
+ end
+
+ it "works if the gem has development dependencies" do
+ build_repo2 do
+ build_gem "with_development_dependency" do |s|
+ s.add_development_dependency "activesupport", "= 2.3.5"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "with_development_dependency"
+ G
+
+ bundle "binstubs with_development_dependency"
+ expect(err).to include("no executables for the gem with_development_dependency")
+ end
+ end
+
+ context "when BUNDLE_INSTALL is specified" do
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "config set auto_install 1"
+ bundle "binstubs rack"
+ expect(out).to include("Installing rack 1.0.0")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does nothing when already up to date" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "config set auto_install 1"
+ bundle "binstubs rack", :env => { "BUNDLE_INSTALL" => "1" }
+ expect(out).not_to include("Installing rack 1.0.0")
+ end
+ end
+end
diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb
new file mode 100644
index 0000000000..a9ed389233
--- /dev/null
+++ b/spec/bundler/commands/cache_spec.rb
@@ -0,0 +1,428 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle cache" do
+ it "doesn't update the cache multiple times, even if it already exists" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle :cache
+ expect(out).to include("Updating files in vendor/cache").once
+
+ bundle :cache
+ expect(out).to include("Updating files in vendor/cache").once
+ end
+
+ context "with --gemfile" do
+ it "finds the gemfile" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ bundle "cache --gemfile=NotGemfile"
+
+ ENV["BUNDLE_GEMFILE"] = "NotGemfile"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with --all" do
+ context "without a gemspec" do
+ it "caches all dependencies except bundler itself" do
+ gemfile <<-D
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ gem 'bundler'
+ D
+
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist
+ end
+ end
+
+ context "with a gemspec" do
+ context "that has the same name as the gem" do
+ before do
+ File.open(bundled_app("mygem.gemspec"), "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = "mygem"
+ s.version = "0.1.1"
+ s.summary = ""
+ s.authors = ["gem author"]
+ s.add_development_dependency "nokogiri", "=1.4.2"
+ end
+ G
+ end
+ end
+
+ it "caches all dependencies except bundler and the gemspec specified gem" do
+ gemfile <<-D
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ gemspec
+ D
+
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist
+ expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist
+ expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist
+ end
+ end
+
+ context "that has a different name as the gem" do
+ before do
+ File.open(bundled_app("mygem_diffname.gemspec"), "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = "mygem"
+ s.version = "0.1.1"
+ s.summary = ""
+ s.authors = ["gem author"]
+ s.add_development_dependency "nokogiri", "=1.4.2"
+ end
+ G
+ end
+ end
+
+ it "caches all dependencies except bundler and the gemspec specified gem" do
+ gemfile <<-D
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ gemspec
+ D
+
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist
+ expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist
+ expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist
+ end
+ end
+ end
+
+ context "with multiple gemspecs" do
+ before do
+ File.open(bundled_app("mygem.gemspec"), "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = "mygem"
+ s.version = "0.1.1"
+ s.summary = ""
+ s.authors = ["gem author"]
+ s.add_development_dependency "nokogiri", "=1.4.2"
+ end
+ G
+ end
+ File.open(bundled_app("mygem_client.gemspec"), "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = "mygem_test"
+ s.version = "0.1.1"
+ s.summary = ""
+ s.authors = ["gem author"]
+ s.add_development_dependency "weakling", "=0.0.3"
+ end
+ G
+ end
+ end
+
+ it "caches all dependencies except bundler and the gemspec specified gems" do
+ gemfile <<-D
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ gemspec :name => 'mygem'
+ gemspec :name => 'mygem_test'
+ D
+
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist
+ expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist
+ expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist
+ expect(bundled_app("vendor/cache/mygem_test-0.1.1.gem")).to_not exist
+ expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist
+ end
+ end
+ end
+
+ context "with --path", :bundler => "< 3" do
+ it "sets root directory for gems" do
+ gemfile <<-D
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ D
+
+ bundle "cache --path #{bundled_app("test")}"
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(bundled_app("test/vendor/cache/")).to exist
+ end
+ end
+
+ context "with --no-install" do
+ it "puts the gems in vendor/cache but does not install them" do
+ gemfile <<-D
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ D
+
+ bundle "cache --no-install"
+
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "does not prevent installing gems with bundle install" do
+ gemfile <<-D
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ D
+
+ bundle "cache --no-install"
+ bundle "install"
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not prevent installing gems with bundle update" do
+ gemfile <<-D
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ D
+
+ bundle "cache --no-install"
+ bundle "update --all"
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with --all-platforms" do
+ it "puts the gems in vendor/cache even for other rubies" do
+ gemfile <<-D
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack', :platforms => [:ruby_20, :x64_mingw_20]
+ D
+
+ bundle "cache --all-platforms"
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "does not attempt to install gems in without groups" do
+ build_repo4 do
+ build_gem "uninstallable", "2.0" do |s|
+ s.add_development_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", "task(:default) { raise 'CANNOT INSTALL' }"
+ end
+ end
+
+ bundle "config set --local without wo"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ group :wo do
+ gem "weakling"
+ gem "uninstallable", :source => "#{file_uri_for(gem_repo4)}"
+ end
+ G
+
+ bundle :cache, "all-platforms" => true
+ expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist
+ expect(bundled_app("vendor/cache/uninstallable-2.0.gem")).to exist
+ expect(the_bundle).to include_gem "rack 1.0"
+ expect(the_bundle).not_to include_gems "weakling", "uninstallable"
+
+ bundle "config set --local without wo"
+ bundle :install
+ expect(the_bundle).to include_gem "rack 1.0"
+ expect(the_bundle).not_to include_gems "weakling", "uninstallable"
+ end
+
+ it "does not fail to cache gems in excluded groups when there's a lockfile but gems not previously installed" do
+ bundle "config set --local without wo"
+ gemfile <<-G
+ source "https://my.gem.repo.1"
+ gem "rack"
+ group :wo do
+ gem "weakling"
+ end
+ G
+
+ bundle :lock, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }
+ bundle :cache, "all-platforms" => true, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }
+ expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist
+ end
+ end
+
+ context "with frozen configured" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ bundle "install"
+ end
+
+ subject do
+ bundle "config set --local frozen true"
+ bundle :cache, :raise_on_error => false
+ end
+
+ it "tries to install with frozen" do
+ bundle "config set deployment true"
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+ subject
+ expect(exitstatus).to eq(16)
+ expect(err).to include("frozen mode")
+ expect(err).to include("You have added to the Gemfile")
+ expect(err).to include("* rack-obama")
+ bundle "env"
+ expect(out).to include("frozen").or include("deployment")
+ end
+ end
+
+ context "with gems with extensions" do
+ before do
+ build_repo2 do
+ build_gem "racc", "2.0" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", "task(:default) { puts 'INSTALLING rack' }"
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "racc"
+ G
+ end
+
+ it "installs them properly from cache to a different path" do
+ bundle "cache"
+ bundle "config set --local path vendor/bundle"
+ bundle "install --local"
+ end
+ end
+end
+
+RSpec.describe "bundle install with gem sources" do
+ describe "when cached and locked" do
+ it "does not hit the remote at all" do
+ build_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ G
+
+ bundle :cache
+ simulate_new_machine
+ FileUtils.rm_rf gem_repo2
+
+ bundle "install --local"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not hit the remote at all in frozen mode" do
+ build_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ G
+
+ bundle :cache
+ simulate_new_machine
+ FileUtils.rm_rf gem_repo2
+
+ bundle "config set --local deployment true"
+ bundle "config set --local path vendor/bundle"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not hit the remote at all when cache_all_platforms configured" do
+ build_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ G
+
+ bundle :cache
+ simulate_new_machine
+ FileUtils.rm_rf gem_repo2
+
+ bundle "config set --local cache_all_platforms true"
+ bundle "config set --local path vendor/bundle"
+ bundle "install --local"
+ expect(out).not_to include("Fetching gem metadata")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not reinstall already-installed gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ bundle :cache
+
+ build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s|
+ s.write "lib/rack.rb", "raise 'omg'"
+ end
+
+ bundle :install
+ expect(err).to be_empty
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "ignores cached gems for the wrong platform" do
+ simulate_platform "java" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+ bundle :cache
+ end
+
+ simulate_new_machine
+
+ bundle "config set --local force_ruby_platform true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+ run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
+ expect(out).to eq("1.0.0 RUBY")
+ end
+
+ it "does not update the cache if --no-cache is passed" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ bundled_app("vendor/cache").mkpath
+ expect(bundled_app("vendor/cache").children).to be_empty
+
+ bundle "install --no-cache"
+ expect(bundled_app("vendor/cache").children).to be_empty
+ end
+ end
+end
diff --git a/spec/bundler/commands/check_spec.rb b/spec/bundler/commands/check_spec.rb
new file mode 100644
index 0000000000..99a858e9e9
--- /dev/null
+++ b/spec/bundler/commands/check_spec.rb
@@ -0,0 +1,554 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle check" do
+ it "returns success when the Gemfile is satisfied" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ bundle :check
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "works with the --gemfile flag when not in the directory" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ bundle "check --gemfile bundled_app/Gemfile", :dir => tmp
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "creates a Gemfile.lock by default if one does not exist" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ FileUtils.rm(bundled_app_lock)
+
+ bundle "check"
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "does not create a Gemfile.lock if --dry-run was passed" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ FileUtils.rm(bundled_app_lock)
+
+ bundle "check --dry-run"
+
+ expect(bundled_app_lock).not_to exist
+ end
+
+ it "prints a generic error if the missing gems are unresolvable" do
+ system_gems ["rails-2.3.2"]
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ bundle :check, :raise_on_error => false
+ expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.")
+ end
+
+ it "prints a generic error if a Gemfile.lock does not exist and a toplevel dependency does not exist" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ bundle :check, :raise_on_error => false
+ expect(exitstatus).to be > 0
+ expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.")
+ end
+
+ it "prints a generic message if you changed your lockfile" do
+ build_repo2 do
+ build_gem "rails_pinned_to_old_activesupport" do |s|
+ s.add_dependency "activesupport", "= 1.2.3"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rails'
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails"
+ gem "rails_pinned_to_old_activesupport"
+ G
+
+ bundle :check, :raise_on_error => false
+ expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.")
+ end
+
+ it "remembers --without option from install", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ group :foo do
+ gem "rack"
+ end
+ G
+
+ bundle "install --without foo"
+ bundle "check"
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "uses the without setting" do
+ bundle "config set without foo"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ group :foo do
+ gem "rack"
+ end
+ G
+
+ bundle "check"
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "ensures that gems are actually installed and not just cached" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :group => :foo
+ G
+
+ bundle "config set --local without foo"
+ bundle :install
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "check", :raise_on_error => false
+ expect(err).to include("* rack (1.0.0)")
+ expect(exitstatus).to eq(1)
+ end
+
+ it "ensures that gems are actually installed and not just cached in applications' cache" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "config set --local path vendor/bundle"
+ bundle :cache
+
+ gem_command "uninstall rack", :env => { "GEM_HOME" => vendored_gems.to_s }
+
+ bundle "check", :raise_on_error => false
+ expect(err).to include("* rack (1.0.0)")
+ expect(exitstatus).to eq(1)
+ end
+
+ it "ignores missing gems restricted to other platforms" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ platforms :#{not_local_tag} do
+ gem "activesupport"
+ end
+ G
+
+ system_gems "rack-1.0.0", :path => default_bundle_path
+
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ activesupport (2.3.5)
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+ #{not_local}
+
+ DEPENDENCIES
+ rack
+ activesupport
+ G
+
+ bundle :check
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "works with env conditionals" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ env :NOT_GOING_TO_BE_SET do
+ gem "activesupport"
+ end
+ G
+
+ system_gems "rack-1.0.0", :path => default_bundle_path
+
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ activesupport (2.3.5)
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+ #{not_local}
+
+ DEPENDENCIES
+ rack
+ activesupport
+ G
+
+ bundle :check
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "outputs an error when the default Gemfile is not found" do
+ bundle :check, :raise_on_error => false
+ expect(exitstatus).to eq(10)
+ expect(err).to include("Could not locate Gemfile")
+ end
+
+ it "does not output fatal error message" do
+ bundle :check, :raise_on_error => false
+ expect(exitstatus).to eq(10)
+ expect(err).not_to include("Unfortunately, a fatal error has occurred. ")
+ end
+
+ it "fails when there's no lock file and frozen is set" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo"
+ G
+
+ bundle "config set --local deployment true"
+ bundle "install"
+ FileUtils.rm(bundled_app_lock)
+
+ bundle :check, :raise_on_error => false
+ expect(last_command).to be_failure
+ end
+
+ context "--path", :bundler => "< 3" do
+ context "after installing gems in the proper directory" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+ bundle "install --path vendor/bundle"
+
+ FileUtils.rm_rf(bundled_app(".bundle"))
+ end
+
+ it "returns success" do
+ bundle "check --path vendor/bundle"
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "should write to .bundle/config" do
+ bundle "check --path vendor/bundle"
+ bundle "check"
+ end
+ end
+
+ context "after installing gems on a different directory" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ bundle "check --path vendor/bundle", :raise_on_error => false
+ end
+
+ it "returns false" do
+ expect(exitstatus).to eq(1)
+ expect(err).to match(/The following gems are missing/)
+ end
+ end
+ end
+
+ describe "when locked" do
+ before :each do
+ system_gems "rack-1.0.0"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0"
+ G
+ end
+
+ it "returns success when the Gemfile is satisfied" do
+ bundle :install
+ bundle :check
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "shows what is missing with the current Gemfile if it is not satisfied" do
+ simulate_new_machine
+ bundle :check, :raise_on_error => false
+ expect(err).to match(/The following gems are missing/)
+ expect(err).to include("* rack (1.0")
+ end
+ end
+
+ describe "when locked with multiple dependents with different requirements" do
+ before :each do
+ build_repo4 do
+ build_gem "depends_on_rack" do |s|
+ s.add_dependency "rack", ">= 1.0"
+ end
+ build_gem "also_depends_on_rack" do |s|
+ s.add_dependency "rack", "~> 1.0"
+ end
+ build_gem "rack"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "depends_on_rack"
+ gem "also_depends_on_rack"
+ G
+
+ bundle "lock"
+ end
+
+ it "shows what is missing with the current Gemfile without duplications" do
+ bundle :check, :raise_on_error => false
+ expect(err).to match(/The following gems are missing/)
+ expect(err).to include("* rack (1.0").once
+ end
+ end
+
+ describe "when locked under multiple platforms" do
+ before :each do
+ build_repo4 do
+ build_gem "rack"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "rack"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ rack (1.0)
+
+ PLATFORMS
+ ruby
+ #{local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "shows what is missing with the current Gemfile without duplications" do
+ bundle :check, :raise_on_error => false
+ expect(err).to match(/The following gems are missing/)
+ expect(err).to include("* rack (1.0").once
+ end
+ end
+
+ describe "when using only scoped rubygems sources" do
+ before do
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo2)}"
+ source "#{file_uri_for(gem_repo1)}" do
+ gem "rack"
+ end
+ G
+ end
+
+ it "returns success when the Gemfile is satisfied" do
+ system_gems "rack-1.0.0", :path => default_bundle_path
+ bundle :check
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+ end
+
+ describe "when using only scoped rubygems sources with indirect dependencies" do
+ before do
+ build_repo4 do
+ build_gem "depends_on_rack" do |s|
+ s.add_dependency "rack"
+ end
+
+ build_gem "rack"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ source "#{file_uri_for(gem_repo4)}" do
+ gem "depends_on_rack"
+ end
+ G
+ end
+
+ it "returns success when the Gemfile is satisfied and generates a correct lockfile" do
+ system_gems "depends_on_rack-1.0", "rack-1.0", :gem_repo => gem_repo4, :path => default_bundle_path
+ bundle :check
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ depends_on_rack (1.0)
+ rack
+ rack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ depends_on_rack!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "with gemspec directive and scoped sources" do
+ before do
+ build_repo4 do
+ build_gem "awesome_print"
+ end
+
+ build_repo2 do
+ build_gem "dex-dispatch-engine"
+ end
+
+ build_lib("bundle-check-issue", :path => tmp.join("bundle-check-issue")) do |s|
+ s.write "Gemfile", <<-G
+ source "https://localgemserver.test"
+
+ gemspec
+
+ source "https://localgemserver.test/extra" do
+ gem "dex-dispatch-engine"
+ end
+ G
+
+ s.add_dependency "awesome_print"
+ end
+
+ bundle "install", :artifice => "compact_index_extra", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, :dir => tmp.join("bundle-check-issue")
+ end
+
+ it "does not corrupt lockfile when changing version" do
+ version_file = tmp.join("bundle-check-issue/bundle-check-issue.gemspec")
+ File.write(version_file, File.read(version_file).gsub(/s\.version = .+/, "s.version = '9999'"))
+
+ bundle "check --verbose", :dir => tmp.join("bundle-check-issue")
+
+ expect(File.read(tmp.join("bundle-check-issue/Gemfile.lock"))).to eq <<~L
+ PATH
+ remote: .
+ specs:
+ bundle-check-issue (9999)
+ awesome_print
+
+ GEM
+ remote: https://localgemserver.test/
+ specs:
+ awesome_print (1.0)
+
+ GEM
+ remote: https://localgemserver.test/extra/
+ specs:
+ dex-dispatch-engine (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ bundle-check-issue!
+ dex-dispatch-engine!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ describe "BUNDLED WITH" do
+ def lock_with(bundler_version = nil)
+ lock = <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+ L
+
+ if bundler_version
+ lock += "\nBUNDLED WITH\n #{bundler_version}\n"
+ end
+
+ lock
+ end
+
+ before do
+ bundle "config set --local path vendor/bundle"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ context "is not present" do
+ it "does not change the lock" do
+ lockfile lock_with(nil)
+ bundle :check
+ expect(lockfile).to eq lock_with(nil)
+ end
+ end
+
+ context "is newer" do
+ it "does not change the lock and does not warn" do
+ lockfile lock_with(Bundler::VERSION.succ)
+ bundle :check
+ expect(err).to be_empty
+ expect(lockfile).to eq lock_with(Bundler::VERSION.succ)
+ end
+ end
+
+ context "is older" do
+ it "does not change the lock" do
+ system_gems "bundler-1.18.0"
+ lockfile lock_with("1.18.0")
+ bundle :check
+ expect(lockfile).to eq lock_with("1.18.0")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb
new file mode 100644
index 0000000000..471cd6c354
--- /dev/null
+++ b/spec/bundler/commands/clean_spec.rb
@@ -0,0 +1,916 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle clean" do
+ def should_have_gems(*gems)
+ gems.each do |g|
+ expect(vendored_gems("gems/#{g}")).to exist
+ expect(vendored_gems("specifications/#{g}.gemspec")).to exist
+ expect(vendored_gems("cache/#{g}.gem")).to exist
+ end
+ end
+
+ def should_not_have_gems(*gems)
+ gems.each do |g|
+ expect(vendored_gems("gems/#{g}")).not_to exist
+ expect(vendored_gems("specifications/#{g}.gemspec")).not_to exist
+ expect(vendored_gems("cache/#{g}.gem")).not_to exist
+ end
+ end
+
+ it "removes unused gems that are different" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "config set clean false"
+ bundle "install"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ G
+ bundle "install"
+
+ bundle :clean
+
+ expect(out).to include("Removing foo (1.0)")
+
+ should_have_gems "thin-1.0", "rack-1.0.0"
+ should_not_have_gems "foo-1.0"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "removes old version of gem if unused" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "0.9.1"
+ gem "foo"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "config set clean false"
+ bundle "install"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ gem "foo"
+ G
+ bundle "install"
+
+ bundle :clean
+
+ expect(out).to include("Removing rack (0.9.1)")
+
+ should_have_gems "foo-1.0", "rack-1.0.0"
+ should_not_have_gems "rack-0.9.1"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "removes new version of gem if unused" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ gem "foo"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "config set clean false"
+ bundle "install"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "0.9.1"
+ gem "foo"
+ G
+ bundle "update rack"
+
+ bundle :clean
+
+ expect(out).to include("Removing rack (1.0.0)")
+
+ should_have_gems "foo-1.0", "rack-0.9.1"
+ should_not_have_gems "rack-1.0.0"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "removes gems in bundle without groups" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "foo"
+
+ group :test_group do
+ gem "rack", "1.0.0"
+ end
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+ bundle "config set without test_group"
+ bundle "install"
+ bundle :clean
+
+ expect(out).to include("Removing rack (1.0.0)")
+
+ should_have_gems "foo-1.0"
+ should_not_have_gems "rack-1.0.0"
+
+ expect(vendored_gems("bin/rackup")).to_not exist
+ end
+
+ it "does not remove cached git dir if it's being used" do
+ build_git "foo"
+ revision = revision_for(lib_path("foo-1.0"))
+ git_path = lib_path("foo-1.0")
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ git "#{git_path}", :ref => "#{revision}" do
+ gem "foo"
+ end
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+
+ bundle :clean
+
+ digest = Digest(:SHA1).hexdigest(git_path.to_s)
+ cache_path = Bundler::VERSION.start_with?("2.") ? vendored_gems("cache/bundler/git/foo-1.0-#{digest}") : home(".bundle/cache/git/foo-1.0-#{digest}")
+ expect(cache_path).to exist
+ end
+
+ it "removes unused git gems" do
+ build_git "foo", :path => lib_path("foo")
+ git_path = lib_path("foo")
+ revision = revision_for(git_path)
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ git "#{git_path}", :ref => "#{revision}" do
+ gem "foo"
+ end
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ G
+ bundle "install"
+
+ bundle :clean
+
+ expect(out).to include("Removing foo (#{revision[0..11]})")
+
+ expect(vendored_gems("gems/rack-1.0.0")).to exist
+ expect(vendored_gems("bundler/gems/foo-#{revision[0..11]}")).not_to exist
+ digest = Digest(:SHA1).hexdigest(git_path.to_s)
+ expect(vendored_gems("cache/bundler/git/foo-#{digest}")).not_to exist
+
+ expect(vendored_gems("specifications/rack-1.0.0.gemspec")).to exist
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "keeps used git gems even if installed to a symlinked location" do
+ build_git "foo", :path => lib_path("foo")
+ git_path = lib_path("foo")
+ revision = revision_for(git_path)
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ git "#{git_path}", :ref => "#{revision}" do
+ gem "foo"
+ end
+ G
+
+ FileUtils.mkdir_p(bundled_app("real-path"))
+ File.symlink(bundled_app("real-path"), bundled_app("symlink-path"))
+
+ bundle "config set path #{bundled_app("symlink-path")}"
+ bundle "install"
+
+ bundle :clean
+
+ expect(out).not_to include("Removing foo (#{revision[0..11]})")
+
+ expect(bundled_app("symlink-path/#{Bundler.ruby_scope}/bundler/gems/foo-#{revision[0..11]}")).to exist
+ end
+
+ it "removes old git gems" do
+ build_git "foo-bar", :path => lib_path("foo-bar")
+ revision = revision_for(lib_path("foo-bar"))
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ git "#{lib_path("foo-bar")}" do
+ gem "foo-bar"
+ end
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+
+ update_git "foo-bar", :path => lib_path("foo-bar")
+ revision2 = revision_for(lib_path("foo-bar"))
+
+ bundle "update", :all => true
+ bundle :clean
+
+ expect(out).to include("Removing foo-bar (#{revision[0..11]})")
+
+ expect(vendored_gems("gems/rack-1.0.0")).to exist
+ expect(vendored_gems("bundler/gems/foo-bar-#{revision[0..11]}")).not_to exist
+ expect(vendored_gems("bundler/gems/foo-bar-#{revision2[0..11]}")).to exist
+
+ expect(vendored_gems("specifications/rack-1.0.0.gemspec")).to exist
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "does not remove nested gems in a git repo" do
+ build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport")
+ build_git "rails", "3.0", :path => lib_path("rails") do |s|
+ s.add_dependency "activesupport", "= 3.0"
+ end
+ revision = revision_for(lib_path("rails"))
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport", :git => "#{lib_path("rails")}", :ref => '#{revision}'
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+ bundle :clean
+ expect(out).to include("")
+
+ expect(vendored_gems("bundler/gems/rails-#{revision[0..11]}")).to exist
+ end
+
+ it "does not remove git sources that are in without groups" do
+ build_git "foo", :path => lib_path("foo")
+ git_path = lib_path("foo")
+ revision = revision_for(git_path)
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ group :test do
+ git "#{git_path}", :ref => "#{revision}" do
+ gem "foo"
+ end
+ end
+ G
+ bundle "config set path vendor/bundle"
+ bundle "config set without test"
+ bundle "install"
+
+ bundle :clean
+
+ expect(out).to include("")
+ expect(vendored_gems("bundler/gems/foo-#{revision[0..11]}")).to exist
+ digest = Digest(:SHA1).hexdigest(git_path.to_s)
+ expect(vendored_gems("cache/bundler/git/foo-#{digest}")).to_not exist
+ end
+
+ it "does not blow up when using without groups" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+
+ group :development do
+ gem "foo"
+ end
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "config set without development"
+ bundle "install"
+
+ bundle :clean
+ end
+
+ it "displays an error when used without --path" do
+ bundle "config set path.system true"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ G
+
+ bundle :clean, :raise_on_error => false
+
+ expect(exitstatus).to eq(15)
+ expect(err).to include("--force")
+ end
+
+ # handling bundle clean upgrade path from the pre's
+ it "removes .gem/.gemspec file even if there's no corresponding gem dir" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "foo"
+ G
+ bundle "install"
+
+ FileUtils.rm(vendored_gems("bin/rackup"))
+ FileUtils.rm_rf(vendored_gems("gems/thin-1.0"))
+ FileUtils.rm_rf(vendored_gems("gems/rack-1.0.0"))
+
+ bundle :clean
+
+ should_not_have_gems "thin-1.0", "rack-1.0"
+ should_have_gems "foo-1.0"
+
+ expect(vendored_gems("bin/rackup")).not_to exist
+ end
+
+ it "does not call clean automatically when using system gems" do
+ bundle "config set path.system true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+
+ gem_command :list
+ expect(out).to include("rack (1.0.0)").and include("thin (1.0)")
+ end
+
+ it "--clean should override the bundle setting on install", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "rack"
+ G
+ bundle "config set path vendor/bundle"
+ bundle "config set clean false"
+ bundle "install --clean true"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+ bundle "install"
+
+ should_have_gems "rack-1.0.0"
+ should_not_have_gems "thin-1.0"
+ end
+
+ it "--clean should override the bundle setting on update", :bundler => "< 3" do
+ build_repo2
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "foo"
+ G
+ bundle "config set path vendor/bundle"
+ bundle "config set clean false"
+ bundle "install --clean true"
+
+ update_repo2 do
+ build_gem "foo", "1.0.1"
+ end
+
+ bundle "update", :all => true
+
+ should_have_gems "foo-1.0.1"
+ should_not_have_gems "foo-1.0"
+ end
+
+ it "automatically cleans when path has not been set", :bundler => "3" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "foo"
+ G
+
+ update_repo2 do
+ build_gem "foo", "1.0.1"
+ end
+
+ bundle "update", :all => true
+
+ files = Pathname.glob(bundled_app(".bundle", Bundler.ruby_scope, "*", "*"))
+ files.map! {|f| f.to_s.sub(bundled_app(".bundle", Bundler.ruby_scope).to_s, "") }
+ expect(files.sort).to eq %w[
+ /cache/foo-1.0.1.gem
+ /gems/foo-1.0.1
+ /specifications/foo-1.0.1.gemspec
+ ]
+ end
+
+ it "does not clean automatically on --path" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "rack"
+ G
+ bundle "config set path vendor/bundle"
+ bundle "install"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+ bundle "install"
+
+ should_have_gems "rack-1.0.0", "thin-1.0"
+ end
+
+ it "does not clean on bundle update with --path" do
+ build_repo2
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "foo"
+ G
+ bundle "config set path vendor/bundle"
+ bundle "install"
+
+ update_repo2 do
+ build_gem "foo", "1.0.1"
+ end
+
+ bundle :update, :all => true
+ should_have_gems "foo-1.0", "foo-1.0.1"
+ end
+
+ it "does not clean on bundle update when using --system" do
+ bundle "config set path.system true"
+
+ build_repo2
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "foo"
+ G
+ bundle "install"
+
+ update_repo2 do
+ build_gem "foo", "1.0.1"
+ end
+ bundle :update, :all => true
+
+ gem_command :list
+ expect(out).to include("foo (1.0.1, 1.0)")
+ end
+
+ it "cleans system gems when --force is used" do
+ bundle "config set path.system true"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "foo"
+ gem "rack"
+ G
+ bundle :install
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+ bundle :install
+ bundle "clean --force"
+
+ expect(out).to include("Removing foo (1.0)")
+ gem_command :list
+ expect(out).not_to include("foo (1.0)")
+ expect(out).to include("rack (1.0.0)")
+ end
+
+ describe "when missing permissions", :permissions do
+ before { ENV["BUNDLE_PATH__SYSTEM"] = "true" }
+ let(:system_cache_path) { system_gem_path("cache") }
+ after do
+ FileUtils.chmod(0o755, system_cache_path)
+ end
+ it "returns a helpful error message" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "foo"
+ gem "rack"
+ G
+ bundle :install
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+ bundle :install
+
+ FileUtils.chmod(0o500, system_cache_path)
+
+ bundle :clean, :force => true, :raise_on_error => false
+
+ expect(err).to include(system_gem_path.to_s)
+ expect(err).to include("grant write permissions")
+
+ gem_command :list
+ expect(out).to include("foo (1.0)")
+ expect(out).to include("rack (1.0.0)")
+ end
+ end
+
+ it "cleans git gems with a 7 length git revision" do
+ build_git "foo"
+ revision = revision_for(lib_path("foo-1.0"))
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+
+ # mimic 7 length git revisions in Gemfile.lock
+ gemfile_lock = File.read(bundled_app_lock).split("\n")
+ gemfile_lock.each_with_index do |line, index|
+ gemfile_lock[index] = line[0..(11 + 7)] if line.include?(" revision:")
+ end
+ lockfile(bundled_app_lock, gemfile_lock.join("\n"))
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+
+ bundle :clean
+
+ expect(out).not_to include("Removing foo (1.0 #{revision[0..6]})")
+
+ expect(vendored_gems("bundler/gems/foo-1.0-#{revision[0..6]}")).to exist
+ end
+
+ it "when using --force on system gems, it doesn't remove binaries" do
+ bundle "config set path.system true"
+
+ build_repo2 do
+ build_gem "bindir" do |s|
+ s.bindir = "exe"
+ s.executables = "foo"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "bindir"
+ G
+ bundle :install
+
+ bundle "clean --force"
+
+ sys_exec "foo"
+
+ expect(out).to eq("1.0")
+ end
+
+ it "when using --force, it doesn't remove default gem binaries", :realworld do
+ skip "does not work on old rubies because the realworld gems that need to be installed don't support them" if RUBY_VERSION < "2.7.0"
+
+ skip "does not work on rubygems versions where `--install_dir` doesn't respect --default" unless Gem::Installer.for_spec(loaded_gemspec, :install_dir => "/foo").default_spec_file == "/foo/specifications/default/bundler-#{Bundler::VERSION}.gemspec" # Since rubygems 3.2.0.rc.2
+
+ default_irb_version = ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", :raise_on_error => false
+ skip "irb isn't a default gem" if default_irb_version.empty?
+
+ # simulate executable for default gem
+ build_gem "irb", default_irb_version, :to_system => true, :default => true do |s|
+ s.executables = "irb"
+ end
+
+ realworld_system_gems "tsort --version 0.1.0", "pathname --version 0.1.0", "set --version 1.0.1"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ G
+
+ bundle "clean --force", :env => { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s }
+
+ expect(out).not_to include("Removing irb")
+ end
+
+ it "doesn't blow up on path gems without a .gemspec" do
+ relative_path = "vendor/private_gems/bar-1.0"
+ absolute_path = bundled_app(relative_path)
+ FileUtils.mkdir_p("#{absolute_path}/lib/bar")
+ File.open("#{absolute_path}/lib/bar/bar.rb", "wb") do |file|
+ file.puts "module Bar; end"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "foo"
+ gem "bar", "1.0", :path => "#{relative_path}"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+ bundle :clean
+ end
+
+ it "doesn't remove gems in dry-run mode with path set" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "config set clean false"
+ bundle "install"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ G
+
+ bundle :install
+
+ bundle "clean --dry-run"
+
+ expect(out).not_to include("Removing foo (1.0)")
+ expect(out).to include("Would have removed foo (1.0)")
+
+ should_have_gems "thin-1.0", "rack-1.0.0", "foo-1.0"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "doesn't remove gems in dry-run mode with no path set" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "config set clean false"
+ bundle "install"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ G
+
+ bundle :install
+
+ bundle "clean --dry-run"
+
+ expect(out).not_to include("Removing foo (1.0)")
+ expect(out).to include("Would have removed foo (1.0)")
+
+ should_have_gems "thin-1.0", "rack-1.0.0", "foo-1.0"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "doesn't store dry run as a config setting" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "config set clean false"
+ bundle "install"
+ bundle "config set dry_run false"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ G
+
+ bundle :install
+
+ bundle "clean"
+
+ expect(out).to include("Removing foo (1.0)")
+ expect(out).not_to include("Would have removed foo (1.0)")
+
+ should_have_gems "thin-1.0", "rack-1.0.0"
+ should_not_have_gems "foo-1.0"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "config set clean false"
+ bundle "install"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "weakling"
+ G
+
+ bundle "config set auto_install 1"
+ bundle :clean
+ expect(out).to include("Installing weakling 0.0.3")
+ should_have_gems "thin-1.0", "rack-1.0.0", "weakling-0.0.3"
+ should_not_have_gems "foo-1.0"
+ end
+
+ it "doesn't remove extensions artifacts from bundled git gems after clean" do
+ build_git "very_simple_git_binary", &:add_c_extension
+
+ revision = revision_for(lib_path("very_simple_git_binary-1.0"))
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+ expect(vendored_gems("bundler/gems/extensions")).to exist
+ expect(vendored_gems("bundler/gems/very_simple_git_binary-1.0-#{revision[0..11]}")).to exist
+
+ bundle :clean
+ expect(out).to be_empty
+
+ expect(vendored_gems("bundler/gems/extensions")).to exist
+ expect(vendored_gems("bundler/gems/very_simple_git_binary-1.0-#{revision[0..11]}")).to exist
+ end
+
+ it "removes extension directories" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "very_simple_binary"
+ gem "simple_binary"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+
+ very_simple_binary_extensions_dir =
+ Pathname.glob("#{vendored_gems}/extensions/*/*/very_simple_binary-1.0").first
+
+ simple_binary_extensions_dir =
+ Pathname.glob("#{vendored_gems}/extensions/*/*/simple_binary-1.0").first
+
+ expect(very_simple_binary_extensions_dir).to exist
+ expect(simple_binary_extensions_dir).to exist
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "simple_binary"
+ G
+
+ bundle "install"
+ bundle :clean
+ expect(out).to eq("Removing very_simple_binary (1.0)")
+
+ expect(very_simple_binary_extensions_dir).not_to exist
+ expect(simple_binary_extensions_dir).to exist
+ end
+
+ it "removes git extension directories" do
+ build_git "very_simple_git_binary", &:add_c_extension
+
+ revision = revision_for(lib_path("very_simple_git_binary-1.0"))
+ short_revision = revision[0..11]
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}"
+ G
+
+ bundle "config set path vendor/bundle"
+ bundle "install"
+
+ very_simple_binary_extensions_dir =
+ Pathname.glob("#{vendored_gems}/bundler/gems/extensions/*/*/very_simple_git_binary-1.0-#{short_revision}").first
+
+ expect(very_simple_binary_extensions_dir).to exist
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}"
+ G
+
+ bundle "install"
+ bundle :clean
+ expect(out).to include("Removing thin (1.0)")
+ expect(very_simple_binary_extensions_dir).to exist
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ bundle "install"
+ bundle :clean
+ expect(out).to eq("Removing very_simple_git_binary-1.0 (#{short_revision})")
+
+ expect(very_simple_binary_extensions_dir).not_to exist
+ end
+
+ it "keeps git extension directories when excluded by group" do
+ build_git "very_simple_git_binary", &:add_c_extension
+
+ revision = revision_for(lib_path("very_simple_git_binary-1.0"))
+ short_revision = revision[0..11]
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :development do
+ gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}"
+ end
+ G
+
+ bundle :lock
+ bundle "config set without development"
+ bundle "config set path vendor/bundle"
+ bundle "install"
+ bundle :clean
+
+ very_simple_binary_extensions_dir =
+ Pathname.glob("#{vendored_gems}/bundler/gems/extensions/*/*/very_simple_git_binary-1.0-#{short_revision}").first
+
+ expect(very_simple_binary_extensions_dir).to be_nil
+ end
+end
diff --git a/spec/bundler/commands/config_spec.rb b/spec/bundler/commands/config_spec.rb
new file mode 100644
index 0000000000..ede93f99eb
--- /dev/null
+++ b/spec/bundler/commands/config_spec.rb
@@ -0,0 +1,580 @@
+# frozen_string_literal: true
+
+RSpec.describe ".bundle/config" do
+ describe "config" do
+ before { bundle "config set foo bar" }
+
+ it "prints a detailed report of local and user configuration" do
+ bundle "config list"
+
+ expect(out).to include("Settings are listed in order of priority. The top value will be used")
+ expect(out).to include("foo\nSet for the current user")
+ expect(out).to include(": \"bar\"")
+ end
+
+ context "given --parseable flag" do
+ it "prints a minimal report of local and user configuration" do
+ bundle "config list --parseable"
+ expect(out).to include("foo=bar")
+ end
+
+ context "with global config" do
+ it "prints config assigned to local scope" do
+ bundle "config set --local foo bar2"
+ bundle "config list --parseable"
+ expect(out).to include("foo=bar2")
+ end
+ end
+
+ context "with env overwrite" do
+ it "prints config with env" do
+ bundle "config list --parseable", :env => { "BUNDLE_FOO" => "bar3" }
+ expect(out).to include("foo=bar3")
+ end
+ end
+ end
+ end
+
+ describe "location with a gemfile" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ G
+ end
+
+ it "is local by default" do
+ bundle "config set foo bar"
+ expect(bundled_app(".bundle/config")).to exist
+ expect(home(".bundle/config")).not_to exist
+ end
+
+ it "can be moved with an environment variable" do
+ ENV["BUNDLE_APP_CONFIG"] = tmp("foo/bar").to_s
+ bundle "config set --local path vendor/bundle"
+ bundle "install"
+
+ expect(bundled_app(".bundle")).not_to exist
+ expect(tmp("foo/bar/config")).to exist
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "can provide a relative path with the environment variable" do
+ FileUtils.mkdir_p bundled_app("omg")
+
+ ENV["BUNDLE_APP_CONFIG"] = "../foo"
+ bundle "config set --local path vendor/bundle"
+ bundle "install", :dir => bundled_app("omg")
+
+ expect(bundled_app(".bundle")).not_to exist
+ expect(bundled_app("../foo/config")).to exist
+ expect(the_bundle).to include_gems "rack 1.0.0", :dir => bundled_app("omg")
+ end
+ end
+
+ describe "location without a gemfile" do
+ it "is global by default" do
+ bundle "config set foo bar"
+ expect(bundled_app(".bundle/config")).not_to exist
+ expect(home(".bundle/config")).to exist
+ end
+
+ it "works with an absolute path" do
+ ENV["BUNDLE_APP_CONFIG"] = tmp("foo/bar").to_s
+ bundle "config set --local path vendor/bundle"
+
+ expect(bundled_app(".bundle")).not_to exist
+ expect(tmp("foo/bar/config")).to exist
+ end
+ end
+
+ describe "config location" do
+ let(:bundle_user_config) { File.join(Dir.home, ".config/bundler") }
+
+ before do
+ Dir.mkdir File.dirname(bundle_user_config)
+ end
+
+ it "can be configured through BUNDLE_USER_CONFIG" do
+ bundle "config set path vendor", :env => { "BUNDLE_USER_CONFIG" => bundle_user_config }
+ bundle "config get path", :env => { "BUNDLE_USER_CONFIG" => bundle_user_config }
+ expect(out).to include("Set for the current user (#{bundle_user_config}): \"vendor\"")
+ end
+
+ context "when not explicitly configured, but BUNDLE_USER_HOME set" do
+ let(:bundle_user_home) { bundled_app(".bundle").to_s }
+
+ it "uses the right location" do
+ bundle "config set path vendor", :env => { "BUNDLE_USER_HOME" => bundle_user_home }
+ bundle "config get path", :env => { "BUNDLE_USER_HOME" => bundle_user_home }
+ expect(out).to include("Set for the current user (#{bundle_user_home}/config): \"vendor\"")
+ end
+ end
+ end
+
+ describe "global" do
+ before(:each) do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ G
+ end
+
+ it "is the default" do
+ bundle "config set foo global"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("global")
+ end
+
+ it "can also be set explicitly" do
+ bundle "config set --global foo global"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("global")
+ end
+
+ it "has lower precedence than local" do
+ bundle "config set --local foo local"
+
+ bundle "config set --global foo global"
+ expect(out).to match(/Your application has set foo to "local"/)
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("local")
+ end
+
+ it "has lower precedence than env" do
+ ENV["BUNDLE_FOO"] = "env"
+
+ bundle "config set --global foo global"
+ expect(out).to match(/You have a bundler environment variable for foo set to "env"/)
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("env")
+ ensure
+ ENV.delete("BUNDLE_FOO")
+ end
+
+ it "can be deleted" do
+ bundle "config set --global foo global"
+ bundle "config unset foo"
+
+ run "puts Bundler.settings[:foo] == nil"
+ expect(out).to eq("true")
+ end
+
+ it "warns when overriding" do
+ bundle "config set --global foo previous"
+ bundle "config set --global foo global"
+ expect(out).to match(/You are replacing the current global value of foo/)
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("global")
+ end
+
+ it "does not warn when using the same value twice" do
+ bundle "config set --global foo value"
+ bundle "config set --global foo value"
+ expect(out).not_to match(/You are replacing the current global value of foo/)
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("value")
+ end
+
+ it "expands the path at time of setting" do
+ bundle "config set --global local.foo .."
+ run "puts Bundler.settings['local.foo']"
+ expect(out).to eq(File.expand_path(bundled_app.to_s + "/.."))
+ end
+
+ it "saves with parseable option" do
+ bundle "config set --global --parseable foo value"
+ expect(out).to eq("foo=value")
+ run "puts Bundler.settings['foo']"
+ expect(out).to eq("value")
+ end
+
+ context "when replacing a current value with the parseable flag" do
+ before { bundle "config set --global foo value" }
+ it "prints the current value in a parseable format" do
+ bundle "config set --global --parseable foo value2"
+ expect(out).to eq "foo=value2"
+ run "puts Bundler.settings['foo']"
+ expect(out).to eq("value2")
+ end
+ end
+ end
+
+ describe "local" do
+ before(:each) do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ G
+ end
+
+ it "can also be set explicitly" do
+ bundle "config set --local foo local"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("local")
+ end
+
+ it "has higher precedence than env" do
+ ENV["BUNDLE_FOO"] = "env"
+ bundle "config set --local foo local"
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("local")
+ ensure
+ ENV.delete("BUNDLE_FOO")
+ end
+
+ it "can be deleted" do
+ bundle "config set --local foo local"
+ bundle "config unset foo"
+
+ run "puts Bundler.settings[:foo] == nil"
+ expect(out).to eq("true")
+ end
+
+ it "warns when overriding" do
+ bundle "config set --local foo previous"
+ bundle "config set --local foo local"
+ expect(out).to match(/You are replacing the current local value of foo/)
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("local")
+ end
+
+ it "expands the path at time of setting" do
+ bundle "config set --local local.foo .."
+ run "puts Bundler.settings['local.foo']"
+ expect(out).to eq(File.expand_path(bundled_app.to_s + "/.."))
+ end
+
+ it "can be deleted with parseable option" do
+ bundle "config set --local foo value"
+ bundle "config unset --parseable foo"
+ expect(out).to eq ""
+ run "puts Bundler.settings['foo'] == nil"
+ expect(out).to eq("true")
+ end
+ end
+
+ describe "env" do
+ before(:each) do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ G
+ end
+
+ it "can set boolean properties via the environment" do
+ ENV["BUNDLE_FROZEN"] = "true"
+
+ run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end"
+ expect(out).to eq("true")
+ end
+
+ it "can set negative boolean properties via the environment" do
+ run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end"
+ expect(out).to eq("false")
+
+ ENV["BUNDLE_FROZEN"] = "false"
+
+ run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end"
+ expect(out).to eq("false")
+
+ ENV["BUNDLE_FROZEN"] = "0"
+
+ run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end"
+ expect(out).to eq("false")
+
+ ENV["BUNDLE_FROZEN"] = ""
+
+ run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end"
+ expect(out).to eq("false")
+ end
+
+ it "can set properties with periods via the environment" do
+ ENV["BUNDLE_FOO__BAR"] = "baz"
+
+ run "puts Bundler.settings['foo.bar']"
+ expect(out).to eq("baz")
+ end
+ end
+
+ describe "parseable option" do
+ it "prints an empty string" do
+ bundle "config get foo --parseable"
+
+ expect(out).to eq ""
+ end
+
+ it "only prints the value of the config" do
+ bundle "config set foo local"
+ bundle "config get foo --parseable"
+
+ expect(out).to eq "foo=local"
+ end
+
+ it "can print global config" do
+ bundle "config set --global bar value"
+ bundle "config get bar --parseable"
+
+ expect(out).to eq "bar=value"
+ end
+
+ it "prefers local config over global" do
+ bundle "config set --local bar value2"
+ bundle "config set --global bar value"
+ bundle "config get bar --parseable"
+
+ expect(out).to eq "bar=value2"
+ end
+ end
+
+ describe "gem mirrors" do
+ before(:each) do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ G
+ end
+
+ it "configures mirrors using keys with `mirror.`" do
+ bundle "config set --local mirror.http://gems.example.org http://gem-mirror.example.org"
+ run(<<-E)
+Bundler.settings.gem_mirrors.each do |k, v|
+ puts "\#{k} => \#{v}"
+end
+E
+ expect(out).to eq("http://gems.example.org/ => http://gem-mirror.example.org/")
+ end
+ end
+
+ describe "quoting" do
+ before(:each) { gemfile "source \"#{file_uri_for(gem_repo1)}\"" }
+ let(:long_string) do
+ "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \
+ "--with-xslt-dir=/usr/pkg"
+ end
+
+ it "saves quotes" do
+ bundle "config set foo something\\'"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("something'")
+ end
+
+ it "doesn't return quotes around values" do
+ bundle "config set foo '1'"
+ run "puts Bundler.settings.send(:local_config_file).read"
+ expect(out).to include('"1"')
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("1")
+ end
+
+ it "doesn't duplicate quotes around values" do
+ bundled_app(".bundle").mkpath
+ File.open(bundled_app(".bundle/config"), "w") do |f|
+ f.write 'BUNDLE_FOO: "$BUILD_DIR"'
+ end
+
+ bundle "config set bar baz"
+ run "puts Bundler.settings.send(:local_config_file).read"
+
+ # Starting in Ruby 2.1, YAML automatically adds double quotes
+ # around some values, including $ and newlines.
+ expect(out).to include('BUNDLE_FOO: "$BUILD_DIR"')
+ end
+
+ it "doesn't duplicate quotes around long wrapped values" do
+ bundle "config set foo #{long_string}"
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq(long_string)
+
+ bundle "config set bar baz"
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq(long_string)
+ end
+ end
+
+ describe "very long lines" do
+ before(:each) do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ G
+ end
+
+ let(:long_string) do
+ "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \
+ "--with-xslt-dir=/usr/pkg"
+ end
+
+ let(:long_string_without_special_characters) do
+ "here is quite a long string that will wrap to a second line but will not be " \
+ "surrounded by quotes"
+ end
+
+ it "doesn't wrap values" do
+ bundle "config set foo #{long_string}"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to match(long_string)
+ end
+
+ it "can read wrapped unquoted values" do
+ bundle "config set foo #{long_string_without_special_characters}"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to match(long_string_without_special_characters)
+ end
+ end
+
+ describe "commented out settings with urls" do
+ before do
+ bundle "config set #mirror.https://rails-assets.org http://localhost:9292"
+ end
+
+ it "does not make bundler crash and ignores the configuration" do
+ bundle "config list --parseable"
+
+ expect(out).to eq("#mirror.https://rails-assets.org/=http://localhost:9292")
+ expect(err).to be_empty
+
+ ruby(<<~RUBY)
+ require "#{entrypoint}"
+ print Bundler.settings.mirror_for("https://rails-assets.org")
+ RUBY
+ expect(out).to eq("https://rails-assets.org/")
+ expect(err).to be_empty
+
+ bundle "config set mirror.all http://localhost:9293"
+ ruby(<<~RUBY)
+ require "#{entrypoint}"
+ print Bundler.settings.mirror_for("https://rails-assets.org")
+ RUBY
+ expect(out).to eq("http://localhost:9293/")
+ expect(err).to be_empty
+ end
+ end
+
+ describe "subcommands" do
+ it "list" do
+ bundle "config list", :env => { "BUNDLE_FOO" => "bar" }
+ expect(out).to eq "Settings are listed in order of priority. The top value will be used.\nfoo\nSet via BUNDLE_FOO: \"bar\""
+
+ bundle "config list", :env => { "BUNDLE_FOO" => "bar" }, :parseable => true
+ expect(out).to eq "foo=bar"
+ end
+
+ it "list with credentials" do
+ bundle "config list", :env => { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" }
+ expect(out).to eq "Settings are listed in order of priority. The top value will be used.\ngems.myserver.com\nSet via BUNDLE_GEMS__MYSERVER__COM: \"user:[REDACTED]\""
+
+ bundle "config list", :parseable => true, :env => { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" }
+ expect(out).to eq "gems.myserver.com=user:password"
+ end
+
+ it "list with API token credentials" do
+ bundle "config list", :env => { "BUNDLE_GEMS__MYSERVER__COM" => "api_token:x-oauth-basic" }
+ expect(out).to eq "Settings are listed in order of priority. The top value will be used.\ngems.myserver.com\nSet via BUNDLE_GEMS__MYSERVER__COM: \"[REDACTED]:x-oauth-basic\""
+
+ bundle "config list", :parseable => true, :env => { "BUNDLE_GEMS__MYSERVER__COM" => "api_token:x-oauth-basic" }
+ expect(out).to eq "gems.myserver.com=api_token:x-oauth-basic"
+ end
+
+ it "get" do
+ ENV["BUNDLE_BAR"] = "bar_val"
+
+ bundle "config get foo"
+ expect(out).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`"
+
+ ENV["BUNDLE_FOO"] = "foo_val"
+
+ bundle "config get foo --parseable"
+ expect(out).to eq "foo=foo_val"
+
+ bundle "config get foo"
+ expect(out).to eq "Settings for `foo` in order of priority. The top value will be used\nSet via BUNDLE_FOO: \"foo_val\""
+ end
+
+ it "set" do
+ bundle "config set foo 1"
+ expect(out).to eq ""
+
+ bundle "config set --local foo 2"
+ expect(out).to eq ""
+
+ bundle "config set --global foo 3"
+ expect(out).to eq "Your application has set foo to \"2\". This will override the global value you are currently setting"
+
+ bundle "config set --parseable --local foo 4"
+ expect(out).to eq "foo=4"
+
+ bundle "config set --local foo 4.1"
+ expect(out).to eq "You are replacing the current local value of foo, which is currently \"4\""
+
+ bundle "config set --global --local foo 5", :raise_on_error => false
+ expect(last_command).to be_failure
+ expect(err).to eq "The options global and local were specified. Please only use one of the switches at a time."
+ end
+
+ it "unset" do
+ bundle "config unset foo"
+ expect(out).to eq ""
+
+ bundle "config set foo 1"
+ bundle "config unset foo --parseable"
+ expect(out).to eq ""
+
+ bundle "config set --local foo 1"
+ bundle "config set --global foo 2"
+
+ bundle "config unset foo"
+ expect(out).to eq ""
+ expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`"
+
+ bundle "config set --local foo 1"
+ bundle "config set --global foo 2"
+
+ bundle "config unset foo --local"
+ expect(out).to eq ""
+ expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nSet for the current user (#{home(".bundle/config")}): \"2\""
+ bundle "config unset foo --global"
+ expect(out).to eq ""
+ expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`"
+
+ bundle "config set --local foo 1"
+ bundle "config set --global foo 2"
+
+ bundle "config unset foo --global"
+ expect(out).to eq ""
+ expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nSet for your local app (#{bundled_app(".bundle/config")}): \"1\""
+ bundle "config unset foo --local"
+ expect(out).to eq ""
+ expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`"
+
+ bundle "config unset foo --local --global", :raise_on_error => false
+ expect(last_command).to be_failure
+ expect(err).to eq "The options global and local were specified. Please only use one of the switches at a time."
+ end
+ end
+end
+
+RSpec.describe "setting gemfile via config" do
+ context "when only the non-default Gemfile exists" do
+ it "persists the gemfile location to .bundle/config" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ bundle "config set --local gemfile #{bundled_app("NotGemfile")}"
+ expect(File.exist?(bundled_app(".bundle/config"))).to eq(true)
+
+ bundle "config list"
+ expect(out).to include("NotGemfile")
+ end
+ end
+end
diff --git a/spec/bundler/commands/console_spec.rb b/spec/bundler/commands/console_spec.rb
new file mode 100644
index 0000000000..aa76096e3d
--- /dev/null
+++ b/spec/bundler/commands/console_spec.rb
@@ -0,0 +1,141 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle console", :bundler => "< 3", :readline => true do
+ before :each do
+ build_repo2 do
+ # A minimal fake pry console
+ build_gem "pry" do |s|
+ s.write "lib/pry.rb", <<-RUBY
+ class Pry
+ class << self
+ def toplevel_binding
+ unless defined?(@toplevel_binding) && @toplevel_binding
+ TOPLEVEL_BINDING.eval %{
+ def self.__pry__; binding; end
+ Pry.instance_variable_set(:@toplevel_binding, __pry__)
+ class << self; undef __pry__; end
+ }
+ end
+ @toplevel_binding.eval('private')
+ @toplevel_binding
+ end
+
+ def __pry__
+ while line = gets
+ begin
+ puts eval(line, toplevel_binding).inspect.sub(/^"(.*)"$/, '=> \\1')
+ rescue Exception => e
+ puts "\#{e.class}: \#{e.message}"
+ puts e.backtrace.first
+ end
+ end
+ end
+ alias start __pry__
+ end
+ end
+ RUBY
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+ G
+ end
+
+ it "starts IRB with the default group loaded" do
+ bundle "console" do |input, _, _|
+ input.puts("puts RACK")
+ input.puts("exit")
+ end
+ expect(out).to include("0.9.1")
+ end
+
+ it "uses IRB as default console" do
+ bundle "console" do |input, _, _|
+ input.puts("__FILE__")
+ input.puts("exit")
+ end
+ expect(out).to include("(irb)")
+ end
+
+ it "starts another REPL if configured as such" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "pry"
+ G
+ bundle "config set console pry"
+
+ bundle "console" do |input, _, _|
+ input.puts("__method__")
+ input.puts("exit")
+ end
+ expect(out).to include(":__pry__")
+ end
+
+ it "falls back to IRB if the other REPL isn't available" do
+ bundle "config set console pry"
+ # make sure pry isn't there
+
+ bundle "console" do |input, _, _|
+ input.puts("__FILE__")
+ input.puts("exit")
+ end
+ expect(out).to include("(irb)")
+ end
+
+ it "doesn't load any other groups" do
+ bundle "console" do |input, _, _|
+ input.puts("puts ACTIVESUPPORT")
+ input.puts("exit")
+ end
+ expect(out).to include("NameError")
+ end
+
+ describe "when given a group" do
+ it "loads the given group" do
+ bundle "console test" do |input, _, _|
+ input.puts("puts ACTIVESUPPORT")
+ input.puts("exit")
+ end
+ expect(out).to include("2.3.5")
+ end
+
+ it "loads the default group" do
+ bundle "console test" do |input, _, _|
+ input.puts("puts RACK")
+ input.puts("exit")
+ end
+ expect(out).to include("0.9.1")
+ end
+
+ it "doesn't load other groups" do
+ bundle "console test" do |input, _, _|
+ input.puts("puts RACK_MIDDLEWARE")
+ input.puts("exit")
+ end
+ expect(out).to include("NameError")
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+ gem "foo"
+ G
+
+ bundle "config set auto_install 1"
+ bundle :console do |input, _, _|
+ input.puts("puts 'hello'")
+ input.puts("exit")
+ end
+ expect(out).to include("Installing foo 1.0")
+ expect(out).to include("hello")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+end
diff --git a/spec/bundler/commands/doctor_spec.rb b/spec/bundler/commands/doctor_spec.rb
new file mode 100644
index 0000000000..1afac00923
--- /dev/null
+++ b/spec/bundler/commands/doctor_spec.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+require "find"
+require "stringio"
+require "bundler/cli"
+require "bundler/cli/doctor"
+
+RSpec.describe "bundle doctor" do
+ before(:each) do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ @stdout = StringIO.new
+
+ [:error, :warn].each do |method|
+ allow(Bundler.ui).to receive(method).and_wrap_original do |m, message|
+ m.call message
+ @stdout.puts message
+ end
+ end
+ end
+
+ it "succeeds on a sane installation" do
+ bundle :doctor
+ end
+
+ context "when all files in home are readable/writable" do
+ before(:each) do
+ stat = double("stat")
+ unwritable_file = double("file")
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ allow(Find).to receive(:find).with(Bundler.bundle_path.to_s) { [unwritable_file] }
+ allow(File).to receive(:exist?).and_call_original
+ allow(File).to receive(:exist?).with(unwritable_file).and_return(true)
+ allow(File).to receive(:stat).with(unwritable_file) { stat }
+ allow(stat).to receive(:uid) { Process.uid }
+ allow(File).to receive(:writable?).with(unwritable_file) { true }
+ allow(File).to receive(:readable?).with(unwritable_file) { true }
+ end
+
+ it "exits with no message if the installed gem has no C extensions" do
+ expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error
+ expect(@stdout.string).to be_empty
+ end
+
+ it "exits with no message if the installed gem's C extension dylib breakage is fine" do
+ doctor = Bundler::CLI::Doctor.new({})
+ expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"]
+ expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/lib/libSystem.dylib"]
+ allow(Fiddle).to receive(:dlopen).with("/usr/lib/libSystem.dylib").and_return(true)
+ expect { doctor.run }.not_to raise_error
+ expect(@stdout.string).to be_empty
+ end
+
+ it "exits with a message if one of the linked libraries is missing" do
+ doctor = Bundler::CLI::Doctor.new({})
+ expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"]
+ expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib"]
+ allow(Fiddle).to receive(:dlopen).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_raise(Fiddle::DLError)
+ expect { doctor.run }.to raise_error(Bundler::ProductionError, strip_whitespace(<<-E).strip), @stdout.string
+ The following gems are missing OS dependencies:
+ * bundler: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib
+ * rack: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib
+ E
+ end
+ end
+
+ context "when home contains broken symlinks" do
+ before(:each) do
+ @broken_symlink = double("file")
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ allow(Find).to receive(:find).with(Bundler.bundle_path.to_s) { [@broken_symlink] }
+ allow(File).to receive(:exist?).and_call_original
+ allow(File).to receive(:exist?).with(@broken_symlink) { false }
+ end
+
+ it "exits with an error if home contains files that are not readable/writable" do
+ expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error
+ expect(@stdout.string).to include(
+ "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{@broken_symlink}"
+ )
+ expect(@stdout.string).not_to include("No issues")
+ end
+ end
+
+ context "when home contains files that are not readable/writable" do
+ before(:each) do
+ @stat = double("stat")
+ @unwritable_file = double("file")
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ allow(Find).to receive(:find).with(Bundler.bundle_path.to_s) { [@unwritable_file] }
+ allow(File).to receive(:exist?).and_call_original
+ allow(File).to receive(:exist?).with(@unwritable_file) { true }
+ allow(File).to receive(:stat).with(@unwritable_file) { @stat }
+ end
+
+ it "exits with an error if home contains files that are not readable/writable" do
+ allow(@stat).to receive(:uid) { Process.uid }
+ allow(File).to receive(:writable?).with(@unwritable_file) { false }
+ allow(File).to receive(:readable?).with(@unwritable_file) { false }
+ expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error
+ expect(@stdout.string).to include(
+ "Files exist in the Bundler home that are not readable/writable by the current user. These files are:\n - #{@unwritable_file}"
+ )
+ expect(@stdout.string).not_to include("No issues")
+ end
+
+ context "when home contains files that are not owned by the current process", :permissions do
+ before(:each) do
+ allow(@stat).to receive(:uid) { 0o0000 }
+ end
+
+ it "exits with an error if home contains files that are not readable/writable and are not owned by the current user" do
+ allow(File).to receive(:writable?).with(@unwritable_file) { false }
+ allow(File).to receive(:readable?).with(@unwritable_file) { false }
+ expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error
+ expect(@stdout.string).to include(
+ "Files exist in the Bundler home that are owned by another user, and are not readable/writable. These files are:\n - #{@unwritable_file}"
+ )
+ expect(@stdout.string).not_to include("No issues")
+ end
+
+ it "exits with a warning if home contains files that are read/write but not owned by current user" do
+ allow(File).to receive(:writable?).with(@unwritable_file) { true }
+ allow(File).to receive(:readable?).with(@unwritable_file) { true }
+ expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error
+ expect(@stdout.string).to include(
+ "Files exist in the Bundler home that are owned by another user, but are still readable/writable. These files are:\n - #{@unwritable_file}"
+ )
+ expect(@stdout.string).not_to include("No issues")
+ end
+ end
+ end
+
+ context "when home contains filenames with special characters" do
+ it "escape filename before command execute" do
+ doctor = Bundler::CLI::Doctor.new({})
+ expect(doctor).to receive(:`).with("/usr/bin/otool -L \\$\\(date\\)\\ \\\"\\'\\\\.bundle").and_return("dummy string")
+ doctor.dylibs_darwin('$(date) "\'\.bundle')
+ expect(doctor).to receive(:`).with("/usr/bin/ldd \\$\\(date\\)\\ \\\"\\'\\\\.bundle").and_return("dummy string")
+ doctor.dylibs_ldd('$(date) "\'\.bundle')
+ end
+ end
+end
diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb
new file mode 100644
index 0000000000..099cdb39fa
--- /dev/null
+++ b/spec/bundler/commands/exec_spec.rb
@@ -0,0 +1,1254 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle exec" do
+ let(:system_gems_to_install) { %w[rack-1.0.0 rack-0.9.1] }
+
+ it "works with --gemfile flag" do
+ system_gems(system_gems_to_install, :path => default_bundle_path)
+
+ create_file "CustomGemfile", <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ G
+
+ bundle "exec --gemfile CustomGemfile rackup"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "activates the correct gem" do
+ system_gems(system_gems_to_install, :path => default_bundle_path)
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ G
+
+ bundle "exec rackup"
+ expect(out).to eq("0.9.1")
+ end
+
+ it "works and prints no warnings when HOME is not writable" do
+ system_gems(system_gems_to_install, :path => default_bundle_path)
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ G
+
+ bundle "exec rackup", :env => { "HOME" => "/" }
+ expect(out).to eq("0.9.1")
+ expect(err).to be_empty
+ end
+
+ it "works when the bins are in ~/.bundle" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "exec rackup"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "works when running from a random directory" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "exec 'cd #{tmp("gems")} && rackup'"
+
+ expect(out).to eq("1.0.0")
+ end
+
+ it "works when exec'ing something else" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\""
+ bundle "exec echo exec"
+ expect(out).to eq("exec")
+ end
+
+ it "works when exec'ing to ruby" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\""
+ bundle "exec ruby -e 'puts %{hi}'"
+ expect(out).to eq("hi")
+ end
+
+ it "works when exec'ing to rubygems" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\""
+ bundle "exec #{gem_cmd} --version"
+ expect(out).to eq(Gem::VERSION)
+ end
+
+ it "works when exec'ing to rubygems through sh -c" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\""
+ bundle "exec sh -c '#{gem_cmd} --version'"
+ expect(out).to eq(Gem::VERSION)
+ end
+
+ it "works when exec'ing back to bundler with a lockfile that doesn't include the current platform" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ G
+
+ # simulate lockfile generated with old version not including specific platform
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ RUBY
+
+ DEPENDENCIES
+ rack (= 0.9.1)
+
+ BUNDLED WITH
+ 2.1.4
+ L
+
+ bundle "exec bundle cache", :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+
+ expect(out).to include("Updating files in vendor/cache")
+ end
+
+ it "respects custom process title when loading through ruby" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ script_that_changes_its_own_title_and_checks_if_picked_up_by_ps_unix_utility = <<~'RUBY'
+ Process.setproctitle("1-2-3-4-5-6-7")
+ puts `ps -ocommand= -p#{$$}`
+ RUBY
+ create_file "Gemfile", "source \"#{file_uri_for(gem_repo1)}\""
+ create_file "a.rb", script_that_changes_its_own_title_and_checks_if_picked_up_by_ps_unix_utility
+ bundle "exec ruby a.rb"
+ expect(out).to eq("1-2-3-4-5-6-7")
+ end
+
+ it "accepts --verbose" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\""
+ bundle "exec --verbose echo foobar"
+ expect(out).to eq("foobar")
+ end
+
+ it "passes --verbose to command if it is given after the command" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\""
+ bundle "exec echo --verbose"
+ expect(out).to eq("--verbose")
+ end
+
+ it "handles --keep-file-descriptors" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ require "tempfile"
+
+ command = Tempfile.new("io-test")
+ command.sync = true
+ command.write <<-G
+ if ARGV[0]
+ IO.for_fd(ARGV[0].to_i)
+ else
+ require 'tempfile'
+ io = Tempfile.new("io-test-fd")
+ args = %W[#{Gem.ruby} -I#{lib_dir} #{bindir.join("bundle")} exec --keep-file-descriptors #{Gem.ruby} #{command.path} \#{io.to_i}]
+ args << { io.to_i => io }
+ exec(*args)
+ end
+ G
+
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ sys_exec "#{Gem.ruby} #{command.path}"
+
+ expect(out).to be_empty
+ expect(err).to be_empty
+ end
+
+ it "accepts --keep-file-descriptors" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ bundle "exec --keep-file-descriptors echo foobar"
+
+ expect(err).to be_empty
+ end
+
+ it "can run a command named --verbose" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\""
+ File.open(bundled_app("--verbose"), "w") do |f|
+ f.puts "#!/bin/sh"
+ f.puts "echo foobar"
+ end
+ File.chmod(0o744, bundled_app("--verbose"))
+ with_path_as(".") do
+ bundle "exec -- --verbose"
+ end
+ expect(out).to eq("foobar")
+ end
+
+ it "handles different versions in different bundles" do
+ build_repo2 do
+ build_gem "rack_two", "1.0.0" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ G
+
+ install_gemfile bundled_app2("Gemfile"), <<-G, :dir => bundled_app2
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack_two", "1.0.0"
+ G
+
+ bundle "exec rackup"
+
+ expect(out).to eq("0.9.1")
+
+ bundle "exec rackup", :dir => bundled_app2
+ expect(out).to eq("1.0.0")
+ end
+
+ context "with default gems" do
+ let(:default_irb_version) { ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", :raise_on_error => false }
+
+ context "when not specified in Gemfile" do
+ before do
+ skip "irb isn't a default gem" if default_irb_version.empty?
+
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ end
+
+ it "uses version provided by ruby" do
+ bundle "exec irb --version"
+
+ expect(out).to include(default_irb_version)
+ end
+ end
+
+ context "when specified in Gemfile directly" do
+ let(:specified_irb_version) { "0.9.6" }
+
+ before do
+ skip "irb isn't a default gem" if default_irb_version.empty?
+
+ build_repo2 do
+ build_gem "irb", specified_irb_version do |s|
+ s.executables = "irb"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "irb", "#{specified_irb_version}"
+ G
+ end
+
+ it "uses version specified" do
+ bundle "exec irb --version"
+
+ expect(out).to eq(specified_irb_version)
+ expect(err).to be_empty
+ end
+ end
+
+ context "when specified in Gemfile indirectly" do
+ let(:indirect_irb_version) { "0.9.6" }
+
+ before do
+ skip "irb isn't a default gem" if default_irb_version.empty?
+
+ build_repo2 do
+ build_gem "irb", indirect_irb_version do |s|
+ s.executables = "irb"
+ end
+
+ build_gem "gem_depending_on_old_irb" do |s|
+ s.add_dependency "irb", indirect_irb_version
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "gem_depending_on_old_irb"
+ G
+
+ bundle "exec irb --version"
+ end
+
+ it "uses resolved version" do
+ expect(out).to eq(indirect_irb_version)
+ expect(err).to be_empty
+ end
+ end
+ end
+
+ it "warns about executable conflicts" do
+ build_repo2 do
+ build_gem "rack_two", "1.0.0" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ bundle "config set --global path.system true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ G
+
+ install_gemfile bundled_app2("Gemfile"), <<-G, :dir => bundled_app2
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack_two", "1.0.0"
+ G
+
+ bundle "exec rackup"
+
+ expect(last_command.stderr).to eq(
+ "Bundler is using a binstub that was created for a different gem (rack).\n" \
+ "You should run `bundle binstub rack_two` to work around a system/bundle conflict."
+ )
+ end
+
+ it "handles gems installed with --without" do
+ bundle "config set --local without middleware"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack" # rack 0.9.1 and 1.0 exist
+
+ group :middleware do
+ gem "rack_middleware" # rack_middleware depends on rack 0.9.1
+ end
+ G
+
+ bundle "exec rackup"
+
+ expect(out).to eq("0.9.1")
+ expect(the_bundle).not_to include_gems "rack_middleware 1.0"
+ end
+
+ it "does not duplicate already exec'ed RUBYOPT" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundler_setup_opt = "-r#{lib_dir}/bundler/setup"
+
+ rubyopt = opt_add(bundler_setup_opt, ENV["RUBYOPT"])
+
+ bundle "exec 'echo $RUBYOPT'"
+ expect(out.split(" ").count(bundler_setup_opt)).to eq(1)
+
+ bundle "exec 'echo $RUBYOPT'", :env => { "RUBYOPT" => rubyopt }
+ expect(out.split(" ").count(bundler_setup_opt)).to eq(1)
+ end
+
+ it "does not duplicate already exec'ed RUBYLIB" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ rubylib = ENV["RUBYLIB"]
+ rubylib = rubylib.to_s.split(File::PATH_SEPARATOR).unshift lib_dir.to_s
+ rubylib = rubylib.uniq.join(File::PATH_SEPARATOR)
+
+ bundle "exec 'echo $RUBYLIB'"
+ expect(out).to include(rubylib)
+
+ bundle "exec 'echo $RUBYLIB'", :env => { "RUBYLIB" => rubylib }
+ expect(out).to include(rubylib)
+ end
+
+ it "errors nicely when the argument doesn't exist" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "exec foobarbaz", :raise_on_error => false
+ expect(exitstatus).to eq(127)
+ expect(err).to include("bundler: command not found: foobarbaz")
+ expect(err).to include("Install missing gem executables with `bundle install`")
+ end
+
+ it "errors nicely when the argument is not executable" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "exec touch foo"
+ bundle "exec ./foo", :raise_on_error => false
+ expect(exitstatus).to eq(126)
+ expect(err).to include("bundler: not executable: ./foo")
+ end
+
+ it "errors nicely when no arguments are passed" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "exec", :raise_on_error => false
+ expect(exitstatus).to eq(128)
+ expect(err).to include("bundler: exec needs a command to run")
+ end
+
+ it "raises a helpful error when exec'ing to something outside of the bundle" do
+ system_gems(system_gems_to_install, :path => default_bundle_path)
+
+ bundle "config set clean false" # want to keep the rackup binstub
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo"
+ G
+ [true, false].each do |l|
+ bundle "config set disable_exec_load #{l}"
+ bundle "exec rackup", :raise_on_error => false
+ expect(err).to include "can't find executable rackup for gem rack. rack is not currently included in the bundle, perhaps you meant to add it to your Gemfile?"
+ end
+ end
+
+ describe "with help flags" do
+ each_prefix = proc do |string, &blk|
+ 1.upto(string.length) {|l| blk.call(string[0, l]) }
+ end
+ each_prefix.call("exec") do |exec|
+ describe "when #{exec} is used" do
+ before(:each) do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ create_file("print_args", <<-'RUBY')
+ #!/usr/bin/env ruby
+ puts "args: #{ARGV.inspect}"
+ RUBY
+ bundled_app("print_args").chmod(0o755)
+ end
+
+ it "shows executable's man page when --help is after the executable" do
+ bundle "#{exec} print_args --help"
+ expect(out).to eq('args: ["--help"]')
+ end
+
+ it "shows executable's man page when --help is after the executable and an argument" do
+ bundle "#{exec} print_args foo --help"
+ expect(out).to eq('args: ["foo", "--help"]')
+
+ bundle "#{exec} print_args foo bar --help"
+ expect(out).to eq('args: ["foo", "bar", "--help"]')
+
+ bundle "#{exec} print_args foo --help bar"
+ expect(out).to eq('args: ["foo", "--help", "bar"]')
+ end
+
+ it "shows executable's man page when the executable has a -" do
+ FileUtils.mv(bundled_app("print_args"), bundled_app("docker-template"))
+ bundle "#{exec} docker-template build discourse --help"
+ expect(out).to eq('args: ["build", "discourse", "--help"]')
+ end
+
+ it "shows executable's man page when --help is after another flag" do
+ bundle "#{exec} print_args --bar --help"
+ expect(out).to eq('args: ["--bar", "--help"]')
+ end
+
+ it "uses executable's original behavior for -h" do
+ bundle "#{exec} print_args -h"
+ expect(out).to eq('args: ["-h"]')
+ end
+
+ it "shows bundle-exec's man page when --help is between exec and the executable" do
+ with_fake_man do
+ bundle "#{exec} --help cat"
+ end
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
+ end
+
+ it "shows bundle-exec's man page when --help is before exec" do
+ with_fake_man do
+ bundle "--help #{exec}"
+ end
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
+ end
+
+ it "shows bundle-exec's man page when -h is before exec" do
+ with_fake_man do
+ bundle "-h #{exec}"
+ end
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
+ end
+
+ it "shows bundle-exec's man page when --help is after exec" do
+ with_fake_man do
+ bundle "#{exec} --help"
+ end
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
+ end
+
+ it "shows bundle-exec's man page when -h is after exec" do
+ with_fake_man do
+ bundle "#{exec} -h"
+ end
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
+ end
+ end
+ end
+ end
+
+ describe "with gem executables" do
+ describe "run from a random directory" do
+ before(:each) do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ it "works when unlocked" do
+ bundle "exec 'cd #{tmp("gems")} && rackup'"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "works when locked" do
+ expect(the_bundle).to be_locked
+ bundle "exec 'cd #{tmp("gems")} && rackup'"
+ expect(out).to eq("1.0.0")
+ end
+ end
+
+ describe "from gems bundled via :path" do
+ before(:each) do
+ build_lib "fizz", :path => home("fizz") do |s|
+ s.executables = "fizz"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "fizz", :path => "#{File.expand_path(home("fizz"))}"
+ G
+ end
+
+ it "works when unlocked" do
+ bundle "exec fizz"
+ expect(out).to eq("1.0")
+ end
+
+ it "works when locked" do
+ expect(the_bundle).to be_locked
+
+ bundle "exec fizz"
+ expect(out).to eq("1.0")
+ end
+ end
+
+ describe "from gems bundled via :git" do
+ before(:each) do
+ build_git "fizz_git" do |s|
+ s.executables = "fizz_git"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "fizz_git", :git => "#{lib_path("fizz_git-1.0")}"
+ G
+ end
+
+ it "works when unlocked" do
+ bundle "exec fizz_git"
+ expect(out).to eq("1.0")
+ end
+
+ it "works when locked" do
+ expect(the_bundle).to be_locked
+ bundle "exec fizz_git"
+ expect(out).to eq("1.0")
+ end
+ end
+
+ describe "from gems bundled via :git with no gemspec" do
+ before(:each) do
+ build_git "fizz_no_gemspec", :gemspec => false do |s|
+ s.executables = "fizz_no_gemspec"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "fizz_no_gemspec", "1.0", :git => "#{lib_path("fizz_no_gemspec-1.0")}"
+ G
+ end
+
+ it "works when unlocked" do
+ bundle "exec fizz_no_gemspec"
+ expect(out).to eq("1.0")
+ end
+
+ it "works when locked" do
+ expect(the_bundle).to be_locked
+ bundle "exec fizz_no_gemspec"
+ expect(out).to eq("1.0")
+ end
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ gem "foo"
+ G
+
+ bundle "config set auto_install 1"
+ bundle "exec rackup"
+ expect(out).to include("Installing foo 1.0")
+ end
+
+ it "loads the correct optparse when `auto_install` is set, and optparse is a dependency" do
+ if Gem.ruby_version >= Gem::Version.new("3.0.0") && Gem.rubygems_version < Gem::Version.new("3.3.0.a")
+ skip "optparse is a default gem, and rubygems loads it during install"
+ end
+
+ build_repo4 do
+ build_gem "fastlane", "2.192.0" do |s|
+ s.executables = "fastlane"
+ s.add_dependency "optparse", "~> 999.999.999"
+ end
+
+ build_gem "optparse", "999.999.998"
+ build_gem "optparse", "999.999.999"
+ end
+
+ system_gems "optparse-999.999.998", :gem_repo => gem_repo4
+
+ bundle "config set auto_install 1"
+ bundle "config set --local path vendor/bundle"
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "fastlane"
+ G
+
+ bundle "exec fastlane"
+ expect(out).to include("Installing optparse 999.999.999")
+ expect(out).to include("2.192.0")
+ end
+
+ describe "with gems bundled via :path with invalid gemspecs" do
+ it "outputs the gemspec validation errors" do
+ build_lib "foo"
+
+ gemspec = lib_path("foo-1.0").join("foo.gemspec").to_s
+ File.open(gemspec, "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = 'foo'
+ s.version = '1.0'
+ s.summary = 'TODO: Add summary'
+ s.authors = 'Me'
+ end
+ G
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "exec irb", :raise_on_error => false
+
+ expect(err).to match("The gemspec at #{lib_path("foo-1.0").join("foo.gemspec")} is not valid")
+ expect(err).to match('"TODO" is not a summary')
+ end
+ end
+
+ describe "with gems bundled for deployment" do
+ it "works when calling bundler from another script" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ module Monkey
+ def bin_path(a,b,c)
+ raise Gem::GemNotFoundException.new('Fail')
+ end
+ end
+ Bundler.rubygems.extend(Monkey)
+ G
+ bundle "config set path.system true"
+ bundle "install"
+ bundle "exec ruby -e '`bundle -v`; puts $?.success?'", :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+ expect(out).to match("true")
+ end
+ end
+
+ context "`load`ing a ruby file instead of `exec`ing" do
+ let(:path) { bundled_app("ruby_executable") }
+ let(:shebang) { "#!/usr/bin/env ruby" }
+ let(:executable) { <<-RUBY.gsub(/^ */, "").strip }
+ #{shebang}
+
+ require "rack"
+ puts "EXEC: \#{caller.grep(/load/).empty? ? 'exec' : 'load'}"
+ puts "ARGS: \#{$0} \#{ARGV.join(' ')}"
+ puts "RACK: \#{RACK}"
+ process_title = `ps -o args -p \#{Process.pid}`.split("\n", 2).last.strip
+ puts "PROCESS: \#{process_title}"
+ RUBY
+
+ before do
+ system_gems(system_gems_to_install, :path => default_bundle_path)
+
+ bundled_app(path).open("w") {|f| f << executable }
+ bundled_app(path).chmod(0o755)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ let(:exec) { "EXEC: load" }
+ let(:args) { "ARGS: #{path} arg1 arg2" }
+ let(:rack) { "RACK: 1.0.0" }
+ let(:process) do
+ title = "PROCESS: #{path}"
+ title += " arg1 arg2"
+ title
+ end
+ let(:exit_code) { 0 }
+ let(:expected) { [exec, args, rack, process].join("\n") }
+ let(:expected_err) { "" }
+
+ subject { bundle "exec #{path} arg1 arg2", :raise_on_error => false }
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+
+ context "the executable exits explicitly" do
+ let(:executable) { super() << "\nexit #{exit_code}\nputs 'POST_EXIT'\n" }
+
+ context "with exit 0" do
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "with exit 99" do
+ let(:exit_code) { 99 }
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+ end
+
+ context "the executable exits by SignalException" do
+ let(:executable) do
+ ex = super()
+ ex << "\n"
+ ex << "raise SignalException, 'SIGTERM'\n"
+ ex
+ end
+ let(:expected_err) { "" }
+ let(:exit_code) do
+ exit_status_for_signal(Signal.list["TERM"])
+ end
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "the executable is empty" do
+ let(:executable) { "" }
+
+ let(:exit_code) { 0 }
+ let(:expected_err) { "#{path} is empty" }
+ let(:expected) { "" }
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "the executable raises" do
+ let(:executable) { super() << "\nraise 'ERROR'" }
+ let(:exit_code) { 1 }
+ let(:expected_err) do
+ "bundler: failed to load command: #{path} (#{path})" \
+ "\n#{path}:10:in `<top (required)>': ERROR (RuntimeError)"
+ end
+
+ it "runs like a normally executed executable" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to start_with(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "the executable raises an error without a backtrace" do
+ let(:executable) { super() << "\nclass Err < Exception\ndef backtrace; end;\nend\nraise Err" }
+ let(:exit_code) { 1 }
+ let(:expected_err) { "bundler: failed to load command: #{path} (#{path})\n#{system_gem_path("bin/bundle")}: Err (Err)" }
+ let(:expected) { super() }
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "when the file uses the current ruby shebang" do
+ let(:shebang) { "#!#{Gem.ruby}" }
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "when Bundler.setup fails", :bundler => "< 3" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack', '2'
+ G
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ end
+
+ let(:exit_code) { Bundler::GemNotFound.new.status_code }
+ let(:expected) { "" }
+ let(:expected_err) { <<-EOS.strip }
+Could not find gem 'rack (= 2)' in locally installed gems.
+
+The source contains the following gems matching 'rack':
+ * rack-0.9.1
+ * rack-1.0.0
+Run `bundle install` to install missing gems.
+ EOS
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "when Bundler.setup fails", :bundler => "3" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack', '2'
+ G
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ end
+
+ let(:exit_code) { Bundler::GemNotFound.new.status_code }
+ let(:expected) { "" }
+ let(:expected_err) { <<-EOS.strip }
+Could not find gem 'rack (= 2)' in locally installed gems.
+
+The source contains the following gems matching 'rack':
+ * rack-1.0.0
+Run `bundle install` to install missing gems.
+ EOS
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "when Bundler.setup fails and Gemfile is not the default" do
+ before do
+ create_file "CustomGemfile", <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack', '2'
+ G
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ ENV["BUNDLE_GEMFILE"] = "CustomGemfile"
+ ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = nil
+ end
+
+ let(:exit_code) { Bundler::GemNotFound.new.status_code }
+ let(:expected) { "" }
+
+ it "prints proper suggestion" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to include("Run `bundle install --gemfile CustomGemfile` to install missing gems.")
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "when the executable exits non-zero via at_exit" do
+ let(:executable) { super() + "\n\nat_exit { $! ? raise($!) : exit(1) }" }
+ let(:exit_code) { 1 }
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "when disable_exec_load is set" do
+ let(:exec) { "EXEC: exec" }
+ let(:process) { "PROCESS: ruby #{path} arg1 arg2" }
+
+ before do
+ bundle "config set disable_exec_load true"
+ end
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "regarding $0 and __FILE__" do
+ let(:executable) { super() + <<-'RUBY' }
+
+ puts "$0: #{$0.inspect}"
+ puts "__FILE__: #{__FILE__.inspect}"
+ RUBY
+
+ let(:expected) { super() + <<-EOS.chomp }
+
+$0: #{path.to_s.inspect}
+__FILE__: #{path.to_s.inspect}
+ EOS
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+
+ context "when the path is relative" do
+ let(:path) { super().relative_path_from(bundled_app) }
+
+ it "runs" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ context "when the path is relative with a leading ./" do
+ let(:path) { Pathname.new("./#{super().relative_path_from(bundled_app)}") }
+
+ pending "relative paths with ./ have absolute __FILE__"
+ end
+ end
+
+ context "signal handling" do
+ let(:test_signals) do
+ open3_reserved_signals = %w[CHLD CLD PIPE]
+ reserved_signals = %w[SEGV BUS ILL FPE VTALRM KILL STOP EXIT]
+ bundler_signals = %w[INT]
+
+ Signal.list.keys - (bundler_signals + reserved_signals + open3_reserved_signals)
+ end
+
+ context "signals being trapped by bundler" do
+ let(:executable) { strip_whitespace <<-RUBY }
+ #{shebang}
+ begin
+ Thread.new do
+ puts 'Started' # For process sync
+ STDOUT.flush
+ sleep 1 # ignore quality_spec
+ raise "Didn't receive INT at all"
+ end.join
+ rescue Interrupt
+ puts "foo"
+ end
+ RUBY
+
+ it "receives the signal" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ bundle("exec #{path}") do |_, o, thr|
+ o.gets # Consumes 'Started' and ensures that thread has started
+ Process.kill("INT", thr.pid)
+ end
+
+ expect(out).to eq("foo")
+ end
+ end
+
+ context "signals not being trapped by bunder" do
+ let(:executable) { strip_whitespace <<-RUBY }
+ #{shebang}
+
+ signals = #{test_signals.inspect}
+ result = signals.map do |sig|
+ Signal.trap(sig, "IGNORE")
+ end
+ puts result.select { |ret| ret == "IGNORE" }.count
+ RUBY
+
+ it "makes sure no unexpected signals are restored to DEFAULT" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ test_signals.each do |n|
+ Signal.trap(n, "IGNORE")
+ end
+
+ bundle("exec #{path}")
+
+ expect(out).to eq(test_signals.count.to_s)
+ end
+ end
+ end
+ end
+
+ context "nested bundle exec" do
+ context "when bundle in a local path" do
+ before do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ bundle "config set path vendor/bundler"
+ bundle :install
+ end
+
+ it "correctly shells out" do
+ file = bundled_app("file_that_bundle_execs.rb")
+ create_file(file, <<-RUBY)
+ #!#{Gem.ruby}
+ puts `bundle exec echo foo`
+ RUBY
+ file.chmod(0o777)
+ bundle "exec #{file}", :env => { "PATH" => path }
+ expect(out).to eq("foo")
+ end
+ end
+
+ context "when Kernel.require uses extra monkeypatches" do
+ before do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ end
+
+ it "does not undo the monkeypatches" do
+ karafka = bundled_app("bin/karafka")
+ create_file(karafka, <<~RUBY)
+ #!#{Gem.ruby}
+
+ module Kernel
+ module_function
+
+ alias_method :require_before_extra_monkeypatches, :require
+
+ def require(path)
+ puts "requiring \#{path} used the monkeypatch"
+
+ require_before_extra_monkeypatches(path)
+ end
+ end
+
+ Bundler.setup(:default)
+
+ require "foo"
+ RUBY
+ karafka.chmod(0o777)
+
+ foreman = bundled_app("bin/foreman")
+ create_file(foreman, <<~RUBY)
+ #!#{Gem.ruby}
+
+ puts `bundle exec bin/karafka`
+ RUBY
+ foreman.chmod(0o777)
+
+ bundle "exec #{foreman}"
+ expect(out).to eq("requiring foo used the monkeypatch")
+ end
+ end
+
+ context "when gemfile and path are configured", :ruby_repo do
+ before do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+
+ build_repo2 do
+ build_gem "rails", "6.1.0" do |s|
+ s.executables = "rails"
+ end
+ end
+
+ bundle "config set path vendor/bundle"
+ bundle "config set gemfile gemfiles/rack_6_1.gemfile"
+
+ create_file(bundled_app("gemfiles/rack_6_1.gemfile"), <<~RUBY)
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rails", "6.1.0"
+ RUBY
+
+ # A Gemfile needs to be in the root to trick bundler's root resolution
+ create_file(bundled_app("Gemfile"), "source \"#{file_uri_for(gem_repo1)}\"")
+
+ bundle "install"
+ end
+
+ it "can still find gems after a nested subprocess" do
+ script = bundled_app("bin/myscript")
+
+ create_file(script, <<~RUBY)
+ #!#{Gem.ruby}
+
+ puts `bundle exec rails`
+ RUBY
+
+ script.chmod(0o777)
+
+ bundle "exec #{script}"
+
+ expect(err).to be_empty
+ expect(out).to eq("6.1.0")
+ end
+ end
+
+ context "with a system gem that shadows a default gem" do
+ let(:openssl_version) { "99.9.9" }
+ let(:expected) { ruby "gem 'openssl', '< 999999'; require 'openssl'; puts OpenSSL::VERSION", :artifice => nil, :raise_on_error => false }
+
+ it "only leaves the default gem in the stdlib available" do
+ skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
+ skip "openssl isn't a default gem" if expected.empty?
+
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" # must happen before installing the broken system gem
+
+ build_repo4 do
+ build_gem "openssl", openssl_version do |s|
+ s.write("lib/openssl.rb", <<-RUBY)
+ raise "custom openssl should not be loaded, it's not in the gemfile!"
+ RUBY
+ end
+ end
+
+ system_gems("openssl-#{openssl_version}", :gem_repo => gem_repo4)
+
+ file = bundled_app("require_openssl.rb")
+ create_file(file, <<-RUBY)
+ #!/usr/bin/env ruby
+ require "openssl"
+ puts OpenSSL::VERSION
+ warn Gem.loaded_specs.values.map(&:full_name)
+ RUBY
+ file.chmod(0o777)
+
+ env = { "PATH" => path }
+ aggregate_failures do
+ expect(bundle("exec #{file}", :artifice => nil, :env => env)).to eq(expected)
+ expect(bundle("exec bundle exec #{file}", :artifice => nil, :env => env)).to eq(expected)
+ expect(bundle("exec ruby #{file}", :artifice => nil, :env => env)).to eq(expected)
+ expect(run(file.read, :artifice => nil, :env => env)).to eq(expected)
+ end
+
+ skip "ruby_core has openssl and rubygems in the same folder, and this test needs rubygems require but default openssl not in a directly added entry in $LOAD_PATH" if ruby_core?
+ # sanity check that we get the newer, custom version without bundler
+ sys_exec "#{Gem.ruby} #{file}", :env => env, :raise_on_error => false
+ expect(err).to include("custom openssl should not be loaded")
+ end
+ end
+
+ context "with a git gem that includes extensions", :ruby_repo do
+ before do
+ build_git "simple_git_binary", &:add_c_extension
+ bundle "config set --local path .bundle"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "simple_git_binary", :git => '#{lib_path("simple_git_binary-1.0")}'
+ G
+ end
+
+ it "allows calling bundle install" do
+ bundle "exec bundle install"
+ end
+
+ it "allows calling bundle install after removing gem.build_complete" do
+ FileUtils.rm_rf Dir[bundled_app(".bundle/**/gem.build_complete")]
+ bundle "exec #{Gem.ruby} -S bundle install"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/commands/fund_spec.rb b/spec/bundler/commands/fund_spec.rb
new file mode 100644
index 0000000000..5415b88eeb
--- /dev/null
+++ b/spec/bundler/commands/fund_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle fund" do
+ before do
+ build_repo2 do
+ build_gem "has_funding_and_other_metadata" do |s|
+ s.metadata = {
+ "bug_tracker_uri" => "https://example.com/user/bestgemever/issues",
+ "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md",
+ "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1",
+ "homepage_uri" => "https://bestgemever.example.io",
+ "mailing_list_uri" => "https://groups.example.com/bestgemever",
+ "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding",
+ "source_code_uri" => "https://example.com/user/bestgemever",
+ "wiki_uri" => "https://example.com/user/bestgemever/wiki",
+ }
+ end
+
+ build_gem "has_funding", "1.2.3" do |s|
+ s.metadata = {
+ "funding_uri" => "https://example.com/has_funding/funding",
+ }
+ end
+
+ build_gem "gem_with_dependent_funding", "1.0" do |s|
+ s.add_dependency "has_funding"
+ end
+ end
+ end
+
+ it "prints fund information for all gems in the bundle" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'has_funding_and_other_metadata'
+ gem 'has_funding'
+ gem 'rack-obama'
+ G
+
+ bundle "fund"
+
+ expect(out).to include("* has_funding_and_other_metadata (1.0)\n Funding: https://example.com/has_funding_and_other_metadata/funding")
+ expect(out).to include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding")
+ expect(out).to_not include("rack-obama")
+ end
+
+ it "does not consider fund information for gem dependencies" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'gem_with_dependent_funding'
+ G
+
+ bundle "fund"
+
+ expect(out).to_not include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding")
+ expect(out).to_not include("gem_with_dependent_funding")
+ end
+
+ it "prints message if none of the gems have fund information" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rack-obama'
+ G
+
+ bundle "fund"
+
+ expect(out).to include("None of the installed gems you directly depend on are looking for funding.")
+ end
+
+ describe "with --group option" do
+ it "prints fund message for only specified group gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'has_funding_and_other_metadata', :group => :development
+ gem 'has_funding'
+ G
+
+ bundle "fund --group development"
+ expect(out).to include("* has_funding_and_other_metadata (1.0)\n Funding: https://example.com/has_funding_and_other_metadata/funding")
+ expect(out).to_not include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding")
+ end
+ end
+end
diff --git a/spec/bundler/commands/help_spec.rb b/spec/bundler/commands/help_spec.rb
new file mode 100644
index 0000000000..409c49f9e1
--- /dev/null
+++ b/spec/bundler/commands/help_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle help" do
+ it "uses man when available" do
+ with_fake_man do
+ bundle "help gemfile"
+ end
+ expect(out).to eq(%(["#{man_dir}/gemfile.5"]))
+ end
+
+ it "prefixes bundle commands with bundle- when finding the man files" do
+ with_fake_man do
+ bundle "help install"
+ end
+ expect(out).to eq(%(["#{man_dir}/bundle-install.1"]))
+ end
+
+ it "simply outputs the human readable file when there is no man on the path" do
+ with_path_as("") do
+ bundle "help install"
+ end
+ expect(out).to match(/bundle-install/)
+ end
+
+ it "still outputs the old help for commands that do not have man pages yet" do
+ bundle "help fund"
+ expect(out).to include("Lists information about gems seeking funding assistance")
+ end
+
+ it "looks for a binary and executes it with --help option if it's named bundler-<task>" do
+ skip "Could not find command testtasks, probably because not a windows friendly executable" if Gem.win_platform?
+
+ File.open(tmp("bundler-testtasks"), "w", 0o755) do |f|
+ f.puts "#!/usr/bin/env ruby\nputs ARGV.join(' ')\n"
+ end
+
+ with_path_added(tmp) do
+ bundle "help testtasks"
+ end
+
+ expect(out).to eq("--help")
+ end
+
+ it "is called when the --help flag is used after the command" do
+ with_fake_man do
+ bundle "install --help"
+ end
+ expect(out).to eq(%(["#{man_dir}/bundle-install.1"]))
+ end
+
+ it "is called when the --help flag is used before the command" do
+ with_fake_man do
+ bundle "--help install"
+ end
+ expect(out).to eq(%(["#{man_dir}/bundle-install.1"]))
+ end
+
+ it "is called when the -h flag is used before the command" do
+ with_fake_man do
+ bundle "-h install"
+ end
+ expect(out).to eq(%(["#{man_dir}/bundle-install.1"]))
+ end
+
+ it "is called when the -h flag is used after the command" do
+ with_fake_man do
+ bundle "install -h"
+ end
+ expect(out).to eq(%(["#{man_dir}/bundle-install.1"]))
+ end
+
+ it "has helpful output when using --help flag for a non-existent command" do
+ with_fake_man do
+ bundle "instill -h", :raise_on_error => false
+ end
+ expect(err).to include('Could not find command "instill".')
+ end
+
+ it "is called when only using the --help flag" do
+ with_fake_man do
+ bundle "--help"
+ end
+ expect(out).to eq(%(["#{man_dir}/bundle.1"]))
+
+ with_fake_man do
+ bundle "-h"
+ end
+ expect(out).to eq(%(["#{man_dir}/bundle.1"]))
+ end
+end
diff --git a/spec/bundler/commands/info_spec.rb b/spec/bundler/commands/info_spec.rb
new file mode 100644
index 0000000000..851f50240f
--- /dev/null
+++ b/spec/bundler/commands/info_spec.rb
@@ -0,0 +1,249 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle info" do
+ context "with a standard Gemfile" do
+ before do
+ build_repo2 do
+ build_gem "has_metadata" do |s|
+ s.metadata = {
+ "bug_tracker_uri" => "https://example.com/user/bestgemever/issues",
+ "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md",
+ "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1",
+ "homepage_uri" => "https://bestgemever.example.io",
+ "mailing_list_uri" => "https://groups.example.com/bestgemever",
+ "source_code_uri" => "https://example.com/user/bestgemever",
+ "wiki_uri" => "https://example.com/user/bestgemever/wiki",
+ }
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails"
+ gem "has_metadata"
+ gem "thin"
+ G
+ end
+
+ it "creates a Gemfile.lock when invoked with a gem name" do
+ FileUtils.rm(bundled_app_lock)
+
+ bundle "info rails"
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "prints information if gem exists in bundle" do
+ bundle "info rails"
+ expect(out).to include "* rails (2.3.2)
+\tSummary: This is just a fake gem for testing
+\tHomepage: http://example.com
+\tPath: #{default_bundle_path("gems", "rails-2.3.2")}"
+ end
+
+ it "prints path if gem exists in bundle" do
+ bundle "info rails --path"
+ expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ it "prints the path to the running bundler" do
+ bundle "info bundler --path"
+ expect(out).to eq(root.to_s)
+ end
+
+ it "prints gem version if exists in bundle" do
+ bundle "info rails --version"
+ expect(out).to eq("2.3.2")
+ end
+
+ it "doesn't claim that bundler has been deleted, even if using a custom path without bundler there" do
+ bundle "config set --local path vendor/bundle"
+ bundle "install"
+ bundle "info bundler"
+ expect(out).to include("\tPath: #{root}")
+ expect(err).not_to match(/The gem bundler has been deleted/i)
+ end
+
+ it "complains if gem not in bundle" do
+ bundle "info missing", :raise_on_error => false
+ expect(err).to eq("Could not find gem 'missing'.")
+ end
+
+ it "warns if path no longer exists on disk" do
+ FileUtils.rm_rf(default_bundle_path("gems", "rails-2.3.2"))
+
+ bundle "info rails --path"
+
+ expect(err).to match(/The gem rails has been deleted/i)
+ expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s)
+
+ bundle "info rail --path"
+ expect(err).to match(/The gem rails has been deleted/i)
+ expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s)
+
+ bundle "info rails"
+ expect(err).to match(/The gem rails has been deleted/i)
+ expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ context "given a default gem shippped in ruby", :ruby_repo do
+ it "prints information about the default gem" do
+ bundle "info rdoc"
+ expect(out).to include("* rdoc")
+ expect(out).to include("Default Gem: yes")
+ end
+ end
+
+ context "given a gem with metadata" do
+ it "prints the gem metadata" do
+ bundle "info has_metadata"
+ expect(out).to include "* has_metadata (1.0)
+\tSummary: This is just a fake gem for testing
+\tHomepage: http://example.com
+\tDocumentation: https://www.example.info/gems/bestgemever/0.0.1
+\tSource Code: https://example.com/user/bestgemever
+\tWiki: https://example.com/user/bestgemever/wiki
+\tChangelog: https://example.com/user/bestgemever/CHANGELOG.md
+\tBug Tracker: https://example.com/user/bestgemever/issues
+\tMailing List: https://groups.example.com/bestgemever
+\tPath: #{default_bundle_path("gems", "has_metadata-1.0")}"
+ end
+ end
+
+ context "when gem does not have homepage" do
+ before do
+ build_repo2 do
+ build_gem "rails", "2.3.2" do |s|
+ s.executables = "rails"
+ s.summary = "Just another test gem"
+ end
+ end
+ end
+
+ it "excludes the homepage field from the output" do
+ expect(out).to_not include("Homepage:")
+ end
+ end
+
+ context "when gem has a reverse dependency on any version" do
+ it "prints the details" do
+ bundle "info rack"
+
+ expect(out).to include("Reverse Dependencies: \n\t\tthin (1.0) depends on rack (>= 0)")
+ end
+ end
+
+ context "when gem has a reverse dependency on a specific version" do
+ it "prints the details" do
+ bundle "info actionpack"
+
+ expect(out).to include("Reverse Dependencies: \n\t\trails (2.3.2) depends on actionpack (= 2.3.2)")
+ end
+ end
+
+ context "when gem has no reverse dependencies" do
+ it "excludes the reverse dependencies field from the output" do
+ bundle "info rails"
+
+ expect(out).not_to include("Reverse Dependencies:")
+ end
+ end
+ end
+
+ context "with a git repo in the Gemfile" do
+ before :each do
+ @git = build_git "foo", "1.0"
+ end
+
+ it "prints out git info" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+ expect(the_bundle).to include_gems "foo 1.0"
+
+ bundle "info foo"
+ expect(out).to include("foo (1.0 #{@git.ref_for("main", 6)}")
+ end
+
+ it "prints out branch names other than main" do
+ update_git "foo", :branch => "omg" do |s|
+ s.write "lib/foo.rb", "FOO = '1.0.omg'"
+ end
+ @revision = revision_for(lib_path("foo-1.0"))[0...6]
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg"
+ G
+ expect(the_bundle).to include_gems "foo 1.0.omg"
+
+ bundle "info foo"
+ expect(out).to include("foo (1.0 #{@git.ref_for("omg", 6)}")
+ end
+
+ it "doesn't print the branch when tied to a ref" do
+ sha = revision_for(lib_path("foo-1.0"))
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{sha}"
+ G
+
+ bundle "info foo"
+ expect(out).to include("foo (1.0 #{sha[0..6]})")
+ end
+
+ it "handles when a version is a '-' prerelease" do
+ @git = build_git("foo", "1.0.0-beta.1", :path => lib_path("foo"))
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", "1.0.0-beta.1", :git => "#{lib_path("foo")}"
+ G
+ expect(the_bundle).to include_gems "foo 1.0.0.pre.beta.1"
+
+ bundle "info foo"
+ expect(out).to include("foo (1.0.0.pre.beta.1")
+ end
+ end
+
+ context "with a valid regexp for gem name" do
+ it "presents alternatives", :readline do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ bundle "info rac"
+ expect(out).to match(/\A1 : rack\n2 : rack-obama\n0 : - exit -(\n>.*)?\z/)
+ end
+ end
+
+ context "with an invalid regexp for gem name" do
+ it "does not find the gem" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ invalid_regexp = "[]"
+
+ bundle "info #{invalid_regexp}", :raise_on_error => false
+ expect(err).to include("Could not find gem '#{invalid_regexp}'.")
+ end
+ end
+
+ context "with without configured" do
+ it "does not find the gem, but gives a helpful error" do
+ bundle "config without test"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails", group: :test
+ G
+
+ bundle "info rails", :raise_on_error => false
+ expect(err).to include("Could not find gem 'rails', because it's in the group 'test', configured to be ignored.")
+ end
+ end
+end
diff --git a/spec/bundler/commands/init_spec.rb b/spec/bundler/commands/init_spec.rb
new file mode 100644
index 0000000000..6aa3e9edd1
--- /dev/null
+++ b/spec/bundler/commands/init_spec.rb
@@ -0,0 +1,207 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle init" do
+ it "generates a Gemfile" do
+ bundle :init
+ expect(out).to include("Writing new Gemfile")
+ expect(bundled_app_gemfile).to be_file
+ end
+
+ context "with a template with permission flags not matching current process umask" do
+ let(:template_file) do
+ gemfile = Bundler.preferred_gemfile_name
+ templates_dir.join(gemfile)
+ end
+
+ let(:target_dir) { bundled_app("init_permissions_test") }
+
+ around do |example|
+ old_chmod = File.stat(template_file).mode
+ FileUtils.chmod(old_chmod | 0o111, template_file) # chmod +x
+ example.run
+ FileUtils.chmod(old_chmod, template_file)
+ end
+
+ it "honours the current process umask when generating from a template" do
+ FileUtils.mkdir(target_dir)
+ bundle :init, :dir => target_dir
+ generated_mode = File.stat(File.join(target_dir, "Gemfile")).mode & 0o111
+ expect(generated_mode).to be_zero
+ end
+ end
+
+ context "when a Gemfile already exists" do
+ before do
+ create_file "Gemfile", <<-G
+ gem "rails"
+ G
+ end
+
+ it "does not change existing Gemfiles" do
+ expect { bundle :init, :raise_on_error => false }.not_to change { File.read(bundled_app_gemfile) }
+ end
+
+ it "notifies the user that an existing Gemfile already exists" do
+ bundle :init, :raise_on_error => false
+ expect(err).to include("Gemfile already exists")
+ end
+ end
+
+ context "when a Gemfile exists in a parent directory" do
+ let(:subdir) { "child_dir" }
+
+ it "lets users generate a Gemfile in a child directory" do
+ bundle :init
+
+ FileUtils.mkdir bundled_app(subdir)
+
+ bundle :init, :dir => bundled_app(subdir)
+
+ expect(out).to include("Writing new Gemfile")
+ expect(bundled_app("#{subdir}/Gemfile")).to be_file
+ end
+ end
+
+ context "when the dir is not writable by the current user" do
+ let(:subdir) { "child_dir" }
+
+ it "notifies the user that it cannot write to it" do
+ FileUtils.mkdir bundled_app(subdir)
+ # chmod a-w it
+ mode = File.stat(bundled_app(subdir)).mode ^ 0o222
+ FileUtils.chmod mode, bundled_app(subdir)
+
+ bundle :init, :dir => bundled_app(subdir), :raise_on_error => false
+
+ expect(err).to include("directory is not writable")
+ expect(Dir[bundled_app("#{subdir}/*")]).to be_empty
+ end
+ end
+
+ context "given --gemspec option" do
+ let(:spec_file) { tmp.join("test.gemspec") }
+
+ it "should generate from an existing gemspec" do
+ File.open(spec_file, "w") do |file|
+ file << <<-S
+ Gem::Specification.new do |s|
+ s.name = 'test'
+ s.add_dependency 'rack', '= 1.0.1'
+ s.add_development_dependency 'rspec', '1.2'
+ end
+ S
+ end
+
+ bundle :init, :gemspec => spec_file
+
+ gemfile = bundled_app_gemfile.read
+ expect(gemfile).to match(%r{source 'https://rubygems.org'})
+ expect(gemfile.scan(/gem "rack", "= 1.0.1"/).size).to eq(1)
+ expect(gemfile.scan(/gem "rspec", "= 1.2"/).size).to eq(1)
+ expect(gemfile.scan(/group :development/).size).to eq(1)
+ end
+
+ context "when gemspec file is invalid" do
+ it "notifies the user that specification is invalid" do
+ File.open(spec_file, "w") do |file|
+ file << <<-S
+ Gem::Specification.new do |s|
+ s.name = 'test'
+ s.invalid_method_name
+ end
+ S
+ end
+
+ bundle :init, :gemspec => spec_file, :raise_on_error => false
+ expect(err).to include("There was an error while loading `test.gemspec`")
+ end
+ end
+ end
+
+ context "when init_gems_rb setting is enabled" do
+ before { bundle "config set init_gems_rb true" }
+
+ it "generates a gems.rb" do
+ bundle :init
+ expect(out).to include("Writing new gems.rb")
+ expect(bundled_app("gems.rb")).to be_file
+ end
+
+ context "when gems.rb already exists" do
+ before do
+ create_file("gems.rb", <<-G)
+ gem "rails"
+ G
+ end
+
+ it "does not change existing Gemfiles" do
+ expect { bundle :init, :raise_on_error => false }.not_to change { File.read(bundled_app("gems.rb")) }
+ end
+
+ it "notifies the user that an existing gems.rb already exists" do
+ bundle :init, :raise_on_error => false
+ expect(err).to include("gems.rb already exists")
+ end
+ end
+
+ context "when a gems.rb file exists in a parent directory" do
+ let(:subdir) { "child_dir" }
+
+ it "lets users generate a Gemfile in a child directory" do
+ bundle :init
+
+ FileUtils.mkdir bundled_app(subdir)
+
+ bundle :init, :dir => bundled_app(subdir)
+
+ expect(out).to include("Writing new gems.rb")
+ expect(bundled_app("#{subdir}/gems.rb")).to be_file
+ end
+ end
+
+ context "given --gemspec option" do
+ let(:spec_file) { tmp.join("test.gemspec") }
+
+ before do
+ File.open(spec_file, "w") do |file|
+ file << <<-S
+ Gem::Specification.new do |s|
+ s.name = 'test'
+ s.add_dependency 'rack', '= 1.0.1'
+ s.add_development_dependency 'rspec', '1.2'
+ end
+ S
+ end
+ end
+
+ it "should generate from an existing gemspec" do
+ bundle :init, :gemspec => spec_file
+
+ gemfile = bundled_app("gems.rb").read
+ expect(gemfile).to match(%r{source 'https://rubygems.org'})
+ expect(gemfile.scan(/gem "rack", "= 1.0.1"/).size).to eq(1)
+ expect(gemfile.scan(/gem "rspec", "= 1.2"/).size).to eq(1)
+ expect(gemfile.scan(/group :development/).size).to eq(1)
+ end
+
+ it "prints message to user" do
+ bundle :init, :gemspec => spec_file
+
+ expect(out).to include("Writing new gems.rb")
+ end
+ end
+ end
+
+ describe "using the --gemfile" do
+ it "should use the --gemfile value to name the gemfile" do
+ custom_gemfile_name = "NiceGemfileName"
+
+ bundle :init, :gemfile => custom_gemfile_name
+
+ expect(out).to include("Writing new #{custom_gemfile_name}")
+ used_template = File.read("#{source_root}/lib/bundler/templates/Gemfile")
+ generated_gemfile = bundled_app(custom_gemfile_name).read
+ expect(generated_gemfile).to eq(used_template)
+ end
+ end
+end
diff --git a/spec/bundler/commands/inject_spec.rb b/spec/bundler/commands/inject_spec.rb
new file mode 100644
index 0000000000..d711fe010d
--- /dev/null
+++ b/spec/bundler/commands/inject_spec.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle inject", :bundler => "< 3" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ context "without a lockfile" do
+ it "locks with the injected gems" do
+ expect(bundled_app_lock).not_to exist
+ bundle "inject 'rack-obama' '> 0'"
+ expect(bundled_app_lock.read).to match(/rack-obama/)
+ end
+ end
+
+ context "with a lockfile" do
+ before do
+ bundle "install"
+ end
+
+ it "adds the injected gems to the Gemfile" do
+ expect(bundled_app_gemfile.read).not_to match(/rack-obama/)
+ bundle "inject 'rack-obama' '> 0'"
+ expect(bundled_app_gemfile.read).to match(/rack-obama/)
+ end
+
+ it "locks with the injected gems" do
+ expect(bundled_app_lock.read).not_to match(/rack-obama/)
+ bundle "inject 'rack-obama' '> 0'"
+ expect(bundled_app_lock.read).to match(/rack-obama/)
+ end
+ end
+
+ context "with injected gems already in the Gemfile" do
+ it "doesn't add existing gems" do
+ bundle "inject 'rack' '> 0'", :raise_on_error => false
+ expect(err).to match(/cannot specify the same gem twice/i)
+ end
+ end
+
+ context "incorrect arguments" do
+ it "fails when more than 2 arguments are passed" do
+ bundle "inject gem_name 1 v", :raise_on_error => false
+ expect(err).to eq(<<-E.strip)
+ERROR: "bundle inject" was called with arguments ["gem_name", "1", "v"]
+Usage: "bundle inject GEM VERSION"
+ E
+ end
+ end
+
+ context "with source option" do
+ it "add gem with source option in gemfile" do
+ bundle "inject 'foo' '>0' --source #{file_uri_for(gem_repo1)}"
+ gemfile = bundled_app_gemfile.read
+ str = "gem \"foo\", \"> 0\", :source => \"#{file_uri_for(gem_repo1)}\""
+ expect(gemfile).to include str
+ end
+ end
+
+ context "with group option" do
+ it "add gem with group option in gemfile" do
+ bundle "inject 'rack-obama' '>0' --group=development"
+ gemfile = bundled_app_gemfile.read
+ str = "gem \"rack-obama\", \"> 0\", :group => :development"
+ expect(gemfile).to include str
+ end
+
+ it "add gem with multiple groups in gemfile" do
+ bundle "inject 'rack-obama' '>0' --group=development,test"
+ gemfile = bundled_app_gemfile.read
+ str = "gem \"rack-obama\", \"> 0\", :groups => [:development, :test]"
+ expect(gemfile).to include str
+ end
+ end
+
+ context "when frozen" do
+ before do
+ bundle "install"
+ if Bundler.feature_flag.bundler_3_mode?
+ bundle "config set --local deployment true"
+ else
+ bundle "config set --local frozen true"
+ end
+ end
+
+ it "injects anyway" do
+ bundle "inject 'rack-obama' '> 0'"
+ expect(bundled_app_gemfile.read).to match(/rack-obama/)
+ end
+
+ it "locks with the injected gems" do
+ expect(bundled_app_lock.read).not_to match(/rack-obama/)
+ bundle "inject 'rack-obama' '> 0'"
+ expect(bundled_app_lock.read).to match(/rack-obama/)
+ end
+
+ it "restores frozen afterwards" do
+ bundle "inject 'rack-obama' '> 0'"
+ config = Psych.load(bundled_app(".bundle/config").read)
+ expect(config["BUNDLE_DEPLOYMENT"] || config["BUNDLE_FROZEN"]).to eq("true")
+ end
+
+ it "doesn't allow Gemfile changes" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack-obama"
+ G
+ bundle "inject 'rack' '> 0'", :raise_on_error => false
+ expect(err).to match(/the lockfile can't be updated because frozen mode is set/)
+
+ expect(bundled_app_lock.read).not_to match(/rack-obama/)
+ end
+ end
+end
diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb
new file mode 100644
index 0000000000..f572bdf176
--- /dev/null
+++ b/spec/bundler/commands/install_spec.rb
@@ -0,0 +1,1106 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with gem sources" do
+ describe "the simple case" do
+ it "prints output and returns if no dependencies are specified" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ bundle :install
+ expect(err).to match(/no dependencies/)
+ end
+
+ it "does not make a lockfile if the install fails" do
+ install_gemfile <<-G, :raise_on_error => false
+ raise StandardError, "FAIL"
+ G
+
+ expect(err).to include('StandardError, "FAIL"')
+ expect(bundled_app_lock).not_to exist
+ end
+
+ it "creates a Gemfile.lock" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "does not create ./.bundle by default", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle :install # can't use install_gemfile since it sets retry
+ expect(bundled_app(".bundle")).not_to exist
+ end
+
+ it "does not create ./.bundle by default when installing to system gems" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle :install, :env => { "BUNDLE_PATH__SYSTEM" => "true" } # can't use install_gemfile since it sets retry
+ expect(bundled_app(".bundle")).not_to exist
+ end
+
+ it "creates lock files based on the Gemfile name" do
+ gemfile bundled_app("OmgFile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0"
+ G
+
+ bundle "install --gemfile OmgFile"
+
+ expect(bundled_app("OmgFile.lock")).to exist
+ end
+
+ it "doesn't delete the lockfile if one already exists" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ lockfile = File.read(bundled_app_lock)
+
+ install_gemfile <<-G, :raise_on_error => false
+ raise StandardError, "FAIL"
+ G
+
+ expect(File.read(bundled_app_lock)).to eq(lockfile)
+ end
+
+ it "does not touch the lockfile if nothing changed" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ expect { run "1" }.not_to change { File.mtime(bundled_app_lock) }
+ end
+
+ it "fetches gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ expect(default_bundle_path("gems/rack-1.0.0")).to exist
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+
+ it "auto-heals missing gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ FileUtils.rm_rf(default_bundle_path("gems/rack-1.0.0"))
+
+ bundle "install --verbose"
+
+ expect(out).to include("Installing rack 1.0.0")
+ expect(default_bundle_path("gems/rack-1.0.0")).to exist
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+
+ it "fetches gems when multiple versions are specified" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack', "> 0.9", "< 1.0"
+ G
+
+ expect(default_bundle_path("gems/rack-0.9.1")).to exist
+ expect(the_bundle).to include_gems("rack 0.9.1")
+ end
+
+ it "fetches gems when multiple versions are specified take 2" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack', "< 1.0", "> 0.9"
+ G
+
+ expect(default_bundle_path("gems/rack-0.9.1")).to exist
+ expect(the_bundle).to include_gems("rack 0.9.1")
+ end
+
+ it "raises an appropriate error when gems are specified using symbols" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem :rack
+ G
+ expect(exitstatus).to eq(4)
+ end
+
+ it "pulls in dependencies" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ expect(the_bundle).to include_gems "actionpack 2.3.2", "rails 2.3.2"
+ end
+
+ it "does the right version" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ G
+
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ end
+
+ it "does not install the development dependency" do
+ build_repo2 do
+ build_gem "with_development_dependency" do |s|
+ s.add_development_dependency "activesupport", "= 2.3.5"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "with_development_dependency"
+ G
+
+ expect(the_bundle).to include_gems("with_development_dependency 1.0.0").
+ and not_include_gems("activesupport 2.3.5")
+ end
+
+ it "resolves correctly" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activemerchant"
+ gem "rails"
+ G
+
+ expect(the_bundle).to include_gems "activemerchant 1.0", "activesupport 2.3.2", "actionpack 2.3.2"
+ end
+
+ it "activates gem correctly according to the resolved gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport", "2.3.5"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activemerchant"
+ gem "rails"
+ G
+
+ expect(the_bundle).to include_gems "activemerchant 1.0", "activesupport 2.3.2", "actionpack 2.3.2"
+ end
+
+ it "does not reinstall any gem that is already available locally" do
+ system_gems "activesupport-2.3.2", :path => default_bundle_path
+
+ build_repo2 do
+ build_gem "activesupport", "2.3.2" do |s|
+ s.write "lib/activesupport.rb", "ACTIVESUPPORT = 'fail'"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activerecord", "2.3.2"
+ G
+
+ expect(the_bundle).to include_gems "activesupport 2.3.2"
+ end
+
+ it "works when the gemfile specifies gems that only exist in the system" do
+ build_gem "foo", :to_bundle => true
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "foo"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "foo 1.0.0"
+ end
+
+ it "prioritizes local gems over remote gems" do
+ build_gem "rack", "1.0.0", :to_bundle => true do |s|
+ s.add_dependency "activesupport", "2.3.5"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ end
+
+ it "loads env plugins" do
+ plugin_msg = "hello from an env plugin!"
+ create_file "plugins/rubygems_plugin.rb", "puts '#{plugin_msg}'"
+ rubylib = ENV["RUBYLIB"].to_s.split(File::PATH_SEPARATOR).unshift(bundled_app("plugins").to_s).join(File::PATH_SEPARATOR)
+ install_gemfile <<-G, :env => { "RUBYLIB" => rubylib }
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ expect(last_command.stdboth).to include(plugin_msg)
+ end
+
+ describe "with a gem that installs multiple platforms" do
+ it "installs gems for the local platform as first choice" do
+ skip "version is 1.0, not 1.0.0" if Gem.win_platform?
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+
+ run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
+ expect(out).to eq("1.0.0 #{Bundler.local_platform}")
+ end
+
+ it "falls back on plain ruby" do
+ simulate_platform "foo-bar-baz"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+
+ run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
+ expect(out).to eq("1.0.0 RUBY")
+ end
+
+ it "installs gems for java" do
+ simulate_platform "java"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+
+ run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
+ expect(out).to eq("1.0.0 JAVA")
+ end
+
+ it "installs gems for windows" do
+ simulate_platform x86_mswin32
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+
+ run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
+ expect(out).to eq("1.0 x86-mswin32")
+ end
+ end
+
+ describe "doing bundle install foo" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ it "works" do
+ bundle "config set --local path vendor"
+ bundle "install"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "allows running bundle install --system without deleting foo", :bundler => "< 3" do
+ bundle "install --path vendor"
+ bundle "install --system"
+ FileUtils.rm_rf(bundled_app("vendor"))
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "allows running bundle install --system after deleting foo", :bundler => "< 3" do
+ bundle "install --path vendor"
+ FileUtils.rm_rf(bundled_app("vendor"))
+ bundle "install --system"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ it "finds gems in multiple sources", :bundler => "< 3" do
+ build_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "activesupport", "1.2.3"
+ gem "rack", "1.2"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.2", "activesupport 1.2.3"
+ end
+
+ it "gives a useful error if no sources are set" do
+ install_gemfile <<-G, :raise_on_error => false
+ gem "rack"
+ G
+
+ expect(err).to include("This Gemfile does not include an explicit global source. " \
+ "Not using an explicit global source may result in a different lockfile being generated depending on " \
+ "the gems you have installed locally before bundler is run. " \
+ "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\".")
+ end
+
+ it "creates a Gemfile.lock on a blank Gemfile" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ expect(File.exist?(bundled_app_lock)).to eq(true)
+ end
+
+ it "throws a warning if a gem is added twice in Gemfile without version requirements" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ gem "rack"
+ G
+
+ expect(err).to include("Your Gemfile lists the gem rack (>= 0) more than once.")
+ expect(err).to include("Remove any duplicate entries and specify the gem only once.")
+ expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.")
+ end
+
+ it "throws a warning if a gem is added twice in Gemfile with same versions" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack", "1.0"
+ gem "rack", "1.0"
+ G
+
+ expect(err).to include("Your Gemfile lists the gem rack (= 1.0) more than once.")
+ expect(err).to include("Remove any duplicate entries and specify the gem only once.")
+ expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.")
+ end
+
+ it "throws a warning if a gem is added twice under different platforms and does not crash when using the generated lockfile" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack", :platform => :jruby
+ gem "rack"
+ G
+
+ bundle "install"
+
+ expect(err).to include("Your Gemfile lists the gem rack (>= 0) more than once.")
+ expect(err).to include("Remove any duplicate entries and specify the gem only once.")
+ expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.")
+ end
+
+ it "does not throw a warning if a gem is added once in Gemfile and also inside a gemspec as a development dependency" do
+ build_lib "my-gem", :path => bundled_app do |s|
+ s.add_development_dependency "my-private-gem"
+ end
+
+ build_repo2 do
+ build_gem "my-private-gem"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gemspec
+
+ gem "my-private-gem", :group => :development
+ G
+
+ bundle :install
+
+ expect(err).to be_empty
+ expect(the_bundle).to include_gems("my-private-gem 1.0")
+ end
+
+ it "throws an error if a gem is added twice in Gemfile when version of one dependency is not specified" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ gem "rack", "1.0"
+ G
+
+ expect(err).to include("You cannot specify the same gem twice with different version requirements")
+ expect(err).to include("You specified: rack (>= 0) and rack (= 1.0).")
+ end
+
+ it "throws an error if a gem is added twice in Gemfile when different versions of both dependencies are specified" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack", "1.0"
+ gem "rack", "1.1"
+ G
+
+ expect(err).to include("You cannot specify the same gem twice with different version requirements")
+ expect(err).to include("You specified: rack (= 1.0) and rack (= 1.1).")
+ end
+
+ it "gracefully handles error when rubygems server is unavailable" do
+ skip "networking issue" if Gem.win_platform?
+
+ install_gemfile <<-G, :artifice => nil, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ source "http://0.0.0.0:9384" do
+ gem 'foo'
+ end
+ G
+
+ expect(err).to include("Could not fetch specs from http://0.0.0.0:9384/")
+ expect(err).not_to include("file://")
+ end
+
+ it "fails gracefully when downloading an invalid specification from the full index" do
+ build_repo2 do
+ build_gem "ajp-rails", "0.0.0", :gemspec => false, :skip_validation => true do |s|
+ bad_deps = [["ruby-ajp", ">= 0.2.0"], ["rails", ">= 0.14"]]
+ s.
+ instance_variable_get(:@spec).
+ instance_variable_set(:@dependencies, bad_deps)
+
+ raise "failed to set bad deps" unless s.dependencies == bad_deps
+ end
+ build_gem "ruby-ajp", "1.0.0"
+ end
+
+ install_gemfile <<-G, :full_index => true, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "ajp-rails", "0.0.0"
+ G
+
+ expect(last_command.stdboth).not_to match(/Error Report/i)
+ expect(err).to include("An error occurred while installing ajp-rails (0.0.0), and Bundler cannot continue.").
+ and include("Bundler::APIResponseInvalidDependenciesError")
+ end
+
+ it "doesn't blow up when the local .bundle/config is empty" do
+ FileUtils.mkdir_p(bundled_app(".bundle"))
+ FileUtils.touch(bundled_app(".bundle/config"))
+
+ install_gemfile(<<-G)
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem 'foo'
+ G
+ end
+
+ it "doesn't blow up when the global .bundle/config is empty" do
+ FileUtils.mkdir_p("#{Bundler.rubygems.user_home}/.bundle")
+ FileUtils.touch("#{Bundler.rubygems.user_home}/.bundle/config")
+
+ install_gemfile(<<-G)
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem 'foo'
+ G
+ end
+ end
+
+ describe "Ruby version in Gemfile.lock" do
+ include Bundler::GemHelpers
+
+ context "and using an unsupported Ruby version" do
+ it "prints an error" do
+ install_gemfile <<-G, :raise_on_error => false
+ ruby '~> 1.2'
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ expect(err).to include("Your Ruby version is #{Gem.ruby_version}, but your Gemfile specified ~> 1.2")
+ end
+ end
+
+ context "and using a supported Ruby version" do
+ before do
+ install_gemfile <<-G
+ ruby '~> #{Gem.ruby_version}'
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+
+ it "writes current Ruby version to Gemfile.lock" do
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "updates Gemfile.lock with updated yet still compatible ruby version" do
+ install_gemfile <<-G
+ ruby '~> #{current_ruby_minor}'
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "does not crash when unlocking" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby '>= 2.1.0'
+ G
+
+ bundle "update"
+
+ expect(err).not_to include("Could not find gem 'Ruby")
+ end
+ end
+ end
+
+ describe "when Bundler root contains regex chars" do
+ it "doesn't blow up when using the `gem` DSL" do
+ root_dir = tmp("foo[]bar")
+
+ FileUtils.mkdir_p(root_dir)
+
+ build_lib "foo"
+ gemfile = <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :path => "#{lib_path("foo-1.0")}"
+ G
+ File.open("#{root_dir}/Gemfile", "w") do |file|
+ file.puts gemfile
+ end
+
+ bundle :install, :dir => root_dir
+ end
+
+ it "doesn't blow up when using the `gemspec` DSL" do
+ root_dir = tmp("foo[]bar")
+
+ FileUtils.mkdir_p(root_dir)
+
+ build_lib "foo", :path => root_dir
+ gemfile = <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+ File.open("#{root_dir}/Gemfile", "w") do |file|
+ file.puts gemfile
+ end
+
+ bundle :install, :dir => root_dir
+ end
+ end
+
+ describe "when requesting a quiet install via --quiet" do
+ it "should be quiet if there are no warnings" do
+ bundle "config set force_ruby_platform true"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ bundle :install, :quiet => true
+ expect(out).to be_empty
+ expect(err).to be_empty
+ end
+
+ it "should still display warnings and errors" do
+ bundle "config set force_ruby_platform true"
+
+ create_file("install_with_warning.rb", <<~RUBY)
+ require "#{lib_dir}/bundler"
+ require "#{lib_dir}/bundler/cli"
+ require "#{lib_dir}/bundler/cli/install"
+
+ module RunWithWarning
+ def run
+ super
+ rescue
+ Bundler.ui.warn "BOOOOO"
+ raise
+ end
+ end
+
+ Bundler::CLI::Install.prepend(RunWithWarning)
+ RUBY
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'non-existing-gem'
+ G
+
+ bundle :install, :quiet => true, :raise_on_error => false, :env => { "RUBYOPT" => "-r#{bundled_app("install_with_warning.rb")}" }
+ expect(out).to be_empty
+ expect(err).to include("Could not find gem 'non-existing-gem'")
+ expect(err).to include("BOOOOO")
+ end
+ end
+
+ describe "when bundle path does not have write access", :permissions do
+ let(:bundle_path) { bundled_app("vendor") }
+
+ before do
+ FileUtils.mkdir_p(bundle_path)
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ FileUtils.chmod(0o500, bundle_path)
+
+ bundle "config set --local path vendor"
+ bundle :install, :raise_on_error => false
+ expect(err).to include(bundle_path.to_s)
+ expect(err).to include("grant write permissions")
+ end
+ end
+
+ describe "when bundle gems path does not have write access", :permissions do
+ let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") }
+
+ before do
+ FileUtils.mkdir_p(gems_path)
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ FileUtils.chmod("-x", gems_path)
+ bundle "config set --local path vendor"
+
+ begin
+ bundle :install, :raise_on_error => false
+ ensure
+ FileUtils.chmod("+x", gems_path)
+ end
+
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+
+ expect(err).to include(
+ "There was an error while trying to create `#{gems_path.join("rack-1.0.0")}`. " \
+ "It is likely that you need to grant executable permissions for all parent directories and write permissions for `#{gems_path}`."
+ )
+ end
+ end
+
+ describe "when bundle extensions path does not have write access", :permissions do
+ let(:extensions_path) { bundled_app("vendor/#{Bundler.ruby_scope}/extensions/#{Gem::Platform.local}/#{Gem.extension_api_version}") }
+
+ before do
+ FileUtils.mkdir_p(extensions_path)
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'simple_binary'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ FileUtils.chmod("-x", extensions_path)
+ bundle "config set --local path vendor"
+
+ begin
+ bundle :install, :raise_on_error => false
+ ensure
+ FileUtils.chmod("+x", extensions_path)
+ end
+
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+
+ expect(err).to include(
+ "There was an error while trying to create `#{extensions_path.join("simple_binary-1.0")}`. " \
+ "It is likely that you need to grant executable permissions for all parent directories and write permissions for `#{extensions_path}`."
+ )
+ end
+ end
+
+ describe "when the path of a specific gem is not writable", :permissions do
+ let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") }
+ let(:foo_path) { gems_path.join("foo-1.0.0") }
+
+ before do
+ build_repo4 do
+ build_gem "foo", "1.0.0" do |s|
+ s.write "CHANGELOG.md", "foo"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'foo'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ bundle "config set --local path vendor"
+ bundle :install
+ expect(out).to include("Bundle complete!")
+ expect(err).to be_empty
+
+ FileUtils.chmod("-x", foo_path)
+
+ begin
+ bundle "install --redownload", :raise_on_error => false
+ ensure
+ FileUtils.chmod("+x", foo_path)
+ end
+
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+ expect(err).to include("Could not delete previous installation of `#{foo_path}`.")
+ expect(err).to include("The underlying error was Errno::EACCES")
+ end
+ end
+
+ describe "when bundle cache path does not have write access", :permissions do
+ let(:cache_path) { bundled_app("vendor/#{Bundler.ruby_scope}/cache") }
+
+ before do
+ FileUtils.mkdir_p(cache_path)
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ FileUtils.chmod(0o500, cache_path)
+
+ bundle "config set --local path vendor"
+ bundle :install, :raise_on_error => false
+ expect(err).to include(cache_path.to_s)
+ expect(err).to include("grant write permissions")
+ end
+ end
+
+ context "after installing with --standalone" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ bundle "config set --local path bundle"
+ bundle "install", :standalone => true
+ end
+
+ it "includes the standalone path" do
+ bundle "binstubs rack", :standalone => true
+ standalone_line = File.read(bundled_app("bin/rackup")).each_line.find {|line| line.include? "$:.unshift" }.strip
+ expect(standalone_line).to eq %($:.unshift File.expand_path "../bundle", __dir__)
+ end
+ end
+
+ describe "when bundle install is executed with unencoded authentication" do
+ before do
+ gemfile <<-G
+ source 'https://rubygems.org/'
+ gem "."
+ G
+ end
+
+ it "should display a helpful message explaining how to fix it" do
+ bundle :install, :env => { "BUNDLE_RUBYGEMS__ORG" => "user:pass{word" }, :raise_on_error => false
+ expect(exitstatus).to eq(17)
+ expect(err).to eq("Please CGI escape your usernames and passwords before " \
+ "setting them for authentication.")
+ end
+ end
+
+ context "in a frozen bundle" do
+ before do
+ build_repo4 do
+ build_gem "libv8", "8.4.255.0" do |s|
+ s.platform = "x86_64-darwin-19"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "libv8"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ libv8 (8.4.255.0-x86_64-darwin-19)
+
+ PLATFORMS
+ x86_64-darwin-19
+
+ DEPENDENCIES
+ libv8
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "config set --local deployment true"
+ end
+
+ it "should fail loudly if the lockfile platforms don't include the current platform" do
+ simulate_platform(Gem::Platform.new("x86_64-linux")) { bundle "install", :raise_on_error => false }
+
+ expect(err).to eq(
+ "Your bundle only supports platforms [\"x86_64-darwin-19\"] but your local platform is x86_64-linux. " \
+ "Add the current platform to the lockfile with\n`bundle lock --add-platform x86_64-linux` and try again."
+ )
+ end
+ end
+
+ context "with missing platform specific gems in lockfile" do
+ before do
+ build_repo4 do
+ build_gem "racc", "1.5.2"
+
+ build_gem "nokogiri", "1.12.4" do |s|
+ s.platform = "x86_64-darwin"
+ s.add_runtime_dependency "racc", "~> 1.4"
+ end
+
+ build_gem "nokogiri", "1.12.4" do |s|
+ s.platform = "x86_64-linux"
+ s.add_runtime_dependency "racc", "~> 1.4"
+ end
+
+ build_gem "crass", "1.0.6"
+
+ build_gem "loofah", "2.12.0" do |s|
+ s.add_runtime_dependency "crass", "~> 1.0.2"
+ s.add_runtime_dependency "nokogiri", ">= 1.5.9"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+
+ ruby "#{Gem.ruby_version}"
+
+ gem "loofah", "~> 2.12.0"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ crass (1.0.6)
+ loofah (2.12.0)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.5.9)
+ nokogiri (1.12.4-x86_64-darwin)
+ racc (~> 1.4)
+ racc (1.5.2)
+
+ PLATFORMS
+ x86_64-darwin-20
+ x86_64-linux
+
+ DEPENDENCIES
+ loofah (~> 2.12.0)
+
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically fixes the lockfile" do
+ bundle "config set --local path vendor/bundle"
+
+ simulate_platform "x86_64-linux" do
+ bundle "install", :artifice => "compact_index"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ crass (1.0.6)
+ loofah (2.12.0)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.5.9)
+ nokogiri (1.12.4-x86_64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.12.4-x86_64-linux)
+ racc (~> 1.4)
+ racc (1.5.2)
+
+ PLATFORMS
+ x86_64-darwin-20
+ x86_64-linux
+
+ DEPENDENCIES
+ loofah (~> 2.12.0)
+
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "with --local flag" do
+ before do
+ system_gems "rack-1.0.0", :path => default_bundle_path
+ end
+
+ it "respects installed gems without fetching any remote sources" do
+ install_gemfile <<-G, :local => true
+ source "#{file_uri_for(gem_repo1)}"
+
+ source "https://not-existing-source" do
+ gem "rack"
+ end
+ G
+
+ expect(last_command).to be_success
+ end
+ end
+
+ context "with only option" do
+ before do
+ bundle "config set only a:b"
+ end
+
+ it "installs only gems of the specified groups" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ gem "rack", group: :a
+ gem "rake", group: :b
+ gem "yard", group: :c
+ G
+
+ expect(out).to include("Installing rack")
+ expect(out).to include("Installing rake")
+ expect(out).not_to include("Installing yard")
+ end
+ end
+
+ context "with --prefer-local flag" do
+ before do
+ build_repo4 do
+ build_gem "foo", "1.0.1"
+ build_gem "foo", "1.0.0"
+ build_gem "bar", "1.0.0"
+ end
+
+ system_gems "foo-1.0.0", :path => default_bundle_path, :gem_repo => gem_repo4
+ end
+
+ it "fetches remote sources only when not available locally" do
+ install_gemfile <<-G, :"prefer-local" => true, :verbose => true
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "foo"
+ gem "bar"
+ G
+
+ expect(out).to include("Using foo 1.0.0").and include("Fetching bar 1.0.0").and include("Installing bar 1.0.0")
+ expect(last_command).to be_success
+ end
+ end
+
+ context "with a symlinked configured as bundle path and a gem with symlinks" do
+ before do
+ symlinked_bundled_app = tmp("bundled_app-symlink")
+ File.symlink(bundled_app, symlinked_bundled_app)
+ bundle "config path #{File.join(symlinked_bundled_app, ".vendor")}"
+
+ binman_path = tmp("binman")
+ FileUtils.mkdir_p binman_path
+
+ readme_path = File.join(binman_path, "README.markdown")
+ FileUtils.touch(readme_path)
+
+ man_path = File.join(binman_path, "man", "man0")
+ FileUtils.mkdir_p man_path
+
+ File.symlink("../../README.markdown", File.join(man_path, "README.markdown"))
+
+ build_repo4 do
+ build_gem "binman", :path => gem_repo4("gems"), :lib_path => binman_path, :no_default => true do |s|
+ s.files = ["README.markdown", "man/man0/README.markdown"]
+ end
+ end
+ end
+
+ it "installs fine" do
+ install_gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "binman"
+ G
+ end
+ end
+
+ context "when a gem has equivalent versions with inconsistent dependencies" do
+ before do
+ build_repo4 do
+ build_gem "autobuild", "1.10.rc2" do |s|
+ s.add_dependency "utilrb", ">= 1.6.0"
+ end
+
+ build_gem "autobuild", "1.10.0.rc2" do |s|
+ s.add_dependency "utilrb", ">= 2.0"
+ end
+ end
+ end
+
+ it "does not crash unexpectedly" do
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "autobuild", "1.10.rc2"
+ G
+
+ bundle "install --jobs 1", :raise_on_error => false
+
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+ expect(err).to include("Could not find compatible versions")
+ end
+ end
+end
diff --git a/spec/bundler/commands/issue_spec.rb b/spec/bundler/commands/issue_spec.rb
new file mode 100644
index 0000000000..143f6333ce
--- /dev/null
+++ b/spec/bundler/commands/issue_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle issue" do
+ it "exits with a message" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ bundle "issue"
+ expect(out).to include "Did you find an issue with Bundler?"
+ expect(out).to include "## Environment"
+ expect(out).to include "## Gemfile"
+ expect(out).to include "## Bundle Doctor"
+ end
+end
diff --git a/spec/bundler/commands/licenses_spec.rb b/spec/bundler/commands/licenses_spec.rb
new file mode 100644
index 0000000000..a203984890
--- /dev/null
+++ b/spec/bundler/commands/licenses_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle licenses" do
+ before :each do
+ build_repo2 do
+ build_gem "with_license" do |s|
+ s.license = "MIT"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails"
+ gem "with_license"
+ G
+ end
+
+ it "prints license information for all gems in the bundle" do
+ bundle "licenses"
+
+ expect(out).to include("bundler: MIT")
+ expect(out).to include("with_license: MIT")
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails"
+ gem "with_license"
+ gem "foo"
+ G
+
+ bundle "config set auto_install 1"
+ bundle :licenses
+ expect(out).to include("Installing foo 1.0")
+ end
+end
diff --git a/spec/bundler/commands/list_spec.rb b/spec/bundler/commands/list_spec.rb
new file mode 100644
index 0000000000..66930ded75
--- /dev/null
+++ b/spec/bundler/commands/list_spec.rb
@@ -0,0 +1,195 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle list" do
+ context "with name-only and paths option" do
+ it "raises an error" do
+ bundle "list --name-only --paths", :raise_on_error => false
+
+ expect(err).to eq "The `--name-only` and `--paths` options cannot be used together"
+ end
+ end
+
+ context "with without-group and only-group option" do
+ it "raises an error" do
+ bundle "list --without-group dev --only-group test", :raise_on_error => false
+
+ expect(err).to eq "The `--only-group` and `--without-group` options cannot be used together"
+ end
+ end
+
+ describe "with without-group option" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ gem "rspec", :group => [:test]
+ gem "rails", :group => [:production]
+ G
+ end
+
+ context "when group is present" do
+ it "prints the gems not in the specified group" do
+ bundle "list --without-group test"
+
+ expect(out).to include(" * rack (1.0.0)")
+ expect(out).to include(" * rails (2.3.2)")
+ expect(out).not_to include(" * rspec (1.2.7)")
+ end
+ end
+
+ context "when group is not found" do
+ it "raises an error" do
+ bundle "list --without-group random", :raise_on_error => false
+
+ expect(err).to eq "`random` group could not be found."
+ end
+ end
+
+ context "when multiple groups" do
+ it "prints the gems not in the specified groups" do
+ bundle "list --without-group test production"
+
+ expect(out).to include(" * rack (1.0.0)")
+ expect(out).not_to include(" * rails (2.3.2)")
+ expect(out).not_to include(" * rspec (1.2.7)")
+ end
+ end
+ end
+
+ describe "with only-group option" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ gem "rspec", :group => [:test]
+ gem "rails", :group => [:production]
+ G
+ end
+
+ context "when group is present" do
+ it "prints the gems in the specified group" do
+ bundle "list --only-group default"
+
+ expect(out).to include(" * rack (1.0.0)")
+ expect(out).not_to include(" * rspec (1.2.7)")
+ end
+ end
+
+ context "when group is not found" do
+ it "raises an error" do
+ bundle "list --only-group random", :raise_on_error => false
+
+ expect(err).to eq "`random` group could not be found."
+ end
+ end
+
+ context "when multiple groups" do
+ it "prints the gems in the specified groups" do
+ bundle "list --only-group default production"
+
+ expect(out).to include(" * rack (1.0.0)")
+ expect(out).to include(" * rails (2.3.2)")
+ expect(out).not_to include(" * rspec (1.2.7)")
+ end
+ end
+ end
+
+ context "with name-only option" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ gem "rspec", :group => [:test]
+ G
+ end
+
+ it "prints only the name of the gems in the bundle" do
+ bundle "list --name-only"
+
+ expect(out).to include("rack")
+ expect(out).to include("rspec")
+ end
+ end
+
+ context "with paths option" do
+ before do
+ build_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "bar"
+ end
+
+ build_git "git_test", "1.0.0", :path => lib_path("git_test")
+
+ build_lib("gemspec_test", :path => tmp.join("gemspec_test")) do |s|
+ s.add_dependency "bar", "=1.0.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ gem "rails"
+ gem "git_test", :git => "#{lib_path("git_test")}"
+ gemspec :path => "#{tmp.join("gemspec_test")}"
+ G
+ end
+
+ it "prints the path of each gem in the bundle" do
+ bundle "list --paths"
+ expect(out).to match(%r{.*\/rails\-2\.3\.2})
+ expect(out).to match(%r{.*\/rack\-1\.2})
+ expect(out).to match(%r{.*\/git_test\-\w})
+ expect(out).to match(%r{.*\/gemspec_test})
+ end
+ end
+
+ context "when no gems are in the gemfile" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+
+ it "prints message saying no gems are in the bundle" do
+ bundle "list"
+ expect(out).to include("No gems in the Gemfile")
+ end
+ end
+
+ context "without options" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ gem "rspec", :group => [:test]
+ G
+ end
+
+ it "lists gems installed in the bundle" do
+ bundle "list"
+ expect(out).to include(" * rack (1.0.0)")
+ end
+ end
+
+ context "when using the ls alias" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ gem "rspec", :group => [:test]
+ G
+ end
+
+ it "runs the list command" do
+ bundle "ls"
+ expect(out).to include("Gems included by the bundle")
+ end
+ end
+end
diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb
new file mode 100644
index 0000000000..491fa30949
--- /dev/null
+++ b/spec/bundler/commands/lock_spec.rb
@@ -0,0 +1,1273 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle lock" do
+ def strip_lockfile(lockfile)
+ strip_whitespace(lockfile).sub(/\n\Z/, "")
+ end
+
+ def read_lockfile(file = "Gemfile.lock")
+ strip_lockfile bundled_app(file).read
+ end
+
+ let(:repo) { gem_repo1 }
+
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(repo)}"
+ gem "rails"
+ gem "weakling"
+ gem "foo"
+ G
+
+ @lockfile = strip_lockfile(<<-L)
+ GEM
+ remote: #{file_uri_for(repo)}/
+ specs:
+ actionmailer (2.3.2)
+ activesupport (= 2.3.2)
+ actionpack (2.3.2)
+ activesupport (= 2.3.2)
+ activerecord (2.3.2)
+ activesupport (= 2.3.2)
+ activeresource (2.3.2)
+ activesupport (= 2.3.2)
+ activesupport (2.3.2)
+ foo (1.0)
+ rails (2.3.2)
+ actionmailer (= 2.3.2)
+ actionpack (= 2.3.2)
+ activerecord (= 2.3.2)
+ activeresource (= 2.3.2)
+ rake (= 13.0.1)
+ rake (13.0.1)
+ weakling (0.0.3)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+ rails
+ weakling
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "prints a lockfile when there is no existing lockfile with --print" do
+ bundle "lock --print"
+
+ expect(out).to eq(@lockfile)
+ end
+
+ it "prints a lockfile when there is an existing lockfile with --print" do
+ lockfile @lockfile
+
+ bundle "lock --print"
+
+ expect(out).to eq(@lockfile)
+ end
+
+ it "writes a lockfile when there is no existing lockfile" do
+ bundle "lock"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "writes a lockfile when there is an outdated lockfile using --update" do
+ lockfile @lockfile.gsub("2.3.2", "2.3.1")
+
+ bundle "lock --update"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "does not fetch remote specs when using the --local option" do
+ bundle "lock --update --local", :raise_on_error => false
+
+ expect(err).to match(/locally installed gems/)
+ end
+
+ it "works with --gemfile flag" do
+ create_file "CustomGemfile", <<-G
+ source "#{file_uri_for(repo)}"
+ gem "foo"
+ G
+ lockfile = strip_lockfile(<<-L)
+ GEM
+ remote: #{file_uri_for(repo)}/
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ bundle "lock --gemfile CustomGemfile"
+
+ expect(out).to match(/Writing lockfile to.+CustomGemfile\.lock/)
+ expect(read_lockfile("CustomGemfile.lock")).to eq(lockfile)
+ expect { read_lockfile }.to raise_error(Errno::ENOENT)
+ end
+
+ it "writes to a custom location using --lockfile" do
+ bundle "lock --lockfile=lock"
+
+ expect(out).to match(/Writing lockfile to.+lock/)
+ expect(read_lockfile("lock")).to eq(@lockfile)
+ expect { read_lockfile }.to raise_error(Errno::ENOENT)
+ end
+
+ it "writes to custom location using --lockfile when a default lockfile is present" do
+ bundle "install"
+ bundle "lock --lockfile=lock"
+
+ expect(out).to match(/Writing lockfile to.+lock/)
+ expect(read_lockfile("lock")).to eq(@lockfile)
+ end
+
+ it "update specific gems using --update" do
+ lockfile @lockfile.gsub("2.3.2", "2.3.1").gsub("13.0.1", "10.0.1")
+
+ bundle "lock --update rails rake"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "does not unlock git sources when only uri shape changes" do
+ build_git("foo")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}"
+ G
+
+ # Change uri format to end with "/" and reinstall
+ install_gemfile <<-G, :verbose => true
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}/"
+ G
+
+ expect(out).to include("using resolution from the lockfile")
+ expect(out).not_to include("re-resolving dependencies because the list of sources changed")
+ end
+
+ it "updates specific gems using --update using the locked revision of unrelated git gems for resolving" do
+ ref = build_git("foo").ref_for("HEAD")
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rake"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :branch => "deadbeef"
+ G
+
+ lockfile <<~L
+ GIT
+ remote: #{file_uri_for(lib_path("foo-1.0"))}
+ revision: #{ref}
+ branch: deadbeef
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rake (10.0.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+ rake
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock --update rake --verbose"
+ expect(out).to match(/Writing lockfile to.+lock/)
+ expect(lockfile).to include("rake (13.0.1)")
+ end
+
+ it "errors when updating a missing specific gems using --update" do
+ lockfile @lockfile
+
+ bundle "lock --update blahblah", :raise_on_error => false
+ expect(err).to eq("Could not find gem 'blahblah'.")
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "can lock without downloading gems" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "thin"
+ gem "rack_middleware", :group => "test"
+ G
+ bundle "config set without test"
+ bundle "config set path vendor/bundle"
+ bundle "lock"
+ expect(bundled_app("vendor/bundle")).not_to exist
+ end
+
+ # see update_spec for more coverage on same options. logic is shared so it's not necessary
+ # to repeat coverage here.
+ context "conservative updates" do
+ before do
+ build_repo4 do
+ build_gem "foo", %w[1.4.3 1.4.4] do |s|
+ s.add_dependency "bar", "~> 2.0"
+ end
+ build_gem "foo", %w[1.4.5 1.5.0] do |s|
+ s.add_dependency "bar", "~> 2.1"
+ end
+ build_gem "foo", %w[1.5.1] do |s|
+ s.add_dependency "bar", "~> 3.0"
+ end
+ build_gem "foo", %w[2.0.0.pre] do |s|
+ s.add_dependency "bar"
+ end
+ build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 2.1.2.pre 3.0.0 3.1.0.pre 4.0.0.pre]
+ build_gem "qux", %w[1.0.0 1.0.1 1.1.0 2.0.0]
+ end
+
+ # establish a lockfile set to 1.4.3
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'foo', '1.4.3'
+ gem 'bar', '2.0.3'
+ gem 'qux', '1.0.0'
+ G
+
+ # remove 1.4.3 requirement and bar altogether
+ # to setup update specs below
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'foo'
+ gem 'qux'
+ G
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ end
+
+ it "single gem updates dependent gem to minor" do
+ bundle "lock --update foo --patch"
+
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.4.5 bar-2.1.1 qux-1.0.0].sort)
+ end
+
+ it "minor preferred with strict" do
+ bundle "lock --update --minor --strict"
+
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.5.0 bar-2.1.1 qux-1.1.0].sort)
+ end
+
+ context "pre" do
+ it "defaults to major" do
+ bundle "lock --update --pre"
+
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-2.0.0.pre bar-4.0.0.pre qux-2.0.0].sort)
+ end
+
+ it "patch preferred" do
+ bundle "lock --update --patch --pre"
+
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.4.5 bar-2.1.2.pre qux-1.0.1].sort)
+ end
+
+ it "minor preferred" do
+ bundle "lock --update --minor --pre"
+
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.5.1 bar-3.1.0.pre qux-1.1.0].sort)
+ end
+
+ it "major preferred" do
+ bundle "lock --update --major --pre"
+
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-2.0.0.pre bar-4.0.0.pre qux-2.0.0].sort)
+ end
+ end
+ end
+
+ it "updates the bundler version in the lockfile to the latest bundler version" do
+ build_repo4 do
+ build_gem "bundler", "55"
+ end
+
+ system_gems "bundler-55", :gem_repo => gem_repo4
+
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ source "https://gems.repo4"
+ G
+ lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2')
+
+ bundle "lock --update --bundler --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ expect(lockfile).to end_with("BUNDLED WITH\n 55\n")
+
+ update_repo4 do
+ build_gem "bundler", "99"
+ end
+
+ bundle "lock --update --bundler --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ expect(lockfile).to end_with("BUNDLED WITH\n 99\n")
+ end
+
+ it "supports adding new platforms" do
+ bundle "lock --add-platform java x86-mingw32"
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ lockfile = Bundler::LockfileParser.new(read_lockfile)
+ expect(lockfile.platforms).to match_array([java, x86_mingw32, local_platform].uniq)
+ end
+
+ it "supports adding new platforms with force_ruby_platform = true" do
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-x86-64_linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ platform_specific
+ L
+
+ bundle "config set force_ruby_platform true"
+ bundle "lock --add-platform java x86-mingw32"
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ lockfile = Bundler::LockfileParser.new(read_lockfile)
+ expect(lockfile.platforms).to contain_exactly(rb, linux, java, x86_mingw32)
+ end
+
+ it "supports adding the `ruby` platform" do
+ bundle "lock --add-platform ruby"
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ lockfile = Bundler::LockfileParser.new(read_lockfile)
+ expect(lockfile.platforms).to match_array(["ruby", local_platform].uniq)
+ end
+
+ it "warns when adding an unknown platform" do
+ bundle "lock --add-platform foobarbaz"
+ expect(err).to include("The platform `foobarbaz` is unknown to RubyGems and adding it will likely lead to resolution errors")
+ end
+
+ it "allows removing platforms" do
+ bundle "lock --add-platform java x86-mingw32"
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ lockfile = Bundler::LockfileParser.new(read_lockfile)
+ expect(lockfile.platforms).to match_array([java, x86_mingw32, local_platform].uniq)
+
+ bundle "lock --remove-platform java"
+
+ lockfile = Bundler::LockfileParser.new(read_lockfile)
+ expect(lockfile.platforms).to match_array([x86_mingw32, local_platform].uniq)
+ end
+
+ it "also cleans up redundant platform gems when removing platforms" do
+ build_repo4 do
+ build_gem "nokogiri", "1.12.0"
+ build_gem "nokogiri", "1.12.0" do |s|
+ s.platform = "x86_64-darwin"
+ end
+ end
+
+ simulate_platform "x86_64-darwin-22" do
+ install_gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "nokogiri"
+ G
+ end
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ nokogiri (1.12.0)
+ nokogiri (1.12.0-x86_64-darwin)
+
+ PLATFORMS
+ ruby
+ x86_64-darwin
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-darwin-22" do
+ bundle "lock --remove-platform ruby"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ nokogiri (1.12.0-x86_64-darwin)
+
+ PLATFORMS
+ x86_64-darwin
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "errors when removing all platforms" do
+ bundle "lock --remove-platform #{local_platform}", :raise_on_error => false
+ expect(err).to include("Removing all platforms from the bundle is not allowed")
+ end
+
+ # from https://github.com/rubygems/bundler/issues/4896
+ it "properly adds platforms when platform requirements come from different dependencies" do
+ build_repo4 do
+ build_gem "ffi", "1.9.14"
+ build_gem "ffi", "1.9.14" do |s|
+ s.platform = x86_mingw32
+ end
+
+ build_gem "gssapi", "0.1"
+ build_gem "gssapi", "0.2"
+ build_gem "gssapi", "0.3"
+ build_gem "gssapi", "1.2.0" do |s|
+ s.add_dependency "ffi", ">= 1.0.1"
+ end
+
+ build_gem "mixlib-shellout", "2.2.6"
+ build_gem "mixlib-shellout", "2.2.6" do |s|
+ s.platform = "universal-mingw32"
+ s.add_dependency "win32-process", "~> 0.8.2"
+ end
+
+ # we need all these versions to get the sorting the same as it would be
+ # pulling from rubygems.org
+ %w[0.8.3 0.8.2 0.8.1 0.8.0].each do |v|
+ build_gem "win32-process", v do |s|
+ s.add_dependency "ffi", ">= 1.0.0"
+ end
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "mixlib-shellout"
+ gem "gssapi"
+ G
+
+ simulate_platform(x86_mingw32) { bundle :lock }
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ ffi (1.9.14-x86-mingw32)
+ gssapi (1.2.0)
+ ffi (>= 1.0.1)
+ mixlib-shellout (2.2.6-universal-mingw32)
+ win32-process (~> 0.8.2)
+ win32-process (0.8.3)
+ ffi (>= 1.0.0)
+
+ PLATFORMS
+ x86-mingw32
+
+ DEPENDENCIES
+ gssapi
+ mixlib-shellout
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ bundle "config set --local force_ruby_platform true"
+ bundle :lock
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ ffi (1.9.14)
+ ffi (1.9.14-x86-mingw32)
+ gssapi (1.2.0)
+ ffi (>= 1.0.1)
+ mixlib-shellout (2.2.6)
+ mixlib-shellout (2.2.6-universal-mingw32)
+ win32-process (~> 0.8.2)
+ win32-process (0.8.3)
+ ffi (>= 1.0.0)
+
+ PLATFORMS
+ ruby
+ x86-mingw32
+
+ DEPENDENCIES
+ gssapi
+ mixlib-shellout
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "doesn't crash when an update candidate doesn't have any matching platform" do
+ build_repo4 do
+ build_gem "libv8", "8.4.255.0"
+ build_gem "libv8", "8.4.255.0" do |s|
+ s.platform = "x86_64-darwin-19"
+ end
+
+ build_gem "libv8", "15.0.71.48.1beta2" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "libv8"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ libv8 (8.4.255.0)
+ libv8 (8.4.255.0-x86_64-darwin-19)
+
+ PLATFORMS
+ ruby
+ x86_64-darwin-19
+
+ DEPENDENCIES
+ libv8
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ simulate_platform(Gem::Platform.new("x86_64-darwin-19")) { bundle "lock --update" }
+
+ expect(out).to match(/Writing lockfile to.+Gemfile\.lock/)
+ end
+
+ it "adds all more specific candidates when they all have the same dependencies" do
+ build_repo4 do
+ build_gem "libv8", "8.4.255.0" do |s|
+ s.platform = "x86_64-darwin-19"
+ end
+
+ build_gem "libv8", "8.4.255.0" do |s|
+ s.platform = "x86_64-darwin-20"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "libv8"
+ G
+
+ simulate_platform(Gem::Platform.new("x86_64-darwin")) { bundle "lock" }
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ libv8 (8.4.255.0-x86_64-darwin-19)
+ libv8 (8.4.255.0-x86_64-darwin-20)
+
+ PLATFORMS
+ x86_64-darwin
+
+ DEPENDENCIES
+ libv8
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "respects the previous lockfile if it had a matching less specific platform already locked, and installs the best variant for each platform" do
+ build_repo4 do
+ build_gem "libv8", "8.4.255.0" do |s|
+ s.platform = "x86_64-darwin-19"
+ end
+
+ build_gem "libv8", "8.4.255.0" do |s|
+ s.platform = "x86_64-darwin-20"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "libv8"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ libv8 (8.4.255.0-x86_64-darwin-19)
+ libv8 (8.4.255.0-x86_64-darwin-20)
+
+ PLATFORMS
+ x86_64-darwin
+
+ DEPENDENCIES
+ libv8
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ previous_lockfile = lockfile
+
+ %w[x86_64-darwin-19 x86_64-darwin-20].each do |platform|
+ simulate_platform(Gem::Platform.new(platform)) do
+ bundle "lock"
+ expect(lockfile).to eq(previous_lockfile)
+
+ bundle "install"
+ expect(the_bundle).to include_gem("libv8 8.4.255.0 #{platform}")
+ end
+ end
+ end
+
+ it "does not conflict on ruby requirements when adding new platforms" do
+ build_repo4 do
+ build_gem "raygun-apm", "1.0.78" do |s|
+ s.platform = "x86_64-linux"
+ s.required_ruby_version = "< #{next_ruby_minor}.dev"
+ end
+
+ build_gem "raygun-apm", "1.0.78" do |s|
+ s.platform = "universal-darwin"
+ s.required_ruby_version = "< #{next_ruby_minor}.dev"
+ end
+
+ build_gem "raygun-apm", "1.0.78" do |s|
+ s.platform = "x64-mingw32"
+ s.required_ruby_version = "< #{next_ruby_minor}.dev"
+ end
+
+ build_gem "raygun-apm", "1.0.78" do |s|
+ s.platform = "x64-mingw-ucrt"
+ s.required_ruby_version = "< #{next_ruby_minor}.dev"
+ end
+ end
+
+ gemfile <<-G
+ source "https://localgemserver.test"
+
+ gem "raygun-apm"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://localgemserver.test/
+ specs:
+ raygun-apm (1.0.78-universal-darwin)
+
+ PLATFORMS
+ x86_64-darwin-19
+
+ DEPENDENCIES
+ raygun-apm
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock --add-platform x86_64-linux", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ end
+
+ it "does not crash on conflicting ruby requirements between platform versions in two different gems" do
+ build_repo4 do
+ build_gem "unf_ext", "0.0.8.2"
+
+ build_gem "unf_ext", "0.0.8.2" do |s|
+ s.required_ruby_version = [">= 2.4", "< #{previous_ruby_minor}"]
+ s.platform = "x64-mingw32"
+ end
+
+ build_gem "unf_ext", "0.0.8.2" do |s|
+ s.required_ruby_version = [">= #{previous_ruby_minor}", "< #{current_ruby_minor}"]
+ s.platform = "x64-mingw-ucrt"
+ end
+
+ build_gem "google-protobuf", "3.21.12"
+
+ build_gem "google-protobuf", "3.21.12" do |s|
+ s.required_ruby_version = [">= 2.5", "< #{previous_ruby_minor}"]
+ s.platform = "x64-mingw32"
+ end
+
+ build_gem "google-protobuf", "3.21.12" do |s|
+ s.required_ruby_version = [">= #{previous_ruby_minor}", "< #{current_ruby_minor}"]
+ s.platform = "x64-mingw-ucrt"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "google-protobuf"
+ gem "unf_ext"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ google-protobuf (3.21.12)
+ unf_ext (0.0.8.2)
+
+ PLATFORMS
+ x64-mingw-ucrt
+ x64-mingw32
+
+ DEPENDENCIES
+ google-protobuf
+ unf_ext
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "DEBUG_RESOLVER" => "1" }
+ end
+
+ it "respects lower bound ruby requirements" do
+ build_repo4 do
+ build_gem "our_private_gem", "0.1.0" do |s|
+ s.required_ruby_version = ">= #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<-G
+ source "https://localgemserver.test"
+
+ gem "our_private_gem"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://localgemserver.test/
+ specs:
+ our_private_gem (0.1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ our_private_gem
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ end
+
+ context "when an update is available" do
+ let(:repo) { gem_repo2 }
+
+ before do
+ lockfile(@lockfile)
+ build_repo2 do
+ build_gem "foo", "2.0"
+ end
+ end
+
+ it "does not implicitly update" do
+ bundle "lock"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "accounts for changes in the gemfile" do
+ gemfile gemfile.gsub('"foo"', '"foo", "2.0"')
+ bundle "lock"
+
+ expect(read_lockfile).to eq(@lockfile.sub("foo (1.0)", "foo (2.0)").sub(/foo$/, "foo (= 2.0)"))
+ end
+ end
+
+ context "when a system gem has incorrect dependencies, different from the lockfile" do
+ before do
+ build_repo4 do
+ build_gem "debug", "1.6.3" do |s|
+ s.add_dependency "irb", ">= 1.3.6"
+ end
+
+ build_gem "irb", "1.5.0"
+ end
+
+ system_gems "irb-1.5.0", :gem_repo => gem_repo4
+ system_gems "debug-1.6.3", :gem_repo => gem_repo4
+
+ # simulate gemspec with wrong empty dependencies
+ debug_gemspec_path = system_gem_path("specifications/debug-1.6.3.gemspec")
+ debug_gemspec = Gem::Specification.load(debug_gemspec_path.to_s)
+ debug_gemspec.dependencies.clear
+ File.write(debug_gemspec_path, debug_gemspec.to_ruby)
+ end
+
+ it "respects the existing lockfile, even when reresolving" do
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "debug"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ debug (1.6.3)
+ irb (>= 1.3.6)
+ irb (1.5.0)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ debug
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "arm64-darwin-22" do
+ bundle "lock"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ debug (1.6.3)
+ irb (>= 1.3.6)
+ irb (1.5.0)
+
+ PLATFORMS
+ arm64-darwin-22
+ x86_64-linux
+
+ DEPENDENCIES
+ debug
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ it "properly shows resolution errors including OR requirements" do
+ build_repo4 do
+ build_gem "activeadmin", "2.13.1" do |s|
+ s.add_dependency "railties", ">= 6.1", "< 7.1"
+ end
+ build_gem "actionpack", "6.1.4"
+ build_gem "actionpack", "7.0.3.1"
+ build_gem "actionpack", "7.0.4"
+ build_gem "railties", "6.1.4" do |s|
+ s.add_dependency "actionpack", "6.1.4"
+ end
+ build_gem "rails", "7.0.3.1" do |s|
+ s.add_dependency "railties", "7.0.3.1"
+ end
+ build_gem "rails", "7.0.4" do |s|
+ s.add_dependency "railties", "7.0.4"
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "rails", ">= 7.0.3.1"
+ gem "activeadmin", "2.13.1"
+ G
+
+ bundle "lock", :raise_on_error => false
+
+ expect(err).to eq <<~ERR.strip
+ Could not find compatible versions
+
+ Because rails >= 7.0.4 depends on railties = 7.0.4
+ and rails < 7.0.4 depends on railties = 7.0.3.1,
+ railties = 7.0.3.1 OR = 7.0.4 is required.
+ So, because railties = 7.0.3.1 OR = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally,
+ version solving has failed.
+ ERR
+ end
+
+ it "is able to display some explanation on crazy irresolvable cases" do
+ build_repo4 do
+ build_gem "activeadmin", "2.13.1" do |s|
+ s.add_dependency "ransack", "= 3.1.0"
+ end
+
+ # Activemodel is missing as a dependency in lockfile
+ build_gem "ransack", "3.1.0" do |s|
+ s.add_dependency "activemodel", ">= 6.0.4"
+ s.add_dependency "activesupport", ">= 6.0.4"
+ end
+
+ %w[6.0.4 7.0.2.3 7.0.3.1 7.0.4].each do |version|
+ build_gem "activesupport", version
+
+ # Activemodel is only available on 6.0.4
+ if version == "6.0.4"
+ build_gem "activemodel", version do |s|
+ s.add_dependency "activesupport", version
+ end
+ end
+
+ build_gem "rails", version do |s|
+ # Depednencies of Rails 7.0.2.3 are in reverse order
+ if version == "7.0.2.3"
+ s.add_dependency "activesupport", version
+ s.add_dependency "activemodel", version
+ else
+ s.add_dependency "activemodel", version
+ s.add_dependency "activesupport", version
+ end
+ end
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "rails", ">= 7.0.2.3"
+ gem "activeadmin", "= 2.13.1"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ activeadmin (2.13.1)
+ ransack (= 3.1.0)
+ ransack (3.1.0)
+ activemodel (>= 6.0.4)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ activeadmin (= 2.13.1)
+ ransack (= 3.1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock", :raise_on_error => false
+
+ expect(err).to eq <<~ERR.strip
+ Could not find compatible versions
+
+ Because every version of activemodel depends on activesupport = 6.0.4
+ and rails >= 7.0.2.3, < 7.0.3.1 depends on activesupport = 7.0.2.3,
+ every version of activemodel is incompatible with rails >= 7.0.2.3, < 7.0.3.1.
+ And because rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3,
+ rails >= 7.0.2.3, < 7.0.3.1 cannot be used.
+ (1) So, because rails >= 7.0.3.1, < 7.0.4 depends on activemodel = 7.0.3.1
+ and rails >= 7.0.4 depends on activemodel = 7.0.4,
+ rails >= 7.0.2.3 requires activemodel = 7.0.3.1 OR = 7.0.4.
+
+ Because rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3
+ and rails >= 7.0.3.1, < 7.0.4 depends on activesupport = 7.0.3.1,
+ rails >= 7.0.2.3, < 7.0.4 requires activemodel = 7.0.2.3 or activesupport = 7.0.3.1.
+ And because rails >= 7.0.4 depends on activesupport = 7.0.4
+ and every version of activemodel depends on activesupport = 6.0.4,
+ activemodel != 7.0.2.3 is incompatible with rails >= 7.0.2.3.
+ And because rails >= 7.0.2.3 requires activemodel = 7.0.3.1 OR = 7.0.4 (1),
+ rails >= 7.0.2.3 cannot be used.
+ So, because Gemfile depends on rails >= 7.0.2.3,
+ version solving has failed.
+ ERR
+
+ lockfile lockfile.gsub(/PLATFORMS\n #{lockfile_platforms}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}")
+
+ bundle "lock", :raise_on_error => false
+
+ expect(err).to eq <<~ERR.strip
+ Could not find compatible versions
+
+ Because rails >= 7.0.3.1, < 7.0.4 depends on activemodel = 7.0.3.1
+ and rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3,
+ rails >= 7.0.2.3, < 7.0.4 requires activemodel = 7.0.2.3 OR = 7.0.3.1.
+ And because every version of activemodel depends on activesupport = 6.0.4,
+ rails >= 7.0.2.3, < 7.0.4 requires activesupport = 6.0.4.
+ Because rails >= 7.0.3.1, < 7.0.4 depends on activesupport = 7.0.3.1
+ and rails >= 7.0.2.3, < 7.0.3.1 depends on activesupport = 7.0.2.3,
+ rails >= 7.0.2.3, < 7.0.4 requires activesupport = 7.0.2.3 OR = 7.0.3.1.
+ Thus, rails >= 7.0.2.3, < 7.0.4 cannot be used.
+ And because rails >= 7.0.4 depends on activemodel = 7.0.4,
+ rails >= 7.0.2.3 requires activemodel = 7.0.4.
+ So, because activemodel = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally
+ and Gemfile depends on rails >= 7.0.2.3,
+ version solving has failed.
+ ERR
+ end
+
+ it "does not accidentally resolves to prereleases" do
+ build_repo4 do
+ build_gem "autoproj", "2.0.3" do |s|
+ s.add_dependency "autobuild", ">= 1.10.0.a"
+ s.add_dependency "tty-prompt"
+ end
+
+ build_gem "tty-prompt", "0.6.0"
+ build_gem "tty-prompt", "0.7.0"
+
+ build_gem "autobuild", "1.10.0.b3"
+ build_gem "autobuild", "1.10.1" do |s|
+ s.add_dependency "tty-prompt", "~> 0.6.0"
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "autoproj", ">= 2.0.0"
+ G
+
+ bundle "lock"
+ expect(lockfile).to_not include("autobuild (1.10.0.b3)")
+ expect(lockfile).to include("autobuild (1.10.1)")
+ end
+
+ # Newer rails depends on Bundler, while ancient Rails does not. Bundler tries
+ # a first resolution pass that does not consider pre-releases. However, when
+ # using a pre-release Bundler (like the .dev version), that results in that
+ # pre-release being ignored and resolving to a version that does not depend on
+ # Bundler at all. We should avoid that and still consider .dev Bundler.
+ #
+ it "does not ignore prereleases with there's only one candidate" do
+ build_repo4 do
+ build_gem "rails", "7.4.0.2" do |s|
+ s.add_dependency "bundler", ">= 1.15.0"
+ end
+
+ build_gem "rails", "2.3.18"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "rails"
+ G
+
+ bundle "lock"
+ expect(lockfile).to_not include("rails (2.3.18)")
+ expect(lockfile).to include("rails (7.4.0.2)")
+ end
+
+ it "deals with platform specific incompatibilities" do
+ build_repo4 do
+ build_gem "activerecord", "6.0.6"
+ build_gem "activerecord-jdbc-adapter", "60.4" do |s|
+ s.platform = "java"
+ s.add_dependency "activerecord", "~> 6.0.0"
+ end
+ build_gem "activerecord-jdbc-adapter", "61.0" do |s|
+ s.platform = "java"
+ s.add_dependency "activerecord", "~> 6.1.0"
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "activerecord", "6.0.6"
+ gem "activerecord-jdbc-adapter", "61.0"
+ G
+
+ simulate_platform "universal-java-19" do
+ bundle "lock", :raise_on_error => false
+ end
+
+ expect(err).to include("Could not find compatible versions")
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+ end
+
+ context "when re-resolving to include prereleases" do
+ before do
+ build_repo4 do
+ build_gem "tzinfo-data", "1.2022.7"
+ build_gem "rails", "7.1.0.alpha" do |s|
+ s.add_dependency "activesupport"
+ end
+ build_gem "activesupport", "7.1.0.alpha"
+ end
+ end
+
+ it "does not end up including gems scoped to other platforms in the lockfile" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "rails"
+ gem "tzinfo-data", platform: :windows
+ G
+
+ simulate_platform "x86_64-darwin-22" do
+ bundle "lock"
+ end
+
+ expect(lockfile).not_to include("tzinfo-data (1.2022.7)")
+ end
+ end
+
+ context "when resolving platform specific gems as indirect dependencies on truffleruby", :truffleruby_only do
+ before do
+ build_lib "foo", :path => bundled_app do |s|
+ s.add_dependency "nokogiri"
+ end
+
+ build_repo4 do
+ build_gem "nokogiri", "1.14.2"
+ build_gem "nokogiri", "1.14.2" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gemspec
+ G
+ end
+
+ it "locks ruby specs" do
+ simulate_platform "x86_64-linux" do
+ bundle "lock"
+ end
+
+ expect(lockfile).to eq <<~L
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+ nokogiri
+
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ nokogiri (1.14.2)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when adding a new gem that requires unlocking other transitive deps" do
+ before do
+ build_repo4 do
+ build_gem "govuk_app_config", "0.1.0"
+
+ build_gem "govuk_app_config", "4.13.0" do |s|
+ s.add_dependency "railties", ">= 5.0"
+ end
+
+ %w[7.0.4.1 7.0.4.3].each do |v|
+ build_gem "railties", v do |s|
+ s.add_dependency "actionpack", v
+ s.add_dependency "activesupport", v
+ end
+
+ build_gem "activesupport", v
+ build_gem "actionpack", v
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "govuk_app_config"
+ gem "activesupport", "7.0.4.3"
+ G
+
+ # Simulate out of sync lockfile because top level dependency on
+ # activesuport has just been added to the Gemfile, and locked to a higher
+ # version
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ actionpack (7.0.4.1)
+ activesupport (7.0.4.1)
+ govuk_app_config (4.13.0)
+ railties (>= 5.0)
+ railties (7.0.4.1)
+ actionpack (= 7.0.4.1)
+ activesupport (= 7.0.4.1)
+
+ PLATFORMS
+ arm64-darwin-22
+
+ DEPENDENCIES
+ govuk_app_config
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "does not downgrade top level dependencies" do
+ simulate_platform "arm64-darwin-22" do
+ bundle "lock"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ actionpack (7.0.4.3)
+ activesupport (7.0.4.3)
+ govuk_app_config (4.13.0)
+ railties (>= 5.0)
+ railties (7.0.4.3)
+ actionpack (= 7.0.4.3)
+ activesupport (= 7.0.4.3)
+
+ PLATFORMS
+ arm64-darwin-22
+
+ DEPENDENCIES
+ activesupport (= 7.0.4.3)
+ govuk_app_config
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+end
diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb
new file mode 100644
index 0000000000..ede1ff6b8e
--- /dev/null
+++ b/spec/bundler/commands/newgem_spec.rb
@@ -0,0 +1,1662 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle gem" do
+ def gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to exist
+ expect(bundled_app("#{gem_name}/README.md")).to exist
+ expect(bundled_app("#{gem_name}/Gemfile")).to exist
+ expect(bundled_app("#{gem_name}/Rakefile")).to exist
+ expect(bundled_app("#{gem_name}/lib/#{require_path}.rb")).to exist
+ expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb")).to exist
+ end
+
+ def bundle_exec_rubocop
+ prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec"))
+ bundle "config set path #{rubocop_gems}", :dir => bundled_app(gem_name)
+ bundle "exec rubocop --debug --config .rubocop.yml", :dir => bundled_app(gem_name)
+ end
+
+ def bundle_exec_standardrb
+ prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec"))
+ bundle "config set path #{standard_gems}", :dir => bundled_app(gem_name)
+ bundle "exec standardrb --debug", :dir => bundled_app(gem_name)
+ end
+
+ let(:generated_gemspec) { Bundler.load_gemspec_uncached(bundled_app(gem_name).join("#{gem_name}.gemspec")) }
+
+ let(:gem_name) { "mygem" }
+
+ let(:require_path) { "mygem" }
+
+ let(:minitest_test_file_path) { "test/test_mygem.rb" }
+
+ let(:minitest_test_class_name) { "class TestMygem < Minitest::Test" }
+
+ before do
+ sys_exec("git config --global user.name 'Bundler User'")
+ sys_exec("git config --global user.email user@example.com")
+ sys_exec("git config --global github.user bundleuser")
+ end
+
+ describe "git repo initialization" do
+ it "generates a gem skeleton with a .git folder", :readline do
+ bundle "gem #{gem_name}"
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/.git")).to exist
+ end
+
+ it "generates a gem skeleton with a .git folder when passing --git", :readline do
+ bundle "gem #{gem_name} --git"
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/.git")).to exist
+ end
+
+ it "generates a gem skeleton without a .git folder when passing --no-git", :readline do
+ bundle "gem #{gem_name} --no-git"
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/.git")).not_to exist
+ end
+
+ context "on a path with spaces" do
+ before do
+ Dir.mkdir(bundled_app("path with spaces"))
+ end
+
+ it "properly initializes git repo", :readline do
+ bundle "gem #{gem_name}", :dir => bundled_app("path with spaces")
+ expect(bundled_app("path with spaces/#{gem_name}/.git")).to exist
+ end
+ end
+ end
+
+ shared_examples_for "--mit flag" do
+ before do
+ bundle "gem #{gem_name} --mit"
+ end
+ it "generates a gem skeleton with MIT license" do
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/LICENSE.txt")).to exist
+ expect(generated_gemspec.license).to eq("MIT")
+ end
+ end
+
+ shared_examples_for "--no-mit flag" do
+ before do
+ bundle "gem #{gem_name} --no-mit"
+ end
+ it "generates a gem skeleton without MIT license" do
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/LICENSE.txt")).to_not exist
+ end
+ end
+
+ shared_examples_for "--coc flag" do
+ it "generates a gem skeleton with MIT license" do
+ bundle "gem #{gem_name} --coc"
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/CODE_OF_CONDUCT.md")).to exist
+ end
+
+ it "generates the README with a section for the Code of Conduct" do
+ bundle "gem #{gem_name} --coc"
+ expect(bundled_app("#{gem_name}/README.md").read).to include("## Code of Conduct")
+ expect(bundled_app("#{gem_name}/README.md").read).to match(%r{https://github\.com/bundleuser/#{gem_name}/blob/.*/CODE_OF_CONDUCT.md})
+ end
+
+ it "generates the README with a section for the Code of Conduct, respecting the configured git default branch", :git => ">= 2.28.0" do
+ sys_exec("git config --global init.defaultBranch main")
+ bundle "gem #{gem_name} --coc"
+
+ expect(bundled_app("#{gem_name}/README.md").read).to include("## Code of Conduct")
+ expect(bundled_app("#{gem_name}/README.md").read).to include("https://github.com/bundleuser/#{gem_name}/blob/main/CODE_OF_CONDUCT.md")
+ end
+ end
+
+ shared_examples_for "--no-coc flag" do
+ before do
+ bundle "gem #{gem_name} --no-coc"
+ end
+ it "generates a gem skeleton without Code of Conduct" do
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/CODE_OF_CONDUCT.md")).to_not exist
+ end
+
+ it "generates the README without a section for the Code of Conduct" do
+ expect(bundled_app("#{gem_name}/README.md").read).not_to include("## Code of Conduct")
+ expect(bundled_app("#{gem_name}/README.md").read).not_to match(%r{https://github\.com/bundleuser/#{gem_name}/blob/.*/CODE_OF_CONDUCT.md})
+ end
+ end
+
+ shared_examples_for "--changelog flag" do
+ before do
+ bundle "gem #{gem_name} --changelog"
+ end
+ it "generates a gem skeleton with a CHANGELOG", :readline do
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/CHANGELOG.md")).to exist
+ end
+ end
+
+ shared_examples_for "--no-changelog flag" do
+ before do
+ bundle "gem #{gem_name} --no-changelog"
+ end
+ it "generates a gem skeleton without a CHANGELOG", :readline do
+ gem_skeleton_assertions
+ expect(bundled_app("#{gem_name}/CHANGELOG.md")).to_not exist
+ end
+ end
+
+ shared_examples_for "--rubocop flag" do
+ context "is deprecated", :bundler => "< 3" do
+ before do
+ bundle "gem #{gem_name} --rubocop"
+ end
+
+ it "generates a gem skeleton with rubocop" do
+ gem_skeleton_assertions
+ expect(bundled_app("test-gem/Rakefile")).to read_as(
+ include("# frozen_string_literal: true").
+ and(include('require "rubocop/rake_task"').
+ and(include("RuboCop::RakeTask.new").
+ and(match(/default:.+:rubocop/))))
+ )
+ end
+
+ it "includes rubocop in generated Gemfile" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ builder = Bundler::Dsl.new
+ builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile"))
+ builder.dependencies
+ rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" }
+ expect(rubocop_dep).not_to be_nil
+ end
+
+ it "generates a default .rubocop.yml" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist
+ end
+ end
+ end
+
+ shared_examples_for "--no-rubocop flag" do
+ context "is deprecated", :bundler => "< 3" do
+ define_negated_matcher :exclude, :include
+
+ before do
+ bundle "gem #{gem_name} --no-rubocop"
+ end
+
+ it "generates a gem skeleton without rubocop" do
+ gem_skeleton_assertions
+ expect(bundled_app("test-gem/Rakefile")).to read_as(exclude("rubocop"))
+ expect(bundled_app("test-gem/#{gem_name}.gemspec")).to read_as(exclude("rubocop"))
+ end
+
+ it "does not include rubocop in generated Gemfile" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ builder = Bundler::Dsl.new
+ builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile"))
+ builder.dependencies
+ rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" }
+ expect(rubocop_dep).to be_nil
+ end
+
+ it "doesn't generate a default .rubocop.yml" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist
+ end
+ end
+ end
+
+ shared_examples_for "--linter=rubocop flag" do
+ before do
+ bundle "gem #{gem_name} --linter=rubocop"
+ end
+
+ it "generates a gem skeleton with rubocop" do
+ gem_skeleton_assertions
+ expect(bundled_app("test-gem/Rakefile")).to read_as(
+ include("# frozen_string_literal: true").
+ and(include('require "rubocop/rake_task"').
+ and(include("RuboCop::RakeTask.new").
+ and(match(/default:.+:rubocop/))))
+ )
+ end
+
+ it "includes rubocop in generated Gemfile" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ builder = Bundler::Dsl.new
+ builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile"))
+ builder.dependencies
+ rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" }
+ expect(rubocop_dep).not_to be_nil
+ end
+
+ it "generates a default .rubocop.yml" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist
+ end
+ end
+
+ shared_examples_for "--linter=standard flag" do
+ before do
+ bundle "gem #{gem_name} --linter=standard"
+ end
+
+ it "generates a gem skeleton with standard" do
+ gem_skeleton_assertions
+ expect(bundled_app("test-gem/Rakefile")).to read_as(
+ include('require "standard/rake"').
+ and(match(/default:.+:standard/))
+ )
+ end
+
+ it "includes standard in generated Gemfile" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ builder = Bundler::Dsl.new
+ builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile"))
+ builder.dependencies
+ standard_dep = builder.dependencies.find {|d| d.name == "standard" }
+ expect(standard_dep).not_to be_nil
+ end
+
+ it "generates a default .standard.yml" do
+ expect(bundled_app("#{gem_name}/.standard.yml")).to exist
+ end
+ end
+
+ shared_examples_for "--linter=none flag" do
+ define_negated_matcher :exclude, :include
+
+ before do
+ bundle "gem #{gem_name} --linter=none"
+ end
+
+ it "generates a gem skeleton without rubocop" do
+ gem_skeleton_assertions
+ expect(bundled_app("test-gem/Rakefile")).to read_as(exclude("rubocop"))
+ expect(bundled_app("test-gem/#{gem_name}.gemspec")).to read_as(exclude("rubocop"))
+ end
+
+ it "does not include rubocop in generated Gemfile" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ builder = Bundler::Dsl.new
+ builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile"))
+ builder.dependencies
+ rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" }
+ expect(rubocop_dep).to be_nil
+ end
+
+ it "does not include standard in generated Gemfile" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ builder = Bundler::Dsl.new
+ builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile"))
+ builder.dependencies
+ standard_dep = builder.dependencies.find {|d| d.name == "standard" }
+ expect(standard_dep).to be_nil
+ end
+
+ it "doesn't generate a default .rubocop.yml" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist
+ end
+
+ it "doesn't generate a default .standard.yml" do
+ expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist
+ end
+ end
+
+ it "has no rubocop offenses when using --linter=rubocop flag", :readline do
+ skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core?
+ bundle "gem #{gem_name} --linter=rubocop"
+ bundle_exec_rubocop
+ expect(last_command).to be_success
+ end
+
+ it "has no rubocop offenses when using --ext=c and --linter=rubocop flag", :readline do
+ skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core?
+ bundle "gem #{gem_name} --ext=c --linter=rubocop"
+ bundle_exec_rubocop
+ expect(last_command).to be_success
+ end
+
+ it "has no rubocop offenses when using --ext=c, --test=minitest, and --linter=rubocop flag", :readline do
+ skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core?
+ bundle "gem #{gem_name} --ext=c --test=minitest --linter=rubocop"
+ bundle_exec_rubocop
+ expect(last_command).to be_success
+ end
+
+ it "has no rubocop offenses when using --ext=c, --test=rspec, and --linter=rubocop flag", :readline do
+ skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core?
+ bundle "gem #{gem_name} --ext=c --test=rspec --linter=rubocop"
+ bundle_exec_rubocop
+ expect(last_command).to be_success
+ end
+
+ it "has no rubocop offenses when using --ext=c, --test=test-unit, and --linter=rubocop flag", :readline do
+ skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core?
+ bundle "gem #{gem_name} --ext=c --test=test-unit --linter=rubocop"
+ bundle_exec_rubocop
+ expect(last_command).to be_success
+ end
+
+ it "has no standard offenses when using --linter=standard flag", :readline do
+ skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core?
+ bundle "gem #{gem_name} --linter=standard"
+ bundle_exec_standardrb
+ expect(last_command).to be_success
+ end
+
+ it "has no rubocop offenses when using --ext=rust and --linter=rubocop flag", :readline do
+ skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core?
+ skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version
+
+ bundle "gem #{gem_name} --ext=rust --linter=rubocop"
+ bundle_exec_rubocop
+ expect(last_command).to be_success
+ end
+
+ it "has no rubocop offenses when using --ext=rust, --test=minitest, and --linter=rubocop flag", :readline do
+ skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core?
+ skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version
+
+ bundle "gem #{gem_name} --ext=rust --test=minitest --linter=rubocop"
+ bundle_exec_rubocop
+ expect(last_command).to be_success
+ end
+
+ it "has no rubocop offenses when using --ext=rust, --test=rspec, and --linter=rubocop flag", :readline do
+ skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core?
+ skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version
+
+ bundle "gem #{gem_name} --ext=rust --test=rspec --linter=rubocop"
+ bundle_exec_rubocop
+ expect(last_command).to be_success
+ end
+
+ it "has no rubocop offenses when using --ext=rust, --test=test-unit, and --linter=rubocop flag", :readline do
+ skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core?
+ skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version
+
+ bundle "gem #{gem_name} --ext=rust --test=test-unit --linter=rubocop"
+ bundle_exec_rubocop
+ expect(last_command).to be_success
+ end
+
+ shared_examples_for "CI config is absent" do
+ it "does not create any CI files" do
+ expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist
+ end
+ end
+
+ shared_examples_for "test framework is absent" do
+ it "does not create any test framework files" do
+ expect(bundled_app("#{gem_name}/.rspec")).to_not exist
+ expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to_not exist
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to_not exist
+ expect(bundled_app("#{gem_name}/test/#{require_path}.rb")).to_not exist
+ expect(bundled_app("#{gem_name}/test/test_helper.rb")).to_not exist
+ end
+ end
+
+ context "README.md", :readline do
+ context "git config github.user present" do
+ before do
+ bundle "gem #{gem_name}"
+ end
+
+ it "contribute URL set to git username" do
+ expect(bundled_app("#{gem_name}/README.md").read).not_to include("[USERNAME]")
+ expect(bundled_app("#{gem_name}/README.md").read).to include("github.com/bundleuser")
+ end
+ end
+
+ context "git config github.user is absent" do
+ before do
+ sys_exec("git config --global --unset github.user")
+ bundle "gem #{gem_name}"
+ end
+
+ it "contribute URL set to [USERNAME]" do
+ expect(bundled_app("#{gem_name}/README.md").read).to include("[USERNAME]")
+ expect(bundled_app("#{gem_name}/README.md").read).not_to include("github.com/bundleuser")
+ end
+ end
+ end
+
+ it "creates a new git repository", :readline do
+ bundle "gem #{gem_name}"
+ expect(bundled_app("#{gem_name}/.git")).to exist
+ end
+
+ context "when git is not available", :readline do
+ # This spec cannot have `git` available in the test env
+ before do
+ load_paths = [lib_dir, spec_dir]
+ load_path_str = "-I#{load_paths.join(File::PATH_SEPARATOR)}"
+
+ sys_exec "#{Gem.ruby} #{load_path_str} #{bindir.join("bundle")} gem #{gem_name}", :env => { "PATH" => "" }
+ end
+
+ it "creates the gem without the need for git" do
+ expect(bundled_app("#{gem_name}/README.md")).to exist
+ end
+
+ it "doesn't create a git repo" do
+ expect(bundled_app("#{gem_name}/.git")).to_not exist
+ end
+
+ it "doesn't create a .gitignore file" do
+ expect(bundled_app("#{gem_name}/.gitignore")).to_not exist
+ end
+ end
+
+ it "generates a valid gemspec", :readline, :ruby_repo do
+ bundle "gem newgem --bin"
+
+ prepare_gemspec(bundled_app("newgem", "newgem.gemspec"))
+
+ gems = ["rake-13.0.1"]
+ path = Bundler.feature_flag.default_install_uses_path? ? local_gem_path(:base => bundled_app("newgem")) : system_gem_path
+ system_gems gems, :path => path
+ bundle "exec rake build", :dir => bundled_app("newgem")
+
+ expect(last_command.stdboth).not_to include("ERROR")
+ end
+
+ context "gem naming with relative paths", :readline do
+ it "resolves ." do
+ create_temporary_dir("tmp")
+
+ bundle "gem .", :dir => bundled_app("tmp")
+
+ expect(bundled_app("tmp/lib/tmp.rb")).to exist
+ end
+
+ it "resolves .." do
+ create_temporary_dir("temp/empty_dir")
+
+ bundle "gem ..", :dir => bundled_app("temp/empty_dir")
+
+ expect(bundled_app("temp/lib/temp.rb")).to exist
+ end
+
+ it "resolves relative directory" do
+ create_temporary_dir("tmp/empty/tmp")
+
+ bundle "gem ../../empty", :dir => bundled_app("tmp/empty/tmp")
+
+ expect(bundled_app("tmp/empty/lib/empty.rb")).to exist
+ end
+
+ def create_temporary_dir(dir)
+ FileUtils.mkdir_p(bundled_app(dir))
+ end
+ end
+
+ shared_examples_for "--github-username option" do |github_username|
+ before do
+ bundle "gem #{gem_name} --github-username=#{github_username}"
+ end
+
+ it "generates a gem skeleton" do
+ gem_skeleton_assertions
+ end
+
+ it "contribute URL set to given github username" do
+ expect(bundled_app("#{gem_name}/README.md").read).not_to include("[USERNAME]")
+ expect(bundled_app("#{gem_name}/README.md").read).to include("github.com/#{github_username}")
+ end
+ end
+
+ shared_examples_for "github_username configuration" do
+ context "with github_username setting set to some value" do
+ before do
+ global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username"
+ bundle "gem #{gem_name}"
+ end
+
+ it "generates a gem skeleton" do
+ gem_skeleton_assertions
+ end
+
+ it "contribute URL set to bundle config setting" do
+ expect(bundled_app("#{gem_name}/README.md").read).not_to include("[USERNAME]")
+ expect(bundled_app("#{gem_name}/README.md").read).to include("github.com/different_username")
+ end
+ end
+
+ context "with github_username setting set to false" do
+ before do
+ global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false"
+ bundle "gem #{gem_name}"
+ end
+
+ it "generates a gem skeleton" do
+ gem_skeleton_assertions
+ end
+
+ it "contribute URL set to [USERNAME]" do
+ expect(bundled_app("#{gem_name}/README.md").read).to include("[USERNAME]")
+ expect(bundled_app("#{gem_name}/README.md").read).not_to include("github.com/bundleuser")
+ end
+ end
+ end
+
+ shared_examples_for "generating a gem" do
+ it "generates a gem skeleton" do
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to exist
+ expect(bundled_app("#{gem_name}/Gemfile")).to exist
+ expect(bundled_app("#{gem_name}/Rakefile")).to exist
+ expect(bundled_app("#{gem_name}/lib/#{require_path}.rb")).to exist
+ expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb")).to exist
+ expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs")).to exist
+ expect(bundled_app("#{gem_name}/.gitignore")).to exist
+
+ expect(bundled_app("#{gem_name}/bin/setup")).to exist
+ expect(bundled_app("#{gem_name}/bin/console")).to exist
+ expect(bundled_app("#{gem_name}/bin/setup")).to be_executable
+ expect(bundled_app("#{gem_name}/bin/console")).to be_executable
+ expect(bundled_app("#{gem_name}/bin/setup").read).to start_with("#!")
+ expect(bundled_app("#{gem_name}/bin/console").read).to start_with("#!")
+ end
+
+ it "starts with version 0.1.0" do
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb").read).to match(/VERSION = "0.1.0"/)
+ end
+
+ it "declare String type for VERSION constant" do
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs").read).to match(/VERSION: String/)
+ end
+
+ context "git config user.{name,email} is set" do
+ before do
+ bundle "gem #{gem_name}"
+ end
+
+ it "sets gemspec author to git user.name if available" do
+ expect(generated_gemspec.authors.first).to eq("Bundler User")
+ end
+
+ it "sets gemspec email to git user.email if available" do
+ expect(generated_gemspec.email.first).to eq("user@example.com")
+ end
+ end
+
+ context "git config user.{name,email} is not set" do
+ before do
+ sys_exec("git config --global --unset user.name")
+ sys_exec("git config --global --unset user.email")
+ bundle "gem #{gem_name}"
+ end
+
+ it "sets gemspec author to default message if git user.name is not set or empty" do
+ expect(generated_gemspec.authors.first).to eq("TODO: Write your name")
+ end
+
+ it "sets gemspec email to default message if git user.email is not set or empty" do
+ expect(generated_gemspec.email.first).to eq("TODO: Write your email address")
+ end
+ end
+
+ it "sets gemspec metadata['allowed_push_host']" do
+ bundle "gem #{gem_name}"
+
+ expect(generated_gemspec.metadata["allowed_push_host"]).
+ to match(/example\.com/)
+ end
+
+ it "sets a minimum ruby version" do
+ bundle "gem #{gem_name}"
+
+ expect(generated_gemspec.required_ruby_version.to_s).to start_with(">=")
+ end
+
+ it "requires the version file" do
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(%r{require_relative "#{require_relative_path}/version"})
+ end
+
+ it "creates a base error class" do
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/class Error < StandardError; end$/)
+ end
+
+ it "does not include the gemspec file in files" do
+ bundle "gem #{gem_name}"
+
+ bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec
+
+ expect(bundler_gemspec.files).not_to include("#{gem_name}.gemspec")
+ end
+
+ it "does not include the Gemfile file in files" do
+ bundle "gem #{gem_name}"
+
+ bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec
+
+ expect(bundler_gemspec.files).not_to include("Gemfile")
+ end
+
+ it "runs rake without problems" do
+ bundle "gem #{gem_name}"
+
+ system_gems ["rake-13.0.1"]
+
+ rakefile = strip_whitespace <<-RAKEFILE
+ task :default do
+ puts 'SUCCESS'
+ end
+ RAKEFILE
+ File.open(bundled_app("#{gem_name}/Rakefile"), "w") do |file|
+ file.puts rakefile
+ end
+
+ sys_exec(rake, :dir => bundled_app(gem_name))
+ expect(out).to include("SUCCESS")
+ end
+
+ context "--exe parameter set" do
+ before do
+ bundle "gem #{gem_name} --exe"
+ end
+
+ it "builds exe skeleton" do
+ expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist
+ end
+
+ it "requires the main file" do
+ expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/)
+ end
+ end
+
+ context "--bin parameter set" do
+ before do
+ bundle "gem #{gem_name} --bin"
+ end
+
+ it "builds exe skeleton" do
+ expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist
+ end
+
+ it "requires the main file" do
+ expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/)
+ end
+ end
+
+ context "no --test parameter" do
+ before do
+ bundle "gem #{gem_name}"
+ end
+
+ it_behaves_like "test framework is absent"
+ end
+
+ context "--test parameter set to rspec" do
+ before do
+ bundle "gem #{gem_name} --test=rspec"
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("#{gem_name}/.rspec")).to exist
+ expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist
+ end
+
+ it "depends on a specific version of rspec in generated Gemfile" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ builder = Bundler::Dsl.new
+ builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile"))
+ builder.dependencies
+ rspec_dep = builder.dependencies.find {|d| d.name == "rspec" }
+ expect(rspec_dep).to be_specific
+ end
+
+ it "requires the main file" do
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb").read).to include(%(require "#{require_path}"))
+ end
+
+ it "creates a default test which fails" do
+ expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb").read).to include("expect(false).to eq(true)")
+ end
+ end
+
+ context "gem.test setting set to rspec" do
+ before do
+ bundle "config set gem.test rspec"
+ bundle "gem #{gem_name}"
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("#{gem_name}/.rspec")).to exist
+ expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist
+ end
+ end
+
+ context "gem.test setting set to rspec and --test is set to minitest" do
+ before do
+ bundle "config set gem.test rspec"
+ bundle "gem #{gem_name} --test=minitest"
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist
+ expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist
+ end
+ end
+
+ context "--test parameter set to minitest" do
+ before do
+ bundle "gem #{gem_name} --test=minitest"
+ end
+
+ it "depends on a specific version of minitest" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ builder = Bundler::Dsl.new
+ builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile"))
+ builder.dependencies
+ minitest_dep = builder.dependencies.find {|d| d.name == "minitest" }
+ expect(minitest_dep).to be_specific
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist
+ expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist
+ end
+
+ it "requires the main file" do
+ expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}"))
+ end
+
+ it "requires 'test_helper'" do
+ expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(%(require "test_helper"))
+ end
+
+ it "defines valid test class name" do
+ expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(minitest_test_class_name)
+ end
+
+ it "creates a default test which fails" do
+ expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include("assert false")
+ end
+ end
+
+ context "gem.test setting set to minitest" do
+ before do
+ bundle "config set gem.test minitest"
+ bundle "gem #{gem_name}"
+ end
+
+ it "creates a default rake task to run the test suite" do
+ rakefile = strip_whitespace <<-RAKEFILE
+ # frozen_string_literal: true
+
+ require "bundler/gem_tasks"
+ require "rake/testtask"
+
+ Rake::TestTask.new(:test) do |t|
+ t.libs << "test"
+ t.libs << "lib"
+ t.test_files = FileList["test/**/test_*.rb"]
+ end
+
+ task default: :test
+ RAKEFILE
+
+ expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile)
+ end
+ end
+
+ context "--test parameter set to test-unit" do
+ before do
+ bundle "gem #{gem_name} --test=test-unit"
+ end
+
+ it "depends on a specific version of test-unit" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ builder = Bundler::Dsl.new
+ builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile"))
+ builder.dependencies
+ test_unit_dep = builder.dependencies.find {|d| d.name == "test-unit" }
+ expect(test_unit_dep).to be_specific
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb")).to exist
+ expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist
+ end
+
+ it "requires the main file" do
+ expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}"))
+ end
+
+ it "requires 'test_helper'" do
+ expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include(%(require "test_helper"))
+ end
+
+ it "creates a default test which fails" do
+ expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include("assert_equal(\"expected\", \"actual\")")
+ end
+ end
+
+ context "gem.test setting set to test-unit" do
+ before do
+ bundle "config set gem.test test-unit"
+ bundle "gem #{gem_name}"
+ end
+
+ it "creates a default rake task to run the test suite" do
+ rakefile = strip_whitespace <<-RAKEFILE
+ # frozen_string_literal: true
+
+ require "bundler/gem_tasks"
+ require "rake/testtask"
+
+ Rake::TestTask.new(:test) do |t|
+ t.libs << "test"
+ t.libs << "lib"
+ t.test_files = FileList["test/**/*_test.rb"]
+ end
+
+ task default: :test
+ RAKEFILE
+
+ expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile)
+ end
+ end
+
+ context "gem.test set to rspec and --test with no arguments", :hint_text do
+ before do
+ bundle "config set gem.test rspec"
+ bundle "gem #{gem_name} --test"
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("#{gem_name}/.rspec")).to exist
+ expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist
+ expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist
+ end
+
+ it "hints that --test is already configured" do
+ expect(out).to match("rspec is already configured, ignoring --test flag.")
+ end
+ end
+
+ context "gem.test setting set to false and --test with no arguments", :hint_text do
+ before do
+ bundle "config set gem.test false"
+ bundle "gem #{gem_name} --test"
+ end
+
+ it "asks to generate test files" do
+ expect(out).to match("Do you want to generate tests with your gem?")
+ end
+
+ it "hints that the choice will only be applied to the current gem" do
+ expect(out).to match("Your choice will only be applied to this gem.")
+ end
+
+ it_behaves_like "test framework is absent"
+ end
+
+ context "gem.test setting not set and --test with no arguments", :hint_text do
+ before do
+ bundle "gem #{gem_name} --test"
+ end
+
+ it "asks to generate test files" do
+ expect(out).to match("Do you want to generate tests with your gem?")
+ end
+
+ it "hints that the choice will be applied to future bundle gem calls" do
+ hint = "Future `bundle gem` calls will use your choice. " \
+ "This setting can be changed anytime with `bundle config gem.test`."
+ expect(out).to match(hint)
+ end
+
+ it_behaves_like "test framework is absent"
+ end
+
+ context "--ci with no argument" do
+ it "does not generate any CI config" do
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist
+ end
+ end
+
+ context "--ci set to travis" do
+ it "generates a GitHub Actions config file" do
+ bundle "gem #{gem_name} --ci=travis", :raise_on_error => false
+ expect(err).to include("Support for Travis CI was removed from gem skeleton generator.")
+
+ expect(bundled_app("#{gem_name}/.travis.yml")).to_not exist
+ end
+ end
+
+ context "--ci set to travis" do
+ it "generates a GitHub Actions config file" do
+ bundle "gem #{gem_name} --ci=travis", :raise_on_error => false
+ expect(err).to include("Support for Travis CI was removed from gem skeleton generator.")
+
+ expect(bundled_app("#{gem_name}/.travis.yml")).to_not exist
+ end
+ end
+
+ context "--ci set to github" do
+ it "generates a GitHub Actions config file" do
+ bundle "gem #{gem_name} --ci=github"
+
+ expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist
+ end
+ end
+
+ context "--ci set to gitlab" do
+ it "generates a GitLab CI config file" do
+ bundle "gem #{gem_name} --ci=gitlab"
+
+ expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist
+ end
+ end
+
+ context "--ci set to circle" do
+ it "generates a CircleCI config file" do
+ bundle "gem #{gem_name} --ci=circle"
+
+ expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist
+ end
+ end
+
+ context "gem.ci setting set to none" do
+ it "doesn't generate any CI config" do
+ expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist
+ end
+ end
+
+ context "gem.ci setting set to travis" do
+ it "errors with friendly message" do
+ bundle "config set gem.ci travis"
+ bundle "gem #{gem_name}", :raise_on_error => false
+
+ expect(err).to include("Support for Travis CI was removed from gem skeleton generator,")
+ expect(err).to include("bundle config unset gem.ci")
+
+ expect(bundled_app("#{gem_name}/.travis.yml")).to_not exist
+ end
+ end
+
+ context "gem.ci setting set to github" do
+ it "generates a GitHub Actions config file" do
+ bundle "config set gem.ci github"
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist
+ end
+ end
+
+ context "gem.ci setting set to gitlab" do
+ it "generates a GitLab CI config file" do
+ bundle "config set gem.ci gitlab"
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist
+ end
+ end
+
+ context "gem.ci setting set to circle" do
+ it "generates a CircleCI config file" do
+ bundle "config set gem.ci circle"
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist
+ end
+ end
+
+ context "gem.ci set to github and --ci with no arguments", :hint_text do
+ before do
+ bundle "config set gem.ci github"
+ bundle "gem #{gem_name} --ci"
+ end
+
+ it "generates a GitHub Actions config file" do
+ expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist
+ end
+
+ it "hints that --ci is already configured" do
+ expect(out).to match("github is already configured, ignoring --ci flag.")
+ end
+ end
+
+ context "gem.ci setting set to false and --ci with no arguments", :hint_text do
+ before do
+ bundle "config set gem.ci false"
+ bundle "gem #{gem_name} --ci"
+ end
+
+ it "asks to setup CI" do
+ expect(out).to match("Do you want to set up continuous integration for your gem?")
+ end
+
+ it "hints that the choice will only be applied to the current gem" do
+ expect(out).to match("Your choice will only be applied to this gem.")
+ end
+ end
+
+ context "gem.ci setting not set and --ci with no arguments", :hint_text do
+ before do
+ bundle "gem #{gem_name} --ci"
+ end
+
+ it "asks to setup CI" do
+ expect(out).to match("Do you want to set up continuous integration for your gem?")
+ end
+
+ it "hints that the choice will be applied to future bundle gem calls" do
+ hint = "Future `bundle gem` calls will use your choice. " \
+ "This setting can be changed anytime with `bundle config gem.ci`."
+ expect(out).to match(hint)
+ end
+ end
+
+ context "--linter with no argument" do
+ it "does not generate any linter config" do
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist
+ end
+ end
+
+ context "--linter set to rubocop" do
+ it "generates a RuboCop config" do
+ bundle "gem #{gem_name} --linter=rubocop"
+
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist
+ expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist
+ end
+ end
+
+ context "--linter set to standard" do
+ it "generates a Standard config" do
+ bundle "gem #{gem_name} --linter=standard"
+
+ expect(bundled_app("#{gem_name}/.standard.yml")).to exist
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist
+ end
+ end
+
+ context "gem.linter setting set to none" do
+ it "doesn't generate any linter config" do
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist
+ expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist
+ end
+ end
+
+ context "gem.linter setting set to rubocop" do
+ it "generates a RuboCop config file" do
+ bundle "config set gem.linter rubocop"
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist
+ end
+ end
+
+ context "gem.linter setting set to standard" do
+ it "generates a Standard config file" do
+ bundle "config set gem.linter standard"
+ bundle "gem #{gem_name}"
+
+ expect(bundled_app("#{gem_name}/.standard.yml")).to exist
+ end
+ end
+
+ context "gem.rubocop setting set to true", :bundler => "< 3" do
+ before do
+ bundle "config set gem.rubocop true"
+ bundle "gem #{gem_name}"
+ end
+
+ it "generates rubocop config" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist
+ end
+
+ it "unsets gem.rubocop" do
+ bundle "config gem.rubocop"
+ expect(out).to include("You have not configured a value for `gem.rubocop`")
+ end
+
+ it "sets gem.linter=rubocop instead" do
+ bundle "config gem.linter"
+ expect(out).to match(/Set for the current user .*: "rubocop"/)
+ end
+ end
+
+ context "gem.linter set to rubocop and --linter with no arguments", :hint_text do
+ before do
+ bundle "config set gem.linter rubocop"
+ bundle "gem #{gem_name} --linter"
+ end
+
+ it "generates a RuboCop config file" do
+ expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist
+ end
+
+ it "hints that --linter is already configured" do
+ expect(out).to match("rubocop is already configured, ignoring --linter flag.")
+ end
+ end
+
+ context "gem.linter setting set to false and --linter with no arguments", :hint_text do
+ before do
+ bundle "config set gem.linter false"
+ bundle "gem #{gem_name} --linter"
+ end
+
+ it "asks to setup a linter" do
+ expect(out).to match("Do you want to add a code linter and formatter to your gem?")
+ end
+
+ it "hints that the choice will only be applied to the current gem" do
+ expect(out).to match("Your choice will only be applied to this gem.")
+ end
+ end
+
+ context "gem.linter setting not set and --linter with no arguments", :hint_text do
+ before do
+ bundle "gem #{gem_name} --linter"
+ end
+
+ it "asks to setup a linter" do
+ expect(out).to match("Do you want to add a code linter and formatter to your gem?")
+ end
+
+ it "hints that the choice will be applied to future bundle gem calls" do
+ hint = "Future `bundle gem` calls will use your choice. " \
+ "This setting can be changed anytime with `bundle config gem.linter`."
+ expect(out).to match(hint)
+ end
+ end
+
+ context "--edit option" do
+ it "opens the generated gemspec in the user's text editor" do
+ output = bundle "gem #{gem_name} --edit=echo"
+ gemspec_path = File.join(bundled_app, gem_name, "#{gem_name}.gemspec")
+ expect(output).to include("echo \"#{gemspec_path}\"")
+ end
+ end
+ end
+
+ context "testing --mit and --coc options against bundle config settings", :readline do
+ let(:gem_name) { "test-gem" }
+
+ let(:require_path) { "test/gem" }
+
+ context "with mit option in bundle config settings set to true" do
+ before do
+ global_config "BUNDLE_GEM__MIT" => "true"
+ end
+ it_behaves_like "--mit flag"
+ it_behaves_like "--no-mit flag"
+ end
+
+ context "with mit option in bundle config settings set to false" do
+ before do
+ global_config "BUNDLE_GEM__MIT" => "false"
+ end
+ it_behaves_like "--mit flag"
+ it_behaves_like "--no-mit flag"
+ end
+
+ context "with coc option in bundle config settings set to true" do
+ before do
+ global_config "BUNDLE_GEM__COC" => "true"
+ end
+ it_behaves_like "--coc flag"
+ it_behaves_like "--no-coc flag"
+ end
+
+ context "with coc option in bundle config settings set to false" do
+ before do
+ global_config "BUNDLE_GEM__COC" => "false"
+ end
+ it_behaves_like "--coc flag"
+ it_behaves_like "--no-coc flag"
+ end
+
+ context "with rubocop option in bundle config settings set to true" do
+ before do
+ global_config "BUNDLE_GEM__RUBOCOP" => "true"
+ end
+ it_behaves_like "--linter=rubocop flag"
+ it_behaves_like "--linter=standard flag"
+ it_behaves_like "--linter=none flag"
+ it_behaves_like "--rubocop flag"
+ it_behaves_like "--no-rubocop flag"
+ end
+
+ context "with rubocop option in bundle config settings set to false" do
+ before do
+ global_config "BUNDLE_GEM__RUBOCOP" => "false"
+ end
+ it_behaves_like "--linter=rubocop flag"
+ it_behaves_like "--linter=standard flag"
+ it_behaves_like "--linter=none flag"
+ it_behaves_like "--rubocop flag"
+ it_behaves_like "--no-rubocop flag"
+ end
+
+ context "with linter option in bundle config settings set to rubocop" do
+ before do
+ global_config "BUNDLE_GEM__LINTER" => "rubocop"
+ end
+ it_behaves_like "--linter=rubocop flag"
+ it_behaves_like "--linter=standard flag"
+ it_behaves_like "--linter=none flag"
+ end
+
+ context "with linter option in bundle config settings set to standard" do
+ before do
+ global_config "BUNDLE_GEM__LINTER" => "standard"
+ end
+ it_behaves_like "--linter=rubocop flag"
+ it_behaves_like "--linter=standard flag"
+ it_behaves_like "--linter=none flag"
+ end
+
+ context "with linter option in bundle config settings set to false" do
+ before do
+ global_config "BUNDLE_GEM__LINTER" => "false"
+ end
+ it_behaves_like "--linter=rubocop flag"
+ it_behaves_like "--linter=standard flag"
+ it_behaves_like "--linter=none flag"
+ end
+
+ context "with changelog option in bundle config settings set to true" do
+ before do
+ global_config "BUNDLE_GEM__CHANGELOG" => "true"
+ end
+ it_behaves_like "--changelog flag"
+ it_behaves_like "--no-changelog flag"
+ end
+
+ context "with changelog option in bundle config settings set to false" do
+ before do
+ global_config "BUNDLE_GEM__CHANGELOG" => "false"
+ end
+ it_behaves_like "--changelog flag"
+ it_behaves_like "--no-changelog flag"
+ end
+ end
+
+ context "testing --github-username option against git and bundle config settings", :readline do
+ context "without git config set" do
+ before do
+ sys_exec("git config --global --unset github.user")
+ end
+ context "with github-username option in bundle config settings set to some value" do
+ before do
+ global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username"
+ end
+ it_behaves_like "--github-username option", "gh_user"
+ end
+
+ context "with github-username option in bundle config settings set to false" do
+ before do
+ global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false"
+ end
+ it_behaves_like "--github-username option", "gh_user"
+ end
+ end
+
+ context "with git config set" do
+ context "with github-username option in bundle config settings set to some value" do
+ before do
+ global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username"
+ end
+ it_behaves_like "--github-username option", "gh_user"
+ end
+
+ context "with github-username option in bundle config settings set to false" do
+ before do
+ global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false"
+ end
+ it_behaves_like "--github-username option", "gh_user"
+ end
+ end
+ end
+
+ context "testing github_username bundle config against git config settings", :readline do
+ context "without git config set" do
+ before do
+ sys_exec("git config --global --unset github.user")
+ end
+
+ it_behaves_like "github_username configuration"
+ end
+
+ context "with git config set" do
+ it_behaves_like "github_username configuration"
+ end
+ end
+
+ context "gem naming with underscore", :readline do
+ let(:gem_name) { "test_gem" }
+
+ let(:require_path) { "test_gem" }
+
+ let(:require_relative_path) { "test_gem" }
+
+ let(:minitest_test_file_path) { "test/test_test_gem.rb" }
+
+ let(:minitest_test_class_name) { "class TestTestGem < Minitest::Test" }
+
+ let(:flags) { nil }
+
+ it "does not nest constants" do
+ bundle ["gem", gem_name, flags].compact.join(" ")
+ expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb").read).to match(/module TestGem/)
+ expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/module TestGem/)
+ end
+
+ include_examples "generating a gem"
+
+ context "--ext parameter with no value" do
+ context "is deprecated", :bundler => "< 3" do
+ it "prints deprecation when used after gem name" do
+ bundle ["gem", "--ext", gem_name].compact.join(" ")
+ expect(err).to include "[DEPRECATED]"
+ expect(err).to include "`--ext` with no arguments has been deprecated"
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist
+ end
+
+ it "prints deprecation when used before gem name" do
+ bundle ["gem", gem_name, "--ext"].compact.join(" ")
+ expect(err).to include "[DEPRECATED]"
+ expect(err).to include "`--ext` with no arguments has been deprecated"
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist
+ end
+ end
+ end
+
+ context "--ext parameter set with C" do
+ let(:flags) { "--ext=c" }
+
+ before do
+ bundle ["gem", gem_name, flags].compact.join(" ")
+ end
+
+ it "is not deprecated" do
+ expect(err).not_to include "[DEPRECATED] Option `--ext` without explicit value is deprecated."
+ end
+
+ it "builds ext skeleton" do
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.h")).to exist
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist
+ end
+
+ it "includes rake-compiler, but no Rust related changes" do
+ expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"')
+
+ expect(bundled_app("#{gem_name}/Gemfile").read).to_not include('gem "rb_sys"')
+ expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to_not include('spec.required_rubygems_version = ">= ')
+ end
+
+ it "depends on compile task for build" do
+ rakefile = strip_whitespace <<-RAKEFILE
+ # frozen_string_literal: true
+
+ require "bundler/gem_tasks"
+ require "rake/extensiontask"
+
+ task build: :compile
+
+ Rake::ExtensionTask.new("#{gem_name}") do |ext|
+ ext.lib_dir = "lib/#{gem_name}"
+ end
+
+ task default: %i[clobber compile]
+ RAKEFILE
+
+ expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile)
+ end
+ end
+
+ context "--ext parameter set with rust and old RubyGems" do
+ it "fails in friendly way" do
+ if ::Gem::Version.new("3.3.11") <= ::Gem.rubygems_version
+ skip "RubyGems compatible with Rust builder"
+ end
+
+ expect do
+ bundle ["gem", gem_name, "--ext=rust"].compact.join(" ")
+ end.to raise_error(RuntimeError, /too old to build Rust extension/)
+ end
+ end
+
+ context "--ext parameter set with rust" do
+ let(:flags) { "--ext=rust" }
+
+ before do
+ skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version
+
+ bundle ["gem", gem_name, flags].compact.join(" ")
+ end
+
+ it "is not deprecated" do
+ expect(err).not_to include "[DEPRECATED] Option `--ext` without explicit value is deprecated."
+ end
+
+ it "builds ext skeleton" do
+ expect(bundled_app("#{gem_name}/Cargo.toml")).to exist
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/Cargo.toml")).to exist
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist
+ expect(bundled_app("#{gem_name}/ext/#{gem_name}/src/lib.rs")).to exist
+ end
+
+ it "includes rake-compiler, rb_sys gems and required_rubygems_version constraint" do
+ expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"')
+ expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rb_sys"')
+ expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.required_rubygems_version = ">= ')
+ end
+
+ it "depends on compile task for build" do
+ rakefile = strip_whitespace <<-RAKEFILE
+ # frozen_string_literal: true
+
+ require "bundler/gem_tasks"
+ require "rb_sys/extensiontask"
+
+ task build: :compile
+
+ RbSys::ExtensionTask.new("#{gem_name}") do |ext|
+ ext.lib_dir = "lib/#{gem_name}"
+ end
+
+ task default: :compile
+ RAKEFILE
+
+ expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile)
+ end
+ end
+ end
+
+ context "gem naming with dashed", :readline do
+ let(:gem_name) { "test-gem" }
+
+ let(:require_path) { "test/gem" }
+
+ let(:require_relative_path) { "gem" }
+
+ let(:minitest_test_file_path) { "test/test/test_gem.rb" }
+
+ let(:minitest_test_class_name) { "class Test::TestGem < Minitest::Test" }
+
+ it "nests constants so they work" do
+ bundle "gem #{gem_name}"
+ expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb").read).to match(/module Test\n module Gem/)
+ expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/module Test\n module Gem/)
+ end
+
+ include_examples "generating a gem"
+ end
+
+ describe "uncommon gem names" do
+ it "can deal with two dashes", :readline do
+ bundle "gem a--a"
+
+ expect(bundled_app("a--a/a--a.gemspec")).to exist
+ end
+
+ it "fails gracefully with a ." do
+ bundle "gem foo.gemspec", :raise_on_error => false
+ expect(err).to end_with("Invalid gem name foo.gemspec -- `Foo.gemspec` is an invalid constant name")
+ end
+
+ it "fails gracefully with a ^" do
+ bundle "gem ^", :raise_on_error => false
+ expect(err).to end_with("Invalid gem name ^ -- `^` is an invalid constant name")
+ end
+
+ it "fails gracefully with a space" do
+ bundle "gem 'foo bar'", :raise_on_error => false
+ expect(err).to end_with("Invalid gem name foo bar -- `Foo bar` is an invalid constant name")
+ end
+
+ it "fails gracefully when multiple names are passed" do
+ bundle "gem foo bar baz", :raise_on_error => false
+ expect(err).to eq(<<-E.strip)
+ERROR: "bundle gem" was called with arguments ["foo", "bar", "baz"]
+Usage: "bundle gem NAME [OPTIONS]"
+ E
+ end
+ end
+
+ describe "#ensure_safe_gem_name", :readline do
+ before do
+ bundle "gem #{subject}", :raise_on_error => false
+ end
+
+ context "with an existing const name" do
+ subject { "gem" }
+ it { expect(err).to include("Invalid gem name #{subject}") }
+ end
+
+ context "with an existing hyphenated const name" do
+ subject { "gem-specification" }
+ it { expect(err).to include("Invalid gem name #{subject}") }
+ end
+
+ context "starting with an existing const name" do
+ subject { "gem-somenewconstantname" }
+ it { expect(err).not_to include("Invalid gem name #{subject}") }
+ end
+
+ context "ending with an existing const name" do
+ subject { "somenewconstantname-gem" }
+ it { expect(err).not_to include("Invalid gem name #{subject}") }
+ end
+ end
+
+ context "on first run", :readline do
+ it "asks about test framework" do
+ global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__COC" => "false"
+
+ bundle "gem foobar" do |input, _, _|
+ input.puts "rspec"
+ end
+
+ expect(bundled_app("foobar/spec/spec_helper.rb")).to exist
+ rakefile = strip_whitespace <<-RAKEFILE
+ # frozen_string_literal: true
+
+ require "bundler/gem_tasks"
+ require "rspec/core/rake_task"
+
+ RSpec::Core::RakeTask.new(:spec)
+
+ task default: :spec
+ RAKEFILE
+
+ expect(bundled_app("foobar/Rakefile").read).to eq(rakefile)
+ expect(bundled_app("foobar/Gemfile").read).to include('gem "rspec"')
+ end
+
+ it "asks about CI service" do
+ global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__LINTER" => "false"
+
+ bundle "gem foobar" do |input, _, _|
+ input.puts "github"
+ end
+
+ expect(bundled_app("foobar/.github/workflows/main.yml")).to exist
+ end
+
+ it "asks about MIT license" do
+ global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__LINTER" => "false"
+
+ bundle "config list"
+
+ bundle "gem foobar" do |input, _, _|
+ input.puts "yes"
+ end
+
+ expect(bundled_app("foobar/LICENSE.txt")).to exist
+ end
+
+ it "asks about CoC" do
+ global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__LINTER" => "false"
+
+ bundle "gem foobar" do |input, _, _|
+ input.puts "yes"
+ end
+
+ expect(bundled_app("foobar/CODE_OF_CONDUCT.md")).to exist
+ end
+
+ it "asks about CHANGELOG" do
+ global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__LINTER" => "false",
+ "BUNDLE_GEM__COC" => "false"
+
+ bundle "gem foobar" do |input, _, _|
+ input.puts "yes"
+ end
+
+ expect(bundled_app("foobar/CHANGELOG.md")).to exist
+ end
+ end
+
+ context "on conflicts with a previously created file", :readline do
+ it "should fail gracefully" do
+ FileUtils.touch(bundled_app("conflict-foobar"))
+ bundle "gem conflict-foobar", :raise_on_error => false
+ expect(err).to eq("Couldn't create a new gem named `conflict-foobar` because there's an existing file named `conflict-foobar`.")
+ expect(exitstatus).to eql(32)
+ end
+ end
+
+ context "on conflicts with a previously created directory", :readline do
+ it "should succeed" do
+ FileUtils.mkdir_p(bundled_app("conflict-foobar/Gemfile"))
+ bundle "gem conflict-foobar"
+ expect(out).to include("file_clash conflict-foobar/Gemfile").
+ and include "Initializing git repo in #{bundled_app("conflict-foobar")}"
+ end
+ end
+end
diff --git a/spec/bundler/commands/open_spec.rb b/spec/bundler/commands/open_spec.rb
new file mode 100644
index 0000000000..e30ebfea5b
--- /dev/null
+++ b/spec/bundler/commands/open_spec.rb
@@ -0,0 +1,176 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle open" do
+ context "when opening a regular gem" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+ end
+
+ it "opens the gem with BUNDLER_EDITOR as highest priority" do
+ bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+ expect(out).to include("bundler_editor #{default_bundle_path("gems", "rails-2.3.2")}")
+ end
+
+ it "opens the gem with VISUAL as 2nd highest priority" do
+ bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("visual #{default_bundle_path("gems", "rails-2.3.2")}")
+ end
+
+ it "opens the gem with EDITOR as 3rd highest priority" do
+ bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("editor #{default_bundle_path("gems", "rails-2.3.2")}")
+ end
+
+ it "complains if no EDITOR is set" do
+ bundle "open rails", :env => { "EDITOR" => "", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to eq("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR")
+ end
+
+ it "complains if gem not in bundle" do
+ bundle "open missing", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false
+ expect(err).to match(/could not find gem 'missing'/i)
+ end
+
+ it "does not blow up if the gem to open does not have a Gemfile" do
+ git = build_git "foo"
+ ref = git.ref_for("main", 11)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "open foo", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to match("editor #{default_bundle_path.join("bundler/gems/foo-1.0-#{ref}")}")
+ end
+
+ it "suggests alternatives for similar-sounding gems" do
+ bundle "open Rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false
+ expect(err).to match(/did you mean rails\?/i)
+ end
+
+ it "opens the gem with short words" do
+ bundle "open rec", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+
+ expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2")}")
+ end
+
+ it "opens subpath of the gem" do
+ bundle "open activerecord --path lib/activerecord", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/activerecord")
+ end
+
+ it "opens subpath file of the gem" do
+ bundle "open activerecord --path lib/version.rb", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/version.rb")
+ end
+
+ it "opens deep subpath of the gem" do
+ bundle "open activerecord --path lib/active_record", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/active_record")
+ end
+
+ it "requires value for --path arg" do
+ bundle "open activerecord --path", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false
+ expect(err).to eq "Cannot specify `--path` option without a value"
+ end
+
+ it "suggests alternatives for similar-sounding gems when using subpath" do
+ bundle "open Rails --path README.md", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false
+ expect(err).to match(/did you mean rails\?/i)
+ end
+
+ it "suggests alternatives for similar-sounding gems when using deep subpath" do
+ bundle "open Rails --path some/path/here", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false
+ expect(err).to match(/did you mean rails\?/i)
+ end
+
+ it "opens subpath of the short worded gem" do
+ bundle "open rec --path CHANGELOG.md", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/CHANGELOG.md")
+ end
+
+ it "opens deep subpath of the short worded gem" do
+ bundle "open rec --path lib/activerecord", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/activerecord")
+ end
+
+ it "opens subpath of the selected matching gem", :readline do
+ env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+ bundle "open active --path CHANGELOG.md", :env => env do |input, _, _|
+ input.puts "2"
+ end
+
+ expect(out).to match(%r{bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}/CHANGELOG\.md\z})
+ end
+
+ it "opens deep subpath of the selected matching gem", :readline do
+ env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+ bundle "open active --path lib/activerecord/version.rb", :env => env do |input, _, _|
+ input.puts "2"
+ end
+
+ expect(out).to match(%r{bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}/lib/activerecord/version\.rb\z})
+ end
+
+ it "select the gem from many match gems", :readline do
+ env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+ bundle "open active", :env => env do |input, _, _|
+ input.puts "2"
+ end
+
+ expect(out).to match(/bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}\z/)
+ end
+
+ it "allows selecting exit from many match gems", :readline do
+ env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+ bundle "open active", :env => env do |input, _, _|
+ input.puts "0"
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ gem "foo"
+ G
+
+ bundle "config set auto_install 1"
+ bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("Installing foo 1.0")
+ end
+
+ it "opens the editor with a clean env" do
+ bundle "open", :env => { "EDITOR" => "sh -c 'env'", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false
+ expect(out).not_to include("BUNDLE_GEMFILE=")
+ end
+ end
+
+ context "when opening a default gem" do
+ let(:default_gems) do
+ ruby(<<-RUBY).split("\n")
+ if Gem::Specification.is_a?(Enumerable)
+ puts Gem::Specification.select(&:default_gem?).map(&:name)
+ end
+ RUBY
+ end
+
+ before do
+ skip "No default gems available on this test run" if default_gems.empty?
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "json"
+ G
+ end
+
+ it "throws proper error when trying to open default gem" do
+ bundle "open json", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+ expect(out).to include("Unable to open json because it's a default gem, so the directory it would normally be installed to does not exist.")
+ end
+ end
+end
diff --git a/spec/bundler/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb
new file mode 100644
index 0000000000..bbc3cb54ae
--- /dev/null
+++ b/spec/bundler/commands/outdated_spec.rb
@@ -0,0 +1,1368 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle outdated" do
+ describe "with no arguments" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+ end
+
+ it "returns a sorted list of outdated gems" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "weakling", "0.2"
+ update_git "foo", :path => lib_path("foo")
+ update_git "zebra", :path => lib_path("zebra")
+ end
+
+ bundle "outdated", :raise_on_error => false
+
+ expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip
+ Gem Current Latest Requested Groups
+ activesupport 2.3.5 3.0 = 2.3.5 default
+ foo 1.0 xxxxxxx 1.0 xxxxxxx >= 0 default
+ weakling 0.0.3 0.2 ~> 0.0.1 default
+ zebra 1.0 xxxxxxx 1.0 xxxxxxx >= 0 default
+ TABLE
+
+ expect(out).to match(Regexp.new(expected_output))
+ end
+
+ it "excludes header row from the sorting" do
+ update_repo2 do
+ build_gem "AAA", %w[1.0.0 2.0.0]
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "AAA", "1.0.0"
+ G
+
+ bundle "outdated", :raise_on_error => false
+
+ expected_output = <<~TABLE
+ Gem Current Latest Requested Groups
+ AAA 1.0.0 2.0.0 = 1.0.0 default
+ TABLE
+
+ expect(out).to include(expected_output.strip)
+ end
+
+ it "returns non zero exit status if outdated gems present" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ bundle "outdated", :raise_on_error => false
+
+ expect(exitstatus).to_not be_zero
+ end
+
+ it "returns success exit status if no outdated gems present" do
+ bundle "outdated"
+ end
+
+ it "adds gem group to dependency output when repo is updated" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "terranova", '8'
+
+ group :development, :test do
+ gem 'activesupport', '2.3.5'
+ end
+ G
+
+ update_repo2 { build_gem "activesupport", "3.0" }
+ update_repo2 { build_gem "terranova", "9" }
+
+ bundle "outdated", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ activesupport 2.3.5 3.0 = 2.3.5 development, test
+ terranova 8 9 = 8 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+
+ describe "with --verbose option" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+ end
+
+ it "shows the location of the latest version's gemspec if installed" do
+ bundle "config set clean false"
+
+ update_repo2 { build_gem "activesupport", "3.0" }
+ update_repo2 { build_gem "terranova", "9" }
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "terranova", '9'
+ gem 'activesupport', '2.3.5'
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "terranova", '8'
+ gem 'activesupport', '2.3.5'
+ G
+
+ bundle "outdated --verbose", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Path
+ activesupport 2.3.5 3.0 = 2.3.5 default
+ terranova 8 9 = 8 default #{default_bundle_path("specifications/terranova-9.gemspec")}
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+
+ describe "with multiple, duplicated sources, with lockfile in old format", :bundler => "< 3" do
+ before do
+ build_repo2 do
+ build_gem "dotenv", "2.7.6"
+
+ build_gem "oj", "3.11.3"
+ build_gem "oj", "3.11.5"
+
+ build_gem "vcr", "6.0.0"
+ end
+
+ build_repo gem_repo3 do
+ build_gem "pkg-gem-flowbyte-with-dep", "1.0.0" do |s|
+ s.add_dependency "oj"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo2"
+
+ gem "dotenv"
+
+ source "https://gem.repo3" do
+ gem 'pkg-gem-flowbyte-with-dep'
+ end
+
+ gem "vcr",source: "https://gem.repo2"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo2/
+ remote: https://gem.repo3/
+ specs:
+ dotenv (2.7.6)
+ oj (3.11.3)
+ pkg-gem-flowbyte-with-dep (1.0.0)
+ oj
+ vcr (6.0.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ dotenv
+ pkg-gem-flowbyte-with-dep!
+ vcr!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "works" do
+ bundle :install, :artifice => "compact_index"
+ bundle :outdated, :artifice => "compact_index", :raise_on_error => false
+
+ expected_output = <<~TABLE
+ Gem Current Latest Requested Groups
+ oj 3.11.3 3.11.5
+ TABLE
+
+ expect(out).to include(expected_output.strip)
+ end
+ end
+
+ describe "with --group option" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem "duradura", '7.0'
+ gem 'activesupport', '2.3.5'
+ end
+ G
+ end
+
+ def test_group_option(group)
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "terranova", "9"
+ build_gem "duradura", "8.0"
+ end
+
+ bundle "outdated --group #{group}", :raise_on_error => false
+ end
+
+ it "works when the bundle is up to date" do
+ bundle "outdated --group"
+ expect(out).to end_with("Bundle up to date!")
+ end
+
+ it "returns a sorted list of outdated gems from one group => 'default'" do
+ test_group_option("default")
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ terranova 8 9 = 8 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+
+ it "returns a sorted list of outdated gems from one group => 'development'" do
+ test_group_option("development")
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ activesupport 2.3.5 3.0 = 2.3.5 development, test
+ duradura 7.0 8.0 = 7.0 development, test
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+
+ it "returns a sorted list of outdated gems from one group => 'test'" do
+ test_group_option("test")
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ activesupport 2.3.5 3.0 = 2.3.5 development, test
+ duradura 7.0 8.0 = 7.0 development, test
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+
+ describe "with --groups option and outdated transitive dependencies" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+
+ build_gem "bar", %w[2.0.0]
+
+ build_gem "bar_dependant", "7.0" do |s|
+ s.add_dependency "bar", "~> 2.0"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "bar_dependant", '7.0'
+ G
+
+ update_repo2 do
+ build_gem "bar", %w[3.0.0]
+ end
+ end
+
+ it "returns a sorted list of outdated gems" do
+ bundle "outdated --groups", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ bar 2.0.0 3.0.0
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+
+ describe "with --groups option" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem 'activesupport', '2.3.5'
+ gem "duradura", '7.0'
+ end
+ G
+ end
+
+ it "not outdated gems" do
+ bundle "outdated --groups"
+ expect(out).to end_with("Bundle up to date!")
+ end
+
+ it "returns a sorted list of outdated gems by groups" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "terranova", "9"
+ build_gem "duradura", "8.0"
+ end
+
+ bundle "outdated --groups", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ activesupport 2.3.5 3.0 = 2.3.5 development, test
+ duradura 7.0 8.0 = 7.0 development, test
+ terranova 8 9 = 8 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+
+ describe "with --local option" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem 'activesupport', '2.3.5'
+ gem "duradura", '7.0'
+ end
+ G
+ end
+
+ it "uses local cache to return a list of outdated gems" do
+ update_repo2 do
+ build_gem "activesupport", "2.3.4"
+ end
+
+ bundle "config set clean false"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "2.3.4"
+ G
+
+ bundle "outdated --local", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ activesupport 2.3.4 2.3.5 = 2.3.4 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+
+ it "doesn't hit repo2" do
+ FileUtils.rm_rf(gem_repo2)
+
+ bundle "outdated --local"
+ expect(out).not_to match(/Fetching (gem|version|dependency) metadata from/)
+ end
+ end
+
+ shared_examples_for "a minimal output is desired" do
+ context "and gems are outdated" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+
+ build_gem "activesupport", "3.0"
+ build_gem "weakling", "0.2"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+ end
+
+ it "outputs a sorted list of outdated gems with a more minimal format" do
+ minimal_output = "activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)\n" \
+ "weakling (newest 0.2, installed 0.0.3, requested ~> 0.0.1)"
+ subject
+ expect(out).to eq(minimal_output)
+ end
+ end
+
+ context "and no gems are outdated" do
+ it "has empty output" do
+ subject
+ expect(out).to be_empty
+ end
+ end
+ end
+
+ describe "with --parseable option" do
+ subject { bundle "outdated --parseable", :raise_on_error => false }
+
+ it_behaves_like "a minimal output is desired"
+ end
+
+ describe "with aliased --porcelain option" do
+ subject { bundle "outdated --porcelain", :raise_on_error => false }
+
+ it_behaves_like "a minimal output is desired"
+ end
+
+ describe "with specified gems" do
+ it "returns list of outdated gems" do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ bundle "outdated foo", :raise_on_error => false
+
+ expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip
+ Gem Current Latest Requested Groups
+ foo 1.0 xxxxxxx 1.0 xxxxxxx >= 0 default
+ TABLE
+
+ expect(out).to match(Regexp.new(expected_output))
+ end
+ end
+
+ describe "pre-release gems" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+ end
+
+ context "without the --pre option" do
+ it "ignores pre-release versions" do
+ update_repo2 do
+ build_gem "activesupport", "3.0.0.beta"
+ end
+
+ bundle "outdated"
+
+ expect(out).to end_with("Bundle up to date!")
+ end
+ end
+
+ context "with the --pre option" do
+ it "includes pre-release versions" do
+ update_repo2 do
+ build_gem "activesupport", "3.0.0.beta"
+ end
+
+ bundle "outdated --pre", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ activesupport 2.3.5 3.0.0.beta = 2.3.5 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+
+ context "when current gem is a pre-release" do
+ it "includes the gem" do
+ update_repo2 do
+ build_gem "activesupport", "3.0.0.beta.1"
+ build_gem "activesupport", "3.0.0.beta.2"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "3.0.0.beta.1"
+ G
+
+ bundle "outdated", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ activesupport 3.0.0.beta.1 3.0.0.beta.2 = 3.0.0.beta.1 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+ end
+
+ describe "with --filter-strict option" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+ end
+
+ it "only reports gems that have a newer version that matches the specified dependency version requirements" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "weakling", "0.0.5"
+ end
+
+ bundle :outdated, :"filter-strict" => true, :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ weakling 0.0.3 0.0.5 ~> 0.0.1 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+
+ it "only reports gems that have a newer version that matches the specified dependency version requirements, using --strict alias" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "weakling", "0.0.5"
+ end
+
+ bundle :outdated, :strict => true, :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ weakling 0.0.3 0.0.5 ~> 0.0.1 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+
+ it "doesn't crash when some deps unused on the current platform" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", platforms: [:ruby_22]
+ G
+
+ bundle :outdated, :"filter-strict" => true
+
+ expect(out).to end_with("Bundle up to date!")
+ end
+
+ it "only reports gem dependencies when they can actually be updated" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack_middleware", "1.0"
+ G
+
+ bundle :outdated, :"filter-strict" => true
+
+ expect(out).to end_with("Bundle up to date!")
+ end
+
+ describe "and filter options" do
+ it "only reports gems that match requirement and patch filter level" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "~> 2.3"
+ gem "weakling", ">= 0.0.1"
+ G
+
+ update_repo2 do
+ build_gem "activesupport", %w[2.4.0 3.0.0]
+ build_gem "weakling", "0.0.5"
+ end
+
+ bundle :outdated, :"filter-strict" => true, "filter-patch" => true, :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ weakling 0.0.3 0.0.5 >= 0.0.1 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+
+ it "only reports gems that match requirement and minor filter level" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "~> 2.3"
+ gem "weakling", ">= 0.0.1"
+ G
+
+ update_repo2 do
+ build_gem "activesupport", %w[2.3.9]
+ build_gem "weakling", "0.1.5"
+ end
+
+ bundle :outdated, :"filter-strict" => true, "filter-minor" => true, :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ weakling 0.0.3 0.1.5 >= 0.0.1 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+
+ it "only reports gems that match requirement and major filter level" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "~> 2.3"
+ gem "weakling", ">= 0.0.1"
+ G
+
+ update_repo2 do
+ build_gem "activesupport", %w[2.4.0 2.5.0]
+ build_gem "weakling", "1.1.5"
+ end
+
+ bundle :outdated, :"filter-strict" => true, "filter-major" => true, :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ weakling 0.0.3 1.1.5 >= 0.0.1 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+ end
+
+ describe "with invalid gem name" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+ end
+
+ it "returns could not find gem name" do
+ bundle "outdated invalid_gem_name", :raise_on_error => false
+ expect(err).to include("Could not find gem 'invalid_gem_name'.")
+ end
+
+ it "returns non-zero exit code" do
+ bundle "outdated invalid_gem_name", :raise_on_error => false
+ expect(exitstatus).to_not be_zero
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ gem "foo"
+ G
+
+ bundle "config set auto_install 1"
+ bundle :outdated, :raise_on_error => false
+ expect(out).to include("Installing foo 1.0")
+ end
+
+ context "after bundle install --deployment", :bundler => "< 3" do
+ before do
+ build_repo2
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rack"
+ gem "foo"
+ G
+ bundle :lock
+ bundle :install, :deployment => true
+ end
+
+ it "outputs a helpful message about being in deployment mode" do
+ update_repo2 { build_gem "activesupport", "3.0" }
+
+ bundle "outdated", :raise_on_error => false
+ expect(last_command).to be_failure
+ expect(err).to include("You are trying to check outdated gems in deployment mode.")
+ expect(err).to include("Run `bundle outdated` elsewhere.")
+ expect(err).to include("If this is a development machine, remove the ")
+ expect(err).to include("Gemfile freeze\nby running `bundle config unset deployment`.")
+ end
+ end
+
+ context "after bundle config set --local deployment true" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rack"
+ gem "foo"
+ G
+ bundle "config set --local deployment true"
+ end
+
+ it "outputs a helpful message about being in deployment mode" do
+ update_repo2 { build_gem "activesupport", "3.0" }
+
+ bundle "outdated", :raise_on_error => false
+ expect(last_command).to be_failure
+ expect(err).to include("You are trying to check outdated gems in deployment mode.")
+ expect(err).to include("Run `bundle outdated` elsewhere.")
+ expect(err).to include("If this is a development machine, remove the ")
+ expect(err).to include("Gemfile freeze\nby running `bundle config unset deployment`.")
+ end
+ end
+
+ context "update available for a gem on a different platform" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "laduradura", '= 5.15.2'
+ G
+ end
+
+ it "reports that no updates are available" do
+ bundle "outdated"
+ expect(out).to end_with("Bundle up to date!")
+ end
+ end
+
+ context "update available for a gem on the same platform while multiple platforms used for gem" do
+ before do
+ build_repo2
+ end
+
+ it "reports that updates are available if the Ruby platform is used" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby]
+ G
+
+ bundle "outdated"
+ expect(out).to end_with("Bundle up to date!")
+ end
+
+ it "reports that updates are available if the JRuby platform is used", :jruby_only do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby]
+ G
+
+ bundle "outdated", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ laduradura 5.15.2 5.15.3 = 5.15.2 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+
+ shared_examples_for "version update is detected" do
+ it "reports that a gem has a newer version" do
+ subject
+
+ outdated_gems = out.split("\n").drop_while {|l| !l.start_with?("Gem") }[1..-1]
+
+ expect(outdated_gems.size).to be > 0
+ end
+ end
+
+ shared_examples_for "major version updates are detected" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "3.3.5"
+ build_gem "weakling", "0.8.0"
+ end
+ end
+
+ it_behaves_like "version update is detected"
+ end
+
+ context "when on a new machine" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+
+ simulate_new_machine
+
+ update_git "foo", :path => lib_path("foo")
+ update_repo2 do
+ build_gem "activesupport", "3.3.5"
+ build_gem "weakling", "0.8.0"
+ end
+ end
+
+ subject { bundle "outdated", :raise_on_error => false }
+ it_behaves_like "version update is detected"
+ end
+
+ shared_examples_for "minor version updates are detected" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "2.7.5"
+ build_gem "weakling", "2.0.1"
+ end
+ end
+
+ it_behaves_like "version update is detected"
+ end
+
+ shared_examples_for "patch version updates are detected" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "2.3.7"
+ build_gem "weakling", "0.3.1"
+ end
+ end
+
+ it_behaves_like "version update is detected"
+ end
+
+ shared_examples_for "no version updates are detected" do
+ it "does not detect any version updates" do
+ subject
+ expect(out).to end_with("updates to display.")
+ end
+ end
+
+ shared_examples_for "major version is ignored" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "3.3.5"
+ build_gem "weakling", "1.0.1"
+ end
+ end
+
+ it_behaves_like "no version updates are detected"
+ end
+
+ shared_examples_for "minor version is ignored" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "2.4.5"
+ build_gem "weakling", "0.3.1"
+ end
+ end
+
+ it_behaves_like "no version updates are detected"
+ end
+
+ shared_examples_for "patch version is ignored" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "2.3.6"
+ build_gem "weakling", "0.0.4"
+ end
+ end
+
+ it_behaves_like "no version updates are detected"
+ end
+
+ describe "with --filter-major option" do
+ subject { bundle "outdated --filter-major", :raise_on_error => false }
+
+ it_behaves_like "major version updates are detected"
+ it_behaves_like "minor version is ignored"
+ it_behaves_like "patch version is ignored"
+ end
+
+ describe "with --filter-minor option" do
+ subject { bundle "outdated --filter-minor", :raise_on_error => false }
+
+ it_behaves_like "minor version updates are detected"
+ it_behaves_like "major version is ignored"
+ it_behaves_like "patch version is ignored"
+ end
+
+ describe "with --filter-patch option" do
+ subject { bundle "outdated --filter-patch", :raise_on_error => false }
+
+ it_behaves_like "patch version updates are detected"
+ it_behaves_like "major version is ignored"
+ it_behaves_like "minor version is ignored"
+ end
+
+ describe "with --filter-minor --filter-patch options" do
+ subject { bundle "outdated --filter-minor --filter-patch", :raise_on_error => false }
+
+ it_behaves_like "minor version updates are detected"
+ it_behaves_like "patch version updates are detected"
+ it_behaves_like "major version is ignored"
+ end
+
+ describe "with --filter-major --filter-minor options" do
+ subject { bundle "outdated --filter-major --filter-minor", :raise_on_error => false }
+
+ it_behaves_like "major version updates are detected"
+ it_behaves_like "minor version updates are detected"
+ it_behaves_like "patch version is ignored"
+ end
+
+ describe "with --filter-major --filter-patch options" do
+ subject { bundle "outdated --filter-major --filter-patch", :raise_on_error => false }
+
+ it_behaves_like "major version updates are detected"
+ it_behaves_like "patch version updates are detected"
+ it_behaves_like "minor version is ignored"
+ end
+
+ describe "with --filter-major --filter-minor --filter-patch options" do
+ subject { bundle "outdated --filter-major --filter-minor --filter-patch", :raise_on_error => false }
+
+ it_behaves_like "major version updates are detected"
+ it_behaves_like "minor version updates are detected"
+ it_behaves_like "patch version updates are detected"
+ end
+
+ context "conservative updates" do
+ before do
+ build_repo4 do
+ build_gem "patch", %w[1.0.0 1.0.1]
+ build_gem "minor", %w[1.0.0 1.0.1 1.1.0]
+ build_gem "major", %w[1.0.0 1.0.1 1.1.0 2.0.0]
+ end
+
+ # establish a lockfile set to 1.0.0
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'patch', '1.0.0'
+ gem 'minor', '1.0.0'
+ gem 'major', '1.0.0'
+ G
+
+ # remove all version requirements
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'patch'
+ gem 'minor'
+ gem 'major'
+ G
+ end
+
+ it "shows nothing when patching and filtering to minor" do
+ bundle "outdated --patch --filter-minor"
+
+ expect(out).to end_with("No minor updates to display.")
+ end
+
+ it "shows all gems when patching and filtering to patch" do
+ bundle "outdated --patch --filter-patch", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ major 1.0.0 1.0.1 >= 0 default
+ minor 1.0.0 1.0.1 >= 0 default
+ patch 1.0.0 1.0.1 >= 0 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+
+ it "shows minor and major when updating to minor and filtering to patch and minor" do
+ bundle "outdated --minor --filter-minor", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ major 1.0.0 1.1.0 >= 0 default
+ minor 1.0.0 1.1.0 >= 0 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+
+ it "shows minor when updating to major and filtering to minor with parseable" do
+ bundle "outdated --major --filter-minor --parseable", :raise_on_error => false
+
+ expect(out).not_to include("patch (newest")
+ expect(out).to include("minor (newest")
+ expect(out).not_to include("major (newest")
+ end
+ end
+
+ context "tricky conservative updates" do
+ before do
+ build_repo4 do
+ build_gem "foo", %w[1.4.3 1.4.4] do |s|
+ s.add_dependency "bar", "~> 2.0"
+ end
+ build_gem "foo", %w[1.4.5 1.5.0] do |s|
+ s.add_dependency "bar", "~> 2.1"
+ end
+ build_gem "foo", %w[1.5.1] do |s|
+ s.add_dependency "bar", "~> 3.0"
+ end
+ build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0]
+ build_gem "qux", %w[1.0.0 1.1.0 2.0.0]
+ end
+
+ # establish a lockfile set to 1.4.3
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'foo', '1.4.3'
+ gem 'bar', '2.0.3'
+ gem 'qux', '1.0.0'
+ G
+
+ # remove 1.4.3 requirement and bar altogether
+ # to setup update specs below
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'foo'
+ gem 'qux'
+ G
+ end
+
+ it "shows gems updating to patch and filtering to patch" do
+ bundle "outdated --patch --filter-patch", :raise_on_error => false, :env => { "DEBUG_RESOLVER" => "1" }
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ bar 2.0.3 2.0.5
+ foo 1.4.3 1.4.4 >= 0 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+
+ it "shows gems updating to patch and filtering to patch, in debug mode" do
+ bundle "outdated --patch --filter-patch", :raise_on_error => false, :env => { "DEBUG" => "1" }
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups Path
+ bar 2.0.3 2.0.5
+ foo 1.4.3 1.4.4 >= 0 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+
+ describe "with --only-explicit" do
+ it "does not report outdated dependent gems" do
+ build_repo4 do
+ build_gem "weakling", %w[0.2 0.3] do |s|
+ s.add_dependency "bar", "~> 2.1"
+ end
+ build_gem "bar", %w[2.1 2.2]
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'weakling', '0.2'
+ gem 'bar', '2.1'
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'weakling'
+ G
+
+ bundle "outdated --only-explicit", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ weakling 0.2 0.3 >= 0 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+
+ describe "with a multiplatform lockfile" do
+ before do
+ build_repo4 do
+ build_gem "nokogiri", "1.11.1"
+ build_gem "nokogiri", "1.11.1" do |s|
+ s.platform = Bundler.local_platform
+ end
+
+ build_gem "nokogiri", "1.11.2"
+ build_gem "nokogiri", "1.11.2" do |s|
+ s.platform = Bundler.local_platform
+ end
+ end
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ nokogiri (1.11.1)
+ nokogiri (1.11.1-#{Bundler.local_platform})
+
+ PLATFORMS
+ ruby
+ #{Bundler.local_platform}
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "nokogiri"
+ G
+ end
+
+ it "reports a single entry per gem" do
+ bundle "outdated", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ nokogiri 1.11.1 1.11.2 >= 0 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+
+ context "when a gem is no longer a dependency after a full update" do
+ before do
+ build_repo4 do
+ build_gem "mini_portile2", "2.5.2" do |s|
+ s.add_dependency "net-ftp", "~> 0.1"
+ end
+
+ build_gem "mini_portile2", "2.5.3"
+
+ build_gem "net-ftp", "0.1.2"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "mini_portile2"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ mini_portile2 (2.5.2)
+ net-ftp (~> 0.1)
+ net-ftp (0.1.2)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ mini_portile2
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "works" do
+ bundle "outdated", :raise_on_error => false
+
+ expected_output = <<~TABLE.strip
+ Gem Current Latest Requested Groups
+ mini_portile2 2.5.2 2.5.3 >= 0 default
+ TABLE
+
+ expect(out).to end_with(expected_output)
+ end
+ end
+end
diff --git a/spec/bundler/commands/platform_spec.rb b/spec/bundler/commands/platform_spec.rb
new file mode 100644
index 0000000000..688bf7b6df
--- /dev/null
+++ b/spec/bundler/commands/platform_spec.rb
@@ -0,0 +1,1307 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle platform" do
+ context "without flags" do
+ it "returns all the output" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ #{ruby_version_correct}
+
+ gem "foo"
+ G
+
+ bundle "platform"
+ expect(out).to eq(<<-G.chomp)
+Your platform is: #{Gem::Platform.local}
+
+Your app has gems that work on these platforms:
+* #{local_platform}
+
+Your Gemfile specifies a Ruby version requirement:
+* ruby #{Gem.ruby_version}
+
+Your current platform satisfies the Ruby version requirement.
+G
+ end
+
+ it "returns all the output including the patchlevel" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ #{ruby_version_correct_patchlevel}
+
+ gem "foo"
+ G
+
+ bundle "platform"
+ expect(out).to eq(<<-G.chomp)
+Your platform is: #{Gem::Platform.local}
+
+Your app has gems that work on these platforms:
+* #{local_platform}
+
+Your Gemfile specifies a Ruby version requirement:
+* #{Bundler::RubyVersion.system.single_version_string}
+
+Your current platform satisfies the Ruby version requirement.
+G
+ end
+
+ it "doesn't print ruby version requirement if it isn't specified" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "foo"
+ G
+
+ bundle "platform"
+ expect(out).to eq(<<-G.chomp)
+Your platform is: #{Gem::Platform.local}
+
+Your app has gems that work on these platforms:
+* #{local_platform}
+
+Your Gemfile does not specify a Ruby version requirement.
+G
+ end
+
+ it "doesn't match the ruby version requirement" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ #{ruby_version_incorrect}
+
+ gem "foo"
+ G
+
+ bundle "platform"
+ expect(out).to eq(<<-G.chomp)
+Your platform is: #{Gem::Platform.local}
+
+Your app has gems that work on these platforms:
+* #{local_platform}
+
+Your Gemfile specifies a Ruby version requirement:
+* ruby #{not_local_ruby_version}
+
+Your Ruby version is #{Gem.ruby_version}, but your Gemfile specified #{not_local_ruby_version}
+G
+ end
+ end
+
+ context "--ruby" do
+ it "returns ruby version when explicit" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "1.9.3", :engine => 'ruby', :engine_version => '1.9.3'
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("ruby 1.9.3")
+ end
+
+ it "defaults to MRI" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "1.9.3"
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("ruby 1.9.3")
+ end
+
+ it "handles jruby" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "1.8.7", :engine => 'jruby', :engine_version => '1.6.5'
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("ruby 1.8.7 (jruby 1.6.5)")
+ end
+
+ it "handles rbx" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "1.8.7", :engine => 'rbx', :engine_version => '1.2.4'
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("ruby 1.8.7 (rbx 1.2.4)")
+ end
+
+ it "handles truffleruby" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "2.5.1", :engine => 'truffleruby', :engine_version => '1.0.0-rc6'
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("ruby 2.5.1 (truffleruby 1.0.0-rc6)")
+ end
+
+ it "raises an error if engine is used but engine version is not" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "1.8.7", :engine => 'rbx'
+
+ gem "foo"
+ G
+
+ bundle "platform", :raise_on_error => false
+
+ expect(exitstatus).not_to eq(0)
+ end
+
+ it "raises an error if engine_version is used but engine is not" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "1.8.7", :engine_version => '1.2.4'
+
+ gem "foo"
+ G
+
+ bundle "platform", :raise_on_error => false
+
+ expect(exitstatus).not_to eq(0)
+ end
+
+ it "raises an error if engine version doesn't match ruby version for MRI" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "1.8.7", :engine => 'ruby', :engine_version => '1.2.4'
+
+ gem "foo"
+ G
+
+ bundle "platform", :raise_on_error => false
+
+ expect(exitstatus).not_to eq(0)
+ end
+
+ it "should print if no ruby version is specified" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("No ruby version specified")
+ end
+
+ it "handles when there is a locked requirement" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "< 1.8.7"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ ruby 1.0.0p127
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "platform --ruby"
+ expect(out).to eq("ruby 1.0.0")
+ end
+
+ it "handles when there is a lockfile with no requirement" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "platform --ruby"
+ expect(out).to eq("No ruby version specified")
+ end
+
+ it "handles when there is a requirement in the gemfile" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby ">= 1.8.7"
+ G
+
+ bundle "platform --ruby"
+ expect(out).to eq("ruby 1.8.7")
+ end
+
+ it "handles when there are multiple requirements in the gemfile" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby ">= 1.8.7", "< 2.0.0"
+ G
+
+ bundle "platform --ruby"
+ expect(out).to eq("ruby 1.8.7")
+ end
+ end
+
+ let(:ruby_version_correct) { "ruby \"#{Gem.ruby_version}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{local_engine_version}\"" }
+ let(:ruby_version_correct_engineless) { "ruby \"#{Gem.ruby_version}\"" }
+ let(:ruby_version_correct_patchlevel) { "#{ruby_version_correct}, :patchlevel => '#{RUBY_PATCHLEVEL}'" }
+ let(:ruby_version_incorrect) { "ruby \"#{not_local_ruby_version}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{not_local_ruby_version}\"" }
+ let(:engine_incorrect) { "ruby \"#{Gem.ruby_version}\", :engine => \"#{not_local_tag}\", :engine_version => \"#{Gem.ruby_version}\"" }
+ let(:engine_version_incorrect) { "ruby \"#{Gem.ruby_version}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{not_local_engine_version}\"" }
+ let(:patchlevel_incorrect) { "#{ruby_version_correct}, :patchlevel => '#{not_local_patchlevel}'" }
+ let(:patchlevel_fixnum) { "#{ruby_version_correct}, :patchlevel => #{RUBY_PATCHLEVEL}1" }
+
+ def should_be_ruby_version_incorrect
+ expect(exitstatus).to eq(18)
+ expect(err).to be_include("Your Ruby version is #{Gem.ruby_version}, but your Gemfile specified #{not_local_ruby_version}")
+ end
+
+ def should_be_engine_incorrect
+ expect(exitstatus).to eq(18)
+ expect(err).to be_include("Your Ruby engine is #{local_ruby_engine}, but your Gemfile specified #{not_local_tag}")
+ end
+
+ def should_be_engine_version_incorrect
+ expect(exitstatus).to eq(18)
+ expect(err).to be_include("Your #{local_ruby_engine} version is #{local_engine_version}, but your Gemfile specified #{local_ruby_engine} #{not_local_engine_version}")
+ end
+
+ def should_be_patchlevel_incorrect
+ expect(exitstatus).to eq(18)
+ expect(err).to be_include("Your Ruby patchlevel is #{RUBY_PATCHLEVEL}, but your Gemfile specified #{not_local_patchlevel}")
+ end
+
+ def should_be_patchlevel_fixnum
+ expect(exitstatus).to eq(18)
+ expect(err).to be_include("The Ruby patchlevel in your Gemfile must be a string")
+ end
+
+ context "bundle install" do
+ it "installs fine when the ruby version matches" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{ruby_version_correct}
+ G
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "installs fine with any engine", :jruby_only do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "installs fine when the patchlevel matches" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{ruby_version_correct_patchlevel}
+ G
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "doesn't install when the ruby version doesn't match" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{ruby_version_incorrect}
+ G
+
+ expect(bundled_app_lock).not_to exist
+ should_be_ruby_version_incorrect
+ end
+
+ it "doesn't install when engine doesn't match" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{engine_incorrect}
+ G
+
+ expect(bundled_app_lock).not_to exist
+ should_be_engine_incorrect
+ end
+
+ it "doesn't install when engine version doesn't match", :jruby_only do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{engine_version_incorrect}
+ G
+
+ expect(bundled_app_lock).not_to exist
+ should_be_engine_version_incorrect
+ end
+
+ it "doesn't install when patchlevel doesn't match" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ expect(bundled_app_lock).not_to exist
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle check" do
+ it "checks fine when the ruby version matches" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{ruby_version_correct}
+ G
+
+ bundle :check
+ expect(out).to match(/\AResolving dependencies\.\.\.\.*\nThe Gemfile's dependencies are satisfied\z/)
+ end
+
+ it "checks fine with any engine", :jruby_only do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle :check
+ expect(out).to match(/\AResolving dependencies\.\.\.\.*\nThe Gemfile's dependencies are satisfied\z/)
+ end
+
+ it "fails when ruby version doesn't match" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle :check, :raise_on_error => false
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when engine doesn't match" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{engine_incorrect}
+ G
+
+ bundle :check, :raise_on_error => false
+ should_be_engine_incorrect
+ end
+
+ it "fails when engine version doesn't match", :jruby_only do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{engine_version_incorrect}
+ G
+
+ bundle :check, :raise_on_error => false
+ should_be_engine_version_incorrect
+ end
+
+ it "fails when patchlevel doesn't match" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle :check, :raise_on_error => false
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle update" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ G
+ end
+
+ it "updates successfully when the ruby version matches" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+
+ #{ruby_version_correct}
+ G
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update", :all => true
+ expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0"
+ end
+
+ it "updates fine with any engine", :jruby_only do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+
+ #{ruby_version_correct_engineless}
+ G
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update", :all => true
+ expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0"
+ end
+
+ it "fails when ruby version doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+
+ #{ruby_version_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle :update, :all => true, :raise_on_error => false
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when ruby engine doesn't match", :jruby_only do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+
+ #{engine_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle :update, :all => true, :raise_on_error => false
+ should_be_engine_incorrect
+ end
+
+ it "fails when ruby engine version doesn't match", :jruby_only do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+
+ #{engine_version_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle :update, :all => true, :raise_on_error => false
+ should_be_engine_version_incorrect
+ end
+
+ it "fails when patchlevel doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle :update, :all => true, :raise_on_error => false
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle info" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+ end
+
+ it "prints path if ruby version is correct" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+
+ #{ruby_version_correct}
+ G
+
+ bundle "info rails --path"
+ expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ it "prints path if ruby version is correct for any engine", :jruby_only do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle "info rails --path"
+ expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ it "fails if ruby version doesn't match", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle "show rails", :raise_on_error => false
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails if engine doesn't match", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+
+ #{engine_incorrect}
+ G
+
+ bundle "show rails", :raise_on_error => false
+ should_be_engine_incorrect
+ end
+
+ it "fails if engine version doesn't match", :bundler => "< 3", :jruby_only => true do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+
+ #{engine_version_incorrect}
+ G
+
+ bundle "show rails", :raise_on_error => false
+ should_be_engine_version_incorrect
+ end
+
+ it "fails when patchlevel doesn't match", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "show rails", :raise_on_error => false
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle cache" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+ end
+
+ it "copies the .gem file to vendor/cache when ruby version matches" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+
+ #{ruby_version_correct}
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "copies the .gem file to vendor/cache when ruby version matches for any engine", :jruby_only do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "fails if the ruby version doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle :cache, :raise_on_error => false
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails if the engine doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+
+ #{engine_incorrect}
+ G
+
+ bundle :cache, :raise_on_error => false
+ should_be_engine_incorrect
+ end
+
+ it "fails if the engine version doesn't match", :jruby_only do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+
+ #{engine_version_incorrect}
+ G
+
+ bundle :cache, :raise_on_error => false
+ should_be_engine_version_incorrect
+ end
+
+ it "fails when patchlevel doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle :cache, :raise_on_error => false
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle pack" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+ end
+
+ it "copies the .gem file to vendor/cache when ruby version matches" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+
+ #{ruby_version_correct}
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "copies the .gem file to vendor/cache when ruby version matches any engine", :jruby_only do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "fails if the ruby version doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle :cache, :raise_on_error => false
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails if the engine doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+
+ #{engine_incorrect}
+ G
+
+ bundle :cache, :raise_on_error => false
+ should_be_engine_incorrect
+ end
+
+ it "fails if the engine version doesn't match", :jruby_only do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+
+ #{engine_version_incorrect}
+ G
+
+ bundle :cache, :raise_on_error => false
+ should_be_engine_version_incorrect
+ end
+
+ it "fails when patchlevel doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle :cache, :raise_on_error => false
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle exec" do
+ before do
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ system_gems "rack-1.0.0", "rack-0.9.1", :path => default_bundle_path
+ end
+
+ it "activates the correct gem when ruby version matches" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+
+ #{ruby_version_correct}
+ G
+
+ bundle "exec rackup"
+ expect(out).to include("0.9.1")
+ end
+
+ it "activates the correct gem when ruby version matches any engine", :jruby_only do
+ system_gems "rack-1.0.0", "rack-0.9.1", :path => default_bundle_path
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle "exec rackup"
+ expect(out).to include("0.9.1")
+ end
+
+ it "fails when the ruby version doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle "exec rackup", :raise_on_error => false
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when the engine doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+
+ #{engine_incorrect}
+ G
+
+ bundle "exec rackup", :raise_on_error => false
+ should_be_engine_incorrect
+ end
+
+ # it "fails when the engine version doesn't match", :jruby_only do
+ # gemfile <<-G
+ # gem "rack", "0.9.1"
+ #
+ # #{engine_version_incorrect}
+ # G
+ #
+ # bundle "exec rackup"
+ # should_be_engine_version_incorrect
+ # end
+
+ it "fails when patchlevel doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle "exec rackup", :raise_on_error => false
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle console", :bundler => "< 3" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+ G
+ end
+
+ it "starts IRB with the default group loaded when ruby version matches", :readline do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{ruby_version_correct}
+ G
+
+ bundle "console" do |input, _, _|
+ input.puts("puts RACK")
+ input.puts("exit")
+ end
+ expect(out).to include("0.9.1")
+ end
+
+ it "starts IRB with the default group loaded when ruby version matches", :readline, :jruby_only do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle "console" do |input, _, _|
+ input.puts("puts RACK")
+ input.puts("exit")
+ end
+ expect(out).to include("0.9.1")
+ end
+
+ it "fails when ruby version doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle "console", :raise_on_error => false
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when engine doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{engine_incorrect}
+ G
+
+ bundle "console", :raise_on_error => false
+ should_be_engine_incorrect
+ end
+
+ it "fails when engine version doesn't match", :jruby_only do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{engine_version_incorrect}
+ G
+
+ bundle "console", :raise_on_error => false
+ should_be_engine_version_incorrect
+ end
+
+ it "fails when patchlevel doesn't match" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle "console", :raise_on_error => false
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "Bundler.setup" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "yard"
+ gem "rack", :group => :test
+ G
+
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ end
+
+ it "makes a Gemfile.lock if setup succeeds" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "yard"
+ gem "rack"
+
+ #{ruby_version_correct}
+ G
+
+ FileUtils.rm(bundled_app_lock)
+
+ run "1"
+ expect(bundled_app_lock).to exist
+ end
+
+ it "makes a Gemfile.lock if setup succeeds for any engine", :jruby_only do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "yard"
+ gem "rack"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ FileUtils.rm(bundled_app_lock)
+
+ run "1"
+ expect(bundled_app_lock).to exist
+ end
+
+ it "fails when ruby version doesn't match" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "yard"
+ gem "rack"
+
+ #{ruby_version_incorrect}
+ G
+
+ FileUtils.rm(bundled_app_lock)
+
+ ruby "require 'bundler/setup'", :env => { "BUNDLER_VERSION" => Bundler::VERSION }, :raise_on_error => false
+
+ expect(bundled_app_lock).not_to exist
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when engine doesn't match" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "yard"
+ gem "rack"
+
+ #{engine_incorrect}
+ G
+
+ FileUtils.rm(bundled_app_lock)
+
+ ruby "require 'bundler/setup'", :env => { "BUNDLER_VERSION" => Bundler::VERSION }, :raise_on_error => false
+
+ expect(bundled_app_lock).not_to exist
+ should_be_engine_incorrect
+ end
+
+ it "fails when engine version doesn't match", :jruby_only do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "yard"
+ gem "rack"
+
+ #{engine_version_incorrect}
+ G
+
+ FileUtils.rm(bundled_app_lock)
+
+ ruby "require 'bundler/setup'", :env => { "BUNDLER_VERSION" => Bundler::VERSION }, :raise_on_error => false
+
+ expect(bundled_app_lock).not_to exist
+ should_be_engine_version_incorrect
+ end
+
+ it "fails when patchlevel doesn't match" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "yard"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ FileUtils.rm(bundled_app_lock)
+
+ ruby "require 'bundler/setup'", :env => { "BUNDLER_VERSION" => Bundler::VERSION }, :raise_on_error => false
+
+ expect(bundled_app_lock).not_to exist
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle outdated" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+ G
+ end
+
+ it "returns list of outdated gems when the ruby version matches" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{ruby_version_correct}
+ G
+
+ bundle "outdated", :raise_on_error => false
+
+ expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip
+ Gem Current Latest Requested Groups
+ activesupport 2.3.5 3.0 = 2.3.5 default
+ foo 1.0 xxxxxxx 1.0 xxxxxxx >= 0 default
+ TABLE
+
+ expect(out).to match(Regexp.new(expected_output))
+ end
+
+ it "returns list of outdated gems when the ruby version matches for any engine", :jruby_only do
+ bundle :install
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle "outdated", :raise_on_error => false
+
+ expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip
+ Gem Current Latest Requested Groups
+ activesupport 2.3.5 3.0 = 2.3.5 default
+ foo 1.0 xxxxxxx 1.0 xxxxxxx >= 0 default
+ TABLE
+
+ expect(out).to match(Regexp.new(expected_output))
+ end
+
+ it "fails when the ruby version doesn't match" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle "outdated", :raise_on_error => false
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when the engine doesn't match" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{engine_incorrect}
+ G
+
+ bundle "outdated", :raise_on_error => false
+ should_be_engine_incorrect
+ end
+
+ it "fails when the engine version doesn't match", :jruby_only do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{engine_version_incorrect}
+ G
+
+ bundle "outdated", :raise_on_error => false
+ should_be_engine_version_incorrect
+ end
+
+ it "fails when the patchlevel doesn't match", :jruby_only do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle "outdated", :raise_on_error => false
+ should_be_patchlevel_incorrect
+ end
+
+ it "fails when the patchlevel is a fixnum", :jruby_only do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{patchlevel_fixnum}
+ G
+
+ bundle "outdated", :raise_on_error => false
+ should_be_patchlevel_fixnum
+ end
+ end
+end
diff --git a/spec/bundler/commands/post_bundle_message_spec.rb b/spec/bundler/commands/post_bundle_message_spec.rb
new file mode 100644
index 0000000000..3050b87754
--- /dev/null
+++ b/spec/bundler/commands/post_bundle_message_spec.rb
@@ -0,0 +1,205 @@
+# frozen_string_literal: true
+
+RSpec.describe "post bundle message" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "activesupport", "2.3.5", :group => [:emo, :test]
+ group :test do
+ gem "rspec"
+ end
+ gem "rack-obama", :group => :obama
+ G
+ end
+
+ let(:bundle_path) { "./.bundle" }
+ let(:bundle_show_system_message) { "Use `bundle info [gemname]` to see where a bundled gem is installed." }
+ let(:bundle_show_path_message) { "Bundled gems are installed into `#{bundle_path}`" }
+ let(:bundle_complete_message) { "Bundle complete!" }
+ let(:bundle_updated_message) { "Bundle updated!" }
+ let(:installed_gems_stats) { "4 Gemfile dependencies, 5 gems now installed." }
+ let(:bundle_show_message) { Bundler::VERSION.split(".").first.to_i < 3 ? bundle_show_system_message : bundle_show_path_message }
+
+ describe "for fresh bundle install" do
+ it "shows proper messages according to the configured groups" do
+ bundle :install
+ expect(out).to include(bundle_show_message)
+ expect(out).not_to include("Gems in the group")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include(installed_gems_stats)
+
+ bundle "config set --local without emo"
+ bundle :install
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the group 'emo' were not installed")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include(installed_gems_stats)
+
+ bundle "config set --local without emo test"
+ bundle :install
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the groups 'emo' and 'test' were not installed")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include("4 Gemfile dependencies, 3 gems now installed.")
+
+ bundle "config set --local without emo obama test"
+ bundle :install
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include("4 Gemfile dependencies, 2 gems now installed.")
+ end
+
+ describe "with `path` configured" do
+ let(:bundle_path) { "./vendor" }
+
+ it "shows proper messages according to the configured groups" do
+ bundle "config set --local path vendor"
+ bundle :install
+ expect(out).to include(bundle_show_path_message)
+ expect(out).to_not include("Gems in the group")
+ expect(out).to include(bundle_complete_message)
+
+ bundle "config set --local path vendor"
+ bundle "config set --local without emo"
+ bundle :install
+ expect(out).to include(bundle_show_path_message)
+ expect(out).to include("Gems in the group 'emo' were not installed")
+ expect(out).to include(bundle_complete_message)
+
+ bundle "config set --local path vendor"
+ bundle "config set --local without emo test"
+ bundle :install
+ expect(out).to include(bundle_show_path_message)
+ expect(out).to include("Gems in the groups 'emo' and 'test' were not installed")
+ expect(out).to include(bundle_complete_message)
+
+ bundle "config set --local path vendor"
+ bundle "config set --local without emo obama test"
+ bundle :install
+ expect(out).to include(bundle_show_path_message)
+ expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed")
+ expect(out).to include(bundle_complete_message)
+ end
+ end
+
+ describe "with an absolute `path` inside the cwd configured" do
+ let(:bundle_path) { bundled_app("cache") }
+
+ it "shows proper messages according to the configured groups" do
+ bundle "config set --local path #{bundle_path}"
+ bundle :install
+ expect(out).to include("Bundled gems are installed into `./cache`")
+ expect(out).to_not include("Gems in the group")
+ expect(out).to include(bundle_complete_message)
+ end
+ end
+
+ describe "with `path` configured to an absolute path outside the cwd" do
+ let(:bundle_path) { tmp("not_bundled_app") }
+
+ it "shows proper messages according to the configured groups" do
+ bundle "config set --local path #{bundle_path}"
+ bundle :install
+ expect(out).to include("Bundled gems are installed into `#{tmp("not_bundled_app")}`")
+ expect(out).to_not include("Gems in the group")
+ expect(out).to include(bundle_complete_message)
+ end
+ end
+
+ describe "with misspelled or non-existent gem name" do
+ before do
+ bundle "config set force_ruby_platform true"
+ end
+
+ it "should report a helpful error message" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "not-a-gem", :group => :development
+ G
+ expect(err).to include <<-EOS.strip
+Could not find gem 'not-a-gem' in rubygems repository #{file_uri_for(gem_repo1)}/ or installed locally.
+ EOS
+ end
+
+ it "should report a helpful error message with reference to cache if available" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ bundle :cache
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "not-a-gem", :group => :development
+ G
+ expect(err).to include("Could not find gem 'not-a-gem' in").
+ and include("or in gems cached in vendor/cache.")
+ end
+ end
+ end
+
+ describe "for second bundle install run", :bundler => "< 3" do
+ it "without any options" do
+ 2.times { bundle :install }
+ expect(out).to include(bundle_show_message)
+ expect(out).to_not include("Gems in the groups")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include(installed_gems_stats)
+ end
+
+ it "with --without one group" do
+ bundle "install --without emo"
+ bundle :install
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the group 'emo' were not installed")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include(installed_gems_stats)
+ end
+
+ it "with --without two groups" do
+ bundle "install --without emo test"
+ bundle :install
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the groups 'emo' and 'test' were not installed")
+ expect(out).to include(bundle_complete_message)
+ end
+
+ it "with --without more groups" do
+ bundle "install --without emo obama test"
+ bundle :install
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed")
+ expect(out).to include(bundle_complete_message)
+ end
+ end
+
+ describe "for bundle update" do
+ it "shows proper messages according to the configured groups" do
+ bundle :update, :all => true
+ expect(out).not_to include("Gems in the groups")
+ expect(out).to include(bundle_updated_message)
+
+ bundle "config set --local without emo"
+ bundle :install
+ bundle :update, :all => true
+ expect(out).to include("Gems in the group 'emo' were not updated")
+ expect(out).to include(bundle_updated_message)
+
+ bundle "config set --local without emo test"
+ bundle :install
+ bundle :update, :all => true
+ expect(out).to include("Gems in the groups 'emo' and 'test' were not updated")
+ expect(out).to include(bundle_updated_message)
+
+ bundle "config set --local without emo obama test"
+ bundle :install
+ bundle :update, :all => true
+ expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not updated")
+ expect(out).to include(bundle_updated_message)
+ end
+ end
+end
diff --git a/spec/bundler/commands/pristine_spec.rb b/spec/bundler/commands/pristine_spec.rb
new file mode 100644
index 0000000000..9e496dc91a
--- /dev/null
+++ b/spec/bundler/commands/pristine_spec.rb
@@ -0,0 +1,221 @@
+# frozen_string_literal: true
+
+require "bundler/vendored_fileutils"
+
+RSpec.describe "bundle pristine" do
+ before :each do
+ build_lib "baz", :path => bundled_app do |s|
+ s.version = "1.0.0"
+ s.add_development_dependency "baz-dev", "=1.0.0"
+ end
+
+ build_repo2 do
+ build_gem "weakling"
+ build_gem "baz-dev", "1.0.0"
+ build_gem "very_simple_binary", &:add_c_extension
+ build_git "foo", :path => lib_path("foo")
+ build_git "git_with_ext", :path => lib_path("git_with_ext"), &:add_c_extension
+ build_lib "bar", :path => lib_path("bar")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "weakling"
+ gem "very_simple_binary"
+ gem "foo", :git => "#{lib_path("foo")}", :branch => "main"
+ gem "git_with_ext", :git => "#{lib_path("git_with_ext")}"
+ gem "bar", :path => "#{lib_path("bar")}"
+
+ gemspec
+ G
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ end
+
+ context "when sourced from RubyGems" do
+ it "reverts using cached .gem file" do
+ spec = find_spec("weakling")
+ changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt")
+
+ FileUtils.touch(changes_txt)
+ expect(changes_txt).to be_file
+
+ bundle "pristine"
+ expect(changes_txt).to_not be_file
+ end
+
+ it "does not delete the bundler gem" do
+ bundle "install"
+ bundle "pristine"
+ bundle "-v"
+
+ expected = if Bundler::VERSION < "3.0"
+ "Bundler version"
+ else
+ Bundler::VERSION
+ end
+
+ expect(out).to start_with(expected)
+ end
+ end
+
+ context "when sourced from git repo" do
+ it "reverts by resetting to current revision`" do
+ spec = find_spec("foo")
+ changed_file = Pathname.new(spec.full_gem_path).join("lib/foo.rb")
+ diff = "#Pristine spec changes"
+
+ File.open(changed_file, "a") {|f| f.puts diff }
+ expect(File.read(changed_file)).to include(diff)
+
+ bundle "pristine"
+ expect(File.read(changed_file)).to_not include(diff)
+ end
+
+ it "removes added files" do
+ spec = find_spec("foo")
+ changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt")
+
+ FileUtils.touch(changes_txt)
+ expect(changes_txt).to be_file
+
+ bundle "pristine"
+ expect(changes_txt).not_to be_file
+ end
+
+ it "displays warning and ignores changes when a local config exists" do
+ spec = find_spec("foo")
+ bundle "config set local.#{spec.name} #{lib_path(spec.name)}"
+
+ changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt")
+ FileUtils.touch(changes_txt)
+ expect(changes_txt).to be_file
+
+ bundle "pristine"
+ expect(changes_txt).to be_file
+ expect(err).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is locally overridden.")
+ end
+ end
+
+ context "when sourced from gemspec" do
+ it "displays warning and ignores changes when sourced from gemspec" do
+ spec = find_spec("baz")
+ changed_file = Pathname.new(spec.full_gem_path).join("lib/baz.rb")
+ diff = "#Pristine spec changes"
+
+ File.open(changed_file, "a") {|f| f.puts diff }
+ expect(File.read(changed_file)).to include(diff)
+
+ bundle "pristine"
+ expect(File.read(changed_file)).to include(diff)
+ expect(err).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is sourced from local path.")
+ end
+
+ it "reinstall gemspec dependency" do
+ spec = find_spec("baz-dev")
+ changed_file = Pathname.new(spec.full_gem_path).join("lib/baz/dev.rb")
+ diff = "#Pristine spec changes"
+
+ File.open(changed_file, "a") {|f| f.puts "#Pristine spec changes" }
+ expect(File.read(changed_file)).to include(diff)
+
+ bundle "pristine"
+ expect(File.read(changed_file)).to_not include(diff)
+ end
+ end
+
+ context "when sourced from path" do
+ it "displays warning and ignores changes when sourced from local path" do
+ spec = find_spec("bar")
+ changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt")
+ FileUtils.touch(changes_txt)
+ expect(changes_txt).to be_file
+ bundle "pristine"
+ expect(err).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is sourced from local path.")
+ expect(changes_txt).to be_file
+ end
+ end
+
+ context "when passing a list of gems to pristine" do
+ it "resets them" do
+ foo = find_spec("foo")
+ foo_changes_txt = Pathname.new(foo.full_gem_path).join("lib/changes.txt")
+ FileUtils.touch(foo_changes_txt)
+ expect(foo_changes_txt).to be_file
+
+ bar = find_spec("bar")
+ bar_changes_txt = Pathname.new(bar.full_gem_path).join("lib/changes.txt")
+ FileUtils.touch(bar_changes_txt)
+ expect(bar_changes_txt).to be_file
+
+ weakling = find_spec("weakling")
+ weakling_changes_txt = Pathname.new(weakling.full_gem_path).join("lib/changes.txt")
+ FileUtils.touch(weakling_changes_txt)
+ expect(weakling_changes_txt).to be_file
+
+ bundle "pristine foo bar weakling"
+
+ expect(err).to include("Cannot pristine bar (1.0). Gem is sourced from local path.")
+ expect(out).to include("Installing weakling 1.0")
+
+ expect(weakling_changes_txt).not_to be_file
+ expect(foo_changes_txt).not_to be_file
+ expect(bar_changes_txt).to be_file
+ end
+
+ it "raises when one of them is not in the lockfile" do
+ bundle "pristine abcabcabc", :raise_on_error => false
+ expect(err).to include("Could not find gem 'abcabcabc'.")
+ end
+ end
+
+ context "when a build config exists for one of the gems" do
+ let(:very_simple_binary) { find_spec("very_simple_binary") }
+ let(:c_ext_dir) { Pathname.new(very_simple_binary.full_gem_path).join("ext") }
+ let(:build_opt) { "--with-ext-lib=#{c_ext_dir}" }
+ before { bundle "config set build.very_simple_binary -- #{build_opt}" }
+
+ # This just verifies that the generated Makefile from the c_ext gem makes
+ # use of the build_args from the bundle config
+ it "applies the config when installing the gem" do
+ bundle "pristine"
+
+ makefile_contents = File.read(c_ext_dir.join("Makefile").to_s)
+ expect(makefile_contents).to match(/libpath =.*#{c_ext_dir}/)
+ expect(makefile_contents).to match(/LIBPATH =.*-L#{c_ext_dir}/)
+ end
+ end
+
+ context "when a build config exists for a git sourced gem" do
+ let(:git_with_ext) { find_spec("git_with_ext") }
+ let(:c_ext_dir) { Pathname.new(git_with_ext.full_gem_path).join("ext") }
+ let(:build_opt) { "--with-ext-lib=#{c_ext_dir}" }
+ before { bundle "config set build.git_with_ext -- #{build_opt}" }
+
+ # This just verifies that the generated Makefile from the c_ext gem makes
+ # use of the build_args from the bundle config
+ it "applies the config when installing the gem" do
+ bundle "pristine"
+
+ makefile_contents = File.read(c_ext_dir.join("Makefile").to_s)
+ expect(makefile_contents).to match(/libpath =.*#{c_ext_dir}/)
+ expect(makefile_contents).to match(/LIBPATH =.*-L#{c_ext_dir}/)
+ end
+ end
+
+ context "when BUNDLE_GEMFILE doesn't exist" do
+ before do
+ bundle "pristine", :env => { "BUNDLE_GEMFILE" => "does/not/exist" }, :raise_on_error => false
+ end
+
+ it "shows a meaningful error" do
+ expect(err).to eq("#{bundled_app("does/not/exist")} not found")
+ end
+ end
+
+ def find_spec(name)
+ without_env_side_effects do
+ Bundler.definition.specs[name].first
+ end
+ end
+end
diff --git a/spec/bundler/commands/remove_spec.rb b/spec/bundler/commands/remove_spec.rb
new file mode 100644
index 0000000000..d757e0be4b
--- /dev/null
+++ b/spec/bundler/commands/remove_spec.rb
@@ -0,0 +1,732 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle remove" do
+ context "when no gems are specified" do
+ it "throws error" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ bundle "remove", :raise_on_error => false
+
+ expect(err).to include("Please specify gems to remove.")
+ end
+ end
+
+ context "after 'bundle install' is run" do
+ describe "running 'bundle remove GEM_NAME'" do
+ it "removes it from the lockfile" do
+ rack_dep = <<~L
+
+ DEPENDENCIES
+ rack
+
+ L
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+
+ bundle "install"
+
+ expect(lockfile).to include(rack_dep)
+
+ bundle "remove rack"
+
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ expect(lockfile).to_not include(rack_dep)
+ end
+ end
+ end
+
+ context "when --install flag is specified", :bundler => "< 3" do
+ it "removes gems from .bundle" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+
+ bundle "remove rack --install"
+
+ expect(out).to include("rack was removed.")
+ expect(the_bundle).to_not include_gems "rack"
+ end
+ end
+
+ describe "remove single gem from gemfile" do
+ context "when gem is present in gemfile" do
+ it "shows success for removed gem" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(the_bundle).to_not include_gems "rack"
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+
+ context "when gem is specified in multiple lines" do
+ it "shows success for removed gem" do
+ build_git "rack"
+
+ gemfile <<-G
+ source '#{file_uri_for(gem_repo1)}'
+
+ gem 'git'
+ gem 'rack',
+ git: "#{lib_path("rack-1.0")}",
+ branch: 'main'
+ gem 'nokogiri'
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(gemfile).to eq <<~G
+ source '#{file_uri_for(gem_repo1)}'
+
+ gem 'git'
+ gem 'nokogiri'
+ G
+ end
+ end
+ end
+
+ context "when gem is not present in gemfile" do
+ it "shows warning for gem that could not be removed" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ bundle "remove rack", :raise_on_error => false
+
+ expect(err).to include("`rack` is not specified in #{bundled_app_gemfile} so it could not be removed.")
+ end
+ end
+ end
+
+ describe "remove multiple gems from gemfile" do
+ context "when all gems are present in gemfile" do
+ it "shows success fir all removed gems" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ gem "rails"
+ G
+
+ bundle "remove rack rails"
+
+ expect(out).to include("rack was removed.")
+ expect(out).to include("rails was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ end
+
+ context "when some gems are not present in the gemfile" do
+ it "shows warning for those not present and success for those that can be removed" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rails"
+ gem "minitest"
+ gem "rspec"
+ G
+
+ bundle "remove rails rack minitest", :raise_on_error => false
+
+ expect(err).to include("`rack` is not specified in #{bundled_app_gemfile} so it could not be removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rails"
+ gem "minitest"
+ gem "rspec"
+ G
+ end
+ end
+ end
+
+ context "with inline groups" do
+ it "removes the specified gem" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", :group => [:dev]
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ end
+
+ describe "with group blocks" do
+ context "when single group block with gem to be removed is present" do
+ it "removes the group block" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :test do
+ gem "rspec"
+ end
+ G
+
+ bundle "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ end
+
+ context "when gem to be removed is outside block" do
+ it "does not modify group" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ group :test do
+ gem "coffee-script-source"
+ end
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :test do
+ gem "coffee-script-source"
+ end
+ G
+ end
+ end
+
+ context "when an empty block is also present" do
+ it "removes all empty blocks" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :test do
+ gem "rspec"
+ end
+
+ group :dev do
+ end
+ G
+
+ bundle "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ end
+
+ context "when the gem belongs to multiple groups" do
+ it "removes the groups" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :test, :serioustest do
+ gem "rspec"
+ end
+ G
+
+ bundle "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ end
+
+ context "when the gem is present in multiple groups" do
+ it "removes all empty blocks" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :one do
+ gem "rspec"
+ end
+
+ group :two do
+ gem "rspec"
+ end
+ G
+
+ bundle "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ end
+ end
+
+ describe "nested group blocks" do
+ context "when all the groups will be empty after removal" do
+ it "removes the empty nested blocks" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :test do
+ group :serioustest do
+ gem "rspec"
+ end
+ end
+ G
+
+ bundle "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ end
+
+ context "when outer group will not be empty after removal" do
+ it "removes only empty blocks" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :test do
+ gem "rack-test"
+
+ group :serioustest do
+ gem "rspec"
+ end
+ end
+ G
+
+ bundle "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :test do
+ gem "rack-test"
+
+ end
+ G
+ end
+ end
+
+ context "when inner group will not be empty after removal" do
+ it "removes only empty blocks" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :test do
+ group :serioustest do
+ gem "rspec"
+ gem "rack-test"
+ end
+ end
+ G
+
+ bundle "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+
+ group :test do
+ group :serioustest do
+ gem "rack-test"
+ end
+ end
+ G
+ end
+ end
+ end
+
+ describe "arbitrary gemfile" do
+ context "when multiple gems are present in same line" do
+ it "shows warning for gems not removed" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"; gem "rails"
+ G
+
+ bundle "remove rails", :raise_on_error => false
+
+ expect(err).to include("Gems could not be removed. rack (>= 0) would also have been removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"; gem "rails"
+ G
+ end
+ end
+
+ context "when some gems could not be removed" do
+ it "shows warning for gems not removed and success for those removed" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem"rack"
+ gem"rspec"
+ gem "rails"
+ gem "minitest"
+ G
+
+ bundle "remove rails rack rspec minitest"
+
+ expect(out).to include("rails was removed.")
+ expect(out).to include("minitest was removed.")
+ expect(out).to include("rack, rspec could not be removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ gem"rack"
+ gem"rspec"
+ G
+ end
+ end
+ end
+
+ context "with sources" do
+ before do
+ build_repo gem_repo3 do
+ build_gem "rspec"
+ end
+ end
+
+ it "removes gems and empty source blocks" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+
+ source "#{file_uri_for(gem_repo3)}" do
+ gem "rspec"
+ end
+ G
+
+ bundle "install"
+
+ bundle "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+ end
+ end
+
+ describe "with eval_gemfile" do
+ context "when gems are present in both gemfiles" do
+ it "removes the gems" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ eval_gemfile "Gemfile-other"
+
+ gem "rack"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ end
+ end
+
+ context "when gems are present in other gemfile" do
+ it "removes the gems" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ eval_gemfile "Gemfile-other"
+ G
+
+ bundle "remove rack"
+
+ expect(bundled_app("Gemfile-other").read).to_not include("gem \"rack\"")
+ expect(out).to include("rack was removed.")
+ end
+ end
+
+ context "when gems to be removed are not specified in any of the gemfiles" do
+ it "throws error for the gems not present" do
+ # an empty gemfile
+ # indicating the gem is not present in the gemfile
+ create_file "Gemfile-other", <<-G
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ eval_gemfile "Gemfile-other"
+ G
+
+ bundle "remove rack", :raise_on_error => false
+
+ expect(err).to include("`rack` is not specified in #{bundled_app_gemfile} so it could not be removed.")
+ end
+ end
+
+ context "when the gem is present in parent file but not in gemfile specified by eval_gemfile" do
+ it "removes the gem" do
+ create_file "Gemfile-other", <<-G
+ gem "rails"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rack"
+ G
+
+ bundle "remove rack", :raise_on_error => false
+
+ expect(out).to include("rack was removed.")
+ expect(err).to include("`rack` is not specified in #{bundled_app("Gemfile-other")} so it could not be removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+
+ eval_gemfile "Gemfile-other"
+ G
+ end
+ end
+
+ context "when gems cannot be removed from other gemfile" do
+ it "shows error" do
+ create_file "Gemfile-other", <<-G
+ gem "rails"; gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rack"
+ G
+
+ bundle "remove rack", :raise_on_error => false
+
+ expect(out).to include("rack was removed.")
+ expect(err).to include("Gems could not be removed. rails (>= 0) would also have been removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+
+ eval_gemfile "Gemfile-other"
+ G
+ end
+ end
+
+ context "when gems could not be removed from parent gemfile" do
+ it "shows error" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rails"; gem "rack"
+ G
+
+ bundle "remove rack", :raise_on_error => false
+
+ expect(err).to include("Gems could not be removed. rails (>= 0) would also have been removed.")
+ expect(bundled_app("Gemfile-other").read).to include("gem \"rack\"")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rails"; gem "rack"
+ G
+ end
+ end
+
+ context "when gem present in gemfiles but could not be removed from one from one of them" do
+ it "removes gem which can be removed and shows warning for file from which it cannot be removed" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ eval_gemfile "Gemfile-other"
+ gem"rack"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(bundled_app("Gemfile-other").read).to_not include("gem \"rack\"")
+ end
+ end
+ end
+
+ context "with install_if" do
+ it "removes gems inside blocks and empty blocks" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ install_if(lambda { false }) do
+ gem "rack"
+ end
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ end
+
+ context "with env" do
+ it "removes gems inside blocks and empty blocks" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ env "BUNDLER_TEST" do
+ gem "rack"
+ end
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ end
+
+ context "with gemspec" do
+ it "should not remove the gem" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("foo.gemspec", "")
+ s.add_dependency "rack"
+ end
+
+ install_gemfile(<<-G)
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ bundle "remove foo"
+
+ expect(out).to include("foo could not be removed.")
+ end
+ end
+
+ describe "with comments that mention gems" do
+ context "when comment is a separate line comment" do
+ it "does not remove the line comment" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ # gem "rack" might be used in the future
+ gem "rack"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+
+ # gem "rack" might be used in the future
+ G
+ end
+ end
+
+ context "when gem specified for removal has an inline comment" do
+ it "removes the inline comment" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack" # this can be removed
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ end
+
+ context "when gem specified for removal is mentioned in other gem's comment" do
+ it "does not remove other gem" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "puma" # implements interface provided by gem "rack"
+
+ gem "rack"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to_not include("puma was removed.")
+ expect(out).to include("rack was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "puma" # implements interface provided by gem "rack"
+ G
+ end
+ end
+
+ context "when gem specified for removal has a comment that mentions other gem" do
+ it "does not remove other gem" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "puma" # implements interface provided by gem "rack"
+
+ gem "rack"
+ G
+
+ bundle "remove puma"
+
+ expect(out).to include("puma was removed.")
+ expect(out).to_not include("rack was removed.")
+ expect(gemfile).to eq <<~G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+ end
+ end
+ end
+end
diff --git a/spec/bundler/commands/show_spec.rb b/spec/bundler/commands/show_spec.rb
new file mode 100644
index 0000000000..1c6244be41
--- /dev/null
+++ b/spec/bundler/commands/show_spec.rb
@@ -0,0 +1,224 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle show", :bundler => "< 3" do
+ context "with a standard Gemfile" do
+ before :each do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+ end
+
+ it "creates a Gemfile.lock if one did not exist" do
+ FileUtils.rm(bundled_app_lock)
+
+ bundle "show"
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "creates a Gemfile.lock when invoked with a gem name" do
+ FileUtils.rm(bundled_app_lock)
+
+ bundle "show rails"
+
+ expect(bundled_app_lock).to exist
+ end
+
+ it "prints path if gem exists in bundle" do
+ bundle "show rails"
+ expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ it "prints path if gem exists in bundle (with --paths option)" do
+ bundle "show rails --paths"
+ expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ it "warns if path no longer exists on disk" do
+ FileUtils.rm_rf(default_bundle_path("gems", "rails-2.3.2"))
+
+ bundle "show rails"
+
+ expect(err).to match(/has been deleted/i)
+ expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ it "prints the path to the running bundler" do
+ bundle "show bundler"
+ expect(out).to eq(root.to_s)
+ end
+
+ it "complains if gem not in bundle" do
+ bundle "show missing", :raise_on_error => false
+ expect(err).to match(/could not find gem 'missing'/i)
+ end
+
+ it "prints path of all gems in bundle sorted by name" do
+ bundle "show --paths"
+
+ expect(out).to include(default_bundle_path("gems", "rake-13.0.1").to_s)
+ expect(out).to include(default_bundle_path("gems", "rails-2.3.2").to_s)
+
+ # Gem names are the last component of their path.
+ gem_list = out.split.map {|p| p.split("/").last }
+ expect(gem_list).to eq(gem_list.sort)
+ end
+
+ it "prints summary of gems" do
+ bundle "show --verbose"
+
+ expect(out).to include <<~MSG
+ * actionmailer (2.3.2)
+ \tSummary: This is just a fake gem for testing
+ \tHomepage: http://example.com
+ \tStatus: Up to date
+ MSG
+ end
+
+ it "includes bundler in the summary of gems" do
+ bundle "show --verbose"
+
+ expect(out).to include <<~MSG
+ * bundler (#{Bundler::VERSION})
+ \tSummary: The best way to manage your application's dependencies
+ \tHomepage: https://bundler.io
+ \tStatus: Up to date
+ MSG
+ end
+ end
+
+ context "with a git repo in the Gemfile" do
+ before :each do
+ @git = build_git "foo", "1.0"
+ end
+
+ it "prints out git info" do
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+ expect(the_bundle).to include_gems "foo 1.0"
+
+ bundle :show
+ expect(out).to include("foo (1.0 #{@git.ref_for("main", 6)}")
+ end
+
+ it "prints out branch names other than main" do
+ update_git "foo", :branch => "omg" do |s|
+ s.write "lib/foo.rb", "FOO = '1.0.omg'"
+ end
+ @revision = revision_for(lib_path("foo-1.0"))[0...6]
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg"
+ G
+ expect(the_bundle).to include_gems "foo 1.0.omg"
+
+ bundle :show
+ expect(out).to include("foo (1.0 #{@git.ref_for("omg", 6)}")
+ end
+
+ it "doesn't print the branch when tied to a ref" do
+ sha = revision_for(lib_path("foo-1.0"))
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{sha}"
+ G
+
+ bundle :show
+ expect(out).to include("foo (1.0 #{sha[0..6]})")
+ end
+
+ it "handles when a version is a '-' prerelease" do
+ @git = build_git("foo", "1.0.0-beta.1", :path => lib_path("foo"))
+ install_gemfile <<-G
+ gem "foo", "1.0.0-beta.1", :git => "#{lib_path("foo")}"
+ G
+ expect(the_bundle).to include_gems "foo 1.0.0.pre.beta.1"
+
+ bundle :show
+ expect(out).to include("foo (1.0.0.pre.beta.1")
+ end
+ end
+
+ context "in a fresh gem in a blank git repo" do
+ before :each do
+ build_git "foo", :path => lib_path("foo")
+ File.open(lib_path("foo/Gemfile"), "w") {|f| f.puts "gemspec" }
+ sys_exec "rm -rf .git && git init", :dir => lib_path("foo")
+ end
+
+ it "does not output git errors" do
+ bundle :show, :dir => lib_path("foo")
+ expect(err_without_deprecations).to be_empty
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo"
+ G
+
+ bundle "config set auto_install 1"
+ bundle :show
+ expect(out).to include("Installing foo 1.0")
+ end
+
+ context "with a valid regexp for gem name" do
+ it "presents alternatives", :readline do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ bundle "show rac"
+ expect(out).to match(/\A1 : rack\n2 : rack-obama\n0 : - exit -(\n>.*)?\z/)
+ end
+ end
+
+ context "with an invalid regexp for gem name" do
+ it "does not find the gem" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ invalid_regexp = "[]"
+
+ bundle "show #{invalid_regexp}", :raise_on_error => false
+ expect(err).to include("Could not find gem '#{invalid_regexp}'.")
+ end
+ end
+
+ context "--outdated option" do
+ # Regression test for https://github.com/rubygems/bundler/issues/5375
+ before do
+ build_repo2
+ end
+
+ it "doesn't update gems to newer versions" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails"
+ G
+
+ expect(the_bundle).to include_gem("rails 2.3.2")
+
+ update_repo2 do
+ build_gem "rails", "3.0.0" do |s|
+ s.executables = "rails"
+ end
+ end
+
+ bundle "show --outdated"
+
+ bundle "install"
+ expect(the_bundle).to include_gem("rails 2.3.2")
+ end
+ end
+end
+
+RSpec.describe "bundle show", :bundler => "3" do
+ pending "shows a friendly error about the command removal"
+end
diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb
new file mode 100644
index 0000000000..84042709bf
--- /dev/null
+++ b/spec/bundler/commands/update_spec.rb
@@ -0,0 +1,1715 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ describe "with no arguments" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ gem "platform_specific"
+ G
+ end
+
+ it "updates the entire bundle" do
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update"
+ expect(out).to include("Bundle updated!")
+ expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0"
+ end
+
+ it "doesn't delete the Gemfile.lock file if something goes wrong" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ exit!
+ G
+ bundle "update", :raise_on_error => false
+ expect(bundled_app_lock).to exist
+ end
+ end
+
+ describe "with --all" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ gem "platform_specific"
+ G
+ end
+
+ it "updates the entire bundle" do
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update", :all => true
+ expect(out).to include("Bundle updated!")
+ expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0"
+ end
+
+ it "doesn't delete the Gemfile.lock file if something goes wrong" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ exit!
+ G
+ bundle "update", :all => true, :raise_on_error => false
+ expect(bundled_app_lock).to exist
+ end
+ end
+
+ describe "with --gemfile" do
+ it "creates lock files based on the Gemfile name" do
+ gemfile bundled_app("OmgFile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0"
+ G
+
+ bundle "update --gemfile OmgFile", :all => true
+
+ expect(bundled_app("OmgFile.lock")).to exist
+ end
+ end
+
+ context "when update_requires_all_flag is set" do
+ before { bundle "config set update_requires_all_flag true" }
+
+ it "errors when passed nothing" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ bundle :update, :raise_on_error => false
+ expect(err).to eq("To update everything, pass the `--all` flag.")
+ end
+
+ it "errors when passed --all and another option" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ bundle "update --all foo", :raise_on_error => false
+ expect(err).to eq("Cannot specify --all along with specific options.")
+ end
+
+ it "updates everything when passed --all" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ bundle "update --all"
+ expect(out).to include("Bundle updated!")
+ end
+ end
+
+ describe "--quiet argument" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ gem "platform_specific"
+ G
+ end
+
+ it "hides UI messages" do
+ bundle "update --quiet"
+ expect(out).not_to include("Bundle updated!")
+ end
+ end
+
+ describe "with a top level dependency" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ gem "platform_specific"
+ G
+ end
+
+ it "unlocks all child dependencies that are unrelated to other locked dependencies" do
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update rack-obama"
+ expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 2.3.5"
+ end
+ end
+
+ describe "with an unknown dependency" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ gem "platform_specific"
+ G
+ end
+
+ it "should inform the user" do
+ bundle "update halting-problem-solver", :raise_on_error => false
+ expect(err).to include "Could not find gem 'halting-problem-solver'"
+ end
+ it "should suggest alternatives" do
+ bundle "update platformspecific", :raise_on_error => false
+ expect(err).to include "Did you mean platform_specific?"
+ end
+ end
+
+ describe "with a child dependency" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ gem "platform_specific"
+ G
+ end
+
+ it "should update the child dependency" do
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ bundle "update rack"
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+ end
+
+ describe "when a possible resolve requires an older version of a locked gem" do
+ it "does not go to an older version" do
+ build_repo4 do
+ build_gem "tilt", "2.0.8"
+ build_gem "slim", "3.0.9" do |s|
+ s.add_dependency "tilt", [">= 1.3.3", "< 2.1"]
+ end
+ build_gem "slim_lint", "0.16.1" do |s|
+ s.add_dependency "slim", [">= 3.0", "< 5.0"]
+ end
+ build_gem "slim-rails", "0.2.1" do |s|
+ s.add_dependency "slim", ">= 0.9.2"
+ end
+ build_gem "slim-rails", "3.1.3" do |s|
+ s.add_dependency "slim", "~> 3.0"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "slim-rails"
+ gem "slim_lint"
+ G
+
+ expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1")
+
+ update_repo4 do
+ build_gem "slim", "4.0.0" do |s|
+ s.add_dependency "tilt", [">= 2.0.6", "< 2.1"]
+ end
+ end
+
+ bundle "update", :all => true
+
+ expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1")
+ end
+
+ it "does not go to an older version, even if the version upgrade that could cause another gem to downgrade is activated first" do
+ build_repo4 do
+ # countries is processed before country_select by the resolver due to having less spec groups (groups of versions with the same dependencies) (2 vs 3)
+
+ build_gem "countries", "2.1.4"
+ build_gem "countries", "3.1.0"
+
+ build_gem "countries", "4.0.0" do |s|
+ s.add_dependency "sixarm_ruby_unaccent", "~> 1.1"
+ end
+
+ build_gem "country_select", "1.2.0"
+
+ build_gem "country_select", "2.1.4" do |s|
+ s.add_dependency "countries", "~> 2.0"
+ end
+ build_gem "country_select", "3.1.1" do |s|
+ s.add_dependency "countries", "~> 2.0"
+ end
+
+ build_gem "country_select", "5.1.0" do |s|
+ s.add_dependency "countries", "~> 3.0"
+ end
+
+ build_gem "sixarm_ruby_unaccent", "1.1.0"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "country_select"
+ gem "countries"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ countries (3.1.0)
+ country_select (5.1.0)
+ countries (~> 3.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ countries
+ country_select
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ previous_lockfile = lockfile
+
+ bundle "lock --update"
+
+ expect(lockfile).to eq(previous_lockfile)
+ end
+
+ it "does not downgrade direct dependencies when run with --conservative" do
+ build_repo4 do
+ build_gem "oauth2", "2.0.6" do |s|
+ s.add_dependency "faraday", ">= 0.17.3", "< 3.0"
+ end
+
+ build_gem "oauth2", "1.4.10" do |s|
+ s.add_dependency "faraday", ">= 0.17.3", "< 3.0"
+ s.add_dependency "multi_json", "~> 1.3"
+ end
+
+ build_gem "faraday", "2.5.2"
+
+ build_gem "multi_json", "1.15.0"
+
+ build_gem "quickbooks-ruby", "1.0.19" do |s|
+ s.add_dependency "oauth2", "~> 1.4"
+ end
+
+ build_gem "quickbooks-ruby", "0.1.9" do |s|
+ s.add_dependency "oauth2"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "oauth2"
+ gem "quickbooks-ruby"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ faraday (2.5.2)
+ multi_json (1.15.0)
+ oauth2 (1.4.10)
+ faraday (>= 0.17.3, < 3.0)
+ multi_json (~> 1.3)
+ quickbooks-ruby (1.0.19)
+ oauth2 (~> 1.4)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ oauth2
+ quickbooks-ruby
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update --conservative --verbose"
+
+ expect(out).not_to include("Installing quickbooks-ruby 0.1.9")
+ expect(out).to include("Installing quickbooks-ruby 1.0.19").and include("Installing oauth2 1.4.10")
+ end
+
+ it "does not downgrade direct dependencies when using gemspec sources" do
+ create_file("rails.gemspec", <<-G)
+ Gem::Specification.new do |gem|
+ gem.name = "rails"
+ gem.version = "7.1.0.alpha"
+ gem.author = "DHH"
+ gem.summary = "Full-stack web application framework."
+ end
+ G
+
+ build_repo4 do
+ build_gem "rake", "12.3.3"
+ build_gem "rake", "13.0.6"
+
+ build_gem "sneakers", "2.11.0" do |s|
+ s.add_dependency "rake"
+ end
+
+ build_gem "sneakers", "2.12.0" do |s|
+ s.add_dependency "rake", "~> 12.3"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gemspec
+
+ gem "rake"
+ gem "sneakers"
+ G
+
+ lockfile <<~L
+ PATH
+ remote: .
+ specs:
+
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ rake (13.0.6)
+ sneakers (2.11.0)
+ rake
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rake
+ sneakers
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update --verbose"
+
+ expect(out).not_to include("Installing sneakers 2.12.0")
+ expect(out).not_to include("Installing rake 12.3.3")
+ expect(out).to include("Installing sneakers 2.11.0").and include("Installing rake 13.0.6")
+ end
+
+ it "does not downgrade indirect dependencies unnecessarily" do
+ build_repo4 do
+ build_gem "a" do |s|
+ s.add_dependency "b"
+ s.add_dependency "c"
+ end
+ build_gem "b"
+ build_gem "c"
+ build_gem "c", "2.0"
+ end
+
+ install_gemfile <<-G, :verbose => true
+ source "#{file_uri_for(gem_repo4)}"
+ gem "a"
+ G
+
+ expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0")
+
+ update_repo4 do
+ build_gem "b", "2.0" do |s|
+ s.add_dependency "c", "< 2"
+ end
+ end
+
+ bundle "update", :all => true, :verbose => true
+ expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0")
+ end
+
+ it "should still downgrade if forced by the Gemfile" do
+ build_repo4 do
+ build_gem "a"
+ build_gem "b", "1.0"
+ build_gem "b", "2.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "a"
+ gem "b"
+ G
+
+ expect(the_bundle).to include_gems("a 1.0", "b 2.0")
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "a"
+ gem "b", "1.0"
+ G
+
+ bundle "update b"
+
+ expect(the_bundle).to include_gems("a 1.0", "b 1.0")
+ end
+
+ it "should still downgrade if forced by the Gemfile, when transitive dependencies also need downgrade" do
+ build_repo4 do
+ build_gem "activesupport", "6.1.4.1" do |s|
+ s.add_dependency "tzinfo", "~> 2.0"
+ end
+
+ build_gem "activesupport", "6.0.4.1" do |s|
+ s.add_dependency "tzinfo", "~> 1.1"
+ end
+
+ build_gem "tzinfo", "2.0.4"
+ build_gem "tzinfo", "1.2.9"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "activesupport", "~> 6.1.0"
+ G
+
+ expect(the_bundle).to include_gems("activesupport 6.1.4.1", "tzinfo 2.0.4")
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "activesupport", "~> 6.0.0"
+ G
+
+ original_lockfile = lockfile
+
+ expected_lockfile = <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ activesupport (6.0.4.1)
+ tzinfo (~> 1.1)
+ tzinfo (1.2.9)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ activesupport (~> 6.0.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update activesupport"
+ expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9")
+ expect(lockfile).to eq(expected_lockfile)
+
+ lockfile original_lockfile
+ bundle "update"
+ expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9")
+ expect(lockfile).to eq(expected_lockfile)
+
+ lockfile original_lockfile
+ bundle "lock --update"
+ expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9")
+ expect(lockfile).to eq(expected_lockfile)
+ end
+ end
+
+ describe "with --local option" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ gem "platform_specific"
+ G
+ end
+
+ it "doesn't hit repo2" do
+ FileUtils.rm_rf(gem_repo2)
+
+ bundle "update --local --all"
+ expect(out).not_to include("Fetching source index")
+ end
+ end
+
+ describe "with --group option" do
+ before do
+ build_repo2
+ end
+
+ it "should update only specified group gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", :group => :development
+ gem "rack"
+ G
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "activesupport", "3.0"
+ end
+ bundle "update --group development"
+ expect(the_bundle).to include_gems "activesupport 3.0"
+ expect(the_bundle).not_to include_gems "rack 1.2"
+ end
+
+ context "when conservatively updating a group with non-group sub-deps" do
+ it "should update only specified group gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activemerchant", :group => :development
+ gem "activesupport"
+ G
+ update_repo2 do
+ build_gem "activemerchant", "2.0"
+ build_gem "activesupport", "3.0"
+ end
+ bundle "update --conservative --group development"
+ expect(the_bundle).to include_gems "activemerchant 2.0"
+ expect(the_bundle).not_to include_gems "activesupport 3.0"
+ end
+ end
+
+ context "when there is a source with the same name as a gem in a group" do
+ before do
+ build_git "foo", :path => lib_path("activesupport")
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport", :group => :development
+ gem "foo", :git => "#{lib_path("activesupport")}"
+ G
+ end
+
+ it "should not update the gems from that source" do
+ update_repo2 { build_gem "activesupport", "3.0" }
+ update_git "foo", "2.0", :path => lib_path("activesupport")
+
+ bundle "update --group development"
+ expect(the_bundle).to include_gems "activesupport 3.0"
+ expect(the_bundle).not_to include_gems "foo 2.0"
+ end
+ end
+
+ context "when bundler itself is a transitive dependency" do
+ it "executes without error" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport", :group => :development
+ gem "rack"
+ G
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "activesupport", "3.0"
+ end
+ bundle "update --group development"
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}"
+ expect(the_bundle).not_to include_gems "rack 1.2"
+ end
+ end
+ end
+
+ describe "in a frozen bundle" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ gem "rack-obama"
+ gem "platform_specific"
+ G
+ end
+
+ it "should fail loudly", :bundler => "< 3" do
+ bundle "install --deployment"
+ bundle "update", :all => true, :raise_on_error => false
+
+ expect(last_command).to be_failure
+ expect(err).to match(/Bundler is unlocking, but the lockfile can't be updated because frozen mode is set/)
+ expect(err).to match(/freeze by running `bundle config set frozen false`./)
+ end
+
+ it "should fail loudly when frozen is set globally" do
+ bundle "config set --global frozen 1"
+ bundle "update", :all => true, :raise_on_error => false
+ expect(err).to match(/Bundler is unlocking, but the lockfile can't be updated because frozen mode is set/).
+ and match(/freeze by running `bundle config set frozen false`./)
+ end
+
+ it "should fail loudly when deployment is set globally" do
+ bundle "config set --global deployment true"
+ bundle "update", :all => true, :raise_on_error => false
+ expect(err).to match(/Bundler is unlocking, but the lockfile can't be updated because frozen mode is set/).
+ and match(/freeze by running `bundle config set frozen false`./)
+ end
+
+ it "should not suggest any command to unfreeze bundler if frozen is set through ENV" do
+ bundle "update", :all => true, :raise_on_error => false, :env => { "BUNDLE_FROZEN" => "true" }
+ expect(err).to match(/Bundler is unlocking, but the lockfile can't be updated because frozen mode is set/)
+ expect(err).not_to match(/by running/)
+ end
+ end
+
+ describe "with --source option" do
+ before do
+ build_repo2
+ end
+
+ it "should not update gems not included in the source that happen to have the same name" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ G
+ update_repo2 { build_gem "activesupport", "3.0" }
+
+ bundle "update --source activesupport"
+ expect(the_bundle).not_to include_gem "activesupport 3.0"
+ end
+
+ it "should not update gems not included in the source that happen to have the same name" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ G
+ update_repo2 { build_gem "activesupport", "3.0" }
+
+ bundle "update --source activesupport"
+ expect(the_bundle).not_to include_gems "activesupport 3.0"
+ end
+ end
+
+ context "when there is a child dependency that is also in the gemfile" do
+ before do
+ build_repo2 do
+ build_gem "fred", "1.0"
+ build_gem "harry", "1.0" do |s|
+ s.add_dependency "fred"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "harry"
+ gem "fred"
+ G
+ end
+
+ it "should not update the child dependencies of a gem that has the same name as the source" do
+ update_repo2 do
+ build_gem "fred", "2.0"
+ build_gem "harry", "2.0" do |s|
+ s.add_dependency "fred"
+ end
+ end
+
+ bundle "update --source harry"
+ expect(the_bundle).to include_gems "harry 1.0", "fred 1.0"
+ end
+ end
+
+ context "when there is a child dependency that appears elsewhere in the dependency graph" do
+ before do
+ build_repo2 do
+ build_gem "fred", "1.0" do |s|
+ s.add_dependency "george"
+ end
+ build_gem "george", "1.0"
+ build_gem "harry", "1.0" do |s|
+ s.add_dependency "george"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "harry"
+ gem "fred"
+ G
+ end
+
+ it "should not update the child dependencies of a gem that has the same name as the source" do
+ update_repo2 do
+ build_gem "george", "2.0"
+ build_gem "harry", "2.0" do |s|
+ s.add_dependency "george"
+ end
+ end
+
+ bundle "update --source harry"
+ expect(the_bundle).to include_gems "harry 1.0", "fred 1.0", "george 1.0"
+ end
+ end
+
+ it "shows the previous version of the gem when updated from rubygems source" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ G
+
+ bundle "update", :all => true, :verbose => true
+ expect(out).to include("Using activesupport 2.3.5")
+
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update", :all => true
+ expect(out).to include("Installing activesupport 3.0 (was 2.3.5)")
+ end
+
+ it "only prints `Using` for versions that have changed" do
+ build_repo4 do
+ build_gem "bar"
+ build_gem "foo"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "bar"
+ gem "foo"
+ G
+
+ bundle "update", :all => true
+ expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/)
+
+ update_repo4 do
+ build_gem "foo", "2.0"
+ end
+
+ bundle "update", :all => true
+ out.sub!("Removing foo (1.0)\n", "")
+ expect(out).to match(/Resolving dependencies\.\.\.\.*\nFetching foo 2\.0 \(was 1\.0\)\nInstalling foo 2\.0 \(was 1\.0\)\nBundle updated/)
+ end
+
+ it "shows error message when Gemfile.lock is not preset and gem is specified" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ G
+
+ bundle "update nonexisting", :raise_on_error => false
+ expect(err).to include("This Bundle hasn't been installed yet. Run `bundle install` to update and install the bundled gems.")
+ expect(exitstatus).to eq(22)
+ end
+
+ context "with multiple, duplicated sources, with lockfile in old format", :bundler => "< 3" do
+ before do
+ build_repo2 do
+ build_gem "dotenv", "2.7.6"
+
+ build_gem "oj", "3.11.3"
+ build_gem "oj", "3.11.5"
+
+ build_gem "vcr", "6.0.0"
+ end
+
+ build_repo gem_repo3 do
+ build_gem "pkg-gem-flowbyte-with-dep", "1.0.0" do |s|
+ s.add_dependency "oj"
+ end
+ end
+
+ gemfile <<~G
+ source "https://gem.repo2"
+
+ gem "dotenv"
+
+ source "https://gem.repo3" do
+ gem 'pkg-gem-flowbyte-with-dep'
+ end
+
+ gem "vcr",source: "https://gem.repo2"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo2/
+ remote: https://gem.repo3/
+ specs:
+ dotenv (2.7.6)
+ oj (3.11.3)
+ pkg-gem-flowbyte-with-dep (1.0.0)
+ oj
+ vcr (6.0.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ dotenv
+ pkg-gem-flowbyte-with-dep!
+ vcr!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "works" do
+ bundle :install, :artifice => "compact_index"
+ bundle "update oj", :artifice => "compact_index"
+
+ expect(out).to include("Bundle updated!")
+ expect(the_bundle).to include_gems "oj 3.11.5"
+ end
+ end
+end
+
+RSpec.describe "bundle update in more complicated situations" do
+ before do
+ build_repo2
+ end
+
+ it "will eagerly unlock dependencies of a specified gem" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "thin"
+ gem "rack-obama"
+ G
+
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "thin", "2.0" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ bundle "update thin"
+ expect(the_bundle).to include_gems "thin 2.0", "rack 1.2", "rack-obama 1.0"
+ end
+
+ it "will warn when some explicitly updated gems are not updated" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "thin"
+ gem "rack-obama"
+ G
+
+ update_repo2 do
+ build_gem("thin", "2.0") {|s| s.add_dependency "rack" }
+ build_gem "rack", "10.0"
+ end
+
+ bundle "update thin rack-obama"
+ expect(last_command.stdboth).to include "Bundler attempted to update rack-obama but its version stayed the same"
+ expect(the_bundle).to include_gems "thin 2.0", "rack 10.0", "rack-obama 1.0"
+ end
+
+ it "will not warn when an explicitly updated git gem changes sha but not version" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ update_git "foo" do |s|
+ s.write "lib/foo2.rb", "puts :foo2"
+ end
+
+ bundle "update foo"
+
+ expect(last_command.stdboth).not_to include "attempted to update"
+ end
+
+ it "will not warn when changing gem sources but not versions" do
+ build_git "rack"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack", :git => '#{lib_path("rack-1.0")}'
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "update rack"
+
+ expect(last_command.stdboth).not_to include "attempted to update"
+ end
+
+ it "will update only from pinned source" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ source "#{file_uri_for(gem_repo1)}" do
+ gem "thin"
+ end
+ G
+
+ update_repo2 do
+ build_gem "thin", "2.0"
+ end
+
+ bundle "update"
+ expect(the_bundle).to include_gems "thin 1.0"
+ end
+
+ context "when the lockfile is for a different platform" do
+ before do
+ build_repo4 do
+ build_gem("a", "0.9")
+ build_gem("a", "0.9") {|s| s.platform = "java" }
+ build_gem("a", "1.1")
+ build_gem("a", "1.1") {|s| s.platform = "java" }
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "a"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}
+ specs:
+ a (0.9-java)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ a
+ L
+
+ simulate_platform linux
+ end
+
+ it "allows updating" do
+ bundle :update, :all => true
+ expect(the_bundle).to include_gem "a 1.1"
+ end
+
+ it "allows updating a specific gem" do
+ bundle "update a"
+ expect(the_bundle).to include_gem "a 1.1"
+ end
+ end
+
+ context "when the dependency is for a different platform" do
+ before do
+ build_repo4 do
+ build_gem("a", "0.9") {|s| s.platform = "java" }
+ build_gem("a", "1.1") {|s| s.platform = "java" }
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "a", platform: :jruby
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}
+ specs:
+ a (0.9-java)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ a
+ L
+
+ simulate_platform linux
+ end
+
+ it "is not updated because it is not actually included in the bundle" do
+ bundle "update a"
+ expect(last_command.stdboth).to include "Bundler attempted to update a but it was not considered because it is for a different platform from the current one"
+ expect(the_bundle).to_not include_gem "a"
+ end
+ end
+end
+
+RSpec.describe "bundle update without a Gemfile.lock" do
+ it "should not explode" do
+ build_repo2
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rack", "1.0"
+ G
+
+ bundle "update", :all => true
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+end
+
+RSpec.describe "bundle update when a gem depends on a newer version of bundler" do
+ before do
+ build_repo2 do
+ build_gem "rails", "3.0.1" do |s|
+ s.add_dependency "bundler", Bundler::VERSION.succ
+ end
+
+ build_gem "bundler", Bundler::VERSION.succ
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails", "3.0.1"
+ G
+ end
+
+ it "should explain that bundler conflicted and how to resolve the conflict" do
+ bundle "update", :all => true, :raise_on_error => false
+ expect(last_command.stdboth).not_to match(/in snapshot/i)
+ expect(err).to match(/current Bundler version/i).
+ and match(/Install the necessary version with `gem install bundler:#{Bundler::VERSION.succ}`/i)
+ end
+end
+
+RSpec.describe "bundle update --ruby" do
+ context "when the Gemfile removes the ruby" do
+ before do
+ install_gemfile <<-G
+ ruby '~> #{Gem.ruby_version}'
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ it "removes the Ruby from the Gemfile.lock" do
+ bundle "update --ruby"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when the Gemfile specified an updated Ruby version" do
+ before do
+ install_gemfile <<-G
+ ruby '~> #{Gem.ruby_version}'
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ gemfile <<-G
+ ruby '~> #{current_ruby_minor}'
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ it "updates the Gemfile.lock with the latest version" do
+ bundle "update --ruby"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when a different Ruby is being used than has been versioned" do
+ before do
+ install_gemfile <<-G
+ ruby '~> #{Gem.ruby_version}'
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ gemfile <<-G
+ ruby '~> 2.1.0'
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ it "shows a helpful error message" do
+ bundle "update --ruby", :raise_on_error => false
+
+ expect(err).to include("Your Ruby version is #{Bundler::RubyVersion.system.gem_version}, but your Gemfile specified ~> 2.1.0")
+ end
+ end
+
+ context "when updating Ruby version and Gemfile `ruby`" do
+ before do
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ ruby 2.1.4p222
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile <<-G
+ ruby '~> #{Gem.ruby_version}'
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+ it "updates the Gemfile.lock with the latest version" do
+ bundle "update --ruby"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+end
+
+RSpec.describe "bundle update --bundler" do
+ it "updates the bundler version in the lockfile" do
+ build_repo4 do
+ build_gem "rack", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "rack"
+ G
+ lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2')
+
+ bundle :update, :bundler => true, :artifice => "compact_index", :verbose => true
+ expect(out).to include("Using bundler #{Bundler::VERSION}")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ rack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ expect(the_bundle).to include_gem "rack 1.0"
+ end
+
+ it "updates the bundler version in the lockfile without re-resolving if the highest version is already installed" do
+ system_gems "bundler-2.3.9"
+
+ build_repo4 do
+ build_gem "rack", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "rack"
+ G
+ lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9")
+
+ bundle :update, :bundler => true, :artifice => "compact_index", :verbose => true
+ expect(out).to include("Using bundler #{Bundler::VERSION}")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ rack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ expect(the_bundle).to include_gem "rack 1.0"
+ end
+
+ it "updates the bundler version in the lockfile even if the latest version is not installed", :ruby_repo, :realworld do
+ pristine_system_gems "bundler-2.3.9"
+
+ build_repo4 do
+ build_gem "rack", "1.0"
+ end
+
+ install_gemfile <<-G, :env => { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" }
+ source "#{file_uri_for(gem_repo4)}"
+ gem "rack"
+ G
+ lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9")
+
+ bundle :update, :bundler => true, :artifice => "vcr", :verbose => true, :env => { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" }
+
+ # Only updates properly on modern RubyGems.
+
+ if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev")
+ expect(out).to include("Updating bundler to 2.3.10")
+ expect(out).to include("Using bundler 2.3.10")
+ expect(out).not_to include("Installing Bundler 2.3.9 and restarting using that version.")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ rack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 2.3.10
+ L
+
+ expect(the_bundle).to include_gems "bundler 2.3.10"
+ end
+
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "errors if the explicit target version does not exist", :realworld do
+ pristine_system_gems "bundler-2.3.9"
+
+ build_repo4 do
+ build_gem "rack", "1.0"
+ end
+
+ install_gemfile <<-G, :env => { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" }
+ source "#{file_uri_for(gem_repo4)}"
+ gem "rack"
+ G
+ lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9")
+
+ bundle :update, :bundler => "999.999.999", :artifice => "vcr", :raise_on_error => false
+
+ # Only gives a meaningful error message on modern RubyGems.
+
+ if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev")
+ expect(last_command).to be_failure
+ expect(err).to include("The `bundle update --bundler` target version (999.999.999) does not exist")
+ end
+ end
+
+ it "allows updating to development versions if already installed locally" do
+ system_gems "bundler-2.3.0.dev"
+
+ build_repo4 do
+ build_gem "rack", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "rack"
+ G
+
+ bundle :update, :bundler => "2.3.0.dev", :verbose => "true"
+
+ # Only updates properly on modern RubyGems.
+
+ if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev")
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ rack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 2.3.0.dev
+ L
+
+ expect(out).to include("Using bundler 2.3.0.dev")
+ end
+ end
+
+ it "does not touch the network if not necessary" do
+ system_gems "bundler-2.3.9"
+
+ build_repo4 do
+ build_gem "rack", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "rack"
+ G
+
+ bundle :update, :bundler => "2.3.9", :raise_on_error => false, :verbose => true
+
+ expect(out).not_to include("Fetching gem metadata from https://rubygems.org/")
+
+ # Only updates properly on modern RubyGems.
+
+ if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev")
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ rack (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 2.3.9
+ L
+
+ expect(out).to include("Using bundler 2.3.9")
+ end
+ end
+
+ it "prints an error when trying to update bundler in frozen mode" do
+ system_gems "bundler-2.3.9"
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo2)}"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+
+ BUNDLED WITH
+ 2.1.4
+ L
+
+ bundle "update --bundler=2.3.9", :env => { "BUNDLE_FROZEN" => "true" }, :raise_on_error => false
+ expect(err).to include("An update to the version of bundler itself was requested, but the lockfile can't be updated because frozen mode is set")
+ end
+end
+
+# these specs are slow and focus on integration and therefore are not exhaustive. unit specs elsewhere handle that.
+RSpec.describe "bundle update conservative" do
+ context "patch and minor options" do
+ before do
+ build_repo4 do
+ build_gem "foo", %w[1.4.3 1.4.4] do |s|
+ s.add_dependency "bar", "~> 2.0"
+ end
+ build_gem "foo", %w[1.4.5 1.5.0] do |s|
+ s.add_dependency "bar", "~> 2.1"
+ end
+ build_gem "foo", %w[1.5.1] do |s|
+ s.add_dependency "bar", "~> 3.0"
+ end
+ build_gem "foo", %w[2.0.0.pre] do |s|
+ s.add_dependency "bar"
+ end
+ build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 2.1.2.pre 3.0.0 3.1.0.pre 4.0.0.pre]
+ build_gem "qux", %w[1.0.0 1.0.1 1.1.0 2.0.0]
+ end
+
+ # establish a lockfile set to 1.4.3
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'foo', '1.4.3'
+ gem 'bar', '2.0.3'
+ gem 'qux', '1.0.0'
+ G
+
+ # remove 1.4.3 requirement and bar altogether
+ # to setup update specs below
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'foo'
+ gem 'qux'
+ G
+ end
+
+ context "with patch set as default update level in config" do
+ it "should do a patch level update" do
+ bundle "config set --local prefer_patch true"
+ bundle "update foo"
+
+ expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.0"
+ end
+ end
+
+ context "patch preferred" do
+ it "single gem updates dependent gem to minor" do
+ bundle "update --patch foo"
+
+ expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.0"
+ end
+
+ it "update all" do
+ bundle "update --patch", :all => true
+
+ expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.1"
+ end
+ end
+
+ context "minor preferred" do
+ it "single gem updates dependent gem to major" do
+ bundle "update --minor foo"
+
+ expect(the_bundle).to include_gems "foo 1.5.1", "bar 3.0.0", "qux 1.0.0"
+ end
+ end
+
+ context "strict" do
+ it "patch preferred" do
+ bundle "update --patch foo bar --strict"
+
+ expect(the_bundle).to include_gems "foo 1.4.4", "bar 2.0.5", "qux 1.0.0"
+ end
+
+ it "minor preferred" do
+ bundle "update --minor --strict", :all => true
+
+ expect(the_bundle).to include_gems "foo 1.5.0", "bar 2.1.1", "qux 1.1.0"
+ end
+ end
+
+ context "pre" do
+ it "defaults to major" do
+ bundle "update --pre foo bar"
+
+ expect(the_bundle).to include_gems "foo 2.0.0.pre", "bar 4.0.0.pre", "qux 1.0.0"
+ end
+
+ it "patch preferred" do
+ bundle "update --patch --pre foo bar"
+
+ expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.2.pre", "qux 1.0.0"
+ end
+
+ it "minor preferred" do
+ bundle "update --minor --pre foo bar"
+
+ expect(the_bundle).to include_gems "foo 1.5.1", "bar 3.1.0.pre", "qux 1.0.0"
+ end
+
+ it "major preferred" do
+ bundle "update --major --pre foo bar"
+
+ expect(the_bundle).to include_gems "foo 2.0.0.pre", "bar 4.0.0.pre", "qux 1.0.0"
+ end
+ end
+ end
+
+ context "eager unlocking" do
+ before do
+ build_repo4 do
+ build_gem "isolated_owner", %w[1.0.1 1.0.2] do |s|
+ s.add_dependency "isolated_dep", "~> 2.0"
+ end
+ build_gem "isolated_dep", %w[2.0.1 2.0.2]
+
+ build_gem "shared_owner_a", %w[3.0.1 3.0.2] do |s|
+ s.add_dependency "shared_dep", "~> 5.0"
+ end
+ build_gem "shared_owner_b", %w[4.0.1 4.0.2] do |s|
+ s.add_dependency "shared_dep", "~> 5.0"
+ end
+ build_gem "shared_dep", %w[5.0.1 5.0.2]
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'isolated_owner'
+
+ gem 'shared_owner_a'
+ gem 'shared_owner_b'
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ isolated_dep (2.0.1)
+ isolated_owner (1.0.1)
+ isolated_dep (~> 2.0)
+ shared_dep (5.0.1)
+ shared_owner_a (3.0.1)
+ shared_dep (~> 5.0)
+ shared_owner_b (4.0.1)
+ shared_dep (~> 5.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ isolated_owner
+ shared_owner_a
+ shared_owner_b
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "should eagerly unlock isolated dependency" do
+ bundle "update isolated_owner"
+
+ expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.1", "shared_owner_b 4.0.1"
+ end
+
+ it "should eagerly unlock shared dependency" do
+ bundle "update shared_owner_a"
+
+ expect(the_bundle).to include_gems "isolated_owner 1.0.1", "isolated_dep 2.0.1", "shared_dep 5.0.2", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1"
+ end
+
+ it "should not eagerly unlock with --conservative" do
+ bundle "update --conservative shared_owner_a isolated_owner"
+
+ expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.1", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1"
+ end
+
+ it "should only update direct dependencies when fully updating with --conservative" do
+ bundle "update --conservative"
+
+ expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.1", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.2"
+ end
+
+ it "should only change direct dependencies when updating the lockfile with --conservative" do
+ bundle "lock --update --conservative"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ isolated_dep (2.0.1)
+ isolated_owner (1.0.2)
+ isolated_dep (~> 2.0)
+ shared_dep (5.0.1)
+ shared_owner_a (3.0.2)
+ shared_dep (~> 5.0)
+ shared_owner_b (4.0.2)
+ shared_dep (~> 5.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ isolated_owner
+ shared_owner_a
+ shared_owner_b
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "should match bundle install conservative update behavior when not eagerly unlocking" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'isolated_owner', '1.0.2'
+
+ gem 'shared_owner_a', '3.0.2'
+ gem 'shared_owner_b'
+ G
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1"
+ end
+ end
+
+ context "error handling" do
+ before do
+ gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ end
+
+ it "raises if too many flags are provided" do
+ bundle "update --patch --minor", :all => true, :raise_on_error => false
+
+ expect(err).to eq "Provide only one of the following options: minor, patch"
+ end
+ end
+end
diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb
new file mode 100644
index 0000000000..53d545f5fa
--- /dev/null
+++ b/spec/bundler/commands/version_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require_relative "../support/path"
+
+RSpec.describe "bundle version" do
+ if Spec::Path.ruby_core?
+ COMMIT_HASH = /unknown|[a-fA-F0-9]{7,}/.freeze
+ else
+ COMMIT_HASH = /[a-fA-F0-9]{7,}/.freeze
+ end
+
+ context "with -v" do
+ it "outputs the version", :bundler => "< 3" do
+ bundle "-v"
+ expect(out).to eq("Bundler version #{Bundler::VERSION}")
+ end
+
+ it "outputs the version", :bundler => "3" do
+ bundle "-v"
+ expect(out).to eq(Bundler::VERSION)
+ end
+ end
+
+ context "with --version" do
+ it "outputs the version", :bundler => "< 3" do
+ bundle "--version"
+ expect(out).to eq("Bundler version #{Bundler::VERSION}")
+ end
+
+ it "outputs the version", :bundler => "3" do
+ bundle "--version"
+ expect(out).to eq(Bundler::VERSION)
+ end
+ end
+
+ context "with version" do
+ it "outputs the version with build metadata", :bundler => "< 3" do
+ bundle "version"
+ expect(out).to match(/\ABundler version #{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/)
+ end
+
+ it "outputs the version with build metadata", :bundler => "3" do
+ bundle "version"
+ expect(out).to match(/\A#{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/)
+ end
+ end
+end
diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb
new file mode 100644
index 0000000000..cf612397ab
--- /dev/null
+++ b/spec/bundler/commands/viz_spec.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle viz", :bundler => "< 3", :if => Bundler.which("dot"), :realworld => true do
+ before do
+ realworld_system_gems "ruby-graphviz --version 1.2.5"
+ end
+
+ it "graphs gems from the Gemfile" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ bundle "viz"
+ expect(out).to include("gem_graph.png")
+
+ bundle "viz", :format => "debug"
+ expect(out).to eq(strip_whitespace(<<-DOT).strip)
+ digraph Gemfile {
+ concentrate = "true";
+ normalize = "true";
+ nodesep = "0.55";
+ edge[ weight = "2"];
+ node[ fontname = "Arial, Helvetica, SansSerif"];
+ edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"];
+ default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"];
+ rack [style = "filled", fillcolor = "#B9B9D5", label = "rack"];
+ default -> rack [constraint = "false"];
+ "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama"];
+ default -> "rack-obama" [constraint = "false"];
+ "rack-obama" -> rack;
+ }
+ debugging bundle viz...
+ DOT
+ end
+
+ it "graphs gems that are prereleases" do
+ build_repo2 do
+ build_gem "rack", "1.3.pre"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack", "= 1.3.pre"
+ gem "rack-obama"
+ G
+
+ bundle "viz"
+ expect(out).to include("gem_graph.png")
+
+ bundle "viz", :format => :debug, :version => true
+ expect(out).to eq(strip_whitespace(<<-EOS).strip)
+ digraph Gemfile {
+ concentrate = "true";
+ normalize = "true";
+ nodesep = "0.55";
+ edge[ weight = "2"];
+ node[ fontname = "Arial, Helvetica, SansSerif"];
+ edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"];
+ default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"];
+ rack [style = "filled", fillcolor = "#B9B9D5", label = "rack\\n1.3.pre"];
+ default -> rack [constraint = "false"];
+ "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama\\n1.0"];
+ default -> "rack-obama" [constraint = "false"];
+ "rack-obama" -> rack;
+ }
+ debugging bundle viz...
+ EOS
+ end
+
+ context "with another gem that has a graphviz file" do
+ before do
+ build_repo4 do
+ build_gem "graphviz", "999" do |s|
+ s.write("lib/graphviz.rb", "abort 'wrong graphviz gem loaded'")
+ end
+ end
+
+ system_gems "graphviz-999", :gem_repo => gem_repo4
+ end
+
+ it "loads the correct ruby-graphviz gem" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ bundle "viz", :format => "debug"
+ expect(out).to eq(strip_whitespace(<<-DOT).strip)
+ digraph Gemfile {
+ concentrate = "true";
+ normalize = "true";
+ nodesep = "0.55";
+ edge[ weight = "2"];
+ node[ fontname = "Arial, Helvetica, SansSerif"];
+ edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"];
+ default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"];
+ rack [style = "filled", fillcolor = "#B9B9D5", label = "rack"];
+ default -> rack [constraint = "false"];
+ "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama"];
+ default -> "rack-obama" [constraint = "false"];
+ "rack-obama" -> rack;
+ }
+ debugging bundle viz...
+ DOT
+ end
+ end
+
+ context "--without option" do
+ it "one group" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+
+ group :rails do
+ gem "rails"
+ end
+ G
+
+ bundle "viz --without=rails"
+ expect(out).to include("gem_graph.png")
+ end
+
+ it "two groups" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+
+ group :rack do
+ gem "rack"
+ end
+
+ group :rails do
+ gem "rails"
+ end
+ G
+
+ bundle "viz --without=rails:rack"
+ expect(out).to include("gem_graph.png")
+ end
+ end
+end
diff --git a/spec/bundler/install/allow_offline_install_spec.rb b/spec/bundler/install/allow_offline_install_spec.rb
new file mode 100644
index 0000000000..4c6c77a61e
--- /dev/null
+++ b/spec/bundler/install/allow_offline_install_spec.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with :allow_offline_install" do
+ before do
+ bundle "config set allow_offline_install true"
+ end
+
+ context "with no cached data locally" do
+ it "still installs" do
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "http://testgemserver.local"
+ gem "rack-obama"
+ G
+ expect(the_bundle).to include_gem("rack 1.0")
+ end
+
+ it "still fails when the network is down" do
+ install_gemfile <<-G, :artifice => "fail", :raise_on_error => false
+ source "http://testgemserver.local"
+ gem "rack-obama"
+ G
+ expect(err).to include("Could not reach host testgemserver.local.")
+ expect(the_bundle).to_not be_locked
+ end
+ end
+
+ context "with cached data locally" do
+ it "will install from the compact index" do
+ system_gems ["rack-1.0.0"], :path => default_bundle_path
+
+ bundle "config set clean false"
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "http://testgemserver.local"
+ gem "rack-obama"
+ gem "rack", "< 1.0"
+ G
+
+ expect(the_bundle).to include_gems("rack-obama 1.0", "rack 0.9.1")
+
+ gemfile <<-G
+ source "http://testgemserver.local"
+ gem "rack-obama"
+ G
+
+ bundle :update, :artifice => "fail", :all => true
+ expect(last_command.stdboth).to include "Using the cached data for the new index because of a network error"
+
+ expect(the_bundle).to include_gems("rack-obama 1.0", "rack 1.0.0")
+ end
+
+ def break_git_remote_ops!
+ FileUtils.mkdir_p(tmp("broken_path"))
+ File.open(tmp("broken_path/git"), "w", 0o755) do |f|
+ f.puts strip_whitespace(<<-RUBY)
+ #!/usr/bin/env ruby
+ fetch_args = %w(fetch --force --quiet)
+ clone_args = %w(clone --bare --no-hardlinks --quiet)
+
+ if (fetch_args.-(ARGV).empty? || clone_args.-(ARGV).empty?) && ARGV.any? {|arg| arg.start_with?("file://") }
+ warn "git remote ops have been disabled"
+ exit 1
+ end
+ ENV["PATH"] = ENV["PATH"].sub(/^.*?:/, "")
+ exec("git", *ARGV)
+ RUBY
+ end
+
+ old_path = ENV["PATH"]
+ ENV["PATH"] = "#{tmp("broken_path")}:#{ENV["PATH"]}"
+ yield if block_given?
+ ensure
+ ENV["PATH"] = old_path if block_given?
+ end
+
+ it "will install from a cached git repo" do
+ skip "doesn't print errors" if Gem.win_platform?
+
+ git = build_git "a", "1.0.0", :path => lib_path("a")
+ update_git("a", :path => git.path, :branch => "new_branch")
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "a", :git => #{git.path.to_s.dump}
+ G
+
+ break_git_remote_ops! { bundle :update, :all => true }
+ expect(err).to include("Using cached git data because of network errors")
+ expect(the_bundle).to be_locked
+
+ break_git_remote_ops! do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "a", :git => #{git.path.to_s.dump}, :branch => "new_branch"
+ G
+ end
+ expect(err).to include("Using cached git data because of network errors")
+ expect(the_bundle).to be_locked
+ end
+ end
+end
diff --git a/spec/bundler/install/binstubs_spec.rb b/spec/bundler/install/binstubs_spec.rb
new file mode 100644
index 0000000000..928ba80b15
--- /dev/null
+++ b/spec/bundler/install/binstubs_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ describe "when system_bindir is set" do
+ it "overrides Gem.bindir" do
+ expect(Pathname.new("/usr/bin")).not_to be_writable
+ gemfile <<-G
+ def Gem.bindir; "/usr/bin"; end
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ config "BUNDLE_SYSTEM_BINDIR" => system_gem_path("altbin").to_s
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(system_gem_path("altbin/rackup")).to exist
+ end
+ end
+
+ describe "when multiple gems contain the same exe" do
+ before do
+ build_repo2 do
+ build_gem "fake", "14" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "fake"
+ gem "rack"
+ G
+ end
+
+ it "warns about the situation" do
+ bundle "exec rackup"
+
+ expect(last_command.stderr).to include(
+ "The `rackup` executable in the `fake` gem is being loaded, but it's also present in other gems (rack).\n" \
+ "If you meant to run the executable for another gem, make sure you use a project specific binstub (`bundle binstub <gem_name>`).\n" \
+ "If you plan to use multiple conflicting executables, generate binstubs for them and disambiguate their names."
+ ).or include(
+ "The `rackup` executable in the `rack` gem is being loaded, but it's also present in other gems (fake).\n" \
+ "If you meant to run the executable for another gem, make sure you use a project specific binstub (`bundle binstub <gem_name>`).\n" \
+ "If you plan to use multiple conflicting executables, generate binstubs for them and disambiguate their names."
+ )
+ end
+ end
+end
diff --git a/spec/bundler/install/bundler_spec.rb b/spec/bundler/install/bundler_spec.rb
new file mode 100644
index 0000000000..e7ec3bc7e7
--- /dev/null
+++ b/spec/bundler/install/bundler_spec.rb
@@ -0,0 +1,268 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ describe "with bundler dependencies" do
+ before(:each) do
+ build_repo2 do
+ build_gem "rails", "3.0" do |s|
+ s.add_dependency "bundler", ">= 0.9.0.pre"
+ end
+ build_gem "bundler", "0.9.1"
+ build_gem "bundler", Bundler::VERSION
+ end
+ end
+
+ it "are forced to the current bundler version" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails", "3.0"
+ G
+
+ expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}"
+ end
+
+ it "are forced to the current bundler version even if not already present" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}"
+ end
+
+ it "causes a conflict if explicitly requesting a different version of bundler" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails", "3.0"
+ gem "bundler", "0.9.1"
+ G
+
+ nice_error = <<-E.strip.gsub(/^ {8}/, "")
+ Could not find compatible versions
+
+ Because the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler = 0.9.1
+ and Gemfile depends on bundler = 0.9.1,
+ version solving has failed.
+
+ Your bundle requires a different version of Bundler than the one you're running.
+ Install the necessary version with `gem install bundler:0.9.1` and rerun bundler using `bundle _0.9.1_ install`
+ E
+ expect(err).to include(nice_error)
+ end
+
+ it "causes a conflict if explicitly requesting a non matching requirement on bundler" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails", "3.0"
+ gem "bundler", "~> 0.8"
+ G
+
+ nice_error = <<-E.strip.gsub(/^ {8}/, "")
+ Could not find compatible versions
+
+ Because rails >= 3.0 depends on bundler >= 0.9.0.pre
+ and the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler >= 0.9.0.pre, < 1.A,
+ rails >= 3.0 requires bundler >= 1.A.
+ So, because Gemfile depends on rails = 3.0
+ and Gemfile depends on bundler ~> 0.8,
+ version solving has failed.
+
+ Your bundle requires a different version of Bundler than the one you're running.
+ Install the necessary version with `gem install bundler:0.9.1` and rerun bundler using `bundle _0.9.1_ install`
+ E
+ expect(err).to include(nice_error)
+ end
+
+ it "causes a conflict if explicitly requesting a version of bundler that doesn't exist" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails", "3.0"
+ gem "bundler", "0.9.2"
+ G
+
+ nice_error = <<-E.strip.gsub(/^ {8}/, "")
+ Could not find compatible versions
+
+ Because the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler = 0.9.2
+ and Gemfile depends on bundler = 0.9.2,
+ version solving has failed.
+
+ Your bundle requires a different version of Bundler than the one you're running, and that version could not be found.
+ E
+ expect(err).to include(nice_error)
+ end
+
+ it "works for gems with multiple versions in its dependencies" do
+ build_repo2 do
+ build_gem "multiple_versioned_deps" do |s|
+ s.add_dependency "weakling", ">= 0.0.1", "< 0.1"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "multiple_versioned_deps"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "multiple_versioned_deps"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "multiple_versioned_deps 1.0.0"
+ end
+
+ it "includes bundler in the bundle when it's a child dependency" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails", "3.0"
+ G
+
+ run "begin; gem 'bundler'; puts 'WIN'; rescue Gem::LoadError; puts 'FAIL'; end"
+ expect(out).to eq("WIN")
+ end
+
+ it "allows gem 'bundler' when Bundler is not in the Gemfile or its dependencies" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ G
+
+ run "begin; gem 'bundler'; puts 'WIN'; rescue Gem::LoadError => e; puts e.backtrace; end"
+ expect(out).to eq("WIN")
+ end
+
+ it "causes a conflict if child dependencies conflict" do
+ bundle "config set force_ruby_platform true"
+
+ update_repo2 do
+ build_gem "rails_pinned_to_old_activesupport" do |s|
+ s.add_dependency "activesupport", "= 1.2.3"
+ end
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activemerchant"
+ gem "rails_pinned_to_old_activesupport"
+ G
+
+ nice_error = <<-E.strip.gsub(/^ {8}/, "")
+ Could not find compatible versions
+
+ Because every version of rails_pinned_to_old_activesupport depends on activesupport = 1.2.3
+ and every version of activemerchant depends on activesupport >= 2.0.0,
+ every version of rails_pinned_to_old_activesupport is incompatible with activemerchant >= 0.
+ So, because Gemfile depends on activemerchant >= 0
+ and Gemfile depends on rails_pinned_to_old_activesupport >= 0,
+ version solving has failed.
+ E
+ expect(err).to include(nice_error)
+ end
+
+ it "causes a conflict if a child dependency conflicts with the Gemfile" do
+ bundle "config set force_ruby_platform true"
+
+ update_repo2 do
+ build_gem "rails_pinned_to_old_activesupport" do |s|
+ s.add_dependency "activesupport", "= 1.2.3"
+ end
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails_pinned_to_old_activesupport"
+ gem "activesupport", "2.3.5"
+ G
+
+ nice_error = <<-E.strip.gsub(/^ {8}/, "")
+ Could not find compatible versions
+
+ Because every version of rails_pinned_to_old_activesupport depends on activesupport = 1.2.3
+ and Gemfile depends on rails_pinned_to_old_activesupport >= 0,
+ activesupport = 1.2.3 is required.
+ So, because Gemfile depends on activesupport = 2.3.5,
+ version solving has failed.
+ E
+ expect(err).to include(nice_error)
+ end
+
+ it "does not cause a conflict if new dependencies in the Gemfile require older dependencies than the lockfile" do
+ update_repo2 do
+ build_gem "rails_pinned_to_old_activesupport" do |s|
+ s.add_dependency "activesupport", "= 1.2.3"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rails', "2.3.2"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails_pinned_to_old_activesupport"
+ G
+
+ expect(out).to include("Installing activesupport 1.2.3 (was 2.3.2)")
+ expect(err).to be_empty
+ end
+
+ it "prints the previous version when switching to a previously downloaded gem" do
+ build_repo4 do
+ build_gem "rails", "7.0.3"
+ build_gem "rails", "7.0.4"
+ end
+
+ bundle "config set path.system true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'rails', "7.0.4"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'rails', "7.0.3"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'rails', "7.0.4"
+ G
+
+ expect(out).to include("Using rails 7.0.4 (was 7.0.3)")
+ expect(err).to be_empty
+ end
+
+ it "can install dependencies with newer bundler version with system gems" do
+ bundle "config set path.system true"
+
+ system_gems "bundler-99999999.99.1"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails", "3.0"
+ G
+
+ bundle "check"
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "can install dependencies with newer bundler version with a local path" do
+ bundle "config set path .bundle"
+
+ system_gems "bundler-99999999.99.1"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails", "3.0"
+ G
+
+ bundle "check"
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+ end
+end
diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb
new file mode 100644
index 0000000000..2a88ed5b06
--- /dev/null
+++ b/spec/bundler/install/deploy_spec.rb
@@ -0,0 +1,550 @@
+# frozen_string_literal: true
+
+RSpec.describe "install in deployment or frozen mode" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ context "with CLI flags", :bundler => "< 3" do
+ it "fails without a lockfile and says that --deployment requires a lock" do
+ bundle "install --deployment", :raise_on_error => false
+ expect(err).to include("The --deployment flag requires a Gemfile.lock")
+ end
+
+ it "fails without a lockfile and says that --frozen requires a lock" do
+ bundle "install --frozen", :raise_on_error => false
+ expect(err).to include("The --frozen flag requires a Gemfile.lock")
+ end
+
+ it "disallows --deployment --system" do
+ bundle "install --deployment --system", :raise_on_error => false
+ expect(err).to include("You have specified both --deployment")
+ expect(err).to include("Please choose only one option")
+ expect(exitstatus).to eq(15)
+ end
+
+ it "disallows --deployment --path --system" do
+ bundle "install --deployment --path . --system", :raise_on_error => false
+ expect(err).to include("You have specified both --path")
+ expect(err).to include("as well as --system")
+ expect(err).to include("Please choose only one option")
+ expect(exitstatus).to eq(15)
+ end
+
+ it "doesn't mess up a subsequent `bundle install` after you try to deploy without a lock" do
+ bundle "install --deployment", :raise_on_error => false
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "installs gems by default to vendor/bundle" do
+ bundle :lock
+ bundle "install --deployment"
+ expect(out).to include("vendor/bundle")
+ end
+
+ it "installs gems to custom path if specified" do
+ bundle :lock
+ bundle "install --path vendor/bundle2 --deployment"
+ expect(out).to include("vendor/bundle2")
+ end
+
+ it "works with the --frozen flag" do
+ bundle :lock
+ bundle "install --frozen"
+ end
+
+ it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do
+ bundle :lock
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ bundle "install --deployment", :raise_on_error => false
+ expect(err).to include("frozen mode")
+ expect(err).to include("You have added to the Gemfile")
+ expect(err).to include("* rack-obama")
+ expect(err).not_to include("You have deleted from the Gemfile")
+ expect(err).not_to include("You have changed in the Gemfile")
+ end
+ end
+
+ it "still works if you are not in the app directory and specify --gemfile" do
+ bundle "install"
+ simulate_new_machine
+ bundle "config set --local deployment true"
+ bundle "config set --local path vendor/bundle"
+ bundle "install --gemfile #{tmp}/bundled_app/Gemfile", :dir => tmp
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "works if you exclude a group with a git gem" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ group :test do
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ end
+ G
+ bundle :install
+ bundle "config set --local deployment true"
+ bundle "config set --local without test"
+ bundle :install
+ end
+
+ it "works when you bundle exec bundle" do
+ skip "doesn't find bundle" if Gem.win_platform?
+
+ bundle :install
+ bundle "config set --local deployment true"
+ bundle :install
+ bundle "exec bundle check", :env => { "PATH" => path }
+ end
+
+ it "works when using path gems from the same path and the version is specified" do
+ build_lib "foo", :path => lib_path("nested/foo")
+ build_lib "bar", :path => lib_path("nested/bar")
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", "1.0", :path => "#{lib_path("nested")}"
+ gem "bar", :path => "#{lib_path("nested")}"
+ G
+
+ bundle :install
+ bundle "config set --local deployment true"
+ bundle :install
+ end
+
+ it "works when path gems are specified twice" do
+ build_lib "foo", :path => lib_path("nested/foo")
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("nested/foo")}"
+ gem "foo", :path => "#{lib_path("nested/foo")}"
+ G
+
+ bundle :install
+ bundle "config set --local deployment true"
+ bundle :install
+ end
+
+ it "works when there are credentials in the source URL" do
+ install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true)
+ source "http://user:pass@localgemserver.test/"
+
+ gem "rack-obama", ">= 1.0"
+ G
+
+ bundle "config set --local deployment true"
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+ end
+
+ it "works with sources given by a block" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ source "#{file_uri_for(gem_repo1)}" do
+ gem "rack"
+ end
+ G
+
+ bundle "config set --local deployment true"
+ bundle :install
+
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ context "when replacing a host with the same host with credentials" do
+ before do
+ bundle "config set --local path vendor/bundle"
+ bundle "install"
+ gemfile <<-G
+ source "http://user_name:password@localgemserver.test/"
+ gem "rack"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: http://localgemserver.test/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+ G
+
+ bundle "config set --local deployment true"
+ end
+
+ it "prevents the replace by default" do
+ bundle :install, :raise_on_error => false
+
+ expect(err).to match(/The list of sources changed/)
+ end
+
+ context "when allow_deployment_source_credential_changes is true" do
+ before { bundle "config set allow_deployment_source_credential_changes true" }
+
+ it "allows the replace" do
+ bundle :install
+
+ expect(out).to match(/Bundle complete!/)
+ end
+ end
+
+ context "when allow_deployment_source_credential_changes is false" do
+ before { bundle "config set allow_deployment_source_credential_changes false" }
+
+ it "prevents the replace" do
+ bundle :install, :raise_on_error => false
+
+ expect(err).to match(/The list of sources changed/)
+ end
+ end
+
+ context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is true" do
+ before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "true" }
+
+ it "allows the replace" do
+ bundle :install
+
+ expect(out).to match(/Bundle complete!/)
+ end
+ end
+
+ context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is false" do
+ before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "false" }
+
+ it "prevents the replace" do
+ bundle :install, :raise_on_error => false
+
+ expect(err).to match(/The list of sources changed/)
+ end
+ end
+ end
+
+ describe "with an existing lockfile" do
+ before do
+ bundle "install"
+ end
+
+ it "installs gems by default to vendor/bundle" do
+ bundle "config set deployment true"
+ bundle "install"
+ expect(out).to include("vendor/bundle")
+ end
+
+ it "installs gems to custom path if specified" do
+ bundle "config set path vendor/bundle2"
+ bundle "config set deployment true"
+ bundle "install"
+ expect(out).to include("vendor/bundle2")
+ end
+
+ it "installs gems to custom path if specified, even when configured through ENV" do
+ bundle "config set deployment true"
+ bundle "install", :env => { "BUNDLE_PATH" => "vendor/bundle2" }
+ expect(out).to include("vendor/bundle2")
+ end
+
+ it "works with the `frozen` setting" do
+ bundle "config set frozen true"
+ bundle "install"
+ end
+
+ it "works with BUNDLE_FROZEN if you didn't change anything" do
+ bundle :install, :env => { "BUNDLE_FROZEN" => "true" }
+ end
+
+ it "explodes with the `deployment` setting if you make a change and don't check in the lockfile" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ bundle "config set --local deployment true"
+ bundle :install, :raise_on_error => false
+ expect(err).to include("frozen mode")
+ expect(err).to include("You have added to the Gemfile")
+ expect(err).to include("* rack-obama")
+ expect(err).not_to include("You have deleted from the Gemfile")
+ expect(err).not_to include("You have changed in the Gemfile")
+ end
+
+ it "works if a path gem is missing but is in a without group" do
+ build_lib "path_gem"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rake"
+ gem "path_gem", :path => "#{lib_path("path_gem-1.0")}", :group => :development
+ G
+ expect(the_bundle).to include_gems "path_gem 1.0"
+ FileUtils.rm_r lib_path("path_gem-1.0")
+
+ bundle "config set --local path .bundle"
+ bundle "config set --local without development"
+ bundle "config set --local deployment true"
+ bundle :install, :env => { "DEBUG" => "1" }
+ run "puts :WIN"
+ expect(out).to eq("WIN")
+ end
+
+ it "works if a gem is missing, but it's on a different platform" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ source "#{file_uri_for(gem_repo1)}" do
+ gem "rake", platform: :#{not_local_tag}
+ end
+ G
+
+ bundle :install, :env => { "BUNDLE_FROZEN" => "true" }
+ expect(last_command).to be_success
+ end
+
+ it "shows a good error if a gem is missing from the lockfile" do
+ build_repo4 do
+ build_gem "foo"
+ build_gem "bar"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "foo"
+ gem "bar"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+ bar
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle :install, :env => { "BUNDLE_FROZEN" => "true" }, :raise_on_error => false, :artifice => "compact_index"
+ expect(err).to include("Your lock file is missing \"bar\", but the lockfile can't be updated because frozen mode is set")
+ end
+
+ it "explodes if a path gem is missing" do
+ build_lib "path_gem"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rake"
+ gem "path_gem", :path => "#{lib_path("path_gem-1.0")}", :group => :development
+ G
+ expect(the_bundle).to include_gems "path_gem 1.0"
+ FileUtils.rm_r lib_path("path_gem-1.0")
+
+ bundle "config set --local path .bundle"
+ bundle "config set --local deployment true"
+ bundle :install, :raise_on_error => false
+ expect(err).to include("The path `#{lib_path("path_gem-1.0")}` does not exist.")
+ end
+
+ it "can have --frozen set via an environment variable" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ ENV["BUNDLE_FROZEN"] = "1"
+ bundle "install", :raise_on_error => false
+ expect(err).to include("frozen mode")
+ expect(err).to include("You have added to the Gemfile")
+ expect(err).to include("* rack-obama")
+ expect(err).not_to include("You have deleted from the Gemfile")
+ expect(err).not_to include("You have changed in the Gemfile")
+ end
+
+ it "can have --deployment set via an environment variable" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ ENV["BUNDLE_DEPLOYMENT"] = "true"
+ bundle "install", :raise_on_error => false
+ expect(err).to include("frozen mode")
+ expect(err).to include("You have added to the Gemfile")
+ expect(err).to include("* rack-obama")
+ expect(err).not_to include("You have deleted from the Gemfile")
+ expect(err).not_to include("You have changed in the Gemfile")
+ end
+
+ it "installs gems by default to vendor/bundle when deployment mode is set via an environment variable" do
+ ENV["BUNDLE_DEPLOYMENT"] = "true"
+ bundle "install"
+ expect(out).to include("vendor/bundle")
+ end
+
+ it "installs gems to custom path when deployment mode is set via an environment variable " do
+ ENV["BUNDLE_DEPLOYMENT"] = "true"
+ ENV["BUNDLE_PATH"] = "vendor/bundle2"
+ bundle "install"
+ expect(out).to include("vendor/bundle2")
+ end
+
+ it "can have --frozen set to false via an environment variable" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ ENV["BUNDLE_FROZEN"] = "false"
+ ENV["BUNDLE_DEPLOYMENT"] = "false"
+ bundle "install"
+ expect(out).not_to include("frozen mode")
+ expect(out).not_to include("You have added to the Gemfile")
+ expect(out).not_to include("* rack-obama")
+ end
+
+ it "explodes if you remove a gem and don't check in the lockfile" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+ G
+
+ bundle "config set --local deployment true"
+ bundle :install, :raise_on_error => false
+ expect(err).to include("frozen mode")
+ expect(err).to include("You have added to the Gemfile:\n* activesupport\n\n")
+ expect(err).to include("You have deleted from the Gemfile:\n* rack")
+ expect(err).not_to include("You have changed in the Gemfile")
+ end
+
+ it "explodes if you add a source" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "git://hubz.com"
+ G
+
+ bundle "config set --local deployment true"
+ bundle :install, :raise_on_error => false
+ expect(err).to include("frozen mode")
+ expect(err).not_to include("You have added to the Gemfile")
+ expect(err).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `git://hubz.com`")
+ end
+
+ it "explodes if you change a source" do
+ build_git "rack"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-1.0")}"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "config set --local deployment true"
+ bundle :install, :raise_on_error => false
+ expect(err).to include("frozen mode")
+ expect(err).not_to include("You have deleted from the Gemfile")
+ expect(err).not_to include("You have added to the Gemfile")
+ expect(err).to include("You have changed in the Gemfile:\n* rack from `#{lib_path("rack-1.0")}` to `no specified source`")
+ end
+
+ it "explodes if you change a source" do
+ build_lib "foo", :path => lib_path("rack/foo")
+ build_git "rack", :path => lib_path("rack")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack")}"
+ gem "foo", :git => "#{lib_path("rack")}"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "foo", :git => "#{lib_path("rack")}"
+ G
+
+ bundle "config set --local deployment true"
+ bundle :install, :raise_on_error => false
+ expect(err).to include("frozen mode")
+ expect(err).to include("You have changed in the Gemfile:\n* rack from `#{lib_path("rack")}` to `no specified source`")
+ expect(err).not_to include("You have added to the Gemfile")
+ expect(err).not_to include("You have deleted from the Gemfile")
+ end
+
+ it "remembers that the bundle is frozen at runtime" do
+ bundle :lock
+
+ bundle "config set --local deployment true"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ gem "rack-obama"
+ G
+
+ run "require 'rack'", :raise_on_error => false
+ expect(err).to include strip_whitespace(<<-E).strip
+The dependencies in your gemfile changed, but the lockfile can't be updated because frozen mode is set (Bundler::ProductionError)
+
+You have added to the Gemfile:
+* rack (= 1.0.0)
+* rack-obama
+
+You have deleted from the Gemfile:
+* rack
+ E
+ end
+ end
+
+ context "with path in Gemfile and packed" do
+ it "works fine after bundle package and bundle install --local" do
+ build_lib "foo", :path => lib_path("foo")
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ bundle :install
+ expect(the_bundle).to include_gems "foo 1.0"
+
+ bundle "config set cache_all true"
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo")).to be_directory
+
+ bundle "install --local"
+ expect(out).to include("Updating files in vendor/cache")
+
+ simulate_new_machine
+ bundle "config set --local deployment true"
+ bundle "install --verbose"
+ expect(out).not_to include("but the lockfile can't be updated because frozen mode is set")
+ expect(out).not_to include("You have added to the Gemfile")
+ expect(out).not_to include("You have deleted from the Gemfile")
+ expect(out).to include("vendor/cache/foo")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/failure_spec.rb b/spec/bundler/install/failure_spec.rb
new file mode 100644
index 0000000000..4a9c33754f
--- /dev/null
+++ b/spec/bundler/install/failure_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ context "installing a gem fails" do
+ it "prints out why that gem was being installed and the underlying error" do
+ build_repo2 do
+ build_gem "activesupport", "2.3.2" do |s|
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ abort "make installing activesupport-2.3.2 fail"
+ end
+ RUBY
+ end
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails"
+ G
+ expect(err).to start_with("Gem::Ext::BuildError: ERROR: Failed to build gem native extension.")
+ expect(err).to end_with(<<-M.strip)
+An error occurred while installing activesupport (2.3.2), and Bundler cannot continue.
+
+In Gemfile:
+ rails was resolved to 2.3.2, which depends on
+ actionmailer was resolved to 2.3.2, which depends on
+ activesupport
+ M
+ end
+
+ context "because the downloaded .gem was invalid" do
+ before do
+ build_repo4 do
+ build_gem "a"
+ end
+
+ gem_repo4("gems", "a-1.0.gem").open("w") {|f| f << "<html></html>" }
+ end
+
+ it "removes the downloaded .gem" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo4)}"
+ gem "a"
+ G
+
+ expect(default_bundle_path("cache", "a-1.0.gem")).not_to exist
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/eval_gemfile_spec.rb b/spec/bundler/install/gemfile/eval_gemfile_spec.rb
new file mode 100644
index 0000000000..02283291b4
--- /dev/null
+++ b/spec/bundler/install/gemfile/eval_gemfile_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with gemfile that uses eval_gemfile" do
+ before do
+ build_lib("gunks", :path => bundled_app.join("gems/gunks")) do |s|
+ s.name = "gunks"
+ s.version = "0.0.1"
+ end
+ end
+
+ context "eval-ed Gemfile points to an internal gemspec" do
+ before do
+ create_file "Gemfile-other", <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => 'gems/gunks'
+ G
+ end
+
+ it "installs the gemspec specified gem" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ eval_gemfile 'Gemfile-other'
+ G
+ expect(out).to include("Resolving dependencies")
+ expect(out).to include("Bundle complete")
+
+ expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}"
+ end
+ end
+
+ context "eval-ed Gemfile points to an internal gemspec and uses a scoped source that duplicates the main Gemfile global source" do
+ before do
+ build_repo2 do
+ build_gem "rails", "6.1.3.2"
+
+ build_gem "zip-zip", "0.3"
+ end
+
+ create_file bundled_app("gems/Gemfile"), <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gemspec :path => "\#{__dir__}/gunks"
+
+ source "#{file_uri_for(gem_repo2)}" do
+ gem "zip-zip"
+ end
+ G
+ end
+
+ it "installs and finds gems correctly" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rails"
+
+ eval_gemfile File.join(__dir__, "gems/Gemfile")
+ G
+ expect(out).to include("Resolving dependencies")
+ expect(out).to include("Bundle complete")
+
+ expect(the_bundle).to include_gem "rails 6.1.3.2"
+ end
+ end
+
+ context "eval-ed Gemfile has relative-path gems" do
+ before do
+ build_lib("a", :path => bundled_app("gems/a"))
+ create_file bundled_app("nested/Gemfile-nested"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "a", :path => "../gems/a"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ eval_gemfile "nested/Gemfile-nested"
+ G
+ end
+
+ it "installs the path gem" do
+ bundle :install
+ expect(the_bundle).to include_gem("a 1.0")
+ end
+
+ # Make sure that we are properly comparing path based gems between the
+ # parsed lockfile and the evaluated gemfile.
+ it "bundles with deployment mode configured" do
+ bundle :install
+ bundle "config set --local deployment true"
+ bundle :install
+ end
+ end
+
+ context "Gemfile uses gemspec paths after eval-ing a Gemfile" do
+ before { create_file "other/Gemfile-other" }
+
+ it "installs the gemspec specified gem" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ eval_gemfile 'other/Gemfile-other'
+ gemspec :path => 'gems/gunks'
+ G
+ expect(out).to include("Resolving dependencies")
+ expect(out).to include("Bundle complete")
+
+ expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}"
+ end
+ end
+
+ context "eval-ed Gemfile references other gemfiles" do
+ it "works with relative paths" do
+ create_file "other/Gemfile-other", "gem 'rack'"
+ create_file "other/Gemfile", "eval_gemfile 'Gemfile-other'"
+ create_file "Gemfile-alt", <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ eval_gemfile "other/Gemfile"
+ G
+ install_gemfile "eval_gemfile File.expand_path('Gemfile-alt')"
+
+ expect(the_bundle).to include_gem "rack 1.0.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/force_ruby_platform_spec.rb b/spec/bundler/install/gemfile/force_ruby_platform_spec.rb
new file mode 100644
index 0000000000..0e9f1f0292
--- /dev/null
+++ b/spec/bundler/install/gemfile/force_ruby_platform_spec.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with force_ruby_platform DSL option", :jruby do
+ context "when no transitive deps" do
+ before do
+ build_repo4 do
+ # Build a gem with platform specific versions
+ build_gem("platform_specific") do |s|
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'"
+ end
+
+ build_gem("platform_specific") do |s|
+ s.platform = Bundler.local_platform
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'"
+ end
+
+ # Build the exact same gem with a different name to compare using vs not using the option
+ build_gem("platform_specific_forced") do |s|
+ s.write "lib/platform_specific_forced.rb", "PLATFORM_SPECIFIC_FORCED = '1.0.0 RUBY'"
+ end
+
+ build_gem("platform_specific_forced") do |s|
+ s.platform = Bundler.local_platform
+ s.write "lib/platform_specific_forced.rb", "PLATFORM_SPECIFIC_FORCED = '1.0.0 #{Bundler.local_platform}'"
+ end
+ end
+ end
+
+ it "pulls the pure ruby variant of the given gem" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "platform_specific_forced", :force_ruby_platform => true
+ gem "platform_specific"
+ G
+
+ expect(the_bundle).to include_gems "platform_specific_forced 1.0.0 RUBY"
+ expect(the_bundle).to include_gems "platform_specific 1.0.0 #{Bundler.local_platform}"
+ end
+
+ it "still respects a global `force_ruby_platform` config" do
+ install_gemfile <<-G, :env => { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" }
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "platform_specific_forced", :force_ruby_platform => true
+ gem "platform_specific"
+ G
+
+ expect(the_bundle).to include_gems "platform_specific_forced 1.0.0 RUBY"
+ expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY"
+ end
+ end
+
+ context "when also a transitive dependency" do
+ before do
+ build_repo4 do
+ build_gem("depends_on_platform_specific") {|s| s.add_runtime_dependency "platform_specific" }
+
+ build_gem("platform_specific") do |s|
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'"
+ end
+
+ build_gem("platform_specific") do |s|
+ s.platform = Bundler.local_platform
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'"
+ end
+ end
+ end
+
+ it "still pulls the ruby variant" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "depends_on_platform_specific"
+ gem "platform_specific", :force_ruby_platform => true
+ G
+
+ expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY"
+ end
+ end
+
+ context "with transitive dependencies with platform specific versions" do
+ before do
+ build_repo4 do
+ build_gem("depends_on_platform_specific") do |s|
+ s.add_runtime_dependency "platform_specific"
+ s.write "lib/depends_on_platform_specific.rb", "DEPENDS_ON_PLATFORM_SPECIFIC = '1.0.0 RUBY'"
+ end
+
+ build_gem("depends_on_platform_specific") do |s|
+ s.add_runtime_dependency "platform_specific"
+ s.platform = Bundler.local_platform
+ s.write "lib/depends_on_platform_specific.rb", "DEPENDS_ON_PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'"
+ end
+
+ build_gem("platform_specific") do |s|
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'"
+ end
+
+ build_gem("platform_specific") do |s|
+ s.platform = Bundler.local_platform
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'"
+ end
+ end
+ end
+
+ it "ignores ruby variants for the transitive dependencies" do
+ install_gemfile <<-G, :env => { "DEBUG_RESOLVER" => "true" }
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "depends_on_platform_specific", :force_ruby_platform => true
+ G
+
+ expect(the_bundle).to include_gems "depends_on_platform_specific 1.0.0 RUBY"
+ expect(the_bundle).to include_gems "platform_specific 1.0.0 #{Bundler.local_platform}"
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb
new file mode 100644
index 0000000000..73f04f071d
--- /dev/null
+++ b/spec/bundler/install/gemfile/gemspec_spec.rb
@@ -0,0 +1,697 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install from an existing gemspec" do
+ before(:each) do
+ build_repo2 do
+ build_gem "bar"
+ build_gem "bar-dev"
+ end
+ end
+
+ let(:x64_mingw_archs) do
+ if RUBY_PLATFORM == "x64-mingw-ucrt"
+ if Gem.rubygems_version >= Gem::Version.new("3.2.28")
+ ["x64-mingw-ucrt", "x64-mingw32"]
+ else
+ ["x64-mingw32", "x64-unknown"]
+ end
+ else
+ ["x64-mingw32"]
+ end
+ end
+
+ let(:x64_mingw_gems) do
+ x64_mingw_archs.map {|p| "platform_specific (1.0-#{p})" }.join("\n ")
+ end
+
+ let(:x64_mingw_platforms) do
+ x64_mingw_archs.join("\n ")
+ end
+
+ it "should install runtime and development dependencies" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("Gemfile", "source :rubygems\ngemspec")
+ s.add_dependency "bar", "=1.0.0"
+ s.add_development_dependency "bar-dev", "=1.0.0"
+ end
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0.0"
+ expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development
+ end
+
+ it "that is hidden should install runtime and development dependencies" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("Gemfile", "source :rubygems\ngemspec")
+ s.add_dependency "bar", "=1.0.0"
+ s.add_development_dependency "bar-dev", "=1.0.0"
+ end
+ FileUtils.mv tmp.join("foo", "foo.gemspec"), tmp.join("foo", ".gemspec")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0.0"
+ expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development
+ end
+
+ it "should handle a list of requirements" do
+ update_repo2 do
+ build_gem "baz", "1.0"
+ build_gem "baz", "1.1"
+ end
+
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("Gemfile", "source :rubygems\ngemspec")
+ s.add_dependency "baz", ">= 1.0", "< 1.1"
+ end
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ expect(the_bundle).to include_gems "baz 1.0"
+ end
+
+ it "should raise if there are no gemspecs available" do
+ build_lib("foo", :path => tmp.join("foo"), :gemspec => false)
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+ expect(err).to match(/There are no gemspecs at #{tmp.join('foo')}/)
+ end
+
+ it "should raise if there are too many gemspecs available" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("foo2.gemspec", build_spec("foo", "4.0").first.to_ruby)
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+ expect(err).to match(/There are multiple gemspecs at #{tmp.join('foo')}/)
+ end
+
+ it "should pick a specific gemspec" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("foo2.gemspec", "")
+ s.add_dependency "bar", "=1.0.0"
+ s.add_development_dependency "bar-dev", "=1.0.0"
+ end
+
+ install_gemfile(<<-G)
+ source "#{file_uri_for(gem_repo2)}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0.0"
+ expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development
+ end
+
+ it "should use a specific group for development dependencies" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("foo2.gemspec", "")
+ s.add_dependency "bar", "=1.0.0"
+ s.add_development_dependency "bar-dev", "=1.0.0"
+ end
+
+ install_gemfile(<<-G)
+ source "#{file_uri_for(gem_repo2)}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo', :development_group => :dev
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0.0"
+ expect(the_bundle).not_to include_gems "bar-dev 1.0.0", :groups => :development
+ expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :dev
+ end
+
+ it "should match a lockfile even if the gemspec defines development dependencies" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("Gemfile", "source '#{file_uri_for(gem_repo1)}'\ngemspec")
+ s.add_dependency "actionpack", "=2.3.2"
+ s.add_development_dependency "rake", "=13.0.1"
+ end
+
+ bundle "install", :dir => tmp.join("foo")
+ # This should really be able to rely on $stderr, but, it's not written
+ # right, so we can't. In fact, this is a bug negation test, and so it'll
+ # ghost pass in future, and will only catch a regression if the message
+ # doesn't change. Exit codes should be used correctly (they can be more
+ # than just 0 and 1).
+ bundle "config set --local deployment true"
+ output = bundle("install", :dir => tmp.join("foo"))
+ expect(output).not_to match(/You have added to the Gemfile/)
+ expect(output).not_to match(/You have deleted from the Gemfile/)
+ expect(output).not_to match(/the lockfile can't be updated because frozen mode is set/)
+ end
+
+ it "should match a lockfile without needing to re-resolve" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.add_dependency "rack"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ bundle "install", :verbose => true
+
+ message = "Found no changes, using resolution from the lockfile"
+ expect(out.scan(message).size).to eq(1)
+ end
+
+ it "should match a lockfile without needing to re-resolve with development dependencies" do
+ simulate_platform java
+
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.add_dependency "rack"
+ s.add_development_dependency "thin"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ bundle "install", :verbose => true
+
+ message = "Found no changes, using resolution from the lockfile"
+ expect(out.scan(message).size).to eq(1)
+ end
+
+ it "should match a lockfile on non-ruby platforms with a transitive platform dependency", :jruby_only do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.add_dependency "platform_specific"
+ end
+
+ system_gems "platform_specific-1.0-java", :path => default_bundle_path
+
+ install_gemfile <<-G
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ bundle "update --bundler", :artifice => "compact_index", :verbose => true
+ expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 JAVA"
+ end
+
+ it "should evaluate the gemspec in its directory" do
+ build_lib("foo", :path => tmp.join("foo"))
+ File.open(tmp.join("foo/foo.gemspec"), "w") do |s|
+ s.write "raise 'ahh' unless Dir.pwd == '#{tmp.join("foo")}'"
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+ expect(last_command.stdboth).not_to include("ahh")
+ end
+
+ it "allows the gemspec to activate other gems" do
+ ENV["BUNDLE_PATH__SYSTEM"] = "true"
+ # see https://github.com/rubygems/bundler/issues/5409
+ #
+ # issue was caused by rubygems having an unresolved gem during a require,
+ # so emulate that
+ system_gems %w[rack-1.0.0 rack-0.9.1 rack-obama-1.0]
+
+ build_lib("foo", :path => bundled_app)
+ gemspec = bundled_app("foo.gemspec").read
+ bundled_app("foo.gemspec").open("w") do |f|
+ f.write "#{gemspec.strip}.tap { gem 'rack-obama'; require 'rack/obama' }"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+
+ expect(the_bundle).to include_gem "foo 1.0"
+ end
+
+ it "allows conflicts" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.version = "1.0.0"
+ s.add_dependency "bar", "= 1.0.0"
+ end
+ build_gem "deps", :to_bundle => true do |s|
+ s.add_dependency "foo", "= 0.0.1"
+ end
+ build_gem "foo", "0.0.1", :to_bundle => true
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "deps"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0.0"
+ end
+
+ it "does not break Gem.finish_resolve with conflicts" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.version = "1.0.0"
+ s.add_dependency "bar", "= 1.0.0"
+ end
+ update_repo2 do
+ build_gem "deps" do |s|
+ s.add_dependency "foo", "= 0.0.1"
+ end
+ build_gem "foo", "0.0.1"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "deps"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0.0"
+
+ run "Gem.finish_resolve; puts 'WIN'"
+ expect(out).to eq("WIN")
+ end
+
+ it "handles downgrades" do
+ build_lib "omg", "2.0", :path => lib_path("omg")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => "#{lib_path("omg")}"
+ G
+
+ build_lib "omg", "1.0", :path => lib_path("omg")
+
+ bundle :install
+
+ expect(the_bundle).to include_gems "omg 1.0"
+ end
+
+ context "in deployment mode" do
+ context "when the lockfile was not updated after a change to the gemspec's dependencies" do
+ it "reports that installation failed" do
+ build_lib "cocoapods", :path => bundled_app do |s|
+ s.add_dependency "activesupport", ">= 1"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+
+ expect(the_bundle).to include_gems("cocoapods 1.0", "activesupport 2.3.5")
+
+ build_lib "cocoapods", :path => bundled_app do |s|
+ s.add_dependency "activesupport", ">= 1.0.1"
+ end
+
+ bundle "config set --local deployment true"
+ bundle :install, :raise_on_error => false
+
+ expect(err).to include("changed")
+ end
+ end
+ end
+
+ context "when child gemspecs conflict with a released gemspec" do
+ before do
+ # build the "parent" gem that depends on another gem in the same repo
+ build_lib "source_conflict", :path => bundled_app do |s|
+ s.add_dependency "rack_middleware"
+ end
+
+ # build the "child" gem that is the same version as a released gem, but
+ # has completely different and conflicting dependency requirements
+ build_lib "rack_middleware", "1.0", :path => bundled_app("rack_middleware") do |s|
+ s.add_dependency "rack", "1.0" # anything other than 0.9.1
+ end
+ end
+
+ it "should install the child gemspec's deps" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ context "with a lockfile and some missing dependencies" do
+ let(:source_uri) { "http://localgemserver.test" }
+
+ before do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.add_dependency "rack", "=1.0.0"
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gemspec :path => "../foo"
+ G
+
+ lockfile <<-L
+ PATH
+ remote: ../foo
+ specs:
+ foo (1.0)
+ rack (= 1.0.0)
+
+ GEM
+ remote: #{source_uri}
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ context "using JRuby with explicit platform", :jruby_only do
+ before do
+ create_file(
+ tmp.join("foo", "foo-java.gemspec"),
+ build_spec("foo", "1.0", "java") do
+ dep "rack", "=1.0.0"
+ @spec.authors = "authors"
+ @spec.summary = "summary"
+ end.first.to_ruby
+ )
+ end
+
+ it "should install" do
+ results = bundle "install", :artifice => "endpoint"
+ expect(results).to include("Installing rack 1.0.0")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ it "should install", :jruby do
+ results = bundle "install", :artifice => "endpoint"
+ expect(results).to include("Installing rack 1.0.0")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ context "bundled for multiple platforms" do
+ let(:platform_specific_type) { :runtime }
+ let(:dependency) { "platform_specific" }
+ before do
+ build_repo2 do
+ build_gem "indirect_platform_specific" do |s|
+ s.add_runtime_dependency "platform_specific"
+ end
+ end
+
+ build_lib "foo", :path => bundled_app do |s|
+ if platform_specific_type == :runtime
+ s.add_runtime_dependency dependency
+ elsif platform_specific_type == :development
+ s.add_development_dependency dependency
+ else
+ raise "wrong dependency type #{platform_specific_type}, can only be :development or :runtime"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gemspec
+ G
+
+ bundle "config set --local force_ruby_platform true"
+ bundle "install"
+
+ simulate_new_machine
+ simulate_platform("jruby") { bundle "install" }
+ simulate_platform(x64_mingw32) { bundle "install" }
+ end
+
+ context "on ruby" do
+ before do
+ bundle "config set --local force_ruby_platform true"
+ bundle :install
+ end
+
+ context "as a runtime dependency" do
+ it "keeps all platform dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY"
+ expect(lockfile).to eq strip_whitespace(<<-L)
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+ platform_specific
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+ #{x64_mingw_gems}
+
+ PLATFORMS
+ java
+ ruby
+ #{x64_mingw_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "as a development dependency" do
+ let(:platform_specific_type) { :development }
+
+ it "keeps all platform dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY"
+ expect(lockfile).to eq strip_whitespace(<<-L)
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+ #{x64_mingw_gems}
+
+ PLATFORMS
+ java
+ ruby
+ #{x64_mingw_platforms}
+
+ DEPENDENCIES
+ foo!
+ platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "with an indirect platform-specific development dependency" do
+ let(:platform_specific_type) { :development }
+ let(:dependency) { "indirect_platform_specific" }
+
+ it "keeps all platform dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 RUBY"
+ expect(lockfile).to eq strip_whitespace(<<-L)
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ indirect_platform_specific (1.0)
+ platform_specific
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+ #{x64_mingw_gems}
+
+ PLATFORMS
+ java
+ ruby
+ #{x64_mingw_platforms}
+
+ DEPENDENCIES
+ foo!
+ indirect_platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+ end
+ end
+ end
+
+ context "with multiple platforms" do
+ before do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.version = "1.0.0"
+ s.add_development_dependency "rack"
+ s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "rack", "1.0.0" }.first.to_ruby
+ end
+ end
+
+ it "installs the ruby platform gemspec" do
+ bundle "config set --local force_ruby_platform true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0.0", "rack 1.0.0"
+ end
+
+ it "installs the ruby platform gemspec and skips dev deps with `without development` configured" do
+ bundle "config set --local force_ruby_platform true"
+
+ bundle "config set --local without development"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ expect(the_bundle).to include_gem "foo 1.0.0"
+ expect(the_bundle).not_to include_gem "rack"
+ end
+ end
+
+ context "with multiple platforms and resolving for more specific platforms" do
+ before do
+ build_lib("chef", :path => tmp.join("chef")) do |s|
+ s.version = "17.1.17"
+ s.write "chef-universal-mingw32.gemspec", build_spec("chef", "17.1.17", "universal-mingw32") {|sw| sw.runtime "win32-api", "~> 1.5.3" }.first.to_ruby
+ end
+ end
+
+ it "does not remove the platform specific specs from the lockfile when updating" do
+ build_repo4 do
+ build_gem "win32-api", "1.5.3" do |s|
+ s.platform = "universal-mingw32"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gemspec :path => "../chef"
+ G
+
+ initial_lockfile = <<~L
+ PATH
+ remote: ../chef
+ specs:
+ chef (17.1.17)
+ chef (17.1.17-universal-mingw32)
+ win32-api (~> 1.5.3)
+
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ win32-api (1.5.3-universal-mingw32)
+
+ PLATFORMS
+ ruby
+ #{x64_mingw_platforms}
+ x86-mingw32
+
+ DEPENDENCIES
+ chef!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lockfile initial_lockfile
+
+ bundle "update"
+
+ expect(lockfile).to eq initial_lockfile
+ end
+ end
+
+ context "with multiple locked platforms" do
+ before do
+ build_lib("activeadmin", :path => tmp.join("activeadmin")) do |s|
+ s.version = "2.9.0"
+ s.add_dependency "railties", ">= 5.2", "< 6.2"
+ end
+
+ build_repo4 do
+ build_gem "railties", "6.1.4"
+
+ build_gem "jruby-openssl", "0.10.7" do |s|
+ s.platform = "java"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gemspec :path => "../activeadmin"
+ gem "jruby-openssl", :platform => :jruby
+ G
+
+ bundle "lock --add-platform java"
+ end
+
+ it "does not remove the platform specific specs from the lockfile when re-resolving due to gemspec changes" do
+ expect(lockfile).to eq <<~L
+ PATH
+ remote: ../activeadmin
+ specs:
+ activeadmin (2.9.0)
+ railties (>= 5.2, < 6.2)
+
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ jruby-openssl (0.10.7-java)
+ railties (6.1.4)
+
+ PLATFORMS
+ #{lockfile_platforms("java")}
+
+ DEPENDENCIES
+ activeadmin!
+ jruby-openssl
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemspec = tmp.join("activeadmin/activeadmin.gemspec")
+ File.write(gemspec, File.read(gemspec).sub(">= 5.2", ">= 6.0"))
+
+ previous_lockfile = lockfile
+
+ bundle "install --local"
+
+ expect(lockfile).to eq(previous_lockfile.sub(">= 5.2", ">= 6.0"))
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb
new file mode 100644
index 0000000000..910f96f4ab
--- /dev/null
+++ b/spec/bundler/install/gemfile/git_spec.rb
@@ -0,0 +1,1630 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with git sources" do
+ describe "when floating on main" do
+ before :each do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+ end
+
+ it "fetches gems" do
+ expect(the_bundle).to include_gems("foo 1.0")
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" unless defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "caches the git repo", :bundler => "< 3" do
+ expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes :size => 1
+ end
+
+ it "does not write to cache on bundler/setup" do
+ cache_path = default_bundle_path.join("cache")
+ FileUtils.rm_rf(cache_path)
+ ruby "require 'bundler/setup'"
+ expect(cache_path).not_to exist
+ end
+
+ it "caches the git repo globally and properly uses the cached repo on the next invocation" do
+ simulate_new_machine
+ bundle "config set global_gem_cache true"
+ bundle :install
+ expect(Dir["#{home}/.bundle/cache/git/foo-1.0-*"]).to have_attributes :size => 1
+
+ bundle "install --verbose"
+ expect(err).to be_empty
+ expect(out).to include("Using foo 1.0 from #{lib_path("foo")}")
+ end
+
+ it "caches the evaluated gemspec" do
+ git = update_git "foo" do |s|
+ s.executables = ["foobar"] # we added this the first time, so keep it now
+ s.files = ["bin/foobar"] # updating git nukes the files list
+ foospec = s.to_ruby.gsub(/s\.files.*/, 's.files = `git ls-files -z`.split("\x0")')
+ s.write "foo.gemspec", foospec
+ end
+
+ bundle "update foo"
+
+ sha = git.ref_for("main", 11)
+ spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec")
+ expect(spec_file).to exist
+ ruby_code = Gem::Specification.load(spec_file.to_s).to_ruby
+ file_code = File.read(spec_file)
+ expect(file_code).to eq(ruby_code)
+ end
+
+ it "does not update the git source implicitly" do
+ update_git "foo"
+
+ install_gemfile bundled_app2("Gemfile"), <<-G, :dir => bundled_app2
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+
+ run <<-RUBY
+ require 'foo'
+ puts "fail" if defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to be_empty
+ end
+
+ it "sets up git gem executables on the path" do
+ bundle "exec foobar"
+ expect(out).to eq("1.0")
+ end
+
+ it "complains if pinned specs don't exist in the git repo" do
+ build_git "foo"
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", "1.1", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(err).to include("The source contains the following gems matching 'foo':\n * foo-1.0")
+ end
+
+ it "complains with version and platform if pinned specs don't exist in the git repo", :jruby_only do
+ build_git "only_java" do |s|
+ s.platform = "java"
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ platforms :jruby do
+ gem "only_java", "1.2", :git => "#{lib_path("only_java-1.0-java")}"
+ end
+ G
+
+ expect(err).to include("The source contains the following gems matching 'only_java':\n * only_java-1.0-java")
+ end
+
+ it "complains with multiple versions and platforms if pinned specs don't exist in the git repo", :jruby_only do
+ build_git "only_java", "1.0" do |s|
+ s.platform = "java"
+ end
+
+ build_git "only_java", "1.1" do |s|
+ s.platform = "java"
+ s.write "only_java1-0.gemspec", File.read("#{lib_path("only_java-1.0-java")}/only_java.gemspec")
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ platforms :jruby do
+ gem "only_java", "1.2", :git => "#{lib_path("only_java-1.1-java")}"
+ end
+ G
+
+ expect(err).to include("The source contains the following gems matching 'only_java':\n * only_java-1.0-java\n * only_java-1.1-java")
+ end
+
+ it "still works after moving the application directory" do
+ bundle "config set --local path vendor/bundle"
+ bundle "install"
+
+ FileUtils.mv bundled_app, tmp("bundled_app.bck")
+
+ expect(the_bundle).to include_gems "foo 1.0", :dir => tmp("bundled_app.bck")
+ end
+
+ it "can still install after moving the application directory" do
+ bundle "config set --local path vendor/bundle"
+ bundle "install"
+
+ FileUtils.mv bundled_app, tmp("bundled_app.bck")
+
+ update_git "foo", "1.1", :path => lib_path("foo-1.0")
+
+ gemfile tmp("bundled_app.bck/Gemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+
+ gem "rack", "1.0"
+ G
+
+ bundle "update foo", :dir => tmp("bundled_app.bck")
+
+ expect(the_bundle).to include_gems "foo 1.1", "rack 1.0", :dir => tmp("bundled_app.bck")
+ end
+ end
+
+ describe "with an empty git block" do
+ before do
+ build_git "foo"
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ git "#{lib_path("foo-1.0")}" do
+ # this page left intentionally blank
+ end
+ G
+ end
+
+ it "does not explode" do
+ bundle "install"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ describe "when specifying a revision" do
+ before(:each) do
+ build_git "foo"
+ @revision = revision_for(lib_path("foo-1.0"))
+ update_git "foo"
+ end
+
+ it "works" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}", :ref => "#{@revision}" do
+ gem "foo"
+ end
+ G
+ expect(err).to be_empty
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" unless defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "works when the revision is a symbol" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}", :ref => #{@revision.to_sym.inspect} do
+ gem "foo"
+ end
+ G
+ expect(err).to be_empty
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" unless defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "works when an abbreviated revision is added after an initial, potentially shallow clone" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}", :ref => #{@revision[0..7].inspect} do
+ gem "foo"
+ end
+ G
+ end
+
+ it "works when a tag that does not look like a commit hash is used as the value of :ref" do
+ build_git "foo"
+ @remote = build_git("bar", :bare => true)
+ update_git "foo", :remote => file_uri_for(@remote.path)
+ update_git "foo", :push => "main"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => "#{@remote.path}"
+ G
+
+ # Create a new tag on the remote that needs fetching
+ update_git "foo", :tag => "v1.0.0"
+ update_git "foo", :push => "v1.0.0"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => "#{@remote.path}", :ref => "v1.0.0"
+ G
+
+ expect(err).to be_empty
+ end
+
+ it "works when the revision is a non-head ref" do
+ # want to ensure we don't fallback to main
+ update_git "foo", :path => lib_path("foo-1.0") do |s|
+ s.write("lib/foo.rb", "raise 'FAIL'")
+ end
+
+ sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", :dir => lib_path("foo-1.0"))
+
+ # want to ensure we don't fallback to HEAD
+ update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s|
+ s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do
+ gem "foo"
+ end
+ G
+ expect(err).to be_empty
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" if defined?(FOO)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "works when the revision is a non-head ref and it was previously downloaded" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ # want to ensure we don't fallback to main
+ update_git "foo", :path => lib_path("foo-1.0") do |s|
+ s.write("lib/foo.rb", "raise 'FAIL'")
+ end
+
+ sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", :dir => lib_path("foo-1.0"))
+
+ # want to ensure we don't fallback to HEAD
+ update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s|
+ s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do
+ gem "foo"
+ end
+ G
+ expect(err).to be_empty
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" if defined?(FOO)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "does not download random non-head refs" do
+ sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", :dir => lib_path("foo-1.0"))
+
+ bundle "config set global_gem_cache true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ # ensure we also git fetch after cloning
+ bundle :update, :all => true
+
+ sys_exec("git ls-remote .", :dir => Dir[home(".bundle/cache/git/foo-*")].first)
+
+ expect(out).not_to include("refs/bundler/1")
+ end
+ end
+
+ describe "when specifying a branch" do
+ let(:branch) { "branch" }
+ let(:repo) { build_git("foo").path }
+
+ it "works" do
+ update_git("foo", :path => repo, :branch => branch)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{repo}", :branch => #{branch.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ context "when the branch starts with a `#`" do
+ let(:branch) { "#149/redirect-url-fragment" }
+ it "works" do
+ skip "git does not accept this" if Gem.win_platform?
+
+ update_git("foo", :path => repo, :branch => branch)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{repo}", :branch => #{branch.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+ end
+
+ context "when the branch includes quotes" do
+ let(:branch) { %('") }
+ it "works" do
+ skip "git does not accept this" if Gem.win_platform?
+
+ update_git("foo", :path => repo, :branch => branch)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{repo}", :branch => #{branch.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+ end
+ end
+
+ describe "when specifying a tag" do
+ let(:tag) { "tag" }
+ let(:repo) { build_git("foo").path }
+
+ it "works" do
+ update_git("foo", :path => repo, :tag => tag)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{repo}", :tag => #{tag.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ context "when the tag starts with a `#`" do
+ let(:tag) { "#149/redirect-url-fragment" }
+ it "works" do
+ skip "git does not accept this" if Gem.win_platform?
+
+ update_git("foo", :path => repo, :tag => tag)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{repo}", :tag => #{tag.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+ end
+
+ context "when the tag includes quotes" do
+ let(:tag) { %('") }
+ it "works" do
+ skip "git does not accept this" if Gem.win_platform?
+
+ update_git("foo", :path => repo, :tag => tag)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{repo}", :tag => #{tag.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+ end
+ end
+
+ describe "when specifying local override" do
+ it "uses the local repository instead of checking a new one out" do
+ build_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle :install
+
+ run "require 'rack'"
+ expect(out).to eq("LOCAL")
+ end
+
+ it "chooses the local repository on runtime" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ update_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ run "require 'rack'"
+ expect(out).to eq("LOCAL")
+ end
+
+ it "unlocks the source when the dependencies have changed while switching to the local" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ update_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "rack.gemspec", build_spec("rack", "0.8") { runtime "rspec", "> 0" }.first.to_ruby
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle :install
+ run "require 'rack'"
+ expect(out).to eq("LOCAL")
+ end
+
+ it "updates specs on runtime" do
+ system_gems "nokogiri-1.4.2"
+
+ build_git "rack", "0.8"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ lockfile0 = File.read(bundled_app_lock)
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ update_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.add_dependency "nokogiri", "1.4.2"
+ end
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ run "require 'rack'"
+
+ lockfile1 = File.read(bundled_app_lock)
+ expect(lockfile1).not_to eq(lockfile0)
+ end
+
+ it "updates ref on install" do
+ build_git "rack", "0.8"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ lockfile0 = File.read(bundled_app_lock)
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ update_git "rack", "0.8", :path => lib_path("local-rack")
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle :install
+
+ lockfile1 = File.read(bundled_app_lock)
+ expect(lockfile1).not_to eq(lockfile0)
+ end
+
+ it "explodes and gives correct solution if given path does not exist on install" do
+ build_git "rack", "0.8"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle :install, :raise_on_error => false
+ expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/)
+
+ solution = "config unset local.rack"
+ expect(err).to match(/Run `bundle #{solution}` to remove the local override/)
+
+ bundle solution
+ bundle :install
+
+ expect(err).to be_empty
+ end
+
+ it "explodes and gives correct solution if branch is not given on install" do
+ build_git "rack", "0.8"
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle :install, :raise_on_error => false
+ expect(err).to match(/Cannot use local override for rack-0.8 at #{Regexp.escape(lib_path('local-rack').to_s)} because :branch is not specified in Gemfile/)
+
+ solution = "config unset local.rack"
+ expect(err).to match(/Specify a branch or run `bundle #{solution}` to remove the local override/)
+
+ bundle solution
+ bundle :install
+
+ expect(err).to be_empty
+ end
+
+ it "does not explode if disable_local_branch_check is given" do
+ build_git "rack", "0.8"
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle %(config set disable_local_branch_check true)
+ bundle :install
+ expect(out).to match(/Bundle complete!/)
+ end
+
+ it "explodes on different branches on install" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ update_git "rack", "0.8", :path => lib_path("local-rack"), :branch => "another" do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle :install, :raise_on_error => false
+ expect(err).to match(/is using branch another but Gemfile specifies main/)
+ end
+
+ it "explodes on invalid revision on install" do
+ build_git "rack", "0.8"
+
+ build_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle :install, :raise_on_error => false
+ expect(err).to match(/The Gemfile lock is pointing to revision \w+/)
+ end
+
+ it "does not explode on invalid revision on install" do
+ build_git "rack", "0.8"
+
+ build_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle %(config set disable_local_revision_check true)
+ bundle :install
+ expect(out).to match(/Bundle complete!/)
+ end
+ end
+
+ describe "specified inline" do
+ # TODO: Figure out how to write this test so that it is not flaky depending
+ # on the current network situation.
+ # it "supports private git URLs" do
+ # gemfile <<-G
+ # gem "thingy", :git => "git@notthere.fallingsnow.net:somebody/thingy.git"
+ # G
+ #
+ # bundle :install
+ #
+ # # p out
+ # # p err
+ # puts err unless err.empty? # This spec fails randomly every so often
+ # err.should include("notthere.fallingsnow.net")
+ # err.should include("ssh")
+ # end
+
+ it "installs from git even if a newer gem is available elsewhere" do
+ build_git "rack", "0.8"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}"
+ G
+
+ expect(the_bundle).to include_gems "rack 0.8"
+ end
+
+ it "installs dependencies from git even if a newer gem is available elsewhere" do
+ system_gems "rack-1.0.0"
+
+ build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s|
+ s.write "lib/rack.rb", "puts 'WIN OVERRIDE'"
+ end
+
+ build_git "foo", :path => lib_path("nested") do |s|
+ s.add_dependency "rack", "= 1.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("nested")}"
+ G
+
+ run "require 'rack'"
+ expect(out).to eq("WIN OVERRIDE")
+ end
+
+ it "correctly unlocks when changing to a git source" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ G
+
+ build_git "rack", :path => lib_path("rack")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0", :git => "#{lib_path("rack")}"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "correctly unlocks when changing to a git source without versions" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ build_git "rack", "1.2", :path => lib_path("rack")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack")}"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+ end
+
+ describe "block syntax" do
+ it "pulls all gems from a git block" do
+ build_lib "omg", :path => lib_path("hi2u/omg")
+ build_lib "hi2u", :path => lib_path("hi2u")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path("hi2u")}" do
+ gem "omg"
+ gem "hi2u"
+ end
+ G
+
+ expect(the_bundle).to include_gems "omg 1.0", "hi2u 1.0"
+ end
+ end
+
+ it "uses a ref if specified" do
+ build_git "foo"
+ @revision = revision_for(lib_path("foo-1.0"))
+ update_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{@revision}"
+ G
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" unless defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "correctly handles cases with invalid gemspecs" do
+ build_git "foo" do |s|
+ s.summary = nil
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ gem "rails", "2.3.2"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ expect(the_bundle).to include_gems "rails 2.3.2"
+ end
+
+ it "runs the gemspec in the context of its parent directory" do
+ build_lib "bar", :path => lib_path("foo/bar"), :gemspec => false do |s|
+ s.write lib_path("foo/bar/lib/version.rb"), %(BAR_VERSION = '1.0')
+ s.write "bar.gemspec", <<-G
+ $:.unshift Dir.pwd
+ require 'lib/version'
+ Gem::Specification.new do |s|
+ s.name = 'bar'
+ s.author = 'no one'
+ s.version = BAR_VERSION
+ s.summary = 'Bar'
+ s.files = Dir["lib/**/*.rb"]
+ end
+ G
+ end
+
+ build_git "foo", :path => lib_path("foo") do |s|
+ s.write "bin/foo", ""
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bar", :git => "#{lib_path("foo")}"
+ gem "rails", "2.3.2"
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0"
+ expect(the_bundle).to include_gems "rails 2.3.2"
+ end
+
+ it "installs from git even if a rubygems gem is present" do
+ build_gem "foo", "1.0", :path => lib_path("fake_foo"), :to_system => true do |s|
+ s.write "lib/foo.rb", "raise 'FAIL'"
+ end
+
+ build_git "foo", "1.0"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "fakes the gem out if there is no gemspec" do
+ build_git "foo", :gemspec => false
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}"
+ gem "rails", "2.3.2"
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ expect(the_bundle).to include_gems("rails 2.3.2")
+ end
+
+ it "catches git errors and spits out useful output" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", "1.0", :git => "omgomg"
+ G
+
+ bundle :install, :raise_on_error => false
+
+ expect(err).to include("Git error:")
+ expect(err).to include("fatal")
+ expect(err).to include("omgomg")
+ end
+
+ it "works when the gem path has spaces in it" do
+ build_git "foo", :path => lib_path("foo space-1.0")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo space-1.0")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "handles repos that have been force-pushed" do
+ build_git "forced", "1.0"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("forced-1.0")}" do
+ gem 'forced'
+ end
+ G
+ expect(the_bundle).to include_gems "forced 1.0"
+
+ update_git "forced" do |s|
+ s.write "lib/forced.rb", "FORCED = '1.1'"
+ end
+
+ bundle "update", :all => true
+ expect(the_bundle).to include_gems "forced 1.1"
+
+ sys_exec("git reset --hard HEAD^", :dir => lib_path("forced-1.0"))
+
+ bundle "update", :all => true
+ expect(the_bundle).to include_gems "forced 1.0"
+ end
+
+ it "ignores submodules if :submodule is not passed" do
+ # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
+ system(*%W[git config --global protocol.file.allow always])
+
+ build_git "submodule", "1.0"
+ build_git "has_submodule", "1.0" do |s|
+ s.add_dependency "submodule"
+ end
+ sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0")
+ sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0")
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("has_submodule-1.0")}" do
+ gem "has_submodule"
+ end
+ G
+ expect(err).to match(%r{submodule >= 0 could not be found in rubygems repository #{file_uri_for(gem_repo1)}/ or installed locally})
+
+ expect(the_bundle).not_to include_gems "has_submodule 1.0"
+ end
+
+ it "handles repos with submodules" do
+ # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
+ system(*%W[git config --global protocol.file.allow always])
+
+ build_git "submodule", "1.0"
+ build_git "has_submodule", "1.0" do |s|
+ s.add_dependency "submodule"
+ end
+ sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0")
+ sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("has_submodule-1.0")}", :submodules => true do
+ gem "has_submodule"
+ end
+ G
+
+ expect(the_bundle).to include_gems "has_submodule 1.0"
+ end
+
+ it "does not warn when deiniting submodules" do
+ # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
+ system(*%W[git config --global protocol.file.allow always])
+
+ build_git "submodule", "1.0"
+ build_git "has_submodule", "1.0"
+
+ sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0")
+ sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("has_submodule-1.0")}" do
+ gem "has_submodule"
+ end
+ G
+ expect(err).to be_empty
+
+ expect(the_bundle).to include_gems "has_submodule 1.0"
+ expect(the_bundle).to_not include_gems "submodule 1.0"
+ end
+
+ it "handles implicit updates when modifying the source info" do
+ git = build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ update_git "foo"
+ update_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}", :ref => "#{git.ref_for("HEAD^")}" do
+ gem "foo"
+ end
+ G
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" if FOO_PREV_REF == '#{git.ref_for("HEAD^^")}'
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "does not to a remote fetch if the revision is cached locally" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ FileUtils.rm_rf(lib_path("foo-1.0"))
+
+ bundle "install"
+ expect(out).not_to match(/updating/i)
+ end
+
+ it "doesn't blow up if bundle install is run twice in a row" do
+ build_git "foo"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "install"
+ bundle "install"
+ end
+
+ it "prints a friendly error if a file blocks the git repo" do
+ build_git "foo"
+
+ FileUtils.mkdir_p(default_bundle_path)
+ FileUtils.touch(default_bundle_path("bundler"))
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(exitstatus).to_not eq(0)
+ expect(err).to include("Bundler could not install a gem because it " \
+ "needs to create a directory, but a file exists " \
+ "- #{default_bundle_path("bundler")}")
+ end
+
+ it "does not duplicate git gem sources" do
+ build_lib "foo", :path => lib_path("nested/foo")
+ build_lib "bar", :path => lib_path("nested/bar")
+
+ build_git "foo", :path => lib_path("nested")
+ build_git "bar", :path => lib_path("nested")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("nested")}"
+ gem "bar", :git => "#{lib_path("nested")}"
+ G
+
+ expect(File.read(bundled_app_lock).scan("GIT").size).to eq(1)
+ end
+
+ describe "switching sources" do
+ it "doesn't explode when switching Path to Git sources" do
+ build_gem "foo", "1.0", :to_system => true do |s|
+ s.write "lib/foo.rb", "raise 'fail'"
+ end
+ build_lib "foo", "1.0", :path => lib_path("bar/foo")
+ build_git "bar", "1.0", :path => lib_path("bar") do |s|
+ s.add_dependency "foo"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bar", :path => "#{lib_path("bar")}"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bar", :git => "#{lib_path("bar")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0", "bar 1.0"
+ end
+
+ it "doesn't explode when switching Gem to Git source" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack-obama"
+ gem "rack", "1.0.0"
+ G
+
+ build_git "rack", "1.0" do |s|
+ s.write "lib/new_file.rb", "puts 'USING GIT'"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack-obama"
+ gem "rack", "1.0.0", :git => "#{lib_path("rack-1.0")}"
+ G
+
+ run "require 'new_file'"
+ expect(out).to eq("USING GIT")
+ end
+ end
+
+ describe "bundle install after the remote has been updated" do
+ it "installs" do
+ build_git "valim"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "valim", :git => "#{file_uri_for(lib_path("valim-1.0"))}"
+ G
+
+ old_revision = revision_for(lib_path("valim-1.0"))
+ update_git "valim"
+ new_revision = revision_for(lib_path("valim-1.0"))
+
+ old_lockfile = File.read(bundled_app_lock)
+ lockfile(bundled_app_lock, old_lockfile.gsub(/revision: #{old_revision}/, "revision: #{new_revision}"))
+
+ bundle "install"
+
+ run <<-R
+ require "valim"
+ puts VALIM_PREV_REF
+ R
+
+ expect(out).to eq(old_revision)
+ end
+
+ it "gives a helpful error message when the remote ref no longer exists" do
+ build_git "foo"
+ revision = revision_for(lib_path("foo-1.0"))
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :ref => "#{revision}"
+ G
+ expect(out).to_not match(/Revision.*does not exist/)
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :ref => "deadbeef"
+ G
+ expect(err).to include("Revision deadbeef does not exist in the repository")
+ end
+
+ it "gives a helpful error message when the remote branch no longer exists" do
+ build_git "foo"
+
+ install_gemfile <<-G, :env => { "LANG" => "en" }, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :branch => "deadbeef"
+ G
+
+ expect(err).to include("Revision deadbeef does not exist in the repository")
+ end
+ end
+
+ describe "bundle install with deployment mode configured and git sources" do
+ it "works" do
+ build_git "valim", :path => lib_path("valim")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "valim", "= 1.0", :git => "#{lib_path("valim")}"
+ G
+
+ simulate_new_machine
+
+ bundle "config set --local deployment true"
+ bundle :install
+ end
+ end
+
+ describe "gem install hooks" do
+ it "runs pre-install hooks" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ Gem.pre_install_hooks << lambda do |inst|
+ STDERR.puts "Ran pre-install hook: \#{inst.spec.full_name}"
+ end
+ H
+ end
+
+ bundle :install,
+ :requires => [lib_path("install_hooks.rb")]
+ expect(err_without_deprecations).to eq("Ran pre-install hook: foo-1.0")
+ end
+
+ it "runs post-install hooks" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ Gem.post_install_hooks << lambda do |inst|
+ STDERR.puts "Ran post-install hook: \#{inst.spec.full_name}"
+ end
+ H
+ end
+
+ bundle :install,
+ :requires => [lib_path("install_hooks.rb")]
+ expect(err_without_deprecations).to eq("Ran post-install hook: foo-1.0")
+ end
+
+ it "complains if the install hook fails" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ Gem.pre_install_hooks << lambda do |inst|
+ false
+ end
+ H
+ end
+
+ bundle :install, :requires => [lib_path("install_hooks.rb")], :raise_on_error => false
+ expect(err).to include("failed for foo-1.0")
+ end
+ end
+
+ context "with an extension" do
+ it "installs the extension" do
+ build_git "foo" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("lib", __dir__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/foo.rb", "w") do |f|
+ f.puts "FOO = 'YES'"
+ end
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+ expect(out).to eq("YES")
+
+ run <<-R
+ puts $:.grep(/ext/)
+ R
+ expect(out).to include(Pathname.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s)
+ end
+
+ it "does not use old extension after ref changes" do
+ git_reader = build_git "foo", :no_default => true do |s|
+ s.extensions = ["ext/extconf.rb"]
+ s.write "ext/extconf.rb", <<-RUBY
+ require "mkmf"
+ $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"]
+ create_makefile("foo")
+ RUBY
+ s.write "ext/foo.c", "void Init_foo() {}"
+ end
+
+ 2.times do |i|
+ File.open(git_reader.path.join("ext/foo.c"), "w") do |file|
+ file.write <<-C
+ #include "ruby.h"
+ VALUE foo() { return INT2FIX(#{i}); }
+ void Init_foo() { rb_define_global_function("foo", &foo, 0); }
+ C
+ end
+ sys_exec("git commit -m \"commit for iteration #{i}\" ext/foo.c", :dir => git_reader.path)
+
+ git_commit_sha = git_reader.ref_for("HEAD")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{git_commit_sha}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts foo
+ R
+
+ expect(out).to eq(i.to_s)
+ end
+ end
+
+ it "does not prompt to gem install if extension fails" do
+ build_git "foo" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ raise
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(err).to end_with(<<-M.strip)
+An error occurred while installing foo (1.0), and Bundler cannot continue.
+
+In Gemfile:
+ foo
+ M
+ expect(out).not_to include("gem install foo")
+ end
+
+ it "does not reinstall the extension" do
+ build_git "foo" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("lib", __dir__)
+ FileUtils.mkdir_p(path)
+ cur_time = Time.now.to_f.to_s
+ File.open("\#{path}/foo.rb", "w") do |f|
+ f.puts "FOO = \#{cur_time}"
+ end
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+
+ installed_time = out
+ expect(installed_time).to match(/\A\d+\.\d+\z/)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+ expect(out).to eq(installed_time)
+ end
+
+ it "does not reinstall the extension when changing another gem" do
+ build_git "foo" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("lib", __dir__)
+ FileUtils.mkdir_p(path)
+ cur_time = Time.now.to_f.to_s
+ File.open("\#{path}/foo.rb", "w") do |f|
+ f.puts "FOO = \#{cur_time}"
+ end
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+
+ installed_time = out
+ expect(installed_time).to match(/\A\d+\.\d+\z/)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+ expect(out).to eq(installed_time)
+ end
+
+ it "does reinstall the extension when changing refs" do
+ build_git "foo" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("lib", __dir__)
+ FileUtils.mkdir_p(path)
+ cur_time = Time.now.to_f.to_s
+ File.open("\#{path}/foo.rb", "w") do |f|
+ f.puts "FOO = \#{cur_time}"
+ end
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+
+ installed_time = out
+
+ update_git("foo", :branch => "branch2")
+
+ expect(installed_time).to match(/\A\d+\.\d+\z/)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "branch2"
+ G
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+ expect(out).not_to eq(installed_time)
+
+ installed_time = out
+
+ update_git("foo")
+ bundle "update foo"
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+ expect(out).not_to eq(installed_time)
+ end
+ end
+
+ it "ignores git environment variables" do
+ build_git "xxxxxx" do |s|
+ s.executables = "xxxxxxbar"
+ end
+
+ Bundler::SharedHelpers.with_clean_git_env do
+ ENV["GIT_DIR"] = "bar"
+ ENV["GIT_WORK_TREE"] = "bar"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("xxxxxx-1.0")}" do
+ gem 'xxxxxx'
+ end
+ G
+
+ expect(ENV["GIT_DIR"]).to eq("bar")
+ expect(ENV["GIT_WORK_TREE"]).to eq("bar")
+ end
+ end
+
+ describe "without git installed" do
+ it "prints a better error message when installing" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rake", git: "https://github.com/ruby/rake"
+ G
+
+ lockfile <<-L
+ GIT
+ remote: https://github.com/ruby/rake
+ revision: 5c60da8644a9e4f655e819252e3b6ca77f42b7af
+ specs:
+ rake (13.0.6)
+
+ GEM
+ remote: https://rubygems.org/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rake!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ with_path_as("") do
+ bundle "install", :raise_on_error => false
+ end
+ expect(err).
+ to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git")
+ end
+
+ it "prints a better error message when updating" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+
+ with_path_as("") do
+ bundle "update", :all => true, :raise_on_error => false
+ end
+ expect(err).
+ to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git")
+ end
+
+ it "installs a packaged git gem successfully" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+ bundle "config set cache_all true"
+ bundle :cache
+ simulate_new_machine
+
+ bundle "install", :env => { "PATH" => "" }
+ expect(out).to_not include("You need to install git to be able to use gems from git repositories.")
+ end
+ end
+
+ describe "when the git source is overridden with a local git repo" do
+ before do
+ bundle "config set --global local.foo #{lib_path("foo")}"
+ end
+
+ describe "and git output is colorized" do
+ before do
+ File.open("#{ENV["HOME"]}/.gitconfig", "w") do |f|
+ f.write("[color]\n\tui = always\n")
+ end
+ end
+
+ it "installs successfully" do
+ build_git "foo", "1.0", :path => lib_path("foo")
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo")}", :branch => "main"
+ G
+
+ bundle :install
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+ end
+ end
+
+ context "git sources that include credentials" do
+ context "that are username and password" do
+ let(:credentials) { "user1:password1" }
+
+ it "does not display the password" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ git "https://#{credentials}@github.com/company/private-repo" do
+ gem "foo"
+ end
+ G
+
+ expect(last_command.stdboth).to_not include("password1")
+ expect(out).to include("Fetching https://user1@github.com/company/private-repo")
+ end
+ end
+
+ context "that is an oauth token" do
+ let(:credentials) { "oauth_token" }
+
+ it "displays the oauth scheme but not the oauth token" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ git "https://#{credentials}:x-oauth-basic@github.com/company/private-repo" do
+ gem "foo"
+ end
+ G
+
+ expect(last_command.stdboth).to_not include("oauth_token")
+ expect(out).to include("Fetching https://x-oauth-basic@github.com/company/private-repo")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb
new file mode 100644
index 0000000000..734e012e84
--- /dev/null
+++ b/spec/bundler/install/gemfile/groups_spec.rb
@@ -0,0 +1,403 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with groups" do
+ describe "installing with no options" do
+ before :each do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ group :emo do
+ gem "activesupport", "2.3.5"
+ end
+ gem "thin", :groups => [:emo]
+ G
+ end
+
+ it "installs gems in the default group" do
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs gems in a group block into that group" do
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+
+ load_error_run <<-R, "activesupport", :default
+ require 'activesupport'
+ puts ACTIVESUPPORT
+ R
+
+ expect(err_without_deprecations).to eq("ZOMG LOAD ERROR")
+ end
+
+ it "installs gems with inline :groups into those groups" do
+ expect(the_bundle).to include_gems "thin 1.0"
+
+ load_error_run <<-R, "thin", :default
+ require 'thin'
+ puts THIN
+ R
+
+ expect(err_without_deprecations).to eq("ZOMG LOAD ERROR")
+ end
+
+ it "sets up everything if Bundler.setup is used with no groups" do
+ output = run("require 'rack'; puts RACK")
+ expect(output).to eq("1.0.0")
+
+ output = run("require 'activesupport'; puts ACTIVESUPPORT")
+ expect(output).to eq("2.3.5")
+
+ output = run("require 'thin'; puts THIN")
+ expect(output).to eq("1.0")
+ end
+
+ it "removes old groups when new groups are set up" do
+ load_error_run <<-RUBY, "thin", :emo
+ Bundler.setup(:default)
+ require 'thin'
+ puts THIN
+ RUBY
+
+ expect(err_without_deprecations).to eq("ZOMG LOAD ERROR")
+ end
+
+ it "sets up old groups when they have previously been removed" do
+ output = run <<-RUBY, :emo
+ Bundler.setup(:default)
+ Bundler.setup(:default, :emo)
+ require 'thin'; puts THIN
+ RUBY
+ expect(output).to eq("1.0")
+ end
+ end
+
+ describe "without option" do
+ describe "with gems assigned to a single group" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ group :emo do
+ gem "activesupport", "2.3.5"
+ end
+ group :debugging, :optional => true do
+ gem "thin"
+ end
+ G
+ end
+
+ it "installs gems in the default group" do
+ bundle "config set --local without emo"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default]
+ end
+
+ it "respects global `without` configuration, but does not save it locally" do
+ bundle "config set --global without emo"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default]
+ bundle "config list"
+ expect(out).not_to include("Set for your local app (#{bundled_app(".bundle/config")}): [:emo]")
+ expect(out).to include("Set for the current user (#{home(".bundle/config")}): [:emo]")
+ end
+
+ it "allows running application where groups where configured by a different user", :bundler => "< 3" do
+ bundle "config set without emo"
+ bundle :install
+ bundle "exec ruby -e 'puts 42'", :env => { "BUNDLE_USER_HOME" => tmp("new_home").to_s }
+ expect(out).to include("42")
+ end
+
+ it "does not install gems from the excluded group" do
+ bundle "config set --local without emo"
+ bundle :install
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default]
+ end
+
+ it "remembers previous exclusion with `--without`", :bundler => "< 3" do
+ bundle "install --without emo"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ bundle :install
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+
+ it "does not say it installed gems from the excluded group" do
+ bundle "config set --local without emo"
+ bundle :install
+ expect(out).not_to include("activesupport")
+ end
+
+ it "allows Bundler.setup for specific groups" do
+ bundle "config set --local without emo"
+ bundle :install
+ run("require 'rack'; puts RACK", :default)
+ expect(out).to eq("1.0.0")
+ end
+
+ it "does not effect the resolve" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+ group :emo do
+ gem "rails", "2.3.2"
+ end
+ G
+
+ bundle "config set --local without emo"
+ bundle :install
+ expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => [:default]
+ end
+
+ it "still works when BUNDLE_WITHOUT is set" do
+ ENV["BUNDLE_WITHOUT"] = "emo"
+
+ bundle :install
+ expect(out).not_to include("activesupport")
+
+ expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default]
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default]
+
+ ENV["BUNDLE_WITHOUT"] = nil
+ end
+
+ it "clears --without when passed an empty list", :bundler => "< 3" do
+ bundle "install --without emo"
+
+ bundle "install --without ''"
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ end
+
+ it "doesn't clear without when nothing is passed", :bundler => "< 3" do
+ bundle "install --without emo"
+
+ bundle :install
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+
+ it "does not install gems from the optional group" do
+ bundle :install
+ expect(the_bundle).not_to include_gems "thin 1.0"
+ end
+
+ it "installs gems from the optional group when requested" do
+ bundle "config set --local with debugging"
+ bundle :install
+ expect(the_bundle).to include_gems "thin 1.0"
+ end
+
+ it "installs gems from the previously requested group", :bundler => "< 3" do
+ bundle "install --with debugging"
+ expect(the_bundle).to include_gems "thin 1.0"
+ bundle :install
+ expect(the_bundle).to include_gems "thin 1.0"
+ end
+
+ it "installs gems from the optional groups requested with BUNDLE_WITH" do
+ ENV["BUNDLE_WITH"] = "debugging"
+ bundle :install
+ expect(the_bundle).to include_gems "thin 1.0"
+ ENV["BUNDLE_WITH"] = nil
+ end
+
+ it "clears --with when passed an empty list", :bundler => "< 3" do
+ bundle "install --with debugging"
+ bundle "install --with ''"
+ expect(the_bundle).not_to include_gems "thin 1.0"
+ end
+
+ it "removes groups from without when passed at --with", :bundler => "< 3" do
+ bundle "config set --local without emo"
+ bundle "install --with emo"
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ end
+
+ it "removes groups from with when passed at --without", :bundler => "< 3" do
+ bundle "config set --local with debugging"
+ bundle "install --without debugging", :raise_on_error => false
+ expect(the_bundle).not_to include_gem "thin 1.0"
+ end
+
+ it "errors out when passing a group to with and without via CLI flags", :bundler => "< 3" do
+ bundle "install --with emo debugging --without emo", :raise_on_error => false
+ expect(last_command).to be_failure
+ expect(err).to include("The offending groups are: emo")
+ end
+
+ it "allows the BUNDLE_WITH setting to override BUNDLE_WITHOUT" do
+ ENV["BUNDLE_WITH"] = "debugging"
+
+ bundle :install
+ expect(the_bundle).to include_gem "thin 1.0"
+
+ ENV["BUNDLE_WITHOUT"] = "debugging"
+ expect(the_bundle).to include_gem "thin 1.0"
+
+ bundle :install
+ expect(the_bundle).to include_gem "thin 1.0"
+ end
+
+ it "can add and remove a group at the same time", :bundler => "< 3" do
+ bundle "install --with debugging --without emo"
+ expect(the_bundle).to include_gems "thin 1.0"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+
+ it "has no effect when listing a not optional group in with" do
+ bundle "config set --local with emo"
+ bundle :install
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ end
+
+ it "has no effect when listing an optional group in without" do
+ bundle "config set --local without debugging"
+ bundle :install
+ expect(the_bundle).not_to include_gems "thin 1.0"
+ end
+ end
+
+ describe "with gems assigned to multiple groups" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ group :emo, :lolercoaster do
+ gem "activesupport", "2.3.5"
+ end
+ G
+ end
+
+ it "installs gems in the default group" do
+ bundle "config set --local without emo lolercoaster"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs the gem if any of its groups are installed" do
+ bundle "config set --local without emo"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ end
+
+ describe "with a gem defined multiple times in different groups" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+
+ group :emo do
+ gem "activesupport", "2.3.5"
+ end
+
+ group :lolercoaster do
+ gem "activesupport", "2.3.5"
+ end
+ G
+ end
+
+ it "installs the gem unless all groups are excluded" do
+ bundle "config set --local without emo"
+ bundle :install
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+
+ bundle "config set --local without lolercoaster"
+ bundle :install
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+
+ bundle "config set --local without emo lolercoaster"
+ bundle :install
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+
+ bundle "config set --local without 'emo lolercoaster'"
+ bundle :install
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+ end
+ end
+
+ describe "nesting groups" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ group :emo do
+ group :lolercoaster do
+ gem "activesupport", "2.3.5"
+ end
+ end
+ G
+ end
+
+ it "installs gems in the default group" do
+ bundle "config set --local without emo lolercoaster"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs the gem if any of its groups are installed" do
+ bundle "config set --local without emo"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ end
+ end
+ end
+
+ describe "when loading only the default group" do
+ it "should not load all groups" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "activesupport", :groups => :development
+ G
+
+ ruby <<-R
+ require "#{entrypoint}"
+ Bundler.setup :default
+ Bundler.require :default
+ puts RACK
+ begin
+ require "activesupport"
+ rescue LoadError
+ puts "no activesupport"
+ end
+ R
+
+ expect(out).to include("1.0")
+ expect(out).to include("no activesupport")
+ end
+ end
+
+ describe "when locked and installed with `without` option" do
+ before(:each) do
+ build_repo2
+
+ system_gems "rack-0.9.1"
+
+ bundle "config set --local without rack"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+
+ group :rack do
+ gem "rack_middleware"
+ end
+ G
+ end
+
+ it "uses the correct versions even if --without was used on the original" do
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ expect(the_bundle).not_to include_gems "rack_middleware 1.0"
+ simulate_new_machine
+
+ bundle :install
+
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ expect(the_bundle).to include_gems "rack_middleware 1.0"
+ end
+
+ it "does not hit the remote a second time" do
+ FileUtils.rm_rf gem_repo2
+ bundle "config set --local without rack"
+ bundle :install, :verbose => true
+ expect(last_command.stdboth).not_to match(/fetching/i)
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/install_if_spec.rb b/spec/bundler/install/gemfile/install_if_spec.rb
new file mode 100644
index 0000000000..3d2d15a698
--- /dev/null
+++ b/spec/bundler/install/gemfile/install_if_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with install_if conditionals" do
+ it "follows the install_if DSL" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ install_if(lambda { true }) do
+ gem "activesupport", "2.3.5"
+ end
+ gem "thin", :install_if => false
+ install_if(lambda { false }) do
+ gem "foo"
+ end
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems("rack 1.0", "activesupport 2.3.5")
+ expect(the_bundle).not_to include_gems("thin")
+ expect(the_bundle).not_to include_gems("foo")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ activesupport (2.3.5)
+ foo (1.0)
+ rack (1.0.0)
+ thin (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ activesupport (= 2.3.5)
+ foo
+ rack
+ thin
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+end
diff --git a/spec/bundler/install/gemfile/lockfile_spec.rb b/spec/bundler/install/gemfile/lockfile_spec.rb
new file mode 100644
index 0000000000..313e99d0b8
--- /dev/null
+++ b/spec/bundler/install/gemfile/lockfile_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with a lockfile present" do
+ let(:gf) { <<-G }
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ G
+
+ subject do
+ install_gemfile(gf)
+ end
+
+ context "gemfile evaluation" do
+ let(:gf) { super() + "\n\n File.open('evals', 'a') {|f| f << %(1\n) } unless ENV['BUNDLER_SPEC_NO_APPEND']" }
+
+ context "with plugins disabled" do
+ before do
+ bundle "config set plugins false"
+ subject
+ end
+
+ it "does not evaluate the gemfile twice" do
+ bundle :install
+
+ with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "rack 1.0.0" }
+
+ # The first eval is from the initial install, we're testing that the
+ # second install doesn't double-eval
+ expect(bundled_app("evals").read.lines.to_a.size).to eq(2)
+ end
+
+ context "when the gem is not installed" do
+ before { FileUtils.rm_rf bundled_app(".bundle") }
+
+ it "does not evaluate the gemfile twice" do
+ bundle :install
+
+ with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "rack 1.0.0" }
+
+ # The first eval is from the initial install, we're testing that the
+ # second install doesn't double-eval
+ expect(bundled_app("evals").read.lines.to_a.size).to eq(2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb
new file mode 100644
index 0000000000..a5207036c3
--- /dev/null
+++ b/spec/bundler/install/gemfile/path_spec.rb
@@ -0,0 +1,984 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with explicit source paths" do
+ it "fetches gems with a global path source", :bundler => "< 3" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ path "#{lib_path("foo-1.0")}"
+ gem 'foo'
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "fetches gems" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "supports pinned paths" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :path => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "supports relative paths" do
+ build_lib "foo"
+
+ relative_path = lib_path("foo-1.0").relative_path_from(bundled_app)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :path => "#{relative_path}"
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "expands paths" do
+ build_lib "foo"
+
+ relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("~").expand_path)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :path => "~/#{relative_path}"
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "expands paths raise error with not existing user's home dir" do
+ skip "problems with ~ expansion" if Gem.win_platform?
+
+ build_lib "foo"
+ username = "some_unexisting_user"
+ relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("/home/#{username}").expand_path)
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :path => "~#{username}/#{relative_path}"
+ G
+ expect(err).to match("There was an error while trying to use the path `~#{username}/#{relative_path}`.")
+ expect(err).to match("user #{username} doesn't exist")
+ end
+
+ it "expands paths relative to Bundler.root" do
+ build_lib "foo", :path => bundled_app("foo-1.0")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :path => "./foo-1.0"
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0", :dir => bundled_app("subdir").mkpath)
+ end
+
+ it "sorts paths consistently on install and update when they start with ./" do
+ build_lib "demo", :path => lib_path("demo")
+ build_lib "aaa", :path => lib_path("demo/aaa")
+
+ gemfile lib_path("demo/Gemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ gem "aaa", :path => "./aaa"
+ G
+
+ lockfile = <<~L
+ PATH
+ remote: .
+ specs:
+ demo (1.0)
+
+ PATH
+ remote: aaa
+ specs:
+ aaa (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ aaa!
+ demo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle :install, :dir => lib_path("demo")
+ expect(lib_path("demo/Gemfile.lock")).to read_as(lockfile)
+ bundle :update, :all => true, :dir => lib_path("demo")
+ expect(lib_path("demo/Gemfile.lock")).to read_as(lockfile)
+ end
+
+ it "expands paths when comparing locked paths to Gemfile paths" do
+ build_lib "foo", :path => bundled_app("foo-1.0")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :path => File.expand_path("foo-1.0", __dir__)
+ G
+
+ bundle "config set --local frozen true"
+ bundle :install
+ end
+
+ it "installs dependencies from the path even if a newer gem is available elsewhere" do
+ system_gems "rack-1.0.0"
+
+ build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s|
+ s.write "lib/rack.rb", "puts 'WIN OVERRIDE'"
+ end
+
+ build_lib "foo", :path => lib_path("nested") do |s|
+ s.add_dependency "rack", "= 1.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("nested")}"
+ G
+
+ run "require 'rack'"
+ expect(out).to eq("WIN OVERRIDE")
+ end
+
+ it "works" do
+ build_gem "foo", "1.0.0", :to_system => true do |s|
+ s.write "lib/foo.rb", "puts 'FAIL'"
+ end
+
+ build_lib "omg", "1.0", :path => lib_path("omg") do |s|
+ s.add_dependency "foo"
+ end
+
+ build_lib "foo", "1.0.0", :path => lib_path("omg/foo")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "omg", :path => "#{lib_path("omg")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "works when using prereleases of 0.0.0" do
+ build_lib "foo", "0.0.0.dev", :path => lib_path("foo")
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ lockfile <<~L
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (0.0.0.dev)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle :install
+
+ expect(the_bundle).to include_gems "foo 0.0.0.dev"
+ end
+
+ it "works when using uppercase prereleases of 0.0.0" do
+ build_lib "foo", "0.0.0.SNAPSHOT", :path => lib_path("foo")
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ lockfile <<~L
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (0.0.0.SNAPSHOT)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle :install
+
+ expect(the_bundle).to include_gems "foo 0.0.0.SNAPSHOT"
+ end
+
+ it "handles downgrades" do
+ build_lib "omg", "2.0", :path => lib_path("omg")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "omg", :path => "#{lib_path("omg")}"
+ G
+
+ build_lib "omg", "1.0", :path => lib_path("omg")
+
+ bundle :install
+
+ expect(the_bundle).to include_gems "omg 1.0"
+ end
+
+ it "prefers gemspecs closer to the path root" do
+ build_lib "premailer", "1.0.0", :path => lib_path("premailer") do |s|
+ s.write "gemfiles/ruby187.gemspec", <<-G
+ Gem::Specification.new do |s|
+ s.name = 'premailer'
+ s.version = '1.0.0'
+ s.summary = 'Hi'
+ s.authors = 'Me'
+ end
+ G
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "premailer", :path => "#{lib_path("premailer")}"
+ G
+
+ # Installation of the 'gemfiles' gemspec would fail since it will be unable
+ # to require 'premailer.rb'
+ expect(the_bundle).to include_gems "premailer 1.0.0"
+ end
+
+ it "warns on invalid specs" do
+ build_lib "foo"
+
+ gemspec = lib_path("foo-1.0").join("foo.gemspec").to_s
+ File.open(gemspec, "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = "foo"
+ end
+ G
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(err).to_not include("Your Gemfile has no gem server sources.")
+ expect(err).to match(/is not valid. Please fix this gemspec./)
+ expect(err).to match(/The validation error was 'missing value for attribute version'/)
+ expect(err).to match(/You have one or more invalid gemspecs that need to be fixed/)
+ end
+
+ it "supports gemspec syntax" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "1.0"
+ end
+
+ gemfile lib_path("foo/Gemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+
+ bundle "install", :dir => lib_path("foo")
+ expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo")
+ expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo")
+ end
+
+ it "does not unlock dependencies of path sources" do
+ build_repo4 do
+ build_gem "graphql", "2.0.15"
+ build_gem "graphql", "2.0.16"
+ end
+
+ build_lib "foo", "0.1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "graphql", "~> 2.0"
+ end
+
+ gemfile_path = lib_path("foo/Gemfile")
+
+ gemfile gemfile_path, <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gemspec
+ G
+
+ lockfile_path = lib_path("foo/Gemfile.lock")
+
+ original_lockfile = <<~L
+ PATH
+ remote: .
+ specs:
+ foo (0.1.0)
+ graphql (~> 2.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ graphql (2.0.15)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lockfile lockfile_path, original_lockfile
+
+ build_lib "foo", "0.1.1", :path => lib_path("foo") do |s|
+ s.add_dependency "graphql", "~> 2.0"
+ end
+
+ bundle "install", :dir => lib_path("foo")
+ expect(lockfile_path).to read_as(original_lockfile.gsub("foo (0.1.0)", "foo (0.1.1)"))
+ end
+
+ it "supports gemspec syntax with an alternative path" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => "#{lib_path("foo")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "doesn't automatically unlock dependencies when using the gemspec syntax" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", ">= 1.0"
+ end
+
+ install_gemfile lib_path("foo/Gemfile"), <<-G, :dir => lib_path("foo")
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+
+ build_gem "rack", "1.0.1", :to_system => true
+
+ bundle "install", :dir => lib_path("foo")
+
+ expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo")
+ expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo")
+ end
+
+ it "doesn't automatically unlock dependencies when using the gemspec syntax and the gem has development dependencies" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", ">= 1.0"
+ s.add_development_dependency "activesupport"
+ end
+
+ install_gemfile lib_path("foo/Gemfile"), <<-G, :dir => lib_path("foo")
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+
+ build_gem "rack", "1.0.1", :to_system => true
+
+ bundle "install", :dir => lib_path("foo")
+
+ expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo")
+ expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo")
+ end
+
+ it "raises if there are multiple gemspecs" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.write "bar.gemspec", build_spec("bar", "1.0").first.to_ruby
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => "#{lib_path("foo")}"
+ G
+
+ expect(exitstatus).to eq(15)
+ expect(err).to match(/There are multiple gemspecs/)
+ end
+
+ it "allows :name to be specified to resolve ambiguity" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.write "bar.gemspec"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => "#{lib_path("foo")}", :name => "foo"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "sets up executables" do
+ build_lib "foo" do |s|
+ s.executables = "foobar"
+ end
+
+ install_gemfile <<-G, :verbose => true
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+ expect(out).to include("Using foo 1.0 from source at `#{lib_path("foo-1.0")}` and installing its executables")
+ expect(the_bundle).to include_gems "foo 1.0"
+
+ bundle "exec foobar"
+ expect(out).to eq("1.0")
+ end
+
+ it "handles directories in bin/" do
+ build_lib "foo"
+ lib_path("foo-1.0").join("foo.gemspec").rmtree
+ lib_path("foo-1.0").join("bin/performance").mkpath
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', '1.0', :path => "#{lib_path("foo-1.0")}"
+ G
+ expect(err).to be_empty
+ end
+
+ it "removes the .gem file after installing" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :path => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(lib_path("foo-1.0").join("foo-1.0.gem")).not_to exist
+ end
+
+ describe "block syntax" do
+ it "pulls all gems from a path block" do
+ build_lib "omg"
+ build_lib "hi2u"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "omg"
+ gem "hi2u"
+ end
+ G
+
+ expect(the_bundle).to include_gems "omg 1.0", "hi2u 1.0"
+ end
+ end
+
+ it "keeps source pinning" do
+ build_lib "foo", "1.0", :path => lib_path("foo")
+ build_lib "omg", "1.0", :path => lib_path("omg")
+ build_lib "foo", "1.0", :path => lib_path("omg/foo") do |s|
+ s.write "lib/foo.rb", "puts 'FAIL'"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ gem "omg", :path => "#{lib_path("omg")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "works when the path does not have a gemspec" do
+ build_lib "foo", :gemspec => false
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", "1.0", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "works when the path does not have a gemspec but there is a lockfile" do
+ lockfile <<~L
+ PATH
+ remote: vendor/bar
+ specs:
+
+ GEM
+ remote: http://rubygems.org/
+ L
+
+ FileUtils.mkdir_p(bundled_app("vendor/bar"))
+
+ install_gemfile <<-G
+ source "http://rubygems.org"
+ gem "bar", "1.0.0", path: "vendor/bar", require: "bar/nyard"
+ G
+ end
+
+ context "existing lockfile" do
+ it "rubygems gems don't re-resolve without changes" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack-obama', '1.0'
+ gem 'net-ssh', '1.0'
+ G
+
+ bundle :check, :env => { "DEBUG" => "1" }
+ expect(out).to match(/using resolution from the lockfile/)
+ expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0"
+ end
+
+ it "source path gems w/deps don't re-resolve without changes" do
+ build_lib "rack-obama", "1.0", :path => lib_path("omg") do |s|
+ s.add_dependency "yard"
+ end
+
+ build_lib "net-ssh", "1.0", :path => lib_path("omg") do |s|
+ s.add_dependency "yard"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack-obama', :path => "#{lib_path("omg")}"
+ gem 'net-ssh', :path => "#{lib_path("omg")}"
+ G
+
+ bundle :check, :env => { "DEBUG" => "1" }
+ expect(out).to match(/using resolution from the lockfile/)
+ expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0"
+ end
+ end
+
+ it "installs executable stubs" do
+ build_lib "foo" do |s|
+ s.executables = ["foo"]
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "exec foo"
+ expect(out).to eq("1.0")
+ end
+
+ describe "when the gem version in the path is updated" do
+ before :each do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "bar"
+ end
+ build_lib "bar", "1.0", :path => lib_path("foo/bar")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+ end
+
+ it "unlocks all gems when the top level gem is updated" do
+ build_lib "foo", "2.0", :path => lib_path("foo") do |s|
+ s.add_dependency "bar"
+ end
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "foo 2.0", "bar 1.0"
+ end
+
+ it "unlocks all gems when a child dependency gem is updated" do
+ build_lib "bar", "2.0", :path => lib_path("foo/bar")
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "foo 1.0", "bar 2.0"
+ end
+ end
+
+ describe "when dependencies in the path are updated" do
+ before :each do
+ build_lib "foo", "1.0", :path => lib_path("foo")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+ end
+
+ it "gets dependencies that are updated in the path" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack"
+ end
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "keeps using the same version if it's compatible" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "0.9.1"
+ end
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "rack 0.9.1"
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack (= 0.9.1)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack"
+ end
+
+ bundle "install"
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ end
+
+ it "keeps using the same version even when another dependency is added" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "0.9.1"
+ end
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "rack 0.9.1"
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack (= 0.9.1)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack"
+ s.add_dependency "rake", "13.0.1"
+ end
+
+ bundle "install"
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack
+ rake (= 13.0.1)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (0.9.1)
+ rake (13.0.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ end
+
+ it "does not remove existing ruby platform" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "0.9.1"
+ end
+
+ lockfile <<~L
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms("ruby")}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock"
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack (= 0.9.1)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms("ruby")}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+ end
+
+ describe "switching sources" do
+ it "doesn't switch pinned git sources to rubygems when pinning the parent gem to a path source" do
+ build_gem "foo", "1.0", :to_system => true do |s|
+ s.write "lib/foo.rb", "raise 'fail'"
+ end
+ build_lib "foo", "1.0", :path => lib_path("bar/foo")
+ build_git "bar", "1.0", :path => lib_path("bar") do |s|
+ s.add_dependency "foo"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bar", :git => "#{lib_path("bar")}"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bar", :path => "#{lib_path("bar")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0", "bar 1.0"
+ end
+
+ it "switches the source when the gem existed in rubygems and the path was already being used for another gem" do
+ build_lib "foo", "1.0", :path => lib_path("foo")
+ build_gem "bar", "1.0", :to_bundle => true do |s|
+ s.write "lib/bar.rb", "raise 'fail'"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bar"
+ path "#{lib_path("foo")}" do
+ gem "foo"
+ end
+ G
+
+ build_lib "bar", "1.0", :path => lib_path("foo/bar")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path("foo")}" do
+ gem "foo"
+ gem "bar"
+ end
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0"
+ end
+ end
+
+ describe "when there are both a gemspec and remote gems" do
+ it "doesn't query rubygems for local gemspec name" do
+ build_lib "private_lib", "2.2", :path => lib_path("private_lib")
+ gemfile lib_path("private_lib/Gemfile"), <<-G
+ source "http://localgemserver.test"
+ gemspec
+ gem 'rack'
+ G
+ bundle :install, :env => { "DEBUG" => "1" }, :artifice => "endpoint", :dir => lib_path("private_lib")
+ expect(out).to match(%r{^HTTP GET http://localgemserver\.test/api/v1/dependencies\?gems=rack$})
+ expect(out).not_to match(/^HTTP GET.*private_lib/)
+ expect(the_bundle).to include_gems "private_lib 2.2", :dir => lib_path("private_lib")
+ expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("private_lib")
+ end
+ end
+
+ describe "gem install hooks" do
+ it "runs pre-install hooks" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ Gem.pre_install_hooks << lambda do |inst|
+ STDERR.puts "Ran pre-install hook: \#{inst.spec.full_name}"
+ end
+ H
+ end
+
+ bundle :install,
+ :requires => [lib_path("install_hooks.rb")]
+ expect(err_without_deprecations).to eq("Ran pre-install hook: foo-1.0")
+ end
+
+ it "runs post-install hooks" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ Gem.post_install_hooks << lambda do |inst|
+ STDERR.puts "Ran post-install hook: \#{inst.spec.full_name}"
+ end
+ H
+ end
+
+ bundle :install,
+ :requires => [lib_path("install_hooks.rb")]
+ expect(err_without_deprecations).to eq("Ran post-install hook: foo-1.0")
+ end
+
+ it "complains if the install hook fails" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ Gem.pre_install_hooks << lambda do |inst|
+ false
+ end
+ H
+ end
+
+ bundle :install, :requires => [lib_path("install_hooks.rb")], :raise_on_error => false
+ expect(err).to include("failed for foo-1.0")
+ end
+
+ it "loads plugins from the path gem" do
+ foo_file = home("foo_plugin_loaded")
+ bar_file = home("bar_plugin_loaded")
+ expect(foo_file).not_to be_file
+ expect(bar_file).not_to be_file
+
+ build_lib "foo" do |s|
+ s.write("lib/rubygems_plugin.rb", "require 'fileutils'; FileUtils.touch('#{foo_file}')")
+ end
+
+ build_git "bar" do |s|
+ s.write("lib/rubygems_plugin.rb", "require 'fileutils'; FileUtils.touch('#{bar_file}')")
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ gem "bar", :path => "#{lib_path("bar-1.0")}"
+ G
+
+ expect(foo_file).to be_file
+ expect(bar_file).to be_file
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/platform_spec.rb b/spec/bundler/install/gemfile/platform_spec.rb
new file mode 100644
index 0000000000..219ae6c2f4
--- /dev/null
+++ b/spec/bundler/install/gemfile/platform_spec.rb
@@ -0,0 +1,618 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install across platforms" do
+ it "maintains the same lockfile if all gems are compatible across platforms" do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{not_local}
+
+ DEPENDENCIES
+ rack
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ end
+
+ it "pulls in the correct platform specific gem" do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+ platform_specific (1.0-x86-mswin32)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ platform_specific
+ G
+
+ simulate_platform "java"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "platform_specific"
+ G
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 JAVA"
+ end
+
+ it "pulls the pure ruby version on jruby if the java platform is not present in the lockfile and bundler is run in frozen mode", :jruby_only do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}
+ specs:
+ platform_specific (1.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ platform_specific
+ G
+
+ bundle "config set --local frozen true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "platform_specific"
+ G
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 RUBY"
+ end
+
+ context "on universal Rubies" do
+ before do
+ build_repo4 do
+ build_gem "darwin_single_arch" do |s|
+ s.platform = "ruby"
+ s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 RUBY'"
+ end
+ build_gem "darwin_single_arch" do |s|
+ s.platform = "arm64-darwin"
+ s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 arm64-darwin'"
+ end
+ build_gem "darwin_single_arch" do |s|
+ s.platform = "x86_64-darwin"
+ s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 x86_64-darwin'"
+ end
+ end
+ end
+
+ it "pulls in the correct architecture gem" do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}
+ specs:
+ darwin_single_arch (1.0)
+ darwin_single_arch (1.0-arm64-darwin)
+ darwin_single_arch (1.0-x86_64-darwin)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ darwin_single_arch
+ G
+
+ simulate_platform "universal-darwin-21"
+ simulate_ruby_platform "universal.x86_64-darwin21" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "darwin_single_arch"
+ G
+
+ expect(the_bundle).to include_gems "darwin_single_arch 1.0 x86_64-darwin"
+ end
+ end
+
+ it "pulls in the correct architecture gem on arm64e macOS Ruby" do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}
+ specs:
+ darwin_single_arch (1.0)
+ darwin_single_arch (1.0-arm64-darwin)
+ darwin_single_arch (1.0-x86_64-darwin)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ darwin_single_arch
+ G
+
+ simulate_platform "universal-darwin-21"
+ simulate_ruby_platform "universal.arm64e-darwin21" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "darwin_single_arch"
+ G
+
+ expect(the_bundle).to include_gems "darwin_single_arch 1.0 arm64-darwin"
+ end
+ end
+ end
+
+ it "works with gems that have different dependencies" do
+ simulate_platform "java"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "nokogiri"
+ G
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3"
+
+ simulate_new_machine
+ bundle "config set --local force_ruby_platform true"
+ bundle "install"
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ expect(the_bundle).not_to include_gems "weakling"
+
+ simulate_new_machine
+ simulate_platform "java"
+ bundle "install"
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3"
+ end
+
+ it "does not keep unneeded platforms for gems that are used" do
+ build_repo4 do
+ build_gem "empyrean", "0.1.0"
+ build_gem "coderay", "1.1.2"
+ build_gem "method_source", "0.9.0"
+ build_gem("spoon", "0.0.6") {|s| s.add_runtime_dependency "ffi" }
+ build_gem "pry", "0.11.3" do |s|
+ s.platform = "java"
+ s.add_runtime_dependency "coderay", "~> 1.1.0"
+ s.add_runtime_dependency "method_source", "~> 0.9.0"
+ s.add_runtime_dependency "spoon", "~> 0.0"
+ end
+ build_gem "pry", "0.11.3" do |s|
+ s.add_runtime_dependency "coderay", "~> 1.1.0"
+ s.add_runtime_dependency "method_source", "~> 0.9.0"
+ end
+ build_gem("ffi", "1.9.23") {|s| s.platform = "java" }
+ build_gem("ffi", "1.9.23")
+ end
+
+ simulate_platform java
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "empyrean", "0.1.0"
+ gem "pry"
+ G
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ coderay (1.1.2)
+ empyrean (0.1.0)
+ ffi (1.9.23-java)
+ method_source (0.9.0)
+ pry (0.11.3-java)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ spoon (~> 0.0)
+ spoon (0.0.6)
+ ffi
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ empyrean (= 0.1.0)
+ pry
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock --add-platform ruby"
+
+ good_lockfile = <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ coderay (1.1.2)
+ empyrean (0.1.0)
+ ffi (1.9.23-java)
+ method_source (0.9.0)
+ pry (0.11.3)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ pry (0.11.3-java)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ spoon (~> 0.0)
+ spoon (0.0.6)
+ ffi
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ empyrean (= 0.1.0)
+ pry
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ expect(lockfile).to eq good_lockfile
+
+ bad_lockfile = <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ coderay (1.1.2)
+ empyrean (0.1.0)
+ ffi (1.9.23)
+ ffi (1.9.23-java)
+ method_source (0.9.0)
+ pry (0.11.3)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ pry (0.11.3-java)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ spoon (~> 0.0)
+ spoon (0.0.6)
+ ffi
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ empyrean (= 0.1.0)
+ pry
+
+ BUNDLED WITH
+ 1.16.1
+ L
+
+ aggregate_failures do
+ lockfile bad_lockfile
+ bundle :install, :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+ expect(lockfile).to eq good_lockfile
+
+ lockfile bad_lockfile
+ bundle :update, :all => true, :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+ expect(lockfile).to eq good_lockfile
+
+ lockfile bad_lockfile
+ bundle "update ffi", :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+ expect(lockfile).to eq good_lockfile
+
+ lockfile bad_lockfile
+ bundle "update empyrean", :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+ expect(lockfile).to eq good_lockfile
+
+ lockfile bad_lockfile
+ bundle :lock, :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+ expect(lockfile).to eq good_lockfile
+ end
+ end
+
+ it "works with gems with platform-specific dependency having different requirements order" do
+ simulate_platform x64_mac
+
+ update_repo2 do
+ build_gem "fspath", "3"
+ build_gem "image_optim_pack", "1.2.3" do |s|
+ s.add_runtime_dependency "fspath", ">= 2.1", "< 4"
+ end
+ build_gem "image_optim_pack", "1.2.3" do |s|
+ s.platform = "universal-darwin"
+ s.add_runtime_dependency "fspath", "< 4", ">= 2.1"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "image_optim_pack"
+ G
+
+ expect(err).not_to include "Unable to use the platform-specific"
+
+ expect(the_bundle).to include_gem "image_optim_pack 1.2.3 universal-darwin"
+ end
+
+ it "fetches gems again after changing the version of Ruby" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", "1.0.0"
+ G
+
+ bundle "config set --local path vendor/bundle"
+ bundle :install
+
+ FileUtils.mv(vendored_gems, bundled_app("vendor/bundle", Gem.ruby_engine, "1.8"))
+
+ bundle :install
+ expect(vendored_gems("gems/rack-1.0.0")).to exist
+ end
+
+ it "keeps existing platforms when installing with force_ruby_platform" do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ platform_specific (1.0-java)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ platform_specific
+ G
+
+ bundle "config set --local force_ruby_platform true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+
+ expect(the_bundle).to include_gem "platform_specific 1.0 RUBY"
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+end
+
+RSpec.describe "bundle install with platform conditionals" do
+ it "installs gems tagged w/ the current platforms" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ platforms :#{local_tag} do
+ gem "nokogiri"
+ end
+ G
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ end
+
+ it "does not install gems tagged w/ another platforms" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ platforms :#{not_local_tag} do
+ gem "nokogiri"
+ end
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).not_to include_gems "nokogiri 1.4.2"
+ end
+
+ it "installs gems tagged w/ another platform but also dependent on the current one transitively" do
+ build_repo4 do
+ build_gem "activesupport", "6.1.4.1" do |s|
+ s.add_dependency "tzinfo", "~> 2.0"
+ end
+
+ build_gem "tzinfo", "2.0.4"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "activesupport"
+
+ platforms :#{not_local_tag} do
+ gem "tzinfo", "~> 1.2"
+ end
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ activesupport (6.1.4.1)
+ tzinfo (~> 2.0)
+ tzinfo (2.0.4)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ activesupport
+ tzinfo (~> 1.2)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install --verbose"
+
+ expect(the_bundle).to include_gems "tzinfo 2.0.4"
+ end
+
+ it "installs gems tagged w/ the current platforms inline" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "nokogiri", :platforms => :#{local_tag}
+ G
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ end
+
+ it "does not install gems tagged w/ another platforms inline" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "nokogiri", :platforms => :#{not_local_tag}
+ G
+ expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).not_to include_gems "nokogiri 1.4.2"
+ end
+
+ it "installs gems tagged w/ the current platform inline" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "nokogiri", :platform => :#{local_tag}
+ G
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ end
+
+ it "doesn't install gems tagged w/ another platform inline" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "nokogiri", :platform => :#{not_local_tag}
+ G
+ expect(the_bundle).not_to include_gems "nokogiri 1.4.2"
+ end
+
+ it "does not blow up on sources with all platform-excluded specs" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ platform :#{not_local_tag} do
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ end
+ G
+
+ bundle :list
+ end
+
+ it "does not attempt to install gems from :rbx when using --local" do
+ bundle "config set --local force_ruby_platform true"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "some_gem", :platform => :rbx
+ G
+
+ bundle "install --local"
+ expect(out).not_to match(/Could not find gem 'some_gem/)
+ end
+
+ it "does not attempt to install gems from other rubies when using --local" do
+ bundle "config set --local force_ruby_platform true"
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "some_gem", platform: :ruby_22
+ G
+
+ bundle "install --local"
+ expect(out).not_to match(/Could not find gem 'some_gem/)
+ end
+
+ it "does not print a warning when a dependency is unused on a platform different from the current one" do
+ bundle "config set --local force_ruby_platform true"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", :platform => [:windows, :mingw, :mswin, :x64_mingw, :jruby]
+ G
+
+ bundle "install"
+
+ expect(err).to be_empty
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "resolves fine when a dependency is unused on a platform different from the current one, but reintroduced transitively" do
+ bundle "config set --local force_ruby_platform true"
+
+ build_repo4 do
+ build_gem "listen", "3.7.1" do |s|
+ s.add_dependency "ffi"
+ end
+
+ build_gem "ffi", "1.15.5"
+ end
+
+ install_gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "listen"
+ gem "ffi", :platform => :windows
+ G
+ expect(err).to be_empty
+ end
+end
+
+RSpec.describe "when a gem has no architecture" do
+ it "still installs correctly" do
+ simulate_platform x86_mswin32
+
+ build_repo2 do
+ # The rcov gem is platform mswin32, but has no arch
+ build_gem "rcov" do |s|
+ s.platform = Gem::Platform.new([nil, "mswin32", nil])
+ s.write "lib/rcov.rb", "RCOV = '1.0.0'"
+ end
+ end
+
+ gemfile <<-G
+ # Try to install gem with nil arch
+ source "http://localgemserver.test/"
+ gem "rcov"
+ G
+
+ bundle :install, :artifice => "windows", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ expect(the_bundle).to include_gems "rcov 1.0.0"
+ end
+end
diff --git a/spec/bundler/install/gemfile/ruby_spec.rb b/spec/bundler/install/gemfile/ruby_spec.rb
new file mode 100644
index 0000000000..39f09031b7
--- /dev/null
+++ b/spec/bundler/install/gemfile/ruby_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+RSpec.describe "ruby requirement" do
+ def locked_ruby_version
+ Bundler::RubyVersion.from_string(Bundler::LockfileParser.new(File.read(bundled_app_lock)).ruby_version)
+ end
+
+ # As discovered by https://github.com/rubygems/bundler/issues/4147, there is
+ # no test coverage to ensure that adding a gem is possible with a ruby
+ # requirement. This test verifies the fix, committed in bfbad5c5.
+ it "allows adding gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "#{Gem.ruby_version}"
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "#{Gem.ruby_version}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ expect(the_bundle).to include_gems "rack-obama 1.0"
+ end
+
+ it "allows removing the ruby version requirement" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "~> #{Gem.ruby_version}"
+ gem "rack"
+ G
+
+ expect(lockfile).to include("RUBY VERSION")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(lockfile).not_to include("RUBY VERSION")
+ end
+
+ it "allows changing the ruby version requirement to something compatible" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby ">= #{current_ruby_minor}"
+ gem "rack"
+ G
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ expect(locked_ruby_version).to eq(Bundler::RubyVersion.system)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby ">= #{Gem.ruby_version}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(locked_ruby_version).to eq(Bundler::RubyVersion.system)
+ end
+
+ it "allows changing the ruby version requirement to something incompatible" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby ">= 1.0.0"
+ gem "rack"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ RUBY VERSION
+ ruby 2.1.4p422
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby ">= #{current_ruby_minor}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(locked_ruby_version).to eq(Bundler::RubyVersion.system)
+ end
+
+ it "allows requirements with trailing whitespace" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "#{Gem.ruby_version}\\n \t\\n"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "fails gracefully with malformed requirements" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ ruby ">= 0", "-.\\0"
+ gem "rack"
+ G
+
+ expect(err).to include("There was an error parsing") # i.e. DSL error, not error template
+ end
+end
diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb
new file mode 100644
index 0000000000..dd86187a6b
--- /dev/null
+++ b/spec/bundler/install/gemfile/sources_spec.rb
@@ -0,0 +1,1672 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with gems on multiple sources" do
+ # repo1 is built automatically before all of the specs run
+ # it contains rack-obama 1.0.0 and rack 0.9.1 & 1.0.0 amongst other gems
+
+ context "without source affinity" do
+ before do
+ # Oh no! Someone evil is trying to hijack rack :(
+ # need this to be broken to check for correct source ordering
+ build_repo gem_repo3 do
+ build_gem "rack", repo3_rack_version do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+ end
+
+ context "with multiple toplevel sources" do
+ let(:repo3_rack_version) { "1.0.0" }
+
+ before do
+ gemfile <<-G
+ source "https://gem.repo3"
+ source "https://gem.repo1"
+ gem "rack-obama"
+ gem "rack"
+ G
+ end
+
+ it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "< 3" do
+ bundle :install, :artifice => "compact_index"
+
+ expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(err).to include("Installed from: https://gem.repo1")
+ expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1")
+ end
+
+ it "fails", :bundler => "3" do
+ bundle :install, :artifice => "compact_index", :raise_on_error => false
+ expect(err).to include("Each source after the first must include a block")
+ expect(exitstatus).to eq(4)
+ end
+ end
+
+ context "when different versions of the same gem are in multiple sources" do
+ let(:repo3_rack_version) { "1.2" }
+
+ before do
+ gemfile <<-G
+ source "https://gem.repo3"
+ source "https://gem.repo1"
+ gem "rack-obama"
+ gem "rack", "1.0.0" # force it to install the working version in repo1
+ G
+ end
+
+ it "warns about ambiguous gems, but installs anyway", :bundler => "< 3" do
+ bundle :install, :artifice => "compact_index"
+ expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(err).to include("Installed from: https://gem.repo1")
+ expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1")
+ end
+
+ it "fails", :bundler => "3" do
+ bundle :install, :artifice => "compact_index", :raise_on_error => false
+ expect(err).to include("Each source after the first must include a block")
+ expect(exitstatus).to eq(4)
+ end
+ end
+ end
+
+ context "with source affinity" do
+ context "with sources given by a block" do
+ before do
+ # Oh no! Someone evil is trying to hijack rack :(
+ # need this to be broken to check for correct source ordering
+ build_repo gem_repo3 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+
+ build_gem "rack-obama" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo3"
+ source "https://gem.repo1" do
+ gem "thin" # comes first to test name sorting
+ gem "rack"
+ end
+ gem "rack-obama" # should come from repo3!
+ G
+ end
+
+ it "installs the gems without any warning" do
+ bundle :install, :artifice => "compact_index"
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("rack-obama 1.0.0")
+ expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote1")
+ end
+
+ it "can cache and deploy" do
+ bundle :cache, :artifice => "compact_index"
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/rack-obama-1.0.gem")).to exist
+
+ bundle "config set --local deployment true"
+ bundle :install, :artifice => "compact_index"
+
+ expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0")
+ end
+ end
+
+ context "with sources set by an option" do
+ before do
+ # Oh no! Someone evil is trying to hijack rack :(
+ # need this to be broken to check for correct source ordering
+ build_repo gem_repo3 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+
+ build_gem "rack-obama" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "https://gem.repo3"
+ gem "rack-obama" # should come from repo3!
+ gem "rack", :source => "https://gem.repo1"
+ G
+ end
+
+ it "installs the gems without any warning" do
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0")
+ end
+ end
+
+ context "when a pinned gem has an indirect dependency in the pinned source" do
+ before do
+ build_repo gem_repo3 do
+ build_gem "depends_on_rack", "1.0.1" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ # we need a working rack gem in repo3
+ update_repo gem_repo3 do
+ build_gem "rack", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ source "https://gem.repo3" do
+ gem "depends_on_rack"
+ end
+ G
+ end
+
+ context "and not in any other sources" do
+ before do
+ build_repo(gem_repo2) {}
+ end
+
+ it "installs from the same source without any warning" do
+ bundle :install, :artifice => "compact_index"
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
+ end
+ end
+
+ context "and in another source" do
+ before do
+ # need this to be broken to check for correct source ordering
+ build_repo gem_repo2 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+ end
+
+ it "installs from the same source without any warning" do
+ bundle :install, :artifice => "compact_index"
+
+ expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
+
+ # In https://github.com/bundler/bundler/issues/3585 this failed
+ # when there is already a lock file, and the gems are missing, so try again
+ system_gems []
+ bundle :install, :artifice => "compact_index"
+
+ expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
+ end
+ end
+ end
+
+ context "when a pinned gem has an indirect dependency in a different source" do
+ before do
+ # In these tests, we need a working rack gem in repo2 and not repo3
+
+ build_repo gem_repo3 do
+ build_gem "depends_on_rack", "1.0.1" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ build_repo gem_repo2 do
+ build_gem "rack", "1.0.0"
+ end
+ end
+
+ context "and not in any other sources" do
+ before do
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "https://gem.repo2"
+ source "https://gem.repo3" do
+ gem "depends_on_rack"
+ end
+ G
+ end
+
+ it "installs from the other source without any warning" do
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ end
+ end
+
+ context "and in yet another source" do
+ before do
+ gemfile <<-G
+ source "https://gem.repo1"
+ source "https://gem.repo2"
+ source "https://gem.repo3" do
+ gem "depends_on_rack"
+ end
+ G
+ end
+
+ it "installs from the other source and warns about ambiguous gems", :bundler => "< 3" do
+ bundle :install, :artifice => "compact_index"
+ expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(err).to include("Installed from: https://gem.repo2")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo1/
+ remote: https://gem.repo2/
+ specs:
+ rack (1.0.0)
+
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ depends_on_rack (1.0.1)
+ rack
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ depends_on_rack!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ previous_lockfile = lockfile
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ expect(lockfile).to eq(previous_lockfile)
+ end
+
+ it "fails", :bundler => "3" do
+ bundle :install, :artifice => "compact_index", :raise_on_error => false
+ expect(err).to include("Each source after the first must include a block")
+ expect(exitstatus).to eq(4)
+ end
+ end
+
+ context "and only the dependency is pinned" do
+ before do
+ # need this to be broken to check for correct source ordering
+ build_repo gem_repo2 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo3" # contains depends_on_rack
+ source "https://gem.repo2" # contains broken rack
+
+ gem "depends_on_rack" # installed from gem_repo3
+ gem "rack", :source => "https://gem.repo1"
+ G
+ end
+
+ it "installs the dependency from the pinned source without warning", :bundler => "< 3" do
+ bundle :install, :artifice => "compact_index"
+
+ expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+
+ # In https://github.com/rubygems/bundler/issues/3585 this failed
+ # when there is already a lock file, and the gems are missing, so try again
+ system_gems []
+ bundle :install, :artifice => "compact_index"
+
+ expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ end
+
+ it "fails", :bundler => "3" do
+ bundle :install, :artifice => "compact_index", :raise_on_error => false
+ expect(err).to include("Each source after the first must include a block")
+ expect(exitstatus).to eq(4)
+ end
+ end
+ end
+
+ context "when a top-level gem can only be found in an scoped source" do
+ before do
+ build_repo2
+
+ build_repo gem_repo3 do
+ build_gem "private_gem_1", "1.0.0"
+ build_gem "private_gem_2", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ gem "private_gem_1"
+
+ source "https://gem.repo3" do
+ gem "private_gem_2"
+ end
+ G
+ end
+
+ it "fails" do
+ bundle :install, :artifice => "compact_index", :raise_on_error => false
+ expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/ or installed locally.")
+ end
+ end
+
+ context "when an indirect dependency can't be found in the aggregate rubygems source", :bundler => "< 3" do
+ before do
+ build_repo2
+
+ build_repo gem_repo3 do
+ build_gem "depends_on_missing", "1.0.1" do |s|
+ s.add_dependency "missing"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ source "https://gem.repo3"
+
+ gem "depends_on_missing"
+ G
+ end
+
+ it "fails" do
+ bundle :install, :artifice => "compact_index", :raise_on_error => false
+ expect(err).to end_with <<~E.strip
+ Could not find compatible versions
+
+ Because every version of depends_on_missing depends on missing >= 0
+ and missing >= 0 could not be found in any of the sources,
+ depends_on_missing cannot be used.
+ So, because Gemfile depends on depends_on_missing >= 0,
+ version solving has failed.
+ E
+ end
+ end
+
+ context "when a top-level gem has an indirect dependency" do
+ before do
+ build_repo gem_repo2 do
+ build_gem "depends_on_rack", "1.0.1" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ build_repo gem_repo3 do
+ build_gem "unrelated_gem", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ gem "depends_on_rack"
+
+ source "https://gem.repo3" do
+ gem "unrelated_gem"
+ end
+ G
+ end
+
+ context "and the dependency is only in the top-level source" do
+ before do
+ update_repo gem_repo2 do
+ build_gem "rack", "1.0.0"
+ end
+ end
+
+ it "installs the dependency from the top-level source without warning" do
+ bundle :install, :artifice => "compact_index"
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2")
+ expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3")
+ end
+ end
+
+ context "and the dependency is only in a pinned source" do
+ before do
+ update_repo gem_repo3 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+ end
+
+ it "does not find the dependency" do
+ bundle :install, :artifice => "compact_index", :raise_on_error => false
+ expect(err).to end_with <<~E.strip
+ Could not find compatible versions
+
+ Because every version of depends_on_rack depends on rack >= 0
+ and rack >= 0 could not be found in rubygems repository https://gem.repo2/ or installed locally,
+ depends_on_rack cannot be used.
+ So, because Gemfile depends on depends_on_rack >= 0,
+ version solving has failed.
+ E
+ end
+ end
+
+ context "and the dependency is in both the top-level and a pinned source" do
+ before do
+ update_repo gem_repo2 do
+ build_gem "rack", "1.0.0"
+ end
+
+ update_repo gem_repo3 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+ end
+
+ it "installs the dependency from the top-level source without warning" do
+ bundle :install, :artifice => "compact_index"
+ expect(err).not_to include("Warning")
+ expect(run("require 'rack'; puts RACK")).to eq("1.0.0")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2")
+ expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3")
+ end
+ end
+ end
+
+ context "when a scoped gem has a deeply nested indirect dependency" do
+ before do
+ build_repo gem_repo3 do
+ build_gem "depends_on_depends_on_rack", "1.0.1" do |s|
+ s.add_dependency "depends_on_rack"
+ end
+
+ build_gem "depends_on_rack", "1.0.1" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ source "https://gem.repo3" do
+ gem "depends_on_depends_on_rack"
+ end
+ G
+ end
+
+ context "and the dependency is only in the top-level source" do
+ before do
+ update_repo gem_repo2 do
+ build_gem "rack", "1.0.0"
+ end
+ end
+
+ it "installs the dependency from the top-level source" do
+ bundle :install, :artifice => "compact_index"
+ expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0")
+ expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote2")
+ expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", :source => "remote3")
+ end
+ end
+
+ context "and the dependency is only in a pinned source" do
+ before do
+ build_repo2
+
+ update_repo gem_repo3 do
+ build_gem "rack", "1.0.0"
+ end
+ end
+
+ it "installs the dependency from the pinned source" do
+ bundle :install, :artifice => "compact_index"
+ expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
+ end
+ end
+
+ context "and the dependency is in both the top-level and a pinned source" do
+ before do
+ update_repo gem_repo2 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+
+ update_repo gem_repo3 do
+ build_gem "rack", "1.0.0"
+ end
+ end
+
+ it "installs the dependency from the pinned source without warning" do
+ bundle :install, :artifice => "compact_index"
+ expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
+ end
+ end
+ end
+
+ context "when the lockfile has aggregated rubygems sources and newer versions of dependencies are available" do
+ before do
+ build_repo gem_repo2 do
+ build_gem "activesupport", "6.0.3.4" do |s|
+ s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
+ s.add_dependency "i18n", ">= 0.7", "< 2"
+ s.add_dependency "minitest", "~> 5.1"
+ s.add_dependency "tzinfo", "~> 1.1"
+ s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2"
+ end
+
+ build_gem "activesupport", "6.1.2.1" do |s|
+ s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
+ s.add_dependency "i18n", ">= 1.6", "< 2"
+ s.add_dependency "minitest", ">= 5.1"
+ s.add_dependency "tzinfo", "~> 2.0"
+ s.add_dependency "zeitwerk", "~> 2.3"
+ end
+
+ build_gem "concurrent-ruby", "1.1.8"
+ build_gem "concurrent-ruby", "1.1.9"
+ build_gem "connection_pool", "2.2.3"
+
+ build_gem "i18n", "1.8.9" do |s|
+ s.add_dependency "concurrent-ruby", "~> 1.0"
+ end
+
+ build_gem "minitest", "5.14.3"
+ build_gem "rack", "2.2.3"
+ build_gem "redis", "4.2.5"
+
+ build_gem "sidekiq", "6.1.3" do |s|
+ s.add_dependency "connection_pool", ">= 2.2.2"
+ s.add_dependency "rack", "~> 2.0"
+ s.add_dependency "redis", ">= 4.2.0"
+ end
+
+ build_gem "thread_safe", "0.3.6"
+
+ build_gem "tzinfo", "1.2.9" do |s|
+ s.add_dependency "thread_safe", "~> 0.1"
+ end
+
+ build_gem "tzinfo", "2.0.4" do |s|
+ s.add_dependency "concurrent-ruby", "~> 1.0"
+ end
+
+ build_gem "zeitwerk", "2.4.2"
+ end
+
+ build_repo gem_repo3 do
+ build_gem "sidekiq-pro", "5.2.1" do |s|
+ s.add_dependency "connection_pool", ">= 2.2.3"
+ s.add_dependency "sidekiq", ">= 6.1.0"
+ end
+ end
+
+ gemfile <<-G
+ # frozen_string_literal: true
+
+ source "https://gem.repo2"
+
+ gem "activesupport"
+
+ source "https://gem.repo3" do
+ gem "sidekiq-pro"
+ end
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gem.repo2/
+ remote: https://gem.repo3/
+ specs:
+ activesupport (6.0.3.4)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (>= 0.7, < 2)
+ minitest (~> 5.1)
+ tzinfo (~> 1.1)
+ zeitwerk (~> 2.2, >= 2.2.2)
+ concurrent-ruby (1.1.8)
+ connection_pool (2.2.3)
+ i18n (1.8.9)
+ concurrent-ruby (~> 1.0)
+ minitest (5.14.3)
+ rack (2.2.3)
+ redis (4.2.5)
+ sidekiq (6.1.3)
+ connection_pool (>= 2.2.2)
+ rack (~> 2.0)
+ redis (>= 4.2.0)
+ sidekiq-pro (5.2.1)
+ connection_pool (>= 2.2.3)
+ sidekiq (>= 6.1.0)
+ thread_safe (0.3.6)
+ tzinfo (1.2.9)
+ thread_safe (~> 0.1)
+ zeitwerk (2.4.2)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ activesupport
+ sidekiq-pro!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "does not install newer versions but updates the lockfile format when running bundle install in non frozen mode, and doesn't warn" do
+ bundle :install, :artifice => "compact_index"
+ expect(err).to be_empty
+
+ expect(the_bundle).to include_gems("activesupport 6.0.3.4")
+ expect(the_bundle).not_to include_gems("activesupport 6.1.2.1")
+ expect(the_bundle).to include_gems("tzinfo 1.2.9")
+ expect(the_bundle).not_to include_gems("tzinfo 2.0.4")
+ expect(the_bundle).to include_gems("concurrent-ruby 1.1.8")
+ expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ activesupport (6.0.3.4)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (>= 0.7, < 2)
+ minitest (~> 5.1)
+ tzinfo (~> 1.1)
+ zeitwerk (~> 2.2, >= 2.2.2)
+ concurrent-ruby (1.1.8)
+ connection_pool (2.2.3)
+ i18n (1.8.9)
+ concurrent-ruby (~> 1.0)
+ minitest (5.14.3)
+ rack (2.2.3)
+ redis (4.2.5)
+ sidekiq (6.1.3)
+ connection_pool (>= 2.2.2)
+ rack (~> 2.0)
+ redis (>= 4.2.0)
+ thread_safe (0.3.6)
+ tzinfo (1.2.9)
+ thread_safe (~> 0.1)
+ zeitwerk (2.4.2)
+
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ sidekiq-pro (5.2.1)
+ connection_pool (>= 2.2.3)
+ sidekiq (>= 6.1.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ activesupport
+ sidekiq-pro!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", :bundler => "< 3" do
+ initial_lockfile = lockfile
+
+ bundle "config set --local frozen true"
+ bundle :install, :artifice => "compact_index"
+
+ expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
+
+ expect(the_bundle).to include_gems("activesupport 6.0.3.4")
+ expect(the_bundle).not_to include_gems("activesupport 6.1.2.1")
+ expect(the_bundle).to include_gems("tzinfo 1.2.9")
+ expect(the_bundle).not_to include_gems("tzinfo 2.0.4")
+ expect(the_bundle).to include_gems("concurrent-ruby 1.1.8")
+ expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9")
+
+ expect(lockfile).to eq(initial_lockfile)
+ end
+
+ it "fails when running bundle install in frozen mode", :bundler => "3" do
+ initial_lockfile = lockfile
+
+ bundle "config set --local frozen true"
+ bundle :install, :artifice => "compact_index", :raise_on_error => false
+
+ expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
+
+ expect(lockfile).to eq(initial_lockfile)
+ end
+
+ it "splits sections and upgrades gems when running bundle update, and doesn't warn" do
+ bundle "update --all", :artifice => "compact_index"
+ expect(err).to be_empty
+
+ expect(the_bundle).not_to include_gems("activesupport 6.0.3.4")
+ expect(the_bundle).to include_gems("activesupport 6.1.2.1")
+ expect(the_bundle).not_to include_gems("tzinfo 1.2.9")
+ expect(the_bundle).to include_gems("tzinfo 2.0.4")
+ expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8")
+ expect(the_bundle).to include_gems("concurrent-ruby 1.1.9")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ activesupport (6.1.2.1)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (>= 1.6, < 2)
+ minitest (>= 5.1)
+ tzinfo (~> 2.0)
+ zeitwerk (~> 2.3)
+ concurrent-ruby (1.1.9)
+ connection_pool (2.2.3)
+ i18n (1.8.9)
+ concurrent-ruby (~> 1.0)
+ minitest (5.14.3)
+ rack (2.2.3)
+ redis (4.2.5)
+ sidekiq (6.1.3)
+ connection_pool (>= 2.2.2)
+ rack (~> 2.0)
+ redis (>= 4.2.0)
+ tzinfo (2.0.4)
+ concurrent-ruby (~> 1.0)
+ zeitwerk (2.4.2)
+
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ sidekiq-pro (5.2.1)
+ connection_pool (>= 2.2.3)
+ sidekiq (>= 6.1.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ activesupport
+ sidekiq-pro!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "upgrades the lockfile format and upgrades the requested gem when running bundle update with an argument" do
+ bundle "update concurrent-ruby", :artifice => "compact_index"
+ expect(err).to be_empty
+
+ expect(the_bundle).to include_gems("activesupport 6.0.3.4")
+ expect(the_bundle).not_to include_gems("activesupport 6.1.2.1")
+ expect(the_bundle).to include_gems("tzinfo 1.2.9")
+ expect(the_bundle).not_to include_gems("tzinfo 2.0.4")
+ expect(the_bundle).to include_gems("concurrent-ruby 1.1.9")
+ expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ activesupport (6.0.3.4)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (>= 0.7, < 2)
+ minitest (~> 5.1)
+ tzinfo (~> 1.1)
+ zeitwerk (~> 2.2, >= 2.2.2)
+ concurrent-ruby (1.1.9)
+ connection_pool (2.2.3)
+ i18n (1.8.9)
+ concurrent-ruby (~> 1.0)
+ minitest (5.14.3)
+ rack (2.2.3)
+ redis (4.2.5)
+ sidekiq (6.1.3)
+ connection_pool (>= 2.2.2)
+ rack (~> 2.0)
+ redis (>= 4.2.0)
+ thread_safe (0.3.6)
+ tzinfo (1.2.9)
+ thread_safe (~> 0.1)
+ zeitwerk (2.4.2)
+
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ sidekiq-pro (5.2.1)
+ connection_pool (>= 2.2.3)
+ sidekiq (>= 6.1.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ activesupport
+ sidekiq-pro!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when a top-level gem has an indirect dependency present in the default source, but with a different version from the one resolved" do
+ before do
+ build_lib "activesupport", "7.0.0.alpha", :path => lib_path("rails/activesupport")
+ build_lib "rails", "7.0.0.alpha", :path => lib_path("rails") do |s|
+ s.add_dependency "activesupport", "= 7.0.0.alpha"
+ end
+
+ build_repo gem_repo2 do
+ build_gem "activesupport", "6.1.2"
+
+ build_gem "webpacker", "5.2.1" do |s|
+ s.add_dependency "activesupport", ">= 5.2"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ gemspec :path => "#{lib_path("rails")}"
+
+ gem "webpacker", "~> 5.0"
+ G
+ end
+
+ it "installs all gems without warning" do
+ bundle :install, :artifice => "compact_index"
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", "rails 7.0.0.alpha")
+ expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", :source => "path@#{lib_path("rails/activesupport")}")
+ expect(the_bundle).to include_gems("rails 7.0.0.alpha", :source => "path@#{lib_path("rails")}")
+ end
+ end
+
+ context "when a pinned gem has an indirect dependency with more than one level of indirection in the default source " do
+ before do
+ build_repo gem_repo3 do
+ build_gem "handsoap", "0.2.5.5" do |s|
+ s.add_dependency "nokogiri", ">= 1.2.3"
+ end
+ end
+
+ update_repo gem_repo2 do
+ build_gem "nokogiri", "1.11.1" do |s|
+ s.add_dependency "racca", "~> 1.4"
+ end
+
+ build_gem "racca", "1.5.2"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo2"
+
+ source "https://gem.repo3" do
+ gem "handsoap"
+ end
+
+ gem "nokogiri"
+ G
+ end
+
+ it "installs from the default source without any warnings or errors and generates a proper lockfile" do
+ expected_lockfile = <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ nokogiri (1.11.1)
+ racca (~> 1.4)
+ racca (1.5.2)
+
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ handsoap (0.2.5.5)
+ nokogiri (>= 1.2.3)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ handsoap!
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install --verbose", :artifice => "compact_index"
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2")
+ expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3")
+ expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2")
+ expect(lockfile).to eq(expected_lockfile)
+
+ # Even if the gems are already installed
+ FileUtils.rm bundled_app_lock
+ bundle "install --verbose", :artifice => "compact_index"
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2")
+ expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3")
+ expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2")
+ expect(lockfile).to eq(expected_lockfile)
+ end
+ end
+
+ context "with a gem that is only found in the wrong source" do
+ before do
+ build_repo gem_repo3 do
+ build_gem "not_in_repo1", "1.0.0"
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false
+ source "https://gem.repo3"
+ gem "not_in_repo1", :source => "https://gem.repo1"
+ G
+ end
+
+ it "does not install the gem" do
+ expect(err).to include("Could not find gem 'not_in_repo1'")
+ end
+ end
+
+ context "with an existing lockfile" do
+ before do
+ system_gems "rack-0.9.1", "rack-1.0.0", :path => default_bundle_path
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo1
+ specs:
+
+ GEM
+ remote: https://gem.repo3
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ rack!
+ L
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ source "https://gem.repo3" do
+ gem 'rack'
+ end
+ G
+ end
+
+ # Reproduction of https://github.com/rubygems/bundler/issues/3298
+ it "does not unlock the installed gem on exec" do
+ expect(the_bundle).to include_gems("rack 0.9.1")
+ end
+ end
+
+ context "with a lockfile with aggregated rubygems sources" do
+ let(:aggregate_gem_section_lockfile) do
+ <<~L
+ GEM
+ remote: https://gem.repo1/
+ remote: https://gem.repo3/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ rack!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ let(:split_gem_section_lockfile) do
+ <<~L
+ GEM
+ remote: https://gem.repo1/
+ specs:
+
+ GEM
+ remote: https://gem.repo3/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ rack!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ before do
+ build_repo gem_repo3 do
+ build_gem "rack", "0.9.1"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo1"
+ source "https://gem.repo3" do
+ gem 'rack'
+ end
+ G
+
+ lockfile aggregate_gem_section_lockfile
+ end
+
+ it "installs the existing lockfile but prints a warning", :bundler => "< 3" do
+ bundle "config set --local deployment true"
+
+ bundle "install", :artifice => "compact_index"
+
+ expect(lockfile).to eq(aggregate_gem_section_lockfile)
+ expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
+ expect(the_bundle).to include_gems("rack 0.9.1", :source => "remote3")
+ end
+
+ it "refuses to install the existing lockfile and prints an error", :bundler => "3" do
+ bundle "config set --local deployment true"
+
+ bundle "install", :artifice => "compact_index", :raise_on_error =>false
+
+ expect(lockfile).to eq(aggregate_gem_section_lockfile)
+ expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
+ expect(out).to be_empty
+ end
+ end
+
+ context "with a path gem in the same Gemfile" do
+ before do
+ build_lib "foo"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :source => "https://gem.repo1"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+ end
+
+ it "does not unlock the non-path gem after install" do
+ bundle :install, :artifice => "compact_index"
+
+ bundle %(exec ruby -e 'puts "OK"')
+
+ expect(out).to include("OK")
+ end
+ end
+ end
+
+ context "when an older version of the same gem also ships with Ruby" do
+ before do
+ system_gems "rack-0.9.1"
+
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "https://gem.repo1"
+ gem "rack" # should come from repo1!
+ G
+ end
+
+ it "installs the gems without any warning" do
+ expect(err).not_to include("Warning")
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+ end
+
+ context "when a single source contains multiple locked gems" do
+ before do
+ # With these gems,
+ build_repo4 do
+ build_gem "foo", "0.1"
+ build_gem "bar", "0.1"
+ end
+
+ # Installing this gemfile...
+ gemfile <<-G
+ source 'https://gem.repo1'
+ gem 'rack'
+ gem 'foo', '~> 0.1', :source => 'https://gem.repo4'
+ gem 'bar', '~> 0.1', :source => 'https://gem.repo4'
+ G
+
+ bundle "config set --local path ../gems/system"
+ bundle :install, :artifice => "compact_index"
+
+ # And then we add some new versions...
+ update_repo4 do
+ build_gem "foo", "0.2"
+ build_gem "bar", "0.3"
+ end
+ end
+
+ it "allows them to be unlocked separately" do
+ # And install this gemfile, updating only foo.
+ install_gemfile <<-G, :artifice => "compact_index"
+ source 'https://gem.repo1'
+ gem 'rack'
+ gem 'foo', '~> 0.2', :source => 'https://gem.repo4'
+ gem 'bar', '~> 0.1', :source => 'https://gem.repo4'
+ G
+
+ # It should update foo to 0.2, but not the (locked) bar 0.1
+ expect(the_bundle).to include_gems("foo 0.2", "bar 0.1")
+ end
+ end
+
+ context "re-resolving" do
+ context "when there is a mix of sources in the gemfile" do
+ before do
+ build_repo gem_repo3 do
+ build_gem "rack"
+ end
+
+ build_lib "path1"
+ build_lib "path2"
+ build_git "git1"
+ build_git "git2"
+
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "https://gem.repo1"
+ gem "rails"
+
+ source "https://gem.repo3" do
+ gem "rack"
+ end
+
+ gem "path1", :path => "#{lib_path("path1-1.0")}"
+ gem "path2", :path => "#{lib_path("path2-1.0")}"
+ gem "git1", :git => "#{lib_path("git1-1.0")}"
+ gem "git2", :git => "#{lib_path("git2-1.0")}"
+ G
+ end
+
+ it "does not re-resolve" do
+ bundle :install, :artifice => "compact_index", :verbose => true
+ expect(out).to include("using resolution from the lockfile")
+ expect(out).not_to include("re-resolving dependencies")
+ end
+ end
+ end
+
+ context "when a gem is installed to system gems" do
+ before do
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "https://gem.repo1"
+ gem "rack"
+ G
+ end
+
+ context "and the gemfile changes" do
+ it "is still able to find that gem from remote sources" do
+ build_repo4 do
+ build_gem "rack", "2.0.1.1.forked"
+ build_gem "thor", "0.19.1.1.forked"
+ end
+
+ # When this gemfile is installed...
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "https://gem.repo1"
+
+ source "https://gem.repo4" do
+ gem "rack", "2.0.1.1.forked"
+ gem "thor"
+ end
+ gem "rack-obama"
+ G
+
+ # Then we change the Gemfile by adding a version to thor
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ source "https://gem.repo4" do
+ gem "rack", "2.0.1.1.forked"
+ gem "thor", "0.19.1.1.forked"
+ end
+ gem "rack-obama"
+ G
+
+ # But we should still be able to find rack 2.0.1.1.forked and install it
+ bundle :install, :artifice => "compact_index"
+ end
+ end
+ end
+
+ describe "source changed to one containing a higher version of a dependency" do
+ before do
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "https://gem.repo1"
+
+ gem "rack"
+ G
+
+ build_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "bar"
+ end
+
+ build_lib("gemspec_test", :path => tmp.join("gemspec_test")) do |s|
+ s.add_dependency "bar", "=1.0.0"
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "https://gem.repo2"
+ gem "rack"
+ gemspec :path => "#{tmp.join("gemspec_test")}"
+ G
+ end
+
+ it "conservatively installs the existing locked version" do
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+ end
+
+ context "when Gemfile overrides a gemspec development dependency to change the default source" do
+ before do
+ build_repo4 do
+ build_gem "bar"
+ end
+
+ build_lib("gemspec_test", :path => tmp.join("gemspec_test")) do |s|
+ s.add_development_dependency "bar"
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "https://gem.repo1"
+
+ source "https://gem.repo4" do
+ gem "bar"
+ end
+
+ gemspec :path => "#{tmp.join("gemspec_test")}"
+ G
+ end
+
+ it "does not print warnings" do
+ expect(err).to be_empty
+ end
+ end
+
+ it "doesn't update version when a gem uses a source block but a higher version from another source is already installed locally" do
+ build_repo2 do
+ build_gem "example", "0.1.0"
+ end
+
+ build_repo4 do
+ build_gem "example", "1.0.2"
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "https://gem.repo4"
+
+ gem "example", :source => "https://gem.repo2"
+ G
+
+ bundle "info example"
+ expect(out).to include("example (0.1.0)")
+
+ system_gems "example-1.0.2", :path => default_bundle_path, :gem_repo => gem_repo4
+
+ bundle "update example --verbose", :artifice => "compact_index"
+ expect(out).not_to include("Using example 1.0.2")
+ expect(out).to include("Using example 0.1.0")
+ end
+
+ it "fails immediately with a helpful error when a rubygems source does not exist and bundler/setup is required" do
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ source "https://gem.repo4" do
+ gem "example"
+ end
+ G
+
+ simulate_bundler_version_when_missing_prerelease_default_gem_activation do
+ ruby <<~R, :raise_on_error => false
+ require 'bundler/setup'
+ R
+ end
+
+ expect(last_command).to be_failure
+ expect(err).to include("Could not find gem 'example' in locally installed gems.")
+ end
+
+ it "fails immediately with a helpful error when a non retriable network error happens while resolving sources" do
+ gemfile <<-G
+ source "https://gem.repo1"
+
+ source "https://gem.repo4" do
+ gem "example"
+ end
+ G
+
+ bundle "install", :artifice => nil, :raise_on_error => false
+
+ expect(last_command).to be_failure
+ expect(err).to include("Could not reach host gem.repo4. Check your network connection and try again.")
+ end
+
+ context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "< 3" do
+ it "succeeds but warns, suggesting a source block" do
+ build_repo4 do
+ build_gem "depends_on_rack" do |s|
+ s.add_dependency "rack"
+ end
+ build_gem "rack"
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+
+ source "https://gem.repo4" do
+ gem "depends_on_rack"
+ end
+
+ source "https://gem.repo1" do
+ gem "thin"
+ end
+ G
+ expect(err).to eq strip_whitespace(<<-EOS).strip
+ Warning: The gem 'rack' was found in multiple relevant sources.
+ * rubygems repository https://gem.repo1/
+ * rubygems repository https://gem.repo4/
+ You should add this gem to the source block for the source you wish it to be installed from.
+ EOS
+ expect(last_command).to be_success
+ expect(the_bundle).to be_locked
+ end
+ end
+
+ context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "3" do
+ it "raises, suggesting a source block" do
+ build_repo4 do
+ build_gem "depends_on_rack" do |s|
+ s.add_dependency "rack"
+ end
+ build_gem "rack"
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ source "https://gem.repo4" do
+ gem "depends_on_rack"
+ end
+ source "https://gem.repo1" do
+ gem "thin"
+ end
+ G
+ expect(last_command).to be_failure
+ expect(err).to eq strip_whitespace(<<-EOS).strip
+ The gem 'rack' was found in multiple relevant sources.
+ * rubygems repository https://gem.repo1/
+ * rubygems repository https://gem.repo4/
+ You must add this gem to the source block for the source you wish it to be installed from.
+ EOS
+ expect(the_bundle).not_to be_locked
+ end
+ end
+
+ context "when upgrading a lockfile suffering from dependency confusion" do
+ before do
+ build_repo4 do
+ build_gem "mime-types", "3.0.0"
+ end
+
+ build_repo2 do
+ build_gem "capybara", "2.5.0" do |s|
+ s.add_dependency "mime-types", ">= 1.16"
+ end
+
+ build_gem "mime-types", "3.3.1"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo2"
+
+ gem "capybara", "~> 2.5.0"
+
+ source "https://gem.repo4" do
+ gem "mime-types", "~> 3.0"
+ end
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ remote: https://gem.repo4/
+ specs:
+ capybara (2.5.0)
+ mime-types (>= 1.16)
+ mime-types (3.3.1)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ capybara (~> 2.5.0)
+ mime-types (~> 3.0)!
+ L
+ end
+
+ it "upgrades the lockfile correctly" do
+ bundle "lock --update", :artifice => "compact_index"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ capybara (2.5.0)
+ mime-types (>= 1.16)
+
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ mime-types (3.0.0)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ capybara (~> 2.5.0)
+ mime-types (~> 3.0)!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when default source includes old gems with nil required_ruby_version" do
+ before do
+ build_repo2 do
+ build_gem "ruport", "1.7.0.3" do |s|
+ s.add_dependency "pdf-writer", "1.1.8"
+ end
+ end
+
+ build_repo gem_repo4 do
+ build_gem "pdf-writer", "1.1.8"
+ end
+
+ path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz"
+ spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path)))
+ spec.instance_variable_set(:@required_ruby_version, nil)
+ File.open(path, "wb") do |f|
+ f.write Gem.deflate(Marshal.dump(spec))
+ end
+
+ gemfile <<~G
+ source "https://localgemserver.test"
+
+ gem "ruport", "= 1.7.0.3", :source => "https://localgemserver.test/extra"
+ G
+ end
+
+ it "handles that fine" do
+ bundle "install", :artifice => "compact_index_extra", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://localgemserver.test/
+ specs:
+ pdf-writer (1.1.8)
+
+ GEM
+ remote: https://localgemserver.test/extra/
+ specs:
+ ruport (1.7.0.3)
+ pdf-writer (= 1.1.8)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ ruport (= 1.7.0.3)!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when default source includes old gems with nil required_rubygems_version" do
+ before do
+ build_repo2 do
+ build_gem "ruport", "1.7.0.3" do |s|
+ s.add_dependency "pdf-writer", "1.1.8"
+ end
+ end
+
+ build_repo gem_repo4 do
+ build_gem "pdf-writer", "1.1.8"
+ end
+
+ path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz"
+ spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path)))
+ spec.instance_variable_set(:@required_rubygems_version, nil)
+ File.open(path, "wb") do |f|
+ f.write Gem.deflate(Marshal.dump(spec))
+ end
+
+ gemfile <<~G
+ source "https://localgemserver.test"
+
+ gem "ruport", "= 1.7.0.3", :source => "https://localgemserver.test/extra"
+ G
+ end
+
+ it "handles that fine" do
+ bundle "install", :artifice => "compact_index_extra", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://localgemserver.test/
+ specs:
+ pdf-writer (1.1.8)
+
+ GEM
+ remote: https://localgemserver.test/extra/
+ specs:
+ ruport (1.7.0.3)
+ pdf-writer (= 1.1.8)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ ruport (= 1.7.0.3)!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when default source uses the old API and includes old gems with nil required_rubygems_version" do
+ before do
+ build_repo4 do
+ build_gem "pdf-writer", "1.1.8"
+ end
+
+ path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz"
+ spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path)))
+ spec.instance_variable_set(:@required_rubygems_version, nil)
+ File.open(path, "wb") do |f|
+ f.write Gem.deflate(Marshal.dump(spec))
+ end
+
+ gemfile <<~G
+ source "https://localgemserver.test"
+
+ gem "pdf-writer", "= 1.1.8"
+ G
+ end
+
+ it "handles that fine" do
+ bundle "install --verbose", :artifice => "endpoint", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://localgemserver.test/
+ specs:
+ pdf-writer (1.1.8)
+
+ PLATFORMS
+ #{local_platform}
+
+ DEPENDENCIES
+ pdf-writer (= 1.1.8)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when mistakenly adding a top level gem already depended on and cached under the wrong source" do
+ before do
+ build_repo4 do
+ build_gem "some_private_gem", "0.1.0" do |s|
+ s.add_dependency "example", "~> 1.0"
+ end
+ end
+
+ build_repo2 do
+ build_gem "example", "1.0.0"
+ end
+
+ install_gemfile <<~G, :artifice => "compact_index"
+ source "https://gem.repo2"
+
+ source "https://gem.repo4" do
+ gem "some_private_gem"
+ end
+ G
+
+ gemfile <<~G
+ source "https://gem.repo2"
+
+ source "https://gem.repo4" do
+ gem "some_private_gem"
+ gem "example" # MISTAKE, example is not available at gem.repo4
+ end
+ G
+ end
+
+ it "shows a proper error message and does not generate a corrupted lockfile" do
+ expect do
+ bundle :install, :artifice => "compact_index", :raise_on_error => false, :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ end.not_to change { lockfile }
+
+ expect(err).to include("Could not find gem 'example' in rubygems repository https://gem.repo4/")
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb
new file mode 100644
index 0000000000..cab53a663a
--- /dev/null
+++ b/spec/bundler/install/gemfile/specific_platform_spec.rb
@@ -0,0 +1,971 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with specific platforms" do
+ let(:google_protobuf) { <<-G }
+ source "#{file_uri_for(gem_repo2)}"
+ gem "google-protobuf"
+ G
+
+ it "locks to the specific darwin platform" do
+ simulate_platform "x86_64-darwin-15" do
+ setup_multiplatform_gem
+ install_gemfile(google_protobuf)
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ expect(the_bundle.locked_gems.platforms).to eq([pl("x86_64-darwin-15")])
+ expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin")
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[
+ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin
+ ])
+ end
+ end
+
+ it "understands that a non-platform specific gem in a old lockfile doesn't necessarily mean installing the non-specific variant" do
+ simulate_platform "x86_64-darwin-15" do
+ setup_multiplatform_gem
+
+ system_gems "bundler-2.1.4"
+
+ # Consistent location to install and look for gems
+ bundle "config set --local path vendor/bundle", :env => { "BUNDLER_VERSION" => "2.1.4" }
+
+ install_gemfile(google_protobuf, :env => { "BUNDLER_VERSION" => "2.1.4" })
+
+ # simulate lockfile created with old bundler, which only locks for ruby platform
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ google-protobuf (3.0.0.alpha.5.0.5.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ google-protobuf
+
+ BUNDLED WITH
+ 2.1.4
+ L
+
+ # force strict usage of the lock file by setting frozen mode
+ bundle "config set --local frozen true", :env => { "BUNDLER_VERSION" => "2.1.4" }
+
+ # make sure the platform that got actually installed with the old bundler is used
+ expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin")
+ end
+ end
+
+ it "understands that a non-platform specific gem in a new lockfile locked only to RUBY doesn't necessarily mean installing the non-specific variant" do
+ simulate_platform "x86_64-darwin-15" do
+ setup_multiplatform_gem
+
+ system_gems "bundler-2.1.4"
+
+ # Consistent location to install and look for gems
+ bundle "config set --local path vendor/bundle", :env => { "BUNDLER_VERSION" => "2.1.4" }
+
+ gemfile google_protobuf
+
+ # simulate lockfile created with old bundler, which only locks for ruby platform
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ google-protobuf (3.0.0.alpha.4.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ google-protobuf
+
+ BUNDLED WITH
+ 2.1.4
+ L
+
+ bundle "update", :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+
+ # make sure the platform that the platform specific dependency is used, since we're only locked to ruby
+ expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin")
+
+ # make sure we're still only locked to ruby
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ google-protobuf (3.0.0.alpha.5.0.5.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ google-protobuf
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when running on a legacy lockfile locked only to RUBY" do
+ around do |example|
+ build_repo4 do
+ build_gem "nokogiri", "1.3.10"
+ build_gem "nokogiri", "1.3.10" do |s|
+ s.platform = "arm64-darwin"
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "nokogiri"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ nokogiri (1.3.10)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "arm64-darwin-22", &example
+ end
+
+ it "still installs the generic RUBY variant if necessary" do
+ bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ expect(out).to include("Installing nokogiri 1.3.10")
+ end
+
+ it "still installs the generic RUBY variant if necessary, even in frozen mode" do
+ bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "BUNDLE_FROZEN" => "true" }
+ expect(out).to include("Installing nokogiri 1.3.10")
+ end
+ end
+
+ it "doesn't discard previously installed platform specific gem and fall back to ruby on subsequent bundles" do
+ simulate_platform "x86_64-darwin-15" do
+ build_repo2 do
+ build_gem("libv8", "8.4.255.0")
+ build_gem("libv8", "8.4.255.0") {|s| s.platform = "universal-darwin" }
+
+ build_gem("mini_racer", "1.0.0") do |s|
+ s.add_runtime_dependency "libv8"
+ end
+ end
+
+ system_gems "bundler-2.1.4"
+
+ # Consistent location to install and look for gems
+ bundle "config set --local path vendor/bundle", :env => { "BUNDLER_VERSION" => "2.1.4" }
+
+ gemfile <<-G
+ source "https://localgemserver.test"
+ gem "libv8"
+ G
+
+ # simulate lockfile created with old bundler, which only locks for ruby platform
+ lockfile <<-L
+ GEM
+ remote: https://localgemserver.test/
+ specs:
+ libv8 (8.4.255.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ libv8
+
+ BUNDLED WITH
+ 2.1.4
+ L
+
+ bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_VERSION" => "2.1.4", "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ expect(out).to include("Installing libv8 8.4.255.0 (universal-darwin)")
+
+ bundle "add mini_racer --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ expect(out).to include("Using libv8 8.4.255.0 (universal-darwin)")
+ end
+ end
+
+ it "chooses platform specific gems even when resolving upon materialization and the API returns more specific platforms first" do
+ simulate_platform "x86_64-darwin-15" do
+ build_repo4 do
+ build_gem("grpc", "1.50.0")
+ build_gem("grpc", "1.50.0") {|s| s.platform = "universal-darwin" }
+ end
+
+ gemfile <<-G
+ source "https://localgemserver.test"
+ gem "grpc"
+ G
+
+ # simulate lockfile created with old bundler, which only locks for ruby platform
+ lockfile <<-L
+ GEM
+ remote: https://localgemserver.test/
+ specs:
+ grpc (1.50.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ grpc
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install --verbose", :artifice => "compact_index_precompiled_before", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ expect(out).to include("Installing grpc 1.50.0 (universal-darwin)")
+ end
+ end
+
+ it "caches the universal-darwin gem when --all-platforms is passed and properly picks it up on further bundler invocations" do
+ simulate_platform "x86_64-darwin-15" do
+ setup_multiplatform_gem
+ gemfile(google_protobuf)
+ bundle "cache --all-platforms"
+ expect(cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin")).to exist
+
+ bundle "install --verbose"
+ expect(err).to be_empty
+ end
+ end
+
+ it "caches the universal-darwin gem when cache_all_platforms is configured and properly picks it up on further bundler invocations" do
+ simulate_platform "x86_64-darwin-15" do
+ setup_multiplatform_gem
+ gemfile(google_protobuf)
+ bundle "config set --local cache_all_platforms true"
+ bundle "cache"
+ expect(cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin")).to exist
+
+ bundle "install --verbose"
+ expect(err).to be_empty
+ end
+ end
+
+ it "caches multiplatform git gems with a single gemspec when --all-platforms is passed" do
+ git = build_git "pg_array_parser", "1.0"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "pg_array_parser", :git => "#{lib_path("pg_array_parser-1.0")}"
+ G
+
+ lockfile <<-L
+ GIT
+ remote: #{lib_path("pg_array_parser-1.0")}
+ revision: #{git.ref_for("main")}
+ specs:
+ pg_array_parser (1.0-java)
+ pg_array_parser (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ java
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ pg_array_parser!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "config set --local cache_all true"
+ bundle "cache --all-platforms"
+
+ expect(err).to be_empty
+ end
+
+ it "uses the platform-specific gem with extra dependencies" do
+ simulate_platform "x86_64-darwin-15" do
+ setup_multiplatform_gem_with_different_dependencies_per_platform
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "facter"
+ G
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+
+ expect(the_bundle.locked_gems.platforms).to eq([pl("x86_64-darwin-15")])
+ expect(the_bundle).to include_gems("facter 2.4.6 universal-darwin", "CFPropertyList 1.0")
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(["CFPropertyList-1.0",
+ "facter-2.4.6-universal-darwin"])
+ end
+ end
+
+ context "when adding a platform via lock --add_platform" do
+ before do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ end
+
+ it "adds the foreign platform" do
+ simulate_platform "x86_64-darwin-15" do
+ setup_multiplatform_gem
+ install_gemfile(google_protobuf)
+ bundle "lock --add-platform=#{x64_mingw32}"
+
+ expect(the_bundle.locked_gems.platforms).to eq([x64_mingw32, pl("x86_64-darwin-15")])
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[
+ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin
+ google-protobuf-3.0.0.alpha.5.0.5.1-x64-mingw32
+ ])
+ end
+ end
+
+ it "falls back on plain ruby when that version doesn't have a platform-specific gem" do
+ simulate_platform "x86_64-darwin-15" do
+ setup_multiplatform_gem
+ install_gemfile(google_protobuf)
+ bundle "lock --add-platform=#{java}"
+
+ expect(the_bundle.locked_gems.platforms).to eq([java, pl("x86_64-darwin-15")])
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[
+ google-protobuf-3.0.0.alpha.5.0.5.1
+ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin
+ ])
+ end
+ end
+ end
+
+ it "installs sorbet-static, which does not provide a pure ruby variant, just fine", :truffleruby do
+ skip "does not apply to Windows" if Gem.win_platform?
+
+ build_repo2 do
+ build_gem("sorbet-static", "0.5.6403") {|s| s.platform = Bundler.local_platform }
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "sorbet-static", "0.5.6403"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ sorbet-static (0.5.6403-#{Bundler.local_platform})
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ sorbet-static (= 0.5.6403)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install --verbose"
+ end
+
+ it "does not resolve if the current platform does not match any of available platform specific variants for a top level dependency" do
+ build_repo4 do
+ build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "x86_64-linux" }
+ build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "universal-darwin-20" }
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "sorbet-static", "0.5.6433"
+ G
+
+ error_message = <<~ERROR.strip
+ Could not find gem 'sorbet-static (= 0.5.6433)' with platform 'arm64-darwin-21' in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally.
+
+ The source contains the following gems matching 'sorbet-static (= 0.5.6433)':
+ * sorbet-static-0.5.6433-universal-darwin-20
+ * sorbet-static-0.5.6433-x86_64-linux
+ ERROR
+
+ simulate_platform "arm64-darwin-21" do
+ bundle "lock", :raise_on_error => false
+ end
+
+ expect(err).to include(error_message).once
+
+ # Make sure it doesn't print error twice in verbose mode
+
+ simulate_platform "arm64-darwin-21" do
+ bundle "lock --verbose", :raise_on_error => false
+ end
+
+ expect(err).to include(error_message).once
+ end
+
+ it "does not resolve if the current platform does not match any of available platform specific variants for a transitive dependency" do
+ build_repo4 do
+ build_gem("sorbet", "0.5.6433") {|s| s.add_dependency "sorbet-static", "= 0.5.6433" }
+ build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "x86_64-linux" }
+ build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "universal-darwin-20" }
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "sorbet", "0.5.6433"
+ G
+
+ error_message = <<~ERROR.strip
+ Could not find compatible versions
+
+ Because every version of sorbet depends on sorbet-static = 0.5.6433
+ and sorbet-static = 0.5.6433 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally for any resolution platforms (arm64-darwin-21),
+ sorbet cannot be used.
+ So, because Gemfile depends on sorbet = 0.5.6433,
+ version solving has failed.
+
+ The source contains the following gems matching 'sorbet-static (= 0.5.6433)':
+ * sorbet-static-0.5.6433-universal-darwin-20
+ * sorbet-static-0.5.6433-x86_64-linux
+ ERROR
+
+ simulate_platform "arm64-darwin-21" do
+ bundle "lock", :raise_on_error => false
+ end
+
+ expect(err).to include(error_message).once
+
+ # Make sure it doesn't print error twice in verbose mode
+
+ simulate_platform "arm64-darwin-21" do
+ bundle "lock --verbose", :raise_on_error => false
+ end
+
+ expect(err).to include(error_message).once
+ end
+
+ it "does not generate a lockfile if RUBY platform is forced and some gem has no RUBY variant available" do
+ build_repo4 do
+ build_gem("sorbet-static", "0.5.9889") {|s| s.platform = Gem::Platform.local }
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "sorbet-static", "0.5.9889"
+ G
+
+ bundle "lock", :raise_on_error => false, :env => { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" }
+
+ expect(err).to include <<~ERROR.rstrip
+ Could not find gem 'sorbet-static (= 0.5.9889)' with platform 'ruby' in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally.
+
+ The source contains the following gems matching 'sorbet-static (= 0.5.9889)':
+ * sorbet-static-0.5.9889-#{Gem::Platform.local}
+ ERROR
+ end
+
+ it "automatically fixes the lockfile if RUBY platform is locked and some gem has no RUBY variant available" do
+ build_repo4 do
+ build_gem("sorbet-static-and-runtime", "0.5.10160") do |s|
+ s.add_runtime_dependency "sorbet", "= 0.5.10160"
+ s.add_runtime_dependency "sorbet-runtime", "= 0.5.10160"
+ end
+
+ build_gem("sorbet", "0.5.10160") do |s|
+ s.add_runtime_dependency "sorbet-static", "= 0.5.10160"
+ end
+
+ build_gem("sorbet-runtime", "0.5.10160")
+
+ build_gem("sorbet-static", "0.5.10160") do |s|
+ s.platform = Gem::Platform.local
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "sorbet-static-and-runtime"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ sorbet (0.5.10160)
+ sorbet-static (= 0.5.10160)
+ sorbet-runtime (0.5.10160)
+ sorbet-static (0.5.10160-#{Gem::Platform.local})
+ sorbet-static-and-runtime (0.5.10160)
+ sorbet (= 0.5.10160)
+ sorbet-runtime (= 0.5.10160)
+
+ PLATFORMS
+ #{lockfile_platforms("ruby")}
+
+ DEPENDENCIES
+ sorbet-static-and-runtime
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ sorbet (0.5.10160)
+ sorbet-static (= 0.5.10160)
+ sorbet-runtime (0.5.10160)
+ sorbet-static (0.5.10160-#{Gem::Platform.local})
+ sorbet-static-and-runtime (0.5.10160)
+ sorbet (= 0.5.10160)
+ sorbet-runtime (= 0.5.10160)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ sorbet-static-and-runtime
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically fixes the lockfile if both RUBY platform and a more specific platform are locked, and some gem has no RUBY variant available" do
+ build_repo4 do
+ build_gem "nokogiri", "1.12.0"
+ build_gem "nokogiri", "1.12.0" do |s|
+ s.platform = "x86_64-darwin"
+ end
+
+ build_gem "nokogiri", "1.13.0"
+ build_gem "nokogiri", "1.13.0" do |s|
+ s.platform = "x86_64-darwin"
+ end
+
+ build_gem("sorbet-static", "0.5.10601") do |s|
+ s.platform = "x86_64-darwin"
+ end
+ end
+
+ simulate_platform "x86_64-darwin-22" do
+ install_gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "nokogiri"
+ gem "sorbet-static"
+ G
+ end
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ nokogiri (1.12.0)
+ nokogiri (1.12.0-x86_64-darwin)
+ sorbet-static (0.5.10601-x86_64-darwin)
+
+ PLATFORMS
+ ruby
+ x86_64-darwin
+
+ DEPENDENCIES
+ nokogiri
+ sorbet
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ simulate_platform "x86_64-darwin-22" do
+ bundle "update --conservative nokogiri"
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ nokogiri (1.13.0-x86_64-darwin)
+ sorbet-static (0.5.10601-x86_64-darwin)
+
+ PLATFORMS
+ x86_64-darwin
+
+ DEPENDENCIES
+ nokogiri
+ sorbet-static
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically fixes the lockfile if only RUBY platform is locked and some gem has no RUBY variant available" do
+ build_repo4 do
+ build_gem("sorbet-static-and-runtime", "0.5.10160") do |s|
+ s.add_runtime_dependency "sorbet", "= 0.5.10160"
+ s.add_runtime_dependency "sorbet-runtime", "= 0.5.10160"
+ end
+
+ build_gem("sorbet", "0.5.10160") do |s|
+ s.add_runtime_dependency "sorbet-static", "= 0.5.10160"
+ end
+
+ build_gem("sorbet-runtime", "0.5.10160")
+
+ build_gem("sorbet-static", "0.5.10160") do |s|
+ s.platform = Gem::Platform.local
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "sorbet-static-and-runtime"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ sorbet (0.5.10160)
+ sorbet-static (= 0.5.10160)
+ sorbet-runtime (0.5.10160)
+ sorbet-static (0.5.10160-#{Gem::Platform.local})
+ sorbet-static-and-runtime (0.5.10160)
+ sorbet (= 0.5.10160)
+ sorbet-runtime (= 0.5.10160)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ sorbet-static-and-runtime
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "update"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ sorbet (0.5.10160)
+ sorbet-static (= 0.5.10160)
+ sorbet-runtime (0.5.10160)
+ sorbet-static (0.5.10160-#{Gem::Platform.local})
+ sorbet-static-and-runtime (0.5.10160)
+ sorbet (= 0.5.10160)
+ sorbet-runtime (= 0.5.10160)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ sorbet-static-and-runtime
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically fixes the lockfile without removing other variants if it's missing platform gems, but they are installed locally" do
+ simulate_platform "x86_64-darwin-21" do
+ build_repo4 do
+ build_gem("sorbet-static", "0.5.10549") do |s|
+ s.platform = "universal-darwin-20"
+ end
+
+ build_gem("sorbet-static", "0.5.10549") do |s|
+ s.platform = "universal-darwin-21"
+ end
+ end
+
+ # Make sure sorbet-static-0.5.10549-universal-darwin-21 is installed
+ install_gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "sorbet-static", "= 0.5.10549"
+ G
+
+ # Make sure the lockfile is missing sorbet-static-0.5.10549-universal-darwin-21
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ sorbet-static (0.5.10549-universal-darwin-20)
+
+ PLATFORMS
+ x86_64-darwin
+
+ DEPENDENCIES
+ sorbet-static (= 0.5.10549)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ sorbet-static (0.5.10549-universal-darwin-20)
+ sorbet-static (0.5.10549-universal-darwin-21)
+
+ PLATFORMS
+ x86_64-darwin
+
+ DEPENDENCIES
+ sorbet-static (= 0.5.10549)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ it "does not remove ruby if gems for other platforms, and not present in the lockfile, exist in the Gemfile" do
+ build_repo4 do
+ build_gem "nokogiri", "1.13.8"
+ build_gem "nokogiri", "1.13.8" do |s|
+ s.platform = Gem::Platform.local
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "nokogiri"
+
+ gem "tzinfo", "~> 1.2", platform: :#{not_local_tag}
+ G
+
+ original_lockfile = <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ nokogiri (1.13.8)
+ nokogiri (1.13.8-#{Gem::Platform.local})
+
+ PLATFORMS
+ #{lockfile_platforms("ruby")}
+
+ DEPENDENCIES
+ nokogiri
+ tzinfo (~> 1.2)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lockfile original_lockfile
+
+ bundle "lock --update"
+
+ expect(lockfile).to eq(original_lockfile)
+ end
+
+ it "does not remove ruby when adding a new gem to the Gemfile" do
+ build_repo4 do
+ build_gem "concurrent-ruby", "1.2.2"
+ build_gem "rack", "3.0.7"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "concurrent-ruby"
+ gem "rack"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ concurrent-ruby (1.2.2)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ concurrent-ruby
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ concurrent-ruby (1.2.2)
+ rack (3.0.7)
+
+ PLATFORMS
+ #{formatted_lockfile_platforms(*["ruby", generic_local_platform].uniq)}
+
+ DEPENDENCIES
+ concurrent-ruby
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "can fallback to a source gem when platform gems are incompatible with current ruby version" do
+ setup_multiplatform_gem_with_source_gem
+
+ source = file_uri_for(gem_repo2)
+
+ gemfile <<~G
+ source "#{source}"
+
+ gem "my-precompiled-gem"
+ G
+
+ # simulate lockfile which includes both a precompiled gem with:
+ # - Gem the current platform (with incompatible ruby version)
+ # - A source gem with compatible ruby version
+ lockfile <<-L
+ GEM
+ remote: #{source}/
+ specs:
+ my-precompiled-gem (3.0.0)
+ my-precompiled-gem (3.0.0-#{Bundler.local_platform})
+
+ PLATFORMS
+ ruby
+ #{Bundler.local_platform}
+
+ DEPENDENCIES
+ my-precompiled-gem
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle :install
+ end
+
+ it "automatically fixes the lockfile if the specific platform is locked and we move to a newer ruby version for which a native package is not available" do
+ #
+ # Given an existing application using native gems (e.g., nokogiri)
+ # And a lockfile generated with a stable ruby version
+ # When want test the application against ruby-head and `bundle install`
+ # Then bundler should fall back to the generic ruby platform gem
+ #
+ simulate_platform "x86_64-linux" do
+ build_repo4 do
+ build_gem "nokogiri", "1.14.0"
+ build_gem "nokogiri", "1.14.0" do |s|
+ s.platform = "x86_64-linux"
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "nokogiri", "1.14.0"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ nokogiri (1.14.0-x86_64-linux)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri (= 1.14.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle :install
+
+ expect(lockfile).to eq(<<~L)
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ nokogiri (1.14.0)
+
+ PLATFORMS
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri (= 1.14.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ private
+
+ def setup_multiplatform_gem
+ build_repo2 do
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1")
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86_64-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x64-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "universal-darwin" }
+
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86_64-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x64-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5")
+
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "universal-darwin" }
+
+ build_gem("google-protobuf", "3.0.0.alpha.4.0")
+ build_gem("google-protobuf", "3.0.0.alpha.3.1.pre")
+ end
+ end
+
+ def setup_multiplatform_gem_with_different_dependencies_per_platform
+ build_repo2 do
+ build_gem("facter", "2.4.6")
+ build_gem("facter", "2.4.6") do |s|
+ s.platform = "universal-darwin"
+ s.add_runtime_dependency "CFPropertyList"
+ end
+ build_gem("CFPropertyList")
+ end
+ end
+
+ def setup_multiplatform_gem_with_source_gem
+ build_repo2 do
+ build_gem("my-precompiled-gem", "3.0.0")
+ build_gem("my-precompiled-gem", "3.0.0") do |s|
+ s.platform = Bundler.local_platform
+
+ # purposely unresolvable
+ s.required_ruby_version = ">= 1000.0.0"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb
new file mode 100644
index 0000000000..e643573454
--- /dev/null
+++ b/spec/bundler/install/gemfile_spec.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ context "with duplicated gems" do
+ it "will display a warning" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem 'rails', '~> 4.0.0'
+ gem 'rails', '~> 4.0.0'
+ G
+ expect(err).to include("more than once")
+ end
+ end
+
+ context "with --gemfile" do
+ it "finds the gemfile" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ bundle :install, :gemfile => bundled_app("NotGemfile")
+
+ # Specify BUNDLE_GEMFILE for `the_bundle`
+ # to retrieve the proper Gemfile
+ ENV["BUNDLE_GEMFILE"] = "NotGemfile"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with gemfile set via config" do
+ before do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ bundle "config set --local gemfile #{bundled_app("NotGemfile")}"
+ end
+ it "uses the gemfile to install" do
+ bundle "install"
+ bundle "list"
+
+ expect(out).to include("rack (1.0.0)")
+ end
+ it "uses the gemfile while in a subdirectory" do
+ bundled_app("subdir").mkpath
+ bundle "install", :dir => bundled_app("subdir")
+ bundle "list", :dir => bundled_app("subdir")
+
+ expect(out).to include("rack (1.0.0)")
+ end
+ end
+
+ context "with deprecated features" do
+ it "reports that lib is an invalid option" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack", :lib => "rack"
+ G
+
+ bundle :install, :raise_on_error => false
+ expect(err).to match(/You passed :lib as an option for gem 'rack', but it is invalid/)
+ end
+ end
+
+ context "with engine specified in symbol", :jruby_only do
+ it "does not raise any error parsing Gemfile" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "#{RUBY_VERSION}", :engine => :jruby, :engine_version => "#{RUBY_ENGINE_VERSION}"
+ G
+
+ expect(out).to match(/Bundle complete!/)
+ end
+
+ it "installation succeeds" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ ruby "#{RUBY_VERSION}", :engine => :jruby, :engine_version => "#{RUBY_ENGINE_VERSION}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with a Gemfile containing non-US-ASCII characters" do
+ it "reads the Gemfile with the UTF-8 encoding by default" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ str = "Il était une fois ..."
+ puts "The source encoding is: " + str.encoding.name
+ G
+
+ expect(out).to include("The source encoding is: UTF-8")
+ expect(out).not_to include("The source encoding is: ASCII-8BIT")
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "respects the magic encoding comment" do
+ # NOTE: This works thanks to #eval interpreting the magic encoding comment
+ install_gemfile <<-G
+ # encoding: iso-8859-1
+ source "#{file_uri_for(gem_repo1)}"
+
+ str = "Il #{"\xE9".dup.force_encoding("binary")}tait une fois ..."
+ puts "The source encoding is: " + str.encoding.name
+ G
+
+ expect(out).to include("The source encoding is: ISO-8859-1")
+ expect(out).to include("Bundle complete!")
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb
new file mode 100644
index 0000000000..65be262748
--- /dev/null
+++ b/spec/bundler/install/gems/compact_index_spec.rb
@@ -0,0 +1,954 @@
+# frozen_string_literal: true
+
+RSpec.describe "compact index api" do
+ let(:source_hostname) { "localgemserver.test" }
+ let(:source_uri) { "http://#{source_hostname}" }
+
+ it "should use the API" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "compact_index"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should URI encode gem names" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem " sinatra"
+ G
+
+ bundle :install, :artifice => "compact_index", :raise_on_error => false
+ expect(err).to include("' sinatra' is not a valid gem name because it contains whitespace.")
+ end
+
+ it "should handle nested dependencies" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rails"
+ G
+
+ bundle :install, :artifice => "compact_index"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems(
+ "rails 2.3.2",
+ "actionpack 2.3.2",
+ "activerecord 2.3.2",
+ "actionmailer 2.3.2",
+ "activeresource 2.3.2",
+ "activesupport 2.3.2"
+ )
+ end
+
+ it "should handle case sensitivity conflicts" do
+ build_repo4 do
+ build_gem "rack", "1.0" do |s|
+ s.add_runtime_dependency("Rack", "0.1")
+ end
+ build_gem "Rack", "0.1"
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ source "#{source_uri}"
+ gem "rack", "1.0"
+ gem "Rack", "0.1"
+ G
+
+ # can't use `include_gems` here since the `require` will conflict on a
+ # case-insensitive FS
+ run "Bundler.require; puts Gem.loaded_specs.values_at('rack', 'Rack').map(&:full_name)"
+ expect(out).to eq("rack-1.0\nRack-0.1")
+ end
+
+ it "should handle multiple gem dependencies on the same gem" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "net-sftp"
+ G
+
+ bundle :install, :artifice => "compact_index"
+ expect(the_bundle).to include_gems "net-sftp 1.1.1"
+ end
+
+ it "should use the endpoint when using deployment mode" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+ bundle :install, :artifice => "compact_index"
+
+ bundle "config set --local deployment true"
+ bundle "config set --local path vendor/bundle"
+ bundle :install, :artifice => "compact_index"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles git dependencies that are in rubygems" do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ s.add_dependency "rails", "2.3.2"
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ git "#{file_uri_for(lib_path("foo-1.0"))}" do
+ gem 'foo'
+ end
+ G
+
+ bundle :install, :artifice => "compact_index"
+
+ expect(the_bundle).to include_gems("rails 2.3.2")
+ end
+
+ it "handles git dependencies that are in rubygems using deployment mode" do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ s.add_dependency "rails", "2.3.2"
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}"
+ G
+
+ bundle :install, :artifice => "compact_index"
+
+ bundle "config set --local deployment true"
+ bundle :install, :artifice => "compact_index"
+
+ expect(the_bundle).to include_gems("rails 2.3.2")
+ end
+
+ it "doesn't fail if you only have a git gem with no deps when using deployment mode" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}"
+ G
+
+ bundle "install", :artifice => "compact_index"
+ bundle "config set --local deployment true"
+ bundle :install, :artifice => "compact_index"
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "falls back when the API URL returns 403 Forbidden" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :verbose => true, :artifice => "compact_index_forbidden"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "falls back when the versions endpoint has a checksum mismatch" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :verbose => true, :artifice => "compact_index_checksum_mismatch"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(out).to include <<-'WARN'
+The checksum of /versions does not match the checksum provided by the server! Something is wrong (local checksum is "\"d41d8cd98f00b204e9800998ecf8427e\"", was expecting "\"123\"").
+ WARN
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "shows proper path when permission errors happen", :permissions do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions")
+ FileUtils.mkdir_p(File.dirname(versions))
+ FileUtils.touch(versions)
+ FileUtils.chmod("-r", versions)
+
+ bundle :install, :artifice => "compact_index", :raise_on_error => false
+
+ expect(err).to include(
+ "There was an error while trying to read from `#{versions}`. It is likely that you need to grant read permissions for that path."
+ )
+ end
+
+ it "falls back when the user's home directory does not exist or is not writable" do
+ ENV["HOME"] = tmp("missing_home").to_s
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "compact_index"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles host redirects" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "compact_index_host_redirect"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles host redirects without Net::HTTP::Persistent" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ FileUtils.mkdir_p lib_path
+ File.open(lib_path("disable_net_http_persistent.rb"), "w") do |h|
+ h.write <<-H
+ module Kernel
+ alias require_without_disabled_net_http require
+ def require(*args)
+ raise LoadError, 'simulated' if args.first == 'openssl' && !caller.grep(/vendored_persistent/).empty?
+ require_without_disabled_net_http(*args)
+ end
+ end
+ H
+ end
+
+ bundle :install, :artifice => "compact_index_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")]
+ expect(out).to_not match(/Too many redirects/)
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "times out when Bundler::Fetcher redirects too much" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "compact_index_redirects", :raise_on_error => false
+ expect(err).to match(/Too many redirects/)
+ end
+
+ context "when --full-index is specified" do
+ it "should use the modern index for install" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --full-index", :artifice => "compact_index"
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should use the modern index for update" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "update --full-index", :artifice => "compact_index", :all => true
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ it "does not double check for gems that are only installed locally" do
+ build_repo2 do
+ build_gem "net_a" do |s|
+ s.add_dependency "net_b"
+ s.add_dependency "net_build_extensions"
+ end
+
+ build_gem "net_b"
+
+ build_gem "net_build_extensions" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("lib", __dir__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/net_build_extensions.rb", "w") do |f|
+ f.puts "NET_BUILD_EXTENSIONS = 'YES'"
+ end
+ end
+ RUBY
+ end
+ end
+
+ system_gems %w[rack-1.0.0 thin-1.0 net_a-1.0], :gem_repo => gem_repo2
+ bundle "config set --local path.system true"
+ ENV["BUNDLER_SPEC_ALL_REQUESTS"] = strip_whitespace(<<-EOS).strip
+ #{source_uri}/versions
+ #{source_uri}/info/rack
+ EOS
+
+ install_gemfile <<-G, :artifice => "compact_index", :verbose => true, :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ expect(last_command.stdboth).not_to include "Double checking"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources", :bundler => "< 3" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem "back_deps"
+ G
+
+ bundle :install, :artifice => "compact_index_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources with source blocks" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index_extra", :verbose => true
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0"
+ end
+
+ it "fetches gem versions even when those gems are already installed" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack", "1.0.0"
+ G
+ bundle :install, :artifice => "compact_index_extra_api"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+
+ build_repo4 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}" do; end
+ source "#{source_uri}/extra"
+ gem "rack", "1.2"
+ G
+ bundle :install, :artifice => "compact_index_extra_api"
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+
+ it "considers all possible versions of dependencies from all api gem sources", :bundler => "< 3" do
+ # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that
+ # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0
+ # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other
+ # repo and installs it.
+ build_repo4 do
+ build_gem "activesupport", "1.2.0"
+ build_gem "somegem", "1.0.0" do |s|
+ s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem 'somegem', '1.0.0'
+ G
+
+ bundle :install, :artifice => "compact_index_extra_api"
+
+ expect(the_bundle).to include_gems "somegem 1.0.0"
+ expect(the_bundle).to include_gems "activesupport 1.2.3"
+ end
+
+ it "prints API output properly with back deps" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle :install, :artifice => "compact_index_extra"
+
+ expect(out).to include("Fetching gem metadata from http://localgemserver.test/")
+ expect(out).to include("Fetching source index from http://localgemserver.test/extra")
+ end
+
+ it "does not fetch every spec when doing back deps" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ build_gem "missing"
+
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index_extra_missing", :env => env_for_missing_prerelease_default_gem_activation
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "does not fetch every spec when doing back deps & everything is the compact index" do
+ build_repo4 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ build_gem "missing"
+
+ FileUtils.rm_rf Dir[gem_repo4("gems/foo-*.gem")]
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index_extra_api_missing", :env => env_for_missing_prerelease_default_gem_activation
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ expect(the_bundle).to include_gem "back_deps 1.0"
+ end
+
+ it "uses the endpoint if all sources support it" do
+ gemfile <<-G
+ source "#{source_uri}"
+
+ gem 'foo'
+ G
+
+ bundle :install, :artifice => "compact_index_api_missing"
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem "back_deps"
+ G
+
+ bundle :install, :artifice => "compact_index_extra"
+ bundle "config --set local deployment true"
+ bundle :install, :artifice => "compact_index_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources using deployment mode with blocks" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle :install, :artifice => "compact_index_extra"
+ bundle "config set --local deployment true"
+ bundle :install, :artifice => "compact_index_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "does not refetch if the only unmet dependency is bundler" do
+ build_repo2 do
+ build_gem "bundler_dep" do |s|
+ s.add_dependency "bundler"
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+
+ gem "bundler_dep"
+ G
+
+ bundle :install, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ end
+
+ it "installs the binstubs", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --binstubs", :artifice => "compact_index"
+
+ gembin "rackup"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "installs the bins when using --path and uses autoclean", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --path vendor/bundle", :artifice => "compact_index"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "installs the bins when using --path and uses bundle clean", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --path vendor/bundle --no-clean", :artifice => "compact_index"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "prints post_install_messages" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack-obama'
+ G
+
+ bundle :install, :artifice => "compact_index"
+ expect(out).to include("Post-install message from rack:")
+ end
+
+ it "should display the post install message for a dependency" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack_middleware'
+ G
+
+ bundle :install, :artifice => "compact_index"
+ expect(out).to include("Post-install message from rack:")
+ expect(out).to include("Rack's post install message")
+ end
+
+ context "when using basic authentication" do
+ let(:user) { "user" }
+ let(:password) { "pass" }
+ let(:basic_auth_source_uri) do
+ uri = Bundler::URI.parse(source_uri)
+ uri.user = user
+ uri.password = password
+
+ uri
+ end
+
+ it "passes basic authentication details and strips out creds" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "compact_index_basic_authentication"
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "passes basic authentication details and strips out creds also in verbose mode" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :verbose => true, :artifice => "compact_index_basic_authentication"
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "compact_index_basic_authentication"
+ expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(err).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not pass the user / password to different hosts on redirect" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "compact_index_creds_diff_host"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ describe "with authentication details in bundle config" do
+ before do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+ end
+
+ it "reads authentication details by host name from bundle config" do
+ bundle "config set #{source_hostname} #{user}:#{password}"
+
+ bundle :install, :artifice => "compact_index_strict_basic_authentication"
+
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "reads authentication details by full url from bundle config" do
+ # The trailing slash is necessary here; Fetcher canonicalizes the URI.
+ bundle "config set #{source_uri}/ #{user}:#{password}"
+
+ bundle :install, :artifice => "compact_index_strict_basic_authentication"
+
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should use the API" do
+ bundle "config set #{source_hostname} #{user}:#{password}"
+ bundle :install, :artifice => "compact_index_strict_basic_authentication"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "prefers auth supplied in the source uri" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle "config set #{source_hostname} otheruser:wrong"
+
+ bundle :install, :artifice => "compact_index_strict_basic_authentication"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "shows instructions if auth is not provided for the source" do
+ bundle :install, :artifice => "compact_index_strict_basic_authentication", :raise_on_error => false
+ expect(err).to include("bundle config set --global #{source_hostname} username:password")
+ end
+
+ it "fails if authentication has already been provided, but failed" do
+ bundle "config set #{source_hostname} #{user}:wrong"
+
+ bundle :install, :artifice => "compact_index_strict_basic_authentication", :raise_on_error => false
+ expect(err).to include("Bad username or password")
+ end
+
+ it "does not fallback to old dependency API if bad authentication is provided" do
+ bundle "config set #{source_hostname} #{user}:wrong"
+
+ bundle :install, :artifice => "compact_index_strict_basic_authentication", :raise_on_error => false, :verbose => true
+ expect(err).to include("Bad username or password")
+ expect(out).to include("HTTP 401 Unauthorized http://user@localgemserver.test/versions")
+ expect(out).not_to include("HTTP 401 Unauthorized http://user@localgemserver.test/api/v1/dependencies")
+ end
+ end
+
+ describe "with no password" do
+ let(:password) { nil }
+
+ it "passes basic authentication details" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "compact_index_basic_authentication"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+
+ context "when ruby is compiled without openssl" do
+ before do
+ # Install a monkeypatch that reproduces the effects of openssl being
+ # missing when the fetcher runs, as happens in real life. The reason
+ # we can't just overwrite openssl.rb is that Artifice uses it.
+ bundled_app("broken_ssl").mkpath
+ bundled_app("broken_ssl/openssl.rb").open("w") do |f|
+ f.write <<-RUBY
+ raise LoadError, "cannot load such file -- openssl"
+ RUBY
+ end
+ end
+
+ it "explains what to do to get it" do
+ gemfile <<-G
+ source "#{source_uri.gsub(/http/, "https")}"
+ gem "rack"
+ G
+
+ bundle :install, :env => { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, :raise_on_error => false
+ expect(err).to include("OpenSSL")
+ end
+ end
+
+ context "when SSL certificate verification fails" do
+ it "explains what happened" do
+ # Install a monkeypatch that reproduces the effects of openssl raising
+ # a certificate validation error when RubyGems tries to connect.
+ gemfile <<-G
+ class Net::HTTP
+ def start
+ raise OpenSSL::SSL::SSLError, "certificate verify failed"
+ end
+ end
+
+ source "#{source_uri.gsub(/http/, "https")}"
+ gem "rack"
+ G
+
+ bundle :install, :raise_on_error => false
+ expect(err).to match(/could not verify the SSL certificate/i)
+ end
+ end
+
+ context ".gemrc with sources is present" do
+ it "uses other sources declared in the Gemfile" do
+ File.open(home(".gemrc"), "w") do |file|
+ file.puts({ :sources => ["https://rubygems.org"] }.to_yaml)
+ end
+
+ begin
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack'
+ G
+
+ bundle :install, :artifice => "compact_index_forbidden"
+ ensure
+ home(".gemrc").rmtree
+ end
+ end
+ end
+
+ it "performs partial update with a non-empty range" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack', '0.9.1'
+ G
+
+ # Initial install creates the cached versions file
+ bundle :install, :artifice => "compact_index"
+
+ # Update the Gemfile so we can check subsequent install was successful
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack', '1.0.0'
+ G
+
+ # Second install should make only a partial request to /versions
+ bundle :install, :artifice => "compact_index_partial_update"
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "performs partial update while local cache is updated by another process" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack'
+ G
+
+ # Create an empty file to trigger a partial download
+ versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions")
+ FileUtils.mkdir_p(File.dirname(versions))
+ FileUtils.touch(versions)
+
+ bundle :install, :artifice => "compact_index_concurrent_download"
+
+ expect(File.read(versions)).to start_with("created_at")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "performs full update if server endpoints serve partial content responses but don't have incremental content and provide no Etag" do
+ build_repo4 do
+ build_gem "rack", "0.9.1"
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ source "#{source_uri}"
+ gem 'rack', '0.9.1'
+ G
+
+ update_repo4 do
+ build_gem "rack", "1.0.0"
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index_partial_update_no_etag_not_incremental", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ source "#{source_uri}"
+ gem 'rack', '1.0.0'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "performs full update of compact index info cache if range is not satisfiable" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack', '0.9.1'
+ G
+
+ rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack")
+
+ bundle :install, :artifice => "compact_index"
+
+ expected_rack_info_content = File.read(rake_info_path)
+
+ # Modify the cache files. We expect them to be reset to the normal ones when we re-run :install
+ File.open(rake_info_path, "w") {|f| f << (expected_rack_info_content + "this is different") }
+
+ # Update the Gemfile so the next install does its normal things
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack', '1.0.0'
+ G
+
+ # The cache files now being longer means the requested range is going to be not satisfiable
+ # Bundler must end up requesting the whole file to fix things up.
+ bundle :install, :artifice => "compact_index_range_not_satisfiable"
+
+ resulting_rack_info_content = File.read(rake_info_path)
+
+ expect(resulting_rack_info_content).to eq(expected_rack_info_content)
+ end
+
+ it "fails gracefully when the source URI has an invalid scheme" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "htps://rubygems.org"
+ gem "rack"
+ G
+ expect(exitstatus).to eq(15)
+ expect(err).to end_with(<<-E.strip)
+ The request uri `htps://index.rubygems.org/versions` has an invalid scheme (`htps`). Did you mean `http` or `https`?
+ E
+ end
+
+ describe "checksum validation" do
+ it "raises when the checksum does not match" do
+ install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :raise_on_error => false
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ expect(exitstatus).to eq(19)
+ expect(err).
+ to include("Bundler cannot continue installing rack (1.0.0).").
+ and include("The checksum for the downloaded `rack-1.0.0.gem` does not match the checksum given by the server.").
+ and include("This means the contents of the downloaded gem is different from what was uploaded to the server, and could be a potential security issue.").
+ and include("To resolve this issue:").
+ and include("1. delete the downloaded gem located at: `#{default_bundle_path}/gems/rack-1.0.0/rack-1.0.0.gem`").
+ and include("2. run `bundle install`").
+ and include("If you wish to continue installing the downloaded gem, and are certain it does not pose a security issue despite the mismatching checksum, do the following:").
+ and include("1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification").
+ and include("2. run `bundle install`").
+ and match(/\(More info: The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/)
+ end
+
+ it "raises when the checksum is the wrong length" do
+ install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :env => { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!", "DEBUG" => "1" }, :verbose => true, :raise_on_error => false
+ source "#{source_uri}"
+ gem "rack"
+ G
+ expect(exitstatus).to eq(5)
+ expect(err).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest")
+ end
+
+ it "does not raise when disable_checksum_validation is set" do
+ bundle "config set disable_checksum_validation true"
+ install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum"
+ source "#{source_uri}"
+ gem "rack"
+ G
+ end
+ end
+
+ it "works when cache dir is world-writable" do
+ install_gemfile <<-G, :artifice => "compact_index"
+ File.umask(0000)
+ source "#{source_uri}"
+ gem "rack"
+ G
+ end
+
+ it "doesn't explode when the API dependencies are wrong" do
+ install_gemfile <<-G, :artifice => "compact_index_wrong_dependencies", :env => { "DEBUG" => "true" }, :raise_on_error => false
+ source "#{source_uri}"
+ gem "rails"
+ G
+ deps = [Gem::Dependency.new("rake", "= 13.0.1"),
+ Gem::Dependency.new("actionpack", "= 2.3.2"),
+ Gem::Dependency.new("activerecord", "= 2.3.2"),
+ Gem::Dependency.new("actionmailer", "= 2.3.2"),
+ Gem::Dependency.new("activeresource", "= 2.3.2")]
+ expect(out).to include("rails-2.3.2 from rubygems remote at #{source_uri}/ has either corrupted API or lockfile dependencies")
+ expect(err).to include(<<-E.strip)
+Bundler::APIResponseMismatchError: Downloading rails-2.3.2 revealed dependencies not in the API or the lockfile (#{deps.map(&:to_s).join(", ")}).
+Running `bundle update rails` should fix the problem.
+ E
+ end
+
+ it "does not duplicate specs in the lockfile when updating and a dependency is not installed" do
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "#{file_uri_for(gem_repo1)}"
+ source "#{source_uri}" do
+ gem "rails"
+ gem "activemerchant"
+ end
+ G
+ gem_command "uninstall activemerchant"
+ bundle "update rails", :artifice => "compact_index"
+ expect(lockfile.scan(/activemerchant \(/).size).to eq(1)
+ end
+end
diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb
new file mode 100644
index 0000000000..a54f1db772
--- /dev/null
+++ b/spec/bundler/install/gems/dependency_api_spec.rb
@@ -0,0 +1,759 @@
+# frozen_string_literal: true
+
+RSpec.describe "gemcutter's dependency API" do
+ let(:source_hostname) { "localgemserver.test" }
+ let(:source_uri) { "http://#{source_hostname}" }
+
+ it "should use the API" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should URI encode gem names" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem " sinatra"
+ G
+
+ bundle :install, :artifice => "endpoint", :raise_on_error => false
+ expect(err).to include("' sinatra' is not a valid gem name because it contains whitespace.")
+ end
+
+ it "should handle nested dependencies" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rails"
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("Fetching gem metadata from #{source_uri}/...")
+ expect(the_bundle).to include_gems(
+ "rails 2.3.2",
+ "actionpack 2.3.2",
+ "activerecord 2.3.2",
+ "actionmailer 2.3.2",
+ "activeresource 2.3.2",
+ "activesupport 2.3.2"
+ )
+ end
+
+ it "should handle multiple gem dependencies on the same gem" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "net-sftp"
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(the_bundle).to include_gems "net-sftp 1.1.1"
+ end
+
+ it "should use the endpoint when using deployment mode" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+ bundle :install, :artifice => "endpoint"
+
+ bundle "config set --local deployment true"
+ bundle "config set --local path vendor/bundle"
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles git dependencies that are in rubygems" do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ s.add_dependency "rails", "2.3.2"
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ git "#{file_uri_for(lib_path("foo-1.0"))}" do
+ gem 'foo'
+ end
+ G
+
+ bundle :install, :artifice => "endpoint"
+
+ expect(the_bundle).to include_gems("rails 2.3.2")
+ end
+
+ it "handles git dependencies that are in rubygems using deployment mode" do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ s.add_dependency "rails", "2.3.2"
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}"
+ G
+
+ bundle :install, :artifice => "endpoint"
+
+ bundle "config set --local deployment true"
+ bundle :install, :artifice => "endpoint"
+
+ expect(the_bundle).to include_gems("rails 2.3.2")
+ end
+
+ it "doesn't fail if you only have a git gem with no deps when using deployment mode" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}"
+ G
+
+ bundle "install", :artifice => "endpoint"
+ bundle "config set --local deployment true"
+ bundle :install, :artifice => "endpoint"
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "falls back when the API errors out" do
+ simulate_platform x86_mswin32
+
+ build_repo2 do
+ # The rcov gem is platform mswin32, but has no arch
+ build_gem "rcov" do |s|
+ s.platform = Gem::Platform.new([nil, "mswin32", nil])
+ s.write "lib/rcov.rb", "RCOV = '1.0.0'"
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rcov"
+ G
+
+ bundle :install, :artifice => "windows", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rcov 1.0.0"
+ end
+
+ it "falls back when hitting the Gemcutter Dependency Limit" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "activesupport"
+ gem "actionpack"
+ gem "actionmailer"
+ gem "activeresource"
+ gem "thin"
+ gem "rack"
+ gem "rails"
+ G
+ bundle :install, :artifice => "endpoint_fallback"
+ expect(out).to include("Fetching source index from #{source_uri}")
+
+ expect(the_bundle).to include_gems(
+ "activesupport 2.3.2",
+ "actionpack 2.3.2",
+ "actionmailer 2.3.2",
+ "activeresource 2.3.2",
+ "activesupport 2.3.2",
+ "thin 1.0.0",
+ "rack 1.0.0",
+ "rails 2.3.2"
+ )
+ end
+
+ it "falls back when Gemcutter API doesn't return proper Marshal format" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :verbose => true, :artifice => "endpoint_marshal_fail"
+ expect(out).to include("could not fetch from the dependency API, trying the full index")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "falls back when the API URL returns 403 Forbidden" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :verbose => true, :artifice => "endpoint_api_forbidden"
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles host redirects" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_host_redirect"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles host redirects without Net::HTTP::Persistent" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ FileUtils.mkdir_p lib_path
+ File.open(lib_path("disable_net_http_persistent.rb"), "w") do |h|
+ h.write <<-H
+ module Kernel
+ alias require_without_disabled_net_http require
+ def require(*args)
+ raise LoadError, 'simulated' if args.first == 'openssl' && !caller.grep(/vendored_persistent/).empty?
+ require_without_disabled_net_http(*args)
+ end
+ end
+ H
+ end
+
+ bundle :install, :artifice => "endpoint_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")]
+ expect(out).to_not match(/Too many redirects/)
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "timeouts when Bundler::Fetcher redirects too much" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_redirect", :raise_on_error => false
+ expect(err).to match(/Too many redirects/)
+ end
+
+ context "when --full-index is specified" do
+ it "should use the modern index for install" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --full-index", :artifice => "endpoint"
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should use the modern index for update" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "update --full-index", :artifice => "endpoint", :all => true
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources", :bundler => "< 3" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem "back_deps"
+ G
+
+ bundle :install, :artifice => "endpoint_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources using blocks" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle :install, :artifice => "endpoint_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0"
+ end
+
+ it "fetches gem versions even when those gems are already installed" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack", "1.0.0"
+ G
+ bundle :install, :artifice => "endpoint_extra_api"
+
+ build_repo4 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}" do; end
+ source "#{source_uri}/extra"
+ gem "rack", "1.2"
+ G
+ bundle :install, :artifice => "endpoint_extra_api"
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+
+ it "considers all possible versions of dependencies from all api gem sources", :bundler => "< 3" do
+ # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that
+ # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0
+ # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other
+ # repo and installs it.
+ build_repo4 do
+ build_gem "activesupport", "1.2.0"
+ build_gem "somegem", "1.0.0" do |s|
+ s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem 'somegem', '1.0.0'
+ G
+
+ bundle :install, :artifice => "endpoint_extra_api"
+
+ expect(the_bundle).to include_gems "somegem 1.0.0"
+ expect(the_bundle).to include_gems "activesupport 1.2.3"
+ end
+
+ it "prints API output properly with back deps" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle :install, :artifice => "endpoint_extra"
+
+ expect(out).to include("Fetching gem metadata from http://localgemserver.test/.")
+ expect(out).to include("Fetching source index from http://localgemserver.test/extra")
+ end
+
+ it "does not fetch every spec when doing back deps", :bundler => "< 3" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ build_gem "missing"
+
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ install_gemfile <<-G, :artifice => "endpoint_extra_missing", :env => env_for_missing_prerelease_default_gem_activation
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem "back_deps"
+ G
+
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "does not fetch every spec when doing back deps using blocks" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ build_gem "missing"
+
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ install_gemfile <<-G, :artifice => "endpoint_extra_missing", :env => env_for_missing_prerelease_default_gem_activation
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem "back_deps"
+ G
+
+ bundle :install, :artifice => "endpoint_extra"
+ bundle "config set --local deployment true"
+ bundle :install, :artifice => "endpoint_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources using deployment mode with blocks" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle :install, :artifice => "endpoint_extra"
+ bundle "config set --local deployment true"
+ bundle "install", :artifice => "endpoint_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "does not fetch all marshaled specs" do
+ build_repo2 do
+ build_gem "foo", "1.0"
+ build_gem "foo", "2.0"
+ end
+
+ install_gemfile <<-G, :artifice => "endpoint", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, :verbose => true
+ source "#{source_uri}"
+
+ gem "foo"
+ G
+
+ expect(out).to include("foo-2.0.gemspec.rz")
+ expect(out).not_to include("foo-1.0.gemspec.rz")
+ end
+
+ it "does not refetch if the only unmet dependency is bundler" do
+ build_repo2 do
+ build_gem "bundler_dep" do |s|
+ s.add_dependency "bundler"
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+
+ gem "bundler_dep"
+ G
+
+ bundle :install, :artifice => "endpoint", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ end
+
+ it "installs the binstubs", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --binstubs", :artifice => "endpoint"
+
+ gembin "rackup"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "installs the bins when using --path and uses autoclean", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --path vendor/bundle", :artifice => "endpoint"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "installs the bins when using --path and uses bundle clean", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --path vendor/bundle --no-clean", :artifice => "endpoint"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "prints post_install_messages" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack-obama'
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("Post-install message from rack:")
+ end
+
+ it "should display the post install message for a dependency" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack_middleware'
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("Post-install message from rack:")
+ expect(out).to include("Rack's post install message")
+ end
+
+ context "when using basic authentication" do
+ let(:user) { "user" }
+ let(:password) { "pass" }
+ let(:basic_auth_source_uri) do
+ uri = Bundler::URI.parse(source_uri)
+ uri.user = user
+ uri.password = password
+
+ uri
+ end
+
+ it "passes basic authentication details and strips out creds" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_basic_authentication"
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "passes basic authentication details and strips out creds also in verbose mode" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :verbose => true, :artifice => "endpoint_basic_authentication"
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "strips http basic authentication creds for modern index" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_marshal_fail_basic_authentication"
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "strips http basic auth creds when it can't reach the server" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_500", :raise_on_error => false
+ expect(out).not_to include("#{user}:#{password}")
+ end
+
+ it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_basic_authentication"
+ expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(err).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not pass the user / password to different hosts on redirect" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_creds_diff_host"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ describe "with host including dashes" do
+ before do
+ gemfile <<-G
+ source "http://local-gemserver.test"
+ gem "rack"
+ G
+ end
+
+ it "reads authentication details from a valid ENV variable" do
+ bundle :install, :artifice => "endpoint_strict_basic_authentication", :env => { "BUNDLE_LOCAL___GEMSERVER__TEST" => "#{user}:#{password}" }
+
+ expect(out).to include("Fetching gem metadata from http://local-gemserver.test")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ describe "with authentication details in bundle config" do
+ before do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+ end
+
+ it "reads authentication details by host name from bundle config" do
+ bundle "config set #{source_hostname} #{user}:#{password}"
+
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "reads authentication details by full url from bundle config" do
+ # The trailing slash is necessary here; Fetcher canonicalizes the URI.
+ bundle "config set #{source_uri}/ #{user}:#{password}"
+
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should use the API" do
+ bundle "config set #{source_hostname} #{user}:#{password}"
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "prefers auth supplied in the source uri" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle "config set #{source_hostname} otheruser:wrong"
+
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "shows instructions if auth is not provided for the source" do
+ bundle :install, :artifice => "endpoint_strict_basic_authentication", :raise_on_error => false
+ expect(err).to include("bundle config set --global #{source_hostname} username:password")
+ end
+
+ it "fails if authentication has already been provided, but failed" do
+ bundle "config set #{source_hostname} #{user}:wrong"
+
+ bundle :install, :artifice => "endpoint_strict_basic_authentication", :raise_on_error => false
+ expect(err).to include("Bad username or password")
+ end
+ end
+
+ describe "with no password" do
+ let(:password) { nil }
+
+ it "passes basic authentication details" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_basic_authentication"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+
+ context "when ruby is compiled without openssl" do
+ before do
+ # Install a monkeypatch that reproduces the effects of openssl being
+ # missing when the fetcher runs, as happens in real life. The reason
+ # we can't just overwrite openssl.rb is that Artifice uses it.
+ bundled_app("broken_ssl").mkpath
+ bundled_app("broken_ssl/openssl.rb").open("w") do |f|
+ f.write <<-RUBY
+ raise LoadError, "cannot load such file -- openssl"
+ RUBY
+ end
+ end
+
+ it "explains what to do to get it" do
+ gemfile <<-G
+ source "#{source_uri.gsub(/http/, "https")}"
+ gem "rack"
+ G
+
+ bundle :install, :env => { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, :raise_on_error => false
+ expect(err).to include("OpenSSL")
+ end
+ end
+
+ context "when SSL certificate verification fails" do
+ it "explains what happened" do
+ # Install a monkeypatch that reproduces the effects of openssl raising
+ # a certificate validation error when RubyGems tries to connect.
+ gemfile <<-G
+ class Net::HTTP
+ def start
+ raise OpenSSL::SSL::SSLError, "certificate verify failed"
+ end
+ end
+
+ source "#{source_uri.gsub(/http/, "https")}"
+ gem "rack"
+ G
+
+ bundle :install, :raise_on_error => false
+ expect(err).to match(/could not verify the SSL certificate/i)
+ end
+ end
+
+ context ".gemrc with sources is present" do
+ it "uses other sources declared in the Gemfile" do
+ File.open(home(".gemrc"), "w") do |file|
+ file.puts({ :sources => ["https://rubygems.org"] }.to_yaml)
+ end
+
+ begin
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack'
+ G
+
+ bundle "install", :artifice => "endpoint_marshal_fail"
+ ensure
+ home(".gemrc").rmtree
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/env_spec.rb b/spec/bundler/install/gems/env_spec.rb
new file mode 100644
index 0000000000..a6dfadcfc8
--- /dev/null
+++ b/spec/bundler/install/gems/env_spec.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with ENV conditionals" do
+ describe "when just setting an ENV key as a string" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ env "BUNDLER_TEST" do
+ gem "rack"
+ end
+ G
+ end
+
+ it "excludes the gems when the ENV variable is not set" do
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "includes the gems when the ENV variable is set" do
+ ENV["BUNDLER_TEST"] = "1"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ describe "when just setting an ENV key as a symbol" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ env :BUNDLER_TEST do
+ gem "rack"
+ end
+ G
+ end
+
+ it "excludes the gems when the ENV variable is not set" do
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "includes the gems when the ENV variable is set" do
+ ENV["BUNDLER_TEST"] = "1"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ describe "when setting a string to match the env" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ env "BUNDLER_TEST" => "foo" do
+ gem "rack"
+ end
+ G
+ end
+
+ it "excludes the gems when the ENV variable is not set" do
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "excludes the gems when the ENV variable is set but does not match the condition" do
+ ENV["BUNDLER_TEST"] = "1"
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "includes the gems when the ENV variable is set and matches the condition" do
+ ENV["BUNDLER_TEST"] = "foo"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ describe "when setting a regex to match the env" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ env "BUNDLER_TEST" => /foo/ do
+ gem "rack"
+ end
+ G
+ end
+
+ it "excludes the gems when the ENV variable is not set" do
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "excludes the gems when the ENV variable is set but does not match the condition" do
+ ENV["BUNDLER_TEST"] = "fo"
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "includes the gems when the ENV variable is set and matches the condition" do
+ ENV["BUNDLER_TEST"] = "foobar"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/flex_spec.rb b/spec/bundler/install/gems/flex_spec.rb
new file mode 100644
index 0000000000..d5fa55be48
--- /dev/null
+++ b/spec/bundler/install/gems/flex_spec.rb
@@ -0,0 +1,370 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle flex_install" do
+ it "installs the gems as expected" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to be_locked
+ end
+
+ it "installs even when the lockfile is invalid" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to be_locked
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack', '1.0'
+ G
+
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to be_locked
+ end
+
+ it "keeps child dependencies at the same version" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack-obama"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0"
+
+ update_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack-obama", "1.0"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0"
+ end
+
+ describe "adding new gems" do
+ it "installs added gems without updating previously installed gems" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rack'
+ G
+
+ update_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rack'
+ gem 'activesupport', '2.3.5'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ end
+
+ it "keeps child dependencies pinned" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack-obama"
+ G
+
+ update_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack-obama"
+ gem "thin"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0", "thin 1.0"
+ end
+ end
+
+ describe "removing gems" do
+ it "removes gems without changing the versions of remaining gems" do
+ build_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rack'
+ gem 'activesupport', '2.3.5'
+ G
+
+ update_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rack'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rack'
+ gem 'activesupport', '2.3.2'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2"
+ end
+
+ it "removes top level dependencies when removed from the Gemfile while leaving other dependencies intact" do
+ build_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rack'
+ gem 'activesupport', '2.3.5'
+ G
+
+ update_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rack'
+ G
+
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+
+ it "removes child dependencies" do
+ build_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'rack-obama'
+ gem 'activesupport'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0", "activesupport 2.3.5"
+
+ update_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'activesupport'
+ G
+
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ expect(the_bundle).not_to include_gems "rack-obama", "rack"
+ end
+ end
+
+ describe "when running bundle install and Gemfile conflicts with lockfile" do
+ before(:each) do
+ build_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack_middleware"
+ G
+
+ expect(the_bundle).to include_gems "rack_middleware 1.0", "rack 0.9.1"
+
+ build_repo2 do
+ build_gem "rack-obama", "2.0" do |s|
+ s.add_dependency "rack", "=1.2"
+ end
+ build_gem "rack_middleware", "2.0" do |s|
+ s.add_dependency "rack", ">=1.0"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack-obama", "2.0"
+ gem "rack_middleware"
+ G
+ end
+
+ it "does not install gems whose dependencies are not met" do
+ bundle :install, :raise_on_error => false
+ ruby <<-RUBY, :raise_on_error => false
+ require 'bundler/setup'
+ RUBY
+ expect(err).to match(/could not find gem 'rack-obama/i)
+ end
+
+ it "discards the locked gems when the Gemfile requires different versions than the lock" do
+ bundle "config set force_ruby_platform true"
+
+ nice_error = <<-E.strip.gsub(/^ {8}/, "")
+ Could not find compatible versions
+
+ Because rack-obama >= 2.0 depends on rack = 1.2
+ and rack = 1.2 could not be found in rubygems repository #{file_uri_for(gem_repo2)}/ or installed locally,
+ rack-obama >= 2.0 cannot be used.
+ So, because Gemfile depends on rack-obama = 2.0,
+ version solving has failed.
+ E
+
+ bundle :install, :retry => 0, :raise_on_error => false
+ expect(err).to end_with(nice_error)
+ end
+
+ it "does not include conflicts with a single requirement tree, because that can't possibly be a conflict" do
+ bundle "config set force_ruby_platform true"
+
+ bad_error = <<-E.strip.gsub(/^ {8}/, "")
+ Bundler could not find compatible versions for gem "rack-obama":
+ In Gemfile:
+ rack-obama (= 2.0)
+ E
+
+ bundle "update rack_middleware", :retry => 0, :raise_on_error => false
+ expect(err).not_to end_with(bad_error)
+ end
+ end
+
+ describe "when running bundle update and Gemfile conflicts with lockfile" do
+ before(:each) do
+ build_repo4 do
+ build_gem "jekyll-feed", "0.16.0"
+ build_gem "jekyll-feed", "0.15.1"
+
+ build_gem "github-pages", "226" do |s|
+ s.add_dependency "jekyll-feed", "0.15.1"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "jekyll-feed", "~> 0.12"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "github-pages", "~> 226"
+ gem "jekyll-feed", "~> 0.12"
+ G
+ end
+
+ it "discards the conflicting lockfile information and resolves properly" do
+ bundle :update, :raise_on_error => false, :all => true
+ expect(err).to be_empty
+ end
+ end
+
+ describe "subtler cases" do
+ before :each do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ gem "rack-obama"
+ G
+ end
+
+ it "should work when you install" do
+ bundle "install"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (0.9.1)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (= 0.9.1)
+ rack-obama
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "should work when you update" do
+ bundle "update rack"
+ end
+ end
+
+ describe "when adding a new source" do
+ it "updates the lockfile" do
+ build_repo2
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ source "#{file_uri_for(gem_repo2)}" do
+ end
+ gem "rack"
+ G
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (1.0.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ # This was written to test github issue #636
+ describe "when a locked child dependency conflicts" do
+ before(:each) do
+ build_repo2 do
+ build_gem "capybara", "0.3.9" do |s|
+ s.add_dependency "rack", ">= 1.0.0"
+ end
+
+ build_gem "rack", "1.1.0"
+ build_gem "rails", "3.0.0.rc4" do |s|
+ s.add_dependency "rack", "~> 1.1.0"
+ end
+
+ build_gem "rack", "1.2.1"
+ build_gem "rails", "3.0.0" do |s|
+ s.add_dependency "rack", "~> 1.2.1"
+ end
+ end
+ end
+
+ it "resolves them" do
+ # install Rails 3.0.0.rc
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails", "3.0.0.rc4"
+ gem "capybara", "0.3.9"
+ G
+
+ # upgrade Rails to 3.0.0 and then install again
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rails", "3.0.0"
+ gem "capybara", "0.3.9"
+ G
+ expect(err).to be_empty
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/fund_spec.rb b/spec/bundler/install/gems/fund_spec.rb
new file mode 100644
index 0000000000..9aadc9ed25
--- /dev/null
+++ b/spec/bundler/install/gems/fund_spec.rb
@@ -0,0 +1,164 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ context "with gem sources" do
+ before do
+ build_repo2 do
+ build_gem "has_funding_and_other_metadata" do |s|
+ s.metadata = {
+ "bug_tracker_uri" => "https://example.com/user/bestgemever/issues",
+ "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md",
+ "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1",
+ "homepage_uri" => "https://bestgemever.example.io",
+ "mailing_list_uri" => "https://groups.example.com/bestgemever",
+ "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding",
+ "source_code_uri" => "https://example.com/user/bestgemever",
+ "wiki_uri" => "https://example.com/user/bestgemever/wiki",
+ }
+ end
+
+ build_gem "has_funding", "1.2.3" do |s|
+ s.metadata = {
+ "funding_uri" => "https://example.com/has_funding/funding",
+ }
+ end
+
+ build_gem "gem_with_dependent_funding", "1.0" do |s|
+ s.add_dependency "has_funding"
+ end
+ end
+ end
+
+ context "when gems include a fund URI" do
+ it "displays the plural fund message after installing" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'has_funding_and_other_metadata'
+ gem 'has_funding'
+ gem 'rack-obama'
+ G
+
+ expect(out).to include("2 installed gems you directly depend on are looking for funding.")
+ end
+
+ it "displays the singular fund message after installing" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'has_funding'
+ gem 'rack-obama'
+ G
+
+ expect(out).to include("1 installed gem you directly depend on is looking for funding.")
+ end
+ end
+
+ context "when gems include a fund URI but `ignore_funding_requests` is configured" do
+ before do
+ bundle "config set ignore_funding_requests true"
+ end
+
+ it "does not display the plural fund message after installing" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'has_funding_and_other_metadata'
+ gem 'has_funding'
+ gem 'rack-obama'
+ G
+
+ expect(out).not_to include("2 installed gems you directly depend on are looking for funding.")
+ end
+
+ it "does not display the singular fund message after installing" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'has_funding'
+ gem 'rack-obama'
+ G
+
+ expect(out).not_to include("1 installed gem you directly depend on is looking for funding.")
+ end
+ end
+
+ context "when gems do not include fund messages" do
+ it "does not display any fund messages" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "activesupport"
+ G
+
+ expect(out).not_to include("gem you depend on")
+ end
+ end
+
+ context "when a dependency includes a fund message" do
+ it "does not display the fund message" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'gem_with_dependent_funding'
+ G
+
+ expect(out).not_to include("gem you depend on")
+ end
+ end
+ end
+
+ context "with git sources" do
+ context "when gems include fund URI" do
+ it "displays the fund message after installing" do
+ build_git "also_has_funding" do |s|
+ s.metadata = {
+ "funding_uri" => "https://example.com/also_has_funding/funding",
+ }
+ end
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}'
+ G
+
+ expect(out).to include("1 installed gem you directly depend on is looking for funding.")
+ end
+
+ it "displays the fund message if repo is updated" do
+ build_git "also_has_funding" do |s|
+ s.metadata = {
+ "funding_uri" => "https://example.com/also_has_funding/funding",
+ }
+ end
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}'
+ G
+
+ build_git "also_has_funding", "1.1" do |s|
+ s.metadata = {
+ "funding_uri" => "https://example.com/also_has_funding/funding",
+ }
+ end
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.1")}'
+ G
+
+ expect(out).to include("1 installed gem you directly depend on is looking for funding.")
+ end
+
+ it "displays the fund message if repo is not updated" do
+ build_git "also_has_funding" do |s|
+ s.metadata = {
+ "funding_uri" => "https://example.com/also_has_funding/funding",
+ }
+ end
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}'
+ G
+
+ bundle :install
+ expect(out).to include("1 installed gem you directly depend on is looking for funding.")
+
+ bundle :install
+ expect(out).to include("1 installed gem you directly depend on is looking for funding.")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/mirror_spec.rb b/spec/bundler/install/gems/mirror_spec.rb
new file mode 100644
index 0000000000..9611973701
--- /dev/null
+++ b/spec/bundler/install/gems/mirror_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with a mirror configured" do
+ describe "when the mirror does not match the gem source" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+ bundle "config set --local mirror.http://gems.example.org http://gem-mirror.example.org"
+ end
+
+ it "installs from the normal location" do
+ bundle :install
+ expect(out).to include("Fetching source index from #{file_uri_for(gem_repo1)}")
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ describe "when the gem source matches a configured mirror" do
+ before :each do
+ gemfile <<-G
+ # This source is bogus and doesn't have the gem we're looking for
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rack"
+ G
+ bundle "config set --local mirror.#{file_uri_for(gem_repo2)} #{file_uri_for(gem_repo1)}"
+ end
+
+ it "installs the gem from the mirror" do
+ bundle :install
+ expect(out).to include("Fetching source index from #{file_uri_for(gem_repo1)}")
+ expect(out).not_to include("Fetching source index from #{file_uri_for(gem_repo2)}")
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/native_extensions_spec.rb b/spec/bundler/install/gems/native_extensions_spec.rb
new file mode 100644
index 0000000000..5c18d2cc51
--- /dev/null
+++ b/spec/bundler/install/gems/native_extensions_spec.rb
@@ -0,0 +1,188 @@
+# frozen_string_literal: true
+
+RSpec.describe "installing a gem with native extensions" do
+ it "installs" do
+ build_repo2 do
+ build_gem "c_extension" do |s|
+ s.extensions = ["ext/extconf.rb"]
+ s.write "ext/extconf.rb", <<-E
+ require "mkmf"
+ $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"]
+ name = "c_extension_bundle"
+ dir_config(name)
+ raise "OMG" unless with_config("c_extension") == "hello"
+ create_makefile(name)
+ E
+
+ s.write "ext/c_extension.c", <<-C
+ #include "ruby.h"
+
+ VALUE c_extension_true(VALUE self) {
+ return Qtrue;
+ }
+
+ void Init_c_extension_bundle() {
+ VALUE c_Extension = rb_define_class("CExtension", rb_cObject);
+ rb_define_method(c_Extension, "its_true", c_extension_true, 0);
+ }
+ C
+
+ s.write "lib/c_extension.rb", <<-C
+ require "c_extension_bundle"
+ C
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "c_extension"
+ G
+
+ bundle "config set build.c_extension --with-c_extension=hello"
+ bundle "install"
+
+ expect(out).to include("Installing c_extension 1.0 with native extensions")
+
+ run "Bundler.require; puts CExtension.new.its_true"
+ expect(out).to eq("true")
+ end
+
+ it "installs from git" do
+ build_git "c_extension" do |s|
+ s.extensions = ["ext/extconf.rb"]
+ s.write "ext/extconf.rb", <<-E
+ require "mkmf"
+ $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"]
+ name = "c_extension_bundle"
+ dir_config(name)
+ raise "OMG" unless with_config("c_extension") == "hello"
+ create_makefile(name)
+ E
+
+ s.write "ext/c_extension.c", <<-C
+ #include "ruby.h"
+
+ VALUE c_extension_true(VALUE self) {
+ return Qtrue;
+ }
+
+ void Init_c_extension_bundle() {
+ VALUE c_Extension = rb_define_class("CExtension", rb_cObject);
+ rb_define_method(c_Extension, "its_true", c_extension_true, 0);
+ }
+ C
+
+ s.write "lib/c_extension.rb", <<-C
+ require "c_extension_bundle"
+ C
+ end
+
+ bundle "config set build.c_extension --with-c_extension=hello"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "c_extension", :git => #{lib_path("c_extension-1.0").to_s.dump}
+ G
+
+ expect(err).to_not include("warning: conflicting chdir during another chdir block")
+
+ run "Bundler.require; puts CExtension.new.its_true"
+ expect(out).to eq("true")
+ end
+
+ it "installs correctly from git when multiple gems with extensions share one repository" do
+ build_repo2 do
+ ["one", "two"].each do |n|
+ build_lib "c_extension_#{n}", "1.0", :path => lib_path("gems/c_extension_#{n}") do |s|
+ s.extensions = ["ext/extconf.rb"]
+ s.write "ext/extconf.rb", <<-E
+ require "mkmf"
+ $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"]
+ name = "c_extension_bundle_#{n}"
+ dir_config(name)
+ raise "OMG" unless with_config("c_extension_#{n}") == "#{n}"
+ create_makefile(name)
+ E
+
+ s.write "ext/c_extension_#{n}.c", <<-C
+ #include "ruby.h"
+
+ VALUE c_extension_#{n}_value(VALUE self) {
+ return rb_str_new_cstr("#{n}");
+ }
+
+ void Init_c_extension_bundle_#{n}() {
+ VALUE c_Extension = rb_define_class("CExtension_#{n}", rb_cObject);
+ rb_define_method(c_Extension, "value", c_extension_#{n}_value, 0);
+ }
+ C
+
+ s.write "lib/c_extension_#{n}.rb", <<-C
+ require "c_extension_bundle_#{n}"
+ C
+ end
+ end
+ build_git "gems", :path => lib_path("gems"), :gemspec => false
+ end
+
+ bundle "config set build.c_extension_one --with-c_extension_one=one"
+ bundle "config set build.c_extension_two --with-c_extension_two=two"
+
+ # 1st time, require only one gem -- only one of the extensions gets built.
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "c_extension_one", :git => #{lib_path("gems").to_s.dump}
+ G
+
+ # 2nd time, require both gems -- we need both extensions to be built now.
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "c_extension_one", :git => #{lib_path("gems").to_s.dump}
+ gem "c_extension_two", :git => #{lib_path("gems").to_s.dump}
+ G
+
+ run "Bundler.require; puts CExtension_one.new.value; puts CExtension_two.new.value"
+ expect(out).to eq("one\ntwo")
+ end
+
+ it "install with multiple build flags" do
+ build_git "c_extension" do |s|
+ s.extensions = ["ext/extconf.rb"]
+ s.write "ext/extconf.rb", <<-E
+ require "mkmf"
+ $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"]
+ name = "c_extension_bundle"
+ dir_config(name)
+ raise "OMG" unless with_config("c_extension") == "hello" && with_config("c_extension_bundle-dir") == "hola"
+ create_makefile(name)
+ E
+
+ s.write "ext/c_extension.c", <<-C
+ #include "ruby.h"
+
+ VALUE c_extension_true(VALUE self) {
+ return Qtrue;
+ }
+
+ void Init_c_extension_bundle() {
+ VALUE c_Extension = rb_define_class("CExtension", rb_cObject);
+ rb_define_method(c_Extension, "its_true", c_extension_true, 0);
+ }
+ C
+
+ s.write "lib/c_extension.rb", <<-C
+ require "c_extension_bundle"
+ C
+ end
+
+ bundle "config set build.c_extension --with-c_extension=hello --with-c_extension_bundle-dir=hola"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "c_extension", :git => #{lib_path("c_extension-1.0").to_s.dump}
+ G
+
+ run "Bundler.require; puts CExtension.new.its_true"
+ expect(out).to eq("true")
+ end
+end
diff --git a/spec/bundler/install/gems/post_install_spec.rb b/spec/bundler/install/gems/post_install_spec.rb
new file mode 100644
index 0000000000..7426f54877
--- /dev/null
+++ b/spec/bundler/install/gems/post_install_spec.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ context "with gem sources" do
+ context "when gems include post install messages" do
+ it "should display the post-install messages after installing" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ gem 'thin'
+ gem 'rack-obama'
+ G
+
+ bundle :install
+ expect(out).to include("Post-install message from rack:")
+ expect(out).to include("Rack's post install message")
+ expect(out).to include("Post-install message from thin:")
+ expect(out).to include("Thin's post install message")
+ expect(out).to include("Post-install message from rack-obama:")
+ expect(out).to include("Rack-obama's post install message")
+ end
+ end
+
+ context "when gems do not include post install messages" do
+ it "should not display any post-install messages" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+ G
+
+ bundle :install
+ expect(out).not_to include("Post-install message")
+ end
+ end
+
+ context "when a dependency includes a post install message" do
+ it "should display the post install message" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack_middleware'
+ G
+
+ bundle :install
+ expect(out).to include("Post-install message from rack:")
+ expect(out).to include("Rack's post install message")
+ end
+ end
+ end
+
+ context "with git sources" do
+ context "when gems include post install messages" do
+ it "should display the post-install messages after installing" do
+ build_git "foo" do |s|
+ s.post_install_message = "Foo's post install message"
+ end
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :install
+ expect(out).to include("Post-install message from foo:")
+ expect(out).to include("Foo's post install message")
+ end
+
+ it "should display the post-install messages if repo is updated" do
+ build_git "foo" do |s|
+ s.post_install_message = "Foo's post install message"
+ end
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle :install
+
+ build_git "foo", "1.1" do |s|
+ s.post_install_message = "Foo's 1.1 post install message"
+ end
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => '#{lib_path("foo-1.1")}'
+ G
+ bundle :install
+
+ expect(out).to include("Post-install message from foo:")
+ expect(out).to include("Foo's 1.1 post install message")
+ end
+
+ it "should not display the post-install messages if repo is not updated" do
+ build_git "foo" do |s|
+ s.post_install_message = "Foo's post install message"
+ end
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :install
+ expect(out).to include("Post-install message from foo:")
+ expect(out).to include("Foo's post install message")
+
+ bundle :install
+ expect(out).not_to include("Post-install message")
+ end
+ end
+
+ context "when gems do not include post install messages" do
+ it "should not display any post-install messages" do
+ build_git "foo" do |s|
+ s.post_install_message = nil
+ end
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :install
+ expect(out).not_to include("Post-install message")
+ end
+ end
+ end
+
+ context "when ignore post-install messages for gem is set" do
+ it "doesn't display any post-install messages" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "config set ignore_messages.rack true"
+
+ bundle :install
+ expect(out).not_to include("Post-install message")
+ end
+ end
+
+ context "when ignore post-install messages for all gems" do
+ it "doesn't display any post-install messages" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "config set ignore_messages true"
+
+ bundle :install
+ expect(out).not_to include("Post-install message")
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb
new file mode 100644
index 0000000000..fb6dda2f88
--- /dev/null
+++ b/spec/bundler/install/gems/resolving_spec.rb
@@ -0,0 +1,600 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with install-time dependencies" do
+ before do
+ build_repo2 do
+ build_gem "with_implicit_rake_dep" do |s|
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("lib", __dir__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/implicit_rake_dep.rb", "w") do |f|
+ f.puts "IMPLICIT_RAKE_DEP = 'YES'"
+ end
+ end
+ RUBY
+ end
+
+ build_gem "another_implicit_rake_dep" do |s|
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("lib", __dir__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/another_implicit_rake_dep.rb", "w") do |f|
+ f.puts "ANOTHER_IMPLICIT_RAKE_DEP = 'YES'"
+ end
+ end
+ RUBY
+ end
+
+ # Test complicated gem dependencies for install
+ build_gem "net_a" do |s|
+ s.add_dependency "net_b"
+ s.add_dependency "net_build_extensions"
+ end
+
+ build_gem "net_b"
+
+ build_gem "net_build_extensions" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("lib", __dir__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/net_build_extensions.rb", "w") do |f|
+ f.puts "NET_BUILD_EXTENSIONS = 'YES'"
+ end
+ end
+ RUBY
+ end
+
+ build_gem "net_c" do |s|
+ s.add_dependency "net_a"
+ s.add_dependency "net_d"
+ end
+
+ build_gem "net_d"
+
+ build_gem "net_e" do |s|
+ s.add_dependency "net_d"
+ end
+ end
+ end
+
+ it "installs gems with implicit rake dependencies" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "with_implicit_rake_dep"
+ gem "another_implicit_rake_dep"
+ gem "rake"
+ G
+
+ run <<-R
+ require 'implicit_rake_dep'
+ require 'another_implicit_rake_dep'
+ puts IMPLICIT_RAKE_DEP
+ puts ANOTHER_IMPLICIT_RAKE_DEP
+ R
+ expect(out).to eq("YES\nYES")
+ end
+
+ it "installs gems with implicit rake dependencies without rake previously installed" do
+ with_path_as("") do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "with_implicit_rake_dep"
+ gem "another_implicit_rake_dep"
+ gem "rake"
+ G
+ end
+
+ run <<-R
+ require 'implicit_rake_dep'
+ require 'another_implicit_rake_dep'
+ puts IMPLICIT_RAKE_DEP
+ puts ANOTHER_IMPLICIT_RAKE_DEP
+ R
+ expect(out).to eq("YES\nYES")
+ end
+
+ it "installs gems with a dependency with no type" do
+ build_repo2
+
+ path = "#{gem_repo2}/#{Gem::MARSHAL_SPEC_DIR}/actionpack-2.3.2.gemspec.rz"
+ spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path)))
+ spec.dependencies.each do |d|
+ d.instance_variable_set(:@type, :fail)
+ end
+ File.open(path, "wb") do |f|
+ f.write Gem.deflate(Marshal.dump(spec))
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "actionpack", "2.3.2"
+ G
+
+ expect(the_bundle).to include_gems "actionpack 2.3.2", "activesupport 2.3.2"
+ end
+
+ describe "with crazy rubygem plugin stuff" do
+ it "installs plugins" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "net_b"
+ G
+
+ expect(the_bundle).to include_gems "net_b 1.0"
+ end
+
+ it "installs plugins depended on by other plugins" do
+ install_gemfile <<-G, :env => { "DEBUG" => "1" }
+ source "#{file_uri_for(gem_repo2)}"
+ gem "net_a"
+ G
+
+ expect(the_bundle).to include_gems "net_a 1.0", "net_b 1.0"
+ end
+
+ it "installs multiple levels of dependencies" do
+ install_gemfile <<-G, :env => { "DEBUG" => "1" }
+ source "#{file_uri_for(gem_repo2)}"
+ gem "net_c"
+ gem "net_e"
+ G
+
+ expect(the_bundle).to include_gems "net_a 1.0", "net_b 1.0", "net_c 1.0", "net_d 1.0", "net_e 1.0"
+ end
+
+ context "with ENV['BUNDLER_DEBUG_RESOLVER'] set" do
+ it "produces debug output" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "net_c"
+ gem "net_e"
+ G
+
+ bundle :install, :env => { "BUNDLER_DEBUG_RESOLVER" => "1", "DEBUG" => "1" }
+
+ expect(out).to include("Resolving dependencies...")
+ end
+ end
+
+ context "with ENV['DEBUG_RESOLVER'] set" do
+ it "produces debug output" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "net_c"
+ gem "net_e"
+ G
+
+ bundle :install, :env => { "DEBUG_RESOLVER" => "1", "DEBUG" => "1" }
+
+ expect(out).to include("Resolving dependencies...")
+ end
+ end
+
+ context "with ENV['DEBUG_RESOLVER_TREE'] set" do
+ it "produces debug output" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "net_c"
+ gem "net_e"
+ G
+
+ bundle :install, :env => { "DEBUG_RESOLVER_TREE" => "1", "DEBUG" => "1" }
+
+ expect(out).to include(" net_b").
+ and include("Resolving dependencies...").
+ and include("Solution found after 1 attempts:").
+ and include("selected net_b 1.0")
+ end
+ end
+ end
+
+ describe "when a required ruby version" do
+ context "allows only an older version" do
+ it "installs the older version" do
+ build_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "rack", "9001.0.0" do |s|
+ s.required_ruby_version = "> 9000"
+ end
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ ruby "#{Gem.ruby_version}"
+ source "http://localgemserver.test/"
+ gem 'rack'
+ G
+
+ expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000")
+ expect(the_bundle).to include_gems("rack 1.2")
+ end
+
+ it "installs the older version when using servers not implementing the compact index API" do
+ build_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+
+ build_gem "rack", "9001.0.0" do |s|
+ s.required_ruby_version = "> 9000"
+ end
+ end
+
+ install_gemfile <<-G, :artifice => "endpoint", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+ ruby "#{Gem.ruby_version}"
+ source "http://localgemserver.test/"
+ gem 'rack'
+ G
+
+ expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000")
+ expect(the_bundle).to include_gems("rack 1.2")
+ end
+
+ context "when there is a lockfile using the newer incompatible version" do
+ before do
+ build_repo2 do
+ build_gem "parallel_tests", "3.7.0" do |s|
+ s.required_ruby_version = ">= #{current_ruby_minor}"
+ end
+
+ build_gem "parallel_tests", "3.8.0" do |s|
+ s.required_ruby_version = ">= #{next_ruby_minor}"
+ end
+ end
+
+ gemfile <<-G
+ source "http://localgemserver.test/"
+ gem 'parallel_tests'
+ G
+
+ lockfile <<~L
+ GEM
+ remote: http://localgemserver.test/
+ specs:
+ parallel_tests (3.8.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ parallel_tests
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically updates lockfile to use the older version" do
+ bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: http://localgemserver.test/
+ specs:
+ parallel_tests (3.7.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ parallel_tests
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "gives a meaningful error if we're in frozen mode" do
+ expect do
+ bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s, "BUNDLE_FROZEN" => "true" }, :raise_on_error => false
+ end.not_to change { lockfile }
+
+ expect(err).to include("parallel_tests-3.8.0 requires ruby version >= #{next_ruby_minor}")
+ expect(err).not_to include("That means the author of parallel_tests (3.8.0) has removed it.")
+ end
+ end
+
+ context "with transitive dependencies in a lockfile" do
+ before do
+ build_repo2 do
+ build_gem "rubocop", "1.28.2" do |s|
+ s.required_ruby_version = ">= #{current_ruby_minor}"
+
+ s.add_dependency "rubocop-ast", ">= 1.17.0", "< 2.0"
+ end
+
+ build_gem "rubocop", "1.35.0" do |s|
+ s.required_ruby_version = ">= #{next_ruby_minor}"
+
+ s.add_dependency "rubocop-ast", ">= 1.20.1", "< 2.0"
+ end
+
+ build_gem "rubocop-ast", "1.17.0" do |s|
+ s.required_ruby_version = ">= #{current_ruby_minor}"
+ end
+
+ build_gem "rubocop-ast", "1.21.0" do |s|
+ s.required_ruby_version = ">= #{next_ruby_minor}"
+ end
+ end
+
+ gemfile <<-G
+ source "http://localgemserver.test/"
+ gem 'rubocop'
+ G
+
+ lockfile <<~L
+ GEM
+ remote: http://localgemserver.test/
+ specs:
+ rubocop (1.35.0)
+ rubocop-ast (>= 1.20.1, < 2.0)
+ rubocop-ast (1.21.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ parallel_tests
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "automatically updates lockfile to use the older compatible versions" do
+ bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: http://localgemserver.test/
+ specs:
+ rubocop (1.28.2)
+ rubocop-ast (>= 1.17.0, < 2.0)
+ rubocop-ast (1.17.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rubocop
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "with a Gemfile and lock file that don't resolve under the current platform" do
+ before do
+ build_repo4 do
+ build_gem "sorbet", "0.5.10554" do |s|
+ s.add_dependency "sorbet-static", "0.5.10554"
+ end
+
+ build_gem "sorbet-static", "0.5.10554" do |s|
+ s.platform = "universal-darwin-21"
+ end
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem 'sorbet', '= 0.5.10554'
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ sorbet (0.5.10554)
+ sorbet-static (= 0.5.10554)
+ sorbet-static (0.5.10554-universal-darwin-21)
+
+ PLATFORMS
+ arm64-darwin-21
+
+ DEPENDENCIES
+ sorbet (= 0.5.10554)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "raises a proper error" do
+ simulate_platform "aarch64-linux" do
+ bundle "install", :raise_on_error => false
+ end
+
+ nice_error = strip_whitespace(<<-E).strip
+ Could not find gem 'sorbet-static (= 0.5.10554)' with platforms 'arm64-darwin-21', 'aarch64-linux' in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally.
+
+ The source contains the following gems matching 'sorbet-static (= 0.5.10554)':
+ * sorbet-static-0.5.10554-universal-darwin-21
+ E
+ expect(err).to end_with(nice_error)
+ end
+ end
+
+ it "gives a meaningful error on ruby version mismatches between dependencies" do
+ build_repo4 do
+ build_gem "requires-old-ruby" do |s|
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ build_lib("foo", :path => bundled_app) do |s|
+ s.required_ruby_version = ">= #{Gem.ruby_version}"
+
+ s.add_dependency "requires-old-ruby"
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo4)}"
+ gemspec
+ G
+
+ expect(err).to end_with <<~E.strip
+ Could not find compatible versions
+
+ Because every version of foo depends on requires-old-ruby >= 0
+ and every version of requires-old-ruby depends on Ruby < #{Gem.ruby_version},
+ every version of foo requires Ruby < #{Gem.ruby_version}.
+ So, because Gemfile depends on foo >= 0
+ and current Ruby version is = #{Gem.ruby_version},
+ version solving has failed.
+ E
+ end
+
+ it "installs the older version under rate limiting conditions" do
+ build_repo4 do
+ build_gem "rack", "9001.0.0" do |s|
+ s.required_ruby_version = "> 9000"
+ end
+ build_gem "rack", "1.2"
+ build_gem "foo1", "1.0"
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index_rate_limited", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ ruby "#{Gem.ruby_version}"
+ source "http://localgemserver.test/"
+ gem 'rack'
+ gem 'foo1'
+ G
+
+ expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000")
+ expect(the_bundle).to include_gems("rack 1.2")
+ end
+
+ it "installs the older not platform specific version" do
+ build_repo4 do
+ build_gem "rack", "9001.0.0" do |s|
+ s.required_ruby_version = "> 9000"
+ end
+ build_gem "rack", "1.2" do |s|
+ s.platform = x86_mingw32
+ s.required_ruby_version = "> 9000"
+ end
+ build_gem "rack", "1.2"
+ end
+
+ simulate_platform x86_mingw32 do
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ ruby "#{Gem.ruby_version}"
+ source "http://localgemserver.test/"
+ gem 'rack'
+ G
+ end
+
+ expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000")
+ expect(err).to_not include("rack-1.2-#{Bundler.local_platform} requires ruby version > 9000")
+ expect(the_bundle).to include_gems("rack 1.2")
+ end
+ end
+
+ context "allows no gems" do
+ before do
+ build_repo2 do
+ build_gem "require_ruby" do |s|
+ s.required_ruby_version = "> 9000"
+ end
+ end
+ end
+
+ let(:ruby_requirement) { %("#{Gem.ruby_version}") }
+ let(:error_message_requirement) { "= #{Gem.ruby_version}" }
+
+ it "raises a proper error that mentions the current Ruby version during resolution" do
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, :raise_on_error => false
+ source "http://localgemserver.test/"
+ gem 'require_ruby'
+ G
+
+ expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000")
+
+ nice_error = strip_whitespace(<<-E).strip
+ Could not find compatible versions
+
+ Because every version of require_ruby depends on Ruby > 9000
+ and Gemfile depends on require_ruby >= 0,
+ Ruby > 9000 is required.
+ So, because current Ruby version is #{error_message_requirement},
+ version solving has failed.
+ E
+ expect(err).to end_with(nice_error)
+ end
+
+ shared_examples_for "ruby version conflicts" do
+ it "raises an error during resolution" do
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, :raise_on_error => false
+ source "http://localgemserver.test/"
+ ruby #{ruby_requirement}
+ gem 'require_ruby'
+ G
+
+ expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000")
+
+ nice_error = strip_whitespace(<<-E).strip
+ Could not find compatible versions
+
+ Because every version of require_ruby depends on Ruby > 9000
+ and Gemfile depends on require_ruby >= 0,
+ Ruby > 9000 is required.
+ So, because current Ruby version is #{error_message_requirement},
+ version solving has failed.
+ E
+ expect(err).to end_with(nice_error)
+ end
+ end
+
+ it_behaves_like "ruby version conflicts"
+
+ describe "with a < requirement" do
+ let(:ruby_requirement) { %("< 5000") }
+
+ it_behaves_like "ruby version conflicts"
+ end
+
+ describe "with a compound requirement" do
+ let(:reqs) { ["> 0.1", "< 5000"] }
+ let(:ruby_requirement) { reqs.map(&:dump).join(", ") }
+
+ it_behaves_like "ruby version conflicts"
+ end
+ end
+ end
+
+ describe "when a required rubygems version disallows a gem" do
+ it "does not try to install those gems" do
+ build_repo2 do
+ build_gem "require_rubygems" do |s|
+ s.required_rubygems_version = "> 9000"
+ end
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'require_rubygems'
+ G
+
+ expect(err).to_not include("Gem::InstallError: require_rubygems requires RubyGems version > 9000")
+ nice_error = strip_whitespace(<<-E).strip
+ Because every version of require_rubygems depends on RubyGems > 9000
+ and Gemfile depends on require_rubygems >= 0,
+ RubyGems > 9000 is required.
+ So, because current RubyGems version is = #{Gem::VERSION},
+ version solving has failed.
+ E
+ expect(err).to end_with(nice_error)
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb
new file mode 100644
index 0000000000..4d08752256
--- /dev/null
+++ b/spec/bundler/install/gems/standalone_spec.rb
@@ -0,0 +1,515 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples "bundle install --standalone" do
+ shared_examples "common functionality" do
+ it "still makes the gems available to normal bundler" do
+ args = expected_gems.map {|k, v| "#{k} #{v}" }
+ expect(the_bundle).to include_gems(*args)
+ end
+
+ it "still makes system gems unavailable to normal bundler" do
+ system_gems "rack-1.0.0"
+
+ expect(the_bundle).to_not include_gems("rack")
+ end
+
+ it "generates a bundle/bundler/setup.rb" do
+ expect(bundled_app("bundle/bundler/setup.rb")).to exist
+ end
+
+ it "makes the gems available without bundler" do
+ testrb = String.new <<-RUBY
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ RUBY
+ expected_gems.each do |k, _|
+ testrb << "\nrequire \"#{k}\""
+ testrb << "\nputs #{k.upcase}"
+ end
+ ruby testrb
+
+ expect(out).to eq(expected_gems.values.join("\n"))
+ end
+
+ it "makes the gems available without bundler nor rubygems" do
+ testrb = String.new <<-RUBY
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ RUBY
+ expected_gems.each do |k, _|
+ testrb << "\nrequire \"#{k}\""
+ testrb << "\nputs #{k.upcase}"
+ end
+ sys_exec %(#{Gem.ruby} --disable-gems -w -e #{testrb.shellescape})
+
+ expect(out).to eq(expected_gems.values.join("\n"))
+ end
+
+ it "makes the gems available without bundler via Kernel.require" do
+ testrb = String.new <<-RUBY
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ RUBY
+ expected_gems.each do |k, _|
+ testrb << "\nKernel.require \"#{k}\""
+ testrb << "\nputs #{k.upcase}"
+ end
+ ruby testrb
+
+ expect(out).to eq(expected_gems.values.join("\n"))
+ end
+
+ it "makes system gems unavailable without bundler" do
+ system_gems "rack-1.0.0"
+
+ testrb = String.new <<-RUBY
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ begin
+ require "rack"
+ rescue LoadError
+ puts "LoadError"
+ end
+ RUBY
+ ruby testrb
+
+ expect(out).to eq("LoadError")
+ end
+
+ it "works on a different system" do
+ begin
+ FileUtils.mv(bundled_app, "#{bundled_app}2")
+ rescue Errno::ENOTEMPTY
+ puts "Couldn't rename test app since the target folder has these files: #{Dir.glob("#{bundled_app}2/*")}"
+ raise
+ end
+
+ testrb = String.new <<-RUBY
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ RUBY
+ expected_gems.each do |k, _|
+ testrb << "\nrequire \"#{k}\""
+ testrb << "\nputs #{k.upcase}"
+ end
+ ruby testrb, :dir => "#{bundled_app}2"
+
+ expect(out).to eq(expected_gems.values.join("\n"))
+ end
+ end
+
+ describe "with simple gems" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+ bundle "config set --local path #{bundled_app("bundle")}"
+ bundle :install, :standalone => true, :dir => cwd
+ end
+
+ let(:expected_gems) do
+ {
+ "actionpack" => "2.3.2",
+ "rails" => "2.3.2",
+ }
+ end
+
+ include_examples "common functionality"
+ end
+
+ describe "with default gems and a lockfile", :ruby_repo do
+ before do
+ skip "does not work on rubygems versions where `--install_dir` doesn't respect --default" unless Gem::Installer.for_spec(loaded_gemspec, :install_dir => "/foo").default_spec_file == "/foo/specifications/default/bundler-#{Bundler::VERSION}.gemspec" # Since rubygems 3.2.0.rc.2
+ skip "does not work on old rubies because the realworld gems that need to be installed don't support them" if RUBY_VERSION < "2.7.0"
+
+ realworld_system_gems "tsort --version 0.1.0"
+
+ necessary_system_gems = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.2.0", "stringio --version 3.0.1"]
+ necessary_system_gems += ["shellwords --version 0.1.0", "base64 --version 0.1.0", "resolv --version 0.2.1"] if Gem.rubygems_version < Gem::Version.new("3.3.a")
+ necessary_system_gems += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a")
+ realworld_system_gems(*necessary_system_gems, :path => scoped_gem_path(bundled_app("bundle")))
+
+ build_gem "foo", "1.0.0", :to_system => true, :default => true do |s|
+ s.add_dependency "bar"
+ end
+
+ build_gem "bar", "1.0.0", :to_system => true, :default => true
+
+ build_repo4 do
+ build_gem "foo", "1.0.0" do |s|
+ s.add_dependency "bar"
+ end
+
+ build_gem "bar", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "https://gem.repo4"
+ gem "foo"
+ G
+
+ bundle "lock", :dir => cwd, :artifice => "compact_index"
+ end
+
+ it "works and points to the vendored copies, not to the default copies", :realworld do
+ bundle "config set --local path #{bundled_app("bundle")}"
+ bundle :install, :standalone => true, :dir => cwd, :artifice => "compact_index", :env => { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s }
+
+ load_path_lines = bundled_app("bundle/bundler/setup.rb").read.split("\n").select {|line| line.start_with?("$:.unshift") }
+
+ expect(load_path_lines).to eq [
+ '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/bar-1.0.0/lib")',
+ '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/foo-1.0.0/lib")',
+ ]
+ end
+ end
+
+ describe "with Gemfiles using absolute path sources and resulting bundle moved to a folder hierarchy with different nesting" do
+ before do
+ build_lib "minitest", "1.0.0", :path => lib_path("minitest")
+
+ Dir.mkdir bundled_app("app")
+
+ gemfile bundled_app("app/Gemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "minitest", :path => "#{lib_path("minitest")}"
+ G
+
+ bundle "install", :standalone => true, :dir => bundled_app("app")
+
+ Dir.mkdir tmp("one_more_level")
+ FileUtils.mv bundled_app, tmp("one_more_level")
+ end
+
+ it "also works" do
+ ruby <<-RUBY, :dir => tmp("one_more_level/bundled_app/app")
+ require "./bundle/bundler/setup"
+
+ require "minitest"
+ puts MINITEST
+ RUBY
+
+ expect(out).to eq("1.0.0")
+ expect(err).to be_empty
+ end
+ end
+
+ describe "with Gemfiles using relative path sources and app moved to a different root" do
+ before do
+ FileUtils.mkdir_p bundled_app("app/vendor")
+
+ build_lib "minitest", "1.0.0", :path => bundled_app("app/vendor/minitest")
+
+ gemfile bundled_app("app/Gemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "minitest", :path => "vendor/minitest"
+ G
+
+ bundle "install", :standalone => true, :dir => bundled_app("app")
+
+ FileUtils.mv(bundled_app("app"), bundled_app2("app"))
+ end
+
+ it "also works" do
+ ruby <<-RUBY, :dir => bundled_app2("app")
+ require "./bundle/bundler/setup"
+
+ require "minitest"
+ puts MINITEST
+ RUBY
+
+ expect(out).to eq("1.0.0")
+ expect(err).to be_empty
+ end
+ end
+
+ describe "with gems with native extension" do
+ before do
+ bundle "config set --local path #{bundled_app("bundle")}"
+ install_gemfile <<-G, :standalone => true, :dir => cwd
+ source "#{file_uri_for(gem_repo1)}"
+ gem "very_simple_binary"
+ G
+ end
+
+ it "generates a bundle/bundler/setup.rb with the proper paths" do
+ expected_path = bundled_app("bundle/bundler/setup.rb")
+ script_content = File.read(expected_path)
+ expect(script_content).to include("def self.ruby_api_version")
+ expect(script_content).to include("def self.extension_api_version")
+ extension_line = script_content.each_line.find {|line| line.include? "/extensions/" }.strip
+ platform = Gem::Platform.local
+ expect(extension_line).to start_with '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/'
+ expect(extension_line).to end_with platform.to_s + '/#{Gem.extension_api_version}/very_simple_binary-1.0")'
+ end
+ end
+
+ describe "with gem that has an invalid gemspec" do
+ before do
+ build_git "bar", :gemspec => false do |s|
+ s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0')
+ s.write "bar.gemspec", <<-G
+ lib = File.expand_path('lib/', __dir__)
+ $:.unshift lib unless $:.include?(lib)
+ require 'bar/version'
+
+ Gem::Specification.new do |s|
+ s.name = 'bar'
+ s.version = BAR_VERSION
+ s.summary = 'Bar'
+ s.files = Dir["lib/**/*.rb"]
+ s.author = 'Anonymous'
+ s.require_path = [1,2]
+ end
+ G
+ end
+ bundle "config set --local path #{bundled_app("bundle")}"
+ install_gemfile <<-G, :standalone => true, :dir => cwd, :raise_on_error => false
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bar", :git => "#{lib_path("bar-1.0")}"
+ G
+ end
+
+ it "outputs a helpful error message" do
+ expect(err).to include("You have one or more invalid gemspecs that need to be fixed.")
+ expect(err).to include("bar 1.0 has an invalid gemspec")
+ end
+ end
+
+ describe "with a combination of gems and git repos" do
+ before do
+ build_git "devise", "1.0"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ gem "devise", :git => "#{lib_path("devise-1.0")}"
+ G
+ bundle "config set --local path #{bundled_app("bundle")}"
+ bundle :install, :standalone => true, :dir => cwd
+ end
+
+ let(:expected_gems) do
+ {
+ "actionpack" => "2.3.2",
+ "devise" => "1.0",
+ "rails" => "2.3.2",
+ }
+ end
+
+ include_examples "common functionality"
+ end
+
+ describe "with groups" do
+ before do
+ build_git "devise", "1.0"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+
+ group :test do
+ gem "rspec"
+ gem "rack-test"
+ end
+ G
+ bundle "config set --local path #{bundled_app("bundle")}"
+ bundle :install, :standalone => true, :dir => cwd
+ end
+
+ let(:expected_gems) do
+ {
+ "actionpack" => "2.3.2",
+ "rails" => "2.3.2",
+ }
+ end
+
+ include_examples "common functionality"
+
+ it "allows creating a standalone file with limited groups" do
+ bundle "config set --local path #{bundled_app("bundle")}"
+ bundle :install, :standalone => "default", :dir => cwd
+
+ load_error_ruby <<-RUBY, "spec"
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ require "actionpack"
+ puts ACTIONPACK
+ require "spec"
+ RUBY
+
+ expect(out).to eq("2.3.2")
+ expect(err).to eq("ZOMG LOAD ERROR")
+ end
+
+ it "allows `without` configuration to limit the groups used in a standalone" do
+ bundle "config set --local path #{bundled_app("bundle")}"
+ bundle "config set --local without test"
+ bundle :install, :standalone => true, :dir => cwd
+
+ load_error_ruby <<-RUBY, "spec"
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ require "actionpack"
+ puts ACTIONPACK
+ require "spec"
+ RUBY
+
+ expect(out).to eq("2.3.2")
+ expect(err).to eq("ZOMG LOAD ERROR")
+ end
+
+ it "allows `path` configuration to change the location of the standalone bundle" do
+ bundle "config set --local path path/to/bundle"
+ bundle "install", :standalone => true, :dir => cwd
+
+ ruby <<-RUBY
+ $:.unshift File.expand_path("path/to/bundle")
+ require "bundler/setup"
+
+ require "actionpack"
+ puts ACTIONPACK
+ RUBY
+
+ expect(out).to eq("2.3.2")
+ end
+
+ it "allows `without` to limit the groups used in a standalone" do
+ bundle "config set --local without test"
+ bundle :install, :dir => cwd
+ bundle "config set --local path #{bundled_app("bundle")}"
+ bundle :install, :standalone => true, :dir => cwd
+
+ load_error_ruby <<-RUBY, "spec"
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ require "actionpack"
+ puts ACTIONPACK
+ require "spec"
+ RUBY
+
+ expect(out).to eq("2.3.2")
+ expect(err).to eq("ZOMG LOAD ERROR")
+ end
+ end
+
+ describe "with gemcutter's dependency API" do
+ let(:source_uri) { "http://localgemserver.test" }
+
+ describe "simple gems" do
+ before do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rails"
+ G
+ bundle "config set --local path #{bundled_app("bundle")}"
+ bundle :install, :standalone => true, :artifice => "endpoint", :dir => cwd
+ end
+
+ let(:expected_gems) do
+ {
+ "actionpack" => "2.3.2",
+ "rails" => "2.3.2",
+ }
+ end
+
+ include_examples "common functionality"
+ end
+ end
+
+ describe "with --binstubs", :bundler => "< 3" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+ bundle "config set --local path #{bundled_app("bundle")}"
+ bundle :install, :standalone => true, :binstubs => true, :dir => cwd
+ end
+
+ let(:expected_gems) do
+ {
+ "actionpack" => "2.3.2",
+ "rails" => "2.3.2",
+ }
+ end
+
+ include_examples "common functionality"
+
+ it "creates stubs that use the standalone load path" do
+ expect(sys_exec("bin/rails -v").chomp).to eql "2.3.2"
+ end
+
+ it "creates stubs that can be executed from anywhere" do
+ require "tmpdir"
+ sys_exec(%(#{bundled_app("bin/rails")} -v), :dir => Dir.tmpdir)
+ expect(out).to eq("2.3.2")
+ end
+
+ it "creates stubs that can be symlinked" do
+ skip "symlinks unsupported" if Gem.win_platform?
+
+ symlink_dir = tmp("symlink")
+ FileUtils.mkdir_p(symlink_dir)
+ symlink = File.join(symlink_dir, "rails")
+
+ File.symlink(bundled_app("bin/rails"), symlink)
+ sys_exec("#{symlink} -v")
+ expect(out).to eq("2.3.2")
+ end
+
+ it "creates stubs with the correct load path" do
+ extension_line = File.read(bundled_app("bin/rails")).each_line.find {|line| line.include? "$:.unshift" }.strip
+ expect(extension_line).to eq %($:.unshift File.expand_path "../bundle", __dir__)
+ end
+ end
+end
+
+RSpec.describe "bundle install --standalone" do
+ let(:cwd) { bundled_app }
+
+ include_examples("bundle install --standalone")
+end
+
+RSpec.describe "bundle install --standalone run in a subdirectory" do
+ let(:cwd) { bundled_app("bob").tap(&:mkpath) }
+
+ include_examples("bundle install --standalone")
+end
+
+RSpec.describe "bundle install --standalone --local" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ system_gems "rack-1.0.0", :path => default_bundle_path
+ end
+
+ it "generates script pointing to system gems" do
+ bundle "install --standalone --local --verbose"
+
+ expect(out).to include("Using rack 1.0.0")
+
+ load_error_ruby <<-RUBY, "spec"
+ require "./bundler/setup"
+
+ require "rack"
+ puts RACK
+ require "spec"
+ RUBY
+
+ expect(out).to eq("1.0.0")
+ expect(err).to eq("ZOMG LOAD ERROR")
+ end
+end
diff --git a/spec/bundler/install/gems/win32_spec.rb b/spec/bundler/install/gems/win32_spec.rb
new file mode 100644
index 0000000000..419b14ff0f
--- /dev/null
+++ b/spec/bundler/install/gems/win32_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with win32-generated lockfile" do
+ it "should read lockfile" do
+ File.open(bundled_app_lock, "wb") do |f|
+ f << "GEM\r\n"
+ f << " remote: #{file_uri_for(gem_repo1)}/\r\n"
+ f << " specs:\r\n"
+ f << "\r\n"
+ f << " rack (1.0.0)\r\n"
+ f << "\r\n"
+ f << "PLATFORMS\r\n"
+ f << " ruby\r\n"
+ f << "\r\n"
+ f << "DEPENDENCIES\r\n"
+ f << " rack\r\n"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rack"
+ G
+ end
+end
diff --git a/spec/bundler/install/gemspecs_spec.rb b/spec/bundler/install/gemspecs_spec.rb
new file mode 100644
index 0000000000..7b58ea9839
--- /dev/null
+++ b/spec/bundler/install/gemspecs_spec.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ describe "when a gem has a YAML gemspec" do
+ before :each do
+ build_repo2 do
+ build_gem "yaml_spec", :gemspec => :yaml
+ end
+ end
+
+ it "still installs correctly" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "yaml_spec"
+ G
+ bundle :install
+ expect(err).to be_empty
+ end
+
+ it "still installs correctly when using path" do
+ build_lib "yaml_spec", :gemspec => :yaml
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'yaml_spec', :path => "#{lib_path("yaml_spec-1.0")}"
+ G
+ expect(err).to be_empty
+ end
+ end
+
+ it "should use gemspecs in the system cache when available" do
+ gemfile <<-G
+ source "http://localtestserver.gem"
+ gem 'rack'
+ G
+
+ system_gems "rack-1.0.0", :path => default_bundle_path
+
+ FileUtils.mkdir_p "#{default_bundle_path}/specifications"
+ File.open("#{default_bundle_path}/specifications/rack-1.0.0.gemspec", "w+") do |f|
+ spec = Gem::Specification.new do |s|
+ s.name = "rack"
+ s.version = "1.0.0"
+ s.add_runtime_dependency "activesupport", "2.3.2"
+ end
+ f.write spec.to_ruby
+ end
+ bundle :install, :artifice => "endpoint_marshal_fail" # force gemspec load
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2"
+ end
+
+ it "does not hang when gemspec has incompatible encoding" do
+ create_file("foo.gemspec", <<-G)
+ Gem::Specification.new do |gem|
+ gem.name = "pry-byebug"
+ gem.version = "3.4.2"
+ gem.author = "David Rodríguez"
+ gem.summary = "Good stuff"
+ end
+ G
+
+ install_gemfile <<-G, :env => { "LANG" => "C" }
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "reads gemspecs respecting their encoding" do
+ create_file "version.rb", <<-RUBY
+ module Persistent💎
+ VERSION = "0.0.1"
+ end
+ RUBY
+
+ create_file "persistent-dmnd.gemspec", <<-G
+ require_relative "version"
+
+ Gem::Specification.new do |gem|
+ gem.name = "persistent-dmnd"
+ gem.version = Persistent💎::VERSION
+ gem.author = "Ivo Anjo"
+ gem.summary = "Unscratchable stuff"
+ end
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+
+ expect(out).to include("Bundle complete!")
+ end
+
+ context "when ruby version is specified in gemspec and gemfile" do
+ it "installs when patch level is not specified and the version matches",
+ :if => RUBY_PATCHLEVEL >= 0 do
+ build_lib("foo", :path => bundled_app) do |s|
+ s.required_ruby_version = "~> #{RUBY_VERSION}.0"
+ end
+
+ install_gemfile <<-G
+ ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby'
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "installs when patch level is specified and the version still matches the current version",
+ :if => RUBY_PATCHLEVEL >= 0 do
+ build_lib("foo", :path => bundled_app) do |s|
+ s.required_ruby_version = "#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{RUBY_PATCHLEVEL}'
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "fails and complains about patchlevel on patchlevel mismatch",
+ :if => RUBY_PATCHLEVEL >= 0 do
+ patchlevel = RUBY_PATCHLEVEL.to_i + 1
+ build_lib("foo", :path => bundled_app) do |s|
+ s.required_ruby_version = "#{RUBY_VERSION}.#{patchlevel}"
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{patchlevel}'
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+
+ expect(err).to include("Ruby patchlevel")
+ expect(err).to include("but your Gemfile specified")
+ expect(exitstatus).to eq(18)
+ end
+
+ it "fails and complains about version on version mismatch" do
+ version = Gem::Requirement.create(RUBY_VERSION).requirements.first.last.bump.version
+
+ build_lib("foo", :path => bundled_app) do |s|
+ s.required_ruby_version = version
+ end
+
+ install_gemfile <<-G, :raise_on_error => false
+ ruby '#{version}', :engine_version => '#{version}', :engine => 'ruby'
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+
+ expect(err).to include("Ruby version")
+ expect(err).to include("but your Gemfile specified")
+ expect(exitstatus).to eq(18)
+ end
+ end
+end
diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb
new file mode 100644
index 0000000000..882f2a2d42
--- /dev/null
+++ b/spec/bundler/install/git_spec.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ context "git sources" do
+ it "displays the revision hash of the gem repository" do
+ build_git "foo", "1.0", :path => lib_path("foo")
+
+ install_gemfile <<-G, :verbose => true
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo"))}"
+ G
+
+ expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{revision_for(lib_path("foo"))[0..6]})")
+ expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}"
+ end
+
+ it "displays the correct default branch", :git => ">= 2.28.0" do
+ build_git "foo", "1.0", :path => lib_path("foo"), :default_branch => "main"
+
+ install_gemfile <<-G, :verbose => true
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo"))}"
+ G
+
+ expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{revision_for(lib_path("foo"))[0..6]})")
+ expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}"
+ end
+
+ it "displays the ref of the gem repository when using branch~num as a ref" do
+ skip "maybe branch~num notation doesn't work on Windows' git" if Gem.win_platform?
+
+ build_git "foo", "1.0", :path => lib_path("foo")
+ rev = revision_for(lib_path("foo"))[0..6]
+ update_git "foo", "2.0", :path => lib_path("foo"), :gemspec => true
+ rev2 = revision_for(lib_path("foo"))[0..6]
+ update_git "foo", "3.0", :path => lib_path("foo"), :gemspec => true
+
+ install_gemfile <<-G, :verbose => true
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo"))}", :ref => "main~2"
+ G
+
+ expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main~2@#{rev})")
+ expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}"
+
+ update_git "foo", "4.0", :path => lib_path("foo"), :gemspec => true
+
+ bundle :update, :all => true, :verbose => true
+ expect(out).to include("Using foo 2.0 (was 1.0) from #{file_uri_for(lib_path("foo"))} (at main~2@#{rev2})")
+ expect(the_bundle).to include_gems "foo 2.0", :source => "git@#{lib_path("foo")}"
+ end
+
+ it "should allows git repos that are missing but not being installed" do
+ revision = build_git("foo").ref_for("HEAD")
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :group => :development
+ G
+
+ lockfile <<-L
+ GIT
+ remote: #{file_uri_for(lib_path("foo-1.0"))}
+ revision: #{revision}
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ foo!
+ L
+
+ bundle "config set --local path vendor/bundle"
+ bundle "config set --local without development"
+ bundle :install
+
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "allows multiple gems from the same git source" do
+ build_repo2 do
+ build_lib "foo", "1.0", :path => lib_path("gems/foo")
+ build_lib "zebra", "2.0", :path => lib_path("gems/zebra")
+ build_git "gems", :path => lib_path("gems"), :gemspec => false
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("gems"))}", :glob => "foo/*.gemspec"
+ gem "zebra", :git => "#{file_uri_for(lib_path("gems"))}", :glob => "zebra/*.gemspec"
+ G
+
+ bundle "info foo"
+ expect(out).to include("* foo (1.0 #{revision_for(lib_path("gems"))[0..6]})")
+
+ bundle "info zebra"
+ expect(out).to include("* zebra (2.0 #{revision_for(lib_path("gems"))[0..6]})")
+ end
+
+ it "should always sort dependencies in the same order" do
+ # This Gemfile + lockfile had a problem where the first
+ # `bundle install` would change the order, but the second would
+ # change it back.
+
+ # NOTE: both gems MUST have the same path! It has to be two gems in one repo.
+
+ test = build_git "test", "1.0.0", :path => lib_path("test-and-other")
+ other = build_git "other", "1.0.0", :path => lib_path("test-and-other")
+ test_ref = test.ref_for("HEAD")
+ other_ref = other.ref_for("HEAD")
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "test", git: #{test.path.to_s.inspect}
+ gem "other", ref: #{other_ref.inspect}, git: #{other.path.to_s.inspect}
+ G
+
+ lockfile <<-L
+ GIT
+ remote: #{test.path}
+ revision: #{test_ref}
+ specs:
+ test (1.0.0)
+
+ GIT
+ remote: #{other.path}
+ revision: #{other_ref}
+ ref: #{other_ref}
+ specs:
+ other (1.0.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ other!
+ test!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ # If GH#6743 is present, the first `bundle install` will change the
+ # lockfile, by flipping the order (`other` would be moved to the top).
+ #
+ # The second `bundle install` would then change the lockfile back
+ # to the original.
+ #
+ # The fix makes it so it may change it once, but it will not change
+ # it a second time.
+ #
+ # So, we run `bundle install` once, and store the value of the
+ # modified lockfile.
+ bundle :install
+ modified_lockfile = lockfile
+
+ # If GH#6743 is present, the second `bundle install` would change the
+ # lockfile back to what it was originally.
+ #
+ # This `expect` makes sure it doesn't change a second time.
+ bundle :install
+ expect(lockfile).to eq(modified_lockfile)
+
+ expect(out).to include("Bundle complete!")
+ end
+ end
+end
diff --git a/spec/bundler/install/global_cache_spec.rb b/spec/bundler/install/global_cache_spec.rb
new file mode 100644
index 0000000000..be34d8b5bc
--- /dev/null
+++ b/spec/bundler/install/global_cache_spec.rb
@@ -0,0 +1,254 @@
+# frozen_string_literal: true
+
+RSpec.describe "global gem caching" do
+ before { bundle "config set global_gem_cache true" }
+
+ describe "using the cross-application user cache" do
+ let(:source) { "http://localgemserver.test" }
+ let(:source2) { "http://gemserver.example.org" }
+
+ def source_global_cache(*segments)
+ home(".bundle", "cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", *segments)
+ end
+
+ def source2_global_cache(*segments)
+ home(".bundle", "cache", "gems", "gemserver.example.org.80.1ae1663619ffe0a3c9d97712f44c705b", *segments)
+ end
+
+ it "caches gems into the global cache on download" do
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "#{source}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ end
+
+ it "uses globally cached gems if they exist" do
+ source_global_cache.mkpath
+ FileUtils.cp(gem_repo1("gems/rack-1.0.0.gem"), source_global_cache("rack-1.0.0.gem"))
+
+ install_gemfile <<-G, :artifice => "compact_index_no_gem"
+ source "#{source}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "shows a proper error message if a cached gem is corrupted" do
+ source_global_cache.mkpath
+ FileUtils.touch(source_global_cache("rack-1.0.0.gem"))
+
+ install_gemfile <<-G, :artifice => "compact_index_no_gem", :raise_on_error => false
+ source "#{source}"
+ gem "rack"
+ G
+
+ expect(err).to include("Gem::Package::FormatError: package metadata is missing in #{source_global_cache("rack-1.0.0.gem")}")
+ end
+
+ describe "when the same gem from different sources is installed" do
+ it "should use the appropriate one from the global cache" do
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "#{source}"
+ gem "rack"
+ G
+
+ simulate_new_machine
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ # rack 1.0.0 is not installed and it is in the global cache
+
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "#{source2}"
+ gem "rack", "0.9.1"
+ G
+
+ simulate_new_machine
+ expect(the_bundle).not_to include_gems "rack 0.9.1"
+ expect(source2_global_cache("rack-0.9.1.gem")).to exist
+ # rack 0.9.1 is not installed and it is in the global cache
+
+ gemfile <<-G
+ source "#{source}"
+ gem "rack", "1.0.0"
+ G
+
+ bundle :install, :artifice => "compact_index_no_gem"
+ # rack 1.0.0 is installed and rack 0.9.1 is not
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "rack 0.9.1"
+ simulate_new_machine
+
+ gemfile <<-G
+ source "#{source2}"
+ gem "rack", "0.9.1"
+ G
+
+ bundle :install, :artifice => "compact_index_no_gem"
+ # rack 0.9.1 is installed and rack 1.0.0 is not
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ end
+
+ it "should not install if the wrong source is provided" do
+ gemfile <<-G
+ source "#{source}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "compact_index"
+ simulate_new_machine
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ # rack 1.0.0 is not installed and it is in the global cache
+
+ gemfile <<-G
+ source "#{source2}"
+ gem "rack", "0.9.1"
+ G
+
+ bundle :install, :artifice => "compact_index"
+ simulate_new_machine
+ expect(the_bundle).not_to include_gems "rack 0.9.1"
+ expect(source2_global_cache("rack-0.9.1.gem")).to exist
+ # rack 0.9.1 is not installed and it is in the global cache
+
+ gemfile <<-G
+ source "#{source2}"
+ gem "rack", "1.0.0"
+ G
+
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source2_global_cache("rack-0.9.1.gem")).to exist
+ bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false
+ expect(err).to include("Internal Server Error 500")
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+
+ # rack 1.0.0 is not installed and rack 0.9.1 is not
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "rack 0.9.1"
+
+ gemfile <<-G
+ source "#{source}"
+ gem "rack", "0.9.1"
+ G
+
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source2_global_cache("rack-0.9.1.gem")).to exist
+ bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false
+ expect(err).to include("Internal Server Error 500")
+ expect(err).not_to include("ERROR REPORT TEMPLATE")
+
+ # rack 0.9.1 is not installed and rack 1.0.0 is not
+ expect(the_bundle).not_to include_gems "rack 0.9.1"
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ end
+ end
+
+ describe "when installing gems from a different directory" do
+ it "uses the global cache as a source" do
+ install_gemfile <<-G, :artifice => "compact_index"
+ source "#{source}"
+ gem "rack"
+ gem "activesupport"
+ G
+
+ # Both gems are installed and in the global cache
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source_global_cache("activesupport-2.3.5.gem")).to exist
+ simulate_new_machine
+ # Both gems are now only in the global cache
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+
+ install_gemfile <<-G, :artifice => "compact_index_no_gem"
+ source "#{source}"
+ gem "rack"
+ G
+
+ # rack is installed and both are in the global cache
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source_global_cache("activesupport-2.3.5.gem")).to exist
+
+ create_file bundled_app2("gems.rb"), <<-G
+ source "#{source}"
+ gem "activesupport"
+ G
+
+ # Neither gem is installed and both are in the global cache
+ expect(the_bundle).not_to include_gems "rack 1.0.0", :dir => bundled_app2
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5", :dir => bundled_app2
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source_global_cache("activesupport-2.3.5.gem")).to exist
+
+ # Install using the global cache instead of by downloading the .gem
+ # from the server
+ bundle :install, :artifice => "compact_index_no_gem", :dir => bundled_app2
+
+ # activesupport is installed and both are in the global cache
+ simulate_bundler_version_when_missing_prerelease_default_gem_activation do
+ expect(the_bundle).not_to include_gems "rack 1.0.0", :dir => bundled_app2
+ expect(the_bundle).to include_gems "activesupport 2.3.5", :dir => bundled_app2
+ end
+
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source_global_cache("activesupport-2.3.5.gem")).to exist
+ end
+ end
+ end
+
+ describe "extension caching" do
+ it "works" do
+ skip "gets incorrect ref in path" if Gem.win_platform?
+
+ build_git "very_simple_git_binary", &:add_c_extension
+ build_lib "very_simple_path_binary", &:add_c_extension
+ revision = revision_for(lib_path("very_simple_git_binary-1.0"))[0, 12]
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "very_simple_binary"
+ gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}"
+ gem "very_simple_path_binary", :path => "#{lib_path("very_simple_path_binary-1.0")}"
+ G
+
+ gem_binary_cache = home(".bundle", "cache", "extensions", local_platform.to_s, Bundler.ruby_scope,
+ Digest(:MD5).hexdigest("#{gem_repo1}/"), "very_simple_binary-1.0")
+ git_binary_cache = home(".bundle", "cache", "extensions", local_platform.to_s, Bundler.ruby_scope,
+ "very_simple_git_binary-1.0-#{revision}", "very_simple_git_binary-1.0")
+
+ cached_extensions = Pathname.glob(home(".bundle", "cache", "extensions", "*", "*", "*", "*", "*")).sort
+ expect(cached_extensions).to eq [gem_binary_cache, git_binary_cache].sort
+
+ run <<-R
+ require 'very_simple_binary_c'; puts ::VERY_SIMPLE_BINARY_IN_C
+ require 'very_simple_git_binary_c'; puts ::VERY_SIMPLE_GIT_BINARY_IN_C
+ R
+ expect(out).to eq "VERY_SIMPLE_BINARY_IN_C\nVERY_SIMPLE_GIT_BINARY_IN_C"
+
+ FileUtils.rm Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")]
+
+ gem_binary_cache.join("very_simple_binary_c.rb").open("w") {|f| f << "puts File.basename(__FILE__)" }
+ git_binary_cache.join("very_simple_git_binary_c.rb").open("w") {|f| f << "puts File.basename(__FILE__)" }
+
+ bundle "config set --local path different_path"
+ bundle :install
+
+ expect(Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")]).to all(end_with(".rb"))
+
+ run <<-R
+ require 'very_simple_binary_c'
+ require 'very_simple_git_binary_c'
+ R
+ expect(out).to eq "very_simple_binary_c.rb\nvery_simple_git_binary_c.rb"
+ end
+ end
+end
diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb
new file mode 100644
index 0000000000..bd5385b265
--- /dev/null
+++ b/spec/bundler/install/path_spec.rb
@@ -0,0 +1,226 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ describe "with path configured" do
+ before :each do
+ build_gem "rack", "1.0.0", :to_system => true do |s|
+ s.write "lib/rack.rb", "puts 'FAIL'"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ it "does not use available system gems with `vendor/bundle" do
+ bundle "config set --local path vendor/bundle"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "uses system gems with `path.system` configured with more priority than `path`" do
+ bundle "config set --local path.system true"
+ bundle "config set --global path vendor/bundle"
+ bundle :install
+ run "require 'rack'", :raise_on_error => false
+ expect(out).to include("FAIL")
+ end
+
+ it "handles paths with regex characters in them" do
+ dir = bundled_app("bun++dle")
+ dir.mkpath
+
+ bundle "config set --local path #{dir.join("vendor/bundle")}"
+ bundle :install, :dir => dir
+ expect(out).to include("installed into `./vendor/bundle`")
+
+ dir.rmtree
+ end
+
+ it "prints a message to let the user know where gems where installed" do
+ bundle "config set --local path vendor/bundle"
+ bundle :install
+ expect(out).to include("gems are installed into `./vendor/bundle`")
+ end
+
+ it "disallows --path vendor/bundle --system", :bundler => "< 3" do
+ bundle "install --path vendor/bundle --system", :raise_on_error => false
+ expect(err).to include("Please choose only one option.")
+ expect(exitstatus).to eq(15)
+ end
+
+ it "remembers to disable system gems after the first time with bundle --path vendor/bundle", :bundler => "< 3" do
+ bundle "install --path vendor/bundle"
+ FileUtils.rm_rf bundled_app("vendor")
+ bundle "install"
+
+ expect(vendored_gems("gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ context "with path_relative_to_cwd set to true" do
+ before { bundle "config set path_relative_to_cwd true" }
+
+ it "installs the bundle relatively to current working directory", :bundler => "< 3" do
+ bundle "install --gemfile='#{bundled_app}/Gemfile' --path vendor/bundle", :dir => bundled_app.parent
+ expect(out).to include("installed into `./vendor/bundle`")
+ expect(bundled_app("../vendor/bundle")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs the standalone bundle relative to the cwd" do
+ bundle :install, :gemfile => bundled_app_gemfile, :standalone => true, :dir => bundled_app.parent
+ expect(out).to include("installed into `./bundled_app/bundle`")
+ expect(bundled_app("bundle")).to be_directory
+ expect(bundled_app("bundle/ruby")).to be_directory
+
+ bundle "config unset path"
+
+ bundle :install, :gemfile => bundled_app_gemfile, :standalone => true, :dir => bundled_app("subdir").tap(&:mkpath)
+ expect(out).to include("installed into `../bundle`")
+ expect(bundled_app("bundle")).to be_directory
+ expect(bundled_app("bundle/ruby")).to be_directory
+ end
+ end
+ end
+
+ describe "when BUNDLE_PATH or the global path config is set" do
+ before :each do
+ build_lib "rack", "1.0.0", :to_system => true do |s|
+ s.write "lib/rack.rb", "raise 'FAIL'"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ def set_bundle_path(type, location)
+ if type == :env
+ ENV["BUNDLE_PATH"] = location
+ elsif type == :global
+ bundle "config set path #{location}", "no-color" => nil
+ end
+ end
+
+ [:env, :global].each do |type|
+ context "when set via #{type}" do
+ it "installs gems to a path if one is specified" do
+ set_bundle_path(type, bundled_app("vendor2").to_s)
+ bundle "config set --local path vendor/bundle"
+ bundle :install
+
+ expect(vendored_gems("gems/rack-1.0.0")).to be_directory
+ expect(bundled_app("vendor2")).not_to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs gems to ." do
+ set_bundle_path(type, ".")
+ bundle "config set --global disable_shared_gems true"
+
+ bundle :install
+
+ paths_to_exist = %w[cache/rack-1.0.0.gem gems/rack-1.0.0 specifications/rack-1.0.0.gemspec].map {|path| bundled_app(Bundler.ruby_scope, path) }
+ expect(paths_to_exist).to all exist
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs gems to the path" do
+ set_bundle_path(type, bundled_app("vendor").to_s)
+
+ bundle :install
+
+ expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs gems to the path relative to root when relative" do
+ set_bundle_path(type, "vendor")
+
+ FileUtils.mkdir_p bundled_app("lol")
+ bundle :install, :dir => bundled_app("lol")
+
+ expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+
+ it "installs gems to BUNDLE_PATH from .bundle/config" do
+ config "BUNDLE_PATH" => bundled_app("vendor/bundle").to_s
+
+ bundle :install
+
+ expect(vendored_gems("gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "sets BUNDLE_PATH as the first argument to bundle install" do
+ bundle "config set --local path ./vendor/bundle"
+ bundle :install
+
+ expect(vendored_gems("gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "disables system gems when passing a path to install" do
+ # This is so that vendored gems can be distributed to others
+ build_gem "rack", "1.1.0", :to_system => true
+ bundle "config set --local path ./vendor/bundle"
+ bundle :install
+
+ expect(vendored_gems("gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "re-installs gems whose extensions have been deleted" do
+ build_lib "very_simple_binary", "1.0.0", :to_system => true do |s|
+ s.write "lib/very_simple_binary.rb", "raise 'FAIL'"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "very_simple_binary"
+ G
+
+ bundle "config set --local path ./vendor/bundle"
+ bundle :install
+
+ expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory
+ expect(vendored_gems("extensions")).to be_directory
+ expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1"
+
+ vendored_gems("extensions").rmtree
+
+ run "require 'very_simple_binary_c'", :raise_on_error => false
+ expect(err).to include("Bundler::GemNotFound")
+
+ bundle "config set --local path ./vendor/bundle"
+ bundle :install
+
+ expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory
+ expect(vendored_gems("extensions")).to be_directory
+ expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1"
+ end
+ end
+
+ describe "to a file" do
+ before do
+ FileUtils.touch bundled_app("bundle")
+ end
+
+ it "reports the file exists" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "config set --local path bundle"
+ bundle :install, :raise_on_error => false
+ expect(err).to include("file already exists")
+ end
+ end
+end
diff --git a/spec/bundler/install/prereleases_spec.rb b/spec/bundler/install/prereleases_spec.rb
new file mode 100644
index 0000000000..629eb89dac
--- /dev/null
+++ b/spec/bundler/install/prereleases_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ before do
+ build_repo2 do
+ build_gem "not_released", "1.0.pre"
+
+ build_gem "has_prerelease", "1.0"
+ build_gem "has_prerelease", "1.1.pre"
+ end
+ end
+
+ describe "when prerelease gems are available" do
+ it "finds prereleases" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "not_released"
+ G
+ expect(the_bundle).to include_gems "not_released 1.0.pre"
+ end
+
+ it "uses regular releases if available" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "has_prerelease"
+ G
+ expect(the_bundle).to include_gems "has_prerelease 1.0"
+ end
+
+ it "uses prereleases if requested" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "has_prerelease", "1.1.pre"
+ G
+ expect(the_bundle).to include_gems "has_prerelease 1.1.pre"
+ end
+ end
+
+ describe "when prerelease gems are not available" do
+ it "still works" do
+ build_repo gem_repo3 do
+ build_gem "rack"
+ end
+ FileUtils.rm_rf Dir[gem_repo3("prerelease*")]
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo3)}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/process_lock_spec.rb b/spec/bundler/install/process_lock_spec.rb
new file mode 100644
index 0000000000..1f8c62f26e
--- /dev/null
+++ b/spec/bundler/install/process_lock_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+RSpec.describe "process lock spec" do
+ describe "when an install operation is already holding a process lock" do
+ before { FileUtils.mkdir_p(default_bundle_path) }
+
+ it "will not run a second concurrent bundle install until the lock is released" do
+ thread = Thread.new do
+ Bundler::ProcessLock.lock(default_bundle_path) do
+ sleep 1 # ignore quality_spec
+ expect(the_bundle).not_to include_gems "rack 1.0"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ thread.join
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ context "when creating a lock raises Errno::ENOTSUP" do
+ before { allow(File).to receive(:open).and_raise(Errno::ENOTSUP) }
+
+ it "skips creating the lock file and yields" do
+ processed = false
+ Bundler::ProcessLock.lock(default_bundle_path) { processed = true }
+
+ expect(processed).to eq true
+ end
+ end
+
+ context "when creating a lock raises Errno::EPERM" do
+ before { allow(File).to receive(:open).and_raise(Errno::EPERM) }
+
+ it "skips creating the lock file and yields" do
+ processed = false
+ Bundler::ProcessLock.lock(default_bundle_path) { processed = true }
+
+ expect(processed).to eq true
+ end
+ end
+
+ context "when creating a lock raises Errno::EROFS" do
+ before { allow(File).to receive(:open).and_raise(Errno::EROFS) }
+
+ it "skips creating the lock file and yields" do
+ processed = false
+ Bundler::ProcessLock.lock(default_bundle_path) { processed = true }
+
+ expect(processed).to eq true
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/redownload_spec.rb b/spec/bundler/install/redownload_spec.rb
new file mode 100644
index 0000000000..a936b2b536
--- /dev/null
+++ b/spec/bundler/install/redownload_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ before :each do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ shared_examples_for "an option to force redownloading gems" do
+ it "re-installs installed gems" do
+ rack_lib = default_bundle_path("gems/rack-1.0.0/lib/rack.rb")
+
+ bundle :install
+ rack_lib.open("w") {|f| f.write("blah blah blah") }
+ bundle :install, flag => true
+
+ expect(out).to include "Installing rack 1.0.0"
+ expect(rack_lib.open(&:read)).to eq("RACK = '1.0.0'\n")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "works on first bundle install" do
+ bundle :install, flag => true
+
+ expect(out).to include "Installing rack 1.0.0"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ context "with a git gem" do
+ let!(:ref) { build_git("foo", "1.0").ref_for("HEAD", 11) }
+
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+ end
+
+ it "re-installs installed gems" do
+ foo_lib = default_bundle_path("bundler/gems/foo-1.0-#{ref}/lib/foo.rb")
+
+ bundle :install
+ foo_lib.open("w") {|f| f.write("blah blah blah") }
+ bundle :install, flag => true
+
+ expect(foo_lib.open(&:read)).to eq("FOO = '1.0'\n")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "works on first bundle install" do
+ bundle :install, flag => true
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+ end
+ end
+
+ describe "with --force", :bundler => 2 do
+ it_behaves_like "an option to force redownloading gems" do
+ let(:flag) { "force" }
+ end
+
+ it "shows a deprecation when single flag passed" do
+ bundle "install --force"
+ expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`"
+ end
+
+ it "shows a deprecation when multiple flags passed" do
+ bundle "install --no-color --force"
+ expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`"
+ end
+ end
+
+ describe "with --redownload" do
+ it_behaves_like "an option to force redownloading gems" do
+ let(:flag) { "redownload" }
+ end
+
+ it "does not show a deprecation when single flag passed" do
+ bundle "install --redownload"
+ expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`"
+ end
+
+ it "does not show a deprecation when single multiple flags passed" do
+ bundle "install --no-color --redownload"
+ expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`"
+ end
+ end
+end
diff --git a/spec/bundler/install/security_policy_spec.rb b/spec/bundler/install/security_policy_spec.rb
new file mode 100644
index 0000000000..43c3069c4e
--- /dev/null
+++ b/spec/bundler/install/security_policy_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require "rubygems/security"
+
+# unfortunately, testing signed gems with a provided CA is extremely difficult
+# as 'gem cert' is currently the only way to add CAs to the system.
+
+RSpec.describe "policies with unsigned gems" do
+ before do
+ build_security_repo
+ gemfile <<-G
+ source "#{file_uri_for(security_repo)}"
+ gem "rack"
+ gem "signed_gem"
+ G
+ end
+
+ it "will work after you try to deploy without a lock" do
+ bundle "install --deployment", :raise_on_error => false
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0", "signed_gem 1.0"
+ end
+
+ it "will fail when given invalid security policy" do
+ bundle "install --trust-policy=InvalidPolicyName", :raise_on_error => false
+ expect(err).to include("RubyGems doesn't know about trust policy")
+ end
+
+ it "will fail with High Security setting due to presence of unsigned gem" do
+ bundle "install --trust-policy=HighSecurity", :raise_on_error => false
+ expect(err).to include("security policy didn't allow")
+ end
+
+ it "will fail with Medium Security setting due to presence of unsigned gem" do
+ bundle "install --trust-policy=MediumSecurity", :raise_on_error => false
+ expect(err).to include("security policy didn't allow")
+ end
+
+ it "will succeed with no policy" do
+ bundle "install"
+ end
+end
+
+RSpec.describe "policies with signed gems and no CA" do
+ before do
+ build_security_repo
+ gemfile <<-G
+ source "#{file_uri_for(security_repo)}"
+ gem "signed_gem"
+ G
+ end
+
+ it "will fail with High Security setting, gem is self-signed" do
+ bundle "install --trust-policy=HighSecurity", :raise_on_error => false
+ expect(err).to include("security policy didn't allow")
+ end
+
+ it "will fail with Medium Security setting, gem is self-signed" do
+ bundle "install --trust-policy=MediumSecurity", :raise_on_error => false
+ expect(err).to include("security policy didn't allow")
+ end
+
+ it "will succeed with Low Security setting, low security accepts self signed gem" do
+ bundle "install --trust-policy=LowSecurity"
+ expect(the_bundle).to include_gems "signed_gem 1.0"
+ end
+
+ it "will succeed with no policy" do
+ bundle "install"
+ expect(the_bundle).to include_gems "signed_gem 1.0"
+ end
+end
diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb
new file mode 100644
index 0000000000..dc054b50bb
--- /dev/null
+++ b/spec/bundler/install/yanked_spec.rb
@@ -0,0 +1,233 @@
+# frozen_string_literal: true
+
+RSpec.context "when installing a bundle that includes yanked gems" do
+ before(:each) do
+ build_repo4 do
+ build_gem "foo", "9.0.0"
+ end
+ end
+
+ it "throws an error when the original gem version is yanked" do
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}
+ specs:
+ foo (10.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo (= 10.0.0)
+
+ L
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo4)}"
+ gem "foo", "10.0.0"
+ G
+
+ expect(err).to include("Your bundle is locked to foo (10.0.0)")
+ end
+
+ context "when a re-resolve is necessary, and a yanked version is considered by the resolver" do
+ before do
+ skip "Materialization on Windows is not yet strict, so the example does not detect the gem has been yanked" if Gem.win_platform?
+
+ build_repo4 do
+ build_gem "foo", "1.0.0", "1.0.1"
+ build_gem "actiontext", "6.1.7" do |s|
+ s.add_dependency "nokogiri", ">= 1.8"
+ end
+ build_gem "actiontext", "6.1.6" do |s|
+ s.add_dependency "nokogiri", ">= 1.8"
+ end
+ build_gem "actiontext", "6.1.7" do |s|
+ s.add_dependency "nokogiri", ">= 1.8"
+ end
+ build_gem "nokogiri", "1.13.8"
+ end
+
+ gemfile <<~G
+ source "#{source_uri}"
+ gem "foo", "1.0.1"
+ gem "actiontext", "6.1.6"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{source_uri}/
+ specs:
+ actiontext (6.1.6)
+ nokogiri (>= 1.8)
+ foo (1.0.0)
+ nokogiri (1.13.8-#{Bundler.local_platform})
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ actiontext (= 6.1.6)
+ foo (= 1.0.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ context "and the old index is used" do
+ let(:source_uri) { file_uri_for(gem_repo4) }
+
+ it "reports the yanked gem properly" do
+ bundle "install", :raise_on_error => false
+
+ expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})")
+ end
+ end
+
+ context "and the compact index API is used" do
+ let(:source_uri) { "https://gem.repo4" }
+
+ it "reports the yanked gem properly" do
+ bundle "install", :artifice => "compact_index", :raise_on_error => false
+
+ expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})")
+ end
+ end
+ end
+
+ it "throws the original error when only the Gemfile specifies a gem version that doesn't exist" do
+ bundle "config set force_ruby_platform true"
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo4)}"
+ gem "foo", "10.0.0"
+ G
+
+ expect(err).not_to include("Your bundle is locked to foo (10.0.0)")
+ expect(err).to include("Could not find gem 'foo (= 10.0.0)' in")
+ end
+end
+
+RSpec.context "when resolving a bundle that includes yanked gems, but unlocking an unrelated gem" do
+ before(:each) do
+ build_repo4 do
+ build_gem "foo", "10.0.0"
+
+ build_gem "bar", "1.0.0"
+ build_gem "bar", "2.0.0"
+ end
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}
+ specs:
+ foo (9.0.0)
+ bar (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+ bar
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "foo"
+ gem "bar"
+ G
+ end
+
+ it "does not update the yanked gem" do
+ bundle "lock --update bar"
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ bar (2.0.0)
+ foo (9.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ bar
+ foo
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+end
+
+RSpec.context "when using gem before installing" do
+ it "does not suggest the author has yanked the gem" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (= 0.9.1)
+ L
+
+ bundle :list, :raise_on_error => false
+
+ expect(err).to include("Could not find rack-0.9.1 in locally installed gems")
+ expect(err).to_not include("Your bundle is locked to rack (0.9.1) from")
+ expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.")
+ expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.")
+
+ # Check error message is still correct when multiple platforms are locked
+ lockfile lockfile.gsub(/PLATFORMS\n #{lockfile_platforms}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}")
+
+ bundle :list, :raise_on_error => false
+ expect(err).to include("Could not find rack-0.9.1 in locally installed gems")
+ end
+
+ it "does not suggest the author has yanked the gem when using more than one gem, but shows all gems that couldn't be found in the source" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ gem "rack_middleware", "1.0"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}
+ specs:
+ rack (0.9.1)
+ rack_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (= 0.9.1)
+ rack_middleware (1.0)
+ L
+
+ bundle :list, :raise_on_error => false
+
+ expect(err).to include("Could not find rack-0.9.1, rack_middleware-1.0 in locally installed gems")
+ expect(err).to include("Install missing gems with `bundle install`.")
+ expect(err).to_not include("Your bundle is locked to rack (0.9.1) from")
+ expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.")
+ expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.")
+ end
+end
diff --git a/spec/bundler/lock/git_spec.rb b/spec/bundler/lock/git_spec.rb
new file mode 100644
index 0000000000..ac3d10223c
--- /dev/null
+++ b/spec/bundler/lock/git_spec.rb
@@ -0,0 +1,162 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle lock with git gems" do
+ before :each do
+ build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => "#{lib_path("foo-1.0")}"
+ G
+ end
+
+ it "doesn't break right after running lock" do
+ expect(the_bundle).to include_gems "foo 1.0.0"
+ end
+
+ it "doesn't print errors even if running lock after removing the cache" do
+ FileUtils.rm_rf(Dir[default_cache_path("git/foo-1.0-*")].first)
+
+ bundle "lock --verbose"
+
+ expect(err).to be_empty
+ end
+
+ it "prints a proper error when changing a locked Gemfile to point to a bad branch" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => "#{lib_path("foo-1.0")}", :branch => "bad"
+ G
+
+ bundle "lock --update foo", :env => { "LANG" => "en" }, :raise_on_error => false
+
+ expect(err).to include("Revision bad does not exist in the repository")
+ end
+
+ it "prints a proper error when installing a Gemfile with a locked ref that does not exist" do
+ lockfile <<~L
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{"a" * 40}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install", :raise_on_error => false
+
+ expect(err).to include("Revision #{"a" * 40} does not exist in the repository")
+ end
+
+ it "locks a git source to the current ref" do
+ update_git "foo"
+ bundle :install
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" unless defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "properly clones a git source locked to an out of date ref" do
+ update_git "foo"
+
+ bundle :install, :env => { "BUNDLE_PATH" => "foo" }
+ expect(err).to be_empty
+ end
+
+ it "properly fetches a git source locked to an unreachable ref" do
+ # Create a commit and make it unreachable
+ git "checkout -b foo ", lib_path("foo-1.0")
+ unreachable_sha = update_git("foo").ref_for("HEAD")
+ git "checkout main ", lib_path("foo-1.0")
+ git "branch -D foo ", lib_path("foo-1.0")
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => "#{lib_path("foo-1.0")}"
+ G
+
+ lockfile <<-L
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{unreachable_sha}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+
+ expect(err).to be_empty
+ end
+
+ it "properly fetches a git source locked to an annotated tag" do
+ # Create an annotated tag
+ git("tag -a v1.0 -m 'Annotated v1.0'", lib_path("foo-1.0"))
+ annotated_tag = git("rev-parse v1.0", lib_path("foo-1.0"))
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => "#{lib_path("foo-1.0")}"
+ G
+
+ lockfile <<-L
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{annotated_tag}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+
+ expect(err).to be_empty
+ end
+
+ it "provides correct #full_gem_path" do
+ run <<-RUBY
+ puts Bundler.rubygems.find_name('foo').first.full_gem_path
+ RUBY
+ expect(out).to eq(bundle("info foo --path"))
+ end
+end
diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb
new file mode 100644
index 0000000000..ccf23a9e3c
--- /dev/null
+++ b/spec/bundler/lock/lockfile_spec.rb
@@ -0,0 +1,1608 @@
+# frozen_string_literal: true
+
+RSpec.describe "the lockfile format" do
+ before do
+ build_repo2
+ end
+
+ it "generates a simple lockfile for a single source, gem" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rack"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "updates the lockfile's bundler version if current ver. is newer, and version was forced through BUNDLER_VERSION" do
+ system_gems "bundler-1.8.2"
+
+ lockfile <<-L
+ GIT
+ remote: git://github.com/nex3/haml.git
+ revision: 8a2271f
+ specs:
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ omg!
+ rack
+
+ BUNDLED WITH
+ 1.8.2
+ L
+
+ install_gemfile <<-G, :verbose => true, :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rack"
+ G
+
+ expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with 1.8.2.")
+ expect(out).to include("Using bundler #{Bundler::VERSION}")
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not update the lockfile's bundler version if nothing changed during bundle install, but uses the locked version", :rubygems => ">= 3.3.0.a", :realworld => true do
+ version = "2.3.0"
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{version}
+ L
+
+ install_gemfile <<-G, :verbose => true, :artifice => "vcr"
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rack"
+ G
+
+ expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{version}.")
+ expect(out).to include("Using bundler #{version}")
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{version}
+ G
+ end
+
+ it "does not update the lockfile's bundler version if nothing changed during bundle install, and uses the latest version", :rubygems => "< 3.3.0.a" do
+ version = "#{Bundler::VERSION.split(".").first}.0.0.a"
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{version}
+ L
+
+ install_gemfile <<-G, :verbose => true
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rack"
+ G
+
+ expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{version}.")
+ expect(out).to include("Using bundler #{Bundler::VERSION}")
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{version}
+ G
+ end
+
+ it "adds the BUNDLED WITH section if not present" do
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+ L
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rack", "> 0"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (> 0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "update the bundler major version just fine" do
+ current_version = Bundler::VERSION
+ older_major = previous_major(current_version)
+
+ system_gems "bundler-#{older_major}"
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{older_major}
+ L
+
+ install_gemfile <<-G, :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+ source "#{file_uri_for(gem_repo2)}/"
+
+ gem "rack"
+ G
+
+ expect(err).to be_empty
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{current_version}
+ G
+ end
+
+ it "generates a simple lockfile for a single source, gem with dependencies" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+
+ gem "rack-obama"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack-obama
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a simple lockfile for a single source, gem with a version requirement" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+
+ gem "rack-obama", ">= 1.0"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a lockfile without credentials for a configured source" do
+ bundle "config set http://localgemserver.test/ user:pass"
+
+ install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true)
+ source "#{file_uri_for(gem_repo1)}"
+
+ source "http://localgemserver.test/" do
+
+ end
+
+ source "http://user:pass@othergemserver.test/" do
+ gem "rack-obama", ">= 1.0"
+ end
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ GEM
+ remote: http://localgemserver.test/
+ specs:
+
+ GEM
+ remote: http://user:pass@othergemserver.test/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates lockfiles with multiple requirements" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+ gem "net-sftp"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ net-sftp (1.1.1)
+ net-ssh (>= 1.0.0, < 1.99.0)
+ net-ssh (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ net-sftp
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ expect(the_bundle).to include_gems "net-sftp 1.1.1", "net-ssh 1.0.0"
+ end
+
+ it "generates a simple lockfile for a single pinned source, gem with a version requirement" do
+ git = build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(lockfile).to eq <<~G
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("main")}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not asplode when a platform specific dependency is present and the Gemfile has not been resolved on that platform" do
+ build_lib "omg", :path => lib_path("omg")
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+
+ platforms :#{not_local_tag} do
+ gem "omg", :path => "#{lib_path("omg")}"
+ end
+
+ gem "rack"
+ G
+
+ lockfile <<-L
+ GIT
+ remote: git://github.com/nex3/haml.git
+ revision: 8a2271f
+ specs:
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}//
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{not_local}
+
+ DEPENDENCIES
+ omg!
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "serializes global git sources" do
+ git = build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ expect(lockfile).to eq <<~G
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("main")}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a lockfile with a ref for a single pinned source, git gem with a branch requirement" do
+ git = build_git "foo"
+ update_git "foo", :branch => "omg"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg"
+ G
+
+ expect(lockfile).to eq <<~G
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("omg")}
+ branch: omg
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a lockfile with a ref for a single pinned source, git gem with a tag requirement" do
+ git = build_git "foo"
+ update_git "foo", :tag => "omg"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :tag => "omg"
+ G
+
+ expect(lockfile).to eq <<~G
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("omg")}
+ tag: omg
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "is conservative with dependencies of git gems" do
+ build_repo4 do
+ build_gem "orm_adapter", "0.4.1"
+ build_gem "orm_adapter", "0.5.0"
+ end
+
+ FileUtils.mkdir_p lib_path("ckeditor/lib")
+
+ @remote = build_git("ckeditor_remote", :bare => true)
+
+ build_git "ckeditor", :path => lib_path("ckeditor") do |s|
+ s.write "lib/ckeditor.rb", "CKEDITOR = '4.0.7'"
+ s.version = "4.0.7"
+ s.add_dependency "orm_adapter"
+ end
+
+ update_git "ckeditor", :path => lib_path("ckeditor"), :remote => file_uri_for(@remote.path)
+ update_git "ckeditor", :path => lib_path("ckeditor"), :tag => "v4.0.7"
+ old_git = update_git "ckeditor", :path => lib_path("ckeditor"), :push => "v4.0.7"
+
+ update_git "ckeditor", :path => lib_path("ckeditor"), :gemspec => true do |s|
+ s.write "lib/ckeditor.rb", "CKEDITOR = '4.0.8'"
+ s.version = "4.0.8"
+ s.add_dependency "orm_adapter"
+ end
+ update_git "ckeditor", :path => lib_path("ckeditor"), :tag => "v4.0.8"
+
+ new_git = update_git "ckeditor", :path => lib_path("ckeditor"), :push => "v4.0.8"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "ckeditor", :git => "#{@remote.path}", :tag => "v4.0.8"
+ G
+
+ lockfile <<~L
+ GIT
+ remote: #{@remote.path}
+ revision: #{old_git.ref_for("v4.0.7")}
+ tag: v4.0.7
+ specs:
+ ckeditor (4.0.7)
+ orm_adapter
+
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ orm_adapter (0.4.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ckeditor!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "lock"
+
+ # Bumps the git gem, but keeps its dependency locked
+ expect(lockfile).to eq <<~L
+ GIT
+ remote: #{@remote.path}
+ revision: #{new_git.ref_for("v4.0.8")}
+ tag: v4.0.8
+ specs:
+ ckeditor (4.0.8)
+ orm_adapter
+
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ orm_adapter (0.4.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ckeditor!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "serializes pinned path sources to the lockfile" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo-1.0")}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "serializes pinned path sources to the lockfile even when packaging" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+ bundle :install, :local => true
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: #{lib_path("foo-1.0")}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "sorts serialized sources by type" do
+ build_lib "foo"
+ bar = build_git "bar"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+
+ gem "rack"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ gem "bar", :git => "#{lib_path("bar-1.0")}"
+ G
+
+ expect(lockfile).to eq <<~G
+ GIT
+ remote: #{lib_path("bar-1.0")}
+ revision: #{bar.ref_for("main")}
+ specs:
+ bar (1.0)
+
+ PATH
+ remote: #{lib_path("foo-1.0")}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ bar!
+ foo!
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "removes redundant sources" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+
+ gem "rack", :source => "#{file_uri_for(gem_repo2)}/"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "lists gems alphabetically" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+
+ gem "thin"
+ gem "actionpack"
+ gem "rack-obama"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ actionpack (2.3.2)
+ activesupport (= 2.3.2)
+ activesupport (2.3.2)
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+ thin (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ actionpack
+ rack-obama
+ thin
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "orders dependencies' dependencies in alphabetical order" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+
+ gem "rails"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ actionmailer (2.3.2)
+ activesupport (= 2.3.2)
+ actionpack (2.3.2)
+ activesupport (= 2.3.2)
+ activerecord (2.3.2)
+ activesupport (= 2.3.2)
+ activeresource (2.3.2)
+ activesupport (= 2.3.2)
+ activesupport (2.3.2)
+ rails (2.3.2)
+ actionmailer (= 2.3.2)
+ actionpack (= 2.3.2)
+ activerecord (= 2.3.2)
+ activeresource (= 2.3.2)
+ rake (= 13.0.1)
+ rake (13.0.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rails
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "orders dependencies by version" do
+ update_repo2 do
+ # Capistrano did this (at least until version 2.5.10)
+ # RubyGems 2.2 doesn't allow the specifying of a dependency twice
+ # See https://github.com/rubygems/rubygems/commit/03dbac93a3396a80db258d9bc63500333c25bd2f
+ build_gem "double_deps", "1.0", :skip_validation => true do |s|
+ s.add_dependency "net-ssh", ">= 1.0.0"
+ s.add_dependency "net-ssh"
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+ gem 'double_deps'
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ double_deps (1.0)
+ net-ssh
+ net-ssh (>= 1.0.0)
+ net-ssh (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ double_deps
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add the :require option to the lockfile" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+
+ gem "rack-obama", ">= 1.0", :require => "rack/obama"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add the :group option to the lockfile" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+
+ gem "rack-obama", ">= 1.0", :group => :test
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided in a relative fashion and in Gemfile dir" do
+ build_lib "foo", :path => bundled_app("foo")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "foo" do
+ gem "foo"
+ end
+ G
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: foo
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided in a relative fashion and is above Gemfile dir" do
+ build_lib "foo", :path => bundled_app(File.join("..", "foo"))
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "../foo" do
+ gem "foo"
+ end
+ G
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: ../foo
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided in an absolute fashion but is relative" do
+ build_lib "foo", :path => bundled_app("foo")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path File.expand_path("foo", __dir__) do
+ gem "foo"
+ end
+ G
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: foo
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided for gemspec" do
+ build_lib("foo", :path => tmp.join("foo"))
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec :path => "../foo"
+ G
+
+ expect(lockfile).to eq <<~G
+ PATH
+ remote: ../foo
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "keeps existing platforms in the lockfile" do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+
+ gem "rack"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms("java")}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "persists the spec's specific platform to the lockfile" do
+ build_repo2 do
+ build_gem "platform_specific", "1.0" do |s|
+ s.platform = Gem::Platform.new("universal-java-16")
+ end
+ end
+
+ simulate_platform "universal-java-16"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "platform_specific"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ platform_specific (1.0-universal-java-16)
+
+ PLATFORMS
+ universal-java-16
+
+ DEPENDENCIES
+ platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate gems" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+ gem "rack"
+ gem "activesupport"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ activesupport (2.3.5)
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ activesupport
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate dependencies" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+ gem "rack"
+ gem "rack"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate dependencies with versions" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+ gem "rack", "1.0"
+ gem "rack", "1.0"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate dependencies in different groups" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+ gem "rack", "1.0", :group => :one
+ gem "rack", "1.0", :group => :two
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "raises if two different versions are used" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}/"
+ gem "rack", "1.0"
+ gem "rack", "1.1"
+ G
+
+ expect(bundled_app_lock).not_to exist
+ expect(err).to include "rack (= 1.0) and rack (= 1.1)"
+ end
+
+ it "raises if two different sources are used" do
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}/"
+ gem "rack"
+ gem "rack", :git => "git://hubz.com"
+ G
+
+ expect(bundled_app_lock).not_to exist
+ expect(err).to include "rack (>= 0) should come from an unspecified source and git://hubz.com"
+ end
+
+ it "works correctly with multiple version dependencies" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+ gem "rack", "> 0.9", "< 1.0"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (> 0.9, < 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "captures the Ruby version in the lockfile" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}/"
+ ruby '#{Gem.ruby_version}'
+ gem "rack", "> 0.9", "< 1.0"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (> 0.9, < 1.0)
+
+ RUBY VERSION
+ #{Bundler::RubyVersion.system}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "raises a helpful error message when the lockfile is missing deps" do
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack_middleware
+ L
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack_middleware"
+ G
+
+ expect(err).to include("Downloading rack_middleware-1.0 revealed dependencies not in the API or the lockfile (#{Gem::Dependency.new("rack", "= 0.9.1")}).").
+ and include("Running `bundle update rack_middleware` should fix the problem.")
+ end
+
+ it "regenerates a lockfile with no specs" do
+ build_repo4 do
+ build_gem "indirect_dependency", "1.2.3" do |s|
+ s.metadata["funding_uri"] = "https://example.com/donate"
+ end
+
+ build_gem "direct_dependency", "4.5.6" do |s|
+ s.add_dependency "indirect_dependency", ">= 0"
+ end
+ end
+
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ direct_dependency
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+
+ gem "direct_dependency"
+ G
+
+ expect(lockfile).to eq <<~G
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ direct_dependency (4.5.6)
+ indirect_dependency
+ indirect_dependency (1.2.3)
+
+ PLATFORMS
+ #{formatted_lockfile_platforms(*["ruby", generic_local_platform].uniq)}
+
+ DEPENDENCIES
+ direct_dependency
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ shared_examples_for "a lockfile missing dependent specs" do
+ it "auto-heals" do
+ build_repo4 do
+ build_gem "minitest-bisect", "1.6.0" do |s|
+ s.add_dependency "path_expander", "~> 1.1"
+ end
+
+ build_gem "path_expander", "1.1.1"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "minitest-bisect"
+ G
+
+ # Corrupt lockfile (completely missing path_expander)
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ minitest-bisect (1.6.0)
+
+ PLATFORMS
+ #{platforms}
+
+ DEPENDENCIES
+ minitest-bisect
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ cache_gems "minitest-bisect-1.6.0", "path_expander-1.1.1", :gem_repo => gem_repo4
+ bundle :install
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ minitest-bisect (1.6.0)
+ path_expander (~> 1.1)
+ path_expander (1.1.1)
+
+ PLATFORMS
+ #{platforms}
+
+ DEPENDENCIES
+ minitest-bisect
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "with just specific platform" do
+ let(:platforms) { lockfile_platforms }
+
+ it_behaves_like "a lockfile missing dependent specs"
+ end
+
+ context "with both ruby and specific platform" do
+ let(:platforms) { lockfile_platforms("ruby") }
+
+ it_behaves_like "a lockfile missing dependent specs"
+ end
+
+ it "auto-heals when the lockfile is missing specs" do
+ build_repo4 do
+ build_gem "minitest-bisect", "1.6.0" do |s|
+ s.add_dependency "path_expander", "~> 1.1"
+ end
+
+ build_gem "path_expander", "1.1.1"
+ end
+
+ gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "minitest-bisect"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ minitest-bisect (1.6.0)
+ path_expander (~> 1.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ minitest-bisect
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install --verbose"
+ expect(out).to include("re-resolving dependencies because your lock file is missing \"minitest-bisect\"")
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ minitest-bisect (1.6.0)
+ path_expander (~> 1.1)
+ path_expander (1.1.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ minitest-bisect
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ describe "a line ending" do
+ def set_lockfile_mtime_to_known_value
+ time = Time.local(2000, 1, 1, 0, 0, 0)
+ File.utime(time, time, bundled_app_lock)
+ end
+ before(:each) do
+ build_repo2
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "rack"
+ G
+ set_lockfile_mtime_to_known_value
+ end
+
+ it "generates Gemfile.lock with \\n line endings" do
+ expect(File.read(bundled_app_lock)).not_to match("\r\n")
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ context "during updates" do
+ it "preserves Gemfile.lock \\n line endings" do
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ expect { bundle "update", :all => true }.to change { File.mtime(bundled_app_lock) }
+ expect(File.read(bundled_app_lock)).not_to match("\r\n")
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+
+ it "preserves Gemfile.lock \\n\\r line endings" do
+ skip "needs to be adapted" if Gem.win_platform?
+
+ update_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ win_lock = File.read(bundled_app_lock).gsub(/\n/, "\r\n")
+ File.open(bundled_app_lock, "wb") {|f| f.puts(win_lock) }
+ set_lockfile_mtime_to_known_value
+
+ expect { bundle "update", :all => true }.to change { File.mtime(bundled_app_lock) }
+ expect(File.read(bundled_app_lock)).to match("\r\n")
+
+ simulate_bundler_version_when_missing_prerelease_default_gem_activation do
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+ end
+ end
+
+ context "when nothing changes" do
+ it "preserves Gemfile.lock \\n line endings" do
+ expect do
+ ruby <<-RUBY
+ require 'bundler'
+ Bundler.setup
+ RUBY
+ end.not_to change { File.mtime(bundled_app_lock) }
+ end
+
+ it "preserves Gemfile.lock \\n\\r line endings" do
+ win_lock = File.read(bundled_app_lock).gsub(/\n/, "\r\n")
+ File.open(bundled_app_lock, "wb") {|f| f.puts(win_lock) }
+ set_lockfile_mtime_to_known_value
+
+ expect do
+ ruby <<-RUBY
+ require '#{entrypoint}'
+ Bundler.setup
+ RUBY
+ end.not_to change { File.mtime(bundled_app_lock) }
+ end
+ end
+ end
+
+ it "refuses to install if Gemfile.lock contains conflict markers" do
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}//
+ specs:
+ <<<<<<<
+ rack (1.0.0)
+ =======
+ rack (1.0.1)
+ >>>>>>>
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ install_gemfile <<-G, :raise_on_error => false
+ source "#{file_uri_for(gem_repo2)}/"
+ gem "rack"
+ G
+
+ expect(err).to match(/your Gemfile.lock contains merge conflicts/i)
+ expect(err).to match(/git checkout HEAD -- Gemfile.lock/i)
+ end
+
+ private
+
+ def prerelease?(version)
+ Gem::Version.new(version).prerelease?
+ end
+
+ def previous_major(version)
+ version.split(".").map.with_index {|v, i| i == 0 ? v.to_i - 1 : v }.join(".")
+ end
+
+ def bump_minor(version)
+ bump(version, 1)
+ end
+
+ def bump(version, segment)
+ version.split(".").map.with_index {|v, i| i == segment ? v.to_i + 1 : v }.join(".")
+ end
+end
diff --git a/spec/bundler/other/cli_dispatch_spec.rb b/spec/bundler/other/cli_dispatch_spec.rb
new file mode 100644
index 0000000000..2d6080296f
--- /dev/null
+++ b/spec/bundler/other/cli_dispatch_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle command names" do
+ it "work when given fully" do
+ bundle "install", :raise_on_error => false
+ expect(err).to eq("Could not locate Gemfile")
+ expect(last_command.stdboth).not_to include("Ambiguous command")
+ end
+
+ it "work when not ambiguous" do
+ bundle "ins", :raise_on_error => false
+ expect(err).to eq("Could not locate Gemfile")
+ expect(last_command.stdboth).not_to include("Ambiguous command")
+ end
+
+ it "print a friendly error when ambiguous" do
+ bundle "in", :raise_on_error => false
+ expect(err).to eq("Ambiguous command in matches [info, init, inject, install]")
+ end
+end
diff --git a/spec/bundler/other/ext_spec.rb b/spec/bundler/other/ext_spec.rb
new file mode 100644
index 0000000000..9b829a2b36
--- /dev/null
+++ b/spec/bundler/other/ext_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+RSpec.describe "Gem::Specification#match_platform" do
+ it "does not match platforms other than the gem platform" do
+ darwin = gem "lol", "1.0", "platform_specific-1.0-x86-darwin-10"
+ expect(darwin.match_platform(pl("java"))).to eq(false)
+ end
+
+ context "when platform is a string" do
+ it "matches when platform is a string" do
+ lazy_spec = Bundler::LazySpecification.new("lol", "1.0", "universal-mingw32")
+ expect(lazy_spec.match_platform(pl("x86-mingw32"))).to eq(true)
+ expect(lazy_spec.match_platform(pl("x64-mingw32"))).to eq(true)
+ end
+ end
+end
+
+RSpec.describe "Bundler::GemHelpers#generic" do
+ include Bundler::GemHelpers
+
+ it "converts non-windows platforms into ruby" do
+ expect(generic(pl("x86-darwin-10"))).to eq(pl("ruby"))
+ expect(generic(pl("ruby"))).to eq(pl("ruby"))
+ end
+
+ it "converts java platform variants into java" do
+ expect(generic(pl("universal-java-17"))).to eq(pl("java"))
+ expect(generic(pl("java"))).to eq(pl("java"))
+ end
+
+ it "converts mswin platform variants into x86-mswin32" do
+ expect(generic(pl("mswin32"))).to eq(pl("x86-mswin32"))
+ expect(generic(pl("i386-mswin32"))).to eq(pl("x86-mswin32"))
+ expect(generic(pl("x86-mswin32"))).to eq(pl("x86-mswin32"))
+ end
+
+ it "converts 32-bit mingw platform variants into x86-mingw32" do
+ expect(generic(pl("mingw32"))).to eq(pl("x86-mingw32"))
+ expect(generic(pl("i386-mingw32"))).to eq(pl("x86-mingw32"))
+ expect(generic(pl("x86-mingw32"))).to eq(pl("x86-mingw32"))
+ end
+
+ it "converts 64-bit mingw platform variants into x64-mingw32" do
+ expect(generic(pl("x64-mingw32"))).to eq(pl("x64-mingw32"))
+ expect(generic(pl("x86_64-mingw32"))).to eq(pl("x64-mingw32"))
+ end
+
+ it "converts 64-bit mingw UCRT platform variants into x64-mingw-ucrt" do
+ expect(generic(pl("x64-mingw-ucrt"))).to eq(pl("x64-mingw-ucrt"))
+ end
+end
+
+RSpec.describe "Gem::SourceIndex#refresh!" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ it "does not explode when called" do
+ run "Gem.source_index.refresh!", :raise_on_error => false
+ run "Gem::SourceIndex.new([]).refresh!", :raise_on_error => false
+ end
+end
diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb
new file mode 100644
index 0000000000..7b7ceb4586
--- /dev/null
+++ b/spec/bundler/other/major_deprecation_spec.rb
@@ -0,0 +1,649 @@
+# frozen_string_literal: true
+
+RSpec.describe "major deprecations" do
+ let(:warnings) { err }
+
+ describe "Bundler" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ describe ".clean_env" do
+ before do
+ source = "Bundler.clean_env"
+ bundle "exec ruby -e #{source.dump}"
+ end
+
+ it "is deprecated in favor of .unbundled_env", :bundler => "< 3" do
+ expect(deprecations).to include \
+ "`Bundler.clean_env` has been deprecated in favor of `Bundler.unbundled_env`. " \
+ "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env` " \
+ "(called at -e:1)"
+ end
+
+ pending "is removed and shows a helpful error message about it", :bundler => "3"
+ end
+
+ describe ".with_clean_env" do
+ before do
+ source = "Bundler.with_clean_env {}"
+ bundle "exec ruby -e #{source.dump}"
+ end
+
+ it "is deprecated in favor of .unbundled_env", :bundler => "< 3" do
+ expect(deprecations).to include(
+ "`Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. " \
+ "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` " \
+ "(called at -e:1)"
+ )
+ end
+
+ pending "is removed and shows a helpful error message about it", :bundler => "3"
+ end
+
+ describe ".clean_system" do
+ before do
+ source = "Bundler.clean_system('ls')"
+ bundle "exec ruby -e #{source.dump}"
+ end
+
+ it "is deprecated in favor of .unbundled_system", :bundler => "< 3" do
+ expect(deprecations).to include(
+ "`Bundler.clean_system` has been deprecated in favor of `Bundler.unbundled_system`. " \
+ "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system` " \
+ "(called at -e:1)"
+ )
+ end
+
+ pending "is removed and shows a helpful error message about it", :bundler => "3"
+ end
+
+ describe ".clean_exec" do
+ before do
+ source = "Bundler.clean_exec('ls')"
+ bundle "exec ruby -e #{source.dump}"
+ end
+
+ it "is deprecated in favor of .unbundled_exec", :bundler => "< 3" do
+ expect(deprecations).to include(
+ "`Bundler.clean_exec` has been deprecated in favor of `Bundler.unbundled_exec`. " \
+ "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec` " \
+ "(called at -e:1)"
+ )
+ end
+
+ pending "is removed and shows a helpful error message about it", :bundler => "3"
+ end
+
+ describe ".environment" do
+ before do
+ source = "Bundler.environment"
+ bundle "exec ruby -e #{source.dump}"
+ end
+
+ it "is deprecated in favor of .load", :bundler => "< 3" do
+ expect(deprecations).to include "Bundler.environment has been removed in favor of Bundler.load (called at -e:1)"
+ end
+
+ pending "is removed and shows a helpful error message about it", :bundler => "3"
+ end
+ end
+
+ describe "bundle exec --no-keep-file-descriptors" do
+ before do
+ bundle "exec --no-keep-file-descriptors -e 1", :raise_on_error => false
+ end
+
+ it "is deprecated", :bundler => "< 3" do
+ expect(deprecations).to include "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to"
+ end
+
+ pending "is removed and shows a helpful error message about it", :bundler => "3"
+ end
+
+ describe "bundle update --quiet" do
+ it "does not print any deprecations" do
+ bundle :update, :quiet => true, :raise_on_error => false
+ expect(deprecations).to be_empty
+ end
+ end
+
+ context "bundle check --path" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "check --path vendor/bundle", :raise_on_error => false
+ end
+
+ it "should print a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include(
+ "The `--path` flag is deprecated because it relies on being " \
+ "remembered across bundler invocations, which bundler will no " \
+ "longer do in future versions. Instead please use `bundle config set --local " \
+ "path 'vendor/bundle'`, and stop using this flag"
+ )
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ context "bundle check --path=" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "check --path=vendor/bundle", :raise_on_error => false
+ end
+
+ it "should print a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include(
+ "The `--path` flag is deprecated because it relies on being " \
+ "remembered across bundler invocations, which bundler will no " \
+ "longer do in future versions. Instead please use `bundle config set --local " \
+ "path 'vendor/bundle'`, and stop using this flag"
+ )
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ context "bundle cache --all" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "cache --all", :raise_on_error => false
+ end
+
+ it "should print a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include(
+ "The `--all` flag is deprecated because it relies on being " \
+ "remembered across bundler invocations, which bundler will no " \
+ "longer do in future versions. Instead please use `bundle config set " \
+ "cache_all true`, and stop using this flag"
+ )
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ context "bundle cache --path" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle "cache --path foo", :raise_on_error => false
+ end
+
+ it "should print a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include(
+ "The `--path` flag is deprecated because its semantics are unclear. " \
+ "Use `bundle config cache_path` to configure the path of your cache of gems, " \
+ "and `bundle config path` to configure the path where your gems are installed, " \
+ "and stop using this flag"
+ )
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ describe "bundle config" do
+ describe "old list interface" do
+ before do
+ bundle "config"
+ end
+
+ it "warns", :bundler => "3" do
+ expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config list` instead.")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ describe "old get interface" do
+ before do
+ bundle "config waka"
+ end
+
+ it "warns", :bundler => "3" do
+ expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config get waka` instead.")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ describe "old set interface" do
+ before do
+ bundle "config waka wakapun"
+ end
+
+ it "warns", :bundler => "3" do
+ expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set waka wakapun` instead.")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ describe "old set interface with --local" do
+ before do
+ bundle "config --local waka wakapun"
+ end
+
+ it "warns", :bundler => "3" do
+ expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set --local waka wakapun` instead.")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ describe "old set interface with --global" do
+ before do
+ bundle "config --global waka wakapun"
+ end
+
+ it "warns", :bundler => "3" do
+ expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set --global waka wakapun` instead.")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ describe "old unset interface" do
+ before do
+ bundle "config --delete waka"
+ end
+
+ it "warns", :bundler => "3" do
+ expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset waka` instead.")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ describe "old unset interface with --local" do
+ before do
+ bundle "config --delete --local waka"
+ end
+
+ it "warns", :bundler => "3" do
+ expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset --local waka` instead.")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ describe "old unset interface with --global" do
+ before do
+ bundle "config --delete --global waka"
+ end
+
+ it "warns", :bundler => "3" do
+ expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset --global waka` instead.")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+ end
+
+ describe "bundle update" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ it "warns when no options are given", :bundler => "3" do
+ bundle "update"
+ expect(deprecations).to include("Pass --all to `bundle update` to update everything")
+ end
+
+ pending "fails with a helpful error when no options are given", :bundler => "3"
+
+ it "does not warn when --all is passed" do
+ bundle "update --all"
+ expect(deprecations).to be_empty
+ end
+ end
+
+ describe "bundle install --binstubs" do
+ before do
+ install_gemfile <<-G, :binstubs => true
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ it "should output a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include("The --binstubs option will be removed in favor of `bundle binstubs --all`")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ context "bundle install with both gems.rb and Gemfile present" do
+ it "should not warn about gems.rb" do
+ create_file "gems.rb", <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ bundle :install
+ expect(deprecations).to be_empty
+ end
+
+ it "should print a proper warning, and use gems.rb" do
+ create_file "gems.rb", "source \"#{file_uri_for(gem_repo1)}\""
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ expect(warnings).to include(
+ "Multiple gemfiles (gems.rb and Gemfile) detected. Make sure you remove Gemfile and Gemfile.lock since bundler is ignoring them in favor of gems.rb and gems.locked."
+ )
+
+ expect(the_bundle).not_to include_gem "rack 1.0"
+ end
+ end
+
+ context "bundle install with flags" do
+ before do
+ bundle "config set --local path vendor/bundle"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ {
+ "clean" => ["clean", true],
+ "deployment" => ["deployment", true],
+ "frozen" => ["frozen", true],
+ "no-deployment" => ["deployment", false],
+ "no-prune" => ["no_prune", true],
+ "path" => ["path", "vendor/bundle"],
+ "shebang" => ["shebang", "ruby27"],
+ "system" => ["system", true],
+ "without" => ["without", "development"],
+ "with" => ["with", "development"],
+ }.each do |name, expectations|
+ option_name, value = *expectations
+ flag_name = "--#{name}"
+
+ context "with the #{flag_name} flag" do
+ before do
+ bundle "install" # to create a lockfile, which deployment or frozen need
+ bundle "install #{flag_name} #{value}"
+ end
+
+ it "should print a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include(
+ "The `#{flag_name}` flag is deprecated because it relies on " \
+ "being remembered across bundler invocations, which bundler " \
+ "will no longer do in future versions. Instead please use " \
+ "`bundle config set --local #{option_name} '#{value}'`, and stop using this flag"
+ )
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+ end
+ end
+
+ context "bundle install with multiple sources" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo3)}"
+ source "#{file_uri_for(gem_repo1)}"
+ G
+ end
+
+ it "shows a deprecation", :bundler => "< 3" do
+ expect(deprecations).to include(
+ "Your Gemfile contains multiple global sources. " \
+ "Using `source` more than once without a block is a security risk, and " \
+ "may result in installing unexpected gems. To resolve this warning, use " \
+ "a block to indicate which gems should come from the secondary source."
+ )
+ end
+
+ it "doesn't show lockfile deprecations if there's a lockfile", :bundler => "< 3" do
+ bundle "install"
+
+ expect(deprecations).to include(
+ "Your Gemfile contains multiple global sources. " \
+ "Using `source` more than once without a block is a security risk, and " \
+ "may result in installing unexpected gems. To resolve this warning, use " \
+ "a block to indicate which gems should come from the secondary source."
+ )
+ expect(deprecations).not_to include(
+ "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \
+ "Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure."
+ )
+ bundle "config set --local frozen true"
+ bundle "install"
+
+ expect(deprecations).to include(
+ "Your Gemfile contains multiple global sources. " \
+ "Using `source` more than once without a block is a security risk, and " \
+ "may result in installing unexpected gems. To resolve this warning, use " \
+ "a block to indicate which gems should come from the secondary source."
+ )
+ expect(deprecations).not_to include(
+ "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \
+ "Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure."
+ )
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ context "bundle install in frozen mode with a lockfile with a single rubygems section with multiple remotes" do
+ before do
+ build_repo gem_repo3 do
+ build_gem "rack", "0.9.1"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ source "#{file_uri_for(gem_repo3)}" do
+ gem 'rack'
+ end
+ G
+
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ remote: #{file_uri_for(gem_repo3)}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "config set --local frozen true"
+ end
+
+ it "shows a deprecation", :bundler => "< 3" do
+ bundle "install"
+
+ expect(deprecations).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure.")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ context "when Bundler.setup is run in a ruby script" do
+ before do
+ create_file "gems.rb", "source \"#{file_uri_for(gem_repo1)}\""
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :group => :test
+ G
+
+ ruby <<-RUBY
+ require '#{entrypoint}'
+
+ Bundler.setup
+ Bundler.setup
+ RUBY
+ end
+
+ it "should print a single deprecation warning" do
+ expect(warnings).to include(
+ "Multiple gemfiles (gems.rb and Gemfile) detected. Make sure you remove Gemfile and Gemfile.lock since bundler is ignoring them in favor of gems.rb and gems.locked."
+ )
+ end
+ end
+
+ context "when `bundler/deployment` is required in a ruby script" do
+ before do
+ ruby(<<-RUBY, :env => env_for_missing_prerelease_default_gem_activation)
+ require 'bundler/deployment'
+ RUBY
+ end
+
+ it "should print a capistrano deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include("Bundler no longer integrates " \
+ "with Capistrano, but Capistrano provides " \
+ "its own integration with Bundler via the " \
+ "capistrano-bundler gem. Use it instead.")
+ end
+
+ pending "fails with a helpful error", :bundler => "3"
+ end
+
+ context "bundle show" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ context "with --outdated flag" do
+ before do
+ bundle "show --outdated"
+ end
+
+ it "prints a deprecation warning informing about its removal", :bundler => "< 3" do
+ expect(deprecations).to include("the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement")
+ end
+
+ pending "fails with a helpful message", :bundler => "3"
+ end
+ end
+
+ context "bundle remove" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ context "with --install" do
+ it "shows a deprecation warning", :bundler => "< 3" do
+ bundle "remove rack --install"
+
+ expect(err).to include "[DEPRECATED] The `--install` flag has been deprecated. `bundle install` is triggered by default."
+ end
+
+ pending "fails with a helpful message", :bundler => "3"
+ end
+ end
+
+ context "bundle console" do
+ before do
+ bundle "console", :raise_on_error => false
+ end
+
+ it "prints a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include \
+ "bundle console will be replaced by `bin/console` generated by `bundle gem <name>`"
+ end
+
+ pending "fails with a helpful message", :bundler => "3"
+ end
+
+ context "bundle viz", :realworld do
+ before do
+ realworld_system_gems "ruby-graphviz --version 1.2.5"
+ create_file "gems.rb", "source \"#{file_uri_for(gem_repo1)}\""
+ bundle "viz"
+ end
+
+ it "prints a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph"
+ end
+
+ pending "fails with a helpful message", :bundler => "3"
+ end
+
+ describe "deprecating rubocop", :readline do
+ context "bundle gem --rubocop" do
+ before do
+ bundle "gem my_new_gem --rubocop", :raise_on_error => false
+ end
+
+ it "prints a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include \
+ "--rubocop is deprecated, use --linter=rubocop"
+ end
+ end
+
+ context "bundle gem --no-rubocop" do
+ before do
+ bundle "gem my_new_gem --no-rubocop", :raise_on_error => false
+ end
+
+ it "prints a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include \
+ "--no-rubocop is deprecated, use --linter"
+ end
+ end
+
+ context "bundle gem with gem.rubocop set to true" do
+ before do
+ bundle "gem my_new_gem", :env => { "BUNDLE_GEM__RUBOCOP" => "true" }, :raise_on_error => false
+ end
+
+ it "prints a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include \
+ "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead"
+ end
+ end
+
+ context "bundle gem with gem.rubocop set to false" do
+ before do
+ bundle "gem my_new_gem", :env => { "BUNDLE_GEM__RUBOCOP" => "false" }, :raise_on_error => false
+ end
+
+ it "prints a deprecation warning", :bundler => "< 3" do
+ expect(deprecations).to include \
+ "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/plugins/command_spec.rb b/spec/bundler/plugins/command_spec.rb
new file mode 100644
index 0000000000..3a7adf4b48
--- /dev/null
+++ b/spec/bundler/plugins/command_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+RSpec.describe "command plugins" do
+ before do
+ build_repo2 do
+ build_plugin "command-mah" do |s|
+ s.write "plugins.rb", <<-RUBY
+ module Mah
+ class Plugin < Bundler::Plugin::API
+ command "mahcommand" # declares the command
+
+ def exec(command, args)
+ puts "MahHello"
+ end
+ end
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install command-mah --source #{file_uri_for(gem_repo2)}"
+ end
+
+ it "executes without arguments" do
+ expect(out).to include("Installed plugin command-mah")
+
+ bundle "mahcommand"
+ expect(out).to eq("MahHello")
+ end
+
+ it "accepts the arguments" do
+ build_repo2 do
+ build_plugin "the-echoer" do |s|
+ s.write "plugins.rb", <<-RUBY
+ module Resonance
+ class Echoer
+ # Another method to declare the command
+ Bundler::Plugin::API.command "echo", self
+
+ def exec(command, args)
+ puts "You gave me \#{args.join(", ")}"
+ end
+ end
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install the-echoer --source #{file_uri_for(gem_repo2)}"
+ expect(out).to include("Installed plugin the-echoer")
+
+ bundle "echo tacos tofu lasange"
+ expect(out).to eq("You gave me tacos, tofu, lasange")
+ end
+
+ it "raises error on redeclaration of command" do
+ build_repo2 do
+ build_plugin "copycat" do |s|
+ s.write "plugins.rb", <<-RUBY
+ module CopyCat
+ class Cheater < Bundler::Plugin::API
+ command "mahcommand", self
+
+ def exec(command, args)
+ end
+ end
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install copycat --source #{file_uri_for(gem_repo2)}", :raise_on_error => false
+
+ expect(out).not_to include("Installed plugin copycat")
+
+ expect(err).to include("Failed to install plugin `copycat`, due to Bundler::Plugin::Index::CommandConflict (Command(s) `mahcommand` declared by copycat are already registered.)")
+ end
+end
diff --git a/spec/bundler/plugins/hook_spec.rb b/spec/bundler/plugins/hook_spec.rb
new file mode 100644
index 0000000000..72feb14d84
--- /dev/null
+++ b/spec/bundler/plugins/hook_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+RSpec.describe "hook plugins" do
+ context "before-install-all hook" do
+ before do
+ build_repo2 do
+ build_plugin "before-install-all-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_INSTALL_ALL do |deps|
+ puts "gems to be installed \#{deps.map(&:name).join(", ")}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install before-install-all-plugin --source #{file_uri_for(gem_repo2)}"
+ end
+
+ it "runs before all rubygems are installed" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rake"
+ gem "rack"
+ G
+
+ expect(out).to include "gems to be installed rake, rack"
+ end
+ end
+
+ context "before-install hook" do
+ before do
+ build_repo2 do
+ build_plugin "before-install-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_INSTALL do |spec_install|
+ puts "installing gem \#{spec_install.name}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install before-install-plugin --source #{file_uri_for(gem_repo2)}"
+ end
+
+ it "runs before each rubygem is installed" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rake"
+ gem "rack"
+ G
+
+ expect(out).to include "installing gem rake"
+ expect(out).to include "installing gem rack"
+ end
+ end
+
+ context "after-install-all hook" do
+ before do
+ build_repo2 do
+ build_plugin "after-install-all-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_INSTALL_ALL do |deps|
+ puts "installed gems \#{deps.map(&:name).join(", ")}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install after-install-all-plugin --source #{file_uri_for(gem_repo2)}"
+ end
+
+ it "runs after each rubygem is installed" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rake"
+ gem "rack"
+ G
+
+ expect(out).to include "installed gems rake, rack"
+ end
+ end
+
+ context "after-install hook" do
+ before do
+ build_repo2 do
+ build_plugin "after-install-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_INSTALL do |spec_install|
+ puts "installed gem \#{spec_install.name} : \#{spec_install.state}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install after-install-plugin --source #{file_uri_for(gem_repo2)}"
+ end
+
+ it "runs after each rubygem is installed" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rake"
+ gem "rack"
+ G
+
+ expect(out).to include "installed gem rake : installed"
+ expect(out).to include "installed gem rack : installed"
+ end
+ end
+end
diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb
new file mode 100644
index 0000000000..aec131f2ee
--- /dev/null
+++ b/spec/bundler/plugins/install_spec.rb
@@ -0,0 +1,381 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundler plugin install" do
+ before do
+ build_repo2 do
+ build_plugin "foo"
+ build_plugin "kung-foo"
+ end
+ end
+
+ it "shows proper message when gem in not found in the source" do
+ bundle "plugin install no-foo --source #{file_uri_for(gem_repo1)}", :raise_on_error => false
+
+ expect(err).to include("Could not find")
+ plugin_should_not_be_installed("no-foo")
+ end
+
+ it "installs from rubygems source" do
+ bundle "plugin install foo --source #{file_uri_for(gem_repo2)}"
+
+ expect(out).to include("Installed plugin foo")
+ plugin_should_be_installed("foo")
+ end
+
+ it "installs from rubygems source in frozen mode" do
+ bundle "plugin install foo --source #{file_uri_for(gem_repo2)}", :env => { "BUNDLE_DEPLOYMENT" => "true" }
+
+ expect(out).to include("Installed plugin foo")
+ plugin_should_be_installed("foo")
+ end
+
+ it "installs from sources configured as Gem.sources without any flags" do
+ bundle "plugin install foo", :env => { "BUNDLER_SPEC_GEM_SOURCES" => file_uri_for(gem_repo2).to_s }
+
+ expect(out).to include("Installed plugin foo")
+ plugin_should_be_installed("foo")
+ end
+
+ it "shows help when --help flag is given" do
+ bundle "plugin install --help"
+
+ # The help message defined in ../../lib/bundler/man/bundle-plugin.1.ronn will be output.
+ expect(out).to include("You can install, uninstall, and list plugin(s)")
+ end
+
+ context "plugin is already installed" do
+ before do
+ bundle "plugin install foo --source #{file_uri_for(gem_repo2)}"
+ end
+
+ it "doesn't install plugin again" do
+ bundle "plugin install foo --source #{file_uri_for(gem_repo2)}"
+ expect(out).not_to include("Installing plugin foo")
+ expect(out).not_to include("Installed plugin foo")
+ end
+ end
+
+ it "installs multiple plugins" do
+ bundle "plugin install foo kung-foo --source #{file_uri_for(gem_repo2)}"
+
+ expect(out).to include("Installed plugin foo")
+ expect(out).to include("Installed plugin kung-foo")
+
+ plugin_should_be_installed("foo", "kung-foo")
+ end
+
+ it "uses the same version for multiple plugins" do
+ update_repo2 do
+ build_plugin "foo", "1.1"
+ build_plugin "kung-foo", "1.1"
+ end
+
+ bundle "plugin install foo kung-foo --version '1.0' --source #{file_uri_for(gem_repo2)}"
+
+ expect(out).to include("Installing foo 1.0")
+ expect(out).to include("Installing kung-foo 1.0")
+ plugin_should_be_installed("foo", "kung-foo")
+ end
+
+ it "installs the latest version if not installed" do
+ update_repo2 do
+ build_plugin "foo", "1.1"
+ end
+
+ bundle "plugin install foo --version 1.0 --source #{file_uri_for(gem_repo2)} --verbose"
+ expect(out).to include("Installing foo 1.0")
+
+ bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --verbose"
+ expect(out).to include("Installing foo 1.1")
+
+ bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --verbose"
+ expect(out).to include("Using foo 1.1")
+ end
+
+ it "installs when --branch specified" do
+ bundle "plugin install foo --branch main --source #{file_uri_for(gem_repo2)}"
+
+ expect(out).to include("Installed plugin foo")
+ end
+
+ it "installs when --ref specified" do
+ bundle "plugin install foo --ref v1.2.3 --source #{file_uri_for(gem_repo2)}"
+
+ expect(out).to include("Installed plugin foo")
+ end
+
+ it "raises error when both --branch and --ref options are specified" do
+ bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --branch main --ref v1.2.3", :raise_on_error => false
+
+ expect(out).not_to include("Installed plugin foo")
+
+ expect(err).to include("You cannot specify `--branch` and `--ref` at the same time.")
+ end
+
+ it "works with different load paths" do
+ build_repo2 do
+ build_plugin "testing" do |s|
+ s.write "plugins.rb", <<-RUBY
+ require "fubar"
+ class Test < Bundler::Plugin::API
+ command "check2"
+
+ def exec(command, args)
+ puts "mate"
+ end
+ end
+ RUBY
+ s.require_paths = %w[lib src]
+ s.write("src/fubar.rb")
+ end
+ end
+ bundle "plugin install testing --source #{file_uri_for(gem_repo2)}"
+
+ bundle "check2", "no-color" => false
+ expect(out).to eq("mate")
+ end
+
+ context "malformatted plugin" do
+ it "fails when plugins.rb is missing" do
+ update_repo2 do
+ build_plugin "foo", "1.1"
+ build_plugin "kung-foo", "1.1"
+ end
+
+ bundle "plugin install foo kung-foo --version '1.0' --source #{file_uri_for(gem_repo2)}"
+
+ expect(out).to include("Installing foo 1.0")
+ expect(out).to include("Installing kung-foo 1.0")
+ plugin_should_be_installed("foo", "kung-foo")
+
+ build_repo2 do
+ build_gem "charlie"
+ end
+
+ bundle "plugin install charlie --source #{file_uri_for(gem_repo2)}", :raise_on_error => false
+
+ expect(err).to include("Failed to install plugin `charlie`, due to Bundler::Plugin::MalformattedPlugin (plugins.rb was not found in the plugin.)")
+
+ expect(global_plugin_gem("charlie-1.0")).not_to be_directory
+
+ plugin_should_be_installed("foo", "kung-foo")
+ plugin_should_not_be_installed("charlie")
+ end
+
+ it "fails when plugins.rb throws exception on load" do
+ build_repo2 do
+ build_plugin "chaplin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ raise "I got you man"
+ RUBY
+ end
+ end
+
+ bundle "plugin install chaplin --source #{file_uri_for(gem_repo2)}", :raise_on_error => false
+
+ expect(global_plugin_gem("chaplin-1.0")).not_to be_directory
+
+ plugin_should_not_be_installed("chaplin")
+ end
+ end
+
+ context "git plugins" do
+ it "installs form a git source" do
+ build_git "foo" do |s|
+ s.write "plugins.rb"
+ end
+
+ bundle "plugin install foo --git #{file_uri_for(lib_path("foo-1.0"))}"
+
+ expect(out).to include("Installed plugin foo")
+ plugin_should_be_installed("foo")
+ end
+
+ it "installs form a local git source" do
+ build_git "foo" do |s|
+ s.write "plugins.rb"
+ end
+
+ bundle "plugin install foo --local_git #{lib_path("foo-1.0")}"
+
+ expect(out).to include("Installed plugin foo")
+ plugin_should_be_installed("foo")
+ end
+
+ it "raises an error when both git and local git sources are specified" do
+ bundle "plugin install foo --local_git /phony/path/project --git git@gitphony.com:/repo/project", :raise_on_error => false
+
+ expect(exitstatus).not_to eq(0)
+ expect(err).to eq("Remote and local plugin git sources can't be both specified")
+ end
+ end
+
+ context "Gemfile eval" do
+ before do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ end
+
+ it "installs plugins listed in gemfile" do
+ gemfile <<-G
+ source '#{file_uri_for(gem_repo2)}'
+ plugin 'foo'
+ gem 'rack', "1.0.0"
+ G
+
+ bundle "install"
+
+ expect(out).to include("Installed plugin foo")
+
+ expect(out).to include("Bundle complete!")
+
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ plugin_should_be_installed("foo")
+ end
+
+ it "accepts plugin version" do
+ update_repo2 do
+ build_plugin "foo", "1.1.0"
+ end
+
+ gemfile <<-G
+ source '#{file_uri_for(gem_repo2)}'
+ plugin 'foo', "1.0"
+ G
+
+ bundle "install"
+
+ expect(out).to include("Installing foo 1.0")
+
+ plugin_should_be_installed("foo")
+
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "accepts git sources" do
+ build_git "ga-plugin" do |s|
+ s.write "plugins.rb"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ plugin 'ga-plugin', :git => "#{lib_path("ga-plugin-1.0")}"
+ G
+
+ expect(out).to include("Installed plugin ga-plugin")
+ plugin_should_be_installed("ga-plugin")
+ end
+
+ it "accepts path sources" do
+ build_lib "ga-plugin" do |s|
+ s.write "plugins.rb"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ plugin 'ga-plugin', :path => "#{lib_path("ga-plugin-1.0")}"
+ G
+
+ expect(out).to include("Installed plugin ga-plugin")
+ plugin_should_be_installed("ga-plugin")
+ end
+
+ context "in deployment mode" do
+ it "installs plugins" do
+ install_gemfile <<-G
+ source '#{file_uri_for(gem_repo2)}'
+ gem 'rack', "1.0.0"
+ G
+
+ bundle "config set --local deployment true"
+ install_gemfile <<-G
+ source '#{file_uri_for(gem_repo2)}'
+ plugin 'foo'
+ gem 'rack', "1.0.0"
+ G
+
+ expect(out).to include("Installed plugin foo")
+
+ expect(out).to include("Bundle complete!")
+
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ plugin_should_be_installed("foo")
+ end
+ end
+ end
+
+ context "inline gemfiles" do
+ it "installs the listed plugins" do
+ code = <<-RUBY
+ require "bundler/inline"
+
+ gemfile do
+ source '#{file_uri_for(gem_repo2)}'
+ plugin 'foo'
+ end
+ RUBY
+
+ ruby code, :env => { "BUNDLER_VERSION" => Bundler::VERSION }
+ expect(local_plugin_gem("foo-1.0", "plugins.rb")).to exist
+ end
+ end
+
+ describe "local plugin" do
+ it "is installed when inside an app" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ gemfile ""
+ bundle "plugin install foo --source #{file_uri_for(gem_repo2)}"
+
+ plugin_should_be_installed("foo")
+ expect(local_plugin_gem("foo-1.0")).to be_directory
+ end
+
+ context "conflict with global plugin" do
+ before do
+ update_repo2 do
+ build_plugin "fubar" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class Fubar < Bundler::Plugin::API
+ command "shout"
+
+ def exec(command, args)
+ puts "local_one"
+ end
+ end
+ RUBY
+ end
+ end
+
+ # inside the app
+ gemfile "source '#{file_uri_for(gem_repo2)}'\nplugin 'fubar'"
+ bundle "install"
+
+ update_repo2 do
+ build_plugin "fubar", "1.1" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class Fubar < Bundler::Plugin::API
+ command "shout"
+
+ def exec(command, args)
+ puts "global_one"
+ end
+ end
+ RUBY
+ end
+ end
+
+ # outside the app
+ bundle "plugin install fubar --source #{file_uri_for(gem_repo2)}", :dir => tmp
+ end
+
+ it "inside the app takes precedence over global plugin" do
+ bundle "shout"
+ expect(out).to eq("local_one")
+ end
+
+ it "outside the app global plugin is used" do
+ bundle "shout", :dir => tmp
+ expect(out).to eq("global_one")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/plugins/list_spec.rb b/spec/bundler/plugins/list_spec.rb
new file mode 100644
index 0000000000..4a686415ad
--- /dev/null
+++ b/spec/bundler/plugins/list_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundler plugin list" do
+ before do
+ build_repo2 do
+ build_plugin "foo" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class Foo < Bundler::Plugin::API
+ command "shout"
+
+ def exec(command, args)
+ puts "Foo shout"
+ end
+ end
+ RUBY
+ end
+ build_plugin "bar" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class Bar < Bundler::Plugin::API
+ command "scream"
+
+ def exec(command, args)
+ puts "Bar scream"
+ end
+ end
+ RUBY
+ end
+ end
+ end
+
+ context "no plugins installed" do
+ it "shows proper no plugins installed message" do
+ bundle "plugin list"
+
+ expect(out).to include("No plugins installed")
+ end
+ end
+
+ context "single plugin installed" do
+ it "shows plugin name with commands list" do
+ bundle "plugin install foo --source #{file_uri_for(gem_repo2)}"
+ plugin_should_be_installed("foo")
+ bundle "plugin list"
+
+ expected_output = "foo\n-----\n shout"
+ expect(out).to include(expected_output)
+ end
+ end
+
+ context "multiple plugins installed" do
+ it "shows plugin names with commands list" do
+ bundle "plugin install foo bar --source #{file_uri_for(gem_repo2)}"
+ plugin_should_be_installed("foo", "bar")
+ bundle "plugin list"
+
+ expected_output = "foo\n-----\n shout\n\nbar\n-----\n scream"
+ expect(out).to include(expected_output)
+ end
+ end
+end
diff --git a/spec/bundler/plugins/source/example_spec.rb b/spec/bundler/plugins/source/example_spec.rb
new file mode 100644
index 0000000000..9d153b6063
--- /dev/null
+++ b/spec/bundler/plugins/source/example_spec.rb
@@ -0,0 +1,452 @@
+# frozen_string_literal: true
+
+RSpec.describe "real source plugins" do
+ context "with a minimal source plugin" do
+ before do
+ build_repo2 do
+ build_plugin "bundler-source-mpath" do |s|
+ s.write "plugins.rb", <<-RUBY
+ require "bundler-source-mpath"
+
+ class MPath < Bundler::Plugin::API
+ source "mpath"
+
+ attr_reader :path
+
+ def initialize(opts)
+ super
+
+ @path = Pathname.new options["uri"]
+ end
+
+ def fetch_gemspec_files
+ @spec_files ||= begin
+ glob = "{,*,*/*}.gemspec"
+ if installed?
+ search_path = install_path
+ else
+ search_path = path
+ end
+ Dir["\#{search_path.to_s}/\#{glob}"]
+ end
+ end
+
+ def install(spec, opts)
+ mkdir_p(install_path.parent)
+ require 'fileutils'
+ FileUtils.cp_r(path, install_path)
+
+ spec_path = install_path.join("\#{spec.full_name}.gemspec")
+ spec_path.open("wb") {|f| f.write spec.to_ruby }
+ spec.loaded_from = spec_path.to_s
+
+ post_install(spec)
+
+ nil
+ end
+ end
+ RUBY
+ end # build_plugin
+ end
+
+ build_lib "a-path-gem"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}" # plugin source
+ source "#{lib_path("a-path-gem-1.0")}", :type => :mpath do
+ gem "a-path-gem"
+ end
+ G
+ end
+
+ it "installs" do
+ bundle "install"
+
+ expect(out).to include("Bundle complete!")
+
+ expect(the_bundle).to include_gems("a-path-gem 1.0")
+ end
+
+ it "writes to lock file" do
+ bundle "install"
+
+ expect(lockfile).to eq <<~G
+ PLUGIN SOURCE
+ remote: #{lib_path("a-path-gem-1.0")}
+ type: mpath
+ specs:
+ a-path-gem (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ a-path-gem!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "provides correct #full_gem_path" do
+ bundle "install"
+ run <<-RUBY
+ puts Bundler.rubygems.find_name('a-path-gem').first.full_gem_path
+ RUBY
+ expect(out).to eq(bundle("info a-path-gem --path"))
+ end
+
+ it "installs the gem executables" do
+ build_lib "gem_with_bin" do |s|
+ s.executables = ["foo"]
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}" # plugin source
+ source "#{lib_path("gem_with_bin-1.0")}", :type => :mpath do
+ gem "gem_with_bin"
+ end
+ G
+
+ bundle "exec foo"
+ expect(out).to eq("1.0")
+ end
+
+ describe "bundle cache/package" do
+ let(:uri_hash) { Digest(:SHA1).hexdigest(lib_path("a-path-gem-1.0").to_s) }
+ it "copies repository to vendor cache and uses it" do
+ bundle "install"
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
+ expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}/.git")).not_to exist
+ expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}/.bundlecache")).to be_file
+
+ FileUtils.rm_rf lib_path("a-path-gem-1.0")
+ expect(the_bundle).to include_gems("a-path-gem 1.0")
+ end
+
+ it "copies repository to vendor cache and uses it even when installed with `path` configured" do
+ bundle "config set --local path vendor/bundle"
+ bundle :install
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
+
+ FileUtils.rm_rf lib_path("a-path-gem-1.0")
+ expect(the_bundle).to include_gems("a-path-gem 1.0")
+ end
+
+ it "bundler package copies repository to vendor cache" do
+ bundle "config set --local path vendor/bundle"
+ bundle :install
+ bundle "config set cache_all true"
+ bundle :cache
+
+ expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
+
+ FileUtils.rm_rf lib_path("a-path-gem-1.0")
+ expect(the_bundle).to include_gems("a-path-gem 1.0")
+ end
+ end
+
+ context "with lockfile" do
+ before do
+ lockfile <<-G
+ PLUGIN SOURCE
+ remote: #{lib_path("a-path-gem-1.0")}
+ type: mpath
+ specs:
+ a-path-gem (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ a-path-gem!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "installs" do
+ bundle "install"
+
+ expect(the_bundle).to include_gems("a-path-gem 1.0")
+ end
+ end
+ end
+
+ context "with a more elaborate source plugin" do
+ before do
+ build_repo2 do
+ build_plugin "bundler-source-gitp" do |s|
+ s.write "plugins.rb", <<-RUBY
+ require "open3"
+
+ class SPlugin < Bundler::Plugin::API
+ source "gitp"
+
+ attr_reader :ref
+
+ def initialize(opts)
+ super
+
+ @ref = options["ref"] || options["branch"] || options["tag"] || "main"
+ @unlocked = false
+ end
+
+ def eql?(other)
+ other.is_a?(self.class) && uri == other.uri && ref == other.ref
+ end
+
+ alias_method :==, :eql?
+
+ def fetch_gemspec_files
+ @spec_files ||= begin
+ glob = "{,*,*/*}.gemspec"
+ if !cached?
+ cache_repo
+ end
+
+ if installed? && !@unlocked
+ path = install_path
+ else
+ path = cache_path
+ end
+
+ Dir["\#{path}/\#{glob}"]
+ end
+ end
+
+ def install(spec, opts)
+ mkdir_p(install_path.dirname)
+ rm_rf(install_path)
+ `git clone --no-checkout --quiet "\#{cache_path}" "\#{install_path}"`
+ Open3.capture2e("git reset --hard \#{revision}", :chdir => install_path)
+
+ spec_path = install_path.join("\#{spec.full_name}.gemspec")
+ spec_path.open("wb") {|f| f.write spec.to_ruby }
+ spec.loaded_from = spec_path.to_s
+
+ post_install(spec)
+
+ nil
+ end
+
+ def options_to_lock
+ opts = {"revision" => revision}
+ opts["ref"] = ref if ref != "main"
+ opts
+ end
+
+ def unlock!
+ @unlocked = true
+ @revision = latest_revision
+ end
+
+ def app_cache_dirname
+ "\#{base_name}-\#{shortref_for_path(revision)}"
+ end
+
+ private
+
+ def cache_path
+ @cache_path ||= cache_dir.join("gitp", base_name)
+ end
+
+ def cache_repo
+ `git clone --quiet \#{@options["uri"]} \#{cache_path}`
+ end
+
+ def cached?
+ File.directory?(cache_path)
+ end
+
+ def locked_revision
+ options["revision"]
+ end
+
+ def revision
+ @revision ||= locked_revision || latest_revision
+ end
+
+ def latest_revision
+ if !cached? || @unlocked
+ rm_rf(cache_path)
+ cache_repo
+ end
+
+ output, _status = Open3.capture2e("git rev-parse --verify \#{@ref}", :chdir => cache_path)
+ output.strip
+ end
+
+ def base_name
+ File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)?(//\w*/)?(\w*/)*}, ""), ".git")
+ end
+
+ def shortref_for_path(ref)
+ ref[0..11]
+ end
+
+ def install_path
+ @install_path ||= begin
+ git_scope = "\#{base_name}-\#{shortref_for_path(revision)}"
+
+ gem_install_dir.join(git_scope)
+ end
+ end
+
+ def installed?
+ File.directory?(install_path)
+ end
+ end
+ RUBY
+ end
+ end
+
+ build_git "ma-gitp-gem"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}" # plugin source
+ source "#{file_uri_for(lib_path("ma-gitp-gem-1.0"))}", :type => :gitp do
+ gem "ma-gitp-gem"
+ end
+ G
+ end
+
+ it "handles the source option" do
+ bundle "install"
+ expect(out).to include("Bundle complete!")
+ expect(the_bundle).to include_gems("ma-gitp-gem 1.0")
+ end
+
+ it "writes to lock file" do
+ revision = revision_for(lib_path("ma-gitp-gem-1.0"))
+ bundle "install"
+
+ expect(lockfile).to eq <<~G
+ PLUGIN SOURCE
+ remote: #{file_uri_for(lib_path("ma-gitp-gem-1.0"))}
+ type: gitp
+ revision: #{revision}
+ specs:
+ ma-gitp-gem (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ma-gitp-gem!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ context "with lockfile" do
+ before do
+ revision = revision_for(lib_path("ma-gitp-gem-1.0"))
+ lockfile <<-G
+ PLUGIN SOURCE
+ remote: #{file_uri_for(lib_path("ma-gitp-gem-1.0"))}
+ type: gitp
+ revision: #{revision}
+ specs:
+ ma-gitp-gem (1.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ ma-gitp-gem!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "installs" do
+ bundle "install"
+ expect(the_bundle).to include_gems("ma-gitp-gem 1.0")
+ end
+
+ it "uses the locked ref" do
+ update_git "ma-gitp-gem"
+ bundle "install"
+
+ run <<-RUBY
+ require 'ma/gitp/gem'
+ puts "WIN" unless defined?(MAGITPGEM_PREV_REF)
+ RUBY
+ expect(out).to eq("WIN")
+ end
+
+ it "updates the deps on bundler update" do
+ update_git "ma-gitp-gem"
+ bundle "update ma-gitp-gem"
+
+ run <<-RUBY
+ require 'ma/gitp/gem'
+ puts "WIN" if defined?(MAGITPGEM_PREV_REF)
+ RUBY
+ expect(out).to eq("WIN")
+ end
+
+ it "updates the deps on change in gemfile" do
+ update_git "ma-gitp-gem", "1.1", :path => lib_path("ma-gitp-gem-1.0"), :gemspec => true
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}" # plugin source
+ source "#{file_uri_for(lib_path("ma-gitp-gem-1.0"))}", :type => :gitp do
+ gem "ma-gitp-gem", "1.1"
+ end
+ G
+ bundle "install"
+
+ expect(the_bundle).to include_gems("ma-gitp-gem 1.1")
+ end
+ end
+
+ describe "bundle cache with gitp" do
+ it "copies repository to vendor cache and uses it" do
+ git = build_git "foo"
+ ref = git.ref_for("main", 11)
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}" # plugin source
+ source '#{lib_path("foo-1.0")}', :type => :gitp do
+ gem "foo"
+ end
+ G
+
+ bundle "config set cache_all true"
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.bundlecache")).to be_file
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/plugins/source_spec.rb b/spec/bundler/plugins/source_spec.rb
new file mode 100644
index 0000000000..14643e5c81
--- /dev/null
+++ b/spec/bundler/plugins/source_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundler source plugin" do
+ describe "plugins dsl eval for #source with :type option" do
+ before do
+ update_repo2 do
+ build_plugin "bundler-source-psource" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class OPSource < Bundler::Plugin::API
+ source "psource"
+ end
+ RUBY
+ end
+ end
+ end
+
+ it "installs bundler-source-* gem when no handler for source is present" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ source "#{file_uri_for(lib_path("gitp"))}", :type => :psource do
+ end
+ G
+
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ plugin_should_be_installed("bundler-source-psource")
+ end
+
+ it "enables the plugin to require a lib path" do
+ update_repo2 do
+ build_plugin "bundler-source-psource" do |s|
+ s.write "plugins.rb", <<-RUBY
+ require "bundler-source-psource"
+ class PSource < Bundler::Plugin::API
+ source "psource"
+ end
+ RUBY
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ source "#{file_uri_for(lib_path("gitp"))}", :type => :psource do
+ end
+ G
+
+ expect(out).to include("Bundle complete!")
+ end
+
+ context "with an explicit handler" do
+ before do
+ update_repo2 do
+ build_plugin "another-psource" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class Cheater < Bundler::Plugin::API
+ source "psource"
+ end
+ RUBY
+ end
+ end
+ end
+
+ context "explicit presence in gemfile" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ plugin "another-psource"
+
+ source "#{file_uri_for(lib_path("gitp"))}", :type => :psource do
+ end
+ G
+ end
+
+ it "completes successfully" do
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "installs the explicit one" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ plugin_should_be_installed("another-psource")
+ end
+
+ it "doesn't install the default one" do
+ plugin_should_not_be_installed("bundler-source-psource")
+ end
+ end
+
+ context "explicit default source" do
+ before do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ plugin "bundler-source-psource"
+
+ source "#{file_uri_for(lib_path("gitp"))}", :type => :psource do
+ end
+ G
+ end
+
+ it "completes successfully" do
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "installs the default one" do
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ plugin_should_be_installed("bundler-source-psource")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/plugins/uninstall_spec.rb b/spec/bundler/plugins/uninstall_spec.rb
new file mode 100644
index 0000000000..8180241911
--- /dev/null
+++ b/spec/bundler/plugins/uninstall_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundler plugin uninstall" do
+ before do
+ build_repo2 do
+ build_plugin "foo"
+ build_plugin "kung-foo"
+ end
+ end
+
+ it "shows proper error message when plugins are not specified" do
+ bundle "plugin uninstall"
+ expect(err).to include("No plugins to uninstall")
+ end
+
+ it "uninstalls specified plugins" do
+ bundle "plugin install foo kung-foo --source #{file_uri_for(gem_repo2)}"
+ plugin_should_be_installed("foo")
+ plugin_should_be_installed("kung-foo")
+
+ bundle "plugin uninstall foo"
+ expect(out).to include("Uninstalled plugin foo")
+ plugin_should_not_be_installed("foo")
+ plugin_should_be_installed("kung-foo")
+ end
+
+ it "shows proper message when plugin is not installed" do
+ bundle "plugin uninstall foo"
+ expect(err).to include("Plugin foo is not installed")
+ plugin_should_not_be_installed("foo")
+ end
+
+ describe "with --all" do
+ it "uninstalls all installed plugins" do
+ bundle "plugin install foo kung-foo --source #{file_uri_for(gem_repo2)}"
+ plugin_should_be_installed("foo")
+ plugin_should_be_installed("kung-foo")
+
+ bundle "plugin uninstall --all"
+ plugin_should_not_be_installed("foo")
+ plugin_should_not_be_installed("kung-foo")
+ end
+
+ it "shows proper no plugins installed message when no plugins installed" do
+ bundle "plugin uninstall --all"
+ expect(out).to include("No plugins installed")
+ end
+ end
+end
diff --git a/spec/bundler/quality_es_spec.rb b/spec/bundler/quality_es_spec.rb
new file mode 100644
index 0000000000..0dbd77e451
--- /dev/null
+++ b/spec/bundler/quality_es_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+RSpec.describe "La biblioteca si misma" do
+ def check_for_expendable_words(filename)
+ failing_line_message = []
+ useless_words = %w[
+ básicamente
+ claramente
+ sólo
+ solamente
+ obvio
+ obviamente
+ fácil
+ fácilmente
+ sencillamente
+ simplemente
+ ]
+ pattern = /\b#{Regexp.union(useless_words)}\b/i
+
+ File.readlines(filename).each_with_index do |line, number|
+ next unless word_found = pattern.match(line)
+ failing_line_message << "#{filename}:#{number.succ} contiene '#{word_found}'. Esta palabra tiene un significado subjetivo y es mejor obviarla en textos técnicos."
+ end
+
+ failing_line_message unless failing_line_message.empty?
+ end
+
+ def check_for_specific_pronouns(filename)
+ failing_line_message = []
+ specific_pronouns = /\b(él|ella|ellos|ellas)\b/i
+
+ File.readlines(filename).each_with_index do |line, number|
+ next unless word_found = specific_pronouns.match(line)
+ failing_line_message << "#{filename}:#{number.succ} contiene '#{word_found}'. Use pronombres más genéricos en la documentación."
+ end
+
+ failing_line_message unless failing_line_message.empty?
+ end
+
+ it "mantiene la calidad de lenguaje de la documentación" do
+ included = /ronn/
+ error_messages = []
+ man_tracked_files.each do |filename|
+ next unless filename&.match?(included)
+ error_messages << check_for_expendable_words(filename)
+ error_messages << check_for_specific_pronouns(filename)
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "mantiene la calidad de lenguaje de oraciones usadas en el código fuente" do
+ error_messages = []
+ exempt = /vendor/
+ lib_tracked_files.each do |filename|
+ next if filename&.match?(exempt)
+ error_messages << check_for_expendable_words(filename)
+ error_messages << check_for_specific_pronouns(filename)
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+end
diff --git a/spec/bundler/quality_spec.rb b/spec/bundler/quality_spec.rb
new file mode 100644
index 0000000000..a98815158e
--- /dev/null
+++ b/spec/bundler/quality_spec.rb
@@ -0,0 +1,245 @@
+# frozen_string_literal: true
+
+require "set"
+
+RSpec.describe "The library itself" do
+ def check_for_git_merge_conflicts(filename)
+ merge_conflicts_regex = /
+ <<<<<<<|
+ =======|
+ >>>>>>>
+ /x
+
+ failing_lines = []
+ each_line(filename) do |line, number|
+ failing_lines << number + 1 if line&.match?(merge_conflicts_regex)
+ end
+
+ return if failing_lines.empty?
+ "#{filename} has unresolved git merge conflicts on lines #{failing_lines.join(", ")}"
+ end
+
+ def check_for_tab_characters(filename)
+ failing_lines = []
+ each_line(filename) do |line, number|
+ failing_lines << number + 1 if line.include?("\t")
+ end
+
+ return if failing_lines.empty?
+ "#{filename} has tab characters on lines #{failing_lines.join(", ")}"
+ end
+
+ def check_for_extra_spaces(filename)
+ failing_lines = []
+ each_line(filename) do |line, number|
+ next if /^\s+#.*\s+\n$/.match?(line)
+ failing_lines << number + 1 if /\s+\n$/.match?(line)
+ end
+
+ return if failing_lines.empty?
+ "#{filename} has spaces on the EOL on lines #{failing_lines.join(", ")}"
+ end
+
+ def check_for_straneous_quotes(filename)
+ return if File.expand_path(filename) == __FILE__
+
+ failing_lines = []
+ each_line(filename) do |line, number|
+ failing_lines << number + 1 if /’/.match?(line)
+ end
+
+ return if failing_lines.empty?
+ "#{filename} has an straneous quote on lines #{failing_lines.join(", ")}"
+ end
+
+ def check_for_expendable_words(filename)
+ failing_line_message = []
+ useless_words = %w[
+ actually
+ basically
+ clearly
+ just
+ obviously
+ really
+ simply
+ ]
+ pattern = /\b#{Regexp.union(useless_words)}\b/i
+
+ each_line(filename) do |line, number|
+ next unless word_found = pattern.match(line)
+ failing_line_message << "#{filename}:#{number.succ} has '#{word_found}'. Avoid using these kinds of weak modifiers."
+ end
+
+ failing_line_message unless failing_line_message.empty?
+ end
+
+ def check_for_specific_pronouns(filename)
+ failing_line_message = []
+ specific_pronouns = /\b(he|she|his|hers|him|her|himself|herself)\b/i
+
+ each_line(filename) do |line, number|
+ next unless word_found = specific_pronouns.match(line)
+ failing_line_message << "#{filename}:#{number.succ} has '#{word_found}'. Use more generic pronouns in documentation."
+ end
+
+ failing_line_message unless failing_line_message.empty?
+ end
+
+ it "has no malformed whitespace" do
+ exempt = /\.gitmodules|fixtures|vendor|LICENSE|vcr_cassettes|rbreadline\.diff|index\.txt$/
+ error_messages = []
+ tracked_files.each do |filename|
+ next if filename&.match?(exempt)
+ error_messages << check_for_tab_characters(filename)
+ error_messages << check_for_extra_spaces(filename)
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "has no estraneous quotes" do
+ exempt = /vendor|vcr_cassettes|LICENSE|rbreadline\.diff/
+ error_messages = []
+ tracked_files.each do |filename|
+ next if filename&.match?(exempt)
+ error_messages << check_for_straneous_quotes(filename)
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "does not include any unresolved merge conflicts" do
+ error_messages = []
+ exempt = %r{lock/lockfile_spec|quality_spec|vcr_cassettes|\.ronn|lockfile_parser\.rb}
+ tracked_files.each do |filename|
+ next if filename&.match?(exempt)
+ error_messages << check_for_git_merge_conflicts(filename)
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "maintains language quality of the documentation" do
+ included = /ronn/
+ error_messages = []
+ man_tracked_files.each do |filename|
+ next unless filename&.match?(included)
+ error_messages << check_for_expendable_words(filename)
+ error_messages << check_for_specific_pronouns(filename)
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "maintains language quality of sentences used in source code" do
+ error_messages = []
+ exempt = /vendor|vcr_cassettes|CODE_OF_CONDUCT/
+ lib_tracked_files.each do |filename|
+ next if filename&.match?(exempt)
+ error_messages << check_for_expendable_words(filename)
+ error_messages << check_for_specific_pronouns(filename)
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "documents all used settings" do
+ exemptions = %w[
+ forget_cli_options
+ gem.changelog
+ gem.ci
+ gem.coc
+ gem.linter
+ gem.mit
+ gem.rubocop
+ gem.test
+ git.allow_insecure
+ inline
+ trust-policy
+ ]
+
+ all_settings = Hash.new {|h, k| h[k] = [] }
+ documented_settings = []
+
+ Bundler::Settings::BOOL_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::BOOL_KEYS" }
+ Bundler::Settings::NUMBER_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::NUMBER_KEYS" }
+ Bundler::Settings::ARRAY_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::ARRAY_KEYS" }
+ Bundler::Settings::STRING_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::STRING_KEYS" }
+
+ key_pattern = /([a-z\._-]+)/i
+ lib_tracked_files.each do |filename|
+ each_line(filename) do |line, number|
+ line.scan(/Bundler\.settings\[:#{key_pattern}\]/).flatten.each {|s| all_settings[s] << "referenced at `#{filename}:#{number.succ}`" }
+ end
+ end
+ documented_settings = File.read("lib/bundler/man/bundle-config.1.ronn")[/LIST OF AVAILABLE KEYS.*/m].scan(/^\* `#{key_pattern}`/).flatten
+
+ documented_settings.each do |s|
+ all_settings.delete(s)
+ expect(exemptions.delete(s)).to be_nil, "setting #{s} was exempted but was actually documented"
+ end
+
+ exemptions.each do |s|
+ expect(all_settings.delete(s)).to be_truthy, "setting #{s} was exempted but unused"
+ end
+ error_messages = all_settings.map do |setting, refs|
+ "The `#{setting}` setting is undocumented\n\t- #{refs.join("\n\t- ")}\n"
+ end
+
+ expect(error_messages.sort).to be_well_formed
+
+ expect(documented_settings).to be_sorted
+ end
+
+ it "can still be built" do
+ with_built_bundler do |_gem_path|
+ expect(err).to be_empty, "bundler should build as a gem without warnings, but\n#{err}"
+ end
+ end
+
+ it "ships the correct set of files" do
+ git_list = git_ls_files(ruby_core? ? "lib/bundler lib/bundler.rb libexec/bundle*" : "lib exe CHANGELOG.md LICENSE.md README.md bundler.gemspec")
+
+ gem_list = loaded_gemspec.files
+
+ expect(git_list).to match_array(gem_list)
+ end
+
+ it "does not contain any warnings" do
+ exclusions = %w[
+ lib/bundler/capistrano.rb
+ lib/bundler/deployment.rb
+ lib/bundler/gem_tasks.rb
+ lib/bundler/vlad.rb
+ ]
+ files_to_require = lib_tracked_files.grep(/\.rb$/) - exclusions
+ files_to_require.reject! {|f| f.start_with?("lib/bundler/vendor") }
+ files_to_require.map! {|f| File.expand_path(f, source_root) }
+ files_to_require.sort!
+ sys_exec("ruby -w") do |input, _, _|
+ files_to_require.each do |f|
+ input.puts "require '#{f}'"
+ end
+ end
+
+ warnings = last_command.stdboth.split("\n")
+ # ignore warnings around deprecated Object#=~ method in RubyGems
+ warnings.reject! {|w| w =~ %r{rubygems\/version.rb.*deprecated\ Object#=~} }
+
+ expect(warnings).to be_well_formed
+ end
+
+ it "does not use require internally, but require_relative" do
+ exempt = %r{templates/|\.5|\.1|vendor/}
+ all_bad_requires = []
+ lib_tracked_files.each do |filename|
+ next if filename&.match?(exempt)
+ each_line(filename) do |line, number|
+ line.scan(/^ *require "bundler/).each { all_bad_requires << "#{filename}:#{number.succ}" }
+ end
+ end
+
+ expect(all_bad_requires).to be_empty, "#{all_bad_requires.size} internal requires that should use `require_relative`: #{all_bad_requires}"
+ end
+
+ private
+
+ def each_line(filename, &block)
+ File.readlines(filename, :encoding => "UTF-8").each_with_index(&block)
+ end
+end
diff --git a/spec/bundler/realworld/dependency_api_spec.rb b/spec/bundler/realworld/dependency_api_spec.rb
new file mode 100644
index 0000000000..14f99bd262
--- /dev/null
+++ b/spec/bundler/realworld/dependency_api_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require_relative "../support/silent_logger"
+
+RSpec.describe "gemcutter's dependency API", :realworld => true do
+ context "when Gemcutter API takes too long to respond" do
+ before do
+ require_rack
+
+ port = find_unused_port
+ @server_uri = "http://127.0.0.1:#{port}"
+
+ require_relative "../support/artifice/endpoint_timeout"
+
+ @t = Thread.new do
+ server = Rack::Server.start(:app => EndpointTimeout,
+ :Host => "0.0.0.0",
+ :Port => port,
+ :server => "webrick",
+ :AccessLog => [],
+ :Logger => Spec::SilentLogger.new)
+ server.start
+ end
+ @t.run
+
+ wait_for_server("127.0.0.1", port)
+ bundle "config set timeout 1"
+ end
+
+ after do
+ Artifice.deactivate
+ @t.kill
+ @t.join
+ end
+
+ it "times out and falls back on the modern index" do
+ install_gemfile <<-G, :artifice => nil
+ source "#{@server_uri}"
+ gem "rack"
+ G
+
+ expect(out).to include("Fetching source index from #{@server_uri}/")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+end
diff --git a/spec/bundler/realworld/double_check_spec.rb b/spec/bundler/realworld/double_check_spec.rb
new file mode 100644
index 0000000000..d7f28d10bb
--- /dev/null
+++ b/spec/bundler/realworld/double_check_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+RSpec.describe "double checking sources", :realworld => true do
+ it "finds already-installed gems" do
+ create_file("rails.gemspec", <<-RUBY)
+ Gem::Specification.new do |s|
+ s.name = "rails"
+ s.version = "5.1.4"
+ s.summary = ""
+ s.description = ""
+ s.author = ""
+ s.add_dependency "actionpack", "5.1.4"
+ end
+ RUBY
+
+ create_file("actionpack.gemspec", <<-RUBY)
+ Gem::Specification.new do |s|
+ s.name = "actionpack"
+ s.version = "5.1.4"
+ s.summary = ""
+ s.description = ""
+ s.author = ""
+ s.add_dependency "rack", "~> 2.0.0"
+ end
+ RUBY
+
+ cmd = <<-RUBY
+ require "#{entrypoint}"
+ require "#{spec_dir}/support/artifice/vcr"
+ require "#{entrypoint}/inline"
+ gemfile(true) do
+ source "https://rubygems.org"
+ gem "rails", path: "."
+ end
+ RUBY
+
+ ruby cmd
+ ruby cmd
+ end
+end
diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb
new file mode 100644
index 0000000000..2f465b7b25
--- /dev/null
+++ b/spec/bundler/realworld/edgecases_spec.rb
@@ -0,0 +1,516 @@
+# frozen_string_literal: true
+
+RSpec.describe "real world edgecases", :realworld => true do
+ def rubygems_version(name, requirement)
+ ruby <<-RUBY
+ require "#{spec_dir}/support/artifice/vcr"
+ require "bundler"
+ require "bundler/source/rubygems/remote"
+ require "bundler/fetcher"
+ rubygem = Bundler.ui.silence do
+ source = Bundler::Source::Rubygems::Remote.new(Bundler::URI("https://rubygems.org"))
+ fetcher = Bundler::Fetcher.new(source)
+ index = fetcher.specs([#{name.dump}], nil)
+ requirement = Gem::Requirement.create(#{requirement.dump})
+ index.search(#{name.dump}).select {|spec| requirement.satisfied_by?(spec.version) }.last
+ end
+ if rubygem.nil?
+ raise "Could not find #{name} (#{requirement}) on rubygems.org!\n" \
+ "Found specs:\n\#{index.send(:specs).inspect}"
+ end
+ puts "#{name} (\#{rubygem.version})"
+ RUBY
+ end
+
+ it "resolves dependencies correctly" do
+ gemfile <<-G
+ source "https://rubygems.org"
+
+ gem 'rails', '~> 5.0'
+ gem 'capybara', '~> 2.2.0'
+ gem 'rack-cache', '1.2.0' # last version that works on Ruby 1.9
+ G
+ bundle :lock
+ expect(lockfile).to include(rubygems_version("rails", "~> 5.0"))
+ expect(lockfile).to include("capybara (2.2.1)")
+ end
+
+ it "installs the latest version of gxapi_rails" do
+ gemfile <<-G
+ source "https://rubygems.org"
+
+ gem "sass-rails"
+ gem "rails", "~> 5"
+ gem "gxapi_rails", "< 0.1.0" # 0.1.0 was released way after the test was written
+ gem 'rack-cache', '1.2.0' # last version that works on Ruby 1.9
+ G
+ bundle :lock
+ expect(lockfile).to include("gxapi_rails (0.0.6)")
+ end
+
+ it "installs the latest version of i18n" do
+ gemfile <<-G
+ source "https://rubygems.org"
+
+ gem "i18n", "~> 0.6.0"
+ gem "activesupport", "~> 3.0"
+ gem "activerecord", "~> 3.0"
+ gem "builder", "~> 2.1.2"
+ G
+ bundle :lock
+ expect(lockfile).to include(rubygems_version("i18n", "~> 0.6.0"))
+ expect(lockfile).to include(rubygems_version("activesupport", "~> 3.0"))
+ end
+
+ it "is able to update a top-level dependency when there is a conflict on a shared transitive child" do
+ # from https://github.com/rubygems/bundler/issues/5031
+
+ pristine_system_gems "bundler-1.99.0"
+
+ gemfile <<-G
+ source "https://rubygems.org"
+ gem 'rails', '~> 4.2.7.1'
+ gem 'paperclip', '~> 5.1.0'
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ actionmailer (4.2.7.1)
+ actionpack (= 4.2.7.1)
+ actionview (= 4.2.7.1)
+ activejob (= 4.2.7.1)
+ mail (~> 2.5, >= 2.5.4)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ actionpack (4.2.7.1)
+ actionview (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
+ rack (~> 1.6)
+ rack-test (~> 0.6.2)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ actionview (4.2.7.1)
+ activesupport (= 4.2.7.1)
+ builder (~> 3.1)
+ erubis (~> 2.7.0)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ activejob (4.2.7.1)
+ activesupport (= 4.2.7.1)
+ globalid (>= 0.3.0)
+ activemodel (4.2.7.1)
+ activesupport (= 4.2.7.1)
+ builder (~> 3.1)
+ activerecord (4.2.7.1)
+ activemodel (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
+ arel (~> 6.0)
+ activesupport (4.2.7.1)
+ i18n (~> 0.7)
+ json (~> 1.7, >= 1.7.7)
+ minitest (~> 5.1)
+ thread_safe (~> 0.3, >= 0.3.4)
+ tzinfo (~> 1.1)
+ arel (6.0.3)
+ builder (3.2.2)
+ climate_control (0.0.3)
+ activesupport (>= 3.0)
+ cocaine (0.5.8)
+ climate_control (>= 0.0.3, < 1.0)
+ concurrent-ruby (1.0.2)
+ erubis (2.7.0)
+ globalid (0.3.7)
+ activesupport (>= 4.1.0)
+ i18n (0.7.0)
+ json (1.8.3)
+ loofah (2.0.3)
+ nokogiri (>= 1.5.9)
+ mail (2.6.4)
+ mime-types (>= 1.16, < 4)
+ mime-types (3.1)
+ mime-types-data (~> 3.2015)
+ mime-types-data (3.2016.0521)
+ mimemagic (0.3.2)
+ mini_portile2 (2.1.0)
+ minitest (5.9.1)
+ nokogiri (1.6.8)
+ mini_portile2 (~> 2.1.0)
+ pkg-config (~> 1.1.7)
+ paperclip (5.1.0)
+ activemodel (>= 4.2.0)
+ activesupport (>= 4.2.0)
+ cocaine (~> 0.5.5)
+ mime-types
+ mimemagic (~> 0.3.0)
+ pkg-config (1.1.7)
+ rack (1.6.4)
+ rack-test (0.6.3)
+ rack (>= 1.0)
+ rails (4.2.7.1)
+ actionmailer (= 4.2.7.1)
+ actionpack (= 4.2.7.1)
+ actionview (= 4.2.7.1)
+ activejob (= 4.2.7.1)
+ activemodel (= 4.2.7.1)
+ activerecord (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
+ bundler (>= 1.3.0, < 2.0)
+ railties (= 4.2.7.1)
+ sprockets-rails
+ rails-deprecated_sanitizer (1.0.3)
+ activesupport (>= 4.2.0.alpha)
+ rails-dom-testing (1.0.7)
+ activesupport (>= 4.2.0.beta, < 5.0)
+ nokogiri (~> 1.6.0)
+ rails-deprecated_sanitizer (>= 1.0.1)
+ rails-html-sanitizer (1.0.3)
+ loofah (~> 2.0)
+ railties (4.2.7.1)
+ actionpack (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
+ rake (>= 0.8.7)
+ thor (>= 0.18.1, < 2.0)
+ rake (11.3.0)
+ sprockets (3.7.0)
+ concurrent-ruby (~> 1.0)
+ rack (> 1, < 3)
+ sprockets-rails (3.2.0)
+ actionpack (>= 4.0)
+ activesupport (>= 4.0)
+ sprockets (>= 3.0.0)
+ thor (0.19.1)
+ thread_safe (0.3.5)
+ tzinfo (1.2.2)
+ thread_safe (~> 0.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ paperclip (~> 5.1.0)
+ rails (~> 4.2.7.1)
+ L
+
+ bundle "lock --update paperclip", :env => { "BUNDLER_VERSION" => "1.99.0" }
+
+ expect(lockfile).to include(rubygems_version("paperclip", "~> 5.1.0"))
+ end
+
+ it "outputs a helpful error message when gems have invalid gemspecs", :rubygems => "< 3.3.16" do
+ install_gemfile <<-G, :standalone => true, :raise_on_error => false, :env => { "BUNDLE_FORCE_RUBY_PLATFORM" => "1" }
+ source 'https://rubygems.org'
+ gem "resque-scheduler", "2.2.0"
+ gem "redis-namespace", "1.6.0" # for a consistent resolution including ruby 2.3.0
+ gem "ruby2_keywords", "0.0.5"
+ G
+ expect(err).to include("You have one or more invalid gemspecs that need to be fixed.")
+ expect(err).to include("resque-scheduler 2.2.0 has an invalid gemspec")
+ end
+
+ it "outputs a helpful warning when gems have a gemspec with invalid `require_paths`", :rubygems => ">= 3.3.16" do
+ install_gemfile <<-G, :standalone => true, :env => { "BUNDLE_FORCE_RUBY_PLATFORM" => "1" }
+ source 'https://rubygems.org'
+ gem "resque-scheduler", "2.2.0"
+ gem "redis-namespace", "1.6.0" # for a consistent resolution including ruby 2.3.0
+ gem "ruby2_keywords", "0.0.5"
+ G
+ expect(err).to include("resque-scheduler 2.2.0 includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this").once
+ end
+
+ it "doesn't hang on big gemfile" do
+ skip "Only for ruby 2.7" unless RUBY_VERSION.start_with?("2.7")
+
+ gemfile <<~G
+ # frozen_string_literal: true
+
+ source "https://rubygems.org"
+
+ ruby "~> 2.7.7"
+
+ gem "rails"
+ gem "pg", ">= 0.18", "< 2.0"
+ gem "goldiloader"
+ gem "awesome_nested_set"
+ gem "circuitbox"
+ gem "passenger"
+ gem "globalid"
+ gem "rack-cors"
+ gem "rails-pg-extras"
+ gem "linear_regression_trend"
+ gem "rack-protection"
+ gem "pundit"
+ gem "remote_ip_proxy_scrubber"
+ gem "bcrypt"
+ gem "searchkick"
+ gem "excon"
+ gem "faraday_middleware-aws-sigv4"
+ gem "typhoeus"
+ gem "sidekiq"
+ gem "sidekiq-undertaker"
+ gem "sidekiq-cron"
+ gem "storext"
+ gem "appsignal"
+ gem "fcm"
+ gem "business_time"
+ gem "tzinfo"
+ gem "holidays"
+ gem "bigdecimal"
+ gem "progress_bar"
+ gem "redis"
+ gem "hiredis"
+ gem "state_machines"
+ gem "state_machines-audit_trail"
+ gem "state_machines-activerecord"
+ gem "interactor"
+ gem "ar_transaction_changes"
+ gem "redis-rails"
+ gem "seed_migration"
+ gem "lograge"
+ gem "graphiql-rails", group: :development
+ gem "graphql"
+ gem "pusher"
+ gem "rbnacl"
+ gem "jwt"
+ gem "json-schema"
+ gem "discard"
+ gem "money"
+ gem "strip_attributes"
+ gem "validates_email_format_of"
+ gem "audited"
+ gem "concurrent-ruby"
+ gem "with_advisory_lock"
+
+ group :test do
+ gem "rspec-sidekiq"
+ gem "simplecov", require: false
+ end
+
+ group :development, :test do
+ gem "byebug", platform: :mri
+ gem "guard"
+ gem "guard-bundler"
+ gem "guard-rspec"
+ gem "rb-fsevent"
+ gem "rspec_junit_formatter"
+ gem "rspec-collection_matchers"
+ gem "rspec-rails"
+ gem "rspec-retry"
+ gem "state_machines-rspec"
+ gem "dotenv-rails"
+ gem "database_cleaner-active_record"
+ gem "database_cleaner-redis"
+ gem "timecop"
+ end
+
+ gem "factory_bot_rails"
+ gem "faker"
+
+ group :development do
+ gem "listen"
+ gem "sql_queries_count"
+ gem "rubocop"
+ gem "rubocop-performance"
+ gem "rubocop-rspec"
+ gem "rubocop-rails"
+ gem "brakeman"
+ gem "bundler-audit"
+ gem "solargraph"
+ gem "annotate"
+ end
+ G
+
+ if Bundler.feature_flag.bundler_3_mode?
+ # Conflicts on bundler version, so we count attempts differently
+ bundle :lock, :env => { "DEBUG_RESOLVER" => "1" }, :raise_on_error => false
+ expect(out.split("\n").grep(/backtracking to/).count).to eq(16)
+ else
+ bundle :lock, :env => { "DEBUG_RESOLVER" => "1" }
+ expect(out).to include("Solution found after 7 attempts")
+ end
+ end
+
+ it "doesn't hang on tricky gemfile" do
+ skip "Only for ruby 2.7" unless RUBY_VERSION.start_with?("2.7")
+
+ gemfile <<~G
+ source 'https://rubygems.org'
+
+ group :development do
+ gem "puppet-module-posix-default-r2.7", '~> 0.3'
+ gem "puppet-module-posix-dev-r2.7", '~> 0.3'
+ gem "beaker-rspec"
+ gem "beaker-puppet"
+ gem "beaker-docker"
+ gem "beaker-puppet_install_helper"
+ gem "beaker-module_install_helper"
+ end
+ G
+
+ bundle :lock, :env => { "DEBUG_RESOLVER" => "1" }
+
+ expect(out).to include("Solution found after 6 attempts")
+ end
+
+ it "doesn't hang on nix gemfile" do
+ skip "Only for ruby 3.0" unless RUBY_VERSION.start_with?("3.0")
+
+ gemfile <<~G
+ source "https://rubygems.org"
+
+ gem "addressable"
+ gem "atk"
+ gem "awesome_print"
+ gem "bacon"
+ gem "byebug"
+ gem "cairo"
+ gem "cairo-gobject"
+ gem "camping"
+ gem "charlock_holmes"
+ gem "cld3"
+ gem "cocoapods"
+ gem "cocoapods-acknowledgements"
+ gem "cocoapods-art"
+ gem "cocoapods-bin"
+ gem "cocoapods-browser"
+ gem "cocoapods-bugsnag"
+ gem "cocoapods-check"
+ gem "cocoapods-clean"
+ gem "cocoapods-clean_build_phases_scripts"
+ gem "cocoapods-core"
+ gem "cocoapods-coverage"
+ gem "cocoapods-deintegrate"
+ gem "cocoapods-dependencies"
+ gem "cocoapods-deploy"
+ gem "cocoapods-downloader"
+ gem "cocoapods-expert-difficulty"
+ gem "cocoapods-fix-react-native"
+ gem "cocoapods-generate"
+ gem "cocoapods-git_url_rewriter"
+ gem "cocoapods-keys"
+ gem "cocoapods-no-dev-schemes"
+ gem "cocoapods-open"
+ gem "cocoapods-packager"
+ gem "cocoapods-playgrounds"
+ gem "cocoapods-plugins"
+ gem "cocoapods-prune-localizations"
+ gem "cocoapods-rome"
+ gem "cocoapods-search"
+ gem "cocoapods-sorted-search"
+ gem "cocoapods-static-swift-framework"
+ gem "cocoapods-stats"
+ gem "cocoapods-tdfire-binary"
+ gem "cocoapods-testing"
+ gem "cocoapods-trunk"
+ gem "cocoapods-try"
+ gem "cocoapods-try-release-fix"
+ gem "cocoapods-update-if-you-dare"
+ gem "cocoapods-whitelist"
+ gem "cocoapods-wholemodule"
+ gem "coderay"
+ gem "concurrent-ruby"
+ gem "curb"
+ gem "curses"
+ gem "daemons"
+ gem "dep-selector-libgecode"
+ gem "digest-sha3"
+ gem "domain_name"
+ gem "do_sqlite3"
+ gem "ethon"
+ gem "eventmachine"
+ gem "excon"
+ gem "faraday"
+ gem "ffi"
+ gem "ffi-rzmq-core"
+ gem "fog-dnsimple"
+ gem "gdk_pixbuf2"
+ gem "gio2"
+ gem "gitlab-markup"
+ gem "glib2"
+ gem "gpgme"
+ gem "gtk2"
+ gem "hashie"
+ gem "highline"
+ gem "hike"
+ gem "hitimes"
+ gem "hpricot"
+ gem "httpclient"
+ gem "http-cookie"
+ gem "iconv"
+ gem "idn-ruby"
+ gem "jbuilder"
+ gem "jekyll"
+ gem "jmespath"
+ gem "jwt"
+ gem "libv8"
+ gem "libxml-ruby"
+ gem "magic"
+ gem "markaby"
+ gem "method_source"
+ gem "mini_magick"
+ gem "msgpack"
+ gem "mysql2"
+ gem "ncursesw"
+ gem "netrc"
+ gem "net-scp"
+ gem "net-ssh"
+ gem "nokogiri"
+ gem "opus-ruby"
+ gem "ovirt-engine-sdk"
+ gem "pango"
+ gem "patron"
+ gem "pcaprub"
+ gem "pg"
+ gem "pry"
+ gem "pry-byebug"
+ gem "pry-doc"
+ gem "public_suffix"
+ gem "puma"
+ gem "rails"
+ gem "rainbow"
+ gem "rbnacl"
+ gem "rb-readline"
+ gem "re2"
+ gem "redis"
+ gem "redis-rack"
+ gem "rest-client"
+ gem "rmagick"
+ gem "rpam2"
+ gem "rspec"
+ gem "rubocop"
+ gem "rubocop-performance"
+ gem "ruby-libvirt"
+ gem "ruby-lxc"
+ gem "ruby-progressbar"
+ gem "ruby-terminfo"
+ gem "ruby-vips"
+ gem "rubyzip"
+ gem "rugged"
+ gem "sassc"
+ gem "scrypt"
+ gem "semian"
+ gem "sequel"
+ gem "sequel_pg"
+ gem "simplecov"
+ gem "sinatra"
+ gem "slop"
+ gem "snappy"
+ gem "sqlite3"
+ gem "taglib-ruby"
+ gem "thrift"
+ gem "tilt"
+ gem "tiny_tds"
+ gem "treetop"
+ gem "typhoeus"
+ gem "tzinfo"
+ gem "unf_ext"
+ gem "uuid4r"
+ gem "whois"
+ gem "zookeeper"
+ G
+
+ bundle :lock, :env => { "DEBUG_RESOLVER" => "1" }
+
+ expect(out).to include("Solution found after 4 attempts")
+ end
+end
diff --git a/spec/bundler/realworld/ffi_spec.rb b/spec/bundler/realworld/ffi_spec.rb
new file mode 100644
index 0000000000..fdefc14091
--- /dev/null
+++ b/spec/bundler/realworld/ffi_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+RSpec.describe "loading dynamically linked library on a bundle exec context", :realworld => true do
+ it "passes ENV right after argv in memory" do
+ create_file "foo.rb", <<~RUBY
+ require 'ffi'
+
+ module FOO
+ extend FFI::Library
+ ffi_lib './libfoo.so'
+
+ attach_function :Hello, [], :void
+ end
+
+ FOO.Hello()
+ RUBY
+
+ create_file "libfoo.c", <<~'C'
+ #include <stdio.h>
+
+ static int foo_init(int argc, char** argv, char** envp) {
+ if (argv[argc+1] == NULL) {
+ printf("FAIL\n");
+ } else {
+ printf("OK\n");
+ }
+
+ return 0;
+ }
+
+ #if defined(__APPLE__) && defined(__MACH__)
+ __attribute__((section("__DATA,__mod_init_func"), used, aligned(sizeof(void*))))
+ #else
+ __attribute__((section(".init_array")))
+ #endif
+ static void *ctr = &foo_init;
+
+ extern char** environ;
+
+ void Hello() {
+ return;
+ }
+ C
+
+ sys_exec "gcc -g -o libfoo.so -shared -fpic libfoo.c"
+
+ install_gemfile <<-G
+ source "https://rubygems.org"
+
+ gem 'ffi'
+ G
+
+ bundle "exec ruby foo.rb"
+
+ expect(out).to eq("OK")
+ end
+end
diff --git a/spec/bundler/realworld/fixtures/warbler/.gitignore b/spec/bundler/realworld/fixtures/warbler/.gitignore
new file mode 100644
index 0000000000..d392f0e82c
--- /dev/null
+++ b/spec/bundler/realworld/fixtures/warbler/.gitignore
@@ -0,0 +1 @@
+*.jar
diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile b/spec/bundler/realworld/fixtures/warbler/Gemfile
new file mode 100644
index 0000000000..4fbf2d05a7
--- /dev/null
+++ b/spec/bundler/realworld/fixtures/warbler/Gemfile
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+gem "demo", :path => "./demo"
+gem "jruby-jars", "~> 9.2"
+gem "warbler", "~> 2.0"
diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock
new file mode 100644
index 0000000000..8f29bd341f
--- /dev/null
+++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock
@@ -0,0 +1,30 @@
+PATH
+ remote: demo
+ specs:
+ demo (1.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ jruby-jars (9.2.16.0)
+ jruby-rack (1.1.21)
+ rake (13.0.1)
+ rubyzip (1.3.0)
+ warbler (2.0.5)
+ jruby-jars (>= 9.0.0.0)
+ jruby-rack (>= 1.1.1, < 1.3)
+ rake (>= 10.1.0)
+ rubyzip (~> 1.0, < 1.4)
+
+PLATFORMS
+ java
+ ruby
+ universal-java-11
+
+DEPENDENCIES
+ demo!
+ jruby-jars (~> 9.2)
+ warbler (~> 2.0)
+
+BUNDLED WITH
+ 2.4.0.dev
diff --git a/spec/bundler/realworld/fixtures/warbler/bin/warbler-example.rb b/spec/bundler/realworld/fixtures/warbler/bin/warbler-example.rb
new file mode 100644
index 0000000000..25f614ecc2
--- /dev/null
+++ b/spec/bundler/realworld/fixtures/warbler/bin/warbler-example.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+puts require "bundler/setup"
diff --git a/spec/bundler/realworld/fixtures/warbler/demo/demo.gemspec b/spec/bundler/realworld/fixtures/warbler/demo/demo.gemspec
new file mode 100644
index 0000000000..ed5a0dc080
--- /dev/null
+++ b/spec/bundler/realworld/fixtures/warbler/demo/demo.gemspec
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+Gem::Specification.new do |spec|
+ spec.name = "demo"
+ spec.version = "1.0"
+ spec.author = "Somebody"
+ spec.summary = "A demo gem"
+ spec.license = "MIT"
+ spec.homepage = "https://example.org"
+end
diff --git a/spec/bundler/realworld/gemfile_source_header_spec.rb b/spec/bundler/realworld/gemfile_source_header_spec.rb
new file mode 100644
index 0000000000..60c0055a62
--- /dev/null
+++ b/spec/bundler/realworld/gemfile_source_header_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require_relative "../support/silent_logger"
+
+RSpec.describe "fetching dependencies with a mirrored source", :realworld => true do
+ let(:mirror) { "https://server.example.org" }
+ let(:original) { "http://127.0.0.1:#{@port}" }
+
+ before do
+ setup_server
+ bundle "config set --local mirror.#{mirror} #{original}"
+ end
+
+ after do
+ Artifice.deactivate
+ @t.kill
+ @t.join
+ end
+
+ it "sets the 'X-Gemfile-Source' header and bundles successfully" do
+ gemfile <<-G
+ source "#{mirror}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil
+
+ expect(out).to include("Installing weakling")
+ expect(out).to include("Bundle complete")
+ expect(the_bundle).to include_gems "weakling 0.0.3"
+ end
+
+ private
+
+ def setup_server
+ require_rack
+ @port = find_unused_port
+ @server_uri = "http://127.0.0.1:#{@port}"
+
+ require_relative "../support/artifice/endpoint_mirror_source"
+
+ @t = Thread.new do
+ Rack::Server.start(:app => EndpointMirrorSource,
+ :Host => "0.0.0.0",
+ :Port => @port,
+ :server => "webrick",
+ :AccessLog => [],
+ :Logger => Spec::SilentLogger.new)
+ end.run
+
+ wait_for_server("127.0.0.1", @port)
+ end
+end
diff --git a/spec/bundler/realworld/git_spec.rb b/spec/bundler/realworld/git_spec.rb
new file mode 100644
index 0000000000..3d352626ea
--- /dev/null
+++ b/spec/bundler/realworld/git_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+RSpec.describe "github source", :realworld => true do
+ it "properly fetches PRs" do
+ install_gemfile <<-G
+ source "https://rubygems.org"
+
+ gem "reline", github: "https://github.com/ruby/reline/pull/488"
+ G
+ end
+end
diff --git a/spec/bundler/realworld/mirror_probe_spec.rb b/spec/bundler/realworld/mirror_probe_spec.rb
new file mode 100644
index 0000000000..f2ce477c10
--- /dev/null
+++ b/spec/bundler/realworld/mirror_probe_spec.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+require_relative "../support/silent_logger"
+
+RSpec.describe "fetching dependencies with a not available mirror", :realworld => true do
+ let(:mirror) { @mirror_uri }
+ let(:original) { @server_uri }
+ let(:server_port) { @server_port }
+ let(:host) { "127.0.0.1" }
+
+ before do
+ require_rack
+ setup_server
+ setup_mirror
+ end
+
+ after do
+ Artifice.deactivate
+ @server_thread.kill
+ @server_thread.join
+ end
+
+ context "with a specific fallback timeout" do
+ before do
+ global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/__FALLBACK_TIMEOUT/" => "true",
+ "BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/" => mirror)
+ end
+
+ it "install a gem using the original uri when the mirror is not responding" do
+ gemfile <<-G
+ source "#{original}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil
+
+ expect(out).to include("Installing weakling")
+ expect(out).to include("Bundle complete")
+ expect(the_bundle).to include_gems "weakling 0.0.3"
+ end
+ end
+
+ context "with a global fallback timeout" do
+ before do
+ global_config("BUNDLE_MIRROR__ALL__FALLBACK_TIMEOUT/" => "1",
+ "BUNDLE_MIRROR__ALL" => mirror)
+ end
+
+ it "install a gem using the original uri when the mirror is not responding" do
+ gemfile <<-G
+ source "#{original}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil
+
+ expect(out).to include("Installing weakling")
+ expect(out).to include("Bundle complete")
+ expect(the_bundle).to include_gems "weakling 0.0.3"
+ end
+ end
+
+ context "with a specific mirror without a fallback timeout" do
+ before do
+ global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/" => mirror)
+ end
+
+ it "fails to install the gem with a timeout error" do
+ gemfile <<-G
+ source "#{original}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil, :raise_on_error => false
+
+ expect(out).to include("Fetching source index from #{mirror}")
+
+ err_lines = err.split("\n")
+ expect(err_lines).to include(%r{\ARetrying fetcher due to error \(2/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <})
+ expect(err_lines).to include(%r{\ARetrying fetcher due to error \(3/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <})
+ expect(err_lines).to include(%r{\ARetrying fetcher due to error \(4/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <})
+ expect(err_lines).to include(%r{\ACould not fetch specs from #{mirror}/ due to underlying error <})
+ end
+ end
+
+ context "with a global mirror without a fallback timeout" do
+ before do
+ global_config("BUNDLE_MIRROR__ALL" => mirror)
+ end
+
+ it "fails to install the gem with a timeout error" do
+ gemfile <<-G
+ source "#{original}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil, :raise_on_error => false
+
+ expect(out).to include("Fetching source index from #{mirror}")
+
+ err_lines = err.split("\n")
+ expect(err_lines).to include(%r{\ARetrying fetcher due to error \(2/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <})
+ expect(err_lines).to include(%r{\ARetrying fetcher due to error \(3/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <})
+ expect(err_lines).to include(%r{\ARetrying fetcher due to error \(4/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <})
+ expect(err_lines).to include(%r{\ACould not fetch specs from #{mirror}/ due to underlying error <})
+ end
+ end
+
+ def setup_server
+ @server_port = find_unused_port
+ @server_uri = "http://#{host}:#{@server_port}"
+
+ require_relative "../support/artifice/endpoint"
+
+ @server_thread = Thread.new do
+ Rack::Server.start(:app => Endpoint,
+ :Host => host,
+ :Port => @server_port,
+ :server => "webrick",
+ :AccessLog => [],
+ :Logger => Spec::SilentLogger.new)
+ end.run
+
+ wait_for_server(host, @server_port)
+ end
+
+ def setup_mirror
+ @mirror_port = find_unused_port
+ @mirror_uri = "http://#{host}:#{@mirror_port}"
+ end
+end
diff --git a/spec/bundler/realworld/parallel_spec.rb b/spec/bundler/realworld/parallel_spec.rb
new file mode 100644
index 0000000000..a1e4f83909
--- /dev/null
+++ b/spec/bundler/realworld/parallel_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+RSpec.describe "parallel", :realworld => true do
+ it "installs" do
+ gemfile <<-G
+ source "https://rubygems.org"
+ gem 'activesupport', '~> 3.2.13'
+ gem 'faker', '~> 1.1.2'
+ gem 'i18n', '~> 0.6.0' # Because 0.7+ requires Ruby 1.9.3+
+ G
+
+ bundle :install, :jobs => 4, :env => { "DEBUG" => "1" }
+
+ expect(out).to match(/[1-3]: /)
+
+ bundle "info activesupport --path"
+ expect(out).to match(/activesupport/)
+
+ bundle "info faker --path"
+ expect(out).to match(/faker/)
+ end
+
+ it "updates" do
+ install_gemfile <<-G
+ source "https://rubygems.org"
+ gem 'activesupport', '3.2.12'
+ gem 'faker', '~> 1.1.2'
+ G
+
+ gemfile <<-G
+ source "https://rubygems.org"
+ gem 'activesupport', '~> 3.2.12'
+ gem 'faker', '~> 1.1.2'
+ gem 'i18n', '~> 0.6.0' # Because 0.7+ requires Ruby 1.9.3+
+ G
+
+ bundle :update, :jobs => 4, :env => { "DEBUG" => "1" }, :all => true
+
+ expect(out).to match(/[1-3]: /)
+
+ bundle "info activesupport --path"
+ expect(out).to match(/activesupport-3\.2\.\d+/)
+
+ bundle "info faker --path"
+ expect(out).to match(/faker/)
+ end
+
+ it "works with --standalone" do
+ gemfile <<-G
+ source "https://rubygems.org"
+ gem "diff-lcs"
+ G
+
+ bundle :install, :standalone => true, :jobs => 4
+
+ ruby <<-RUBY
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ require "diff/lcs"
+ puts Diff::LCS
+ RUBY
+
+ expect(out).to eq("Diff::LCS")
+ end
+end
diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb
new file mode 100644
index 0000000000..aa8a48fcc7
--- /dev/null
+++ b/spec/bundler/realworld/slow_perf_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "bundle install with complex dependencies", :realworld => true do
+ it "resolves quickly" do
+ gemfile <<-G
+ source 'https://rubygems.org'
+
+ gem "actionmailer"
+ gem "mongoid", ">= 0.10.2"
+ G
+
+ expect { bundle "lock" }.to take_less_than(18) # seconds
+ end
+
+ it "resolves quickly (case 2)" do
+ gemfile <<-G
+ source "https://rubygems.org"
+
+ gem 'metasploit-erd'
+ gem 'rails-erd'
+ gem 'yard'
+
+ gem 'coveralls'
+ gem 'rails'
+ gem 'simplecov'
+ gem 'rspec-rails'
+ G
+
+ expect { bundle "lock" }.to take_less_than(30) # seconds
+ end
+end
diff --git a/spec/bundler/resolver/basic_spec.rb b/spec/bundler/resolver/basic_spec.rb
new file mode 100644
index 0000000000..f739f8c02b
--- /dev/null
+++ b/spec/bundler/resolver/basic_spec.rb
@@ -0,0 +1,350 @@
+# frozen_string_literal: true
+
+RSpec.describe "Resolving" do
+ before :each do
+ @index = an_awesome_index
+ end
+
+ it "resolves a single gem" do
+ dep "rack"
+
+ should_resolve_as %w[rack-1.1]
+ end
+
+ it "resolves a gem with dependencies" do
+ dep "actionpack"
+
+ should_resolve_as %w[actionpack-2.3.5 activesupport-2.3.5 rack-1.0]
+ end
+
+ it "resolves a conflicting index" do
+ @index = a_conflict_index
+ dep "my_app"
+ should_resolve_as %w[activemodel-3.2.11 builder-3.0.4 grape-0.2.6 my_app-1.0.0]
+ end
+
+ it "resolves a complex conflicting index" do
+ @index = a_complex_conflict_index
+ dep "my_app"
+ should_resolve_as %w[a-1.4.0 b-0.3.5 c-3.2 d-0.9.8 my_app-1.1.0]
+ end
+
+ it "resolves a index with conflict on child" do
+ @index = index_with_conflict_on_child
+ dep "chef_app"
+ should_resolve_as %w[berkshelf-2.0.7 chef-10.26 chef_app-1.0.0 json-1.7.7]
+ end
+
+ it "prefers explicitly requested dependencies when resolving an index which would otherwise be ambiguous" do
+ @index = an_ambiguous_index
+ dep "a"
+ dep "b"
+ should_resolve_as %w[a-1.0.0 b-2.0.0 c-1.0.0 d-1.0.0]
+ end
+
+ it "prefers non-prerelease resolutions in sort order" do
+ @index = optional_prereleases_index
+ dep "a"
+ dep "b"
+ should_resolve_as %w[a-1.0.0 b-1.5.0]
+ end
+
+ it "resolves a index with root level conflict on child" do
+ @index = a_index_with_root_conflict_on_child
+ dep "i18n", "~> 0.4"
+ dep "activesupport", "~> 3.0"
+ dep "activerecord", "~> 3.0"
+ dep "builder", "~> 2.1.2"
+ should_resolve_as %w[activesupport-3.0.5 i18n-0.4.2 builder-2.1.2 activerecord-3.0.5 activemodel-3.0.5]
+ end
+
+ it "resolves a gem specified with a pre-release version" do
+ dep "activesupport", "~> 3.0.0.beta"
+ dep "activemerchant"
+ should_resolve_as %w[activemerchant-2.3.5 activesupport-3.0.0.beta1]
+ end
+
+ it "doesn't select a pre-release if not specified in the Gemfile" do
+ dep "activesupport"
+ dep "reform"
+ should_resolve_as %w[reform-1.0.0 activesupport-2.3.5]
+ end
+
+ it "doesn't select a pre-release for sub-dependencies" do
+ dep "reform"
+ should_resolve_as %w[reform-1.0.0 activesupport-2.3.5]
+ end
+
+ it "selects a pre-release for sub-dependencies if it's the only option" do
+ dep "need-pre"
+ should_resolve_as %w[need-pre-1.0.0 activesupport-3.0.0.beta1]
+ end
+
+ it "selects a pre-release if it's specified in the Gemfile" do
+ dep "activesupport", "= 3.0.0.beta"
+ dep "actionpack"
+
+ should_resolve_as %w[activesupport-3.0.0.beta actionpack-3.0.0.beta rack-1.1 rack-mount-0.6]
+ end
+
+ it "prefers non-pre-releases when doing conservative updates" do
+ @index = build_index do
+ gem "mail", "2.7.0"
+ gem "mail", "2.7.1.rc1"
+ gem "RubyGems\0", Gem::VERSION
+ end
+ dep "mail"
+ @locked = locked ["mail", "2.7.0"]
+ @base = locked
+ should_conservative_resolve_and_include [:patch], [], ["mail-2.7.0"]
+ end
+
+ it "raises an exception if a child dependency is not resolved" do
+ @index = a_unresovable_child_index
+ dep "chef_app_error"
+ expect do
+ resolve
+ end.to raise_error(Bundler::SolveFailure)
+ end
+
+ it "raises an exception with the minimal set of conflicting dependencies" do
+ @index = build_index do
+ %w[0.9 1.0 2.0].each {|v| gem("a", v) }
+ gem("b", "1.0") { dep "a", ">= 2" }
+ gem("c", "1.0") { dep "a", "< 1" }
+ end
+ dep "a"
+ dep "b"
+ dep "c"
+ expect do
+ resolve
+ end.to raise_error(Bundler::SolveFailure, <<~E.strip)
+ Could not find compatible versions
+
+ Because every version of c depends on a < 1
+ and every version of b depends on a >= 2,
+ every version of c is incompatible with b >= 0.
+ So, because Gemfile depends on b >= 0
+ and Gemfile depends on c >= 0,
+ version solving has failed.
+ E
+ end
+
+ it "should throw error in case of circular dependencies" do
+ @index = a_circular_index
+ dep "circular_app"
+
+ expect do
+ Bundler::SpecSet.new(resolve).sort
+ end.to raise_error(Bundler::CyclicDependencyError, /please remove either gem 'bar' or gem 'foo'/i)
+ end
+
+ # Issue #3459
+ it "should install the latest possible version of a direct requirement with no constraints given" do
+ @index = a_complicated_index
+ dep "foo"
+ should_resolve_and_include %w[foo-3.0.5]
+ end
+
+ # Issue #3459
+ it "should install the latest possible version of a direct requirement with constraints given" do
+ @index = a_complicated_index
+ dep "foo", ">= 3.0.0"
+ should_resolve_and_include %w[foo-3.0.5]
+ end
+
+ it "takes into account required_ruby_version" do
+ @index = build_index do
+ gem "foo", "1.0.0" do
+ dep "bar", ">= 0"
+ end
+
+ gem "foo", "2.0.0" do |s|
+ dep "bar", ">= 0"
+ s.required_ruby_version = "~> 2.0.0"
+ end
+
+ gem "bar", "1.0.0"
+
+ gem "bar", "2.0.0" do |s|
+ s.required_ruby_version = "~> 2.0.0"
+ end
+
+ gem "Ruby\0", "1.8.7"
+ end
+ dep "foo"
+ dep "Ruby\0", "1.8.7"
+
+ should_resolve_and_include %w[foo-1.0.0 bar-1.0.0]
+ end
+
+ context "conservative" do
+ before :each do
+ @index = build_index do
+ gem("foo", "1.3.7") { dep "bar", "~> 2.0" }
+ gem("foo", "1.3.8") { dep "bar", "~> 2.0" }
+ gem("foo", "1.4.3") { dep "bar", "~> 2.0" }
+ gem("foo", "1.4.4") { dep "bar", "~> 2.0" }
+ gem("foo", "1.4.5") { dep "bar", "~> 2.1" }
+ gem("foo", "1.5.0") { dep "bar", "~> 2.1" }
+ gem("foo", "1.5.1") { dep "bar", "~> 3.0" }
+ gem("foo", "2.0.0") { dep "bar", "~> 3.0" }
+ gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0]
+ end
+ dep "foo"
+
+ # base represents declared dependencies in the Gemfile that are still satisfied by the lockfile
+ @base = Bundler::SpecSet.new([])
+
+ # locked represents versions in lockfile
+ @locked = locked(%w[foo 1.4.3], %w[bar 2.0.3])
+ end
+
+ it "resolves all gems to latest patch" do
+ # strict is not set, so bar goes up a minor version due to dependency from foo 1.4.5
+ should_conservative_resolve_and_include :patch, [], %w[foo-1.4.5 bar-2.1.1]
+ end
+
+ it "resolves all gems to latest patch strict" do
+ # strict is set, so foo can only go up to 1.4.4 to avoid bar going up a minor version, and bar can go up to 2.0.5
+ should_conservative_resolve_and_include [:patch, :strict], [], %w[foo-1.4.4 bar-2.0.5]
+ end
+
+ it "resolves foo only to latest patch - same dependency case" do
+ @locked = locked(%w[foo 1.3.7], %w[bar 2.0.3])
+ # bar is locked, and the lock holds here because the dependency on bar doesn't change on the matching foo version.
+ should_conservative_resolve_and_include :patch, ["foo"], %w[foo-1.3.8 bar-2.0.3]
+ end
+
+ it "resolves foo only to latest patch - changing dependency not declared case" do
+ # foo is the only gem being requested for update, therefore bar is locked, but bar is NOT
+ # declared as a dependency in the Gemfile. In this case, locks don't apply to _changing_
+ # dependencies and since the dependency of the selected foo gem changes, the latest matching
+ # dependency of "bar", "~> 2.1" -- bar-2.1.1 -- is selected. This is not a bug and follows
+ # the long-standing documented Conservative Updating behavior of bundle install.
+ # https://bundler.io/v1.12/man/bundle-install.1.html#CONSERVATIVE-UPDATING
+ should_conservative_resolve_and_include :patch, ["foo"], %w[foo-1.4.5 bar-2.1.1]
+ end
+
+ it "resolves foo only to latest patch - changing dependency declared case" do
+ # bar is locked AND a declared dependency in the Gemfile, so it will not move, and therefore
+ # foo can only move up to 1.4.4.
+ @base << build_spec("bar", "2.0.3").first
+ should_conservative_resolve_and_include :patch, ["foo"], %w[foo-1.4.4 bar-2.0.3]
+ end
+
+ it "resolves foo only to latest patch strict" do
+ # adding strict helps solve the possibly unexpected behavior of bar changing in the prior test case,
+ # because no versions will be returned for bar ~> 2.1, so the engine falls back to ~> 2.0 (turn on
+ # debugging to see this happen).
+ should_conservative_resolve_and_include [:patch, :strict], ["foo"], %w[foo-1.4.4 bar-2.0.3]
+ end
+
+ it "resolves bar only to latest patch" do
+ # bar is locked, so foo can only go up to 1.4.4
+ should_conservative_resolve_and_include :patch, ["bar"], %w[foo-1.4.3 bar-2.0.5]
+ end
+
+ it "resolves all gems to latest minor" do
+ # strict is not set, so bar goes up a major version due to dependency from foo 1.4.5
+ should_conservative_resolve_and_include :minor, [], %w[foo-1.5.1 bar-3.0.0]
+ end
+
+ it "resolves all gems to latest minor strict" do
+ # strict is set, so foo can only go up to 1.5.0 to avoid bar going up a major version
+ should_conservative_resolve_and_include [:minor, :strict], [], %w[foo-1.5.0 bar-2.1.1]
+ end
+
+ it "resolves all gems to latest major" do
+ should_conservative_resolve_and_include :major, [], %w[foo-2.0.0 bar-3.0.0]
+ end
+
+ it "resolves all gems to latest major strict" do
+ should_conservative_resolve_and_include [:major, :strict], [], %w[foo-2.0.0 bar-3.0.0]
+ end
+
+ # Why would this happen in real life? If bar 2.2 has a bug that the author of foo wants to bypass
+ # by reverting the dependency, the author of foo could release a new gem with an older requirement.
+ context "revert to previous" do
+ before :each do
+ @index = build_index do
+ gem("foo", "1.4.3") { dep "bar", "~> 2.2" }
+ gem("foo", "1.4.4") { dep "bar", "~> 2.1.0" }
+ gem("foo", "1.5.0") { dep "bar", "~> 2.0.0" }
+ gem "bar", %w[2.0.5 2.1.1 2.2.3]
+ end
+ dep "foo"
+
+ # base represents declared dependencies in the Gemfile that are still satisfied by the lockfile
+ @base = Bundler::SpecSet.new([])
+
+ # locked represents versions in lockfile
+ @locked = locked(%w[foo 1.4.3], %w[bar 2.2.3])
+ end
+
+ it "could revert to a previous version level patch" do
+ should_conservative_resolve_and_include :patch, [], %w[foo-1.4.4 bar-2.1.1]
+ end
+
+ it "cannot revert to a previous version in strict mode level patch" do
+ # fall back to the locked resolution since strict means we can't regress either version
+ should_conservative_resolve_and_include [:patch, :strict], [], %w[foo-1.4.3 bar-2.2.3]
+ end
+
+ it "could revert to a previous version level minor" do
+ should_conservative_resolve_and_include :minor, [], %w[foo-1.5.0 bar-2.0.5]
+ end
+
+ it "cannot revert to a previous version in strict mode level minor" do
+ # fall back to the locked resolution since strict means we can't regress either version
+ should_conservative_resolve_and_include [:minor, :strict], [], %w[foo-1.4.3 bar-2.2.3]
+ end
+ end
+ end
+
+ it "handles versions that redundantly depend on themselves" do
+ @index = build_index do
+ gem "rack", "3.0.0"
+
+ gem "standalone_migrations", "7.1.0" do
+ dep "rack", "~> 2.0"
+ end
+
+ gem "standalone_migrations", "2.0.4" do
+ dep "standalone_migrations", ">= 0"
+ end
+
+ gem "standalone_migrations", "1.0.13" do
+ dep "rack", ">= 0"
+ end
+ end
+
+ dep "rack", "~> 3.0"
+ dep "standalone_migrations"
+
+ should_resolve_as %w[rack-3.0.0 standalone_migrations-2.0.4]
+ end
+
+ it "ignores versions that incorrectly depend on themselves" do
+ @index = build_index do
+ gem "rack", "3.0.0"
+
+ gem "standalone_migrations", "7.1.0" do
+ dep "rack", "~> 2.0"
+ end
+
+ gem "standalone_migrations", "2.0.4" do
+ dep "standalone_migrations", ">= 2.0.5"
+ end
+
+ gem "standalone_migrations", "1.0.13" do
+ dep "rack", ">= 0"
+ end
+ end
+
+ dep "rack", "~> 3.0"
+ dep "standalone_migrations"
+
+ should_resolve_as %w[rack-3.0.0 standalone_migrations-1.0.13]
+ end
+end
diff --git a/spec/bundler/resolver/platform_spec.rb b/spec/bundler/resolver/platform_spec.rb
new file mode 100644
index 0000000000..a710dfcb28
--- /dev/null
+++ b/spec/bundler/resolver/platform_spec.rb
@@ -0,0 +1,427 @@
+# frozen_string_literal: true
+
+RSpec.describe "Resolving platform craziness" do
+ describe "with cross-platform gems" do
+ before :each do
+ @index = an_awesome_index
+ end
+
+ it "resolves a simple multi platform gem" do
+ dep "nokogiri"
+ platforms "ruby", "java"
+
+ should_resolve_as %w[nokogiri-1.4.2 nokogiri-1.4.2-java weakling-0.0.3]
+ end
+
+ it "doesn't pull gems that don't exist for the current platform" do
+ dep "nokogiri"
+ platforms "ruby"
+
+ should_resolve_as %w[nokogiri-1.4.2]
+ end
+
+ it "doesn't pull gems when the version is available for all requested platforms" do
+ dep "nokogiri"
+ platforms "mswin32"
+
+ should_resolve_as %w[nokogiri-1.4.2.1-x86-mswin32]
+ end
+ end
+
+ it "resolves multiplatform gems with redundant platforms correctly" do
+ @index = build_index do
+ gem "zookeeper", "1.4.11"
+ gem "zookeeper", "1.4.11", "java" do
+ dep "slyphon-log4j", "= 1.2.15"
+ dep "slyphon-zookeeper_jar", "= 3.3.5"
+ end
+ gem "slyphon-log4j", "1.2.15"
+ gem "slyphon-zookeeper_jar", "3.3.5", "java"
+ end
+
+ dep "zookeeper"
+ platforms "java", "ruby", "universal-java-11"
+
+ should_resolve_as %w[zookeeper-1.4.11 zookeeper-1.4.11-java slyphon-log4j-1.2.15 slyphon-zookeeper_jar-3.3.5-java]
+ end
+
+ it "takes the latest ruby gem, even if an older platform specific version is available" do
+ @index = build_index do
+ gem "foo", "1.0.0"
+ gem "foo", "1.0.0", "x64-mingw32"
+ gem "foo", "1.1.0"
+ end
+ dep "foo"
+ platforms "x64-mingw32"
+
+ should_resolve_as %w[foo-1.1.0]
+ end
+
+ it "takes the ruby version if the platform version is incompatible" do
+ @index = build_index do
+ gem "bar", "1.0.0"
+ gem "foo", "1.0.0"
+ gem "foo", "1.0.0", "x64-mingw32" do
+ dep "bar", "< 1"
+ end
+ end
+ dep "foo"
+ platforms "x64-mingw32"
+
+ should_resolve_as %w[foo-1.0.0]
+ end
+
+ it "prefers the platform specific gem to the ruby version" do
+ @index = build_index do
+ gem "foo", "1.0.0"
+ gem "foo", "1.0.0", "x64-mingw32"
+ end
+ dep "foo"
+ platforms "x64-mingw32"
+
+ should_resolve_as %w[foo-1.0.0-x64-mingw32]
+ end
+
+ describe "on a linux platform", :rubygems => ">= 3.1.0.pre.1" do
+ # Ruby's platform is *-linux => platform's libc is glibc, so not musl
+ # Ruby's platform is *-linux-musl => platform's libc is musl, so not glibc
+ # Gem's platform is *-linux => gem is glibc + maybe musl compatible
+ # Gem's platform is *-linux-musl => gem is musl compatible but not glibc
+
+ it "favors the platform version-specific gem on a version-specifying linux platform" do
+ @index = build_index do
+ gem "foo", "1.0.0"
+ gem "foo", "1.0.0", "x86_64-linux"
+ gem "foo", "1.0.0", "x86_64-linux-musl"
+ end
+ dep "foo"
+ platforms "x86_64-linux-musl"
+
+ should_resolve_as %w[foo-1.0.0-x86_64-linux-musl]
+ end
+
+ it "favors the version-less gem over the version-specific gem on a gnu linux platform" do
+ @index = build_index do
+ gem "foo", "1.0.0"
+ gem "foo", "1.0.0", "x86_64-linux"
+ gem "foo", "1.0.0", "x86_64-linux-musl"
+ end
+ dep "foo"
+ platforms "x86_64-linux"
+
+ should_resolve_as %w[foo-1.0.0-x86_64-linux]
+ end
+
+ it "ignores the platform version-specific gem on a gnu linux platform" do
+ @index = build_index do
+ gem "foo", "1.0.0", "x86_64-linux-musl"
+ end
+ dep "foo"
+ platforms "x86_64-linux"
+
+ should_not_resolve
+ end
+
+ it "falls back to the platform version-less gem on a linux platform with a version" do
+ @index = build_index do
+ gem "foo", "1.0.0"
+ gem "foo", "1.0.0", "x86_64-linux"
+ end
+ dep "foo"
+ platforms "x86_64-linux-musl"
+
+ should_resolve_as %w[foo-1.0.0-x86_64-linux]
+ end
+
+ it "falls back to the ruby platform gem on a gnu linux platform when only a version-specifying gem is available" do
+ @index = build_index do
+ gem "foo", "1.0.0"
+ gem "foo", "1.0.0", "x86_64-linux-musl"
+ end
+ dep "foo"
+ platforms "x86_64-linux"
+
+ should_resolve_as %w[foo-1.0.0]
+ end
+
+ it "falls back to the platform version-less gem on a version-specifying linux platform and no ruby platform gem is available" do
+ @index = build_index do
+ gem "foo", "1.0.0", "x86_64-linux"
+ end
+ dep "foo"
+ platforms "x86_64-linux-musl"
+
+ should_resolve_as %w[foo-1.0.0-x86_64-linux]
+ end
+ end
+
+ context "when the platform specific gem doesn't match the required_ruby_version" do
+ before do
+ @index = build_index do
+ gem "foo", "1.0.0"
+ gem "foo", "1.0.0", "x64-mingw32"
+ gem "foo", "1.1.0"
+ gem "foo", "1.1.0", "x64-mingw32" do |s|
+ s.required_ruby_version = [">= 2.0", "< 2.4"]
+ end
+ gem "Ruby\0", "2.5.1"
+ end
+ dep "Ruby\0", "2.5.1"
+ platforms "x64-mingw32"
+ end
+
+ it "takes the latest ruby gem" do
+ dep "foo"
+
+ should_resolve_as %w[foo-1.1.0]
+ end
+
+ it "takes the latest ruby gem, even if requirement does not match previous versions with the same ruby requirement" do
+ dep "foo", "1.1.0"
+
+ should_resolve_as %w[foo-1.1.0]
+ end
+ end
+
+ it "takes the latest ruby gem with required_ruby_version if the platform specific gem doesn't match the required_ruby_version" do
+ @index = build_index do
+ gem "foo", "1.0.0"
+ gem "foo", "1.0.0", "x64-mingw32"
+ gem "foo", "1.1.0" do |s|
+ s.required_ruby_version = [">= 2.0"]
+ end
+ gem "foo", "1.1.0", "x64-mingw32" do |s|
+ s.required_ruby_version = [">= 2.0", "< 2.4"]
+ end
+ gem "Ruby\0", "2.5.1"
+ end
+ dep "foo"
+ dep "Ruby\0", "2.5.1"
+ platforms "x64-mingw32"
+
+ should_resolve_as %w[foo-1.1.0]
+ end
+
+ it "takes the latest ruby gem if the platform specific gem doesn't match the required_ruby_version with multiple platforms" do
+ @index = build_index do
+ gem "foo", "1.0.0"
+ gem "foo", "1.0.0", "x64-mingw32"
+ gem "foo", "1.1.0" do |s|
+ s.required_ruby_version = [">= 2.0"]
+ end
+ gem "foo", "1.1.0", "x64-mingw32" do |s|
+ s.required_ruby_version = [">= 2.0", "< 2.4"]
+ end
+ gem "Ruby\0", "2.5.1"
+ end
+ dep "foo"
+ dep "Ruby\0", "2.5.1"
+ platforms "x86_64-linux", "x64-mingw32"
+
+ should_resolve_as %w[foo-1.1.0]
+ end
+
+ it "includes gems needed for at least one platform" do
+ @index = build_index do
+ gem "empyrean", "0.1.0"
+ gem "coderay", "1.1.2"
+ gem "method_source", "0.9.0"
+
+ gem "spoon", "0.0.6" do
+ dep "ffi", ">= 0"
+ end
+
+ gem "pry", "0.11.3", "java" do
+ dep "coderay", "~> 1.1.0"
+ dep "method_source", "~> 0.9.0"
+ dep "spoon", "~> 0.0"
+ end
+
+ gem "pry", "0.11.3" do
+ dep "coderay", "~> 1.1.0"
+ dep "method_source", "~> 0.9.0"
+ end
+
+ gem "ffi", "1.9.23", "java"
+ gem "ffi", "1.9.23"
+
+ gem "extra", "1.0.0" do
+ dep "ffi", ">= 0"
+ end
+ end
+
+ dep "empyrean", "0.1.0"
+ dep "pry"
+ dep "extra"
+
+ platforms "ruby", "java"
+
+ should_resolve_as %w[coderay-1.1.2 empyrean-0.1.0 extra-1.0.0 ffi-1.9.23 ffi-1.9.23-java method_source-0.9.0 pry-0.11.3 pry-0.11.3-java spoon-0.0.6]
+ end
+
+ it "includes gems needed for at least one platform even when the platform specific requirement is processed earlier than the generic requirement" do
+ @index = build_index do
+ gem "empyrean", "0.1.0"
+ gem "coderay", "1.1.2"
+ gem "method_source", "0.9.0"
+
+ gem "spoon", "0.0.6" do
+ dep "ffi", ">= 0"
+ end
+
+ gem "pry", "0.11.3", "java" do
+ dep "coderay", "~> 1.1.0"
+ dep "method_source", "~> 0.9.0"
+ dep "spoon", "~> 0.0"
+ end
+
+ gem "pry", "0.11.3" do
+ dep "coderay", "~> 1.1.0"
+ dep "method_source", "~> 0.9.0"
+ end
+
+ gem "ffi", "1.9.23", "java"
+ gem "ffi", "1.9.23"
+
+ gem "extra", "1.0.0" do
+ dep "extra2", ">= 0"
+ end
+
+ gem "extra2", "1.0.0" do
+ dep "extra3", ">= 0"
+ end
+
+ gem "extra3", "1.0.0" do
+ dep "ffi", ">= 0"
+ end
+ end
+
+ dep "empyrean", "0.1.0"
+ dep "pry"
+ dep "extra"
+
+ platforms "ruby", "java"
+
+ should_resolve_as %w[coderay-1.1.2 empyrean-0.1.0 extra-1.0.0 extra2-1.0.0 extra3-1.0.0 ffi-1.9.23 ffi-1.9.23-java method_source-0.9.0 pry-0.11.3 pry-0.11.3-java spoon-0.0.6]
+ end
+
+ it "properly adds platforms when platform requirements come from different dependencies" do
+ @index = build_index do
+ gem "ffi", "1.9.14"
+ gem "ffi", "1.9.14", "universal-mingw32"
+
+ gem "gssapi", "0.1"
+ gem "gssapi", "0.2"
+ gem "gssapi", "0.3"
+ gem "gssapi", "1.2.0" do
+ dep "ffi", ">= 1.0.1"
+ end
+
+ gem "mixlib-shellout", "2.2.6"
+ gem "mixlib-shellout", "2.2.6", "universal-mingw32" do
+ dep "win32-process", "~> 0.8.2"
+ end
+
+ # we need all these versions to get the sorting the same as it would be
+ # pulling from rubygems.org
+ %w[0.8.3 0.8.2 0.8.1 0.8.0].each do |v|
+ gem "win32-process", v do
+ dep "ffi", ">= 1.0.0"
+ end
+ end
+ end
+
+ dep "mixlib-shellout"
+ dep "gssapi"
+
+ platforms "universal-mingw32", "ruby"
+
+ should_resolve_as %w[ffi-1.9.14 ffi-1.9.14-universal-mingw32 gssapi-1.2.0 mixlib-shellout-2.2.6 mixlib-shellout-2.2.6-universal-mingw32 win32-process-0.8.3]
+ end
+
+ describe "with mingw32" do
+ before :each do
+ @index = build_index do
+ platforms "mingw32 mswin32 x64-mingw32 x64-mingw-ucrt" do |platform|
+ gem "thin", "1.2.7", platform
+ end
+ gem "win32-api", "1.5.1", "universal-mingw32"
+ end
+ end
+
+ it "finds mswin gems" do
+ # win32 is hardcoded to get CPU x86 in rubygems
+ platforms "mswin32"
+ dep "thin"
+ should_resolve_as %w[thin-1.2.7-x86-mswin32]
+ end
+
+ it "finds mingw gems" do
+ # mingw is _not_ hardcoded to add CPU x86 in rubygems
+ platforms "x86-mingw32"
+ dep "thin"
+ should_resolve_as %w[thin-1.2.7-mingw32]
+ end
+
+ it "finds x64-mingw32 gems" do
+ platforms "x64-mingw32"
+ dep "thin"
+ should_resolve_as %w[thin-1.2.7-x64-mingw32]
+ end
+
+ it "finds universal-mingw gems on x86-mingw" do
+ platform "x86-mingw32"
+ dep "win32-api"
+ should_resolve_as %w[win32-api-1.5.1-universal-mingw32]
+ end
+
+ it "finds universal-mingw gems on x64-mingw" do
+ platform "x64-mingw32"
+ dep "win32-api"
+ should_resolve_as %w[win32-api-1.5.1-universal-mingw32]
+ end
+
+ if Gem.rubygems_version >= Gem::Version.new("3.2.28")
+ it "finds x64-mingw-ucrt gems" do
+ platforms "x64-mingw-ucrt"
+ dep "thin"
+ should_resolve_as %w[thin-1.2.7-x64-mingw-ucrt]
+ end
+ end
+
+ if Gem.rubygems_version >= Gem::Version.new("3.3.18")
+ it "finds universal-mingw gems on x64-mingw-ucrt" do
+ platform "x64-mingw-ucrt"
+ dep "win32-api"
+ should_resolve_as %w[win32-api-1.5.1-universal-mingw32]
+ end
+ end
+ end
+
+ describe "with conflicting cases" do
+ before :each do
+ @index = build_index do
+ gem "foo", "1.0.0" do
+ dep "bar", ">= 0"
+ end
+
+ gem "bar", "1.0.0" do
+ dep "baz", "~> 1.0.0"
+ end
+
+ gem "bar", "1.0.0", "java" do
+ dep "baz", " ~> 1.1.0"
+ end
+
+ gem "baz", %w[1.0.0 1.1.0 1.2.0]
+ end
+ end
+
+ it "takes the ruby version as fallback" do
+ platforms "ruby", "java"
+ dep "foo"
+
+ should_resolve_as %w[bar-1.0.0 baz-1.0.0 foo-1.0.0]
+ end
+ end
+end
diff --git a/spec/bundler/runtime/executable_spec.rb b/spec/bundler/runtime/executable_spec.rb
new file mode 100644
index 0000000000..a11f547648
--- /dev/null
+++ b/spec/bundler/runtime/executable_spec.rb
@@ -0,0 +1,169 @@
+# frozen_string_literal: true
+
+RSpec.describe "Running bin/* commands" do
+ before :each do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ it "runs the bundled command when in the bundle" do
+ bundle "binstubs rack"
+
+ build_gem "rack", "2.0", :to_system => true do |s|
+ s.executables = "rackup"
+ end
+
+ gembin "rackup"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "allows the location of the gem stubs to be specified" do
+ bundle "binstubs rack", :path => "gbin"
+
+ expect(bundled_app("bin")).not_to exist
+ expect(bundled_app("gbin/rackup")).to exist
+
+ gembin bundled_app("gbin/rackup")
+ expect(out).to eq("1.0.0")
+ end
+
+ it "allows absolute paths as a specification of where to install bin stubs" do
+ bundle "binstubs rack", :path => tmp("bin")
+
+ gembin tmp("bin/rackup")
+ expect(out).to eq("1.0.0")
+ end
+
+ it "uses the default ruby install name when shebang is not specified" do
+ bundle "binstubs rack"
+ expect(File.readlines(bundled_app("bin/rackup")).first).to eq("#!/usr/bin/env #{RbConfig::CONFIG["ruby_install_name"]}\n")
+ end
+
+ it "allows the name of the shebang executable to be specified" do
+ bundle "binstubs rack", :shebang => "ruby-foo"
+ expect(File.readlines(bundled_app("bin/rackup")).first).to eq("#!/usr/bin/env ruby-foo\n")
+ end
+
+ it "runs the bundled command when out of the bundle" do
+ bundle "binstubs rack"
+
+ build_gem "rack", "2.0", :to_system => true do |s|
+ s.executables = "rackup"
+ end
+
+ gembin "rackup", :dir => tmp
+ expect(out).to eq("1.0.0")
+ end
+
+ it "works with gems in path" do
+ build_lib "rack", :path => lib_path("rack") do |s|
+ s.executables = "rackup"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :path => "#{lib_path("rack")}"
+ G
+
+ bundle "binstubs rack"
+
+ build_gem "rack", "2.0", :to_system => true do |s|
+ s.executables = "rackup"
+ end
+
+ gembin "rackup"
+ expect(out).to eq("1.0")
+ end
+
+ it "creates a bundle binstub" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bundler"
+ G
+
+ bundle "binstubs bundler"
+
+ expect(bundled_app("bin/bundle")).to exist
+ end
+
+ it "does not generate bin stubs if the option was not specified" do
+ bundle "install"
+
+ expect(bundled_app("bin/rackup")).not_to exist
+ end
+
+ it "allows you to stop installing binstubs", :bundler => "< 3" do
+ skip "delete permission error" if Gem.win_platform?
+
+ bundle "install --binstubs bin/"
+ bundled_app("bin/rackup").rmtree
+ bundle "install --binstubs \"\""
+
+ expect(bundled_app("bin/rackup")).not_to exist
+
+ bundle "config bin"
+ expect(out).to include("You have not configured a value for `bin`")
+ end
+
+ it "remembers that the option was specified", :bundler => "< 3" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+ G
+
+ bundle :install, :binstubs => "bin"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+ gem "rack"
+ G
+
+ bundle "install"
+
+ expect(bundled_app("bin/rackup")).to exist
+ end
+
+ it "rewrites bins on binstubs (to maintain backwards compatibility)" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ create_file("bin/rackup", "OMG")
+
+ bundle "binstubs rack"
+
+ expect(bundled_app("bin/rackup").read).to_not eq("OMG")
+ end
+
+ it "use BUNDLE_GEMFILE gemfile for binstub" do
+ # context with bin/bundler w/ default Gemfile
+ bundle "binstubs bundler"
+
+ # generate other Gemfile with executable gem
+ build_repo2 do
+ build_gem("bindir") {|s| s.executables = "foo" }
+ end
+
+ create_file("OtherGemfile", <<-G)
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'bindir'
+ G
+
+ # generate binstub for executable from non default Gemfile (other then bin/bundler version)
+ ENV["BUNDLE_GEMFILE"] = "OtherGemfile"
+ bundle "install"
+ bundle "binstubs bindir"
+
+ # remove user settings
+ ENV["BUNDLE_GEMFILE"] = nil
+
+ # run binstub for non default Gemfile
+ gembin "foo"
+
+ expect(out).to eq("1.0")
+ end
+end
diff --git a/spec/bundler/runtime/gem_tasks_spec.rb b/spec/bundler/runtime/gem_tasks_spec.rb
new file mode 100644
index 0000000000..b89fdf2cb1
--- /dev/null
+++ b/spec/bundler/runtime/gem_tasks_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+RSpec.describe "require 'bundler/gem_tasks'" do
+ before :each do
+ bundled_app("foo.gemspec").open("w") do |f|
+ f.write <<-GEMSPEC
+ Gem::Specification.new do |s|
+ s.name = "foo"
+ s.version = "1.0"
+ s.summary = "dummy"
+ s.author = "Perry Mason"
+ end
+ GEMSPEC
+ end
+
+ bundled_app("Rakefile").open("w") do |f|
+ f.write <<-RAKEFILE
+ require "bundler/gem_tasks"
+ RAKEFILE
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rake"
+ G
+ end
+
+ it "includes the relevant tasks" do
+ with_gem_path_as(base_system_gem_path.to_s) do
+ sys_exec "#{rake} -T", :env => { "GEM_HOME" => system_gem_path.to_s }
+ end
+
+ expect(err).to be_empty
+ expected_tasks = [
+ "rake build",
+ "rake clean",
+ "rake clobber",
+ "rake install",
+ "rake release[remote]",
+ ]
+ tasks = out.lines.to_a.map {|s| s.split("#").first.strip }
+ expect(tasks & expected_tasks).to eq(expected_tasks)
+ end
+
+ it "defines a working `rake install` task", :ruby_repo do
+ with_gem_path_as(base_system_gem_path.to_s) do
+ sys_exec "#{rake} install", :env => { "GEM_HOME" => system_gem_path.to_s }
+ end
+
+ expect(err).to be_empty
+
+ bundle "exec rake install"
+
+ expect(err).to be_empty
+ end
+
+ context "rake build when path has spaces", :ruby_repo do
+ before do
+ spaced_bundled_app = tmp.join("bundled app")
+ FileUtils.cp_r bundled_app, spaced_bundled_app
+ bundle "exec rake build", :dir => spaced_bundled_app
+ end
+
+ it "still runs successfully" do
+ expect(err).to be_empty
+ end
+ end
+
+ context "rake build when path has brackets", :ruby_repo do
+ before do
+ bracketed_bundled_app = tmp.join("bundled[app")
+ FileUtils.cp_r bundled_app, bracketed_bundled_app
+ bundle "exec rake build", :dir => bracketed_bundled_app
+ end
+
+ it "still runs successfully" do
+ expect(err).to be_empty
+ end
+ end
+
+ context "bundle path configured locally" do
+ before do
+ bundle "config set path vendor/bundle"
+ end
+
+ it "works", :ruby_repo do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "rake"
+ G
+
+ bundle "exec rake -T"
+
+ expect(err).to be_empty
+ end
+ end
+
+ it "adds 'pkg' to rake/clean's CLOBBER" do
+ with_gem_path_as(base_system_gem_path.to_s) do
+ sys_exec %(#{rake} -e 'load "Rakefile"; puts CLOBBER.inspect'), :env => { "GEM_HOME" => system_gem_path.to_s }
+ end
+ expect(out).to eq '["pkg"]'
+ end
+end
diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb
new file mode 100644
index 0000000000..29ef036828
--- /dev/null
+++ b/spec/bundler/runtime/inline_spec.rb
@@ -0,0 +1,626 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundler/inline#gemfile" do
+ def script(code, options = {})
+ requires = ["#{entrypoint}/inline"]
+ requires.unshift "#{spec_dir}/support/artifice/" + options.delete(:artifice) if options.key?(:artifice)
+ requires = requires.map {|r| "require '#{r}'" }.join("\n")
+ ruby("#{requires}\n\n" + code, options)
+ end
+
+ before :each do
+ build_lib "one", "1.0.0" do |s|
+ s.write "lib/baz.rb", "puts 'baz'"
+ s.write "lib/qux.rb", "puts 'qux'"
+ end
+
+ build_lib "two", "1.0.0" do |s|
+ s.write "lib/two.rb", "puts 'two'"
+ s.add_dependency "three", "= 1.0.0"
+ end
+
+ build_lib "three", "1.0.0" do |s|
+ s.write "lib/three.rb", "puts 'three'"
+ s.add_dependency "seven", "= 1.0.0"
+ end
+
+ build_lib "four", "1.0.0" do |s|
+ s.write "lib/four.rb", "puts 'four'"
+ end
+
+ build_lib "five", "1.0.0", :no_default => true do |s|
+ s.write "lib/mofive.rb", "puts 'five'"
+ end
+
+ build_lib "six", "1.0.0" do |s|
+ s.write "lib/six.rb", "puts 'six'"
+ end
+
+ build_lib "seven", "1.0.0" do |s|
+ s.write "lib/seven.rb", "puts 'seven'"
+ end
+
+ build_lib "eight", "1.0.0" do |s|
+ s.write "lib/eight.rb", "puts 'eight'"
+ end
+ end
+
+ it "requires the gems" do
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "two"
+ end
+ end
+ RUBY
+
+ expect(out).to eq("two")
+
+ script <<-RUBY, :raise_on_error => false
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "eleven"
+ end
+ end
+
+ puts "success"
+ RUBY
+
+ expect(err).to include "Could not find gem 'eleven'"
+ expect(out).not_to include "success"
+
+ script <<-RUBY
+ gemfile(true) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+ RUBY
+
+ expect(out).to include("Rack's post install message")
+
+ script <<-RUBY, :artifice => "endpoint"
+ gemfile(true) do
+ source "https://notaserver.com"
+ gem "activesupport", :require => true
+ end
+ RUBY
+
+ expect(out).to include("Installing activesupport")
+ err_lines = err.split("\n")
+ err_lines.reject! {|line| line =~ /\.rb:\d+: warning: / } unless RUBY_VERSION < "2.7"
+ expect(err_lines).to be_empty
+ end
+
+ it "lets me use my own ui object" do
+ script <<-RUBY, :artifice => "endpoint"
+ require '#{entrypoint}'
+ class MyBundlerUI < Bundler::UI::Shell
+ def confirm(msg, newline = nil)
+ puts "CONFIRMED!"
+ end
+ end
+ my_ui = MyBundlerUI.new
+ my_ui.level = "confirm"
+ gemfile(true, :ui => my_ui) do
+ source "https://notaserver.com"
+ gem "activesupport", :require => true
+ end
+ RUBY
+
+ expect(out).to eq("CONFIRMED!\nCONFIRMED!")
+ end
+
+ it "has an option for quiet installation" do
+ script <<-RUBY, :artifice => "endpoint"
+ require '#{entrypoint}/inline'
+
+ gemfile(true, :quiet => true) do
+ source "https://notaserver.com"
+ gem "activesupport", :require => true
+ end
+ RUBY
+
+ expect(out).to be_empty
+ end
+
+ it "raises an exception if passed unknown arguments" do
+ script <<-RUBY, :raise_on_error => false
+ gemfile(true, :arglebargle => true) do
+ path "#{lib_path}"
+ gem "two"
+ end
+
+ puts "success"
+ RUBY
+ expect(err).to include "Unknown options: arglebargle"
+ expect(out).not_to include "success"
+ end
+
+ it "does not mutate the option argument" do
+ script <<-RUBY
+ require '#{entrypoint}'
+ options = { :ui => Bundler::UI::Shell.new }
+ gemfile(false, options) do
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "two"
+ end
+ end
+ puts "OKAY" if options.key?(:ui)
+ RUBY
+
+ expect(out).to match("OKAY")
+ end
+
+ it "installs quietly if necessary when the install option is not set" do
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+
+ puts RACK
+ RUBY
+
+ expect(out).to eq("1.0.0")
+ expect(err).to be_empty
+ end
+
+ it "installs subdependencies quietly if necessary when the install option is not set" do
+ build_repo4 do
+ build_gem "rack" do |s|
+ s.add_dependency "rackdep"
+ end
+
+ build_gem "rackdep", "1.0.0"
+ end
+
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo4)}"
+ gem "rack"
+ end
+
+ require "rackdep"
+ puts RACKDEP
+ RUBY
+
+ expect(out).to eq("1.0.0")
+ expect(err).to be_empty
+ end
+
+ it "installs subdependencies quietly if necessary when the install option is not set, and multiple sources used" do
+ build_repo4 do
+ build_gem "rack" do |s|
+ s.add_dependency "rackdep"
+ end
+
+ build_gem "rackdep", "1.0.0"
+ end
+
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ source "#{file_uri_for(gem_repo4)}" do
+ gem "rack"
+ end
+ end
+
+ require "rackdep"
+ puts RACKDEP
+ RUBY
+
+ expect(out).to eq("1.0.0")
+ expect(err).to be_empty
+ end
+
+ it "installs quietly from git if necessary when the install option is not set" do
+ build_git "foo", "1.0.0"
+ baz_ref = build_git("baz", "2.0.0").ref_for("HEAD")
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => #{lib_path("foo-1.0.0").to_s.dump}
+ gem "baz", :git => #{lib_path("baz-2.0.0").to_s.dump}, :ref => #{baz_ref.dump}
+ end
+
+ puts FOO
+ puts BAZ
+ RUBY
+
+ expect(out).to eq("1.0.0\n2.0.0")
+ expect(err).to be_empty
+ end
+
+ it "allows calling gemfile twice" do
+ script <<-RUBY
+ gemfile do
+ path "#{lib_path}" do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "two"
+ end
+ end
+
+ gemfile do
+ path "#{lib_path}" do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "four"
+ end
+ end
+ RUBY
+
+ expect(out).to eq("two\nfour")
+ expect(err).to be_empty
+ end
+
+ it "doesn't reinstall already installed gems" do
+ system_gems "rack-1.0.0"
+
+ script <<-RUBY
+ require '#{entrypoint}'
+ ui = Bundler::UI::Shell.new
+ ui.level = "confirm"
+
+ gemfile(true, ui: ui) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+ gem "rack"
+ end
+ RUBY
+
+ expect(out).to include("Installing activesupport")
+ expect(out).not_to include("Installing rack")
+ expect(err).to be_empty
+ end
+
+ it "installs gems in later gemfile calls" do
+ system_gems "rack-1.0.0"
+
+ script <<-RUBY
+ require '#{entrypoint}'
+ ui = Bundler::UI::Shell.new
+ ui.level = "confirm"
+ gemfile(true, ui: ui) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+
+ gemfile(true, ui: ui) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+ end
+ RUBY
+
+ expect(out).to include("Installing activesupport")
+ expect(out).not_to include("Installing rack")
+ expect(err).to be_empty
+ end
+
+ it "doesn't reinstall already installed gems in later gemfile calls" do
+ system_gems "rack-1.0.0"
+
+ script <<-RUBY
+ require '#{entrypoint}'
+ ui = Bundler::UI::Shell.new
+ ui.level = "confirm"
+ gemfile(true, ui: ui) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+ end
+
+ gemfile(true, ui: ui) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+ RUBY
+
+ expect(out).to include("Installing activesupport")
+ expect(out).not_to include("Installing rack")
+ expect(err).to be_empty
+ end
+
+ it "installs gems with native extensions in later gemfile calls" do
+ system_gems "rack-1.0.0"
+
+ build_git "foo" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("lib", __dir__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/foo.rb", "w") do |f|
+ f.puts "FOO = 'YES'"
+ end
+ end
+ RUBY
+ end
+
+ script <<-RUBY
+ require '#{entrypoint}'
+ ui = Bundler::UI::Shell.new
+ ui.level = "confirm"
+ gemfile(true, ui: ui) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+
+ gemfile(true, ui: ui) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ end
+
+ require 'foo'
+ puts FOO
+ puts $:.grep(/ext/)
+ RUBY
+
+ expect(out).to include("YES")
+ expect(out).to include(Pathname.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s)
+ expect(err).to be_empty
+ end
+
+ it "installs inline gems when a Gemfile.lock is present" do
+ gemfile <<-G
+ source "https://notaserver.com"
+ gem "rake"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ rake (11.3.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rake
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+
+ puts RACK
+ RUBY
+
+ expect(err).to be_empty
+ end
+
+ it "does not leak Gemfile.lock versions to the installation output" do
+ gemfile <<-G
+ source "https://notaserver.com"
+ gem "rake"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ rake (11.3.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rake
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ script <<-RUBY
+ gemfile(true) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rake", "~> 13.0"
+ end
+ RUBY
+
+ expect(out).to include("Installing rake 13.0")
+ expect(out).not_to include("was 11.3.0")
+ expect(err).to be_empty
+ end
+
+ it "installs inline gems when frozen is set" do
+ script <<-RUBY, :env => { "BUNDLE_FROZEN" => "true" }
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+
+ puts RACK
+ RUBY
+
+ expect(last_command.stderr).to be_empty
+ end
+
+ it "installs inline gems when deployment is set" do
+ script <<-RUBY, :env => { "BUNDLE_DEPLOYMENT" => "true" }
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+
+ puts RACK
+ RUBY
+
+ expect(last_command.stderr).to be_empty
+ end
+
+ it "installs inline gems when BUNDLE_GEMFILE is set to an empty string" do
+ ENV["BUNDLE_GEMFILE"] = ""
+
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+
+ puts RACK
+ RUBY
+
+ expect(err).to be_empty
+ end
+
+ it "installs inline gems when BUNDLE_BIN is set" do
+ ENV["BUNDLE_BIN"] = "/usr/local/bundle/bin"
+
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack" # has the rackup executable
+ end
+
+ puts RACK
+ RUBY
+ expect(last_command).to be_success
+ expect(out).to eq "1.0.0"
+ end
+
+ context "when BUNDLE_PATH is set" do
+ it "installs inline gems to the system path regardless" do
+ script <<-RUBY, :env => { "BUNDLE_PATH" => "./vendor/inline" }
+ gemfile(true) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+ RUBY
+ expect(last_command).to be_success
+ expect(system_gem_path("gems/rack-1.0.0")).to exist
+ end
+ end
+
+ it "skips platform warnings" do
+ bundle "config set --local force_ruby_platform true"
+
+ script <<-RUBY
+ gemfile(true) do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", platform: :jruby
+ end
+ RUBY
+
+ expect(err).to be_empty
+ end
+
+ it "still installs if the application has `bundle package` no_install config set" do
+ bundle "config set --local no_install true"
+
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+ RUBY
+
+ expect(last_command).to be_success
+ expect(system_gem_path("gems/rack-1.0.0")).to exist
+ end
+
+ it "preserves previous BUNDLE_GEMFILE value" do
+ ENV["BUNDLE_GEMFILE"] = ""
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+
+ puts "BUNDLE_GEMFILE is empty" if ENV["BUNDLE_GEMFILE"].empty?
+ system("#{Gem.ruby} -w -e '42'") # this should see original value of BUNDLE_GEMFILE
+ exit $?.exitstatus
+ RUBY
+
+ expect(last_command).to be_success
+ expect(out).to include("BUNDLE_GEMFILE is empty")
+ end
+
+ it "resets BUNDLE_GEMFILE to the empty string if it wasn't set previously" do
+ ENV["BUNDLE_GEMFILE"] = nil
+ script <<-RUBY
+ gemfile do
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ end
+
+ puts "BUNDLE_GEMFILE is empty" if ENV["BUNDLE_GEMFILE"].empty?
+ system("#{Gem.ruby} -w -e '42'") # this should see original value of BUNDLE_GEMFILE
+ exit $?.exitstatus
+ RUBY
+
+ expect(last_command).to be_success
+ expect(out).to include("BUNDLE_GEMFILE is empty")
+ end
+
+ it "does not error out if library requires optional dependencies" do
+ Dir.mkdir tmp("path_without_gemfile")
+
+ foo_code = <<~RUBY
+ begin
+ gem "bar"
+ rescue LoadError
+ end
+
+ puts "WIN"
+ RUBY
+
+ build_lib "foo", "1.0.0" do |s|
+ s.write "lib/foo.rb", foo_code
+ end
+
+ script <<-RUBY, :dir => tmp("path_without_gemfile")
+ gemfile do
+ source "#{file_uri_for(gem_repo2)}"
+ path "#{lib_path}" do
+ gem "foo", require: false
+ end
+ end
+
+ require "foo"
+ RUBY
+
+ expect(out).to eq("WIN")
+ expect(err).to be_empty
+ end
+
+ it "when requiring fileutils after does not show redefinition warnings", :realworld do
+ dependency_installer_loads_fileutils = ruby "require 'rubygems/dependency_installer'; puts $LOADED_FEATURES.grep(/fileutils/)", :raise_on_error => false
+ skip "does not work if rubygems/dependency_installer loads fileutils, which happens until rubygems 3.2.0" unless dependency_installer_loads_fileutils.empty?
+
+ skip "pathname does not install cleanly on this ruby" if RUBY_VERSION < "2.7.0"
+
+ Dir.mkdir tmp("path_without_gemfile")
+
+ default_fileutils_version = ruby "gem 'fileutils', '< 999999'; require 'fileutils'; puts FileUtils::VERSION", :raise_on_error => false
+ skip "fileutils isn't a default gem" if default_fileutils_version.empty?
+
+ realworld_system_gems "fileutils --version 1.4.1"
+
+ realworld_system_gems "pathname --version 0.2.0"
+
+ realworld_system_gems "timeout uri" # this spec uses net/http which requires these default gems
+
+ # on prerelease rubies, a required_rubygems_version constraint is added by RubyGems to the resolution, causing Molinillo to load the `set` gem
+ realworld_system_gems "set --version 1.0.3" if Gem.ruby_version.prerelease?
+
+ script <<-RUBY, :dir => tmp("path_without_gemfile"), :env => { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s }
+ require "bundler/inline"
+
+ gemfile(true) do
+ source "#{file_uri_for(gem_repo2)}"
+ end
+
+ require "fileutils"
+ RUBY
+
+ expect(err).to eq("The Gemfile specifies no dependencies")
+ end
+end
diff --git a/spec/bundler/runtime/load_spec.rb b/spec/bundler/runtime/load_spec.rb
new file mode 100644
index 0000000000..96a22a46cc
--- /dev/null
+++ b/spec/bundler/runtime/load_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+RSpec.describe "Bundler.load" do
+ describe "with a gemfile" do
+ before(:each) do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ allow(Bundler::SharedHelpers).to receive(:pwd).and_return(bundled_app)
+ end
+
+ it "provides a list of the env dependencies" do
+ expect(Bundler.load.dependencies).to have_dep("rack", ">= 0")
+ end
+
+ it "provides a list of the resolved gems" do
+ expect(Bundler.load.gems).to have_gem("rack-1.0.0", "bundler-#{Bundler::VERSION}")
+ end
+
+ it "ignores blank BUNDLE_GEMFILEs" do
+ expect do
+ ENV["BUNDLE_GEMFILE"] = ""
+ Bundler.load
+ end.not_to raise_error
+ end
+ end
+
+ describe "with a gems.rb file" do
+ before(:each) do
+ create_file "gems.rb", <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ bundle :install
+ allow(Bundler::SharedHelpers).to receive(:pwd).and_return(bundled_app)
+ end
+
+ it "provides a list of the env dependencies" do
+ expect(Bundler.load.dependencies).to have_dep("rack", ">= 0")
+ end
+
+ it "provides a list of the resolved gems" do
+ expect(Bundler.load.gems).to have_gem("rack-1.0.0", "bundler-#{Bundler::VERSION}")
+ end
+ end
+
+ describe "without a gemfile" do
+ it "raises an exception if the default gemfile is not found" do
+ expect do
+ Bundler.load
+ end.to raise_error(Bundler::GemfileNotFound, /could not locate gemfile/i)
+ end
+
+ it "raises an exception if a specified gemfile is not found" do
+ expect do
+ ENV["BUNDLE_GEMFILE"] = "omg.rb"
+ Bundler.load
+ end.to raise_error(Bundler::GemfileNotFound, /omg\.rb/)
+ end
+
+ it "does not find a Gemfile above the testing directory" do
+ bundler_gemfile = Pathname.new(__dir__).join("../../Gemfile")
+ unless File.exist?(bundler_gemfile)
+ FileUtils.touch(bundler_gemfile)
+ @remove_bundler_gemfile = true
+ end
+ begin
+ expect { Bundler.load }.to raise_error(Bundler::GemfileNotFound)
+ ensure
+ bundler_gemfile.rmtree if @remove_bundler_gemfile
+ end
+ end
+ end
+
+ describe "when called twice" do
+ it "doesn't try to load the runtime twice" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ G
+
+ ruby <<-RUBY
+ require "#{entrypoint}"
+ Bundler.setup :default
+ Bundler.require :default
+ puts RACK
+ begin
+ require "activesupport"
+ rescue LoadError
+ puts "no activesupport"
+ end
+ RUBY
+
+ expect(out.split("\n")).to eq(["1.0.0", "no activesupport"])
+ end
+ end
+
+ describe "not hurting brittle rubygems" do
+ it "does not inject #source into the generated YAML of the gem specs" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activerecord"
+ G
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
+ Bundler.load.specs.each do |spec|
+ expect(spec.to_yaml).not_to match(/^\s+source:/)
+ expect(spec.to_yaml).not_to match(/^\s+groups:/)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/runtime/platform_spec.rb b/spec/bundler/runtime/platform_spec.rb
new file mode 100644
index 0000000000..a9933f90e9
--- /dev/null
+++ b/spec/bundler/runtime/platform_spec.rb
@@ -0,0 +1,464 @@
+# frozen_string_literal: true
+
+RSpec.describe "Bundler.setup with multi platform stuff" do
+ it "raises a friendly error when gems are missing locally" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (1.0)
+
+ PLATFORMS
+ #{local_tag}
+
+ DEPENDENCIES
+ rack
+ G
+
+ ruby <<-R
+ begin
+ require '#{entrypoint}'
+ Bundler.ui.silence { Bundler.setup }
+ rescue Bundler::GemNotFound => e
+ puts "WIN"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "will resolve correctly on the current platform when the lockfile was targeted for a different one" do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ nokogiri (1.4.2-java)
+ weakling (= 0.0.3)
+ weakling (0.0.3)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ nokogiri
+ G
+
+ simulate_platform "x86-darwin-10"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "nokogiri"
+ G
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ end
+
+ it "will keep both platforms when both ruby and a specific ruby platform are locked and the bundle is unlocked" do
+ build_repo4 do
+ build_gem "nokogiri", "1.11.1" do |s|
+ s.add_dependency "mini_portile2", "~> 2.5.0"
+ s.add_dependency "racc", "~> 1.5.2"
+ end
+
+ build_gem "nokogiri", "1.11.1" do |s|
+ s.platform = Bundler.local_platform
+ s.add_dependency "racc", "~> 1.4"
+ end
+
+ build_gem "mini_portile2", "2.5.0"
+ build_gem "racc", "1.5.2"
+ end
+
+ good_lockfile = <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ mini_portile2 (2.5.0)
+ nokogiri (1.11.1)
+ mini_portile2 (~> 2.5.0)
+ racc (~> 1.5.2)
+ nokogiri (1.11.1-#{Bundler.local_platform})
+ racc (~> 1.4)
+ racc (1.5.2)
+
+ PLATFORMS
+ #{lockfile_platforms("ruby")}
+
+ DEPENDENCIES
+ nokogiri (~> 1.11)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "nokogiri", "~> 1.11"
+ G
+
+ lockfile good_lockfile
+
+ bundle "update nokogiri"
+
+ expect(lockfile).to eq(good_lockfile)
+ end
+
+ it "will not try to install platform specific gems when they don't match the current ruby if locked only to ruby" do
+ build_repo4 do
+ build_gem "nokogiri", "1.11.1"
+
+ build_gem "nokogiri", "1.11.1" do |s|
+ s.platform = Bundler.local_platform
+ s.required_ruby_version = "< #{Gem.ruby_version}"
+ end
+ end
+
+ gemfile <<-G
+ source "https://gems.repo4"
+ gem "nokogiri"
+ G
+
+ lockfile <<~L
+ GEM
+ remote: https://gems.repo4/
+ specs:
+ nokogiri (1.11.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+
+ expect(out).to include("Fetching nokogiri 1.11.1")
+ expect(the_bundle).to include_gems "nokogiri 1.11.1"
+ expect(the_bundle).not_to include_gems "nokogiri 1.11.1 #{Bundler.local_platform}"
+ end
+
+ it "will use the java platform if both generic java and generic ruby platforms are locked", :jruby_only do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "nokogiri"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ nokogiri (1.4.2)
+ nokogiri (1.4.2-java)
+ weakling (>= 0.0.3)
+ weakling (0.0.3)
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ bundle "install"
+
+ expect(out).to include("Fetching nokogiri 1.4.2 (java)")
+ expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA"
+ end
+
+ it "will add the resolve for the current platform" do
+ lockfile <<-G
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ nokogiri (1.4.2-java)
+ weakling (= 0.0.3)
+ weakling (0.0.3)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ nokogiri
+ G
+
+ simulate_platform "x86-darwin-100"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "nokogiri"
+ gem "platform_specific"
+ G
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 x86-darwin-100"
+ end
+
+ it "allows specifying only-ruby-platform on jruby", :jruby_only do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "nokogiri"
+ gem "platform_specific"
+ G
+
+ bundle "config set force_ruby_platform true"
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY"
+ end
+
+ it "allows specifying only-ruby-platform" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "nokogiri"
+ gem "platform_specific"
+ G
+
+ bundle "config set force_ruby_platform true"
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY"
+ end
+
+ it "allows specifying only-ruby-platform even if the lockfile is locked to a specific compatible platform" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "nokogiri"
+ gem "platform_specific"
+ G
+
+ bundle "config set force_ruby_platform true"
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY"
+ end
+
+ it "doesn't pull platform specific gems on truffleruby", :truffleruby_only do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 RUBY"
+ end
+
+ it "doesn't pull platform specific gems on truffleruby (except when whitelisted) even if lockfile was generated with an older version that declared RUBY as platform", :truffleruby_only do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ platform_specific (1.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 RUBY"
+
+ simulate_platform "x86_64-linux" do
+ build_repo4 do
+ build_gem "libv8"
+
+ build_gem "libv8" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "libv8"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo4)}/
+ specs:
+ libv8 (1.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ libv8
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "libv8 1.0 x86_64-linux"
+ end
+ end
+
+ it "doesn't pull platform specific gems on truffleruby, even if lockfile only includes those", :truffleruby_only do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ platform_specific (1.0-x86-darwin-100)
+
+ PLATFORMS
+ x86-darwin-100
+
+ DEPENDENCIES
+ platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 RUBY"
+ end
+
+ it "pulls platform specific gems correctly on musl" do
+ build_repo4 do
+ build_gem "nokogiri", "1.13.8" do |s|
+ s.platform = "aarch64-linux"
+ end
+ end
+
+ simulate_platform "aarch64-linux-musl" do
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, :verbose => true
+ source "https://gems.repo4"
+ gem "nokogiri"
+ G
+ end
+
+ expect(out).to include("Fetching nokogiri 1.13.8 (aarch64-linux)")
+ end
+
+ it "allows specifying only-ruby-platform on windows with dependency platforms" do
+ simulate_windows do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "nokogiri", :platforms => [:mingw, :mswin, :x64_mingw, :jruby]
+ gem "platform_specific"
+ G
+
+ bundle "config set force_ruby_platform true"
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 RUBY"
+ expect(the_bundle).to not_include_gems "nokogiri"
+ end
+ end
+
+ it "allows specifying only-ruby-platform on windows with gemspec dependency" do
+ build_lib("foo", "1.0", :path => bundled_app) do |s|
+ s.add_dependency "rack"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gemspec
+ G
+ bundle :lock
+
+ simulate_windows do
+ bundle "config set force_ruby_platform true"
+ bundle "install"
+
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ it "recovers when the lockfile is missing a platform-specific gem" do
+ build_repo2 do
+ build_gem "requires_platform_specific" do |s|
+ s.add_dependency "platform_specific"
+ end
+ end
+ simulate_windows x64_mingw32 do
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ platform_specific (1.0-x86-mingw32)
+ requires_platform_specific (1.0)
+ platform_specific
+
+ PLATFORMS
+ x64-mingw32
+ x86-mingw32
+
+ DEPENDENCIES
+ requires_platform_specific
+ L
+
+ install_gemfile <<-G, :verbose => true
+ source "#{file_uri_for(gem_repo2)}"
+ gem "requires_platform_specific"
+ G
+
+ expect(out).to include("lockfile does not have all gems needed for the current platform")
+ expect(the_bundle).to include_gem "platform_specific 1.0 x64-mingw32"
+ end
+ end
+
+ %w[x86-mswin32 x64-mswin64 x86-mingw32 x64-mingw32 x64-mingw-ucrt].each do |arch|
+ it "allows specifying platform windows on #{arch} arch" do
+ platform = send(arch.tr("-", "_"))
+
+ simulate_windows platform do
+ lockfile <<-L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ platform_specific (1.0-#{platform})
+ requires_platform_specific (1.0)
+ platform_specific
+
+ PLATFORMS
+ #{platform}
+
+ DEPENDENCIES
+ requires_platform_specific
+ L
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "platform_specific", :platforms => [:windows]
+ G
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 #{platform}"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/runtime/require_spec.rb b/spec/bundler/runtime/require_spec.rb
new file mode 100644
index 0000000000..e59fa564f6
--- /dev/null
+++ b/spec/bundler/runtime/require_spec.rb
@@ -0,0 +1,465 @@
+# frozen_string_literal: true
+
+RSpec.describe "Bundler.require" do
+ before :each do
+ build_lib "one", "1.0.0" do |s|
+ s.write "lib/baz.rb", "puts 'baz'"
+ s.write "lib/qux.rb", "puts 'qux'"
+ end
+
+ build_lib "two", "1.0.0" do |s|
+ s.write "lib/two.rb", "puts 'two'"
+ s.add_dependency "three", "= 1.0.0"
+ end
+
+ build_lib "three", "1.0.0" do |s|
+ s.write "lib/three.rb", "puts 'three'"
+ s.add_dependency "seven", "= 1.0.0"
+ end
+
+ build_lib "four", "1.0.0" do |s|
+ s.write "lib/four.rb", "puts 'four'"
+ end
+
+ build_lib "five", "1.0.0", :no_default => true do |s|
+ s.write "lib/mofive.rb", "puts 'five'"
+ end
+
+ build_lib "six", "1.0.0" do |s|
+ s.write "lib/six.rb", "puts 'six'"
+ end
+
+ build_lib "seven", "1.0.0" do |s|
+ s.write "lib/seven.rb", "puts 'seven'"
+ end
+
+ build_lib "eight", "1.0.0" do |s|
+ s.write "lib/eight.rb", "puts 'eight'"
+ end
+
+ build_lib "nine", "1.0.0" do |s|
+ s.write "lib/nine.rb", "puts 'nine'"
+ end
+
+ build_lib "ten", "1.0.0" do |s|
+ s.write "lib/ten.rb", "puts 'ten'"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "one", :group => :bar, :require => %w[baz qux]
+ gem "two"
+ gem "three", :group => :not
+ gem "four", :require => false
+ gem "five"
+ gem "six", :group => "string"
+ gem "seven", :group => :not
+ gem "eight", :require => true, :group => :require_true
+ env "BUNDLER_TEST" => "nine" do
+ gem "nine", :require => true
+ end
+ gem "ten", :install_if => lambda { ENV["BUNDLER_TEST"] == "ten" }
+ end
+ G
+ end
+
+ it "requires the gems" do
+ # default group
+ run "Bundler.require"
+ expect(out).to eq("two")
+
+ # specific group
+ run "Bundler.require(:bar)"
+ expect(out).to eq("baz\nqux")
+
+ # default and specific group
+ run "Bundler.require(:default, :bar)"
+ expect(out).to eq("baz\nqux\ntwo")
+
+ # specific group given as a string
+ run "Bundler.require('bar')"
+ expect(out).to eq("baz\nqux")
+
+ # specific group declared as a string
+ run "Bundler.require(:string)"
+ expect(out).to eq("six")
+
+ # required in resolver order instead of gemfile order
+ run("Bundler.require(:not)")
+ expect(out.split("\n").sort).to eq(%w[seven three])
+
+ # test require: true
+ run "Bundler.require(:require_true)"
+ expect(out).to eq("eight")
+ end
+
+ it "allows requiring gems with non standard names explicitly" do
+ run "Bundler.require ; require 'mofive'"
+ expect(out).to eq("two\nfive")
+ end
+
+ it "allows requiring gems which are scoped by env" do
+ ENV["BUNDLER_TEST"] = "nine"
+ run "Bundler.require"
+ expect(out).to eq("two\nnine")
+ end
+
+ it "allows requiring gems which are scoped by install_if" do
+ ENV["BUNDLER_TEST"] = "ten"
+ run "Bundler.require"
+ expect(out).to eq("two\nten")
+ end
+
+ it "raises an exception if a require is specified but the file does not exist" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "two", :require => 'fail'
+ end
+ G
+
+ load_error_run <<-R, "fail"
+ Bundler.require
+ R
+
+ expect(err_without_deprecations).to eq("ZOMG LOAD ERROR")
+ end
+
+ it "displays a helpful message if the required gem throws an error" do
+ build_lib "faulty", "1.0.0" do |s|
+ s.write "lib/faulty.rb", "raise RuntimeError.new(\"Gem Internal Error Message\")"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "faulty"
+ end
+ G
+
+ run "Bundler.require", :raise_on_error => false
+ expect(err).to match("error while trying to load the gem 'faulty'")
+ expect(err).to match("Gem Internal Error Message")
+ end
+
+ it "doesn't swallow the error when the library has an unrelated error" do
+ build_lib "loadfuuu", "1.0.0" do |s|
+ s.write "lib/loadfuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "loadfuuu"
+ end
+ G
+
+ cmd = <<-RUBY
+ begin
+ Bundler.require
+ rescue LoadError => e
+ warn "ZOMG LOAD ERROR: \#{e.message}"
+ end
+ RUBY
+ run(cmd)
+
+ expect(err_without_deprecations).to eq("ZOMG LOAD ERROR: cannot load such file -- load-bar")
+ end
+
+ describe "with namespaced gems" do
+ before :each do
+ build_lib "jquery-rails", "1.0.0" do |s|
+ s.write "lib/jquery/rails.rb", "puts 'jquery/rails'"
+ end
+ end
+
+ it "requires gem names that are namespaced" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path '#{lib_path}' do
+ gem 'jquery-rails'
+ end
+ G
+
+ run "Bundler.require"
+ expect(out).to eq("jquery/rails")
+ end
+
+ it "silently passes if the require fails" do
+ build_lib "bcrypt-ruby", "1.0.0", :no_default => true do |s|
+ s.write "lib/brcrypt.rb", "BCrypt = '1.0.0'"
+ end
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ path "#{lib_path}" do
+ gem "bcrypt-ruby"
+ end
+ G
+
+ cmd = <<-RUBY
+ require '#{entrypoint}'
+ Bundler.require
+ RUBY
+ ruby(cmd)
+
+ expect(err).to be_empty
+ end
+
+ it "does not mangle explicitly given requires" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem 'jquery-rails', :require => 'jquery-rails'
+ end
+ G
+
+ load_error_run <<-R, "jquery-rails"
+ Bundler.require
+ R
+ expect(err_without_deprecations).to eq("ZOMG LOAD ERROR")
+ end
+
+ it "handles the case where regex fails" do
+ build_lib "load-fuuu", "1.0.0" do |s|
+ s.write "lib/load-fuuu.rb", "raise LoadError.new(\"Could not open library 'libfuuu-1.0': libfuuu-1.0: cannot open shared object file: No such file or directory.\")"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "load-fuuu"
+ end
+ G
+
+ cmd = <<-RUBY
+ begin
+ Bundler.require
+ rescue LoadError => e
+ warn "ZOMG LOAD ERROR" if e.message.include?("Could not open library 'libfuuu-1.0'")
+ end
+ RUBY
+ run(cmd)
+
+ expect(err_without_deprecations).to eq("ZOMG LOAD ERROR")
+ end
+
+ it "doesn't swallow the error when the library has an unrelated error" do
+ build_lib "load-fuuu", "1.0.0" do |s|
+ s.write "lib/load/fuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "load-fuuu"
+ end
+ G
+
+ cmd = <<-RUBY
+ begin
+ Bundler.require
+ rescue LoadError => e
+ warn "ZOMG LOAD ERROR: \#{e.message}"
+ end
+ RUBY
+ run(cmd)
+
+ expect(err_without_deprecations).to eq("ZOMG LOAD ERROR: cannot load such file -- load-bar")
+ end
+ end
+
+ describe "using bundle exec" do
+ it "requires the locked gems" do
+ bundle "exec ruby -e 'Bundler.require'"
+ expect(out).to eq("two")
+
+ bundle "exec ruby -e 'Bundler.require(:bar)'"
+ expect(out).to eq("baz\nqux")
+
+ bundle "exec ruby -e 'Bundler.require(:default, :bar)'"
+ expect(out).to eq("baz\nqux\ntwo")
+ end
+ end
+
+ describe "order" do
+ before(:each) do
+ build_lib "one", "1.0.0" do |s|
+ s.write "lib/one.rb", <<-ONE
+ if defined?(Two)
+ Two.two
+ else
+ puts "two_not_loaded"
+ end
+ puts 'one'
+ ONE
+ end
+
+ build_lib "two", "1.0.0" do |s|
+ s.write "lib/two.rb", <<-TWO
+ module Two
+ def self.two
+ puts 'module_two'
+ end
+ end
+ puts 'two'
+ TWO
+ end
+ end
+
+ it "works when the gems are in the Gemfile in the correct order" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "two"
+ gem "one"
+ end
+ G
+
+ run "Bundler.require"
+ expect(out).to eq("two\nmodule_two\none")
+ end
+
+ describe "a gem with different requires for different envs" do
+ before(:each) do
+ build_gem "multi_gem", :to_bundle => true do |s|
+ s.write "lib/one.rb", "puts 'ONE'"
+ s.write "lib/two.rb", "puts 'TWO'"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "multi_gem", :require => "one", :group => :one
+ gem "multi_gem", :require => "two", :group => :two
+ G
+ end
+
+ it "requires both with Bundler.require(both)" do
+ run "Bundler.require(:one, :two)"
+ expect(out).to eq("ONE\nTWO")
+ end
+
+ it "requires one with Bundler.require(:one)" do
+ run "Bundler.require(:one)"
+ expect(out).to eq("ONE")
+ end
+
+ it "requires :two with Bundler.require(:two)" do
+ run "Bundler.require(:two)"
+ expect(out).to eq("TWO")
+ end
+ end
+
+ it "fails when the gems are in the Gemfile in the wrong order" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path}" do
+ gem "one"
+ gem "two"
+ end
+ G
+
+ run "Bundler.require"
+ expect(out).to eq("two_not_loaded\none\ntwo")
+ end
+
+ describe "with busted gems" do
+ it "should be busted" do
+ build_gem "busted_require", :to_bundle => true do |s|
+ s.write "lib/busted_require.rb", "require 'no_such_file_omg'"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "busted_require"
+ G
+
+ load_error_run <<-R, "no_such_file_omg"
+ Bundler.require
+ R
+ expect(err_without_deprecations).to eq("ZOMG LOAD ERROR")
+ end
+ end
+ end
+
+ it "does not load rubygems gemspecs that are used" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ run <<-R
+ path = File.join(Gem.dir, "specifications", "rack-1.0.0.gemspec")
+ contents = File.read(path)
+ contents = contents.lines.to_a.insert(-2, "\n raise 'broken gemspec'\n").join
+ File.open(path, "w") do |f|
+ f.write contents
+ end
+ R
+
+ run <<-R
+ Bundler.require
+ puts "WIN"
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "does not load git gemspecs that are used" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run <<-R
+ path = Gem.loaded_specs["foo"].loaded_from
+ contents = File.read(path)
+ contents = contents.lines.to_a.insert(-2, "\n raise 'broken gemspec'\n").join
+ File.open(path, "w") do |f|
+ f.write contents
+ end
+ R
+
+ run <<-R
+ Bundler.require
+ puts "WIN"
+ R
+
+ expect(out).to eq("WIN")
+ end
+end
+
+RSpec.describe "Bundler.require with platform specific dependencies" do
+ it "does not require the gems that are pinned to other platforms" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ platforms :#{not_local_tag} do
+ gem "platform_specific", :require => "omgomg"
+ end
+
+ gem "rack", "1.0.0"
+ G
+
+ run "Bundler.require"
+ expect(err).to be_empty
+ end
+
+ it "requires gems pinned to multiple platforms, including the current one" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ platforms :#{not_local_tag}, :#{local_tag} do
+ gem "rack", :require => "rack"
+ end
+ G
+
+ run "Bundler.require; puts RACK"
+
+ expect(out).to eq("1.0.0")
+ expect(err).to be_empty
+ end
+end
diff --git a/spec/bundler/runtime/self_management_spec.rb b/spec/bundler/runtime/self_management_spec.rb
new file mode 100644
index 0000000000..61cfc9b795
--- /dev/null
+++ b/spec/bundler/runtime/self_management_spec.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+RSpec.describe "Self management", :rubygems => ">= 3.3.0.dev", :realworld => true do
+ describe "auto switching" do
+ let(:previous_minor) do
+ "2.3.0"
+ end
+
+ before do
+ build_repo2
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+
+ gem "rack"
+ G
+ end
+
+ it "installs locked version when using system path and uses it" do
+ lockfile_bundled_with(previous_minor)
+
+ bundle "config set --local path.system true"
+ bundle "install", :artifice => "vcr"
+ expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.")
+
+ # It uninstalls the older system bundler
+ bundle "clean --force"
+ expect(out).to eq("Removing bundler (#{Bundler::VERSION})")
+
+ # App now uses locked version
+ bundle "-v"
+ expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor)
+
+ # Subsequent installs use the locked version without reinstalling
+ bundle "install --verbose"
+ expect(out).to include("Using bundler #{previous_minor}")
+ expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.")
+ end
+
+ it "installs locked version when using local path and uses it" do
+ lockfile_bundled_with(previous_minor)
+
+ bundle "config set --local path vendor/bundle"
+ bundle "install", :artifice => "vcr"
+ expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.")
+ expect(vendored_gems("gems/bundler-#{previous_minor}")).to exist
+
+ # It does not uninstall the locked bundler
+ bundle "clean"
+ expect(out).to be_empty
+
+ # App now uses locked version
+ bundle "-v"
+ expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor)
+
+ # Subsequent installs use the locked version without reinstalling
+ bundle "install --verbose"
+ expect(out).to include("Using bundler #{previous_minor}")
+ expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.")
+ end
+
+ it "installs locked version when using deployment option and uses it" do
+ lockfile_bundled_with(previous_minor)
+
+ bundle "config set --local deployment true"
+ bundle "install", :artifice => "vcr"
+ expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.")
+ expect(vendored_gems("gems/bundler-#{previous_minor}")).to exist
+
+ # It does not uninstall the locked bundler
+ bundle "clean"
+ expect(out).to be_empty
+
+ # App now uses locked version
+ bundle "-v"
+ expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor)
+
+ # Subsequent installs use the locked version without reinstalling
+ bundle "install --verbose"
+ expect(out).to include("Using bundler #{previous_minor}")
+ expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.")
+ end
+
+ it "does not try to install a development version" do
+ lockfile_bundled_with("#{previous_minor}.dev")
+
+ bundle "install --verbose"
+ expect(out).not_to match(/restarting using that version/)
+
+ bundle "-v"
+ expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION)
+ end
+
+ it "shows a discrete message if locked bundler does not exist" do
+ missing_minor ="#{Bundler::VERSION[0]}.999.999"
+
+ lockfile_bundled_with(missing_minor)
+
+ bundle "install", :artifice => "vcr"
+ expect(err).to eq("Your lockfile is locked to a version of bundler (#{missing_minor}) that doesn't exist at https://rubygems.org/. Going on using #{Bundler::VERSION}")
+
+ bundle "-v"
+ expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION)
+ end
+
+ private
+
+ def lockfile_bundled_with(version)
+ lockfile <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{version}
+ L
+ end
+ end
+end
diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb
new file mode 100644
index 0000000000..2d39d72937
--- /dev/null
+++ b/spec/bundler/runtime/setup_spec.rb
@@ -0,0 +1,1557 @@
+# frozen_string_literal: true
+
+require "tmpdir"
+
+RSpec.describe "Bundler.setup" do
+ describe "with no arguments" do
+ it "makes all groups available" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :group => :test
+ G
+
+ ruby <<-RUBY
+ require 'bundler'
+ Bundler.setup
+
+ require 'rack'
+ puts RACK
+ RUBY
+ expect(err).to be_empty
+ expect(out).to eq("1.0.0")
+ end
+ end
+
+ describe "when called with groups" do
+ before(:each) do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "yard"
+ gem "rack", :group => :test
+ G
+ end
+
+ it "doesn't make all groups available" do
+ ruby <<-RUBY
+ require 'bundler'
+ Bundler.setup(:default)
+
+ begin
+ require 'rack'
+ rescue LoadError
+ puts "WIN"
+ end
+ RUBY
+ expect(err).to be_empty
+ expect(out).to eq("WIN")
+ end
+
+ it "accepts string for group name" do
+ ruby <<-RUBY
+ require 'bundler'
+ Bundler.setup(:default, 'test')
+
+ require 'rack'
+ puts RACK
+ RUBY
+ expect(err).to be_empty
+ expect(out).to eq("1.0.0")
+ end
+
+ it "leaves all groups available if they were already" do
+ ruby <<-RUBY
+ require 'bundler'
+ Bundler.setup
+ Bundler.setup(:default)
+
+ require 'rack'
+ puts RACK
+ RUBY
+ expect(err).to be_empty
+ expect(out).to eq("1.0.0")
+ end
+
+ it "leaves :default available if setup is called twice" do
+ ruby <<-RUBY
+ require 'bundler'
+ Bundler.setup(:default)
+ Bundler.setup(:default, :test)
+
+ begin
+ require 'yard'
+ puts "WIN"
+ rescue LoadError
+ puts "FAIL"
+ end
+ RUBY
+ expect(err).to be_empty
+ expect(out).to match("WIN")
+ end
+
+ it "handles multiple non-additive invocations" do
+ ruby <<-RUBY, :raise_on_error => false
+ require 'bundler'
+ Bundler.setup(:default, :test)
+ Bundler.setup(:default)
+ require 'rack'
+
+ puts "FAIL"
+ RUBY
+
+ expect(err).to match("rack")
+ expect(err).to match("LoadError")
+ expect(out).not_to match("FAIL")
+ end
+ end
+
+ context "load order" do
+ def clean_load_path(lp)
+ without_bundler_load_path = ruby("puts $LOAD_PATH").split("\n")
+ lp -= [*without_bundler_load_path, lib_dir.to_s]
+ lp.map! {|p| p.sub(system_gem_path.to_s, "") }
+ end
+
+ it "puts loaded gems after -I and RUBYLIB", :ruby_repo do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ ENV["RUBYOPT"] = "#{ENV["RUBYOPT"]} -Idash_i_dir"
+ ENV["RUBYLIB"] = "rubylib_dir"
+
+ ruby <<-RUBY
+ require 'bundler'
+ Bundler.setup
+ puts $LOAD_PATH
+ RUBY
+
+ load_path = out.split("\n")
+ rack_load_order = load_path.index {|path| path.include?("rack") }
+
+ expect(err).to be_empty
+ expect(load_path).to include(a_string_ending_with("dash_i_dir"), "rubylib_dir")
+ expect(rack_load_order).to be > 0
+ end
+
+ it "orders the load path correctly when there are dependencies" do
+ bundle "config set path.system true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails"
+ G
+
+ ruby <<-RUBY
+ require 'bundler'
+ Bundler.setup
+ puts $LOAD_PATH
+ RUBY
+
+ load_path = clean_load_path(out.split("\n"))
+
+ expect(load_path).to start_with(
+ "/gems/rails-2.3.2/lib",
+ "/gems/activeresource-2.3.2/lib",
+ "/gems/activerecord-2.3.2/lib",
+ "/gems/actionpack-2.3.2/lib",
+ "/gems/actionmailer-2.3.2/lib",
+ "/gems/activesupport-2.3.2/lib",
+ "/gems/rake-13.0.1/lib"
+ )
+ end
+
+ it "falls back to order the load path alphabetically for backwards compatibility" do
+ bundle "config set path.system true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "weakling"
+ gem "duradura"
+ gem "terranova"
+ G
+
+ ruby <<-RUBY
+ require 'bundler/setup'
+ puts $LOAD_PATH
+ RUBY
+
+ load_path = clean_load_path(out.split("\n"))
+
+ expect(load_path).to start_with(
+ "/gems/weakling-0.0.3/lib",
+ "/gems/terranova-8/lib",
+ "/gems/duradura-7.0/lib"
+ )
+ end
+ end
+
+ it "raises if the Gemfile was not yet installed" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ ruby <<-R
+ require '#{entrypoint}'
+
+ begin
+ Bundler.setup
+ puts "FAIL"
+ rescue Bundler::GemNotFound
+ puts "WIN"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "doesn't create a Gemfile.lock if the setup fails" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ ruby <<-R, :raise_on_error => false
+ require 'bundler'
+
+ Bundler.setup
+ R
+
+ expect(bundled_app_lock).not_to exist
+ end
+
+ it "doesn't change the Gemfile.lock if the setup fails" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ lockfile = File.read(bundled_app_lock)
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "nosuchgem", "10.0"
+ G
+
+ ruby <<-R, :raise_on_error => false
+ require 'bundler'
+
+ Bundler.setup
+ R
+
+ expect(File.read(bundled_app_lock)).to eq(lockfile)
+ end
+
+ it "makes a Gemfile.lock if setup succeeds" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ File.read(bundled_app_lock)
+
+ FileUtils.rm(bundled_app_lock)
+
+ run "1"
+ expect(bundled_app_lock).to exist
+ end
+
+ describe "$BUNDLE_GEMFILE" do
+ context "user provides an absolute path" do
+ it "uses BUNDLE_GEMFILE to locate the gemfile if present" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ gemfile bundled_app("4realz"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport", "2.3.5"
+ G
+
+ ENV["BUNDLE_GEMFILE"] = bundled_app("4realz").to_s
+ bundle :install
+
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ end
+ end
+
+ context "an absolute path is not provided" do
+ it "uses BUNDLE_GEMFILE to locate the gemfile if present and doesn't fail in deployment mode" do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ bundle "install"
+ bundle "config set --local deployment true"
+
+ ENV["BUNDLE_GEMFILE"] = "Gemfile"
+ ruby <<-R
+ require 'bundler'
+
+ begin
+ Bundler.setup
+ puts "WIN"
+ rescue ArgumentError => e
+ puts "FAIL"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+ end
+ end
+
+ it "prioritizes gems in BUNDLE_PATH over gems in GEM_HOME" do
+ ENV["BUNDLE_PATH"] = bundled_app(".bundle").to_s
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ G
+
+ build_gem "rack", "1.0", :to_system => true do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ describe "integrate with rubygems" do
+ describe "by replacing #gem" do
+ before :each do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "0.9.1"
+ G
+ end
+
+ it "replaces #gem but raises when the gem is missing" do
+ run <<-R
+ begin
+ gem "activesupport"
+ puts "FAIL"
+ rescue LoadError
+ puts "WIN"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "replaces #gem but raises when the version is wrong" do
+ run <<-R
+ begin
+ gem "rack", "1.0.0"
+ puts "FAIL"
+ rescue LoadError
+ puts "WIN"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+ end
+
+ describe "by hiding system gems" do
+ before :each do
+ system_gems "activesupport-2.3.5"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "yard"
+ G
+ end
+
+ it "removes system gems from Gem.source_index" do
+ run "require 'yard'"
+ expect(out).to eq("bundler-#{Bundler::VERSION}\nyard-1.0")
+ end
+
+ context "when the ruby stdlib is a substring of Gem.path" do
+ it "does not reject the stdlib from $LOAD_PATH" do
+ substring = "/" + $LOAD_PATH.find {|p| p.include?("vendor_ruby") }.split("/")[2]
+ run "puts 'worked!'", :env => { "GEM_PATH" => substring }
+ expect(out).to eq("worked!")
+ end
+ end
+ end
+ end
+
+ describe "with paths" do
+ it "activates the gems in the path source" do
+ system_gems "rack-1.0.0"
+
+ build_lib "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "puts 'WIN'"
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ path "#{lib_path("rack-1.0.0")}" do
+ gem "rack"
+ end
+ G
+
+ run "require 'rack'"
+ expect(out).to eq("WIN")
+ end
+ end
+
+ describe "with git" do
+ before do
+ build_git "rack", "1.0.0"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-1.0.0")}"
+ G
+ end
+
+ it "provides a useful exception when the git repo is not checked out yet" do
+ run "1", :raise_on_error => false
+ expect(err).to match(/the git source #{lib_path('rack-1.0.0')} is not yet checked out. Please run `bundle install`/i)
+ end
+
+ it "does not hit the git binary if the lockfile is available and up to date" do
+ bundle "install"
+
+ break_git!
+
+ ruby <<-R
+ require 'bundler'
+
+ begin
+ Bundler.setup
+ puts "WIN"
+ rescue Exception => e
+ puts "FAIL"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "provides a good exception if the lockfile is unavailable" do
+ bundle "install"
+
+ FileUtils.rm(bundled_app_lock)
+
+ break_git!
+
+ ruby <<-R
+ require "#{entrypoint}"
+
+ begin
+ Bundler.setup
+ puts "FAIL"
+ rescue Bundler::GitError => e
+ puts e.message
+ end
+ R
+
+ run "puts 'FAIL'", :raise_on_error => false
+
+ expect(err).not_to include "This is not the git you are looking for"
+ end
+
+ it "works even when the cache directory has been deleted" do
+ bundle "config set --local path vendor/bundle"
+ bundle :install
+ FileUtils.rm_rf vendored_gems("cache")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not randomly change the path when specifying --path and the bundle directory becomes read only" do
+ bundle "config set --local path vendor/bundle"
+ bundle :install
+
+ with_read_only("#{bundled_app}/**/*") do
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ it "finds git gem when default bundle path becomes read only" do
+ bundle "config set --local path .bundle"
+ bundle "install"
+
+ with_read_only("#{bundled_app(".bundle")}/**/*") do
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+
+ describe "when specifying local override" do
+ it "explodes if given path does not exist on runtime" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle :install
+
+ FileUtils.rm_rf(lib_path("local-rack"))
+ run "require 'rack'", :raise_on_error => false
+ expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/)
+ end
+
+ it "explodes if branch is not given on runtime" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle :install
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}"
+ G
+
+ run "require 'rack'", :raise_on_error => false
+ expect(err).to match(/because :branch is not specified in Gemfile/)
+ end
+
+ it "explodes on different branches on runtime" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle :install
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "changed"
+ G
+
+ run "require 'rack'", :raise_on_error => false
+ expect(err).to match(/is using branch main but Gemfile specifies changed/)
+ end
+
+ it "explodes on refs with different branches on runtime" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "main", :branch => "main"
+ G
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "main", :branch => "nonexistent"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ run "require 'rack'", :raise_on_error => false
+ expect(err).to match(/is using branch main but Gemfile specifies nonexistent/)
+ end
+ end
+
+ describe "when excluding groups" do
+ it "doesn't change the resolve if --without is used" do
+ bundle "config set --local without rails"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+
+ group :rails do
+ gem "rails", "2.3.2"
+ end
+ G
+
+ system_gems "activesupport-2.3.5"
+
+ expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => :default
+ end
+
+ it "remembers --without and does not bail on bare Bundler.setup" do
+ bundle "config set --local without rails"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+
+ group :rails do
+ gem "rails", "2.3.2"
+ end
+ G
+
+ system_gems "activesupport-2.3.5"
+
+ expect(the_bundle).to include_gems "activesupport 2.3.2"
+ end
+
+ it "remembers --without and does not bail on bare Bundler.setup, even in the case of path gems no longer available" do
+ bundle "config set --local without development"
+
+ path = bundled_app(File.join("vendor", "foo"))
+ build_lib "foo", :path => path
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport", "2.3.2"
+ gem 'foo', :path => 'vendor/foo', :group => :development
+ G
+
+ FileUtils.rm_rf(path)
+
+ ruby "require 'bundler'; Bundler.setup", :env => { "DEBUG" => "1" }
+ expect(out).to include("Assuming that source at `vendor/foo` has not changed since fetching its specs errored")
+ expect(out).to include("Found no changes, using resolution from the lockfile")
+ expect(err).to be_empty
+ end
+
+ it "doesn't re-resolve when a pre-release bundler is used and a dependency includes a dependency on bundler" do
+ system_gems "bundler-9.99.9.beta1"
+
+ build_repo4 do
+ build_gem "depends_on_bundler", "1.0" do |s|
+ s.add_dependency "bundler", ">= 1.5.0"
+ end
+ end
+
+ install_gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "depends_on_bundler"
+ G
+
+ ruby "require '#{system_gem_path("gems/bundler-9.99.9.beta1/lib/bundler.rb")}'; Bundler.setup", :env => { "DEBUG" => "1" }
+ expect(out).to include("Found no changes, using resolution from the lockfile")
+ expect(out).not_to include("lockfile does not have all gems needed for the current platform")
+ expect(err).to be_empty
+ end
+
+ it "doesn't fail in frozen mode when bundler is a Gemfile dependency" do
+ install_gemfile <<~G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "bundler"
+ G
+
+ bundle "install --verbose", :env => { "BUNDLE_FROZEN" => "true" }
+ expect(err).to be_empty
+ end
+
+ it "doesn't re-resolve when deleting dependencies" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "actionpack"
+ G
+
+ install_gemfile <<-G, :verbose => true
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ expect(out).to include("Some dependencies were deleted, using a subset of the resolution from the lockfile")
+ expect(err).to be_empty
+ end
+
+ it "remembers --without and does not include groups passed to Bundler.setup" do
+ bundle "config set --local without rails"
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+
+ group :rack do
+ gem "rack"
+ end
+
+ group :rails do
+ gem "rails", "2.3.2"
+ end
+ G
+
+ expect(the_bundle).not_to include_gems "activesupport 2.3.2", :groups => :rack
+ expect(the_bundle).to include_gems "rack 1.0.0", :groups => :rack
+ end
+ end
+
+ # RubyGems returns loaded_from as a string
+ it "has loaded_from as a string on all specs" do
+ build_git "foo"
+ build_git "no-gemspec", :gemspec => false
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ gem "no-gemspec", "1.0", :git => "#{lib_path("no-gemspec-1.0")}"
+ G
+
+ run <<-R
+ Gem.loaded_specs.each do |n, s|
+ puts "FAIL" unless s.loaded_from.is_a?(String)
+ end
+ R
+
+ expect(out).to be_empty
+ end
+
+ it "does not load all gemspecs" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ run <<-R
+ File.open(File.join(Gem.dir, "specifications", "broken.gemspec"), "w") do |f|
+ f.write <<-RUBY
+# -*- encoding: utf-8 -*-
+# stub: broken 1.0.0 ruby lib
+
+Gem::Specification.new do |s|
+ s.name = "broken"
+ s.version = "1.0.0"
+ raise "BROKEN GEMSPEC"
+end
+ RUBY
+ end
+ R
+
+ run <<-R
+ puts "WIN"
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "ignores empty gem paths" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ ENV["GEM_HOME"] = ""
+ bundle %(exec ruby -e "require 'set'")
+
+ expect(err).to be_empty
+ end
+
+ context "when the user has `MANPATH` set", :man do
+ before { ENV["MANPATH"] = "/foo#{File::PATH_SEPARATOR}" }
+
+ it "adds the gem's man dir to the MANPATH" do
+ build_repo4 do
+ build_gem "with_man" do |s|
+ s.write("man/man1/page.1", "MANPAGE")
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "with_man"
+ G
+
+ run "puts ENV['MANPATH']"
+ expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}/foo")
+ end
+ end
+
+ context "when the user does not have `MANPATH` set", :man do
+ before { ENV.delete("MANPATH") }
+
+ it "adds the gem's man dir to the MANPATH, leaving : in the end so that system man pages still work" do
+ build_repo4 do
+ build_gem "with_man" do |s|
+ s.write("man/man1/page.1", "MANPAGE")
+ end
+
+ build_gem "with_man_overriding_system_man" do |s|
+ s.write("man/man1/ls.1", "LS MANPAGE")
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "with_man"
+ G
+
+ run <<~RUBY
+ puts ENV['MANPATH']
+ require "open3"
+ puts Open3.capture2e("man", "ls")[1].success?
+ RUBY
+
+ expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}\ntrue")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "with_man_overriding_system_man"
+ G
+
+ run <<~RUBY
+ puts ENV['MANPATH']
+ require "open3"
+ puts Open3.capture2e({ "LC_ALL" => "C" }, "man", "ls")[0]
+ RUBY
+
+ lines = out.split("\n")
+
+ expect(lines).to include("#{default_bundle_path("gems/with_man_overriding_system_man-1.0/man")}#{File::PATH_SEPARATOR}")
+ expect(lines).to include("LS MANPAGE")
+ end
+ end
+
+ it "should prepend gemspec require paths to $LOAD_PATH in order" do
+ update_repo2 do
+ build_gem("requirepaths") do |s|
+ s.write("lib/rq.rb", "puts 'yay'")
+ s.write("src/rq.rb", "puts 'nooo'")
+ s.require_paths = %w[lib src]
+ end
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem "requirepaths", :require => nil
+ G
+
+ run "require 'rq'"
+ expect(out).to eq("yay")
+ end
+
+ it "should clean $LOAD_PATH properly" do
+ gem_name = "very_simple_binary"
+ full_gem_name = gem_name + "-1.0"
+ ext_dir = File.join(tmp("extensions", full_gem_name))
+
+ system_gems full_gem_name
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ G
+
+ ruby <<-R
+ s = Gem::Specification.find_by_name '#{gem_name}'
+ s.extension_dir = '#{ext_dir}'
+
+ # Don't build extensions.
+ s.class.send(:define_method, :build_extensions) { nil }
+
+ require 'bundler'
+ gem '#{gem_name}'
+
+ puts $LOAD_PATH.count {|path| path =~ /#{gem_name}/} >= 2
+
+ Bundler.setup
+
+ puts $LOAD_PATH.count {|path| path =~ /#{gem_name}/} == 0
+ R
+
+ expect(out).to eq("true\ntrue")
+ end
+
+ context "with bundler is located in symlinked GEM_HOME" do
+ let(:gem_home) { Dir.mktmpdir }
+ let(:symlinked_gem_home) { tmp("gem_home-symlink").to_s }
+ let(:full_name) { "bundler-#{Bundler::VERSION}" }
+
+ before do
+ File.symlink(gem_home, symlinked_gem_home)
+ gems_dir = File.join(gem_home, "gems")
+ specifications_dir = File.join(gem_home, "specifications")
+ Dir.mkdir(gems_dir)
+ Dir.mkdir(specifications_dir)
+
+ File.symlink(source_root, File.join(gems_dir, full_name))
+
+ gemspec_content = File.binread(gemspec).
+ sub("Bundler::VERSION", %("#{Bundler::VERSION}")).
+ lines.reject {|line| line.include?("lib/bundler/version") }.join
+
+ File.open(File.join(specifications_dir, "#{full_name}.gemspec"), "wb") do |f|
+ f.write(gemspec_content)
+ end
+ end
+
+ it "should not remove itself from the LOAD_PATH and require a different copy of 'bundler/setup'" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+
+ ruby <<-R, :env => { "GEM_PATH" => symlinked_gem_home }
+ TracePoint.trace(:class) do |tp|
+ if tp.path.include?("bundler") && !tp.path.start_with?("#{source_root}")
+ puts "OMG. Defining a class from another bundler at \#{tp.path}:\#{tp.lineno}"
+ end
+ end
+ gem 'bundler', '#{Bundler::VERSION}'
+ require 'bundler/setup'
+ R
+
+ expect(out).to be_empty
+ end
+ end
+
+ it "does not reveal system gems even when Gem.refresh is called" do
+ system_gems "rack-1.0.0"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport"
+ G
+
+ run <<-R
+ puts Bundler.rubygems.all_specs.map(&:name)
+ Gem.refresh
+ puts Bundler.rubygems.all_specs.map(&:name)
+ R
+
+ expect(out).to eq("activesupport\nbundler\nactivesupport\nbundler")
+ end
+
+ describe "when a vendored gem specification uses the :path option" do
+ let(:filesystem_root) do
+ current = Pathname.new(Dir.pwd)
+ current = current.parent until current == current.parent
+ current
+ end
+
+ it "should resolve paths relative to the Gemfile" do
+ path = bundled_app(File.join("vendor", "foo"))
+ build_lib "foo", :path => path
+
+ # If the .gemspec exists, then Bundler handles the path differently.
+ # See Source::Path.load_spec_files for details.
+ FileUtils.rm(File.join(path, "foo.gemspec"))
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', '1.2.3', :path => 'vendor/foo'
+ G
+
+ run <<-R, :env => { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, :dir => bundled_app.parent
+ require 'foo'
+ R
+ expect(err).to be_empty
+ end
+
+ it "should make sure the Bundler.root is really included in the path relative to the Gemfile" do
+ relative_path = File.join("vendor", Dir.pwd.gsub(/^#{filesystem_root}/, ""))
+ absolute_path = bundled_app(relative_path)
+ FileUtils.mkdir_p(absolute_path)
+ build_lib "foo", :path => absolute_path
+
+ # If the .gemspec exists, then Bundler handles the path differently.
+ # See Source::Path.load_spec_files for details.
+ FileUtils.rm(File.join(absolute_path, "foo.gemspec"))
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', '1.2.3', :path => '#{relative_path}'
+ G
+
+ bundle :install
+
+ run <<-R, :env => { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, :dir => bundled_app.parent
+ require 'foo'
+ R
+
+ expect(err).to be_empty
+ end
+ end
+
+ describe "with git gems that don't have gemspecs" do
+ before :each do
+ build_git "no_gemspec", :gemspec => false
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "no_gemspec", "1.0", :git => "#{lib_path("no_gemspec-1.0")}"
+ G
+ end
+
+ it "loads the library via a virtual spec" do
+ run <<-R
+ require 'no_gemspec'
+ puts NO_GEMSPEC
+ R
+
+ expect(out).to eq("1.0")
+ end
+ end
+
+ describe "with bundled and system gems" do
+ before :each do
+ system_gems "rack-1.0.0"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+
+ gem "activesupport", "2.3.5"
+ G
+ end
+
+ it "does not pull in system gems" do
+ run <<-R
+ begin;
+ require 'rack'
+ rescue LoadError
+ puts 'WIN'
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "provides a gem method" do
+ run <<-R
+ gem 'activesupport'
+ require 'activesupport'
+ puts ACTIVESUPPORT
+ R
+
+ expect(out).to eq("2.3.5")
+ end
+
+ it "raises an exception if gem is used to invoke a system gem not in the bundle" do
+ run <<-R
+ begin
+ gem 'rack'
+ rescue LoadError => e
+ puts e.message
+ end
+ R
+
+ expect(out).to eq("rack is not part of the bundle. Add it to your Gemfile.")
+ end
+
+ it "sets GEM_HOME appropriately" do
+ run "puts ENV['GEM_HOME']"
+ expect(out).to eq(default_bundle_path.to_s)
+ end
+ end
+
+ describe "with system gems in the bundle" do
+ before :each do
+ bundle "config set path.system true"
+ system_gems "rack-1.0.0"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", "1.0.0"
+ gem "activesupport", "2.3.5"
+ G
+ end
+
+ it "sets GEM_PATH appropriately" do
+ run "puts Gem.path"
+ paths = out.split("\n")
+ expect(paths).to include(system_gem_path.to_s)
+ end
+ end
+
+ describe "with a gemspec that requires other files" do
+ before :each do
+ build_git "bar", :gemspec => false do |s|
+ s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0')
+ s.write "bar.gemspec", <<-G
+ require_relative 'lib/bar/version'
+
+ Gem::Specification.new do |s|
+ s.name = 'bar'
+ s.version = BAR_VERSION
+ s.summary = 'Bar'
+ s.files = Dir["lib/**/*.rb"]
+ s.author = 'no one'
+ end
+ G
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bar", :git => "#{lib_path("bar-1.0")}"
+ G
+ end
+
+ it "evals each gemspec in the context of its parent directory" do
+ bundle :install
+ run "require 'bar'; puts BAR"
+ expect(out).to eq("1.0")
+ end
+
+ it "error intelligently if the gemspec has a LoadError" do
+ skip "whitespace issue?" if Gem.win_platform?
+
+ ref = update_git "bar", :gemspec => false do |s|
+ s.write "bar.gemspec", "require 'foobarbaz'"
+ end.ref_for("HEAD")
+ bundle :install, :raise_on_error => false
+
+ expect(err.lines.map(&:chomp)).to include(
+ a_string_starting_with("[!] There was an error while loading `bar.gemspec`:"),
+ " # from #{default_bundle_path "bundler", "gems", "bar-1.0-#{ref[0, 12]}", "bar.gemspec"}:1",
+ " > require 'foobarbaz'"
+ )
+ end
+
+ it "evals each gemspec with a binding from the top level" do
+ bundle "install"
+
+ ruby <<-RUBY
+ require 'bundler'
+ bundler_module = class << Bundler; self; end
+ bundler_module.send(:remove_method, :require)
+ def Bundler.require(path)
+ raise "LOSE"
+ end
+ Bundler.load
+ RUBY
+
+ expect(err).to be_empty
+ expect(out).to be_empty
+ end
+ end
+
+ describe "when Bundler is bundled" do
+ it "doesn't blow up" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bundler", :path => "#{root}"
+ G
+
+ bundle %(exec ruby -e "require 'bundler'; Bundler.setup")
+ expect(err).to be_empty
+ end
+ end
+
+ describe "when BUNDLED WITH" do
+ def lock_with(bundler_version = nil)
+ lock = <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+ L
+
+ if bundler_version
+ lock += "\nBUNDLED WITH\n #{bundler_version}\n"
+ end
+
+ lock
+ end
+
+ before do
+ bundle "config set --local path.system true"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ context "is not present" do
+ it "does not change the lock" do
+ lockfile lock_with(nil)
+ ruby "require '#{entrypoint}/setup'"
+ expect(lockfile).to eq lock_with(nil)
+ end
+ end
+
+ context "is newer" do
+ it "does not change the lock or warn" do
+ lockfile lock_with(Bundler::VERSION.succ)
+ ruby "require 'bundler/setup'"
+ expect(out).to be_empty
+ expect(err).to be_empty
+ expect(lockfile).to eq lock_with(Bundler::VERSION.succ)
+ end
+ end
+
+ context "is older" do
+ it "does not change the lock" do
+ system_gems "bundler-1.10.1"
+ lockfile lock_with("1.10.1")
+ ruby "require '#{entrypoint}/setup'"
+ expect(lockfile).to eq lock_with("1.10.1")
+ end
+ end
+ end
+
+ describe "when RUBY VERSION" do
+ let(:ruby_version) { nil }
+
+ def lock_with(ruby_version = nil)
+ lock = <<~L
+ GEM
+ remote: #{file_uri_for(gem_repo1)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+ L
+
+ if ruby_version
+ lock += "\nRUBY VERSION\n ruby #{ruby_version}\n"
+ end
+
+ lock += <<~L
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ lock
+ end
+
+ before do
+ install_gemfile <<-G
+ ruby ">= 0"
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ lockfile lock_with(ruby_version)
+ end
+
+ context "is not present" do
+ it "does not change the lock" do
+ expect { ruby "require 'bundler/setup'" }.not_to change { lockfile }
+ end
+ end
+
+ context "is newer" do
+ let(:ruby_version) { "5.5.5" }
+ it "does not change the lock or warn" do
+ expect { ruby "require 'bundler/setup'" }.not_to change { lockfile }
+ expect(out).to be_empty
+ expect(err).to be_empty
+ end
+ end
+
+ context "is older" do
+ let(:ruby_version) { "1.0.0" }
+ it "does not change the lock" do
+ expect { ruby "require 'bundler/setup'" }.not_to change { lockfile }
+ end
+ end
+ end
+
+ describe "with gemified standard libraries" do
+ it "does not load Digest", :ruby_repo do
+ skip "Only for Ruby 3.0+" unless RUBY_VERSION >= "3.0"
+
+ build_git "bar", :gemspec => false do |s|
+ s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0')
+ s.write "bar.gemspec", <<-G
+ require_relative 'lib/bar/version'
+
+ Gem::Specification.new do |s|
+ s.name = 'bar'
+ s.version = BAR_VERSION
+ s.summary = 'Bar'
+ s.files = Dir["lib/**/*.rb"]
+ s.author = 'no one'
+
+ s.add_runtime_dependency 'digest'
+ end
+ G
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "bar", :git => "#{lib_path("bar-1.0")}"
+ G
+
+ bundle :install
+
+ ruby <<-RUBY
+ require '#{entrypoint}/setup'
+ puts defined?(::Digest) ? "Digest defined" : "Digest undefined"
+ require 'digest'
+ RUBY
+ expect(out).to eq("Digest undefined")
+ end
+
+ it "does not load Psych" do
+ gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ ruby <<-RUBY
+ require '#{entrypoint}/setup'
+ puts defined?(Psych::VERSION) ? Psych::VERSION : "undefined"
+ require 'psych'
+ puts Psych::VERSION
+ RUBY
+ pre_bundler, post_bundler = out.split("\n")
+ expect(pre_bundler).to eq("undefined")
+ expect(post_bundler).to match(/\d+\.\d+\.\d+/)
+ end
+
+ it "does not load openssl" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ ruby <<-RUBY
+ require "bundler/setup"
+ puts defined?(OpenSSL) || "undefined"
+ require "openssl"
+ puts defined?(OpenSSL) || "undefined"
+ RUBY
+ expect(out).to eq("undefined\nconstant")
+ end
+
+ describe "default gem activation" do
+ let(:exemptions) do
+ exempts = %w[did_you_mean bundler]
+ exempts << "uri" if Gem.ruby_version >= Gem::Version.new("2.7")
+ exempts << "pathname" if Gem.ruby_version >= Gem::Version.new("3.0")
+ exempts << "set" unless Gem.rubygems_version >= Gem::Version.new("3.2.6")
+ exempts << "tsort" unless Gem.rubygems_version >= Gem::Version.new("3.2.31")
+ exempts << "error_highlight" # added in Ruby 3.1 as a default gem
+ exempts << "ruby2_keywords" # added in Ruby 3.1 as a default gem
+ exempts << "syntax_suggest" # added in Ruby 3.2 as a default gem
+ exempts
+ end
+
+ let(:activation_warning_hack) { strip_whitespace(<<-RUBY) }
+ require #{spec_dir.join("support/hax").to_s.dump}
+
+ Gem::Specification.send(:alias_method, :bundler_spec_activate, :activate)
+ Gem::Specification.send(:define_method, :activate) do
+ unless #{exemptions.inspect}.include?(name)
+ warn '-' * 80
+ warn "activating \#{full_name}"
+ warn(*caller)
+ warn '*' * 80
+ end
+ bundler_spec_activate
+ end
+ RUBY
+
+ let(:activation_warning_hack_rubyopt) do
+ create_file("activation_warning_hack.rb", activation_warning_hack)
+ "-r#{bundled_app("activation_warning_hack.rb")} #{ENV["RUBYOPT"]}"
+ end
+
+ let(:code) { strip_whitespace(<<-RUBY) }
+ require "pp"
+ loaded_specs = Gem.loaded_specs.dup
+ #{exemptions.inspect}.each {|s| loaded_specs.delete(s) }
+ pp loaded_specs
+
+ # not a default gem, but harmful to have loaded
+ open_uri = $LOADED_FEATURES.grep(/open.uri/)
+ unless open_uri.empty?
+ warn "open_uri: \#{open_uri}"
+ end
+ RUBY
+
+ it "activates no gems with -rbundler/setup" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ ruby code, :env => { "RUBYOPT" => activation_warning_hack_rubyopt + " -rbundler/setup" }
+ expect(out).to eq("{}")
+ end
+
+ it "activates no gems with bundle exec" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ create_file("script.rb", code)
+ bundle "exec ruby ./script.rb", :env => { "RUBYOPT" => activation_warning_hack_rubyopt }
+ expect(out).to eq("{}")
+ end
+
+ it "activates no gems with bundle exec that is loaded" do
+ skip "not executable" if Gem.win_platform?
+
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ create_file("script.rb", "#!/usr/bin/env ruby\n\n#{code}")
+ FileUtils.chmod(0o777, bundled_app("script.rb"))
+ bundle "exec ./script.rb", :artifice => nil, :env => { "RUBYOPT" => activation_warning_hack_rubyopt }
+ expect(out).to eq("{}")
+ end
+
+ it "does not load net-http-pipeline too early" do
+ build_repo4 do
+ build_gem "net-http-pipeline", "1.0.1"
+ end
+
+ system_gems "net-http-pipeline-1.0.1", :gem_repo => gem_repo4
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "net-http-pipeline", "1.0.1"
+ G
+
+ bundle "config set --local path vendor/bundle"
+
+ bundle :install
+
+ bundle :check
+
+ expect(out).to eq("The Gemfile's dependencies are satisfied")
+ end
+
+ Gem::Specification.select(&:default_gem?).map(&:name).each do |g|
+ it "activates newer versions of #{g}", :ruby_repo do
+ skip if exemptions.include?(g)
+
+ build_repo4 do
+ build_gem g, "999999"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "#{g}", "999999"
+ G
+
+ expect(the_bundle).to include_gem("#{g} 999999", :env => { "RUBYOPT" => activation_warning_hack_rubyopt })
+ end
+
+ it "activates older versions of #{g}", :ruby_repo do
+ skip if exemptions.include?(g)
+
+ build_repo4 do
+ build_gem g, "0.0.0.a"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ gem "#{g}", "0.0.0.a"
+ G
+
+ expect(the_bundle).to include_gem("#{g} 0.0.0.a", :env => { "RUBYOPT" => activation_warning_hack_rubyopt })
+ end
+ end
+ end
+ end
+
+ describe "after setup" do
+ it "allows calling #gem on random objects", :bundler => "< 3" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ ruby <<-RUBY
+ require "bundler/setup"
+ Object.new.gem "rack"
+ puts Gem.loaded_specs["rack"].full_name
+ RUBY
+
+ expect(out).to eq("rack-1.0.0")
+ end
+
+ it "keeps Kernel#gem private", :bundler => "3" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ ruby <<-RUBY, :raise_on_error => false
+ require "bundler/setup"
+ Object.new.gem "rack"
+ puts "FAIL"
+ RUBY
+
+ expect(last_command.stdboth).not_to include "FAIL"
+ expect(err).to include "private method `gem'"
+ end
+
+ it "keeps Kernel#require private" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+
+ ruby <<-RUBY, :raise_on_error => false
+ require "bundler/setup"
+ Object.new.require "rack"
+ puts "FAIL"
+ RUBY
+
+ expect(last_command.stdboth).not_to include "FAIL"
+ expect(err).to include "private method `require'"
+ end
+
+ it "takes care of requiring rubygems" do
+ sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler/setup -e'puts true'", :env => { "RUBYOPT" => opt_add("--disable=gems", ENV["RUBYOPT"]) })
+
+ expect(last_command.stdboth).to eq("true")
+ end
+
+ it "memoizes initial set of specs when requiring bundler/setup, so that even if further code mutates dependencies, Bundler.definition.specs is not affected" do
+ install_gemfile <<~G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "yard"
+ gem "rack", :group => :test
+ G
+
+ ruby <<-RUBY, :raise_on_error => false
+ require "bundler/setup"
+ Bundler.require(:test).select! {|d| (d.groups & [:test]).any? }
+ puts Bundler.definition.specs.map(&:name).join(", ")
+ RUBY
+
+ expect(out).to include("rack, yard")
+ end
+
+ it "does not cause double loads when higher versions of default gems are activated before bundler" do
+ build_repo2 do
+ build_gem "json", "999.999.999" do |s|
+ s.write "lib/json.rb", <<~RUBY
+ module JSON
+ VERSION = "999.999.999"
+ end
+ RUBY
+ end
+ end
+
+ system_gems "json-999.999.999", :gem_repo => gem_repo2
+
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ ruby <<-RUBY
+ require "json"
+ require "bundler/setup"
+ require "json"
+ RUBY
+
+ expect(err).to be_empty
+ end
+ end
+
+ it "does not undo the Kernel.require decorations", :rubygems => ">= 3.4.6" do
+ install_gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ script = bundled_app("bin/script")
+ create_file(script, <<~RUBY)
+ module Kernel
+ module_function
+
+ alias_method :require_before_extra_monkeypatches, :require
+
+ def require(path)
+ puts "requiring \#{path} used the monkeypatch"
+
+ require_before_extra_monkeypatches(path)
+ end
+ end
+
+ require "bundler/setup"
+
+ require "foo"
+ RUBY
+
+ sys_exec "#{Gem.ruby} #{script}", :raise_on_error => false
+ expect(out).to include("requiring foo used the monkeypatch")
+ end
+end
diff --git a/spec/bundler/runtime/with_unbundled_env_spec.rb b/spec/bundler/runtime/with_unbundled_env_spec.rb
new file mode 100644
index 0000000000..731a9921a2
--- /dev/null
+++ b/spec/bundler/runtime/with_unbundled_env_spec.rb
@@ -0,0 +1,302 @@
+# frozen_string_literal: true
+
+RSpec.describe "Bundler.with_env helpers" do
+ def bundle_exec_ruby(args, options = {})
+ build_bundler_context options
+ bundle "exec '#{Gem.ruby}' #{args}", options
+ end
+
+ def build_bundler_context(options = {})
+ bundle "config set path vendor/bundle"
+ gemfile "source \"#{file_uri_for(gem_repo1)}\""
+ bundle "install", options
+ end
+
+ def run_bundler_script(env, script)
+ system(env, "ruby", "-I#{lib_dir}", "-rbundler", script.to_s)
+ end
+
+ describe "Bundler.original_env" do
+ it "should return the PATH present before bundle was activated" do
+ create_file("source.rb", <<-RUBY)
+ print Bundler.original_env["PATH"]
+ RUBY
+ path = `getconf PATH`.strip + "#{File::PATH_SEPARATOR}/foo"
+ with_path_as(path) do
+ bundle_exec_ruby(bundled_app("source.rb").to_s)
+ expect(last_command.stdboth).to eq(path)
+ end
+ end
+
+ it "should return the GEM_PATH present before bundle was activated" do
+ create_file("source.rb", <<-RUBY)
+ print Bundler.original_env['GEM_PATH']
+ RUBY
+ gem_path = ENV["GEM_PATH"] + "#{File::PATH_SEPARATOR}/foo"
+ with_gem_path_as(gem_path) do
+ bundle_exec_ruby(bundled_app("source.rb").to_s)
+ expect(last_command.stdboth).to eq(gem_path)
+ end
+ end
+
+ it "works with nested bundle exec invocations", :ruby_repo do
+ create_file("exe.rb", <<-'RUBY')
+ count = ARGV.first.to_i
+ exit if count < 0
+ STDERR.puts "#{count} #{ENV["PATH"].end_with?("#{File::PATH_SEPARATOR}/foo")}"
+ if count == 2
+ ENV["PATH"] = "#{ENV["PATH"]}#{File::PATH_SEPARATOR}/foo"
+ end
+ exec(Gem.ruby, __FILE__, (count - 1).to_s)
+ RUBY
+ path = `getconf PATH`.strip + File::PATH_SEPARATOR + File.dirname(Gem.ruby)
+ with_path_as(path) do
+ build_bundler_context
+ bundle_exec_ruby("#{bundled_app("exe.rb")} 2")
+ end
+ expect(err).to eq <<-EOS.strip
+2 false
+1 true
+0 true
+ EOS
+ end
+
+ it "removes variables that bundler added", :ruby_repo do
+ # Simulate bundler has not yet been loaded
+ ENV.replace(ENV.to_hash.delete_if {|k, _v| k.start_with?(Bundler::EnvironmentPreserver::BUNDLER_PREFIX) })
+
+ original = ruby('puts ENV.to_a.map {|e| e.join("=") }.sort.join("\n")')
+ create_file("source.rb", <<-RUBY)
+ puts Bundler.original_env.to_a.map {|e| e.join("=") }.sort.join("\n")
+ RUBY
+ bundle_exec_ruby bundled_app("source.rb")
+ expect(out).to eq original
+ end
+ end
+
+ shared_examples_for "an unbundling helper" do
+ it "should delete BUNDLE_PATH" do
+ create_file("source.rb", <<-RUBY)
+ print #{modified_env}.has_key?('BUNDLE_PATH')
+ RUBY
+ ENV["BUNDLE_PATH"] = "./foo"
+ bundle_exec_ruby bundled_app("source.rb")
+ expect(last_command.stdboth).to include "false"
+ end
+
+ it "should remove absolute path to 'bundler/setup' from RUBYOPT even if it was present in original env" do
+ create_file("source.rb", <<-RUBY)
+ print #{modified_env}['RUBYOPT']
+ RUBY
+ setup_require = "-r#{lib_dir}/bundler/setup"
+ ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 #{setup_require} #{ENV["RUBYOPT"]}"
+ simulate_bundler_version_when_missing_prerelease_default_gem_activation do
+ bundle_exec_ruby bundled_app("source.rb")
+ end
+ expect(last_command.stdboth).not_to include(setup_require)
+ end
+
+ it "should remove relative path to 'bundler/setup' from RUBYOPT even if it was present in original env" do
+ create_file("source.rb", <<-RUBY)
+ print #{modified_env}['RUBYOPT']
+ RUBY
+ ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 -rbundler/setup #{ENV["RUBYOPT"]}"
+ simulate_bundler_version_when_missing_prerelease_default_gem_activation do
+ bundle_exec_ruby bundled_app("source.rb")
+ end
+ expect(last_command.stdboth).not_to include("-rbundler/setup")
+ end
+
+ it "should restore RUBYLIB", :ruby_repo do
+ create_file("source.rb", <<-RUBY)
+ print #{modified_env}['RUBYLIB']
+ RUBY
+ ENV["RUBYLIB"] = lib_dir.to_s + File::PATH_SEPARATOR + "/foo"
+ ENV["BUNDLER_ORIG_RUBYLIB"] = lib_dir.to_s + File::PATH_SEPARATOR + "/foo-original"
+ bundle_exec_ruby bundled_app("source.rb")
+ expect(last_command.stdboth).to include("/foo-original")
+ end
+
+ it "should restore the original MANPATH" do
+ create_file("source.rb", <<-RUBY)
+ print #{modified_env}['MANPATH']
+ RUBY
+ ENV["MANPATH"] = "/foo"
+ ENV["BUNDLER_ORIG_MANPATH"] = "/foo-original"
+ bundle_exec_ruby bundled_app("source.rb")
+ expect(last_command.stdboth).to include("/foo-original")
+ end
+ end
+
+ describe "Bundler.unbundled_env" do
+ let(:modified_env) { "Bundler.unbundled_env" }
+
+ it_behaves_like "an unbundling helper"
+ end
+
+ describe "Bundler.clean_env", :bundler => 2 do
+ let(:modified_env) { "Bundler.clean_env" }
+
+ it_behaves_like "an unbundling helper"
+ end
+
+ describe "Bundler.with_original_env" do
+ it "should set ENV to original_env in the block" do
+ expected = Bundler.original_env
+ actual = Bundler.with_original_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) }
+ expect(actual).to eq(expected)
+ end
+
+ it "should restore the environment after execution" do
+ Bundler.with_original_env do
+ ENV["FOO"] = "hello"
+ end
+
+ expect(ENV).not_to have_key("FOO")
+ end
+ end
+
+ describe "Bundler.with_clean_env", :bundler => 2 do
+ it "should set ENV to unbundled_env in the block" do
+ expected = Bundler.unbundled_env
+
+ actual = Bundler.ui.silence do
+ Bundler.with_clean_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) }
+ end
+
+ expect(actual).to eq(expected)
+ end
+
+ it "should restore the environment after execution" do
+ Bundler.ui.silence do
+ Bundler.with_clean_env { ENV["FOO"] = "hello" }
+ end
+
+ expect(ENV).not_to have_key("FOO")
+ end
+ end
+
+ describe "Bundler.with_unbundled_env" do
+ it "should set ENV to unbundled_env in the block" do
+ expected = Bundler.unbundled_env
+ actual = Bundler.with_unbundled_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) }
+ expect(actual).to eq(expected)
+ end
+
+ it "should restore the environment after execution" do
+ Bundler.with_unbundled_env do
+ ENV["FOO"] = "hello"
+ end
+
+ expect(ENV).not_to have_key("FOO")
+ end
+ end
+
+ describe "Bundler.original_system" do
+ before do
+ create_file("source.rb", <<-'RUBY')
+ Bundler.original_system("ruby", "-e", "exit(42) if ENV['BUNDLE_FOO'] == 'bar'")
+
+ exit $?.exitstatus
+ RUBY
+ end
+
+ it "runs system inside with_original_env" do
+ run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb"))
+ expect($?.exitstatus).to eq(42)
+ end
+ end
+
+ describe "Bundler.clean_system", :bundler => 2 do
+ before do
+ create_file("source.rb", <<-'RUBY')
+ Bundler.ui.silence { Bundler.clean_system("ruby", "-e", "exit(42) unless ENV['BUNDLE_FOO'] == 'bar'") }
+
+ exit $?.exitstatus
+ RUBY
+ end
+
+ it "runs system inside with_clean_env" do
+ run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb"))
+ expect($?.exitstatus).to eq(42)
+ end
+ end
+
+ describe "Bundler.unbundled_system" do
+ before do
+ create_file("source.rb", <<-'RUBY')
+ Bundler.unbundled_system("ruby", "-e", "exit(42) unless ENV['BUNDLE_FOO'] == 'bar'")
+
+ exit $?.exitstatus
+ RUBY
+ end
+
+ it "runs system inside with_unbundled_env" do
+ run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb"))
+ expect($?.exitstatus).to eq(42)
+ end
+ end
+
+ describe "Bundler.original_exec" do
+ before do
+ create_file("source.rb", <<-'RUBY')
+ Process.fork do
+ exit Bundler.original_exec(%(test "\$BUNDLE_FOO" = "bar"))
+ end
+
+ _, status = Process.wait2
+
+ exit(status.exitstatus)
+ RUBY
+ end
+
+ it "runs exec inside with_original_env" do
+ skip "Fork not implemented" if Gem.win_platform?
+
+ run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb"))
+ expect($?.exitstatus).to eq(0)
+ end
+ end
+
+ describe "Bundler.clean_exec", :bundler => 2 do
+ before do
+ create_file("source.rb", <<-'RUBY')
+ Process.fork do
+ exit Bundler.ui.silence { Bundler.clean_exec(%(test "\$BUNDLE_FOO" = "bar")) }
+ end
+
+ _, status = Process.wait2
+
+ exit(status.exitstatus)
+ RUBY
+ end
+
+ it "runs exec inside with_clean_env" do
+ skip "Fork not implemented" if Gem.win_platform?
+
+ run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb"))
+ expect($?.exitstatus).to eq(1)
+ end
+ end
+
+ describe "Bundler.unbundled_exec" do
+ before do
+ create_file("source.rb", <<-'RUBY')
+ Process.fork do
+ exit Bundler.unbundled_exec(%(test "\$BUNDLE_FOO" = "bar"))
+ end
+
+ _, status = Process.wait2
+
+ exit(status.exitstatus)
+ RUBY
+ end
+
+ it "runs exec inside with_clean_env" do
+ skip "Fork not implemented" if Gem.win_platform?
+
+ run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb"))
+ expect($?.exitstatus).to eq(1)
+ end
+ end
+end
diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb
new file mode 100644
index 0000000000..6a7e2891a6
--- /dev/null
+++ b/spec/bundler/spec_helper.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+require "psych"
+require "bundler/vendored_fileutils"
+require "bundler/vendored_uri"
+require "digest"
+
+if File.expand_path(__FILE__) =~ %r{([^\w/\.:\-])}
+ abort "The bundler specs cannot be run from a path that contains special characters (particularly #{$1.inspect})"
+end
+
+require "bundler"
+require "rspec/core"
+require "rspec/expectations"
+require "rspec/mocks"
+require "rspec/support/differ"
+
+require_relative "support/builders"
+require_relative "support/build_metadata"
+require_relative "support/filters"
+require_relative "support/helpers"
+require_relative "support/indexes"
+require_relative "support/matchers"
+require_relative "support/permissions"
+require_relative "support/platforms"
+
+$debug = false
+
+module Gem
+ def self.ruby=(ruby)
+ @ruby = ruby
+ end
+end
+
+RSpec.configure do |config|
+ config.include Spec::Builders
+ config.include Spec::Helpers
+ config.include Spec::Indexes
+ config.include Spec::Matchers
+ config.include Spec::Path
+ config.include Spec::Platforms
+ config.include Spec::Permissions
+
+ # Enable flags like --only-failures and --next-failure
+ config.example_status_persistence_file_path = ".rspec_status"
+
+ config.silence_filter_announcements = !ENV["TEST_ENV_NUMBER"].nil?
+
+ config.disable_monkey_patching!
+
+ # Since failures cause us to keep a bunch of long strings in memory, stop
+ # once we have a large number of failures (indicative of core pieces of
+ # bundler being broken) so that running the full test suite doesn't take
+ # forever due to memory constraints
+ config.fail_fast ||= 25 if ENV["CI"]
+
+ config.bisect_runner = :shell
+
+ config.expect_with :rspec do |c|
+ c.syntax = :expect
+
+ c.max_formatted_output_length = 1000
+ end
+
+ config.mock_with :rspec do |mocks|
+ mocks.allow_message_expectations_on_nil = false
+ end
+
+ config.before :suite do
+ Gem.ruby = ENV["RUBY"] if ENV["RUBY"]
+
+ require_relative "support/rubygems_ext"
+ Spec::Rubygems.test_setup
+ ENV["BUNDLER_SPEC_RUN"] = "true"
+ ENV["BUNDLER_NO_OLD_RUBYGEMS_WARNING"] = "true"
+ ENV["BUNDLE_USER_CONFIG"] = ENV["BUNDLE_USER_CACHE"] = ENV["BUNDLE_USER_PLUGIN"] = nil
+ ENV["BUNDLE_APP_CONFIG"] = nil
+ ENV["BUNDLE_SILENCE_ROOT_WARNING"] = nil
+ ENV["RUBYGEMS_GEMDEPS"] = nil
+ ENV["XDG_CONFIG_HOME"] = nil
+ ENV["GEMRC"] = nil
+
+ # Don't wrap output in tests
+ ENV["THOR_COLUMNS"] = "10000"
+
+ extend(Spec::Helpers)
+ system_gems :bundler, :path => pristine_system_gem_path
+ end
+
+ config.before :all do
+ check_test_gems!
+
+ build_repo1
+
+ reset_paths!
+ end
+
+ config.around :each do |example|
+ FileUtils.cp_r pristine_system_gem_path, system_gem_path
+
+ with_gem_path_as(system_gem_path) do
+ Bundler.ui.silence { example.run }
+
+ all_output = all_commands_output
+ if example.exception && !all_output.empty?
+ message = all_output + "\n" + example.exception.message
+ (class << example.exception; self; end).send(:define_method, :message) do
+ message
+ end
+ end
+ end
+ ensure
+ reset!
+ end
+
+ config.after :suite do
+ FileUtils.rm_r Spec::Path.pristine_system_gem_path
+ end
+end
diff --git a/spec/bundler/support/artifice/compact_index.rb b/spec/bundler/support/artifice/compact_index.rb
new file mode 100644
index 0000000000..ebc4d0ae5b
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexAPI)
diff --git a/spec/bundler/support/artifice/compact_index_api_missing.rb b/spec/bundler/support/artifice/compact_index_api_missing.rb
new file mode 100644
index 0000000000..f771f7d1f0
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_api_missing.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexApiMissing < CompactIndexAPI
+ get "/fetch/actual/gem/:id" do
+ halt 404
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexApiMissing)
diff --git a/spec/bundler/support/artifice/compact_index_basic_authentication.rb b/spec/bundler/support/artifice/compact_index_basic_authentication.rb
new file mode 100644
index 0000000000..b9115cdd86
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_basic_authentication.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexBasicAuthentication < CompactIndexAPI
+ before do
+ unless env["HTTP_AUTHORIZATION"]
+ halt 401, "Authentication info not supplied"
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexBasicAuthentication)
diff --git a/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb
new file mode 100644
index 0000000000..a6545b9ee4
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexChecksumMismatch < CompactIndexAPI
+ get "/versions" do
+ headers "ETag" => quote("123")
+ headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
+ content_type "text/plain"
+ body ""
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexChecksumMismatch)
diff --git a/spec/bundler/support/artifice/compact_index_concurrent_download.rb b/spec/bundler/support/artifice/compact_index_concurrent_download.rb
new file mode 100644
index 0000000000..35548f278c
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_concurrent_download.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexConcurrentDownload < CompactIndexAPI
+ get "/versions" do
+ versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions")
+
+ # Verify the original (empty) content hasn't been deleted, e.g. on a retry
+ File.binread(versions) == "" || raise("Original file should be present and empty")
+
+ # Verify this is only requested once for a partial download
+ env["HTTP_RANGE"] || raise("Missing Range header for expected partial download")
+
+ # Overwrite the file in parallel, which should be then overwritten
+ # after a successful download to prevent corruption
+ File.open(versions, "w") {|f| f.puts "another process" }
+
+ etag_response do
+ file = tmp("versions.list")
+ FileUtils.rm_f(file)
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems)
+ file.contents
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexConcurrentDownload)
diff --git a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb
new file mode 100644
index 0000000000..401e8a98d8
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexCredsDiffHost < CompactIndexAPI
+ helpers do
+ def auth
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
+ end
+
+ def authorized?
+ auth.provided? && auth.basic? && auth.credentials && auth.credentials == %w[user pass]
+ end
+
+ def protected!
+ return if authorized?
+ response["WWW-Authenticate"] = %(Basic realm="Testing HTTP Auth")
+ throw(:halt, [401, "Not authorized\n"])
+ end
+ end
+
+ before do
+ protected! unless request.path_info.include?("/no/creds/")
+ end
+
+ get "/gems/:id" do
+ redirect "http://diffhost.com/no/creds/#{params[:id]}"
+ end
+
+ get "/no/creds/:id" do
+ if request.host.include?("diffhost") && !auth.provided?
+ File.binread("#{gem_repo1}/gems/#{params[:id]}")
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexCredsDiffHost)
diff --git a/spec/bundler/support/artifice/compact_index_extra.rb b/spec/bundler/support/artifice/compact_index_extra.rb
new file mode 100644
index 0000000000..cd41b3ecca
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_extra.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index_extra"
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexExtra)
diff --git a/spec/bundler/support/artifice/compact_index_extra_api.rb b/spec/bundler/support/artifice/compact_index_extra_api.rb
new file mode 100644
index 0000000000..8b9d304ab4
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_extra_api.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index_extra_api"
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexExtraApi)
diff --git a/spec/bundler/support/artifice/compact_index_extra_api_missing.rb b/spec/bundler/support/artifice/compact_index_extra_api_missing.rb
new file mode 100644
index 0000000000..df6ede584c
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_extra_api_missing.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index_extra_api"
+
+class CompactIndexExtraAPIMissing < CompactIndexExtraApi
+ get "/extra/fetch/actual/gem/:id" do
+ if params[:id] == "missing-1.0.gemspec.rz"
+ halt 404
+ else
+ File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexExtraAPIMissing)
diff --git a/spec/bundler/support/artifice/compact_index_extra_missing.rb b/spec/bundler/support/artifice/compact_index_extra_missing.rb
new file mode 100644
index 0000000000..255c89afdb
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_extra_missing.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index_extra"
+
+class CompactIndexExtraMissing < CompactIndexExtra
+ get "/extra/fetch/actual/gem/:id" do
+ if params[:id] == "missing-1.0.gemspec.rz"
+ halt 404
+ else
+ File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexExtraMissing)
diff --git a/spec/bundler/support/artifice/compact_index_forbidden.rb b/spec/bundler/support/artifice/compact_index_forbidden.rb
new file mode 100644
index 0000000000..18c30ed9a2
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_forbidden.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexForbidden < CompactIndexAPI
+ get "/versions" do
+ halt 403
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexForbidden)
diff --git a/spec/bundler/support/artifice/compact_index_host_redirect.rb b/spec/bundler/support/artifice/compact_index_host_redirect.rb
new file mode 100644
index 0000000000..9a711186db
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_host_redirect.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexHostRedirect < CompactIndexAPI
+ get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do
+ redirect "http://bundler.localgemserver.test#{request.path_info}"
+ end
+
+ get "/versions" do
+ status 404
+ end
+
+ get "/api/v1/dependencies" do
+ status 404
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexHostRedirect)
diff --git a/spec/bundler/support/artifice/compact_index_no_gem.rb b/spec/bundler/support/artifice/compact_index_no_gem.rb
new file mode 100644
index 0000000000..71f6629688
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_no_gem.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexNoGem < CompactIndexAPI
+ get "/gems/:id" do
+ halt 500
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexNoGem)
diff --git a/spec/bundler/support/artifice/compact_index_partial_update.rb b/spec/bundler/support/artifice/compact_index_partial_update.rb
new file mode 100644
index 0000000000..8c73011346
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_partial_update.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexPartialUpdate < CompactIndexAPI
+ # Stub the server to never return 304s. This simulates the behaviour of
+ # Fastly / Rubygems ignoring ETag headers.
+ def not_modified?(_checksum)
+ false
+ end
+
+ get "/versions" do
+ cached_versions_path = File.join(
+ Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions"
+ )
+
+ # Verify a cached copy of the versions file exists
+ unless File.binread(cached_versions_path).start_with?("created_at: ")
+ raise("Cached versions file should be present and have content")
+ end
+
+ # Verify that a partial request is made, starting from the index of the
+ # final byte of the cached file.
+ unless env["HTTP_RANGE"] == "bytes=#{File.binread(cached_versions_path).bytesize - 1}-"
+ raise("Range header should be present, and start from the index of the final byte of the cache.")
+ end
+
+ etag_response do
+ # Return the exact contents of the cache.
+ File.binread(cached_versions_path)
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexPartialUpdate)
diff --git a/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb b/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb
new file mode 100644
index 0000000000..20546ba4c3
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI
+ def partial_update_no_etag
+ response_body = yield
+ headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
+ content_type "text/plain"
+ requested_range_for(response_body)
+ end
+
+ get "/versions" do
+ partial_update_no_etag do
+ file = tmp("versions.list")
+ FileUtils.rm_f(file)
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems)
+ lines = file.contents([], :calculate_info_checksums => true).split("\n")
+ name, versions, checksum = lines.last.split(" ")
+
+ # shuffle versions so new versions are not appended to the end
+ [*lines[0..-2], [name, versions.split(",").reverse.join(","), checksum].join(" ")].join("\n")
+ end
+ end
+
+ get "/info/:name" do
+ partial_update_no_etag do
+ gem = gems.find {|g| g.name == params[:name] }
+ lines = CompactIndex.info(gem ? gem.versions : []).split("\n")
+
+ # shuffle versions so new versions are not appended to the end
+ [lines.first, lines.last, *lines[1..-2]].join("\n")
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexPartialUpdateNoEtagNotIncremental)
diff --git a/spec/bundler/support/artifice/compact_index_precompiled_before.rb b/spec/bundler/support/artifice/compact_index_precompiled_before.rb
new file mode 100644
index 0000000000..b5f72f546a
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_precompiled_before.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexPrecompiledBefore < CompactIndexAPI
+ get "/info/:name" do
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ move_ruby_variant_to_the_end(CompactIndex.info(gem ? gem.versions : []))
+ end
+ end
+
+ private
+
+ def move_ruby_variant_to_the_end(response)
+ lines = response.split("\n")
+ ruby = lines.find {|line| /\A\d+\.\d+\.\d* \|/.match(line) }
+ lines.delete(ruby)
+ lines.push(ruby).join("\n")
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexPrecompiledBefore)
diff --git a/spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb b/spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb
new file mode 100644
index 0000000000..8a7c4b79b0
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexRangeNotSatisfiable < CompactIndexAPI
+ get "/versions" do
+ if env["HTTP_RANGE"]
+ status 416
+ else
+ etag_response do
+ file = tmp("versions.list")
+ FileUtils.rm_f(file)
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems)
+ file.contents
+ end
+ end
+ end
+
+ get "/info/:name" do
+ if env["HTTP_RANGE"]
+ status 416
+ else
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexRangeNotSatisfiable)
diff --git a/spec/bundler/support/artifice/compact_index_rate_limited.rb b/spec/bundler/support/artifice/compact_index_rate_limited.rb
new file mode 100644
index 0000000000..4495491635
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_rate_limited.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexRateLimited < CompactIndexAPI
+ class RequestCounter
+ def self.queue
+ @queue ||= Thread::Queue.new
+ end
+
+ def self.size
+ @queue.size
+ end
+
+ def self.enq(name)
+ @queue.enq(name)
+ end
+
+ def self.deq
+ @queue.deq
+ end
+ end
+
+ configure do
+ RequestCounter.queue
+ end
+
+ get "/info/:name" do
+ RequestCounter.enq(params[:name])
+
+ begin
+ if RequestCounter.size == 1
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ else
+ status 429
+ end
+ ensure
+ RequestCounter.deq
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexRateLimited)
diff --git a/spec/bundler/support/artifice/compact_index_redirects.rb b/spec/bundler/support/artifice/compact_index_redirects.rb
new file mode 100644
index 0000000000..f7ba393239
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_redirects.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexRedirect < CompactIndexAPI
+ get "/fetch/actual/gem/:id" do
+ redirect "/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/versions" do
+ status 404
+ end
+
+ get "/api/v1/dependencies" do
+ status 404
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexRedirect)
diff --git a/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb
new file mode 100644
index 0000000000..96259385e7
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexStrictBasicAuthentication < CompactIndexAPI
+ before do
+ unless env["HTTP_AUTHORIZATION"]
+ halt 401, "Authentication info not supplied"
+ end
+
+ # Only accepts password == "password"
+ unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz"
+ halt 401, "Authentication failed"
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexStrictBasicAuthentication)
diff --git a/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb b/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb
new file mode 100644
index 0000000000..15850599b6
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexWrongDependencies < CompactIndexAPI
+ get "/info/:name" do
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ gem.versions.each {|gv| gv.dependencies.clear } if gem
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexWrongDependencies)
diff --git a/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb
new file mode 100644
index 0000000000..acc13a56ff
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexWrongGemChecksum < CompactIndexAPI
+ get "/info/:name" do
+ etag_response do
+ name = params[:name]
+ gem = gems.find {|g| g.name == name }
+ checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "ab" * 22 }
+ versions = gem ? gem.versions : []
+ versions.each {|v| v.checksum = checksum }
+ CompactIndex.info(versions)
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexWrongGemChecksum)
diff --git a/spec/bundler/support/artifice/endpoint.rb b/spec/bundler/support/artifice/endpoint.rb
new file mode 100644
index 0000000000..15242a7942
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+require_relative "helpers/artifice"
+
+Artifice.activate_with(Endpoint)
diff --git a/spec/bundler/support/artifice/endpoint_500.rb b/spec/bundler/support/artifice/endpoint_500.rb
new file mode 100644
index 0000000000..d8ab6b65bc
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_500.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "../path"
+
+$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
+
+require "sinatra/base"
+
+class Endpoint500 < Sinatra::Base
+ before do
+ halt 500
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(Endpoint500)
diff --git a/spec/bundler/support/artifice/endpoint_api_forbidden.rb b/spec/bundler/support/artifice/endpoint_api_forbidden.rb
new file mode 100644
index 0000000000..6bdc5896d6
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_api_forbidden.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+
+class EndpointApiForbidden < Endpoint
+ get "/api/v1/dependencies" do
+ halt 403
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointApiForbidden)
diff --git a/spec/bundler/support/artifice/endpoint_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_basic_authentication.rb
new file mode 100644
index 0000000000..e8e3569e63
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_basic_authentication.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+
+class EndpointBasicAuthentication < Endpoint
+ before do
+ unless env["HTTP_AUTHORIZATION"]
+ halt 401, "Authentication info not supplied"
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointBasicAuthentication)
diff --git a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb
new file mode 100644
index 0000000000..ce30de0a68
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+
+class EndpointCredsDiffHost < Endpoint
+ helpers do
+ def auth
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
+ end
+
+ def authorized?
+ auth.provided? && auth.basic? && auth.credentials && auth.credentials == %w[user pass]
+ end
+
+ def protected!
+ return if authorized?
+ response["WWW-Authenticate"] = %(Basic realm="Testing HTTP Auth")
+ throw(:halt, [401, "Not authorized\n"])
+ end
+ end
+
+ before do
+ protected! unless request.path_info.include?("/no/creds/")
+ end
+
+ get "/gems/:id" do
+ redirect "http://diffhost.com/no/creds/#{params[:id]}"
+ end
+
+ get "/no/creds/:id" do
+ if request.host.include?("diffhost") && !auth.provided?
+ File.binread("#{gem_repo1}/gems/#{params[:id]}")
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointCredsDiffHost)
diff --git a/spec/bundler/support/artifice/endpoint_extra.rb b/spec/bundler/support/artifice/endpoint_extra.rb
new file mode 100644
index 0000000000..021fd435fe
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_extra.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+
+class EndpointExtra < Endpoint
+ get "/extra/api/v1/dependencies" do
+ halt 404
+ end
+
+ get "/extra/specs.4.8.gz" do
+ File.binread("#{gem_repo2}/specs.4.8.gz")
+ end
+
+ get "/extra/prerelease_specs.4.8.gz" do
+ File.binread("#{gem_repo2}/prerelease_specs.4.8.gz")
+ end
+
+ get "/extra/quick/Marshal.4.8/:id" do
+ redirect "/extra/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/extra/fetch/actual/gem/:id" do
+ File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/extra/gems/:id" do
+ File.binread("#{gem_repo2}/gems/#{params[:id]}")
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointExtra)
diff --git a/spec/bundler/support/artifice/endpoint_extra_api.rb b/spec/bundler/support/artifice/endpoint_extra_api.rb
new file mode 100644
index 0000000000..a965af6e73
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_extra_api.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+
+class EndpointExtraApi < Endpoint
+ get "/extra/api/v1/dependencies" do
+ deps = dependencies_for(params[:gems], gem_repo4)
+ Marshal.dump(deps)
+ end
+
+ get "/extra/specs.4.8.gz" do
+ File.binread("#{gem_repo4}/specs.4.8.gz")
+ end
+
+ get "/extra/prerelease_specs.4.8.gz" do
+ File.binread("#{gem_repo4}/prerelease_specs.4.8.gz")
+ end
+
+ get "/extra/quick/Marshal.4.8/:id" do
+ redirect "/extra/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/extra/fetch/actual/gem/:id" do
+ File.binread("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/extra/gems/:id" do
+ File.binread("#{gem_repo4}/gems/#{params[:id]}")
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointExtraApi)
diff --git a/spec/bundler/support/artifice/endpoint_extra_missing.rb b/spec/bundler/support/artifice/endpoint_extra_missing.rb
new file mode 100644
index 0000000000..73e2defb32
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_extra_missing.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint_extra"
+
+class EndpointExtraMissing < EndpointExtra
+ get "/extra/fetch/actual/gem/:id" do
+ if params[:id] == "missing-1.0.gemspec.rz"
+ halt 404
+ else
+ File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointExtraMissing)
diff --git a/spec/bundler/support/artifice/endpoint_fallback.rb b/spec/bundler/support/artifice/endpoint_fallback.rb
new file mode 100644
index 0000000000..742e563f07
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_fallback.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+
+class EndpointFallback < Endpoint
+ DEPENDENCY_LIMIT = 60
+
+ get "/api/v1/dependencies" do
+ if params[:gems] && params[:gems].size <= DEPENDENCY_LIMIT
+ Marshal.dump(dependencies_for(params[:gems]))
+ else
+ halt 413, "Too many gems to resolve, please request less than #{DEPENDENCY_LIMIT} gems"
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointFallback)
diff --git a/spec/bundler/support/artifice/endpoint_host_redirect.rb b/spec/bundler/support/artifice/endpoint_host_redirect.rb
new file mode 100644
index 0000000000..0efb6cda02
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_host_redirect.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+
+class EndpointHostRedirect < Endpoint
+ get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do
+ redirect "http://bundler.localgemserver.test#{request.path_info}"
+ end
+
+ get "/api/v1/dependencies" do
+ status 404
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointHostRedirect)
diff --git a/spec/bundler/support/artifice/endpoint_marshal_fail.rb b/spec/bundler/support/artifice/endpoint_marshal_fail.rb
new file mode 100644
index 0000000000..74ce321de6
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_marshal_fail.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint_marshal_fail"
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointMarshalFail)
diff --git a/spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb
new file mode 100644
index 0000000000..ea4cfbe965
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint_marshal_fail"
+
+class EndpointMarshalFailBasicAuthentication < EndpointMarshalFail
+ before do
+ unless env["HTTP_AUTHORIZATION"]
+ halt 401, "Authentication info not supplied"
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointMarshalFailBasicAuthentication)
diff --git a/spec/bundler/support/artifice/endpoint_mirror_source.rb b/spec/bundler/support/artifice/endpoint_mirror_source.rb
new file mode 100644
index 0000000000..6ea1a77eca
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_mirror_source.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+
+class EndpointMirrorSource < Endpoint
+ get "/gems/:id" do
+ if request.env["HTTP_X_GEMFILE_SOURCE"] == "https://server.example.org/"
+ File.binread("#{gem_repo1}/gems/#{params[:id]}")
+ else
+ halt 500
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointMirrorSource)
diff --git a/spec/bundler/support/artifice/endpoint_redirect.rb b/spec/bundler/support/artifice/endpoint_redirect.rb
new file mode 100644
index 0000000000..84f546ba9d
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_redirect.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+
+class EndpointRedirect < Endpoint
+ get "/fetch/actual/gem/:id" do
+ redirect "/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/api/v1/dependencies" do
+ status 404
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointRedirect)
diff --git a/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb
new file mode 100644
index 0000000000..dff360c5c5
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint"
+
+class EndpointStrictBasicAuthentication < Endpoint
+ before do
+ unless env["HTTP_AUTHORIZATION"]
+ halt 401, "Authentication info not supplied"
+ end
+
+ # Only accepts password == "password"
+ unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz"
+ halt 401, "Authentication failed"
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointStrictBasicAuthentication)
diff --git a/spec/bundler/support/artifice/endpoint_timeout.rb b/spec/bundler/support/artifice/endpoint_timeout.rb
new file mode 100644
index 0000000000..86b793e499
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_timeout.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative "helpers/endpoint_fallback"
+
+class EndpointTimeout < EndpointFallback
+ SLEEP_TIMEOUT = 3
+
+ get "/api/v1/dependencies" do
+ sleep(SLEEP_TIMEOUT)
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(EndpointTimeout)
diff --git a/spec/bundler/support/artifice/fail.rb b/spec/bundler/support/artifice/fail.rb
new file mode 100644
index 0000000000..6286e43fbd
--- /dev/null
+++ b/spec/bundler/support/artifice/fail.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require "net/http"
+
+class Fail < Net::HTTP
+ # Net::HTTP uses a @newimpl instance variable to decide whether
+ # to use a legacy implementation. Since we are subclassing
+ # Net::HTTP, we must set it
+ @newimpl = true
+
+ def request(req, body = nil, &block)
+ raise(exception(req))
+ end
+
+ # Ensure we don't start a connect here
+ def connect
+ end
+
+ def exception(req)
+ name = ENV.fetch("BUNDLER_SPEC_EXCEPTION") { "Errno::ENETUNREACH" }
+ const = name.split("::").reduce(Object) {|mod, sym| mod.const_get(sym) }
+ const.new("host down: Bundler spec artifice fail! #{req["PATH_INFO"]}")
+ end
+end
+
+require_relative "helpers/artifice"
+
+# Replace Net::HTTP with our failing subclass
+Artifice.replace_net_http(::Fail)
diff --git a/spec/bundler/support/artifice/helpers/artifice.rb b/spec/bundler/support/artifice/helpers/artifice.rb
new file mode 100644
index 0000000000..b8c78614fb
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/artifice.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+# This module was initially borrowed from https://github.com/wycats/artifice
+module Artifice
+ # Activate Artifice with a particular Rack endpoint.
+ #
+ # Calling this method will replace the Net::HTTP system
+ # with a replacement that routes all requests to the
+ # Rack endpoint.
+ #
+ # @param [#call] endpoint A valid Rack endpoint
+ def self.activate_with(endpoint)
+ require_relative "rack_request"
+
+ Net::HTTP.endpoint = endpoint
+ replace_net_http(Artifice::Net::HTTP)
+ end
+
+ # Deactivate the Artifice replacement.
+ def self.deactivate
+ replace_net_http(::Net::HTTP)
+ end
+
+ def self.replace_net_http(value)
+ ::Net.class_eval do
+ remove_const(:HTTP)
+ const_set(:HTTP, value)
+ end
+ end
+end
diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb
new file mode 100644
index 0000000000..4df47a9659
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/compact_index.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+require_relative "endpoint"
+
+$LOAD_PATH.unshift Dir[Spec::Path.base_system_gem_path.join("gems/compact_index*/lib")].first.to_s
+require "compact_index"
+
+class CompactIndexAPI < Endpoint
+ helpers do
+ include Spec::Path
+
+ def load_spec(name, version, platform, gem_repo)
+ full_name = "#{name}-#{version}"
+ full_name += "-#{platform}" if platform != "ruby"
+ Marshal.load(Bundler.rubygems.inflate(File.binread(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz"))))
+ end
+
+ def etag_response
+ response_body = yield
+ checksum = Digest(:MD5).hexdigest(response_body)
+ return if not_modified?(checksum)
+ headers "ETag" => quote(checksum)
+ headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
+ content_type "text/plain"
+ requested_range_for(response_body)
+ rescue StandardError => e
+ puts e
+ puts e.backtrace
+ raise
+ end
+
+ def not_modified?(checksum)
+ etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"])
+
+ return unless etags.include?(checksum)
+ headers "ETag" => quote(checksum)
+ status 304
+ body ""
+ end
+
+ def requested_range_for(response_body)
+ ranges = Rack::Utils.byte_ranges(env, response_body.bytesize)
+
+ if ranges
+ status 206
+ body ranges.map! {|range| slice_body(response_body, range) }.join
+ else
+ status 200
+ body response_body
+ end
+ end
+
+ def quote(string)
+ %("#{string}")
+ end
+
+ def parse_etags(value)
+ value ? value.split(/, ?/).select {|s| s.sub!(/"(.*)"/, '\1') } : []
+ end
+
+ def slice_body(body, range)
+ body.byteslice(range)
+ end
+
+ def gems(gem_repo = default_gem_repo)
+ @gems ||= {}
+ @gems[gem_repo] ||= begin
+ specs = Bundler::Deprecate.skip_during do
+ %w[specs.4.8 prerelease_specs.4.8].map do |filename|
+ Marshal.load(File.open(gem_repo.join(filename)).read).map do |name, version, platform|
+ load_spec(name, version, platform, gem_repo)
+ end
+ end.flatten
+ end
+
+ specs.group_by(&:name).map do |name, versions|
+ gem_versions = versions.map do |spec|
+ deps = spec.dependencies.select {|d| d.type == :runtime }.map do |d|
+ reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ")
+ CompactIndex::Dependency.new(d.name, reqs)
+ end
+ checksum = begin
+ Digest(:SHA256).file("#{gem_repo}/gems/#{spec.original_name}.gem").base64digest
+ rescue StandardError
+ nil
+ end
+ CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil,
+ deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s)
+ end
+ CompactIndex::Gem.new(name, gem_versions)
+ end
+ end
+ end
+ end
+
+ get "/names" do
+ etag_response do
+ CompactIndex.names(gems.map(&:name))
+ end
+ end
+
+ get "/versions" do
+ etag_response do
+ file = tmp("versions.list")
+ FileUtils.rm_f(file)
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems)
+ file.contents
+ end
+ end
+
+ get "/info/:name" do
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ end
+end
diff --git a/spec/bundler/support/artifice/helpers/compact_index_extra.rb b/spec/bundler/support/artifice/helpers/compact_index_extra.rb
new file mode 100644
index 0000000000..9e742630dd
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/compact_index_extra.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require_relative "compact_index"
+
+class CompactIndexExtra < CompactIndexAPI
+ get "/extra/versions" do
+ halt 404
+ end
+
+ get "/extra/api/v1/dependencies" do
+ halt 404
+ end
+
+ get "/extra/specs.4.8.gz" do
+ File.binread("#{gem_repo2}/specs.4.8.gz")
+ end
+
+ get "/extra/prerelease_specs.4.8.gz" do
+ File.binread("#{gem_repo2}/prerelease_specs.4.8.gz")
+ end
+
+ get "/extra/quick/Marshal.4.8/:id" do
+ redirect "/extra/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/extra/fetch/actual/gem/:id" do
+ File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/extra/gems/:id" do
+ File.binread("#{gem_repo2}/gems/#{params[:id]}")
+ end
+end
diff --git a/spec/bundler/support/artifice/helpers/compact_index_extra_api.rb b/spec/bundler/support/artifice/helpers/compact_index_extra_api.rb
new file mode 100644
index 0000000000..d9a7d83d23
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/compact_index_extra_api.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require_relative "compact_index"
+
+class CompactIndexExtraApi < CompactIndexAPI
+ get "/extra/names" do
+ etag_response do
+ CompactIndex.names(gems(gem_repo4).map(&:name))
+ end
+ end
+
+ get "/extra/versions" do
+ etag_response do
+ file = tmp("versions.list")
+ FileUtils.rm_f(file)
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems(gem_repo4))
+ file.contents
+ end
+ end
+
+ get "/extra/info/:name" do
+ etag_response do
+ gem = gems(gem_repo4).find {|g| g.name == params[:name] }
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ end
+
+ get "/extra/specs.4.8.gz" do
+ File.binread("#{gem_repo4}/specs.4.8.gz")
+ end
+
+ get "/extra/prerelease_specs.4.8.gz" do
+ File.binread("#{gem_repo4}/prerelease_specs.4.8.gz")
+ end
+
+ get "/extra/quick/Marshal.4.8/:id" do
+ redirect "/extra/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/extra/fetch/actual/gem/:id" do
+ File.binread("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/extra/gems/:id" do
+ File.binread("#{gem_repo4}/gems/#{params[:id]}")
+ end
+end
diff --git a/spec/bundler/support/artifice/helpers/endpoint.rb b/spec/bundler/support/artifice/helpers/endpoint.rb
new file mode 100644
index 0000000000..fc0381dc38
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/endpoint.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require_relative "../../path"
+
+$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
+
+require "sinatra/base"
+
+ALL_REQUESTS = [] # rubocop:disable Style/MutableConstant
+ALL_REQUESTS_MUTEX = Thread::Mutex.new
+
+at_exit do
+ if expected = ENV["BUNDLER_SPEC_ALL_REQUESTS"]
+ expected = expected.split("\n").sort
+ actual = ALL_REQUESTS.sort
+
+ unless expected == actual
+ raise "Unexpected requests!\nExpected:\n\t#{expected.join("\n\t")}\n\nActual:\n\t#{actual.join("\n\t")}"
+ end
+ end
+end
+
+class Endpoint < Sinatra::Base
+ def self.all_requests
+ @all_requests ||= []
+ end
+
+ set :raise_errors, true
+ set :show_exceptions, false
+
+ def call!(*)
+ super.tap do
+ ALL_REQUESTS_MUTEX.synchronize do
+ ALL_REQUESTS << @request.url
+ end
+ end
+ end
+
+ helpers do
+ include Spec::Path
+
+ def default_gem_repo
+ if ENV["BUNDLER_SPEC_GEM_REPO"]
+ Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"])
+ else
+ case request.host
+ when "gem.repo1"
+ Spec::Path.gem_repo1
+ when "gem.repo2"
+ Spec::Path.gem_repo2
+ when "gem.repo3"
+ Spec::Path.gem_repo3
+ when "gem.repo4"
+ Spec::Path.gem_repo4
+ else
+ Spec::Path.gem_repo1
+ end
+ end
+ end
+
+ def dependencies_for(gem_names, gem_repo = default_gem_repo)
+ return [] if gem_names.nil? || gem_names.empty?
+
+ all_specs = %w[specs.4.8 prerelease_specs.4.8].map do |filename|
+ Marshal.load(File.open(gem_repo.join(filename)).read)
+ end.inject(:+)
+
+ all_specs.map do |name, version, platform|
+ spec = load_spec(name, version, platform, gem_repo)
+ next unless gem_names.include?(spec.name)
+ {
+ :name => spec.name,
+ :number => spec.version.version,
+ :platform => spec.platform.to_s,
+ :dependencies => spec.dependencies.select {|dep| dep.type == :runtime }.map do |dep|
+ [dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")]
+ end,
+ }
+ end.compact
+ end
+
+ def load_spec(name, version, platform, gem_repo)
+ full_name = "#{name}-#{version}"
+ full_name += "-#{platform}" if platform != "ruby"
+ Marshal.load(Bundler.rubygems.inflate(File.binread(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz"))))
+ end
+ end
+
+ get "/quick/Marshal.4.8/:id" do
+ redirect "/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/fetch/actual/gem/:id" do
+ File.binread("#{default_gem_repo}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/gems/:id" do
+ File.binread("#{default_gem_repo}/gems/#{params[:id]}")
+ end
+
+ get "/api/v1/dependencies" do
+ Marshal.dump(dependencies_for(params[:gems]))
+ end
+
+ get "/specs.4.8.gz" do
+ File.binread("#{default_gem_repo}/specs.4.8.gz")
+ end
+
+ get "/prerelease_specs.4.8.gz" do
+ File.binread("#{default_gem_repo}/prerelease_specs.4.8.gz")
+ end
+end
diff --git a/spec/bundler/support/artifice/helpers/endpoint_extra.rb b/spec/bundler/support/artifice/helpers/endpoint_extra.rb
new file mode 100644
index 0000000000..ad08495b50
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/endpoint_extra.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require_relative "endpoint"
+
+class EndpointExtra < Endpoint
+ get "/extra/api/v1/dependencies" do
+ halt 404
+ end
+
+ get "/extra/specs.4.8.gz" do
+ File.binread("#{gem_repo2}/specs.4.8.gz")
+ end
+
+ get "/extra/prerelease_specs.4.8.gz" do
+ File.binread("#{gem_repo2}/prerelease_specs.4.8.gz")
+ end
+
+ get "/extra/quick/Marshal.4.8/:id" do
+ redirect "/extra/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/extra/fetch/actual/gem/:id" do
+ File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/extra/gems/:id" do
+ File.binread("#{gem_repo2}/gems/#{params[:id]}")
+ end
+end
diff --git a/spec/bundler/support/artifice/helpers/endpoint_fallback.rb b/spec/bundler/support/artifice/helpers/endpoint_fallback.rb
new file mode 100644
index 0000000000..a232930b67
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/endpoint_fallback.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative "endpoint"
+
+class EndpointFallback < Endpoint
+ DEPENDENCY_LIMIT = 60
+
+ get "/api/v1/dependencies" do
+ if params[:gems] && params[:gems].size <= DEPENDENCY_LIMIT
+ Marshal.dump(dependencies_for(params[:gems]))
+ else
+ halt 413, "Too many gems to resolve, please request less than #{DEPENDENCY_LIMIT} gems"
+ end
+ end
+end
diff --git a/spec/bundler/support/artifice/helpers/endpoint_marshal_fail.rb b/spec/bundler/support/artifice/helpers/endpoint_marshal_fail.rb
new file mode 100644
index 0000000000..c409d39d99
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/endpoint_marshal_fail.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require_relative "endpoint_fallback"
+
+class EndpointMarshalFail < EndpointFallback
+ get "/api/v1/dependencies" do
+ "f0283y01hasf"
+ end
+end
diff --git a/spec/bundler/support/artifice/helpers/rack_request.rb b/spec/bundler/support/artifice/helpers/rack_request.rb
new file mode 100644
index 0000000000..c4a07812a6
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/rack_request.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require "rack/test"
+require "net/http"
+
+module Artifice
+ module Net
+ # This is an internal object that can receive Rack requests
+ # to the application using the Rack::Test API
+ class RackRequest
+ include Rack::Test::Methods
+ attr_reader :app
+
+ def initialize(app)
+ @app = app
+ end
+ end
+
+ class HTTP < ::Net::HTTP
+ class << self
+ attr_accessor :endpoint
+ end
+
+ # Net::HTTP uses a @newimpl instance variable to decide whether
+ # to use a legacy implementation. Since we are subclassing
+ # Net::HTTP, we must set it
+ @newimpl = true
+
+ # We don't need to connect, so blank out this method
+ def connect
+ end
+
+ # Replace the Net::HTTP request method with a method
+ # that converts the request into a Rack request and
+ # dispatches it to the Rack endpoint.
+ #
+ # @param [Net::HTTPRequest] req A Net::HTTPRequest
+ # object, or one if its subclasses
+ # @param [optional, String, #read] body This should
+ # be sent as "rack.input". If it's a String, it will
+ # be converted to a StringIO.
+ # @return [Net::HTTPResponse]
+ #
+ # @yield [Net::HTTPResponse] If a block is provided,
+ # this method will yield the Net::HTTPResponse to
+ # it after the body is read.
+ def request(req, body = nil, &block)
+ rack_request = RackRequest.new(self.class.endpoint)
+
+ req.each_header do |header, value|
+ rack_request.header(header, value)
+ end
+
+ scheme = use_ssl? ? "https" : "http"
+ prefix = "#{scheme}://#{addr_port}"
+ body_stream_contents = req.body_stream.read if req.body_stream
+
+ response = rack_request.request("#{prefix}#{req.path}",
+ { :method => req.method, :input => body || req.body || body_stream_contents })
+
+ make_net_http_response(response, &block)
+ end
+
+ private
+
+ # This method takes a Rack response and creates a Net::HTTPResponse
+ # Instead of trying to mock HTTPResponse directly, we just convert
+ # the Rack response into a String that looks like a normal HTTP
+ # response and call Net::HTTPResponse.read_new
+ #
+ # @param [Array(#to_i, Hash, #each)] response a Rack response
+ # @return [Net::HTTPResponse]
+ # @yield [Net::HTTPResponse] If a block is provided, yield the
+ # response to it after the body is read
+ def make_net_http_response(response)
+ status = response.status
+ headers = response.headers
+ body = response.body
+
+ response_string = []
+ response_string << "HTTP/1.1 #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
+
+ headers.each do |header, value|
+ response_string << "#{header}: #{value}"
+ end
+
+ response_string << "" << body
+
+ response_io = ::Net::BufferedIO.new(StringIO.new(response_string.join("\n")))
+ res = ::Net::HTTPResponse.read_new(response_io)
+
+ res.reading_body(response_io, true) do
+ yield res if block_given?
+ end
+
+ res
+ end
+ end
+ end
+end
diff --git a/spec/bundler/support/artifice/used_cassettes.txt b/spec/bundler/support/artifice/used_cassettes.txt
new file mode 100644
index 0000000000..a96efc790d
--- /dev/null
+++ b/spec/bundler/support/artifice/used_cassettes.txt
@@ -0,0 +1,20908 @@
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ffi-1.15.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uglifier/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/matrix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-active_record/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/therubyracer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-formatter-dots/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-html-formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-gherkin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-cucumber-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-messages/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-create-meta/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-uname/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libv8/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/c21e/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag_expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-tag-expressions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-protobuf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/protobuf-cucumber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fugit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/raabro/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.2.3.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-4.5.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/ruby2_keywords-0.0.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mustermann-1.1.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.6.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-2.1.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-2.0.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/terrapin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-generator/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sassc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_http/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-net_http_persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-em_synchrony/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-httpclient/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-patron/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday-rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trailblazer-option/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrick/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-apis-discovery_v1/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gems/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/excon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-http-persistent/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday_middleware/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rash/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/oauth2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/roauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpauth/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.10.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongoid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongodb-mongo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/durran-validatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mislav-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongo_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/leshill-will_paginate/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bson/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/origin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/moped/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby2_keywords/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pry/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/optionable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/connection_pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coderay/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metasploit-erd/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/yard/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-graphviz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/choice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/webrat/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-collection_matchers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov_json_formatter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activestorage/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actiontext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailbox/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hpricot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-accept/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sync/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-serializers-xml/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/marcel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_mime/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ftp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hocon/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/time/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-protocol/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jaro_winkler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parallel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop-ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/regexp_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/psych/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/date/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/io-wait/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timeout/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jar-dependencies/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strscan/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/maven-tools/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-maven-libs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-2.3.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/concurrent-ruby-1.1.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.9.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/zeitwerk/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pastel/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-pager/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-screen/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-tree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equatable/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-which/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tty-color/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/verse/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode_utils/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/strings-ansi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.15.0.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.4.4.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/response
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/request
+spec/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.9.gem/GET/response
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
+spec/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/response
diff --git a/spec/bundler/support/artifice/vcr.rb b/spec/bundler/support/artifice/vcr.rb
new file mode 100644
index 0000000000..6a346f1ff9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+require "net/http"
+require_relative "../path"
+
+CASSETTE_PATH = "#{Spec::Path.spec_dir}/support/artifice/vcr_cassettes"
+USED_CASSETTES_PATH = "#{Spec::Path.spec_dir}/support/artifice/used_cassettes.txt"
+CASSETTE_NAME = ENV.fetch("BUNDLER_SPEC_VCR_CASSETTE_NAME") { "realworld" }
+
+class BundlerVCRHTTP < Net::HTTP
+ class RequestHandler
+ attr_reader :http, :request, :body, :response_block
+ def initialize(http, request, body = nil, &response_block)
+ @http = http
+ @request = request
+ @body = body
+ @response_block = response_block
+ end
+
+ def handle_request
+ handler = self
+ request.instance_eval do
+ @__vcr_request_handler = handler
+ end
+
+ File.open(USED_CASSETTES_PATH, "a+") do |f|
+ f.puts request_pair_paths.map {|path| Pathname.new(path).relative_path_from(Spec::Path.source_root).to_s }.join("\n")
+ end
+
+ if recorded_response?
+ recorded_response
+ else
+ record_response
+ end
+ end
+
+ def recorded_response?
+ return true if ENV["BUNDLER_SPEC_PRE_RECORDED"]
+ request_pair_paths.all? {|f| File.exist?(f) }
+ end
+
+ def recorded_response
+ File.open(request_pair_paths.last, "rb:ASCII-8BIT") do |response_file|
+ response_io = ::Net::BufferedIO.new(response_file)
+ ::Net::HTTPResponse.read_new(response_io).tap do |response|
+ response.decode_content = request.decode_content if request.respond_to?(:decode_content)
+ response.uri = request.uri
+
+ response.reading_body(response_io, request.response_body_permitted?) do
+ response_block&.call(response)
+ end
+ end
+ end
+ end
+
+ def record_response
+ request_path, response_path = *request_pair_paths
+
+ @recording = true
+
+ response = http.request_without_vcr(request, body, &response_block)
+ @recording = false
+ unless @recording
+ require "fileutils"
+ FileUtils.mkdir_p(File.dirname(request_path))
+ binwrite(request_path, request_to_string(request))
+ binwrite(response_path, response_to_string(response))
+ end
+ response
+ end
+
+ def key
+ [request["host"] || http.address, request.path, request.method].compact
+ end
+
+ def file_name_for_key(key)
+ File.join(*key).gsub(/[\:*?"<>|]/, "-")
+ end
+
+ def request_pair_paths
+ %w[request response].map do |kind|
+ File.join(CASSETTE_PATH, CASSETTE_NAME, file_name_for_key(key), kind)
+ end
+ end
+
+ def request_to_string(request)
+ request_string = []
+ request_string << "> #{request.method.upcase} #{request.path}"
+ request.to_hash.each do |key, value|
+ request_string << "> #{key}: #{Array(value).first}"
+ end
+ request << "" << request.body if request.body
+ request_string.join("\n")
+ end
+
+ def response_to_string(response)
+ headers = response.to_hash
+ body = response.body
+
+ response_string = []
+ response_string << "HTTP/1.1 #{response.code} #{response.message}"
+
+ headers["content-length"] = [body.bytesize.to_s] if body
+
+ headers.each do |header, value|
+ response_string << "#{header}: #{value.join(", ")}"
+ end
+
+ response_string << "" << body
+
+ response_string = response_string.join("\n")
+ if response_string.respond_to?(:force_encoding)
+ response_string.force_encoding("ASCII-8BIT")
+ else
+ response_string
+ end
+ end
+
+ def binwrite(path, contents)
+ File.open(path, "wb:ASCII-8BIT") {|f| f.write(contents) }
+ end
+ end
+
+ def start_with_vcr
+ if ENV["BUNDLER_SPEC_PRE_RECORDED"]
+ raise IOError, "HTTP session already opened" if @started
+ @socket = nil
+ @started = true
+ else
+ start_without_vcr
+ end
+ end
+
+ alias_method :start_without_vcr, :start
+ alias_method :start, :start_with_vcr
+
+ def request_with_vcr(request, *args, &block)
+ handler = request.instance_eval do
+ remove_instance_variable(:@__vcr_request_handler) if defined?(@__vcr_request_handler)
+ end || RequestHandler.new(self, request, *args, &block)
+
+ handler.handle_request
+ end
+
+ alias_method :request_without_vcr, :request
+ alias_method :request, :request_with_vcr
+end
+
+require_relative "helpers/artifice"
+
+# Replace Net::HTTP with our VCR subclass
+Artifice.replace_net_http(BundlerVCRHTTP)
diff --git a/spec/bundler/support/artifice/windows.rb b/spec/bundler/support/artifice/windows.rb
new file mode 100644
index 0000000000..4d90e0a426
--- /dev/null
+++ b/spec/bundler/support/artifice/windows.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require_relative "../path"
+
+$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
+
+require "sinatra/base"
+
+class Windows < Sinatra::Base
+ set :raise_errors, true
+ set :show_exceptions, false
+
+ helpers do
+ def default_gem_repo
+ Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"] || Spec::Path.gem_repo1)
+ end
+ end
+
+ files = ["specs.4.8.gz",
+ "prerelease_specs.4.8.gz",
+ "quick/Marshal.4.8/rcov-1.0-mswin32.gemspec.rz",
+ "gems/rcov-1.0-mswin32.gem"]
+
+ files.each do |file|
+ get "/#{file}" do
+ File.binread default_gem_repo.join(file)
+ end
+ end
+
+ get "/gems/rcov-1.0-x86-mswin32.gem" do
+ halt 404
+ end
+
+ get "/api/v1/dependencies" do
+ halt 404
+ end
+
+ get "/versions" do
+ halt 500
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(Windows)
diff --git a/spec/bundler/support/build_metadata.rb b/spec/bundler/support/build_metadata.rb
new file mode 100644
index 0000000000..98d8ac23c8
--- /dev/null
+++ b/spec/bundler/support/build_metadata.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require_relative "path"
+require_relative "helpers"
+
+module Spec
+ module BuildMetadata
+ include Spec::Path
+ include Spec::Helpers
+
+ def write_build_metadata(dir: source_root)
+ build_metadata = {
+ :git_commit_sha => git_commit_sha,
+ :built_at => loaded_gemspec.date.utc.strftime("%Y-%m-%d"),
+ :release => true,
+ }
+
+ replace_build_metadata(build_metadata, dir: dir) # rubocop:disable Style/HashSyntax
+ end
+
+ def reset_build_metadata(dir: source_root)
+ build_metadata = {
+ :release => false,
+ }
+
+ replace_build_metadata(build_metadata, dir: dir) # rubocop:disable Style/HashSyntax
+ end
+
+ private
+
+ def replace_build_metadata(build_metadata, dir:)
+ build_metadata_file = File.expand_path("lib/bundler/build_metadata.rb", dir)
+
+ ivars = build_metadata.sort.map do |k, v|
+ " @#{k} = #{loaded_gemspec.send(:ruby_code, v)}"
+ end.join("\n")
+
+ contents = File.read(build_metadata_file)
+ contents.sub!(/^(\s+# begin ivars).+(^\s+# end ivars)/m, "\\1\n#{ivars}\n\\2")
+ File.open(build_metadata_file, "w") {|f| f << contents }
+ end
+
+ def git_commit_sha
+ ruby_core_tarball? ? "unknown" : sys_exec("git rev-parse --short HEAD", :dir => source_root).strip
+ end
+
+ extend self
+ end
+end
diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb
new file mode 100644
index 0000000000..dfc7139523
--- /dev/null
+++ b/spec/bundler/support/builders.rb
@@ -0,0 +1,677 @@
+# frozen_string_literal: true
+
+require "bundler/shared_helpers"
+require "shellwords"
+
+module Spec
+ module Builders
+ def self.constantize(name)
+ name.delete("-").upcase
+ end
+
+ def v(version)
+ Gem::Version.new(version)
+ end
+
+ def pl(platform)
+ Gem::Platform.new(platform)
+ end
+
+ def build_repo1
+ rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first
+
+ build_repo gem_repo1 do
+ FileUtils.cp rake_path, "#{gem_repo1}/gems/"
+
+ build_gem "coffee-script-source"
+ build_gem "git"
+ build_gem "puma"
+ build_gem "minitest"
+
+ build_gem "rack", %w[0.9.1 1.0.0] do |s|
+ s.executables = "rackup"
+ s.post_install_message = "Rack's post install message"
+ end
+
+ build_gem "thin" do |s|
+ s.add_dependency "rack"
+ s.post_install_message = "Thin's post install message"
+ end
+
+ build_gem "rack-obama" do |s|
+ s.add_dependency "rack"
+ s.post_install_message = "Rack-obama's post install message"
+ end
+
+ build_gem "rack_middleware", "1.0" do |s|
+ s.add_dependency "rack", "0.9.1"
+ end
+
+ build_gem "rails", "2.3.2" do |s|
+ s.executables = "rails"
+ s.add_dependency "rake", "13.0.1"
+ s.add_dependency "actionpack", "2.3.2"
+ s.add_dependency "activerecord", "2.3.2"
+ s.add_dependency "actionmailer", "2.3.2"
+ s.add_dependency "activeresource", "2.3.2"
+ end
+ build_gem "actionpack", "2.3.2" do |s|
+ s.add_dependency "activesupport", "2.3.2"
+ end
+ build_gem "activerecord", ["2.3.1", "2.3.2"] do |s|
+ s.add_dependency "activesupport", "2.3.2"
+ end
+ build_gem "actionmailer", "2.3.2" do |s|
+ s.add_dependency "activesupport", "2.3.2"
+ end
+ build_gem "activeresource", "2.3.2" do |s|
+ s.add_dependency "activesupport", "2.3.2"
+ end
+ build_gem "activesupport", %w[1.2.3 2.3.2 2.3.5]
+
+ build_gem "activemerchant" do |s|
+ s.add_dependency "activesupport", ">= 2.0.0"
+ end
+
+ build_gem "rspec", "1.2.7", :no_default => true do |s|
+ s.write "lib/spec.rb", "SPEC = '1.2.7'"
+ end
+
+ build_gem "rack-test", :no_default => true do |s|
+ s.write "lib/rack/test.rb", "RACK_TEST = '1.0'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = Gem::Platform.local
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Gem::Platform.local}'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "java"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 JAVA'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "ruby"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "x86-mswin32"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x86-mswin32'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "x64-mswin64"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mswin64'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "x86-mingw32"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x86-mingw32'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "x64-mingw32"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mingw32'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "x64-mingw-ucrt"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mingw-ucrt'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "x86-darwin-100"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 x86-darwin-100'"
+ end
+
+ build_gem "only_java", "1.0" do |s|
+ s.platform = "java"
+ s.write "lib/only_java.rb", "ONLY_JAVA = '1.0.0 JAVA'"
+ end
+
+ build_gem "only_java", "1.1" do |s|
+ s.platform = "java"
+ s.write "lib/only_java.rb", "ONLY_JAVA = '1.1.0 JAVA'"
+ end
+
+ build_gem "nokogiri", "1.4.2"
+ build_gem "nokogiri", "1.4.2" do |s|
+ s.platform = "java"
+ s.write "lib/nokogiri.rb", "NOKOGIRI = '1.4.2 JAVA'"
+ s.add_dependency "weakling", ">= 0.0.3"
+ end
+
+ build_gem "laduradura", "5.15.2"
+ build_gem "laduradura", "5.15.2" do |s|
+ s.platform = "java"
+ s.write "lib/laduradura.rb", "LADURADURA = '5.15.2 JAVA'"
+ end
+ build_gem "laduradura", "5.15.3" do |s|
+ s.platform = "java"
+ s.write "lib/laduradura.rb", "LADURADURA = '5.15.2 JAVA'"
+ end
+
+ build_gem "weakling", "0.0.3"
+
+ build_gem "terranova", "8"
+
+ build_gem "duradura", "7.0"
+
+ build_gem "very_simple_binary", &:add_c_extension
+ build_gem "simple_binary", &:add_c_extension
+
+ build_gem "bundler", "0.9" do |s|
+ s.executables = "bundle"
+ s.write "bin/bundle", "puts 'FAIL'"
+ end
+
+ # The bundler 0.8 gem has a rubygems plugin that always loads :(
+ build_gem "bundler", "0.8.1" do |s|
+ s.write "lib/bundler/omg.rb", ""
+ s.write "lib/rubygems_plugin.rb", "require 'bundler/omg' ; puts 'FAIL'"
+ end
+
+ # The yard gem iterates over Gem.source_index looking for plugins
+ build_gem "yard" do |s|
+ s.write "lib/yard.rb", <<-Y
+ Gem::Specification.sort_by(&:name).each do |gem|
+ puts gem.full_name
+ end
+ Y
+ end
+
+ build_gem "net-ssh"
+ build_gem "net-sftp", "1.1.1" do |s|
+ s.add_dependency "net-ssh", ">= 1.0.0", "< 1.99.0"
+ end
+
+ build_gem "foo"
+ end
+ end
+
+ def build_repo2(&blk)
+ FileUtils.rm_rf gem_repo2
+ FileUtils.cp_r gem_repo1, gem_repo2
+ update_repo2(&blk) if block_given?
+ end
+
+ # A repo that has no pre-installed gems included. (The caller completely
+ # determines the contents with the block.)
+ def build_repo4(&blk)
+ FileUtils.rm_rf gem_repo4
+ build_repo(gem_repo4, &blk)
+ end
+
+ def update_repo4(&blk)
+ update_repo(gem_repo4, &blk)
+ end
+
+ def update_repo2(&blk)
+ update_repo(gem_repo2, &blk)
+ end
+
+ def build_security_repo
+ build_repo security_repo do
+ build_gem "rack"
+
+ build_gem "signed_gem" do |s|
+ cert = "signing-cert.pem"
+ pkey = "signing-pkey.pem"
+ s.write cert, TEST_CERT
+ s.write pkey, TEST_PKEY
+ s.signing_key = pkey
+ s.cert_chain = [cert]
+ end
+ end
+ end
+
+ def build_repo(path, &blk)
+ return if File.directory?(path)
+
+ FileUtils.mkdir_p("#{path}/gems")
+
+ update_repo(path, &blk)
+ end
+
+ def check_test_gems!
+ rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first
+
+ if rake_path.nil?
+ FileUtils.rm_rf(Path.base_system_gems)
+ Spec::Rubygems.install_test_deps
+ rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first
+ end
+
+ if rake_path.nil?
+ abort "Your test gems are missing! Run `rm -rf #{tmp}` and try again."
+ end
+ end
+
+ def update_repo(path)
+ if path == gem_repo1 && caller.first.split(" ").last == "`build_repo`"
+ raise "Updating gem_repo1 is unsupported -- use gem_repo2 instead"
+ end
+ return unless block_given?
+ @_build_path = "#{path}/gems"
+ @_build_repo = File.basename(path)
+ yield
+ with_gem_path_as Path.base_system_gem_path do
+ gem_command :generate_index, :dir => path
+ end
+ ensure
+ @_build_path = nil
+ @_build_repo = nil
+ end
+
+ def build_index(&block)
+ index = Bundler::Index.new
+ IndexBuilder.run(index, &block) if block_given?
+ index
+ end
+
+ def build_spec(name, version = "0.0.1", platform = nil, &block)
+ Array(version).map do |v|
+ Gem::Specification.new do |s|
+ s.name = name
+ s.version = Gem::Version.new(v)
+ s.platform = platform
+ s.authors = ["no one in particular"]
+ s.summary = "a gemspec used only for testing"
+ DepBuilder.run(s, &block) if block_given?
+ end
+ end
+ end
+
+ def build_lib(name, *args, &blk)
+ build_with(LibBuilder, name, args, &blk)
+ end
+
+ def build_gem(name, *args, &blk)
+ build_with(GemBuilder, name, args, &blk)
+ end
+
+ def build_git(name, *args, &block)
+ opts = args.last.is_a?(Hash) ? args.last : {}
+ builder = opts[:bare] ? GitBareBuilder : GitBuilder
+ spec = build_with(builder, name, args, &block)
+ GitReader.new(self, opts[:path] || lib_path(spec.full_name))
+ end
+
+ def update_git(name, *args, &block)
+ opts = args.last.is_a?(Hash) ? args.last : {}
+ spec = build_with(GitUpdater, name, args, &block)
+ GitReader.new(self, opts[:path] || lib_path(spec.full_name))
+ end
+
+ def build_plugin(name, *args, &blk)
+ build_with(PluginBuilder, name, args, &blk)
+ end
+
+ private
+
+ def build_with(builder, name, args, &blk)
+ @_build_path ||= nil
+ @_build_repo ||= nil
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ versions = args.last || "1.0"
+ spec = nil
+
+ options[:path] ||= @_build_path
+ options[:source] ||= @_build_repo
+
+ Array(versions).each do |version|
+ spec = builder.new(self, name, version)
+ yield spec if block_given?
+ spec._build(options)
+ end
+
+ spec
+ end
+
+ class IndexBuilder
+ include Builders
+
+ def self.run(index, &block)
+ new(index).run(&block)
+ end
+
+ def initialize(index)
+ @index = index
+ end
+
+ def run(&block)
+ instance_eval(&block)
+ end
+
+ def gem(*args, &block)
+ build_spec(*args, &block).each do |s|
+ @index << s
+ end
+ end
+
+ def platforms(platforms)
+ platforms.split(/\s+/).each do |platform|
+ platform.gsub!(/^(mswin32)$/, 'x86-\1')
+ yield Gem::Platform.new(platform)
+ end
+ end
+
+ def versions(versions)
+ versions.split(/\s+/).each {|version| yield v(version) }
+ end
+ end
+
+ class DepBuilder
+ include Builders
+
+ def self.run(spec, &block)
+ new(spec).run(&block)
+ end
+
+ def initialize(spec)
+ @spec = spec
+ end
+
+ def run(&block)
+ instance_eval(&block)
+ end
+
+ def runtime(name, requirements)
+ @spec.add_runtime_dependency(name, requirements)
+ end
+
+ def development(name, requirements)
+ @spec.add_development_dependency(name, requirements)
+ end
+
+ def required_ruby_version=(*reqs)
+ @spec.required_ruby_version = *reqs
+ end
+
+ alias_method :dep, :runtime
+ end
+
+ class LibBuilder
+ def initialize(context, name, version)
+ @context = context
+ @name = name
+ @spec = Gem::Specification.new do |s|
+ s.name = name
+ s.version = version
+ s.summary = "This is just a fake gem for testing"
+ s.description = "This is a completely fake gem, for testing purposes."
+ s.author = "no one"
+ s.email = "foo@bar.baz"
+ s.homepage = "http://example.com"
+ s.license = "MIT"
+ end
+ @files = {}
+ end
+
+ def method_missing(*args, &blk)
+ @spec.send(*args, &blk)
+ end
+
+ def write(file, source = "")
+ @files[file] = source
+ end
+
+ def executables=(val)
+ @spec.executables = Array(val)
+ @spec.executables.each do |file|
+ executable = "#{@spec.bindir}/#{file}"
+ shebang = if Bundler.current_ruby.jruby?
+ "#!/usr/bin/env jruby\n"
+ else
+ "#!/usr/bin/env ruby\n"
+ end
+ @spec.files << executable
+ write executable, "#{shebang}require_relative '../lib/#{@name}' ; puts #{Builders.constantize(@name)}"
+ end
+ end
+
+ def add_c_extension
+ require_paths << "ext"
+ extensions << "ext/extconf.rb"
+ write "ext/extconf.rb", <<-RUBY
+ require "mkmf"
+
+ $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"]
+
+ extension_name = "#{name}_c"
+ if extra_lib_dir = with_config("ext-lib")
+ # add extra libpath if --with-ext-lib is
+ # passed in as a build_arg
+ dir_config extension_name, nil, extra_lib_dir
+ else
+ dir_config extension_name
+ end
+ create_makefile extension_name
+ RUBY
+ write "ext/#{name}.c", <<-C
+ #include "ruby.h"
+
+ void Init_#{name}_c() {
+ rb_define_module("#{Builders.constantize(name)}_IN_C");
+ }
+ C
+ end
+
+ def _build(options)
+ path = options[:path] || _default_path
+
+ if options[:rubygems_version]
+ @spec.rubygems_version = options[:rubygems_version]
+ def @spec.mark_version; end
+
+ def @spec.validate(*); end
+ end
+
+ unless options[:no_default]
+ gem_source = options[:source] || "path@#{path}"
+ @files = _default_files.
+ merge("lib/#{entrypoint}/source.rb" => "#{Builders.constantize(name)}_SOURCE = #{gem_source.to_s.dump}").
+ merge(@files)
+ end
+
+ @spec.authors = ["no one"]
+ @spec.files += @files.keys
+
+ case options[:gemspec]
+ when false
+ # do nothing
+ when :yaml
+ @spec.files << "#{name}.gemspec"
+ @files["#{name}.gemspec"] = @spec.to_yaml
+ else
+ @spec.files << "#{name}.gemspec"
+ @files["#{name}.gemspec"] = @spec.to_ruby
+ end
+
+ @files.each do |file, source|
+ file = Pathname.new(path).join(file)
+ FileUtils.mkdir_p(file.dirname)
+ File.open(file, "w") {|f| f.puts source }
+ File.chmod("+x", file) if @spec.executables.map {|exe| "#{@spec.bindir}/#{exe}" }.include?(file)
+ end
+ path
+ end
+
+ def _default_files
+ @_default_files ||= { "lib/#{entrypoint}.rb" => "#{Builders.constantize(name)} = '#{version}#{platform_string}'" }
+ end
+
+ def entrypoint
+ name.tr("-", "/")
+ end
+
+ def _default_path
+ @context.tmp("libs", @spec.full_name)
+ end
+
+ def platform_string
+ " #{@spec.platform}" unless @spec.platform == Gem::Platform::RUBY
+ end
+ end
+
+ class GitBuilder < LibBuilder
+ def _build(options)
+ default_branch = options[:default_branch] || "main"
+ path = options[:path] || _default_path
+ source = options[:source] || "git@#{path}"
+ super(options.merge(:path => path, :source => source))
+ @context.git("config --global init.defaultBranch #{default_branch}", path)
+ @context.git("init", path)
+ @context.git("add *", path)
+ @context.git("config user.email lol@wut.com", path)
+ @context.git("config user.name lolwut", path)
+ @context.git("config commit.gpgsign false", path)
+ @context.git("commit -m OMG_INITIAL_COMMIT", path)
+ end
+ end
+
+ class GitBareBuilder < LibBuilder
+ def _build(options)
+ path = options[:path] || _default_path
+ super(options.merge(:path => path))
+ @context.git("init --bare", path)
+ end
+ end
+
+ class GitUpdater < LibBuilder
+ def _build(options)
+ libpath = options[:path] || _default_path
+ update_gemspec = options[:gemspec] || false
+ source = options[:source] || "git@#{libpath}"
+
+ if branch = options[:branch]
+ @context.git("checkout -b #{Shellwords.shellescape(branch)}", libpath)
+ elsif tag = options[:tag]
+ @context.git("tag #{Shellwords.shellescape(tag)}", libpath)
+ elsif options[:remote]
+ @context.git("remote add origin #{options[:remote]}", libpath)
+ elsif options[:push]
+ @context.git("push origin #{options[:push]}", libpath)
+ end
+
+ current_ref = @context.git("rev-parse HEAD", libpath).strip
+ _default_files.keys.each do |path|
+ _default_files[path] += "\n#{Builders.constantize(name)}_PREV_REF = '#{current_ref}'"
+ end
+ super(options.merge(:path => libpath, :gemspec => update_gemspec, :source => source))
+ @context.git("commit -am BUMP", libpath)
+ end
+ end
+
+ class GitReader
+ attr_reader :context, :path
+
+ def initialize(context, path)
+ @context = context
+ @path = path
+ end
+
+ def ref_for(ref, len = nil)
+ ref = context.git "rev-parse #{ref}", path
+ ref = ref[0..len] if len
+ ref
+ end
+ end
+
+ class GemBuilder < LibBuilder
+ def _build(opts)
+ lib_path = opts[:lib_path] || @context.tmp(".tmp/#{@spec.full_name}")
+ lib_path = super(opts.merge(:path => lib_path, :no_default => opts[:no_default]))
+ destination = opts[:path] || _default_path
+ FileUtils.mkdir_p(lib_path.join(destination))
+
+ if opts[:gemspec] == :yaml || opts[:gemspec] == false
+ Dir.chdir(lib_path) do
+ Bundler.rubygems.build(@spec, opts[:skip_validation])
+ end
+ elsif opts[:skip_validation]
+ @context.gem_command "build --force #{@spec.name}", :dir => lib_path
+ else
+ @context.gem_command "build #{@spec.name}", :dir => lib_path
+ end
+
+ gem_path = File.expand_path("#{@spec.full_name}.gem", lib_path)
+ if opts[:to_system]
+ @context.system_gems gem_path, :default => opts[:default]
+ elsif opts[:to_bundle]
+ @context.system_gems gem_path, :path => @context.default_bundle_path
+ else
+ FileUtils.mv(gem_path, destination)
+ end
+ end
+
+ def _default_path
+ @context.gem_repo1("gems")
+ end
+ end
+
+ class PluginBuilder < GemBuilder
+ def _default_files
+ @_default_files ||= {
+ "lib/#{name}.rb" => "#{Builders.constantize(name)} = '#{version}#{platform_string}'",
+ "plugins.rb" => "",
+ }
+ end
+ end
+
+ TEST_CERT = <<-CERT.gsub(/^\s*/, "")
+ -----BEGIN CERTIFICATE-----
+ MIIDMjCCAhqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAnMQwwCgYDVQQDDAN5b3Ux
+ FzAVBgoJkiaJk/IsZAEZFgdleGFtcGxlMB4XDTE1MDIwODAwMTIyM1oXDTQyMDYy
+ NTAwMTIyM1owJzEMMAoGA1UEAwwDeW91MRcwFQYKCZImiZPyLGQBGRYHZXhhbXBs
+ ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANlvFdpN43c4DMS9Jo06
+ m0a7k3bQ3HWQ1yrYhZMi77F1F73NpBknYHIzDktQpGn6hs/4QFJT4m4zNEBF47UL
+ jHU5nTK5rjkS3niGYUjvh3ZEzVeo9zHUlD/UwflDo4ALl3TSo2KY/KdPS/UTdLXL
+ ajkQvaVJtEDgBPE3DPhlj5whp+Ik3mDHej7qpV6F502leAwYaFyOtlEG/ZGNG+nZ
+ L0clH0j77HpP42AylHDi+vakEM3xcjo9BeWQ6Vkboic93c9RTt6CWBWxMQP7Nol1
+ MOebz9XOSQclxpxWteXNfPRtMdAhmRl76SMI8ywzThNPpa4EH/yz34ftebVOgKyM
+ nd0CAwEAAaNpMGcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFA7D
+ n9qo0np23qi3aOYuAAPn/5IdMBYGA1UdEQQPMA2BC3lvdUBleGFtcGxlMBYGA1Ud
+ EgQPMA2BC3lvdUBleGFtcGxlMA0GCSqGSIb3DQEBBQUAA4IBAQA7Gyk62sWOUX/N
+ vk4tJrgKESph6Ns8+E36A7n3jt8zCep8ldzMvwTWquf9iqhsC68FilEoaDnUlWw7
+ d6oNuaFkv7zfrWGLlvqQJC+cu2X5EpcCksg5oRp8VNbwJysJ6JgwosxzROII8eXc
+ R+j1j6mDvQYqig2QOnzf480pjaqbP+tspfDFZbhKPrgM3Blrb3ZYuFpv4zkqI7aB
+ 6fuk2DUhNO1CuwrJA84TqC+jGo73bDKaT5hrIDiaJRrN5+zcWja2uEWrj5jSbep4
+ oXdEdyH73hOHMBP40uds3PqnUsxEJhzjB2sCCe1geV24kw9J4m7EQXPVkUKDgKrt
+ LlpDmOoo
+ -----END CERTIFICATE-----
+ CERT
+
+ TEST_PKEY = <<-PKEY.gsub(/^\s*/, "")
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEowIBAAKCAQEA2W8V2k3jdzgMxL0mjTqbRruTdtDcdZDXKtiFkyLvsXUXvc2k
+ GSdgcjMOS1CkafqGz/hAUlPibjM0QEXjtQuMdTmdMrmuORLeeIZhSO+HdkTNV6j3
+ MdSUP9TB+UOjgAuXdNKjYpj8p09L9RN0tctqORC9pUm0QOAE8TcM+GWPnCGn4iTe
+ YMd6PuqlXoXnTaV4DBhoXI62UQb9kY0b6dkvRyUfSPvsek/jYDKUcOL69qQQzfFy
+ Oj0F5ZDpWRuiJz3dz1FO3oJYFbExA/s2iXUw55vP1c5JByXGnFa15c189G0x0CGZ
+ GXvpIwjzLDNOE0+lrgQf/LPfh+15tU6ArIyd3QIDAQABAoIBACbDqz20TS1gDMa2
+ gj0DidNedbflHKjJHdNBru7Ad8NHgOgR1YO2hXdWquG6itVqGMbTF4SV9/R1pIcg
+ 7qvEV1I+50u31tvOBWOvcYCzU48+TO2n7gowQA3xPHPYHzog1uu48fAOHl0lwgD7
+ av9OOK3b0jO5pC08wyTOD73pPWU0NrkTh2+N364leIi1pNuI1z4V+nEuIIm7XpVd
+ 5V4sXidMTiEMJwE6baEDfTjHKaoRndXrrPo3ryIXmcX7Ag1SwAQwF5fBCRToCgIx
+ dszEZB1bJD5gA6r+eGnJLB/F60nK607az5o3EdguoB2LKa6q6krpaRCmZU5svvoF
+ J7xgBPECgYEA8RIzHAQ3zbaibKdnllBLIgsqGdSzebTLKheFuigRotEV3Or/z5Lg
+ k/nVnThWVkTOSRqXTNpJAME6a4KTdcVSxYP+SdZVO1esazHrGb7xPVb7MWSE1cqp
+ WEk3Yy8OUOPoPQMc4dyGzd30Mi8IBB6gnFIYOTrpUo0XtkBv8rGGhfsCgYEA5uYn
+ 6QgL4NqNT84IXylmMb5ia3iBt6lhxI/A28CDtQvfScl4eYK0IjBwdfG6E1vJgyzg
+ nJzv3xEVo9bz+Kq7CcThWpK5JQaPnsV0Q74Wjk0ShHet15txOdJuKImnh5F6lylC
+ GTLR9gnptytfMH/uuw4ws0Q2kcg4l5NHKOWOnAcCgYEAvAwIVkhsB0n59Wu4gCZu
+ FUZENxYWUk/XUyQ6KnZrG2ih90xQ8+iMyqFOIm/52R2fFKNrdoWoALC6E3ct8+ZS
+ pMRLrelFXx8K3it4SwMJR2H8XBEfFW4bH0UtsW7Zafv+AunUs9LETP5gKG1LgXsq
+ qgXX43yy2LQ61O365YPZfdUCgYBVbTvA3MhARbvYldrFEnUL3GtfZbNgdxuD9Mee
+ xig0eJMBIrgfBLuOlqtVB70XYnM4xAbKCso4loKSHnofO1N99siFkRlM2JOUY2tz
+ kMWZmmxKdFjuF0WZ5f/5oYxI/QsFGC+rUQEbbWl56mMKd5qkvEhKWudxoklF0yiV
+ ufC8SwKBgDWb8iWqWN5a/kfvKoxFcDM74UHk/SeKMGAL+ujKLf58F+CbweM5pX9C
+ EUsxeoUEraVWTiyFVNqD81rCdceus9TdBj0ZIK1vUttaRZyrMAwF0uQSfjtxsOpd
+ l69BkyvzjgDPkmOHVGiSZDLi3YDvypbUpo6LOy4v5rVg5U2F/A0v
+ -----END RSA PRIVATE KEY-----
+ PKEY
+ end
+end
diff --git a/spec/bundler/support/bundle.rb b/spec/bundler/support/bundle.rb
new file mode 100644
index 0000000000..5f808531ff
--- /dev/null
+++ b/spec/bundler/support/bundle.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require "rubygems"
+Gem.instance_variable_set(:@ruby, ENV["RUBY"]) if ENV["RUBY"]
+
+require_relative "path"
+bundler_gemspec = Spec::Path.loaded_gemspec
+bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root)
+bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate)
+load File.expand_path("bundle", Spec::Path.bindir)
diff --git a/spec/bundler/support/command_execution.rb b/spec/bundler/support/command_execution.rb
new file mode 100644
index 0000000000..68e5c56c75
--- /dev/null
+++ b/spec/bundler/support/command_execution.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Spec
+ CommandExecution = Struct.new(:command, :working_directory, :exitstatus, :stdout, :stderr) do
+ def to_s
+ "$ #{command}"
+ end
+ alias_method :inspect, :to_s
+
+ def stdboth
+ @stdboth ||= [stderr, stdout].join("\n").strip
+ end
+
+ def to_s_verbose
+ [
+ to_s,
+ stdout,
+ stderr,
+ exitstatus ? "# $? => #{exitstatus}" : "",
+ ].reject(&:empty?).join("\n")
+ end
+
+ def success?
+ return true unless exitstatus
+ exitstatus == 0
+ end
+
+ def failure?
+ return true unless exitstatus
+ exitstatus > 0
+ end
+ end
+end
diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb
new file mode 100644
index 0000000000..78545d2e64
--- /dev/null
+++ b/spec/bundler/support/filters.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+class RequirementChecker < Proc
+ def self.against(present)
+ provided = Gem::Version.new(present)
+
+ new do |required|
+ !Gem::Requirement.new(required).satisfied_by?(provided)
+ end.tap do |checker|
+ checker.provided = provided
+ end
+ end
+
+ attr_accessor :provided
+
+ def inspect
+ "\"!= #{provided}\""
+ end
+end
+
+RSpec.configure do |config|
+ config.filter_run_excluding :realworld => true
+
+ git_version = Bundler::Source::Git::GitProxy.new(nil, nil).version
+
+ config.filter_run_excluding :git => RequirementChecker.against(git_version)
+ config.filter_run_excluding :bundler => RequirementChecker.against(Bundler::VERSION.split(".")[0])
+ config.filter_run_excluding :rubygems => RequirementChecker.against(Gem::VERSION)
+ config.filter_run_excluding :ruby_repo => !ENV["GEM_COMMAND"].nil?
+ config.filter_run_excluding :no_color_tty => Gem.win_platform? || !ENV["GITHUB_ACTION"].nil?
+ config.filter_run_excluding :permissions => Gem.win_platform?
+ config.filter_run_excluding :readline => Gem.win_platform?
+ config.filter_run_excluding :jruby_only => RUBY_ENGINE != "jruby"
+ config.filter_run_excluding :truffleruby_only => RUBY_ENGINE != "truffleruby"
+ config.filter_run_excluding :man => Gem.win_platform?
+
+ config.filter_run_when_matching :focus unless ENV["CI"]
+end
diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb
new file mode 100644
index 0000000000..c7fe3637cc
--- /dev/null
+++ b/spec/bundler/support/hax.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+if ENV["BUNDLER_SPEC_RUBY_PLATFORM"]
+ Object.send(:remove_const, :RUBY_PLATFORM)
+ RUBY_PLATFORM = ENV["BUNDLER_SPEC_RUBY_PLATFORM"]
+end
+
+module Gem
+ def self.ruby=(ruby)
+ @ruby = ruby
+ end
+
+ if ENV["RUBY"]
+ Gem.ruby = ENV["RUBY"]
+ end
+
+ if ENV["BUNDLER_GEM_DEFAULT_DIR"]
+ @default_dir = ENV["BUNDLER_GEM_DEFAULT_DIR"]
+ @default_specifications_dir = nil
+ end
+
+ if ENV["BUNDLER_SPEC_WINDOWS"]
+ @@win_platform = true # rubocop:disable Style/ClassVars
+ end
+
+ if ENV["BUNDLER_SPEC_PLATFORM"]
+ previous_platforms = @platforms
+ previous_local = Platform.local
+
+ class Platform
+ @local = new(ENV["BUNDLER_SPEC_PLATFORM"])
+ end
+ @platforms = previous_platforms.map {|platform| platform == previous_local ? Platform.local : platform }
+ end
+
+ if ENV["BUNDLER_SPEC_GEM_SOURCES"]
+ self.sources = [ENV["BUNDLER_SPEC_GEM_SOURCES"]]
+ end
+
+ if ENV["BUNDLER_IGNORE_DEFAULT_GEM"]
+ module RemoveDefaultBundlerStub
+ def default_stubs(pattern = "*")
+ super.delete_if {|stub| stub.name == "bundler" }
+ end
+ end
+
+ class Specification
+ class << self
+ prepend RemoveDefaultBundlerStub
+ end
+ end
+ end
+end
diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb
new file mode 100644
index 0000000000..7b8c56b6ad
--- /dev/null
+++ b/spec/bundler/support/helpers.rb
@@ -0,0 +1,597 @@
+# frozen_string_literal: true
+
+require_relative "command_execution"
+require_relative "the_bundle"
+require_relative "path"
+
+module Spec
+ module Helpers
+ include Spec::Path
+
+ def reset!
+ Dir.glob("#{tmp}/{gems/*,*}", File::FNM_DOTMATCH).each do |dir|
+ next if %w[base base_system remote1 rubocop standard gems rubygems . ..].include?(File.basename(dir))
+ FileUtils.rm_rf(dir)
+ end
+ FileUtils.mkdir_p(home)
+ FileUtils.mkdir_p(tmpdir)
+ reset_paths!
+ end
+
+ def reset_paths!
+ Bundler.reset!
+ Gem.clear_paths
+ end
+
+ def the_bundle(*args)
+ TheBundle.new(*args)
+ end
+
+ def command_executions
+ @command_executions ||= []
+ end
+
+ def last_command
+ command_executions.last || raise("There is no last command")
+ end
+
+ def out
+ last_command.stdout
+ end
+
+ def err
+ last_command.stderr
+ end
+
+ MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/.freeze
+
+ def err_without_deprecations
+ err.gsub(/#{MAJOR_DEPRECATION}.+[\n]?/, "")
+ end
+
+ def deprecations
+ err.split("\n").select {|l| l =~ MAJOR_DEPRECATION }.join("\n").split(MAJOR_DEPRECATION)
+ end
+
+ def exitstatus
+ last_command.exitstatus
+ end
+
+ def run(cmd, *args)
+ opts = args.last.is_a?(Hash) ? args.pop : {}
+ groups = args.map(&:inspect).join(", ")
+ setup = "require '#{entrypoint}' ; Bundler.ui.silence { Bundler.setup(#{groups}) }"
+ ruby([setup, cmd].join(" ; "), opts)
+ end
+
+ def load_error_run(ruby, name, *args)
+ cmd = <<-RUBY
+ begin
+ #{ruby}
+ rescue LoadError => e
+ warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}")
+ end
+ RUBY
+ opts = args.last.is_a?(Hash) ? args.pop : {}
+ args += [opts]
+ run(cmd, *args)
+ end
+
+ def bundle(cmd, options = {}, &block)
+ bundle_bin = options.delete(:bundle_bin)
+ bundle_bin ||= installed_bindir.join("bundle")
+
+ env = options.delete(:env) || {}
+
+ requires = options.delete(:requires) || []
+ realworld = RSpec.current_example.metadata[:realworld]
+
+ artifice = options.delete(:artifice) do
+ if realworld
+ "vcr"
+ else
+ "fail"
+ end
+ end
+ if artifice
+ requires << "#{Path.spec_dir}/support/artifice/#{artifice}.rb"
+ end
+
+ load_path = []
+ load_path << spec_dir
+
+ dir = options.delete(:dir) || bundled_app
+ raise_on_error = options.delete(:raise_on_error)
+
+ args = options.map do |k, v|
+ case v
+ when nil
+ next
+ when true
+ " --#{k}"
+ when false
+ " --no-#{k}"
+ else
+ " --#{k} #{v}"
+ end
+ end.join
+
+ ruby_cmd = build_ruby_cmd({ :load_path => load_path, :requires => requires })
+ cmd = "#{ruby_cmd} #{bundle_bin} #{cmd}#{args}"
+ sys_exec(cmd, { :env => env, :dir => dir, :raise_on_error => raise_on_error }, &block)
+ end
+
+ def bundler(cmd, options = {})
+ options[:bundle_bin] = system_gem_path.join("bin/bundler")
+ bundle(cmd, options)
+ end
+
+ def ruby(ruby, options = {})
+ ruby_cmd = build_ruby_cmd
+ escaped_ruby = ruby.shellescape
+ sys_exec(%(#{ruby_cmd} -w -e #{escaped_ruby}), options)
+ end
+
+ def load_error_ruby(ruby, name, opts = {})
+ ruby(<<-R)
+ begin
+ #{ruby}
+ rescue LoadError => e
+ warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}")
+ end
+ R
+ end
+
+ def build_ruby_cmd(options = {})
+ libs = options.delete(:load_path)
+ lib_option = libs ? "-I#{libs.join(File::PATH_SEPARATOR)}" : []
+
+ requires = options.delete(:requires) || []
+ requires << "#{Path.spec_dir}/support/hax.rb"
+ require_option = requires.map {|r| "-r#{r}" }
+
+ [Gem.ruby, *lib_option, *require_option].compact.join(" ")
+ end
+
+ def gembin(cmd, options = {})
+ cmd = bundled_app("bin/#{cmd}") unless cmd.to_s.include?("/")
+ sys_exec(cmd.to_s, options)
+ end
+
+ def gem_command(command, options = {})
+ env = options[:env] || {}
+ env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/hax.rb", env["RUBYOPT"]), ENV["RUBYOPT"])
+ options[:env] = env
+ sys_exec("#{Path.gem_bin} #{command}", options)
+ end
+
+ def rake
+ "#{Gem.ruby} -S #{ENV["GEM_PATH"]}/bin/rake"
+ end
+
+ def git(cmd, path, options = {})
+ sys_exec("git #{cmd}", options.merge(:dir => path))
+ end
+
+ def sys_exec(cmd, options = {})
+ env = options[:env] || {}
+ env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"]), ENV["RUBYOPT"])
+ dir = options[:dir] || bundled_app
+ command_execution = CommandExecution.new(cmd.to_s, dir)
+
+ require "open3"
+ require "shellwords"
+ Open3.popen3(env, *cmd.shellsplit, :chdir => dir) do |stdin, stdout, stderr, wait_thr|
+ yield stdin, stdout, wait_thr if block_given?
+ stdin.close
+
+ stdout_read_thread = Thread.new { stdout.read }
+ stderr_read_thread = Thread.new { stderr.read }
+ command_execution.stdout = stdout_read_thread.value.strip
+ command_execution.stderr = stderr_read_thread.value.strip
+
+ status = wait_thr.value
+ command_execution.exitstatus = if status.exited?
+ status.exitstatus
+ elsif status.signaled?
+ exit_status_for_signal(status.termsig)
+ end
+ end
+
+ unless options[:raise_on_error] == false || command_execution.success?
+ raise <<~ERROR
+
+ Invoking `#{cmd}` failed with output:
+ ----------------------------------------------------------------------
+ #{command_execution.stdboth}
+ ----------------------------------------------------------------------
+ ERROR
+ end
+
+ command_executions << command_execution
+
+ command_execution.stdout
+ end
+
+ def all_commands_output
+ return "" if command_executions.empty?
+
+ "\n\nCommands:\n#{command_executions.map(&:to_s_verbose).join("\n\n")}"
+ end
+
+ def config(config = nil, path = bundled_app(".bundle/config"))
+ return Psych.load_file(path) unless config
+ FileUtils.mkdir_p(File.dirname(path))
+ File.open(path, "w") do |f|
+ f.puts config.to_yaml
+ end
+ config
+ end
+
+ def global_config(config = nil)
+ config(config, home(".bundle/config"))
+ end
+
+ def create_file(path, contents = "")
+ path = Pathname.new(path).expand_path(bundled_app) unless path.is_a?(Pathname)
+ path.dirname.mkpath
+ File.open(path.to_s, "w") do |f|
+ f.puts strip_whitespace(contents)
+ end
+ end
+
+ def gemfile(*args)
+ contents = args.pop
+
+ if contents.nil?
+ File.open(bundled_app_gemfile, "r", &:read)
+ else
+ create_file(args.pop || "Gemfile", contents)
+ end
+ end
+
+ def lockfile(*args)
+ contents = args.pop
+
+ if contents.nil?
+ File.open(bundled_app_lock, "r", &:read)
+ else
+ create_file(args.pop || "Gemfile.lock", contents)
+ end
+ end
+
+ def strip_whitespace(str)
+ # Trim the leading spaces
+ spaces = str[/\A\s+/, 0] || ""
+ str.gsub(/^#{spaces}/, "")
+ end
+
+ def install_gemfile(*args)
+ opts = args.last.is_a?(Hash) ? args.pop : {}
+ gemfile(*args)
+ bundle :install, opts
+ end
+
+ def lock_gemfile(*args)
+ gemfile(*args)
+ opts = args.last.is_a?(Hash) ? args.last : {}
+ bundle :lock, opts
+ end
+
+ def system_gems(*gems)
+ gems = gems.flatten
+ options = gems.last.is_a?(Hash) ? gems.pop : {}
+ path = options.fetch(:path, system_gem_path)
+ default = options.fetch(:default, false)
+ with_gem_path_as(path) do
+ gem_repo = options.fetch(:gem_repo, gem_repo1)
+ gems.each do |g|
+ gem_name = g.to_s
+ if gem_name.start_with?("bundler")
+ version = gem_name.match(/\Abundler-(?<version>.*)\z/)[:version] if gem_name != "bundler"
+ with_built_bundler(version) {|gem_path| install_gem(gem_path, default) }
+ elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name)
+ install_gem(gem_name, default)
+ else
+ install_gem("#{gem_repo}/gems/#{gem_name}.gem", default)
+ end
+ end
+ end
+ end
+
+ def install_gem(path, default = false)
+ raise "OMG `#{path}` does not exist!" unless File.exist?(path)
+
+ args = "--no-document --ignore-dependencies --verbose --local"
+ args += " --default --install-dir #{system_gem_path}" if default
+
+ gem_command "install #{args} '#{path}'"
+ end
+
+ def with_built_bundler(version = nil)
+ version ||= Bundler::VERSION
+ full_name = "bundler-#{version}"
+ build_path = tmp + full_name
+ bundler_path = build_path + "#{full_name}.gem"
+
+ Dir.mkdir build_path
+
+ begin
+ shipped_files.each do |shipped_file|
+ target_shipped_file = build_path + shipped_file
+ target_shipped_dir = File.dirname(target_shipped_file)
+ FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir)
+ FileUtils.cp shipped_file, target_shipped_file, :preserve => true
+ end
+
+ replace_version_file(version, dir: build_path) # rubocop:disable Style/HashSyntax
+
+ Spec::BuildMetadata.write_build_metadata(dir: build_path) # rubocop:disable Style/HashSyntax
+
+ gem_command "build #{relative_gemspec}", :dir => build_path
+
+ yield(bundler_path)
+ ensure
+ build_path.rmtree
+ end
+ end
+
+ def with_gem_path_as(path)
+ without_env_side_effects do
+ ENV["GEM_HOME"] = path.to_s
+ ENV["GEM_PATH"] = path.to_s
+ ENV["BUNDLER_ORIG_GEM_HOME"] = nil
+ ENV["BUNDLER_ORIG_GEM_PATH"] = nil
+ yield
+ end
+ end
+
+ def with_path_as(path)
+ without_env_side_effects do
+ ENV["PATH"] = path.to_s
+ ENV["BUNDLER_ORIG_PATH"] = nil
+ yield
+ end
+ end
+
+ def without_env_side_effects
+ backup = ENV.to_hash
+ yield
+ ensure
+ ENV.replace(backup)
+ end
+
+ def with_path_added(path)
+ with_path_as([path.to_s, ENV["PATH"]].join(File::PATH_SEPARATOR)) do
+ yield
+ end
+ end
+
+ def opt_add(option, options)
+ [option.strip, options].compact.reject(&:empty?).join(" ")
+ end
+
+ def opt_remove(option, options)
+ return unless options
+
+ options.split(" ").reject {|opt| opt.strip == option.strip }.join(" ")
+ end
+
+ def break_git!
+ FileUtils.mkdir_p(tmp("broken_path"))
+ File.open(tmp("broken_path/git"), "w", 0o755) do |f|
+ f.puts "#!/usr/bin/env ruby\nSTDERR.puts 'This is not the git you are looking for'\nexit 1"
+ end
+
+ ENV["PATH"] = "#{tmp("broken_path")}:#{ENV["PATH"]}"
+ end
+
+ def with_fake_man
+ skip "fake_man is not a Windows friendly binstub" if Gem.win_platform?
+
+ FileUtils.mkdir_p(tmp("fake_man"))
+ File.open(tmp("fake_man/man"), "w", 0o755) do |f|
+ f.puts "#!/usr/bin/env ruby\nputs ARGV.inspect\n"
+ end
+ with_path_added(tmp("fake_man")) { yield }
+ end
+
+ def pristine_system_gems(*gems)
+ FileUtils.rm_rf(system_gem_path)
+
+ system_gems(*gems)
+ end
+
+ def realworld_system_gems(*gems)
+ gems = gems.flatten
+ opts = gems.last.is_a?(Hash) ? gems.pop : {}
+ path = opts.fetch(:path, system_gem_path)
+
+ with_gem_path_as(path) do
+ gems.each do |gem|
+ gem_command "install --no-document #{gem}"
+ end
+ end
+ end
+
+ def cache_gems(*gems, gem_repo: gem_repo1)
+ gems = gems.flatten
+
+ FileUtils.rm_rf("#{bundled_app}/vendor/cache")
+ FileUtils.mkdir_p("#{bundled_app}/vendor/cache")
+
+ gems.each do |g|
+ path = "#{gem_repo}/gems/#{g}.gem"
+ raise "OMG `#{path}` does not exist!" unless File.exist?(path)
+ FileUtils.cp(path, "#{bundled_app}/vendor/cache")
+ end
+ end
+
+ def simulate_new_machine
+ FileUtils.rm_rf bundled_app(".bundle")
+ pristine_system_gems :bundler
+ end
+
+ def simulate_ruby_platform(ruby_platform)
+ old = ENV["BUNDLER_SPEC_RUBY_PLATFORM"]
+ ENV["BUNDLER_SPEC_RUBY_PLATFORM"] = ruby_platform.to_s
+ yield
+ ensure
+ ENV["BUNDLER_SPEC_RUBY_PLATFORM"] = old
+ end
+
+ def simulate_platform(platform)
+ old = ENV["BUNDLER_SPEC_PLATFORM"]
+ ENV["BUNDLER_SPEC_PLATFORM"] = platform.to_s
+ yield if block_given?
+ ensure
+ ENV["BUNDLER_SPEC_PLATFORM"] = old if block_given?
+ end
+
+ def simulate_windows(platform = x86_mswin32)
+ old = ENV["BUNDLER_SPEC_WINDOWS"]
+ ENV["BUNDLER_SPEC_WINDOWS"] = "true"
+ simulate_platform platform do
+ simulate_bundler_version_when_missing_prerelease_default_gem_activation do
+ yield
+ end
+ end
+ ensure
+ ENV["BUNDLER_SPEC_WINDOWS"] = old
+ end
+
+ def simulate_bundler_version_when_missing_prerelease_default_gem_activation
+ return yield unless rubygems_version_failing_to_activate_bundler_prereleases
+
+ old = ENV["BUNDLER_VERSION"]
+ ENV["BUNDLER_VERSION"] = Bundler::VERSION
+ yield
+ ensure
+ ENV["BUNDLER_VERSION"] = old
+ end
+
+ def env_for_missing_prerelease_default_gem_activation
+ if rubygems_version_failing_to_activate_bundler_prereleases
+ { "BUNDLER_VERSION" => Bundler::VERSION }
+ else
+ {}
+ end
+ end
+
+ def current_ruby_minor
+ Gem.ruby_version.segments.tap {|s| s.delete_at(2) }.join(".")
+ end
+
+ def next_ruby_minor
+ ruby_major_minor.map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".")
+ end
+
+ def previous_ruby_minor
+ return "2.7" if ruby_major_minor == [3, 0]
+
+ ruby_major_minor.map.with_index {|s, i| i == 1 ? s - 1 : s }.join(".")
+ end
+
+ def ruby_major_minor
+ Gem.ruby_version.segments[0..1]
+ end
+
+ # versions not including
+ # https://github.com/rubygems/rubygems/commit/929e92d752baad3a08f3ac92eaec162cb96aedd1
+ def rubygems_version_failing_to_activate_bundler_prereleases
+ Gem.rubygems_version < Gem::Version.new("3.1.0.pre.1")
+ end
+
+ def revision_for(path)
+ sys_exec("git rev-parse HEAD", :dir => path).strip
+ end
+
+ def with_read_only(pattern)
+ chmod = lambda do |dirmode, filemode|
+ lambda do |f|
+ mode = File.directory?(f) ? dirmode : filemode
+ File.chmod(mode, f)
+ end
+ end
+
+ Dir[pattern].each(&chmod[0o555, 0o444])
+ yield
+ ensure
+ Dir[pattern].each(&chmod[0o755, 0o644])
+ end
+
+ # Simulate replacing TODOs with real values
+ def prepare_gemspec(pathname)
+ process_file(pathname) do |line|
+ case line
+ when /spec\.metadata\["(?:allowed_push_host|homepage_uri|source_code_uri|changelog_uri)"\]/, /spec\.homepage/
+ line.gsub(/\=.*$/, '= "http://example.org"')
+ when /spec\.summary/
+ line.gsub(/\=.*$/, '= "A short summary of my new gem."')
+ when /spec\.description/
+ line.gsub(/\=.*$/, '= "A longer description of my new gem."')
+ else
+ line
+ end
+ end
+ end
+
+ def process_file(pathname)
+ changed_lines = pathname.readlines.map do |line|
+ yield line
+ end
+ File.open(pathname, "w") {|file| file.puts(changed_lines.join) }
+ end
+
+ def with_env_vars(env_hash, &block)
+ current_values = {}
+ env_hash.each do |k, v|
+ current_values[k] = ENV[k]
+ ENV[k] = v
+ end
+ block.call if block_given?
+ env_hash.each do |k, _|
+ ENV[k] = current_values[k]
+ end
+ end
+
+ def require_rack
+ # need to hack, so we can require rack
+ old_gem_home = ENV["GEM_HOME"]
+ ENV["GEM_HOME"] = Spec::Path.base_system_gem_path.to_s
+ require "rack"
+ ENV["GEM_HOME"] = old_gem_home
+ end
+
+ def wait_for_server(host, port, seconds = 15)
+ tries = 0
+ sleep 0.5
+ TCPSocket.new(host, port)
+ rescue StandardError => e
+ raise(e) if tries > (seconds * 2)
+ tries += 1
+ retry
+ end
+
+ def find_unused_port
+ port = 21_453
+ begin
+ port += 1 while TCPSocket.new("127.0.0.1", port)
+ rescue StandardError
+ false
+ end
+ port
+ end
+
+ def exit_status_for_signal(signal_number)
+ # For details see: https://en.wikipedia.org/wiki/Exit_status#Shell_and_scripts
+ 128 + signal_number
+ end
+
+ private
+
+ def git_root_dir?
+ root.to_s == `git rev-parse --show-toplevel`.chomp
+ end
+ end
+end
diff --git a/spec/bundler/support/indexes.rb b/spec/bundler/support/indexes.rb
new file mode 100644
index 0000000000..78372302f1
--- /dev/null
+++ b/spec/bundler/support/indexes.rb
@@ -0,0 +1,419 @@
+# frozen_string_literal: true
+
+module Spec
+ module Indexes
+ def dep(name, reqs = nil)
+ @deps ||= []
+ @deps << Bundler::Dependency.new(name, reqs)
+ end
+
+ def platform(*args)
+ @platforms ||= []
+ @platforms.concat args.map {|p| Gem::Platform.new(p) }
+ end
+
+ alias_method :platforms, :platform
+
+ def resolve(args = [])
+ @platforms ||= ["ruby"]
+ default_source = instance_double("Bundler::Source::Rubygems", :specs => @index, :to_s => "locally install gems")
+ source_requirements = { :default => default_source }
+ base = args[0] || Bundler::SpecSet.new([])
+ base.each {|ls| ls.source = default_source }
+ gem_version_promoter = args[1] || Bundler::GemVersionPromoter.new
+ originally_locked = args[2] || Bundler::SpecSet.new([])
+ unlock = args[3] || []
+ @deps.each do |d|
+ name = d.name
+ source_requirements[name] = d.source = default_source
+ end
+ packages = Bundler::Resolver::Base.new(source_requirements, @deps, base, @platforms, :locked_specs => originally_locked, :unlock => unlock)
+ Bundler::Resolver.new(packages, gem_version_promoter).start
+ end
+
+ def should_not_resolve
+ expect { resolve }.to raise_error(Bundler::GemNotFound)
+ end
+
+ def should_resolve_as(specs)
+ got = resolve
+ got = got.map(&:full_name).sort
+ expect(got).to eq(specs.sort)
+ end
+
+ def should_resolve_and_include(specs, args = [])
+ got = resolve(args)
+ got = got.map(&:full_name).sort
+ specs.each do |s|
+ expect(got).to include(s)
+ end
+ end
+
+ def gem(*args, &blk)
+ build_spec(*args, &blk).first
+ end
+
+ def locked(*args)
+ Bundler::SpecSet.new(args.map do |name, version|
+ gem(name, version)
+ end)
+ end
+
+ def should_conservative_resolve_and_include(opts, unlock, specs)
+ # empty unlock means unlock all
+ opts = Array(opts)
+ search = Bundler::GemVersionPromoter.new.tap do |s|
+ s.level = opts.first
+ s.strict = opts.include?(:strict)
+ end
+ should_resolve_and_include specs, [@base, search, @locked, unlock]
+ end
+
+ def an_awesome_index
+ build_index do
+ gem "rack", %w[0.8 0.9 0.9.1 0.9.2 1.0 1.1]
+ gem "rack-mount", %w[0.4 0.5 0.5.1 0.5.2 0.6]
+
+ # --- Pre-release support
+ gem "RubyGems\0", ["1.3.2"]
+
+ # --- Rails
+ versions "1.2.3 2.2.3 2.3.5 3.0.0.beta 3.0.0.beta1" do |version|
+ gem "activesupport", version
+ gem "actionpack", version do
+ dep "activesupport", version
+ if version >= v("3.0.0.beta")
+ dep "rack", "~> 1.1"
+ dep "rack-mount", ">= 0.5"
+ elsif version > v("2.3") then dep "rack", "~> 1.0.0"
+ elsif version > v("2.0.0") then dep "rack", "~> 0.9.0"
+ end
+ end
+ gem "activerecord", version do
+ dep "activesupport", version
+ dep "arel", ">= 0.2" if version >= v("3.0.0.beta")
+ end
+ gem "actionmailer", version do
+ dep "activesupport", version
+ dep "actionmailer", version
+ end
+ if version < v("3.0.0.beta")
+ gem "railties", version do
+ dep "activerecord", version
+ dep "actionpack", version
+ dep "actionmailer", version
+ dep "activesupport", version
+ end
+ else
+ gem "railties", version
+ gem "rails", version do
+ dep "activerecord", version
+ dep "actionpack", version
+ dep "actionmailer", version
+ dep "activesupport", version
+ dep "railties", version
+ end
+ end
+ end
+
+ versions "1.0 1.2 1.2.1 1.2.2 1.3 1.3.0.1 1.3.5 1.4.0 1.4.2 1.4.2.1" do |version|
+ platforms "ruby java mswin32 mingw32 x64-mingw32" do |platform|
+ next if version == v("1.4.2.1") && platform != pl("x86-mswin32")
+ next if version == v("1.4.2") && platform == pl("x86-mswin32")
+ gem "nokogiri", version, platform do
+ dep "weakling", ">= 0.0.3" if platform =~ pl("java") # rubocop:disable Performance/RegexpMatch
+ end
+ end
+ end
+
+ versions "0.0.1 0.0.2 0.0.3" do |version|
+ gem "weakling", version
+ end
+
+ # --- Rails related
+ versions "1.2.3 2.2.3 2.3.5" do |version|
+ gem "activemerchant", version do
+ dep "activesupport", ">= #{version}"
+ end
+ end
+
+ gem "reform", ["1.0.0"] do
+ dep "activesupport", ">= 1.0.0.beta1"
+ end
+
+ gem "need-pre", ["1.0.0"] do
+ dep "activesupport", "~> 3.0.0.beta1"
+ end
+ end
+ end
+
+ # Builder 3.1.4 will activate first, but if all
+ # goes well, it should resolve to 3.0.4
+ def a_conflict_index
+ build_index do
+ gem "builder", %w[3.0.4 3.1.4]
+ gem("grape", "0.2.6") do
+ dep "builder", ">= 0"
+ end
+
+ versions "3.2.8 3.2.9 3.2.10 3.2.11" do |version|
+ gem("activemodel", version) do
+ dep "builder", "~> 3.0.0"
+ end
+ end
+
+ gem("my_app", "1.0.0") do
+ dep "activemodel", ">= 0"
+ dep "grape", ">= 0"
+ end
+ end
+ end
+
+ def a_complex_conflict_index
+ build_index do
+ gem("a", %w[1.0.2 1.1.4 1.2.0 1.4.0]) do
+ dep "d", ">= 0"
+ end
+
+ gem("d", %w[1.3.0 1.4.1]) do
+ dep "x", ">= 0"
+ end
+
+ gem "d", "0.9.8"
+
+ gem("b", "0.3.4") do
+ dep "a", ">= 1.5.0"
+ end
+
+ gem("b", "0.3.5") do
+ dep "a", ">= 1.2"
+ end
+
+ gem("b", "0.3.3") do
+ dep "a", "> 1.0"
+ end
+
+ versions "3.2 3.3" do |version|
+ gem("c", version) do
+ dep "a", "~> 1.0"
+ end
+ end
+
+ gem("my_app", "1.3.0") do
+ dep "c", ">= 4.0"
+ dep "b", ">= 0"
+ end
+
+ gem("my_app", "1.2.0") do
+ dep "c", "~> 3.3.0"
+ dep "b", "0.3.4"
+ end
+
+ gem("my_app", "1.1.0") do
+ dep "c", "~> 3.2.0"
+ dep "b", "0.3.5"
+ end
+ end
+ end
+
+ def index_with_conflict_on_child
+ build_index do
+ gem "json", %w[1.6.5 1.7.7 1.8.0]
+
+ gem("chef", "10.26") do
+ dep "json", [">= 1.4.4", "<= 1.7.7"]
+ end
+
+ gem("berkshelf", "2.0.7") do
+ dep "json", ">= 1.7.7"
+ end
+
+ gem("chef_app", "1.0.0") do
+ dep "berkshelf", "~> 2.0"
+ dep "chef", "~> 10.26"
+ end
+ end
+ end
+
+ # Issue #3459
+ def a_complicated_index
+ build_index do
+ gem "foo", %w[3.0.0 3.0.5] do
+ dep "qux", ["~> 3.1"]
+ dep "baz", ["< 9.0", ">= 5.0"]
+ dep "bar", ["~> 1.0"]
+ dep "grault", ["~> 3.1"]
+ end
+
+ gem "foo", "1.2.1" do
+ dep "baz", ["~> 4.2"]
+ dep "bar", ["~> 1.0"]
+ dep "qux", ["~> 3.1"]
+ dep "grault", ["~> 2.0"]
+ end
+
+ gem "bar", "1.0.5" do
+ dep "grault", ["~> 3.1"]
+ dep "baz", ["< 9", ">= 4.2"]
+ end
+
+ gem "bar", "1.0.3" do
+ dep "baz", ["< 9", ">= 4.2"]
+ dep "grault", ["~> 2.0"]
+ end
+
+ gem "baz", "8.2.10" do
+ dep "grault", ["~> 3.0"]
+ dep "garply", [">= 0.5.1", "~> 0.5"]
+ end
+
+ gem "baz", "5.0.2" do
+ dep "grault", ["~> 2.0"]
+ dep "garply", [">= 0.3.1"]
+ end
+
+ gem "baz", "4.2.0" do
+ dep "grault", ["~> 2.0"]
+ dep "garply", [">= 0.3.1"]
+ end
+
+ gem "grault", %w[2.6.3 3.1.1]
+
+ gem "garply", "0.5.1" do
+ dep "waldo", ["~> 0.1.3"]
+ end
+
+ gem "waldo", "0.1.5" do
+ dep "plugh", ["~> 0.6.0"]
+ end
+
+ gem "plugh", %w[0.6.3 0.6.11 0.7.0]
+
+ gem "qux", "3.2.21" do
+ dep "plugh", [">= 0.6.4", "~> 0.6"]
+ dep "corge", ["~> 1.0"]
+ end
+
+ gem "corge", "1.10.1"
+ end
+ end
+
+ def a_unresovable_child_index
+ build_index do
+ gem "json", %w[1.8.0]
+
+ gem("chef", "10.26") do
+ dep "json", [">= 1.4.4", "<= 1.7.7"]
+ end
+
+ gem("berkshelf", "2.0.7") do
+ dep "json", ">= 1.7.7"
+ end
+
+ gem("chef_app_error", "1.0.0") do
+ dep "berkshelf", "~> 2.0"
+ dep "chef", "~> 10.26"
+ end
+ end
+ end
+
+ def a_index_with_root_conflict_on_child
+ build_index do
+ gem "builder", %w[2.1.2 3.0.1 3.1.3]
+ gem "i18n", %w[0.4.1 0.4.2]
+
+ gem "activesupport", %w[3.0.0 3.0.1 3.0.5 3.1.7]
+
+ gem("activemodel", "3.0.5") do
+ dep "activesupport", "= 3.0.5"
+ dep "builder", "~> 2.1.2"
+ dep "i18n", "~> 0.4"
+ end
+
+ gem("activemodel", "3.0.0") do
+ dep "activesupport", "= 3.0.0"
+ dep "builder", "~> 2.1.2"
+ dep "i18n", "~> 0.4.1"
+ end
+
+ gem("activemodel", "3.1.3") do
+ dep "activesupport", "= 3.1.3"
+ dep "builder", "~> 2.1.2"
+ dep "i18n", "~> 0.5"
+ end
+
+ gem("activerecord", "3.0.0") do
+ dep "activesupport", "= 3.0.0"
+ dep "activemodel", "= 3.0.0"
+ end
+
+ gem("activerecord", "3.0.5") do
+ dep "activesupport", "= 3.0.5"
+ dep "activemodel", "= 3.0.5"
+ end
+
+ gem("activerecord", "3.0.9") do
+ dep "activesupport", "= 3.1.5"
+ dep "activemodel", "= 3.1.5"
+ end
+ end
+ end
+
+ def a_circular_index
+ build_index do
+ gem "rack", "1.0.1"
+ gem("foo", "0.2.6") do
+ dep "bar", ">= 0"
+ end
+
+ gem("bar", "1.0.0") do
+ dep "foo", ">= 0"
+ end
+
+ gem("circular_app", "1.0.0") do
+ dep "foo", ">= 0"
+ dep "bar", ">= 0"
+ end
+ end
+ end
+
+ def an_ambiguous_index
+ build_index do
+ gem("a", "1.0.0") do
+ dep "c", ">= 0"
+ end
+
+ gem("b", %w[0.5.0 1.0.0])
+
+ gem("b", "2.0.0") do
+ dep "c", "< 2.0.0"
+ end
+
+ gem("c", "1.0.0") do
+ dep "d", "1.0.0"
+ end
+
+ gem("c", "2.0.0") do
+ dep "d", "2.0.0"
+ end
+
+ gem("d", %w[1.0.0 2.0.0])
+ end
+ end
+
+ def optional_prereleases_index
+ build_index do
+ gem("a", %w[1.0.0])
+
+ gem("a", "2.0.0") do
+ dep "b", ">= 2.0.0.pre"
+ end
+
+ gem("b", %w[0.9.0 1.5.0 2.0.0.pre])
+
+ # --- Pre-release support
+ gem "RubyGems\0", ["1.3.2"]
+ end
+ end
+ end
+end
diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb
new file mode 100644
index 0000000000..ea7c784683
--- /dev/null
+++ b/spec/bundler/support/matchers.rb
@@ -0,0 +1,237 @@
+# frozen_string_literal: true
+
+require "forwardable"
+require_relative "the_bundle"
+
+module Spec
+ module Matchers
+ extend RSpec::Matchers
+
+ class Precondition
+ include RSpec::Matchers::Composable
+ extend Forwardable
+ def_delegators :failing_matcher,
+ :failure_message,
+ :actual,
+ :description,
+ :diffable?,
+ :expected,
+ :failure_message_when_negated
+
+ def initialize(matcher, preconditions)
+ @matcher = with_matchers_cloned(matcher)
+ @preconditions = with_matchers_cloned(preconditions)
+ @failure_index = nil
+ end
+
+ def matches?(target, &blk)
+ return false if @failure_index = @preconditions.index {|pc| !pc.matches?(target, &blk) }
+ @matcher.matches?(target, &blk)
+ end
+
+ def does_not_match?(target, &blk)
+ return false if @failure_index = @preconditions.index {|pc| !pc.matches?(target, &blk) }
+ if @matcher.respond_to?(:does_not_match?)
+ @matcher.does_not_match?(target, &blk)
+ else
+ !@matcher.matches?(target, &blk)
+ end
+ end
+
+ def expects_call_stack_jump?
+ @matcher.expects_call_stack_jump? || @preconditions.any?(&:expects_call_stack_jump)
+ end
+
+ def supports_block_expectations?
+ @matcher.supports_block_expectations? || @preconditions.any?(&:supports_block_expectations)
+ end
+
+ def failing_matcher
+ @failure_index ? @preconditions[@failure_index] : @matcher
+ end
+ end
+
+ def self.define_compound_matcher(matcher, preconditions, &declarations)
+ raise "Must have preconditions to define a compound matcher" if preconditions.empty?
+ define_method(matcher) do |*expected, &block_arg|
+ Precondition.new(
+ RSpec::Matchers::DSL::Matcher.new(matcher, declarations, self, *expected, &block_arg),
+ preconditions
+ )
+ end
+ end
+
+ RSpec::Matchers.define :have_dep do |*args|
+ dep = Bundler::Dependency.new(*args)
+
+ match do |actual|
+ actual.length == 1 && actual.all? {|d| d == dep }
+ end
+ end
+
+ RSpec::Matchers.define :have_gem do |*args|
+ match do |actual|
+ actual.length == args.length && actual.all? {|a| args.include?(a.full_name) }
+ end
+ end
+
+ RSpec::Matchers.define :be_sorted do
+ diffable
+ attr_reader :expected
+ match do |actual|
+ expected = block_arg ? actual.sort_by(&block_arg) : actual.sort
+ actual.==(expected).tap do
+ # HACK: since rspec won't show a diff when everything is a string
+ differ = RSpec::Support::Differ.new
+ @actual = differ.send(:object_to_string, actual)
+ @expected = differ.send(:object_to_string, expected)
+ end
+ end
+ end
+
+ RSpec::Matchers.define :be_well_formed do
+ match(&:empty?)
+
+ failure_message do |actual|
+ actual.join("\n")
+ end
+ end
+
+ RSpec::Matchers.define :take_less_than do |seconds|
+ match do |actual|
+ start_time = Time.now
+
+ actual.call
+
+ (Time.now - start_time).to_f < seconds
+ end
+
+ supports_block_expectations
+ end
+
+ define_compound_matcher :read_as, [exist] do |file_contents|
+ diffable
+
+ match do |actual|
+ @actual = Bundler.read_file(actual)
+ values_match?(file_contents, @actual)
+ end
+ end
+
+ def indent(string, padding = 4, indent_character = " ")
+ string.to_s.gsub(/^/, indent_character * padding).gsub("\t", " ")
+ end
+
+ define_compound_matcher :include_gems, [be_an_instance_of(Spec::TheBundle)] do |*names|
+ match do
+ opts = names.last.is_a?(Hash) ? names.pop : {}
+ source = opts.delete(:source)
+ groups = Array(opts.delete(:groups)).map(&:inspect).join(", ")
+ opts[:raise_on_error] = false
+ @errors = names.map do |full_name|
+ name, version, platform = full_name.split(/\s+/)
+ require_path = name.tr("-", "/")
+ version_const = name == "bundler" ? "Bundler::VERSION" : Spec::Builders.constantize(name)
+ source_const = "#{Spec::Builders.constantize(name)}_SOURCE"
+ ruby <<~R, opts
+ require 'bundler'
+ Bundler.setup(#{groups})
+
+ require '#{require_path}'
+ actual_version, actual_platform = #{version_const}.split(/\s+/, 2)
+ unless Gem::Version.new(actual_version) == Gem::Version.new('#{version}')
+ puts actual_version
+ exit 64
+ end
+ unless actual_platform.to_s == '#{platform}'
+ puts actual_platform
+ exit 65
+ end
+ require '#{require_path}/source'
+ exit 0 if #{source.nil?}
+ actual_source = #{source_const}
+ unless actual_source == '#{source}'
+ puts actual_source
+ exit 66
+ end
+ R
+ next if exitstatus == 0
+ if exitstatus == 64
+ actual_version = out.split("\n").last
+ next "#{name} was expected to be at version #{version} but was #{actual_version}"
+ end
+ if exitstatus == 65
+ actual_platform = out.split("\n").last
+ next "#{name} was expected to be of platform #{platform || "ruby"} but was #{actual_platform || "ruby"}"
+ end
+ if exitstatus == 66
+ actual_source = out.split("\n").last
+ next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{actual_source}`"
+ end
+ next "Command to check for inclusion of gem #{full_name} failed"
+ end.compact
+
+ @errors.empty?
+ end
+
+ match_when_negated do
+ opts = names.last.is_a?(Hash) ? names.pop : {}
+ groups = Array(opts.delete(:groups)).map(&:inspect).join(", ")
+ opts[:raise_on_error] = false
+ @errors = names.map do |name|
+ name, version = name.split(/\s+/, 2)
+ ruby <<-R, opts
+ begin
+ require 'bundler'
+ Bundler.setup(#{groups})
+ rescue Bundler::GemNotFound, Bundler::GitError
+ exit 0
+ end
+
+ begin
+ require '#{name}'
+ name_constant = #{Spec::Builders.constantize(name)}
+ if #{version.nil?} || name_constant == '#{version}'
+ exit 64
+ else
+ exit 0
+ end
+ rescue LoadError, NameError
+ exit 0
+ end
+ R
+ next if exitstatus == 0
+ next "command to check version of #{name} installed failed" unless exitstatus == 64
+ next "expected #{name} to not be installed, but it was" if version.nil?
+ next "expected #{name} (#{version}) not to be installed, but it was"
+ end.compact
+
+ @errors.empty?
+ end
+
+ failure_message do
+ super() + " but:\n" + @errors.map {|e| indent(e) }.join("\n")
+ end
+
+ failure_message_when_negated do
+ super() + " but:\n" + @errors.map {|e| indent(e) }.join("\n")
+ end
+ end
+ RSpec::Matchers.define_negated_matcher :not_include_gems, :include_gems
+ RSpec::Matchers.alias_matcher :include_gem, :include_gems
+
+ def plugin_should_be_installed(*names)
+ names.each do |name|
+ expect(Bundler::Plugin).to be_installed(name)
+ path = Pathname.new(Bundler::Plugin.installed?(name))
+ expect(path + "plugins.rb").to exist
+ end
+ end
+
+ def plugin_should_not_be_installed(*names)
+ names.each do |name|
+ expect(Bundler::Plugin).not_to be_installed(name)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb
new file mode 100644
index 0000000000..8b9c0e1290
--- /dev/null
+++ b/spec/bundler/support/path.rb
@@ -0,0 +1,311 @@
+# frozen_string_literal: true
+
+require "pathname"
+require "rbconfig"
+
+module Spec
+ module Path
+ def source_root
+ @source_root ||= Pathname.new(ruby_core? ? "../../.." : "../..").expand_path(__dir__)
+ end
+
+ def root
+ @root ||= system_gem_path("gems/bundler-#{Bundler::VERSION}")
+ end
+
+ def gemspec
+ @gemspec ||= source_root.join(relative_gemspec)
+ end
+
+ def relative_gemspec
+ @relative_gemspec ||= ruby_core? ? "lib/bundler/bundler.gemspec" : "bundler.gemspec"
+ end
+
+ def gemspec_dir
+ @gemspec_dir ||= gemspec.parent
+ end
+
+ def loaded_gemspec
+ @loaded_gemspec ||= Gem::Specification.load(gemspec.to_s)
+ end
+
+ def test_gemfile
+ @test_gemfile ||= tool_dir.join("test_gems.rb")
+ end
+
+ def rubocop_gemfile
+ @rubocop_gemfile ||= source_root.join(rubocop_gemfile_basename)
+ end
+
+ def standard_gemfile
+ @standard_gemfile ||= source_root.join(standard_gemfile_basename)
+ end
+
+ def dev_gemfile
+ @dev_gemfile ||= tool_dir.join("dev_gems.rb")
+ end
+
+ def bindir
+ @bindir ||= source_root.join(ruby_core? ? "libexec" : "exe")
+ end
+
+ def installed_bindir
+ @installed_bindir ||= system_gem_path("bin")
+ end
+
+ def gem_cmd
+ @gem_cmd ||= ruby_core? ? source_root.join("bin/gem") : "gem"
+ end
+
+ def gem_bin
+ @gem_bin ||= ruby_core? ? ENV["GEM_COMMAND"] : "gem"
+ end
+
+ def path
+ env_path = ENV["PATH"]
+ env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == bindir.to_s }.join(File::PATH_SEPARATOR) if ruby_core?
+ env_path
+ end
+
+ def spec_dir
+ @spec_dir ||= source_root.join(ruby_core? ? "spec/bundler" : "spec")
+ end
+
+ def man_dir
+ @man_dir ||= lib_dir.join("bundler/man")
+ end
+
+ def tracked_files
+ @tracked_files ||= git_ls_files(tracked_files_glob)
+ end
+
+ def shipped_files
+ @shipped_files ||= loaded_gemspec.files
+ end
+
+ def lib_tracked_files
+ @lib_tracked_files ||= git_ls_files(lib_tracked_files_glob)
+ end
+
+ def man_tracked_files
+ @man_tracked_files ||= git_ls_files(man_tracked_files_glob)
+ end
+
+ def tmp(*path)
+ source_root.join("tmp", scope, *path)
+ end
+
+ def scope
+ test_number = ENV["TEST_ENV_NUMBER"]
+ return "1" if test_number.nil?
+
+ test_number.empty? ? "1" : test_number
+ end
+
+ def home(*path)
+ tmp.join("home", *path)
+ end
+
+ def default_bundle_path(*path)
+ if Bundler.feature_flag.default_install_uses_path?
+ local_gem_path(*path)
+ else
+ system_gem_path(*path)
+ end
+ end
+
+ def default_cache_path(*path)
+ if Bundler.feature_flag.global_gem_cache?
+ home(".bundle/cache", *path)
+ else
+ default_bundle_path("cache/bundler", *path)
+ end
+ end
+
+ def bundled_app(*path)
+ root = tmp.join("bundled_app")
+ FileUtils.mkdir_p(root)
+ root.join(*path)
+ end
+
+ def bundled_app2(*path)
+ root = tmp.join("bundled_app2")
+ FileUtils.mkdir_p(root)
+ root.join(*path)
+ end
+
+ def vendored_gems(path = nil)
+ scoped_gem_path(bundled_app("vendor/bundle")).join(*[path].compact)
+ end
+
+ def cached_gem(path)
+ bundled_app("vendor/cache/#{path}.gem")
+ end
+
+ def bundled_app_gemfile
+ bundled_app("Gemfile")
+ end
+
+ def bundled_app_lock
+ bundled_app("Gemfile.lock")
+ end
+
+ def base_system_gem_path
+ scoped_gem_path(base_system_gems)
+ end
+
+ def base_system_gems
+ tmp.join("gems/base")
+ end
+
+ def rubocop_gems
+ tmp.join("gems/rubocop")
+ end
+
+ def standard_gems
+ tmp.join("gems/standard")
+ end
+
+ def file_uri_for(path)
+ protocol = "file://"
+ root = Gem.win_platform? ? "/" : ""
+
+ protocol + root + path.to_s
+ end
+
+ def gem_repo1(*args)
+ tmp("gems/remote1", *args)
+ end
+
+ def gem_repo_missing(*args)
+ tmp("gems/missing", *args)
+ end
+
+ def gem_repo2(*args)
+ tmp("gems/remote2", *args)
+ end
+
+ def gem_repo3(*args)
+ tmp("gems/remote3", *args)
+ end
+
+ def gem_repo4(*args)
+ tmp("gems/remote4", *args)
+ end
+
+ def security_repo(*args)
+ tmp("gems/security_repo", *args)
+ end
+
+ def system_gem_path(*path)
+ tmp("gems/system", *path)
+ end
+
+ def pristine_system_gem_path
+ tmp("gems/base_system")
+ end
+
+ def local_gem_path(*path, base: bundled_app)
+ scoped_gem_path(base.join(".bundle")).join(*path)
+ end
+
+ def scoped_gem_path(base)
+ base.join(Gem.ruby_engine, RbConfig::CONFIG["ruby_version"])
+ end
+
+ def lib_path(*args)
+ tmp("libs", *args)
+ end
+
+ def source_lib_dir
+ source_root.join("lib")
+ end
+
+ def lib_dir
+ root.join("lib")
+ end
+
+ # Sometimes rubygems version under test does not include
+ # https://github.com/rubygems/rubygems/pull/2728 and will not always end up
+ # activating the current bundler. In that case, require bundler absolutely.
+ def entrypoint
+ Gem.rubygems_version < Gem::Version.new("3.1.a") ? "#{lib_dir}/bundler" : "bundler"
+ end
+
+ def global_plugin_gem(*args)
+ home ".bundle", "plugin", "gems", *args
+ end
+
+ def local_plugin_gem(*args)
+ bundled_app ".bundle", "plugin", "gems", *args
+ end
+
+ def tmpdir(*args)
+ tmp "tmpdir", *args
+ end
+
+ def replace_version_file(version, dir: source_root)
+ version_file = File.expand_path("lib/bundler/version.rb", dir)
+ contents = File.read(version_file)
+ contents.sub!(/(^\s+VERSION\s*=\s*)"#{Gem::Version::VERSION_PATTERN}"/, %(\\1"#{version}"))
+ File.open(version_file, "w") {|f| f << contents }
+ end
+
+ def ruby_core?
+ # avoid to warnings
+ @ruby_core ||= nil
+
+ if @ruby_core.nil?
+ @ruby_core = true & ENV["GEM_COMMAND"]
+ else
+ @ruby_core
+ end
+ end
+
+ def git_root
+ ruby_core? ? source_root : source_root.parent
+ end
+
+ private
+
+ def git_ls_files(glob)
+ skip "Not running on a git context, since running tests from a tarball" if ruby_core_tarball?
+
+ sys_exec("git ls-files -z -- #{glob}", :dir => source_root).split("\x0")
+ end
+
+ def tracked_files_glob
+ ruby_core? ? "lib/bundler lib/bundler.rb spec/bundler man/bundle*" : ""
+ end
+
+ def lib_tracked_files_glob
+ ruby_core? ? "lib/bundler lib/bundler.rb" : "lib"
+ end
+
+ def man_tracked_files_glob
+ ruby_core? ? "man/bundle* man/gemfile*" : "lib/bundler/man/bundle*.1 lib/bundler/man/gemfile*.5"
+ end
+
+ def ruby_core_tarball?
+ !git_root.join(".git").directory?
+ end
+
+ def rubocop_gemfile_basename
+ tool_dir.join("rubocop_gems.rb")
+ end
+
+ def standard_gemfile_basename
+ tool_dir.join("standard_gems.rb")
+ end
+
+ def tool_dir
+ ruby_core? ? source_root.join("tool/bundler") : source_root.join("../tool/bundler")
+ end
+
+ def templates_dir
+ lib_dir.join("bundler", "templates")
+ end
+
+ extend self
+ end
+end
diff --git a/spec/bundler/support/permissions.rb b/spec/bundler/support/permissions.rb
new file mode 100644
index 0000000000..b21ce3848d
--- /dev/null
+++ b/spec/bundler/support/permissions.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Spec
+ module Permissions
+ def with_umask(new_umask)
+ old_umask = File.umask(new_umask)
+ yield if block_given?
+ ensure
+ File.umask(old_umask)
+ end
+ end
+end
diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb
new file mode 100644
index 0000000000..eca1b2e60d
--- /dev/null
+++ b/spec/bundler/support/platforms.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+module Spec
+ module Platforms
+ include Bundler::GemHelpers
+
+ def rb
+ Gem::Platform::RUBY
+ end
+
+ def mac
+ Gem::Platform.new("x86-darwin-10")
+ end
+
+ def x64_mac
+ Gem::Platform.new("x86_64-darwin-15")
+ end
+
+ def java
+ Gem::Platform.new([nil, "java", nil])
+ end
+
+ def linux
+ Gem::Platform.new("x86_64-linux")
+ end
+
+ def x86_mswin32
+ Gem::Platform.new(["x86", "mswin32", nil])
+ end
+
+ def x64_mswin64
+ Gem::Platform.new(["x64", "mswin64", nil])
+ end
+
+ def x86_mingw32
+ Gem::Platform.new(["x86", "mingw32", nil])
+ end
+
+ def x64_mingw32
+ Gem::Platform.new(["x64", "mingw32", nil])
+ end
+
+ def x64_mingw_ucrt
+ Gem::Platform.new(["x64", "mingw", "ucrt"])
+ end
+
+ def windows_platforms
+ [x86_mswin32, x64_mswin64, x86_mingw32, x64_mingw32, x64_mingw_ucrt]
+ end
+
+ def all_platforms
+ [rb, java, linux, windows_platforms].flatten
+ end
+
+ def not_local
+ all_platforms.find {|p| p != generic_local_platform }
+ end
+
+ def local_tag
+ if RUBY_PLATFORM == "java"
+ :jruby
+ elsif ["x64-mingw32", "x64-mingw-ucrt"].include?(RUBY_PLATFORM)
+ :windows
+ else
+ :ruby
+ end
+ end
+
+ def not_local_tag
+ [:jruby, :windows, :ruby].find {|tag| tag != local_tag }
+ end
+
+ def local_ruby_engine
+ RUBY_ENGINE
+ end
+
+ def local_engine_version
+ RUBY_ENGINE == "ruby" ? Gem.ruby_version : RUBY_ENGINE_VERSION
+ end
+
+ def not_local_engine_version
+ case not_local_tag
+ when :ruby, :windows
+ not_local_ruby_version
+ when :jruby
+ "1.6.1"
+ end
+ end
+
+ def not_local_ruby_version
+ "1.12"
+ end
+
+ def not_local_patchlevel
+ 9999
+ end
+
+ def lockfile_platforms(*extra)
+ formatted_lockfile_platforms(local_platform, *extra)
+ end
+
+ def formatted_lockfile_platforms(*platforms)
+ platforms.map(&:to_s).sort.join("\n ")
+ end
+ end
+end
diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb
new file mode 100644
index 0000000000..4553c0606e
--- /dev/null
+++ b/spec/bundler/support/rubygems_ext.rb
@@ -0,0 +1,177 @@
+# frozen_string_literal: true
+
+require_relative "path"
+
+$LOAD_PATH.unshift(Spec::Path.source_lib_dir.to_s)
+
+module Spec
+ module Rubygems
+ extend self
+
+ def dev_setup
+ install_gems(dev_gemfile)
+ end
+
+ def gem_load(gem_name, bin_container)
+ require_relative "switch_rubygems"
+
+ gem_load_and_activate(gem_name, bin_container)
+ end
+
+ def gem_load_and_possibly_install(gem_name, bin_container)
+ require_relative "switch_rubygems"
+
+ gem_load_activate_and_possibly_install(gem_name, bin_container)
+ end
+
+ def gem_require(gem_name)
+ gem_activate(gem_name)
+ require gem_name
+ end
+
+ def test_setup
+ setup_test_paths
+
+ require "fileutils"
+
+ FileUtils.mkdir_p(Path.home)
+ FileUtils.mkdir_p(Path.tmpdir)
+
+ ENV["HOME"] = Path.home.to_s
+ ENV["TMPDIR"] = Path.tmpdir.to_s
+
+ require "rubygems/user_interaction"
+ Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
+ end
+
+ def install_parallel_test_deps
+ Gem.clear_paths
+
+ require "parallel"
+ require "fileutils"
+
+ install_test_deps
+
+ (2..Parallel.processor_count).each do |n|
+ source = Path.source_root.join("tmp", "1")
+ destination = Path.source_root.join("tmp", n.to_s)
+
+ FileUtils.rm_rf destination
+ FileUtils.cp_r source, destination
+ end
+ end
+
+ def setup_test_paths
+ Gem.clear_paths
+
+ ENV["BUNDLE_PATH"] = nil
+ ENV["GEM_HOME"] = ENV["GEM_PATH"] = Path.base_system_gem_path.to_s
+ ENV["PATH"] = [Path.system_gem_path.join("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR)
+ ENV["PATH"] = [Path.bindir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core?
+ end
+
+ def install_test_deps
+ install_gems(test_gemfile, Path.base_system_gems.to_s)
+ install_gems(rubocop_gemfile, Path.rubocop_gems.to_s)
+ install_gems(standard_gemfile, Path.standard_gems.to_s)
+ end
+
+ def check_source_control_changes(success_message:, error_message:)
+ require "open3"
+
+ output, status = Open3.capture2e("git status --porcelain")
+
+ if status.success? && output.empty?
+ puts
+ puts success_message
+ puts
+ else
+ system("git status --porcelain")
+
+ puts
+ puts error_message
+ puts
+
+ exit(1)
+ end
+ end
+
+ private
+
+ def gem_load_and_activate(gem_name, bin_container)
+ gem_activate(gem_name)
+ load Gem.bin_path(gem_name, bin_container)
+ rescue Gem::LoadError => e
+ abort "We couldn't activate #{gem_name} (#{e.requirement}). Run `gem install #{gem_name}:'#{e.requirement}'`"
+ end
+
+ def gem_load_activate_and_possibly_install(gem_name, bin_container)
+ gem_activate_and_possibly_install(gem_name)
+ load Gem.bin_path(gem_name, bin_container)
+ end
+
+ def gem_activate_and_possibly_install(gem_name)
+ gem_activate(gem_name)
+ rescue Gem::LoadError => e
+ Gem.install(gem_name, e.requirement)
+ retry
+ end
+
+ def gem_activate(gem_name)
+ require "bundler"
+ gem_requirement = Bundler::LockfileParser.new(File.read(dev_lockfile)).specs.find {|spec| spec.name == gem_name }.version
+ gem gem_name, gem_requirement
+ end
+
+ def install_gems(gemfile, path = nil)
+ old_gemfile = ENV["BUNDLE_GEMFILE"]
+ old_orig_gemfile = ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"]
+ ENV["BUNDLE_GEMFILE"] = gemfile.to_s
+ ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = nil
+
+ if path
+ old_path = ENV["BUNDLE_PATH"]
+ ENV["BUNDLE_PATH"] = path
+ else
+ old_path__system = ENV["BUNDLE_PATH__SYSTEM"]
+ ENV["BUNDLE_PATH__SYSTEM"] = "true"
+ end
+
+ puts `#{Gem.ruby} #{File.expand_path("support/bundle.rb", Path.spec_dir)} install --verbose`
+ raise unless $?.success?
+ ensure
+ if path
+ ENV["BUNDLE_PATH"] = old_path
+ else
+ ENV["BUNDLE_PATH__SYSTEM"] = old_path__system
+ end
+
+ ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = old_orig_gemfile
+ ENV["BUNDLE_GEMFILE"] = old_gemfile
+ end
+
+ def test_gemfile
+ Path.test_gemfile
+ end
+
+ def rubocop_gemfile
+ Path.rubocop_gemfile
+ end
+
+ def standard_gemfile
+ Path.standard_gemfile
+ end
+
+ def dev_gemfile
+ Path.dev_gemfile
+ end
+
+ def dev_lockfile
+ lockfile_for(dev_gemfile)
+ end
+
+ def lockfile_for(gemfile)
+ Pathname.new("#{gemfile.expand_path}.lock")
+ end
+ end
+end
diff --git a/spec/bundler/support/rubygems_version_manager.rb b/spec/bundler/support/rubygems_version_manager.rb
new file mode 100644
index 0000000000..5653601ae8
--- /dev/null
+++ b/spec/bundler/support/rubygems_version_manager.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+require "pathname"
+require_relative "helpers"
+require_relative "path"
+
+class RubygemsVersionManager
+ include Spec::Helpers
+ include Spec::Path
+
+ def initialize(source)
+ @source = source
+ end
+
+ def switch
+ return if use_system?
+
+ assert_system_features_not_loaded!
+
+ switch_local_copy_if_needed
+
+ reexec_if_needed
+ end
+
+ def assert_system_features_not_loaded!
+ at_exit do
+ rubylibdir = RbConfig::CONFIG["rubylibdir"]
+
+ rubygems_path = rubylibdir + "/rubygems"
+ rubygems_default_path = rubygems_path + "/defaults"
+
+ bundler_path = rubylibdir + "/bundler"
+ bundler_exemptions = Gem.rubygems_version < Gem::Version.new("3.2.0") ? [bundler_path + "/errors.rb"] : []
+
+ bad_loaded_features = $LOADED_FEATURES.select do |loaded_feature|
+ (loaded_feature.start_with?(rubygems_path) && !loaded_feature.start_with?(rubygems_default_path)) ||
+ (loaded_feature.start_with?(bundler_path) && !bundler_exemptions.any? {|bundler_exemption| loaded_feature.start_with?(bundler_exemption) })
+ end
+
+ errors = if bad_loaded_features.any?
+ all_commands_output + "the following features were incorrectly loaded:\n#{bad_loaded_features.join("\n")}"
+ end
+
+ raise errors if errors
+ end
+ end
+
+ private
+
+ def use_system?
+ @source.nil?
+ end
+
+ def reexec_if_needed
+ return unless rubygems_unrequire_needed?
+
+ require "rbconfig"
+
+ cmd = [RbConfig.ruby, $0, *ARGV].compact
+
+ ENV["RUBYOPT"] = opt_add("-I#{local_copy_path.join("lib")}", opt_remove("--disable-gems", ENV["RUBYOPT"]))
+
+ exec(ENV, *cmd)
+ end
+
+ def switch_local_copy_if_needed
+ return unless local_copy_switch_needed?
+
+ sys_exec("git checkout #{target_tag}", :dir => local_copy_path)
+
+ ENV["RGV"] = local_copy_path.to_s
+ end
+
+ def rubygems_unrequire_needed?
+ require "rubygems"
+ !$LOADED_FEATURES.include?(local_copy_path.join("lib/rubygems.rb").to_s)
+ end
+
+ def local_copy_switch_needed?
+ !source_is_path? && target_tag != local_copy_tag
+ end
+
+ def target_tag
+ @target_tag ||= resolve_target_tag
+ end
+
+ def local_copy_tag
+ sys_exec("git rev-parse --abbrev-ref HEAD", :dir => local_copy_path)
+ end
+
+ def local_copy_path
+ @local_copy_path ||= resolve_local_copy_path
+ end
+
+ def resolve_local_copy_path
+ return expanded_source if source_is_path?
+
+ rubygems_path = source_root.join("tmp/rubygems")
+
+ unless rubygems_path.directory?
+ sys_exec("git clone .. #{rubygems_path}", :dir => source_root)
+ end
+
+ rubygems_path
+ end
+
+ def source_is_path?
+ expanded_source.directory?
+ end
+
+ def expanded_source
+ @expanded_source ||= Pathname.new(@source).expand_path(source_root)
+ end
+
+ def resolve_target_tag
+ return "v#{@source}" if @source.match?(/^\d/)
+
+ @source
+ end
+end
diff --git a/spec/bundler/support/silent_logger.rb b/spec/bundler/support/silent_logger.rb
new file mode 100644
index 0000000000..8665beb2c9
--- /dev/null
+++ b/spec/bundler/support/silent_logger.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require "logger"
+module Spec
+ class SilentLogger
+ (::Logger.instance_methods - Object.instance_methods).each do |logger_instance_method|
+ define_method(logger_instance_method) {|*args, &blk| }
+ end
+ end
+end
diff --git a/spec/bundler/support/switch_rubygems.rb b/spec/bundler/support/switch_rubygems.rb
new file mode 100644
index 0000000000..a138d22333
--- /dev/null
+++ b/spec/bundler/support/switch_rubygems.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+require_relative "rubygems_version_manager"
+RubygemsVersionManager.new(ENV["RGV"]).switch
diff --git a/spec/bundler/support/the_bundle.rb b/spec/bundler/support/the_bundle.rb
new file mode 100644
index 0000000000..f252a4515b
--- /dev/null
+++ b/spec/bundler/support/the_bundle.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require_relative "path"
+
+module Spec
+ class TheBundle
+ include Spec::Path
+
+ attr_accessor :bundle_dir
+
+ def initialize(opts = {})
+ opts = opts.dup
+ @bundle_dir = Pathname.new(opts.delete(:bundle_dir) { bundled_app })
+ raise "Too many options! #{opts}" unless opts.empty?
+ end
+
+ def to_s
+ "the bundle"
+ end
+ alias_method :inspect, :to_s
+
+ def locked?
+ lockfile.file?
+ end
+
+ def lockfile
+ bundle_dir.join("Gemfile.lock")
+ end
+
+ def locked_gems
+ raise "Cannot read lockfile if it doesn't exist" unless locked?
+ Bundler::LockfileParser.new(lockfile.read)
+ end
+ end
+end
diff --git a/spec/bundler/update/gemfile_spec.rb b/spec/bundler/update/gemfile_spec.rb
new file mode 100644
index 0000000000..1c5294101e
--- /dev/null
+++ b/spec/bundler/update/gemfile_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ context "with --gemfile" do
+ it "finds the gemfile" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ bundle :install, :gemfile => bundled_app("NotGemfile")
+ bundle :update, :gemfile => bundled_app("NotGemfile"), :all => true
+
+ # Specify BUNDLE_GEMFILE for `the_bundle`
+ # to retrieve the proper Gemfile
+ ENV["BUNDLE_GEMFILE"] = "NotGemfile"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with gemfile set via config" do
+ before do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ G
+
+ bundle "config set --local gemfile #{bundled_app("NotGemfile")}"
+ bundle :install
+ end
+
+ it "uses the gemfile to update" do
+ bundle "update", :all => true
+ bundle "list"
+
+ expect(out).to include("rack (1.0.0)")
+ end
+
+ it "uses the gemfile while in a subdirectory" do
+ bundled_app("subdir").mkpath
+ bundle "update", :all => true, :dir => bundled_app("subdir")
+ bundle "list", :dir => bundled_app("subdir")
+
+ expect(out).to include("rack (1.0.0)")
+ end
+ end
+end
diff --git a/spec/bundler/update/gems/fund_spec.rb b/spec/bundler/update/gems/fund_spec.rb
new file mode 100644
index 0000000000..d80f4018f3
--- /dev/null
+++ b/spec/bundler/update/gems/fund_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ before do
+ build_repo2 do
+ build_gem "has_funding_and_other_metadata" do |s|
+ s.metadata = {
+ "bug_tracker_uri" => "https://example.com/user/bestgemever/issues",
+ "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md",
+ "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1",
+ "homepage_uri" => "https://bestgemever.example.io",
+ "mailing_list_uri" => "https://groups.example.com/bestgemever",
+ "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding",
+ "source_code_uri" => "https://example.com/user/bestgemever",
+ "wiki_uri" => "https://example.com/user/bestgemever/wiki",
+ }
+ end
+
+ build_gem "has_funding", "1.2.3" do |s|
+ s.metadata = {
+ "funding_uri" => "https://example.com/has_funding/funding",
+ }
+ end
+ end
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'has_funding_and_other_metadata'
+ gem 'has_funding', '< 2.0'
+ G
+
+ bundle :install
+ end
+
+ context "when listed gems are updated" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ gem 'has_funding_and_other_metadata'
+ gem 'has_funding'
+ G
+
+ bundle :update, :all => true
+ end
+
+ it "displays fund message" do
+ expect(out).to include("2 installed gems you directly depend on are looking for funding.")
+ end
+ end
+end
diff --git a/spec/bundler/update/gems/post_install_spec.rb b/spec/bundler/update/gems/post_install_spec.rb
new file mode 100644
index 0000000000..3aaa659d57
--- /dev/null
+++ b/spec/bundler/update/gems/post_install_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ let(:config) {}
+
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack', "< 1.0"
+ gem 'thin'
+ G
+
+ bundle "config set #{config}" if config
+
+ bundle :install
+ end
+
+ shared_examples "a config observer" do
+ context "when ignore post-install messages for gem is set" do
+ let(:config) { "ignore_messages.rack true" }
+
+ it "doesn't display gem's post-install message" do
+ expect(out).not_to include("Rack's post install message")
+ end
+ end
+
+ context "when ignore post-install messages for all gems" do
+ let(:config) { "ignore_messages true" }
+
+ it "doesn't display any post-install messages" do
+ expect(out).not_to include("Post-install message")
+ end
+ end
+ end
+
+ shared_examples "a post-install message outputter" do
+ it "should display post-install messages for updated gems" do
+ expect(out).to include("Post-install message from rack:")
+ expect(out).to include("Rack's post install message")
+ end
+
+ it "should not display the post-install message for non-updated gems" do
+ expect(out).not_to include("Thin's post install message")
+ end
+ end
+
+ context "when listed gem is updated" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack'
+ gem 'thin'
+ G
+
+ bundle :update, :all => true
+ end
+
+ it_behaves_like "a post-install message outputter"
+ it_behaves_like "a config observer"
+ end
+
+ context "when dependency triggers update" do
+ before do
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'rack-obama'
+ gem 'thin'
+ G
+
+ bundle :update, :all => true
+ end
+
+ it_behaves_like "a post-install message outputter"
+ it_behaves_like "a config observer"
+ end
+end
diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb
new file mode 100644
index 0000000000..59e3d2f5fb
--- /dev/null
+++ b/spec/bundler/update/git_spec.rb
@@ -0,0 +1,336 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ describe "git sources" do
+ it "floats on a branch when :branch is used" do
+ build_git "foo", "1.0"
+ update_git "foo", :branch => "omg"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo-1.0")}", :branch => "omg" do
+ gem 'foo'
+ end
+ G
+
+ update_git "foo" do |s|
+ s.write "lib/foo.rb", "FOO = '1.1'"
+ end
+
+ bundle "update", :all => true
+
+ expect(the_bundle).to include_gems "foo 1.1"
+ end
+
+ it "updates correctly when you have like craziness" do
+ build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport")
+ build_git "rails", "3.0", :path => lib_path("rails") do |s|
+ s.add_dependency "activesupport", "= 3.0"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails", :git => "#{file_uri_for(lib_path("rails"))}"
+ G
+
+ bundle "update rails"
+ expect(the_bundle).to include_gems "rails 3.0", "activesupport 3.0"
+ end
+
+ it "floats on a branch when :branch is used and the source is specified in the update" do
+ build_git "foo", "1.0", :path => lib_path("foo")
+ update_git "foo", :branch => "omg", :path => lib_path("foo")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ git "#{lib_path("foo")}", :branch => "omg" do
+ gem 'foo'
+ end
+ G
+
+ update_git "foo", :path => lib_path("foo") do |s|
+ s.write "lib/foo.rb", "FOO = '1.1'"
+ end
+
+ bundle "update --source foo"
+
+ expect(the_bundle).to include_gems "foo 1.1"
+ end
+
+ it "floats on main when updating all gems that are pinned to the source even if you have child dependencies" do
+ build_git "foo", :path => lib_path("foo")
+ build_gem "bar", :to_bundle => true do |s|
+ s.add_dependency "foo"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo"))}"
+ gem "bar"
+ G
+
+ update_git "foo", :path => lib_path("foo") do |s|
+ s.write "lib/foo.rb", "FOO = '1.1'"
+ end
+
+ bundle "update foo"
+
+ expect(the_bundle).to include_gems "foo 1.1"
+ end
+
+ it "notices when you change the repo url in the Gemfile" do
+ build_git "foo", :path => lib_path("foo_one")
+ build_git "foo", :path => lib_path("foo_two")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", "1.0", :git => "#{file_uri_for(lib_path("foo_one"))}"
+ G
+
+ FileUtils.rm_rf lib_path("foo_one")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", "1.0", :git => "#{file_uri_for(lib_path("foo_two"))}"
+ G
+
+ expect(err).to be_empty
+ expect(out).to include("Fetching #{file_uri_for(lib_path)}/foo_two")
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "fetches tags from the remote" do
+ build_git "foo"
+ @remote = build_git("bar", :bare => true)
+ update_git "foo", :remote => file_uri_for(@remote.path)
+ update_git "foo", :push => "main"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => "#{@remote.path}"
+ G
+
+ # Create a new tag on the remote that needs fetching
+ update_git "foo", :tag => "fubar"
+ update_git "foo", :push => "fubar"
+
+ gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem 'foo', :git => "#{@remote.path}", :tag => "fubar"
+ G
+
+ bundle "update", :all => true
+ expect(err).to be_empty
+ end
+
+ describe "with submodules" do
+ before :each do
+ # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
+ system(*%W[git config --global protocol.file.allow always])
+
+ build_repo4 do
+ build_gem "submodule" do |s|
+ s.write "lib/submodule.rb", "puts 'GEM'"
+ end
+ end
+
+ build_git "submodule", "1.0" do |s|
+ s.write "lib/submodule.rb", "puts 'GIT'"
+ end
+
+ build_git "has_submodule", "1.0" do |s|
+ s.add_dependency "submodule"
+ end
+
+ sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0")
+ sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0")
+ end
+
+ it "it unlocks the source when submodules are added to a git source" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ git "#{lib_path("has_submodule-1.0")}" do
+ gem "has_submodule"
+ end
+ G
+
+ run "require 'submodule'"
+ expect(out).to eq("GEM")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ git "#{lib_path("has_submodule-1.0")}", :submodules => true do
+ gem "has_submodule"
+ end
+ G
+
+ run "require 'submodule'"
+ expect(out).to eq("GIT")
+ end
+
+ it "unlocks the source when submodules are removed from git source", :git => ">= 2.9.0" do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ git "#{lib_path("has_submodule-1.0")}", :submodules => true do
+ gem "has_submodule"
+ end
+ G
+
+ run "require 'submodule'"
+ expect(out).to eq("GIT")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo4)}"
+ git "#{lib_path("has_submodule-1.0")}" do
+ gem "has_submodule"
+ end
+ G
+
+ run "require 'submodule'"
+ expect(out).to eq("GEM")
+ end
+ end
+
+ it "errors with a message when the .git repo is gone" do
+ build_git "foo", "1.0"
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}"
+ G
+
+ lib_path("foo-1.0").join(".git").rmtree
+
+ bundle :update, :all => true, :raise_on_error => false
+ expect(err).to include(lib_path("foo-1.0").to_s).
+ and match(/Git error: command `git fetch.+has failed/)
+ end
+
+ it "should not explode on invalid revision on update of gem by name" do
+ build_git "rack", "0.8"
+
+ build_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack", :git => "#{file_uri_for(lib_path("rack-0.8"))}", :branch => "main"
+ G
+
+ bundle %(config set local.rack #{lib_path("local-rack")})
+ bundle "update rack"
+ expect(out).to include("Bundle updated!")
+ end
+
+ it "shows the previous version of the gem" do
+ build_git "rails", "2.3.2", :path => lib_path("rails")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rails", :git => "#{file_uri_for(lib_path("rails"))}"
+ G
+
+ update_git "rails", "3.0", :path => lib_path("rails"), :gemspec => true
+
+ bundle "update", :all => true
+ expect(out).to include("Using rails 3.0 (was 2.3.2) from #{file_uri_for(lib_path("rails"))} (at main@#{revision_for(lib_path("rails"))[0..6]})")
+ end
+ end
+
+ describe "with --source flag" do
+ before :each do
+ build_repo2
+ @git = build_git "foo", :path => lib_path("foo") do |s|
+ s.executables = "foobar"
+ end
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ git "#{lib_path("foo")}" do
+ gem 'foo'
+ end
+ gem 'rack'
+ G
+ end
+
+ it "updates the source" do
+ update_git "foo", :path => @git.path
+
+ bundle "update --source foo"
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" if defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "unlocks gems that were originally pulled in by the source" do
+ update_git "foo", "2.0", :path => @git.path
+
+ bundle "update --source foo"
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+
+ it "leaves all other gems frozen" do
+ update_repo2
+ update_git "foo", :path => @git.path
+
+ bundle "update --source foo"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ context "when the gem and the repository have different names" do
+ before :each do
+ build_repo2
+ @git = build_git "foo", :path => lib_path("bar")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo2)}"
+ git "#{lib_path("bar")}" do
+ gem 'foo'
+ end
+ gem 'rack'
+ G
+ end
+
+ it "the --source flag updates version of gems that were originally pulled in by the source" do
+ spec_lines = lib_path("bar/foo.gemspec").read.split("\n")
+ spec_lines[5] = "s.version = '2.0'"
+
+ update_git "foo", "2.0", :path => @git.path do |s|
+ s.write "foo.gemspec", spec_lines.join("\n")
+ end
+
+ ref = @git.ref_for "main"
+
+ bundle "update --source bar"
+
+ expect(lockfile).to eq <<~G
+ GIT
+ remote: #{@git.path}
+ revision: #{ref}
+ specs:
+ foo (2.0)
+
+ GEM
+ remote: #{file_uri_for(gem_repo2)}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+ end
+end
diff --git a/spec/bundler/update/path_spec.rb b/spec/bundler/update/path_spec.rb
new file mode 100644
index 0000000000..756770313b
--- /dev/null
+++ b/spec/bundler/update/path_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+RSpec.describe "path sources" do
+ describe "bundle update --source" do
+ it "shows the previous version of the gem when updated from path source" do
+ build_lib "activesupport", "2.3.5", :path => lib_path("rails/activesupport")
+
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "activesupport", :path => "#{lib_path("rails/activesupport")}"
+ G
+
+ build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport")
+
+ bundle "update --source activesupport"
+ expect(out).to include("Using activesupport 3.0 (was 2.3.5) from source at `#{lib_path("rails/activesupport")}`")
+ end
+ end
+end
diff --git a/spec/bundler/update/redownload_spec.rb b/spec/bundler/update/redownload_spec.rb
new file mode 100644
index 0000000000..147be823f5
--- /dev/null
+++ b/spec/bundler/update/redownload_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ before :each do
+ install_gemfile <<-G
+ source "#{file_uri_for(gem_repo1)}"
+ gem "rack"
+ G
+ end
+
+ describe "with --force" do
+ it "shows a deprecation when single flag passed", :bundler => 2 do
+ bundle "update rack --force"
+ expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`"
+ end
+
+ it "shows a deprecation when multiple flags passed", :bundler => 2 do
+ bundle "update rack --no-color --force"
+ expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`"
+ end
+ end
+
+ describe "with --redownload" do
+ it "does not show a deprecation when single flag passed" do
+ bundle "update rack --redownload"
+ expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`"
+ end
+
+ it "does not show a deprecation when single multiple flags passed" do
+ bundle "update rack --no-color --redownload"
+ expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`"
+ end
+ end
+end
diff --git a/spec/default.mspec b/spec/default.mspec
index f9392e6da2..0dba98306c 100644
--- a/spec/default.mspec
+++ b/spec/default.mspec
@@ -1,25 +1,67 @@
# -*- ruby -*-
-load "./rbconfig.rb"
-load File.dirname(__FILE__) + '/rubyspec/default.mspec'
-OBJDIR = File.expand_path("spec/rubyspec/optional/capi/ext")
+$VERBOSE = false
+if (opt = ENV["RUBYOPT"]) and (opt = opt.dup).sub!(/(?:\A|\s)-w(?=\z|\s)/, '')
+ ENV["RUBYOPT"] = opt
+end
+require "./rbconfig" unless defined?(RbConfig)
+load File.dirname(__FILE__) + '/ruby/default.mspec'
+OBJDIR = File.expand_path("spec/ruby/optional/capi/ext")
class MSpecScript
+ @testing_ruby = true
+
builddir = Dir.pwd
srcdir = ENV['SRCDIR']
- if !srcdir and File.exist?("#{builddir}/Makefile") then
- File.open("#{builddir}/Makefile", "r:US-ASCII") {|f|
- f.read[/^\s*srcdir\s*=\s*(.+)/i] and srcdir = $1
- }
- end
- srcdir = File.expand_path(srcdir)
+ srcdir ||= File.read("Makefile", encoding: "US-ASCII")[/^\s*srcdir\s*=\s*(.+)/i, 1] rescue nil
config = RbConfig::CONFIG
# The default implementation to run the specs.
set :target, File.join(builddir, "miniruby#{config['exeext']}")
- set :prefix, File.expand_path('rubyspec', File.dirname(__FILE__))
- set :flags, %W[
- -I#{srcdir}/lib
- -I#{srcdir}
- -I#{srcdir}/#{config['EXTOUT']}/common
- #{srcdir}/tool/runruby.rb --archdir=#{Dir.pwd} --extout=#{config['EXTOUT']}
- ]
+ set :prefix, File.expand_path('ruby', File.dirname(__FILE__))
+ if srcdir
+ srcdir = File.expand_path(srcdir)
+ set :flags, %W[
+ -I#{srcdir}/lib
+ #{srcdir}/tool/runruby.rb --archdir=#{builddir} --extout=#{config['EXTOUT']}
+ --
+ ]
+ end
+end
+
+module MSpecScript::JobServer
+ def cores(max = 1)
+ if max > 1 and /(?:\A|\s)--jobserver-(?:auth|fds)=(\d+),(\d+)/ =~ ENV["MAKEFLAGS"]
+ cores = 1
+ begin
+ r = IO.for_fd($1.to_i(10), "rb", autoclose: false)
+ w = IO.for_fd($2.to_i(10), "wb", autoclose: false)
+ jobtokens = r.read_nonblock(max - 1)
+ cores = jobtokens.size
+ if cores > 0
+ cores += 1
+ jobserver = w
+ w = nil
+ at_exit {
+ jobserver.print(jobtokens)
+ jobserver.close
+ }
+ MSpecScript::JobServer.module_eval do
+ remove_method :cores
+ define_method(:cores) do
+ cores
+ end
+ end
+ return cores
+ end
+ rescue Errno::EBADF
+ ensure
+ r&.close
+ w&.close
+ end
+ end
+ super
+ end
+end
+
+class MSpecScript
+ prepend JobServer
end
diff --git a/spec/mspec/.rspec b/spec/mspec/.rspec
new file mode 100644
index 0000000000..4e1e0d2f72
--- /dev/null
+++ b/spec/mspec/.rspec
@@ -0,0 +1 @@
+--color
diff --git a/spec/mspec/Gemfile b/spec/mspec/Gemfile
new file mode 100644
index 0000000000..617a995cad
--- /dev/null
+++ b/spec/mspec/Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+
+gem "rake", "~> 12.3"
+gem "rspec", "~> 3.0"
diff --git a/spec/mspec/Gemfile.lock b/spec/mspec/Gemfile.lock
new file mode 100644
index 0000000000..cd39906044
--- /dev/null
+++ b/spec/mspec/Gemfile.lock
@@ -0,0 +1,26 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ diff-lcs (1.4.4)
+ rake (12.3.3)
+ rspec (3.10.0)
+ rspec-core (~> 3.10.0)
+ rspec-expectations (~> 3.10.0)
+ rspec-mocks (~> 3.10.0)
+ rspec-core (3.10.1)
+ rspec-support (~> 3.10.0)
+ rspec-expectations (3.10.1)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.10.0)
+ rspec-mocks (3.10.2)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.10.0)
+ rspec-support (3.10.2)
+
+PLATFORMS
+ java
+ ruby
+
+DEPENDENCIES
+ rake (~> 12.3)
+ rspec (~> 3.0)
diff --git a/spec/mspec/LICENSE b/spec/mspec/LICENSE
new file mode 100644
index 0000000000..d581dd1c9f
--- /dev/null
+++ b/spec/mspec/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2008 Engine Yard, Inc. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/spec/mspec/README.md b/spec/mspec/README.md
new file mode 100644
index 0000000000..94ab608031
--- /dev/null
+++ b/spec/mspec/README.md
@@ -0,0 +1,84 @@
+## Overview
+
+MSpec is a specialized framework that is syntax-compatible with RSpec 2 for
+basic things like `describe`, `it` blocks and `before`, `after` actions.
+MSpec contains additional features that assist in writing specs for
+Ruby implementations in [ruby/spec](https://github.com/ruby/spec).
+
+MSpec attempts to use the simplest Ruby language features so that beginning
+Ruby implementations can run the Ruby specs. For example, no file from the
+standard library or RubyGems is necessary to run MSpec.
+
+MSpec is not intended as a replacement for RSpec. MSpec attempts to provide a
+subset of RSpec's features in some cases and a superset in others. It does not
+provide all the matchers, for instance.
+
+However, MSpec provides several extensions to facilitate writing the Ruby
+specs in a manner compatible with multiple Ruby implementations.
+
+ 1. MSpec offers a set of guards to control execution of the specs. These
+ guards not only enable or disable execution but also annotate the specs
+ with additional information about why they are run or not run.
+
+ 2. MSpec provides a different shared spec implementation specifically
+ designed to ease writing specs for the numerous aliased methods in Ruby.
+
+ 3. MSpec provides various helper methods to simplify some specs, for
+ example, creating temporary file names.
+
+ 4. MSpec has several specialized runner scripts that includes a
+ configuration facility with a default project file and user-specific
+ overrides.
+
+ 5. MSpec support "tagging", that is excluding specs known as failing on
+ a particular Ruby implementation, and automatically adding and removing tags
+ while running the specs.
+
+## Requirements
+
+MSpec requires Ruby 2.6 or more recent.
+
+## Bundler
+
+A Gemfile is provided. Use Bundler to install gem dependencies. To install
+Bundler, run the following:
+
+```bash
+gem install bundler
+```
+
+To install the gem dependencies with Bundler, run the following:
+
+```bash
+ruby -S bundle install
+```
+
+## Development
+
+Use RSpec to run the MSpec specs. There are no plans currently to make the
+MSpec specs runnable by MSpec: https://github.com/ruby/mspec/issues/19.
+
+After installing the gem dependencies, the specs can be run as follows:
+
+```bash
+ruby -S bundle exec rspec
+```
+
+To run an individual spec file, use the following example:
+
+```bash
+ruby -S bundle exec rspec spec/helpers/ruby_exe_spec.rb
+```
+
+## Documentation
+
+See [CONTRIBUTING.md](https://github.com/ruby/spec/blob/master/CONTRIBUTING.md) in ruby/spec
+for a list of matchers and how to use `mspec`.
+
+## Source Code
+
+See https://github.com/ruby/mspec
+
+## License
+
+See the LICENSE in the source code.
diff --git a/spec/mspec/Rakefile b/spec/mspec/Rakefile
new file mode 100644
index 0000000000..6a9de7a95e
--- /dev/null
+++ b/spec/mspec/Rakefile
@@ -0,0 +1,6 @@
+require 'bundler/setup'
+require 'rspec/core/rake_task'
+
+RSpec::Core::RakeTask.new(:spec)
+
+task :default => :spec
diff --git a/spec/mspec/bin/mkspec b/spec/mspec/bin/mkspec
new file mode 100755
index 0000000000..00f1fdff47
--- /dev/null
+++ b/spec/mspec/bin/mkspec
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path('../../lib', __FILE__)
+
+require 'mspec/commands/mkspec'
+
+MkSpec.main
diff --git a/spec/mspec/bin/mkspec.bat b/spec/mspec/bin/mkspec.bat
new file mode 100755
index 0000000000..1073d20a9b
--- /dev/null
+++ b/spec/mspec/bin/mkspec.bat
@@ -0,0 +1 @@
+@"ruby.exe" "%~dpn0" %*
diff --git a/spec/mspec/bin/mspec b/spec/mspec/bin/mspec
new file mode 100755
index 0000000000..5bd753c06d
--- /dev/null
+++ b/spec/mspec/bin/mspec
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path('../../lib', __FILE__)
+
+require 'mspec/commands/mspec'
+
+MSpecMain.main(false)
diff --git a/spec/mspec/bin/mspec-ci b/spec/mspec/bin/mspec-ci
new file mode 100755
index 0000000000..d7cd50a827
--- /dev/null
+++ b/spec/mspec/bin/mspec-ci
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path('../../lib', __FILE__)
+
+require 'mspec/commands/mspec-ci'
+
+MSpecCI.main
diff --git a/spec/mspec/bin/mspec-ci.bat b/spec/mspec/bin/mspec-ci.bat
new file mode 100755
index 0000000000..1073d20a9b
--- /dev/null
+++ b/spec/mspec/bin/mspec-ci.bat
@@ -0,0 +1 @@
+@"ruby.exe" "%~dpn0" %*
diff --git a/spec/mspec/bin/mspec-run b/spec/mspec/bin/mspec-run
new file mode 100755
index 0000000000..010ecefe35
--- /dev/null
+++ b/spec/mspec/bin/mspec-run
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path('../../lib', __FILE__)
+
+require 'mspec/commands/mspec-run'
+
+MSpecRun.main
diff --git a/spec/mspec/bin/mspec-run.bat b/spec/mspec/bin/mspec-run.bat
new file mode 100755
index 0000000000..1073d20a9b
--- /dev/null
+++ b/spec/mspec/bin/mspec-run.bat
@@ -0,0 +1 @@
+@"ruby.exe" "%~dpn0" %*
diff --git a/spec/mspec/bin/mspec-tag b/spec/mspec/bin/mspec-tag
new file mode 100755
index 0000000000..a5f9fffaaa
--- /dev/null
+++ b/spec/mspec/bin/mspec-tag
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path('../../lib', __FILE__)
+
+require 'mspec/commands/mspec-tag'
+
+MSpecTag.main
diff --git a/spec/mspec/bin/mspec-tag.bat b/spec/mspec/bin/mspec-tag.bat
new file mode 100755
index 0000000000..1073d20a9b
--- /dev/null
+++ b/spec/mspec/bin/mspec-tag.bat
@@ -0,0 +1 @@
+@"ruby.exe" "%~dpn0" %*
diff --git a/spec/mspec/bin/mspec.bat b/spec/mspec/bin/mspec.bat
new file mode 100755
index 0000000000..1073d20a9b
--- /dev/null
+++ b/spec/mspec/bin/mspec.bat
@@ -0,0 +1 @@
+@"ruby.exe" "%~dpn0" %*
diff --git a/spec/mspec/lib/mspec.rb b/spec/mspec/lib/mspec.rb
new file mode 100644
index 0000000000..d24abd96f1
--- /dev/null
+++ b/spec/mspec/lib/mspec.rb
@@ -0,0 +1,8 @@
+require 'mspec/utils/format'
+require 'mspec/matchers'
+require 'mspec/expectations'
+require 'mspec/mocks'
+require 'mspec/runner'
+require 'mspec/guards'
+require 'mspec/helpers'
+require 'mspec/version'
diff --git a/spec/mspec/lib/mspec/commands/mkspec.rb b/spec/mspec/lib/mspec/commands/mkspec.rb
new file mode 100755
index 0000000000..a31cb2191c
--- /dev/null
+++ b/spec/mspec/lib/mspec/commands/mkspec.rb
@@ -0,0 +1,145 @@
+#!/usr/bin/env ruby
+
+require 'rbconfig'
+require 'mspec/version'
+require 'mspec/utils/options'
+require 'mspec/utils/name_map'
+require 'mspec/helpers/fs'
+
+class MkSpec
+ attr_reader :config
+
+ def initialize
+ @config = {
+ :constants => [],
+ :requires => [],
+ :base => "core",
+ :version => nil
+ }
+ @map = NameMap.new true
+ end
+
+ def options(argv = ARGV)
+ options = MSpecOptions.new "mkspec [options]", 32
+
+ options.on("-c", "--constant", "CONSTANT",
+ "Class or Module to generate spec stubs for") do |name|
+ config[:constants] << name
+ end
+ options.on("-b", "--base", "DIR",
+ "Directory to generate specs into") do |directory|
+ config[:base] = File.expand_path directory
+ end
+ options.on("-r", "--require", "LIBRARY",
+ "A library to require") do |file|
+ config[:requires] << file
+ end
+ options.on("-V", "--version-guard", "VERSION",
+ "Specify version for ruby_version_is guards") do |version|
+ config[:version] = version
+ end
+ options.version MSpec::VERSION
+ options.help
+
+ options.doc "\n How might this work in the real world?\n"
+ options.doc " 1. To create spec stubs for every class or module in Object\n"
+ options.doc " $ mkspec\n"
+ options.doc " 2. To create spec stubs for Fixnum\n"
+ options.doc " $ mkspec -c Fixnum\n"
+ options.doc " 3. To create spec stubs for Complex in 'superspec/complex'\n"
+ options.doc " $ mkspec -c Complex -r complex -b superspec"
+ options.doc ""
+
+ options.parse argv
+ end
+
+ def create_directory(mod)
+ subdir = @map.dir_name mod, config[:base]
+
+ if File.exist? subdir
+ unless File.directory? subdir
+ puts "#{subdir} already exists and is not a directory."
+ return nil
+ end
+ else
+ mkdir_p subdir
+ end
+
+ subdir
+ end
+
+ def write_requires(dir, file)
+ prefix = config[:base] + '/'
+ raise dir unless dir.start_with? prefix
+ sub = dir[prefix.size..-1]
+ parents = '../' * (sub.split('/').length + 1)
+
+ File.open(file, 'w') do |f|
+ f.puts "require_relative '#{parents}spec_helper'"
+ config[:requires].each do |lib|
+ f.puts "require '#{lib}'"
+ end
+ end
+ end
+
+ def write_version(f)
+ f.puts ""
+ if version = config[:version]
+ f.puts "ruby_version_is #{version} do"
+ yield " "
+ f.puts "end"
+ else
+ yield ""
+ end
+ end
+
+ def write_spec(file, meth, exists)
+ if exists
+ command = "#{RbConfig.ruby} #{MSPEC_HOME}/bin/mspec-run --dry-run --unguarded -fs -e '#{meth}' #{file}"
+ puts "$ #{command}" if $DEBUG
+ out = `#{command}`
+ return if out.include?(meth)
+ end
+
+ File.open file, 'a' do |f|
+ write_version(f) do |indent|
+ f.puts <<-EOS
+#{indent}describe "#{meth}" do
+#{indent} it "needs to be reviewed for spec completeness"
+#{indent}end
+EOS
+ end
+ end
+
+ puts file
+ end
+
+ def create_file(dir, mod, meth, name)
+ file = File.join dir, @map.file_name(meth, mod)
+ exists = File.exist? file
+
+ write_requires dir, file unless exists
+ write_spec file, name, exists
+ end
+
+ def run
+ config[:requires].each { |lib| require lib }
+ constants = config[:constants]
+ constants = Object.constants if constants.empty?
+
+ @map.map({}, constants).each do |mod, methods|
+ name = mod.chop
+ next unless dir = create_directory(name)
+
+ methods.each { |method| create_file dir, name, method, mod + method }
+ end
+ end
+
+ def self.main
+ ENV['MSPEC_RUNNER'] = '1'
+
+ script = new
+ script.options
+ script.run
+ end
+end
diff --git a/spec/mspec/lib/mspec/commands/mspec-ci.rb b/spec/mspec/lib/mspec/commands/mspec-ci.rb
new file mode 100644
index 0000000000..a31db1d7dc
--- /dev/null
+++ b/spec/mspec/lib/mspec/commands/mspec-ci.rb
@@ -0,0 +1,79 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+
+require 'mspec/version'
+require 'mspec/utils/options'
+require 'mspec/utils/script'
+
+
+class MSpecCI < MSpecScript
+ def options(argv = ARGV)
+ options = MSpecOptions.new "mspec ci [options] (FILE|DIRECTORY|GLOB)+", 30, config
+
+ options.doc " Ask yourself:"
+ options.doc " 1. How to run the specs?"
+ options.doc " 2. How to modify the guard behavior?"
+ options.doc " 2. How to display the output?"
+ options.doc " 3. What action to perform?"
+ options.doc " 4. When to perform it?"
+
+ options.doc "\n How to run the specs"
+ options.chdir
+ options.prefix
+ options.configure { |f| load f }
+ options.pretend
+ options.interrupt
+ options.timeout
+
+ options.doc "\n How to modify the guard behavior"
+ options.unguarded
+ options.verify
+
+ options.doc "\n How to display their output"
+ options.formatters
+ options.verbose
+
+ options.doc "\n What action to perform"
+ options.actions
+
+ options.doc "\n When to perform it"
+ options.action_filters
+
+ options.doc "\n Help!"
+ options.debug
+ options.version MSpec::VERSION
+ options.help
+
+ options.doc "\n Custom options"
+ custom_options options
+
+ options.doc "\n How might this work in the real world?"
+ options.doc "\n 1. To simply run the known good specs"
+ options.doc "\n $ mspec ci"
+ options.doc "\n 2. To run a subset of the known good specs"
+ options.doc "\n $ mspec ci path/to/specs"
+ options.doc "\n 3. To start the debugger before the spec matching 'this crashes'"
+ options.doc "\n $ mspec ci --spec-debug -S 'this crashes'"
+ options.doc ""
+
+ patterns = options.parse argv
+ patterns = config[:ci_files] if patterns.empty?
+ @files = files patterns
+ end
+
+ def run
+ MSpec.register_tags_patterns config[:tags_patterns]
+ MSpec.register_files @files
+
+ tags = ["fails", "critical", "unstable", "incomplete", "unsupported"]
+ tags += Array(config[:ci_xtags])
+
+ require 'mspec/runner/filters/tag'
+ filter = TagFilter.new(:exclude, *tags)
+ filter.register
+
+ MSpec.process
+ exit MSpec.exit_code
+ end
+end
diff --git a/spec/mspec/lib/mspec/commands/mspec-run.rb b/spec/mspec/lib/mspec/commands/mspec-run.rb
new file mode 100644
index 0000000000..4d8f4d9984
--- /dev/null
+++ b/spec/mspec/lib/mspec/commands/mspec-run.rb
@@ -0,0 +1,87 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+
+require 'mspec/version'
+require 'mspec/utils/options'
+require 'mspec/utils/script'
+
+
+class MSpecRun < MSpecScript
+ def initialize
+ super
+
+ config[:files] = []
+ end
+
+ def options(argv = ARGV)
+ options = MSpecOptions.new "mspec run [options] (FILE|DIRECTORY|GLOB)+", 30, config
+
+ options.doc " Ask yourself:"
+ options.doc " 1. What specs to run?"
+ options.doc " 2. How to modify the execution?"
+ options.doc " 3. How to modify the guard behavior?"
+ options.doc " 4. How to display the output?"
+ options.doc " 5. What action to perform?"
+ options.doc " 6. When to perform it?"
+
+ options.doc "\n What specs to run"
+ options.filters
+
+ options.doc "\n How to modify the execution"
+ options.chdir
+ options.prefix
+ options.configure { |f| load f }
+ options.randomize
+ options.repeat
+ options.pretend
+ options.interrupt
+ options.timeout
+
+ options.doc "\n How to modify the guard behavior"
+ options.unguarded
+ options.verify
+
+ options.doc "\n How to display their output"
+ options.formatters
+ options.verbose
+
+ options.doc "\n What action to perform"
+ options.actions
+
+ options.doc "\n When to perform it"
+ options.action_filters
+
+ options.doc "\n Help!"
+ options.debug
+ options.version MSpec::VERSION
+ options.help
+
+ options.doc "\n Custom options"
+ custom_options options
+
+ options.doc "\n How might this work in the real world?"
+ options.doc "\n 1. To simply run some specs"
+ options.doc "\n $ mspec path/to/the/specs"
+ options.doc " mspec path/to/the_file_spec.rb"
+ options.doc "\n 2. To run specs tagged with 'fails'"
+ options.doc "\n $ mspec -g fails path/to/the_file_spec.rb"
+ options.doc "\n 3. To start the debugger before the spec matching 'this crashes'"
+ options.doc "\n $ mspec --spec-debug -S 'this crashes' path/to/the_file_spec.rb"
+ options.doc "\n 4. To run some specs matching 'this crashes'"
+ options.doc "\n $ mspec -e 'this crashes' path/to/the_file_spec.rb"
+
+ options.doc ""
+
+ patterns = options.parse argv
+ @files = files_from_patterns(patterns)
+ end
+
+ def run
+ MSpec.register_tags_patterns config[:tags_patterns]
+ MSpec.register_files @files
+
+ MSpec.process
+ exit MSpec.exit_code
+ end
+end
diff --git a/spec/mspec/lib/mspec/commands/mspec-tag.rb b/spec/mspec/lib/mspec/commands/mspec-tag.rb
new file mode 100644
index 0000000000..e1d04d1446
--- /dev/null
+++ b/spec/mspec/lib/mspec/commands/mspec-tag.rb
@@ -0,0 +1,133 @@
+#!/usr/bin/env ruby
+
+require 'mspec/version'
+require 'mspec/utils/options'
+require 'mspec/utils/script'
+
+
+class MSpecTag < MSpecScript
+ def initialize
+ super
+
+ config[:tagger] = :add
+ config[:tag] = 'fails:'
+ config[:outcome] = :fail
+ config[:ltags] = []
+ end
+
+ def options(argv = ARGV)
+ options = MSpecOptions.new "mspec tag [options] (FILE|DIRECTORY|GLOB)+", 30, config
+
+ options.doc " Ask yourself:"
+ options.doc " 1. What specs to run?"
+ options.doc " 2. How to modify the execution?"
+ options.doc " 3. How to display the output?"
+ options.doc " 4. What tag action to perform?"
+ options.doc " 5. When to perform it?"
+
+ options.doc "\n What specs to run"
+ options.filters
+
+ options.doc "\n How to modify the execution"
+ options.configure { |f| load f }
+ options.pretend
+ options.unguarded
+ options.interrupt
+ options.timeout
+
+ options.doc "\n How to display their output"
+ options.formatters
+ options.verbose
+
+ options.doc "\n What action to perform and when to perform it"
+ options.on("-N", "--add", "TAG",
+ "Add TAG with format 'tag' or 'tag(comment)' (see -Q, -F, -L)") do |o|
+ config[:tagger] = :add
+ config[:tag] = "#{o}:"
+ end
+ options.on("-R", "--del", "TAG",
+ "Delete TAG (see -Q, -F, -L)") do |o|
+ config[:tagger] = :del
+ config[:tag] = "#{o}:"
+ config[:outcome] = :pass
+ end
+ options.on("-Q", "--pass", "Apply action to specs that pass (default for --del)") do
+ config[:outcome] = :pass
+ end
+ options.on("-F", "--fail", "Apply action to specs that fail (default for --add)") do
+ config[:outcome] = :fail
+ end
+ options.on("-L", "--all", "Apply action to all specs") do
+ config[:outcome] = :all
+ end
+ options.on("--list", "TAG", "Display descriptions of any specs tagged with TAG") do |t|
+ config[:tagger] = :list
+ config[:ltags] << t
+ end
+ options.on("--list-all", "Display descriptions of any tagged specs") do
+ config[:tagger] = :list_all
+ end
+ options.on("--purge", "Remove all tags not matching any specs") do
+ config[:tagger] = :purge
+ end
+
+ options.doc "\n Help!"
+ options.debug
+ options.version MSpec::VERSION
+ options.help
+
+ options.doc "\n Custom options"
+ custom_options options
+
+ options.doc "\n How might this work in the real world?"
+ options.doc "\n 1. To add the 'fails' tag to failing specs"
+ options.doc "\n $ mspec tag path/to/the_file_spec.rb"
+ options.doc "\n 2. To remove the 'fails' tag from passing specs"
+ options.doc "\n $ mspec tag --del fails path/to/the_file_spec.rb"
+ options.doc "\n 3. To display the descriptions for all specs tagged with 'fails'"
+ options.doc "\n $ mspec tag --list fails path/to/the/specs"
+ options.doc ""
+
+ patterns = options.parse argv
+ if patterns.empty?
+ puts options
+ puts "No files specified."
+ exit 1
+ end
+ @files = files patterns
+ end
+
+ def register
+ require 'mspec/runner/actions'
+
+ case config[:tagger]
+ when :add, :del
+ tag = SpecTag.new config[:tag]
+ tagger = TagAction.new(config[:tagger], config[:outcome], tag.tag, tag.comment,
+ config[:atags], config[:astrings])
+ when :list, :list_all
+ tagger = TagListAction.new config[:tagger] == :list_all ? nil : config[:ltags]
+ MSpec.register_mode :pretend
+ config[:formatter] = false
+ when :purge
+ tagger = TagPurgeAction.new
+ MSpec.register_mode :pretend
+ MSpec.register_mode :unguarded
+ config[:formatter] = false
+ else
+ raise ArgumentError, "No recognized action given"
+ end
+ tagger.register
+
+ super
+ end
+
+ def run
+ MSpec.register_tags_patterns config[:tags_patterns]
+ MSpec.register_files @files
+
+ MSpec.process
+ exit MSpec.exit_code
+ end
+end
+
diff --git a/spec/mspec/lib/mspec/commands/mspec.rb b/spec/mspec/lib/mspec/commands/mspec.rb
new file mode 100755
index 0000000000..9c38cebcda
--- /dev/null
+++ b/spec/mspec/lib/mspec/commands/mspec.rb
@@ -0,0 +1,120 @@
+#!/usr/bin/env ruby
+
+require 'mspec/version'
+require 'mspec/utils/options'
+require 'mspec/utils/script'
+require 'mspec/helpers/tmp'
+require 'mspec/runner/actions/filter'
+require 'mspec/runner/actions/timer'
+
+
+class MSpecMain < MSpecScript
+ def initialize
+ super
+
+ config[:loadpath] = []
+ config[:requires] = []
+ config[:target] = ENV['RUBY'] || 'ruby'
+ config[:flags] = []
+ config[:command] = nil
+ config[:options] = []
+ config[:launch] = []
+ end
+
+ def options(argv = ARGV)
+ config[:command] = argv.shift if ["ci", "run", "tag"].include?(argv[0])
+
+ options = MSpecOptions.new "mspec [COMMAND] [options] (FILE|DIRECTORY|GLOB)+", 30, config
+ @options = options
+
+ options.doc " The mspec command sets up and invokes the sub-commands"
+ options.doc " (see below) to enable, for instance, running the specs"
+ options.doc " with different implementations like ruby, jruby, rbx, etc.\n"
+
+ options.configure do |f|
+ load f
+ config[:options] << '-B' << f
+ end
+
+ options.targets
+
+ options.on("--warnings", "Don't suppress warnings") do
+ config[:flags] << '-w'
+ ENV['OUTPUT_WARNINGS'] = '1'
+ end
+
+ options.on("-j", "--multi", "Run multiple (possibly parallel) subprocesses") do
+ config[:multi] = true
+ end
+
+ options.version MSpec::VERSION do
+ if config[:command]
+ config[:options] << "-v"
+ else
+ puts "#{File.basename $0} #{MSpec::VERSION}"
+ exit
+ end
+ end
+
+ options.help do
+ if config[:command]
+ config[:options] << "-h"
+ else
+ puts options
+ exit 1
+ end
+ end
+
+ options.doc "\n Custom options"
+ custom_options options
+
+ # The rest of the help output
+ options.doc "\n where COMMAND is one of:\n"
+ options.doc " run - Run the specified specs (default)"
+ options.doc " ci - Run the known good specs"
+ options.doc " tag - Add or remove tags\n"
+ options.doc " mspec COMMAND -h for more options\n"
+ options.doc " example: $ mspec run -h\n"
+
+ options.on_extra { |o| config[:options] << o }
+ options.parse(argv)
+
+ if config[:multi]
+ options = MSpecOptions.new "mspec", 30, config
+ options.all
+ patterns = options.parse(config[:options])
+ @files = files_from_patterns(patterns)
+ end
+ end
+
+ def register; end
+
+ def multi_exec(argv)
+ require 'mspec/runner/formatters/multi'
+ formatter = config_formatter.extend(MultiFormatter)
+
+ require 'mspec/runner/parallel'
+ processes = cores(@files.size)
+ ParallelRunner.new(@files, processes, formatter, argv).run
+ end
+
+ def run
+ argv = config[:target].split(/\s+/)
+
+ argv.concat config[:launch]
+ argv.concat config[:flags]
+ argv.concat config[:loadpath]
+ argv.concat config[:requires]
+ argv << "#{MSPEC_HOME}/bin/mspec-#{config[:command] || 'run'}"
+ argv.concat config[:options]
+
+ if config[:multi]
+ exit multi_exec(argv)
+ else
+ log = config[:options].include?('--error-output') ? $stdout : $stderr
+ log.puts "$ #{argv.join(' ')}"
+ log.flush
+ exec(*argv, close_others: false)
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/expectations.rb b/spec/mspec/lib/mspec/expectations.rb
new file mode 100644
index 0000000000..d07f959b27
--- /dev/null
+++ b/spec/mspec/lib/mspec/expectations.rb
@@ -0,0 +1,2 @@
+require 'mspec/expectations/expectations'
+require 'mspec/expectations/should'
diff --git a/spec/mspec/lib/mspec/expectations/expectations.rb b/spec/mspec/lib/mspec/expectations/expectations.rb
new file mode 100644
index 0000000000..09852ab557
--- /dev/null
+++ b/spec/mspec/lib/mspec/expectations/expectations.rb
@@ -0,0 +1,39 @@
+class SpecExpectationNotMetError < StandardError
+end
+
+class SpecExpectationNotFoundError < StandardError
+ def message
+ "No behavior expectation was found in the example"
+ end
+end
+
+class SkippedSpecError < StandardError
+end
+
+class SpecExpectation
+ def self.fail_with(expected, actual)
+ expected_to_s = expected.to_s
+ actual_to_s = actual.to_s
+ if expected_to_s.size + actual_to_s.size > 80
+ message = "#{expected_to_s}\n#{actual_to_s}"
+ else
+ message = "#{expected_to_s} #{actual_to_s}"
+ end
+ raise SpecExpectationNotMetError, message
+ end
+
+ def self.fail_predicate(receiver, predicate, args, block, result, expectation)
+ receiver_to_s = MSpec.format(receiver)
+ before_method = predicate.to_s =~ /^[a-z]/ ? "." : " "
+ predicate_to_s = "#{before_method}#{predicate}"
+ predicate_to_s += " " unless args.empty?
+ args_to_s = args.map { |arg| MSpec.format(arg) }.join(', ')
+ args_to_s += " { ... }" if block
+ result_to_s = MSpec.format(result)
+ raise SpecExpectationNotMetError, "Expected #{receiver_to_s}#{predicate_to_s}#{args_to_s}\n#{expectation} but was #{result_to_s}"
+ end
+
+ def self.fail_single_arg_predicate(receiver, predicate, arg, result, expectation)
+ fail_predicate(receiver, predicate, [arg], nil, result, expectation)
+ end
+end
diff --git a/spec/mspec/lib/mspec/expectations/should.rb b/spec/mspec/lib/mspec/expectations/should.rb
new file mode 100644
index 0000000000..c1790e0ac8
--- /dev/null
+++ b/spec/mspec/lib/mspec/expectations/should.rb
@@ -0,0 +1,41 @@
+class Object
+ NO_MATCHER_GIVEN = Object.new
+
+ def should(matcher = NO_MATCHER_GIVEN, &block)
+ MSpec.expectation
+ state = MSpec.current.state
+ raise "should outside example" unless state
+ MSpec.actions :expectation, state
+
+ if NO_MATCHER_GIVEN.equal?(matcher)
+ SpecPositiveOperatorMatcher.new(self)
+ else
+ # The block was given to #should syntactically, but it was intended for a matcher like #raise_error
+ matcher.block = block if block
+
+ unless matcher.matches? self
+ expected, actual = matcher.failure_message
+ SpecExpectation.fail_with(expected, actual)
+ end
+ end
+ end
+
+ def should_not(matcher = NO_MATCHER_GIVEN, &block)
+ MSpec.expectation
+ state = MSpec.current.state
+ raise "should_not outside example" unless state
+ MSpec.actions :expectation, state
+
+ if NO_MATCHER_GIVEN.equal?(matcher)
+ SpecNegativeOperatorMatcher.new(self)
+ else
+ # The block was given to #should_not syntactically, but it was intended for the matcher
+ matcher.block = block if block
+
+ if matcher.matches? self
+ expected, actual = matcher.negative_failure_message
+ SpecExpectation.fail_with(expected, actual)
+ end
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/guards.rb b/spec/mspec/lib/mspec/guards.rb
new file mode 100644
index 0000000000..454ac0c776
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards.rb
@@ -0,0 +1,11 @@
+require 'mspec/guards/block_device'
+require 'mspec/guards/bug'
+require 'mspec/guards/conflict'
+require 'mspec/guards/endian'
+require 'mspec/guards/feature'
+require 'mspec/guards/guard'
+require 'mspec/guards/platform'
+require 'mspec/guards/quarantine'
+require 'mspec/guards/support'
+require 'mspec/guards/superuser'
+require 'mspec/guards/version'
diff --git a/spec/mspec/lib/mspec/guards/block_device.rb b/spec/mspec/lib/mspec/guards/block_device.rb
new file mode 100644
index 0000000000..ae736a2d4e
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/block_device.rb
@@ -0,0 +1,16 @@
+require 'mspec/guards/guard'
+
+class BlockDeviceGuard < SpecGuard
+ def match?
+ platform_is_not :freebsd, :windows, :opal do
+ block = `find /dev /devices -type b 2> /dev/null`
+ return !(block.nil? || block.empty?)
+ end
+
+ false
+ end
+end
+
+def with_block_device(&block)
+ BlockDeviceGuard.new.run_if(:with_block_device, &block)
+end
diff --git a/spec/mspec/lib/mspec/guards/bug.rb b/spec/mspec/lib/mspec/guards/bug.rb
new file mode 100644
index 0000000000..a6af0ef964
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/bug.rb
@@ -0,0 +1,29 @@
+require 'mspec/guards/version'
+
+class BugGuard < VersionGuard
+ def initialize(bug, requirement)
+ @bug = bug
+ if String === requirement
+ MSpec.deprecate "ruby_bug with a single version", 'an exclusive range ("2.1"..."2.3")'
+ super(FULL_RUBY_VERSION, requirement)
+ @requirement = SpecVersion.new requirement, true
+ else
+ super(FULL_RUBY_VERSION, requirement)
+ end
+ end
+
+ def match?
+ return false if MSpec.mode? :no_ruby_bug
+ return false unless PlatformGuard.standard?
+
+ if Range === @requirement
+ super
+ else
+ FULL_RUBY_VERSION <= @requirement
+ end
+ end
+end
+
+def ruby_bug(bug, requirement, &block)
+ BugGuard.new(bug, requirement).run_unless(:ruby_bug, &block)
+end
diff --git a/spec/mspec/lib/mspec/guards/conflict.rb b/spec/mspec/lib/mspec/guards/conflict.rb
new file mode 100644
index 0000000000..4930e5734d
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/conflict.rb
@@ -0,0 +1,23 @@
+require 'mspec/guards/guard'
+require 'mspec/utils/deprecate'
+
+class ConflictsGuard < SpecGuard
+ def initialize(*args)
+ MSpec.deprecate 'conflicts_with', 'guard -> { condition } do'
+ super(*args)
+ end
+
+ def match?
+ # Always convert constants to symbols regardless of version.
+ constants = Object.constants.map { |x| x.to_sym }
+ @parameters.any? { |mod| constants.include? mod }
+ end
+end
+
+# In some cases, libraries will modify another Ruby method's
+# behavior. The specs for the method's behavior will then fail
+# if that library is loaded. This guard will not run if any of
+# the specified constants exist in Object.constants.
+def conflicts_with(*modules, &block)
+ ConflictsGuard.new(*modules).run_unless(:conflicts_with, &block)
+end
diff --git a/spec/mspec/lib/mspec/guards/endian.rb b/spec/mspec/lib/mspec/guards/endian.rb
new file mode 100644
index 0000000000..79335a8933
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/endian.rb
@@ -0,0 +1,25 @@
+require 'mspec/guards/guard'
+
+# Despite that these are inverses, the two classes are
+# used to simplify MSpec guard reporting modes
+
+class EndianGuard < SpecGuard
+ def pattern
+ @pattern ||= [1].pack('L')
+ end
+ private :pattern
+end
+
+class BigEndianGuard < EndianGuard
+ def match?
+ pattern[-1] == ?\001
+ end
+end
+
+def big_endian(&block)
+ BigEndianGuard.new.run_if(:big_endian, &block)
+end
+
+def little_endian(&block)
+ BigEndianGuard.new.run_unless(:little_endian, &block)
+end
diff --git a/spec/mspec/lib/mspec/guards/feature.rb b/spec/mspec/lib/mspec/guards/feature.rb
new file mode 100644
index 0000000000..d4c6dd1cde
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/feature.rb
@@ -0,0 +1,45 @@
+require 'mspec/guards/guard'
+
+class FeatureGuard < SpecGuard
+ def self.enabled?(*features)
+ new(*features).match?
+ end
+
+ def match?
+ @parameters.all? { |f| MSpec.feature_enabled? f }
+ end
+end
+
+# Provides better documentation in the specs by
+# naming sets of features that work together as
+# a whole. Examples include :encoding, :fiber,
+# :continuation, :fork.
+#
+# Usage example:
+#
+# with_feature :encoding do
+# # specs for a method that provides aspects
+# # of the encoding feature
+# end
+#
+# Multiple features must all be enabled for the
+# guard to run:
+#
+# with_feature :one, :two do
+# # these specs will run if features :one AND
+# # :two are enabled.
+# end
+#
+# The implementation must explicitly enable a feature
+# by adding code like the following to the .mspec
+# configuration file:
+#
+# MSpec.enable_feature :encoding
+#
+def with_feature(*features, &block)
+ FeatureGuard.new(*features).run_if(:with_feature, &block)
+end
+
+def without_feature(*features, &block)
+ FeatureGuard.new(*features).run_unless(:without_feature, &block)
+end
diff --git a/spec/mspec/lib/mspec/guards/guard.rb b/spec/mspec/lib/mspec/guards/guard.rb
new file mode 100644
index 0000000000..3a6372a660
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/guard.rb
@@ -0,0 +1,141 @@
+require 'mspec/runner/mspec'
+require 'mspec/runner/actions/tally'
+
+class SpecGuard
+ def self.report
+ @report ||= Hash.new { |h,k| h[k] = [] }
+ end
+
+ def self.clear
+ @report = nil
+ end
+
+ def self.finish
+ report.keys.sort.each do |key|
+ desc = report[key]
+ size = desc.size
+ spec = size == 1 ? "spec" : "specs"
+ print "\n\n#{size} #{spec} omitted by guard: #{key}:\n"
+ desc.each { |description| print "\n", description; }
+ end
+
+ print "\n\n"
+ end
+
+ def self.guards
+ @guards ||= []
+ end
+
+ def self.clear_guards
+ @guards = []
+ end
+
+ # Returns a partial Ruby version string based on +which+.
+ # For example, if RUBY_VERSION = 8.2.3:
+ #
+ # :major => "8"
+ # :minor => "8.2"
+ # :tiny => "8.2.3"
+ # :teeny => "8.2.3"
+ # :full => "8.2.3"
+ def self.ruby_version(which = :minor)
+ case which
+ when :major
+ n = 1
+ when :minor
+ n = 2
+ when :tiny, :teeny, :full
+ n = 3
+ end
+
+ RUBY_VERSION.split('.')[0,n].join('.')
+ end
+
+ attr_accessor :name
+
+ def initialize(*args)
+ @parameters = args
+ end
+
+ def yield?(invert = false)
+ return true if MSpec.mode? :unguarded
+
+ allow = match? ^ invert
+
+ if !allow and reporting?
+ MSpec.guard
+ MSpec.register :finish, SpecGuard
+ MSpec.register :add, self
+ return true
+ elsif MSpec.mode? :verify
+ return true
+ end
+
+ allow
+ end
+
+ def run_if(name, &block)
+ @name = name
+ if block
+ yield if yield?(false)
+ else
+ yield?(false)
+ end
+ ensure
+ unregister
+ end
+
+ def run_unless(name, &block)
+ @name = name
+ if block
+ yield if yield?(true)
+ else
+ yield?(true)
+ end
+ ensure
+ unregister
+ end
+
+ def reporting?
+ MSpec.mode?(:report) or
+ (MSpec.mode?(:report_on) and SpecGuard.guards.include?(name))
+ end
+
+ def report_key
+ "#{name} #{@parameters.join(", ")}"
+ end
+
+ def record(description)
+ SpecGuard.report[report_key] << description
+ end
+
+ def add(example)
+ record example.description
+ MSpec.formatter.tally.counter.guards!
+ end
+
+ def unregister
+ MSpec.unguard
+ MSpec.unregister :add, self
+ end
+
+ def match?
+ raise "must be implemented by the subclass"
+ end
+end
+
+# Combined guards
+
+def guard(condition, &block)
+ raise "condition must be a Proc" unless condition.is_a?(Proc)
+ raise LocalJumpError, "no block given" unless block
+ return yield if MSpec.mode? :unguarded or MSpec.mode? :verify or MSpec.mode? :report
+ yield if condition.call
+end
+
+def guard_not(condition, &block)
+ raise "condition must be a Proc" unless condition.is_a?(Proc)
+ raise LocalJumpError, "no block given" unless block
+ return yield if MSpec.mode? :unguarded or MSpec.mode? :verify or MSpec.mode? :report
+ yield unless condition.call
+end
diff --git a/spec/mspec/lib/mspec/guards/platform.rb b/spec/mspec/lib/mspec/guards/platform.rb
new file mode 100644
index 0000000000..e87b08a4c1
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/platform.rb
@@ -0,0 +1,104 @@
+require 'mspec/guards/guard'
+
+class PlatformGuard < SpecGuard
+ def self.implementation?(*args)
+ args.any? do |name|
+ case name
+ when :rubinius
+ RUBY_ENGINE.start_with?('rbx')
+ else
+ RUBY_ENGINE.start_with?(name.to_s)
+ end
+ end
+ end
+
+ def self.standard?
+ implementation? :ruby
+ end
+
+ PLATFORM = if RUBY_ENGINE == "jruby"
+ require 'rbconfig'
+ "#{RbConfig::CONFIG['host_cpu']}-#{RbConfig::CONFIG['host_os']}"
+ else
+ RUBY_PLATFORM
+ end
+
+ def self.os?(*oses)
+ oses.any? do |os|
+ raise ":java is not a valid OS" if os == :java
+ case os
+ when :windows
+ PLATFORM =~ /(mswin|mingw)/
+ when :wsl
+ wsl?
+ else
+ PLATFORM.include?(os.to_s)
+ end
+ end
+ end
+
+ def self.windows?
+ os?(:windows)
+ end
+
+ def self.wasi?
+ os?(:wasi)
+ end
+
+ def self.wsl?
+ if defined?(@wsl_p)
+ @wsl_p
+ else
+ @wsl_p = `uname -r`.match?(/microsoft/i)
+ end
+ end
+
+ WORD_SIZE = 1.size * 8
+
+ POINTER_SIZE = begin
+ require 'rbconfig/sizeof'
+ RbConfig::SIZEOF["void*"] * 8
+ rescue LoadError
+ WORD_SIZE
+ end
+
+ def self.wordsize?(size)
+ size == WORD_SIZE
+ end
+
+ def self.pointer_size?(size)
+ size == POINTER_SIZE
+ end
+
+ def initialize(*args)
+ if args.last.is_a?(Hash)
+ @options, @platforms = args.last, args[0..-2]
+ else
+ @options, @platforms = {}, args
+ end
+ @parameters = args
+ end
+
+ def match?
+ match = @platforms.empty? ? true : PlatformGuard.os?(*@platforms)
+ @options.each do |key, value|
+ case key
+ when :os
+ match &&= PlatformGuard.os?(*value)
+ when :wordsize
+ match &&= PlatformGuard.wordsize? value
+ when :pointer_size
+ match &&= PlatformGuard.pointer_size? value
+ end
+ end
+ match
+ end
+end
+
+def platform_is(*args, &block)
+ PlatformGuard.new(*args).run_if(:platform_is, &block)
+end
+
+def platform_is_not(*args, &block)
+ PlatformGuard.new(*args).run_unless(:platform_is_not, &block)
+end
diff --git a/spec/mspec/lib/mspec/guards/quarantine.rb b/spec/mspec/lib/mspec/guards/quarantine.rb
new file mode 100644
index 0000000000..ec4d01f9ea
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/quarantine.rb
@@ -0,0 +1,11 @@
+require 'mspec/guards/guard'
+
+class QuarantineGuard < SpecGuard
+ def match?
+ true
+ end
+end
+
+def quarantine!(&block)
+ QuarantineGuard.new.run_unless(:quarantine!, &block)
+end
diff --git a/spec/mspec/lib/mspec/guards/superuser.rb b/spec/mspec/lib/mspec/guards/superuser.rb
new file mode 100644
index 0000000000..24daf9b26c
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/superuser.rb
@@ -0,0 +1,25 @@
+require 'mspec/guards/guard'
+
+class SuperUserGuard < SpecGuard
+ def match?
+ Process.euid == 0
+ end
+end
+
+class RealSuperUserGuard < SpecGuard
+ def match?
+ Process.uid == 0
+ end
+end
+
+def as_superuser(&block)
+ SuperUserGuard.new.run_if(:as_superuser, &block)
+end
+
+def as_real_superuser(&block)
+ RealSuperUserGuard.new.run_if(:as_real_superuser, &block)
+end
+
+def as_user(&block)
+ SuperUserGuard.new.run_unless(:as_user, &block)
+end
diff --git a/spec/mspec/lib/mspec/guards/support.rb b/spec/mspec/lib/mspec/guards/support.rb
new file mode 100644
index 0000000000..790bea1077
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/support.rb
@@ -0,0 +1,14 @@
+require 'mspec/guards/platform'
+
+class SupportedGuard < SpecGuard
+ def match?
+ if @parameters.include? :ruby
+ raise Exception, "improper use of not_supported_on guard"
+ end
+ !PlatformGuard.standard? and PlatformGuard.implementation?(*@parameters)
+ end
+end
+
+def not_supported_on(*args, &block)
+ SupportedGuard.new(*args).run_unless(:not_supported_on, &block)
+end
diff --git a/spec/mspec/lib/mspec/guards/version.rb b/spec/mspec/lib/mspec/guards/version.rb
new file mode 100644
index 0000000000..f5ea1988ae
--- /dev/null
+++ b/spec/mspec/lib/mspec/guards/version.rb
@@ -0,0 +1,72 @@
+require 'mspec/utils/deprecate'
+require 'mspec/utils/version'
+require 'mspec/guards/guard'
+
+class VersionGuard < SpecGuard
+ FULL_RUBY_VERSION = SpecVersion.new SpecGuard.ruby_version(:full)
+
+ def initialize(version, requirement)
+ version = SpecVersion.new(version) unless SpecVersion === version
+ @version = version
+
+ case requirement
+ when String
+ @requirement = SpecVersion.new requirement
+ when Range
+ MSpec.deprecate "an empty version range end", 'a specific version' if requirement.end.empty?
+ a = SpecVersion.new requirement.begin
+ b = SpecVersion.new requirement.end
+ unless requirement.exclude_end?
+ MSpec.deprecate "ruby_version_is with an inclusive range", 'an exclusive range ("2.1"..."2.3")'
+ end
+ @requirement = requirement.exclude_end? ? a...b : a..b
+ else
+ raise "version must be a String or Range but was a #{requirement.class}"
+ end
+ super(@version, @requirement)
+ end
+
+ def match?
+ if Range === @requirement
+ @requirement.include? @version
+ else
+ @version >= @requirement
+ end
+ end
+
+ @kernel_version = nil
+ def self.kernel_version
+ if @kernel_version
+ @kernel_version
+ else
+ if v = RUBY_PLATFORM[/darwin(\d+)/, 1] # build time version
+ uname = v
+ else
+ begin
+ require 'etc'
+ etc = true
+ rescue LoadError
+ etc = false
+ end
+ if etc and Etc.respond_to?(:uname)
+ uname = Etc.uname.fetch(:release)
+ else
+ uname = `uname -r`.chomp
+ end
+ end
+ @kernel_version = uname
+ end
+ end
+end
+
+def version_is(base_version, requirement, &block)
+ VersionGuard.new(base_version, requirement).run_if(:version_is, &block)
+end
+
+def ruby_version_is(requirement, &block)
+ VersionGuard.new(VersionGuard::FULL_RUBY_VERSION, requirement).run_if(:ruby_version_is, &block)
+end
+
+def kernel_version_is(requirement, &block)
+ VersionGuard.new(VersionGuard.kernel_version, requirement).run_if(:kernel_version_is, &block)
+end
diff --git a/spec/mspec/lib/mspec/helpers.rb b/spec/mspec/lib/mspec/helpers.rb
new file mode 100644
index 0000000000..90f9fd3fd4
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers.rb
@@ -0,0 +1,13 @@
+require 'mspec/helpers/argf'
+require 'mspec/helpers/argv'
+require 'mspec/helpers/datetime'
+require 'mspec/helpers/fixture'
+require 'mspec/helpers/flunk'
+require 'mspec/helpers/fs'
+require 'mspec/helpers/io'
+require 'mspec/helpers/mock_to_path'
+require 'mspec/helpers/numeric'
+require 'mspec/helpers/ruby_exe'
+require 'mspec/helpers/scratch'
+require 'mspec/helpers/tmp'
+require 'mspec/helpers/warning'
diff --git a/spec/mspec/lib/mspec/helpers/argf.rb b/spec/mspec/lib/mspec/helpers/argf.rb
new file mode 100644
index 0000000000..4d3e0f46b3
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/argf.rb
@@ -0,0 +1,35 @@
+# Convenience helper for specs using ARGF.
+# Set @argf to an instance of ARGF.class with the given +argv+.
+# That instance must be used instead of ARGF as ARGF is global
+# and it is not always possible to reset its state correctly.
+#
+# The helper yields to the block and then close
+# the files open by the instance. Example:
+#
+# describe "That" do
+# it "does something" do
+# argf ['a', 'b'] do
+# # do something
+# end
+# end
+# end
+def argf(argv)
+ if argv.empty? or argv.length > 2
+ raise "Only 1 or 2 filenames are allowed for the argf helper so files can be properly closed: #{argv.inspect}"
+ end
+ @argf ||= nil
+ raise "Cannot nest calls to the argf helper" if @argf
+
+ @argf = ARGF.class.new(*argv)
+ @__mspec_saved_argf_file__ = @argf.file
+ begin
+ yield
+ ensure
+ file1 = @__mspec_saved_argf_file__
+ file2 = @argf.file # Either the first file or the second
+ file1.close if !file1.closed? and file1 != STDIN
+ file2.close if !file2.closed? and file2 != STDIN
+ @argf = nil
+ @__mspec_saved_argf_file__ = nil
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/argv.rb b/spec/mspec/lib/mspec/helpers/argv.rb
new file mode 100644
index 0000000000..9dac384dbd
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/argv.rb
@@ -0,0 +1,44 @@
+# Convenience helper for altering ARGV. Saves the
+# value of ARGV and sets it to +args+. If a block
+# is given, yields to the block and then restores
+# the value of ARGV. The previously saved value of
+# ARGV can be restored by passing +:restore+. The
+# former is useful in a single spec. The latter is
+# useful in before/after actions. For example:
+#
+# describe "This" do
+# before do
+# argv ['a', 'b']
+# end
+#
+# after do
+# argv :restore
+# end
+#
+# it "does something" do
+# # do something
+# end
+# end
+#
+# describe "That" do
+# it "does something" do
+# argv ['a', 'b'] do
+# # do something
+# end
+# end
+# end
+def argv(args)
+ if args == :restore
+ ARGV.replace(@__mspec_saved_argv__ || [])
+ else
+ @__mspec_saved_argv__ = ARGV.dup
+ ARGV.replace args
+ if block_given?
+ begin
+ yield
+ ensure
+ argv :restore
+ end
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/datetime.rb b/spec/mspec/lib/mspec/helpers/datetime.rb
new file mode 100644
index 0000000000..84ac86b686
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/datetime.rb
@@ -0,0 +1,48 @@
+# The new_datetime helper makes writing DateTime specs more simple by
+# providing default constructor values and accepting a Hash of only the
+# constructor values needed for the particular spec. For example:
+#
+# new_datetime :hour => 1, :minute => 20
+#
+# Possible keys are:
+# :year, :month, :day, :hour, :minute, :second, :offset and :sg.
+def new_datetime(opts = {})
+ require 'date'
+
+ value = {
+ :year => -4712,
+ :month => 1,
+ :day => 1,
+ :hour => 0,
+ :minute => 0,
+ :second => 0,
+ :offset => 0,
+ :sg => Date::ITALY
+ }.merge opts
+
+ DateTime.new value[:year], value[:month], value[:day], value[:hour],
+ value[:minute], value[:second], value[:offset], value[:sg]
+end
+
+def with_timezone(name, offset = nil, daylight_saving_zone = "")
+ skip "WASI doesn't have TZ concept" if PlatformGuard.wasi?
+ zone = name.dup
+
+ if offset
+ # TZ convention is backwards
+ offset = -offset
+
+ zone += offset.to_s
+ zone += ":00:00"
+ end
+ zone += daylight_saving_zone
+
+ old = ENV["TZ"]
+ ENV["TZ"] = zone
+
+ begin
+ yield
+ ensure
+ ENV["TZ"] = old
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/fixture.rb b/spec/mspec/lib/mspec/helpers/fixture.rb
new file mode 100644
index 0000000000..f3bbe423bd
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/fixture.rb
@@ -0,0 +1,24 @@
+# Returns the name of a fixture file by adjoining the directory
+# of the +file+ argument with "fixtures" and the contents of the
+# +args+ array. For example,
+#
+# +file+ == "some/example_spec.rb"
+#
+# and
+#
+# +args+ == ["subdir", "file.txt"]
+#
+# then the result is the expanded path of
+#
+# "some/fixtures/subdir/file.txt".
+def fixture(file, *args)
+ path = File.dirname(file)
+ path = path[0..-7] if path[-7..-1] == "/shared"
+ fixtures = path[-9..-1] == "/fixtures" ? "" : "fixtures"
+ if File.respond_to?(:realpath)
+ path = File.realpath(path)
+ else
+ path = File.expand_path(path)
+ end
+ File.join(path, fixtures, args)
+end
diff --git a/spec/mspec/lib/mspec/helpers/flunk.rb b/spec/mspec/lib/mspec/helpers/flunk.rb
new file mode 100644
index 0000000000..84fb3ab39c
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/flunk.rb
@@ -0,0 +1,3 @@
+def flunk(msg = "This example is a failure")
+ SpecExpectation.fail_with "Failed:", msg
+end
diff --git a/spec/mspec/lib/mspec/helpers/fs.rb b/spec/mspec/lib/mspec/helpers/fs.rb
new file mode 100644
index 0000000000..67453eb302
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/fs.rb
@@ -0,0 +1,64 @@
+# Copies a file
+def cp(source, dest)
+ IO.copy_stream source, dest
+end
+
+# Creates each directory in path that does not exist.
+def mkdir_p(path)
+ parts = File.expand_path(path).split %r[/|\\]
+ name = parts.shift
+ parts.each do |part|
+ name = File.join name, part
+
+ if File.file? name
+ raise ArgumentError, "path component of #{path} is a file"
+ end
+
+ unless File.directory? name
+ begin
+ Dir.mkdir name
+ rescue Errno::EEXIST => e
+ if File.directory? name
+ # OK, another process/thread created the same directory
+ else
+ raise e
+ end
+ end
+ end
+ end
+end
+
+# Recursively removes all files and directories in +path+
+# if +path+ is a directory. Removes the file if +path+ is
+# a file.
+def rm_r(*paths)
+ paths.each do |path|
+ path = File.expand_path path
+
+ prefix = SPEC_TEMP_DIR
+ unless path[0, prefix.size] == prefix
+ raise ArgumentError, "#{path} is not prefixed by #{prefix}"
+ end
+
+ # File.symlink? needs to be checked first as
+ # File.exist? returns false for dangling symlinks
+ if File.symlink? path
+ File.unlink path
+ elsif File.directory? path
+ Dir.entries(path).each { |x| rm_r "#{path}/#{x}" unless x =~ /^\.\.?$/ }
+ Dir.rmdir path
+ elsif File.exist? path
+ File.delete path
+ end
+ end
+end
+
+# Creates a file +name+. Creates the directory for +name+
+# if it does not exist.
+def touch(name, mode = "w")
+ mkdir_p File.dirname(name)
+
+ File.open(name, mode) do |f|
+ yield f if block_given?
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/io.rb b/spec/mspec/lib/mspec/helpers/io.rb
new file mode 100644
index 0000000000..29c6c37a1a
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/io.rb
@@ -0,0 +1,87 @@
+require 'mspec/guards/feature'
+
+class IOStub
+ def initialize
+ @buffer = []
+ @output = ''
+ end
+
+ def write(*str)
+ self << str.join
+ end
+
+ def << str
+ @buffer << str
+ self
+ end
+
+ def print(*str)
+ write(str.join + $\.to_s)
+ end
+
+ def method_missing(name, *args, &block)
+ to_s.send(name, *args, &block)
+ end
+
+ def == other
+ to_s == other
+ end
+
+ def =~ other
+ to_s =~ other
+ end
+
+ def puts(*str)
+ if str.empty?
+ write "\n"
+ else
+ write(str.collect { |s| s.to_s.chomp }.concat([nil]).join("\n"))
+ end
+ end
+
+ def printf(format, *args)
+ self << sprintf(format, *args)
+ end
+
+ def flush
+ @output += @buffer.join('')
+ @buffer.clear
+ self
+ end
+
+ def to_s
+ flush
+ @output
+ end
+
+ alias_method :to_str, :to_s
+
+ def inspect
+ to_s.inspect
+ end
+end
+
+# Creates a "bare" file descriptor (i.e. one that is not associated
+# with any Ruby object). The file descriptor can safely be passed
+# to IO.new without creating a Ruby object alias to the fd.
+def new_fd(name, mode = "w:utf-8")
+ if mode.kind_of? Hash
+ if mode.key? :mode
+ mode = mode[:mode]
+ else
+ raise ArgumentError, "new_fd options Hash must include :mode"
+ end
+ end
+
+ IO.sysopen name, mode
+end
+
+# Creates an IO instance for a temporary file name. The file
+# must be deleted.
+def new_io(name, mode = "w:utf-8")
+ if Hash === mode # Avoid kwargs warnings on Ruby 2.7+
+ File.new(name, **mode)
+ else
+ File.new(name, mode)
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/mock_to_path.rb b/spec/mspec/lib/mspec/helpers/mock_to_path.rb
new file mode 100644
index 0000000000..2780afc54a
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/mock_to_path.rb
@@ -0,0 +1,6 @@
+def mock_to_path(path)
+ # Cannot use our Object#mock here since it conflicts with RSpec
+ obj = MockObject.new('path')
+ obj.should_receive(:to_path).and_return(path)
+ obj
+end
diff --git a/spec/mspec/lib/mspec/helpers/numeric.rb b/spec/mspec/lib/mspec/helpers/numeric.rb
new file mode 100644
index 0000000000..c1ed81a233
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/numeric.rb
@@ -0,0 +1,80 @@
+require 'mspec/guards/platform'
+
+def nan_value
+ 0/0.0
+end
+
+def infinity_value
+ 1/0.0
+end
+
+def bignum_value(plus = 0)
+ # Must be >= fixnum_max + 2, so -bignum_value is < fixnum_min
+ # A fixed value has the advantage to be the same numeric value for all Rubies and is much easier to spec
+ (2**64) + plus
+end
+
+def max_long
+ 2**(0.size * 8 - 1) - 1
+end
+
+def min_long
+ -(2**(0.size * 8 - 1))
+end
+
+# This is a bit hairy, but we need to be able to write specs that cover the
+# boundary between Fixnum and Bignum for operations like Fixnum#<<. Since
+# this boundary is implementation-dependent, we use these helpers to write
+# specs based on the relationship between values rather than specific
+# values.
+if PlatformGuard.standard? or PlatformGuard.implementation? :topaz
+ if PlatformGuard.wordsize? 32
+ def fixnum_max
+ (2**30) - 1
+ end
+
+ def fixnum_min
+ -(2**30)
+ end
+ elsif PlatformGuard.wordsize? 64
+ def fixnum_max
+ (2**62) - 1
+ end
+
+ def fixnum_min
+ -(2**62)
+ end
+ end
+elsif PlatformGuard.implementation? :opal
+ def fixnum_max
+ Integer::MAX
+ end
+
+ def fixnum_min
+ Integer::MIN
+ end
+elsif PlatformGuard.implementation? :rubinius
+ def fixnum_max
+ Fixnum::MAX
+ end
+
+ def fixnum_min
+ Fixnum::MIN
+ end
+elsif PlatformGuard.implementation?(:jruby) || PlatformGuard.implementation?(:truffleruby)
+ def fixnum_max
+ 9223372036854775807
+ end
+
+ def fixnum_min
+ -9223372036854775808
+ end
+else
+ def fixnum_max
+ raise "unknown implementation for fixnum_max() helper"
+ end
+
+ def fixnum_min
+ raise "unknown implementation for fixnum_min() helper"
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/ruby_exe.rb b/spec/mspec/lib/mspec/helpers/ruby_exe.rb
new file mode 100644
index 0000000000..7fde001cda
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/ruby_exe.rb
@@ -0,0 +1,189 @@
+require 'mspec/guards/platform'
+require 'mspec/helpers/tmp'
+
+# The ruby_exe helper provides a wrapper for invoking the
+# same Ruby interpreter with the same flags as the one running
+# the specs and getting the output from running the code.
+#
+# If +code+ is a file that exists, it will be run.
+# Otherwise, +code+ will be written to a temporary file and be run.
+# For example:
+#
+# ruby_exe('path/to/some/file.rb')
+#
+# will be executed as
+#
+# `#{RUBY_EXE} 'path/to/some/file.rb'`
+#
+# The ruby_exe helper also accepts an options hash with four
+# keys: :options, :args :env and :exception.
+#
+# For example:
+#
+# ruby_exe('file.rb', :options => "-w",
+# :args => "arg1 arg2",
+# :env => { :FOO => "bar" })
+#
+# will be executed as
+#
+# `#{RUBY_EXE} -w file.rb arg1 arg2`
+#
+# with access to ENV["FOO"] with value "bar".
+#
+# When `exception: false` and Ruby command fails then exception will not be
+# raised.
+#
+# If +nil+ is passed for the first argument, the command line
+# will be built only from the options hash.
+#
+# If no arguments are passed to ruby_exe, it returns an Array
+# containing the interpreter executable and the flags:
+#
+# spawn(*ruby_exe, "-e", "puts :hello")
+#
+# This avoids spawning an extra shell, and ensure the pid returned by spawn
+# corresponds to the ruby process and not the shell.
+#
+# The RUBY_EXE constant is setup by mspec automatically
+# and is used by ruby_exe and ruby_cmd. The mspec runner script
+# will set ENV['RUBY_EXE'] to the name of the executable used
+# to invoke the mspec-run script.
+#
+# The value will only be used if the file exists and is executable.
+# The flags will then be appended to the resulting value, such that
+# the RUBY_EXE constant contains both the executable and the flags.
+#
+# Additionally, the flags passed to mspec
+# (with -T on the command line or in the config with set :flags)
+# will be appended to RUBY_EXE so that the interpreter
+# is always called with those flags.
+#
+# Failure of a Ruby command leads to raising exception by default.
+
+def ruby_exe_options(option)
+ case option
+ when :env
+ ENV['RUBY_EXE']
+ when :engine
+ case RUBY_ENGINE
+ when 'rbx'
+ "bin/rbx"
+ when 'jruby'
+ "bin/jruby"
+ when 'maglev'
+ "maglev-ruby"
+ when 'topaz'
+ "topaz"
+ when 'ironruby'
+ "ir"
+ end
+ when :name
+ require 'rbconfig'
+ bin = RUBY_ENGINE + (RbConfig::CONFIG['EXEEXT'] || '')
+ File.join(".", bin)
+ when :install_name
+ require 'rbconfig'
+ bin = RbConfig::CONFIG["RUBY_INSTALL_NAME"] || RbConfig::CONFIG["ruby_install_name"]
+ bin << (RbConfig::CONFIG['EXEEXT'] || '')
+ File.join(RbConfig::CONFIG['bindir'], bin)
+ end
+end
+
+def resolve_ruby_exe
+ [:env, :engine, :name, :install_name].each do |option|
+ next unless exe = ruby_exe_options(option)
+
+ if File.file?(exe) and File.executable?(exe)
+ exe = File.expand_path(exe)
+ exe = exe.tr('/', '\\') if PlatformGuard.windows?
+ flags = ENV['RUBY_FLAGS']
+ if flags and !flags.empty?
+ return exe + ' ' + flags
+ else
+ return exe
+ end
+ end
+ end
+ raise Exception, "Unable to find a suitable ruby executable."
+end
+
+unless Object.const_defined?(:RUBY_EXE) and RUBY_EXE
+ RUBY_EXE = resolve_ruby_exe
+end
+
+def ruby_exe(code = :not_given, opts = {})
+ skip "WASI doesn't provide subprocess" if PlatformGuard.wasi?
+
+ if opts[:dir]
+ raise "ruby_exe(..., dir: dir) is no longer supported, use Dir.chdir"
+ end
+
+ if code == :not_given
+ return RUBY_EXE.split(' ')
+ end
+
+ env = opts[:env] || {}
+ saved_env = {}
+ env.each do |key, value|
+ key = key.to_s
+ saved_env[key] = ENV[key] if ENV.key? key
+ ENV[key] = value
+ end
+
+ escape = opts.delete(:escape)
+ if code and !File.exist?(code) and escape != false
+ tmpfile = tmp("rubyexe.rb")
+ File.open(tmpfile, "w") { |f| f.write(code) }
+ code = tmpfile
+ end
+
+ expected_status = opts.fetch(:exit_status, 0)
+
+ begin
+ platform_is_not :opal do
+ command = ruby_cmd(code, opts)
+ output = `#{command}`
+ status = Process.last_status
+
+ exit_status = if status.exited?
+ status.exitstatus
+ elsif status.signaled?
+ signame = Signal.signame status.termsig
+ raise "No signal name?" unless signame
+ :"SIG#{signame}"
+ else
+ raise SpecExpectationNotMetError, "#{exit_status.inspect} is neither exited? nor signaled?"
+ end
+ if exit_status != expected_status
+ formatted_output = output.lines.map { |line| " #{line}" }.join
+ raise SpecExpectationNotMetError,
+ "Expected exit status is #{expected_status.inspect} but actual is #{exit_status.inspect} for command ruby_exe(#{command.inspect})\nOutput:\n#{formatted_output}"
+ end
+
+ output
+ end
+ ensure
+ saved_env.each { |key, value| ENV[key] = value }
+ env.keys.each do |key|
+ key = key.to_s
+ ENV.delete key unless saved_env.key? key
+ end
+ File.delete tmpfile if tmpfile
+ end
+end
+
+def ruby_cmd(code, opts = {})
+ body = code
+
+ if opts[:escape]
+ raise "escape: true is no longer supported in ruby_cmd, use ruby_exe or a fixture"
+ end
+
+ if code and !File.exist?(code)
+ body = "-e #{code.inspect}"
+ end
+
+ command = [RUBY_EXE, opts[:options], body, opts[:args]].compact.join(' ')
+ STDERR.puts "\nruby_cmd: #{command}" if ENV["DEBUG_MSPEC_RUBY_CMD"] == "true"
+ command
+end
diff --git a/spec/mspec/lib/mspec/helpers/scratch.rb b/spec/mspec/lib/mspec/helpers/scratch.rb
new file mode 100644
index 0000000000..0da3315cd8
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/scratch.rb
@@ -0,0 +1,21 @@
+module ScratchPad
+ def self.clear
+ @record = nil
+ end
+
+ def self.record(arg)
+ @record = arg
+ end
+
+ def self.<<(arg)
+ @record << arg
+ end
+
+ def self.recorded
+ @record
+ end
+
+ def self.inspect
+ "<ScratchPad @record=#{@record.inspect}>"
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/tmp.rb b/spec/mspec/lib/mspec/helpers/tmp.rb
new file mode 100644
index 0000000000..b2a38ee983
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/tmp.rb
@@ -0,0 +1,48 @@
+# Creates a temporary directory in the current working directory
+# for temporary files created while running the specs. All specs
+# should clean up any temporary files created so that the temp
+# directory is empty when the process exits.
+
+SPEC_TEMP_DIR_PID = Process.pid
+
+if spec_temp_dir = ENV["SPEC_TEMP_DIR"]
+ spec_temp_dir = File.realdirpath(spec_temp_dir)
+else
+ spec_temp_dir = "#{File.realpath(Dir.pwd)}/rubyspec_temp/#{SPEC_TEMP_DIR_PID}"
+end
+SPEC_TEMP_DIR = spec_temp_dir
+
+SPEC_TEMP_UNIQUIFIER = "0"
+
+at_exit do
+ begin
+ if SPEC_TEMP_DIR_PID == Process.pid
+ Dir.delete SPEC_TEMP_DIR if File.directory? SPEC_TEMP_DIR
+ end
+ rescue SystemCallError
+ STDERR.puts <<-EOM
+
+-----------------------------------------------------
+The rubyspec temp directory is not empty. Ensure that
+all specs are cleaning up temporary files:
+ #{SPEC_TEMP_DIR}
+-----------------------------------------------------
+
+ EOM
+ rescue Object => e
+ STDERR.puts "failed to remove spec temp directory"
+ STDERR.puts e.message
+ end
+end
+
+def tmp(name, uniquify = true)
+ mkdir_p SPEC_TEMP_DIR unless Dir.exist? SPEC_TEMP_DIR
+
+ if uniquify and !name.empty?
+ slash = name.rindex "/"
+ index = slash ? slash + 1 : 0
+ name.insert index, "#{SPEC_TEMP_UNIQUIFIER.succ!}-"
+ end
+
+ File.join SPEC_TEMP_DIR, name
+end
diff --git a/spec/mspec/lib/mspec/helpers/warning.rb b/spec/mspec/lib/mspec/helpers/warning.rb
new file mode 100644
index 0000000000..e3d72b78bd
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/warning.rb
@@ -0,0 +1,21 @@
+require 'mspec/guards/version'
+
+# You might be looking for #silence_warnings, use #suppress_warning instead.
+# MSpec calls it #suppress_warning for consistency with EnvUtil.suppress_warning in CRuby test/.
+def suppress_warning
+ verbose = $VERBOSE
+ $VERBOSE = nil
+ yield
+ensure
+ $VERBOSE = verbose
+end
+
+if ruby_version_is("2.7")
+ def suppress_keyword_warning(&block)
+ suppress_warning(&block)
+ end
+else
+ def suppress_keyword_warning
+ yield
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers.rb b/spec/mspec/lib/mspec/matchers.rb
new file mode 100644
index 0000000000..356e4a9f32
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers.rb
@@ -0,0 +1,37 @@
+require 'mspec/matchers/base'
+require 'mspec/matchers/be_an_instance_of'
+require 'mspec/matchers/be_ancestor_of'
+require 'mspec/matchers/be_close'
+require 'mspec/matchers/be_computed_by'
+require 'mspec/matchers/be_empty'
+require 'mspec/matchers/be_false'
+require 'mspec/matchers/be_kind_of'
+require 'mspec/matchers/be_nan'
+require 'mspec/matchers/be_nil'
+require 'mspec/matchers/be_true'
+require 'mspec/matchers/be_true_or_false'
+require 'mspec/matchers/complain'
+require 'mspec/matchers/eql'
+require 'mspec/matchers/equal'
+require 'mspec/matchers/equal_element'
+require 'mspec/matchers/have_constant'
+require 'mspec/matchers/have_class_variable'
+require 'mspec/matchers/have_instance_method'
+require 'mspec/matchers/have_instance_variable'
+require 'mspec/matchers/have_method'
+require 'mspec/matchers/have_private_instance_method'
+require 'mspec/matchers/have_private_method'
+require 'mspec/matchers/have_protected_instance_method'
+require 'mspec/matchers/have_public_instance_method'
+require 'mspec/matchers/have_singleton_method'
+require 'mspec/matchers/include'
+require 'mspec/matchers/include_any_of'
+require 'mspec/matchers/infinity'
+require 'mspec/matchers/match_yaml'
+require 'mspec/matchers/raise_error'
+require 'mspec/matchers/output'
+require 'mspec/matchers/output_to_fd'
+require 'mspec/matchers/respond_to'
+require 'mspec/matchers/signed_zero'
+require 'mspec/matchers/block_caller'
+require 'mspec/matchers/skip'
diff --git a/spec/mspec/lib/mspec/matchers/base.rb b/spec/mspec/lib/mspec/matchers/base.rb
new file mode 100644
index 0000000000..d9d7f6fec0
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/base.rb
@@ -0,0 +1,79 @@
+module MSpecMatchers
+end
+
+class MSpecEnv
+ include MSpecMatchers
+end
+
+# Expectations are sometimes used in a module body
+class Module
+ include MSpecMatchers
+end
+
+class SpecPositiveOperatorMatcher < BasicObject
+ def initialize(actual)
+ @actual = actual
+ end
+
+ def ==(expected)
+ result = @actual == expected
+ unless result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :==, expected, result, "to be truthy")
+ end
+ end
+
+ def !=(expected)
+ result = @actual != expected
+ unless result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :!=, expected, result, "to be truthy")
+ end
+ end
+
+ def equal?(expected)
+ result = @actual.equal?(expected)
+ unless result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :equal?, expected, result, "to be truthy")
+ end
+ end
+
+ def method_missing(name, *args, &block)
+ result = @actual.__send__(name, *args, &block)
+ unless result
+ ::SpecExpectation.fail_predicate(@actual, name, args, block, result, "to be truthy")
+ end
+ end
+end
+
+class SpecNegativeOperatorMatcher < BasicObject
+ def initialize(actual)
+ @actual = actual
+ end
+
+ def ==(expected)
+ result = @actual == expected
+ if result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :==, expected, result, "to be falsy")
+ end
+ end
+
+ def !=(expected)
+ result = @actual != expected
+ if result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :!=, expected, result, "to be falsy")
+ end
+ end
+
+ def equal?(expected)
+ result = @actual.equal?(expected)
+ if result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :equal?, expected, result, "to be falsy")
+ end
+ end
+
+ def method_missing(name, *args, &block)
+ result = @actual.__send__(name, *args, &block)
+ if result
+ ::SpecExpectation.fail_predicate(@actual, name, args, block, result, "to be falsy")
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_an_instance_of.rb b/spec/mspec/lib/mspec/matchers/be_an_instance_of.rb
new file mode 100644
index 0000000000..fdf3736ac2
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_an_instance_of.rb
@@ -0,0 +1,26 @@
+class BeAnInstanceOfMatcher
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @actual.instance_of?(@expected)
+ end
+
+ def failure_message
+ ["Expected #{@actual.inspect} (#{@actual.class})",
+ "to be an instance of #{@expected}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual.inspect} (#{@actual.class})",
+ "not to be an instance of #{@expected}"]
+ end
+end
+
+module MSpecMatchers
+ private def be_an_instance_of(expected)
+ BeAnInstanceOfMatcher.new(expected)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_ancestor_of.rb b/spec/mspec/lib/mspec/matchers/be_ancestor_of.rb
new file mode 100644
index 0000000000..05f72099e4
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_ancestor_of.rb
@@ -0,0 +1,24 @@
+class BeAncestorOfMatcher
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @expected.ancestors.include? @actual
+ end
+
+ def failure_message
+ ["Expected #{@actual}", "to be an ancestor of #{@expected}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual}", "not to be an ancestor of #{@expected}"]
+ end
+end
+
+module MSpecMatchers
+ private def be_ancestor_of(expected)
+ BeAncestorOfMatcher.new(expected)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_close.rb b/spec/mspec/lib/mspec/matchers/be_close.rb
new file mode 100644
index 0000000000..d6a6626f31
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_close.rb
@@ -0,0 +1,29 @@
+TOLERANCE = 0.00003 unless Object.const_defined?(:TOLERANCE)
+# To account for GC, context switches, other processes, load, etc.
+TIME_TOLERANCE = 20.0 unless Object.const_defined?(:TIME_TOLERANCE)
+
+class BeCloseMatcher
+ def initialize(expected, tolerance)
+ @expected = expected
+ @tolerance = tolerance
+ end
+
+ def matches?(actual)
+ @actual = actual
+ (@actual - @expected).abs <= @tolerance
+ end
+
+ def failure_message
+ ["Expected #{@actual}", "to be within #{@expected} +/- #{@tolerance}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual}", "not to be within #{@expected} +/- #{@tolerance}"]
+ end
+end
+
+module MSpecMatchers
+ private def be_close(expected, tolerance)
+ BeCloseMatcher.new(expected, tolerance)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_computed_by.rb b/spec/mspec/lib/mspec/matchers/be_computed_by.rb
new file mode 100644
index 0000000000..2e31bc93af
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_computed_by.rb
@@ -0,0 +1,37 @@
+class BeComputedByMatcher
+ def initialize(sym, *args)
+ @method = sym
+ @args = args
+ end
+
+ def matches?(array)
+ array.each do |line|
+ @receiver = line.shift
+ @value = line.pop
+ @arguments = line
+ @arguments += @args
+ @actual = @receiver.send(@method, *@arguments)
+ return false unless @actual == @value
+ end
+
+ return true
+ end
+
+ def method_call
+ method_call = "#{@receiver.inspect}.#{@method}"
+ unless @arguments.empty?
+ method_call = "#{method_call} from #{@arguments.map { |x| x.inspect }.join(", ")}"
+ end
+ method_call
+ end
+
+ def failure_message
+ ["Expected #{@value.inspect}", "to be computed by #{method_call} (computed #{@actual.inspect} instead)"]
+ end
+end
+
+module MSpecMatchers
+ private def be_computed_by(sym, *args)
+ BeComputedByMatcher.new(sym, *args)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_empty.rb b/spec/mspec/lib/mspec/matchers/be_empty.rb
new file mode 100644
index 0000000000..5abd5c9485
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_empty.rb
@@ -0,0 +1,20 @@
+class BeEmptyMatcher
+ def matches?(actual)
+ @actual = actual
+ @actual.empty?
+ end
+
+ def failure_message
+ ["Expected #{@actual.inspect}", "to be empty"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual.inspect}", "not to be empty"]
+ end
+end
+
+module MSpecMatchers
+ private def be_empty
+ BeEmptyMatcher.new
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_false.rb b/spec/mspec/lib/mspec/matchers/be_false.rb
new file mode 100644
index 0000000000..9e9a2608e1
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_false.rb
@@ -0,0 +1,20 @@
+class BeFalseMatcher
+ def matches?(actual)
+ @actual = actual
+ @actual == false
+ end
+
+ def failure_message
+ ["Expected #{@actual.inspect}", "to be false"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual.inspect}", "not to be false"]
+ end
+end
+
+module MSpecMatchers
+ private def be_false
+ BeFalseMatcher.new
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_kind_of.rb b/spec/mspec/lib/mspec/matchers/be_kind_of.rb
new file mode 100644
index 0000000000..a69906f210
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_kind_of.rb
@@ -0,0 +1,24 @@
+class BeKindOfMatcher
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @actual.is_a?(@expected)
+ end
+
+ def failure_message
+ ["Expected #{@actual.inspect} (#{@actual.class})", "to be kind of #{@expected}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual.inspect} (#{@actual.class})", "not to be kind of #{@expected}"]
+ end
+end
+
+module MSpecMatchers
+ private def be_kind_of(expected)
+ BeKindOfMatcher.new(expected)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_nan.rb b/spec/mspec/lib/mspec/matchers/be_nan.rb
new file mode 100644
index 0000000000..b279d8f1cf
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_nan.rb
@@ -0,0 +1,20 @@
+class BeNaNMatcher
+ def matches?(actual)
+ @actual = actual
+ @actual.kind_of?(Float) && @actual.nan?
+ end
+
+ def failure_message
+ ["Expected #{@actual}", "to be NaN"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual}", "not to be NaN"]
+ end
+end
+
+module MSpecMatchers
+ private def be_nan
+ BeNaNMatcher.new
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_nil.rb b/spec/mspec/lib/mspec/matchers/be_nil.rb
new file mode 100644
index 0000000000..049b1e3a53
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_nil.rb
@@ -0,0 +1,20 @@
+class BeNilMatcher
+ def matches?(actual)
+ @actual = actual
+ @actual.nil?
+ end
+
+ def failure_message
+ ["Expected #{@actual.inspect}", "to be nil"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual.inspect}", "not to be nil"]
+ end
+end
+
+module MSpecMatchers
+ private def be_nil
+ BeNilMatcher.new
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_true.rb b/spec/mspec/lib/mspec/matchers/be_true.rb
new file mode 100644
index 0000000000..52f5013752
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_true.rb
@@ -0,0 +1,20 @@
+class BeTrueMatcher
+ def matches?(actual)
+ @actual = actual
+ @actual == true
+ end
+
+ def failure_message
+ ["Expected #{@actual.inspect}", "to be true"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual.inspect}", "not to be true"]
+ end
+end
+
+module MSpecMatchers
+ private def be_true
+ BeTrueMatcher.new
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/be_true_or_false.rb b/spec/mspec/lib/mspec/matchers/be_true_or_false.rb
new file mode 100644
index 0000000000..4294b08d1b
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/be_true_or_false.rb
@@ -0,0 +1,20 @@
+class BeTrueOrFalseMatcher
+ def matches?(actual)
+ @actual = actual
+ @actual == true || @actual == false
+ end
+
+ def failure_message
+ ["Expected #{@actual.inspect}", "to be true or false"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual.inspect}", "not to be true or false"]
+ end
+end
+
+module MSpecMatchers
+ private def be_true_or_false
+ BeTrueOrFalseMatcher.new
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/block_caller.rb b/spec/mspec/lib/mspec/matchers/block_caller.rb
new file mode 100644
index 0000000000..30fab4fc68
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/block_caller.rb
@@ -0,0 +1,37 @@
+class BlockingMatcher
+ def matches?(block)
+ t = Thread.new do
+ block.call
+ end
+
+ loop do
+ case t.status
+ when "sleep" # blocked
+ t.kill
+ t.join
+ return true
+ when false # terminated normally, so never blocked
+ t.join
+ return false
+ when nil # terminated exceptionally
+ t.value
+ else
+ Thread.pass
+ end
+ end
+ end
+
+ def failure_message
+ ['Expected the given Proc', 'to block the caller']
+ end
+
+ def negative_failure_message
+ ['Expected the given Proc', 'to not block the caller']
+ end
+end
+
+module MSpecMatchers
+ private def block_caller
+ BlockingMatcher.new
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/complain.rb b/spec/mspec/lib/mspec/matchers/complain.rb
new file mode 100644
index 0000000000..887e72b4b0
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/complain.rb
@@ -0,0 +1,71 @@
+require 'mspec/helpers/io'
+
+class ComplainMatcher
+ def initialize(complaint = nil, options = nil)
+ # the proper solution is to use double splat operator e.g.
+ # def initialize(complaint = nil, **options)
+ # but we are trying to minimize language features required to run MSpec
+ if complaint.is_a?(Hash)
+ @complaint = nil
+ @options = complaint
+ else
+ @complaint = complaint
+ @options = options || {}
+ end
+ end
+
+ def matches?(proc)
+ @saved_err = $stderr
+ @verbose = $VERBOSE
+ err = IOStub.new
+
+ Thread.current[:in_mspec_complain_matcher] = true
+ $stderr = err
+ $VERBOSE = @options.key?(:verbose) ? @options[:verbose] : false
+ begin
+ proc.call
+ ensure
+ $VERBOSE = @verbose
+ $stderr = @saved_err
+ Thread.current[:in_mspec_complain_matcher] = false
+ end
+
+ @warning = err.to_s
+ unless @complaint.nil?
+ case @complaint
+ when Regexp
+ return false unless @warning =~ @complaint
+ else
+ return false unless @warning == @complaint
+ end
+ end
+
+ return @warning.empty? ? false : true
+ end
+
+ def failure_message
+ if @complaint.nil?
+ ["Expected a warning", "but received none"]
+ elsif @complaint.kind_of? Regexp
+ ["Expected warning to match: #{@complaint.inspect}", "but got: #{@warning.chomp.inspect}"]
+ else
+ ["Expected warning: #{@complaint.inspect}", "but got: #{@warning.chomp.inspect}"]
+ end
+ end
+
+ def negative_failure_message
+ if @complaint.nil?
+ ["Unexpected warning: ", @warning.chomp.inspect]
+ elsif @complaint.kind_of? Regexp
+ ["Expected warning not to match: #{@complaint.inspect}", "but got: #{@warning.chomp.inspect}"]
+ else
+ ["Expected warning: #{@complaint.inspect}", "but got: #{@warning.chomp.inspect}"]
+ end
+ end
+end
+
+module MSpecMatchers
+ private def complain(complaint = nil, options = nil)
+ ComplainMatcher.new(complaint, options)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/eql.rb b/spec/mspec/lib/mspec/matchers/eql.rb
new file mode 100644
index 0000000000..bcab88ebee
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/eql.rb
@@ -0,0 +1,26 @@
+class EqlMatcher
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @actual.eql?(@expected)
+ end
+
+ def failure_message
+ ["Expected #{MSpec.format(@actual)}",
+ "to have same value and type as #{MSpec.format(@expected)}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{MSpec.format(@actual)}",
+ "not to have same value or type as #{MSpec.format(@expected)}"]
+ end
+end
+
+module MSpecMatchers
+ private def eql(expected)
+ EqlMatcher.new(expected)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/equal.rb b/spec/mspec/lib/mspec/matchers/equal.rb
new file mode 100644
index 0000000000..5ba4856d82
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/equal.rb
@@ -0,0 +1,26 @@
+class EqualMatcher
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @actual.equal?(@expected)
+ end
+
+ def failure_message
+ ["Expected #{MSpec.format(@actual)}",
+ "to be identical to #{MSpec.format(@expected)}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{MSpec.format(@actual)}",
+ "not to be identical to #{MSpec.format(@expected)}"]
+ end
+end
+
+module MSpecMatchers
+ private def equal(expected)
+ EqualMatcher.new(expected)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/equal_element.rb b/spec/mspec/lib/mspec/matchers/equal_element.rb
new file mode 100644
index 0000000000..8da2567fcf
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/equal_element.rb
@@ -0,0 +1,78 @@
+class EqualElementMatcher
+ def initialize(element, attributes = nil, content = nil, options = {})
+ @element = element
+ @attributes = attributes
+ @content = content
+ @options = options
+ end
+
+ def matches?(actual)
+ @actual = actual
+
+ matched = true
+
+ if @options[:not_closed]
+ matched &&= actual =~ /^#{Regexp.quote("<" + @element)}.*#{Regexp.quote(">" + (@content || ''))}$/
+ else
+ matched &&= actual =~ /^#{Regexp.quote("<" + @element)}/
+ matched &&= actual =~ /#{Regexp.quote("</" + @element + ">")}$/
+ matched &&= actual =~ /#{Regexp.quote(">" + @content + "</")}/ if @content
+ end
+
+ if @attributes
+ if @attributes.empty?
+ matched &&= actual.scan(/\w+\=\"(.*)\"/).size == 0
+ else
+ @attributes.each do |key, value|
+ if value == true
+ matched &&= (actual.scan(/#{Regexp.quote(key)}(\s|>)/).size == 1)
+ else
+ matched &&= (actual.scan(%Q{ #{key}="#{value}"}).size == 1)
+ end
+ end
+ end
+ end
+
+ !!matched
+ end
+
+ def failure_message
+ ["Expected #{MSpec.format(@actual)}",
+ "to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{MSpec.format(@actual)}",
+ "not to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"]
+ end
+
+ def attributes_for_failure_message
+ if @attributes
+ if @attributes.empty?
+ "no attributes"
+ else
+ @attributes.inject([]) { |memo, n| memo << %Q{#{n[0]}="#{n[1]}"} }.join(" ")
+ end
+ else
+ "any attributes"
+ end
+ end
+
+ def content_for_failure_message
+ if @content
+ if @content.empty?
+ "no content"
+ else
+ "#{@content.inspect} as content"
+ end
+ else
+ "any content"
+ end
+ end
+end
+
+module MSpecMatchers
+ private def equal_element(*args)
+ EqualElementMatcher.new(*args)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/have_class_variable.rb b/spec/mspec/lib/mspec/matchers/have_class_variable.rb
new file mode 100644
index 0000000000..dd43ced621
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/have_class_variable.rb
@@ -0,0 +1,12 @@
+require 'mspec/matchers/variable'
+
+class HaveClassVariableMatcher < VariableMatcher
+ self.variables_method = :class_variables
+ self.description = 'class variable'
+end
+
+module MSpecMatchers
+ private def have_class_variable(variable)
+ HaveClassVariableMatcher.new(variable)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/have_constant.rb b/spec/mspec/lib/mspec/matchers/have_constant.rb
new file mode 100644
index 0000000000..6ec7c75b85
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/have_constant.rb
@@ -0,0 +1,12 @@
+require 'mspec/matchers/variable'
+
+class HaveConstantMatcher < VariableMatcher
+ self.variables_method = :constants
+ self.description = 'constant'
+end
+
+module MSpecMatchers
+ private def have_constant(variable)
+ HaveConstantMatcher.new(variable)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/have_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_instance_method.rb
new file mode 100644
index 0000000000..9a5a31aa0f
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/have_instance_method.rb
@@ -0,0 +1,24 @@
+require 'mspec/matchers/method'
+
+class HaveInstanceMethodMatcher < MethodMatcher
+ def matches?(mod)
+ @mod = mod
+ mod.instance_methods(@include_super).include? @method
+ end
+
+ def failure_message
+ ["Expected #{@mod} to have instance method '#{@method.to_s}'",
+ "but it does not"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@mod} NOT to have instance method '#{@method.to_s}'",
+ "but it does"]
+ end
+end
+
+module MSpecMatchers
+ private def have_instance_method(method, include_super = true)
+ HaveInstanceMethodMatcher.new method, include_super
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/have_instance_variable.rb b/spec/mspec/lib/mspec/matchers/have_instance_variable.rb
new file mode 100644
index 0000000000..de51b3209d
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/have_instance_variable.rb
@@ -0,0 +1,12 @@
+require 'mspec/matchers/variable'
+
+class HaveInstanceVariableMatcher < VariableMatcher
+ self.variables_method = :instance_variables
+ self.description = 'instance variable'
+end
+
+module MSpecMatchers
+ private def have_instance_variable(variable)
+ HaveInstanceVariableMatcher.new(variable)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/have_method.rb b/spec/mspec/lib/mspec/matchers/have_method.rb
new file mode 100644
index 0000000000..e962e69e0a
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/have_method.rb
@@ -0,0 +1,24 @@
+require 'mspec/matchers/method'
+
+class HaveMethodMatcher < MethodMatcher
+ def matches?(mod)
+ @mod = mod
+ @mod.methods(@include_super).include? @method
+ end
+
+ def failure_message
+ ["Expected #{@mod} to have method '#{@method.to_s}'",
+ "but it does not"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@mod} NOT to have method '#{@method.to_s}'",
+ "but it does"]
+ end
+end
+
+module MSpecMatchers
+ private def have_method(method, include_super = true)
+ HaveMethodMatcher.new method, include_super
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/have_private_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_private_instance_method.rb
new file mode 100644
index 0000000000..d32db76c6a
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/have_private_instance_method.rb
@@ -0,0 +1,24 @@
+require 'mspec/matchers/method'
+
+class HavePrivateInstanceMethodMatcher < MethodMatcher
+ def matches?(mod)
+ @mod = mod
+ mod.private_instance_methods(@include_super).include? @method
+ end
+
+ def failure_message
+ ["Expected #{@mod} to have private instance method '#{@method.to_s}'",
+ "but it does not"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@mod} NOT to have private instance method '#{@method.to_s}'",
+ "but it does"]
+ end
+end
+
+module MSpecMatchers
+ private def have_private_instance_method(method, include_super = true)
+ HavePrivateInstanceMethodMatcher.new method, include_super
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/have_private_method.rb b/spec/mspec/lib/mspec/matchers/have_private_method.rb
new file mode 100644
index 0000000000..c74165cfc7
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/have_private_method.rb
@@ -0,0 +1,24 @@
+require 'mspec/matchers/method'
+
+class HavePrivateMethodMatcher < MethodMatcher
+ def matches?(mod)
+ @mod = mod
+ mod.private_methods(@include_super).include? @method
+ end
+
+ def failure_message
+ ["Expected #{@mod} to have private method '#{@method.to_s}'",
+ "but it does not"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@mod} NOT to have private method '#{@method.to_s}'",
+ "but it does"]
+ end
+end
+
+module MSpecMatchers
+ private def have_private_method(method, include_super = true)
+ HavePrivateMethodMatcher.new method, include_super
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb
new file mode 100644
index 0000000000..1deb2f995d
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb
@@ -0,0 +1,24 @@
+require 'mspec/matchers/method'
+
+class HaveProtectedInstanceMethodMatcher < MethodMatcher
+ def matches?(mod)
+ @mod = mod
+ mod.protected_instance_methods(@include_super).include? @method
+ end
+
+ def failure_message
+ ["Expected #{@mod} to have protected instance method '#{@method.to_s}'",
+ "but it does not"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@mod} NOT to have protected instance method '#{@method.to_s}'",
+ "but it does"]
+ end
+end
+
+module MSpecMatchers
+ private def have_protected_instance_method(method, include_super = true)
+ HaveProtectedInstanceMethodMatcher.new method, include_super
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/have_public_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_public_instance_method.rb
new file mode 100644
index 0000000000..0e620532c0
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/have_public_instance_method.rb
@@ -0,0 +1,24 @@
+require 'mspec/matchers/method'
+
+class HavePublicInstanceMethodMatcher < MethodMatcher
+ def matches?(mod)
+ @mod = mod
+ mod.public_instance_methods(@include_super).include? @method
+ end
+
+ def failure_message
+ ["Expected #{@mod} to have public instance method '#{@method.to_s}'",
+ "but it does not"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@mod} NOT to have public instance method '#{@method.to_s}'",
+ "but it does"]
+ end
+end
+
+module MSpecMatchers
+ private def have_public_instance_method(method, include_super = true)
+ HavePublicInstanceMethodMatcher.new method, include_super
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/have_singleton_method.rb b/spec/mspec/lib/mspec/matchers/have_singleton_method.rb
new file mode 100644
index 0000000000..b60dd2536b
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/have_singleton_method.rb
@@ -0,0 +1,24 @@
+require 'mspec/matchers/method'
+
+class HaveSingletonMethodMatcher < MethodMatcher
+ def matches?(obj)
+ @obj = obj
+ obj.singleton_methods(@include_super).include? @method
+ end
+
+ def failure_message
+ ["Expected #{@obj} to have singleton method '#{@method.to_s}'",
+ "but it does not"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@obj} NOT to have singleton method '#{@method.to_s}'",
+ "but it does"]
+ end
+end
+
+module MSpecMatchers
+ private def have_singleton_method(method, include_super = true)
+ HaveSingletonMethodMatcher.new method, include_super
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/include.rb b/spec/mspec/lib/mspec/matchers/include.rb
new file mode 100644
index 0000000000..3f07f35548
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/include.rb
@@ -0,0 +1,31 @@
+class IncludeMatcher
+ def initialize(*expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @expected.each do |e|
+ @element = e
+ unless @actual.include?(e)
+ return false
+ end
+ end
+ return true
+ end
+
+ def failure_message
+ ["Expected #{MSpec.format(@actual)}", "to include #{MSpec.format(@element)}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{MSpec.format(@actual)}", "not to include #{MSpec.format(@element)}"]
+ end
+end
+
+# Cannot override #include at the toplevel in MRI
+module MSpecMatchers
+ private def include(*expected)
+ IncludeMatcher.new(*expected)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/include_any_of.rb b/spec/mspec/lib/mspec/matchers/include_any_of.rb
new file mode 100644
index 0000000000..ce097ccf0f
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/include_any_of.rb
@@ -0,0 +1,29 @@
+class IncludeAnyOfMatcher
+ def initialize(*expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @expected.each do |e|
+ if @actual.include?(e)
+ return true
+ end
+ end
+ return false
+ end
+
+ def failure_message
+ ["Expected #{@actual.inspect}", "to include any of #{@expected.inspect}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual.inspect}", "not to include any of #{@expected.inspect}"]
+ end
+end
+
+module MSpecMatchers
+ private def include_any_of(*expected)
+ IncludeAnyOfMatcher.new(*expected)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/infinity.rb b/spec/mspec/lib/mspec/matchers/infinity.rb
new file mode 100644
index 0000000000..8bfa6dbd10
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/infinity.rb
@@ -0,0 +1,28 @@
+class InfinityMatcher
+ def initialize(expected_sign)
+ @expected_sign = expected_sign
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @actual.kind_of?(Float) && @actual.infinite? == @expected_sign
+ end
+
+ def failure_message
+ ["Expected #{@actual}", "to be #{"-" if @expected_sign == -1}Infinity"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual}", "not to be #{"-" if @expected_sign == -1}Infinity"]
+ end
+end
+
+module MSpecMatchers
+ private def be_positive_infinity
+ InfinityMatcher.new(1)
+ end
+
+ private def be_negative_infinity
+ InfinityMatcher.new(-1)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/match_yaml.rb b/spec/mspec/lib/mspec/matchers/match_yaml.rb
new file mode 100644
index 0000000000..30561627c3
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/match_yaml.rb
@@ -0,0 +1,50 @@
+class MatchYAMLMatcher
+
+ def initialize(expected)
+ if valid_yaml?(expected)
+ @expected = expected
+ else
+ @expected = expected.to_yaml
+ end
+ end
+
+ def matches?(actual)
+ @actual = actual
+ clean_yaml(@actual) == clean_yaml(@expected)
+ end
+
+ def failure_message
+ ["Expected #{@actual.inspect}", " to match #{@expected.inspect}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual.inspect}", " to match #{@expected.inspect}"]
+ end
+
+ protected
+
+ def clean_yaml(yaml)
+ yaml.gsub(/([^-]|^---)\s+\n/, "\\1\n").sub(/\n\.\.\.\n$/, "\n")
+ end
+
+ def valid_yaml?(obj)
+ require 'yaml'
+ begin
+ if YAML.respond_to?(:unsafe_load)
+ YAML.unsafe_load(obj)
+ else
+ YAML.load(obj)
+ end
+ rescue
+ false
+ else
+ true
+ end
+ end
+end
+
+module MSpecMatchers
+ private def match_yaml(expected)
+ MatchYAMLMatcher.new(expected)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/method.rb b/spec/mspec/lib/mspec/matchers/method.rb
new file mode 100644
index 0000000000..2b54419faa
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/method.rb
@@ -0,0 +1,10 @@
+class MethodMatcher
+ def initialize(method, include_super = true)
+ @include_super = include_super
+ @method = method.to_sym
+ end
+
+ def matches?(mod)
+ raise Exception, "define #matches? in the subclass"
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/output.rb b/spec/mspec/lib/mspec/matchers/output.rb
new file mode 100644
index 0000000000..5bb5d55027
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/output.rb
@@ -0,0 +1,67 @@
+require 'mspec/helpers/io'
+
+class OutputMatcher
+ def initialize(stdout, stderr)
+ @out = stdout
+ @err = stderr
+ end
+
+ def matches?(proc)
+ @saved_out = $stdout
+ @saved_err = $stderr
+ @stdout = $stdout = IOStub.new
+ @stderr = $stderr = IOStub.new
+
+ proc.call
+
+ unless @out.nil?
+ case @out
+ when Regexp
+ return false unless $stdout =~ @out
+ else
+ return false unless $stdout == @out
+ end
+ end
+
+ unless @err.nil?
+ case @err
+ when Regexp
+ return false unless $stderr =~ @err
+ else
+ return false unless $stderr == @err
+ end
+ end
+
+ return true
+ ensure
+ $stdout = @saved_out
+ $stderr = @saved_err
+ end
+
+ def failure_message
+ expected_out = "\n"
+ actual_out = "\n"
+ unless @out.nil?
+ expected_out += " $stdout: #{MSpec.format(@out)}\n"
+ actual_out += " $stdout: #{MSpec.format(@stdout.to_s)}\n"
+ end
+ unless @err.nil?
+ expected_out += " $stderr: #{MSpec.format(@err)}\n"
+ actual_out += " $stderr: #{MSpec.format(@stderr.to_s)}\n"
+ end
+ ["Expected:#{expected_out}", " got:#{actual_out}"]
+ end
+
+ def negative_failure_message
+ out = ""
+ out += " $stdout: #{@stdout.chomp.dump}\n" unless @out.nil?
+ out += " $stderr: #{@stderr.chomp.dump}\n" unless @err.nil?
+ ["Expected output not to be:\n", out]
+ end
+end
+
+module MSpecMatchers
+ private def output(stdout = nil, stderr = nil)
+ OutputMatcher.new(stdout, stderr)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/output_to_fd.rb b/spec/mspec/lib/mspec/matchers/output_to_fd.rb
new file mode 100644
index 0000000000..f4d7b4ea1f
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/output_to_fd.rb
@@ -0,0 +1,71 @@
+require 'mspec/helpers/tmp'
+
+# Lower-level output speccing mechanism for a single
+# output stream. Unlike OutputMatcher which provides
+# methods to capture the output, we actually replace
+# the FD itself so that there is no reliance on a
+# certain method being used.
+class OutputToFDMatcher
+ def initialize(expected, to)
+ @to, @expected = to, expected
+
+ case @to
+ when STDOUT
+ @to_name = "STDOUT"
+ when STDERR
+ @to_name = "STDERR"
+ when IO
+ @to_name = @to.object_id.to_s
+ else
+ raise ArgumentError, "#{@to.inspect} is not a supported output target"
+ end
+ end
+
+ def with_tmp
+ path = tmp("mspec_output_to_#{$$}_#{Time.now.to_i}")
+ File.open(path, 'w+') { |io|
+ yield(io)
+ }
+ ensure
+ File.delete path if path
+ end
+
+ def matches?(block)
+ old_to = @to.dup
+ with_tmp do |out|
+ # Replacing with a file handle so that Readline etc. work
+ @to.reopen out
+ begin
+ block.call
+ ensure
+ @to.reopen old_to
+ old_to.close
+ end
+
+ out.rewind
+ @actual = out.read
+
+ case @expected
+ when Regexp
+ !(@actual =~ @expected).nil?
+ else
+ @actual == @expected
+ end
+ end
+ end
+
+ def failure_message()
+ ["Expected (#{@to_name}): #{@expected.inspect}\n",
+ "#{'but got'.rjust(@to_name.length + 10)}: #{@actual.inspect}\nBacktrace"]
+ end
+
+ def negative_failure_message()
+ ["Expected output (#{@to_name}) to NOT be:\n", @actual.inspect]
+ end
+end
+
+module MSpecMatchers
+ private def output_to_fd(what, where = STDOUT)
+ OutputToFDMatcher.new what, where
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/raise_error.rb b/spec/mspec/lib/mspec/matchers/raise_error.rb
new file mode 100644
index 0000000000..54378bb34c
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/raise_error.rb
@@ -0,0 +1,93 @@
+class RaiseErrorMatcher
+ FAILURE_MESSAGE_FOR_EXCEPTION = {}.compare_by_identity
+
+ attr_writer :block
+
+ def initialize(exception, message, &block)
+ @exception = exception
+ @message = message
+ @block = block
+ @actual = nil
+ end
+
+ # This #matches? method is unusual because it doesn't always return a boolean but instead
+ # re-raises the original exception if proc.call raises an exception and #matching_exception? is false.
+ # The reasoning is the original exception class matters and we don't want to change it by raising another exception,
+ # so instead we attach the #failure_message and extract it in ExceptionState#message.
+ def matches?(proc)
+ @result = proc.call
+ return false
+ rescue Object => actual
+ @actual = actual
+
+ if matching_exception?(actual)
+ # The block has its own expectations and will throw an exception if it fails
+ @block[actual] if @block
+ return true
+ else
+ FAILURE_MESSAGE_FOR_EXCEPTION[actual] = failure_message
+ raise actual
+ end
+ end
+
+ def matching_class?(exc)
+ @exception === exc
+ end
+
+ def matching_message?(exc)
+ case @message
+ when String
+ @message == exc.message
+ when Regexp
+ @message =~ exc.message
+ else
+ true
+ end
+ end
+
+ def matching_exception?(exc)
+ matching_class?(exc) and matching_message?(exc)
+ end
+
+ def exception_class_and_message(exception_class, message)
+ if message
+ "#{exception_class} (#{message})"
+ else
+ "#{exception_class}"
+ end
+ end
+
+ def format_expected_exception
+ exception_class_and_message(@exception, @message)
+ end
+
+ def format_exception(exception)
+ exception_class_and_message(exception.class, exception.message)
+ end
+
+ def failure_message
+ message = ["Expected #{format_expected_exception}"]
+
+ if @actual
+ message << "but got: #{format_exception(@actual)}"
+ else
+ message << "but no exception was raised (#{MSpec.format(@result)} was returned)"
+ end
+
+ message
+ end
+
+ def negative_failure_message
+ message = ["Expected to not get #{format_expected_exception}", ""]
+ unless @actual.class == @exception
+ message[1] = "but got: #{format_exception(@actual)}"
+ end
+ message
+ end
+end
+
+module MSpecMatchers
+ private def raise_error(exception = Exception, message = nil, &block)
+ RaiseErrorMatcher.new(exception, message, &block)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/respond_to.rb b/spec/mspec/lib/mspec/matchers/respond_to.rb
new file mode 100644
index 0000000000..6b35ae2d3c
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/respond_to.rb
@@ -0,0 +1,24 @@
+class RespondToMatcher
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @actual.respond_to?(@expected)
+ end
+
+ def failure_message
+ ["Expected #{@actual.inspect} (#{@actual.class})", "to respond to #{@expected}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual.inspect} (#{@actual.class})", "not to respond to #{@expected}"]
+ end
+end
+
+module MSpecMatchers
+ private def respond_to(expected)
+ RespondToMatcher.new(expected)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/signed_zero.rb b/spec/mspec/lib/mspec/matchers/signed_zero.rb
new file mode 100644
index 0000000000..2ff90f4994
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/signed_zero.rb
@@ -0,0 +1,28 @@
+class SignedZeroMatcher
+ def initialize(expected_sign)
+ @expected_sign = expected_sign
+ end
+
+ def matches?(actual)
+ @actual = actual
+ (1.0/actual).infinite? == @expected_sign
+ end
+
+ def failure_message
+ ["Expected #{@actual}", "to be #{"-" if @expected_sign == -1}0.0"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual}", "not to be #{"-" if @expected_sign == -1}0.0"]
+ end
+end
+
+module MSpecMatchers
+ private def be_positive_zero
+ SignedZeroMatcher.new(1)
+ end
+
+ private def be_negative_zero
+ SignedZeroMatcher.new(-1)
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/skip.rb b/spec/mspec/lib/mspec/matchers/skip.rb
new file mode 100644
index 0000000000..7c175d358d
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/skip.rb
@@ -0,0 +1,5 @@
+module MSpecMatchers
+ private def skip(reason = 'no reason')
+ raise SkippedSpecError, reason
+ end
+end
diff --git a/spec/mspec/lib/mspec/matchers/variable.rb b/spec/mspec/lib/mspec/matchers/variable.rb
new file mode 100644
index 0000000000..4d801ea337
--- /dev/null
+++ b/spec/mspec/lib/mspec/matchers/variable.rb
@@ -0,0 +1,24 @@
+class VariableMatcher
+ class << self
+ attr_accessor :variables_method, :description
+ end
+
+ def initialize(variable)
+ @variable = variable.to_sym
+ end
+
+ def matches?(object)
+ @object = object
+ @object.send(self.class.variables_method).include? @variable
+ end
+
+ def failure_message
+ ["Expected #{@object} to have #{self.class.description} '#{@variable}'",
+ "but it does not"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@object} NOT to have #{self.class.description} '#{@variable}'",
+ "but it does"]
+ end
+end
diff --git a/spec/mspec/lib/mspec/mocks.rb b/spec/mspec/lib/mspec/mocks.rb
new file mode 100644
index 0000000000..6a029c7b53
--- /dev/null
+++ b/spec/mspec/lib/mspec/mocks.rb
@@ -0,0 +1,3 @@
+require 'mspec/mocks/mock'
+require 'mspec/mocks/proxy'
+require 'mspec/mocks/object'
diff --git a/spec/mspec/lib/mspec/mocks/mock.rb b/spec/mspec/lib/mspec/mocks/mock.rb
new file mode 100644
index 0000000000..28a083cc15
--- /dev/null
+++ b/spec/mspec/lib/mspec/mocks/mock.rb
@@ -0,0 +1,212 @@
+require 'mspec/expectations/expectations'
+require 'mspec/helpers/warning'
+
+module Mock
+ def self.reset
+ @mocks = @stubs = @objects = nil
+ end
+
+ def self.objects
+ @objects ||= {}
+ end
+
+ def self.mocks
+ @mocks ||= Hash.new { |h,k| h[k] = [] }
+ end
+
+ def self.stubs
+ @stubs ||= Hash.new { |h,k| h[k] = [] }
+ end
+
+ def self.replaced_name(obj, sym)
+ :"__mspec_#{obj.__id__}_#{sym}__"
+ end
+
+ def self.replaced_key(obj, sym)
+ [replaced_name(obj, sym), sym]
+ end
+
+ def self.has_key?(keys, sym)
+ !!keys.find { |k| k.first == sym }
+ end
+
+ def self.replaced?(sym)
+ has_key?(mocks.keys, sym) or has_key?(stubs.keys, sym)
+ end
+
+ def self.clear_replaced(key)
+ mocks.delete key
+ stubs.delete key
+ end
+
+ def self.mock_respond_to?(obj, sym, include_private = false)
+ name = replaced_name(obj, :respond_to?)
+ if replaced? name
+ obj.__send__ name, sym, include_private
+ else
+ obj.respond_to? sym, include_private
+ end
+ end
+
+ def self.install_method(obj, sym, type = nil)
+ meta = obj.singleton_class
+
+ key = replaced_key obj, sym
+ sym = sym.to_sym
+
+ if type == :stub and mocks.key?(key)
+ # Defining a stub and there is already a mock, ignore the stub
+ return
+ end
+
+ if (sym == :respond_to? or mock_respond_to?(obj, sym, true)) and !replaced?(key.first)
+ meta.__send__ :alias_method, key.first, sym
+ end
+
+ suppress_warning {
+ meta.class_eval {
+ define_method(sym) do |*args, &block|
+ Mock.verify_call self, sym, *args, &block
+ end
+ }
+ }
+
+ proxy = MockProxy.new type
+
+ if proxy.mock?
+ MSpec.expectation
+ MSpec.actions :expectation, MSpec.current.state
+ end
+
+ if proxy.mock? and stubs.key?(key)
+ # Defining a mock and there is already a stub, remove the stub
+ stubs.delete key
+ end
+
+ if proxy.stub?
+ stubs[key].unshift proxy
+ else
+ mocks[key] << proxy
+ end
+ objects[key] = obj
+
+ proxy
+ end
+
+ def self.name_or_inspect(obj)
+ obj.instance_variable_get(:@name) || obj.inspect
+ end
+
+ def self.inspect_args(args)
+ "(#{Array(args).map(&:inspect).join(', ')})"
+ end
+
+ def self.verify_count
+ mocks.each do |key, proxies|
+ obj = objects[key]
+ proxies.each do |proxy|
+ qualifier, count = proxy.count
+ pass = case qualifier
+ when :at_least
+ proxy.calls >= count
+ when :at_most
+ proxy.calls <= count
+ when :exactly
+ proxy.calls == count
+ when :any_number_of_times
+ true
+ else
+ false
+ end
+ unless pass
+ SpecExpectation.fail_with(
+ "Mock '#{name_or_inspect obj}' expected to receive #{key.last}#{inspect_args proxy.arguments} " + \
+ "#{qualifier.to_s.sub('_', ' ')} #{count} times",
+ "but received it #{proxy.calls} times")
+ end
+ end
+ end
+ end
+
+ def self.verify_call(obj, sym, *args, &block)
+ compare = *args
+ compare = compare.first if compare.length <= 1
+
+ key = replaced_key obj, sym
+ [mocks, stubs].each do |proxies|
+ proxies.fetch(key, []).each do |proxy|
+ pass = case proxy.arguments
+ when :any_args
+ true
+ when :no_args
+ compare.nil?
+ else
+ proxy.arguments == compare
+ end
+
+ if proxy.yielding?
+ if block
+ proxy.yielding.each do |args_to_yield|
+ if block.arity == -1 || block.arity == args_to_yield.size
+ block.call(*args_to_yield)
+ else
+ SpecExpectation.fail_with(
+ "Mock '#{name_or_inspect obj}' asked to yield " + \
+ "|#{proxy.yielding.join(', ')}| on #{sym}\n",
+ "but a block with arity #{block.arity} was passed")
+ end
+ end
+ else
+ SpecExpectation.fail_with(
+ "Mock '#{name_or_inspect obj}' asked to yield " + \
+ "|[#{proxy.yielding.join('], [')}]| on #{sym}\n",
+ "but no block was passed")
+ end
+ end
+
+ if pass
+ proxy.called
+
+ if proxy.raising?
+ raise proxy.raising
+ else
+ return proxy.returning
+ end
+ end
+ end
+ end
+
+ if sym.to_sym == :respond_to?
+ mock_respond_to? obj, *args
+ else
+ SpecExpectation.fail_with("Mock '#{name_or_inspect obj}': method #{sym}\n",
+ "called with unexpected arguments #{inspect_args args}")
+ end
+ end
+
+ def self.cleanup
+ objects.each do |key, obj|
+ if obj.kind_of? MockIntObject
+ clear_replaced key
+ next
+ end
+
+ replaced = key.first
+ sym = key.last
+ meta = obj.singleton_class
+
+ if mock_respond_to? obj, replaced, true
+ suppress_warning do
+ meta.__send__ :alias_method, sym, replaced
+ end
+ meta.__send__ :remove_method, replaced
+ else
+ meta.__send__ :remove_method, sym
+ end
+
+ clear_replaced key
+ end
+ ensure
+ reset
+ end
+end
diff --git a/spec/mspec/lib/mspec/mocks/object.rb b/spec/mspec/lib/mspec/mocks/object.rb
new file mode 100644
index 0000000000..fcaa1caef0
--- /dev/null
+++ b/spec/mspec/lib/mspec/mocks/object.rb
@@ -0,0 +1,28 @@
+require 'mspec/mocks/proxy'
+
+class Object
+ def should_receive(sym)
+ Mock.install_method self, sym
+ end
+
+ def should_not_receive(sym)
+ proxy = Mock.install_method self, sym
+ proxy.exactly(0).times
+ end
+
+ def stub!(sym)
+ Mock.install_method self, sym, :stub
+ end
+end
+
+def mock(name, options = {})
+ MockObject.new name, options
+end
+
+def mock_int(val)
+ MockIntObject.new(val)
+end
+
+def mock_numeric(name, options = {})
+ NumericMockObject.new name, options
+end
diff --git a/spec/mspec/lib/mspec/mocks/proxy.rb b/spec/mspec/lib/mspec/mocks/proxy.rb
new file mode 100644
index 0000000000..8473132b0b
--- /dev/null
+++ b/spec/mspec/lib/mspec/mocks/proxy.rb
@@ -0,0 +1,186 @@
+class MockObject
+ def initialize(name, options = {})
+ @name = name
+ @null = options[:null_object]
+ end
+
+ def method_missing(sym, *args, &block)
+ @null ? self : super
+ end
+ private :method_missing
+end
+
+class NumericMockObject < Numeric
+ def initialize(name, options = {})
+ @name = name
+ @null = options[:null_object]
+ end
+
+ def method_missing(sym, *args, &block)
+ @null ? self : super
+ end
+
+ def singleton_method_added(val)
+ end
+end
+
+class MockIntObject
+ def initialize(val)
+ @value = val
+ @calls = 0
+
+ key = [self, :to_int]
+
+ Mock.objects[key] = self
+ Mock.mocks[key] << self
+ end
+
+ attr_reader :calls
+
+ def to_int
+ @calls += 1
+ @value.to_int
+ end
+
+ def count
+ [:at_least, 1]
+ end
+end
+
+class MockProxy
+ attr_reader :raising, :yielding
+
+ def initialize(type = nil)
+ @multiple_returns = nil
+ @returning = nil
+ @raising = nil
+ @yielding = []
+ @arguments = :any_args
+ @type = type || :mock
+ end
+
+ def mock?
+ @type == :mock
+ end
+
+ def stub?
+ @type == :stub
+ end
+
+ def count
+ @count ||= mock? ? [:exactly, 1] : [:any_number_of_times, 0]
+ end
+
+ def arguments
+ @arguments
+ end
+
+ def returning
+ if @multiple_returns
+ if @returning.size == 1
+ @multiple_returns = false
+ return @returning = @returning.shift
+ end
+ return @returning.shift
+ end
+ @returning
+ end
+
+ def times
+ self
+ end
+
+ def calls
+ @calls ||= 0
+ end
+
+ def called
+ @calls = calls + 1
+ end
+
+ def exactly(n)
+ @count = [:exactly, n_times(n)]
+ self
+ end
+
+ def at_least(n)
+ @count = [:at_least, n_times(n)]
+ self
+ end
+
+ def at_most(n)
+ @count = [:at_most, n_times(n)]
+ self
+ end
+
+ def once
+ exactly 1
+ end
+
+ def twice
+ exactly 2
+ end
+
+ def any_number_of_times
+ @count = [:any_number_of_times, 0]
+ self
+ end
+
+ def with(*args)
+ raise ArgumentError, "you must specify the expected arguments" if args.empty?
+ if args.length == 1
+ @arguments = args.first
+ else
+ @arguments = args
+ end
+ self
+ end
+
+ def and_return(*args)
+ case args.size
+ when 0
+ @returning = nil
+ when 1
+ @returning = args[0]
+ else
+ @multiple_returns = true
+ @returning = args
+ count[1] = args.size if count[1] < args.size
+ end
+ self
+ end
+
+ def and_raise(exception)
+ if exception.kind_of? String
+ @raising = RuntimeError.new exception
+ else
+ @raising = exception
+ end
+ end
+
+ def raising?
+ @raising != nil
+ end
+
+ def and_yield(*args)
+ @yielding << args
+ self
+ end
+
+ def yielding?
+ !@yielding.empty?
+ end
+
+ private
+
+ def n_times(n)
+ case n
+ when :once
+ 1
+ when :twice
+ 2
+ else
+ Integer n
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner.rb b/spec/mspec/lib/mspec/runner.rb
new file mode 100644
index 0000000000..df57b9f69b
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner.rb
@@ -0,0 +1,12 @@
+require 'mspec/mocks'
+require 'mspec/runner/mspec'
+require 'mspec/runner/context'
+require 'mspec/runner/evaluate'
+require 'mspec/runner/example'
+require 'mspec/runner/exception'
+require 'mspec/runner/object'
+require 'mspec/runner/formatters'
+require 'mspec/runner/actions'
+require 'mspec/runner/filters'
+require 'mspec/runner/shared'
+require 'mspec/runner/tag'
diff --git a/spec/mspec/lib/mspec/runner/actions.rb b/spec/mspec/lib/mspec/runner/actions.rb
new file mode 100644
index 0000000000..0a5a05fbd1
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions.rb
@@ -0,0 +1,6 @@
+require 'mspec/runner/actions/tally'
+require 'mspec/runner/actions/timer'
+require 'mspec/runner/actions/filter'
+require 'mspec/runner/actions/tag'
+require 'mspec/runner/actions/taglist'
+require 'mspec/runner/actions/tagpurge'
diff --git a/spec/mspec/lib/mspec/runner/actions/constants_leak_checker.rb b/spec/mspec/lib/mspec/runner/actions/constants_leak_checker.rb
new file mode 100644
index 0000000000..abfb6dd0ee
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions/constants_leak_checker.rb
@@ -0,0 +1,84 @@
+class ConstantsLockFile
+ LOCK_FILE_NAME = '.mspec.constants'
+
+ def self.lock_file
+ @prefix ||= File.expand_path(MSpecScript.get(:prefix) || '.')
+ "#{@prefix}/#{LOCK_FILE_NAME}"
+ end
+
+ def self.load
+ if File.exist?(lock_file)
+ File.readlines(lock_file).map(&:chomp)
+ else
+ []
+ end
+ end
+
+ def self.dump(ary)
+ contents = ary.map(&:to_s).uniq.sort.join("\n") + "\n"
+ File.write(lock_file, contents)
+ end
+end
+
+class ConstantLeakError < StandardError
+end
+
+class ConstantsLeakCheckerAction
+ def initialize(save)
+ @save = save
+ @check = !save
+ @constants_locked = ConstantsLockFile.load
+ @exclude_patterns = MSpecScript.get(:toplevel_constants_excludes) || []
+ end
+
+ def register
+ MSpec.register :start, self
+ MSpec.register :before, self
+ MSpec.register :after, self
+ MSpec.register :finish, self
+ end
+
+ def start
+ @constants_start = constants_now
+ end
+
+ def before(state)
+ @constants_before = constants_now
+ end
+
+ def after(state)
+ constants = remove_excludes(constants_now - @constants_before - @constants_locked)
+
+ if @check && !constants.empty?
+ MSpec.protect 'Constants leak check' do
+ raise ConstantLeakError, "Top level constants leaked: #{constants.join(', ')}"
+ end
+ end
+ end
+
+ def finish
+ constants = remove_excludes(constants_now - @constants_start - @constants_locked)
+
+ if @save
+ ConstantsLockFile.dump(@constants_locked + constants)
+ end
+
+ if @check && !constants.empty?
+ MSpec.protect 'Global constants leak check' do
+ raise ConstantLeakError, "Top level constants leaked in the whole test suite: #{constants.join(', ')}"
+ end
+ end
+ end
+
+ private
+
+ def constants_now
+ Object.constants.map(&:to_s)
+ end
+
+ def remove_excludes(constants)
+ constants.reject { |name|
+ @exclude_patterns.any? { |pattern| pattern === name }
+ }
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/actions/filter.rb b/spec/mspec/lib/mspec/runner/actions/filter.rb
new file mode 100644
index 0000000000..b0ad7080da
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions/filter.rb
@@ -0,0 +1,40 @@
+require 'mspec/runner/filters/match'
+
+# ActionFilter is a base class for actions that are triggered by
+# specs that match the filter. The filter may be specified by
+# strings that match spec descriptions or by tags for strings
+# that match spec descriptions.
+#
+# Unlike TagFilter and RegexpFilter, ActionFilter instances do
+# not affect the specs that are run. The filter is only used to
+# trigger the action.
+
+class ActionFilter
+ def initialize(tags = nil, descs = nil)
+ @tags = Array(tags)
+ descs = Array(descs)
+ @sfilter = descs.empty? ? nil : MatchFilter.new(nil, *descs)
+ @tfilter = nil
+ end
+
+ def ===(string)
+ @sfilter === string or @tfilter === string
+ end
+
+ def load
+ return if @tags.empty?
+
+ desc = MSpec.read_tags(@tags).map { |t| t.description }
+ return if desc.empty?
+
+ @tfilter = MatchFilter.new(nil, *desc)
+ end
+
+ def register
+ MSpec.register :load, self
+ end
+
+ def unregister
+ MSpec.unregister :load, self
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/actions/leakchecker.rb b/spec/mspec/lib/mspec/runner/actions/leakchecker.rb
new file mode 100644
index 0000000000..69181b71d3
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions/leakchecker.rb
@@ -0,0 +1,319 @@
+# Adapted from ruby's test/lib/leakchecker.rb.
+# Ruby's 2-clause BSDL follows.
+
+# Copyright (C) 1993-2013 Yukihiro Matsumoto. 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 AUTHOR 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 AUTHOR 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.
+
+class LeakError < StandardError
+end
+
+class LeakChecker
+ attr_reader :leaks
+
+ def initialize
+ @fd_info = find_fds
+ @tempfile_info = find_tempfiles
+ @thread_info = find_threads
+ @env_info = find_env
+ @argv_info = find_argv
+ @globals_info = find_globals
+ @encoding_info = find_encodings
+ end
+
+ def check(state)
+ @state = state
+ @leaks = []
+ check_fd_leak
+ check_tempfile_leak
+ check_thread_leak
+ check_process_leak
+ check_env
+ check_argv
+ check_globals
+ check_encodings
+ check_tracepoints
+ GC.start unless @leaks.empty?
+ @leaks.empty?
+ end
+
+ private
+ def find_fds
+ fd_dir = "/proc/self/fd"
+ if File.directory?(fd_dir)
+ fds = Dir.open(fd_dir) {|d|
+ a = d.grep(/\A\d+\z/, &:to_i)
+ if d.respond_to? :fileno
+ a -= [d.fileno]
+ end
+ a
+ }
+ fds.sort
+ else
+ []
+ end
+ end
+
+ def check_fd_leak
+ live1 = @fd_info
+ if IO.respond_to?(:console) and (m = IO.method(:console)).arity.nonzero?
+ m[:close]
+ end
+ live2 = find_fds
+ fd_closed = live1 - live2
+ if !fd_closed.empty?
+ fd_closed.each {|fd|
+ leak "Closed file descriptor: #{fd}"
+ }
+ end
+ fd_leaked = live2 - live1
+ if !fd_leaked.empty?
+ h = {}
+ ObjectSpace.each_object(IO) {|io|
+ inspect = io.inspect
+ begin
+ autoclose = io.autoclose?
+ fd = io.fileno
+ rescue IOError # closed IO object
+ next
+ end
+ (h[fd] ||= []) << [io, autoclose, inspect]
+ }
+ fd_leaked.each {|fd|
+ str = ''
+ if h[fd]
+ str << ' :'
+ h[fd].map {|io, autoclose, inspect|
+ s = ' ' + inspect
+ s << "(not-autoclose)" if !autoclose
+ s
+ }.sort.each {|s|
+ str << s
+ }
+ end
+ leak "Leaked file descriptor: #{fd}#{str}"
+ }
+ #system("lsof -p #$$") if !fd_leaked.empty?
+ h.each {|fd, list|
+ next if list.length <= 1
+ if 1 < list.count {|io, autoclose, inspect| autoclose }
+ str = list.map {|io, autoclose, inspect| " #{inspect}" + (autoclose ? "(autoclose)" : "") }.sort.join
+ leak "Multiple autoclose IO object for a file descriptor:#{str}"
+ end
+ }
+ end
+ @fd_info = live2
+ end
+
+ def extend_tempfile_counter
+ return if defined? LeakChecker::TempfileCounter
+ m = Module.new {
+ @count = 0
+ class << self
+ attr_accessor :count
+ end
+
+ def new(data)
+ LeakChecker::TempfileCounter.count += 1
+ super(data)
+ end
+ }
+ LeakChecker.const_set(:TempfileCounter, m)
+
+ class << Tempfile::Remover
+ prepend LeakChecker::TempfileCounter
+ end
+ end
+
+ def find_tempfiles(prev_count = -1)
+ return [prev_count, []] unless defined? Tempfile
+ extend_tempfile_counter
+ count = TempfileCounter.count
+ if prev_count == count
+ [prev_count, []]
+ else
+ tempfiles = ObjectSpace.each_object(Tempfile).find_all {|t| t.path }
+ [count, tempfiles]
+ end
+ end
+
+ def check_tempfile_leak
+ return false unless defined? Tempfile
+ count1, initial_tempfiles = @tempfile_info
+ count2, current_tempfiles = find_tempfiles(count1)
+ tempfiles_leaked = current_tempfiles - initial_tempfiles
+ if !tempfiles_leaked.empty?
+ list = tempfiles_leaked.map {|t| t.inspect }.sort
+ list.each {|str|
+ leak "Leaked tempfile: #{str}"
+ }
+ tempfiles_leaked.each {|t| t.close! }
+ end
+ @tempfile_info = [count2, initial_tempfiles]
+ end
+
+ def find_threads
+ Thread.list.find_all {|t|
+ t != Thread.current && t.alive? &&
+ !(t.thread_variable?(:"\0__detached_thread__") && t.thread_variable_get(:"\0__detached_thread__"))
+ }
+ end
+
+ def check_thread_leak
+ live1 = @thread_info
+ live2 = find_threads
+ thread_finished = live1 - live2
+ if !thread_finished.empty?
+ list = thread_finished.map {|t| t.inspect }.sort
+ list.each {|str|
+ leak "Finished thread: #{str}"
+ }
+ end
+ thread_leaked = live2 - live1
+ if !thread_leaked.empty?
+ list = thread_leaked.map {|t| t.inspect }.sort
+ list.each {|str|
+ leak "Leaked thread: #{str}"
+ }
+ end
+ @thread_info = live2
+ end
+
+ def check_process_leak
+ subprocesses_leaked = Process.waitall
+ subprocesses_leaked.each { |pid, status|
+ leak "Leaked subprocess: #{pid}: #{status}"
+ }
+ end
+
+ def find_env
+ ENV.to_h
+ end
+
+ def check_env
+ old_env = @env_info
+ new_env = find_env
+ return if old_env == new_env
+
+ (old_env.keys | new_env.keys).sort.each {|k|
+ if old_env.has_key?(k)
+ if new_env.has_key?(k)
+ if old_env[k] != new_env[k]
+ leak "Environment variable changed : #{k.inspect} changed : #{old_env[k].inspect} -> #{new_env[k].inspect}"
+ end
+ else
+ leak "Environment variable changed: #{k.inspect} deleted"
+ end
+ else
+ if new_env.has_key?(k)
+ leak "Environment variable changed: #{k.inspect} added"
+ else
+ flunk "unreachable"
+ end
+ end
+ }
+ @env_info = new_env
+ end
+
+ def find_argv
+ ARGV.map { |e| e.dup }
+ end
+
+ def check_argv
+ old_argv = @argv_info
+ new_argv = find_argv
+ if new_argv != old_argv
+ leak "ARGV changed: #{old_argv.inspect} to #{new_argv.inspect}"
+ @argv_info = new_argv
+ end
+ end
+
+ def find_globals
+ { verbose: $VERBOSE, debug: $DEBUG }
+ end
+
+ def check_globals
+ old_globals = @globals_info
+ new_globals = find_globals
+ if new_globals != old_globals
+ leak "Globals changed: #{old_globals.inspect} to #{new_globals.inspect}"
+ @globals_info = new_globals
+ end
+ end
+
+ def find_encodings
+ [Encoding.default_internal, Encoding.default_external]
+ end
+
+ def check_encodings
+ old_internal, old_external = @encoding_info
+ new_internal, new_external = find_encodings
+ if new_internal != old_internal
+ leak "Encoding.default_internal changed: #{old_internal.inspect} to #{new_internal.inspect}"
+ end
+ if new_external != old_external
+ leak "Encoding.default_external changed: #{old_external.inspect} to #{new_external.inspect}"
+ end
+ @encoding_info = [new_internal, new_external]
+ end
+
+ def check_tracepoints
+ ObjectSpace.each_object(TracePoint) do |tp|
+ if tp.enabled?
+ leak "TracePoint is still enabled: #{tp.inspect}"
+ end
+ end
+ end
+
+ def leak(message)
+ if @leaks.empty?
+ $stderr.puts "\n"
+ $stderr.puts @state.description
+ end
+ @leaks << message
+ $stderr.puts message
+ end
+end
+
+class LeakCheckerAction
+ def register
+ MSpec.register :start, self
+ MSpec.register :after, self
+ end
+
+ def start
+ @checker = LeakChecker.new
+ end
+
+ def after(state)
+ unless @checker.check(state)
+ leak_messages = @checker.leaks
+ location = state.description
+ if state.example
+ location = "#{location}\n#{state.example.source_location.join(':')}"
+ end
+ MSpec.protect(location) do
+ raise LeakError, leak_messages.join("\n")
+ end
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/actions/profile.rb b/spec/mspec/lib/mspec/runner/actions/profile.rb
new file mode 100644
index 0000000000..c743d6e3e8
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions/profile.rb
@@ -0,0 +1,60 @@
+class ProfileAction
+ def initialize
+ @describe_name = nil
+ @describe_time = nil
+ @describes = []
+ @its = []
+ end
+
+ def register
+ MSpec.register :enter, self
+ MSpec.register :before,self
+ MSpec.register :after, self
+ MSpec.register :finish,self
+ end
+
+ def enter(describe)
+ if @describe_time
+ @describes << [@describe_name, now - @describe_time]
+ end
+
+ @describe_name = describe
+ @describe_time = now
+ end
+
+ def before(state)
+ @it_name = state.it
+ @it_time = now
+ end
+
+ def after(state = nil)
+ @its << [@describe_name, @it_name, now - @it_time]
+ end
+
+ def finish
+ puts "\nProfiling info:"
+
+ desc = @describes.sort { |a,b| b.last <=> a.last }
+ desc.delete_if { |a| a.last <= 0.001 }
+ show = desc[0, 100]
+
+ puts "Top #{show.size} describes:"
+
+ show.each do |des, time|
+ printf "%3.3f - %s\n", time, des
+ end
+
+ its = @its.sort { |a,b| b.last <=> a.last }
+ its.delete_if { |a| a.last <= 0.001 }
+ show = its[0, 100]
+
+ puts "\nTop #{show.size} its:"
+ show.each do |des, it, time|
+ printf "%3.3f - %s %s\n", time, des, it
+ end
+ end
+
+ def now
+ Time.now.to_f
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/actions/tag.rb b/spec/mspec/lib/mspec/runner/actions/tag.rb
new file mode 100644
index 0000000000..d40d562451
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions/tag.rb
@@ -0,0 +1,133 @@
+require 'mspec/runner/actions/filter'
+
+# TagAction - Write tagged spec description string to a
+# tag file associated with each spec file.
+#
+# The action is triggered by specs whose descriptions
+# match the filter created with 'tags' and/or 'desc'
+#
+# The action fires in the :after event, after the spec
+# had been run. The action fires if the outcome of
+# running the spec matches 'outcome'.
+#
+# The arguments are:
+#
+# action: :add, :del
+# outcome: :pass, :fail, :all
+# tag: the tag to create/delete
+# comment: the comment to create
+# tags: zero or more tags to get matching
+# spec description strings from
+# desc: zero or more strings to match the
+# spec description strings
+
+class TagAction < ActionFilter
+ def initialize(action, outcome, tag, comment, tags = nil, descs = nil)
+ super tags, descs
+ @action = action
+ @outcome = outcome
+ @tag = tag
+ @comment = comment
+ @report = []
+ @exception = false
+ end
+
+ # Returns true if there are no _tag_ or _description_ filters. This
+ # means that a TagAction matches any example by default. Otherwise,
+ # returns true if either the _tag_ or the _description_ filter
+ # matches +string+.
+ def ===(string)
+ return true unless @sfilter or @tfilter
+ @sfilter === string or @tfilter === string
+ end
+
+ # Callback for the MSpec :before event. Resets the +#exception?+
+ # flag to false.
+ def before(state)
+ @exception = false
+ end
+
+ # Callback for the MSpec :exception event. Sets the +#exception?+
+ # flag to true.
+ def exception(exception)
+ @exception = true
+ end
+
+ # Callback for the MSpec :after event. Performs the tag action
+ # depending on the type of action and the outcome of evaluating
+ # the example. See +TagAction+ for a description of the actions.
+ def after(state)
+ if self === state.description and outcome?
+ tag = SpecTag.new
+ tag.tag = @tag
+ tag.comment = @comment
+ tag.description = state.description
+
+ case @action
+ when :add
+ changed = MSpec.write_tag tag
+ when :del
+ changed = MSpec.delete_tag tag
+ end
+
+ @report << state.description if changed
+ end
+ end
+
+ # Returns true if the result of evaluating the example matches
+ # the _outcome_ registered for this tag action. See +TagAction+
+ # for a description of the _outcome_ types.
+ def outcome?
+ @outcome == :all or
+ (@outcome == :pass and not exception?) or
+ (@outcome == :fail and exception?)
+ end
+
+ # Returns true if an exception was raised while evaluating the
+ # current example.
+ def exception?
+ @exception
+ end
+
+ def report
+ @report.join("\n") + "\n"
+ end
+ private :report
+
+ # Callback for the MSpec :finish event. Prints the actions
+ # performed while evaluating the examples.
+ def finish
+ case @action
+ when :add
+ if @report.empty?
+ print "\nTagAction: no specs were tagged with '#{@tag}'\n"
+ else
+ print "\nTagAction: specs tagged with '#{@tag}':\n\n"
+ print report
+ end
+ when :del
+ if @report.empty?
+ print "\nTagAction: no tags '#{@tag}' were deleted\n"
+ else
+ print "\nTagAction: tag '#{@tag}' deleted for specs:\n\n"
+ print report
+ end
+ end
+ end
+
+ def register
+ super
+ MSpec.register :before, self
+ MSpec.register :exception, self
+ MSpec.register :after, self
+ MSpec.register :finish, self
+ end
+
+ def unregister
+ super
+ MSpec.unregister :before, self
+ MSpec.unregister :exception, self
+ MSpec.unregister :after, self
+ MSpec.unregister :finish, self
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/actions/taglist.rb b/spec/mspec/lib/mspec/runner/actions/taglist.rb
new file mode 100644
index 0000000000..3097e655d5
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions/taglist.rb
@@ -0,0 +1,56 @@
+require 'mspec/runner/actions/filter'
+
+# TagListAction - prints out the descriptions for any specs
+# tagged with +tags+. If +tags+ is an empty list, prints out
+# descriptions for any specs that are tagged.
+class TagListAction
+ def initialize(tags = nil)
+ @tags = tags.nil? || tags.empty? ? nil : Array(tags)
+ @filter = nil
+ end
+
+ # Returns true. This enables us to match any tag when loading
+ # tags from the file.
+ def include?(arg)
+ true
+ end
+
+ # Returns true if any tagged descriptions matches +string+.
+ def ===(string)
+ @filter === string
+ end
+
+ # Prints a banner about matching tagged specs.
+ def start
+ if @tags
+ print "\nListing specs tagged with #{@tags.map { |t| "'#{t}'" }.join(", ") }\n\n"
+ else
+ print "\nListing all tagged specs\n\n"
+ end
+ end
+
+ # Creates a MatchFilter for specific tags or for all tags.
+ def load
+ @filter = nil
+ desc = MSpec.read_tags(@tags || self).map { |t| t.description }
+ @filter = MatchFilter.new(nil, *desc) unless desc.empty?
+ end
+
+ # Prints the spec description if it matches the filter.
+ def after(state)
+ return unless self === state.description
+ print state.description, "\n"
+ end
+
+ def register
+ MSpec.register :start, self
+ MSpec.register :load, self
+ MSpec.register :after, self
+ end
+
+ def unregister
+ MSpec.unregister :start, self
+ MSpec.unregister :load, self
+ MSpec.unregister :after, self
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/actions/tagpurge.rb b/spec/mspec/lib/mspec/runner/actions/tagpurge.rb
new file mode 100644
index 0000000000..f4587de6bc
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions/tagpurge.rb
@@ -0,0 +1,56 @@
+require 'mspec/runner/actions/filter'
+require 'mspec/runner/actions/taglist'
+
+# TagPurgeAction - removes all tags not matching any spec
+# descriptions.
+class TagPurgeAction < TagListAction
+ attr_reader :matching
+
+ def initialize
+ @matching = []
+ @filter = nil
+ @tags = nil
+ end
+
+ # Prints a banner about purging tags.
+ def start
+ print "\nRemoving tags not matching any specs\n\n"
+ end
+
+ # Creates a MatchFilter for all tags.
+ def load
+ @filter = nil
+ @tags = MSpec.read_tags self
+ desc = @tags.map { |t| t.description }
+ @filter = MatchFilter.new(nil, *desc) unless desc.empty?
+ end
+
+ # Saves any matching tags
+ def after(state)
+ @matching << state.description if self === state.description
+ end
+
+ # Rewrites any matching tags. Prints non-matching tags.
+ # Deletes the tag file if there were no tags (this cleans
+ # up empty or malformed tag files).
+ def unload
+ if @filter
+ matched = @tags.select { |t| @matching.any? { |s| s == t.description } }
+ MSpec.write_tags matched
+
+ (@tags - matched).each { |t| print t.description, "\n" }
+ else
+ MSpec.delete_tags
+ end
+ end
+
+ def register
+ super
+ MSpec.register :unload, self
+ end
+
+ def unregister
+ super
+ MSpec.unregister :unload, self
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/actions/tally.rb b/spec/mspec/lib/mspec/runner/actions/tally.rb
new file mode 100644
index 0000000000..d6ada53bab
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions/tally.rb
@@ -0,0 +1,133 @@
+class Tally
+ attr_accessor :files, :examples, :expectations, :failures, :errors, :guards, :tagged
+
+ def initialize
+ @files = @examples = @expectations = @failures = @errors = @guards = @tagged = 0
+ end
+
+ def files!(add = 1)
+ @files += add
+ end
+
+ def examples!(add = 1)
+ @examples += add
+ end
+
+ def expectations!(add = 1)
+ @expectations += add
+ end
+
+ def failures!(add = 1)
+ @failures += add
+ end
+
+ def errors!(add = 1)
+ @errors += add
+ end
+
+ def guards!(add = 1)
+ @guards += add
+ end
+
+ def tagged!(add = 1)
+ @tagged += add
+ end
+
+ def file
+ pluralize files, "file"
+ end
+
+ def example
+ pluralize examples, "example"
+ end
+
+ def expectation
+ pluralize expectations, "expectation"
+ end
+
+ def failure
+ pluralize failures, "failure"
+ end
+
+ def error
+ pluralize errors, "error"
+ end
+
+ def guard
+ pluralize guards, "guard"
+ end
+
+ def tag
+ "#{tagged} tagged"
+ end
+
+ def format
+ results = [ file, example, expectation, failure, error, tag ]
+ if [:report, :report_on, :verify].any? { |m| MSpec.mode? m }
+ results << guard
+ end
+ results.join(", ")
+ end
+
+ alias_method :to_s, :format
+
+ def pluralize(count, singular)
+ "#{count} #{singular}#{'s' unless count == 1}"
+ end
+ private :pluralize
+end
+
+class TallyAction
+ attr_reader :counter
+
+ def initialize
+ @counter = Tally.new
+ end
+
+ def register
+ MSpec.register :load, self
+ MSpec.register :exception, self
+ MSpec.register :example, self
+ MSpec.register :tagged, self
+ MSpec.register :expectation, self
+ end
+
+ def unregister
+ MSpec.unregister :load, self
+ MSpec.unregister :exception, self
+ MSpec.unregister :example, self
+ MSpec.unregister :tagged, self
+ MSpec.unregister :expectation, self
+ end
+
+ def load
+ @counter.files!
+ end
+
+ # Callback for the MSpec :expectation event. Increments the
+ # tally of expectations (e.g. #should, #should_receive, etc.).
+ def expectation(state)
+ @counter.expectations!
+ end
+
+ # Callback for the MSpec :exception event. Increments the
+ # tally of errors and failures.
+ def exception(exception)
+ exception.failure? ? @counter.failures! : @counter.errors!
+ end
+
+ # Callback for the MSpec :example event. Increments the tally
+ # of examples.
+ def example(state, block)
+ @counter.examples!
+ end
+
+ def tagged(state)
+ @counter.examples!
+ @counter.tagged!
+ end
+
+ def format
+ @counter.format
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/actions/timeout.rb b/spec/mspec/lib/mspec/runner/actions/timeout.rb
new file mode 100644
index 0000000000..543b7366d7
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions/timeout.rb
@@ -0,0 +1,93 @@
+class TimeoutAction
+ def initialize(timeout)
+ @timeout = timeout
+ @queue = Queue.new
+ @started = now
+ end
+
+ def register
+ MSpec.register :start, self
+ MSpec.register :before, self
+ MSpec.register :after, self
+ MSpec.register :finish, self
+ end
+
+ private def now
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ end
+
+ private def fetch_item
+ @queue.pop(true)
+ rescue ThreadError
+ nil
+ end
+
+ def start
+ @thread = Thread.new do
+ loop do
+ if action = fetch_item
+ action.call
+ else
+ wakeup_at = @started + @timeout
+ left = wakeup_at - now
+ sleep left if left > 0
+ Thread.pass # Let the main thread run
+
+ if @queue.empty?
+ elapsed = now - @started
+ if elapsed > @timeout
+ if @current_state
+ STDERR.puts "\nExample took longer than the configured timeout of #{@timeout}s:"
+ STDERR.puts "#{@current_state.description}"
+ else
+ STDERR.puts "\nSome code outside an example took longer than the configured timeout of #{@timeout}s"
+ end
+ STDERR.flush
+
+ show_backtraces
+ exit 2
+ end
+ end
+ end
+ end
+ end
+ end
+
+ def before(state = nil)
+ time = now
+ @queue << -> do
+ @current_state = state
+ @started = time
+ end
+ end
+
+ def after(state = nil)
+ @queue << -> do
+ @current_state = nil
+ end
+ end
+
+ def finish
+ @thread.kill
+ @thread.join
+ end
+
+ private def show_backtraces
+ if RUBY_ENGINE == 'truffleruby'
+ STDERR.puts 'Java stacktraces:'
+ Process.kill :SIGQUIT, Process.pid
+ sleep 1
+ end
+
+ STDERR.puts "\nRuby backtraces:"
+ if defined?(Truffle::Debug.show_backtraces)
+ Truffle::Debug.show_backtraces
+ else
+ Thread.list.each do |thread|
+ unless thread == Thread.current
+ STDERR.puts thread.inspect, thread.backtrace, ''
+ end
+ end
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/actions/timer.rb b/spec/mspec/lib/mspec/runner/actions/timer.rb
new file mode 100644
index 0000000000..e7ebfebe0d
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/actions/timer.rb
@@ -0,0 +1,22 @@
+class TimerAction
+ def register
+ MSpec.register :start, self
+ MSpec.register :finish, self
+ end
+
+ def start
+ @start = Time.now
+ end
+
+ def finish
+ @stop = Time.now
+ end
+
+ def elapsed
+ @stop - @start
+ end
+
+ def format
+ "Finished in %f seconds" % elapsed
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/context.rb b/spec/mspec/lib/mspec/runner/context.rb
new file mode 100644
index 0000000000..bcd83b2465
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/context.rb
@@ -0,0 +1,237 @@
+# Holds the state of the +describe+ block that is being
+# evaluated. Every example (i.e. +it+ block) is evaluated
+# in a context, which may include state set up in <tt>before
+# :each</tt> or <tt>before :all</tt> blocks.
+#
+#--
+# A note on naming: this is named _ContextState_ rather
+# than _DescribeState_ because +describe+ is the keyword
+# in the DSL for referring to the context in which an example
+# is evaluated, just as +it+ refers to the example itself.
+#++
+class ContextState
+ attr_reader :state, :parent, :parents, :children, :examples, :to_s
+
+ MOCK_VERIFY = -> { Mock.verify_count }
+ MOCK_CLEANUP = -> { Mock.cleanup }
+ EXPECTATION_MISSING = -> { raise SpecExpectationNotFoundError }
+
+ def initialize(description, options = nil)
+ raise "#describe options should be a Hash or nil" unless Hash === options or options.nil?
+ @to_s = description.to_s
+ @shared = options && options[:shared]
+
+ @parsed = false
+ @before = { :all => [], :each => [] }
+ @after = { :all => [], :each => [] }
+ @pre = {}
+ @post = {}
+ @examples = []
+ @state = nil
+ @parent = nil
+ @parents = [self]
+ @children = []
+ end
+
+ # Remove caching when a ContextState is dup'd for shared specs.
+ def initialize_copy(other)
+ @pre = {}
+ @post = {}
+ end
+
+ # Returns true if this is a shared +ContextState+. Essentially, when
+ # created with: describe "Something", :shared => true { ... }
+ def shared?
+ @shared
+ end
+
+ # Set the parent (enclosing) +ContextState+ for this state. Creates
+ # the +parents+ list.
+ def parent=(parent)
+ @description = nil
+
+ if shared?
+ @parent = nil
+ else
+ @parent = parent
+ parent.child self if parent
+
+ @parents = [self]
+ state = parent
+ while state
+ @parents.unshift state
+ state = state.parent
+ end
+ end
+ end
+
+ # Add the ContextState instance +child+ to the list of nested
+ # describe blocks.
+ def child(child)
+ @children << child
+ end
+
+ # Adds a nested ContextState in a shared ContextState to a containing
+ # ContextState.
+ #
+ # Normal adoption is from the parent's perspective. But adopt is a good
+ # verb and it's reasonable for the child to adopt the parent as well. In
+ # this case, manipulating state from inside the child avoids needlessly
+ # exposing the state to manipulate it externally in the dup. (See
+ # #it_should_behave_like)
+ def adopt(parent)
+ self.parent = parent
+
+ @examples = @examples.map do |example|
+ example = example.dup
+ example.context = self
+ example
+ end
+
+ children = @children
+ @children = []
+
+ children.each { |child| child.dup.adopt self }
+ end
+
+ # Returns a list of all before(+what+) blocks from self and any parents.
+ def pre(what)
+ @pre[what] ||= parents.inject([]) { |l, s| l.push(*s.before(what)) }
+ end
+
+ # Returns a list of all after(+what+) blocks from self and any parents.
+ # The list is in reverse order. In other words, the blocks defined in
+ # inner describes are in the list before those defined in outer describes,
+ # and in a particular describe block those defined later are in the list
+ # before those defined earlier.
+ def post(what)
+ @post[what] ||= parents.inject([]) { |l, s| l.unshift(*s.after(what)) }
+ end
+
+ # Records before(:each) and before(:all) blocks.
+ def before(what, &block)
+ return if MSpec.guarded?
+ block ? @before[what].push(block) : @before[what]
+ end
+
+ # Records after(:each) and after(:all) blocks.
+ def after(what, &block)
+ return if MSpec.guarded?
+ block ? @after[what].unshift(block) : @after[what]
+ end
+
+ # Creates an ExampleState instance for the block and stores it
+ # in a list of examples to evaluate unless the example is filtered.
+ def it(desc, &block)
+ raise "nested #it" if @state
+ example = ExampleState.new(self, desc, block)
+ MSpec.actions :add, example
+ return if MSpec.guarded?
+ @examples << example
+ end
+
+ # Evaluates the block and resets the toplevel +ContextState+ to #parent.
+ def describe(&block)
+ @parsed = protect @to_s, block, false
+ MSpec.register_current parent
+ MSpec.register_shared self if shared?
+ end
+
+ # Returns a description string generated from self and all parents
+ def description
+ @description ||= parents.map { |p| p.to_s }.compact.join(" ")
+ end
+
+ # Injects the before/after blocks and examples from the shared
+ # describe block into this +ContextState+ instance.
+ def it_should_behave_like(desc)
+ return if MSpec.guarded?
+
+ unless state = MSpec.retrieve_shared(desc)
+ raise Exception, "Unable to find shared 'describe' for #{desc}"
+ end
+
+ state.before(:all).each { |b| before :all, &b }
+ state.before(:each).each { |b| before :each, &b }
+ state.after(:each).each { |b| after :each, &b }
+ state.after(:all).each { |b| after :all, &b }
+
+ state.examples.each do |example|
+ example = example.dup
+ example.context = self
+ @examples << example
+ end
+
+ state.children.each do |child|
+ child.dup.adopt self
+ end
+ end
+
+ # Evaluates each block in +blocks+ using the +MSpec.protect+ method
+ # so that exceptions are handled and tallied. Returns true and does
+ # NOT evaluate any blocks if +check+ is true and
+ # <tt>MSpec.mode?(:pretend)</tt> is true.
+ def protect(what, blocks, check = true)
+ return true if check and MSpec.mode? :pretend
+ Array(blocks).all? { |block| MSpec.protect what, &block }
+ end
+
+ # Removes filtered examples. Returns true if there are examples
+ # left to evaluate.
+ def filter_examples
+ filtered, @examples = @examples.partition do |ex|
+ ex.filtered?
+ end
+
+ filtered.each do |ex|
+ MSpec.actions :tagged, ex
+ end
+
+ !@examples.empty?
+ end
+
+ # Evaluates the examples in a +ContextState+. Invokes the MSpec events
+ # for :enter, :before, :after, :leave.
+ def process
+ MSpec.register_current self
+
+ if @parsed and filter_examples
+ MSpec.shuffle @examples if MSpec.randomize?
+ MSpec.actions :enter, description
+
+ if protect "before :all", pre(:all)
+ @examples.each do |state|
+ MSpec.repeat do
+ @state = state
+ example = state.example
+ MSpec.actions :before, state
+
+ if protect "before :each", pre(:each)
+ MSpec.clear_expectations
+ if example
+ passed = protect nil, example
+ passed = protect nil, -> { MSpec.actions :passed, state, example } if passed
+ MSpec.actions :example, state, example
+ protect nil, EXPECTATION_MISSING if !MSpec.expectation? and passed
+ end
+ end
+ protect "after :each", post(:each)
+ protect "Mock.verify_count", MOCK_VERIFY
+
+ protect "Mock.cleanup", MOCK_CLEANUP
+ MSpec.actions :after, state
+ @state = nil
+ end
+ end
+ protect "after :all", post(:all)
+ else
+ protect "Mock.cleanup", MOCK_CLEANUP
+ end
+
+ MSpec.actions :leave
+ end
+
+ MSpec.register_current nil
+ children.each { |child| child.process }
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/evaluate.rb b/spec/mspec/lib/mspec/runner/evaluate.rb
new file mode 100644
index 0000000000..396a84c118
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/evaluate.rb
@@ -0,0 +1,54 @@
+class SpecEvaluate
+ include MSpecMatchers
+
+ def self.desc=(desc)
+ @desc = desc
+ end
+
+ def self.desc
+ @desc ||= "evaluates "
+ end
+
+ def initialize(ruby, desc)
+ @ruby = ruby.rstrip
+ @desc = desc || self.class.desc
+ end
+
+ # Formats the Ruby source code for reabable output in the -fs formatter
+ # option. If the source contains no newline characters, wraps the source in
+ # single quotes to set if off from the rest of the description string. If
+ # the source does contain newline characters, sets the indent level to four
+ # characters.
+ def format(ruby, newline = true)
+ if ruby.include?("\n")
+ lines = ruby.each_line.to_a
+ if /( *)/ =~ lines.first
+ if $1.size > 4
+ dedent = $1.size - 4
+ ruby = lines.map { |l| l[dedent..-1] }.join
+ else
+ indent = " " * (4 - $1.size)
+ ruby = lines.map { |l| "#{indent}#{l}" }.join
+ end
+ end
+ "\n#{ruby}"
+ else
+ "'#{ruby.lstrip}'"
+ end
+ end
+
+ def define(&block)
+ ruby = @ruby
+ desc = @desc
+ evaluator = self
+
+ specify "#{desc} #{format ruby}" do
+ evaluator.instance_eval(ruby)
+ evaluator.instance_eval(&block)
+ end
+ end
+end
+
+def evaluate(str, desc = nil, &block)
+ SpecEvaluate.new(str, desc).define(&block)
+end
diff --git a/spec/mspec/lib/mspec/runner/example.rb b/spec/mspec/lib/mspec/runner/example.rb
new file mode 100644
index 0000000000..0d9f0d618c
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/example.rb
@@ -0,0 +1,34 @@
+require 'mspec/runner/mspec'
+
+# Holds some of the state of the example (i.e. +it+ block) that is
+# being evaluated. See also +ContextState+.
+class ExampleState
+ attr_reader :context, :it, :example
+
+ def initialize(context, it, example = nil)
+ @context = context
+ @it = it
+ @example = example
+ end
+
+ def context=(context)
+ @description = nil
+ @context = context
+ end
+
+ def describe
+ @context.description
+ end
+
+ def description
+ @description ||= "#{describe} #{@it}"
+ end
+
+ def filtered?
+ incl = MSpec.include
+ excl = MSpec.exclude
+ included = incl.empty? || incl.any? { |f| f === description }
+ included &&= excl.empty? || !excl.any? { |f| f === description }
+ !included
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/exception.rb b/spec/mspec/lib/mspec/runner/exception.rb
new file mode 100644
index 0000000000..23375733e6
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/exception.rb
@@ -0,0 +1,54 @@
+# Initialize $MSPEC_DEBUG
+$MSPEC_DEBUG ||= false
+
+class ExceptionState
+ attr_reader :description, :describe, :it, :exception
+
+ def initialize(state, location, exception)
+ @exception = exception
+ @failure = exception.class == SpecExpectationNotMetError || exception.class == SpecExpectationNotFoundError
+
+ @description = location ? "An exception occurred during: #{location}" : ""
+ if state
+ @description += "\n" unless @description.empty?
+ @description += state.description
+ @describe = state.describe
+ @it = state.it
+ else
+ @describe = @it = ""
+ end
+ end
+
+ def failure?
+ @failure
+ end
+
+ def message
+ message = @exception.message
+ message = "<No message>" if message.empty?
+
+ if @failure
+ message
+ elsif raise_error_message = RaiseErrorMatcher::FAILURE_MESSAGE_FOR_EXCEPTION[@exception]
+ raise_error_message.join("\n")
+ else
+ "#{@exception.class}: #{message}"
+ end
+ end
+
+ def backtrace
+ @backtrace_filter ||= MSpecScript.config[:backtrace_filter] || %r{(?:/bin/mspec|/lib/mspec/)}
+
+ bt = @exception.backtrace || []
+ unless $MSPEC_DEBUG
+ # Exclude <internal: entries inside MSpec code, so only after the first ignored entry
+ first_excluded_line = bt.index { |line| @backtrace_filter =~ line }
+ if first_excluded_line
+ bt = bt[0...first_excluded_line] + bt[first_excluded_line..-1].reject { |line|
+ @backtrace_filter =~ line || /^<internal:/ =~ line
+ }
+ end
+ end
+ bt.join("\n")
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/filters.rb b/spec/mspec/lib/mspec/runner/filters.rb
new file mode 100644
index 0000000000..d0420faca6
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/filters.rb
@@ -0,0 +1,4 @@
+require 'mspec/runner/filters/match'
+require 'mspec/runner/filters/regexp'
+require 'mspec/runner/filters/tag'
+require 'mspec/runner/filters/profile'
diff --git a/spec/mspec/lib/mspec/runner/filters/match.rb b/spec/mspec/lib/mspec/runner/filters/match.rb
new file mode 100644
index 0000000000..539fd02d01
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/filters/match.rb
@@ -0,0 +1,18 @@
+class MatchFilter
+ def initialize(what, *strings)
+ @what = what
+ @strings = strings
+ end
+
+ def ===(string)
+ @strings.any? { |s| string.include?(s) }
+ end
+
+ def register
+ MSpec.register @what, self
+ end
+
+ def unregister
+ MSpec.unregister @what, self
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/filters/profile.rb b/spec/mspec/lib/mspec/runner/filters/profile.rb
new file mode 100644
index 0000000000..a59722c451
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/filters/profile.rb
@@ -0,0 +1,54 @@
+class ProfileFilter
+ def initialize(what, *files)
+ @what = what
+ @methods = load(*files)
+ @pattern = /([^ .#]+[.#])([^ ]+)/
+ end
+
+ def find(name)
+ return name if File.exist?(File.expand_path(name))
+
+ ["spec/profiles", "spec", "profiles", "."].each do |dir|
+ file = File.join dir, name
+ return file if File.exist? file
+ end
+ end
+
+ def parse(file)
+ pattern = /(\S+):\s*/
+ key = ""
+ file.inject(Hash.new { |h,k| h[k] = [] }) do |hash, line|
+ line.chomp!
+ if line[0,2] == "- "
+ hash[key] << line[2..-1].gsub(/[ '"]/, "")
+ elsif m = pattern.match(line)
+ key = m[1]
+ end
+ hash
+ end
+ end
+
+ def load(*files)
+ files.inject({}) do |hash, file|
+ next hash unless name = find(file)
+
+ File.open name, "r" do |f|
+ hash.merge parse(f)
+ end
+ end
+ end
+
+ def ===(string)
+ return false unless m = @pattern.match(string)
+ return false unless l = @methods[m[1]]
+ l.include? m[2]
+ end
+
+ def register
+ MSpec.register @what, self
+ end
+
+ def unregister
+ MSpec.unregister @what, self
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/filters/regexp.rb b/spec/mspec/lib/mspec/runner/filters/regexp.rb
new file mode 100644
index 0000000000..097ec6a755
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/filters/regexp.rb
@@ -0,0 +1,23 @@
+class RegexpFilter
+ def initialize(what, *regexps)
+ @what = what
+ @regexps = to_regexp(*regexps)
+ end
+
+ def ===(string)
+ @regexps.any? { |regexp| regexp === string }
+ end
+
+ def register
+ MSpec.register @what, self
+ end
+
+ def unregister
+ MSpec.unregister @what, self
+ end
+
+ def to_regexp(*regexps)
+ regexps.map { |str| Regexp.new str }
+ end
+ private :to_regexp
+end
diff --git a/spec/mspec/lib/mspec/runner/filters/tag.rb b/spec/mspec/lib/mspec/runner/filters/tag.rb
new file mode 100644
index 0000000000..c641c01606
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/filters/tag.rb
@@ -0,0 +1,29 @@
+class TagFilter
+ def initialize(what, *tags)
+ @what = what
+ @tags = tags
+ end
+
+ def load
+ @descriptions = MSpec.read_tags(@tags).map { |t| t.description }
+ MSpec.register @what, self
+ end
+
+ def unload
+ MSpec.unregister @what, self
+ end
+
+ def ===(string)
+ @descriptions.include?(string)
+ end
+
+ def register
+ MSpec.register :load, self
+ MSpec.register :unload, self
+ end
+
+ def unregister
+ MSpec.unregister :load, self
+ MSpec.unregister :unload, self
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters.rb b/spec/mspec/lib/mspec/runner/formatters.rb
new file mode 100644
index 0000000000..66f515ddff
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters.rb
@@ -0,0 +1,13 @@
+require 'mspec/runner/formatters/describe'
+require 'mspec/runner/formatters/dotted'
+require 'mspec/runner/formatters/file'
+require 'mspec/runner/formatters/specdoc'
+require 'mspec/runner/formatters/html'
+require 'mspec/runner/formatters/summary'
+require 'mspec/runner/formatters/unit'
+require 'mspec/runner/formatters/spinner'
+require 'mspec/runner/formatters/method'
+require 'mspec/runner/formatters/stats'
+require 'mspec/runner/formatters/yaml'
+require 'mspec/runner/formatters/profile'
+require 'mspec/runner/formatters/junit'
diff --git a/spec/mspec/lib/mspec/runner/formatters/base.rb b/spec/mspec/lib/mspec/runner/formatters/base.rb
new file mode 100644
index 0000000000..54a83c9c32
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/base.rb
@@ -0,0 +1,144 @@
+require 'mspec/expectations/expectations'
+require 'mspec/runner/actions/timer'
+require 'mspec/runner/actions/tally'
+require 'mspec/utils/options'
+
+if ENV['CHECK_LEAKS']
+ require 'mspec/runner/actions/leakchecker'
+ require 'mspec/runner/actions/constants_leak_checker'
+end
+
+class BaseFormatter
+ attr_reader :exceptions, :timer, :tally
+
+ def initialize(out = nil)
+ @current_state = nil
+ @exception = false
+ @failure = false
+ @exceptions = []
+
+ @count = 0 # For subclasses
+
+ if out
+ @out = File.open out, "w"
+ else
+ @out = $stdout
+ end
+
+ err = MSpecOptions.latest && MSpecOptions.latest.config[:error_output]
+ if err
+ @err = (err == 'stderr') ? $stderr : File.open(err, "w")
+ else
+ @err = @out
+ end
+ end
+
+ # Creates the +TimerAction+ and +TallyAction+ instances and registers them.
+ def register
+ (@timer = TimerAction.new).register
+ (@tally = TallyAction.new).register
+ @counter = @tally.counter
+
+ if ENV['CHECK_LEAKS']
+ save = ENV['CHECK_LEAKS'] == 'save'
+ LeakCheckerAction.new.register
+ ConstantsLeakCheckerAction.new(save).register
+ end
+
+ MSpec.register :abort, self
+ MSpec.register :before, self
+ MSpec.register :after, self
+ MSpec.register :exception, self
+ MSpec.register :finish, self
+ end
+
+ def abort
+ if @current_state
+ puts "\naborting example: #{@current_state.description}"
+ end
+ end
+
+ # Returns true if any exception is raised while running
+ # an example. This flag is reset before each example
+ # is evaluated.
+ def exception?
+ @exception
+ end
+
+ # Returns true if all exceptions during the evaluation
+ # of an example are failures rather than errors. See
+ # <tt>ExceptionState#failure</tt>. This flag is reset
+ # before each example is evaluated.
+ def failure?
+ @failure
+ end
+
+ # Callback for the MSpec :before event. Resets the
+ # +#exception?+ and +#failure+ flags.
+ def before(state = nil)
+ @current_state = state
+ @failure = @exception = false
+ end
+
+ # Callback for the MSpec :exception event. Stores the
+ # +ExceptionState+ object to generate the list of backtraces
+ # after all the specs are run. Also updates the internal
+ # +#exception?+ and +#failure?+ flags.
+ def exception(exception)
+ @count += 1
+ @failure = @exception ? @failure && exception.failure? : exception.failure?
+ @exception = true
+ @exceptions << exception
+ end
+
+ # Callback for the MSpec :after event.
+ def after(state = nil)
+ @current_state = nil
+ end
+
+ # Callback for the MSpec :start event. Calls :after event.
+ # Defined here, in the base class, and used by MultiFormatter.
+ def start
+ after
+ end
+
+ # Callback for the MSpec :unload event. Calls :after event.
+ # Defined here, in the base class, and used by MultiFormatter.
+ def unload
+ after
+ end
+
+ # Callback for the MSpec :finish event. Prints a description
+ # and backtrace for every exception that occurred while
+ # evaluating the examples.
+ def finish
+ print "\n"
+
+ if MSpecOptions.latest && MSpecOptions.latest.config[:print_skips]
+ print "\nSkips:\n" unless MSpec.skips.empty?
+ MSpec.skips.each do |skip, block|
+ print "#{skip.message} in #{(block.source_location || ['?']).join(':')}\n"
+ end
+ end
+
+ count = 0
+ @exceptions.each do |exc|
+ count += 1
+ print_exception(exc, count)
+ end
+ print "\n#{@timer.format}\n\n#{@tally.format}\n"
+ end
+
+ def print_exception(exc, count)
+ outcome = exc.failure? ? "FAILED" : "ERROR"
+ @err.print "\n#{count})\n#{exc.description} #{outcome}\n"
+ @err.print exc.message, "\n"
+ @err.print exc.backtrace, "\n"
+ end
+
+ # A convenience method to allow printing to different outputs.
+ def print(*args)
+ @out.print(*args)
+ @out.flush
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/describe.rb b/spec/mspec/lib/mspec/runner/formatters/describe.rb
new file mode 100644
index 0000000000..fc4122d13b
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/describe.rb
@@ -0,0 +1,23 @@
+require 'mspec/runner/formatters/dotted'
+
+class DescribeFormatter < DottedFormatter
+ # Callback for the MSpec :finish event. Prints a summary of
+ # the number of errors and failures for each +describe+ block.
+ def finish
+ describes = Hash.new { |h,k| h[k] = Tally.new }
+
+ @exceptions.each do |exc|
+ desc = describes[exc.describe]
+ exc.failure? ? desc.failures! : desc.errors!
+ end
+
+ print "\n"
+ describes.each do |d, t|
+ text = d.size > 40 ? "#{d[0,37]}..." : d.ljust(40)
+ print "\n#{text} #{t.failure}, #{t.error}"
+ end
+ print "\n" unless describes.empty?
+
+ print "\n#{@timer.format}\n\n#{@tally.format}\n"
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/dotted.rb b/spec/mspec/lib/mspec/runner/formatters/dotted.rb
new file mode 100644
index 0000000000..672cdf81dc
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/dotted.rb
@@ -0,0 +1,23 @@
+require 'mspec/runner/formatters/base'
+
+class DottedFormatter < BaseFormatter
+ def register
+ super
+ MSpec.register :after, self
+ end
+
+ # Callback for the MSpec :after event. Prints an indicator
+ # for the result of evaluating this example as follows:
+ # . = No failure or error
+ # F = An SpecExpectationNotMetError was raised
+ # E = Any exception other than SpecExpectationNotMetError
+ def after(state = nil)
+ super(state)
+
+ if exception?
+ print failure? ? "F" : "E"
+ else
+ print "."
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/file.rb b/spec/mspec/lib/mspec/runner/formatters/file.rb
new file mode 100644
index 0000000000..65cfb1f75b
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/file.rb
@@ -0,0 +1,24 @@
+require 'mspec/runner/formatters/dotted'
+
+class FileFormatter < DottedFormatter
+ # Unregisters DottedFormatter#before, #after methods and
+ # registers #load, #unload, which perform the same duties
+ # as #before, #after in DottedFormatter.
+ def register
+ super
+
+ MSpec.unregister :before, self
+ MSpec.unregister :after, self
+
+ MSpec.register :load, self
+ MSpec.register :unload, self
+ end
+
+ def load(state = nil)
+ before(state)
+ end
+
+ def unload(state = nil)
+ after(state)
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/html.rb b/spec/mspec/lib/mspec/runner/formatters/html.rb
new file mode 100644
index 0000000000..e37e89a088
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/html.rb
@@ -0,0 +1,81 @@
+require 'mspec/runner/formatters/base'
+
+class HtmlFormatter < BaseFormatter
+ def register
+ super
+ MSpec.register :start, self
+ MSpec.register :enter, self
+ MSpec.register :leave, self
+ end
+
+ def start
+ print <<-EOH
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<title>Spec Output For #{RUBY_ENGINE} (#{RUBY_VERSION})</title>
+<style type="text/css">
+ul {
+ list-style: none;
+}
+.fail {
+ color: red;
+}
+.pass {
+ color: green;
+}
+#details :target {
+ background-color: #ffffe0;
+}
+</style>
+</head>
+<body>
+EOH
+ end
+
+ def enter(describe)
+ print "<div><p>#{describe}</p>\n<ul>\n"
+ end
+
+ def leave
+ print "</ul>\n</div>\n"
+ end
+
+ def exception(exception)
+ super(exception)
+ outcome = exception.failure? ? "FAILED" : "ERROR"
+ print %[<li class="fail">- #{exception.it} (<a href="#details-#{@count}">]
+ print %[#{outcome} - #{@count}</a>)</li>\n]
+ end
+
+ def after(state = nil)
+ super(state)
+ print %[<li class="pass">- #{state.it}</li>\n] unless exception?
+ end
+
+ def finish
+ success = @exceptions.empty?
+ unless success
+ print "<hr>\n"
+ print %[<ol id="details">]
+ count = 0
+ @exceptions.each do |exc|
+ outcome = exc.failure? ? "FAILED" : "ERROR"
+ print %[\n<li id="details-#{count += 1}"><p>#{escape(exc.description)} #{outcome}</p>\n<p>]
+ print escape(exc.message)
+ print "</p>\n<pre>\n"
+ print escape(exc.backtrace)
+ print "</pre>\n</li>\n"
+ end
+ print "</ol>\n"
+ end
+ print %[<p>#{@timer.format}</p>\n]
+ print %[<p class="#{success ? "pass" : "fail"}">#{@tally.format}</p>\n]
+ print "</body>\n</html>\n"
+ end
+
+ def escape(string)
+ string.gsub("&", "&nbsp;").gsub("<", "&lt;").gsub(">", "&gt;")
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/junit.rb b/spec/mspec/lib/mspec/runner/formatters/junit.rb
new file mode 100644
index 0000000000..6351ccbce9
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/junit.rb
@@ -0,0 +1,87 @@
+require 'mspec/runner/formatters/yaml'
+
+class JUnitFormatter < YamlFormatter
+ def initialize(out = nil)
+ super(out)
+ @tests = []
+ end
+
+ def after(state = nil)
+ super(state)
+ @tests << {:test => state, :exception => false} unless exception?
+ end
+
+ def exception(exception)
+ super(exception)
+ @tests << {:test => exception, :exception => true}
+ end
+
+ def finish
+ switch
+
+ time = @timer.elapsed
+ tests = @tally.counter.examples
+ errors = @tally.counter.errors
+ failures = @tally.counter.failures
+
+ print <<-XML
+
+<?xml version="1.0" encoding="UTF-8" ?>
+ <testsuites
+ testCount="#{tests}"
+ errorCount="#{errors}"
+ failureCount="#{failures}"
+ timeCount="#{time}" time="#{time}">
+ <testsuite
+ tests="#{tests}"
+ errors="#{errors}"
+ failures="#{failures}"
+ time="#{time}"
+ name="Spec Output For #{::RUBY_ENGINE} (#{::RUBY_VERSION})">
+ XML
+ @tests.each do |h|
+ description = encode_for_xml h[:test].description
+
+ print <<-XML
+ <testcase classname="Spec" name="#{description}" time="0.0">
+ XML
+ if h[:exception]
+ outcome = h[:test].failure? ? "failure" : "error"
+ message = encode_for_xml h[:test].message
+ backtrace = encode_for_xml h[:test].backtrace
+ print <<-XML
+ <#{outcome} message="error in #{description}" type="#{outcome}">
+ #{message}
+ #{backtrace}
+ </#{outcome}>
+ XML
+ end
+ print <<-XML
+ </testcase>
+ XML
+ end
+
+ print <<-XML
+ </testsuite>
+ </testsuites>
+ XML
+ end
+
+ private
+ LT = "&lt;"
+ GT = "&gt;"
+ QU = "&quot;"
+ AP = "&apos;"
+ AM = "&amp;"
+ TARGET_ENCODING = "ISO-8859-1"
+
+ def encode_for_xml(str)
+ encode_as_latin1(str).gsub("<", LT).gsub(">", GT).
+ gsub('"', QU).gsub("'", AP).gsub("&", AM).
+ tr("\x00-\x08", "?")
+ end
+
+ def encode_as_latin1(str)
+ str.encode(TARGET_ENCODING, :undef => :replace, :invalid => :replace)
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/method.rb b/spec/mspec/lib/mspec/runner/formatters/method.rb
new file mode 100644
index 0000000000..925858c845
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/method.rb
@@ -0,0 +1,95 @@
+require 'mspec/runner/formatters/base'
+
+class MethodFormatter < BaseFormatter
+ attr_accessor :methods
+
+ def initialize(out = nil)
+ super(out)
+ @methods = Hash.new do |h, k|
+ h[k] = {
+ examples: 0,
+ expectations: 0,
+ failures: 0,
+ errors: 0,
+ exceptions: []
+ }
+ end
+ end
+
+ # Returns the type of method as a "class", "instance",
+ # or "unknown".
+ def method_type(sep)
+ case sep
+ when '.', '::'
+ "class"
+ when '#'
+ "instance"
+ else
+ "unknown"
+ end
+ end
+
+ # Callback for the MSpec :before event. Parses the
+ # describe string into class and method if possible.
+ # Resets the tallies so the counts are only for this
+ # example.
+ def before(state)
+ super(state)
+
+ # The pattern for a method name is not correctly
+ # restrictive but it is simplistic and useful
+ # for our purpose.
+ /^([A-Za-z_]+\w*)(\.|#|::)([^ ]+)/ =~ state.describe
+ @key = $1 && $2 && $3 ? "#{$1}#{$2}#{$3}" : state.describe
+
+ unless methods.key? @key
+ h = methods[@key]
+ h[:class] = "#{$1}"
+ h[:method] = "#{$3}"
+ h[:type] = method_type $2
+ h[:description] = state.description
+ end
+
+ tally.counter.examples = 0
+ tally.counter.expectations = 0
+ tally.counter.failures = 0
+ tally.counter.errors = 0
+
+ @exceptions = []
+ end
+
+ # Callback for the MSpec :after event. Sets or adds to
+ # tallies for the example block.
+ def after(state = nil)
+ super(state)
+
+ h = methods[@key]
+ h[:examples] += tally.counter.examples
+ h[:expectations] += tally.counter.expectations
+ h[:failures] += tally.counter.failures
+ h[:errors] += tally.counter.errors
+ @exceptions.each do |exc|
+ h[:exceptions] << "#{exc.message}\n#{exc.backtrace}\n"
+ end
+ end
+
+ # Callback for the MSpec :finish event. Prints out the
+ # summary information in YAML format for all the methods.
+ def finish
+ print "---\n"
+
+ methods.each do |key, hash|
+ print key.inspect, ":\n"
+ print " class: ", hash[:class].inspect, "\n"
+ print " method: ", hash[:method].inspect, "\n"
+ print " type: ", hash[:type], "\n"
+ print " description: ", hash[:description].inspect, "\n"
+ print " examples: ", hash[:examples], "\n"
+ print " expectations: ", hash[:expectations], "\n"
+ print " failures: ", hash[:failures], "\n"
+ print " errors: ", hash[:errors], "\n"
+ print " exceptions:\n"
+ hash[:exceptions].each { |exc| print " - ", exc.inspect, "\n" }
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/multi.rb b/spec/mspec/lib/mspec/runner/formatters/multi.rb
new file mode 100644
index 0000000000..a723ae8eb9
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/multi.rb
@@ -0,0 +1,47 @@
+module MultiFormatter
+ def self.extend_object(obj)
+ super
+ obj.multi_initialize
+ end
+
+ def multi_initialize
+ @tally = TallyAction.new
+ @counter = @tally.counter
+ @timer = TimerAction.new
+ @timer.start
+ end
+
+ def register
+ super
+
+ MSpec.register :start, self
+ MSpec.register :unload, self
+ MSpec.unregister :before, self
+ end
+
+ def aggregate_results(files)
+ require 'yaml'
+
+ @timer.finish
+ @exceptions = []
+
+ files.each do |file|
+ contents = File.read(file)
+ d = YAML.load(contents)
+ File.delete file
+
+ if d # The file might be empty if the child process died
+ @exceptions += Array(d['exceptions'])
+ @counter.files! d['files']
+ @counter.examples! d['examples']
+ @counter.expectations! d['expectations']
+ @counter.errors! d['errors']
+ @counter.failures! d['failures']
+ end
+ end
+ end
+
+ def print_exception(exc, count)
+ print "\n#{count})\n#{exc}\n"
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/profile.rb b/spec/mspec/lib/mspec/runner/formatters/profile.rb
new file mode 100644
index 0000000000..38ef5b12ed
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/profile.rb
@@ -0,0 +1,18 @@
+require 'mspec/runner/formatters/dotted'
+require 'mspec/runner/actions/profile'
+
+class ProfileFormatter < DottedFormatter
+ def initialize(out = nil)
+ super(out)
+
+ @describe_name = nil
+ @describe_time = nil
+ @describes = []
+ @its = []
+ end
+
+ def register
+ (@profile = ProfileAction.new).register
+ super
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/specdoc.rb b/spec/mspec/lib/mspec/runner/formatters/specdoc.rb
new file mode 100644
index 0000000000..d3a5c3d729
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/specdoc.rb
@@ -0,0 +1,41 @@
+require 'mspec/runner/formatters/base'
+
+class SpecdocFormatter < BaseFormatter
+ def register
+ super
+ MSpec.register :enter, self
+ end
+
+ # Callback for the MSpec :enter event. Prints the
+ # +describe+ block string.
+ def enter(describe)
+ print "\n#{describe}\n"
+ end
+
+ # Callback for the MSpec :before event. Prints the
+ # +it+ block string.
+ def before(state)
+ super(state)
+ print "- #{state.it}"
+ end
+
+ # Callback for the MSpec :exception event. Prints
+ # either 'ERROR - X' or 'FAILED - X' where _X_ is
+ # the sequential number of the exception raised. If
+ # there has already been an exception raised while
+ # evaluating this example, it prints another +it+
+ # block description string so that each description
+ # string has an associated 'ERROR' or 'FAILED'
+ def exception(exception)
+ print "\n- #{exception.it}" if exception?
+ super(exception)
+ print " (#{exception.failure? ? 'FAILED' : 'ERROR'} - #{@count})"
+ end
+
+ # Callback for the MSpec :after event. Prints a
+ # newline to finish the description string output.
+ def after(state = nil)
+ super(state)
+ print "\n"
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/spinner.rb b/spec/mspec/lib/mspec/runner/formatters/spinner.rb
new file mode 100644
index 0000000000..817d8c02be
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/spinner.rb
@@ -0,0 +1,111 @@
+require 'mspec/runner/formatters/base'
+
+class SpinnerFormatter < BaseFormatter
+ attr_reader :length
+
+ Spins = %w!| / - \\!
+ HOUR = 3600
+ MIN = 60
+
+ def initialize(out = nil)
+ super(nil)
+
+ @which = 0
+ @loaded = 0
+ self.length = 40
+ @percent = 0
+ @start = Time.now
+
+ term = ENV['TERM']
+ @color = (term != "dumb")
+ @fail_color = "32"
+ @error_color = "32"
+ end
+
+ def register
+ super
+
+ MSpec.register :start, self
+ MSpec.register :unload, self
+ end
+
+ def length=(length)
+ @length = length
+ @ratio = 100.0 / length
+ @position = length / 2 - 2
+ end
+
+ def compute_etr
+ return @etr = "00:00:00" if @percent == 0
+ elapsed = Time.now - @start
+ remain = (100 * elapsed / @percent) - elapsed
+
+ hour = remain >= HOUR ? (remain / HOUR).to_i : 0
+ remain -= hour * HOUR
+ min = remain >= MIN ? (remain / MIN).to_i : 0
+ sec = remain - min * MIN
+
+ @etr = "%02d:%02d:%02d" % [hour, min, sec]
+ end
+
+ def compute_percentage
+ @percent = @loaded * 100 / @total
+ bar = ("=" * (@percent / @ratio)).ljust @length
+ label = "%d%%" % @percent
+ bar[@position, label.size] = label
+ @bar = bar
+ end
+
+ def compute_progress
+ compute_percentage
+ compute_etr
+ end
+
+ def progress_line
+ @which = (@which + 1) % Spins.size
+ data = [Spins[@which], @bar, @etr, @counter.failures, @counter.errors]
+ if @color
+ "\r[%s | %s | %s] \e[0;#{@fail_color}m%6dF \e[0;#{@error_color}m%6dE\e[0m " % data
+ else
+ "\r[%s | %s | %s] %6dF %6dE " % data
+ end
+ end
+
+ def clear_progress_line
+ print "\r#{' '*progress_line.length}"
+ end
+
+ # Callback for the MSpec :start event. Stores the total
+ # number of files that will be processed.
+ def start
+ @total = MSpec.files_array.size
+ compute_progress
+ print progress_line
+ end
+
+ # Callback for the MSpec :unload event. Increments the number
+ # of files that have been run.
+ def unload
+ @loaded += 1
+ compute_progress
+ print progress_line
+ end
+
+ # Callback for the MSpec :exception event. Changes the color
+ # used to display the tally of errors and failures
+ def exception(exception)
+ super
+ @fail_color = "31" if exception.failure?
+ @error_color = "33" unless exception.failure?
+
+ clear_progress_line
+ print_exception(exception, @count)
+ exceptions.clear
+ end
+
+ # Callback for the MSpec :after event. Updates the spinner.
+ def after(state = nil)
+ super(state)
+ print progress_line
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/stats.rb b/spec/mspec/lib/mspec/runner/formatters/stats.rb
new file mode 100644
index 0000000000..8cff96d145
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/stats.rb
@@ -0,0 +1,57 @@
+require 'mspec/runner/formatters/base'
+
+class StatsPerFileFormatter < BaseFormatter
+ def initialize(out = nil)
+ super(out)
+ @data = {}
+ @root = File.expand_path(MSpecScript.get(:prefix) || '.')
+ end
+
+ def register
+ super
+ MSpec.register :load, self
+ MSpec.register :unload, self
+ end
+
+ # Resets the tallies so the counts are only for this file.
+ def load
+ tally.counter.examples = 0
+ tally.counter.errors = 0
+ tally.counter.failures = 0
+ tally.counter.tagged = 0
+ end
+
+ def unload
+ file = format_file MSpec.file
+
+ raise if @data.key?(file)
+ @data[file] = {
+ examples: tally.counter.examples,
+ errors: tally.counter.errors,
+ failures: tally.counter.failures,
+ tagged: tally.counter.tagged,
+ }
+ end
+
+ def finish
+ width = @data.keys.max_by(&:size).size
+ f = "%3d"
+ @data.each_pair do |file, data|
+ total = data[:examples]
+ passing = total - data[:errors] - data[:failures] - data[:tagged]
+ puts "#{file.ljust(width)} #{f % passing}/#{f % total}"
+ end
+
+ require 'yaml'
+ yaml = YAML.dump(@data)
+ File.write "results-#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}.yml", yaml
+ end
+
+ private def format_file(file)
+ if file.start_with?(@root)
+ file[@root.size+1..-1]
+ else
+ raise file
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/summary.rb b/spec/mspec/lib/mspec/runner/formatters/summary.rb
new file mode 100644
index 0000000000..41819d2158
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/summary.rb
@@ -0,0 +1,4 @@
+require 'mspec/runner/formatters/base'
+
+class SummaryFormatter < BaseFormatter
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/unit.rb b/spec/mspec/lib/mspec/runner/formatters/unit.rb
new file mode 100644
index 0000000000..d03ae79e9f
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/unit.rb
@@ -0,0 +1,20 @@
+require 'mspec/runner/formatters/dotted'
+
+class UnitdiffFormatter < DottedFormatter
+ def finish
+ print "\n\n#{@timer.format}\n"
+ count = 0
+ @exceptions.each do |exc|
+ outcome = exc.failure? ? "FAILED" : "ERROR"
+ print "\n#{count += 1})\n#{exc.description} #{outcome}\n"
+ print exc.message, ":\n"
+ print exc.backtrace, "\n"
+ end
+ print "\n#{@tally.format}\n"
+ end
+
+ def backtrace(exc)
+ exc.backtrace && exc.backtrace.join("\n")
+ end
+ private :backtrace
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/yaml.rb b/spec/mspec/lib/mspec/runner/formatters/yaml.rb
new file mode 100644
index 0000000000..6c05cc902f
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/yaml.rb
@@ -0,0 +1,38 @@
+require 'mspec/runner/formatters/base'
+
+class YamlFormatter < BaseFormatter
+ def initialize(out = nil)
+ super(nil)
+
+ if out.nil?
+ @finish = $stdout
+ else
+ @finish = File.open out, "w"
+ end
+ end
+
+ def switch
+ @out = @finish
+ end
+
+ def finish
+ switch
+
+ print "---\n"
+ print "exceptions:\n"
+ @exceptions.each do |exc|
+ outcome = exc.failure? ? "FAILED" : "ERROR"
+ str = "#{exc.description} #{outcome}\n"
+ str << exc.message << "\n" << exc.backtrace
+ print "- ", str.inspect, "\n"
+ end
+
+ print "time: ", @timer.elapsed, "\n"
+ print "files: ", @tally.counter.files, "\n"
+ print "examples: ", @tally.counter.examples, "\n"
+ print "expectations: ", @tally.counter.expectations, "\n"
+ print "failures: ", @tally.counter.failures, "\n"
+ print "errors: ", @tally.counter.errors, "\n"
+ print "tagged: ", @tally.counter.tagged, "\n"
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/mspec.rb b/spec/mspec/lib/mspec/runner/mspec.rb
new file mode 100644
index 0000000000..889e085175
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/mspec.rb
@@ -0,0 +1,422 @@
+require 'mspec/runner/context'
+require 'mspec/runner/exception'
+require 'mspec/runner/tag'
+
+module MSpec
+end
+
+class MSpecEnv
+ include MSpec
+end
+
+module MSpec
+ @exit = nil
+ @abort = nil
+ @start = nil
+ @enter = nil
+ @before = nil
+ @add = nil
+ @after = nil
+ @leave = nil
+ @finish = nil
+ @exclude = []
+ @include = []
+ @leave = nil
+ @load = nil
+ @unload = nil
+ @tagged = nil
+ @current = nil
+ @passed = nil
+ @example = nil
+ @modes = []
+ @shared = {}
+ @guarded = []
+ @features = {}
+ @exception = nil
+ @randomize = false
+ @repeat = 1
+ @expectation = nil
+ @expectations = false
+ @skips = []
+
+ class << self
+ attr_reader :file, :include, :exclude, :skips
+ attr_writer :repeat, :randomize
+ attr_accessor :formatter
+ end
+
+ def self.describe(description, options = nil, &block)
+ state = ContextState.new description, options
+ state.parent = current
+
+ MSpec.register_current state
+ state.describe(&block)
+
+ state.process unless state.shared? or current
+ end
+
+ def self.process
+ STDOUT.puts RUBY_DESCRIPTION
+ STDOUT.flush
+
+ actions :start
+ files
+ actions :finish
+ end
+
+ def self.files_array
+ @files
+ end
+
+ def self.each_file(&block)
+ if ENV["MSPEC_MULTI"]
+ while file = STDIN.gets
+ file = file.chomp
+ return if file == "QUIT"
+ yield file
+ begin
+ STDOUT.print "."
+ STDOUT.flush
+ rescue Errno::EPIPE
+ # The parent died
+ exit 1
+ end
+ end
+ # The parent closed the connection without QUIT
+ abort "the parent did not send QUIT"
+ else
+ return unless files = @files
+ shuffle files if randomize?
+ files.each(&block)
+ end
+ end
+
+ def self.files
+ each_file do |file|
+ setup_env
+ @file = file
+ actions :load
+ protect("loading #{file}") { Kernel.load file }
+ actions :unload
+ raise "#{file} was executed but did not reset the current example: #{@current}" if @current
+ end
+ end
+
+ def self.setup_env
+ @env = MSpecEnv.new
+ end
+
+ def self.actions(action, *args)
+ actions = retrieve(action)
+ actions.each { |obj| obj.send action, *args } if actions
+ end
+
+ def self.protect(location, &block)
+ begin
+ @env.instance_exec(&block)
+ return true
+ rescue SystemExit => e
+ raise e
+ rescue SkippedSpecError => e
+ @skips << [e, block]
+ return false
+ rescue Object => exc
+ register_exit 1
+ actions :exception, ExceptionState.new(current && current.state, location, exc)
+ return false
+ end
+ end
+
+ # Guards can be nested, so a stack is necessary to know when we have
+ # exited the toplevel guard.
+ def self.guard
+ @guarded << true
+ end
+
+ def self.unguard
+ @guarded.pop
+ end
+
+ def self.guarded?
+ !@guarded.empty?
+ end
+
+ # Sets the toplevel ContextState to +state+.
+ def self.register_current(state)
+ @current = state
+ end
+
+ # Sets the toplevel ContextState to +nil+.
+ def self.clear_current
+ @current = nil
+ end
+
+ # Returns the toplevel ContextState.
+ def self.current
+ @current
+ end
+
+ # Stores the shared ContextState keyed by description.
+ def self.register_shared(state)
+ name = state.to_s
+ raise "duplicated shared #describe: #{name}" if @shared.key?(name)
+ @shared[name] = state
+ end
+
+ # Returns the shared ContextState matching description.
+ def self.retrieve_shared(desc)
+ @shared[desc.to_s]
+ end
+
+ # Stores the exit code used by the runner scripts.
+ def self.register_exit(code)
+ @exit = code
+ end
+
+ # Retrieves the stored exit code.
+ def self.exit_code
+ @exit.to_i
+ end
+
+ # Stores the list of files to be evaluated.
+ def self.register_files(files)
+ @files = files
+ end
+
+ # Stores one or more substitution patterns for transforming
+ # a spec filename into a tags filename, where each pattern
+ # has the form:
+ #
+ # [Regexp, String]
+ #
+ # See also +tags_file+.
+ def self.register_tags_patterns(patterns)
+ @tags_patterns = patterns
+ end
+
+ # Registers an operating mode. Modes recognized by MSpec:
+ #
+ # :pretend - actions execute but specs are not run
+ # :verify - specs are run despite guards and the result is
+ # verified to match the expectation of the guard
+ # :report - specs that are guarded are reported
+ # :unguarded - all guards are forced off
+ def self.register_mode(mode)
+ modes = @modes
+ modes << mode unless modes.include? mode
+ end
+
+ # Clears all registered modes.
+ def self.clear_modes
+ @modes = []
+ end
+
+ # Returns +true+ if +mode+ is registered.
+ def self.mode?(mode)
+ @modes.include? mode
+ end
+
+ def self.enable_feature(feature)
+ @features[feature] = true
+ end
+
+ def self.disable_feature(feature)
+ @features[feature] = false
+ end
+
+ def self.feature_enabled?(feature)
+ @features[feature] || false
+ end
+
+ def self.retrieve(symbol)
+ instance_variable_get :"@#{symbol}"
+ end
+
+ def self.store(symbol, value)
+ instance_variable_set :"@#{symbol}", value
+ end
+
+ # This method is used for registering actions that are
+ # run at particular points in the spec cycle:
+ # :start before any specs are run
+ # :load before a spec file is loaded
+ # :enter before a describe block is run
+ # :before before a single spec is run
+ # :add while a describe block is adding examples to run later
+ # :expectation before a 'should', 'should_receive', etc.
+ # :passed after an example block is run and passes, passed the block, run before :example action
+ # :example after an example block is run, passed the block
+ # :exception after an exception is rescued
+ # :after after a single spec is run
+ # :leave after a describe block is run
+ # :unload after a spec file is run
+ # :finish after all specs are run
+ #
+ # Objects registered as actions above should respond to
+ # a method of the same name. For example, if an object
+ # is registered as a :start action, it should respond to
+ # a #start method call.
+ #
+ # Additionally, there are two "action" lists for
+ # filtering specs:
+ # :include return true if the spec should be run
+ # :exclude return true if the spec should NOT be run
+ #
+ def self.register(symbol, action)
+ unless value = retrieve(symbol)
+ value = store symbol, []
+ end
+ value << action unless value.include? action
+ end
+
+ def self.unregister(symbol, action)
+ if value = retrieve(symbol)
+ value.delete action
+ end
+ end
+
+ def self.randomize?
+ @randomize
+ end
+
+ def self.repeat
+ if @repeat == 1
+ yield
+ else
+ @repeat.times do
+ yield
+ end
+ end
+ end
+
+ def self.shuffle(ary)
+ return if ary.empty?
+
+ size = ary.size
+ size.times do |i|
+ r = rand(size - i - 1)
+ ary[i], ary[r] = ary[r], ary[i]
+ end
+ end
+
+ # Records that an expectation has been encountered in an example.
+ def self.expectation
+ @expectations = true
+ end
+
+ # Returns true if an expectation has been encountered
+ def self.expectation?
+ @expectations
+ end
+
+ # Resets the flag that an expectation has been encountered in an example.
+ def self.clear_expectations
+ @expectations = false
+ end
+
+ # Transforms a spec filename into a tags filename by applying each
+ # substitution pattern in :tags_pattern. The default patterns are:
+ #
+ # [%r(/spec/), '/spec/tags/'], [/_spec.rb$/, '_tags.txt']
+ #
+ # which will perform the following transformation:
+ #
+ # path/to/spec/class/method_spec.rb => path/to/spec/tags/class/method_tags.txt
+ #
+ # See also +register_tags_patterns+.
+ def self.tags_file
+ patterns = @tags_patterns ||
+ [[%r(spec/), 'spec/tags/'], [/_spec.rb$/, '_tags.txt']]
+ patterns.inject(@file.dup) do |file, pattern|
+ file.gsub(*pattern)
+ end
+ end
+
+ # Returns a list of tags matching any tag string in +keys+ based
+ # on the return value of <tt>keys.include?("tag_name")</tt>
+ def self.read_tags(keys)
+ tags = []
+ file = tags_file
+ if File.exist? file
+ File.open(file, "r:utf-8") do |f|
+ f.each_line do |line|
+ line.chomp!
+ next if line.empty?
+ tag = SpecTag.new line
+ tags << tag if keys.include? tag.tag
+ end
+ end
+ end
+ tags
+ end
+
+ def self.make_tag_dir(path)
+ parent = File.dirname(path)
+ return if File.exist? parent
+ begin
+ Dir.mkdir(parent)
+ rescue SystemCallError
+ make_tag_dir(parent)
+ Dir.mkdir(parent)
+ end
+ end
+
+ # Writes each tag in +tags+ to the tag file. Overwrites the
+ # tag file if it exists.
+ def self.write_tags(tags)
+ file = tags_file
+ make_tag_dir(file)
+ File.open(file, "w:utf-8") do |f|
+ tags.each { |t| f.puts t }
+ end
+ end
+
+ # Writes +tag+ to the tag file if it does not already exist.
+ # Returns +true+ if the tag is written, +false+ otherwise.
+ def self.write_tag(tag)
+ tags = read_tags([tag.tag])
+ tags.each do |t|
+ if t.tag == tag.tag and t.description == tag.description
+ return false
+ end
+ end
+
+ file = tags_file
+ make_tag_dir(file)
+ File.open(file, "a:utf-8") { |f| f.puts tag.to_s }
+ return true
+ end
+
+ # Deletes +tag+ from the tag file if it exists. Returns +true+
+ # if the tag is deleted, +false+ otherwise. Deletes the tag
+ # file if it is empty.
+ def self.delete_tag(tag)
+ deleted = false
+ desc = tag.escape(tag.description)
+ file = tags_file
+ if File.exist? file
+ lines = IO.readlines(file)
+ File.open(file, "w:utf-8") do |f|
+ lines.each do |line|
+ line = line.chomp
+ if line.start_with?(tag.tag) and line.end_with?(desc)
+ deleted = true
+ else
+ f.puts line unless line.empty?
+ end
+ end
+ end
+ File.delete file unless File.size? file
+ end
+ return deleted
+ end
+
+ # Removes the tag file associated with a spec file.
+ def self.delete_tags
+ file = tags_file
+ File.delete file if File.exist? file
+ end
+
+ # Initialize @env
+ setup_env
+end
diff --git a/spec/mspec/lib/mspec/runner/object.rb b/spec/mspec/lib/mspec/runner/object.rb
new file mode 100644
index 0000000000..58d98cc4df
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/object.rb
@@ -0,0 +1,26 @@
+class Object
+ private def before(at = :each, &block)
+ MSpec.current.before at, &block
+ end
+
+ private def after(at = :each, &block)
+ MSpec.current.after at, &block
+ end
+
+ private def describe(description, options = nil, &block)
+ MSpec.describe description, options, &block
+ end
+
+ private def it(desc, &block)
+ MSpec.current.it desc, &block
+ end
+
+ private def it_should_behave_like(desc)
+ MSpec.current.it_should_behave_like desc
+ end
+
+ alias_method :context, :describe
+ private :context
+ alias_method :specify, :it
+ private :specify
+end
diff --git a/spec/mspec/lib/mspec/runner/parallel.rb b/spec/mspec/lib/mspec/runner/parallel.rb
new file mode 100644
index 0000000000..6a9ecd155d
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/parallel.rb
@@ -0,0 +1,98 @@
+class ParallelRunner
+ def initialize(files, processes, formatter, argv)
+ @files = files
+ @processes = processes
+ @formatter = formatter
+ @argv = argv
+ @last_files = {}
+ @output_files = []
+ @success = true
+ end
+
+ def launch_children
+ @children = @processes.times.map { |i|
+ name = tmp "mspec-multi-#{i}"
+ @output_files << name
+
+ env = {
+ "SPEC_TEMP_DIR" => "#{SPEC_TEMP_DIR}_#{i}",
+ "MSPEC_MULTI" => i.to_s
+ }
+ command = @argv + ["-fy", "-o", name]
+ $stderr.puts "$ #{command.join(' ')}" if $MSPEC_DEBUG
+ IO.popen([env, *command, close_others: false], "rb+")
+ }
+ end
+
+ def handle(child, message)
+ case message
+ when '.'
+ @formatter.unload
+ send_new_file_or_quit(child)
+ else
+ if message == nil
+ msg = "A child mspec-run process died unexpectedly"
+ else
+ msg = "A child mspec-run process printed unexpected output on STDOUT"
+ while chunk = (child.read_nonblock(4096) rescue nil)
+ message += chunk
+ end
+ message.chomp!('.')
+ msg += ": #{message.inspect}"
+ end
+
+ if last_file = @last_files[child]
+ msg += " while running #{last_file}"
+ end
+
+ @success = false
+ quit(child)
+ abort "\n#{msg}"
+ end
+ end
+
+ def quit(child)
+ begin
+ child.puts "QUIT"
+ rescue Errno::EPIPE
+ # The child process already died
+ end
+ _pid, status = Process.wait2(child.pid)
+ @success &&= status.success?
+ child.close
+ @children.delete(child)
+ end
+
+ def send_new_file_or_quit(child)
+ if @files.empty?
+ quit(child)
+ else
+ file = @files.shift
+ @last_files[child] = file
+ child.puts file
+ end
+ end
+
+ def run
+ MSpec.register_files @files
+ launch_children
+
+ puts @children.map { |child| child.gets }.uniq
+ @formatter.start
+ begin
+ @children.each { |child| send_new_file_or_quit(child) }
+
+ until @children.empty?
+ IO.select(@children)[0].each { |child|
+ handle(child, child.read(1))
+ }
+ end
+ ensure
+ @children.dup.each { |child| quit(child) }
+ @formatter.aggregate_results(@output_files)
+ @formatter.finish
+ end
+
+ @success
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/shared.rb b/spec/mspec/lib/mspec/runner/shared.rb
new file mode 100644
index 0000000000..283711c1d7
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/shared.rb
@@ -0,0 +1,14 @@
+require 'mspec/runner/mspec'
+
+def it_behaves_like(desc, meth, obj = nil)
+ before :all do
+ @method = meth
+ @object = obj
+ end
+ after :all do
+ @method = nil
+ @object = nil
+ end
+
+ it_should_behave_like desc.to_s
+end
diff --git a/spec/mspec/lib/mspec/runner/tag.rb b/spec/mspec/lib/mspec/runner/tag.rb
new file mode 100644
index 0000000000..820df9159e
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/tag.rb
@@ -0,0 +1,38 @@
+class SpecTag
+ attr_accessor :tag, :comment, :description
+
+ def initialize(string = nil)
+ parse(string) if string
+ end
+
+ def parse(string)
+ m = /^([^()#:]+)(\(([^)]+)?\))?:(.*)$/.match string
+ @tag, @comment, description = m.values_at(1, 3, 4) if m
+ @description = unescape description
+ end
+
+ def unescape(str)
+ return unless str
+ if str[0] == ?" and str[-1] == ?"
+ str[1..-2].gsub('\n', "\n")
+ else
+ str
+ end
+ end
+
+ def escape(str)
+ if str.include? "\n"
+ %["#{str.gsub("\n", '\n')}"]
+ else
+ str
+ end
+ end
+
+ def to_s
+ "#{@tag}#{ "(#{@comment})" if @comment }:#{escape @description}"
+ end
+
+ def ==(o)
+ @tag == o.tag and @comment == o.comment and @description == o.description
+ end
+end
diff --git a/spec/mspec/lib/mspec/utils/deprecate.rb b/spec/mspec/lib/mspec/utils/deprecate.rb
new file mode 100644
index 0000000000..1db843b329
--- /dev/null
+++ b/spec/mspec/lib/mspec/utils/deprecate.rb
@@ -0,0 +1,6 @@
+module MSpec
+ def self.deprecate(what, replacement)
+ user_caller = caller.find { |line| !line.include?('lib/mspec') }
+ $stderr.puts "\n#{what} is deprecated, use #{replacement} instead.\nfrom #{user_caller}"
+ end
+end
diff --git a/spec/mspec/lib/mspec/utils/format.rb b/spec/mspec/lib/mspec/utils/format.rb
new file mode 100644
index 0000000000..425dd4d11c
--- /dev/null
+++ b/spec/mspec/lib/mspec/utils/format.rb
@@ -0,0 +1,24 @@
+# If the implementation on which the specs are run cannot
+# load pp from the standard library, add a pp.rb file that
+# defines the #pretty_inspect method on Object or Kernel.
+begin
+ require 'pp'
+rescue LoadError
+ module Kernel
+ def pretty_inspect
+ inspect
+ end
+ end
+end
+
+module MSpec
+ def self.format(obj)
+ if String === obj and obj.include?("\n")
+ "\n#{obj.inspect.gsub('\n', "\n")}"
+ else
+ obj.pretty_inspect.chomp
+ end
+ rescue => e
+ "#<#{obj.class}>(#pretty_inspect raised #{e.inspect})"
+ end
+end
diff --git a/spec/mspec/lib/mspec/utils/name_map.rb b/spec/mspec/lib/mspec/utils/name_map.rb
new file mode 100644
index 0000000000..bf70e651a2
--- /dev/null
+++ b/spec/mspec/lib/mspec/utils/name_map.rb
@@ -0,0 +1,126 @@
+class NameMap
+ MAP = {
+ '`' => 'backtick',
+ '+' => 'plus',
+ '-' => 'minus',
+ '+@' => 'uplus',
+ '-@' => 'uminus',
+ '*' => 'multiply',
+ '/' => 'divide',
+ '%' => 'modulo',
+ '<<' => {'Integer' => 'left_shift',
+ 'IO' => 'output',
+ :default => 'append' },
+ '>>' => 'right_shift',
+ '<' => 'lt',
+ '<=' => 'lte',
+ '>' => 'gt',
+ '>=' => 'gte',
+ '=' => 'assignment',
+ '==' => 'equal_value',
+ '===' => 'case_compare',
+ '<=>' => 'comparison',
+ '[]' => 'element_reference',
+ '[]=' => 'element_set',
+ '**' => 'exponent',
+ '!' => 'not',
+ '~' => {'Integer' => 'complement',
+ :default => 'match' },
+ '!=' => 'not_equal',
+ '!~' => 'not_match',
+ '=~' => 'match',
+ '&' => {'Integer' => 'bit_and',
+ 'Array' => 'intersection',
+ 'Set' => 'intersection',
+ :default => 'and' },
+ '|' => {'Integer' => 'bit_or',
+ 'Array' => 'union',
+ 'Set' => 'union',
+ :default => 'or' },
+ '^' => {'Integer' => 'bit_xor',
+ 'Set' => 'exclusion',
+ :default => 'xor' },
+ }
+
+ EXCLUDED = %w[
+ MSpecScript
+ MkSpec
+ MSpecOption
+ MSpecOptions
+ NameMap
+ SpecVersion
+ ]
+
+ ALWAYS_PRIVATE = %w[
+ initialize initialize_copy initialize_clone initialize_dup respond_to_missing?
+ ].map(&:to_sym)
+
+ def initialize(filter = false)
+ @seen = {}
+ @filter = filter
+ end
+
+ def exception?(name)
+ return false unless c = class_or_module(name)
+ c == Errno or c.ancestors.include? Exception
+ end
+
+ def class_or_module(c)
+ const = Object.const_get(c, false)
+ filtered = @filter && EXCLUDED.include?(const.name)
+ return const if Module === const and !filtered
+ rescue NameError
+ end
+
+ def namespace(mod, const)
+ return const.to_s if mod.nil? or %w[Object Class Module].include? mod
+ "#{mod}::#{const}"
+ end
+
+ def map(hash, constants, mod = nil)
+ @seen = {} unless mod
+
+ constants.each do |const|
+ name = namespace mod, const
+ m = class_or_module name
+ next unless m and !@seen[m]
+ @seen[m] = true
+
+ ms = m.methods(false).map { |x| x.to_s }
+ hash["#{name}."] = ms.sort unless ms.empty?
+
+ ms = m.public_instance_methods(false) +
+ m.protected_instance_methods(false) +
+ (m.private_instance_methods(false) & ALWAYS_PRIVATE)
+ ms.map! { |x| x.to_s }
+ hash["#{name}#"] = ms.sort unless ms.empty?
+
+ map hash, m.constants(false), name
+ end
+
+ hash
+ end
+
+ def dir_name(c, base)
+ return File.join(base, 'exception') if exception? c
+
+ c.split('::').inject(base) do |dir, name|
+ name.gsub!(/Class/, '') unless name == 'Class'
+ File.join dir, name.downcase
+ end
+ end
+
+ def file_name(m, c)
+ if MAP.key?(m)
+ mapping = MAP[m]
+ if mapping.is_a?(Hash)
+ name = mapping[c.split('::').last] || mapping.fetch(:default)
+ else
+ name = mapping
+ end
+ else
+ name = m.gsub(/[?!=]/, '')
+ end
+ "#{name}_spec.rb"
+ end
+end
diff --git a/spec/mspec/lib/mspec/utils/options.rb b/spec/mspec/lib/mspec/utils/options.rb
new file mode 100644
index 0000000000..612caf6771
--- /dev/null
+++ b/spec/mspec/lib/mspec/utils/options.rb
@@ -0,0 +1,504 @@
+require 'mspec/version'
+
+MSPEC_HOME = File.expand_path('../../../..', __FILE__)
+
+class MSpecOption
+ attr_reader :short, :long, :arg, :description, :block
+
+ def initialize(short, long, arg, description, block)
+ @short = short
+ @long = long
+ @arg = arg
+ @description = description
+ @block = block
+ end
+
+ def arg?
+ @arg != nil
+ end
+
+ def match?(opt)
+ opt == @short or opt == @long
+ end
+end
+
+# MSpecOptions provides a parser for command line options. It also
+# provides a composable set of options from which the runner scripts
+# can select for their particular functionality.
+class MSpecOptions
+ # Raised if incorrect or incomplete formats are passed to #on.
+ class OptionError < Exception; end
+
+ # Raised if an unrecognized option is encountered.
+ class ParseError < Exception; end
+
+ class << self
+ attr_accessor :latest
+ end
+
+ attr_accessor :config, :banner, :width, :options
+
+ def initialize(banner = "", width = 30, config = nil)
+ @banner = banner
+ @config = config
+ @width = width
+ @options = []
+ @doc = []
+ @extra = []
+ @on_extra = lambda { |x|
+ raise ParseError, "Unrecognized option: #{x}" if x[0] == ?-
+ @extra << x
+ }
+
+ MSpecOptions.latest = self
+ end
+
+ # Registers an option. Acceptable formats for arguments are:
+ #
+ # on "-a", "description"
+ # on "-a", "--abdc", "description"
+ # on "-a", "ARG", "description"
+ # on "--abdc", "ARG", "description"
+ # on "-a", "--abdc", "ARG", "description"
+ #
+ # If an block is passed, it will be invoked when the option is
+ # matched. Not passing a block is permitted, but nonsensical.
+ def on(*args, &block)
+ raise OptionError, "option and description are required" if args.size < 2
+
+ description = args.pop
+ short, long, argument = nil
+ args.each do |arg|
+ if arg[0] == ?-
+ if arg[1] == ?-
+ long = arg
+ else
+ short = arg
+ end
+ else
+ argument = arg
+ end
+ end
+
+ add short, long, argument, description, block
+ end
+
+ # Adds documentation text for an option and adds an +MSpecOption+
+ # instance to the list of registered options.
+ def add(short, long, arg, description, block)
+ s = short ? short.dup : " "
+ s += (short ? ", " : " ") if long
+ doc " #{s}#{long} #{arg}".ljust(@width-1) + " #{description}"
+ @options << MSpecOption.new(short, long, arg, description, block)
+ end
+
+ # Searches all registered options to find a match for +opt+. Returns
+ # +nil+ if no registered options match.
+ def match?(opt)
+ @options.find { |o| o.match? opt }
+ end
+
+ # Processes an option. Calls the #on_extra block (or default) for
+ # unrecognized options. For registered options, possibly fetches an
+ # argument and invokes the option's block if it is not nil.
+ def process(argv, entry, opt, arg)
+ unless option = match?(opt)
+ @on_extra[entry]
+ else
+ if option.arg?
+ arg = argv.shift if arg.nil?
+ raise ParseError, "No argument provided for #{opt}" unless arg
+ option.block[arg] if option.block
+ else
+ option.block[] if option.block
+ end
+ end
+ option
+ end
+
+ # Splits a string at +n+ characters into the +opt+ and the +rest+.
+ # The +arg+ is set to +nil+ if +rest+ is an empty string.
+ def split(str, n)
+ opt = str[0, n]
+ rest = str[n, str.size]
+ arg = rest == "" ? nil : rest
+ return opt, arg, rest
+ end
+
+ # Parses an array of command line entries, calling blocks for
+ # registered options.
+ def parse(argv = ARGV)
+ argv = Array(argv).dup
+
+ while entry = argv.shift
+ # collect everything that is not an option
+ if entry[0] != ?- or entry.size < 2
+ @on_extra[entry]
+ next
+ end
+
+ # this is a long option
+ if entry[1] == ?-
+ opt, arg = entry.split "="
+ process argv, entry, opt, arg
+ next
+ end
+
+ # disambiguate short option group from short option with argument
+ opt, arg, rest = split entry, 2
+
+ # process first option
+ option = process argv, entry, opt, arg
+ next unless option and !option.arg?
+
+ # process the rest of the options
+ while rest.size > 0
+ opt, arg, rest = split rest, 1
+ opt = "-" + opt
+ option = process argv, opt, opt, arg
+ break if !option or option.arg?
+ end
+ end
+
+ @extra
+ rescue ParseError => e
+ puts self
+ puts e
+ exit 1
+ end
+
+ # Adds a string of documentation text inline in the text generated
+ # from the options. See #on and #add.
+ def doc(str)
+ @doc << str
+ end
+
+ # Convenience method for providing -v, --version options.
+ def version(version, &block)
+ show = block || lambda { puts "#{File.basename $0} #{version}"; exit }
+ on "-v", "--version", "Show version", &show
+ end
+
+ # Convenience method for providing -h, --help options.
+ def help(&block)
+ help = block || lambda { puts self; exit 1 }
+ on "-h", "--help", "Show this message", &help
+ end
+
+ # Stores a block that will be called with unrecognized options
+ def on_extra(&block)
+ @on_extra = block
+ end
+
+ # Returns a string representation of the options and doc strings.
+ def to_s
+ @banner + "\n\n" + @doc.join("\n") + "\n"
+ end
+
+ # The methods below provide groups of options that
+ # are composed by the particular runners to provide
+ # their functionality
+
+ def configure(&block)
+ on("-B", "--config", "FILE",
+ "Load FILE containing configuration options", &block)
+ end
+
+ def targets
+ on("-t", "--target", "TARGET",
+ "Implementation to run the specs, where TARGET is:") do |t|
+ case t
+ when 'r', 'ruby'
+ config[:target] = 'ruby'
+ when 'x', 'rubinius'
+ config[:target] = './bin/rbx'
+ when 'X', 'rbx'
+ config[:target] = 'rbx'
+ when 'j', 'jruby'
+ config[:target] = 'jruby'
+ when 'i','ironruby'
+ config[:target] = 'ir'
+ when 'm','maglev'
+ config[:target] = 'maglev-ruby'
+ when 't','topaz'
+ config[:target] = 'topaz'
+ when 'o','opal'
+ mspec_lib = File.expand_path('../../../', __FILE__)
+ config[:target] = "./bin/opal -syaml -sfileutils -rnodejs -rnodejs/require -rnodejs/yaml -rprocess -Derror -I#{mspec_lib} -I./lib/ -I. "
+ else
+ config[:target] = t
+ end
+ end
+
+ doc ""
+ doc " r or ruby invokes ruby in PATH"
+ doc " x or rubinius invokes ./bin/rbx"
+ doc " X or rbx invokes rbx in PATH"
+ doc " j or jruby invokes jruby in PATH"
+ doc " i or ironruby invokes ir in PATH"
+ doc " m or maglev invokes maglev-ruby in PATH"
+ doc " t or topaz invokes topaz in PATH"
+ doc " o or opal invokes ./bin/opal with options"
+ doc " full path to EXE invokes EXE directly\n"
+
+ on("-T", "--target-opt", "OPT",
+ "Pass OPT as a flag to the target implementation") do |t|
+ config[:flags] << t
+ end
+ on("-I", "--include", "DIR",
+ "Pass DIR through as the -I option to the target") do |d|
+ config[:loadpath] << "-I#{d}"
+ end
+ on("-r", "--require", "LIBRARY",
+ "Pass LIBRARY through as the -r option to the target") do |f|
+ config[:requires] << "-r#{f}"
+ end
+ end
+
+ def formatters
+ on("-f", "--format", "FORMAT",
+ "Formatter for reporting, where FORMAT is one of:") do |o|
+ require 'mspec/runner/formatters'
+ case o
+ when 's', 'spec', 'specdoc'
+ config[:formatter] = SpecdocFormatter
+ when 'h', 'html'
+ config[:formatter] = HtmlFormatter
+ when 'd', 'dot', 'dotted'
+ config[:formatter] = DottedFormatter
+ when 'b', 'describe'
+ config[:formatter] = DescribeFormatter
+ when 'f', 'file'
+ config[:formatter] = FileFormatter
+ when 'u', 'unit', 'unitdiff'
+ config[:formatter] = UnitdiffFormatter
+ when 'm', 'summary'
+ config[:formatter] = SummaryFormatter
+ when 'a', '*', 'spin'
+ config[:formatter] = SpinnerFormatter
+ when 't', 'method'
+ config[:formatter] = MethodFormatter
+ when 'e', 'stats'
+ config[:formatter] = StatsPerFileFormatter
+ when 'y', 'yaml'
+ config[:formatter] = YamlFormatter
+ when 'p', 'profile'
+ config[:formatter] = ProfileFormatter
+ when 'j', 'junit'
+ config[:formatter] = JUnitFormatter
+ else
+ abort "Unknown format: #{o}" unless File.exist?(o)
+ require File.expand_path(o)
+ if Object.const_defined?(:CUSTOM_MSPEC_FORMATTER)
+ config[:formatter] = CUSTOM_MSPEC_FORMATTER
+ else
+ abort "You must define CUSTOM_MSPEC_FORMATTER in your custom formatter file"
+ end
+ end
+ end
+
+ doc ""
+ doc " s, spec, specdoc SpecdocFormatter"
+ doc " h, html, HtmlFormatter"
+ doc " d, dot, dotted DottedFormatter"
+ doc " f, file FileFormatter"
+ doc " u, unit, unitdiff UnitdiffFormatter"
+ doc " m, summary SummaryFormatter"
+ doc " a, *, spin SpinnerFormatter"
+ doc " t, method MethodFormatter"
+ doc " e, stats StatsPerFileFormatter"
+ doc " y, yaml YamlFormatter"
+ doc " p, profile ProfileFormatter"
+ doc " j, junit JUnitFormatter\n"
+
+ on("-o", "--output", "FILE",
+ "Write formatter output to FILE") do |f|
+ config[:output] = f
+ end
+
+ on("--error-output", "FILE",
+ "Write error output of failing specs to FILE, or $stderr if value is 'stderr'.") do |f|
+ config[:error_output] = f
+ end
+ end
+
+ def filters
+ on("-e", "--example", "STR",
+ "Run examples with descriptions matching STR") do |o|
+ config[:includes] << o
+ end
+ on("-E", "--exclude", "STR",
+ "Exclude examples with descriptions matching STR") do |o|
+ config[:excludes] << o
+ end
+ on("-p", "--pattern", "PATTERN",
+ "Run examples with descriptions matching PATTERN") do |o|
+ config[:patterns] << Regexp.new(o)
+ end
+ on("-P", "--excl-pattern", "PATTERN",
+ "Exclude examples with descriptions matching PATTERN") do |o|
+ config[:xpatterns] << Regexp.new(o)
+ end
+ on("-g", "--tag", "TAG",
+ "Run examples with descriptions matching ones tagged with TAG") do |o|
+ config[:tags] << o
+ end
+ on("-G", "--excl-tag", "TAG",
+ "Exclude examples with descriptions matching ones tagged with TAG") do |o|
+ config[:xtags] << o
+ end
+ on("-w", "--profile", "FILE",
+ "Run examples for methods listed in the profile FILE") do |f|
+ config[:profiles] << f
+ end
+ on("-W", "--excl-profile", "FILE",
+ "Exclude examples for methods listed in the profile FILE") do |f|
+ config[:xprofiles] << f
+ end
+ end
+
+ def chdir
+ on("-C", "--chdir", "DIR",
+ "Change the working directory to DIR before running specs") do |d|
+ Dir.chdir d
+ end
+ end
+
+ def prefix
+ on("--prefix", "STR", "Prepend STR when resolving spec file names") do |p|
+ config[:prefix] = p
+ end
+ end
+
+ def pretend
+ on("-Z", "--dry-run",
+ "Invoke formatters and other actions, but don't execute the specs") do
+ MSpec.register_mode :pretend
+ end
+ end
+
+ def unguarded
+ on("--unguarded", "Turn off all guards") do
+ MSpec.register_mode :unguarded
+ end
+ on("--no-ruby_bug", "Turn off the ruby_bug guard") do
+ MSpec.register_mode :no_ruby_bug
+ end
+ end
+
+ def randomize
+ on("-H", "--random",
+ "Randomize the list of spec files") do
+ MSpec.randomize = true
+ end
+ end
+
+ def repeat
+ on("-R", "--repeat", "NUMBER",
+ "Repeatedly run an example NUMBER times") do |o|
+ MSpec.repeat = Integer(o)
+ end
+ end
+
+ def verbose
+ on("-V", "--verbose", "Output the name of each file processed") do
+ obj = Object.new
+ def obj.start
+ @width = MSpec.files_array.inject(0) { |max, f| f.size > max ? f.size : max }
+ end
+ def obj.load
+ file = MSpec.file
+ STDERR.print "\n#{file.ljust(@width)}\n"
+ end
+ MSpec.register :start, obj
+ MSpec.register :load, obj
+ end
+
+ on("-m", "--marker", "MARKER",
+ "Output MARKER for each file processed") do |o|
+ obj = Object.new
+ obj.instance_variable_set :@marker, o
+ def obj.load
+ STDERR.print @marker
+ end
+ MSpec.register :load, obj
+ end
+
+ on("--print-skips", "Print skips") do
+ config[:print_skips] = true
+ end
+ end
+
+ def interrupt
+ on("--int-spec", "Control-C interrupts the current spec only") do
+ config[:abort] = false
+ end
+ end
+
+ def timeout
+ on("--timeout", "TIMEOUT", "Abort if a spec takes longer than TIMEOUT seconds") do |timeout|
+ require 'mspec/runner/actions/timeout'
+ timeout = Float(timeout)
+ TimeoutAction.new(timeout).register
+ end
+ end
+
+ def verify
+ on("--report-on", "GUARD", "Report specs guarded by GUARD") do |g|
+ MSpec.register_mode :report_on
+ SpecGuard.guards << g.to_sym
+ end
+ on("-O", "--report", "Report guarded specs") do
+ MSpec.register_mode :report
+ end
+ on("-Y", "--verify",
+ "Verify that guarded specs pass and fail as expected") do
+ MSpec.register_mode :verify
+ end
+ end
+
+ def action_filters
+ on("-K", "--action-tag", "TAG",
+ "Spec descriptions marked with TAG will trigger the specified action") do |o|
+ config[:atags] << o
+ end
+ on("-S", "--action-string", "STR",
+ "Spec descriptions matching STR will trigger the specified action") do |o|
+ config[:astrings] << o
+ end
+ end
+
+ def actions
+ on("--spec-debug",
+ "Invoke the debugger when a spec description matches (see -K, -S)") do
+ config[:debugger] = true
+ end
+ end
+
+ def debug
+ on("-d", "--debug",
+ "Set MSpec debugging flag for more verbose output") do
+ $MSPEC_DEBUG = true
+ end
+ end
+
+ def all
+ configure {}
+ targets
+ formatters
+ filters
+ chdir
+ prefix
+ pretend
+ unguarded
+ randomize
+ repeat
+ verbose
+ interrupt
+ timeout
+ verify
+ action_filters
+ actions
+ debug
+ end
+end
diff --git a/spec/mspec/lib/mspec/utils/script.rb b/spec/mspec/lib/mspec/utils/script.rb
new file mode 100644
index 0000000000..e86beaab86
--- /dev/null
+++ b/spec/mspec/lib/mspec/utils/script.rb
@@ -0,0 +1,299 @@
+require 'mspec/guards/guard'
+require 'mspec/guards/version'
+require 'mspec/utils/warnings'
+
+# MSpecScript provides a skeleton for all the MSpec runner scripts.
+class MSpecScript
+ # Returns the config object. Maintained at the class
+ # level to easily enable simple config files. See the
+ # class method +set+.
+ def self.config
+ @config ||= {
+ :path => ['.', 'spec'],
+ :config_ext => '.mspec'
+ }
+ end
+
+ # Associates +value+ with +key+ in the config object. Enables
+ # simple config files of the form:
+ #
+ # class MSpecScript
+ # set :target, "ruby"
+ # set :files, ["one_spec.rb", "two_spec.rb"]
+ # end
+ def self.set(key, value)
+ config[key] = value
+ end
+
+ # Gets the value of +key+ from the config object. Simplifies
+ # getting values in a config file:
+ #
+ # class MSpecScript
+ # set :a, 1
+ # set :b, 2
+ # set :c, get(:a) + get(:b)
+ # end
+ def self.get(key)
+ config[key]
+ end
+
+ class << self
+ attr_accessor :child_process
+ end
+
+ # True if the current process is the one going to run the specs with `MSpec.process`.
+ # False for e.g. `mspec` which exec's to `mspec-run`.
+ # This is useful in .mspec config files.
+ def self.child_process?
+ MSpecScript.child_process
+ end
+
+ def initialize
+ check_version!
+
+ config[:formatter] = nil
+ config[:includes] = []
+ config[:excludes] = []
+ config[:patterns] = []
+ config[:xpatterns] = []
+ config[:tags] = []
+ config[:xtags] = []
+ config[:profiles] = []
+ config[:xprofiles] = []
+ config[:atags] = []
+ config[:astrings] = []
+ config[:ltags] = []
+ config[:abort] = true
+ @loaded = []
+ end
+
+ # Returns the config object maintained by the instance's class.
+ # See the class methods +set+ and +config+.
+ def config
+ MSpecScript.config
+ end
+
+ # Returns +true+ if the file was located in +config[:path]+,
+ # possibly appending +config[:config_ext]. Returns +false+
+ # otherwise.
+ def try_load(target)
+ names = [target]
+ unless target[-6..-1] == config[:config_ext]
+ names << target + config[:config_ext]
+ end
+
+ names.each do |name|
+ config[:path].each do |dir|
+ file = File.expand_path name, dir
+ if @loaded.include?(file)
+ return true
+ elsif File.exist? file
+ value = Kernel.load(file)
+ @loaded << file
+ return value
+ end
+ end
+ end
+
+ false
+ end
+
+ def load(target)
+ try_load(target) or abort "Could not load config file #{target}"
+ end
+
+ # Attempts to load a default config file. First tries to load
+ # 'default.mspec'. If that fails, attempts to load a config
+ # file name constructed from the value of RUBY_ENGINE and the
+ # first two numbers in RUBY_VERSION. For example, on MRI 1.8.6,
+ # the file name would be 'ruby.1.8.mspec'.
+ def load_default
+ try_load 'default.mspec'
+
+ if Object.const_defined?(:RUBY_ENGINE)
+ engine = RUBY_ENGINE
+ else
+ engine = 'ruby'
+ end
+ try_load "#{engine}.#{SpecGuard.ruby_version}.mspec"
+ try_load "#{engine}.mspec"
+ end
+
+ # Callback for enabling custom options. This version is a no-op.
+ # Provide an implementation specific version in a config file.
+ # Called by #options after the MSpec-provided options are added.
+ def custom_options(options)
+ options.doc " No custom options registered"
+ end
+
+ # Registers all filters and actions.
+ def register
+ require 'mspec/runner/formatters/dotted'
+ require 'mspec/runner/formatters/spinner'
+ require 'mspec/runner/formatters/file'
+ require 'mspec/runner/filters'
+
+ if formatter = config_formatter
+ formatter.register
+ MSpec.formatter = formatter
+ end
+
+ MatchFilter.new(:include, *config[:includes]).register unless config[:includes].empty?
+ MatchFilter.new(:exclude, *config[:excludes]).register unless config[:excludes].empty?
+ RegexpFilter.new(:include, *config[:patterns]).register unless config[:patterns].empty?
+ RegexpFilter.new(:exclude, *config[:xpatterns]).register unless config[:xpatterns].empty?
+ TagFilter.new(:include, *config[:tags]).register unless config[:tags].empty?
+ TagFilter.new(:exclude, *config[:xtags]).register unless config[:xtags].empty?
+ ProfileFilter.new(:include, *config[:profiles]).register unless config[:profiles].empty?
+ ProfileFilter.new(:exclude, *config[:xprofiles]).register unless config[:xprofiles].empty?
+
+ DebugAction.new(config[:atags], config[:astrings]).register if config[:debugger]
+
+ custom_register
+ end
+
+ # Makes a formatter specified by :formatter option.
+ def config_formatter
+ if config[:formatter].nil?
+ config[:formatter] = STDOUT.tty? ? SpinnerFormatter : @files.size < 50 ? DottedFormatter : FileFormatter
+ end
+
+ if config[:formatter]
+ config[:formatter].new(config[:output])
+ end
+ end
+
+ # Callback for enabling custom actions, etc. This version is a
+ # no-op. Provide an implementation specific version in a config
+ # file. Called by #register.
+ def custom_register
+ end
+
+ # Sets up signal handlers. Only a handler for SIGINT is
+ # registered currently.
+ def signals
+ if config[:abort]
+ Signal.trap "INT" do
+ MSpec.actions :abort
+ puts "\nProcess aborted!"
+ exit! 1
+ end
+ end
+ end
+
+ # Attempts to resolve +partial+ as a file or directory name in the
+ # following order:
+ #
+ # 1. +partial+
+ # 2. +partial+ + "_spec.rb"
+ # 3. <tt>File.join(config[:prefix], partial)</tt>
+ # 4. <tt>File.join(config[:prefix], partial + "_spec.rb")</tt>
+ #
+ # If it is a file name, returns the name as an entry in an array.
+ # If it is a directory, returns all *_spec.rb files in the
+ # directory and subdirectories.
+ #
+ # If unable to resolve +partial+, +Kernel.abort+ is called.
+ def entries(partial)
+ file = partial + "_spec.rb"
+ patterns = [partial, file]
+ if config[:prefix]
+ patterns << File.join(config[:prefix], partial)
+ patterns << File.join(config[:prefix], file)
+ end
+
+ patterns.each do |pattern|
+ begin
+ expanded = File.realpath(pattern)
+ rescue Errno::ENOENT, Errno::ENOTDIR
+ next
+ end
+ if File.file?(expanded) && expanded.end_with?('.rb')
+ return [expanded]
+ elsif File.directory?(expanded)
+ specs = Dir["#{expanded}/**/*_spec.rb"].sort
+ return specs unless specs.empty?
+ end
+ end
+
+ abort "Could not find spec file #{partial}"
+ end
+
+ # Resolves each entry in +patterns+ to a set of files.
+ #
+ # If the pattern has a leading '^' character, the list of files
+ # is subtracted from the list of files accumulated to that point.
+ #
+ # If the entry has a leading ':' character, the corresponding
+ # key is looked up in the config object and the entries in the
+ # value retrieved are processed through #entries.
+ def files(patterns)
+ list = []
+ patterns.each do |pattern|
+ case pattern[0]
+ when ?^
+ list -= entries(pattern[1..-1])
+ when ?:
+ key = pattern[1..-1].to_sym
+ value = config[key]
+ abort "Key #{pattern} not found in mspec config." unless value
+ list += files(Array(value))
+ else
+ list += entries(pattern)
+ end
+ end
+ list
+ end
+
+ def files_from_patterns(patterns)
+ unless $0.end_with?("_spec.rb")
+ if patterns.empty?
+ patterns = config[:files]
+ end
+ if patterns.empty? and File.directory? "./spec"
+ patterns = ["spec/"]
+ end
+ end
+ list = files(patterns)
+ abort "No files specified." if list.empty?
+ list
+ end
+
+ def cores(max)
+ require 'etc'
+ [Etc.nprocessors, max].min
+ end
+
+ def setup_env
+ ENV['MSPEC_RUNNER'] = '1'
+
+ unless ENV['RUBY_EXE']
+ ENV['RUBY_EXE'] = config[:target] if config[:target]
+ end
+
+ unless ENV['RUBY_FLAGS']
+ ENV['RUBY_FLAGS'] = config[:flags].join(" ") if config[:flags]
+ end
+ end
+
+ # Instantiates an instance and calls the series of methods to
+ # invoke the script.
+ def self.main(child_process = true)
+ MSpecScript.child_process = child_process
+
+ script = new
+ script.load_default
+ script.options
+ script.signals
+ script.register
+ script.setup_env
+ require 'mspec'
+ script.run
+ end
+
+ private def check_version!
+ ruby_version_is ""..."2.6" do
+ warn "MSpec is supported for Ruby 2.6 and above only"
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/utils/version.rb b/spec/mspec/lib/mspec/utils/version.rb
new file mode 100644
index 0000000000..9c1c58b8df
--- /dev/null
+++ b/spec/mspec/lib/mspec/utils/version.rb
@@ -0,0 +1,52 @@
+class SpecVersion
+ # If beginning implementations have a problem with this include, we can
+ # manually implement the relational operators that are needed.
+ include Comparable
+
+ # SpecVersion handles comparison correctly for the context by filling in
+ # missing version parts according to the value of +ceil+. If +ceil+ is
+ # +false+, 0 digits fill in missing version parts. If +ceil+ is +true+, 9
+ # digits fill in missing parts. (See e.g. VersionGuard and BugGuard.)
+ def initialize(version, ceil = false)
+ @version = version
+ @ceil = ceil
+ @integer = nil
+ end
+
+ def to_s
+ @version
+ end
+
+ def to_str
+ to_s
+ end
+
+ # Converts a string representation of a version major.minor.tiny
+ # to an integer representation so that comparisons can be made. For example,
+ # "2.2.10" < "2.2.2" would be false if compared as strings.
+ def to_i
+ unless @integer
+ major, minor, tiny = @version.split "."
+ if @ceil
+ tiny = 99 unless tiny
+ end
+ parts = [major, minor, tiny].map { |x| x.to_i }
+ @integer = ("1%02d%02d%02d" % parts).to_i
+ end
+ @integer
+ end
+
+ def to_int
+ to_i
+ end
+
+ def <=>(other)
+ if other.respond_to? :to_int
+ other = Integer(other.to_int)
+ else
+ other = SpecVersion.new(String(other)).to_i
+ end
+
+ self.to_i <=> other
+ end
+end
diff --git a/spec/mspec/lib/mspec/utils/warnings.rb b/spec/mspec/lib/mspec/utils/warnings.rb
new file mode 100644
index 0000000000..0d3d36fada
--- /dev/null
+++ b/spec/mspec/lib/mspec/utils/warnings.rb
@@ -0,0 +1,53 @@
+require 'mspec/guards/version'
+
+# Always enable deprecation warnings when running MSpec, as ruby/spec tests for them,
+# and like in most test frameworks, deprecation warnings should be enabled by default,
+# so that deprecations are noticed before the breaking change.
+# Disable experimental warnings, we want to test new experimental features in ruby/spec.
+if Object.const_defined?(:Warning) and Warning.respond_to?(:[]=)
+ Warning[:deprecated] = true
+ Warning[:experimental] = false
+end
+
+if Object.const_defined?(:Warning) and Warning.respond_to?(:warn)
+ def Warning.warn(message, category: nil)
+ # Suppress any warning inside the method to prevent recursion
+ verbose = $VERBOSE
+ $VERBOSE = nil
+
+ if Thread.current[:in_mspec_complain_matcher]
+ return $stderr.write(message)
+ end
+
+ case message
+ # $VERBOSE = true warnings
+ when /(.+\.rb):(\d+):.+possibly useless use of (<|<=|==|>=|>) in void context/
+ # Make sure there is a .should otherwise it is missing
+ line_nb = Integer($2)
+ unless File.exist?($1) and /\.should(_not)? (<|<=|==|>=|>)/ === File.readlines($1)[line_nb-1]
+ $stderr.write message
+ end
+ when /possibly useless use of (\+|-) in void context/
+ when /assigned but unused variable/
+ when /method redefined/
+ when /previous definition of/
+ when /instance variable @.+ not initialized/
+ when /statement not reached/
+ when /shadowing outer local variable/
+ when /setting Encoding.default_(in|ex)ternal/
+ when /unknown (un)?pack directive/
+ when /(un)?trust(ed\?)? is deprecated/
+ when /\.exists\? is a deprecated name/
+ when /Float .+ out of range/
+ when /passing a block to String#(bytes|chars|codepoints|lines) is deprecated/
+ when /core\/string\/modulo_spec\.rb:\d+: warning: too many arguments for format string/
+ when /regexp\/shared\/new_ascii(_8bit)?\.rb:\d+: warning: Unknown escape .+ is ignored/
+ else
+ $stderr.write message
+ end
+ ensure
+ $VERBOSE = verbose
+ end
+else
+ $VERBOSE = nil unless ENV['OUTPUT_WARNINGS']
+end
diff --git a/spec/mspec/lib/mspec/version.rb b/spec/mspec/lib/mspec/version.rb
new file mode 100644
index 0000000000..9126f5366e
--- /dev/null
+++ b/spec/mspec/lib/mspec/version.rb
@@ -0,0 +1,5 @@
+require 'mspec/utils/version'
+
+module MSpec
+ VERSION = SpecVersion.new "1.8.0"
+end
diff --git a/spec/mspec/spec/commands/fixtures/four.txt b/spec/mspec/spec/commands/fixtures/four.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/spec/mspec/spec/commands/fixtures/four.txt
diff --git a/spec/mspec/spec/commands/fixtures/level2/three_spec.rb b/spec/mspec/spec/commands/fixtures/level2/three_spec.rb
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/spec/mspec/spec/commands/fixtures/level2/three_spec.rb
@@ -0,0 +1 @@
+
diff --git a/spec/mspec/spec/commands/fixtures/one_spec.rb b/spec/mspec/spec/commands/fixtures/one_spec.rb
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/spec/mspec/spec/commands/fixtures/one_spec.rb
@@ -0,0 +1 @@
+
diff --git a/spec/mspec/spec/commands/fixtures/three.rb b/spec/mspec/spec/commands/fixtures/three.rb
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/spec/mspec/spec/commands/fixtures/three.rb
@@ -0,0 +1 @@
+
diff --git a/spec/mspec/spec/commands/fixtures/two_spec.rb b/spec/mspec/spec/commands/fixtures/two_spec.rb
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/spec/mspec/spec/commands/fixtures/two_spec.rb
@@ -0,0 +1 @@
+
diff --git a/spec/mspec/spec/commands/mkspec_spec.rb b/spec/mspec/spec/commands/mkspec_spec.rb
new file mode 100644
index 0000000000..32262723de
--- /dev/null
+++ b/spec/mspec/spec/commands/mkspec_spec.rb
@@ -0,0 +1,363 @@
+require 'spec_helper'
+require 'mspec/commands/mkspec'
+require 'fileutils'
+
+RSpec.describe "The -c, --constant CONSTANT option" do
+ before :each do
+ @options = MSpecOptions.new
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MkSpec.new
+ @config = @script.config
+ end
+
+ it "is enabled by #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-c", "--constant", "CONSTANT",
+ an_instance_of(String))
+ @script.options []
+ end
+
+ it "adds CONSTANT to the list of constants" do
+ ["-c", "--constant"].each do |opt|
+ @config[:constants] = []
+ @script.options [opt, "Object"]
+ expect(@config[:constants]).to include("Object")
+ end
+ end
+end
+
+RSpec.describe "The -b, --base DIR option" do
+ before :each do
+ @options = MSpecOptions.new
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MkSpec.new
+ @config = @script.config
+ end
+
+ it "is enabled by #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-b", "--base", "DIR",
+ an_instance_of(String))
+ @script.options []
+ end
+
+ it "sets the base directory relative to which the spec directories are created" do
+ ["-b", "--base"].each do |opt|
+ @config[:base] = nil
+ @script.options [opt, "superspec"]
+ expect(@config[:base]).to eq(File.expand_path("superspec"))
+ end
+ end
+end
+
+RSpec.describe "The -r, --require LIBRARY option" do
+ before :each do
+ @options = MSpecOptions.new
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MkSpec.new
+ @config = @script.config
+ end
+
+ it "is enabled by #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-r", "--require", "LIBRARY",
+ an_instance_of(String))
+ @script.options []
+ end
+
+ it "adds CONSTANT to the list of constants" do
+ ["-r", "--require"].each do |opt|
+ @config[:requires] = []
+ @script.options [opt, "libspec"]
+ expect(@config[:requires]).to include("libspec")
+ end
+ end
+end
+
+RSpec.describe "The -V, --version-guard VERSION option" do
+ before :each do
+ @options = MSpecOptions.new
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MkSpec.new
+ @config = @script.config
+ end
+
+ it "is enabled by #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-V", "--version-guard", "VERSION",
+ an_instance_of(String))
+ @script.options []
+ end
+
+ it "sets the version for the ruby_version_is guards to VERSION" do
+ ["-r", "--require"].each do |opt|
+ @config[:requires] = []
+ @script.options [opt, "libspec"]
+ expect(@config[:requires]).to include("libspec")
+ end
+ end
+end
+
+RSpec.describe MkSpec, "#options" do
+ before :each do
+ @options = MSpecOptions.new
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MkSpec.new
+ end
+
+ it "parses the command line options" do
+ expect(@options).to receive(:parse).with(["--this", "and", "--that"])
+ @script.options ["--this", "and", "--that"]
+ end
+
+ it "parses ARGV unless passed other options" do
+ expect(@options).to receive(:parse).with(ARGV)
+ @script.options
+ end
+
+ it "prints help and exits if passed an unrecognized option" do
+ expect(@options).to receive(:raise).with(MSpecOptions::ParseError, an_instance_of(String))
+ allow(@options).to receive(:puts)
+ allow(@options).to receive(:exit)
+ @script.options ["--iunknown"]
+ end
+end
+
+RSpec.describe MkSpec, "#create_directory" do
+ before :each do
+ @script = MkSpec.new
+ @script.config[:base] = "spec"
+ end
+
+ it "prints a warning if a file with the directory name exists" do
+ expect(File).to receive(:exist?).and_return(true)
+ expect(File).to receive(:directory?).and_return(false)
+ expect(FileUtils).not_to receive(:mkdir_p)
+ expect(@script).to receive(:puts).with("spec/class already exists and is not a directory.")
+ expect(@script.create_directory("Class")).to eq(nil)
+ end
+
+ it "does nothing if the directory already exists" do
+ expect(File).to receive(:exist?).and_return(true)
+ expect(File).to receive(:directory?).and_return(true)
+ expect(FileUtils).not_to receive(:mkdir_p)
+ expect(@script.create_directory("Class")).to eq("spec/class")
+ end
+
+ it "creates the directory if it does not exist" do
+ expect(File).to receive(:exist?).and_return(false)
+ expect(@script).to receive(:mkdir_p).with("spec/class")
+ expect(@script.create_directory("Class")).to eq("spec/class")
+ end
+
+ it "creates the directory for a namespaced module if it does not exist" do
+ expect(File).to receive(:exist?).and_return(false)
+ expect(@script).to receive(:mkdir_p).with("spec/struct/tms")
+ expect(@script.create_directory("Struct::Tms")).to eq("spec/struct/tms")
+ end
+end
+
+RSpec.describe MkSpec, "#write_requires" do
+ before :each do
+ @script = MkSpec.new
+ @script.config[:base] = "spec"
+
+ @file = double("file")
+ allow(File).to receive(:open).and_yield(@file)
+ end
+
+ it "writes the spec_helper require line" do
+ expect(@file).to receive(:puts).with("require_relative '../../../spec_helper'")
+ @script.write_requires("spec/core/tcejbo", "spec/core/tcejbo/inspect_spec.rb")
+ end
+
+ it "writes require lines for each library specified on the command line" do
+ allow(@file).to receive(:puts)
+ expect(@file).to receive(:puts).with("require_relative '../../../spec_helper'")
+ expect(@file).to receive(:puts).with("require 'complex'")
+ @script.config[:requires] << 'complex'
+ @script.write_requires("spec/core/tcejbo", "spec/core/tcejbo/inspect_spec.rb")
+ end
+end
+
+RSpec.describe MkSpec, "#write_spec" do
+ before :each do
+ @file = IOStub.new
+ allow(File).to receive(:open).and_yield(@file)
+
+ @script = MkSpec.new
+ allow(@script).to receive(:puts)
+
+ @response = double("system command response")
+ allow(@response).to receive(:include?).and_return(false)
+ allow(@script).to receive(:`).and_return(@response)
+ end
+
+ it "checks if specs exist for the method if the spec file exists" do
+ name = Regexp.escape(RbConfig.ruby)
+ expect(@script).to receive(:`).with(
+ %r"#{name} #{MSPEC_HOME}/bin/mspec-run --dry-run --unguarded -fs -e 'Object#inspect' spec/core/tcejbo/inspect_spec.rb")
+ @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+ end
+
+ it "checks for the method name in the spec file output" do
+ expect(@response).to receive(:include?).with("Array#[]=")
+ @script.write_spec("spec/core/yarra/element_set_spec.rb", "Array#[]=", true)
+ end
+
+ it "returns nil if the spec file exists and contains a spec for the method" do
+ allow(@response).to receive(:include?).and_return(true)
+ expect(@script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)).to eq(nil)
+ end
+
+ it "does not print the spec file name if it exists and contains a spec for the method" do
+ allow(@response).to receive(:include?).and_return(true)
+ expect(@script).not_to receive(:puts)
+ @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+ end
+
+ it "prints the spec file name if a template spec is written" do
+ expect(@script).to receive(:puts).with("spec/core/tcejbo/inspect_spec.rb")
+ @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+ end
+
+ it "writes a template spec to the file if the spec file does not exist" do
+ expect(@file).to receive(:puts).twice
+ expect(@script).to receive(:puts).with("spec/core/tcejbo/inspect_spec.rb")
+ @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", false)
+ end
+
+ it "writes a template spec to the file if it exists but contains no spec for the method" do
+ expect(@response).to receive(:include?).and_return(false)
+ expect(@file).to receive(:puts).twice
+ expect(@script).to receive(:puts).with("spec/core/tcejbo/inspect_spec.rb")
+ @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+ end
+
+ it "writes a template spec" do
+ @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+ expect(@file).to eq <<EOS
+
+describe "Object#inspect" do
+ it "needs to be reviewed for spec completeness"
+end
+EOS
+ end
+
+ it "writes a template spec with version guard" do
+ @script.config[:version] = '""..."1.9"'
+ @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+ expect(@file).to eq <<EOS
+
+ruby_version_is ""..."1.9" do
+ describe "Object#inspect" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
+EOS
+
+ end
+end
+
+RSpec.describe MkSpec, "#create_file" do
+ before :each do
+ @script = MkSpec.new
+ allow(@script).to receive(:write_requires)
+ allow(@script).to receive(:write_spec)
+
+ allow(File).to receive(:exist?).and_return(false)
+ end
+
+ it "generates a file name based on the directory, class/module, and method" do
+ expect(File).to receive(:join).with("spec/tcejbo", "inspect_spec.rb"
+ ).and_return("spec/tcejbo/inspect_spec.rb")
+ @script.create_file("spec/tcejbo", "Object", "inspect", "Object#inspect")
+ end
+
+ it "does not call #write_requires if the spec file already exists" do
+ expect(File).to receive(:exist?).and_return(true)
+ expect(@script).not_to receive(:write_requires)
+ @script.create_file("spec/tcejbo", "Object", "inspect", "Object#inspect")
+ end
+
+ it "calls #write_requires if the spec file does not exist" do
+ expect(File).to receive(:exist?).and_return(false)
+ expect(@script).to receive(:write_requires).with(
+ "spec/tcejbo", "spec/tcejbo/inspect_spec.rb")
+ @script.create_file("spec/tcejbo", "Object", "inspect", "Object#inspect")
+ end
+
+ it "calls #write_spec with the file, method name" do
+ expect(@script).to receive(:write_spec).with(
+ "spec/tcejbo/inspect_spec.rb", "Object#inspect", false)
+ @script.create_file("spec/tcejbo", "Object", "inspect", "Object#inspect")
+ end
+end
+
+RSpec.describe MkSpec, "#run" do
+ before :each do
+ @options = MSpecOptions.new
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+
+ @map = NameMap.new
+ allow(NameMap).to receive(:new).and_return(@map)
+
+ @script = MkSpec.new
+ allow(@script).to receive(:create_directory).and_return("spec/mkspec")
+ allow(@script).to receive(:create_file)
+ @script.config[:constants] = [MkSpec]
+ end
+
+ it "loads files in the requires list" do
+ allow(@script).to receive(:require)
+ expect(@script).to receive(:require).with("alib")
+ expect(@script).to receive(:require).with("blib")
+ @script.config[:requires] = ["alib", "blib"]
+ @script.run
+ end
+
+ it "creates a map of constants to methods" do
+ expect(@map).to receive(:map).with({}, @script.config[:constants]).and_return({})
+ @script.run
+ end
+
+ it "calls #create_directory for each class/module in the map" do
+ expect(@script).to receive(:create_directory).with("MkSpec").twice
+ @script.run
+ end
+
+ it "calls #create_file for each method on each class/module in the map" do
+ expect(@map).to receive(:map).with({}, @script.config[:constants]
+ ).and_return({"MkSpec#" => ["run"]})
+ expect(@script).to receive(:create_file).with("spec/mkspec", "MkSpec", "run", "MkSpec#run")
+ @script.run
+ end
+end
+
+RSpec.describe MkSpec, ".main" do
+ before :each do
+ @script = double("MkSpec").as_null_object
+ allow(MkSpec).to receive(:new).and_return(@script)
+ end
+
+ it "sets MSPEC_RUNNER = '1' in the environment" do
+ ENV["MSPEC_RUNNER"] = "0"
+ MkSpec.main
+ expect(ENV["MSPEC_RUNNER"]).to eq("1")
+ end
+
+ it "creates an instance of MSpecScript" do
+ expect(MkSpec).to receive(:new).and_return(@script)
+ MkSpec.main
+ end
+
+ it "calls the #options method on the script" do
+ expect(@script).to receive(:options)
+ MkSpec.main
+ end
+
+ it "calls the #run method on the script" do
+ expect(@script).to receive(:run)
+ MkSpec.main
+ end
+end
diff --git a/spec/mspec/spec/commands/mspec_ci_spec.rb b/spec/mspec/spec/commands/mspec_ci_spec.rb
new file mode 100644
index 0000000000..bcbc5b4224
--- /dev/null
+++ b/spec/mspec/spec/commands/mspec_ci_spec.rb
@@ -0,0 +1,150 @@
+require 'spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters/tag'
+require 'mspec/commands/mspec-ci'
+
+RSpec.describe MSpecCI, "#options" do
+ before :each do
+ @options, @config = new_option
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+
+ @script = MSpecCI.new
+ allow(@script).to receive(:config).and_return(@config)
+ allow(@script).to receive(:files).and_return([])
+ end
+
+ it "enables the chdir option" do
+ expect(@options).to receive(:chdir)
+ @script.options []
+ end
+
+ it "enables the prefix option" do
+ expect(@options).to receive(:prefix)
+ @script.options []
+ end
+
+ it "enables the config option" do
+ expect(@options).to receive(:configure)
+ @script.options []
+ end
+
+ it "provides a custom action (block) to the config option" do
+ expect(@script).to receive(:load).with("cfg.mspec")
+ @script.options ["-B", "cfg.mspec"]
+ end
+
+ it "enables the dry run option" do
+ expect(@options).to receive(:pretend)
+ @script.options []
+ end
+
+ it "enables the unguarded option" do
+ expect(@options).to receive(:unguarded)
+ @script.options []
+ end
+
+ it "enables the interrupt single specs option" do
+ expect(@options).to receive(:interrupt)
+ @script.options []
+ end
+
+ it "enables the formatter options" do
+ expect(@options).to receive(:formatters)
+ @script.options []
+ end
+
+ it "enables the verbose option" do
+ expect(@options).to receive(:verbose)
+ @script.options []
+ end
+
+ it "enables the action options" do
+ expect(@options).to receive(:actions)
+ @script.options []
+ end
+
+ it "enables the action filter options" do
+ expect(@options).to receive(:action_filters)
+ @script.options []
+ end
+
+ it "enables the version option" do
+ expect(@options).to receive(:version)
+ @script.options []
+ end
+
+ it "enables the help option" do
+ expect(@options).to receive(:help)
+ @script.options []
+ end
+
+ it "calls #custom_options" do
+ expect(@script).to receive(:custom_options).with(@options)
+ @script.options []
+ end
+end
+
+RSpec.describe MSpecCI, "#run" do
+ before :each do
+ allow(MSpec).to receive(:process)
+
+ @filter = double("TagFilter")
+ allow(TagFilter).to receive(:new).and_return(@filter)
+ allow(@filter).to receive(:register)
+
+ @tags = ["fails", "critical", "unstable", "incomplete", "unsupported"]
+
+ @config = { :ci_files => ["one", "two"] }
+ @script = MSpecCI.new
+ allow(@script).to receive(:exit)
+ allow(@script).to receive(:config).and_return(@config)
+ allow(@script).to receive(:files).and_return(["one", "two"])
+ @script.options []
+ end
+
+ it "registers the tags patterns" do
+ @config[:tags_patterns] = [/spec/, "tags"]
+ expect(MSpec).to receive(:register_tags_patterns).with([/spec/, "tags"])
+ @script.run
+ end
+
+ it "registers the files to process" do
+ expect(MSpec).to receive(:register_files).with(["one", "two"])
+ @script.run
+ end
+
+ it "registers a tag filter for 'fails', 'unstable', 'incomplete', 'critical', 'unsupported'" do
+ filter = double("fails filter")
+ expect(TagFilter).to receive(:new).with(:exclude, *@tags).and_return(filter)
+ expect(filter).to receive(:register)
+ @script.run
+ end
+
+ it "registers an additional exclude tag specified by :ci_xtags" do
+ @config[:ci_xtags] = "windows"
+ filter = double("fails filter")
+ expect(TagFilter).to receive(:new).with(:exclude, *(@tags + ["windows"])).and_return(filter)
+ expect(filter).to receive(:register)
+ @script.run
+ end
+
+ it "registers additional exclude tags specified by a :ci_xtags array" do
+ @config[:ci_xtags] = ["windows", "windoze"]
+ filter = double("fails filter")
+ expect(TagFilter).to receive(:new).with(:exclude,
+ *(@tags + ["windows", "windoze"])).and_return(filter)
+ expect(filter).to receive(:register)
+ @script.run
+ end
+
+ it "processes the files" do
+ expect(MSpec).to receive(:process)
+ @script.run
+ end
+
+ it "exits with the exit code registered with MSpec" do
+ allow(MSpec).to receive(:exit_code).and_return(7)
+ expect(@script).to receive(:exit).with(7)
+ @script.run
+ end
+end
diff --git a/spec/mspec/spec/commands/mspec_run_spec.rb b/spec/mspec/spec/commands/mspec_run_spec.rb
new file mode 100644
index 0000000000..62acd49d7f
--- /dev/null
+++ b/spec/mspec/spec/commands/mspec_run_spec.rb
@@ -0,0 +1,173 @@
+require 'spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/commands/mspec-run'
+
+one_spec = File.expand_path(File.dirname(__FILE__)) + '/fixtures/one_spec.rb'
+two_spec = File.expand_path(File.dirname(__FILE__)) + '/fixtures/two_spec.rb'
+
+RSpec.describe MSpecRun, ".new" do
+ before :each do
+ @script = MSpecRun.new
+ end
+
+ it "sets config[:files] to an empty list" do
+ expect(@script.config[:files]).to eq([])
+ end
+end
+
+RSpec.describe MSpecRun, "#options" do
+ before :each do
+ @argv = [one_spec, two_spec]
+ @options, @config = new_option
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+
+ @script = MSpecRun.new
+ allow(@script).to receive(:config).and_return(@config)
+ end
+
+ it "enables the filter options" do
+ expect(@options).to receive(:filters)
+ @script.options @argv
+ end
+
+ it "enables the chdir option" do
+ expect(@options).to receive(:chdir)
+ @script.options @argv
+ end
+
+ it "enables the prefix option" do
+ expect(@options).to receive(:prefix)
+ @script.options @argv
+ end
+
+ it "enables the configure option" do
+ expect(@options).to receive(:configure)
+ @script.options @argv
+ end
+
+ it "provides a custom action (block) to the config option" do
+ expect(@script).to receive(:load).with("cfg.mspec")
+ @script.options ["-B", "cfg.mspec", one_spec]
+ end
+
+ it "enables the randomize option to runs specs in random order" do
+ expect(@options).to receive(:randomize)
+ @script.options @argv
+ end
+
+ it "enables the dry run option" do
+ expect(@options).to receive(:pretend)
+ @script.options @argv
+ end
+
+ it "enables the unguarded option" do
+ expect(@options).to receive(:unguarded)
+ @script.options @argv
+ end
+
+ it "enables the interrupt single specs option" do
+ expect(@options).to receive(:interrupt)
+ @script.options @argv
+ end
+
+ it "enables the formatter options" do
+ expect(@options).to receive(:formatters)
+ @script.options @argv
+ end
+
+ it "enables the verbose option" do
+ expect(@options).to receive(:verbose)
+ @script.options @argv
+ end
+
+ it "enables the verify options" do
+ expect(@options).to receive(:verify)
+ @script.options @argv
+ end
+
+ it "enables the action options" do
+ expect(@options).to receive(:actions)
+ @script.options @argv
+ end
+
+ it "enables the action filter options" do
+ expect(@options).to receive(:action_filters)
+ @script.options @argv
+ end
+
+ it "enables the version option" do
+ expect(@options).to receive(:version)
+ @script.options @argv
+ end
+
+ it "enables the help option" do
+ expect(@options).to receive(:help)
+ @script.options @argv
+ end
+
+ it "exits if there are no files to process and './spec' is not a directory" do
+ expect(File).to receive(:directory?).with("./spec").and_return(false)
+ expect(@options).to receive(:parse).and_return([])
+ expect(@script).to receive(:abort).with("No files specified.")
+ @script.options
+ end
+
+ it "process 'spec/' if it is a directory and no files were specified" do
+ expect(File).to receive(:directory?).with("./spec").and_return(true)
+ expect(@options).to receive(:parse).and_return([])
+ expect(@script).to receive(:files).with(["spec/"]).and_return(["spec/a_spec.rb"])
+ @script.options
+ end
+
+ it "calls #custom_options" do
+ expect(@script).to receive(:custom_options).with(@options)
+ @script.options @argv
+ end
+end
+
+RSpec.describe MSpecRun, "#run" do
+ before :each do
+ @script = MSpecRun.new
+ allow(@script).to receive(:exit)
+ @spec_dir = File.expand_path(File.dirname(__FILE__)+"/fixtures")
+ @file_patterns = [
+ @spec_dir+"/level2",
+ @spec_dir+"/one_spec.rb",
+ @spec_dir+"/two_spec.rb"]
+ @files = [
+ @spec_dir+"/level2/three_spec.rb",
+ @spec_dir+"/one_spec.rb",
+ @spec_dir+"/two_spec.rb"]
+ @script.options @file_patterns
+ allow(MSpec).to receive :process
+ end
+
+ it "registers the tags patterns" do
+ @script.config[:tags_patterns] = [/spec/, "tags"]
+ expect(MSpec).to receive(:register_tags_patterns).with([/spec/, "tags"])
+ @script.run
+ end
+
+ it "registers the files to process" do
+ expect(MSpec).to receive(:register_files).with(@files)
+ @script.run
+ end
+
+ it "uses config[:files] if no files are given on the command line" do
+ @script.config[:files] = @file_patterns
+ expect(MSpec).to receive(:register_files).with(@files)
+ @script.options []
+ @script.run
+ end
+
+ it "processes the files" do
+ expect(MSpec).to receive(:process)
+ @script.run
+ end
+
+ it "exits with the exit code registered with MSpec" do
+ allow(MSpec).to receive(:exit_code).and_return(7)
+ expect(@script).to receive(:exit).with(7)
+ @script.run
+ end
+end
diff --git a/spec/mspec/spec/commands/mspec_spec.rb b/spec/mspec/spec/commands/mspec_spec.rb
new file mode 100644
index 0000000000..82201c2075
--- /dev/null
+++ b/spec/mspec/spec/commands/mspec_spec.rb
@@ -0,0 +1,207 @@
+require 'spec_helper'
+require 'yaml'
+require 'mspec/commands/mspec'
+
+RSpec.describe MSpecMain, "#options" do
+ before :each do
+ @options, @config = new_option
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+
+ @script = MSpecMain.new
+ allow(@script).to receive(:config).and_return(@config)
+ allow(@script).to receive(:load)
+ end
+
+ it "enables the configure option" do
+ expect(@options).to receive(:configure)
+ @script.options
+ end
+
+ it "provides a custom action (block) to the config option" do
+ @script.options ["-B", "config"]
+ expect(@config[:options]).to include("-B", "config")
+ end
+
+ it "loads the file specified by the config option" do
+ expect(@script).to receive(:load).with("config")
+ @script.options ["-B", "config"]
+ end
+
+ it "enables the target options" do
+ expect(@options).to receive(:targets)
+ @script.options
+ end
+
+ it "sets config[:options] to all argv entries that are not registered options" do
+ @options.on "-X", "--exclude", "ARG", "description"
+ @script.options [".", "-G", "fail", "-X", "ARG", "--list", "unstable", "some/file.rb"]
+ expect(@config[:options]).to eq([".", "-G", "fail", "--list", "unstable", "some/file.rb"])
+ end
+
+ it "calls #custom_options" do
+ expect(@script).to receive(:custom_options).with(@options)
+ @script.options
+ end
+end
+
+RSpec.describe MSpecMain, "#run" do
+ before :each do
+ @options, @config = new_option
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MSpecMain.new
+ allow(@script).to receive(:config).and_return(@config)
+ allow(@script).to receive(:exec)
+ @err = $stderr
+ $stderr = IOStub.new
+ end
+
+ after :each do
+ $stderr = @err
+ end
+
+ it "uses exec to invoke the runner script" do
+ expect(@script).to receive(:exec).with("ruby", "#{MSPEC_HOME}/bin/mspec-run", close_others: false)
+ @script.options []
+ @script.run
+ end
+
+ it "shows the command line on stderr" do
+ expect(@script).to receive(:exec).with("ruby", "#{MSPEC_HOME}/bin/mspec-run", close_others: false)
+ @script.options []
+ @script.run
+ expect($stderr.to_s).to eq("$ ruby #{Dir.pwd}/bin/mspec-run\n")
+ end
+
+ it "adds config[:launch] to the exec options" do
+ expect(@script).to receive(:exec).with("ruby",
+ "-Xlaunch.option", "#{MSPEC_HOME}/bin/mspec-run", close_others: false)
+ @config[:launch] << "-Xlaunch.option"
+ @script.options []
+ @script.run
+ expect($stderr.to_s).to eq("$ ruby -Xlaunch.option #{Dir.pwd}/bin/mspec-run\n")
+ end
+
+ it "calls #multi_exec if the command is 'ci' and the multi option is passed" do
+ expect(@script).to receive(:multi_exec) do |argv|
+ expect(argv).to eq(["ruby", "#{MSPEC_HOME}/bin/mspec-ci"])
+ end
+ @script.options ["ci", "-j"]
+ expect do
+ @script.run
+ end.to raise_error(SystemExit)
+ end
+end
+
+RSpec.describe "The --warnings option" do
+ before :each do
+ @options, @config = new_option
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MSpecMain.new
+ allow(@script).to receive(:config).and_return(@config)
+ end
+
+ it "is enabled by #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("--warnings", an_instance_of(String))
+ @script.options
+ end
+
+ it "sets flags to -w" do
+ @config[:flags] = []
+ @script.options ["--warnings"]
+ expect(@config[:flags]).to include("-w")
+ end
+
+ it "set OUTPUT_WARNINGS = '1' in the environment" do
+ ENV['OUTPUT_WARNINGS'] = '0'
+ @script.options ["--warnings"]
+ expect(ENV['OUTPUT_WARNINGS']).to eq('1')
+ end
+end
+
+RSpec.describe "The -j, --multi option" do
+ before :each do
+ @options, @config = new_option
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MSpecMain.new
+ allow(@script).to receive(:config).and_return(@config)
+ end
+
+ it "is enabled by #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-j", "--multi", an_instance_of(String))
+ @script.options
+ end
+
+ it "sets the multiple process option" do
+ ["-j", "--multi"].each do |opt|
+ @config[:multi] = nil
+ @script.options [opt]
+ expect(@config[:multi]).to eq(true)
+ end
+ end
+end
+
+RSpec.describe "The -h, --help option" do
+ before :each do
+ @options, @config = new_option
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MSpecMain.new
+ allow(@script).to receive(:config).and_return(@config)
+ end
+
+ it "is enabled by #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-h", "--help", an_instance_of(String))
+ @script.options
+ end
+
+ it "passes the option to the subscript" do
+ ["-h", "--help"].each do |opt|
+ @config[:options] = []
+ @script.options ["ci", opt]
+ expect(@config[:options].sort).to eq(["-h"])
+ end
+ end
+
+ it "prints help and exits" do
+ expect(@script).to receive(:puts).twice
+ expect(@script).to receive(:exit).twice
+ ["-h", "--help"].each do |opt|
+ @script.options [opt]
+ end
+ end
+end
+
+RSpec.describe "The -v, --version option" do
+ before :each do
+ @options, @config = new_option
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MSpecMain.new
+ allow(@script).to receive(:config).and_return(@config)
+ end
+
+ it "is enabled by #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-v", "--version", an_instance_of(String))
+ @script.options
+ end
+
+ it "passes the option to the subscripts" do
+ ["-v", "--version"].each do |opt|
+ @config[:options] = []
+ @script.options ["ci", opt]
+ expect(@config[:options].sort).to eq(["-v"])
+ end
+ end
+
+ it "prints the version and exits if no subscript is invoked" do
+ @config[:command] = nil
+ allow(File).to receive(:basename).and_return("mspec")
+ expect(@script).to receive(:puts).twice.with("mspec #{MSpec::VERSION}")
+ expect(@script).to receive(:exit).twice
+ ["-v", "--version"].each do |opt|
+ @script.options [opt]
+ end
+ end
+end
diff --git a/spec/mspec/spec/commands/mspec_tag_spec.rb b/spec/mspec/spec/commands/mspec_tag_spec.rb
new file mode 100644
index 0000000000..1ab5f6ea58
--- /dev/null
+++ b/spec/mspec/spec/commands/mspec_tag_spec.rb
@@ -0,0 +1,414 @@
+require 'spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/commands/mspec-tag'
+require 'mspec/runner/actions/tag'
+require 'mspec/runner/actions/taglist'
+require 'mspec/runner/actions/tagpurge'
+
+one_spec = File.expand_path(File.dirname(__FILE__)) + '/fixtures/one_spec.rb'
+two_spec = File.expand_path(File.dirname(__FILE__)) + '/fixtures/two_spec.rb'
+
+RSpec.describe MSpecTag, ".new" do
+ before :each do
+ @script = MSpecTag.new
+ end
+
+ it "sets config[:ltags] to an empty list" do
+ expect(@script.config[:ltags]).to eq([])
+ end
+
+ it "sets config[:tagger] to :add" do
+ @script.config[:tagger] = :add
+ end
+
+ it "sets config[:tag] to 'fails:'" do
+ @script.config[:tag] = 'fails:'
+ end
+
+ it "sets config[:outcome] to :fail" do
+ @script.config[:outcome] = :fail
+ end
+end
+
+RSpec.describe MSpecTag, "#options" do
+ before :each do
+ @stdout, $stdout = $stdout, IOStub.new
+
+ @argv = [one_spec, two_spec]
+ @options, @config = new_option
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+
+ @script = MSpecTag.new
+ allow(@script).to receive(:config).and_return(@config)
+ end
+
+ after :each do
+ $stdout = @stdout
+ end
+
+ it "enables the filter options" do
+ expect(@options).to receive(:filters)
+ @script.options @argv
+ end
+
+ it "enables the configure option" do
+ expect(@options).to receive(:configure)
+ @script.options @argv
+ end
+
+ it "provides a custom action (block) to the config option" do
+ expect(@script).to receive(:load).with("cfg.mspec")
+ @script.options ["-B", "cfg.mspec", one_spec]
+ end
+
+ it "enables the dry run option" do
+ expect(@options).to receive(:pretend)
+ @script.options @argv
+ end
+
+ it "enables the unguarded option" do
+ expect(@options).to receive(:unguarded)
+ @script.options @argv
+ end
+
+ it "enables the interrupt single specs option" do
+ expect(@options).to receive(:interrupt)
+ @script.options @argv
+ end
+
+ it "enables the formatter options" do
+ expect(@options).to receive(:formatters)
+ @script.options @argv
+ end
+
+ it "enables the verbose option" do
+ expect(@options).to receive(:verbose)
+ @script.options @argv
+ end
+
+ it "enables the version option" do
+ expect(@options).to receive(:version)
+ @script.options @argv
+ end
+
+ it "enables the help option" do
+ expect(@options).to receive(:help)
+ @script.options @argv
+ end
+
+ it "calls #custom_options" do
+ expect(@script).to receive(:custom_options).with(@options)
+ @script.options @argv
+ end
+
+ it "exits if there are no files to process" do
+ expect(@options).to receive(:parse).and_return([])
+ expect(@script).to receive(:exit)
+ @script.options
+ expect($stdout.to_s).to include "No files specified"
+ end
+end
+
+RSpec.describe MSpecTag, "options" do
+ before :each do
+ @options, @config = new_option
+ allow(MSpecOptions).to receive(:new).and_return(@options)
+ @script = MSpecTag.new
+ allow(@script).to receive(:config).and_return(@config)
+ end
+
+ describe "-N, --add TAG" do
+ it "is enabled with #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-N", "--add", "TAG", an_instance_of(String))
+ @script.options [one_spec]
+ end
+
+ it "sets the mode to :add and sets the tag to TAG" do
+ ["-N", "--add"].each do |opt|
+ @config[:tagger] = nil
+ @config[:tag] = nil
+ @script.options [opt, "taggit", one_spec]
+ expect(@config[:tagger]).to eq(:add)
+ expect(@config[:tag]).to eq("taggit:")
+ end
+ end
+ end
+
+ describe "-R, --del TAG" do
+ it "is enabled with #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-R", "--del", "TAG",
+ an_instance_of(String))
+ @script.options [one_spec]
+ end
+
+ it "it sets the mode to :del, the tag to TAG, and the outcome to :pass" do
+ ["-R", "--del"].each do |opt|
+ @config[:tagger] = nil
+ @config[:tag] = nil
+ @config[:outcome] = nil
+ @script.options [opt, "taggit", one_spec]
+ expect(@config[:tagger]).to eq(:del)
+ expect(@config[:tag]).to eq("taggit:")
+ expect(@config[:outcome]).to eq(:pass)
+ end
+ end
+ end
+
+ describe "-Q, --pass" do
+ it "is enabled with #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-Q", "--pass", an_instance_of(String))
+ @script.options [one_spec]
+ end
+
+ it "sets the outcome to :pass" do
+ ["-Q", "--pass"].each do |opt|
+ @config[:outcome] = nil
+ @script.options [opt, one_spec]
+ expect(@config[:outcome]).to eq(:pass)
+ end
+ end
+ end
+
+ describe "-F, --fail" do
+ it "is enabled with #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-F", "--fail", an_instance_of(String))
+ @script.options [one_spec]
+ end
+
+ it "sets the outcome to :fail" do
+ ["-F", "--fail"].each do |opt|
+ @config[:outcome] = nil
+ @script.options [opt, one_spec]
+ expect(@config[:outcome]).to eq(:fail)
+ end
+ end
+ end
+
+ describe "-L, --all" do
+ it "is enabled with #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-L", "--all", an_instance_of(String))
+ @script.options [one_spec]
+ end
+
+ it "sets the outcome to :all" do
+ ["-L", "--all"].each do |opt|
+ @config[:outcome] = nil
+ @script.options [opt, one_spec]
+ expect(@config[:outcome]).to eq(:all)
+ end
+ end
+ end
+
+ describe "--list TAG" do
+ it "is enabled with #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("--list", "TAG", an_instance_of(String))
+ @script.options [one_spec]
+ end
+
+ it "sets the mode to :list" do
+ @config[:tagger] = nil
+ @script.options ["--list", "TAG", one_spec]
+ expect(@config[:tagger]).to eq(:list)
+ end
+
+ it "sets ltags to include TAG" do
+ @config[:tag] = nil
+ @script.options ["--list", "TAG", one_spec]
+ expect(@config[:ltags]).to eq(["TAG"])
+ end
+ end
+
+ describe "--list-all" do
+ it "is enabled with #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("--list-all", an_instance_of(String))
+ @script.options [one_spec]
+ end
+
+ it "sets the mode to :list_all" do
+ @config[:tagger] = nil
+ @script.options ["--list-all", one_spec]
+ expect(@config[:tagger]).to eq(:list_all)
+ end
+ end
+
+ describe "--purge" do
+ it "is enabled with #options" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("--purge", an_instance_of(String))
+ @script.options [one_spec]
+ end
+
+ it "sets the mode to :purge" do
+ @config[:tagger] = nil
+ @script.options ["--purge", one_spec]
+ expect(@config[:tagger]).to eq(:purge)
+ end
+ end
+end
+
+RSpec.describe MSpecTag, "#run" do
+ before :each do
+ allow(MSpec).to receive(:process)
+
+ options = double("MSpecOptions").as_null_object
+ allow(options).to receive(:parse).and_return(["one", "two"])
+ allow(MSpecOptions).to receive(:new).and_return(options)
+
+ @config = { }
+ @script = MSpecTag.new
+ allow(@script).to receive(:exit)
+ allow(@script).to receive(:config).and_return(@config)
+ allow(@script).to receive(:files).and_return(["one", "two"])
+ @script.options
+ end
+
+ it "registers the tags patterns" do
+ @config[:tags_patterns] = [/spec/, "tags"]
+ expect(MSpec).to receive(:register_tags_patterns).with([/spec/, "tags"])
+ @script.run
+ end
+
+ it "registers the files to process" do
+ expect(MSpec).to receive(:register_files).with(["one", "two"])
+ @script.run
+ end
+
+ it "processes the files" do
+ expect(MSpec).to receive(:process)
+ @script.run
+ end
+
+ it "exits with the exit code registered with MSpec" do
+ allow(MSpec).to receive(:exit_code).and_return(7)
+ expect(@script).to receive(:exit).with(7)
+ @script.run
+ end
+end
+
+RSpec.describe MSpecTag, "#register" do
+ before :each do
+ @script = MSpecTag.new
+ @config = @script.config
+ @config[:tag] = "fake:"
+ @config[:atags] = []
+ @config[:astrings] = []
+ @config[:ltags] = ["fails", "unstable"]
+
+ allow(@script).to receive(:files).and_return([])
+ @script.options "fake"
+
+ @t = double("TagAction")
+ allow(@t).to receive(:register)
+
+ @tl = double("TagListAction")
+ allow(@tl).to receive(:register)
+ end
+
+ it "raises an ArgumentError if no recognized action is given" do
+ @config[:tagger] = :totally_whack
+ expect { @script.register }.to raise_error(ArgumentError)
+ end
+
+ describe "when config[:tagger] is the default (:add)" do
+ before :each do
+ @config[:formatter] = false
+ end
+
+ it "creates a TagAction" do
+ expect(TagAction).to receive(:new).and_return(@t)
+ @script.register
+ end
+
+ it "creates a TagAction if config[:tagger] is :del" do
+ @config[:tagger] = :del
+ @config[:outcome] = :pass
+ expect(TagAction).to receive(:new).with(:del, :pass, "fake", nil, [], []).and_return(@t)
+ @script.register
+ end
+
+ it "calls #register on the TagAction instance" do
+ expect(TagAction).to receive(:new).and_return(@t)
+ expect(@t).to receive(:register)
+ @script.register
+ end
+ end
+
+ describe "when config[:tagger] is :list" do
+ before :each do
+ expect(TagListAction).to receive(:new).with(@config[:ltags]).and_return(@tl)
+ @config[:tagger] = :list
+ end
+
+ it "creates a TagListAction" do
+ expect(@tl).to receive(:register)
+ @script.register
+ end
+
+ it "registers MSpec pretend mode" do
+ expect(MSpec).to receive(:register_mode).with(:pretend)
+ @script.register
+ end
+
+ it "sets config[:formatter] to false" do
+ @script.register
+ expect(@config[:formatter]).to be_falsey
+ end
+ end
+
+ describe "when config[:tagger] is :list_all" do
+ before :each do
+ expect(TagListAction).to receive(:new).with(nil).and_return(@tl)
+ @config[:tagger] = :list_all
+ end
+
+ it "creates a TagListAction" do
+ expect(@tl).to receive(:register)
+ @script.register
+ end
+
+ it "registers MSpec pretend mode" do
+ expect(MSpec).to receive(:register_mode).with(:pretend)
+ @script.register
+ end
+
+ it "sets config[:formatter] to false" do
+ @script.register
+ expect(@config[:formatter]).to be_falsey
+ end
+ end
+
+ describe "when config[:tagger] is :purge" do
+ before :each do
+ expect(TagPurgeAction).to receive(:new).and_return(@tl)
+ allow(MSpec).to receive(:register_mode)
+ @config[:tagger] = :purge
+ end
+
+ it "creates a TagPurgeAction" do
+ expect(@tl).to receive(:register)
+ @script.register
+ end
+
+ it "registers MSpec in pretend mode" do
+ expect(MSpec).to receive(:register_mode).with(:pretend)
+ @script.register
+ end
+
+ it "registers MSpec in unguarded mode" do
+ expect(MSpec).to receive(:register_mode).with(:unguarded)
+ @script.register
+ end
+
+ it "sets config[:formatter] to false" do
+ @script.register
+ expect(@config[:formatter]).to be_falsey
+ end
+ end
+end
diff --git a/spec/mspec/spec/expectations/expectations_spec.rb b/spec/mspec/spec/expectations/expectations_spec.rb
new file mode 100644
index 0000000000..371829d4f9
--- /dev/null
+++ b/spec/mspec/spec/expectations/expectations_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+
+RSpec.describe SpecExpectationNotMetError do
+ it "is a subclass of StandardError" do
+ expect(SpecExpectationNotMetError.ancestors).to include(StandardError)
+ end
+end
+
+RSpec.describe SpecExpectationNotFoundError do
+ it "is a subclass of StandardError" do
+ expect(SpecExpectationNotFoundError.ancestors).to include(StandardError)
+ end
+end
+
+RSpec.describe SpecExpectationNotFoundError, "#message" do
+ it "returns 'No behavior expectation was found in the example'" do
+ m = SpecExpectationNotFoundError.new.message
+ expect(m).to eq("No behavior expectation was found in the example")
+ end
+end
+
+RSpec.describe SpecExpectation, "#fail_with" do
+ it "raises an SpecExpectationNotMetError" do
+ expect {
+ SpecExpectation.fail_with "expected this", "to equal that"
+ }.to raise_error(SpecExpectationNotMetError, "expected this to equal that")
+ end
+end
diff --git a/spec/mspec/spec/expectations/should_spec.rb b/spec/mspec/spec/expectations/should_spec.rb
new file mode 100644
index 0000000000..472890979d
--- /dev/null
+++ b/spec/mspec/spec/expectations/should_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+require 'rbconfig'
+
+RSpec.describe "MSpec" do
+ before :all do
+ path = RbConfig::CONFIG['bindir']
+ exe = RbConfig::CONFIG['ruby_install_name']
+ file = File.expand_path('../../fixtures/should.rb', __FILE__)
+ @out = `#{path}/#{exe} #{file}`
+ end
+
+ describe "#should" do
+ it "records failures" do
+ expect(@out).to include <<-EOS
+1)
+MSpec expectation method #should causes a failure to be recorded FAILED
+Expected 1 == 2
+to be truthy but was false
+EOS
+ end
+
+ it "raises exceptions for examples with no expectations" do
+ expect(@out).to include <<-EOS
+2)
+MSpec expectation method #should registers that an expectation has been encountered FAILED
+No behavior expectation was found in the example
+EOS
+ end
+ end
+
+ describe "#should_not" do
+ it "records failures" do
+ expect(@out).to include <<-EOS
+3)
+MSpec expectation method #should_not causes a failure to be recorded FAILED
+Expected 1 == 1
+to be falsy but was true
+EOS
+ end
+
+ it "raises exceptions for examples with no expectations" do
+ expect(@out).to include <<-EOS
+4)
+MSpec expectation method #should_not registers that an expectation has been encountered FAILED
+No behavior expectation was found in the example
+EOS
+ end
+ end
+
+ it "prints status information" do
+ expect(@out).to include ".FF..FF."
+ end
+
+ it "prints out a summary" do
+ expect(@out).to include "0 files, 8 examples, 6 expectations, 4 failures, 0 errors"
+ end
+
+ it "records expectations" do
+ expect(@out).to include "I was called 6 times"
+ end
+end
diff --git a/spec/mspec/spec/fixtures/a_spec.rb b/spec/mspec/spec/fixtures/a_spec.rb
new file mode 100644
index 0000000000..17a7e8b664
--- /dev/null
+++ b/spec/mspec/spec/fixtures/a_spec.rb
@@ -0,0 +1,15 @@
+unless defined?(RSpec)
+ describe "Foo#bar" do
+ it "passes" do
+ 1.should == 1
+ end
+
+ it "errors" do
+ 1.should == 2
+ end
+
+ it "fails" do
+ raise "failure"
+ end
+ end
+end
diff --git a/spec/mspec/spec/fixtures/b_spec.rb b/spec/mspec/spec/fixtures/b_spec.rb
new file mode 100644
index 0000000000..f1f63317cb
--- /dev/null
+++ b/spec/mspec/spec/fixtures/b_spec.rb
@@ -0,0 +1,7 @@
+unless defined?(RSpec)
+ describe "Bar#baz" do
+ it "works" do
+ 1.should == 1
+ end
+ end
+end
diff --git a/spec/mspec/spec/fixtures/chatty_spec.rb b/spec/mspec/spec/fixtures/chatty_spec.rb
new file mode 100644
index 0000000000..2d110d8ce4
--- /dev/null
+++ b/spec/mspec/spec/fixtures/chatty_spec.rb
@@ -0,0 +1,8 @@
+unless defined?(RSpec)
+ describe "Chatty#spec" do
+ it "prints too much" do
+ STDOUT.puts "Hello\nIt's me!"
+ 1.should == 1
+ end
+ end
+end
diff --git a/spec/mspec/spec/fixtures/config.mspec b/spec/mspec/spec/fixtures/config.mspec
new file mode 100644
index 0000000000..01654c5094
--- /dev/null
+++ b/spec/mspec/spec/fixtures/config.mspec
@@ -0,0 +1,8 @@
+class MSpecScript
+ set :target, 'ruby'
+
+ set :tags_patterns, [
+ [%r(spec/fixtures/), 'spec/fixtures/tags/'],
+ [/_spec.rb$/, '_tags.txt']
+ ]
+end
diff --git a/spec/mspec/spec/fixtures/die_spec.rb b/spec/mspec/spec/fixtures/die_spec.rb
new file mode 100644
index 0000000000..0f66793274
--- /dev/null
+++ b/spec/mspec/spec/fixtures/die_spec.rb
@@ -0,0 +1,7 @@
+unless defined?(RSpec)
+ describe "Deadly#spec" do
+ it "dies" do
+ abort "DEAD"
+ end
+ end
+end
diff --git a/spec/mspec/spec/fixtures/my_ruby b/spec/mspec/spec/fixtures/my_ruby
new file mode 100755
index 0000000000..eeda3eeeec
--- /dev/null
+++ b/spec/mspec/spec/fixtures/my_ruby
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+echo $RUBY_EXE
+exec ruby "$@"
diff --git a/spec/mspec/spec/fixtures/object_methods_spec.rb b/spec/mspec/spec/fixtures/object_methods_spec.rb
new file mode 100644
index 0000000000..9b7c1523e5
--- /dev/null
+++ b/spec/mspec/spec/fixtures/object_methods_spec.rb
@@ -0,0 +1,8 @@
+unless defined?(RSpec)
+ describe "Object" do
+ it ".public_instance_methods(false) is empty" do
+ Object.public_instance_methods(false).sort.should ==
+ [:should, :should_not, :should_not_receive, :should_receive, :stub!]
+ end
+ end
+end
diff --git a/spec/mspec/spec/fixtures/print_interpreter_spec.rb b/spec/mspec/spec/fixtures/print_interpreter_spec.rb
new file mode 100644
index 0000000000..a662346d0a
--- /dev/null
+++ b/spec/mspec/spec/fixtures/print_interpreter_spec.rb
@@ -0,0 +1,4 @@
+unless defined?(RSpec)
+ puts ENV["RUBY_EXE"]
+ puts ruby_cmd(nil).split.first
+end
diff --git a/spec/mspec/spec/fixtures/should.rb b/spec/mspec/spec/fixtures/should.rb
new file mode 100644
index 0000000000..f494775c5f
--- /dev/null
+++ b/spec/mspec/spec/fixtures/should.rb
@@ -0,0 +1,75 @@
+$: << File.dirname(__FILE__) + '/../../lib'
+require 'mspec'
+require 'mspec/utils/script'
+
+# The purpose of these specs is to confirm that the #should
+# and #should_not methods are functioning appropriately. We
+# use a separate spec file that is invoked from the MSpec
+# specs but is run by MSpec. This avoids conflicting with
+# RSpec's #should and #should_not methods.
+
+raise "RSpec should not be loaded" if defined?(RSpec)
+
+class ShouldSpecsMonitor
+ def initialize
+ @called = 0
+ end
+
+ def expectation(state)
+ @called += 1
+ end
+
+ def finish
+ puts "I was called #{@called} times"
+ end
+end
+
+# Simplistic runner
+formatter = DottedFormatter.new
+formatter.register
+
+monitor = ShouldSpecsMonitor.new
+MSpec.register :expectation, monitor
+MSpec.register :finish, monitor
+
+at_exit { MSpec.actions :finish }
+
+MSpec.actions :start
+MSpec.setup_env
+
+# Specs
+describe "MSpec expectation method #should" do
+ it "accepts a matcher" do
+ :sym.should be_kind_of(Symbol)
+ end
+
+ it "causes a failure to be recorded" do
+ 1.should == 2
+ end
+
+ it "registers that an expectation has been encountered" do
+ # an empty example block causes an exception because
+ # no expectation was encountered
+ end
+
+ it "invokes the MSpec :expectation actions" do
+ 1.should == 1
+ end
+end
+
+describe "MSpec expectation method #should_not" do
+ it "accepts a matcher" do
+ "sym".should_not be_kind_of(Symbol)
+ end
+
+ it "causes a failure to be recorded" do
+ 1.should_not == 1
+ end
+
+ it "registers that an expectation has been encountered" do
+ end
+
+ it "invokes the MSpec :expectation actions" do
+ 1.should_not == 2
+ end
+end
diff --git a/spec/mspec/spec/fixtures/tagging_spec.rb b/spec/mspec/spec/fixtures/tagging_spec.rb
new file mode 100644
index 0000000000..0097fd1808
--- /dev/null
+++ b/spec/mspec/spec/fixtures/tagging_spec.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+unless defined?(RSpec)
+ describe "Tag#me" do
+ it "passes" do
+ 1.should == 1
+ end
+
+ it "errors" do
+ 1.should == 2
+ end
+
+ it "érròrs in unicode" do
+ 1.should == 2
+ end
+ end
+end
diff --git a/spec/mspec/spec/guards/block_device_spec.rb b/spec/mspec/spec/guards/block_device_spec.rb
new file mode 100644
index 0000000000..dd420d4a81
--- /dev/null
+++ b/spec/mspec/spec/guards/block_device_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+RSpec.describe Object, "#with_block_device" do
+ before :each do
+ ScratchPad.clear
+
+ @guard = BlockDeviceGuard.new
+ allow(BlockDeviceGuard).to receive(:new).and_return(@guard)
+ end
+
+ platform_is_not :freebsd, :windows do
+ it "yields if block device is available" do
+ expect(@guard).to receive(:`).and_return("block devices")
+ with_block_device { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield if block device is not available" do
+ expect(@guard).to receive(:`).and_return(nil)
+ with_block_device { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+ end
+
+ platform_is :freebsd, :windows do
+ it "does not yield, since platform does not support block devices" do
+ expect(@guard).not_to receive(:`)
+ with_block_device { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+ end
+
+ it "sets the name of the guard to :with_block_device" do
+ with_block_device { }
+ expect(@guard.name).to eq(:with_block_device)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:match?).and_return(true)
+ expect(@guard).to receive(:unregister)
+ expect do
+ with_block_device { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
diff --git a/spec/mspec/spec/guards/bug_spec.rb b/spec/mspec/spec/guards/bug_spec.rb
new file mode 100644
index 0000000000..72a3405dbc
--- /dev/null
+++ b/spec/mspec/spec/guards/bug_spec.rb
@@ -0,0 +1,151 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+RSpec.describe BugGuard, "#match? when #implementation? is 'ruby'" do
+ before :all do
+ @verbose = $VERBOSE
+ $VERBOSE = nil
+ end
+
+ after :all do
+ $VERBOSE = @verbose
+ end
+
+ before :each do
+ hide_deprecation_warnings
+ stub_const "VersionGuard::FULL_RUBY_VERSION", SpecVersion.new('1.8.6')
+ @ruby_engine = Object.const_get :RUBY_ENGINE
+ Object.const_set :RUBY_ENGINE, 'ruby'
+ end
+
+ after :each do
+ Object.const_set :RUBY_ENGINE, @ruby_engine
+ end
+
+ it "returns false when version argument is less than RUBY_VERSION" do
+ expect(BugGuard.new("#1", "1.8.5").match?).to eq(false)
+ end
+
+ it "returns true when version argument is equal to RUBY_VERSION" do
+ expect(BugGuard.new("#1", "1.8.6").match?).to eq(true)
+ end
+
+ it "returns true when version argument is greater than RUBY_VERSION" do
+ expect(BugGuard.new("#1", "1.8.7").match?).to eq(true)
+ end
+
+ it "returns true when version argument implicitly includes RUBY_VERSION" do
+ expect(BugGuard.new("#1", "1.8").match?).to eq(true)
+ expect(BugGuard.new("#1", "1.8.6").match?).to eq(true)
+ end
+
+ it "returns true when the argument range includes RUBY_VERSION" do
+ expect(BugGuard.new("#1", '1.8.5'..'1.8.7').match?).to eq(true)
+ expect(BugGuard.new("#1", '1.8'..'1.9').match?).to eq(true)
+ expect(BugGuard.new("#1", '1.8'...'1.9').match?).to eq(true)
+ expect(BugGuard.new("#1", '1.8'..'1.8.6').match?).to eq(true)
+ expect(BugGuard.new("#1", '1.8.5'..'1.8.6').match?).to eq(true)
+ expect(BugGuard.new("#1", ''...'1.8.7').match?).to eq(true)
+ end
+
+ it "returns false when the argument range does not include RUBY_VERSION" do
+ expect(BugGuard.new("#1", '1.8.7'..'1.8.9').match?).to eq(false)
+ expect(BugGuard.new("#1", '1.8.4'..'1.8.5').match?).to eq(false)
+ expect(BugGuard.new("#1", '1.8.4'...'1.8.6').match?).to eq(false)
+ expect(BugGuard.new("#1", '1.8.5'...'1.8.6').match?).to eq(false)
+ expect(BugGuard.new("#1", ''...'1.8.6').match?).to eq(false)
+ end
+
+ it "returns false when MSpec.mode?(:no_ruby_bug) is true" do
+ expect(MSpec).to receive(:mode?).with(:no_ruby_bug).twice.and_return(:true)
+ expect(BugGuard.new("#1", "1.8.5").match?).to eq(false)
+ expect(BugGuard.new("#1", "1.8").match?).to eq(false)
+ end
+end
+
+RSpec.describe BugGuard, "#match? when #implementation? is not 'ruby'" do
+ before :all do
+ @verbose = $VERBOSE
+ $VERBOSE = nil
+ end
+
+ after :all do
+ $VERBOSE = @verbose
+ end
+
+ before :each do
+ hide_deprecation_warnings
+ @ruby_version = Object.const_get :RUBY_VERSION
+ @ruby_engine = Object.const_get :RUBY_ENGINE
+
+ Object.const_set :RUBY_VERSION, '1.8.6'
+ Object.const_set :RUBY_ENGINE, 'jruby'
+ end
+
+ after :each do
+ Object.const_set :RUBY_VERSION, @ruby_version
+ Object.const_set :RUBY_ENGINE, @ruby_engine
+ end
+
+ it "returns false when version argument is less than RUBY_VERSION" do
+ expect(BugGuard.new("#1", "1.8").match?).to eq(false)
+ expect(BugGuard.new("#1", "1.8.6").match?).to eq(false)
+ end
+
+ it "returns false when version argument is equal to RUBY_VERSION" do
+ expect(BugGuard.new("#1", "1.8.6").match?).to eq(false)
+ end
+
+ it "returns false when version argument is greater than RUBY_VERSION" do
+ expect(BugGuard.new("#1", "1.8.7").match?).to eq(false)
+ end
+
+ it "returns false no matter if the argument range includes RUBY_VERSION" do
+ expect(BugGuard.new("#1", '1.8'...'1.9').match?).to eq(false)
+ expect(BugGuard.new("#1", '1.8.5'...'1.8.7').match?).to eq(false)
+ expect(BugGuard.new("#1", '1.8.4'...'1.8.6').match?).to eq(false)
+ end
+
+ it "returns false when MSpec.mode?(:no_ruby_bug) is true" do
+ allow(MSpec).to receive(:mode?).and_return(:true)
+ expect(BugGuard.new("#1", "1.8.6").match?).to eq(false)
+ end
+end
+
+RSpec.describe Object, "#ruby_bug" do
+ before :each do
+ hide_deprecation_warnings
+ @guard = BugGuard.new "#1234", "x.x.x"
+ allow(BugGuard).to receive(:new).and_return(@guard)
+ ScratchPad.clear
+ end
+
+ it "yields when #match? returns false" do
+ allow(@guard).to receive(:match?).and_return(false)
+ ruby_bug("#1234", "1.8.6") { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield when #match? returns true" do
+ allow(@guard).to receive(:match?).and_return(true)
+ ruby_bug("#1234", "1.8.6") { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "requires a bug tracker number and a version number" do
+ expect { ruby_bug { } }.to raise_error(ArgumentError)
+ expect { ruby_bug("#1234") { } }.to raise_error(ArgumentError)
+ end
+
+ it "sets the name of the guard to :ruby_bug" do
+ ruby_bug("#1234", "1.8.6") { }
+ expect(@guard.name).to eq(:ruby_bug)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:unregister)
+ expect do
+ ruby_bug("", "") { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
diff --git a/spec/mspec/spec/guards/conflict_spec.rb b/spec/mspec/spec/guards/conflict_spec.rb
new file mode 100644
index 0000000000..7dbe83153d
--- /dev/null
+++ b/spec/mspec/spec/guards/conflict_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+RSpec.describe Object, "#conflicts_with" do
+ before :each do
+ hide_deprecation_warnings
+ ScratchPad.clear
+ end
+
+ it "does not yield if Object.constants includes any of the arguments" do
+ allow(Object).to receive(:constants).and_return(["SomeClass", "OtherClass"])
+ conflicts_with(:SomeClass, :AClass, :BClass) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "does not yield if Object.constants (as Symbols) includes any of the arguments" do
+ allow(Object).to receive(:constants).and_return([:SomeClass, :OtherClass])
+ conflicts_with(:SomeClass, :AClass, :BClass) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "yields if Object.constants does not include any of the arguments" do
+ allow(Object).to receive(:constants).and_return(["SomeClass", "OtherClass"])
+ conflicts_with(:AClass, :BClass) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "yields if Object.constants (as Symbols) does not include any of the arguments" do
+ allow(Object).to receive(:constants).and_return([:SomeClass, :OtherClass])
+ conflicts_with(:AClass, :BClass) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+end
+
+RSpec.describe Object, "#conflicts_with" do
+ before :each do
+ hide_deprecation_warnings
+ @guard = ConflictsGuard.new
+ allow(ConflictsGuard).to receive(:new).and_return(@guard)
+ end
+
+ it "sets the name of the guard to :conflicts_with" do
+ conflicts_with(:AClass, :BClass) { }
+ expect(@guard.name).to eq(:conflicts_with)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:unregister)
+ expect do
+ conflicts_with(:AClass, :BClass) { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
diff --git a/spec/mspec/spec/guards/endian_spec.rb b/spec/mspec/spec/guards/endian_spec.rb
new file mode 100644
index 0000000000..943b558ed3
--- /dev/null
+++ b/spec/mspec/spec/guards/endian_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+RSpec.describe Object, "#big_endian" do
+ before :each do
+ @guard = BigEndianGuard.new
+ allow(BigEndianGuard).to receive(:new).and_return(@guard)
+ ScratchPad.clear
+ end
+
+ it "yields on big-endian platforms" do
+ allow(@guard).to receive(:pattern).and_return([?\001])
+ big_endian { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield on little-endian platforms" do
+ allow(@guard).to receive(:pattern).and_return([?\000])
+ big_endian { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "sets the name of the guard to :big_endian" do
+ big_endian { }
+ expect(@guard.name).to eq(:big_endian)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ allow(@guard).to receive(:pattern).and_return([?\001])
+ expect(@guard).to receive(:unregister)
+ expect do
+ big_endian { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
+
+RSpec.describe Object, "#little_endian" do
+ before :each do
+ @guard = BigEndianGuard.new
+ allow(BigEndianGuard).to receive(:new).and_return(@guard)
+ ScratchPad.clear
+ end
+
+ it "yields on little-endian platforms" do
+ allow(@guard).to receive(:pattern).and_return([?\000])
+ little_endian { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield on big-endian platforms" do
+ allow(@guard).to receive(:pattern).and_return([?\001])
+ little_endian { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+end
diff --git a/spec/mspec/spec/guards/feature_spec.rb b/spec/mspec/spec/guards/feature_spec.rb
new file mode 100644
index 0000000000..fcb8997591
--- /dev/null
+++ b/spec/mspec/spec/guards/feature_spec.rb
@@ -0,0 +1,120 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+RSpec.describe FeatureGuard, ".enabled?" do
+ it "returns true if the feature is enabled" do
+ expect(MSpec).to receive(:feature_enabled?).with(:encoding).and_return(true)
+ expect(FeatureGuard.enabled?(:encoding)).to be_truthy
+ end
+
+ it "returns false if the feature is not enabled" do
+ expect(MSpec).to receive(:feature_enabled?).with(:encoding).and_return(false)
+ expect(FeatureGuard.enabled?(:encoding)).to be_falsey
+ end
+
+ it "returns true if all the features are enabled" do
+ expect(MSpec).to receive(:feature_enabled?).with(:one).and_return(true)
+ expect(MSpec).to receive(:feature_enabled?).with(:two).and_return(true)
+ expect(FeatureGuard.enabled?(:one, :two)).to be_truthy
+ end
+
+ it "returns false if any of the features are not enabled" do
+ expect(MSpec).to receive(:feature_enabled?).with(:one).and_return(true)
+ expect(MSpec).to receive(:feature_enabled?).with(:two).and_return(false)
+ expect(FeatureGuard.enabled?(:one, :two)).to be_falsey
+ end
+end
+
+RSpec.describe Object, "#with_feature" do
+ before :each do
+ ScratchPad.clear
+
+ @guard = FeatureGuard.new :encoding
+ allow(FeatureGuard).to receive(:new).and_return(@guard)
+ end
+
+ it "sets the name of the guard to :with_feature" do
+ with_feature(:encoding) { }
+ expect(@guard.name).to eq(:with_feature)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:match?).and_return(true)
+ expect(@guard).to receive(:unregister)
+ expect do
+ with_feature { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
+
+RSpec.describe Object, "#with_feature" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "yields if the feature is enabled" do
+ expect(MSpec).to receive(:feature_enabled?).with(:encoding).and_return(true)
+ with_feature(:encoding) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "yields if all the features are enabled" do
+ expect(MSpec).to receive(:feature_enabled?).with(:one).and_return(true)
+ expect(MSpec).to receive(:feature_enabled?).with(:two).and_return(true)
+ with_feature(:one, :two) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield if the feature is not enabled" do
+ expect(MSpec).to receive(:feature_enabled?).with(:encoding).and_return(false)
+ with_feature(:encoding) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to be_nil
+ end
+
+ it "does not yield if any of the features are not enabled" do
+ expect(MSpec).to receive(:feature_enabled?).with(:one).and_return(true)
+ expect(MSpec).to receive(:feature_enabled?).with(:two).and_return(false)
+ with_feature(:one, :two) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to be_nil
+ end
+end
+
+RSpec.describe Object, "#without_feature" do
+ before :each do
+ ScratchPad.clear
+
+ @guard = FeatureGuard.new :encoding
+ allow(FeatureGuard).to receive(:new).and_return(@guard)
+ end
+
+ it "sets the name of the guard to :without_feature" do
+ without_feature(:encoding) { }
+ expect(@guard.name).to eq(:without_feature)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:match?).and_return(false)
+ expect(@guard).to receive(:unregister)
+ expect do
+ without_feature { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
+
+RSpec.describe Object, "#without_feature" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "does not yield if the feature is enabled" do
+ expect(MSpec).to receive(:feature_enabled?).with(:encoding).and_return(true)
+ without_feature(:encoding) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to be_nil
+ end
+
+ it "yields if the feature is disabled" do
+ expect(MSpec).to receive(:feature_enabled?).with(:encoding).and_return(false)
+ without_feature(:encoding) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+end
diff --git a/spec/mspec/spec/guards/guard_spec.rb b/spec/mspec/spec/guards/guard_spec.rb
new file mode 100644
index 0000000000..e29d235747
--- /dev/null
+++ b/spec/mspec/spec/guards/guard_spec.rb
@@ -0,0 +1,421 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'rbconfig'
+
+RSpec.describe SpecGuard, ".ruby_version" do
+ before :each do
+ stub_const "RUBY_VERSION", "8.2.3"
+ end
+
+ it "returns the full version for :full" do
+ expect(SpecGuard.ruby_version(:full)).to eq("8.2.3")
+ end
+
+ it "returns major.minor.tiny for :tiny" do
+ expect(SpecGuard.ruby_version(:tiny)).to eq("8.2.3")
+ end
+
+ it "returns major.minor.tiny for :teeny" do
+ expect(SpecGuard.ruby_version(:tiny)).to eq("8.2.3")
+ end
+
+ it "returns major.minor for :minor" do
+ expect(SpecGuard.ruby_version(:minor)).to eq("8.2")
+ end
+
+ it "defaults to :minor" do
+ expect(SpecGuard.ruby_version).to eq("8.2")
+ end
+
+ it "returns major for :major" do
+ expect(SpecGuard.ruby_version(:major)).to eq("8")
+ end
+end
+
+RSpec.describe SpecGuard, "#yield?" do
+ before :each do
+ MSpec.clear_modes
+ @guard = SpecGuard.new
+ allow(@guard).to receive(:match?).and_return(false)
+ end
+
+ after :each do
+ MSpec.unregister :add, @guard
+ MSpec.clear_modes
+ SpecGuard.clear_guards
+ end
+
+ it "returns true if MSpec.mode?(:unguarded) is true" do
+ MSpec.register_mode :unguarded
+ expect(@guard.yield?).to eq(true)
+ end
+
+ it "returns true if MSpec.mode?(:verify) is true" do
+ MSpec.register_mode :verify
+ expect(@guard.yield?).to eq(true)
+ end
+
+ it "returns true if MSpec.mode?(:verify) is true regardless of invert being true" do
+ MSpec.register_mode :verify
+ expect(@guard.yield?(true)).to eq(true)
+ end
+
+ it "returns true if MSpec.mode?(:report) is true" do
+ MSpec.register_mode :report
+ expect(@guard.yield?).to eq(true)
+ end
+
+ it "returns true if MSpec.mode?(:report) is true regardless of invert being true" do
+ MSpec.register_mode :report
+ expect(@guard.yield?(true)).to eq(true)
+ end
+
+ it "returns true if MSpec.mode?(:report_on) is true and SpecGuards.guards contains the named guard" do
+ MSpec.register_mode :report_on
+ SpecGuard.guards << :guard_name
+ expect(@guard.yield?).to eq(false)
+ @guard.name = :guard_name
+ expect(@guard.yield?).to eq(true)
+ end
+
+ it "returns #match? if neither report nor verify mode are true" do
+ allow(@guard).to receive(:match?).and_return(false)
+ expect(@guard.yield?).to eq(false)
+ allow(@guard).to receive(:match?).and_return(true)
+ expect(@guard.yield?).to eq(true)
+ end
+
+ it "returns #match? if invert is true and neither report nor verify mode are true" do
+ allow(@guard).to receive(:match?).and_return(false)
+ expect(@guard.yield?(true)).to eq(true)
+ allow(@guard).to receive(:match?).and_return(true)
+ expect(@guard.yield?(true)).to eq(false)
+ end
+end
+
+RSpec.describe SpecGuard, "#match?" do
+ before :each do
+ @guard = SpecGuard.new
+ end
+
+ it "must be implemented in subclasses" do
+ expect {
+ @guard.match?
+ }.to raise_error("must be implemented by the subclass")
+ end
+end
+
+RSpec.describe SpecGuard, "#unregister" do
+ before :each do
+ allow(MSpec).to receive(:unregister)
+ @guard = SpecGuard.new
+ end
+
+ it "unregisters from MSpec :add actions" do
+ expect(MSpec).to receive(:unregister).with(:add, @guard)
+ @guard.unregister
+ end
+end
+
+RSpec.describe SpecGuard, "#record" do
+ after :each do
+ SpecGuard.clear
+ end
+
+ it "saves the name of the guarded spec under the name of the guard" do
+ guard = SpecGuard.new "a", "1.8"..."1.9"
+ guard.name = :named_guard
+ guard.record "SomeClass#action returns true"
+ expect(SpecGuard.report).to eq({
+ 'named_guard a, 1.8...1.9' => ["SomeClass#action returns true"]
+ })
+ end
+end
+
+RSpec.describe SpecGuard, ".guards" do
+ it "returns an Array" do
+ expect(SpecGuard.guards).to be_kind_of(Array)
+ end
+end
+
+RSpec.describe SpecGuard, ".clear_guards" do
+ it "resets the array to empty" do
+ SpecGuard.guards << :guard
+ expect(SpecGuard.guards).to eq([:guard])
+ SpecGuard.clear_guards
+ expect(SpecGuard.guards).to eq([])
+ end
+end
+
+RSpec.describe SpecGuard, ".finish" do
+ before :each do
+ $stdout = @out = IOStub.new
+ end
+
+ after :each do
+ $stdout = STDOUT
+ SpecGuard.clear
+ end
+
+ it "prints the descriptions of the guarded specs" do
+ guard = SpecGuard.new "a", "1.8"..."1.9"
+ guard.name = :named_guard
+ guard.record "SomeClass#action returns true"
+ guard.record "SomeClass#reverse returns false"
+ SpecGuard.finish
+ expect($stdout).to eq(%[
+
+2 specs omitted by guard: named_guard a, 1.8...1.9:
+
+SomeClass#action returns true
+SomeClass#reverse returns false
+
+])
+ end
+end
+
+RSpec.describe SpecGuard, ".run_if" do
+ before :each do
+ @guard = SpecGuard.new
+ ScratchPad.clear
+ end
+
+ it "yields if match? returns true" do
+ allow(@guard).to receive(:match?).and_return(true)
+ @guard.run_if(:name) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield if match? returns false" do
+ allow(@guard).to receive(:match?).and_return(false)
+ @guard.run_if(:name) { fail }
+ end
+
+ it "returns the result of the block if match? is true" do
+ allow(@guard).to receive(:match?).and_return(true)
+ expect(@guard.run_if(:name) { 42 }).to eq(42)
+ end
+
+ it "returns nil if given a block and match? is false" do
+ allow(@guard).to receive(:match?).and_return(false)
+ expect(@guard.run_if(:name) { 42 }).to eq(nil)
+ end
+
+ it "returns what #match? returns when no block is given" do
+ allow(@guard).to receive(:match?).and_return(true)
+ expect(@guard.run_if(:name)).to eq(true)
+ allow(@guard).to receive(:match?).and_return(false)
+ expect(@guard.run_if(:name)).to eq(false)
+ end
+end
+
+RSpec.describe SpecGuard, ".run_unless" do
+ before :each do
+ @guard = SpecGuard.new
+ ScratchPad.clear
+ end
+
+ it "yields if match? returns false" do
+ allow(@guard).to receive(:match?).and_return(false)
+ @guard.run_unless(:name) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield if match? returns true" do
+ allow(@guard).to receive(:match?).and_return(true)
+ @guard.run_unless(:name) { fail }
+ end
+
+ it "returns the result of the block if match? is false" do
+ allow(@guard).to receive(:match?).and_return(false)
+ expect(@guard.run_unless(:name) { 42 }).to eq(42)
+ end
+
+ it "returns nil if given a block and match? is true" do
+ allow(@guard).to receive(:match?).and_return(true)
+ expect(@guard.run_unless(:name) { 42 }).to eq(nil)
+ end
+
+ it "returns the opposite of what #match? returns when no block is given" do
+ allow(@guard).to receive(:match?).and_return(true)
+ expect(@guard.run_unless(:name)).to eq(false)
+ allow(@guard).to receive(:match?).and_return(false)
+ expect(@guard.run_unless(:name)).to eq(true)
+ end
+end
+
+RSpec.describe Object, "#guard" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ after :each do
+ MSpec.clear_modes
+ end
+
+ it "allows to combine guards" do
+ guard1 = VersionGuard.new '1.2.3', 'x.x.x'
+ allow(VersionGuard).to receive(:new).and_return(guard1)
+ guard2 = PlatformGuard.new :dummy
+ allow(PlatformGuard).to receive(:new).and_return(guard2)
+
+ allow(guard1).to receive(:match?).and_return(true)
+ allow(guard2).to receive(:match?).and_return(true)
+ guard -> { ruby_version_is "2.4" and platform_is :linux } do
+ ScratchPad.record :yield
+ end
+ expect(ScratchPad.recorded).to eq(:yield)
+
+ allow(guard1).to receive(:match?).and_return(false)
+ allow(guard2).to receive(:match?).and_return(true)
+ guard -> { ruby_version_is "2.4" and platform_is :linux } do
+ fail
+ end
+
+ allow(guard1).to receive(:match?).and_return(true)
+ allow(guard2).to receive(:match?).and_return(false)
+ guard -> { ruby_version_is "2.4" and platform_is :linux } do
+ fail
+ end
+
+ allow(guard1).to receive(:match?).and_return(false)
+ allow(guard2).to receive(:match?).and_return(false)
+ guard -> { ruby_version_is "2.4" and platform_is :linux } do
+ fail
+ end
+ end
+
+ it "yields when the Proc returns true" do
+ guard -> { true } do
+ ScratchPad.record :yield
+ end
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield when the Proc returns false" do
+ guard -> { false } do
+ fail
+ end
+ end
+
+ it "yields if MSpec.mode?(:unguarded) is true" do
+ MSpec.register_mode :unguarded
+
+ guard -> { false } do
+ ScratchPad.record :yield1
+ end
+ expect(ScratchPad.recorded).to eq(:yield1)
+
+ guard -> { true } do
+ ScratchPad.record :yield2
+ end
+ expect(ScratchPad.recorded).to eq(:yield2)
+ end
+
+ it "yields if MSpec.mode?(:verify) is true" do
+ MSpec.register_mode :verify
+
+ guard -> { false } do
+ ScratchPad.record :yield1
+ end
+ expect(ScratchPad.recorded).to eq(:yield1)
+
+ guard -> { true } do
+ ScratchPad.record :yield2
+ end
+ expect(ScratchPad.recorded).to eq(:yield2)
+ end
+
+ it "yields if MSpec.mode?(:report) is true" do
+ MSpec.register_mode :report
+
+ guard -> { false } do
+ ScratchPad.record :yield1
+ end
+ expect(ScratchPad.recorded).to eq(:yield1)
+
+ guard -> { true } do
+ ScratchPad.record :yield2
+ end
+ expect(ScratchPad.recorded).to eq(:yield2)
+ end
+
+ it "raises an error if no Proc is given" do
+ expect { guard :foo }.to raise_error(RuntimeError)
+ end
+
+ it "requires a block" do
+ expect {
+ guard(-> { true })
+ }.to raise_error(LocalJumpError)
+ expect {
+ guard(-> { false })
+ }.to raise_error(LocalJumpError)
+ end
+end
+
+RSpec.describe Object, "#guard_not" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "allows to combine guards" do
+ guard1 = VersionGuard.new '1.2.3', 'x.x.x'
+ allow(VersionGuard).to receive(:new).and_return(guard1)
+ guard2 = PlatformGuard.new :dummy
+ allow(PlatformGuard).to receive(:new).and_return(guard2)
+
+ allow(guard1).to receive(:match?).and_return(true)
+ allow(guard2).to receive(:match?).and_return(true)
+ guard_not -> { ruby_version_is "2.4" and platform_is :linux } do
+ fail
+ end
+
+ allow(guard1).to receive(:match?).and_return(false)
+ allow(guard2).to receive(:match?).and_return(true)
+ guard_not -> { ruby_version_is "2.4" and platform_is :linux } do
+ ScratchPad.record :yield1
+ end
+ expect(ScratchPad.recorded).to eq(:yield1)
+
+ allow(guard1).to receive(:match?).and_return(true)
+ allow(guard2).to receive(:match?).and_return(false)
+ guard_not -> { ruby_version_is "2.4" and platform_is :linux } do
+ ScratchPad.record :yield2
+ end
+ expect(ScratchPad.recorded).to eq(:yield2)
+
+ allow(guard1).to receive(:match?).and_return(false)
+ allow(guard2).to receive(:match?).and_return(false)
+ guard_not -> { ruby_version_is "2.4" and platform_is :linux } do
+ ScratchPad.record :yield3
+ end
+ expect(ScratchPad.recorded).to eq(:yield3)
+ end
+
+ it "yields when the Proc returns false" do
+ guard_not -> { false } do
+ ScratchPad.record :yield
+ end
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield when the Proc returns true" do
+ guard_not -> { true } do
+ fail
+ end
+ end
+
+ it "raises an error if no Proc is given" do
+ expect { guard_not :foo }.to raise_error(RuntimeError)
+ end
+
+ it "requires a block" do
+ expect {
+ guard_not(-> { true })
+ }.to raise_error(LocalJumpError)
+ expect {
+ guard_not(-> { false })
+ }.to raise_error(LocalJumpError)
+ end
+end
diff --git a/spec/mspec/spec/guards/platform_spec.rb b/spec/mspec/spec/guards/platform_spec.rb
new file mode 100644
index 0000000000..88a7ad86f2
--- /dev/null
+++ b/spec/mspec/spec/guards/platform_spec.rb
@@ -0,0 +1,337 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+RSpec.describe Object, "#platform_is" do
+ before :each do
+ @guard = PlatformGuard.new :dummy
+ allow(PlatformGuard).to receive(:new).and_return(@guard)
+ ScratchPad.clear
+ end
+
+ it "does not yield when #os? returns false" do
+ allow(PlatformGuard).to receive(:os?).and_return(false)
+ platform_is(:ruby) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "yields when #os? returns true" do
+ allow(PlatformGuard).to receive(:os?).and_return(true)
+ platform_is(:solarce) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "returns what #os? returns when no block is given" do
+ allow(PlatformGuard).to receive(:os?).and_return(true)
+ expect(platform_is(:solarce)).to eq(true)
+ allow(PlatformGuard).to receive(:os?).and_return(false)
+ expect(platform_is(:solarce)).to eq(false)
+ end
+
+ it "sets the name of the guard to :platform_is" do
+ platform_is(:solarce) { }
+ expect(@guard.name).to eq(:platform_is)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:match?).and_return(true)
+ expect(@guard).to receive(:unregister)
+ expect do
+ platform_is(:solarce) { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
+
+RSpec.describe Object, "#platform_is_not" do
+ before :each do
+ @guard = PlatformGuard.new :dummy
+ allow(PlatformGuard).to receive(:new).and_return(@guard)
+ ScratchPad.clear
+ end
+
+ it "does not yield when #os? returns true" do
+ allow(PlatformGuard).to receive(:os?).and_return(true)
+ platform_is_not(:ruby) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "yields when #os? returns false" do
+ allow(PlatformGuard).to receive(:os?).and_return(false)
+ platform_is_not(:solarce) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "returns the opposite of what #os? returns when no block is given" do
+ allow(PlatformGuard).to receive(:os?).and_return(true)
+ expect(platform_is_not(:solarce)).to eq(false)
+ allow(PlatformGuard).to receive(:os?).and_return(false)
+ expect(platform_is_not(:solarce)).to eq(true)
+ end
+
+ it "sets the name of the guard to :platform_is_not" do
+ platform_is_not(:solarce) { }
+ expect(@guard.name).to eq(:platform_is_not)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:match?).and_return(false)
+ expect(@guard).to receive(:unregister)
+ expect do
+ platform_is_not(:solarce) { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
+
+RSpec.describe Object, "#platform_is :wordsize => SIZE_SPEC" do
+ before :each do
+ @guard = PlatformGuard.new :darwin, :wordsize => 32
+ allow(PlatformGuard).to receive(:os?).and_return(true)
+ allow(PlatformGuard).to receive(:new).and_return(@guard)
+ ScratchPad.clear
+ end
+
+ it "yields when #wordsize? returns true" do
+ allow(PlatformGuard).to receive(:wordsize?).and_return(true)
+ platform_is(:wordsize => 32) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "doesn not yield when #wordsize? returns false" do
+ allow(PlatformGuard).to receive(:wordsize?).and_return(false)
+ platform_is(:wordsize => 32) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+end
+
+RSpec.describe Object, "#platform_is_not :wordsize => SIZE_SPEC" do
+ before :each do
+ @guard = PlatformGuard.new :darwin, :wordsize => 32
+ allow(PlatformGuard).to receive(:os?).and_return(true)
+ allow(PlatformGuard).to receive(:new).and_return(@guard)
+ ScratchPad.clear
+ end
+
+ it "yields when #wordsize? returns false" do
+ allow(PlatformGuard).to receive(:wordsize?).and_return(false)
+ platform_is_not(:wordsize => 32) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "doesn not yield when #wordsize? returns true" do
+ allow(PlatformGuard).to receive(:wordsize?).and_return(true)
+ platform_is_not(:wordsize => 32) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+end
+
+RSpec.describe PlatformGuard, ".implementation?" do
+ it "returns true if passed :ruby and RUBY_ENGINE == 'ruby'" do
+ stub_const 'RUBY_ENGINE', 'ruby'
+ expect(PlatformGuard.implementation?(:ruby)).to eq(true)
+ end
+
+ it "returns true if passed :rubinius and RUBY_ENGINE == 'rbx'" do
+ stub_const 'RUBY_ENGINE', 'rbx'
+ expect(PlatformGuard.implementation?(:rubinius)).to eq(true)
+ end
+
+ it "returns true if passed :jruby and RUBY_ENGINE == 'jruby'" do
+ stub_const 'RUBY_ENGINE', 'jruby'
+ expect(PlatformGuard.implementation?(:jruby)).to eq(true)
+ end
+
+ it "returns true if passed :ironruby and RUBY_ENGINE == 'ironruby'" do
+ stub_const 'RUBY_ENGINE', 'ironruby'
+ expect(PlatformGuard.implementation?(:ironruby)).to eq(true)
+ end
+
+ it "returns true if passed :maglev and RUBY_ENGINE == 'maglev'" do
+ stub_const 'RUBY_ENGINE', 'maglev'
+ expect(PlatformGuard.implementation?(:maglev)).to eq(true)
+ end
+
+ it "returns true if passed :topaz and RUBY_ENGINE == 'topaz'" do
+ stub_const 'RUBY_ENGINE', 'topaz'
+ expect(PlatformGuard.implementation?(:topaz)).to eq(true)
+ end
+
+ it "returns true if passed :ruby and RUBY_ENGINE matches /^ruby/" do
+ stub_const 'RUBY_ENGINE', 'ruby'
+ expect(PlatformGuard.implementation?(:ruby)).to eq(true)
+
+ stub_const 'RUBY_ENGINE', 'ruby1.8'
+ expect(PlatformGuard.implementation?(:ruby)).to eq(true)
+
+ stub_const 'RUBY_ENGINE', 'ruby1.9'
+ expect(PlatformGuard.implementation?(:ruby)).to eq(true)
+ end
+
+ it "works for an unrecognized name" do
+ stub_const 'RUBY_ENGINE', 'myrubyimplementation'
+ expect(PlatformGuard.implementation?(:myrubyimplementation)).to eq(true)
+ expect(PlatformGuard.implementation?(:other)).to eq(false)
+ end
+end
+
+RSpec.describe PlatformGuard, ".standard?" do
+ it "returns true if implementation? returns true" do
+ expect(PlatformGuard).to receive(:implementation?).with(:ruby).and_return(true)
+ expect(PlatformGuard.standard?).to be_truthy
+ end
+
+ it "returns false if implementation? returns false" do
+ expect(PlatformGuard).to receive(:implementation?).with(:ruby).and_return(false)
+ expect(PlatformGuard.standard?).to be_falsey
+ end
+end
+
+RSpec.describe PlatformGuard, ".wordsize?" do
+ it "returns true when arg is 32 and 1.size is 4" do
+ expect(PlatformGuard.wordsize?(32)).to eq(1.size == 4)
+ end
+
+ it "returns true when arg is 64 and 1.size is 8" do
+ expect(PlatformGuard.wordsize?(64)).to eq(1.size == 8)
+ end
+end
+
+RSpec.describe PlatformGuard, ".os?" do
+ before :each do
+ stub_const 'PlatformGuard::PLATFORM', 'solarce'
+ end
+
+ it "returns false when arg does not match the platform" do
+ expect(PlatformGuard.os?(:ruby)).to eq(false)
+ end
+
+ it "returns false when no arg matches the platform" do
+ expect(PlatformGuard.os?(:ruby, :jruby, :rubinius, :maglev)).to eq(false)
+ end
+
+ it "returns true when arg matches the platform" do
+ expect(PlatformGuard.os?(:solarce)).to eq(true)
+ end
+
+ it "returns true when any arg matches the platform" do
+ expect(PlatformGuard.os?(:ruby, :jruby, :solarce, :rubinius, :maglev)).to eq(true)
+ end
+
+ it "returns true when arg is :windows and the platform contains 'mswin'" do
+ stub_const 'PlatformGuard::PLATFORM', 'mswin32'
+ expect(PlatformGuard.os?(:windows)).to eq(true)
+ end
+
+ it "returns true when arg is :windows and the platform contains 'mingw'" do
+ stub_const 'PlatformGuard::PLATFORM', 'i386-mingw32'
+ expect(PlatformGuard.os?(:windows)).to eq(true)
+ end
+
+ it "returns false when arg is not :windows and RbConfig::CONFIG['host_os'] contains 'mswin'" do
+ stub_const 'PlatformGuard::PLATFORM', 'i386-mswin32'
+ expect(PlatformGuard.os?(:linux)).to eq(false)
+ end
+
+ it "returns false when arg is not :windows and RbConfig::CONFIG['host_os'] contains 'mingw'" do
+ stub_const 'PlatformGuard::PLATFORM', 'i386-mingw32'
+ expect(PlatformGuard.os?(:linux)).to eq(false)
+ end
+end
+
+RSpec.describe PlatformGuard, ".os?" do
+ it "returns true if called with the current OS or architecture" do
+ os = RbConfig::CONFIG["host_os"].sub("-gnu", "")
+ arch = RbConfig::CONFIG["host_arch"]
+ expect(PlatformGuard.os?(os)).to eq(true)
+ expect(PlatformGuard.os?(arch)).to eq(true)
+ expect(PlatformGuard.os?("#{arch}-#{os}")).to eq(true)
+ end
+end
+
+RSpec.describe PlatformGuard, ".os? on JRuby" do
+ before :all do
+ @verbose = $VERBOSE
+ $VERBOSE = nil
+ end
+
+ after :all do
+ $VERBOSE = @verbose
+ end
+
+ before :each do
+ @ruby_platform = Object.const_get :RUBY_PLATFORM
+ Object.const_set :RUBY_PLATFORM, 'java'
+ end
+
+ after :each do
+ Object.const_set :RUBY_PLATFORM, @ruby_platform
+ end
+
+ it "raises an error when testing for a :java platform" do
+ expect {
+ PlatformGuard.os?(:java)
+ }.to raise_error(":java is not a valid OS")
+ end
+
+ it "returns true when arg is :windows and RUBY_PLATFORM contains 'java' and os?(:windows) is true" do
+ stub_const 'PlatformGuard::PLATFORM', 'mswin32'
+ expect(PlatformGuard.os?(:windows)).to eq(true)
+ end
+
+ it "returns true when RUBY_PLATFORM contains 'java' and os?(argument) is true" do
+ stub_const 'PlatformGuard::PLATFORM', 'amiga'
+ expect(PlatformGuard.os?(:amiga)).to eq(true)
+ end
+end
+
+RSpec.describe PlatformGuard, ".os?" do
+ before :each do
+ stub_const 'PlatformGuard::PLATFORM', 'unreal'
+ end
+
+ it "returns true if argument matches RbConfig::CONFIG['host_os']" do
+ expect(PlatformGuard.os?(:unreal)).to eq(true)
+ end
+
+ it "returns true if any argument matches RbConfig::CONFIG['host_os']" do
+ expect(PlatformGuard.os?(:bsd, :unreal, :amiga)).to eq(true)
+ end
+
+ it "returns false if no argument matches RbConfig::CONFIG['host_os']" do
+ expect(PlatformGuard.os?(:bsd, :netbsd, :amiga, :msdos)).to eq(false)
+ end
+
+ it "returns false if argument does not match RbConfig::CONFIG['host_os']" do
+ expect(PlatformGuard.os?(:amiga)).to eq(false)
+ end
+
+ it "returns true when arg is :windows and RbConfig::CONFIG['host_os'] contains 'mswin'" do
+ stub_const 'PlatformGuard::PLATFORM', 'i386-mswin32'
+ expect(PlatformGuard.os?(:windows)).to eq(true)
+ end
+
+ it "returns true when arg is :windows and RbConfig::CONFIG['host_os'] contains 'mingw'" do
+ stub_const 'PlatformGuard::PLATFORM', 'i386-mingw32'
+ expect(PlatformGuard.os?(:windows)).to eq(true)
+ end
+
+ it "returns false when arg is not :windows and RbConfig::CONFIG['host_os'] contains 'mswin'" do
+ stub_const 'PlatformGuard::PLATFORM', 'i386-mingw32'
+ expect(PlatformGuard.os?(:linux)).to eq(false)
+ end
+
+ it "returns false when arg is not :windows and RbConfig::CONFIG['host_os'] contains 'mingw'" do
+ stub_const 'PlatformGuard::PLATFORM', 'i386-mingw32'
+ expect(PlatformGuard.os?(:linux)).to eq(false)
+ end
+end
+
+RSpec.describe PlatformGuard, ".windows?" do
+ it "returns true on windows" do
+ stub_const 'PlatformGuard::PLATFORM', 'i386-mingw32'
+ expect(PlatformGuard.windows?).to eq(true)
+ end
+
+ it "returns false on non-windows" do
+ stub_const 'PlatformGuard::PLATFORM', 'i586-linux'
+ expect(PlatformGuard.windows?).to eq(false)
+ end
+end
diff --git a/spec/mspec/spec/guards/quarantine_spec.rb b/spec/mspec/spec/guards/quarantine_spec.rb
new file mode 100644
index 0000000000..eb5ff1da27
--- /dev/null
+++ b/spec/mspec/spec/guards/quarantine_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+RSpec.describe QuarantineGuard, "#match?" do
+ it "returns true" do
+ expect(QuarantineGuard.new.match?).to eq(true)
+ end
+end
+
+RSpec.describe Object, "#quarantine!" do
+ before :each do
+ ScratchPad.clear
+
+ @guard = QuarantineGuard.new
+ allow(QuarantineGuard).to receive(:new).and_return(@guard)
+ end
+
+ it "does not yield" do
+ quarantine! { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "sets the name of the guard to :quarantine!" do
+ quarantine! { }
+ expect(@guard.name).to eq(:quarantine!)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:match?).and_return(false)
+ expect(@guard).to receive(:unregister)
+ expect do
+ quarantine! { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
diff --git a/spec/mspec/spec/guards/superuser_spec.rb b/spec/mspec/spec/guards/superuser_spec.rb
new file mode 100644
index 0000000000..aba2cc2bb0
--- /dev/null
+++ b/spec/mspec/spec/guards/superuser_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+RSpec.describe Object, "#as_superuser" do
+ before :each do
+ @guard = SuperUserGuard.new
+ allow(SuperUserGuard).to receive(:new).and_return(@guard)
+ ScratchPad.clear
+ end
+
+ it "does not yield when Process.euid is not 0" do
+ allow(Process).to receive(:euid).and_return(501)
+ as_superuser { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "yields when Process.euid is 0" do
+ allow(Process).to receive(:euid).and_return(0)
+ as_superuser { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "sets the name of the guard to :as_superuser" do
+ as_superuser { }
+ expect(@guard.name).to eq(:as_superuser)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:match?).and_return(true)
+ expect(@guard).to receive(:unregister)
+ expect do
+ as_superuser { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
diff --git a/spec/mspec/spec/guards/support_spec.rb b/spec/mspec/spec/guards/support_spec.rb
new file mode 100644
index 0000000000..a61d003d6c
--- /dev/null
+++ b/spec/mspec/spec/guards/support_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+RSpec.describe Object, "#not_supported_on" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "raises an Exception when passed :ruby" do
+ stub_const "RUBY_ENGINE", "jruby"
+ expect {
+ not_supported_on(:ruby) { ScratchPad.record :yield }
+ }.to raise_error(Exception)
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "does not yield when #implementation? returns true" do
+ stub_const "RUBY_ENGINE", "jruby"
+ not_supported_on(:jruby) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "yields when #standard? returns true" do
+ stub_const "RUBY_ENGINE", "ruby"
+ not_supported_on(:rubinius) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "yields when #implementation? returns false" do
+ stub_const "RUBY_ENGINE", "jruby"
+ not_supported_on(:rubinius) { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+end
+
+RSpec.describe Object, "#not_supported_on" do
+ before :each do
+ @guard = SupportedGuard.new
+ allow(SupportedGuard).to receive(:new).and_return(@guard)
+ end
+
+ it "sets the name of the guard to :not_supported_on" do
+ not_supported_on(:rubinius) { }
+ expect(@guard.name).to eq(:not_supported_on)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:match?).and_return(false)
+ expect(@guard).to receive(:unregister)
+ expect do
+ not_supported_on(:rubinius) { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
diff --git a/spec/mspec/spec/guards/user_spec.rb b/spec/mspec/spec/guards/user_spec.rb
new file mode 100644
index 0000000000..2526504656
--- /dev/null
+++ b/spec/mspec/spec/guards/user_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+RSpec.describe Object, "#as_user" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "yields when the Process.euid is not 0" do
+ allow(Process).to receive(:euid).and_return(501)
+ as_user { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield when the Process.euid is 0" do
+ allow(Process).to receive(:euid).and_return(0)
+ as_user { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+end
diff --git a/spec/mspec/spec/guards/version_spec.rb b/spec/mspec/spec/guards/version_spec.rb
new file mode 100644
index 0000000000..5a5f4ddc3b
--- /dev/null
+++ b/spec/mspec/spec/guards/version_spec.rb
@@ -0,0 +1,112 @@
+require 'spec_helper'
+require 'mspec/guards'
+
+# The VersionGuard specifies a version of Ruby with a String of
+# the form: v = 'major.minor.tiny'.
+#
+# A VersionGuard instance can be created with a single String,
+# which means any version >= each component of v.
+# Or, the guard can be created with a Range, a..b, or a...b,
+# where a, b are of the same form as v. The meaning of the Range
+# is as typically understood: a..b means v >= a and v <= b;
+# a...b means v >= a and v < b.
+
+RSpec.describe VersionGuard, "#match?" do
+ before :each do
+ hide_deprecation_warnings
+ @current = '1.8.6'
+ end
+
+ it "returns true when the argument is equal to RUBY_VERSION" do
+ expect(VersionGuard.new(@current, '1.8.6').match?).to eq(true)
+ end
+
+ it "returns true when the argument is less than RUBY_VERSION" do
+ expect(VersionGuard.new(@current, '1.8').match?).to eq(true)
+ expect(VersionGuard.new(@current, '1.8.5').match?).to eq(true)
+ end
+
+ it "returns false when the argument is greater than RUBY_VERSION" do
+ expect(VersionGuard.new(@current, '1.8.7').match?).to eq(false)
+ expect(VersionGuard.new(@current, '1.9.2').match?).to eq(false)
+ end
+
+ it "returns true when the argument range includes RUBY_VERSION" do
+ expect(VersionGuard.new(@current, '1.8.5'..'1.8.7').match?).to eq(true)
+ expect(VersionGuard.new(@current, '1.8'..'1.9').match?).to eq(true)
+ expect(VersionGuard.new(@current, '1.8'...'1.9').match?).to eq(true)
+ expect(VersionGuard.new(@current, '1.8'..'1.8.6').match?).to eq(true)
+ expect(VersionGuard.new(@current, '1.8.5'..'1.8.6').match?).to eq(true)
+ expect(VersionGuard.new(@current, ''...'1.8.7').match?).to eq(true)
+ end
+
+ it "returns false when the argument range does not include RUBY_VERSION" do
+ expect(VersionGuard.new(@current, '1.8.7'..'1.8.9').match?).to eq(false)
+ expect(VersionGuard.new(@current, '1.8.4'..'1.8.5').match?).to eq(false)
+ expect(VersionGuard.new(@current, '1.8.4'...'1.8.6').match?).to eq(false)
+ expect(VersionGuard.new(@current, '1.8.5'...'1.8.6').match?).to eq(false)
+ expect(VersionGuard.new(@current, ''...'1.8.6').match?).to eq(false)
+ end
+end
+
+RSpec.describe Object, "#ruby_version_is" do
+ before :each do
+ @guard = VersionGuard.new '1.2.3', 'x.x.x'
+ allow(VersionGuard).to receive(:new).and_return(@guard)
+ ScratchPad.clear
+ end
+
+ it "yields when #match? returns true" do
+ allow(@guard).to receive(:match?).and_return(true)
+ ruby_version_is('x.x.x') { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).to eq(:yield)
+ end
+
+ it "does not yield when #match? returns false" do
+ allow(@guard).to receive(:match?).and_return(false)
+ ruby_version_is('x.x.x') { ScratchPad.record :yield }
+ expect(ScratchPad.recorded).not_to eq(:yield)
+ end
+
+ it "returns what #match? returns when no block is given" do
+ allow(@guard).to receive(:match?).and_return(true)
+ expect(ruby_version_is('x.x.x')).to eq(true)
+ allow(@guard).to receive(:match?).and_return(false)
+ expect(ruby_version_is('x.x.x')).to eq(false)
+ end
+
+ it "sets the name of the guard to :ruby_version_is" do
+ ruby_version_is("") { }
+ expect(@guard.name).to eq(:ruby_version_is)
+ end
+
+ it "calls #unregister even when an exception is raised in the guard block" do
+ expect(@guard).to receive(:match?).and_return(true)
+ expect(@guard).to receive(:unregister)
+ expect do
+ ruby_version_is("") { raise Exception }
+ end.to raise_error(Exception)
+ end
+end
+
+RSpec.describe Object, "#version_is" do
+ before :each do
+ hide_deprecation_warnings
+ end
+
+ it "returns the expected values" do
+ expect(version_is('1.2.3', '1.2.2')).to eq(true)
+ expect(version_is('1.2.3', '1.2.3')).to eq(true)
+ expect(version_is('1.2.3', '1.2.4')).to eq(false)
+
+ expect(version_is('1.2.3', '1')).to eq(true)
+ expect(version_is('1.2.3', '1.0')).to eq(true)
+ expect(version_is('1.2.3', '2')).to eq(false)
+ expect(version_is('1.2.3', '2.0')).to eq(false)
+
+ expect(version_is('1.2.3', '1.2.2'..'1.2.4')).to eq(true)
+ expect(version_is('1.2.3', '1.2.2'..'1.2.3')).to eq(true)
+ expect(version_is('1.2.3', '1.2.2'...'1.2.3')).to eq(false)
+ expect(version_is('1.2.3', '1.2.3'..'1.2.4')).to eq(true)
+ end
+end
diff --git a/spec/mspec/spec/helpers/argf_spec.rb b/spec/mspec/spec/helpers/argf_spec.rb
new file mode 100644
index 0000000000..1412d71f84
--- /dev/null
+++ b/spec/mspec/spec/helpers/argf_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe Object, "#argf" do
+ before :each do
+ @saved_argv = ARGV.dup
+ @argv = [__FILE__]
+ end
+
+ it "sets @argf to an instance of ARGF.class with the given argv" do
+ argf @argv do
+ expect(@argf).to be_an_instance_of ARGF.class
+ expect(@argf.filename).to eq(@argv.first)
+ end
+ expect(@argf).to be_nil
+ end
+
+ it "does not alter ARGV nor ARGF" do
+ argf @argv do
+ end
+ expect(ARGV).to eq(@saved_argv)
+ expect(ARGF.argv).to eq(@saved_argv)
+ end
+
+ it "does not close STDIN" do
+ argf ['-'] do
+ end
+ expect(STDIN).not_to be_closed
+ end
+
+ it "disallows nested calls" do
+ argf @argv do
+ expect { argf @argv }.to raise_error
+ end
+ end
+end
diff --git a/spec/mspec/spec/helpers/argv_spec.rb b/spec/mspec/spec/helpers/argv_spec.rb
new file mode 100644
index 0000000000..1db7e38650
--- /dev/null
+++ b/spec/mspec/spec/helpers/argv_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe Object, "#argv" do
+ before :each do
+ ScratchPad.clear
+
+ @saved_argv = ARGV.dup
+ @argv = ["a", "b"]
+ end
+
+ it "replaces and restores the value of ARGV" do
+ argv @argv
+ expect(ARGV).to eq(@argv)
+ argv :restore
+ expect(ARGV).to eq(@saved_argv)
+ end
+
+ it "yields to the block after setting ARGV" do
+ argv @argv do
+ ScratchPad.record ARGV.dup
+ end
+ expect(ScratchPad.recorded).to eq(@argv)
+ expect(ARGV).to eq(@saved_argv)
+ end
+end
diff --git a/spec/mspec/spec/helpers/datetime_spec.rb b/spec/mspec/spec/helpers/datetime_spec.rb
new file mode 100644
index 0000000000..af4f557376
--- /dev/null
+++ b/spec/mspec/spec/helpers/datetime_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe Object, "#new_datetime" do
+ it "returns a default DateTime instance" do
+ expect(new_datetime).to eq(DateTime.new)
+ end
+
+ it "returns a DateTime instance with the specified year value" do
+ d = new_datetime :year => 1970
+ expect(d.year).to eq(1970)
+ end
+
+ it "returns a DateTime instance with the specified month value" do
+ d = new_datetime :month => 11
+ expect(d.mon).to eq(11)
+ end
+
+ it "returns a DateTime instance with the specified day value" do
+ d = new_datetime :day => 23
+ expect(d.day).to eq(23)
+ end
+
+ it "returns a DateTime instance with the specified hour value" do
+ d = new_datetime :hour => 10
+ expect(d.hour).to eq(10)
+ end
+
+ it "returns a DateTime instance with the specified minute value" do
+ d = new_datetime :minute => 10
+ expect(d.min).to eq(10)
+ end
+
+ it "returns a DateTime instance with the specified second value" do
+ d = new_datetime :second => 2
+ expect(d.sec).to eq(2)
+ end
+
+ it "returns a DateTime instance with the specified offset value" do
+ d = new_datetime :offset => Rational(3,24)
+ expect(d.offset).to eq(Rational(3,24))
+ end
+end
diff --git a/spec/mspec/spec/helpers/fixture_spec.rb b/spec/mspec/spec/helpers/fixture_spec.rb
new file mode 100644
index 0000000000..d8e2ae7be4
--- /dev/null
+++ b/spec/mspec/spec/helpers/fixture_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe Object, "#fixture" do
+ before :each do
+ @dir = File.realpath("..", __FILE__)
+ end
+
+ it "returns the expanded path to a fixture file" do
+ name = fixture(__FILE__, "subdir", "file.txt")
+ expect(name).to eq("#{@dir}/fixtures/subdir/file.txt")
+ end
+
+ it "omits '/shared' if it is the suffix of the directory string" do
+ name = fixture("#{@dir}/shared/file.rb", "subdir", "file.txt")
+ expect(name).to eq("#{@dir}/fixtures/subdir/file.txt")
+ end
+
+ it "does not append '/fixtures' if it is the suffix of the directory string" do
+ commands_dir = "#{File.dirname(@dir)}/commands"
+ name = fixture("#{commands_dir}/fixtures/file.rb", "subdir", "file.txt")
+ expect(name).to eq("#{commands_dir}/fixtures/subdir/file.txt")
+ end
+end
diff --git a/spec/mspec/spec/helpers/flunk_spec.rb b/spec/mspec/spec/helpers/flunk_spec.rb
new file mode 100644
index 0000000000..b6a1f21c12
--- /dev/null
+++ b/spec/mspec/spec/helpers/flunk_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/runner/mspec'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe Object, "#flunk" do
+ before :each do
+ allow(MSpec).to receive(:actions)
+ allow(MSpec).to receive(:current).and_return(double("spec state").as_null_object)
+ end
+
+ it "raises an SpecExpectationNotMetError unconditionally" do
+ expect { flunk }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ it "accepts on argument for an optional message" do
+ expect {flunk "test"}.to raise_error(SpecExpectationNotMetError)
+ end
+end
diff --git a/spec/mspec/spec/helpers/fs_spec.rb b/spec/mspec/spec/helpers/fs_spec.rb
new file mode 100644
index 0000000000..15bb43903a
--- /dev/null
+++ b/spec/mspec/spec/helpers/fs_spec.rb
@@ -0,0 +1,195 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe Object, "#cp" do
+ before :each do
+ @source = tmp("source.txt")
+ @copy = tmp("copied.txt")
+
+ @contents = "This is a copy."
+ File.open(@source, "w") { |f| f.write @contents }
+ end
+
+ after :each do
+ File.delete @source if File.exist? @source
+ File.delete @copy if File.exist? @copy
+ end
+
+ it "copies a file" do
+ cp @source, @copy
+ data = IO.read(@copy)
+ expect(data).to eq(@contents)
+ expect(data).to eq(IO.read(@source))
+ end
+end
+
+RSpec.describe Object, "#touch" do
+ before :all do
+ @name = tmp("touched.txt")
+ end
+
+ after :each do
+ File.delete @name if File.exist? @name
+ end
+
+ it "creates a file" do
+ touch @name
+ expect(File.exist?(@name)).to be_truthy
+ end
+
+ it "accepts an optional mode argument" do
+ touch @name, "wb"
+ expect(File.exist?(@name)).to be_truthy
+ end
+
+ it "overwrites an existing file" do
+ File.open(@name, "w") { |f| f.puts "used" }
+ expect(File.size(@name)).to be > 0
+
+ touch @name
+ expect(File.size(@name)).to eq(0)
+ end
+
+ it "yields the open file if passed a block" do
+ touch(@name) { |f| f.write "touching" }
+ expect(IO.read(@name)).to eq("touching")
+ end
+end
+
+RSpec.describe Object, "#touch" do
+ before :all do
+ @name = tmp("subdir/touched.txt")
+ end
+
+ after :each do
+ rm_r File.dirname(@name)
+ end
+
+ it "creates all the directories in the path to the file" do
+ touch @name
+ expect(File.exist?(@name)).to be_truthy
+ end
+end
+
+RSpec.describe Object, "#mkdir_p" do
+ before :all do
+ @dir1 = tmp("/nested")
+ @dir2 = @dir1 + "/directory"
+ @paths = [ @dir2, @dir1 ]
+ end
+
+ after :each do
+ File.delete @dir1 if File.file? @dir1
+ @paths.each { |path| Dir.rmdir path if File.directory? path }
+ end
+
+ it "creates all the directories in a path" do
+ mkdir_p @dir2
+ expect(File.directory?(@dir2)).to be_truthy
+ end
+
+ it "raises an ArgumentError if a path component is a file" do
+ File.open(@dir1, "w") { |f| }
+ expect { mkdir_p @dir2 }.to raise_error(ArgumentError)
+ end
+
+ it "works if multiple processes try to create the same directory concurrently" do
+ original = File.method(:directory?)
+ expect(File).to receive(:directory?).at_least(:once) { |dir|
+ ret = original.call(dir)
+ if !ret and dir == @dir1
+ Dir.mkdir(dir) # Simulate race
+ end
+ ret
+ }
+ mkdir_p @dir1
+ expect(original.call(@dir1)).to be_truthy
+ end
+end
+
+RSpec.describe Object, "#rm_r" do
+ before :all do
+ @topdir = tmp("rm_r_tree")
+ @topfile = @topdir + "/file.txt"
+ @link = @topdir + "/file.lnk"
+ @socket = @topdir + "/socket.sck"
+ @subdir1 = @topdir + "/subdir1"
+ @subdir2 = @subdir1 + "/subdir2"
+ @subfile = @subdir1 + "/subfile.txt"
+ end
+
+ before :each do
+ mkdir_p @subdir2
+ touch @topfile
+ touch @subfile
+ end
+
+ after :each do
+ File.delete @link if File.exist? @link or File.symlink? @link
+ File.delete @socket if File.exist? @socket
+ File.delete @subfile if File.exist? @subfile
+ File.delete @topfile if File.exist? @topfile
+
+ Dir.rmdir @subdir2 if File.directory? @subdir2
+ Dir.rmdir @subdir1 if File.directory? @subdir1
+ Dir.rmdir @topdir if File.directory? @topdir
+ end
+
+ it "raises an ArgumentError if the path is not prefixed by MSPEC_RM_PREFIX" do
+ expect { rm_r "some_file.txt" }.to raise_error(ArgumentError)
+ end
+
+ it "removes a single file" do
+ rm_r @subfile
+ expect(File.exist?(@subfile)).to be_falsey
+ end
+
+ it "removes multiple files" do
+ rm_r @topfile, @subfile
+ expect(File.exist?(@topfile)).to be_falsey
+ expect(File.exist?(@subfile)).to be_falsey
+ end
+
+ platform_is_not :windows do
+ it "removes a symlink to a file" do
+ File.symlink @topfile, @link
+ rm_r @link
+ expect(File.exist?(@link)).to be_falsey
+ end
+
+ it "removes a symlink to a directory" do
+ File.symlink @subdir1, @link
+ rm_r @link
+ expect do
+ File.lstat(@link)
+ end.to raise_error(Errno::ENOENT)
+ expect(File.exist?(@subdir1)).to be_truthy
+ end
+
+ it "removes a dangling symlink" do
+ File.symlink "non_existent_file", @link
+ rm_r @link
+ expect do
+ File.lstat(@link)
+ end.to raise_error(Errno::ENOENT)
+ end
+
+ it "removes a socket" do
+ require 'socket'
+ UNIXServer.new(@socket).close
+ rm_r @socket
+ expect(File.exist?(@socket)).to be_falsey
+ end
+ end
+
+ it "removes a single directory" do
+ rm_r @subdir2
+ expect(File.directory?(@subdir2)).to be_falsey
+ end
+
+ it "recursively removes a directory tree" do
+ rm_r @topdir
+ expect(File.directory?(@topdir)).to be_falsey
+ end
+end
diff --git a/spec/mspec/spec/helpers/io_spec.rb b/spec/mspec/spec/helpers/io_spec.rb
new file mode 100644
index 0000000000..14c1a2d6b5
--- /dev/null
+++ b/spec/mspec/spec/helpers/io_spec.rb
@@ -0,0 +1,136 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe IOStub do
+ before :each do
+ @out = IOStub.new
+ @sep = $\
+ end
+
+ after :each do
+ $\ = @sep
+ end
+
+ it "provides a write method" do
+ @out.write "this"
+ expect(@out).to eq("this")
+ end
+
+ it "concatenates the arguments sent to write" do
+ @out.write "flim ", "flam"
+ expect(@out).to eq("flim flam")
+ end
+
+ it "provides a print method that appends the default separator" do
+ $\ = " [newline] "
+ @out.print "hello"
+ @out.print "world"
+ expect(@out).to eq("hello [newline] world [newline] ")
+ end
+
+ it "provides a puts method that appends the default separator" do
+ @out.puts "hello", 1, 2, 3
+ expect(@out).to eq("hello\n1\n2\n3\n")
+ end
+
+ it "provides a puts method that appends separator if argument not given" do
+ @out.puts
+ expect(@out).to eq("\n")
+ end
+
+ it "provides a printf method" do
+ @out.printf "%-10s, %03d, %2.1f", "test", 42, 4.2
+ expect(@out).to eq("test , 042, 4.2")
+ end
+
+ it "provides a flush method that does nothing and returns self" do
+ expect(@out.flush).to eq(@out)
+ end
+end
+
+RSpec.describe Object, "#new_fd" do
+ before :each do
+ @name = tmp("io_specs")
+ @io = nil
+ end
+
+ after :each do
+ @io.close if @io and not @io.closed?
+ rm_r @name
+ end
+
+ it "returns an Integer that can be used to create an IO instance" do
+ fd = new_fd @name
+ expect(fd).to be_kind_of(Integer)
+
+ @io = IO.new fd, 'w:utf-8'
+ @io.sync = true
+ @io.print "io data"
+
+ expect(IO.read(@name)).to eq("io data")
+ end
+
+ it "accepts an options Hash" do
+ allow(FeatureGuard).to receive(:enabled?).and_return(true)
+ fd = new_fd @name, { :mode => 'w:utf-8' }
+ expect(fd).to be_kind_of(Integer)
+
+ @io = IO.new fd, 'w:utf-8'
+ @io.sync = true
+ @io.print "io data"
+
+ expect(IO.read(@name)).to eq("io data")
+ end
+
+ it "raises an ArgumentError if the options Hash does not include :mode" do
+ allow(FeatureGuard).to receive(:enabled?).and_return(true)
+ expect { new_fd @name, { :encoding => "utf-8" } }.to raise_error(ArgumentError)
+ end
+end
+
+RSpec.describe Object, "#new_io" do
+ before :each do
+ @name = tmp("io_specs.txt")
+ end
+
+ after :each do
+ @io.close if @io and !@io.closed?
+ rm_r @name
+ end
+
+ it "returns a File instance" do
+ @io = new_io @name
+ expect(@io).to be_an_instance_of(File)
+ end
+
+ it "opens the IO for reading if passed 'r'" do
+ touch(@name) { |f| f.print "io data" }
+ @io = new_io @name, "r"
+ expect(@io.read).to eq("io data")
+ expect { @io.puts "more data" }.to raise_error(IOError)
+ end
+
+ it "opens the IO for writing if passed 'w'" do
+ @io = new_io @name, "w"
+ @io.sync = true
+
+ @io.print "io data"
+ expect(IO.read(@name)).to eq("io data")
+ end
+
+ it "opens the IO for reading if passed { :mode => 'r' }" do
+ touch(@name) { |f| f.print "io data" }
+ @io = new_io @name, { :mode => "r" }
+ expect(@io.read).to eq("io data")
+ expect { @io.puts "more data" }.to raise_error(IOError)
+ end
+
+ it "opens the IO for writing if passed { :mode => 'w' }" do
+ @io = new_io @name, { :mode => "w" }
+ @io.sync = true
+
+ @io.print "io data"
+ expect(IO.read(@name)).to eq("io data")
+ end
+end
diff --git a/spec/mspec/spec/helpers/mock_to_path_spec.rb b/spec/mspec/spec/helpers/mock_to_path_spec.rb
new file mode 100644
index 0000000000..c2ce985190
--- /dev/null
+++ b/spec/mspec/spec/helpers/mock_to_path_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+require 'mspec/mocks'
+
+RSpec.describe Object, "#mock_to_path" do
+ before :each do
+ state = double("run state").as_null_object
+ expect(MSpec).to receive(:current).and_return(state)
+ end
+
+ it "returns an object that responds to #to_path" do
+ obj = mock_to_path("foo")
+ expect(obj).to be_a(MockObject)
+ expect(obj).to respond_to(:to_path)
+ obj.to_path
+ end
+
+ it "returns the provided path when #to_path is called" do
+ obj = mock_to_path("/tmp/foo")
+ expect(obj.to_path).to eq("/tmp/foo")
+ end
+end
diff --git a/spec/mspec/spec/helpers/numeric_spec.rb b/spec/mspec/spec/helpers/numeric_spec.rb
new file mode 100644
index 0000000000..64495b7276
--- /dev/null
+++ b/spec/mspec/spec/helpers/numeric_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe Object, "#bignum_value" do
+ it "returns a value that is an instance of Bignum on any platform" do
+ expect(bignum_value).to be > fixnum_max
+ end
+
+ it "returns the default value incremented by the argument" do
+ expect(bignum_value(42)).to eq(bignum_value + 42)
+ end
+end
+
+RSpec.describe Object, "-bignum_value" do
+ it "returns a value that is an instance of Bignum on any platform" do
+ expect(-bignum_value).to be < fixnum_min
+ end
+end
+
+RSpec.describe Object, "#nan_value" do
+ it "returns NaN" do
+ expect(nan_value.nan?).to be_truthy
+ end
+end
+
+RSpec.describe Object, "#infinity_value" do
+ it "returns Infinity" do
+ expect(infinity_value.infinite?).to eq(1)
+ end
+end
diff --git a/spec/mspec/spec/helpers/ruby_exe_spec.rb b/spec/mspec/spec/helpers/ruby_exe_spec.rb
new file mode 100644
index 0000000000..61225a2756
--- /dev/null
+++ b/spec/mspec/spec/helpers/ruby_exe_spec.rb
@@ -0,0 +1,256 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+require 'rbconfig'
+
+class RubyExeSpecs
+ public :ruby_exe_options
+ public :resolve_ruby_exe
+ public :ruby_cmd
+ public :ruby_exe
+end
+
+RSpec.describe "#ruby_exe_options" do
+ before :each do
+ @ruby_exe_env = ENV['RUBY_EXE']
+ @script = RubyExeSpecs.new
+ end
+
+ after :each do
+ ENV['RUBY_EXE'] = @ruby_exe_env
+ end
+
+ it "returns ENV['RUBY_EXE'] when passed :env" do
+ ENV['RUBY_EXE'] = "kowabunga"
+ expect(@script.ruby_exe_options(:env)).to eq("kowabunga")
+ end
+
+ it "returns 'bin/jruby' when passed :engine and RUBY_ENGINE is 'jruby'" do
+ stub_const "RUBY_ENGINE", 'jruby'
+ expect(@script.ruby_exe_options(:engine)).to eq('bin/jruby')
+ end
+
+ it "returns 'bin/rbx' when passed :engine, RUBY_ENGINE is 'rbx'" do
+ stub_const "RUBY_ENGINE", 'rbx'
+ expect(@script.ruby_exe_options(:engine)).to eq('bin/rbx')
+ end
+
+ it "returns 'ir' when passed :engine and RUBY_ENGINE is 'ironruby'" do
+ stub_const "RUBY_ENGINE", 'ironruby'
+ expect(@script.ruby_exe_options(:engine)).to eq('ir')
+ end
+
+ it "returns 'maglev-ruby' when passed :engine and RUBY_ENGINE is 'maglev'" do
+ stub_const "RUBY_ENGINE", 'maglev'
+ expect(@script.ruby_exe_options(:engine)).to eq('maglev-ruby')
+ end
+
+ it "returns 'topaz' when passed :engine and RUBY_ENGINE is 'topaz'" do
+ stub_const "RUBY_ENGINE", 'topaz'
+ expect(@script.ruby_exe_options(:engine)).to eq('topaz')
+ end
+
+ it "returns RUBY_ENGINE + $(EXEEXT) when passed :name" do
+ bin = RUBY_ENGINE + (RbConfig::CONFIG['EXEEXT'] || RbConfig::CONFIG['exeext'] || '')
+ name = File.join ".", bin
+ expect(@script.ruby_exe_options(:name)).to eq(name)
+ end
+
+ it "returns $(bindir)/$(RUBY_INSTALL_NAME) + $(EXEEXT) when passed :install_name" do
+ bin = RbConfig::CONFIG['RUBY_INSTALL_NAME'] + (RbConfig::CONFIG['EXEEXT'] || RbConfig::CONFIG['exeext'] || '')
+ name = File.join RbConfig::CONFIG['bindir'], bin
+ expect(@script.ruby_exe_options(:install_name)).to eq(name)
+ end
+end
+
+RSpec.describe "#resolve_ruby_exe" do
+ before :each do
+ @name = "ruby_spec_exe"
+ @script = RubyExeSpecs.new
+ end
+
+ it "returns the value returned by #ruby_exe_options if it exists and is executable" do
+ expect(@script).to receive(:ruby_exe_options).and_return(@name)
+ expect(File).to receive(:file?).with(@name).and_return(true)
+ expect(File).to receive(:executable?).with(@name).and_return(true)
+ expect(File).to receive(:expand_path).with(@name).and_return(@name)
+ expect(@script.resolve_ruby_exe).to eq(@name)
+ end
+
+ it "expands the path portion of the result of #ruby_exe_options" do
+ expect(@script).to receive(:ruby_exe_options).and_return("#{@name}")
+ expect(File).to receive(:file?).with(@name).and_return(true)
+ expect(File).to receive(:executable?).with(@name).and_return(true)
+ expect(File).to receive(:expand_path).with(@name).and_return("/usr/bin/#{@name}")
+ expect(@script.resolve_ruby_exe).to eq("/usr/bin/#{@name}")
+ end
+
+ it "adds the flags after the executable" do
+ @name = 'bin/rbx'
+ expect(@script).to receive(:ruby_exe_options).and_return(@name)
+ expect(File).to receive(:file?).with(@name).and_return(true)
+ expect(File).to receive(:executable?).with(@name).and_return(true)
+ expect(File).to receive(:expand_path).with(@name).and_return(@name)
+
+ expect(ENV).to receive(:[]).with("RUBY_FLAGS").and_return('-X19')
+ expect(@script.resolve_ruby_exe).to eq('bin/rbx -X19')
+ end
+
+ it "raises an exception if no exe is found" do
+ expect(File).to receive(:file?).at_least(:once).and_return(false)
+ expect {
+ @script.resolve_ruby_exe
+ }.to raise_error(Exception)
+ end
+end
+
+RSpec.describe Object, "#ruby_cmd" do
+ before :each do
+ stub_const 'RUBY_EXE', 'ruby_spec_exe -w -Q'
+
+ @file = "some/ruby/file.rb"
+ @code = %(some "real" 'ruby' code)
+
+ @script = RubyExeSpecs.new
+ end
+
+ it "returns a command that runs the given file if it is a file that exists" do
+ expect(File).to receive(:exist?).with(@file).and_return(true)
+ expect(@script.ruby_cmd(@file)).to eq("ruby_spec_exe -w -Q some/ruby/file.rb")
+ end
+
+ it "includes the given options and arguments with a file" do
+ expect(File).to receive(:exist?).with(@file).and_return(true)
+ expect(@script.ruby_cmd(@file, :options => "-w -Cdir", :args => "< file.txt")).to eq(
+ "ruby_spec_exe -w -Q -w -Cdir some/ruby/file.rb < file.txt"
+ )
+ end
+
+ it "includes the given options and arguments with -e" do
+ expect(File).to receive(:exist?).with(@code).and_return(false)
+ expect(@script.ruby_cmd(@code, :options => "-W0 -Cdir", :args => "< file.txt")).to eq(
+ %(ruby_spec_exe -w -Q -W0 -Cdir -e "some \\"real\\" 'ruby' code" < file.txt)
+ )
+ end
+
+ it "returns a command with options and arguments but without code or file" do
+ expect(@script.ruby_cmd(nil, :options => "-c", :args => "> file.txt")).to eq(
+ "ruby_spec_exe -w -Q -c > file.txt"
+ )
+ end
+end
+
+RSpec.describe Object, "#ruby_exe" do
+ before :each do
+ stub_const 'RUBY_EXE', 'ruby_spec_exe -w -Q'
+
+ @script = RubyExeSpecs.new
+ allow(@script).to receive(:`).and_return('OUTPUT')
+
+ status_successful = double(Process::Status, exited?: true, exitstatus: 0)
+ allow(Process).to receive(:last_status).and_return(status_successful)
+ end
+
+ it "returns command STDOUT when given command" do
+ code = "code"
+ options = {}
+ output = "output"
+ allow(@script).to receive(:`).and_return(output)
+
+ expect(@script.ruby_exe(code, options)).to eq output
+ end
+
+ it "returns an Array containing the interpreter executable and flags when given no arguments" do
+ expect(@script.ruby_exe).to eq(['ruby_spec_exe', '-w', '-Q'])
+ end
+
+ it "executes (using `) the result of calling #ruby_cmd with the given arguments" do
+ code = "code"
+ options = {}
+ expect(@script).to receive(:ruby_cmd).and_return("ruby_cmd")
+ expect(@script).to receive(:`).with("ruby_cmd")
+ @script.ruby_exe(code, options)
+ end
+
+ it "raises exception when command exit status is not successful" do
+ code = "code"
+ options = {}
+
+ status_failed = double(Process::Status, exited?: true, exitstatus: 4)
+ allow(Process).to receive(:last_status).and_return(status_failed)
+
+ expect {
+ @script.ruby_exe(code, options)
+ }.to raise_error(%r{Expected exit status is 0 but actual is 4 for command ruby_exe\(.+\)})
+ end
+
+ it "shows in the exception message if a signal killed the process" do
+ code = "code"
+ options = {}
+
+ status_failed = double(Process::Status, exited?: false, signaled?: true, termsig: Signal.list.fetch('TERM'))
+ allow(Process).to receive(:last_status).and_return(status_failed)
+
+ expect {
+ @script.ruby_exe(code, options)
+ }.to raise_error(%r{Expected exit status is 0 but actual is :SIGTERM for command ruby_exe\(.+\)})
+ end
+
+ describe "with :dir option" do
+ it "is deprecated" do
+ expect {
+ @script.ruby_exe nil, :dir => "tmp"
+ }.to raise_error(/no longer supported, use Dir\.chdir/)
+ end
+ end
+
+ describe "with :env option" do
+ it "preserves the values of existing ENV keys" do
+ ENV["ABC"] = "123"
+ allow(ENV).to receive(:[])
+ expect(ENV).to receive(:[]).with("ABC")
+ @script.ruby_exe nil, :env => { :ABC => "xyz" }
+ end
+
+ it "adds the :env entries to ENV" do
+ expect(ENV).to receive(:[]=).with("ABC", "xyz")
+ @script.ruby_exe nil, :env => { :ABC => "xyz" }
+ end
+
+ it "deletes the :env entries in ENV when an exception is raised" do
+ expect(ENV).to receive(:delete).with("XYZ")
+ @script.ruby_exe nil, :env => { :XYZ => "xyz" }
+ end
+
+ it "resets the values of existing ENV keys when an exception is raised" do
+ ENV["ABC"] = "123"
+ expect(ENV).to receive(:[]=).with("ABC", "xyz")
+ expect(ENV).to receive(:[]=).with("ABC", "123")
+
+ expect(@script).to receive(:`).and_raise(Exception)
+ expect do
+ @script.ruby_exe nil, :env => { :ABC => "xyz" }
+ end.to raise_error(Exception)
+ end
+ end
+
+ describe "with :exit_status option" do
+ before do
+ status_failed = double(Process::Status, exited?: true, exitstatus: 4)
+ allow(Process).to receive(:last_status).and_return(status_failed)
+ end
+
+ it "raises exception when command ends with not expected status" do
+ expect {
+ @script.ruby_exe("path", exit_status: 1)
+ }.to raise_error(%r{Expected exit status is 1 but actual is 4 for command ruby_exe\(.+\)})
+ end
+
+ it "does not raise exception when command ends with expected status" do
+ output = "output"
+ allow(@script).to receive(:`).and_return(output)
+
+ expect(@script.ruby_exe("path", exit_status: 4)).to eq output
+ end
+ end
+end
diff --git a/spec/mspec/spec/helpers/scratch_spec.rb b/spec/mspec/spec/helpers/scratch_spec.rb
new file mode 100644
index 0000000000..9dbef94aa3
--- /dev/null
+++ b/spec/mspec/spec/helpers/scratch_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe ScratchPad do
+ it "records an object and returns a previously recorded object" do
+ ScratchPad.record :this
+ expect(ScratchPad.recorded).to eq(:this)
+ end
+
+ it "clears the recorded object" do
+ ScratchPad.record :that
+ expect(ScratchPad.recorded).to eq(:that)
+ ScratchPad.clear
+ expect(ScratchPad.recorded).to eq(nil)
+ end
+
+ it "provides a convenience shortcut to append to a previously recorded object" do
+ ScratchPad.record []
+ ScratchPad << :new
+ ScratchPad << :another
+ expect(ScratchPad.recorded).to eq([:new, :another])
+ end
+end
diff --git a/spec/mspec/spec/helpers/suppress_warning_spec.rb b/spec/mspec/spec/helpers/suppress_warning_spec.rb
new file mode 100644
index 0000000000..4cae189bd3
--- /dev/null
+++ b/spec/mspec/spec/helpers/suppress_warning_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe Object, "#suppress_warning" do
+ it "hides warnings" do
+ suppress_warning do
+ warn "should not be shown"
+ end
+ end
+
+ it "yields the block" do
+ a = 0
+ suppress_warning do
+ a = 1
+ end
+ expect(a).to eq(1)
+ end
+end
diff --git a/spec/mspec/spec/helpers/tmp_spec.rb b/spec/mspec/spec/helpers/tmp_spec.rb
new file mode 100644
index 0000000000..c41dcd57b3
--- /dev/null
+++ b/spec/mspec/spec/helpers/tmp_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+RSpec.describe Object, "#tmp" do
+ before :all do
+ @dir = SPEC_TEMP_DIR
+ end
+
+ it "returns a name relative to the current working directory" do
+ expect(tmp("test.txt")).to eq("#{@dir}/#{SPEC_TEMP_UNIQUIFIER}-test.txt")
+ end
+
+ it "returns a 'unique' name on repeated calls" do
+ a = tmp("text.txt")
+ b = tmp("text.txt")
+ expect(a).not_to eq(b)
+ end
+
+ it "does not 'uniquify' the name if requested not to" do
+ expect(tmp("test.txt", false)).to eq("#{@dir}/test.txt")
+ end
+
+ it "returns the name of the temporary directory when passed an empty string" do
+ expect(tmp("")).to eq("#{@dir}/")
+ end
+end
diff --git a/spec/mspec/spec/integration/interpreter_spec.rb b/spec/mspec/spec/integration/interpreter_spec.rb
new file mode 100644
index 0000000000..dbf3987a08
--- /dev/null
+++ b/spec/mspec/spec/integration/interpreter_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+RSpec.describe "The interpreter passed with -t" do
+ it "is used in subprocess" do
+ fixtures = "spec/fixtures"
+ interpreter = "#{fixtures}/my_ruby"
+ out, ret = run_mspec("run", "#{fixtures}/print_interpreter_spec.rb -t #{interpreter}")
+ out = out.lines.map(&:chomp).reject { |line|
+ line == 'RUBY_DESCRIPTION'
+ }.take(3)
+ expect(out).to eq([
+ interpreter,
+ interpreter,
+ "CWD/#{interpreter}"
+ ])
+ expect(ret.success?).to eq(true)
+ end
+end
diff --git a/spec/mspec/spec/integration/object_methods_spec.rb b/spec/mspec/spec/integration/object_methods_spec.rb
new file mode 100644
index 0000000000..697fbd10fa
--- /dev/null
+++ b/spec/mspec/spec/integration/object_methods_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+expected_output = <<EOS
+RUBY_DESCRIPTION
+.
+
+Finished in D.DDDDDD seconds
+
+1 file, 1 example, 1 expectation, 0 failures, 0 errors, 0 tagged
+EOS
+
+RSpec.describe "MSpec" do
+ it "does not define public methods on Object" do
+ out, ret = run_mspec("run", "spec/fixtures/object_methods_spec.rb")
+ expect(out).to eq(expected_output)
+ expect(ret.success?).to eq(true)
+ end
+end
diff --git a/spec/mspec/spec/integration/run_spec.rb b/spec/mspec/spec/integration/run_spec.rb
new file mode 100644
index 0000000000..90dc051543
--- /dev/null
+++ b/spec/mspec/spec/integration/run_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+RSpec.describe "Running mspec" do
+ a_spec_output = <<EOS
+
+1)
+Foo#bar errors FAILED
+Expected 1 == 2
+to be truthy but was false
+CWD/spec/fixtures/a_spec.rb:8:in `block (2 levels) in <top (required)>'
+CWD/spec/fixtures/a_spec.rb:2:in `<top (required)>'
+
+2)
+Foo#bar fails ERROR
+RuntimeError: failure
+CWD/spec/fixtures/a_spec.rb:12:in `block (2 levels) in <top (required)>'
+CWD/spec/fixtures/a_spec.rb:2:in `<top (required)>'
+
+Finished in D.DDDDDD seconds
+EOS
+
+ a_stats = "1 file, 3 examples, 2 expectations, 1 failure, 1 error, 0 tagged\n"
+ ab_stats = "2 files, 4 examples, 3 expectations, 1 failure, 1 error, 0 tagged\n"
+ fixtures = "spec/fixtures"
+
+ it "runs the specs" do
+ out, ret = run_mspec("run", "#{fixtures}/a_spec.rb")
+ expect(out).to eq("RUBY_DESCRIPTION\n.FE\n#{a_spec_output}\n#{a_stats}")
+ expect(ret.success?).to eq(false)
+ end
+
+ it "directly with mspec-run runs the specs" do
+ out, ret = run_mspec("-run", "#{fixtures}/a_spec.rb")
+ expect(out).to eq("RUBY_DESCRIPTION\n.FE\n#{a_spec_output}\n#{a_stats}")
+ expect(ret.success?).to eq(false)
+ end
+
+ it "runs the specs in parallel with -j using the dotted formatter" do
+ out, ret = run_mspec("run", "-j #{fixtures}/a_spec.rb #{fixtures}/b_spec.rb")
+ expect(out).to eq("RUBY_DESCRIPTION\n...\n#{a_spec_output}\n#{ab_stats}")
+ expect(ret.success?).to eq(false)
+ end
+
+ it "runs the specs in parallel with -j -fa" do
+ out, ret = run_mspec("run", "-j -fa #{fixtures}/a_spec.rb #{fixtures}/b_spec.rb")
+ progress_bar =
+ "\r[/ | 0% | 00:00:00] \e[0;32m 0F \e[0;32m 0E\e[0m " +
+ "\r[- | ==================50% | 00:00:00] \e[0;32m 0F \e[0;32m 0E\e[0m " +
+ "\r[\\ | ==================100%================== | 00:00:00] \e[0;32m 0F \e[0;32m 0E\e[0m "
+ expect(out).to eq("RUBY_DESCRIPTION\n#{progress_bar}\n#{a_spec_output}\n#{ab_stats}")
+ expect(ret.success?).to eq(false)
+ end
+
+ it "gives a useful error message when a subprocess dies in parallel mode" do
+ out, ret = run_mspec("run", "-j #{fixtures}/b_spec.rb #{fixtures}/die_spec.rb")
+ lines = out.lines
+ expect(lines).to include "A child mspec-run process died unexpectedly while running CWD/spec/fixtures/die_spec.rb\n"
+ expect(lines).to include "Finished in D.DDDDDD seconds\n"
+ expect(lines.last).to match(/^\d files?, \d examples?, \d expectations?, 0 failures, 0 errors, 0 tagged$/)
+ expect(ret.success?).to eq(false)
+ end
+
+ it "gives a useful error message when a subprocess prints unexpected output on STDOUT in parallel mode" do
+ out, ret = run_mspec("run", "-j #{fixtures}/b_spec.rb #{fixtures}/chatty_spec.rb")
+ lines = out.lines
+ expect(lines).to include "A child mspec-run process printed unexpected output on STDOUT: #{'"Hello\nIt\'s me!\n"'} while running CWD/spec/fixtures/chatty_spec.rb\n"
+ expect(lines).to include "Finished in D.DDDDDD seconds\n"
+ expect(lines.last).to eq("2 files, 2 examples, 2 expectations, 0 failures, 0 errors, 0 tagged\n")
+ expect(ret.success?).to eq(false)
+ end
+end
diff --git a/spec/mspec/spec/integration/tag_spec.rb b/spec/mspec/spec/integration/tag_spec.rb
new file mode 100644
index 0000000000..33df1cfd40
--- /dev/null
+++ b/spec/mspec/spec/integration/tag_spec.rb
@@ -0,0 +1,59 @@
+# encoding: utf-8
+require 'spec_helper'
+
+RSpec.describe "Running mspec tag" do
+ before :all do
+ FileUtils.rm_rf 'spec/fixtures/tags'
+ end
+
+ after :all do
+ FileUtils.rm_rf 'spec/fixtures/tags'
+ end
+
+ it "tags the failing specs" do
+ fixtures = "spec/fixtures"
+ out, ret = run_mspec("tag", "--add fails --fail #{fixtures}/tagging_spec.rb")
+ expect(out).to eq <<EOS
+RUBY_DESCRIPTION
+.FF
+TagAction: specs tagged with 'fails':
+
+Tag#me errors
+Tag#me érròrs in unicode
+
+
+1)
+Tag#me errors FAILED
+Expected 1 == 2
+to be truthy but was false
+CWD/spec/fixtures/tagging_spec.rb:9:in `block (2 levels) in <top (required)>'
+CWD/spec/fixtures/tagging_spec.rb:3:in `<top (required)>'
+
+2)
+Tag#me érròrs in unicode FAILED
+Expected 1 == 2
+to be truthy but was false
+CWD/spec/fixtures/tagging_spec.rb:13:in `block (2 levels) in <top (required)>'
+CWD/spec/fixtures/tagging_spec.rb:3:in `<top (required)>'
+
+Finished in D.DDDDDD seconds
+
+1 file, 3 examples, 3 expectations, 2 failures, 0 errors, 0 tagged
+EOS
+ expect(ret.success?).to eq(false)
+ end
+
+ it "does not run already tagged specs" do
+ fixtures = "spec/fixtures"
+ out, ret = run_mspec("run", "--excl-tag fails #{fixtures}/tagging_spec.rb")
+ expect(out).to eq <<EOS
+RUBY_DESCRIPTION
+.
+
+Finished in D.DDDDDD seconds
+
+1 file, 3 examples, 1 expectation, 0 failures, 0 errors, 2 tagged
+EOS
+ expect(ret.success?).to eq(true)
+ end
+end
diff --git a/spec/mspec/spec/matchers/base_spec.rb b/spec/mspec/spec/matchers/base_spec.rb
new file mode 100644
index 0000000000..6b5a3fbd72
--- /dev/null
+++ b/spec/mspec/spec/matchers/base_spec.rb
@@ -0,0 +1,228 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+require 'time'
+
+RSpec.describe SpecPositiveOperatorMatcher, "== operator" do
+ it "provides a failure message that 'Expected x to equal y'" do
+ expect {
+ SpecPositiveOperatorMatcher.new(1) == 2
+ }.to raise_error(SpecExpectationNotMetError, "Expected 1 == 2\nto be truthy but was false")
+ end
+
+ it "does not raise an exception when == returns true" do
+ SpecPositiveOperatorMatcher.new(1) == 1
+ end
+end
+
+RSpec.describe SpecPositiveOperatorMatcher, "=~ operator" do
+ it "provides a failure message that 'Expected \"x\" to match y'" do
+ expect {
+ SpecPositiveOperatorMatcher.new('real') =~ /fake/
+ }.to raise_error(SpecExpectationNotMetError, "Expected \"real\" =~ /fake/\nto be truthy but was nil")
+ end
+
+ it "does not raise an exception when =~ returns true" do
+ SpecPositiveOperatorMatcher.new('real') =~ /real/
+ end
+end
+
+RSpec.describe SpecPositiveOperatorMatcher, "> operator" do
+ it "provides a failure message that 'Expected x to be greater than y'" do
+ expect {
+ SpecPositiveOperatorMatcher.new(4) > 5
+ }.to raise_error(SpecExpectationNotMetError, "Expected 4 > 5\nto be truthy but was false")
+ end
+
+ it "does not raise an exception when > returns true" do
+ SpecPositiveOperatorMatcher.new(5) > 4
+ end
+end
+
+RSpec.describe SpecPositiveOperatorMatcher, ">= operator" do
+ it "provides a failure message that 'Expected x to be greater than or equal to y'" do
+ expect {
+ SpecPositiveOperatorMatcher.new(4) >= 5
+ }.to raise_error(SpecExpectationNotMetError, "Expected 4 >= 5\nto be truthy but was false")
+ end
+
+ it "does not raise an exception when > returns true" do
+ SpecPositiveOperatorMatcher.new(5) >= 4
+ SpecPositiveOperatorMatcher.new(5) >= 5
+ end
+end
+
+RSpec.describe SpecPositiveOperatorMatcher, "< operator" do
+ it "provides a failure message that 'Expected x to be less than y'" do
+ expect {
+ SpecPositiveOperatorMatcher.new(5) < 4
+ }.to raise_error(SpecExpectationNotMetError, "Expected 5 < 4\nto be truthy but was false")
+ end
+
+ it "does not raise an exception when < returns true" do
+ SpecPositiveOperatorMatcher.new(4) < 5
+ end
+end
+
+RSpec.describe SpecPositiveOperatorMatcher, "<= operator" do
+ it "provides a failure message that 'Expected x to be less than or equal to y'" do
+ expect {
+ SpecPositiveOperatorMatcher.new(5) <= 4
+ }.to raise_error(SpecExpectationNotMetError, "Expected 5 <= 4\nto be truthy but was false")
+ end
+
+ it "does not raise an exception when < returns true" do
+ SpecPositiveOperatorMatcher.new(4) <= 5
+ SpecPositiveOperatorMatcher.new(4) <= 4
+ end
+end
+
+RSpec.describe SpecPositiveOperatorMatcher, "arbitrary predicates" do
+ it "do not raise an exception when the predicate is truthy" do
+ SpecPositiveOperatorMatcher.new(2).eql?(2)
+ SpecPositiveOperatorMatcher.new(2).equal?(2)
+ SpecPositiveOperatorMatcher.new([1, 2, 3]).include?(2)
+ SpecPositiveOperatorMatcher.new("abc").start_with?("ab")
+ SpecPositiveOperatorMatcher.new("abc").start_with?("d", "a")
+ SpecPositiveOperatorMatcher.new(3).odd?
+ SpecPositiveOperatorMatcher.new([1, 2]).any? { |e| e.even? }
+ end
+
+ it "provide a failure message when the predicate returns a falsy value" do
+ expect {
+ SpecPositiveOperatorMatcher.new(2).eql?(3)
+ }.to raise_error(SpecExpectationNotMetError, "Expected 2.eql? 3\nto be truthy but was false")
+ expect {
+ SpecPositiveOperatorMatcher.new(2).equal?(3)
+ }.to raise_error(SpecExpectationNotMetError, "Expected 2.equal? 3\nto be truthy but was false")
+ expect {
+ SpecPositiveOperatorMatcher.new([1, 2, 3]).include?(4)
+ }.to raise_error(SpecExpectationNotMetError, "Expected [1, 2, 3].include? 4\nto be truthy but was false")
+ expect {
+ SpecPositiveOperatorMatcher.new("abc").start_with?("de")
+ }.to raise_error(SpecExpectationNotMetError, "Expected \"abc\".start_with? \"de\"\nto be truthy but was false")
+ expect {
+ SpecPositiveOperatorMatcher.new("abc").start_with?("d", "e")
+ }.to raise_error(SpecExpectationNotMetError, "Expected \"abc\".start_with? \"d\", \"e\"\nto be truthy but was false")
+ expect {
+ SpecPositiveOperatorMatcher.new(2).odd?
+ }.to raise_error(SpecExpectationNotMetError, "Expected 2.odd?\nto be truthy but was false")
+ expect {
+ SpecPositiveOperatorMatcher.new([1, 3]).any? { |e| e.even? }
+ }.to raise_error(SpecExpectationNotMetError, "Expected [1, 3].any? { ... }\nto be truthy but was false")
+ end
+end
+
+RSpec.describe SpecNegativeOperatorMatcher, "arbitrary predicates" do
+ it "do not raise an exception when the predicate returns a falsy value" do
+ SpecNegativeOperatorMatcher.new(2).eql?(3)
+ SpecNegativeOperatorMatcher.new(2).equal?(3)
+ SpecNegativeOperatorMatcher.new([1, 2, 3]).include?(4)
+ SpecNegativeOperatorMatcher.new("abc").start_with?("de")
+ SpecNegativeOperatorMatcher.new("abc").start_with?("d", "e")
+ SpecNegativeOperatorMatcher.new(2).odd?
+ SpecNegativeOperatorMatcher.new([1, 3]).any? { |e| e.even? }
+ end
+
+ it "provide a failure message when the predicate returns a truthy value" do
+ expect {
+ SpecNegativeOperatorMatcher.new(2).eql?(2)
+ }.to raise_error(SpecExpectationNotMetError, "Expected 2.eql? 2\nto be falsy but was true")
+ expect {
+ SpecNegativeOperatorMatcher.new(2).equal?(2)
+ }.to raise_error(SpecExpectationNotMetError, "Expected 2.equal? 2\nto be falsy but was true")
+ expect {
+ SpecNegativeOperatorMatcher.new([1, 2, 3]).include?(2)
+ }.to raise_error(SpecExpectationNotMetError, "Expected [1, 2, 3].include? 2\nto be falsy but was true")
+ expect {
+ SpecNegativeOperatorMatcher.new("abc").start_with?("ab")
+ }.to raise_error(SpecExpectationNotMetError, "Expected \"abc\".start_with? \"ab\"\nto be falsy but was true")
+ expect {
+ SpecNegativeOperatorMatcher.new("abc").start_with?("d", "a")
+ }.to raise_error(SpecExpectationNotMetError, "Expected \"abc\".start_with? \"d\", \"a\"\nto be falsy but was true")
+ expect {
+ SpecNegativeOperatorMatcher.new(3).odd?
+ }.to raise_error(SpecExpectationNotMetError, "Expected 3.odd?\nto be falsy but was true")
+ expect {
+ SpecNegativeOperatorMatcher.new([1, 2]).any? { |e| e.even? }
+ }.to raise_error(SpecExpectationNotMetError, "Expected [1, 2].any? { ... }\nto be falsy but was true")
+ end
+end
+
+RSpec.describe SpecNegativeOperatorMatcher, "== operator" do
+ it "provides a failure message that 'Expected x not to equal y'" do
+ expect {
+ SpecNegativeOperatorMatcher.new(1) == 1
+ }.to raise_error(SpecExpectationNotMetError, "Expected 1 == 1\nto be falsy but was true")
+ end
+
+ it "does not raise an exception when == returns false" do
+ SpecNegativeOperatorMatcher.new(1) == 2
+ end
+end
+
+RSpec.describe SpecNegativeOperatorMatcher, "=~ operator" do
+ it "provides a failure message that 'Expected \"x\" not to match /y/'" do
+ expect {
+ SpecNegativeOperatorMatcher.new('real') =~ /real/
+ }.to raise_error(SpecExpectationNotMetError, "Expected \"real\" =~ /real/\nto be falsy but was 0")
+ end
+
+ it "does not raise an exception when =~ returns false" do
+ SpecNegativeOperatorMatcher.new('real') =~ /fake/
+ end
+end
+
+RSpec.describe SpecNegativeOperatorMatcher, "< operator" do
+ it "provides a failure message that 'Expected x not to be less than y'" do
+ expect {
+ SpecNegativeOperatorMatcher.new(4) < 5
+ }.to raise_error(SpecExpectationNotMetError, "Expected 4 < 5\nto be falsy but was true")
+ end
+
+ it "does not raise an exception when < returns false" do
+ SpecNegativeOperatorMatcher.new(5) < 4
+ end
+end
+
+RSpec.describe SpecNegativeOperatorMatcher, "<= operator" do
+ it "provides a failure message that 'Expected x not to be less than or equal to y'" do
+ expect {
+ SpecNegativeOperatorMatcher.new(4) <= 5
+ }.to raise_error(SpecExpectationNotMetError, "Expected 4 <= 5\nto be falsy but was true")
+ expect {
+ SpecNegativeOperatorMatcher.new(5) <= 5
+ }.to raise_error(SpecExpectationNotMetError, "Expected 5 <= 5\nto be falsy but was true")
+ end
+
+ it "does not raise an exception when <= returns false" do
+ SpecNegativeOperatorMatcher.new(5) <= 4
+ end
+end
+
+RSpec.describe SpecNegativeOperatorMatcher, "> operator" do
+ it "provides a failure message that 'Expected x not to be greater than y'" do
+ expect {
+ SpecNegativeOperatorMatcher.new(5) > 4
+ }.to raise_error(SpecExpectationNotMetError, "Expected 5 > 4\nto be falsy but was true")
+ end
+
+ it "does not raise an exception when > returns false" do
+ SpecNegativeOperatorMatcher.new(4) > 5
+ end
+end
+
+RSpec.describe SpecNegativeOperatorMatcher, ">= operator" do
+ it "provides a failure message that 'Expected x not to be greater than or equal to y'" do
+ expect {
+ SpecNegativeOperatorMatcher.new(5) >= 4
+ }.to raise_error(SpecExpectationNotMetError, "Expected 5 >= 4\nto be falsy but was true")
+ expect {
+ SpecNegativeOperatorMatcher.new(5) >= 5
+ }.to raise_error(SpecExpectationNotMetError, "Expected 5 >= 5\nto be falsy but was true")
+ end
+
+ it "does not raise an exception when >= returns false" do
+ SpecNegativeOperatorMatcher.new(4) >= 5
+ end
+end
diff --git a/spec/mspec/spec/matchers/be_an_instance_of_spec.rb b/spec/mspec/spec/matchers/be_an_instance_of_spec.rb
new file mode 100644
index 0000000000..7c74249d24
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_an_instance_of_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+module BeAnInOfSpecs
+ class A
+ end
+
+ class B < A
+ end
+
+ class C < B
+ end
+end
+
+RSpec.describe BeAnInstanceOfMatcher do
+ it "matches when actual is an instance_of? expected" do
+ a = BeAnInOfSpecs::A.new
+ expect(BeAnInstanceOfMatcher.new(BeAnInOfSpecs::A).matches?(a)).to be_truthy
+
+ b = BeAnInOfSpecs::B.new
+ expect(BeAnInstanceOfMatcher.new(BeAnInOfSpecs::B).matches?(b)).to be_truthy
+ end
+
+ it "does not match when actual is not an instance_of? expected" do
+ a = BeAnInOfSpecs::A.new
+ expect(BeAnInstanceOfMatcher.new(BeAnInOfSpecs::B).matches?(a)).to be_falsey
+
+ b = BeAnInOfSpecs::B.new
+ expect(BeAnInstanceOfMatcher.new(BeAnInOfSpecs::A).matches?(b)).to be_falsey
+
+ c = BeAnInOfSpecs::C.new
+ expect(BeAnInstanceOfMatcher.new(BeAnInOfSpecs::A).matches?(c)).to be_falsey
+ expect(BeAnInstanceOfMatcher.new(BeAnInOfSpecs::B).matches?(c)).to be_falsey
+ end
+
+ it "provides a useful failure message" do
+ matcher = BeAnInstanceOfMatcher.new(Numeric)
+ matcher.matches?("string")
+ expect(matcher.failure_message).to eq([
+ "Expected \"string\" (String)", "to be an instance of Numeric"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = BeAnInstanceOfMatcher.new(Numeric)
+ matcher.matches?(4.0)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected 4.0 (Float)", "not to be an instance of Numeric"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/be_ancestor_of_spec.rb b/spec/mspec/spec/matchers/be_ancestor_of_spec.rb
new file mode 100644
index 0000000000..abc05e0f7a
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_ancestor_of_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+class Parent; end
+class Child < Parent; end
+
+RSpec.describe BeAncestorOfMatcher do
+ it "matches when actual is an ancestor of expected" do
+ expect(BeAncestorOfMatcher.new(Child).matches?(Parent)).to eq(true)
+ end
+
+ it "does not match when actual is not an ancestor of expected" do
+ expect(BeAncestorOfMatcher.new(Parent).matches?(Child)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = BeAncestorOfMatcher.new(Parent)
+ matcher.matches?(Child)
+ expect(matcher.failure_message).to eq(["Expected Child", "to be an ancestor of Parent"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = BeAncestorOfMatcher.new(Child)
+ matcher.matches?(Parent)
+ expect(matcher.negative_failure_message).to eq(["Expected Parent", "not to be an ancestor of Child"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/be_close_spec.rb b/spec/mspec/spec/matchers/be_close_spec.rb
new file mode 100644
index 0000000000..dfd4f4ddbb
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_close_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+# Adapted from RSpec 1.0.8
+RSpec.describe BeCloseMatcher do
+ it "matches when actual == expected" do
+ expect(BeCloseMatcher.new(5.0, 0.5).matches?(5.0)).to eq(true)
+ end
+
+ it "matches when actual < (expected + tolerance)" do
+ expect(BeCloseMatcher.new(5.0, 0.5).matches?(5.49)).to eq(true)
+ end
+
+ it "matches when actual > (expected - tolerance)" do
+ expect(BeCloseMatcher.new(5.0, 0.5).matches?(4.51)).to eq(true)
+ end
+
+ it "matches when actual == (expected + tolerance)" do
+ expect(BeCloseMatcher.new(5.0, 0.5).matches?(5.5)).to eq(true)
+ expect(BeCloseMatcher.new(3, 2).matches?(5)).to eq(true)
+ end
+
+ it "matches when actual == (expected - tolerance)" do
+ expect(BeCloseMatcher.new(5.0, 0.5).matches?(4.5)).to eq(true)
+ expect(BeCloseMatcher.new(3, 2).matches?(1)).to eq(true)
+ end
+
+ it "does not match when actual < (expected - tolerance)" do
+ expect(BeCloseMatcher.new(5.0, 0.5).matches?(4.49)).to eq(false)
+ end
+
+ it "does not match when actual > (expected + tolerance)" do
+ expect(BeCloseMatcher.new(5.0, 0.5).matches?(5.51)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = BeCloseMatcher.new(5.0, 0.5)
+ matcher.matches?(6.5)
+ expect(matcher.failure_message).to eq(["Expected 6.5", "to be within 5.0 +/- 0.5"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = BeCloseMatcher.new(5.0, 0.5)
+ matcher.matches?(4.9)
+ expect(matcher.negative_failure_message).to eq(["Expected 4.9", "not to be within 5.0 +/- 0.5"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/be_computed_by_spec.rb b/spec/mspec/spec/matchers/be_computed_by_spec.rb
new file mode 100644
index 0000000000..f73861a576
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_computed_by_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+require 'mspec/matchers'
+
+RSpec.describe BeComputedByMatcher do
+ it "matches when all entries in the Array compute" do
+ array = [ [65, "A"],
+ [90, "Z"] ]
+ expect(BeComputedByMatcher.new(:chr).matches?(array)).to be_truthy
+ end
+
+ it "matches when all entries in the Array with arguments compute" do
+ array = [ [1, 2, 3],
+ [2, 4, 6] ]
+ expect(BeComputedByMatcher.new(:+).matches?(array)).to be_truthy
+ end
+
+ it "does not match when any entry in the Array does not compute" do
+ array = [ [65, "A" ],
+ [91, "Z" ] ]
+ expect(BeComputedByMatcher.new(:chr).matches?(array)).to be_falsey
+ end
+
+ it "accepts an argument list to apply to each method call" do
+ array = [ [65, "1000001" ],
+ [90, "1011010" ] ]
+ expect(BeComputedByMatcher.new(:to_s, 2).matches?(array)).to be_truthy
+ end
+
+ it "does not match when any entry in the Array with arguments does not compute" do
+ array = [ [1, 2, 3],
+ [2, 4, 7] ]
+ expect(BeComputedByMatcher.new(:+).matches?(array)).to be_falsey
+ end
+
+ it "provides a useful failure message" do
+ array = [ [65, "A" ],
+ [91, "Z" ] ]
+ matcher = BeComputedByMatcher.new(:chr)
+ matcher.matches?(array)
+ expect(matcher.failure_message).to eq(["Expected \"Z\"", "to be computed by 91.chr (computed \"[\" instead)"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/be_empty_spec.rb b/spec/mspec/spec/matchers/be_empty_spec.rb
new file mode 100644
index 0000000000..30678fe85f
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_empty_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe BeEmptyMatcher do
+ it "matches when actual is empty" do
+ expect(BeEmptyMatcher.new.matches?("")).to eq(true)
+ end
+
+ it "does not match when actual is not empty" do
+ expect(BeEmptyMatcher.new.matches?([10])).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = BeEmptyMatcher.new
+ matcher.matches?("not empty string")
+ expect(matcher.failure_message).to eq(["Expected \"not empty string\"", "to be empty"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = BeEmptyMatcher.new
+ matcher.matches?("")
+ expect(matcher.negative_failure_message).to eq(["Expected \"\"", "not to be empty"])
+ end
+end
+
diff --git a/spec/mspec/spec/matchers/be_false_spec.rb b/spec/mspec/spec/matchers/be_false_spec.rb
new file mode 100644
index 0000000000..46d7253220
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_false_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe BeFalseMatcher do
+ it "matches when actual is false" do
+ expect(BeFalseMatcher.new.matches?(false)).to eq(true)
+ end
+
+ it "does not match when actual is not false" do
+ expect(BeFalseMatcher.new.matches?("")).to eq(false)
+ expect(BeFalseMatcher.new.matches?(true)).to eq(false)
+ expect(BeFalseMatcher.new.matches?(nil)).to eq(false)
+ expect(BeFalseMatcher.new.matches?(0)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = BeFalseMatcher.new
+ matcher.matches?("some string")
+ expect(matcher.failure_message).to eq(["Expected \"some string\"", "to be false"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = BeFalseMatcher.new
+ matcher.matches?(false)
+ expect(matcher.negative_failure_message).to eq(["Expected false", "not to be false"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/be_kind_of_spec.rb b/spec/mspec/spec/matchers/be_kind_of_spec.rb
new file mode 100644
index 0000000000..1e19058411
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_kind_of_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe BeKindOfMatcher do
+ it "matches when actual is a kind_of? expected" do
+ expect(BeKindOfMatcher.new(Numeric).matches?(1)).to eq(true)
+ expect(BeKindOfMatcher.new(Integer).matches?(2)).to eq(true)
+ expect(BeKindOfMatcher.new(Regexp).matches?(/m/)).to eq(true)
+ end
+
+ it "does not match when actual is not a kind_of? expected" do
+ expect(BeKindOfMatcher.new(Integer).matches?(1.5)).to eq(false)
+ expect(BeKindOfMatcher.new(String).matches?(:a)).to eq(false)
+ expect(BeKindOfMatcher.new(Hash).matches?([])).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = BeKindOfMatcher.new(Numeric)
+ matcher.matches?('string')
+ expect(matcher.failure_message).to eq([
+ "Expected \"string\" (String)", "to be kind of Numeric"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = BeKindOfMatcher.new(Numeric)
+ matcher.matches?(4.0)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected 4.0 (Float)", "not to be kind of Numeric"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/be_nan_spec.rb b/spec/mspec/spec/matchers/be_nan_spec.rb
new file mode 100644
index 0000000000..baa7447943
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_nan_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/guards'
+require 'mspec/helpers'
+require 'mspec/matchers'
+
+RSpec.describe BeNaNMatcher do
+ it "matches when actual is NaN" do
+ expect(BeNaNMatcher.new.matches?(nan_value)).to eq(true)
+ end
+
+ it "does not match when actual is not NaN" do
+ expect(BeNaNMatcher.new.matches?(1.0)).to eq(false)
+ expect(BeNaNMatcher.new.matches?(0)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = BeNaNMatcher.new
+ matcher.matches?(0)
+ expect(matcher.failure_message).to eq(["Expected 0", "to be NaN"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = BeNaNMatcher.new
+ matcher.matches?(nan_value)
+ expect(matcher.negative_failure_message).to eq(["Expected NaN", "not to be NaN"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/be_nil_spec.rb b/spec/mspec/spec/matchers/be_nil_spec.rb
new file mode 100644
index 0000000000..e2768acf83
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_nil_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe BeNilMatcher do
+ it "matches when actual is nil" do
+ expect(BeNilMatcher.new.matches?(nil)).to eq(true)
+ end
+
+ it "does not match when actual is not nil" do
+ expect(BeNilMatcher.new.matches?("")).to eq(false)
+ expect(BeNilMatcher.new.matches?(false)).to eq(false)
+ expect(BeNilMatcher.new.matches?(0)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = BeNilMatcher.new
+ matcher.matches?("some string")
+ expect(matcher.failure_message).to eq(["Expected \"some string\"", "to be nil"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = BeNilMatcher.new
+ matcher.matches?(nil)
+ expect(matcher.negative_failure_message).to eq(["Expected nil", "not to be nil"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/be_true_or_false_spec.rb b/spec/mspec/spec/matchers/be_true_or_false_spec.rb
new file mode 100644
index 0000000000..e4b456eafc
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_true_or_false_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe BeTrueOrFalseMatcher do
+ it "matches when actual is true" do
+ expect(BeTrueOrFalseMatcher.new.matches?(true)).to eq(true)
+ end
+
+ it "matches when actual is false" do
+ expect(BeTrueOrFalseMatcher.new.matches?(false)).to eq(true)
+ end
+
+ it "provides a useful failure message" do
+ matcher = BeTrueOrFalseMatcher.new
+ matcher.matches?("some string")
+ expect(matcher.failure_message).to eq(["Expected \"some string\"", "to be true or false"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/be_true_spec.rb b/spec/mspec/spec/matchers/be_true_spec.rb
new file mode 100644
index 0000000000..39ef05a0f8
--- /dev/null
+++ b/spec/mspec/spec/matchers/be_true_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe BeTrueMatcher do
+ it "matches when actual is true" do
+ expect(BeTrueMatcher.new.matches?(true)).to eq(true)
+ end
+
+ it "does not match when actual is not true" do
+ expect(BeTrueMatcher.new.matches?("")).to eq(false)
+ expect(BeTrueMatcher.new.matches?(false)).to eq(false)
+ expect(BeTrueMatcher.new.matches?(nil)).to eq(false)
+ expect(BeTrueMatcher.new.matches?(0)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = BeTrueMatcher.new
+ matcher.matches?("some string")
+ expect(matcher.failure_message).to eq(["Expected \"some string\"", "to be true"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = BeTrueMatcher.new
+ matcher.matches?(true)
+ expect(matcher.negative_failure_message).to eq(["Expected true", "not to be true"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/block_caller_spec.rb b/spec/mspec/spec/matchers/block_caller_spec.rb
new file mode 100644
index 0000000000..5d7085fa63
--- /dev/null
+++ b/spec/mspec/spec/matchers/block_caller_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe BlockingMatcher do
+ it 'matches when a Proc blocks the caller' do
+ expect(BlockingMatcher.new.matches?(proc { sleep })).to eq(true)
+ end
+
+ it 'does not match when a Proc does not block the caller' do
+ expect(BlockingMatcher.new.matches?(proc { 1 })).to eq(false)
+ end
+end
diff --git a/spec/mspec/spec/matchers/complain_spec.rb b/spec/mspec/spec/matchers/complain_spec.rb
new file mode 100644
index 0000000000..399ef3105b
--- /dev/null
+++ b/spec/mspec/spec/matchers/complain_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe ComplainMatcher do
+ it "matches when executing the proc results in output to $stderr" do
+ proc = lambda { warn "I'm gonna tell yo mama" }
+ expect(ComplainMatcher.new(nil).matches?(proc)).to eq(true)
+ end
+
+ it "matches when executing the proc results in the expected output to $stderr" do
+ proc = lambda { warn "Que haces?" }
+ expect(ComplainMatcher.new("Que haces?\n").matches?(proc)).to eq(true)
+ expect(ComplainMatcher.new("Que pasa?\n").matches?(proc)).to eq(false)
+ expect(ComplainMatcher.new(/Que/).matches?(proc)).to eq(true)
+ expect(ComplainMatcher.new(/Quoi/).matches?(proc)).to eq(false)
+ end
+
+ it "does not match when there is no output to $stderr" do
+ expect(ComplainMatcher.new(nil).matches?(lambda {})).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = ComplainMatcher.new(nil)
+ matcher.matches?(lambda { })
+ expect(matcher.failure_message).to eq(["Expected a warning", "but received none"])
+ matcher = ComplainMatcher.new("listen here")
+ matcher.matches?(lambda { warn "look out" })
+ expect(matcher.failure_message).to eq(
+ ["Expected warning: \"listen here\"", "but got: \"look out\""]
+ )
+ matcher = ComplainMatcher.new(/talk/)
+ matcher.matches?(lambda { warn "listen up" })
+ expect(matcher.failure_message).to eq(
+ ["Expected warning to match: /talk/", "but got: \"listen up\""]
+ )
+ end
+
+ it "provides a useful negative failure message" do
+ proc = lambda { warn "ouch" }
+ matcher = ComplainMatcher.new(nil)
+ matcher.matches?(proc)
+ expect(matcher.negative_failure_message).to eq(
+ ["Unexpected warning: ", "\"ouch\""]
+ )
+ matcher = ComplainMatcher.new("ouchy")
+ matcher.matches?(proc)
+ expect(matcher.negative_failure_message).to eq(
+ ["Expected warning: \"ouchy\"", "but got: \"ouch\""]
+ )
+ matcher = ComplainMatcher.new(/ou/)
+ matcher.matches?(proc)
+ expect(matcher.negative_failure_message).to eq(
+ ["Expected warning not to match: /ou/", "but got: \"ouch\""]
+ )
+ end
+
+ context "`verbose` option specified" do
+ before do
+ $VERBOSE, @verbose = nil, $VERBOSE
+ end
+
+ after do
+ $VERBOSE = @verbose
+ end
+
+ it "sets $VERBOSE with specified second optional parameter" do
+ verbose = nil
+ proc = lambda { verbose = $VERBOSE }
+
+ ComplainMatcher.new(nil, verbose: true).matches?(proc)
+ expect(verbose).to eq(true)
+
+ ComplainMatcher.new(nil, verbose: false).matches?(proc)
+ expect(verbose).to eq(false)
+ end
+
+ it "sets $VERBOSE with false by default" do
+ verbose = nil
+ proc = lambda { verbose = $VERBOSE }
+
+ ComplainMatcher.new(nil).matches?(proc)
+ expect(verbose).to eq(false)
+ end
+
+ it "does not have side effect" do
+ proc = lambda { safe_value = $VERBOSE }
+
+ expect do
+ ComplainMatcher.new(nil, verbose: true).matches?(proc)
+ end.not_to change { $VERBOSE }
+ end
+
+ it "accepts a verbose level as single argument" do
+ verbose = nil
+ proc = lambda { verbose = $VERBOSE }
+
+ ComplainMatcher.new(verbose: true).matches?(proc)
+ expect(verbose).to eq(true)
+ end
+ end
+end
diff --git a/spec/mspec/spec/matchers/eql_spec.rb b/spec/mspec/spec/matchers/eql_spec.rb
new file mode 100644
index 0000000000..66307d2a9d
--- /dev/null
+++ b/spec/mspec/spec/matchers/eql_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe EqlMatcher do
+ it "matches when actual is eql? to expected" do
+ expect(EqlMatcher.new(1).matches?(1)).to eq(true)
+ expect(EqlMatcher.new(1.5).matches?(1.5)).to eq(true)
+ expect(EqlMatcher.new("red").matches?("red")).to eq(true)
+ expect(EqlMatcher.new(:blue).matches?(:blue)).to eq(true)
+ expect(EqlMatcher.new(Object).matches?(Object)).to eq(true)
+
+ o = Object.new
+ expect(EqlMatcher.new(o).matches?(o)).to eq(true)
+ end
+
+ it "does not match when actual is not eql? to expected" do
+ expect(EqlMatcher.new(1).matches?(1.0)).to eq(false)
+ expect(EqlMatcher.new(Hash).matches?(Object)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = EqlMatcher.new("red")
+ matcher.matches?("red")
+ expect(matcher.failure_message).to eq(["Expected \"red\"", "to have same value and type as \"red\""])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = EqlMatcher.new(1)
+ matcher.matches?(1.0)
+ expect(matcher.negative_failure_message).to eq(["Expected 1.0", "not to have same value or type as 1"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/equal_element_spec.rb b/spec/mspec/spec/matchers/equal_element_spec.rb
new file mode 100644
index 0000000000..3a5ae4ede2
--- /dev/null
+++ b/spec/mspec/spec/matchers/equal_element_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe EqualElementMatcher do
+ it "matches if it finds an element with the passed name, no matter what attributes/content" do
+ expect(EqualElementMatcher.new("A").matches?('<A></A>')).to be_truthy
+ expect(EqualElementMatcher.new("A").matches?('<A HREF="http://example.com"></A>')).to be_truthy
+ expect(EqualElementMatcher.new("A").matches?('<A HREF="http://example.com"></A>')).to be_truthy
+
+ expect(EqualElementMatcher.new("BASE").matches?('<BASE></A>')).to be_falsey
+ expect(EqualElementMatcher.new("BASE").matches?('<A></BASE>')).to be_falsey
+ expect(EqualElementMatcher.new("BASE").matches?('<A></A>')).to be_falsey
+ expect(EqualElementMatcher.new("BASE").matches?('<A HREF="http://example.com"></A>')).to be_falsey
+ expect(EqualElementMatcher.new("BASE").matches?('<A HREF="http://example.com"></A>')).to be_falsey
+ end
+
+ it "matches if it finds an element with the passed name and the passed attributes" do
+ expect(EqualElementMatcher.new("A", {}).matches?('<A></A>')).to be_truthy
+ expect(EqualElementMatcher.new("A", nil).matches?('<A HREF="http://example.com"></A>')).to be_truthy
+ expect(EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('<A HREF="http://example.com"></A>')).to be_truthy
+
+ expect(EqualElementMatcher.new("A", {}).matches?('<A HREF="http://example.com"></A>')).to be_falsey
+ expect(EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('<A></A>')).to be_falsey
+ expect(EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('<A HREF="http://test.com"></A>')).to be_falsey
+ expect(EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('<A HREF="http://example.com" HREF="http://example.com"></A>')).to be_falsey
+ end
+
+ it "matches if it finds an element with the passed name, the passed attributes and the passed content" do
+ expect(EqualElementMatcher.new("A", {}, "").matches?('<A></A>')).to be_truthy
+ expect(EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('<A HREF="http://example.com">Example</A>')).to be_truthy
+
+ expect(EqualElementMatcher.new("A", {}, "Test").matches?('<A></A>')).to be_falsey
+ expect(EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('<A HREF="http://example.com"></A>')).to be_falsey
+ expect(EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('<A HREF="http://example.com">Test</A>')).to be_falsey
+ end
+
+ it "can match unclosed elements" do
+ expect(EqualElementMatcher.new("BASE", nil, nil, :not_closed => true).matches?('<BASE>')).to be_truthy
+ expect(EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, nil, :not_closed => true).matches?('<BASE HREF="http://example.com">')).to be_truthy
+ expect(EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "Example", :not_closed => true).matches?('<BASE HREF="http://example.com">Example')).to be_truthy
+
+ expect(EqualElementMatcher.new("BASE", {}, nil, :not_closed => true).matches?('<BASE HREF="http://example.com">')).to be_falsey
+ expect(EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "", :not_closed => true).matches?('<BASE HREF="http://example.com">Example')).to be_falsey
+ expect(EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "Test", :not_closed => true).matches?('<BASE HREF="http://example.com">Example')).to be_falsey
+ end
+
+ it "provides a useful failure message" do
+ equal_element = EqualElementMatcher.new("A", {}, "Test")
+ expect(equal_element.matches?('<A></A>')).to be_falsey
+ expect(equal_element.failure_message).to eq([%{Expected "<A></A>"}, %{to be a 'A' element with no attributes and "Test" as content}])
+
+ equal_element = EqualElementMatcher.new("A", {}, "")
+ expect(equal_element.matches?('<A>Test</A>')).to be_falsey
+ expect(equal_element.failure_message).to eq([%{Expected "<A>Test</A>"}, %{to be a 'A' element with no attributes and no content}])
+
+ equal_element = EqualElementMatcher.new("A", "HREF" => "http://www.example.com")
+ expect(equal_element.matches?('<A>Test</A>')).to be_falsey
+ expect(equal_element.failure_message).to eq([%{Expected "<A>Test</A>"}, %{to be a 'A' element with HREF="http://www.example.com" and any content}])
+ end
+
+ it "provides a useful negative failure message" do
+ equal_element = EqualElementMatcher.new("A", {}, "Test")
+ expect(equal_element.matches?('<A></A>')).to be_falsey
+ expect(equal_element.negative_failure_message).to eq([%{Expected "<A></A>"}, %{not to be a 'A' element with no attributes and "Test" as content}])
+
+ equal_element = EqualElementMatcher.new("A", {}, "")
+ expect(equal_element.matches?('<A>Test</A>')).to be_falsey
+ expect(equal_element.negative_failure_message).to eq([%{Expected "<A>Test</A>"}, %{not to be a 'A' element with no attributes and no content}])
+
+ equal_element = EqualElementMatcher.new("A", "HREF" => "http://www.example.com")
+ expect(equal_element.matches?('<A>Test</A>')).to be_falsey
+ expect(equal_element.negative_failure_message).to eq([%{Expected "<A>Test</A>"}, %{not to be a 'A' element with HREF="http://www.example.com" and any content}])
+ end
+end
diff --git a/spec/mspec/spec/matchers/equal_spec.rb b/spec/mspec/spec/matchers/equal_spec.rb
new file mode 100644
index 0000000000..2df1de54b4
--- /dev/null
+++ b/spec/mspec/spec/matchers/equal_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe EqualMatcher do
+ it "matches when actual is equal? to expected" do
+ expect(EqualMatcher.new(1).matches?(1)).to eq(true)
+ expect(EqualMatcher.new(:blue).matches?(:blue)).to eq(true)
+ expect(EqualMatcher.new(Object).matches?(Object)).to eq(true)
+
+ o = Object.new
+ expect(EqualMatcher.new(o).matches?(o)).to eq(true)
+ end
+
+ it "does not match when actual is not a equal? to expected" do
+ expect(EqualMatcher.new(1).matches?(1.0)).to eq(false)
+ expect(EqualMatcher.new("blue").matches?("blue")).to eq(false)
+ expect(EqualMatcher.new(Hash).matches?(Object)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = EqualMatcher.new("red")
+ matcher.matches?("red")
+ expect(matcher.failure_message).to eq(["Expected \"red\"", "to be identical to \"red\""])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = EqualMatcher.new(1)
+ matcher.matches?(1)
+ expect(matcher.negative_failure_message).to eq(["Expected 1", "not to be identical to 1"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/have_class_variable_spec.rb b/spec/mspec/spec/matchers/have_class_variable_spec.rb
new file mode 100644
index 0000000000..d6fcf9d4e2
--- /dev/null
+++ b/spec/mspec/spec/matchers/have_class_variable_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+class IVarModMock
+ def self.class_variables
+ [:@foo]
+ end
+end
+
+RSpec.describe HaveClassVariableMatcher, "on RUBY_VERSION >= 1.9" do
+ it "matches when mod has the class variable, given as string" do
+ matcher = HaveClassVariableMatcher.new('@foo')
+ expect(matcher.matches?(IVarModMock)).to be_truthy
+ end
+
+ it "matches when mod has the class variable, given as symbol" do
+ matcher = HaveClassVariableMatcher.new(:@foo)
+ expect(matcher.matches?(IVarModMock)).to be_truthy
+ end
+
+ it "does not match when mod hasn't got the class variable, given as string" do
+ matcher = HaveClassVariableMatcher.new('@bar')
+ expect(matcher.matches?(IVarModMock)).to be_falsey
+ end
+
+ it "does not match when mod hasn't got the class variable, given as symbol" do
+ matcher = HaveClassVariableMatcher.new(:@bar)
+ expect(matcher.matches?(IVarModMock)).to be_falsey
+ end
+
+ it "provides a failure message for #should" do
+ matcher = HaveClassVariableMatcher.new(:@bar)
+ matcher.matches?(IVarModMock)
+ expect(matcher.failure_message).to eq([
+ "Expected IVarModMock to have class variable '@bar'",
+ "but it does not"
+ ])
+ end
+
+ it "provides a failure messoge for #should_not" do
+ matcher = HaveClassVariableMatcher.new(:@bar)
+ matcher.matches?(IVarModMock)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected IVarModMock NOT to have class variable '@bar'",
+ "but it does"
+ ])
+ end
+end
diff --git a/spec/mspec/spec/matchers/have_constant_spec.rb b/spec/mspec/spec/matchers/have_constant_spec.rb
new file mode 100644
index 0000000000..0bf44dbe2b
--- /dev/null
+++ b/spec/mspec/spec/matchers/have_constant_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+class HCMSpecs
+ X = :x
+end
+
+RSpec.describe HaveConstantMatcher do
+ it "matches when mod has the constant" do
+ matcher = HaveConstantMatcher.new :X
+ expect(matcher.matches?(HCMSpecs)).to be_truthy
+ end
+
+ it "does not match when mod does not have the constant" do
+ matcher = HaveConstantMatcher.new :A
+ expect(matcher.matches?(HCMSpecs)).to be_falsey
+ end
+
+ it "provides a failure message for #should" do
+ matcher = HaveConstantMatcher.new :A
+ matcher.matches?(HCMSpecs)
+ expect(matcher.failure_message).to eq([
+ "Expected HCMSpecs to have constant 'A'",
+ "but it does not"
+ ])
+ end
+
+ it "provides a failure messoge for #should_not" do
+ matcher = HaveConstantMatcher.new :X
+ matcher.matches?(HCMSpecs)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected HCMSpecs NOT to have constant 'X'",
+ "but it does"
+ ])
+ end
+end
diff --git a/spec/mspec/spec/matchers/have_instance_method_spec.rb b/spec/mspec/spec/matchers/have_instance_method_spec.rb
new file mode 100644
index 0000000000..7c2e50dba6
--- /dev/null
+++ b/spec/mspec/spec/matchers/have_instance_method_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+class HIMMSpecs
+ def instance_method
+ end
+
+ class Subclass < HIMMSpecs
+ def instance_sub_method
+ end
+ end
+end
+
+RSpec.describe HaveInstanceMethodMatcher do
+ it "inherits from MethodMatcher" do
+ expect(HaveInstanceMethodMatcher.new(:m)).to be_kind_of(MethodMatcher)
+ end
+
+ it "matches when mod has the instance method" do
+ matcher = HaveInstanceMethodMatcher.new :instance_method
+ expect(matcher.matches?(HIMMSpecs)).to be_truthy
+ expect(matcher.matches?(HIMMSpecs::Subclass)).to be_truthy
+ end
+
+ it "does not match when mod does not have the instance method" do
+ matcher = HaveInstanceMethodMatcher.new :another_method
+ expect(matcher.matches?(HIMMSpecs)).to be_falsey
+ end
+
+ it "does not match if the method is in a superclass and include_super is false" do
+ matcher = HaveInstanceMethodMatcher.new :instance_method, false
+ expect(matcher.matches?(HIMMSpecs::Subclass)).to be_falsey
+ end
+
+ it "provides a failure message for #should" do
+ matcher = HaveInstanceMethodMatcher.new :some_method
+ matcher.matches?(HIMMSpecs)
+ expect(matcher.failure_message).to eq([
+ "Expected HIMMSpecs to have instance method 'some_method'",
+ "but it does not"
+ ])
+ end
+
+ it "provides a failure messoge for #should_not" do
+ matcher = HaveInstanceMethodMatcher.new :some_method
+ matcher.matches?(HIMMSpecs)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected HIMMSpecs NOT to have instance method 'some_method'",
+ "but it does"
+ ])
+ end
+end
diff --git a/spec/mspec/spec/matchers/have_instance_variable_spec.rb b/spec/mspec/spec/matchers/have_instance_variable_spec.rb
new file mode 100644
index 0000000000..12e2470f14
--- /dev/null
+++ b/spec/mspec/spec/matchers/have_instance_variable_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe HaveInstanceVariableMatcher do
+ before :each do
+ @object = Object.new
+ def @object.instance_variables
+ [:@foo]
+ end
+ end
+
+ it "matches when object has the instance variable, given as string" do
+ matcher = HaveInstanceVariableMatcher.new('@foo')
+ expect(matcher.matches?(@object)).to be_truthy
+ end
+
+ it "matches when object has the instance variable, given as symbol" do
+ matcher = HaveInstanceVariableMatcher.new(:@foo)
+ expect(matcher.matches?(@object)).to be_truthy
+ end
+
+ it "does not match when object hasn't got the instance variable, given as string" do
+ matcher = HaveInstanceVariableMatcher.new('@bar')
+ expect(matcher.matches?(@object)).to be_falsey
+ end
+
+ it "does not match when object hasn't got the instance variable, given as symbol" do
+ matcher = HaveInstanceVariableMatcher.new(:@bar)
+ expect(matcher.matches?(@object)).to be_falsey
+ end
+
+ it "provides a failure message for #should" do
+ matcher = HaveInstanceVariableMatcher.new(:@bar)
+ matcher.matches?(@object)
+ expect(matcher.failure_message).to eq([
+ "Expected #{@object.inspect} to have instance variable '@bar'",
+ "but it does not"
+ ])
+ end
+
+ it "provides a failure messoge for #should_not" do
+ matcher = HaveInstanceVariableMatcher.new(:@bar)
+ matcher.matches?(@object)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected #{@object.inspect} NOT to have instance variable '@bar'",
+ "but it does"
+ ])
+ end
+end
diff --git a/spec/mspec/spec/matchers/have_method_spec.rb b/spec/mspec/spec/matchers/have_method_spec.rb
new file mode 100644
index 0000000000..4fc0bf5e45
--- /dev/null
+++ b/spec/mspec/spec/matchers/have_method_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+class HMMSpecs
+ def instance_method
+ end
+
+ class Subclass < HMMSpecs
+ def instance_sub_method
+ end
+ end
+end
+
+RSpec.describe HaveMethodMatcher do
+ it "inherits from MethodMatcher" do
+ expect(HaveMethodMatcher.new(:m)).to be_kind_of(MethodMatcher)
+ end
+
+ it "matches when mod has the method" do
+ matcher = HaveMethodMatcher.new :instance_method
+ expect(matcher.matches?(HMMSpecs)).to be_truthy
+ expect(matcher.matches?(HMMSpecs.new)).to be_truthy
+ expect(matcher.matches?(HMMSpecs::Subclass)).to be_truthy
+ expect(matcher.matches?(HMMSpecs::Subclass.new)).to be_truthy
+ end
+
+ it "does not match when mod does not have the method" do
+ matcher = HaveMethodMatcher.new :another_method
+ expect(matcher.matches?(HMMSpecs)).to be_falsey
+ end
+
+ it "does not match if the method is in a superclass and include_super is false" do
+ matcher = HaveMethodMatcher.new :instance_method, false
+ expect(matcher.matches?(HMMSpecs::Subclass)).to be_falsey
+ end
+
+ it "provides a failure message for #should" do
+ matcher = HaveMethodMatcher.new :some_method
+ matcher.matches?(HMMSpecs)
+ expect(matcher.failure_message).to eq([
+ "Expected HMMSpecs to have method 'some_method'",
+ "but it does not"
+ ])
+ end
+
+ it "provides a failure messoge for #should_not" do
+ matcher = HaveMethodMatcher.new :some_method
+ matcher.matches?(HMMSpecs)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected HMMSpecs NOT to have method 'some_method'",
+ "but it does"
+ ])
+ end
+end
diff --git a/spec/mspec/spec/matchers/have_private_instance_method_spec.rb b/spec/mspec/spec/matchers/have_private_instance_method_spec.rb
new file mode 100644
index 0000000000..0e65c264d9
--- /dev/null
+++ b/spec/mspec/spec/matchers/have_private_instance_method_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+class HPIMMSpecs
+ private
+
+ def private_method
+ end
+
+ class Subclass < HPIMMSpecs
+ private
+
+ def private_sub_method
+ end
+ end
+end
+
+RSpec.describe HavePrivateInstanceMethodMatcher do
+ it "inherits from MethodMatcher" do
+ expect(HavePrivateInstanceMethodMatcher.new(:m)).to be_kind_of(MethodMatcher)
+ end
+
+ it "matches when mod has the private instance method" do
+ matcher = HavePrivateInstanceMethodMatcher.new :private_method
+ expect(matcher.matches?(HPIMMSpecs)).to be_truthy
+ expect(matcher.matches?(HPIMMSpecs::Subclass)).to be_truthy
+ end
+
+ it "does not match when mod does not have the private instance method" do
+ matcher = HavePrivateInstanceMethodMatcher.new :another_method
+ expect(matcher.matches?(HPIMMSpecs)).to be_falsey
+ end
+
+ it "does not match if the method is in a superclass and include_super is false" do
+ matcher = HavePrivateInstanceMethodMatcher.new :private_method, false
+ expect(matcher.matches?(HPIMMSpecs::Subclass)).to be_falsey
+ end
+
+ it "provides a failure message for #should" do
+ matcher = HavePrivateInstanceMethodMatcher.new :some_method
+ matcher.matches?(HPIMMSpecs)
+ expect(matcher.failure_message).to eq([
+ "Expected HPIMMSpecs to have private instance method 'some_method'",
+ "but it does not"
+ ])
+ end
+
+ it "provides a failure message for #should_not" do
+ matcher = HavePrivateInstanceMethodMatcher.new :some_method
+ matcher.matches?(HPIMMSpecs)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected HPIMMSpecs NOT to have private instance method 'some_method'",
+ "but it does"
+ ])
+ end
+end
diff --git a/spec/mspec/spec/matchers/have_private_method_spec.rb b/spec/mspec/spec/matchers/have_private_method_spec.rb
new file mode 100644
index 0000000000..f433288057
--- /dev/null
+++ b/spec/mspec/spec/matchers/have_private_method_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+class HPMMSpecs
+ def self.private_method
+ end
+
+ private_class_method :private_method
+end
+
+RSpec.describe HavePrivateMethodMatcher do
+ it "inherits from MethodMatcher" do
+ expect(HavePrivateMethodMatcher.new(:m)).to be_kind_of(MethodMatcher)
+ end
+
+ it "matches when mod has the private method" do
+ matcher = HavePrivateMethodMatcher.new :private_method
+ expect(matcher.matches?(HPMMSpecs)).to be_truthy
+ end
+
+ it "does not match when mod does not have the private method" do
+ matcher = HavePrivateMethodMatcher.new :another_method
+ expect(matcher.matches?(HPMMSpecs)).to be_falsey
+ end
+
+ it "provides a failure message for #should" do
+ matcher = HavePrivateMethodMatcher.new :some_method
+ matcher.matches?(HPMMSpecs)
+ expect(matcher.failure_message).to eq([
+ "Expected HPMMSpecs to have private method 'some_method'",
+ "but it does not"
+ ])
+ end
+
+ it "provides a failure message for #should_not" do
+ matcher = HavePrivateMethodMatcher.new :private_method
+ matcher.matches?(HPMMSpecs)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected HPMMSpecs NOT to have private method 'private_method'",
+ "but it does"
+ ])
+ end
+end
diff --git a/spec/mspec/spec/matchers/have_protected_instance_method_spec.rb b/spec/mspec/spec/matchers/have_protected_instance_method_spec.rb
new file mode 100644
index 0000000000..45b39004a3
--- /dev/null
+++ b/spec/mspec/spec/matchers/have_protected_instance_method_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+class HPIMMSpecs
+ protected
+
+ def protected_method
+ end
+
+ class Subclass < HPIMMSpecs
+ protected
+
+ def protected_sub_method
+ end
+ end
+end
+
+RSpec.describe HaveProtectedInstanceMethodMatcher do
+ it "inherits from MethodMatcher" do
+ expect(HaveProtectedInstanceMethodMatcher.new(:m)).to be_kind_of(MethodMatcher)
+ end
+
+ it "matches when mod has the protected instance method" do
+ matcher = HaveProtectedInstanceMethodMatcher.new :protected_method
+ expect(matcher.matches?(HPIMMSpecs)).to be_truthy
+ expect(matcher.matches?(HPIMMSpecs::Subclass)).to be_truthy
+ end
+
+ it "does not match when mod does not have the protected instance method" do
+ matcher = HaveProtectedInstanceMethodMatcher.new :another_method
+ expect(matcher.matches?(HPIMMSpecs)).to be_falsey
+ end
+
+ it "does not match if the method is in a superclass and include_super is false" do
+ matcher = HaveProtectedInstanceMethodMatcher.new :protected_method, false
+ expect(matcher.matches?(HPIMMSpecs::Subclass)).to be_falsey
+ end
+
+ it "provides a failure message for #should" do
+ matcher = HaveProtectedInstanceMethodMatcher.new :some_method
+ matcher.matches?(HPIMMSpecs)
+ expect(matcher.failure_message).to eq([
+ "Expected HPIMMSpecs to have protected instance method 'some_method'",
+ "but it does not"
+ ])
+ end
+
+ it "provides a failure messoge for #should_not" do
+ matcher = HaveProtectedInstanceMethodMatcher.new :some_method
+ matcher.matches?(HPIMMSpecs)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected HPIMMSpecs NOT to have protected instance method 'some_method'",
+ "but it does"
+ ])
+ end
+end
diff --git a/spec/mspec/spec/matchers/have_public_instance_method_spec.rb b/spec/mspec/spec/matchers/have_public_instance_method_spec.rb
new file mode 100644
index 0000000000..771d5b7911
--- /dev/null
+++ b/spec/mspec/spec/matchers/have_public_instance_method_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+class HPIMMSpecs
+ def public_method
+ end
+
+ class Subclass < HPIMMSpecs
+ def public_sub_method
+ end
+ end
+end
+
+RSpec.describe HavePublicInstanceMethodMatcher do
+ it "inherits from MethodMatcher" do
+ expect(HavePublicInstanceMethodMatcher.new(:m)).to be_kind_of(MethodMatcher)
+ end
+
+ it "matches when mod has the public instance method" do
+ matcher = HavePublicInstanceMethodMatcher.new :public_method
+ expect(matcher.matches?(HPIMMSpecs)).to be_truthy
+ expect(matcher.matches?(HPIMMSpecs::Subclass)).to be_truthy
+ end
+
+ it "does not match when mod does not have the public instance method" do
+ matcher = HavePublicInstanceMethodMatcher.new :another_method
+ expect(matcher.matches?(HPIMMSpecs)).to be_falsey
+ end
+
+ it "does not match if the method is in a superclass and include_super is false" do
+ matcher = HavePublicInstanceMethodMatcher.new :public_method, false
+ expect(matcher.matches?(HPIMMSpecs::Subclass)).to be_falsey
+ end
+
+ it "provides a failure message for #should" do
+ matcher = HavePublicInstanceMethodMatcher.new :some_method
+ matcher.matches?(HPIMMSpecs)
+ expect(matcher.failure_message).to eq([
+ "Expected HPIMMSpecs to have public instance method 'some_method'",
+ "but it does not"
+ ])
+ end
+
+ it "provides a failure messoge for #should_not" do
+ matcher = HavePublicInstanceMethodMatcher.new :some_method
+ matcher.matches?(HPIMMSpecs)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected HPIMMSpecs NOT to have public instance method 'some_method'",
+ "but it does"
+ ])
+ end
+end
diff --git a/spec/mspec/spec/matchers/have_singleton_method_spec.rb b/spec/mspec/spec/matchers/have_singleton_method_spec.rb
new file mode 100644
index 0000000000..61ef00d49c
--- /dev/null
+++ b/spec/mspec/spec/matchers/have_singleton_method_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+class HSMMSpecs
+ def self.singleton_method
+ end
+end
+
+RSpec.describe HaveSingletonMethodMatcher do
+ it "inherits from MethodMatcher" do
+ expect(HaveSingletonMethodMatcher.new(:m)).to be_kind_of(MethodMatcher)
+ end
+
+ it "matches when the class has a singleton method" do
+ matcher = HaveSingletonMethodMatcher.new :singleton_method
+ expect(matcher.matches?(HSMMSpecs)).to be_truthy
+ end
+
+ it "matches when the object has a singleton method" do
+ obj = double("HSMMSpecs")
+ def obj.singleton_method; end
+
+ matcher = HaveSingletonMethodMatcher.new :singleton_method
+ expect(matcher.matches?(obj)).to be_truthy
+ end
+
+ it "provides a failure message for #should" do
+ matcher = HaveSingletonMethodMatcher.new :some_method
+ matcher.matches?(HSMMSpecs)
+ expect(matcher.failure_message).to eq([
+ "Expected HSMMSpecs to have singleton method 'some_method'",
+ "but it does not"
+ ])
+ end
+
+ it "provides a failure message for #should_not" do
+ matcher = HaveSingletonMethodMatcher.new :singleton_method
+ matcher.matches?(HSMMSpecs)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected HSMMSpecs NOT to have singleton method 'singleton_method'",
+ "but it does"
+ ])
+ end
+end
diff --git a/spec/mspec/spec/matchers/include_any_of_spec.rb b/spec/mspec/spec/matchers/include_any_of_spec.rb
new file mode 100644
index 0000000000..1473bb6d0b
--- /dev/null
+++ b/spec/mspec/spec/matchers/include_any_of_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe IncludeAnyOfMatcher do
+ it "matches when actual includes expected" do
+ expect(IncludeAnyOfMatcher.new(2).matches?([1,2,3])).to eq(true)
+ expect(IncludeAnyOfMatcher.new("b").matches?("abc")).to eq(true)
+ end
+
+ it "does not match when actual does not include expected" do
+ expect(IncludeAnyOfMatcher.new(4).matches?([1,2,3])).to eq(false)
+ expect(IncludeAnyOfMatcher.new("d").matches?("abc")).to eq(false)
+ end
+
+ it "matches when actual includes all expected" do
+ expect(IncludeAnyOfMatcher.new(3, 2, 1).matches?([1,2,3])).to eq(true)
+ expect(IncludeAnyOfMatcher.new("a", "b", "c").matches?("abc")).to eq(true)
+ end
+
+ it "matches when actual includes any expected" do
+ expect(IncludeAnyOfMatcher.new(3, 4, 5).matches?([1,2,3])).to eq(true)
+ expect(IncludeAnyOfMatcher.new("c", "d", "e").matches?("abc")).to eq(true)
+ end
+
+ it "does not match when actual does not include any expected" do
+ expect(IncludeAnyOfMatcher.new(4, 5).matches?([1,2,3])).to eq(false)
+ expect(IncludeAnyOfMatcher.new("de").matches?("abc")).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = IncludeAnyOfMatcher.new(5, 6)
+ matcher.matches?([1,2,3])
+ expect(matcher.failure_message).to eq(["Expected [1, 2, 3]", "to include any of [5, 6]"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = IncludeAnyOfMatcher.new(1, 2, 3)
+ matcher.matches?([1,2])
+ expect(matcher.negative_failure_message).to eq(["Expected [1, 2]", "not to include any of [1, 2, 3]"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/include_spec.rb b/spec/mspec/spec/matchers/include_spec.rb
new file mode 100644
index 0000000000..6bf1bef085
--- /dev/null
+++ b/spec/mspec/spec/matchers/include_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe IncludeMatcher do
+ it "matches when actual includes expected" do
+ expect(IncludeMatcher.new(2).matches?([1,2,3])).to eq(true)
+ expect(IncludeMatcher.new("b").matches?("abc")).to eq(true)
+ end
+
+ it "does not match when actual does not include expected" do
+ expect(IncludeMatcher.new(4).matches?([1,2,3])).to eq(false)
+ expect(IncludeMatcher.new("d").matches?("abc")).to eq(false)
+ end
+
+ it "matches when actual includes all expected" do
+ expect(IncludeMatcher.new(3, 2, 1).matches?([1,2,3])).to eq(true)
+ expect(IncludeMatcher.new("a", "b", "c").matches?("abc")).to eq(true)
+ end
+
+ it "does not match when actual does not include all expected" do
+ expect(IncludeMatcher.new(3, 2, 4).matches?([1,2,3])).to eq(false)
+ expect(IncludeMatcher.new("a", "b", "c", "d").matches?("abc")).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = IncludeMatcher.new(5, 2)
+ matcher.matches?([1,2,3])
+ expect(matcher.failure_message).to eq(["Expected [1, 2, 3]", "to include 5"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = IncludeMatcher.new(1, 2, 3)
+ matcher.matches?([1,2,3])
+ expect(matcher.negative_failure_message).to eq(["Expected [1, 2, 3]", "not to include 3"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/infinity_spec.rb b/spec/mspec/spec/matchers/infinity_spec.rb
new file mode 100644
index 0000000000..78c4194526
--- /dev/null
+++ b/spec/mspec/spec/matchers/infinity_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/guards'
+require 'mspec/helpers'
+require 'mspec/matchers'
+
+RSpec.describe InfinityMatcher do
+ it "matches when actual is infinite and has the correct sign" do
+ expect(InfinityMatcher.new(1).matches?(infinity_value)).to eq(true)
+ expect(InfinityMatcher.new(-1).matches?(-infinity_value)).to eq(true)
+ end
+
+ it "does not match when actual is not infinite" do
+ expect(InfinityMatcher.new(1).matches?(1.0)).to eq(false)
+ expect(InfinityMatcher.new(-1).matches?(-1.0)).to eq(false)
+ end
+
+ it "does not match when actual is infinite but has the incorrect sign" do
+ expect(InfinityMatcher.new(1).matches?(-infinity_value)).to eq(false)
+ expect(InfinityMatcher.new(-1).matches?(infinity_value)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = InfinityMatcher.new(-1)
+ matcher.matches?(0)
+ expect(matcher.failure_message).to eq(["Expected 0", "to be -Infinity"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = InfinityMatcher.new(1)
+ matcher.matches?(infinity_value)
+ expect(matcher.negative_failure_message).to eq(["Expected Infinity", "not to be Infinity"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/match_yaml_spec.rb b/spec/mspec/spec/matchers/match_yaml_spec.rb
new file mode 100644
index 0000000000..85123bb87d
--- /dev/null
+++ b/spec/mspec/spec/matchers/match_yaml_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe MatchYAMLMatcher do
+ before :each do
+ @matcher = MatchYAMLMatcher.new("--- \nfoo: bar\n")
+ end
+
+ it "compares YAML documents and matches if they're equivalent" do
+ expect(@matcher.matches?("--- \nfoo: bar\n")).to eq(true)
+ end
+
+ it "compares YAML documents and does not match if they're not equivalent" do
+ expect(@matcher.matches?("--- \nbar: foo\n")).to eq(false)
+ expect(@matcher.matches?("--- \nfoo: \nbar\n")).to eq(false)
+ end
+
+ it "also receives objects that respond_to to_yaml" do
+ matcher = MatchYAMLMatcher.new("some string")
+ expect(matcher.matches?("some string")).to eq(true)
+
+ matcher = MatchYAMLMatcher.new(['a', 'b'])
+ expect(matcher.matches?("--- \n- a\n- b\n")).to eq(true)
+
+ matcher = MatchYAMLMatcher.new("foo" => "bar")
+ expect(matcher.matches?("--- \nfoo: bar\n")).to eq(true)
+ end
+
+ it "matches documents with trailing whitespace" do
+ expect(@matcher.matches?("--- \nfoo: bar \n")).to eq(true)
+ expect(@matcher.matches?("--- \nfoo: bar \n")).to eq(true)
+ end
+
+ it "fails with a descriptive error message" do
+ expect(@matcher.matches?("foo")).to eq(false)
+ expect(@matcher.failure_message).to eq(["Expected \"foo\"", " to match \"--- \\nfoo: bar\\n\""])
+ end
+end
diff --git a/spec/mspec/spec/matchers/output_spec.rb b/spec/mspec/spec/matchers/output_spec.rb
new file mode 100644
index 0000000000..3baad9a4b2
--- /dev/null
+++ b/spec/mspec/spec/matchers/output_spec.rb
@@ -0,0 +1,84 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe OutputMatcher do
+ it "matches when executing the proc results in the expected output to $stdout" do
+ proc = Proc.new { puts "bang!" }
+ expect(OutputMatcher.new("bang!\n", nil).matches?(proc)).to eq(true)
+ expect(OutputMatcher.new("pop", nil).matches?(proc)).to eq(false)
+ expect(OutputMatcher.new(/bang/, nil).matches?(proc)).to eq(true)
+ expect(OutputMatcher.new(/po/, nil).matches?(proc)).to eq(false)
+ end
+
+ it "matches when executing the proc results in the expected output to $stderr" do
+ proc = Proc.new { $stderr.write "boom!" }
+ expect(OutputMatcher.new(nil, "boom!").matches?(proc)).to eq(true)
+ expect(OutputMatcher.new(nil, "fizzle").matches?(proc)).to eq(false)
+ expect(OutputMatcher.new(nil, /boom/).matches?(proc)).to eq(true)
+ expect(OutputMatcher.new(nil, /fizzl/).matches?(proc)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ proc = Proc.new { print "unexpected"; $stderr.print "unerror" }
+ matcher = OutputMatcher.new("expected", "error")
+ matcher.matches?(proc)
+ expect(matcher.failure_message).to eq(
+ ["Expected:\n $stdout: \"expected\"\n $stderr: \"error\"\n",
+ " got:\n $stdout: \"unexpected\"\n $stderr: \"unerror\"\n"]
+ )
+ matcher = OutputMatcher.new("expected", nil)
+ matcher.matches?(proc)
+ expect(matcher.failure_message).to eq(
+ ["Expected:\n $stdout: \"expected\"\n",
+ " got:\n $stdout: \"unexpected\"\n"]
+ )
+ matcher = OutputMatcher.new(nil, "error")
+ matcher.matches?(proc)
+ expect(matcher.failure_message).to eq(
+ ["Expected:\n $stderr: \"error\"\n",
+ " got:\n $stderr: \"unerror\"\n"]
+ )
+ matcher = OutputMatcher.new(/base/, nil)
+ matcher.matches?(proc)
+ expect(matcher.failure_message).to eq(
+ ["Expected:\n $stdout: /base/\n",
+ " got:\n $stdout: \"unexpected\"\n"]
+ )
+ matcher = OutputMatcher.new(nil, /octave/)
+ matcher.matches?(proc)
+ expect(matcher.failure_message).to eq(
+ ["Expected:\n $stderr: /octave/\n",
+ " got:\n $stderr: \"unerror\"\n"]
+ )
+ end
+
+ it "provides a useful negative failure message" do
+ proc = Proc.new { puts "expected"; $stderr.puts "error" }
+ matcher = OutputMatcher.new("expected", "error")
+ matcher.matches?(proc)
+ expect(matcher.negative_failure_message).to eq(
+ ["Expected output not to be:\n", " $stdout: \"expected\"\n $stderr: \"error\"\n"]
+ )
+ matcher = OutputMatcher.new("expected", nil)
+ matcher.matches?(proc)
+ expect(matcher.negative_failure_message).to eq(
+ ["Expected output not to be:\n", " $stdout: \"expected\"\n"]
+ )
+ matcher = OutputMatcher.new(nil, "error")
+ matcher.matches?(proc)
+ expect(matcher.negative_failure_message).to eq(
+ ["Expected output not to be:\n", " $stderr: \"error\"\n"]
+ )
+ matcher = OutputMatcher.new(/expect/, nil)
+ matcher.matches?(proc)
+ expect(matcher.negative_failure_message).to eq(
+ ["Expected output not to be:\n", " $stdout: \"expected\"\n"]
+ )
+ matcher = OutputMatcher.new(nil, /err/)
+ matcher.matches?(proc)
+ expect(matcher.negative_failure_message).to eq(
+ ["Expected output not to be:\n", " $stderr: \"error\"\n"]
+ )
+ end
+end
diff --git a/spec/mspec/spec/matchers/output_to_fd_spec.rb b/spec/mspec/spec/matchers/output_to_fd_spec.rb
new file mode 100644
index 0000000000..a39cab3206
--- /dev/null
+++ b/spec/mspec/spec/matchers/output_to_fd_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe OutputToFDMatcher do
+ # Figure out how in the hell to achieve this
+ it "matches when running the block produces the expected output to the given FD" do
+ expect(OutputToFDMatcher.new("Hi\n", STDERR).matches?(lambda { $stderr.print "Hi\n" })).to eq(true)
+ end
+
+ it "does not match if running the block does not produce the expected output to the FD" do
+ expect(OutputToFDMatcher.new("Hi\n", STDERR).matches?(lambda { $stderr.puts("Hello\n") })).to eq(false)
+ end
+
+ it "propagate the exception if one is thrown while matching" do
+ exc = RuntimeError.new("propagates")
+ expect {
+ expect(OutputToFDMatcher.new("Hi\n", STDERR).matches?(lambda {
+ raise exc
+ })).to eq(false)
+ }.to raise_error(exc)
+ end
+
+ it "defaults to matching against STDOUT" do
+ object = Object.new
+ object.extend MSpecMatchers
+ expect(object.send(:output_to_fd, "Hi\n").matches?(lambda { $stdout.print "Hi\n" })).to eq(true)
+ end
+
+ it "accepts any IO instance" do
+ io = IO.new STDOUT.fileno
+ expect(OutputToFDMatcher.new("Hi\n", io).matches?(lambda { io.print "Hi\n" })).to eq(true)
+ end
+
+ it "allows matching with a Regexp" do
+ s = "Hi there\n"
+ expect(OutputToFDMatcher.new(/Hi/, STDERR).matches?(lambda { $stderr.print s })).to eq(true)
+ expect(OutputToFDMatcher.new(/Hi?/, STDERR).matches?(lambda { $stderr.print s })).to eq(true)
+ expect(OutputToFDMatcher.new(/[hH]i?/, STDERR).matches?(lambda { $stderr.print s })).to eq(true)
+ expect(OutputToFDMatcher.new(/.*/, STDERR).matches?(lambda { $stderr.print s })).to eq(true)
+ expect(OutputToFDMatcher.new(/H.*?here/, STDERR).matches?(lambda { $stderr.print s })).to eq(true)
+ expect(OutputToFDMatcher.new(/Ahoy/, STDERR).matches?(lambda { $stderr.print s })).to eq(false)
+ end
+end
diff --git a/spec/mspec/spec/matchers/raise_error_spec.rb b/spec/mspec/spec/matchers/raise_error_spec.rb
new file mode 100644
index 0000000000..8613eee118
--- /dev/null
+++ b/spec/mspec/spec/matchers/raise_error_spec.rb
@@ -0,0 +1,183 @@
+require 'spec_helper'
+
+class ExpectedException < Exception; end
+class UnexpectedException < Exception; end
+
+RSpec.describe RaiseErrorMatcher do
+ before :each do
+ state = double("run state").as_null_object
+ allow(MSpec).to receive(:current).and_return(state)
+ end
+
+ it "matches when the proc raises the expected exception" do
+ proc = Proc.new { raise ExpectedException }
+ matcher = RaiseErrorMatcher.new(ExpectedException, nil)
+ expect(matcher.matches?(proc)).to eq(true)
+ end
+
+ it "executes its optional {/} block if matched" do
+ ensure_mspec_method(-> {}.method(:should))
+
+ run = false
+ -> { raise ExpectedException }.should PublicMSpecMatchers.raise_error { |error|
+ expect(error.class).to eq(ExpectedException)
+ run = true
+ }
+ expect(run).to eq(true)
+ end
+
+ it "executes its optional do/end block if matched" do
+ ensure_mspec_method(-> {}.method(:should))
+
+ run = false
+ -> { raise ExpectedException }.should PublicMSpecMatchers.raise_error do |error|
+ expect(error.class).to eq(ExpectedException)
+ run = true
+ end
+ expect(run).to eq(true)
+ end
+
+ it "matches when the proc raises the expected exception with the expected message" do
+ proc = Proc.new { raise ExpectedException, "message" }
+ matcher = RaiseErrorMatcher.new(ExpectedException, "message")
+ expect(matcher.matches?(proc)).to eq(true)
+ end
+
+ it "matches when the proc raises the expected exception with a matching message" do
+ proc = Proc.new { raise ExpectedException, "some message" }
+ matcher = RaiseErrorMatcher.new(ExpectedException, /some/)
+ expect(matcher.matches?(proc)).to eq(true)
+ end
+
+ it "does not match when the proc does not raise the expected exception" do
+ exc = UnexpectedException.new
+ matcher = RaiseErrorMatcher.new(ExpectedException, nil)
+
+ expect(matcher.matching_exception?(exc)).to eq(false)
+ expect {
+ matcher.matches?(Proc.new { raise exc })
+ }.to raise_error(UnexpectedException)
+ end
+
+ it "does not match when the proc raises the expected exception with an unexpected message" do
+ exc = ExpectedException.new("unexpected")
+ matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
+
+ expect(matcher.matching_exception?(exc)).to eq(false)
+ expect {
+ matcher.matches?(Proc.new { raise exc })
+ }.to raise_error(ExpectedException)
+ end
+
+ it "does not match when the proc does not raise an exception" do
+ proc = Proc.new {}
+ matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
+ expect(matcher.matches?(proc)).to eq(false)
+ end
+
+ it "provides a useful failure message when the exception class differs" do
+ exc = UnexpectedException.new("message")
+ matcher = RaiseErrorMatcher.new(ExpectedException, "message")
+
+ expect(matcher.matching_exception?(exc)).to eq(false)
+ begin
+ matcher.matches?(Proc.new { raise exc })
+ rescue UnexpectedException => e
+ expect(matcher.failure_message).to eq(
+ ["Expected ExpectedException (message)", "but got: UnexpectedException (message)"]
+ )
+ expect(ExceptionState.new(nil, nil, e).message).to eq(
+ "Expected ExpectedException (message)\nbut got: UnexpectedException (message)"
+ )
+ else
+ raise "no exception"
+ end
+ end
+
+ it "provides a useful failure message when the proc raises the expected exception with an unexpected message" do
+ exc = ExpectedException.new("unexpected")
+ matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
+
+ expect(matcher.matching_exception?(exc)).to eq(false)
+ begin
+ matcher.matches?(Proc.new { raise exc })
+ rescue ExpectedException => e
+ expect(matcher.failure_message).to eq(
+ ["Expected ExpectedException (expected)", "but got: ExpectedException (unexpected)"]
+ )
+ expect(ExceptionState.new(nil, nil, e).message).to eq(
+ "Expected ExpectedException (expected)\nbut got: ExpectedException (unexpected)"
+ )
+ else
+ raise "no exception"
+ end
+ end
+
+ it "provides a useful failure message when both the exception class and message differ" do
+ exc = UnexpectedException.new("unexpected")
+ matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
+
+ expect(matcher.matching_exception?(exc)).to eq(false)
+ begin
+ matcher.matches?(Proc.new { raise exc })
+ rescue UnexpectedException => e
+ expect(matcher.failure_message).to eq(
+ ["Expected ExpectedException (expected)", "but got: UnexpectedException (unexpected)"]
+ )
+ expect(ExceptionState.new(nil, nil, e).message).to eq(
+ "Expected ExpectedException (expected)\nbut got: UnexpectedException (unexpected)"
+ )
+ else
+ raise "no exception"
+ end
+ end
+
+ it "provides a useful failure message when no exception is raised" do
+ proc = Proc.new { 120 }
+ matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
+ matcher.matches?(proc)
+ expect(matcher.failure_message).to eq(
+ ["Expected ExpectedException (expected)", "but no exception was raised (120 was returned)"]
+ )
+ end
+
+ it "provides a useful failure message when no exception is raised and nil is returned" do
+ proc = Proc.new { nil }
+ matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
+ matcher.matches?(proc)
+ expect(matcher.failure_message).to eq(
+ ["Expected ExpectedException (expected)", "but no exception was raised (nil was returned)"]
+ )
+ end
+
+ it "provides a useful failure message when no exception is raised and the result raises in #pretty_inspect" do
+ result = Object.new
+ def result.pretty_inspect
+ raise ArgumentError, "bad"
+ end
+ proc = Proc.new { result }
+ matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
+ matcher.matches?(proc)
+ expect(matcher.failure_message).to eq(
+ ["Expected ExpectedException (expected)", "but no exception was raised (#<Object>(#pretty_inspect raised #<ArgumentError: bad>) was returned)"]
+ )
+ end
+
+ it "provides a useful negative failure message" do
+ proc = Proc.new { raise ExpectedException, "expected" }
+ matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
+ matcher.matches?(proc)
+ expect(matcher.negative_failure_message).to eq(
+ ["Expected to not get ExpectedException (expected)", ""]
+ )
+ end
+
+ it "provides a useful negative failure message for strict subclasses of the matched exception class" do
+ proc = Proc.new { raise UnexpectedException, "unexpected" }
+ matcher = RaiseErrorMatcher.new(Exception, nil)
+ matcher.matches?(proc)
+ expect(matcher.negative_failure_message).to eq(
+ ["Expected to not get Exception", "but got: UnexpectedException (unexpected)"]
+ )
+ end
+end
diff --git a/spec/mspec/spec/matchers/respond_to_spec.rb b/spec/mspec/spec/matchers/respond_to_spec.rb
new file mode 100644
index 0000000000..6f1cd8d148
--- /dev/null
+++ b/spec/mspec/spec/matchers/respond_to_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe RespondToMatcher do
+ it "matches when actual does respond_to? expected" do
+ expect(RespondToMatcher.new(:to_s).matches?(Object.new)).to eq(true)
+ expect(RespondToMatcher.new(:inject).matches?([])).to eq(true)
+ expect(RespondToMatcher.new(:[]).matches?(1)).to eq(true)
+ expect(RespondToMatcher.new(:[]=).matches?("string")).to eq(true)
+ end
+
+ it "does not match when actual does not respond_to? expected" do
+ expect(RespondToMatcher.new(:to_i).matches?(Object.new)).to eq(false)
+ expect(RespondToMatcher.new(:inject).matches?(1)).to eq(false)
+ expect(RespondToMatcher.new(:non_existent_method).matches?([])).to eq(false)
+ expect(RespondToMatcher.new(:[]=).matches?(1)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = RespondToMatcher.new(:non_existent_method)
+ matcher.matches?('string')
+ expect(matcher.failure_message).to eq([
+ "Expected \"string\" (String)", "to respond to non_existent_method"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = RespondToMatcher.new(:to_i)
+ matcher.matches?(4.0)
+ expect(matcher.negative_failure_message).to eq([
+ "Expected 4.0 (Float)", "not to respond to to_i"])
+ end
+end
diff --git a/spec/mspec/spec/matchers/signed_zero_spec.rb b/spec/mspec/spec/matchers/signed_zero_spec.rb
new file mode 100644
index 0000000000..6d1c1007bc
--- /dev/null
+++ b/spec/mspec/spec/matchers/signed_zero_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers'
+
+RSpec.describe SignedZeroMatcher do
+ it "matches when actual is zero and has the correct sign" do
+ expect(SignedZeroMatcher.new(1).matches?(0.0)).to eq(true)
+ expect(SignedZeroMatcher.new(-1).matches?(-0.0)).to eq(true)
+ end
+
+ it "does not match when actual is non-zero" do
+ expect(SignedZeroMatcher.new(1).matches?(1.0)).to eq(false)
+ expect(SignedZeroMatcher.new(-1).matches?(-1.0)).to eq(false)
+ end
+
+ it "does not match when actual is zero but has the incorrect sign" do
+ expect(SignedZeroMatcher.new(1).matches?(-0.0)).to eq(false)
+ expect(SignedZeroMatcher.new(-1).matches?(0.0)).to eq(false)
+ end
+
+ it "provides a useful failure message" do
+ matcher = SignedZeroMatcher.new(-1)
+ matcher.matches?(0.0)
+ expect(matcher.failure_message).to eq(["Expected 0.0", "to be -0.0"])
+ end
+
+ it "provides a useful negative failure message" do
+ matcher = SignedZeroMatcher.new(-1)
+ matcher.matches?(-0.0)
+ expect(matcher.negative_failure_message).to eq(["Expected -0.0", "not to be -0.0"])
+ end
+end
diff --git a/spec/mspec/spec/mocks/mock_spec.rb b/spec/mspec/spec/mocks/mock_spec.rb
new file mode 100644
index 0000000000..73f9bdfa14
--- /dev/null
+++ b/spec/mspec/spec/mocks/mock_spec.rb
@@ -0,0 +1,530 @@
+# This is a bit awkward. Currently the way to verify that the
+# opposites are true (for example a failure when the specified
+# arguments are NOT provided) is to simply alter the particular
+# spec to a failure condition.
+require 'spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/mocks/mock'
+require 'mspec/mocks/proxy'
+
+RSpec.describe Mock, ".mocks" do
+ it "returns a Hash" do
+ expect(Mock.mocks).to be_kind_of(Hash)
+ end
+end
+
+RSpec.describe Mock, ".stubs" do
+ it "returns a Hash" do
+ expect(Mock.stubs).to be_kind_of(Hash)
+ end
+end
+
+RSpec.describe Mock, ".replaced_name" do
+ it "returns the name for a method that is being replaced by a mock method" do
+ m = double('a fake id')
+ expect(Mock.replaced_name(m, :method_call)).to eq(:"__mspec_#{m.object_id}_method_call__")
+ end
+end
+
+RSpec.describe Mock, ".replaced_key" do
+ it "returns a key used internally by Mock" do
+ m = double('a fake id')
+ expect(Mock.replaced_key(m, :method_call)).to eq([:"__mspec_#{m.object_id}_method_call__", :method_call])
+ end
+end
+
+RSpec.describe Mock, ".replaced?" do
+ before :each do
+ @mock = double('install_method')
+ allow(MSpec).to receive(:actions)
+ allow(MSpec).to receive(:current).and_return(double("spec state").as_null_object)
+ end
+
+ it "returns true if a method has been stubbed on an object" do
+ Mock.install_method @mock, :method_call
+ expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_truthy
+ end
+
+ it "returns true if a method has been mocked on an object" do
+ Mock.install_method @mock, :method_call, :stub
+ expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_truthy
+ end
+
+ it "returns false if a method has not been stubbed or mocked" do
+ expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_falsey
+ end
+end
+
+RSpec.describe Mock, ".name_or_inspect" do
+ before :each do
+ @mock = double("I have a #name")
+ end
+
+ it "returns the value of @name if set" do
+ @mock.instance_variable_set(:@name, "Myself")
+ expect(Mock.name_or_inspect(@mock)).to eq("Myself")
+ end
+end
+
+RSpec.describe Mock, ".install_method for mocks" do
+ before :each do
+ @mock = double('install_method')
+ allow(MSpec).to receive(:actions)
+ allow(MSpec).to receive(:current).and_return(double("spec state").as_null_object)
+ end
+
+ after :each do
+ Mock.reset
+ end
+
+ it "returns a MockProxy instance" do
+ expect(Mock.install_method(@mock, :method_call)).to be_an_instance_of(MockProxy)
+ end
+
+ it "does not override a previously mocked method with the same name" do
+ Mock.install_method(@mock, :method_call).with(:a, :b).and_return(1)
+ Mock.install_method(@mock, :method_call).with(:c).and_return(2)
+ @mock.method_call(:a, :b)
+ @mock.method_call(:c)
+ expect { @mock.method_call(:d) }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ # This illustrates RSpec's behavior. This spec fails in mock call count verification
+ # on RSpec (i.e. Mock 'foo' expected :foo with (any args) once, but received it 0 times)
+ # and we mimic the behavior of RSpec.
+ #
+ # describe "A mock receiving multiple calls to #should_receive" do
+ # it "returns the first value mocked" do
+ # m = mock 'multiple #should_receive'
+ # m.should_receive(:foo).and_return(true)
+ # m.foo.should == true
+ # m.should_receive(:foo).and_return(false)
+ # m.foo.should == true
+ # end
+ # end
+ #
+ it "does not override a previously mocked method having the same arguments" do
+ Mock.install_method(@mock, :method_call).with(:a).and_return(true)
+ expect(@mock.method_call(:a)).to eq(true)
+ Mock.install_method(@mock, :method_call).with(:a).and_return(false)
+ expect(@mock.method_call(:a)).to eq(true)
+ expect { Mock.verify_count }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ it "properly sends #respond_to? calls to the aliased respond_to? method when not matching mock expectations" do
+ Mock.install_method(@mock, :respond_to?).with(:to_str).and_return('mock to_str')
+ Mock.install_method(@mock, :respond_to?).with(:to_int).and_return('mock to_int')
+ expect(@mock.respond_to?(:to_str)).to eq('mock to_str')
+ expect(@mock.respond_to?(:to_int)).to eq('mock to_int')
+ expect(@mock.respond_to?(:to_s)).to eq(true)
+ expect(@mock.respond_to?(:not_really_a_real_method_seriously)).to eq(false)
+ end
+
+ it "adds to the expectation tally" do
+ state = double("run state").as_null_object
+ allow(state).to receive(:state).and_return(double("spec state"))
+ expect(MSpec).to receive(:current).and_return(state)
+ expect(MSpec).to receive(:actions).with(:expectation, state.state)
+ Mock.install_method(@mock, :method_call).and_return(1)
+ expect(@mock.method_call).to eq(1)
+ end
+
+ it "registers that an expectation has been encountered" do
+ state = double("run state").as_null_object
+ allow(state).to receive(:state).and_return(double("spec state"))
+ expect(MSpec).to receive(:expectation)
+ Mock.install_method(@mock, :method_call).and_return(1)
+ expect(@mock.method_call).to eq(1)
+ end
+end
+
+RSpec.describe Mock, ".install_method for stubs" do
+ before :each do
+ @mock = double('install_method')
+ allow(MSpec).to receive(:actions)
+ allow(MSpec).to receive(:current).and_return(double("spec state").as_null_object)
+ end
+
+ after :each do
+ Mock.cleanup
+ end
+
+ it "returns a MockProxy instance" do
+ expect(Mock.install_method(@mock, :method_call, :stub)).to be_an_instance_of(MockProxy)
+ end
+
+ # This illustrates RSpec's behavior. This spec passes on RSpec and we mimic it
+ #
+ # describe "A mock receiving multiple calls to #stub" do
+ # it "returns the last value stubbed" do
+ # m = mock 'multiple #stub'
+ # m.stub(:foo).and_return(true)
+ # m.foo.should == true
+ # m.stub(:foo).and_return(false)
+ # m.foo.should == false
+ # end
+ # end
+ it "inserts new stubs before old stubs" do
+ Mock.install_method(@mock, :method_call, :stub).with(:a).and_return(true)
+ expect(@mock.method_call(:a)).to eq(true)
+ Mock.install_method(@mock, :method_call, :stub).with(:a).and_return(false)
+ expect(@mock.method_call(:a)).to eq(false)
+ Mock.verify_count
+ end
+
+ it "does not add to the expectation tally" do
+ state = double("run state").as_null_object
+ allow(state).to receive(:state).and_return(double("spec state"))
+ expect(MSpec).not_to receive(:actions)
+ Mock.install_method(@mock, :method_call, :stub).and_return(1)
+ expect(@mock.method_call).to eq(1)
+ end
+end
+
+RSpec.describe Mock, ".install_method" do
+ before :each do
+ @mock = double('install_method')
+ allow(MSpec).to receive(:actions)
+ allow(MSpec).to receive(:current).and_return(double("spec state").as_null_object)
+ end
+
+ after :each do
+ Mock.cleanup
+ end
+
+ it "does not alias a mocked or stubbed method when installing a new mock or stub" do
+ expect(@mock).not_to respond_to(:method_call)
+
+ Mock.install_method @mock, :method_call
+ expect(@mock).to respond_to(:method_call)
+ expect(@mock).not_to respond_to(Mock.replaced_name(@mock, :method_call))
+
+ Mock.install_method @mock, :method_call, :stub
+ expect(@mock).to respond_to(:method_call)
+ expect(@mock).not_to respond_to(Mock.replaced_name(@mock, :method_call))
+ end
+end
+
+class MockAndRaiseError < Exception; end
+
+RSpec.describe Mock, ".verify_call" do
+ before :each do
+ allow(MSpec).to receive(:actions)
+ allow(MSpec).to receive(:current).and_return(double("spec state").as_null_object)
+
+ @mock = double('verify_call')
+ @proxy = Mock.install_method @mock, :method_call
+ end
+
+ after :each do
+ ScratchPad.clear
+ Mock.cleanup
+ end
+
+ it "does not raise an exception when the mock method receives the expected arguments" do
+ @proxy.with(1, 'two', :three)
+ Mock.verify_call @mock, :method_call, 1, 'two', :three
+ end
+
+ it "raises an SpecExpectationNotMetError when the mock method does not receive the expected arguments" do
+ @proxy.with(4, 2)
+ expect {
+ Mock.verify_call @mock, :method_call, 42
+ }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ it "raises an SpecExpectationNotMetError when the mock method is called with arguments but expects none" do
+ expect {
+ @proxy.with(:no_args)
+ Mock.verify_call @mock, :method_call, "hello"
+ }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ it "raises an SpecExpectationNotMetError when the mock method is called with no arguments but expects some" do
+ @proxy.with("hello", "beautiful", "world")
+ expect {
+ Mock.verify_call @mock, :method_call
+ }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ it "does not raise an exception when the mock method is called with arguments and is expecting :any_args" do
+ @proxy.with(:any_args)
+ Mock.verify_call @mock, :method_call, 1, 2, 3
+ end
+
+ it "yields a passed block when it is expected to" do
+ @proxy.and_yield()
+ Mock.verify_call @mock, :method_call do
+ ScratchPad.record true
+ end
+ expect(ScratchPad.recorded).to eq(true)
+ end
+
+ it "does not yield a passed block when it is not expected to" do
+ Mock.verify_call @mock, :method_call do
+ ScratchPad.record true
+ end
+ expect(ScratchPad.recorded).to eq(nil)
+ end
+
+ it "can yield subsequently" do
+ @proxy.and_yield(1).and_yield(2).and_yield(3)
+
+ ScratchPad.record []
+ Mock.verify_call @mock, :method_call do |arg|
+ ScratchPad << arg
+ end
+ expect(ScratchPad.recorded).to eq([1, 2, 3])
+ end
+
+ it "can yield and return an expected value" do
+ @proxy.and_yield(1).and_return(3)
+
+ expect(Mock.verify_call(@mock, :method_call) { |arg| ScratchPad.record arg }).to eq(3)
+ expect(ScratchPad.recorded).to eq(1)
+ end
+
+ it "raises an exception when it is expected to yield but no block is given" do
+ @proxy.and_yield(1, 2, 3)
+ expect {
+ Mock.verify_call(@mock, :method_call)
+ }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ it "raises an exception when it is expected to yield more arguments than the block can take" do
+ @proxy.and_yield(1, 2, 3)
+ expect {
+ Mock.verify_call(@mock, :method_call) {|a, b|}
+ }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ it "does not raise an exception when it is expected to yield to a block that can take any number of arguments" do
+ @proxy.and_yield(1, 2, 3)
+ expect {
+ Mock.verify_call(@mock, :method_call) {|*a|}
+ }.not_to raise_error
+ end
+
+ it "raises an exception when expected to" do
+ @proxy.and_raise(MockAndRaiseError)
+ expect {
+ Mock.verify_call @mock, :method_call
+ }.to raise_error(MockAndRaiseError)
+ end
+end
+
+RSpec.describe Mock, ".verify_call mixing mocks and stubs" do
+ before :each do
+ allow(MSpec).to receive(:actions)
+ allow(MSpec).to receive(:current).and_return(double("spec state").as_null_object)
+
+ @mock = double('verify_call')
+ end
+
+ after :each do
+ ScratchPad.clear
+ Mock.cleanup
+ end
+
+ it "checks the mock arguments when a mock is defined after a stub" do
+ Mock.install_method @mock, :method_call, :stub
+ Mock.install_method(@mock, :method_call, :mock).with("arg")
+
+ expect {
+ @mock.method_call
+ }.to raise_error(SpecExpectationNotMetError, /called with unexpected arguments \(\)/)
+
+ expect {
+ @mock.method_call("a", "b")
+ }.to raise_error(SpecExpectationNotMetError, /called with unexpected arguments \("a", "b"\)/)
+
+ expect {
+ @mock.method_call("foo")
+ }.to raise_error(SpecExpectationNotMetError, /called with unexpected arguments \("foo"\)/)
+
+ @mock.method_call("arg")
+ end
+
+ it "checks the mock arguments when a stub is defined after a mock" do
+ Mock.install_method(@mock, :method_call, :mock).with("arg")
+ Mock.install_method @mock, :method_call, :stub
+
+ expect {
+ @mock.method_call
+ }.to raise_error(SpecExpectationNotMetError, /called with unexpected arguments \(\)/)
+
+ expect {
+ @mock.method_call("a", "b")
+ }.to raise_error(SpecExpectationNotMetError, /called with unexpected arguments \("a", "b"\)/)
+
+ expect {
+ @mock.method_call("foo")
+ }.to raise_error(SpecExpectationNotMetError, /called with unexpected arguments \("foo"\)/)
+
+ @mock.method_call("arg")
+ end
+end
+
+RSpec.describe Mock, ".verify_count" do
+ before :each do
+ allow(MSpec).to receive(:actions)
+ allow(MSpec).to receive(:current).and_return(double("spec state").as_null_object)
+
+ @mock = double('verify_count')
+ @proxy = Mock.install_method @mock, :method_call
+ end
+
+ after :each do
+ Mock.cleanup
+ end
+
+ it "does not raise an exception when the mock receives at least the expected number of calls" do
+ @proxy.at_least(2)
+ @mock.method_call
+ @mock.method_call
+ Mock.verify_count
+ end
+
+ it "raises an SpecExpectationNotMetError when the mock receives less than at least the expected number of calls" do
+ @proxy.at_least(2)
+ @mock.method_call
+ expect { Mock.verify_count }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ it "does not raise an exception when the mock receives at most the expected number of calls" do
+ @proxy.at_most(2)
+ @mock.method_call
+ @mock.method_call
+ Mock.verify_count
+ end
+
+ it "raises an SpecExpectationNotMetError when the mock receives more than at most the expected number of calls" do
+ @proxy.at_most(2)
+ @mock.method_call
+ @mock.method_call
+ @mock.method_call
+ expect { Mock.verify_count }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ it "does not raise an exception when the mock receives exactly the expected number of calls" do
+ @proxy.exactly(2)
+ @mock.method_call
+ @mock.method_call
+ Mock.verify_count
+ end
+
+ it "raises an SpecExpectationNotMetError when the mock receives less than exactly the expected number of calls" do
+ @proxy.exactly(2)
+ @mock.method_call
+ expect { Mock.verify_count }.to raise_error(SpecExpectationNotMetError)
+ end
+
+ it "raises an SpecExpectationNotMetError when the mock receives more than exactly the expected number of calls" do
+ @proxy.exactly(2)
+ @mock.method_call
+ @mock.method_call
+ @mock.method_call
+ expect { Mock.verify_count }.to raise_error(SpecExpectationNotMetError)
+ end
+end
+
+RSpec.describe Mock, ".verify_count mixing mocks and stubs" do
+ before :each do
+ allow(MSpec).to receive(:actions)
+ allow(MSpec).to receive(:current).and_return(double("spec state").as_null_object)
+
+ @mock = double('verify_count')
+ end
+
+ after :each do
+ Mock.cleanup
+ end
+
+ it "does not raise an exception for a stubbed method that is never called" do
+ Mock.install_method @mock, :method_call, :stub
+ Mock.verify_count
+ end
+
+ it "verifies the calls to the mocked method when a mock is defined after a stub" do
+ Mock.install_method @mock, :method_call, :stub
+ Mock.install_method @mock, :method_call, :mock
+
+ expect {
+ Mock.verify_count
+ }.to raise_error(SpecExpectationNotMetError, /received it 0 times/)
+
+ @mock.method_call
+ Mock.verify_count
+ end
+
+ it "verifies the calls to the mocked method when a mock is defined before a stub" do
+ Mock.install_method @mock, :method_call, :mock
+ Mock.install_method @mock, :method_call, :stub
+
+ expect {
+ Mock.verify_count
+ }.to raise_error(SpecExpectationNotMetError, /received it 0 times/)
+
+ @mock.method_call
+ Mock.verify_count
+ end
+end
+
+RSpec.describe Mock, ".cleanup" do
+ before :each do
+ allow(MSpec).to receive(:actions)
+ allow(MSpec).to receive(:current).and_return(double("spec state").as_null_object)
+
+ @mock = double('cleanup')
+ @proxy = Mock.install_method @mock, :method_call
+ end
+
+ after :each do
+ Mock.cleanup
+ end
+
+ it "removes the mock method call if it did not override an existing method" do
+ expect(@mock).to respond_to(:method_call)
+
+ Mock.cleanup
+ expect(@mock).not_to respond_to(:method_call)
+ end
+
+ it "removes the replaced method if the mock method overrides an existing method" do
+ def @mock.already_here() :hey end
+ expect(@mock).to respond_to(:already_here)
+ replaced_name = Mock.replaced_name(@mock, :already_here)
+ Mock.install_method @mock, :already_here
+ expect(@mock).to respond_to(replaced_name)
+
+ Mock.cleanup
+ expect(@mock).not_to respond_to(replaced_name)
+ expect(@mock).to respond_to(:already_here)
+ expect(@mock.already_here).to eq(:hey)
+ end
+
+ it "removes all mock expectations" do
+ expect(Mock.mocks).to eq({ Mock.replaced_key(@mock, :method_call) => [@proxy] })
+ Mock.cleanup
+ expect(Mock.mocks).to eq({})
+ end
+
+ it "removes all stubs" do
+ Mock.cleanup # remove @proxy
+ @stub = Mock.install_method @mock, :method_call, :stub
+ expect(Mock.stubs).to eq({ Mock.replaced_key(@mock, :method_call) => [@stub] })
+ Mock.cleanup
+ expect(Mock.stubs).to eq({})
+ end
+
+ it "removes the replaced name for mocks" do
+ replaced_key = Mock.replaced_key(@mock, :method_call)
+ expect(Mock).to receive(:clear_replaced).with(replaced_key)
+
+ replaced_name = Mock.replaced_name(@mock, :method_call)
+ expect(Mock.replaced?(replaced_name)).to be_truthy
+
+ Mock.cleanup
+ expect(Mock.replaced?(replaced_name)).to be_falsey
+ end
+end
diff --git a/spec/mspec/spec/mocks/proxy_spec.rb b/spec/mspec/spec/mocks/proxy_spec.rb
new file mode 100644
index 0000000000..b994634694
--- /dev/null
+++ b/spec/mspec/spec/mocks/proxy_spec.rb
@@ -0,0 +1,405 @@
+require 'spec_helper'
+require 'mspec/mocks/proxy'
+
+RSpec.describe MockObject, ".new" do
+ it "creates a new mock object" do
+ m = MockObject.new('not a null object')
+ expect { m.not_a_method }.to raise_error(NoMethodError)
+ end
+
+ it "creates a new mock object that follows the NullObject pattern" do
+ m = MockObject.new('null object', :null_object => true)
+ expect(m.not_really_a_method).to equal(m)
+ end
+end
+
+RSpec.describe MockProxy, ".new" do
+ it "creates a mock proxy by default" do
+ expect(MockProxy.new.mock?).to be_truthy
+ end
+
+ it "creates a stub proxy by request" do
+ expect(MockProxy.new(:stub).stub?).to be_truthy
+ end
+
+ it "sets the call expectation to 1 call for a mock" do
+ expect(MockProxy.new.count).to eq([:exactly, 1])
+ end
+
+ it "sets the call expectation to any number of times for a stub" do
+ expect(MockProxy.new(:stub).count).to eq([:any_number_of_times, 0])
+ end
+end
+
+RSpec.describe MockProxy, "#count" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns the expected number of calls the mock should receive" do
+ expect(@proxy.count).to eq([:exactly, 1])
+ expect(@proxy.at_least(3).count).to eq([:at_least, 3])
+ end
+end
+
+RSpec.describe MockProxy, "#arguments" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns the expected arguments" do
+ expect(@proxy.arguments).to eq(:any_args)
+ end
+end
+
+RSpec.describe MockProxy, "#with" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns self" do
+ expect(@proxy.with(:a)).to be_equal(@proxy)
+ end
+
+ it "raises an ArgumentError if no arguments are given" do
+ expect { @proxy.with }.to raise_error(ArgumentError)
+ end
+
+ it "accepts any number of arguments" do
+ expect(@proxy.with(1, 2, 3)).to be_an_instance_of(MockProxy)
+ expect(@proxy.arguments).to eq([1,2,3])
+ end
+end
+
+RSpec.describe MockProxy, "#once" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns self" do
+ expect(@proxy.once).to be_equal(@proxy)
+ end
+
+ it "sets the expected calls to 1" do
+ @proxy.once
+ expect(@proxy.count).to eq([:exactly, 1])
+ end
+
+ it "accepts no arguments" do
+ expect { @proxy.once(:a) }.to raise_error
+ end
+end
+
+RSpec.describe MockProxy, "#twice" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns self" do
+ expect(@proxy.twice).to be_equal(@proxy)
+ end
+
+ it "sets the expected calls to 2" do
+ @proxy.twice
+ expect(@proxy.count).to eq([:exactly, 2])
+ end
+
+ it "accepts no arguments" do
+ expect { @proxy.twice(:b) }.to raise_error
+ end
+end
+
+RSpec.describe MockProxy, "#exactly" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns self" do
+ expect(@proxy.exactly(2)).to be_equal(@proxy)
+ end
+
+ it "sets the expected calls to exactly n" do
+ @proxy.exactly(5)
+ expect(@proxy.count).to eq([:exactly, 5])
+ end
+
+ it "does not accept an argument that Integer() cannot convert" do
+ expect { @proxy.exactly('x') }.to raise_error
+ end
+end
+
+RSpec.describe MockProxy, "#at_least" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns self" do
+ expect(@proxy.at_least(3)).to be_equal(@proxy)
+ end
+
+ it "sets the expected calls to at least n" do
+ @proxy.at_least(3)
+ expect(@proxy.count).to eq([:at_least, 3])
+ end
+
+ it "accepts :once :twice" do
+ @proxy.at_least(:once)
+ expect(@proxy.count).to eq([:at_least, 1])
+ @proxy.at_least(:twice)
+ expect(@proxy.count).to eq([:at_least, 2])
+ end
+
+ it "does not accept an argument that Integer() cannot convert" do
+ expect { @proxy.at_least('x') }.to raise_error
+ end
+end
+
+RSpec.describe MockProxy, "#at_most" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns self" do
+ expect(@proxy.at_most(2)).to be_equal(@proxy)
+ end
+
+ it "sets the expected calls to at most n" do
+ @proxy.at_most(2)
+ expect(@proxy.count).to eq([:at_most, 2])
+ end
+
+ it "accepts :once, :twice" do
+ @proxy.at_most(:once)
+ expect(@proxy.count).to eq([:at_most, 1])
+ @proxy.at_most(:twice)
+ expect(@proxy.count).to eq([:at_most, 2])
+ end
+
+ it "does not accept an argument that Integer() cannot convert" do
+ expect { @proxy.at_most('x') }.to raise_error
+ end
+end
+
+RSpec.describe MockProxy, "#any_number_of_times" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns self" do
+ expect(@proxy.any_number_of_times).to be_equal(@proxy)
+ end
+
+ it "sets the expected calls to any number of times" do
+ @proxy.any_number_of_times
+ expect(@proxy.count).to eq([:any_number_of_times, 0])
+ end
+
+ it "does not accept an argument" do
+ expect { @proxy.any_number_of_times(2) }.to raise_error
+ end
+end
+
+RSpec.describe MockProxy, "#and_return" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns self" do
+ expect(@proxy.and_return(false)).to equal(@proxy)
+ end
+
+ it "sets the expected return value" do
+ @proxy.and_return(false)
+ expect(@proxy.returning).to eq(false)
+ end
+
+ it "accepts any number of return values" do
+ @proxy.and_return(1, 2, 3)
+ expect(@proxy.returning).to eq(1)
+ expect(@proxy.returning).to eq(2)
+ expect(@proxy.returning).to eq(3)
+ end
+
+ it "implicitly sets the expected number of calls" do
+ @proxy.and_return(1, 2, 3)
+ expect(@proxy.count).to eq([:exactly, 3])
+ end
+
+ it "only sets the expected number of calls if it is higher than what is already set" do
+ @proxy.at_least(5).times.and_return(1, 2, 3)
+ expect(@proxy.count).to eq([:at_least, 5])
+
+ @proxy.at_least(2).times.and_return(1, 2, 3)
+ expect(@proxy.count).to eq([:at_least, 3])
+ end
+end
+
+RSpec.describe MockProxy, "#returning" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns nil by default" do
+ expect(@proxy.returning).to be_nil
+ end
+
+ it "returns the value set by #and_return" do
+ @proxy.and_return(2)
+ expect(@proxy.returning).to eq(2)
+ expect(@proxy.returning).to eq(2)
+ end
+
+ it "returns a sequence of values set by #and_return" do
+ @proxy.and_return(1,2,3,4)
+ expect(@proxy.returning).to eq(1)
+ expect(@proxy.returning).to eq(2)
+ expect(@proxy.returning).to eq(3)
+ expect(@proxy.returning).to eq(4)
+ expect(@proxy.returning).to eq(4)
+ expect(@proxy.returning).to eq(4)
+ end
+end
+
+RSpec.describe MockProxy, "#calls" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns the number of times the proxy is called" do
+ expect(@proxy.calls).to eq(0)
+ end
+end
+
+RSpec.describe MockProxy, "#called" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "increments the number of times the proxy is called" do
+ @proxy.called
+ @proxy.called
+ expect(@proxy.calls).to eq(2)
+ end
+end
+
+RSpec.describe MockProxy, "#times" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "is a no-op" do
+ expect(@proxy.times).to eq(@proxy)
+ end
+end
+
+RSpec.describe MockProxy, "#stub?" do
+ it "returns true if the proxy is created as a stub" do
+ expect(MockProxy.new(:stub).stub?).to be_truthy
+ end
+
+ it "returns false if the proxy is created as a mock" do
+ expect(MockProxy.new(:mock).stub?).to be_falsey
+ end
+end
+
+RSpec.describe MockProxy, "#mock?" do
+ it "returns true if the proxy is created as a mock" do
+ expect(MockProxy.new(:mock).mock?).to be_truthy
+ end
+
+ it "returns false if the proxy is created as a stub" do
+ expect(MockProxy.new(:stub).mock?).to be_falsey
+ end
+end
+
+RSpec.describe MockProxy, "#and_yield" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns self" do
+ expect(@proxy.and_yield(false)).to equal(@proxy)
+ end
+
+ it "sets the expected values to yield" do
+ expect(@proxy.and_yield(1).yielding).to eq([[1]])
+ end
+
+ it "accepts multiple values to yield" do
+ expect(@proxy.and_yield(1, 2, 3).yielding).to eq([[1, 2, 3]])
+ end
+end
+
+RSpec.describe MockProxy, "#raising" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns nil by default" do
+ expect(@proxy.raising).to be_nil
+ end
+
+ it "returns the exception object passed to #and_raise" do
+ exc = double("exception")
+ @proxy.and_raise(exc)
+ expect(@proxy.raising).to equal(exc)
+ end
+
+ it "returns an instance of RuntimeError when a String is passed to #and_raise" do
+ @proxy.and_raise("an error")
+ exc = @proxy.raising
+ expect(exc).to be_an_instance_of(RuntimeError)
+ expect(exc.message).to eq("an error")
+ end
+end
+
+RSpec.describe MockProxy, "#yielding" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns an empty array by default" do
+ expect(@proxy.yielding).to eq([])
+ end
+
+ it "returns an array of arrays of values the proxy should yield" do
+ @proxy.and_yield(3)
+ expect(@proxy.yielding).to eq([[3]])
+ end
+
+ it "returns an accumulation of arrays of values the proxy should yield" do
+ @proxy.and_yield(1).and_yield(2, 3)
+ expect(@proxy.yielding).to eq([[1], [2, 3]])
+ end
+end
+
+RSpec.describe MockProxy, "#yielding?" do
+ before :each do
+ @proxy = MockProxy.new
+ end
+
+ it "returns false if the proxy is not yielding" do
+ expect(@proxy.yielding?).to be_falsey
+ end
+
+ it "returns true if the proxy is yielding" do
+ @proxy.and_yield(1)
+ expect(@proxy.yielding?).to be_truthy
+ end
+end
+
+RSpec.describe MockIntObject, "#to_int" do
+ before :each do
+ @int = MockIntObject.new(10)
+ end
+
+ it "returns the number if to_int is called" do
+ expect(@int.to_int).to eq(10)
+ expect(@int.count).to eq([:at_least, 1])
+ end
+
+ it "tries to convert the target to int if to_int is called" do
+ expect(MockIntObject.new(@int).to_int).to eq(10)
+ expect(@int.count).to eq([:at_least, 1])
+ end
+end
diff --git a/spec/mspec/spec/runner/actions/filter_spec.rb b/spec/mspec/spec/runner/actions/filter_spec.rb
new file mode 100644
index 0000000000..7582b31c1d
--- /dev/null
+++ b/spec/mspec/spec/runner/actions/filter_spec.rb
@@ -0,0 +1,84 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/filter'
+require 'mspec/runner/mspec'
+require 'mspec/runner/tag'
+
+RSpec.describe ActionFilter do
+ it "creates a filter when not passed a description" do
+ expect(MatchFilter).not_to receive(:new)
+ ActionFilter.new(nil, nil)
+ end
+
+ it "creates a filter from a single description" do
+ expect(MatchFilter).to receive(:new).with(nil, "match me")
+ ActionFilter.new(nil, "match me")
+ end
+
+ it "creates a filter from an array of descriptions" do
+ expect(MatchFilter).to receive(:new).with(nil, "match me", "again")
+ ActionFilter.new(nil, ["match me", "again"])
+ end
+end
+
+RSpec.describe ActionFilter, "#===" do
+ before :each do
+ allow(MSpec).to receive(:read_tags).and_return(["match"])
+ @action = ActionFilter.new(nil, ["catch", "if you"])
+ end
+
+ it "returns false if there are no filters" do
+ action = ActionFilter.new
+ expect(action.===("anything")).to eq(false)
+ end
+
+ it "returns true if the argument matches any of the descriptions" do
+ expect(@action.===("catch")).to eq(true)
+ expect(@action.===("if you can")).to eq(true)
+ end
+
+ it "returns false if the argument does not match any of the descriptions" do
+ expect(@action.===("patch me")).to eq(false)
+ expect(@action.===("if I can")).to eq(false)
+ end
+end
+
+RSpec.describe ActionFilter, "#load" do
+ before :each do
+ @tag = SpecTag.new "tag(comment):description"
+ end
+
+ it "creates a filter from a single tag" do
+ expect(MSpec).to receive(:read_tags).with(["tag"]).and_return([@tag])
+ expect(MatchFilter).to receive(:new).with(nil, "description")
+ ActionFilter.new("tag", nil).load
+ end
+
+ it "creates a filter from an array of tags" do
+ expect(MSpec).to receive(:read_tags).with(["tag", "key"]).and_return([@tag])
+ expect(MatchFilter).to receive(:new).with(nil, "description")
+ ActionFilter.new(["tag", "key"], nil).load
+ end
+
+ it "creates a filter from both tags and descriptions" do
+ expect(MSpec).to receive(:read_tags).and_return([@tag])
+ filter = ActionFilter.new("tag", ["match me", "again"])
+ expect(MatchFilter).to receive(:new).with(nil, "description")
+ filter.load
+ end
+end
+
+RSpec.describe ActionFilter, "#register" do
+ it "registers itself with MSpec for the :load actions" do
+ filter = ActionFilter.new
+ expect(MSpec).to receive(:register).with(:load, filter)
+ filter.register
+ end
+end
+
+RSpec.describe ActionFilter, "#unregister" do
+ it "unregisters itself with MSpec for the :load actions" do
+ filter = ActionFilter.new
+ expect(MSpec).to receive(:unregister).with(:load, filter)
+ filter.unregister
+ end
+end
diff --git a/spec/mspec/spec/runner/actions/tag_spec.rb b/spec/mspec/spec/runner/actions/tag_spec.rb
new file mode 100644
index 0000000000..738e9a18c9
--- /dev/null
+++ b/spec/mspec/spec/runner/actions/tag_spec.rb
@@ -0,0 +1,313 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/tag'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+require 'mspec/runner/tag'
+
+RSpec.describe TagAction, ".new" do
+ it "creates an MatchFilter with its tag and desc arguments" do
+ filter = double('action filter').as_null_object
+ expect(MatchFilter).to receive(:new).with(nil, "some", "thing").and_return(filter)
+ TagAction.new :add, :all, nil, nil, ["tag", "key"], ["some", "thing"]
+ end
+end
+
+RSpec.describe TagAction, "#===" do
+ before :each do
+ allow(MSpec).to receive(:read_tags).and_return(["match"])
+ @action = TagAction.new :add, :fail, nil, nil, nil, ["catch", "if you"]
+ end
+
+ it "returns true if there are no filters" do
+ action = TagAction.new :add, :all, nil, nil
+ expect(action.===("anything")).to eq(true)
+ end
+
+ it "returns true if the argument matches any of the descriptions" do
+ expect(@action.===("catch")).to eq(true)
+ expect(@action.===("if you can")).to eq(true)
+ end
+
+ it "returns false if the argument does not match any of the descriptions" do
+ expect(@action.===("patch me")).to eq(false)
+ expect(@action.===("if I can")).to eq(false)
+ end
+end
+
+RSpec.describe TagAction, "#exception?" do
+ before :each do
+ @action = TagAction.new :add, :fail, nil, nil, nil, nil
+ end
+
+ it "returns false if no exception has been raised while evaluating an example" do
+ expect(@action.exception?).to be_falsey
+ end
+
+ it "returns true if an exception was raised while evaluating an example" do
+ @action.exception ExceptionState.new nil, nil, Exception.new("failed")
+ expect(@action.exception?).to be_truthy
+ end
+end
+
+RSpec.describe TagAction, "#outcome?" do
+ before :each do
+ allow(MSpec).to receive(:read_tags).and_return([])
+ @exception = ExceptionState.new nil, nil, Exception.new("failed")
+ end
+
+ it "returns true if outcome is :fail and the spec fails" do
+ action = TagAction.new :add, :fail, nil, nil, nil, nil
+ action.exception @exception
+ expect(action.outcome?).to eq(true)
+ end
+
+ it "returns false if the outcome is :fail and the spec passes" do
+ action = TagAction.new :add, :fail, nil, nil, nil, nil
+ expect(action.outcome?).to eq(false)
+ end
+
+ it "returns true if the outcome is :pass and the spec passes" do
+ action = TagAction.new :del, :pass, nil, nil, nil, nil
+ expect(action.outcome?).to eq(true)
+ end
+
+ it "returns false if the outcome is :pass and the spec fails" do
+ action = TagAction.new :del, :pass, nil, nil, nil, nil
+ action.exception @exception
+ expect(action.outcome?).to eq(false)
+ end
+
+ it "returns true if the outcome is :all" do
+ action = TagAction.new :add, :all, nil, nil, nil, nil
+ action.exception @exception
+ expect(action.outcome?).to eq(true)
+ end
+end
+
+RSpec.describe TagAction, "#before" do
+ it "resets the #exception? flag to false" do
+ action = TagAction.new :add, :fail, nil, nil, nil, nil
+ expect(action.exception?).to be_falsey
+ action.exception ExceptionState.new(nil, nil, Exception.new("Fail!"))
+ expect(action.exception?).to be_truthy
+ action.before(ExampleState.new(ContextState.new("describe"), "it"))
+ expect(action.exception?).to be_falsey
+ end
+end
+
+RSpec.describe TagAction, "#exception" do
+ it "sets the #exception? flag" do
+ action = TagAction.new :add, :fail, nil, nil, nil, nil
+ expect(action.exception?).to be_falsey
+ action.exception ExceptionState.new(nil, nil, Exception.new("Fail!"))
+ expect(action.exception?).to be_truthy
+ end
+end
+
+RSpec.describe TagAction, "#after when action is :add" do
+ before :each do
+ allow(MSpec).to receive(:read_tags).and_return([])
+ context = ContextState.new "Catch#me"
+ @state = ExampleState.new context, "if you can"
+ @tag = SpecTag.new "tag(comment):Catch#me if you can"
+ allow(SpecTag).to receive(:new).and_return(@tag)
+ @exception = ExceptionState.new nil, nil, Exception.new("failed")
+ end
+
+ it "does not write a tag if the description does not match" do
+ expect(MSpec).not_to receive(:write_tag)
+ action = TagAction.new :add, :all, "tag", "comment", nil, "match"
+ action.after @state
+ end
+
+ it "does not write a tag if outcome is :fail and the spec passed" do
+ expect(MSpec).not_to receive(:write_tag)
+ action = TagAction.new :add, :fail, "tag", "comment", nil, "can"
+ action.after @state
+ end
+
+ it "writes a tag if the outcome is :fail and the spec failed" do
+ expect(MSpec).to receive(:write_tag).with(@tag)
+ action = TagAction.new :add, :fail, "tag", "comment", nil, "can"
+ action.exception @exception
+ action.after @state
+ end
+
+ it "does not write a tag if outcome is :pass and the spec failed" do
+ expect(MSpec).not_to receive(:write_tag)
+ action = TagAction.new :add, :pass, "tag", "comment", nil, "can"
+ action.exception @exception
+ action.after @state
+ end
+
+ it "writes a tag if the outcome is :pass and the spec passed" do
+ expect(MSpec).to receive(:write_tag).with(@tag)
+ action = TagAction.new :add, :pass, "tag", "comment", nil, "can"
+ action.after @state
+ end
+
+ it "writes a tag if the outcome is :all" do
+ expect(MSpec).to receive(:write_tag).with(@tag)
+ action = TagAction.new :add, :all, "tag", "comment", nil, "can"
+ action.after @state
+ end
+end
+
+RSpec.describe TagAction, "#after when action is :del" do
+ before :each do
+ allow(MSpec).to receive(:read_tags).and_return([])
+ context = ContextState.new "Catch#me"
+ @state = ExampleState.new context, "if you can"
+ @tag = SpecTag.new "tag(comment):Catch#me if you can"
+ allow(SpecTag).to receive(:new).and_return(@tag)
+ @exception = ExceptionState.new nil, nil, Exception.new("failed")
+ end
+
+ it "does not delete a tag if the description does not match" do
+ expect(MSpec).not_to receive(:delete_tag)
+ action = TagAction.new :del, :all, "tag", "comment", nil, "match"
+ action.after @state
+ end
+
+ it "does not delete a tag if outcome is :fail and the spec passed" do
+ expect(MSpec).not_to receive(:delete_tag)
+ action = TagAction.new :del, :fail, "tag", "comment", nil, "can"
+ action.after @state
+ end
+
+ it "deletes a tag if the outcome is :fail and the spec failed" do
+ expect(MSpec).to receive(:delete_tag).with(@tag)
+ action = TagAction.new :del, :fail, "tag", "comment", nil, "can"
+ action.exception @exception
+ action.after @state
+ end
+
+ it "does not delete a tag if outcome is :pass and the spec failed" do
+ expect(MSpec).not_to receive(:delete_tag)
+ action = TagAction.new :del, :pass, "tag", "comment", nil, "can"
+ action.exception @exception
+ action.after @state
+ end
+
+ it "deletes a tag if the outcome is :pass and the spec passed" do
+ expect(MSpec).to receive(:delete_tag).with(@tag)
+ action = TagAction.new :del, :pass, "tag", "comment", nil, "can"
+ action.after @state
+ end
+
+ it "deletes a tag if the outcome is :all" do
+ expect(MSpec).to receive(:delete_tag).with(@tag)
+ action = TagAction.new :del, :all, "tag", "comment", nil, "can"
+ action.after @state
+ end
+end
+
+RSpec.describe TagAction, "#finish" do
+ before :each do
+ $stdout = @out = IOStub.new
+ context = ContextState.new "Catch#me"
+ @state = ExampleState.new context, "if you can"
+ allow(MSpec).to receive(:write_tag).and_return(true)
+ allow(MSpec).to receive(:delete_tag).and_return(true)
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "reports no specs tagged if none where tagged" do
+ action = TagAction.new :add, :fail, "tag", "comment", nil, "can"
+ allow(action).to receive(:outcome?).and_return(false)
+ action.after @state
+ action.finish
+ expect(@out).to eq("\nTagAction: no specs were tagged with 'tag'\n")
+ end
+
+ it "reports no specs tagged if none where tagged" do
+ action = TagAction.new :del, :fail, "tag", "comment", nil, "can"
+ allow(action).to receive(:outcome?).and_return(false)
+ action.after @state
+ action.finish
+ expect(@out).to eq("\nTagAction: no tags 'tag' were deleted\n")
+ end
+
+ it "reports the spec descriptions that were tagged" do
+ action = TagAction.new :add, :fail, "tag", "comment", nil, "can"
+ allow(action).to receive(:outcome?).and_return(true)
+ action.after @state
+ action.finish
+ expect(@out).to eq(%[
+TagAction: specs tagged with 'tag':
+
+Catch#me if you can
+])
+ end
+
+ it "reports the spec descriptions for the tags that were deleted" do
+ action = TagAction.new :del, :fail, "tag", "comment", nil, "can"
+ allow(action).to receive(:outcome?).and_return(true)
+ action.after @state
+ action.finish
+ expect(@out).to eq(%[
+TagAction: tag 'tag' deleted for specs:
+
+Catch#me if you can
+])
+ end
+end
+
+RSpec.describe TagAction, "#register" do
+ before :each do
+ allow(MSpec).to receive(:register)
+ allow(MSpec).to receive(:read_tags).and_return([])
+ @action = TagAction.new :add, :all, nil, nil, nil, nil
+ end
+
+ it "registers itself with MSpec for the :before event" do
+ expect(MSpec).to receive(:register).with(:before, @action)
+ @action.register
+ end
+
+ it "registers itself with MSpec for the :after event" do
+ expect(MSpec).to receive(:register).with(:after, @action)
+ @action.register
+ end
+
+ it "registers itself with MSpec for the :exception event" do
+ expect(MSpec).to receive(:register).with(:exception, @action)
+ @action.register
+ end
+
+ it "registers itself with MSpec for the :finish event" do
+ expect(MSpec).to receive(:register).with(:finish, @action)
+ @action.register
+ end
+end
+
+RSpec.describe TagAction, "#unregister" do
+ before :each do
+ allow(MSpec).to receive(:unregister)
+ allow(MSpec).to receive(:read_tags).and_return([])
+ @action = TagAction.new :add, :all, nil, nil, nil, nil
+ end
+
+ it "unregisters itself with MSpec for the :before event" do
+ expect(MSpec).to receive(:unregister).with(:before, @action)
+ @action.unregister
+ end
+
+ it "unregisters itself with MSpec for the :after event" do
+ expect(MSpec).to receive(:unregister).with(:after, @action)
+ @action.unregister
+ end
+
+ it "unregisters itself with MSpec for the :exception event" do
+ expect(MSpec).to receive(:unregister).with(:exception, @action)
+ @action.unregister
+ end
+
+ it "unregisters itself with MSpec for the :finish event" do
+ expect(MSpec).to receive(:unregister).with(:finish, @action)
+ @action.unregister
+ end
+end
diff --git a/spec/mspec/spec/runner/actions/taglist_spec.rb b/spec/mspec/spec/runner/actions/taglist_spec.rb
new file mode 100644
index 0000000000..b6a5400f7d
--- /dev/null
+++ b/spec/mspec/spec/runner/actions/taglist_spec.rb
@@ -0,0 +1,152 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/taglist'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+require 'mspec/runner/tag'
+
+RSpec.describe TagListAction, "#include?" do
+ it "returns true" do
+ expect(TagListAction.new.include?(:anything)).to be_truthy
+ end
+end
+
+RSpec.describe TagListAction, "#===" do
+ before :each do
+ tag = SpecTag.new "fails:description"
+ allow(MSpec).to receive(:read_tags).and_return([tag])
+ @filter = double("MatchFilter").as_null_object
+ allow(MatchFilter).to receive(:new).and_return(@filter)
+ @action = TagListAction.new
+ @action.load
+ end
+
+ it "returns true if filter === string returns true" do
+ expect(@filter).to receive(:===).with("str").and_return(true)
+ expect(@action.===("str")).to be_truthy
+ end
+
+ it "returns false if filter === string returns false" do
+ expect(@filter).to receive(:===).with("str").and_return(false)
+ expect(@action.===("str")).to be_falsey
+ end
+end
+
+RSpec.describe TagListAction, "#start" do
+ before :each do
+ @stdout = $stdout
+ $stdout = IOStub.new
+ end
+
+ after :each do
+ $stdout = @stdout
+ end
+
+ it "prints a banner for specific tags" do
+ action = TagListAction.new ["fails", "unstable"]
+ action.start
+ expect($stdout).to eq("\nListing specs tagged with 'fails', 'unstable'\n\n")
+ end
+
+ it "prints a banner for all tags" do
+ action = TagListAction.new
+ action.start
+ expect($stdout).to eq("\nListing all tagged specs\n\n")
+ end
+end
+
+RSpec.describe TagListAction, "#load" do
+ before :each do
+ @t1 = SpecTag.new "fails:I fail"
+ @t2 = SpecTag.new "unstable:I'm unstable"
+ end
+
+ it "creates a MatchFilter for matching tags" do
+ expect(MSpec).to receive(:read_tags).with(["fails"]).and_return([@t1])
+ expect(MatchFilter).to receive(:new).with(nil, "I fail")
+ TagListAction.new(["fails"]).load
+ end
+
+ it "creates a MatchFilter for all tags" do
+ expect(MSpec).to receive(:read_tags).and_return([@t1, @t2])
+ expect(MatchFilter).to receive(:new).with(nil, "I fail", "I'm unstable")
+ TagListAction.new.load
+ end
+
+ it "does not create a MatchFilter if there are no matching tags" do
+ allow(MSpec).to receive(:read_tags).and_return([])
+ expect(MatchFilter).not_to receive(:new)
+ TagListAction.new(["fails"]).load
+ end
+end
+
+RSpec.describe TagListAction, "#after" do
+ before :each do
+ @stdout = $stdout
+ $stdout = IOStub.new
+
+ @state = double("ExampleState")
+ allow(@state).to receive(:description).and_return("str")
+
+ @action = TagListAction.new
+ end
+
+ after :each do
+ $stdout = @stdout
+ end
+
+ it "prints nothing if the filter does not match" do
+ expect(@action).to receive(:===).with("str").and_return(false)
+ @action.after(@state)
+ expect($stdout).to eq("")
+ end
+
+ it "prints the example description if the filter matches" do
+ expect(@action).to receive(:===).with("str").and_return(true)
+ @action.after(@state)
+ expect($stdout).to eq("str\n")
+ end
+end
+
+RSpec.describe TagListAction, "#register" do
+ before :each do
+ allow(MSpec).to receive(:register)
+ @action = TagListAction.new
+ end
+
+ it "registers itself with MSpec for the :start event" do
+ expect(MSpec).to receive(:register).with(:start, @action)
+ @action.register
+ end
+
+ it "registers itself with MSpec for the :load event" do
+ expect(MSpec).to receive(:register).with(:load, @action)
+ @action.register
+ end
+
+ it "registers itself with MSpec for the :after event" do
+ expect(MSpec).to receive(:register).with(:after, @action)
+ @action.register
+ end
+end
+
+RSpec.describe TagListAction, "#unregister" do
+ before :each do
+ allow(MSpec).to receive(:unregister)
+ @action = TagListAction.new
+ end
+
+ it "unregisters itself with MSpec for the :start event" do
+ expect(MSpec).to receive(:unregister).with(:start, @action)
+ @action.unregister
+ end
+
+ it "unregisters itself with MSpec for the :load event" do
+ expect(MSpec).to receive(:unregister).with(:load, @action)
+ @action.unregister
+ end
+
+ it "unregisters itself with MSpec for the :after event" do
+ expect(MSpec).to receive(:unregister).with(:after, @action)
+ @action.unregister
+ end
+end
diff --git a/spec/mspec/spec/runner/actions/tagpurge_spec.rb b/spec/mspec/spec/runner/actions/tagpurge_spec.rb
new file mode 100644
index 0000000000..37df0afd5a
--- /dev/null
+++ b/spec/mspec/spec/runner/actions/tagpurge_spec.rb
@@ -0,0 +1,154 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/tagpurge'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+require 'mspec/runner/tag'
+
+RSpec.describe TagPurgeAction, "#start" do
+ before :each do
+ @stdout = $stdout
+ $stdout = IOStub.new
+ end
+
+ after :each do
+ $stdout = @stdout
+ end
+
+ it "prints a banner" do
+ action = TagPurgeAction.new
+ action.start
+ expect($stdout).to eq("\nRemoving tags not matching any specs\n\n")
+ end
+end
+
+RSpec.describe TagPurgeAction, "#load" do
+ before :each do
+ @t1 = SpecTag.new "fails:I fail"
+ @t2 = SpecTag.new "unstable:I'm unstable"
+ end
+
+ it "creates a MatchFilter for all tags" do
+ expect(MSpec).to receive(:read_tags).and_return([@t1, @t2])
+ expect(MatchFilter).to receive(:new).with(nil, "I fail", "I'm unstable")
+ TagPurgeAction.new.load
+ end
+end
+
+RSpec.describe TagPurgeAction, "#after" do
+ before :each do
+ @state = double("ExampleState")
+ allow(@state).to receive(:description).and_return("str")
+
+ @action = TagPurgeAction.new
+ end
+
+ it "does not save the description if the filter does not match" do
+ expect(@action).to receive(:===).with("str").and_return(false)
+ @action.after @state
+ expect(@action.matching).to eq([])
+ end
+
+ it "saves the description if the filter matches" do
+ expect(@action).to receive(:===).with("str").and_return(true)
+ @action.after @state
+ expect(@action.matching).to eq(["str"])
+ end
+end
+
+RSpec.describe TagPurgeAction, "#unload" do
+ before :each do
+ @stdout = $stdout
+ $stdout = IOStub.new
+
+ @t1 = SpecTag.new "fails:I fail"
+ @t2 = SpecTag.new "unstable:I'm unstable"
+ @t3 = SpecTag.new "fails:I'm unstable"
+
+ allow(MSpec).to receive(:read_tags).and_return([@t1, @t2, @t3])
+ allow(MSpec).to receive(:write_tags)
+
+ @state = double("ExampleState")
+ allow(@state).to receive(:description).and_return("I'm unstable")
+
+ @action = TagPurgeAction.new
+ @action.load
+ @action.after @state
+ end
+
+ after :each do
+ $stdout = @stdout
+ end
+
+ it "does not rewrite any tags if there were no tags for the specs" do
+ expect(MSpec).to receive(:read_tags).and_return([])
+ expect(MSpec).to receive(:delete_tags)
+ expect(MSpec).not_to receive(:write_tags)
+
+ @action.load
+ @action.after @state
+ @action.unload
+
+ expect($stdout).to eq("")
+ end
+
+ it "rewrites tags that were matched" do
+ expect(MSpec).to receive(:write_tags).with([@t2, @t3])
+ @action.unload
+ end
+
+ it "prints tags that were not matched" do
+ @action.unload
+ expect($stdout).to eq("I fail\n")
+ end
+end
+
+RSpec.describe TagPurgeAction, "#unload" do
+ before :each do
+ @stdout = $stdout
+ $stdout = IOStub.new
+
+ allow(MSpec).to receive(:read_tags).and_return([])
+
+ @state = double("ExampleState")
+ allow(@state).to receive(:description).and_return("I'm unstable")
+
+ @action = TagPurgeAction.new
+ @action.load
+ @action.after @state
+ end
+
+ after :each do
+ $stdout = @stdout
+ end
+
+ it "deletes the tag file if no tags were found" do
+ expect(MSpec).not_to receive(:write_tags)
+ expect(MSpec).to receive(:delete_tags)
+ @action.unload
+ expect($stdout).to eq("")
+ end
+end
+
+RSpec.describe TagPurgeAction, "#register" do
+ before :each do
+ allow(MSpec).to receive(:register)
+ @action = TagPurgeAction.new
+ end
+
+ it "registers itself with MSpec for the :unload event" do
+ expect(MSpec).to receive(:register).with(:unload, @action)
+ @action.register
+ end
+end
+
+RSpec.describe TagPurgeAction, "#unregister" do
+ before :each do
+ allow(MSpec).to receive(:unregister)
+ @action = TagPurgeAction.new
+ end
+
+ it "unregisters itself with MSpec for the :unload event" do
+ expect(MSpec).to receive(:unregister).with(:unload, @action)
+ @action.unregister
+ end
+end
diff --git a/spec/mspec/spec/runner/actions/tally_spec.rb b/spec/mspec/spec/runner/actions/tally_spec.rb
new file mode 100644
index 0000000000..d80ab1164a
--- /dev/null
+++ b/spec/mspec/spec/runner/actions/tally_spec.rb
@@ -0,0 +1,355 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/runner/actions/tally'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+RSpec.describe Tally, "#files!" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "increments the count returned by #files" do
+ @tally.files! 3
+ expect(@tally.files).to eq(3)
+ @tally.files!
+ expect(@tally.files).to eq(4)
+ end
+end
+
+RSpec.describe Tally, "#examples!" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "increments the count returned by #examples" do
+ @tally.examples! 2
+ expect(@tally.examples).to eq(2)
+ @tally.examples! 2
+ expect(@tally.examples).to eq(4)
+ end
+end
+
+RSpec.describe Tally, "#expectations!" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "increments the count returned by #expectations" do
+ @tally.expectations!
+ expect(@tally.expectations).to eq(1)
+ @tally.expectations! 3
+ expect(@tally.expectations).to eq(4)
+ end
+end
+
+RSpec.describe Tally, "#failures!" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "increments the count returned by #failures" do
+ @tally.failures! 1
+ expect(@tally.failures).to eq(1)
+ @tally.failures!
+ expect(@tally.failures).to eq(2)
+ end
+end
+
+RSpec.describe Tally, "#errors!" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "increments the count returned by #errors" do
+ @tally.errors!
+ expect(@tally.errors).to eq(1)
+ @tally.errors! 2
+ expect(@tally.errors).to eq(3)
+ end
+end
+
+RSpec.describe Tally, "#guards!" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "increments the count returned by #guards" do
+ @tally.guards!
+ expect(@tally.guards).to eq(1)
+ @tally.guards! 2
+ expect(@tally.guards).to eq(3)
+ end
+end
+
+RSpec.describe Tally, "#file" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "returns a formatted string of the number of #files" do
+ expect(@tally.file).to eq("0 files")
+ @tally.files!
+ expect(@tally.file).to eq("1 file")
+ @tally.files!
+ expect(@tally.file).to eq("2 files")
+ end
+end
+
+RSpec.describe Tally, "#example" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "returns a formatted string of the number of #examples" do
+ expect(@tally.example).to eq("0 examples")
+ @tally.examples!
+ expect(@tally.example).to eq("1 example")
+ @tally.examples!
+ expect(@tally.example).to eq("2 examples")
+ end
+end
+
+RSpec.describe Tally, "#expectation" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "returns a formatted string of the number of #expectations" do
+ expect(@tally.expectation).to eq("0 expectations")
+ @tally.expectations!
+ expect(@tally.expectation).to eq("1 expectation")
+ @tally.expectations!
+ expect(@tally.expectation).to eq("2 expectations")
+ end
+end
+
+RSpec.describe Tally, "#failure" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "returns a formatted string of the number of #failures" do
+ expect(@tally.failure).to eq("0 failures")
+ @tally.failures!
+ expect(@tally.failure).to eq("1 failure")
+ @tally.failures!
+ expect(@tally.failure).to eq("2 failures")
+ end
+end
+
+RSpec.describe Tally, "#error" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "returns a formatted string of the number of #errors" do
+ expect(@tally.error).to eq("0 errors")
+ @tally.errors!
+ expect(@tally.error).to eq("1 error")
+ @tally.errors!
+ expect(@tally.error).to eq("2 errors")
+ end
+end
+
+RSpec.describe Tally, "#guard" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ it "returns a formatted string of the number of #guards" do
+ expect(@tally.guard).to eq("0 guards")
+ @tally.guards!
+ expect(@tally.guard).to eq("1 guard")
+ @tally.guards!
+ expect(@tally.guard).to eq("2 guards")
+ end
+end
+
+RSpec.describe Tally, "#format" do
+ before :each do
+ @tally = Tally.new
+ end
+
+ after :each do
+ MSpec.clear_modes
+ end
+
+ it "returns a formatted string of counts" do
+ @tally.files!
+ @tally.examples! 2
+ @tally.expectations! 4
+ @tally.errors!
+ @tally.tagged!
+ expect(@tally.format).to eq("1 file, 2 examples, 4 expectations, 0 failures, 1 error, 1 tagged")
+ end
+
+ it "includes guards if MSpec is in verify mode" do
+ MSpec.register_mode :verify
+ @tally.files!
+ @tally.examples! 2
+ @tally.expectations! 4
+ @tally.errors!
+ @tally.tagged!
+ @tally.guards!
+ expect(@tally.format).to eq(
+ "1 file, 2 examples, 4 expectations, 0 failures, 1 error, 1 tagged, 1 guard"
+ )
+ end
+
+ it "includes guards if MSpec is in report mode" do
+ MSpec.register_mode :report
+ @tally.files!
+ @tally.examples! 2
+ @tally.expectations! 4
+ @tally.errors!
+ @tally.tagged!
+ @tally.guards! 2
+ expect(@tally.format).to eq(
+ "1 file, 2 examples, 4 expectations, 0 failures, 1 error, 1 tagged, 2 guards"
+ )
+ end
+
+ it "includes guards if MSpec is in report_on mode" do
+ MSpec.register_mode :report_on
+ @tally.files!
+ @tally.examples! 2
+ @tally.expectations! 4
+ @tally.errors!
+ @tally.guards! 2
+ expect(@tally.format).to eq(
+ "1 file, 2 examples, 4 expectations, 0 failures, 1 error, 0 tagged, 2 guards"
+ )
+ end
+end
+
+RSpec.describe TallyAction, "#counter" do
+ before :each do
+ @tally = TallyAction.new
+ @state = ExampleState.new("describe", "it")
+ end
+
+ it "returns the Tally object" do
+ expect(@tally.counter).to be_kind_of(Tally)
+ end
+end
+
+RSpec.describe TallyAction, "#load" do
+ before :each do
+ @tally = TallyAction.new
+ @state = ExampleState.new("describe", "it")
+ end
+
+ it "increments the count returned by Tally#files" do
+ @tally.load
+ expect(@tally.counter.files).to eq(1)
+ end
+end
+
+RSpec.describe TallyAction, "#expectation" do
+ before :each do
+ @tally = TallyAction.new
+ @state = ExampleState.new("describe", "it")
+ end
+
+ it "increments the count returned by Tally#expectations" do
+ @tally.expectation @state
+ expect(@tally.counter.expectations).to eq(1)
+ end
+end
+
+RSpec.describe TallyAction, "#example" do
+ before :each do
+ @tally = TallyAction.new
+ @state = ExampleState.new("describe", "it")
+ end
+
+ it "increments counts returned by Tally#examples" do
+ @tally.example @state, nil
+ expect(@tally.counter.examples).to eq(1)
+ expect(@tally.counter.expectations).to eq(0)
+ expect(@tally.counter.failures).to eq(0)
+ expect(@tally.counter.errors).to eq(0)
+ end
+end
+
+RSpec.describe TallyAction, "#exception" do
+ before :each do
+ @tally = TallyAction.new
+ @state = ExampleState.new("describe", "it")
+ end
+
+ it "increments counts returned by Tally#failures" do
+ exc = ExceptionState.new nil, nil, SpecExpectationNotMetError.new("Failed!")
+ @tally.exception exc
+ expect(@tally.counter.examples).to eq(0)
+ expect(@tally.counter.expectations).to eq(0)
+ expect(@tally.counter.failures).to eq(1)
+ expect(@tally.counter.errors).to eq(0)
+ end
+end
+
+RSpec.describe TallyAction, "#exception" do
+ before :each do
+ @tally = TallyAction.new
+ @state = ExampleState.new("describe", "it")
+ end
+
+ it "increments counts returned by Tally#errors" do
+ exc = ExceptionState.new nil, nil, Exception.new("Error!")
+ @tally.exception exc
+ expect(@tally.counter.examples).to eq(0)
+ expect(@tally.counter.expectations).to eq(0)
+ expect(@tally.counter.failures).to eq(0)
+ expect(@tally.counter.errors).to eq(1)
+ end
+end
+
+RSpec.describe TallyAction, "#format" do
+ before :each do
+ @tally = TallyAction.new
+ @state = ExampleState.new("describe", "it")
+ end
+
+ it "returns a readable string of counts" do
+ @tally.load
+ @tally.example @state, nil
+ @tally.expectation @state
+ @tally.expectation @state
+ exc = ExceptionState.new nil, nil, SpecExpectationNotMetError.new("Failed!")
+ @tally.exception exc
+ expect(@tally.format).to eq("1 file, 1 example, 2 expectations, 1 failure, 0 errors, 0 tagged")
+ end
+end
+
+RSpec.describe TallyAction, "#register" do
+ before :each do
+ @tally = TallyAction.new
+ @state = ExampleState.new("describe", "it")
+ end
+
+ it "registers itself with MSpec for appropriate actions" do
+ expect(MSpec).to receive(:register).with(:load, @tally)
+ expect(MSpec).to receive(:register).with(:exception, @tally)
+ expect(MSpec).to receive(:register).with(:example, @tally)
+ expect(MSpec).to receive(:register).with(:tagged, @tally)
+ expect(MSpec).to receive(:register).with(:expectation, @tally)
+ @tally.register
+ end
+end
+
+RSpec.describe TallyAction, "#unregister" do
+ before :each do
+ @tally = TallyAction.new
+ @state = ExampleState.new("describe", "it")
+ end
+
+ it "unregisters itself with MSpec for appropriate actions" do
+ expect(MSpec).to receive(:unregister).with(:load, @tally)
+ expect(MSpec).to receive(:unregister).with(:exception, @tally)
+ expect(MSpec).to receive(:unregister).with(:example, @tally)
+ expect(MSpec).to receive(:unregister).with(:tagged, @tally)
+ expect(MSpec).to receive(:unregister).with(:expectation, @tally)
+ @tally.unregister
+ end
+end
diff --git a/spec/mspec/spec/runner/actions/timer_spec.rb b/spec/mspec/spec/runner/actions/timer_spec.rb
new file mode 100644
index 0000000000..28a317177b
--- /dev/null
+++ b/spec/mspec/spec/runner/actions/timer_spec.rb
@@ -0,0 +1,44 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/timer'
+require 'mspec/runner/mspec'
+require 'time'
+
+RSpec.describe TimerAction do
+ before :each do
+ @timer = TimerAction.new
+ @start_time = Time.utc(2009, 3, 30, 14, 5, 19)
+ @stop_time = Time.utc(2009, 3, 30, 14, 5, 52)
+ end
+
+ it "responds to #start by recording the current time" do
+ expect(Time).to receive(:now)
+ @timer.start
+ end
+
+ it "responds to #finish by recording the current time" do
+ expect(Time).to receive(:now)
+ @timer.finish
+ end
+
+ it "responds to #elapsed by returning the difference between stop and start" do
+ allow(Time).to receive(:now).and_return(@start_time)
+ @timer.start
+ allow(Time).to receive(:now).and_return(@stop_time)
+ @timer.finish
+ expect(@timer.elapsed).to eq(33)
+ end
+
+ it "responds to #format by returning a readable string of elapsed time" do
+ allow(Time).to receive(:now).and_return(@start_time)
+ @timer.start
+ allow(Time).to receive(:now).and_return(@stop_time)
+ @timer.finish
+ expect(@timer.format).to eq("Finished in 33.000000 seconds")
+ end
+
+ it "responds to #register by registering itself with MSpec for appropriate actions" do
+ expect(MSpec).to receive(:register).with(:start, @timer)
+ expect(MSpec).to receive(:register).with(:finish, @timer)
+ @timer.register
+ end
+end
diff --git a/spec/mspec/spec/runner/context_spec.rb b/spec/mspec/spec/runner/context_spec.rb
new file mode 100644
index 0000000000..a864428aec
--- /dev/null
+++ b/spec/mspec/spec/runner/context_spec.rb
@@ -0,0 +1,1028 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/base'
+require 'mspec/runner/mspec'
+require 'mspec/mocks/mock'
+require 'mspec/runner/context'
+require 'mspec/runner/example'
+
+RSpec.describe ContextState, "#describe" do
+ before :each do
+ @state = ContextState.new "C#m"
+ @proc = proc { ScratchPad.record :a }
+ ScratchPad.clear
+ end
+
+ it "evaluates the passed block" do
+ @state.describe(&@proc)
+ expect(ScratchPad.recorded).to eq(:a)
+ end
+
+ it "evaluates the passed block via #protect" do
+ expect(@state).to receive(:protect).with("C#m", @proc, false)
+ @state.describe(&@proc)
+ end
+
+ it "registers #parent as the current MSpec ContextState" do
+ parent = ContextState.new ""
+ @state.parent = parent
+ expect(MSpec).to receive(:register_current).with(parent)
+ @state.describe { }
+ end
+
+ it "registers self with MSpec when #shared? is true" do
+ state = ContextState.new "something shared", :shared => true
+ expect(MSpec).to receive(:register_shared).with(state)
+ state.describe { }
+ end
+end
+
+RSpec.describe ContextState, "#shared?" do
+ it "returns false when the ContextState is not shared" do
+ expect(ContextState.new("").shared?).to be_falsey
+ end
+
+ it "returns true when the ContextState is shared" do
+ expect(ContextState.new("", {:shared => true}).shared?).to be_truthy
+ end
+end
+
+RSpec.describe ContextState, "#to_s" do
+ it "returns a description string for self when passed a Module" do
+ expect(ContextState.new(Object).to_s).to eq("Object")
+ end
+
+ it "returns a description string for self when passed a String" do
+ expect(ContextState.new("SomeClass").to_s).to eq("SomeClass")
+ end
+end
+
+RSpec.describe ContextState, "#description" do
+ before :each do
+ @state = ContextState.new "when empty"
+ @parent = ContextState.new "Toplevel"
+ end
+
+ it "returns a composite description string from self and all parents" do
+ expect(@parent.description).to eq("Toplevel")
+ expect(@state.description).to eq("when empty")
+ @state.parent = @parent
+ expect(@state.description).to eq("Toplevel when empty")
+ end
+end
+
+RSpec.describe ContextState, "#it" do
+ before :each do
+ @state = ContextState.new ""
+ @proc = lambda {|*| }
+
+ @ex = ExampleState.new("", "", &@proc)
+ end
+
+ it "creates an ExampleState instance for the block" do
+ expect(ExampleState).to receive(:new).with(@state, "it", @proc).and_return(@ex)
+ @state.describe(&@proc)
+ @state.it("it", &@proc)
+ end
+
+ it "calls registered :add actions" do
+ expect(ExampleState).to receive(:new).with(@state, "it", @proc).and_return(@ex)
+
+ add_action = double("add")
+ expect(add_action).to receive(:add).with(@ex) { ScratchPad.record :add }
+ MSpec.register :add, add_action
+
+ @state.it("it", &@proc)
+ expect(ScratchPad.recorded).to eq(:add)
+ MSpec.unregister :add, add_action
+ end
+end
+
+RSpec.describe ContextState, "#examples" do
+ before :each do
+ @state = ContextState.new ""
+ end
+
+ it "returns a list of all examples in this ContextState" do
+ @state.it("first") { }
+ @state.it("second") { }
+ expect(@state.examples.size).to eq(2)
+ end
+end
+
+RSpec.describe ContextState, "#before" do
+ before :each do
+ @state = ContextState.new ""
+ @proc = lambda {|*| }
+ end
+
+ it "records the block for :each" do
+ @state.before(:each, &@proc)
+ expect(@state.before(:each)).to eq([@proc])
+ end
+
+ it "records the block for :all" do
+ @state.before(:all, &@proc)
+ expect(@state.before(:all)).to eq([@proc])
+ end
+end
+
+RSpec.describe ContextState, "#after" do
+ before :each do
+ @state = ContextState.new ""
+ @proc = lambda {|*| }
+ end
+
+ it "records the block for :each" do
+ @state.after(:each, &@proc)
+ expect(@state.after(:each)).to eq([@proc])
+ end
+
+ it "records the block for :all" do
+ @state.after(:all, &@proc)
+ expect(@state.after(:all)).to eq([@proc])
+ end
+end
+
+RSpec.describe ContextState, "#pre" do
+ before :each do
+ @a = lambda {|*| }
+ @b = lambda {|*| }
+ @c = lambda {|*| }
+
+ parent = ContextState.new ""
+ parent.before(:each, &@c)
+ parent.before(:all, &@c)
+
+ @state = ContextState.new ""
+ @state.parent = parent
+ end
+
+ it "returns before(:each) actions in the order they were defined" do
+ @state.before(:each, &@a)
+ @state.before(:each, &@b)
+ expect(@state.pre(:each)).to eq([@c, @a, @b])
+ end
+
+ it "returns before(:all) actions in the order they were defined" do
+ @state.before(:all, &@a)
+ @state.before(:all, &@b)
+ expect(@state.pre(:all)).to eq([@c, @a, @b])
+ end
+end
+
+RSpec.describe ContextState, "#post" do
+ before :each do
+ @a = lambda {|*| }
+ @b = lambda {|*| }
+ @c = lambda {|*| }
+
+ parent = ContextState.new ""
+ parent.after(:each, &@c)
+ parent.after(:all, &@c)
+
+ @state = ContextState.new ""
+ @state.parent = parent
+ end
+
+ it "returns after(:each) actions in the reverse order they were defined" do
+ @state.after(:each, &@a)
+ @state.after(:each, &@b)
+ expect(@state.post(:each)).to eq([@b, @a, @c])
+ end
+
+ it "returns after(:all) actions in the reverse order they were defined" do
+ @state.after(:all, &@a)
+ @state.after(:all, &@b)
+ expect(@state.post(:all)).to eq([@b, @a, @c])
+ end
+end
+
+RSpec.describe ContextState, "#protect" do
+ before :each do
+ ScratchPad.record []
+ @a = lambda {|*| ScratchPad << :a }
+ @b = lambda {|*| ScratchPad << :b }
+ @c = lambda {|*| raise Exception, "Fail!" }
+ end
+
+ it "returns true and does execute any blocks if check and MSpec.mode?(:pretend) are true" do
+ expect(MSpec).to receive(:mode?).with(:pretend).and_return(true)
+ expect(ContextState.new("").protect("message", [@a, @b])).to be_truthy
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "executes the blocks if MSpec.mode?(:pretend) is false" do
+ expect(MSpec).to receive(:mode?).with(:pretend).and_return(false)
+ ContextState.new("").protect("message", [@a, @b])
+ expect(ScratchPad.recorded).to eq([:a, :b])
+ end
+
+ it "executes the blocks if check is false" do
+ ContextState.new("").protect("message", [@a, @b], false)
+ expect(ScratchPad.recorded).to eq([:a, :b])
+ end
+
+ it "returns true if none of the blocks raise an exception" do
+ expect(ContextState.new("").protect("message", [@a, @b])).to be_truthy
+ end
+
+ it "returns false if any of the blocks raise an exception" do
+ expect(ContextState.new("").protect("message", [@a, @c, @b])).to be_falsey
+ end
+end
+
+RSpec.describe ContextState, "#parent=" do
+ before :each do
+ @state = ContextState.new ""
+ @parent = double("describe")
+ allow(@parent).to receive(:parent).and_return(nil)
+ allow(@parent).to receive(:child)
+ end
+
+ it "does not set self as a child of parent if shared" do
+ expect(@parent).not_to receive(:child)
+ state = ContextState.new "", :shared => true
+ state.parent = @parent
+ end
+
+ it "does not set parents if shared" do
+ state = ContextState.new "", :shared => true
+ state.parent = @parent
+ expect(state.parents).to eq([state])
+ end
+
+ it "sets self as a child of parent" do
+ expect(@parent).to receive(:child).with(@state)
+ @state.parent = @parent
+ end
+
+ it "creates the list of parents" do
+ @state.parent = @parent
+ expect(@state.parents).to eq([@parent, @state])
+ end
+end
+
+RSpec.describe ContextState, "#parent" do
+ before :each do
+ @state = ContextState.new ""
+ @parent = double("describe")
+ allow(@parent).to receive(:parent).and_return(nil)
+ allow(@parent).to receive(:child)
+ end
+
+ it "returns nil if parent has not been set" do
+ expect(@state.parent).to be_nil
+ end
+
+ it "returns the parent" do
+ @state.parent = @parent
+ expect(@state.parent).to eq(@parent)
+ end
+end
+
+RSpec.describe ContextState, "#parents" do
+ before :each do
+ @first = ContextState.new ""
+ @second = ContextState.new ""
+ @parent = double("describe")
+ allow(@parent).to receive(:parent).and_return(nil)
+ allow(@parent).to receive(:child)
+ end
+
+ it "returns a list of all enclosing ContextState instances" do
+ @first.parent = @parent
+ @second.parent = @first
+ expect(@second.parents).to eq([@parent, @first, @second])
+ end
+end
+
+RSpec.describe ContextState, "#child" do
+ before :each do
+ @first = ContextState.new ""
+ @second = ContextState.new ""
+ @parent = double("describe")
+ allow(@parent).to receive(:parent).and_return(nil)
+ allow(@parent).to receive(:child)
+ end
+
+ it "adds the ContextState to the list of contained ContextStates" do
+ @first.child @second
+ expect(@first.children).to eq([@second])
+ end
+end
+
+RSpec.describe ContextState, "#children" do
+ before :each do
+ @parent = ContextState.new ""
+ @first = ContextState.new ""
+ @second = ContextState.new ""
+ end
+
+ it "returns the list of directly contained ContextStates" do
+ @first.parent = @parent
+ @second.parent = @first
+ expect(@parent.children).to eq([@first])
+ expect(@first.children).to eq([@second])
+ end
+end
+
+RSpec.describe ContextState, "#state" do
+ before :each do
+ MSpec.store :before, []
+ MSpec.store :after, []
+
+ @state = ContextState.new ""
+ end
+
+ it "returns nil if no spec is being executed" do
+ expect(@state.state).to eq(nil)
+ end
+
+ it "returns a ExampleState instance if an example is being executed" do
+ ScratchPad.record @state
+ @state.describe { }
+ @state.it("") { ScratchPad.record ScratchPad.recorded.state }
+ @state.process
+ expect(@state.state).to eq(nil)
+ expect(ScratchPad.recorded).to be_kind_of(ExampleState)
+ end
+end
+
+RSpec.describe ContextState, "#process" do
+ before :each do
+ MSpec.store :before, []
+ MSpec.store :after, []
+ allow(MSpec).to receive(:register_current)
+
+ @state = ContextState.new ""
+ @state.describe { }
+
+ @a = lambda {|*| ScratchPad << :a }
+ @b = lambda {|*| ScratchPad << :b }
+ ScratchPad.record []
+ end
+
+ it "calls each before(:all) block" do
+ @state.before(:all, &@a)
+ @state.before(:all, &@b)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([:a, :b])
+ end
+
+ it "calls each after(:all) block" do
+ @state.after(:all, &@a)
+ @state.after(:all, &@b)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([:b, :a])
+ end
+
+ it "calls each it block" do
+ @state.it("one", &@a)
+ @state.it("two", &@b)
+ @state.process
+ expect(ScratchPad.recorded).to eq([:a, :b])
+ end
+
+ it "does not call the #it block if #filtered? returns true" do
+ @state.it("one", &@a)
+ @state.it("two", &@b)
+ allow(@state.examples.first).to receive(:filtered?).and_return(true)
+ @state.process
+ expect(ScratchPad.recorded).to eq([:b])
+ end
+
+ it "calls each before(:each) block" do
+ @state.before(:each, &@a)
+ @state.before(:each, &@b)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([:a, :b])
+ end
+
+ it "calls each after(:each) block" do
+ @state.after(:each, &@a)
+ @state.after(:each, &@b)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([:b, :a])
+ end
+
+ it "calls Mock.cleanup for each it block" do
+ @state.it("") { }
+ @state.it("") { }
+ expect(Mock).to receive(:cleanup).twice
+ @state.process
+ end
+
+ it "calls Mock.verify_count for each it block" do
+ @state.it("") { }
+ @state.it("") { }
+ expect(Mock).to receive(:verify_count).twice
+ @state.process
+ end
+
+ it "calls the describe block" do
+ ScratchPad.record []
+ @state.describe { ScratchPad << :a }
+ @state.process
+ expect(ScratchPad.recorded).to eq([:a])
+ end
+
+ it "creates a new ExampleState instance for each example" do
+ ScratchPad.record @state
+ @state.describe { }
+ @state.it("it") { ScratchPad.record ScratchPad.recorded.state }
+ @state.process
+ expect(ScratchPad.recorded).to be_kind_of(ExampleState)
+ end
+
+ it "clears the expectations flag before evaluating the #it block" do
+ MSpec.clear_expectations
+ expect(MSpec).to receive(:clear_expectations)
+ @state.it("it") { ScratchPad.record MSpec.expectation? }
+ @state.process
+ expect(ScratchPad.recorded).to be_falsey
+ end
+
+ it "shuffles the spec list if MSpec.randomize? is true" do
+ MSpec.randomize = true
+ begin
+ expect(MSpec).to receive(:shuffle)
+ @state.it("") { }
+ @state.process
+ ensure
+ MSpec.randomize = false
+ end
+ end
+
+ it "sets the current MSpec ContextState" do
+ expect(MSpec).to receive(:register_current).with(@state)
+ @state.process
+ end
+
+ it "resets the current MSpec ContextState to nil when there are examples" do
+ expect(MSpec).to receive(:register_current).with(nil)
+ @state.it("") { }
+ @state.process
+ end
+
+ it "resets the current MSpec ContextState to nil when there are no examples" do
+ expect(MSpec).to receive(:register_current).with(nil)
+ @state.process
+ end
+
+ it "call #process on children when there are examples" do
+ child = ContextState.new ""
+ expect(child).to receive(:process)
+ @state.child child
+ @state.it("") { }
+ @state.process
+ end
+
+ it "call #process on children when there are no examples" do
+ child = ContextState.new ""
+ expect(child).to receive(:process)
+ @state.child child
+ @state.process
+ end
+end
+
+RSpec.describe ContextState, "#process" do
+ before :each do
+ MSpec.store :exception, []
+
+ @state = ContextState.new ""
+ @state.describe { }
+
+ action = double("action")
+ def action.exception(exc)
+ ScratchPad.record :exception if exc.exception.is_a? SpecExpectationNotFoundError
+ end
+ MSpec.register :exception, action
+
+ MSpec.clear_expectations
+ ScratchPad.clear
+ end
+
+ after :each do
+ MSpec.store :exception, nil
+ end
+
+ it "raises an SpecExpectationNotFoundError if an #it block does not contain an expectation" do
+ @state.it("it") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq(:exception)
+ end
+
+ it "does not raise an SpecExpectationNotFoundError if an #it block does contain an expectation" do
+ @state.it("it") { MSpec.expectation }
+ @state.process
+ expect(ScratchPad.recorded).to be_nil
+ end
+
+ it "does not raise an SpecExpectationNotFoundError if the #it block causes a failure" do
+ @state.it("it") { raise Exception, "Failed!" }
+ @state.process
+ expect(ScratchPad.recorded).to be_nil
+ end
+end
+
+RSpec.describe ContextState, "#process" do
+ before :each do
+ MSpec.store :example, []
+
+ @state = ContextState.new ""
+ @state.describe { }
+
+ example = double("example")
+ def example.example(state, spec)
+ ScratchPad << state << spec
+ end
+ MSpec.register :example, example
+
+ ScratchPad.record []
+ end
+
+ after :each do
+ MSpec.store :example, nil
+ end
+
+ it "calls registered :example actions with the current ExampleState and block" do
+ @state.it("") { MSpec.expectation }
+ @state.process
+
+ expect(ScratchPad.recorded.first).to be_kind_of(ExampleState)
+ expect(ScratchPad.recorded.last).to be_kind_of(Proc)
+ end
+
+ it "does not call registered example actions if the example has no block" do
+ @state.it("empty example")
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+end
+
+RSpec.describe ContextState, "#process" do
+ before :each do
+ MSpec.store :before, []
+ MSpec.store :after, []
+
+ @state = ContextState.new ""
+ @state.describe { }
+ @state.it("") { MSpec.expectation }
+ end
+
+ after :each do
+ MSpec.store :before, nil
+ MSpec.store :after, nil
+ end
+
+ it "calls registered :before actions with the current ExampleState instance" do
+ before = double("before")
+ expect(before).to receive(:before) {
+ ScratchPad.record :before
+ @spec_state = @state.state
+ }
+ MSpec.register :before, before
+ @state.process
+ expect(ScratchPad.recorded).to eq(:before)
+ expect(@spec_state).to be_kind_of(ExampleState)
+ end
+
+ it "calls registered :after actions with the current ExampleState instance" do
+ after = double("after")
+ expect(after).to receive(:after) {
+ ScratchPad.record :after
+ @spec_state = @state.state
+ }
+ MSpec.register :after, after
+ @state.process
+ expect(ScratchPad.recorded).to eq(:after)
+ expect(@spec_state).to be_kind_of(ExampleState)
+ end
+end
+
+RSpec.describe ContextState, "#process" do
+ before :each do
+ MSpec.store :enter, []
+ MSpec.store :leave, []
+
+ @state = ContextState.new "C#m"
+ @state.describe { }
+ @state.it("") { MSpec.expectation }
+ end
+
+ after :each do
+ MSpec.store :enter, nil
+ MSpec.store :leave, nil
+ end
+
+ it "calls registered :enter actions with the current #describe string" do
+ enter = double("enter")
+ expect(enter).to receive(:enter).with("C#m") { ScratchPad.record :enter }
+ MSpec.register :enter, enter
+ @state.process
+ expect(ScratchPad.recorded).to eq(:enter)
+ end
+
+ it "calls registered :leave actions" do
+ leave = double("leave")
+ expect(leave).to receive(:leave) { ScratchPad.record :leave }
+ MSpec.register :leave, leave
+ @state.process
+ expect(ScratchPad.recorded).to eq(:leave)
+ end
+end
+
+RSpec.describe ContextState, "#process when an exception is raised in before(:all)" do
+ before :each do
+ MSpec.store :before, []
+ MSpec.store :after, []
+
+ @state = ContextState.new ""
+ @state.describe { }
+
+ @a = lambda {|*| ScratchPad << :a }
+ @b = lambda {|*| ScratchPad << :b }
+ ScratchPad.record []
+
+ @state.before(:all) { raise Exception, "Fail!" }
+ end
+
+ after :each do
+ MSpec.store :before, nil
+ MSpec.store :after, nil
+ end
+
+ it "does not call before(:each)" do
+ @state.before(:each, &@a)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "does not call the it block" do
+ @state.it("one", &@a)
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "does not call after(:each)" do
+ @state.after(:each, &@a)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "does not call after(:each)" do
+ @state.after(:all, &@a)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "does not call Mock.verify_count" do
+ @state.it("") { }
+ expect(Mock).not_to receive(:verify_count)
+ @state.process
+ end
+
+ it "calls Mock.cleanup" do
+ @state.it("") { }
+ expect(Mock).to receive(:cleanup)
+ @state.process
+ end
+end
+
+RSpec.describe ContextState, "#process when an exception is raised in before(:each)" do
+ before :each do
+ MSpec.store :before, []
+ MSpec.store :after, []
+
+ @state = ContextState.new ""
+ @state.describe { }
+
+ @a = lambda {|*| ScratchPad << :a }
+ @b = lambda {|*| ScratchPad << :b }
+ ScratchPad.record []
+
+ @state.before(:each) { raise Exception, "Fail!" }
+ end
+
+ after :each do
+ MSpec.store :before, nil
+ MSpec.store :after, nil
+ end
+
+ it "does not call the it block" do
+ @state.it("one", &@a)
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "calls after(:each)" do
+ @state.after(:each, &@a)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([:a])
+ end
+
+ it "calls Mock.verify_count" do
+ @state.it("") { }
+ expect(Mock).to receive(:verify_count)
+ @state.process
+ end
+end
+
+RSpec.describe ContextState, "#process in pretend mode" do
+ before :all do
+ MSpec.register_mode :pretend
+ end
+
+ after :all do
+ MSpec.clear_modes
+ end
+
+ before :each do
+ ScratchPad.clear
+ MSpec.store :before, []
+ MSpec.store :after, []
+
+ @state = ContextState.new ""
+ @state.describe { }
+ @state.it("") { }
+ end
+
+ after :each do
+ MSpec.store :before, nil
+ MSpec.store :after, nil
+ end
+
+ it "calls registered :before actions with the current ExampleState instance" do
+ before = double("before")
+ expect(before).to receive(:before) {
+ ScratchPad.record :before
+ @spec_state = @state.state
+ }
+ MSpec.register :before, before
+ @state.process
+ expect(ScratchPad.recorded).to eq(:before)
+ expect(@spec_state).to be_kind_of(ExampleState)
+ end
+
+ it "calls registered :after actions with the current ExampleState instance" do
+ after = double("after")
+ expect(after).to receive(:after) {
+ ScratchPad.record :after
+ @spec_state = @state.state
+ }
+ MSpec.register :after, after
+ @state.process
+ expect(ScratchPad.recorded).to eq(:after)
+ expect(@spec_state).to be_kind_of(ExampleState)
+ end
+end
+
+RSpec.describe ContextState, "#process in pretend mode" do
+ before :all do
+ MSpec.register_mode :pretend
+ end
+
+ after :all do
+ MSpec.clear_modes
+ end
+
+ before :each do
+ MSpec.store :before, []
+ MSpec.store :after, []
+
+ @state = ContextState.new ""
+ @state.describe { }
+
+ @a = lambda {|*| ScratchPad << :a }
+ @b = lambda {|*| ScratchPad << :b }
+ ScratchPad.record []
+ end
+
+ it "calls the describe block" do
+ ScratchPad.record []
+ @state.describe { ScratchPad << :a }
+ @state.process
+ expect(ScratchPad.recorded).to eq([:a])
+ end
+
+ it "does not call any before(:all) block" do
+ @state.before(:all, &@a)
+ @state.before(:all, &@b)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "does not call any after(:all) block" do
+ @state.after(:all, &@a)
+ @state.after(:all, &@b)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "does not call any it block" do
+ @state.it("one", &@a)
+ @state.it("two", &@b)
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "does not call any before(:each) block" do
+ @state.before(:each, &@a)
+ @state.before(:each, &@b)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "does not call any after(:each) block" do
+ @state.after(:each, &@a)
+ @state.after(:each, &@b)
+ @state.it("") { }
+ @state.process
+ expect(ScratchPad.recorded).to eq([])
+ end
+
+ it "does not call Mock.cleanup" do
+ @state.it("") { }
+ @state.it("") { }
+ expect(Mock).not_to receive(:cleanup)
+ @state.process
+ end
+end
+
+RSpec.describe ContextState, "#process in pretend mode" do
+ before :all do
+ MSpec.register_mode :pretend
+ end
+
+ after :all do
+ MSpec.clear_modes
+ end
+
+ before :each do
+ MSpec.store :enter, []
+ MSpec.store :leave, []
+
+ @state = ContextState.new ""
+ @state.describe { }
+ @state.it("") { }
+ end
+
+ after :each do
+ MSpec.store :enter, nil
+ MSpec.store :leave, nil
+ end
+
+ it "calls registered :enter actions with the current #describe string" do
+ enter = double("enter")
+ expect(enter).to receive(:enter) { ScratchPad.record :enter }
+ MSpec.register :enter, enter
+ @state.process
+ expect(ScratchPad.recorded).to eq(:enter)
+ end
+
+ it "calls registered :leave actions" do
+ leave = double("leave")
+ expect(leave).to receive(:leave) { ScratchPad.record :leave }
+ MSpec.register :leave, leave
+ @state.process
+ expect(ScratchPad.recorded).to eq(:leave)
+ end
+end
+
+RSpec.describe ContextState, "#it_should_behave_like" do
+ before :each do
+ @shared_desc = :shared_context
+ @shared = ContextState.new(@shared_desc, :shared => true)
+ allow(MSpec).to receive(:retrieve_shared).and_return(@shared)
+
+ @state = ContextState.new "Top level"
+ @a = lambda {|*| }
+ @b = lambda {|*| }
+ end
+
+ it "raises an Exception if unable to find the shared ContextState" do
+ expect(MSpec).to receive(:retrieve_shared).and_return(nil)
+ expect { @state.it_should_behave_like "this" }.to raise_error(Exception)
+ end
+
+ describe "for nested ContextState instances" do
+ before :each do
+ @nested = ContextState.new "nested context"
+ @nested.parents.unshift @shared
+
+ @shared.children << @nested
+
+ @nested_dup = @nested.dup
+ allow(@nested).to receive(:dup).and_return(@nested_dup)
+ end
+
+ it "duplicates the nested ContextState" do
+ @state.it_should_behave_like @shared_desc
+ expect(@state.children.first).to equal(@nested_dup)
+ end
+
+ it "sets the parent of the nested ContextState to the containing ContextState" do
+ @state.it_should_behave_like @shared_desc
+ expect(@nested_dup.parent).to equal(@state)
+ end
+
+ it "sets the context for nested examples to the nested ContextState's dup" do
+ @shared.it "an example", &@a
+ @shared.it "another example", &@b
+ @state.it_should_behave_like @shared_desc
+ @nested_dup.examples.each { |x| expect(x.context).to equal(@nested_dup) }
+ end
+
+ it "omits the shored ContextState's description" do
+ @nested.it "an example", &@a
+ @nested.it "another example", &@b
+ @state.it_should_behave_like @shared_desc
+
+ expect(@nested_dup.description).to eq("Top level nested context")
+ expect(@nested_dup.examples.first.description).to eq("Top level nested context an example")
+ expect(@nested_dup.examples.last.description).to eq("Top level nested context another example")
+ end
+ end
+
+ it "adds duped examples from the shared ContextState" do
+ @shared.it "some method", &@a
+ ex_dup = @shared.examples.first.dup
+ allow(@shared.examples.first).to receive(:dup).and_return(ex_dup)
+
+ @state.it_should_behave_like @shared_desc
+ expect(@state.examples).to eq([ex_dup])
+ end
+
+ it "sets the context for examples to the containing ContextState" do
+ @shared.it "an example", &@a
+ @shared.it "another example", &@b
+ @state.it_should_behave_like @shared_desc
+ @state.examples.each { |x| expect(x.context).to equal(@state) }
+ end
+
+ it "adds before(:all) blocks from the shared ContextState" do
+ @shared.before :all, &@a
+ @shared.before :all, &@b
+ @state.it_should_behave_like @shared_desc
+ expect(@state.before(:all)).to include(*@shared.before(:all))
+ end
+
+ it "adds before(:each) blocks from the shared ContextState" do
+ @shared.before :each, &@a
+ @shared.before :each, &@b
+ @state.it_should_behave_like @shared_desc
+ expect(@state.before(:each)).to include(*@shared.before(:each))
+ end
+
+ it "adds after(:each) blocks from the shared ContextState" do
+ @shared.after :each, &@a
+ @shared.after :each, &@b
+ @state.it_should_behave_like @shared_desc
+ expect(@state.after(:each)).to include(*@shared.after(:each))
+ end
+
+ it "adds after(:all) blocks from the shared ContextState" do
+ @shared.after :all, &@a
+ @shared.after :all, &@b
+ @state.it_should_behave_like @shared_desc
+ expect(@state.after(:all)).to include(*@shared.after(:all))
+ end
+end
+
+RSpec.describe ContextState, "#filter_examples" do
+ before :each do
+ @state = ContextState.new ""
+ @state.it("one") { }
+ @state.it("two") { }
+ end
+
+ it "removes examples that are filtered" do
+ allow(@state.examples.first).to receive(:filtered?).and_return(true)
+ expect(@state.examples.size).to eq(2)
+ @state.filter_examples
+ expect(@state.examples.size).to eq(1)
+ end
+
+ it "returns true if there are remaining examples to evaluate" do
+ allow(@state.examples.first).to receive(:filtered?).and_return(true)
+ expect(@state.filter_examples).to be_truthy
+ end
+
+ it "returns false if there are no remaining examples to evaluate" do
+ allow(@state.examples.first).to receive(:filtered?).and_return(true)
+ allow(@state.examples.last).to receive(:filtered?).and_return(true)
+ expect(@state.filter_examples).to be_falsey
+ end
+end
diff --git a/spec/mspec/spec/runner/example_spec.rb b/spec/mspec/spec/runner/example_spec.rb
new file mode 100644
index 0000000000..8bac166da8
--- /dev/null
+++ b/spec/mspec/spec/runner/example_spec.rb
@@ -0,0 +1,117 @@
+require 'spec_helper'
+require 'mspec/matchers/base'
+require 'mspec/runner/mspec'
+require 'mspec/mocks/mock'
+require 'mspec/runner/example'
+
+RSpec.describe ExampleState do
+ it "is initialized with the ContextState, #it string, and #it block" do
+ prc = lambda { }
+ context = ContextState.new ""
+ expect(ExampleState.new(context, "does", prc)).to be_kind_of(ExampleState)
+ end
+end
+
+RSpec.describe ExampleState, "#describe" do
+ before :each do
+ @context = ContextState.new "Object#to_s"
+ @state = ExampleState.new @context, "it"
+ end
+
+ it "returns the ContextState#description" do
+ expect(@state.describe).to eq(@context.description)
+ end
+end
+
+RSpec.describe ExampleState, "#it" do
+ before :each do
+ @state = ExampleState.new ContextState.new("describe"), "it"
+ end
+
+ it "returns the argument to the #it block" do
+ expect(@state.it).to eq("it")
+ end
+end
+
+RSpec.describe ExampleState, "#context=" do
+ before :each do
+ @state = ExampleState.new ContextState.new("describe"), "it"
+ @context = ContextState.new "New#context"
+ end
+
+ it "sets the containing ContextState" do
+ @state.context = @context
+ expect(@state.context).to eq(@context)
+ end
+
+ it "resets the description" do
+ expect(@state.description).to eq("describe it")
+ @state.context = @context
+ expect(@state.description).to eq("New#context it")
+ end
+end
+
+RSpec.describe ExampleState, "#example" do
+ before :each do
+ @proc = lambda { }
+ @state = ExampleState.new ContextState.new("describe"), "it", @proc
+ end
+
+ it "returns the #it block" do
+ expect(@state.example).to eq(@proc)
+ end
+end
+
+RSpec.describe ExampleState, "#filtered?" do
+ before :each do
+ MSpec.store :include, []
+ MSpec.store :exclude, []
+
+ @state = ExampleState.new ContextState.new("describe"), "it"
+ @filter = double("filter")
+ end
+
+ after :each do
+ MSpec.store :include, []
+ MSpec.store :exclude, []
+ end
+
+ it "returns false if MSpec include filters list is empty" do
+ expect(@state.filtered?).to eq(false)
+ end
+
+ it "returns false if MSpec include filters match this spec" do
+ expect(@filter).to receive(:===).and_return(true)
+ MSpec.register :include, @filter
+ expect(@state.filtered?).to eq(false)
+ end
+
+ it "returns true if MSpec include filters do not match this spec" do
+ expect(@filter).to receive(:===).and_return(false)
+ MSpec.register :include, @filter
+ expect(@state.filtered?).to eq(true)
+ end
+
+ it "returns false if MSpec exclude filters list is empty" do
+ expect(@state.filtered?).to eq(false)
+ end
+
+ it "returns false if MSpec exclude filters do not match this spec" do
+ expect(@filter).to receive(:===).and_return(false)
+ MSpec.register :exclude, @filter
+ expect(@state.filtered?).to eq(false)
+ end
+
+ it "returns true if MSpec exclude filters match this spec" do
+ expect(@filter).to receive(:===).and_return(true)
+ MSpec.register :exclude, @filter
+ expect(@state.filtered?).to eq(true)
+ end
+
+ it "returns true if MSpec include and exclude filters match this spec" do
+ expect(@filter).to receive(:===).twice.and_return(true)
+ MSpec.register :include, @filter
+ MSpec.register :exclude, @filter
+ expect(@state.filtered?).to eq(true)
+ end
+end
diff --git a/spec/mspec/spec/runner/exception_spec.rb b/spec/mspec/spec/runner/exception_spec.rb
new file mode 100644
index 0000000000..a77a2c9cf4
--- /dev/null
+++ b/spec/mspec/spec/runner/exception_spec.rb
@@ -0,0 +1,146 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/runner/example'
+require 'mspec/runner/exception'
+require 'mspec/utils/script'
+
+RSpec.describe ExceptionState, "#initialize" do
+ it "takes a state, location (e.g. before :each), and exception" do
+ context = ContextState.new "Class#method"
+ state = ExampleState.new context, "does something"
+ exc = Exception.new "Fail!"
+ expect(ExceptionState.new(state, "location", exc)).to be_kind_of(ExceptionState)
+ end
+end
+
+RSpec.describe ExceptionState, "#description" do
+ before :each do
+ context = ContextState.new "Class#method"
+ @state = ExampleState.new context, "does something"
+ end
+
+ it "returns the state description if state was not nil" do
+ exc = ExceptionState.new(@state, nil, nil)
+ expect(exc.description).to eq("Class#method does something")
+ end
+
+ it "returns the location if it is not nil and description is nil" do
+ exc = ExceptionState.new(nil, "location", nil)
+ expect(exc.description).to eq("An exception occurred during: location")
+ end
+
+ it "returns both description and location if neither are nil" do
+ exc = ExceptionState.new(@state, "location", nil)
+ expect(exc.description).to eq("An exception occurred during: location\nClass#method does something")
+ end
+end
+
+RSpec.describe ExceptionState, "#describe" do
+ before :each do
+ context = ContextState.new "Class#method"
+ @state = ExampleState.new context, "does something"
+ end
+
+ it "returns the ExampleState#describe string if created with a non-nil state" do
+ expect(ExceptionState.new(@state, nil, nil).describe).to eq(@state.describe)
+ end
+
+ it "returns an empty string if created with a nil state" do
+ expect(ExceptionState.new(nil, nil, nil).describe).to eq("")
+ end
+end
+
+RSpec.describe ExceptionState, "#it" do
+ before :each do
+ context = ContextState.new "Class#method"
+ @state = ExampleState.new context, "does something"
+ end
+
+ it "returns the ExampleState#it string if created with a non-nil state" do
+ expect(ExceptionState.new(@state, nil, nil).it).to eq(@state.it)
+ end
+
+ it "returns an empty string if created with a nil state" do
+ expect(ExceptionState.new(nil, nil, nil).it).to eq("")
+ end
+end
+
+RSpec.describe ExceptionState, "#failure?" do
+ before :each do
+ @state = ExampleState.new ContextState.new("C#m"), "works"
+ end
+
+ it "returns true if the exception is an SpecExpectationNotMetError" do
+ exc = ExceptionState.new @state, "", SpecExpectationNotMetError.new("Fail!")
+ expect(exc.failure?).to be_truthy
+ end
+
+ it "returns true if the exception is an SpecExpectationNotFoundError" do
+ exc = ExceptionState.new @state, "", SpecExpectationNotFoundError.new("Fail!")
+ expect(exc.failure?).to be_truthy
+ end
+
+ it "returns false if the exception is not an SpecExpectationNotMetError or an SpecExpectationNotFoundError" do
+ exc = ExceptionState.new @state, "", Exception.new("Fail!")
+ expect(exc.failure?).to be_falsey
+ end
+end
+
+RSpec.describe ExceptionState, "#message" do
+ before :each do
+ @state = ExampleState.new ContextState.new("C#m"), "works"
+ end
+
+ it "returns <No message> if the exception message is empty" do
+ exc = ExceptionState.new @state, "", Exception.new("")
+ expect(exc.message).to eq("Exception: <No message>")
+ end
+
+ it "returns the message without exception class when the exception is an SpecExpectationNotMetError" do
+ exc = ExceptionState.new @state, "", SpecExpectationNotMetError.new("Fail!")
+ expect(exc.message).to eq("Fail!")
+ end
+
+ it "returns SpecExpectationNotFoundError#message when the exception is an SpecExpectationNotFoundError" do
+ e = SpecExpectationNotFoundError.new
+ exc = ExceptionState.new @state, "", e
+ expect(exc.message).to eq(e.message)
+ end
+
+ it "returns the message with exception class when the exception is not an SpecExpectationNotMetError or an SpecExpectationNotFoundError" do
+ exc = ExceptionState.new @state, "", Exception.new("Fail!")
+ expect(exc.message).to eq("Exception: Fail!")
+ end
+end
+
+RSpec.describe ExceptionState, "#backtrace" do
+ before :each do
+ @state = ExampleState.new ContextState.new("C#m"), "works"
+ begin
+ raise Exception
+ rescue Exception => @exception
+ @exc = ExceptionState.new @state, "", @exception
+ end
+ end
+
+ after :each do
+ $MSPEC_DEBUG = nil
+ end
+
+ it "returns a string representation of the exception backtrace" do
+ expect(@exc.backtrace).to be_kind_of(String)
+ end
+
+ it "does not filter files from the backtrace if $MSPEC_DEBUG is true" do
+ $MSPEC_DEBUG = true
+ expect(@exc.backtrace).to eq(@exception.backtrace.join("\n"))
+ end
+
+ it "filters files matching config[:backtrace_filter]" do
+ MSpecScript.set :backtrace_filter, %r[mspec/lib]
+ $MSPEC_DEBUG = nil
+ @exc.backtrace.split("\n").each do |line|
+ expect(line).not_to match(%r[mspec/lib])
+ end
+ end
+end
diff --git a/spec/mspec/spec/runner/filters/a.yaml b/spec/mspec/spec/runner/filters/a.yaml
new file mode 100644
index 0000000000..1940e3cba6
--- /dev/null
+++ b/spec/mspec/spec/runner/filters/a.yaml
@@ -0,0 +1,4 @@
+---
+A#:
+- a
+- aa
diff --git a/spec/mspec/spec/runner/filters/b.yaml b/spec/mspec/spec/runner/filters/b.yaml
new file mode 100644
index 0000000000..a24bdb2f19
--- /dev/null
+++ b/spec/mspec/spec/runner/filters/b.yaml
@@ -0,0 +1,11 @@
+---
+B.:
+- b
+- bb
+B::C#:
+- b!
+- b=
+- b?
+- "-"
+- "[]"
+- "[]="
diff --git a/spec/mspec/spec/runner/filters/match_spec.rb b/spec/mspec/spec/runner/filters/match_spec.rb
new file mode 100644
index 0000000000..970da00446
--- /dev/null
+++ b/spec/mspec/spec/runner/filters/match_spec.rb
@@ -0,0 +1,34 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters/match'
+
+RSpec.describe MatchFilter, "#===" do
+ before :each do
+ @filter = MatchFilter.new nil, 'a', 'b', 'c'
+ end
+
+ it "returns true if the argument matches any of the #initialize strings" do
+ expect(@filter.===('aaa')).to eq(true)
+ expect(@filter.===('bccb')).to eq(true)
+ end
+
+ it "returns false if the argument matches none of the #initialize strings" do
+ expect(@filter.===('d')).to eq(false)
+ end
+end
+
+RSpec.describe MatchFilter, "#register" do
+ it "registers itself with MSpec for the designated action list" do
+ filter = MatchFilter.new :include
+ expect(MSpec).to receive(:register).with(:include, filter)
+ filter.register
+ end
+end
+
+RSpec.describe MatchFilter, "#unregister" do
+ it "unregisters itself with MSpec for the designated action list" do
+ filter = MatchFilter.new :exclude
+ expect(MSpec).to receive(:unregister).with(:exclude, filter)
+ filter.unregister
+ end
+end
diff --git a/spec/mspec/spec/runner/filters/profile_spec.rb b/spec/mspec/spec/runner/filters/profile_spec.rb
new file mode 100644
index 0000000000..25f5e07aef
--- /dev/null
+++ b/spec/mspec/spec/runner/filters/profile_spec.rb
@@ -0,0 +1,117 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters/profile'
+
+RSpec.describe ProfileFilter, "#find" do
+ before :each do
+ @filter = ProfileFilter.new nil
+ allow(File).to receive(:exist?).and_return(false)
+ @file = "rails.yaml"
+ end
+
+ it "attempts to locate the file through the expanded path name" do
+ expect(File).to receive(:expand_path).with(@file).and_return(@file)
+ expect(File).to receive(:exist?).with(@file).and_return(true)
+ expect(@filter.find(@file)).to eq(@file)
+ end
+
+ it "attempts to locate the file in 'spec/profiles'" do
+ path = File.join "spec/profiles", @file
+ expect(File).to receive(:exist?).with(path).and_return(true)
+ expect(@filter.find(@file)).to eq(path)
+ end
+
+ it "attempts to locate the file in 'spec'" do
+ path = File.join "spec", @file
+ expect(File).to receive(:exist?).with(path).and_return(true)
+ expect(@filter.find(@file)).to eq(path)
+ end
+
+ it "attempts to locate the file in 'profiles'" do
+ path = File.join "profiles", @file
+ expect(File).to receive(:exist?).with(path).and_return(true)
+ expect(@filter.find(@file)).to eq(path)
+ end
+
+ it "attempts to locate the file in '.'" do
+ path = File.join ".", @file
+ expect(File).to receive(:exist?).with(path).and_return(true)
+ expect(@filter.find(@file)).to eq(path)
+ end
+end
+
+RSpec.describe ProfileFilter, "#parse" do
+ before :each do
+ @filter = ProfileFilter.new nil
+ @file = File.open(File.dirname(__FILE__) + "/b.yaml", "r")
+ end
+
+ after :each do
+ @file.close
+ end
+
+ it "creates a Hash of the contents of the YAML file" do
+ expect(@filter.parse(@file)).to eq({
+ "B." => ["b", "bb"],
+ "B::C#" => ["b!", "b=", "b?", "-", "[]", "[]="]
+ })
+ end
+end
+
+RSpec.describe ProfileFilter, "#load" do
+ before :each do
+ @filter = ProfileFilter.new nil
+ @files = [
+ File.dirname(__FILE__) + "/a.yaml",
+ File.dirname(__FILE__) + "/b.yaml"
+ ]
+ end
+
+ it "generates a composite hash from multiple YAML files" do
+ expect(@filter.load(*@files)).to eq({
+ "A#" => ["a", "aa"],
+ "B." => ["b", "bb"],
+ "B::C#" => ["b!", "b=", "b?", "-", "[]", "[]="]
+ })
+ end
+end
+
+RSpec.describe ProfileFilter, "#===" do
+ before :each do
+ @filter = ProfileFilter.new nil
+ allow(@filter).to receive(:load).and_return({ "A#" => ["[]=", "a", "a!", "a?", "aa="]})
+ @filter.send :initialize, nil
+ end
+
+ it "returns true if the spec description is for a method in the profile" do
+ expect(@filter.===("The A#[]= method")).to eq(true)
+ expect(@filter.===("A#a returns")).to eq(true)
+ expect(@filter.===("A#a! replaces")).to eq(true)
+ expect(@filter.===("A#a? returns")).to eq(true)
+ expect(@filter.===("A#aa= raises")).to eq(true)
+ end
+
+ it "returns false if the spec description is for a method not in the profile" do
+ expect(@filter.===("The A#[] method")).to eq(false)
+ expect(@filter.===("B#a returns")).to eq(false)
+ expect(@filter.===("A.a! replaces")).to eq(false)
+ expect(@filter.===("AA#a? returns")).to eq(false)
+ expect(@filter.===("A#aa raises")).to eq(false)
+ end
+end
+
+RSpec.describe ProfileFilter, "#register" do
+ it "registers itself with MSpec for the designated action list" do
+ filter = ProfileFilter.new :include
+ expect(MSpec).to receive(:register).with(:include, filter)
+ filter.register
+ end
+end
+
+RSpec.describe ProfileFilter, "#unregister" do
+ it "unregisters itself with MSpec for the designated action list" do
+ filter = ProfileFilter.new :exclude
+ expect(MSpec).to receive(:unregister).with(:exclude, filter)
+ filter.unregister
+ end
+end
diff --git a/spec/mspec/spec/runner/filters/regexp_spec.rb b/spec/mspec/spec/runner/filters/regexp_spec.rb
new file mode 100644
index 0000000000..1d1d3554f6
--- /dev/null
+++ b/spec/mspec/spec/runner/filters/regexp_spec.rb
@@ -0,0 +1,31 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters/regexp'
+
+RSpec.describe MatchFilter, "#===" do
+ before :each do
+ @filter = RegexpFilter.new nil, 'a(b|c)', 'b[^ab]', 'cc?'
+ end
+
+ it "returns true if the argument matches any of the #initialize strings" do
+ expect(@filter.===('ab')).to eq(true)
+ expect(@filter.===('bc suffix')).to eq(true)
+ expect(@filter.===('prefix cc')).to eq(true)
+ end
+
+ it "returns false if the argument matches none of the #initialize strings" do
+ expect(@filter.===('aa')).to eq(false)
+ expect(@filter.===('ba')).to eq(false)
+ expect(@filter.===('prefix d suffix')).to eq(false)
+ end
+end
+
+RSpec.describe RegexpFilter, "#to_regexp" do
+ before :each do
+ @filter = RegexpFilter.new nil
+ end
+
+ it "converts its arguments to Regexp instances" do
+ expect(@filter.send(:to_regexp, 'a(b|c)', 'b[^ab]', 'cc?')).to eq([/a(b|c)/, /b[^ab]/, /cc?/])
+ end
+end
diff --git a/spec/mspec/spec/runner/filters/tag_spec.rb b/spec/mspec/spec/runner/filters/tag_spec.rb
new file mode 100644
index 0000000000..356175a754
--- /dev/null
+++ b/spec/mspec/spec/runner/filters/tag_spec.rb
@@ -0,0 +1,92 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters/match'
+require 'mspec/runner/filters/tag'
+
+RSpec.describe TagFilter, "#load" do
+ before :each do
+ @match = double("match filter").as_null_object
+ @filter = TagFilter.new :include, "tag", "key"
+ @tag = SpecTag.new "tag(comment):description"
+ allow(MSpec).to receive(:read_tags).and_return([@tag])
+ allow(MSpec).to receive(:register)
+ end
+
+ it "loads tags from the tag file" do
+ expect(MSpec).to receive(:read_tags).with(["tag", "key"]).and_return([])
+ @filter.load
+ end
+
+
+ it "registers itself with MSpec for the :include action" do
+ filter = TagFilter.new(:include)
+ expect(MSpec).to receive(:register).with(:include, filter)
+ filter.load
+ end
+
+ it "registers itself with MSpec for the :exclude action" do
+ filter = TagFilter.new(:exclude)
+ expect(MSpec).to receive(:register).with(:exclude, filter)
+ filter.load
+ end
+end
+
+RSpec.describe TagFilter, "#unload" do
+ before :each do
+ @filter = TagFilter.new :include, "tag", "key"
+ @tag = SpecTag.new "tag(comment):description"
+ allow(MSpec).to receive(:read_tags).and_return([@tag])
+ allow(MSpec).to receive(:register)
+ end
+
+ it "unregisters itself" do
+ @filter.load
+ expect(MSpec).to receive(:unregister).with(:include, @filter)
+ @filter.unload
+ end
+end
+
+RSpec.describe TagFilter, "#register" do
+ before :each do
+ allow(MSpec).to receive(:register)
+ end
+
+ it "registers itself with MSpec for the :load, :unload actions" do
+ filter = TagFilter.new(nil)
+ expect(MSpec).to receive(:register).with(:load, filter)
+ expect(MSpec).to receive(:register).with(:unload, filter)
+ filter.register
+ end
+end
+
+RSpec.describe TagFilter, "#unregister" do
+ before :each do
+ allow(MSpec).to receive(:unregister)
+ end
+
+ it "unregisters itself with MSpec for the :load, :unload actions" do
+ filter = TagFilter.new(nil)
+ expect(MSpec).to receive(:unregister).with(:load, filter)
+ expect(MSpec).to receive(:unregister).with(:unload, filter)
+ filter.unregister
+ end
+end
+
+RSpec.describe TagFilter, "#===" do
+ before :each do
+ @filter = TagFilter.new nil, "tag", "key"
+ @tag = SpecTag.new "tag(comment):description"
+ allow(MSpec).to receive(:read_tags).and_return([@tag])
+ allow(MSpec).to receive(:register)
+ @filter.load
+ end
+
+ it "returns true if the argument matches any of the descriptions" do
+ expect(@filter.===('description')).to eq(true)
+ end
+
+ it "returns false if the argument matches none of the descriptions" do
+ expect(@filter.===('descriptionA')).to eq(false)
+ expect(@filter.===('adescription')).to eq(false)
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/describe_spec.rb b/spec/mspec/spec/runner/formatters/describe_spec.rb
new file mode 100644
index 0000000000..55f497aca6
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/describe_spec.rb
@@ -0,0 +1,67 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/describe'
+require 'mspec/runner/example'
+
+RSpec.describe DescribeFormatter, "#finish" do
+ before :each do
+ allow(MSpec).to receive(:register)
+ allow(MSpec).to receive(:unregister)
+
+ @timer = double("timer").as_null_object
+ allow(TimerAction).to receive(:new).and_return(@timer)
+ allow(@timer).to receive(:format).and_return("Finished in 2.0 seconds")
+
+ $stdout = @out = IOStub.new
+ context = ContextState.new "Class#method"
+ @state = ExampleState.new(context, "runs")
+
+ @formatter = DescribeFormatter.new
+ @formatter.register
+
+ @tally = @formatter.tally
+ @counter = @tally.counter
+
+ @counter.files!
+ @counter.examples!
+ @counter.expectations! 2
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints a summary of elapsed time" do
+ @formatter.finish
+ expect(@out).to match(/^Finished in 2.0 seconds$/)
+ end
+
+ it "prints a tally of counts" do
+ @formatter.finish
+ expect(@out).to match(/^1 file, 1 example, 2 expectations, 0 failures, 0 errors, 0 tagged$/)
+ end
+
+ it "does not print exceptions" do
+ @formatter.finish
+ expect(@out).to eq(%[
+
+Finished in 2.0 seconds
+
+1 file, 1 example, 2 expectations, 0 failures, 0 errors, 0 tagged
+])
+ end
+
+ it "prints a summary of failures and errors for each describe block" do
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+ allow(exc).to receive(:backtrace).and_return("path/to/some/file.rb:35:in method")
+ @formatter.exception exc
+ @formatter.finish
+ expect(@out).to eq(%[
+
+Class#method 0 failures, 1 error
+
+Finished in 2.0 seconds
+
+1 file, 1 example, 2 expectations, 0 failures, 0 errors, 0 tagged
+])
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/dotted_spec.rb b/spec/mspec/spec/runner/formatters/dotted_spec.rb
new file mode 100644
index 0000000000..336b1227ed
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/dotted_spec.rb
@@ -0,0 +1,284 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/dotted'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+require 'mspec/utils/script'
+
+RSpec.describe DottedFormatter, "#initialize" do
+ it "permits zero arguments" do
+ DottedFormatter.new
+ end
+
+ it "accepts one argument" do
+ DottedFormatter.new nil
+ end
+end
+
+RSpec.describe DottedFormatter, "#register" do
+ before :each do
+ @formatter = DottedFormatter.new
+ allow(MSpec).to receive(:register)
+ end
+
+ it "registers self with MSpec for appropriate actions" do
+ expect(MSpec).to receive(:register).with(:exception, @formatter)
+ expect(MSpec).to receive(:register).with(:before, @formatter)
+ expect(MSpec).to receive(:register).with(:after, @formatter)
+ expect(MSpec).to receive(:register).with(:finish, @formatter)
+ @formatter.register
+ end
+
+ it "creates TimerAction and TallyAction" do
+ timer = double("timer")
+ tally = double("tally")
+ expect(timer).to receive(:register)
+ expect(tally).to receive(:register)
+ expect(tally).to receive(:counter)
+ expect(TimerAction).to receive(:new).and_return(timer)
+ expect(TallyAction).to receive(:new).and_return(tally)
+ @formatter.register
+ end
+end
+
+RSpec.describe DottedFormatter, "#print" do
+ before :each do
+ $stdout = IOStub.new
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "writes to $stdout by default" do
+ formatter = DottedFormatter.new
+ formatter.print "begonias"
+ expect($stdout).to eq("begonias")
+ end
+
+ it "writes to the file specified when the formatter was created" do
+ out = IOStub.new
+ expect(File).to receive(:open).with("some/file", "w").and_return(out)
+ formatter = DottedFormatter.new "some/file"
+ formatter.print "begonias"
+ expect(out).to eq("begonias")
+ end
+
+ it "flushes the IO output" do
+ expect($stdout).to receive(:flush)
+ formatter = DottedFormatter.new
+ formatter.print "begonias"
+ end
+end
+
+RSpec.describe DottedFormatter, "#exception" do
+ before :each do
+ @formatter = DottedFormatter.new
+ @failure = ExceptionState.new nil, nil, SpecExpectationNotMetError.new("failed")
+ @error = ExceptionState.new nil, nil, MSpecExampleError.new("boom!")
+ end
+
+ it "sets the #failure? flag" do
+ @formatter.exception @failure
+ expect(@formatter.failure?).to be_truthy
+ @formatter.exception @error
+ expect(@formatter.failure?).to be_falsey
+ end
+
+ it "sets the #exception? flag" do
+ @formatter.exception @error
+ expect(@formatter.exception?).to be_truthy
+ @formatter.exception @failure
+ expect(@formatter.exception?).to be_truthy
+ end
+
+ it "adds the exception to the list of exceptions" do
+ expect(@formatter.exceptions).to eq([])
+ @formatter.exception @error
+ @formatter.exception @failure
+ expect(@formatter.exceptions).to eq([@error, @failure])
+ end
+end
+
+RSpec.describe DottedFormatter, "#exception?" do
+ before :each do
+ @formatter = DottedFormatter.new
+ @failure = ExceptionState.new nil, nil, SpecExpectationNotMetError.new("failed")
+ @error = ExceptionState.new nil, nil, MSpecExampleError.new("boom!")
+ end
+
+ it "returns false if there have been no exceptions" do
+ expect(@formatter.exception?).to be_falsey
+ end
+
+ it "returns true if any exceptions are errors" do
+ @formatter.exception @failure
+ @formatter.exception @error
+ expect(@formatter.exception?).to be_truthy
+ end
+
+ it "returns true if all exceptions are failures" do
+ @formatter.exception @failure
+ @formatter.exception @failure
+ expect(@formatter.exception?).to be_truthy
+ end
+
+ it "returns true if all exceptions are errors" do
+ @formatter.exception @error
+ @formatter.exception @error
+ expect(@formatter.exception?).to be_truthy
+ end
+end
+
+RSpec.describe DottedFormatter, "#failure?" do
+ before :each do
+ @formatter = DottedFormatter.new
+ @failure = ExceptionState.new nil, nil, SpecExpectationNotMetError.new("failed")
+ @error = ExceptionState.new nil, nil, MSpecExampleError.new("boom!")
+ end
+
+ it "returns false if there have been no exceptions" do
+ expect(@formatter.failure?).to be_falsey
+ end
+
+ it "returns false if any exceptions are errors" do
+ @formatter.exception @failure
+ @formatter.exception @error
+ expect(@formatter.failure?).to be_falsey
+ end
+
+ it "returns true if all exceptions are failures" do
+ @formatter.exception @failure
+ @formatter.exception @failure
+ expect(@formatter.failure?).to be_truthy
+ end
+end
+
+RSpec.describe DottedFormatter, "#before" do
+ before :each do
+ @state = ExampleState.new ContextState.new("describe"), "it"
+ @formatter = DottedFormatter.new
+ @formatter.exception ExceptionState.new(nil, nil, SpecExpectationNotMetError.new("Failed!"))
+ end
+
+ it "resets the #failure? flag to false" do
+ expect(@formatter.failure?).to be_truthy
+ @formatter.before @state
+ expect(@formatter.failure?).to be_falsey
+ end
+
+ it "resets the #exception? flag to false" do
+ expect(@formatter.exception?).to be_truthy
+ @formatter.before @state
+ expect(@formatter.exception?).to be_falsey
+ end
+end
+
+RSpec.describe DottedFormatter, "#after" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = DottedFormatter.new
+ @state = ExampleState.new ContextState.new("describe"), "it"
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints a '.' if there was no exception raised" do
+ @formatter.after(@state)
+ expect(@out).to eq(".")
+ end
+
+ it "prints an 'F' if there was an expectation failure" do
+ exc = SpecExpectationNotMetError.new "failed"
+ @formatter.exception ExceptionState.new(@state, nil, exc)
+ @formatter.after(@state)
+ expect(@out).to eq("F")
+ end
+
+ it "prints an 'E' if there was an exception other than expectation failure" do
+ exc = MSpecExampleError.new("boom!")
+ @formatter.exception ExceptionState.new(@state, nil, exc)
+ @formatter.after(@state)
+ expect(@out).to eq("E")
+ end
+
+ it "prints an 'E' if there are mixed exceptions and exepctation failures" do
+ exc = SpecExpectationNotMetError.new "failed"
+ @formatter.exception ExceptionState.new(@state, nil, exc)
+ exc = MSpecExampleError.new("boom!")
+ @formatter.exception ExceptionState.new(@state, nil, exc)
+ @formatter.after(@state)
+ expect(@out).to eq("E")
+ end
+end
+
+RSpec.describe DottedFormatter, "#finish" do
+ before :each do
+ @tally = double("tally").as_null_object
+ allow(TallyAction).to receive(:new).and_return(@tally)
+ @timer = double("timer").as_null_object
+ allow(TimerAction).to receive(:new).and_return(@timer)
+
+ $stdout = @out = IOStub.new
+ context = ContextState.new "Class#method"
+ @state = ExampleState.new(context, "runs")
+ allow(MSpec).to receive(:register)
+ @formatter = DottedFormatter.new
+ @formatter.register
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints a failure message for an exception" do
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+ @formatter.exception exc
+ @formatter.after @state
+ @formatter.finish
+ expect(@out).to match(/^1\)\nClass#method runs ERROR$/)
+ end
+
+ it "prints a backtrace for an exception" do
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+ allow(exc).to receive(:backtrace).and_return("path/to/some/file.rb:35:in method")
+ @formatter.exception exc
+ @formatter.after @state
+ @formatter.finish
+ expect(@out).to match(%r[path/to/some/file.rb:35:in method$])
+ end
+
+ it "prints a summary of elapsed time" do
+ expect(@timer).to receive(:format).and_return("Finished in 2.0 seconds")
+ @formatter.finish
+ expect(@out).to match(/^Finished in 2.0 seconds$/)
+ end
+
+ it "prints a tally of counts" do
+ expect(@tally).to receive(:format).and_return("1 example, 0 failures")
+ @formatter.finish
+ expect(@out).to match(/^1 example, 0 failures$/)
+ end
+
+ it "prints errors, backtraces, elapsed time, and tallies" do
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+ allow(exc).to receive(:backtrace).and_return("path/to/some/file.rb:35:in method")
+ @formatter.exception exc
+ expect(@timer).to receive(:format).and_return("Finished in 2.0 seconds")
+ expect(@tally).to receive(:format).and_return("1 example, 1 failure")
+ @formatter.after @state
+ @formatter.finish
+ expect(@out).to eq(%[E
+
+1)
+Class#method runs ERROR
+MSpecExampleError: broken
+path/to/some/file.rb:35:in method
+
+Finished in 2.0 seconds
+
+1 example, 1 failure
+])
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/file_spec.rb b/spec/mspec/spec/runner/formatters/file_spec.rb
new file mode 100644
index 0000000000..ae11d60845
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/file_spec.rb
@@ -0,0 +1,84 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/file'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+RSpec.describe FileFormatter, "#register" do
+ before :each do
+ @formatter = FileFormatter.new
+ allow(MSpec).to receive(:register)
+ allow(MSpec).to receive(:unregister)
+ end
+
+ it "registers self with MSpec for :load, :unload actions" do
+ expect(MSpec).to receive(:register).with(:load, @formatter)
+ expect(MSpec).to receive(:register).with(:unload, @formatter)
+ @formatter.register
+ end
+
+ it "unregisters self with MSpec for :before, :after actions" do
+ expect(MSpec).to receive(:unregister).with(:before, @formatter)
+ expect(MSpec).to receive(:unregister).with(:after, @formatter)
+ @formatter.register
+ end
+end
+
+RSpec.describe FileFormatter, "#load" do
+ before :each do
+ @state = ExampleState.new ContextState.new("describe"), "it"
+ @formatter = FileFormatter.new
+ @formatter.exception ExceptionState.new(nil, nil, SpecExpectationNotMetError.new("Failed!"))
+ end
+
+ it "resets the #failure? flag to false" do
+ expect(@formatter.failure?).to be_truthy
+ @formatter.load @state
+ expect(@formatter.failure?).to be_falsey
+ end
+
+ it "resets the #exception? flag to false" do
+ expect(@formatter.exception?).to be_truthy
+ @formatter.load @state
+ expect(@formatter.exception?).to be_falsey
+ end
+end
+
+RSpec.describe FileFormatter, "#unload" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = FileFormatter.new
+ @state = ExampleState.new ContextState.new("describe"), "it"
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints a '.' if there was no exception raised" do
+ @formatter.unload(@state)
+ expect(@out).to eq(".")
+ end
+
+ it "prints an 'F' if there was an expectation failure" do
+ exc = SpecExpectationNotMetError.new "failed"
+ @formatter.exception ExceptionState.new(@state, nil, exc)
+ @formatter.unload(@state)
+ expect(@out).to eq("F")
+ end
+
+ it "prints an 'E' if there was an exception other than expectation failure" do
+ exc = MSpecExampleError.new("boom!")
+ @formatter.exception ExceptionState.new(@state, nil, exc)
+ @formatter.unload(@state)
+ expect(@out).to eq("E")
+ end
+
+ it "prints an 'E' if there are mixed exceptions and exepctation failures" do
+ exc = SpecExpectationNotMetError.new "failed"
+ @formatter.exception ExceptionState.new(@state, nil, exc)
+ exc = MSpecExampleError.new("boom!")
+ @formatter.exception ExceptionState.new(@state, nil, exc)
+ @formatter.unload(@state)
+ expect(@out).to eq("E")
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/html_spec.rb b/spec/mspec/spec/runner/formatters/html_spec.rb
new file mode 100644
index 0000000000..ed973ad93f
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/html_spec.rb
@@ -0,0 +1,220 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/guards/guard'
+require 'mspec/runner/formatters/html'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+require 'mspec/utils/script'
+require 'mspec/helpers'
+
+RSpec.describe HtmlFormatter do
+ before :each do
+ @formatter = HtmlFormatter.new
+ end
+
+ it "responds to #register by registering itself with MSpec for appropriate actions" do
+ allow(MSpec).to receive(:register)
+ expect(MSpec).to receive(:register).with(:start, @formatter)
+ expect(MSpec).to receive(:register).with(:enter, @formatter)
+ expect(MSpec).to receive(:register).with(:leave, @formatter)
+ @formatter.register
+ end
+end
+
+RSpec.describe HtmlFormatter, "#start" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = HtmlFormatter.new
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints the HTML head" do
+ @formatter.start
+ ruby_engine = RUBY_ENGINE
+ expect(ruby_engine).to match(/^#{ruby_engine}/)
+ expect(@out).to eq(%[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<title>Spec Output For #{ruby_engine} (#{RUBY_VERSION})</title>
+<style type="text/css">
+ul {
+ list-style: none;
+}
+.fail {
+ color: red;
+}
+.pass {
+ color: green;
+}
+#details :target {
+ background-color: #ffffe0;
+}
+</style>
+</head>
+<body>
+])
+ end
+end
+
+RSpec.describe HtmlFormatter, "#enter" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = HtmlFormatter.new
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints the #describe string" do
+ @formatter.enter "describe"
+ expect(@out).to eq("<div><p>describe</p>\n<ul>\n")
+ end
+end
+
+RSpec.describe HtmlFormatter, "#leave" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = HtmlFormatter.new
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints the closing tags for the #describe string" do
+ @formatter.leave
+ expect(@out).to eq("</ul>\n</div>\n")
+ end
+end
+
+RSpec.describe HtmlFormatter, "#exception" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = HtmlFormatter.new
+ @formatter.register
+ @state = ExampleState.new ContextState.new("describe"), "it"
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints the #it string once for each exception raised" do
+ exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing")
+ @formatter.exception exc
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful")
+ @formatter.exception exc
+ expect(@out).to eq(%[<li class="fail">- it (<a href="#details-1">FAILED - 1</a>)</li>
+<li class="fail">- it (<a href="#details-2">ERROR - 2</a>)</li>
+])
+ end
+end
+
+RSpec.describe HtmlFormatter, "#after" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = HtmlFormatter.new
+ @formatter.register
+ @state = ExampleState.new ContextState.new("describe"), "it"
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints the #it once when there are no exceptions raised" do
+ @formatter.after @state
+ expect(@out).to eq(%[<li class="pass">- it</li>\n])
+ end
+
+ it "does not print any output if an exception is raised" do
+ exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing")
+ @formatter.exception exc
+ out = @out.dup
+ @formatter.after @state
+ expect(@out).to eq(out)
+ end
+end
+
+RSpec.describe HtmlFormatter, "#finish" do
+ before :each do
+ @tally = double("tally").as_null_object
+ allow(TallyAction).to receive(:new).and_return(@tally)
+ @timer = double("timer").as_null_object
+ allow(TimerAction).to receive(:new).and_return(@timer)
+
+ @out = tmp("HtmlFormatter")
+
+ context = ContextState.new "describe"
+ @state = ExampleState.new(context, "it")
+ allow(MSpec).to receive(:register)
+ @formatter = HtmlFormatter.new(@out)
+ @formatter.register
+ @exception = MSpecExampleError.new("broken")
+ allow(@exception).to receive(:backtrace).and_return(["file.rb:1", "file.rb:2"])
+ end
+
+ after :each do
+ rm_r @out
+ end
+
+ it "prints a failure message for an exception" do
+ exc = ExceptionState.new @state, nil, @exception
+ @formatter.exception exc
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include "<p>describe it ERROR</p>"
+ end
+
+ it "prints a backtrace for an exception" do
+ exc = ExceptionState.new @state, nil, @exception
+ allow(exc).to receive(:backtrace).and_return("path/to/some/file.rb:35:in method")
+ @formatter.exception exc
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to match(%r[<pre>.*path/to/some/file.rb:35:in method.*</pre>]m)
+ end
+
+ it "prints a summary of elapsed time" do
+ expect(@timer).to receive(:format).and_return("Finished in 2.0 seconds")
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include "<p>Finished in 2.0 seconds</p>\n"
+ end
+
+ it "prints a tally of counts" do
+ expect(@tally).to receive(:format).and_return("1 example, 0 failures")
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include '<p class="pass">1 example, 0 failures</p>'
+ end
+
+ it "prints errors, backtraces, elapsed time, and tallies" do
+ exc = ExceptionState.new @state, nil, @exception
+ allow(exc).to receive(:backtrace).and_return("path/to/some/file.rb:35:in method")
+ @formatter.exception exc
+
+ expect(@timer).to receive(:format).and_return("Finished in 2.0 seconds")
+ expect(@tally).to receive(:format).and_return("1 example, 1 failures")
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to eq(%[<li class=\"fail\">- it (<a href=\"#details-1\">ERROR - 1</a>)</li>
+<hr>
+<ol id="details">
+<li id="details-1"><p>describe it ERROR</p>
+<p>MSpecExampleError: broken</p>
+<pre>
+path/to/some/file.rb:35:in method</pre>
+</li>
+</ol>
+<p>Finished in 2.0 seconds</p>
+<p class="fail">1 example, 1 failures</p>
+</body>
+</html>
+])
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/junit_spec.rb b/spec/mspec/spec/runner/formatters/junit_spec.rb
new file mode 100644
index 0000000000..3b3da73849
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/junit_spec.rb
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/junit'
+require 'mspec/runner/example'
+require 'mspec/helpers'
+
+RSpec.describe JUnitFormatter, "#initialize" do
+ it "permits zero arguments" do
+ expect { JUnitFormatter.new }.not_to raise_error
+ end
+
+ it "accepts one argument" do
+ expect { JUnitFormatter.new nil }.not_to raise_error
+ end
+end
+
+RSpec.describe JUnitFormatter, "#print" do
+ before :each do
+ $stdout = IOStub.new
+ @out = IOStub.new
+ allow(File).to receive(:open).and_return(@out)
+ @formatter = JUnitFormatter.new "some/file"
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "writes to $stdout if #switch has not been called" do
+ @formatter.print "begonias"
+ expect($stdout).to eq("begonias")
+ expect(@out).to eq("")
+ end
+
+ it "writes to the file passed to #initialize once #switch has been called" do
+ @formatter.switch
+ @formatter.print "begonias"
+ expect($stdout).to eq("")
+ expect(@out).to eq("begonias")
+ end
+
+ it "writes to $stdout once #switch is called if no file was passed to #initialize" do
+ formatter = JUnitFormatter.new
+ formatter.switch
+ formatter.print "begonias"
+ expect($stdout).to eq("begonias")
+ expect(@out).to eq("")
+ end
+end
+
+RSpec.describe JUnitFormatter, "#finish" do
+ before :each do
+ @tally = double("tally").as_null_object
+ @counter = double("counter").as_null_object
+ allow(@tally).to receive(:counter).and_return(@counter)
+ allow(TallyAction).to receive(:new).and_return(@tally)
+
+ @timer = double("timer").as_null_object
+ allow(TimerAction).to receive(:new).and_return(@timer)
+
+ @out = tmp("JUnitFormatter")
+
+ context = ContextState.new "describe"
+ @state = ExampleState.new(context, "it")
+
+ @formatter = JUnitFormatter.new(@out)
+ allow(@formatter).to receive(:backtrace).and_return("")
+ allow(MSpec).to receive(:register)
+ @formatter.register
+
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+ allow(exc).to receive(:backtrace).and_return("path/to/some/file.rb:35:in method")
+ @formatter.exception exc
+ @formatter.after @state
+ end
+
+ after :each do
+ rm_r @out
+ end
+
+ it "calls #switch" do
+ expect(@formatter).to receive(:switch).and_call_original
+ @formatter.finish
+ end
+
+ it "outputs a failure message and backtrace" do
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include 'message="error in describe it" type="error"'
+ expect(output).to include "MSpecExampleError: broken\n"
+ expect(output).to include "path/to/some/file.rb:35:in method"
+ end
+
+ it "encodes message and backtrace in latin1 for jenkins" do
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken…")
+ allow(exc).to receive(:backtrace).and_return("path/to/some/file.rb:35:in methød")
+ @formatter.exception exc
+ @formatter.finish
+ output = File.binread(@out)
+ expect(output).to match(/MSpecExampleError: broken((\.\.\.)|\?)\n/)
+ expect(output).to match(/path\/to\/some\/file\.rb:35:in meth(\?|o)d/)
+ end
+
+ it "outputs an elapsed time" do
+ expect(@timer).to receive(:elapsed).and_return(4.2)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include 'time="4.2"'
+ end
+
+ it "outputs overall elapsed time" do
+ expect(@timer).to receive(:elapsed).and_return(4.2)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include 'timeCount="4.2"'
+ end
+
+ it "outputs the number of examples as test count" do
+ expect(@counter).to receive(:examples).and_return(9)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include 'tests="9"'
+ end
+
+ it "outputs overall number of examples as test count" do
+ expect(@counter).to receive(:examples).and_return(9)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include 'testCount="9"'
+ end
+
+ it "outputs a failure count" do
+ expect(@counter).to receive(:failures).and_return(2)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include 'failureCount="2"'
+ end
+
+ it "outputs overall failure count" do
+ expect(@counter).to receive(:failures).and_return(2)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include 'failures="2"'
+ end
+
+ it "outputs an error count" do
+ expect(@counter).to receive(:errors).and_return(1)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include 'errors="1"'
+ end
+
+ it "outputs overall error count" do
+ expect(@counter).to receive(:errors).and_return(1)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include 'errorCount="1"'
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/method_spec.rb b/spec/mspec/spec/runner/formatters/method_spec.rb
new file mode 100644
index 0000000000..02bf47d538
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/method_spec.rb
@@ -0,0 +1,177 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/method'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+require 'mspec/utils/script'
+
+RSpec.describe MethodFormatter, "#method_type" do
+ before :each do
+ @formatter = MethodFormatter.new
+ end
+
+ it "returns 'class' if the separator is '.' or '::'" do
+ expect(@formatter.method_type('.')).to eq("class")
+ expect(@formatter.method_type('::')).to eq("class")
+ end
+
+ it "returns 'instance' if the separator is '#'" do
+ expect(@formatter.method_type('#')).to eq("instance")
+ end
+
+ it "returns 'unknown' for all other cases" do
+ expect(@formatter.method_type(nil)).to eq("unknown")
+ end
+end
+
+RSpec.describe MethodFormatter, "#before" do
+ before :each do
+ @formatter = MethodFormatter.new
+ allow(MSpec).to receive(:register)
+ @formatter.register
+ end
+
+ it "resets the tally counters to 0" do
+ @formatter.tally.counter.examples = 3
+ @formatter.tally.counter.expectations = 4
+ @formatter.tally.counter.failures = 2
+ @formatter.tally.counter.errors = 1
+
+ state = ExampleState.new ContextState.new("describe"), "it"
+ @formatter.before state
+ expect(@formatter.tally.counter.examples).to eq(0)
+ expect(@formatter.tally.counter.expectations).to eq(0)
+ expect(@formatter.tally.counter.failures).to eq(0)
+ expect(@formatter.tally.counter.errors).to eq(0)
+ end
+
+ it "records the class, method if available" do
+ state = ExampleState.new ContextState.new("Some#method"), "it"
+ @formatter.before state
+ key = "Some#method"
+ expect(@formatter.methods.keys).to include(key)
+ h = @formatter.methods[key]
+ expect(h[:class]).to eq("Some")
+ expect(h[:method]).to eq("method")
+ expect(h[:description]).to eq("Some#method it")
+ end
+
+ it "does not record class, method unless both are available" do
+ state = ExampleState.new ContextState.new("Some method"), "it"
+ @formatter.before state
+ key = "Some method"
+ expect(@formatter.methods.keys).to include(key)
+ h = @formatter.methods[key]
+ expect(h[:class]).to eq("")
+ expect(h[:method]).to eq("")
+ expect(h[:description]).to eq("Some method it")
+ end
+
+ it "sets the method type to unknown if class and method are not available" do
+ state = ExampleState.new ContextState.new("Some method"), "it"
+ @formatter.before state
+ key = "Some method"
+ h = @formatter.methods[key]
+ expect(h[:type]).to eq("unknown")
+ end
+
+ it "sets the method type based on the class, method separator" do
+ [["C#m", "instance"], ["C.m", "class"], ["C::m", "class"]].each do |k, t|
+ state = ExampleState.new ContextState.new(k), "it"
+ @formatter.before state
+ h = @formatter.methods[k]
+ expect(h[:type]).to eq(t)
+ end
+ end
+
+ it "clears the list of exceptions" do
+ state = ExampleState.new ContextState.new("describe"), "it"
+ @formatter.exceptions << "stuff"
+ @formatter.before state
+ expect(@formatter.exceptions).to be_empty
+ end
+end
+
+RSpec.describe MethodFormatter, "#after" do
+ before :each do
+ @formatter = MethodFormatter.new
+ allow(MSpec).to receive(:register)
+ @formatter.register
+ end
+
+ it "sets the tally counts" do
+ state = ExampleState.new ContextState.new("Some#method"), "it"
+ @formatter.before state
+
+ @formatter.tally.counter.examples = 3
+ @formatter.tally.counter.expectations = 4
+ @formatter.tally.counter.failures = 2
+ @formatter.tally.counter.errors = 1
+
+ @formatter.after state
+ h = @formatter.methods["Some#method"]
+ expect(h[:examples]).to eq(3)
+ expect(h[:expectations]).to eq(4)
+ expect(h[:failures]).to eq(2)
+ expect(h[:errors]).to eq(1)
+ end
+
+ it "renders the list of exceptions" do
+ state = ExampleState.new ContextState.new("Some#method"), "it"
+ @formatter.before state
+
+ exc = SpecExpectationNotMetError.new "failed"
+ @formatter.exception ExceptionState.new(state, nil, exc)
+ @formatter.exception ExceptionState.new(state, nil, exc)
+
+ @formatter.after state
+ h = @formatter.methods["Some#method"]
+ expect(h[:exceptions]).to eq([
+ %[failed\n\n],
+ %[failed\n\n]
+ ])
+ end
+end
+
+RSpec.describe MethodFormatter, "#after" do
+ before :each do
+ $stdout = IOStub.new
+ context = ContextState.new "Class#method"
+ @state = ExampleState.new(context, "runs")
+ @formatter = MethodFormatter.new
+ allow(MSpec).to receive(:register)
+ @formatter.register
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints a summary of the results of an example in YAML format" do
+ @formatter.before @state
+ @formatter.tally.counter.examples = 3
+ @formatter.tally.counter.expectations = 4
+ @formatter.tally.counter.failures = 2
+ @formatter.tally.counter.errors = 1
+
+ exc = SpecExpectationNotMetError.new "failed"
+ @formatter.exception ExceptionState.new(@state, nil, exc)
+ @formatter.exception ExceptionState.new(@state, nil, exc)
+
+ @formatter.after @state
+ @formatter.finish
+ expect($stdout).to eq(%[---
+"Class#method":
+ class: "Class"
+ method: "method"
+ type: instance
+ description: "Class#method runs"
+ examples: 3
+ expectations: 4
+ failures: 2
+ errors: 1
+ exceptions:
+ - "failed\\n\\n"
+ - "failed\\n\\n"
+])
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/multi_spec.rb b/spec/mspec/spec/runner/formatters/multi_spec.rb
new file mode 100644
index 0000000000..2d13c05836
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/multi_spec.rb
@@ -0,0 +1,68 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/dotted'
+require 'mspec/runner/formatters/multi'
+require 'mspec/runner/example'
+require 'yaml'
+
+RSpec.describe MultiFormatter, "#aggregate_results" do
+ before :each do
+ @stdout, $stdout = $stdout, IOStub.new
+
+ @file = double("file").as_null_object
+
+ allow(File).to receive(:delete)
+ allow(File).to receive(:read)
+
+ @hash = { "files"=>1, "examples"=>1, "expectations"=>2, "failures"=>0, "errors"=>0 }
+ allow(YAML).to receive(:load).and_return(@hash)
+
+ @formatter = DottedFormatter.new.extend(MultiFormatter)
+ allow(@formatter.timer).to receive(:format).and_return("Finished in 42 seconds")
+ end
+
+ after :each do
+ $stdout = @stdout
+ end
+
+ it "outputs a summary without errors" do
+ @formatter.aggregate_results(["a", "b"])
+ @formatter.finish
+ expect($stdout).to eq(%[
+
+Finished in 42 seconds
+
+2 files, 2 examples, 4 expectations, 0 failures, 0 errors, 0 tagged
+])
+ end
+
+ it "outputs a summary with errors" do
+ @hash["exceptions"] = [
+ "Some#method works real good FAILED\nExpected real good\n to equal fail\n\nfoo.rb:1\nfoo.rb:2",
+ "Some#method never fails ERROR\nExpected 5\n to equal 3\n\nfoo.rb:1\nfoo.rb:2"
+ ]
+ @formatter.aggregate_results(["a"])
+ @formatter.finish
+ expect($stdout).to eq(%[
+
+1)
+Some#method works real good FAILED
+Expected real good
+ to equal fail
+
+foo.rb:1
+foo.rb:2
+
+2)
+Some#method never fails ERROR
+Expected 5
+ to equal 3
+
+foo.rb:1
+foo.rb:2
+
+Finished in 42 seconds
+
+1 file, 1 example, 2 expectations, 0 failures, 0 errors, 0 tagged
+])
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/specdoc_spec.rb b/spec/mspec/spec/runner/formatters/specdoc_spec.rb
new file mode 100644
index 0000000000..54b5e2cf0d
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/specdoc_spec.rb
@@ -0,0 +1,106 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/specdoc'
+require 'mspec/runner/example'
+
+RSpec.describe SpecdocFormatter do
+ before :each do
+ @formatter = SpecdocFormatter.new
+ end
+
+ it "responds to #register by registering itself with MSpec for appropriate actions" do
+ allow(MSpec).to receive(:register)
+ expect(MSpec).to receive(:register).with(:enter, @formatter)
+ @formatter.register
+ end
+end
+
+RSpec.describe SpecdocFormatter, "#enter" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = SpecdocFormatter.new
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints the #describe string" do
+ @formatter.enter("describe")
+ expect(@out).to eq("\ndescribe\n")
+ end
+end
+
+RSpec.describe SpecdocFormatter, "#before" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = SpecdocFormatter.new
+ @state = ExampleState.new ContextState.new("describe"), "it"
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints the #it string" do
+ @formatter.before @state
+ expect(@out).to eq("- it")
+ end
+
+ it "resets the #exception? flag" do
+ exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing")
+ @formatter.exception exc
+ expect(@formatter.exception?).to be_truthy
+ @formatter.before @state
+ expect(@formatter.exception?).to be_falsey
+ end
+end
+
+RSpec.describe SpecdocFormatter, "#exception" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = SpecdocFormatter.new
+ context = ContextState.new "describe"
+ @state = ExampleState.new context, "it"
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints 'ERROR' if an exception is not an SpecExpectationNotMetError" do
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful")
+ @formatter.exception exc
+ expect(@out).to eq(" (ERROR - 1)")
+ end
+
+ it "prints 'FAILED' if an exception is an SpecExpectationNotMetError" do
+ exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing")
+ @formatter.exception exc
+ expect(@out).to eq(" (FAILED - 1)")
+ end
+
+ it "prints the #it string if an exception has already been raised" do
+ exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing")
+ @formatter.exception exc
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful")
+ @formatter.exception exc
+ expect(@out).to eq(" (FAILED - 1)\n- it (ERROR - 2)")
+ end
+end
+
+RSpec.describe SpecdocFormatter, "#after" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = SpecdocFormatter.new
+ @state = ExampleState.new "describe", "it"
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints a newline character" do
+ @formatter.after @state
+ expect(@out).to eq("\n")
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/spinner_spec.rb b/spec/mspec/spec/runner/formatters/spinner_spec.rb
new file mode 100644
index 0000000000..5c93d38822
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/spinner_spec.rb
@@ -0,0 +1,83 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/spinner'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+RSpec.describe SpinnerFormatter, "#initialize" do
+ it "permits zero arguments" do
+ SpinnerFormatter.new
+ end
+
+ it "accepts one argument" do
+ SpinnerFormatter.new nil
+ end
+end
+
+RSpec.describe SpinnerFormatter, "#register" do
+ before :each do
+ @formatter = SpinnerFormatter.new
+ allow(MSpec).to receive(:register)
+ end
+
+ it "registers self with MSpec for appropriate actions" do
+ expect(MSpec).to receive(:register).with(:start, @formatter)
+ expect(MSpec).to receive(:register).with(:unload, @formatter)
+ expect(MSpec).to receive(:register).with(:after, @formatter)
+ expect(MSpec).to receive(:register).with(:finish, @formatter)
+ @formatter.register
+ end
+
+ it "creates TimerAction and TallyAction" do
+ timer = double("timer")
+ tally = double("tally")
+ expect(timer).to receive(:register)
+ expect(tally).to receive(:register)
+ expect(tally).to receive(:counter)
+ expect(TimerAction).to receive(:new).and_return(timer)
+ expect(TallyAction).to receive(:new).and_return(tally)
+ @formatter.register
+ end
+end
+
+RSpec.describe SpinnerFormatter, "#print" do
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "ignores the argument to #initialize and writes to $stdout" do
+ $stdout = IOStub.new
+ formatter = SpinnerFormatter.new "some/file"
+ formatter.print "begonias"
+ expect($stdout).to eq("begonias")
+ end
+end
+
+RSpec.describe SpinnerFormatter, "#after" do
+ before :each do
+ $stdout = IOStub.new
+ MSpec.store(:files, ["a", "b", "c", "d"])
+ @formatter = SpinnerFormatter.new
+ @formatter.register
+ @state = ExampleState.new("describe", "it")
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "updates the spinner" do
+ @formatter.start
+ @formatter.after @state
+ @formatter.unload
+
+ if ENV["TERM"] != "dumb"
+ green = "\e[0;32m"
+ reset = "\e[0m"
+ end
+
+ output = "\r[/ | 0% | 00:00:00] #{green} 0F #{green} 0E#{reset} " \
+ "\r[- | 0% | 00:00:00] #{green} 0F #{green} 0E#{reset} " \
+ "\r[\\ | ========== 25% | 00:00:00] #{green} 0F #{green} 0E#{reset} "
+ expect($stdout).to eq(output)
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/summary_spec.rb b/spec/mspec/spec/runner/formatters/summary_spec.rb
new file mode 100644
index 0000000000..c87d940042
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/summary_spec.rb
@@ -0,0 +1,26 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/summary'
+require 'mspec/runner/example'
+
+RSpec.describe SummaryFormatter, "#after" do
+ before :each do
+ $stdout = @out = IOStub.new
+ @formatter = SummaryFormatter.new
+ @formatter.register
+ context = ContextState.new "describe"
+ @state = ExampleState.new(context, "it")
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "does not print anything" do
+ exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing")
+ @formatter.exception exc
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful")
+ @formatter.exception exc
+ @formatter.after(@state)
+ expect(@out).to eq("")
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/unit_spec.rb b/spec/mspec/spec/runner/formatters/unit_spec.rb
new file mode 100644
index 0000000000..d349e6871d
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/unit_spec.rb
@@ -0,0 +1,73 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/unit'
+require 'mspec/runner/example'
+require 'mspec/utils/script'
+
+RSpec.describe UnitdiffFormatter, "#finish" do
+ before :each do
+ @tally = double("tally").as_null_object
+ allow(TallyAction).to receive(:new).and_return(@tally)
+ @timer = double("timer").as_null_object
+ allow(TimerAction).to receive(:new).and_return(@timer)
+
+ $stdout = @out = IOStub.new
+ context = ContextState.new "describe"
+ @state = ExampleState.new(context, "it")
+ allow(MSpec).to receive(:register)
+ @formatter = UnitdiffFormatter.new
+ @formatter.register
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "prints a failure message for an exception" do
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+ @formatter.exception exc
+ @formatter.after @state
+ @formatter.finish
+ expect(@out).to match(/^1\)\ndescribe it ERROR$/)
+ end
+
+ it "prints a backtrace for an exception" do
+ exc = ExceptionState.new @state, nil, Exception.new("broken")
+ allow(exc).to receive(:backtrace).and_return("path/to/some/file.rb:35:in method")
+ @formatter.exception exc
+ @formatter.finish
+ expect(@out).to match(%r[path/to/some/file.rb:35:in method$])
+ end
+
+ it "prints a summary of elapsed time" do
+ expect(@timer).to receive(:format).and_return("Finished in 2.0 seconds")
+ @formatter.finish
+ expect(@out).to match(/^Finished in 2.0 seconds$/)
+ end
+
+ it "prints a tally of counts" do
+ expect(@tally).to receive(:format).and_return("1 example, 0 failures")
+ @formatter.finish
+ expect(@out).to match(/^1 example, 0 failures$/)
+ end
+
+ it "prints errors, backtraces, elapsed time, and tallies" do
+ exc = ExceptionState.new @state, nil, Exception.new("broken")
+ allow(exc).to receive(:backtrace).and_return("path/to/some/file.rb:35:in method")
+ @formatter.exception exc
+ @formatter.after @state
+ expect(@timer).to receive(:format).and_return("Finished in 2.0 seconds")
+ expect(@tally).to receive(:format).and_return("1 example, 0 failures")
+ @formatter.finish
+ expect(@out).to eq(%[E
+
+Finished in 2.0 seconds
+
+1)
+describe it ERROR
+Exception: broken:
+path/to/some/file.rb:35:in method
+
+1 example, 0 failures
+])
+ end
+end
diff --git a/spec/mspec/spec/runner/formatters/yaml_spec.rb b/spec/mspec/spec/runner/formatters/yaml_spec.rb
new file mode 100644
index 0000000000..2e334fdbb9
--- /dev/null
+++ b/spec/mspec/spec/runner/formatters/yaml_spec.rb
@@ -0,0 +1,134 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/yaml'
+require 'mspec/runner/example'
+require 'mspec/helpers'
+
+RSpec.describe YamlFormatter, "#initialize" do
+ it "permits zero arguments" do
+ YamlFormatter.new
+ end
+
+ it "accepts one argument" do
+ YamlFormatter.new nil
+ end
+end
+
+RSpec.describe YamlFormatter, "#print" do
+ before :each do
+ $stdout = IOStub.new
+ @out = IOStub.new
+ allow(File).to receive(:open).and_return(@out)
+ @formatter = YamlFormatter.new "some/file"
+ end
+
+ after :each do
+ $stdout = STDOUT
+ end
+
+ it "writes to $stdout if #switch has not been called" do
+ @formatter.print "begonias"
+ expect($stdout).to eq("begonias")
+ expect(@out).to eq("")
+ end
+
+ it "writes to the file passed to #initialize once #switch has been called" do
+ @formatter.switch
+ @formatter.print "begonias"
+ expect($stdout).to eq("")
+ expect(@out).to eq("begonias")
+ end
+
+ it "writes to $stdout once #switch is called if no file was passed to #initialize" do
+ formatter = YamlFormatter.new
+ formatter.switch
+ formatter.print "begonias"
+ expect($stdout).to eq("begonias")
+ expect(@out).to eq("")
+ end
+end
+
+RSpec.describe YamlFormatter, "#finish" do
+ before :each do
+ @tally = double("tally").as_null_object
+ @counter = double("counter").as_null_object
+ allow(@tally).to receive(:counter).and_return(@counter)
+ allow(TallyAction).to receive(:new).and_return(@tally)
+
+ @timer = double("timer").as_null_object
+ allow(TimerAction).to receive(:new).and_return(@timer)
+
+ @out = tmp("YamlFormatter")
+
+ context = ContextState.new "describe"
+ @state = ExampleState.new(context, "it")
+
+ @formatter = YamlFormatter.new(@out)
+ allow(@formatter).to receive(:backtrace).and_return("")
+ allow(MSpec).to receive(:register)
+ @formatter.register
+
+ exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+ allow(exc).to receive(:backtrace).and_return("path/to/some/file.rb:35:in method")
+ @formatter.exception exc
+ @formatter.after @state
+ end
+
+ after :each do
+ rm_r @out
+ end
+
+ it "calls #switch" do
+ expect(@formatter).to receive(:switch).and_call_original
+ @formatter.finish
+ end
+
+ it "outputs a failure message and backtrace" do
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include "describe it ERROR"
+ expect(output).to include "MSpecExampleError: broken\\n"
+ expect(output).to include "path/to/some/file.rb:35:in method"
+ end
+
+ it "outputs an elapsed time" do
+ expect(@timer).to receive(:elapsed).and_return(4.2)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include "time: 4.2"
+ end
+
+ it "outputs a file count" do
+ expect(@counter).to receive(:files).and_return(3)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include "files: 3"
+ end
+
+ it "outputs an example count" do
+ expect(@counter).to receive(:examples).and_return(3)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include "examples: 3"
+ end
+
+ it "outputs an expectation count" do
+ expect(@counter).to receive(:expectations).and_return(9)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include "expectations: 9"
+ end
+
+ it "outputs a failure count" do
+ expect(@counter).to receive(:failures).and_return(2)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include "failures: 2"
+ end
+
+ it "outputs an error count" do
+ expect(@counter).to receive(:errors).and_return(1)
+ @formatter.finish
+ output = File.read(@out)
+ expect(output).to include "errors: 1"
+ end
+end
diff --git a/spec/mspec/spec/runner/mspec_spec.rb b/spec/mspec/spec/runner/mspec_spec.rb
new file mode 100644
index 0000000000..4af01806c0
--- /dev/null
+++ b/spec/mspec/spec/runner/mspec_spec.rb
@@ -0,0 +1,597 @@
+require 'spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/helpers/tmp'
+require 'mspec/helpers/fs'
+require 'mspec/matchers/base'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+RSpec.describe MSpec, ".register_files" do
+ it "records which spec files to run" do
+ MSpec.register_files [:one, :two, :three]
+ expect(MSpec.files_array).to eq([:one, :two, :three])
+ end
+end
+
+RSpec.describe MSpec, ".register_mode" do
+ before :each do
+ MSpec.clear_modes
+ end
+
+ it "sets execution mode flags" do
+ MSpec.register_mode :verify
+ expect(MSpec.retrieve(:modes)).to eq([:verify])
+ end
+end
+
+RSpec.describe MSpec, ".register_tags_patterns" do
+ it "records the patterns for generating a tag file from a spec file" do
+ MSpec.register_tags_patterns [[/spec\/ruby/, "spec/tags"], [/frozen/, "ruby"]]
+ expect(MSpec.retrieve(:tags_patterns)).to eq([[/spec\/ruby/, "spec/tags"], [/frozen/, "ruby"]])
+ end
+end
+
+RSpec.describe MSpec, ".register_exit" do
+ before :each do
+ MSpec.store :exit, 0
+ end
+
+ it "records the exit code" do
+ expect(MSpec.exit_code).to eq(0)
+ MSpec.register_exit 1
+ expect(MSpec.exit_code).to eq(1)
+ end
+end
+
+RSpec.describe MSpec, ".exit_code" do
+ it "retrieves the code set with .register_exit" do
+ MSpec.register_exit 99
+ expect(MSpec.exit_code).to eq(99)
+ end
+end
+
+RSpec.describe MSpec, ".store" do
+ it "records data for MSpec settings" do
+ MSpec.store :anything, :value
+ expect(MSpec.retrieve(:anything)).to eq(:value)
+ end
+end
+
+RSpec.describe MSpec, ".retrieve" do
+ it "accesses .store'd data" do
+ MSpec.register :retrieve, :first
+ expect(MSpec.retrieve(:retrieve)).to eq([:first])
+ end
+end
+
+RSpec.describe MSpec, ".randomize" do
+ it "sets the flag to randomize spec execution order" do
+ expect(MSpec.randomize?).to eq(false)
+ MSpec.randomize = true
+ expect(MSpec.randomize?).to eq(true)
+ MSpec.randomize = false
+ expect(MSpec.randomize?).to eq(false)
+ end
+end
+
+RSpec.describe MSpec, ".register" do
+ it "is the gateway behind the register(symbol, action) facility" do
+ MSpec.register :bonus, :first
+ MSpec.register :bonus, :second
+ MSpec.register :bonus, :second
+ expect(MSpec.retrieve(:bonus)).to eq([:first, :second])
+ end
+end
+
+RSpec.describe MSpec, ".unregister" do
+ it "is the gateway behind the unregister(symbol, actions) facility" do
+ MSpec.register :unregister, :first
+ MSpec.register :unregister, :second
+ MSpec.unregister :unregister, :second
+ expect(MSpec.retrieve(:unregister)).to eq([:first])
+ end
+end
+
+RSpec.describe MSpec, ".protect" do
+ before :each do
+ MSpec.clear_current
+ @cs = ContextState.new "C#m"
+ @cs.parent = MSpec.current
+
+ @es = ExampleState.new @cs, "runs"
+ ScratchPad.record Exception.new("Sharp!")
+ end
+
+ it "returns true if no exception is raised" do
+ expect(MSpec.protect("passed") { 1 }).to be_truthy
+ end
+
+ it "returns false if an exception is raised" do
+ expect(MSpec.protect("testing") { raise ScratchPad.recorded }).to be_falsey
+ end
+
+ it "rescues any exceptions raised when evaluating the block argument" do
+ MSpec.protect("") { raise Exception, "Now you see me..." }
+ end
+
+ it "does not rescue SystemExit" do
+ begin
+ MSpec.protect("") { exit 1 }
+ rescue SystemExit
+ ScratchPad.record :system_exit
+ end
+ expect(ScratchPad.recorded).to eq(:system_exit)
+ end
+
+ it "calls all the exception actions" do
+ exc = ExceptionState.new @es, "testing", ScratchPad.recorded
+ allow(ExceptionState).to receive(:new).and_return(exc)
+ action = double("exception")
+ expect(action).to receive(:exception).with(exc)
+ MSpec.register :exception, action
+ MSpec.protect("testing") { raise ScratchPad.recorded }
+ MSpec.unregister :exception, action
+ end
+
+ it "registers a non-zero exit code when an exception is raised" do
+ expect(MSpec).to receive(:register_exit).with(1)
+ MSpec.protect("testing") { raise ScratchPad.recorded }
+ end
+end
+
+RSpec.describe MSpec, ".register_current" do
+ before :each do
+ MSpec.clear_current
+ end
+
+ it "sets the value returned by MSpec.current" do
+ expect(MSpec.current).to be_nil
+ MSpec.register_current :a
+ expect(MSpec.current).to eq(:a)
+ end
+end
+
+RSpec.describe MSpec, ".clear_current" do
+ it "sets the value returned by MSpec.current to nil" do
+ MSpec.register_current :a
+ expect(MSpec.current).not_to be_nil
+ MSpec.clear_current
+ expect(MSpec.current).to be_nil
+ end
+end
+
+RSpec.describe MSpec, ".current" do
+ before :each do
+ MSpec.clear_current
+ end
+
+ it "returns nil if no ContextState has been registered" do
+ expect(MSpec.current).to be_nil
+ end
+
+ it "returns the most recently registered ContextState" do
+ first = ContextState.new ""
+ second = ContextState.new ""
+ MSpec.register_current first
+ expect(MSpec.current).to eq(first)
+ MSpec.register_current second
+ expect(MSpec.current).to eq(second)
+ end
+end
+
+RSpec.describe MSpec, ".actions" do
+ before :each do
+ MSpec.store :start, []
+ ScratchPad.record []
+ start_one = double("one")
+ allow(start_one).to receive(:start) { ScratchPad << :one }
+ start_two = double("two")
+ allow(start_two).to receive(:start) { ScratchPad << :two }
+ MSpec.register :start, start_one
+ MSpec.register :start, start_two
+ end
+
+ it "does not attempt to run any actions if none have been registered" do
+ MSpec.store :finish, nil
+ expect { MSpec.actions :finish }.not_to raise_error
+ end
+
+ it "runs each action registered as a start action" do
+ MSpec.actions :start
+ expect(ScratchPad.recorded).to eq([:one, :two])
+ end
+end
+
+RSpec.describe MSpec, ".mode?" do
+ before :each do
+ MSpec.clear_modes
+ end
+
+ it "returns true if the mode has been set" do
+ expect(MSpec.mode?(:verify)).to eq(false)
+ MSpec.register_mode :verify
+ expect(MSpec.mode?(:verify)).to eq(true)
+ end
+end
+
+RSpec.describe MSpec, ".clear_modes" do
+ it "clears all registered modes" do
+ MSpec.register_mode(:pretend)
+ MSpec.register_mode(:verify)
+
+ expect(MSpec.mode?(:pretend)).to eq(true)
+ expect(MSpec.mode?(:verify)).to eq(true)
+
+ MSpec.clear_modes
+
+ expect(MSpec.mode?(:pretend)).to eq(false)
+ expect(MSpec.mode?(:verify)).to eq(false)
+ end
+end
+
+RSpec.describe MSpec, ".guarded?" do
+ before :each do
+ MSpec.instance_variable_set :@guarded, []
+ end
+
+ it "returns false if no guard has run" do
+ expect(MSpec.guarded?).to eq(false)
+ end
+
+ it "returns true if a single guard has run" do
+ MSpec.guard
+ expect(MSpec.guarded?).to eq(true)
+ end
+
+ it "returns true if more than one guard has run" do
+ MSpec.guard
+ MSpec.guard
+ expect(MSpec.guarded?).to eq(true)
+ end
+
+ it "returns true until all guards have finished" do
+ MSpec.guard
+ MSpec.guard
+ expect(MSpec.guarded?).to eq(true)
+ MSpec.unguard
+ expect(MSpec.guarded?).to eq(true)
+ MSpec.unguard
+ expect(MSpec.guarded?).to eq(false)
+ end
+end
+
+RSpec.describe MSpec, ".describe" do
+ before :each do
+ MSpec.clear_current
+ @cs = ContextState.new ""
+ allow(ContextState).to receive(:new).and_return(@cs)
+ allow(MSpec).to receive(:current).and_return(nil)
+ allow(MSpec).to receive(:register_current)
+ end
+
+ it "creates a new ContextState for the block" do
+ expect(ContextState).to receive(:new).and_return(@cs)
+ MSpec.describe(Object) { }
+ end
+
+ it "accepts an optional second argument" do
+ expect(ContextState).to receive(:new).and_return(@cs)
+ MSpec.describe(Object, "msg") { }
+ end
+
+ it "registers the newly created ContextState" do
+ expect(MSpec).to receive(:register_current).with(@cs).twice
+ MSpec.describe(Object) { }
+ end
+
+ it "invokes the ContextState#describe method" do
+ expect(@cs).to receive(:describe)
+ MSpec.describe(Object, "msg") {}
+ end
+end
+
+RSpec.describe MSpec, ".process" do
+ before :each do
+ allow(MSpec).to receive(:files)
+ MSpec.store :start, []
+ MSpec.store :finish, []
+ allow(STDOUT).to receive(:puts)
+ end
+
+ it "prints the RUBY_DESCRIPTION" do
+ expect(STDOUT).to receive(:puts).with(RUBY_DESCRIPTION)
+ MSpec.process
+ end
+
+ it "calls all start actions" do
+ start = double("start")
+ allow(start).to receive(:start) { ScratchPad.record :start }
+ MSpec.register :start, start
+ MSpec.process
+ expect(ScratchPad.recorded).to eq(:start)
+ end
+
+ it "calls all finish actions" do
+ finish = double("finish")
+ allow(finish).to receive(:finish) { ScratchPad.record :finish }
+ MSpec.register :finish, finish
+ MSpec.process
+ expect(ScratchPad.recorded).to eq(:finish)
+ end
+
+ it "calls the files method" do
+ expect(MSpec).to receive(:files)
+ MSpec.process
+ end
+end
+
+RSpec.describe MSpec, ".files" do
+ before :each do
+ MSpec.store :load, []
+ MSpec.store :unload, []
+ MSpec.register_files [:one, :two, :three]
+ allow(Kernel).to receive(:load)
+ end
+
+ it "calls load actions before each file" do
+ load = double("load")
+ allow(load).to receive(:load) { ScratchPad.record :load }
+ MSpec.register :load, load
+ MSpec.files
+ expect(ScratchPad.recorded).to eq(:load)
+ end
+
+ it "shuffles the file list if .randomize? is true" do
+ MSpec.randomize = true
+ expect(MSpec).to receive(:shuffle)
+ MSpec.files
+ MSpec.randomize = false
+ end
+
+ it "registers the current file" do
+ load = double("load")
+ files = []
+ allow(load).to receive(:load) { files << MSpec.file }
+ MSpec.register :load, load
+ MSpec.files
+ expect(files).to eq([:one, :two, :three])
+ end
+end
+
+RSpec.describe MSpec, ".shuffle" do
+ before :each do
+ @base = (0..100).to_a
+ @list = @base.clone
+ MSpec.shuffle @list
+ end
+
+ it "does not alter the elements in the list" do
+ @base.each do |elt|
+ expect(@list).to include(elt)
+ end
+ end
+
+ it "changes the order of the list" do
+ # obviously, this spec has a certain probability
+ # of failing. If it fails, run it again.
+ expect(@list).not_to eq(@base)
+ end
+end
+
+RSpec.describe MSpec, ".tags_file" do
+ before :each do
+ MSpec.store :file, "path/to/spec/something/some_spec.rb"
+ MSpec.store :tags_patterns, nil
+ end
+
+ it "returns the default tags file for the current spec file" do
+ expect(MSpec.tags_file).to eq("path/to/spec/tags/something/some_tags.txt")
+ end
+
+ it "returns the tags file for the current spec file with custom tags_patterns" do
+ MSpec.register_tags_patterns [[/^(.*)\/spec/, '\1/tags'], [/_spec.rb/, "_tags.txt"]]
+ expect(MSpec.tags_file).to eq("path/to/tags/something/some_tags.txt")
+ end
+
+ it "performs multiple substitutions" do
+ MSpec.register_tags_patterns [
+ [%r(/spec/something/), "/spec/other/"],
+ [%r(/spec/), "/spec/tags/"],
+ [/_spec.rb/, "_tags.txt"]
+ ]
+ expect(MSpec.tags_file).to eq("path/to/spec/tags/other/some_tags.txt")
+ end
+
+ it "handles cases where no substitution is performed" do
+ MSpec.register_tags_patterns [[/nothing/, "something"]]
+ expect(MSpec.tags_file).to eq("path/to/spec/something/some_spec.rb")
+ end
+end
+
+RSpec.describe MSpec, ".read_tags" do
+ before :each do
+ allow(MSpec).to receive(:tags_file).and_return(File.dirname(__FILE__) + '/tags.txt')
+ end
+
+ it "returns a list of tag instances for matching tag names found" do
+ one = SpecTag.new "fail(broken):Some#method? works"
+ expect(MSpec.read_tags(["fail", "pass"])).to eq([one])
+ end
+
+ it "returns [] if no tags names match" do
+ expect(MSpec.read_tags("super")).to eq([])
+ end
+end
+
+RSpec.describe MSpec, ".read_tags" do
+ before :each do
+ @tag = SpecTag.new "fails:Some#method"
+ File.open(tmp("tags.txt", false), "w") do |f|
+ f.puts ""
+ f.puts @tag
+ f.puts ""
+ end
+ allow(MSpec).to receive(:tags_file).and_return(tmp("tags.txt", false))
+ end
+
+ it "does not return a tag object for empty lines" do
+ expect(MSpec.read_tags(["fails"])).to eq([@tag])
+ end
+end
+
+RSpec.describe MSpec, ".write_tags" do
+ before :each do
+ FileUtils.cp File.dirname(__FILE__) + "/tags.txt", tmp("tags.txt", false)
+ allow(MSpec).to receive(:tags_file).and_return(tmp("tags.txt", false))
+ @tag1 = SpecTag.new "check(broken):Tag#rewrite works"
+ @tag2 = SpecTag.new "broken:Tag#write_tags fails"
+ end
+
+ after :all do
+ rm_r tmp("tags.txt", false)
+ end
+
+ it "overwrites the tags in the tag file" do
+ expect(IO.read(tmp("tags.txt", false))).to eq(%[fail(broken):Some#method? works
+incomplete(20%):The#best method ever
+benchmark(0.01825):The#fastest method today
+extended():\"Multi-line\\ntext\\ntag\"
+])
+ MSpec.write_tags [@tag1, @tag2]
+ expect(IO.read(tmp("tags.txt", false))).to eq(%[check(broken):Tag#rewrite works
+broken:Tag#write_tags fails
+])
+ end
+end
+
+RSpec.describe MSpec, ".write_tag" do
+ before :each do
+ allow(FileUtils).to receive(:mkdir_p)
+ allow(MSpec).to receive(:tags_file).and_return(tmp("tags.txt", false))
+ @tag = SpecTag.new "fail(broken):Some#method works"
+ end
+
+ after :all do
+ rm_r tmp("tags.txt", false)
+ end
+
+ it "writes a tag to the tags file for the current spec file" do
+ MSpec.write_tag @tag
+ expect(IO.read(tmp("tags.txt", false))).to eq("fail(broken):Some#method works\n")
+ end
+
+ it "does not write a duplicate tag" do
+ File.open(tmp("tags.txt", false), "w") { |f| f.puts @tag }
+ MSpec.write_tag @tag
+ expect(IO.read(tmp("tags.txt", false))).to eq("fail(broken):Some#method works\n")
+ end
+end
+
+RSpec.describe MSpec, ".delete_tag" do
+ before :each do
+ FileUtils.cp File.dirname(__FILE__) + "/tags.txt", tmp("tags.txt", false)
+ allow(MSpec).to receive(:tags_file).and_return(tmp("tags.txt", false))
+ @tag = SpecTag.new "fail(Comments don't matter):Some#method? works"
+ end
+
+ after :each do
+ rm_r tmp("tags.txt", false)
+ end
+
+ it "deletes the tag if it exists" do
+ expect(MSpec.delete_tag(@tag)).to eq(true)
+ expect(IO.read(tmp("tags.txt", false))).to eq(%[incomplete(20%):The#best method ever
+benchmark(0.01825):The#fastest method today
+extended():\"Multi-line\\ntext\\ntag\"
+])
+ end
+
+ it "deletes a tag with escaped newlines" do
+ expect(MSpec.delete_tag(SpecTag.new('extended:"Multi-line\ntext\ntag"'))).to eq(true)
+ expect(IO.read(tmp("tags.txt", false))).to eq(%[fail(broken):Some#method? works
+incomplete(20%):The#best method ever
+benchmark(0.01825):The#fastest method today
+])
+ end
+
+ it "does not change the tags file contents if the tag doesn't exist" do
+ @tag.tag = "failed"
+ expect(MSpec.delete_tag(@tag)).to eq(false)
+ expect(IO.read(tmp("tags.txt", false))).to eq(%[fail(broken):Some#method? works
+incomplete(20%):The#best method ever
+benchmark(0.01825):The#fastest method today
+extended():\"Multi-line\\ntext\\ntag\"
+])
+ end
+
+ it "deletes the tag file if it is empty" do
+ expect(MSpec.delete_tag(@tag)).to eq(true)
+ expect(MSpec.delete_tag(SpecTag.new("incomplete:The#best method ever"))).to eq(true)
+ expect(MSpec.delete_tag(SpecTag.new("benchmark:The#fastest method today"))).to eq(true)
+ expect(MSpec.delete_tag(SpecTag.new('extended:"Multi-line\ntext\ntag"'))).to eq(true)
+ expect(File.exist?(tmp("tags.txt", false))).to eq(false)
+ end
+end
+
+RSpec.describe MSpec, ".delete_tags" do
+ before :each do
+ @tags = tmp("tags.txt", false)
+ FileUtils.cp File.dirname(__FILE__) + "/tags.txt", @tags
+ allow(MSpec).to receive(:tags_file).and_return(@tags)
+ end
+
+ it "deletes the tag file" do
+ MSpec.delete_tags
+ expect(File.exist?(@tags)).to be_falsey
+ end
+end
+
+RSpec.describe MSpec, ".expectation" do
+ it "sets the flag that an expectation has been reported" do
+ MSpec.clear_expectations
+ expect(MSpec.expectation?).to be_falsey
+ MSpec.expectation
+ expect(MSpec.expectation?).to be_truthy
+ end
+end
+
+RSpec.describe MSpec, ".expectation?" do
+ it "returns true if an expectation has been reported" do
+ MSpec.expectation
+ expect(MSpec.expectation?).to be_truthy
+ end
+
+ it "returns false if an expectation has not been reported" do
+ MSpec.clear_expectations
+ expect(MSpec.expectation?).to be_falsey
+ end
+end
+
+RSpec.describe MSpec, ".clear_expectations" do
+ it "clears the flag that an expectation has been reported" do
+ MSpec.expectation
+ expect(MSpec.expectation?).to be_truthy
+ MSpec.clear_expectations
+ expect(MSpec.expectation?).to be_falsey
+ end
+end
+
+RSpec.describe MSpec, ".register_shared" do
+ it "stores a shared ContextState by description" do
+ parent = ContextState.new "container"
+ state = ContextState.new "shared"
+ state.parent = parent
+ prc = lambda { }
+ state.describe(&prc)
+ MSpec.register_shared(state)
+ expect(MSpec.retrieve(:shared)["shared"]).to eq(state)
+ end
+end
+
+RSpec.describe MSpec, ".retrieve_shared" do
+ it "retrieves the shared ContextState matching description" do
+ state = ContextState.new ""
+ MSpec.retrieve(:shared)["shared"] = state
+ expect(MSpec.retrieve_shared(:shared)).to eq(state)
+ end
+end
diff --git a/spec/mspec/spec/runner/shared_spec.rb b/spec/mspec/spec/runner/shared_spec.rb
new file mode 100644
index 0000000000..153b8f0698
--- /dev/null
+++ b/spec/mspec/spec/runner/shared_spec.rb
@@ -0,0 +1,90 @@
+require 'spec_helper'
+require 'mspec/runner/shared'
+require 'mspec/runner/context'
+require 'mspec/runner/example'
+
+RSpec.describe Object, "#it_behaves_like" do
+ before :each do
+ ScratchPad.clear
+
+ MSpec.setup_env
+
+ @state = ContextState.new "Top level"
+ @state.instance_variable_set :@parsed, true
+ @state.singleton_class.send(:public, :it_behaves_like)
+
+ @shared = ContextState.new :shared_spec, :shared => true
+ allow(MSpec).to receive(:retrieve_shared).and_return(@shared)
+ end
+
+ it "creates @method set to the name of the aliased method" do
+ @shared.it("an example") { ScratchPad.record @method }
+ @state.it_behaves_like :shared_spec, :some_method
+ @state.process
+ expect(ScratchPad.recorded).to eq(:some_method)
+ end
+
+ it "creates @object if the passed object" do
+ object = Object.new
+ @shared.it("an example") { ScratchPad.record @object }
+ @state.it_behaves_like :shared_spec, :some_method, object
+ @state.process
+ expect(ScratchPad.recorded).to eq(object)
+ end
+
+ it "creates @object if the passed false" do
+ object = false
+ @shared.it("an example") { ScratchPad.record @object }
+ @state.it_behaves_like :shared_spec, :some_method, object
+ @state.process
+ expect(ScratchPad.recorded).to eq(object)
+ end
+
+ it "sends :it_should_behave_like" do
+ expect(@state).to receive(:it_should_behave_like)
+ @state.it_behaves_like :shared_spec, :some_method
+ end
+
+ describe "with multiple shared contexts" do
+ before :each do
+ @obj = Object.new
+ @obj2 = Object.new
+
+ @state2 = ContextState.new "Second top level"
+ @state2.instance_variable_set :@parsed, true
+ @state2.singleton_class.send(:public, :it_behaves_like)
+ end
+
+ it "ensures the shared spec state is distinct" do
+ @shared.it("an example") { ScratchPad.record [@method, @object] }
+
+ @state.it_behaves_like :shared_spec, :some_method, @obj
+
+ @state.process
+ expect(ScratchPad.recorded).to eq([:some_method, @obj])
+
+ @state2.it_behaves_like :shared_spec, :another_method, @obj2
+
+ @state2.process
+ expect(ScratchPad.recorded).to eq([:another_method, @obj2])
+ end
+
+ it "ensures the shared spec state is distinct for nested shared specs" do
+ nested = ContextState.new "nested context"
+ nested.instance_variable_set :@parsed, true
+ nested.parent = @shared
+
+ nested.it("another example") { ScratchPad.record [:shared, @method, @object] }
+
+ @state.it_behaves_like :shared_spec, :some_method, @obj
+
+ @state.process
+ expect(ScratchPad.recorded).to eq([:shared, :some_method, @obj])
+
+ @state2.it_behaves_like :shared_spec, :another_method, @obj2
+
+ @state2.process
+ expect(ScratchPad.recorded).to eq([:shared, :another_method, @obj2])
+ end
+ end
+end
diff --git a/spec/mspec/spec/runner/tag_spec.rb b/spec/mspec/spec/runner/tag_spec.rb
new file mode 100644
index 0000000000..bda9ac4280
--- /dev/null
+++ b/spec/mspec/spec/runner/tag_spec.rb
@@ -0,0 +1,123 @@
+require 'spec_helper'
+require 'mspec/runner/tag'
+
+RSpec.describe SpecTag do
+ it "accepts an optional string to parse into fields" do
+ tag = SpecTag.new "tag(comment):description"
+ expect(tag.tag).to eq("tag")
+ expect(tag.comment).to eq("comment")
+ expect(tag.description).to eq("description")
+ end
+end
+
+RSpec.describe SpecTag, "#parse" do
+ before :each do
+ @tag = SpecTag.new
+ end
+
+ it "accepts 'tag(comment):description'" do
+ @tag.parse "tag(I'm real):Some#method returns a value"
+ expect(@tag.tag).to eq("tag")
+ expect(@tag.comment).to eq("I'm real")
+ expect(@tag.description).to eq("Some#method returns a value")
+ end
+
+ it "accepts 'tag:description'" do
+ @tag.parse "tag:Another#method"
+ expect(@tag.tag).to eq("tag")
+ expect(@tag.comment).to eq(nil)
+ expect(@tag.description).to eq("Another#method")
+ end
+
+ it "accepts 'tag():description'" do
+ @tag.parse "tag():Another#method"
+ expect(@tag.tag).to eq("tag")
+ expect(@tag.comment).to eq(nil)
+ expect(@tag.description).to eq("Another#method")
+ end
+
+ it "accepts 'tag:'" do
+ @tag.parse "tag:"
+ expect(@tag.tag).to eq("tag")
+ expect(@tag.comment).to eq(nil)
+ expect(@tag.description).to eq("")
+ end
+
+ it "accepts 'tag(bug:555):Another#method'" do
+ @tag.parse "tag(bug:555):Another#method"
+ expect(@tag.tag).to eq("tag")
+ expect(@tag.comment).to eq("bug:555")
+ expect(@tag.description).to eq("Another#method")
+ end
+
+ it "accepts 'tag(http://someplace.com/neato):Another#method'" do
+ @tag.parse "tag(http://someplace.com/neato):Another#method"
+ expect(@tag.tag).to eq("tag")
+ expect(@tag.comment).to eq("http://someplace.com/neato")
+ expect(@tag.description).to eq("Another#method")
+ end
+
+ it "accepts 'tag(comment):\"Multi-line\\ntext\"'" do
+ @tag.parse 'tag(comment):"Multi-line\ntext"'
+ expect(@tag.tag).to eq("tag")
+ expect(@tag.comment).to eq("comment")
+ expect(@tag.description).to eq("Multi-line\ntext")
+ end
+
+ it "ignores '#anything'" do
+ @tag.parse "# this could be a comment"
+ expect(@tag.tag).to eq(nil)
+ expect(@tag.comment).to eq(nil)
+ expect(@tag.description).to eq(nil)
+ end
+end
+
+RSpec.describe SpecTag, "#to_s" do
+ it "formats itself as 'tag(comment):description'" do
+ txt = "tag(comment):description"
+ tag = SpecTag.new txt
+ expect(tag.tag).to eq("tag")
+ expect(tag.comment).to eq("comment")
+ expect(tag.description).to eq("description")
+ expect(tag.to_s).to eq(txt)
+ end
+
+ it "formats itself as 'tag:description" do
+ txt = "tag:description"
+ tag = SpecTag.new txt
+ expect(tag.tag).to eq("tag")
+ expect(tag.comment).to eq(nil)
+ expect(tag.description).to eq("description")
+ expect(tag.to_s).to eq(txt)
+ end
+
+ it "formats itself as 'tag(comment):\"multi-line\\ntext\\ntag\"'" do
+ txt = 'tag(comment):"multi-line\ntext\ntag"'
+ tag = SpecTag.new txt
+ expect(tag.tag).to eq("tag")
+ expect(tag.comment).to eq("comment")
+ expect(tag.description).to eq("multi-line\ntext\ntag")
+ expect(tag.to_s).to eq(txt)
+ end
+end
+
+RSpec.describe SpecTag, "#==" do
+ it "returns true if the tags have the same fields" do
+ one = SpecTag.new "tag(this):unicorn"
+ two = SpecTag.new "tag(this):unicorn"
+ expect(one.==(two)).to eq(true)
+ expect([one].==([two])).to eq(true)
+ end
+end
+
+RSpec.describe SpecTag, "#unescape" do
+ it "replaces \\n by LF when the description is quoted" do
+ tag = SpecTag.new 'tag:"desc with\nnew line"'
+ expect(tag.description).to eq("desc with\nnew line")
+ end
+
+ it "does not replaces \\n by LF when the description is not quoted " do
+ tag = SpecTag.new 'tag:desc with\nnew line'
+ expect(tag.description).to eq("desc with\\nnew line")
+ end
+end
diff --git a/spec/mspec/spec/runner/tags.txt b/spec/mspec/spec/runner/tags.txt
new file mode 100644
index 0000000000..f4eb6ad034
--- /dev/null
+++ b/spec/mspec/spec/runner/tags.txt
@@ -0,0 +1,4 @@
+fail(broken):Some#method? works
+incomplete(20%):The#best method ever
+benchmark(0.01825):The#fastest method today
+extended():"Multi-line\ntext\ntag"
diff --git a/spec/mspec/spec/spec_helper.rb b/spec/mspec/spec/spec_helper.rb
new file mode 100644
index 0000000000..3a749581ee
--- /dev/null
+++ b/spec/mspec/spec/spec_helper.rb
@@ -0,0 +1,68 @@
+RSpec.configure do |config|
+ config.disable_monkey_patching!
+ config.raise_errors_for_deprecations!
+end
+
+require 'mspec'
+
+# Remove this when MRI has intelligent warnings
+$VERBOSE = nil unless $VERBOSE
+
+class MOSConfig < Hash
+ def initialize
+ self[:loadpath] = []
+ self[:requires] = []
+ self[:flags] = []
+ self[:options] = []
+ self[:includes] = []
+ self[:excludes] = []
+ self[:patterns] = []
+ self[:xpatterns] = []
+ self[:tags] = []
+ self[:xtags] = []
+ self[:atags] = []
+ self[:astrings] = []
+ self[:target] = 'ruby'
+ self[:command] = nil
+ self[:ltags] = []
+ self[:files] = []
+ self[:launch] = []
+ end
+end
+
+def new_option
+ config = MOSConfig.new
+ return MSpecOptions.new("spec", 20, config), config
+end
+
+# Just to have an exception name output not be "Exception"
+class MSpecExampleError < Exception
+end
+
+def hide_deprecation_warnings
+ allow(MSpec).to receive(:deprecate)
+end
+
+def run_mspec(command, args)
+ cwd = Dir.pwd
+ command = " #{command}" unless command.start_with?('-')
+ cmd = "#{cwd}/bin/mspec#{command} -B spec/fixtures/config.mspec #{args}"
+ out = `#{cmd} 2>&1`
+ ret = $?
+ out = out.sub(/\A\$.+\n/, '') # Remove printed command line
+ out = out.sub(RUBY_DESCRIPTION, "RUBY_DESCRIPTION")
+ out = out.gsub(/\d+\.\d{6}/, "D.DDDDDD") # Specs total time
+ out = out.gsub(/\d{2}:\d{2}:\d{2}/, "00:00:00") # Progress bar time
+ out = out.gsub(cwd, "CWD")
+ return out, ret
+end
+
+def ensure_mspec_method(method)
+ file, _line = method.source_location
+ expect(file).to start_with(File.expand_path('../../lib/mspec', __FILE__ ))
+end
+
+PublicMSpecMatchers = Class.new {
+ include MSpecMatchers
+ public :raise_error
+}.new
diff --git a/spec/mspec/spec/utils/deprecate_spec.rb b/spec/mspec/spec/utils/deprecate_spec.rb
new file mode 100644
index 0000000000..73eaf7d04e
--- /dev/null
+++ b/spec/mspec/spec/utils/deprecate_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+require 'mspec/utils/deprecate'
+
+RSpec.describe MSpec, "#deprecate" do
+ it "warns when using a deprecated method" do
+ warning = nil
+ allow($stderr).to receive(:puts) { |str| warning = str }
+ MSpec.deprecate(:some_method, :other_method)
+ expect(warning).to start_with(<<-EOS.chomp)
+
+some_method is deprecated, use other_method instead.
+from
+EOS
+ expect(warning).to include(__FILE__)
+ expect(warning).to include('8')
+ end
+end
diff --git a/spec/mspec/spec/utils/name_map_spec.rb b/spec/mspec/spec/utils/name_map_spec.rb
new file mode 100644
index 0000000000..a18a481500
--- /dev/null
+++ b/spec/mspec/spec/utils/name_map_spec.rb
@@ -0,0 +1,175 @@
+require 'spec_helper'
+require 'mspec/utils/name_map'
+
+module NameMapSpecs
+ class A
+ A = self
+
+ def self.a; end
+ def a; end
+ def c; end
+
+ class B
+ def b; end
+ end
+ end
+
+ class Error
+ end
+
+ class Fixnum
+ def f; end
+ end
+
+ def self.n; end
+ def n; end
+end
+
+RSpec.describe NameMap, "#exception?" do
+ before :each do
+ @map = NameMap.new
+ end
+
+ it "returns true if the constant is Errno" do
+ expect(@map.exception?("Errno")).to eq(true)
+ end
+
+ it "returns true if the constant is a kind of Exception" do
+ expect(@map.exception?("Errno::EBADF")).to eq(true)
+ expect(@map.exception?("LoadError")).to eq(true)
+ expect(@map.exception?("SystemExit")).to eq(true)
+ end
+
+ it "returns false if the constant is not a kind of Exception" do
+ expect(@map.exception?("NameMapSpecs::Error")).to eq(false)
+ expect(@map.exception?("NameMapSpecs")).to eq(false)
+ end
+
+ it "returns false if the constant does not exist" do
+ expect(@map.exception?("Nonexistent")).to eq(false)
+ end
+end
+
+RSpec.describe NameMap, "#class_or_module" do
+ before :each do
+ @map = NameMap.new true
+ end
+
+ it "returns the constant specified by the string" do
+ expect(@map.class_or_module("NameMapSpecs")).to eq(NameMapSpecs)
+ end
+
+ it "returns the constant specified by the 'A::B' string" do
+ expect(@map.class_or_module("NameMapSpecs::A")).to eq(NameMapSpecs::A)
+ end
+
+ it "returns nil if the constant is not a class or module" do
+ expect(@map.class_or_module("Float::MAX")).to eq(nil)
+ end
+
+ it "returns nil if the constant is in the set of excluded constants" do
+ excluded = %w[
+ MSpecScript
+ MkSpec
+ NameMap
+ ]
+
+ excluded.each do |const|
+ expect(@map.class_or_module(const)).to eq(nil)
+ end
+ end
+
+ it "returns nil if the constant does not exist" do
+ expect(@map.class_or_module("Heaven")).to eq(nil)
+ expect(@map.class_or_module("Hell")).to eq(nil)
+ expect(@map.class_or_module("Bush::Brain")).to eq(nil)
+ end
+end
+
+RSpec.describe NameMap, "#dir_name" do
+ before :each do
+ @map = NameMap.new
+ end
+
+ it "returns a directory name from the base name and constant" do
+ expect(@map.dir_name("NameMapSpecs", 'spec/core')).to eq('spec/core/namemapspecs')
+ end
+
+ it "returns a directory name from the components in the constants name" do
+ expect(@map.dir_name("NameMapSpecs::A", 'spec')).to eq('spec/namemapspecs/a')
+ expect(@map.dir_name("NameMapSpecs::A::B", 'spec')).to eq('spec/namemapspecs/a/b')
+ end
+
+ it "returns a directory name without 'class' for constants like TrueClass" do
+ expect(@map.dir_name("TrueClass", 'spec')).to eq('spec/true')
+ expect(@map.dir_name("FalseClass", 'spec')).to eq('spec/false')
+ end
+
+ it "returns 'exception' for the directory name of any Exception subclass" do
+ expect(@map.dir_name("SystemExit", 'spec')).to eq('spec/exception')
+ expect(@map.dir_name("Errno::EBADF", 'spec')).to eq('spec/exception')
+ end
+
+ it "returns 'class' for Class" do
+ expect(@map.dir_name("Class", 'spec')).to eq('spec/class')
+ end
+end
+
+# These specs do not cover all the mappings, but only describe how the
+# name is derived when the hash item maps to a single value, a hash with
+# a specific item, or a hash with a :default item.
+RSpec.describe NameMap, "#file_name" do
+ before :each do
+ @map = NameMap.new
+ end
+
+ it "returns the name of the spec file based on the constant and method" do
+ expect(@map.file_name("[]=", "Array")).to eq("element_set_spec.rb")
+ end
+
+ it "returns the name of the spec file based on the special entry for the method" do
+ expect(@map.file_name("~", "Regexp")).to eq("match_spec.rb")
+ expect(@map.file_name("~", "Integer")).to eq("complement_spec.rb")
+ end
+
+ it "returns the name of the spec file based on the default entry for the method" do
+ expect(@map.file_name("<<", "NameMapSpecs")).to eq("append_spec.rb")
+ end
+
+ it "uses the last component of the constant to look up the method name" do
+ expect(@map.file_name("^", "NameMapSpecs::Integer")).to eq("bit_xor_spec.rb")
+ end
+end
+
+RSpec.describe NameMap, "#namespace" do
+ before :each do
+ @map = NameMap.new
+ end
+
+ it "prepends the module to the constant name" do
+ expect(@map.namespace("SubModule", Integer)).to eq("SubModule::Integer")
+ end
+
+ it "does not prepend Object, Class, or Module to the constant name" do
+ expect(@map.namespace("Object", String)).to eq("String")
+ expect(@map.namespace("Module", Integer)).to eq("Integer")
+ expect(@map.namespace("Class", Float)).to eq("Float")
+ end
+end
+
+RSpec.describe NameMap, "#map" do
+ before :each do
+ @map = NameMap.new
+ end
+
+ it "flattens an object hierarchy into a single Hash" do
+ expect(@map.map({}, [NameMapSpecs])).to eq({
+ "NameMapSpecs." => ["n"],
+ "NameMapSpecs#" => ["n"],
+ "NameMapSpecs::A." => ["a"],
+ "NameMapSpecs::A#" => ["a", "c"],
+ "NameMapSpecs::A::B#" => ["b"],
+ "NameMapSpecs::Fixnum#" => ["f"]
+ })
+ end
+end
diff --git a/spec/mspec/spec/utils/options_spec.rb b/spec/mspec/spec/utils/options_spec.rb
new file mode 100644
index 0000000000..2e3925f579
--- /dev/null
+++ b/spec/mspec/spec/utils/options_spec.rb
@@ -0,0 +1,1302 @@
+require 'spec_helper'
+require 'mspec/utils/options'
+require 'mspec/version'
+require 'mspec/guards/guard'
+require 'mspec/runner/mspec'
+require 'mspec/runner/formatters'
+
+RSpec.describe MSpecOption, ".new" do
+ before :each do
+ @opt = MSpecOption.new("-a", "--bdc", "ARG", "desc", :block)
+ end
+
+ it "sets the short attribute" do
+ expect(@opt.short).to eq("-a")
+ end
+
+ it "sets the long attribute" do
+ expect(@opt.long).to eq("--bdc")
+ end
+
+ it "sets the arg attribute" do
+ expect(@opt.arg).to eq("ARG")
+ end
+
+ it "sets the description attribute" do
+ expect(@opt.description).to eq("desc")
+ end
+
+ it "sets the block attribute" do
+ expect(@opt.block).to eq(:block)
+ end
+end
+
+RSpec.describe MSpecOption, "#arg?" do
+ it "returns true if arg attribute is not nil" do
+ expect(MSpecOption.new(nil, nil, "ARG", nil, nil).arg?).to be_truthy
+ end
+
+ it "returns false if arg attribute is nil" do
+ expect(MSpecOption.new(nil, nil, nil, nil, nil).arg?).to be_falsey
+ end
+end
+
+RSpec.describe MSpecOption, "#match?" do
+ before :each do
+ @opt = MSpecOption.new("-a", "--bdc", "ARG", "desc", :block)
+ end
+
+ it "returns true if the argument matches the short option" do
+ expect(@opt.match?("-a")).to be_truthy
+ end
+
+ it "returns true if the argument matches the long option" do
+ expect(@opt.match?("--bdc")).to be_truthy
+ end
+
+ it "returns false if the argument matches neither the short nor long option" do
+ expect(@opt.match?("-b")).to be_falsey
+ expect(@opt.match?("-abdc")).to be_falsey
+ end
+end
+
+RSpec.describe MSpecOptions, ".new" do
+ before :each do
+ @opt = MSpecOptions.new("cmd", 20, :config)
+ end
+
+ it "sets the banner attribute" do
+ expect(@opt.banner).to eq("cmd")
+ end
+
+ it "sets the config attribute" do
+ expect(@opt.config).to eq(:config)
+ end
+
+ it "sets the width attribute" do
+ expect(@opt.width).to eq(20)
+ end
+
+ it "sets the default width attribute" do
+ expect(MSpecOptions.new.width).to eq(30)
+ end
+end
+
+RSpec.describe MSpecOptions, "#on" do
+ before :each do
+ @opt = MSpecOptions.new
+ end
+
+ it "adds a short option" do
+ expect(@opt).to receive(:add).with("-a", nil, nil, "desc", nil)
+ @opt.on("-a", "desc")
+ end
+
+ it "adds a short option taking an argument" do
+ expect(@opt).to receive(:add).with("-a", nil, "ARG", "desc", nil)
+ @opt.on("-a", "ARG", "desc")
+ end
+
+ it "adds a long option" do
+ expect(@opt).to receive(:add).with("-a", nil, nil, "desc", nil)
+ @opt.on("-a", "desc")
+ end
+
+ it "adds a long option taking an argument" do
+ expect(@opt).to receive(:add).with("-a", nil, nil, "desc", nil)
+ @opt.on("-a", "desc")
+ end
+
+ it "adds a short and long option" do
+ expect(@opt).to receive(:add).with("-a", nil, nil, "desc", nil)
+ @opt.on("-a", "desc")
+ end
+
+ it "adds a short and long option taking an argument" do
+ expect(@opt).to receive(:add).with("-a", nil, nil, "desc", nil)
+ @opt.on("-a", "desc")
+ end
+
+ it "raises MSpecOptions::OptionError if pass less than 2 arguments" do
+ expect { @opt.on }.to raise_error(MSpecOptions::OptionError)
+ expect { @opt.on "" }.to raise_error(MSpecOptions::OptionError)
+ end
+end
+
+RSpec.describe MSpecOptions, "#add" do
+ before :each do
+ @opt = MSpecOptions.new "cmd", 20
+ @prc = lambda { }
+ end
+
+ it "adds documentation for an option" do
+ expect(@opt).to receive(:doc).with(" -t, --typo ARG Correct typo ARG")
+ @opt.add("-t", "--typo", "ARG", "Correct typo ARG", @prc)
+ end
+
+ it "leaves spaces in the documentation for a missing short option" do
+ expect(@opt).to receive(:doc).with(" --typo ARG Correct typo ARG")
+ @opt.add(nil, "--typo", "ARG", "Correct typo ARG", @prc)
+ end
+
+ it "handles a short option with argument but no long argument" do
+ expect(@opt).to receive(:doc).with(" -t ARG Correct typo ARG")
+ @opt.add("-t", nil, "ARG", "Correct typo ARG", @prc)
+ end
+
+ it "registers an option" do
+ option = MSpecOption.new "-t", "--typo", "ARG", "Correct typo ARG", @prc
+ expect(MSpecOption).to receive(:new).with(
+ "-t", "--typo", "ARG", "Correct typo ARG", @prc).and_return(option)
+ @opt.add("-t", "--typo", "ARG", "Correct typo ARG", @prc)
+ expect(@opt.options).to eq([option])
+ end
+end
+
+RSpec.describe MSpecOptions, "#match?" do
+ before :each do
+ @opt = MSpecOptions.new
+ end
+
+ it "returns the MSpecOption instance matching the argument" do
+ @opt.on "-a", "--abdc", "desc"
+ option = @opt.match? "-a"
+ expect(@opt.match?("--abdc")).to be(option)
+ expect(option).to be_kind_of(MSpecOption)
+ expect(option.short).to eq("-a")
+ expect(option.long).to eq("--abdc")
+ expect(option.description).to eq("desc")
+ end
+end
+
+RSpec.describe MSpecOptions, "#process" do
+ before :each do
+ @opt = MSpecOptions.new
+ ScratchPad.clear
+ end
+
+ it "calls the on_extra block if the argument does not match any option" do
+ @opt.on_extra { ScratchPad.record :extra }
+ @opt.process ["-a"], "-a", "-a", nil
+ expect(ScratchPad.recorded).to eq(:extra)
+ end
+
+ it "returns the matching option" do
+ @opt.on "-a", "ARG", "desc"
+ option = @opt.process [], "-a", "-a", "ARG"
+ expect(option).to be_kind_of(MSpecOption)
+ expect(option.short).to eq("-a")
+ expect(option.arg).to eq("ARG")
+ expect(option.description).to eq("desc")
+ end
+
+ it "raises an MSpecOptions::ParseError if arg is nil and there are no more entries in argv" do
+ @opt.on "-a", "ARG", "desc"
+ expect { @opt.process [], "-a", "-a", nil }.to raise_error(MSpecOptions::ParseError)
+ end
+
+ it "fetches the argument for the option from argv if arg is nil" do
+ @opt.on("-a", "ARG", "desc") { |o| ScratchPad.record o }
+ @opt.process ["ARG"], "-a", "-a", nil
+ expect(ScratchPad.recorded).to eq("ARG")
+ end
+
+ it "calls the option's block" do
+ @opt.on("-a", "ARG", "desc") { ScratchPad.record :option }
+ @opt.process [], "-a", "-a", "ARG"
+ expect(ScratchPad.recorded).to eq(:option)
+ end
+
+ it "does not call the option's block if it is nil" do
+ @opt.on "-a", "ARG", "desc"
+ expect { @opt.process [], "-a", "-a", "ARG" }.not_to raise_error
+ end
+end
+
+RSpec.describe MSpecOptions, "#split" do
+ before :each do
+ @opt = MSpecOptions.new
+ end
+
+ it "breaks a string at the nth character" do
+ opt, arg, rest = @opt.split "-bdc", 2
+ expect(opt).to eq("-b")
+ expect(arg).to eq("dc")
+ expect(rest).to eq("dc")
+ end
+
+ it "returns nil for arg if there are no characters left" do
+ opt, arg, rest = @opt.split "-b", 2
+ expect(opt).to eq("-b")
+ expect(arg).to eq(nil)
+ expect(rest).to eq("")
+ end
+end
+
+RSpec.describe MSpecOptions, "#parse" do
+ before :each do
+ @opt = MSpecOptions.new
+ @prc = lambda { ScratchPad.record :parsed }
+ @arg_prc = lambda { |o| ScratchPad.record [:parsed, o] }
+ ScratchPad.clear
+ end
+
+ it "parses a short option" do
+ @opt.on "-a", "desc", &@prc
+ @opt.parse ["-a"]
+ expect(ScratchPad.recorded).to eq(:parsed)
+ end
+
+ it "parse a long option" do
+ @opt.on "--abdc", "desc", &@prc
+ @opt.parse ["--abdc"]
+ expect(ScratchPad.recorded).to eq(:parsed)
+ end
+
+ it "parses a short option group" do
+ @opt.on "-a", "ARG", "desc", &@arg_prc
+ @opt.parse ["-a", "ARG"]
+ expect(ScratchPad.recorded).to eq([:parsed, "ARG"])
+ end
+
+ it "parses a short option with an argument" do
+ @opt.on "-a", "ARG", "desc", &@arg_prc
+ @opt.parse ["-a", "ARG"]
+ expect(ScratchPad.recorded).to eq([:parsed, "ARG"])
+ end
+
+ it "parses a short option with connected argument" do
+ @opt.on "-a", "ARG", "desc", &@arg_prc
+ @opt.parse ["-aARG"]
+ expect(ScratchPad.recorded).to eq([:parsed, "ARG"])
+ end
+
+ it "parses a long option with an argument" do
+ @opt.on "--abdc", "ARG", "desc", &@arg_prc
+ @opt.parse ["--abdc", "ARG"]
+ expect(ScratchPad.recorded).to eq([:parsed, "ARG"])
+ end
+
+ it "parses a long option with an '=' argument" do
+ @opt.on "--abdc", "ARG", "desc", &@arg_prc
+ @opt.parse ["--abdc=ARG"]
+ expect(ScratchPad.recorded).to eq([:parsed, "ARG"])
+ end
+
+ it "parses a short option group with the final option taking an argument" do
+ ScratchPad.record []
+ @opt.on("-a", "desc") { |o| ScratchPad << :a }
+ @opt.on("-b", "ARG", "desc") { |o| ScratchPad << [:b, o] }
+ @opt.parse ["-ab", "ARG"]
+ expect(ScratchPad.recorded).to eq([:a, [:b, "ARG"]])
+ end
+
+ it "parses a short option group with a connected argument" do
+ ScratchPad.record []
+ @opt.on("-a", "desc") { |o| ScratchPad << :a }
+ @opt.on("-b", "ARG", "desc") { |o| ScratchPad << [:b, o] }
+ @opt.on("-c", "desc") { |o| ScratchPad << :c }
+ @opt.parse ["-acbARG"]
+ expect(ScratchPad.recorded).to eq([:a, :c, [:b, "ARG"]])
+ end
+
+ it "returns the unprocessed entries" do
+ @opt.on "-a", "ARG", "desc", &@arg_prc
+ expect(@opt.parse(["abdc", "-a", "ilny"])).to eq(["abdc"])
+ end
+
+ it "calls the on_extra handler with unrecognized options" do
+ ScratchPad.record []
+ @opt.on_extra { |o| ScratchPad << o }
+ @opt.on "-a", "desc"
+ @opt.parse ["-a", "-b"]
+ expect(ScratchPad.recorded).to eq(["-b"])
+ end
+
+ it "does not attempt to call the block if it is nil" do
+ @opt.on "-a", "ARG", "desc"
+ expect(@opt.parse(["-a", "ARG"])).to eq([])
+ end
+
+ it "raises MSpecOptions::ParseError if passed an unrecognized option" do
+ expect(@opt).to receive(:raise).with(MSpecOptions::ParseError, an_instance_of(String))
+ allow(@opt).to receive(:puts)
+ allow(@opt).to receive(:exit)
+ @opt.parse "-u"
+ end
+end
+
+RSpec.describe MSpecOptions, "#banner=" do
+ before :each do
+ @opt = MSpecOptions.new
+ end
+
+ it "sets the banner attribute" do
+ expect(@opt.banner).to eq("")
+ @opt.banner = "banner"
+ expect(@opt.banner).to eq("banner")
+ end
+end
+
+RSpec.describe MSpecOptions, "#width=" do
+ before :each do
+ @opt = MSpecOptions.new
+ end
+
+ it "sets the width attribute" do
+ expect(@opt.width).to eq(30)
+ @opt.width = 20
+ expect(@opt.width).to eq(20)
+ end
+end
+
+RSpec.describe MSpecOptions, "#config=" do
+ before :each do
+ @opt = MSpecOptions.new
+ end
+
+ it "sets the config attribute" do
+ expect(@opt.config).to be_nil
+ @opt.config = :config
+ expect(@opt.config).to eq(:config)
+ end
+end
+
+RSpec.describe MSpecOptions, "#doc" do
+ before :each do
+ @opt = MSpecOptions.new "command"
+ end
+
+ it "adds text to be displayed with #to_s" do
+ @opt.doc "Some message"
+ @opt.doc "Another message"
+ expect(@opt.to_s).to eq <<-EOD
+command
+
+Some message
+Another message
+EOD
+ end
+end
+
+RSpec.describe MSpecOptions, "#version" do
+ before :each do
+ @opt = MSpecOptions.new
+ ScratchPad.clear
+ end
+
+ it "installs a basic -v, --version option" do
+ expect(@opt).to receive(:puts)
+ expect(@opt).to receive(:exit)
+ @opt.version "1.0.0"
+ @opt.parse "-v"
+ end
+
+ it "accepts a block instead of using the default block" do
+ @opt.version("1.0.0") { |o| ScratchPad.record :version }
+ @opt.parse "-v"
+ expect(ScratchPad.recorded).to eq(:version)
+ end
+end
+
+RSpec.describe MSpecOptions, "#help" do
+ before :each do
+ @opt = MSpecOptions.new
+ ScratchPad.clear
+ end
+
+ it "installs a basic -h, --help option" do
+ expect(@opt).to receive(:puts)
+ expect(@opt).to receive(:exit).with(1)
+ @opt.help
+ @opt.parse "-h"
+ end
+
+ it "accepts a block instead of using the default block" do
+ @opt.help { |o| ScratchPad.record :help }
+ @opt.parse "-h"
+ expect(ScratchPad.recorded).to eq(:help)
+ end
+end
+
+RSpec.describe MSpecOptions, "#on_extra" do
+ before :each do
+ @opt = MSpecOptions.new
+ ScratchPad.clear
+ end
+
+ it "registers a block to be called when an option is not recognized" do
+ @opt.on_extra { ScratchPad.record :extra }
+ @opt.parse "-g"
+ expect(ScratchPad.recorded).to eq(:extra)
+ end
+end
+
+RSpec.describe MSpecOptions, "#to_s" do
+ before :each do
+ @opt = MSpecOptions.new "command"
+ end
+
+ it "returns the banner and descriptive strings for all registered options" do
+ @opt.on "-t", "--this ARG", "Adds this ARG to the list"
+ expect(@opt.to_s).to eq <<-EOD
+command
+
+ -t, --this ARG Adds this ARG to the list
+EOD
+ end
+end
+
+RSpec.describe "The -B, --config FILE option" do
+ before :each do
+ @options, @config = new_option
+ end
+
+ it "is enabled with #configure { }" do
+ expect(@options).to receive(:on).with("-B", "--config", "FILE",
+ an_instance_of(String))
+ @options.configure {}
+ end
+
+ it "calls the passed block" do
+ ["-B", "--config"].each do |opt|
+ ScratchPad.clear
+
+ @options.configure { |x| ScratchPad.record x }
+ @options.parse [opt, "file"]
+ expect(ScratchPad.recorded).to eq("file")
+ end
+ end
+end
+
+RSpec.describe "The -C, --chdir DIR option" do
+ before :each do
+ @options, @config = new_option
+ @options.chdir
+ end
+
+ it "is enabled with #chdir" do
+ expect(@options).to receive(:on).with("-C", "--chdir", "DIR",
+ an_instance_of(String))
+ @options.chdir
+ end
+
+ it "changes the working directory to DIR" do
+ expect(Dir).to receive(:chdir).with("dir").twice
+ ["-C", "--chdir"].each do |opt|
+ @options.parse [opt, "dir"]
+ end
+ end
+end
+
+RSpec.describe "The --prefix STR option" do
+ before :each do
+ @options, @config = new_option
+ end
+
+ it "is enabled with #prefix" do
+ expect(@options).to receive(:on).with("--prefix", "STR",
+ an_instance_of(String))
+ @options.prefix
+ end
+
+ it "sets the prefix config value" do
+ @options.prefix
+ @options.parse ["--prefix", "some/dir"]
+ expect(@config[:prefix]).to eq("some/dir")
+ end
+end
+
+RSpec.describe "The -t, --target TARGET option" do
+ before :each do
+ @options, @config = new_option
+ @options.targets
+ end
+
+ it "is enabled with #targets" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-t", "--target", "TARGET",
+ an_instance_of(String))
+ @options.targets
+ end
+
+ it "sets the target to 'ruby' and flags to verbose with TARGET 'r' or 'ruby'" do
+ ["-t", "--target"].each do |opt|
+ ["r", "ruby"].each do |t|
+ @config[:target] = nil
+ @options.parse [opt, t]
+ expect(@config[:target]).to eq("ruby")
+ end
+ end
+ end
+
+ it "sets the target to 'jruby' with TARGET 'j' or 'jruby'" do
+ ["-t", "--target"].each do |opt|
+ ["j", "jruby"].each do |t|
+ @config[:target] = nil
+ @options.parse [opt, t]
+ expect(@config[:target]).to eq("jruby")
+ end
+ end
+ end
+
+ it "sets the target to 'shotgun/rubinius' with TARGET 'x' or 'rubinius'" do
+ ["-t", "--target"].each do |opt|
+ ["x", "rubinius"].each do |t|
+ @config[:target] = nil
+ @options.parse [opt, t]
+ expect(@config[:target]).to eq("./bin/rbx")
+ end
+ end
+ end
+
+ it "set the target to 'rbx' with TARGET 'rbx'" do
+ ["-t", "--target"].each do |opt|
+ ["X", "rbx"].each do |t|
+ @config[:target] = nil
+ @options.parse [opt, t]
+ expect(@config[:target]).to eq("rbx")
+ end
+ end
+ end
+
+ it "sets the target to 'maglev' with TARGET 'm' or 'maglev'" do
+ ["-t", "--target"].each do |opt|
+ ["m", "maglev"].each do |t|
+ @config[:target] = nil
+ @options.parse [opt, t]
+ expect(@config[:target]).to eq("maglev-ruby")
+ end
+ end
+ end
+
+ it "sets the target to 'topaz' with TARGET 't' or 'topaz'" do
+ ["-t", "--target"].each do |opt|
+ ["t", "topaz"].each do |t|
+ @config[:target] = nil
+ @options.parse [opt, t]
+ expect(@config[:target]).to eq("topaz")
+ end
+ end
+ end
+
+ it "sets the target to TARGET" do
+ ["-t", "--target"].each do |opt|
+ @config[:target] = nil
+ @options.parse [opt, "whateva"]
+ expect(@config[:target]).to eq("whateva")
+ end
+ end
+end
+
+RSpec.describe "The -T, --target-opt OPT option" do
+ before :each do
+ @options, @config = new_option
+ @options.targets
+ end
+
+ it "is enabled with #targets" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-T", "--target-opt", "OPT",
+ an_instance_of(String))
+ @options.targets
+ end
+
+ it "adds OPT to flags" do
+ ["-T", "--target-opt"].each do |opt|
+ @config[:flags].delete "--whateva"
+ @options.parse [opt, "--whateva"]
+ expect(@config[:flags]).to include("--whateva")
+ end
+ end
+end
+
+RSpec.describe "The -I, --include DIR option" do
+ before :each do
+ @options, @config = new_option
+ @options.targets
+ end
+
+ it "is enabled with #targets" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-I", "--include", "DIR",
+ an_instance_of(String))
+ @options.targets
+ end
+
+ it "add DIR to the load path" do
+ ["-I", "--include"].each do |opt|
+ @config[:loadpath].delete "-Ipackage"
+ @options.parse [opt, "package"]
+ expect(@config[:loadpath]).to include("-Ipackage")
+ end
+ end
+end
+
+RSpec.describe "The -r, --require LIBRARY option" do
+ before :each do
+ @options, @config = new_option
+ @options.targets
+ end
+
+ it "is enabled with #targets" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-r", "--require", "LIBRARY",
+ an_instance_of(String))
+ @options.targets
+ end
+
+ it "adds LIBRARY to the requires list" do
+ ["-r", "--require"].each do |opt|
+ @config[:requires].delete "-rlibrick"
+ @options.parse [opt, "librick"]
+ expect(@config[:requires]).to include("-rlibrick")
+ end
+ end
+end
+
+RSpec.describe "The -f, --format FORMAT option" do
+ before :each do
+ @options, @config = new_option
+ @options.formatters
+ end
+
+ it "is enabled with #formatters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-f", "--format", "FORMAT",
+ an_instance_of(String))
+ @options.formatters
+ end
+
+ it "sets the SpecdocFormatter with FORMAT 's' or 'specdoc'" do
+ ["-f", "--format"].each do |opt|
+ ["s", "specdoc"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(SpecdocFormatter)
+ end
+ end
+ end
+
+ it "sets the HtmlFormatter with FORMAT 'h' or 'html'" do
+ ["-f", "--format"].each do |opt|
+ ["h", "html"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(HtmlFormatter)
+ end
+ end
+ end
+
+ it "sets the DottedFormatter with FORMAT 'd', 'dot' or 'dotted'" do
+ ["-f", "--format"].each do |opt|
+ ["d", "dot", "dotted"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(DottedFormatter)
+ end
+ end
+ end
+
+ it "sets the DescribeFormatter with FORMAT 'b' or 'describe'" do
+ ["-f", "--format"].each do |opt|
+ ["b", "describe"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(DescribeFormatter)
+ end
+ end
+ end
+
+ it "sets the FileFormatter with FORMAT 'f', 'file'" do
+ ["-f", "--format"].each do |opt|
+ ["f", "file"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(FileFormatter)
+ end
+ end
+ end
+
+ it "sets the UnitdiffFormatter with FORMAT 'u', 'unit', or 'unitdiff'" do
+ ["-f", "--format"].each do |opt|
+ ["u", "unit", "unitdiff"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(UnitdiffFormatter)
+ end
+ end
+ end
+
+ it "sets the SummaryFormatter with FORMAT 'm' or 'summary'" do
+ ["-f", "--format"].each do |opt|
+ ["m", "summary"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(SummaryFormatter)
+ end
+ end
+ end
+
+ it "sets the SpinnerFormatter with FORMAT 'a', '*', or 'spin'" do
+ ["-f", "--format"].each do |opt|
+ ["a", "*", "spin"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(SpinnerFormatter)
+ end
+ end
+ end
+
+ it "sets the MethodFormatter with FORMAT 't' or 'method'" do
+ ["-f", "--format"].each do |opt|
+ ["t", "method"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(MethodFormatter)
+ end
+ end
+ end
+
+ it "sets the YamlFormatter with FORMAT 'y' or 'yaml'" do
+ ["-f", "--format"].each do |opt|
+ ["y", "yaml"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(YamlFormatter)
+ end
+ end
+ end
+
+ it "sets the JUnitFormatter with FORMAT 'j' or 'junit'" do
+ ["-f", "--format"].each do |opt|
+ ["j", "junit"].each do |f|
+ @config[:formatter] = nil
+ @options.parse [opt, f]
+ expect(@config[:formatter]).to eq(JUnitFormatter)
+ end
+ end
+ end
+end
+
+RSpec.describe "The -o, --output FILE option" do
+ before :each do
+ @options, @config = new_option
+ @options.formatters
+ end
+
+ it "is enabled with #formatters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-o", "--output", "FILE",
+ an_instance_of(String))
+ @options.formatters
+ end
+
+ it "sets the output to FILE" do
+ ["-o", "--output"].each do |opt|
+ @config[:output] = nil
+ @options.parse [opt, "some/file"]
+ expect(@config[:output]).to eq("some/file")
+ end
+ end
+end
+
+RSpec.describe "The -e, --example STR" do
+ before :each do
+ @options, @config = new_option
+ @options.filters
+ end
+
+ it "is enabled with #filters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-e", "--example", "STR",
+ an_instance_of(String))
+ @options.filters
+ end
+
+ it "adds STR to the includes list" do
+ ["-e", "--example"].each do |opt|
+ @config[:includes] = []
+ @options.parse [opt, "this spec"]
+ expect(@config[:includes]).to include("this spec")
+ end
+ end
+end
+
+RSpec.describe "The -E, --exclude STR" do
+ before :each do
+ @options, @config = new_option
+ @options.filters
+ end
+
+ it "is enabled with #filters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-E", "--exclude", "STR",
+ an_instance_of(String))
+ @options.filters
+ end
+
+ it "adds STR to the excludes list" do
+ ["-E", "--exclude"].each do |opt|
+ @config[:excludes] = []
+ @options.parse [opt, "this spec"]
+ expect(@config[:excludes]).to include("this spec")
+ end
+ end
+end
+
+RSpec.describe "The -p, --pattern PATTERN" do
+ before :each do
+ @options, @config = new_option
+ @options.filters
+ end
+
+ it "is enabled with #filters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-p", "--pattern", "PATTERN",
+ an_instance_of(String))
+ @options.filters
+ end
+
+ it "adds PATTERN to the included patterns list" do
+ ["-p", "--pattern"].each do |opt|
+ @config[:patterns] = []
+ @options.parse [opt, "this spec"]
+ expect(@config[:patterns]).to include(/this spec/)
+ end
+ end
+end
+
+RSpec.describe "The -P, --excl-pattern PATTERN" do
+ before :each do
+ @options, @config = new_option
+ @options.filters
+ end
+
+ it "is enabled with #filters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-P", "--excl-pattern", "PATTERN",
+ an_instance_of(String))
+ @options.filters
+ end
+
+ it "adds PATTERN to the excluded patterns list" do
+ ["-P", "--excl-pattern"].each do |opt|
+ @config[:xpatterns] = []
+ @options.parse [opt, "this spec"]
+ expect(@config[:xpatterns]).to include(/this spec/)
+ end
+ end
+end
+
+RSpec.describe "The -g, --tag TAG" do
+ before :each do
+ @options, @config = new_option
+ @options.filters
+ end
+
+ it "is enabled with #filters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-g", "--tag", "TAG",
+ an_instance_of(String))
+ @options.filters
+ end
+
+ it "adds TAG to the included tags list" do
+ ["-g", "--tag"].each do |opt|
+ @config[:tags] = []
+ @options.parse [opt, "this spec"]
+ expect(@config[:tags]).to include("this spec")
+ end
+ end
+end
+
+RSpec.describe "The -G, --excl-tag TAG" do
+ before :each do
+ @options, @config = new_option
+ @options.filters
+ end
+
+ it "is enabled with #filters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-G", "--excl-tag", "TAG",
+ an_instance_of(String))
+ @options.filters
+ end
+
+ it "adds TAG to the excluded tags list" do
+ ["-G", "--excl-tag"].each do |opt|
+ @config[:xtags] = []
+ @options.parse [opt, "this spec"]
+ expect(@config[:xtags]).to include("this spec")
+ end
+ end
+end
+
+RSpec.describe "The -w, --profile FILE option" do
+ before :each do
+ @options, @config = new_option
+ @options.filters
+ end
+
+ it "is enabled with #filters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-w", "--profile", "FILE",
+ an_instance_of(String))
+ @options.filters
+ end
+
+ it "adds FILE to the included profiles list" do
+ ["-w", "--profile"].each do |opt|
+ @config[:profiles] = []
+ @options.parse [opt, "spec/profiles/rails.yaml"]
+ expect(@config[:profiles]).to include("spec/profiles/rails.yaml")
+ end
+ end
+end
+
+RSpec.describe "The -W, --excl-profile FILE option" do
+ before :each do
+ @options, @config = new_option
+ @options.filters
+ end
+
+ it "is enabled with #filters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-W", "--excl-profile", "FILE",
+ an_instance_of(String))
+ @options.filters
+ end
+
+ it "adds FILE to the excluded profiles list" do
+ ["-W", "--excl-profile"].each do |opt|
+ @config[:xprofiles] = []
+ @options.parse [opt, "spec/profiles/rails.yaml"]
+ expect(@config[:xprofiles]).to include("spec/profiles/rails.yaml")
+ end
+ end
+end
+
+RSpec.describe "The -Z, --dry-run option" do
+ before :each do
+ @options, @config = new_option
+ @options.pretend
+ end
+
+ it "is enabled with #pretend" do
+ expect(@options).to receive(:on).with("-Z", "--dry-run", an_instance_of(String))
+ @options.pretend
+ end
+
+ it "registers the MSpec pretend mode" do
+ expect(MSpec).to receive(:register_mode).with(:pretend).twice
+ ["-Z", "--dry-run"].each do |opt|
+ @options.parse opt
+ end
+ end
+end
+
+RSpec.describe "The --unguarded option" do
+ before :each do
+ @options, @config = new_option
+ @options.unguarded
+ end
+
+ it "is enabled with #unguarded" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("--unguarded", an_instance_of(String))
+ @options.unguarded
+ end
+
+ it "registers the MSpec unguarded mode" do
+ expect(MSpec).to receive(:register_mode).with(:unguarded)
+ @options.parse "--unguarded"
+ end
+end
+
+RSpec.describe "The --no-ruby_guard option" do
+ before :each do
+ @options, @config = new_option
+ @options.unguarded
+ end
+
+ it "is enabled with #unguarded" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("--no-ruby_bug", an_instance_of(String))
+ @options.unguarded
+ end
+
+ it "registers the MSpec no_ruby_bug mode" do
+ expect(MSpec).to receive(:register_mode).with(:no_ruby_bug)
+ @options.parse "--no-ruby_bug"
+ end
+end
+
+RSpec.describe "The -H, --random option" do
+ before :each do
+ @options, @config = new_option
+ @options.randomize
+ end
+
+ it "is enabled with #randomize" do
+ expect(@options).to receive(:on).with("-H", "--random", an_instance_of(String))
+ @options.randomize
+ end
+
+ it "registers the MSpec randomize mode" do
+ expect(MSpec).to receive(:randomize=).twice
+ ["-H", "--random"].each do |opt|
+ @options.parse opt
+ end
+ end
+end
+
+RSpec.describe "The -R, --repeat option" do
+ before :each do
+ @options, @config = new_option
+ @options.repeat
+ end
+
+ it "is enabled with #repeat" do
+ expect(@options).to receive(:on).with("-R", "--repeat", "NUMBER", an_instance_of(String))
+ @options.repeat
+ end
+
+ it "registers the MSpec repeat mode" do
+ ["-R", "--repeat"].each do |opt|
+ MSpec.repeat = 1
+ @options.parse [opt, "10"]
+ repeat_count = 0
+ MSpec.repeat do
+ repeat_count += 1
+ end
+ expect(repeat_count).to eq(10)
+ end
+ end
+end
+
+RSpec.describe "The -V, --verbose option" do
+ before :each do
+ @options, @config = new_option
+ @options.verbose
+ end
+
+ it "is enabled with #verbose" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-V", "--verbose", an_instance_of(String))
+ @options.verbose
+ end
+
+ it "registers a verbose output object with MSpec" do
+ expect(MSpec).to receive(:register).with(:start, anything()).twice
+ expect(MSpec).to receive(:register).with(:load, anything()).twice
+ ["-V", "--verbose"].each do |opt|
+ @options.parse opt
+ end
+ end
+end
+
+RSpec.describe "The -m, --marker MARKER option" do
+ before :each do
+ @options, @config = new_option
+ @options.verbose
+ end
+
+ it "is enabled with #verbose" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-m", "--marker", "MARKER",
+ an_instance_of(String))
+ @options.verbose
+ end
+
+ it "registers a marker output object with MSpec" do
+ expect(MSpec).to receive(:register).with(:load, anything()).twice
+ ["-m", "--marker"].each do |opt|
+ @options.parse [opt, ","]
+ end
+ end
+end
+
+RSpec.describe "The --int-spec option" do
+ before :each do
+ @options, @config = new_option
+ @options.interrupt
+ end
+
+ it "is enabled with #interrupt" do
+ expect(@options).to receive(:on).with("--int-spec", an_instance_of(String))
+ @options.interrupt
+ end
+
+ it "sets the abort config option to false to only abort the running spec with ^C" do
+ @config[:abort] = true
+ @options.parse "--int-spec"
+ expect(@config[:abort]).to eq(false)
+ end
+end
+
+RSpec.describe "The -Y, --verify option" do
+ before :each do
+ @options, @config = new_option
+ @options.verify
+ end
+
+ it "is enabled with #interrupt" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-Y", "--verify", an_instance_of(String))
+ @options.verify
+ end
+
+ it "sets the MSpec mode to :verify" do
+ expect(MSpec).to receive(:register_mode).with(:verify).twice
+ ["-Y", "--verify"].each do |m|
+ @options.parse m
+ end
+ end
+end
+
+RSpec.describe "The -O, --report option" do
+ before :each do
+ @options, @config = new_option
+ @options.verify
+ end
+
+ it "is enabled with #interrupt" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-O", "--report", an_instance_of(String))
+ @options.verify
+ end
+
+ it "sets the MSpec mode to :report" do
+ expect(MSpec).to receive(:register_mode).with(:report).twice
+ ["-O", "--report"].each do |m|
+ @options.parse m
+ end
+ end
+end
+
+RSpec.describe "The --report-on GUARD option" do
+ before :each do
+ allow(MSpec).to receive(:register_mode)
+
+ @options, @config = new_option
+ @options.verify
+
+ SpecGuard.clear_guards
+ end
+
+ after :each do
+ SpecGuard.clear_guards
+ end
+
+ it "is enabled with #interrupt" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("--report-on", "GUARD", an_instance_of(String))
+ @options.verify
+ end
+
+ it "sets the MSpec mode to :report_on" do
+ expect(MSpec).to receive(:register_mode).with(:report_on)
+ @options.parse ["--report-on", "ruby_bug"]
+ end
+
+ it "converts the guard name to a symbol" do
+ name = double("ruby_bug")
+ expect(name).to receive(:to_sym)
+ @options.parse ["--report-on", name]
+ end
+
+ it "saves the name of the guard" do
+ @options.parse ["--report-on", "ruby_bug"]
+ expect(SpecGuard.guards).to eq([:ruby_bug])
+ end
+end
+
+RSpec.describe "The -K, --action-tag TAG option" do
+ before :each do
+ @options, @config = new_option
+ @options.action_filters
+ end
+
+ it "is enabled with #action_filters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-K", "--action-tag", "TAG",
+ an_instance_of(String))
+ @options.action_filters
+ end
+
+ it "adds TAG to the list of tags that trigger actions" do
+ ["-K", "--action-tag"].each do |opt|
+ @config[:atags] = []
+ @options.parse [opt, "action-tag"]
+ expect(@config[:atags]).to include("action-tag")
+ end
+ end
+end
+
+RSpec.describe "The -S, --action-string STR option" do
+ before :each do
+ @options, @config = new_option
+ @options.action_filters
+ end
+
+ it "is enabled with #action_filters" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-S", "--action-string", "STR",
+ an_instance_of(String))
+ @options.action_filters
+ end
+
+ it "adds STR to the list of spec descriptions that trigger actions" do
+ ["-S", "--action-string"].each do |opt|
+ @config[:astrings] = []
+ @options.parse [opt, "action-str"]
+ expect(@config[:astrings]).to include("action-str")
+ end
+ end
+end
+
+RSpec.describe "The -d, --debug option" do
+ before :each do
+ @options, @config = new_option
+ @options.debug
+ end
+
+ after :each do
+ $MSPEC_DEBUG = nil
+ end
+
+ it "is enabled with #debug" do
+ allow(@options).to receive(:on)
+ expect(@options).to receive(:on).with("-d", "--debug", an_instance_of(String))
+ @options.debug
+ end
+
+ it "sets $MSPEC_DEBUG to true" do
+ ["-d", "--debug"].each do |opt|
+ expect($MSPEC_DEBUG).not_to be_truthy
+ @options.parse opt
+ expect($MSPEC_DEBUG).to be_truthy
+ $MSPEC_DEBUG = nil
+ end
+ end
+end
+
+RSpec.describe "MSpecOptions#all" do
+ it "includes all options" do
+ meth = MSpecOptions.instance_method(:all)
+ file, line = meth.source_location
+ contents = File.read(file)
+ lines = contents.lines
+
+ from = line
+ to = from
+ to += 1 until /^\s*end\s*$/ =~ lines[to]
+ calls = lines[from...to].map(&:strip)
+
+ option_methods = contents.scan(/def (\w+).*\n\s*on\(/).map(&:first)
+ option_methods[0].sub!("configure", "configure {}")
+
+ expect(calls).to eq(option_methods)
+ end
+end
diff --git a/spec/mspec/spec/utils/script_spec.rb b/spec/mspec/spec/utils/script_spec.rb
new file mode 100644
index 0000000000..c35bda8b47
--- /dev/null
+++ b/spec/mspec/spec/utils/script_spec.rb
@@ -0,0 +1,470 @@
+require 'spec_helper'
+require 'mspec/utils/script'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters'
+require 'mspec/runner/actions/filter'
+
+RSpec.describe MSpecScript, ".config" do
+ it "returns a Hash" do
+ expect(MSpecScript.config).to be_kind_of(Hash)
+ end
+end
+
+RSpec.describe MSpecScript, ".set" do
+ it "sets the config hash key, value" do
+ MSpecScript.set :a, 10
+ expect(MSpecScript.config[:a]).to eq(10)
+ end
+end
+
+RSpec.describe MSpecScript, ".get" do
+ it "gets the config hash value for a key" do
+ MSpecScript.set :a, 10
+ expect(MSpecScript.get(:a)).to eq(10)
+ end
+end
+
+RSpec.describe MSpecScript, "#config" do
+ it "returns the MSpecScript config hash" do
+ MSpecScript.set :b, 5
+ expect(MSpecScript.new.config[:b]).to eq(5)
+ end
+
+ it "returns the MSpecScript config hash from subclasses" do
+ class MSSClass < MSpecScript; end
+ MSpecScript.set :b, 5
+ expect(MSSClass.new.config[:b]).to eq(5)
+ end
+end
+
+RSpec.describe MSpecScript, "#load_default" do
+ before :all do
+ @verbose = $VERBOSE
+ $VERBOSE = nil
+ end
+
+ after :all do
+ $VERBOSE = @verbose
+ end
+
+ before :each do
+ @version = RUBY_VERSION
+ if Object.const_defined? :RUBY_ENGINE
+ @engine = Object.const_get :RUBY_ENGINE
+ end
+ @script = MSpecScript.new
+ allow(MSpecScript).to receive(:new).and_return(@script)
+ end
+
+ after :each do
+ Object.const_set :RUBY_VERSION, @version
+ Object.const_set :RUBY_ENGINE, @engine if @engine
+ end
+
+ it "attempts to load 'default.mspec'" do
+ allow(@script).to receive(:try_load)
+ expect(@script).to receive(:try_load).with('default.mspec').and_return(true)
+ @script.load_default
+ end
+
+ it "attempts to load a config file based on RUBY_ENGINE and RUBY_VERSION" do
+ Object.const_set :RUBY_ENGINE, "ybur"
+ Object.const_set :RUBY_VERSION, "1.8.9"
+ default = "ybur.1.8.mspec"
+ expect(@script).to receive(:try_load).with('default.mspec').and_return(false)
+ expect(@script).to receive(:try_load).with(default)
+ expect(@script).to receive(:try_load).with('ybur.mspec')
+ @script.load_default
+ end
+end
+
+RSpec.describe MSpecScript, ".main" do
+ before :each do
+ @script = double("MSpecScript").as_null_object
+ allow(MSpecScript).to receive(:new).and_return(@script)
+ # Do not require full mspec as it would conflict with RSpec
+ expect(MSpecScript).to receive(:require).with('mspec')
+ end
+
+ it "creates an instance of MSpecScript" do
+ expect(MSpecScript).to receive(:new).and_return(@script)
+ MSpecScript.main
+ end
+
+ it "attempts to load the default config" do
+ expect(@script).to receive(:load_default)
+ MSpecScript.main
+ end
+
+ it "calls the #options method on the script" do
+ expect(@script).to receive(:options)
+ MSpecScript.main
+ end
+
+ it "calls the #signals method on the script" do
+ expect(@script).to receive(:signals)
+ MSpecScript.main
+ end
+
+ it "calls the #register method on the script" do
+ expect(@script).to receive(:register)
+ MSpecScript.main
+ end
+
+ it "calls the #setup_env method on the script" do
+ expect(@script).to receive(:setup_env)
+ MSpecScript.main
+ end
+
+ it "calls the #run method on the script" do
+ expect(@script).to receive(:run)
+ MSpecScript.main
+ end
+end
+
+RSpec.describe MSpecScript, "#initialize" do
+ before :each do
+ @config = MSpecScript.new.config
+ end
+
+ it "sets the default config values" do
+ expect(@config[:formatter]).to eq(nil)
+ expect(@config[:includes]).to eq([])
+ expect(@config[:excludes]).to eq([])
+ expect(@config[:patterns]).to eq([])
+ expect(@config[:xpatterns]).to eq([])
+ expect(@config[:tags]).to eq([])
+ expect(@config[:xtags]).to eq([])
+ expect(@config[:atags]).to eq([])
+ expect(@config[:astrings]).to eq([])
+ expect(@config[:abort]).to eq(true)
+ expect(@config[:config_ext]).to eq('.mspec')
+ end
+end
+
+RSpec.describe MSpecScript, "#load" do
+ before :each do
+ allow(File).to receive(:exist?).and_return(false)
+ @script = MSpecScript.new
+ @file = "default.mspec"
+ @base = "default"
+ end
+
+ it "attempts to locate the file through the expanded path name" do
+ expect(File).to receive(:expand_path).with(@file, ".").and_return(@file)
+ expect(File).to receive(:exist?).with(@file).and_return(true)
+ expect(Kernel).to receive(:load).with(@file).and_return(:loaded)
+ expect(@script.load(@file)).to eq(:loaded)
+ end
+
+ it "appends config[:config_ext] to the name and attempts to locate the file through the expanded path name" do
+ expect(File).to receive(:expand_path).with(@base, ".").and_return(@base)
+ expect(File).to receive(:expand_path).with(@base, "spec").and_return(@base)
+ expect(File).to receive(:expand_path).with(@file, ".").and_return(@file)
+ expect(File).to receive(:exist?).with(@base).and_return(false)
+ expect(File).to receive(:exist?).with(@file).and_return(true)
+ expect(Kernel).to receive(:load).with(@file).and_return(:loaded)
+ expect(@script.load(@base)).to eq(:loaded)
+ end
+
+ it "attempts to locate the file in '.'" do
+ path = File.expand_path @file, "."
+ expect(File).to receive(:exist?).with(path).and_return(true)
+ expect(Kernel).to receive(:load).with(path).and_return(:loaded)
+ expect(@script.load(@file)).to eq(:loaded)
+ end
+
+ it "appends config[:config_ext] to the name and attempts to locate the file in '.'" do
+ path = File.expand_path @file, "."
+ expect(File).to receive(:exist?).with(path).and_return(true)
+ expect(Kernel).to receive(:load).with(path).and_return(:loaded)
+ expect(@script.load(@base)).to eq(:loaded)
+ end
+
+ it "attempts to locate the file in 'spec'" do
+ path = File.expand_path @file, "spec"
+ expect(File).to receive(:exist?).with(path).and_return(true)
+ expect(Kernel).to receive(:load).with(path).and_return(:loaded)
+ expect(@script.load(@file)).to eq(:loaded)
+ end
+
+ it "appends config[:config_ext] to the name and attempts to locate the file in 'spec'" do
+ path = File.expand_path @file, "spec"
+ expect(File).to receive(:exist?).with(path).and_return(true)
+ expect(Kernel).to receive(:load).with(path).and_return(:loaded)
+ expect(@script.load(@base)).to eq(:loaded)
+ end
+
+ it "loads a given file only once" do
+ path = File.expand_path @file, "spec"
+ expect(File).to receive(:exist?).with(path).and_return(true)
+ expect(Kernel).to receive(:load).once.with(path).and_return(:loaded)
+ expect(@script.load(@base)).to eq(:loaded)
+ expect(@script.load(@base)).to eq(true)
+ end
+end
+
+RSpec.describe MSpecScript, "#custom_options" do
+ before :each do
+ @script = MSpecScript.new
+ end
+
+ after :each do
+ end
+
+ it "prints 'None'" do
+ options = double("options")
+ expect(options).to receive(:doc).with(" No custom options registered")
+ @script.custom_options options
+ end
+end
+
+RSpec.describe MSpecScript, "#register" do
+ before :each do
+ @script = MSpecScript.new
+
+ @formatter = double("formatter").as_null_object
+ @script.config[:formatter] = @formatter
+ end
+
+ it "creates and registers the formatter" do
+ expect(@formatter).to receive(:new).and_return(@formatter)
+ expect(@formatter).to receive(:register)
+ @script.register
+ end
+
+ it "does not register the formatter if config[:formatter] is false" do
+ @script.config[:formatter] = false
+ @script.register
+ end
+
+ it "calls #custom_register" do
+ expect(@script).to receive(:custom_register)
+ @script.register
+ end
+
+ it "registers :formatter with the formatter instance" do
+ allow(@formatter).to receive(:new).and_return(@formatter)
+ @script.register
+ expect(MSpec.formatter).to be(@formatter)
+ end
+
+ it "does not register :formatter if config[:formatter] is false" do
+ @script.config[:formatter] = false
+ expect(MSpec).not_to receive(:store)
+ @script.register
+ end
+end
+
+RSpec.describe MSpecScript, "#register" do
+ before :each do
+ @script = MSpecScript.new
+
+ @formatter = double("formatter").as_null_object
+ @script.config[:formatter] = @formatter
+
+ @filter = double("filter")
+ expect(@filter).to receive(:register)
+
+ @ary = ["some", "spec"]
+ end
+
+ it "creates and registers a MatchFilter for include specs" do
+ expect(MatchFilter).to receive(:new).with(:include, *@ary).and_return(@filter)
+ @script.config[:includes] = @ary
+ @script.register
+ end
+
+ it "creates and registers a MatchFilter for excluded specs" do
+ expect(MatchFilter).to receive(:new).with(:exclude, *@ary).and_return(@filter)
+ @script.config[:excludes] = @ary
+ @script.register
+ end
+
+ it "creates and registers a RegexpFilter for include specs" do
+ expect(RegexpFilter).to receive(:new).with(:include, *@ary).and_return(@filter)
+ @script.config[:patterns] = @ary
+ @script.register
+ end
+
+ it "creates and registers a RegexpFilter for excluded specs" do
+ expect(RegexpFilter).to receive(:new).with(:exclude, *@ary).and_return(@filter)
+ @script.config[:xpatterns] = @ary
+ @script.register
+ end
+
+ it "creates and registers a TagFilter for include specs" do
+ expect(TagFilter).to receive(:new).with(:include, *@ary).and_return(@filter)
+ @script.config[:tags] = @ary
+ @script.register
+ end
+
+ it "creates and registers a TagFilter for excluded specs" do
+ expect(TagFilter).to receive(:new).with(:exclude, *@ary).and_return(@filter)
+ @script.config[:xtags] = @ary
+ @script.register
+ end
+
+ it "creates and registers a ProfileFilter for include specs" do
+ expect(ProfileFilter).to receive(:new).with(:include, *@ary).and_return(@filter)
+ @script.config[:profiles] = @ary
+ @script.register
+ end
+
+ it "creates and registers a ProfileFilter for excluded specs" do
+ expect(ProfileFilter).to receive(:new).with(:exclude, *@ary).and_return(@filter)
+ @script.config[:xprofiles] = @ary
+ @script.register
+ end
+end
+
+RSpec.describe MSpecScript, "#signals" do
+ before :each do
+ @script = MSpecScript.new
+ @abort = @script.config[:abort]
+ end
+
+ after :each do
+ @script.config[:abort] = @abort
+ end
+
+ it "traps the INT signal if config[:abort] is true" do
+ expect(Signal).to receive(:trap).with("INT")
+ @script.config[:abort] = true
+ @script.signals
+ end
+
+ it "does not trap the INT signal if config[:abort] is not true" do
+ expect(Signal).not_to receive(:trap).with("INT")
+ @script.config[:abort] = false
+ @script.signals
+ end
+end
+
+RSpec.describe MSpecScript, "#entries" do
+ before :each do
+ @script = MSpecScript.new
+
+ allow(File).to receive(:realpath).and_return("name")
+ allow(File).to receive(:file?).and_return(false)
+ allow(File).to receive(:directory?).and_return(false)
+ end
+
+ it "returns the pattern in an array if it is a file" do
+ expect(File).to receive(:realpath).with("file").and_return("file/expanded.rb")
+ expect(File).to receive(:file?).with("file/expanded.rb").and_return(true)
+ expect(@script.entries("file")).to eq(["file/expanded.rb"])
+ end
+
+ it "returns Dir['pattern/**/*_spec.rb'] if pattern is a directory" do
+ expect(File).to receive(:directory?).with("name").and_return(true)
+ allow(File).to receive(:realpath).and_return("name", "name/**/*_spec.rb")
+ expect(Dir).to receive(:[]).with("name/**/*_spec.rb").and_return(["dir1", "dir2"])
+ expect(@script.entries("name")).to eq(["dir1", "dir2"])
+ end
+
+ it "aborts if pattern cannot be resolved to a file nor a directory" do
+ expect(@script).to receive(:abort)
+ @script.entries("pattern")
+ end
+
+ describe "with config[:prefix] set" do
+ before :each do
+ prefix = "prefix/dir"
+ @script.config[:prefix] = prefix
+ @name = prefix + "/name"
+ end
+
+ it "returns the pattern in an array if it is a file" do
+ name = "#{@name}.rb"
+ expect(File).to receive(:realpath).with(name).and_return(name)
+ expect(File).to receive(:file?).with(name).and_return(true)
+ expect(@script.entries("name.rb")).to eq([name])
+ end
+
+ it "returns Dir['pattern/**/*_spec.rb'] if pattern is a directory" do
+ allow(File).to receive(:realpath).and_return(@name, @name+"/**/*_spec.rb")
+ expect(File).to receive(:directory?).with(@name).and_return(true)
+ expect(Dir).to receive(:[]).with(@name + "/**/*_spec.rb").and_return(["dir1", "dir2"])
+ expect(@script.entries("name")).to eq(["dir1", "dir2"])
+ end
+
+ it "aborts if pattern cannot be resolved to a file nor a directory" do
+ expect(@script).to receive(:abort)
+ @script.entries("pattern")
+ end
+ end
+end
+
+RSpec.describe MSpecScript, "#files" do
+ before :each do
+ @script = MSpecScript.new
+ end
+
+ it "accumulates the values returned by #entries" do
+ expect(@script).to receive(:entries).and_return(["file1"], ["file2"])
+ expect(@script.files(["a", "b"])).to eq(["file1", "file2"])
+ end
+
+ it "strips a leading '^' and removes the values returned by #entries" do
+ expect(@script).to receive(:entries).and_return(["file1"], ["file2"], ["file1"])
+ expect(@script.files(["a", "b", "^a"])).to eq(["file2"])
+ end
+
+ it "processes the array elements in order" do
+ expect(@script).to receive(:entries).and_return(["file1"], ["file1"], ["file2"])
+ expect(@script.files(["^a", "a", "b"])).to eq(["file1", "file2"])
+ end
+end
+
+RSpec.describe MSpecScript, "#files" do
+ before :each do
+ MSpecScript.set :files, ["file1", "file2"]
+
+ @script = MSpecScript.new
+ end
+
+ after :each do
+ MSpecScript.config.delete :files
+ end
+
+ it "looks up items with leading ':' in the config object" do
+ expect(@script).to receive(:entries).and_return(["file1"], ["file2"])
+ expect(@script.files([":files"])).to eq(["file1", "file2"])
+ end
+
+ it "aborts if the config key is not set" do
+ expect(@script).to receive(:abort).with("Key :all_files not found in mspec config.")
+ @script.files([":all_files"])
+ end
+end
+
+RSpec.describe MSpecScript, "#setup_env" do
+ before :each do
+ @script = MSpecScript.new
+ @options, @config = new_option
+ allow(@script).to receive(:config).and_return(@config)
+ end
+
+ after :each do
+ end
+
+ it "sets MSPEC_RUNNER = '1' in the environment" do
+ ENV["MSPEC_RUNNER"] = "0"
+ @script.setup_env
+ expect(ENV["MSPEC_RUNNER"]).to eq("1")
+ end
+
+ it "sets RUBY_EXE = config[:target] in the environment" do
+ ENV["RUBY_EXE"] = nil
+ @script.setup_env
+ expect(ENV["RUBY_EXE"]).to eq(@config[:target])
+ end
+
+ it "sets RUBY_FLAGS = config[:flags] in the environment" do
+ ENV["RUBY_FLAGS"] = nil
+ @config[:flags] = ["-w", "-Q"]
+ @script.setup_env
+ expect(ENV["RUBY_FLAGS"]).to eq("-w -Q")
+ end
+end
diff --git a/spec/mspec/spec/utils/version_spec.rb b/spec/mspec/spec/utils/version_spec.rb
new file mode 100644
index 0000000000..ec367d2a1e
--- /dev/null
+++ b/spec/mspec/spec/utils/version_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+require 'mspec/utils/version'
+
+RSpec.describe SpecVersion, "#to_s" do
+ it "returns the string with which it was initialized" do
+ expect(SpecVersion.new("1.8").to_s).to eq("1.8")
+ expect(SpecVersion.new("2.118.9").to_s).to eq("2.118.9")
+ end
+end
+
+RSpec.describe SpecVersion, "#to_str" do
+ it "returns the same string as #to_s" do
+ version = SpecVersion.new("2.118.9")
+ expect(version.to_str).to eq(version.to_s)
+ end
+end
+
+RSpec.describe SpecVersion, "#to_i with ceil = false" do
+ it "returns an integer representation of the version string" do
+ expect(SpecVersion.new("2.23.10").to_i).to eq(1022310)
+ end
+
+ it "replaces missing version parts with zeros" do
+ expect(SpecVersion.new("1.8").to_i).to eq(1010800)
+ expect(SpecVersion.new("1.8.6").to_i).to eq(1010806)
+ end
+end
+
+RSpec.describe SpecVersion, "#to_i with ceil = true" do
+ it "returns an integer representation of the version string" do
+ expect(SpecVersion.new("1.8.6", true).to_i).to eq(1010806)
+ end
+
+ it "fills in 9s for missing tiny values" do
+ expect(SpecVersion.new("1.8", true).to_i).to eq(1010899)
+ expect(SpecVersion.new("1.8.6", true).to_i).to eq(1010806)
+ end
+end
+
+RSpec.describe SpecVersion, "#to_int" do
+ it "returns the same value as #to_i" do
+ version = SpecVersion.new("4.16.87")
+ expect(version.to_int).to eq(version.to_i)
+ end
+end
diff --git a/spec/mspec/tool/find.rb b/spec/mspec/tool/find.rb
new file mode 100755
index 0000000000..322b023f15
--- /dev/null
+++ b/spec/mspec/tool/find.rb
@@ -0,0 +1,10 @@
+#!/usr/bin/env ruby
+Dir.chdir('../rubyspec') do
+ regexp = Regexp.new(ARGV[0])
+ Dir.glob('**/*.rb') do |file|
+ contents = File.read(file)
+ if regexp =~ contents
+ puts file
+ end
+ end
+end
diff --git a/spec/mspec/tool/pull-latest-mspec-spec b/spec/mspec/tool/pull-latest-mspec-spec
new file mode 100755
index 0000000000..154a353e64
--- /dev/null
+++ b/spec/mspec/tool/pull-latest-mspec-spec
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Assumes all commits have been synchronized to https://github.com/ruby/spec
+# See spec/mspec/tool/sync/sync-rubyspec.rb
+
+function sync {
+ dir="$1"
+ repo="$2"
+ short_repo_name="ruby/$(basename "$repo" .git)"
+
+ rm -rf "$dir"
+ git clone --depth 1 "$repo" "$dir"
+ commit=$(git -C "$dir" log -n 1 --format='%h')
+ rm -rf "$dir/.git"
+
+ # Remove CI files to avoid confusion
+ rm -f "$dir/appveyor.yml"
+ rm -f "$dir/.travis.yml"
+ rm -rf "$dir/.github"
+
+ git add "$dir"
+ git commit -m "Update to ${short_repo_name}@${commit}"
+}
+
+sync spec/mspec https://github.com/ruby/mspec.git
+sync spec/ruby https://github.com/ruby/spec.git
diff --git a/spec/mspec/tool/remove_old_guards.rb b/spec/mspec/tool/remove_old_guards.rb
new file mode 100644
index 0000000000..67485446bb
--- /dev/null
+++ b/spec/mspec/tool/remove_old_guards.rb
@@ -0,0 +1,70 @@
+# Removes old version guards in ruby/spec.
+# Run it from the ruby/spec repository root.
+# The argument is the new minimum supported version.
+
+def dedent(line)
+ if line.start_with?(" ")
+ line[2..-1]
+ else
+ line
+ end
+end
+
+def each_spec_file(&block)
+ Dir["*/**/*.rb"].each(&block)
+end
+
+def remove_guards(guard, keep)
+ each_spec_file do |file|
+ contents = File.binread(file)
+ if contents =~ guard
+ puts file
+ lines = contents.lines.to_a
+ while first = lines.find_index { |line| line =~ guard }
+ comment = first
+ while comment > 0 and lines[comment-1] =~ /^(\s*)#/
+ comment -= 1
+ end
+ indent = lines[first][/^(\s*)/, 1].length
+ last = (first+1...lines.size).find { |i|
+ space = lines[i][/^(\s*)end$/, 1] and space.length == indent
+ }
+ raise file unless last
+ if keep
+ lines[comment..last] = lines[first+1..last-1].map { |l| dedent(l) }
+ else
+ if comment > 0 and lines[comment-1] == "\n"
+ comment -= 1
+ elsif lines[last+1] == "\n"
+ last += 1
+ end
+ lines[comment..last] = []
+ end
+ end
+ File.binwrite file, lines.join
+ end
+ end
+end
+
+def search(regexp)
+ each_spec_file do |file|
+ contents = File.binread(file)
+ if contents =~ regexp
+ puts file
+ contents.each_line do |line|
+ if line =~ regexp
+ puts line
+ end
+ end
+ end
+ end
+end
+
+version = Regexp.escape(ARGV.fetch(0))
+version += "(?:\\.0)?" if version.count(".") < 2
+remove_guards(/ruby_version_is (["'])#{version}\1 do/, true)
+remove_guards(/ruby_version_is (["'])[0-9.]*\1 *... *(["'])#{version}\2 do/, false)
+remove_guards(/ruby_bug "#\d+", (["'])[0-9.]*\1 *... *(["'])#{version}\2 do/, true)
+
+search(/(["'])#{version}\1/)
+search(/^\s*#.+#{version}/)
diff --git a/spec/mspec/tool/sync/.gitignore b/spec/mspec/tool/sync/.gitignore
new file mode 100644
index 0000000000..e64f1e8542
--- /dev/null
+++ b/spec/mspec/tool/sync/.gitignore
@@ -0,0 +1,4 @@
+/jruby
+/rubinius
+/ruby
+/truffleruby
diff --git a/spec/mspec/tool/sync/sync-rubyspec.rb b/spec/mspec/tool/sync/sync-rubyspec.rb
new file mode 100644
index 0000000000..13f1d8004d
--- /dev/null
+++ b/spec/mspec/tool/sync/sync-rubyspec.rb
@@ -0,0 +1,254 @@
+# This script is based on commands from the wiki:
+# https://github.com/ruby/spec/wiki/Merging-specs-from-JRuby-and-other-sources
+
+IMPLS = {
+ truffleruby: {
+ git: "https://github.com/oracle/truffleruby.git",
+ from_commit: "f10ab6988d",
+ },
+ jruby: {
+ git: "https://github.com/jruby/jruby.git",
+ from_commit: "f10ab6988d",
+ },
+ rbx: {
+ git: "https://github.com/rubinius/rubinius.git",
+ },
+ mri: {
+ git: "https://github.com/ruby/ruby.git",
+ },
+}
+
+MSPEC = ARGV.delete('--mspec')
+
+CHECK_LAST_MERGE = !MSPEC && ENV['CHECK_LAST_MERGE'] != 'false'
+TEST_MASTER = ENV['TEST_MASTER'] != 'false'
+
+MSPEC_REPO = File.expand_path("../../..", __FILE__)
+raise MSPEC_REPO if !Dir.exist?(MSPEC_REPO) or !Dir.exist?("#{MSPEC_REPO}/.git")
+
+# Assuming the rubyspec repo is a sibling of the mspec repo
+RUBYSPEC_REPO = File.expand_path("../rubyspec", MSPEC_REPO)
+raise RUBYSPEC_REPO unless Dir.exist?(RUBYSPEC_REPO)
+
+SOURCE_REPO = MSPEC ? MSPEC_REPO : RUBYSPEC_REPO
+
+NOW = Time.now
+
+BRIGHT_RED = "\e[31;1m"
+BRIGHT_YELLOW = "\e[33;1m"
+RESET = "\e[0m"
+
+# git filter-branch --subdirectory-filter works fine for our use case
+ENV['FILTER_BRANCH_SQUELCH_WARNING'] = '1'
+
+class RubyImplementation
+ attr_reader :name
+
+ def initialize(name, data)
+ @name = name.to_s
+ @data = data
+ end
+
+ def git_url
+ @data[:git]
+ end
+
+ def repo_name
+ File.basename(git_url, ".git")
+ end
+
+ def repo_path
+ "#{__dir__}/#{repo_name}"
+ end
+
+ def repo_org
+ File.basename(File.dirname(git_url))
+ end
+
+ def from_commit
+ from = @data[:from_commit]
+ "#{from}..." if from
+ end
+
+ def last_merge_message
+ message = @data[:merge_message] || "Update to ruby/spec@"
+ message.gsub!("ruby/spec", "ruby/mspec") if MSPEC
+ message
+ end
+
+ def prefix
+ MSPEC ? "spec/mspec" : "spec/ruby"
+ end
+
+ def rebased_branch
+ "#{@name}-rebased"
+ end
+end
+
+def sh(*args)
+ puts args.join(' ')
+ system(*args)
+ raise unless $?.success?
+end
+
+def branch?(name)
+ branches = `git branch`.sub('*', '').lines.map(&:strip)
+ branches.include?(name)
+end
+
+def update_repo(impl)
+ unless File.directory? impl.repo_name
+ sh "git", "clone", impl.git_url
+ end
+
+ Dir.chdir(impl.repo_name) do
+ puts Dir.pwd
+
+ sh "git", "checkout", "master"
+ sh "git", "pull"
+ end
+end
+
+def filter_commits(impl)
+ Dir.chdir(impl.repo_name) do
+ date = NOW.strftime("%F")
+ branch = "#{MSPEC ? :mspec : :specs}-#{date}"
+
+ unless branch?(branch)
+ sh "git", "checkout", "-b", branch
+ sh "git", "filter-branch", "-f", "--subdirectory-filter", impl.prefix, *impl.from_commit
+ sh "git", "push", "-f", SOURCE_REPO, "#{branch}:#{impl.name}"
+ end
+ end
+end
+
+def rebase_commits(impl)
+ Dir.chdir(SOURCE_REPO) do
+ sh "git", "checkout", "master"
+ sh "git", "pull"
+
+ rebased = impl.rebased_branch
+ if branch?(rebased)
+ last_commit = Time.at(Integer(`git log -n 1 --format='%ct' #{rebased}`))
+ days_since_last_commit = (NOW-last_commit) / 86400
+ if days_since_last_commit > 7
+ abort "#{BRIGHT_RED}#{rebased} exists but last commit is old (#{last_commit}), delete the branch if it was merged#{RESET}"
+ else
+ puts "#{BRIGHT_YELLOW}#{rebased} already exists, last commit on #{last_commit}, assuming it correct#{RESET}"
+ sh "git", "checkout", rebased
+ end
+ else
+ sh "git", "checkout", impl.name
+
+ if ENV["LAST_MERGE"]
+ last_merge = `git log -n 1 --format='%H %ct' #{ENV["LAST_MERGE"]}`
+ else
+ last_merge = `git log --grep='^#{impl.last_merge_message}' -n 1 --format='%H %ct'`
+ end
+ last_merge, commit_timestamp = last_merge.split(' ')
+
+ raise "Could not find last merge" unless last_merge
+ puts "Last merge is #{last_merge}"
+
+ commit_date = Time.at(Integer(commit_timestamp))
+ days_since_last_merge = (NOW-commit_date) / 86400
+ if CHECK_LAST_MERGE and days_since_last_merge > 60
+ raise "#{days_since_last_merge.floor} days since last merge, probably wrong commit"
+ end
+
+ puts "Checking if the last merge is consistent with upstream files"
+ rubyspec_commit = `git log -n 1 --format='%s' #{last_merge}`.chomp.split('@', 2)[-1]
+ sh "git", "checkout", last_merge
+ sh "git", "diff", "--exit-code", rubyspec_commit, "--", ":!.github"
+
+ puts "Rebasing..."
+ sh "git", "branch", "-D", rebased if branch?(rebased)
+ sh "git", "checkout", "-b", rebased, impl.name
+ sh "git", "rebase", "--onto", "master", last_merge
+ end
+ end
+end
+
+def new_commits?(impl)
+ Dir.chdir(SOURCE_REPO) do
+ diff = `git diff master #{impl.rebased_branch}`
+ !diff.empty?
+ end
+end
+
+def test_new_specs
+ require "yaml"
+ Dir.chdir(SOURCE_REPO) do
+ workflow = YAML.load_file(".github/workflows/ci.yml")
+ job_name = MSPEC ? "test" : "specs"
+ versions = workflow.dig("jobs", job_name, "strategy", "matrix", "ruby")
+ versions = versions.grep(/^\d+\./) # Test on MRI
+ min_version, max_version = versions.minmax
+
+ test_command = MSPEC ? "bundle install && bundle exec rspec" : "../mspec/bin/mspec -j"
+
+ run_test = -> version {
+ command = "chruby #{version} && #{test_command}"
+ sh ENV["SHELL"], "-c", command
+ }
+
+ run_test[min_version]
+ run_test[max_version]
+ run_test["ruby-master"] if TEST_MASTER
+ end
+end
+
+def verify_commits(impl)
+ puts
+ Dir.chdir(SOURCE_REPO) do
+ puts "Manually check commit messages:"
+ print "Press enter >"
+ STDIN.gets
+ system "git", "log", "master..."
+ end
+end
+
+def fast_forward_master(impl)
+ Dir.chdir(SOURCE_REPO) do
+ sh "git", "checkout", "master"
+ sh "git", "merge", "--ff-only", impl.rebased_branch
+ sh "git", "branch", "--delete", impl.rebased_branch
+ end
+end
+
+def check_ci
+ puts
+ puts <<-EOS
+ Push to master, and check that the CI passes:
+ https://github.com/ruby/#{:m if MSPEC}spec/commits/master
+
+ EOS
+end
+
+def main(impls)
+ impls.each_pair do |impl, data|
+ impl = RubyImplementation.new(impl, data)
+ update_repo(impl)
+ filter_commits(impl)
+ rebase_commits(impl)
+ if new_commits?(impl)
+ test_new_specs
+ verify_commits(impl)
+ fast_forward_master(impl)
+ check_ci
+ else
+ STDERR.puts "#{BRIGHT_YELLOW}No new commits#{RESET}"
+ fast_forward_master(impl)
+ end
+ end
+end
+
+if ARGV == ["all"]
+ impls = IMPLS
+else
+ args = ARGV.map { |arg| arg.to_sym }
+ raise ARGV.to_s unless (args - IMPLS.keys).empty?
+ impls = IMPLS.select { |impl| args.include?(impl) }
+end
+
+main(impls)
diff --git a/spec/mspec/tool/tag_from_output.rb b/spec/mspec/tool/tag_from_output.rb
new file mode 100755
index 0000000000..a6e60945cd
--- /dev/null
+++ b/spec/mspec/tool/tag_from_output.rb
@@ -0,0 +1,63 @@
+#!/usr/bin/env ruby
+
+# Adds tags based on error and failures output (e.g., from a CI log),
+# without running any spec code.
+
+tags_dir = %w[
+ spec/tags
+ spec/tags/ruby
+].find { |dir| Dir.exist?("#{dir}/language") }
+abort 'Could not find tags directory' unless tags_dir
+
+output = ARGF.readlines
+
+# Automatically strip datetime of GitHub Actions
+if output.first =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z /
+ output = output.map { |line| line.split(' ', 2).last }
+end
+
+NUMBER = /^\d+\)$/
+ERROR_OR_FAILED = / (ERROR|FAILED)$/
+SPEC_FILE = /^(\/.+_spec\.rb)\:\d+/
+
+output.slice_before(NUMBER).select { |number, *rest|
+ number =~ NUMBER and rest.any? { |line| line =~ ERROR_OR_FAILED }
+}.each { |number, *rest|
+ error_line = rest.find { |line| line =~ ERROR_OR_FAILED }
+ description = error_line.match(ERROR_OR_FAILED).pre_match
+
+ spec_file = rest.find { |line| line =~ SPEC_FILE }
+ if spec_file
+ spec_file = spec_file[SPEC_FILE, 1] or raise
+ else
+ if error_line =~ /^(\w+)[#\.](\w+) /
+ module_method = error_line.split(' ', 2).first
+ file = "#{$1.downcase}/#{$2}_spec.rb"
+ spec_file = ['spec/ruby/core', 'spec/ruby/library', *Dir.glob('spec/ruby/library/*')].find { |dir|
+ path = "#{dir}/#{file}"
+ break path if File.exist?(path)
+ }
+ end
+
+ unless spec_file
+ warn "Could not find file for:\n#{error_line}"
+ next
+ end
+ end
+
+ prefix = spec_file.index('spec/ruby/') || spec_file.index('spec/truffle/')
+ spec_file = spec_file[prefix..-1]
+
+ tags_file = spec_file.sub('spec/ruby/', "#{tags_dir}/").sub('spec/truffle/', "#{tags_dir}/truffle/")
+ tags_file = tags_file.sub(/_spec\.rb$/, '_tags.txt')
+
+ dir = File.dirname(tags_file)
+ Dir.mkdir(dir) unless Dir.exist?(dir)
+
+ tag_line = "fails:#{description}"
+ lines = File.exist?(tags_file) ? File.readlines(tags_file, chomp: true) : []
+ unless lines.include?(tag_line)
+ puts tags_file
+ File.write(tags_file, (lines + [tag_line]).join("\n") + "\n")
+ end
+}
diff --git a/spec/mspec/tool/wrap_with_guard.rb b/spec/mspec/tool/wrap_with_guard.rb
new file mode 100755
index 0000000000..5b1bf4d7f7
--- /dev/null
+++ b/spec/mspec/tool/wrap_with_guard.rb
@@ -0,0 +1,28 @@
+#!/usr/bin/env ruby
+# Wrap the passed the files with a guard (e.g., `ruby_version_is ""..."3.0"`).
+# Notably if some methods are removed, this is a convenient way to skip such file from a given version.
+# Example usage:
+# $ spec/mspec/tool/wrap_with_guard.rb 'ruby_version_is ""..."3.0"' spec/ruby/library/set/sortedset/**/*_spec.rb
+
+guard, *files = ARGV
+abort "Usage: #{$0} GUARD FILES..." if files.empty?
+
+files.each do |file|
+ contents = File.binread(file)
+ lines = contents.lines.to_a
+
+ lines = lines.map { |line| line.chomp.empty? ? line : " #{line}" }
+
+ version_line = "#{guard} do\n"
+ if lines[0] =~ /^\s*require.+spec_helper/
+ lines[0] = lines[0].sub(/^ /, '')
+ lines.insert 1, "\n", version_line
+ else
+ warn "Could not find 'require spec_helper' line in #{file}"
+ lines.insert 0, version_line
+ end
+
+ lines << "end\n"
+
+ File.binwrite file, lines.join
+end
diff --git a/spec/ruby/.gitignore b/spec/ruby/.gitignore
new file mode 100644
index 0000000000..3f1206a16e
--- /dev/null
+++ b/spec/ruby/.gitignore
@@ -0,0 +1,5 @@
+/Gemfile.lock
+/rubyspec_temp
+/ext
+/.ruby-version
+/.ruby-gemset
diff --git a/spec/ruby/.mspec.constants b/spec/ruby/.mspec.constants
new file mode 100644
index 0000000000..5dd477eb66
--- /dev/null
+++ b/spec/ruby/.mspec.constants
@@ -0,0 +1,234 @@
+Abbrev
+Addrinfo
+AliasObject
+AliasObject2
+AnonWithConstant
+ArbitraryException
+ArraySub
+ArraySubPush
+AryChild
+Base64
+BaseClass
+BasicSocket
+BeCloseToMatrixMatcher
+BigDecimal
+BigMath
+BitwiseAndTest
+BreakTest
+BreakTest2
+CAPI_SIZEOF_LONG
+CApiModuleSpecsAutoload
+CApiModuleSpecsModuleA
+CGI
+CMath
+CODE_LOADING_DIR
+CSAutoloadA
+CSAutoloadB
+CSAutoloadC
+CSAutoloadD
+CSV
+ChainedNextTest
+ChildClass
+ClassIdUnderAutoload
+ClassSpecDefineClass
+ClassSpecsKeywordWithSemicolon
+ClassSpecsKeywordWithoutSemicolon
+ClassSpecsNumber
+ClassUnderAutoload
+CodingUS_ASCII
+CodingUTF_8
+ComparisonTest
+ConstantSpecsIncludedModule
+ConstantSpecsTwo
+ConstantSpecsThree
+ConstantVisibility
+Coverage
+CoverageSpecs
+CustomArgumentError
+DRb
+DRbIdConv
+DRbObject
+DRbUndumped
+Date
+DateTime
+DefSpecNested
+DefSpecNestedB
+DefSpecSingleton
+DefSpecsLambdaVisibility
+DefineMethodByProcClass
+DefineMethodSpecClass
+DefineSingletonMethodSpecClass
+Delegator
+DescArray
+DescObjectTest
+Digest
+DumpableDir
+ERB
+EnsureInClassExample
+EnumerableSpecGrep
+EnumerableSpecGrep2
+EnumerableSpecIncludeP
+EnumerableSpecIncludeP11
+Etc
+EvalBindingA
+EvalBindingProcA
+Exception2MessageMapper
+ExceptionForMatrix
+Fcntl
+Fiddle
+FileStat
+FileUtils
+Find
+Forwardable
+GetoptLong
+HMACConstants
+HashStringsBinary
+HashStringsUSASCII
+HashStringsUTF8
+IPAddr
+IPSocket
+Importer
+IncludeSpecsClass
+IncludeSpecsMiddle
+IncludeSpecsTop
+IncludesMath
+JSON
+KSAutoloadA
+KSAutoloadB
+KSAutoloadBB
+KSAutoloadCallsRequire
+KSAutoloadD
+Logger
+MD5Constants
+MY_INPUT4_FOR_ERB
+Matrix
+MatrixSub
+MethodArity
+Meths
+MethsMore
+Mixin
+ModuleSpecsKeywordWithoutSemicolon
+ModuleSpecsToplevel
+ModuleSpecs_CS1
+ModuleSpecs_CS2
+ModuleSpecs_CS3
+MyClass
+MyClass0ForErb
+MyClass1ForErb
+MyClass1ForErb_
+MyClass2ForErb
+MyClass4ForErb
+MyFiber
+MyModule2ForErb
+MyString
+NamespaceTest
+Net
+OBJDIR
+OBJECT_SPACE_TOP_LEVEL_CONSTANT
+OFor
+ObjectSpaceFixtures
+ObjectSpecDup
+ObjectSpecDupInitCopy
+ObjectTest
+Observable
+Open3
+OpenSSL
+OpenStruct
+OperatorImplementor
+OptParse
+OptionParser
+OrAndXorTest
+OtherCustomException
+ParentClass
+Pathname
+Person
+Prime
+Private
+ProcFromMethod
+Psych
+REXML
+RUBY_SIGNALS
+RbReadline
+Readline
+ReceiverClass
+RegexpSpecsSubclass
+RegexpSpecsSubclassTwo
+Reline
+RescueInClassExample
+Resolv
+Ripper
+SHA1Constants
+SHA256Constants
+SHA384Constants
+SHA512Constants
+SameName
+ScanError
+Scanf
+SecondClass
+SecureRandom
+Set
+Shellwords
+SimpleDelegator
+SingleForwardable
+Singleton
+Socket
+SocketError
+SomeClass
+SortedSet
+SpecificExampleException
+Specs
+StrChild
+StrangeEach
+StringIO
+StringRefinement
+StringScanner
+StringSubclass
+StructClasses
+Syck
+Syslog
+TCPServer
+TCPSocket
+TSort
+Tempfile
+TestServer
+Timeout
+TimeoutError
+UDPSocket
+UNIXServer
+UNIXSocket
+URI
+UnaryMinusTest
+UnicodeNormalize
+UnloadableDumpableDir
+UserArray
+UserCustomConstructorString
+UserDefined
+UserDefinedImmediate
+UserDefinedWithIvar
+UserHash
+UserHashInitParams
+UserMarshal
+UserMarshalWithClassName
+UserMarshalWithIvar
+UserObject
+UserPreviouslyDefinedWithInitializedIvar
+UserRegexp
+UserString
+Vector
+WEBrick
+WIN32OLE
+WIN32OLEQueryInterfaceError
+WIN32OLERuntimeError
+WIN32OLE_EVENT
+WIN32OLE_METHOD
+WIN32OLE_PARAM
+WIN32OLE_RECORD
+WIN32OLE_RUBYSPEC
+WIN32OLE_TYPE
+WIN32OLE_TYPELIB
+WIN32OLE_VARIABLE
+WIN32OLE_VARIANT
+WeakRef
+Win32
+YAML
+Zlib
diff --git a/spec/ruby/.rubocop.yml b/spec/ruby/.rubocop.yml
new file mode 100644
index 0000000000..82733c4b4d
--- /dev/null
+++ b/spec/ruby/.rubocop.yml
@@ -0,0 +1,185 @@
+inherit_from: .rubocop_todo.yml
+
+AllCops:
+ TargetRubyVersion: 2.7
+ DisplayCopNames: true
+ Exclude:
+ - command_line/fixtures/bad_syntax.rb
+ DisabledByDefault: true
+ NewCops: disable
+
+Layout/TrailingWhitespace:
+ Enabled: true
+
+Layout/TrailingEmptyLines:
+ Enabled: true
+ Exclude:
+ - library/coverage/fixtures/some_class.rb
+
+Layout/SpaceInLambdaLiteral:
+ Enabled: true
+ EnforcedStyle: require_space
+
+Lint:
+ Enabled: true
+
+# {...} has higher precedence than do ... end, on purpose
+Lint/AmbiguousBlockAssociation:
+ Enabled: false
+
+Lint/AssignmentInCondition:
+ Enabled: false
+
+Lint/BooleanSymbol:
+ Enabled: false
+
+Lint/InterpolationCheck:
+ Enabled: false
+
+Lint/LiteralAsCondition:
+ Enabled: false
+
+Lint/RedundantRequireStatement:
+ Enabled: false
+
+Lint/RedundantSplatExpansion:
+ Enabled: false
+
+Lint/UnifiedInteger:
+ Enabled: false
+
+Lint/UnusedBlockArgument:
+ Enabled: false
+
+Lint/UnusedMethodArgument:
+ Enabled: false
+
+Lint/UselessAssignment:
+ Enabled: false
+
+Lint/BinaryOperatorWithIdenticalOperands:
+ Enabled: false
+
+Lint/EmptyConditionalBody:
+ Enabled: false # buggy
+
+Lint/Void:
+ Enabled: false
+
+Lint/ConstantDefinitionInBlock:
+ Enabled: false
+
+Lint/RaiseException:
+ Enabled: false
+
+Lint/FloatComparison:
+ Enabled: false
+
+Lint/DeprecatedClassMethods:
+ Enabled: false
+
+Lint/UnreachableLoop:
+ Enabled: false
+
+Lint/MissingSuper:
+ Enabled: false
+
+Lint/UselessMethodDefinition:
+ Enabled: false
+
+Lint/UselessTimes:
+ Enabled: false
+
+Lint/MixedRegexpCaptureTypes:
+ Enabled: false
+
+Lint/DuplicateElsifCondition:
+ Enabled: false
+
+Lint/OutOfRangeRegexpRef:
+ Enabled: false
+
+Lint/InheritException:
+ Enabled: false
+
+Lint/ElseLayout:
+ Exclude:
+ - 'language/if_spec.rb'
+
+Lint/EmptyExpression:
+ Exclude:
+ - 'language/**/*.rb'
+
+Lint/EmptyWhen:
+ Exclude:
+ - language/case_spec.rb
+ - optional/capi/spec_helper.rb
+
+Lint/ErbNewArguments:
+ Exclude:
+ - 'library/erb/new_spec.rb'
+
+Lint/FormatParameterMismatch:
+ Exclude:
+ - 'core/kernel/shared/sprintf.rb'
+ - 'core/string/modulo_spec.rb'
+
+Lint/NestedMethodDefinition:
+ Exclude:
+ - language/def_spec.rb
+ - language/fixtures/def.rb
+
+Lint/ShadowingOuterLocalVariable:
+ Exclude:
+ - 'core/binding/local_variables_spec.rb'
+ - 'core/kernel/local_variables_spec.rb'
+ - 'language/block_spec.rb'
+ - 'language/proc_spec.rb'
+
+Lint/UnreachableCode:
+ Exclude:
+ - 'core/enumerator/lazy/fixtures/classes.rb'
+ - 'core/kernel/catch_spec.rb'
+ - 'core/kernel/raise_spec.rb'
+ - 'core/kernel/throw_spec.rb'
+ - 'language/break_spec.rb'
+ - 'language/optional_assignments_spec.rb'
+ - 'language/fixtures/break.rb'
+ - 'language/fixtures/break_lambda_toplevel.rb'
+ - 'language/fixtures/break_lambda_toplevel_block.rb'
+ - 'language/fixtures/break_lambda_toplevel_method.rb'
+ - 'language/fixtures/return.rb'
+ - 'language/next_spec.rb'
+ - 'language/return_spec.rb'
+ - 'optional/capi/kernel_spec.rb'
+ - 'shared/kernel/raise.rb'
+
+Lint/UriRegexp:
+ Exclude:
+ - 'library/uri/regexp_spec.rb'
+
+Lint/Debugger:
+ Exclude:
+ - 'core/binding/fixtures/irb.rb'
+
+Lint/Loop:
+ Enabled: false
+
+Style/BlockComments:
+ Enabled: true
+
+Style/Lambda:
+ Enabled: true
+ EnforcedStyle: literal
+ Exclude:
+ - 'language/lambda_spec.rb'
+ - 'language/proc_spec.rb'
+ - 'language/numbered_parameters_spec.rb'
+ - 'core/kernel/lambda_spec.rb'
+
+Style/EmptyLambdaParameter:
+ Enabled: true
+
+Style/StabbyLambdaParentheses:
+ Enabled: true
+ EnforcedStyle: require_no_parentheses
diff --git a/spec/ruby/.rubocop_todo.yml b/spec/ruby/.rubocop_todo.yml
new file mode 100644
index 0000000000..ac9cfae2bf
--- /dev/null
+++ b/spec/ruby/.rubocop_todo.yml
@@ -0,0 +1,137 @@
+# This configuration was generated by
+# `rubocop --auto-gen-config`
+# on 2019-12-12 22:16:26 +0900 using RuboCop version 0.77.0.
+# The point is for the user to remove these configuration records
+# one by one as the offenses are removed from the code base.
+# Note that changes in the inspected code, or installation of new
+# versions of RuboCop, may require this file to be generated again.
+
+# Offense count: 2
+Lint/DuplicateCaseCondition:
+ Exclude:
+ - 'language/case_spec.rb'
+
+# Offense count: 6
+Lint/DuplicateMethods:
+ Exclude:
+ - 'core/array/fixtures/encoded_strings.rb'
+ - 'core/method/fixtures/classes.rb'
+ - 'core/module/fixtures/classes.rb'
+ - 'core/unboundmethod/fixtures/classes.rb'
+ - 'fixtures/class.rb'
+
+# Offense count: 8
+Lint/EnsureReturn:
+ Exclude:
+ - 'language/fixtures/ensure.rb'
+ - 'language/fixtures/return.rb'
+ - 'language/return_spec.rb'
+
+# Offense count: 10
+Lint/FlipFlop:
+ Exclude:
+ - 'language/if_spec.rb'
+ - 'language/precedence_spec.rb'
+
+# Offense count: 10
+Lint/FloatOutOfRange:
+ Exclude:
+ - 'core/string/modulo_spec.rb'
+
+# Offense count: 2
+Lint/ImplicitStringConcatenation:
+ Exclude:
+ - 'language/string_spec.rb'
+
+# Offense count: 4
+Lint/IneffectiveAccessModifier:
+ Exclude:
+ - 'core/kernel/fixtures/classes.rb'
+ - 'core/module/fixtures/classes.rb'
+ - 'language/fixtures/private.rb'
+
+# Offense count: 72
+# Cop supports --auto-correct.
+Lint/LiteralInInterpolation:
+ Exclude:
+ - 'core/module/refine_spec.rb'
+ - 'core/regexp/shared/new.rb'
+ - 'core/string/shared/to_sym.rb'
+ - 'language/alias_spec.rb'
+ - 'language/defined_spec.rb'
+ - 'language/fixtures/squiggly_heredoc.rb'
+ - 'language/string_spec.rb'
+ - 'language/symbol_spec.rb'
+ - 'language/undef_spec.rb'
+ - 'library/net/ftp/connect_spec.rb'
+
+# Offense count: 8
+# Cop supports --auto-correct.
+Lint/MultipleComparison:
+ Exclude:
+ - 'language/precedence_spec.rb'
+
+# Offense count: 9
+Lint/ParenthesesAsGroupedExpression:
+ Exclude:
+ - 'core/string/fixtures/freeze_magic_comment.rb'
+ - 'language/block_spec.rb'
+ - 'language/fixtures/send.rb'
+ - 'language/method_spec.rb'
+
+# Offense count: 2
+# Cop supports --auto-correct.
+Lint/RedundantStringCoercion:
+ Exclude:
+ - 'core/io/print_spec.rb'
+
+# Offense count: 1
+# Cop supports --auto-correct.
+Lint/RedundantWithIndex:
+ Exclude:
+ - 'core/enumerator/with_index_spec.rb'
+
+# Offense count: 22
+Lint/RescueException:
+ Exclude:
+ - 'command_line/fixtures/debug_info.rb'
+ - 'core/dir/fileno_spec.rb'
+ - 'core/exception/cause_spec.rb'
+ - 'core/exception/no_method_error_spec.rb'
+ - 'core/kernel/fixtures/autoload_frozen.rb'
+ - 'core/kernel/raise_spec.rb'
+ - 'core/module/autoload_spec.rb'
+ - 'core/mutex/sleep_spec.rb'
+ - 'core/thread/abort_on_exception_spec.rb'
+ - 'core/thread/shared/exit.rb'
+ - 'language/rescue_spec.rb'
+ - 'library/erb/filename_spec.rb'
+
+# Offense count: 4
+# Configuration parameters: IgnoreImplicitReferences.
+Lint/ShadowedArgument:
+ Exclude:
+ - 'language/fixtures/super.rb'
+
+# Offense count: 39
+# Configuration parameters: AllowComments.
+Lint/SuppressedException:
+ Enabled: false
+
+# Offense count: 9
+# Configuration parameters: AllowKeywordBlockArguments.
+Lint/UnderscorePrefixedVariableName:
+ Exclude:
+ - 'core/io/pipe_spec.rb'
+ - 'core/io/popen_spec.rb'
+ - 'language/block_spec.rb'
+
+# Offense count: 7
+# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods.
+Lint/UselessAccessModifier:
+ Exclude:
+ - 'core/module/define_method_spec.rb'
+ - 'core/module/fixtures/classes.rb'
+ - 'core/module/module_function_spec.rb'
+ - 'core/module/private_class_method_spec.rb'
+ - 'language/fixtures/send.rb'
diff --git a/spec/ruby/CONTRIBUTING.md b/spec/ruby/CONTRIBUTING.md
new file mode 100644
index 0000000000..adfc2fb0ca
--- /dev/null
+++ b/spec/ruby/CONTRIBUTING.md
@@ -0,0 +1,295 @@
+Contributions are much appreciated.
+Please open a pull request or add an issue to discuss what you intend to work on.
+If the pull requests passes the CI and conforms to the existing style of specs, it will be merged.
+
+### File organization
+
+Spec are grouped in 5 separate top-level groups:
+
+* `command_line`: for the ruby executable command-line flags (`-v`, `-e`, etc)
+* `language`: for the language keywords and syntax constructs (`if`, `def`, `A::B`, etc)
+* `core`: for the core methods (`Integer#+`, `String#upcase`, no need to require anything)
+* `library`: for the standard libraries methods (`CSV.new`, `YAML.parse`, need to require the stdlib)
+* `optional/capi`: for functions available to the Ruby C-extension API
+
+The exact file for methods is decided by the `#owner` of a method, for instance for `#group_by`:
+
+```ruby
+> [].method(:group_by)
+=> #<Method: Array(Enumerable)#group_by>
+> [].method(:group_by).owner
+=> Enumerable
+```
+
+Which should therefore be specified in `core/enumerable/group_by_spec.rb`.
+
+### MkSpec - a tool to generate the spec structure
+
+If you want to create new specs, you should use `mkspec`, part of [MSpec](http://github.com/ruby/mspec).
+
+ $ ../mspec/bin/mkspec -h
+
+#### Creating files for unspecified modules or classes
+
+For instance, to create specs for `forwardable`:
+
+ $ ../mspec/bin/mkspec -b library -rforwardable -c Forwardable
+
+Specify `core` or `library` as the `base`.
+
+#### Finding unspecified core methods
+
+This is very easy, just run the command below in your `spec` directory.
+`ruby` must be a recent version of MRI.
+
+ $ ruby --disable-gem ../mspec/bin/mkspec
+
+You might also want to search for:
+
+ it "needs to be reviewed for spec completeness"
+
+which indicates the file was generated but the method unspecified.
+
+### Matchers and expectations
+
+Here is a list of frequently-used matchers, which should be enough for most specs.
+There are a few extra specific matchers used in the couple specs that need it.
+
+#### Comparison matchers
+
+```ruby
+(1 + 2).should == 3 # Calls #==
+(1 + 2).should_not == 5
+
+File.should.equal?(File) # Calls #equal? (tests identity)
+(1 + 2).should.eql?(3) # Calls #eql? (Hash equality)
+
+1.should < 2
+2.should <= 2
+3.should >= 3
+4.should > 3
+
+"Hello".should =~ /l{2}/ # Calls #=~ (Regexp match)
+```
+
+#### Predicate matchers
+
+```ruby
+[].should.empty?
+[1,2,3].should.include?(2)
+
+"hello".should.start_with?("h")
+"hello".should.end_with?("o")
+
+(0.1 + 0.2).should be_close(0.3, TOLERANCE) # (0.2-0.1).abs < TOLERANCE
+(0.0/0.0).should.nan?
+(1.0/0.0).should be_positive_infinity
+(-1.0/0.0).should be_negative_infinity
+
+3.14.should be_an_instance_of(Float) # Calls #instance_of?
+3.14.should be_kind_of(Numeric) # Calls #is_a?
+Numeric.should be_ancestor_of(Float) # Float.ancestors.include?(Numeric)
+
+3.14.should.respond_to?(:to_i)
+Integer.should have_instance_method(:+)
+Array.should have_method(:new)
+```
+
+Also `have_constant`, `have_private_instance_method`, `have_singleton_method`, etc.
+
+#### Exception matchers
+
+```ruby
+-> {
+ raise "oops"
+}.should raise_error(RuntimeError, /oops/)
+
+-> {
+ raise "oops"
+}.should raise_error(RuntimeError) { |e|
+ # Custom checks on the Exception object
+ e.message.should.include?("oops")
+ e.cause.should == nil
+}
+```
+
+##### should_not raise_error
+
+**To avoid!** Instead, use an expectation testing what the code in the lambda does.
+If an exception is raised, it will fail the example anyway.
+
+```ruby
+-> { ... }.should_not raise_error
+```
+
+#### Warning matcher
+
+```ruby
+-> {
+ Fixnum
+}.should complain(/constant ::Fixnum is deprecated/) # Expect a warning
+```
+
+### Guards
+
+Different guards are available as defined by mspec.
+Here is a list of the most commonly-used guards:
+
+#### Version guards
+
+```ruby
+ruby_version_is ""..."2.6" do
+ # Specs for RUBY_VERSION < 2.6
+end
+
+ruby_version_is "2.6" do
+ # Specs for RUBY_VERSION >= 2.6
+end
+```
+
+#### Platform guards
+
+```ruby
+platform_is :windows do
+ # Specs only valid on Windows
+end
+
+platform_is_not :windows do
+ # Specs valid on platforms other than Windows
+end
+
+platform_is :linux, :darwin do # OR
+end
+
+platform_is_not :linux, :darwin do # Not Linux and not Darwin
+end
+
+platform_is wordsize: 64 do
+ # 64-bit platform
+end
+
+big_endian do
+ # Big-endian platform
+end
+```
+
+#### Guard for bug
+
+In case there is a bug in MRI and the fix will be backported to previous versions.
+If it is not backported or not likely, use `ruby_version_is` instead.
+First, file a bug at https://bugs.ruby-lang.org/.
+The problem is `ruby_bug` would make non-MRI implementations fail this spec while MRI itself does not pass it, so it should only be used if the bug is/will be fixed and backported.
+
+```ruby
+ruby_bug '#13669', ''...'3.2' do
+ it "works like this" do
+ # Specify the expected behavior here, not the bug
+ end
+end
+```
+
+#### Combining guards
+
+```ruby
+guard -> { platform_is :windows and ruby_version_is ""..."2.6" } do
+ # Windows and RUBY_VERSION < 2.6
+end
+
+guard_not -> { platform_is :windows and ruby_version_is ""..."2.6" } do
+ # The opposite
+end
+```
+
+#### Custom guard
+
+```ruby
+max_uint = (1 << 32) - 1
+guard -> { max_uint <= fixnum_max } do
+end
+```
+
+Custom guards are better than a simple `if` as they allow [mspec commands](https://github.com/ruby/mspec/issues/30#issuecomment-312487779) to work properly.
+
+#### Implementation-specific behaviors
+
+In general, the usage of guards should be minimized as possible.
+
+There are no guards to define implementation-specific behavior because
+the Ruby Spec Suite defines common behavior and not implementation details.
+Use the implementation test suite for these.
+
+If an implementation does not support some feature, simply tag the related specs as failing instead.
+
+### Shared Specs
+
+Often throughout Ruby, identical functionality is used by different methods and modules. In order
+to avoid duplication of specs, we have shared specs that are re-used in other specs. The use is a
+bit tricky however, so let's go over it.
+
+Commonly, if a shared spec is only reused within its own module, the shared spec will live within a
+shared directory inside that module's directory. For example, the `core/hash/shared/key.rb` spec is
+only used by `Hash` specs, and so it lives inside `core/hash/shared/`.
+
+When a shared spec is used across multiple modules or classes, it lives within the `shared/` directory.
+An example of this is the `shared/file/socket.rb` which is used by `core/file/socket_spec.rb`,
+`core/filetest/socket_spec.rb`, and `core/file/state/socket_spec.rb` and so it lives in the root `shared/`.
+
+Defining a shared spec involves adding a `shared: true` option to the top-level `describe` block. This
+will signal not to run the specs directly by the runner. Shared specs have access to two instance
+variables from the implementor spec: `@method` and `@object`, which the implementor spec will pass in.
+
+Here's an example of a snippet of a shared spec and two specs which integrates it:
+
+```ruby
+# core/hash/shared/key.rb
+describe :hash_key_p, shared: true do
+ it "returns true if the key's matching value was false" do
+ { xyz: false }.send(@method, :xyz).should == true
+ end
+end
+
+# core/hash/key_spec.rb
+describe "Hash#key?" do
+ it_behaves_like :hash_key_p, :key?
+end
+
+# core/hash/include_spec.rb
+describe "Hash#include?" do
+ it_behaves_like :hash_key_p, :include?
+end
+```
+
+In the example, the first `describe` defines the shared spec `:hash_key_p`, which defines a spec that
+calls the `@method` method with an expectation. In the implementor spec, we use `it_behaves_like` to
+integrate the shared spec. `it_behaves_like` takes 3 parameters: the key of the shared spec, a method,
+and an object. These last two parameters are accessible via `@method` and `@object` in the shared spec.
+
+Sometimes, shared specs require more context from the implementor class than a simple object. We can address
+this by passing a lambda as the method, which will have the scope of the implementor. Here's an example of
+how this is used currently:
+
+```ruby
+describe :kernel_sprintf, shared: true do
+ it "raises TypeError exception if cannot convert to Integer" do
+ -> { @method.call("%b", Object.new) }.should raise_error(TypeError)
+ end
+end
+
+describe "Kernel#sprintf" do
+ it_behaves_like :kernel_sprintf, -> (format, *args) {
+ sprintf(format, *args)
+ }
+end
+
+describe "Kernel.sprintf" do
+ it_behaves_like :kernel_sprintf, -> (format, *args) {
+ Kernel.sprintf(format, *args)
+ }
+end
+```
+
+In the above example, the method being passed is a lambda that triggers the specific conditions of the shared spec.
+
+### Style
+
+Do not leave any trailing space and follow the existing style.
diff --git a/spec/ruby/LICENSE b/spec/ruby/LICENSE
new file mode 100644
index 0000000000..d581dd1c9f
--- /dev/null
+++ b/spec/ruby/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2008 Engine Yard, Inc. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/spec/ruby/README.md b/spec/ruby/README.md
new file mode 100644
index 0000000000..018bf0ca3e
--- /dev/null
+++ b/spec/ruby/README.md
@@ -0,0 +1,157 @@
+# The Ruby Spec Suite
+
+[![Actions Build Status](https://github.com/ruby/spec/workflows/CI/badge.svg)](https://github.com/ruby/spec/actions)
+
+The Ruby Spec Suite, abbreviated `ruby/spec`, is a test suite for the behavior of the Ruby programming language.
+
+### Description and Motivation
+
+It is not a standardized specification like the ISO one, and does not aim to become one.
+Instead, it is a practical tool to describe and test the behavior of Ruby with code.
+
+Every example code has a textual description, which presents several advantages:
+
+* It is easier to understand the intent of the author
+* It documents how recent versions of Ruby should behave
+* It helps Ruby implementations to agree on a common behavior
+
+The specs are written with syntax similar to RSpec 2.
+They are run with MSpec, the purpose-built framework for running the Ruby Spec Suite.
+For more information, see the [MSpec](https://github.com/ruby/mspec) project.
+
+The specs describe the [language syntax](language/), the [core library](core/), the [standard library](library/), the [C API for extensions](optional/capi) and the [command line flags](command_line/).
+The language specs are grouped by keyword while the core and standard library specs are grouped by class and method.
+
+ruby/spec is known to be tested in these implementations for every commit:
+
+* [MRI](https://rubyci.org/) on 30 platforms and 4 versions
+* [JRuby](https://github.com/jruby/jruby/tree/master/spec/ruby) for both 1.7 and 9.x
+* [TruffleRuby](https://github.com/oracle/truffleruby/tree/master/spec/ruby)
+* [Opal](https://github.com/opal/opal/tree/master/spec)
+* [Artichoke](https://github.com/artichoke/spec/tree/artichoke-vendor)
+
+ruby/spec describes the behavior of Ruby 2.7 and more recent Ruby versions.
+More precisely, every latest stable MRI release should [pass](https://github.com/ruby/spec/actions/workflows/ci.yml) all specs of ruby/spec (2.7.x, 3.0.x, 3.1.x, etc), and those are tested in CI.
+
+### Synchronization with Ruby Implementations
+
+The specs are synchronized both ways around once a month by @eregon between ruby/spec, MRI, JRuby and TruffleRuby,
+using [this script](https://github.com/ruby/mspec/blob/master/tool/sync/sync-rubyspec.rb).
+Each of these repositories has a full copy of the specs under `spec/ruby` to ease editing specs.
+Any of these repositories can be used to add or edit specs, use what is most convenient for you.
+
+For *testing* the development version of a Ruby implementation, one should always test against that implementation's copy of the specs under `spec/ruby`, as that's what the Ruby implementation tests against in their CI.
+Also, this repository doesn't always contain the latest spec changes from MRI (it's synchronized monthly), and does not contain tags (specs marked as failing on that Ruby implementation).
+Running specs on a Ruby implementation can be done with:
+
+```
+$ cd ruby_implementation/spec/ruby
+# Add ../ruby_implementation/bin in PATH, or pass -t /path/to/bin/ruby
+$ ../mspec/bin/mspec
+```
+
+### Specs for old Ruby versions
+
+For older specs try these commits:
+
+* Ruby 2.0.0-p647 - [Suite](https://github.com/ruby/spec/commit/245862558761d5abc676843ef74f86c9bcc8ea8d) using [MSpec](https://github.com/ruby/mspec/commit/f90efa068791064f955de7a843e96e2d7d3041c2) (may encounter 2 failures)
+* Ruby 2.1.9 - [Suite](https://github.com/ruby/spec/commit/f029e65241374386077ac500add557ae65069b55) using [MSpec](https://github.com/ruby/mspec/commit/55568ea3918c6380e64db8c567d732fa5781efed)
+* Ruby 2.2.10 - [Suite](https://github.com/ruby/spec/commit/cbaa0e412270c944df0c2532fc500c920dba0e92) using [MSpec](https://github.com/ruby/mspec/commit/d84d7668449e96856c5f6bac8cb1526b6d357ce3)
+* Ruby 2.3.8 - [Suite](https://github.com/ruby/spec/commit/dc733114d8ae66a3368ba3a98422c50147a76ba5) using [MSpec](https://github.com/ruby/mspec/commit/4599bc195fb109f2a482a01c32a7d659518369ea)
+* Ruby 2.4.10 - [Suite](https://github.com/ruby/spec/commit/bce4f2b81d6c31db67cf4d023a0625ceadde59bd) using [MSpec](https://github.com/ruby/mspec/commit/e7eb8aa4c26495b7b461e687d950b96eb08b3ff2)
+* Ruby 2.5.9 - [Suite](https://github.com/ruby/spec/commit/c503335d3d9f6ec6ef24de60a0716c34af69b64f) using [MSpec](https://github.com/ruby/mspec/commit/0091e8a62e954717cd54641f935eaf1403692041)
+* Ruby 2.6.10 - [Suite](https://github.com/ruby/spec/commit/aaf998fb8c92c4e63ad423a2e7ca6e6921818c6e) using [MSpec](https://github.com/ruby/mspec/commit/5e36c684e9e2b92b1187589bba1df22c640a8661)
+
+### Running the specs
+
+First, clone this repository:
+
+ $ git clone https://github.com/ruby/spec.git
+
+Then move to it:
+
+ $ cd spec
+
+Clone [MSpec](https://github.com/ruby/mspec):
+
+ $ git clone https://github.com/ruby/mspec.git ../mspec
+
+And run the spec suite:
+
+ $ ../mspec/bin/mspec
+
+This will execute all the specs using the executable named `ruby` on your current PATH.
+
+### Running Specs with a Specific Ruby Implementation
+
+Use the `-t` option to specify the Ruby implementation with which to run the specs.
+The argument is either a full path to the Ruby binary, or an executable in `$PATH`.
+
+ $ ../mspec/bin/mspec -t /path/to/some/bin/ruby
+
+### Running Selected Specs
+
+To run a single spec file, pass the filename to `mspec`:
+
+ $ ../mspec/bin/mspec core/kernel/kind_of_spec.rb
+
+You can also pass a directory, in which case all specs in that directories will be run:
+
+ $ ../mspec/bin/mspec core/kernel
+
+Finally, you can also run them per group as defined in `default.mspec`.
+The following command will run all language specs:
+
+ $ ../mspec/bin/mspec :language
+
+In similar fashion, the following commands run the respective specs:
+
+ $ ../mspec/bin/mspec :core
+ $ ../mspec/bin/mspec :library
+ $ ../mspec/bin/mspec :capi
+
+### Sanity Checks When Running Specs
+
+A number of checks for various kind of "leaks" (file descriptors, temporary files,
+threads, subprocesses, `ENV`, `ARGV`, global encodings, top-level constants) can be
+enabled with `CHECK_LEAKS=true`:
+
+ $ CHECK_LEAKS=true ../mspec/bin/mspec
+
+New top-level constants should only be introduced when needed or follow the
+pattern `<ClassBeingTested>Specs` such as `module StringSpecs`.
+Other constants used for testing should be nested under such a module.
+
+Exceptions to these rules are contained in the file `.mspec.constants`.
+MSpec can automatically add new top-level constants in this file with:
+
+ $ CHECK_LEAKS=save mspec ../mspec/bin/mspec file
+
+### Contributing and Writing Specs
+
+See [CONTRIBUTING.md](https://github.com/ruby/spec/blob/master/CONTRIBUTING.md) for documentation about contributing and writing specs (guards, matchers, etc).
+
+### Dependencies
+
+These command-line executables are needed to run the specs.
+
+* `echo`
+* `stat` for `core/file/*time_spec.rb`
+* `find` for `core/file/fixtures/file_types.rb` (package `findutils`, not needed on Windows)
+
+The file `/etc/services` is required for socket specs (package `netbase` on Debian, not needed on Windows).
+
+### Socket specs from rubysl-socket
+
+Most specs under `library/socket` were imported from the rubysl-socket project (which is no longer on GitHub).
+The 3 copyright holders of rubysl-socket, Yorick Peterse, Chuck Remes and
+Brian Shirai, agreed to relicense those specs under the MIT license in ruby/spec.
+
+### History and RubySpec
+
+This project was originally born from [Rubinius](https://github.com/rubinius/rubinius) tests being converted to the spec style.
+The revision history of these specs is available [here](https://github.com/ruby/spec/blob/2b886623/CHANGES.before-2008-05-10).
+These specs were later extracted to their own project, RubySpec, with a specific vision and principles.
+At the end of 2014, Brian Shirai, the creator of RubySpec, decided to [end RubySpec](http://rubinius.com/2014/12/31/matz-s-ruby-developers-don-t-use-rubyspec/).
+A couple months later, the different repositories were merged and [the project was revived](https://eregon.github.io/rubyspec/2015/07/29/rubyspec-is-reborn.html).
+On 12 January 2016, the name was changed to "The Ruby Spec Suite" for clarity and to let the RubySpec ideology rest in peace.
diff --git a/spec/ruby/TODO b/spec/ruby/TODO
new file mode 100644
index 0000000000..f81f070d71
--- /dev/null
+++ b/spec/ruby/TODO
@@ -0,0 +1,8 @@
+* Decide a way to test methods that are only visible given a specific
+ command-line option. For example, Kernel#gsub with -n/-p on 1.9.
+* Look at automating discovery of guarded bugs which have been fixed.
+* Use mocks for all Math functions that coerce with #to_f; currently a fixture
+ is used.
+* investigate slow specs (run with -fp) and make them faster.
+* restore some caller specs from 642bf529
+* restore refinements specs and update. See 56c5528f and f20a62e8.
diff --git a/spec/ruby/command_line/backtrace_limit_spec.rb b/spec/ruby/command_line/backtrace_limit_spec.rb
new file mode 100644
index 0000000000..56afa8efef
--- /dev/null
+++ b/spec/ruby/command_line/backtrace_limit_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../spec_helper'
+
+ruby_version_is "3.0" do
+ describe "The --backtrace-limit command line option" do
+ it "limits top-level backtraces to a given number of entries" do
+ file = fixture(__FILE__ , "backtrace.rb")
+ out = ruby_exe(file, options: "--backtrace-limit=2", args: "top 2>&1", exit_status: 1)
+ out = out.gsub(__dir__, '')
+
+ out.should == <<-MSG
+top
+/fixtures/backtrace.rb:2:in `a': oops (RuntimeError)
+\tfrom /fixtures/backtrace.rb:6:in `b'
+\tfrom /fixtures/backtrace.rb:10:in `c'
+\t ... 2 levels...
+ MSG
+ end
+
+ it "affects Exception#full_message" do
+ file = fixture(__FILE__ , "backtrace.rb")
+ out = ruby_exe(file, options: "--backtrace-limit=2", args: "full_message 2>&1")
+ out = out.gsub(__dir__, '')
+
+ out.should == <<-MSG
+full_message
+/fixtures/backtrace.rb:2:in `a': oops (RuntimeError)
+\tfrom /fixtures/backtrace.rb:6:in `b'
+\tfrom /fixtures/backtrace.rb:10:in `c'
+\t ... 2 levels...
+ MSG
+ end
+
+ it "does not affect Exception#backtrace" do
+ file = fixture(__FILE__ , "backtrace.rb")
+ out = ruby_exe(file, options: "--backtrace-limit=2", args: "backtrace 2>&1")
+ out = out.gsub(__dir__, '')
+
+ out.should == <<-MSG
+backtrace
+/fixtures/backtrace.rb:2:in `a'
+/fixtures/backtrace.rb:6:in `b'
+/fixtures/backtrace.rb:10:in `c'
+/fixtures/backtrace.rb:14:in `d'
+/fixtures/backtrace.rb:29:in `<main>'
+ MSG
+ end
+ end
+end
diff --git a/spec/ruby/command_line/dash_a_spec.rb b/spec/ruby/command_line/dash_a_spec.rb
new file mode 100644
index 0000000000..9ea135dc76
--- /dev/null
+++ b/spec/ruby/command_line/dash_a_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../spec_helper'
+
+describe "The -a command line option" do
+ before :each do
+ @names = fixture __FILE__, "full_names.txt"
+ end
+
+ it "runs the code in loop conditional on Kernel.gets()" do
+ ruby_exe("puts $F.last", options: "-n -a", escape: true,
+ args: " < #{@names}").should ==
+ "jones\nfield\ngrey\n"
+ end
+
+ it "sets $-a" do
+ ruby_exe("puts $-a", options: "-n -a", escape: true,
+ args: " < #{@names}").should ==
+ "true\ntrue\ntrue\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_c_spec.rb b/spec/ruby/command_line/dash_c_spec.rb
new file mode 100644
index 0000000000..6b3a5de685
--- /dev/null
+++ b/spec/ruby/command_line/dash_c_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+describe "The -c command line option" do
+ it "checks syntax in given file" do
+ ruby_exe(nil, args: "-c #{__FILE__}").chomp.should == "Syntax OK"
+ end
+
+ it "checks syntax in -e strings" do
+ ruby_exe(nil, args: "-c -e 'puts 1' -e 'hello world'").chomp.should == "Syntax OK"
+ end
+
+ #Also needs spec for reading from STDIN
+end
diff --git a/spec/ruby/command_line/dash_d_spec.rb b/spec/ruby/command_line/dash_d_spec.rb
new file mode 100644
index 0000000000..26891b4791
--- /dev/null
+++ b/spec/ruby/command_line/dash_d_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../spec_helper'
+
+describe "The -d command line option" do
+ before :each do
+ @script = fixture __FILE__, "debug.rb"
+ end
+
+ it "sets $DEBUG to true" do
+ ruby_exe(@script, options: "-d",
+ args: "0 2> #{File::NULL}").chomp.should == "$DEBUG true"
+ end
+
+ it "sets $VERBOSE to true" do
+ ruby_exe(@script, options: "-d",
+ args: "1 2> #{File::NULL}").chomp.should == "$VERBOSE true"
+ end
+
+ it "sets $-d to true" do
+ ruby_exe(@script, options: "-d",
+ args: "2 2> #{File::NULL}").chomp.should == "$-d true"
+ end
+end
diff --git a/spec/ruby/command_line/dash_e_spec.rb b/spec/ruby/command_line/dash_e_spec.rb
new file mode 100644
index 0000000000..24ed34376d
--- /dev/null
+++ b/spec/ruby/command_line/dash_e_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../spec_helper'
+
+describe "The -e command line option" do
+ it "evaluates the given string" do
+ ruby_exe("puts 'foo'").chomp.should == "foo"
+ end
+
+ it "joins multiple strings with newlines" do
+ ruby_exe(nil, args: %Q{-e "puts 'hello" -e "world'" 2>&1}).chomp.should == "hello\nworld"
+ end
+
+ it "uses 'main' as self" do
+ ruby_exe("puts self", escape: false).chomp.should == "main"
+ end
+
+ it "uses '-e' as file" do
+ ruby_exe("puts __FILE__", escape: false).chomp.should == "-e"
+ end
+
+ it "uses '-e' in $0" do
+ system(*ruby_exe, '-e', 'exit $0 == "-e"').should == true
+ end
+
+ #needs to test return => LocalJumpError
+
+ describe "with -n and an Integer range" do
+ before :each do
+ @script = "-ne 'print if %s' #{fixture(__FILE__, "conditional_range.txt")}"
+ end
+
+ it "mimics an awk conditional by comparing an inclusive-end range with $." do
+ ruby_exe(nil, args: (@script % "2..3")).should == "2\n3\n"
+ ruby_exe(nil, args: (@script % "2..2")).should == "2\n"
+ end
+
+ it "mimics a sed conditional by comparing an exclusive-end range with $." do
+ ruby_exe(nil, args: (@script % "2...3")).should == "2\n3\n"
+ ruby_exe(nil, args: (@script % "2...2")).should == "2\n3\n4\n5\n"
+ end
+ end
+end
diff --git a/spec/ruby/command_line/dash_encoding_spec.rb b/spec/ruby/command_line/dash_encoding_spec.rb
new file mode 100644
index 0000000000..5803d328c1
--- /dev/null
+++ b/spec/ruby/command_line/dash_encoding_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../spec_helper'
+
+describe "The --encoding command line option" do
+ before :each do
+ @test_string = "print [Encoding.default_external.name, Encoding.default_internal&.name].inspect"
+ @enc2 = Encoding::ISO_8859_1
+ end
+
+ describe "sets Encoding.default_external and optionally Encoding.default_internal" do
+ it "if given a single encoding with an =" do
+ ruby_exe(@test_string, options: "--disable-gems --encoding=big5").should == [Encoding::Big5.name, nil].inspect
+ end
+
+ it "if given a single encoding as a separate argument" do
+ ruby_exe(@test_string, options: "--disable-gems --encoding big5").should == [Encoding::Big5.name, nil].inspect
+ end
+
+ it "if given two encodings with an =" do
+ ruby_exe(@test_string, options: "--disable-gems --encoding=big5:#{@enc2}").should == [Encoding::Big5.name, @enc2.name].inspect
+ end
+
+ it "if given two encodings as a separate argument" do
+ ruby_exe(@test_string, options: "--disable-gems --encoding big5:#{@enc2}").should == [Encoding::Big5.name, @enc2.name].inspect
+ end
+ end
+
+ it "does not accept a third encoding" do
+ options = {
+ options: "--disable-gems --encoding big5:#{@enc2}:utf-32le",
+ args: "2>&1",
+ exit_status: 1
+ }
+
+ ruby_exe(@test_string, options).should =~ /extra argument for --encoding: utf-32le/
+ end
+end
diff --git a/spec/ruby/command_line/dash_external_encoding_spec.rb b/spec/ruby/command_line/dash_external_encoding_spec.rb
new file mode 100644
index 0000000000..f052674dc8
--- /dev/null
+++ b/spec/ruby/command_line/dash_external_encoding_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../spec_helper'
+
+describe 'The --external-encoding command line option sets Encoding.default_external' do
+ before :each do
+ @test_string = "print Encoding.default_external.name"
+ end
+
+ it "if given an encoding with an =" do
+ ruby_exe(@test_string, options: '--external-encoding=big5').should == Encoding::Big5.name
+ end
+
+ it "if given an encoding as a separate argument" do
+ ruby_exe(@test_string, options: '--external-encoding big5').should == Encoding::Big5.name
+ end
+end
diff --git a/spec/ruby/command_line/dash_internal_encoding_spec.rb b/spec/ruby/command_line/dash_internal_encoding_spec.rb
new file mode 100644
index 0000000000..3049040bb4
--- /dev/null
+++ b/spec/ruby/command_line/dash_internal_encoding_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../spec_helper'
+
+describe 'The --internal-encoding command line option sets Encoding.default_internal' do
+ before :each do
+ @test_string = "print Encoding.default_internal.name"
+ end
+
+ it "if given an encoding with an =" do
+ ruby_exe(@test_string, options: '--internal-encoding=big5').should == Encoding::Big5.name
+ end
+
+ it "if given an encoding as a separate argument" do
+ ruby_exe(@test_string, options: '--internal-encoding big5').should == Encoding::Big5.name
+ end
+end
diff --git a/spec/ruby/command_line/dash_l_spec.rb b/spec/ruby/command_line/dash_l_spec.rb
new file mode 100644
index 0000000000..5c1d3cf4cd
--- /dev/null
+++ b/spec/ruby/command_line/dash_l_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../spec_helper'
+
+describe "The -l command line option" do
+ before :each do
+ @names = fixture __FILE__, "full_names.txt"
+ end
+
+ it "chomps lines with default separator" do
+ ruby_exe('puts $_.end_with?("\n")', options: "-n -l", escape: true,
+ args: " < #{@names}").should ==
+ "false\nfalse\nfalse\n"
+ end
+
+ it "chomps last line based on $/" do
+ ruby_exe('BEGIN { $/ = "ones\n" }; puts $_', options: "-W0 -n -l", escape: true,
+ args: " < #{@names}").should ==
+ "alice j\nbob field\njames grey\n"
+ end
+
+ it "sets $\\ to the value of $/" do
+ ruby_exe("puts $\\ == $/", options: "-W0 -n -l", escape: true,
+ args: " < #{@names}").should ==
+ "true\ntrue\ntrue\n"
+ end
+
+ it "sets $-l" do
+ ruby_exe("puts $-l", options: "-n -l", escape: true,
+ args: " < #{@names}").should ==
+ "true\ntrue\ntrue\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_n_spec.rb b/spec/ruby/command_line/dash_n_spec.rb
new file mode 100644
index 0000000000..9d331d6065
--- /dev/null
+++ b/spec/ruby/command_line/dash_n_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../spec_helper'
+
+describe "The -n command line option" do
+ before :each do
+ @names = fixture __FILE__, "names.txt"
+ end
+
+ it "runs the code in loop conditional on Kernel.gets()" do
+ ruby_exe("puts $_", options: "-n", escape: true,
+ args: " < #{@names}").should ==
+ "alice\nbob\njames\n"
+ end
+
+ it "only evaluates BEGIN blocks once" do
+ ruby_exe("BEGIN { puts \"hi\" }; puts $_", options: "-n", escape: true,
+ args: " < #{@names}").should ==
+ "hi\nalice\nbob\njames\n"
+ end
+
+ it "only evaluates END blocks once" do
+ ruby_exe("puts $_; END {puts \"bye\"}", options: "-n", escape: true,
+ args: " < #{@names}").should ==
+ "alice\nbob\njames\nbye\n"
+ end
+
+ it "allows summing over a whole file" do
+ script = <<-script
+ BEGIN { $total = 0 }
+ $total += 1
+ END { puts $total }
+ script
+ ruby_exe(script, options: "-n", escape: true,
+ args: " < #{@names}").should ==
+ "3\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_p_spec.rb b/spec/ruby/command_line/dash_p_spec.rb
new file mode 100644
index 0000000000..39827c3868
--- /dev/null
+++ b/spec/ruby/command_line/dash_p_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../spec_helper'
+
+describe "The -p command line option" do
+ before :each do
+ @names = fixture __FILE__, "names.txt"
+ end
+
+ it "runs the code in loop conditional on Kernel.gets() and prints $_" do
+ ruby_exe("$_ = $_.upcase", options: "-p", escape: true,
+ args: " < #{@names}").should ==
+ "ALICE\nBOB\nJAMES\n"
+ end
+
+ it "sets $-p" do
+ ruby_exe("$_ = $-p", options: "-p", escape: true,
+ args: " < #{@names}").should ==
+ "truetruetrue"
+ end
+end
diff --git a/spec/ruby/command_line/dash_r_spec.rb b/spec/ruby/command_line/dash_r_spec.rb
new file mode 100644
index 0000000000..ea5bde5adf
--- /dev/null
+++ b/spec/ruby/command_line/dash_r_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../spec_helper'
+
+describe "The -r command line option" do
+ before :each do
+ @script = fixture __FILE__, "require.rb"
+ @test_file = fixture __FILE__, "test_file"
+ end
+
+ it "requires the specified file" do
+ out = ruby_exe(@script, options: "-r #{@test_file}")
+ out.should include("REQUIRED")
+ out.should include(@test_file + ".rb")
+ end
+
+ it "requires the file before parsing the main script" do
+ out = ruby_exe(fixture(__FILE__, "bad_syntax.rb"), options: "-r #{@test_file}", args: "2>&1", exit_status: 1)
+ $?.should_not.success?
+ out.should include("REQUIRED")
+ out.should include("syntax error")
+ end
+
+ it "does not require the file if the main script file does not exist" do
+ out = `#{ruby_exe.to_a.join(' ')} -r #{@test_file} #{fixture(__FILE__, "does_not_exist.rb")} 2>&1`
+ $?.should_not.success?
+ out.should_not.include?("REQUIRED")
+ out.should.include?("No such file or directory")
+ end
+end
diff --git a/spec/ruby/command_line/dash_s_spec.rb b/spec/ruby/command_line/dash_s_spec.rb
new file mode 100644
index 0000000000..eaaeea7c96
--- /dev/null
+++ b/spec/ruby/command_line/dash_s_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../spec_helper'
+
+describe "The -s command line option" do
+ describe "when using -- to stop parsing" do
+ it "sets the value to true without an explicit value" do
+ ruby_exe(nil, options: "-s -e 'p $n'",
+ args: "-- -n").chomp.should == "true"
+ end
+
+ it "parses single letter args into globals" do
+ ruby_exe(nil, options: "-s -e 'puts $n'",
+ args: "-- -n=blah").chomp.should == "blah"
+ end
+
+ it "parses long args into globals" do
+ ruby_exe(nil, options: "-s -e 'puts $_name'",
+ args: "-- --name=blah").chomp.should == "blah"
+ end
+
+ it "converts extra dashes into underscores" do
+ ruby_exe(nil, options: "-s -e 'puts $___name__test__'",
+ args: "-- ----name--test--=blah").chomp.should == "blah"
+ end
+ end
+
+ describe "when running a script" do
+ before :all do
+ @script = fixture __FILE__, "dash_s_script.rb"
+ end
+
+ it "sets the value to true without an explicit value" do
+ ruby_exe(@script, options: "-s",
+ args: "-n 0").chomp.should == "true"
+ end
+
+ it "parses single letter args into globals" do
+ ruby_exe(@script, options: "-s",
+ args: "-n=blah 1").chomp.should == "blah"
+ end
+
+ it "parses long args into globals" do
+ ruby_exe(@script, options: "-s",
+ args: "--name=blah 2").chomp.should == "blah"
+ end
+
+ it "converts extra dashes into underscores" do
+ ruby_exe(@script, options: "-s",
+ args: "----name--test--=blah 3").chomp.should == "blah"
+ end
+
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_c_spec.rb b/spec/ruby/command_line/dash_upper_c_spec.rb
new file mode 100644
index 0000000000..ece1b32105
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_c_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../spec_helper'
+require_relative 'shared/change_directory'
+
+describe "The -C command line option" do
+ it_behaves_like :command_line_change_directory, "-C"
+end
diff --git a/spec/ruby/command_line/dash_upper_e_spec.rb b/spec/ruby/command_line/dash_upper_e_spec.rb
new file mode 100644
index 0000000000..5a83962583
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_e_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../spec_helper'
+
+describe "ruby -E" do
+ it "sets the external encoding with '-E external'" do
+ result = ruby_exe("print Encoding.default_external", options: '-E euc-jp')
+ result.should == "EUC-JP"
+ end
+
+ platform_is_not :windows do
+ it "also sets the filesystem encoding with '-E external'" do
+ result = ruby_exe("print Encoding.find('filesystem')", options: '-E euc-jp')
+ result.should == "EUC-JP"
+ end
+ end
+
+ it "sets the external encoding with '-E external:'" do
+ result = ruby_exe("print Encoding.default_external", options: '-E Shift_JIS:')
+ result.should == "Shift_JIS"
+ end
+
+ it "sets the internal encoding with '-E :internal'" do
+ ruby_exe("print Encoding.default_internal", options: '-E :SHIFT_JIS').
+ should == 'Shift_JIS'
+ end
+
+ it "sets the external and internal encodings with '-E external:internal'" do
+ ruby_exe("puts Encoding.default_external, Encoding.default_internal", options: '-E euc-jp:SHIFT_JIS').
+ should == "EUC-JP\nShift_JIS\n"
+ end
+
+ it "raises a RuntimeError if used with -U" do
+ ruby_exe("p 1",
+ options: '-Eascii:ascii -U',
+ args: '2>&1',
+ exit_status: 1).should =~ /RuntimeError/
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_f_spec.rb b/spec/ruby/command_line/dash_upper_f_spec.rb
new file mode 100644
index 0000000000..967acc2ece
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_f_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+describe "the -F command line option" do
+ before :each do
+ @passwd = fixture __FILE__, "passwd_file.txt"
+ end
+
+ it "specifies the field separator pattern for -a" do
+ ruby_exe("puts $F[0]", options: "-naF:", escape: true,
+ args: " < #{@passwd}").should ==
+ "nobody\nroot\ndaemon\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_i_spec.rb b/spec/ruby/command_line/dash_upper_i_spec.rb
new file mode 100644
index 0000000000..4cafb724e3
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_i_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../spec_helper'
+
+describe "The -I command line option" do
+ before :each do
+ @script = fixture __FILE__, "loadpath.rb"
+ end
+
+ it "adds the path to the load path ($:)" do
+ ruby_exe(@script, options: "-I fixtures").should include("fixtures")
+ end
+
+ it "adds the path at the front of $LOAD_PATH" do
+ lines = ruby_exe(@script, options: "-I fixtures").lines
+ if PlatformGuard.implementation? :ruby
+ # In a MRI checkout, $PWD ends up as the first entry in $LOAD_PATH.
+ # So just assert that it's at the beginning.
+ idx = lines.index { |l| l.include?("fixtures") }
+ idx.should < 2
+ idx.should < lines.size-1
+ else
+ lines[0].should include("fixtures")
+ end
+ end
+
+ it "adds the path expanded from CWD to $LOAD_PATH" do
+ ruby_exe(@script, options: "-I fixtures").lines.should include "#{Dir.pwd}/fixtures\n"
+ end
+
+ it "expands a path from CWD even if it does not exist" do
+ ruby_exe(@script, options: "-I not_exist/not_exist").lines.should include "#{Dir.pwd}/not_exist/not_exist\n"
+ end
+end
+
+platform_is_not :windows do
+ describe "The -I command line option" do
+ before :each do
+ @script = fixture __FILE__, "loadpath.rb"
+ @fixtures = File.dirname(@script)
+ @symlink = tmp("loadpath_symlink")
+ File.symlink(@fixtures, @symlink)
+ end
+
+ after :each do
+ rm_r @symlink
+ end
+
+ it "does not expand symlinks" do
+ ruby_exe(@script, options: "-I #{@symlink}").lines.should include "#{@symlink}\n"
+ end
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_k_spec.rb b/spec/ruby/command_line/dash_upper_k_spec.rb
new file mode 100644
index 0000000000..7e71532295
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_k_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../spec_helper'
+
+describe 'The -K command line option' do
+ before :each do
+ @test_string = "print [__ENCODING__&.name, Encoding.default_external&.name, Encoding.default_internal&.name].inspect"
+ end
+
+ describe 'sets __ENCODING__ and Encoding.default_external' do
+ it "to Encoding::BINARY with -Ka" do
+ ruby_exe(@test_string, options: '-Ka').should ==
+ [Encoding::BINARY.name, Encoding::BINARY.name, nil].inspect
+ end
+
+ it "to Encoding::BINARY with -KA" do
+ ruby_exe(@test_string, options: '-KA').should ==
+ [Encoding::BINARY.name, Encoding::BINARY.name, nil].inspect
+ end
+
+ it "to Encoding::BINARY with -Kn" do
+ ruby_exe(@test_string, options: '-Kn').should ==
+ [Encoding::BINARY.name, Encoding::BINARY.name, nil].inspect
+ end
+
+ it "to Encoding::BINARY with -KN" do
+ ruby_exe(@test_string, options: '-KN').should ==
+ [Encoding::BINARY.name, Encoding::BINARY.name, nil].inspect
+ end
+
+ it "to Encoding::EUC_JP with -Ke" do
+ ruby_exe(@test_string, options: '-Ke').should ==
+ [Encoding::EUC_JP.name, Encoding::EUC_JP.name, nil].inspect
+ end
+
+ it "to Encoding::EUC_JP with -KE" do
+ ruby_exe(@test_string, options: '-KE').should ==
+ [Encoding::EUC_JP.name, Encoding::EUC_JP.name, nil].inspect
+ end
+
+ it "to Encoding::UTF_8 with -Ku" do
+ ruby_exe(@test_string, options: '-Ku').should ==
+ [Encoding::UTF_8.name, Encoding::UTF_8.name, nil].inspect
+ end
+
+ it "to Encoding::UTF_8 with -KU" do
+ ruby_exe(@test_string, options: '-KU').should ==
+ [Encoding::UTF_8.name, Encoding::UTF_8.name, nil].inspect
+ end
+
+ it "to Encoding::Windows_31J with -Ks" do
+ ruby_exe(@test_string, options: '-Ks').should ==
+ [Encoding::Windows_31J.name, Encoding::Windows_31J.name, nil].inspect
+ end
+
+ it "to Encoding::Windows_31J with -KS" do
+ ruby_exe(@test_string, options: '-KS').should ==
+ [Encoding::Windows_31J.name, Encoding::Windows_31J.name, nil].inspect
+ end
+ end
+
+ it "ignores unknown codes" do
+ external = Encoding.find('external')
+ ruby_exe(@test_string, options: '-KZ').should ==
+ [Encoding::UTF_8.name, external.name, nil].inspect
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_s_spec.rb b/spec/ruby/command_line/dash_upper_s_spec.rb
new file mode 100644
index 0000000000..17991503f1
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_s_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../spec_helper'
+
+describe 'The -S command line option' do
+ before :each do
+ @path = [ENV['PATH'], fixture(__FILE__, "bin")].join(':')
+ end
+
+ platform_is_not :windows do
+ # On VirtualBox shared directory (vboxsf) all files are world writable
+ # and MRI shows warning when including world writable path in ENV['PATH'].
+ # This is why we are using /success$/ matching in the following cases.
+
+ it "runs launcher found in PATH, but only code after the first /\#!.*ruby.*/-ish line in target file" do
+ result = ruby_exe(nil, options: '-S hybrid_launcher.sh', env: { 'PATH' => @path }, args: '2>&1')
+ result.should =~ /success$/
+ end
+
+ it "runs launcher found in PATH" do
+ result = ruby_exe(nil, options: '-S launcher.rb', env: { 'PATH' => @path }, args: '2>&1')
+ result.should =~ /success$/
+ end
+
+ it "runs launcher found in PATH and sets the exit status to 1 if it fails" do
+ result = ruby_exe(nil, options: '-S dash_s_fail', env: { 'PATH' => @path }, args: '2>&1', exit_status: 1)
+ result.should =~ /\bdie\b/
+ $?.exitstatus.should == 1
+ end
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_u_spec.rb b/spec/ruby/command_line/dash_upper_u_spec.rb
new file mode 100644
index 0000000000..d62718b095
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_u_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../spec_helper'
+
+describe "ruby -U" do
+ it "sets Encoding.default_internal to UTF-8" do
+ ruby_exe('print Encoding.default_internal.name',
+ options: '-U').should == 'UTF-8'
+ end
+
+ it "does nothing different if specified multiple times" do
+ ruby_exe('print Encoding.default_internal.name',
+ options: '-U -U').should == 'UTF-8'
+ end
+
+ it "is overruled by Encoding.default_internal=" do
+ ruby_exe('Encoding.default_internal="ascii"; print Encoding.default_internal.name',
+ options: '-U').should == 'US-ASCII'
+ end
+
+ it "does not affect the default external encoding" do
+ ruby_exe('Encoding.default_external="ascii"; print Encoding.default_external.name',
+ options: '-U').should == 'US-ASCII'
+ end
+
+ it "does not affect the source encoding" do
+ ruby_exe("print __ENCODING__.name",
+ options: '-U -KE').should == 'EUC-JP'
+ ruby_exe("print __ENCODING__.name",
+ options: '-KE -U').should == 'EUC-JP'
+ end
+
+ # I assume IO redirection will break on Windows...
+ it "raises a RuntimeError if used with -Eext:int" do
+ ruby_exe("p 1",
+ options: '-U -Eascii:ascii',
+ args: '2>&1',
+ exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError if used with -E:int" do
+ ruby_exe("p 1",
+ options: '-U -E:ascii',
+ args: '2>&1',
+ exit_status: 1).should =~ /RuntimeError/
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_w_spec.rb b/spec/ruby/command_line/dash_upper_w_spec.rb
new file mode 100644
index 0000000000..4019510d42
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_w_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../spec_helper'
+require_relative 'shared/verbose'
+
+describe "The -W command line option" do
+ before :each do
+ @script = fixture __FILE__, "verbose.rb"
+ end
+
+ it "with 0 sets $VERBOSE to nil" do
+ ruby_exe(@script, options: "-W0").chomp.should == "nil"
+ end
+
+ it "with 1 sets $VERBOSE to false" do
+ ruby_exe(@script, options: "-W1").chomp.should == "false"
+ end
+end
+
+describe "The -W command line option with 2" do
+ it_behaves_like :command_line_verbose, "-W2"
+end
+
+describe "The -W command line option with :deprecated" do
+ it "enables deprecation warnings" do
+ ruby_exe('p Warning[:deprecated]', options: '-W:deprecated').should == "true\n"
+ end
+end
+
+describe "The -W command line option with :no-deprecated" do
+ it "suppresses deprecation warnings" do
+ ruby_exe('p Warning[:deprecated]', options: '-w -W:no-deprecated').should == "false\n"
+ end
+end
+
+describe "The -W command line option with :experimental" do
+ it "enables experimental warnings" do
+ ruby_exe('p Warning[:experimental]', options: '-W:experimental').should == "true\n"
+ end
+end
+
+describe "The -W command line option with :no-experimental" do
+ it "suppresses experimental warnings" do
+ ruby_exe('p Warning[:experimental]', options: '-w -W:no-experimental').should == "false\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_x_spec.rb b/spec/ruby/command_line/dash_upper_x_spec.rb
new file mode 100644
index 0000000000..8ef9aae4b1
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_x_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../spec_helper'
+require_relative 'shared/change_directory'
+
+describe "The -X command line option" do
+ it_behaves_like :command_line_change_directory, "-X"
+end
diff --git a/spec/ruby/command_line/dash_v_spec.rb b/spec/ruby/command_line/dash_v_spec.rb
new file mode 100644
index 0000000000..a4f4dcd051
--- /dev/null
+++ b/spec/ruby/command_line/dash_v_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+require_relative 'shared/verbose'
+
+describe "The -v command line option" do
+ it_behaves_like :command_line_verbose, "-v"
+
+ describe "when used alone" do
+ it "prints version and ends" do
+ ruby_exe(nil, args: '-v').should include(RUBY_DESCRIPTION)
+ end unless (defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?) ||
+ (defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?)
+ end
+end
diff --git a/spec/ruby/command_line/dash_w_spec.rb b/spec/ruby/command_line/dash_w_spec.rb
new file mode 100644
index 0000000000..f310dca3ed
--- /dev/null
+++ b/spec/ruby/command_line/dash_w_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../spec_helper'
+require_relative 'shared/verbose'
+
+describe "The -w command line option" do
+ it_behaves_like :command_line_verbose, "-w"
+
+ it "enables both deprecated and experimental warnings" do
+ ruby_exe('p Warning[:deprecated]; p Warning[:experimental]', options: '-w').should == "true\ntrue\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_x_spec.rb b/spec/ruby/command_line/dash_x_spec.rb
new file mode 100644
index 0000000000..ae14b61070
--- /dev/null
+++ b/spec/ruby/command_line/dash_x_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../spec_helper'
+
+describe "The -x command line option" do
+ it "runs code after the first /\#!.*ruby.*/-ish line in target file" do
+ embedded_ruby = fixture __FILE__, "bin/embedded_ruby.txt"
+ result = ruby_exe(embedded_ruby, options: '-x')
+ result.should == "success\n"
+ end
+
+ it "fails when /\#!.*ruby.*/-ish line in target file is not found" do
+ bad_embedded_ruby = fixture __FILE__, "bin/bad_embedded_ruby.txt"
+ result = ruby_exe(bad_embedded_ruby, options: '-x', args: '2>&1', exit_status: 1)
+ result.should include "no Ruby script found in input"
+ end
+
+ it "behaves as -x was set when non-ruby shebang is encountered on first line" do
+ embedded_ruby = fixture __FILE__, "bin/hybrid_launcher.sh"
+ result = ruby_exe(embedded_ruby)
+ result.should == "success\n"
+ end
+end
diff --git a/spec/ruby/command_line/error_message_spec.rb b/spec/ruby/command_line/error_message_spec.rb
new file mode 100644
index 0000000000..02150f30ce
--- /dev/null
+++ b/spec/ruby/command_line/error_message_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../spec_helper'
+
+describe "The error message caused by an exception" do
+ it "is not printed to stdout" do
+ out = ruby_exe("this_does_not_exist", args: "2> #{File::NULL}", exit_status: 1)
+ out.chomp.should.empty?
+
+ out = ruby_exe("end #syntax error", args: "2> #{File::NULL}", exit_status: 1)
+ out.chomp.should.empty?
+ end
+end
diff --git a/spec/ruby/command_line/feature_spec.rb b/spec/ruby/command_line/feature_spec.rb
new file mode 100644
index 0000000000..4a24cc6795
--- /dev/null
+++ b/spec/ruby/command_line/feature_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../spec_helper'
+
+describe "The --enable and --disable flags" do
+ before :all do
+ # Since some specs disable reading RUBYOPT, we instead pass its contents as :options for those specs
+ rubyopt = [ENV["RUBYOPT"]]
+ rubyopt << ENV["#{RUBY_ENGINE.upcase}OPT"] unless RUBY_ENGINE == 'ruby'
+ @rubyopt = RUBY_ENGINE == "ruby" ? "" : rubyopt.compact.join(" ")
+ end
+
+ it "can be used with gems" do
+ ruby_exe("p defined?(Gem)", options: "--enable=gems").chomp.should == "\"constant\""
+ ruby_exe("p defined?(Gem)", options: "--disable=gems").chomp.should == "nil"
+ ruby_exe("p defined?(Gem)", options: "--enable-gems").chomp.should == "\"constant\""
+ ruby_exe("p defined?(Gem)", options: "--disable-gems").chomp.should == "nil"
+ end
+
+ it "can be used with gem" do
+ ruby_exe("p defined?(Gem)", options: "--enable=gem").chomp.should == "\"constant\""
+ ruby_exe("p defined?(Gem)", options: "--disable=gem").chomp.should == "nil"
+ ruby_exe("p defined?(Gem)", options: "--enable-gem").chomp.should == "\"constant\""
+ ruby_exe("p defined?(Gem)", options: "--disable-gem").chomp.should == "nil"
+ end
+
+ it "can be used with did_you_mean" do
+ ruby_exe("p defined?(DidYouMean)", options: "--enable=did_you_mean").chomp.should == "\"constant\""
+ ruby_exe("p defined?(DidYouMean)", options: "--disable=did_you_mean").chomp.should == "nil"
+ ruby_exe("p defined?(DidYouMean)", options: "--enable-did_you_mean").chomp.should == "\"constant\""
+ ruby_exe("p defined?(DidYouMean)", options: "--disable-did_you_mean").chomp.should == "nil"
+ end
+
+ it "can be used with rubyopt" do
+ ruby_exe("p $VERBOSE", options: "--enable=rubyopt", env: {'RUBYOPT' => '-w'}).chomp.should == "true"
+ ruby_exe("p $VERBOSE", options: "#{@rubyopt} --disable=rubyopt", env: {'RUBYOPT' => '-w'}).chomp.should == "false"
+ ruby_exe("p $VERBOSE", options: "--enable-rubyopt", env: {'RUBYOPT' => '-w'}).chomp.should == "true"
+ ruby_exe("p $VERBOSE", options: "#{@rubyopt} --disable-rubyopt", env: {'RUBYOPT' => '-w'}).chomp.should == "false"
+ end
+
+ it "can be used with frozen-string-literal" do
+ ruby_exe("p 'foo'.frozen?", options: "--enable=frozen-string-literal").chomp.should == "true"
+ ruby_exe("p 'foo'.frozen?", options: "--disable=frozen-string-literal").chomp.should == "false"
+ ruby_exe("p 'foo'.frozen?", options: "--enable-frozen-string-literal").chomp.should == "true"
+ ruby_exe("p 'foo'.frozen?", options: "--disable-frozen-string-literal").chomp.should == "false"
+ end
+
+ # frequently hangs for >60s on GitHub Actions macos-latest
+ # MinGW's YJIT support seems broken
+ platform_is_not :darwin, :mingw do
+ it "can be used with all for enable" do
+ e = "p [defined?(Gem), defined?(DidYouMean), $VERBOSE, 'foo'.frozen?]"
+ env = {'RUBYOPT' => '-w'}
+ # Use a single variant here because it can be quite slow as it might enable jit, etc
+ ruby_exe(e, options: "--enable-all", env: env).chomp.should == "[\"constant\", \"constant\", true, true]"
+ end
+ end
+
+ it "can be used with all for disable" do
+ e = "p [defined?(Gem), defined?(DidYouMean), $VERBOSE, 'foo'.frozen?]"
+ env = {'RUBYOPT' => '-w'}
+ ruby_exe(e, options: "#{@rubyopt} --disable=all", env: env).chomp.should == "[nil, nil, false, false]"
+ ruby_exe(e, options: "#{@rubyopt} --disable-all", env: env).chomp.should == "[nil, nil, false, false]"
+ end
+
+ it "prints a warning for unknown features" do
+ ruby_exe("p 14", options: "--enable=ruby-spec-feature-does-not-exist 2>&1").chomp.should include('warning: unknown argument for --enable')
+ ruby_exe("p 14", options: "--disable=ruby-spec-feature-does-not-exist 2>&1").chomp.should include('warning: unknown argument for --disable')
+ ruby_exe("p 14", options: "--enable-ruby-spec-feature-does-not-exist 2>&1").chomp.should include('warning: unknown argument for --enable')
+ ruby_exe("p 14", options: "--disable-ruby-spec-feature-does-not-exist 2>&1").chomp.should include('warning: unknown argument for --disable')
+ end
+
+end
diff --git a/spec/ruby/command_line/fixtures/backtrace.rb b/spec/ruby/command_line/fixtures/backtrace.rb
new file mode 100644
index 0000000000..99acae95c8
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/backtrace.rb
@@ -0,0 +1,35 @@
+def a
+ raise 'oops'
+end
+
+def b
+ a
+end
+
+def c
+ b
+end
+
+def d
+ c
+end
+
+arg = ARGV.first
+$stderr.puts arg
+
+case arg
+when 'full_message'
+ begin
+ d
+ rescue => exc
+ puts exc.full_message
+ end
+when 'backtrace'
+ begin
+ d
+ rescue => exc
+ puts exc.backtrace
+ end
+else
+ d
+end
diff --git a/spec/ruby/command_line/fixtures/bad_syntax.rb b/spec/ruby/command_line/fixtures/bad_syntax.rb
new file mode 100644
index 0000000000..e7b8c7a357
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bad_syntax.rb
@@ -0,0 +1 @@
+f {
diff --git a/spec/ruby/command_line/fixtures/bin/bad_embedded_ruby.txt b/spec/ruby/command_line/fixtures/bin/bad_embedded_ruby.txt
new file mode 100644
index 0000000000..a2b7ad085f
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bin/bad_embedded_ruby.txt
@@ -0,0 +1,3 @@
+@@@This line is not value Ruby
+#!rub_y
+puts 'success'
diff --git a/spec/ruby/command_line/fixtures/bin/dash_s_fail b/spec/ruby/command_line/fixtures/bin/dash_s_fail
new file mode 100644
index 0000000000..70c1b8759c
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bin/dash_s_fail
@@ -0,0 +1 @@
+raise 'die'
diff --git a/spec/ruby/command_line/fixtures/bin/embedded_ruby.txt b/spec/ruby/command_line/fixtures/bin/embedded_ruby.txt
new file mode 100644
index 0000000000..c556bf0b71
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bin/embedded_ruby.txt
@@ -0,0 +1,3 @@
+@@@This line is not value Ruby
+#!ruby
+puts 'success' \ No newline at end of file
diff --git a/spec/ruby/command_line/fixtures/bin/hybrid_launcher.sh b/spec/ruby/command_line/fixtures/bin/hybrid_launcher.sh
new file mode 100644
index 0000000000..fd3249f0e5
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bin/hybrid_launcher.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+echo 'error' && exit 1
+#!ruby
+puts 'success'
diff --git a/spec/ruby/command_line/fixtures/bin/launcher.rb b/spec/ruby/command_line/fixtures/bin/launcher.rb
new file mode 100755
index 0000000000..92a0ee2b49
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bin/launcher.rb
@@ -0,0 +1,2 @@
+#!ruby
+puts 'success'
diff --git a/spec/ruby/command_line/fixtures/change_directory_script.rb b/spec/ruby/command_line/fixtures/change_directory_script.rb
new file mode 100644
index 0000000000..abe244705f
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/change_directory_script.rb
@@ -0,0 +1 @@
+print Dir.pwd
diff --git a/spec/ruby/command_line/fixtures/conditional_range.txt b/spec/ruby/command_line/fixtures/conditional_range.txt
new file mode 100644
index 0000000000..8a1218a102
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/conditional_range.txt
@@ -0,0 +1,5 @@
+1
+2
+3
+4
+5
diff --git a/spec/ruby/command_line/fixtures/dash_s_script.rb b/spec/ruby/command_line/fixtures/dash_s_script.rb
new file mode 100644
index 0000000000..500eccbb84
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/dash_s_script.rb
@@ -0,0 +1,12 @@
+which = ARGV.shift.to_i
+
+case which
+when 0
+ p $n
+when 1
+ puts $n
+when 2
+ puts $_name
+when 3
+ puts $___name__test__
+end
diff --git a/spec/ruby/command_line/fixtures/debug.rb b/spec/ruby/command_line/fixtures/debug.rb
new file mode 100644
index 0000000000..2d84c5faf6
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/debug.rb
@@ -0,0 +1,10 @@
+which = ARGV.first.to_i
+
+case which
+when 0
+ puts "$DEBUG #{$DEBUG}"
+when 1
+ puts "$VERBOSE #{$VERBOSE}"
+when 2
+ puts "$-d #{$-d}"
+end
diff --git a/spec/ruby/command_line/fixtures/debug_info.rb b/spec/ruby/command_line/fixtures/debug_info.rb
new file mode 100644
index 0000000000..ee607910c0
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/debug_info.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+a = 'string'
+b = a
+c = b
+d = c
+e = d
+begin
+ a << 'new part'
+rescue Exception => e
+ print e.message
+end
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_across_files.rb b/spec/ruby/command_line/fixtures/freeze_flag_across_files.rb
new file mode 100644
index 0000000000..b258249f3a
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_across_files.rb
@@ -0,0 +1,3 @@
+require_relative 'freeze_flag_required'
+
+p "abc".object_id == $second_literal_id
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_across_files_diff_enc.rb b/spec/ruby/command_line/fixtures/freeze_flag_across_files_diff_enc.rb
new file mode 100644
index 0000000000..e9f045e9ea
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_across_files_diff_enc.rb
@@ -0,0 +1,3 @@
+require_relative 'freeze_flag_required_diff_enc'
+
+p "abc".object_id != $second_literal_id
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb b/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb
new file mode 100644
index 0000000000..3718899d61
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb
@@ -0,0 +1,2 @@
+ids = Array.new(2) { "abc".object_id }
+p ids.first == ids.last
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_required.rb b/spec/ruby/command_line/fixtures/freeze_flag_required.rb
new file mode 100644
index 0000000000..e09232a5f4
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_required.rb
@@ -0,0 +1 @@
+$second_literal_id = "abc".object_id
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb b/spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb
new file mode 100644
index 0000000000..fa348d59e7
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb
Binary files differ
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_two_literals.rb b/spec/ruby/command_line/fixtures/freeze_flag_two_literals.rb
new file mode 100644
index 0000000000..f5547a5bae
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_two_literals.rb
@@ -0,0 +1 @@
+p "abc".equal?("abc")
diff --git a/spec/ruby/command_line/fixtures/full_names.txt b/spec/ruby/command_line/fixtures/full_names.txt
new file mode 100644
index 0000000000..602a20b9dd
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/full_names.txt
@@ -0,0 +1,3 @@
+alice jones
+bob field
+james grey
diff --git a/spec/ruby/command_line/fixtures/loadpath.rb b/spec/ruby/command_line/fixtures/loadpath.rb
new file mode 100644
index 0000000000..d7fdf45d46
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/loadpath.rb
@@ -0,0 +1 @@
+puts $:
diff --git a/spec/ruby/command_line/fixtures/names.txt b/spec/ruby/command_line/fixtures/names.txt
new file mode 100644
index 0000000000..ae4bf4c8ad
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/names.txt
@@ -0,0 +1,3 @@
+alice
+bob
+james
diff --git a/spec/ruby/command_line/fixtures/passwd_file.txt b/spec/ruby/command_line/fixtures/passwd_file.txt
new file mode 100644
index 0000000000..08a4b23bbd
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/passwd_file.txt
@@ -0,0 +1,3 @@
+nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
+root:*:0:0:System Administrator:/var/root:/bin/sh
+daemon:*:1:1:System Services:/var/root:/usr/bin/false
diff --git a/spec/ruby/command_line/fixtures/require.rb b/spec/ruby/command_line/fixtures/require.rb
new file mode 100644
index 0000000000..0be7049c66
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/require.rb
@@ -0,0 +1 @@
+puts $"
diff --git a/spec/ruby/command_line/fixtures/rubyopt.rb b/spec/ruby/command_line/fixtures/rubyopt.rb
new file mode 100644
index 0000000000..48d81e1bca
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/rubyopt.rb
@@ -0,0 +1 @@
+puts "rubyopt.rb required"
diff --git a/spec/ruby/command_line/fixtures/test_file.rb b/spec/ruby/command_line/fixtures/test_file.rb
new file mode 100644
index 0000000000..30a832299e
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/test_file.rb
@@ -0,0 +1 @@
+puts "REQUIRED"
diff --git a/spec/ruby/command_line/fixtures/verbose.rb b/spec/ruby/command_line/fixtures/verbose.rb
new file mode 100644
index 0000000000..2aa99ed44d
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/verbose.rb
@@ -0,0 +1 @@
+puts $VERBOSE.inspect
diff --git a/spec/ruby/command_line/frozen_strings_spec.rb b/spec/ruby/command_line/frozen_strings_spec.rb
new file mode 100644
index 0000000000..647b69daed
--- /dev/null
+++ b/spec/ruby/command_line/frozen_strings_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../spec_helper'
+
+describe "The --enable-frozen-string-literal flag causes string literals to" do
+
+ it "produce the same object each time" do
+ ruby_exe(fixture(__FILE__, "freeze_flag_one_literal.rb"), options: "--enable-frozen-string-literal").chomp.should == "true"
+ end
+
+ it "produce the same object for literals with the same content" do
+ ruby_exe(fixture(__FILE__, "freeze_flag_two_literals.rb"), options: "--enable-frozen-string-literal").chomp.should == "true"
+ end
+
+ it "produce the same object for literals with the same content in different files" do
+ ruby_exe(fixture(__FILE__, "freeze_flag_across_files.rb"), options: "--enable-frozen-string-literal").chomp.should == "true"
+ end
+
+ it "produce different objects for literals with the same content in different files if they have different encodings" do
+ ruby_exe(fixture(__FILE__, "freeze_flag_across_files_diff_enc.rb"), options: "--enable-frozen-string-literal").chomp.should == "true"
+ end
+end
+
+describe "The --debug flag produces" do
+ it "debugging info on attempted frozen string modification" do
+ error_str = ruby_exe(fixture(__FILE__, 'debug_info.rb'), options: '--debug', args: "2>&1")
+ error_str.should include("can't modify frozen String")
+ error_str.should include("created at")
+ error_str.should include("command_line/fixtures/debug_info.rb:2")
+ end
+end
diff --git a/spec/ruby/command_line/rubylib_spec.rb b/spec/ruby/command_line/rubylib_spec.rb
new file mode 100644
index 0000000000..b45919b997
--- /dev/null
+++ b/spec/ruby/command_line/rubylib_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../spec_helper'
+
+describe "The RUBYLIB environment variable" do
+ before :each do
+ @rubylib, ENV["RUBYLIB"] = ENV["RUBYLIB"], nil
+ @pre = @rubylib.nil? ? '' : @rubylib + File::PATH_SEPARATOR
+ end
+
+ after :each do
+ ENV["RUBYLIB"] = @rubylib
+ end
+
+ it "adds a directory to $LOAD_PATH" do
+ dir = tmp("rubylib/incl")
+ ENV["RUBYLIB"] = @pre + dir
+ paths = ruby_exe("puts $LOAD_PATH").lines.map(&:chomp)
+ paths.should include(dir)
+ end
+
+ it "adds a File::PATH_SEPARATOR-separated list of directories to $LOAD_PATH" do
+ dir1, dir2 = tmp("rubylib/incl1"), tmp("rubylib/incl2")
+ ENV["RUBYLIB"] = @pre + "#{dir1}#{File::PATH_SEPARATOR}#{dir2}"
+ paths = ruby_exe("puts $LOAD_PATH").lines.map(&:chomp)
+ paths.should include(dir1)
+ paths.should include(dir2)
+ paths.index(dir1).should < paths.index(dir2)
+ end
+
+ it "adds the directory at the front of $LOAD_PATH" do
+ dir = tmp("rubylib/incl_front")
+ ENV["RUBYLIB"] = @pre + dir
+ paths = ruby_exe("puts $LOAD_PATH").lines.map(&:chomp)
+ paths.shift if paths.first.end_with?('/gem-rehash')
+ if PlatformGuard.implementation? :ruby
+ # In a MRI checkout, $PWD and some extra -I entries end up as
+ # the first entries in $LOAD_PATH. So just assert that it's not last.
+ idx = paths.index(dir)
+ idx.should < paths.size-1
+ else
+ paths[0].should == dir
+ end
+ end
+
+ it "adds the directory after directories added by -I" do
+ dash_i_dir = tmp("dash_I_include")
+ rubylib_dir = tmp("rubylib_include")
+ ENV["RUBYLIB"] = @pre + rubylib_dir
+ paths = ruby_exe("puts $LOAD_PATH", options: "-I #{dash_i_dir}").lines.map(&:chomp)
+ paths.should include(dash_i_dir)
+ paths.should include(rubylib_dir)
+ paths.index(dash_i_dir).should < paths.index(rubylib_dir)
+ end
+
+ it "adds the directory after directories added by -I within RUBYOPT" do
+ rubyopt_dir = tmp("rubyopt_include")
+ rubylib_dir = tmp("rubylib_include")
+ ENV["RUBYLIB"] = @pre + rubylib_dir
+ paths = ruby_exe("puts $LOAD_PATH", env: { "RUBYOPT" => "-I#{rubyopt_dir}" }).lines.map(&:chomp)
+ paths.should include(rubyopt_dir)
+ paths.should include(rubylib_dir)
+ paths.index(rubyopt_dir).should < paths.index(rubylib_dir)
+ end
+
+ it "keeps spaces in the value" do
+ ENV["RUBYLIB"] = @pre + " rubylib/incl "
+ out = ruby_exe("puts $LOAD_PATH")
+ out.should include(" rubylib/incl ")
+ end
+end
diff --git a/spec/ruby/command_line/rubyopt_spec.rb b/spec/ruby/command_line/rubyopt_spec.rb
new file mode 100644
index 0000000000..bbea4d557d
--- /dev/null
+++ b/spec/ruby/command_line/rubyopt_spec.rb
@@ -0,0 +1,185 @@
+require_relative '../spec_helper'
+
+describe "Processing RUBYOPT" do
+ before :each do
+ @rubyopt, ENV["RUBYOPT"] = ENV["RUBYOPT"], nil
+ end
+
+ after :each do
+ ENV["RUBYOPT"] = @rubyopt
+ end
+
+ it "adds the -I path to $LOAD_PATH" do
+ ENV["RUBYOPT"] = "-Ioptrubyspecincl"
+ result = ruby_exe("puts $LOAD_PATH.grep(/byspecin/)", escape: true)
+ result.chomp[-15..-1].should == "optrubyspecincl"
+ end
+
+ it "sets $DEBUG to true for '-d'" do
+ ENV["RUBYOPT"] = '-d'
+ command = %[puts "value of $DEBUG is \#{$DEBUG}"]
+ result = ruby_exe(command, escape: true, args: "2>&1")
+ result.should =~ /value of \$DEBUG is true/
+ end
+
+ guard -> { not CROSS_COMPILING } do
+ it "prints the version number for '-v'" do
+ ENV["RUBYOPT"] = '-v'
+ ruby_exe("")[/\A.*/].should == RUBY_DESCRIPTION
+ end
+
+ it "ignores whitespace around the option" do
+ ENV["RUBYOPT"] = ' -v '
+ ruby_exe("")[/\A.*/].should == RUBY_DESCRIPTION
+ end
+ end
+
+ it "sets $VERBOSE to true for '-w'" do
+ ENV["RUBYOPT"] = '-w'
+ ruby_exe("p $VERBOSE", escape: true).chomp.should == "true"
+ end
+
+ it "sets $VERBOSE to true for '-W'" do
+ ENV["RUBYOPT"] = '-W'
+ ruby_exe("p $VERBOSE", escape: true).chomp.should == "true"
+ end
+
+ it "sets $VERBOSE to nil for '-W0'" do
+ ENV["RUBYOPT"] = '-W0'
+ ruby_exe("p $VERBOSE", escape: true).chomp.should == "nil"
+ end
+
+ it "sets $VERBOSE to false for '-W1'" do
+ ENV["RUBYOPT"] = '-W1'
+ ruby_exe("p $VERBOSE", escape: true).chomp.should == "false"
+ end
+
+ it "sets $VERBOSE to true for '-W2'" do
+ ENV["RUBYOPT"] = '-W2'
+ ruby_exe("p $VERBOSE", escape: true).chomp.should == "true"
+ end
+
+ it "suppresses deprecation warnings for '-W:no-deprecated'" do
+ ENV["RUBYOPT"] = '-W:no-deprecated'
+ result = ruby_exe('$; = ""', args: '2>&1')
+ result.should == ""
+ end
+
+ it "suppresses experimental warnings for '-W:no-experimental'" do
+ ENV["RUBYOPT"] = '-W:no-experimental'
+ result = ruby_exe('case 0; in a; end', args: '2>&1')
+ result.should == ""
+ end
+
+ it "suppresses deprecation and experimental warnings for '-W:no-deprecated -W:no-experimental'" do
+ ENV["RUBYOPT"] = '-W:no-deprecated -W:no-experimental'
+ result = ruby_exe('case ($; = ""); in a; end', args: '2>&1')
+ result.should == ""
+ end
+
+ it "requires the file for '-r'" do
+ f = fixture __FILE__, "rubyopt"
+ ENV["RUBYOPT"] = "-r#{f}"
+ ruby_exe("0", args: '2>&1').should =~ /^rubyopt.rb required/
+ end
+
+ it "raises a RuntimeError for '-a'" do
+ ENV["RUBYOPT"] = '-a'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-p'" do
+ ENV["RUBYOPT"] = '-p'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-n'" do
+ ENV["RUBYOPT"] = '-n'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-y'" do
+ ENV["RUBYOPT"] = '-y'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-c'" do
+ ENV["RUBYOPT"] = '-c'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-s'" do
+ ENV["RUBYOPT"] = '-s'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-h'" do
+ ENV["RUBYOPT"] = '-h'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '--help'" do
+ ENV["RUBYOPT"] = '--help'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-l'" do
+ ENV["RUBYOPT"] = '-l'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-S'" do
+ ENV["RUBYOPT"] = '-S irb'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-e'" do
+ ENV["RUBYOPT"] = '-e0'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-i'" do
+ ENV["RUBYOPT"] = '-i.bak'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-x'" do
+ ENV["RUBYOPT"] = '-x'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-C'" do
+ ENV["RUBYOPT"] = '-C'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-X'" do
+ ENV["RUBYOPT"] = '-X.'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-F'" do
+ ENV["RUBYOPT"] = '-F'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-0'" do
+ ENV["RUBYOPT"] = '-0'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '--copyright'" do
+ ENV["RUBYOPT"] = '--copyright'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '--version'" do
+ ENV["RUBYOPT"] = '--version'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '--yydebug'" do
+ ENV["RUBYOPT"] = '--yydebug'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+end
diff --git a/spec/ruby/command_line/shared/change_directory.rb b/spec/ruby/command_line/shared/change_directory.rb
new file mode 100644
index 0000000000..9cb6e90ac6
--- /dev/null
+++ b/spec/ruby/command_line/shared/change_directory.rb
@@ -0,0 +1,21 @@
+describe :command_line_change_directory, shared: true do
+ before :all do
+ @script = fixture(__FILE__, 'change_directory_script.rb')
+ @tempdir = File.dirname(@script)
+ end
+
+ it 'changes the PWD when using a file' do
+ output = ruby_exe(@script, options: "#{@method} #{@tempdir}")
+ output.should == @tempdir
+ end
+
+ it 'does not need a space after -C for the argument' do
+ output = ruby_exe(@script, options: "#{@method}#{@tempdir}")
+ output.should == @tempdir
+ end
+
+ it 'changes the PWD when using -e' do
+ output = ruby_exe(nil, options: "#{@method} #{@tempdir} -e 'print Dir.pwd'")
+ output.should == @tempdir
+ end
+end
diff --git a/spec/ruby/command_line/shared/verbose.rb b/spec/ruby/command_line/shared/verbose.rb
new file mode 100644
index 0000000000..c5c44c269e
--- /dev/null
+++ b/spec/ruby/command_line/shared/verbose.rb
@@ -0,0 +1,9 @@
+describe :command_line_verbose, shared: true do
+ before :each do
+ @script = fixture __FILE__, "verbose.rb"
+ end
+
+ it "sets $VERBOSE to true" do
+ ruby_exe(@script, options: @method).chomp.b.split.last.should == "true"
+ end
+end
diff --git a/spec/ruby/command_line/syntax_error_spec.rb b/spec/ruby/command_line/syntax_error_spec.rb
new file mode 100644
index 0000000000..444ea9635c
--- /dev/null
+++ b/spec/ruby/command_line/syntax_error_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+describe "The interpreter" do
+ it "prints an error when given a file with invalid syntax" do
+ out = ruby_exe(fixture(__FILE__, "bad_syntax.rb"), args: "2>&1", exit_status: 1)
+ out.should include "syntax error"
+ end
+
+ it "prints an error when given code via -e with invalid syntax" do
+ out = ruby_exe(nil, args: "-e 'a{' 2>&1", exit_status: 1)
+ out.should include "syntax error"
+ end
+end
diff --git a/spec/ruby/core/argf/argf_spec.rb b/spec/ruby/core/argf/argf_spec.rb
new file mode 100644
index 0000000000..af67170b98
--- /dev/null
+++ b/spec/ruby/core/argf/argf_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "ARGF" do
+ it "is extended by the Enumerable module" do
+ ARGF.should be_kind_of(Enumerable)
+ end
+
+ it "is an instance of ARGF.class" do
+ ARGF.should be_an_instance_of(ARGF.class)
+ end
+end
diff --git a/spec/ruby/core/argf/argv_spec.rb b/spec/ruby/core/argf/argv_spec.rb
new file mode 100644
index 0000000000..eab03c450f
--- /dev/null
+++ b/spec/ruby/core/argf/argv_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.argv" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "returns ARGV for the initial ARGF" do
+ ARGF.argv.should equal ARGV
+ end
+
+ it "returns the remaining arguments to treat" do
+ argf [@file1, @file2] do
+ # @file1 is stored in current file
+ @argf.argv.should == [@file2]
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/binmode_spec.rb b/spec/ruby/core/argf/binmode_spec.rb
new file mode 100644
index 0000000000..e083a30a27
--- /dev/null
+++ b/spec/ruby/core/argf/binmode_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.binmode" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ @bin_file = fixture __FILE__, "bin_file.txt"
+ end
+
+ it "returns self" do
+ argf [@bin_file] do
+ @argf.binmode.should equal @argf
+ end
+ end
+
+ platform_is :windows do
+ it "puts reading into binmode" do
+ argf [@bin_file, @bin_file] do
+ @argf.gets.should == "test\n"
+ @argf.binmode
+ @argf.gets.should == "test\r\n"
+ end
+ end
+
+ it "puts all subsequent streams reading through ARGF into binmode" do
+ argf [@bin_file, @bin_file] do
+ @argf.binmode
+ @argf.gets.should == "test\r\n"
+ @argf.gets.should == "test\r\n"
+ end
+ end
+ end
+
+ it "sets the file's encoding to BINARY" do
+ argf [@bin_file, @file1] do
+ @argf.binmode
+ @argf.should.binmode?
+ @argf.gets.encoding.should == Encoding::BINARY
+ @argf.skip
+ @argf.read.encoding.should == Encoding::BINARY
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/bytes_spec.rb b/spec/ruby/core/argf/bytes_spec.rb
new file mode 100644
index 0000000000..bf35ded1db
--- /dev/null
+++ b/spec/ruby/core/argf/bytes_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'shared/each_byte'
+
+ruby_version_is ''...'3.0' do
+ describe "ARGF.bytes" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :argf_each_byte, :bytes
+ end
+end
diff --git a/spec/ruby/core/argf/chars_spec.rb b/spec/ruby/core/argf/chars_spec.rb
new file mode 100644
index 0000000000..6af73cdabb
--- /dev/null
+++ b/spec/ruby/core/argf/chars_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'shared/each_char'
+
+ruby_version_is ''...'3.0' do
+ describe "ARGF.chars" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :argf_each_char, :chars
+ end
+end
diff --git a/spec/ruby/core/argf/close_spec.rb b/spec/ruby/core/argf/close_spec.rb
new file mode 100644
index 0000000000..d4d6a51e72
--- /dev/null
+++ b/spec/ruby/core/argf/close_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.close" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+ end
+
+ it "closes the current open stream" do
+ argf [@file1_name, @file2_name] do
+ io = @argf.to_io
+ @argf.close
+ io.closed?.should be_true
+ end
+ end
+
+ it "returns self" do
+ argf [@file1_name, @file2_name] do
+ @argf.close.should equal(@argf)
+ end
+ end
+
+ it "doesn't raise an IOError if called on a closed stream" do
+ argf [@file1_name] do
+ -> { @argf.close }.should_not raise_error
+ -> { @argf.close }.should_not raise_error
+ end
+ end
+end
+
+describe "ARGF.close" do
+ it "does not close STDIN" do
+ ruby_exe("ARGV.replace(['-']); ARGF.close; print ARGF.closed?").should == "false"
+ end
+end
diff --git a/spec/ruby/core/argf/closed_spec.rb b/spec/ruby/core/argf/closed_spec.rb
new file mode 100644
index 0000000000..e2dd6134e5
--- /dev/null
+++ b/spec/ruby/core/argf/closed_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.closed?" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+ end
+
+ it "returns true if the current stream has been closed" do
+ argf [@file1_name, @file2_name] do
+ stream = @argf.to_io
+ stream.close
+
+ @argf.closed?.should be_true
+ stream.reopen(@argf.filename, 'r')
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/codepoints_spec.rb b/spec/ruby/core/argf/codepoints_spec.rb
new file mode 100644
index 0000000000..bb28c17fbb
--- /dev/null
+++ b/spec/ruby/core/argf/codepoints_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'shared/each_codepoint'
+
+ruby_version_is ''...'3.0' do
+ describe "ARGF.codepoints" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :argf_each_codepoint, :codepoints
+ end
+end
diff --git a/spec/ruby/core/argf/each_byte_spec.rb b/spec/ruby/core/argf/each_byte_spec.rb
new file mode 100644
index 0000000000..c5cce9f250
--- /dev/null
+++ b/spec/ruby/core/argf/each_byte_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/each_byte'
+
+describe "ARGF.each_byte" do
+ it_behaves_like :argf_each_byte, :each_byte
+end
diff --git a/spec/ruby/core/argf/each_char_spec.rb b/spec/ruby/core/argf/each_char_spec.rb
new file mode 100644
index 0000000000..724e5e6e3e
--- /dev/null
+++ b/spec/ruby/core/argf/each_char_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/each_char'
+
+describe "ARGF.each_char" do
+ it_behaves_like :argf_each_char, :each_char
+end
diff --git a/spec/ruby/core/argf/each_codepoint_spec.rb b/spec/ruby/core/argf/each_codepoint_spec.rb
new file mode 100644
index 0000000000..0bf8bf9764
--- /dev/null
+++ b/spec/ruby/core/argf/each_codepoint_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/each_codepoint'
+
+describe "ARGF.each_codepoint" do
+ it_behaves_like :argf_each_codepoint, :each_codepoint
+end
diff --git a/spec/ruby/core/argf/each_line_spec.rb b/spec/ruby/core/argf/each_line_spec.rb
new file mode 100644
index 0000000000..52a7e5c411
--- /dev/null
+++ b/spec/ruby/core/argf/each_line_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/each_line'
+
+describe "ARGF.each_line" do
+ it_behaves_like :argf_each_line, :each_line
+end
diff --git a/spec/ruby/core/argf/each_spec.rb b/spec/ruby/core/argf/each_spec.rb
new file mode 100644
index 0000000000..5742ba43bd
--- /dev/null
+++ b/spec/ruby/core/argf/each_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/each_line'
+
+describe "ARGF.each" do
+ it_behaves_like :argf_each_line, :each
+end
diff --git a/spec/ruby/core/argf/eof_spec.rb b/spec/ruby/core/argf/eof_spec.rb
new file mode 100644
index 0000000000..518f6e566e
--- /dev/null
+++ b/spec/ruby/core/argf/eof_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eof'
+
+describe "ARGF.eof" do
+ it_behaves_like :argf_eof, :eof
+end
+
+describe "ARGF.eof?" do
+ it_behaves_like :argf_eof, :eof?
+end
diff --git a/spec/ruby/core/argf/file_spec.rb b/spec/ruby/core/argf/file_spec.rb
new file mode 100644
index 0000000000..df8552d457
--- /dev/null
+++ b/spec/ruby/core/argf/file_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.file" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "returns the current file object on each file" do
+ argf [@file1, @file2] do
+ result = []
+ # returns first current file even when not yet open
+ result << @argf.file.path
+ result << @argf.file.path while @argf.gets
+ # returns last current file even when closed
+ result << @argf.file.path
+ result.should == [@file1, @file1, @file1, @file2, @file2, @file2]
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/filename_spec.rb b/spec/ruby/core/argf/filename_spec.rb
new file mode 100644
index 0000000000..7c0446269d
--- /dev/null
+++ b/spec/ruby/core/argf/filename_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/filename'
+
+describe "ARGF.filename" do
+ it_behaves_like :argf_filename, :filename
+end
diff --git a/spec/ruby/core/argf/fileno_spec.rb b/spec/ruby/core/argf/fileno_spec.rb
new file mode 100644
index 0000000000..29d50c3582
--- /dev/null
+++ b/spec/ruby/core/argf/fileno_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/fileno'
+
+describe "ARGF.fileno" do
+ it_behaves_like :argf_fileno, :fileno
+end
diff --git a/spec/ruby/core/argf/fixtures/bin_file.txt b/spec/ruby/core/argf/fixtures/bin_file.txt
new file mode 100644
index 0000000000..2545e9037e
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/bin_file.txt
@@ -0,0 +1,2 @@
+test
+test
diff --git a/spec/ruby/core/argf/fixtures/file1.txt b/spec/ruby/core/argf/fixtures/file1.txt
new file mode 100644
index 0000000000..1c89bfbd82
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/file1.txt
@@ -0,0 +1,2 @@
+file1.1
+file1.2
diff --git a/spec/ruby/core/argf/fixtures/file2.txt b/spec/ruby/core/argf/fixtures/file2.txt
new file mode 100644
index 0000000000..62e8dba00b
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/file2.txt
@@ -0,0 +1,2 @@
+line2.1
+line2.2
diff --git a/spec/ruby/core/argf/fixtures/filename.rb b/spec/ruby/core/argf/fixtures/filename.rb
new file mode 100644
index 0000000000..599c97dd57
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/filename.rb
@@ -0,0 +1,3 @@
+puts $FILENAME while ARGF.gets
+# returns last current file even when closed
+puts $FILENAME
diff --git a/spec/ruby/core/argf/fixtures/lineno.rb b/spec/ruby/core/argf/fixtures/lineno.rb
new file mode 100644
index 0000000000..079cc92e8e
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/lineno.rb
@@ -0,0 +1,5 @@
+puts $.
+ARGF.gets
+puts $.
+ARGF.gets
+puts $.
diff --git a/spec/ruby/core/argf/fixtures/rewind.rb b/spec/ruby/core/argf/fixtures/rewind.rb
new file mode 100644
index 0000000000..90a334cd89
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/rewind.rb
@@ -0,0 +1,5 @@
+puts ARGF.lineno
+ARGF.gets
+puts ARGF.lineno
+ARGF.rewind
+puts ARGF.lineno
diff --git a/spec/ruby/core/argf/fixtures/stdin.txt b/spec/ruby/core/argf/fixtures/stdin.txt
new file mode 100644
index 0000000000..063cdcb1d4
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/stdin.txt
@@ -0,0 +1,2 @@
+stdin.1
+stdin.2
diff --git a/spec/ruby/core/argf/getc_spec.rb b/spec/ruby/core/argf/getc_spec.rb
new file mode 100644
index 0000000000..dc5de9b7df
--- /dev/null
+++ b/spec/ruby/core/argf/getc_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'shared/getc'
+
+describe "ARGF.getc" do
+ it_behaves_like :argf_getc, :getc
+end
+
+describe "ARGF.getc" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "returns nil when end of stream reached" do
+ argf [@file1, @file2] do
+ @argf.read
+ @argf.getc.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/gets_spec.rb b/spec/ruby/core/argf/gets_spec.rb
new file mode 100644
index 0000000000..cc7673b190
--- /dev/null
+++ b/spec/ruby/core/argf/gets_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+require_relative 'shared/gets'
+
+describe "ARGF.gets" do
+ it_behaves_like :argf_gets, :gets
+end
+
+describe "ARGF.gets" do
+ it_behaves_like :argf_gets_inplace_edit, :gets
+end
+
+describe "ARGF.gets" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @file1 = File.readlines @file1_name
+ @file2 = File.readlines @file2_name
+ end
+
+ it "returns nil when reaching end of files" do
+ argf [@file1_name, @file2_name] do
+ total = @file1.size + @file2.size
+ total.times { @argf.gets }
+ @argf.gets.should == nil
+ end
+ end
+
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = nil
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "reads the contents of the file with default encoding" do
+ Encoding.default_external = Encoding::US_ASCII
+ argf [@file1_name, @file2_name] do
+ @argf.gets.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+end
diff --git a/spec/ruby/core/argf/lineno_spec.rb b/spec/ruby/core/argf/lineno_spec.rb
new file mode 100644
index 0000000000..72a108c187
--- /dev/null
+++ b/spec/ruby/core/argf/lineno_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.lineno" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ # TODO: break this into four specs
+ it "returns the current line number on each file" do
+ argf [@file1, @file2] do
+ @argf.lineno = 0
+ @argf.gets
+ @argf.lineno.should == 1
+ @argf.gets
+ @argf.lineno.should == 2
+ @argf.gets
+ @argf.lineno.should == 3
+ @argf.gets
+ @argf.lineno.should == 4
+ end
+ end
+
+ it "aliases to $." do
+ script = fixture __FILE__, "lineno.rb"
+ out = ruby_exe(script, args: [@file1, @file2])
+ out.should == "0\n1\n2\n"
+ end
+end
diff --git a/spec/ruby/core/argf/lines_spec.rb b/spec/ruby/core/argf/lines_spec.rb
new file mode 100644
index 0000000000..e964dbd0d3
--- /dev/null
+++ b/spec/ruby/core/argf/lines_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'shared/each_line'
+
+ruby_version_is ''...'3.0' do
+ describe "ARGF.lines" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :argf_each_line, :lines
+ end
+end
diff --git a/spec/ruby/core/argf/path_spec.rb b/spec/ruby/core/argf/path_spec.rb
new file mode 100644
index 0000000000..7120f7d0e3
--- /dev/null
+++ b/spec/ruby/core/argf/path_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/filename'
+
+describe "ARGF.path" do
+ it_behaves_like :argf_filename, :path
+end
diff --git a/spec/ruby/core/argf/pos_spec.rb b/spec/ruby/core/argf/pos_spec.rb
new file mode 100644
index 0000000000..fb3f25b945
--- /dev/null
+++ b/spec/ruby/core/argf/pos_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'shared/pos'
+
+describe "ARGF.pos" do
+ it_behaves_like :argf_pos, :pos
+end
+
+describe "ARGF.pos=" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @file1 = File.readlines @file1_name
+ @file2 = File.readlines @file2_name
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "sets the correct position in files" do
+ argf [@file1_name, @file2_name] do
+ @argf.pos = @file1.first.size
+ @argf.gets.should == @file1.last
+ @argf.pos = 0
+ @argf.gets.should == @file1.first
+
+ # finish reading file1
+ @argf.gets
+
+ @argf.gets
+ @argf.pos = 1
+ @argf.gets.should == @file2.first[1..-1]
+
+ @argf.pos = @file2.first.size + @file2.last.size - 1
+ @argf.gets.should == @file2.last[-1,1]
+ @argf.pos = 1000
+ @argf.read.should == ""
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/read_nonblock_spec.rb b/spec/ruby/core/argf/read_nonblock_spec.rb
new file mode 100644
index 0000000000..804a459a62
--- /dev/null
+++ b/spec/ruby/core/argf/read_nonblock_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../../spec_helper'
+require_relative 'shared/read'
+
+platform_is_not :windows do
+ describe 'ARGF.read_nonblock' do
+ it_behaves_like :argf_read, :read_nonblock
+
+ before do
+ @file1_name = fixture(__FILE__, 'file1.txt')
+ @file2_name = fixture(__FILE__, 'file2.txt')
+
+ @file1 = File.read(@file1_name)
+ @file2 = File.read(@file2_name)
+
+ @chunk1 = File.read(@file1_name, 4)
+ @chunk2 = File.read(@file2_name, 4)
+ end
+
+ it 'reads up to the given amount of bytes' do
+ argf [@file1_name] do
+ @argf.read_nonblock(4).should == @chunk1
+ end
+ end
+
+ describe 'when using multiple files' do
+ it 'reads up to the given amount of bytes from the first file' do
+ argf [@file1_name, @file2_name] do
+ @argf.read_nonblock(4).should == @chunk1
+ end
+ end
+
+ it 'returns an empty String when reading after having read the first file in its entirety' do
+ argf [@file1_name, @file2_name] do
+ @argf.read_nonblock(File.size(@file1_name)).should == @file1
+ @argf.read_nonblock(4).should == ''
+ end
+ end
+ end
+
+ it 'reads up to the given bytes from STDIN' do
+ stdin = ruby_exe('print ARGF.read_nonblock(4)', :args => "< #{@file1_name}")
+
+ stdin.should == @chunk1
+ end
+
+ it 'reads up to the given bytes from a file when a file and STDIN are present' do
+ stdin = ruby_exe("print ARGF.read_nonblock(4)", :args => "#{@file1_name} - < #{@file2_name}")
+
+ stdin.should == @chunk1
+ end
+
+ context "with STDIN" do
+ before do
+ @r, @w = IO.pipe
+ @stdin = $stdin
+ $stdin = @r
+ end
+
+ after do
+ $stdin = @stdin
+ @w.close
+ @r.close unless @r.closed?
+ end
+
+ it 'raises IO::EAGAINWaitReadable when empty' do
+ argf ['-'] do
+ -> {
+ @argf.read_nonblock(4)
+ }.should raise_error(IO::EAGAINWaitReadable)
+ end
+ end
+
+ it 'returns :wait_readable when the :exception is set to false' do
+ argf ['-'] do
+ @argf.read_nonblock(4, nil, exception: false).should == :wait_readable
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/read_spec.rb b/spec/ruby/core/argf/read_spec.rb
new file mode 100644
index 0000000000..bbeef95456
--- /dev/null
+++ b/spec/ruby/core/argf/read_spec.rb
@@ -0,0 +1,85 @@
+require_relative '../../spec_helper'
+require_relative 'shared/read'
+
+describe "ARGF.read" do
+ it_behaves_like :argf_read, :read
+
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+ @stdin_name = fixture __FILE__, "stdin.txt"
+
+ @file1 = File.read @file1_name
+ @file2 = File.read @file2_name
+ @stdin = File.read @stdin_name
+ end
+
+ it "reads the contents of a file" do
+ argf [@file1_name] do
+ @argf.read().should == @file1
+ end
+ end
+
+ it "treats first nil argument as no length limit" do
+ argf [@file1_name] do
+ @argf.read(nil).should == @file1
+ end
+ end
+
+ it "reads the contents of two files" do
+ argf [@file1_name, @file2_name] do
+ @argf.read.should == @file1 + @file2
+ end
+ end
+
+ it "reads the contents of one file and some characters from the second" do
+ argf [@file1_name, @file2_name] do
+ len = @file1.size + (@file2.size / 2)
+ @argf.read(len).should == (@file1 + @file2)[0,len]
+ end
+ end
+
+ it "reads across two files consecutively" do
+ argf [@file1_name, @file2_name] do
+ @argf.read(@file1.size - 2).should == @file1[0..-3]
+ @argf.read(2+5).should == @file1[-2..-1] + @file2[0,5]
+ end
+ end
+
+ it "reads the contents of stdin" do
+ stdin = ruby_exe("print ARGF.read", args: "< #{@stdin_name}")
+ stdin.should == @stdin
+ end
+
+ it "reads the contents of one file and stdin" do
+ stdin = ruby_exe("print ARGF.read", args: "#{@file1_name} - < #{@stdin_name}")
+ stdin.should == @file1 + @stdin
+ end
+
+ it "reads the contents of the same file twice" do
+ argf [@file1_name, @file1_name] do
+ @argf.read.should == @file1 + @file1
+ end
+ end
+
+
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = nil
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "reads the contents of the file with default encoding" do
+ Encoding.default_external = Encoding::US_ASCII
+ argf [@file1_name, @file2_name] do
+ @argf.read.encoding.should == Encoding::US_ASCII
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/readchar_spec.rb b/spec/ruby/core/argf/readchar_spec.rb
new file mode 100644
index 0000000000..4eca2efcf7
--- /dev/null
+++ b/spec/ruby/core/argf/readchar_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'shared/getc'
+
+describe "ARGF.getc" do
+ it_behaves_like :argf_getc, :readchar
+end
+
+describe "ARGF.readchar" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "raises EOFError when end of stream reached" do
+ argf [@file1, @file2] do
+ -> { while @argf.readchar; end }.should raise_error(EOFError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/readline_spec.rb b/spec/ruby/core/argf/readline_spec.rb
new file mode 100644
index 0000000000..db53c499e9
--- /dev/null
+++ b/spec/ruby/core/argf/readline_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'shared/gets'
+
+describe "ARGF.readline" do
+ it_behaves_like :argf_gets, :readline
+end
+
+describe "ARGF.readline" do
+ it_behaves_like :argf_gets_inplace_edit, :readline
+end
+
+describe "ARGF.readline" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "raises an EOFError when reaching end of files" do
+ argf [@file1, @file2] do
+ -> { while @argf.readline; end }.should raise_error(EOFError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/readlines_spec.rb b/spec/ruby/core/argf/readlines_spec.rb
new file mode 100644
index 0000000000..30be936dab
--- /dev/null
+++ b/spec/ruby/core/argf/readlines_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/readlines'
+
+describe "ARGF.readlines" do
+ it_behaves_like :argf_readlines, :readlines
+end
diff --git a/spec/ruby/core/argf/readpartial_spec.rb b/spec/ruby/core/argf/readpartial_spec.rb
new file mode 100644
index 0000000000..5e284b3423
--- /dev/null
+++ b/spec/ruby/core/argf/readpartial_spec.rb
@@ -0,0 +1,75 @@
+require_relative '../../spec_helper'
+require_relative 'shared/read'
+
+describe "ARGF.readpartial" do
+ it_behaves_like :argf_read, :readpartial
+
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+ @stdin_name = fixture __FILE__, "stdin.txt"
+
+ @file1 = File.read @file1_name
+ @file2 = File.read @file2_name
+ @stdin = File.read @stdin_name
+ end
+
+ it "raises an ArgumentError if called without a maximum read length" do
+ argf [@file1_name] do
+ -> { @argf.readpartial }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "reads maximum number of bytes from one file at a time" do
+ argf [@file1_name, @file2_name] do
+ len = @file1.size + @file2.size
+ @argf.readpartial(len).should == @file1
+ end
+ end
+
+ it "clears output buffer even if EOFError is raised because @argf is at end" do
+ begin
+ output = "to be cleared"
+
+ argf [@file1_name] do
+ @argf.read
+ @argf.readpartial(1, output)
+ end
+ rescue EOFError
+ output.should == ""
+ end
+ end
+
+ it "reads maximum number of bytes from one file at a time" do
+ argf [@file1_name, @file2_name] do
+ len = @file1.size + @file2.size
+ @argf.readpartial(len).should == @file1
+ end
+ end
+
+ it "returns an empty string if EOFError is raised while reading any but the last file" do
+ argf [@file1_name, @file2_name] do
+ @argf.readpartial(@file1.size)
+ @argf.readpartial(1).should == ""
+ end
+ end
+
+ it "raises an EOFError if the exception was raised while reading the last file" do
+ argf [@file1_name, @file2_name] do
+ @argf.readpartial(@file1.size)
+ @argf.readpartial(1)
+ @argf.readpartial(@file2.size)
+ -> { @argf.readpartial(1) }.should raise_error(EOFError)
+ -> { @argf.readpartial(1) }.should raise_error(EOFError)
+ end
+ end
+
+ it "raises an EOFError if the exception was raised while reading STDIN" do
+ ruby_str = <<-STR
+ print ARGF.readpartial(#{@stdin.size})
+ ARGF.readpartial(1) rescue print $!.class
+ STR
+ stdin = ruby_exe(ruby_str, args: "< #{@stdin_name}", escape: true)
+ stdin.should == @stdin + "EOFError"
+ end
+end
diff --git a/spec/ruby/core/argf/rewind_spec.rb b/spec/ruby/core/argf/rewind_spec.rb
new file mode 100644
index 0000000000..b29f0b75b7
--- /dev/null
+++ b/spec/ruby/core/argf/rewind_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.rewind" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @file1 = File.readlines @file1_name
+ @file2 = File.readlines @file2_name
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "goes back to beginning of current file" do
+ argf [@file1_name, @file2_name] do
+ @argf.gets
+ @argf.rewind
+ @argf.gets.should == @file1.first
+
+ @argf.gets # finish reading file1
+
+ @argf.gets
+ @argf.rewind
+ @argf.gets.should == @file2.first
+ end
+ end
+
+ it "resets ARGF.lineno to 0" do
+ script = fixture __FILE__, "rewind.rb"
+ out = ruby_exe(script, args: [@file1_name, @file2_name])
+ out.should == "0\n1\n0\n"
+ end
+
+ it "raises an ArgumentError when end of stream reached" do
+ argf [@file1_name, @file2_name] do
+ @argf.read
+ -> { @argf.rewind }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/seek_spec.rb b/spec/ruby/core/argf/seek_spec.rb
new file mode 100644
index 0000000000..2b73bd46fb
--- /dev/null
+++ b/spec/ruby/core/argf/seek_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.seek" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @file1 = File.readlines @file1_name
+ @file2 = File.readlines @file2_name
+ end
+
+ it "sets the absolute position relative to beginning of file" do
+ argf [@file1_name, @file2_name] do
+ @argf.seek 2
+ @argf.gets.should == @file1.first[2..-1]
+ @argf.seek @file1.first.size
+ @argf.gets.should == @file1.last
+ @argf.seek 0, IO::SEEK_END
+ @argf.gets.should == @file2.first
+ end
+ end
+
+ it "sets the position relative to current position in file" do
+ argf [@file1_name, @file2_name] do
+ @argf.seek(0, IO::SEEK_CUR)
+ @argf.gets.should == @file1.first
+ @argf.seek(-@file1.first.size+2, IO::SEEK_CUR)
+ @argf.gets.should == @file1.first[2..-1]
+ @argf.seek(1, IO::SEEK_CUR)
+ @argf.gets.should == @file1.last[1..-1]
+ @argf.seek(3, IO::SEEK_CUR)
+ @argf.gets.should == @file2.first
+ @argf.seek(@file1.last.size, IO::SEEK_CUR)
+ @argf.gets.should == nil
+ end
+ end
+
+ it "sets the absolute position relative to end of file" do
+ argf [@file1_name, @file2_name] do
+ @argf.seek(-@file1.first.size-@file1.last.size, IO::SEEK_END)
+ @argf.gets.should == @file1.first
+ @argf.seek(-6, IO::SEEK_END)
+ @argf.gets.should == @file1.last[-6..-1]
+ @argf.seek(-4, IO::SEEK_END)
+ @argf.gets.should == @file1.last[4..-1]
+ @argf.gets.should == @file2.first
+ @argf.seek(-6, IO::SEEK_END)
+ @argf.gets.should == @file2.last[-6..-1]
+ end
+ end
+end
+
+describe "ARGF.seek" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ end
+
+ it "takes at least one argument (offset)" do
+ argf [@file1_name] do
+ -> { @argf.seek }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/set_encoding_spec.rb b/spec/ruby/core/argf/set_encoding_spec.rb
new file mode 100644
index 0000000000..a871e084b6
--- /dev/null
+++ b/spec/ruby/core/argf/set_encoding_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.set_encoding" do
+ before :each do
+ @file = fixture __FILE__, "file1.txt"
+ end
+
+ it "sets the external encoding when passed an encoding instance" do
+ argf [@file] do
+ @argf.set_encoding(Encoding::US_ASCII)
+ @argf.external_encoding.should == Encoding::US_ASCII
+ @argf.gets.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ it "sets the external encoding when passed an encoding name" do
+ argf [@file] do
+ @argf.set_encoding("us-ascii")
+ @argf.external_encoding.should == Encoding::US_ASCII
+ @argf.gets.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ it "sets the external, internal encoding when passed two encoding instances" do
+ argf [@file] do
+ @argf.set_encoding(Encoding::US_ASCII, Encoding::EUC_JP)
+ @argf.external_encoding.should == Encoding::US_ASCII
+ @argf.internal_encoding.should == Encoding::EUC_JP
+ @argf.gets.encoding.should == Encoding::EUC_JP
+ end
+ end
+
+ it "sets the external, internal encoding when passed 'ext:int' String" do
+ argf [@file] do
+ @argf.set_encoding("us-ascii:euc-jp")
+ @argf.external_encoding.should == Encoding::US_ASCII
+ @argf.internal_encoding.should == Encoding::EUC_JP
+ @argf.gets.encoding.should == Encoding::EUC_JP
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/each_byte.rb b/spec/ruby/core/argf/shared/each_byte.rb
new file mode 100644
index 0000000000..6b1dc1dae2
--- /dev/null
+++ b/spec/ruby/core/argf/shared/each_byte.rb
@@ -0,0 +1,58 @@
+describe :argf_each_byte, shared: true do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @bytes = []
+ File.read(@file1_name).each_byte { |b| @bytes << b }
+ File.read(@file2_name).each_byte { |b| @bytes << b }
+ end
+
+ it "yields each byte of all streams to the passed block" do
+ argf [@file1_name, @file2_name] do
+ bytes = []
+ @argf.send(@method) { |b| bytes << b }
+ bytes.should == @bytes
+ end
+ end
+
+ it "returns self when passed a block" do
+ argf [@file1_name, @file2_name] do
+ @argf.send(@method) {}.should equal(@argf)
+ end
+ end
+
+ it "returns an Enumerator when passed no block" do
+ argf [@file1_name, @file2_name] do
+ enum = @argf.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+
+ bytes = []
+ enum.each { |b| bytes << b }
+ bytes.should == @bytes
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ argf [@file1_name, @file2_name] do
+ enum = @argf.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+
+ bytes = []
+ enum.each { |b| bytes << b }
+ bytes.should == @bytes
+ end
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ argf [@file1_name, @file2_name] do
+ @argf.send(@method).size.should == nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/each_char.rb b/spec/ruby/core/argf/shared/each_char.rb
new file mode 100644
index 0000000000..9e333ecc5b
--- /dev/null
+++ b/spec/ruby/core/argf/shared/each_char.rb
@@ -0,0 +1,58 @@
+describe :argf_each_char, shared: true do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @chars = []
+ File.read(@file1_name).each_char { |c| @chars << c }
+ File.read(@file2_name).each_char { |c| @chars << c }
+ end
+
+ it "yields each char of all streams to the passed block" do
+ argf [@file1_name, @file2_name] do
+ chars = []
+ @argf.send(@method) { |c| chars << c }
+ chars.should == @chars
+ end
+ end
+
+ it "returns self when passed a block" do
+ argf [@file1_name, @file2_name] do
+ @argf.send(@method) {}.should equal(@argf)
+ end
+ end
+
+ it "returns an Enumerator when passed no block" do
+ argf [@file1_name, @file2_name] do
+ enum = @argf.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+
+ chars = []
+ enum.each { |c| chars << c }
+ chars.should == @chars
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ argf [@file1_name, @file2_name] do
+ enum = @argf.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+
+ chars = []
+ enum.each { |c| chars << c }
+ chars.should == @chars
+ end
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ argf [@file1_name, @file2_name] do
+ @argf.send(@method).size.should == nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/each_codepoint.rb b/spec/ruby/core/argf/shared/each_codepoint.rb
new file mode 100644
index 0000000000..e2a2dfff46
--- /dev/null
+++ b/spec/ruby/core/argf/shared/each_codepoint.rb
@@ -0,0 +1,58 @@
+describe :argf_each_codepoint, shared: true do
+ before :each do
+ file1_name = fixture __FILE__, "file1.txt"
+ file2_name = fixture __FILE__, "file2.txt"
+ @filenames = [file1_name, file2_name]
+
+ @codepoints = File.read(file1_name).codepoints
+ @codepoints.concat File.read(file2_name).codepoints
+ end
+
+ it "is a public method" do
+ argf @filenames do
+ @argf.public_methods(false).should include(@method)
+ end
+ end
+
+ it "does not require arguments" do
+ argf @filenames do
+ @argf.method(@method).arity.should == 0
+ end
+ end
+
+ it "returns self when passed a block" do
+ argf @filenames do
+ @argf.send(@method) {}.should equal(@argf)
+ end
+ end
+
+ it "returns an Enumerator when passed no block" do
+ argf @filenames do
+ @argf.send(@method).should be_an_instance_of(Enumerator)
+ end
+ end
+
+ it "yields each codepoint of all streams" do
+ argf @filenames do
+ @argf.send(@method).to_a.should == @codepoints
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ argf @filenames do
+ @argf.send(@method).should be_an_instance_of(Enumerator)
+ end
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ argf @filenames do
+ @argf.send(@method).size.should == nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/each_line.rb b/spec/ruby/core/argf/shared/each_line.rb
new file mode 100644
index 0000000000..c0ef77dc54
--- /dev/null
+++ b/spec/ruby/core/argf/shared/each_line.rb
@@ -0,0 +1,62 @@
+describe :argf_each_line, shared: true do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @lines = File.readlines @file1_name
+ @lines += File.readlines @file2_name
+ end
+
+ it "is a public method" do
+ argf [@file1_name, @file2_name] do
+ @argf.public_methods(false).should include(@method)
+ end
+ end
+
+ it "requires multiple arguments" do
+ argf [@file1_name, @file2_name] do
+ @argf.method(@method).arity.should < 0
+ end
+ end
+
+ it "reads each line of files" do
+ argf [@file1_name, @file2_name] do
+ lines = []
+ @argf.send(@method) { |b| lines << b }
+ lines.should == @lines
+ end
+ end
+
+ it "returns self when passed a block" do
+ argf [@file1_name, @file2_name] do
+ @argf.send(@method) {}.should equal(@argf)
+ end
+ end
+
+ describe "with a separator" do
+ it "yields each separated section of all streams" do
+ argf [@file1_name, @file2_name] do
+ @argf.send(@method, '.').to_a.should ==
+ (File.readlines(@file1_name, '.') + File.readlines(@file2_name, '.'))
+ end
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ argf [@file1_name, @file2_name] do
+ @argf.send(@method).should be_an_instance_of(Enumerator)
+ end
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ argf [@file1_name, @file2_name] do
+ @argf.send(@method).size.should == nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/eof.rb b/spec/ruby/core/argf/shared/eof.rb
new file mode 100644
index 0000000000..0e684f943f
--- /dev/null
+++ b/spec/ruby/core/argf/shared/eof.rb
@@ -0,0 +1,24 @@
+describe :argf_eof, shared: true do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "returns true when reaching the end of a file" do
+ argf [@file1, @file2] do
+ result = []
+ while @argf.gets
+ result << @argf.send(@method)
+ end
+ result.should == [false, true, false, true]
+ end
+ end
+
+ it "raises IOError when called on a closed stream" do
+ argf [@file1] do
+ @argf.read
+ -> { @argf.send(@method) }.should raise_error(IOError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/filename.rb b/spec/ruby/core/argf/shared/filename.rb
new file mode 100644
index 0000000000..f47c673dc0
--- /dev/null
+++ b/spec/ruby/core/argf/shared/filename.rb
@@ -0,0 +1,28 @@
+describe :argf_filename, shared: true do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "returns the current file name on each file" do
+ argf [@file1, @file2] do
+ result = []
+ # returns first current file even when not yet open
+ result << @argf.send(@method)
+ result << @argf.send(@method) while @argf.gets
+ # returns last current file even when closed
+ result << @argf.send(@method)
+
+ result.map! { |f| File.expand_path(f) }
+ result.should == [@file1, @file1, @file1, @file2, @file2, @file2]
+ end
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "sets the $FILENAME global variable with the current file name on each file" do
+ script = fixture __FILE__, "filename.rb"
+ out = ruby_exe(script, args: [@file1, @file2])
+ out.should == "#{@file1}\n#{@file1}\n#{@file2}\n#{@file2}\n#{@file2}\n"
+ end
+end
diff --git a/spec/ruby/core/argf/shared/fileno.rb b/spec/ruby/core/argf/shared/fileno.rb
new file mode 100644
index 0000000000..4350d43747
--- /dev/null
+++ b/spec/ruby/core/argf/shared/fileno.rb
@@ -0,0 +1,24 @@
+describe :argf_fileno, shared: true do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "returns the current file number on each file" do
+ argf [@file1, @file2] do
+ result = []
+ # returns first current file even when not yet open
+ result << @argf.send(@method) while @argf.gets
+ # returns last current file even when closed
+ result.map { |d| d.class }.should == [Integer, Integer, Integer, Integer]
+ end
+ end
+
+ it "raises an ArgumentError when called on a closed stream" do
+ argf [@file1] do
+ @argf.read
+ -> { @argf.send(@method) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/getc.rb b/spec/ruby/core/argf/shared/getc.rb
new file mode 100644
index 0000000000..8be39c60b6
--- /dev/null
+++ b/spec/ruby/core/argf/shared/getc.rb
@@ -0,0 +1,17 @@
+describe :argf_getc, shared: true do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+
+ @chars = File.read @file1
+ @chars += File.read @file2
+ end
+
+ it "reads each char of files" do
+ argf [@file1, @file2] do
+ chars = ""
+ @chars.size.times { chars << @argf.send(@method) }
+ chars.should == @chars
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/gets.rb b/spec/ruby/core/argf/shared/gets.rb
new file mode 100644
index 0000000000..160d24c27b
--- /dev/null
+++ b/spec/ruby/core/argf/shared/gets.rb
@@ -0,0 +1,99 @@
+describe :argf_gets, shared: true do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+ @stdin_name = fixture __FILE__, "stdin.txt"
+
+ @file1 = File.readlines @file1_name
+ @file2 = File.readlines @file2_name
+ @stdin = File.read @stdin_name
+ end
+
+ it "reads one line of a file" do
+ argf [@file1_name] do
+ @argf.send(@method).should == @file1.first
+ end
+ end
+
+ it "reads all lines of a file" do
+ argf [@file1_name] do
+ lines = []
+ @file1.size.times { lines << @argf.send(@method) }
+ lines.should == @file1
+ end
+ end
+
+ it "reads all lines of stdin" do
+ total = @stdin.count $/
+ stdin = ruby_exe(
+ "#{total}.times { print ARGF.send(#{@method.inspect}) }",
+ args: "< #{@stdin_name}")
+ stdin.should == @stdin
+ end
+
+ it "reads all lines of two files" do
+ argf [@file1_name, @file2_name] do
+ total = @file1.size + @file2.size
+ lines = []
+ total.times { lines << @argf.send(@method) }
+ lines.should == @file1 + @file2
+ end
+ end
+
+ it "sets $_ global variable with each line read" do
+ argf [@file1_name, @file2_name] do
+ total = @file1.size + @file2.size
+ total.times do
+ line = @argf.send(@method)
+ $_.should == line
+ end
+ end
+ end
+end
+
+describe :argf_gets_inplace_edit, shared: true do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @tmp1_name = tmp "file1.txt"
+ @tmp2_name = tmp "file2.txt"
+
+ @tmp1_name_bak = @tmp1_name + ".bak"
+ @tmp2_name_bak = @tmp2_name + ".bak"
+
+ cp @file1_name, @tmp1_name
+ cp @file2_name, @tmp2_name
+
+ method = "ARGF.send(#{@method.inspect})"
+ @code = "begin while line = #{method} do puts 'x' end rescue EOFError; end"
+ end
+
+ after :each do
+ rm_r @tmp1_name, @tmp2_name, @tmp1_name_bak, @tmp2_name_bak
+ end
+
+ # -i with no backup extension is not supported on Windows
+ platform_is_not :windows do
+ it "modifies the files when in place edit mode is on" do
+ ruby_exe(@code,
+ options: "-i",
+ args: "#{@tmp1_name} #{@tmp2_name}")
+
+ File.read(@tmp1_name).should == "x\nx\n"
+ File.read(@tmp2_name).should == "x\nx\n"
+ end
+ end
+
+ it "modifies and backups two files when in place edit mode is on" do
+ ruby_exe(@code,
+ options: "-i.bak",
+ args: "#{@tmp1_name} #{@tmp2_name}")
+
+ File.read(@tmp1_name).should == "x\nx\n"
+ File.read(@tmp2_name).should == "x\nx\n"
+
+ File.read(@tmp1_name_bak).should == "file1.1\nfile1.2\n"
+ File.read(@tmp2_name_bak).should == "line2.1\nline2.2\n"
+ end
+end
diff --git a/spec/ruby/core/argf/shared/pos.rb b/spec/ruby/core/argf/shared/pos.rb
new file mode 100644
index 0000000000..9836d5f1e4
--- /dev/null
+++ b/spec/ruby/core/argf/shared/pos.rb
@@ -0,0 +1,31 @@
+describe :argf_pos, shared: true do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "gives the correct position for each read operation" do
+ argf [@file1, @file2] do
+ size1 = File.size(@file1)
+ size2 = File.size(@file2)
+
+ @argf.read(2)
+ @argf.send(@method).should == 2
+ @argf.read(size1-2)
+ @argf.send(@method).should == size1
+ @argf.read(6)
+ @argf.send(@method).should == 6
+ @argf.rewind
+ @argf.send(@method).should == 0
+ @argf.read(size2)
+ @argf.send(@method).should == size2
+ end
+ end
+
+ it "raises an ArgumentError when called on a closed stream" do
+ argf [@file1] do
+ @argf.read
+ -> { @argf.send(@method) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/read.rb b/spec/ruby/core/argf/shared/read.rb
new file mode 100644
index 0000000000..fe903983c0
--- /dev/null
+++ b/spec/ruby/core/argf/shared/read.rb
@@ -0,0 +1,58 @@
+describe :argf_read, shared: true do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @stdin_name = fixture __FILE__, "stdin.txt"
+
+ @file1 = File.read @file1_name
+ @stdin = File.read @stdin_name
+ end
+
+ it "treats second nil argument as no output buffer" do
+ argf [@file1_name] do
+ @argf.send(@method, @file1.size, nil).should == @file1
+ end
+ end
+
+ it "treats second argument as an output buffer" do
+ argf [@file1_name] do
+ buffer = ""
+ @argf.send(@method, @file1.size, buffer)
+ buffer.should == @file1
+ end
+ end
+
+ it "clears output buffer before appending to it" do
+ argf [@file1_name] do
+ buffer = "to be cleared"
+ @argf.send(@method, @file1.size, buffer)
+ buffer.should == @file1
+ end
+ end
+
+ it "reads a number of bytes from the first file" do
+ argf [@file1_name] do
+ @argf.send(@method, 5).should == @file1[0, 5]
+ end
+ end
+
+ it "reads from a single file consecutively" do
+ argf [@file1_name] do
+ @argf.send(@method, 1).should == @file1[0, 1]
+ @argf.send(@method, 2).should == @file1[1, 2]
+ @argf.send(@method, 3).should == @file1[3, 3]
+ end
+ end
+
+ it "reads a number of bytes from stdin" do
+ stdin = ruby_exe("print ARGF.#{@method}(10)", :args => "< #{@stdin_name}")
+ stdin.should == @stdin[0, 10]
+ end
+
+ platform_is_not :windows do
+ it "reads the contents of a special device file" do
+ argf ['/dev/zero'] do
+ @argf.send(@method, 100).should == "\000" * 100
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/readlines.rb b/spec/ruby/core/argf/shared/readlines.rb
new file mode 100644
index 0000000000..505fa94acb
--- /dev/null
+++ b/spec/ruby/core/argf/shared/readlines.rb
@@ -0,0 +1,22 @@
+describe :argf_readlines, shared: true do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+
+ @lines = File.readlines(@file1)
+ @lines += File.readlines(@file2)
+ end
+
+ it "reads all lines of all files" do
+ argf [@file1, @file2] do
+ @argf.send(@method).should == @lines
+ end
+ end
+
+ it "returns an empty Array when end of stream reached" do
+ argf [@file1, @file2] do
+ @argf.read
+ @argf.send(@method).should == []
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/skip_spec.rb b/spec/ruby/core/argf/skip_spec.rb
new file mode 100644
index 0000000000..0181801c2d
--- /dev/null
+++ b/spec/ruby/core/argf/skip_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.skip" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @file2 = File.readlines @file2_name
+ end
+
+ it "skips the current file" do
+ argf [@file1_name, @file2_name] do
+ @argf.read(1)
+ @argf.skip
+ @argf.gets.should == @file2.first
+ end
+ end
+
+ it "has no effect when called twice in a row" do
+ argf [@file1_name, @file2_name] do
+ @argf.read(1)
+ @argf.skip
+ @argf.skip
+ @argf.gets.should == @file2.first
+ end
+ end
+
+ it "has no effect at end of stream" do
+ argf [@file1_name, @file2_name] do
+ @argf.read
+ @argf.skip
+ @argf.gets.should == nil
+ end
+ end
+
+ # This bypasses argf helper because the helper will call argf.file
+ # which as a side-effect calls argf.file which will initialize
+ # internals of ARGF enough for this to work.
+ it "has no effect when nothing has been processed yet" do
+ -> { ARGF.class.new(@file1_name).skip }.should_not raise_error
+ end
+end
diff --git a/spec/ruby/core/argf/tell_spec.rb b/spec/ruby/core/argf/tell_spec.rb
new file mode 100644
index 0000000000..16d9f29920
--- /dev/null
+++ b/spec/ruby/core/argf/tell_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/pos'
+
+describe "ARGF.tell" do
+ it_behaves_like :argf_pos, :tell
+end
diff --git a/spec/ruby/core/argf/to_a_spec.rb b/spec/ruby/core/argf/to_a_spec.rb
new file mode 100644
index 0000000000..b17a93db33
--- /dev/null
+++ b/spec/ruby/core/argf/to_a_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/readlines'
+
+describe "ARGF.to_a" do
+ it_behaves_like :argf_readlines, :to_a
+end
diff --git a/spec/ruby/core/argf/to_i_spec.rb b/spec/ruby/core/argf/to_i_spec.rb
new file mode 100644
index 0000000000..2183de6cd4
--- /dev/null
+++ b/spec/ruby/core/argf/to_i_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/fileno'
+
+describe "ARGF.to_i" do
+ it_behaves_like :argf_fileno, :to_i
+end
diff --git a/spec/ruby/core/argf/to_io_spec.rb b/spec/ruby/core/argf/to_io_spec.rb
new file mode 100644
index 0000000000..062383d291
--- /dev/null
+++ b/spec/ruby/core/argf/to_io_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.to_io" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "returns the IO of the current file" do
+ argf [@file1, @file2] do
+ result = []
+ 4.times do
+ @argf.gets
+ result << @argf.to_io
+ end
+
+ result.each { |io| io.should be_kind_of(IO) }
+ result[0].should == result[1]
+ result[2].should == result[3]
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/to_s_spec.rb b/spec/ruby/core/argf/to_s_spec.rb
new file mode 100644
index 0000000000..3f505898f4
--- /dev/null
+++ b/spec/ruby/core/argf/to_s_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.to_s" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "returns 'ARGF'" do
+ argf [@file1, @file2] do
+ @argf.to_s.should == "ARGF"
+ end
+ end
+end
diff --git a/spec/ruby/core/array/allocate_spec.rb b/spec/ruby/core/array/allocate_spec.rb
new file mode 100644
index 0000000000..04f7c0d0ad
--- /dev/null
+++ b/spec/ruby/core/array/allocate_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Array.allocate" do
+ it "returns an instance of Array" do
+ ary = Array.allocate
+ ary.should be_an_instance_of(Array)
+ end
+
+ it "returns a fully-formed instance of Array" do
+ ary = Array.allocate
+ ary.size.should == 0
+ ary << 1
+ ary.should == [1]
+ end
+
+ it "does not accept any arguments" do
+ -> { Array.allocate(1) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/array/any_spec.rb b/spec/ruby/core/array/any_spec.rb
new file mode 100644
index 0000000000..09d949fe6e
--- /dev/null
+++ b/spec/ruby/core/array/any_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+describe "Array#any?" do
+ describe 'with no block given (a default block of { |x| x } is implicit)' do
+ it "is false if the array is empty" do
+ empty_array = []
+ empty_array.should_not.any?
+ end
+
+ it "is false if the array is not empty, but all the members of the array are falsy" do
+ falsy_array = [false, nil, false]
+ falsy_array.should_not.any?
+ end
+
+ it "is true if the array has any truthy members" do
+ not_empty_array = ['anything', nil]
+ not_empty_array.should.any?
+ end
+ end
+
+ describe 'with a block given' do
+ it 'is false if the array is empty' do
+ empty_array = []
+ empty_array.any? {|v| 1 == 1 }.should == false
+ end
+
+ it 'is true if the block returns true for any member of the array' do
+ array_with_members = [false, false, true, false]
+ array_with_members.any? {|v| v == true }.should == true
+ end
+
+ it 'is false if the block returns false for all members of the array' do
+ array_with_members = [false, false, true, false]
+ array_with_members.any? {|v| v == 42 }.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/array/append_spec.rb b/spec/ruby/core/array/append_spec.rb
new file mode 100644
index 0000000000..c12473dc07
--- /dev/null
+++ b/spec/ruby/core/array/append_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/push'
+
+describe "Array#<<" do
+ it "pushes the object onto the end of the array" do
+ ([ 1, 2 ] << "c" << "d" << [ 3, 4 ]).should == [1, 2, "c", "d", [3, 4]]
+ end
+
+ it "returns self to allow chaining" do
+ a = []
+ b = a
+ (a << 1).should equal(b)
+ (a << 2 << 3).should equal(b)
+ end
+
+ it "correctly resizes the Array" do
+ a = []
+ a.size.should == 0
+ a << :foo
+ a.size.should == 1
+ a << :bar << :baz
+ a.size.should == 3
+
+ a = [1, 2, 3]
+ a.shift
+ a.shift
+ a.shift
+ a << :foo
+ a.should == [:foo]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array << 5 }.should raise_error(FrozenError)
+ end
+end
+
+describe "Array#append" do
+ it_behaves_like :array_push, :append
+end
diff --git a/spec/ruby/core/array/array_spec.rb b/spec/ruby/core/array/array_spec.rb
new file mode 100644
index 0000000000..855f17348f
--- /dev/null
+++ b/spec/ruby/core/array/array_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Array" do
+ it "includes Enumerable" do
+ Array.include?(Enumerable).should == true
+ end
+end
diff --git a/spec/ruby/core/array/assoc_spec.rb b/spec/ruby/core/array/assoc_spec.rb
new file mode 100644
index 0000000000..f8479d763c
--- /dev/null
+++ b/spec/ruby/core/array/assoc_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#assoc" do
+ it "returns the first array whose 1st item is == obj or nil" do
+ s1 = ["colors", "red", "blue", "green"]
+ s2 = [:letters, "a", "b", "c"]
+ s3 = [4]
+ s4 = ["colors", "cyan", "yellow", "magenda"]
+ s5 = [:letters, "a", "i", "u"]
+ s_nil = [nil, nil]
+ a = [s1, s2, s3, s4, s5, s_nil]
+ a.assoc(s1.first).should equal(s1)
+ a.assoc(s2.first).should equal(s2)
+ a.assoc(s3.first).should equal(s3)
+ a.assoc(s4.first).should equal(s1)
+ a.assoc(s5.first).should equal(s2)
+ a.assoc(s_nil.first).should equal(s_nil)
+ a.assoc(4).should equal(s3)
+ a.assoc("key not in array").should be_nil
+ end
+
+ it "calls == on first element of each array" do
+ key1 = 'it'
+ key2 = mock('key2')
+ items = [['not it', 1], [ArraySpecs::AssocKey.new, 2], ['na', 3]]
+
+ items.assoc(key1).should equal(items[1])
+ items.assoc(key2).should be_nil
+ end
+
+ it "ignores any non-Array elements" do
+ [1, 2, 3].assoc(2).should be_nil
+ s1 = [4]
+ s2 = [5, 4, 3]
+ a = ["foo", [], s1, s2, nil, []]
+ a.assoc(s1.first).should equal(s1)
+ a.assoc(s2.first).should equal(s2)
+ end
+end
diff --git a/spec/ruby/core/array/at_spec.rb b/spec/ruby/core/array/at_spec.rb
new file mode 100644
index 0000000000..8bc789fef7
--- /dev/null
+++ b/spec/ruby/core/array/at_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#at" do
+ it "returns the (n+1)'th element for the passed index n" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.at(0).should == 1
+ a.at(1).should == 2
+ a.at(5).should == 6
+ end
+
+ it "returns nil if the given index is greater than or equal to the array's length" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.at(6).should == nil
+ a.at(7).should == nil
+ end
+
+ it "returns the (-n)'th element from the last, for the given negative index n" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.at(-1).should == 6
+ a.at(-2).should == 5
+ a.at(-6).should == 1
+ end
+
+ it "returns nil if the given index is less than -len, where len is length of the array" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.at(-7).should == nil
+ a.at(-8).should == nil
+ end
+
+ it "does not extend the array unless the given index is out of range" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.length.should == 6
+ a.at(100)
+ a.length.should == 6
+ a.at(-100)
+ a.length.should == 6
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ a = ["a", "b", "c"]
+ a.at(0.5).should == "a"
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ a.at(obj).should == "c"
+ end
+
+ it "raises a TypeError when the passed argument can't be coerced to Integer" do
+ -> { [].at("cat") }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError when 2 or more arguments are passed" do
+ -> { [:a, :b].at(0,1) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/array/bsearch_index_spec.rb b/spec/ruby/core/array/bsearch_index_spec.rb
new file mode 100644
index 0000000000..df2c7c098e
--- /dev/null
+++ b/spec/ruby/core/array/bsearch_index_spec.rb
@@ -0,0 +1,85 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#bsearch_index" do
+ context "when not passed a block" do
+ before :each do
+ @enum = [1, 2, 42, 100, 666].bsearch_index
+ end
+
+ it "returns an Enumerator" do
+ @enum.should be_an_instance_of(Enumerator)
+ end
+
+ it "returns an Enumerator with unknown size" do
+ @enum.size.should be_nil
+ end
+
+ it "returns index of element when block condition is satisfied" do
+ @enum.each { |x| x >= 33 }.should == 2
+ end
+ end
+
+ it "raises a TypeError when block returns a String" do
+ -> { [1, 2, 3].bsearch_index { "not ok" } }.should raise_error(TypeError)
+ end
+
+ it "returns nil when block is empty" do
+ [1, 2, 3].bsearch_index {}.should be_nil
+ end
+
+ context "minimum mode" do
+ before :each do
+ @array = [0, 4, 7, 10, 12]
+ end
+
+ it "returns index of first element which satisfies the block" do
+ @array.bsearch_index { |x| x >= 4 }.should == 1
+ @array.bsearch_index { |x| x >= 6 }.should == 2
+ @array.bsearch_index { |x| x >= -1 }.should == 0
+ end
+
+ it "returns nil when block condition is never satisfied" do
+ @array.bsearch_index { false }.should be_nil
+ @array.bsearch_index { |x| x >= 100 }.should be_nil
+ end
+ end
+
+ context "find any mode" do
+ before :each do
+ @array = [0, 4, 7, 10, 12]
+ end
+
+ it "returns the index of any matched elements where element is between 4 <= x < 8" do
+ [1, 2].should include(@array.bsearch_index { |x| 1 - x / 4 })
+ end
+
+ it "returns the index of any matched elements where element is between 8 <= x < 10" do
+ @array.bsearch_index { |x| 4 - x / 2 }.should be_nil
+ end
+
+ it "returns nil when block never returns 0" do
+ @array.bsearch_index { |x| 1 }.should be_nil
+ @array.bsearch_index { |x| -1 }.should be_nil
+ end
+
+ it "returns the middle element when block always returns zero" do
+ @array.bsearch_index { |x| 0 }.should == 2
+ end
+
+ context "magnitude does not effect the result" do
+ it "returns the index of any matched elements where element is between 4n <= xn < 8n" do
+ [1, 2].should include(@array.bsearch_index { |x| (1 - x / 4) * (2**100) })
+ end
+
+ it "returns nil when block never returns 0" do
+ @array.bsearch_index { |x| 1 * (2**100) }.should be_nil
+ @array.bsearch_index { |x| (-1) * (2**100) }.should be_nil
+ end
+
+ it "handles values from Integer#coerce" do
+ [1, 2].should include(@array.bsearch_index { |x| (2**100).coerce((1 - x / 4) * (2**100)).first })
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/bsearch_spec.rb b/spec/ruby/core/array/bsearch_spec.rb
new file mode 100644
index 0000000000..8fa6245dbf
--- /dev/null
+++ b/spec/ruby/core/array/bsearch_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#bsearch" do
+ it "returns an Enumerator when not passed a block" do
+ [1].bsearch.should be_an_instance_of(Enumerator)
+ end
+
+ it_behaves_like :enumeratorized_with_unknown_size, :bsearch, [1,2,3]
+
+ it "raises a TypeError if the block returns an Object" do
+ -> { [1].bsearch { Object.new } }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the block returns a String" do
+ -> { [1].bsearch { "1" } }.should raise_error(TypeError)
+ end
+
+ context "with a block returning true or false" do
+ it "returns nil if the block returns false for every element" do
+ [0, 1, 2, 3].bsearch { |x| x > 3 }.should be_nil
+ end
+
+ it "returns nil if the block returns nil for every element" do
+ [0, 1, 2, 3].bsearch { |x| nil }.should be_nil
+ end
+
+ it "returns element at zero if the block returns true for every element" do
+ [0, 1, 2, 3].bsearch { |x| x < 4 }.should == 0
+
+ end
+
+ it "returns the element at the smallest index for which block returns true" do
+ [0, 1, 3, 4].bsearch { |x| x >= 2 }.should == 3
+ [0, 1, 3, 4].bsearch { |x| x >= 1 }.should == 1
+ end
+ end
+
+ context "with a block returning negative, zero, positive numbers" do
+ it "returns nil if the block returns less than zero for every element" do
+ [0, 1, 2, 3].bsearch { |x| x <=> 5 }.should be_nil
+ end
+
+ it "returns nil if the block returns greater than zero for every element" do
+ [0, 1, 2, 3].bsearch { |x| x <=> -1 }.should be_nil
+
+ end
+
+ it "returns nil if the block never returns zero" do
+ [0, 1, 3, 4].bsearch { |x| x <=> 2 }.should be_nil
+ end
+
+ it "accepts (+/-)Float::INFINITY from the block" do
+ [0, 1, 3, 4].bsearch { |x| Float::INFINITY }.should be_nil
+ [0, 1, 3, 4].bsearch { |x| -Float::INFINITY }.should be_nil
+ end
+
+ it "returns an element at an index for which block returns 0.0" do
+ result = [0, 1, 2, 3, 4].bsearch { |x| x < 2 ? 1.0 : x > 2 ? -1.0 : 0.0 }
+ result.should == 2
+ end
+
+ it "returns an element at an index for which block returns 0" do
+ result = [0, 1, 2, 3, 4].bsearch { |x| x < 1 ? 1 : x > 3 ? -1 : 0 }
+ [1, 2].should include(result)
+ end
+ end
+
+ context "with a block that calls break" do
+ it "returns nil if break is called without a value" do
+ ['a', 'b', 'c'].bsearch { |v| break }.should be_nil
+ end
+
+ it "returns nil if break is called with a nil value" do
+ ['a', 'b', 'c'].bsearch { |v| break nil }.should be_nil
+ end
+
+ it "returns object if break is called with an object" do
+ ['a', 'b', 'c'].bsearch { |v| break 1234 }.should == 1234
+ ['a', 'b', 'c'].bsearch { |v| break 'hi' }.should == 'hi'
+ ['a', 'b', 'c'].bsearch { |v| break [42] }.should == [42]
+ end
+ end
+end
diff --git a/spec/ruby/core/array/clear_spec.rb b/spec/ruby/core/array/clear_spec.rb
new file mode 100644
index 0000000000..81ba56e01e
--- /dev/null
+++ b/spec/ruby/core/array/clear_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#clear" do
+ it "removes all elements" do
+ a = [1, 2, 3, 4]
+ a.clear.should equal(a)
+ a.should == []
+ end
+
+ it "returns self" do
+ a = [1]
+ a.should equal a.clear
+ end
+
+ it "leaves the Array empty" do
+ a = [1]
+ a.clear
+ a.should.empty?
+ a.size.should == 0
+ end
+
+ it "does not accept any arguments" do
+ -> { [1].clear(true) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ a = [1]
+ a.freeze
+ -> { a.clear }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/clone_spec.rb b/spec/ruby/core/array/clone_spec.rb
new file mode 100644
index 0000000000..e22a6c6d53
--- /dev/null
+++ b/spec/ruby/core/array/clone_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/clone'
+
+describe "Array#clone" do
+ it_behaves_like :array_clone, :clone
+
+ it "copies frozen status from the original" do
+ a = [1, 2, 3, 4]
+ b = [1, 2, 3, 4]
+ a.freeze
+ aa = a.clone
+ bb = b.clone
+
+ aa.should.frozen?
+ bb.should_not.frozen?
+ end
+
+ it "copies singleton methods" do
+ a = [1, 2, 3, 4]
+ b = [1, 2, 3, 4]
+ def a.a_singleton_method; end
+ aa = a.clone
+ bb = b.clone
+
+ a.respond_to?(:a_singleton_method).should be_true
+ b.respond_to?(:a_singleton_method).should be_false
+ aa.respond_to?(:a_singleton_method).should be_true
+ bb.respond_to?(:a_singleton_method).should be_false
+ end
+end
diff --git a/spec/ruby/core/array/collect_spec.rb b/spec/ruby/core/array/collect_spec.rb
new file mode 100644
index 0000000000..0ad4c283b1
--- /dev/null
+++ b/spec/ruby/core/array/collect_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/collect'
+
+describe "Array#collect" do
+ it_behaves_like :array_collect, :collect
+end
+
+describe "Array#collect!" do
+ it_behaves_like :array_collect_b, :collect!
+end
diff --git a/spec/ruby/core/array/combination_spec.rb b/spec/ruby/core/array/combination_spec.rb
new file mode 100644
index 0000000000..f16d6f98fc
--- /dev/null
+++ b/spec/ruby/core/array/combination_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+
+describe "Array#combination" do
+ before :each do
+ @array = [1, 2, 3, 4]
+ end
+
+ it "returns an enumerator when no block is provided" do
+ @array.combination(2).should be_an_instance_of(Enumerator)
+ end
+
+ it "returns self when a block is given" do
+ @array.combination(2){}.should equal(@array)
+ end
+
+ it "yields nothing for out of bounds length and return self" do
+ @array.combination(5).to_a.should == []
+ @array.combination(-1).to_a.should == []
+ end
+
+ it "yields the expected combinations" do
+ @array.combination(3).to_a.sort.should == [[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
+ end
+
+ it "yields nothing if the argument is out of bounds" do
+ @array.combination(-1).to_a.should == []
+ @array.combination(5).to_a.should == []
+ end
+
+ it "yields a copy of self if the argument is the size of the receiver" do
+ r = @array.combination(4).to_a
+ r.should == [@array]
+ r[0].should_not equal(@array)
+ end
+
+ it "yields [] when length is 0" do
+ @array.combination(0).to_a.should == [[]] # one combination of length 0
+ [].combination(0).to_a.should == [[]] # one combination of length 0
+ end
+
+ it "yields a partition consisting of only singletons" do
+ @array.combination(1).to_a.sort.should == [[1],[2],[3],[4]]
+ end
+
+ it "generates from a defensive copy, ignoring mutations" do
+ accum = []
+ @array.combination(2) do |x|
+ accum << x
+ @array[0] = 1
+ end
+ accum.should == [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns 0 when the number of combinations is < 0" do
+ @array.combination(-1).size.should == 0
+ [].combination(-2).size.should == 0
+ end
+ it "returns the binomial coefficient between the array size the number of combinations" do
+ @array.combination(5).size.should == 0
+ @array.combination(4).size.should == 1
+ @array.combination(3).size.should == 4
+ @array.combination(2).size.should == 6
+ @array.combination(1).size.should == 4
+ @array.combination(0).size.should == 1
+ [].combination(0).size.should == 1
+ [].combination(1).size.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/compact_spec.rb b/spec/ruby/core/array/compact_spec.rb
new file mode 100644
index 0000000000..83b3fa2a89
--- /dev/null
+++ b/spec/ruby/core/array/compact_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#compact" do
+ it "returns a copy of array with all nil elements removed" do
+ a = [1, 2, 4]
+ a.compact.should == [1, 2, 4]
+ a = [1, nil, 2, 4]
+ a.compact.should == [1, 2, 4]
+ a = [1, 2, 4, nil]
+ a.compact.should == [1, 2, 4]
+ a = [nil, 1, 2, 4]
+ a.compact.should == [1, 2, 4]
+ end
+
+ it "does not return self" do
+ a = [1, 2, 3]
+ a.compact.should_not equal(a)
+ end
+
+ it "does not return subclass instance for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3, nil].compact.should be_an_instance_of(Array)
+ end
+end
+
+describe "Array#compact!" do
+ it "removes all nil elements" do
+ a = ['a', nil, 'b', false, 'c']
+ a.compact!.should equal(a)
+ a.should == ["a", "b", false, "c"]
+ a = [nil, 'a', 'b', false, 'c']
+ a.compact!.should equal(a)
+ a.should == ["a", "b", false, "c"]
+ a = ['a', 'b', false, 'c', nil]
+ a.compact!.should equal(a)
+ a.should == ["a", "b", false, "c"]
+ end
+
+ it "returns self if some nil elements are removed" do
+ a = ['a', nil, 'b', false, 'c']
+ a.compact!.should equal a
+ end
+
+ it "returns nil if there are no nil elements to remove" do
+ [1, 2, false, 3].compact!.should == nil
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.compact! }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/comparison_spec.rb b/spec/ruby/core/array/comparison_spec.rb
new file mode 100644
index 0000000000..5d1c3265f1
--- /dev/null
+++ b/spec/ruby/core/array/comparison_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#<=>" do
+ it "calls <=> left to right and return first non-0 result" do
+ [-1, +1, nil, "foobar"].each do |result|
+ lhs = Array.new(3) { mock("#{result}") }
+ rhs = Array.new(3) { mock("#{result}") }
+
+ lhs[0].should_receive(:<=>).with(rhs[0]).and_return(0)
+ lhs[1].should_receive(:<=>).with(rhs[1]).and_return(result)
+ lhs[2].should_not_receive(:<=>)
+
+ (lhs <=> rhs).should == result
+ end
+ end
+
+ it "returns 0 if the arrays are equal" do
+ ([] <=> []).should == 0
+ ([1, 2, 3, 4, 5, 6] <=> [1, 2, 3, 4, 5.0, 6.0]).should == 0
+ end
+
+ it "returns -1 if the array is shorter than the other array" do
+ ([] <=> [1]).should == -1
+ ([1, 1] <=> [1, 1, 1]).should == -1
+ end
+
+ it "returns +1 if the array is longer than the other array" do
+ ([1] <=> []).should == +1
+ ([1, 1, 1] <=> [1, 1]).should == +1
+ end
+
+ it "returns -1 if the arrays have same length and a pair of corresponding elements returns -1 for <=>" do
+ eq_l = mock('an object equal to the other')
+ eq_r = mock('an object equal to the other')
+ eq_l.should_receive(:<=>).with(eq_r).any_number_of_times.and_return(0)
+
+ less = mock('less than the other')
+ greater = mock('greater then the other')
+ less.should_receive(:<=>).with(greater).any_number_of_times.and_return(-1)
+
+ rest = mock('an rest element of the arrays')
+ rest.should_receive(:<=>).with(rest).any_number_of_times.and_return(0)
+ lhs = [eq_l, eq_l, less, rest]
+ rhs = [eq_r, eq_r, greater, rest]
+
+ (lhs <=> rhs).should == -1
+ end
+
+ it "returns +1 if the arrays have same length and a pair of corresponding elements returns +1 for <=>" do
+ eq_l = mock('an object equal to the other')
+ eq_r = mock('an object equal to the other')
+ eq_l.should_receive(:<=>).with(eq_r).any_number_of_times.and_return(0)
+
+ greater = mock('greater then the other')
+ less = mock('less than the other')
+ greater.should_receive(:<=>).with(less).any_number_of_times.and_return(+1)
+
+ rest = mock('an rest element of the arrays')
+ rest.should_receive(:<=>).with(rest).any_number_of_times.and_return(0)
+ lhs = [eq_l, eq_l, greater, rest]
+ rhs = [eq_r, eq_r, less, rest]
+
+ (lhs <=> rhs).should == +1
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ (empty <=> empty).should == 0
+ (empty <=> []).should == 1
+ ([] <=> empty).should == -1
+
+ (ArraySpecs.recursive_array <=> []).should == 1
+ ([] <=> ArraySpecs.recursive_array).should == -1
+
+ (ArraySpecs.recursive_array <=> ArraySpecs.empty_recursive_array).should == nil
+
+ array = ArraySpecs.recursive_array
+ (array <=> array).should == 0
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('to_ary')
+ obj.stub!(:to_ary).and_return([1, 2, 3])
+ ([4, 5] <=> obj).should == ([4, 5] <=> obj.to_ary)
+ end
+
+ it "does not call #to_ary on Array subclasses" do
+ obj = ArraySpecs::ToAryArray[5, 6, 7]
+ obj.should_not_receive(:to_ary)
+ ([5, 6, 7] <=> obj).should == 0
+ end
+
+ it "returns nil when the argument is not array-like" do
+ ([] <=> false).should be_nil
+ end
+end
diff --git a/spec/ruby/core/array/concat_spec.rb b/spec/ruby/core/array/concat_spec.rb
new file mode 100644
index 0000000000..f3cab9c17c
--- /dev/null
+++ b/spec/ruby/core/array/concat_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#concat" do
+ it "returns the array itself" do
+ ary = [1,2,3]
+ ary.concat([4,5,6]).equal?(ary).should be_true
+ end
+
+ it "appends the elements in the other array" do
+ ary = [1, 2, 3]
+ ary.concat([9, 10, 11]).should equal(ary)
+ ary.should == [1, 2, 3, 9, 10, 11]
+ ary.concat([])
+ ary.should == [1, 2, 3, 9, 10, 11]
+ end
+
+ it "does not loop endlessly when argument is self" do
+ ary = ["x", "y"]
+ ary.concat(ary).should == ["x", "y", "x", "y"]
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('to_ary')
+ obj.should_receive(:to_ary).and_return(["x", "y"])
+ [4, 5, 6].concat(obj).should == [4, 5, 6, "x", "y"]
+ end
+
+ it "does not call #to_ary on Array subclasses" do
+ obj = ArraySpecs::ToAryArray[5, 6, 7]
+ obj.should_not_receive(:to_ary)
+ [].concat(obj).should == [5, 6, 7]
+ end
+
+ it "raises a FrozenError when Array is frozen and modification occurs" do
+ -> { ArraySpecs.frozen_array.concat [1] }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError when Array is frozen and no modification occurs" do
+ -> { ArraySpecs.frozen_array.concat([]) }.should raise_error(FrozenError)
+ end
+
+ it "appends elements to an Array with enough capacity that has been shifted" do
+ ary = [1, 2, 3, 4, 5]
+ 2.times { ary.shift }
+ 2.times { ary.pop }
+ ary.concat([5, 6]).should == [3, 5, 6]
+ end
+
+ it "appends elements to an Array without enough capacity that has been shifted" do
+ ary = [1, 2, 3, 4]
+ 3.times { ary.shift }
+ ary.concat([5, 6]).should == [4, 5, 6]
+ end
+
+ it "takes multiple arguments" do
+ ary = [1, 2]
+ ary.concat [3, 4]
+ ary.should == [1, 2, 3, 4]
+ end
+
+ it "concatenates the initial value when given arguments contain 2 self" do
+ ary = [1, 2]
+ ary.concat ary, ary
+ ary.should == [1, 2, 1, 2, 1, 2]
+ end
+
+ it "returns self when given no arguments" do
+ ary = [1, 2]
+ ary.concat.should equal(ary)
+ ary.should == [1, 2]
+ end
+end
diff --git a/spec/ruby/core/array/constructor_spec.rb b/spec/ruby/core/array/constructor_spec.rb
new file mode 100644
index 0000000000..6f36074c45
--- /dev/null
+++ b/spec/ruby/core/array/constructor_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array.[]" do
+ it "returns a new array populated with the given elements" do
+ obj = Object.new
+ Array.[](5, true, nil, 'a', "Ruby", obj).should == [5, true, nil, "a", "Ruby", obj]
+
+ a = ArraySpecs::MyArray.[](5, true, nil, 'a', "Ruby", obj)
+ a.should be_an_instance_of(ArraySpecs::MyArray)
+ a.inspect.should == [5, true, nil, "a", "Ruby", obj].inspect
+ end
+end
+
+describe "Array[]" do
+ it "is a synonym for .[]" do
+ obj = Object.new
+ Array[5, true, nil, 'a', "Ruby", obj].should == Array.[](5, true, nil, "a", "Ruby", obj)
+
+ a = ArraySpecs::MyArray[5, true, nil, 'a', "Ruby", obj]
+ a.should be_an_instance_of(ArraySpecs::MyArray)
+ a.inspect.should == [5, true, nil, "a", "Ruby", obj].inspect
+ end
+end
diff --git a/spec/ruby/core/array/count_spec.rb b/spec/ruby/core/array/count_spec.rb
new file mode 100644
index 0000000000..eaf275aeb7
--- /dev/null
+++ b/spec/ruby/core/array/count_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "Array#count" do
+ it "returns the number of elements" do
+ [:a, :b, :c].count.should == 3
+ end
+
+ it "returns the number of elements that equal the argument" do
+ [:a, :b, :b, :c].count(:b).should == 2
+ end
+
+ it "returns the number of element for which the block evaluates to true" do
+ [:a, :b, :c].count { |s| s != :b }.should == 2
+ end
+end
diff --git a/spec/ruby/core/array/cycle_spec.rb b/spec/ruby/core/array/cycle_spec.rb
new file mode 100644
index 0000000000..7219b49883
--- /dev/null
+++ b/spec/ruby/core/array/cycle_spec.rb
@@ -0,0 +1,101 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#cycle" do
+ before :each do
+ ScratchPad.record []
+
+ @array = [1, 2, 3]
+ @prc = -> x { ScratchPad << x }
+ end
+
+ it "does not yield and returns nil when the array is empty and passed value is an integer" do
+ [].cycle(6, &@prc).should be_nil
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not yield and returns nil when the array is empty and passed value is nil" do
+ [].cycle(nil, &@prc).should be_nil
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not yield and returns nil when passed 0" do
+ @array.cycle(0, &@prc).should be_nil
+ ScratchPad.recorded.should == []
+ end
+
+ it "iterates the array 'count' times yielding each item to the block" do
+ @array.cycle(2, &@prc)
+ ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3]
+ end
+
+ it "iterates indefinitely when not passed a count" do
+ @array.cycle do |x|
+ ScratchPad << x
+ break if ScratchPad.recorded.size > 7
+ end
+ ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3, 1, 2]
+ end
+
+ it "iterates indefinitely when passed nil" do
+ @array.cycle(nil) do |x|
+ ScratchPad << x
+ break if ScratchPad.recorded.size > 7
+ end
+ ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3, 1, 2]
+ end
+
+ it "does not rescue StopIteration when not passed a count" do
+ -> do
+ @array.cycle { raise StopIteration }
+ end.should raise_error(StopIteration)
+ end
+
+ it "does not rescue StopIteration when passed a count" do
+ -> do
+ @array.cycle(3) { raise StopIteration }
+ end.should raise_error(StopIteration)
+ end
+
+ it "iterates the array Integer(count) times when passed a Float count" do
+ @array.cycle(2.7, &@prc)
+ ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3]
+ end
+
+ it "calls #to_int to convert count to an Integer" do
+ count = mock("cycle count 2")
+ count.should_receive(:to_int).and_return(2)
+
+ @array.cycle(count, &@prc)
+ ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3]
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ count = mock("cycle count 2")
+ count.should_receive(:to_int).and_return("2")
+
+ -> { @array.cycle(count, &@prc) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed a String" do
+ -> { @array.cycle("4") { } }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an Object" do
+ -> { @array.cycle(mock("cycle count")) { } }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed true" do
+ -> { @array.cycle(true) { } }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed false" do
+ -> { @array.cycle(false) { } }.should raise_error(TypeError)
+ end
+
+ before :all do
+ @object = [1, 2, 3, 4]
+ @empty_object = []
+ end
+ it_should_behave_like :enumeratorized_with_cycle_size
+end
diff --git a/spec/ruby/core/array/deconstruct_spec.rb b/spec/ruby/core/array/deconstruct_spec.rb
new file mode 100644
index 0000000000..ad67abe47b
--- /dev/null
+++ b/spec/ruby/core/array/deconstruct_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Array#deconstruct" do
+ it "returns self" do
+ array = [1]
+
+ array.deconstruct.should equal array
+ end
+end
diff --git a/spec/ruby/core/array/delete_at_spec.rb b/spec/ruby/core/array/delete_at_spec.rb
new file mode 100644
index 0000000000..80ec643702
--- /dev/null
+++ b/spec/ruby/core/array/delete_at_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#delete_at" do
+ it "removes the element at the specified index" do
+ a = [1, 2, 3, 4]
+ a.delete_at(2)
+ a.should == [1, 2, 4]
+ a.delete_at(-1)
+ a.should == [1, 2]
+ end
+
+ it "returns the removed element at the specified index" do
+ a = [1, 2, 3, 4]
+ a.delete_at(2).should == 3
+ a.delete_at(-1).should == 4
+ end
+
+ it "returns nil and makes no modification if the index is out of range" do
+ a = [1, 2]
+ a.delete_at(3).should == nil
+ a.should == [1, 2]
+ a.delete_at(-3).should == nil
+ a.should == [1, 2]
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(-1)
+ [1, 2].delete_at(obj).should == 2
+ end
+
+ it "accepts negative indices" do
+ a = [1, 2]
+ a.delete_at(-2).should == 1
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { [1,2,3].freeze.delete_at(0) }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/delete_if_spec.rb b/spec/ruby/core/array/delete_if_spec.rb
new file mode 100644
index 0000000000..1459cc8d04
--- /dev/null
+++ b/spec/ruby/core/array/delete_if_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative 'shared/delete_if'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#delete_if" do
+ before do
+ @a = [ "a", "b", "c" ]
+ end
+
+ it "removes each element for which block returns true" do
+ @a = [ "a", "b", "c" ]
+ @a.delete_if { |x| x >= "b" }
+ @a.should == ["a"]
+ end
+
+ it "returns self" do
+ @a.delete_if{ true }.equal?(@a).should be_true
+ end
+
+ it_behaves_like :enumeratorize, :delete_if
+
+ it "returns self when called on an Array emptied with #shift" do
+ array = [1]
+ array.shift
+ array.delete_if { |x| true }.should equal(array)
+ end
+
+ it "returns an Enumerator if no block given, and the enumerator can modify the original array" do
+ enum = @a.delete_if
+ enum.should be_an_instance_of(Enumerator)
+ @a.should_not be_empty
+ enum.each { true }
+ @a.should be_empty
+ end
+
+ it "returns an Enumerator if no block given, and the array is frozen" do
+ @a.freeze.delete_if.should be_an_instance_of(Enumerator)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.delete_if {} }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.delete_if {} }.should raise_error(FrozenError)
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :delete_if, [1,2,3]
+ it_behaves_like :delete_if, :delete_if
+end
diff --git a/spec/ruby/core/array/delete_spec.rb b/spec/ruby/core/array/delete_spec.rb
new file mode 100644
index 0000000000..dddbbe6bd3
--- /dev/null
+++ b/spec/ruby/core/array/delete_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#delete" do
+ it "removes elements that are #== to object" do
+ x = mock('delete')
+ def x.==(other) 3 == other end
+
+ a = [1, 2, 3, x, 4, 3, 5, x]
+ a.delete mock('not contained')
+ a.should == [1, 2, 3, x, 4, 3, 5, x]
+
+ a.delete 3
+ a.should == [1, 2, 4, 5]
+ end
+
+ it "calculates equality correctly for reference values" do
+ a = ["foo", "bar", "foo", "quux", "foo"]
+ a.delete "foo"
+ a.should == ["bar","quux"]
+ end
+
+ it "returns object or nil if no elements match object" do
+ [1, 2, 4, 5].delete(1).should == 1
+ [1, 2, 4, 5].delete(3).should == nil
+ end
+
+ it "may be given a block that is executed if no element matches object" do
+ [1].delete(1) {:not_found}.should == 1
+ [].delete('a') {:not_found}.should == :not_found
+ end
+
+ it "returns nil if the array is empty due to a shift" do
+ a = [1]
+ a.shift
+ a.delete(nil).should == nil
+ end
+
+ it "returns nil on a frozen array if a modification does not take place" do
+ [1, 2, 3].freeze.delete(0).should == nil
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { [1, 2, 3].freeze.delete(1) }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/difference_spec.rb b/spec/ruby/core/array/difference_spec.rb
new file mode 100644
index 0000000000..9f7d4c4a1a
--- /dev/null
+++ b/spec/ruby/core/array/difference_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/difference'
+
+describe "Array#difference" do
+ it_behaves_like :array_binary_difference, :difference
+
+ it "returns a copy when called without any parameter" do
+ x = [1, 2, 3, 2]
+ x.difference.should == x
+ x.difference.should_not equal x
+ end
+
+ it "does not return subclass instances for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].difference.should be_an_instance_of(Array)
+ end
+
+ it "accepts multiple arguments" do
+ x = [1, 2, 3, 1]
+ x.difference([], [0, 1], [3, 4], [3]).should == [2]
+ end
+end
diff --git a/spec/ruby/core/array/dig_spec.rb b/spec/ruby/core/array/dig_spec.rb
new file mode 100644
index 0000000000..f2d8ff47fd
--- /dev/null
+++ b/spec/ruby/core/array/dig_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+
+describe "Array#dig" do
+
+ it "returns #at with one arg" do
+ ['a'].dig(0).should == 'a'
+ ['a'].dig(1).should be_nil
+ end
+
+ it "recurses array elements" do
+ a = [ [ 1, [2, '3'] ] ]
+ a.dig(0, 0).should == 1
+ a.dig(0, 1, 1).should == '3'
+ a.dig(0, -1, 0).should == 2
+ end
+
+ it "returns the nested value specified if the sequence includes a key" do
+ a = [42, { foo: :bar }]
+ a.dig(1, :foo).should == :bar
+ end
+
+ it "raises a TypeError for a non-numeric index" do
+ -> {
+ ['a'].dig(:first)
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if any intermediate step does not respond to #dig" do
+ a = [1, 2]
+ -> {
+ a.dig(0, 1)
+ }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError if no arguments provided" do
+ -> {
+ [10].dig()
+ }.should raise_error(ArgumentError)
+ end
+
+ it "returns nil if any intermediate step is nil" do
+ a = [[1, [2, 3]]]
+ a.dig(1, 2, 3).should == nil
+ end
+
+ it "calls #dig on the result of #at with the remaining arguments" do
+ h = [[nil, [nil, nil, 42]]]
+ h[0].should_receive(:dig).with(1, 2).and_return(42)
+ h.dig(0, 1, 2).should == 42
+ end
+
+end
diff --git a/spec/ruby/core/array/drop_spec.rb b/spec/ruby/core/array/drop_spec.rb
new file mode 100644
index 0000000000..f911fd9018
--- /dev/null
+++ b/spec/ruby/core/array/drop_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#drop" do
+ it "removes the specified number of elements from the start of the array" do
+ [1, 2, 3, 4, 5].drop(2).should == [3, 4, 5]
+ end
+
+ it "raises an ArgumentError if the number of elements specified is negative" do
+ -> { [1, 2].drop(-3) }.should raise_error(ArgumentError)
+ end
+
+ it "returns an empty Array if all elements are dropped" do
+ [1, 2].drop(2).should == []
+ end
+
+ it "returns an empty Array when called on an empty Array" do
+ [].drop(0).should == []
+ end
+
+ it "does not remove any elements when passed zero" do
+ [1, 2].drop(0).should == [1, 2]
+ end
+
+ it "returns an empty Array if more elements than exist are dropped" do
+ [1, 2].drop(3).should == []
+ end
+
+ it 'acts correctly after a shift' do
+ ary = [nil, 1, 2]
+ ary.shift
+ ary.drop(1).should == [2]
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(2)
+
+ [1, 2, 3].drop(obj).should == [3]
+ end
+
+ it "raises a TypeError when the passed argument can't be coerced to Integer" do
+ -> { [1, 2].drop("cat") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the passed argument isn't an integer and #to_int returns non-Integer" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return("cat")
+
+ -> { [1, 2].drop(obj) }.should raise_error(TypeError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it 'returns a subclass instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].drop(1).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it 'returns a Array instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].drop(1).should be_an_instance_of(Array)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/drop_while_spec.rb b/spec/ruby/core/array/drop_while_spec.rb
new file mode 100644
index 0000000000..bb783d22a5
--- /dev/null
+++ b/spec/ruby/core/array/drop_while_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#drop_while" do
+ it "removes elements from the start of the array while the block evaluates to true" do
+ [1, 2, 3, 4].drop_while { |n| n < 4 }.should == [4]
+ end
+
+ it "removes elements from the start of the array until the block returns nil" do
+ [1, 2, 3, nil, 5].drop_while { |n| n }.should == [nil, 5]
+ end
+
+ it "removes elements from the start of the array until the block returns false" do
+ [1, 2, 3, false, 5].drop_while { |n| n }.should == [false, 5]
+ end
+
+ ruby_version_is ''...'3.0' do
+ it 'returns a subclass instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].drop_while { |n| n < 4 }.should be_an_instance_of(ArraySpecs::MyArray)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it 'returns a Array instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].drop_while { |n| n < 4 }.should be_an_instance_of(Array)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/dup_spec.rb b/spec/ruby/core/array/dup_spec.rb
new file mode 100644
index 0000000000..17f467d5fc
--- /dev/null
+++ b/spec/ruby/core/array/dup_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/clone'
+
+describe "Array#dup" do
+ it_behaves_like :array_clone, :dup # FIX: no, clone and dup are not alike
+
+ it "does not copy frozen status from the original" do
+ a = [1, 2, 3, 4]
+ b = [1, 2, 3, 4]
+ a.freeze
+ aa = a.dup
+ bb = b.dup
+
+ aa.frozen?.should be_false
+ bb.frozen?.should be_false
+ end
+
+ it "does not copy singleton methods" do
+ a = [1, 2, 3, 4]
+ b = [1, 2, 3, 4]
+ def a.a_singleton_method; end
+ aa = a.dup
+ bb = b.dup
+
+ a.respond_to?(:a_singleton_method).should be_true
+ b.respond_to?(:a_singleton_method).should be_false
+ aa.respond_to?(:a_singleton_method).should be_false
+ bb.respond_to?(:a_singleton_method).should be_false
+ end
+end
diff --git a/spec/ruby/core/array/each_index_spec.rb b/spec/ruby/core/array/each_index_spec.rb
new file mode 100644
index 0000000000..51af5842c4
--- /dev/null
+++ b/spec/ruby/core/array/each_index_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative '../enumerable/shared/enumeratorized'
+
+# Modifying a collection while the contents are being iterated
+# gives undefined behavior. See
+# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/23633
+
+describe "Array#each_index" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "passes the index of each element to the block" do
+ a = ['a', 'b', 'c', 'd']
+ a.each_index { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [0, 1, 2, 3]
+ end
+
+ it "returns self" do
+ a = [:a, :b, :c]
+ a.each_index { |i| }.should equal(a)
+ end
+
+ it "is not confused by removing elements from the front" do
+ a = [1, 2, 3]
+
+ a.shift
+ ScratchPad.record []
+ a.each_index { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [0, 1]
+
+ a.shift
+ ScratchPad.record []
+ a.each_index { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [0]
+ end
+
+ it_behaves_like :enumeratorize, :each_index
+ it_behaves_like :enumeratorized_with_origin_size, :each_index, [1,2,3]
+end
diff --git a/spec/ruby/core/array/each_spec.rb b/spec/ruby/core/array/each_spec.rb
new file mode 100644
index 0000000000..cf8e9da6d8
--- /dev/null
+++ b/spec/ruby/core/array/each_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative '../enumerable/shared/enumeratorized'
+
+# Mutating the array while it is being iterated is discouraged as it can result in confusing behavior.
+# Yet a Ruby implementation must not crash in such a case, and following the simple CRuby behavior makes sense.
+# CRuby simply reads the array storage and checks the size for every iteration;
+# like `i = 0; while i < size; yield self[i]; end`
+
+describe "Array#each" do
+ it "yields each element to the block" do
+ a = []
+ x = [1, 2, 3]
+ x.each { |item| a << item }.should equal(x)
+ a.should == [1, 2, 3]
+ end
+
+ it "yields each element to the block even if the array is changed during iteration" do
+ a = [1, 2, 3, 4, 5]
+ iterated = []
+ a.each { |x| iterated << x; a << x+5 if x.even? }
+ iterated.should == [1, 2, 3, 4, 5, 7, 9]
+ end
+
+ it "yields only elements that are still in the array" do
+ a = [0, 1, 2, 3, 4]
+ iterated = []
+ a.each { |x| iterated << x; a.pop if x.even? }
+ iterated.should == [0, 1, 2]
+ end
+
+ it "yields elements based on an internal index" do
+ a = [0, 1, 2, 3, 4]
+ iterated = []
+ a.each { |x| iterated << x; a.shift if x.even? }
+ iterated.should == [0, 2, 4]
+ end
+
+ it "yields the same element multiple times if inserting while iterating" do
+ a = [1, 2]
+ iterated = []
+ a.each { |x| iterated << x; a.unshift(0) if a.size == 2 }
+ iterated.should == [1, 1, 2]
+ end
+
+ it "yields each element to a block that takes multiple arguments" do
+ a = [[1, 2], :a, [3, 4]]
+ b = []
+
+ a.each { |x, y| b << x }
+ b.should == [1, :a, 3]
+
+ b = []
+ a.each { |x, y| b << y }
+ b.should == [2, nil, 4]
+ end
+
+ it "yields elements added to the end of the array by the block" do
+ a = [2]
+ iterated = []
+ a.each { |x| iterated << x; x.times { a << 0 } }
+
+ iterated.should == [2, 0, 0]
+ end
+
+ it "does not yield elements deleted from the end of the array" do
+ a = [2, 3, 1]
+ iterated = []
+ a.each { |x| iterated << x; a.delete_at(2) if x == 3 }
+
+ iterated.should == [2, 3]
+ end
+
+ it_behaves_like :enumeratorize, :each
+ it_behaves_like :enumeratorized_with_origin_size, :each, [1,2,3]
+end
diff --git a/spec/ruby/core/array/element_reference_spec.rb b/spec/ruby/core/array/element_reference_spec.rb
new file mode 100644
index 0000000000..31e5578a09
--- /dev/null
+++ b/spec/ruby/core/array/element_reference_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/slice'
+
+describe "Array#[]" do
+ it_behaves_like :array_slice, :[]
+end
+
+describe "Array.[]" do
+ it "[] should return a new array populated with the given elements" do
+ array = Array[1, 'a', nil]
+ array[0].should == 1
+ array[1].should == 'a'
+ array[2].should == nil
+ end
+
+ it "when applied to a literal nested array, unpacks its elements into the containing array" do
+ Array[1, 2, *[3, 4, 5]].should == [1, 2, 3, 4, 5]
+ end
+
+ it "when applied to a nested referenced array, unpacks its elements into the containing array" do
+ splatted_array = Array[3, 4, 5]
+ Array[1, 2, *splatted_array].should == [1, 2, 3, 4, 5]
+ end
+
+ it "can unpack 2 or more nested referenced array" do
+ splatted_array = Array[3, 4, 5]
+ splatted_array2 = Array[6, 7, 8]
+ Array[1, 2, *splatted_array, *splatted_array2].should == [1, 2, 3, 4, 5, 6, 7, 8]
+ end
+
+ it "constructs a nested Hash for tailing key-value pairs" do
+ Array[1, 2, 3 => 4, 5 => 6].should == [1, 2, { 3 => 4, 5 => 6 }]
+ end
+
+ describe "with a subclass of Array" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "returns an instance of the subclass" do
+ ArraySpecs::MyArray[1, 2, 3].should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "does not call #initialize on the subclass instance" do
+ ArraySpecs::MyArray[1, 2, 3].should == [1, 2, 3]
+ ScratchPad.recorded.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/array/element_set_spec.rb b/spec/ruby/core/array/element_set_spec.rb
new file mode 100644
index 0000000000..df5ca9582e
--- /dev/null
+++ b/spec/ruby/core/array/element_set_spec.rb
@@ -0,0 +1,537 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#[]=" do
+ it "sets the value of the element at index" do
+ a = [1, 2, 3, 4]
+ a[2] = 5
+ a[-1] = 6
+ a[5] = 3
+ a.should == [1, 2, 5, 6, nil, 3]
+
+ a = []
+ a[4] = "e"
+ a.should == [nil, nil, nil, nil, "e"]
+ a[3] = "d"
+ a.should == [nil, nil, nil, "d", "e"]
+ a[0] = "a"
+ a.should == ["a", nil, nil, "d", "e"]
+ a[-3] = "C"
+ a.should == ["a", nil, "C", "d", "e"]
+ a[-1] = "E"
+ a.should == ["a", nil, "C", "d", "E"]
+ a[-5] = "A"
+ a.should == ["A", nil, "C", "d", "E"]
+ a[5] = "f"
+ a.should == ["A", nil, "C", "d", "E", "f"]
+ a[1] = []
+ a.should == ["A", [], "C", "d", "E", "f"]
+ a[-1] = nil
+ a.should == ["A", [], "C", "d", "E", nil]
+ end
+
+ it "sets the section defined by [start,length] to other" do
+ a = [1, 2, 3, 4, 5, 6]
+ a[0, 1] = 2
+ a[3, 2] = ['a', 'b', 'c', 'd']
+ a.should == [2, 2, 3, "a", "b", "c", "d", 6]
+ end
+
+ it "replaces the section defined by [start,length] with the given values" do
+ a = [1, 2, 3, 4, 5, 6]
+ a[3, 2] = 'a', 'b', 'c', 'd'
+ a.should == [1, 2, 3, "a", "b", "c", "d", 6]
+ end
+
+ it "just sets the section defined by [start,length] to other even if other is nil" do
+ a = ['a', 'b', 'c', 'd', 'e']
+ a[1, 3] = nil
+ a.should == ["a", nil, "e"]
+ end
+
+ it "returns nil if the rhs is nil" do
+ a = [1, 2, 3]
+ (a[1, 3] = nil).should == nil
+ (a[1..3] = nil).should == nil
+ end
+
+ it "sets the section defined by range to other" do
+ a = [6, 5, 4, 3, 2, 1]
+ a[1...2] = 9
+ a[3..6] = [6, 6, 6]
+ a.should == [6, 9, 4, 6, 6, 6]
+ end
+
+ it "replaces the section defined by range with the given values" do
+ a = [6, 5, 4, 3, 2, 1]
+ a[3..6] = :a, :b, :c
+ a.should == [6, 5, 4, :a, :b, :c]
+ end
+
+ it "just sets the section defined by range to other even if other is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[0..1] = nil
+ a.should == [nil, 3, 4, 5]
+ end
+
+ it 'expands and nil-pads the array if section assigned by range is outside array boundaries' do
+ a = ['a']
+ a[3..4] = ['b', 'c']
+ a.should == ['a', nil, nil, 'b', 'c']
+ end
+
+ it "calls to_int on its start and length arguments" do
+ obj = mock('to_int')
+ obj.stub!(:to_int).and_return(2)
+
+ a = [1, 2, 3, 4]
+ a[obj, 0] = [9]
+ a.should == [1, 2, 9, 3, 4]
+ a[obj, obj] = []
+ a.should == [1, 2, 4]
+ a[obj] = -1
+ a.should == [1, 2, -1]
+ end
+
+ it "checks frozen before attempting to coerce arguments" do
+ a = [1,2,3,4].freeze
+ -> {a[:foo] = 1}.should raise_error(FrozenError)
+ -> {a[:foo, :bar] = 1}.should raise_error(FrozenError)
+ end
+
+ it "sets elements in the range arguments when passed ranges" do
+ ary = [1, 2, 3]
+ rhs = [nil, [], ["x"], ["x", "y"]]
+ (0 .. ary.size + 2).each do |a|
+ (a .. ary.size + 3).each do |b|
+ rhs.each do |c|
+ ary1 = ary.dup
+ ary1[a .. b] = c
+ ary2 = ary.dup
+ ary2[a, 1 + b-a] = c
+ ary1.should == ary2
+
+ ary1 = ary.dup
+ ary1[a ... b] = c
+ ary2 = ary.dup
+ ary2[a, b-a] = c
+ ary1.should == ary2
+ end
+ end
+ end
+ end
+
+ it "inserts the given elements with [range] which the range is zero-width" do
+ ary = [1, 2, 3]
+ ary[1...1] = 0
+ ary.should == [1, 0, 2, 3]
+ ary[1...1] = [5]
+ ary.should == [1, 5, 0, 2, 3]
+ ary[1...1] = :a, :b, :c
+ ary.should == [1, :a, :b, :c, 5, 0, 2, 3]
+ end
+
+ it "inserts the given elements with [start, length] which length is zero" do
+ ary = [1, 2, 3]
+ ary[1, 0] = 0
+ ary.should == [1, 0, 2, 3]
+ ary[1, 0] = [5]
+ ary.should == [1, 5, 0, 2, 3]
+ ary[1, 0] = :a, :b, :c
+ ary.should == [1, :a, :b, :c, 5, 0, 2, 3]
+ end
+
+ # Now we only have to test cases where the start, length interface would
+ # have raise an exception because of negative size
+ it "inserts the given elements with [range] which the range has negative width" do
+ ary = [1, 2, 3]
+ ary[1..0] = 0
+ ary.should == [1, 0, 2, 3]
+ ary[1..0] = [4, 3]
+ ary.should == [1, 4, 3, 0, 2, 3]
+ ary[1..0] = :a, :b, :c
+ ary.should == [1, :a, :b, :c, 4, 3, 0, 2, 3]
+ end
+
+ it "just inserts nil if the section defined by range is zero-width and the rhs is nil" do
+ ary = [1, 2, 3]
+ ary[1...1] = nil
+ ary.should == [1, nil, 2, 3]
+ end
+
+ it "just inserts nil if the section defined by range has negative width and the rhs is nil" do
+ ary = [1, 2, 3]
+ ary[1..0] = nil
+ ary.should == [1, nil, 2, 3]
+ end
+
+ it "does nothing if the section defined by range is zero-width and the rhs is an empty array" do
+ ary = [1, 2, 3]
+ ary[1...1] = []
+ ary.should == [1, 2, 3]
+ end
+
+ it "does nothing if the section defined by range has negative width and the rhs is an empty array" do
+ ary = [1, 2, 3, 4, 5]
+ ary[1...0] = []
+ ary.should == [1, 2, 3, 4, 5]
+ ary[-2..2] = []
+ ary.should == [1, 2, 3, 4, 5]
+ end
+
+ it "tries to convert Range elements to Integers using #to_int with [m..n] and [m...n]" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ def from.to_int() 1 end
+ def to.to_int() -2 end
+
+ a = [1, 2, 3, 4]
+
+ a[from .. to] = ["a", "b", "c"]
+ a.should == [1, "a", "b", "c", 4]
+
+ a[to .. from] = ["x"]
+ a.should == [1, "a", "b", "x", "c", 4]
+ -> { a["a" .. "b"] = [] }.should raise_error(TypeError)
+ -> { a[from .. "b"] = [] }.should raise_error(TypeError)
+ end
+
+ it "raises an IndexError when passed indexes out of bounds" do
+ a = [1, 2, 3, 4]
+ -> { a[-5] = "" }.should raise_error(IndexError)
+ -> { a[-5, -1] = "" }.should raise_error(IndexError)
+ -> { a[-5, 0] = "" }.should raise_error(IndexError)
+ -> { a[-5, 1] = "" }.should raise_error(IndexError)
+ -> { a[-5, 2] = "" }.should raise_error(IndexError)
+ -> { a[-5, 10] = "" }.should raise_error(IndexError)
+
+ -> { a[-5..-5] = "" }.should raise_error(RangeError)
+ -> { a[-5...-5] = "" }.should raise_error(RangeError)
+ -> { a[-5..-4] = "" }.should raise_error(RangeError)
+ -> { a[-5...-4] = "" }.should raise_error(RangeError)
+ -> { a[-5..10] = "" }.should raise_error(RangeError)
+ -> { a[-5...10] = "" }.should raise_error(RangeError)
+
+ # ok
+ a[0..-9] = [1]
+ a.should == [1, 1, 2, 3, 4]
+ end
+
+ it "calls to_ary on its rhs argument for multi-element sets" do
+ obj = mock('to_ary')
+ def obj.to_ary() [1, 2, 3] end
+ ary = [1, 2]
+ ary[0, 0] = obj
+ ary.should == [1, 2, 3, 1, 2]
+ ary[1, 10] = obj
+ ary.should == [1, 1, 2, 3]
+ end
+
+ it "does not call to_ary on rhs array subclasses for multi-element sets" do
+ ary = []
+ ary[0, 0] = ArraySpecs::ToAryArray[5, 6, 7]
+ ary.should == [5, 6, 7]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array[0, 0] = [] }.should raise_error(FrozenError)
+ end
+end
+
+describe "Array#[]= with [index]" do
+ it "returns value assigned if idx is inside array" do
+ a = [1, 2, 3, 4, 5]
+ (a[3] = 6).should == 6
+ end
+
+ it "returns value assigned if idx is right beyond right array boundary" do
+ a = [1, 2, 3, 4, 5]
+ (a[5] = 6).should == 6
+ end
+
+ it "returns value assigned if idx far beyond right array boundary" do
+ a = [1, 2, 3, 4, 5]
+ (a[10] = 6).should == 6
+ end
+
+ it "sets the value of the element at index" do
+ a = [1, 2, 3, 4]
+ a[2] = 5
+ a[-1] = 6
+ a[5] = 3
+ a.should == [1, 2, 5, 6, nil, 3]
+ end
+
+ it "sets the value of the element if it is right beyond the array boundary" do
+ a = [1, 2, 3, 4]
+ a[4] = 8
+ a.should == [1, 2, 3, 4, 8]
+ end
+
+end
+
+describe "Array#[]= with [index, count]" do
+ it "returns non-array value if non-array value assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[2, 3] = 10).should == 10
+ end
+
+ it "returns array if array assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[2, 3] = [4, 5]).should == [4, 5]
+ end
+
+ it "accepts a frozen String literal as RHS" do
+ a = ['a', 'b', 'c']
+ a[0, 2] = 'd'.freeze
+ a.should == ['d', 'c']
+ end
+
+ it "just sets the section defined by [start,length] to nil even if the rhs is nil" do
+ a = ['a', 'b', 'c', 'd', 'e']
+ a[1, 3] = nil
+ a.should == ["a", nil, "e"]
+ end
+
+ it "just sets the section defined by [start,length] to nil if negative index within bounds, cnt > 0 and the rhs is nil" do
+ a = ['a', 'b', 'c', 'd', 'e']
+ a[-3, 2] = nil
+ a.should == ["a", "b", nil, "e"]
+ end
+
+ it "replaces the section defined by [start,length] to other" do
+ a = [1, 2, 3, 4, 5, 6]
+ a[0, 1] = 2
+ a[3, 2] = ['a', 'b', 'c', 'd']
+ a.should == [2, 2, 3, "a", "b", "c", "d", 6]
+ end
+
+ it "replaces the section to other if idx < 0 and cnt > 0" do
+ a = [1, 2, 3, 4, 5, 6]
+ a[-3, 2] = ["x", "y", "z"]
+ a.should == [1, 2, 3, "x", "y", "z", 6]
+ end
+
+ it "replaces the section to other even if cnt spanning beyond the array boundary" do
+ a = [1, 2, 3, 4, 5]
+ a[-1, 3] = [7, 8]
+ a.should == [1, 2, 3, 4, 7, 8]
+ end
+
+ it "pads the Array with nils if the span is past the end" do
+ a = [1, 2, 3, 4, 5]
+ a[10, 1] = [1]
+ a.should == [1, 2, 3, 4, 5, nil, nil, nil, nil, nil, 1]
+
+ b = [1, 2, 3, 4, 5]
+ b[10, 0] = [1]
+ a.should == [1, 2, 3, 4, 5, nil, nil, nil, nil, nil, 1]
+
+ c = [1, 2, 3, 4, 5]
+ c[10, 0] = []
+ c.should == [1, 2, 3, 4, 5, nil, nil, nil, nil, nil]
+ end
+
+ it "inserts other section in place defined by idx" do
+ a = [1, 2, 3, 4, 5]
+ a[3, 0] = [7, 8]
+ a.should == [1, 2, 3, 7, 8, 4, 5]
+
+ b = [1, 2, 3, 4, 5]
+ b[1, 0] = b
+ b.should == [1, 1, 2, 3, 4, 5, 2, 3, 4, 5]
+ end
+
+ it "raises an IndexError when passed start and negative length" do
+ a = [1, 2, 3, 4]
+ -> { a[-2, -1] = "" }.should raise_error(IndexError)
+ -> { a[0, -1] = "" }.should raise_error(IndexError)
+ -> { a[2, -1] = "" }.should raise_error(IndexError)
+ -> { a[4, -1] = "" }.should raise_error(IndexError)
+ -> { a[10, -1] = "" }.should raise_error(IndexError)
+ -> { [1, 2, 3, 4, 5][2, -1] = [7, 8] }.should raise_error(IndexError)
+ end
+end
+
+describe "Array#[]= with [m..n]" do
+ it "returns non-array value if non-array value assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[2..4] = 10).should == 10
+ (a.[]=(2..4, 10)).should == 10
+ end
+
+ it "returns array if array assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[2..4] = [7, 8]).should == [7, 8]
+ (a.[]=(2..4, [7, 8])).should == [7, 8]
+ end
+
+ it "just sets the section defined by range to nil even if the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[0..1] = nil
+ a.should == [nil, 3, 4, 5]
+ end
+
+ it "just sets the section defined by range to nil if m and n < 0 and the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[-3..-2] = nil
+ a.should == [1, 2, nil, 5]
+ end
+
+ it "replaces the section defined by range" do
+ a = [6, 5, 4, 3, 2, 1]
+ a[1...2] = 9
+ a[3..6] = [6, 6, 6]
+ a.should == [6, 9, 4, 6, 6, 6]
+ end
+
+ it "replaces the section if m and n < 0" do
+ a = [1, 2, 3, 4, 5]
+ a[-3..-2] = [7, 8, 9]
+ a.should == [1, 2, 7, 8, 9, 5]
+ end
+
+ it "replaces the section if m < 0 and n > 0" do
+ a = [1, 2, 3, 4, 5]
+ a[-4..3] = [8]
+ a.should == [1, 8, 5]
+ end
+
+ it "inserts the other section at m if m > n" do
+ a = [1, 2, 3, 4, 5]
+ a[3..1] = [8]
+ a.should == [1, 2, 3, 8, 4, 5]
+ end
+
+ it "inserts at the end if m > the array size" do
+ a = [1, 2, 3]
+ a[3..3] = [4]
+ a.should == [1, 2, 3, 4]
+ a[5..7] = [6]
+ a.should == [1, 2, 3, 4, nil, 6]
+ end
+
+ describe "Range subclasses" do
+ before :each do
+ @range_incl = ArraySpecs::MyRange.new(1, 2)
+ @range_excl = ArraySpecs::MyRange.new(-3, -1, true)
+ end
+
+ it "accepts Range subclasses" do
+ a = [1, 2, 3, 4]
+
+ a[@range_incl] = ["a", "b"]
+ a.should == [1, "a", "b", 4]
+ a[@range_excl] = ["A", "B"]
+ a.should == [1, "A", "B", 4]
+ end
+
+ it "returns non-array value if non-array value assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[@range_incl] = 10).should == 10
+ (a.[]=(@range_incl, 10)).should == 10
+ end
+
+ it "returns array if array assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[@range_incl] = [7, 8]).should == [7, 8]
+ a.[]=(@range_incl, [7, 8]).should == [7, 8]
+ end
+ end
+end
+
+describe "Array#[]= with [m..]" do
+ it "just sets the section defined by range to nil even if the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[eval("(2..)")] = nil
+ a.should == [1, 2, nil]
+ end
+
+ it "just sets the section defined by range to nil if m and n < 0 and the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[eval("(-3..)")] = nil
+ a.should == [1, 2, nil]
+ end
+
+ it "replaces the section defined by range" do
+ a = [6, 5, 4, 3, 2, 1]
+ a[eval("(3...)")] = 9
+ a.should == [6, 5, 4, 9]
+ a[eval("(2..)")] = [7, 7, 7]
+ a.should == [6, 5, 7, 7, 7]
+ end
+
+ it "replaces the section if m and n < 0" do
+ a = [1, 2, 3, 4, 5]
+ a[eval("(-3..)")] = [7, 8, 9]
+ a.should == [1, 2, 7, 8, 9]
+ end
+
+ it "inserts at the end if m > the array size" do
+ a = [1, 2, 3]
+ a[eval("(3..)")] = [4]
+ a.should == [1, 2, 3, 4]
+ a[eval("(5..)")] = [6]
+ a.should == [1, 2, 3, 4, nil, 6]
+ end
+end
+
+describe "Array#[]= with [..n] and [...n]" do
+ it "just sets the section defined by range to nil even if the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[(..2)] = nil
+ a.should == [nil, 4, 5]
+ a[(...2)] = nil
+ a.should == [nil, 5]
+ end
+
+ it "just sets the section defined by range to nil if n < 0 and the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[(..-3)] = nil
+ a.should == [nil, 4, 5]
+ a[(...-1)] = [nil, 5]
+ end
+
+ it "replaces the section defined by range" do
+ a = [6, 5, 4, 3, 2, 1]
+ a[(...3)] = 9
+ a.should == [9, 3, 2, 1]
+ a[(..2)] = [7, 7, 7, 7, 7]
+ a.should == [7, 7, 7, 7, 7, 1]
+ end
+
+ it "replaces the section if n < 0" do
+ a = [1, 2, 3, 4, 5]
+ a[(..-2)] = [7, 8, 9]
+ a.should == [7, 8, 9, 5]
+ end
+
+ it "replaces everything if n > the array size" do
+ a = [1, 2, 3]
+ a[(...7)] = [4]
+ a.should == [4]
+ end
+
+ it "inserts at the beginning if n < negative the array size" do
+ a = [1, 2, 3]
+ a[(..-7)] = [4]
+ a.should == [4, 1, 2, 3]
+ a[(...-10)] = [6]
+ a.should == [6, 4, 1, 2, 3]
+ end
+end
+
+describe "Array#[] after a shift" do
+ it "works for insertion" do
+ a = [1,2]
+ a.shift
+ a.shift
+ a[0,0] = [3,4]
+ a.should == [3,4]
+ end
+end
diff --git a/spec/ruby/core/array/empty_spec.rb b/spec/ruby/core/array/empty_spec.rb
new file mode 100644
index 0000000000..f70b1b6ebe
--- /dev/null
+++ b/spec/ruby/core/array/empty_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#empty?" do
+ it "returns true if the array has no elements" do
+ [].should.empty?
+ [1].should_not.empty?
+ [1, 2].should_not.empty?
+ end
+end
diff --git a/spec/ruby/core/array/eql_spec.rb b/spec/ruby/core/array/eql_spec.rb
new file mode 100644
index 0000000000..8565b94c60
--- /dev/null
+++ b/spec/ruby/core/array/eql_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/eql'
+
+describe "Array#eql?" do
+ it_behaves_like :array_eql, :eql?
+
+ it "returns false if any corresponding elements are not #eql?" do
+ [1, 2, 3, 4].should_not eql([1, 2, 3, 4.0])
+ end
+
+ it "returns false if other is not a kind of Array" do
+ obj = mock("array eql?")
+ obj.should_not_receive(:to_ary)
+ obj.should_not_receive(:eql?)
+
+ [1, 2, 3].should_not eql(obj)
+ end
+end
diff --git a/spec/ruby/core/array/equal_value_spec.rb b/spec/ruby/core/array/equal_value_spec.rb
new file mode 100644
index 0000000000..a82e07b218
--- /dev/null
+++ b/spec/ruby/core/array/equal_value_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/eql'
+
+describe "Array#==" do
+ it_behaves_like :array_eql, :==
+
+ it "compares with an equivalent Array-like object using #to_ary" do
+ obj = mock('array-like')
+ obj.should_receive(:respond_to?).at_least(1).with(:to_ary).and_return(true)
+ obj.should_receive(:==).with([1]).at_least(1).and_return(true)
+
+ ([1] == obj).should be_true
+ ([[1]] == [obj]).should be_true
+ ([[[1], 3], 2] == [[obj, 3], 2]).should be_true
+
+ # recursive arrays
+ arr1 = [[1]]
+ arr1 << arr1
+ arr2 = [obj]
+ arr2 << arr2
+ (arr1 == arr2).should be_true
+ (arr2 == arr1).should be_true
+ end
+
+ it "returns false if any corresponding elements are not #==" do
+ a = ["a", "b", "c"]
+ b = ["a", "b", "not equal value"]
+ a.should_not == b
+
+ c = mock("c")
+ c.should_receive(:==).and_return(false)
+ ["a", "b", c].should_not == a
+ end
+
+ it "returns true if corresponding elements are #==" do
+ [].should == []
+ ["a", "c", 7].should == ["a", "c", 7]
+
+ [1, 2, 3].should == [1.0, 2.0, 3.0]
+
+ obj = mock('5')
+ obj.should_receive(:==).and_return(true)
+ [obj].should == [5]
+ end
+
+ # See https://bugs.ruby-lang.org/issues/1720
+ it "returns true for [NaN] == [NaN] because Array#== first checks with #equal? and NaN.equal?(NaN) is true" do
+ [Float::NAN].should == [Float::NAN]
+ end
+end
diff --git a/spec/ruby/core/array/fetch_spec.rb b/spec/ruby/core/array/fetch_spec.rb
new file mode 100644
index 0000000000..b81c0b48d7
--- /dev/null
+++ b/spec/ruby/core/array/fetch_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#fetch" do
+ it "returns the element at the passed index" do
+ [1, 2, 3].fetch(1).should == 2
+ [nil].fetch(0).should == nil
+ end
+
+ it "counts negative indices backwards from end" do
+ [1, 2, 3, 4].fetch(-1).should == 4
+ end
+
+ it "raises an IndexError if there is no element at index" do
+ -> { [1, 2, 3].fetch(3) }.should raise_error(IndexError)
+ -> { [1, 2, 3].fetch(-4) }.should raise_error(IndexError)
+ -> { [].fetch(0) }.should raise_error(IndexError)
+ end
+
+ it "returns default if there is no element at index if passed a default value" do
+ [1, 2, 3].fetch(5, :not_found).should == :not_found
+ [1, 2, 3].fetch(5, nil).should == nil
+ [1, 2, 3].fetch(-4, :not_found).should == :not_found
+ [nil].fetch(0, :not_found).should == nil
+ end
+
+ it "returns the value of block if there is no element at index if passed a block" do
+ [1, 2, 3].fetch(9) { |i| i * i }.should == 81
+ [1, 2, 3].fetch(-9) { |i| i * i }.should == 81
+ end
+
+ it "passes the original index argument object to the block, not the converted Integer" do
+ o = mock('5')
+ def o.to_int(); 5; end
+
+ [1, 2, 3].fetch(o) { |i| i }.should equal(o)
+ end
+
+ it "gives precedence to the default block over the default argument" do
+ -> {
+ @result = [1, 2, 3].fetch(9, :foo) { |i| i * i }
+ }.should complain(/block supersedes default value argument/)
+ @result.should == 81
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ ["a", "b", "c"].fetch(obj).should == "c"
+ end
+
+ it "raises a TypeError when the passed argument can't be coerced to Integer" do
+ -> { [].fetch("cat") }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/array/fill_spec.rb b/spec/ruby/core/array/fill_spec.rb
new file mode 100644
index 0000000000..23728414be
--- /dev/null
+++ b/spec/ruby/core/array/fill_spec.rb
@@ -0,0 +1,336 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#fill" do
+ before :all do
+ @never_passed = -> i do
+ raise ExpectationNotMetError, "the control path should not pass here"
+ end
+ end
+
+ it "returns self" do
+ ary = [1, 2, 3]
+ ary.fill(:a).should equal(ary)
+ end
+
+ it "is destructive" do
+ ary = [1, 2, 3]
+ ary.fill(:a)
+ ary.should == [:a, :a, :a]
+ end
+
+ it "does not replicate the filler" do
+ ary = [1, 2, 3, 4]
+ str = "x"
+ ary.fill(str).should == [str, str, str, str]
+ str << "y"
+ ary.should == [str, str, str, str]
+ ary[0].should equal(str)
+ ary[1].should equal(str)
+ ary[2].should equal(str)
+ ary[3].should equal(str)
+ end
+
+ it "replaces all elements in the array with the filler if not given a index nor a length" do
+ ary = ['a', 'b', 'c', 'duh']
+ ary.fill(8).should == [8, 8, 8, 8]
+
+ str = "x"
+ ary.fill(str).should == [str, str, str, str]
+ end
+
+ it "replaces all elements with the value of block (index given to block)" do
+ [nil, nil, nil, nil].fill { |i| i * 2 }.should == [0, 2, 4, 6]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.fill('x') }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.fill('x') }.should raise_error(FrozenError)
+ end
+
+ it "raises an ArgumentError if 4 or more arguments are passed when no block given" do
+ -> { [].fill('a') }.should_not raise_error(ArgumentError)
+
+ -> { [].fill('a', 1) }.should_not raise_error(ArgumentError)
+
+ -> { [].fill('a', 1, 2) }.should_not raise_error(ArgumentError)
+ -> { [].fill('a', 1, 2, true) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if no argument passed and no block given" do
+ -> { [].fill }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if 3 or more arguments are passed when a block given" do
+ -> { [].fill() {|i|} }.should_not raise_error(ArgumentError)
+
+ -> { [].fill(1) {|i|} }.should_not raise_error(ArgumentError)
+
+ -> { [].fill(1, 2) {|i|} }.should_not raise_error(ArgumentError)
+ -> { [].fill(1, 2, true) {|i|} }.should raise_error(ArgumentError)
+ end
+end
+
+describe "Array#fill with (filler, index, length)" do
+ it "replaces length elements beginning with the index with the filler if given an index and a length" do
+ ary = [1, 2, 3, 4, 5, 6]
+ ary.fill('x', 2, 3).should == [1, 2, 'x', 'x', 'x', 6]
+ end
+
+ it "replaces length elements beginning with the index with the value of block" do
+ [true, false, true, false, true, false, true].fill(1, 4) { |i| i + 3 }.should == [true, 4, 5, 6, 7, false, true]
+ end
+
+ it "replaces all elements after the index if given an index and no length" do
+ ary = [1, 2, 3]
+ ary.fill('x', 1).should == [1, 'x', 'x']
+ ary.fill(1){|i| i*2}.should == [1, 2, 4]
+ end
+
+ it "replaces all elements after the index if given an index and nil as a length" do
+ a = [1, 2, 3]
+ a.fill('x', 1, nil).should == [1, 'x', 'x']
+ a.fill(1, nil){|i| i*2}.should == [1, 2, 4]
+ a.fill('y', nil).should == ['y', 'y', 'y']
+ end
+
+ it "replaces the last (-n) elements if given an index n which is negative and no length" do
+ a = [1, 2, 3, 4, 5]
+ a.fill('x', -2).should == [1, 2, 3, 'x', 'x']
+ a.fill(-2){|i| i.to_s}.should == [1, 2, 3, '3', '4']
+ end
+
+ it "replaces the last (-n) elements if given an index n which is negative and nil as a length" do
+ a = [1, 2, 3, 4, 5]
+ a.fill('x', -2, nil).should == [1, 2, 3, 'x', 'x']
+ a.fill(-2, nil){|i| i.to_s}.should == [1, 2, 3, '3', '4']
+ end
+
+ it "makes no modifications if given an index greater than end and no length" do
+ [1, 2, 3, 4, 5].fill('a', 5).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill(5, &@never_passed).should == [1, 2, 3, 4, 5]
+ end
+
+ it "makes no modifications if given an index greater than end and nil as a length" do
+ [1, 2, 3, 4, 5].fill('a', 5, nil).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill(5, nil, &@never_passed).should == [1, 2, 3, 4, 5]
+ end
+
+ it "replaces length elements beginning with start index if given an index >= 0 and a length >= 0" do
+ [1, 2, 3, 4, 5].fill('a', 2, 0).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill('a', 2, 2).should == [1, 2, "a", "a", 5]
+
+ [1, 2, 3, 4, 5].fill(2, 0, &@never_passed).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill(2, 2){|i| i*2}.should == [1, 2, 4, 6, 5]
+ end
+
+ it "increases the Array size when necessary" do
+ a = [1, 2, 3]
+ a.size.should == 3
+ a.fill 'a', 0, 10
+ a.size.should == 10
+ end
+
+ it "pads between the last element and the index with nil if given an index which is greater than size of the array" do
+ [1, 2, 3, 4, 5].fill('a', 8, 5).should == [1, 2, 3, 4, 5, nil, nil, nil, 'a', 'a', 'a', 'a', 'a']
+ [1, 2, 3, 4, 5].fill(8, 5){|i| 'a'}.should == [1, 2, 3, 4, 5, nil, nil, nil, 'a', 'a', 'a', 'a', 'a']
+ end
+
+ it "replaces length elements beginning with the (-n)th if given an index n < 0 and a length > 0" do
+ [1, 2, 3, 4, 5].fill('a', -2, 2).should == [1, 2, 3, "a", "a"]
+ [1, 2, 3, 4, 5].fill('a', -2, 4).should == [1, 2, 3, "a", "a", "a", "a"]
+
+ [1, 2, 3, 4, 5].fill(-2, 2){|i| 'a'}.should == [1, 2, 3, "a", "a"]
+ [1, 2, 3, 4, 5].fill(-2, 4){|i| 'a'}.should == [1, 2, 3, "a", "a", "a", "a"]
+ end
+
+ it "starts at 0 if the negative index is before the start of the array" do
+ [1, 2, 3, 4, 5].fill('a', -25, 3).should == ['a', 'a', 'a', 4, 5]
+ [1, 2, 3, 4, 5].fill('a', -10, 10).should == %w|a a a a a a a a a a|
+
+ [1, 2, 3, 4, 5].fill(-25, 3){|i| 'a'}.should == ['a', 'a', 'a', 4, 5]
+ [1, 2, 3, 4, 5].fill(-10, 10){|i| 'a'}.should == %w|a a a a a a a a a a|
+ end
+
+ it "makes no modifications if the given length <= 0" do
+ [1, 2, 3, 4, 5].fill('a', 2, 0).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill('a', -2, 0).should == [1, 2, 3, 4, 5]
+
+ [1, 2, 3, 4, 5].fill('a', 2, -2).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill('a', -2, -2).should == [1, 2, 3, 4, 5]
+
+ [1, 2, 3, 4, 5].fill(2, 0, &@never_passed).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill(-2, 0, &@never_passed).should == [1, 2, 3, 4, 5]
+
+ [1, 2, 3, 4, 5].fill(2, -2, &@never_passed).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill(-2, -2, &@never_passed).should == [1, 2, 3, 4, 5]
+ end
+
+ # See: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/17481
+ it "does not raise an exception if the given length is negative and its absolute value does not exceed the index" do
+ -> { [1, 2, 3, 4].fill('a', 3, -1)}.should_not raise_error(ArgumentError)
+ -> { [1, 2, 3, 4].fill('a', 3, -2)}.should_not raise_error(ArgumentError)
+ -> { [1, 2, 3, 4].fill('a', 3, -3)}.should_not raise_error(ArgumentError)
+
+ -> { [1, 2, 3, 4].fill(3, -1, &@never_passed)}.should_not raise_error(ArgumentError)
+ -> { [1, 2, 3, 4].fill(3, -2, &@never_passed)}.should_not raise_error(ArgumentError)
+ -> { [1, 2, 3, 4].fill(3, -3, &@never_passed)}.should_not raise_error(ArgumentError)
+ end
+
+ it "does not raise an exception even if the given length is negative and its absolute value exceeds the index" do
+ -> { [1, 2, 3, 4].fill('a', 3, -4)}.should_not raise_error(ArgumentError)
+ -> { [1, 2, 3, 4].fill('a', 3, -5)}.should_not raise_error(ArgumentError)
+ -> { [1, 2, 3, 4].fill('a', 3, -10000)}.should_not raise_error(ArgumentError)
+
+ -> { [1, 2, 3, 4].fill(3, -4, &@never_passed)}.should_not raise_error(ArgumentError)
+ -> { [1, 2, 3, 4].fill(3, -5, &@never_passed)}.should_not raise_error(ArgumentError)
+ -> { [1, 2, 3, 4].fill(3, -10000, &@never_passed)}.should_not raise_error(ArgumentError)
+ end
+
+ it "tries to convert the second and third arguments to Integers using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2, 2)
+ filler = mock('filler')
+ filler.should_not_receive(:to_int)
+ [1, 2, 3, 4, 5].fill(filler, obj, obj).should == [1, 2, filler, filler, 5]
+ end
+
+ it "raises a TypeError if the index is not numeric" do
+ -> { [].fill 'a', true }.should raise_error(TypeError)
+
+ obj = mock('nonnumeric')
+ -> { [].fill('a', obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the length is not numeric" do
+ -> { [1, 2, 3].fill("x", 1, "foo") }.should raise_error(TypeError, /no implicit conversion of String into Integer/)
+ -> { [1, 2, 3].fill("x", 1, :"foo") }.should raise_error(TypeError, /no implicit conversion of Symbol into Integer/)
+ -> { [1, 2, 3].fill("x", 1, Object.new) }.should raise_error(TypeError, /no implicit conversion of Object into Integer/)
+ end
+
+ not_supported_on :opal do
+ it "raises an ArgumentError or RangeError for too-large sizes" do
+ error_types = [RangeError, ArgumentError]
+ arr = [1, 2, 3]
+ -> { arr.fill(10, 1, fixnum_max) }.should raise_error { |err| error_types.should include(err.class) }
+ -> { arr.fill(10, 1, bignum_value) }.should raise_error(RangeError)
+ end
+ end
+end
+
+describe "Array#fill with (filler, range)" do
+ it "replaces elements in range with object" do
+ [1, 2, 3, 4, 5, 6].fill(8, 0..3).should == [8, 8, 8, 8, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(8, 0...3).should == [8, 8, 8, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 4..6).should == [1, 2, 3, 4, 'x', 'x', 'x']
+ [1, 2, 3, 4, 5, 6].fill('x', 4...6).should == [1, 2, 3, 4, 'x', 'x']
+ [1, 2, 3, 4, 5, 6].fill('x', -2..-1).should == [1, 2, 3, 4, 'x', 'x']
+ [1, 2, 3, 4, 5, 6].fill('x', -2...-1).should == [1, 2, 3, 4, 'x', 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -2...-2).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -2..-2).should == [1, 2, 3, 4, 'x', 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -2..0).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 0...0).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 1..1).should == [1, 'x', 3, 4, 5, 6]
+ end
+
+ it "replaces all elements in range with the value of block" do
+ [1, 1, 1, 1, 1, 1].fill(1..6) { |i| i + 1 }.should == [1, 2, 3, 4, 5, 6, 7]
+ end
+
+ it "increases the Array size when necessary" do
+ [1, 2, 3].fill('x', 1..6).should == [1, 'x', 'x', 'x', 'x', 'x', 'x']
+ [1, 2, 3].fill(1..6){|i| i+1}.should == [1, 2, 3, 4, 5, 6, 7]
+ end
+
+ it "raises a TypeError with range and length argument" do
+ -> { [].fill('x', 0 .. 2, 5) }.should raise_error(TypeError)
+ end
+
+ it "replaces elements between the (-m)th to the last and the (n+1)th from the first if given an range m..n where m < 0 and n >= 0" do
+ [1, 2, 3, 4, 5, 6].fill('x', -4..4).should == [1, 2, 'x', 'x', 'x', 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -4...4).should == [1, 2, 'x', 'x', 5, 6]
+
+ [1, 2, 3, 4, 5, 6].fill(-4..4){|i| (i+1).to_s}.should == [1, 2, '3', '4', '5', 6]
+ [1, 2, 3, 4, 5, 6].fill(-4...4){|i| (i+1).to_s}.should == [1, 2, '3', '4', 5, 6]
+ end
+
+ it "replaces elements between the (-m)th and (-n)th to the last if given an range m..n where m < 0 and n < 0" do
+ [1, 2, 3, 4, 5, 6].fill('x', -4..-2).should == [1, 2, 'x', 'x', 'x', 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -4...-2).should == [1, 2, 'x', 'x', 5, 6]
+
+ [1, 2, 3, 4, 5, 6].fill(-4..-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', '5', 6]
+ [1, 2, 3, 4, 5, 6].fill(-4...-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', 5, 6]
+ end
+
+ it "replaces elements between the (m+1)th from the first and (-n)th to the last if given an range m..n where m >= 0 and n < 0" do
+ [1, 2, 3, 4, 5, 6].fill('x', 2..-2).should == [1, 2, 'x', 'x', 'x', 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 2...-2).should == [1, 2, 'x', 'x', 5, 6]
+
+ [1, 2, 3, 4, 5, 6].fill(2..-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', '5', 6]
+ [1, 2, 3, 4, 5, 6].fill(2...-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', 5, 6]
+ end
+
+ it "makes no modifications if given an range which implies a section of zero width" do
+ [1, 2, 3, 4, 5, 6].fill('x', 2...2).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -4...2).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -4...-4).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 2...-4).should == [1, 2, 3, 4, 5, 6]
+
+ [1, 2, 3, 4, 5, 6].fill(2...2, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(-4...2, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(-4...-4, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(2...-4, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "makes no modifications if given an range which implies a section of negative width" do
+ [1, 2, 3, 4, 5, 6].fill('x', 2..1).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -4..1).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -2..-4).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 2..-5).should == [1, 2, 3, 4, 5, 6]
+
+ [1, 2, 3, 4, 5, 6].fill(2..1, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(-4..1, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(-2..-4, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(2..-5, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "raises an exception if some of the given range lies before the first of the array" do
+ -> { [1, 2, 3].fill('x', -5..-3) }.should raise_error(RangeError)
+ -> { [1, 2, 3].fill('x', -5...-3) }.should raise_error(RangeError)
+ -> { [1, 2, 3].fill('x', -5..-4) }.should raise_error(RangeError)
+
+ -> { [1, 2, 3].fill(-5..-3, &@never_passed) }.should raise_error(RangeError)
+ -> { [1, 2, 3].fill(-5...-3, &@never_passed) }.should raise_error(RangeError)
+ -> { [1, 2, 3].fill(-5..-4, &@never_passed) }.should raise_error(RangeError)
+ end
+
+ it "tries to convert the start and end of the passed range to Integers using #to_int" do
+ obj = mock('to_int')
+ def obj.<=>(rhs); rhs == self ? 0 : nil end
+ obj.should_receive(:to_int).twice.and_return(2)
+ filler = mock('filler')
+ filler.should_not_receive(:to_int)
+ [1, 2, 3, 4, 5].fill(filler, obj..obj).should == [1, 2, filler, 4, 5]
+ end
+
+ it "raises a TypeError if the start or end of the passed range is not numeric" do
+ obj = mock('nonnumeric')
+ def obj.<=>(rhs); rhs == self ? 0 : nil end
+ -> { [].fill('a', obj..obj) }.should raise_error(TypeError)
+ end
+
+ it "works with endless ranges" do
+ [1, 2, 3, 4].fill('x', eval("(1..)")).should == [1, 'x', 'x', 'x']
+ [1, 2, 3, 4].fill('x', eval("(3...)")).should == [1, 2, 3, 'x']
+ [1, 2, 3, 4].fill(eval("(1..)")) { |x| x + 2 }.should == [1, 3, 4, 5]
+ [1, 2, 3, 4].fill(eval("(3...)")) { |x| x + 2 }.should == [1, 2, 3, 5]
+ end
+
+ it "works with beginless ranges" do
+ [1, 2, 3, 4].fill('x', (..2)).should == ["x", "x", "x", 4]
+ [1, 2, 3, 4].fill((...2)) { |x| x + 2 }.should == [2, 3, 3, 4]
+ end
+end
diff --git a/spec/ruby/core/array/filter_spec.rb b/spec/ruby/core/array/filter_spec.rb
new file mode 100644
index 0000000000..156ad14f9c
--- /dev/null
+++ b/spec/ruby/core/array/filter_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'shared/select'
+
+describe "Array#filter" do
+ it_behaves_like :array_select, :filter
+end
+
+describe "Array#filter!" do
+ it "returns nil if no changes were made in the array" do
+ [1, 2, 3].filter! { true }.should be_nil
+ end
+
+ it_behaves_like :keep_if, :filter!
+end
diff --git a/spec/ruby/core/array/find_index_spec.rb b/spec/ruby/core/array/find_index_spec.rb
new file mode 100644
index 0000000000..759472024a
--- /dev/null
+++ b/spec/ruby/core/array/find_index_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/index'
+
+describe "Array#find_index" do
+ it_behaves_like :array_index, :find_index
+end
diff --git a/spec/ruby/core/array/first_spec.rb b/spec/ruby/core/array/first_spec.rb
new file mode 100644
index 0000000000..66eeba6565
--- /dev/null
+++ b/spec/ruby/core/array/first_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#first" do
+ it "returns the first element" do
+ %w{a b c}.first.should == 'a'
+ [nil].first.should == nil
+ end
+
+ it "returns nil if self is empty" do
+ [].first.should == nil
+ end
+
+ it "returns the first count elements if given a count" do
+ [true, false, true, nil, false].first(2).should == [true, false]
+ end
+
+ it "returns an empty array when passed count on an empty array" do
+ [].first(0).should == []
+ [].first(1).should == []
+ [].first(2).should == []
+ end
+
+ it "returns an empty array when passed count == 0" do
+ [1, 2, 3, 4, 5].first(0).should == []
+ end
+
+ it "returns an array containing the first element when passed count == 1" do
+ [1, 2, 3, 4, 5].first(1).should == [1]
+ end
+
+ it "raises an ArgumentError when count is negative" do
+ -> { [1, 2].first(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a RangeError when count is a Bignum" do
+ -> { [].first(bignum_value) }.should raise_error(RangeError)
+ end
+
+ it "returns the entire array when count > length" do
+ [1, 2, 3, 4, 5, 9].first(10).should == [1, 2, 3, 4, 5, 9]
+ end
+
+ it "returns an array which is independent to the original when passed count" do
+ ary = [1, 2, 3, 4, 5]
+ ary.first(0).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ ary.first(1).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ ary.first(6).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.first.should equal(empty)
+
+ ary = ArraySpecs.head_recursive_array
+ ary.first.should equal(ary)
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ [1, 2, 3, 4, 5].first(obj).should == [1, 2]
+ end
+
+ it "raises a TypeError if the passed argument is not numeric" do
+ -> { [1,2].first(nil) }.should raise_error(TypeError)
+ -> { [1,2].first("a") }.should raise_error(TypeError)
+
+ obj = mock("nonnumeric")
+ -> { [1,2].first(obj) }.should raise_error(TypeError)
+ end
+
+ it "does not return subclass instance when passed count on Array subclasses" do
+ ArraySpecs::MyArray[].first(0).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[].first(2).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2, 3].first(0).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2, 3].first(1).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2, 3].first(2).should be_an_instance_of(Array)
+ end
+
+ it "is not destructive" do
+ a = [1, 2, 3]
+ a.first
+ a.should == [1, 2, 3]
+ a.first(2)
+ a.should == [1, 2, 3]
+ a.first(3)
+ a.should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/array/fixtures/classes.rb b/spec/ruby/core/array/fixtures/classes.rb
new file mode 100644
index 0000000000..aa5fecd96b
--- /dev/null
+++ b/spec/ruby/core/array/fixtures/classes.rb
@@ -0,0 +1,584 @@
+class Object
+ # This helper is defined here rather than in MSpec because
+ # it is only used in #pack specs.
+ def pack_format(count=nil, repeat=nil)
+ format = instance_variable_get(:@method)
+ format += count.to_s unless format == 'P' || format == 'p'
+ format *= repeat if repeat
+ format.dup # because it may then become tainted
+ end
+end
+
+module ArraySpecs
+ SampleRange = 0..1000
+ SampleCount = 1000
+
+ def self.frozen_array
+ [1,2,3].freeze
+ end
+
+ def self.empty_frozen_array
+ [].freeze
+ end
+
+ def self.recursive_array
+ a = [1, 'two', 3.0]
+ 5.times { a << a }
+ a
+ end
+
+ def self.head_recursive_array
+ a = []
+ 5.times { a << a }
+ a << 1 << 'two' << 3.0
+ a
+ end
+
+ def self.empty_recursive_array
+ a = []
+ a << a
+ a
+ end
+
+ # Chi squared critical values for tests with n degrees of freedom at 99% confidence.
+ # Values obtained from NIST Engineering Statistic Handbook at
+ # https://www.itl.nist.gov/div898/handbook/eda/section3/eda3674.htm
+
+ CHI_SQUARED_CRITICAL_VALUES = [
+ 0,
+ 6.635, 9.210, 11.345, 13.277, 15.086, 16.812, 18.475, 20.090, 21.666, 23.209,
+ 24.725, 26.217, 27.688, 29.141, 30.578, 32.000, 33.409, 34.805, 36.191, 37.566,
+ 38.932, 40.289, 41.638, 42.980, 44.314, 45.642, 46.963, 48.278, 49.588, 50.892,
+ 52.191, 53.486, 54.776, 56.061, 57.342, 58.619, 59.893, 61.162, 62.428, 63.691,
+ 64.950, 66.206, 67.459, 68.710, 69.957, 71.201, 72.443, 73.683, 74.919, 76.154,
+ 77.386, 78.616, 79.843, 81.069, 82.292, 83.513, 84.733, 85.950, 87.166, 88.379,
+ 89.591, 90.802, 92.010, 93.217, 94.422, 95.626, 96.828, 98.028, 99.228, 100.425,
+ 101.621, 102.816, 104.010, 105.202, 106.393, 107.583, 108.771, 109.958, 111.144, 112.329,
+ 113.512, 114.695, 115.876, 117.057, 118.236, 119.414, 120.591, 121.767, 122.942, 124.116,
+ 125.289, 126.462, 127.633, 128.803, 129.973, 131.141, 132.309, 133.476, 134.642, 135.807,
+ ]
+
+ def self.measure_sample_fairness(size, samples, iters)
+ ary = Array.new(size) { |x| x }
+ (samples).times do |i|
+ chi_results = []
+ 3.times do
+ counts = Array.new(size) { 0 }
+ expected = iters / size
+ iters.times do
+ x = ary.sample(samples)[i]
+ counts[x] += 1
+ end
+ chi_squared = 0.0
+ counts.each do |count|
+ chi_squared += (((count - expected) ** 2) * 1.0 / expected)
+ end
+ chi_results << chi_squared
+ break if chi_squared <= CHI_SQUARED_CRITICAL_VALUES[size]
+ end
+
+ chi_results.min.should <= CHI_SQUARED_CRITICAL_VALUES[size]
+ end
+ end
+
+ def self.measure_sample_fairness_large_sample_size(size, samples, iters)
+ ary = Array.new(size) { |x| x }
+ counts = Array.new(size) { 0 }
+ expected = iters * samples / size
+ iters.times do
+ ary.sample(samples).each do |sample|
+ counts[sample] += 1
+ end
+ end
+ chi_squared = 0.0
+ counts.each do |count|
+ chi_squared += (((count - expected) ** 2) * 1.0 / expected)
+ end
+
+ # Chi squared critical values for tests with 4 degrees of freedom
+ # Values obtained from NIST Engineering Statistic Handbook at
+ # https://www.itl.nist.gov/div898/handbook/eda/section3/eda3674.htm
+
+ chi_squared.should <= CHI_SQUARED_CRITICAL_VALUES[size]
+ end
+
+ class MyArray < Array
+ # The #initialize method has a different signature than Array to help
+ # catch places in the specs that do not assert the #initialize is not
+ # called when Array methods make new instances.
+ def initialize(a, b)
+ self << a << b
+ ScratchPad.record :my_array_initialize
+ end
+ end
+
+ class Sexp < Array
+ def initialize(*args)
+ super(args)
+ end
+ end
+
+ # TODO: replace specs that use this with #should_not_receive(:to_ary)
+ # expectations on regular objects (e.g. Array instances).
+ class ToAryArray < Array
+ def to_ary() ["to_ary", "was", "called!"] end
+ end
+
+ class MyRange < Range; end
+
+ class AssocKey
+ def ==(other); other == 'it'; end
+ end
+
+ class D
+ def <=>(obj)
+ return 4 <=> obj unless obj.class == D
+ 0
+ end
+ end
+
+ class SubArray < Array
+ def initialize(*args)
+ ScratchPad.record args
+ end
+ end
+
+ class ArrayConvertible
+ attr_accessor :called
+ def initialize(*values, &block)
+ @values = values;
+ end
+
+ def to_a
+ self.called = :to_a
+ @values
+ end
+
+ def to_ary
+ self.called = :to_ary
+ @values
+ end
+ end
+
+ class SortSame
+ def <=>(other); 0; end
+ def ==(other); true; end
+ end
+
+ class UFOSceptic
+ def <=>(other); raise "N-uh, UFO:s do not exist!"; end
+ end
+
+ class MockForCompared
+ @@count = 0
+ @@compared = false
+ def initialize
+ @@compared = false
+ @order = (@@count += 1)
+ end
+ def <=>(rhs)
+ @@compared = true
+ return rhs.order <=> self.order
+ end
+ def self.compared?
+ @@compared
+ end
+
+ protected
+ attr_accessor :order
+ end
+
+ class ComparableWithInteger
+ include Comparable
+ def initialize(num)
+ @num = num
+ end
+
+ def <=>(fixnum)
+ @num <=> fixnum
+ end
+ end
+
+ class Uncomparable
+ def <=>(obj)
+ nil
+ end
+ end
+
+ def self.universal_pack_object
+ obj = mock("string float int".freeze)
+ obj.stub!(:to_int).and_return(1)
+ obj.stub!(:to_str).and_return("1")
+ obj.stub!(:to_f).and_return(1.0)
+ obj
+ end
+
+ LargeArray = ["test_create_table_with_force_true_does_not_drop_nonexisting_table",
+ "test_add_table",
+ "assert_difference",
+ "assert_operator",
+ "instance_variables",
+ "class",
+ "instance_variable_get",
+ "__class__",
+ "expects",
+ "assert_no_difference",
+ "name",
+ "assert_blank",
+ "assert_not_same",
+ "is_a?",
+ "test_add_table_with_decimals",
+ "test_create_table_with_timestamps_should_create_datetime_columns",
+ "assert_present",
+ "assert_no_match",
+ "__instance_of__",
+ "assert_deprecated",
+ "assert",
+ "assert_throws",
+ "kind_of?",
+ "try",
+ "__instance_variable_get__",
+ "object_id",
+ "timeout",
+ "instance_variable_set",
+ "assert_nothing_thrown",
+ "__instance_variable_set__",
+ "copy_object",
+ "test_create_table_with_timestamps_should_create_datetime_columns_with_options",
+ "assert_not_deprecated",
+ "assert_in_delta",
+ "id",
+ "copy_metaclass",
+ "test_create_table_without_a_block",
+ "dup",
+ "assert_not_nil",
+ "send",
+ "__instance_variables__",
+ "to_sql",
+ "mock",
+ "assert_send",
+ "instance_variable_defined?",
+ "clone",
+ "require",
+ "test_migrator",
+ "__instance_variable_defined_eh__",
+ "frozen?",
+ "test_add_column_not_null_with_default",
+ "freeze",
+ "test_migrator_one_up",
+ "test_migrator_one_down",
+ "singleton_methods",
+ "method_exists?",
+ "create_fixtures",
+ "test_migrator_one_up_one_down",
+ "test_native_decimal_insert_manual_vs_automatic",
+ "instance_exec",
+ "__is_a__",
+ "test_migrator_double_up",
+ "stub",
+ "private_methods",
+ "stubs",
+ "test_migrator_double_down",
+ "fixture_path",
+ "private_singleton_methods",
+ "stub_everything",
+ "test_migrator_one_up_with_exception_and_rollback",
+ "sequence",
+ "protected_methods",
+ "enum_for",
+ "test_finds_migrations",
+ "run_before_mocha",
+ "states",
+ "protected_singleton_methods",
+ "to_json",
+ "instance_values",
+ "==",
+ "mocha_setup",
+ "public_methods",
+ "test_finds_pending_migrations",
+ "mocha_verify",
+ "assert_kind_of",
+ "===",
+ "=~",
+ "test_relative_migrations",
+ "mocha_teardown",
+ "gem",
+ "mocha",
+ "test_only_loads_pending_migrations",
+ "test_add_column_with_precision_and_scale",
+ "require_or_load",
+ "eql?",
+ "require_dependency",
+ "test_native_types",
+ "test_target_version_zero_should_run_only_once",
+ "extend",
+ "to_matcher",
+ "unloadable",
+ "require_association",
+ "hash",
+ "__id__",
+ "load_dependency",
+ "equals",
+ "test_migrator_db_has_no_schema_migrations_table",
+ "test_migrator_verbosity",
+ "kind_of",
+ "to_yaml",
+ "to_bool",
+ "test_migrator_verbosity_off",
+ "taint",
+ "test_migrator_going_down_due_to_version_target",
+ "tainted?",
+ "mocha_inspect",
+ "test_migrator_rollback",
+ "vim",
+ "untaint",
+ "taguri=",
+ "test_migrator_forward",
+ "test_schema_migrations_table_name",
+ "test_proper_table_name",
+ "all_of",
+ "test_add_drop_table_with_prefix_and_suffix",
+ "_setup_callbacks",
+ "setup",
+ "Not",
+ "test_create_table_with_binary_column",
+ "assert_not_equal",
+ "enable_warnings",
+ "acts_like?",
+ "Rational",
+ "_removed_setup_callbacks",
+ "Table",
+ "bind",
+ "any_of",
+ "__method__",
+ "test_migrator_with_duplicates",
+ "_teardown_callbacks",
+ "method",
+ "test_migrator_with_duplicate_names",
+ "_removed_teardown_callbacks",
+ "any_parameters",
+ "test_migrator_with_missing_version_numbers",
+ "test_add_remove_single_field_using_string_arguments",
+ "test_create_table_with_custom_sequence_name",
+ "test_add_remove_single_field_using_symbol_arguments",
+ "_one_time_conditions_valid_14?",
+ "_one_time_conditions_valid_16?",
+ "run_callbacks",
+ "anything",
+ "silence_warnings",
+ "instance_variable_names",
+ "_fixture_path",
+ "copy_instance_variables_from",
+ "fixture_path?",
+ "has_entry",
+ "__marshal__",
+ "_fixture_table_names",
+ "__kind_of__",
+ "fixture_table_names?",
+ "test_add_rename",
+ "assert_equal",
+ "_fixture_class_names",
+ "fixture_class_names?",
+ "has_entries",
+ "_use_transactional_fixtures",
+ "people",
+ "test_rename_column_using_symbol_arguments",
+ "use_transactional_fixtures?",
+ "instance_eval",
+ "blank?",
+ "with_warnings",
+ "__nil__",
+ "load",
+ "metaclass",
+ "_use_instantiated_fixtures",
+ "has_key",
+ "class_eval",
+ "present?",
+ "test_rename_column",
+ "teardown",
+ "use_instantiated_fixtures?",
+ "method_name",
+ "silence_stderr",
+ "presence",
+ "test_rename_column_preserves_default_value_not_null",
+ "silence_stream",
+ "_pre_loaded_fixtures",
+ "__metaclass__",
+ "__fixnum__",
+ "pre_loaded_fixtures?",
+ "has_value",
+ "suppress",
+ "to_yaml_properties",
+ "test_rename_nonexistent_column",
+ "test_add_index",
+ "includes",
+ "find_correlate_in",
+ "equality_predicate_sql",
+ "assert_nothing_raised",
+ "let",
+ "not_predicate_sql",
+ "test_rename_column_with_sql_reserved_word",
+ "singleton_class",
+ "test_rename_column_with_an_index",
+ "display",
+ "taguri",
+ "to_yaml_style",
+ "test_remove_column_with_index",
+ "size",
+ "current_adapter?",
+ "test_remove_column_with_multi_column_index",
+ "respond_to?",
+ "test_change_type_of_not_null_column",
+ "is_a",
+ "to_a",
+ "test_rename_table_for_sqlite_should_work_with_reserved_words",
+ "require_library_or_gem",
+ "setup_fixtures",
+ "equal?",
+ "teardown_fixtures",
+ "nil?",
+ "fixture_table_names",
+ "fixture_class_names",
+ "test_create_table_without_id",
+ "use_transactional_fixtures",
+ "test_add_column_with_primary_key_attribute",
+ "repair_validations",
+ "use_instantiated_fixtures",
+ "instance_of?",
+ "test_create_table_adds_id",
+ "test_rename_table",
+ "pre_loaded_fixtures",
+ "to_enum",
+ "test_create_table_with_not_null_column",
+ "instance_of",
+ "test_change_column_nullability",
+ "optionally",
+ "test_rename_table_with_an_index",
+ "run",
+ "test_change_column",
+ "default_test",
+ "assert_raise",
+ "test_create_table_with_defaults",
+ "assert_nil",
+ "flunk",
+ "regexp_matches",
+ "duplicable?",
+ "reset_mocha",
+ "stubba_method",
+ "filter_backtrace",
+ "test_create_table_with_limits",
+ "responds_with",
+ "stubba_object",
+ "test_change_column_with_nil_default",
+ "assert_block",
+ "__show__",
+ "assert_date_from_db",
+ "__respond_to_eh__",
+ "run_in_transaction?",
+ "inspect",
+ "assert_sql",
+ "test_change_column_with_new_default",
+ "yaml_equivalent",
+ "build_message",
+ "to_s",
+ "test_change_column_default",
+ "assert_queries",
+ "pending",
+ "as_json",
+ "assert_no_queries",
+ "test_change_column_quotes_column_names",
+ "assert_match",
+ "test_keeping_default_and_notnull_constraint_on_change",
+ "methods",
+ "connection_allow_concurrency_setup",
+ "connection_allow_concurrency_teardown",
+ "test_create_table_with_primary_key_prefix_as_table_name_with_underscore",
+ "__send__",
+ "make_connection",
+ "assert_raises",
+ "tap",
+ "with_kcode",
+ "assert_instance_of",
+ "test_create_table_with_primary_key_prefix_as_table_name",
+ "assert_respond_to",
+ "test_change_column_default_to_null",
+ "assert_same",
+ "__extend__"]
+
+ LargeTestArraySorted = ["test_add_column_not_null_with_default",
+ "test_add_column_with_precision_and_scale",
+ "test_add_column_with_primary_key_attribute",
+ "test_add_drop_table_with_prefix_and_suffix",
+ "test_add_index",
+ "test_add_remove_single_field_using_string_arguments",
+ "test_add_remove_single_field_using_symbol_arguments",
+ "test_add_rename",
+ "test_add_table",
+ "test_add_table_with_decimals",
+ "test_change_column",
+ "test_change_column_default",
+ "test_change_column_default_to_null",
+ "test_change_column_nullability",
+ "test_change_column_quotes_column_names",
+ "test_change_column_with_new_default",
+ "test_change_column_with_nil_default",
+ "test_change_type_of_not_null_column",
+ "test_create_table_adds_id",
+ "test_create_table_with_binary_column",
+ "test_create_table_with_custom_sequence_name",
+ "test_create_table_with_defaults",
+ "test_create_table_with_force_true_does_not_drop_nonexisting_table",
+ "test_create_table_with_limits",
+ "test_create_table_with_not_null_column",
+ "test_create_table_with_primary_key_prefix_as_table_name",
+ "test_create_table_with_primary_key_prefix_as_table_name_with_underscore",
+ "test_create_table_with_timestamps_should_create_datetime_columns",
+ "test_create_table_with_timestamps_should_create_datetime_columns_with_options",
+ "test_create_table_without_a_block",
+ "test_create_table_without_id",
+ "test_finds_migrations",
+ "test_finds_pending_migrations",
+ "test_keeping_default_and_notnull_constraint_on_change",
+ "test_migrator",
+ "test_migrator_db_has_no_schema_migrations_table",
+ "test_migrator_double_down",
+ "test_migrator_double_up",
+ "test_migrator_forward",
+ "test_migrator_going_down_due_to_version_target",
+ "test_migrator_one_down",
+ "test_migrator_one_up",
+ "test_migrator_one_up_one_down",
+ "test_migrator_one_up_with_exception_and_rollback",
+ "test_migrator_rollback",
+ "test_migrator_verbosity",
+ "test_migrator_verbosity_off",
+ "test_migrator_with_duplicate_names",
+ "test_migrator_with_duplicates",
+ "test_migrator_with_missing_version_numbers",
+ "test_native_decimal_insert_manual_vs_automatic",
+ "test_native_types",
+ "test_only_loads_pending_migrations",
+ "test_proper_table_name",
+ "test_relative_migrations",
+ "test_remove_column_with_index",
+ "test_remove_column_with_multi_column_index",
+ "test_rename_column",
+ "test_rename_column_preserves_default_value_not_null",
+ "test_rename_column_using_symbol_arguments",
+ "test_rename_column_with_an_index",
+ "test_rename_column_with_sql_reserved_word",
+ "test_rename_nonexistent_column",
+ "test_rename_table",
+ "test_rename_table_for_sqlite_should_work_with_reserved_words",
+ "test_rename_table_with_an_index",
+ "test_schema_migrations_table_name",
+ "test_target_version_zero_should_run_only_once"]
+
+ class PrivateToAry
+ private
+
+ def to_ary
+ [1, 2, 3]
+ end
+ end
+end
diff --git a/spec/ruby/core/array/fixtures/encoded_strings.rb b/spec/ruby/core/array/fixtures/encoded_strings.rb
new file mode 100644
index 0000000000..5b85bd0e06
--- /dev/null
+++ b/spec/ruby/core/array/fixtures/encoded_strings.rb
@@ -0,0 +1,69 @@
+# encoding: utf-8
+module ArraySpecs
+ def self.array_with_usascii_and_7bit_utf8_strings
+ [
+ 'foo'.force_encoding('US-ASCII'),
+ 'bar'
+ ]
+ end
+
+ def self.array_with_usascii_and_utf8_strings
+ [
+ 'foo'.force_encoding('US-ASCII'),
+ 'báz'
+ ]
+ end
+
+ def self.array_with_7bit_utf8_and_usascii_strings
+ [
+ 'bar',
+ 'foo'.force_encoding('US-ASCII')
+ ]
+ end
+
+ def self.array_with_utf8_and_usascii_strings
+ [
+ 'báz',
+ 'bar',
+ 'foo'.force_encoding('US-ASCII')
+ ]
+ end
+
+ def self.array_with_usascii_and_utf8_strings
+ [
+ 'foo'.force_encoding('US-ASCII'),
+ 'bar',
+ 'báz'
+ ]
+ end
+
+ def self.array_with_utf8_and_7bit_binary_strings
+ [
+ 'bar',
+ 'báz',
+ 'foo'.force_encoding('BINARY')
+ ]
+ end
+
+ def self.array_with_utf8_and_binary_strings
+ [
+ 'bar',
+ 'báz',
+ [255].pack('C').force_encoding('BINARY')
+ ]
+ end
+
+ def self.array_with_usascii_and_7bit_binary_strings
+ [
+ 'bar'.force_encoding('US-ASCII'),
+ 'foo'.force_encoding('BINARY')
+ ]
+ end
+
+ def self.array_with_usascii_and_binary_strings
+ [
+ 'bar'.force_encoding('US-ASCII'),
+ [255].pack('C').force_encoding('BINARY')
+ ]
+ end
+end
diff --git a/spec/ruby/core/array/flatten_spec.rb b/spec/ruby/core/array/flatten_spec.rb
new file mode 100644
index 0000000000..1770b5389a
--- /dev/null
+++ b/spec/ruby/core/array/flatten_spec.rb
@@ -0,0 +1,278 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#flatten" do
+ it "returns a one-dimensional flattening recursively" do
+ [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []].flatten.should == [1, 2, 3, 2, 3, 4, 4, 5, 5, 1, 2, 3, 4]
+ end
+
+ it "takes an optional argument that determines the level of recursion" do
+ [ 1, 2, [3, [4, 5] ] ].flatten(1).should == [1, 2, 3, [4, 5]]
+ end
+
+ it "returns dup when the level of recursion is 0" do
+ a = [ 1, 2, [3, [4, 5] ] ]
+ a.flatten(0).should == a
+ a.flatten(0).should_not equal(a)
+ end
+
+ it "ignores negative levels" do
+ [ 1, 2, [ 3, 4, [5, 6] ] ].flatten(-1).should == [1, 2, 3, 4, 5, 6]
+ [ 1, 2, [ 3, 4, [5, 6] ] ].flatten(-10).should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "tries to convert passed Objects to Integers using #to_int" do
+ obj = mock("Converted to Integer")
+ obj.should_receive(:to_int).and_return(1)
+
+ [ 1, 2, [3, [4, 5] ] ].flatten(obj).should == [1, 2, 3, [4, 5]]
+ end
+
+ it "raises a TypeError when the passed Object can't be converted to an Integer" do
+ obj = mock("Not converted")
+ -> { [ 1, 2, [3, [4, 5] ] ].flatten(obj) }.should raise_error(TypeError)
+ end
+
+ it "does not call flatten on elements" do
+ obj = mock('[1,2]')
+ obj.should_not_receive(:flatten)
+ [obj, obj].flatten.should == [obj, obj]
+
+ obj = [5, 4]
+ obj.should_not_receive(:flatten)
+ [obj, obj].flatten.should == [5, 4, 5, 4]
+ end
+
+ it "raises an ArgumentError on recursive arrays" do
+ x = []
+ x << x
+ -> { x.flatten }.should raise_error(ArgumentError)
+
+ x = []
+ y = []
+ x << y
+ y << x
+ -> { x.flatten }.should raise_error(ArgumentError)
+ end
+
+ it "flattens any element which responds to #to_ary, using the return value of said method" do
+ x = mock("[3,4]")
+ x.should_receive(:to_ary).at_least(:once).and_return([3, 4])
+ [1, 2, x, 5].flatten.should == [1, 2, 3, 4, 5]
+
+ y = mock("MyArray[]")
+ y.should_receive(:to_ary).at_least(:once).and_return(ArraySpecs::MyArray[])
+ [y].flatten.should == []
+
+ z = mock("[2,x,y,5]")
+ z.should_receive(:to_ary).and_return([2, x, y, 5])
+ [1, z, 6].flatten.should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "does not call #to_ary on elements beyond the given level" do
+ obj = mock("1")
+ obj.should_not_receive(:to_ary)
+ [[obj]].flatten(1)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instance for Array subclasses" do
+ ArraySpecs::MyArray[].flatten.should be_an_instance_of(ArraySpecs::MyArray)
+ ArraySpecs::MyArray[1, 2, 3].flatten.should be_an_instance_of(ArraySpecs::MyArray)
+ ArraySpecs::MyArray[1, [2], 3].flatten.should be_an_instance_of(ArraySpecs::MyArray)
+ ArraySpecs::MyArray[1, [2, 3], 4].flatten.should == ArraySpecs::MyArray[1, 2, 3, 4]
+ [ArraySpecs::MyArray[1, 2, 3]].flatten.should be_an_instance_of(Array)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns Array instance for Array subclasses" do
+ ArraySpecs::MyArray[].flatten.should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2, 3].flatten.should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, [2], 3].flatten.should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, [2, 3], 4].flatten.should == [1, 2, 3, 4]
+ [ArraySpecs::MyArray[1, 2, 3]].flatten.should be_an_instance_of(Array)
+ end
+ end
+
+ it "is not destructive" do
+ ary = [1, [2, 3]]
+ ary.flatten
+ ary.should == [1, [2, 3]]
+ end
+
+ describe "with a non-Array object in the Array" do
+ before :each do
+ @obj = mock("Array#flatten")
+ ScratchPad.record []
+ end
+
+ it "does not call #to_ary if the method is not defined" do
+ [@obj].flatten.should == [@obj]
+ end
+
+ it "does not raise an exception if #to_ary returns nil" do
+ @obj.should_receive(:to_ary).and_return(nil)
+ [@obj].flatten.should == [@obj]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ @obj.should_receive(:to_ary).and_return(1)
+ -> { [@obj].flatten }.should raise_error(TypeError)
+ end
+
+ it "calls respond_to_missing?(:to_ary, true) to try coercing" do
+ def @obj.respond_to_missing?(*args) ScratchPad << args; false end
+ [@obj].flatten.should == [@obj]
+ ScratchPad.recorded.should == [[:to_ary, true]]
+ end
+
+ it "does not call #to_ary if not defined when #respond_to_missing? returns false" do
+ def @obj.respond_to_missing?(name, priv) ScratchPad << name; false end
+
+ [@obj].flatten.should == [@obj]
+ ScratchPad.recorded.should == [:to_ary]
+ end
+
+ it "calls #to_ary if not defined when #respond_to_missing? returns true" do
+ def @obj.respond_to_missing?(name, priv) ScratchPad << name; true end
+
+ -> { [@obj].flatten }.should raise_error(NoMethodError)
+ ScratchPad.recorded.should == [:to_ary]
+ end
+
+ it "calls #method_missing if defined" do
+ @obj.should_receive(:method_missing).with(:to_ary).and_return([1, 2, 3])
+ [@obj].flatten.should == [1, 2, 3]
+ end
+ end
+
+ it "performs respond_to? and method_missing-aware checks when coercing elements to array" do
+ bo = BasicObject.new
+ [bo].flatten.should == [bo]
+
+ def bo.method_missing(name, *)
+ [1,2]
+ end
+
+ [bo].flatten.should == [1,2]
+
+ def bo.respond_to?(name, *)
+ false
+ end
+
+ [bo].flatten.should == [bo]
+
+ def bo.respond_to?(name, *)
+ true
+ end
+
+ [bo].flatten.should == [1,2]
+ end
+end
+
+describe "Array#flatten!" do
+ it "modifies array to produce a one-dimensional flattening recursively" do
+ a = [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []]
+ a.flatten!
+ a.should == [1, 2, 3, 2, 3, 4, 4, 5, 5, 1, 2, 3, 4]
+ end
+
+ it "returns self if made some modifications" do
+ a = [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []]
+ a.flatten!.should equal(a)
+ end
+
+ it "returns nil if no modifications took place" do
+ a = [1, 2, 3]
+ a.flatten!.should == nil
+ a = [1, [2, 3]]
+ a.flatten!.should_not == nil
+ end
+
+ it "should not check modification by size" do
+ a = [1, 2, [3]]
+ a.flatten!.should_not == nil
+ a.should == [1, 2, 3]
+ end
+
+ it "takes an optional argument that determines the level of recursion" do
+ [ 1, 2, [3, [4, 5] ] ].flatten!(1).should == [1, 2, 3, [4, 5]]
+ end
+
+ # redmine #1440
+ it "returns nil when the level of recursion is 0" do
+ a = [ 1, 2, [3, [4, 5] ] ]
+ a.flatten!(0).should == nil
+ end
+
+ it "treats negative levels as no arguments" do
+ [ 1, 2, [ 3, 4, [5, 6] ] ].flatten!(-1).should == [1, 2, 3, 4, 5, 6]
+ [ 1, 2, [ 3, 4, [5, 6] ] ].flatten!(-10).should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "tries to convert passed Objects to Integers using #to_int" do
+ obj = mock("Converted to Integer")
+ obj.should_receive(:to_int).and_return(1)
+
+ [ 1, 2, [3, [4, 5] ] ].flatten!(obj).should == [1, 2, 3, [4, 5]]
+ end
+
+ it "raises a TypeError when the passed Object can't be converted to an Integer" do
+ obj = mock("Not converted")
+ -> { [ 1, 2, [3, [4, 5] ] ].flatten!(obj) }.should raise_error(TypeError)
+ end
+
+ it "does not call flatten! on elements" do
+ obj = mock('[1,2]')
+ obj.should_not_receive(:flatten!)
+ [obj, obj].flatten!.should == nil
+
+ obj = [5, 4]
+ obj.should_not_receive(:flatten!)
+ [obj, obj].flatten!.should == [5, 4, 5, 4]
+ end
+
+ it "raises an ArgumentError on recursive arrays" do
+ x = []
+ x << x
+ -> { x.flatten! }.should raise_error(ArgumentError)
+
+ x = []
+ y = []
+ x << y
+ y << x
+ -> { x.flatten! }.should raise_error(ArgumentError)
+ end
+
+ it "flattens any elements which responds to #to_ary, using the return value of said method" do
+ x = mock("[3,4]")
+ x.should_receive(:to_ary).at_least(:once).and_return([3, 4])
+ [1, 2, x, 5].flatten!.should == [1, 2, 3, 4, 5]
+
+ y = mock("MyArray[]")
+ y.should_receive(:to_ary).at_least(:once).and_return(ArraySpecs::MyArray[])
+ [y].flatten!.should == []
+
+ z = mock("[2,x,y,5]")
+ z.should_receive(:to_ary).and_return([2, x, y, 5])
+ [1, z, 6].flatten!.should == [1, 2, 3, 4, 5, 6]
+
+ ary = [ArraySpecs::MyArray[1, 2, 3]]
+ ary.flatten!
+ ary.should be_an_instance_of(Array)
+ ary.should == [1, 2, 3]
+ end
+
+ it "raises a FrozenError on frozen arrays when the array is modified" do
+ nested_ary = [1, 2, []]
+ nested_ary.freeze
+ -> { nested_ary.flatten! }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23663]
+ it "raises a FrozenError on frozen arrays when the array would not be modified" do
+ -> { ArraySpecs.frozen_array.flatten! }.should raise_error(FrozenError)
+ -> { ArraySpecs.empty_frozen_array.flatten! }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/frozen_spec.rb b/spec/ruby/core/array/frozen_spec.rb
new file mode 100644
index 0000000000..3ba54be46b
--- /dev/null
+++ b/spec/ruby/core/array/frozen_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#frozen?" do
+ it "returns true if array is frozen" do
+ a = [1, 2, 3]
+ a.should_not.frozen?
+ a.freeze
+ a.should.frozen?
+ end
+
+ it "returns false for an array being sorted by #sort" do
+ a = [1, 2, 3]
+ a.sort { |x,y| a.should_not.frozen?; x <=> y }
+ end
+end
diff --git a/spec/ruby/core/array/hash_spec.rb b/spec/ruby/core/array/hash_spec.rb
new file mode 100644
index 0000000000..f3bcc83fce
--- /dev/null
+++ b/spec/ruby/core/array/hash_spec.rb
@@ -0,0 +1,83 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#hash" do
+ it "returns the same fixnum for arrays with the same content" do
+ [].respond_to?(:hash).should == true
+
+ [[], [1, 2, 3]].each do |ary|
+ ary.hash.should == ary.dup.hash
+ ary.hash.should be_an_instance_of(Integer)
+ end
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ -> { empty.hash }.should_not raise_error
+
+ array = ArraySpecs.recursive_array
+ -> { array.hash }.should_not raise_error
+ end
+
+ it "returns the same hash for equal recursive arrays" do
+ rec = []; rec << rec
+ rec.hash.should == [rec].hash
+ rec.hash.should == [[rec]].hash
+ # This is because rec.eql?([[rec]])
+ # Remember that if two objects are eql?
+ # then the need to have the same hash
+ # Check the Array#eql? specs!
+ end
+
+ it "returns the same hash for equal recursive arrays through hashes" do
+ h = {} ; rec = [h] ; h[:x] = rec
+ rec.hash.should == [h].hash
+ rec.hash.should == [{x: rec}].hash
+ # Like above, this is because rec.eql?([{x: rec}])
+ end
+
+ it "calls to_int on result of calling hash on each element" do
+ ary = Array.new(5) do
+ obj = mock('0')
+ obj.should_receive(:hash).and_return(obj)
+ obj.should_receive(:to_int).and_return(0)
+ obj
+ end
+
+ ary.hash
+
+
+ hash = mock('1')
+ hash.should_receive(:to_int).and_return(1.hash)
+
+ obj = mock('@hash')
+ obj.instance_variable_set(:@hash, hash)
+ def obj.hash() @hash end
+
+ [obj].hash.should == [1].hash
+ end
+
+ it "ignores array class differences" do
+ ArraySpecs::MyArray[].hash.should == [].hash
+ ArraySpecs::MyArray[1, 2].hash.should == [1, 2].hash
+ end
+
+ it "returns same hash code for arrays with the same content" do
+ a = [1, 2, 3, 4]
+ a.fill 'a', 0..3
+ b = %w|a a a a|
+ a.hash.should == b.hash
+ end
+
+ it "returns the same value if arrays are #eql?" do
+ a = [1, 2, 3, 4]
+ a.fill 'a', 0..3
+ b = %w|a a a a|
+ a.hash.should == b.hash
+ a.should eql(b)
+ end
+
+ it "produces different hashes for nested arrays with different values and empty terminator" do
+ [1, [1, []]].hash.should_not == [2, [2, []]].hash
+ end
+end
diff --git a/spec/ruby/core/array/include_spec.rb b/spec/ruby/core/array/include_spec.rb
new file mode 100644
index 0000000000..227173218f
--- /dev/null
+++ b/spec/ruby/core/array/include_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#include?" do
+ it "returns true if object is present, false otherwise" do
+ [1, 2, "a", "b"].include?("c").should == false
+ [1, 2, "a", "b"].include?("a").should == true
+ end
+
+ it "determines presence by using element == obj" do
+ o = mock('')
+
+ [1, 2, "a", "b"].include?(o).should == false
+
+ def o.==(other); other == 'a'; end
+
+ [1, 2, o, "b"].include?('a').should == true
+
+ [1, 2.0, 3].include?(2).should == true
+ end
+
+ it "calls == on elements from left to right until success" do
+ key = "x"
+ one = mock('one')
+ two = mock('two')
+ three = mock('three')
+ one.should_receive(:==).any_number_of_times.and_return(false)
+ two.should_receive(:==).any_number_of_times.and_return(true)
+ three.should_not_receive(:==)
+ ary = [one, two, three]
+ ary.include?(key).should == true
+ end
+end
diff --git a/spec/ruby/core/array/index_spec.rb b/spec/ruby/core/array/index_spec.rb
new file mode 100644
index 0000000000..3acb7d0ef3
--- /dev/null
+++ b/spec/ruby/core/array/index_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/index'
+
+describe "Array#index" do
+ it_behaves_like :array_index, :index
+end
diff --git a/spec/ruby/core/array/initialize_spec.rb b/spec/ruby/core/array/initialize_spec.rb
new file mode 100644
index 0000000000..a8deed2b84
--- /dev/null
+++ b/spec/ruby/core/array/initialize_spec.rb
@@ -0,0 +1,156 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#initialize" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is private" do
+ Array.should have_private_instance_method("initialize")
+ end
+
+ it "is called on subclasses" do
+ b = ArraySpecs::SubArray.new :size_or_array, :obj
+
+ b.should == []
+ ScratchPad.recorded.should == [:size_or_array, :obj]
+ end
+
+ it "preserves the object's identity even when changing its value" do
+ a = [1, 2, 3]
+ a.send(:initialize).should equal(a)
+ a.should_not == [1, 2, 3]
+ end
+
+ it "raises an ArgumentError if passed 3 or more arguments" do
+ -> do
+ [1, 2].send :initialize, 1, 'x', true
+ end.should raise_error(ArgumentError)
+ -> do
+ [1, 2].send(:initialize, 1, 'x', true) {}
+ end.should raise_error(ArgumentError)
+ end
+
+ it "raises a FrozenError on frozen arrays" do
+ -> do
+ ArraySpecs.frozen_array.send :initialize
+ end.should raise_error(FrozenError)
+ -> do
+ ArraySpecs.frozen_array.send :initialize, ArraySpecs.frozen_array
+ end.should raise_error(FrozenError)
+ end
+
+ it "calls #to_ary to convert the value to an array, even if it's private" do
+ a = ArraySpecs::PrivateToAry.new
+ [].send(:initialize, a).should == [1, 2, 3]
+ end
+end
+
+describe "Array#initialize with no arguments" do
+ it "makes the array empty" do
+ [1, 2, 3].send(:initialize).should be_empty
+ end
+
+ it "does not use the given block" do
+ ->{ [1, 2, 3].send(:initialize) { raise } }.should_not raise_error
+ end
+end
+
+describe "Array#initialize with (array)" do
+ it "replaces self with the other array" do
+ b = [4, 5, 6]
+ [1, 2, 3].send(:initialize, b).should == b
+ end
+
+ it "does not use the given block" do
+ ->{ [1, 2, 3].send(:initialize) { raise } }.should_not raise_error
+ end
+
+ it "calls #to_ary to convert the value to an array" do
+ a = mock("array")
+ a.should_receive(:to_ary).and_return([1, 2])
+ a.should_not_receive(:to_int)
+ [].send(:initialize, a).should == [1, 2]
+ end
+
+ it "does not call #to_ary on instances of Array or subclasses of Array" do
+ a = [1, 2]
+ a.should_not_receive(:to_ary)
+ [].send(:initialize, a).should == a
+ end
+
+ it "raises a TypeError if an Array type argument and a default object" do
+ -> { [].send(:initialize, [1, 2], 1) }.should raise_error(TypeError)
+ end
+end
+
+describe "Array#initialize with (size, object=nil)" do
+ it "sets the array to size and fills with the object" do
+ a = []
+ obj = [3]
+ a.send(:initialize, 2, obj).should == [obj, obj]
+ a[0].should equal(obj)
+ a[1].should equal(obj)
+
+ b = []
+ b.send(:initialize, 3, 14).should == [14, 14, 14]
+ b.should == [14, 14, 14]
+ end
+
+ it "sets the array to size and fills with nil when object is omitted" do
+ [].send(:initialize, 3).should == [nil, nil, nil]
+ end
+
+ it "raises an ArgumentError if size is negative" do
+ -> { [].send(:initialize, -1, :a) }.should raise_error(ArgumentError)
+ -> { [].send(:initialize, -1) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if size is too large" do
+ -> { [].send(:initialize, fixnum_max+1) }.should raise_error(ArgumentError)
+ end
+
+ it "calls #to_int to convert the size argument to an Integer when object is given" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ [].send(:initialize, obj, :a).should == [:a]
+ end
+
+ it "calls #to_int to convert the size argument to an Integer when object is not given" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ [].send(:initialize, obj).should == [nil]
+ end
+
+ it "raises a TypeError if the size argument is not an Integer type" do
+ obj = mock('nonnumeric')
+ obj.stub!(:to_ary).and_return([1, 2])
+ ->{ [].send(:initialize, obj, :a) }.should raise_error(TypeError)
+ end
+
+ it "yields the index of the element and sets the element to the value of the block" do
+ [].send(:initialize, 3) { |i| i.to_s }.should == ['0', '1', '2']
+ end
+
+ it "uses the block value instead of using the default value" do
+ -> {
+ @result = [].send(:initialize, 3, :obj) { |i| i.to_s }
+ }.should complain(/block supersedes default value argument/)
+ @result.should == ['0', '1', '2']
+ end
+
+ it "returns the value passed to break" do
+ [].send(:initialize, 3) { break :a }.should == :a
+ end
+
+ it "sets the array to the values returned by the block before break is executed" do
+ a = [1, 2, 3]
+ a.send(:initialize, 3) do |i|
+ break if i == 2
+ i.to_s
+ end
+
+ a.should == ['0', '1']
+ end
+end
diff --git a/spec/ruby/core/array/insert_spec.rb b/spec/ruby/core/array/insert_spec.rb
new file mode 100644
index 0000000000..9e1757f68b
--- /dev/null
+++ b/spec/ruby/core/array/insert_spec.rb
@@ -0,0 +1,78 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#insert" do
+ it "returns self" do
+ ary = []
+ ary.insert(0).should equal(ary)
+ ary.insert(0, :a).should equal(ary)
+ end
+
+ it "inserts objects before the element at index for non-negative index" do
+ ary = []
+ ary.insert(0, 3).should == [3]
+ ary.insert(0, 1, 2).should == [1, 2, 3]
+ ary.insert(0).should == [1, 2, 3]
+
+ # Let's just assume insert() always modifies the array from now on.
+ ary.insert(1, 'a').should == [1, 'a', 2, 3]
+ ary.insert(0, 'b').should == ['b', 1, 'a', 2, 3]
+ ary.insert(5, 'c').should == ['b', 1, 'a', 2, 3, 'c']
+ ary.insert(7, 'd').should == ['b', 1, 'a', 2, 3, 'c', nil, 'd']
+ ary.insert(10, 5, 4).should == ['b', 1, 'a', 2, 3, 'c', nil, 'd', nil, nil, 5, 4]
+ end
+
+ it "appends objects to the end of the array for index == -1" do
+ [1, 3, 3].insert(-1, 2, 'x', 0.5).should == [1, 3, 3, 2, 'x', 0.5]
+ end
+
+ it "inserts objects after the element at index with negative index" do
+ ary = []
+ ary.insert(-1, 3).should == [3]
+ ary.insert(-2, 2).should == [2, 3]
+ ary.insert(-3, 1).should == [1, 2, 3]
+ ary.insert(-2, -3).should == [1, 2, -3, 3]
+ ary.insert(-1, []).should == [1, 2, -3, 3, []]
+ ary.insert(-2, 'x', 'y').should == [1, 2, -3, 3, 'x', 'y', []]
+ ary = [1, 2, 3]
+ end
+
+ it "pads with nils if the index to be inserted to is past the end" do
+ [].insert(5, 5).should == [nil, nil, nil, nil, nil, 5]
+ end
+
+ it "can insert before the first element with a negative index" do
+ [1, 2, 3].insert(-4, -3).should == [-3, 1, 2, 3]
+ end
+
+ it "raises an IndexError if the negative index is out of bounds" do
+ -> { [].insert(-2, 1) }.should raise_error(IndexError)
+ -> { [1].insert(-3, 2) }.should raise_error(IndexError)
+ end
+
+ it "does nothing of no object is passed" do
+ [].insert(0).should == []
+ [].insert(-1).should == []
+ [].insert(10).should == []
+ [].insert(-2).should == []
+ end
+
+ it "tries to convert the passed position argument to an Integer using #to_int" do
+ obj = mock('2')
+ obj.should_receive(:to_int).and_return(2)
+ [].insert(obj, 'x').should == [nil, nil, 'x']
+ end
+
+ it "raises an ArgumentError if no argument passed" do
+ -> { [].insert() }.should raise_error(ArgumentError)
+ end
+
+ it "raises a FrozenError on frozen arrays when the array is modified" do
+ -> { ArraySpecs.frozen_array.insert(0, 'x') }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on frozen arrays when the array would not be modified" do
+ -> { ArraySpecs.frozen_array.insert(0) }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/inspect_spec.rb b/spec/ruby/core/array/inspect_spec.rb
new file mode 100644
index 0000000000..0832224f5a
--- /dev/null
+++ b/spec/ruby/core/array/inspect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/inspect'
+
+describe "Array#inspect" do
+ it_behaves_like :array_inspect, :inspect
+end
diff --git a/spec/ruby/core/array/intersect_spec.rb b/spec/ruby/core/array/intersect_spec.rb
new file mode 100644
index 0000000000..b8c5b1e69a
--- /dev/null
+++ b/spec/ruby/core/array/intersect_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe 'Array#intersect?' do
+ ruby_version_is '3.1' do # https://bugs.ruby-lang.org/issues/15198
+ describe 'when at least one element in two Arrays is the same' do
+ it 'returns true' do
+ [1, 2].intersect?([2, 3]).should == true
+ end
+ end
+
+ describe 'when there are no elements in common between two Arrays' do
+ it 'returns false' do
+ [1, 2].intersect?([3, 4]).should == false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/intersection_spec.rb b/spec/ruby/core/array/intersection_spec.rb
new file mode 100644
index 0000000000..e01a68d389
--- /dev/null
+++ b/spec/ruby/core/array/intersection_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/intersection'
+
+describe "Array#&" do
+ it_behaves_like :array_intersection, :&
+end
+
+describe "Array#intersection" do
+ it_behaves_like :array_intersection, :intersection
+
+ it "accepts multiple arguments" do
+ [1, 2, 3, 4].intersection([1, 2, 3], [2, 3, 4]).should == [2, 3]
+ end
+
+ it "preserves elements order from original array" do
+ [1, 2, 3, 4].intersection([3, 2, 1]).should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/array/join_spec.rb b/spec/ruby/core/array/join_spec.rb
new file mode 100644
index 0000000000..e78ea6f9e1
--- /dev/null
+++ b/spec/ruby/core/array/join_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/join'
+
+describe "Array#join" do
+ it_behaves_like :array_join_with_string_separator, :join
+ it_behaves_like :array_join_with_default_separator, :join
+
+ it "does not separate elements when the passed separator is nil" do
+ [1, 2, 3].join(nil).should == '123'
+ end
+
+ it "calls #to_str to convert the separator to a String" do
+ sep = mock("separator")
+ sep.should_receive(:to_str).and_return(", ")
+ [1, 2].join(sep).should == "1, 2"
+ end
+
+ it "does not call #to_str on the separator if the array is empty" do
+ sep = mock("separator")
+ sep.should_not_receive(:to_str)
+ [].join(sep).should == ""
+ end
+
+ it "raises a TypeError if the separator cannot be coerced to a String by calling #to_str" do
+ obj = mock("not a string")
+ -> { [1, 2].join(obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed false as the separator" do
+ -> { [1, 2].join(false) }.should raise_error(TypeError)
+ end
+end
+
+describe "Array#join with $," do
+ before :each do
+ @before_separator = $,
+ end
+
+ after :each do
+ suppress_warning {$, = @before_separator}
+ end
+
+ it "separates elements with default separator when the passed separator is nil" do
+ suppress_warning {
+ $, = "_"
+ [1, 2, 3].join(nil).should == '1_2_3'
+ }
+ end
+end
diff --git a/spec/ruby/core/array/keep_if_spec.rb b/spec/ruby/core/array/keep_if_spec.rb
new file mode 100644
index 0000000000..40f7329b7c
--- /dev/null
+++ b/spec/ruby/core/array/keep_if_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/keep_if'
+
+describe "Array#keep_if" do
+ it "returns the same array if no changes were made" do
+ array = [1, 2, 3]
+ array.keep_if { true }.should equal(array)
+ end
+
+ it_behaves_like :keep_if, :keep_if
+end
diff --git a/spec/ruby/core/array/last_spec.rb b/spec/ruby/core/array/last_spec.rb
new file mode 100644
index 0000000000..d6fefada09
--- /dev/null
+++ b/spec/ruby/core/array/last_spec.rb
@@ -0,0 +1,87 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#last" do
+ it "returns the last element" do
+ [1, 1, 1, 1, 2].last.should == 2
+ end
+
+ it "returns nil if self is empty" do
+ [].last.should == nil
+ end
+
+ it "returns the last count elements if given a count" do
+ [1, 2, 3, 4, 5, 9].last(3).should == [4, 5, 9]
+ end
+
+ it "returns an empty array when passed a count on an empty array" do
+ [].last(0).should == []
+ [].last(1).should == []
+ end
+
+ it "returns an empty array when count == 0" do
+ [1, 2, 3, 4, 5].last(0).should == []
+ end
+
+ it "returns an array containing the last element when passed count == 1" do
+ [1, 2, 3, 4, 5].last(1).should == [5]
+ end
+
+ it "raises an ArgumentError when count is negative" do
+ -> { [1, 2].last(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "returns the entire array when count > length" do
+ [1, 2, 3, 4, 5, 9].last(10).should == [1, 2, 3, 4, 5, 9]
+ end
+
+ it "returns an array which is independent to the original when passed count" do
+ ary = [1, 2, 3, 4, 5]
+ ary.last(0).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ ary.last(1).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ ary.last(6).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.last.should equal(empty)
+
+ array = ArraySpecs.recursive_array
+ array.last.should equal(array)
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ [1, 2, 3, 4, 5].last(obj).should == [4, 5]
+ end
+
+ it "raises a TypeError if the passed argument is not numeric" do
+ -> { [1,2].last(nil) }.should raise_error(TypeError)
+ -> { [1,2].last("a") }.should raise_error(TypeError)
+
+ obj = mock("nonnumeric")
+ -> { [1,2].last(obj) }.should raise_error(TypeError)
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[].last(0).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[].last(2).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2, 3].last(0).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2, 3].last(1).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2, 3].last(2).should be_an_instance_of(Array)
+ end
+
+ it "is not destructive" do
+ a = [1, 2, 3]
+ a.last
+ a.should == [1, 2, 3]
+ a.last(2)
+ a.should == [1, 2, 3]
+ a.last(3)
+ a.should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/array/length_spec.rb b/spec/ruby/core/array/length_spec.rb
new file mode 100644
index 0000000000..a90c001300
--- /dev/null
+++ b/spec/ruby/core/array/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/length'
+
+describe "Array#length" do
+ it_behaves_like :array_length, :length
+end
diff --git a/spec/ruby/core/array/map_spec.rb b/spec/ruby/core/array/map_spec.rb
new file mode 100644
index 0000000000..0c7f3afa8c
--- /dev/null
+++ b/spec/ruby/core/array/map_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/collect'
+
+describe "Array#map" do
+ it_behaves_like :array_collect, :map
+end
+
+describe "Array#map!" do
+ it_behaves_like :array_collect_b, :map!
+end
diff --git a/spec/ruby/core/array/max_spec.rb b/spec/ruby/core/array/max_spec.rb
new file mode 100644
index 0000000000..d1c64519d0
--- /dev/null
+++ b/spec/ruby/core/array/max_spec.rb
@@ -0,0 +1,116 @@
+require_relative '../../spec_helper'
+
+describe "Array#max" do
+ it "is defined on Array" do
+ [1].method(:max).owner.should equal Array
+ end
+
+ it "returns nil with no values" do
+ [].max.should == nil
+ end
+
+ it "returns only element in one element array" do
+ [1].max.should == 1
+ end
+
+ it "returns largest value with multiple elements" do
+ [1,2].max.should == 2
+ [2,1].max.should == 2
+ end
+
+ describe "given a block with one argument" do
+ it "yields in turn the last length-1 values from the array" do
+ ary = []
+ result = [1,2,3,4,5].max {|x| ary << x; x}
+
+ ary.should == [2,3,4,5]
+ result.should == 5
+ end
+ end
+end
+
+# From Enumerable#max, copied for better readability
+describe "Array#max" do
+ before :each do
+ @a = [2, 4, 6, 8, 10]
+
+ @e_strs = ["333", "22", "666666", "1", "55555", "1010101010"]
+ @e_ints = [333, 22, 666666, 55555, 1010101010]
+ end
+
+ it "max should return the maximum element" do
+ [18, 42].max.should == 42
+ [2, 5, 3, 6, 1, 4].max.should == 6
+ end
+
+ it "returns the maximum element (basics cases)" do
+ [55].max.should == 55
+
+ [11,99].max.should == 99
+ [99,11].max.should == 99
+ [2, 33, 4, 11].max.should == 33
+
+ [1,2,3,4,5].max.should == 5
+ [5,4,3,2,1].max.should == 5
+ [1,4,3,5,2].max.should == 5
+ [5,5,5,5,5].max.should == 5
+
+ ["aa","tt"].max.should == "tt"
+ ["tt","aa"].max.should == "tt"
+ ["2","33","4","11"].max.should == "4"
+
+ @e_strs.max.should == "666666"
+ @e_ints.max.should == 1010101010
+ end
+
+ it "returns nil for an empty Enumerable" do
+ [].max.should == nil
+ end
+
+ it "raises a NoMethodError for elements without #<=>" do
+ -> do
+ [BasicObject.new, BasicObject.new].max
+ end.should raise_error(NoMethodError)
+ end
+
+ it "raises an ArgumentError for incomparable elements" do
+ -> do
+ [11,"22"].max
+ end.should raise_error(ArgumentError)
+ -> do
+ [11,12,22,33].max{|a, b| nil}
+ end.should raise_error(ArgumentError)
+ end
+
+ it "returns the maximum element (with block)" do
+ # with a block
+ ["2","33","4","11"].max {|a,b| a <=> b }.should == "4"
+ [ 2 , 33 , 4 , 11 ].max {|a,b| a <=> b }.should == 33
+
+ ["2","33","4","11"].max {|a,b| b <=> a }.should == "11"
+ [ 2 , 33 , 4 , 11 ].max {|a,b| b <=> a }.should == 2
+
+ @e_strs.max {|a,b| a.length <=> b.length }.should == "1010101010"
+
+ @e_strs.max {|a,b| a <=> b }.should == "666666"
+ @e_strs.max {|a,b| a.to_i <=> b.to_i }.should == "1010101010"
+
+ @e_ints.max {|a,b| a <=> b }.should == 1010101010
+ @e_ints.max {|a,b| a.to_s <=> b.to_s }.should == 666666
+ end
+
+ it "returns the minimum for enumerables that contain nils" do
+ arr = [nil, nil, true]
+ arr.max { |a, b|
+ x = a.nil? ? 1 : a ? 0 : -1
+ y = b.nil? ? 1 : b ? 0 : -1
+ x <=> y
+ }.should == nil
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = [[1,2], [3,4,5], [6,7,8,9]]
+ multi.max.should == [6, 7, 8, 9]
+ end
+
+end
diff --git a/spec/ruby/core/array/min_spec.rb b/spec/ruby/core/array/min_spec.rb
new file mode 100644
index 0000000000..3bdef0dd00
--- /dev/null
+++ b/spec/ruby/core/array/min_spec.rb
@@ -0,0 +1,121 @@
+require_relative '../../spec_helper'
+
+describe "Array#min" do
+ it "is defined on Array" do
+ [1].method(:max).owner.should equal Array
+ end
+
+ it "returns nil with no values" do
+ [].min.should == nil
+ end
+
+ it "returns only element in one element array" do
+ [1].min.should == 1
+ end
+
+ it "returns smallest value with multiple elements" do
+ [1,2].min.should == 1
+ [2,1].min.should == 1
+ end
+
+ describe "given a block with one argument" do
+ it "yields in turn the last length-1 values from the array" do
+ ary = []
+ result = [1,2,3,4,5].min {|x| ary << x; x}
+
+ ary.should == [2,3,4,5]
+ result.should == 1
+ end
+ end
+end
+
+# From Enumerable#min, copied for better readability
+describe "Array#min" do
+ before :each do
+ @a = [2, 4, 6, 8, 10]
+
+ @e_strs = ["333", "22", "666666", "1", "55555", "1010101010"]
+ @e_ints = [ 333, 22, 666666, 55555, 1010101010]
+ end
+
+ it "min should return the minimum element" do
+ [18, 42].min.should == 18
+ [2, 5, 3, 6, 1, 4].min.should == 1
+ end
+
+ it "returns the minimum (basic cases)" do
+ [55].min.should == 55
+
+ [11,99].min.should == 11
+ [99,11].min.should == 11
+ [2, 33, 4, 11].min.should == 2
+
+ [1,2,3,4,5].min.should == 1
+ [5,4,3,2,1].min.should == 1
+ [4,1,3,5,2].min.should == 1
+ [5,5,5,5,5].min.should == 5
+
+ ["aa","tt"].min.should == "aa"
+ ["tt","aa"].min.should == "aa"
+ ["2","33","4","11"].min.should == "11"
+
+ @e_strs.min.should == "1"
+ @e_ints.min.should == 22
+ end
+
+ it "returns nil for an empty Enumerable" do
+ [].min.should be_nil
+ end
+
+ it "raises a NoMethodError for elements without #<=>" do
+ -> do
+ [BasicObject.new, BasicObject.new].min
+ end.should raise_error(NoMethodError)
+ end
+
+ it "raises an ArgumentError for incomparable elements" do
+ -> do
+ [11,"22"].min
+ end.should raise_error(ArgumentError)
+ -> do
+ [11,12,22,33].min{|a, b| nil}
+ end.should raise_error(ArgumentError)
+ end
+
+ it "returns the minimum when using a block rule" do
+ ["2","33","4","11"].min {|a,b| a <=> b }.should == "11"
+ [ 2 , 33 , 4 , 11 ].min {|a,b| a <=> b }.should == 2
+
+ ["2","33","4","11"].min {|a,b| b <=> a }.should == "4"
+ [ 2 , 33 , 4 , 11 ].min {|a,b| b <=> a }.should == 33
+
+ [ 1, 2, 3, 4 ].min {|a,b| 15 }.should == 1
+
+ [11,12,22,33].min{|a, b| 2 }.should == 11
+ @i = -2
+ [11,12,22,33].min{|a, b| @i += 1 }.should == 12
+
+ @e_strs.min {|a,b| a.length <=> b.length }.should == "1"
+
+ @e_strs.min {|a,b| a <=> b }.should == "1"
+ @e_strs.min {|a,b| a.to_i <=> b.to_i }.should == "1"
+
+ @e_ints.min {|a,b| a <=> b }.should == 22
+ @e_ints.min {|a,b| a.to_s <=> b.to_s }.should == 1010101010
+ end
+
+ it "returns the minimum for enumerables that contain nils" do
+ arr = [nil, nil, true]
+ arr.min { |a, b|
+ x = a.nil? ? -1 : a ? 0 : 1
+ y = b.nil? ? -1 : b ? 0 : 1
+ x <=> y
+ }.should == nil
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = [[1,2], [3,4,5], [6,7,8,9]]
+ multi.min.should == [1, 2]
+ end
+
+end
diff --git a/spec/ruby/core/array/minmax_spec.rb b/spec/ruby/core/array/minmax_spec.rb
new file mode 100644
index 0000000000..e11fe63347
--- /dev/null
+++ b/spec/ruby/core/array/minmax_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/enumerable/minmax'
+
+describe "Array#minmax" do
+ before :each do
+ @enum = [6, 4, 5, 10, 8]
+ @empty_enum = []
+ @incomparable_enum = [BasicObject.new, BasicObject.new]
+ @incompatible_enum = [11, "22"]
+ @strs = ["333", "2", "60", "55555", "1010", "111"]
+ end
+
+ it_behaves_like :enumerable_minmax, :minmax
+end
diff --git a/spec/ruby/core/array/minus_spec.rb b/spec/ruby/core/array/minus_spec.rb
new file mode 100644
index 0000000000..cb1bf56d76
--- /dev/null
+++ b/spec/ruby/core/array/minus_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/difference'
+
+describe "Array#-" do
+ it_behaves_like :array_binary_difference, :-
+end
diff --git a/spec/ruby/core/array/multiply_spec.rb b/spec/ruby/core/array/multiply_spec.rb
new file mode 100644
index 0000000000..23d5c99f3a
--- /dev/null
+++ b/spec/ruby/core/array/multiply_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/join'
+
+describe "Array#*" do
+ it "tries to convert the passed argument to a String using #to_str" do
+ obj = mock('separator')
+ obj.should_receive(:to_str).and_return('::')
+ ([1, 2, 3, 4] * obj).should == '1::2::3::4'
+ end
+
+ it "tires to convert the passed argument to an Integer using #to_int" do
+ obj = mock('count')
+ obj.should_receive(:to_int).and_return(2)
+ ([1, 2, 3, 4] * obj).should == [1, 2, 3, 4, 1, 2, 3, 4]
+ end
+
+ it "raises a TypeError if the argument can neither be converted to a string nor an integer" do
+ obj = mock('not a string or integer')
+ ->{ [1,2] * obj }.should raise_error(TypeError)
+ end
+
+ it "converts the passed argument to a String rather than an Integer" do
+ obj = mock('2')
+ def obj.to_int() 2 end
+ def obj.to_str() "2" end
+ ([:a, :b, :c] * obj).should == "a2b2c"
+ end
+
+ it "raises a TypeError is the passed argument is nil" do
+ ->{ [1,2] * nil }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError when passed 2 or more arguments" do
+ ->{ [1,2].send(:*, 1, 2) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed no arguments" do
+ ->{ [1,2].send(:*) }.should raise_error(ArgumentError)
+ end
+end
+
+describe "Array#* with an integer" do
+ it "concatenates n copies of the array when passed an integer" do
+ ([ 1, 2, 3 ] * 0).should == []
+ ([ 1, 2, 3 ] * 1).should == [1, 2, 3]
+ ([ 1, 2, 3 ] * 3).should == [1, 2, 3, 1, 2, 3, 1, 2, 3]
+ ([] * 10).should == []
+ end
+
+ it "does not return self even if the passed integer is 1" do
+ ary = [1, 2, 3]
+ (ary * 1).should_not equal(ary)
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ (empty * 0).should == []
+ (empty * 1).should == empty
+ (empty * 3).should == [empty, empty, empty]
+
+ array = ArraySpecs.recursive_array
+ (array * 0).should == []
+ (array * 1).should == array
+ end
+
+ it "raises an ArgumentError when passed a negative integer" do
+ -> { [ 1, 2, 3 ] * -1 }.should raise_error(ArgumentError)
+ -> { [] * -1 }.should raise_error(ArgumentError)
+ end
+
+ describe "with a subclass of Array" do
+ before :each do
+ ScratchPad.clear
+
+ @array = ArraySpecs::MyArray[1, 2, 3, 4, 5]
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a subclass instance" do
+ (@array * 0).should be_an_instance_of(ArraySpecs::MyArray)
+ (@array * 1).should be_an_instance_of(ArraySpecs::MyArray)
+ (@array * 2).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns an Array instance" do
+ (@array * 0).should be_an_instance_of(Array)
+ (@array * 1).should be_an_instance_of(Array)
+ (@array * 2).should be_an_instance_of(Array)
+ end
+ end
+
+ it "does not call #initialize on the subclass instance" do
+ (@array * 2).should == [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
+ ScratchPad.recorded.should be_nil
+ end
+ end
+end
+
+describe "Array#* with a string" do
+ it_behaves_like :array_join_with_string_separator, :*
+end
diff --git a/spec/ruby/core/array/new_spec.rb b/spec/ruby/core/array/new_spec.rb
new file mode 100644
index 0000000000..96ec6b8198
--- /dev/null
+++ b/spec/ruby/core/array/new_spec.rb
@@ -0,0 +1,122 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array.new" do
+ it "returns an instance of Array" do
+ Array.new.should be_an_instance_of(Array)
+ end
+
+ it "returns an instance of a subclass" do
+ ArraySpecs::MyArray.new(1, 2).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "raises an ArgumentError if passed 3 or more arguments" do
+ -> do
+ [1, 2].send :initialize, 1, 'x', true
+ end.should raise_error(ArgumentError)
+ -> do
+ [1, 2].send(:initialize, 1, 'x', true) {}
+ end.should raise_error(ArgumentError)
+ end
+end
+
+describe "Array.new with no arguments" do
+ it "returns an empty array" do
+ Array.new.should be_empty
+ end
+
+ it "does not use the given block" do
+ ->{ Array.new { raise } }.should_not raise_error
+ end
+end
+
+describe "Array.new with (array)" do
+ it "returns an array initialized to the other array" do
+ b = [4, 5, 6]
+ Array.new(b).should == b
+ end
+
+ it "does not use the given block" do
+ ->{ Array.new([1, 2]) { raise } }.should_not raise_error
+ end
+
+ it "calls #to_ary to convert the value to an array" do
+ a = mock("array")
+ a.should_receive(:to_ary).and_return([1, 2])
+ a.should_not_receive(:to_int)
+ Array.new(a).should == [1, 2]
+ end
+
+ it "does not call #to_ary on instances of Array or subclasses of Array" do
+ a = [1, 2]
+ a.should_not_receive(:to_ary)
+ Array.new(a)
+ end
+
+ it "raises a TypeError if an Array type argument and a default object" do
+ -> { Array.new([1, 2], 1) }.should raise_error(TypeError)
+ end
+end
+
+describe "Array.new with (size, object=nil)" do
+ it "returns an array of size filled with object" do
+ obj = [3]
+ a = Array.new(2, obj)
+ a.should == [obj, obj]
+ a[0].should equal(obj)
+ a[1].should equal(obj)
+
+ Array.new(3, 14).should == [14, 14, 14]
+ end
+
+ it "returns an array of size filled with nil when object is omitted" do
+ Array.new(3).should == [nil, nil, nil]
+ end
+
+ it "raises an ArgumentError if size is negative" do
+ -> { Array.new(-1, :a) }.should raise_error(ArgumentError)
+ -> { Array.new(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if size is too large" do
+ -> { Array.new(fixnum_max+1) }.should raise_error(ArgumentError)
+ end
+
+ it "calls #to_int to convert the size argument to an Integer when object is given" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ Array.new(obj, :a).should == [:a]
+ end
+
+ it "calls #to_int to convert the size argument to an Integer when object is not given" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ Array.new(obj).should == [nil]
+ end
+
+ it "raises a TypeError if the size argument is not an Integer type" do
+ obj = mock('nonnumeric')
+ obj.stub!(:to_ary).and_return([1, 2])
+ ->{ Array.new(obj, :a) }.should raise_error(TypeError)
+ end
+
+ it "yields the index of the element and sets the element to the value of the block" do
+ Array.new(3) { |i| i.to_s }.should == ['0', '1', '2']
+ end
+
+ it "uses the block value instead of using the default value" do
+ -> {
+ @result = Array.new(3, :obj) { |i| i.to_s }
+ }.should complain(/block supersedes default value argument/)
+ @result.should == ['0', '1', '2']
+ end
+
+ it "returns the value passed to break" do
+ a = Array.new(3) do |i|
+ break if i == 2
+ i.to_s
+ end
+
+ a.should == nil
+ end
+end
diff --git a/spec/ruby/core/array/pack/a_spec.rb b/spec/ruby/core/array/pack/a_spec.rb
new file mode 100644
index 0000000000..f4a40502c2
--- /dev/null
+++ b/spec/ruby/core/array/pack/a_spec.rb
@@ -0,0 +1,73 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/string'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'A'" do
+ it_behaves_like :array_pack_basic, 'A'
+ it_behaves_like :array_pack_basic_non_float, 'A'
+ it_behaves_like :array_pack_no_platform, 'A'
+ it_behaves_like :array_pack_string, 'A'
+ it_behaves_like :array_pack_taint, 'A'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack A string")
+ obj.should_receive(:to_str).and_return("``abcdef")
+ [obj].pack("A*").should == "``abcdef"
+ end
+
+ it "will not implicitly convert a number to a string" do
+ -> { [0].pack('A') }.should raise_error(TypeError)
+ -> { [0].pack('a') }.should raise_error(TypeError)
+ end
+
+ it "adds all the bytes to the output when passed the '*' modifier" do
+ ["abc"].pack("A*").should == "abc"
+ end
+
+ it "padds the output with spaces when the count exceeds the size of the String" do
+ ["abc"].pack("A6").should == "abc "
+ end
+
+ it "adds a space when the value is nil" do
+ [nil].pack("A").should == " "
+ end
+
+ it "pads the output with spaces when the value is nil" do
+ [nil].pack("A3").should == " "
+ end
+
+ it "does not pad with spaces when passed the '*' modifier and the value is nil" do
+ [nil].pack("A*").should == ""
+ end
+end
+
+describe "Array#pack with format 'a'" do
+ it_behaves_like :array_pack_basic, 'a'
+ it_behaves_like :array_pack_basic_non_float, 'a'
+ it_behaves_like :array_pack_no_platform, 'a'
+ it_behaves_like :array_pack_string, 'a'
+ it_behaves_like :array_pack_taint, 'a'
+
+ it "adds all the bytes to the output when passed the '*' modifier" do
+ ["abc"].pack("a*").should == "abc"
+ end
+
+ it "padds the output with NULL bytes when the count exceeds the size of the String" do
+ ["abc"].pack("a6").should == "abc\x00\x00\x00"
+ end
+
+ it "adds a NULL byte when the value is nil" do
+ [nil].pack("a").should == "\x00"
+ end
+
+ it "pads the output with NULL bytes when the value is nil" do
+ [nil].pack("a3").should == "\x00\x00\x00"
+ end
+
+ it "does not pad with NULL bytes when passed the '*' modifier and the value is nil" do
+ [nil].pack("a*").should == ""
+ end
+end
diff --git a/spec/ruby/core/array/pack/at_spec.rb b/spec/ruby/core/array/pack/at_spec.rb
new file mode 100644
index 0000000000..3942677913
--- /dev/null
+++ b/spec/ruby/core/array/pack/at_spec.rb
@@ -0,0 +1,30 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+
+describe "Array#pack with format '@'" do
+ it_behaves_like :array_pack_basic, '@'
+ it_behaves_like :array_pack_basic_non_float, '@'
+ it_behaves_like :array_pack_no_platform, '@'
+
+ it "moves the insertion point to the index specified by the count modifier" do
+ [1, 2, 3, 4, 5].pack("C4@2C").should == "\x01\x02\x05"
+ end
+
+ it "does not consume any elements" do
+ [1, 2, 3].pack("C@3C").should == "\x01\x00\x00\x02"
+ end
+
+ it "extends the string with NULL bytes if the string size is less than the count" do
+ [1, 2, 3].pack("@3C*").should == "\x00\x00\x00\x01\x02\x03"
+ end
+
+ it "truncates the string if the string size is greater than the count" do
+ [1, 2, 3].pack("Cx5@2C").should == "\x01\x00\x02"
+ end
+
+ it "implicitly has a count of one when no count modifier is passed" do
+ [1, 2, 3].pack("C*@").should == "\x01"
+ end
+end
diff --git a/spec/ruby/core/array/pack/b_spec.rb b/spec/ruby/core/array/pack/b_spec.rb
new file mode 100644
index 0000000000..ec82b7d1ab
--- /dev/null
+++ b/spec/ruby/core/array/pack/b_spec.rb
@@ -0,0 +1,113 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/encodings'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'B'" do
+ it_behaves_like :array_pack_basic, 'B'
+ it_behaves_like :array_pack_basic_non_float, 'B'
+ it_behaves_like :array_pack_arguments, 'B'
+ it_behaves_like :array_pack_hex, 'B'
+ it_behaves_like :array_pack_taint, 'B'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack B string")
+ obj.should_receive(:to_str).and_return("``abcdef")
+ [obj].pack("B*").should == "\x2a"
+ end
+
+ it "will not implicitly convert a number to a string" do
+ -> { [0].pack('B') }.should raise_error(TypeError)
+ -> { [0].pack('b') }.should raise_error(TypeError)
+ end
+
+ it "encodes one bit for each character starting with the most significant bit" do
+ [ [["0"], "\x00"],
+ [["1"], "\x80"]
+ ].should be_computed_by(:pack, "B")
+ end
+
+ it "implicitly has a count of one when not passed a count modifier" do
+ ["1"].pack("B").should == "\x80"
+ end
+
+ it "implicitly has count equal to the string length when passed the '*' modifier" do
+ [ [["00101010"], "\x2a"],
+ [["00000000"], "\x00"],
+ [["11111111"], "\xff"],
+ [["10000000"], "\x80"],
+ [["00000001"], "\x01"]
+ ].should be_computed_by(:pack, "B*")
+ end
+
+ it "encodes the least significant bit of a character other than 0 or 1" do
+ [ [["bbababab"], "\x2a"],
+ [["^&#&#^#^"], "\x2a"],
+ [["(()()()("], "\x2a"],
+ [["@@%@%@%@"], "\x2a"],
+ [["ppqrstuv"], "\x2a"],
+ [["rqtvtrqp"], "\x42"]
+ ].should be_computed_by(:pack, "B*")
+ end
+
+ it "returns a binary string" do
+ ["1"].pack("B").encoding.should == Encoding::BINARY
+ end
+
+ it "encodes the string as a sequence of bytes" do
+ ["ã‚ã‚ã‚ã‚ã‚ã‚ã‚ã‚"].pack("B*").should == "\xdbm\xb6"
+ end
+end
+
+describe "Array#pack with format 'b'" do
+ it_behaves_like :array_pack_basic, 'b'
+ it_behaves_like :array_pack_basic_non_float, 'b'
+ it_behaves_like :array_pack_arguments, 'b'
+ it_behaves_like :array_pack_hex, 'b'
+ it_behaves_like :array_pack_taint, 'b'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack H string")
+ obj.should_receive(:to_str).and_return("`abcdef`")
+ [obj].pack("b*").should == "\x2a"
+ end
+
+ it "encodes one bit for each character starting with the least significant bit" do
+ [ [["0"], "\x00"],
+ [["1"], "\x01"]
+ ].should be_computed_by(:pack, "b")
+ end
+
+ it "implicitly has a count of one when not passed a count modifier" do
+ ["1"].pack("b").should == "\x01"
+ end
+
+ it "implicitly has count equal to the string length when passed the '*' modifier" do
+ [ [["0101010"], "\x2a"],
+ [["00000000"], "\x00"],
+ [["11111111"], "\xff"],
+ [["10000000"], "\x01"],
+ [["00000001"], "\x80"]
+ ].should be_computed_by(:pack, "b*")
+ end
+
+ it "encodes the least significant bit of a character other than 0 or 1" do
+ [ [["bababab"], "\x2a"],
+ [["&#&#^#^"], "\x2a"],
+ [["()()()("], "\x2a"],
+ [["@%@%@%@"], "\x2a"],
+ [["pqrstuv"], "\x2a"],
+ [["qrtrtvs"], "\x41"]
+ ].should be_computed_by(:pack, "b*")
+ end
+
+ it "returns a binary string" do
+ ["1"].pack("b").encoding.should == Encoding::BINARY
+ end
+
+ it "encodes the string as a sequence of bytes" do
+ ["ã‚ã‚ã‚ã‚ã‚ã‚ã‚ã‚"].pack("b*").should == "\xdb\xb6m"
+ end
+end
diff --git a/spec/ruby/core/array/pack/buffer_spec.rb b/spec/ruby/core/array/pack/buffer_spec.rb
new file mode 100644
index 0000000000..ecb40bfd06
--- /dev/null
+++ b/spec/ruby/core/array/pack/buffer_spec.rb
@@ -0,0 +1,50 @@
+# encoding: binary
+
+require_relative '../../../spec_helper'
+
+describe "Array#pack with :buffer option" do
+ it "returns specified buffer" do
+ n = [ 65, 66, 67 ]
+ buffer = " "*3
+ result = n.pack("ccc", buffer: buffer) #=> "ABC"
+ result.should equal(buffer)
+ end
+
+ it "adds result at the end of buffer content" do
+ n = [ 65, 66, 67 ] # result without buffer is "ABC"
+
+ buffer = ""
+ n.pack("ccc", buffer: buffer).should == "ABC"
+
+ buffer = "123"
+ n.pack("ccc", buffer: buffer).should == "123ABC"
+
+ buffer = "12345"
+ n.pack("ccc", buffer: buffer).should == "12345ABC"
+ end
+
+ it "raises TypeError exception if buffer is not String" do
+ -> { [65].pack("ccc", buffer: []) }.should raise_error(
+ TypeError, "buffer must be String, not Array")
+ end
+
+ context "offset (@) is specified" do
+ it 'keeps buffer content if it is longer than offset' do
+ n = [ 65, 66, 67 ]
+ buffer = "123456"
+ n.pack("@3ccc", buffer: buffer).should == "123ABC"
+ end
+
+ it "fills the gap with \\0 if buffer content is shorter than offset" do
+ n = [ 65, 66, 67 ]
+ buffer = "123"
+ n.pack("@6ccc", buffer: buffer).should == "123\0\0\0ABC"
+ end
+
+ it 'does not keep buffer content if it is longer than offset + result' do
+ n = [ 65, 66, 67 ]
+ buffer = "1234567890"
+ n.pack("@3ccc", buffer: buffer).should == "123ABC"
+ end
+ end
+end
diff --git a/spec/ruby/core/array/pack/c_spec.rb b/spec/ruby/core/array/pack/c_spec.rb
new file mode 100644
index 0000000000..be03551629
--- /dev/null
+++ b/spec/ruby/core/array/pack/c_spec.rb
@@ -0,0 +1,85 @@
+# -*- encoding: binary -*-
+
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+
+describe :array_pack_8bit, shared: true do
+ it "encodes the least significant eight bits of a positive number" do
+ [ [[49], "1"],
+ [[0b11111111], "\xFF"],
+ [[0b100000000], "\x00"],
+ [[0b100000001], "\x01"]
+ ].should be_computed_by(:pack, pack_format)
+ end
+
+ it "encodes the least significant eight bits of a negative number" do
+ [ [[-1], "\xFF"],
+ [[-0b10000000], "\x80"],
+ [[-0b11111111], "\x01"],
+ [[-0b100000000], "\x00"],
+ [[-0b100000001], "\xFF"]
+ ].should be_computed_by(:pack, pack_format)
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[5.2], "\x05"],
+ [[5.8], "\x05"]
+ ].should be_computed_by(:pack, pack_format)
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(5)
+ [obj].pack(pack_format).should == "\x05"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [ [[1, 2, 3], pack_format(3), "\x01\x02\x03"],
+ [[1, 2, 3], pack_format(2) + pack_format(1), "\x01\x02\x03"]
+ ].should be_computed_by(:pack)
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [1, 2, 3, 4, 5].pack(pack_format('*')).should == "\x01\x02\x03\x04\x05"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ [1, 2, 3].pack(pack_format("\000", 2)).should == "\x01\x02"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [1, 2, 3].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ [1, 2, 3].pack(pack_format(' ', 2)).should == "\x01\x02"
+ end
+end
+
+describe "Array#pack with format 'C'" do
+ it_behaves_like :array_pack_basic, 'C'
+ it_behaves_like :array_pack_basic_non_float, 'C'
+ it_behaves_like :array_pack_8bit, 'C'
+ it_behaves_like :array_pack_arguments, 'C'
+ it_behaves_like :array_pack_numeric_basic, 'C'
+ it_behaves_like :array_pack_integer, 'C'
+ it_behaves_like :array_pack_no_platform, 'C'
+end
+
+describe "Array#pack with format 'c'" do
+ it_behaves_like :array_pack_basic, 'c'
+ it_behaves_like :array_pack_basic_non_float, 'c'
+ it_behaves_like :array_pack_8bit, 'c'
+ it_behaves_like :array_pack_arguments, 'c'
+ it_behaves_like :array_pack_numeric_basic, 'c'
+ it_behaves_like :array_pack_integer, 'c'
+ it_behaves_like :array_pack_no_platform, 'c'
+end
diff --git a/spec/ruby/core/array/pack/comment_spec.rb b/spec/ruby/core/array/pack/comment_spec.rb
new file mode 100644
index 0000000000..254c827ccc
--- /dev/null
+++ b/spec/ruby/core/array/pack/comment_spec.rb
@@ -0,0 +1,25 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Array#pack" do
+ it "ignores directives text from '#' to the first newline" do
+ [1, 2, 3].pack("c#this is a comment\nc").should == "\x01\x02"
+ end
+
+ it "ignores directives text from '#' to the end if no newline is present" do
+ [1, 2, 3].pack("c#this is a comment c").should == "\x01"
+ end
+
+ it "ignores comments at the start of the directives string" do
+ [1, 2, 3].pack("#this is a comment\nc").should == "\x01"
+ end
+
+ it "ignores the entire directive string if it is a comment" do
+ [1, 2, 3].pack("#this is a comment").should == ""
+ end
+
+ it "ignores multiple comments" do
+ [1, 2, 3].pack("c#comment\nc#comment\nc#c").should == "\x01\x02\x03"
+ end
+end
diff --git a/spec/ruby/core/array/pack/d_spec.rb b/spec/ruby/core/array/pack/d_spec.rb
new file mode 100644
index 0000000000..8bb3654633
--- /dev/null
+++ b/spec/ruby/core/array/pack/d_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/float'
+
+describe "Array#pack with format 'D'" do
+ it_behaves_like :array_pack_basic, 'D'
+ it_behaves_like :array_pack_basic_float, 'D'
+ it_behaves_like :array_pack_arguments, 'D'
+ it_behaves_like :array_pack_no_platform, 'D'
+ it_behaves_like :array_pack_numeric_basic, 'D'
+ it_behaves_like :array_pack_float, 'D'
+
+ little_endian do
+ it_behaves_like :array_pack_double_le, 'D'
+ end
+
+ big_endian do
+ it_behaves_like :array_pack_double_be, 'D'
+ end
+end
+
+describe "Array#pack with format 'd'" do
+ it_behaves_like :array_pack_basic, 'd'
+ it_behaves_like :array_pack_basic_float, 'd'
+ it_behaves_like :array_pack_arguments, 'd'
+ it_behaves_like :array_pack_no_platform, 'd'
+ it_behaves_like :array_pack_numeric_basic, 'd'
+ it_behaves_like :array_pack_float, 'd'
+
+ little_endian do
+ it_behaves_like :array_pack_double_le, 'd'
+ end
+
+ big_endian do
+ it_behaves_like :array_pack_double_be, 'd'
+ end
+end
diff --git a/spec/ruby/core/array/pack/e_spec.rb b/spec/ruby/core/array/pack/e_spec.rb
new file mode 100644
index 0000000000..ab61ef578f
--- /dev/null
+++ b/spec/ruby/core/array/pack/e_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/float'
+
+describe "Array#pack with format 'E'" do
+ it_behaves_like :array_pack_basic, 'E'
+ it_behaves_like :array_pack_basic_float, 'E'
+ it_behaves_like :array_pack_arguments, 'E'
+ it_behaves_like :array_pack_no_platform, 'E'
+ it_behaves_like :array_pack_numeric_basic, 'E'
+ it_behaves_like :array_pack_float, 'E'
+ it_behaves_like :array_pack_double_le, 'E'
+end
+
+describe "Array#pack with format 'e'" do
+ it_behaves_like :array_pack_basic, 'e'
+ it_behaves_like :array_pack_basic_float, 'e'
+ it_behaves_like :array_pack_arguments, 'e'
+ it_behaves_like :array_pack_no_platform, 'e'
+ it_behaves_like :array_pack_numeric_basic, 'e'
+ it_behaves_like :array_pack_float, 'e'
+ it_behaves_like :array_pack_float_le, 'e'
+end
diff --git a/spec/ruby/core/array/pack/empty_spec.rb b/spec/ruby/core/array/pack/empty_spec.rb
new file mode 100644
index 0000000000..d635d6a563
--- /dev/null
+++ b/spec/ruby/core/array/pack/empty_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+describe "Array#pack with empty format" do
+ it "returns an empty String" do
+ [1, 2, 3].pack("").should == ""
+ end
+
+ it "returns a String with US-ASCII encoding" do
+ [1, 2, 3].pack("").encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/array/pack/f_spec.rb b/spec/ruby/core/array/pack/f_spec.rb
new file mode 100644
index 0000000000..d436e0787c
--- /dev/null
+++ b/spec/ruby/core/array/pack/f_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/float'
+
+describe "Array#pack with format 'F'" do
+ it_behaves_like :array_pack_basic, 'F'
+ it_behaves_like :array_pack_basic_float, 'F'
+ it_behaves_like :array_pack_arguments, 'F'
+ it_behaves_like :array_pack_no_platform, 'F'
+ it_behaves_like :array_pack_numeric_basic, 'F'
+ it_behaves_like :array_pack_float, 'F'
+
+ little_endian do
+ it_behaves_like :array_pack_float_le, 'F'
+ end
+
+ big_endian do
+ it_behaves_like :array_pack_float_be, 'F'
+ end
+end
+
+describe "Array#pack with format 'f'" do
+ it_behaves_like :array_pack_basic, 'f'
+ it_behaves_like :array_pack_basic_float, 'f'
+ it_behaves_like :array_pack_arguments, 'f'
+ it_behaves_like :array_pack_no_platform, 'f'
+ it_behaves_like :array_pack_numeric_basic, 'f'
+ it_behaves_like :array_pack_float, 'f'
+
+ little_endian do
+ it_behaves_like :array_pack_float_le, 'f'
+ end
+
+ big_endian do
+ it_behaves_like :array_pack_float_be, 'f'
+ end
+end
diff --git a/spec/ruby/core/array/pack/g_spec.rb b/spec/ruby/core/array/pack/g_spec.rb
new file mode 100644
index 0000000000..83b7f81acc
--- /dev/null
+++ b/spec/ruby/core/array/pack/g_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/float'
+
+describe "Array#pack with format 'G'" do
+ it_behaves_like :array_pack_basic, 'G'
+ it_behaves_like :array_pack_basic_float, 'G'
+ it_behaves_like :array_pack_arguments, 'G'
+ it_behaves_like :array_pack_no_platform, 'G'
+ it_behaves_like :array_pack_numeric_basic, 'G'
+ it_behaves_like :array_pack_float, 'G'
+ it_behaves_like :array_pack_double_be, 'G'
+end
+
+describe "Array#pack with format 'g'" do
+ it_behaves_like :array_pack_basic, 'g'
+ it_behaves_like :array_pack_basic_float, 'g'
+ it_behaves_like :array_pack_arguments, 'g'
+ it_behaves_like :array_pack_no_platform, 'g'
+ it_behaves_like :array_pack_numeric_basic, 'g'
+ it_behaves_like :array_pack_float, 'g'
+ it_behaves_like :array_pack_float_be, 'g'
+end
diff --git a/spec/ruby/core/array/pack/h_spec.rb b/spec/ruby/core/array/pack/h_spec.rb
new file mode 100644
index 0000000000..2c1dac8d4a
--- /dev/null
+++ b/spec/ruby/core/array/pack/h_spec.rb
@@ -0,0 +1,205 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/encodings'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'H'" do
+ it_behaves_like :array_pack_basic, 'H'
+ it_behaves_like :array_pack_basic_non_float, 'H'
+ it_behaves_like :array_pack_arguments, 'H'
+ it_behaves_like :array_pack_hex, 'H'
+ it_behaves_like :array_pack_taint, 'H'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack H string")
+ obj.should_receive(:to_str).and_return("a")
+ [obj].pack("H").should == "\xa0"
+ end
+
+ it "will not implicitly convert a number to a string" do
+ -> { [0].pack('H') }.should raise_error(TypeError)
+ -> { [0].pack('h') }.should raise_error(TypeError)
+ end
+
+ it "encodes the first character as the most significant nibble when passed no count modifier" do
+ ["ab"].pack("H").should == "\xa0"
+ end
+
+ it "implicitly has count equal to the string length when passed the '*' modifier" do
+ ["deadbeef"].pack("H*").should == "\xde\xad\xbe\xef"
+ end
+
+ it "encodes count nibbles when passed a count modifier exceeding the string length" do
+ ["ab"].pack('H8').should == "\xab\x00\x00\x00"
+ end
+
+ it "encodes the first character as the most significant nibble of a hex value" do
+ [ [["0"], "\x00"],
+ [["1"], "\x10"],
+ [["2"], "\x20"],
+ [["3"], "\x30"],
+ [["4"], "\x40"],
+ [["5"], "\x50"],
+ [["6"], "\x60"],
+ [["7"], "\x70"],
+ [["8"], "\x80"],
+ [["9"], "\x90"],
+ [["a"], "\xa0"],
+ [["b"], "\xb0"],
+ [["c"], "\xc0"],
+ [["d"], "\xd0"],
+ [["e"], "\xe0"],
+ [["f"], "\xf0"],
+ [["A"], "\xa0"],
+ [["B"], "\xb0"],
+ [["C"], "\xc0"],
+ [["D"], "\xd0"],
+ [["E"], "\xe0"],
+ [["F"], "\xf0"]
+ ].should be_computed_by(:pack, "H")
+ end
+
+ it "encodes the second character as the least significant nibble of a hex value" do
+ [ [["00"], "\x00"],
+ [["01"], "\x01"],
+ [["02"], "\x02"],
+ [["03"], "\x03"],
+ [["04"], "\x04"],
+ [["05"], "\x05"],
+ [["06"], "\x06"],
+ [["07"], "\x07"],
+ [["08"], "\x08"],
+ [["09"], "\x09"],
+ [["0a"], "\x0a"],
+ [["0b"], "\x0b"],
+ [["0c"], "\x0c"],
+ [["0d"], "\x0d"],
+ [["0e"], "\x0e"],
+ [["0f"], "\x0f"],
+ [["0A"], "\x0a"],
+ [["0B"], "\x0b"],
+ [["0C"], "\x0c"],
+ [["0D"], "\x0d"],
+ [["0E"], "\x0e"],
+ [["0F"], "\x0f"]
+ ].should be_computed_by(:pack, "H2")
+ end
+
+ it "encodes the least significant nibble of a non alphanumeric character as the most significant nibble of the hex value" do
+ [ [["^"], "\xe0"],
+ [["*"], "\xa0"],
+ [["#"], "\x30"],
+ [["["], "\xb0"],
+ [["]"], "\xd0"],
+ [["@"], "\x00"],
+ [["!"], "\x10"],
+ [["H"], "\x10"],
+ [["O"], "\x80"],
+ [["T"], "\xd0"],
+ [["Z"], "\x30"],
+ ].should be_computed_by(:pack, "H")
+ end
+
+ it "returns a binary string" do
+ ["41"].pack("H").encoding.should == Encoding::BINARY
+ end
+end
+
+describe "Array#pack with format 'h'" do
+ it_behaves_like :array_pack_basic, 'h'
+ it_behaves_like :array_pack_basic_non_float, 'h'
+ it_behaves_like :array_pack_arguments, 'h'
+ it_behaves_like :array_pack_hex, 'h'
+ it_behaves_like :array_pack_taint, 'h'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack H string")
+ obj.should_receive(:to_str).and_return("a")
+ [obj].pack("h").should == "\x0a"
+ end
+
+ it "encodes the first character as the least significant nibble when passed no count modifier" do
+ ["ab"].pack("h").should == "\x0a"
+ end
+
+ it "implicitly has count equal to the string length when passed the '*' modifier" do
+ ["deadbeef"].pack("h*").should == "\xed\xda\xeb\xfe"
+ end
+
+ it "encodes count nibbles when passed a count modifier exceeding the string length" do
+ ["ab"].pack('h8').should == "\xba\x00\x00\x00"
+ end
+
+ it "encodes the first character as the least significant nibble of a hex value" do
+ [ [["0"], "\x00"],
+ [["1"], "\x01"],
+ [["2"], "\x02"],
+ [["3"], "\x03"],
+ [["4"], "\x04"],
+ [["5"], "\x05"],
+ [["6"], "\x06"],
+ [["7"], "\x07"],
+ [["8"], "\x08"],
+ [["9"], "\x09"],
+ [["a"], "\x0a"],
+ [["b"], "\x0b"],
+ [["c"], "\x0c"],
+ [["d"], "\x0d"],
+ [["e"], "\x0e"],
+ [["f"], "\x0f"],
+ [["A"], "\x0a"],
+ [["B"], "\x0b"],
+ [["C"], "\x0c"],
+ [["D"], "\x0d"],
+ [["E"], "\x0e"],
+ [["F"], "\x0f"]
+ ].should be_computed_by(:pack, "h")
+ end
+
+ it "encodes the second character as the most significant nibble of a hex value" do
+ [ [["00"], "\x00"],
+ [["01"], "\x10"],
+ [["02"], "\x20"],
+ [["03"], "\x30"],
+ [["04"], "\x40"],
+ [["05"], "\x50"],
+ [["06"], "\x60"],
+ [["07"], "\x70"],
+ [["08"], "\x80"],
+ [["09"], "\x90"],
+ [["0a"], "\xa0"],
+ [["0b"], "\xb0"],
+ [["0c"], "\xc0"],
+ [["0d"], "\xd0"],
+ [["0e"], "\xe0"],
+ [["0f"], "\xf0"],
+ [["0A"], "\xa0"],
+ [["0B"], "\xb0"],
+ [["0C"], "\xc0"],
+ [["0D"], "\xd0"],
+ [["0E"], "\xe0"],
+ [["0F"], "\xf0"]
+ ].should be_computed_by(:pack, "h2")
+ end
+
+ it "encodes the least significant nibble of a non alphanumeric character as the least significant nibble of the hex value" do
+ [ [["^"], "\x0e"],
+ [["*"], "\x0a"],
+ [["#"], "\x03"],
+ [["["], "\x0b"],
+ [["]"], "\x0d"],
+ [["@"], "\x00"],
+ [["!"], "\x01"],
+ [["H"], "\x01"],
+ [["O"], "\x08"],
+ [["T"], "\x0d"],
+ [["Z"], "\x03"],
+ ].should be_computed_by(:pack, "h")
+ end
+
+ it "returns a binary string" do
+ ["41"].pack("h").encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/array/pack/i_spec.rb b/spec/ruby/core/array/pack/i_spec.rb
new file mode 100644
index 0000000000..a237071227
--- /dev/null
+++ b/spec/ruby/core/array/pack/i_spec.rb
@@ -0,0 +1,133 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'I'" do
+ it_behaves_like :array_pack_basic, 'I'
+ it_behaves_like :array_pack_basic_non_float, 'I'
+ it_behaves_like :array_pack_arguments, 'I'
+ it_behaves_like :array_pack_numeric_basic, 'I'
+ it_behaves_like :array_pack_integer, 'I'
+end
+
+describe "Array#pack with format 'i'" do
+ it_behaves_like :array_pack_basic, 'i'
+ it_behaves_like :array_pack_basic_non_float, 'i'
+ it_behaves_like :array_pack_arguments, 'i'
+ it_behaves_like :array_pack_numeric_basic, 'i'
+ it_behaves_like :array_pack_integer, 'i'
+end
+
+describe "Array#pack with format 'I'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_32bit_le, 'I<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_32bit_le, 'I<_'
+ it_behaves_like :array_pack_32bit_le, 'I_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'I<!'
+ it_behaves_like :array_pack_32bit_le, 'I!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_32bit_be, 'I>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'I>_'
+ it_behaves_like :array_pack_32bit_be, 'I_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'I>!'
+ it_behaves_like :array_pack_32bit_be, 'I!>'
+ end
+end
+
+describe "Array#pack with format 'i'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_32bit_le, 'i<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_32bit_le, 'i<_'
+ it_behaves_like :array_pack_32bit_le, 'i_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'i<!'
+ it_behaves_like :array_pack_32bit_le, 'i!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_32bit_be, 'i>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'i>_'
+ it_behaves_like :array_pack_32bit_be, 'i_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'i>!'
+ it_behaves_like :array_pack_32bit_be, 'i!>'
+ end
+end
+
+little_endian do
+ describe "Array#pack with format 'I'" do
+ it_behaves_like :array_pack_32bit_le, 'I'
+ end
+
+ describe "Array#pack with format 'I' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le_platform, 'I_'
+ end
+
+ describe "Array#pack with format 'I' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le_platform, 'I!'
+ end
+
+ describe "Array#pack with format 'i'" do
+ it_behaves_like :array_pack_32bit_le, 'i'
+ end
+
+ describe "Array#pack with format 'i' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le_platform, 'i_'
+ end
+
+ describe "Array#pack with format 'i' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le_platform, 'i!'
+ end
+end
+
+big_endian do
+ describe "Array#pack with format 'I'" do
+ it_behaves_like :array_pack_32bit_be, 'I'
+ end
+
+ describe "Array#pack with format 'I' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be_platform, 'I_'
+ end
+
+ describe "Array#pack with format 'I' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be_platform, 'I!'
+ end
+
+ describe "Array#pack with format 'i'" do
+ it_behaves_like :array_pack_32bit_be, 'i'
+ end
+
+ describe "Array#pack with format 'i' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be_platform, 'i_'
+ end
+
+ describe "Array#pack with format 'i' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be_platform, 'i!'
+ end
+end
diff --git a/spec/ruby/core/array/pack/j_spec.rb b/spec/ruby/core/array/pack/j_spec.rb
new file mode 100644
index 0000000000..7b62d5efdf
--- /dev/null
+++ b/spec/ruby/core/array/pack/j_spec.rb
@@ -0,0 +1,217 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+platform_is pointer_size: 64 do
+ describe "Array#pack with format 'J'" do
+ it_behaves_like :array_pack_basic, 'J'
+ it_behaves_like :array_pack_basic_non_float, 'J'
+ it_behaves_like :array_pack_arguments, 'J'
+ it_behaves_like :array_pack_numeric_basic, 'J'
+ it_behaves_like :array_pack_integer, 'J'
+ end
+
+ describe "Array#pack with format 'j'" do
+ it_behaves_like :array_pack_basic, 'j'
+ it_behaves_like :array_pack_basic_non_float, 'j'
+ it_behaves_like :array_pack_arguments, 'j'
+ it_behaves_like :array_pack_numeric_basic, 'j'
+ it_behaves_like :array_pack_integer, 'j'
+ end
+
+ little_endian do
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_64bit_le, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_64bit_le, 'J!'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_64bit_le, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_64bit_le, 'j!'
+ end
+ end
+ end
+
+ big_endian do
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_64bit_be, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_64bit_be, 'J!'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_64bit_be, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_64bit_be, 'j!'
+ end
+ end
+ end
+
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_64bit_le, 'J<_'
+ it_behaves_like :array_pack_64bit_le, 'J_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_64bit_le, 'J<!'
+ it_behaves_like :array_pack_64bit_le, 'J!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_64bit_be, 'J>_'
+ it_behaves_like :array_pack_64bit_be, 'J_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_64bit_be, 'J>!'
+ it_behaves_like :array_pack_64bit_be, 'J!>'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_64bit_le, 'j<_'
+ it_behaves_like :array_pack_64bit_le, 'j_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_64bit_le, 'j<!'
+ it_behaves_like :array_pack_64bit_le, 'j!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_64bit_be, 'j>_'
+ it_behaves_like :array_pack_64bit_be, 'j_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_64bit_be, 'j>!'
+ it_behaves_like :array_pack_64bit_be, 'j!>'
+ end
+ end
+end
+
+platform_is pointer_size: 32 do
+ describe "Array#pack with format 'J'" do
+ it_behaves_like :array_pack_basic, 'J'
+ it_behaves_like :array_pack_basic_non_float, 'J'
+ it_behaves_like :array_pack_arguments, 'J'
+ it_behaves_like :array_pack_numeric_basic, 'J'
+ it_behaves_like :array_pack_integer, 'J'
+ end
+
+ describe "Array#pack with format 'j'" do
+ it_behaves_like :array_pack_basic, 'j'
+ it_behaves_like :array_pack_basic_non_float, 'j'
+ it_behaves_like :array_pack_arguments, 'j'
+ it_behaves_like :array_pack_numeric_basic, 'j'
+ it_behaves_like :array_pack_integer, 'j'
+ end
+
+ big_endian do
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be, 'J!'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be, 'j!'
+ end
+ end
+ end
+
+ little_endian do
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le, 'J!'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le, 'j!'
+ end
+ end
+ end
+
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_32bit_le, 'J<_'
+ it_behaves_like :array_pack_32bit_le, 'J_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'J<!'
+ it_behaves_like :array_pack_32bit_le, 'J!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'J>_'
+ it_behaves_like :array_pack_32bit_be, 'J_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'J>!'
+ it_behaves_like :array_pack_32bit_be, 'J!>'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_32bit_le, 'j<_'
+ it_behaves_like :array_pack_32bit_le, 'j_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'j<!'
+ it_behaves_like :array_pack_32bit_le, 'j!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'j>_'
+ it_behaves_like :array_pack_32bit_be, 'j_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'j>!'
+ it_behaves_like :array_pack_32bit_be, 'j!>'
+ end
+ end
+end
diff --git a/spec/ruby/core/array/pack/l_spec.rb b/spec/ruby/core/array/pack/l_spec.rb
new file mode 100644
index 0000000000..b446a7a36a
--- /dev/null
+++ b/spec/ruby/core/array/pack/l_spec.rb
@@ -0,0 +1,221 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'L'" do
+ it_behaves_like :array_pack_basic, 'L'
+ it_behaves_like :array_pack_basic_non_float, 'L'
+ it_behaves_like :array_pack_arguments, 'L'
+ it_behaves_like :array_pack_numeric_basic, 'L'
+ it_behaves_like :array_pack_integer, 'L'
+end
+
+describe "Array#pack with format 'l'" do
+ it_behaves_like :array_pack_basic, 'l'
+ it_behaves_like :array_pack_basic_non_float, 'l'
+ it_behaves_like :array_pack_arguments, 'l'
+ it_behaves_like :array_pack_numeric_basic, 'l'
+ it_behaves_like :array_pack_integer, 'l'
+end
+
+describe "Array#pack with format 'L'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_32bit_le, 'L<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_32bit_be, 'L>'
+ end
+
+ platform_is wordsize: 32 do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_32bit_le, 'L<_'
+ it_behaves_like :array_pack_32bit_le, 'L_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'L<!'
+ it_behaves_like :array_pack_32bit_le, 'L!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'L>_'
+ it_behaves_like :array_pack_32bit_be, 'L_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'L>!'
+ it_behaves_like :array_pack_32bit_be, 'L!>'
+ end
+ end
+
+ platform_is wordsize: 64 do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_64bit_le, 'L<_'
+ it_behaves_like :array_pack_64bit_le, 'L_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_64bit_le, 'L<!'
+ it_behaves_like :array_pack_64bit_le, 'L!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_64bit_be, 'L>_'
+ it_behaves_like :array_pack_64bit_be, 'L_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_64bit_be, 'L>!'
+ it_behaves_like :array_pack_64bit_be, 'L!>'
+ end
+ end
+end
+
+describe "Array#pack with format 'l'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_32bit_le, 'l<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_32bit_be, 'l>'
+ end
+
+ platform_is wordsize: 32 do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_32bit_le, 'l<_'
+ it_behaves_like :array_pack_32bit_le, 'l_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'l<!'
+ it_behaves_like :array_pack_32bit_le, 'l!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'l>_'
+ it_behaves_like :array_pack_32bit_be, 'l_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'l>!'
+ it_behaves_like :array_pack_32bit_be, 'l!>'
+ end
+ end
+
+ platform_is wordsize: 64 do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_64bit_le, 'l<_'
+ it_behaves_like :array_pack_64bit_le, 'l_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_64bit_le, 'l<!'
+ it_behaves_like :array_pack_64bit_le, 'l!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_64bit_be, 'l>_'
+ it_behaves_like :array_pack_64bit_be, 'l_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_64bit_be, 'l>!'
+ it_behaves_like :array_pack_64bit_be, 'l!>'
+ end
+ end
+end
+
+little_endian do
+ describe "Array#pack with format 'L'" do
+ it_behaves_like :array_pack_32bit_le, 'L'
+ end
+
+ describe "Array#pack with format 'l'" do
+ it_behaves_like :array_pack_32bit_le, 'l'
+ end
+
+ platform_is wordsize: 32 do
+ describe "Array#pack with format 'L' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le, 'L_'
+ end
+
+ describe "Array#pack with format 'L' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le, 'L!'
+ end
+
+ describe "Array#pack with format 'l' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le, 'l_'
+ end
+
+ describe "Array#pack with format 'l' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le, 'l!'
+ end
+ end
+
+ platform_is wordsize: 64 do
+ describe "Array#pack with format 'L' with modifier '_'" do
+ it_behaves_like :array_pack_64bit_le, 'L_'
+ end
+
+ describe "Array#pack with format 'L' with modifier '!'" do
+ it_behaves_like :array_pack_64bit_le, 'L!'
+ end
+
+ describe "Array#pack with format 'l' with modifier '_'" do
+ it_behaves_like :array_pack_64bit_le, 'l_'
+ end
+
+ describe "Array#pack with format 'l' with modifier '!'" do
+ it_behaves_like :array_pack_64bit_le, 'l!'
+ end
+ end
+end
+
+big_endian do
+ describe "Array#pack with format 'L'" do
+ it_behaves_like :array_pack_32bit_be, 'L'
+ end
+
+ describe "Array#pack with format 'l'" do
+ it_behaves_like :array_pack_32bit_be, 'l'
+ end
+
+ platform_is wordsize: 32 do
+ describe "Array#pack with format 'L' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be, 'L_'
+ end
+
+ describe "Array#pack with format 'L' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be, 'L!'
+ end
+
+ describe "Array#pack with format 'l' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be, 'l_'
+ end
+
+ describe "Array#pack with format 'l' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be, 'l!'
+ end
+ end
+
+ platform_is wordsize: 64 do
+ describe "Array#pack with format 'L' with modifier '_'" do
+ it_behaves_like :array_pack_64bit_be, 'L_'
+ end
+
+ describe "Array#pack with format 'L' with modifier '!'" do
+ it_behaves_like :array_pack_64bit_be, 'L!'
+ end
+
+ describe "Array#pack with format 'l' with modifier '_'" do
+ it_behaves_like :array_pack_64bit_be, 'l_'
+ end
+
+ describe "Array#pack with format 'l' with modifier '!'" do
+ it_behaves_like :array_pack_64bit_be, 'l!'
+ end
+ end
+end
diff --git a/spec/ruby/core/array/pack/m_spec.rb b/spec/ruby/core/array/pack/m_spec.rb
new file mode 100644
index 0000000000..c6364af12d
--- /dev/null
+++ b/spec/ruby/core/array/pack/m_spec.rb
@@ -0,0 +1,317 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'M'" do
+ it_behaves_like :array_pack_basic, 'M'
+ it_behaves_like :array_pack_basic_non_float, 'M'
+ it_behaves_like :array_pack_arguments, 'M'
+ it_behaves_like :array_pack_taint, 'M'
+
+ it "encodes an empty string as an empty string" do
+ [""].pack("M").should == ""
+ end
+
+ it "encodes nil as an empty string" do
+ [nil].pack("M").should == ""
+ end
+
+ it "appends a soft line break at the end of an encoded string" do
+ ["a"].pack("M").should == "a=\n"
+ end
+
+ it "does not append a soft break if the string ends with a newline" do
+ ["a\n"].pack("M").should == "a\n"
+ end
+
+ it "encodes one element for each directive" do
+ ["a", "b", "c"].pack("MM").should == "a=\nb=\n"
+ end
+
+ it "encodes byte values 33..60 directly" do
+ [ [["!\"\#$%&'()*+,-./"], "!\"\#$%&'()*+,-./=\n"],
+ [["0123456789"], "0123456789=\n"],
+ [[":;<"], ":;<=\n"]
+ ].should be_computed_by(:pack, "M")
+ end
+
+ it "encodes byte values 62..126 directly" do
+ [ [[">?@"], ">?@=\n"],
+ [["ABCDEFGHIJKLMNOPQRSTUVWXYZ"], "ABCDEFGHIJKLMNOPQRSTUVWXYZ=\n"],
+ [["[\\]^_`"], "[\\]^_`=\n"],
+ [["abcdefghijklmnopqrstuvwxyz"], "abcdefghijklmnopqrstuvwxyz=\n"],
+ [["{|}~"], "{|}~=\n"]
+ ].should be_computed_by(:pack, "M")
+ end
+
+ it "encodes an '=' character in hex format" do
+ ["="].pack("M").should == "=3D=\n"
+ end
+
+ it "encodes an embedded space directly" do
+ ["a b"].pack("M").should == "a b=\n"
+ end
+
+ it "encodes a space at the end of the string directly" do
+ ["a "].pack("M").should == "a =\n"
+ end
+
+ it "encodes an embedded tab directly" do
+ ["a\tb"].pack("M").should == "a\tb=\n"
+ end
+
+ it "encodes a tab at the end of the string directly" do
+ ["a\t"].pack("M").should == "a\t=\n"
+ end
+
+ it "encodes an embedded newline directly" do
+ ["a\nb"].pack("M").should == "a\nb=\n"
+ end
+
+ it "encodes 0..31 except tab and newline in hex format" do
+ [ [["\x00\x01\x02\x03\x04\x05\x06"], "=00=01=02=03=04=05=06=\n"],
+ [["\a\b\v\f\r"], "=07=08=0B=0C=0D=\n"],
+ [["\x0e\x0f\x10\x11\x12\x13\x14"], "=0E=0F=10=11=12=13=14=\n"],
+ [["\x15\x16\x17\x18\x19\x1a"], "=15=16=17=18=19=1A=\n"],
+ [["\e"], "=1B=\n"],
+ [["\x1c\x1d\x1e\x1f"], "=1C=1D=1E=1F=\n"]
+ ].should be_computed_by(:pack, "M")
+ end
+
+ it "encodes a tab at the end of a line with an encoded newline" do
+ ["\t"].pack("M").should == "\t=\n"
+ ["\t\n"].pack("M").should == "\t=\n\n"
+ ["abc\t\nxyz"].pack("M").should == "abc\t=\n\nxyz=\n"
+ end
+
+ it "encodes a space at the end of a line with an encoded newline" do
+ [" "].pack("M").should == " =\n"
+ [" \n"].pack("M").should == " =\n\n"
+ ["abc \nxyz"].pack("M").should == "abc =\n\nxyz=\n"
+ end
+
+ it "encodes 127..255 in hex format" do
+ [ [["\x7f\x80\x81\x82\x83\x84\x85\x86"], "=7F=80=81=82=83=84=85=86=\n"],
+ [["\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"], "=87=88=89=8A=8B=8C=8D=8E=\n"],
+ [["\x8f\x90\x91\x92\x93\x94\x95\x96"], "=8F=90=91=92=93=94=95=96=\n"],
+ [["\x97\x98\x99\x9a\x9b\x9c\x9d\x9e"], "=97=98=99=9A=9B=9C=9D=9E=\n"],
+ [["\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6"], "=9F=A0=A1=A2=A3=A4=A5=A6=\n"],
+ [["\xa7\xa8\xa9\xaa\xab\xac\xad\xae"], "=A7=A8=A9=AA=AB=AC=AD=AE=\n"],
+ [["\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6"], "=AF=B0=B1=B2=B3=B4=B5=B6=\n"],
+ [["\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe"], "=B7=B8=B9=BA=BB=BC=BD=BE=\n"],
+ [["\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6"], "=BF=C0=C1=C2=C3=C4=C5=C6=\n"],
+ [["\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce"], "=C7=C8=C9=CA=CB=CC=CD=CE=\n"],
+ [["\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6"], "=CF=D0=D1=D2=D3=D4=D5=D6=\n"],
+ [["\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde"], "=D7=D8=D9=DA=DB=DC=DD=DE=\n"],
+ [["\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6"], "=DF=E0=E1=E2=E3=E4=E5=E6=\n"],
+ [["\xe7\xe8\xe9\xea\xeb\xec\xed\xee"], "=E7=E8=E9=EA=EB=EC=ED=EE=\n"],
+ [["\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"], "=EF=F0=F1=F2=F3=F4=F5=F6=\n"],
+ [["\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe"], "=F7=F8=F9=FA=FB=FC=FD=FE=\n"],
+ [["\xff"], "=FF=\n"]
+ ].should be_computed_by(:pack, "M")
+ end
+
+ it "emits a soft line break when the output exceeds 72 characters when passed '*', 0, 1, or no count modifier" do
+ s1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ r1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=\na=\n"
+ s2 = "\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19"
+ r2 = "=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=\n=19=\n"
+ s3 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x15a"
+ r3 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=15=\na=\n"
+ s4 = "\x15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x15a"
+ r4 = "=15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=\na=15a=\n"
+
+ [ [[s1], "M", r1],
+ [[s1], "M0", r1],
+ [[s1], "M1", r1],
+ [[s2], "M", r2],
+ [[s2], "M0", r2],
+ [[s2], "M1", r2],
+ [[s3], "M", r3],
+ [[s3], "M0", r3],
+ [[s3], "M1", r3],
+ [[s4], "M", r4],
+ [[s4], "M0", r4],
+ [[s4], "M1", r4]
+ ].should be_computed_by(:pack)
+ end
+
+ it "emits a soft line break when the output exceeds count characters" do
+ [ [["abcdefghi"], "M2", "abc=\ndef=\nghi=\n"],
+ [["abcdefghi"], "M3", "abcd=\nefgh=\ni=\n"],
+ [["abcdefghi"], "M4", "abcde=\nfghi=\n"],
+ [["abcdefghi"], "M5", "abcdef=\nghi=\n"],
+ [["abcdefghi"], "M6", "abcdefg=\nhi=\n"],
+ [["\x19\x19\x19\x19"], "M2", "=19=\n=19=\n=19=\n=19=\n"],
+ [["\x19\x19\x19\x19"], "M3", "=19=19=\n=19=19=\n"],
+ [["\x19\x19\x19\x19"], "M4", "=19=19=\n=19=19=\n"],
+ [["\x19\x19\x19\x19"], "M5", "=19=19=\n=19=19=\n"],
+ [["\x19\x19\x19\x19"], "M6", "=19=19=19=\n=19=\n"],
+ [["\x19\x19\x19\x19"], "M7", "=19=19=19=\n=19=\n"]
+ ].should be_computed_by(:pack)
+ end
+
+ it "encodes a recursive array" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.pack('M').should be_an_instance_of(String)
+
+ array = ArraySpecs.recursive_array
+ array.pack('M').should == "1=\n"
+ end
+
+ it "calls #to_s to convert an object to a String" do
+ obj = mock("pack M string")
+ obj.should_receive(:to_s).and_return("packing")
+
+ [obj].pack("M").should == "packing=\n"
+ end
+
+ it "converts the object to a String representation if #to_s does not return a String" do
+ obj = mock("pack M non-string")
+ obj.should_receive(:to_s).and_return(2)
+
+ [obj].pack("M").should be_an_instance_of(String)
+ end
+
+ it "encodes a Symbol as a String" do
+ [:symbol].pack("M").should == "symbol=\n"
+ end
+
+ it "encodes an Integer as a String" do
+ [ [[1], "1=\n"],
+ [[bignum_value], "#{bignum_value}=\n"]
+ ].should be_computed_by(:pack, "M")
+ end
+
+ it "encodes a Float as a String" do
+ [1.0].pack("M").should == "1.0=\n"
+ end
+
+ it "converts Floats to the minimum unique representation" do
+ [1.0 / 3.0].pack("M").should == "0.3333333333333333=\n"
+ end
+
+ it "sets the output string to US-ASCII encoding" do
+ ["abcd"].pack("M").encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "Array#pack with format 'm'" do
+ it_behaves_like :array_pack_basic, 'm'
+ it_behaves_like :array_pack_basic_non_float, 'm'
+ it_behaves_like :array_pack_arguments, 'm'
+ it_behaves_like :array_pack_taint, 'm'
+
+ it "encodes an empty string as an empty string" do
+ [""].pack("m").should == ""
+ end
+
+ it "appends a newline to the end of the encoded string" do
+ ["a"].pack("m").should == "YQ==\n"
+ end
+
+ it "encodes one element per directive" do
+ ["abc", "DEF"].pack("mm").should == "YWJj\nREVG\n"
+ end
+
+ it "encodes 1, 2, or 3 characters in 4 output characters (Base64 encoding)" do
+ [ [["a"], "YQ==\n"],
+ [["ab"], "YWI=\n"],
+ [["abc"], "YWJj\n"],
+ [["abcd"], "YWJjZA==\n"],
+ [["abcde"], "YWJjZGU=\n"],
+ [["abcdef"], "YWJjZGVm\n"],
+ [["abcdefg"], "YWJjZGVmZw==\n"],
+ ].should be_computed_by(:pack, "m")
+ end
+
+ it "emits a newline after complete groups of count / 3 input characters when passed a count modifier" do
+ ["abcdefg"].pack("m3").should == "YWJj\nZGVm\nZw==\n"
+ end
+
+ it "implicitly has a count of 45 when passed '*', 1, 2 or no count modifier" do
+ s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ r = "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\nYWFhYWE=\n"
+ [ [[s], "m", r],
+ [[s], "m*", r],
+ [[s], "m1", r],
+ [[s], "m2", r],
+ ].should be_computed_by(:pack)
+ end
+
+ it "encodes all ascii characters" do
+ [ [["\x00\x01\x02\x03\x04\x05\x06"], "AAECAwQFBg==\n"],
+ [["\a\b\t\n\v\f\r"], "BwgJCgsMDQ==\n"],
+ [["\x0E\x0F\x10\x11\x12\x13\x14\x15\x16"], "Dg8QERITFBUW\n"],
+ [["\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f"], "FxgZGhscHR4f\n"],
+ [["!\"\#$%&'()*+,-./"], "ISIjJCUmJygpKissLS4v\n"],
+ [["0123456789"], "MDEyMzQ1Njc4OQ==\n"],
+ [[":;<=>?@"], "Ojs8PT4/QA==\n"],
+ [["ABCDEFGHIJKLMNOPQRSTUVWXYZ"], "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=\n"],
+ [["[\\]^_`"], "W1xdXl9g\n"],
+ [["abcdefghijklmnopqrstuvwxyz"], "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=\n"],
+ [["{|}~"], "e3x9fg==\n"],
+ [["\x7f\xc2\x80\xc2\x81\xc2\x82\xc2\x83"], "f8KAwoHCgsKD\n"],
+ [["\xc2\x84\xc2\x85\xc2\x86\xc2\x87\xc2"], "woTChcKGwofC\n"],
+ [["\x88\xc2\x89\xc2\x8a\xc2\x8b\xc2\x8c"], "iMKJworCi8KM\n"],
+ [["\xc2\x8d\xc2\x8e\xc2\x8f\xc2\x90\xc2"], "wo3CjsKPwpDC\n"],
+ [["\x91\xc2\x92\xc2\x93\xc2\x94\xc2\x95"], "kcKSwpPClMKV\n"],
+ [["\xc2\x96\xc2\x97\xc2\x98\xc2\x99\xc2"], "wpbCl8KYwpnC\n"],
+ [["\x9a\xc2\x9b\xc2\x9c\xc2\x9d\xc2\x9e"], "msKbwpzCncKe\n"],
+ [["\xc2\x9f\xc2\xa0\xc2\xa1\xc2\xa2\xc2"], "wp/CoMKhwqLC\n"],
+ [["\xa3\xc2\xa4\xc2\xa5\xc2\xa6\xc2\xa7"], "o8KkwqXCpsKn\n"],
+ [["\xc2\xa8\xc2\xa9\xc2\xaa\xc2\xab\xc2"], "wqjCqcKqwqvC\n"],
+ [["\xac\xc2\xad\xc2\xae\xc2\xaf\xc2\xb0"], "rMKtwq7Cr8Kw\n"],
+ [["\xc2\xb1\xc2\xb2\xc2\xb3\xc2\xb4\xc2"], "wrHCssKzwrTC\n"],
+ [["\xb5\xc2\xb6\xc2\xb7\xc2\xb8\xc2\xb9"], "tcK2wrfCuMK5\n"],
+ [["\xc2\xba\xc2\xbb\xc2\xbc\xc2\xbd\xc2"], "wrrCu8K8wr3C\n"],
+ [["\xbe\xc2\xbf\xc3\x80\xc3\x81\xc3\x82"], "vsK/w4DDgcOC\n"],
+ [["\xc3\x83\xc3\x84\xc3\x85\xc3\x86\xc3"], "w4PDhMOFw4bD\n"],
+ [["\x87\xc3\x88\xc3\x89\xc3\x8a\xc3\x8b"], "h8OIw4nDisOL\n"],
+ [["\xc3\x8c\xc3\x8d\xc3\x8e\xc3\x8f\xc3"], "w4zDjcOOw4/D\n"],
+ [["\x90\xc3\x91\xc3\x92\xc3\x93\xc3\x94"], "kMORw5LDk8OU\n"],
+ [["\xc3\x95\xc3\x96\xc3\x97\xc3\x98\xc3"], "w5XDlsOXw5jD\n"],
+ [["\x99\xc3\x9a\xc3\x9b\xc3\x9c\xc3\x9d"], "mcOaw5vDnMOd\n"],
+ [["\xc3\x9e\xc3\x9f\xc3\xa0\xc3\xa1\xc3"], "w57Dn8Ogw6HD\n"],
+ [["\xa2\xc3\xa3\xc3\xa4\xc3\xa5\xc3\xa6"], "osOjw6TDpcOm\n"],
+ [["\xc3\xa7\xc3\xa8\xc3\xa9\xc3\xaa\xc3"], "w6fDqMOpw6rD\n"],
+ [["\xab\xc3\xac\xc3\xad\xc3\xae\xc3\xaf"], "q8Osw63DrsOv\n"],
+ [["\xc3\xb0\xc3\xb1\xc3\xb2\xc3\xb3\xc3"], "w7DDscOyw7PD\n"],
+ [["\xb4\xc3\xb5\xc3\xb6\xc3\xb7\xc3\xb8"], "tMO1w7bDt8O4\n"],
+ [["\xc3\xb9\xc3\xba\xc3\xbb\xc3\xbc\xc3"], "w7nDusO7w7zD\n"],
+ [["\xbd\xc3\xbe\xc3\xbf"], "vcO+w78=\n"]
+ ].should be_computed_by(:pack, "m")
+ end
+
+ it "calls #to_str to convert an object to a String" do
+ obj = mock("pack m string")
+ obj.should_receive(:to_str).and_return("abc")
+ [obj].pack("m").should == "YWJj\n"
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock("pack m non-string")
+ -> { [obj].pack("m") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed nil" do
+ -> { [nil].pack("m") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an Integer" do
+ -> { [0].pack("m") }.should raise_error(TypeError)
+ -> { [bignum_value].pack("m") }.should raise_error(TypeError)
+ end
+
+ it "does not emit a newline if passed zero as the count modifier" do
+ s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ r = "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE="
+ [s].pack("m0").should == r
+ end
+
+ it "sets the output string to US-ASCII encoding" do
+ ["abcd"].pack("m").encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/array/pack/n_spec.rb b/spec/ruby/core/array/pack/n_spec.rb
new file mode 100644
index 0000000000..ab9409fc1e
--- /dev/null
+++ b/spec/ruby/core/array/pack/n_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'N'" do
+ it_behaves_like :array_pack_basic, 'N'
+ it_behaves_like :array_pack_basic_non_float, 'N'
+ it_behaves_like :array_pack_arguments, 'N'
+ it_behaves_like :array_pack_numeric_basic, 'N'
+ it_behaves_like :array_pack_integer, 'N'
+ it_behaves_like :array_pack_no_platform, 'N'
+ it_behaves_like :array_pack_32bit_be, 'N'
+end
+
+describe "Array#pack with format 'n'" do
+ it_behaves_like :array_pack_basic, 'n'
+ it_behaves_like :array_pack_basic_non_float, 'n'
+ it_behaves_like :array_pack_arguments, 'n'
+ it_behaves_like :array_pack_numeric_basic, 'n'
+ it_behaves_like :array_pack_integer, 'n'
+ it_behaves_like :array_pack_no_platform, 'n'
+ it_behaves_like :array_pack_16bit_be, 'n'
+end
diff --git a/spec/ruby/core/array/pack/p_spec.rb b/spec/ruby/core/array/pack/p_spec.rb
new file mode 100644
index 0000000000..b023bf9110
--- /dev/null
+++ b/spec/ruby/core/array/pack/p_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'P'" do
+ it_behaves_like :array_pack_basic_non_float, 'P'
+ it_behaves_like :array_pack_taint, 'P'
+
+ it "produces as many bytes as there are in a pointer" do
+ ["hello"].pack("P").size.should == [0].pack("J").size
+ end
+
+ it "round-trips a string through pack and unpack" do
+ ["hello"].pack("P").unpack("P5").should == ["hello"]
+ end
+
+ it "with nil gives a null pointer" do
+ [nil].pack("P").unpack("J").should == [0]
+ end
+end
+
+describe "Array#pack with format 'p'" do
+ it_behaves_like :array_pack_basic_non_float, 'p'
+ it_behaves_like :array_pack_taint, 'p'
+
+ it "produces as many bytes as there are in a pointer" do
+ ["hello"].pack("p").size.should == [0].pack("J").size
+ end
+
+ it "round-trips a string through pack and unpack" do
+ ["hello"].pack("p").unpack("p").should == ["hello"]
+ end
+
+ it "with nil gives a null pointer" do
+ [nil].pack("p").unpack("J").should == [0]
+ end
+end
diff --git a/spec/ruby/core/array/pack/percent_spec.rb b/spec/ruby/core/array/pack/percent_spec.rb
new file mode 100644
index 0000000000..5d56dea5fe
--- /dev/null
+++ b/spec/ruby/core/array/pack/percent_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+
+describe "Array#pack with format '%'" do
+ it "raises an Argument Error" do
+ -> { [1].pack("%") }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/array/pack/q_spec.rb b/spec/ruby/core/array/pack/q_spec.rb
new file mode 100644
index 0000000000..bd6b2a4b71
--- /dev/null
+++ b/spec/ruby/core/array/pack/q_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'Q'" do
+ it_behaves_like :array_pack_basic, 'Q'
+ it_behaves_like :array_pack_basic_non_float, 'Q'
+ it_behaves_like :array_pack_arguments, 'Q'
+ it_behaves_like :array_pack_numeric_basic, 'Q'
+ it_behaves_like :array_pack_integer, 'Q'
+end
+
+describe "Array#pack with format 'q'" do
+ it_behaves_like :array_pack_basic, 'q'
+ it_behaves_like :array_pack_basic_non_float, 'q'
+ it_behaves_like :array_pack_arguments, 'q'
+ it_behaves_like :array_pack_numeric_basic, 'q'
+ it_behaves_like :array_pack_integer, 'q'
+end
+
+describe "Array#pack with format 'Q'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_64bit_le, 'Q<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_64bit_be, 'Q>'
+ end
+end
+
+describe "Array#pack with format 'q'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_64bit_le, 'q<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_64bit_be, 'q>'
+ end
+end
+
+little_endian do
+ describe "Array#pack with format 'Q'" do
+ it_behaves_like :array_pack_64bit_le, 'Q'
+ end
+
+ describe "Array#pack with format 'q'" do
+ it_behaves_like :array_pack_64bit_le, 'q'
+ end
+end
+
+big_endian do
+ describe "Array#pack with format 'Q'" do
+ it_behaves_like :array_pack_64bit_be, 'Q'
+ end
+
+ describe "Array#pack with format 'q'" do
+ it_behaves_like :array_pack_64bit_be, 'q'
+ end
+end
diff --git a/spec/ruby/core/array/pack/s_spec.rb b/spec/ruby/core/array/pack/s_spec.rb
new file mode 100644
index 0000000000..4212d6a0b1
--- /dev/null
+++ b/spec/ruby/core/array/pack/s_spec.rb
@@ -0,0 +1,133 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'S'" do
+ it_behaves_like :array_pack_basic, 'S'
+ it_behaves_like :array_pack_basic_non_float, 'S'
+ it_behaves_like :array_pack_arguments, 'S'
+ it_behaves_like :array_pack_numeric_basic, 'S'
+ it_behaves_like :array_pack_integer, 'S'
+end
+
+describe "Array#pack with format 's'" do
+ it_behaves_like :array_pack_basic, 's'
+ it_behaves_like :array_pack_basic_non_float, 's'
+ it_behaves_like :array_pack_arguments, 's'
+ it_behaves_like :array_pack_numeric_basic, 's'
+ it_behaves_like :array_pack_integer, 's'
+end
+
+describe "Array#pack with format 'S'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_16bit_le, 'S<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_16bit_le, 'S<_'
+ it_behaves_like :array_pack_16bit_le, 'S_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_16bit_le, 'S<!'
+ it_behaves_like :array_pack_16bit_le, 'S!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_16bit_be, 'S>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_16bit_be, 'S>_'
+ it_behaves_like :array_pack_16bit_be, 'S_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_16bit_be, 'S>!'
+ it_behaves_like :array_pack_16bit_be, 'S!>'
+ end
+end
+
+describe "Array#pack with format 's'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_16bit_le, 's<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_16bit_le, 's<_'
+ it_behaves_like :array_pack_16bit_le, 's_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_16bit_le, 's<!'
+ it_behaves_like :array_pack_16bit_le, 's!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_16bit_be, 's>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_16bit_be, 's>_'
+ it_behaves_like :array_pack_16bit_be, 's_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_16bit_be, 's>!'
+ it_behaves_like :array_pack_16bit_be, 's!>'
+ end
+end
+
+little_endian do
+ describe "Array#pack with format 'S'" do
+ it_behaves_like :array_pack_16bit_le, 'S'
+ end
+
+ describe "Array#pack with format 'S' with modifier '_'" do
+ it_behaves_like :array_pack_16bit_le, 'S_'
+ end
+
+ describe "Array#pack with format 'S' with modifier '!'" do
+ it_behaves_like :array_pack_16bit_le, 'S!'
+ end
+
+ describe "Array#pack with format 's'" do
+ it_behaves_like :array_pack_16bit_le, 's'
+ end
+
+ describe "Array#pack with format 's' with modifier '_'" do
+ it_behaves_like :array_pack_16bit_le, 's_'
+ end
+
+ describe "Array#pack with format 's' with modifier '!'" do
+ it_behaves_like :array_pack_16bit_le, 's!'
+ end
+end
+
+big_endian do
+ describe "Array#pack with format 'S'" do
+ it_behaves_like :array_pack_16bit_be, 'S'
+ end
+
+ describe "Array#pack with format 'S' with modifier '_'" do
+ it_behaves_like :array_pack_16bit_be, 'S_'
+ end
+
+ describe "Array#pack with format 'S' with modifier '!'" do
+ it_behaves_like :array_pack_16bit_be, 'S!'
+ end
+
+ describe "Array#pack with format 's'" do
+ it_behaves_like :array_pack_16bit_be, 's'
+ end
+
+ describe "Array#pack with format 's' with modifier '_'" do
+ it_behaves_like :array_pack_16bit_be, 's_'
+ end
+
+ describe "Array#pack with format 's' with modifier '!'" do
+ it_behaves_like :array_pack_16bit_be, 's!'
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/basic.rb b/spec/ruby/core/array/pack/shared/basic.rb
new file mode 100644
index 0000000000..65fdaa45d8
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/basic.rb
@@ -0,0 +1,97 @@
+describe :array_pack_arguments, shared: true do
+ it "raises an ArgumentError if there are fewer elements than the format requires" do
+ -> { [].pack(pack_format(1)) }.should raise_error(ArgumentError)
+ end
+end
+
+describe :array_pack_basic, shared: true do
+ before :each do
+ @obj = ArraySpecs.universal_pack_object
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { [@obj].pack(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed an Integer" do
+ -> { [@obj].pack(1) }.should raise_error(TypeError)
+ end
+end
+
+describe :array_pack_basic_non_float, shared: true do
+ before :each do
+ @obj = ArraySpecs.universal_pack_object
+ end
+
+ it "ignores whitespace in the format string" do
+ [@obj, @obj].pack("a \t\n\v\f\r"+pack_format).should be_an_instance_of(String)
+ end
+
+ it "ignores comments in the format string" do
+ # 2 additional directives ('a') are required for the X directive
+ [@obj, @obj, @obj, @obj].pack("aa #{pack_format} # some comment \n#{pack_format}").should be_an_instance_of(String)
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "warns in verbose mode that a directive is unknown" do
+ # additional directive ('a') is required for the X directive
+ -> { [@obj, @obj].pack("a R" + pack_format) }.should complain(/unknown pack directive 'R'/, verbose: true)
+ -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0'/, verbose: true)
+ -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':'/, verbose: true)
+ end
+ end
+
+ ruby_version_is "3.2"..."3.3" do
+ # https://bugs.ruby-lang.org/issues/19150
+ # NOTE: it's just a plan of the Ruby core team
+ it "warns that a directive is unknown" do
+ # additional directive ('a') is required for the X directive
+ -> { [@obj, @obj].pack("a R" + pack_format) }.should complain(/unknown pack directive 'R'/)
+ -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0'/)
+ -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':'/)
+ end
+ end
+
+ ruby_version_is "3.3" do
+ # https://bugs.ruby-lang.org/issues/19150
+ # NOTE: Added this case just to not forget about the decision in the ticket
+ it "raise ArgumentError when a directive is unknown" do
+ # additional directive ('a') is required for the X directive
+ -> { [@obj, @obj].pack("a R" + pack_format) }.should raise_error(ArgumentError)
+ -> { [@obj, @obj].pack("a 0" + pack_format) }.should raise_error(ArgumentError)
+ -> { [@obj, @obj].pack("a :" + pack_format) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "calls #to_str to coerce the directives string" do
+ d = mock("pack directive")
+ d.should_receive(:to_str).and_return("x"+pack_format)
+ [@obj, @obj].pack(d).should be_an_instance_of(String)
+ end
+end
+
+describe :array_pack_basic_float, shared: true do
+ it "ignores whitespace in the format string" do
+ [9.3, 4.7].pack(" \t\n\v\f\r"+pack_format).should be_an_instance_of(String)
+ end
+
+ it "ignores comments in the format string" do
+ [9.3, 4.7].pack(pack_format + "# some comment \n" + pack_format).should be_an_instance_of(String)
+ end
+
+ it "calls #to_str to coerce the directives string" do
+ d = mock("pack directive")
+ d.should_receive(:to_str).and_return("x"+pack_format)
+ [1.2, 4.7].pack(d).should be_an_instance_of(String)
+ end
+end
+
+describe :array_pack_no_platform, shared: true do
+ it "raises ArgumentError when the format modifier is '_'" do
+ ->{ [1].pack(pack_format("_")) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError when the format modifier is '!'" do
+ ->{ [1].pack(pack_format("!")) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/encodings.rb b/spec/ruby/core/array/pack/shared/encodings.rb
new file mode 100644
index 0000000000..6b7ffac764
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/encodings.rb
@@ -0,0 +1,16 @@
+describe :array_pack_hex, shared: true do
+ it "encodes no bytes when passed zero as the count modifier" do
+ ["abc"].pack(pack_format(0)).should == ""
+ end
+
+ it "raises a TypeError if the object does not respond to #to_str" do
+ obj = mock("pack hex non-string")
+ -> { [obj].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock("pack hex non-string")
+ obj.should_receive(:to_str).and_return(1)
+ -> { [obj].pack(pack_format) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/float.rb b/spec/ruby/core/array/pack/shared/float.rb
new file mode 100644
index 0000000000..9510cffed7
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/float.rb
@@ -0,0 +1,287 @@
+# -*- encoding: binary -*-
+
+describe :array_pack_float_le, shared: true do
+ it "encodes a positive Float" do
+ [1.42].pack(pack_format).should == "\x8f\xc2\xb5?"
+ end
+
+ it "encodes a negative Float" do
+ [-34.2].pack(pack_format).should == "\xcd\xcc\x08\xc2"
+ end
+
+ it "converts an Integer to a Float" do
+ [8].pack(pack_format).should == "\x00\x00\x00A"
+ end
+
+ it "raises a TypeError if passed a String representation of a floating point number" do
+ -> { ["13"].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "\x9a\x999@33\xb3?"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "\x9a\x999@33\xb3?33\x03A"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ [5.3, 9.2].pack(pack_format("\000", 2)).should == "\x9a\x99\xa9@33\x13A"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [5.3, 9.2].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ [5.3, 9.2].pack(pack_format(" ", 2)).should == "\x9a\x99\xa9@33\x13A"
+ end
+
+ it "encodes positive Infinity" do
+ [infinity_value].pack(pack_format).should == "\x00\x00\x80\x7f"
+ end
+
+ it "encodes negative Infinity" do
+ [-infinity_value].pack(pack_format).should == "\x00\x00\x80\xff"
+ end
+
+ it "encodes NaN" do
+ nans = ["\x00\x00\xc0\xff", "\x00\x00\xc0\x7f", "\xFF\xFF\xFF\x7F"]
+ nans.should include([nan_value].pack(pack_format))
+ end
+
+ it "encodes a positive Float outside the range of a single precision float" do
+ [1e150].pack(pack_format).should == "\x00\x00\x80\x7f"
+ end
+
+ it "encodes a negative Float outside the range of a single precision float" do
+ [-1e150].pack(pack_format).should == "\x00\x00\x80\xff"
+ end
+
+ it "encodes a bignum as a float" do
+ [2 ** 65].pack(pack_format).should == [(2 ** 65).to_f].pack(pack_format)
+ end
+
+ it "encodes a rational as a float" do
+ [Rational(3, 4)].pack(pack_format).should == [Rational(3, 4).to_f].pack(pack_format)
+ end
+end
+
+describe :array_pack_float_be, shared: true do
+ it "encodes a positive Float" do
+ [1.42].pack(pack_format).should == "?\xb5\xc2\x8f"
+ end
+
+ it "encodes a negative Float" do
+ [-34.2].pack(pack_format).should == "\xc2\x08\xcc\xcd"
+ end
+
+ it "converts an Integer to a Float" do
+ [8].pack(pack_format).should == "A\x00\x00\x00"
+ [bignum_value].pack(pack_format).should == "_\x80\x00\x00"
+ end
+
+ it "converts a Rational to a Float" do
+ [Rational(8)].pack(pack_format).should == "A\x00\x00\x00"
+ end
+
+ it "raises a TypeError if passed a String representation of a floating point number" do
+ -> { ["13"].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "@9\x99\x9a?\xb333"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "@9\x99\x9a?\xb333A\x0333"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ [5.3, 9.2].pack(pack_format("\000", 2)).should == "@\xa9\x99\x9aA\x1333"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [5.3, 9.2].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ [5.3, 9.2].pack(pack_format(" ", 2)).should == "@\xa9\x99\x9aA\x1333"
+ end
+
+ it "encodes positive Infinity" do
+ [infinity_value].pack(pack_format).should == "\x7f\x80\x00\x00"
+ end
+
+ it "encodes negative Infinity" do
+ [-infinity_value].pack(pack_format).should == "\xff\x80\x00\x00"
+ end
+
+ it "encodes NaN" do
+ nans = ["\xff\xc0\x00\x00", "\x7f\xc0\x00\x00", "\x7F\xFF\xFF\xFF"]
+ nans.should include([nan_value].pack(pack_format))
+ end
+
+ it "encodes a positive Float outside the range of a single precision float" do
+ [1e150].pack(pack_format).should == "\x7f\x80\x00\x00"
+ end
+
+ it "encodes a negative Float outside the range of a single precision float" do
+ [-1e150].pack(pack_format).should == "\xff\x80\x00\x00"
+ end
+end
+
+describe :array_pack_double_le, shared: true do
+ it "encodes a positive Float" do
+ [1.42].pack(pack_format).should == "\xb8\x1e\x85\xebQ\xb8\xf6?"
+ end
+
+ it "encodes a negative Float" do
+ [-34.2].pack(pack_format).should == "\x9a\x99\x99\x99\x99\x19A\xc0"
+ end
+
+ it "converts an Integer to a Float" do
+ [8].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\x20@"
+ [bignum_value].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\xF0C"
+ end
+
+ it "converts a Rational to a Float" do
+ [Rational(8)].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00 @"
+ end
+
+ it "raises a TypeError if passed a String representation of a floating point number" do
+ -> { ["13"].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "333333\x07@ffffff\xf6?"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "333333\x07@ffffff\xf6?ffffff\x20@"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ [5.3, 9.2].pack(pack_format("\000", 2)).should == "333333\x15@ffffff\x22@"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [5.3, 9.2].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ [5.3, 9.2].pack(pack_format(" ", 2)).should == "333333\x15@ffffff\x22@"
+ end
+
+ it "encodes positive Infinity" do
+ [infinity_value].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\xf0\x7f"
+ end
+
+ it "encodes negative Infinity" do
+ [-infinity_value].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\xf0\xff"
+ end
+
+ it "encodes NaN" do
+ nans = [
+ "\x00\x00\x00\x00\x00\x00\xf8\xff",
+ "\x00\x00\x00\x00\x00\x00\xf8\x7f",
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F"
+ ]
+ nans.should include([nan_value].pack(pack_format))
+ end
+
+ it "encodes a positive Float outside the range of a single precision float" do
+ [1e150].pack(pack_format).should == "\xaf\x96P\x2e5\x8d\x13_"
+ end
+
+ it "encodes a negative Float outside the range of a single precision float" do
+ [-1e150].pack(pack_format).should == "\xaf\x96P\x2e5\x8d\x13\xdf"
+ end
+end
+
+describe :array_pack_double_be, shared: true do
+ it "encodes a positive Float" do
+ [1.42].pack(pack_format).should == "?\xf6\xb8Q\xeb\x85\x1e\xb8"
+ end
+
+ it "encodes a negative Float" do
+ [-34.2].pack(pack_format).should == "\xc0A\x19\x99\x99\x99\x99\x9a"
+ end
+
+ it "converts an Integer to a Float" do
+ [8].pack(pack_format).should == "@\x20\x00\x00\x00\x00\x00\x00"
+ end
+
+ it "raises a TypeError if passed a String representation of a floating point number" do
+ -> { ["13"].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "@\x07333333?\xf6ffffff"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "@\x07333333?\xf6ffffff@\x20ffffff"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ [5.3, 9.2].pack(pack_format("\000", 2)).should == "@\x15333333@\x22ffffff"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [5.3, 9.2].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ [5.3, 9.2].pack(pack_format(" ", 2)).should == "@\x15333333@\x22ffffff"
+ end
+
+ it "encodes positive Infinity" do
+ [infinity_value].pack(pack_format).should == "\x7f\xf0\x00\x00\x00\x00\x00\x00"
+ end
+
+ it "encodes negative Infinity" do
+ [-infinity_value].pack(pack_format).should == "\xff\xf0\x00\x00\x00\x00\x00\x00"
+ end
+
+ it "encodes NaN" do
+ nans = [
+ "\xff\xf8\x00\x00\x00\x00\x00\x00",
+ "\x7f\xf8\x00\x00\x00\x00\x00\x00",
+ "\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ ]
+ nans.should include([nan_value].pack(pack_format))
+ end
+
+ it "encodes a positive Float outside the range of a single precision float" do
+ [1e150].pack(pack_format).should == "_\x13\x8d5\x2eP\x96\xaf"
+ end
+
+ it "encodes a negative Float outside the range of a single precision float" do
+ [-1e150].pack(pack_format).should == "\xdf\x13\x8d5\x2eP\x96\xaf"
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/integer.rb b/spec/ruby/core/array/pack/shared/integer.rb
new file mode 100644
index 0000000000..d3ce9b5792
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/integer.rb
@@ -0,0 +1,441 @@
+# -*- encoding: binary -*-
+
+describe :array_pack_16bit_le, shared: true do
+ it "encodes the least significant 16 bits of a positive number" do
+ [ [[0x0000_0021], "\x21\x00"],
+ [[0x0000_4321], "\x21\x43"],
+ [[0x0065_4321], "\x21\x43"],
+ [[0x7865_4321], "\x21\x43"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 16 bits of a negative number" do
+ [ [[-0x0000_0021], "\xdf\xff"],
+ [[-0x0000_4321], "\xdf\xbc"],
+ [[-0x0065_4321], "\xdf\xbc"],
+ [[-0x7865_4321], "\xdf\xbc"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[2019902241.2], "\x21\x43"],
+ [[2019902241.8], "\x21\x43"],
+ [[-2019902241.2], "\xdf\xbc"],
+ [[-2019902241.8], "\xdf\xbc"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678)
+ [obj].pack(pack_format()).should == "\x78\x56"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x78\x65\xcd\xab"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x78\x65\xcd\xab\x21\x43"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ str.should == "\x78\x65\xcd\xab"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2))
+ str.should == "\x78\x65\xcd\xab"
+ end
+end
+
+describe :array_pack_16bit_be, shared: true do
+ it "encodes the least significant 16 bits of a positive number" do
+ [ [[0x0000_0021], "\x00\x21"],
+ [[0x0000_4321], "\x43\x21"],
+ [[0x0065_4321], "\x43\x21"],
+ [[0x7865_4321], "\x43\x21"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 16 bits of a negative number" do
+ [ [[-0x0000_0021], "\xff\xdf"],
+ [[-0x0000_4321], "\xbc\xdf"],
+ [[-0x0065_4321], "\xbc\xdf"],
+ [[-0x7865_4321], "\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[2019902241.2], "\x43\x21"],
+ [[2019902241.8], "\x43\x21"],
+ [[-2019902241.2], "\xbc\xdf"],
+ [[-2019902241.8], "\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678)
+ [obj].pack(pack_format()).should == "\x56\x78"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x65\x78\xab\xcd"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x65\x78\xab\xcd\x43\x21"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ str.should == "\x65\x78\xab\xcd"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2))
+ str.should == "\x65\x78\xab\xcd"
+ end
+end
+
+describe :array_pack_32bit_le, shared: true do
+ it "encodes the least significant 32 bits of a positive number" do
+ [ [[0x0000_0021], "\x21\x00\x00\x00"],
+ [[0x0000_4321], "\x21\x43\x00\x00"],
+ [[0x0065_4321], "\x21\x43\x65\x00"],
+ [[0x7865_4321], "\x21\x43\x65\x78"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 32 bits of a negative number" do
+ [ [[-0x0000_0021], "\xdf\xff\xff\xff"],
+ [[-0x0000_4321], "\xdf\xbc\xff\xff"],
+ [[-0x0065_4321], "\xdf\xbc\x9a\xff"],
+ [[-0x7865_4321], "\xdf\xbc\x9a\x87"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[2019902241.2], "\x21\x43\x65\x78"],
+ [[2019902241.8], "\x21\x43\x65\x78"],
+ [[-2019902241.2], "\xdf\xbc\x9a\x87"],
+ [[-2019902241.8], "\xdf\xbc\x9a\x87"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678)
+ [obj].pack(pack_format()).should == "\x78\x56\x34\x12"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde\x21\x43\x65\x78"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde"
+ end
+end
+
+describe :array_pack_32bit_be, shared: true do
+ it "encodes the least significant 32 bits of a positive number" do
+ [ [[0x0000_0021], "\x00\x00\x00\x21"],
+ [[0x0000_4321], "\x00\x00\x43\x21"],
+ [[0x0065_4321], "\x00\x65\x43\x21"],
+ [[0x7865_4321], "\x78\x65\x43\x21"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 32 bits of a negative number" do
+ [ [[-0x0000_0021], "\xff\xff\xff\xdf"],
+ [[-0x0000_4321], "\xff\xff\xbc\xdf"],
+ [[-0x0065_4321], "\xff\x9a\xbc\xdf"],
+ [[-0x7865_4321], "\x87\x9a\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[2019902241.2], "\x78\x65\x43\x21"],
+ [[2019902241.8], "\x78\x65\x43\x21"],
+ [[-2019902241.2], "\x87\x9a\xbc\xdf"],
+ [[-2019902241.8], "\x87\x9a\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678)
+ [obj].pack(pack_format()).should == "\x12\x34\x56\x78"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd\x78\x65\x43\x21"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd"
+ end
+end
+
+describe :array_pack_32bit_le_platform, shared: true do
+ it "encodes the least significant 32 bits of a number" do
+ [ [[0x7865_4321], "\x21\x43\x65\x78"],
+ [[-0x7865_4321], "\xdf\xbc\x9a\x87"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde\x21\x43\x65\x78"
+ end
+
+ platform_is wordsize: 64 do
+ it "encodes the least significant 32 bits of a number that is greater than 32 bits" do
+ [ [[0xff_7865_4321], "\x21\x43\x65\x78"],
+ [[-0xff_7865_4321], "\xdf\xbc\x9a\x87"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+ end
+end
+
+describe :array_pack_32bit_be_platform, shared: true do
+ it "encodes the least significant 32 bits of a number" do
+ [ [[0x7865_4321], "\x78\x65\x43\x21"],
+ [[-0x7865_4321], "\x87\x9a\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd\x78\x65\x43\x21"
+ end
+
+ platform_is wordsize: 64 do
+ it "encodes the least significant 32 bits of a number that is greater than 32 bits" do
+ [ [[0xff_7865_4321], "\x78\x65\x43\x21"],
+ [[-0xff_7865_4321], "\x87\x9a\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+ end
+end
+
+describe :array_pack_64bit_le, shared: true do
+ it "encodes the least significant 64 bits of a positive number" do
+ [ [[0x0000_0000_0000_0021], "\x21\x00\x00\x00\x00\x00\x00\x00"],
+ [[0x0000_0000_0000_4321], "\x21\x43\x00\x00\x00\x00\x00\x00"],
+ [[0x0000_0000_0065_4321], "\x21\x43\x65\x00\x00\x00\x00\x00"],
+ [[0x0000_0000_7865_4321], "\x21\x43\x65\x78\x00\x00\x00\x00"],
+ [[0x0000_0090_7865_4321], "\x21\x43\x65\x78\x90\x00\x00\x00"],
+ [[0x0000_ba90_7865_4321], "\x21\x43\x65\x78\x90\xba\x00\x00"],
+ [[0x00dc_ba90_7865_4321], "\x21\x43\x65\x78\x90\xba\xdc\x00"],
+ [[0x7edc_ba90_7865_4321], "\x21\x43\x65\x78\x90\xba\xdc\x7e"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 64 bits of a negative number" do
+ [ [[-0x0000_0000_0000_0021], "\xdf\xff\xff\xff\xff\xff\xff\xff"],
+ [[-0x0000_0000_0000_4321], "\xdf\xbc\xff\xff\xff\xff\xff\xff"],
+ [[-0x0000_0000_0065_4321], "\xdf\xbc\x9a\xff\xff\xff\xff\xff"],
+ [[-0x0000_0000_7865_4321], "\xdf\xbc\x9a\x87\xff\xff\xff\xff"],
+ [[-0x0000_0090_7865_4321], "\xdf\xbc\x9a\x87\x6f\xff\xff\xff"],
+ [[-0x0000_ba90_7865_4321], "\xdf\xbc\x9a\x87\x6f\x45\xff\xff"],
+ [[-0x00dc_ba90_7865_4321], "\xdf\xbc\x9a\x87\x6f\x45\x23\xff"],
+ [[-0x7edc_ba90_7865_4321], "\xdf\xbc\x9a\x87\x6f\x45\x23\x81"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[9.14138647331322368e+18], "\x00\x44\x65\x78\x90\xba\xdc\x7e"],
+ [[-9.14138647331322368e+18], "\x00\xbc\x9a\x87\x6f\x45\x23\x81"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678_90ab_cdef)
+ [obj].pack(pack_format()).should == "\xef\xcd\xab\x90\x78\x56\x34\x12"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1234_5678_90ab_cdef,
+ 0xdef0_abcd_3412_7856,
+ 0x7865_4321_dcba_def0].pack(pack_format(2))
+ str.should == "\xef\xcd\xab\x90\x78\x56\x34\x12\x56\x78\x12\x34\xcd\xab\xf0\xde"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format('*'))
+ str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2))
+ str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format(' ', 2))
+ str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78"
+ end
+end
+
+describe :array_pack_64bit_be, shared: true do
+ it "encodes the least significant 64 bits of a positive number" do
+ [ [[0x0000_0000_0000_0021], "\x00\x00\x00\x00\x00\x00\x00\x21"],
+ [[0x0000_0000_0000_4321], "\x00\x00\x00\x00\x00\x00\x43\x21"],
+ [[0x0000_0000_0065_4321], "\x00\x00\x00\x00\x00\x65\x43\x21"],
+ [[0x0000_0000_7865_4321], "\x00\x00\x00\x00\x78\x65\x43\x21"],
+ [[0x0000_0090_7865_4321], "\x00\x00\x00\x90\x78\x65\x43\x21"],
+ [[0x0000_ba90_7865_4321], "\x00\x00\xba\x90\x78\x65\x43\x21"],
+ [[0x00dc_ba90_7865_4321], "\x00\xdc\xba\x90\x78\x65\x43\x21"],
+ [[0x7edc_ba90_7865_4321], "\x7e\xdc\xba\x90\x78\x65\x43\x21"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 64 bits of a negative number" do
+ [ [[-0x0000_0000_0000_0021], "\xff\xff\xff\xff\xff\xff\xff\xdf"],
+ [[-0x0000_0000_0000_4321], "\xff\xff\xff\xff\xff\xff\xbc\xdf"],
+ [[-0x0000_0000_0065_4321], "\xff\xff\xff\xff\xff\x9a\xbc\xdf"],
+ [[-0x0000_0000_7865_4321], "\xff\xff\xff\xff\x87\x9a\xbc\xdf"],
+ [[-0x0000_0090_7865_4321], "\xff\xff\xff\x6f\x87\x9a\xbc\xdf"],
+ [[-0x0000_ba90_7865_4321], "\xff\xff\x45\x6f\x87\x9a\xbc\xdf"],
+ [[-0x00dc_ba90_7865_4321], "\xff\x23\x45\x6f\x87\x9a\xbc\xdf"],
+ [[-0x7edc_ba90_7865_4321], "\x81\x23\x45\x6f\x87\x9a\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[9.14138647331322368e+18], "\x7e\xdc\xba\x90\x78\x65\x44\x00"],
+ [[-9.14138647331322368e+18], "\x81\x23\x45\x6f\x87\x9a\xbc\x00"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678_90ab_cdef)
+ [obj].pack(pack_format()).should == "\x12\x34\x56\x78\x90\xab\xcd\xef"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1234_5678_90ab_cdef,
+ 0xdef0_abcd_3412_7856,
+ 0x7865_4321_dcba_def0].pack(pack_format(2))
+ str.should == "\x12\x34\x56\x78\x90\xab\xcd\xef\xde\xf0\xab\xcd\x34\x12\x78\x56"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format('*'))
+ str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2))
+ str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format(' ', 2))
+ str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0"
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/numeric_basic.rb b/spec/ruby/core/array/pack/shared/numeric_basic.rb
new file mode 100644
index 0000000000..545e215e64
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/numeric_basic.rb
@@ -0,0 +1,50 @@
+describe :array_pack_numeric_basic, shared: true do
+ it "returns an empty String if count is zero" do
+ [1].pack(pack_format(0)).should == ""
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { [nil].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed true" do
+ -> { [true].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed false" do
+ -> { [false].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "returns a binary string" do
+ [0xFF].pack(pack_format).encoding.should == Encoding::BINARY
+ [0xE3, 0x81, 0x82].pack(pack_format(3)).encoding.should == Encoding::BINARY
+ end
+end
+
+describe :array_pack_integer, shared: true do
+ it "raises a TypeError when the object does not respond to #to_int" do
+ obj = mock('not an integer')
+ -> { [obj].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { ["5"].pack(pack_format) }.should raise_error(TypeError)
+ end
+end
+
+describe :array_pack_float, shared: true do
+ it "raises a TypeError if a String does not represent a floating point number" do
+ -> { ["a"].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the object is not Numeric" do
+ obj = Object.new
+ -> { [obj].pack(pack_format) }.should raise_error(TypeError, /can't convert Object into Float/)
+ end
+
+ it "raises a TypeError when the Numeric object does not respond to #to_f" do
+ klass = Class.new(Numeric)
+ obj = klass.new
+ -> { [obj].pack(pack_format) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/string.rb b/spec/ruby/core/array/pack/shared/string.rb
new file mode 100644
index 0000000000..8c82e8c617
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/string.rb
@@ -0,0 +1,48 @@
+# -*- encoding: binary -*-
+describe :array_pack_string, shared: true do
+ it "adds count bytes of a String to the output" do
+ ["abc"].pack(pack_format(2)).should == "ab"
+ end
+
+ it "implicitly has a count of one when no count is specified" do
+ ["abc"].pack(pack_format).should == "a"
+ end
+
+ it "does not add any bytes when the count is zero" do
+ ["abc"].pack(pack_format(0)).should == ""
+ end
+
+ it "is not affected by a previous count modifier" do
+ ["abcde", "defg"].pack(pack_format(3)+pack_format).should == "abcd"
+ end
+
+ it "raises an ArgumentError when the Array is empty" do
+ -> { [].pack(pack_format) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the Array has too few elements" do
+ -> { ["a"].pack(pack_format(nil, 2)) }.should raise_error(ArgumentError)
+ end
+
+ it "calls #to_str to convert the element to a String" do
+ obj = mock('pack string')
+ obj.should_receive(:to_str).and_return("abc")
+
+ [obj].pack(pack_format).should == "a"
+ end
+
+ it "raises a TypeError when the object does not respond to #to_str" do
+ obj = mock("not a string")
+ -> { [obj].pack(pack_format) }.should raise_error(TypeError)
+ end
+
+ it "returns a string in encoding of common to the concatenated results" do
+ f = pack_format("*")
+ [ [["\u{3042 3044 3046 3048}", 0x2000B].pack(f+"U"), Encoding::BINARY],
+ [["abcde\xd1", "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY],
+ [["a".force_encoding("ascii"), "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY],
+ # under discussion [ruby-dev:37294]
+ [["\u{3042 3044 3046 3048}", 1].pack(f+"N"), Encoding::BINARY]
+ ].should be_computed_by(:encoding)
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/taint.rb b/spec/ruby/core/array/pack/shared/taint.rb
new file mode 100644
index 0000000000..2c2b011c34
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/taint.rb
@@ -0,0 +1,2 @@
+describe :array_pack_taint, shared: true do
+end
diff --git a/spec/ruby/core/array/pack/shared/unicode.rb b/spec/ruby/core/array/pack/shared/unicode.rb
new file mode 100644
index 0000000000..130c447bb7
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/unicode.rb
@@ -0,0 +1,104 @@
+# -*- encoding: utf-8 -*-
+
+describe :array_pack_unicode, shared: true do
+ it "encodes ASCII values as a Unicode codepoint" do
+ [ [[0], "\x00"],
+ [[1], "\x01"],
+ [[8], "\x08"],
+ [[15], "\x0f"],
+ [[24], "\x18"],
+ [[31], "\x1f"],
+ [[127], "\x7f"],
+ [[128], "\xc2\x80"],
+ [[129], "\xc2\x81"],
+ [[255], "\xc3\xbf"]
+ ].should be_computed_by(:pack, "U")
+ end
+
+ it "encodes UTF-8 BMP codepoints" do
+ [ [[0x80], "\xc2\x80"],
+ [[0x7ff], "\xdf\xbf"],
+ [[0x800], "\xe0\xa0\x80"],
+ [[0xffff], "\xef\xbf\xbf"]
+ ].should be_computed_by(:pack, "U")
+ end
+
+ it "constructs strings with valid encodings" do
+ str = [0x85].pack("U*")
+ str.should == "\xc2\x85"
+ str.valid_encoding?.should be_true
+ end
+
+ it "encodes values larger than UTF-8 max codepoints" do
+ [
+ [[0x00110000], [244, 144, 128, 128].pack('C*').force_encoding('utf-8')],
+ [[0x04000000], [252, 132, 128, 128, 128, 128].pack('C*').force_encoding('utf-8')],
+ [[0x7FFFFFFF], [253, 191, 191, 191, 191, 191].pack('C*').force_encoding('utf-8')]
+ ].should be_computed_by(:pack, "U")
+ end
+
+ it "encodes UTF-8 max codepoints" do
+ [ [[0x10000], "\xf0\x90\x80\x80"],
+ [[0xfffff], "\xf3\xbf\xbf\xbf"],
+ [[0x100000], "\xf4\x80\x80\x80"],
+ [[0x10ffff], "\xf4\x8f\xbf\xbf"]
+ ].should be_computed_by(:pack, "U")
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [ [[0x41, 0x42, 0x43, 0x44], "U2", "\x41\x42"],
+ [[0x41, 0x42, 0x43, 0x44], "U2U", "\x41\x42\x43"]
+ ].should be_computed_by(:pack)
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [0x41, 0x42, 0x43, 0x44].pack("U*").should == "\x41\x42\x43\x44"
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(5)
+ [obj].pack("U").should == "\x05"
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return("5")
+ -> { [obj].pack("U") }.should raise_error(TypeError)
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ [1, 2, 3].pack("U\x00U").should == "\x01\x02"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [1, 2, 3].pack("U\x00U")
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ [1, 2, 3].pack("U U").should == "\x01\x02"
+ end
+
+ it "raises a RangeError if passed a negative number" do
+ -> { [-1].pack("U") }.should raise_error(RangeError)
+ end
+
+ it "raises a RangeError if passed a number larger than an unsigned 32-bit integer" do
+ -> { [2**32].pack("U") }.should raise_error(RangeError)
+ end
+
+ it "sets the output string to UTF-8 encoding" do
+ [ [[0x00].pack("U"), Encoding::UTF_8],
+ [[0x41].pack("U"), Encoding::UTF_8],
+ [[0x7F].pack("U"), Encoding::UTF_8],
+ [[0x80].pack("U"), Encoding::UTF_8],
+ [[0x10FFFF].pack("U"), Encoding::UTF_8]
+ ].should be_computed_by(:encoding)
+ end
+end
diff --git a/spec/ruby/core/array/pack/u_spec.rb b/spec/ruby/core/array/pack/u_spec.rb
new file mode 100644
index 0000000000..b20093a647
--- /dev/null
+++ b/spec/ruby/core/array/pack/u_spec.rb
@@ -0,0 +1,140 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/unicode'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'U'" do
+ it_behaves_like :array_pack_basic, 'U'
+ it_behaves_like :array_pack_basic_non_float, 'U'
+ it_behaves_like :array_pack_arguments, 'U'
+ it_behaves_like :array_pack_unicode, 'U'
+end
+
+describe "Array#pack with format 'u'" do
+ it_behaves_like :array_pack_basic, 'u'
+ it_behaves_like :array_pack_basic_non_float, 'u'
+ it_behaves_like :array_pack_arguments, 'u'
+ it_behaves_like :array_pack_taint, 'u'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack u string")
+ obj.should_receive(:to_str).and_return("``abcdef")
+ [obj].pack("u*").should == "(8&!A8F-D968`\n"
+ end
+
+ it "will not implicitly convert a number to a string" do
+ -> { [0].pack('u') }.should raise_error(TypeError)
+ end
+
+ it "encodes an empty string as an empty string" do
+ [""].pack("u").should == ""
+ end
+
+ it "appends a newline to the end of the encoded string" do
+ ["a"].pack("u").should == "!80``\n"
+ end
+
+ it "encodes one element per directive" do
+ ["abc", "DEF"].pack("uu").should == "#86)C\n#1$5&\n"
+ end
+
+ it "prepends the length of each segment of the input string as the first character (+32) in each line of the output" do
+ ["abcdefghijklm"].pack("u7").should == "&86)C9&5F\n&9VAI:FML\n!;0``\n"
+ end
+
+ it "encodes 1, 2, or 3 characters in 4 output characters (uuencoding)" do
+ [ [["a"], "!80``\n"],
+ [["ab"], "\"86(`\n"],
+ [["abc"], "#86)C\n"],
+ [["abcd"], "$86)C9```\n"],
+ [["abcde"], "%86)C9&4`\n"],
+ [["abcdef"], "&86)C9&5F\n"],
+ [["abcdefg"], "'86)C9&5F9P``\n"],
+ ].should be_computed_by(:pack, "u")
+ end
+
+ it "emits a newline after complete groups of count / 3 input characters when passed a count modifier" do
+ ["abcdefg"].pack("u3").should == "#86)C\n#9&5F\n!9P``\n"
+ end
+
+ it "implicitly has a count of 45 when passed '*', 0, 1, 2 or no count modifier" do
+ s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ r = "M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n%86%A86$`\n"
+ [ [[s], "u", r],
+ [[s], "u*", r],
+ [[s], "u0", r],
+ [[s], "u1", r],
+ [[s], "u2", r],
+ ].should be_computed_by(:pack)
+ end
+
+ it "encodes all ascii characters" do
+ [ [["\x00\x01\x02\x03\x04\x05\x06"], "'``$\"`P0%!@``\n"],
+ [["\a\b\t\n\v\f\r"], "'!P@)\"@L,#0``\n"],
+ [["\x0E\x0F\x10\x11\x12\x13\x14\x15\x16"], ")\#@\\0$1(3%!46\n"],
+ [["\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f"], ")%Q@9&AL<'1X?\n"],
+ [["!\"\#$%&'()*+,-./"], "/(2(C)\"4F)R@I*BLL+2XO\n"],
+ [["0123456789"], "*,\#$R,S0U-C<X.0``\n"],
+ [[":;<=>?@"], "'.CL\\/3X_0```\n"],
+ [["ABCDEFGHIJKLMNOPQRSTUVWXYZ"], ":04)#1$5&1TA)2DM,34Y/4%%24U155E=865H`\n"],
+ [["[\\]^_`"], "&6UQ=7E]@\n"],
+ [["abcdefghijklmnopqrstuvwxyz"], ":86)C9&5F9VAI:FML;6YO<'%R<W1U=G=X>7H`\n"],
+ [["{|}~"], "$>WQ]?@``\n"],
+ [["\x7f\xc2\x80\xc2\x81\xc2\x82\xc2\x83"], ")?\\*`PH'\"@L*#\n"],
+ [["\xc2\x84\xc2\x85\xc2\x86\xc2\x87\xc2"], ")PH3\"A<*&PH?\"\n"],
+ [["\x88\xc2\x89\xc2\x8a\xc2\x8b\xc2\x8c"], ")B,*)PHK\"B\\*,\n"],
+ [["\xc2\x8d\xc2\x8e\xc2\x8f\xc2\x90\xc2"], ")PHW\"CL*/PI#\"\n"],
+ [["\x91\xc2\x92\xc2\x93\xc2\x94\xc2\x95"], ")D<*2PI/\"E,*5\n"],
+ [["\xc2\x96\xc2\x97\xc2\x98\xc2\x99\xc2"], ")PI;\"E\\*8PIG\"\n"],
+ [["\x9a\xc2\x9b\xc2\x9c\xc2\x9d\xc2\x9e"], ")FL*;PIS\"G<*>\n"],
+ [["\xc2\x9f\xc2\xa0\xc2\xa1\xc2\xa2\xc2"], ")PI_\"H,*APJ+\"\n"],
+ [["\xa3\xc2\xa4\xc2\xa5\xc2\xa6\xc2\xa7"], ")H\\*DPJ7\"IL*G\n"],
+ [["\xc2\xa8\xc2\xa9\xc2\xaa\xc2\xab\xc2"], ")PJC\"J<*JPJO\"\n"],
+ [["\xac\xc2\xad\xc2\xae\xc2\xaf\xc2\xb0"], ")K,*MPJ[\"K\\*P\n"],
+ [["\xc2\xb1\xc2\xb2\xc2\xb3\xc2\xb4\xc2"], ")PK'\"LL*SPK3\"\n"],
+ [["\xb5\xc2\xb6\xc2\xb7\xc2\xb8\xc2\xb9"], ")M<*VPK?\"N,*Y\n"],
+ [["\xc2\xba\xc2\xbb\xc2\xbc\xc2\xbd\xc2"], ")PKK\"N\\*\\PKW\"\n"],
+ [["\xbe\xc2\xbf\xc3\x80\xc3\x81\xc3\x82"], ")OL*_PX#\#@<.\"\n"],
+ [["\xc3\x83\xc3\x84\xc3\x85\xc3\x86\xc3"], ")PX/#A,.%PX;#\n"],
+ [["\x87\xc3\x88\xc3\x89\xc3\x8a\xc3\x8b"], ")A\\.(PXG#BL.+\n"],
+ [["\xc3\x8c\xc3\x8d\xc3\x8e\xc3\x8f\xc3"], ")PXS#C<..PX_#\n"],
+ [["\x90\xc3\x91\xc3\x92\xc3\x93\xc3\x94"], ")D,.1PY+#D\\.4\n"],
+ [["\xc3\x95\xc3\x96\xc3\x97\xc3\x98\xc3"], ")PY7#EL.7PYC#\n"],
+ [["\x99\xc3\x9a\xc3\x9b\xc3\x9c\xc3\x9d"], ")F<.:PYO#G,.=\n"],
+ [["\xc3\x9e\xc3\x9f\xc3\xa0\xc3\xa1\xc3"], ")PY[#G\\.@PZ'#\n"],
+ [["\xa2\xc3\xa3\xc3\xa4\xc3\xa5\xc3\xa6"], ")HL.CPZ3#I<.F\n"],
+ [["\xc3\xa7\xc3\xa8\xc3\xa9\xc3\xaa\xc3"], ")PZ?#J,.IPZK#\n"],
+ [["\xab\xc3\xac\xc3\xad\xc3\xae\xc3\xaf"], ")J\\.LPZW#KL.O\n"],
+ [["\xc3\xb0\xc3\xb1\xc3\xb2\xc3\xb3\xc3"], ")P[##L<.RP[/#\n"],
+ [["\xb4\xc3\xb5\xc3\xb6\xc3\xb7\xc3\xb8"], ")M,.UP[;#M\\.X\n"],
+ [["\xc3\xb9\xc3\xba\xc3\xbb\xc3\xbc\xc3"], ")P[G#NL.[P[S#\n"],
+ [["\xbd\xc3\xbe\xc3\xbf"], "%O<.^P[\\`\n"]
+ ].should be_computed_by(:pack, "u")
+ end
+
+ it "calls #to_str to convert an object to a String" do
+ obj = mock("pack m string")
+ obj.should_receive(:to_str).and_return("abc")
+ [obj].pack("u").should == "#86)C\n"
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock("pack m non-string")
+ -> { [obj].pack("u") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed nil" do
+ -> { [nil].pack("u") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an Integer" do
+ -> { [0].pack("u") }.should raise_error(TypeError)
+ -> { [bignum_value].pack("u") }.should raise_error(TypeError)
+ end
+
+ it "sets the output string to US-ASCII encoding" do
+ ["abcd"].pack("u").encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/array/pack/v_spec.rb b/spec/ruby/core/array/pack/v_spec.rb
new file mode 100644
index 0000000000..d3932c84af
--- /dev/null
+++ b/spec/ruby/core/array/pack/v_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'V'" do
+ it_behaves_like :array_pack_basic, 'V'
+ it_behaves_like :array_pack_basic_non_float, 'V'
+ it_behaves_like :array_pack_arguments, 'V'
+ it_behaves_like :array_pack_numeric_basic, 'V'
+ it_behaves_like :array_pack_integer, 'V'
+ it_behaves_like :array_pack_no_platform, 'V'
+ it_behaves_like :array_pack_32bit_le, 'V'
+end
+
+describe "Array#pack with format 'v'" do
+ it_behaves_like :array_pack_basic, 'v'
+ it_behaves_like :array_pack_basic_non_float, 'v'
+ it_behaves_like :array_pack_arguments, 'v'
+ it_behaves_like :array_pack_numeric_basic, 'v'
+ it_behaves_like :array_pack_integer, 'v'
+ it_behaves_like :array_pack_no_platform, 'v'
+ it_behaves_like :array_pack_16bit_le, 'v'
+end
diff --git a/spec/ruby/core/array/pack/w_spec.rb b/spec/ruby/core/array/pack/w_spec.rb
new file mode 100644
index 0000000000..e241d1519c
--- /dev/null
+++ b/spec/ruby/core/array/pack/w_spec.rb
@@ -0,0 +1,52 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+
+describe "Array#pack with format 'w'" do
+ it_behaves_like :array_pack_basic, 'w'
+ it_behaves_like :array_pack_basic_non_float, 'w'
+ it_behaves_like :array_pack_arguments, 'w'
+ it_behaves_like :array_pack_numeric_basic, 'w'
+
+ it "encodes a BER-compressed integer" do
+ [ [[0], "\x00"],
+ [[1], "\x01"],
+ [[9999], "\xce\x0f"],
+ [[2**65], "\x84\x80\x80\x80\x80\x80\x80\x80\x80\x00"]
+ ].should be_computed_by(:pack, "w")
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(5)
+ [obj].pack("w").should == "\x05"
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ [1, 2, 3].pack("w\x00w").should == "\x01\x02"
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [1, 2, 3].pack("w\x00w")
+ }.should raise_error(ArgumentError, /unknown pack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ [1, 2, 3].pack("w w").should == "\x01\x02"
+ end
+
+ it "raises an ArgumentError when passed a negative value" do
+ -> { [-1].pack("w") }.should raise_error(ArgumentError)
+ end
+
+ it "returns a binary string" do
+ [1].pack('w').encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/array/pack/x_spec.rb b/spec/ruby/core/array/pack/x_spec.rb
new file mode 100644
index 0000000000..86c3ad1aa4
--- /dev/null
+++ b/spec/ruby/core/array/pack/x_spec.rb
@@ -0,0 +1,65 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+
+describe "Array#pack with format 'x'" do
+ it_behaves_like :array_pack_basic, 'x'
+ it_behaves_like :array_pack_basic_non_float, 'x'
+ it_behaves_like :array_pack_no_platform, 'x'
+
+ it "adds a NULL byte with an empty array" do
+ [].pack("x").should == "\x00"
+ end
+
+ it "adds a NULL byte without consuming an element" do
+ [1, 2].pack("CxC").should == "\x01\x00\x02"
+ end
+
+ it "is not affected by a previous count modifier" do
+ [].pack("x3x").should == "\x00\x00\x00\x00"
+ end
+
+ it "adds multiple NULL bytes when passed a count modifier" do
+ [].pack("x3").should == "\x00\x00\x00"
+ end
+
+ it "does not add a NULL byte if the count modifier is zero" do
+ [].pack("x0").should == ""
+ end
+
+ it "does not add a NULL byte when passed the '*' modifier" do
+ [].pack("x*").should == ""
+ [1, 2].pack("Cx*C").should == "\x01\x02"
+ end
+end
+
+describe "Array#pack with format 'X'" do
+ it_behaves_like :array_pack_basic, 'X'
+ it_behaves_like :array_pack_basic_non_float, 'X'
+ it_behaves_like :array_pack_no_platform, 'X'
+
+ it "reduces the output string by one byte at the point it is encountered" do
+ [1, 2, 3].pack("C2XC").should == "\x01\x03"
+ end
+
+ it "does not consume any elements" do
+ [1, 2, 3].pack("CXC").should == "\x02"
+ end
+
+ it "reduces the output string by multiple bytes when passed a count modifier" do
+ [1, 2, 3, 4, 5].pack("C2X2C").should == "\x03"
+ end
+
+ it "has no affect when passed the '*' modifier" do
+ [1, 2, 3].pack("C2X*C").should == "\x01\x02\x03"
+ end
+
+ it "raises an ArgumentError if the output string is empty" do
+ -> { [1, 2, 3].pack("XC") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the count modifier is greater than the bytes in the string" do
+ -> { [1, 2, 3].pack("C2X3") }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/array/pack/z_spec.rb b/spec/ruby/core/array/pack/z_spec.rb
new file mode 100644
index 0000000000..5ad3afd69e
--- /dev/null
+++ b/spec/ruby/core/array/pack/z_spec.rb
@@ -0,0 +1,44 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/string'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'Z'" do
+ it_behaves_like :array_pack_basic, 'Z'
+ it_behaves_like :array_pack_basic_non_float, 'Z'
+ it_behaves_like :array_pack_no_platform, 'Z'
+ it_behaves_like :array_pack_string, 'Z'
+ it_behaves_like :array_pack_taint, 'Z'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack Z string")
+ obj.should_receive(:to_str).and_return("``abcdef")
+ [obj].pack("Z*").should == "``abcdef\x00"
+ end
+
+ it "will not implicitly convert a number to a string" do
+ -> { [0].pack('Z') }.should raise_error(TypeError)
+ end
+
+ it "adds all the bytes and appends a NULL byte when passed the '*' modifier" do
+ ["abc"].pack("Z*").should == "abc\x00"
+ end
+
+ it "padds the output with NULL bytes when the count exceeds the size of the String" do
+ ["abc"].pack("Z6").should == "abc\x00\x00\x00"
+ end
+
+ it "adds a NULL byte when the value is nil" do
+ [nil].pack("Z").should == "\x00"
+ end
+
+ it "pads the output with NULL bytes when the value is nil" do
+ [nil].pack("Z3").should == "\x00\x00\x00"
+ end
+
+ it "does not append a NULL byte when passed the '*' modifier and the value is nil" do
+ [nil].pack("Z*").should == "\x00"
+ end
+end
diff --git a/spec/ruby/core/array/partition_spec.rb b/spec/ruby/core/array/partition_spec.rb
new file mode 100644
index 0000000000..be36fffcab
--- /dev/null
+++ b/spec/ruby/core/array/partition_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#partition" do
+ it "returns two arrays" do
+ [].partition {}.should == [[], []]
+ end
+
+ it "returns in the left array values for which the block evaluates to true" do
+ ary = [0, 1, 2, 3, 4, 5]
+
+ ary.partition { |i| true }.should == [ary, []]
+ ary.partition { |i| 5 }.should == [ary, []]
+ ary.partition { |i| false }.should == [[], ary]
+ ary.partition { |i| nil }.should == [[], ary]
+ ary.partition { |i| i % 2 == 0 }.should == [[0, 2, 4], [1, 3, 5]]
+ ary.partition { |i| i / 3 == 0 }.should == [[0, 1, 2], [3, 4, 5]]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.partition { true }.should == [[empty], []]
+ empty.partition { false }.should == [[], [empty]]
+
+ array = ArraySpecs.recursive_array
+ array.partition { true }.should == [
+ [1, 'two', 3.0, array, array, array, array, array],
+ []
+ ]
+ condition = true
+ array.partition { condition = !condition }.should == [
+ ['two', array, array, array],
+ [1, 3.0, array, array]
+ ]
+ end
+
+ it "does not return subclass instances on Array subclasses" do
+ result = ArraySpecs::MyArray[1, 2, 3].partition { |x| x % 2 == 0 }
+ result.should be_an_instance_of(Array)
+ result[0].should be_an_instance_of(Array)
+ result[1].should be_an_instance_of(Array)
+ end
+end
diff --git a/spec/ruby/core/array/permutation_spec.rb b/spec/ruby/core/array/permutation_spec.rb
new file mode 100644
index 0000000000..f15bd76639
--- /dev/null
+++ b/spec/ruby/core/array/permutation_spec.rb
@@ -0,0 +1,138 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+
+describe "Array#permutation" do
+
+ before :each do
+ @numbers = (1..3).to_a
+ @yielded = []
+ end
+
+ it "returns an Enumerator of all permutations when called without a block or arguments" do
+ enum = @numbers.permutation
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.sort.should == [
+ [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]
+ ].sort
+ end
+
+ it "returns an Enumerator of permutations of given length when called with an argument but no block" do
+ enum = @numbers.permutation(1)
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.sort.should == [[1],[2],[3]]
+ end
+
+ it "yields all permutations to the block then returns self when called with block but no arguments" do
+ array = @numbers.permutation {|n| @yielded << n}
+ array.should be_an_instance_of(Array)
+ array.sort.should == @numbers.sort
+ @yielded.sort.should == [
+ [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]
+ ].sort
+ end
+
+ it "yields all permutations of given length to the block then returns self when called with block and argument" do
+ array = @numbers.permutation(2) {|n| @yielded << n}
+ array.should be_an_instance_of(Array)
+ array.sort.should == @numbers.sort
+ @yielded.sort.should == [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]].sort
+ end
+
+ it "returns the empty permutation ([[]]) when the given length is 0" do
+ @numbers.permutation(0).to_a.should == [[]]
+ @numbers.permutation(0) { |n| @yielded << n }
+ @yielded.should == [[]]
+ end
+
+ it "returns the empty permutation([]) when called on an empty Array" do
+ [].permutation.to_a.should == [[]]
+ [].permutation { |n| @yielded << n }
+ @yielded.should == [[]]
+ end
+
+ it "returns no permutations when the given length has no permutations" do
+ @numbers.permutation(9).entries.size.should == 0
+ @numbers.permutation(9) { |n| @yielded << n }
+ @yielded.should == []
+ end
+
+ it "handles duplicate elements correctly" do
+ @numbers << 1
+ @numbers.permutation(2).sort.should == [
+ [1,1],[1,1],[1,2],[1,2],[1,3],[1,3],
+ [2,1],[2,1],[2,3],
+ [3,1],[3,1],[3,2]
+ ].sort
+ end
+
+ it "handles nested Arrays correctly" do
+ # The ugliness is due to the order of permutations returned by
+ # permutation being undefined combined with #sort croaking on Arrays of
+ # Arrays.
+ @numbers << [4,5]
+ got = @numbers.permutation(2).to_a
+ expected = [
+ [1, 2], [1, 3], [1, [4, 5]],
+ [2, 1], [2, 3], [2, [4, 5]],
+ [3, 1], [3, 2], [3, [4, 5]],
+ [[4, 5], 1], [[4, 5], 2], [[4, 5], 3]
+ ]
+ expected.each {|e| got.include?(e).should be_true}
+ got.size.should == expected.size
+ end
+
+ it "truncates Float arguments" do
+ @numbers.permutation(3.7).to_a.sort.should ==
+ @numbers.permutation(3).to_a.sort
+ end
+
+ it "returns an Enumerator which works as expected even when the array was modified" do
+ @numbers = [1, 2]
+ enum = @numbers.permutation
+ @numbers << 3
+ enum.to_a.sort.should == [
+ [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]
+ ].sort
+ end
+
+ it "generates from a defensive copy, ignoring mutations" do
+ accum = []
+ ary = [1,2,3]
+ ary.permutation(3) do |x|
+ accum << x
+ ary[0] = 5
+ end
+
+ accum.should == [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ describe "with an array size greater than 0" do
+ it "returns the descending factorial of array size and given length" do
+ @numbers.permutation(4).size.should == 0
+ @numbers.permutation(3).size.should == 6
+ @numbers.permutation(2).size.should == 6
+ @numbers.permutation(1).size.should == 3
+ @numbers.permutation(0).size.should == 1
+ end
+ it "returns the descending factorial of array size with array size when there's no param" do
+ @numbers.permutation.size.should == 6
+ [1,2,3,4].permutation.size.should == 24
+ [1].permutation.size.should == 1
+ end
+ end
+ describe "with an empty array" do
+ it "returns 1 when the given length is 0" do
+ [].permutation(0).size.should == 1
+ end
+ it "returns 1 when there's param" do
+ [].permutation.size.should == 1
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/plus_spec.rb b/spec/ruby/core/array/plus_spec.rb
new file mode 100644
index 0000000000..3962b05c39
--- /dev/null
+++ b/spec/ruby/core/array/plus_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#+" do
+ it "concatenates two arrays" do
+ ([ 1, 2, 3 ] + [ 3, 4, 5 ]).should == [1, 2, 3, 3, 4, 5]
+ ([ 1, 2, 3 ] + []).should == [1, 2, 3]
+ ([] + [ 1, 2, 3 ]).should == [1, 2, 3]
+ ([] + []).should == []
+ end
+
+ it "can concatenate an array with itself" do
+ ary = [1, 2, 3]
+ (ary + ary).should == [1, 2, 3, 1, 2, 3]
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('["x", "y"]')
+ obj.should_receive(:to_ary).and_return(["x", "y"])
+ ([1, 2, 3] + obj).should == [1, 2, 3, "x", "y"]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ (empty + empty).should == [empty, empty]
+
+ array = ArraySpecs.recursive_array
+ (empty + array).should == [empty, 1, 'two', 3.0, array, array, array, array, array]
+ (array + array).should == [
+ 1, 'two', 3.0, array, array, array, array, array,
+ 1, 'two', 3.0, array, array, array, array, array]
+ end
+
+ it "does return subclass instances with Array subclasses" do
+ (ArraySpecs::MyArray[1, 2, 3] + []).should be_an_instance_of(Array)
+ (ArraySpecs::MyArray[1, 2, 3] + ArraySpecs::MyArray[]).should be_an_instance_of(Array)
+ ([1, 2, 3] + ArraySpecs::MyArray[]).should be_an_instance_of(Array)
+ end
+
+ it "does not call to_ary on array subclasses" do
+ ([5, 6] + ArraySpecs::ToAryArray[1, 2]).should == [5, 6, 1, 2]
+ end
+end
diff --git a/spec/ruby/core/array/pop_spec.rb b/spec/ruby/core/array/pop_spec.rb
new file mode 100644
index 0000000000..2a19408660
--- /dev/null
+++ b/spec/ruby/core/array/pop_spec.rb
@@ -0,0 +1,124 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#pop" do
+ it "removes and returns the last element of the array" do
+ a = ["a", 1, nil, true]
+
+ a.pop.should == true
+ a.should == ["a", 1, nil]
+
+ a.pop.should == nil
+ a.should == ["a", 1]
+
+ a.pop.should == 1
+ a.should == ["a"]
+
+ a.pop.should == "a"
+ a.should == []
+ end
+
+ it "returns nil if there are no more elements" do
+ [].pop.should == nil
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.pop.should == []
+
+ array = ArraySpecs.recursive_array
+ array.pop.should == [1, 'two', 3.0, array, array, array, array]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.pop }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.pop }.should raise_error(FrozenError)
+ end
+
+ describe "passed a number n as an argument" do
+ it "removes and returns an array with the last n elements of the array" do
+ a = [1, 2, 3, 4, 5, 6]
+
+ a.pop(0).should == []
+ a.should == [1, 2, 3, 4, 5, 6]
+
+ a.pop(1).should == [6]
+ a.should == [1, 2, 3, 4, 5]
+
+ a.pop(2).should == [4, 5]
+ a.should == [1, 2, 3]
+
+ a.pop(3).should == [1, 2, 3]
+ a.should == []
+ end
+
+ it "returns an array with the last n elements even if shift was invoked" do
+ a = [1, 2, 3, 4]
+ a.shift
+ a.pop(3).should == [2, 3, 4]
+ end
+
+ it "returns a new empty array if there are no more elements" do
+ a = []
+ popped1 = a.pop(1)
+ popped1.should == []
+ a.should == []
+
+ popped2 = a.pop(2)
+ popped2.should == []
+ a.should == []
+
+ popped1.should_not equal(popped2)
+ end
+
+ it "returns whole elements if n exceeds size of the array" do
+ a = [1, 2, 3, 4, 5]
+ a.pop(6).should == [1, 2, 3, 4, 5]
+ a.should == []
+ end
+
+ it "does not return self even when it returns whole elements" do
+ a = [1, 2, 3, 4, 5]
+ a.pop(5).should_not equal(a)
+
+ a = [1, 2, 3, 4, 5]
+ a.pop(6).should_not equal(a)
+ end
+
+ it "raises an ArgumentError if n is negative" do
+ ->{ [1, 2, 3].pop(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ a = [1, 2, 3, 4]
+ a.pop(2.3).should == [3, 4]
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ a.should == [1, 2]
+ a.pop(obj).should == [1, 2]
+ a.should == []
+ end
+
+ it "raises a TypeError when the passed n cannot be coerced to Integer" do
+ ->{ [1, 2].pop("cat") }.should raise_error(TypeError)
+ ->{ [1, 2].pop(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError if more arguments are passed" do
+ ->{ [1, 2].pop(1, 2) }.should raise_error(ArgumentError)
+ end
+
+ it "does not return subclass instances with Array subclass" do
+ ArraySpecs::MyArray[1, 2, 3].pop(2).should be_an_instance_of(Array)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.pop(2) }.should raise_error(FrozenError)
+ -> { ArraySpecs.frozen_array.pop(0) }.should raise_error(FrozenError)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/prepend_spec.rb b/spec/ruby/core/array/prepend_spec.rb
new file mode 100644
index 0000000000..368b8dcfcd
--- /dev/null
+++ b/spec/ruby/core/array/prepend_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/unshift'
+
+describe "Array#prepend" do
+ it_behaves_like :array_unshift, :prepend
+end
diff --git a/spec/ruby/core/array/product_spec.rb b/spec/ruby/core/array/product_spec.rb
new file mode 100644
index 0000000000..07d2880a96
--- /dev/null
+++ b/spec/ruby/core/array/product_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#product" do
+ it "returns converted arguments using :to_ary" do
+ ->{ [1].product(2..3) }.should raise_error(TypeError)
+ ar = ArraySpecs::ArrayConvertible.new(2,3)
+ [1].product(ar).should == [[1,2],[1,3]]
+ ar.called.should == :to_ary
+ end
+
+ it "returns the expected result" do
+ [1,2].product([3,4,5],[6,8]).should == [[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8],
+ [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8]]
+ end
+
+ it "has no required argument" do
+ [1,2].product.should == [[1],[2]]
+ end
+
+ it "returns an empty array when the argument is an empty array" do
+ [1, 2].product([]).should == []
+ end
+
+ it "does not attempt to produce an unreasonable number of products" do
+ a = (0..100).to_a
+ -> do
+ a.product(a, a, a, a, a, a, a, a, a, a)
+ end.should raise_error(RangeError)
+ end
+
+ describe "when given a block" do
+ it "yields all combinations in turn" do
+ acc = []
+ [1,2].product([3,4,5],[6,8]){|array| acc << array}
+ acc.should == [[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8],
+ [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8]]
+
+ acc = []
+ [1,2].product([3,4,5],[],[6,8]){|array| acc << array}
+ acc.should be_empty
+ end
+
+ it "returns self" do
+ a = [1, 2, 3].freeze
+
+ a.product([1, 2]) { |p| p.first }.should == a
+ end
+
+ it "will ignore unreasonable numbers of products and yield anyway" do
+ a = (0..100).to_a
+ -> do
+ a.product(a, a, a, a, a, a, a, a, a, a)
+ end.should raise_error(RangeError)
+ end
+ end
+
+ describe "when given an empty block" do
+ it "returns self" do
+ arr = [1,2]
+ arr.product([3,4,5],[6,8]){}.should equal(arr)
+ arr = []
+ arr.product([3,4,5],[6,8]){}.should equal(arr)
+ arr = [1,2]
+ arr.product([]){}.should equal(arr)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/push_spec.rb b/spec/ruby/core/array/push_spec.rb
new file mode 100644
index 0000000000..607cbc7b4d
--- /dev/null
+++ b/spec/ruby/core/array/push_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/push'
+
+describe "Array#push" do
+ it_behaves_like :array_push, :push
+end
diff --git a/spec/ruby/core/array/rassoc_spec.rb b/spec/ruby/core/array/rassoc_spec.rb
new file mode 100644
index 0000000000..62fbd40611
--- /dev/null
+++ b/spec/ruby/core/array/rassoc_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#rassoc" do
+ it "returns the first contained array whose second element is == object" do
+ ary = [[1, "a", 0.5], [2, "b"], [3, "b"], [4, "c"], [], [5], [6, "d"]]
+ ary.rassoc("a").should == [1, "a", 0.5]
+ ary.rassoc("b").should == [2, "b"]
+ ary.rassoc("d").should == [6, "d"]
+ ary.rassoc("z").should == nil
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.rassoc([]).should be_nil
+ [[empty, empty]].rassoc(empty).should == [empty, empty]
+
+ array = ArraySpecs.recursive_array
+ array.rassoc(array).should be_nil
+ [[empty, array]].rassoc(array).should == [empty, array]
+ end
+
+ it "calls elem == obj on the second element of each contained array" do
+ key = 'foobar'
+ o = mock('foobar')
+ def o.==(other); other == 'foobar'; end
+
+ [[1, :foobar], [2, o], [3, mock('foo')]].rassoc(key).should == [2, o]
+ end
+
+ it "does not check the last element in each contained but specifically the second" do
+ key = 'foobar'
+ o = mock('foobar')
+ def o.==(other); other == 'foobar'; end
+
+ [[1, :foobar, o], [2, o, 1], [3, mock('foo')]].rassoc(key).should == [2, o, 1]
+ end
+end
diff --git a/spec/ruby/core/array/reject_spec.rb b/spec/ruby/core/array/reject_spec.rb
new file mode 100644
index 0000000000..fcf43fabde
--- /dev/null
+++ b/spec/ruby/core/array/reject_spec.rb
@@ -0,0 +1,143 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative 'shared/delete_if'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#reject" do
+ it "returns a new array without elements for which block is true" do
+ ary = [1, 2, 3, 4, 5]
+ ary.reject { true }.should == []
+ ary.reject { false }.should == ary
+ ary.reject { false }.should_not equal ary
+ ary.reject { nil }.should == ary
+ ary.reject { nil }.should_not equal ary
+ ary.reject { 5 }.should == []
+ ary.reject { |i| i < 3 }.should == [3, 4, 5]
+ ary.reject { |i| i % 2 == 0 }.should == [1, 3, 5]
+ end
+
+ it "returns self when called on an Array emptied with #shift" do
+ array = [1]
+ array.shift
+ array.reject { |x| true }.should == []
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.reject { false }.should == [empty]
+ empty.reject { true }.should == []
+
+ array = ArraySpecs.recursive_array
+ array.reject { false }.should == [1, 'two', 3.0, array, array, array, array, array]
+ array.reject { true }.should == []
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].reject { |x| x % 2 == 0 }.should be_an_instance_of(Array)
+ end
+
+ it "does not retain instance variables" do
+ array = []
+ array.instance_variable_set("@variable", "value")
+ array.reject { false }.instance_variable_get("@variable").should == nil
+ end
+
+ it_behaves_like :enumeratorize, :reject
+ it_behaves_like :enumeratorized_with_origin_size, :reject, [1,2,3]
+end
+
+describe "Array#reject!" do
+ it "removes elements for which block is true" do
+ a = [3, 4, 5, 6, 7, 8, 9, 10, 11]
+ a.reject! { |i| i % 2 == 0 }.should equal(a)
+ a.should == [3, 5, 7, 9, 11]
+ a.reject! { |i| i > 8 }
+ a.should == [3, 5, 7]
+ a.reject! { |i| i < 4 }
+ a.should == [5, 7]
+ a.reject! { |i| i == 5 }
+ a.should == [7]
+ a.reject! { true }
+ a.should == []
+ a.reject! { true }
+ a.should == []
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty_dup = empty.dup
+ empty.reject! { false }.should == nil
+ empty.should == empty_dup
+
+ empty = ArraySpecs.empty_recursive_array
+ empty.reject! { true }.should == []
+ empty.should == []
+
+ array = ArraySpecs.recursive_array
+ array_dup = array.dup
+ array.reject! { false }.should == nil
+ array.should == array_dup
+
+ array = ArraySpecs.recursive_array
+ array.reject! { true }.should == []
+ array.should == []
+ end
+
+ it "returns nil when called on an Array emptied with #shift" do
+ array = [1]
+ array.shift
+ array.reject! { |x| true }.should == nil
+ end
+
+ it "returns nil if no changes are made" do
+ a = [1, 2, 3]
+
+ a.reject! { |i| i < 0 }.should == nil
+
+ a.reject! { true }
+ a.reject! { true }.should == nil
+ end
+
+ it "returns an Enumerator if no block given, and the array is frozen" do
+ ArraySpecs.frozen_array.reject!.should be_an_instance_of(Enumerator)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.reject! {} }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.reject! {} }.should raise_error(FrozenError)
+ end
+
+ it "does not truncate the array is the block raises an exception" do
+ a = [1, 2, 3]
+ begin
+ a.reject! { raise StandardError, 'Oops' }
+ rescue
+ end
+
+ a.should == [1, 2, 3]
+ end
+
+ it "only removes elements for which the block returns true, keeping the element which raised an error." do
+ a = [1, 2, 3, 4]
+ begin
+ a.reject! do |x|
+ case x
+ when 2 then true
+ when 3 then raise StandardError, 'Oops'
+ else false
+ end
+ end
+ rescue StandardError
+ end
+
+ a.should == [1, 3, 4]
+ end
+
+ it_behaves_like :enumeratorize, :reject!
+ it_behaves_like :enumeratorized_with_origin_size, :reject!, [1,2,3]
+ it_behaves_like :delete_if, :reject!
+end
diff --git a/spec/ruby/core/array/repeated_combination_spec.rb b/spec/ruby/core/array/repeated_combination_spec.rb
new file mode 100644
index 0000000000..b62382024a
--- /dev/null
+++ b/spec/ruby/core/array/repeated_combination_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+
+describe "Array#repeated_combination" do
+ before :each do
+ @array = [10, 11, 12]
+ end
+
+ it "returns an enumerator when no block is provided" do
+ @array.repeated_combination(2).should be_an_instance_of(Enumerator)
+ end
+
+ it "returns self when a block is given" do
+ @array.repeated_combination(2){}.should equal(@array)
+ end
+
+ it "yields nothing for negative length and return self" do
+ @array.repeated_combination(-1){ fail }.should equal(@array)
+ @array.repeated_combination(-10){ fail }.should equal(@array)
+ end
+
+ it "yields the expected repeated_combinations" do
+ @array.repeated_combination(2).to_a.sort.should == [[10, 10], [10, 11], [10, 12], [11, 11], [11, 12], [12, 12]]
+ @array.repeated_combination(3).to_a.sort.should == [[10, 10, 10], [10, 10, 11], [10, 10, 12], [10, 11, 11], [10, 11, 12],
+ [10, 12, 12], [11, 11, 11], [11, 11, 12], [11, 12, 12], [12, 12, 12]]
+ end
+
+ it "yields [] when length is 0" do
+ @array.repeated_combination(0).to_a.should == [[]] # one repeated_combination of length 0
+ [].repeated_combination(0).to_a.should == [[]] # one repeated_combination of length 0
+ end
+
+ it "yields nothing when the array is empty and num is non zero" do
+ [].repeated_combination(5).to_a.should == [] # one repeated_combination of length 0
+ end
+
+ it "yields a partition consisting of only singletons" do
+ @array.repeated_combination(1).sort.to_a.should == [[10],[11],[12]]
+ end
+
+ it "accepts sizes larger than the original array" do
+ @array.repeated_combination(4).to_a.sort.should ==
+ [[10, 10, 10, 10], [10, 10, 10, 11], [10, 10, 10, 12],
+ [10, 10, 11, 11], [10, 10, 11, 12], [10, 10, 12, 12],
+ [10, 11, 11, 11], [10, 11, 11, 12], [10, 11, 12, 12],
+ [10, 12, 12, 12], [11, 11, 11, 11], [11, 11, 11, 12],
+ [11, 11, 12, 12], [11, 12, 12, 12], [12, 12, 12, 12]]
+ end
+
+ it "generates from a defensive copy, ignoring mutations" do
+ accum = []
+ @array.repeated_combination(2) do |x|
+ accum << x
+ @array[0] = 1
+ end
+ accum.sort.should == [[10, 10], [10, 11], [10, 12], [11, 11], [11, 12], [12, 12]]
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns 0 when the combination_size is < 0" do
+ @array.repeated_combination(-1).size.should == 0
+ [].repeated_combination(-2).size.should == 0
+ end
+
+ it "returns 1 when the combination_size is 0" do
+ @array.repeated_combination(0).size.should == 1
+ [].repeated_combination(0).size.should == 1
+ end
+
+ it "returns the binomial coefficient between combination_size and array size + combination_size -1" do
+ @array.repeated_combination(5).size.should == 21
+ @array.repeated_combination(4).size.should == 15
+ @array.repeated_combination(3).size.should == 10
+ @array.repeated_combination(2).size.should == 6
+ @array.repeated_combination(1).size.should == 3
+ @array.repeated_combination(0).size.should == 1
+ [].repeated_combination(0).size.should == 1
+ [].repeated_combination(1).size.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/repeated_permutation_spec.rb b/spec/ruby/core/array/repeated_permutation_spec.rb
new file mode 100644
index 0000000000..a165fda09e
--- /dev/null
+++ b/spec/ruby/core/array/repeated_permutation_spec.rb
@@ -0,0 +1,94 @@
+require_relative '../../spec_helper'
+
+
+describe "Array#repeated_permutation" do
+
+ before :each do
+ @numbers = [10, 11, 12]
+ @permutations = [[10, 10], [10, 11], [10, 12], [11, 10], [11, 11], [11, 12], [12, 10], [12, 11], [12, 12]]
+ end
+
+ it "returns an Enumerator of all repeated permutations of given length when called without a block" do
+ enum = @numbers.repeated_permutation(2)
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.sort.should == @permutations
+ end
+
+ it "yields all repeated_permutations to the block then returns self when called with block but no arguments" do
+ yielded = []
+ @numbers.repeated_permutation(2) {|n| yielded << n}.should equal(@numbers)
+ yielded.sort.should == @permutations
+ end
+
+ it "yields the empty repeated_permutation ([[]]) when the given length is 0" do
+ @numbers.repeated_permutation(0).to_a.should == [[]]
+ [].repeated_permutation(0).to_a.should == [[]]
+ end
+
+ it "does not yield when called on an empty Array with a nonzero argument" do
+ [].repeated_permutation(10).to_a.should == []
+ end
+
+ it "handles duplicate elements correctly" do
+ @numbers[-1] = 10
+ @numbers.repeated_permutation(2).sort.should ==
+ [[10, 10], [10, 10], [10, 10], [10, 10], [10, 11], [10, 11], [11, 10], [11, 10], [11, 11]]
+ end
+
+ it "truncates Float arguments" do
+ @numbers.repeated_permutation(3.7).to_a.sort.should ==
+ @numbers.repeated_permutation(3).to_a.sort
+ end
+
+ it "returns an Enumerator which works as expected even when the array was modified" do
+ @numbers.shift
+ enum = @numbers.repeated_permutation(2)
+ @numbers.unshift 10
+ enum.to_a.sort.should == @permutations
+ end
+
+ it "allows permutations larger than the number of elements" do
+ [1,2].repeated_permutation(3).sort.should ==
+ [[1, 1, 1], [1, 1, 2], [1, 2, 1],
+ [1, 2, 2], [2, 1, 1], [2, 1, 2],
+ [2, 2, 1], [2, 2, 2]]
+ end
+
+ it "generates from a defensive copy, ignoring mutations" do
+ accum = []
+ ary = [1,2]
+ ary.repeated_permutation(3) do |x|
+ accum << x
+ ary[0] = 5
+ end
+
+ accum.sort.should ==
+ [[1, 1, 1], [1, 1, 2], [1, 2, 1],
+ [1, 2, 2], [2, 1, 1], [2, 1, 2],
+ [2, 2, 1], [2, 2, 2]]
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns 0 when combination_size is < 0" do
+ @numbers.repeated_permutation(-1).size.should == 0
+ [].repeated_permutation(-1).size.should == 0
+ end
+
+ it "returns array size ** combination_size" do
+ @numbers.repeated_permutation(4).size.should == 81
+ @numbers.repeated_permutation(3).size.should == 27
+ @numbers.repeated_permutation(2).size.should == 9
+ @numbers.repeated_permutation(1).size.should == 3
+ @numbers.repeated_permutation(0).size.should == 1
+ [].repeated_permutation(4).size.should == 0
+ [].repeated_permutation(3).size.should == 0
+ [].repeated_permutation(2).size.should == 0
+ [].repeated_permutation(1).size.should == 0
+ [].repeated_permutation(0).size.should == 1
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/replace_spec.rb b/spec/ruby/core/array/replace_spec.rb
new file mode 100644
index 0000000000..2f53338f5e
--- /dev/null
+++ b/spec/ruby/core/array/replace_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/replace'
+
+describe "Array#replace" do
+ it_behaves_like :array_replace, :replace
+end
diff --git a/spec/ruby/core/array/reverse_each_spec.rb b/spec/ruby/core/array/reverse_each_spec.rb
new file mode 100644
index 0000000000..28b8bfcb34
--- /dev/null
+++ b/spec/ruby/core/array/reverse_each_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative '../enumerable/shared/enumeratorized'
+
+# Modifying a collection while the contents are being iterated
+# gives undefined behavior. See
+# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/23633
+
+describe "Array#reverse_each" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "traverses array in reverse order and pass each element to block" do
+ [1, 3, 4, 6].reverse_each { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [6, 4, 3, 1]
+ end
+
+ it "returns self" do
+ a = [:a, :b, :c]
+ a.reverse_each { |x| }.should equal(a)
+ end
+
+ it "yields only the top level element of an empty recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.reverse_each { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [empty]
+ end
+
+ it "yields only the top level element of a recursive array" do
+ array = ArraySpecs.recursive_array
+ array.reverse_each { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [array, array, array, array, array, 3.0, 'two', 1]
+ end
+
+ it "returns the correct size when no block is given" do
+ [1, 2, 3].reverse_each.size.should == 3
+ end
+
+ it_behaves_like :enumeratorize, :reverse_each
+ it_behaves_like :enumeratorized_with_origin_size, :reverse_each, [1,2,3]
+end
diff --git a/spec/ruby/core/array/reverse_spec.rb b/spec/ruby/core/array/reverse_spec.rb
new file mode 100644
index 0000000000..05dbd2efcf
--- /dev/null
+++ b/spec/ruby/core/array/reverse_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#reverse" do
+ it "returns a new array with the elements in reverse order" do
+ [].reverse.should == []
+ [1, 3, 5, 2].reverse.should == [2, 5, 3, 1]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.reverse.should == empty
+
+ array = ArraySpecs.recursive_array
+ array.reverse.should == [array, array, array, array, array, 3.0, 'two', 1]
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].reverse.should be_an_instance_of(Array)
+ end
+end
+
+describe "Array#reverse!" do
+ it "reverses the elements in place" do
+ a = [6, 3, 4, 2, 1]
+ a.reverse!.should equal(a)
+ a.should == [1, 2, 4, 3, 6]
+ [].reverse!.should == []
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.reverse!.should == [empty]
+
+ array = ArraySpecs.recursive_array
+ array.reverse!.should == [array, array, array, array, array, 3.0, 'two', 1]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.reverse! }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/rindex_spec.rb b/spec/ruby/core/array/rindex_spec.rb
new file mode 100644
index 0000000000..175c7bcfe2
--- /dev/null
+++ b/spec/ruby/core/array/rindex_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../enumerable/shared/enumeratorized'
+
+# Modifying a collection while the contents are being iterated
+# gives undefined behavior. See
+# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/23633
+
+describe "Array#rindex" do
+ it "returns the first index backwards from the end where element == to object" do
+ key = 3
+ uno = mock('one')
+ dos = mock('two')
+ tres = mock('three')
+ tres.should_receive(:==).any_number_of_times.and_return(false)
+ dos.should_receive(:==).any_number_of_times.and_return(true)
+ uno.should_not_receive(:==)
+ ary = [uno, dos, tres]
+
+ ary.rindex(key).should == 1
+ end
+
+ it "returns size-1 if last element == to object" do
+ [2, 1, 3, 2, 5].rindex(5).should == 4
+ end
+
+ it "returns 0 if only first element == to object" do
+ [2, 1, 3, 1, 5].rindex(2).should == 0
+ end
+
+ it "returns nil if no element == to object" do
+ [1, 1, 3, 2, 1, 3].rindex(4).should == nil
+ end
+
+ it "returns correct index even after delete_at" do
+ array = ["fish", "bird", "lion", "cat"]
+ array.delete_at(0)
+ array.rindex("lion").should == 1
+ end
+
+ it "properly handles empty recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.rindex(empty).should == 0
+ empty.rindex(1).should be_nil
+ end
+
+ it "properly handles recursive arrays" do
+ array = ArraySpecs.recursive_array
+ array.rindex(1).should == 0
+ array.rindex(array).should == 7
+ end
+
+ it "accepts a block instead of an argument" do
+ [4, 2, 1, 5, 1, 3].rindex { |x| x < 2 }.should == 4
+ end
+
+ it "ignores the block if there is an argument" do
+ -> {
+ [4, 2, 1, 5, 1, 3].rindex(5) { |x| x < 2 }.should == 3
+ }.should complain(/given block not used/)
+ end
+
+ it "rechecks the array size during iteration" do
+ ary = [4, 2, 1, 5, 1, 3]
+ seen = []
+ ary.rindex { |x| seen << x; ary.clear; false }
+
+ seen.should == [3]
+ end
+
+ describe "given no argument and no block" do
+ it "produces an Enumerator" do
+ enum = [4, 2, 1, 5, 1, 3].rindex
+ enum.should be_an_instance_of(Enumerator)
+ enum.each { |x| x < 2 }.should == 4
+ end
+ end
+
+ it_behaves_like :enumeratorized_with_unknown_size, :bsearch, [1,2,3]
+end
diff --git a/spec/ruby/core/array/rotate_spec.rb b/spec/ruby/core/array/rotate_spec.rb
new file mode 100644
index 0000000000..60dcc8b113
--- /dev/null
+++ b/spec/ruby/core/array/rotate_spec.rb
@@ -0,0 +1,129 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#rotate" do
+ describe "when passed no argument" do
+ it "returns a copy of the array with the first element moved at the end" do
+ [1, 2, 3, 4, 5].rotate.should == [2, 3, 4, 5, 1]
+ end
+ end
+
+ describe "with an argument n" do
+ it "returns a copy of the array with the first (n % size) elements moved at the end" do
+ a = [1, 2, 3, 4, 5]
+ a.rotate( 2).should == [3, 4, 5, 1, 2]
+ a.rotate( -1).should == [5, 1, 2, 3, 4]
+ a.rotate(-21).should == [5, 1, 2, 3, 4]
+ a.rotate( 13).should == [4, 5, 1, 2, 3]
+ a.rotate( 0).should == a
+ end
+
+ it "coerces the argument using to_int" do
+ [1, 2, 3].rotate(2.6).should == [3, 1, 2]
+
+ obj = mock('integer_like')
+ obj.should_receive(:to_int).and_return(2)
+ [1, 2, 3].rotate(obj).should == [3, 1, 2]
+ end
+
+ it "raises a TypeError if not passed an integer-like argument" do
+ -> {
+ [1, 2].rotate(nil)
+ }.should raise_error(TypeError)
+ -> {
+ [1, 2].rotate("4")
+ }.should raise_error(TypeError)
+ end
+ end
+
+ it "returns a copy of the array when its length is one or zero" do
+ [1].rotate.should == [1]
+ [1].rotate(2).should == [1]
+ [1].rotate(-42).should == [1]
+ [ ].rotate.should == []
+ [ ].rotate(2).should == []
+ [ ].rotate(-42).should == []
+ end
+
+ it "does not mutate the receiver" do
+ -> {
+ [].freeze.rotate
+ [2].freeze.rotate(2)
+ [1,2,3].freeze.rotate(-3)
+ }.should_not raise_error
+ end
+
+ it "does not return self" do
+ a = [1, 2, 3]
+ a.rotate.should_not equal(a)
+ a = []
+ a.rotate(0).should_not equal(a)
+ end
+
+ it "does not return subclass instance for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].rotate.should be_an_instance_of(Array)
+ end
+end
+
+describe "Array#rotate!" do
+ describe "when passed no argument" do
+ it "moves the first element to the end and returns self" do
+ a = [1, 2, 3, 4, 5]
+ a.rotate!.should equal(a)
+ a.should == [2, 3, 4, 5, 1]
+ end
+ end
+
+ describe "with an argument n" do
+ it "moves the first (n % size) elements at the end and returns self" do
+ a = [1, 2, 3, 4, 5]
+ a.rotate!(2).should equal(a)
+ a.should == [3, 4, 5, 1, 2]
+ a.rotate!(-12).should equal(a)
+ a.should == [1, 2, 3, 4, 5]
+ a.rotate!(13).should equal(a)
+ a.should == [4, 5, 1, 2, 3]
+ end
+
+ it "coerces the argument using to_int" do
+ [1, 2, 3].rotate!(2.6).should == [3, 1, 2]
+
+ obj = mock('integer_like')
+ obj.should_receive(:to_int).and_return(2)
+ [1, 2, 3].rotate!(obj).should == [3, 1, 2]
+ end
+
+ it "raises a TypeError if not passed an integer-like argument" do
+ -> {
+ [1, 2].rotate!(nil)
+ }.should raise_error(TypeError)
+ -> {
+ [1, 2].rotate!("4")
+ }.should raise_error(TypeError)
+ end
+ end
+
+ it "does nothing and returns self when the length is zero or one" do
+ a = [1]
+ a.rotate!.should equal(a)
+ a.should == [1]
+ a.rotate!(2).should equal(a)
+ a.should == [1]
+ a.rotate!(-21).should equal(a)
+ a.should == [1]
+
+ a = []
+ a.rotate!.should equal(a)
+ a.should == []
+ a.rotate!(2).should equal(a)
+ a.should == []
+ a.rotate!(-21).should equal(a)
+ a.should == []
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { [1, 2, 3].freeze.rotate!(0) }.should raise_error(FrozenError)
+ -> { [1].freeze.rotate!(42) }.should raise_error(FrozenError)
+ -> { [].freeze.rotate! }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/sample_spec.rb b/spec/ruby/core/array/sample_spec.rb
new file mode 100644
index 0000000000..5b3aac9aed
--- /dev/null
+++ b/spec/ruby/core/array/sample_spec.rb
@@ -0,0 +1,144 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#sample" do
+ it "samples evenly" do
+ ArraySpecs.measure_sample_fairness(4, 1, 400)
+ ArraySpecs.measure_sample_fairness(4, 2, 400)
+ ArraySpecs.measure_sample_fairness(4, 3, 400)
+ ArraySpecs.measure_sample_fairness(40, 3, 400)
+ ArraySpecs.measure_sample_fairness(40, 4, 400)
+ ArraySpecs.measure_sample_fairness(40, 8, 400)
+ ArraySpecs.measure_sample_fairness(40, 16, 400)
+ ArraySpecs.measure_sample_fairness_large_sample_size(100, 80, 4000)
+ end
+
+ it "returns nil for an empty Array" do
+ [].sample.should be_nil
+ end
+
+ it "returns nil for an empty array when called without n and a Random is given" do
+ [].sample(random: Random.new(42)).should be_nil
+ end
+
+ it "returns a single value when not passed a count" do
+ [4].sample.should equal(4)
+ end
+
+ it "returns a single value when not passed a count and a Random is given" do
+ [4].sample(random: Random.new(42)).should equal(4)
+ end
+
+ it "returns an empty Array when passed zero" do
+ [4].sample(0).should == []
+ end
+
+ it "returns an Array of elements when passed a count" do
+ [1, 2, 3, 4].sample(3).should be_an_instance_of(Array)
+ end
+
+ it "returns elements from the Array" do
+ array = [1, 2, 3, 4]
+ array.sample(3).all? { |x| array.should include(x) }
+ end
+
+ it "returns at most the number of elements in the Array" do
+ array = [1, 2, 3, 4]
+ result = array.sample(20)
+ result.size.should == 4
+ end
+
+ it "does not return the same value if the Array has unique values" do
+ array = [1, 2, 3, 4]
+ result = array.sample(20)
+ result.sort.should == array
+ end
+
+ it "may return the same value if the array is not unique" do
+ [4, 4].sample(2).should == [4,4]
+ end
+
+ it "calls #to_int to convert the count when passed an Object" do
+ [1, 2, 3, 4].sample(mock_int(2)).size.should == 2
+ end
+
+ it "raises ArgumentError when passed a negative count" do
+ -> { [1, 2].sample(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "does not return subclass instances with Array subclass" do
+ ArraySpecs::MyArray[1, 2, 3].sample(2).should be_an_instance_of(Array)
+ end
+
+ describe "with options" do
+ it "calls #rand on the Object passed by the :random key in the arguments Hash" do
+ obj = mock("array_sample_random")
+ obj.should_receive(:rand).and_return(0.5)
+
+ [1, 2].sample(random: obj).should be_an_instance_of(Integer)
+ end
+
+ it "raises a NoMethodError if an object passed for the RNG does not define #rand" do
+ obj = BasicObject.new
+
+ -> { [1, 2].sample(random: obj) }.should raise_error(NoMethodError)
+ end
+
+ describe "when the object returned by #rand is an Integer" do
+ it "uses the integer as index" do
+ random = mock("array_sample_random_ret")
+ random.should_receive(:rand).and_return(0)
+
+ [1, 2].sample(random: random).should == 1
+
+ random = mock("array_sample_random_ret")
+ random.should_receive(:rand).and_return(1)
+
+ [1, 2].sample(random: random).should == 2
+ end
+
+ it "raises a RangeError if the value is less than zero" do
+ random = mock("array_sample_random")
+ random.should_receive(:rand).and_return(-1)
+
+ -> { [1, 2].sample(random: random) }.should raise_error(RangeError)
+ end
+
+ it "raises a RangeError if the value is equal to the Array size" do
+ random = mock("array_sample_random")
+ random.should_receive(:rand).and_return(2)
+
+ -> { [1, 2].sample(random: random) }.should raise_error(RangeError)
+ end
+ end
+ end
+
+ describe "when the object returned by #rand is not an Integer but responds to #to_int" do
+ it "calls #to_int on the Object" do
+ value = mock("array_sample_random_value")
+ value.should_receive(:to_int).and_return(1)
+ random = mock("array_sample_random")
+ random.should_receive(:rand).and_return(value)
+
+ [1, 2].sample(random: random).should == 2
+ end
+
+ it "raises a RangeError if the value is less than zero" do
+ value = mock("array_sample_random_value")
+ value.should_receive(:to_int).and_return(-1)
+ random = mock("array_sample_random")
+ random.should_receive(:rand).and_return(value)
+
+ -> { [1, 2].sample(random: random) }.should raise_error(RangeError)
+ end
+
+ it "raises a RangeError if the value is equal to the Array size" do
+ value = mock("array_sample_random_value")
+ value.should_receive(:to_int).and_return(2)
+ random = mock("array_sample_random")
+ random.should_receive(:rand).and_return(value)
+
+ -> { [1, 2].sample(random: random) }.should raise_error(RangeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/select_spec.rb b/spec/ruby/core/array/select_spec.rb
new file mode 100644
index 0000000000..298b591744
--- /dev/null
+++ b/spec/ruby/core/array/select_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'shared/select'
+
+describe "Array#select" do
+ it_behaves_like :array_select, :select
+end
+
+describe "Array#select!" do
+ it "returns nil if no changes were made in the array" do
+ [1, 2, 3].select! { true }.should be_nil
+ end
+
+ it_behaves_like :keep_if, :select!
+end
diff --git a/spec/ruby/core/array/shared/clone.rb b/spec/ruby/core/array/shared/clone.rb
new file mode 100644
index 0000000000..035b45ec99
--- /dev/null
+++ b/spec/ruby/core/array/shared/clone.rb
@@ -0,0 +1,20 @@
+describe :array_clone, shared: true do
+ it "returns an Array or a subclass instance" do
+ [].send(@method).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2].send(@method).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "produces a shallow copy where the references are directly copied" do
+ a = [mock('1'), mock('2')]
+ b = a.send @method
+ b.first.should equal a.first
+ b.last.should equal a.last
+ end
+
+ it "creates a new array containing all elements or the original" do
+ a = [1, 2, 3, 4]
+ b = a.send @method
+ b.should == a
+ b.__id__.should_not == a.__id__
+ end
+end
diff --git a/spec/ruby/core/array/shared/collect.rb b/spec/ruby/core/array/shared/collect.rb
new file mode 100644
index 0000000000..8d75f7db9a
--- /dev/null
+++ b/spec/ruby/core/array/shared/collect.rb
@@ -0,0 +1,109 @@
+require_relative '../../enumerable/shared/enumeratorized'
+
+describe :array_collect, shared: true do
+ it "returns a copy of array with each element replaced by the value returned by block" do
+ a = ['a', 'b', 'c', 'd']
+ b = a.send(@method) { |i| i + '!' }
+ b.should == ["a!", "b!", "c!", "d!"]
+ b.should_not equal a
+ end
+
+ it "does not return subclass instance" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method) { |x| x + 1 }.should be_an_instance_of(Array)
+ end
+
+ it "does not change self" do
+ a = ['a', 'b', 'c', 'd']
+ a.send(@method) { |i| i + '!' }
+ a.should == ['a', 'b', 'c', 'd']
+ end
+
+ it "returns the evaluated value of block if it broke in the block" do
+ a = ['a', 'b', 'c', 'd']
+ b = a.send(@method) {|i|
+ if i == 'c'
+ break 0
+ else
+ i + '!'
+ end
+ }
+ b.should == 0
+ end
+
+ it "returns an Enumerator when no block given" do
+ a = [1, 2, 3]
+ a.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "raises an ArgumentError when no block and with arguments" do
+ a = [1, 2, 3]
+ -> {
+ a.send(@method, :foo)
+ }.should raise_error(ArgumentError)
+ end
+
+ before :all do
+ @object = [1, 2, 3, 4]
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+end
+
+describe :array_collect_b, shared: true do
+ it "replaces each element with the value returned by block" do
+ a = [7, 9, 3, 5]
+ a.send(@method) { |i| i - 1 }.should equal(a)
+ a.should == [6, 8, 2, 4]
+ end
+
+ it "returns self" do
+ a = [1, 2, 3, 4, 5]
+ b = a.send(@method) {|i| i+1 }
+ a.should equal b
+ end
+
+ it "returns the evaluated value of block but its contents is partially modified, if it broke in the block" do
+ a = ['a', 'b', 'c', 'd']
+ b = a.send(@method) {|i|
+ if i == 'c'
+ break 0
+ else
+ i + '!'
+ end
+ }
+ b.should == 0
+ a.should == ['a!', 'b!', 'c', 'd']
+ end
+
+ it "returns an Enumerator when no block given, and the enumerator can modify the original array" do
+ a = [1, 2, 3]
+ enum = a.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+ enum.each{|i| "#{i}!" }
+ a.should == ["1!", "2!", "3!"]
+ end
+
+ describe "when frozen" do
+ it "raises a FrozenError" do
+ -> { ArraySpecs.frozen_array.send(@method) {} }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError when empty" do
+ -> { ArraySpecs.empty_frozen_array.send(@method) {} }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError when calling #each on the returned Enumerator" do
+ enumerator = ArraySpecs.frozen_array.send(@method)
+ -> { enumerator.each {|x| x } }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError when calling #each on the returned Enumerator when empty" do
+ enumerator = ArraySpecs.empty_frozen_array.send(@method)
+ -> { enumerator.each {|x| x } }.should raise_error(FrozenError)
+ end
+ end
+
+ before :all do
+ @object = [1, 2, 3, 4]
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+end
diff --git a/spec/ruby/core/array/shared/delete_if.rb b/spec/ruby/core/array/shared/delete_if.rb
new file mode 100644
index 0000000000..a3fdcf4fac
--- /dev/null
+++ b/spec/ruby/core/array/shared/delete_if.rb
@@ -0,0 +1,13 @@
+describe :delete_if, shared: true do
+ before :each do
+ @object = [1,2,3]
+ end
+
+ it "updates the receiver after all blocks" do
+ @object.send(@method) do |e|
+ @object.length.should == 3
+ true
+ end
+ @object.length.should == 0
+ end
+end
diff --git a/spec/ruby/core/array/shared/difference.rb b/spec/ruby/core/array/shared/difference.rb
new file mode 100644
index 0000000000..3e69050d82
--- /dev/null
+++ b/spec/ruby/core/array/shared/difference.rb
@@ -0,0 +1,78 @@
+describe :array_binary_difference, shared: true do
+ it "creates an array minus any items from other array" do
+ [].send(@method, [ 1, 2, 4 ]).should == []
+ [1, 2, 4].send(@method, []).should == [1, 2, 4]
+ [ 1, 2, 3, 4, 5 ].send(@method, [ 1, 2, 4 ]).should == [3, 5]
+ end
+
+ it "removes multiple items on the lhs equal to one on the rhs" do
+ [1, 1, 2, 2, 3, 3, 4, 5].send(@method, [1, 2, 4]).should == [3, 3, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.send(@method, empty).should == []
+
+ [].send(@method, ArraySpecs.recursive_array).should == []
+
+ array = ArraySpecs.recursive_array
+ array.send(@method, array).should == []
+ end
+
+ it "tries to convert the passed arguments to Arrays using #to_ary" do
+ obj = mock('[2,3,3,4]')
+ obj.should_receive(:to_ary).and_return([2, 3, 3, 4])
+ [1, 1, 2, 2, 3, 4].send(@method, obj).should == [1, 1]
+ end
+
+ it "raises a TypeError if the argument cannot be coerced to an Array by calling #to_ary" do
+ obj = mock('not an array')
+ -> { [1, 2, 3].send(@method, obj) }.should raise_error(TypeError)
+ end
+
+ it "does not return subclass instance for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method, []).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[]).should be_an_instance_of(Array)
+ [1, 2, 3].send(@method, ArraySpecs::MyArray[]).should be_an_instance_of(Array)
+ end
+
+ it "does not call to_ary on array subclasses" do
+ [5, 6, 7].send(@method, ArraySpecs::ToAryArray[7]).should == [5, 6]
+ end
+
+ it "removes an item identified as equivalent via #hash and #eql?" do
+ obj1 = mock('1')
+ obj2 = mock('2')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj1.should_receive(:eql?).at_least(1).and_return(true)
+
+ [obj1].send(@method, [obj2]).should == []
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == []
+ end
+
+ it "doesn't remove an item with the same hash but not #eql?" do
+ obj1 = mock('1')
+ obj2 = mock('2')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj1.should_receive(:eql?).at_least(1).and_return(false)
+
+ [obj1].send(@method, [obj2]).should == [obj1]
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1, obj1]
+ end
+
+ it "removes an identical item even when its #eql? isn't reflexive" do
+ x = mock('x')
+ x.stub!(:hash).and_return(42)
+ x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI.
+
+ [x].send(@method, [x]).should == []
+ end
+
+ it "is not destructive" do
+ a = [1, 2, 3]
+ a.send(@method, [1])
+ a.should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/array/shared/enumeratorize.rb b/spec/ruby/core/array/shared/enumeratorize.rb
new file mode 100644
index 0000000000..a19a5d3b9b
--- /dev/null
+++ b/spec/ruby/core/array/shared/enumeratorize.rb
@@ -0,0 +1,5 @@
+describe :enumeratorize, shared: true do
+ it "returns an Enumerator if no block given" do
+ [1,2].send(@method).should be_an_instance_of(Enumerator)
+ end
+end
diff --git a/spec/ruby/core/array/shared/eql.rb b/spec/ruby/core/array/shared/eql.rb
new file mode 100644
index 0000000000..b5d9128434
--- /dev/null
+++ b/spec/ruby/core/array/shared/eql.rb
@@ -0,0 +1,92 @@
+describe :array_eql, shared: true do
+ it "returns true if other is the same array" do
+ a = [1]
+ a.send(@method, a).should be_true
+ end
+
+ it "returns true if corresponding elements are #eql?" do
+ [].send(@method, []).should be_true
+ [1, 2, 3, 4].send(@method, [1, 2, 3, 4]).should be_true
+ end
+
+ it "returns false if other is shorter than self" do
+ [1, 2, 3, 4].send(@method, [1, 2, 3]).should be_false
+ end
+
+ it "returns false if other is longer than self" do
+ [1, 2, 3, 4].send(@method, [1, 2, 3, 4, 5]).should be_false
+ end
+
+ it "returns false immediately when sizes of the arrays differ" do
+ obj = mock('1')
+ obj.should_not_receive(@method)
+
+ [] .send(@method, [obj] ).should be_false
+ [obj] .send(@method, [] ).should be_false
+ end
+
+ it "handles well recursive arrays" do
+ a = ArraySpecs.empty_recursive_array
+ a .send(@method, [a] ).should be_true
+ a .send(@method, [[a]] ).should be_true
+ [a] .send(@method, a ).should be_true
+ [[a]] .send(@method, a ).should be_true
+ # These may be surprising, but no difference can be
+ # found between these arrays, so they are ==.
+ # There is no "path" that will lead to a difference
+ # (contrary to other examples below)
+
+ a2 = ArraySpecs.empty_recursive_array
+ a .send(@method, a2 ).should be_true
+ a .send(@method, [a2] ).should be_true
+ a .send(@method, [[a2]] ).should be_true
+ [a] .send(@method, a2 ).should be_true
+ [[a]] .send(@method, a2 ).should be_true
+
+ back = []
+ forth = [back]; back << forth;
+ back .send(@method, a ).should be_true
+
+ x = []; x << x << x
+ x .send(@method, a ).should be_false # since x.size != a.size
+ x .send(@method, [a, a] ).should be_false # since x[0].size != [a, a][0].size
+ x .send(@method, [x, a] ).should be_false # since x[1].size != [x, a][1].size
+ [x, a] .send(@method, [a, x] ).should be_false # etc...
+ x .send(@method, [x, x] ).should be_true
+ x .send(@method, [[x, x], [x, x]] ).should be_true
+
+ tree = [];
+ branch = []; branch << tree << tree; tree << branch
+ tree2 = [];
+ branch2 = []; branch2 << tree2 << tree2; tree2 << branch2
+ forest = [tree, branch, :bird, a]; forest << forest
+ forest2 = [tree2, branch2, :bird, a2]; forest2 << forest2
+
+ forest .send(@method, forest2 ).should be_true
+ forest .send(@method, [tree2, branch, :bird, a, forest2]).should be_true
+
+ diffforest = [branch2, tree2, :bird, a2]; diffforest << forest2
+ forest .send(@method, diffforest ).should be_false # since forest[0].size == 1 != 3 == diffforest[0]
+ forest .send(@method, [nil] ).should be_false
+ forest .send(@method, [forest] ).should be_false
+ end
+
+ it "does not call #to_ary on its argument" do
+ obj = mock('to_ary')
+ obj.should_not_receive(:to_ary)
+
+ [1, 2, 3].send(@method, obj).should be_false
+ end
+
+ it "does not call #to_ary on Array subclasses" do
+ ary = ArraySpecs::ToAryArray[5, 6, 7]
+ ary.should_not_receive(:to_ary)
+ [5, 6, 7].send(@method, ary).should be_true
+ end
+
+ it "ignores array class differences" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method, [1, 2, 3]).should be_true
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_true
+ [1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_true
+ end
+end
diff --git a/spec/ruby/core/array/shared/index.rb b/spec/ruby/core/array/shared/index.rb
new file mode 100644
index 0000000000..a9896554f2
--- /dev/null
+++ b/spec/ruby/core/array/shared/index.rb
@@ -0,0 +1,37 @@
+describe :array_index, shared: true do
+ it "returns the index of the first element == to object" do
+ x = mock('3')
+ def x.==(obj) 3 == obj; end
+
+ [2, x, 3, 1, 3, 1].send(@method, 3).should == 1
+ [2, 3.0, 3, x, 1, 3, 1].send(@method, x).should == 1
+ end
+
+ it "returns 0 if first element == to object" do
+ [2, 1, 3, 2, 5].send(@method, 2).should == 0
+ end
+
+ it "returns size-1 if only last element == to object" do
+ [2, 1, 3, 1, 5].send(@method, 5).should == 4
+ end
+
+ it "returns nil if no element == to object" do
+ [2, 1, 1, 1, 1].send(@method, 3).should == nil
+ end
+
+ it "accepts a block instead of an argument" do
+ [4, 2, 1, 5, 1, 3].send(@method) {|x| x < 2}.should == 2
+ end
+
+ it "ignores the block if there is an argument" do
+ -> {
+ [4, 2, 1, 5, 1, 3].send(@method, 5) {|x| x < 2}.should == 3
+ }.should complain(/given block not used/)
+ end
+
+ describe "given no argument and no block" do
+ it "produces an Enumerator" do
+ [].send(@method).should be_an_instance_of(Enumerator)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/shared/inspect.rb b/spec/ruby/core/array/shared/inspect.rb
new file mode 100644
index 0000000000..a2b43d4959
--- /dev/null
+++ b/spec/ruby/core/array/shared/inspect.rb
@@ -0,0 +1,107 @@
+require_relative '../fixtures/encoded_strings'
+
+describe :array_inspect, shared: true do
+ it "returns a string" do
+ [1, 2, 3].send(@method).should be_an_instance_of(String)
+ end
+
+ it "returns '[]' for an empty Array" do
+ [].send(@method).should == "[]"
+ end
+
+ it "calls inspect on its elements and joins the results with commas" do
+ items = Array.new(3) do |i|
+ obj = mock(i.to_s)
+ obj.should_receive(:inspect).and_return(i.to_s)
+ obj
+ end
+ items.send(@method).should == "[0, 1, 2]"
+ end
+
+ it "does not call #to_s on a String returned from #inspect" do
+ str = "abc"
+ str.should_not_receive(:to_s)
+
+ [str].send(@method).should == '["abc"]'
+ end
+
+ it "calls #to_s on the object returned from #inspect if the Object isn't a String" do
+ obj = mock("Array#inspect/to_s calls #to_s")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_return("abc")
+
+ [obj].send(@method).should == "[abc]"
+ end
+
+ it "does not call #to_str on the object returned from #inspect when it is not a String" do
+ obj = mock("Array#inspect/to_s does not call #to_str")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_not_receive(:to_str)
+
+ [obj].send(@method).should =~ /^\[#<MockObject:0x[0-9a-f]+>\]$/
+ end
+
+ it "does not call #to_str on the object returned from #to_s when it is not a String" do
+ obj = mock("Array#inspect/to_s does not call #to_str on #to_s result")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_return(obj)
+ obj.should_not_receive(:to_str)
+
+ [obj].send(@method).should =~ /^\[#<MockObject:0x[0-9a-f]+>\]$/
+ end
+
+ it "does not swallow exceptions raised by #to_s" do
+ obj = mock("Array#inspect/to_s does not swallow #to_s exceptions")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_raise(Exception)
+
+ -> { [obj].send(@method) }.should raise_error(Exception)
+ end
+
+ it "represents a recursive element with '[...]'" do
+ ArraySpecs.recursive_array.send(@method).should == "[1, \"two\", 3.0, [...], [...], [...], [...], [...]]"
+ ArraySpecs.head_recursive_array.send(@method).should == "[[...], [...], [...], [...], [...], 1, \"two\", 3.0]"
+ ArraySpecs.empty_recursive_array.send(@method).should == "[[...]]"
+ end
+
+ describe "with encoding" do
+ before :each do
+ @default_external_encoding = Encoding.default_external
+ end
+
+ after :each do
+ Encoding.default_external = @default_external_encoding
+ end
+
+ it "returns a US-ASCII string for an empty Array" do
+ [].send(@method).encoding.should == Encoding::US_ASCII
+ end
+
+ it "use the default external encoding if it is ascii compatible" do
+ Encoding.default_external = Encoding.find('UTF-8')
+
+ utf8 = "utf8".encode("UTF-8")
+ jp = "jp".encode("EUC-JP")
+ array = [jp, utf8]
+
+ array.send(@method).encoding.name.should == "UTF-8"
+ end
+
+ it "use US-ASCII encoding if the default external encoding is not ascii compatible" do
+ Encoding.default_external = Encoding.find('UTF-32')
+
+ utf8 = "utf8".encode("UTF-8")
+ jp = "jp".encode("EUC-JP")
+ array = [jp, utf8]
+
+ array.send(@method).encoding.name.should == "US-ASCII"
+ end
+
+ it "does not raise if inspected result is not default external encoding" do
+ utf_16be = mock("utf_16be")
+ utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode!(Encoding::UTF_16BE))
+
+ [utf_16be].send(@method).should == '["utf_16be \u3042"]'
+ end
+ end
+end
diff --git a/spec/ruby/core/array/shared/intersection.rb b/spec/ruby/core/array/shared/intersection.rb
new file mode 100644
index 0000000000..49849b08c2
--- /dev/null
+++ b/spec/ruby/core/array/shared/intersection.rb
@@ -0,0 +1,84 @@
+describe :array_intersection, shared: true do
+ it "creates an array with elements common to both arrays (intersection)" do
+ [].send(@method, []).should == []
+ [1, 2].send(@method, []).should == []
+ [].send(@method, [1, 2]).should == []
+ [ 1, 3, 5 ].send(@method, [ 1, 2, 3 ]).should == [1, 3]
+ end
+
+ it "creates an array with no duplicates" do
+ [ 1, 1, 3, 5 ].send(@method, [ 1, 2, 3 ]).uniq!.should == nil
+ end
+
+ it "creates an array with elements in order they are first encountered" do
+ [ 1, 2, 3, 2, 5 ].send(@method, [ 5, 2, 3, 4 ]).should == [2, 3, 5]
+ end
+
+ it "does not modify the original Array" do
+ a = [1, 1, 3, 5]
+ a.send(@method, [1, 2, 3]).should == [1, 3]
+ a.should == [1, 1, 3, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.send(@method, empty).should == empty
+
+ ArraySpecs.recursive_array.send(@method, []).should == []
+ [].send(@method, ArraySpecs.recursive_array).should == []
+
+ ArraySpecs.recursive_array.send(@method, ArraySpecs.recursive_array).should == [1, 'two', 3.0, ArraySpecs.recursive_array]
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('[1,2,3]')
+ obj.should_receive(:to_ary).and_return([1, 2, 3])
+ [1, 2].send(@method, obj).should == ([1, 2])
+ end
+
+ it "determines equivalence between elements in the sense of eql?" do
+ not_supported_on :opal do
+ [5.0, 4.0].send(@method, [5, 4]).should == []
+ end
+
+ str = "x"
+ [str].send(@method, [str.dup]).should == [str]
+
+ obj1 = mock('1')
+ obj2 = mock('2')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj1.should_receive(:eql?).at_least(1).and_return(true)
+ obj2.stub!(:eql?).and_return(true)
+
+ [obj1].send(@method, [obj2]).should == [obj1]
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1]
+
+ obj1 = mock('3')
+ obj2 = mock('4')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj1.should_receive(:eql?).at_least(1).and_return(false)
+
+ [obj1].send(@method, [obj2]).should == []
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj2]
+ end
+
+ it "does return subclass instances for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method, []).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_an_instance_of(Array)
+ [].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_an_instance_of(Array)
+ end
+
+ it "does not call to_ary on array subclasses" do
+ [5, 6].send(@method, ArraySpecs::ToAryArray[1, 2, 5, 6]).should == [5, 6]
+ end
+
+ it "properly handles an identical item even when its #eql? isn't reflexive" do
+ x = mock('x')
+ x.stub!(:hash).and_return(42)
+ x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI.
+
+ [x].send(@method, [x]).should == [x]
+ end
+end
diff --git a/spec/ruby/core/array/shared/join.rb b/spec/ruby/core/array/shared/join.rb
new file mode 100644
index 0000000000..507b13e3c8
--- /dev/null
+++ b/spec/ruby/core/array/shared/join.rb
@@ -0,0 +1,112 @@
+require_relative '../fixtures/classes'
+require_relative '../fixtures/encoded_strings'
+
+describe :array_join_with_default_separator, shared: true do
+ before :each do
+ @separator = $,
+ end
+
+ after :each do
+ $, = @separator
+ end
+
+ it "returns an empty string if the Array is empty" do
+ [].send(@method).should == ''
+ end
+
+ it "returns a US-ASCII string for an empty Array" do
+ [].send(@method).encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns a string formed by concatenating each String element separated by $," do
+ suppress_warning {
+ $, = " | "
+ ["1", "2", "3"].send(@method).should == "1 | 2 | 3"
+ }
+ end
+
+ it "attempts coercion via #to_str first" do
+ obj = mock('foo')
+ obj.should_receive(:to_str).any_number_of_times.and_return("foo")
+ [obj].send(@method).should == "foo"
+ end
+
+ it "attempts coercion via #to_ary second" do
+ obj = mock('foo')
+ obj.should_receive(:to_str).any_number_of_times.and_return(nil)
+ obj.should_receive(:to_ary).any_number_of_times.and_return(["foo"])
+ [obj].send(@method).should == "foo"
+ end
+
+ it "attempts coercion via #to_s third" do
+ obj = mock('foo')
+ obj.should_receive(:to_str).any_number_of_times.and_return(nil)
+ obj.should_receive(:to_ary).any_number_of_times.and_return(nil)
+ obj.should_receive(:to_s).any_number_of_times.and_return("foo")
+ [obj].send(@method).should == "foo"
+ end
+
+ it "raises a NoMethodError if an element does not respond to #to_str, #to_ary, or #to_s" do
+ obj = mock('o')
+ class << obj; undef :to_s; end
+ -> { [1, obj].send(@method) }.should raise_error(NoMethodError)
+ end
+
+ it "raises an ArgumentError when the Array is recursive" do
+ -> { ArraySpecs.recursive_array.send(@method) }.should raise_error(ArgumentError)
+ -> { ArraySpecs.head_recursive_array.send(@method) }.should raise_error(ArgumentError)
+ -> { ArraySpecs.empty_recursive_array.send(@method) }.should raise_error(ArgumentError)
+ end
+
+ it "uses the first encoding when other strings are compatible" do
+ ary1 = ArraySpecs.array_with_7bit_utf8_and_usascii_strings
+ ary2 = ArraySpecs.array_with_usascii_and_7bit_utf8_strings
+ ary3 = ArraySpecs.array_with_utf8_and_7bit_binary_strings
+ ary4 = ArraySpecs.array_with_usascii_and_7bit_binary_strings
+
+ ary1.send(@method).encoding.should == Encoding::UTF_8
+ ary2.send(@method).encoding.should == Encoding::US_ASCII
+ ary3.send(@method).encoding.should == Encoding::UTF_8
+ ary4.send(@method).encoding.should == Encoding::US_ASCII
+ end
+
+ it "uses the widest common encoding when other strings are incompatible" do
+ ary1 = ArraySpecs.array_with_utf8_and_usascii_strings
+ ary2 = ArraySpecs.array_with_usascii_and_utf8_strings
+
+ ary1.send(@method).encoding.should == Encoding::UTF_8
+ ary2.send(@method).encoding.should == Encoding::UTF_8
+ end
+
+ it "fails for arrays with incompatibly-encoded strings" do
+ ary_utf8_bad_binary = ArraySpecs.array_with_utf8_and_binary_strings
+
+ -> { ary_utf8_bad_binary.send(@method) }.should raise_error(EncodingError)
+ end
+
+ context "when $, is not nil" do
+ before do
+ suppress_warning do
+ $, = '*'
+ end
+ end
+
+ it "warns" do
+ -> { [].join }.should complain(/warning: \$, is set to non-nil value/)
+ -> { [].join(nil) }.should complain(/warning: \$, is set to non-nil value/)
+ end
+ end
+end
+
+describe :array_join_with_string_separator, shared: true do
+ it "returns a string formed by concatenating each element.to_str separated by separator" do
+ obj = mock('foo')
+ obj.should_receive(:to_str).and_return("foo")
+ [1, 2, 3, 4, obj].send(@method, ' | ').should == '1 | 2 | 3 | 4 | foo'
+ end
+
+ it "uses the same separator with nested arrays" do
+ [1, [2, [3, 4], 5], 6].send(@method, ":").should == "1:2:3:4:5:6"
+ [1, [2, ArraySpecs::MyArray[3, 4], 5], 6].send(@method, ":").should == "1:2:3:4:5:6"
+ end
+end
diff --git a/spec/ruby/core/array/shared/keep_if.rb b/spec/ruby/core/array/shared/keep_if.rb
new file mode 100644
index 0000000000..f26aff028c
--- /dev/null
+++ b/spec/ruby/core/array/shared/keep_if.rb
@@ -0,0 +1,60 @@
+require_relative '../../enumerable/shared/enumeratorized'
+
+describe :keep_if, shared: true do
+ it "deletes elements for which the block returns a false value" do
+ array = [1, 2, 3, 4, 5]
+ array.send(@method) {|item| item > 3 }.should equal(array)
+ array.should == [4, 5]
+ end
+
+ it "returns an enumerator if no block is given" do
+ [1, 2, 3].send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "updates the receiver after all blocks" do
+ a = [1, 2, 3]
+ a.send(@method) do |e|
+ a.length.should == 3
+ false
+ end
+ a.length.should == 0
+ end
+
+ before :all do
+ @object = [1,2,3]
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+
+ describe "on frozen objects" do
+ before :each do
+ @origin = [true, false]
+ @frozen = @origin.dup.freeze
+ end
+
+ it "returns an Enumerator if no block is given" do
+ @frozen.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ describe "with truthy block" do
+ it "keeps elements after any exception" do
+ -> { @frozen.send(@method) { true } }.should raise_error(Exception)
+ @frozen.should == @origin
+ end
+
+ it "raises a FrozenError" do
+ -> { @frozen.send(@method) { true } }.should raise_error(FrozenError)
+ end
+ end
+
+ describe "with falsy block" do
+ it "keeps elements after any exception" do
+ -> { @frozen.send(@method) { false } }.should raise_error(Exception)
+ @frozen.should == @origin
+ end
+
+ it "raises a FrozenError" do
+ -> { @frozen.send(@method) { false } }.should raise_error(FrozenError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/shared/length.rb b/spec/ruby/core/array/shared/length.rb
new file mode 100644
index 0000000000..f84966d0ba
--- /dev/null
+++ b/spec/ruby/core/array/shared/length.rb
@@ -0,0 +1,11 @@
+describe :array_length, shared: true do
+ it "returns the number of elements" do
+ [].send(@method).should == 0
+ [1, 2, 3].send(@method).should == 3
+ end
+
+ it "properly handles recursive arrays" do
+ ArraySpecs.empty_recursive_array.send(@method).should == 1
+ ArraySpecs.recursive_array.send(@method).should == 8
+ end
+end
diff --git a/spec/ruby/core/array/shared/push.rb b/spec/ruby/core/array/shared/push.rb
new file mode 100644
index 0000000000..ac790fb6a4
--- /dev/null
+++ b/spec/ruby/core/array/shared/push.rb
@@ -0,0 +1,33 @@
+describe :array_push, shared: true do
+ it "appends the arguments to the array" do
+ a = [ "a", "b", "c" ]
+ a.send(@method, "d", "e", "f").should equal(a)
+ a.send(@method).should == ["a", "b", "c", "d", "e", "f"]
+ a.send(@method, 5)
+ a.should == ["a", "b", "c", "d", "e", "f", 5]
+
+ a = [0, 1]
+ a.send(@method, 2)
+ a.should == [0, 1, 2]
+ end
+
+ it "isn't confused by previous shift" do
+ a = [ "a", "b", "c" ]
+ a.shift
+ a.send(@method, "foo")
+ a.should == ["b", "c", "foo"]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.send(@method, :last).should == [empty, :last]
+
+ array = ArraySpecs.recursive_array
+ array.send(@method, :last).should == [1, 'two', 3.0, array, array, array, array, array, :last]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.send(@method, 1) }.should raise_error(FrozenError)
+ -> { ArraySpecs.frozen_array.send(@method) }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/shared/replace.rb b/spec/ruby/core/array/shared/replace.rb
new file mode 100644
index 0000000000..9a6e60c1b0
--- /dev/null
+++ b/spec/ruby/core/array/shared/replace.rb
@@ -0,0 +1,60 @@
+describe :array_replace, shared: true do
+ it "replaces the elements with elements from other array" do
+ a = [1, 2, 3, 4, 5]
+ b = ['a', 'b', 'c']
+ a.send(@method, b).should equal(a)
+ a.should == b
+ a.should_not equal(b)
+
+ a.send(@method, [4] * 10)
+ a.should == [4] * 10
+
+ a.send(@method, [])
+ a.should == []
+ end
+
+ it "properly handles recursive arrays" do
+ orig = [1, 2, 3]
+ empty = ArraySpecs.empty_recursive_array
+ orig.send(@method, empty)
+ orig.should == empty
+
+ array = ArraySpecs.recursive_array
+ orig.send(@method, array)
+ orig.should == array
+ end
+
+ it "returns self" do
+ ary = [1, 2, 3]
+ other = [:a, :b, :c]
+ ary.send(@method, other).should equal(ary)
+ end
+
+ it "does not make self dependent to the original array" do
+ ary = [1, 2, 3]
+ other = [:a, :b, :c]
+ ary.send(@method, other)
+ ary.should == [:a, :b, :c]
+ ary << :d
+ ary.should == [:a, :b, :c, :d]
+ other.should == [:a, :b, :c]
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('to_ary')
+ obj.stub!(:to_ary).and_return([1, 2, 3])
+ [].send(@method, obj).should == [1, 2, 3]
+ end
+
+ it "does not call #to_ary on Array subclasses" do
+ obj = ArraySpecs::ToAryArray[5, 6, 7]
+ obj.should_not_receive(:to_ary)
+ [].send(@method, ArraySpecs::ToAryArray[5, 6, 7]).should == [5, 6, 7]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> {
+ ArraySpecs.frozen_array.send(@method, ArraySpecs.frozen_array)
+ }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/shared/select.rb b/spec/ruby/core/array/shared/select.rb
new file mode 100644
index 0000000000..09101e8ab5
--- /dev/null
+++ b/spec/ruby/core/array/shared/select.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/enumeratorize'
+require_relative '../shared/keep_if'
+require_relative '../../enumerable/shared/enumeratorized'
+
+describe :array_select, shared: true do
+ it_should_behave_like :enumeratorize
+
+ before :each do
+ @object = [1,2,3]
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+
+ it "returns a new array of elements for which block is true" do
+ [1, 3, 4, 5, 6, 9].send(@method) { |i| i % ((i + 1) / 2) == 0}.should == [1, 4, 6]
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method) { true }.should be_an_instance_of(Array)
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.send(@method) { true }.should == empty
+ empty.send(@method) { false }.should == []
+
+ array = ArraySpecs.recursive_array
+ array.send(@method) { true }.should == [1, 'two', 3.0, array, array, array, array, array]
+ array.send(@method) { false }.should == []
+ end
+end
diff --git a/spec/ruby/core/array/shared/slice.rb b/spec/ruby/core/array/shared/slice.rb
new file mode 100644
index 0000000000..8fb33738b9
--- /dev/null
+++ b/spec/ruby/core/array/shared/slice.rb
@@ -0,0 +1,889 @@
+describe :array_slice, shared: true do
+ it "returns the element at index with [index]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, 1).should == "b"
+
+ a = [1, 2, 3, 4]
+
+ a.send(@method, 0).should == 1
+ a.send(@method, 1).should == 2
+ a.send(@method, 2).should == 3
+ a.send(@method, 3).should == 4
+ a.send(@method, 4).should == nil
+ a.send(@method, 10).should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns the element at index from the end of the array with [-index]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, -2).should == "d"
+
+ a = [1, 2, 3, 4]
+
+ a.send(@method, -1).should == 4
+ a.send(@method, -2).should == 3
+ a.send(@method, -3).should == 2
+ a.send(@method, -4).should == 1
+ a.send(@method, -5).should == nil
+ a.send(@method, -10).should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns count elements starting from index with [index, count]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, 2, 3).should == ["c", "d", "e"]
+
+ a = [1, 2, 3, 4]
+
+ a.send(@method, 0, 0).should == []
+ a.send(@method, 0, 1).should == [1]
+ a.send(@method, 0, 2).should == [1, 2]
+ a.send(@method, 0, 4).should == [1, 2, 3, 4]
+ a.send(@method, 0, 6).should == [1, 2, 3, 4]
+ a.send(@method, 0, -1).should == nil
+ a.send(@method, 0, -2).should == nil
+ a.send(@method, 0, -4).should == nil
+
+ a.send(@method, 2, 0).should == []
+ a.send(@method, 2, 1).should == [3]
+ a.send(@method, 2, 2).should == [3, 4]
+ a.send(@method, 2, 4).should == [3, 4]
+ a.send(@method, 2, -1).should == nil
+
+ a.send(@method, 4, 0).should == []
+ a.send(@method, 4, 2).should == []
+ a.send(@method, 4, -1).should == nil
+
+ a.send(@method, 5, 0).should == nil
+ a.send(@method, 5, 2).should == nil
+ a.send(@method, 5, -1).should == nil
+
+ a.send(@method, 6, 0).should == nil
+ a.send(@method, 6, 2).should == nil
+ a.send(@method, 6, -1).should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns count elements starting at index from the end of array with [-index, count]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, -2, 2).should == ["d", "e"]
+
+ a = [1, 2, 3, 4]
+
+ a.send(@method, -1, 0).should == []
+ a.send(@method, -1, 1).should == [4]
+ a.send(@method, -1, 2).should == [4]
+ a.send(@method, -1, -1).should == nil
+
+ a.send(@method, -2, 0).should == []
+ a.send(@method, -2, 1).should == [3]
+ a.send(@method, -2, 2).should == [3, 4]
+ a.send(@method, -2, 4).should == [3, 4]
+ a.send(@method, -2, -1).should == nil
+
+ a.send(@method, -4, 0).should == []
+ a.send(@method, -4, 1).should == [1]
+ a.send(@method, -4, 2).should == [1, 2]
+ a.send(@method, -4, 4).should == [1, 2, 3, 4]
+ a.send(@method, -4, 6).should == [1, 2, 3, 4]
+ a.send(@method, -4, -1).should == nil
+
+ a.send(@method, -5, 0).should == nil
+ a.send(@method, -5, 1).should == nil
+ a.send(@method, -5, 10).should == nil
+ a.send(@method, -5, -1).should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns the first count elements with [0, count]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, 0, 3).should == ["a", "b", "c"]
+ end
+
+ it "returns the subarray which is independent to self with [index,count]" do
+ a = [1, 2, 3]
+ sub = a.send(@method, 1,2)
+ sub.replace([:a, :b])
+ a.should == [1, 2, 3]
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.stub!(:to_int).and_return(2)
+
+ a = [1, 2, 3, 4]
+ a.send(@method, obj).should == 3
+ a.send(@method, obj, 1).should == [3]
+ a.send(@method, obj, obj).should == [3, 4]
+ a.send(@method, 0, obj).should == [1, 2]
+ end
+
+ it "raises TypeError if to_int returns non-integer" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ a = [1, 2, 3, 4, 5]
+
+ def from.to_int() 'cat' end
+ def to.to_int() -2 end
+
+ -> { a.send(@method, from..to) }.should raise_error(TypeError)
+
+ def from.to_int() 1 end
+ def to.to_int() 'cat' end
+
+ -> { a.send(@method, from..to) }.should raise_error(TypeError)
+ end
+
+ it "returns the elements specified by Range indexes with [m..n]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, 1..3).should == ["b", "c", "d"]
+ [ "a", "b", "c", "d", "e" ].send(@method, 4..-1).should == ['e']
+ [ "a", "b", "c", "d", "e" ].send(@method, 3..3).should == ['d']
+ [ "a", "b", "c", "d", "e" ].send(@method, 3..-2).should == ['d']
+ ['a'].send(@method, 0..-1).should == ['a']
+
+ a = [1, 2, 3, 4]
+
+ a.send(@method, 0..-10).should == []
+ a.send(@method, 0..0).should == [1]
+ a.send(@method, 0..1).should == [1, 2]
+ a.send(@method, 0..2).should == [1, 2, 3]
+ a.send(@method, 0..3).should == [1, 2, 3, 4]
+ a.send(@method, 0..4).should == [1, 2, 3, 4]
+ a.send(@method, 0..10).should == [1, 2, 3, 4]
+
+ a.send(@method, 2..-10).should == []
+ a.send(@method, 2..0).should == []
+ a.send(@method, 2..2).should == [3]
+ a.send(@method, 2..3).should == [3, 4]
+ a.send(@method, 2..4).should == [3, 4]
+
+ a.send(@method, 3..0).should == []
+ a.send(@method, 3..3).should == [4]
+ a.send(@method, 3..4).should == [4]
+
+ a.send(@method, 4..0).should == []
+ a.send(@method, 4..4).should == []
+ a.send(@method, 4..5).should == []
+
+ a.send(@method, 5..0).should == nil
+ a.send(@method, 5..5).should == nil
+ a.send(@method, 5..6).should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns elements specified by Range indexes except the element at index n with [m...n]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, 1...3).should == ["b", "c"]
+
+ a = [1, 2, 3, 4]
+
+ a.send(@method, 0...-10).should == []
+ a.send(@method, 0...0).should == []
+ a.send(@method, 0...1).should == [1]
+ a.send(@method, 0...2).should == [1, 2]
+ a.send(@method, 0...3).should == [1, 2, 3]
+ a.send(@method, 0...4).should == [1, 2, 3, 4]
+ a.send(@method, 0...10).should == [1, 2, 3, 4]
+
+ a.send(@method, 2...-10).should == []
+ a.send(@method, 2...0).should == []
+ a.send(@method, 2...2).should == []
+ a.send(@method, 2...3).should == [3]
+ a.send(@method, 2...4).should == [3, 4]
+
+ a.send(@method, 3...0).should == []
+ a.send(@method, 3...3).should == []
+ a.send(@method, 3...4).should == [4]
+
+ a.send(@method, 4...0).should == []
+ a.send(@method, 4...4).should == []
+ a.send(@method, 4...5).should == []
+
+ a.send(@method, 5...0).should == nil
+ a.send(@method, 5...5).should == nil
+ a.send(@method, 5...6).should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns elements that exist if range start is in the array but range end is not with [m..n]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, 4..7).should == ["e"]
+ end
+
+ it "accepts Range instances having a negative m and both signs for n with [m..n] and [m...n]" do
+ a = [1, 2, 3, 4]
+
+ a.send(@method, -1..-1).should == [4]
+ a.send(@method, -1...-1).should == []
+ a.send(@method, -1..3).should == [4]
+ a.send(@method, -1...3).should == []
+ a.send(@method, -1..4).should == [4]
+ a.send(@method, -1...4).should == [4]
+ a.send(@method, -1..10).should == [4]
+ a.send(@method, -1...10).should == [4]
+ a.send(@method, -1..0).should == []
+ a.send(@method, -1..-4).should == []
+ a.send(@method, -1...-4).should == []
+ a.send(@method, -1..-6).should == []
+ a.send(@method, -1...-6).should == []
+
+ a.send(@method, -2..-2).should == [3]
+ a.send(@method, -2...-2).should == []
+ a.send(@method, -2..-1).should == [3, 4]
+ a.send(@method, -2...-1).should == [3]
+ a.send(@method, -2..10).should == [3, 4]
+ a.send(@method, -2...10).should == [3, 4]
+
+ a.send(@method, -4..-4).should == [1]
+ a.send(@method, -4..-2).should == [1, 2, 3]
+ a.send(@method, -4...-2).should == [1, 2]
+ a.send(@method, -4..-1).should == [1, 2, 3, 4]
+ a.send(@method, -4...-1).should == [1, 2, 3]
+ a.send(@method, -4..3).should == [1, 2, 3, 4]
+ a.send(@method, -4...3).should == [1, 2, 3]
+ a.send(@method, -4..4).should == [1, 2, 3, 4]
+ a.send(@method, -4...4).should == [1, 2, 3, 4]
+ a.send(@method, -4...4).should == [1, 2, 3, 4]
+ a.send(@method, -4..0).should == [1]
+ a.send(@method, -4...0).should == []
+ a.send(@method, -4..1).should == [1, 2]
+ a.send(@method, -4...1).should == [1]
+
+ a.send(@method, -5..-5).should == nil
+ a.send(@method, -5...-5).should == nil
+ a.send(@method, -5..-4).should == nil
+ a.send(@method, -5..-1).should == nil
+ a.send(@method, -5..10).should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns the subarray which is independent to self with [m..n]" do
+ a = [1, 2, 3]
+ sub = a.send(@method, 1..2)
+ sub.replace([:a, :b])
+ a.should == [1, 2, 3]
+ end
+
+ it "tries to convert Range elements to Integers using #to_int with [m..n] and [m...n]" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ def from.to_int() 1 end
+ def to.to_int() -2 end
+
+ a = [1, 2, 3, 4]
+
+ a.send(@method, from..to).should == [2, 3]
+ a.send(@method, from...to).should == [2]
+ a.send(@method, 1..0).should == []
+ a.send(@method, 1...0).should == []
+
+ -> { a.send(@method, "a" .. "b") }.should raise_error(TypeError)
+ -> { a.send(@method, "a" ... "b") }.should raise_error(TypeError)
+ -> { a.send(@method, from .. "b") }.should raise_error(TypeError)
+ -> { a.send(@method, from ... "b") }.should raise_error(TypeError)
+ end
+
+ it "returns the same elements as [m..n] and [m...n] with Range subclasses" do
+ a = [1, 2, 3, 4]
+ range_incl = ArraySpecs::MyRange.new(1, 2)
+ range_excl = ArraySpecs::MyRange.new(-3, -1, true)
+
+ a.send(@method, range_incl).should == [2, 3]
+ a.send(@method, range_excl).should == [2, 3]
+ end
+
+ it "returns nil for a requested index not in the array with [index]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, 5).should == nil
+ end
+
+ it "returns [] if the index is valid but length is zero with [index, length]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, 0, 0).should == []
+ [ "a", "b", "c", "d", "e" ].send(@method, 2, 0).should == []
+ end
+
+ it "returns nil if length is zero but index is invalid with [index, length]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, 100, 0).should == nil
+ [ "a", "b", "c", "d", "e" ].send(@method, -50, 0).should == nil
+ end
+
+ # This is by design. It is in the official documentation.
+ it "returns [] if index == array.size with [index, length]" do
+ %w|a b c d e|.send(@method, 5, 2).should == []
+ end
+
+ it "returns nil if index > array.size with [index, length]" do
+ %w|a b c d e|.send(@method, 6, 2).should == nil
+ end
+
+ it "returns nil if length is negative with [index, length]" do
+ %w|a b c d e|.send(@method, 3, -1).should == nil
+ %w|a b c d e|.send(@method, 2, -2).should == nil
+ %w|a b c d e|.send(@method, 1, -100).should == nil
+ end
+
+ it "returns nil if no requested index is in the array with [m..n]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, 6..10).should == nil
+ end
+
+ it "returns nil if range start is not in the array with [m..n]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, -10..2).should == nil
+ [ "a", "b", "c", "d", "e" ].send(@method, 10..12).should == nil
+ end
+
+ it "returns an empty array when m == n with [m...n]" do
+ [1, 2, 3, 4, 5].send(@method, 1...1).should == []
+ end
+
+ it "returns an empty array with [0...0]" do
+ [1, 2, 3, 4, 5].send(@method, 0...0).should == []
+ end
+
+ it "returns a subarray where m, n negatives and m < n with [m..n]" do
+ [ "a", "b", "c", "d", "e" ].send(@method, -3..-2).should == ["c", "d"]
+ end
+
+ it "returns an array containing the first element with [0..0]" do
+ [1, 2, 3, 4, 5].send(@method, 0..0).should == [1]
+ end
+
+ it "returns the entire array with [0..-1]" do
+ [1, 2, 3, 4, 5].send(@method, 0..-1).should == [1, 2, 3, 4, 5]
+ end
+
+ it "returns all but the last element with [0...-1]" do
+ [1, 2, 3, 4, 5].send(@method, 0...-1).should == [1, 2, 3, 4]
+ end
+
+ it "returns [3] for [2..-1] out of [1, 2, 3]" do
+ [1,2,3].send(@method, 2..-1).should == [3]
+ end
+
+ it "returns an empty array when m > n and m, n are positive with [m..n]" do
+ [1, 2, 3, 4, 5].send(@method, 3..2).should == []
+ end
+
+ it "returns an empty array when m > n and m, n are negative with [m..n]" do
+ [1, 2, 3, 4, 5].send(@method, -2..-3).should == []
+ end
+
+ it "does not expand array when the indices are outside of the array bounds" do
+ a = [1, 2]
+ a.send(@method, 4).should == nil
+ a.should == [1, 2]
+ a.send(@method, 4, 0).should == nil
+ a.should == [1, 2]
+ a.send(@method, 6, 1).should == nil
+ a.should == [1, 2]
+ a.send(@method, 8...8).should == nil
+ a.should == [1, 2]
+ a.send(@method, 10..10).should == nil
+ a.should == [1, 2]
+ end
+
+ describe "with a subclass of Array" do
+ before :each do
+ ScratchPad.clear
+
+ @array = ArraySpecs::MyArray[1, 2, 3, 4, 5]
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a subclass instance with [n, m]" do
+ @array.send(@method, 0, 2).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "returns a subclass instance with [-n, m]" do
+ @array.send(@method, -3, 2).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "returns a subclass instance with [n..m]" do
+ @array.send(@method, 1..3).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "returns a subclass instance with [n...m]" do
+ @array.send(@method, 1...3).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "returns a subclass instance with [-n..-m]" do
+ @array.send(@method, -3..-1).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "returns a subclass instance with [-n...-m]" do
+ @array.send(@method, -3...-1).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a Array instance with [n, m]" do
+ @array.send(@method, 0, 2).should be_an_instance_of(Array)
+ end
+
+ it "returns a Array instance with [-n, m]" do
+ @array.send(@method, -3, 2).should be_an_instance_of(Array)
+ end
+
+ it "returns a Array instance with [n..m]" do
+ @array.send(@method, 1..3).should be_an_instance_of(Array)
+ end
+
+ it "returns a Array instance with [n...m]" do
+ @array.send(@method, 1...3).should be_an_instance_of(Array)
+ end
+
+ it "returns a Array instance with [-n..-m]" do
+ @array.send(@method, -3..-1).should be_an_instance_of(Array)
+ end
+
+ it "returns a Array instance with [-n...-m]" do
+ @array.send(@method, -3...-1).should be_an_instance_of(Array)
+ end
+ end
+
+ it "returns an empty array when m == n with [m...n]" do
+ @array.send(@method, 1...1).should == []
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "returns an empty array with [0...0]" do
+ @array.send(@method, 0...0).should == []
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "returns an empty array when m > n and m, n are positive with [m..n]" do
+ @array.send(@method, 3..2).should == []
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "returns an empty array when m > n and m, n are negative with [m..n]" do
+ @array.send(@method, -2..-3).should == []
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "returns [] if index == array.size with [index, length]" do
+ @array.send(@method, 5, 2).should == []
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "returns [] if the index is valid but length is zero with [index, length]" do
+ @array.send(@method, 0, 0).should == []
+ @array.send(@method, 2, 0).should == []
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "does not call #initialize on the subclass instance" do
+ @array.send(@method, 0, 3).should == [1, 2, 3]
+ ScratchPad.recorded.should be_nil
+ end
+ end
+
+ it "raises a RangeError when the start index is out of range of Fixnum" do
+ array = [1, 2, 3, 4, 5, 6]
+ obj = mock('large value')
+ obj.should_receive(:to_int).and_return(bignum_value)
+ -> { array.send(@method, obj) }.should raise_error(RangeError)
+
+ obj = 8e19
+ -> { array.send(@method, obj) }.should raise_error(RangeError)
+
+ # boundary value when longs are 64 bits
+ -> { array.send(@method, 2.0**63) }.should raise_error(RangeError)
+
+ # just under the boundary value when longs are 64 bits
+ array.send(@method, max_long.to_f.prev_float).should == nil
+ end
+
+ it "raises a RangeError when the length is out of range of Fixnum" do
+ array = [1, 2, 3, 4, 5, 6]
+ obj = mock('large value')
+ obj.should_receive(:to_int).and_return(bignum_value)
+ -> { array.send(@method, 1, obj) }.should raise_error(RangeError)
+
+ obj = 8e19
+ -> { array.send(@method, 1, obj) }.should raise_error(RangeError)
+ end
+
+ it "raises a type error if a range is passed with a length" do
+ ->{ [1, 2, 3].send(@method, 1..2, 1) }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError if passed a range with a bound that is too large" do
+ array = [1, 2, 3, 4, 5, 6]
+ -> { array.send(@method, bignum_value..(bignum_value + 1)) }.should raise_error(RangeError)
+ -> { array.send(@method, 0..bignum_value) }.should raise_error(RangeError)
+ end
+
+ it "can accept endless ranges" do
+ a = [0, 1, 2, 3, 4, 5]
+ a.send(@method, eval("(2..)")).should == [2, 3, 4, 5]
+ a.send(@method, eval("(2...)")).should == [2, 3, 4, 5]
+ a.send(@method, eval("(-2..)")).should == [4, 5]
+ a.send(@method, eval("(-2...)")).should == [4, 5]
+ a.send(@method, eval("(9..)")).should == nil
+ a.send(@method, eval("(9...)")).should == nil
+ a.send(@method, eval("(-9..)")).should == nil
+ a.send(@method, eval("(-9...)")).should == nil
+ end
+
+ ruby_version_is "3.0" do
+ describe "can be sliced with Enumerator::ArithmeticSequence" do
+ before :each do
+ @array = [0, 1, 2, 3, 4, 5]
+ end
+
+ it "has endless range and positive steps" do
+ @array.send(@method, eval("(0..).step(1)")).should == [0, 1, 2, 3, 4, 5]
+ @array.send(@method, eval("(0..).step(2)")).should == [0, 2, 4]
+ @array.send(@method, eval("(0..).step(10)")).should == [0]
+
+ @array.send(@method, eval("(2..).step(1)")).should == [2, 3, 4, 5]
+ @array.send(@method, eval("(2..).step(2)")).should == [2, 4]
+ @array.send(@method, eval("(2..).step(10)")).should == [2]
+
+ @array.send(@method, eval("(-3..).step(1)")).should == [3, 4, 5]
+ @array.send(@method, eval("(-3..).step(2)")).should == [3, 5]
+ @array.send(@method, eval("(-3..).step(10)")).should == [3]
+ end
+
+ it "has beginless range and positive steps" do
+ # end with zero index
+ @array.send(@method, (..0).step(1)).should == [0]
+ @array.send(@method, (...0).step(1)).should == []
+
+ @array.send(@method, (..0).step(2)).should == [0]
+ @array.send(@method, (...0).step(2)).should == []
+
+ @array.send(@method, (..0).step(10)).should == [0]
+ @array.send(@method, (...0).step(10)).should == []
+
+ # end with positive index
+ @array.send(@method, (..3).step(1)).should == [0, 1, 2, 3]
+ @array.send(@method, (...3).step(1)).should == [0, 1, 2]
+
+ @array.send(@method, (..3).step(2)).should == [0, 2]
+ @array.send(@method, (...3).step(2)).should == [0, 2]
+
+ @array.send(@method, (..3).step(10)).should == [0]
+ @array.send(@method, (...3).step(10)).should == [0]
+
+ # end with negative index
+ @array.send(@method, (..-2).step(1)).should == [0, 1, 2, 3, 4,]
+ @array.send(@method, (...-2).step(1)).should == [0, 1, 2, 3]
+
+ @array.send(@method, (..-2).step(2)).should == [0, 2, 4]
+ @array.send(@method, (...-2).step(2)).should == [0, 2]
+
+ @array.send(@method, (..-2).step(10)).should == [0]
+ @array.send(@method, (...-2).step(10)).should == [0]
+ end
+
+ it "has endless range and negative steps" do
+ @array.send(@method, eval("(0..).step(-1)")).should == [0]
+ @array.send(@method, eval("(0..).step(-2)")).should == [0]
+ @array.send(@method, eval("(0..).step(-10)")).should == [0]
+
+ @array.send(@method, eval("(2..).step(-1)")).should == [2, 1, 0]
+ @array.send(@method, eval("(2..).step(-2)")).should == [2, 0]
+
+ @array.send(@method, eval("(-3..).step(-1)")).should == [3, 2, 1, 0]
+ @array.send(@method, eval("(-3..).step(-2)")).should == [3, 1]
+ end
+
+ it "has closed range and positive steps" do
+ # start and end with 0
+ @array.send(@method, eval("(0..0).step(1)")).should == [0]
+ @array.send(@method, eval("(0...0).step(1)")).should == []
+
+ @array.send(@method, eval("(0..0).step(2)")).should == [0]
+ @array.send(@method, eval("(0...0).step(2)")).should == []
+
+ @array.send(@method, eval("(0..0).step(10)")).should == [0]
+ @array.send(@method, eval("(0...0).step(10)")).should == []
+
+ # start and end with positive index
+ @array.send(@method, eval("(1..3).step(1)")).should == [1, 2, 3]
+ @array.send(@method, eval("(1...3).step(1)")).should == [1, 2]
+
+ @array.send(@method, eval("(1..3).step(2)")).should == [1, 3]
+ @array.send(@method, eval("(1...3).step(2)")).should == [1]
+
+ @array.send(@method, eval("(1..3).step(10)")).should == [1]
+ @array.send(@method, eval("(1...3).step(10)")).should == [1]
+
+ # start with positive index, end with negative index
+ @array.send(@method, eval("(1..-2).step(1)")).should == [1, 2, 3, 4]
+ @array.send(@method, eval("(1...-2).step(1)")).should == [1, 2, 3]
+
+ @array.send(@method, eval("(1..-2).step(2)")).should == [1, 3]
+ @array.send(@method, eval("(1...-2).step(2)")).should == [1, 3]
+
+ @array.send(@method, eval("(1..-2).step(10)")).should == [1]
+ @array.send(@method, eval("(1...-2).step(10)")).should == [1]
+
+ # start with negative index, end with positive index
+ @array.send(@method, eval("(-4..4).step(1)")).should == [2, 3, 4]
+ @array.send(@method, eval("(-4...4).step(1)")).should == [2, 3]
+
+ @array.send(@method, eval("(-4..4).step(2)")).should == [2, 4]
+ @array.send(@method, eval("(-4...4).step(2)")).should == [2]
+
+ @array.send(@method, eval("(-4..4).step(10)")).should == [2]
+ @array.send(@method, eval("(-4...4).step(10)")).should == [2]
+
+ # start with negative index, end with negative index
+ @array.send(@method, eval("(-4..-2).step(1)")).should == [2, 3, 4]
+ @array.send(@method, eval("(-4...-2).step(1)")).should == [2, 3]
+
+ @array.send(@method, eval("(-4..-2).step(2)")).should == [2, 4]
+ @array.send(@method, eval("(-4...-2).step(2)")).should == [2]
+
+ @array.send(@method, eval("(-4..-2).step(10)")).should == [2]
+ @array.send(@method, eval("(-4...-2).step(10)")).should == [2]
+ end
+
+ it "has closed range and negative steps" do
+ # start and end with 0
+ @array.send(@method, eval("(0..0).step(-1)")).should == [0]
+ @array.send(@method, eval("(0...0).step(-1)")).should == []
+
+ @array.send(@method, eval("(0..0).step(-2)")).should == [0]
+ @array.send(@method, eval("(0...0).step(-2)")).should == []
+
+ @array.send(@method, eval("(0..0).step(-10)")).should == [0]
+ @array.send(@method, eval("(0...0).step(-10)")).should == []
+
+ # start and end with positive index
+ @array.send(@method, eval("(1..3).step(-1)")).should == []
+ @array.send(@method, eval("(1...3).step(-1)")).should == []
+
+ @array.send(@method, eval("(1..3).step(-2)")).should == []
+ @array.send(@method, eval("(1...3).step(-2)")).should == []
+
+ @array.send(@method, eval("(1..3).step(-10)")).should == []
+ @array.send(@method, eval("(1...3).step(-10)")).should == []
+
+ # start with positive index, end with negative index
+ @array.send(@method, eval("(1..-2).step(-1)")).should == []
+ @array.send(@method, eval("(1...-2).step(-1)")).should == []
+
+ @array.send(@method, eval("(1..-2).step(-2)")).should == []
+ @array.send(@method, eval("(1...-2).step(-2)")).should == []
+
+ @array.send(@method, eval("(1..-2).step(-10)")).should == []
+ @array.send(@method, eval("(1...-2).step(-10)")).should == []
+
+ # start with negative index, end with positive index
+ @array.send(@method, eval("(-4..4).step(-1)")).should == []
+ @array.send(@method, eval("(-4...4).step(-1)")).should == []
+
+ @array.send(@method, eval("(-4..4).step(-2)")).should == []
+ @array.send(@method, eval("(-4...4).step(-2)")).should == []
+
+ @array.send(@method, eval("(-4..4).step(-10)")).should == []
+ @array.send(@method, eval("(-4...4).step(-10)")).should == []
+
+ # start with negative index, end with negative index
+ @array.send(@method, eval("(-4..-2).step(-1)")).should == []
+ @array.send(@method, eval("(-4...-2).step(-1)")).should == []
+
+ @array.send(@method, eval("(-4..-2).step(-2)")).should == []
+ @array.send(@method, eval("(-4...-2).step(-2)")).should == []
+
+ @array.send(@method, eval("(-4..-2).step(-10)")).should == []
+ @array.send(@method, eval("(-4...-2).step(-10)")).should == []
+ end
+
+ it "has inverted closed range and positive steps" do
+ # start and end with positive index
+ @array.send(@method, eval("(3..1).step(1)")).should == []
+ @array.send(@method, eval("(3...1).step(1)")).should == []
+
+ @array.send(@method, eval("(3..1).step(2)")).should == []
+ @array.send(@method, eval("(3...1).step(2)")).should == []
+
+ @array.send(@method, eval("(3..1).step(10)")).should == []
+ @array.send(@method, eval("(3...1).step(10)")).should == []
+
+ # start with negative index, end with positive index
+ @array.send(@method, eval("(-2..1).step(1)")).should == []
+ @array.send(@method, eval("(-2...1).step(1)")).should == []
+
+ @array.send(@method, eval("(-2..1).step(2)")).should == []
+ @array.send(@method, eval("(-2...1).step(2)")).should == []
+
+ @array.send(@method, eval("(-2..1).step(10)")).should == []
+ @array.send(@method, eval("(-2...1).step(10)")).should == []
+
+ # start with positive index, end with negative index
+ @array.send(@method, eval("(4..-4).step(1)")).should == []
+ @array.send(@method, eval("(4...-4).step(1)")).should == []
+
+ @array.send(@method, eval("(4..-4).step(2)")).should == []
+ @array.send(@method, eval("(4...-4).step(2)")).should == []
+
+ @array.send(@method, eval("(4..-4).step(10)")).should == []
+ @array.send(@method, eval("(4...-4).step(10)")).should == []
+
+ # start with negative index, end with negative index
+ @array.send(@method, eval("(-2..-4).step(1)")).should == []
+ @array.send(@method, eval("(-2...-4).step(1)")).should == []
+
+ @array.send(@method, eval("(-2..-4).step(2)")).should == []
+ @array.send(@method, eval("(-2...-4).step(2)")).should == []
+
+ @array.send(@method, eval("(-2..-4).step(10)")).should == []
+ @array.send(@method, eval("(-2...-4).step(10)")).should == []
+ end
+
+ it "has range with bounds outside of array" do
+ # end is equal to array's length
+ @array.send(@method, (0..6).step(1)).should == [0, 1, 2, 3, 4, 5]
+ -> { @array.send(@method, (0..6).step(2)) }.should raise_error(RangeError)
+
+ # end is greater than length with positive steps
+ @array.send(@method, (1..6).step(2)).should == [1, 3, 5]
+ @array.send(@method, (2..7).step(2)).should == [2, 4]
+ -> { @array.send(@method, (2..8).step(2)) }.should raise_error(RangeError)
+
+ # begin is greater than length with negative steps
+ @array.send(@method, (6..1).step(-2)).should == [5, 3, 1]
+ @array.send(@method, (7..2).step(-2)).should == [5, 3]
+ -> { @array.send(@method, (8..2).step(-2)) }.should raise_error(RangeError)
+ end
+
+ it "has endless range with start outside of array's bounds" do
+ @array.send(@method, eval("(6..).step(1)")).should == []
+ @array.send(@method, eval("(7..).step(1)")).should == nil
+
+ @array.send(@method, eval("(6..).step(2)")).should == []
+ -> { @array.send(@method, eval("(7..).step(2)")) }.should raise_error(RangeError)
+ end
+ end
+ end
+
+ it "can accept beginless ranges" do
+ a = [0, 1, 2, 3, 4, 5]
+ a.send(@method, (..3)).should == [0, 1, 2, 3]
+ a.send(@method, (...3)).should == [0, 1, 2]
+ a.send(@method, (..-3)).should == [0, 1, 2, 3]
+ a.send(@method, (...-3)).should == [0, 1, 2]
+ a.send(@method, (..0)).should == [0]
+ a.send(@method, (...0)).should == []
+ a.send(@method, (..9)).should == [0, 1, 2, 3, 4, 5]
+ a.send(@method, (...9)).should == [0, 1, 2, 3, 4, 5]
+ a.send(@method, (..-9)).should == []
+ a.send(@method, (...-9)).should == []
+ end
+
+ ruby_version_is "3.2" do
+ describe "can be sliced with Enumerator::ArithmeticSequence" do
+ it "with infinite/inverted ranges and negative steps" do
+ @array = [0, 1, 2, 3, 4, 5]
+ @array.send(@method, (2..).step(-1)).should == [2, 1, 0]
+ @array.send(@method, (2..).step(-2)).should == [2, 0]
+ @array.send(@method, (2..).step(-3)).should == [2]
+ @array.send(@method, (2..).step(-4)).should == [2]
+
+ @array.send(@method, (-3..).step(-1)).should == [3, 2, 1, 0]
+ @array.send(@method, (-3..).step(-2)).should == [3, 1]
+ @array.send(@method, (-3..).step(-3)).should == [3, 0]
+ @array.send(@method, (-3..).step(-4)).should == [3]
+ @array.send(@method, (-3..).step(-5)).should == [3]
+
+ @array.send(@method, (..0).step(-1)).should == [5, 4, 3, 2, 1, 0]
+ @array.send(@method, (..0).step(-2)).should == [5, 3, 1]
+ @array.send(@method, (..0).step(-3)).should == [5, 2]
+ @array.send(@method, (..0).step(-4)).should == [5, 1]
+ @array.send(@method, (..0).step(-5)).should == [5, 0]
+ @array.send(@method, (..0).step(-6)).should == [5]
+ @array.send(@method, (..0).step(-7)).should == [5]
+
+ @array.send(@method, (...0).step(-1)).should == [5, 4, 3, 2, 1]
+ @array.send(@method, (...0).step(-2)).should == [5, 3, 1]
+ @array.send(@method, (...0).step(-3)).should == [5, 2]
+ @array.send(@method, (...0).step(-4)).should == [5, 1]
+ @array.send(@method, (...0).step(-5)).should == [5]
+ @array.send(@method, (...0).step(-6)).should == [5]
+
+ @array.send(@method, (...1).step(-1)).should == [5, 4, 3, 2]
+ @array.send(@method, (...1).step(-2)).should == [5, 3]
+ @array.send(@method, (...1).step(-3)).should == [5, 2]
+ @array.send(@method, (...1).step(-4)).should == [5]
+ @array.send(@method, (...1).step(-5)).should == [5]
+
+ @array.send(@method, (..-5).step(-1)).should == [5, 4, 3, 2, 1]
+ @array.send(@method, (..-5).step(-2)).should == [5, 3, 1]
+ @array.send(@method, (..-5).step(-3)).should == [5, 2]
+ @array.send(@method, (..-5).step(-4)).should == [5, 1]
+ @array.send(@method, (..-5).step(-5)).should == [5]
+ @array.send(@method, (..-5).step(-6)).should == [5]
+
+ @array.send(@method, (...-5).step(-1)).should == [5, 4, 3, 2]
+ @array.send(@method, (...-5).step(-2)).should == [5, 3]
+ @array.send(@method, (...-5).step(-3)).should == [5, 2]
+ @array.send(@method, (...-5).step(-4)).should == [5]
+ @array.send(@method, (...-5).step(-5)).should == [5]
+
+ @array.send(@method, (4..1).step(-1)).should == [4, 3, 2, 1]
+ @array.send(@method, (4..1).step(-2)).should == [4, 2]
+ @array.send(@method, (4..1).step(-3)).should == [4, 1]
+ @array.send(@method, (4..1).step(-4)).should == [4]
+ @array.send(@method, (4..1).step(-5)).should == [4]
+
+ @array.send(@method, (4...1).step(-1)).should == [4, 3, 2]
+ @array.send(@method, (4...1).step(-2)).should == [4, 2]
+ @array.send(@method, (4...1).step(-3)).should == [4]
+ @array.send(@method, (4...1).step(-4)).should == [4]
+
+ @array.send(@method, (-2..1).step(-1)).should == [4, 3, 2, 1]
+ @array.send(@method, (-2..1).step(-2)).should == [4, 2]
+ @array.send(@method, (-2..1).step(-3)).should == [4, 1]
+ @array.send(@method, (-2..1).step(-4)).should == [4]
+ @array.send(@method, (-2..1).step(-5)).should == [4]
+
+ @array.send(@method, (-2...1).step(-1)).should == [4, 3, 2]
+ @array.send(@method, (-2...1).step(-2)).should == [4, 2]
+ @array.send(@method, (-2...1).step(-3)).should == [4]
+ @array.send(@method, (-2...1).step(-4)).should == [4]
+
+ @array.send(@method, (4..-5).step(-1)).should == [4, 3, 2, 1]
+ @array.send(@method, (4..-5).step(-2)).should == [4, 2]
+ @array.send(@method, (4..-5).step(-3)).should == [4, 1]
+ @array.send(@method, (4..-5).step(-4)).should == [4]
+ @array.send(@method, (4..-5).step(-5)).should == [4]
+
+ @array.send(@method, (4...-5).step(-1)).should == [4, 3, 2]
+ @array.send(@method, (4...-5).step(-2)).should == [4, 2]
+ @array.send(@method, (4...-5).step(-3)).should == [4]
+ @array.send(@method, (4...-5).step(-4)).should == [4]
+
+ @array.send(@method, (-2..-5).step(-1)).should == [4, 3, 2, 1]
+ @array.send(@method, (-2..-5).step(-2)).should == [4, 2]
+ @array.send(@method, (-2..-5).step(-3)).should == [4, 1]
+ @array.send(@method, (-2..-5).step(-4)).should == [4]
+ @array.send(@method, (-2..-5).step(-5)).should == [4]
+
+ @array.send(@method, (-2...-5).step(-1)).should == [4, 3, 2]
+ @array.send(@method, (-2...-5).step(-2)).should == [4, 2]
+ @array.send(@method, (-2...-5).step(-3)).should == [4]
+ @array.send(@method, (-2...-5).step(-4)).should == [4]
+ end
+ end
+ end
+
+ it "can accept nil...nil ranges" do
+ a = [0, 1, 2, 3, 4, 5]
+ a.send(@method, eval("(nil...nil)")).should == a
+ a.send(@method, (...nil)).should == a
+ a.send(@method, eval("(nil..)")).should == a
+ end
+end
diff --git a/spec/ruby/core/array/shared/union.rb b/spec/ruby/core/array/shared/union.rb
new file mode 100644
index 0000000000..0b60df9ca4
--- /dev/null
+++ b/spec/ruby/core/array/shared/union.rb
@@ -0,0 +1,79 @@
+describe :array_binary_union, shared: true do
+ it "returns an array of elements that appear in either array (union)" do
+ [].send(@method, []).should == []
+ [1, 2].send(@method, []).should == [1, 2]
+ [].send(@method, [1, 2]).should == [1, 2]
+ [ 1, 2, 3, 4 ].send(@method, [ 3, 4, 5 ]).should == [1, 2, 3, 4, 5]
+ end
+
+ it "creates an array with no duplicates" do
+ [ 1, 2, 3, 1, 4, 5 ].send(@method, [ 1, 3, 4, 5, 3, 6 ]).should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "creates an array with elements in order they are first encountered" do
+ [ 1, 2, 3, 1 ].send(@method, [ 1, 3, 4, 5 ]).should == [1, 2, 3, 4, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.send(@method, empty).should == empty
+
+ array = ArraySpecs.recursive_array
+ array.send(@method, []).should == [1, 'two', 3.0, array]
+ [].send(@method, array).should == [1, 'two', 3.0, array]
+ array.send(@method, array).should == [1, 'two', 3.0, array]
+ array.send(@method, empty).should == [1, 'two', 3.0, array, empty]
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('[1,2,3]')
+ obj.should_receive(:to_ary).and_return([1, 2, 3])
+ [0].send(@method, obj).should == ([0] | [1, 2, 3])
+ end
+
+ # MRI follows hashing semantics here, so doesn't actually call eql?/hash for Integer/Symbol
+ it "acts as if using an intermediate hash to collect values" do
+ not_supported_on :opal do
+ [5.0, 4.0].send(@method, [5, 4]).should == [5.0, 4.0, 5, 4]
+ end
+
+ str = "x"
+ [str].send(@method, [str.dup]).should == [str]
+
+ obj1 = mock('1')
+ obj2 = mock('2')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj2.should_receive(:eql?).at_least(1).and_return(true)
+
+ [obj1].send(@method, [obj2]).should == [obj1]
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1]
+
+ obj1 = mock('3')
+ obj2 = mock('4')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj2.should_receive(:eql?).at_least(1).and_return(false)
+
+ [obj1].send(@method, [obj2]).should == [obj1, obj2]
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1, obj2]
+ end
+
+ it "does not return subclass instances for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method, []).should be_an_instance_of(Array)
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_an_instance_of(Array)
+ [].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_an_instance_of(Array)
+ end
+
+ it "does not call to_ary on array subclasses" do
+ [1, 2].send(@method, ArraySpecs::ToAryArray[5, 6]).should == [1, 2, 5, 6]
+ end
+
+ it "properly handles an identical item even when its #eql? isn't reflexive" do
+ x = mock('x')
+ x.stub!(:hash).and_return(42)
+ x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI.
+
+ [x].send(@method, [x]).should == [x]
+ end
+end
diff --git a/spec/ruby/core/array/shared/unshift.rb b/spec/ruby/core/array/shared/unshift.rb
new file mode 100644
index 0000000000..4941e098f6
--- /dev/null
+++ b/spec/ruby/core/array/shared/unshift.rb
@@ -0,0 +1,64 @@
+describe :array_unshift, shared: true do
+ it "prepends object to the original array" do
+ a = [1, 2, 3]
+ a.send(@method, "a").should equal(a)
+ a.should == ['a', 1, 2, 3]
+ a.send(@method).should equal(a)
+ a.should == ['a', 1, 2, 3]
+ a.send(@method, 5, 4, 3)
+ a.should == [5, 4, 3, 'a', 1, 2, 3]
+
+ # shift all but one element
+ a = [1, 2]
+ a.shift
+ a.send(@method, 3, 4)
+ a.should == [3, 4, 2]
+
+ # now shift all elements
+ a.shift
+ a.shift
+ a.shift
+ a.send(@method, 3, 4)
+ a.should == [3, 4]
+ end
+
+ it "returns self" do
+ a = [1, 2, 3]
+ a.send(@method, "a").should.equal?(a)
+ end
+
+ it "quietly ignores unshifting nothing" do
+ [].send(@method).should == []
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.send(@method, :new).should == [:new, empty]
+
+ array = ArraySpecs.recursive_array
+ array.send(@method, :new)
+ array[0..5].should == [:new, 1, 'two', 3.0, array, array]
+ end
+
+ it "raises a FrozenError on a frozen array when the array is modified" do
+ -> { ArraySpecs.frozen_array.send(@method, 1) }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on a frozen array when the array would not be modified" do
+ -> { ArraySpecs.frozen_array.send(@method) }.should raise_error(FrozenError)
+ end
+
+ # https://github.com/oracle/truffleruby/issues/2772
+ it "doesn't rely on Array#[]= so it can be overridden" do
+ subclass = Class.new(Array) do
+ def []=(*)
+ raise "[]= is called"
+ end
+ end
+
+ array = subclass.new
+ array.send(@method, 1)
+ array.should == [1]
+ end
+end
diff --git a/spec/ruby/core/array/shift_spec.rb b/spec/ruby/core/array/shift_spec.rb
new file mode 100644
index 0000000000..6b4ef39f77
--- /dev/null
+++ b/spec/ruby/core/array/shift_spec.rb
@@ -0,0 +1,120 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#shift" do
+ it "removes and returns the first element" do
+ a = [5, 1, 1, 5, 4]
+ a.shift.should == 5
+ a.should == [1, 1, 5, 4]
+ a.shift.should == 1
+ a.should == [1, 5, 4]
+ a.shift.should == 1
+ a.should == [5, 4]
+ a.shift.should == 5
+ a.should == [4]
+ a.shift.should == 4
+ a.should == []
+ end
+
+ it "returns nil when the array is empty" do
+ [].shift.should == nil
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.shift.should == []
+ empty.should == []
+
+ array = ArraySpecs.recursive_array
+ array.shift.should == 1
+ array[0..2].should == ['two', 3.0, array]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.shift }.should raise_error(FrozenError)
+ end
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.shift }.should raise_error(FrozenError)
+ end
+
+ describe "passed a number n as an argument" do
+ it "removes and returns an array with the first n element of the array" do
+ a = [1, 2, 3, 4, 5, 6]
+
+ a.shift(0).should == []
+ a.should == [1, 2, 3, 4, 5, 6]
+
+ a.shift(1).should == [1]
+ a.should == [2, 3, 4, 5, 6]
+
+ a.shift(2).should == [2, 3]
+ a.should == [4, 5, 6]
+
+ a.shift(3).should == [4, 5, 6]
+ a.should == []
+ end
+
+ it "does not corrupt the array when shift without arguments is followed by shift with an argument" do
+ a = [1, 2, 3, 4, 5]
+
+ a.shift.should == 1
+ a.shift(3).should == [2, 3, 4]
+ a.should == [5]
+ end
+
+ it "returns a new empty array if there are no more elements" do
+ a = []
+ popped1 = a.shift(1)
+ popped1.should == []
+ a.should == []
+
+ popped2 = a.shift(2)
+ popped2.should == []
+ a.should == []
+
+ popped1.should_not equal(popped2)
+ end
+
+ it "returns whole elements if n exceeds size of the array" do
+ a = [1, 2, 3, 4, 5]
+ a.shift(6).should == [1, 2, 3, 4, 5]
+ a.should == []
+ end
+
+ it "does not return self even when it returns whole elements" do
+ a = [1, 2, 3, 4, 5]
+ a.shift(5).should_not equal(a)
+
+ a = [1, 2, 3, 4, 5]
+ a.shift(6).should_not equal(a)
+ end
+
+ it "raises an ArgumentError if n is negative" do
+ ->{ [1, 2, 3].shift(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ a = [1, 2, 3, 4]
+ a.shift(2.3).should == [1, 2]
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ a.should == [3, 4]
+ a.shift(obj).should == [3, 4]
+ a.should == []
+ end
+
+ it "raises a TypeError when the passed n cannot be coerced to Integer" do
+ ->{ [1, 2].shift("cat") }.should raise_error(TypeError)
+ ->{ [1, 2].shift(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError if more arguments are passed" do
+ ->{ [1, 2].shift(1, 2) }.should raise_error(ArgumentError)
+ end
+
+ it "does not return subclass instances with Array subclass" do
+ ArraySpecs::MyArray[1, 2, 3].shift(2).should be_an_instance_of(Array)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/shuffle_spec.rb b/spec/ruby/core/array/shuffle_spec.rb
new file mode 100644
index 0000000000..b255147c75
--- /dev/null
+++ b/spec/ruby/core/array/shuffle_spec.rb
@@ -0,0 +1,96 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#shuffle" do
+ it "returns the same values, in a usually different order" do
+ a = [1, 2, 3, 4]
+ different = false
+ 10.times do
+ s = a.shuffle
+ s.sort.should == a
+ different ||= (a != s)
+ end
+ different.should be_true # Will fail once in a blue moon (4!^10)
+ end
+
+ it "is not destructive" do
+ a = [1, 2, 3]
+ 10.times do
+ a.shuffle
+ a.should == [1, 2, 3]
+ end
+ end
+
+ it "does not return subclass instances with Array subclass" do
+ ArraySpecs::MyArray[1, 2, 3].shuffle.should be_an_instance_of(Array)
+ end
+
+ it "calls #rand on the Object passed by the :random key in the arguments Hash" do
+ obj = mock("array_shuffle_random")
+ obj.should_receive(:rand).at_least(1).times.and_return(0.5)
+
+ result = [1, 2].shuffle(random: obj)
+ result.size.should == 2
+ result.should include(1, 2)
+ end
+
+ it "raises a NoMethodError if an object passed for the RNG does not define #rand" do
+ obj = BasicObject.new
+
+ -> { [1, 2].shuffle(random: obj) }.should raise_error(NoMethodError)
+ end
+
+ it "accepts a Float for the value returned by #rand" do
+ random = mock("array_shuffle_random")
+ random.should_receive(:rand).at_least(1).times.and_return(0.3)
+
+ [1, 2].shuffle(random: random).should be_an_instance_of(Array)
+ end
+
+ it "calls #to_int on the Object returned by #rand" do
+ value = mock("array_shuffle_random_value")
+ value.should_receive(:to_int).at_least(1).times.and_return(0)
+ random = mock("array_shuffle_random")
+ random.should_receive(:rand).at_least(1).times.and_return(value)
+
+ [1, 2].shuffle(random: random).should be_an_instance_of(Array)
+ end
+
+ it "raises a RangeError if the value is less than zero" do
+ value = mock("array_shuffle_random_value")
+ value.should_receive(:to_int).and_return(-1)
+ random = mock("array_shuffle_random")
+ random.should_receive(:rand).and_return(value)
+
+ -> { [1, 2].shuffle(random: random) }.should raise_error(RangeError)
+ end
+
+ it "raises a RangeError if the value is equal to one" do
+ value = mock("array_shuffle_random_value")
+ value.should_receive(:to_int).at_least(1).times.and_return(1)
+ random = mock("array_shuffle_random")
+ random.should_receive(:rand).at_least(1).times.and_return(value)
+
+ -> { [1, 2].shuffle(random: random) }.should raise_error(RangeError)
+ end
+end
+
+describe "Array#shuffle!" do
+ it "returns the same values, in a usually different order" do
+ a = [1, 2, 3, 4]
+ original = a
+ different = false
+ 10.times do
+ a = a.shuffle!
+ a.sort.should == [1, 2, 3, 4]
+ different ||= (a != [1, 2, 3, 4])
+ end
+ different.should be_true # Will fail once in a blue moon (4!^10)
+ a.should equal(original)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.shuffle! }.should raise_error(FrozenError)
+ -> { ArraySpecs.empty_frozen_array.shuffle! }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/size_spec.rb b/spec/ruby/core/array/size_spec.rb
new file mode 100644
index 0000000000..d68f956a83
--- /dev/null
+++ b/spec/ruby/core/array/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/length'
+
+describe "Array#size" do
+ it_behaves_like :array_length, :size
+end
diff --git a/spec/ruby/core/array/slice_spec.rb b/spec/ruby/core/array/slice_spec.rb
new file mode 100644
index 0000000000..8e1499855a
--- /dev/null
+++ b/spec/ruby/core/array/slice_spec.rb
@@ -0,0 +1,246 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/slice'
+
+describe "Array#slice!" do
+ it "removes and return the element at index" do
+ a = [1, 2, 3, 4]
+ a.slice!(10).should == nil
+ a.should == [1, 2, 3, 4]
+ a.slice!(-10).should == nil
+ a.should == [1, 2, 3, 4]
+ a.slice!(2).should == 3
+ a.should == [1, 2, 4]
+ a.slice!(-1).should == 4
+ a.should == [1, 2]
+ a.slice!(1).should == 2
+ a.should == [1]
+ a.slice!(-1).should == 1
+ a.should == []
+ a.slice!(-1).should == nil
+ a.should == []
+ a.slice!(0).should == nil
+ a.should == []
+ end
+
+ it "removes and returns length elements beginning at start" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.slice!(2, 3).should == [3, 4, 5]
+ a.should == [1, 2, 6]
+ a.slice!(1, 1).should == [2]
+ a.should == [1, 6]
+ a.slice!(1, 0).should == []
+ a.should == [1, 6]
+ a.slice!(2, 0).should == []
+ a.should == [1, 6]
+ a.slice!(0, 4).should == [1, 6]
+ a.should == []
+ a.slice!(0, 4).should == []
+ a.should == []
+
+ a = [1]
+ a.slice!(0, 1).should == [1]
+ a.should == []
+ a[-1].should == nil
+
+ a = [1, 2, 3]
+ a.slice!(0,1).should == [1]
+ a.should == [2, 3]
+ end
+
+ it "returns nil if length is negative" do
+ a = [1, 2, 3]
+ a.slice!(2, -1).should == nil
+ a.should == [1, 2, 3]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.slice(0).should == empty
+
+ array = ArraySpecs.recursive_array
+ array.slice(4).should == array
+ array.slice(0..3).should == [1, 'two', 3.0, array]
+ end
+
+ it "calls to_int on start and length arguments" do
+ obj = mock('2')
+ def obj.to_int() 2 end
+
+ a = [1, 2, 3, 4, 5]
+ a.slice!(obj).should == 3
+ a.should == [1, 2, 4, 5]
+ a.slice!(obj, obj).should == [4, 5]
+ a.should == [1, 2]
+ a.slice!(0, obj).should == [1, 2]
+ a.should == []
+ end
+
+ it "removes and return elements in range" do
+ a = [1, 2, 3, 4, 5, 6, 7, 8]
+ a.slice!(1..4).should == [2, 3, 4, 5]
+ a.should == [1, 6, 7, 8]
+ a.slice!(1...3).should == [6, 7]
+ a.should == [1, 8]
+ a.slice!(-1..-1).should == [8]
+ a.should == [1]
+ a.slice!(0...0).should == []
+ a.should == [1]
+ a.slice!(0..0).should == [1]
+ a.should == []
+
+ a = [1,2,3]
+ a.slice!(0..3).should == [1,2,3]
+ a.should == []
+ end
+
+ it "removes and returns elements in end-exclusive ranges" do
+ a = [1, 2, 3, 4, 5, 6, 7, 8]
+ a.slice!(4...a.length).should == [5, 6, 7, 8]
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "calls to_int on range arguments" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ def from.to_int() 1 end
+ def to.to_int() -2 end
+
+ a = [1, 2, 3, 4, 5]
+
+ a.slice!(from .. to).should == [2, 3, 4]
+ a.should == [1, 5]
+
+ -> { a.slice!("a" .. "b") }.should raise_error(TypeError)
+ -> { a.slice!(from .. "b") }.should raise_error(TypeError)
+ end
+
+ it "returns last element for consecutive calls at zero index" do
+ a = [ 1, 2, 3 ]
+ a.slice!(0).should == 1
+ a.slice!(0).should == 2
+ a.slice!(0).should == 3
+ a.should == []
+ end
+
+ it "does not expand array with indices out of bounds" do
+ a = [1, 2]
+ a.slice!(4).should == nil
+ a.should == [1, 2]
+ a.slice!(4, 0).should == nil
+ a.should == [1, 2]
+ a.slice!(6, 1).should == nil
+ a.should == [1, 2]
+ a.slice!(8...8).should == nil
+ a.should == [1, 2]
+ a.slice!(10..10).should == nil
+ a.should == [1, 2]
+ end
+
+ it "does not expand array with negative indices out of bounds" do
+ a = [1, 2]
+ a.slice!(-3, 1).should == nil
+ a.should == [1, 2]
+ a.slice!(-3..2).should == nil
+ a.should == [1, 2]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.slice!(0, 0) }.should raise_error(FrozenError)
+ end
+
+ it "works with endless ranges" do
+ a = [1, 2, 3]
+ a.slice!(eval("(1..)")).should == [2, 3]
+ a.should == [1]
+
+ a = [1, 2, 3]
+ a.slice!(eval("(2...)")).should == [3]
+ a.should == [1, 2]
+
+ a = [1, 2, 3]
+ a.slice!(eval("(-2..)")).should == [2, 3]
+ a.should == [1]
+
+ a = [1, 2, 3]
+ a.slice!(eval("(-1...)")).should == [3]
+ a.should == [1, 2]
+ end
+
+ it "works with beginless ranges" do
+ a = [0,1,2,3,4]
+ a.slice!((..3)).should == [0, 1, 2, 3]
+ a.should == [4]
+
+ a = [0,1,2,3,4]
+ a.slice!((...-2)).should == [0, 1, 2]
+ a.should == [3, 4]
+ end
+
+ describe "with a subclass of Array" do
+ before :each do
+ @array = ArraySpecs::MyArray[1, 2, 3, 4, 5]
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a subclass instance with [n, m]" do
+ @array.slice!(0, 2).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "returns a subclass instance with [-n, m]" do
+ @array.slice!(-3, 2).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "returns a subclass instance with [n..m]" do
+ @array.slice!(1..3).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "returns a subclass instance with [n...m]" do
+ @array.slice!(1...3).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "returns a subclass instance with [-n..-m]" do
+ @array.slice!(-3..-1).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+
+ it "returns a subclass instance with [-n...-m]" do
+ @array.slice!(-3...-1).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a Array instance with [n, m]" do
+ @array.slice!(0, 2).should be_an_instance_of(Array)
+ end
+
+ it "returns a Array instance with [-n, m]" do
+ @array.slice!(-3, 2).should be_an_instance_of(Array)
+ end
+
+ it "returns a Array instance with [n..m]" do
+ @array.slice!(1..3).should be_an_instance_of(Array)
+ end
+
+ it "returns a Array instance with [n...m]" do
+ @array.slice!(1...3).should be_an_instance_of(Array)
+ end
+
+ it "returns a Array instance with [-n..-m]" do
+ @array.slice!(-3..-1).should be_an_instance_of(Array)
+ end
+
+ it "returns a Array instance with [-n...-m]" do
+ @array.slice!(-3...-1).should be_an_instance_of(Array)
+ end
+ end
+ end
+end
+
+describe "Array#slice" do
+ it_behaves_like :array_slice, :slice
+end
diff --git a/spec/ruby/core/array/sort_by_spec.rb b/spec/ruby/core/array/sort_by_spec.rb
new file mode 100644
index 0000000000..7cea6ec6d3
--- /dev/null
+++ b/spec/ruby/core/array/sort_by_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#sort_by!" do
+ it "sorts array in place by passing each element to the given block" do
+ a = [-100, -2, 1, 200, 30000]
+ a.sort_by!{ |e| e.to_s.size }
+ a.should == [1, -2, 200, -100, 30000]
+ end
+
+ it "returns an Enumerator if not given a block" do
+ (1..10).to_a.sort_by!.should be_an_instance_of(Enumerator)
+ end
+
+ it "completes when supplied a block that always returns the same result" do
+ a = [2, 3, 5, 1, 4]
+ a.sort_by!{ 1 }
+ a.should be_an_instance_of(Array)
+ a.sort_by!{ 0 }
+ a.should be_an_instance_of(Array)
+ a.sort_by!{ -1 }
+ a.should be_an_instance_of(Array)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.sort_by! {}}.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.sort_by! {}}.should raise_error(FrozenError)
+ end
+
+ it "returns the specified value when it would break in the given block" do
+ [1, 2, 3].sort_by!{ break :a }.should == :a
+ end
+
+ it "makes some modification even if finished sorting when it would break in the given block" do
+ partially_sorted = (1..5).map{|i|
+ ary = [5, 4, 3, 2, 1]
+ ary.sort_by!{|x,y| break if x==i; x<=>y}
+ ary
+ }
+ partially_sorted.any?{|ary| ary != [1, 2, 3, 4, 5]}.should be_true
+ end
+
+ it "changes nothing when called on a single element array" do
+ [1].sort_by!(&:to_s).should == [1]
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :sort_by!, [1,2,3]
+end
diff --git a/spec/ruby/core/array/sort_spec.rb b/spec/ruby/core/array/sort_spec.rb
new file mode 100644
index 0000000000..e20b650516
--- /dev/null
+++ b/spec/ruby/core/array/sort_spec.rb
@@ -0,0 +1,252 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#sort" do
+ it "returns a new array sorted based on comparing elements with <=>" do
+ a = [1, -2, 3, 9, 1, 5, -5, 1000, -5, 2, -10, 14, 6, 23, 0]
+ a.sort.should == [-10, -5, -5, -2, 0, 1, 1, 2, 3, 5, 6, 9, 14, 23, 1000]
+ end
+
+ it "does not affect the original Array" do
+ a = [3, 1, 2]
+ a.sort.should == [1, 2, 3]
+ a.should == [3, 1, 2]
+
+ a = [0, 15, 2, 3, 4, 6, 14, 5, 7, 12, 8, 9, 1, 10, 11, 13]
+ b = a.sort
+ a.should == [0, 15, 2, 3, 4, 6, 14, 5, 7, 12, 8, 9, 1, 10, 11, 13]
+ b.should == (0..15).to_a
+ end
+
+ it "sorts already-sorted Arrays" do
+ (0..15).to_a.sort.should == (0..15).to_a
+ end
+
+ it "sorts reverse-sorted Arrays" do
+ (0..15).to_a.reverse.sort.should == (0..15).to_a
+ end
+
+ it "sorts Arrays that consist entirely of equal elements" do
+ a = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
+ a.sort.should == a
+ b = Array.new(15).map { ArraySpecs::SortSame.new }
+ b.sort.should == b
+ end
+
+ it "sorts Arrays that consist mostly of equal elements" do
+ a = [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]
+ a.sort.should == [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
+ end
+
+ it "does not return self even if the array would be already sorted" do
+ a = [1, 2, 3]
+ sorted = a.sort
+ sorted.should == a
+ sorted.should_not equal(a)
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.sort.should == empty
+
+ array = [[]]; array << array
+ array.sort.should == [[], array]
+ end
+
+ it "uses #<=> of elements in order to sort" do
+ a = ArraySpecs::MockForCompared.new
+ b = ArraySpecs::MockForCompared.new
+ c = ArraySpecs::MockForCompared.new
+
+ ArraySpecs::MockForCompared.should_not.compared?
+ [a, b, c].sort.should == [c, b, a]
+ ArraySpecs::MockForCompared.should.compared?
+ end
+
+ it "does not deal with exceptions raised by unimplemented or incorrect #<=>" do
+ o = Object.new
+
+ -> {
+ [o, 1].sort
+ }.should raise_error(ArgumentError)
+ end
+
+ it "may take a block which is used to determine the order of objects a and b described as -1, 0 or +1" do
+ a = [5, 1, 4, 3, 2]
+ a.sort.should == [1, 2, 3, 4, 5]
+ a.sort {|x, y| y <=> x}.should == [5, 4, 3, 2, 1]
+ end
+
+ it "raises an error when a given block returns nil" do
+ -> { [1, 2].sort {} }.should raise_error(ArgumentError)
+ end
+
+ it "does not call #<=> on contained objects when invoked with a block" do
+ a = Array.new(25)
+ (0...25).each {|i| a[i] = ArraySpecs::UFOSceptic.new }
+
+ a.sort { -1 }.should be_an_instance_of(Array)
+ end
+
+ it "does not call #<=> on elements when invoked with a block even if Array is large (Rubinius #412)" do
+ a = Array.new(1500)
+ (0...1500).each {|i| a[i] = ArraySpecs::UFOSceptic.new }
+
+ a.sort { -1 }.should be_an_instance_of(Array)
+ end
+
+ it "completes when supplied a block that always returns the same result" do
+ a = [2, 3, 5, 1, 4]
+ a.sort { 1 }.should be_an_instance_of(Array)
+ a.sort { 0 }.should be_an_instance_of(Array)
+ a.sort { -1 }.should be_an_instance_of(Array)
+ end
+
+ it "does not freezes self during being sorted" do
+ a = [1, 2, 3]
+ a.sort { |x,y| a.should_not.frozen?; x <=> y }
+ end
+
+ it "returns the specified value when it would break in the given block" do
+ [1, 2, 3].sort{ break :a }.should == :a
+ end
+
+ it "uses the sign of Integer block results as the sort result" do
+ a = [1, 2, 5, 10, 7, -4, 12]
+ begin
+ class Integer
+ alias old_spaceship <=>
+ def <=>(other)
+ raise
+ end
+ end
+ a.sort {|n, m| (n - m) * (2 ** 200)}.should == [-4, 1, 2, 5, 7, 10, 12]
+ ensure
+ class Integer
+ alias <=> old_spaceship
+ end
+ end
+ end
+
+ it "compares values returned by block with 0" do
+ a = [1, 2, 5, 10, 7, -4, 12]
+ a.sort { |n, m| n - m }.should == [-4, 1, 2, 5, 7, 10, 12]
+ a.sort { |n, m|
+ ArraySpecs::ComparableWithInteger.new(n-m)
+ }.should == [-4, 1, 2, 5, 7, 10, 12]
+ -> {
+ a.sort { |n, m| (n - m).to_s }
+ }.should raise_error(ArgumentError)
+ end
+
+ it "sorts an array that has a value shifted off without a block" do
+ a = Array.new(20, 1)
+ a.shift
+ a[0] = 2
+ a.sort.last.should == 2
+ end
+
+ it "sorts an array that has a value shifted off with a block" do
+ a = Array.new(20, 1)
+ a.shift
+ a[0] = 2
+ a.sort {|x, y| x <=> y }.last.should == 2
+ end
+
+ it "raises an error if objects can't be compared" do
+ a=[ArraySpecs::Uncomparable.new, ArraySpecs::Uncomparable.new]
+ -> {a.sort}.should raise_error(ArgumentError)
+ end
+
+ # From a strange Rubinius bug
+ it "handles a large array that has been pruned" do
+ pruned = ArraySpecs::LargeArray.dup.delete_if { |n| n !~ /^test./ }
+ pruned.sort.should == ArraySpecs::LargeTestArraySorted
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ary = ArraySpecs::MyArray[1, 2, 3]
+ ary.sort.should be_an_instance_of(Array)
+ end
+end
+
+describe "Array#sort!" do
+ it "sorts array in place using <=>" do
+ a = [1, -2, 3, 9, 1, 5, -5, 1000, -5, 2, -10, 14, 6, 23, 0]
+ a.sort!
+ a.should == [-10, -5, -5, -2, 0, 1, 1, 2, 3, 5, 6, 9, 14, 23, 1000]
+ end
+
+ it "sorts array in place using block value if a block given" do
+ a = [0, 15, 2, 3, 4, 6, 14, 5, 7, 12, 8, 9, 1, 10, 11, 13]
+ a.sort! { |x, y| y <=> x }.should == (0..15).to_a.reverse
+ end
+
+ it "returns self if the order of elements changed" do
+ a = [6, 7, 2, 3, 7]
+ a.sort!.should equal(a)
+ a.should == [2, 3, 6, 7, 7]
+ end
+
+ it "returns self even if makes no modification" do
+ a = [1, 2, 3, 4, 5]
+ a.sort!.should equal(a)
+ a.should == [1, 2, 3, 4, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.sort!.should == empty
+
+ array = [[]]; array << array
+ array.sort!.should == array
+ end
+
+ it "uses #<=> of elements in order to sort" do
+ a = ArraySpecs::MockForCompared.new
+ b = ArraySpecs::MockForCompared.new
+ c = ArraySpecs::MockForCompared.new
+
+ ArraySpecs::MockForCompared.should_not.compared?
+ [a, b, c].sort!.should == [c, b, a]
+ ArraySpecs::MockForCompared.should.compared?
+ end
+
+ it "does not call #<=> on contained objects when invoked with a block" do
+ a = Array.new(25)
+ (0...25).each {|i| a[i] = ArraySpecs::UFOSceptic.new }
+
+ a.sort! { -1 }.should be_an_instance_of(Array)
+ end
+
+ it "does not call #<=> on elements when invoked with a block even if Array is large (Rubinius #412)" do
+ a = Array.new(1500)
+ (0...1500).each {|i| a[i] = ArraySpecs::UFOSceptic.new }
+
+ a.sort! { -1 }.should be_an_instance_of(Array)
+ end
+
+ it "completes when supplied a block that always returns the same result" do
+ a = [2, 3, 5, 1, 4]
+ a.sort!{ 1 }.should be_an_instance_of(Array)
+ a.sort!{ 0 }.should be_an_instance_of(Array)
+ a.sort!{ -1 }.should be_an_instance_of(Array)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.sort! }.should raise_error(FrozenError)
+ end
+
+ it "returns the specified value when it would break in the given block" do
+ [1, 2, 3].sort{ break :a }.should == :a
+ end
+
+ it "makes some modification even if finished sorting when it would break in the given block" do
+ partially_sorted = (1..5).map{|i|
+ ary = [5, 4, 3, 2, 1]
+ ary.sort!{|x,y| break if x==i; x<=>y}
+ ary
+ }
+ partially_sorted.any?{|ary| ary != [1, 2, 3, 4, 5]}.should be_true
+ end
+end
diff --git a/spec/ruby/core/array/sum_spec.rb b/spec/ruby/core/array/sum_spec.rb
new file mode 100644
index 0000000000..8ca8353a67
--- /dev/null
+++ b/spec/ruby/core/array/sum_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+
+describe "Array#sum" do
+ it "returns the sum of elements" do
+ [1, 2, 3].sum.should == 6
+ end
+
+ it "applies a block to each element before adding if it's given" do
+ [1, 2, 3].sum { |i| i * 10 }.should == 60
+ end
+
+ # https://bugs.ruby-lang.org/issues/12217
+ # https://github.com/ruby/ruby/blob/master/doc/ChangeLog-2.4.0#L6208-L6214
+ it "uses Kahan's compensated summation algorithm for precise sum of float numbers" do
+ floats = [2.7800000000000002, 5.0, 2.5, 4.44, 3.89, 3.89, 4.44, 7.78, 5.0, 2.7800000000000002, 5.0, 2.5]
+ naive_sum = floats.reduce { |sum, e| sum + e }
+ naive_sum.should == 50.00000000000001
+ floats.sum.should == 50.0
+ end
+
+ it "handles infinite values and NaN" do
+ [1.0, Float::INFINITY].sum.should == Float::INFINITY
+ [1.0, -Float::INFINITY].sum.should == -Float::INFINITY
+ [1.0, Float::NAN].sum.should.nan?
+
+ [Float::INFINITY, 1.0].sum.should == Float::INFINITY
+ [-Float::INFINITY, 1.0].sum.should == -Float::INFINITY
+ [Float::NAN, 1.0].sum.should.nan?
+
+ [Float::NAN, Float::INFINITY].sum.should.nan?
+ [Float::INFINITY, Float::NAN].sum.should.nan?
+
+ [Float::INFINITY, -Float::INFINITY].sum.should.nan?
+ [-Float::INFINITY, Float::INFINITY].sum.should.nan?
+
+ [Float::INFINITY, Float::INFINITY].sum.should == Float::INFINITY
+ [-Float::INFINITY, -Float::INFINITY].sum.should == -Float::INFINITY
+ [Float::NAN, Float::NAN].sum.should.nan?
+ end
+
+ it "returns init value if array is empty" do
+ [].sum(-1).should == -1
+ end
+
+ it "returns 0 if array is empty and init is omitted" do
+ [].sum.should == 0
+ end
+
+ it "adds init value to the sum of elements" do
+ [1, 2, 3].sum(10).should == 16
+ end
+
+ it "can be used for non-numeric objects by providing init value" do
+ ["a", "b", "c"].sum("").should == "abc"
+ end
+
+ it 'raises TypeError if any element are not numeric' do
+ -> { ["a"].sum }.should raise_error(TypeError)
+ end
+
+ it 'raises TypeError if any element cannot be added to init value' do
+ -> { [1].sum([]) }.should raise_error(TypeError)
+ end
+
+ it "calls + to sum the elements" do
+ a = mock("a")
+ b = mock("b")
+ a.should_receive(:+).with(b).and_return(42)
+ [b].sum(a).should == 42
+ end
+end
diff --git a/spec/ruby/core/array/take_spec.rb b/spec/ruby/core/array/take_spec.rb
new file mode 100644
index 0000000000..4fb6f0ce75
--- /dev/null
+++ b/spec/ruby/core/array/take_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#take" do
+ it "returns the first specified number of elements" do
+ [1, 2, 3].take(2).should == [1, 2]
+ end
+
+ it "returns all elements when the argument is greater than the Array size" do
+ [1, 2].take(99).should == [1, 2]
+ end
+
+ it "returns all elements when the argument is less than the Array size" do
+ [1, 2].take(4).should == [1, 2]
+ end
+
+ it "returns an empty Array when passed zero" do
+ [1].take(0).should == []
+ end
+
+ it "returns an empty Array when called on an empty Array" do
+ [].take(3).should == []
+ end
+
+ it "raises an ArgumentError when the argument is negative" do
+ ->{ [1].take(-3) }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it 'returns a subclass instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].take(1).should be_an_instance_of(ArraySpecs::MyArray)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it 'returns a Array instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].take(1).should be_an_instance_of(Array)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/take_while_spec.rb b/spec/ruby/core/array/take_while_spec.rb
new file mode 100644
index 0000000000..363419b265
--- /dev/null
+++ b/spec/ruby/core/array/take_while_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#take_while" do
+ it "returns all elements until the block returns false" do
+ [1, 2, 3].take_while{ |element| element < 3 }.should == [1, 2]
+ end
+
+ it "returns all elements until the block returns nil" do
+ [1, 2, nil, 4].take_while{ |element| element }.should == [1, 2]
+ end
+
+ it "returns all elements until the block returns false" do
+ [1, 2, false, 4].take_while{ |element| element }.should == [1, 2]
+ end
+
+ ruby_version_is ''...'3.0' do
+ it 'returns a subclass instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].take_while { |n| n < 4 }.should be_an_instance_of(ArraySpecs::MyArray)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it 'returns a Array instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].take_while { |n| n < 4 }.should be_an_instance_of(Array)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/to_a_spec.rb b/spec/ruby/core/array/to_a_spec.rb
new file mode 100644
index 0000000000..49d0a4782e
--- /dev/null
+++ b/spec/ruby/core/array/to_a_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#to_a" do
+ it "returns self" do
+ a = [1, 2, 3]
+ a.to_a.should == [1, 2, 3]
+ a.should equal(a.to_a)
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ e = ArraySpecs::MyArray.new(1, 2)
+ e.to_a.should be_an_instance_of(Array)
+ e.to_a.should == [1, 2]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.to_a.should == empty
+
+ array = ArraySpecs.recursive_array
+ array.to_a.should == array
+ end
+end
diff --git a/spec/ruby/core/array/to_ary_spec.rb b/spec/ruby/core/array/to_ary_spec.rb
new file mode 100644
index 0000000000..314699b709
--- /dev/null
+++ b/spec/ruby/core/array/to_ary_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#to_ary" do
+ it "returns self" do
+ a = [1, 2, 3]
+ a.should equal(a.to_ary)
+ a = ArraySpecs::MyArray[1, 2, 3]
+ a.should equal(a.to_ary)
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.to_ary.should == empty
+
+ array = ArraySpecs.recursive_array
+ array.to_ary.should == array
+ end
+
+end
diff --git a/spec/ruby/core/array/to_h_spec.rb b/spec/ruby/core/array/to_h_spec.rb
new file mode 100644
index 0000000000..f5a7e546e6
--- /dev/null
+++ b/spec/ruby/core/array/to_h_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#to_h" do
+ it "converts empty array to empty hash" do
+ [].to_h.should == {}
+ end
+
+ it "converts [key, value] pairs to a hash" do
+ hash = [[:a, 1], [:b, 2]].to_h
+ hash.should == { a: 1, b: 2 }
+ end
+
+ it "uses the last value of a duplicated key" do
+ hash = [[:a, 1], [:b, 2], [:a, 3]].to_h
+ hash.should == { a: 3, b: 2 }
+ end
+
+ it "calls #to_ary on contents" do
+ pair = mock('to_ary')
+ pair.should_receive(:to_ary).and_return([:b, 2])
+ hash = [[:a, 1], pair].to_h
+ hash.should == { a: 1, b: 2 }
+ end
+
+ it "raises TypeError if an element is not an array" do
+ -> { [:x].to_h }.should raise_error(TypeError)
+ end
+
+ it "raises ArgumentError if an element is not a [key, value] pair" do
+ -> { [[:x]].to_h }.should raise_error(ArgumentError)
+ end
+
+ it "does not accept arguments" do
+ -> { [].to_h(:a, :b) }.should raise_error(ArgumentError)
+ end
+
+ it "produces a hash that returns nil for a missing element" do
+ [[:a, 1], [:b, 2]].to_h[:c].should be_nil
+ end
+
+ context "with block" do
+ it "converts [key, value] pairs returned by the block to a Hash" do
+ [:a, :b].to_h { |k| [k, k.to_s] }.should == { a: 'a', b: 'b' }
+ end
+
+ it "raises ArgumentError if block returns longer or shorter array" do
+ -> do
+ [:a, :b].to_h { |k| [k, k.to_s, 1] }
+ end.should raise_error(ArgumentError, /wrong array length at 0/)
+
+ -> do
+ [:a, :b].to_h { |k| [k] }
+ end.should raise_error(ArgumentError, /wrong array length at 0/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ [:a, :b].to_h { |k| "not-array" }
+ end.should raise_error(TypeError, /wrong element type String at 0/)
+ end
+
+ it "coerces returned pair to Array with #to_ary" do
+ x = mock('x')
+ x.stub!(:to_ary).and_return([:b, 'b'])
+
+ [:a].to_h { |k| x }.should == { :b => 'b' }
+ end
+
+ it "does not coerce returned pair to Array with #to_a" do
+ x = mock('x')
+ x.stub!(:to_a).and_return([:b, 'b'])
+
+ -> do
+ [:a].to_h { |k| x }
+ end.should raise_error(TypeError, /wrong element type MockObject at 0/)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/to_s_spec.rb b/spec/ruby/core/array/to_s_spec.rb
new file mode 100644
index 0000000000..e8476702ec
--- /dev/null
+++ b/spec/ruby/core/array/to_s_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/join'
+require_relative 'shared/inspect'
+
+describe "Array#to_s" do
+ it_behaves_like :array_inspect, :to_s
+end
diff --git a/spec/ruby/core/array/transpose_spec.rb b/spec/ruby/core/array/transpose_spec.rb
new file mode 100644
index 0000000000..b39077f4c9
--- /dev/null
+++ b/spec/ruby/core/array/transpose_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#transpose" do
+ it "assumes an array of arrays and returns the result of transposing rows and columns" do
+ [[1, 'a'], [2, 'b'], [3, 'c']].transpose.should == [[1, 2, 3], ["a", "b", "c"]]
+ [[1, 2, 3], ["a", "b", "c"]].transpose.should == [[1, 'a'], [2, 'b'], [3, 'c']]
+ [].transpose.should == []
+ [[]].transpose.should == []
+ [[], []].transpose.should == []
+ [[0]].transpose.should == [[0]]
+ [[0], [1]].transpose.should == [[0, 1]]
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('[1,2]')
+ obj.should_receive(:to_ary).and_return([1, 2])
+ [obj, [:a, :b]].transpose.should == [[1, :a], [2, :b]]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.transpose.should == empty
+
+ a = []; a << a
+ b = []; b << b
+ [a, b].transpose.should == [[a, b]]
+
+ a = [1]; a << a
+ b = [2]; b << b
+ [a, b].transpose.should == [ [1, 2], [a, b] ]
+ end
+
+ it "raises a TypeError if the passed Argument does not respond to #to_ary" do
+ -> { [Object.new, [:a, :b]].transpose }.should raise_error(TypeError)
+ end
+
+ it "does not call to_ary on array subclass elements" do
+ ary = [ArraySpecs::ToAryArray[1, 2], ArraySpecs::ToAryArray[4, 6]]
+ ary.transpose.should == [[1, 4], [2, 6]]
+ end
+
+ it "raises an IndexError if the arrays are not of the same length" do
+ -> { [[1, 2], [:a]].transpose }.should raise_error(IndexError)
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ result = ArraySpecs::MyArray[ArraySpecs::MyArray[1, 2, 3], ArraySpecs::MyArray[4, 5, 6]].transpose
+ result.should be_an_instance_of(Array)
+ result[0].should be_an_instance_of(Array)
+ result[1].should be_an_instance_of(Array)
+ end
+end
diff --git a/spec/ruby/core/array/try_convert_spec.rb b/spec/ruby/core/array/try_convert_spec.rb
new file mode 100644
index 0000000000..47b4722d80
--- /dev/null
+++ b/spec/ruby/core/array/try_convert_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array.try_convert" do
+ it "returns the argument if it's an Array" do
+ x = Array.new
+ Array.try_convert(x).should equal(x)
+ end
+
+ it "returns the argument if it's a kind of Array" do
+ x = ArraySpecs::MyArray[]
+ Array.try_convert(x).should equal(x)
+ end
+
+ it "returns nil when the argument does not respond to #to_ary" do
+ Array.try_convert(Object.new).should be_nil
+ end
+
+ it "sends #to_ary to the argument and returns the result if it's nil" do
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_return(nil)
+ Array.try_convert(obj).should be_nil
+ end
+
+ it "sends #to_ary to the argument and returns the result if it's an Array" do
+ x = Array.new
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_return(x)
+ Array.try_convert(obj).should equal(x)
+ end
+
+ it "sends #to_ary to the argument and returns the result if it's a kind of Array" do
+ x = ArraySpecs::MyArray[]
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_return(x)
+ Array.try_convert(obj).should equal(x)
+ end
+
+ it "sends #to_ary to the argument and raises TypeError if it's not a kind of Array" do
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_return(Object.new)
+ -> { Array.try_convert obj }.should raise_error(TypeError)
+ end
+
+ it "does not rescue exceptions raised by #to_ary" do
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_raise(RuntimeError)
+ -> { Array.try_convert obj }.should raise_error(RuntimeError)
+ end
+end
diff --git a/spec/ruby/core/array/union_spec.rb b/spec/ruby/core/array/union_spec.rb
new file mode 100644
index 0000000000..ba2cc0d6b7
--- /dev/null
+++ b/spec/ruby/core/array/union_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/union'
+
+describe "Array#|" do
+ it_behaves_like :array_binary_union, :|
+end
+
+describe "Array#union" do
+ it_behaves_like :array_binary_union, :union
+
+ it "returns unique elements when given no argument" do
+ x = [1, 2, 3, 2]
+ x.union.should == [1, 2, 3]
+ end
+
+ it "does not return subclass instances for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].union.should be_an_instance_of(Array)
+ end
+
+ it "accepts multiple arguments" do
+ x = [1, 2, 3]
+ x.union(x, x, x, x, [3, 4], x).should == [1, 2, 3, 4]
+ end
+end
diff --git a/spec/ruby/core/array/uniq_spec.rb b/spec/ruby/core/array/uniq_spec.rb
new file mode 100644
index 0000000000..4461cae16b
--- /dev/null
+++ b/spec/ruby/core/array/uniq_spec.rb
@@ -0,0 +1,217 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#uniq" do
+ it "returns an array with no duplicates" do
+ ["a", "a", "b", "b", "c"].uniq.should == ["a", "b", "c"]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.uniq.should == [empty]
+
+ array = ArraySpecs.recursive_array
+ array.uniq.should == [1, 'two', 3.0, array]
+ end
+
+ it "uses eql? semantics" do
+ [1.0, 1].uniq.should == [1.0, 1]
+ end
+
+ it "compares elements first with hash" do
+ x = mock('0')
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y = mock('0')
+ y.should_receive(:hash).at_least(1).and_return(0)
+
+ [x, y].uniq.should == [x, y]
+ end
+
+ it "does not compare elements with different hash codes via eql?" do
+ x = mock('0')
+ x.should_not_receive(:eql?)
+ y = mock('1')
+ y.should_not_receive(:eql?)
+
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y.should_receive(:hash).at_least(1).and_return(1)
+
+ [x, y].uniq.should == [x, y]
+ end
+
+ it "compares elements with matching hash codes with #eql?" do
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ false
+ end
+
+ obj
+ end
+
+ a.uniq.should == a
+
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ true
+ end
+
+ obj
+ end
+
+ a.uniq.size.should == 1
+ end
+
+ it "compares elements based on the value returned from the block" do
+ a = [1, 2, 3, 4]
+ a.uniq { |x| x >= 2 ? 1 : 0 }.should == [1, 2]
+ end
+
+ it "yields items in order" do
+ a = [1, 2, 3]
+ yielded = []
+ a.uniq { |v| yielded << v }
+ yielded.should == a
+ end
+
+ it "handles nil and false like any other values" do
+ [nil, false, 42].uniq { :foo }.should == [nil]
+ [false, nil, 42].uniq { :bar }.should == [false]
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].uniq.should be_an_instance_of(ArraySpecs::MyArray)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns Array instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].uniq.should be_an_instance_of(Array)
+ end
+ end
+
+ it "properly handles an identical item even when its #eql? isn't reflexive" do
+ x = mock('x')
+ x.should_receive(:hash).at_least(1).and_return(42)
+ x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI.
+
+ [x, x].uniq.should == [x]
+ end
+
+ describe "given an array of BasicObject subclasses that define ==, eql?, and hash" do
+ # jruby/jruby#3227
+ it "filters equivalent elements using those definitions" do
+
+ basic = Class.new(BasicObject) do
+ attr_reader :x
+
+ def initialize(x)
+ @x = x
+ end
+
+ def ==(rhs)
+ @x == rhs.x
+ end
+ alias_method :eql?, :==
+
+ def hash
+ @x.hash
+ end
+ end
+
+ a = [basic.new(3), basic.new(2), basic.new(1), basic.new(4), basic.new(1), basic.new(2), basic.new(3)]
+ a.uniq.should == [basic.new(3), basic.new(2), basic.new(1), basic.new(4)]
+ end
+ end
+end
+
+describe "Array#uniq!" do
+ it "modifies the array in place" do
+ a = [ "a", "a", "b", "b", "c" ]
+ a.uniq!
+ a.should == ["a", "b", "c"]
+ end
+
+ it "returns self" do
+ a = [ "a", "a", "b", "b", "c" ]
+ a.should equal(a.uniq!)
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty_dup = empty.dup
+ empty.uniq!
+ empty.should == empty_dup
+
+ array = ArraySpecs.recursive_array
+ expected = array[0..3]
+ array.uniq!
+ array.should == expected
+ end
+
+ it "compares elements first with hash" do
+ x = mock('0')
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y = mock('0')
+ y.should_receive(:hash).at_least(1).and_return(0)
+
+ a = [x, y]
+ a.uniq!
+ a.should == [x, y]
+ end
+
+ it "does not compare elements with different hash codes via eql?" do
+ x = mock('0')
+ x.should_not_receive(:eql?)
+ y = mock('1')
+ y.should_not_receive(:eql?)
+
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y.should_receive(:hash).at_least(1).and_return(1)
+
+ a = [x, y]
+ a.uniq!
+ a.should == [x, y]
+ end
+
+ it "returns nil if no changes are made to the array" do
+ [ "a", "b", "c" ].uniq!.should == nil
+ end
+
+ it "raises a FrozenError on a frozen array when the array is modified" do
+ dup_ary = [1, 1, 2]
+ dup_ary.freeze
+ -> { dup_ary.uniq! }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on a frozen array when the array would not be modified" do
+ -> { ArraySpecs.frozen_array.uniq!}.should raise_error(FrozenError)
+ -> { ArraySpecs.empty_frozen_array.uniq!}.should raise_error(FrozenError)
+ end
+
+ it "doesn't yield to the block on a frozen array" do
+ -> { ArraySpecs.frozen_array.uniq!{ raise RangeError, "shouldn't yield"}}.should raise_error(FrozenError)
+ end
+
+ it "compares elements based on the value returned from the block" do
+ a = [1, 2, 3, 4]
+ a.uniq! { |x| x >= 2 ? 1 : 0 }.should == [1, 2]
+ end
+
+ it "properly handles an identical item even when its #eql? isn't reflexive" do
+ x = mock('x')
+ x.should_receive(:hash).at_least(1).and_return(42)
+ x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI.
+
+ a = [x, x]
+ a.uniq!
+ a.should == [x]
+ end
+end
diff --git a/spec/ruby/core/array/unshift_spec.rb b/spec/ruby/core/array/unshift_spec.rb
new file mode 100644
index 0000000000..b8b675e5f8
--- /dev/null
+++ b/spec/ruby/core/array/unshift_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/unshift'
+
+describe "Array#unshift" do
+ it_behaves_like :array_unshift, :unshift
+end
diff --git a/spec/ruby/core/array/values_at_spec.rb b/spec/ruby/core/array/values_at_spec.rb
new file mode 100644
index 0000000000..e85bbee400
--- /dev/null
+++ b/spec/ruby/core/array/values_at_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# Should be synchronized with core/struct/values_at_spec.rb
+describe "Array#values_at" do
+ it "returns an array of elements at the indexes when passed indexes" do
+ [1, 2, 3, 4, 5].values_at().should == []
+ [1, 2, 3, 4, 5].values_at(1, 0, 5, -1, -8, 10).should == [2, 1, nil, 5, nil, nil]
+ end
+
+ it "calls to_int on its indices" do
+ obj = mock('1')
+ def obj.to_int() 1 end
+ [1, 2].values_at(obj, obj, obj).should == [2, 2, 2]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.values_at(0, 1, 2).should == [empty, nil, nil]
+
+ array = ArraySpecs.recursive_array
+ array.values_at(0, 1, 2, 3).should == [1, 'two', 3.0, array]
+ end
+
+ describe "when passed ranges" do
+ it "returns an array of elements in the ranges" do
+ [1, 2, 3, 4, 5].values_at(0..2, 1...3, 2..-2).should == [1, 2, 3, 2, 3, 3, 4]
+ [1, 2, 3, 4, 5].values_at(6..4).should == []
+ end
+
+ it "calls to_int on arguments of ranges" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ def from.to_int() 1 end
+ def to.to_int() -2 end
+
+ ary = [1, 2, 3, 4, 5]
+ ary.values_at(from .. to, from ... to, to .. from).should == [2, 3, 4, 2, 3]
+ end
+ end
+
+ describe "when passed a range" do
+ it "fills with nil if the index is out of the range" do
+ [0, 1].values_at(0..3).should == [0, 1, nil, nil]
+ [0, 1].values_at(2..4).should == [nil, nil, nil]
+ end
+
+ describe "on an empty array" do
+ it "fills with nils if the index is out of the range" do
+ [].values_at(0..2).should == [nil, nil, nil]
+ [].values_at(1..3).should == [nil, nil, nil]
+ end
+ end
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].values_at(0, 1..2, 1).should be_an_instance_of(Array)
+ end
+
+ it "works when given endless ranges" do
+ [1, 2, 3, 4].values_at(eval("(1..)")).should == [2, 3, 4]
+ [1, 2, 3, 4].values_at(eval("(3...)")).should == [4]
+ end
+
+ it "works when given beginless ranges" do
+ [1, 2, 3, 4].values_at((..2)).should == [1, 2, 3]
+ [1, 2, 3, 4].values_at((...2)).should == [1, 2]
+ end
+end
diff --git a/spec/ruby/core/array/zip_spec.rb b/spec/ruby/core/array/zip_spec.rb
new file mode 100644
index 0000000000..2a0f64cb49
--- /dev/null
+++ b/spec/ruby/core/array/zip_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#zip" do
+ it "returns an array of arrays containing corresponding elements of each array" do
+ [1, 2, 3, 4].zip(["a", "b", "c", "d", "e"]).should ==
+ [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
+ end
+
+ it "fills in missing values with nil" do
+ [1, 2, 3, 4, 5].zip(["a", "b", "c", "d"]).should ==
+ [[1, "a"], [2, "b"], [3, "c"], [4, "d"], [5, nil]]
+ end
+
+ it "properly handles recursive arrays" do
+ a = []; a << a
+ b = [1]; b << b
+
+ a.zip(a).should == [ [a[0], a[0]] ]
+ a.zip(b).should == [ [a[0], b[0]] ]
+ b.zip(a).should == [ [b[0], a[0]], [b[1], a[1]] ]
+ b.zip(b).should == [ [b[0], b[0]], [b[1], b[1]] ]
+ end
+
+ it "calls #to_ary to convert the argument to an Array" do
+ obj = mock('[3,4]')
+ obj.should_receive(:to_ary).and_return([3, 4])
+ [1, 2].zip(obj).should == [[1, 3], [2, 4]]
+ end
+
+ it "uses #each to extract arguments' elements when #to_ary fails" do
+ obj = Class.new do
+ def each(&b)
+ [3,4].each(&b)
+ end
+ end.new
+
+ [1, 2].zip(obj).should == [[1, 3], [2, 4]]
+ end
+
+ it "stops at own size when given an infinite enumerator" do
+ [1, 2].zip(10.upto(Float::INFINITY)).should == [[1, 10], [2, 11]]
+ end
+
+ it "fills nil when the given enumerator is shorter than self" do
+ obj = Object.new
+ def obj.each
+ yield 10
+ end
+ [1, 2].zip(obj).should == [[1, 10], [2, nil]]
+ end
+
+ it "calls block if supplied" do
+ values = []
+ [1, 2, 3, 4].zip(["a", "b", "c", "d", "e"]) { |value|
+ values << value
+ }.should == nil
+
+ values.should == [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].zip(["a", "b"]).should be_an_instance_of(Array)
+ end
+
+ it "raises TypeError when some argument isn't Array and doesn't respond to #to_ary and #to_enum" do
+ -> { [1, 2, 3].zip(Object.new) }.should raise_error(TypeError, "wrong argument type Object (must respond to :each)")
+ -> { [1, 2, 3].zip(1) }.should raise_error(TypeError, "wrong argument type Integer (must respond to :each)")
+ -> { [1, 2, 3].zip(true) }.should raise_error(TypeError, "wrong argument type TrueClass (must respond to :each)")
+ end
+end
diff --git a/spec/ruby/core/basicobject/__id__spec.rb b/spec/ruby/core/basicobject/__id__spec.rb
new file mode 100644
index 0000000000..6766db4e82
--- /dev/null
+++ b/spec/ruby/core/basicobject/__id__spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/object_id'
+
+describe "BasicObject#__id__" do
+ it_behaves_like :object_id, :__id__, BasicObject
+end
diff --git a/spec/ruby/core/basicobject/__send___spec.rb b/spec/ruby/core/basicobject/__send___spec.rb
new file mode 100644
index 0000000000..005b1d0d90
--- /dev/null
+++ b/spec/ruby/core/basicobject/__send___spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/basicobject/send'
+
+describe "BasicObject#__send__" do
+ it "is a public instance method" do
+ BasicObject.should have_public_instance_method(:__send__)
+ end
+
+ it_behaves_like :basicobject_send, :__send__
+end
diff --git a/spec/ruby/core/basicobject/basicobject_spec.rb b/spec/ruby/core/basicobject/basicobject_spec.rb
new file mode 100644
index 0000000000..27a322e72c
--- /dev/null
+++ b/spec/ruby/core/basicobject/basicobject_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "BasicObject" do
+ it "raises NoMethodError for nonexistent methods after #method_missing is removed" do
+ script = fixture __FILE__, "remove_method_missing.rb"
+ ruby_exe(script).chomp.should == "NoMethodError"
+ end
+
+ it "raises NameError when referencing built-in constants" do
+ -> { class BasicObjectSpecs::BOSubclass; Kernel; end }.should raise_error(NameError)
+ end
+
+ it "does not define built-in constants (according to const_defined?)" do
+ BasicObject.const_defined?(:Kernel).should be_false
+ end
+
+ it "does not define built-in constants (according to defined?)" do
+ BasicObjectSpecs::BOSubclass.kernel_defined?.should be_nil
+ end
+
+ it "is included in Object's list of constants" do
+ Object.constants(false).should include(:BasicObject)
+ end
+
+ it "includes itself in its list of constants" do
+ BasicObject.constants(false).should include(:BasicObject)
+ end
+end
+
+describe "BasicObject metaclass" do
+ before :each do
+ @meta = class << BasicObject; self; end
+ end
+
+ it "is an instance of Class" do
+ @meta.should be_an_instance_of(Class)
+ end
+
+ it "has Class as superclass" do
+ @meta.superclass.should equal(Class)
+ end
+
+ it "contains methods for the BasicObject class" do
+ @meta.class_eval do
+ def rubyspec_test_method() :test end
+ end
+
+ BasicObject.rubyspec_test_method.should == :test
+ end
+end
+
+describe "BasicObject instance metaclass" do
+ before :each do
+ @object = BasicObject.new
+ @meta = class << @object; self; end
+ end
+
+ it "is an instance of Class" do
+ @meta.should be_an_instance_of(Class)
+ end
+
+ it "has BasicObject as superclass" do
+ @meta.superclass.should equal(BasicObject)
+ end
+
+ it "contains methods defined for the BasicObject instance" do
+ @meta.class_eval do
+ def test_method() :test end
+ end
+
+ @object.test_method.should == :test
+ end
+end
+
+describe "BasicObject subclass" do
+ it "contains Kernel methods when including Kernel" do
+ obj = BasicObjectSpecs::BOSubclass.new
+
+ obj.instance_variable_set(:@test, :value)
+ obj.instance_variable_get(:@test).should == :value
+
+ obj.respond_to?(:hash).should == true
+ end
+
+ describe "BasicObject references" do
+ it "can refer to BasicObject from within itself" do
+ -> { BasicObject::BasicObject }.should_not raise_error
+ end
+ end
+end
diff --git a/spec/ruby/core/basicobject/equal_spec.rb b/spec/ruby/core/basicobject/equal_spec.rb
new file mode 100644
index 0000000000..3c1ad56d4a
--- /dev/null
+++ b/spec/ruby/core/basicobject/equal_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/equal'
+
+describe "BasicObject#equal?" do
+ it "is a public instance method" do
+ BasicObject.should have_public_instance_method(:equal?)
+ end
+
+ it_behaves_like :object_equal, :equal?
+
+ it "is unaffected by overriding __id__" do
+ o1 = mock("object")
+ o2 = mock("object")
+ def o1.__id__; 10; end
+ def o2.__id__; 10; end
+ o1.equal?(o2).should be_false
+ end
+
+ it "is unaffected by overriding object_id" do
+ o1 = mock("object")
+ o1.stub!(:object_id).and_return(10)
+ o2 = mock("object")
+ o2.stub!(:object_id).and_return(10)
+ o1.equal?(o2).should be_false
+ end
+
+ it "is unaffected by overriding ==" do
+ # different objects, overriding == to return true
+ o1 = mock("object")
+ o1.stub!(:==).and_return(true)
+ o2 = mock("object")
+ o1.equal?(o2).should be_false
+
+ # same objects, overriding == to return false
+ o3 = mock("object")
+ o3.stub!(:==).and_return(false)
+ o3.equal?(o3).should be_true
+ end
+
+ it "is unaffected by overriding eql?" do
+ # different objects, overriding eql? to return true
+ o1 = mock("object")
+ o1.stub!(:eql?).and_return(true)
+ o2 = mock("object")
+ o1.equal?(o2).should be_false
+
+ # same objects, overriding eql? to return false
+ o3 = mock("object")
+ o3.stub!(:eql?).and_return(false)
+ o3.equal?(o3).should be_true
+ end
+end
diff --git a/spec/ruby/core/basicobject/equal_value_spec.rb b/spec/ruby/core/basicobject/equal_value_spec.rb
new file mode 100644
index 0000000000..6c825513c1
--- /dev/null
+++ b/spec/ruby/core/basicobject/equal_value_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/equal'
+
+describe "BasicObject#==" do
+ it "is a public instance method" do
+ BasicObject.should have_public_instance_method(:==)
+ end
+
+ it_behaves_like :object_equal, :==
+end
diff --git a/spec/ruby/core/basicobject/fixtures/classes.rb b/spec/ruby/core/basicobject/fixtures/classes.rb
new file mode 100644
index 0000000000..d1785afe31
--- /dev/null
+++ b/spec/ruby/core/basicobject/fixtures/classes.rb
@@ -0,0 +1,33 @@
+module BasicObjectSpecs
+ class IVars
+ def initialize
+ @secret = 99
+ end
+ end
+
+ module InstExec
+ def self.included(base)
+ base.instance_exec { @@count = 2 }
+ end
+ end
+
+ module InstExecIncluded
+ include InstExec
+ end
+
+ module InstEvalCVar
+ instance_eval { @@count = 2 }
+ end
+
+ class InstEvalConst
+ INST_EVAL_CONST_X = 2
+ end
+
+ module InstEvalOuter
+ module Inner
+ obj = InstEvalConst.new
+ X_BY_STR = obj.instance_eval("INST_EVAL_CONST_X") rescue nil
+ X_BY_BLOCK = obj.instance_eval { INST_EVAL_CONST_X } rescue nil
+ end
+ end
+end
diff --git a/spec/ruby/core/basicobject/fixtures/common.rb b/spec/ruby/core/basicobject/fixtures/common.rb
new file mode 100644
index 0000000000..3447a3a5e7
--- /dev/null
+++ b/spec/ruby/core/basicobject/fixtures/common.rb
@@ -0,0 +1,9 @@
+module BasicObjectSpecs
+ class BOSubclass < BasicObject
+ def self.kernel_defined?
+ defined?(Kernel)
+ end
+
+ include ::Kernel
+ end
+end
diff --git a/spec/ruby/core/basicobject/fixtures/remove_method_missing.rb b/spec/ruby/core/basicobject/fixtures/remove_method_missing.rb
new file mode 100644
index 0000000000..095b982d3a
--- /dev/null
+++ b/spec/ruby/core/basicobject/fixtures/remove_method_missing.rb
@@ -0,0 +1,9 @@
+class BasicObject
+ remove_method :method_missing
+end
+
+begin
+ Object.new.test_method
+rescue NoMethodError => e
+ puts e.class.name
+end
diff --git a/spec/ruby/core/basicobject/fixtures/singleton_method.rb b/spec/ruby/core/basicobject/fixtures/singleton_method.rb
new file mode 100644
index 0000000000..0e00e035fa
--- /dev/null
+++ b/spec/ruby/core/basicobject/fixtures/singleton_method.rb
@@ -0,0 +1,10 @@
+module BasicObjectSpecs
+ class SingletonMethod
+ def self.singleton_method_added name
+ ScratchPad.record [:singleton_method_added, name]
+ end
+
+ def self.singleton_method_to_alias
+ end
+ end
+end
diff --git a/spec/ruby/core/basicobject/initialize_spec.rb b/spec/ruby/core/basicobject/initialize_spec.rb
new file mode 100644
index 0000000000..b7ce73ffd5
--- /dev/null
+++ b/spec/ruby/core/basicobject/initialize_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "BasicObject#initialize" do
+ it "is a private instance method" do
+ BasicObject.should have_private_instance_method(:initialize)
+ end
+
+ it "does not accept arguments" do
+ -> {
+ BasicObject.new("This", "makes it easier", "to call super", "from other constructors")
+ }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/basicobject/instance_eval_spec.rb b/spec/ruby/core/basicobject/instance_eval_spec.rb
new file mode 100644
index 0000000000..350b08a30e
--- /dev/null
+++ b/spec/ruby/core/basicobject/instance_eval_spec.rb
@@ -0,0 +1,248 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "BasicObject#instance_eval" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is a public instance method" do
+ BasicObject.should have_public_instance_method(:instance_eval)
+ end
+
+ it "sets self to the receiver in the context of the passed block" do
+ a = BasicObject.new
+ a.instance_eval { self }.equal?(a).should be_true
+ end
+
+ it "evaluates strings" do
+ a = BasicObject.new
+ a.instance_eval('self').equal?(a).should be_true
+ end
+
+ it "raises an ArgumentError when no arguments and no block are given" do
+ -> { "hola".instance_eval }.should raise_error(ArgumentError, "wrong number of arguments (given 0, expected 1..3)")
+ end
+
+ it "raises an ArgumentError when a block and normal arguments are given" do
+ -> { "hola".instance_eval(4, 5) {|a,b| a + b } }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 0)")
+ end
+
+ it "raises an ArgumentError when more than 3 arguments are given" do
+ -> {
+ "hola".instance_eval("1 + 1", "some file", 0, "bogus")
+ }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 1..3)")
+ end
+
+ it "yields the object to the block" do
+ "hola".instance_eval {|o| ScratchPad.record o }
+ ScratchPad.recorded.should == "hola"
+ end
+
+ it "returns the result of the block" do
+ "hola".instance_eval { :result }.should == :result
+ end
+
+ it "only binds the eval to the receiver" do
+ f = Object.new
+ f.instance_eval do
+ def foo
+ 1
+ end
+ end
+ f.foo.should == 1
+ -> { Object.new.foo }.should raise_error(NoMethodError)
+ end
+
+ it "preserves self in the original block when passed a block argument" do
+ prc = proc { self }
+
+ old_self = prc.call
+
+ new_self = Object.new
+ new_self.instance_eval(&prc).should == new_self
+
+ prc.call.should == old_self
+ end
+
+ # TODO: This should probably be replaced with a "should behave like" that uses
+ # the many scoping/binding specs from kernel/eval_spec, since most of those
+ # behaviors are the same for instance_eval. See also module_eval/class_eval.
+
+ it "binds self to the receiver" do
+ s = "hola"
+ (s == s.instance_eval { self }).should be_true
+ o = mock('o')
+ (o == o.instance_eval("self")).should be_true
+ end
+
+ it "executes in the context of the receiver" do
+ "Ruby-fu".instance_eval { size }.should == 7
+ "hola".instance_eval("size").should == 4
+ Object.class_eval { "hola".instance_eval("to_s") }.should == "hola"
+ Object.class_eval { "Ruby-fu".instance_eval{ to_s } }.should == "Ruby-fu"
+
+ end
+
+ it "has access to receiver's instance variables" do
+ BasicObjectSpecs::IVars.new.instance_eval { @secret }.should == 99
+ BasicObjectSpecs::IVars.new.instance_eval("@secret").should == 99
+ end
+
+ it "treats block-local variables as local to the block" do
+ prc = instance_eval <<-CODE
+ proc do |x, prc|
+ if x
+ n = 2
+ else
+ n = 1
+ prc.call(true, prc)
+ n
+ end
+ end
+ CODE
+
+ prc.call(false, prc).should == 1
+ end
+
+ it "sets class variables in the receiver" do
+ BasicObjectSpecs::InstEvalCVar.class_variables.should include(:@@count)
+ BasicObjectSpecs::InstEvalCVar.send(:class_variable_get, :@@count).should == 2
+ end
+
+ it "makes the receiver metaclass the scoped class when used with a string" do
+ obj = Object.new
+ obj.instance_eval %{
+ class B; end
+ B
+ }
+ obj.singleton_class.const_get(:B).should be_an_instance_of(Class)
+ end
+
+ it "gets constants in the receiver if a string given" do
+ BasicObjectSpecs::InstEvalOuter::Inner::X_BY_STR.should == 2
+ end
+
+ it "doesn't get constants in the receiver if a block given" do
+ BasicObjectSpecs::InstEvalOuter::Inner::X_BY_BLOCK.should be_nil
+ end
+
+ it "raises a TypeError when defining methods on an immediate" do
+ -> do
+ 1.instance_eval { def foo; end }
+ end.should raise_error(TypeError)
+ -> do
+ :foo.instance_eval { def foo; end }
+ end.should raise_error(TypeError)
+ end
+
+quarantine! do # Not clean, leaves cvars lying around to break other specs
+ it "scopes class var accesses in the caller when called on an Integer" do
+ # Integer can take instance vars
+ Integer.class_eval "@@__tmp_instance_eval_spec = 1"
+ (defined? @@__tmp_instance_eval_spec).should be_nil
+
+ @@__tmp_instance_eval_spec = 2
+ 1.instance_eval { @@__tmp_instance_eval_spec }.should == 2
+ Integer.__send__(:remove_class_variable, :@@__tmp_instance_eval_spec)
+ end
+end
+
+ it "raises a TypeError when defining methods on numerics" do
+ -> do
+ (1.0).instance_eval { def foo; end }
+ end.should raise_error(TypeError)
+ -> do
+ (1 << 64).instance_eval { def foo; end }
+ end.should raise_error(TypeError)
+ end
+
+ it "evaluates procs originating from methods" do
+ def meth(arg); arg; end
+
+ m = method(:meth)
+ obj = Object.new
+
+ obj.instance_eval(&m).should == obj
+ end
+
+ it "evaluates string with given filename and linenumber" do
+ err = begin
+ Object.new.instance_eval("raise", "a_file", 10)
+ rescue => e
+ e
+ end
+ err.backtrace.first.split(":")[0..1].should == ["a_file", "10"]
+ end
+
+ it "evaluates string with given filename and negative linenumber" do
+ err = begin
+ Object.new.instance_eval("\n\nraise\n", "b_file", -100)
+ rescue => e
+ e
+ end
+ err.backtrace.first.split(":")[0..1].should == ["b_file", "-98"]
+ end
+
+ it "has access to the caller's local variables" do
+ x = nil
+
+ instance_eval "x = :value"
+
+ x.should == :value
+ end
+
+ it "converts string argument with #to_str method" do
+ source_code = Object.new
+ def source_code.to_str() "1" end
+
+ a = BasicObject.new
+ a.instance_eval(source_code).should == 1
+ end
+
+ it "raises ArgumentError if returned value is not String" do
+ source_code = Object.new
+ def source_code.to_str() :symbol end
+
+ a = BasicObject.new
+ -> { a.instance_eval(source_code) }.should raise_error(TypeError, /can't convert Object to String/)
+ end
+
+ it "converts filename argument with #to_str method" do
+ filename = Object.new
+ def filename.to_str() "file.rb" end
+
+ err = begin
+ Object.new.instance_eval("raise", filename)
+ rescue => e
+ e
+ end
+ err.backtrace.first.split(":")[0].should == "file.rb"
+ end
+
+ it "raises ArgumentError if returned value is not String" do
+ filename = Object.new
+ def filename.to_str() :symbol end
+
+ -> { Object.new.instance_eval("raise", filename) }.should raise_error(TypeError, /can't convert Object to String/)
+ end
+
+ it "converts lineno argument with #to_int method" do
+ lineno = Object.new
+ def lineno.to_int() 15 end
+
+ err = begin
+ Object.new.instance_eval("raise", "file.rb", lineno)
+ rescue => e
+ e
+ end
+ err.backtrace.first.split(":")[1].should == "15"
+ end
+
+ it "raises ArgumentError if returned value is not Integer" do
+ lineno = Object.new
+ def lineno.to_int() :symbol end
+
+ -> { Object.new.instance_eval("raise", "file.rb", lineno) }.should raise_error(TypeError, /can't convert Object to Integer/)
+ end
+end
diff --git a/spec/ruby/core/basicobject/instance_exec_spec.rb b/spec/ruby/core/basicobject/instance_exec_spec.rb
new file mode 100644
index 0000000000..289fdd889b
--- /dev/null
+++ b/spec/ruby/core/basicobject/instance_exec_spec.rb
@@ -0,0 +1,107 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "BasicObject#instance_exec" do
+ it "is a public instance method" do
+ BasicObject.should have_public_instance_method(:instance_exec)
+ end
+
+ it "sets self to the receiver in the context of the passed block" do
+ a = BasicObject.new
+ a.instance_exec { self }.equal?(a).should be_true
+ end
+
+ it "passes arguments to the block" do
+ a = BasicObject.new
+ a.instance_exec(1) { |b| b }.should equal(1)
+ end
+
+ it "raises a LocalJumpError unless given a block" do
+ -> { "hola".instance_exec }.should raise_error(LocalJumpError)
+ end
+
+ it "has an arity of -1" do
+ Object.new.method(:instance_exec).arity.should == -1
+ end
+
+ it "accepts arguments with a block" do
+ -> { "hola".instance_exec(4, 5) { |a,b| a + b } }.should_not raise_error
+ end
+
+ it "doesn't pass self to the block as an argument" do
+ "hola".instance_exec { |o| o }.should be_nil
+ end
+
+ it "passes any arguments to the block" do
+ Object.new.instance_exec(1,2) {|one, two| one + two}.should == 3
+ end
+
+ it "only binds the exec to the receiver" do
+ f = Object.new
+ f.instance_exec do
+ def foo
+ 1
+ end
+ end
+ f.foo.should == 1
+ -> { Object.new.foo }.should raise_error(NoMethodError)
+ end
+
+ # TODO: This should probably be replaced with a "should behave like" that uses
+ # the many scoping/binding specs from kernel/eval_spec, since most of those
+ # behaviors are the same for instance_exec. See also module_eval/class_eval.
+
+ it "binds self to the receiver" do
+ s = "hola"
+ (s == s.instance_exec { self }).should == true
+ end
+
+ it "binds the block's binding self to the receiver" do
+ s = "hola"
+ (s == s.instance_exec { eval "self", binding }).should == true
+ end
+
+ it "executes in the context of the receiver" do
+ "Ruby-fu".instance_exec { size }.should == 7
+ Object.class_eval { "Ruby-fu".instance_exec{ to_s } }.should == "Ruby-fu"
+ end
+
+ it "has access to receiver's instance variables" do
+ BasicObjectSpecs::IVars.new.instance_exec { @secret }.should == 99
+ end
+
+ it "sets class variables in the receiver" do
+ BasicObjectSpecs::InstExec.class_variables.should include(:@@count)
+ BasicObjectSpecs::InstExec.send(:class_variable_get, :@@count).should == 2
+ end
+
+ it "raises a TypeError when defining methods on an immediate" do
+ -> do
+ 1.instance_exec { def foo; end }
+ end.should raise_error(TypeError)
+ -> do
+ :foo.instance_exec { def foo; end }
+ end.should raise_error(TypeError)
+ end
+
+quarantine! do # Not clean, leaves cvars lying around to break other specs
+ it "scopes class var accesses in the caller when called on an Integer" do
+ # Integer can take instance vars
+ Integer.class_eval "@@__tmp_instance_exec_spec = 1"
+ (defined? @@__tmp_instance_exec_spec).should == nil
+
+ @@__tmp_instance_exec_spec = 2
+ 1.instance_exec { @@__tmp_instance_exec_spec }.should == 2
+ Integer.__send__(:remove_class_variable, :@@__tmp_instance_exec_spec)
+ end
+end
+
+ it "raises a TypeError when defining methods on numerics" do
+ -> do
+ (1.0).instance_exec { def foo; end }
+ end.should raise_error(TypeError)
+ -> do
+ (1 << 64).instance_exec { def foo; end }
+ end.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/basicobject/method_missing_spec.rb b/spec/ruby/core/basicobject/method_missing_spec.rb
new file mode 100644
index 0000000000..b048780ee8
--- /dev/null
+++ b/spec/ruby/core/basicobject/method_missing_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../shared/basicobject/method_missing'
+
+describe "BasicObject#method_missing" do
+ it "is a private method" do
+ BasicObject.should have_private_instance_method(:method_missing)
+ end
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_class, nil, BasicObject
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_instance, nil, BasicObject
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_defined_module, nil, KernelSpecs::ModuleMM
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_module, nil, KernelSpecs::ModuleNoMM
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_defined_class, nil, KernelSpecs::ClassMM
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_class, nil, KernelSpecs::ClassNoMM
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_defined_instance, nil, KernelSpecs::ClassMM
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_instance, nil, KernelSpecs::ClassNoMM
+end
diff --git a/spec/ruby/core/basicobject/not_equal_spec.rb b/spec/ruby/core/basicobject/not_equal_spec.rb
new file mode 100644
index 0000000000..9329128c43
--- /dev/null
+++ b/spec/ruby/core/basicobject/not_equal_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+
+describe "BasicObject#!=" do
+ it "is a public instance method" do
+ BasicObject.should have_public_instance_method(:'!=')
+ end
+
+ it "returns true if other is not identical to self" do
+ a = BasicObject.new
+ b = BasicObject.new
+ (a != b).should be_true
+ end
+
+ it "returns true if other is an Object" do
+ a = BasicObject.new
+ b = Object.new
+ (a != b).should be_true
+ end
+
+ it "returns false if other is identical to self" do
+ a = BasicObject.new
+ (a != a).should be_false
+ end
+
+ it "dispatches to #==" do
+ a = mock("not_equal")
+ b = BasicObject.new
+ a.should_receive(:==).and_return(true)
+
+ (a != b).should be_false
+ end
+
+ describe "when invoked using Kernel#send" do
+ it "returns true if other is not identical to self" do
+ a = Object.new
+ b = Object.new
+ a.send(:!=, b).should be_true
+ end
+
+ it "returns false if other is identical to self" do
+ a = Object.new
+ a.send(:!=, a).should be_false
+ end
+
+ it "dispatches to #==" do
+ a = mock("not_equal")
+ b = Object.new
+ a.should_receive(:==).and_return(true)
+
+ a.send(:!=, b).should be_false
+ end
+ end
+end
diff --git a/spec/ruby/core/basicobject/not_spec.rb b/spec/ruby/core/basicobject/not_spec.rb
new file mode 100644
index 0000000000..ca4cb6f5ff
--- /dev/null
+++ b/spec/ruby/core/basicobject/not_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "BasicObject#!" do
+ it "is a public instance method" do
+ BasicObject.should have_public_instance_method(:'!')
+ end
+
+ it "returns false" do
+ (!BasicObject.new).should be_false
+ end
+end
diff --git a/spec/ruby/core/basicobject/singleton_method_added_spec.rb b/spec/ruby/core/basicobject/singleton_method_added_spec.rb
new file mode 100644
index 0000000000..ab6b2a2d10
--- /dev/null
+++ b/spec/ruby/core/basicobject/singleton_method_added_spec.rb
@@ -0,0 +1,145 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/singleton_method'
+
+describe "BasicObject#singleton_method_added" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is a private method" do
+ BasicObject.should have_private_instance_method(:singleton_method_added)
+ end
+
+ it "is called when a singleton method is defined on an object" do
+ obj = BasicObject.new
+
+ def obj.singleton_method_added(name)
+ ScratchPad.record [:singleton_method_added, name]
+ end
+
+ def obj.new_singleton_method
+ end
+
+ ScratchPad.recorded.should == [:singleton_method_added, :new_singleton_method]
+ end
+
+ it "is not called for instance methods" do
+ ScratchPad.record []
+
+ Module.new do
+ def self.singleton_method_added(name)
+ ScratchPad << name
+ end
+
+ def new_instance_method
+ end
+ end
+
+ ScratchPad.recorded.should_not include(:new_instance_method)
+ end
+
+ it "is called when a singleton method is defined on a module" do
+ class BasicObjectSpecs::SingletonMethod
+ def self.new_method_on_self
+ end
+ end
+ ScratchPad.recorded.should == [:singleton_method_added, :new_method_on_self]
+ end
+
+ it "is called when a method is defined in the singleton class" do
+ class BasicObjectSpecs::SingletonMethod
+ class << self
+ def new_method_on_singleton
+ end
+ end
+ end
+ ScratchPad.recorded.should == [:singleton_method_added, :new_method_on_singleton]
+ end
+
+ it "is called when a method is defined with alias_method in the singleton class" do
+ class BasicObjectSpecs::SingletonMethod
+ class << self
+ alias_method :new_method_on_singleton_with_alias_method, :singleton_method_to_alias
+ end
+ end
+ ScratchPad.recorded.should == [:singleton_method_added, :new_method_on_singleton_with_alias_method]
+ end
+
+ it "is called when a method is defined with syntax alias in the singleton class" do
+ class BasicObjectSpecs::SingletonMethod
+ class << self
+ alias new_method_on_singleton_with_syntax_alias singleton_method_to_alias
+ end
+ end
+ ScratchPad.recorded.should == [:singleton_method_added, :new_method_on_singleton_with_syntax_alias]
+ end
+
+ it "is called when define_method is used in the singleton class" do
+ class BasicObjectSpecs::SingletonMethod
+ class << self
+ define_method :new_method_with_define_method do
+ end
+ end
+ end
+ ScratchPad.recorded.should == [:singleton_method_added, :new_method_with_define_method]
+ end
+
+ describe "when singleton_method_added is undefined" do
+ it "raises NoMethodError for a metaclass" do
+ class BasicObjectSpecs::NoSingletonMethodAdded
+ class << self
+ undef_method :singleton_method_added
+ end
+
+ -> {
+ def self.foo
+ end
+ }.should raise_error(NoMethodError, /undefined method `singleton_method_added' for/)
+ end
+ end
+
+ it "raises NoMethodError for a singleton instance" do
+ object = Object.new
+ class << object
+ undef_method :singleton_method_added
+
+ -> {
+ def foo
+ end
+ }.should raise_error(NoMethodError, /undefined method `singleton_method_added' for #<Object:/)
+
+ -> {
+ define_method(:bar) {}
+ }.should raise_error(NoMethodError, /undefined method `singleton_method_added' for #<Object:/)
+ end
+
+ -> {
+ object.define_singleton_method(:baz) {}
+ }.should raise_error(NoMethodError, /undefined method `singleton_method_added' for #<Object:/)
+ end
+
+ it "calls #method_missing" do
+ ScratchPad.record []
+ object = Object.new
+ class << object
+ def method_missing(*args)
+ ScratchPad << args
+ end
+
+ undef_method :singleton_method_added
+
+ def foo
+ end
+
+ define_method(:bar) {}
+ end
+ object.define_singleton_method(:baz) {}
+
+ ScratchPad.recorded.should == [
+ [:singleton_method_added, :foo],
+ [:singleton_method_added, :bar],
+ [:singleton_method_added, :baz],
+ ]
+ end
+ end
+end
diff --git a/spec/ruby/core/basicobject/singleton_method_removed_spec.rb b/spec/ruby/core/basicobject/singleton_method_removed_spec.rb
new file mode 100644
index 0000000000..46f9a6894c
--- /dev/null
+++ b/spec/ruby/core/basicobject/singleton_method_removed_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "BasicObject#singleton_method_removed" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is a private method" do
+ BasicObject.should have_private_instance_method(:singleton_method_removed)
+ end
+
+ it "is called when a method is removed on self" do
+ klass = Class.new
+ def klass.singleton_method_removed(name)
+ ScratchPad.record [:singleton_method_removed, name]
+ end
+ def klass.singleton_method_to_remove
+ end
+ class << klass
+ remove_method :singleton_method_to_remove
+ end
+ ScratchPad.recorded.should == [:singleton_method_removed, :singleton_method_to_remove]
+ end
+end
diff --git a/spec/ruby/core/basicobject/singleton_method_undefined_spec.rb b/spec/ruby/core/basicobject/singleton_method_undefined_spec.rb
new file mode 100644
index 0000000000..7d6c7207db
--- /dev/null
+++ b/spec/ruby/core/basicobject/singleton_method_undefined_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "BasicObject#singleton_method_undefined" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is a private method" do
+ BasicObject.should have_private_instance_method(:singleton_method_undefined)
+ end
+
+ it "is called when a method is removed on self" do
+ klass = Class.new
+ def klass.singleton_method_undefined(name)
+ ScratchPad.record [:singleton_method_undefined, name]
+ end
+ def klass.singleton_method_to_undefine
+ end
+ class << klass
+ undef_method :singleton_method_to_undefine
+ end
+ ScratchPad.recorded.should == [:singleton_method_undefined, :singleton_method_to_undefine]
+ end
+end
diff --git a/spec/ruby/core/binding/clone_spec.rb b/spec/ruby/core/binding/clone_spec.rb
new file mode 100644
index 0000000000..ebd40f5377
--- /dev/null
+++ b/spec/ruby/core/binding/clone_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/clone'
+
+describe "Binding#clone" do
+ it_behaves_like :binding_clone, :clone
+end
diff --git a/spec/ruby/core/binding/dup_spec.rb b/spec/ruby/core/binding/dup_spec.rb
new file mode 100644
index 0000000000..43968213c8
--- /dev/null
+++ b/spec/ruby/core/binding/dup_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/clone'
+
+describe "Binding#dup" do
+ it_behaves_like :binding_clone, :dup
+end
diff --git a/spec/ruby/core/binding/eval_spec.rb b/spec/ruby/core/binding/eval_spec.rb
new file mode 100644
index 0000000000..4bb3da7a6c
--- /dev/null
+++ b/spec/ruby/core/binding/eval_spec.rb
@@ -0,0 +1,152 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Binding#eval" do
+ it "behaves like Kernel.eval(..., self)" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+
+ bind.eval("@secret += square(3)").should == 10
+ bind.eval("a").should be_true
+
+ bind.eval("class Inside; end")
+ bind.eval("Inside.name").should == "BindingSpecs::Demo::Inside"
+ end
+
+ it "does not leak variables to cloned bindings" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_empty_binding
+ bind2 = bind.dup
+
+ bind.eval("x = 72")
+ bind.local_variables.should == [:x]
+ bind2.local_variables.should == []
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "inherits __LINE__ from the enclosing scope" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ suppress_warning {bind.eval("__LINE__")}.should == obj.get_line_of_binding
+ end
+
+ it "preserves __LINE__ across multiple calls to eval" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ suppress_warning {bind.eval("__LINE__")}.should == obj.get_line_of_binding
+ suppress_warning {bind.eval("__LINE__")}.should == obj.get_line_of_binding
+ end
+
+ it "increments __LINE__ on each line of a multiline eval" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ suppress_warning {bind.eval("#foo\n__LINE__")}.should == obj.get_line_of_binding + 1
+ end
+
+ it "inherits __LINE__ from the enclosing scope even if the Binding is created with #send" do
+ obj = BindingSpecs::Demo.new(1)
+ bind, line = obj.get_binding_with_send_and_line
+ suppress_warning {bind.eval("__LINE__")}.should == line
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "starts with line 1 if single argument is given" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ bind.eval("__LINE__").should == 1
+ end
+
+ it "preserves __LINE__ across multiple calls to eval" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ bind.eval("__LINE__").should == 1
+ bind.eval("__LINE__").should == 1
+ end
+
+ it "increments __LINE__ on each line of a multiline eval" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ bind.eval("#foo\n__LINE__").should == 2
+ end
+
+ it "starts with line 1 if the Binding is created with #send" do
+ obj = BindingSpecs::Demo.new(1)
+ bind, line = obj.get_binding_with_send_and_line
+ bind.eval("__LINE__").should == 1
+ end
+ end
+
+ it "starts with a __LINE__ of 1 if a filename is passed" do
+ bind = BindingSpecs::Demo.new(1).get_binding
+ bind.eval("__LINE__", "(test)").should == 1
+ bind.eval("#foo\n__LINE__", "(test)").should == 2
+ end
+
+ it "starts with a __LINE__ from the third argument if passed" do
+ bind = BindingSpecs::Demo.new(1).get_binding
+ bind.eval("__LINE__", "(test)", 88).should == 88
+ bind.eval("#foo\n__LINE__", "(test)", 88).should == 89
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "inherits __FILE__ from the enclosing scope" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ suppress_warning { bind.eval("__FILE__") }.should == obj.get_file_of_binding
+ end
+
+ it "inherits __LINE__ from the enclosing scope" do
+ obj = BindingSpecs::Demo.new(1)
+ bind, line = obj.get_binding_and_line
+ suppress_warning { bind.eval("__LINE__") }.should == line
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "uses (eval) as __FILE__ if single argument given" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ bind.eval("__FILE__").should == '(eval)'
+ end
+
+ it "uses 1 as __LINE__" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ suppress_warning { bind.eval("__LINE__") }.should == 1
+ end
+ end
+
+ it "uses the __FILE__ that is passed in" do
+ bind = BindingSpecs::Demo.new(1).get_binding
+ bind.eval("__FILE__", "(test)").should == "(test)"
+ end
+
+ describe "with a file given" do
+ it "does not store the filename permanently" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+
+ bind.eval("__FILE__", "test.rb").should == "test.rb"
+ suppress_warning {bind.eval("__FILE__")}.should_not == "test.rb"
+ end
+ end
+
+ it "with __method__ returns the method where the Binding was created" do
+ obj = BindingSpecs::Demo.new(1)
+ bind, meth = obj.get_binding_and_method
+ bind.eval("__method__").should == meth
+ end
+
+ it "with __method__ returns the method where the Binding was created, ignoring #send" do
+ obj = BindingSpecs::Demo.new(1)
+ bind, meth = obj.get_binding_with_send_and_method
+ bind.eval("__method__").should == meth
+ end
+
+ it "reflects refinements activated in the binding scope" do
+ bind = BindingSpecs::Refined.refined_binding
+
+ bind.eval("'bar'.foo").should == "foo"
+ end
+end
diff --git a/spec/ruby/core/binding/fixtures/classes.rb b/spec/ruby/core/binding/fixtures/classes.rb
new file mode 100644
index 0000000000..b5f3ce9008
--- /dev/null
+++ b/spec/ruby/core/binding/fixtures/classes.rb
@@ -0,0 +1,66 @@
+module BindingSpecs
+ class Demo
+ def initialize(n)
+ @secret = n
+ end
+
+ def square(n)
+ n * n
+ end
+
+ def get_binding_and_line
+ a = true
+ [binding, __LINE__]
+ end
+
+ def get_binding
+ get_binding_and_line[0]
+ end
+
+ def get_line_of_binding
+ get_binding_and_line[1]
+ end
+
+ def get_file_of_binding
+ __FILE__
+ end
+
+ def get_binding_with_send_and_line
+ [send(:binding), __LINE__]
+ end
+
+ def get_binding_and_method
+ [binding, :get_binding_and_method]
+ end
+
+ def get_binding_with_send_and_method
+ [send(:binding), :get_binding_with_send_and_method]
+ end
+
+ def get_empty_binding
+ binding
+ end
+
+ def get_binding_in_block
+ a = true
+ 1.times do
+ b = false
+ return binding
+ end
+ end
+ end
+
+ module AddFooToString
+ refine(String) do
+ def foo
+ "foo"
+ end
+ end
+ end
+ class Refined
+ using AddFooToString
+ def self.refined_binding
+ binding
+ end
+ end
+end
diff --git a/spec/ruby/core/binding/fixtures/irb.rb b/spec/ruby/core/binding/fixtures/irb.rb
new file mode 100644
index 0000000000..5f305f2d5d
--- /dev/null
+++ b/spec/ruby/core/binding/fixtures/irb.rb
@@ -0,0 +1,3 @@
+a = 10
+
+binding.irb
diff --git a/spec/ruby/core/binding/fixtures/irbrc b/spec/ruby/core/binding/fixtures/irbrc
new file mode 100644
index 0000000000..2bc12af2f7
--- /dev/null
+++ b/spec/ruby/core/binding/fixtures/irbrc
@@ -0,0 +1 @@
+# empty configuration
diff --git a/spec/ruby/core/binding/fixtures/location.rb b/spec/ruby/core/binding/fixtures/location.rb
new file mode 100644
index 0000000000..a78ae75731
--- /dev/null
+++ b/spec/ruby/core/binding/fixtures/location.rb
@@ -0,0 +1,6 @@
+module BindingSpecs
+ module LocationMethod
+ FILE_PATH = __FILE__
+ TEST_BINDING = binding
+ end
+end
diff --git a/spec/ruby/core/binding/irb_spec.rb b/spec/ruby/core/binding/irb_spec.rb
new file mode 100644
index 0000000000..b3bc274f78
--- /dev/null
+++ b/spec/ruby/core/binding/irb_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Binding#irb" do
+ it "creates an IRB session with the binding in scope" do
+ irb_fixture = fixture __FILE__, "irb.rb"
+ irbrc_fixture = fixture __FILE__, "irbrc"
+
+ out = IO.popen([{"IRBRC"=>irbrc_fixture}, *ruby_exe, irb_fixture], "r+") do |pipe|
+ pipe.puts "a ** 2"
+ pipe.puts "exit"
+ pipe.readlines.map(&:chomp)
+ end
+
+ out[-3..-1].should == ["a ** 2", "100", "exit"]
+ end
+end
diff --git a/spec/ruby/core/binding/local_variable_defined_spec.rb b/spec/ruby/core/binding/local_variable_defined_spec.rb
new file mode 100644
index 0000000000..2fc6504ee5
--- /dev/null
+++ b/spec/ruby/core/binding/local_variable_defined_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+
+describe 'Binding#local_variable_defined?' do
+ it 'returns false when a variable is not defined' do
+ binding.local_variable_defined?(:foo).should == false
+ end
+
+ it 'returns true when a regular local variable is defined' do
+ foo = 10
+ binding.local_variable_defined?(:foo).should == true
+ end
+
+ it 'returns true when a local variable is defined using eval()' do
+ bind = binding
+ bind.eval('foo = 10')
+
+ bind.local_variable_defined?(:foo).should == true
+ end
+
+ it 'returns true when a local variable is defined using Binding#local_variable_set' do
+ bind = binding
+ bind.local_variable_set(:foo, 10)
+
+ bind.local_variable_defined?(:foo).should == true
+ end
+
+ it 'returns true when a local variable is defined in a parent scope' do
+ foo = 10
+ -> {
+ binding.local_variable_defined?(:foo)
+ }.call.should == true
+ end
+
+ it 'allows usage of a String as the variable name' do
+ foo = 10
+ binding.local_variable_defined?('foo').should == true
+ end
+
+ it 'allows usage of an object responding to #to_str as the variable name' do
+ foo = 10
+ name = mock(:obj)
+ name.stub!(:to_str).and_return('foo')
+
+ binding.local_variable_defined?(name).should == true
+ end
+end
diff --git a/spec/ruby/core/binding/local_variable_get_spec.rb b/spec/ruby/core/binding/local_variable_get_spec.rb
new file mode 100644
index 0000000000..005670becc
--- /dev/null
+++ b/spec/ruby/core/binding/local_variable_get_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Binding#local_variable_get" do
+ it "reads local variables captured in the binding" do
+ a = 42
+ bind = binding
+ bind.local_variable_get(:a).should == 42
+ end
+
+ it "raises a NameError for missing variables" do
+ bind = BindingSpecs::Demo.new(1).get_empty_binding
+
+ -> {
+ bind.local_variable_get(:no_such_variable)
+ }.should raise_error(NameError)
+ end
+
+ it "reads variables added later to the binding" do
+ bind = BindingSpecs::Demo.new(1).get_empty_binding
+
+ -> {
+ bind.local_variable_get(:a)
+ }.should raise_error(NameError)
+
+ bind.local_variable_set(:a, 42)
+
+ bind.local_variable_get(:a).should == 42
+ end
+
+ it 'gets a local variable defined in a parent scope' do
+ number = 10
+
+ -> {
+ binding.local_variable_get(:number)
+ }.call.should == 10
+ end
+
+ it 'gets a local variable defined using eval()' do
+ bind = binding
+ bind.eval('number = 10')
+
+ bind.local_variable_get(:number).should == 10
+ end
+
+ it "raises a NameError on global access" do
+ bind = binding
+ -> { bind.local_variable_get(:$0) }.should raise_error(NameError)
+ end
+
+ it "raises a NameError on special variable access" do
+ bind = binding
+ -> { bind.local_variable_get(:$~) }.should raise_error(NameError)
+ -> { bind.local_variable_get(:$_) }.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/core/binding/local_variable_set_spec.rb b/spec/ruby/core/binding/local_variable_set_spec.rb
new file mode 100644
index 0000000000..1456c6dda1
--- /dev/null
+++ b/spec/ruby/core/binding/local_variable_set_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Binding#local_variable_set" do
+ it "adds nonexistent variables to the binding's eval scope" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_empty_binding
+ bind.eval('local_variables').should == []
+ bind.local_variable_set :foo, 1
+ bind.eval('local_variables').should == [:foo]
+ bind.eval('foo').should == 1
+ end
+
+ it 'sets a new local variable' do
+ bind = binding
+
+ bind.local_variable_set(:number, 10)
+ bind.local_variable_get(:number).should == 10
+ end
+
+ it 'sets a local variable using a String as the variable name' do
+ bind = binding
+
+ bind.local_variable_set('number', 10)
+ bind.local_variable_get('number').should == 10
+ end
+
+ it 'sets a local variable using an object responding to #to_str as the variable name' do
+ bind = binding
+ name = mock(:obj)
+ name.stub!(:to_str).and_return('number')
+
+ bind.local_variable_set(name, 10)
+ bind.local_variable_get(name).should == 10
+ end
+
+ it 'scopes new local variables to the receiving Binding' do
+ bind = binding
+ bind.local_variable_set(:number, 10)
+
+ -> { number }.should raise_error(NameError)
+ end
+
+ it 'overwrites an existing local variable defined before a Binding' do
+ number = 10
+ bind = binding
+
+ bind.local_variable_set(:number, 20)
+ number.should == 20
+ end
+
+ it 'overwrites a local variable defined using eval()' do
+ bind = binding
+ bind.eval('number = 10')
+
+ bind.local_variable_set(:number, 20)
+ bind.local_variable_get(:number).should == 20
+ end
+
+ it "raises a NameError on global access" do
+ bind = binding
+ -> { bind.local_variable_set(:$0, "") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError on special variable access" do
+ bind = binding
+ -> { bind.local_variable_set(:$~, "") }.should raise_error(NameError)
+ -> { bind.local_variable_set(:$_, "") }.should raise_error(NameError)
+ end
+
+end
diff --git a/spec/ruby/core/binding/local_variables_spec.rb b/spec/ruby/core/binding/local_variables_spec.rb
new file mode 100644
index 0000000000..92c817b9a8
--- /dev/null
+++ b/spec/ruby/core/binding/local_variables_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "Binding#local_variables" do
+ it "returns an Array" do
+ binding.local_variables.should be_kind_of(Array)
+ end
+
+ it "includes local variables in the current scope" do
+ a = 1
+ b = nil
+ binding.local_variables.should == [:a, :b]
+ end
+
+ it "includes local variables defined after calling binding.local_variables" do
+ binding.local_variables.should == [:a, :b]
+ a = 1
+ b = 2
+ end
+
+ it "includes local variables of inherited scopes and eval'ed context" do
+ p = proc { |a| b = 1; eval("c = 2; binding.local_variables") }
+ p.call.should == [:c, :a, :b, :p]
+ end
+
+ it "includes shadowed local variables only once" do
+ a = 1
+ proc { |a| binding.local_variables }.call(2).should == [:a]
+ end
+
+ it "includes new variables defined in the binding" do
+ b = binding
+ b.local_variable_set :a, 42
+ b.local_variables.should == [:a, :b]
+ end
+end
diff --git a/spec/ruby/core/binding/receiver_spec.rb b/spec/ruby/core/binding/receiver_spec.rb
new file mode 100644
index 0000000000..4bf5e7a7bd
--- /dev/null
+++ b/spec/ruby/core/binding/receiver_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Binding#receiver" do
+ it "returns the object to which binding is bound" do
+ obj = BindingSpecs::Demo.new(1)
+ obj.get_binding.receiver.should == obj
+
+ binding.receiver.should == self
+ end
+end
diff --git a/spec/ruby/core/binding/shared/clone.rb b/spec/ruby/core/binding/shared/clone.rb
new file mode 100644
index 0000000000..0e934ac1b5
--- /dev/null
+++ b/spec/ruby/core/binding/shared/clone.rb
@@ -0,0 +1,34 @@
+describe :binding_clone, shared: true do
+ before :each do
+ @b1 = BindingSpecs::Demo.new(99).get_binding
+ @b2 = @b1.send(@method)
+ @b3 = BindingSpecs::Demo.new(99).get_binding_in_block
+ @b4 = @b3.send(@method)
+ end
+
+ it "returns a copy of the Binding object" do
+ [[@b1, @b2, "a"],
+ [@b3, @b4, "a", "b"]].each do |b1, b2, *vars|
+ b1.should_not == b2
+
+ eval("@secret", b1).should == eval("@secret", b2)
+ eval("square(2)", b1).should == eval("square(2)", b2)
+ eval("self.square(2)", b1).should == eval("self.square(2)", b2)
+ vars.each do |v|
+ eval("#{v}", b1).should == eval("#{v}", b2)
+ end
+ end
+ end
+
+ it "is a shallow copy of the Binding object" do
+ [[@b1, @b2, "a"],
+ [@b3, @b4, "a", "b"]].each do |b1, b2, *vars|
+ vars.each do |v|
+ eval("#{v} = false", b1)
+ eval("#{v}", b2).should == false
+ end
+ b1.local_variable_set(:x, 37)
+ b2.local_variable_defined?(:x).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/binding/source_location_spec.rb b/spec/ruby/core/binding/source_location_spec.rb
new file mode 100644
index 0000000000..d439c3e399
--- /dev/null
+++ b/spec/ruby/core/binding/source_location_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/location'
+
+describe "Binding#source_location" do
+ it "returns an [file, line] pair" do
+ b = BindingSpecs::LocationMethod::TEST_BINDING
+ b.source_location.should == [BindingSpecs::LocationMethod::FILE_PATH, 4]
+ end
+end
diff --git a/spec/ruby/core/builtin_constants/builtin_constants_spec.rb b/spec/ruby/core/builtin_constants/builtin_constants_spec.rb
new file mode 100644
index 0000000000..1960f5721f
--- /dev/null
+++ b/spec/ruby/core/builtin_constants/builtin_constants_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "RUBY_VERSION" do
+ it "is a String" do
+ RUBY_VERSION.should be_kind_of(String)
+ end
+end
+
+describe "RUBY_PATCHLEVEL" do
+ it "is an Integer" do
+ RUBY_PATCHLEVEL.should be_kind_of(Integer)
+ end
+end
+
+describe "RUBY_COPYRIGHT" do
+ it "is a String" do
+ RUBY_COPYRIGHT.should be_kind_of(String)
+ end
+end
+
+describe "RUBY_DESCRIPTION" do
+ it "is a String" do
+ RUBY_DESCRIPTION.should be_kind_of(String)
+ end
+end
+
+describe "RUBY_ENGINE" do
+ it "is a String" do
+ RUBY_ENGINE.should be_kind_of(String)
+ end
+end
+
+describe "RUBY_PLATFORM" do
+ it "is a String" do
+ RUBY_PLATFORM.should be_kind_of(String)
+ end
+end
+
+describe "RUBY_RELEASE_DATE" do
+ it "is a String" do
+ RUBY_RELEASE_DATE.should be_kind_of(String)
+ end
+end
+
+describe "RUBY_REVISION" do
+ it "is a String" do
+ RUBY_REVISION.should be_kind_of(String)
+ end
+end
diff --git a/spec/ruby/core/class/allocate_spec.rb b/spec/ruby/core/class/allocate_spec.rb
new file mode 100644
index 0000000000..b39622e06a
--- /dev/null
+++ b/spec/ruby/core/class/allocate_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Class#allocate" do
+ it "returns an instance of self" do
+ klass = Class.new
+ klass.allocate.should be_an_instance_of(klass)
+ end
+
+ it "returns a fully-formed instance of Module" do
+ klass = Class.allocate
+ klass.constants.should_not == nil
+ klass.methods.should_not == nil
+ end
+
+ it "throws an exception when calling a method on a new instance" do
+ klass = Class.allocate
+ -> do
+ klass.new
+ end.should raise_error(Exception)
+ end
+
+ it "does not call initialize on the new instance" do
+ klass = Class.new do
+ def initialize(*args)
+ @initialized = true
+ end
+
+ def initialized?
+ @initialized || false
+ end
+ end
+
+ klass.allocate.should_not.initialized?
+ end
+
+ it "raises TypeError for #superclass" do
+ -> do
+ Class.allocate.superclass
+ end.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/class/attached_object_spec.rb b/spec/ruby/core/class/attached_object_spec.rb
new file mode 100644
index 0000000000..115d5fa563
--- /dev/null
+++ b/spec/ruby/core/class/attached_object_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+ruby_version_is '3.2' do
+ describe "Class#attached_object" do
+ it "returns the object that is attached to a singleton class" do
+ a = Class.new
+
+ a_obj = a.new
+ a_obj.singleton_class.attached_object.should == a_obj
+ end
+
+ it "returns the class object that is attached to a class's singleton class" do
+ a = Class.new
+ singleton_class = (class << a; self; end)
+
+ singleton_class.attached_object.should == a
+ end
+
+ it "raises TypeError if the class is not a singleton class" do
+ a = Class.new
+
+ -> { a.attached_object }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError for special singleton classes" do
+ -> { nil.singleton_class.attached_object }.should raise_error(TypeError)
+ -> { true.singleton_class.attached_object }.should raise_error(TypeError)
+ -> { false.singleton_class.attached_object }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/class/dup_spec.rb b/spec/ruby/core/class/dup_spec.rb
new file mode 100644
index 0000000000..701fd72e19
--- /dev/null
+++ b/spec/ruby/core/class/dup_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# NOTE: This is actually implemented by Module#initialize_copy
+describe "Class#dup" do
+ it "duplicates both the class and the singleton class" do
+ klass = Class.new do
+ def hello
+ "hello"
+ end
+
+ def self.message
+ "text"
+ end
+ end
+
+ klass_dup = klass.dup
+
+ klass_dup.new.hello.should == "hello"
+ klass_dup.message.should == "text"
+ end
+
+ it "retains an included module in the ancestor chain for the singleton class" do
+ klass = Class.new
+ mod = Module.new do
+ def hello
+ "hello"
+ end
+ end
+
+ klass.extend(mod)
+ klass_dup = klass.dup
+ klass_dup.hello.should == "hello"
+ end
+
+ it "retains the correct ancestor chain for the singleton class" do
+ super_klass = Class.new do
+ def hello
+ "hello"
+ end
+
+ def self.message
+ "text"
+ end
+ end
+
+ klass = Class.new(super_klass)
+ klass_dup = klass.dup
+
+ klass_dup.new.hello.should == "hello"
+ klass_dup.message.should == "text"
+ end
+
+ it "sets the name from the class to nil if not assigned to a constant" do
+ copy = CoreClassSpecs::Record.dup
+ copy.name.should be_nil
+ end
+
+ it "stores the new name if assigned to a constant" do
+ CoreClassSpecs::RecordCopy = CoreClassSpecs::Record.dup
+ CoreClassSpecs::RecordCopy.name.should == "CoreClassSpecs::RecordCopy"
+ end
+
+end
diff --git a/spec/ruby/core/class/fixtures/classes.rb b/spec/ruby/core/class/fixtures/classes.rb
new file mode 100644
index 0000000000..f96db90795
--- /dev/null
+++ b/spec/ruby/core/class/fixtures/classes.rb
@@ -0,0 +1,47 @@
+module CoreClassSpecs
+ class Record
+ end
+
+ module M
+ def inherited(klass)
+ ScratchPad.record klass
+ super
+ end
+ end
+
+ class F; end
+ class << F
+ include M
+ end
+
+ class A
+ def self.inherited(klass)
+ ScratchPad.record klass
+ end
+ end
+
+ class H < A
+ def self.inherited(klass)
+ super
+ end
+ end
+
+ module Inherited
+ class A
+ SUBCLASSES = []
+ def self.inherited(subclass)
+ SUBCLASSES << [self, subclass]
+ end
+ end
+
+ class B < A; end
+ class B < A; end # reopen
+ class C < B; end
+
+ class D
+ def self.inherited(subclass)
+ ScratchPad << self
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/class/inherited_spec.rb b/spec/ruby/core/class/inherited_spec.rb
new file mode 100644
index 0000000000..8ef8bb8c35
--- /dev/null
+++ b/spec/ruby/core/class/inherited_spec.rb
@@ -0,0 +1,101 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Class.inherited" do
+
+ before :each do
+ ScratchPad.record nil
+ end
+
+ it "is invoked with the child Class when self is subclassed" do
+ begin
+ top = Class.new do
+ def self.inherited(cls)
+ $child_class = cls
+ end
+ end
+
+ child = Class.new(top)
+ $child_class.should == child
+
+ other_child = Class.new(top)
+ $child_class.should == other_child
+ ensure
+ $child_class = nil
+ end
+ end
+
+ it "is invoked only once per subclass" do
+ expected = [
+ [CoreClassSpecs::Inherited::A, CoreClassSpecs::Inherited::B],
+ [CoreClassSpecs::Inherited::B, CoreClassSpecs::Inherited::C],
+ ]
+
+ CoreClassSpecs::Inherited::A::SUBCLASSES.should == expected
+ end
+
+ it "is called when marked as a private class method" do
+ a = Class.new do
+ def self.inherited(klass)
+ ScratchPad.record klass
+ end
+ end
+ a.private_class_method :inherited
+ ScratchPad.recorded.should == nil
+ b = Class.new(a)
+ ScratchPad.recorded.should == b
+ end
+
+ it "is called when marked as a protected class method" do
+ a = Class.new
+ class << a
+ def inherited(klass)
+ ScratchPad.record klass
+ end
+ protected :inherited
+ end
+ ScratchPad.recorded.should == nil
+ b = Class.new(a)
+ ScratchPad.recorded.should == b
+ end
+
+ it "is called when marked as a public class method" do
+ a = Class.new do
+ def self.inherited(klass)
+ ScratchPad.record klass
+ end
+ end
+ a.public_class_method :inherited
+ ScratchPad.recorded.should == nil
+ b = Class.new(a)
+ ScratchPad.recorded.should == b
+ end
+
+ it "is called by super from a method provided by an included module" do
+ ScratchPad.recorded.should == nil
+ e = Class.new(CoreClassSpecs::F)
+ ScratchPad.recorded.should == e
+ end
+
+ it "is called by super even when marked as a private class method" do
+ ScratchPad.recorded.should == nil
+ CoreClassSpecs::H.private_class_method :inherited
+ i = Class.new(CoreClassSpecs::H)
+ ScratchPad.recorded.should == i
+ end
+
+ it "will be invoked by child class regardless of visibility" do
+ top = Class.new do
+ class << self
+ def inherited(cls); end
+ end
+ end
+
+ class << top; private :inherited; end
+ -> { Class.new(top) }.should_not raise_error
+
+ class << top; protected :inherited; end
+ -> { Class.new(top) }.should_not raise_error
+ end
+
+end
diff --git a/spec/ruby/core/class/initialize_spec.rb b/spec/ruby/core/class/initialize_spec.rb
new file mode 100644
index 0000000000..6348758485
--- /dev/null
+++ b/spec/ruby/core/class/initialize_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "Class#initialize" do
+ it "is private" do
+ Class.should have_private_method(:initialize)
+ end
+
+ it "raises a TypeError when called on already initialized classes" do
+ ->{
+ Integer.send :initialize
+ }.should raise_error(TypeError)
+
+ ->{
+ Object.send :initialize
+ }.should raise_error(TypeError)
+ end
+
+ # See [redmine:2601]
+ it "raises a TypeError when called on BasicObject" do
+ ->{
+ BasicObject.send :initialize
+ }.should raise_error(TypeError)
+ end
+
+ describe "when given the Class" do
+ before :each do
+ @uninitialized = Class.allocate
+ end
+
+ it "raises a TypeError" do
+ ->{@uninitialized.send(:initialize, Class)}.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/class/new_spec.rb b/spec/ruby/core/class/new_spec.rb
new file mode 100644
index 0000000000..93152a83ee
--- /dev/null
+++ b/spec/ruby/core/class/new_spec.rb
@@ -0,0 +1,155 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Class.new with a block given" do
+ it "yields the new class as self in the block" do
+ self_in_block = nil
+ klass = Class.new do
+ self_in_block = self
+ end
+ self_in_block.should equal klass
+ end
+
+ it "uses the given block as the class' body" do
+ klass = Class.new do
+ def self.message
+ "text"
+ end
+
+ def hello
+ "hello again"
+ end
+ end
+
+ klass.message.should == "text"
+ klass.new.hello.should == "hello again"
+ end
+
+ it "creates a subclass of the given superclass" do
+ sc = Class.new do
+ def self.body
+ @body
+ end
+ @body = self
+ def message; "text"; end
+ end
+ klass = Class.new(sc) do
+ def self.body
+ @body
+ end
+ @body = self
+ def message2; "hello"; end
+ end
+
+ klass.body.should == klass
+ sc.body.should == sc
+ klass.superclass.should == sc
+ klass.new.message.should == "text"
+ klass.new.message2.should == "hello"
+ end
+
+ it "runs the inherited hook after yielding the block" do
+ ScratchPad.record []
+ klass = Class.new(CoreClassSpecs::Inherited::D) do
+ ScratchPad << self
+ end
+
+ ScratchPad.recorded.should == [CoreClassSpecs::Inherited::D, klass]
+ end
+end
+
+describe "Class.new" do
+ it "creates a new anonymous class" do
+ klass = Class.new
+ klass.is_a?(Class).should == true
+
+ klass_instance = klass.new
+ klass_instance.is_a?(klass).should == true
+ end
+
+ it "raises a TypeError if passed a metaclass" do
+ obj = mock("Class.new metaclass")
+ meta = obj.singleton_class
+ -> { Class.new meta }.should raise_error(TypeError)
+ end
+
+ it "creates a class without a name" do
+ Class.new.name.should be_nil
+ end
+
+ it "creates a class that can be given a name by assigning it to a constant" do
+ ::MyClass = Class.new
+ ::MyClass.name.should == "MyClass"
+ a = Class.new
+ MyClass::NestedClass = a
+ MyClass::NestedClass.name.should == "MyClass::NestedClass"
+ end
+
+ it "sets the new class' superclass to the given class" do
+ top = Class.new
+ Class.new(top).superclass.should == top
+ end
+
+ it "sets the new class' superclass to Object when no class given" do
+ Class.new.superclass.should == Object
+ end
+
+ it "raises a TypeError when given a non-Class" do
+ error_msg = /superclass must be a.*Class/
+ -> { Class.new("") }.should raise_error(TypeError, error_msg)
+ -> { Class.new(1) }.should raise_error(TypeError, error_msg)
+ -> { Class.new(:symbol) }.should raise_error(TypeError, error_msg)
+ -> { Class.new(mock('o')) }.should raise_error(TypeError, error_msg)
+ -> { Class.new(Module.new) }.should raise_error(TypeError, error_msg)
+ -> { Class.new(BasicObject.new) }.should raise_error(TypeError, error_msg)
+ end
+end
+
+describe "Class#new" do
+ it "returns a new instance of self" do
+ klass = Class.new
+ klass.new.is_a?(klass).should == true
+ end
+
+ it "invokes #initialize on the new instance with the given args" do
+ klass = Class.new do
+ def initialize(*args)
+ @initialized = true
+ @args = args
+ end
+
+ def args
+ @args
+ end
+
+ def initialized?
+ @initialized || false
+ end
+ end
+
+ klass.new.should.initialized?
+ klass.new(1, 2, 3).args.should == [1, 2, 3]
+ end
+
+ it "uses the internal allocator and does not call #allocate" do
+ klass = Class.new do
+ def self.allocate
+ raise "allocate should not be called"
+ end
+ end
+
+ instance = klass.new
+ instance.should be_kind_of klass
+ instance.class.should equal klass
+ end
+
+ it "passes the block to #initialize" do
+ klass = Class.new do
+ def initialize
+ yield
+ end
+ end
+
+ klass.new { break 42 }.should == 42
+ end
+end
diff --git a/spec/ruby/core/class/subclasses_spec.rb b/spec/ruby/core/class/subclasses_spec.rb
new file mode 100644
index 0000000000..a16b934d4f
--- /dev/null
+++ b/spec/ruby/core/class/subclasses_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+require_relative '../module/fixtures/classes'
+
+ruby_version_is '3.1' do
+ describe "Class#subclasses" do
+ it "returns a list of classes directly inheriting from self" do
+ assert_subclasses(ModuleSpecs::Parent, [ModuleSpecs::Child, ModuleSpecs::Child2])
+ end
+
+ it "does not return included modules" do
+ parent = Class.new
+ child = Class.new(parent)
+ mod = Module.new
+ parent.include(mod)
+
+ assert_subclasses(parent, [child])
+ end
+
+ it "does not return singleton classes" do
+ a = Class.new
+
+ a_obj = a.new
+ def a_obj.force_singleton_class
+ 42
+ end
+
+ a.subclasses.should_not include(a_obj.singleton_class)
+ end
+
+ it "has 1 entry per module or class" do
+ ModuleSpecs::Parent.subclasses.should == ModuleSpecs::Parent.subclasses.uniq
+ end
+
+ it "works when creating subclasses concurrently" do
+ t = 16
+ n = 1000
+ go = false
+ superclass = Class.new
+
+ threads = t.times.map do
+ Thread.new do
+ Thread.pass until go
+ n.times.map do
+ Class.new(superclass)
+ end
+ end
+ end
+
+ go = true
+ classes = threads.map(&:value)
+
+ superclass.subclasses.size.should == t * n
+ superclass.subclasses.each { |c| c.should be_kind_of(Class) }
+ end
+
+ def assert_subclasses(mod,subclasses)
+ mod.subclasses.sort_by(&:inspect).should == subclasses.sort_by(&:inspect)
+ end
+ end
+end
diff --git a/spec/ruby/core/class/superclass_spec.rb b/spec/ruby/core/class/superclass_spec.rb
new file mode 100644
index 0000000000..00579238a6
--- /dev/null
+++ b/spec/ruby/core/class/superclass_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Class#superclass" do
+ it "returns the superclass of self" do
+ BasicObject.superclass.should be_nil
+ Object.superclass.should == BasicObject
+ Class.superclass.should == Module
+ Class.new.superclass.should == Object
+ Class.new(String).superclass.should == String
+ Class.new(Integer).superclass.should == Integer
+ end
+
+ # redmine:567
+ describe "for a singleton class" do
+ it "of an object returns the class of the object" do
+ a = CoreClassSpecs::A.new
+ sc = class << a; self; end
+ sc.superclass.should == CoreClassSpecs::A
+ end
+
+ it "of a class returns the singleton class of its superclass" do # sorry, can't find a simpler way to express this...
+ sc = class << CoreClassSpecs::H; self; end
+ sc.superclass.should == class << CoreClassSpecs::A; self; end
+ end
+ end
+end
diff --git a/spec/ruby/core/comparable/between_spec.rb b/spec/ruby/core/comparable/between_spec.rb
new file mode 100644
index 0000000000..fd79bb9b4c
--- /dev/null
+++ b/spec/ruby/core/comparable/between_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Comparable#between?" do
+ it "returns true if self is greater than or equal to the first and less than or equal to the second argument" do
+ a = ComparableSpecs::Weird.new(-1)
+ b = ComparableSpecs::Weird.new(0)
+ c = ComparableSpecs::Weird.new(1)
+ d = ComparableSpecs::Weird.new(2)
+
+ a.between?(a, a).should == true
+ a.between?(a, b).should == true
+ a.between?(a, c).should == true
+ a.between?(a, d).should == true
+ c.between?(c, d).should == true
+ d.between?(d, d).should == true
+ c.between?(a, d).should == true
+
+ a.between?(b, b).should == false
+ a.between?(b, c).should == false
+ a.between?(b, d).should == false
+ c.between?(a, a).should == false
+ c.between?(a, b).should == false
+ end
+end
diff --git a/spec/ruby/core/comparable/clamp_spec.rb b/spec/ruby/core/comparable/clamp_spec.rb
new file mode 100644
index 0000000000..796d4a18c1
--- /dev/null
+++ b/spec/ruby/core/comparable/clamp_spec.rb
@@ -0,0 +1,78 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Comparable#clamp' do
+ it 'raises an Argument error unless the 2 parameters are correctly ordered' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ c = ComparableSpecs::Weird.new(3)
+
+ -> { c.clamp(two, one) }.should raise_error(ArgumentError)
+ one.should_receive(:<=>).any_number_of_times.and_return(nil)
+ -> { c.clamp(one, two) }.should raise_error(ArgumentError)
+ end
+
+ it 'returns self if within the given parameters' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ three = ComparableSpecs::WithOnlyCompareDefined.new(3)
+ c = ComparableSpecs::Weird.new(2)
+
+ c.clamp(one, two).should equal(c)
+ c.clamp(two, two).should equal(c)
+ c.clamp(one, three).should equal(c)
+ c.clamp(two, three).should equal(c)
+ end
+
+ it 'returns the min parameter if smaller than it' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ c = ComparableSpecs::Weird.new(0)
+
+ c.clamp(one, two).should equal(one)
+ end
+
+ it 'returns the max parameter if greater than it' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ c = ComparableSpecs::Weird.new(3)
+
+ c.clamp(one, two).should equal(two)
+ end
+
+ it 'returns self if within the given range parameters' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ three = ComparableSpecs::WithOnlyCompareDefined.new(3)
+ c = ComparableSpecs::Weird.new(2)
+
+ c.clamp(one..two).should equal(c)
+ c.clamp(two..two).should equal(c)
+ c.clamp(one..three).should equal(c)
+ c.clamp(two..three).should equal(c)
+ end
+
+ it 'returns the minimum value of the range parameters if smaller than it' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ c = ComparableSpecs::Weird.new(0)
+
+ c.clamp(one..two).should equal(one)
+ end
+
+ it 'returns the maximum value of the range parameters if greater than it' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ c = ComparableSpecs::Weird.new(3)
+
+ c.clamp(one..two).should equal(two)
+ end
+
+ it 'raises an Argument error if the range parameter is exclusive' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ c = ComparableSpecs::Weird.new(3)
+
+ -> { c.clamp(one...two) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/comparable/equal_value_spec.rb b/spec/ruby/core/comparable/equal_value_spec.rb
new file mode 100644
index 0000000000..ddcc03cb41
--- /dev/null
+++ b/spec/ruby/core/comparable/equal_value_spec.rb
@@ -0,0 +1,114 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Comparable#==" do
+ a = b = nil
+ before :each do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(10)
+ end
+
+ it "returns true if other is the same as self" do
+ (a == a).should == true
+ (b == b).should == true
+ end
+
+ it "calls #<=> on self with other and returns true if #<=> returns 0" do
+ a.should_receive(:<=>).once.and_return(0)
+ (a == b).should == true
+ end
+
+ it "calls #<=> on self with other and returns true if #<=> returns 0.0" do
+ a.should_receive(:<=>).once.and_return(0.0)
+ (a == b).should == true
+ end
+
+ it "returns false if calling #<=> on self returns a positive Integer" do
+ a.should_receive(:<=>).once.and_return(1)
+ (a == b).should == false
+ end
+
+ it "returns false if calling #<=> on self returns a negative Integer" do
+ a.should_receive(:<=>).once.and_return(-1)
+ (a == b).should == false
+ end
+
+ context "when #<=> returns nil" do
+ before :each do
+ a.should_receive(:<=>).once.and_return(nil)
+ end
+
+ it "returns false" do
+ (a == b).should be_false
+ end
+ end
+
+ context "when #<=> returns nor nil neither an Integer" do
+ before :each do
+ a.should_receive(:<=>).once.and_return("abc")
+ end
+
+ it "raises an ArgumentError" do
+ -> { (a == b) }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "when #<=> raises an exception" do
+ context "if it is a StandardError" do
+ before :each do
+ a.should_receive(:<=>).once.and_raise(StandardError)
+ end
+
+ it "lets it go through" do
+ -> { (a == b) }.should raise_error(StandardError)
+ end
+ end
+
+ context "if it is a subclass of StandardError" do
+ # TypeError < StandardError
+ before :each do
+ a.should_receive(:<=>).once.and_raise(TypeError)
+ end
+
+ it "lets it go through" do
+ -> { (a == b) }.should raise_error(TypeError)
+ end
+ end
+
+ it "lets it go through if it is not a StandardError" do
+ a.should_receive(:<=>).once.and_raise(Exception)
+ -> { (a == b) }.should raise_error(Exception)
+ end
+ end
+
+ context "when #<=> is not defined" do
+ before :each do
+ @a = ComparableSpecs::WithoutCompareDefined.new
+ @b = ComparableSpecs::WithoutCompareDefined.new
+ end
+
+ it "returns true for identical objects" do
+ @a.should == @a
+ end
+
+ it "returns false and does not recurse infinitely" do
+ @a.should_not == @b
+ end
+ end
+
+ context "when #<=> calls super" do
+ before :each do
+ @a = ComparableSpecs::CompareCallingSuper.new
+ @b = ComparableSpecs::CompareCallingSuper.new
+ end
+
+ it "returns true for identical objects" do
+ @a.should == @a
+ end
+
+ it "calls the defined #<=> only once for different objects" do
+ @a.should_not == @b
+ @a.calls.should == 1
+ end
+ end
+end
diff --git a/spec/ruby/core/comparable/fixtures/classes.rb b/spec/ruby/core/comparable/fixtures/classes.rb
new file mode 100644
index 0000000000..4239a47d2f
--- /dev/null
+++ b/spec/ruby/core/comparable/fixtures/classes.rb
@@ -0,0 +1,36 @@
+module ComparableSpecs
+ class WithOnlyCompareDefined
+ attr_reader :value
+
+ def initialize(value)
+ @value = value
+ end
+
+ def <=>(other)
+ self.value <=> other.value
+ end
+ end
+
+ class Weird < WithOnlyCompareDefined
+ include Comparable
+ end
+
+ class WithoutCompareDefined
+ include Comparable
+ end
+
+ class CompareCallingSuper
+ include Comparable
+
+ attr_reader :calls
+
+ def initialize
+ @calls = 0
+ end
+
+ def <=>(other)
+ @calls += 1
+ super(other)
+ end
+ end
+end
diff --git a/spec/ruby/core/comparable/gt_spec.rb b/spec/ruby/core/comparable/gt_spec.rb
new file mode 100644
index 0000000000..150e653dc7
--- /dev/null
+++ b/spec/ruby/core/comparable/gt_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Comparable#>" do
+ it "calls #<=> on self with other and returns true if #<=> returns any Integer greater than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(1)
+ (a > b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.1)
+ (a > b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(10000000)
+ (a > b).should == true
+ end
+
+ it "returns false if calling #<=> on self returns 0 or any Integer less than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(10)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0)
+ (a > b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.0)
+ (a > b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-1.0)
+ (a > b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-10000000)
+ (a > b).should == false
+ end
+
+ it "raises an ArgumentError if calling #<=> on self returns nil" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(nil)
+ -> { (a > b) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/comparable/gte_spec.rb b/spec/ruby/core/comparable/gte_spec.rb
new file mode 100644
index 0000000000..328f58c66c
--- /dev/null
+++ b/spec/ruby/core/comparable/gte_spec.rb
@@ -0,0 +1,47 @@
+require_relative 'fixtures/classes'
+require_relative '../../spec_helper'
+
+describe "Comparable#>=" do
+ it "calls #<=> on self with other and returns true if #<=> returns 0 or any Integer greater than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0)
+ (a >= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.0)
+ (a >= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(1)
+ (a >= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.1)
+ (a >= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(10000000)
+ (a >= b).should == true
+ end
+
+ it "returns false if calling #<=> on self returns any Integer less than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(10)
+
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-0.1)
+ (a >= b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-1.0)
+ (a >= b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-10000000)
+ (a >= b).should == false
+ end
+
+ it "raises an ArgumentError if calling #<=> on self returns nil" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(nil)
+ -> { (a >= b) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/comparable/lt_spec.rb b/spec/ruby/core/comparable/lt_spec.rb
new file mode 100644
index 0000000000..bca95f8d25
--- /dev/null
+++ b/spec/ruby/core/comparable/lt_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Comparable#<" do
+ it "calls #<=> on self with other and returns true if #<=> returns any Integer less than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-1)
+ (a < b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-0.1)
+ (a < b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-10000000)
+ (a < b).should == true
+ end
+
+ it "returns false if calling #<=> on self returns 0 or any Integer greater than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(10)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0)
+ (a < b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.0)
+ (a < b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(1.0)
+ (a < b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(10000000)
+ (a < b).should == false
+ end
+
+ it "raises an ArgumentError if calling #<=> on self returns nil" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(nil)
+ -> { (a < b) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an argument error with a message containing the value" do
+ -> { ("foo" < 7) }.should raise_error(ArgumentError) { |e|
+ e.message.should == "comparison of String with 7 failed"
+ }
+ end
+end
diff --git a/spec/ruby/core/comparable/lte_spec.rb b/spec/ruby/core/comparable/lte_spec.rb
new file mode 100644
index 0000000000..b5cb9cc4e7
--- /dev/null
+++ b/spec/ruby/core/comparable/lte_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Comparable#<=" do
+ it "calls #<=> on self with other and returns true if #<=> returns 0 or any Integer less than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0)
+ (a <= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.0)
+ (a <= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-1)
+ (a <= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-0.1)
+ (a <= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-10000000)
+ (a <= b).should == true
+ end
+
+ it "returns false if calling #<=> on self returns any Integer greater than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(10)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.1)
+ (a <= b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(1.0)
+ (a <= b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(10000000)
+ (a <= b).should == false
+ end
+
+ it "raises an ArgumentError if calling #<=> on self returns nil" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(nil)
+ -> { (a <= b) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/complex/abs2_spec.rb b/spec/ruby/core/complex/abs2_spec.rb
new file mode 100644
index 0000000000..3e5c5fd225
--- /dev/null
+++ b/spec/ruby/core/complex/abs2_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Complex#abs2" do
+ it "returns the sum of the squares of the real and imaginary parts" do
+ Complex(1, -2).abs2.should == 1 + 4
+ Complex(-0.1, 0.2).abs2.should be_close(0.01 + 0.04, TOLERANCE)
+ Complex(0).abs2.should == 0
+ end
+end
diff --git a/spec/ruby/core/complex/abs_spec.rb b/spec/ruby/core/complex/abs_spec.rb
new file mode 100644
index 0000000000..43912c517f
--- /dev/null
+++ b/spec/ruby/core/complex/abs_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/abs'
+
+describe "Complex#abs" do
+ it_behaves_like :complex_abs, :abs
+end
diff --git a/spec/ruby/core/complex/angle_spec.rb b/spec/ruby/core/complex/angle_spec.rb
new file mode 100644
index 0000000000..4aa176956f
--- /dev/null
+++ b/spec/ruby/core/complex/angle_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Complex#angle" do
+ it_behaves_like :complex_arg, :angle
+end
diff --git a/spec/ruby/core/complex/arg_spec.rb b/spec/ruby/core/complex/arg_spec.rb
new file mode 100644
index 0000000000..009f19429f
--- /dev/null
+++ b/spec/ruby/core/complex/arg_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Complex#arg" do
+ it_behaves_like :complex_arg, :arg
+end
diff --git a/spec/ruby/core/complex/coerce_spec.rb b/spec/ruby/core/complex/coerce_spec.rb
new file mode 100644
index 0000000000..a30a6c1d5f
--- /dev/null
+++ b/spec/ruby/core/complex/coerce_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+
+describe "Complex#coerce" do
+ before :each do
+ @one = Complex(1)
+ end
+
+ it "returns an array containing other and self as Complex when other is an Integer" do
+ result = @one.coerce(2)
+ result.should == [2, 1]
+ result.first.should be_kind_of(Complex)
+ result.last.should be_kind_of(Complex)
+ end
+
+ it "returns an array containing other and self as Complex when other is a Float" do
+ result = @one.coerce(20.5)
+ result.should == [20.5, 1]
+ result.first.should be_kind_of(Complex)
+ result.last.should be_kind_of(Complex)
+ end
+
+ it "returns an array containing other and self as Complex when other is a Bignum" do
+ result = @one.coerce(4294967296)
+ result.should == [4294967296, 1]
+ result.first.should be_kind_of(Complex)
+ result.last.should be_kind_of(Complex)
+ end
+
+ it "returns an array containing other and self as Complex when other is a Rational" do
+ result = @one.coerce(Rational(5,6))
+ result.should == [Rational(5,6), 1]
+ result.first.should be_kind_of(Complex)
+ result.last.should be_kind_of(Complex)
+ end
+
+ it "returns an array containing other and self when other is a Complex" do
+ other = Complex(2)
+ result = @one.coerce(other)
+ result.should == [other, @one]
+ result.first.should equal(other)
+ result.last.should equal(@one)
+ end
+
+ it "returns an array containing other as Complex and self when other is a Numeric which responds to #real? with true" do
+ other = mock_numeric('other')
+ other.should_receive(:real?).any_number_of_times.and_return(true)
+ result = @one.coerce(other)
+ result.should == [other, @one]
+ result.first.should eql(Complex(other))
+ result.last.should equal(@one)
+ end
+
+ it "raises TypeError when other is a Numeric which responds to #real? with false" do
+ other = mock_numeric('other')
+ other.should_receive(:real?).any_number_of_times.and_return(false)
+ -> { @one.coerce(other) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when other is a String" do
+ -> { @one.coerce("20") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when other is nil" do
+ -> { @one.coerce(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when other is false" do
+ -> { @one.coerce(false) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/complex/comparison_spec.rb b/spec/ruby/core/complex/comparison_spec.rb
new file mode 100644
index 0000000000..3a3142f234
--- /dev/null
+++ b/spec/ruby/core/complex/comparison_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+
+describe "Complex#<=>" do
+ it "returns nil if either self or argument has imaginary part" do
+ (Complex(5, 1) <=> Complex(2)).should be_nil
+ (Complex(1) <=> Complex(2, 1)).should be_nil
+ (5 <=> Complex(2, 1)).should be_nil
+ end
+
+ it "returns nil if argument is not numeric" do
+ (Complex(5, 1) <=> "cmp").should be_nil
+ (Complex(1) <=> "cmp").should be_nil
+ (Complex(1) <=> Object.new).should be_nil
+ end
+
+ it "returns 0, 1, or -1 if self and argument do not have imaginary part" do
+ (Complex(5) <=> Complex(2)).should == 1
+ (Complex(2) <=> Complex(3)).should == -1
+ (Complex(2) <=> Complex(2)).should == 0
+
+ (Complex(5) <=> 2).should == 1
+ (Complex(2) <=> 3).should == -1
+ (Complex(2) <=> 2).should == 0
+ end
+end
diff --git a/spec/ruby/core/complex/conj_spec.rb b/spec/ruby/core/complex/conj_spec.rb
new file mode 100644
index 0000000000..5e3bc1acb8
--- /dev/null
+++ b/spec/ruby/core/complex/conj_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/conjugate'
+
+describe "Complex#conj" do
+ it_behaves_like :complex_conjugate, :conj
+end
diff --git a/spec/ruby/core/complex/conjugate_spec.rb b/spec/ruby/core/complex/conjugate_spec.rb
new file mode 100644
index 0000000000..f658bab4da
--- /dev/null
+++ b/spec/ruby/core/complex/conjugate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/conjugate'
+
+describe "Complex#conjugate" do
+ it_behaves_like :complex_conjugate, :conjugate
+end
diff --git a/spec/ruby/core/complex/constants_spec.rb b/spec/ruby/core/complex/constants_spec.rb
new file mode 100644
index 0000000000..50303de16c
--- /dev/null
+++ b/spec/ruby/core/complex/constants_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Complex::I" do
+ it "is Complex(0, 1)" do
+ Complex::I.should eql(Complex(0, 1))
+ end
+end
diff --git a/spec/ruby/core/complex/denominator_spec.rb b/spec/ruby/core/complex/denominator_spec.rb
new file mode 100644
index 0000000000..c1a2003820
--- /dev/null
+++ b/spec/ruby/core/complex/denominator_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Complex#denominator" do
+ it "returns the least common multiple denominator of the real and imaginary parts" do
+ Complex(3, 4).denominator.should == 1
+ Complex(3, bignum_value).denominator.should == 1
+
+ Complex(3, Rational(3,4)).denominator.should == 4
+
+ Complex(Rational(4,8), Rational(3,4)).denominator.should == 4
+ Complex(Rational(3,8), Rational(3,4)).denominator.should == 8
+ end
+end
diff --git a/spec/ruby/core/complex/divide_spec.rb b/spec/ruby/core/complex/divide_spec.rb
new file mode 100644
index 0000000000..bebf862312
--- /dev/null
+++ b/spec/ruby/core/complex/divide_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/divide'
+
+describe "Complex#/" do
+ it_behaves_like :complex_divide, :/
+end
diff --git a/spec/ruby/core/complex/eql_spec.rb b/spec/ruby/core/complex/eql_spec.rb
new file mode 100644
index 0000000000..9194efc074
--- /dev/null
+++ b/spec/ruby/core/complex/eql_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Complex#eql?" do
+ it "returns false if other is not Complex" do
+ Complex(1).eql?(1).should be_false
+ end
+
+ it "returns true when the respective parts are of the same classes and self == other" do
+ Complex(1, 2).eql?(Complex(1, 2)).should be_true
+ end
+
+ it "returns false when the real parts are of different classes" do
+ Complex(1).eql?(Complex(1.0)).should be_false
+ end
+
+ it "returns false when the imaginary parts are of different classes" do
+ Complex(1, 2).eql?(Complex(1, 2.0)).should be_false
+ end
+
+ it "returns false when self == other is false" do
+ Complex(1, 2).eql?(Complex(2, 3)).should be_false
+ end
+
+ it "does NOT send #eql? to real or imaginary parts" do
+ real = mock_numeric('real')
+ imag = mock_numeric('imag')
+ real.should_not_receive(:eql?)
+ imag.should_not_receive(:eql?)
+ Complex(real, imag).eql?(Complex(real, imag)).should be_true
+ end
+end
diff --git a/spec/ruby/core/complex/equal_value_spec.rb b/spec/ruby/core/complex/equal_value_spec.rb
new file mode 100644
index 0000000000..ad7236b1bd
--- /dev/null
+++ b/spec/ruby/core/complex/equal_value_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../../spec_helper'
+
+describe "Complex#==" do
+ describe "with Complex" do
+ it "returns true when self and other have numerical equality" do
+ Complex(1, 2).should == Complex(1, 2)
+ Complex(3, 9).should == Complex(3, 9)
+ Complex(-3, -9).should == Complex(-3, -9)
+
+ Complex(1, 2).should_not == Complex(3, 4)
+ Complex(3, 9).should_not == Complex(9, 3)
+
+ Complex(1.0, 2.0).should == Complex(1, 2)
+ Complex(3.0, 9.0).should_not == Complex(9.0, 3.0)
+
+ Complex(1.5, 2.5).should == Complex(1.5, 2.5)
+ Complex(1.5, 2.5).should == Complex(1.5, 2.5)
+ Complex(-1.5, 2.5).should == Complex(-1.5, 2.5)
+
+ Complex(1.5, 2.5).should_not == Complex(2.5, 1.5)
+ Complex(3.75, 2.5).should_not == Complex(1.5, 2.5)
+
+ Complex(bignum_value, 2.5).should == Complex(bignum_value, 2.5)
+ Complex(3.75, bignum_value).should_not == Complex(1.5, bignum_value)
+
+ Complex(nan_value).should_not == Complex(nan_value)
+ end
+ end
+
+ describe "with Numeric" do
+ it "returns true when self's imaginary part is 0 and the real part and other have numerical equality" do
+ Complex(3, 0).should == 3
+ Complex(-3, 0).should == -3
+
+ Complex(3.5, 0).should == 3.5
+ Complex(-3.5, 0).should == -3.5
+
+ Complex(bignum_value, 0).should == bignum_value
+ Complex(-bignum_value, 0).should == -bignum_value
+
+ Complex(3.0, 0).should == 3
+ Complex(-3.0, 0).should == -3
+
+ Complex(3, 0).should_not == 4
+ Complex(-3, 0).should_not == -4
+
+ Complex(3.5, 0).should_not == -4.5
+ Complex(-3.5, 0).should_not == 2.5
+
+ Complex(bignum_value, 0).should_not == bignum_value(10)
+ Complex(-bignum_value, 0).should_not == -bignum_value(20)
+ end
+ end
+
+ describe "with Object" do
+ # Integer#== and Float#== only return booleans - Bug?
+ it "calls other#== with self" do
+ value = Complex(3, 0)
+
+ obj = mock("Object")
+ obj.should_receive(:==).with(value).and_return(:expected)
+
+ (value == obj).should_not be_false
+ end
+ end
+
+ describe "with a Numeric which responds to #real? with true" do
+ before do
+ @other = mock_numeric('other')
+ @other.should_receive(:real?).any_number_of_times.and_return(true)
+ end
+
+ it "returns real == other when the imaginary part is zero" do
+ real = mock_numeric('real')
+ real.should_receive(:==).with(@other).and_return(true)
+ (Complex(real, 0) == @other).should be_true
+ end
+
+ it "returns false when when the imaginary part is not zero" do
+ (Complex(3, 1) == @other).should be_false
+ end
+ end
+
+ describe "with a Numeric which responds to #real? with false" do
+ it "returns other == self" do
+ complex = Complex(3, 0)
+ other = mock_numeric('other')
+ other.should_receive(:real?).any_number_of_times.and_return(false)
+ other.should_receive(:==).with(complex).and_return(true)
+ (complex == other).should be_true
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/exponent_spec.rb b/spec/ruby/core/complex/exponent_spec.rb
new file mode 100644
index 0000000000..86f827aece
--- /dev/null
+++ b/spec/ruby/core/complex/exponent_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+
+describe "Complex#**" do
+ describe "with Integer 0" do
+ it "returns Complex(1)" do
+ (Complex(3, 4) ** 0).should eql(Complex(1))
+ end
+ end
+
+ describe "with Float 0.0" do
+ it "returns Complex(1.0, 0.0)" do
+ (Complex(3, 4) ** 0.0).should eql(Complex(1.0, 0.0))
+ end
+ end
+
+ describe "with Complex" do
+ it "returns self raised to the given power" do
+ (Complex(2, 1) ** Complex(2, 1)).should be_close(Complex(-0.504824688978319, 3.10414407699553), TOLERANCE)
+ (Complex(2, 1) ** Complex(3, 4)).should be_close(Complex(-0.179174656916581, -1.74071656397662), TOLERANCE)
+
+ (Complex(2, 1) ** Complex(-2, -1)).should be_close(Complex(-0.051041070450869, -0.313849223270419), TOLERANCE)
+ (Complex(-2, -1) ** Complex(2, 1)).should be_close(Complex(-11.6819929610857, 71.8320439736158), TOLERANCE)
+ end
+ end
+
+ describe "with Integer" do
+ it "returns self raised to the given power" do
+ (Complex(2, 1) ** 2).should == Complex(3, 4)
+ (Complex(3, 4) ** 2).should == Complex(-7, 24)
+ (Complex(3, 4) ** -2).should be_close(Complex(-0.0112, -0.0384), TOLERANCE)
+
+
+ (Complex(2, 1) ** 2.5).should be_close(Complex(2.99179707178602, 6.85206901006896), TOLERANCE)
+ (Complex(3, 4) ** 2.5).should be_close(Complex(-38.0, 41.0), TOLERANCE)
+ (Complex(3, 4) ** -2.5).should be_close(Complex(-0.01216, -0.01312), TOLERANCE)
+
+ (Complex(1) ** 1).should == Complex(1)
+
+ # NOTE: Takes way too long...
+ #(Complex(2, 1) ** bignum_value)
+ end
+ end
+
+ describe "with Rational" do
+ it "returns self raised to the given power" do
+ (Complex(2, 1) ** Rational(3, 4)).should be_close(Complex(1.71913265276568, 0.623124744394697), TOLERANCE)
+ (Complex(2, 1) ** Rational(4, 3)).should be_close(Complex(2.3828547125173, 1.69466313833091), TOLERANCE)
+ (Complex(2, 1) ** Rational(-4, 3)).should be_close(Complex(0.278700377879388, -0.198209003071003), TOLERANCE)
+ end
+ end
+
+ describe "with Object" do
+ it "tries to coerce self into other" do
+ value = Complex(3, 9)
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(value).and_return([2, 5])
+ (value ** obj).should == 2 ** 5
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/fdiv_spec.rb b/spec/ruby/core/complex/fdiv_spec.rb
new file mode 100644
index 0000000000..68f7d1b309
--- /dev/null
+++ b/spec/ruby/core/complex/fdiv_spec.rb
@@ -0,0 +1,129 @@
+require_relative '../../spec_helper'
+
+describe "Complex#fdiv" do
+ it "accepts a numeric argument" do
+ -> { Complex(20).fdiv(2) }.should_not raise_error(TypeError)
+ -> { Complex(20).fdiv(2.0) }.should_not raise_error(TypeError)
+ -> { Complex(20).fdiv(bignum_value) }.should_not raise_error(TypeError)
+ end
+
+ it "accepts a negative numeric argument" do
+ -> { Complex(20).fdiv(-2) }.should_not raise_error(TypeError)
+ -> { Complex(20).fdiv(-2.0) }.should_not raise_error(TypeError)
+ -> { Complex(20).fdiv(-bignum_value) }.should_not raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed a non-numeric argument" do
+ -> { Complex(20).fdiv([]) }.should raise_error(TypeError)
+ -> { Complex(20).fdiv(:sym) }.should raise_error(TypeError)
+ -> { Complex(20).fdiv('s') }.should raise_error(TypeError)
+ end
+
+ it "sets the real part to NaN if self's real part is NaN" do
+ Complex(nan_value).fdiv(2).real.nan?.should be_true
+ end
+
+ it "sets the imaginary part to NaN if self's imaginary part is NaN" do
+ Complex(2, nan_value).fdiv(2).imag.nan?.should be_true
+ end
+
+ it "sets the real and imaginary part to NaN if self's real and imaginary parts are NaN" do
+ Complex(nan_value, nan_value).fdiv(2).imag.nan?.should be_true
+ Complex(nan_value, nan_value).fdiv(2).real.nan?.should be_true
+ end
+
+ it "sets the real and imaginary part to NaN if self's real part and the argument are both NaN" do
+ Complex(nan_value, 2).fdiv(nan_value).imag.nan?.should be_true
+ Complex(nan_value, 2).fdiv(nan_value).real.nan?.should be_true
+ end
+
+ it "sets the real and imaginary part to NaN if self's real part, self's imaginary part, and the argument are NaN" do
+ Complex(nan_value, nan_value).fdiv(nan_value).imag.nan?.should be_true
+ Complex(nan_value, nan_value).fdiv(nan_value).real.nan?.should be_true
+ end
+
+ it "sets the real part to Infinity if self's real part is Infinity" do
+ Complex(infinity_value).fdiv(2).real.infinite?.should == 1
+ Complex(infinity_value,2).fdiv(2).real.infinite?.should == 1
+ end
+
+ it "sets the imaginary part to Infinity if self's imaginary part is Infinity" do
+ Complex(2, infinity_value).fdiv(2).imag.infinite?.should == 1
+ Complex(2, infinity_value).fdiv(2).imag.infinite?.should == 1
+ end
+
+ it "sets the imaginary and real part to Infinity if self's imaginary and real parts are Infinity" do
+ Complex(infinity_value, infinity_value).fdiv(2).real.infinite?.should == 1
+ Complex(infinity_value, infinity_value).fdiv(2).imag.infinite?.should == 1
+ end
+
+ it "sets the real part to NaN and the imaginary part to NaN if self's imaginary part, self's real part, and the argument are Infinity" do
+ Complex(infinity_value, infinity_value).fdiv(infinity_value).real.nan?.should be_true
+ Complex(infinity_value, infinity_value).fdiv(infinity_value).imag.nan?.should be_true
+ end
+end
+
+describe "Complex#fdiv with no imaginary part" do
+ before :each do
+ @numbers = [1, 5.43, 10, bignum_value, 99872.2918710].map{|n| [n,-n]}.flatten
+ end
+
+ it "returns a Complex number" do
+ @numbers.each do |real|
+ @numbers.each do |other|
+ Complex(real).fdiv(other).should be_an_instance_of(Complex)
+ end
+ end
+ end
+
+ it "sets the real part to self's real part fdiv'd with the argument" do
+ @numbers.each do |real|
+ @numbers.each do |other|
+ Complex(real).fdiv(other).real.should == real.fdiv(other)
+ end
+ end
+ end
+
+ it "sets the imaginary part to 0.0" do
+ @numbers.each do |real|
+ @numbers.each do |other|
+ Complex(real).fdiv(other).imaginary.should == 0.0
+ end
+ end
+ end
+end
+
+describe "Complex#fdiv with an imaginary part" do
+ before :each do
+ @numbers = [1, 5.43, 10, bignum_value, 99872.2918710].map{|n| [n,-n]}.flatten
+ end
+
+ it "returns a Complex number" do
+ @numbers.each do |real|
+ @numbers.each_with_index do |other,idx|
+ Complex(
+ real,@numbers[idx == 0 ? -1 : idx-1]
+ ).fdiv(other).should be_an_instance_of(Complex)
+ end
+ end
+ end
+
+ it "sets the real part to self's real part fdiv'd with the argument" do
+ @numbers.each do |real|
+ @numbers.each_with_index do |other,idx|
+ Complex(
+ real,@numbers[idx == 0 ? -1 : idx-1]
+ ).fdiv(other).real.should == real.fdiv(other)
+ end
+ end
+ end
+
+ it "sets the imaginary part to the imaginary part fdiv'd with the argument" do
+ @numbers.each do |real|
+ @numbers.each_with_index do |other,idx|
+ im = @numbers[idx == 0 ? -1 : idx-1]
+ Complex(real, im).fdiv(other).imag.should == im.fdiv(other)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/finite_spec.rb b/spec/ruby/core/complex/finite_spec.rb
new file mode 100644
index 0000000000..7d9f82404e
--- /dev/null
+++ b/spec/ruby/core/complex/finite_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "Complex#finite?" do
+ it "returns true if magnitude is finite" do
+ (1+1i).should.finite?
+ end
+
+ it "returns false for positive infinity" do
+ value = Complex(Float::INFINITY, 42)
+ value.should_not.finite?
+ end
+
+ it "returns false for positive complex with infinite imaginary" do
+ value = Complex(1, Float::INFINITY)
+ value.should_not.finite?
+ end
+
+ it "returns false for negative infinity" do
+ value = -Complex(Float::INFINITY, 42)
+ value.should_not.finite?
+ end
+
+ it "returns false for negative complex with infinite imaginary" do
+ value = -Complex(1, Float::INFINITY)
+ value.should_not.finite?
+ end
+
+ it "returns false for NaN" do
+ value = Complex(Float::NAN, Float::NAN)
+ value.should_not.finite?
+ end
+end
diff --git a/spec/ruby/core/complex/hash_spec.rb b/spec/ruby/core/complex/hash_spec.rb
new file mode 100644
index 0000000000..cad283309d
--- /dev/null
+++ b/spec/ruby/core/complex/hash_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Complex#hash" do
+ it "is static" do
+ Complex(1).hash.should == Complex(1).hash
+ Complex(1, 0).hash.should == Complex(1).hash
+ Complex(1, 1).hash.should == Complex(1, 1).hash
+ end
+
+ it "is different for different instances" do
+ Complex(1, 2).hash.should_not == Complex(1, 1).hash
+ Complex(2, 1).hash.should_not == Complex(1, 1).hash
+
+ Complex(1, 2).hash.should_not == Complex(2, 1).hash
+ end
+end
diff --git a/spec/ruby/core/complex/imag_spec.rb b/spec/ruby/core/complex/imag_spec.rb
new file mode 100644
index 0000000000..2bafd1ab54
--- /dev/null
+++ b/spec/ruby/core/complex/imag_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/image'
+
+describe "Complex#imag" do
+ it_behaves_like :complex_image, :imag
+end
diff --git a/spec/ruby/core/complex/imaginary_spec.rb b/spec/ruby/core/complex/imaginary_spec.rb
new file mode 100644
index 0000000000..a8a1bfea90
--- /dev/null
+++ b/spec/ruby/core/complex/imaginary_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/image'
+
+describe "Complex#imaginary" do
+ it_behaves_like :complex_image, :imaginary
+end
diff --git a/spec/ruby/core/complex/infinite_spec.rb b/spec/ruby/core/complex/infinite_spec.rb
new file mode 100644
index 0000000000..9e48860dee
--- /dev/null
+++ b/spec/ruby/core/complex/infinite_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "Complex#infinite?" do
+ it "returns nil if magnitude is finite" do
+ (1+1i).infinite?.should == nil
+ end
+
+ it "returns 1 for positive infinity" do
+ value = Complex(Float::INFINITY, 42).infinite?
+ value.should == 1
+ end
+
+ it "returns 1 for positive complex with infinite imaginary" do
+ value = Complex(1, Float::INFINITY).infinite?
+ value.should == 1
+ end
+
+ it "returns -1 for negative infinity" do
+ value = -Complex(Float::INFINITY, 42).infinite?
+ value.should == -1
+ end
+
+ it "returns -1 for negative complex with infinite imaginary" do
+ value = -Complex(1, Float::INFINITY).infinite?
+ value.should == -1
+ end
+
+ it "returns nil for NaN" do
+ value = Complex(0, Float::NAN).infinite?
+ value.should == nil
+ end
+end
diff --git a/spec/ruby/core/complex/inspect_spec.rb b/spec/ruby/core/complex/inspect_spec.rb
new file mode 100644
index 0000000000..71aabde5be
--- /dev/null
+++ b/spec/ruby/core/complex/inspect_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Complex#inspect" do
+ it "returns (${real}+${image}i) for positive imaginary parts" do
+ Complex(1).inspect.should == "(1+0i)"
+ Complex(7).inspect.should == "(7+0i)"
+ Complex(-1, 4).inspect.should == "(-1+4i)"
+ Complex(-7, 6.7).inspect.should == "(-7+6.7i)"
+ end
+
+ it "returns (${real}-${image}i) for negative imaginary parts" do
+ Complex(0, -1).inspect.should == "(0-1i)"
+ Complex(-1, -4).inspect.should == "(-1-4i)"
+ Complex(-7, -6.7).inspect.should == "(-7-6.7i)"
+ end
+end
diff --git a/spec/ruby/core/complex/integer_spec.rb b/spec/ruby/core/complex/integer_spec.rb
new file mode 100644
index 0000000000..0957accb70
--- /dev/null
+++ b/spec/ruby/core/complex/integer_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Complex#integer?" do
+ it "returns false for a Complex with no imaginary part" do
+ Complex(20).integer?.should be_false
+ end
+
+ it "returns false for a Complex with an imaginary part" do
+ Complex(20,3).integer?.should be_false
+ end
+end
diff --git a/spec/ruby/core/complex/magnitude_spec.rb b/spec/ruby/core/complex/magnitude_spec.rb
new file mode 100644
index 0000000000..86f3b29868
--- /dev/null
+++ b/spec/ruby/core/complex/magnitude_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/abs'
+
+describe "Complex#magnitude" do
+ it_behaves_like :complex_abs, :magnitude
+end
diff --git a/spec/ruby/core/complex/marshal_dump_spec.rb b/spec/ruby/core/complex/marshal_dump_spec.rb
new file mode 100644
index 0000000000..116899b0ad
--- /dev/null
+++ b/spec/ruby/core/complex/marshal_dump_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Complex#marshal_dump" do
+ it "is a private method" do
+ Complex.should have_private_instance_method(:marshal_dump, false)
+ end
+
+ it "dumps real and imaginary parts" do
+ Complex(1, 2).send(:marshal_dump).should == [1, 2]
+ end
+end
diff --git a/spec/ruby/core/complex/minus_spec.rb b/spec/ruby/core/complex/minus_spec.rb
new file mode 100644
index 0000000000..7c104ce784
--- /dev/null
+++ b/spec/ruby/core/complex/minus_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "Complex#-" do
+ describe "with Complex" do
+ it "subtracts both the real and imaginary components" do
+ (Complex(1, 2) - Complex(10, 20)).should == Complex(1 - 10, 2 - 20)
+ (Complex(1.5, 2.1) - Complex(100.2, -30.3)).should == Complex(1.5 - 100.2, 2.1 - (-30.3))
+ end
+ end
+
+ describe "with Integer" do
+ it "subtracts the real number from the real component of self" do
+ (Complex(1, 2) - 50).should == Complex(-49, 2)
+ (Complex(1, 2) - 50.5).should == Complex(-49.5, 2)
+ end
+ end
+
+ describe "with Object" do
+ it "tries to coerce self into other" do
+ value = Complex(3, 9)
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(value).and_return([2, 5])
+ (value - obj).should == 2 - 5
+ end
+ end
+
+ describe "passed Numeric which responds to #real? with true" do
+ it "coerces the passed argument to the type of the real part and subtracts the resulting elements" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).and_return(true)
+ n.should_receive(:coerce).with(1).and_return([1, 4])
+ (Complex(1, 2) - n).should == Complex(-3, 2)
+ end
+ end
+
+ describe "passed Numeric which responds to #real? with false" do
+ it "coerces the passed argument to Complex and subtracts the resulting elements" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).and_return(false)
+ n.should_receive(:coerce).with(Complex(1, 2)).and_return([Complex(1, 2), Complex(3, 4)])
+ (Complex(1, 2) - n).should == Complex(-2, -2)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/multiply_spec.rb b/spec/ruby/core/complex/multiply_spec.rb
new file mode 100644
index 0000000000..35bf7c8455
--- /dev/null
+++ b/spec/ruby/core/complex/multiply_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Complex#*" do
+ describe "with Complex" do
+ it "multiplies according to the usual rule for complex numbers: (a + bi) * (c + di) = ac - bd + (ad + bc)i" do
+ (Complex(1, 2) * Complex(10, 20)).should == Complex((1 * 10) - (2 * 20), (1 * 20) + (2 * 10))
+ (Complex(1.5, 2.1) * Complex(100.2, -30.3)).should == Complex((1.5 * 100.2) - (2.1 * -30.3), (1.5 * -30.3) + (2.1 * 100.2))
+ end
+ end
+
+ describe "with Integer" do
+ it "multiplies both parts of self by the given Integer" do
+ (Complex(3, 2) * 50).should == Complex(150, 100)
+ (Complex(-3, 2) * 50.5).should == Complex(-151.5, 101)
+ end
+ end
+
+ describe "with Object" do
+ it "tries to coerce self into other" do
+ value = Complex(3, 9)
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(value).and_return([2, 5])
+ (value * obj).should == 2 * 5
+ end
+ end
+
+ describe "with a Numeric which responds to #real? with true" do
+ it "multiples both parts of self by other" do
+ other = mock_numeric('other')
+ real = mock_numeric('real')
+ imag = mock_numeric('imag')
+ other.should_receive(:real?).and_return(true)
+ real.should_receive(:*).with(other).and_return(1)
+ imag.should_receive(:*).with(other).and_return(2)
+ (Complex(real, imag) * other).should == Complex(1, 2)
+ end
+
+ describe "with a Numeric which responds to #real? with false" do
+ it "coerces the passed argument to Complex and multiplies the resulting elements" do
+ complex = Complex(3, 0)
+ other = mock_numeric('other')
+ other.should_receive(:real?).any_number_of_times.and_return(false)
+ other.should_receive(:coerce).with(complex).and_return([5, 2])
+ (complex * other).should == 10
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/negative_spec.rb b/spec/ruby/core/complex/negative_spec.rb
new file mode 100644
index 0000000000..62ab89c04a
--- /dev/null
+++ b/spec/ruby/core/complex/negative_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Complex#negative?" do
+ it "is undefined" do
+ c = Complex(1)
+
+ c.methods.should_not include(:negative?)
+
+ -> {
+ c.negative?
+ }.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/complex/numerator_spec.rb b/spec/ruby/core/complex/numerator_spec.rb
new file mode 100644
index 0000000000..7ab66e6a61
--- /dev/null
+++ b/spec/ruby/core/complex/numerator_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Complex#numerator" do
+ it "returns self's numerator" do
+ Complex(2).numerator.should == Complex(2)
+ Complex(3, 4).numerator.should == Complex(3, 4)
+
+ Complex(Rational(3, 4), Rational(3, 4)).numerator.should == Complex(3, 3)
+ Complex(Rational(7, 4), Rational(8, 4)).numerator.should == Complex(7, 8)
+
+ Complex(Rational(7, 8), Rational(8, 4)).numerator.should == Complex(7, 16)
+ Complex(Rational(7, 4), Rational(8, 8)).numerator.should == Complex(7, 4)
+
+ # NOTE:
+ # Bug? - Fails with a MethodMissingError
+ # (undefined method `denominator' for 3.5:Float)
+ # Complex(3.5, 3.7).numerator
+ end
+end
diff --git a/spec/ruby/core/complex/phase_spec.rb b/spec/ruby/core/complex/phase_spec.rb
new file mode 100644
index 0000000000..89574bf533
--- /dev/null
+++ b/spec/ruby/core/complex/phase_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Complex#phase" do
+ it_behaves_like :complex_arg, :phase
+end
diff --git a/spec/ruby/core/complex/plus_spec.rb b/spec/ruby/core/complex/plus_spec.rb
new file mode 100644
index 0000000000..2056ca786c
--- /dev/null
+++ b/spec/ruby/core/complex/plus_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "Complex#+" do
+ describe "with Complex" do
+ it "adds both the real and imaginary components" do
+ (Complex(1, 2) + Complex(10, 20)).should == Complex(1 + 10, 2 + 20)
+ (Complex(1.5, 2.1) + Complex(100.2, -30.3)).should == Complex(1.5 + 100.2, 2.1 + (-30.3))
+ end
+ end
+
+ describe "with Integer" do
+ it "adds the real number to the real component of self" do
+ (Complex(1, 2) + 50).should == Complex(51, 2)
+ (Complex(1, 2) + 50.5).should == Complex(51.5, 2)
+ end
+ end
+
+ describe "with Object" do
+ it "tries to coerce self into other" do
+ value = Complex(3, 9)
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(value).and_return([2, 5])
+ (value + obj).should == 2 + 5
+ end
+ end
+
+ describe "passed Numeric which responds to #real? with true" do
+ it "coerces the passed argument to the type of the real part and adds the resulting elements" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).and_return(true)
+ n.should_receive(:coerce).with(1).and_return([1, 4])
+ (Complex(1, 2) + n).should == Complex(5, 2)
+ end
+ end
+
+ describe "passed Numeric which responds to #real? with false" do
+ it "coerces the passed argument to Complex and adds the resulting elements" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).and_return(false)
+ n.should_receive(:coerce).with(Complex(1, 2)).and_return([Complex(1, 2), Complex(3, 4)])
+ (Complex(1, 2) + n).should == Complex(4, 6)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/polar_spec.rb b/spec/ruby/core/complex/polar_spec.rb
new file mode 100644
index 0000000000..3bb3751bc6
--- /dev/null
+++ b/spec/ruby/core/complex/polar_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "Complex.polar" do
+ it "returns a complex number in terms of radius and angle" do
+ Complex.polar(50, 60).should be_close(Complex(-47.6206490207578, -15.2405310551108), TOLERANCE)
+ Complex.polar(-10, -20).should be_close(Complex(-4.08082061813392, 9.12945250727628), TOLERANCE)
+ end
+
+ it "raises a TypeError when given non real arguments" do
+ ->{ Complex.polar(nil) }.should raise_error(TypeError)
+ ->{ Complex.polar(nil, nil) }.should raise_error(TypeError)
+ end
+
+ ruby_bug "#19004", ""..."3.2" do
+ it "computes the real values of the real & imaginary parts from the polar form" do
+ a = Complex.polar(1.0+0.0i, Math::PI/2+0.0i)
+ a.real.should be_close(0.0, TOLERANCE)
+ a.imag.should be_close(1.0, TOLERANCE)
+ a.real.real?.should be_true
+ a.imag.real?.should be_true
+
+ b = Complex.polar(1+0.0i)
+ b.real.should be_close(1.0, TOLERANCE)
+ b.imag.should be_close(0.0, TOLERANCE)
+ b.real.real?.should be_true
+ b.imag.real?.should be_true
+ end
+ end
+end
+
+describe "Complex#polar" do
+ it "returns the absolute value and the argument" do
+ a = Complex(3, 4)
+ a.polar.size.should == 2
+ a.polar.first.should == 5.0
+ a.polar.last.should be_close(0.927295218001612, TOLERANCE)
+
+ b = Complex(-3.5, 4.7)
+ b.polar.size.should == 2
+ b.polar.first.should be_close(5.86003412959345, TOLERANCE)
+ b.polar.last.should be_close(2.21088447955664, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/complex/positive_spec.rb b/spec/ruby/core/complex/positive_spec.rb
new file mode 100644
index 0000000000..f1bad8608c
--- /dev/null
+++ b/spec/ruby/core/complex/positive_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Complex#positive?" do
+ it "is undefined" do
+ c = Complex(1)
+
+ c.methods.should_not include(:positive?)
+
+ -> {
+ c.positive?
+ }.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/complex/quo_spec.rb b/spec/ruby/core/complex/quo_spec.rb
new file mode 100644
index 0000000000..ee6fd65c79
--- /dev/null
+++ b/spec/ruby/core/complex/quo_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/divide'
+
+describe "Complex#quo" do
+ it_behaves_like :complex_divide, :quo
+end
diff --git a/spec/ruby/core/complex/rationalize_spec.rb b/spec/ruby/core/complex/rationalize_spec.rb
new file mode 100644
index 0000000000..043b8ddf2a
--- /dev/null
+++ b/spec/ruby/core/complex/rationalize_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Complex#rationalize" do
+ it "raises RangeError if self has non-zero imaginary part" do
+ -> { Complex(1,5).rationalize }.should raise_error(RangeError)
+ end
+
+ it "raises RangeError if self has 0.0 imaginary part" do
+ -> { Complex(1,0.0).rationalize }.should raise_error(RangeError)
+ end
+
+ it "returns a Rational if self has zero imaginary part" do
+ Complex(1,0).rationalize.should == Rational(1,1)
+ Complex(2<<63+5).rationalize.should == Rational(2<<63+5,1)
+ end
+
+ it "sends #rationalize to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:rationalize).with(0.1).and_return(:result)
+ Complex(real, 0).rationalize(0.1).should == :result
+ end
+
+ it "ignores a single argument" do
+ Complex(1,0).rationalize(0.1).should == Rational(1,1)
+ end
+
+ it "raises ArgumentError when passed more than one argument" do
+ -> { Complex(1,0).rationalize(0.1, 0.1) }.should raise_error(ArgumentError)
+ -> { Complex(1,0).rationalize(0.1, 0.1, 2) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/complex/real_spec.rb b/spec/ruby/core/complex/real_spec.rb
new file mode 100644
index 0000000000..2ea791c005
--- /dev/null
+++ b/spec/ruby/core/complex/real_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+
+describe "Complex#real" do
+ it "returns the real part of self" do
+ Complex(1, 0).real.should == 1
+ Complex(2, 1).real.should == 2
+ Complex(6.7, 8.9).real.should == 6.7
+ Complex(bignum_value, 3).real.should == bignum_value
+ end
+end
+
+describe "Complex#real?" do
+ it "returns false if there is an imaginary part" do
+ Complex(2,3).real?.should be_false
+ end
+
+ it "returns false if there is not an imaginary part" do
+ Complex(2).real?.should be_false
+ end
+
+ it "returns false if the real part is Infinity" do
+ Complex(infinity_value).real?.should be_false
+ end
+
+ it "returns false if the real part is NaN" do
+ Complex(nan_value).real?.should be_false
+ end
+end
diff --git a/spec/ruby/core/complex/rect_spec.rb b/spec/ruby/core/complex/rect_spec.rb
new file mode 100644
index 0000000000..9e95f3efc2
--- /dev/null
+++ b/spec/ruby/core/complex/rect_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/rect'
+
+describe "Complex#rect" do
+ it_behaves_like :complex_rect, :rect
+end
+
+describe "Complex.rect" do
+ it_behaves_like :complex_rect_class, :rect
+end
diff --git a/spec/ruby/core/complex/rectangular_spec.rb b/spec/ruby/core/complex/rectangular_spec.rb
new file mode 100644
index 0000000000..d4b8ad9782
--- /dev/null
+++ b/spec/ruby/core/complex/rectangular_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/rect'
+
+describe "Complex#rectangular" do
+ it_behaves_like :complex_rect, :rectangular
+end
+
+describe "Complex.rectangular" do
+ it_behaves_like :complex_rect_class, :rectangular
+end
diff --git a/spec/ruby/core/complex/shared/abs.rb b/spec/ruby/core/complex/shared/abs.rb
new file mode 100644
index 0000000000..2299479341
--- /dev/null
+++ b/spec/ruby/core/complex/shared/abs.rb
@@ -0,0 +1,10 @@
+describe :complex_abs, shared: true do
+ it "returns the modulus: |a + bi| = sqrt((a ^ 2) + (b ^ 2))" do
+ Complex(0, 0).send(@method).should == 0
+ Complex(3, 4).send(@method).should == 5 # well-known integer case
+ Complex(-3, 4).send(@method).should == 5
+ Complex(1, -1).send(@method).should be_close(Math.sqrt(2), TOLERANCE)
+ Complex(6.5, 0).send(@method).should be_close(6.5, TOLERANCE)
+ Complex(0, -7.2).send(@method).should be_close(7.2, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/complex/shared/arg.rb b/spec/ruby/core/complex/shared/arg.rb
new file mode 100644
index 0000000000..c81f197433
--- /dev/null
+++ b/spec/ruby/core/complex/shared/arg.rb
@@ -0,0 +1,9 @@
+describe :complex_arg, shared: true do
+ it "returns the argument -- i.e., the angle from (1, 0) in the complex plane" do
+ two_pi = 2 * Math::PI
+ (Complex(1, 0).send(@method) % two_pi).should be_close(0, TOLERANCE)
+ (Complex(0, 2).send(@method) % two_pi).should be_close(Math::PI * 0.5, TOLERANCE)
+ (Complex(-100, 0).send(@method) % two_pi).should be_close(Math::PI, TOLERANCE)
+ (Complex(0, -75.3).send(@method) % two_pi).should be_close(Math::PI * 1.5, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/complex/shared/conjugate.rb b/spec/ruby/core/complex/shared/conjugate.rb
new file mode 100644
index 0000000000..d1ae47bcb6
--- /dev/null
+++ b/spec/ruby/core/complex/shared/conjugate.rb
@@ -0,0 +1,8 @@
+describe :complex_conjugate, shared: true do
+ it "returns the complex conjugate: conj a + bi = a - bi" do
+ Complex(3, 5).send(@method).should == Complex(3, -5)
+ Complex(3, -5).send(@method).should == Complex(3, 5)
+ Complex(-3.0, 5.2).send(@method).should be_close(Complex(-3.0, -5.2), TOLERANCE)
+ Complex(3.0, -5.2).send(@method).should be_close(Complex(3.0, 5.2), TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/complex/shared/divide.rb b/spec/ruby/core/complex/shared/divide.rb
new file mode 100644
index 0000000000..a60802c74c
--- /dev/null
+++ b/spec/ruby/core/complex/shared/divide.rb
@@ -0,0 +1,82 @@
+describe :complex_divide, shared: true do
+ describe "with Complex" do
+ it "divides according to the usual rule for complex numbers" do
+ a = Complex((1 * 10) - (2 * 20), (1 * 20) + (2 * 10))
+ b = Complex(1, 2)
+ a.send(@method, b).should == Complex(10, 20)
+
+ c = Complex((1.5 * 100.2) - (2.1 * -30.3), (1.5 * -30.3) + (2.1 * 100.2))
+ d = Complex(1.5, 2.1)
+ # remember the floating-point arithmetic
+ c.send(@method, d).should be_close(Complex(100.2, -30.3), TOLERANCE)
+ end
+ end
+
+ describe "with Fixnum" do
+ it "divides both parts of the Complex number" do
+ Complex(20, 40).send(@method, 2).should == Complex(10, 20)
+ Complex(30, 30).send(@method, 10).should == Complex(3, 3)
+ end
+
+ it "raises a ZeroDivisionError when given zero" do
+ -> { Complex(20, 40).send(@method, 0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "produces Rational parts" do
+ Complex(5, 9).send(@method, 2).should eql(Complex(Rational(5,2), Rational(9,2)))
+ end
+ end
+
+ describe "with Bignum" do
+ it "divides both parts of the Complex number" do
+ Complex(20, 40).send(@method, 2).should == Complex(10, 20)
+ Complex(15, 16).send(@method, 2.0).should be_close(Complex(7.5, 8), TOLERANCE)
+ end
+ end
+
+ describe "with Float" do
+ it "divides both parts of the Complex number" do
+ Complex(3, 9).send(@method, 1.5).should == Complex(2, 6)
+ Complex(15, 16).send(@method, 2.0).should be_close(Complex(7.5, 8), TOLERANCE)
+ end
+
+ it "returns Complex(Infinity, Infinity) when given zero" do
+ Complex(20, 40).send(@method, 0.0).real.infinite?.should == 1
+ Complex(20, 40).send(@method, 0.0).imag.infinite?.should == 1
+ Complex(-20, 40).send(@method, 0.0).real.infinite?.should == -1
+ Complex(-20, 40).send(@method, 0.0).imag.infinite?.should == 1
+ end
+ end
+
+ describe "with Object" do
+ it "tries to coerce self into other" do
+ value = Complex(3, 9)
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(value).and_return([4, 2])
+ value.send(@method, obj).should == 2
+ end
+ end
+
+ describe "with a Numeric which responds to #real? with true" do
+ it "returns Complex(real.quo(other), imag.quo(other))" do
+ other = mock_numeric('other')
+ real = mock_numeric('real')
+ imag = mock_numeric('imag')
+ other.should_receive(:real?).and_return(true)
+ real.should_receive(:quo).with(other).and_return(1)
+ imag.should_receive(:quo).with(other).and_return(2)
+ Complex(real, imag).send(@method, other).should == Complex(1, 2)
+ end
+ end
+
+ describe "with a Numeric which responds to #real? with false" do
+ it "coerces the passed argument to Complex and divides the resulting elements" do
+ complex = Complex(3, 0)
+ other = mock_numeric('other')
+ other.should_receive(:real?).any_number_of_times.and_return(false)
+ other.should_receive(:coerce).with(complex).and_return([5, 2])
+ complex.send(@method, other).should eql(Rational(5, 2))
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/shared/image.rb b/spec/ruby/core/complex/shared/image.rb
new file mode 100644
index 0000000000..f839dbcaf9
--- /dev/null
+++ b/spec/ruby/core/complex/shared/image.rb
@@ -0,0 +1,8 @@
+describe :complex_image, shared: true do
+ it "returns the imaginary part of self" do
+ Complex(1, 0).send(@method).should == 0
+ Complex(2, 1).send(@method).should == 1
+ Complex(6.7, 8.9).send(@method).should == 8.9
+ Complex(1, bignum_value).send(@method).should == bignum_value
+ end
+end
diff --git a/spec/ruby/core/complex/shared/rect.rb b/spec/ruby/core/complex/shared/rect.rb
new file mode 100644
index 0000000000..9f5de1ffeb
--- /dev/null
+++ b/spec/ruby/core/complex/shared/rect.rb
@@ -0,0 +1,94 @@
+describe :complex_rect, shared: true do
+ before :each do
+ @numbers = [
+ Complex(1),
+ Complex(0, 20),
+ Complex(0, 0),
+ Complex(0.0),
+ Complex(9999999**99),
+ Complex(-20),
+ Complex.polar(76, 10)
+ ]
+ end
+
+ it "returns an Array" do
+ @numbers.each do |number|
+ number.send(@method).should be_an_instance_of(Array)
+ end
+ end
+
+ it "returns a two-element Array" do
+ @numbers.each do |number|
+ number.send(@method).size.should == 2
+ end
+ end
+
+ it "returns the real part of self as the first element" do
+ @numbers.each do |number|
+ number.send(@method).first.should == number.real
+ end
+ end
+
+ it "returns the imaginary part of self as the last element" do
+ @numbers.each do |number|
+ number.send(@method).last.should == number.imaginary
+ end
+ end
+
+ it "raises an ArgumentError if given any arguments" do
+ @numbers.each do |number|
+ -> { number.send(@method, number) }.should raise_error(ArgumentError)
+ end
+ end
+end
+
+describe :complex_rect_class, shared: true do
+ describe "passed a Numeric n which responds to #real? with true" do
+ it "returns a Complex with real part n and imaginary part 0" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).any_number_of_times.and_return(true)
+ result = Complex.send(@method, n)
+ result.real.should == n
+ result.imag.should == 0
+ end
+ end
+
+ describe "passed a Numeric which responds to #real? with false" do
+ it "raises TypeError" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).any_number_of_times.and_return(false)
+ -> { Complex.send(@method, n) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "passed Numerics n1 and n2 and at least one responds to #real? with false" do
+ [[false, false], [false, true], [true, false]].each do |r1, r2|
+ it "raises TypeError" do
+ n1 = mock_numeric('n1')
+ n2 = mock_numeric('n2')
+ n1.should_receive(:real?).any_number_of_times.and_return(r1)
+ n2.should_receive(:real?).any_number_of_times.and_return(r2)
+ -> { Complex.send(@method, n1, n2) }.should raise_error(TypeError)
+ end
+ end
+ end
+
+ describe "passed Numerics n1 and n2 and both respond to #real? with true" do
+ it "returns a Complex with real part n1 and imaginary part n2" do
+ n1 = mock_numeric('n1')
+ n2 = mock_numeric('n2')
+ n1.should_receive(:real?).any_number_of_times.and_return(true)
+ n2.should_receive(:real?).any_number_of_times.and_return(true)
+ result = Complex.send(@method, n1, n2)
+ result.real.should == n1
+ result.imag.should == n2
+ end
+ end
+
+ describe "passed a non-Numeric" do
+ it "raises TypeError" do
+ -> { Complex.send(@method, :sym) }.should raise_error(TypeError)
+ -> { Complex.send(@method, 0, :sym) }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/to_c_spec.rb b/spec/ruby/core/complex/to_c_spec.rb
new file mode 100644
index 0000000000..5ce01d9d4e
--- /dev/null
+++ b/spec/ruby/core/complex/to_c_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "Complex#to_c" do
+ it "returns self" do
+ value = Complex(1, 5)
+ value.to_c.should equal(value)
+ end
+
+ it 'returns the same value' do
+ Complex(1, 5).to_c.should == Complex(1, 5)
+ end
+end
diff --git a/spec/ruby/core/complex/to_f_spec.rb b/spec/ruby/core/complex/to_f_spec.rb
new file mode 100644
index 0000000000..b53471c1fc
--- /dev/null
+++ b/spec/ruby/core/complex/to_f_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Complex#to_f" do
+ describe "when the imaginary part is Integer 0" do
+ it "returns the result of sending #to_f to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_f).and_return(:f)
+ Complex(real, 0).to_f.should == :f
+ end
+ end
+
+ describe "when the imaginary part is Rational 0" do
+ it "returns the result of sending #to_f to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_f).and_return(:f)
+ Complex(real, Rational(0)).to_f.should == :f
+ end
+ end
+
+ describe "when the imaginary part responds to #== 0 with true" do
+ it "returns the result of sending #to_f to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_f).and_return(:f)
+ imag = mock_numeric('imag')
+ imag.should_receive(:==).with(0).any_number_of_times.and_return(true)
+ Complex(real, imag).to_f.should == :f
+ end
+ end
+
+ describe "when the imaginary part is non-zero" do
+ it "raises RangeError" do
+ -> { Complex(0, 1).to_f }.should raise_error(RangeError)
+ end
+ end
+
+ describe "when the imaginary part is Float 0.0" do
+ it "raises RangeError" do
+ -> { Complex(0, 0.0).to_f }.should raise_error(RangeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/to_i_spec.rb b/spec/ruby/core/complex/to_i_spec.rb
new file mode 100644
index 0000000000..1e78f5ec0e
--- /dev/null
+++ b/spec/ruby/core/complex/to_i_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Complex#to_i" do
+ describe "when the imaginary part is Integer 0" do
+ it "returns the result of sending #to_i to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_i).and_return(:i)
+ Complex(real, 0).to_i.should == :i
+ end
+ end
+
+ describe "when the imaginary part is Rational 0" do
+ it "returns the result of sending #to_i to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_i).and_return(:i)
+ Complex(real, Rational(0)).to_i.should == :i
+ end
+ end
+
+ describe "when the imaginary part responds to #== 0 with true" do
+ it "returns the result of sending #to_i to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_i).and_return(:i)
+ imag = mock_numeric('imag')
+ imag.should_receive(:==).with(0).any_number_of_times.and_return(true)
+ Complex(real, imag).to_i.should == :i
+ end
+ end
+
+ describe "when the imaginary part is non-zero" do
+ it "raises RangeError" do
+ -> { Complex(0, 1).to_i }.should raise_error(RangeError)
+ end
+ end
+
+ describe "when the imaginary part is Float 0.0" do
+ it "raises RangeError" do
+ -> { Complex(0, 0.0).to_i }.should raise_error(RangeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/to_r_spec.rb b/spec/ruby/core/complex/to_r_spec.rb
new file mode 100644
index 0000000000..4559921492
--- /dev/null
+++ b/spec/ruby/core/complex/to_r_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Complex#to_r" do
+ describe "when the imaginary part is Integer 0" do
+ it "returns the result of sending #to_r to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_r).and_return(:r)
+ Complex(real, 0).to_r.should == :r
+ end
+ end
+
+ describe "when the imaginary part is Rational 0" do
+ it "returns the result of sending #to_r to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_r).and_return(:r)
+ Complex(real, Rational(0)).to_r.should == :r
+ end
+ end
+
+ describe "when the imaginary part responds to #== 0 with true" do
+ it "returns the result of sending #to_r to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_r).and_return(:r)
+ imag = mock_numeric('imag')
+ imag.should_receive(:==).with(0).any_number_of_times.and_return(true)
+ Complex(real, imag).to_r.should == :r
+ end
+ end
+
+ describe "when the imaginary part is non-zero" do
+ it "raises RangeError" do
+ -> { Complex(0, 1).to_r }.should raise_error(RangeError)
+ end
+ end
+
+ describe "when the imaginary part is Float 0.0" do
+ it "raises RangeError" do
+ -> { Complex(0, 0.0).to_r }.should raise_error(RangeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/to_s_spec.rb b/spec/ruby/core/complex/to_s_spec.rb
new file mode 100644
index 0000000000..989a7ae0b7
--- /dev/null
+++ b/spec/ruby/core/complex/to_s_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+
+describe "Complex#to_s" do
+ describe "when self's real component is 0" do
+ it "returns both the real and imaginary component even when the real is 0" do
+ Complex(0, 5).to_s.should == "0+5i"
+ Complex(0, -3.2).to_s.should == "0-3.2i"
+ end
+ end
+
+ it "returns self as String" do
+ Complex(1, 5).to_s.should == "1+5i"
+ Complex(-2.5, 1.5).to_s.should == "-2.5+1.5i"
+
+ Complex(1, -5).to_s.should == "1-5i"
+ Complex(-2.5, -1.5).to_s.should == "-2.5-1.5i"
+
+ # Guard against the Mathn library
+ guard -> { !defined?(Math.rsqrt) } do
+ Complex(1, 0).to_s.should == "1+0i"
+ Complex(1, -0).to_s.should == "1+0i"
+ end
+ end
+
+ it "returns 1+0.0i for Complex(1, 0.0)" do
+ Complex(1, 0.0).to_s.should == "1+0.0i"
+ end
+
+ it "returns 1-0.0i for Complex(1, -0.0)" do
+ Complex(1, -0.0).to_s.should == "1-0.0i"
+ end
+
+ it "returns 1+Infinity*i for Complex(1, Infinity)" do
+ Complex(1, infinity_value).to_s.should == "1+Infinity*i"
+ end
+
+ it "returns 1-Infinity*i for Complex(1, -Infinity)" do
+ Complex(1, -infinity_value).to_s.should == "1-Infinity*i"
+ end
+
+ it "returns 1+NaN*i for Complex(1, NaN)" do
+ Complex(1, nan_value).to_s.should == "1+NaN*i"
+ end
+end
diff --git a/spec/ruby/core/complex/uminus_spec.rb b/spec/ruby/core/complex/uminus_spec.rb
new file mode 100644
index 0000000000..c0184e11de
--- /dev/null
+++ b/spec/ruby/core/complex/uminus_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Complex#-@" do
+ it "sends #-@ to the real and imaginary parts and returns a Complex with the resulting respective parts" do
+ real = mock_numeric('real')
+ imag = mock_numeric('imag')
+ real.should_receive(:-@).and_return(-1)
+ imag.should_receive(:-@).and_return(-2)
+ Complex(real, imag).send(:-@).should == Complex(-1, -2)
+ end
+end
diff --git a/spec/ruby/core/conditionvariable/broadcast_spec.rb b/spec/ruby/core/conditionvariable/broadcast_spec.rb
new file mode 100644
index 0000000000..d88159df23
--- /dev/null
+++ b/spec/ruby/core/conditionvariable/broadcast_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require 'thread'
+
+describe "ConditionVariable#broadcast" do
+ it "releases all threads waiting in line for this resource" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ threads = []
+ r1 = []
+ r2 = []
+
+ # large number to attempt to cause race conditions
+ 100.times do |i|
+ threads << Thread.new(i) do |tid|
+ m.synchronize do
+ r1 << tid
+ cv.wait(m)
+ r2 << tid
+ end
+ end
+ end
+
+ # wait for all threads to acquire the mutex the first time
+ Thread.pass until m.synchronize { r1.size == threads.size }
+ # wait until all threads are sleeping (ie waiting)
+ Thread.pass until threads.all?(&:stop?)
+
+ r2.should be_empty
+ m.synchronize do
+ cv.broadcast
+ end
+
+ threads.each {|t| t.join }
+
+ # ensure that all threads that enter cv.wait are released
+ r2.sort.should == r1.sort
+ # note that order is not specified as broadcast results in a race
+ # condition on regaining the lock m
+ end
+end
diff --git a/spec/ruby/core/conditionvariable/marshal_dump_spec.rb b/spec/ruby/core/conditionvariable/marshal_dump_spec.rb
new file mode 100644
index 0000000000..f951a13e28
--- /dev/null
+++ b/spec/ruby/core/conditionvariable/marshal_dump_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require 'thread'
+
+describe "ConditionVariable#marshal_dump" do
+ it "raises a TypeError" do
+ cv = ConditionVariable.new
+ -> { cv.marshal_dump }.should raise_error(TypeError, /can't dump/)
+ end
+end
diff --git a/spec/ruby/core/conditionvariable/signal_spec.rb b/spec/ruby/core/conditionvariable/signal_spec.rb
new file mode 100644
index 0000000000..86383073f1
--- /dev/null
+++ b/spec/ruby/core/conditionvariable/signal_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../spec_helper'
+require 'thread'
+
+describe "ConditionVariable#signal" do
+ it "releases the first thread waiting in line for this resource" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ threads = []
+ r1 = []
+ r2 = []
+
+ # large number to attempt to cause race conditions
+ 100.times do |i|
+ threads << Thread.new(i) do |tid|
+ m.synchronize do
+ r1 << tid
+ cv.wait(m)
+ r2 << tid
+ end
+ end
+ end
+
+ # wait for all threads to acquire the mutex the first time
+ Thread.pass until m.synchronize { r1.size == threads.size }
+ # wait until all threads are sleeping (ie waiting)
+ Thread.pass until threads.all?(&:stop?)
+
+ r2.should be_empty
+ 100.times do |i|
+ m.synchronize do
+ cv.signal
+ end
+ Thread.pass until r2.size == i+1
+ end
+
+ threads.each {|t| t.join }
+
+ # ensure that all the threads that went into the cv.wait are
+ # released in the same order
+ r2.should == r1
+ end
+
+ it "allows control to be passed between a pair of threads" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ repeats = 100
+ in_synchronize = false
+
+ t1 = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ repeats.times do
+ cv.wait(m)
+ cv.signal
+ end
+ end
+ end
+
+ # Make sure t1 is waiting for a signal before launching t2.
+ Thread.pass until in_synchronize
+ Thread.pass until t1.stop?
+
+ t2 = Thread.new do
+ m.synchronize do
+ repeats.times do
+ cv.signal
+ cv.wait(m)
+ end
+ end
+ end
+
+ # Check that both threads terminated without exception
+ t1.join
+ t2.join
+ m.should_not.locked?
+ end
+end
diff --git a/spec/ruby/core/conditionvariable/wait_spec.rb b/spec/ruby/core/conditionvariable/wait_spec.rb
new file mode 100644
index 0000000000..9a68c2b5a1
--- /dev/null
+++ b/spec/ruby/core/conditionvariable/wait_spec.rb
@@ -0,0 +1,175 @@
+require_relative '../../spec_helper'
+require 'thread'
+
+describe "ConditionVariable#wait" do
+ it "calls #sleep on the given object" do
+ o = Object.new
+ o.should_receive(:sleep).with(1234)
+
+ cv = ConditionVariable.new
+
+ cv.wait(o, 1234)
+ end
+
+ it "can be woken up by ConditionVariable#signal" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ in_synchronize = false
+
+ th = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ cv.wait(m)
+ end
+ :success
+ end
+
+ # wait for m to acquire the mutex
+ Thread.pass until in_synchronize
+ # wait until th is sleeping (ie waiting)
+ Thread.pass until th.stop?
+
+ m.synchronize { cv.signal }
+ th.value.should == :success
+ end
+
+ it "can be interrupted by Thread#run" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ in_synchronize = false
+
+ th = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ cv.wait(m)
+ end
+ :success
+ end
+
+ # wait for m to acquire the mutex
+ Thread.pass until in_synchronize
+ # wait until th is sleeping (ie waiting)
+ Thread.pass until th.stop?
+
+ th.run
+ th.value.should == :success
+ end
+
+ it "can be interrupted by Thread#wakeup" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ in_synchronize = false
+
+ th = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ cv.wait(m)
+ end
+ :success
+ end
+
+ # wait for m to acquire the mutex
+ Thread.pass until in_synchronize
+ # wait until th is sleeping (ie waiting)
+ Thread.pass until th.stop?
+
+ th.wakeup
+ th.value.should == :success
+ end
+
+ it "reacquires the lock even if the thread is killed" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ in_synchronize = false
+ owned = nil
+
+ th = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ begin
+ cv.wait(m)
+ ensure
+ owned = m.owned?
+ $stderr.puts "\nThe Thread doesn't own the Mutex!" unless owned
+ end
+ end
+ end
+
+ # wait for m to acquire the mutex
+ Thread.pass until in_synchronize
+ # wait until th is sleeping (ie waiting)
+ Thread.pass until th.stop?
+
+ th.kill
+ th.join
+
+ owned.should == true
+ end
+
+ it "reacquires the lock even if the thread is killed after being signaled" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ in_synchronize = false
+ owned = nil
+
+ th = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ begin
+ cv.wait(m)
+ ensure
+ owned = m.owned?
+ $stderr.puts "\nThe Thread doesn't own the Mutex!" unless owned
+ end
+ end
+ end
+
+ # wait for m to acquire the mutex
+ Thread.pass until in_synchronize
+ # wait until th is sleeping (ie waiting)
+ Thread.pass until th.stop?
+
+ m.synchronize {
+ cv.signal
+ # Wait that the thread is blocked on acquiring the Mutex
+ sleep 0.001
+ # Kill the thread, yet the thread should first acquire the Mutex before going on
+ th.kill
+ }
+
+ th.join
+ owned.should == true
+ end
+
+ it "supports multiple Threads waiting on the same ConditionVariable and Mutex" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ n_threads = 4
+ events = []
+
+ threads = n_threads.times.map {
+ Thread.new {
+ m.synchronize {
+ events << :t_in_synchronize
+ cv.wait(m)
+ }
+ }
+ }
+
+ Thread.pass until m.synchronize { events.size } == n_threads
+ Thread.pass until threads.any?(&:stop?)
+ m.synchronize do
+ threads.each { |t|
+ # Cause interactions with the waiting threads.
+ # On TruffleRuby, this causes a safepoint which has interesting
+ # interactions with the ConditionVariable.
+ bt = t.backtrace
+ bt.should be_kind_of(Array)
+ bt.size.should >= 2
+ }
+ end
+
+ cv.broadcast
+ threads.each(&:join)
+ end
+end
diff --git a/spec/ruby/core/data/constants_spec.rb b/spec/ruby/core/data/constants_spec.rb
new file mode 100644
index 0000000000..d9d55b50f9
--- /dev/null
+++ b/spec/ruby/core/data/constants_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ describe "Data" do
+ it "is a subclass of Object" do
+ suppress_warning do
+ Data.superclass.should == Object
+ end
+ end
+
+ it "is deprecated" do
+ -> { Data }.should complain(/constant ::Data is deprecated/)
+ end
+ end
+end
+
+ruby_version_is '3.0'...'3.2' do
+ describe "Data" do
+ it "does not exist anymore" do
+ Object.should_not have_constant(:Data)
+ end
+ end
+end
+
+ruby_version_is '3.2' do
+ describe "Data" do
+ it "is a new constant" do
+ Data.superclass.should == Object
+ end
+
+ it "is not deprecated" do
+ -> { Data }.should_not complain
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/chdir_spec.rb b/spec/ruby/core/dir/chdir_spec.rb
new file mode 100644
index 0000000000..729ac403e3
--- /dev/null
+++ b/spec/ruby/core/dir/chdir_spec.rb
@@ -0,0 +1,124 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.chdir" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ before :each do
+ @original = Dir.pwd
+ end
+
+ after :each do
+ Dir.chdir(@original)
+ end
+
+ it "defaults to $HOME with no arguments" do
+ if ENV['HOME']
+ Dir.chdir
+ current_dir = Dir.pwd
+
+ Dir.chdir(ENV['HOME'])
+ home = Dir.pwd
+ current_dir.should == home
+ end
+ end
+
+ it "changes to the specified directory" do
+ Dir.chdir DirSpecs.mock_dir
+ Dir.pwd.should == DirSpecs.mock_dir
+ end
+
+ it "returns 0 when successfully changing directory" do
+ Dir.chdir(@original).should == 0
+ end
+
+ it "calls #to_str on the argument if it's not a String" do
+ obj = mock('path')
+ obj.should_receive(:to_str).and_return(Dir.pwd)
+ Dir.chdir(obj)
+ end
+
+ it "calls #to_str on the argument if it's not a String and a block is given" do
+ obj = mock('path')
+ obj.should_receive(:to_str).and_return(Dir.pwd)
+ Dir.chdir(obj) { }
+ end
+
+ it "calls #to_path on the argument if it's not a String" do
+ obj = mock('path')
+ obj.should_receive(:to_path).and_return(Dir.pwd)
+ Dir.chdir(obj)
+ end
+
+ it "prefers #to_path over #to_str" do
+ obj = Class.new do
+ def to_path; Dir.pwd; end
+ def to_str; DirSpecs.mock_dir; end
+ end
+ Dir.chdir(obj.new)
+ Dir.pwd.should == @original
+ end
+
+ it "returns the value of the block when a block is given" do
+ Dir.chdir(@original) { :block_value }.should == :block_value
+ end
+
+ it "defaults to the home directory when given a block but no argument" do
+ # Windows will return a path with forward slashes for ENV["HOME"] so we have
+ # to compare the route representations returned by Dir.chdir.
+ current_dir = ""
+ Dir.chdir { current_dir = Dir.pwd }
+
+ Dir.chdir(ENV['HOME'])
+ home = Dir.pwd
+ current_dir.should == home
+ end
+
+ it "changes to the specified directory for the duration of the block" do
+ ar = Dir.chdir(DirSpecs.mock_dir) { |dir| [dir, Dir.pwd] }
+ ar.should == [DirSpecs.mock_dir, DirSpecs.mock_dir]
+
+ Dir.pwd.should == @original
+ end
+
+ it "raises an Errno::ENOENT if the directory does not exist" do
+ -> { Dir.chdir DirSpecs.nonexistent }.should raise_error(Errno::ENOENT)
+ -> { Dir.chdir(DirSpecs.nonexistent) { } }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises an Errno::ENOENT if the original directory no longer exists" do
+ dir1 = tmp('/testdir1')
+ dir2 = tmp('/testdir2')
+ File.should_not.exist?(dir1)
+ File.should_not.exist?(dir2)
+ Dir.mkdir dir1
+ Dir.mkdir dir2
+ begin
+ -> {
+ Dir.chdir dir1 do
+ Dir.chdir(dir2) { Dir.unlink dir1 }
+ end
+ }.should raise_error(Errno::ENOENT)
+ ensure
+ Dir.unlink dir1 if File.exist?(dir1)
+ Dir.unlink dir2 if File.exist?(dir2)
+ end
+ end
+
+ it "always returns to the original directory when given a block" do
+ begin
+ Dir.chdir(DirSpecs.mock_dir) do
+ raise StandardError, "something bad happened"
+ end
+ rescue StandardError
+ end
+
+ Dir.pwd.should == @original
+ end
+end
diff --git a/spec/ruby/core/dir/children_spec.rb b/spec/ruby/core/dir/children_spec.rb
new file mode 100644
index 0000000000..03698cc246
--- /dev/null
+++ b/spec/ruby/core/dir/children_spec.rb
@@ -0,0 +1,134 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.children" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns an Array of filenames in an existing directory including dotfiles" do
+ a = Dir.children(DirSpecs.mock_dir).sort
+
+ a.should == DirSpecs.expected_paths - %w[. ..]
+
+ a = Dir.children("#{DirSpecs.mock_dir}/deeply/nested").sort
+ a.should == %w|.dotfile.ext directory|
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.children(p)
+ end
+
+ it "accepts an options Hash" do
+ a = Dir.children("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").sort
+ a.should == %w|.dotfile.ext directory|
+ end
+
+ it "returns children encoded with the filesystem encoding by default" do
+ # This spec depends on the locale not being US-ASCII because if it is, the
+ # children that are not ascii_only? will be BINARY encoded.
+ children = Dir.children(File.join(DirSpecs.mock_dir, 'special')).sort
+ encoding = Encoding.find("filesystem")
+ encoding = Encoding::BINARY if encoding == Encoding::US_ASCII
+ platform_is_not :windows do
+ children.should include("ã“ã‚“ã«ã¡ã¯.txt".force_encoding(encoding))
+ end
+ children.first.encoding.should equal(Encoding.find("filesystem"))
+ end
+
+ it "returns children encoded with the specified encoding" do
+ dir = File.join(DirSpecs.mock_dir, 'special')
+ children = Dir.children(dir, encoding: "euc-jp").sort
+ children.first.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "returns children transcoded to the default internal encoding" do
+ Encoding.default_internal = Encoding::EUC_KR
+ children = Dir.children(File.join(DirSpecs.mock_dir, 'special')).sort
+ children.first.encoding.should equal(Encoding::EUC_KR)
+ end
+
+ it "raises a SystemCallError if called with a nonexistent directory" do
+ -> { Dir.children DirSpecs.nonexistent }.should raise_error(SystemCallError)
+ end
+end
+
+describe "Dir#children" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ @dir.close if @dir
+ end
+
+ it "returns an Array of filenames in an existing directory including dotfiles" do
+ @dir = Dir.new(DirSpecs.mock_dir)
+ a = @dir.children.sort
+ @dir.close
+
+ a.should == DirSpecs.expected_paths - %w[. ..]
+
+ @dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested")
+ a = @dir.children.sort
+ a.should == %w|.dotfile.ext directory|
+ end
+
+ it "accepts an encoding keyword for the encoding of the entries" do
+ @dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8")
+ dirs = @dir.to_a.sort
+ dirs.each { |d| d.encoding.should == Encoding::UTF_8 }
+ end
+
+ it "returns children encoded with the filesystem encoding by default" do
+ # This spec depends on the locale not being US-ASCII because if it is, the
+ # children that are not ascii_only? will be BINARY encoded.
+ @dir = Dir.new(File.join(DirSpecs.mock_dir, 'special'))
+ children = @dir.children.sort
+ encoding = Encoding.find("filesystem")
+ encoding = Encoding::BINARY if encoding == Encoding::US_ASCII
+ platform_is_not :windows do
+ children.should include("ã“ã‚“ã«ã¡ã¯.txt".force_encoding(encoding))
+ end
+ children.first.encoding.should equal(Encoding.find("filesystem"))
+ end
+
+ it "returns children encoded with the specified encoding" do
+ path = File.join(DirSpecs.mock_dir, 'special')
+ @dir = Dir.new(path, encoding: "euc-jp")
+ children = @dir.children.sort
+ children.first.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "returns children transcoded to the default internal encoding" do
+ Encoding.default_internal = Encoding::EUC_KR
+ @dir = Dir.new(File.join(DirSpecs.mock_dir, 'special'))
+ children = @dir.children.sort
+ children.first.encoding.should equal(Encoding::EUC_KR)
+ end
+end
diff --git a/spec/ruby/core/dir/chroot_spec.rb b/spec/ruby/core/dir/chroot_spec.rb
new file mode 100644
index 0000000000..a5ca8943fc
--- /dev/null
+++ b/spec/ruby/core/dir/chroot_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/chroot'
+
+platform_is_not :windows do
+ as_superuser do
+ describe "Dir.chroot as root" do
+ it_behaves_like :dir_chroot_as_root, :chroot
+ end
+ end
+
+ platform_is_not :cygwin, :android do
+ as_user do
+ describe "Dir.chroot as regular user" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "raises an Errno::EPERM exception if the directory exists" do
+ -> { Dir.chroot('.') }.should raise_error(Errno::EPERM)
+ end
+
+ it "raises a SystemCallError if the directory doesn't exist" do
+ -> { Dir.chroot('xgwhwhsjai2222jg') }.should raise_error(SystemCallError)
+ end
+
+ it "calls #to_path on non-String argument" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return('.')
+ -> { Dir.chroot(p) }.should raise_error(Errno::EPERM)
+ end
+ end
+ end
+ end
+
+ platform_is :cygwin do
+ as_user do
+ describe "Dir.chroot as regular user" do
+ it_behaves_like :dir_chroot_as_root, :chroot
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/close_spec.rb b/spec/ruby/core/dir/close_spec.rb
new file mode 100644
index 0000000000..5fad5eecfb
--- /dev/null
+++ b/spec/ruby/core/dir/close_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+describe "Dir#close" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "does not raise an IOError even if the Dir instance is closed" do
+ dir = Dir.open DirSpecs.mock_dir
+ dir.close
+ -> {
+ dir.close
+ }.should_not raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/dir/delete_spec.rb b/spec/ruby/core/dir/delete_spec.rb
new file mode 100644
index 0000000000..a0020788ca
--- /dev/null
+++ b/spec/ruby/core/dir/delete_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/delete'
+
+describe "Dir.delete" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_delete, :delete
+end
diff --git a/spec/ruby/core/dir/dir_spec.rb b/spec/ruby/core/dir/dir_spec.rb
new file mode 100644
index 0000000000..7d55ea26d4
--- /dev/null
+++ b/spec/ruby/core/dir/dir_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Dir" do
+ it "includes Enumerable" do
+ Dir.include?(Enumerable).should == true
+ end
+end
diff --git a/spec/ruby/core/dir/each_child_spec.rb b/spec/ruby/core/dir/each_child_spec.rb
new file mode 100644
index 0000000000..520186e79e
--- /dev/null
+++ b/spec/ruby/core/dir/each_child_spec.rb
@@ -0,0 +1,106 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.each_child" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "accepts an encoding keyword for the encoding of the entries" do
+ dirs = Dir.each_child("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort
+ dirs.each {|dir| dir.encoding.should == Encoding::UTF_8}
+ end
+
+ it "yields all names in an existing directory to the provided block" do
+ a, b = [], []
+
+ Dir.each_child(DirSpecs.mock_dir) {|f| a << f}
+ Dir.each_child("#{DirSpecs.mock_dir}/deeply/nested") {|f| b << f}
+
+ a.sort.should == DirSpecs.expected_paths - %w[. ..]
+ b.sort.should == %w|.dotfile.ext directory|
+ end
+
+ it "returns nil when successful" do
+ Dir.each_child(DirSpecs.mock_dir) {|f| f}.should == nil
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.each_child(p).to_a
+ end
+
+ it "raises a SystemCallError if passed a nonexistent directory" do
+ -> { Dir.each_child(DirSpecs.nonexistent) {} }.should raise_error(SystemCallError)
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ Dir.each_child(DirSpecs.mock_dir).should be_an_instance_of(Enumerator)
+ Dir.each_child(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths - %w[. ..]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ Dir.each_child(DirSpecs.mock_dir).size.should == nil
+ end
+ end
+ end
+ end
+end
+
+describe "Dir#each_child" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ after :each do
+ @dir.close if @dir
+ end
+
+ it "yields all names in an existing directory to the provided block" do
+ a, b = [], []
+ @dir = Dir.new(DirSpecs.mock_dir)
+ @dir2 = Dir.new("#{DirSpecs.mock_dir}/deeply/nested")
+
+ @dir.each_child { |f| a << f }
+ @dir2.each_child { |f| b << f }
+ @dir2.close
+
+ a.sort.should == DirSpecs.expected_paths - %w|. ..|
+ b.sort.should == %w|.dotfile.ext directory|
+ end
+
+ it "returns self when successful" do
+ @dir = Dir.new(DirSpecs.mock_dir)
+ @dir.each_child { |f| f }.should == @dir
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ @dir = Dir.new(DirSpecs.mock_dir)
+
+ @dir.each_child.should be_an_instance_of(Enumerator)
+ @dir.each_child.to_a.sort.should == DirSpecs.expected_paths - %w|. ..|
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @dir = Dir.new(DirSpecs.mock_dir)
+ @dir.each_child.size.should == nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/each_spec.rb b/spec/ruby/core/dir/each_spec.rb
new file mode 100644
index 0000000000..8c69a7212b
--- /dev/null
+++ b/spec/ruby/core/dir/each_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/closed'
+
+describe "Dir#each" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ before :each do
+ @dir = Dir.open DirSpecs.mock_dir
+ end
+
+ after :each do
+ @dir.close
+ end
+
+ it "yields each directory entry in succession" do
+ a = []
+ @dir.each {|dir| a << dir}
+
+ a.sort.should == DirSpecs.expected_paths
+ end
+
+ it "returns the directory which remains open" do
+ # an FS does not necessarily impose order
+ ls = Dir.entries(DirSpecs.mock_dir)
+ @dir.each {}.should == @dir
+ @dir.read.should == nil
+ @dir.rewind
+ ls.should include(@dir.read)
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ @dir.each.should be_an_instance_of(Enumerator)
+ @dir.each.to_a.sort.should == DirSpecs.expected_paths
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @dir.each.size.should == nil
+ end
+ end
+ end
+ end
+end
+
+describe "Dir#each" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_closed, :each
+end
diff --git a/spec/ruby/core/dir/element_reference_spec.rb b/spec/ruby/core/dir/element_reference_spec.rb
new file mode 100644
index 0000000000..092114bed4
--- /dev/null
+++ b/spec/ruby/core/dir/element_reference_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/glob'
+
+describe "Dir.[]" do
+ it_behaves_like :dir_glob, :[]
+end
+
+describe "Dir.[]" do
+ it_behaves_like :dir_glob_recursive, :[]
+end
+
+describe "Dir.[]" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ @cwd = Dir.pwd
+ Dir.chdir DirSpecs.mock_dir
+ end
+
+ after :all do
+ Dir.chdir @cwd
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "calls #to_path to convert multiple patterns" do
+ pat1 = mock('file_one.ext')
+ pat1.should_receive(:to_path).and_return('file_one.ext')
+ pat2 = mock('file_two.ext')
+ pat2.should_receive(:to_path).and_return('file_two.ext')
+
+ Dir[pat1, pat2].should == %w[file_one.ext file_two.ext]
+ end
+end
diff --git a/spec/ruby/core/dir/empty_spec.rb b/spec/ruby/core/dir/empty_spec.rb
new file mode 100644
index 0000000000..8cc8757798
--- /dev/null
+++ b/spec/ruby/core/dir/empty_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Dir.empty?" do
+ before :all do
+ @empty_dir = tmp("empty_dir")
+ mkdir_p @empty_dir
+ end
+
+ after :all do
+ rm_r @empty_dir
+ end
+
+ it "returns true for empty directories" do
+ result = Dir.empty? @empty_dir
+ result.should be_true
+ end
+
+ it "returns false for non-empty directories" do
+ result = Dir.empty? __dir__
+ result.should be_false
+ end
+
+ it "returns false for a non-directory" do
+ result = Dir.empty? __FILE__
+ result.should be_false
+ end
+
+ it "raises ENOENT for nonexistent directories" do
+ -> { Dir.empty? tmp("nonexistent") }.should raise_error(Errno::ENOENT)
+ end
+end
diff --git a/spec/ruby/core/dir/entries_spec.rb b/spec/ruby/core/dir/entries_spec.rb
new file mode 100644
index 0000000000..91c30fccae
--- /dev/null
+++ b/spec/ruby/core/dir/entries_spec.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.entries" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns an Array of filenames in an existing directory including dotfiles" do
+ a = Dir.entries(DirSpecs.mock_dir).sort
+
+ a.should == DirSpecs.expected_paths
+
+ a = Dir.entries("#{DirSpecs.mock_dir}/deeply/nested").sort
+ a.should == %w|. .. .dotfile.ext directory|
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.entries(p)
+ end
+
+ it "accepts an encoding keyword for the encoding of the entries" do
+ dirs = Dir.entries("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort
+ dirs.each {|dir| dir.encoding.should == Encoding::UTF_8}
+ end
+
+ it "returns entries encoded with the filesystem encoding by default" do
+ # This spec depends on the locale not being US-ASCII because if it is, the
+ # entries that are not ascii_only? will be BINARY encoded.
+ entries = Dir.entries(File.join(DirSpecs.mock_dir, 'special')).sort
+ encoding = Encoding.find("filesystem")
+ encoding = Encoding::BINARY if encoding == Encoding::US_ASCII
+ platform_is_not :windows do
+ entries.should include("ã“ã‚“ã«ã¡ã¯.txt".force_encoding(encoding))
+ end
+ entries.first.encoding.should equal(Encoding.find("filesystem"))
+ end
+
+ it "returns entries encoded with the specified encoding" do
+ dir = File.join(DirSpecs.mock_dir, 'special')
+ entries = Dir.entries(dir, encoding: "euc-jp").sort
+ entries.first.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "returns entries transcoded to the default internal encoding" do
+ Encoding.default_internal = Encoding::EUC_KR
+ entries = Dir.entries(File.join(DirSpecs.mock_dir, 'special')).sort
+ entries.first.encoding.should equal(Encoding::EUC_KR)
+ end
+
+ it "raises a SystemCallError if called with a nonexistent directory" do
+ -> { Dir.entries DirSpecs.nonexistent }.should raise_error(SystemCallError)
+ end
+end
diff --git a/spec/ruby/core/dir/exist_spec.rb b/spec/ruby/core/dir/exist_spec.rb
new file mode 100644
index 0000000000..43987b0f32
--- /dev/null
+++ b/spec/ruby/core/dir/exist_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/exist'
+
+describe "Dir.exist?" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_exist, :exist?
+end
diff --git a/spec/ruby/core/dir/fileno_spec.rb b/spec/ruby/core/dir/fileno_spec.rb
new file mode 100644
index 0000000000..bb84ef5378
--- /dev/null
+++ b/spec/ruby/core/dir/fileno_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+has_dir_fileno = begin
+ dir = Dir.new('.')
+ dir.fileno
+ true
+rescue NotImplementedError
+ false
+rescue Exception
+ true
+ensure
+ dir.close
+end
+
+describe "Dir#fileno" do
+ before :each do
+ @name = tmp("fileno")
+ mkdir_p @name
+ @dir = Dir.new(@name)
+ end
+
+ after :each do
+ @dir.close
+ rm_r @name
+ end
+
+ if has_dir_fileno
+ it "returns the file descriptor of the dir" do
+ @dir.fileno.should be_kind_of(Integer)
+ end
+ else
+ it "raises an error when not implemented on the platform" do
+ -> { @dir.fileno }.should raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/fixtures/common.rb b/spec/ruby/core/dir/fixtures/common.rb
new file mode 100644
index 0000000000..087f46b331
--- /dev/null
+++ b/spec/ruby/core/dir/fixtures/common.rb
@@ -0,0 +1,204 @@
+# encoding: utf-8
+
+module DirSpecs
+ def self.mock_dir(dirs = ['dir_specs_mock'])
+ @mock_dir ||= tmp("")
+ File.join @mock_dir, dirs
+ end
+
+ def self.nonexistent
+ name = File.join mock_dir, "nonexistent00"
+ name = name.next while File.exist? name
+ name
+ end
+
+ # TODO: make these relative to the mock_dir
+ def self.clear_dirs
+ [ 'nonexisting',
+ 'default_perms',
+ 'reduced',
+ 'always_returns_0',
+ '???',
+ [0xe9].pack('U')
+ ].each do |dir|
+ begin
+ Dir.rmdir mock_dir(dir)
+ rescue
+ end
+ end
+ end
+
+ # The names of the fixture directories and files used by
+ # various Dir specs.
+ def self.mock_dir_files
+ unless @mock_dir_files
+ @mock_dir_files = %w[
+ .dotfile
+ .dotsubdir/.dotfile
+ .dotsubdir/nondotfile
+ nested/.dotsubir/.dotfile
+ nested/.dotsubir/nondotfile
+
+ deeply/.dotfile
+ deeply/nested/.dotfile.ext
+ deeply/nested/directory/structure/.ext
+ deeply/nested/directory/structure/bar
+ deeply/nested/directory/structure/baz
+ deeply/nested/directory/structure/file_one
+ deeply/nested/directory/structure/file_one.ext
+ deeply/nested/directory/structure/foo
+ deeply/nondotfile
+
+ file_one.ext
+ file_two.ext
+
+ dir_filename_ordering
+ dir/filename_ordering
+
+ nondotfile
+
+ subdir_one/.dotfile
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ subdir_two/nondotfile.ext
+
+ brace/a
+ brace/a.js
+ brace/a.erb
+ brace/a.js.rjs
+ brace/a.html.erb
+
+ special/+
+
+ special/^
+ special/$
+
+ special/(
+ special/)
+ special/[
+ special/]
+ special/{
+ special/}
+
+ special/test{1}/file[1]
+ special/{}/special
+ special/test\ +()[]{}/hello_world.erb
+ ]
+
+ platform_is_not :windows do
+ @mock_dir_files += %w[
+ special/*
+ special/?
+
+ special/|
+
+ special/ã“ã‚“ã«ã¡ã¯.txt
+ special/\a
+ ]
+ @mock_dir_files << "special/_\u{1f60e}.erb"
+ end
+ end
+
+ @mock_dir_files
+ end
+
+ def self.mock_dir_links
+ unless @mock_dir_links
+ @mock_dir_links = []
+ platform_is_not :windows do
+ @mock_dir_links += [
+ ['special/ln', 'subdir_one']
+ ]
+ end
+ end
+ @mock_dir_links
+ end
+
+ def self.create_mock_dirs
+ mock_dir_files.each do |name|
+ file = File.join mock_dir, name
+ mkdir_p File.dirname(file)
+ touch file
+ end
+ mock_dir_links.each do |link, target|
+ full_link = File.join mock_dir, link
+ full_target = File.join mock_dir, target
+
+ File.symlink full_target, full_link
+ end
+ end
+
+ def self.delete_mock_dirs
+ begin
+ rm_r mock_dir
+ rescue Errno::ENOTEMPTY => e
+ puts Dir["#{mock_dir}/**/*"]
+ raise e
+ end
+ end
+
+ def self.mock_rmdir(*dirs)
+ mock_dir ['rmdir_dirs'].concat(dirs)
+ end
+
+ def self.rmdir_dirs(create = true)
+ dirs = %w[
+ empty
+ nonempty
+ nonempty/child
+ noperm
+ noperm/child
+ ]
+
+ base_dir = mock_dir ['rmdir_dirs']
+
+ dirs.reverse_each do |d|
+ dir = File.join base_dir, d
+ if File.exist? dir
+ File.chmod 0777, dir
+ rm_r dir
+ end
+ end
+ rm_r base_dir
+
+ if create
+ dirs.each do |d|
+ dir = File.join base_dir, d
+ unless File.exist? dir
+ mkdir_p dir
+ File.chmod 0777, dir
+ end
+ end
+ end
+ end
+
+ def self.expected_paths
+ %w[
+ .
+ ..
+ .dotfile
+ .dotsubdir
+ brace
+ deeply
+ dir
+ dir_filename_ordering
+ file_one.ext
+ file_two.ext
+ nested
+ nondotfile
+ special
+ subdir_one
+ subdir_two
+ ]
+ end
+
+ if RUBY_VERSION > '3.1'
+ def self.expected_glob_paths
+ expected_paths - ['..']
+ end
+ else
+ def self.expected_glob_paths
+ expected_paths
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/foreach_spec.rb b/spec/ruby/core/dir/foreach_spec.rb
new file mode 100644
index 0000000000..9cf34b1d71
--- /dev/null
+++ b/spec/ruby/core/dir/foreach_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.foreach" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "yields all names in an existing directory to the provided block" do
+ a, b = [], []
+
+ Dir.foreach(DirSpecs.mock_dir) {|f| a << f}
+ Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested") {|f| b << f}
+
+ a.sort.should == DirSpecs.expected_paths
+ b.sort.should == %w|. .. .dotfile.ext directory|
+ end
+
+ it "returns nil when successful" do
+ Dir.foreach(DirSpecs.mock_dir) {|f| f}.should == nil
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.foreach(p).to_a
+ end
+
+ it "raises a SystemCallError if passed a nonexistent directory" do
+ -> { Dir.foreach(DirSpecs.nonexistent) {} }.should raise_error(SystemCallError)
+ end
+
+ it "returns an Enumerator if no block given" do
+ Dir.foreach(DirSpecs.mock_dir).should be_an_instance_of(Enumerator)
+ Dir.foreach(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths
+ end
+
+ it "accepts an encoding keyword for the encoding of the entries" do
+ dirs = Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort
+ dirs.each { |dir| dir.encoding.should == Encoding::UTF_8 }
+
+ dirs = Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: Encoding::ISO_8859_1).to_a.sort
+ dirs.each { |dir| dir.encoding.should == Encoding::ISO_8859_1 }
+
+ Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: Encoding::ISO_8859_1) do |f|
+ f.encoding.should == Encoding::ISO_8859_1
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ Dir.foreach(DirSpecs.mock_dir).should be_an_instance_of(Enumerator)
+ Dir.foreach(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ Dir.foreach(DirSpecs.mock_dir).size.should == nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/getwd_spec.rb b/spec/ruby/core/dir/getwd_spec.rb
new file mode 100644
index 0000000000..132634347c
--- /dev/null
+++ b/spec/ruby/core/dir/getwd_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/pwd'
+
+describe "Dir.getwd" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_pwd, :getwd
+end
diff --git a/spec/ruby/core/dir/glob_spec.rb b/spec/ruby/core/dir/glob_spec.rb
new file mode 100644
index 0000000000..06b52b90fb
--- /dev/null
+++ b/spec/ruby/core/dir/glob_spec.rb
@@ -0,0 +1,253 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/glob'
+
+describe "Dir.glob" do
+ it_behaves_like :dir_glob, :glob
+end
+
+describe "Dir.glob" do
+ it_behaves_like :dir_glob_recursive, :glob
+end
+
+describe "Dir.glob" do
+ before :each do
+ DirSpecs.create_mock_dirs
+
+ @cwd = Dir.pwd
+ Dir.chdir DirSpecs.mock_dir
+ end
+
+ after :each do
+ Dir.chdir @cwd
+
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "can take an array of patterns" do
+ Dir.glob(["file_o*", "file_t*"]).should ==
+ %w!file_one.ext file_two.ext!
+ end
+
+ it 'returns matching file paths when supplied :base keyword argument' do
+ dir = tmp('dir_glob_base')
+ file_1 = "#{dir}/lib/bloop.rb"
+ file_2 = "#{dir}/lib/soup.rb"
+ file_3 = "#{dir}/lib/mismatched_file_type.txt"
+ file_4 = "#{dir}/mismatched_directory.rb"
+
+ touch file_1
+ touch file_2
+ touch file_3
+ touch file_4
+
+ Dir.glob('**/*.rb', base: "#{dir}/lib").sort.should == ["bloop.rb", "soup.rb"].sort
+ ensure
+ rm_r dir
+ end
+
+ it "calls #to_path to convert multiple patterns" do
+ pat1 = mock('file_one.ext')
+ pat1.should_receive(:to_path).and_return('file_one.ext')
+ pat2 = mock('file_two.ext')
+ pat2.should_receive(:to_path).and_return('file_two.ext')
+
+ Dir.glob([pat1, pat2]).should == %w[file_one.ext file_two.ext]
+ end
+
+ it "matches both dot and non-dotfiles with '*' and option File::FNM_DOTMATCH" do
+ Dir.glob('*', File::FNM_DOTMATCH).sort.should == DirSpecs.expected_glob_paths
+ end
+
+ it "matches files with any beginning with '*<non-special characters>' and option File::FNM_DOTMATCH" do
+ Dir.glob('*file', File::FNM_DOTMATCH).sort.should == %w|.dotfile nondotfile|.sort
+ end
+
+ it "matches any files in the current directory with '**' and option File::FNM_DOTMATCH" do
+ Dir.glob('**', File::FNM_DOTMATCH).sort.should == DirSpecs.expected_glob_paths
+ end
+
+ it "recursively matches any subdirectories except './' or '../' with '**/' from the current directory and option File::FNM_DOTMATCH" do
+ expected = %w[
+ .dotsubdir/
+ brace/
+ deeply/
+ deeply/nested/
+ deeply/nested/directory/
+ deeply/nested/directory/structure/
+ dir/
+ nested/
+ nested/.dotsubir/
+ special/
+ special/test\ +()[]{}/
+ special/test{1}/
+ special/{}/
+ subdir_one/
+ subdir_two/
+ ]
+
+ Dir.glob('**/', File::FNM_DOTMATCH).sort.should == expected
+ end
+
+ ruby_version_is ''...'3.1' do
+ it "recursively matches files and directories in nested dot subdirectory with 'nested/**/*' from the current directory and option File::FNM_DOTMATCH" do
+ expected = %w[
+ nested/.
+ nested/.dotsubir
+ nested/.dotsubir/.
+ nested/.dotsubir/.dotfile
+ nested/.dotsubir/nondotfile
+ ]
+
+ Dir.glob('nested/**/*', File::FNM_DOTMATCH).sort.should == expected.sort
+ end
+ end
+
+ ruby_version_is '3.1' do
+ it "recursively matches files and directories in nested dot subdirectory except . with 'nested/**/*' from the current directory and option File::FNM_DOTMATCH" do
+ expected = %w[
+ nested/.
+ nested/.dotsubir
+ nested/.dotsubir/.dotfile
+ nested/.dotsubir/nondotfile
+ ]
+
+ Dir.glob('nested/**/*', File::FNM_DOTMATCH).sort.should == expected.sort
+ end
+ end
+
+ # This is a separate case to check **/ coming after a constant
+ # directory as well.
+ it "recursively matches any subdirectories except './' or '../' with '**/' and option File::FNM_DOTMATCH" do
+ expected = %w[
+ ./
+ ./.dotsubdir/
+ ./brace/
+ ./deeply/
+ ./deeply/nested/
+ ./deeply/nested/directory/
+ ./deeply/nested/directory/structure/
+ ./dir/
+ ./nested/
+ ./nested/.dotsubir/
+ ./special/
+ ./special/test\ +()[]{}/
+ ./special/test{1}/
+ ./special/{}/
+ ./subdir_one/
+ ./subdir_two/
+ ]
+
+ Dir.glob('./**/', File::FNM_DOTMATCH).sort.should == expected
+ end
+
+ it "matches a list of paths by concatenating their individual results" do
+ expected = %w[
+ deeply/
+ deeply/nested/
+ deeply/nested/directory/
+ deeply/nested/directory/structure/
+ subdir_two/nondotfile
+ subdir_two/nondotfile.ext
+ ]
+
+ Dir.glob('{deeply/**/,subdir_two/*}').sort.should == expected
+ end
+
+ it "preserves multiple /s before a **" do
+ expected = %w[
+ deeply//nested/directory/structure
+ ]
+
+ Dir.glob('{deeply//**/structure}').sort.should == expected
+ end
+
+ it "accepts a block and yields it with each elements" do
+ ary = []
+ ret = Dir.glob(["file_o*", "file_t*"]) { |t| ary << t }
+ ret.should be_nil
+ ary.should == %w!file_one.ext file_two.ext!
+ end
+
+ it "ignores non-dirs when traversing recursively" do
+ touch "spec"
+ Dir.glob("spec/**/*.rb").should == []
+ end
+
+ it "matches nothing when given an empty list of paths" do
+ Dir.glob('{}').should == []
+ end
+
+ it "handles infinite directory wildcards" do
+ Dir.glob('**/**/**').should_not.empty?
+ end
+
+ it "handles simple filename patterns" do
+ Dir.glob('.dotfile').should == ['.dotfile']
+ end
+
+ it "handles simple directory patterns" do
+ Dir.glob('.dotsubdir/').should == ['.dotsubdir/']
+ end
+
+ it "handles simple directory patterns applied to non-directories" do
+ Dir.glob('nondotfile/').should == []
+ end
+
+ platform_is_not(:windows) do
+ it "matches the literal character '\\' with option File::FNM_NOESCAPE" do
+ Dir.mkdir 'foo?bar'
+
+ begin
+ Dir.glob('foo?bar', File::FNM_NOESCAPE).should == %w|foo?bar|
+ Dir.glob('foo\?bar', File::FNM_NOESCAPE).should == []
+ ensure
+ Dir.rmdir 'foo?bar'
+ end
+
+ Dir.mkdir 'foo\?bar'
+
+ begin
+ Dir.glob('foo\?bar', File::FNM_NOESCAPE).should == %w|foo\\?bar|
+ ensure
+ Dir.rmdir 'foo\?bar'
+ end
+ end
+
+ it "returns nil for directories current user has no permission to read" do
+ Dir.mkdir('no_permission')
+ File.chmod(0, 'no_permission')
+
+ begin
+ Dir.glob('no_permission/*').should == []
+ ensure
+ Dir.rmdir('no_permission')
+ end
+ end
+
+ it "will follow symlinks when processing a `*/` pattern." do
+ expected = ['special/ln/nondotfile']
+ Dir.glob('special/*/nondotfile').should == expected
+ end
+
+ it "will not follow symlinks when recursively traversing directories" do
+ expected = %w[
+ deeply/nondotfile
+ nondotfile
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ ]
+ Dir.glob('**/nondotfile').sort.should == expected
+ end
+
+ it "will follow symlinks when testing directory after recursive directory in pattern" do
+ expected = %w[
+ deeply/nondotfile
+ special/ln/nondotfile
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ ]
+ Dir.glob('**/*/nondotfile').sort.should == expected
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/home_spec.rb b/spec/ruby/core/dir/home_spec.rb
new file mode 100644
index 0000000000..bbe347ba9e
--- /dev/null
+++ b/spec/ruby/core/dir/home_spec.rb
@@ -0,0 +1,88 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.home" do
+ before :each do
+ @home = ENV['HOME']
+ ENV['HOME'] = "/rubyspec_home"
+ end
+
+ after :each do
+ ENV['HOME'] = @home
+ end
+
+ describe "when called without arguments" do
+ it "returns the current user's home directory, reading $HOME first" do
+ Dir.home.should == "/rubyspec_home"
+ end
+
+ it "returns a non-frozen string" do
+ Dir.home.should_not.frozen?
+ end
+
+ it "returns a string with the filesystem encoding" do
+ Dir.home.encoding.should == Encoding.find("filesystem")
+ end
+
+ platform_is_not :windows do
+ it "works even if HOME is unset" do
+ ENV.delete('HOME')
+ Dir.home.should.start_with?('/')
+ Dir.home.encoding.should == Encoding.find("filesystem")
+ end
+ end
+
+ platform_is :windows do
+ ruby_version_is "3.2" do
+ it "returns the home directory with forward slashs and as UTF-8" do
+ ENV['HOME'] = "C:\\rubyspäc\\home"
+ home = Dir.home
+ home.should == "C:/rubyspäc/home"
+ home.encoding.should == Encoding::UTF_8
+ end
+ end
+
+ it "retrieves the directory from HOME, USERPROFILE, HOMEDRIVE/HOMEPATH and the WinAPI in that order" do
+ old_dirs = [ENV.delete('HOME'), ENV.delete('USERPROFILE'), ENV.delete('HOMEDRIVE'), ENV.delete('HOMEPATH')]
+
+ Dir.home.should == old_dirs[1].gsub("\\", "/")
+ ENV['HOMEDRIVE'] = "C:"
+ ENV['HOMEPATH'] = "\\rubyspec\\home1"
+ Dir.home.should == "C:/rubyspec/home1"
+ ENV['USERPROFILE'] = "C:\\rubyspec\\home2"
+ # https://bugs.ruby-lang.org/issues/19244
+ # Dir.home.should == "C:/rubyspec/home2"
+ ENV['HOME'] = "C:\\rubyspec\\home3"
+ Dir.home.should == "C:/rubyspec/home3"
+ ensure
+ ENV['HOME'], ENV['USERPROFILE'], ENV['HOMEDRIVE'], ENV['HOMEPATH'] = *old_dirs
+ end
+ end
+ end
+
+ describe "when called with the current user name" do
+ platform_is :solaris do
+ it "returns the named user's home directory from the user database" do
+ Dir.home(ENV['USER']).should == `getent passwd #{ENV['USER']}|cut -d: -f6`.chomp
+ end
+ end
+
+ platform_is_not :windows, :solaris, :android, :wasi do
+ it "returns the named user's home directory, from the user database" do
+ Dir.home(ENV['USER']).should == `echo ~#{ENV['USER']}`.chomp
+ end
+ end
+
+ it "returns a non-frozen string" do
+ Dir.home(ENV['USER']).should_not.frozen?
+ end
+
+ it "returns a string with the filesystem encoding" do
+ Dir.home(ENV['USER']).encoding.should == Encoding.find("filesystem")
+ end
+ end
+
+ it "raises an ArgumentError if the named user doesn't exist" do
+ -> { Dir.home('geuw2n288dh2k') }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/dir/initialize_spec.rb b/spec/ruby/core/dir/initialize_spec.rb
new file mode 100644
index 0000000000..547b7dc18e
--- /dev/null
+++ b/spec/ruby/core/dir/initialize_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir#initialize" do
+ before :each do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :each do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.stub!(:to_path).and_return(DirSpecs.mock_dir)
+ dir = Dir.new(p)
+ begin
+ dir.path.should == DirSpecs.mock_dir
+ ensure
+ dir.close
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/inspect_spec.rb b/spec/ruby/core/dir/inspect_spec.rb
new file mode 100644
index 0000000000..37338a97d4
--- /dev/null
+++ b/spec/ruby/core/dir/inspect_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir#inspect" do
+ before :each do
+ @dir = Dir.new(Dir.getwd)
+ end
+
+ after :each do
+ @dir.close
+ end
+
+ it "returns a String" do
+ @dir.inspect.should be_an_instance_of(String)
+ end
+
+ it "includes the class name" do
+ @dir.inspect.should =~ /Dir/
+ end
+
+ it "includes the directory name" do
+ @dir.inspect.should include(Dir.getwd)
+ end
+end
diff --git a/spec/ruby/core/dir/mkdir_spec.rb b/spec/ruby/core/dir/mkdir_spec.rb
new file mode 100644
index 0000000000..076ec19dd9
--- /dev/null
+++ b/spec/ruby/core/dir/mkdir_spec.rb
@@ -0,0 +1,107 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.mkdir" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "creates the named directory with the given permissions" do
+ DirSpecs.clear_dirs
+
+ nonexisting = DirSpecs.mock_dir('nonexisting')
+ default_perms = DirSpecs.mock_dir('default_perms')
+ reduced = DirSpecs.mock_dir('reduced')
+ begin
+ File.should_not.exist?(nonexisting)
+ Dir.mkdir nonexisting
+ File.should.exist?(nonexisting)
+ platform_is_not :windows do
+ Dir.mkdir default_perms
+ a = File.stat(default_perms).mode
+ Dir.mkdir reduced, (a - 1)
+ File.stat(reduced).mode.should_not == a
+ end
+ platform_is :windows do
+ Dir.mkdir default_perms, 0666
+ a = File.stat(default_perms).mode
+ Dir.mkdir reduced, 0444
+ File.stat(reduced).mode.should_not == a
+ end
+
+ always_returns_0 = DirSpecs.mock_dir('always_returns_0')
+ Dir.mkdir(always_returns_0).should == 0
+ platform_is_not(:windows) do
+ File.chmod(0777, nonexisting, default_perms, reduced, always_returns_0)
+ end
+ platform_is_not(:windows) do
+ File.chmod(0644, nonexisting, default_perms, reduced, always_returns_0)
+ end
+ ensure
+ DirSpecs.clear_dirs
+ end
+ end
+
+ it "calls #to_path on non-String path arguments" do
+ DirSpecs.clear_dirs
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir('nonexisting'))
+ Dir.mkdir(p)
+ DirSpecs.clear_dirs
+ end
+
+ it "calls #to_int on non-Integer permissions argument" do
+ DirSpecs.clear_dirs
+ path = DirSpecs.mock_dir('nonexisting')
+ permissions = mock('permissions')
+ permissions.should_receive(:to_int).and_return(0666)
+ Dir.mkdir(path, permissions)
+ DirSpecs.clear_dirs
+ end
+
+ it "raises TypeError if non-Integer permissions argument does not have #to_int method" do
+ path = DirSpecs.mock_dir('nonexisting')
+ permissions = Object.new
+
+ -> { Dir.mkdir(path, permissions) }.should raise_error(TypeError, 'no implicit conversion of Object into Integer')
+ end
+
+ it "raises a SystemCallError if any of the directories in the path before the last does not exist" do
+ -> { Dir.mkdir "#{DirSpecs.nonexistent}/subdir" }.should raise_error(SystemCallError)
+ end
+
+ it "raises Errno::EEXIST if the specified directory already exists" do
+ -> { Dir.mkdir("#{DirSpecs.mock_dir}/dir") }.should raise_error(Errno::EEXIST)
+ end
+
+ it "raises Errno::EEXIST if the argument points to the existing file" do
+ -> { Dir.mkdir("#{DirSpecs.mock_dir}/file_one.ext") }.should raise_error(Errno::EEXIST)
+ end
+end
+
+# The permissions flag are not supported on Windows as stated in documentation:
+# The permissions may be modified by the value of File.umask, and are ignored on NT.
+platform_is_not :windows do
+ as_user do
+ describe "Dir.mkdir" do
+ before :each do
+ @dir = tmp "noperms"
+ end
+
+ after :each do
+ File.chmod 0777, @dir
+ rm_r @dir
+ end
+
+ it "raises a SystemCallError when lacking adequate permissions in the parent dir" do
+ Dir.mkdir @dir, 0000
+
+ -> { Dir.mkdir "#{@dir}/subdir" }.should raise_error(SystemCallError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/open_spec.rb b/spec/ruby/core/dir/open_spec.rb
new file mode 100644
index 0000000000..27f362320b
--- /dev/null
+++ b/spec/ruby/core/dir/open_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/open'
+
+describe "Dir.open" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_open, :open
+end
diff --git a/spec/ruby/core/dir/path_spec.rb b/spec/ruby/core/dir/path_spec.rb
new file mode 100644
index 0000000000..b1c24c406b
--- /dev/null
+++ b/spec/ruby/core/dir/path_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/path'
+
+describe "Dir#path" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_path, :path
+end
diff --git a/spec/ruby/core/dir/pos_spec.rb b/spec/ruby/core/dir/pos_spec.rb
new file mode 100644
index 0000000000..b382bff81f
--- /dev/null
+++ b/spec/ruby/core/dir/pos_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/closed'
+require_relative 'shared/pos'
+
+describe "Dir#pos" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_pos, :pos
+end
+
+describe "Dir#pos" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_closed, :pos
+end
+
+describe "Dir#pos=" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_pos_set, :pos=
+end
diff --git a/spec/ruby/core/dir/pwd_spec.rb b/spec/ruby/core/dir/pwd_spec.rb
new file mode 100644
index 0000000000..ad01286c90
--- /dev/null
+++ b/spec/ruby/core/dir/pwd_spec.rb
@@ -0,0 +1,39 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/pwd'
+
+describe "Dir.pwd" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_pwd, :pwd
+end
+
+describe "Dir.pwd" do
+ before :each do
+ @name = tmp("ã‚").force_encoding('binary')
+ @fs_encoding = Encoding.find('filesystem')
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ platform_is_not :windows do
+ it "correctly handles dirs with unicode characters in them" do
+ Dir.mkdir @name
+ Dir.chdir @name do
+ if @fs_encoding == Encoding::UTF_8
+ Dir.pwd.encoding.should == Encoding::UTF_8
+ end
+ Dir.pwd.force_encoding('binary').should == @name
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/read_spec.rb b/spec/ruby/core/dir/read_spec.rb
new file mode 100644
index 0000000000..276930c6b7
--- /dev/null
+++ b/spec/ruby/core/dir/read_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/closed'
+
+describe "Dir#read" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "returns the file name in the current seek position" do
+ # an FS does not necessarily impose order
+ ls = Dir.entries DirSpecs.mock_dir
+ dir = Dir.open DirSpecs.mock_dir
+ ls.should include(dir.read)
+ dir.close
+ end
+
+ it "returns nil when there are no more entries" do
+ dir = Dir.open DirSpecs.mock_dir
+ DirSpecs.expected_paths.size.times do
+ dir.read.should_not == nil
+ end
+ dir.read.should == nil
+ dir.close
+ end
+
+ it "returns each entry successively" do
+ dir = Dir.open DirSpecs.mock_dir
+ entries = []
+ while entry = dir.read
+ entries << entry
+ end
+ dir.close
+
+ entries.sort.should == DirSpecs.expected_paths
+ end
+
+ platform_is_not :windows do
+ it "returns all directory entries even when encoding conversion will fail" do
+ dir = Dir.open(File.join(DirSpecs.mock_dir, 'special'))
+ utf8_entries = []
+ begin
+ while entry = dir.read
+ utf8_entries << entry
+ end
+ ensure
+ dir.close
+ end
+ old_internal_encoding = Encoding::default_internal
+ old_external_encoding = Encoding::default_external
+ Encoding.default_internal = Encoding::UTF_8
+ Encoding.default_external = Encoding::SHIFT_JIS
+ shift_jis_entries = []
+ begin
+ Dir.open(File.join(DirSpecs.mock_dir, 'special')) do |d|
+ -> {
+ while entry = d.read
+ shift_jis_entries << entry
+ end
+ }.should_not raise_error
+ end
+ ensure
+ Encoding.default_internal = old_internal_encoding
+ Encoding.default_external = old_external_encoding
+ end
+ shift_jis_entries.size.should == utf8_entries.size
+ shift_jis_entries.filter { |f| f.encoding == Encoding::SHIFT_JIS }.size.should == 1
+ end
+ end
+
+ it_behaves_like :dir_closed, :read
+end
diff --git a/spec/ruby/core/dir/rewind_spec.rb b/spec/ruby/core/dir/rewind_spec.rb
new file mode 100644
index 0000000000..220d7f5372
--- /dev/null
+++ b/spec/ruby/core/dir/rewind_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/closed'
+
+describe "Dir#rewind" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ before :each do
+ @dir = Dir.open DirSpecs.mock_dir
+ end
+
+ after :each do
+ @dir.close
+ end
+
+ it "resets the next read to start from the first entry" do
+ a = @dir.read
+ b = @dir.read
+ a.should_not == b
+ @dir.rewind
+ c = @dir.read
+ c.should == a
+ end
+
+ it "returns the Dir instance" do
+ @dir.rewind.should == @dir
+ end
+
+ it_behaves_like :dir_closed, :rewind
+end
diff --git a/spec/ruby/core/dir/rmdir_spec.rb b/spec/ruby/core/dir/rmdir_spec.rb
new file mode 100644
index 0000000000..08cd1a5bc6
--- /dev/null
+++ b/spec/ruby/core/dir/rmdir_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/delete'
+
+describe "Dir.rmdir" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_delete, :rmdir
+end
diff --git a/spec/ruby/core/dir/seek_spec.rb b/spec/ruby/core/dir/seek_spec.rb
new file mode 100644
index 0000000000..ed409897cd
--- /dev/null
+++ b/spec/ruby/core/dir/seek_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/pos'
+
+describe "Dir#seek" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "returns the Dir instance" do
+ @dir.seek(@dir.pos).should == @dir
+ end
+
+ it_behaves_like :dir_pos_set, :seek
+end
diff --git a/spec/ruby/core/dir/shared/chroot.rb b/spec/ruby/core/dir/shared/chroot.rb
new file mode 100644
index 0000000000..8c0599fe3f
--- /dev/null
+++ b/spec/ruby/core/dir/shared/chroot.rb
@@ -0,0 +1,44 @@
+describe :dir_chroot_as_root, shared: true do
+ before :all do
+ DirSpecs.create_mock_dirs
+
+ @real_root = "../" * (File.dirname(__FILE__).count('/') - 1)
+ @ref_dir = File.join("/", File.basename(Dir["/*"].first))
+ end
+
+ after :all do
+ until File.exist?(@ref_dir)
+ Dir.send(@method, "../") or break
+ end
+
+ DirSpecs.delete_mock_dirs
+ end
+
+ # Pending until https://github.com/ruby/ruby/runs/8075149420 is fixed
+ compilations_ci = ENV["GITHUB_WORKFLOW"] == "Compilations"
+
+ it "can be used to change the process' root directory" do
+ -> { Dir.send(@method, File.dirname(__FILE__)) }.should_not raise_error
+ File.should.exist?("/#{File.basename(__FILE__)}")
+ end unless compilations_ci
+
+ it "returns 0 if successful" do
+ Dir.send(@method, '/').should == 0
+ end
+
+ it "raises an Errno::ENOENT exception if the directory doesn't exist" do
+ -> { Dir.send(@method, 'xgwhwhsjai2222jg') }.should raise_error(Errno::ENOENT)
+ end
+
+ it "can be escaped from with ../" do
+ Dir.send(@method, @real_root)
+ File.should.exist?(@ref_dir)
+ File.should_not.exist?("/#{File.basename(__FILE__)}")
+ end unless compilations_ci
+
+ it "calls #to_path on non-String argument" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(@real_root)
+ Dir.send(@method, p)
+ end
+end
diff --git a/spec/ruby/core/dir/shared/closed.rb b/spec/ruby/core/dir/shared/closed.rb
new file mode 100644
index 0000000000..17d8332c2a
--- /dev/null
+++ b/spec/ruby/core/dir/shared/closed.rb
@@ -0,0 +1,9 @@
+describe :dir_closed, shared: true do
+ it "raises an IOError when called on a closed Dir instance" do
+ -> {
+ dir = Dir.open DirSpecs.mock_dir
+ dir.close
+ dir.send(@method) {}
+ }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/dir/shared/delete.rb b/spec/ruby/core/dir/shared/delete.rb
new file mode 100644
index 0000000000..49e88360e8
--- /dev/null
+++ b/spec/ruby/core/dir/shared/delete.rb
@@ -0,0 +1,63 @@
+describe :dir_delete, shared: true do
+ before :each do
+ DirSpecs.rmdir_dirs true
+ end
+
+ after :each do
+ DirSpecs.rmdir_dirs false
+ end
+
+ it "removes empty directories" do
+ Dir.send(@method, DirSpecs.mock_rmdir("empty")).should == 0
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_rmdir("empty"))
+ Dir.send(@method, p)
+ end
+
+ platform_is_not :solaris do
+ it "raises an Errno::ENOTEMPTY when trying to remove a nonempty directory" do
+ -> do
+ Dir.send @method, DirSpecs.mock_rmdir("nonempty")
+ end.should raise_error(Errno::ENOTEMPTY)
+ end
+ end
+
+ platform_is :solaris do
+ it "raises an Errno::EEXIST when trying to remove a nonempty directory" do
+ -> do
+ Dir.send @method, DirSpecs.mock_rmdir("nonempty")
+ end.should raise_error(Errno::EEXIST)
+ end
+ end
+
+ it "raises an Errno::ENOENT when trying to remove a non-existing directory" do
+ -> do
+ Dir.send @method, DirSpecs.nonexistent
+ end.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises an Errno::ENOTDIR when trying to remove a non-directory" do
+ file = DirSpecs.mock_rmdir("nonempty/regular")
+ touch(file)
+ -> do
+ Dir.send @method, file
+ end.should raise_error(Errno::ENOTDIR)
+ end
+
+ # this won't work on Windows, since chmod(0000) does not remove all permissions
+ platform_is_not :windows do
+ as_user do
+ it "raises an Errno::EACCES if lacking adequate permissions to remove the directory" do
+ parent = DirSpecs.mock_rmdir("noperm")
+ child = DirSpecs.mock_rmdir("noperm", "child")
+ File.chmod(0000, parent)
+ -> do
+ Dir.send @method, child
+ end.should raise_error(Errno::EACCES)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/shared/exist.rb b/spec/ruby/core/dir/shared/exist.rb
new file mode 100644
index 0000000000..765d1b656c
--- /dev/null
+++ b/spec/ruby/core/dir/shared/exist.rb
@@ -0,0 +1,56 @@
+describe :dir_exist, shared: true do
+ it "returns true if the given directory exists" do
+ Dir.send(@method, File.dirname(__FILE__)).should be_true
+ end
+
+ it "returns true for '.'" do
+ Dir.send(@method, '.').should be_true
+ end
+
+ it "returns true for '..'" do
+ Dir.send(@method, '..').should be_true
+ end
+
+ it "understands non-ASCII paths" do
+ subdir = File.join(tmp("\u{9876}\u{665}"))
+ Dir.send(@method, subdir).should be_false
+ Dir.mkdir(subdir)
+ Dir.send(@method, subdir).should be_true
+ Dir.rmdir(subdir)
+ end
+
+ it "understands relative paths" do
+ Dir.send(@method, File.dirname(__FILE__) + '/../').should be_true
+ end
+
+ it "returns false if the given directory doesn't exist" do
+ Dir.send(@method, 'y26dg27n2nwjs8a/').should be_false
+ end
+
+ it "doesn't require the name to have a trailing slash" do
+ dir = File.dirname(__FILE__)
+ dir.sub!(/\/$/,'')
+ Dir.send(@method, dir).should be_true
+ end
+
+ it "doesn't expand paths" do
+ Dir.send(@method, File.expand_path('~')).should be_true
+ Dir.send(@method, '~').should be_false
+ end
+
+ it "returns false if the argument exists but is a file" do
+ File.should.exist?(__FILE__)
+ Dir.send(@method, __FILE__).should be_false
+ end
+
+ it "doesn't set $! when file doesn't exist" do
+ Dir.send(@method, "/path/to/non/existent/dir")
+ $!.should be_nil
+ end
+
+ it "calls #to_path on non String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(File.dirname(__FILE__))
+ Dir.send(@method, p)
+ end
+end
diff --git a/spec/ruby/core/dir/shared/glob.rb b/spec/ruby/core/dir/shared/glob.rb
new file mode 100644
index 0000000000..33b2828c27
--- /dev/null
+++ b/spec/ruby/core/dir/shared/glob.rb
@@ -0,0 +1,484 @@
+# -*- encoding: utf-8 -*-
+describe :dir_glob, shared: true do
+ before :all do
+ DirSpecs.create_mock_dirs
+ @cwd = Dir.pwd
+ Dir.chdir DirSpecs.mock_dir
+ end
+
+ after :all do
+ Dir.chdir @cwd
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "raises an Encoding::CompatibilityError if the argument encoding is not compatible with US-ASCII" do
+ pattern = "file*".force_encoding Encoding::UTF_16BE
+ -> { Dir.send(@method, pattern) }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "calls #to_path to convert a pattern" do
+ obj = mock('file_one.ext')
+ obj.should_receive(:to_path).and_return('file_one.ext')
+
+ Dir.send(@method, obj).should == %w[file_one.ext]
+ end
+
+ it "raises an ArgumentError if the string contains \\0" do
+ -> {Dir.send(@method, "file_o*\0file_t*")}.should raise_error ArgumentError, /nul-separated/
+ end
+
+ ruby_version_is "3.0" do
+ it "result is sorted by default" do
+ result = Dir.send(@method, '*')
+ result.should == result.sort
+ end
+
+ it "result is sorted with sort: true" do
+ result = Dir.send(@method, '*', sort: true)
+ result.should == result.sort
+ end
+
+ it "sort: false returns same files" do
+ result = Dir.send(@method,'*', sort: false)
+ result.sort.should == Dir.send(@method, '*').sort
+ end
+ end
+
+ ruby_version_is "3.0"..."3.1" do
+ it "result is sorted with any non false value of sort:" do
+ result = Dir.send(@method, '*', sort: 0)
+ result.should == result.sort
+
+ result = Dir.send(@method, '*', sort: nil)
+ result.should == result.sort
+
+ result = Dir.send(@method, '*', sort: 'false')
+ result.should == result.sort
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "raises an ArgumentError if sort: is not true or false" do
+ -> { Dir.send(@method, '*', sort: 0) }.should raise_error ArgumentError, /expected true or false/
+ -> { Dir.send(@method, '*', sort: nil) }.should raise_error ArgumentError, /expected true or false/
+ -> { Dir.send(@method, '*', sort: 'false') }.should raise_error ArgumentError, /expected true or false/
+ end
+ end
+
+ it "matches non-dotfiles with '*'" do
+ expected = %w[
+ brace
+ deeply
+ dir
+ dir_filename_ordering
+ file_one.ext
+ file_two.ext
+ nested
+ nondotfile
+ special
+ subdir_one
+ subdir_two
+ ]
+
+ Dir.send(@method,'*').sort.should == expected
+ end
+
+ it "returns empty array when empty pattern provided" do
+ Dir.send(@method, '').should == []
+ end
+
+ it "matches regexp special +" do
+ Dir.send(@method, 'special/+').should == ['special/+']
+ end
+
+ it "matches directories with special characters when escaped" do
+ Dir.send(@method, 'special/\{}/special').should == ["special/{}/special"]
+ end
+
+ platform_is_not :windows do
+ it "matches regexp special *" do
+ Dir.send(@method, 'special/\*').should == ['special/*']
+ end
+
+ it "matches regexp special ?" do
+ Dir.send(@method, 'special/\?').should == ['special/?']
+ end
+
+ it "matches regexp special |" do
+ Dir.send(@method, 'special/|').should == ['special/|']
+ end
+
+ it "matches files with backslashes in their name" do
+ Dir.glob('special/\\\\{a,b}').should == ['special/\a']
+ end
+
+ it "matches directory with special characters in their name in complex patterns" do
+ Dir.glob("special/test +()\\[\\]\\{\\}/hello_world{.{en},}{.{html},}{+{phone},}{.{erb},}").should == ['special/test +()[]{}/hello_world.erb']
+ end
+ end
+
+ it "matches regexp special ^" do
+ Dir.send(@method, 'special/^').should == ['special/^']
+ end
+
+ it "matches regexp special $" do
+ Dir.send(@method, 'special/$').should == ['special/$']
+ end
+
+ it "matches regexp special (" do
+ Dir.send(@method, 'special/(').should == ['special/(']
+ end
+
+ it "matches regexp special )" do
+ Dir.send(@method, 'special/)').should == ['special/)']
+ end
+
+ it "matches regexp special [" do
+ Dir.send(@method, 'special/\[').should == ['special/[']
+ end
+
+ it "matches regexp special ]" do
+ Dir.send(@method, 'special/]').should == ['special/]']
+ end
+
+ it "matches regexp special {" do
+ Dir.send(@method, 'special/\{').should == ['special/{']
+ end
+
+ it "matches regexp special }" do
+ Dir.send(@method, 'special/\}').should == ['special/}']
+ end
+
+ it "matches paths with glob patterns" do
+ Dir.send(@method, 'special/test\{1\}/*').should == ['special/test{1}/file[1]']
+ end
+
+ ruby_version_is ''...'3.1' do
+ it "matches dotfiles with '.*'" do
+ Dir.send(@method, '.*').sort.should == %w|. .. .dotfile .dotsubdir|.sort
+ end
+ end
+
+ ruby_version_is '3.1' do
+ it "matches dotfiles except .. with '.*'" do
+ Dir.send(@method, '.*').sort.should == %w|. .dotfile .dotsubdir|.sort
+ end
+ end
+
+ it "matches non-dotfiles with '*<non-special characters>'" do
+ Dir.send(@method, '*file').sort.should == %w|nondotfile|.sort
+ end
+
+ it "matches dotfiles with '.*<non-special characters>'" do
+ Dir.send(@method, '.*file').sort.should == %w|.dotfile|.sort
+ end
+
+ it "matches files with any ending with '<non-special characters>*'" do
+ Dir.send(@method, 'file*').sort.should == %w|file_one.ext file_two.ext|.sort
+ end
+
+ it "matches files with any middle with '<non-special characters>*<non-special characters>'" do
+ Dir.send(@method, 'sub*_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "handles directories with globs" do
+ Dir.send(@method, 'sub*/*').sort.should == %w!subdir_one/nondotfile subdir_two/nondotfile subdir_two/nondotfile.ext!
+ end
+
+ it "matches files with multiple '*' special characters" do
+ Dir.send(@method, '*fi*e*').sort.should == %w|dir_filename_ordering nondotfile file_one.ext file_two.ext|.sort
+ end
+
+ it "matches non-dotfiles in the current directory with '**'" do
+ expected = %w[
+ brace
+ deeply
+ dir
+ dir_filename_ordering
+ file_one.ext
+ file_two.ext
+ nested
+ nondotfile
+ special
+ subdir_one
+ subdir_two
+ ]
+
+ Dir.send(@method, '**').sort.should == expected
+ end
+
+ ruby_version_is ''...'3.1' do
+ it "matches dotfiles in the current directory with '.**'" do
+ Dir.send(@method, '.**').sort.should == %w|. .. .dotsubdir .dotfile|.sort
+ end
+ end
+
+ ruby_version_is '3.1' do
+ it "matches dotfiles in the current directory except .. with '.**'" do
+ Dir.send(@method, '.**').sort.should == %w|. .dotsubdir .dotfile|.sort
+ end
+ end
+
+ it "recursively matches any nondot subdirectories with '**/'" do
+ expected = %w[
+ brace/
+ deeply/
+ deeply/nested/
+ deeply/nested/directory/
+ deeply/nested/directory/structure/
+ dir/
+ nested/
+ special/
+ special/test\ +()[]{}/
+ special/test{1}/
+ special/{}/
+ subdir_one/
+ subdir_two/
+ ]
+
+ Dir.send(@method, '**/').sort.should == expected
+ end
+
+ it "recursively matches any subdirectories except './' or '../' with '**/' from the base directory if that is specified" do
+ expected = %w[
+ nested/directory
+ ]
+
+ Dir.send(@method, '**/*ory', base: 'deeply').sort.should == expected
+ end
+
+ ruby_version_is ''...'3.1' do
+ it "recursively matches any subdirectories including ./ and ../ with '.**/'" do
+ Dir.chdir("#{DirSpecs.mock_dir}/subdir_one") do
+ Dir.send(@method, '.**/').sort.should == %w|./ ../|.sort
+ end
+ end
+ end
+
+ ruby_version_is '3.1' do
+ it "recursively matches any subdirectories including ./ with '.**/'" do
+ Dir.chdir("#{DirSpecs.mock_dir}/subdir_one") do
+ Dir.send(@method, '.**/').should == ['./']
+ end
+ end
+ end
+
+ it "matches a single character except leading '.' with '?'" do
+ Dir.send(@method, '?ubdir_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "accepts multiple '?' characters in a pattern" do
+ Dir.send(@method, 'subdir_???').sort.should == %w|subdir_one subdir_two|.sort
+ end
+
+ it "matches any characters in a set with '[<characters>]'" do
+ Dir.send(@method, '[stfu]ubdir_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "matches any characters in a range with '[<character>-<character>]'" do
+ Dir.send(@method, '[a-zA-Z]ubdir_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "matches any characters except those in a set with '[^<characters>]'" do
+ Dir.send(@method, '[^wtf]ubdir_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "matches any characters except those in a range with '[^<character>-<character]'" do
+ Dir.send(@method, '[^0-9]ubdir_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "matches any one of the strings in a set with '{<string>,<other>,...}'" do
+ Dir.send(@method, 'subdir_{one,two,three}').sort.should == %w|subdir_one subdir_two|.sort
+ end
+
+ it "matches a set '{<string>,<other>,...}' which also uses a glob" do
+ Dir.send(@method, 'sub*_{one,two,three}').sort.should == %w|subdir_one subdir_two|.sort
+ end
+
+ it "accepts string sets with empty strings with {<string>,,<other>}" do
+ a = Dir.send(@method, 'deeply/nested/directory/structure/file_one{.ext,}').sort
+ a.should == %w|deeply/nested/directory/structure/file_one.ext
+ deeply/nested/directory/structure/file_one|.sort
+ end
+
+ it "matches dot or non-dotfiles with '{,.}*'" do
+ Dir.send(@method, '{,.}*').sort.should == DirSpecs.expected_glob_paths
+ end
+
+ it "respects the order of {} expressions, expanding left most first" do
+ files = Dir.send(@method, "brace/a{.js,.html}{.erb,.rjs}")
+ files.should == %w!brace/a.js.rjs brace/a.html.erb!
+ end
+
+ it "respects the optional nested {} expressions" do
+ files = Dir.send(@method, "brace/a{.{js,html},}{.{erb,rjs},}")
+ files.should == %w!brace/a.js.rjs brace/a.js brace/a.html.erb brace/a.erb brace/a!
+ end
+
+ it "matches special characters by escaping with a backslash with '\\<character>'" do
+ Dir.mkdir 'foo^bar'
+
+ begin
+ Dir.send(@method, 'foo?bar').should == %w|foo^bar|
+ Dir.send(@method, 'foo\?bar').should == []
+ Dir.send(@method, 'nond\otfile').should == %w|nondotfile|
+ ensure
+ Dir.rmdir 'foo^bar'
+ end
+ end
+
+ it "recursively matches directories with '**/<characters>'" do
+ Dir.send(@method, '**/*fil?{,.}*').uniq.sort.should ==
+ %w[deeply/nested/directory/structure/file_one
+ deeply/nested/directory/structure/file_one.ext
+ deeply/nondotfile
+
+ dir/filename_ordering
+ dir_filename_ordering
+
+ file_one.ext
+ file_two.ext
+
+ nondotfile
+
+ special/test{1}/file[1]
+
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ subdir_two/nondotfile.ext]
+ end
+
+ it "ignores matching through directories that doesn't exist" do
+ Dir.send(@method, "deeply/notthere/blah*/whatever").should == []
+ end
+
+ it "ignores matching only directories under an nonexistent path" do
+ Dir.send(@method, "deeply/notthere/blah/").should == []
+ end
+
+ platform_is_not :windows do
+ it "matches UTF-8 paths" do
+ Dir.send(@method, "special/ã“ã‚“ã«ã¡ã¯{,.txt}").should == ["special/ã“ã‚“ã«ã¡ã¯.txt"]
+ end
+ end
+
+ context ":base option passed" do
+ before :each do
+ @mock_dir = File.expand_path tmp('dir_glob_mock')
+
+ %w[
+ a/b/x
+ a/b/c/y
+ a/b/c/d/z
+ ].each do |path|
+ file = File.join @mock_dir, path
+ mkdir_p File.dirname(file)
+ touch file
+ end
+ end
+
+ after :each do
+ rm_r @mock_dir
+ end
+
+ it "matches entries only from within the specified directory" do
+ path = File.join(@mock_dir, "a/b/c")
+ Dir.send(@method, "*", base: path).sort.should == %w( d y )
+ end
+
+ it "accepts both relative and absolute paths" do
+ require 'pathname'
+
+ path_abs = File.join(@mock_dir, "a/b/c")
+ path_rel = Pathname.new(path_abs).relative_path_from(Pathname.new(Dir.pwd))
+
+ result_abs = Dir.send(@method, "*", base: path_abs).sort
+ result_rel = Dir.send(@method, "*", base: path_rel).sort
+
+ result_abs.should == %w( d y )
+ result_rel.should == %w( d y )
+ end
+
+ it "returns [] if specified path does not exist" do
+ path = File.join(@mock_dir, "fake-name")
+ File.should_not.exist?(path)
+
+ Dir.send(@method, "*", base: path).should == []
+ end
+
+ it "returns [] if specified path is a file" do
+ path = File.join(@mock_dir, "a/b/x")
+ File.should.exist?(path)
+
+ Dir.send(@method, "*", base: path).should == []
+ end
+
+ it "raises TypeError when cannot convert value to string" do
+ -> {
+ Dir.send(@method, "*", base: [])
+ }.should raise_error(TypeError)
+ end
+
+ it "handles '' as current directory path" do
+ Dir.chdir @mock_dir do
+ Dir.send(@method, "*", base: "").should == %w( a )
+ end
+ end
+
+ it "handles nil as current directory path" do
+ Dir.chdir @mock_dir do
+ Dir.send(@method, "*", base: nil).should == %w( a )
+ end
+ end
+ end
+end
+
+describe :dir_glob_recursive, shared: true do
+ before :each do
+ @cwd = Dir.pwd
+ @mock_dir = File.expand_path tmp('dir_glob_mock')
+
+ %w[
+ a/x/b/y/e
+ a/x/b/y/b/z/e
+ ].each do |path|
+ file = File.join @mock_dir, path
+ mkdir_p File.dirname(file)
+ touch file
+ end
+
+ Dir.chdir @mock_dir
+ end
+
+ after :each do
+ Dir.chdir @cwd
+ rm_r @mock_dir
+ end
+
+ it "matches multiple recursives" do
+ expected = %w[
+ a/x/b/y/b/z/e
+ a/x/b/y/e
+ ]
+
+ Dir.send(@method, 'a/**/b/**/e').uniq.sort.should == expected
+ end
+
+ platform_is_not :windows do
+ it "ignores symlinks" do
+ file = File.join @mock_dir, 'b/z/e'
+ link = File.join @mock_dir, 'a/y'
+
+ mkdir_p File.dirname(file)
+ touch file
+ File.symlink(File.dirname(file), link)
+
+ expected = %w[
+ a/x/b/y/b/z/e
+ a/x/b/y/e
+ ]
+
+ Dir.send(@method, 'a/**/e').uniq.sort.should == expected
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/shared/open.rb b/spec/ruby/core/dir/shared/open.rb
new file mode 100644
index 0000000000..920845cba1
--- /dev/null
+++ b/spec/ruby/core/dir/shared/open.rb
@@ -0,0 +1,73 @@
+describe :dir_open, shared: true do
+ it "returns a Dir instance representing the specified directory" do
+ dir = Dir.send(@method, DirSpecs.mock_dir)
+ dir.should be_kind_of(Dir)
+ dir.close
+ end
+
+ it "raises a SystemCallError if the directory does not exist" do
+ -> do
+ Dir.send @method, DirSpecs.nonexistent
+ end.should raise_error(SystemCallError)
+ end
+
+ it "may take a block which is yielded to with the Dir instance" do
+ Dir.send(@method, DirSpecs.mock_dir) {|dir| dir.should be_kind_of(Dir)}
+ end
+
+ it "returns the value of the block if a block is given" do
+ Dir.send(@method, DirSpecs.mock_dir) {|dir| :value }.should == :value
+ end
+
+ it "closes the Dir instance when the block exits if given a block" do
+ closed_dir = Dir.send(@method, DirSpecs.mock_dir) { |dir| dir }
+ -> { closed_dir.read }.should raise_error(IOError)
+ end
+
+ it "closes the Dir instance when the block exits the block even due to an exception" do
+ closed_dir = nil
+
+ -> do
+ Dir.send(@method, DirSpecs.mock_dir) do |dir|
+ closed_dir = dir
+ raise "dir specs"
+ end
+ end.should raise_error(RuntimeError, "dir specs")
+
+ -> { closed_dir.read }.should raise_error(IOError)
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.send(@method, p) { true }
+ end
+
+ it "accepts an options Hash" do
+ dir = Dir.send(@method, DirSpecs.mock_dir, encoding: "utf-8") {|d| d }
+ dir.should be_kind_of(Dir)
+ end
+
+ it "calls #to_hash to convert the options object" do
+ options = mock("dir_open")
+ options.should_receive(:to_hash).and_return({ encoding: Encoding::UTF_8 })
+
+ dir = Dir.send(@method, DirSpecs.mock_dir, **options) {|d| d }
+ dir.should be_kind_of(Dir)
+ end
+
+ it "ignores the :encoding option if it is nil" do
+ dir = Dir.send(@method, DirSpecs.mock_dir, encoding: nil) {|d| d }
+ dir.should be_kind_of(Dir)
+ end
+
+ platform_is_not :windows do
+ it 'sets the close-on-exec flag for the directory file descriptor' do
+ Dir.send(@method, DirSpecs.mock_dir) do |dir|
+ io = IO.for_fd(dir.fileno)
+ io.autoclose = false
+ io.should.close_on_exec?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/shared/path.rb b/spec/ruby/core/dir/shared/path.rb
new file mode 100644
index 0000000000..494dcca775
--- /dev/null
+++ b/spec/ruby/core/dir/shared/path.rb
@@ -0,0 +1,30 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+require_relative 'closed'
+
+describe :dir_path, shared: true do
+ it "returns the path that was supplied to .new or .open" do
+ dir = Dir.open DirSpecs.mock_dir
+ begin
+ dir.send(@method).should == DirSpecs.mock_dir
+ ensure
+ dir.close rescue nil
+ end
+ end
+
+ it "returns the path even when called on a closed Dir instance" do
+ dir = Dir.open DirSpecs.mock_dir
+ dir.close
+ dir.send(@method).should == DirSpecs.mock_dir
+ end
+
+ it "returns a String with the same encoding as the argument to .open" do
+ path = DirSpecs.mock_dir.force_encoding Encoding::IBM866
+ dir = Dir.open path
+ begin
+ dir.send(@method).encoding.should equal(Encoding::IBM866)
+ ensure
+ dir.close
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/shared/pos.rb b/spec/ruby/core/dir/shared/pos.rb
new file mode 100644
index 0000000000..2165932d99
--- /dev/null
+++ b/spec/ruby/core/dir/shared/pos.rb
@@ -0,0 +1,51 @@
+describe :dir_pos, shared: true do
+ before :each do
+ @dir = Dir.open DirSpecs.mock_dir
+ end
+
+ after :each do
+ @dir.close rescue nil
+ end
+
+ it "returns an Integer representing the current position in the directory" do
+ @dir.send(@method).should be_kind_of(Integer)
+ @dir.send(@method).should be_kind_of(Integer)
+ @dir.send(@method).should be_kind_of(Integer)
+ end
+
+ it "returns a different Integer if moved from previous position" do
+ a = @dir.send(@method)
+ @dir.read
+ b = @dir.send(@method)
+
+ a.should be_kind_of(Integer)
+ b.should be_kind_of(Integer)
+
+ a.should_not == b
+ end
+end
+
+describe :dir_pos_set, shared: true do
+ before :each do
+ @dir = Dir.open DirSpecs.mock_dir
+ end
+
+ after :each do
+ @dir.close
+ end
+
+ # NOTE: #seek/#pos= to a position not returned by #tell/#pos is undefined
+ # and should not be spec'd.
+
+ it "moves the read position to a previously obtained position" do
+ pos = @dir.pos
+ a = @dir.read
+ b = @dir.read
+ @dir.send @method, pos
+ c = @dir.read
+
+ a.should_not == b
+ b.should_not == c
+ c.should == a
+ end
+end
diff --git a/spec/ruby/core/dir/shared/pwd.rb b/spec/ruby/core/dir/shared/pwd.rb
new file mode 100644
index 0000000000..2a8d7fe790
--- /dev/null
+++ b/spec/ruby/core/dir/shared/pwd.rb
@@ -0,0 +1,45 @@
+describe :dir_pwd, shared: true do
+ before :each do
+ @fs_encoding = Encoding.find('filesystem')
+ end
+
+ it "returns the current working directory" do
+ pwd = Dir.send(@method)
+
+ File.directory?(pwd).should == true
+
+ # On ubuntu gutsy, for example, /bin/pwd does not
+ # understand -P. With just `pwd -P`, /bin/pwd is run.
+
+ # The following uses inode rather than file names to account for
+ # case insensitive file systems like default OS/X file systems
+ platform_is_not :windows do
+ File.stat(pwd).ino.should == File.stat(`/bin/sh -c "pwd -P"`.chomp).ino
+ end
+ platform_is :windows do
+ File.stat(pwd).ino.should == File.stat(File.expand_path(`cd`.chomp)).ino
+ end
+ end
+
+ it "returns an absolute path" do
+ pwd = Dir.send(@method)
+ pwd.should == File.expand_path(pwd)
+ end
+
+ it "returns an absolute path even when chdir to a relative path" do
+ Dir.chdir(".") do
+ pwd = Dir.send(@method)
+ File.directory?(pwd).should == true
+ pwd.should == File.expand_path(pwd)
+ end
+ end
+
+ it "returns a String with the filesystem encoding" do
+ enc = Dir.send(@method).encoding
+ if @fs_encoding == Encoding::US_ASCII
+ [Encoding::US_ASCII, Encoding::BINARY].should include(enc)
+ else
+ enc.should equal(@fs_encoding)
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/tell_spec.rb b/spec/ruby/core/dir/tell_spec.rb
new file mode 100644
index 0000000000..af86dc1598
--- /dev/null
+++ b/spec/ruby/core/dir/tell_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/closed'
+require_relative 'shared/pos'
+
+describe "Dir#tell" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_pos, :tell
+
+ it_behaves_like :dir_closed, :tell
+end
diff --git a/spec/ruby/core/dir/to_path_spec.rb b/spec/ruby/core/dir/to_path_spec.rb
new file mode 100644
index 0000000000..77404a3dc8
--- /dev/null
+++ b/spec/ruby/core/dir/to_path_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/path'
+
+describe "Dir#to_path" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_path, :to_path
+end
diff --git a/spec/ruby/core/dir/unlink_spec.rb b/spec/ruby/core/dir/unlink_spec.rb
new file mode 100644
index 0000000000..79027e020c
--- /dev/null
+++ b/spec/ruby/core/dir/unlink_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/delete'
+
+describe "Dir.unlink" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_delete, :unlink
+end
diff --git a/spec/ruby/core/encoding/_dump_spec.rb b/spec/ruby/core/encoding/_dump_spec.rb
new file mode 100644
index 0000000000..623fe88ec9
--- /dev/null
+++ b/spec/ruby/core/encoding/_dump_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Encoding#_dump" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/encoding/_load_spec.rb b/spec/ruby/core/encoding/_load_spec.rb
new file mode 100644
index 0000000000..608098d34b
--- /dev/null
+++ b/spec/ruby/core/encoding/_load_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Encoding._load" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/encoding/aliases_spec.rb b/spec/ruby/core/encoding/aliases_spec.rb
new file mode 100644
index 0000000000..786157981a
--- /dev/null
+++ b/spec/ruby/core/encoding/aliases_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.aliases" do
+ it "returns a Hash" do
+ Encoding.aliases.should be_an_instance_of(Hash)
+ end
+
+ it "has Strings as keys" do
+ Encoding.aliases.keys.each do |key|
+ key.should be_an_instance_of(String)
+ end
+ end
+
+ it "has Strings as values" do
+ Encoding.aliases.values.each do |value|
+ value.should be_an_instance_of(String)
+ end
+ end
+
+ it "has alias names as its keys" do
+ Encoding.aliases.key?('BINARY').should be_true
+ Encoding.aliases.key?('ASCII').should be_true
+ end
+
+ it "has the names of the aliased encoding as its values" do
+ Encoding.aliases['BINARY'].should == 'ASCII-8BIT'
+ Encoding.aliases['ASCII'].should == 'US-ASCII'
+ end
+
+ it "has an 'external' key with the external default encoding as its value" do
+ Encoding.aliases['external'].should == Encoding.default_external.name
+ end
+
+ it "has a 'locale' key and its value equals the name of the encoding found by the locale charmap" do
+ Encoding.aliases['locale'].should == Encoding.find(Encoding.locale_charmap).name
+ end
+
+ it "only contains valid aliased encodings" do
+ Encoding.aliases.each do |aliased, original|
+ Encoding.find(aliased).should == Encoding.find(original)
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/ascii_compatible_spec.rb b/spec/ruby/core/encoding/ascii_compatible_spec.rb
new file mode 100644
index 0000000000..4804300e85
--- /dev/null
+++ b/spec/ruby/core/encoding/ascii_compatible_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Encoding#ascii_compatible?" do
+ it "returns true if self represents an ASCII-compatible encoding" do
+ Encoding::UTF_8.ascii_compatible?.should be_true
+ end
+
+ it "returns false if self does not represent an ASCII-compatible encoding" do
+ Encoding::UTF_16LE.ascii_compatible?.should be_false
+ end
+end
diff --git a/spec/ruby/core/encoding/compatible_spec.rb b/spec/ruby/core/encoding/compatible_spec.rb
new file mode 100644
index 0000000000..80ecab6155
--- /dev/null
+++ b/spec/ruby/core/encoding/compatible_spec.rb
@@ -0,0 +1,379 @@
+# -*- encoding: binary -*-
+
+require_relative '../../spec_helper'
+
+# TODO: add IO
+
+describe "Encoding.compatible? String, String" do
+ describe "when the first's Encoding is valid US-ASCII" do
+ before :each do
+ @str = "abc".force_encoding Encoding::US_ASCII
+ end
+
+ it "returns US-ASCII when the second's is US-ASCII" do
+ Encoding.compatible?(@str, "def".encode("us-ascii")).should == Encoding::US_ASCII
+ end
+
+ it "returns US-ASCII if the second String is BINARY and ASCII only" do
+ Encoding.compatible?(@str, "\x7f").should == Encoding::US_ASCII
+ end
+
+ it "returns BINARY if the second String is BINARY but not ASCII only" do
+ Encoding.compatible?(@str, "\xff").should == Encoding::BINARY
+ end
+
+ it "returns US-ASCII if the second String is UTF-8 and ASCII only" do
+ Encoding.compatible?(@str, "\x7f".encode("utf-8")).should == Encoding::US_ASCII
+ end
+
+ it "returns UTF-8 if the second String is UTF-8 but not ASCII only" do
+ Encoding.compatible?(@str, "\u3042".encode("utf-8")).should == Encoding::UTF_8
+ end
+ end
+
+ describe "when the first's Encoding is ASCII compatible and ASCII only" do
+ it "returns the first's Encoding if the second is ASCII compatible and ASCII only" do
+ [ [Encoding, "abc".force_encoding("UTF-8"), "123".force_encoding("Shift_JIS"), Encoding::UTF_8],
+ [Encoding, "123".force_encoding("Shift_JIS"), "abc".force_encoding("UTF-8"), Encoding::Shift_JIS]
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns the first's Encoding if the second is ASCII compatible and ASCII only" do
+ [ [Encoding, "abc".force_encoding("BINARY"), "123".force_encoding("US-ASCII"), Encoding::BINARY],
+ [Encoding, "123".force_encoding("US-ASCII"), "abc".force_encoding("BINARY"), Encoding::US_ASCII]
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns the second's Encoding if the second is ASCII compatible but not ASCII only" do
+ [ [Encoding, "abc".force_encoding("UTF-8"), "\xff".force_encoding("Shift_JIS"), Encoding::Shift_JIS],
+ [Encoding, "123".force_encoding("Shift_JIS"), "\xff".force_encoding("UTF-8"), Encoding::UTF_8],
+ [Encoding, "abc".force_encoding("BINARY"), "\xff".force_encoding("US-ASCII"), Encoding::US_ASCII],
+ [Encoding, "123".force_encoding("US-ASCII"), "\xff".force_encoding("BINARY"), Encoding::BINARY],
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns nil if the second's Encoding is not ASCII compatible" do
+ a = "abc".force_encoding("UTF-8")
+ b = "1234".force_encoding("UTF-16LE")
+ Encoding.compatible?(a, b).should be_nil
+ end
+ end
+
+ describe "when the first's Encoding is ASCII compatible but not ASCII only" do
+ it "returns the first's Encoding if the second's is valid US-ASCII" do
+ Encoding.compatible?("\xff", "def".encode("us-ascii")).should == Encoding::BINARY
+ end
+
+ it "returns the first's Encoding if the second's is UTF-8 and ASCII only" do
+ Encoding.compatible?("\xff", "\u{7f}".encode("utf-8")).should == Encoding::BINARY
+ end
+
+ it "returns nil if the second encoding is ASCII compatible but neither String's encoding is ASCII only" do
+ Encoding.compatible?("\xff", "\u3042".encode("utf-8")).should be_nil
+ end
+ end
+
+ describe "when the first's Encoding is not ASCII compatible" do
+ before :each do
+ @str = "abc".force_encoding Encoding::UTF_7
+ end
+
+ it "returns nil when the second String is US-ASCII" do
+ Encoding.compatible?(@str, "def".encode("us-ascii")).should be_nil
+ end
+
+ it "returns nil when the second String is BINARY and ASCII only" do
+ Encoding.compatible?(@str, "\x7f").should be_nil
+ end
+
+ it "returns nil when the second String is BINARY but not ASCII only" do
+ Encoding.compatible?(@str, "\xff").should be_nil
+ end
+
+ it "returns the Encoding when the second's Encoding is not ASCII compatible but the same as the first's Encoding" do
+ encoding = Encoding.compatible?(@str, "def".force_encoding("utf-7"))
+ encoding.should == Encoding::UTF_7
+ end
+ end
+
+ describe "when the first's Encoding is invalid" do
+ before :each do
+ @str = "\xff".force_encoding Encoding::UTF_8
+ end
+
+ it "returns the first's Encoding when the second's Encoding is US-ASCII" do
+ Encoding.compatible?(@str, "def".encode("us-ascii")).should == Encoding::UTF_8
+ end
+
+ it "returns the first's Encoding when the second String is ASCII only" do
+ Encoding.compatible?(@str, "\x7f").should == Encoding::UTF_8
+ end
+
+ it "returns nil when the second's Encoding is BINARY but not ASCII only" do
+ Encoding.compatible?(@str, "\xff").should be_nil
+ end
+
+ it "returns nil when the second's Encoding is invalid and ASCII only" do
+ Encoding.compatible?(@str, "\x7f".force_encoding("utf-16be")).should be_nil
+ end
+
+ it "returns nil when the second's Encoding is invalid and not ASCII only" do
+ Encoding.compatible?(@str, "\xff".force_encoding("utf-16be")).should be_nil
+ end
+
+ it "returns the Encoding when the second's Encoding is invalid but the same as the first" do
+ Encoding.compatible?(@str, @str).should == Encoding::UTF_8
+ end
+ end
+
+ describe "when the first String is empty and the second is not" do
+ describe "and the first's Encoding is ASCII compatible" do
+ before :each do
+ @str = "".force_encoding("utf-8")
+ end
+
+ it "returns the first's encoding when the second String is ASCII only" do
+ Encoding.compatible?(@str, "def".encode("us-ascii")).should == Encoding::UTF_8
+ end
+
+ it "returns the second's encoding when the second String is not ASCII only" do
+ Encoding.compatible?(@str, "def".encode("utf-32le")).should == Encoding::UTF_32LE
+ end
+ end
+
+ describe "when the first's Encoding is not ASCII compatible" do
+ before :each do
+ @str = "".force_encoding Encoding::UTF_7
+ end
+
+ it "returns the second string's encoding" do
+ Encoding.compatible?(@str, "def".encode("us-ascii")).should == Encoding::US_ASCII
+ end
+ end
+ end
+
+ describe "when the second String is empty" do
+ before :each do
+ @str = "abc".force_encoding("utf-7")
+ end
+
+ it "returns the first Encoding" do
+ Encoding.compatible?(@str, "").should == Encoding::UTF_7
+ end
+ end
+end
+
+describe "Encoding.compatible? String, Regexp" do
+ it "returns US-ASCII if both are US-ASCII" do
+ str = "abc".force_encoding("us-ascii")
+ Encoding.compatible?(str, /abc/).should == Encoding::US_ASCII
+ end
+
+ it "returns the String's Encoding if it is not US-ASCII but both are ASCII only" do
+ [ [Encoding, "abc", Encoding::BINARY],
+ [Encoding, "abc".encode("utf-8"), Encoding::UTF_8],
+ [Encoding, "abc".encode("euc-jp"), Encoding::EUC_JP],
+ [Encoding, "abc".encode("shift_jis"), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, /abc/)
+ end
+
+ it "returns the String's Encoding if the String is not ASCII only" do
+ [ [Encoding, "\xff", Encoding::BINARY],
+ [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8],
+ [Encoding, "\xa4\xa2".force_encoding("euc-jp"), Encoding::EUC_JP],
+ [Encoding, "\x82\xa0".force_encoding("shift_jis"), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, /abc/)
+ end
+end
+
+describe "Encoding.compatible? String, Symbol" do
+ it "returns US-ASCII if both are ASCII only" do
+ str = "abc".force_encoding("us-ascii")
+ Encoding.compatible?(str, :abc).should == Encoding::US_ASCII
+ end
+
+ it "returns the String's Encoding if it is not US-ASCII but both are ASCII only" do
+ [ [Encoding, "abc", Encoding::BINARY],
+ [Encoding, "abc".encode("utf-8"), Encoding::UTF_8],
+ [Encoding, "abc".encode("euc-jp"), Encoding::EUC_JP],
+ [Encoding, "abc".encode("shift_jis"), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, :abc)
+ end
+
+ it "returns the String's Encoding if the String is not ASCII only" do
+ [ [Encoding, "\xff", Encoding::BINARY],
+ [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8],
+ [Encoding, "\xa4\xa2".force_encoding("euc-jp"), Encoding::EUC_JP],
+ [Encoding, "\x82\xa0".force_encoding("shift_jis"), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, :abc)
+ end
+end
+
+describe "Encoding.compatible? String, Encoding" do
+ it "returns nil if the String's encoding is not ASCII compatible" do
+ Encoding.compatible?("abc".encode("utf-32le"), Encoding::US_ASCII).should be_nil
+ end
+
+ it "returns nil if the Encoding is not ASCII compatible" do
+ Encoding.compatible?("abc".encode("us-ascii"), Encoding::UTF_32LE).should be_nil
+ end
+
+ it "returns the String's encoding if the Encoding is US-ASCII" do
+ [ [Encoding, "\xff", Encoding::BINARY],
+ [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8],
+ [Encoding, "\xa4\xa2".force_encoding("euc-jp"), Encoding::EUC_JP],
+ [Encoding, "\x82\xa0".force_encoding("shift_jis"), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, Encoding::US_ASCII)
+ end
+
+ it "returns the Encoding if the String's encoding is ASCII compatible and the String is ASCII only" do
+ str = "abc".encode("utf-8")
+
+ Encoding.compatible?(str, Encoding::BINARY).should == Encoding::BINARY
+ Encoding.compatible?(str, Encoding::UTF_8).should == Encoding::UTF_8
+ Encoding.compatible?(str, Encoding::EUC_JP).should == Encoding::EUC_JP
+ Encoding.compatible?(str, Encoding::Shift_JIS).should == Encoding::Shift_JIS
+ end
+
+ it "returns nil if the String's encoding is ASCII compatible but the string is not ASCII only" do
+ Encoding.compatible?("\u3042".encode("utf-8"), Encoding::BINARY).should be_nil
+ end
+end
+
+describe "Encoding.compatible? Regexp, String" do
+ it "returns US-ASCII if both are US-ASCII" do
+ str = "abc".force_encoding("us-ascii")
+ Encoding.compatible?(/abc/, str).should == Encoding::US_ASCII
+ end
+
+end
+
+describe "Encoding.compatible? Regexp, Regexp" do
+ it "returns US-ASCII if both are US-ASCII" do
+ Encoding.compatible?(/abc/, /def/).should == Encoding::US_ASCII
+ end
+
+ it "returns the first's Encoding if it is not US-ASCII and not ASCII only" do
+ [ [Encoding, Regexp.new("\xff"), Encoding::BINARY],
+ [Encoding, Regexp.new("\u3042".encode("utf-8")), Encoding::UTF_8],
+ [Encoding, Regexp.new("\xa4\xa2".force_encoding("euc-jp")), Encoding::EUC_JP],
+ [Encoding, Regexp.new("\x82\xa0".force_encoding("shift_jis")), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, /abc/)
+ end
+end
+
+describe "Encoding.compatible? Regexp, Symbol" do
+ it "returns US-ASCII if both are US-ASCII" do
+ Encoding.compatible?(/abc/, :def).should == Encoding::US_ASCII
+ end
+
+ it "returns the first's Encoding if it is not US-ASCII and not ASCII only" do
+ [ [Encoding, Regexp.new("\xff"), Encoding::BINARY],
+ [Encoding, Regexp.new("\u3042".encode("utf-8")), Encoding::UTF_8],
+ [Encoding, Regexp.new("\xa4\xa2".force_encoding("euc-jp")), Encoding::EUC_JP],
+ [Encoding, Regexp.new("\x82\xa0".force_encoding("shift_jis")), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, /abc/)
+ end
+end
+
+describe "Encoding.compatible? Symbol, String" do
+ it "returns US-ASCII if both are ASCII only" do
+ str = "abc".force_encoding("us-ascii")
+ Encoding.compatible?(str, :abc).should == Encoding::US_ASCII
+ end
+end
+
+describe "Encoding.compatible? Symbol, Regexp" do
+ it "returns US-ASCII if both are US-ASCII" do
+ Encoding.compatible?(:abc, /def/).should == Encoding::US_ASCII
+ end
+
+ it "returns the Regexp's Encoding if it is not US-ASCII and not ASCII only" do
+ a = Regexp.new("\xff")
+ b = Regexp.new("\u3042".encode("utf-8"))
+ c = Regexp.new("\xa4\xa2".force_encoding("euc-jp"))
+ d = Regexp.new("\x82\xa0".force_encoding("shift_jis"))
+
+ [ [Encoding, :abc, a, Encoding::BINARY],
+ [Encoding, :abc, b, Encoding::UTF_8],
+ [Encoding, :abc, c, Encoding::EUC_JP],
+ [Encoding, :abc, d, Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?)
+ end
+end
+
+describe "Encoding.compatible? Symbol, Symbol" do
+ it "returns US-ASCII if both are US-ASCII" do
+ Encoding.compatible?(:abc, :def).should == Encoding::US_ASCII
+ end
+
+ it "returns the first's Encoding if it is not ASCII only" do
+ [ [Encoding, "\xff".to_sym, Encoding::BINARY],
+ [Encoding, "\u3042".encode("utf-8").to_sym, Encoding::UTF_8],
+ [Encoding, "\xa4\xa2".force_encoding("euc-jp").to_sym, Encoding::EUC_JP],
+ [Encoding, "\x82\xa0".force_encoding("shift_jis").to_sym, Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, :abc)
+ end
+end
+
+describe "Encoding.compatible? Encoding, Encoding" do
+ it "returns nil if one of the encodings is a dummy encoding" do
+ [ [Encoding, Encoding::UTF_7, Encoding::US_ASCII, nil],
+ [Encoding, Encoding::US_ASCII, Encoding::UTF_7, nil],
+ [Encoding, Encoding::EUC_JP, Encoding::UTF_7, nil],
+ [Encoding, Encoding::UTF_7, Encoding::EUC_JP, nil],
+ [Encoding, Encoding::UTF_7, Encoding::BINARY, nil],
+ [Encoding, Encoding::BINARY, Encoding::UTF_7, nil],
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns nil if one of the encodings is not US-ASCII" do
+ [ [Encoding, Encoding::UTF_8, Encoding::BINARY, nil],
+ [Encoding, Encoding::BINARY, Encoding::UTF_8, nil],
+ [Encoding, Encoding::BINARY, Encoding::EUC_JP, nil],
+ [Encoding, Encoding::Shift_JIS, Encoding::EUC_JP, nil],
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns the first if the second is US-ASCII" do
+ [ [Encoding, Encoding::UTF_8, Encoding::US_ASCII, Encoding::UTF_8],
+ [Encoding, Encoding::EUC_JP, Encoding::US_ASCII, Encoding::EUC_JP],
+ [Encoding, Encoding::Shift_JIS, Encoding::US_ASCII, Encoding::Shift_JIS],
+ [Encoding, Encoding::BINARY, Encoding::US_ASCII, Encoding::BINARY],
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns the Encoding if both are the same" do
+ [ [Encoding, Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8],
+ [Encoding, Encoding::US_ASCII, Encoding::US_ASCII, Encoding::US_ASCII],
+ [Encoding, Encoding::BINARY, Encoding::BINARY, Encoding::BINARY],
+ [Encoding, Encoding::UTF_7, Encoding::UTF_7, Encoding::UTF_7],
+ ].should be_computed_by(:compatible?)
+ end
+end
+
+describe "Encoding.compatible? Object, Object" do
+ it "returns nil for Object, String" do
+ Encoding.compatible?(Object.new, "abc").should be_nil
+ end
+
+ it "returns nil for Object, Regexp" do
+ Encoding.compatible?(Object.new, /./).should be_nil
+ end
+
+ it "returns nil for Object, Symbol" do
+ Encoding.compatible?(Object.new, :sym).should be_nil
+ end
+
+ it "returns nil for String, Object" do
+ Encoding.compatible?("abc", Object.new).should be_nil
+ end
+
+ it "returns nil for Regexp, Object" do
+ Encoding.compatible?(/./, Object.new).should be_nil
+ end
+
+ it "returns nil for Symbol, Object" do
+ Encoding.compatible?(:sym, Object.new).should be_nil
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/asciicompat_encoding_spec.rb b/spec/ruby/core/encoding/converter/asciicompat_encoding_spec.rb
new file mode 100644
index 0000000000..1beb40af3f
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/asciicompat_encoding_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter.asciicompat_encoding" do
+ it "accepts an encoding name as a String argument" do
+ -> { Encoding::Converter.asciicompat_encoding('UTF-8') }.
+ should_not raise_error
+ end
+
+ it "coerces non-String/Encoding objects with #to_str" do
+ str = mock('string')
+ str.should_receive(:to_str).at_least(1).times.and_return('string')
+ Encoding::Converter.asciicompat_encoding(str)
+ end
+
+ it "accepts an Encoding object as an argument" do
+ Encoding::Converter.
+ asciicompat_encoding(Encoding.find("ISO-2022-JP")).
+ should == Encoding::Converter.asciicompat_encoding("ISO-2022-JP")
+ end
+
+ it "returns a corresponding ASCII compatible encoding for ASCII-incompatible encodings" do
+ Encoding::Converter.asciicompat_encoding('UTF-16BE').should == Encoding::UTF_8
+ Encoding::Converter.asciicompat_encoding("ISO-2022-JP").should == Encoding.find("stateless-ISO-2022-JP")
+ end
+
+ it "returns nil when the given encoding is ASCII compatible" do
+ Encoding::Converter.asciicompat_encoding('ASCII').should be_nil
+ Encoding::Converter.asciicompat_encoding('UTF-8').should be_nil
+ end
+
+ it "handles encoding names who resolve to nil encodings" do
+ internal = Encoding.default_internal
+ Encoding.default_internal = nil
+ Encoding::Converter.asciicompat_encoding('internal').should be_nil
+ Encoding.default_internal = internal
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/constants_spec.rb b/spec/ruby/core/encoding/converter/constants_spec.rb
new file mode 100644
index 0000000000..7d29bdb278
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/constants_spec.rb
@@ -0,0 +1,131 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter::INVALID_MASK" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:INVALID_MASK)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::INVALID_MASK.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::INVALID_REPLACE" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:INVALID_REPLACE)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::INVALID_REPLACE.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::UNDEF_MASK" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:UNDEF_MASK)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::UNDEF_MASK.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::UNDEF_REPLACE" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:UNDEF_REPLACE)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::UNDEF_REPLACE.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::UNDEF_HEX_CHARREF" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:UNDEF_HEX_CHARREF)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::UNDEF_HEX_CHARREF.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::PARTIAL_INPUT" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:PARTIAL_INPUT)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::PARTIAL_INPUT.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::AFTER_OUTPUT" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:AFTER_OUTPUT)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::AFTER_OUTPUT.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::UNIVERSAL_NEWLINE_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:UNIVERSAL_NEWLINE_DECORATOR)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::UNIVERSAL_NEWLINE_DECORATOR.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::CRLF_NEWLINE_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:CRLF_NEWLINE_DECORATOR)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::CRLF_NEWLINE_DECORATOR.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::CR_NEWLINE_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:CR_NEWLINE_DECORATOR)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::CR_NEWLINE_DECORATOR.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::XML_TEXT_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:XML_TEXT_DECORATOR)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::XML_TEXT_DECORATOR.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::XML_ATTR_CONTENT_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:XML_ATTR_CONTENT_DECORATOR)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::XML_ATTR_CONTENT_DECORATOR.should be_an_instance_of(Integer)
+ end
+end
+
+describe "Encoding::Converter::XML_ATTR_QUOTE_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should have_constant(:XML_ATTR_QUOTE_DECORATOR)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::XML_ATTR_QUOTE_DECORATOR.should be_an_instance_of(Integer)
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/convert_spec.rb b/spec/ruby/core/encoding/converter/convert_spec.rb
new file mode 100644
index 0000000000..95a9e0b758
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/convert_spec.rb
@@ -0,0 +1,45 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#convert" do
+ it "returns a String" do
+ ec = Encoding::Converter.new('ascii', 'utf-8')
+ ec.convert('glark').should be_an_instance_of(String)
+ end
+
+ it "sets the encoding of the result to the target encoding" do
+ ec = Encoding::Converter.new('ascii', 'utf-8')
+ str = 'glark'.force_encoding('ascii')
+ ec.convert(str).encoding.should == Encoding::UTF_8
+ end
+
+ it "transcodes the given String to the target encoding" do
+ ec = Encoding::Converter.new("utf-8", "euc-jp")
+ ec.convert("\u3042".force_encoding('UTF-8')).should == \
+ "\xA4\xA2".force_encoding('EUC-JP')
+ end
+
+ it "allows Strings of different encodings to the source encoding" do
+ ec = Encoding::Converter.new('ascii', 'utf-8')
+ str = 'glark'.force_encoding('SJIS')
+ ec.convert(str).encoding.should == Encoding::UTF_8
+ end
+
+ it "reuses the given encoding pair if called multiple times" do
+ ec = Encoding::Converter.new('ascii', 'SJIS')
+ ec.convert('a'.force_encoding('ASCII')).should == 'a'.force_encoding('SJIS')
+ ec.convert('b'.force_encoding('ASCII')).should == 'b'.force_encoding('SJIS')
+ end
+
+ it "raises UndefinedConversionError if the String contains characters invalid for the target encoding" do
+ ec = Encoding::Converter.new('UTF-8', Encoding.find('macCyrillic'))
+ -> { ec.convert("\u{6543}".force_encoding('UTF-8')) }.should \
+ raise_error(Encoding::UndefinedConversionError)
+ end
+
+ it "raises an ArgumentError if called on a finished stream" do
+ ec = Encoding::Converter.new('UTF-8', Encoding.find('macCyrillic'))
+ ec.finish
+ -> { ec.convert("\u{65}") }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/convpath_spec.rb b/spec/ruby/core/encoding/converter/convpath_spec.rb
new file mode 100644
index 0000000000..23f1e5dc33
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/convpath_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#convpath" do
+ it "returns an Array with a single element if there is a direct converter" do
+ cp = Encoding::Converter.new('ASCII', 'UTF-8').convpath
+ cp.should == [[Encoding::US_ASCII, Encoding::UTF_8]]
+ end
+
+ it "returns multiple encoding pairs when direct conversion is impossible" do
+ cp = Encoding::Converter.new('ascii','Big5').convpath
+ cp.should == [
+ [Encoding::US_ASCII, Encoding::UTF_8],
+ [Encoding::UTF_8, Encoding::Big5]
+ ]
+ end
+
+ it "indicates if crlf_newline conversion would occur" do
+ ec = Encoding::Converter.new("ISo-8859-1", "EUC-JP", crlf_newline: true)
+ ec.convpath.last.should == "crlf_newline"
+
+ ec = Encoding::Converter.new("ASCII", "UTF-8", crlf_newline: false)
+ ec.convpath.last.should_not == "crlf_newline"
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/destination_encoding_spec.rb b/spec/ruby/core/encoding/converter/destination_encoding_spec.rb
new file mode 100644
index 0000000000..481a857909
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/destination_encoding_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#destination_encoding" do
+ it "returns the destination encoding as an Encoding object" do
+ ec = Encoding::Converter.new('ASCII','Big5')
+ ec.destination_encoding.should == Encoding::BIG5
+
+ ec = Encoding::Converter.new('SJIS','EUC-JP')
+ ec.destination_encoding.should == Encoding::EUC_JP
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/finish_spec.rb b/spec/ruby/core/encoding/converter/finish_spec.rb
new file mode 100644
index 0000000000..11ca7e8510
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/finish_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#finish" do
+ before :each do
+ @ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
+ end
+
+ it "returns a String" do
+ @ec.convert('foo')
+ @ec.finish.should be_an_instance_of(String)
+ end
+
+ it "returns an empty String if there is nothing more to convert" do
+ @ec.convert("glark")
+ @ec.finish.should == ""
+ end
+
+ it "returns the last part of the converted String if it hasn't already" do
+ @ec.convert("\u{9999}").should == "\e$B9a".force_encoding('iso-2022-jp')
+ @ec.finish.should == "\e(B".force_encoding('iso-2022-jp')
+ end
+
+ it "returns a String in the destination encoding" do
+ @ec.convert("glark")
+ @ec.finish.encoding.should == Encoding::ISO2022_JP
+ end
+
+ it "returns an empty String if self was not given anything to convert" do
+ @ec.finish.should == ""
+ end
+
+ it "returns an empty String on subsequent invocations" do
+ @ec.finish.should == ""
+ @ec.finish.should == ""
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/insert_output_spec.rb b/spec/ruby/core/encoding/converter/insert_output_spec.rb
new file mode 100644
index 0000000000..1346adde1e
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/insert_output_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#insert_output" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/encoding/converter/inspect_spec.rb b/spec/ruby/core/encoding/converter/inspect_spec.rb
new file mode 100644
index 0000000000..3170ee451f
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/inspect_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#inspect" do
+ it "includes the source and destination encodings in the return value" do
+ source = Encoding::UTF_8
+ destination = Encoding::UTF_16LE
+
+ output = "#<Encoding::Converter: #{source.name} to #{destination.name}>"
+
+ x = Encoding::Converter.new(source, destination)
+ x.inspect.should == output
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/last_error_spec.rb b/spec/ruby/core/encoding/converter/last_error_spec.rb
new file mode 100644
index 0000000000..68567737b7
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/last_error_spec.rb
@@ -0,0 +1,91 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#last_error" do
+ it "returns nil when the no conversion has been attempted" do
+ ec = Encoding::Converter.new('ascii','utf-8')
+ ec.last_error.should be_nil
+ end
+
+ it "returns nil when the last conversion did not produce an error" do
+ ec = Encoding::Converter.new('ascii','utf-8')
+ ec.convert('a'.force_encoding('ascii'))
+ ec.last_error.should be_nil
+ end
+
+ it "returns nil when #primitive_convert last returned :destination_buffer_full" do
+ ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
+ ec.primitive_convert("\u{9999}", "", 0, 0, partial_input: false) \
+ .should == :destination_buffer_full
+ ec.last_error.should be_nil
+ end
+
+ it "returns nil when #primitive_convert last returned :finished" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("glark".force_encoding('utf-8'),"").should == :finished
+ ec.last_error.should be_nil
+ end
+
+ it "returns nil if the last conversion succeeded but the penultimate failed" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence
+ ec.primitive_convert("glark".force_encoding('utf-8'),"").should == :finished
+ ec.last_error.should be_nil
+ end
+
+ it "returns an Encoding::InvalidByteSequenceError when #primitive_convert last returned :invalid_byte_sequence" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence
+ ec.last_error.should be_an_instance_of(Encoding::InvalidByteSequenceError)
+ end
+
+ it "returns an Encoding::UndefinedConversionError when #primitive_convert last returned :undefined_conversion" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("\u{9876}","").should == :undefined_conversion
+ ec.last_error.should be_an_instance_of(Encoding::UndefinedConversionError)
+ end
+
+ it "returns an Encoding::InvalidByteSequenceError when #primitive_convert last returned :incomplete_input" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.primitive_convert("\xa4", "", nil, 10).should == :incomplete_input
+ ec.last_error.should be_an_instance_of(Encoding::InvalidByteSequenceError)
+ end
+
+ it "returns an Encoding::InvalidByteSequenceError when the last call to #convert produced one" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ exception = nil
+ -> {
+ ec.convert("\xf1abcd")
+ }.should raise_error(Encoding::InvalidByteSequenceError) { |e|
+ exception = e
+ }
+ ec.last_error.should be_an_instance_of(Encoding::InvalidByteSequenceError)
+ ec.last_error.message.should == exception.message
+ end
+
+ it "returns an Encoding::UndefinedConversionError when the last call to #convert produced one" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ exception = nil
+ -> {
+ ec.convert("\u{9899}")
+ }.should raise_error(Encoding::UndefinedConversionError) { |e|
+ exception = e
+ }
+ ec.last_error.should be_an_instance_of(Encoding::UndefinedConversionError)
+ ec.last_error.message.should == exception.message
+ ec.last_error.message.should include "from UTF-8 to ISO-8859-1"
+ end
+
+ it "returns the last error of #convert with a message showing the transcoding path" do
+ ec = Encoding::Converter.new("iso-8859-1", "Big5")
+ exception = nil
+ -> {
+ ec.convert("\xE9") # é in ISO-8859-1
+ }.should raise_error(Encoding::UndefinedConversionError) { |e|
+ exception = e
+ }
+ ec.last_error.should be_an_instance_of(Encoding::UndefinedConversionError)
+ ec.last_error.message.should == exception.message
+ ec.last_error.message.should include "from ISO-8859-1 to UTF-8 to Big5"
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/new_spec.rb b/spec/ruby/core/encoding/converter/new_spec.rb
new file mode 100644
index 0000000000..1f7affc72b
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/new_spec.rb
@@ -0,0 +1,119 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter.new" do
+ it "accepts a String for the source encoding" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8")
+ conv.source_encoding.should == Encoding::US_ASCII
+ end
+
+ it "accepts a String for the destination encoding" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8")
+ conv.destination_encoding.should == Encoding::UTF_8
+ end
+
+ it "accepts an Encoding object for the source encoding" do
+ conv = Encoding::Converter.new(Encoding::US_ASCII, "utf-8")
+ conv.source_encoding.should == Encoding::US_ASCII
+ end
+
+ it "accepts an Encoding object for the destination encoding" do
+ conv = Encoding::Converter.new("us-ascii", Encoding::UTF_8)
+ conv.destination_encoding.should == Encoding::UTF_8
+ end
+
+ it "raises an Encoding::ConverterNotFoundError if both encodings are the same" do
+ -> do
+ Encoding::Converter.new "utf-8", "utf-8"
+ end.should raise_error(Encoding::ConverterNotFoundError)
+ end
+
+ it "calls #to_str to convert the source encoding argument to an encoding name" do
+ enc = mock("us-ascii")
+ enc.should_receive(:to_str).and_return("us-ascii")
+ conv = Encoding::Converter.new(enc, "utf-8")
+ conv.source_encoding.should == Encoding::US_ASCII
+ end
+
+ it "calls #to_str to convert the destination encoding argument to an encoding name" do
+ enc = mock("utf-8")
+ enc.should_receive(:to_str).and_return("utf-8")
+ conv = Encoding::Converter.new("us-ascii", enc)
+ conv.destination_encoding.should == Encoding::UTF_8
+ end
+
+ it "sets replacement from the options Hash" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8", replace: "fubar")
+ conv.replacement.should == "fubar"
+ end
+
+ it "calls #to_hash to convert the options argument to a Hash if not an Integer" do
+ opts = mock("encoding converter options")
+ opts.should_receive(:to_hash).and_return({ replace: "fubar" })
+ conv = Encoding::Converter.new("us-ascii", "utf-8", **opts)
+ conv.replacement.should == "fubar"
+ end
+
+ it "calls #to_str to convert the replacement object to a String" do
+ obj = mock("encoding converter replacement")
+ obj.should_receive(:to_str).and_return("fubar")
+ conv = Encoding::Converter.new("us-ascii", "utf-8", replace: obj)
+ conv.replacement.should == "fubar"
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock("encoding converter replacement")
+ obj.should_receive(:to_str).and_return(1)
+
+ -> do
+ Encoding::Converter.new("us-ascii", "utf-8", replace: obj)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed true for the replacement object" do
+ -> do
+ Encoding::Converter.new("us-ascii", "utf-8", replace: true)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed false for the replacement object" do
+ -> do
+ Encoding::Converter.new("us-ascii", "utf-8", replace: false)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an Integer for the replacement object" do
+ -> do
+ Encoding::Converter.new("us-ascii", "utf-8", replace: 1)
+ end.should raise_error(TypeError)
+ end
+
+ it "accepts an empty String for the replacement object" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8", replace: "")
+ conv.replacement.should == ""
+ end
+
+ describe "when passed nil for the replacement object" do
+ describe "when the destination encoding is not UTF-8" do
+ it "sets the replacement String to '?'" do
+ conv = Encoding::Converter.new("us-ascii", "binary", replace: nil)
+ conv.replacement.should == "?"
+ end
+
+ it "sets the replacement String encoding to US-ASCII" do
+ conv = Encoding::Converter.new("us-ascii", "binary", replace: nil)
+ conv.replacement.encoding.should == Encoding::US_ASCII
+ end
+
+ it "sets the replacement String to '\\uFFFD'" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8", replace: nil)
+ conv.replacement.should == "\u{fffd}".force_encoding("utf-8")
+ end
+
+ it "sets the replacement String encoding to UTF-8" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8", replace: nil)
+ conv.replacement.encoding.should == Encoding::UTF_8
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/primitive_convert_spec.rb b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb
new file mode 100644
index 0000000000..802d8e7cb1
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb
@@ -0,0 +1,211 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#primitive_convert" do
+ before :each do
+ @ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ end
+
+ it "accepts a nil source buffer" do
+ -> { @ec.primitive_convert(nil,"") }.should_not raise_error
+ end
+
+ it "accepts a String as the source buffer" do
+ -> { @ec.primitive_convert("","") }.should_not raise_error
+ end
+
+ it "accepts nil for the destination byte offset" do
+ -> { @ec.primitive_convert("","", nil) }.should_not raise_error
+ end
+
+ it "accepts an integer for the destination byte offset" do
+ -> { @ec.primitive_convert("","a", 1) }.should_not raise_error
+ end
+
+ it "calls #to_int to convert the destination byte offset" do
+ offset = mock("encoding primitive_convert destination byte offset")
+ offset.should_receive(:to_int).and_return(2)
+ @ec.primitive_convert("abc", result = " ", offset).should == :finished
+ result.should == " abc"
+ end
+
+ it "raises an ArgumentError if the destination byte offset is greater than the bytesize of the destination buffer" do
+ -> { @ec.primitive_convert("","am", 0) }.should_not raise_error
+ -> { @ec.primitive_convert("","am", 1) }.should_not raise_error
+ -> { @ec.primitive_convert("","am", 2) }.should_not raise_error
+ -> { @ec.primitive_convert("","am", 3) }.should raise_error(ArgumentError)
+ end
+
+ it "uses the destination byte offset to determine where to write the result in the destination buffer" do
+ dest = "aa"
+ @ec.primitive_convert("b",dest, nil, 0)
+ dest.should == "aa"
+
+ @ec.primitive_convert("b",dest, nil, 1)
+ dest.should == "aab"
+
+ @ec.primitive_convert("b",dest, nil, 2)
+ dest.should == "aabbb"
+ end
+
+ it "accepts nil for the destination bytesize" do
+ -> { @ec.primitive_convert("","", nil, nil) }.should_not raise_error
+ end
+
+ it "accepts an integer for the destination bytesize" do
+ -> { @ec.primitive_convert("","", nil, 0) }.should_not raise_error
+ end
+
+ it "allows a destination bytesize value greater than the bytesize of the source buffer" do
+ -> { @ec.primitive_convert("am","", nil, 3) }.should_not raise_error
+ end
+
+ it "allows a destination bytesize value less than the bytesize of the source buffer" do
+ -> { @ec.primitive_convert("am","", nil, 1) }.should_not raise_error
+ end
+
+ it "calls #to_int to convert the destination byte size" do
+ size = mock("encoding primitive_convert destination byte size")
+ size.should_receive(:to_int).and_return(2)
+ @ec.primitive_convert("abc", result = " ", 0, size).should == :destination_buffer_full
+ result.should == "ab"
+ end
+
+ it "uses destination bytesize as the maximum bytesize of the destination buffer" do
+ dest = ""
+ @ec.primitive_convert("glark", dest, nil, 1)
+ dest.bytesize.should == 1
+ end
+
+ it "allows a destination buffer of unlimited size if destination bytesize is nil" do
+ source = "glark".force_encoding('utf-8')
+ dest = ""
+ @ec.primitive_convert("glark", dest, nil, nil)
+ dest.bytesize.should == source.bytesize
+ end
+
+ it "accepts an options hash" do
+ @ec.primitive_convert("","",nil,nil, after_output: true).should == :finished
+ end
+
+ it "sets the destination buffer's encoding to the destination encoding if the conversion succeeded" do
+ dest = "".force_encoding('utf-8')
+ dest.encoding.should == Encoding::UTF_8
+ @ec.primitive_convert("\u{98}",dest).should == :finished
+ dest.encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "sets the destination buffer's encoding to the destination encoding if the conversion failed" do
+ dest = "".force_encoding('utf-8')
+ dest.encoding.should == Encoding::UTF_8
+ @ec.primitive_convert("\u{9878}",dest).should == :undefined_conversion
+ dest.encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "removes the undefined part from the source buffer when returning :undefined_conversion" do
+ dest = "".force_encoding('utf-8')
+ s = "\u{9878}abcd"
+ @ec.primitive_convert(s, dest).should == :undefined_conversion
+
+ s.should == "abcd"
+ end
+
+ it "returns :incomplete_input when source buffer ends unexpectedly and :partial_input isn't specified" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.primitive_convert("\xa4", "", nil, nil, partial_input: false).should == :incomplete_input
+ end
+
+ it "clears the source buffer when returning :incomplete_input" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ s = "\xa4"
+ ec.primitive_convert(s, "").should == :incomplete_input
+
+ s.should == ""
+ end
+
+ it "returns :source_buffer_empty when source buffer ends unexpectedly and :partial_input is true" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.primitive_convert("\xa4", "", nil, nil, partial_input: true).should == :source_buffer_empty
+ end
+
+ it "clears the source buffer when returning :source_buffer_empty" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ s = "\xa4"
+ ec.primitive_convert(s, "", nil, nil, partial_input: true).should == :source_buffer_empty
+
+ s.should == ""
+ end
+
+ it "returns :undefined_conversion when a character in the source buffer is not representable in the output encoding" do
+ @ec.primitive_convert("\u{9876}","").should == :undefined_conversion
+ end
+
+ it "returns :invalid_byte_sequence when an invalid byte sequence was found in the source buffer" do
+ @ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence
+ end
+
+ it "removes consumed and erroneous bytes from the source buffer when returning :invalid_byte_sequence" do
+ ec = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC)
+ s = "\xC3\xA1\x80\x80\xC3\xA1".force_encoding("utf-8")
+ dest = "".force_encoding("utf-8")
+ ec.primitive_convert(s, dest)
+
+ s.should == "\x80\xC3\xA1".force_encoding("utf-8")
+ end
+
+ it "returns :finished when the conversion succeeded" do
+ @ec.primitive_convert("glark".force_encoding('utf-8'),"").should == :finished
+ end
+
+ it "clears the source buffer when returning :finished" do
+ s = "glark".force_encoding('utf-8')
+ @ec.primitive_convert(s, "").should == :finished
+
+ s.should == ""
+ end
+
+ it "returns :destination_buffer_full when the destination buffer is too small" do
+ ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
+ source = "\u{9999}"
+ destination_bytesize = source.bytesize - 1
+ ec.primitive_convert(source, "", 0, destination_bytesize) \
+ .should == :destination_buffer_full
+ source.should == ""
+ end
+
+ it "clears the source buffer when returning :destination_buffer_full" do
+ ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
+ s = "\u{9999}"
+ destination_bytesize = s.bytesize - 1
+ ec.primitive_convert(s, "", 0, destination_bytesize).should == :destination_buffer_full
+
+ s.should == ""
+ end
+
+ it "keeps removing invalid bytes from the source buffer" do
+ ec = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC)
+ s = "\x80\x80\x80"
+ dest = "".force_encoding(Encoding::UTF_8_MAC)
+
+ ec.primitive_convert(s, dest)
+ s.should == "\x80\x80"
+ ec.primitive_convert(s, dest)
+ s.should == "\x80"
+ ec.primitive_convert(s, dest)
+ s.should == ""
+ end
+
+ it "reuses read-again bytes after the first error" do
+ s = "\xf1abcd"
+ dest = ""
+
+ @ec.primitive_convert(s, dest).should == :invalid_byte_sequence
+ s.should == "bcd"
+ @ec.primitive_errinfo[4].should == "a"
+
+ @ec.primitive_convert(s, dest).should == :finished
+ s.should == ""
+
+ dest.should == "abcd"
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb
new file mode 100644
index 0000000000..1f836b259f
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb
@@ -0,0 +1,68 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#primitive_errinfo" do
+ it "returns [:source_buffer_empty,nil,nil,nil,nil] when no conversion has been attempted" do
+ ec = Encoding::Converter.new('ascii','utf-8')
+ ec.primitive_errinfo.should == [:source_buffer_empty, nil, nil, nil, nil]
+ end
+
+ it "returns [:finished,nil,nil,nil,nil] when #primitive_convert last returned :finished" do
+ ec = Encoding::Converter.new('ascii','utf-8')
+ ec.primitive_convert("a","").should == :finished
+ ec.primitive_errinfo.should == [:finished, nil, nil, nil, nil]
+ end
+
+ it "returns [:source_buffer_empty,nil,nil,nil, nil] when #convert last succeeded" do
+ ec = Encoding::Converter.new('ascii','utf-8')
+ ec.convert("a".force_encoding('ascii')).should == "a".force_encoding('utf-8')
+ ec.primitive_errinfo.should == [:source_buffer_empty, nil, nil, nil, nil]
+ end
+
+ it "returns [:destination_buffer_full,nil,nil,nil,nil] when #primitive_convert last returned :destination_buffer_full" do
+ ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
+ ec.primitive_convert("\u{9999}", "", 0, 0, partial_input: false).should == :destination_buffer_full
+ ec.primitive_errinfo.should == [:destination_buffer_full, nil, nil, nil, nil]
+ end
+
+ it "returns the status of the last primitive conversion, even if it was successful and the previous one wasn't" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence
+ ec.primitive_convert("glark".force_encoding('utf-8'),"").should == :finished
+ ec.primitive_errinfo.should == [:finished, nil, nil, nil, nil]
+ end
+
+ it "returns the state, source encoding, target encoding, and the erroneous bytes when #primitive_convert last returned :undefined_conversion" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("\u{9876}","").should == :undefined_conversion
+ ec.primitive_errinfo.should ==
+ [:undefined_conversion, "UTF-8", "ISO-8859-1", "\xE9\xA1\xB6", ""]
+ end
+
+ it "returns the state, source encoding, target encoding, and erroneous bytes when #primitive_convert last returned :incomplete_input" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.primitive_convert("\xa4", "", nil, 10).should == :incomplete_input
+ ec.primitive_errinfo.should == [:incomplete_input, "EUC-JP", "UTF-8", "\xA4", ""]
+ end
+
+ it "returns the state, source encoding, target encoding, erroneous bytes, and the read-again bytes when #primitive_convert last returned :invalid_byte_sequence" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence
+ ec.primitive_errinfo.should ==
+ [:invalid_byte_sequence, "UTF-8", "ISO-8859-1", "\xF1", "a"]
+ end
+
+ it "returns the state, source encoding, target encoding, erroneous bytes, and the read-again bytes when #convert last raised InvalidByteSequenceError" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ -> { ec.convert("\xf1abcd") }.should raise_error(Encoding::InvalidByteSequenceError)
+ ec.primitive_errinfo.should ==
+ [:invalid_byte_sequence, "UTF-8", "ISO-8859-1", "\xF1", "a"]
+ end
+
+ it "returns the state, source encoding, target encoding, erroneous bytes, and the read-again bytes when #finish last raised InvalidByteSequenceError" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.convert("\xa4")
+ -> { ec.finish }.should raise_error(Encoding::InvalidByteSequenceError)
+ ec.primitive_errinfo.should == [:incomplete_input, "EUC-JP", "UTF-8", "\xA4", ""]
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/putback_spec.rb b/spec/ruby/core/encoding/converter/putback_spec.rb
new file mode 100644
index 0000000000..c4e0a5da21
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/putback_spec.rb
@@ -0,0 +1,56 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#putback" do
+ before :each do
+ @ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ @ret = @ec.primitive_convert(@src="abc\xa1def", @dst="", nil, 10)
+ end
+
+ it "returns a String" do
+ @ec.putback.should be_an_instance_of(String)
+ end
+
+ it "returns a String in the source encoding" do
+ @ec.putback.encoding.should == Encoding::EUC_JP
+ end
+
+ it "returns the bytes buffered due to an :invalid_byte_sequence error" do
+ @ret.should == :invalid_byte_sequence
+ @ec.putback.should == 'd'
+ @ec.primitive_errinfo.last.should == 'd'
+ end
+
+ it "allows conversion to be resumed after an :invalid_byte_sequence" do
+ @src = @ec.putback + @src
+ @ret = @ec.primitive_convert(@src, @dst, nil, 10)
+ @ret.should == :finished
+ @dst.should == "abcdef"
+ @src.should == ""
+ end
+
+ it "returns an empty String when there are no more bytes to put back" do
+ @ec.putback
+ @ec.putback.should == ""
+ end
+
+ it "returns the problematic bytes for UTF-16LE" do
+ ec = Encoding::Converter.new("utf-16le", "iso-8859-1")
+ src = "\x00\xd8\x61\x00"
+ dst = ""
+ ec.primitive_convert(src, dst).should == :invalid_byte_sequence
+ ec.primitive_errinfo.should == [:invalid_byte_sequence, "UTF-16LE", "UTF-8", "\x00\xD8", "a\x00"]
+ ec.putback.should == "a\x00".force_encoding("utf-16le")
+ ec.putback.should == ""
+ end
+
+ it "accepts an integer argument corresponding to the number of bytes to be put back" do
+ ec = Encoding::Converter.new("utf-16le", "iso-8859-1")
+ src = "\x00\xd8\x61\x00"
+ dst = ""
+ ec.primitive_convert(src, dst).should == :invalid_byte_sequence
+ ec.primitive_errinfo.should == [:invalid_byte_sequence, "UTF-16LE", "UTF-8", "\x00\xD8", "a\x00"]
+ ec.putback(2).should == "a\x00".force_encoding("utf-16le")
+ ec.putback.should == ""
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/replacement_spec.rb b/spec/ruby/core/encoding/converter/replacement_spec.rb
new file mode 100644
index 0000000000..5ca42e7e5a
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/replacement_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#replacement" do
+ it "returns '?' in US-ASCII when the destination encoding is not UTF-8" do
+ ec = Encoding::Converter.new("utf-8", "us-ascii")
+ ec.replacement.should == "?"
+ ec.replacement.encoding.should == Encoding::US_ASCII
+
+ ec = Encoding::Converter.new("utf-8", "sjis")
+ ec.replacement.should == "?"
+ ec.replacement.encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns \\uFFFD when the destination encoding is UTF-8" do
+ ec = Encoding::Converter.new("us-ascii", "utf-8")
+ ec.replacement.should == "\u{fffd}".force_encoding('utf-8')
+ ec.replacement.encoding.should == Encoding::UTF_8
+ end
+end
+
+describe "Encoding::Converter#replacement=" do
+ it "accepts a String argument" do
+ ec = Encoding::Converter.new("utf-8", "us-ascii")
+ ec.replacement = "!"
+ ec.replacement.should == "!"
+ end
+
+ it "accepts a String argument of arbitrary length" do
+ ec = Encoding::Converter.new("utf-8", "us-ascii")
+ ec.replacement = "?!?" * 9999
+ ec.replacement.should == "?!?" * 9999
+ end
+
+ it "raises a TypeError if assigned a non-String argument" do
+ ec = Encoding::Converter.new("utf-8", "us-ascii")
+ -> { ec.replacement = nil }.should raise_error(TypeError)
+ end
+
+ it "sets #replacement" do
+ ec = Encoding::Converter.new("us-ascii", "utf-8")
+ ec.replacement.should == "\u{fffd}".force_encoding('utf-8')
+ ec.replacement = '?'.encode('utf-8')
+ ec.replacement.should == '?'.force_encoding('utf-8')
+ end
+
+ it "raises an UndefinedConversionError is the argument cannot be converted into the destination encoding" do
+ ec = Encoding::Converter.new("sjis", "ascii")
+ utf8_q = "\u{986}".force_encoding('utf-8')
+ ec.primitive_convert(utf8_q.dup, "").should == :undefined_conversion
+ -> { ec.replacement = utf8_q }.should \
+ raise_error(Encoding::UndefinedConversionError)
+ end
+
+ it "does not change the replacement character if the argument cannot be converted into the destination encoding" do
+ ec = Encoding::Converter.new("sjis", "ascii")
+ utf8_q = "\u{986}".force_encoding('utf-8')
+ ec.primitive_convert(utf8_q.dup, "").should == :undefined_conversion
+ -> { ec.replacement = utf8_q }.should \
+ raise_error(Encoding::UndefinedConversionError)
+ ec.replacement.should == "?".force_encoding('us-ascii')
+ end
+
+ it "uses the replacement character" do
+ ec = Encoding::Converter.new("utf-8", "us-ascii", :invalid => :replace, :undef => :replace)
+ ec.replacement = "!"
+ dest = ""
+ status = ec.primitive_convert "中文123", dest
+
+ status.should == :finished
+ dest.should == "!!123"
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/search_convpath_spec.rb b/spec/ruby/core/encoding/converter/search_convpath_spec.rb
new file mode 100644
index 0000000000..0882af5539
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/search_convpath_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter.search_convpath" do
+ it "returns an Array with a single element if there is a direct converter" do
+ cp = Encoding::Converter.search_convpath('ASCII', 'UTF-8')
+ cp.should == [[Encoding::US_ASCII, Encoding::UTF_8]]
+ end
+
+ it "returns multiple encoding pairs when direct conversion is impossible" do
+ cp = Encoding::Converter.search_convpath('ascii','Big5')
+ cp.should == [
+ [Encoding::US_ASCII, Encoding::UTF_8],
+ [Encoding::UTF_8, Encoding::Big5]
+ ]
+ end
+
+ it "indicates if crlf_newline conversion would occur" do
+ cp = Encoding::Converter.search_convpath("ISO-8859-1", "EUC-JP", crlf_newline: true)
+ cp.last.should == "crlf_newline"
+
+ cp = Encoding::Converter.search_convpath("ASCII", "UTF-8", crlf_newline: false)
+ cp.last.should_not == "crlf_newline"
+ end
+
+ it "raises an Encoding::ConverterNotFoundError if no conversion path exists" do
+ -> do
+ Encoding::Converter.search_convpath(Encoding::BINARY, Encoding::Emacs_Mule)
+ end.should raise_error(Encoding::ConverterNotFoundError)
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/source_encoding_spec.rb b/spec/ruby/core/encoding/converter/source_encoding_spec.rb
new file mode 100644
index 0000000000..6196f717bd
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/source_encoding_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#source_encoding" do
+ it "returns the source encoding as an Encoding object" do
+ ec = Encoding::Converter.new('ASCII','Big5')
+ ec.source_encoding.should == Encoding::US_ASCII
+
+ ec = Encoding::Converter.new('Shift_JIS','EUC-JP')
+ ec.source_encoding.should == Encoding::SHIFT_JIS
+ end
+end
diff --git a/spec/ruby/core/encoding/default_external_spec.rb b/spec/ruby/core/encoding/default_external_spec.rb
new file mode 100644
index 0000000000..682d49d37c
--- /dev/null
+++ b/spec/ruby/core/encoding/default_external_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.default_external" do
+ before :each do
+ @original_encoding = Encoding.default_external
+ end
+
+ after :each do
+ Encoding.default_external = @original_encoding
+ end
+
+ it "returns an Encoding object" do
+ Encoding.default_external.should be_an_instance_of(Encoding)
+ end
+
+ it "returns the default external encoding" do
+ Encoding.default_external = Encoding::SHIFT_JIS
+ Encoding.default_external.should == Encoding::SHIFT_JIS
+ end
+
+ ruby_version_is "3.0" do
+ platform_is :windows do
+ it 'is UTF-8 by default on Windows' do
+ Encoding.default_external.should == Encoding::UTF_8
+ end
+ end
+ end
+end
+
+describe "Encoding.default_external=" do
+ before :each do
+ @original_encoding = Encoding.default_external
+ end
+
+ after :each do
+ Encoding.default_external = @original_encoding
+ end
+
+ it "sets the default external encoding" do
+ Encoding.default_external = Encoding::SHIFT_JIS
+ Encoding.default_external.should == Encoding::SHIFT_JIS
+ Encoding.find('external').should == Encoding::SHIFT_JIS
+ end
+
+ platform_is_not :windows do
+ it "also sets the filesystem encoding" do
+ Encoding.default_external = Encoding::SHIFT_JIS
+ Encoding.find('filesystem').should == Encoding::SHIFT_JIS
+ end
+ end
+
+ it "can accept a name of an encoding as a String" do
+ Encoding.default_external = 'Shift_JIS'
+ Encoding.default_external.should == Encoding::SHIFT_JIS
+ end
+
+ it "calls #to_s on arguments that are neither Strings nor Encodings" do
+ string = mock('string')
+ string.should_receive(:to_str).at_least(1).and_return('US-ASCII')
+ Encoding.default_external = string
+ Encoding.default_external.should == Encoding::ASCII
+ end
+
+ it "raises a TypeError unless the argument is an Encoding or convertible to a String" do
+ -> { Encoding.default_external = [] }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError if the argument is nil" do
+ -> { Encoding.default_external = nil }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/encoding/default_internal_spec.rb b/spec/ruby/core/encoding/default_internal_spec.rb
new file mode 100644
index 0000000000..855f4e9f32
--- /dev/null
+++ b/spec/ruby/core/encoding/default_internal_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.default_internal" do
+ before :each do
+ @original_encoding = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @original_encoding
+ end
+
+ it "is nil by default" do
+ Encoding.default_internal.should be_nil
+ end
+
+ it "returns an Encoding object if a default internal encoding is set" do
+ Encoding.default_internal = Encoding::ASCII
+ Encoding.default_internal.should be_an_instance_of(Encoding)
+ end
+
+ it "returns nil if no default internal encoding is set" do
+ Encoding.default_internal = nil
+ Encoding.default_internal.should be_nil
+ end
+
+ it "returns the default internal encoding" do
+ Encoding.default_internal = Encoding::BINARY
+ Encoding.default_internal.should == Encoding::BINARY
+ end
+end
+
+describe "Encoding.default_internal=" do
+ before :each do
+ @original_encoding = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @original_encoding
+ end
+
+ it "sets the default internal encoding" do
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ Encoding.default_internal.should == Encoding::SHIFT_JIS
+ end
+
+ it "can accept a name of an encoding as a String" do
+ Encoding.default_internal = 'Shift_JIS'
+ Encoding.default_internal.should == Encoding::SHIFT_JIS
+ end
+
+ it "calls #to_str to convert an object to a String" do
+ obj = mock('string')
+ obj.should_receive(:to_str).at_least(1).times.and_return('ascii')
+
+ Encoding.default_internal = obj
+ Encoding.default_internal.should == Encoding::ASCII
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock('string')
+ obj.should_receive(:to_str).at_least(1).times.and_return(1)
+
+ -> { Encoding.default_internal = obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed an object not providing #to_str" do
+ -> { Encoding.default_internal = mock("encoding") }.should raise_error(TypeError)
+ end
+
+ it "accepts an argument of nil to unset the default internal encoding" do
+ Encoding.default_internal = nil
+ Encoding.default_internal.should be_nil
+ end
+end
diff --git a/spec/ruby/core/encoding/dummy_spec.rb b/spec/ruby/core/encoding/dummy_spec.rb
new file mode 100644
index 0000000000..75ffcd5a4e
--- /dev/null
+++ b/spec/ruby/core/encoding/dummy_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "Encoding#dummy?" do
+ it "returns false for proper encodings" do
+ Encoding::UTF_8.dummy?.should be_false
+ Encoding::ASCII.dummy?.should be_false
+ end
+
+ it "returns true for dummy encodings" do
+ Encoding::ISO_2022_JP.dummy?.should be_true
+ Encoding::CP50221.dummy?.should be_true
+ Encoding::UTF_7.dummy?.should be_true
+ end
+end
diff --git a/spec/ruby/core/encoding/find_spec.rb b/spec/ruby/core/encoding/find_spec.rb
new file mode 100644
index 0000000000..8a0873070f
--- /dev/null
+++ b/spec/ruby/core/encoding/find_spec.rb
@@ -0,0 +1,82 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.find" do
+ before :all do
+ @encodings = Encoding.aliases.to_a.flatten.uniq
+ end
+
+ it "returns the corresponding Encoding object if given a valid encoding name" do
+ @encodings.each do |enc|
+ Encoding.find(enc).should be_an_instance_of(Encoding)
+ end
+ end
+
+ it "returns the corresponding Encoding object if given a valid alias name" do
+ Encoding.aliases.keys.each do |enc_alias|
+ Encoding.find(enc_alias).should be_an_instance_of(Encoding)
+ end
+ end
+
+ it "raises a TypeError if passed a Symbol" do
+ -> { Encoding.find(:"utf-8") }.should raise_error(TypeError)
+ end
+
+ it "returns the passed Encoding object" do
+ Encoding.find(Encoding::UTF_8).should == Encoding::UTF_8
+ end
+
+ it "accepts encoding names as Strings" do
+ Encoding.list.each do |enc|
+ Encoding.find(enc.name).should == enc
+ end
+ end
+
+ it "accepts any object as encoding name, if it responds to #to_str" do
+ obj = Class.new do
+ attr_writer :encoding_name
+ def to_str; @encoding_name; end
+ end.new
+
+ Encoding.list.each do |enc|
+ obj.encoding_name = enc.name
+ Encoding.find(obj).should == enc
+ end
+ end
+
+ it "is case insensitive" do
+ @encodings.each do |enc|
+ Encoding.find(enc.upcase).should == Encoding.find(enc)
+ end
+ end
+
+ it "raises an ArgumentError if the given encoding does not exist" do
+ -> { Encoding.find('dh2dh278d') }.should raise_error(ArgumentError)
+ end
+
+ # Not sure how to do a better test, since locale depends on weird platform-specific stuff
+ it "supports the 'locale' encoding alias" do
+ enc = Encoding.find('locale')
+ enc.should_not == nil
+ end
+
+ it "returns default external encoding for the 'external' encoding alias" do
+ enc = Encoding.find('external')
+ enc.should == Encoding.default_external
+ end
+
+ it "returns default internal encoding for the 'internal' encoding alias" do
+ enc = Encoding.find('internal')
+ enc.should == Encoding.default_internal
+ end
+
+ platform_is_not :windows do
+ it "uses default external encoding for the 'filesystem' encoding alias" do
+ enc = Encoding.find('filesystem')
+ enc.should == Encoding.default_external
+ end
+ end
+
+ platform_is :windows do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/core/encoding/fixtures/classes.rb b/spec/ruby/core/encoding/fixtures/classes.rb
new file mode 100644
index 0000000000..12e9a4f348
--- /dev/null
+++ b/spec/ruby/core/encoding/fixtures/classes.rb
@@ -0,0 +1,49 @@
+# -*- encoding: binary -*-
+module EncodingSpecs
+ class UndefinedConversionError
+ def self.exception
+ ec = Encoding::Converter.new('utf-8','ascii')
+ begin
+ ec.convert("\u{8765}")
+ rescue Encoding::UndefinedConversionError => e
+ e
+ end
+ end
+ end
+
+ class UndefinedConversionErrorIndirect
+ def self.exception
+ ec = Encoding::Converter.new("ISO-8859-1", "EUC-JP")
+ begin
+ ec.convert("\xA0")
+ rescue Encoding::UndefinedConversionError => e
+ e
+ end
+ end
+ end
+
+ class InvalidByteSequenceError
+ def self.exception
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ begin
+ ec.convert("\xf1abcd")
+ rescue Encoding::InvalidByteSequenceError => e
+ # Return the exception object and the primitive_errinfo Array
+ [e, ec.primitive_errinfo]
+ end
+ end
+ end
+
+ class InvalidByteSequenceErrorIndirect
+ def self.exception
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ begin
+ ec.convert("abc\xA1\xFFdef")
+ rescue Encoding::InvalidByteSequenceError => e
+ # Return the exception object and the discarded bytes reported by
+ # #primitive_errinfo
+ [e, ec.primitive_errinfo]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/inspect_spec.rb b/spec/ruby/core/encoding/inspect_spec.rb
new file mode 100644
index 0000000000..9a930b2a77
--- /dev/null
+++ b/spec/ruby/core/encoding/inspect_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Encoding#inspect" do
+ it "returns a String" do
+ Encoding::UTF_8.inspect.should be_an_instance_of(String)
+ end
+
+ it "returns #<Encoding:name> for a non-dummy encoding named 'name'" do
+ Encoding.list.to_a.reject {|e| e.dummy? }.each do |enc|
+ enc.inspect.should =~ /#<Encoding:#{enc.name}>/
+ end
+ end
+
+ it "returns #<Encoding:name (dummy)> for a dummy encoding named 'name'" do
+ Encoding.list.to_a.select {|e| e.dummy? }.each do |enc|
+ enc.inspect.should =~ /#<Encoding:#{enc.name} \(dummy\)>/
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_name_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_name_spec.rb
new file mode 100644
index 0000000000..f5fa6f55e3
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_name_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../fixtures/classes'
+
+describe "Encoding::InvalidByteSequenceError#destination_encoding_name" do
+ before :each do
+ @exception, = EncodingSpecs::InvalidByteSequenceError.exception
+ @exception2, = EncodingSpecs::InvalidByteSequenceErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.destination_encoding_name.should be_an_instance_of(String)
+ @exception2.destination_encoding_name.should be_an_instance_of(String)
+ end
+
+ it "is equal to the destination encoding name of the object that raised it" do
+ @exception.destination_encoding_name.should == "ISO-8859-1"
+ @exception2.destination_encoding_name.should == "UTF-8"
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_spec.rb
new file mode 100644
index 0000000000..43be3ddd71
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../fixtures/classes'
+
+describe "Encoding::InvalidByteSequenceError#destination_encoding" do
+ before :each do
+ @exception, = EncodingSpecs::InvalidByteSequenceError.exception
+ @exception2, = EncodingSpecs::InvalidByteSequenceErrorIndirect.exception
+ end
+
+ it "returns an Encoding object" do
+ @exception.destination_encoding.should be_an_instance_of(Encoding)
+ @exception2.destination_encoding.should be_an_instance_of(Encoding)
+ end
+
+ it "is equal to the destination encoding of the object that raised it" do
+ @exception.destination_encoding.should == Encoding::ISO_8859_1
+ @exception2.destination_encoding.should == Encoding::UTF_8
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb
new file mode 100644
index 0000000000..a8f7354b16
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb
@@ -0,0 +1,30 @@
+# -*- encoding: binary -*-
+require_relative '../fixtures/classes'
+
+describe "Encoding::InvalidByteSequenceError#error_bytes" do
+ before :each do
+ @exception, @errinfo = EncodingSpecs::InvalidByteSequenceError.exception
+ @exception2, @errinfo2 = EncodingSpecs::InvalidByteSequenceErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.error_bytes.should be_an_instance_of(String)
+ @exception2.error_bytes.should be_an_instance_of(String)
+ end
+
+ it "returns the bytes that caused the exception" do
+ @exception.error_bytes.size.should == 1
+ @exception.error_bytes.should == "\xF1"
+ @exception.error_bytes.should == @errinfo[-2]
+
+ @exception2.error_bytes.size.should == 1
+ @exception2.error_bytes.should == "\xA1"
+ @exception2.error_bytes.should == @errinfo2[-2]
+ end
+
+ it "uses BINARY as the encoding" do
+ @exception.error_bytes.encoding.should == Encoding::BINARY
+
+ @exception2.error_bytes.encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb
new file mode 100644
index 0000000000..94201a9b15
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb
@@ -0,0 +1,28 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+
+describe "Encoding::InvalidByteSequenceError#incomplete_input?" do
+ it "returns nil by default" do
+ Encoding::InvalidByteSequenceError.new.incomplete_input?.should be_nil
+ end
+
+ it "returns true if #primitive_convert returned :incomplete_input for the same data" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.primitive_convert("\xA1",'').should == :incomplete_input
+ begin
+ ec.convert("\xA1")
+ rescue Encoding::InvalidByteSequenceError => e
+ e.incomplete_input?.should be_true
+ end
+ end
+
+ it "returns false if #primitive_convert returned :invalid_byte_sequence for the same data" do
+ ec = Encoding::Converter.new("ascii", "utf-8")
+ ec.primitive_convert("\xfffffffff",'').should == :invalid_byte_sequence
+ begin
+ ec.convert("\xfffffffff")
+ rescue Encoding::InvalidByteSequenceError => e
+ e.incomplete_input?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb
new file mode 100644
index 0000000000..93823b5db4
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb
@@ -0,0 +1,30 @@
+# -*- encoding: binary -*-
+require_relative '../fixtures/classes'
+
+describe "Encoding::InvalidByteSequenceError#readagain_bytes" do
+ before :each do
+ @exception, @errinfo = EncodingSpecs::InvalidByteSequenceError.exception
+ @exception2, @errinfo2 = EncodingSpecs::InvalidByteSequenceErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.readagain_bytes.should be_an_instance_of(String)
+ @exception2.readagain_bytes.should be_an_instance_of(String)
+ end
+
+ it "returns the bytes to be read again" do
+ @exception.readagain_bytes.size.should == 1
+ @exception.readagain_bytes.should == "a".force_encoding('binary')
+ @exception.readagain_bytes.should == @errinfo[-1]
+
+ @exception2.readagain_bytes.size.should == 1
+ @exception2.readagain_bytes.should == "\xFF".force_encoding('binary')
+ @exception2.readagain_bytes.should == @errinfo2[-1]
+ end
+
+ it "uses BINARY as the encoding" do
+ @exception.readagain_bytes.encoding.should == Encoding::BINARY
+
+ @exception2.readagain_bytes.encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_name_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_name_spec.rb
new file mode 100644
index 0000000000..bd3a51cbc5
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_name_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#source_encoding_name" do
+ before :each do
+ @exception, = EncodingSpecs::UndefinedConversionError.exception
+ @exception2, = EncodingSpecs::UndefinedConversionErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.source_encoding_name.should be_an_instance_of(String)
+ end
+
+ it "is equal to the source encoding name of the object that raised it" do
+ @exception.source_encoding_name.should == "UTF-8"
+ end
+
+ # The source encoding specified in the Encoding::Converter constructor may
+ # differ from the source encoding returned here. What seems to happen is
+ # that when transcoding along a path with multiple pairs of encodings, the
+ # last one encountered when the error occurred is returned. So in this
+ # case, the conversion path is ISO-8859-1 -> UTF-8 -> EUC-JP. The
+ # conversion from ISO-8859-1 -> UTF-8 succeeded, but the conversion from
+ # UTF-8 to EUC-JP failed. IOW, it failed when the source encoding was
+ # UTF-8, so UTF-8 is regarded as the source encoding.
+ it "is equal to the source encoding at the stage of the conversion path where the error occurred" do
+ @exception2.source_encoding_name.should == 'UTF-8'
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_spec.rb
new file mode 100644
index 0000000000..f43d6d5830
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../fixtures/classes'
+
+describe "Encoding::InvalidByteSequenceError#source_encoding" do
+ before :each do
+ @exception, = EncodingSpecs::InvalidByteSequenceError.exception
+ @exception2, = EncodingSpecs::InvalidByteSequenceErrorIndirect.exception
+ end
+
+ it "returns an Encoding object" do
+ @exception.source_encoding.should be_an_instance_of(Encoding)
+ @exception2.source_encoding.should be_an_instance_of(Encoding)
+ end
+
+ it "is equal to the source encoding of the object that raised it" do
+ @exception.source_encoding.should == Encoding::UTF_8
+ end
+
+ # The source encoding specified in the Encoding::Converter constructor may
+ # differ from the source encoding returned here. What seems to happen is
+ # that when transcoding along a path with multiple pairs of encodings, the
+ # last one encountered when the error occurred is returned. So in this
+ # case, the conversion path is EUC-JP -> UTF-8 -> ISO-8859-1. The
+ # conversions failed with the first pair of encodings (i.e. transcoding
+ # from EUC-JP to UTF-8, so UTF-8 is regarded as the source encoding; if
+ # the error had occurred when converting from UTF-8 to ISO-8859-1, UTF-8
+ # would have been the source encoding.
+
+ # FIXME: Derive example where the failure occurs at the UTF-8 ->
+ # ISO-8859-1 case so as to better illustrate the issue
+ it "is equal to the source encoding at the stage of the conversion path where the error occurred" do
+ @exception2.source_encoding.should == Encoding::EUC_JP
+ end
+end
diff --git a/spec/ruby/core/encoding/list_spec.rb b/spec/ruby/core/encoding/list_spec.rb
new file mode 100644
index 0000000000..bd3d5b7bc0
--- /dev/null
+++ b/spec/ruby/core/encoding/list_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.list" do
+ it "returns an Array" do
+ Encoding.list.should be_an_instance_of(Array)
+ end
+
+ it "returns an Array of Encoding objects" do
+ Encoding.list.each do |enc|
+ enc.should be_an_instance_of(Encoding)
+ end
+ end
+
+ it "returns each encoding only once" do
+ orig = Encoding.list.map { |e| e.name }
+ orig.should == orig.uniq
+ end
+
+ it "includes the default external encoding" do
+ Encoding.list.include?(Encoding.default_external).should be_true
+ end
+
+ it "does not include any alias names" do
+ Encoding.aliases.keys.each do |enc_alias|
+ Encoding.list.include?(enc_alias).should be_false
+ end
+ end
+
+ it "includes all aliased encodings" do
+ Encoding.aliases.values.each do |enc_alias|
+ Encoding.list.include?(Encoding.find(enc_alias)).should be_true
+ end
+ end
+
+ it "includes dummy encodings" do
+ Encoding.list.select { |e| e.dummy? }.should_not == []
+ end
+
+ it 'includes UTF-8 encoding' do
+ Encoding.list.should.include?(Encoding::UTF_8)
+ end
+
+ it 'includes CESU-8 encoding' do
+ Encoding.list.should.include?(Encoding::CESU_8)
+ end
+
+ # TODO: Find example that illustrates this
+ it "updates the list when #find is used to load a new encoding"
+end
diff --git a/spec/ruby/core/encoding/locale_charmap_spec.rb b/spec/ruby/core/encoding/locale_charmap_spec.rb
new file mode 100644
index 0000000000..8143b9083a
--- /dev/null
+++ b/spec/ruby/core/encoding/locale_charmap_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.locale_charmap" do
+ it "returns a String" do
+ Encoding.locale_charmap.should be_an_instance_of(String)
+ end
+
+ # FIXME: Get this working on Windows
+ platform_is :linux do
+ platform_is_not :android do
+ it "returns a value based on the LC_ALL environment variable" do
+ old_lc_all = ENV['LC_ALL']
+ ENV['LC_ALL'] = 'C'
+ ruby_exe("print Encoding.locale_charmap").should == 'ANSI_X3.4-1968'
+ ENV['LC_ALL'] = old_lc_all
+ end
+ end
+ end
+
+ platform_is :freebsd, :openbsd, :darwin do
+ it "returns a value based on the LC_ALL environment variable" do
+ old_lc_all = ENV['LC_ALL']
+ ENV['LC_ALL'] = 'C'
+ ruby_exe("print Encoding.locale_charmap").should == 'US-ASCII'
+ ENV['LC_ALL'] = old_lc_all
+ end
+ end
+
+ platform_is :netbsd do
+ it "returns a value based on the LC_ALL environment variable" do
+ old_lc_all = ENV['LC_ALL']
+ ENV['LC_ALL'] = 'C'
+ ruby_exe("print Encoding.locale_charmap").should == '646'
+ ENV['LC_ALL'] = old_lc_all
+ end
+ end
+
+ platform_is :android do
+ it "always returns UTF-8" do
+ old_lc_all = ENV['LC_ALL']
+ ENV['LC_ALL'] = 'C'
+ ruby_exe("print Encoding.locale_charmap").should == 'UTF-8'
+ ENV['LC_ALL'] = old_lc_all
+ end
+ end
+
+ platform_is :bsd, :darwin, :linux do
+ it "is unaffected by assigning to ENV['LC_ALL'] in the same process" do
+ old_charmap = Encoding.locale_charmap
+ old_lc_all = ENV['LC_ALL']
+ ENV['LC_ALL'] = 'C'
+ Encoding.locale_charmap.should == old_charmap
+ ENV['LC_ALL'] = old_lc_all
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/name_list_spec.rb b/spec/ruby/core/encoding/name_list_spec.rb
new file mode 100644
index 0000000000..836381c4d8
--- /dev/null
+++ b/spec/ruby/core/encoding/name_list_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.name_list" do
+ it "returns an Array" do
+ Encoding.name_list.should be_an_instance_of(Array)
+ end
+
+ it "returns encoding names as Strings" do
+ Encoding.name_list.each {|e| e.should be_an_instance_of(String) }
+ end
+
+ it "includes all aliases" do
+ Encoding.aliases.keys.each do |enc_alias|
+ Encoding.name_list.include?(enc_alias).should be_true
+ end
+ end
+
+ it "includes all non-dummy encodings" do
+ Encoding.list.each do |enc|
+ Encoding.name_list.include?(enc.name).should be_true
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/name_spec.rb b/spec/ruby/core/encoding/name_spec.rb
new file mode 100644
index 0000000000..5eadb1d2f5
--- /dev/null
+++ b/spec/ruby/core/encoding/name_spec.rb
@@ -0,0 +1,5 @@
+require_relative 'shared/name'
+
+describe "Encoding#name" do
+ it_behaves_like :encoding_name, :name
+end
diff --git a/spec/ruby/core/encoding/names_spec.rb b/spec/ruby/core/encoding/names_spec.rb
new file mode 100644
index 0000000000..9ded043bbb
--- /dev/null
+++ b/spec/ruby/core/encoding/names_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "Encoding#names" do
+ it "returns an Array" do
+ Encoding.name_list.each do |name|
+ e = Encoding.find(name) or next
+ e.names.should be_an_instance_of(Array)
+ end
+ end
+
+ it "returns names as Strings" do
+ Encoding.name_list.each do |name|
+ e = Encoding.find(name) or next
+ e.names.each do |this_name|
+ this_name.should be_an_instance_of(String)
+ end
+ end
+ end
+
+ it "returns #name as the first value" do
+ Encoding.name_list.each do |name|
+ e = Encoding.find(name) or next
+ e.names.first.should == e.name
+ end
+ end
+
+ it "includes any aliases the encoding has" do
+ Encoding.name_list.each do |name|
+ e = Encoding.find(name) or next
+ aliases = Encoding.aliases.select{|a,n| n == name}.keys
+ names = e.names
+ aliases.each {|a| names.include?(a).should be_true}
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/replicate_spec.rb b/spec/ruby/core/encoding/replicate_spec.rb
new file mode 100644
index 0000000000..848415eeb4
--- /dev/null
+++ b/spec/ruby/core/encoding/replicate_spec.rb
@@ -0,0 +1,75 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+
+describe "Encoding#replicate" do
+ ruby_version_is ""..."3.3" do
+ before :all do
+ @i = 0
+ end
+
+ before :each do
+ @i += 1
+ @prefix = "RS#{@i}"
+ end
+
+ it "returns a replica of ASCII" do
+ name = @prefix + '-ASCII'
+ e = suppress_warning { Encoding::ASCII.replicate(name) }
+ e.name.should == name
+ Encoding.find(name).should == e
+
+ "a".force_encoding(e).valid_encoding?.should be_true
+ "\x80".force_encoding(e).valid_encoding?.should be_false
+ end
+
+ it "returns a replica of UTF-8" do
+ name = @prefix + 'UTF-8'
+ e = suppress_warning { Encoding::UTF_8.replicate(name) }
+ e.name.should == name
+ Encoding.find(name).should == e
+
+ "a".force_encoding(e).valid_encoding?.should be_true
+ "\u3042".force_encoding(e).valid_encoding?.should be_true
+ "\x80".force_encoding(e).valid_encoding?.should be_false
+ end
+
+ it "returns a replica of UTF-16BE" do
+ name = @prefix + 'UTF-16-BE'
+ e = suppress_warning { Encoding::UTF_16BE.replicate(name) }
+ e.name.should == name
+ Encoding.find(name).should == e
+
+ "a".force_encoding(e).valid_encoding?.should be_false
+ "\x30\x42".force_encoding(e).valid_encoding?.should be_true
+ "\x80".force_encoding(e).valid_encoding?.should be_false
+ end
+
+ it "returns a replica of ISO-2022-JP" do
+ name = @prefix + 'ISO-2022-JP'
+ e = suppress_warning { Encoding::ISO_2022_JP.replicate(name) }
+ Encoding.find(name).should == e
+
+ e.name.should == name
+ e.dummy?.should be_true
+ end
+
+ # NOTE: it's unclear of the value of this (for the complexity cost of it),
+ # but it is the current CRuby behavior.
+ it "can be associated with a String" do
+ name = @prefix + '-US-ASCII'
+ e = suppress_warning { Encoding::US_ASCII.replicate(name) }
+ e.name.should == name
+ Encoding.find(name).should == e
+
+ s = "abc".force_encoding(e)
+ s.encoding.should == e
+ s.encoding.name.should == name
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "has been removed" do
+ Encoding::US_ASCII.should_not.respond_to?(:replicate, true)
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/shared/name.rb b/spec/ruby/core/encoding/shared/name.rb
new file mode 100644
index 0000000000..cd37ea06db
--- /dev/null
+++ b/spec/ruby/core/encoding/shared/name.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+describe :encoding_name, shared: true do
+ it "returns a String" do
+ Encoding.list.each do |e|
+ e.send(@method).should be_an_instance_of(String)
+ end
+ end
+
+ it "uniquely identifies an encoding" do
+ Encoding.list.each do |e|
+ e.should == Encoding.find(e.send(@method))
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/to_s_spec.rb b/spec/ruby/core/encoding/to_s_spec.rb
new file mode 100644
index 0000000000..82d282386b
--- /dev/null
+++ b/spec/ruby/core/encoding/to_s_spec.rb
@@ -0,0 +1,5 @@
+require_relative 'shared/name'
+
+describe "Encoding#to_s" do
+ it_behaves_like :encoding_name, :to_s
+end
diff --git a/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_name_spec.rb b/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_name_spec.rb
new file mode 100644
index 0000000000..106fc7ecac
--- /dev/null
+++ b/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_name_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#destination_encoding_name" do
+ before :each do
+ @exception = EncodingSpecs::UndefinedConversionError.exception
+ end
+
+ it "returns a String" do
+ @exception.destination_encoding_name.should be_an_instance_of(String)
+ end
+
+ it "is equal to the destination encoding name of the object that raised it" do
+ @exception.destination_encoding_name.should == "US-ASCII"
+ end
+end
diff --git a/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_spec.rb b/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_spec.rb
new file mode 100644
index 0000000000..c6e24732fd
--- /dev/null
+++ b/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#destination_encoding" do
+ before :each do
+ @exception = EncodingSpecs::UndefinedConversionError.exception
+ end
+
+ it "returns an Encoding object" do
+ @exception.destination_encoding.should be_an_instance_of(Encoding)
+ end
+
+ it "is equal to the destination encoding of the object that raised it" do
+ @exception.destination_encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/encoding/undefined_conversion_error/error_char_spec.rb b/spec/ruby/core/encoding/undefined_conversion_error/error_char_spec.rb
new file mode 100644
index 0000000000..780d81c1ee
--- /dev/null
+++ b/spec/ruby/core/encoding/undefined_conversion_error/error_char_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#error_char" do
+ before :each do
+ @exception = EncodingSpecs::UndefinedConversionError.exception
+ @exception2 = EncodingSpecs::UndefinedConversionErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.error_char.should be_an_instance_of(String)
+ @exception2.error_char.should be_an_instance_of(String)
+ end
+
+ it "returns the one-character String that caused the exception" do
+ @exception.error_char.size.should == 1
+ @exception.error_char.should == "\u{8765}"
+
+ @exception2.error_char.size.should == 1
+ @exception2.error_char.should == "\u{A0}"
+ end
+
+ it "uses the source encoding" do
+ @exception.error_char.encoding.should == @exception.source_encoding
+
+ @exception2.error_char.encoding.should == @exception2.source_encoding
+ end
+end
diff --git a/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_name_spec.rb b/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_name_spec.rb
new file mode 100644
index 0000000000..3b697cb82f
--- /dev/null
+++ b/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_name_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#source_encoding_name" do
+ before :each do
+ @exception = EncodingSpecs::UndefinedConversionError.exception
+ @exception2 = EncodingSpecs::UndefinedConversionErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.source_encoding_name.should be_an_instance_of(String)
+ end
+
+ it "is equal to the source encoding name of the object that raised it" do
+ @exception.source_encoding_name.should == "UTF-8"
+ end
+
+ # The source encoding specified in the Encoding::Converter constructor may
+ # differ from the source encoding returned here. What seems to happen is
+ # that when transcoding along a path with multiple pairs of encodings, the
+ # last one encountered when the error occurred is returned. So in this
+ # case, the conversion path is ISO-8859-1 -> UTF-8 -> EUC-JP. The
+ # conversion from ISO-8859-1 -> UTF-8 succeeded, but the conversion from
+ # UTF-8 to EUC-JP failed. IOW, it failed when the source encoding was
+ # UTF-8, so UTF-8 is regarded as the source encoding.
+ it "is equal to the source encoding at the stage of the conversion path where the error occurred" do
+ @exception2.source_encoding_name.should == 'UTF-8'
+ end
+end
diff --git a/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_spec.rb b/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_spec.rb
new file mode 100644
index 0000000000..9101d51e11
--- /dev/null
+++ b/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#source_encoding" do
+ before :each do
+ @exception = EncodingSpecs::UndefinedConversionError.exception
+ @exception2 = EncodingSpecs::UndefinedConversionErrorIndirect.exception
+ end
+
+ it "returns an Encoding object" do
+ @exception.source_encoding.should be_an_instance_of(Encoding)
+ @exception2.source_encoding.should be_an_instance_of(Encoding)
+ end
+
+ it "is equal to the source encoding of the object that raised it" do
+ @exception.source_encoding.should == Encoding::UTF_8
+ end
+
+ # The source encoding specified in the Encoding::Converter constructor may
+ # differ from the source encoding returned here. What seems to happen is
+ # that when transcoding along a path with multiple pairs of encodings, the
+ # last one encountered when the error occurred is returned. So in this
+ # case, the conversion path is ISO-8859-1 -> UTF-8 -> EUC-JP. The
+ # conversion from ISO-8859-1 -> UTF-8 succeeded, but the conversion from
+ # UTF-8 to EUC-JP failed. IOW, it failed when the source encoding was
+ # UTF-8, so UTF-8 is regarded as the source encoding.
+ it "is equal to the source encoding at the stage of the conversion path where the error occurred" do
+ @exception2.source_encoding.should == Encoding::UTF_8
+ end
+end
diff --git a/spec/ruby/core/enumerable/all_spec.rb b/spec/ruby/core/enumerable/all_spec.rb
new file mode 100644
index 0000000000..0ded1e8eba
--- /dev/null
+++ b/spec/ruby/core/enumerable/all_spec.rb
@@ -0,0 +1,181 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#all?" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new
+ @empty = EnumerableSpecs::Empty.new()
+ @enum1 = EnumerableSpecs::Numerous.new(0, 1, 2, -1)
+ @enum2 = EnumerableSpecs::Numerous.new(nil, false, true)
+ end
+
+ it "always returns true on empty enumeration" do
+ @empty.should.all?
+ @empty.all? { nil }.should == true
+
+ [].should.all?
+ [].all? { false }.should == true
+
+ {}.should.all?
+ {}.all? { nil }.should == true
+ end
+
+ it "raises an ArgumentError when more than 1 argument is provided" do
+ -> { @enum.all?(1, 2, 3) }.should raise_error(ArgumentError)
+ -> { [].all?(1, 2, 3) }.should raise_error(ArgumentError)
+ -> { {}.all?(1, 2, 3) }.should raise_error(ArgumentError)
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.all?
+ }.should raise_error(RuntimeError)
+
+ -> {
+ EnumerableSpecs::ThrowingEach.new.all? { false }
+ }.should raise_error(RuntimeError)
+ end
+
+ describe "with no block" do
+ it "returns true if no elements are false or nil" do
+ @enum.should.all?
+ @enum1.should.all?
+ @enum2.should_not.all?
+
+ EnumerableSpecs::Numerous.new('a','b','c').should.all?
+ EnumerableSpecs::Numerous.new(0, "x", true).should.all?
+ end
+
+ it "returns false if there are false or nil elements" do
+ EnumerableSpecs::Numerous.new(false).should_not.all?
+ EnumerableSpecs::Numerous.new(false, false).should_not.all?
+
+ EnumerableSpecs::Numerous.new(nil).should_not.all?
+ EnumerableSpecs::Numerous.new(nil, nil).should_not.all?
+
+ EnumerableSpecs::Numerous.new(1, nil, 2).should_not.all?
+ EnumerableSpecs::Numerous.new(0, "x", false, true).should_not.all?
+ @enum2.should_not.all?
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMultiWithFalse.new
+ multi.all?.should be_true
+ end
+ end
+
+ describe "with block" do
+ it "returns true if the block never returns false or nil" do
+ @enum.all? { true }.should == true
+ @enum1.all?{ |o| o < 5 }.should == true
+ @enum1.all?{ |o| 5 }.should == true
+ end
+
+ it "returns false if the block ever returns false or nil" do
+ @enum.all? { false }.should == false
+ @enum.all? { nil }.should == false
+ @enum1.all?{ |o| o > 2 }.should == false
+
+ EnumerableSpecs::Numerous.new.all? { |i| i > 5 }.should == false
+ EnumerableSpecs::Numerous.new.all? { |i| i == 3 ? nil : true }.should == false
+ end
+
+ it "stops iterating once the return value is determined" do
+ yielded = []
+ EnumerableSpecs::Numerous.new(:one, :two, :three).all? do |e|
+ yielded << e
+ false
+ end.should == false
+ yielded.should == [:one]
+
+ yielded = []
+ EnumerableSpecs::Numerous.new(true, true, false, true).all? do |e|
+ yielded << e
+ e
+ end.should == false
+ yielded.should == [true, true, false]
+
+ yielded = []
+ EnumerableSpecs::Numerous.new(1, 2, 3, 4, 5).all? do |e|
+ yielded << e
+ e
+ end.should == true
+ yielded.should == [1, 2, 3, 4, 5]
+ end
+
+ it "does not hide exceptions out of the block" do
+ -> {
+ @enum.all? { raise "from block" }
+ }.should raise_error(RuntimeError)
+ end
+
+ it "gathers initial args as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.all? { |e| yielded << e }.should == true
+ yielded.should == [1, 3, 6]
+ end
+
+ it "yields multiple arguments when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.all? { |*args| yielded << args }.should == true
+ yielded.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+ end
+
+ describe 'when given a pattern argument' do
+ it "calls `===` on the pattern the return value " do
+ pattern = EnumerableSpecs::Pattern.new { |x| x >= 0 }
+ @enum1.all?(pattern).should == false
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+ end
+
+ it "always returns true on empty enumeration" do
+ @empty.all?(Integer).should == true
+ [].all?(Integer).should == true
+ {}.all?(NilClass).should == true
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.all?(Integer)
+ }.should raise_error(RuntimeError)
+ end
+
+ it "returns true if the pattern never returns false or nil" do
+ pattern = EnumerableSpecs::Pattern.new { |x| 42 }
+ @enum.all?(pattern).should == true
+
+ [1, 42, 3].all?(pattern).should == true
+
+ pattern = EnumerableSpecs::Pattern.new { |x| Array === x }
+ {a: 1, b: 2}.all?(pattern).should == true
+ end
+
+ it "returns false if the pattern ever returns false or nil" do
+ pattern = EnumerableSpecs::Pattern.new { |x| x >= 0 }
+ @enum1.all?(pattern).should == false
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+
+ [1, 2, 3, -1].all?(pattern).should == false
+
+ pattern = EnumerableSpecs::Pattern.new { |x| x[1] >= 0 }
+ {a: 1, b: -1}.all?(pattern).should == false
+ end
+
+ it "does not hide exceptions out of pattern#===" do
+ pattern = EnumerableSpecs::Pattern.new { raise "from pattern" }
+ -> {
+ @enum.all?(pattern)
+ }.should raise_error(RuntimeError)
+ end
+
+ it "calls the pattern with gathered array when yielded with multiple arguments" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ pattern = EnumerableSpecs::Pattern.new { true }
+ multi.all?(pattern).should == true
+ pattern.yielded.should == [[[1, 2]], [[3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/any_spec.rb b/spec/ruby/core/enumerable/any_spec.rb
new file mode 100644
index 0000000000..355cd0c290
--- /dev/null
+++ b/spec/ruby/core/enumerable/any_spec.rb
@@ -0,0 +1,194 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#any?" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new
+ @empty = EnumerableSpecs::Empty.new
+ @enum1 = EnumerableSpecs::Numerous.new(0, 1, 2, -1)
+ @enum2 = EnumerableSpecs::Numerous.new(nil, false, true)
+ end
+
+ it "always returns false on empty enumeration" do
+ @empty.should_not.any?
+ @empty.any? { nil }.should == false
+
+ [].should_not.any?
+ [].any? { false }.should == false
+
+ {}.should_not.any?
+ {}.any? { nil }.should == false
+ end
+
+ it "raises an ArgumentError when more than 1 argument is provided" do
+ -> { @enum.any?(1, 2, 3) }.should raise_error(ArgumentError)
+ -> { [].any?(1, 2, 3) }.should raise_error(ArgumentError)
+ -> { {}.any?(1, 2, 3) }.should raise_error(ArgumentError)
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.any?
+ }.should raise_error(RuntimeError)
+
+ -> {
+ EnumerableSpecs::ThrowingEach.new.any? { false }
+ }.should raise_error(RuntimeError)
+ end
+
+ describe "with no block" do
+ it "returns true if any element is not false or nil" do
+ @enum.should.any?
+ @enum1.should.any?
+ @enum2.should.any?
+ EnumerableSpecs::Numerous.new(true).should.any?
+ EnumerableSpecs::Numerous.new('a','b','c').should.any?
+ EnumerableSpecs::Numerous.new('a','b','c', nil).should.any?
+ EnumerableSpecs::Numerous.new(1, nil, 2).should.any?
+ EnumerableSpecs::Numerous.new(1, false).should.any?
+ EnumerableSpecs::Numerous.new(false, nil, 1, false).should.any?
+ EnumerableSpecs::Numerous.new(false, 0, nil).should.any?
+ end
+
+ it "returns false if all elements are false or nil" do
+ EnumerableSpecs::Numerous.new(false).should_not.any?
+ EnumerableSpecs::Numerous.new(false, false).should_not.any?
+ EnumerableSpecs::Numerous.new(nil).should_not.any?
+ EnumerableSpecs::Numerous.new(nil, nil).should_not.any?
+ EnumerableSpecs::Numerous.new(nil, false, nil).should_not.any?
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMultiWithFalse.new
+ multi.any?.should be_true
+ end
+ end
+
+ describe "with block" do
+ it "returns true if the block ever returns other than false or nil" do
+ @enum.any? { true }.should == true
+ @enum.any? { 0 }.should == true
+ @enum.any? { 1 }.should == true
+
+ @enum1.any? { Object.new }.should == true
+ @enum1.any?{ |o| o < 1 }.should == true
+ @enum1.any?{ |o| 5 }.should == true
+
+ @enum2.any? { |i| i == nil }.should == true
+ end
+
+ it "returns false if the block never returns other than false or nil" do
+ @enum.any? { false }.should == false
+ @enum.any? { nil }.should == false
+
+ @enum1.any?{ |o| o < -10 }.should == false
+ @enum1.any?{ |o| nil }.should == false
+
+ @enum2.any? { |i| i == :stuff }.should == false
+ end
+
+ it "stops iterating once the return value is determined" do
+ yielded = []
+ EnumerableSpecs::Numerous.new(:one, :two, :three).any? do |e|
+ yielded << e
+ false
+ end.should == false
+ yielded.should == [:one, :two, :three]
+
+ yielded = []
+ EnumerableSpecs::Numerous.new(true, true, false, true).any? do |e|
+ yielded << e
+ e
+ end.should == true
+ yielded.should == [true]
+
+ yielded = []
+ EnumerableSpecs::Numerous.new(false, nil, false, true, false).any? do |e|
+ yielded << e
+ e
+ end.should == true
+ yielded.should == [false, nil, false, true]
+
+ yielded = []
+ EnumerableSpecs::Numerous.new(1, 2, 3, 4, 5).any? do |e|
+ yielded << e
+ e
+ end.should == true
+ yielded.should == [1]
+ end
+
+ it "does not hide exceptions out of the block" do
+ -> {
+ @enum.any? { raise "from block" }
+ }.should raise_error(RuntimeError)
+ end
+
+ it "gathers initial args as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.any? { |e| yielded << e; false }.should == false
+ yielded.should == [1, 3, 6]
+ end
+
+ it "yields multiple arguments when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.any? { |*args| yielded << args; false }.should == false
+ yielded.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+ end
+
+ describe 'when given a pattern argument' do
+ it "calls `===` on the pattern the return value " do
+ pattern = EnumerableSpecs::Pattern.new { |x| x == 2 }
+ @enum1.any?(pattern).should == true
+ pattern.yielded.should == [[0], [1], [2]]
+ end
+
+ it "always returns false on empty enumeration" do
+ @empty.any?(Integer).should == false
+ [].any?(Integer).should == false
+ {}.any?(NilClass).should == false
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.any?(Integer)
+ }.should raise_error(RuntimeError)
+ end
+
+ it "returns true if the pattern ever returns a truthy value" do
+ @enum2.any?(NilClass).should == true
+ pattern = EnumerableSpecs::Pattern.new { |x| 42 }
+ @enum.any?(pattern).should == true
+
+ [1, 42, 3].any?(pattern).should == true
+
+ pattern = EnumerableSpecs::Pattern.new { |x| x == [:b, 2] }
+ {a: 1, b: 2}.any?(pattern).should == true
+ end
+
+ it "returns false if the block never returns other than false or nil" do
+ pattern = EnumerableSpecs::Pattern.new { |x| nil }
+ @enum1.any?(pattern).should == false
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+
+ [1, 2, 3].any?(pattern).should == false
+ {a: 1}.any?(pattern).should == false
+ end
+
+ it "does not hide exceptions out of pattern#===" do
+ pattern = EnumerableSpecs::Pattern.new { raise "from pattern" }
+ -> {
+ @enum.any?(pattern)
+ }.should raise_error(RuntimeError)
+ end
+
+ it "calls the pattern with gathered array when yielded with multiple arguments" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ pattern = EnumerableSpecs::Pattern.new { false }
+ multi.any?(pattern).should == false
+ pattern.yielded.should == [[[1, 2]], [[3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/chain_spec.rb b/spec/ruby/core/enumerable/chain_spec.rb
new file mode 100644
index 0000000000..5e2105d294
--- /dev/null
+++ b/spec/ruby/core/enumerable/chain_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#chain" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "returns a chain of self and provided enumerables" do
+ one = EnumerableSpecs::Numerous.new(1)
+ two = EnumerableSpecs::Numerous.new(2, 3)
+ three = EnumerableSpecs::Numerous.new(4, 5, 6)
+
+ chain = one.chain(two, three)
+
+ chain.each { |item| ScratchPad << item }
+ ScratchPad.recorded.should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "returns an Enumerator::Chain if given a block" do
+ EnumerableSpecs::Numerous.new.chain.should be_an_instance_of(Enumerator::Chain)
+ end
+end
diff --git a/spec/ruby/core/enumerable/chunk_spec.rb b/spec/ruby/core/enumerable/chunk_spec.rb
new file mode 100644
index 0000000000..c5579d67fa
--- /dev/null
+++ b/spec/ruby/core/enumerable/chunk_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#chunk" do
+ before do
+ ScratchPad.record []
+ end
+
+ it "returns an Enumerator if called without a block" do
+ chunk = EnumerableSpecs::Numerous.new(1, 2, 3, 1, 2).chunk
+ chunk.should be_an_instance_of(Enumerator)
+ result = chunk.with_index {|elt, i| elt - i }.to_a
+ result.should == [[1, [1, 2, 3]], [-2, [1, 2]]]
+ end
+
+ it "returns an Enumerator if given a block" do
+ EnumerableSpecs::Numerous.new.chunk {}.should be_an_instance_of(Enumerator)
+ end
+
+ it "yields the current element and the current chunk to the block" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3)
+ e.chunk { |x| ScratchPad << x }.to_a
+ ScratchPad.recorded.should == [1, 2, 3]
+ end
+
+ it "returns elements of the Enumerable in an Array of Arrays, [v, ary], where 'ary' contains the consecutive elements for which the block returned the value 'v'" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3, 2, 3, 2, 1)
+ result = e.chunk { |x| x < 3 && 1 || 0 }.to_a
+ result.should == [[1, [1, 2]], [0, [3]], [1, [2]], [0, [3]], [1, [2, 1]]]
+ end
+
+ it "returns elements for which the block returns :_alone in separate Arrays" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3, 2, 1)
+ result = e.chunk { |x| x < 2 && :_alone }.to_a
+ result.should == [[:_alone, [1]], [false, [2, 3, 2]], [:_alone, [1]]]
+ end
+
+ it "yields Arrays as a single argument to a rest argument" do
+ e = EnumerableSpecs::Numerous.new([1, 2])
+ result = e.chunk { |*x| x.should == [[1,2]] }.to_a
+ end
+
+ it "does not return elements for which the block returns :_separator" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3, 3, 2, 1)
+ result = e.chunk { |x| x == 2 ? :_separator : 1 }.to_a
+ result.should == [[1, [1]], [1, [3, 3]], [1, [1]]]
+ end
+
+ it "does not return elements for which the block returns nil" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3, 2, 1)
+ result = e.chunk { |x| x == 2 ? nil : 1 }.to_a
+ result.should == [[1, [1]], [1, [3]], [1, [1]]]
+ end
+
+ it "raises a RuntimeError if the block returns a Symbol starting with an underscore other than :_alone or :_separator" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3, 2, 1)
+ -> { e.chunk { |x| :_arbitrary }.to_a }.should raise_error(RuntimeError)
+ end
+
+ it "does not accept arguments" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3)
+ -> {
+ e.chunk(1) {}
+ }.should raise_error(ArgumentError)
+ end
+
+ it 'returned Enumerator size returns nil' do
+ e = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 2, 1)
+ enum = e.chunk { |x| true }
+ enum.size.should == nil
+ end
+end
diff --git a/spec/ruby/core/enumerable/chunk_while_spec.rb b/spec/ruby/core/enumerable/chunk_while_spec.rb
new file mode 100644
index 0000000000..26bcc983db
--- /dev/null
+++ b/spec/ruby/core/enumerable/chunk_while_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#chunk_while" do
+ before :each do
+ ary = [10, 9, 7, 6, 4, 3, 2, 1]
+ @enum = EnumerableSpecs::Numerous.new(*ary)
+ @result = @enum.chunk_while { |i, j| i - 1 == j }
+ @enum_length = ary.length
+ end
+
+ context "when given a block" do
+ it "returns an enumerator" do
+ @result.should be_an_instance_of(Enumerator)
+ end
+
+ it "splits chunks between adjacent elements i and j where the block returns false" do
+ @result.to_a.should == [[10, 9], [7, 6], [4, 3, 2, 1]]
+ end
+
+ it "calls the block for length of the receiver enumerable minus one times" do
+ times_called = 0
+ @enum.chunk_while do |i, j|
+ times_called += 1
+ i - 1 == j
+ end.to_a
+ times_called.should == (@enum_length - 1)
+ end
+ end
+
+ context "when not given a block" do
+ it "raises an ArgumentError" do
+ -> { @enum.chunk_while }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "on a single-element array" do
+ it "ignores the block and returns an enumerator that yields [element]" do
+ [1].chunk_while {|x| x.even?}.to_a.should == [[1]]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/collect_concat_spec.rb b/spec/ruby/core/enumerable/collect_concat_spec.rb
new file mode 100644
index 0000000000..6e34c9eb93
--- /dev/null
+++ b/spec/ruby/core/enumerable/collect_concat_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/collect_concat'
+
+describe "Enumerable#collect_concat" do
+ it_behaves_like :enumerable_collect_concat , :collect_concat
+end
diff --git a/spec/ruby/core/enumerable/collect_spec.rb b/spec/ruby/core/enumerable/collect_spec.rb
new file mode 100644
index 0000000000..1016b67798
--- /dev/null
+++ b/spec/ruby/core/enumerable/collect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/collect'
+
+describe "Enumerable#collect" do
+ it_behaves_like :enumerable_collect , :collect
+end
diff --git a/spec/ruby/core/enumerable/compact_spec.rb b/spec/ruby/core/enumerable/compact_spec.rb
new file mode 100644
index 0000000000..86e95dce08
--- /dev/null
+++ b/spec/ruby/core/enumerable/compact_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is '3.1' do
+ describe "Enumerable#compact" do
+ it 'returns array without nil elements' do
+ arr = EnumerableSpecs::Numerous.new(nil, 1, 2, nil, true)
+ arr.compact.should == [1, 2, true]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/count_spec.rb b/spec/ruby/core/enumerable/count_spec.rb
new file mode 100644
index 0000000000..50a1c8e1a4
--- /dev/null
+++ b/spec/ruby/core/enumerable/count_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#count" do
+ before :each do
+ @elements = [1, 2, 4, 2]
+ @numerous = EnumerableSpecs::Numerous.new(*@elements)
+ end
+
+ describe "when no argument or a block" do
+ it "returns size" do
+ @numerous.count.should == 4
+ end
+
+ describe "with a custom size method" do
+ before :each do
+ class << @numerous
+ def size
+ :any_object
+ end
+ end
+ end
+
+ it "ignores the custom size method" do
+ @numerous.count.should == 4
+ end
+ end
+ end
+
+ it "counts nils if given nil as an argument" do
+ EnumerableSpecs::Numerous.new(nil, nil, nil, false).count(nil).should == 3
+ end
+
+ it "accepts an argument for comparison using ==" do
+ @numerous.count(2).should == 2
+ end
+
+ it "uses a block for comparison" do
+ @numerous.count{|x| x%2==0 }.should == 3
+ end
+
+ it "ignores the block when given an argument" do
+ -> {
+ @numerous.count(4){|x| x%2==0 }.should == 1
+ }.should complain(/given block not used/)
+ end
+
+ describe "when each yields multiple values" do
+ it "gathers initial args as elements" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.count {|e| e == 1 }.should == 1
+ end
+
+ it "accepts an argument for comparison using ==" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.count([1, 2]).should == 1
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/cycle_spec.rb b/spec/ruby/core/enumerable/cycle_spec.rb
new file mode 100644
index 0000000000..487086cba3
--- /dev/null
+++ b/spec/ruby/core/enumerable/cycle_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorized'
+
+describe "Enumerable#cycle" do
+ describe "passed no argument or nil" do
+ it "loops indefinitely" do
+ [[],[nil]].each do |args|
+ bomb = 10
+ EnumerableSpecs::Numerous.new.cycle(*args) do
+ bomb -= 1
+ break 42 if bomb <= 0
+ end.should == 42
+ bomb.should == 0
+ end
+ end
+
+ it "returns nil if there are no elements" do
+ out = EnumerableSpecs::Empty.new.cycle { break :nope }
+ out.should be_nil
+ end
+
+ it "yields successive elements of the array repeatedly" do
+ b = []
+ EnumerableSpecs::Numerous.new(1,2,3).cycle do |elem|
+ b << elem
+ break if b.size == 7
+ end
+ b.should == [1,2,3,1,2,3,1]
+ end
+
+ it "calls each at most once" do
+ enum = EnumerableSpecs::EachCounter.new(1, 2)
+ enum.cycle.first(6).should == [1,2,1,2,1,2]
+ enum.times_called.should == 1
+ end
+
+ it "yields only when necessary" do
+ enum = EnumerableSpecs::EachCounter.new(10, 20, 30)
+ enum.cycle { |x| break if x == 20}
+ enum.times_yielded.should == 2
+ end
+ end
+
+ describe "passed a number n as an argument" do
+ it "returns nil and does nothing for non positive n" do
+ EnumerableSpecs::ThrowingEach.new.cycle(0) {}.should be_nil
+ EnumerableSpecs::NoEach.new.cycle(-22) {}.should be_nil
+ end
+
+ it "calls each at most once" do
+ enum = EnumerableSpecs::EachCounter.new(1, 2)
+ enum.cycle(3).to_a.should == [1,2,1,2,1,2]
+ enum.times_called.should == 1
+ end
+
+ it "yields only when necessary" do
+ enum = EnumerableSpecs::EachCounter.new(10, 20, 30)
+ enum.cycle(3) { |x| break if x == 20}
+ enum.times_yielded.should == 2
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ enum = EnumerableSpecs::Numerous.new(3, 2, 1)
+ enum.cycle(2.3).to_a.should == [3, 2, 1, 3, 2, 1]
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ enum.cycle(obj).to_a.should == [3, 2, 1, 3, 2, 1]
+ end
+
+ it "raises a TypeError when the passed n cannot be coerced to Integer" do
+ enum = EnumerableSpecs::Numerous.new
+ ->{ enum.cycle("cat"){} }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError if more arguments are passed" do
+ enum = EnumerableSpecs::Numerous.new
+ ->{ enum.cycle(1, 2) {} }.should raise_error(ArgumentError)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.cycle(2).to_a.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9], [1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+ end
+
+ describe "Enumerable with size" do
+ before :all do
+ @object = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 4)
+ @empty_object = EnumerableSpecs::EmptyWithSize.new
+ end
+ it_should_behave_like :enumeratorized_with_cycle_size
+ end
+
+ describe "Enumerable with no size" do
+ before :all do
+ @object = EnumerableSpecs::Numerous.new(1, 2, 3, 4)
+ @method = :cycle
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+
+end
diff --git a/spec/ruby/core/enumerable/detect_spec.rb b/spec/ruby/core/enumerable/detect_spec.rb
new file mode 100644
index 0000000000..e912134fed
--- /dev/null
+++ b/spec/ruby/core/enumerable/detect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/find'
+
+describe "Enumerable#detect" do
+ it_behaves_like :enumerable_find , :detect
+end
diff --git a/spec/ruby/core/enumerable/drop_spec.rb b/spec/ruby/core/enumerable/drop_spec.rb
new file mode 100644
index 0000000000..423cc0088b
--- /dev/null
+++ b/spec/ruby/core/enumerable/drop_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#drop" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(3, 2, 1, :go)
+ end
+
+ it "requires exactly one argument" do
+ ->{ @enum.drop{} }.should raise_error(ArgumentError)
+ ->{ @enum.drop(1, 2){} }.should raise_error(ArgumentError)
+ end
+
+ describe "passed a number n as an argument" do
+ it "raises ArgumentError if n < 0" do
+ ->{ @enum.drop(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ @enum.drop(2.3).should == [1, :go]
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ @enum.drop(obj).should == [1, :go]
+ end
+
+ it "returns [] for empty enumerables" do
+ EnumerableSpecs::Empty.new.drop(0).should == []
+ EnumerableSpecs::Empty.new.drop(2).should == []
+ end
+
+ it "returns [] if dropping all" do
+ @enum.drop(5).should == []
+ EnumerableSpecs::Numerous.new(3, 2, 1, :go).drop(4).should == []
+ end
+
+ it "raises a TypeError when the passed n cannot be coerced to Integer" do
+ ->{ @enum.drop("hat") }.should raise_error(TypeError)
+ ->{ @enum.drop(nil) }.should raise_error(TypeError)
+ end
+
+ end
+end
diff --git a/spec/ruby/core/enumerable/drop_while_spec.rb b/spec/ruby/core/enumerable/drop_while_spec.rb
new file mode 100644
index 0000000000..636c3d284a
--- /dev/null
+++ b/spec/ruby/core/enumerable/drop_while_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#drop_while" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(3, 2, 1, :go)
+ end
+
+ it "returns an Enumerator if no block given" do
+ @enum.drop_while.should be_an_instance_of(Enumerator)
+ end
+
+ it "returns no/all elements for {true/false} block" do
+ @enum.drop_while{true}.should == []
+ @enum.drop_while{false}.should == @enum.to_a
+ end
+
+ it "accepts returns other than true/false" do
+ @enum.drop_while{1}.should == []
+ @enum.drop_while{nil}.should == @enum.to_a
+ end
+
+ it "passes elements to the block until the first false" do
+ a = []
+ @enum.drop_while{|obj| (a << obj).size < 3}.should == [1, :go]
+ a.should == [3, 2, 1]
+ end
+
+ it "will only go through what's needed" do
+ enum = EnumerableSpecs::EachCounter.new(1,2,3,4)
+ enum.drop_while { |x|
+ break 42 if x == 3
+ true
+ }.should == 42
+ enum.times_yielded.should == 3
+ end
+
+ it "doesn't return self when it could" do
+ a = [1,2,3]
+ a.drop_while{false}.should_not equal(a)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.drop_while {|e| e != [6, 7, 8, 9] }.should == [[6, 7, 8, 9]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_unknown_size, :drop_while
+end
diff --git a/spec/ruby/core/enumerable/each_cons_spec.rb b/spec/ruby/core/enumerable/each_cons_spec.rb
new file mode 100644
index 0000000000..8fb31fb925
--- /dev/null
+++ b/spec/ruby/core/enumerable/each_cons_spec.rb
@@ -0,0 +1,105 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorized'
+
+describe "Enumerable#each_cons" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(4,3,2,1)
+ @in_threes = [[4,3,2],[3,2,1]]
+ end
+
+ it "passes element groups to the block" do
+ acc = []
+ @enum.each_cons(3){|g| acc << g}
+ acc.should == @in_threes
+ end
+
+ it "raises an ArgumentError if there is not a single parameter > 0" do
+ ->{ @enum.each_cons(0){} }.should raise_error(ArgumentError)
+ ->{ @enum.each_cons(-2){} }.should raise_error(ArgumentError)
+ ->{ @enum.each_cons{} }.should raise_error(ArgumentError)
+ ->{ @enum.each_cons(2,2){} }.should raise_error(ArgumentError)
+ ->{ @enum.each_cons(0) }.should raise_error(ArgumentError)
+ ->{ @enum.each_cons(-2) }.should raise_error(ArgumentError)
+ ->{ @enum.each_cons }.should raise_error(ArgumentError)
+ ->{ @enum.each_cons(2,2) }.should raise_error(ArgumentError)
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ acc = []
+ @enum.each_cons(3.3){|g| acc << g}
+ acc.should == @in_threes
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(3)
+ @enum.each_cons(obj){|g| break g.length}.should == 3
+ end
+
+ it "works when n is >= full length" do
+ full = @enum.to_a
+ acc = []
+ @enum.each_cons(full.length){|g| acc << g}
+ acc.should == [full]
+ acc = []
+ @enum.each_cons(full.length+1){|g| acc << g}
+ acc.should == []
+ end
+
+ it "yields only as much as needed" do
+ cnt = EnumerableSpecs::EachCounter.new(1, 2, :stop, "I said stop!", :got_it)
+ cnt.each_cons(2) {|g| break 42 if g[-1] == :stop }.should == 42
+ cnt.times_yielded.should == 3
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.each_cons(2).to_a.should == [[[1, 2], [3, 4, 5]], [[3, 4, 5], [6, 7, 8, 9]]]
+ end
+
+ ruby_version_is "3.1" do
+ it "returns self when a block is given" do
+ @enum.each_cons(3){}.should == @enum
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an enumerator" do
+ e = @enum.each_cons(3)
+ e.should be_an_instance_of(Enumerator)
+ e.to_a.should == @in_threes
+ end
+
+ describe "Enumerable with size" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns enum size - each_cons argument + 1" do
+ enum = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ enum.each_cons(10).size.should == 1
+ enum.each_cons(9).size.should == 2
+ enum.each_cons(3).size.should == 8
+ enum.each_cons(2).size.should == 9
+ enum.each_cons(1).size.should == 10
+ end
+
+ it "returns 0 when the argument is larger than self" do
+ enum = EnumerableSpecs::NumerousWithSize.new(1, 2, 3)
+ enum.each_cons(20).size.should == 0
+ end
+
+ it "returns 0 when the enum is empty" do
+ enum = EnumerableSpecs::EmptyWithSize.new
+ enum.each_cons(10).size.should == 0
+ end
+ end
+ end
+ end
+
+ describe "Enumerable with no size" do
+ before :all do
+ @object = EnumerableSpecs::Numerous.new(1, 2, 3, 4)
+ @method = [:each_cons, 8]
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/each_entry_spec.rb b/spec/ruby/core/enumerable/each_entry_spec.rb
new file mode 100644
index 0000000000..edf00f3137
--- /dev/null
+++ b/spec/ruby/core/enumerable/each_entry_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#each_entry" do
+ before :each do
+ ScratchPad.record []
+ @enum = EnumerableSpecs::YieldsMixed.new
+ @entries = [1, [2], [3,4], [5,6,7], [8,9], nil, []]
+ end
+
+ it "yields multiple arguments as an array" do
+ acc = []
+ @enum.each_entry {|g| acc << g}.should equal(@enum)
+ acc.should == @entries
+ end
+
+ it "returns an enumerator if no block" do
+ e = @enum.each_entry
+ e.should be_an_instance_of(Enumerator)
+ e.to_a.should == @entries
+ end
+
+ it "passes through the values yielded by #each_with_index" do
+ [:a, :b].each_with_index.each_entry { |x, i| ScratchPad << [x, i] }
+ ScratchPad.recorded.should == [[:a, 0], [:b, 1]]
+ end
+
+ it "raises an ArgumentError when extra arguments" do
+ -> { @enum.each_entry("one").to_a }.should raise_error(ArgumentError)
+ -> { @enum.each_entry("one"){}.to_a }.should raise_error(ArgumentError)
+ end
+
+ it "passes extra arguments to #each" do
+ enum = EnumerableSpecs::EachCounter.new(1, 2)
+ enum.each_entry(:foo, "bar").to_a.should == [1,2]
+ enum.arguments_passed.should == [:foo, "bar"]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :each_entry
+end
diff --git a/spec/ruby/core/enumerable/each_slice_spec.rb b/spec/ruby/core/enumerable/each_slice_spec.rb
new file mode 100644
index 0000000000..a57a1dba81
--- /dev/null
+++ b/spec/ruby/core/enumerable/each_slice_spec.rb
@@ -0,0 +1,107 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorized'
+
+describe "Enumerable#each_slice" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(7,6,5,4,3,2,1)
+ @sliced = [[7,6,5],[4,3,2],[1]]
+ end
+
+ it "passes element groups to the block" do
+ acc = []
+ @enum.each_slice(3){|g| acc << g}
+ acc.should == @sliced
+ end
+
+ it "raises an ArgumentError if there is not a single parameter > 0" do
+ ->{ @enum.each_slice(0){} }.should raise_error(ArgumentError)
+ ->{ @enum.each_slice(-2){} }.should raise_error(ArgumentError)
+ ->{ @enum.each_slice{} }.should raise_error(ArgumentError)
+ ->{ @enum.each_slice(2,2){} }.should raise_error(ArgumentError)
+ ->{ @enum.each_slice(0) }.should raise_error(ArgumentError)
+ ->{ @enum.each_slice(-2) }.should raise_error(ArgumentError)
+ ->{ @enum.each_slice }.should raise_error(ArgumentError)
+ ->{ @enum.each_slice(2,2) }.should raise_error(ArgumentError)
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ acc = []
+ @enum.each_slice(3.3){|g| acc << g}
+ acc.should == @sliced
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(3)
+ @enum.each_slice(obj){|g| break g.length}.should == 3
+ end
+
+ it "works when n is >= full length" do
+ full = @enum.to_a
+ acc = []
+ @enum.each_slice(full.length){|g| acc << g}
+ acc.should == [full]
+ acc = []
+ @enum.each_slice(full.length+1){|g| acc << g}
+ acc.should == [full]
+ end
+
+ it "yields only as much as needed" do
+ cnt = EnumerableSpecs::EachCounter.new(1, 2, :stop, "I said stop!", :got_it)
+ cnt.each_slice(2) {|g| break 42 if g[0] == :stop }.should == 42
+ cnt.times_yielded.should == 4
+ end
+
+ it "returns an enumerator if no block" do
+ e = @enum.each_slice(3)
+ e.should be_an_instance_of(Enumerator)
+ e.to_a.should == @sliced
+ end
+
+ ruby_version_is "3.1" do
+ it "returns self when a block is given" do
+ @enum.each_slice(3){}.should == @enum
+ end
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.each_slice(2).to_a.should == [[[1, 2], [3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+
+ describe "when no block is given" do
+ it "returns an enumerator" do
+ e = @enum.each_slice(3)
+ e.should be_an_instance_of(Enumerator)
+ e.to_a.should == @sliced
+ end
+
+ describe "Enumerable with size" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns the ceil of Enumerable size divided by the argument value" do
+ enum = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ enum.each_slice(10).size.should == 1
+ enum.each_slice(9).size.should == 2
+ enum.each_slice(3).size.should == 4
+ enum.each_slice(2).size.should == 5
+ enum.each_slice(1).size.should == 10
+ end
+
+ it "returns 0 when the Enumerable is empty" do
+ enum = EnumerableSpecs::EmptyWithSize.new
+ enum.each_slice(10).size.should == 0
+ end
+ end
+ end
+ end
+
+ describe "Enumerable with no size" do
+ before :all do
+ @object = EnumerableSpecs::Numerous.new(1, 2, 3, 4)
+ @method = [:each_slice, 8]
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+ end
+
+end
diff --git a/spec/ruby/core/enumerable/each_with_index_spec.rb b/spec/ruby/core/enumerable/each_with_index_spec.rb
new file mode 100644
index 0000000000..122e88eab7
--- /dev/null
+++ b/spec/ruby/core/enumerable/each_with_index_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#each_with_index" do
+
+ before :each do
+ @b = EnumerableSpecs::Numerous.new(2, 5, 3, 6, 1, 4)
+ end
+
+ it "passes each element and its index to block" do
+ @a = []
+ @b.each_with_index { |o, i| @a << [o, i] }
+ @a.should == [[2, 0], [5, 1], [3, 2], [6, 3], [1, 4], [4, 5]]
+ end
+
+ it "provides each element to the block" do
+ acc = []
+ obj = EnumerableSpecs::EachDefiner.new()
+ res = obj.each_with_index {|a,i| acc << [a,i]}
+ acc.should == []
+ obj.should == res
+ end
+
+ it "provides each element to the block and its index" do
+ acc = []
+ res = @b.each_with_index {|a,i| acc << [a,i]}
+ [[2, 0], [5, 1], [3, 2], [6, 3], [1, 4], [4, 5]].should == acc
+ res.should eql(@b)
+ end
+
+ it "binds splat arguments properly" do
+ acc = []
+ res = @b.each_with_index { |*b| c,d = b; acc << c; acc << d }
+ [2, 0, 5, 1, 3, 2, 6, 3, 1, 4, 4, 5].should == acc
+ res.should eql(@b)
+ end
+
+ it "returns an enumerator if no block" do
+ e = @b.each_with_index
+ e.should be_an_instance_of(Enumerator)
+ e.to_a.should == [[2, 0], [5, 1], [3, 2], [6, 3], [1, 4], [4, 5]]
+ end
+
+ it "passes extra parameters to each" do
+ count = EnumerableSpecs::EachCounter.new(:apple)
+ e = count.each_with_index(:foo, :bar)
+ e.to_a.should == [[:apple, 0]]
+ count.arguments_passed.should == [:foo, :bar]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :each_with_index
+end
diff --git a/spec/ruby/core/enumerable/each_with_object_spec.rb b/spec/ruby/core/enumerable/each_with_object_spec.rb
new file mode 100644
index 0000000000..35665e7019
--- /dev/null
+++ b/spec/ruby/core/enumerable/each_with_object_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#each_with_object" do
+ before :each do
+ @values = [2, 5, 3, 6, 1, 4]
+ @enum = EnumerableSpecs::Numerous.new(*@values)
+ @initial = "memo"
+ end
+
+ it "passes each element and its argument to the block" do
+ acc = []
+ @enum.each_with_object(@initial) do |elem, obj|
+ obj.should equal(@initial)
+ obj = 42
+ acc << elem
+ end.should equal(@initial)
+ acc.should == @values
+ end
+
+ it "returns an enumerator if no block" do
+ acc = []
+ e = @enum.each_with_object(@initial)
+ e.each do |elem, obj|
+ obj.should equal(@initial)
+ obj = 42
+ acc << elem
+ end.should equal(@initial)
+ acc.should == @values
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ array = []
+ multi.each_with_object(array) { |elem, obj| obj << elem }
+ array.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, [:each_with_object, []]
+end
diff --git a/spec/ruby/core/enumerable/entries_spec.rb b/spec/ruby/core/enumerable/entries_spec.rb
new file mode 100644
index 0000000000..83232cfa06
--- /dev/null
+++ b/spec/ruby/core/enumerable/entries_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/entries'
+
+describe "Enumerable#entries" do
+ it_behaves_like :enumerable_entries , :entries
+end
diff --git a/spec/ruby/core/enumerable/filter_map_spec.rb b/spec/ruby/core/enumerable/filter_map_spec.rb
new file mode 100644
index 0000000000..aa4894230b
--- /dev/null
+++ b/spec/ruby/core/enumerable/filter_map_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Enumerable#filter_map' do
+ before :each do
+ @numerous = EnumerableSpecs::Numerous.new(*(1..8).to_a)
+ end
+
+ it 'returns an empty array if there are no elements' do
+ EnumerableSpecs::Empty.new.filter_map { true }.should == []
+ end
+
+ it 'returns an array with truthy results of passing each element to block' do
+ @numerous.filter_map { |i| i * 2 if i.even? }.should == [4, 8, 12, 16]
+ @numerous.filter_map { |i| i * 2 }.should == [2, 4, 6, 8, 10, 12, 14, 16]
+ @numerous.filter_map { 0 }.should == [0, 0, 0, 0, 0, 0, 0, 0]
+ @numerous.filter_map { false }.should == []
+ @numerous.filter_map { nil }.should == []
+ end
+
+ it 'returns an enumerator when no block given' do
+ @numerous.filter_map.should be_an_instance_of(Enumerator)
+ end
+end
diff --git a/spec/ruby/core/enumerable/filter_spec.rb b/spec/ruby/core/enumerable/filter_spec.rb
new file mode 100644
index 0000000000..7e4f8c0b50
--- /dev/null
+++ b/spec/ruby/core/enumerable/filter_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/find_all'
+
+describe "Enumerable#filter" do
+ it_behaves_like(:enumerable_find_all , :filter)
+end
diff --git a/spec/ruby/core/enumerable/find_all_spec.rb b/spec/ruby/core/enumerable/find_all_spec.rb
new file mode 100644
index 0000000000..ce9058fe77
--- /dev/null
+++ b/spec/ruby/core/enumerable/find_all_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/find_all'
+
+describe "Enumerable#find_all" do
+ it_behaves_like :enumerable_find_all , :find_all
+end
diff --git a/spec/ruby/core/enumerable/find_index_spec.rb b/spec/ruby/core/enumerable/find_index_spec.rb
new file mode 100644
index 0000000000..542660fe04
--- /dev/null
+++ b/spec/ruby/core/enumerable/find_index_spec.rb
@@ -0,0 +1,89 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#find_index" do
+ before :each do
+ @elements = [2, 4, 6, 8, 10]
+ @numerous = EnumerableSpecs::Numerous.new(*@elements)
+ @yieldsmixed = EnumerableSpecs::YieldsMixed2.new
+ end
+
+ it "passes each entry in enum to block while block when block is false" do
+ visited_elements = []
+ @numerous.find_index do |element|
+ visited_elements << element
+ false
+ end
+ visited_elements.should == @elements
+ end
+
+ it "returns nil when the block is false" do
+ @numerous.find_index {|e| false }.should == nil
+ end
+
+ it "returns the first index for which the block is not false" do
+ @elements.each_with_index do |element, index|
+ @numerous.find_index {|e| e > element - 1 }.should == index
+ end
+ end
+
+ it "returns the first index found" do
+ repeated = [10, 11, 11, 13, 11, 13, 10, 10, 13, 11]
+ numerous_repeat = EnumerableSpecs::Numerous.new(*repeated)
+ repeated.each do |element|
+ numerous_repeat.find_index(element).should == element - 10
+ end
+ end
+
+ it "returns nil when the element not found" do
+ @numerous.find_index(-1).should == nil
+ end
+
+ it "ignores the block if an argument is given" do
+ -> {
+ @numerous.find_index(-1) {|e| true }.should == nil
+ }.should complain(/given block not used/)
+ end
+
+ it "returns an Enumerator if no block given" do
+ @numerous.find_index.should be_an_instance_of(Enumerator)
+ end
+
+ it "uses #== for testing equality" do
+ [2].to_enum.find_index(2.0).should == 0
+ [2.0].to_enum.find_index(2).should == 0
+ end
+
+ describe "without block" do
+ it "gathers whole arrays as elements when each yields multiple" do
+ @yieldsmixed.find_index([0, 1, 2]).should == 3
+ end
+ end
+
+ describe "with block" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ describe "given a single yield parameter" do
+ it "passes first element to the parameter" do
+ @yieldsmixed.find_index {|a| ScratchPad << a; false }
+ ScratchPad.recorded.should == EnumerableSpecs::YieldsMixed2.first_yields
+ end
+ end
+
+ describe "given a greedy yield parameter" do
+ it "passes a gathered array to the parameter" do
+ @yieldsmixed.find_index {|*args| ScratchPad << args; false }
+ ScratchPad.recorded.should == EnumerableSpecs::YieldsMixed2.greedy_yields
+ end
+ end
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_unknown_size, :find_index
+end
diff --git a/spec/ruby/core/enumerable/find_spec.rb b/spec/ruby/core/enumerable/find_spec.rb
new file mode 100644
index 0000000000..25aa3bf103
--- /dev/null
+++ b/spec/ruby/core/enumerable/find_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/find'
+
+describe "Enumerable#find" do
+ it_behaves_like :enumerable_find , :find
+end
diff --git a/spec/ruby/core/enumerable/first_spec.rb b/spec/ruby/core/enumerable/first_spec.rb
new file mode 100644
index 0000000000..ed1ba599b4
--- /dev/null
+++ b/spec/ruby/core/enumerable/first_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/take'
+
+describe "Enumerable#first" do
+ it "returns the first element" do
+ EnumerableSpecs::Numerous.new.first.should == 2
+ EnumerableSpecs::Empty.new.first.should == nil
+ end
+
+ it "returns nil if self is empty" do
+ EnumerableSpecs::Empty.new.first.should == nil
+ end
+
+ it 'returns a gathered array from yield parameters' do
+ EnumerableSpecs::YieldsMulti.new.to_enum.first.should == [1, 2]
+ EnumerableSpecs::YieldsMixed2.new.to_enum.first.should == nil
+ end
+
+ it "raises a RangeError when passed a Bignum" do
+ enum = EnumerableSpecs::Empty.new
+ -> { enum.first(bignum_value) }.should raise_error(RangeError)
+ end
+
+ describe "when passed an argument" do
+ it_behaves_like :enumerable_take, :first
+ end
+end
diff --git a/spec/ruby/core/enumerable/fixtures/classes.rb b/spec/ruby/core/enumerable/fixtures/classes.rb
new file mode 100644
index 0000000000..fb4951c6e6
--- /dev/null
+++ b/spec/ruby/core/enumerable/fixtures/classes.rb
@@ -0,0 +1,345 @@
+module EnumerableSpecs
+
+ class Numerous
+ include Enumerable
+ def initialize(*list)
+ @list = list.empty? ? [2, 5, 3, 6, 1, 4] : list
+ end
+
+ def each
+ @list.each { |i| yield i }
+ end
+ end
+
+ class NumerousWithSize < Numerous
+ def size
+ @list.size
+ end
+ end
+
+ class EachCounter < Numerous
+ attr_reader :times_called, :times_yielded, :arguments_passed
+ def initialize(*list)
+ super(*list)
+ @times_yielded = @times_called = 0
+ end
+
+ def each(*arg)
+ @times_called += 1
+ @times_yielded = 0
+ @arguments_passed = arg
+ @list.each do |i|
+ @times_yielded +=1
+ yield i
+ end
+ end
+ end
+
+ class Empty
+ include Enumerable
+ def each
+ end
+ end
+
+ class EmptyWithSize
+ include Enumerable
+ def each
+ end
+ def size
+ 0
+ end
+ end
+
+ class ThrowingEach
+ include Enumerable
+ def each
+ raise "from each"
+ end
+ end
+
+ class NoEach
+ include Enumerable
+ end
+
+ # (Legacy form rubycon)
+ class EachDefiner
+
+ include Enumerable
+
+ attr_reader :arr
+
+ def initialize(*arr)
+ @arr = arr
+ end
+
+ def each
+ i = 0
+ loop do
+ break if i == @arr.size
+ yield @arr[i]
+ i += 1
+ end
+ end
+
+ end
+
+ class SortByDummy
+ def initialize(s)
+ @s = s
+ end
+
+ def s
+ @s
+ end
+ end
+
+ class ComparesByVowelCount
+
+ attr_accessor :value, :vowels
+
+ def self.wrap(*args)
+ args.map {|element| ComparesByVowelCount.new(element)}
+ end
+
+ def initialize(string)
+ self.value = string
+ self.vowels = string.gsub(/[^aeiou]/, '').size
+ end
+
+ def <=>(other)
+ self.vowels <=> other.vowels
+ end
+
+ end
+
+ class InvalidComparable
+ def <=>(other)
+ "Not Valid"
+ end
+ end
+
+ class ArrayConvertible
+ attr_accessor :called
+ def initialize(*values)
+ @values = values
+ end
+
+ def to_a
+ self.called = :to_a
+ @values
+ end
+
+ def to_ary
+ self.called = :to_ary
+ @values
+ end
+ end
+
+ class EnumConvertible
+ attr_accessor :called
+ attr_accessor :sym
+ def initialize(delegate)
+ @delegate = delegate
+ end
+
+ def to_enum(sym)
+ self.called = :to_enum
+ self.sym = sym
+ @delegate.to_enum(sym)
+ end
+
+ def respond_to_missing?(*args)
+ @delegate.respond_to?(*args)
+ end
+ end
+
+ class Equals
+ def initialize(obj)
+ @obj = obj
+ end
+ def ==(other)
+ @obj == other
+ end
+ end
+
+ class YieldsMulti
+ include Enumerable
+ def each
+ yield 1,2
+ yield 3,4,5
+ yield 6,7,8,9
+ end
+ end
+
+ class YieldsMultiWithFalse
+ include Enumerable
+ def each
+ yield false,2
+ yield false,4,5
+ yield false,7,8,9
+ end
+ end
+
+ class YieldsMultiWithSingleTrue
+ include Enumerable
+ def each
+ yield false,2
+ yield true,4,5
+ yield false,7,8,9
+ end
+ end
+
+ class YieldsMixed
+ include Enumerable
+ def each
+ yield 1
+ yield [2]
+ yield 3,4
+ yield 5,6,7
+ yield [8,9]
+ yield nil
+ yield []
+ end
+ end
+
+ class YieldsMixed2
+ include Enumerable
+
+ def self.first_yields
+ [nil, 0, 0, 0, 0, nil, :default_arg, [], [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def self.gathered_yields
+ [nil, 0, [0, 1], [0, 1, 2], [0, 1, 2], nil, :default_arg, [], [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def self.gathered_yields_with_args(arg, *args)
+ [nil, 0, [0, 1], [0, 1, 2], [0, 1, 2], nil, arg, args, [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def self.greedy_yields
+ [[], [0], [0, 1], [0, 1, 2], [0, 1, 2], [nil], [:default_arg], [[]], [[]], [[0]], [[0, 1]], [[0, 1, 2]]]
+ end
+
+ def each(arg=:default_arg, *args)
+ yield
+ yield 0
+ yield 0, 1
+ yield 0, 1, 2
+ yield(*[0, 1, 2])
+ yield nil
+ yield arg
+ yield args
+ yield []
+ yield [0]
+ yield [0, 1]
+ yield [0, 1, 2]
+ end
+ end
+
+ class ReverseComparable
+ include Comparable
+ def initialize(num)
+ @num = num
+ end
+
+ attr_accessor :num
+
+ # Reverse comparison
+ def <=>(other)
+ other.num <=> @num
+ end
+ end
+
+ class ComparableWithInteger
+ include Comparable
+ def initialize(num)
+ @num = num
+ end
+
+ def <=>(fixnum)
+ @num <=> fixnum
+ end
+ end
+
+ class Uncomparable
+ def <=>(obj)
+ nil
+ end
+ end
+
+ class Undupable
+ attr_reader :initialize_called, :initialize_dup_called
+ def dup
+ raise "Can't, sorry"
+ end
+
+ def clone
+ raise "Can't, either, sorry"
+ end
+
+ def initialize
+ @initialize_dup = true
+ end
+
+ def initialize_dup(arg)
+ @initialize_dup_called = true
+ end
+ end
+
+ class Freezy
+ include Enumerable
+
+ def each
+ yield 1
+ yield 2
+ end
+
+ def to_a
+ super.freeze
+ end
+ end
+
+ class MapReturnsEnumerable
+ include Enumerable
+
+ class EnumerableMapping
+ include Enumerable
+
+ def initialize(items, block)
+ @items = items
+ @block = block
+ end
+
+ def each
+ @items.each do |i|
+ yield @block.call(i)
+ end
+ end
+ end
+
+ def each
+ yield 1
+ yield 2
+ yield 3
+ end
+
+ def map(&block)
+ EnumerableMapping.new(self, block)
+ end
+ end
+
+ class Pattern
+ attr_reader :yielded
+
+ def initialize(&block)
+ @block = block
+ @yielded = []
+ end
+
+ def ===(*args)
+ @yielded << args
+ @block.call(*args)
+ end
+ end
+end # EnumerableSpecs utility classes
diff --git a/spec/ruby/core/enumerable/flat_map_spec.rb b/spec/ruby/core/enumerable/flat_map_spec.rb
new file mode 100644
index 0000000000..a294b9ddad
--- /dev/null
+++ b/spec/ruby/core/enumerable/flat_map_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/collect_concat'
+
+describe "Enumerable#flat_map" do
+ it_behaves_like :enumerable_collect_concat , :flat_map
+end
diff --git a/spec/ruby/core/enumerable/grep_spec.rb b/spec/ruby/core/enumerable/grep_spec.rb
new file mode 100644
index 0000000000..b81075291f
--- /dev/null
+++ b/spec/ruby/core/enumerable/grep_spec.rb
@@ -0,0 +1,102 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#grep" do
+ before :each do
+ @a = EnumerableSpecs::EachDefiner.new( 2, 4, 6, 8, 10)
+ end
+
+ it "grep without a block should return an array of all elements === pattern" do
+ class EnumerableSpecGrep; def ===(obj); obj == '2'; end; end
+
+ EnumerableSpecs::Numerous.new('2', 'a', 'nil', '3', false).grep(EnumerableSpecGrep.new).should == ['2']
+ end
+
+ it "grep with a block should return an array of elements === pattern passed through block" do
+ class EnumerableSpecGrep2; def ===(obj); /^ca/ =~ obj; end; end
+
+ EnumerableSpecs::Numerous.new("cat", "coat", "car", "cadr", "cost").grep(EnumerableSpecGrep2.new) { |i| i.upcase }.should == ["CAT", "CAR", "CADR"]
+ end
+
+ it "grep the enumerable (rubycon legacy)" do
+ EnumerableSpecs::EachDefiner.new().grep(1).should == []
+ @a.grep(3..7).should == [4,6]
+ @a.grep(3..7) {|a| a+1}.should == [5,7]
+ end
+
+ it "can use $~ in the block when used with a Regexp" do
+ ary = ["aba", "aba"]
+ ary.grep(/a(b)a/) { $1 }.should == ["b", "b"]
+ end
+
+ it "sets $~ in the block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].grep(/b/) { |e|
+ e.should == "abc"
+ $&.should == "b"
+ }
+
+ # Set by the failed match of "def"
+ $~.should == nil
+ end
+
+ ruby_version_is ""..."3.0.0" do
+ it "sets $~ to the last match when given no block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].grep(/b/).should == ["abc"]
+
+ # Set by the failed match of "def"
+ $~.should == nil
+
+ ["abc", "def"].grep(/e/)
+ $&.should == "e"
+ end
+ end
+
+ ruby_version_is "3.0.0" do
+ it "does not set $~ when given no block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].grep(/b/).should == ["abc"]
+ $&.should == "z"
+ end
+
+ it "does not modify Regexp.last_match without block" do
+ "z" =~ /z/ # Reset last match
+ ["abc", "def"].grep(/b/).should == ["abc"]
+ Regexp.last_match[0].should == "z"
+ end
+
+ it "correctly handles non-string elements" do
+ 'set last match' =~ /set last (.*)/
+ [:a, 'b', 'z', :c, 42, nil].grep(/[a-d]/).should == [:a, 'b', :c]
+ $1.should == 'match'
+
+ o = Object.new
+ def o.to_str
+ 'hello'
+ end
+ [o].grep(/ll/).first.should.equal?(o)
+ end
+ end
+
+ describe "with a block" do
+ before :each do
+ @numerous = EnumerableSpecs::Numerous.new(*(0..9).to_a)
+ def (@odd_matcher = BasicObject.new).===(obj)
+ obj.odd?
+ end
+ end
+
+ it "returns an Array of matched elements that mapped by the block" do
+ @numerous.grep(@odd_matcher) { |n| n * 2 }.should == [2, 6, 10, 14, 18]
+ end
+
+ it "calls the block with gathered array when yielded with multiple arguments" do
+ EnumerableSpecs::YieldsMixed2.new.grep(Object){ |e| e }.should == EnumerableSpecs::YieldsMixed2.gathered_yields
+ end
+
+ it "raises an ArgumentError when not given a pattern" do
+ -> { @numerous.grep { |e| e } }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/grep_v_spec.rb b/spec/ruby/core/enumerable/grep_v_spec.rb
new file mode 100644
index 0000000000..35fde27eb6
--- /dev/null
+++ b/spec/ruby/core/enumerable/grep_v_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#grep_v" do
+ before :each do
+ @numerous = EnumerableSpecs::Numerous.new(*(0..9).to_a)
+ def (@odd_matcher = BasicObject.new).===(obj)
+ obj.odd?
+ end
+ end
+
+ it "sets $~ in the block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].grep_v(/e/) { |e|
+ e.should == "abc"
+ $~.should == nil
+ }
+
+ # Set by the match of "def"
+ $&.should == "e"
+ end
+
+ ruby_version_is ""..."3.0.0" do
+ it "sets $~ to the last match when given no block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].grep_v(/e/).should == ["abc"]
+
+ # Set by the match of "def"
+ $&.should == "e"
+
+ ["abc", "def"].grep_v(/b/)
+ $&.should == nil
+ end
+ end
+
+ ruby_version_is "3.0.0" do
+ it "does not set $~ when given no block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].grep_v(/e/).should == ["abc"]
+ $&.should == "z"
+ end
+
+ it "does not modify Regexp.last_match without block" do
+ "z" =~ /z/ # Reset last match
+ ["abc", "def"].grep_v(/e/).should == ["abc"]
+ Regexp.last_match[0].should == "z"
+ end
+
+ it "correctly handles non-string elements" do
+ 'set last match' =~ /set last (.*)/
+ [:a, 'b', 'z', :c, 42, nil].grep_v(/[a-d]/).should == ['z', 42, nil]
+ $1.should == 'match'
+
+ o = Object.new
+ def o.to_str
+ 'hello'
+ end
+ [o].grep_v(/mm/).first.should.equal?(o)
+ end
+ end
+
+ describe "without block" do
+ it "returns an Array of matched elements" do
+ @numerous.grep_v(@odd_matcher).should == [0, 2, 4, 6, 8]
+ end
+
+ it "compares pattern with gathered array when yielded with multiple arguments" do
+ (unmatcher = Object.new).stub!(:===).and_return(false)
+ EnumerableSpecs::YieldsMixed2.new.grep_v(unmatcher).should == EnumerableSpecs::YieldsMixed2.gathered_yields
+ end
+
+ it "raises an ArgumentError when not given a pattern" do
+ -> { @numerous.grep_v }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with block" do
+ it "returns an Array of matched elements that mapped by the block" do
+ @numerous.grep_v(@odd_matcher) { |n| n * 2 }.should == [0, 4, 8, 12, 16]
+ end
+
+ it "calls the block with gathered array when yielded with multiple arguments" do
+ (unmatcher = Object.new).stub!(:===).and_return(false)
+ EnumerableSpecs::YieldsMixed2.new.grep_v(unmatcher){ |e| e }.should == EnumerableSpecs::YieldsMixed2.gathered_yields
+ end
+
+ it "raises an ArgumentError when not given a pattern" do
+ -> { @numerous.grep_v { |e| e } }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/group_by_spec.rb b/spec/ruby/core/enumerable/group_by_spec.rb
new file mode 100644
index 0000000000..4fd1603819
--- /dev/null
+++ b/spec/ruby/core/enumerable/group_by_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#group_by" do
+ it "returns a hash with values grouped according to the block" do
+ e = EnumerableSpecs::Numerous.new("foo", "bar", "baz")
+ h = e.group_by { |word| word[0..0].to_sym }
+ h.should == { f: ["foo"], b: ["bar", "baz"]}
+ end
+
+ it "returns an empty hash for empty enumerables" do
+ EnumerableSpecs::Empty.new.group_by { |x| x}.should == {}
+ end
+
+ it "returns a hash without default_proc" do
+ e = EnumerableSpecs::Numerous.new("foo", "bar", "baz")
+ h = e.group_by { |word| word[0..0].to_sym }
+ h[:some].should be_nil
+ h.default_proc.should be_nil
+ h.default.should be_nil
+ end
+
+ it "returns an Enumerator if called without a block" do
+ EnumerableSpecs::Numerous.new.group_by.should be_an_instance_of(Enumerator)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ e = EnumerableSpecs::YieldsMulti.new
+ h = e.group_by { |i| i }
+ h.should == { [1, 2] => [[1, 2]],
+ [6, 7, 8, 9] => [[6, 7, 8, 9]],
+ [3, 4, 5] => [[3, 4, 5]] }
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :group_by
+end
diff --git a/spec/ruby/core/enumerable/include_spec.rb b/spec/ruby/core/enumerable/include_spec.rb
new file mode 100644
index 0000000000..dab1b04451
--- /dev/null
+++ b/spec/ruby/core/enumerable/include_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/include'
+
+describe "Enumerable#include?" do
+ it_behaves_like :enumerable_include, :include?
+end
diff --git a/spec/ruby/core/enumerable/inject_spec.rb b/spec/ruby/core/enumerable/inject_spec.rb
new file mode 100644
index 0000000000..e1fe216144
--- /dev/null
+++ b/spec/ruby/core/enumerable/inject_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/inject'
+
+describe "Enumerable#inject" do
+ it_behaves_like :enumerable_inject, :inject
+end
diff --git a/spec/ruby/core/enumerable/lazy_spec.rb b/spec/ruby/core/enumerable/lazy_spec.rb
new file mode 100644
index 0000000000..9a9ead81a0
--- /dev/null
+++ b/spec/ruby/core/enumerable/lazy_spec.rb
@@ -0,0 +1,10 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#lazy" do
+ it "returns an instance of Enumerator::Lazy" do
+ EnumerableSpecs::Numerous.new.lazy.should be_an_instance_of(Enumerator::Lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerable/map_spec.rb b/spec/ruby/core/enumerable/map_spec.rb
new file mode 100644
index 0000000000..d65aec238c
--- /dev/null
+++ b/spec/ruby/core/enumerable/map_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/collect'
+
+describe "Enumerable#map" do
+ it_behaves_like :enumerable_collect , :map
+end
diff --git a/spec/ruby/core/enumerable/max_by_spec.rb b/spec/ruby/core/enumerable/max_by_spec.rb
new file mode 100644
index 0000000000..ec1738ea3b
--- /dev/null
+++ b/spec/ruby/core/enumerable/max_by_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#max_by" do
+ it "returns an enumerator if no block" do
+ EnumerableSpecs::Numerous.new(42).max_by.should be_an_instance_of(Enumerator)
+ end
+
+ it "returns nil if #each yields no objects" do
+ EnumerableSpecs::Empty.new.max_by {|o| o.nonesuch }.should == nil
+ end
+
+ it "returns the object for whom the value returned by block is the largest" do
+ EnumerableSpecs::Numerous.new(*%w[1 2 3]).max_by {|obj| obj.to_i }.should == '3'
+ EnumerableSpecs::Numerous.new(*%w[three five]).max_by {|obj| obj.length }.should == 'three'
+ end
+
+ it "returns the object that appears first in #each in case of a tie" do
+ a, b, c = '1', '2', '2'
+ EnumerableSpecs::Numerous.new(a, b, c).max_by {|obj| obj.to_i }.should equal(b)
+ end
+
+ it "uses max.<=>(current) to determine order" do
+ a, b, c = (1..3).map{|n| EnumerableSpecs::ReverseComparable.new(n)}
+
+ # Just using self here to avoid additional complexity
+ EnumerableSpecs::Numerous.new(a, b, c).max_by {|obj| obj }.should == a
+ end
+
+ it "is able to return the maximum for enums that contain nils" do
+ enum = EnumerableSpecs::Numerous.new(nil, nil, true)
+ enum.max_by {|o| o.nil? ? 0 : 1 }.should == true
+ enum.max_by {|o| o.nil? ? 1 : 0 }.should == nil
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.max_by {|e| e.size}.should == [6, 7, 8, 9]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :max_by
+
+ context "when called with an argument n" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(101, 55, 1, 20, 33, 500, 60)
+ end
+
+ context "without a block" do
+ it "returns an enumerator" do
+ @enum.max_by(2).should be_an_instance_of(Enumerator)
+ end
+ end
+
+ context "with a block" do
+ it "returns an array containing the maximum n elements based on the block's value" do
+ result = @enum.max_by(3) { |i| i.to_s }
+ result.should == [60, 55, 500]
+ end
+
+ context "on a enumerable of length x where x < n" do
+ it "returns an array containing the maximum n elements of length n" do
+ result = @enum.max_by(500) { |i| i.to_s }
+ result.length.should == 7
+ end
+ end
+
+ context "when n is negative" do
+ it "raises an ArgumentError" do
+ -> { @enum.max_by(-1) { |i| i.to_s } }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context "when n is nil" do
+ it "returns the maximum element" do
+ @enum.max_by(nil) { |i| i.to_s }.should == 60
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/max_spec.rb b/spec/ruby/core/enumerable/max_spec.rb
new file mode 100644
index 0000000000..0c11ca0969
--- /dev/null
+++ b/spec/ruby/core/enumerable/max_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#max" do
+ before :each do
+ @e_strs = EnumerableSpecs::EachDefiner.new("333", "22", "666666", "1", "55555", "1010101010")
+ @e_ints = EnumerableSpecs::EachDefiner.new( 333, 22, 666666, 55555, 1010101010)
+ end
+
+ it "returns the maximum element" do
+ EnumerableSpecs::Numerous.new.max.should == 6
+ end
+
+ it "returns the maximum element (basics cases)" do
+ EnumerableSpecs::EachDefiner.new(55).max.should == 55
+
+ EnumerableSpecs::EachDefiner.new(11,99).max.should == 99
+ EnumerableSpecs::EachDefiner.new(99,11).max.should == 99
+ EnumerableSpecs::EachDefiner.new(2, 33, 4, 11).max.should == 33
+
+ EnumerableSpecs::EachDefiner.new(1,2,3,4,5).max.should == 5
+ EnumerableSpecs::EachDefiner.new(5,4,3,2,1).max.should == 5
+ EnumerableSpecs::EachDefiner.new(1,4,3,5,2).max.should == 5
+ EnumerableSpecs::EachDefiner.new(5,5,5,5,5).max.should == 5
+
+ EnumerableSpecs::EachDefiner.new("aa","tt").max.should == "tt"
+ EnumerableSpecs::EachDefiner.new("tt","aa").max.should == "tt"
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").max.should == "4"
+
+ @e_strs.max.should == "666666"
+ @e_ints.max.should == 1010101010
+ end
+
+ it "returns nil for an empty Enumerable" do
+ EnumerableSpecs::EachDefiner.new.max.should == nil
+ end
+
+ it "raises a NoMethodError for elements without #<=>" do
+ -> do
+ EnumerableSpecs::EachDefiner.new(BasicObject.new, BasicObject.new).max
+ end.should raise_error(NoMethodError)
+ end
+
+ it "raises an ArgumentError for incomparable elements" do
+ -> do
+ EnumerableSpecs::EachDefiner.new(11,"22").max
+ end.should raise_error(ArgumentError)
+ -> do
+ EnumerableSpecs::EachDefiner.new(11,12,22,33).max{|a, b| nil}
+ end.should raise_error(ArgumentError)
+ end
+
+ context "when passed a block" do
+ it "returns the maximum element" do
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").max {|a,b| a <=> b }.should == "4"
+ EnumerableSpecs::EachDefiner.new( 2 , 33 , 4 , 11 ).max {|a,b| a <=> b }.should == 33
+
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").max {|a,b| b <=> a }.should == "11"
+ EnumerableSpecs::EachDefiner.new( 2 , 33 , 4 , 11 ).max {|a,b| b <=> a }.should == 2
+
+ @e_strs.max {|a,b| a.length <=> b.length }.should == "1010101010"
+
+ @e_strs.max {|a,b| a <=> b }.should == "666666"
+ @e_strs.max {|a,b| a.to_i <=> b.to_i }.should == "1010101010"
+
+ @e_ints.max {|a,b| a <=> b }.should == 1010101010
+ @e_ints.max {|a,b| a.to_s <=> b.to_s }.should == 666666
+ end
+ end
+
+ it "returns the maximum for enumerables that contain nils" do
+ arr = EnumerableSpecs::Numerous.new(nil, nil, true)
+ arr.max { |a, b|
+ x = a.nil? ? 1 : a ? 0 : -1
+ y = b.nil? ? 1 : b ? 0 : -1
+ x <=> y
+ }.should == nil
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.max.should == [6, 7, 8, 9]
+ end
+
+ context "when called with an argument n" do
+ context "without a block" do
+ it "returns an array containing the maximum n elements" do
+ result = @e_ints.max(2)
+ result.should == [1010101010, 666666]
+ end
+ end
+
+ context "with a block" do
+ it "returns an array containing the maximum n elements" do
+ result = @e_ints.max(2) { |a, b| a * 2 <=> b * 2 }
+ result.should == [1010101010, 666666]
+ end
+ end
+
+ context "on a enumerable of length x where x < n" do
+ it "returns an array containing the maximum n elements of length x" do
+ result = @e_ints.max(500)
+ result.length.should == 5
+ end
+ end
+
+ context "that is negative" do
+ it "raises an ArgumentError" do
+ -> { @e_ints.max(-1) }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context "that is nil" do
+ it "returns the maximum element" do
+ @e_ints.max(nil).should == 1010101010
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/member_spec.rb b/spec/ruby/core/enumerable/member_spec.rb
new file mode 100644
index 0000000000..1fe3cebd28
--- /dev/null
+++ b/spec/ruby/core/enumerable/member_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/include'
+
+describe "Enumerable#member?" do
+ it_behaves_like :enumerable_include, :member?
+end
diff --git a/spec/ruby/core/enumerable/min_by_spec.rb b/spec/ruby/core/enumerable/min_by_spec.rb
new file mode 100644
index 0000000000..3ff87e49d8
--- /dev/null
+++ b/spec/ruby/core/enumerable/min_by_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#min_by" do
+ it "returns an enumerator if no block" do
+ EnumerableSpecs::Numerous.new(42).min_by.should be_an_instance_of(Enumerator)
+ end
+
+ it "returns nil if #each yields no objects" do
+ EnumerableSpecs::Empty.new.min_by {|o| o.nonesuch }.should == nil
+ end
+
+ it "returns the object for whom the value returned by block is the smallest" do
+ EnumerableSpecs::Numerous.new(*%w[3 2 1]).min_by {|obj| obj.to_i }.should == '1'
+ EnumerableSpecs::Numerous.new(*%w[five three]).min_by {|obj| obj.length }.should == 'five'
+ end
+
+ it "returns the object that appears first in #each in case of a tie" do
+ a, b, c = '2', '1', '1'
+ EnumerableSpecs::Numerous.new(a, b, c).min_by {|obj| obj.to_i }.should equal(b)
+ end
+
+ it "uses min.<=>(current) to determine order" do
+ a, b, c = (1..3).map{|n| EnumerableSpecs::ReverseComparable.new(n)}
+
+ # Just using self here to avoid additional complexity
+ EnumerableSpecs::Numerous.new(a, b, c).min_by {|obj| obj }.should == c
+ end
+
+ it "is able to return the minimum for enums that contain nils" do
+ enum = EnumerableSpecs::Numerous.new(nil, nil, true)
+ enum.min_by {|o| o.nil? ? 0 : 1 }.should == nil
+ enum.min_by {|o| o.nil? ? 1 : 0 }.should == true
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.min_by {|e| e.size}.should == [1, 2]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :min_by
+
+ context "when called with an argument n" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(101, 55, 1, 20, 33, 500, 60)
+ end
+
+ context "without a block" do
+ it "returns an enumerator" do
+ @enum.min_by(2).should be_an_instance_of(Enumerator)
+ end
+ end
+
+ context "with a block" do
+ it "returns an array containing the minimum n elements based on the block's value" do
+ result = @enum.min_by(3) { |i| i.to_s }
+ result.should == [1, 101, 20]
+ end
+
+ context "on a enumerable of length x where x < n" do
+ it "returns an array containing the minimum n elements of length n" do
+ result = @enum.min_by(500) { |i| i.to_s }
+ result.length.should == 7
+ end
+ end
+
+ context "when n is negative" do
+ it "raises an ArgumentError" do
+ -> { @enum.min_by(-1) { |i| i.to_s } }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context "when n is nil" do
+ it "returns the minimum element" do
+ @enum.min_by(nil) { |i| i.to_s }.should == 1
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/min_spec.rb b/spec/ruby/core/enumerable/min_spec.rb
new file mode 100644
index 0000000000..4b6ae248fa
--- /dev/null
+++ b/spec/ruby/core/enumerable/min_spec.rb
@@ -0,0 +1,123 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#min" do
+ before :each do
+ @e_strs = EnumerableSpecs::EachDefiner.new("333", "22", "666666", "1", "55555", "1010101010")
+ @e_ints = EnumerableSpecs::EachDefiner.new( 333, 22, 666666, 55555, 1010101010)
+ end
+
+ it "min should return the minimum element" do
+ EnumerableSpecs::Numerous.new.min.should == 1
+ end
+
+ it "returns the minimum (basic cases)" do
+ EnumerableSpecs::EachDefiner.new(55).min.should == 55
+
+ EnumerableSpecs::EachDefiner.new(11,99).min.should == 11
+ EnumerableSpecs::EachDefiner.new(99,11).min.should == 11
+ EnumerableSpecs::EachDefiner.new(2, 33, 4, 11).min.should == 2
+
+ EnumerableSpecs::EachDefiner.new(1,2,3,4,5).min.should == 1
+ EnumerableSpecs::EachDefiner.new(5,4,3,2,1).min.should == 1
+ EnumerableSpecs::EachDefiner.new(4,1,3,5,2).min.should == 1
+ EnumerableSpecs::EachDefiner.new(5,5,5,5,5).min.should == 5
+
+ EnumerableSpecs::EachDefiner.new("aa","tt").min.should == "aa"
+ EnumerableSpecs::EachDefiner.new("tt","aa").min.should == "aa"
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").min.should == "11"
+
+ @e_strs.min.should == "1"
+ @e_ints.min.should == 22
+ end
+
+ it "returns nil for an empty Enumerable" do
+ EnumerableSpecs::EachDefiner.new.min.should be_nil
+ end
+
+ it "raises a NoMethodError for elements without #<=>" do
+ -> do
+ EnumerableSpecs::EachDefiner.new(BasicObject.new, BasicObject.new).min
+ end.should raise_error(NoMethodError)
+ end
+
+ it "raises an ArgumentError for incomparable elements" do
+ -> do
+ EnumerableSpecs::EachDefiner.new(11,"22").min
+ end.should raise_error(ArgumentError)
+ -> do
+ EnumerableSpecs::EachDefiner.new(11,12,22,33).min{|a, b| nil}
+ end.should raise_error(ArgumentError)
+ end
+
+ it "returns the minimum when using a block rule" do
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").min {|a,b| a <=> b }.should == "11"
+ EnumerableSpecs::EachDefiner.new( 2 , 33 , 4 , 11 ).min {|a,b| a <=> b }.should == 2
+
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").min {|a,b| b <=> a }.should == "4"
+ EnumerableSpecs::EachDefiner.new( 2 , 33 , 4 , 11 ).min {|a,b| b <=> a }.should == 33
+
+ EnumerableSpecs::EachDefiner.new( 1, 2, 3, 4 ).min {|a,b| 15 }.should == 1
+
+ EnumerableSpecs::EachDefiner.new(11,12,22,33).min{|a, b| 2 }.should == 11
+ @i = -2
+ EnumerableSpecs::EachDefiner.new(11,12,22,33).min{|a, b| @i += 1 }.should == 12
+
+ @e_strs.min {|a,b| a.length <=> b.length }.should == "1"
+
+ @e_strs.min {|a,b| a <=> b }.should == "1"
+ @e_strs.min {|a,b| a.to_i <=> b.to_i }.should == "1"
+
+ @e_ints.min {|a,b| a <=> b }.should == 22
+ @e_ints.min {|a,b| a.to_s <=> b.to_s }.should == 1010101010
+ end
+
+ it "returns the minimum for enumerables that contain nils" do
+ arr = EnumerableSpecs::Numerous.new(nil, nil, true)
+ arr.min { |a, b|
+ x = a.nil? ? -1 : a ? 0 : 1
+ y = b.nil? ? -1 : b ? 0 : 1
+ x <=> y
+ }.should == nil
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.min.should == [1, 2]
+ end
+
+ context "when called with an argument n" do
+ context "without a block" do
+ it "returns an array containing the minimum n elements" do
+ result = @e_ints.min(2)
+ result.should == [22, 333]
+ end
+ end
+
+ context "with a block" do
+ it "returns an array containing the minimum n elements" do
+ result = @e_ints.min(2) { |a, b| a * 2 <=> b * 2 }
+ result.should == [22, 333]
+ end
+ end
+
+ context "on a enumerable of length x where x < n" do
+ it "returns an array containing the minimum n elements of length x" do
+ result = @e_ints.min(500)
+ result.length.should == 5
+ end
+ end
+
+ context "that is negative" do
+ it "raises an ArgumentError" do
+ -> { @e_ints.min(-1) }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context "that is nil" do
+ it "returns the minimum element" do
+ @e_ints.min(nil).should == 22
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/minmax_by_spec.rb b/spec/ruby/core/enumerable/minmax_by_spec.rb
new file mode 100644
index 0000000000..a6a9249270
--- /dev/null
+++ b/spec/ruby/core/enumerable/minmax_by_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#minmax_by" do
+ it "returns an enumerator if no block" do
+ EnumerableSpecs::Numerous.new(42).minmax_by.should be_an_instance_of(Enumerator)
+ end
+
+ it "returns nil if #each yields no objects" do
+ EnumerableSpecs::Empty.new.minmax_by {|o| o.nonesuch }.should == [nil, nil]
+ end
+
+ it "returns the object for whom the value returned by block is the largest" do
+ EnumerableSpecs::Numerous.new(*%w[1 2 3]).minmax_by {|obj| obj.to_i }.should == ['1', '3']
+ EnumerableSpecs::Numerous.new(*%w[three five]).minmax_by {|obj| obj.length }.should == ['five', 'three']
+ end
+
+ it "returns the object that appears first in #each in case of a tie" do
+ a, b, c, d = '1', '1', '2', '2'
+ mm = EnumerableSpecs::Numerous.new(a, b, c, d).minmax_by {|obj| obj.to_i }
+ mm[0].should equal(a)
+ mm[1].should equal(c)
+ end
+
+ it "uses min/max.<=>(current) to determine order" do
+ a, b, c = (1..3).map{|n| EnumerableSpecs::ReverseComparable.new(n)}
+
+ # Just using self here to avoid additional complexity
+ EnumerableSpecs::Numerous.new(a, b, c).minmax_by {|obj| obj }.should == [c, a]
+ end
+
+ it "is able to return the maximum for enums that contain nils" do
+ enum = EnumerableSpecs::Numerous.new(nil, nil, true)
+ enum.minmax_by {|o| o.nil? ? 0 : 1 }.should == [nil, true]
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.minmax_by {|e| e.size}.should == [[1, 2], [6, 7, 8, 9]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :minmax_by
+end
diff --git a/spec/ruby/core/enumerable/minmax_spec.rb b/spec/ruby/core/enumerable/minmax_spec.rb
new file mode 100644
index 0000000000..f5f17ef079
--- /dev/null
+++ b/spec/ruby/core/enumerable/minmax_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/enumerable/minmax'
+
+describe "Enumerable#minmax" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(6, 4, 5, 10, 8)
+ @empty_enum = EnumerableSpecs::Empty.new
+ @incomparable_enum = EnumerableSpecs::Numerous.new(BasicObject.new, BasicObject.new)
+ @incompatible_enum = EnumerableSpecs::Numerous.new(11,"22")
+ @strs = EnumerableSpecs::Numerous.new("333", "2", "60", "55555", "1010", "111")
+ end
+
+ it_behaves_like :enumerable_minmax, :minmax
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.minmax.should == [[1, 2], [6, 7, 8, 9]]
+ end
+end
diff --git a/spec/ruby/core/enumerable/none_spec.rb b/spec/ruby/core/enumerable/none_spec.rb
new file mode 100644
index 0000000000..99bb7f7a4e
--- /dev/null
+++ b/spec/ruby/core/enumerable/none_spec.rb
@@ -0,0 +1,147 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#none?" do
+ before :each do
+ @empty = EnumerableSpecs::Empty.new
+ @enum = EnumerableSpecs::Numerous.new
+ @enum1 = EnumerableSpecs::Numerous.new(0, 1, 2, -1)
+ @enum2 = EnumerableSpecs::Numerous.new(nil, false, true)
+ end
+
+ it "always returns true on empty enumeration" do
+ @empty.should.none?
+ @empty.none? { true }.should == true
+ end
+
+ it "raises an ArgumentError when more than 1 argument is provided" do
+ -> { @enum.none?(1, 2, 3) }.should raise_error(ArgumentError)
+ -> { [].none?(1, 2, 3) }.should raise_error(ArgumentError)
+ -> { {}.none?(1, 2, 3) }.should raise_error(ArgumentError)
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.none?
+ }.should raise_error(RuntimeError)
+
+ -> {
+ EnumerableSpecs::ThrowingEach.new.none? { false }
+ }.should raise_error(RuntimeError)
+ end
+
+ describe "with no block" do
+ it "returns true if none of the elements in self are true" do
+ e = EnumerableSpecs::Numerous.new(false, nil, false)
+ e.none?.should be_true
+ end
+
+ it "returns false if at least one of the elements in self are true" do
+ e = EnumerableSpecs::Numerous.new(false, nil, true, false)
+ e.none?.should be_false
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMultiWithFalse.new
+ multi.none?.should be_false
+ end
+ end
+
+ describe "with a block" do
+ before :each do
+ @e = EnumerableSpecs::Numerous.new(1,1,2,3,4)
+ end
+
+ it "passes each element to the block in turn until it returns true" do
+ acc = []
+ @e.none? {|e| acc << e; false }
+ acc.should == [1,1,2,3,4]
+ end
+
+ it "stops passing elements to the block when it returns true" do
+ acc = []
+ @e.none? {|e| acc << e; e == 3 ? true : false }
+ acc.should == [1,1,2,3]
+ end
+
+ it "returns true if the block never returns true" do
+ @e.none? {|e| false }.should be_true
+ end
+
+ it "returns false if the block ever returns true" do
+ @e.none? {|e| e == 3 ? true : false }.should be_false
+ end
+
+ it "does not hide exceptions out of the block" do
+ -> {
+ @enum.none? { raise "from block" }
+ }.should raise_error(RuntimeError)
+ end
+
+ it "gathers initial args as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.none? { |e| yielded << e; false }
+ yielded.should == [1, 3, 6]
+ end
+
+ it "yields multiple arguments when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.none? { |*args| yielded << args; false }
+ yielded.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+ end
+
+ describe 'when given a pattern argument' do
+ it "calls `===` on the pattern the return value " do
+ pattern = EnumerableSpecs::Pattern.new { |x| x == 3 }
+ @enum1.none?(pattern).should == true
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+ end
+
+ it "always returns true on empty enumeration" do
+ @empty.none?(Integer).should == true
+ [].none?(Integer).should == true
+ {}.none?(NilClass).should == true
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.none?(Integer)
+ }.should raise_error(RuntimeError)
+ end
+
+ it "returns true if the pattern never returns a truthy value" do
+ @enum2.none?(Integer).should == true
+ pattern = EnumerableSpecs::Pattern.new { |x| nil }
+ @enum.none?(pattern).should == true
+
+ [1, 42, 3].none?(pattern).should == true
+ {a: 1, b: 2}.none?(pattern).should == true
+ end
+
+ it "returns false if the pattern ever returns other than false or nil" do
+ pattern = EnumerableSpecs::Pattern.new { |x| x < 0 }
+ @enum1.none?(pattern).should == false
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+
+ [1, 2, 3, -1].none?(pattern).should == false
+ {a: 1}.none?(Array).should == false
+ end
+
+ it "does not hide exceptions out of pattern#===" do
+ pattern = EnumerableSpecs::Pattern.new { raise "from pattern" }
+ -> {
+ @enum.none?(pattern)
+ }.should raise_error(RuntimeError)
+ end
+
+ it "calls the pattern with gathered array when yielded with multiple arguments" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ pattern = EnumerableSpecs::Pattern.new { false }
+ multi.none?(pattern).should == true
+ pattern.yielded.should == [[[1, 2]], [[3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/one_spec.rb b/spec/ruby/core/enumerable/one_spec.rb
new file mode 100644
index 0000000000..47bfd65a0f
--- /dev/null
+++ b/spec/ruby/core/enumerable/one_spec.rb
@@ -0,0 +1,149 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#one?" do
+ before :each do
+ @empty = EnumerableSpecs::Empty.new
+ @enum = EnumerableSpecs::Numerous.new
+ @enum1 = EnumerableSpecs::Numerous.new(0, 1, 2, -1)
+ @enum2 = EnumerableSpecs::Numerous.new(nil, false, true)
+ end
+
+ it "always returns false on empty enumeration" do
+ @empty.should_not.one?
+ @empty.one? { true }.should == false
+ end
+
+ it "raises an ArgumentError when more than 1 argument is provided" do
+ -> { @enum.one?(1, 2, 3) }.should raise_error(ArgumentError)
+ -> { [].one?(1, 2, 3) }.should raise_error(ArgumentError)
+ -> { {}.one?(1, 2, 3) }.should raise_error(ArgumentError)
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.one?
+ }.should raise_error(RuntimeError)
+
+ -> {
+ EnumerableSpecs::ThrowingEach.new.one? { false }
+ }.should raise_error(RuntimeError)
+ end
+
+ describe "with no block" do
+ it "returns true if only one element evaluates to true" do
+ [false, nil, true].one?.should be_true
+ end
+
+ it "returns false if two elements evaluate to true" do
+ [false, :value, nil, true].one?.should be_false
+ end
+
+ it "returns false if all elements evaluate to false" do
+ [false, nil, false].one?.should be_false
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMultiWithSingleTrue.new
+ multi.one?.should be_false
+ end
+ end
+
+ describe "with a block" do
+ it "returns true if block returns true once" do
+ [:a, :b, :c].one? { |s| s == :a }.should be_true
+ end
+
+ it "returns false if the block returns true more than once" do
+ [:a, :b, :c].one? { |s| s == :a || s == :b }.should be_false
+ end
+
+ it "returns false if the block only returns false" do
+ [:a, :b, :c].one? { |s| s == :d }.should be_false
+ end
+
+ it "does not hide exceptions out of the block" do
+ -> {
+ @enum.one? { raise "from block" }
+ }.should raise_error(RuntimeError)
+ end
+
+ it "gathers initial args as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.one? { |e| yielded << e; false }.should == false
+ yielded.should == [1, 3, 6]
+ end
+
+ it "yields multiple arguments when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.one? { |*args| yielded << args; false }.should == false
+ yielded.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+ end
+
+
+ describe 'when given a pattern argument' do
+ it "calls `===` on the pattern the return value " do
+ pattern = EnumerableSpecs::Pattern.new { |x| x == 1 }
+ @enum1.one?(pattern).should == true
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+ end
+
+ it "always returns false on empty enumeration" do
+ @empty.one?(Integer).should == false
+ [].one?(Integer).should == false
+ {}.one?(NilClass).should == false
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.one?(Integer)
+ }.should raise_error(RuntimeError)
+ end
+
+ it "returns true if the pattern returns a truthy value only once" do
+ @enum2.one?(NilClass).should == true
+ pattern = EnumerableSpecs::Pattern.new { |x| x == 2 }
+ @enum1.one?(pattern).should == true
+
+ [1, 2, 42, 3].one?(pattern).should == true
+
+ pattern = EnumerableSpecs::Pattern.new { |x| x == [:b, 2] }
+ {a: 1, b: 2}.one?(pattern).should == true
+ end
+
+ it "returns false if the pattern returns a truthy value more than once" do
+ pattern = EnumerableSpecs::Pattern.new { |x| !x }
+ @enum2.one?(pattern).should == false
+ pattern.yielded.should == [[nil], [false]]
+
+ [1, 2, 3].one?(Integer).should == false
+ {a: 1, b: 2}.one?(Array).should == false
+ end
+
+ it "returns false if the pattern never returns a truthy value" do
+ pattern = EnumerableSpecs::Pattern.new { |x| nil }
+ @enum1.one?(pattern).should == false
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+
+ [1, 2, 3].one?(pattern).should == false
+ {a: 1}.one?(pattern).should == false
+ end
+
+ it "does not hide exceptions out of pattern#===" do
+ pattern = EnumerableSpecs::Pattern.new { raise "from pattern" }
+ -> {
+ @enum.one?(pattern)
+ }.should raise_error(RuntimeError)
+ end
+
+ it "calls the pattern with gathered array when yielded with multiple arguments" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ pattern = EnumerableSpecs::Pattern.new { false }
+ multi.one?(pattern).should == false
+ pattern.yielded.should == [[[1, 2]], [[3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/partition_spec.rb b/spec/ruby/core/enumerable/partition_spec.rb
new file mode 100644
index 0000000000..d3d220b4b4
--- /dev/null
+++ b/spec/ruby/core/enumerable/partition_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#partition" do
+ it "returns two arrays, the first containing elements for which the block is true, the second containing the rest" do
+ EnumerableSpecs::Numerous.new.partition { |i| i % 2 == 0 }.should == [[2, 6, 4], [5, 3, 1]]
+ end
+
+ it "returns an Enumerator if called without a block" do
+ EnumerableSpecs::Numerous.new.partition.should be_an_instance_of(Enumerator)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.partition {|e| e == [3, 4, 5] }.should == [[[3, 4, 5]], [[1, 2], [6, 7, 8, 9]]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :partition
+end
diff --git a/spec/ruby/core/enumerable/reduce_spec.rb b/spec/ruby/core/enumerable/reduce_spec.rb
new file mode 100644
index 0000000000..bc8691c1b0
--- /dev/null
+++ b/spec/ruby/core/enumerable/reduce_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/inject'
+
+describe "Enumerable#reduce" do
+ it_behaves_like :enumerable_inject, :reduce
+end
diff --git a/spec/ruby/core/enumerable/reject_spec.rb b/spec/ruby/core/enumerable/reject_spec.rb
new file mode 100644
index 0000000000..0d86b49ea2
--- /dev/null
+++ b/spec/ruby/core/enumerable/reject_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#reject" do
+ it "returns an array of the elements for which block is false" do
+ EnumerableSpecs::Numerous.new.reject { |i| i > 3 }.should == [2, 3, 1]
+ entries = (1..10).to_a
+ numerous = EnumerableSpecs::Numerous.new(*entries)
+ numerous.reject {|i| i % 2 == 0 }.should == [1,3,5,7,9]
+ numerous.reject {|i| true }.should == []
+ numerous.reject {|i| false }.should == entries
+ end
+
+ it "returns an Enumerator if called without a block" do
+ EnumerableSpecs::Numerous.new.reject.should be_an_instance_of(Enumerator)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.reject {|e| e == [3, 4, 5] }.should == [[1, 2], [6, 7, 8, 9]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :reject
+end
diff --git a/spec/ruby/core/enumerable/reverse_each_spec.rb b/spec/ruby/core/enumerable/reverse_each_spec.rb
new file mode 100644
index 0000000000..2b1c233488
--- /dev/null
+++ b/spec/ruby/core/enumerable/reverse_each_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#reverse_each" do
+ it "traverses enum in reverse order and pass each element to block" do
+ a=[]
+ EnumerableSpecs::Numerous.new.reverse_each { |i| a << i }
+ a.should == [4, 1, 6, 3, 5, 2]
+ end
+
+ it "returns an Enumerator if no block given" do
+ enum = EnumerableSpecs::Numerous.new.reverse_each
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == [4, 1, 6, 3, 5, 2]
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.reverse_each {|e| yielded << e }
+ yielded.should == [[6, 7, 8, 9], [3, 4, 5], [1, 2]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :reverse_each
+end
diff --git a/spec/ruby/core/enumerable/select_spec.rb b/spec/ruby/core/enumerable/select_spec.rb
new file mode 100644
index 0000000000..11168eb42e
--- /dev/null
+++ b/spec/ruby/core/enumerable/select_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/find_all'
+
+describe "Enumerable#select" do
+ it_behaves_like :enumerable_find_all , :select
+end
diff --git a/spec/ruby/core/enumerable/shared/collect.rb b/spec/ruby/core/enumerable/shared/collect.rb
new file mode 100644
index 0000000000..6df1a616eb
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/collect.rb
@@ -0,0 +1,107 @@
+require_relative 'enumerable_enumeratorized'
+
+describe :enumerable_collect, shared: true do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "returns a new array with the results of passing each element to block" do
+ entries = [0, 1, 3, 4, 5, 6]
+ numerous = EnumerableSpecs::Numerous.new(*entries)
+ numerous.send(@method) { |i| i % 2 }.should == [0, 1, 1, 0, 1, 0]
+ numerous.send(@method) { |i| i }.should == entries
+ end
+
+ it "passes through the values yielded by #each_with_index" do
+ [:a, :b].each_with_index.send(@method) { |x, i| ScratchPad << [x, i]; nil }
+ ScratchPad.recorded.should == [[:a, 0], [:b, 1]]
+ end
+
+ it "gathers initial args as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method) {|e| e}.should == [1,3,6]
+ end
+
+ it "only yields increasing values for a Range" do
+ (1..0).send(@method) { |x| x }.should == []
+ (1..1).send(@method) { |x| x }.should == [1]
+ (1..2).send(@method) { |x| x }.should == [1, 2]
+ end
+
+ it "returns an enumerator when no block given" do
+ enum = EnumerableSpecs::Numerous.new.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+ enum.each { |i| -i }.should == [-2, -5, -3, -6, -1, -4]
+ end
+
+ it "reports the same arity as the given block" do
+ entries = [0, 1, 3, 4, 5, 6]
+ numerous = EnumerableSpecs::Numerous.new(*entries)
+
+ def numerous.each(&block)
+ ScratchPad << block.arity
+ super
+ end
+
+ numerous.send(@method) { |a, b| a % 2 }.should == [0, 1, 1, 0, 1, 0]
+ ScratchPad.recorded.should == [2]
+ ScratchPad.clear
+ ScratchPad.record []
+ numerous.send(@method) { |i| i }.should == entries
+ ScratchPad.recorded.should == [1]
+ end
+
+ it "yields an Array of 2 elements for a Hash when block arity is 1" do
+ c = Class.new do
+ def register(a)
+ ScratchPad << a
+ end
+ end
+ m = c.new.method(:register)
+
+ ScratchPad.record []
+ { 1 => 'a', 2 => 'b' }.map(&m)
+ ScratchPad.recorded.should == [[1, 'a'], [2, 'b']]
+ end
+
+ it "yields 2 arguments for a Hash when block arity is 2" do
+ c = Class.new do
+ def register(a, b)
+ ScratchPad << [a, b]
+ end
+ end
+ m = c.new.method(:register)
+
+ ScratchPad.record []
+ { 1 => 'a', 2 => 'b' }.map(&m)
+ ScratchPad.recorded.should == [[1, 'a'], [2, 'b']]
+ end
+
+ it "raises an error for a Hash when an arity enforcing block of arity >2 is passed in" do
+ c = Class.new do
+ def register(a, b, c)
+ end
+ end
+ m = c.new.method(:register)
+
+ -> do
+ { 1 => 'a', 2 => 'b' }.map(&m)
+ end.should raise_error(ArgumentError)
+ end
+
+ it "calls the each method on sub-classes" do
+ c = Class.new(Hash) do
+ def each
+ ScratchPad << 'in each'
+ super
+ end
+ end
+ h = c.new
+ h[1] = 'a'
+ ScratchPad.record []
+ h.send(@method) { |k,v| v }
+ ScratchPad.recorded.should == ['in each']
+ end
+
+ it_should_behave_like :enumerable_enumeratorized_with_origin_size
+end
diff --git a/spec/ruby/core/enumerable/shared/collect_concat.rb b/spec/ruby/core/enumerable/shared/collect_concat.rb
new file mode 100644
index 0000000000..ddd431baeb
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/collect_concat.rb
@@ -0,0 +1,54 @@
+require_relative 'enumerable_enumeratorized'
+
+describe :enumerable_collect_concat, shared: true do
+ it "yields elements to the block and flattens one level" do
+ numerous = EnumerableSpecs::Numerous.new(1, [2, 3], [4, [5, 6]], {foo: :bar})
+ numerous.send(@method) { |i| i }.should == [1, 2, 3, 4, [5, 6], {foo: :bar}]
+ end
+
+ it "appends non-Array elements that do not define #to_ary" do
+ obj = mock("to_ary undefined")
+
+ numerous = EnumerableSpecs::Numerous.new(1, obj, 2)
+ numerous.send(@method) { |i| i }.should == [1, obj, 2]
+ end
+
+ it "concatenates the result of calling #to_ary if it returns an Array" do
+ obj = mock("to_ary defined")
+ obj.should_receive(:to_ary).and_return([:a, :b])
+
+ numerous = EnumerableSpecs::Numerous.new(1, obj, 2)
+ numerous.send(@method) { |i| i }.should == [1, :a, :b, 2]
+ end
+
+ it "does not call #to_a" do
+ obj = mock("to_ary undefined")
+ obj.should_not_receive(:to_a)
+
+ numerous = EnumerableSpecs::Numerous.new(1, obj, 2)
+ numerous.send(@method) { |i| i }.should == [1, obj, 2]
+ end
+
+ it "appends an element that defines #to_ary that returns nil" do
+ obj = mock("to_ary defined")
+ obj.should_receive(:to_ary).and_return(nil)
+
+ numerous = EnumerableSpecs::Numerous.new(1, obj, 2)
+ numerous.send(@method) { |i| i }.should == [1, obj, 2]
+ end
+
+ it "raises a TypeError if an element defining #to_ary does not return an Array or nil" do
+ obj = mock("to_ary defined")
+ obj.should_receive(:to_ary).and_return("array")
+
+ -> { [1, obj, 3].send(@method) { |i| i } }.should raise_error(TypeError)
+ end
+
+ it "returns an enumerator when no block given" do
+ enum = EnumerableSpecs::Numerous.new(1, 2).send(@method)
+ enum.should be_an_instance_of(Enumerator)
+ enum.each{ |i| [i] * i }.should == [1, 2, 2]
+ end
+
+ it_should_behave_like :enumerable_enumeratorized_with_origin_size
+end
diff --git a/spec/ruby/core/enumerable/shared/entries.rb b/spec/ruby/core/enumerable/shared/entries.rb
new file mode 100644
index 0000000000..e32eb23d2a
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/entries.rb
@@ -0,0 +1,16 @@
+describe :enumerable_entries, shared: true do
+ it "returns an array containing the elements" do
+ numerous = EnumerableSpecs::Numerous.new(1, nil, 'a', 2, false, true)
+ numerous.send(@method).should == [1, nil, "a", 2, false, true]
+ end
+
+ it "passes through the values yielded by #each_with_index" do
+ [:a, :b].each_with_index.send(@method).should == [[:a, 0], [:b, 1]]
+ end
+
+ it "passes arguments to each" do
+ count = EnumerableSpecs::EachCounter.new(1, 2, 3)
+ count.send(@method, :hello, "world").should == [1, 2, 3]
+ count.arguments_passed.should == [:hello, "world"]
+ end
+end
diff --git a/spec/ruby/core/enumerable/shared/enumerable_enumeratorized.rb b/spec/ruby/core/enumerable/shared/enumerable_enumeratorized.rb
new file mode 100644
index 0000000000..e2bbe18eda
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/enumerable_enumeratorized.rb
@@ -0,0 +1,33 @@
+require_relative 'enumeratorized'
+
+describe :enumerable_enumeratorized_with_unknown_size, shared: true do
+ describe "Enumerable with size" do
+ before :all do
+ @object = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 4)
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+
+ describe "Enumerable with no size" do
+ before :all do
+ @object = EnumerableSpecs::Numerous.new(1, 2, 3, 4)
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+end
+
+describe :enumerable_enumeratorized_with_origin_size, shared: true do
+ describe "Enumerable with size" do
+ before :all do
+ @object = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 4)
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+ end
+
+ describe "Enumerable with no size" do
+ before :all do
+ @object = EnumerableSpecs::Numerous.new(1, 2, 3, 4)
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+end
diff --git a/spec/ruby/core/enumerable/shared/enumeratorized.rb b/spec/ruby/core/enumerable/shared/enumeratorized.rb
new file mode 100644
index 0000000000..05d27b5783
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/enumeratorized.rb
@@ -0,0 +1,42 @@
+describe :enumeratorized_with_unknown_size, shared: true do
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ it "size returns nil" do
+ @object.send(*@method).size.should == nil
+ end
+ end
+ end
+end
+
+describe :enumeratorized_with_origin_size, shared: true do
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ it "size returns the enumerable size" do
+ @object.send(*@method).size.should == @object.size
+ end
+ end
+ end
+end
+
+describe :enumeratorized_with_cycle_size, shared: true do
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should be the result of multiplying the enumerable size by the argument passed" do
+ @object.cycle(2).size.should == @object.size * 2
+ @object.cycle(7).size.should == @object.size * 7
+ @object.cycle(0).size.should == 0
+ @empty_object.cycle(2).size.should == 0
+ end
+
+ it "should be zero when the argument passed is 0 or less" do
+ @object.cycle(-1).size.should == 0
+ end
+
+ it "should be Float::INFINITY when no argument is passed" do
+ @object.cycle.size.should == Float::INFINITY
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/shared/find.rb b/spec/ruby/core/enumerable/shared/find.rb
new file mode 100644
index 0000000000..61d63ba3d5
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/find.rb
@@ -0,0 +1,77 @@
+require_relative 'enumerable_enumeratorized'
+
+describe :enumerable_find, shared: true do
+ # #detect and #find are aliases, so we only need one function
+ before :each do
+ ScratchPad.record []
+ @elements = [2, 4, 6, 8, 10]
+ @numerous = EnumerableSpecs::Numerous.new(*@elements)
+ @empty = []
+ end
+
+ it "passes each entry in enum to block while block when block is false" do
+ visited_elements = []
+ @numerous.send(@method) do |element|
+ visited_elements << element
+ false
+ end
+ visited_elements.should == @elements
+ end
+
+ it "returns nil when the block is false and there is no ifnone proc given" do
+ @numerous.send(@method) {|e| false }.should == nil
+ end
+
+ it "returns the first element for which the block is not false" do
+ @elements.each do |element|
+ @numerous.send(@method) {|e| e > element - 1 }.should == element
+ end
+ end
+
+ it "returns the value of the ifnone proc if the block is false" do
+ fail_proc = -> { "cheeseburgers" }
+ @numerous.send(@method, fail_proc) {|e| false }.should == "cheeseburgers"
+ end
+
+ it "doesn't call the ifnone proc if an element is found" do
+ fail_proc = -> { raise "This shouldn't have been called" }
+ @numerous.send(@method, fail_proc) {|e| e == @elements.first }.should == 2
+ end
+
+ it "calls the ifnone proc only once when the block is false" do
+ times = 0
+ fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" }
+ @numerous.send(@method, fail_proc) {|e| false }.should == "cheeseburgers"
+ end
+
+ it "calls the ifnone proc when there are no elements" do
+ fail_proc = -> { "yay" }
+ @empty.send(@method, fail_proc) {|e| true}.should == "yay"
+ end
+
+ it "ignores the ifnone argument when nil" do
+ @numerous.send(@method, nil) {|e| false }.should == nil
+ end
+
+ it "passes through the values yielded by #each_with_index" do
+ [:a, :b].each_with_index.send(@method) { |x, i| ScratchPad << [x, i]; nil }
+ ScratchPad.recorded.should == [[:a, 0], [:b, 1]]
+ end
+
+ it "returns an enumerator when no block given" do
+ @numerous.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "passes the ifnone proc to the enumerator" do
+ times = 0
+ fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" }
+ @numerous.send(@method, fail_proc).each {|e| false }.should == "cheeseburgers"
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method) {|e| e == [1, 2] }.should == [1, 2]
+ end
+
+ it_should_behave_like :enumerable_enumeratorized_with_unknown_size
+end
diff --git a/spec/ruby/core/enumerable/shared/find_all.rb b/spec/ruby/core/enumerable/shared/find_all.rb
new file mode 100644
index 0000000000..1bbe71f372
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/find_all.rb
@@ -0,0 +1,31 @@
+require_relative 'enumerable_enumeratorized'
+
+describe :enumerable_find_all, shared: true do
+ before :each do
+ ScratchPad.record []
+ @elements = (1..10).to_a
+ @numerous = EnumerableSpecs::Numerous.new(*@elements)
+ end
+
+ it "returns all elements for which the block is not false" do
+ @numerous.send(@method) {|i| i % 3 == 0 }.should == [3, 6, 9]
+ @numerous.send(@method) {|i| true }.should == @elements
+ @numerous.send(@method) {|i| false }.should == []
+ end
+
+ it "returns an enumerator when no block given" do
+ @numerous.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "passes through the values yielded by #each_with_index" do
+ [:a, :b].each_with_index.send(@method) { |x, i| ScratchPad << [x, i] }
+ ScratchPad.recorded.should == [[:a, 0], [:b, 1]]
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method) {|e| e == [3, 4, 5] }.should == [[3, 4, 5]]
+ end
+
+ it_should_behave_like :enumerable_enumeratorized_with_origin_size
+end
diff --git a/spec/ruby/core/enumerable/shared/include.rb b/spec/ruby/core/enumerable/shared/include.rb
new file mode 100644
index 0000000000..569f350fd5
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/include.rb
@@ -0,0 +1,34 @@
+describe :enumerable_include, shared: true do
+ it "returns true if any element == argument for numbers" do
+ class EnumerableSpecIncludeP; def ==(obj) obj == 5; end; end
+
+ elements = (0..5).to_a
+ EnumerableSpecs::Numerous.new(*elements).send(@method,5).should == true
+ EnumerableSpecs::Numerous.new(*elements).send(@method,10).should == false
+ EnumerableSpecs::Numerous.new(*elements).send(@method,EnumerableSpecIncludeP.new).should == true
+ end
+
+ it "returns true if any element == argument for other objects" do
+ class EnumerableSpecIncludeP11; def ==(obj); obj == '11'; end; end
+
+ elements = ('0'..'5').to_a + [EnumerableSpecIncludeP11.new]
+ EnumerableSpecs::Numerous.new(*elements).send(@method,'5').should == true
+ EnumerableSpecs::Numerous.new(*elements).send(@method,'10').should == false
+ EnumerableSpecs::Numerous.new(*elements).send(@method,EnumerableSpecIncludeP11.new).should == true
+ EnumerableSpecs::Numerous.new(*elements).send(@method,'11').should == true
+ end
+
+
+ it "returns true if any member of enum equals obj when == compare different classes (legacy rubycon)" do
+ # equality is tested with ==
+ EnumerableSpecs::Numerous.new(2,4,6,8,10).send(@method, 2.0).should == true
+ EnumerableSpecs::Numerous.new(2,4,[6,8],10).send(@method, [6, 8]).should == true
+ EnumerableSpecs::Numerous.new(2,4,[6,8],10).send(@method, [6.0, 8.0]).should == true
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method, [1,2]).should be_true
+ end
+
+end
diff --git a/spec/ruby/core/enumerable/shared/inject.rb b/spec/ruby/core/enumerable/shared/inject.rb
new file mode 100644
index 0000000000..c5907f92d8
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/inject.rb
@@ -0,0 +1,77 @@
+describe :enumerable_inject, shared: true do
+ it "with argument takes a block with an accumulator (with argument as initial value) and the current element. Value of block becomes new accumulator" do
+ a = []
+ EnumerableSpecs::Numerous.new.send(@method, 0) { |memo, i| a << [memo, i]; i }
+ a.should == [[0, 2], [2, 5], [5, 3], [3, 6], [6, 1], [1, 4]]
+ EnumerableSpecs::EachDefiner.new(true, true, true).send(@method, nil) {|result, i| i && result}.should == nil
+ end
+
+ it "produces an array of the accumulator and the argument when given a block with a *arg" do
+ a = []
+ [1,2].send(@method, 0) {|*args| a << args; args[0] + args[1]}
+ a.should == [[0, 1], [1, 2]]
+ end
+
+ it "can take two argument" do
+ EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, :-).should == 4
+ end
+
+ it "ignores the block if two arguments" do
+ EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, :-) { raise "we never get here"}.should == 4
+ [].send(@method, 3, :+) { raise "we never get here"}.should == 3
+ end
+
+ it "can take a symbol argument" do
+ EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, :-).should == 4
+ end
+
+ it "without argument takes a block with an accumulator (with first element as initial value) and the current element. Value of block becomes new accumulator" do
+ a = []
+ EnumerableSpecs::Numerous.new.send(@method) { |memo, i| a << [memo, i]; i }
+ a.should == [[2, 5], [5, 3], [3, 6], [6, 1], [1, 4]]
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method, []) {|acc, e| acc << e }.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+
+ it "with inject arguments(legacy rubycon)" do
+ # with inject argument
+ EnumerableSpecs::EachDefiner.new().send(@method, 1) {|acc,x| 999 }.should == 1
+ EnumerableSpecs::EachDefiner.new(2).send(@method, 1) {|acc,x| 999 }.should == 999
+ EnumerableSpecs::EachDefiner.new(2).send(@method, 1) {|acc,x| acc }.should == 1
+ EnumerableSpecs::EachDefiner.new(2).send(@method, 1) {|acc,x| x }.should == 2
+
+ EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method, 100) {|acc,x| acc + x }.should == 110
+ EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method, 100) {|acc,x| acc * x }.should == 2400
+
+ EnumerableSpecs::EachDefiner.new('a','b','c').send(@method, "z") {|result, i| i+result}.should == "cbaz"
+ end
+
+ it "without inject arguments(legacy rubycon)" do
+ # no inject argument
+ EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| 999 } .should == 2
+ EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| acc }.should == 2
+ EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| x }.should == 2
+
+ EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method) {|acc,x| acc + x }.should == 10
+ EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method) {|acc,x| acc * x }.should == 24
+
+ EnumerableSpecs::EachDefiner.new('a','b','c').send(@method) {|result, i| i+result}.should == "cba"
+ EnumerableSpecs::EachDefiner.new(3, 4, 5).send(@method) {|result, i| result*i}.should == 60
+ EnumerableSpecs::EachDefiner.new([1], 2, 'a','b').send(@method){|r,i| r<<i}.should == [1, 2, 'a', 'b']
+
+ end
+
+ it "returns nil when fails(legacy rubycon)" do
+ EnumerableSpecs::EachDefiner.new().send(@method) {|acc,x| 999 }.should == nil
+ end
+
+ ruby_bug '#18635', ''...'3.2' do
+ it "raises an ArgumentError when no parameters or block is given" do
+ -> { [1,2].send(@method) }.should raise_error(ArgumentError)
+ -> { {one: 1, two: 2}.send(@method) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/shared/take.rb b/spec/ruby/core/enumerable/shared/take.rb
new file mode 100644
index 0000000000..ce2ace20fa
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/take.rb
@@ -0,0 +1,63 @@
+describe :enumerable_take, shared: true do
+ before :each do
+ @values = [4,3,2,1,0,-1]
+ @enum = EnumerableSpecs::Numerous.new(*@values)
+ end
+
+ it "returns the first count elements if given a count" do
+ @enum.send(@method, 2).should == [4, 3]
+ @enum.send(@method, 4).should == [4, 3, 2, 1] # See redmine #1686 !
+ end
+
+ it "returns an empty array when passed count on an empty array" do
+ empty = EnumerableSpecs::Empty.new
+ empty.send(@method, 0).should == []
+ empty.send(@method, 1).should == []
+ empty.send(@method, 2).should == []
+ end
+
+ it "returns an empty array when passed count == 0" do
+ @enum.send(@method, 0).should == []
+ end
+
+ it "returns an array containing the first element when passed count == 1" do
+ @enum.send(@method, 1).should == [4]
+ end
+
+ it "raises an ArgumentError when count is negative" do
+ -> { @enum.send(@method, -1) }.should raise_error(ArgumentError)
+ end
+
+ it "returns the entire array when count > length" do
+ @enum.send(@method, 100).should == @values
+ @enum.send(@method, 8).should == @values # See redmine #1686 !
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(3).at_most(:twice) # called twice, no apparent reason. See redmine #1554
+ @enum.send(@method, obj).should == [4, 3, 2]
+ end
+
+ it "raises a TypeError if the passed argument is not numeric" do
+ -> { @enum.send(@method, nil) }.should raise_error(TypeError)
+ -> { @enum.send(@method, "a") }.should raise_error(TypeError)
+
+ obj = mock("nonnumeric")
+ -> { @enum.send(@method, obj) }.should raise_error(TypeError)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method, 1).should == [[1, 2]]
+ end
+
+ it "consumes only what is needed" do
+ thrower = EnumerableSpecs::ThrowingEach.new
+ thrower.send(@method, 0).should == []
+ counter = EnumerableSpecs::EachCounter.new(1,2,3,4)
+ counter.send(@method, 2).should == [1,2]
+ counter.times_called.should == 1
+ counter.times_yielded.should == 2
+ end
+end
diff --git a/spec/ruby/core/enumerable/slice_after_spec.rb b/spec/ruby/core/enumerable/slice_after_spec.rb
new file mode 100644
index 0000000000..0e46688db1
--- /dev/null
+++ b/spec/ruby/core/enumerable/slice_after_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#slice_after" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(7, 6, 5, 4, 3, 2, 1)
+ end
+
+ describe "when given an argument and no block" do
+ it "calls === on the argument to determine when to yield" do
+ arg = mock("filter")
+ arg.should_receive(:===).and_return(false, true, false, false, false, true, false)
+ e = @enum.slice_after(arg)
+ e.should be_an_instance_of(Enumerator)
+ e.to_a.should == [[7, 6], [5, 4, 3, 2], [1]]
+ end
+
+ it "doesn't yield an empty array if the filter matches the first entry or the last entry" do
+ arg = mock("filter")
+ arg.should_receive(:===).and_return(true).exactly(7)
+ e = @enum.slice_after(arg)
+ e.to_a.should == [[7], [6], [5], [4], [3], [2], [1]]
+ end
+
+ it "uses standard boolean as a test" do
+ arg = mock("filter")
+ arg.should_receive(:===).and_return(false, :foo, nil, false, false, 42, false)
+ e = @enum.slice_after(arg)
+ e.to_a.should == [[7, 6], [5, 4, 3, 2], [1]]
+ end
+ end
+
+ describe "when given a block" do
+ describe "and no argument" do
+ it "calls the block to determine when to yield" do
+ e = @enum.slice_after{ |i| i == 6 || i == 2 }
+ e.should be_an_instance_of(Enumerator)
+ e.to_a.should == [[7, 6], [5, 4, 3, 2], [1]]
+ end
+ end
+
+ describe "and an argument" do
+ it "raises an ArgumentError" do
+ -> { @enum.slice_after(42) { |i| i == 6 } }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ it "raises an ArgumentError when given an incorrect number of arguments" do
+ -> { @enum.slice_after("one", "two") }.should raise_error(ArgumentError)
+ -> { @enum.slice_after }.should raise_error(ArgumentError)
+ end
+end
+
+describe "when an iterator method yields more than one value" do
+ it "processes all yielded values" do
+ enum = EnumerableSpecs::YieldsMulti.new
+ result = enum.slice_after { |i| i == [3, 4, 5] }.to_a
+ result.should == [[[1, 2], [3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+end
diff --git a/spec/ruby/core/enumerable/slice_before_spec.rb b/spec/ruby/core/enumerable/slice_before_spec.rb
new file mode 100644
index 0000000000..f9b33f7b28
--- /dev/null
+++ b/spec/ruby/core/enumerable/slice_before_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#slice_before" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(7,6,5,4,3,2,1)
+ end
+
+ describe "when given an argument and no block" do
+ it "calls === on the argument to determine when to yield" do
+ arg = mock "filter"
+ arg.should_receive(:===).and_return(false, true, false, false, false, true, false)
+ e = @enum.slice_before(arg)
+ e.should be_an_instance_of(Enumerator)
+ e.to_a.should == [[7], [6, 5, 4, 3], [2, 1]]
+ end
+
+ it "doesn't yield an empty array if the filter matches the first entry or the last entry" do
+ arg = mock "filter"
+ arg.should_receive(:===).and_return(true).exactly(7)
+ e = @enum.slice_before(arg)
+ e.to_a.should == [[7], [6], [5], [4], [3], [2], [1]]
+ end
+
+ it "uses standard boolean as a test" do
+ arg = mock "filter"
+ arg.should_receive(:===).and_return(false, :foo, nil, false, false, 42, false)
+ e = @enum.slice_before(arg)
+ e.to_a.should == [[7], [6, 5, 4, 3], [2, 1]]
+ end
+ end
+
+ describe "when given a block" do
+ describe "and no argument" do
+ it "calls the block to determine when to yield" do
+ e = @enum.slice_before{|i| i == 6 || i == 2}
+ e.should be_an_instance_of(Enumerator)
+ e.to_a.should == [[7], [6, 5, 4, 3], [2, 1]]
+ end
+ end
+
+ it "does not accept arguments" do
+ -> {
+ @enum.slice_before(1) {}
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises an ArgumentError when given an incorrect number of arguments" do
+ -> { @enum.slice_before("one", "two") }.should raise_error(ArgumentError)
+ -> { @enum.slice_before }.should raise_error(ArgumentError)
+ end
+
+ describe "when an iterator method yields more than one value" do
+ it "processes all yielded values" do
+ enum = EnumerableSpecs::YieldsMulti.new
+ result = enum.slice_before { |i| i == [3, 4, 5] }.to_a
+ result.should == [[[1, 2]], [[3, 4, 5], [6, 7, 8, 9]]]
+ end
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_unknown_size, [:slice_before, 3]
+end
diff --git a/spec/ruby/core/enumerable/slice_when_spec.rb b/spec/ruby/core/enumerable/slice_when_spec.rb
new file mode 100644
index 0000000000..6b8ea0923e
--- /dev/null
+++ b/spec/ruby/core/enumerable/slice_when_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#slice_when" do
+ before :each do
+ ary = [10, 9, 7, 6, 4, 3, 2, 1]
+ @enum = EnumerableSpecs::Numerous.new(*ary)
+ @result = @enum.slice_when { |i, j| i - 1 != j }
+ @enum_length = ary.length
+ end
+
+ context "when given a block" do
+ it "returns an enumerator" do
+ @result.should be_an_instance_of(Enumerator)
+ end
+
+ it "splits chunks between adjacent elements i and j where the block returns true" do
+ @result.to_a.should == [[10, 9], [7, 6], [4, 3, 2, 1]]
+ end
+
+ it "calls the block for length of the receiver enumerable minus one times" do
+ times_called = 0
+ @enum.slice_when do |i, j|
+ times_called += 1
+ i - 1 != j
+ end.to_a
+ times_called.should == (@enum_length - 1)
+ end
+
+ it "doesn't yield an empty array if the block matches the first or the last time" do
+ @enum.slice_when { true }.to_a.should == [[10], [9], [7], [6], [4], [3], [2], [1]]
+ end
+
+ it "doesn't yield an empty array on a small enumerable" do
+ EnumerableSpecs::Empty.new.slice_when { raise }.to_a.should == []
+ EnumerableSpecs::Numerous.new(42).slice_when { raise }.to_a.should == [[42]]
+ end
+ end
+
+ context "when not given a block" do
+ it "raises an ArgumentError" do
+ -> { @enum.slice_when }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "when an iterator method yields more than one value" do
+ it "processes all yielded values" do
+ def foo
+ yield 1, 2
+ end
+ to_enum(:foo).slice_when { true }.to_a.should == [[[1, 2]]]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/sort_by_spec.rb b/spec/ruby/core/enumerable/sort_by_spec.rb
new file mode 100644
index 0000000000..8fdd923fb4
--- /dev/null
+++ b/spec/ruby/core/enumerable/sort_by_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#sort_by" do
+ it "returns an array of elements ordered by the result of block" do
+ a = EnumerableSpecs::Numerous.new("once", "upon", "a", "time")
+ a.sort_by { |i| i[0] }.should == ["a", "once", "time", "upon"]
+ end
+
+ it "sorts the object by the given attribute" do
+ a = EnumerableSpecs::SortByDummy.new("fooo")
+ b = EnumerableSpecs::SortByDummy.new("bar")
+
+ ar = [a, b].sort_by { |d| d.s }
+ ar.should == [b, a]
+ end
+
+ it "returns an Enumerator when a block is not supplied" do
+ a = EnumerableSpecs::Numerous.new("a","b")
+ a.sort_by.should be_an_instance_of(Enumerator)
+ a.to_a.should == ["a", "b"]
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.sort_by {|e| e.size}.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+
+ it "returns an array of elements when a block is supplied and #map returns an enumerable" do
+ b = EnumerableSpecs::MapReturnsEnumerable.new
+ b.sort_by{ |x| -x }.should == [3, 2, 1]
+ end
+
+ it "calls #each to iterate over the elements to be sorted" do
+ b = EnumerableSpecs::Numerous.new( 1, 2, 3 )
+ b.should_receive(:each).once.and_yield(1).and_yield(2).and_yield(3)
+ b.should_not_receive :map
+ b.sort_by { |x| -x }.should == [3, 2, 1]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :sort_by
+end
diff --git a/spec/ruby/core/enumerable/sort_spec.rb b/spec/ruby/core/enumerable/sort_spec.rb
new file mode 100644
index 0000000000..6fc64f325e
--- /dev/null
+++ b/spec/ruby/core/enumerable/sort_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#sort" do
+ it "sorts by the natural order as defined by <=>" do
+ EnumerableSpecs::Numerous.new.sort.should == [1, 2, 3, 4, 5, 6]
+ sorted = EnumerableSpecs::ComparesByVowelCount.wrap("a" * 1, "a" * 2, "a"*3, "a"*4, "a"*5)
+ EnumerableSpecs::Numerous.new(sorted[2],sorted[0],sorted[1],sorted[3],sorted[4]).sort.should == sorted
+ end
+
+ it "yields elements to the provided block" do
+ EnumerableSpecs::Numerous.new.sort { |a, b| b <=> a }.should == [6, 5, 4, 3, 2, 1]
+ EnumerableSpecs::Numerous.new(2,0,1,3,4).sort { |n, m| -(n <=> m) }.should == [4,3,2,1,0]
+ end
+
+ it "raises a NoMethodError if elements do not define <=>" do
+ -> do
+ EnumerableSpecs::Numerous.new(BasicObject.new, BasicObject.new, BasicObject.new).sort
+ end.should raise_error(NoMethodError)
+ end
+
+ it "sorts enumerables that contain nils" do
+ arr = EnumerableSpecs::Numerous.new(nil, true, nil, false, nil, true, nil, false, nil)
+ arr.sort { |a, b|
+ x = a ? -1 : a.nil? ? 0 : 1
+ y = b ? -1 : b.nil? ? 0 : 1
+ x <=> y
+ }.should == [true, true, nil, nil, nil, nil, nil, false, false]
+ end
+
+ it "compare values returned by block with 0" do
+ EnumerableSpecs::Numerous.new.sort { |n, m| -(n+m) * (n <=> m) }.should == [6, 5, 4, 3, 2, 1]
+ EnumerableSpecs::Numerous.new.sort { |n, m|
+ EnumerableSpecs::ComparableWithInteger.new(-(n+m) * (n <=> m))
+ }.should == [6, 5, 4, 3, 2, 1]
+ -> {
+ EnumerableSpecs::Numerous.new.sort { |n, m| (n <=> m).to_s }
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error if objects can't be compared" do
+ a=EnumerableSpecs::Numerous.new(EnumerableSpecs::Uncomparable.new, EnumerableSpecs::Uncomparable.new)
+ -> {a.sort}.should raise_error(ArgumentError)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.sort {|a, b| a.first <=> b.first}.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+
+ it "doesn't raise an error if #to_a returns a frozen Array" do
+ EnumerableSpecs::Freezy.new.sort.should == [1,2]
+ end
+end
diff --git a/spec/ruby/core/enumerable/sum_spec.rb b/spec/ruby/core/enumerable/sum_spec.rb
new file mode 100644
index 0000000000..fc173e4173
--- /dev/null
+++ b/spec/ruby/core/enumerable/sum_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Enumerable#sum' do
+ before :each do
+ @enum = Object.new.to_enum
+ class << @enum
+ def each
+ yield 0
+ yield(-1)
+ yield 2
+ yield 2/3r
+ end
+ end
+ end
+
+ it 'returns amount of the elements with taking an argument as the initial value' do
+ @enum.sum(10).should == 35/3r
+ end
+
+ it 'gives 0 as a default argument' do
+ @enum.sum.should == 5/3r
+ end
+
+ context 'with a block' do
+ it 'transforms the elements' do
+ @enum.sum { |element| element * 2 }.should == 10/3r
+ end
+
+ it 'does not destructure array elements' do
+ class << @enum
+ def each
+ yield [1,2]
+ yield [3]
+ end
+ end
+
+ @enum.sum(&:last).should == 5
+ end
+ end
+
+ # https://bugs.ruby-lang.org/issues/12217
+ # https://github.com/ruby/ruby/blob/master/doc/ChangeLog-2.4.0#L6208-L6214
+ it "uses Kahan's compensated summation algorithm for precise sum of float numbers" do
+ floats = [2.7800000000000002, 5.0, 2.5, 4.44, 3.89, 3.89, 4.44, 7.78, 5.0, 2.7800000000000002, 5.0, 2.5].to_enum
+ naive_sum = floats.reduce { |sum, e| sum + e }
+ naive_sum.should == 50.00000000000001
+ floats.sum.should == 50.0
+ end
+end
diff --git a/spec/ruby/core/enumerable/take_spec.rb b/spec/ruby/core/enumerable/take_spec.rb
new file mode 100644
index 0000000000..41a7438330
--- /dev/null
+++ b/spec/ruby/core/enumerable/take_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/take'
+
+describe "Enumerable#take" do
+ it "requires an argument" do
+ ->{ EnumerableSpecs::Numerous.new.take}.should raise_error(ArgumentError)
+ end
+
+ describe "when passed an argument" do
+ it_behaves_like :enumerable_take, :take
+ end
+end
diff --git a/spec/ruby/core/enumerable/take_while_spec.rb b/spec/ruby/core/enumerable/take_while_spec.rb
new file mode 100644
index 0000000000..26db39ac4b
--- /dev/null
+++ b/spec/ruby/core/enumerable/take_while_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#take_while" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(3, 2, 1, :go)
+ end
+
+ it "returns an Enumerator if no block given" do
+ @enum.take_while.should be_an_instance_of(Enumerator)
+ end
+
+ it "returns no/all elements for {true/false} block" do
+ @enum.take_while{true}.should == @enum.to_a
+ @enum.take_while{false}.should == []
+ end
+
+ it "accepts returns other than true/false" do
+ @enum.take_while{1}.should == @enum.to_a
+ @enum.take_while{nil}.should == []
+ end
+
+ it "passes elements to the block until the first false" do
+ a = []
+ @enum.take_while{|obj| (a << obj).size < 3}.should == [3, 2]
+ a.should == [3, 2, 1]
+ end
+
+ it "will only go through what's needed" do
+ enum = EnumerableSpecs::EachCounter.new(4, 3, 2, 1, :stop)
+ enum.take_while { |x|
+ break 42 if x == 3
+ true
+ }.should == 42
+ enum.times_yielded.should == 2
+ end
+
+ it "doesn't return self when it could" do
+ a = [1,2,3]
+ a.take_while{true}.should_not equal(a)
+ end
+
+ it "calls the block with initial args when yielded with multiple arguments" do
+ yields = []
+ EnumerableSpecs::YieldsMixed.new.take_while{ |v| yields << v }
+ yields.should == [1, [2], 3, 5, [8, 9], nil, []]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_unknown_size, :take_while
+end
diff --git a/spec/ruby/core/enumerable/tally_spec.rb b/spec/ruby/core/enumerable/tally_spec.rb
new file mode 100644
index 0000000000..f09a8f533a
--- /dev/null
+++ b/spec/ruby/core/enumerable/tally_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#tally" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "returns a hash with counts according to the value" do
+ enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
+ enum.tally.should == { 'foo' => 2, 'bar' => 1, 'baz' => 1}
+ end
+
+ it "returns a hash without default" do
+ hash = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz').tally
+ hash.default_proc.should be_nil
+ hash.default.should be_nil
+ end
+
+ it "returns an empty hash for empty enumerables" do
+ EnumerableSpecs::Empty.new.tally.should == {}
+ end
+
+ it "counts values as gathered array when yielded with multiple arguments" do
+ EnumerableSpecs::YieldsMixed2.new.tally.should == EnumerableSpecs::YieldsMixed2.gathered_yields.group_by(&:itself).transform_values(&:size)
+ end
+
+ it "does not call given block" do
+ enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
+ enum.tally { |v| ScratchPad << v }
+ ScratchPad.recorded.should == []
+ end
+end
+
+ruby_version_is "3.1" do
+ describe "Enumerable#tally with a hash" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "returns a hash with counts according to the value" do
+ enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
+ enum.tally({ 'foo' => 1 }).should == { 'foo' => 3, 'bar' => 1, 'baz' => 1}
+ end
+
+ it "returns the given hash" do
+ enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
+ hash = { 'foo' => 1 }
+ enum.tally(hash).should equal(hash)
+ end
+
+ it "raises a FrozenError and does not update the given hash when the hash is frozen" do
+ enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
+ hash = { 'foo' => 1 }.freeze
+ -> { enum.tally(hash) }.should raise_error(FrozenError)
+ hash.should == { 'foo' => 1 }
+ end
+
+ it "does not call given block" do
+ enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
+ enum.tally({ 'foo' => 1 }) { |v| ScratchPad << v }
+ ScratchPad.recorded.should == []
+ end
+
+ it "ignores the default value" do
+ enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
+ enum.tally(Hash.new(100)).should == { 'foo' => 2, 'bar' => 1, 'baz' => 1}
+ end
+
+ it "ignores the default proc" do
+ enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
+ enum.tally(Hash.new {100}).should == { 'foo' => 2, 'bar' => 1, 'baz' => 1}
+ end
+
+ it "needs the values counting each elements to be an integer" do
+ enum = EnumerableSpecs::Numerous.new('foo')
+ -> { enum.tally({ 'foo' => 'bar' }) }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/to_a_spec.rb b/spec/ruby/core/enumerable/to_a_spec.rb
new file mode 100644
index 0000000000..0f3060dc48
--- /dev/null
+++ b/spec/ruby/core/enumerable/to_a_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/entries'
+
+describe "Enumerable#to_a" do
+ it_behaves_like :enumerable_entries , :to_a
+end
diff --git a/spec/ruby/core/enumerable/to_h_spec.rb b/spec/ruby/core/enumerable/to_h_spec.rb
new file mode 100644
index 0000000000..0489134552
--- /dev/null
+++ b/spec/ruby/core/enumerable/to_h_spec.rb
@@ -0,0 +1,88 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#to_h" do
+ it "converts empty enumerable to empty hash" do
+ enum = EnumerableSpecs::EachDefiner.new
+ enum.to_h.should == {}
+ end
+
+ it "converts yielded [key, value] pairs to a hash" do
+ enum = EnumerableSpecs::EachDefiner.new([:a, 1], [:b, 2])
+ enum.to_h.should == { a: 1, b: 2 }
+ end
+
+ it "uses the last value of a duplicated key" do
+ enum = EnumerableSpecs::EachDefiner.new([:a, 1], [:b, 2], [:a, 3])
+ enum.to_h.should == { a: 3, b: 2 }
+ end
+
+ it "calls #to_ary on contents" do
+ pair = mock('to_ary')
+ pair.should_receive(:to_ary).and_return([:b, 2])
+ enum = EnumerableSpecs::EachDefiner.new([:a, 1], pair)
+ enum.to_h.should == { a: 1, b: 2 }
+ end
+
+ it "forwards arguments to #each" do
+ enum = Object.new
+ def enum.each(*args)
+ yield(*args)
+ yield([:b, 2])
+ end
+ enum.extend Enumerable
+ enum.to_h(:a, 1).should == { a: 1, b: 2 }
+ end
+
+ it "raises TypeError if an element is not an array" do
+ enum = EnumerableSpecs::EachDefiner.new(:x)
+ -> { enum.to_h }.should raise_error(TypeError)
+ end
+
+ it "raises ArgumentError if an element is not a [key, value] pair" do
+ enum = EnumerableSpecs::EachDefiner.new([:x])
+ -> { enum.to_h }.should raise_error(ArgumentError)
+ end
+
+ context "with block" do
+ before do
+ @enum = EnumerableSpecs::EachDefiner.new(:a, :b)
+ end
+
+ it "converts [key, value] pairs returned by the block to a hash" do
+ @enum.to_h { |k| [k, k.to_s] }.should == { a: 'a', b: 'b' }
+ end
+
+ it "raises ArgumentError if block returns longer or shorter array" do
+ -> do
+ @enum.to_h { |k| [k, k.to_s, 1] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+
+ -> do
+ @enum.to_h { |k| [k] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ @enum.to_h { |k| "not-array" }
+ end.should raise_error(TypeError, /wrong element type String/)
+ end
+
+ it "coerces returned pair to Array with #to_ary" do
+ x = mock('x')
+ x.stub!(:to_ary).and_return([:b, 'b'])
+
+ @enum.to_h { |k| x }.should == { :b => 'b' }
+ end
+
+ it "does not coerce returned pair to Array with #to_a" do
+ x = mock('x')
+ x.stub!(:to_a).and_return([:b, 'b'])
+
+ -> do
+ @enum.to_h { |k| x }
+ end.should raise_error(TypeError, /wrong element type MockObject/)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/uniq_spec.rb b/spec/ruby/core/enumerable/uniq_spec.rb
new file mode 100644
index 0000000000..a1ed44796f
--- /dev/null
+++ b/spec/ruby/core/enumerable/uniq_spec.rb
@@ -0,0 +1,78 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Enumerable#uniq' do
+ it 'returns an array that contains only unique elements' do
+ [0, 1, 2, 3].to_enum.uniq { |n| n.even? }.should == [0, 1]
+ end
+
+ it "uses eql? semantics" do
+ [1.0, 1].to_enum.uniq.should == [1.0, 1]
+ end
+
+ it "compares elements first with hash" do
+ x = mock('0')
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y = mock('0')
+ y.should_receive(:hash).at_least(1).and_return(0)
+
+ [x, y].to_enum.uniq.should == [x, y]
+ end
+
+ it "does not compare elements with different hash codes via eql?" do
+ x = mock('0')
+ x.should_not_receive(:eql?)
+ y = mock('1')
+ y.should_not_receive(:eql?)
+
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y.should_receive(:hash).at_least(1).and_return(1)
+
+ [x, y].to_enum.uniq.should == [x, y]
+ end
+
+ it "compares elements with matching hash codes with #eql?" do
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ false
+ end
+
+ obj
+ end
+
+ a.uniq.should == a
+
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ true
+ end
+
+ obj
+ end
+
+ a.to_enum.uniq.size.should == 1
+ end
+
+ context 'when yielded with multiple arguments' do
+ before :each do
+ @enum = Object.new.to_enum
+ class << @enum
+ def each
+ yield 0, 'foo'
+ yield 1, 'FOO'
+ yield 2, 'bar'
+ end
+ end
+ end
+
+ it 'returns all yield arguments as an array' do
+ @enum.uniq { |_, label| label.downcase }.should == [[0, 'foo'], [2, 'bar']]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/zip_spec.rb b/spec/ruby/core/enumerable/zip_spec.rb
new file mode 100644
index 0000000000..ab148f2a6e
--- /dev/null
+++ b/spec/ruby/core/enumerable/zip_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#zip" do
+
+ it "combines each element of the receiver with the element of the same index in arrays given as arguments" do
+ EnumerableSpecs::Numerous.new(1,2,3).zip([4,5,6],[7,8,9]).should == [[1,4,7],[2,5,8],[3,6,9]]
+ EnumerableSpecs::Numerous.new(1,2,3).zip.should == [[1],[2],[3]]
+ end
+
+ it "passes each element of the result array to a block and return nil if a block is given" do
+ expected = [[1,4,7],[2,5,8],[3,6,9]]
+ EnumerableSpecs::Numerous.new(1,2,3).zip([4,5,6],[7,8,9]) do |result_component|
+ result_component.should == expected.shift
+ end.should == nil
+ expected.size.should == 0
+ end
+
+ it "fills resulting array with nils if an argument array is too short" do
+ EnumerableSpecs::Numerous.new(1,2,3).zip([4,5,6], [7,8]).should == [[1,4,7],[2,5,8],[3,6,nil]]
+ end
+
+ it "converts arguments to arrays using #to_ary" do
+ convertible = EnumerableSpecs::ArrayConvertible.new(4,5,6)
+ EnumerableSpecs::Numerous.new(1,2,3).zip(convertible).should == [[1,4],[2,5],[3,6]]
+ convertible.called.should == :to_ary
+ end
+
+ it "converts arguments to enums using #to_enum" do
+ convertible = EnumerableSpecs::EnumConvertible.new(4..6)
+ EnumerableSpecs::Numerous.new(1,2,3).zip(convertible).should == [[1,4],[2,5],[3,6]]
+ convertible.called.should == :to_enum
+ convertible.sym.should == :each
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.zip(multi).should == [[[1, 2], [1, 2]], [[3, 4, 5], [3, 4, 5]], [[6, 7, 8, 9], [6, 7, 8, 9]]]
+ end
+
+ it "raises TypeError when some argument isn't Array and doesn't respond to #to_ary and #to_enum" do
+ -> { EnumerableSpecs::Numerous.new(1,2,3).zip(Object.new) }.should raise_error(TypeError, "wrong argument type Object (must respond to :each)")
+ -> { EnumerableSpecs::Numerous.new(1,2,3).zip(1) }.should raise_error(TypeError, "wrong argument type Integer (must respond to :each)")
+ -> { EnumerableSpecs::Numerous.new(1,2,3).zip(true) }.should raise_error(TypeError, "wrong argument type TrueClass (must respond to :each)")
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb
new file mode 100644
index 0000000000..bd243fa0b5
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#begin" do
+ it "returns the begin of the sequence" do
+ 1.step(10).begin.should == 1
+ (1..10).step.begin.should == 1
+ (1...10).step.begin.should == 1
+ end
+
+ context "with beginless" do
+ it "returns nil as begin of the sequence" do
+ (..10).step(1).begin.should == nil
+ (...10).step(1).begin.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb
new file mode 100644
index 0000000000..d4fff3e01f
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#each" do
+ before :each do
+ ScratchPad.record []
+ @seq = 1.step(10, 4)
+ end
+
+ it "calls given block on each item of the sequence" do
+ @seq.each { |item| ScratchPad << item }
+ ScratchPad.recorded.should == [1, 5, 9]
+ end
+
+ it "returns self" do
+ @seq.each { |item| }.should equal(@seq)
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb
new file mode 100644
index 0000000000..05429cac3e
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#end" do
+ it "returns the end of the sequence" do
+ 1.step(10).end.should == 10
+ (1..10).step.end.should == 10
+ (1...10).step(17).end.should == 10
+ end
+
+ context "with endless" do
+ it "returns nil as end of the sequence" do
+ (1..).step(1).end.should == nil
+ (1...).step(1).end.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb
new file mode 100644
index 0000000000..77eed02d8b
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#==" do
+ it "returns true if begin, end, step and exclude_end? are equal" do
+ 1.step(10).should == 1.step(10)
+ 1.step(10, 5).should == 1.step(10, 5)
+
+ (1..10).step.should == (1..10).step
+ (1...10).step(8).should == (1...10).step(8)
+
+ # both have exclude_end? == false
+ (1..10).step(100).should == 1.step(10, 100)
+
+ ((1..10).step == (1..11).step).should == false
+ ((1..10).step == (1...10).step).should == false
+ ((1..10).step == (1..10).step(2)).should == false
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb
new file mode 100644
index 0000000000..021fe7d90f
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#exclude_end?" do
+ context "when created using Numeric#step" do
+ it "always returns false" do
+ 1.step(10).should_not.exclude_end?
+ 10.step(1).should_not.exclude_end?
+ end
+ end
+
+ context "when created using Range#step" do
+ it "mirrors range.exclude_end?" do
+ (1...10).step.should.exclude_end?
+ (1..10).step.should_not.exclude_end?
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb
new file mode 100644
index 0000000000..ccd02be020
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#first" do
+ it "returns the first element of the sequence" do
+ 1.step(10).first.should == 1
+ (1..10).step.first.should == 1
+ (1...10).step.first.should == 1
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb
new file mode 100644
index 0000000000..bdb308074b
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#hash" do
+ it "is based on begin, end, step and exclude_end?" do
+ 1.step(10).hash.should be_an_instance_of(Integer)
+
+ 1.step(10).hash.should == 1.step(10).hash
+ 1.step(10, 5).hash.should == 1.step(10, 5).hash
+
+ (1..10).step.hash.should == (1..10).step.hash
+ (1...10).step(8).hash.should == (1...10).step(8).hash
+
+ # both have exclude_end? == false
+ (1..10).step(100).hash.should == 1.step(10, 100).hash
+
+ ((1..10).step.hash == (1..11).step.hash).should == false
+ ((1..10).step.hash == (1...10).step.hash).should == false
+ ((1..10).step.hash == (1..10).step(2).hash).should == false
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb
new file mode 100644
index 0000000000..b73b49d272
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#inspect" do
+ context 'when Numeric#step is used' do
+ it "returns '(begin.step(end{, step}))'" do
+ 1.step(10).inspect.should == "(1.step(10))"
+ 1.step(10, 3).inspect.should == "(1.step(10, 3))"
+ end
+ end
+
+ context 'when Range#step is used' do
+ it "returns '((range).step{(step)})'" do
+ (1..10).step.inspect.should == "((1..10).step)"
+ (1..10).step(3).inspect.should == "((1..10).step(3))"
+
+ (1...10).step.inspect.should == "((1...10).step)"
+ (1...10).step(3).inspect.should == "((1...10).step(3))"
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb
new file mode 100644
index 0000000000..31f982b7c4
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#last" do
+ it "returns the last element of the sequence" do
+ 1.step(10).last.should == 10
+ (1..10).step.last.should == 10
+ (1...10).step(4).last.should == 9
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb
new file mode 100644
index 0000000000..2015983826
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence.new" do
+ it "is not defined" do
+ -> {
+ Enumerator::ArithmeticSequence.new
+ }.should raise_error(NoMethodError)
+ end
+end
+
+describe "Enumerator::ArithmeticSequence.allocate" do
+ it "is not defined" do
+ -> {
+ Enumerator::ArithmeticSequence.allocate
+ }.should raise_error(TypeError, 'allocator undefined for Enumerator::ArithmeticSequence')
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb
new file mode 100644
index 0000000000..7e03edd961
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#size" do
+ context "for finite sequence" do
+ it "returns the number of elements in this arithmetic sequence" do
+ 1.step(10).size.should == 10
+ (1...10).step.size.should == 9
+ end
+ end
+
+ context "for infinite sequence" do
+ it "returns Infinity" do
+ 1.step(Float::INFINITY).size.should == Float::INFINITY
+ (1..Float::INFINITY).step.size.should == Float::INFINITY
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb
new file mode 100644
index 0000000000..c1f2d9173f
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#step" do
+ it "returns the original value given to step method" do
+ (1..10).step.step.should == 1
+ (1..10).step(3).step.should == 3
+
+ 1.step(10).step.should == 1
+ 1.step(10, 3).step.should == 3
+ end
+end
diff --git a/spec/ruby/core/enumerator/chain/each_spec.rb b/spec/ruby/core/enumerator/chain/each_spec.rb
new file mode 100644
index 0000000000..cc93cbac60
--- /dev/null
+++ b/spec/ruby/core/enumerator/chain/each_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+require_relative '../../enumerable/fixtures/classes'
+
+describe "Enumerator::Chain#each" do
+ it "calls each on its constituents as needed" do
+ a = EnumerableSpecs::EachCounter.new(:a, :b)
+ b = EnumerableSpecs::EachCounter.new(:c, :d)
+
+ ScratchPad.record []
+ Enumerator::Chain.new(a, b).each do |elem|
+ ScratchPad << elem << b.times_yielded
+ end
+ ScratchPad.recorded.should == [:a, 0, :b, 0, :c, 1, :d, 2]
+ end
+end
diff --git a/spec/ruby/core/enumerator/chain/initialize_spec.rb b/spec/ruby/core/enumerator/chain/initialize_spec.rb
new file mode 100644
index 0000000000..69484dfcb4
--- /dev/null
+++ b/spec/ruby/core/enumerator/chain/initialize_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Chain#initialize" do
+ before :each do
+ @uninitialized = Enumerator::Chain.allocate
+ end
+
+ it "is a private method" do
+ Enumerator::Chain.should have_private_instance_method(:initialize, false)
+ end
+
+ it "returns self" do
+ @uninitialized.send(:initialize).should equal(@uninitialized)
+ end
+
+ it "accepts many arguments" do
+ @uninitialized.send(:initialize, 0..1, 2..3, 4..5).should equal(@uninitialized)
+ end
+
+ it "accepts arguments that are not Enumerable nor responding to :each" do
+ @uninitialized.send(:initialize, Object.new).should equal(@uninitialized)
+ end
+
+ describe "on frozen instance" do
+ it "raises a RuntimeError" do
+ -> {
+ @uninitialized.freeze.send(:initialize)
+ }.should raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/chain/inspect_spec.rb b/spec/ruby/core/enumerator/chain/inspect_spec.rb
new file mode 100644
index 0000000000..a0450c808a
--- /dev/null
+++ b/spec/ruby/core/enumerator/chain/inspect_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Chain#inspect" do
+ it "shows a representation of the Enumerator" do
+ Enumerator::Chain.new.inspect.should == "#<Enumerator::Chain: []>"
+ Enumerator::Chain.new(1..2, 3..4).inspect.should == "#<Enumerator::Chain: [1..2, 3..4]>"
+ end
+
+ it "calls inspect on its chain elements" do
+ obj = mock('inspect')
+ obj.should_receive(:inspect).and_return('some desc')
+ Enumerator::Chain.new(obj).inspect.should == "#<Enumerator::Chain: [some desc]>"
+ end
+end
diff --git a/spec/ruby/core/enumerator/chain/rewind_spec.rb b/spec/ruby/core/enumerator/chain/rewind_spec.rb
new file mode 100644
index 0000000000..5f51ce2cf1
--- /dev/null
+++ b/spec/ruby/core/enumerator/chain/rewind_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Chain#rewind" do
+ before(:each) do
+ @obj = mock('obj')
+ @obj.should_receive(:each).any_number_of_times.and_yield
+ @second = mock('obj')
+ @second.should_receive(:each).any_number_of_times.and_yield
+ @enum = Enumerator::Chain.new(@obj, @second)
+ end
+
+ it "returns self" do
+ @enum.rewind.should equal @enum
+ end
+
+ it "does nothing if receiver has not been iterated" do
+ @obj.should_not_receive(:rewind)
+ @obj.respond_to?(:rewind).should == true # sanity check
+ @enum.rewind
+ end
+
+ it "does nothing on objects that don't respond_to rewind" do
+ @obj.respond_to?(:rewind).should == false # sanity check
+ @enum.each {}
+ @enum.rewind
+ end
+
+ it "calls_rewind its objects" do
+ @obj.should_receive(:rewind)
+ @enum.each {}
+ @enum.rewind
+ end
+
+ it "calls_rewind in reverse order" do
+ @obj.should_not_receive(:rewind)
+ @second.should_receive(:rewind).and_raise(RuntimeError)
+ @enum.each {}
+ -> { @enum.rewind }.should raise_error(RuntimeError)
+ end
+
+ it "calls rewind only for objects that have actually been iterated on" do
+ @obj = mock('obj')
+ @obj.should_receive(:each).any_number_of_times.and_raise(RuntimeError)
+ @enum = Enumerator::Chain.new(@obj, @second)
+
+ @obj.should_receive(:rewind)
+ @second.should_not_receive(:rewind)
+ -> { @enum.each {} }.should raise_error(RuntimeError)
+ @enum.rewind
+ end
+end
diff --git a/spec/ruby/core/enumerator/chain/size_spec.rb b/spec/ruby/core/enumerator/chain/size_spec.rb
new file mode 100644
index 0000000000..d85b88ee8b
--- /dev/null
+++ b/spec/ruby/core/enumerator/chain/size_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require_relative '../../enumerable/fixtures/classes'
+
+describe "Enumerator::Chain#size" do
+ it "returns the sum of the sizes of the elements" do
+ a = mock('size')
+ a.should_receive(:size).and_return(40)
+ Enumerator::Chain.new(a, [:a, :b]).size.should == 42
+ end
+
+ it "returns nil or Infinity for the first element of such a size" do
+ [nil, Float::INFINITY].each do |special|
+ a = mock('size')
+ a.should_receive(:size).and_return(40)
+ b = mock('special')
+ b.should_receive(:size).and_return(special)
+ c = mock('not called')
+ c.should_not_receive(:size)
+ Enumerator::Chain.new(a, b, c).size.should == special
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/each_spec.rb b/spec/ruby/core/enumerator/each_spec.rb
new file mode 100644
index 0000000000..99ac3120af
--- /dev/null
+++ b/spec/ruby/core/enumerator/each_spec.rb
@@ -0,0 +1,89 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#each" do
+ before :each do
+ object_each_with_arguments = Object.new
+ def object_each_with_arguments.each_with_arguments(arg, *args)
+ yield arg, *args
+ :method_returned
+ end
+
+ @enum_with_arguments = object_each_with_arguments.to_enum(:each_with_arguments, :arg0, :arg1, :arg2)
+
+ @enum_with_yielder = Enumerator.new {|y| y.yield :ok}
+ end
+
+ it "yields each element of self to the given block" do
+ acc = []
+ [1,2,3].to_enum.each {|e| acc << e }
+ acc.should == [1,2,3]
+ end
+
+ it "calls #each on the object given in the constructor by default" do
+ each = mock('each')
+ each.should_receive(:each)
+ each.to_enum.each {|e| e }
+ end
+
+ it "calls #each on the underlying object until it's exhausted" do
+ each = mock('each')
+ each.should_receive(:each).and_yield(1).and_yield(2).and_yield(3)
+ acc = []
+ each.to_enum.each {|e| acc << e }
+ acc.should == [1,2,3]
+ end
+
+ it "calls the method given in the constructor instead of #each" do
+ each = mock('peach')
+ each.should_receive(:peach)
+ each.to_enum(:peach).each {|e| e }
+ end
+
+ it "calls the method given in the constructor until it's exhausted" do
+ each = mock('peach')
+ each.should_receive(:peach).and_yield(1).and_yield(2).and_yield(3)
+ acc = []
+ each.to_enum(:peach).each {|e| acc << e }
+ acc.should == [1,2,3]
+ end
+
+ it "raises a NoMethodError if the object doesn't respond to #each" do
+ enum = Object.new.to_enum
+ -> do
+ enum.each { |e| e }
+ end.should raise_error(NoMethodError)
+ end
+
+ it "returns self if not given arguments and not given a block" do
+ @enum_with_arguments.each.should equal(@enum_with_arguments)
+
+ @enum_with_yielder.each.should equal(@enum_with_yielder)
+ end
+
+ it "returns the same value from receiver.each if block is given" do
+ @enum_with_arguments.each {}.should equal(:method_returned)
+ end
+
+ it "passes given arguments at initialized to receiver.each" do
+ @enum_with_arguments.each.to_a.should == [[:arg0, :arg1, :arg2]]
+ end
+
+ it "requires multiple arguments" do
+ Enumerator.instance_method(:each).arity.should < 0
+ end
+
+ it "appends given arguments to receiver.each" do
+ @enum_with_arguments.each(:each0, :each1).to_a.should == [[:arg0, :arg1, :arg2, :each0, :each1]]
+ @enum_with_arguments.each(:each2, :each3).to_a.should == [[:arg0, :arg1, :arg2, :each2, :each3]]
+ end
+
+ it "returns the same value from receiver.each if block and arguments are given" do
+ @enum_with_arguments.each(:each1, :each2) {}.should equal(:method_returned)
+ end
+
+ it "returns new Enumerator if given arguments but not given a block" do
+ ret = @enum_with_arguments.each 1
+ ret.should be_an_instance_of(Enumerator)
+ ret.should_not equal(@enum_with_arguments)
+ end
+end
diff --git a/spec/ruby/core/enumerator/each_with_index_spec.rb b/spec/ruby/core/enumerator/each_with_index_spec.rb
new file mode 100644
index 0000000000..96e53a2804
--- /dev/null
+++ b/spec/ruby/core/enumerator/each_with_index_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/enumerator/with_index'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Enumerator#each_with_index" do
+ it_behaves_like :enum_with_index, :each_with_index
+ it_behaves_like :enumeratorized_with_origin_size, :each_with_index, [1,2,3].select
+
+ it "returns a new Enumerator when no block is given" do
+ enum1 = [1,2,3].select
+ enum2 = enum1.each_with_index
+ enum2.should be_an_instance_of(Enumerator)
+ enum1.should_not == enum2
+ end
+
+ it "raises an ArgumentError if passed extra arguments" do
+ -> do
+ [1].to_enum.each_with_index(:glark)
+ end.should raise_error(ArgumentError)
+ end
+
+ it "passes on the given block's return value" do
+ arr = [1,2,3]
+ arr.delete_if.with_index { |a,b| false }
+ arr.should == [1,2,3]
+ end
+
+ it "returns the iterator's return value" do
+ [1,2,3].select.with_index { |a,b| false }.should == []
+ end
+
+ it "returns the correct value if chained with itself" do
+ [:a].each_with_index.each_with_index.to_a.should == [[[:a,0],0]]
+ [:a].each.with_index.with_index.to_a.should == [[[:a,0],0]]
+ end
+end
diff --git a/spec/ruby/core/enumerator/each_with_object_spec.rb b/spec/ruby/core/enumerator/each_with_object_spec.rb
new file mode 100644
index 0000000000..68524dc74a
--- /dev/null
+++ b/spec/ruby/core/enumerator/each_with_object_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/enumerator/with_object'
+
+describe "Enumerator#each_with_object" do
+ it_behaves_like :enum_with_object, :each_with_object
+end
diff --git a/spec/ruby/core/enumerator/enum_for_spec.rb b/spec/ruby/core/enumerator/enum_for_spec.rb
new file mode 100644
index 0000000000..fd33f463bf
--- /dev/null
+++ b/spec/ruby/core/enumerator/enum_for_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/enumerator/enum_for'
+
+describe "Enumerator#enum_for" do
+ it_behaves_like :enum_for, :enum_for
+end
diff --git a/spec/ruby/core/enumerator/enumerator_spec.rb b/spec/ruby/core/enumerator/enumerator_spec.rb
new file mode 100644
index 0000000000..7a263336cb
--- /dev/null
+++ b/spec/ruby/core/enumerator/enumerator_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator" do
+ it "includes Enumerable" do
+ Enumerator.include?(Enumerable).should == true
+ end
+end
diff --git a/spec/ruby/core/enumerator/feed_spec.rb b/spec/ruby/core/enumerator/feed_spec.rb
new file mode 100644
index 0000000000..e387c6cd39
--- /dev/null
+++ b/spec/ruby/core/enumerator/feed_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Enumerator#feed" do
+ before :each do
+ ScratchPad.record []
+ @enum = EnumeratorSpecs::Feed.new.to_enum(:each)
+ end
+
+ it "sets the future return value of yield if called before advancing the iterator" do
+ @enum.feed :a
+ @enum.next
+ @enum.next
+ @enum.next
+ ScratchPad.recorded.should == [:a, nil]
+ end
+
+ it "causes yield to return the value if called during iteration" do
+ @enum.next
+ @enum.feed :a
+ @enum.next
+ @enum.next
+ ScratchPad.recorded.should == [:a, nil]
+ end
+
+ it "can be called for each iteration" do
+ @enum.next
+ @enum.feed :a
+ @enum.next
+ @enum.feed :b
+ @enum.next
+ ScratchPad.recorded.should == [:a, :b]
+ end
+
+ it "returns nil" do
+ @enum.feed(:a).should be_nil
+ end
+
+ it "raises a TypeError if called more than once without advancing the enumerator" do
+ @enum.feed :a
+ @enum.next
+ -> { @enum.feed :b }.should raise_error(TypeError)
+ end
+
+ it "sets the return value of Yielder#yield" do
+ enum = Enumerator.new { |y| ScratchPad << y.yield }
+ enum.next
+ enum.feed :a
+ -> { enum.next }.should raise_error(StopIteration)
+ ScratchPad.recorded.should == [:a]
+ end
+end
diff --git a/spec/ruby/core/enumerator/first_spec.rb b/spec/ruby/core/enumerator/first_spec.rb
new file mode 100644
index 0000000000..458080bb31
--- /dev/null
+++ b/spec/ruby/core/enumerator/first_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#first" do
+ it "returns arrays correctly when calling #first (2376)" do
+ Enumerator.new {|y| y << [42] }.first.should == [42]
+ end
+end
diff --git a/spec/ruby/core/enumerator/fixtures/common.rb b/spec/ruby/core/enumerator/fixtures/common.rb
new file mode 100644
index 0000000000..e332e3195b
--- /dev/null
+++ b/spec/ruby/core/enumerator/fixtures/common.rb
@@ -0,0 +1,9 @@
+module EnumeratorSpecs
+ class Feed
+ def each
+ ScratchPad << yield
+ ScratchPad << yield
+ ScratchPad << yield
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/generator/each_spec.rb b/spec/ruby/core/enumerator/generator/each_spec.rb
new file mode 100644
index 0000000000..a43805dd16
--- /dev/null
+++ b/spec/ruby/core/enumerator/generator/each_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Generator#each" do
+ before :each do
+ @generator = Enumerator::Generator.new do |y, *args|
+ y << 3 << 2 << 1
+ y << args unless args.empty?
+ :block_returned
+ end
+ end
+
+ it "is an enumerable" do
+ @generator.should be_kind_of(Enumerable)
+ end
+
+ it "supports enumeration with a block" do
+ r = []
+ @generator.each { |v| r << v }
+
+ r.should == [3, 2, 1]
+ end
+
+ it "raises a LocalJumpError if no block given" do
+ -> { @generator.each }.should raise_error(LocalJumpError)
+ end
+
+ it "returns the block returned value" do
+ @generator.each {}.should equal(:block_returned)
+ end
+
+ it "requires multiple arguments" do
+ Enumerator::Generator.instance_method(:each).arity.should < 0
+ end
+
+ it "appends given arguments to receiver.each" do
+ yields = []
+ @generator.each(:each0, :each1) { |yielded| yields << yielded }
+ yields.should == [3, 2, 1, [:each0, :each1]]
+ end
+end
diff --git a/spec/ruby/core/enumerator/generator/initialize_spec.rb b/spec/ruby/core/enumerator/generator/initialize_spec.rb
new file mode 100644
index 0000000000..f75c7d6f26
--- /dev/null
+++ b/spec/ruby/core/enumerator/generator/initialize_spec.rb
@@ -0,0 +1,26 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Generator#initialize" do
+ before :each do
+ @class = Enumerator::Generator
+ @uninitialized = @class.allocate
+ end
+
+ it "is a private method" do
+ @class.should have_private_instance_method(:initialize, false)
+ end
+
+ it "returns self when given a block" do
+ @uninitialized.send(:initialize) {}.should equal(@uninitialized)
+ end
+
+ describe "on frozen instance" do
+ it "raises a RuntimeError" do
+ -> {
+ @uninitialized.freeze.send(:initialize) {}
+ }.should raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/initialize_spec.rb b/spec/ruby/core/enumerator/initialize_spec.rb
new file mode 100644
index 0000000000..217af1d3bc
--- /dev/null
+++ b/spec/ruby/core/enumerator/initialize_spec.rb
@@ -0,0 +1,65 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../spec_helper'
+
+describe "Enumerator#initialize" do
+ before :each do
+ @uninitialized = Enumerator.allocate
+ end
+
+ it "is a private method" do
+ Enumerator.should have_private_instance_method(:initialize, false)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns self when given an object" do
+ suppress_warning do
+ @uninitialized.send(:initialize, Object.new).should equal(@uninitialized)
+ end
+ end
+ end
+
+ it "returns self when given a block" do
+ @uninitialized.send(:initialize) {}.should equal(@uninitialized)
+ end
+
+ # Maybe spec should be broken up?
+ it "accepts a block" do
+ @uninitialized.send(:initialize) do |yielder|
+ r = yielder.yield 3
+ yielder << r << 2 << 1
+ end
+ @uninitialized.should be_an_instance_of(Enumerator)
+ r = []
+ @uninitialized.each{|x| r << x; x * 2}
+ r.should == [3, 6, 2, 1]
+ end
+
+ it "sets size to nil if size is not given" do
+ @uninitialized.send(:initialize) {}.size.should be_nil
+ end
+
+ it "sets size to nil if the given size is nil" do
+ @uninitialized.send(:initialize, nil) {}.size.should be_nil
+ end
+
+ it "sets size to the given size if the given size is Float::INFINITY" do
+ @uninitialized.send(:initialize, Float::INFINITY) {}.size.should equal(Float::INFINITY)
+ end
+
+ it "sets size to the given size if the given size is an Integer" do
+ @uninitialized.send(:initialize, 100) {}.size.should == 100
+ end
+
+ it "sets size to the given size if the given size is a Proc" do
+ @uninitialized.send(:initialize, -> { 200 }) {}.size.should == 200
+ end
+
+ describe "on frozen instance" do
+ it "raises a RuntimeError" do
+ -> {
+ @uninitialized.freeze.send(:initialize) {}
+ }.should raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/inspect_spec.rb b/spec/ruby/core/enumerator/inspect_spec.rb
new file mode 100644
index 0000000000..3bcf07e754
--- /dev/null
+++ b/spec/ruby/core/enumerator/inspect_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#inspect" do
+ describe "shows a representation of the Enumerator" do
+ it "including receiver and method" do
+ (1..3).each.inspect.should == "#<Enumerator: 1..3:each>"
+ end
+
+ it "including receiver and method and arguments" do
+ (1..3).each_slice(2).inspect.should == "#<Enumerator: 1..3:each_slice(2)>"
+ end
+
+ it "including the nested Enumerator" do
+ (1..3).each.each_slice(2).inspect.should == "#<Enumerator: #<Enumerator: 1..3:each>:each_slice(2)>"
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/chunk_spec.rb b/spec/ruby/core/enumerator/lazy/chunk_spec.rb
new file mode 100644
index 0000000000..87d2b0c206
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/chunk_spec.rb
@@ -0,0 +1,67 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#chunk" do
+
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.chunk {}
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.chunk { |v| v }.size.should == nil
+ end
+
+ it "returns an Enumerator if called without a block" do
+ chunk = @yieldsmixed.chunk
+ chunk.should be_an_instance_of(Enumerator::Lazy)
+
+ res = chunk.each { |v| true }.force
+ res.should == [[true, EnumeratorLazySpecs::YieldsMixed.gathered_yields]]
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ first_two = (0..Float::INFINITY).lazy.chunk { |n| n.even? }.first(2)
+ first_two.should == [[true, [0]], [false, [1]]]
+ end
+ end
+
+ it "calls the block with gathered values when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.chunk { |v| yields << v; true }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).chunk { |v| v }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ remains_lazy = (0..Float::INFINITY).lazy.chunk { |n| n }
+ remains_lazy.chunk { |n| n }.first(2).size.should == 2
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.chunk { |n| n.even? }.first(100).should ==
+ s.first(100).chunk { |n| n.even? }.to_a
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb b/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb
new file mode 100644
index 0000000000..772bd42de9
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#chunk_while" do
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.chunk_while { |a, b| false }.first(100).should ==
+ s.first(100).chunk_while { |a, b| false }.to_a
+ end
+
+ it "should return a lazy enumerator" do
+ s = 0..Float::INFINITY
+ s.lazy.chunk_while { |a, b| false }.should be_kind_of(Enumerator::Lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/collect_concat_spec.rb b/spec/ruby/core/enumerator/lazy/collect_concat_spec.rb
new file mode 100644
index 0000000000..8765bb2190
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/collect_concat_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/collect_concat'
+
+describe "Enumerator::Lazy#collect_concat" do
+ it_behaves_like :enumerator_lazy_collect_concat, :collect_concat
+end
diff --git a/spec/ruby/core/enumerator/lazy/collect_spec.rb b/spec/ruby/core/enumerator/lazy/collect_spec.rb
new file mode 100644
index 0000000000..14b79ce16d
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/collect_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/collect'
+
+describe "Enumerator::Lazy#collect" do
+ it_behaves_like :enumerator_lazy_collect, :collect
+end
diff --git a/spec/ruby/core/enumerator/lazy/compact_spec.rb b/spec/ruby/core/enumerator/lazy/compact_spec.rb
new file mode 100644
index 0000000000..80b6f9481d
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/compact_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is '3.1' do
+ describe "Enumerator::Lazy#compact" do
+ it 'returns array without nil elements' do
+ arr = [1, nil, 3, false, 5].to_enum.lazy.compact
+ arr.should be_an_instance_of(Enumerator::Lazy)
+ arr.force.should == [1, 3, false, 5]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/drop_spec.rb b/spec/ruby/core/enumerator/lazy/drop_spec.rb
new file mode 100644
index 0000000000..822b8034fb
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/drop_spec.rb
@@ -0,0 +1,58 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#drop" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.drop(1)
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "sets difference of given count with old size to new size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.drop(20).size.should == 80
+ Enumerator::Lazy.new(Object.new, 100) {}.drop(200).size.should == 0
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.drop(2).first(2).should == [2, 3]
+
+ @eventsmixed.drop(0).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ describe "on a nested Lazy" do
+ it "sets difference of given count with old size to new size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.drop(20).drop(50).size.should == 30
+ Enumerator::Lazy.new(Object.new, 100) {}.drop(50).drop(20).size.should == 30
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.drop(2).drop(2).first(2).should == [4, 5]
+
+ @eventsmixed.drop(0).drop(0).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.drop(100).first(100).should ==
+ s.first(200).drop(100)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/drop_while_spec.rb b/spec/ruby/core/enumerator/lazy/drop_while_spec.rb
new file mode 100644
index 0000000000..4f6e366f88
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/drop_while_spec.rb
@@ -0,0 +1,66 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#drop_while" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.drop_while {}
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.drop_while { |v| v }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.drop_while { |n| n < 5 }.first(2).should == [5, 6]
+
+ @eventsmixed.drop_while { false }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with initial values when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.drop_while { |v| yields << v; true }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields
+ end
+
+ it "raises an ArgumentError when not given a block" do
+ -> { @yieldsmixed.drop_while }.should raise_error(ArgumentError)
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).drop_while { |v| v }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.drop_while { |n| n < 5 }.drop_while { |n| n.odd? }.first(2).should == [6, 7]
+
+ @eventsmixed.drop_while { false }.drop_while { false }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.drop_while { |n| n < 100 }.first(100).should ==
+ s.first(200).drop_while { |n| n < 100 }
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/eager_spec.rb b/spec/ruby/core/enumerator/lazy/eager_spec.rb
new file mode 100644
index 0000000000..592da4fd8c
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/eager_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#eager" do
+ it "returns a non-lazy Enumerator converted from the lazy enumerator" do
+ enum = [1, 2, 3].lazy
+
+ enum.class.should == Enumerator::Lazy
+ enum.eager.class.should == Enumerator
+ end
+
+ it "does not enumerate an enumerator" do
+ ScratchPad.record []
+
+ sequence = [1, 2, 3]
+ enum_lazy = Enumerator::Lazy.new(sequence) do |yielder, value|
+ yielder << value
+ ScratchPad << value
+ end
+
+ ScratchPad.recorded.should == []
+ enum = enum_lazy.eager
+ ScratchPad.recorded.should == []
+
+ enum.map { |i| i }.should == [1, 2, 3]
+ ScratchPad.recorded.should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/enum_for_spec.rb b/spec/ruby/core/enumerator/lazy/enum_for_spec.rb
new file mode 100644
index 0000000000..7e7783f6f1
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/enum_for_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/to_enum'
+
+describe "Enumerator::Lazy#enum_for" do
+ it_behaves_like :enumerator_lazy_to_enum, :enum_for
+end
diff --git a/spec/ruby/core/enumerator/lazy/filter_map_spec.rb b/spec/ruby/core/enumerator/lazy/filter_map_spec.rb
new file mode 100644
index 0000000000..99dd59cebe
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/filter_map_spec.rb
@@ -0,0 +1,14 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#filter_map" do
+ it "maps only truthy results" do
+ (1..Float::INFINITY).lazy.filter_map { |i| i if i.odd? }.first(4).should == [1, 3, 5, 7]
+ end
+
+ it "does not map false results" do
+ (1..Float::INFINITY).lazy.filter_map { |i| i.odd? ? i : false }.first(4).should == [1, 3, 5, 7]
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/filter_spec.rb b/spec/ruby/core/enumerator/lazy/filter_spec.rb
new file mode 100644
index 0000000000..43128241e0
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/filter_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/select'
+
+describe "Enumerator::Lazy#filter" do
+ it_behaves_like :enumerator_lazy_select, :filter
+end
diff --git a/spec/ruby/core/enumerator/lazy/find_all_spec.rb b/spec/ruby/core/enumerator/lazy/find_all_spec.rb
new file mode 100644
index 0000000000..8b05c53803
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/find_all_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/select'
+
+describe "Enumerator::Lazy#find_all" do
+ it_behaves_like :enumerator_lazy_select, :find_all
+end
diff --git a/spec/ruby/core/enumerator/lazy/fixtures/classes.rb b/spec/ruby/core/enumerator/lazy/fixtures/classes.rb
new file mode 100644
index 0000000000..e35592ba1c
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/fixtures/classes.rb
@@ -0,0 +1,54 @@
+# -*- encoding: us-ascii -*-
+
+module EnumeratorLazySpecs
+ class SpecificError < Exception; end
+
+ class YieldsMixed
+ def self.initial_yields
+ [nil, 0, 0, 0, 0, nil, :default_arg, [], [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def self.gathered_yields
+ [nil, 0, [0, 1], [0, 1, 2], [0, 1, 2], nil, :default_arg, [], [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def self.gathered_non_array_yields
+ [nil, 0, nil, :default_arg]
+ end
+
+ def self.gathered_yields_with_args(arg, *args)
+ [nil, 0, [0, 1], [0, 1, 2], [0, 1, 2], nil, arg, args, [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def each(arg=:default_arg, *args)
+ yield
+ yield 0
+ yield 0, 1
+ yield 0, 1, 2
+ yield(*[0, 1, 2])
+ yield nil
+ yield arg
+ yield args
+ yield []
+ yield [0]
+ yield [0, 1]
+ yield [0, 1, 2]
+ end
+ end
+
+ class EventsMixed
+ def each
+ ScratchPad << :before_yield
+
+ yield 0
+
+ ScratchPad << :after_yield
+
+ raise SpecificError
+
+ ScratchPad << :after_error
+
+ :should_not_reach_here
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/flat_map_spec.rb b/spec/ruby/core/enumerator/lazy/flat_map_spec.rb
new file mode 100644
index 0000000000..5dcaa8bfa1
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/flat_map_spec.rb
@@ -0,0 +1,16 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/collect_concat'
+
+describe "Enumerator::Lazy#flat_map" do
+ it_behaves_like :enumerator_lazy_collect_concat, :flat_map
+
+ it "properly unwraps nested yields" do
+ s = Enumerator.new do |y| loop do y << [1, 2] end end
+
+ expected = s.take(3).flat_map { |x| x }.to_a
+ actual = s.lazy.take(3).flat_map{ |x| x }.force
+ actual.should == expected
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/force_spec.rb b/spec/ruby/core/enumerator/lazy/force_spec.rb
new file mode 100644
index 0000000000..a7fa029135
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/force_spec.rb
@@ -0,0 +1,36 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#force" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "passes given arguments to receiver.each" do
+ @yieldsmixed.force(:arg1, :arg2, :arg3).should ==
+ EnumeratorLazySpecs::YieldsMixed.gathered_yields_with_args(:arg1, :arg2, :arg3)
+ end
+
+ describe "on a nested Lazy" do
+ it "calls all block and returns an Array" do
+ (0..Float::INFINITY).lazy.map(&:succ).take(2).force.should == [1, 2]
+
+ @eventsmixed.take(1).map(&:succ).force.should == [1]
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.take(100).force.should ==
+ s.take(100)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/grep_spec.rb b/spec/ruby/core/enumerator/lazy/grep_spec.rb
new file mode 100644
index 0000000000..e67686c9a3
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/grep_spec.rb
@@ -0,0 +1,121 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#grep" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "requires an argument" do
+ Enumerator::Lazy.instance_method(:grep).arity.should == 1
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.grep(Object) {}
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+
+ ret = @yieldsmixed.grep(Object)
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.grep(Object) {}.size.should == nil
+ Enumerator::Lazy.new(Object.new, 100) {}.grep(Object).size.should == nil
+ end
+
+ it "sets $~ in the block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep(/b/) { |e|
+ e.should == "abc"
+ $&.should == "b"
+ }.force
+
+ # Set by the failed match of "def"
+ $~.should == nil
+ end
+
+ it "sets $~ in the next block with each" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep(/b/).each { |e|
+ e.should == "abc"
+ $&.should == "b"
+ }
+
+ # Set by the failed match of "def"
+ $~.should == nil
+ end
+
+ it "sets $~ in the next block with map" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep(/b/).map { |e|
+ e.should == "abc"
+ $&.should == "b"
+ }.force
+
+ # Set by the failed match of "def"
+ $~.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times when not given a block" do
+ (0..Float::INFINITY).lazy.grep(Integer).first(3).should == [0, 1, 2]
+
+ @eventsmixed.grep(BasicObject).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops after specified times when given a block" do
+ (0..Float::INFINITY).lazy.grep(Integer, &:succ).first(3).should == [1, 2, 3]
+
+ @eventsmixed.grep(BasicObject) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with a gathered array when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.grep(BasicObject) { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields
+
+ @yieldsmixed.grep(BasicObject).force.should == yields
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.grep(Object) {}.size.should == nil
+ Enumerator::Lazy.new(Object.new, 100) {}.grep(Object).size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times when not given a block" do
+ (0..Float::INFINITY).lazy.grep(Integer).grep(Object).first(3).should == [0, 1, 2]
+
+ @eventsmixed.grep(BasicObject).grep(Object).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops after specified times when given a block" do
+ (0..Float::INFINITY).lazy.grep(Integer) { |n| n > 3 ? n : false }.grep(Integer) { |n| n.even? ? n : false }.first(3).should == [4, false, 6]
+
+ @eventsmixed.grep(BasicObject) {}.grep(Object) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.grep(Numeric).first(100).should ==
+ s.first(100).grep(Numeric)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/grep_v_spec.rb b/spec/ruby/core/enumerator/lazy/grep_v_spec.rb
new file mode 100644
index 0000000000..67173021bb
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/grep_v_spec.rb
@@ -0,0 +1,123 @@
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#grep_v" do
+ before(:each) do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after(:each) do
+ ScratchPad.clear
+ end
+
+ it "requires an argument" do
+ Enumerator::Lazy.instance_method(:grep_v).arity.should == 1
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.grep_v(Object) {}
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+
+ ret = @yieldsmixed.grep_v(Object)
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object) {}.size.should == nil
+ Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object).size.should == nil
+ end
+
+ it "sets $~ in the block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep_v(/e/) { |e|
+ e.should == "abc"
+ $~.should == nil
+ }.force
+
+ # Set by the match of "def"
+ $&.should == "e"
+ end
+
+ it "sets $~ in the next block with each" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep_v(/e/).each { |e|
+ e.should == "abc"
+ $~.should == nil
+ }
+
+ # Set by the match of "def"
+ $&.should == "e"
+ end
+
+ it "sets $~ in the next block with map" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep_v(/e/).map { |e|
+ e.should == "abc"
+ $~.should == nil
+ }.force
+
+ # Set by the match of "def"
+ $&.should == "e"
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times when not given a block" do
+ (0..Float::INFINITY).lazy.grep_v(3..5).first(3).should == [0, 1, 2]
+
+ @eventsmixed.grep_v(Symbol).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops after specified times when given a block" do
+ (0..Float::INFINITY).lazy.grep_v(4..8, &:succ).first(3).should == [1, 2, 3]
+
+ @eventsmixed.grep_v(Symbol) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with a gathered array when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.grep_v(Array) { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_non_array_yields
+
+ @yieldsmixed.grep_v(Array).force.should == yields
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object).grep_v(Object) {}.size.should == nil
+ Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object).grep_v(Object).size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times when not given a block" do
+ (0..Float::INFINITY).lazy.grep_v(3..5).grep_v(6..10).first(3).should == [0, 1, 2]
+
+ @eventsmixed.grep_v(Symbol).grep_v(String).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops after specified times when given a block" do
+ (0..Float::INFINITY).lazy
+ .grep_v(1..2) { |n| n > 3 ? n : false }
+ .grep_v(false) { |n| n.even? ? n : false }
+ .first(3)
+ .should == [4, false, 6]
+
+ @eventsmixed.grep_v(Symbol) {}.grep_v(String) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.grep_v(String).first(100).should ==
+ s.first(100).grep_v(String)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/initialize_spec.rb b/spec/ruby/core/enumerator/lazy/initialize_spec.rb
new file mode 100644
index 0000000000..f23018d010
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/initialize_spec.rb
@@ -0,0 +1,63 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#initialize" do
+ before :each do
+ @receiver = receiver = Object.new
+
+ def receiver.each
+ yield 0
+ yield 1
+ yield 2
+ end
+
+ @uninitialized = Enumerator::Lazy.allocate
+ end
+
+ it "is a private method" do
+ Enumerator::Lazy.should have_private_instance_method(:initialize, false)
+ end
+
+ it "returns self" do
+ @uninitialized.send(:initialize, @receiver) {}.should equal(@uninitialized)
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ @uninitialized.send(:initialize, @receiver) do |yielder, *values|
+ yielder.<<(*values)
+ end.first(2).should == [0, 1]
+ end
+ end
+
+ it "sets #size to nil if not given a size" do
+ @uninitialized.send(:initialize, @receiver) {}.size.should be_nil
+ end
+
+ it "sets #size to nil if given size is nil" do
+ @uninitialized.send(:initialize, @receiver, nil) {}.size.should be_nil
+ end
+
+ it "sets given size to own size if the given size is Float::INFINITY" do
+ @uninitialized.send(:initialize, @receiver, Float::INFINITY) {}.size.should equal(Float::INFINITY)
+ end
+
+ it "sets given size to own size if the given size is an Integer" do
+ @uninitialized.send(:initialize, @receiver, 100) {}.size.should == 100
+ end
+
+ it "sets given size to own size if the given size is a Proc" do
+ @uninitialized.send(:initialize, @receiver, -> { 200 }) {}.size.should == 200
+ end
+
+ it "raises an ArgumentError when block is not given" do
+ -> { @uninitialized.send :initialize, @receiver }.should raise_error(ArgumentError)
+ end
+
+ describe "on frozen instance" do
+ it "raises a RuntimeError" do
+ -> { @uninitialized.freeze.send(:initialize, @receiver) {} }.should raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/lazy_spec.rb b/spec/ruby/core/enumerator/lazy/lazy_spec.rb
new file mode 100644
index 0000000000..0fb104e25a
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/lazy_spec.rb
@@ -0,0 +1,32 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy" do
+ it "is a subclass of Enumerator" do
+ Enumerator::Lazy.superclass.should equal(Enumerator)
+ end
+
+ it "defines lazy versions of a whitelist of Enumerator methods" do
+ lazy_methods = [
+ :chunk, :collect, :collect_concat, :drop, :drop_while, :enum_for,
+ :find_all, :flat_map, :force, :grep, :grep_v, :lazy, :map, :reject,
+ :select, :slice_after, :slice_before, :slice_when, :take, :take_while,
+ :to_enum, :zip
+ ]
+ lazy_methods += [:chunk_while, :uniq]
+
+ ruby_version_is '3.1' do
+ lazy_methods += [:compact]
+ end
+
+ Enumerator::Lazy.instance_methods(false).should include(*lazy_methods)
+ end
+end
+
+describe "Enumerator::Lazy#lazy" do
+ it "returns self" do
+ lazy = (1..3).to_enum.lazy
+ lazy.lazy.should equal(lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/map_spec.rb b/spec/ruby/core/enumerator/lazy/map_spec.rb
new file mode 100644
index 0000000000..5cb998f5f7
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/map_spec.rb
@@ -0,0 +1,12 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/collect'
+
+describe "Enumerator::Lazy#map" do
+ it_behaves_like :enumerator_lazy_collect, :map
+
+ it "doesn't unwrap Arrays" do
+ Enumerator.new {|y| y.yield([1])}.lazy.to_a.should == [[1]]
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/reject_spec.rb b/spec/ruby/core/enumerator/lazy/reject_spec.rb
new file mode 100644
index 0000000000..0e1632d667
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/reject_spec.rb
@@ -0,0 +1,78 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#reject" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.reject {}
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.reject {}.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.reject(&:even?).first(3).should == [1, 3, 5]
+
+ @eventsmixed.reject { false }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "lets exceptions raised in the block go through" do
+ lazy = 10.times.lazy.map do |i|
+ raise "foo"
+ end
+
+ lazy = lazy.reject(&:nil?)
+
+ -> {
+ lazy.first
+ }.should raise_error(RuntimeError, "foo")
+ end
+
+ it "calls the block with a gathered array when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.reject { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields
+ end
+
+ it "raises an ArgumentError when not given a block" do
+ -> { @yieldsmixed.reject }.should raise_error(ArgumentError)
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).reject {}.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.reject { |n| n < 4 }.reject(&:even?).first(3).should == [5, 7, 9]
+
+ @eventsmixed.reject { false }.reject { false }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.reject { |n| false }.first(100).should ==
+ s.first(100).reject { |n| false }
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/select_spec.rb b/spec/ruby/core/enumerator/lazy/select_spec.rb
new file mode 100644
index 0000000000..3773d8f0a8
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/select_spec.rb
@@ -0,0 +1,47 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/select'
+
+describe "Enumerator::Lazy#select" do
+ it_behaves_like :enumerator_lazy_select, :select
+
+ it "doesn't pre-evaluate the next element" do
+ eval_count = 0
+ enum = %w[Text1 Text2 Text3].lazy.select do
+ eval_count += 1
+ true
+ end
+
+ eval_count.should == 0
+ enum.next
+ eval_count.should == 1
+ end
+
+ it "doesn't over-evaluate when peeked" do
+ eval_count = 0
+ enum = %w[Text1 Text2 Text3].lazy.select do
+ eval_count += 1
+ true
+ end
+
+ eval_count.should == 0
+ enum.peek
+ enum.peek
+ eval_count.should == 1
+ end
+
+ it "doesn't re-evaluate after peek" do
+ eval_count = 0
+ enum = %w[Text1 Text2 Text3].lazy.select do
+ eval_count += 1
+ true
+ end
+
+ eval_count.should == 0
+ enum.peek
+ eval_count.should == 1
+ enum.next
+ eval_count.should == 1
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/shared/collect.rb b/spec/ruby/core/enumerator/lazy/shared/collect.rb
new file mode 100644
index 0000000000..5690255a0c
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/shared/collect.rb
@@ -0,0 +1,62 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :enumerator_lazy_collect, shared: true do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.send(@method) {}
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "keeps size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method) {}.size.should == 100
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.send(@method, &:succ).first(3).should == [1, 2, 3]
+
+ @eventsmixed.send(@method) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with initial values when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.send(@method) { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields
+ end
+
+ describe "on a nested Lazy" do
+ it "keeps size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method) {}.send(@method) {}.size.should == 100
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.send(@method, &:succ).send(@method, &:succ).first(3).should == [2, 3, 4]
+
+ @eventsmixed.send(@method) {}.send(@method) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.send(@method) { |n| n }.first(100).should ==
+ s.first(100).send(@method) { |n| n }.to_a
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/shared/collect_concat.rb b/spec/ruby/core/enumerator/lazy/shared/collect_concat.rb
new file mode 100644
index 0000000000..00d7941a61
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/shared/collect_concat.rb
@@ -0,0 +1,78 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :enumerator_lazy_collect_concat, shared: true do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.send(@method) {}
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { true }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s }.first(6).should == %w[0 10 20 30 40 50]
+
+ @eventsmixed.send(@method) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "flattens elements when the given block returned an array or responding to .each and .force" do
+ (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.chars }.first(6).should == %w[0 1 0 2 0 3]
+ (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.each_char }.first(6).all? { |o| o.instance_of? Enumerator }.should be_true
+ (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.each_char.lazy }.first(6).should == %w[0 1 0 2 0 3]
+ end
+ end
+
+ it "calls the block with initial values when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.send(@method) { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields
+ end
+
+ it "raises an ArgumentError when not given a block" do
+ -> { @yieldsmixed.send(@method) }.should raise_error(ArgumentError)
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(50) {}.send(@method) {}.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s }.first(6).should == %w[0 10 20 30 40 50]
+
+ @eventsmixed.send(@method) {}.send(@method) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "flattens elements when the given block returned an array or responding to .each and .force" do
+ (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.chars }.first(6).should == %w[0 1 0 2 0 3]
+ (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.each_char }.first(6).all? { |o| o.instance_of? Enumerator }.should be_true
+ (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.each_char.lazy }.first(6).should == %w[0 1 0 2 0 3]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.send(@method) { |n| [-n, +n] }.first(200).should ==
+ s.first(100).send(@method) { |n| [-n, +n] }.to_a
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/shared/select.rb b/spec/ruby/core/enumerator/lazy/shared/select.rb
new file mode 100644
index 0000000000..50a00bcbf4
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/shared/select.rb
@@ -0,0 +1,66 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :enumerator_lazy_select, shared: true do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.send(@method) {}
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { true }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.send(@method, &:even?).first(3).should == [0, 2, 4]
+
+ @eventsmixed.send(@method) { true }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with a gathered array when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.send(@method) { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields
+ end
+
+ it "raises an ArgumentError when not given a block" do
+ -> { @yieldsmixed.send(@method) }.should raise_error(ArgumentError)
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(50) {}.send(@method) { true }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.send(@method) { |n| n > 5 }.send(@method, &:even?).first(3).should == [6, 8, 10]
+
+ @eventsmixed.send(@method) { true }.send(@method) { true }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.send(@method) { |n| true }.first(100).should ==
+ s.first(100).send(@method) { |n| true }
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/shared/to_enum.rb b/spec/ruby/core/enumerator/lazy/shared/to_enum.rb
new file mode 100644
index 0000000000..0c91ea55b9
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/shared/to_enum.rb
@@ -0,0 +1,55 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../../spec_helper'
+
+describe :enumerator_lazy_to_enum, shared: true do
+ before :each do
+ @infinite = (0..Float::INFINITY).lazy
+ end
+
+ it "requires multiple arguments" do
+ Enumerator::Lazy.instance_method(@method).arity.should < 0
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @infinite.send @method
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@infinite)
+ end
+
+ it "sets #size to nil when not given a block" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method).size.should == nil
+ end
+
+ it "sets given block to size when given a block" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { 30 }.size.should == 30
+ end
+
+ it "generates a lazy enumerator from the given name" do
+ @infinite.send(@method, :with_index, 10).first(3).should == [[0, 10], [1, 11], [2, 12]]
+ end
+
+ it "passes given arguments to wrapped method" do
+ @infinite.send(@method, :each_slice, 2).map { |assoc| assoc.first * assoc.last }.first(4).should == [0, 6, 20, 42]
+ end
+
+ it "used by some parent's methods though returning Lazy" do
+ { each_with_index: [],
+ with_index: [],
+ cycle: [1],
+ each_with_object: [Object.new],
+ with_object: [Object.new],
+ each_slice: [2],
+ each_entry: [],
+ each_cons: [2]
+ }.each_pair do |method, args|
+ @infinite.send(method, *args).should be_an_instance_of(Enumerator::Lazy)
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.send(@method, :with_index).first(100).should ==
+ s.first(100).to_enum.send(@method, :with_index).to_a
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/slice_after_spec.rb b/spec/ruby/core/enumerator/lazy/slice_after_spec.rb
new file mode 100644
index 0000000000..8b08a1ecfd
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/slice_after_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#slice_after" do
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_after { |n| true }.first(100).should ==
+ s.first(100).slice_after { |n| true }.to_a
+ end
+
+ it "should return a lazy enumerator" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_after { |n| true }.should be_kind_of(Enumerator::Lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/slice_before_spec.rb b/spec/ruby/core/enumerator/lazy/slice_before_spec.rb
new file mode 100644
index 0000000000..9c1ec9ba4a
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/slice_before_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#slice_before" do
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_before { |n| true }.first(100).should ==
+ s.first(100).slice_before { |n| true }.to_a
+ end
+
+ it "should return a lazy enumerator" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_before { |n| true }.should be_kind_of(Enumerator::Lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/slice_when_spec.rb b/spec/ruby/core/enumerator/lazy/slice_when_spec.rb
new file mode 100644
index 0000000000..f83403425d
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/slice_when_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#slice_when" do
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_when { |a, b| true }.first(100).should ==
+ s.first(100).slice_when { |a, b| true }.to_a
+ end
+
+ it "should return a lazy enumerator" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_when { |a, b| true }.should be_kind_of(Enumerator::Lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/take_spec.rb b/spec/ruby/core/enumerator/lazy/take_spec.rb
new file mode 100644
index 0000000000..9fc17e969f
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/take_spec.rb
@@ -0,0 +1,66 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#take" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.take(1)
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "sets given count to size if the given count is less than old size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).size.should == 20
+ Enumerator::Lazy.new(Object.new, 100) {}.take(200).size.should == 100
+ end
+
+ it "sets given count to size if the old size is Infinity" do
+ loop.lazy.take(20).size.should == 20
+ end
+
+ describe "when the returned lazy enumerator is evaluated by .force" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.take(2).force.should == [0, 1]
+
+ @eventsmixed.take(1).force
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops without iterations if the given argument is 0" do
+ @eventsmixed.take(0).force
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "on a nested Lazy" do
+ it "sets given count to size if the given count is less than old size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).take(50).size.should == 20
+ Enumerator::Lazy.new(Object.new, 100) {}.take(50).take(20).size.should == 20
+ end
+
+ describe "when the returned lazy enumerator is evaluated by .force" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.map(&:succ).take(2).force.should == [1, 2]
+
+ @eventsmixed.take(10).take(1).force
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops without iterations if the given argument is 0" do
+ @eventsmixed.take(10).take(0).force
+ ScratchPad.recorded.should == []
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/take_while_spec.rb b/spec/ruby/core/enumerator/lazy/take_while_spec.rb
new file mode 100644
index 0000000000..bcea0b1419
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/take_while_spec.rb
@@ -0,0 +1,60 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#take_while" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.take_while {}
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take_while { true }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by .force" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.take_while { |n| n < 3 }.force.should == [0, 1, 2]
+
+ @eventsmixed.take_while { false }.force
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with initial values when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.take_while { |v| yields << v; true }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields
+ end
+
+ it "raises an ArgumentError when not given a block" do
+ -> { @yieldsmixed.take_while }.should raise_error(ArgumentError)
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).take_while { true }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by .force" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.take_while { |n| n < 3 }.take_while(&:even?).force.should == [0]
+
+ @eventsmixed.take_while { true }.take_while { false }.force
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/to_enum_spec.rb b/spec/ruby/core/enumerator/lazy/to_enum_spec.rb
new file mode 100644
index 0000000000..210e5294b7
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/to_enum_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/to_enum'
+
+describe "Enumerator::Lazy#to_enum" do
+ it_behaves_like :enumerator_lazy_to_enum, :to_enum
+end
diff --git a/spec/ruby/core/enumerator/lazy/uniq_spec.rb b/spec/ruby/core/enumerator/lazy/uniq_spec.rb
new file mode 100644
index 0000000000..ce67ace5ab
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/uniq_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Enumerator::Lazy#uniq' do
+ context 'without block' do
+ before :each do
+ @lazy = [0, 1, 0, 1].to_enum.lazy.uniq
+ end
+
+ it 'returns a lazy enumerator' do
+ @lazy.should be_an_instance_of(Enumerator::Lazy)
+ @lazy.force.should == [0, 1]
+ end
+
+ it 'return same value after rewind' do
+ @lazy.force.should == [0, 1]
+ @lazy.force.should == [0, 1]
+ end
+
+ it 'sets the size to nil' do
+ @lazy.size.should == nil
+ end
+ end
+
+ context 'when yielded with an argument' do
+ before :each do
+ @lazy = [0, 1, 2, 3].to_enum.lazy.uniq(&:even?)
+ end
+
+ it 'returns a lazy enumerator' do
+ @lazy.should be_an_instance_of(Enumerator::Lazy)
+ @lazy.force.should == [0, 1]
+ end
+
+ it 'return same value after rewind' do
+ @lazy.force.should == [0, 1]
+ @lazy.force.should == [0, 1]
+ end
+
+ it 'sets the size to nil' do
+ @lazy.size.should == nil
+ end
+ end
+
+ context 'when yielded with multiple arguments' do
+ before :each do
+ enum = Object.new.to_enum
+ class << enum
+ def each
+ yield 0, 'foo'
+ yield 1, 'FOO'
+ yield 2, 'bar'
+ end
+ end
+ @lazy = enum.lazy
+ end
+
+ it 'return same value after rewind' do
+ enum = @lazy.uniq { |_, label| label.downcase }
+ enum.force.should == [[0, 'foo'], [2, 'bar']]
+ enum.force.should == [[0, 'foo'], [2, 'bar']]
+ end
+
+ it 'returns all yield arguments as an array' do
+ @lazy.uniq { |_, label| label.downcase }.force.should == [[0, 'foo'], [2, 'bar']]
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.uniq.first(100).should ==
+ s.first(100).uniq
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/with_index_spec.rb b/spec/ruby/core/enumerator/lazy/with_index_spec.rb
new file mode 100644
index 0000000000..a6b5c38777
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/with_index_spec.rb
@@ -0,0 +1,36 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#with_index" do
+ it "enumerates with an index" do
+ (0..Float::INFINITY).lazy.with_index.map { |i, idx| [i, idx] }.first(3).should == [[0, 0], [1, 1], [2, 2]]
+ end
+
+ it "enumerates with an index starting at a given offset" do
+ (0..Float::INFINITY).lazy.with_index(3).map { |i, idx| [i, idx] }.first(3).should == [[0, 3], [1, 4], [2, 5]]
+ end
+
+ it "enumerates with an index starting at 0 when offset is nil" do
+ (0..Float::INFINITY).lazy.with_index(nil).map { |i, idx| [i, idx] }.first(3).should == [[0, 0], [1, 1], [2, 2]]
+ end
+
+ it "raises TypeError when offset does not convert to Integer" do
+ -> { (0..Float::INFINITY).lazy.with_index(false).map { |i, idx| i }.first(3) }.should raise_error(TypeError)
+ end
+
+ it "enumerates with a given block" do
+ result = []
+ (0..Float::INFINITY).lazy.with_index { |i, idx| result << [i * 2, idx] }.first(3)
+ result.should == [[0,0],[2,1],[4,2]]
+ end
+
+ it "resets after a new call to each" do
+ enum = (0..2).lazy.with_index.map { |i, idx| [i, idx] }
+ result = []
+ enum.each { |i, idx| result << [i, idx] }
+ enum.each { |i, idx| result << [i, idx] }
+ result.should == [[0,0], [1,1], [2,2], [0,0], [1,1], [2,2]]
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/zip_spec.rb b/spec/ruby/core/enumerator/lazy/zip_spec.rb
new file mode 100644
index 0000000000..5a828c1dcc
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/zip_spec.rb
@@ -0,0 +1,86 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#zip" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.zip []
+ ret.should be_an_instance_of(Enumerator::Lazy)
+ ret.should_not equal(@yieldsmixed)
+ end
+
+ it "keeps size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.zip([], []).size.should == 100
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.zip([4, 5], [8]).first(2).should == [[0, 4, 8], [1, 5, nil]]
+
+ @eventsmixed.zip([0, 1]).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with a gathered array when yield with multiple arguments" do
+ yields = @yieldsmixed.zip(EnumeratorLazySpecs::YieldsMixed.new.to_enum).force
+ yields.should == [EnumeratorLazySpecs::YieldsMixed.gathered_yields,
+ EnumeratorLazySpecs::YieldsMixed.gathered_yields].transpose
+ end
+
+ it "returns a Lazy when no arguments given" do
+ @yieldsmixed.zip.should be_an_instance_of(Enumerator::Lazy)
+ end
+
+ it "raises a TypeError if arguments contain non-list object" do
+ -> { @yieldsmixed.zip [], Object.new, [] }.should raise_error(TypeError)
+ end
+
+ describe "on a nested Lazy" do
+ it "keeps size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.map {}.zip([], []).size.should == 100
+ end
+
+ it "behaves as Enumerable#zip when given a block" do
+ lazy_yields = []
+ lazy_ret = @yieldsmixed.zip(EnumeratorLazySpecs::YieldsMixed.new.to_enum) { |lists| lazy_yields << lists }
+ enum_yields = []
+ enum_ret = EnumeratorLazySpecs::YieldsMixed.new.to_enum.zip(EnumeratorLazySpecs::YieldsMixed.new.to_enum) { |lists| enum_yields << lists }
+
+ lazy_yields.should == enum_yields
+ lazy_ret.should == enum_ret
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.map(&:succ).zip([4, 5], [8]).first(2).should == [[1, 4, 8], [2, 5, nil]]
+
+ @eventsmixed.zip([0, 1]).zip([0, 1]).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable and an array" do
+ s = 0..Float::INFINITY
+ s.lazy.zip(0..1000).first(100).should ==
+ s.first(100).zip(0..100)
+ end
+
+ it "works with two infinite enumerables" do
+ s = 0..Float::INFINITY
+ s.lazy.zip(s).first(100).should ==
+ s.first(100).zip(s)
+ end
+end
diff --git a/spec/ruby/core/enumerator/new_spec.rb b/spec/ruby/core/enumerator/new_spec.rb
new file mode 100644
index 0000000000..c439469525
--- /dev/null
+++ b/spec/ruby/core/enumerator/new_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator.new" do
+ context "no block given" do
+ ruby_version_is '3.0' do
+ it "raises" do
+ -> { Enumerator.new(1, :upto, 3) }.should raise_error(ArgumentError)
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "creates a new custom enumerator with the given object, iterator and arguments" do
+ enum = suppress_warning { Enumerator.new(1, :upto, 3) }
+ enum.should be_an_instance_of(Enumerator)
+ end
+
+ it "creates a new custom enumerator that responds to #each" do
+ enum = suppress_warning { Enumerator.new(1, :upto, 3) }
+ enum.respond_to?(:each).should == true
+ end
+
+ it "creates a new custom enumerator that runs correctly" do
+ suppress_warning { Enumerator.new(1, :upto, 3) }.map{ |x| x }.should == [1,2,3]
+ end
+
+ it "aliases the second argument to :each" do
+ suppress_warning { Enumerator.new(1..2) }.to_a.should ==
+ suppress_warning { Enumerator.new(1..2, :each) }.to_a
+ end
+
+ it "doesn't check for the presence of the iterator method" do
+ suppress_warning { Enumerator.new(nil) }.should be_an_instance_of(Enumerator)
+ end
+
+ it "uses the latest define iterator method" do
+ class StrangeEach
+ def each
+ yield :foo
+ end
+ end
+ enum = suppress_warning { Enumerator.new(StrangeEach.new) }
+ enum.to_a.should == [:foo]
+ class StrangeEach
+ def each
+ yield :bar
+ end
+ end
+ enum.to_a.should == [:bar]
+ end
+ end
+ end
+
+ context "when passed a block" do
+ it "defines iteration with block, yielder argument and calling << method" do
+ enum = Enumerator.new do |yielder|
+ a = 1
+
+ loop do
+ yielder << a
+ a = a + 1
+ end
+ end
+
+ enum.take(3).should == [1, 2, 3]
+ end
+
+ it "defines iteration with block, yielder argument and calling yield method" do
+ enum = Enumerator.new do |yielder|
+ a = 1
+
+ loop do
+ yielder.yield(a)
+ a = a + 1
+ end
+ end
+
+ enum.take(3).should == [1, 2, 3]
+ end
+
+ it "defines iteration with block, yielder argument and treating it as a proc" do
+ enum = Enumerator.new do |yielder|
+ "a\nb\nc".each_line(&yielder)
+ end
+
+ enum.to_a.should == ["a\n", "b\n", "c"]
+ end
+
+ describe 'yielded values' do
+ it 'handles yield arguments properly' do
+ Enumerator.new { |y| y.yield(1) }.to_a.should == [1]
+ Enumerator.new { |y| y.yield(1) }.first.should == 1
+
+ Enumerator.new { |y| y.yield([1]) }.to_a.should == [[1]]
+ Enumerator.new { |y| y.yield([1]) }.first.should == [1]
+
+ Enumerator.new { |y| y.yield(1, 2) }.to_a.should == [[1, 2]]
+ Enumerator.new { |y| y.yield(1, 2) }.first.should == [1, 2]
+
+ Enumerator.new { |y| y.yield([1, 2]) }.to_a.should == [[1, 2]]
+ Enumerator.new { |y| y.yield([1, 2]) }.first.should == [1, 2]
+ end
+
+ it 'handles << arguments properly' do
+ Enumerator.new { |y| y.<<(1) }.to_a.should == [1]
+ Enumerator.new { |y| y.<<(1) }.first.should == 1
+
+ Enumerator.new { |y| y.<<([1]) }.to_a.should == [[1]]
+ Enumerator.new { |y| y.<<([1]) }.first.should == [1]
+
+ # << doesn't accept multiple arguments
+ # Enumerator.new { |y| y.<<(1, 2) }.to_a.should == [[1, 2]]
+ # Enumerator.new { |y| y.<<(1, 2) }.first.should == [1, 2]
+
+ Enumerator.new { |y| y.<<([1, 2]) }.to_a.should == [[1, 2]]
+ Enumerator.new { |y| y.<<([1, 2]) }.first.should == [1, 2]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/next_spec.rb b/spec/ruby/core/enumerator/next_spec.rb
new file mode 100644
index 0000000000..a5e01a399d
--- /dev/null
+++ b/spec/ruby/core/enumerator/next_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#next" do
+ before :each do
+ @enum = 1.upto(3)
+ end
+
+ it "returns the next element of the enumeration" do
+ @enum.next.should == 1
+ @enum.next.should == 2
+ @enum.next.should == 3
+ end
+
+ it "raises a StopIteration exception at the end of the stream" do
+ 3.times { @enum.next }
+ -> { @enum.next }.should raise_error(StopIteration)
+ end
+
+ it "cannot be called again until the enumerator is rewound" do
+ 3.times { @enum.next }
+ -> { @enum.next }.should raise_error(StopIteration)
+ -> { @enum.next }.should raise_error(StopIteration)
+ -> { @enum.next }.should raise_error(StopIteration)
+ @enum.rewind
+ @enum.next.should == 1
+ end
+
+ it "restarts the enumerator if an exception terminated a previous iteration" do
+ exception = StandardError.new
+ enum = Enumerator.new do
+ raise exception
+ end
+
+ result = 2.times.map { enum.next rescue $! }
+
+ result.should == [exception, exception]
+ end
+end
diff --git a/spec/ruby/core/enumerator/next_values_spec.rb b/spec/ruby/core/enumerator/next_values_spec.rb
new file mode 100644
index 0000000000..201b5d323f
--- /dev/null
+++ b/spec/ruby/core/enumerator/next_values_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#next_values" do
+ before :each do
+ o = Object.new
+ def o.each
+ yield :a
+ yield :b1, :b2
+ yield :c
+ yield :d1, :d2
+ yield :e1, :e2, :e3
+ yield nil
+ yield
+ end
+
+ @e = o.to_enum
+ end
+
+ it "returns the next element in self" do
+ @e.next_values.should == [:a]
+ end
+
+ it "advances the position of the current element" do
+ @e.next.should == :a
+ @e.next_values.should == [:b1, :b2]
+ @e.next.should == :c
+ end
+
+ it "advances the position of the enumerator each time when called multiple times" do
+ 2.times { @e.next_values }
+ @e.next_values.should == [:c]
+ @e.next.should == [:d1, :d2]
+ end
+
+ it "works in concert with #rewind" do
+ 2.times { @e.next }
+ @e.rewind
+ @e.next_values.should == [:a]
+ end
+
+ it "returns an array with only nil if yield is called with nil" do
+ 5.times { @e.next }
+ @e.next_values.should == [nil]
+ end
+
+ it "returns an empty array if yield is called without arguments" do
+ 6.times { @e.next }
+ @e.next_values.should == []
+ end
+
+ it "raises StopIteration if called on a finished enumerator" do
+ 7.times { @e.next }
+ -> { @e.next_values }.should raise_error(StopIteration)
+ end
+end
diff --git a/spec/ruby/core/enumerator/peek_spec.rb b/spec/ruby/core/enumerator/peek_spec.rb
new file mode 100644
index 0000000000..2334385437
--- /dev/null
+++ b/spec/ruby/core/enumerator/peek_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#peek" do
+ before :each do
+ @e = (1..5).to_a.to_enum
+ end
+
+ it "returns the next element in self" do
+ @e.peek.should == 1
+ end
+
+ it "does not advance the position of the current element" do
+ @e.next.should == 1
+ @e.peek.should == 2
+ @e.next.should == 2
+ end
+
+ it "can be called repeatedly without advancing the position of the current element" do
+ @e.peek
+ @e.peek
+ @e.peek.should == 1
+ @e.next.should == 1
+ end
+
+ it "works in concert with #rewind" do
+ @e.next
+ @e.next
+ @e.rewind
+ @e.peek.should == 1
+ end
+
+ it "raises StopIteration if called on a finished enumerator" do
+ 5.times { @e.next }
+ -> { @e.peek }.should raise_error(StopIteration)
+ end
+end
diff --git a/spec/ruby/core/enumerator/peek_values_spec.rb b/spec/ruby/core/enumerator/peek_values_spec.rb
new file mode 100644
index 0000000000..7865546515
--- /dev/null
+++ b/spec/ruby/core/enumerator/peek_values_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#peek_values" do
+ before :each do
+ o = Object.new
+ def o.each
+ yield :a
+ yield :b1, :b2
+ yield :c
+ yield :d1, :d2
+ yield :e1, :e2, :e3
+ yield nil
+ yield
+ end
+
+ @e = o.to_enum
+ end
+
+ it "returns the next element in self" do
+ @e.peek_values.should == [:a]
+ end
+
+ it "does not advance the position of the current element" do
+ @e.next.should == :a
+ @e.peek_values.should == [:b1, :b2]
+ @e.next.should == [:b1, :b2]
+ end
+
+ it "can be called repeatedly without advancing the position of the current element" do
+ @e.peek_values
+ @e.peek_values
+ @e.peek_values.should == [:a]
+ @e.next.should == :a
+ end
+
+ it "works in concert with #rewind" do
+ @e.next
+ @e.next
+ @e.rewind
+ @e.peek_values.should == [:a]
+ end
+
+ it "returns an array with only nil if yield is called with nil" do
+ 5.times { @e.next }
+ @e.peek_values.should == [nil]
+ end
+
+ it "returns an empty array if yield is called without arguments" do
+ 6.times { @e.next }
+ @e.peek_values.should == []
+ end
+
+ it "raises StopIteration if called on a finished enumerator" do
+ 7.times { @e.next }
+ -> { @e.peek_values }.should raise_error(StopIteration)
+ end
+end
diff --git a/spec/ruby/core/enumerator/plus_spec.rb b/spec/ruby/core/enumerator/plus_spec.rb
new file mode 100644
index 0000000000..755be5b4eb
--- /dev/null
+++ b/spec/ruby/core/enumerator/plus_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#+" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "returns a chain of self and provided enumerators" do
+ one = Enumerator.new { |y| y << 1 }
+ two = Enumerator.new { |y| y << 2 }
+ three = Enumerator.new { |y| y << 3 }
+
+ chain = one + two + three
+
+ chain.should be_an_instance_of(Enumerator::Chain)
+ chain.each { |item| ScratchPad << item }
+ ScratchPad.recorded.should == [1, 2, 3]
+ end
+
+ it "calls #each on each argument" do
+ enum = Enumerator.new { |y| y << "one" }
+
+ obj1 = mock("obj1")
+ obj1.should_receive(:each).once.and_yield("two")
+
+ obj2 = mock("obj2")
+ obj2.should_receive(:each).once.and_yield("three")
+
+ chain = enum + obj1 + obj2
+ chain.each { |item| ScratchPad << item }
+ ScratchPad.recorded.should == ["one", "two", "three"]
+ end
+end
diff --git a/spec/ruby/core/enumerator/produce_spec.rb b/spec/ruby/core/enumerator/produce_spec.rb
new file mode 100644
index 0000000000..c69fb49303
--- /dev/null
+++ b/spec/ruby/core/enumerator/produce_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator.produce" do
+ it "creates an infinite enumerator" do
+ enum = Enumerator.produce(0) { |prev| prev + 1 }
+ enum.take(5).should == [0, 1, 2, 3, 4]
+ end
+
+ it "terminates iteration when block raises StopIteration exception" do
+ enum = Enumerator.produce(0) do | prev|
+ raise StopIteration if prev >= 2
+ prev + 1
+ end
+
+ enum.to_a.should == [0, 1, 2]
+ end
+
+ context "when initial value skipped" do
+ it "uses nil instead" do
+ ScratchPad.record []
+ enum = Enumerator.produce { |prev| ScratchPad << prev; (prev || 0) + 1 }
+
+ enum.take(3).should == [1, 2, 3]
+ ScratchPad.recorded.should == [nil, 1, 2]
+ end
+
+ it "starts enumerable from result of first block call" do
+ array = "a\nb\nc\nd".lines
+ lines = Enumerator.produce { array.shift }.take_while { |s| s }
+
+ lines.should == ["a\n", "b\n", "c\n", "d"]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/rewind_spec.rb b/spec/ruby/core/enumerator/rewind_spec.rb
new file mode 100644
index 0000000000..a105f2c619
--- /dev/null
+++ b/spec/ruby/core/enumerator/rewind_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Enumerator#rewind" do
+ before :each do
+ @enum = 1.upto(3)
+ end
+
+ it "resets the enumerator to its initial state" do
+ @enum.next.should == 1
+ @enum.next.should == 2
+ @enum.rewind
+ @enum.next.should == 1
+ end
+
+ it "returns self" do
+ @enum.rewind.should == @enum
+ end
+
+ it "has no effect on a new enumerator" do
+ @enum.rewind
+ @enum.next.should == 1
+ end
+
+ it "has no effect if called multiple, consecutive times" do
+ @enum.next.should == 1
+ @enum.rewind
+ @enum.rewind
+ @enum.next.should == 1
+ end
+
+ it "works with peek to reset the position" do
+ @enum.next
+ @enum.next
+ @enum.rewind
+ @enum.next
+ @enum.peek.should == 2
+ end
+
+ it "calls the enclosed object's rewind method if one exists" do
+ obj = mock('rewinder')
+ enum = obj.to_enum
+ obj.should_receive(:each).at_most(1)
+ obj.should_receive(:rewind)
+ enum.rewind
+ end
+
+ it "does nothing if the object doesn't have a #rewind method" do
+ obj = mock('rewinder')
+ enum = obj.to_enum
+ obj.should_receive(:each).at_most(1)
+ -> { enum.rewind.should == enum }.should_not raise_error
+ end
+end
+
+describe "Enumerator#rewind" do
+ before :each do
+ ScratchPad.record []
+ @enum = EnumeratorSpecs::Feed.new.to_enum(:each)
+ end
+
+ it "clears a pending #feed value" do
+ @enum.next
+ @enum.feed :a
+ @enum.rewind
+ @enum.next
+ @enum.next
+ ScratchPad.recorded.should == [nil]
+ end
+end
diff --git a/spec/ruby/core/enumerator/size_spec.rb b/spec/ruby/core/enumerator/size_spec.rb
new file mode 100644
index 0000000000..6accd26a4e
--- /dev/null
+++ b/spec/ruby/core/enumerator/size_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#size" do
+ it "returns same value if set size is an Integer" do
+ Enumerator.new(100) {}.size.should == 100
+ end
+
+ it "returns nil if set size is nil" do
+ Enumerator.new(nil) {}.size.should be_nil
+ end
+
+ it "returns returning value from size.call if set size is a Proc" do
+ base_size = 100
+ enum = Enumerator.new(-> { base_size + 1 }) {}
+ base_size = 200
+ enum.size.should == 201
+ base_size = 300
+ enum.size.should == 301
+ end
+
+ it "returns the result from size.call if the size respond to call" do
+ obj = mock('call')
+ obj.should_receive(:call).and_return(42)
+ Enumerator.new(obj) {}.size.should == 42
+ end
+end
diff --git a/spec/ruby/core/enumerator/to_enum_spec.rb b/spec/ruby/core/enumerator/to_enum_spec.rb
new file mode 100644
index 0000000000..cadfcf6314
--- /dev/null
+++ b/spec/ruby/core/enumerator/to_enum_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/enumerator/enum_for'
+
+describe "Enumerator#to_enum" do
+ it_behaves_like :enum_for, :enum_for
+end
diff --git a/spec/ruby/core/enumerator/with_index_spec.rb b/spec/ruby/core/enumerator/with_index_spec.rb
new file mode 100644
index 0000000000..ac37cee508
--- /dev/null
+++ b/spec/ruby/core/enumerator/with_index_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/enumerator/with_index'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Enumerator#with_index" do
+ it_behaves_like :enum_with_index, :with_index
+ it_behaves_like :enumeratorized_with_origin_size, :with_index, [1,2,3].select
+
+ it "returns a new Enumerator when no block is given" do
+ enum1 = [1,2,3].select
+ enum2 = enum1.with_index
+ enum2.should be_an_instance_of(Enumerator)
+ enum1.should_not === enum2
+ end
+
+ it "accepts an optional argument when given a block" do
+ -> do
+ @enum.with_index(1) { |f| f}
+ end.should_not raise_error(ArgumentError)
+ end
+
+ it "accepts an optional argument when not given a block" do
+ -> do
+ @enum.with_index(1)
+ end.should_not raise_error(ArgumentError)
+ end
+
+ it "numbers indices from the given index when given an offset but no block" do
+ @enum.with_index(1).to_a.should == [[1,1], [2,2], [3,3], [4,4]]
+ end
+
+ it "numbers indices from the given index when given an offset and block" do
+ acc = []
+ @enum.with_index(1) {|e,i| acc << [e,i] }
+ acc.should == [[1,1], [2,2], [3,3], [4,4]]
+ end
+
+ it "raises a TypeError when the argument cannot be converted to numeric" do
+ -> do
+ @enum.with_index('1') {|*i| i}
+ end.should raise_error(TypeError)
+ end
+
+ it "converts non-numeric arguments to Integer via #to_int" do
+ (o = mock('1')).should_receive(:to_int).and_return(1)
+ @enum.with_index(o).to_a.should == [[1,1], [2,2], [3,3], [4,4]]
+ end
+
+ it "coerces the given numeric argument to an Integer" do
+ @enum.with_index(1.678).to_a.should == [[1,1], [2,2], [3,3], [4,4]]
+
+ res = []
+ @enum.with_index(1.001) { |*x| res << x}
+ res.should == [[1,1], [2,2], [3,3], [4,4]]
+ end
+
+ it "treats nil argument as no argument" do
+ @enum.with_index(nil).to_a.should == [[1,0], [2,1], [3,2], [4,3]]
+
+ res = []
+ @enum.with_index(nil) { |*x| res << x}
+ res.should == [[1,0], [2,1], [3,2], [4,3]]
+ end
+
+ it "accepts negative argument" do
+ @enum.with_index(-1).to_a.should == [[1,-1], [2,0], [3,1], [4,2]]
+
+ res = []
+ @enum.with_index(-1) { |*x| res << x}
+ res.should == [[1,-1], [2,0], [3,1], [4,2]]
+ end
+end
diff --git a/spec/ruby/core/enumerator/with_object_spec.rb b/spec/ruby/core/enumerator/with_object_spec.rb
new file mode 100644
index 0000000000..e7ba83fd9f
--- /dev/null
+++ b/spec/ruby/core/enumerator/with_object_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/enumerator/with_object'
+
+describe "Enumerator#with_object" do
+ it_behaves_like :enum_with_object, :with_object
+end
diff --git a/spec/ruby/core/enumerator/yielder/append_spec.rb b/spec/ruby/core/enumerator/yielder/append_spec.rb
new file mode 100644
index 0000000000..a36e5d64b6
--- /dev/null
+++ b/spec/ruby/core/enumerator/yielder/append_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Yielder#<<" do
+ # TODO: There's some common behavior between yield and <<; move to a shared spec
+ it "yields the value to the block" do
+ ary = []
+ y = Enumerator::Yielder.new {|x| ary << x}
+ y << 1
+
+ ary.should == [1]
+ end
+
+ it "doesn't double-wrap Arrays" do
+ yields = []
+ y = Enumerator::Yielder.new {|args| yields << args }
+ y << [1]
+ yields.should == [[1]]
+ end
+
+ it "returns self" do
+ y = Enumerator::Yielder.new {|x| x + 1}
+ (y << 1).should equal(y)
+ end
+
+ context "when multiple arguments passed" do
+ it "raises an ArgumentError" do
+ ary = []
+ y = Enumerator::Yielder.new { |*x| ary << x }
+
+ -> {
+ y.<<(1, 2)
+ }.should raise_error(ArgumentError, /wrong number of arguments/)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/yielder/initialize_spec.rb b/spec/ruby/core/enumerator/yielder/initialize_spec.rb
new file mode 100644
index 0000000000..5a6eee2d0f
--- /dev/null
+++ b/spec/ruby/core/enumerator/yielder/initialize_spec.rb
@@ -0,0 +1,18 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Yielder#initialize" do
+ before :each do
+ @class = Enumerator::Yielder
+ @uninitialized = @class.allocate
+ end
+
+ it "is a private method" do
+ @class.should have_private_instance_method(:initialize, false)
+ end
+
+ it "returns self when given a block" do
+ @uninitialized.send(:initialize) {}.should equal(@uninitialized)
+ end
+end
diff --git a/spec/ruby/core/enumerator/yielder/to_proc_spec.rb b/spec/ruby/core/enumerator/yielder/to_proc_spec.rb
new file mode 100644
index 0000000000..1d3681ab50
--- /dev/null
+++ b/spec/ruby/core/enumerator/yielder/to_proc_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Yielder#to_proc" do
+ it "returns a Proc object that takes an argument and yields it to the block" do
+ ScratchPad.record []
+ y = Enumerator::Yielder.new { |*args| ScratchPad << args; "foobar" }
+
+ callable = y.to_proc
+ callable.class.should == Proc
+
+ result = callable.call(1, 2)
+ ScratchPad.recorded.should == [[1, 2]]
+
+ result.should == "foobar"
+ end
+end
diff --git a/spec/ruby/core/enumerator/yielder/yield_spec.rb b/spec/ruby/core/enumerator/yielder/yield_spec.rb
new file mode 100644
index 0000000000..acfdf114b6
--- /dev/null
+++ b/spec/ruby/core/enumerator/yielder/yield_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Yielder#yield" do
+ it "yields the value to the block" do
+ ary = []
+ y = Enumerator::Yielder.new {|x| ary << x}
+ y.yield 1
+
+ ary.should == [1]
+ end
+
+ it "yields with passed arguments" do
+ yields = []
+ y = Enumerator::Yielder.new {|*args| yields << args }
+ y.yield 1, 2
+ yields.should == [[1, 2]]
+ end
+
+ it "returns the result of the block for the given value" do
+ y = Enumerator::Yielder.new {|x| x + 1}
+ y.yield(1).should == 2
+ end
+
+ context "when multiple arguments passed" do
+ it "yields the arguments list to the block" do
+ ary = []
+ y = Enumerator::Yielder.new { |*x| ary << x }
+ y.yield(1, 2)
+
+ ary.should == [[1, 2]]
+ end
+ end
+end
diff --git a/spec/ruby/core/env/assoc_spec.rb b/spec/ruby/core/env/assoc_spec.rb
new file mode 100644
index 0000000000..c7a388db75
--- /dev/null
+++ b/spec/ruby/core/env/assoc_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "ENV.assoc" do
+ before :each do
+ @foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ end
+
+ it "returns an array of the key and value of the environment variable with the given key" do
+ ENV["foo"] = "bar"
+ ENV.assoc("foo").should == ["foo", "bar"]
+ end
+
+ it "returns nil if no environment variable with the given key exists" do
+ ENV.assoc("foo").should == nil
+ end
+
+ it "returns the key element coerced with #to_str" do
+ ENV["foo"] = "bar"
+ k = mock('key')
+ k.should_receive(:to_str).and_return("foo")
+ ENV.assoc(k).should == ["foo", "bar"]
+ end
+
+ it "raises TypeError if the argument is not a String and does not respond to #to_str" do
+ -> { ENV.assoc(Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/clear_spec.rb b/spec/ruby/core/env/clear_spec.rb
new file mode 100644
index 0000000000..48b034ba1d
--- /dev/null
+++ b/spec/ruby/core/env/clear_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "ENV.clear" do
+ it "deletes all environment variables" do
+ orig = ENV.to_hash
+ begin
+ ENV.clear.should equal(ENV)
+
+ # This used 'env' the helper before. That shells out to 'env' which
+ # itself sets up certain environment variables before it runs, because
+ # the shell sets them up before it runs any command.
+ #
+ # Thusly, you can ONLY test this by asking through ENV itself.
+ ENV.size.should == 0
+ ensure
+ ENV.replace orig
+ end
+ end
+
+end
diff --git a/spec/ruby/core/env/delete_if_spec.rb b/spec/ruby/core/env/delete_if_spec.rb
new file mode 100644
index 0000000000..d2de51c225
--- /dev/null
+++ b/spec/ruby/core/env/delete_if_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "ENV.delete_if" do
+ before :each do
+ @foo = ENV["foo"]
+ @bar = ENV["bar"]
+
+ ENV["foo"] = "0"
+ ENV["bar"] = "1"
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ ENV["bar"] = @bar
+ end
+
+ it "deletes pairs if the block returns true" do
+ ENV.delete_if { |k, v| ["foo", "bar"].include?(k) }
+ ENV["foo"].should == nil
+ ENV["bar"].should == nil
+ end
+
+ it "returns ENV when block given" do
+ ENV.delete_if { |k, v| ["foo", "bar"].include?(k) }.should equal(ENV)
+ end
+
+ it "returns ENV even if nothing deleted" do
+ ENV.delete_if { false }.should equal(ENV)
+ end
+
+ it "returns an Enumerator if no block given" do
+ ENV.delete_if.should be_an_instance_of(Enumerator)
+ end
+
+ it "deletes pairs through enumerator" do
+ enum = ENV.delete_if
+ enum.each { |k, v| ["foo", "bar"].include?(k) }
+ ENV["foo"].should == nil
+ ENV["bar"].should == nil
+ end
+
+ it "returns ENV from enumerator" do
+ enum = ENV.delete_if
+ enum.each { |k, v| ["foo", "bar"].include?(k) }.should equal(ENV)
+ end
+
+ it "returns ENV from enumerator even if nothing deleted" do
+ enum = ENV.delete_if
+ enum.each { false }.should equal(ENV)
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :delete_if, ENV
+end
diff --git a/spec/ruby/core/env/delete_spec.rb b/spec/ruby/core/env/delete_spec.rb
new file mode 100644
index 0000000000..5e7891f74d
--- /dev/null
+++ b/spec/ruby/core/env/delete_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "ENV.delete" do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "removes the variable from the environment" do
+ ENV["foo"] = "bar"
+ ENV.delete("foo")
+ ENV["foo"].should == nil
+ end
+
+ it "returns the previous value" do
+ ENV["foo"] = "bar"
+ ENV.delete("foo").should == "bar"
+ end
+
+ it "returns nil if the named environment variable does not exist and no block given" do
+ ENV.delete("foo")
+ ENV.delete("foo").should == nil
+ end
+
+ it "yields the name to the given block if the named environment variable does not exist" do
+ ENV.delete("foo")
+ ENV.delete("foo") { |name| ScratchPad.record name }
+ ScratchPad.recorded.should == "foo"
+ end
+
+ ruby_version_is "3.0" do
+ it "returns the result of given block if the named environment variable does not exist" do
+ ENV.delete("foo")
+ ENV.delete("foo") { |name| "bar" }.should == "bar"
+ end
+ end
+
+ it "does not evaluate the block if the environment variable exists" do
+ ENV["foo"] = "bar"
+ ENV.delete("foo") { |name| fail "Should not happen" }
+ ENV["foo"].should == nil
+ end
+
+ it "raises TypeError if the argument is not a String and does not respond to #to_str" do
+ -> { ENV.delete(Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/each_key_spec.rb b/spec/ruby/core/env/each_key_spec.rb
new file mode 100644
index 0000000000..0efcb09900
--- /dev/null
+++ b/spec/ruby/core/env/each_key_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "ENV.each_key" do
+
+ it "returns each key" do
+ e = []
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ ENV["1"] = "3"
+ ENV["2"] = "4"
+ ENV.each_key { |k| e << k }.should equal(ENV)
+ e.should include("1")
+ e.should include("2")
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it "returns an Enumerator if called without a block" do
+ enum = ENV.each_key
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == ENV.keys
+ end
+
+ it "returns keys in the locale encoding" do
+ ENV.each_key do |key|
+ key.encoding.should == Encoding.find('locale')
+ end
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :each_key, ENV
+end
diff --git a/spec/ruby/core/env/each_pair_spec.rb b/spec/ruby/core/env/each_pair_spec.rb
new file mode 100644
index 0000000000..2d7ed5faa0
--- /dev/null
+++ b/spec/ruby/core/env/each_pair_spec.rb
@@ -0,0 +1,6 @@
+require_relative 'spec_helper'
+require_relative 'shared/each'
+
+describe "ENV.each_pair" do
+ it_behaves_like :env_each, :each_pair
+end
diff --git a/spec/ruby/core/env/each_spec.rb b/spec/ruby/core/env/each_spec.rb
new file mode 100644
index 0000000000..d1e06f55b6
--- /dev/null
+++ b/spec/ruby/core/env/each_spec.rb
@@ -0,0 +1,6 @@
+require_relative 'spec_helper'
+require_relative 'shared/each'
+
+describe "ENV.each" do
+ it_behaves_like :env_each, :each
+end
diff --git a/spec/ruby/core/env/each_value_spec.rb b/spec/ruby/core/env/each_value_spec.rb
new file mode 100644
index 0000000000..cc3c9ebfb8
--- /dev/null
+++ b/spec/ruby/core/env/each_value_spec.rb
@@ -0,0 +1,34 @@
+require_relative 'spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "ENV.each_value" do
+
+ it "returns each value" do
+ e = []
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ ENV["1"] = "3"
+ ENV["2"] = "4"
+ ENV.each_value { |v| e << v }.should equal(ENV)
+ e.should include("3")
+ e.should include("4")
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it "returns an Enumerator if called without a block" do
+ enum = ENV.each_value
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == ENV.values
+ end
+
+ it "uses the locale encoding" do
+ ENV.each_value do |value|
+ value.should.be_locale_env
+ end
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :each_value, ENV
+end
diff --git a/spec/ruby/core/env/element_reference_spec.rb b/spec/ruby/core/env/element_reference_spec.rb
new file mode 100644
index 0000000000..560c127a9c
--- /dev/null
+++ b/spec/ruby/core/env/element_reference_spec.rb
@@ -0,0 +1,76 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "ENV.[]" do
+ before :each do
+ @variable = "returns_only_frozen_values"
+ end
+
+ after :each do
+ ENV.delete @variable
+ end
+
+ it "returns nil if the variable isn't found" do
+ ENV["this_var_is_never_set"].should == nil
+ end
+
+ it "returns only frozen values" do
+ ENV[@variable] = "a non-frozen string"
+ ENV[@variable].should.frozen?
+ end
+
+ it "coerces a non-string name with #to_str" do
+ ENV[@variable] = "bar"
+ k = mock('key')
+ k.should_receive(:to_str).and_return(@variable)
+ ENV[k].should == "bar"
+ end
+
+ it "raises TypeError if the argument is not a String and does not respond to #to_str" do
+ -> { ENV[Object.new] }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ platform_is :windows do
+ it "looks up values case-insensitively" do
+ ENV[@variable] = "bar"
+ ENV[@variable.upcase].should == "bar"
+ end
+ end
+end
+
+describe "ENV.[]" do
+ before :each do
+ @variable = "env_element_reference_encoding_specs"
+
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::BINARY
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+
+ ENV.delete @variable
+ end
+
+ it "uses the locale encoding if Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+
+ locale = ENVSpecs.encoding
+ locale = Encoding::BINARY if locale == Encoding::US_ASCII
+ ENV[@variable] = "\xC3\xB8"
+ ENV[@variable].encoding.should == locale
+ end
+
+ it "transcodes from the locale encoding to Encoding.default_internal if set" do
+ # We cannot reliably know the locale encoding, so we merely check that
+ # the result string has the expected encoding.
+ ENV[@variable] = ""
+ Encoding.default_internal = Encoding::IBM437
+
+ ENV[@variable].encoding.should equal(Encoding::IBM437)
+ end
+end
diff --git a/spec/ruby/core/env/element_set_spec.rb b/spec/ruby/core/env/element_set_spec.rb
new file mode 100644
index 0000000000..26dfee1ade
--- /dev/null
+++ b/spec/ruby/core/env/element_set_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/store'
+
+describe "ENV.[]=" do
+ it_behaves_like :env_store, :[]=
+end
diff --git a/spec/ruby/core/env/empty_spec.rb b/spec/ruby/core/env/empty_spec.rb
new file mode 100644
index 0000000000..afeb406a9e
--- /dev/null
+++ b/spec/ruby/core/env/empty_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "ENV.empty?" do
+
+ it "returns true if the Environment is empty" do
+ if ENV.keys.size > 0
+ ENV.should_not.empty?
+ end
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ ENV.should.empty?
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it "returns false if not empty" do
+ if ENV.keys.size > 0
+ ENV.should_not.empty?
+ end
+ end
+end
diff --git a/spec/ruby/core/env/except_spec.rb b/spec/ruby/core/env/except_spec.rb
new file mode 100644
index 0000000000..cfe5865abe
--- /dev/null
+++ b/spec/ruby/core/env/except_spec.rb
@@ -0,0 +1,36 @@
+require_relative 'spec_helper'
+require_relative 'shared/to_hash'
+
+ruby_version_is "3.0" do
+ describe "ENV.except" do
+ before do
+ @orig_hash = ENV.to_hash
+ end
+
+ after do
+ ENV.replace @orig_hash
+ end
+
+ # Testing the method without arguments is covered via
+ it_behaves_like :env_to_hash, :except
+
+ it "returns a hash without the requested subset" do
+ ENV.clear
+
+ ENV['one'] = '1'
+ ENV['two'] = '2'
+ ENV['three'] = '3'
+
+ ENV.except('one', 'three').should == { 'two' => '2' }
+ end
+
+ it "ignores keys not present in the original hash" do
+ ENV.clear
+
+ ENV['one'] = '1'
+ ENV['two'] = '2'
+
+ ENV.except('one', 'three').should == { 'two' => '2' }
+ end
+ end
+end
diff --git a/spec/ruby/core/env/fetch_spec.rb b/spec/ruby/core/env/fetch_spec.rb
new file mode 100644
index 0000000000..b2e7a88cab
--- /dev/null
+++ b/spec/ruby/core/env/fetch_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/hash/key_error'
+require_relative 'fixtures/common'
+
+describe "ENV.fetch" do
+ before :each do
+ @foo_saved = ENV.delete("foo")
+ end
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "returns a value" do
+ ENV["foo"] = "bar"
+ ENV.fetch("foo").should == "bar"
+ end
+
+ it "raises a TypeError if the key is not a String" do
+ -> { ENV.fetch Object.new }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ context "when the key is not found" do
+ it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, ENV
+
+ it "formats the object with #inspect in the KeyError message" do
+ -> {
+ ENV.fetch('foo')
+ }.should raise_error(KeyError, 'key not found: "foo"')
+ end
+ end
+
+ it "provides the given default parameter" do
+ ENV.fetch("foo", "default").should == "default"
+ end
+
+ it "does not insist that the default be a String" do
+ ENV.fetch("foo", :default).should == :default
+ end
+
+ it "provides a default value from a block" do
+ ENV.fetch("foo") { |k| "wanted #{k}" }.should == "wanted foo"
+ end
+
+ it "does not insist that the block return a String" do
+ ENV.fetch("foo") { |k| k.to_sym }.should == :foo
+ end
+
+ it "warns on block and default parameter given" do
+ -> do
+ ENV.fetch("foo", "default") { "bar" }.should == "bar"
+ end.should complain(/block supersedes default value argument/)
+ end
+
+ it "does not evaluate the block when key found" do
+ ENV["foo"] = "bar"
+ ENV.fetch("foo") { fail "should not get here"}.should == "bar"
+ end
+
+ it "uses the locale encoding" do
+ ENV["foo"] = "bar"
+ ENV.fetch("foo").encoding.should == ENVSpecs.encoding
+ end
+end
diff --git a/spec/ruby/core/env/filter_spec.rb b/spec/ruby/core/env/filter_spec.rb
new file mode 100644
index 0000000000..52f8b79a0b
--- /dev/null
+++ b/spec/ruby/core/env/filter_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+require_relative 'shared/select'
+
+describe "ENV.filter!" do
+ it_behaves_like :env_select!, :filter!
+ it_behaves_like :enumeratorized_with_origin_size, :filter!, ENV
+end
+
+describe "ENV.filter" do
+ it_behaves_like :env_select, :filter
+ it_behaves_like :enumeratorized_with_origin_size, :filter, ENV
+end
diff --git a/spec/ruby/core/env/fixtures/common.rb b/spec/ruby/core/env/fixtures/common.rb
new file mode 100644
index 0000000000..8d5057614d
--- /dev/null
+++ b/spec/ruby/core/env/fixtures/common.rb
@@ -0,0 +1,9 @@
+module ENVSpecs
+ def self.encoding
+ locale = Encoding.find('locale')
+ if ruby_version_is '3' and platform_is :windows
+ locale = Encoding::UTF_8
+ end
+ locale
+ end
+end
diff --git a/spec/ruby/core/env/has_key_spec.rb b/spec/ruby/core/env/has_key_spec.rb
new file mode 100644
index 0000000000..798668105d
--- /dev/null
+++ b/spec/ruby/core/env/has_key_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+
+describe "ENV.has_key?" do
+ it_behaves_like :env_include, :has_key?
+end
diff --git a/spec/ruby/core/env/has_value_spec.rb b/spec/ruby/core/env/has_value_spec.rb
new file mode 100644
index 0000000000..a2bf3eb877
--- /dev/null
+++ b/spec/ruby/core/env/has_value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/value'
+
+describe "ENV.has_value?" do
+ it_behaves_like :env_value, :has_value?
+end
diff --git a/spec/ruby/core/env/include_spec.rb b/spec/ruby/core/env/include_spec.rb
new file mode 100644
index 0000000000..3975f095ac
--- /dev/null
+++ b/spec/ruby/core/env/include_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+
+describe "ENV.include?" do
+ it_behaves_like :env_include, :include?
+end
diff --git a/spec/ruby/core/env/index_spec.rb b/spec/ruby/core/env/index_spec.rb
new file mode 100644
index 0000000000..301a66ab4e
--- /dev/null
+++ b/spec/ruby/core/env/index_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'shared/key'
+
+ruby_version_is ''...'3.0' do
+ describe "ENV.index" do
+ it_behaves_like :env_key, :index
+
+ it "warns about deprecation" do
+ -> do
+ ENV.index("foo")
+ end.should complain(/warning: ENV.index is deprecated; use ENV.key/)
+ end
+ end
+end
diff --git a/spec/ruby/core/env/indexes_spec.rb b/spec/ruby/core/env/indexes_spec.rb
new file mode 100644
index 0000000000..e724feaa39
--- /dev/null
+++ b/spec/ruby/core/env/indexes_spec.rb
@@ -0,0 +1 @@
+require_relative '../../spec_helper'
diff --git a/spec/ruby/core/env/indices_spec.rb b/spec/ruby/core/env/indices_spec.rb
new file mode 100644
index 0000000000..e724feaa39
--- /dev/null
+++ b/spec/ruby/core/env/indices_spec.rb
@@ -0,0 +1 @@
+require_relative '../../spec_helper'
diff --git a/spec/ruby/core/env/inspect_spec.rb b/spec/ruby/core/env/inspect_spec.rb
new file mode 100644
index 0000000000..3c611c24a1
--- /dev/null
+++ b/spec/ruby/core/env/inspect_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "ENV.inspect" do
+
+ it "returns a String that looks like a Hash with real data" do
+ ENV["foo"] = "bar"
+ ENV.inspect.should =~ /\{.*"foo"=>"bar".*\}/
+ ENV.delete "foo"
+ end
+
+end
diff --git a/spec/ruby/core/env/invert_spec.rb b/spec/ruby/core/env/invert_spec.rb
new file mode 100644
index 0000000000..c095374d95
--- /dev/null
+++ b/spec/ruby/core/env/invert_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "ENV.invert" do
+ before :each do
+ ENV["foo"] = "bar"
+ end
+
+ after :each do
+ ENV.delete "foo"
+ end
+
+ it "returns a hash with ENV.keys as the values and vice versa" do
+ ENV.invert["bar"].should == "foo"
+ ENV["foo"].should == "bar"
+ end
+end
diff --git a/spec/ruby/core/env/keep_if_spec.rb b/spec/ruby/core/env/keep_if_spec.rb
new file mode 100644
index 0000000000..64b6a207d0
--- /dev/null
+++ b/spec/ruby/core/env/keep_if_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "ENV.keep_if" do
+ before :each do
+ @foo = ENV["foo"]
+ @bar = ENV["bar"]
+
+ ENV["foo"] = "0"
+ ENV["bar"] = "1"
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ ENV["bar"] = @bar
+ end
+
+ it "deletes pairs if the block returns false" do
+ ENV.keep_if { |k, v| !["foo", "bar"].include?(k) }
+ ENV["foo"].should == nil
+ ENV["bar"].should == nil
+ end
+
+ it "returns ENV when block given" do
+ ENV.keep_if { |k, v| !["foo", "bar"].include?(k) }.should equal(ENV)
+ end
+
+ it "returns ENV even if nothing deleted" do
+ ENV.keep_if { true }.should equal(ENV)
+ end
+
+ it "returns an Enumerator if no block given" do
+ ENV.keep_if.should be_an_instance_of(Enumerator)
+ end
+
+ it "deletes pairs through enumerator" do
+ enum = ENV.keep_if
+ enum.each { |k, v| !["foo", "bar"].include?(k) }
+ ENV["foo"].should == nil
+ ENV["bar"].should == nil
+ end
+
+ it "returns ENV from enumerator" do
+ enum = ENV.keep_if
+ enum.each { |k, v| !["foo", "bar"].include?(k) }.should equal(ENV)
+ end
+
+ it "returns ENV from enumerator even if nothing deleted" do
+ enum = ENV.keep_if
+ enum.each { true }.should equal(ENV)
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :keep_if, ENV
+end
diff --git a/spec/ruby/core/env/key_spec.rb b/spec/ruby/core/env/key_spec.rb
new file mode 100644
index 0000000000..82cfbefa39
--- /dev/null
+++ b/spec/ruby/core/env/key_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+require_relative 'shared/key'
+
+describe "ENV.key?" do
+ it_behaves_like :env_include, :key?
+end
+
+describe "ENV.key" do
+ it_behaves_like :env_key, :key
+end
diff --git a/spec/ruby/core/env/keys_spec.rb b/spec/ruby/core/env/keys_spec.rb
new file mode 100644
index 0000000000..b074a8f7c7
--- /dev/null
+++ b/spec/ruby/core/env/keys_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "ENV.keys" do
+
+ it "returns an array of the keys" do
+ ENV.keys.should == ENV.to_hash.keys
+ end
+
+ it "returns the keys in the locale encoding" do
+ ENV.keys.each do |key|
+ key.encoding.should == Encoding.find('locale')
+ end
+ end
+end
diff --git a/spec/ruby/core/env/length_spec.rb b/spec/ruby/core/env/length_spec.rb
new file mode 100644
index 0000000000..536af9edf5
--- /dev/null
+++ b/spec/ruby/core/env/length_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+
+describe "ENV.length" do
+ it_behaves_like :env_length, :length
+end
diff --git a/spec/ruby/core/env/member_spec.rb b/spec/ruby/core/env/member_spec.rb
new file mode 100644
index 0000000000..9119022ae5
--- /dev/null
+++ b/spec/ruby/core/env/member_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+
+describe "ENV.member?" do
+ it_behaves_like :env_include, :member?
+end
diff --git a/spec/ruby/core/env/merge_spec.rb b/spec/ruby/core/env/merge_spec.rb
new file mode 100644
index 0000000000..f10662cf79
--- /dev/null
+++ b/spec/ruby/core/env/merge_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/update'
+
+describe "ENV.merge!" do
+ it_behaves_like :env_update, :merge!
+end
diff --git a/spec/ruby/core/env/rassoc_spec.rb b/spec/ruby/core/env/rassoc_spec.rb
new file mode 100644
index 0000000000..ab9fe68088
--- /dev/null
+++ b/spec/ruby/core/env/rassoc_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+
+describe "ENV.rassoc" do
+ before :each do
+ @foo = ENV["foo"]
+ @baz = ENV["baz"]
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ ENV["baz"] = @baz
+ end
+
+ it "returns an array of the key and value of the environment variable with the given value" do
+ ENV["foo"] = "bar"
+ ENV.rassoc("bar").should == ["foo", "bar"]
+ end
+
+ it "returns a single array even if there are multiple such environment variables" do
+ ENV["foo"] = "bar"
+ ENV["baz"] = "bar"
+ [
+ ["foo", "bar"],
+ ["baz", "bar"],
+ ].should include(ENV.rassoc("bar"))
+ end
+
+ it "returns nil if no environment variable with the given value exists" do
+ ENV.rassoc("bar").should == nil
+ end
+
+ it "returns the value element coerced with #to_str" do
+ ENV["foo"] = "bar"
+ v = mock('value')
+ v.should_receive(:to_str).and_return("bar")
+ ENV.rassoc(v).should == ["foo", "bar"]
+ end
+
+ it "returns nil if the argument is not a String and does not respond to #to_str" do
+ ENV.rassoc(Object.new).should == nil
+ end
+end
diff --git a/spec/ruby/core/env/rehash_spec.rb b/spec/ruby/core/env/rehash_spec.rb
new file mode 100644
index 0000000000..3782e4b727
--- /dev/null
+++ b/spec/ruby/core/env/rehash_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "ENV.rehash" do
+ it "returns nil" do
+ ENV.rehash.should == nil
+ end
+end
diff --git a/spec/ruby/core/env/reject_spec.rb b/spec/ruby/core/env/reject_spec.rb
new file mode 100644
index 0000000000..6a9794925d
--- /dev/null
+++ b/spec/ruby/core/env/reject_spec.rb
@@ -0,0 +1,101 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "ENV.reject!" do
+ before :each do
+ @foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ end
+
+ it "rejects entries based on key" do
+ ENV["foo"] = "bar"
+ ENV.reject! { |k, v| k == "foo" }
+ ENV["foo"].should == nil
+ end
+
+ it "rejects entries based on value" do
+ ENV["foo"] = "bar"
+ ENV.reject! { |k, v| v == "bar" }
+ ENV["foo"].should == nil
+ end
+
+ it "returns itself or nil" do
+ ENV.reject! { false }.should == nil
+ ENV["foo"] = "bar"
+ ENV.reject! { |k, v| k == "foo" }.should equal(ENV)
+ ENV["foo"].should == nil
+ end
+
+ it "returns an Enumerator if called without a block" do
+ ENV["foo"] = "bar"
+ enum = ENV.reject!
+ enum.should be_an_instance_of(Enumerator)
+ enum.each { |k, v| k == "foo" }.should equal(ENV)
+ ENV["foo"].should == nil
+ end
+
+ it "doesn't raise if empty" do
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ -> { ENV.reject! }.should_not raise_error(LocalJumpError)
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :reject!, ENV
+end
+
+describe "ENV.reject" do
+ before :each do
+ @foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ end
+
+ it "rejects entries based on key" do
+ ENV["foo"] = "bar"
+ e = ENV.reject { |k, v| k == "foo" }
+ e["foo"].should == nil
+ ENV["foo"].should == "bar"
+ ENV["foo"] = nil
+ end
+
+ it "rejects entries based on value" do
+ ENV["foo"] = "bar"
+ e = ENV.reject { |k, v| v == "bar" }
+ e["foo"].should == nil
+ ENV["foo"].should == "bar"
+ ENV["foo"] = nil
+ end
+
+ it "returns a Hash" do
+ ENV.reject { false }.should be_kind_of(Hash)
+ end
+
+ it "returns an Enumerator if called without a block" do
+ ENV["foo"] = "bar"
+ enum = ENV.reject
+ enum.should be_an_instance_of(Enumerator)
+ enum.each { |k, v| k == "foo"}
+ ENV["foo"] = nil
+ end
+
+ it "doesn't raise if empty" do
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ -> { ENV.reject }.should_not raise_error(LocalJumpError)
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :reject, ENV
+end
diff --git a/spec/ruby/core/env/replace_spec.rb b/spec/ruby/core/env/replace_spec.rb
new file mode 100644
index 0000000000..9fc67643d1
--- /dev/null
+++ b/spec/ruby/core/env/replace_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+
+describe "ENV.replace" do
+ before :each do
+ @orig = ENV.to_hash
+ ENV.delete("foo")
+ end
+
+ after :each do
+ ENV.replace(@orig)
+ end
+
+ it "replaces ENV with a Hash" do
+ ENV.replace("foo" => "0", "bar" => "1").should equal(ENV)
+ ENV.size.should == 2
+ ENV["foo"].should == "0"
+ ENV["bar"].should == "1"
+ end
+
+ it "raises TypeError if the argument is not a Hash" do
+ -> { ENV.replace(Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into Hash")
+ ENV.to_hash.should == @orig
+ end
+
+ it "raises TypeError if a key is not a String" do
+ -> { ENV.replace(Object.new => "0") }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ ENV.to_hash.should == @orig
+ end
+
+ it "raises TypeError if a value is not a String" do
+ -> { ENV.replace("foo" => Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ ENV.to_hash.should == @orig
+ end
+
+ it "raises Errno::EINVAL when the key contains the '=' character" do
+ -> { ENV.replace("foo=" =>"bar") }.should raise_error(Errno::EINVAL)
+ end
+
+ it "raises Errno::EINVAL when the key is an empty string" do
+ -> { ENV.replace("" => "bar") }.should raise_error(Errno::EINVAL)
+ end
+
+ it "does not accept good data preceding an error" do
+ -> { ENV.replace("foo" => "1", Object.new => Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "does not accept good data following an error" do
+ -> { ENV.replace(Object.new => Object.new, "foo" => "0") }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ ENV.to_hash.should == @orig
+ end
+end
diff --git a/spec/ruby/core/env/select_spec.rb b/spec/ruby/core/env/select_spec.rb
new file mode 100644
index 0000000000..c3a76f4434
--- /dev/null
+++ b/spec/ruby/core/env/select_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+require_relative 'shared/select'
+
+describe "ENV.select!" do
+ it_behaves_like :env_select!, :select!
+ it_behaves_like :enumeratorized_with_origin_size, :select!, ENV
+end
+
+describe "ENV.select" do
+ it_behaves_like :env_select, :select
+ it_behaves_like :enumeratorized_with_origin_size, :select, ENV
+end
diff --git a/spec/ruby/core/env/shared/each.rb b/spec/ruby/core/env/shared/each.rb
new file mode 100644
index 0000000000..d901b854c4
--- /dev/null
+++ b/spec/ruby/core/env/shared/each.rb
@@ -0,0 +1,65 @@
+require_relative '../../enumerable/shared/enumeratorized'
+
+describe :env_each, shared: true do
+ it "returns each pair" do
+ orig = ENV.to_hash
+ e = []
+ begin
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "boo"
+ ENV.send(@method) { |k, v| e << [k, v] }.should equal(ENV)
+ e.should include(["foo", "bar"])
+ e.should include(["baz", "boo"])
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it "returns an Enumerator if called without a block" do
+ enum = ENV.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+ enum.each do |name, value|
+ ENV[name].should == value
+ end
+ end
+
+ before :all do
+ @object = ENV
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+
+ describe "with encoding" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::BINARY
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "uses the locale encoding when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+
+ ENV.send(@method) do |key, value|
+ key.should.be_locale_env
+ value.should.be_locale_env
+ end
+ end
+
+ it "transcodes from the locale encoding to Encoding.default_internal if set" do
+ Encoding.default_internal = internal = Encoding::IBM437
+
+ ENV.send(@method) do |key, value|
+ key.encoding.should equal(internal)
+ if value.ascii_only?
+ value.encoding.should equal(internal)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/env/shared/include.rb b/spec/ruby/core/env/shared/include.rb
new file mode 100644
index 0000000000..3efcd523d6
--- /dev/null
+++ b/spec/ruby/core/env/shared/include.rb
@@ -0,0 +1,23 @@
+describe :env_include, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "returns true if ENV has the key" do
+ ENV["foo"] = "bar"
+ ENV.send(@method, "foo").should == true
+ end
+
+ it "returns false if ENV doesn't include the key" do
+ ENV.delete("foo")
+ ENV.send(@method, "foo").should == false
+ end
+
+ it "raises TypeError if the argument is not a String and does not respond to #to_str" do
+ -> { ENV.send(@method, Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/shared/key.rb b/spec/ruby/core/env/shared/key.rb
new file mode 100644
index 0000000000..93396d2aca
--- /dev/null
+++ b/spec/ruby/core/env/shared/key.rb
@@ -0,0 +1,31 @@
+describe :env_key, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "returns the index associated with the passed value" do
+ ENV["foo"] = "bar"
+ suppress_warning {
+ ENV.send(@method, "bar").should == "foo"
+ }
+ end
+
+ it "returns nil if the passed value is not found" do
+ ENV.delete("foo")
+ suppress_warning {
+ ENV.send(@method, "foo").should be_nil
+ }
+ end
+
+ it "raises TypeError if the argument is not a String and does not respond to #to_str" do
+ -> {
+ suppress_warning {
+ ENV.send(@method, Object.new)
+ }
+ }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/shared/length.rb b/spec/ruby/core/env/shared/length.rb
new file mode 100644
index 0000000000..6d788a3f4a
--- /dev/null
+++ b/spec/ruby/core/env/shared/length.rb
@@ -0,0 +1,13 @@
+describe :env_length, shared: true do
+ it "returns the number of ENV entries" do
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "boo"
+ ENV.send(@method).should == 2
+ ensure
+ ENV.replace orig
+ end
+ end
+end
diff --git a/spec/ruby/core/env/shared/select.rb b/spec/ruby/core/env/shared/select.rb
new file mode 100644
index 0000000000..75ba112a32
--- /dev/null
+++ b/spec/ruby/core/env/shared/select.rb
@@ -0,0 +1,61 @@
+describe :env_select, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "returns a Hash of names and values for which block return true" do
+ ENV["foo"] = "bar"
+ (ENV.send(@method) { |k, v| k == "foo" }).should == { "foo" => "bar" }
+ end
+
+ it "returns an Enumerator when no block is given" do
+ enum = ENV.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+ end
+
+ it "selects via the enumerator" do
+ enum = ENV.send(@method)
+ ENV["foo"] = "bar"
+ enum.each { |k, v| k == "foo" }.should == { "foo" => "bar"}
+ end
+end
+
+describe :env_select!, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "removes environment variables for which the block returns true" do
+ ENV["foo"] = "bar"
+ ENV.send(@method) { |k, v| k != "foo" }
+ ENV["foo"].should == nil
+ end
+
+ it "returns self if any changes were made" do
+ ENV["foo"] = "bar"
+ (ENV.send(@method) { |k, v| k != "foo" }).should == ENV
+ end
+
+ it "returns nil if no changes were made" do
+ (ENV.send(@method) { true }).should == nil
+ end
+
+ it "returns an Enumerator if called without a block" do
+ ENV.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "selects via the enumerator" do
+ enum = ENV.send(@method)
+ ENV["foo"] = "bar"
+ enum.each { |k, v| k != "foo" }
+ ENV["foo"].should == nil
+ end
+end
diff --git a/spec/ruby/core/env/shared/store.rb b/spec/ruby/core/env/shared/store.rb
new file mode 100644
index 0000000000..d6265c66a5
--- /dev/null
+++ b/spec/ruby/core/env/shared/store.rb
@@ -0,0 +1,60 @@
+describe :env_store, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "sets the environment variable to the given value" do
+ ENV.send(@method, "foo", "bar")
+ ENV["foo"].should == "bar"
+ end
+
+ it "returns the value" do
+ value = "bar"
+ ENV.send(@method, "foo", value).should equal(value)
+ end
+
+ it "deletes the environment variable when the value is nil" do
+ ENV["foo"] = "bar"
+ ENV.send(@method, "foo", nil)
+ ENV.key?("foo").should be_false
+ end
+
+ it "coerces the key argument with #to_str" do
+ k = mock("key")
+ k.should_receive(:to_str).and_return("foo")
+ ENV.send(@method, k, "bar")
+ ENV["foo"].should == "bar"
+ end
+
+ it "coerces the value argument with #to_str" do
+ v = mock("value")
+ v.should_receive(:to_str).and_return("bar")
+ ENV.send(@method, "foo", v)
+ ENV["foo"].should == "bar"
+ end
+
+ it "raises TypeError when the key is not coercible to String" do
+ -> { ENV.send(@method, Object.new, "bar") }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises TypeError when the value is not coercible to String" do
+ -> { ENV.send(@method, "foo", Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises Errno::EINVAL when the key contains the '=' character" do
+ -> { ENV.send(@method, "foo=", "bar") }.should raise_error(Errno::EINVAL)
+ end
+
+ it "raises Errno::EINVAL when the key is an empty string" do
+ -> { ENV.send(@method, "", "bar") }.should raise_error(Errno::EINVAL)
+ end
+
+ it "does nothing when the key is not a valid environment variable key and the value is nil" do
+ ENV.send(@method, "foo=", nil)
+ ENV.key?("foo=").should be_false
+ end
+end
diff --git a/spec/ruby/core/env/shared/to_hash.rb b/spec/ruby/core/env/shared/to_hash.rb
new file mode 100644
index 0000000000..a0d4d7ce69
--- /dev/null
+++ b/spec/ruby/core/env/shared/to_hash.rb
@@ -0,0 +1,33 @@
+describe :env_to_hash, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"]= @saved_foo
+ end
+
+ it "returns the ENV as a hash" do
+ ENV["foo"] = "bar"
+ h = ENV.send(@method)
+ h.should be_an_instance_of(Hash)
+ h["foo"].should == "bar"
+ end
+
+ it "uses the locale encoding for keys" do
+ ENV.send(@method).keys.each {|k| k.should.be_locale_env }
+ end
+
+ it "uses the locale encoding for values" do
+ ENV.send(@method).values.each {|k| k.should.be_locale_env }
+ end
+
+ it "duplicates the ENV when converting to a Hash" do
+ h = ENV.send(@method)
+ h.should_not equal ENV
+ h.size.should == ENV.size
+ h.each_pair do |k, v|
+ ENV[k].should == v
+ end
+ end
+end
diff --git a/spec/ruby/core/env/shared/update.rb b/spec/ruby/core/env/shared/update.rb
new file mode 100644
index 0000000000..7d4799955b
--- /dev/null
+++ b/spec/ruby/core/env/shared/update.rb
@@ -0,0 +1,106 @@
+describe :env_update, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ @saved_bar = ENV["bar"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ ENV["bar"] = @saved_bar
+ end
+
+ it "adds the parameter hash to ENV, returning ENV" do
+ ENV.send(@method, "foo" => "0", "bar" => "1").should equal(ENV)
+ ENV["foo"].should == "0"
+ ENV["bar"].should == "1"
+ end
+
+ ruby_version_is "3.2" do
+ it "adds the multiple parameter hashes to ENV, returning ENV" do
+ ENV.send(@method, {"foo" => "multi1"}, {"bar" => "multi2"}).should equal(ENV)
+ ENV["foo"].should == "multi1"
+ ENV["bar"].should == "multi2"
+ end
+ end
+
+ it "returns ENV when no block given" do
+ ENV.send(@method, {"foo" => "0", "bar" => "1"}).should equal(ENV)
+ end
+
+ it "yields key, the old value and the new value when replacing an entry" do
+ ENV.send @method, {"foo" => "0", "bar" => "3"}
+ a = []
+ ENV.send @method, {"foo" => "1", "bar" => "4"} do |key, old, new|
+ a << [key, old, new]
+ new
+ end
+ a[0].should == ["foo", "0", "1"]
+ a[1].should == ["bar", "3", "4"]
+ end
+
+ it "yields key, the old value and the new value when replacing an entry" do
+ ENV.send @method, {"foo" => "0", "bar" => "3"}
+ ENV.send @method, {"foo" => "1", "bar" => "4"} do |key, old, new|
+ (new.to_i + 1).to_s
+ end
+ ENV["foo"].should == "2"
+ ENV["bar"].should == "5"
+ end
+
+ # BUG: https://bugs.ruby-lang.org/issues/16192
+ it "does not evaluate the block when the name is new" do
+ ENV.delete("bar")
+ ENV.send @method, {"foo" => "0"}
+ ENV.send(@method, "bar" => "1") { |key, old, new| fail "Should not get here" }
+ ENV["bar"].should == "1"
+ end
+
+ # BUG: https://bugs.ruby-lang.org/issues/16192
+ it "does not use the block's return value as the value when the name is new" do
+ ENV.delete("bar")
+ ENV.send @method, {"foo" => "0"}
+ ENV.send(@method, "bar" => "1") { |key, old, new| "Should not use this value" }
+ ENV["foo"].should == "0"
+ ENV["bar"].should == "1"
+ end
+
+ it "returns ENV when block given" do
+ ENV.send(@method, {"foo" => "0", "bar" => "1"}){}.should equal(ENV)
+ end
+
+ it "raises TypeError when a name is not coercible to String" do
+ -> { ENV.send @method, Object.new => "0" }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises TypeError when a value is not coercible to String" do
+ -> { ENV.send @method, "foo" => Object.new }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises Errno::EINVAL when a name contains the '=' character" do
+ -> { ENV.send(@method, "foo=" => "bar") }.should raise_error(Errno::EINVAL)
+ end
+
+ it "raises Errno::EINVAL when a name is an empty string" do
+ -> { ENV.send(@method, "" => "bar") }.should raise_error(Errno::EINVAL)
+ end
+
+ it "updates good data preceding an error" do
+ ENV["foo"] = "0"
+ begin
+ ENV.send @method, {"foo" => "2", Object.new => "1"}
+ rescue TypeError
+ ensure
+ ENV["foo"].should == "2"
+ end
+ end
+
+ it "does not update good data following an error" do
+ ENV["foo"] = "0"
+ begin
+ ENV.send @method, {Object.new => "1", "foo" => "2"}
+ rescue TypeError
+ ensure
+ ENV["foo"].should == "0"
+ end
+ end
+end
diff --git a/spec/ruby/core/env/shared/value.rb b/spec/ruby/core/env/shared/value.rb
new file mode 100644
index 0000000000..bef96b5fef
--- /dev/null
+++ b/spec/ruby/core/env/shared/value.rb
@@ -0,0 +1,22 @@
+describe :env_value, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "returns true if ENV has the value" do
+ ENV["foo"] = "bar"
+ ENV.send(@method, "bar").should == true
+ end
+
+ it "returns false if ENV doesn't have the value" do
+ ENV.send(@method, "foo").should == false
+ end
+
+ it "returns nil if the argument is not a String and does not respond to #to_str" do
+ ENV.send(@method, Object.new).should == nil
+ end
+end
diff --git a/spec/ruby/core/env/shift_spec.rb b/spec/ruby/core/env/shift_spec.rb
new file mode 100644
index 0000000000..1b92e5d1e4
--- /dev/null
+++ b/spec/ruby/core/env/shift_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "ENV.shift" do
+ before :each do
+ @orig = ENV.to_hash
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::BINARY
+ ENV.replace({"FOO"=>"BAR"})
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ ENV.replace @orig
+ end
+
+ it "returns a pair and deletes it" do
+ ENV.should.has_key?("FOO")
+ pair = ENV.shift
+ pair.should == ["FOO", "BAR"]
+ ENV.should_not.has_key?("FOO")
+ end
+
+ it "returns nil if ENV.empty?" do
+ ENV.clear
+ ENV.shift.should == nil
+ end
+
+ it "uses the locale encoding if Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+
+ pair = ENV.shift
+ pair.first.encoding.should equal(ENVSpecs.encoding)
+ pair.last.encoding.should equal(ENVSpecs.encoding)
+ end
+
+ it "transcodes from the locale encoding to Encoding.default_internal if set" do
+ Encoding.default_internal = Encoding::IBM437
+
+ pair = ENV.shift
+ pair.first.encoding.should equal(Encoding::IBM437)
+ pair.last.encoding.should equal(Encoding::IBM437)
+ end
+end
diff --git a/spec/ruby/core/env/size_spec.rb b/spec/ruby/core/env/size_spec.rb
new file mode 100644
index 0000000000..f050e9e5a9
--- /dev/null
+++ b/spec/ruby/core/env/size_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+
+describe "ENV.size" do
+ it_behaves_like :env_length, :size
+end
diff --git a/spec/ruby/core/env/slice_spec.rb b/spec/ruby/core/env/slice_spec.rb
new file mode 100644
index 0000000000..e3b6020391
--- /dev/null
+++ b/spec/ruby/core/env/slice_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+describe "ENV.slice" do
+ before :each do
+ @saved_foo = ENV["foo"]
+ @saved_bar = ENV["bar"]
+ ENV["foo"] = "0"
+ ENV["bar"] = "1"
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ ENV["bar"] = @saved_bar
+ end
+
+ it "returns a hash of the given environment variable names and their values" do
+ ENV.slice("foo", "bar").should == {"foo" => "0", "bar" => "1"}
+ end
+
+ it "ignores each String that is not an environment variable name" do
+ ENV.slice("foo", "boo", "bar").should == {"foo" => "0", "bar" => "1"}
+ end
+
+ it "raises TypeError if any argument is not a String and does not respond to #to_str" do
+ -> { ENV.slice(Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/spec_helper.rb b/spec/ruby/core/env/spec_helper.rb
new file mode 100644
index 0000000000..470ffa58bc
--- /dev/null
+++ b/spec/ruby/core/env/spec_helper.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+locale_env_matcher = Class.new do
+ def initialize(name = 'locale')
+ encoding = Encoding.find(name)
+ @encodings = (encoding = Encoding::US_ASCII) ?
+ [encoding, Encoding::ASCII_8BIT] : [encoding]
+ end
+
+ def matches?(actual)
+ @actual = actual = actual.encoding
+ @encodings.include?(actual)
+ end
+
+ def failure_message
+ ["Expected #{@actual} to be #{@encodings.join(' or ')}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual} not to be #{@encodings.join(' or ')}"]
+ end
+end
+
+String.__send__(:define_method, :be_locale_env) do |expected = 'locale'|
+ locale_env_matcher.new(expected)
+end
diff --git a/spec/ruby/core/env/store_spec.rb b/spec/ruby/core/env/store_spec.rb
new file mode 100644
index 0000000000..b4700e0a96
--- /dev/null
+++ b/spec/ruby/core/env/store_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/store'
+
+describe "ENV.store" do
+ it_behaves_like :env_store, :store
+end
diff --git a/spec/ruby/core/env/to_a_spec.rb b/spec/ruby/core/env/to_a_spec.rb
new file mode 100644
index 0000000000..39e3877b48
--- /dev/null
+++ b/spec/ruby/core/env/to_a_spec.rb
@@ -0,0 +1,18 @@
+require_relative 'spec_helper'
+
+describe "ENV.to_a" do
+
+ it "returns the ENV as an array" do
+ a = ENV.to_a
+ a.is_a?(Array).should == true
+ a.size.should == ENV.size
+ ENV.each_pair { |k, v| a.should include([k, v])}
+ end
+
+ it "returns the entries in the locale encoding" do
+ ENV.to_a.each do |key, value|
+ key.should.be_locale_env
+ value.should.be_locale_env
+ end
+ end
+end
diff --git a/spec/ruby/core/env/to_h_spec.rb b/spec/ruby/core/env/to_h_spec.rb
new file mode 100644
index 0000000000..3c4a92aa57
--- /dev/null
+++ b/spec/ruby/core/env/to_h_spec.rb
@@ -0,0 +1,58 @@
+require_relative 'spec_helper'
+require_relative 'shared/to_hash'
+
+describe "ENV.to_h" do
+ it_behaves_like :env_to_hash, :to_h
+
+ context "with block" do
+ before do
+ @orig_hash = ENV.to_hash
+ end
+
+ after do
+ ENV.replace @orig_hash
+ end
+
+ it "converts [key, value] pairs returned by the block to a hash" do
+ ENV.replace("a" => "b", "c" => "d")
+ ENV.to_h { |k, v| [k, v.upcase] }.should == { 'a' => "B", 'c' => "D" }
+ end
+
+ it "does not require the array elements to be strings" do
+ ENV.replace("a" => "b", "c" => "d")
+ ENV.to_h { |k, v| [k.to_sym, v.to_sym] }.should == { :a => :b, :c => :d }
+ end
+
+ it "raises ArgumentError if block returns longer or shorter array" do
+ -> do
+ ENV.to_h { |k, v| [k, v.upcase, 1] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+
+ -> do
+ ENV.to_h { |k, v| [k] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ ENV.to_h { |k, v| "not-array" }
+ end.should raise_error(TypeError, /wrong element type String/)
+ end
+
+ it "coerces returned pair to Array with #to_ary" do
+ x = mock('x')
+ x.stub!(:to_ary).and_return([:b, 'b'])
+
+ ENV.to_h { |k| x }.should == { :b => 'b' }
+ end
+
+ it "does not coerce returned pair to Array with #to_a" do
+ x = mock('x')
+ x.stub!(:to_a).and_return([:b, 'b'])
+
+ -> do
+ ENV.to_h { |k| x }
+ end.should raise_error(TypeError, /wrong element type MockObject/)
+ end
+ end
+end
diff --git a/spec/ruby/core/env/to_hash_spec.rb b/spec/ruby/core/env/to_hash_spec.rb
new file mode 100644
index 0000000000..306572c353
--- /dev/null
+++ b/spec/ruby/core/env/to_hash_spec.rb
@@ -0,0 +1,6 @@
+require_relative 'spec_helper'
+require_relative 'shared/to_hash'
+
+describe "ENV.to_hash" do
+ it_behaves_like :env_to_hash, :to_hash
+end
diff --git a/spec/ruby/core/env/to_s_spec.rb b/spec/ruby/core/env/to_s_spec.rb
new file mode 100644
index 0000000000..0bd92cf217
--- /dev/null
+++ b/spec/ruby/core/env/to_s_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "ENV.to_s" do
+ it "returns \"ENV\"" do
+ ENV.to_s.should == "ENV"
+ end
+end
diff --git a/spec/ruby/core/env/update_spec.rb b/spec/ruby/core/env/update_spec.rb
new file mode 100644
index 0000000000..95a8a2eb49
--- /dev/null
+++ b/spec/ruby/core/env/update_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/update'
+
+describe "ENV.update" do
+ it_behaves_like :env_update, :update
+end
diff --git a/spec/ruby/core/env/value_spec.rb b/spec/ruby/core/env/value_spec.rb
new file mode 100644
index 0000000000..906e86ab39
--- /dev/null
+++ b/spec/ruby/core/env/value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/value'
+
+describe "ENV.value?" do
+ it_behaves_like :env_value, :value?
+end
diff --git a/spec/ruby/core/env/values_at_spec.rb b/spec/ruby/core/env/values_at_spec.rb
new file mode 100644
index 0000000000..338680e820
--- /dev/null
+++ b/spec/ruby/core/env/values_at_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "ENV.values_at" do
+ before :each do
+ @saved_foo = ENV["foo"]
+ @saved_bar = ENV["bar"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ ENV["bar"] = @saved_bar
+ end
+
+ it "returns an array of the values corresponding to the given keys" do
+ ENV["foo"] = "oof"
+ ENV["bar"] = "rab"
+ ENV.values_at("bar", "foo").should == ["rab", "oof"]
+ end
+
+ it "returns an empty array if no keys specified" do
+ ENV.values_at.should == []
+ end
+
+ it "returns nil for each key that is not a name" do
+ ENV["foo"] = "oof"
+ ENV["bar"] = "rab"
+ ENV.values_at("x", "bar", "y", "foo", "z").should == [nil, "rab", nil, "oof", nil]
+ end
+
+ it "uses the locale encoding" do
+ ENV.values_at(ENV.keys.first).first.encoding.should == ENVSpecs.encoding
+ end
+
+ it "raises TypeError when a key is not coercible to String" do
+ -> { ENV.values_at("foo", Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/values_spec.rb b/spec/ruby/core/env/values_spec.rb
new file mode 100644
index 0000000000..71bc877d31
--- /dev/null
+++ b/spec/ruby/core/env/values_spec.rb
@@ -0,0 +1,14 @@
+require_relative 'spec_helper'
+
+describe "ENV.values" do
+
+ it "returns an array of the values" do
+ ENV.values.should == ENV.to_hash.values
+ end
+
+ it "uses the locale encoding" do
+ ENV.values.each do |value|
+ value.should.be_locale_env
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/backtrace_locations_spec.rb b/spec/ruby/core/exception/backtrace_locations_spec.rb
new file mode 100644
index 0000000000..86eb9d3413
--- /dev/null
+++ b/spec/ruby/core/exception/backtrace_locations_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#backtrace_locations" do
+ before :each do
+ @backtrace = ExceptionSpecs::Backtrace.backtrace_locations
+ end
+
+ it "returns nil if no backtrace was set" do
+ Exception.new.backtrace_locations.should be_nil
+ end
+
+ it "returns an Array" do
+ @backtrace.should be_an_instance_of(Array)
+ end
+
+ it "sets each element to a Thread::Backtrace::Location" do
+ @backtrace.each {|l| l.should be_an_instance_of(Thread::Backtrace::Location)}
+ end
+
+ it "produces a backtrace for an exception captured using $!" do
+ exception = begin
+ raise
+ rescue RuntimeError
+ $!
+ end
+
+ exception.backtrace_locations.first.path.should =~ /backtrace_locations_spec/
+ end
+
+ it "returns an Array that can be updated" do
+ begin
+ raise
+ rescue RuntimeError => e
+ e.backtrace_locations.unshift "backtrace first"
+ e.backtrace_locations[0].should == "backtrace first"
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/backtrace_spec.rb b/spec/ruby/core/exception/backtrace_spec.rb
new file mode 100644
index 0000000000..3f74c4cefe
--- /dev/null
+++ b/spec/ruby/core/exception/backtrace_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#backtrace" do
+ before :each do
+ @backtrace = ExceptionSpecs::Backtrace.backtrace
+ end
+
+ it "returns nil if no backtrace was set" do
+ Exception.new.backtrace.should be_nil
+ end
+
+ it "returns an Array" do
+ @backtrace.should be_an_instance_of(Array)
+ end
+
+ it "sets each element to a String" do
+ @backtrace.each {|l| l.should be_an_instance_of(String)}
+ end
+
+ it "includes the filename of the location where self raised in the first element" do
+ @backtrace.first.should =~ /common\.rb/
+ end
+
+ it "includes the line number of the location where self raised in the first element" do
+ @backtrace.first.should =~ /:7:in /
+ end
+
+ it "includes the name of the method from where self raised in the first element" do
+ @backtrace.first.should =~ /in `backtrace'/
+ end
+
+ it "includes the filename of the location immediately prior to where self raised in the second element" do
+ @backtrace[1].should =~ /backtrace_spec\.rb/
+ end
+
+ it "includes the line number of the location immediately prior to where self raised in the second element" do
+ @backtrace[1].should =~ /:6(:in )?/
+ end
+
+ it "contains lines of the same format for each prior position in the stack" do
+ @backtrace[2..-1].each do |line|
+ # This regexp is deliberately imprecise to account for the need to abstract out
+ # the paths of the included mspec files and the desire to avoid specifying in any
+ # detail what the in `...' portion looks like.
+ line.should =~ /^.+:\d+:in `[^`]+'$/
+ end
+ end
+
+ it "captures the backtrace for an exception into $!" do
+ exception = begin
+ raise
+ rescue RuntimeError
+ $!
+ end
+
+ exception.backtrace.first.should =~ /backtrace_spec/
+ end
+
+ it "captures the backtrace for an exception into $@" do
+ backtrace = begin
+ raise
+ rescue RuntimeError
+ $@
+ end
+
+ backtrace.first.should =~ /backtrace_spec/
+ end
+
+ it "returns an Array that can be updated" do
+ begin
+ raise
+ rescue RuntimeError => e
+ e.backtrace.unshift "backtrace first"
+ e.backtrace[0].should == "backtrace first"
+ end
+ end
+
+ it "returns the same array after duping" do
+ begin
+ raise
+ rescue RuntimeError => err
+ bt = err.backtrace
+ err.dup.backtrace.should equal(bt)
+
+ new_bt = ['hi']
+ err.set_backtrace new_bt
+
+ err.backtrace.should == new_bt
+ err.dup.backtrace.should equal(new_bt)
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/case_compare_spec.rb b/spec/ruby/core/exception/case_compare_spec.rb
new file mode 100644
index 0000000000..87b9dee3ca
--- /dev/null
+++ b/spec/ruby/core/exception/case_compare_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "SystemCallError.===" do
+ before :all do
+ @example_errno_class = Errno::EINVAL
+ @example_errno = @example_errno_class::Errno
+ end
+
+ it "returns true for an instance of the same class" do
+ Errno::EINVAL.should === Errno::EINVAL.new
+ end
+
+ it "returns true if errnos same" do
+ e = SystemCallError.new('foo', @example_errno)
+ @example_errno_class.===(e).should == true
+ end
+
+ it "returns false if errnos different" do
+ e = SystemCallError.new('foo', @example_errno + 1)
+ @example_errno_class.===(e).should == false
+ end
+
+ it "returns false if arg is not kind of SystemCallError" do
+ e = Object.new
+ @example_errno_class.===(e).should == false
+ end
+
+ it "returns true if receiver is generic and arg is kind of SystemCallError" do
+ unknown_error_number = Errno.constants.size
+ e = SystemCallError.new('foo', @example_errno)
+ SystemCallError.===(e).should == true
+ end
+
+ it "returns false if receiver is generic and arg is not kind of SystemCallError" do
+ unknown_error_number = Errno.constants.size
+ e = Object.new
+ SystemCallError.===(e).should == false
+ end
+end
diff --git a/spec/ruby/core/exception/cause_spec.rb b/spec/ruby/core/exception/cause_spec.rb
new file mode 100644
index 0000000000..cf4aaeb188
--- /dev/null
+++ b/spec/ruby/core/exception/cause_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+
+describe "Exception#cause" do
+ it "returns the active exception when an exception is raised" do
+ begin
+ raise Exception, "the cause"
+ rescue Exception
+ begin
+ raise RuntimeError, "the consequence"
+ rescue RuntimeError => e
+ e.should be_an_instance_of(RuntimeError)
+ e.message.should == "the consequence"
+
+ e.cause.should be_an_instance_of(Exception)
+ e.cause.message.should == "the cause"
+ end
+ end
+ end
+
+ it "is set for user errors caused by internal errors" do
+ -> {
+ begin
+ 1 / 0
+ rescue
+ raise "foo"
+ end
+ }.should raise_error(RuntimeError) { |e|
+ e.cause.should be_kind_of(ZeroDivisionError)
+ }
+ end
+
+ it "is set for internal errors caused by user errors" do
+ cause = RuntimeError.new "cause"
+ -> {
+ begin
+ raise cause
+ rescue
+ 1 / 0
+ end
+ }.should raise_error(ZeroDivisionError) { |e|
+ e.cause.should equal(cause)
+ }
+ end
+
+ it "is not set to the exception itself when it is re-raised" do
+ -> {
+ begin
+ raise RuntimeError
+ rescue RuntimeError => e
+ raise e
+ end
+ }.should raise_error(RuntimeError) { |e|
+ e.cause.should == nil
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/dup_spec.rb b/spec/ruby/core/exception/dup_spec.rb
new file mode 100644
index 0000000000..edd54bfb37
--- /dev/null
+++ b/spec/ruby/core/exception/dup_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#dup" do
+ before :each do
+ @obj = ExceptionSpecs::InitializeException.new("my exception")
+ end
+
+ it "calls #initialize_copy on the new instance" do
+ dup = @obj.dup
+ ScratchPad.recorded.should_not == @obj.object_id
+ ScratchPad.recorded.should == dup.object_id
+ end
+
+ it "copies instance variables" do
+ dup = @obj.dup
+ dup.ivar.should == 1
+ end
+
+ it "does not copy singleton methods" do
+ def @obj.special() :the_one end
+ dup = @obj.dup
+ -> { dup.special }.should raise_error(NameError)
+ end
+
+ it "does not copy modules included in the singleton class" do
+ class << @obj
+ include ExceptionSpecs::ExceptionModule
+ end
+
+ dup = @obj.dup
+ -> { dup.repr }.should raise_error(NameError)
+ end
+
+ it "does not copy constants defined in the singleton class" do
+ class << @obj
+ CLONE = :clone
+ end
+
+ dup = @obj.dup
+ -> { class << dup; CLONE; end }.should raise_error(NameError)
+ end
+
+ it "does copy the message" do
+ @obj.dup.message.should == @obj.message
+ end
+
+ it "does copy the backtrace" do
+ begin
+ # Explicitly raise so a backtrace is associated with the exception.
+ # It's tempting to call `set_backtrace` instead, but that complicates
+ # the test because it might affect other state (e.g., instance variables)
+ # on some implementations.
+ raise ExceptionSpecs::InitializeException.new("my exception")
+ rescue => e
+ @obj = e
+ end
+
+ @obj.dup.backtrace.should == @obj.backtrace
+ end
+
+ it "does copy the cause" do
+ begin
+ raise StandardError, "the cause"
+ rescue StandardError => cause
+ begin
+ raise RuntimeError, "the consequence"
+ rescue RuntimeError => e
+ e.cause.should equal(cause)
+ e.dup.cause.should equal(cause)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/equal_value_spec.rb b/spec/ruby/core/exception/equal_value_spec.rb
new file mode 100644
index 0000000000..7f2065511a
--- /dev/null
+++ b/spec/ruby/core/exception/equal_value_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#==" do
+ it "returns true if both exceptions are the same object" do
+ e = ArgumentError.new
+ e.should == e
+ end
+
+ it "returns true if one exception is the dup'd copy of the other" do
+ e = ArgumentError.new
+ e.should == e.dup
+ end
+
+ it "returns true if both exceptions have the same class, no message, and no backtrace" do
+ RuntimeError.new.should == RuntimeError.new
+ end
+
+ it "returns true if both exceptions have the same class, the same message, and no backtrace" do
+ TypeError.new("message").should == TypeError.new("message")
+ end
+
+ it "returns true if both exceptions have the same class, the same message, and the same backtrace" do
+ one = TypeError.new("message")
+ one.set_backtrace [File.dirname(__FILE__)]
+ two = TypeError.new("message")
+ two.set_backtrace [File.dirname(__FILE__)]
+ one.should == two
+ end
+
+ it "returns false if the two exceptions inherit from Exception but have different classes" do
+ one = RuntimeError.new("message")
+ one.set_backtrace [File.dirname(__FILE__)]
+ one.should be_kind_of(Exception)
+ two = TypeError.new("message")
+ two.set_backtrace [File.dirname(__FILE__)]
+ two.should be_kind_of(Exception)
+ one.should_not == two
+ end
+
+ it "returns true if the two objects subclass Exception and have the same message and backtrace" do
+ one = ExceptionSpecs::UnExceptional.new
+ two = ExceptionSpecs::UnExceptional.new
+ one.message.should == two.message
+ two.backtrace.should == two.backtrace
+ one.should == two
+ end
+
+ it "returns false if the argument is not an Exception" do
+ ArgumentError.new.should_not == String.new
+ end
+
+ it "returns false if the two exceptions differ only in their backtrace" do
+ one = RuntimeError.new("message")
+ one.set_backtrace [File.dirname(__FILE__)]
+ two = RuntimeError.new("message")
+ two.set_backtrace nil
+ one.should_not == two
+ end
+
+ it "returns false if the two exceptions differ only in their message" do
+ one = RuntimeError.new("message")
+ one.set_backtrace [File.dirname(__FILE__)]
+ two = RuntimeError.new("message2")
+ two.set_backtrace [File.dirname(__FILE__)]
+ one.should_not == two
+ end
+end
diff --git a/spec/ruby/core/exception/errno_spec.rb b/spec/ruby/core/exception/errno_spec.rb
new file mode 100644
index 0000000000..a063e522ea
--- /dev/null
+++ b/spec/ruby/core/exception/errno_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Errno::EINVAL.new" do
+ it "can be called with no arguments" do
+ exc = Errno::EINVAL.new
+ exc.should be_an_instance_of(Errno::EINVAL)
+ exc.errno.should == Errno::EINVAL::Errno
+ exc.message.should == "Invalid argument"
+ end
+
+ it "accepts an optional custom message" do
+ exc = Errno::EINVAL.new('custom message')
+ exc.should be_an_instance_of(Errno::EINVAL)
+ exc.errno.should == Errno::EINVAL::Errno
+ exc.message.should == "Invalid argument - custom message"
+ end
+
+ it "accepts an optional custom message and location" do
+ exc = Errno::EINVAL.new('custom message', 'location')
+ exc.should be_an_instance_of(Errno::EINVAL)
+ exc.errno.should == Errno::EINVAL::Errno
+ exc.message.should == "Invalid argument @ location - custom message"
+ end
+end
+
+describe "Errno::EMFILE" do
+ it "can be subclassed" do
+ ExceptionSpecs::EMFILESub = Class.new(Errno::EMFILE)
+ exc = ExceptionSpecs::EMFILESub.new
+ exc.should be_an_instance_of(ExceptionSpecs::EMFILESub)
+ end
+end
+
+describe "Errno::EAGAIN" do
+ # From http://jira.codehaus.org/browse/JRUBY-4747
+ it "is the same class as Errno::EWOULDBLOCK if they represent the same errno value" do
+ if Errno::EAGAIN::Errno == Errno::EWOULDBLOCK::Errno
+ Errno::EAGAIN.should == Errno::EWOULDBLOCK
+ else
+ Errno::EAGAIN.should_not == Errno::EWOULDBLOCK
+ end
+ end
+end
+
+describe "Errno::ENOTSUP" do
+ it "is defined" do
+ Errno.should have_constant(:ENOTSUP)
+ end
+
+ it "is the same class as Errno::EOPNOTSUPP if they represent the same errno value" do
+ if Errno::ENOTSUP::Errno == Errno::EOPNOTSUPP::Errno
+ Errno::ENOTSUP.should == Errno::EOPNOTSUPP
+ else
+ Errno::ENOTSUP.should_not == Errno::EOPNOTSUPP
+ end
+ end
+end
+
+describe "Errno::ENOENT" do
+ it "lets subclasses inherit the default error message" do
+ c = Class.new(Errno::ENOENT)
+ raise c, "custom message"
+ rescue => e
+ e.message.should == "No such file or directory - custom message"
+ end
+end
diff --git a/spec/ruby/core/exception/exception_spec.rb b/spec/ruby/core/exception/exception_spec.rb
new file mode 100644
index 0000000000..d6f5283bd9
--- /dev/null
+++ b/spec/ruby/core/exception/exception_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/new'
+
+describe "Exception.exception" do
+ it_behaves_like :exception_new, :exception
+end
+
+describe "Exception#exception" do
+ it "returns self when passed no argument" do
+ e = RuntimeError.new
+ e.should == e.exception
+ end
+
+ it "returns self when passed self as an argument" do
+ e = RuntimeError.new
+ e.should == e.exception(e)
+ end
+
+ it "returns an exception of the same class as self with the message given as argument" do
+ e = RuntimeError.new
+ e2 = e.exception("message")
+ e2.should be_an_instance_of(RuntimeError)
+ e2.message.should == "message"
+ end
+
+ it "when raised will be rescued as the new exception" do
+ begin
+ begin
+ raised_first = StandardError.new('first')
+ raise raised_first
+ rescue => caught_first
+ raised_second = raised_first.exception('second')
+ raise raised_second
+ end
+ rescue => caught_second
+ end
+
+ raised_first.should == caught_first
+ raised_second.should == caught_second
+ end
+
+ it "captures an exception into $!" do
+ exception = begin
+ raise
+ rescue RuntimeError
+ $!
+ end
+
+ exception.class.should == RuntimeError
+ exception.message.should == ""
+ exception.backtrace.first.should =~ /exception_spec/
+ end
+
+ class CustomArgumentError < StandardError
+ attr_reader :val
+ def initialize(val)
+ @val = val
+ end
+ end
+
+ it "returns an exception of the same class as self with the message given as argument, but without reinitializing" do
+ e = CustomArgumentError.new(:boom)
+ e2 = e.exception("message")
+ e2.should be_an_instance_of(CustomArgumentError)
+ e2.val.should == :boom
+ e2.message.should == "message"
+ end
+end
diff --git a/spec/ruby/core/exception/exit_value_spec.rb b/spec/ruby/core/exception/exit_value_spec.rb
new file mode 100644
index 0000000000..99987dd1bc
--- /dev/null
+++ b/spec/ruby/core/exception/exit_value_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "LocalJumpError#exit_value" do
+ def get_me_a_return
+ Proc.new { return 42 }
+ end
+
+ it "returns the value given to return" do
+ -> { get_me_a_return.call }.should raise_error(LocalJumpError) { |e|
+ e.exit_value.should == 42
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/fixtures/common.rb b/spec/ruby/core/exception/fixtures/common.rb
new file mode 100644
index 0000000000..0ffb3ed855
--- /dev/null
+++ b/spec/ruby/core/exception/fixtures/common.rb
@@ -0,0 +1,95 @@
+module ExceptionSpecs
+ class Exceptional < Exception; end
+
+ class Backtrace
+ def self.backtrace
+ begin
+ raise # If you move this line, update backtrace_spec.rb
+ rescue RuntimeError => e
+ e.backtrace
+ end
+ end
+
+ def self.backtrace_locations
+ begin
+ raise
+ rescue RuntimeError => e
+ e.backtrace_locations
+ end
+ end
+ end
+
+ class UnExceptional < Exception
+ def backtrace
+ nil
+ end
+ def message
+ nil
+ end
+ end
+
+ class ConstructorException < Exception
+
+ def initialize
+ end
+
+ end
+
+ class OverrideToS < RuntimeError
+ def to_s
+ "this is from #to_s"
+ end
+ end
+
+ class EmptyToS < RuntimeError
+ def to_s
+ ""
+ end
+ end
+
+ class InitializeException < StandardError
+ attr_reader :ivar
+
+ def initialize(message = nil)
+ super
+ @ivar = 1
+ end
+
+ def initialize_copy(other)
+ super
+ ScratchPad.record object_id
+ end
+ end
+
+ module ExceptionModule
+ def repr
+ 1
+ end
+ end
+end
+
+module NoMethodErrorSpecs
+ class NoMethodErrorA; end
+
+ class NoMethodErrorB; end
+
+ class NoMethodErrorC;
+ protected
+ def a_protected_method;end
+ private
+ def a_private_method; end
+ end
+
+ class NoMethodErrorD; end
+
+ class InstanceException < Exception
+ end
+end
+
+class NameErrorSpecs
+ class ReceiverClass
+ def call_undefined_class_variable
+ @@doesnt_exist
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/frozen_error_spec.rb b/spec/ruby/core/exception/frozen_error_spec.rb
new file mode 100644
index 0000000000..2efdc239d8
--- /dev/null
+++ b/spec/ruby/core/exception/frozen_error_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+describe "FrozenError.new" do
+ it "should take optional receiver argument" do
+ o = Object.new
+ FrozenError.new("msg", receiver: o).receiver.should equal(o)
+ end
+end
+
+describe "FrozenError#receiver" do
+ it "should return frozen object that modification was attempted on" do
+ o = Object.new.freeze
+ begin
+ def o.x; end
+ rescue => e
+ e.should be_kind_of(FrozenError)
+ e.receiver.should equal(o)
+ else
+ raise
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/full_message_spec.rb b/spec/ruby/core/exception/full_message_spec.rb
new file mode 100644
index 0000000000..9757a2f407
--- /dev/null
+++ b/spec/ruby/core/exception/full_message_spec.rb
@@ -0,0 +1,106 @@
+require_relative '../../spec_helper'
+
+describe "Exception#full_message" do
+ it "returns formatted string of exception using the same format that is used to print an uncaught exceptions to stderr" do
+ e = RuntimeError.new("Some runtime error")
+ e.set_backtrace(["a.rb:1", "b.rb:2"])
+
+ full_message = e.full_message
+ full_message.should include "RuntimeError"
+ full_message.should include "Some runtime error"
+ full_message.should include "a.rb:1"
+ full_message.should include "b.rb:2"
+ end
+
+ it "supports :highlight option and adds escape sequences to highlight some strings" do
+ e = RuntimeError.new("Some runtime error")
+
+ full_message = e.full_message(highlight: true, order: :top).lines
+ full_message[0].should.end_with? "\e[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n"
+
+ full_message = e.full_message(highlight: true, order: :bottom).lines
+ full_message[0].should == "\e[1mTraceback\e[m (most recent call last):\n"
+ full_message[-1].should.end_with? "\e[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n"
+
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.end_with? "Some runtime error (RuntimeError)\n"
+
+ full_message = e.full_message(highlight: false, order: :bottom).lines
+ full_message[0].should == "Traceback (most recent call last):\n"
+ full_message[-1].should.end_with? "Some runtime error (RuntimeError)\n"
+ end
+
+ it "supports :order option and places the error message and the backtrace at the top or the bottom" do
+ e = RuntimeError.new("Some runtime error")
+ e.set_backtrace(["a.rb:1", "b.rb:2"])
+
+ e.full_message(order: :top, highlight: false).should =~ /a.rb:1.*b.rb:2/m
+ e.full_message(order: :bottom, highlight: false).should =~ /b.rb:2.*a.rb:1/m
+ end
+
+ it "shows the caller if the exception has no backtrace" do
+ e = RuntimeError.new("Some runtime error")
+ e.backtrace.should == nil
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.start_with?("#{__FILE__}:#{__LINE__-1}:in `")
+ full_message[0].should.end_with?("': Some runtime error (RuntimeError)\n")
+ end
+
+ it "shows the exception class at the end of the first line of the message when the message contains multiple lines" do
+ begin
+ line = __LINE__; raise "first line\nsecond line"
+ rescue => e
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.start_with?("#{__FILE__}:#{line}:in `")
+ full_message[0].should.end_with?(": first line (RuntimeError)\n")
+ full_message[1].should == "second line\n"
+ end
+ end
+
+ it "highlights the entire message when the message contains multiple lines" do
+ begin
+ line = __LINE__; raise "first line\nsecond line\nthird line"
+ rescue => e
+ full_message = e.full_message(highlight: true, order: :top).lines
+ full_message[0].should.start_with?("#{__FILE__}:#{line}:in `")
+ full_message[0].should.end_with?(": \e[1mfirst line (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n")
+ full_message[1].should == "\e[1msecond line\e[m\n"
+ full_message[2].should == "\e[1mthird line\e[m\n"
+ end
+ end
+
+ it "contains cause of exception" do
+ begin
+ begin
+ raise 'the cause'
+ rescue
+ raise 'main exception'
+ end
+ rescue => e
+ exception = e
+ end
+
+ exception.full_message.should include "main exception"
+ exception.full_message.should include "the cause"
+ end
+
+ it 'contains all the chain of exceptions' do
+ begin
+ begin
+ begin
+ raise 'origin exception'
+ rescue
+ raise 'intermediate exception'
+ end
+ rescue
+ raise 'last exception'
+ end
+ rescue => e
+ exception = e
+ end
+
+ exception.full_message.should include "last exception"
+ exception.full_message.should include "intermediate exception"
+ exception.full_message.should include "origin exception"
+ end
+end
diff --git a/spec/ruby/core/exception/hierarchy_spec.rb b/spec/ruby/core/exception/hierarchy_spec.rb
new file mode 100644
index 0000000000..6514eb1994
--- /dev/null
+++ b/spec/ruby/core/exception/hierarchy_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+
+describe "Exception" do
+ it "has the right class hierarchy" do
+ hierarchy = {
+ Exception => {
+ NoMemoryError => nil,
+ ScriptError => {
+ LoadError => nil,
+ NotImplementedError => nil,
+ SyntaxError => nil,
+ },
+ SecurityError => nil,
+ SignalException => {
+ Interrupt => nil,
+ },
+ StandardError => {
+ ArgumentError => {
+ UncaughtThrowError => nil,
+ },
+ EncodingError => nil,
+ FiberError => nil,
+ IOError => {
+ EOFError => nil,
+ },
+ IndexError => {
+ KeyError => nil,
+ StopIteration => {
+ ClosedQueueError => nil,
+ },
+ },
+ LocalJumpError => nil,
+ NameError => {
+ NoMethodError => nil,
+ },
+ RangeError => {
+ FloatDomainError => nil,
+ },
+ RegexpError => nil,
+ RuntimeError => {
+ FrozenError => nil,
+ },
+ SystemCallError => nil,
+ ThreadError => nil,
+ TypeError => nil,
+ ZeroDivisionError => nil,
+ },
+ SystemExit => nil,
+ SystemStackError => nil,
+ },
+ }
+
+ traverse = -> parent_class, parent_subclass_hash {
+ parent_subclass_hash.each do |child_class, child_subclass_hash|
+ child_class.class.should == Class
+ child_class.superclass.should == parent_class
+ traverse.call(child_class, child_subclass_hash) if child_subclass_hash
+ end
+ }
+ traverse.call(Object, hierarchy)
+ end
+end
diff --git a/spec/ruby/core/exception/inspect_spec.rb b/spec/ruby/core/exception/inspect_spec.rb
new file mode 100644
index 0000000000..6f380a36c7
--- /dev/null
+++ b/spec/ruby/core/exception/inspect_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#inspect" do
+ it "returns '#<Exception: Exception>' when no message given" do
+ Exception.new.inspect.should == "#<Exception: Exception>"
+ end
+
+ it "keeps message encoding" do
+ Exception.new('å').inspect.should == "#<Exception: å>"
+ end
+
+ it "includes #to_s when the result is non-empty" do
+ ExceptionSpecs::OverrideToS.new.inspect.should == "#<ExceptionSpecs::OverrideToS: this is from #to_s>"
+ end
+
+ it "returns the class name when #to_s returns an empty string" do
+ ExceptionSpecs::EmptyToS.new.inspect.should == "ExceptionSpecs::EmptyToS"
+ end
+
+ it "returns the derived class name with a subclassed Exception" do
+ ExceptionSpecs::UnExceptional.new.inspect.should == "#<ExceptionSpecs::UnExceptional: ExceptionSpecs::UnExceptional>"
+ end
+end
diff --git a/spec/ruby/core/exception/interrupt_spec.rb b/spec/ruby/core/exception/interrupt_spec.rb
new file mode 100644
index 0000000000..299b5b81f3
--- /dev/null
+++ b/spec/ruby/core/exception/interrupt_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+
+describe "Interrupt.new" do
+ it "returns an instance of interrupt with no message given" do
+ e = Interrupt.new
+ e.signo.should == Signal.list["INT"]
+ e.signm.should == "Interrupt"
+ end
+
+ it "takes an optional message argument" do
+ e = Interrupt.new("message")
+ e.signo.should == Signal.list["INT"]
+ e.signm.should == "message"
+ end
+end
+
+describe "rescuing Interrupt" do
+ before do
+ @original_sigint_proc = Signal.trap(:INT, :SIG_DFL)
+ end
+
+ after do
+ Signal.trap(:INT, @original_sigint_proc)
+ end
+
+ it "raises an Interrupt when sent a signal SIGINT" do
+ begin
+ Process.kill :INT, Process.pid
+ sleep
+ rescue Interrupt => e
+ e.signo.should == Signal.list["INT"]
+ ["", "Interrupt"].should.include?(e.message)
+ end
+ end
+end
+
+describe "Interrupt" do
+ # This spec is basically the same as above,
+ # but it does not rely on Signal.trap(:INT, :SIG_DFL) which can be tricky
+ it "is raised on the main Thread by the default SIGINT handler" do
+ out = ruby_exe(<<-'RUBY', args: "2>&1")
+ begin
+ Process.kill :INT, Process.pid
+ sleep
+ rescue Interrupt => e
+ puts "Interrupt: #{e.signo}"
+ end
+ RUBY
+ out.should == "Interrupt: #{Signal.list["INT"]}\n"
+ end
+
+ platform_is_not :windows do
+ it "shows the backtrace and has a signaled exit status" do
+ err = IO.popen([*ruby_exe, '-e', 'Process.kill :INT, Process.pid; sleep'], err: [:child, :out], &:read)
+ $?.termsig.should == Signal.list.fetch('INT')
+ err.should.include? ': Interrupt'
+ err.should.include? "from -e:1:in `<main>'"
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/io_error_spec.rb b/spec/ruby/core/exception/io_error_spec.rb
new file mode 100644
index 0000000000..ab8a72518f
--- /dev/null
+++ b/spec/ruby/core/exception/io_error_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "IO::EAGAINWaitReadable" do
+ it "combines Errno::EAGAIN and IO::WaitReadable" do
+ IO::EAGAINWaitReadable.superclass.should == Errno::EAGAIN
+ IO::EAGAINWaitReadable.ancestors.should include IO::WaitReadable
+ end
+
+ it "is the same as IO::EWOULDBLOCKWaitReadable if Errno::EAGAIN is the same as Errno::EWOULDBLOCK" do
+ if Errno::EAGAIN.equal? Errno::EWOULDBLOCK
+ IO::EAGAINWaitReadable.should equal IO::EWOULDBLOCKWaitReadable
+ else
+ IO::EAGAINWaitReadable.should_not equal IO::EWOULDBLOCKWaitReadable
+ end
+ end
+end
+
+describe "IO::EWOULDBLOCKWaitReadable" do
+ it "combines Errno::EWOULDBLOCK and IO::WaitReadable" do
+ IO::EWOULDBLOCKWaitReadable.superclass.should == Errno::EWOULDBLOCK
+ IO::EAGAINWaitReadable.ancestors.should include IO::WaitReadable
+ end
+end
+
+describe "IO::EAGAINWaitWritable" do
+ it "combines Errno::EAGAIN and IO::WaitWritable" do
+ IO::EAGAINWaitWritable.superclass.should == Errno::EAGAIN
+ IO::EAGAINWaitWritable.ancestors.should include IO::WaitWritable
+ end
+
+ it "is the same as IO::EWOULDBLOCKWaitWritable if Errno::EAGAIN is the same as Errno::EWOULDBLOCK" do
+ if Errno::EAGAIN.equal? Errno::EWOULDBLOCK
+ IO::EAGAINWaitWritable.should equal IO::EWOULDBLOCKWaitWritable
+ else
+ IO::EAGAINWaitWritable.should_not equal IO::EWOULDBLOCKWaitWritable
+ end
+ end
+end
+
+describe "IO::EWOULDBLOCKWaitWritable" do
+ it "combines Errno::EWOULDBLOCK and IO::WaitWritable" do
+ IO::EWOULDBLOCKWaitWritable.superclass.should == Errno::EWOULDBLOCK
+ IO::EAGAINWaitWritable.ancestors.should include IO::WaitWritable
+ end
+end
diff --git a/spec/ruby/core/exception/key_error_spec.rb b/spec/ruby/core/exception/key_error_spec.rb
new file mode 100644
index 0000000000..c5e2b1efbc
--- /dev/null
+++ b/spec/ruby/core/exception/key_error_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "KeyError" do
+ it "accepts :receiver and :key options" do
+ receiver = mock("receiver")
+ key = mock("key")
+
+ error = KeyError.new(receiver: receiver, key: key)
+
+ error.receiver.should == receiver
+ error.key.should == key
+
+ error = KeyError.new("message", receiver: receiver, key: key)
+
+ error.message.should == "message"
+ error.receiver.should == receiver
+ error.key.should == key
+ end
+end
diff --git a/spec/ruby/core/exception/load_error_spec.rb b/spec/ruby/core/exception/load_error_spec.rb
new file mode 100644
index 0000000000..0056403e58
--- /dev/null
+++ b/spec/ruby/core/exception/load_error_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "LoadError#path" do
+ before :each do
+ @le = LoadError.new
+ end
+
+ it "is nil when constructed directly" do
+ @le.path.should == nil
+ end
+end
+
+describe "LoadError raised by load or require" do
+ it "provides the failing path in its #path attribute" do
+ begin
+ require 'file_that_does_not_exist'
+ rescue LoadError => le
+ le.path.should == 'file_that_does_not_exist'
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/message_spec.rb b/spec/ruby/core/exception/message_spec.rb
new file mode 100644
index 0000000000..8d7476126e
--- /dev/null
+++ b/spec/ruby/core/exception/message_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#message" do
+ it "returns the class name if there is no message" do
+ Exception.new.message.should == "Exception"
+ end
+
+ it "returns the message passed to #initialize" do
+ Exception.new("Ouch!").message.should == "Ouch!"
+ end
+
+ it "calls #to_s on self" do
+ exc = ExceptionSpecs::OverrideToS.new("you won't see this")
+ exc.message.should == "this is from #to_s"
+ end
+
+ context "when #backtrace is redefined" do
+ it "returns the Exception message" do
+ e = Exception.new
+ e.message.should == 'Exception'
+
+ def e.backtrace; []; end
+ e.message.should == 'Exception'
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/name_error_spec.rb b/spec/ruby/core/exception/name_error_spec.rb
new file mode 100644
index 0000000000..ddd51a92e5
--- /dev/null
+++ b/spec/ruby/core/exception/name_error_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+
+describe "NameError.new" do
+ it "should take optional name argument" do
+ NameError.new("msg","name").name.should == "name"
+ end
+
+ it "accepts a :receiver keyword argument" do
+ receiver = mock("receiver")
+
+ error = NameError.new("msg", :name, receiver: receiver)
+
+ error.receiver.should == receiver
+ error.name.should == :name
+ end
+end
+
+describe "NameError#dup" do
+ it "copies the name and receiver" do
+ begin
+ foo
+ rescue NameError => ne
+ name_error_dup = ne.dup
+ name_error_dup.name.should == :foo
+ name_error_dup.receiver.should == self
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/name_spec.rb b/spec/ruby/core/exception/name_spec.rb
new file mode 100644
index 0000000000..c8a49b40e2
--- /dev/null
+++ b/spec/ruby/core/exception/name_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "NameError#name" do
+ it "returns a method name as a symbol" do
+ -> {
+ doesnt_exist
+ }.should raise_error(NameError) {|e| e.name.should == :doesnt_exist }
+ end
+
+ it "returns a constant name as a symbol" do
+ -> {
+ DoesntExist
+ }.should raise_error(NameError) {|e| e.name.should == :DoesntExist }
+ end
+
+ it "returns a constant name without namespace as a symbol" do
+ -> {
+ Object::DoesntExist
+ }.should raise_error(NameError) {|e| e.name.should == :DoesntExist }
+ end
+
+ it "returns a class variable name as a symbol" do
+ -> {
+ eval("class singleton_class::A; @@doesnt_exist end", binding, __FILE__, __LINE__)
+ }.should raise_error(NameError) { |e| e.name.should == :@@doesnt_exist }
+ end
+
+ it "returns the first argument passed to the method when a NameError is raised from #instance_variable_get" do
+ invalid_ivar_name = "invalid_ivar_name"
+
+ -> {
+ Object.new.instance_variable_get(invalid_ivar_name)
+ }.should raise_error(NameError) {|e| e.name.should equal(invalid_ivar_name) }
+ end
+
+ it "returns the first argument passed to the method when a NameError is raised from #class_variable_get" do
+ invalid_cvar_name = "invalid_cvar_name"
+
+ -> {
+ Object.class_variable_get(invalid_cvar_name)
+ }.should raise_error(NameError) {|e| e.name.should equal(invalid_cvar_name) }
+ end
+end
diff --git a/spec/ruby/core/exception/new_spec.rb b/spec/ruby/core/exception/new_spec.rb
new file mode 100644
index 0000000000..100dbb0a24
--- /dev/null
+++ b/spec/ruby/core/exception/new_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/new'
+
+describe "Exception.new" do
+ it_behaves_like :exception_new, :new
+end
diff --git a/spec/ruby/core/exception/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb
new file mode 100644
index 0000000000..8428ba0382
--- /dev/null
+++ b/spec/ruby/core/exception/no_method_error_spec.rb
@@ -0,0 +1,136 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "NoMethodError.new" do
+ it "allows passing method args" do
+ NoMethodError.new("msg", "name", ["args"]).args.should == ["args"]
+ end
+
+ it "does not require a name" do
+ NoMethodError.new("msg").message.should == "msg"
+ end
+
+ it "accepts a :receiver keyword argument" do
+ receiver = mock("receiver")
+
+ error = NoMethodError.new("msg", :name, receiver: receiver)
+
+ error.receiver.should == receiver
+ error.name.should == :name
+ end
+end
+
+describe "NoMethodError#args" do
+ it "returns an empty array if the caller method had no arguments" do
+ begin
+ NoMethodErrorSpecs::NoMethodErrorB.new.foo
+ rescue Exception => e
+ e.args.should == []
+ end
+ end
+
+ it "returns an array with the same elements as passed to the method" do
+ begin
+ a = NoMethodErrorSpecs::NoMethodErrorA.new
+ NoMethodErrorSpecs::NoMethodErrorB.new.foo(1,a)
+ rescue Exception => e
+ e.args.should == [1,a]
+ e.args[1].should equal a
+ end
+ end
+end
+
+describe "NoMethodError#message" do
+ it "for an undefined method match /undefined method/" do
+ begin
+ NoMethodErrorSpecs::NoMethodErrorD.new.foo
+ rescue Exception => e
+ e.should be_kind_of(NoMethodError)
+ end
+ end
+
+ it "for an protected method match /protected method/" do
+ begin
+ NoMethodErrorSpecs::NoMethodErrorC.new.a_protected_method
+ rescue Exception => e
+ e.should be_kind_of(NoMethodError)
+ end
+ end
+
+ it "for private method match /private method/" do
+ begin
+ NoMethodErrorSpecs::NoMethodErrorC.new.a_private_method
+ rescue Exception => e
+ e.should be_kind_of(NoMethodError)
+ e.message.lines[0].should =~ /private method `a_private_method' called for #<NoMethodErrorSpecs::NoMethodErrorC:0x[\h]+>/
+ end
+ end
+
+ it "calls receiver.inspect only when calling Exception#message" do
+ ScratchPad.record []
+ test_class = Class.new do
+ def inspect
+ ScratchPad << :inspect_called
+ "<inspect>"
+ end
+ end
+ instance = test_class.new
+ begin
+ instance.bar
+ rescue Exception => e
+ e.name.should == :bar
+ ScratchPad.recorded.should == []
+ e.message.should =~ /undefined method.+\bbar\b/
+ ScratchPad.recorded.should == [:inspect_called]
+ end
+ end
+
+ it "fallbacks to a simpler representation of the receiver when receiver.inspect raises an exception" do
+ test_class = Class.new do
+ def inspect
+ raise NoMethodErrorSpecs::InstanceException
+ end
+ end
+ instance = test_class.new
+ begin
+ instance.bar
+ rescue Exception => e
+ e.name.should == :bar
+ message = e.message
+ message.should =~ /undefined method.+\bbar\b/
+ message.should include test_class.inspect
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "uses #name to display the receiver if it is a class or a module" do
+ klass = Class.new { def self.name; "MyClass"; end }
+ begin
+ klass.foo
+ rescue NoMethodError => error
+ error.message.lines.first.chomp.should == "undefined method `foo' for MyClass:Class"
+ end
+
+ mod = Module.new { def self.name; "MyModule"; end }
+ begin
+ mod.foo
+ rescue NoMethodError => error
+ error.message.lines.first.chomp.should == "undefined method `foo' for MyModule:Module"
+ end
+ end
+ end
+end
+
+describe "NoMethodError#dup" do
+ it "copies the name, arguments and receiver" do
+ begin
+ receiver = Object.new
+ receiver.foo(:one, :two)
+ rescue NoMethodError => nme
+ no_method_error_dup = nme.dup
+ no_method_error_dup.name.should == :foo
+ no_method_error_dup.receiver.should == receiver
+ no_method_error_dup.args.should == [:one, :two]
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/reason_spec.rb b/spec/ruby/core/exception/reason_spec.rb
new file mode 100644
index 0000000000..210bbc9725
--- /dev/null
+++ b/spec/ruby/core/exception/reason_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "LocalJumpError#reason" do
+ def get_me_a_return
+ Proc.new { return 42 }
+ end
+
+ it "returns 'return' for a return" do
+ -> { get_me_a_return.call }.should raise_error(LocalJumpError) { |e|
+ e.reason.should == :return
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/receiver_spec.rb b/spec/ruby/core/exception/receiver_spec.rb
new file mode 100644
index 0000000000..d1c23b67be
--- /dev/null
+++ b/spec/ruby/core/exception/receiver_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "NameError#receiver" do
+ class ::ReceiverClass
+ def call_undefined_class_variable; @@doesnt_exist end
+ end
+
+ it "returns the object that raised the exception" do
+ receiver = Object.new
+
+ -> {
+ receiver.doesnt_exist
+ }.should raise_error(NameError) {|e| e.receiver.should equal(receiver) }
+ end
+
+ it "returns the Object class when an undefined constant is called without namespace" do
+ -> {
+ DoesntExist
+ }.should raise_error(NameError) {|e| e.receiver.should equal(Object) }
+ end
+
+ it "returns a class when an undefined constant is called" do
+ -> {
+ NameErrorSpecs::ReceiverClass::DoesntExist
+ }.should raise_error(NameError) {|e| e.receiver.should equal(NameErrorSpecs::ReceiverClass) }
+ end
+
+ it "returns the Object class when an undefined class variable is called" do
+ -> {
+ eval("class singleton_class::A; @@doesnt_exist end", binding, __FILE__, __LINE__)
+ }.should raise_error(NameError) {|e| e.receiver.should equal(singleton_class::A) }
+ end
+
+ it "returns a class when an undefined class variable is called in a subclass' namespace" do
+ -> {
+ NameErrorSpecs::ReceiverClass.new.call_undefined_class_variable
+ }.should raise_error(NameError) {|e| e.receiver.should equal(NameErrorSpecs::ReceiverClass) }
+ end
+
+ it "returns the receiver when raised from #instance_variable_get" do
+ receiver = Object.new
+
+ -> {
+ receiver.instance_variable_get("invalid_ivar_name")
+ }.should raise_error(NameError) {|e| e.receiver.should equal(receiver) }
+ end
+
+ it "returns the receiver when raised from #class_variable_get" do
+ -> {
+ Object.class_variable_get("invalid_cvar_name")
+ }.should raise_error(NameError) {|e| e.receiver.should equal(Object) }
+ end
+
+ it "raises an ArgumentError when the receiver is none" do
+ -> { NameError.new.receiver }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/exception/result_spec.rb b/spec/ruby/core/exception/result_spec.rb
new file mode 100644
index 0000000000..d42fcdffcb
--- /dev/null
+++ b/spec/ruby/core/exception/result_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "StopIteration#result" do
+ before :each do
+ obj = Object.new
+ def obj.each
+ yield :yield_returned_1
+ yield :yield_returned_2
+ :method_returned
+ end
+ @enum = obj.to_enum
+ end
+
+ it "returns the method-returned-object from an Enumerator" do
+ @enum.next
+ @enum.next
+ -> { @enum.next }.should raise_error(StopIteration) { |error|
+ error.result.should equal(:method_returned)
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/set_backtrace_spec.rb b/spec/ruby/core/exception/set_backtrace_spec.rb
new file mode 100644
index 0000000000..ba2e1bf7aa
--- /dev/null
+++ b/spec/ruby/core/exception/set_backtrace_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#set_backtrace" do
+ it "accepts an Array of Strings" do
+ err = RuntimeError.new
+ err.set_backtrace ["unhappy"]
+ err.backtrace.should == ["unhappy"]
+ end
+
+ it "allows the user to set the backtrace from a rescued exception" do
+ bt = ExceptionSpecs::Backtrace.backtrace
+ err = RuntimeError.new
+
+ err.set_backtrace bt
+ err.backtrace.should == bt
+ end
+
+ it "accepts an empty Array" do
+ err = RuntimeError.new
+ err.set_backtrace []
+ err.backtrace.should == []
+ end
+
+ it "accepts a String" do
+ err = RuntimeError.new
+ err.set_backtrace "unhappy"
+ err.backtrace.should == ["unhappy"]
+ end
+
+ it "accepts nil" do
+ err = RuntimeError.new
+ err.set_backtrace nil
+ err.backtrace.should be_nil
+ end
+
+ it "raises a TypeError when passed a Symbol" do
+ err = RuntimeError.new
+ -> { err.set_backtrace :unhappy }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the Array contains a Symbol" do
+ err = RuntimeError.new
+ -> { err.set_backtrace ["String", :unhappy] }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the array contains nil" do
+ err = Exception.new
+ -> { err.set_backtrace ["String", nil] }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the argument is a nested array" do
+ err = Exception.new
+ -> { err.set_backtrace ["String", ["String"]] }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/exception/shared/new.rb b/spec/ruby/core/exception/shared/new.rb
new file mode 100644
index 0000000000..bcde8ee4b2
--- /dev/null
+++ b/spec/ruby/core/exception/shared/new.rb
@@ -0,0 +1,18 @@
+describe :exception_new, shared: true do
+ it "creates a new instance of Exception" do
+ Exception.should be_ancestor_of(Exception.send(@method).class)
+ end
+
+ it "sets the message of the Exception when passes a message" do
+ Exception.send(@method, "I'm broken.").message.should == "I'm broken."
+ end
+
+ it "returns 'Exception' for message when no message given" do
+ Exception.send(@method).message.should == "Exception"
+ end
+
+ it "returns the exception when it has a custom constructor" do
+ ExceptionSpecs::ConstructorException.send(@method).should be_kind_of(ExceptionSpecs::ConstructorException)
+ end
+
+end
diff --git a/spec/ruby/core/exception/signal_exception_spec.rb b/spec/ruby/core/exception/signal_exception_spec.rb
new file mode 100644
index 0000000000..1a0940743f
--- /dev/null
+++ b/spec/ruby/core/exception/signal_exception_spec.rb
@@ -0,0 +1,123 @@
+require_relative '../../spec_helper'
+
+describe "SignalException.new" do
+ it "takes a signal number as the first argument" do
+ exc = SignalException.new(Signal.list["INT"])
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "SIGINT"
+ exc.message.should == "SIGINT"
+ end
+
+ it "raises an exception with an invalid signal number" do
+ -> { SignalException.new(100000) }.should raise_error(ArgumentError)
+ end
+
+ it "takes a signal name without SIG prefix as the first argument" do
+ exc = SignalException.new("INT")
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "SIGINT"
+ exc.message.should == "SIGINT"
+ end
+
+ it "takes a signal name with SIG prefix as the first argument" do
+ exc = SignalException.new("SIGINT")
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "SIGINT"
+ exc.message.should == "SIGINT"
+ end
+
+ it "raises an exception with an invalid signal name" do
+ -> { SignalException.new("NONEXISTENT") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an exception with an invalid first argument type" do
+ -> { SignalException.new(Object.new) }.should raise_error(ArgumentError)
+ end
+
+ it "takes a signal symbol without SIG prefix as the first argument" do
+ exc = SignalException.new(:INT)
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "SIGINT"
+ exc.message.should == "SIGINT"
+ end
+
+ it "takes a signal symbol with SIG prefix as the first argument" do
+ exc = SignalException.new(:SIGINT)
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "SIGINT"
+ exc.message.should == "SIGINT"
+ end
+
+ it "raises an exception with an invalid signal name" do
+ -> { SignalException.new(:NONEXISTENT) }.should raise_error(ArgumentError)
+ end
+
+ it "takes an optional message argument with a signal number" do
+ exc = SignalException.new(Signal.list["INT"], "name")
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "name"
+ exc.message.should == "name"
+ end
+
+ it "raises an exception for an optional argument with a signal name" do
+ -> { SignalException.new("INT","name") }.should raise_error(ArgumentError)
+ end
+end
+
+describe "rescuing SignalException" do
+ it "raises a SignalException when sent a signal" do
+ begin
+ Process.kill :TERM, Process.pid
+ sleep
+ rescue SignalException => e
+ e.signo.should == Signal.list["TERM"]
+ e.signm.should == "SIGTERM"
+ e.message.should == "SIGTERM"
+ end
+ end
+end
+
+describe "SignalException" do
+ it "can be rescued" do
+ ruby_exe(<<-RUBY)
+ begin
+ raise SignalException, 'SIGKILL'
+ rescue SignalException
+ exit(0)
+ end
+ exit(1)
+ RUBY
+
+ $?.exitstatus.should == 0
+ end
+
+ platform_is_not :windows do
+ it "runs after at_exit" do
+ output = ruby_exe(<<-RUBY, exit_status: :SIGKILL)
+ at_exit do
+ puts "hello"
+ $stdout.flush
+ end
+
+ raise SignalException, 'SIGKILL'
+ RUBY
+
+ $?.termsig.should == Signal.list.fetch("KILL")
+ output.should == "hello\n"
+ end
+
+ it "cannot be trapped with Signal.trap" do
+ ruby_exe(<<-RUBY, exit_status: :SIGPROF)
+ Signal.trap("PROF") {}
+ raise(SignalException, "PROF")
+ RUBY
+
+ $?.termsig.should == Signal.list.fetch("PROF")
+ end
+
+ it "self-signals for USR1" do
+ ruby_exe("raise(SignalException, 'USR1')", exit_status: :SIGUSR1)
+ $?.termsig.should == Signal.list.fetch('USR1')
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/signm_spec.rb b/spec/ruby/core/exception/signm_spec.rb
new file mode 100644
index 0000000000..4adff3b5ee
--- /dev/null
+++ b/spec/ruby/core/exception/signm_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "SignalException#signm" do
+ it "returns the signal name" do
+ -> { Process.kill(:TERM, Process.pid) }.should raise_error(SignalException) { |e|
+ e.signm.should == 'SIGTERM'
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/signo_spec.rb b/spec/ruby/core/exception/signo_spec.rb
new file mode 100644
index 0000000000..62fc321516
--- /dev/null
+++ b/spec/ruby/core/exception/signo_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "SignalException#signo" do
+ it "returns the signal number" do
+ -> { Process.kill(:TERM, Process.pid) }.should raise_error(SignalException) { |e|
+ e.signo.should == Signal.list['TERM']
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/standard_error_spec.rb b/spec/ruby/core/exception/standard_error_spec.rb
new file mode 100644
index 0000000000..17e98ce7f0
--- /dev/null
+++ b/spec/ruby/core/exception/standard_error_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "StandardError" do
+ it "rescues StandardError" do
+ begin
+ raise StandardError
+ rescue => exception
+ exception.class.should == StandardError
+ end
+ end
+
+ it "rescues subclass of StandardError" do
+ begin
+ raise RuntimeError
+ rescue => exception
+ exception.class.should == RuntimeError
+ end
+ end
+
+ it "does not rescue superclass of StandardError" do
+ -> { begin; raise Exception; rescue; end }.should raise_error(Exception)
+ end
+end
diff --git a/spec/ruby/core/exception/status_spec.rb b/spec/ruby/core/exception/status_spec.rb
new file mode 100644
index 0000000000..8ace00fe10
--- /dev/null
+++ b/spec/ruby/core/exception/status_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "SystemExit#status" do
+ it "returns the exit status" do
+ -> { exit 42 }.should raise_error(SystemExit) { |e|
+ e.status.should == 42
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/success_spec.rb b/spec/ruby/core/exception/success_spec.rb
new file mode 100644
index 0000000000..6f21743340
--- /dev/null
+++ b/spec/ruby/core/exception/success_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "SystemExit#success?" do
+ it "returns true if the process exited successfully" do
+ -> { exit 0 }.should raise_error(SystemExit) { |e|
+ e.should.success?
+ }
+ end
+
+ it "returns false if the process exited unsuccessfully" do
+ -> { exit(-1) }.should raise_error(SystemExit) { |e|
+ e.should_not.success?
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/system_call_error_spec.rb b/spec/ruby/core/exception/system_call_error_spec.rb
new file mode 100644
index 0000000000..73167bc288
--- /dev/null
+++ b/spec/ruby/core/exception/system_call_error_spec.rb
@@ -0,0 +1,143 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "SystemCallError" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "can be subclassed" do
+ ExceptionSpecs::SCESub = Class.new(SystemCallError) do
+ def initialize
+ ScratchPad.record :initialize
+ end
+ end
+
+ exc = ExceptionSpecs::SCESub.new
+ ScratchPad.recorded.should equal(:initialize)
+ exc.should be_an_instance_of(ExceptionSpecs::SCESub)
+ end
+end
+
+describe "SystemCallError.new" do
+ before :all do
+ @example_errno = Errno::EINVAL::Errno
+ @example_errno_class = Errno::EINVAL
+ @last_known_errno = Errno.constants.size - 1
+ @unknown_errno = Errno.constants.size
+ end
+
+ it "requires at least one argument" do
+ -> { SystemCallError.new }.should raise_error(ArgumentError)
+ end
+
+ it "accepts single Integer argument as errno" do
+ SystemCallError.new(-2**24).errno.should == -2**24
+ SystemCallError.new(-1).errno.should == -1
+ SystemCallError.new(0).errno.should == 0
+ SystemCallError.new(@last_known_errno).errno.should == @last_known_errno
+ SystemCallError.new(@unknown_errno).errno.should == @unknown_errno
+ SystemCallError.new(2**24).errno.should == 2**24
+ end
+
+ it "constructs a SystemCallError for an unknown error number" do
+ SystemCallError.new(-2**24).should be_an_instance_of(SystemCallError)
+ SystemCallError.new(-1).should be_an_instance_of(SystemCallError)
+ SystemCallError.new(@unknown_errno).should be_an_instance_of(SystemCallError)
+ SystemCallError.new(2**24).should be_an_instance_of(SystemCallError)
+ end
+
+ it "constructs the appropriate Errno class" do
+ e = SystemCallError.new(@example_errno)
+ e.should be_kind_of(SystemCallError)
+ e.should be_an_instance_of(@example_errno_class)
+ end
+
+ it "accepts an optional custom message preceding the errno" do
+ exc = SystemCallError.new("custom message", @example_errno)
+ exc.should be_an_instance_of(@example_errno_class)
+ exc.errno.should == @example_errno
+ exc.message.should == 'Invalid argument - custom message'
+ end
+
+ it "accepts an optional third argument specifying the location" do
+ exc = SystemCallError.new("custom message", @example_errno, "location")
+ exc.should be_an_instance_of(@example_errno_class)
+ exc.errno.should == @example_errno
+ exc.message.should == 'Invalid argument @ location - custom message'
+ end
+
+ it "coerces location if it is not a String" do
+ e = SystemCallError.new('foo', 1, :not_a_string)
+ e.message.should =~ /@ not_a_string - foo/
+ end
+
+ it "returns an arity of -1 for the initialize method" do
+ SystemCallError.instance_method(:initialize).arity.should == -1
+ end
+
+ it "converts to Integer if errno is a Float" do
+ SystemCallError.new('foo', 2.0).should == SystemCallError.new('foo', 2)
+ SystemCallError.new('foo', 2.9).should == SystemCallError.new('foo', 2)
+ end
+
+ it "converts to Integer if errno is a Complex convertible to Integer" do
+ SystemCallError.new('foo', Complex(2.9, 0)).should == SystemCallError.new('foo', 2)
+ end
+
+ it "raises TypeError if message is not a String" do
+ -> { SystemCallError.new(:foo, 1) }.should raise_error(TypeError, /no implicit conversion of Symbol into String/)
+ end
+
+ it "raises TypeError if errno is not an Integer" do
+ -> { SystemCallError.new('foo', 'bar') }.should raise_error(TypeError, /no implicit conversion of String into Integer/)
+ end
+
+ it "raises RangeError if errno is a Complex not convertible to Integer" do
+ -> { SystemCallError.new('foo', Complex(2.9, 1)) }.should raise_error(RangeError, /can't convert/)
+ end
+end
+
+describe "SystemCallError#errno" do
+ it "returns nil when no errno given" do
+ SystemCallError.new("message").errno.should == nil
+ end
+
+ it "returns the errno given as optional argument to new" do
+ SystemCallError.new("message", -2**20).errno.should == -2**20
+ SystemCallError.new("message", -1).errno.should == -1
+ SystemCallError.new("message", 0).errno.should == 0
+ SystemCallError.new("message", 1).errno.should == 1
+ SystemCallError.new("message", 42).errno.should == 42
+ SystemCallError.new("message", 2**20).errno.should == 2**20
+ end
+end
+
+describe "SystemCallError#message" do
+ it "returns the default message when no message is given" do
+ platform_is :aix do
+ SystemCallError.new(2**28).message.should =~ /Error .*occurred/i
+ end
+ platform_is_not :aix do
+ SystemCallError.new(2**28).message.should =~ /Unknown error/i
+ end
+ end
+
+ it "returns the message given as an argument to new" do
+ SystemCallError.new("message", 1).message.should =~ /message/
+ SystemCallError.new("XXX").message.should =~ /XXX/
+ end
+end
+
+describe "SystemCallError#dup" do
+ it "copies the errno" do
+ dup_sce = SystemCallError.new("message", 42).dup
+ dup_sce.errno.should == 42
+ end
+end
+
+describe "SystemCallError#backtrace" do
+ it "is nil if not raised" do
+ SystemCallError.new("message", 42).backtrace.should == nil
+ end
+end
diff --git a/spec/ruby/core/exception/system_exit_spec.rb b/spec/ruby/core/exception/system_exit_spec.rb
new file mode 100644
index 0000000000..d899844c4e
--- /dev/null
+++ b/spec/ruby/core/exception/system_exit_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+
+describe "SystemExit" do
+ describe "#initialize" do
+ it "accepts a status and message" do
+ exc = SystemExit.new(42, "message")
+ exc.status.should == 42
+ exc.message.should == "message"
+
+ exc = SystemExit.new(true, "message")
+ exc.status.should == 0
+ exc.message.should == "message"
+
+ exc = SystemExit.new(false, "message")
+ exc.status.should == 1
+ exc.message.should == "message"
+ end
+
+ it "accepts a status only" do
+ exc = SystemExit.new(42)
+ exc.status.should == 42
+ exc.message.should == "SystemExit"
+
+ exc = SystemExit.new(true)
+ exc.status.should == 0
+ exc.message.should == "SystemExit"
+
+ exc = SystemExit.new(false)
+ exc.status.should == 1
+ exc.message.should == "SystemExit"
+ end
+
+ it "accepts a message only" do
+ exc = SystemExit.new("message")
+ exc.status.should == 0
+ exc.message.should == "message"
+ end
+
+ it "accepts no arguments" do
+ exc = SystemExit.new
+ exc.status.should == 0
+ exc.message.should == "SystemExit"
+ end
+ end
+
+ it "sets the exit status and exits silently when raised" do
+ code = 'raise SystemExit.new(7)'
+ result = ruby_exe(code, args: "2>&1", exit_status: 7)
+ result.should == ""
+ $?.exitstatus.should == 7
+ end
+
+ it "sets the exit status and exits silently when raised when subclassed" do
+ code = 'class CustomExit < SystemExit; end; raise CustomExit.new(8)'
+ result = ruby_exe(code, args: "2>&1", exit_status: 8)
+ result.should == ""
+ $?.exitstatus.should == 8
+ end
+end
diff --git a/spec/ruby/core/exception/to_s_spec.rb b/spec/ruby/core/exception/to_s_spec.rb
new file mode 100644
index 0000000000..4c4c7ab432
--- /dev/null
+++ b/spec/ruby/core/exception/to_s_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#to_s" do
+ it "returns the self's name if no message is set" do
+ Exception.new.to_s.should == 'Exception'
+ ExceptionSpecs::Exceptional.new.to_s.should == 'ExceptionSpecs::Exceptional'
+ end
+
+ it "returns self's message if set" do
+ ExceptionSpecs::Exceptional.new('!!').to_s.should == '!!'
+ end
+
+ it "calls #to_s on the message" do
+ message = mock("message")
+ message.should_receive(:to_s).and_return("message")
+ ExceptionSpecs::Exceptional.new(message).to_s.should == "message"
+ end
+end
+
+describe "NameError#to_s" do
+ it "raises its own message for an undefined variable" do
+ begin
+ puts not_defined
+ rescue => exception
+ exception.message.should =~ /undefined local variable or method `not_defined'/
+ end
+ end
+
+ it "raises its own message for an undefined constant" do
+ begin
+ puts NotDefined
+ rescue => exception
+ exception.message.should =~ /uninitialized constant NotDefined/
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/top_level_spec.rb b/spec/ruby/core/exception/top_level_spec.rb
new file mode 100644
index 0000000000..b47648425e
--- /dev/null
+++ b/spec/ruby/core/exception/top_level_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "An Exception reaching the top level" do
+ it "is printed on STDERR" do
+ ruby_exe('raise "foo"', args: "2>&1", exit_status: 1).should.include?("in `<main>': foo (RuntimeError)")
+ end
+
+ it "the Exception#cause is printed to STDERR with backtraces" do
+ code = <<-RUBY
+ def raise_cause
+ raise "the cause"
+ end
+ def raise_wrapped
+ raise "wrapped"
+ end
+ begin
+ raise_cause
+ rescue
+ raise_wrapped
+ end
+ RUBY
+ lines = ruby_exe(code, args: "2>&1", exit_status: 1).lines
+ lines.reject! { |l| l.include?('rescue in') }
+ lines.map! { |l| l.chomp[/:(in.+)/, 1] }
+ lines.should == ["in `raise_wrapped': wrapped (RuntimeError)",
+ "in `<main>'",
+ "in `raise_cause': the cause (RuntimeError)",
+ "in `<main>'"]
+ end
+
+ describe "with a custom backtrace" do
+ it "is printed on STDERR" do
+ code = <<-RUBY
+ raise RuntimeError, "foo", [
+ "/dir/foo.rb:10:in `raising'",
+ "/dir/bar.rb:20:in `caller'",
+ ]
+ RUBY
+ ruby_exe(code, args: "2>&1", exit_status: 1).should == <<-EOS
+/dir/foo.rb:10:in `raising': foo (RuntimeError)
+\tfrom /dir/bar.rb:20:in `caller'
+ EOS
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/uncaught_throw_error_spec.rb b/spec/ruby/core/exception/uncaught_throw_error_spec.rb
new file mode 100644
index 0000000000..9267df6670
--- /dev/null
+++ b/spec/ruby/core/exception/uncaught_throw_error_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "UncaughtThrowError#tag" do
+ it "returns the object thrown" do
+ begin
+ throw :abc
+
+ rescue UncaughtThrowError => e
+ e.tag.should == :abc
+ end
+ end
+end
diff --git a/spec/ruby/core/false/and_spec.rb b/spec/ruby/core/false/and_spec.rb
new file mode 100644
index 0000000000..0b02ae62c5
--- /dev/null
+++ b/spec/ruby/core/false/and_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#&" do
+ it "returns false" do
+ (false & false).should == false
+ (false & true).should == false
+ (false & nil).should == false
+ (false & "").should == false
+ (false & mock('x')).should == false
+ end
+end
diff --git a/spec/ruby/core/false/case_compare_spec.rb b/spec/ruby/core/false/case_compare_spec.rb
new file mode 100644
index 0000000000..0bd0ab44ae
--- /dev/null
+++ b/spec/ruby/core/false/case_compare_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#===" do
+ it "returns true for false" do
+ (false === false).should == true
+ end
+
+ it "returns false for non-false object" do
+ (false === 0).should == false
+ (false === "").should == false
+ (false === Object).should == false
+ (false === nil).should == false
+ end
+end
diff --git a/spec/ruby/core/false/dup_spec.rb b/spec/ruby/core/false/dup_spec.rb
new file mode 100644
index 0000000000..1a569a2f4f
--- /dev/null
+++ b/spec/ruby/core/false/dup_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#dup" do
+ it "returns self" do
+ false.dup.should equal(false)
+ end
+end
diff --git a/spec/ruby/core/false/falseclass_spec.rb b/spec/ruby/core/false/falseclass_spec.rb
new file mode 100644
index 0000000000..c018ef2421
--- /dev/null
+++ b/spec/ruby/core/false/falseclass_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass" do
+ it ".allocate raises a TypeError" do
+ -> do
+ FalseClass.allocate
+ end.should raise_error(TypeError)
+ end
+
+ it ".new is undefined" do
+ -> do
+ FalseClass.new
+ end.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/false/inspect_spec.rb b/spec/ruby/core/false/inspect_spec.rb
new file mode 100644
index 0000000000..4cbb55d434
--- /dev/null
+++ b/spec/ruby/core/false/inspect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#inspect" do
+ it "returns the string 'false'" do
+ false.inspect.should == "false"
+ end
+end
diff --git a/spec/ruby/core/false/or_spec.rb b/spec/ruby/core/false/or_spec.rb
new file mode 100644
index 0000000000..f3ee1a3439
--- /dev/null
+++ b/spec/ruby/core/false/or_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#|" do
+ it "returns false if other is nil or false, otherwise true" do
+ (false | false).should == false
+ (false | true).should == true
+ (false | nil).should == false
+ (false | "").should == true
+ (false | mock('x')).should == true
+ end
+end
diff --git a/spec/ruby/core/false/to_s_spec.rb b/spec/ruby/core/false/to_s_spec.rb
new file mode 100644
index 0000000000..62f67f6f55
--- /dev/null
+++ b/spec/ruby/core/false/to_s_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#to_s" do
+ it "returns the string 'false'" do
+ false.to_s.should == "false"
+ end
+
+ it "returns a frozen string" do
+ false.to_s.should.frozen?
+ end
+
+ it "always returns the same string" do
+ false.to_s.should equal(false.to_s)
+ end
+end
diff --git a/spec/ruby/core/false/xor_spec.rb b/spec/ruby/core/false/xor_spec.rb
new file mode 100644
index 0000000000..1b87b9f412
--- /dev/null
+++ b/spec/ruby/core/false/xor_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#^" do
+ it "returns false if other is nil or false, otherwise true" do
+ (false ^ false).should == false
+ (false ^ true).should == true
+ (false ^ nil).should == false
+ (false ^ "").should == true
+ (false ^ mock('x')).should == true
+ end
+end
diff --git a/spec/ruby/core/fiber/blocking_spec.rb b/spec/ruby/core/fiber/blocking_spec.rb
new file mode 100644
index 0000000000..eeee5a71c1
--- /dev/null
+++ b/spec/ruby/core/fiber/blocking_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+require_relative 'shared/blocking'
+
+ruby_version_is "3.0" do
+ require "fiber"
+
+ describe "Fiber.blocking?" do
+ it_behaves_like :non_blocking_fiber, -> { Fiber.blocking? }
+
+ context "when fiber is blocking" do
+ context "root Fiber of the main thread" do
+ it "returns 1 for blocking: true" do
+ fiber = Fiber.new(blocking: true) { Fiber.blocking? }
+ blocking = fiber.resume
+
+ blocking.should == 1
+ end
+ end
+
+ context "root Fiber of a new thread" do
+ it "returns 1 for blocking: true" do
+ thread = Thread.new do
+ fiber = Fiber.new(blocking: true) { Fiber.blocking? }
+ blocking = fiber.resume
+
+ blocking.should == 1
+ end
+
+ thread.join
+ end
+ end
+ end
+ end
+
+ describe "Fiber#blocking?" do
+ it_behaves_like :non_blocking_fiber, -> { Fiber.current.blocking? }
+
+ context "when fiber is blocking" do
+ context "root Fiber of the main thread" do
+ it "returns true for blocking: true" do
+ fiber = Fiber.new(blocking: true) { Fiber.current.blocking? }
+ blocking = fiber.resume
+
+ blocking.should == true
+ end
+ end
+
+ context "root Fiber of a new thread" do
+ it "returns true for blocking: true" do
+ thread = Thread.new do
+ fiber = Fiber.new(blocking: true) { Fiber.current.blocking? }
+ blocking = fiber.resume
+
+ blocking.should == true
+ end
+
+ thread.join
+ end
+ end
+ end
+ end
+end
+
+ruby_version_is "3.2" do
+ describe "Fiber.blocking" do
+ context "when fiber is non-blocking" do
+ it "can become blocking" do
+ fiber = Fiber.new(blocking: false) do
+ Fiber.blocking do |f|
+ f.blocking? ? :blocking : :non_blocking
+ end
+ end
+
+ blocking = fiber.resume
+ blocking.should == :blocking
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/fiber/fixtures/classes.rb b/spec/ruby/core/fiber/fixtures/classes.rb
new file mode 100644
index 0000000000..c00facd6e1
--- /dev/null
+++ b/spec/ruby/core/fiber/fixtures/classes.rb
@@ -0,0 +1,12 @@
+module FiberSpecs
+
+ class NewFiberToRaise
+ def self.raise(*args)
+ fiber = Fiber.new { Fiber.yield }
+ fiber.resume
+ fiber.raise(*args)
+ end
+ end
+
+ class CustomError < StandardError; end
+end
diff --git a/spec/ruby/core/fiber/new_spec.rb b/spec/ruby/core/fiber/new_spec.rb
new file mode 100644
index 0000000000..b43c1386be
--- /dev/null
+++ b/spec/ruby/core/fiber/new_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "Fiber.new" do
+ it "creates a fiber from the given block" do
+ fiber = Fiber.new {}
+ fiber.resume
+ fiber.should be_an_instance_of(Fiber)
+ end
+
+ it "creates a fiber from a subclass" do
+ class MyFiber < Fiber
+ end
+ fiber = MyFiber.new {}
+ fiber.resume
+ fiber.should be_an_instance_of(MyFiber)
+ end
+
+ it "raises an ArgumentError if called without a block" do
+ -> { Fiber.new }.should raise_error(ArgumentError)
+ end
+
+ it "does not invoke the block" do
+ invoked = false
+ fiber = Fiber.new { invoked = true }
+ invoked.should be_false
+ fiber.resume
+ end
+
+ it "closes over lexical environments" do
+ o = Object.new
+ def o.f
+ a = 1
+ f = Fiber.new { a = 2 }
+ f.resume
+ a
+ end
+ o.f.should == 2
+ end
+end
diff --git a/spec/ruby/core/fiber/raise_spec.rb b/spec/ruby/core/fiber/raise_spec.rb
new file mode 100644
index 0000000000..09c4c1b524
--- /dev/null
+++ b/spec/ruby/core/fiber/raise_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/kernel/raise'
+
+describe "Fiber#raise" do
+ it_behaves_like :kernel_raise, :raise, FiberSpecs::NewFiberToRaise
+end
+
+describe "Fiber#raise" do
+ it 'raises RuntimeError by default' do
+ -> { FiberSpecs::NewFiberToRaise.raise }.should raise_error(RuntimeError)
+ end
+
+ it "raises FiberError if Fiber is not born" do
+ fiber = Fiber.new { true }
+ -> { fiber.raise }.should raise_error(FiberError, "cannot raise exception on unborn fiber")
+ end
+
+ it "raises FiberError if Fiber is dead" do
+ fiber = Fiber.new { true }
+ fiber.resume
+ -> { fiber.raise }.should raise_error(FiberError, /dead fiber called|attempt to resume a terminated fiber/)
+ end
+
+ it 'accepts error class' do
+ -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError }.should raise_error(FiberSpecs::CustomError)
+ end
+
+ it 'accepts error message' do
+ -> { FiberSpecs::NewFiberToRaise.raise "error message" }.should raise_error(RuntimeError, "error message")
+ end
+
+ it 'does not accept array of backtrace information only' do
+ -> { FiberSpecs::NewFiberToRaise.raise ['foo'] }.should raise_error(TypeError)
+ end
+
+ it 'does not accept integer' do
+ -> { FiberSpecs::NewFiberToRaise.raise 100 }.should raise_error(TypeError)
+ end
+
+ it 'accepts error class with error message' do
+ -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error' }.should raise_error(FiberSpecs::CustomError, 'test error')
+ end
+
+ it 'accepts error class with with error message and backtrace information' do
+ -> {
+ FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error', ['foo', 'boo']
+ }.should raise_error(FiberSpecs::CustomError) { |e|
+ e.message.should == 'test error'
+ e.backtrace.should == ['foo', 'boo']
+ }
+ end
+
+ it 'does not accept only error message and backtrace information' do
+ -> { FiberSpecs::NewFiberToRaise.raise 'test error', ['foo', 'boo'] }.should raise_error(TypeError)
+ end
+
+ it "raises a FiberError if invoked from a different Thread" do
+ fiber = Fiber.new { Fiber.yield }
+ fiber.resume
+ Thread.new do
+ -> {
+ fiber.raise
+ }.should raise_error(FiberError, "fiber called across threads")
+ end.join
+ end
+
+ it "kills Fiber" do
+ fiber = Fiber.new { Fiber.yield :first; :second }
+ fiber.resume
+ -> { fiber.raise }.should raise_error
+ -> { fiber.resume }.should raise_error(FiberError, /dead fiber called|attempt to resume a terminated fiber/)
+ end
+
+ it "returns to calling fiber after raise" do
+ fiber_one = Fiber.new do
+ Fiber.yield :yield_one
+ :unreachable
+ end
+
+ fiber_two = Fiber.new do
+ results = []
+ results << fiber_one.resume
+ begin
+ fiber_one.raise
+ rescue
+ results << :rescued
+ end
+ results
+ end
+
+ fiber_two.resume.should == [:yield_one, :rescued]
+ end
+end
+
+
+ruby_version_is ""..."3.0" do
+ describe "Fiber#raise" do
+ it "raises a FiberError if invoked on a transferring Fiber" do
+ require "fiber"
+ root = Fiber.current
+ fiber = Fiber.new { root.transfer }
+ fiber.transfer
+ -> { fiber.raise }.should raise_error(FiberError, "cannot resume transferred Fiber")
+ end
+ end
+end
+
+ruby_version_is "3.0" do
+ describe "Fiber#raise" do
+ it "transfers and raises on a transferring fiber" do
+ require "fiber"
+ root = Fiber.current
+ fiber = Fiber.new { root.transfer }
+ fiber.transfer
+ -> { fiber.raise "msg" }.should raise_error(RuntimeError, "msg")
+ end
+ end
+end
diff --git a/spec/ruby/core/fiber/resume_spec.rb b/spec/ruby/core/fiber/resume_spec.rb
new file mode 100644
index 0000000000..273bc866af
--- /dev/null
+++ b/spec/ruby/core/fiber/resume_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/fiber/resume'
+
+describe "Fiber#resume" do
+ it_behaves_like :fiber_resume, :resume
+end
+
+describe "Fiber#resume" do
+ it "runs until Fiber.yield" do
+ obj = mock('obj')
+ obj.should_not_receive(:do)
+ fiber = Fiber.new { 1 + 2; Fiber.yield; obj.do }
+ fiber.resume
+ end
+
+ it "resumes from the last call to Fiber.yield on subsequent invocations" do
+ fiber = Fiber.new { Fiber.yield :first; :second }
+ fiber.resume.should == :first
+ fiber.resume.should == :second
+ end
+
+ it "sets the block parameters to its arguments on the first invocation" do
+ first = mock('first')
+ first.should_receive(:arg).with(:first).twice
+
+ fiber = Fiber.new { |arg| first.arg arg; Fiber.yield; first.arg arg; }
+ fiber.resume :first
+ fiber.resume :second
+ end
+
+ ruby_version_is '3.0' do
+ it "raises a FiberError if the Fiber tries to resume itself" do
+ fiber = Fiber.new { fiber.resume }
+ -> { fiber.resume }.should raise_error(FiberError, /current fiber/)
+ end
+ end
+
+ ruby_version_is '' ... '3.0' do
+ it "raises a FiberError if the Fiber tries to resume itself" do
+ fiber = Fiber.new { fiber.resume }
+ -> { fiber.resume }.should raise_error(FiberError, /double resume/)
+ end
+ end
+
+ it "returns control to the calling Fiber if called from one" do
+ fiber1 = Fiber.new { :fiber1 }
+ fiber2 = Fiber.new { fiber1.resume; :fiber2 }
+ fiber2.resume.should == :fiber2
+ end
+
+ # Redmine #595
+ it "executes the ensure clause" do
+ code = <<-RUBY
+ f = Fiber.new do
+ begin
+ Fiber.yield
+ ensure
+ puts "ensure executed"
+ end
+ end
+
+ # The apparent issue is that when Fiber.yield executes, control
+ # "leaves" the "ensure block" and so the ensure clause should run. But
+ # control really does NOT leave the ensure block when Fiber.yield
+ # executes. It merely pauses there. To require ensure to run when a
+ # Fiber is suspended then makes ensure-in-a-Fiber-context different
+ # than ensure-in-a-Thread-context and this would be very confusing.
+ f.resume
+
+ # When we execute the second #resume call, the ensure block DOES exit,
+ # the ensure clause runs.
+ f.resume
+
+ exit 0
+ RUBY
+
+ ruby_exe(code).should == "ensure executed\n"
+ end
+end
diff --git a/spec/ruby/core/fiber/shared/blocking.rb b/spec/ruby/core/fiber/shared/blocking.rb
new file mode 100644
index 0000000000..21707e1ea7
--- /dev/null
+++ b/spec/ruby/core/fiber/shared/blocking.rb
@@ -0,0 +1,41 @@
+describe :non_blocking_fiber, shared: true do
+ context "root Fiber of the main thread" do
+ it "returns false" do
+ fiber = Fiber.new { @method.call }
+ blocking = fiber.resume
+
+ blocking.should == false
+ end
+
+ it "returns false for blocking: false" do
+ fiber = Fiber.new(blocking: false) { @method.call }
+ blocking = fiber.resume
+
+ blocking.should == false
+ end
+ end
+
+ context "root Fiber of a new thread" do
+ it "returns false" do
+ thread = Thread.new do
+ fiber = Fiber.new { @method.call }
+ blocking = fiber.resume
+
+ blocking.should == false
+ end
+
+ thread.join
+ end
+
+ it "returns false for blocking: false" do
+ thread = Thread.new do
+ fiber = Fiber.new(blocking: false) { @method.call }
+ blocking = fiber.resume
+
+ blocking.should == false
+ end
+
+ thread.join
+ end
+ end
+end
diff --git a/spec/ruby/core/fiber/storage_spec.rb b/spec/ruby/core/fiber/storage_spec.rb
new file mode 100644
index 0000000000..e2bf6da04c
--- /dev/null
+++ b/spec/ruby/core/fiber/storage_spec.rb
@@ -0,0 +1,117 @@
+require_relative '../../spec_helper'
+
+require 'fiber'
+
+describe "Fiber.new(storage:)" do
+ ruby_version_is "3.2" do
+ it "creates a Fiber with the given storage" do
+ storage = {life: 42}
+ fiber = Fiber.new(storage: storage) { Fiber.current.storage }
+ fiber.resume.should == storage
+ end
+
+ it "creates a fiber with lazily initialized storage" do
+ Fiber.new(storage: nil) { Fiber.current.storage }.resume.should == {}
+ end
+
+ it "creates a fiber by inheriting the storage of the parent fiber" do
+ fiber = Fiber.new(storage: {life: 42}) do
+ Fiber.new { Fiber.current.storage }.resume
+ end
+ fiber.resume.should == {life: 42}
+ end
+
+ it "cannot create a fiber with non-hash storage" do
+ -> { Fiber.new(storage: 42) {} }.should raise_error(TypeError)
+ end
+ end
+end
+
+describe "Fiber#storage=" do
+ ruby_version_is "3.2" do
+ it "can clear the storage of the fiber" do
+ fiber = Fiber.new(storage: {life: 42}) {
+ Fiber.current.storage = nil
+ Fiber.current.storage
+ }
+ fiber.resume.should == {}
+ end
+
+ it "can set the storage of the fiber" do
+ fiber = Fiber.new(storage: {life: 42}) {
+ Fiber.current.storage = {life: 43}
+ Fiber.current.storage
+ }
+ fiber.resume.should == {life: 43}
+ end
+
+ it "can't set the storage of the fiber to non-hash" do
+ -> { Fiber.current.storage = 42 }.should raise_error(TypeError)
+ end
+
+ it "can't set the storage of the fiber to a frozen hash" do
+ -> { Fiber.current.storage = {life: 43}.freeze }.should raise_error(FrozenError)
+ end
+
+ it "can't set the storage of the fiber to a hash with non-symbol keys" do
+ -> { Fiber.current.storage = {life: 43, Object.new => 44} }.should raise_error(TypeError)
+ end
+ end
+end
+
+describe "Fiber.[]" do
+ ruby_version_is "3.2" do
+ it "returns the value of the given key in the storage of the current fiber" do
+ Fiber.new(storage: {life: 42}) { Fiber[:life] }.resume.should == 42
+ end
+
+ it "returns nil if the key is not present in the storage of the current fiber" do
+ Fiber.new(storage: {life: 42}) { Fiber[:death] }.resume.should be_nil
+ end
+
+ it "returns nil if the current fiber has no storage" do
+ Fiber.new { Fiber[:life] }.resume.should be_nil
+ end
+ end
+
+ ruby_version_is "3.2.3" do
+ it "can use dynamically defined keys" do
+ key = :"#{self.class.name}#.#{self.object_id}"
+ Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42
+ end
+
+ it "can't use invalid keys" do
+ invalid_keys = [Object.new, "Foo", 12]
+ invalid_keys.each do |key|
+ -> { Fiber[key] }.should raise_error(TypeError)
+ end
+ end
+ end
+end
+
+describe "Fiber.[]=" do
+ ruby_version_is "3.2" do
+ it "sets the value of the given key in the storage of the current fiber" do
+ Fiber.new(storage: {life: 42}) { Fiber[:life] = 43; Fiber[:life] }.resume.should == 43
+ end
+
+ it "sets the value of the given key in the storage of the current fiber" do
+ Fiber.new(storage: {life: 42}) { Fiber[:death] = 43; Fiber[:death] }.resume.should == 43
+ end
+
+ it "sets the value of the given key in the storage of the current fiber" do
+ Fiber.new { Fiber[:life] = 43; Fiber[:life] }.resume.should == 43
+ end
+ end
+end
+
+describe "Thread.new" do
+ ruby_version_is "3.2" do
+ it "creates a thread with the storage of the current fiber" do
+ fiber = Fiber.new(storage: {life: 42}) do
+ Thread.new { Fiber.current.storage }.value
+ end
+ fiber.resume.should == {life: 42}
+ end
+ end
+end
diff --git a/spec/ruby/core/fiber/yield_spec.rb b/spec/ruby/core/fiber/yield_spec.rb
new file mode 100644
index 0000000000..b010912c87
--- /dev/null
+++ b/spec/ruby/core/fiber/yield_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Fiber.yield" do
+ it "passes control to the Fiber's caller" do
+ step = 0
+ fiber = Fiber.new { step = 1; Fiber.yield; step = 2; Fiber.yield; step = 3 }
+ fiber.resume
+ step.should == 1
+ fiber.resume
+ step.should == 2
+ end
+
+ it "returns its arguments to the caller" do
+ fiber = Fiber.new { true; Fiber.yield :glark; true }
+ fiber.resume.should == :glark
+ fiber.resume
+ end
+
+ it "returns nil to the caller if given no arguments" do
+ fiber = Fiber.new { true; Fiber.yield; true }
+ fiber.resume.should be_nil
+ fiber.resume
+ end
+
+ it "returns to the Fiber the value of the #resume call that invoked it" do
+ fiber = Fiber.new { Fiber.yield.should == :caller }
+ fiber.resume
+ fiber.resume :caller
+ end
+
+ it "does not propagate or reraise a rescued exception" do
+ fiber = Fiber.new do
+ begin
+ raise "an error in a Fiber"
+ rescue
+ Fiber.yield :first
+ end
+
+ :second
+ end
+
+ fiber.resume.should == :first
+ fiber.resume.should == :second
+ end
+
+ it "raises a FiberError if called from the root Fiber" do
+ ->{ Fiber.yield }.should raise_error(FiberError)
+ end
+end
diff --git a/spec/ruby/core/file/absolute_path_spec.rb b/spec/ruby/core/file/absolute_path_spec.rb
new file mode 100644
index 0000000000..e35c80ec3c
--- /dev/null
+++ b/spec/ruby/core/file/absolute_path_spec.rb
@@ -0,0 +1,94 @@
+require_relative '../../spec_helper'
+
+describe "File.absolute_path?" do
+ before :each do
+ @abs = File.expand_path(__FILE__)
+ end
+
+ it "returns true if it's an absolute pathname" do
+ File.absolute_path?(@abs).should be_true
+ end
+
+ it "returns false if it's a relative path" do
+ File.absolute_path?(File.basename(__FILE__)).should be_false
+ end
+
+ it "returns false if it's a tricky relative path" do
+ File.absolute_path?("C:foo\\bar").should be_false
+ end
+
+ it "does not expand '~' to a home directory." do
+ File.absolute_path?('~').should be_false
+ end
+
+ it "does not expand '~user' to a home directory." do
+ path = File.dirname(@abs)
+ Dir.chdir(path) do
+ File.absolute_path?('~user').should be_false
+ end
+ end
+
+ it "calls #to_path on its argument" do
+ mock = mock_to_path(File.expand_path(__FILE__))
+
+ File.absolute_path?(mock).should be_true
+ end
+
+ platform_is_not :windows do
+ it "takes into consideration the platform's root" do
+ File.absolute_path?("C:\\foo\\bar").should be_false
+ File.absolute_path?("C:/foo/bar").should be_false
+ File.absolute_path?("/foo/bar\\baz").should be_true
+ end
+ end
+
+ platform_is :windows do
+ it "takes into consideration the platform path separator(s)" do
+ File.absolute_path?("C:\\foo\\bar").should be_true
+ File.absolute_path?("C:/foo/bar").should be_true
+ File.absolute_path?("/foo/bar\\baz").should be_false
+ end
+ end
+end
+
+describe "File.absolute_path" do
+ before :each do
+ @abs = File.expand_path(__FILE__)
+ end
+
+ it "returns the argument if it's an absolute pathname" do
+ File.absolute_path(@abs).should == @abs
+ end
+
+ it "resolves paths relative to the current working directory" do
+ path = File.dirname(@abs)
+ Dir.chdir(path) do
+ File.absolute_path('hello.txt').should == File.join(Dir.pwd, 'hello.txt')
+ end
+ end
+
+ it "does not expand '~' to a home directory." do
+ File.absolute_path('~').should_not == File.expand_path('~')
+ end
+
+ platform_is_not :windows do
+ it "does not expand '~' when given dir argument" do
+ File.absolute_path('~', '/').should == '/~'
+ end
+ end
+
+ it "does not expand '~user' to a home directory." do
+ path = File.dirname(@abs)
+ Dir.chdir(path) do
+ File.absolute_path('~user').should == File.join(Dir.pwd, '~user')
+ end
+ end
+
+ it "accepts a second argument of a directory from which to resolve the path" do
+ File.absolute_path(__FILE__, File.dirname(__FILE__)).should == @abs
+ end
+
+ it "calls #to_path on its argument" do
+ File.absolute_path(mock_to_path(@abs)).should == @abs
+ end
+end
diff --git a/spec/ruby/core/file/atime_spec.rb b/spec/ruby/core/file/atime_spec.rb
new file mode 100644
index 0000000000..3df258016c
--- /dev/null
+++ b/spec/ruby/core/file/atime_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+
+describe "File.atime" do
+ before :each do
+ @file = tmp('test.txt')
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the last access time for the named file as a Time object" do
+ File.atime(@file)
+ File.atime(@file).should be_kind_of(Time)
+ end
+
+ platform_is :linux do
+ unless ENV.key?('TRAVIS') # https://bugs.ruby-lang.org/issues/17926
+ ## NOTE also that some Linux systems disable atime (e.g. via mount params) for better filesystem speed.
+ it "returns the last access time for the named file with microseconds" do
+ supports_subseconds = Integer(`stat -c%x '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10)
+ if supports_subseconds != 0
+ expected_time = Time.at(Time.now.to_i + 0.123456)
+ File.utime expected_time, 0, @file
+ File.atime(@file).usec.should == expected_time.usec
+ else
+ File.atime(__FILE__).usec.should == 0
+ end
+ end
+ end
+ end
+
+ it "raises an Errno::ENOENT exception if the file is not found" do
+ -> { File.atime('a_fake_file') }.should raise_error(Errno::ENOENT)
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.atime(mock_to_path(@file))
+ end
+end
+
+describe "File#atime" do
+ before :each do
+ @name = File.expand_path(__FILE__)
+ @file = File.open(@name)
+ end
+
+ after :each do
+ @file.close rescue nil
+ end
+
+ it "returns the last access time to self" do
+ @file.atime
+ @file.atime.should be_kind_of(Time)
+ end
+end
diff --git a/spec/ruby/core/file/basename_spec.rb b/spec/ruby/core/file/basename_spec.rb
new file mode 100644
index 0000000000..989409d76b
--- /dev/null
+++ b/spec/ruby/core/file/basename_spec.rb
@@ -0,0 +1,183 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+# TODO: Fix these
+describe "File.basename" do
+ it "returns the basename of a path (basic cases)" do
+ File.basename("/Some/path/to/test.txt").should == "test.txt"
+ File.basename(File.join("/tmp")).should == "tmp"
+ File.basename(File.join(*%w( g f d s a b))).should == "b"
+ File.basename("/tmp", ".*").should == "tmp"
+ File.basename("/tmp", ".c").should == "tmp"
+ File.basename("/tmp.c", ".c").should == "tmp"
+ File.basename("/tmp.c", ".*").should == "tmp"
+ File.basename("/tmp.c", ".?").should == "tmp.c"
+ File.basename("/tmp.cpp", ".*").should == "tmp"
+ File.basename("/tmp.cpp", ".???").should == "tmp.cpp"
+ File.basename("/tmp.o", ".c").should == "tmp.o"
+ File.basename(File.join("/tmp/")).should == "tmp"
+ File.basename("/").should == "/"
+ File.basename("//").should == "/"
+ File.basename("dir///base", ".*").should == "base"
+ File.basename("dir///base", ".c").should == "base"
+ File.basename("dir///base.c", ".c").should == "base"
+ File.basename("dir///base.c", ".*").should == "base"
+ File.basename("dir///base.o", ".c").should == "base.o"
+ File.basename("dir///base///").should == "base"
+ File.basename("dir//base/", ".*").should == "base"
+ File.basename("dir//base/", ".c").should == "base"
+ File.basename("dir//base.c/", ".c").should == "base"
+ File.basename("dir//base.c/", ".*").should == "base"
+ end
+
+ it "returns the last component of the filename" do
+ File.basename('a').should == 'a'
+ File.basename('/a').should == 'a'
+ File.basename('/a/b').should == 'b'
+ File.basename('/ab/ba/bag').should == 'bag'
+ File.basename('/ab/ba/bag.txt').should == 'bag.txt'
+ File.basename('/').should == '/'
+ File.basename('/foo/bar/baz.rb', '.rb').should == 'baz'
+ File.basename('baz.rb', 'z.rb').should == 'ba'
+ end
+
+ it "returns an string" do
+ File.basename("foo").should be_kind_of(String)
+ end
+
+ it "returns the basename for unix format" do
+ File.basename("/foo/bar").should == "bar"
+ File.basename("/foo/bar.txt").should == "bar.txt"
+ File.basename("bar.c").should == "bar.c"
+ File.basename("/bar").should == "bar"
+ File.basename("/bar/").should == "bar"
+
+ # Considered UNC paths on Windows
+ platform_is :windows do
+ File.basename("baz//foo").should =="foo"
+ File.basename("//foo/bar/baz").should == "baz"
+ end
+ end
+
+ it "returns the basename for edge cases" do
+ File.basename("").should == ""
+ File.basename(".").should == "."
+ File.basename("..").should == ".."
+ platform_is_not :windows do
+ File.basename("//foo/").should == "foo"
+ File.basename("//foo//").should == "foo"
+ end
+ File.basename("foo/").should == "foo"
+ end
+
+ it "ignores a trailing directory separator" do
+ File.basename("foo.rb/", '.rb').should == "foo"
+ File.basename("bar.rb///", '.*').should == "bar"
+ end
+
+ it "returns the basename for unix suffix" do
+ File.basename("bar.c", ".c").should == "bar"
+ File.basename("bar.txt", ".txt").should == "bar"
+ File.basename("/bar.txt", ".txt").should == "bar"
+ File.basename("/foo/bar.txt", ".txt").should == "bar"
+ File.basename("bar.txt", ".exe").should == "bar.txt"
+ File.basename("bar.txt.exe", ".exe").should == "bar.txt"
+ File.basename("bar.txt.exe", ".txt").should == "bar.txt.exe"
+ File.basename("bar.txt", ".*").should == "bar"
+ File.basename("bar.txt.exe", ".*").should == "bar.txt"
+ File.basename("bar.txt.exe", ".txt.exe").should == "bar"
+ end
+
+ platform_is_not :windows do
+ it "takes into consideration the platform path separator(s)" do
+ File.basename("C:\\foo\\bar").should == "C:\\foo\\bar"
+ File.basename("C:/foo/bar").should == "bar"
+ File.basename("/foo/bar\\baz").should == "bar\\baz"
+ end
+ end
+
+ platform_is :windows do
+ it "takes into consideration the platform path separator(s)" do
+ File.basename("C:\\foo\\bar").should == "bar"
+ File.basename("C:/foo/bar").should == "bar"
+ File.basename("/foo/bar\\baz").should == "baz"
+ end
+ end
+
+ it "raises a TypeError if the arguments are not String types" do
+ -> { File.basename(nil) }.should raise_error(TypeError)
+ -> { File.basename(1) }.should raise_error(TypeError)
+ -> { File.basename("bar.txt", 1) }.should raise_error(TypeError)
+ -> { File.basename(true) }.should raise_error(TypeError)
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.basename(mock_to_path("foo.txt"))
+ end
+
+ it "raises an ArgumentError if passed more than two arguments" do
+ -> { File.basename('bar.txt', '.txt', '.txt') }.should raise_error(ArgumentError)
+ end
+
+ # specific to MS Windows
+ platform_is :windows do
+ it "returns the basename for windows" do
+ File.basename("C:\\foo\\bar\\baz.txt").should == "baz.txt"
+ File.basename("C:\\foo\\bar").should == "bar"
+ File.basename("C:\\foo\\bar\\").should == "bar"
+ File.basename("C:\\foo").should == "foo"
+ File.basename("C:\\").should == "\\"
+ end
+
+ it "returns basename windows unc" do
+ File.basename("\\\\foo\\bar\\baz.txt").should == "baz.txt"
+ File.basename("\\\\foo\\bar\\baz").should =="baz"
+ end
+
+ it "returns basename windows forward slash" do
+ File.basename("C:/").should == "/"
+ File.basename("C:/foo").should == "foo"
+ File.basename("C:/foo/bar").should == "bar"
+ File.basename("C:/foo/bar/").should == "bar"
+ File.basename("C:/foo/bar//").should == "bar"
+ end
+
+ it "returns basename with windows suffix" do
+ File.basename("c:\\bar.txt", ".txt").should == "bar"
+ File.basename("c:\\foo\\bar.txt", ".txt").should == "bar"
+ File.basename("c:\\bar.txt", ".exe").should == "bar.txt"
+ File.basename("c:\\bar.txt.exe", ".exe").should == "bar.txt"
+ File.basename("c:\\bar.txt.exe", ".txt").should == "bar.txt.exe"
+ File.basename("c:\\bar.txt", ".*").should == "bar"
+ File.basename("c:\\bar.txt.exe", ".*").should == "bar.txt"
+ end
+ end
+
+
+ it "returns the extension for a multibyte filename" do
+ File.basename('/path/ОфиÑ.m4a').should == "ОфиÑ.m4a"
+ end
+
+ it "returns the basename with the same encoding as the original" do
+ basename = File.basename('C:/Users/Scuby Pagrubý'.encode(Encoding::Windows_1250))
+ basename.should == 'Scuby Pagrubý'.encode(Encoding::Windows_1250)
+ basename.encoding.should == Encoding::Windows_1250
+ end
+
+ it "returns a new unfrozen String" do
+ exts = [nil, '.rb', '.*', '.txt']
+ ['foo.rb','//', '/test/', 'test'].each do |example|
+ exts.each do |ext|
+ original = example.freeze
+ result = if ext
+ File.basename(original, ext)
+ else
+ File.basename(original)
+ end
+ result.should_not equal(original)
+ result.frozen?.should == false
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/core/file/birthtime_spec.rb b/spec/ruby/core/file/birthtime_spec.rb
new file mode 100644
index 0000000000..755601df64
--- /dev/null
+++ b/spec/ruby/core/file/birthtime_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+
+describe "File.birthtime" do
+ before :each do
+ @file = __FILE__
+ end
+
+ after :each do
+ @file = nil
+ end
+
+ platform_is :windows, :darwin, :freebsd, :netbsd do
+ it "returns the birth time for the named file as a Time object" do
+ File.birthtime(@file)
+ File.birthtime(@file).should be_kind_of(Time)
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.birthtime(mock_to_path(@file))
+ end
+
+ it "raises an Errno::ENOENT exception if the file is not found" do
+ -> { File.birthtime('bogus') }.should raise_error(Errno::ENOENT)
+ end
+ end
+
+ platform_is :openbsd do
+ it "raises an NotImplementedError" do
+ -> { File.birthtime(@file) }.should raise_error(NotImplementedError)
+ end
+ end
+
+ # TODO: depends on Linux kernel version
+end
+
+describe "File#birthtime" do
+ before :each do
+ @file = File.open(__FILE__)
+ end
+
+ after :each do
+ @file.close
+ @file = nil
+ end
+
+ platform_is :windows, :darwin, :freebsd, :netbsd do
+ it "returns the birth time for self" do
+ @file.birthtime
+ @file.birthtime.should be_kind_of(Time)
+ end
+ end
+
+ platform_is :openbsd do
+ it "raises an NotImplementedError" do
+ -> { @file.birthtime }.should raise_error(NotImplementedError)
+ end
+ end
+
+ # TODO: depends on Linux kernel version
+end
diff --git a/spec/ruby/core/file/blockdev_spec.rb b/spec/ruby/core/file/blockdev_spec.rb
new file mode 100644
index 0000000000..9ba9afc251
--- /dev/null
+++ b/spec/ruby/core/file/blockdev_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/blockdev'
+
+describe "File.blockdev?" do
+ it_behaves_like :file_blockdev, :blockdev?, File
+end
diff --git a/spec/ruby/core/file/chardev_spec.rb b/spec/ruby/core/file/chardev_spec.rb
new file mode 100644
index 0000000000..1fc932ee4e
--- /dev/null
+++ b/spec/ruby/core/file/chardev_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/chardev'
+
+describe "File.chardev?" do
+ it_behaves_like :file_chardev, :chardev?, File
+end
diff --git a/spec/ruby/core/file/chmod_spec.rb b/spec/ruby/core/file/chmod_spec.rb
new file mode 100644
index 0000000000..5ca15c9748
--- /dev/null
+++ b/spec/ruby/core/file/chmod_spec.rb
@@ -0,0 +1,185 @@
+require_relative '../../spec_helper'
+
+describe "File#chmod" do
+ before :each do
+ @filename = tmp('i_exist.exe')
+ @file = File.open(@filename, 'w')
+ end
+
+ after :each do
+ @file.close
+ rm_r @filename
+ end
+
+ it "returns 0 if successful" do
+ @file.chmod(0755).should == 0
+ end
+
+ it "raises RangeError with too large values" do
+ -> { @file.chmod(2**64) }.should raise_error(RangeError)
+ -> { @file.chmod(-2**63 - 1) }.should raise_error(RangeError)
+ end
+
+ it "invokes to_int on non-integer argument" do
+ mode = File.stat(@filename).mode
+ (obj = mock('mode')).should_receive(:to_int).and_return(mode)
+ @file.chmod(obj)
+ File.stat(@filename).mode.should == mode
+ end
+
+ platform_is :windows do
+ it "with '0444' makes file readable and executable but not writable" do
+ @file.chmod(0444)
+ File.readable?(@filename).should == true
+ File.writable?(@filename).should == false
+ File.executable?(@filename).should == true
+ end
+
+ it "with '0644' makes file readable and writable and also executable" do
+ @file.chmod(0644)
+ File.readable?(@filename).should == true
+ File.writable?(@filename).should == true
+ File.executable?(@filename).should == true
+ end
+ end
+
+ platform_is_not :windows do
+ as_user do
+ it "with '0222' makes file writable but not readable or executable" do
+ @file.chmod(0222)
+ File.readable?(@filename).should == false
+ File.writable?(@filename).should == true
+ File.executable?(@filename).should == false
+ end
+
+ it "with '0444' makes file readable but not writable or executable" do
+ @file.chmod(0444)
+ File.readable?(@filename).should == true
+ File.writable?(@filename).should == false
+ File.executable?(@filename).should == false
+ end
+
+ it "with '0666' makes file readable and writable but not executable" do
+ @file.chmod(0666)
+ File.readable?(@filename).should == true
+ File.writable?(@filename).should == true
+ File.executable?(@filename).should == false
+ end
+
+ it "with '0111' makes file executable but not readable or writable" do
+ @file.chmod(0111)
+ File.readable?(@filename).should == false
+ File.writable?(@filename).should == false
+ File.executable?(@filename).should == true
+ end
+
+ it "modifies the permission bits of the files specified" do
+ @file.chmod(0755)
+ File.stat(@filename).mode.should == 33261
+ end
+ end
+ end
+end
+
+describe "File.chmod" do
+ before :each do
+ @file = tmp('i_exist.exe')
+ touch @file
+ @count = File.chmod(0755, @file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the number of files modified" do
+ @count.should == 1
+ end
+
+ it "raises RangeError with too large values" do
+ -> { File.chmod(2**64, @file) }.should raise_error(RangeError)
+ -> { File.chmod(-2**63 - 1, @file) }.should raise_error(RangeError)
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.chmod(0, mock_to_path(@file))
+ end
+
+ it "throws a TypeError if the given path is not coercible into a string" do
+ -> { File.chmod(0, []) }.should raise_error(TypeError)
+ end
+
+ it "raises an error for a non existent path" do
+ -> {
+ File.chmod(0644, "#{@file}.not.existing")
+ }.should raise_error(Errno::ENOENT)
+ end
+
+ it "invokes to_int on non-integer argument" do
+ mode = File.stat(@file).mode
+ (obj = mock('mode')).should_receive(:to_int).and_return(mode)
+ File.chmod(obj, @file)
+ File.stat(@file).mode.should == mode
+ end
+
+ it "invokes to_str on non-string file names" do
+ mode = File.stat(@file).mode
+ (obj = mock('path')).should_receive(:to_str).and_return(@file)
+ File.chmod(mode, obj)
+ File.stat(@file).mode.should == mode
+ end
+
+ platform_is :windows do
+ it "with '0444' makes file readable and executable but not writable" do
+ File.chmod(0444, @file)
+ File.readable?(@file).should == true
+ File.writable?(@file).should == false
+ File.executable?(@file).should == true
+ end
+
+ it "with '0644' makes file readable and writable and also executable" do
+ File.chmod(0644, @file)
+ File.readable?(@file).should == true
+ File.writable?(@file).should == true
+ File.executable?(@file).should == true
+ end
+ end
+
+ platform_is_not :windows do
+ as_user do
+ it "with '0222' makes file writable but not readable or executable" do
+ File.chmod(0222, @file)
+ File.readable?(@file).should == false
+ File.writable?(@file).should == true
+ File.executable?(@file).should == false
+ end
+
+ it "with '0444' makes file readable but not writable or executable" do
+ File.chmod(0444, @file)
+ File.readable?(@file).should == true
+ File.writable?(@file).should == false
+ File.executable?(@file).should == false
+ end
+ end
+
+ it "with '0666' makes file readable and writable but not executable" do
+ File.chmod(0666, @file)
+ File.readable?(@file).should == true
+ File.writable?(@file).should == true
+ File.executable?(@file).should == false
+ end
+
+ as_user do
+ it "with '0111' makes file executable but not readable or writable" do
+ File.chmod(0111, @file)
+ File.readable?(@file).should == false
+ File.writable?(@file).should == false
+ File.executable?(@file).should == true
+ end
+ end
+
+ it "modifies the permission bits of the files specified" do
+ File.stat(@file).mode.should == 33261
+ end
+ end
+end
diff --git a/spec/ruby/core/file/chown_spec.rb b/spec/ruby/core/file/chown_spec.rb
new file mode 100644
index 0000000000..8cc8f0d04b
--- /dev/null
+++ b/spec/ruby/core/file/chown_spec.rb
@@ -0,0 +1,144 @@
+require_relative '../../spec_helper'
+
+describe "File.chown" do
+ before :each do
+ @fname = tmp('file_chown_test')
+ touch @fname
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ as_superuser do
+ platform_is :windows do
+ it "does not modify the owner id of the file" do
+ File.chown 0, nil, @fname
+ File.stat(@fname).uid.should == 0
+ File.chown 501, nil, @fname
+ File.stat(@fname).uid.should == 0
+ end
+
+ it "does not modify the group id of the file" do
+ File.chown nil, 0, @fname
+ File.stat(@fname).gid.should == 0
+ File.chown nil, 501, @fname
+ File.stat(@fname).gid.should == 0
+ end
+ end
+
+ platform_is_not :windows do
+ it "changes the owner id of the file" do
+ File.chown 501, nil, @fname
+ File.stat(@fname).uid.should == 501
+ File.chown 0, nil, @fname
+ File.stat(@fname).uid.should == 0
+ end
+
+ it "changes the group id of the file" do
+ File.chown nil, 501, @fname
+ File.stat(@fname).gid.should == 501
+ File.chown nil, 0, @fname
+ File.stat(@fname).uid.should == 0
+ end
+
+ it "does not modify the owner id of the file if passed nil or -1" do
+ File.chown 501, nil, @fname
+ File.chown nil, nil, @fname
+ File.stat(@fname).uid.should == 501
+ File.chown nil, -1, @fname
+ File.stat(@fname).uid.should == 501
+ end
+
+ it "does not modify the group id of the file if passed nil or -1" do
+ File.chown nil, 501, @fname
+ File.chown nil, nil, @fname
+ File.stat(@fname).gid.should == 501
+ File.chown nil, -1, @fname
+ File.stat(@fname).gid.should == 501
+ end
+ end
+ end
+
+ it "returns the number of files processed" do
+ File.chown(nil, nil, @fname, @fname).should == 2
+ end
+
+ platform_is_not :windows do
+ it "raises an error for a non existent path" do
+ -> {
+ File.chown(nil, nil, "#{@fname}_not_existing")
+ }.should raise_error(Errno::ENOENT)
+ end
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.chown(nil, nil, mock_to_path(@fname)).should == 1
+ end
+end
+
+describe "File#chown" do
+ before :each do
+ @fname = tmp('file_chown_test')
+ @file = File.open(@fname, 'w')
+ end
+
+ after :each do
+ @file.close unless @file.closed?
+ rm_r @fname
+ end
+
+ as_superuser do
+ platform_is :windows do
+ it "does not modify the owner id of the file" do
+ @file.chown 0, nil
+ @file.stat.uid.should == 0
+ @file.chown 501, nil
+ @file.stat.uid.should == 0
+ end
+
+ it "does not modify the group id of the file" do
+ @file.chown nil, 0
+ @file.stat.gid.should == 0
+ @file.chown nil, 501
+ @file.stat.gid.should == 0
+ end
+ end
+
+ platform_is_not :windows do
+ it "changes the owner id of the file" do
+ @file.chown 501, nil
+ @file.stat.uid.should == 501
+ @file.chown 0, nil
+ @file.stat.uid.should == 0
+ end
+
+ it "changes the group id of the file" do
+ @file.chown nil, 501
+ @file.stat.gid.should == 501
+ @file.chown nil, 0
+ @file.stat.uid.should == 0
+ end
+
+ it "does not modify the owner id of the file if passed nil or -1" do
+ @file.chown 501, nil
+ @file.chown nil, nil
+ @file.stat.uid.should == 501
+ @file.chown nil, -1
+ @file.stat.uid.should == 501
+ end
+
+ it "does not modify the group id of the file if passed nil or -1" do
+ @file.chown nil, 501
+ @file.chown nil, nil
+ @file.stat.gid.should == 501
+ @file.chown nil, -1
+ @file.stat.gid.should == 501
+ end
+ end
+ end
+
+ it "returns 0" do
+ @file.chown(nil, nil).should == 0
+ end
+end
diff --git a/spec/ruby/core/file/constants/constants_spec.rb b/spec/ruby/core/file/constants/constants_spec.rb
new file mode 100644
index 0000000000..86946822c5
--- /dev/null
+++ b/spec/ruby/core/file/constants/constants_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+["APPEND", "CREAT", "EXCL", "FNM_CASEFOLD",
+ "FNM_DOTMATCH", "FNM_EXTGLOB", "FNM_NOESCAPE", "FNM_PATHNAME",
+ "FNM_SYSCASE", "LOCK_EX", "LOCK_NB", "LOCK_SH",
+ "LOCK_UN", "NONBLOCK", "RDONLY",
+ "RDWR", "TRUNC", "WRONLY"].each do |const|
+ describe "File::Constants::#{const}" do
+ it "is defined" do
+ File::Constants.const_defined?(const).should be_true
+ end
+ end
+end
+
+platform_is :windows do
+ describe "File::Constants::BINARY" do
+ it "is defined" do
+ File::Constants.const_defined?(:BINARY).should be_true
+ end
+ end
+end
+
+platform_is_not :windows do
+ ["NOCTTY", "SYNC"].each do |const|
+ describe "File::Constants::#{const}" do
+ it "is defined" do
+ File::Constants.const_defined?(const).should be_true
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/constants_spec.rb b/spec/ruby/core/file/constants_spec.rb
new file mode 100644
index 0000000000..5f058a7f40
--- /dev/null
+++ b/spec/ruby/core/file/constants_spec.rb
@@ -0,0 +1,141 @@
+require_relative '../../spec_helper'
+
+# TODO: migrate these to constants/constants_spec.rb
+
+describe "File::Constants" do
+ it "matches mode constants" do
+ File::FNM_NOESCAPE.should_not == nil
+ File::FNM_PATHNAME.should_not == nil
+ File::FNM_DOTMATCH.should_not == nil
+ File::FNM_CASEFOLD.should_not == nil
+ File::FNM_SYSCASE.should_not == nil
+
+ platform_is :windows do #|| VMS
+ File::FNM_SYSCASE.should == 8
+ end
+ end
+
+ # Only these constants are not inherited from the IO class
+ it "the separator constant" do
+ File::SEPARATOR.should_not == nil
+ File::Separator.should_not == nil
+ File::PATH_SEPARATOR.should_not == nil
+ File::SEPARATOR.should == "/"
+
+ platform_is :windows do #|| VMS
+ File::ALT_SEPARATOR.should_not == nil
+ File::PATH_SEPARATOR.should == ";"
+ end
+
+ platform_is_not :windows do
+ File::ALT_SEPARATOR.should == nil
+ File::PATH_SEPARATOR.should == ":"
+ end
+ end
+
+ it "the open mode constants" do
+ File::APPEND.should_not == nil
+ File::CREAT.should_not == nil
+ File::EXCL.should_not == nil
+ File::NONBLOCK.should_not == nil
+ File::RDONLY.should_not == nil
+ File::RDWR.should_not == nil
+ File::TRUNC.should_not == nil
+ File::WRONLY.should_not == nil
+
+ platform_is_not :windows do # Not sure about VMS here
+ File::NOCTTY.should_not == nil
+ end
+ end
+
+ it "lock mode constants" do
+ File::LOCK_EX.should_not == nil
+ File::LOCK_NB.should_not == nil
+ File::LOCK_SH.should_not == nil
+ File::LOCK_UN.should_not == nil
+ end
+end
+
+describe "File::Constants" do
+ # These mode and permission bits are platform dependent
+ it "File::RDONLY" do
+ defined?(File::RDONLY).should == "constant"
+ end
+
+ it "File::WRONLY" do
+ defined?(File::WRONLY).should == "constant"
+ end
+
+ it "File::CREAT" do
+ defined?(File::CREAT).should == "constant"
+ end
+
+ it "File::RDWR" do
+ defined?(File::RDWR).should == "constant"
+ end
+
+ it "File::APPEND" do
+ defined?(File::APPEND).should == "constant"
+ end
+
+ it "File::TRUNC" do
+ defined?(File::TRUNC).should == "constant"
+ end
+
+ platform_is_not :windows do # Not sure about VMS here
+ it "File::NOCTTY" do
+ defined?(File::NOCTTY).should == "constant"
+ end
+ end
+
+ it "File::NONBLOCK" do
+ defined?(File::NONBLOCK).should == "constant"
+ end
+
+ it "File::LOCK_EX" do
+ defined?(File::LOCK_EX).should == "constant"
+ end
+
+ it "File::LOCK_NB" do
+ defined?(File::LOCK_NB).should == "constant"
+ end
+
+ it "File::LOCK_SH" do
+ defined?(File::LOCK_SH).should == "constant"
+ end
+
+ it "File::LOCK_UN" do
+ defined?(File::LOCK_UN).should == "constant"
+ end
+
+ it "File::SEPARATOR" do
+ defined?(File::SEPARATOR).should == "constant"
+ end
+ it "File::Separator" do
+ defined?(File::Separator).should == "constant"
+ end
+
+ it "File::PATH_SEPARATOR" do
+ defined?(File::PATH_SEPARATOR).should == "constant"
+ end
+
+ it "File::SEPARATOR" do
+ defined?(File::SEPARATOR).should == "constant"
+ File::SEPARATOR.should == "/"
+ end
+
+ platform_is :windows do #|| VMS
+ it "File::ALT_SEPARATOR" do
+ defined?(File::ALT_SEPARATOR).should == "constant"
+ File::PATH_SEPARATOR.should == ";"
+ end
+ end
+
+ platform_is_not :windows do
+ it "File::PATH_SEPARATOR" do
+ defined?(File::PATH_SEPARATOR).should == "constant"
+ File::PATH_SEPARATOR.should == ":"
+ end
+ end
+
+end
diff --git a/spec/ruby/core/file/ctime_spec.rb b/spec/ruby/core/file/ctime_spec.rb
new file mode 100644
index 0000000000..9b7ab272ff
--- /dev/null
+++ b/spec/ruby/core/file/ctime_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+
+describe "File.ctime" do
+ before :each do
+ @file = __FILE__
+ end
+
+ after :each do
+ @file = nil
+ end
+
+ it "returns the change time for the named file (the time at which directory information about the file was changed, not the file itself)." do
+ File.ctime(@file)
+ File.ctime(@file).should be_kind_of(Time)
+ end
+
+ platform_is :linux do
+ it "returns the change time for the named file (the time at which directory information about the file was changed, not the file itself) with microseconds." do
+ supports_subseconds = Integer(`stat -c%z '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10)
+ if supports_subseconds != 0
+ File.ctime(__FILE__).usec.should > 0
+ else
+ File.ctime(__FILE__).usec.should == 0
+ end
+ end
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.ctime(mock_to_path(@file))
+ end
+
+ it "raises an Errno::ENOENT exception if the file is not found" do
+ -> { File.ctime('bogus') }.should raise_error(Errno::ENOENT)
+ end
+end
+
+describe "File#ctime" do
+ before :each do
+ @file = File.open(__FILE__)
+ end
+
+ after :each do
+ @file.close
+ @file = nil
+ end
+
+ it "returns the change time for the named file (the time at which directory information about the file was changed, not the file itself)." do
+ @file.ctime
+ @file.ctime.should be_kind_of(Time)
+ end
+end
diff --git a/spec/ruby/core/file/delete_spec.rb b/spec/ruby/core/file/delete_spec.rb
new file mode 100644
index 0000000000..4098499942
--- /dev/null
+++ b/spec/ruby/core/file/delete_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/unlink'
+
+describe "File.delete" do
+ it_behaves_like :file_unlink, :delete
+end
diff --git a/spec/ruby/core/file/directory_spec.rb b/spec/ruby/core/file/directory_spec.rb
new file mode 100644
index 0000000000..8014a7a03d
--- /dev/null
+++ b/spec/ruby/core/file/directory_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/directory'
+
+describe "File.directory?" do
+ it_behaves_like :file_directory, :directory?, File
+end
+
+describe "File.directory?" do
+ it_behaves_like :file_directory_io, :directory?, File
+end
diff --git a/spec/ruby/core/file/dirname_spec.rb b/spec/ruby/core/file/dirname_spec.rb
new file mode 100644
index 0000000000..cf0f909f59
--- /dev/null
+++ b/spec/ruby/core/file/dirname_spec.rb
@@ -0,0 +1,124 @@
+require_relative '../../spec_helper'
+
+describe "File.dirname" do
+ it "returns all the components of filename except the last one" do
+ File.dirname('/home/jason').should == '/home'
+ File.dirname('/home/jason/poot.txt').should == '/home/jason'
+ File.dirname('poot.txt').should == '.'
+ File.dirname('/holy///schnikies//w00t.bin').should == '/holy///schnikies'
+ File.dirname('').should == '.'
+ File.dirname('/').should == '/'
+ File.dirname('/foo/foo').should == '/foo'
+ end
+
+ ruby_version_is '3.1' do
+ it "returns all the components of filename except the last parts by the level" do
+ File.dirname('/home/jason', 2).should == '/'
+ File.dirname('/home/jason/poot.txt', 2).should == '/home'
+ end
+
+ it "returns the same string if the level is 0" do
+ File.dirname('poot.txt', 0).should == 'poot.txt'
+ File.dirname('/', 0).should == '/'
+ end
+
+ it "raises ArgumentError if the level is negative" do
+ -> {File.dirname('/home/jason', -1)}.should raise_error(ArgumentError)
+ end
+ end
+
+ it "returns a String" do
+ File.dirname("foo").should be_kind_of(String)
+ end
+
+ it "does not modify its argument" do
+ x = "/usr/bin"
+ File.dirname(x)
+ x.should == "/usr/bin"
+ end
+
+ it "ignores a trailing /" do
+ File.dirname("/foo/bar/").should == "/foo"
+ end
+
+ it "returns the return all the components of filename except the last one (unix format)" do
+ File.dirname("foo").should =="."
+ File.dirname("/foo").should =="/"
+ File.dirname("/foo/bar").should =="/foo"
+ File.dirname("/foo/bar.txt").should =="/foo"
+ File.dirname("/foo/bar/baz").should =="/foo/bar"
+ end
+
+ it "returns all the components of filename except the last one (edge cases on all platforms)" do
+ File.dirname("").should == "."
+ File.dirname(".").should == "."
+ File.dirname("./").should == "."
+ File.dirname("./b/./").should == "./b"
+ File.dirname("..").should == "."
+ File.dirname("../").should == "."
+ File.dirname("/").should == "/"
+ File.dirname("/.").should == "/"
+ File.dirname("/foo/").should == "/"
+ File.dirname("/foo/.").should == "/foo"
+ File.dirname("/foo/./").should == "/foo"
+ File.dirname("/foo/../.").should == "/foo/.."
+ File.dirname("foo/../").should == "foo"
+ end
+
+ platform_is_not :windows do
+ it "returns all the components of filename except the last one (edge cases on non-windows)" do
+ File.dirname('/////').should == '/'
+ File.dirname("//foo//").should == "/"
+ File.dirname('foo\bar').should == '.'
+ File.dirname('/foo\bar').should == '/'
+ File.dirname('foo/bar\baz').should == 'foo'
+ end
+ end
+
+ platform_is :windows do
+ it "returns all the components of filename except the last one (edge cases on windows)" do
+ File.dirname("//foo").should == "//foo"
+ File.dirname("//foo//").should == "//foo"
+ File.dirname('/////').should == '//'
+ end
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.dirname(mock_to_path("/")).should == "/"
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { File.dirname(nil) }.should raise_error(TypeError)
+ -> { File.dirname(0) }.should raise_error(TypeError)
+ -> { File.dirname(true) }.should raise_error(TypeError)
+ -> { File.dirname(false) }.should raise_error(TypeError)
+ end
+
+ # Windows specific tests
+ platform_is :windows do
+ it "returns the return all the components of filename except the last one (Windows format)" do
+ File.dirname("C:\\foo\\bar\\baz.txt").should =="C:\\foo\\bar"
+ File.dirname("C:\\foo\\bar").should =="C:\\foo"
+ File.dirname("C:\\foo\\bar\\").should == "C:\\foo"
+ File.dirname("C:\\foo").should == "C:\\"
+ File.dirname("C:\\").should =="C:\\"
+ end
+
+ it "returns the return all the components of filename except the last one (windows unc)" do
+ File.dirname("\\\\foo\\bar\\baz.txt").should == "\\\\foo\\bar"
+ File.dirname("\\\\foo\\bar\\baz").should == "\\\\foo\\bar"
+ File.dirname("\\\\foo").should =="\\\\foo"
+ File.dirname("\\\\foo\\bar").should =="\\\\foo\\bar"
+ File.dirname("\\\\\\foo\\bar").should =="\\\\foo\\bar"
+ File.dirname("\\\\\\foo").should =="\\\\foo"
+ end
+
+ it "returns the return all the components of filename except the last one (forward_slash)" do
+ File.dirname("C:/").should == "C:/"
+ File.dirname("C:/foo").should == "C:/"
+ File.dirname("C:/foo/bar").should == "C:/foo"
+ File.dirname("C:/foo/bar/").should == "C:/foo"
+ File.dirname("C:/foo/bar//").should == "C:/foo"
+ end
+ end
+end
diff --git a/spec/ruby/core/file/empty_spec.rb b/spec/ruby/core/file/empty_spec.rb
new file mode 100644
index 0000000000..77f132303e
--- /dev/null
+++ b/spec/ruby/core/file/empty_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/zero'
+
+describe "File.empty?" do
+ it_behaves_like :file_zero, :empty?, File
+ it_behaves_like :file_zero_missing, :empty?, File
+
+ platform_is :solaris do
+ it "returns false for /dev/null" do
+ File.empty?('/dev/null').should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/file/executable_real_spec.rb b/spec/ruby/core/file/executable_real_spec.rb
new file mode 100644
index 0000000000..0cb848b201
--- /dev/null
+++ b/spec/ruby/core/file/executable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/executable_real'
+
+describe "File.executable_real?" do
+ it_behaves_like :file_executable_real, :executable_real?, File
+ it_behaves_like :file_executable_real_missing, :executable_real?, File
+end
diff --git a/spec/ruby/core/file/executable_spec.rb b/spec/ruby/core/file/executable_spec.rb
new file mode 100644
index 0000000000..1dbb3b233d
--- /dev/null
+++ b/spec/ruby/core/file/executable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/executable'
+
+describe "File.executable?" do
+ it_behaves_like :file_executable, :executable?, File
+ it_behaves_like :file_executable_missing, :executable?, File
+end
diff --git a/spec/ruby/core/file/exist_spec.rb b/spec/ruby/core/file/exist_spec.rb
new file mode 100644
index 0000000000..ddb5febcba
--- /dev/null
+++ b/spec/ruby/core/file/exist_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/exist'
+
+describe "File.exist?" do
+ it_behaves_like :file_exist, :exist?, File
+end
diff --git a/spec/ruby/core/file/expand_path_spec.rb b/spec/ruby/core/file/expand_path_spec.rb
new file mode 100644
index 0000000000..c31f885b92
--- /dev/null
+++ b/spec/ruby/core/file/expand_path_spec.rb
@@ -0,0 +1,265 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require 'etc'
+
+describe "File.expand_path" do
+ before :each do
+ platform_is :windows do
+ @base = `cd`.chomp.tr '\\', '/'
+ @tmpdir = "c:/tmp"
+ @rootdir = "c:/"
+ end
+
+ platform_is_not :windows do
+ @base = Dir.pwd
+ @tmpdir = "/tmp"
+ @rootdir = "/"
+ end
+ end
+
+ before :each do
+ @external = Encoding.default_external
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ end
+
+ it "converts a pathname to an absolute pathname" do
+ File.expand_path('').should == @base
+ File.expand_path('a').should == File.join(@base, 'a')
+ File.expand_path('a', nil).should == File.join(@base, 'a')
+ end
+
+ it "converts a pathname to an absolute pathname, Ruby-Talk:18512" do
+ # See Ruby-Talk:18512
+ File.expand_path('.a').should == File.join(@base, '.a')
+ File.expand_path('..a').should == File.join(@base, '..a')
+ File.expand_path('a../b').should == File.join(@base, 'a../b')
+ end
+
+ platform_is_not :windows do
+ it "keeps trailing dots on absolute pathname" do
+ # See Ruby-Talk:18512
+ File.expand_path('a.').should == File.join(@base, 'a.')
+ File.expand_path('a..').should == File.join(@base, 'a..')
+ end
+ end
+
+ it "converts a pathname to an absolute pathname, using a complete path" do
+ File.expand_path("", "#{@tmpdir}").should == "#{@tmpdir}"
+ File.expand_path("a", "#{@tmpdir}").should =="#{@tmpdir}/a"
+ File.expand_path("../a", "#{@tmpdir}/xxx").should == "#{@tmpdir}/a"
+ File.expand_path(".", "#{@rootdir}").should == "#{@rootdir}"
+ end
+
+ platform_is_not :windows do
+ before do
+ @var_home = ENV['HOME'].chomp('/')
+ @db_home = Dir.home(ENV['USER'])
+ end
+
+ # FIXME: these are insane!
+ it "expand path with" do
+ File.expand_path("../../bin", "/tmp/x").should == "/bin"
+ File.expand_path("../../bin", "/tmp").should == "/bin"
+ File.expand_path("../../bin", "/").should == "/bin"
+ File.expand_path("../bin", "tmp/x").should == File.join(@base, 'tmp', 'bin')
+ File.expand_path("../bin", "x/../tmp").should == File.join(@base, 'bin')
+ end
+
+ it "expand_path for common unix path gives a full path" do
+ File.expand_path('/tmp/').should =='/tmp'
+ File.expand_path('/tmp/../../../tmp').should == '/tmp'
+ File.expand_path('').should == Dir.pwd
+ File.expand_path('./////').should == Dir.pwd
+ File.expand_path('.').should == Dir.pwd
+ File.expand_path(Dir.pwd).should == Dir.pwd
+ File.expand_path('~/').should == @var_home
+ File.expand_path('~/..badfilename').should == "#{@var_home}/..badfilename"
+ File.expand_path('~/a','~/b').should == "#{@var_home}/a"
+ File.expand_path('..').should == File.dirname(Dir.pwd)
+ end
+
+ it "does not replace multiple '/' at the beginning of the path" do
+ File.expand_path('////some/path').should == "////some/path"
+ end
+
+ it "replaces multiple '/' with a single '/'" do
+ File.expand_path('/some////path').should == "/some/path"
+ end
+
+ it "raises an ArgumentError if the path is not valid" do
+ -> { File.expand_path("~a_not_existing_user") }.should raise_error(ArgumentError)
+ end
+
+ it "expands ~ENV['USER'] to the user's home directory" do
+ File.expand_path("~#{ENV['USER']}").should == @db_home
+ end
+
+ it "expands ~ENV['USER']/a to a in the user's home directory" do
+ File.expand_path("~#{ENV['USER']}/a").should == "#{@db_home}/a"
+ end
+
+ it "does not expand ~ENV['USER'] when it's not at the start" do
+ File.expand_path("/~#{ENV['USER']}/a").should == "/~#{ENV['USER']}/a"
+ end
+
+ it "expands ../foo with ~/dir as base dir to /path/to/user/home/foo" do
+ File.expand_path('../foo', '~/dir').should == "#{@var_home}/foo"
+ end
+ end
+
+ it "accepts objects that have a #to_path method" do
+ File.expand_path(mock_to_path("a"), mock_to_path("#{@tmpdir}"))
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { File.expand_path(1) }.should raise_error(TypeError)
+ -> { File.expand_path(nil) }.should raise_error(TypeError)
+ -> { File.expand_path(true) }.should raise_error(TypeError)
+ end
+
+ platform_is_not :windows do
+ it "expands /./dir to /dir" do
+ File.expand_path("/./dir").should == "/dir"
+ end
+ end
+
+ platform_is :windows do
+ it "expands C:/./dir to C:/dir" do
+ File.expand_path("C:/./dir").should == "C:/dir"
+ end
+ end
+
+ it "returns a String in the same encoding as the argument" do
+ Encoding.default_external = Encoding::SHIFT_JIS
+
+ path = "./a".force_encoding Encoding::CP1251
+ File.expand_path(path).encoding.should equal(Encoding::CP1251)
+
+ weird_path = [222, 173, 190, 175].pack('C*')
+ File.expand_path(weird_path).encoding.should equal(Encoding::BINARY)
+ end
+
+ platform_is_not :windows do
+ it "expands a path when the default external encoding is BINARY" do
+ Encoding.default_external = Encoding::BINARY
+ path_8bit = [222, 173, 190, 175].pack('C*')
+ File.expand_path( path_8bit, @rootdir).should == "#{@rootdir}" + path_8bit
+ end
+ end
+
+ it "expands a path with multi-byte characters" do
+ File.expand_path("Ångström").should == "#{@base}/Ångström"
+ end
+
+ platform_is_not :windows do
+ it "raises an Encoding::CompatibilityError if the external encoding is not compatible" do
+ Encoding.default_external = Encoding::UTF_16BE
+ -> { File.expand_path("./a") }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+
+ it "does not modify the string argument" do
+ str = "./a/b/../c"
+ File.expand_path(str, @base).should == "#{@base}/a/c"
+ str.should == "./a/b/../c"
+ end
+
+ it "does not modify a HOME string argument" do
+ str = "~/a"
+ File.expand_path(str).should == "#{Dir.home}/a"
+ str.should == "~/a"
+ end
+
+ it "returns a String when passed a String subclass" do
+ str = FileSpecs::SubString.new "./a/b/../c"
+ path = File.expand_path(str, @base)
+ path.should == "#{@base}/a/c"
+ path.should be_an_instance_of(String)
+ end
+end
+
+platform_is_not :windows do
+ describe "File.expand_path when HOME is set" do
+ before :each do
+ @home = ENV["HOME"]
+ ENV["HOME"] = "/rubyspec_home"
+ end
+
+ after :each do
+ ENV["HOME"] = @home
+ end
+
+ it "converts a pathname to an absolute pathname, using ~ (home) as base" do
+ home = "/rubyspec_home"
+ File.expand_path('~').should == home
+ File.expand_path('~', '/tmp/gumby/ddd').should == home
+ File.expand_path('~/a', '/tmp/gumby/ddd').should == File.join(home, 'a')
+ end
+
+ it "does not return a frozen string" do
+ home = "/rubyspec_home"
+ File.expand_path('~').should_not.frozen?
+ File.expand_path('~', '/tmp/gumby/ddd').should_not.frozen?
+ File.expand_path('~/a', '/tmp/gumby/ddd').should_not.frozen?
+ end
+ end
+
+
+ describe "File.expand_path when HOME is not set" do
+ before :each do
+ @home = ENV["HOME"]
+ end
+
+ after :each do
+ ENV["HOME"] = @home
+ end
+
+ guard -> {
+ # We need to check if getlogin(3) returns non-NULL,
+ # as MRI only checks getlogin(3) for expanding '~' if $HOME is not set.
+ user = ENV.delete("USER")
+ begin
+ Etc.getlogin != nil
+ rescue
+ false
+ ensure
+ ENV["USER"] = user
+ end
+ } do
+ it "uses the user database when passed '~' if HOME is nil" do
+ ENV.delete "HOME"
+ File.directory?(File.expand_path("~")).should == true
+ end
+
+ it "uses the user database when passed '~/' if HOME is nil" do
+ ENV.delete "HOME"
+ File.directory?(File.expand_path("~/")).should == true
+ end
+ end
+
+ it "raises an ArgumentError when passed '~' if HOME == ''" do
+ ENV["HOME"] = ""
+ -> { File.expand_path("~") }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "File.expand_path with a non-absolute HOME" do
+ before :each do
+ @home = ENV["HOME"]
+ end
+
+ after :each do
+ ENV["HOME"] = @home
+ end
+
+ it "raises an ArgumentError" do
+ ENV["HOME"] = "non-absolute"
+ -> { File.expand_path("~") }.should raise_error(ArgumentError, 'non-absolute home')
+ end
+ end
+end
diff --git a/spec/ruby/core/file/extname_spec.rb b/spec/ruby/core/file/extname_spec.rb
new file mode 100644
index 0000000000..d20cf813d9
--- /dev/null
+++ b/spec/ruby/core/file/extname_spec.rb
@@ -0,0 +1,76 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "File.extname" do
+ it "returns the extension (the portion of file name in path after the period)" do
+ File.extname("foo.rb").should == ".rb"
+ File.extname("/foo/bar.rb").should == ".rb"
+ File.extname("/foo.rb/bar.c").should == ".c"
+ File.extname("bar").should == ""
+ File.extname(".bashrc").should == ""
+ File.extname("/foo.bar/baz").should == ""
+ File.extname(".app.conf").should == ".conf"
+ end
+
+ it "returns unfrozen strings" do
+ File.extname("foo.rb").frozen?.should == false
+ File.extname("/foo/bar.rb").frozen?.should == false
+ File.extname("/foo.rb/bar.c").frozen?.should == false
+ File.extname("bar").frozen?.should == false
+ File.extname(".bashrc").frozen?.should == false
+ File.extname("/foo.bar/baz").frozen?.should == false
+ File.extname(".app.conf").frozen?.should == false
+ end
+
+ it "returns the extension for edge cases" do
+ File.extname("").should == ""
+ File.extname(".").should == ""
+ File.extname("/").should == ""
+ File.extname("/.").should == ""
+ File.extname("..").should == ""
+ File.extname("...").should == ""
+ File.extname("....").should == ""
+ end
+
+ describe "for a filename ending with a dot" do
+ platform_is :windows do
+ it "returns ''" do
+ File.extname(".foo.").should == ""
+ File.extname("foo.").should == ""
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns '.'" do
+ File.extname(".foo.").should == "."
+ File.extname("foo.").should == "."
+ end
+ end
+ end
+
+ it "returns only the last extension of a file with several dots" do
+ File.extname("a.b.c.d.e").should == ".e"
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.extname(mock_to_path("a.b.c.d.e")).should == ".e"
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { File.extname(nil) }.should raise_error(TypeError)
+ -> { File.extname(0) }.should raise_error(TypeError)
+ -> { File.extname(true) }.should raise_error(TypeError)
+ -> { File.extname(false) }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { File.extname }.should raise_error(ArgumentError)
+ -> { File.extname("foo.bar", "foo.baz") }.should raise_error(ArgumentError)
+ end
+
+
+ it "returns the extension for a multibyte filename" do
+ File.extname('ИмÑ.m4a').should == ".m4a"
+ end
+
+end
diff --git a/spec/ruby/core/file/file_spec.rb b/spec/ruby/core/file/file_spec.rb
new file mode 100644
index 0000000000..8a9dfd5fe2
--- /dev/null
+++ b/spec/ruby/core/file/file_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/file'
+
+describe "File" do
+ it "includes Enumerable" do
+ File.include?(Enumerable).should == true
+ end
+
+ it "includes File::Constants" do
+ File.include?(File::Constants).should == true
+ end
+end
+
+describe "File.file?" do
+ it_behaves_like :file_file, :file?, File
+end
diff --git a/spec/ruby/core/file/fixtures/common.rb b/spec/ruby/core/file/fixtures/common.rb
new file mode 100644
index 0000000000..50721388ad
--- /dev/null
+++ b/spec/ruby/core/file/fixtures/common.rb
@@ -0,0 +1,22 @@
+module FileSpecs
+ class SubString < String; end
+
+ def self.make_closer(obj, exc=nil)
+ ScratchPad << :file_opened
+
+ class << obj
+ attr_accessor :close_exception
+
+ alias_method :original_close, :close
+
+ def close
+ original_close
+ ScratchPad << :file_closed
+
+ raise @close_exception if @close_exception
+ end
+ end
+
+ obj.close_exception = exc
+ end
+end
diff --git a/spec/ruby/core/file/fixtures/do_not_remove b/spec/ruby/core/file/fixtures/do_not_remove
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/spec/ruby/core/file/fixtures/do_not_remove
@@ -0,0 +1 @@
+
diff --git a/spec/ruby/core/file/fixtures/file_types.rb b/spec/ruby/core/file/fixtures/file_types.rb
new file mode 100644
index 0000000000..109bcfe42e
--- /dev/null
+++ b/spec/ruby/core/file/fixtures/file_types.rb
@@ -0,0 +1,66 @@
+module FileSpecs
+ def self.configure_types
+ return if @configured
+
+ @file = tmp("test.txt")
+ @dir = Dir.pwd
+ @fifo = tmp("test_fifo")
+ @link = tmp("test_link")
+
+ platform_is_not :windows do
+ @block = `find /dev /devices -type b 2>/dev/null`.split("\n").first
+ @char = `{ tty || find /dev /devices -type c; } 2>/dev/null`.split("\n").last
+ end
+
+ @configured = true
+ end
+
+ def self.normal_file
+ touch(@file)
+ yield @file
+ ensure
+ rm_r @file
+ end
+
+ def self.directory
+ yield @dir
+ end
+
+ def self.fifo
+ File.mkfifo(@fifo)
+ yield @fifo
+ ensure
+ rm_r @fifo
+ end
+
+ def self.block_device
+ raise "Could not find a block device" unless @block
+ yield @block
+ end
+
+ def self.character_device
+ raise "Could not find a character device" unless @char
+ yield @char
+ end
+
+ def self.symlink
+ touch(@file)
+ File.symlink(@file, @link)
+ yield @link
+ ensure
+ rm_r @file, @link
+ end
+
+ def self.socket
+ require_relative '../../../library/socket/fixtures/classes.rb'
+
+ name = SocketSpecs.socket_path
+ socket = UNIXServer.new name
+ begin
+ yield name
+ ensure
+ socket.close
+ rm_r name
+ end
+ end
+end
diff --git a/spec/ruby/core/file/flock_spec.rb b/spec/ruby/core/file/flock_spec.rb
new file mode 100644
index 0000000000..751e99d994
--- /dev/null
+++ b/spec/ruby/core/file/flock_spec.rb
@@ -0,0 +1,106 @@
+require_relative '../../spec_helper'
+
+describe "File#flock" do
+ before :each do
+ ScratchPad.record []
+
+ @name = tmp("flock_test")
+ touch(@name)
+
+ @file = File.open @name, "w+"
+ end
+
+ after :each do
+ @file.flock File::LOCK_UN
+ @file.close
+
+ rm_r @name
+ end
+
+ it "exclusively locks a file" do
+ @file.flock(File::LOCK_EX).should == 0
+ @file.flock(File::LOCK_UN).should == 0
+ end
+
+ it "non-exclusively locks a file" do
+ @file.flock(File::LOCK_SH).should == 0
+ @file.flock(File::LOCK_UN).should == 0
+ end
+
+ it "returns false if trying to lock an exclusively locked file" do
+ @file.flock File::LOCK_EX
+
+ ruby_exe(<<-END_OF_CODE, escape: true).should == "false"
+ File.open('#{@name}', "w") do |f2|
+ print f2.flock(File::LOCK_EX | File::LOCK_NB).to_s
+ end
+ END_OF_CODE
+ end
+
+ it "blocks if trying to lock an exclusively locked file" do
+ @file.flock File::LOCK_EX
+
+ out = ruby_exe(<<-END_OF_CODE, escape: true)
+ running = false
+
+ t = Thread.new do
+ File.open('#{@name}', "w") do |f2|
+ puts "before"
+ running = true
+ f2.flock(File::LOCK_EX)
+ puts "after"
+ end
+ end
+
+ Thread.pass until running
+ Thread.pass while t.status and t.status != "sleep"
+ sleep 0.1
+
+ t.kill
+ t.join
+ END_OF_CODE
+
+ out.should == "before\n"
+ end
+
+ it "returns 0 if trying to lock a non-exclusively locked file" do
+ @file.flock File::LOCK_SH
+
+ File.open(@name, "r") do |f2|
+ f2.flock(File::LOCK_SH | File::LOCK_NB).should == 0
+ f2.flock(File::LOCK_UN).should == 0
+ end
+ end
+end
+
+platform_is :solaris do
+ describe "File#flock on Solaris" do
+ before :each do
+ @name = tmp("flock_test")
+ touch(@name)
+
+ @read_file = File.open @name, "r"
+ @write_file = File.open @name, "w"
+ end
+
+ after :each do
+ @read_file.flock File::LOCK_UN
+ @read_file.close
+ @write_file.flock File::LOCK_UN
+ @write_file.close
+ rm_r @name
+ end
+
+ it "fails with EBADF acquiring exclusive lock on read-only File" do
+ -> do
+ @read_file.flock File::LOCK_EX
+ end.should raise_error(Errno::EBADF)
+ end
+
+ it "fails with EBADF acquiring shared lock on read-only File" do
+ -> do
+ @write_file.flock File::LOCK_SH
+ end.should raise_error(Errno::EBADF)
+ end
+ end
+end
diff --git a/spec/ruby/core/file/fnmatch_spec.rb b/spec/ruby/core/file/fnmatch_spec.rb
new file mode 100644
index 0000000000..a1b7fa12b3
--- /dev/null
+++ b/spec/ruby/core/file/fnmatch_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/fnmatch'
+
+describe "File.fnmatch" do
+ it_behaves_like :file_fnmatch, :fnmatch
+end
+
+describe "File.fnmatch?" do
+ it_behaves_like :file_fnmatch, :fnmatch?
+end
diff --git a/spec/ruby/core/file/ftype_spec.rb b/spec/ruby/core/file/ftype_spec.rb
new file mode 100644
index 0000000000..cdddc404dc
--- /dev/null
+++ b/spec/ruby/core/file/ftype_spec.rb
@@ -0,0 +1,82 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/file_types'
+
+describe "File.ftype" do
+ before :all do
+ FileSpecs.configure_types
+ end
+
+ it "raises ArgumentError if not given exactly one filename" do
+ -> { File.ftype }.should raise_error(ArgumentError)
+ -> { File.ftype('blah', 'bleh') }.should raise_error(ArgumentError)
+ end
+
+ it "raises Errno::ENOENT if the file is not valid" do
+ -> {
+ File.ftype("/#{$$}#{Time.now.to_f}")
+ }.should raise_error(Errno::ENOENT)
+ end
+
+ it "returns a String" do
+ FileSpecs.normal_file do |file|
+ File.ftype(file).should be_kind_of(String)
+ end
+ end
+
+ it "returns 'file' when the file is a file" do
+ FileSpecs.normal_file do |file|
+ File.ftype(file).should == 'file'
+ end
+ end
+
+ it "returns 'directory' when the file is a dir" do
+ FileSpecs.directory do |dir|
+ File.ftype(dir).should == 'directory'
+ end
+ end
+
+ it "uses to_path to convert arguments" do
+ FileSpecs.normal_file do |file|
+ obj = mock('path')
+ obj.should_receive(:to_path).and_return(file)
+ File.ftype(obj).should == 'file'
+ end
+ end
+
+ # Both FreeBSD and Windows does not have block devices
+ platform_is_not :freebsd, :windows do
+ with_block_device do
+ it "returns 'blockSpecial' when the file is a block" do
+ FileSpecs.block_device do |block|
+ File.ftype(block).should == 'blockSpecial'
+ end
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns 'characterSpecial' when the file is a char" do
+ FileSpecs.character_device do |char|
+ File.ftype(char).should == 'characterSpecial'
+ end
+ end
+
+ it "returns 'link' when the file is a link" do
+ FileSpecs.symlink do |link|
+ File.ftype(link).should == 'link'
+ end
+ end
+
+ it "returns fifo when the file is a fifo" do
+ FileSpecs.fifo do |fifo|
+ File.ftype(fifo).should == 'fifo'
+ end
+ end
+
+ it "returns 'socket' when the file is a socket" do
+ FileSpecs.socket do |socket|
+ File.ftype(socket).should == 'socket'
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/grpowned_spec.rb b/spec/ruby/core/file/grpowned_spec.rb
new file mode 100644
index 0000000000..8ddac5237c
--- /dev/null
+++ b/spec/ruby/core/file/grpowned_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/grpowned'
+
+describe "File.grpowned?" do
+ it_behaves_like :file_grpowned, :grpowned?, File
+
+ it "returns false if file the does not exist" do
+ File.grpowned?("i_am_a_bogus_file").should == false
+ end
+end
diff --git a/spec/ruby/core/file/identical_spec.rb b/spec/ruby/core/file/identical_spec.rb
new file mode 100644
index 0000000000..bbeaef24d2
--- /dev/null
+++ b/spec/ruby/core/file/identical_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/identical'
+
+describe "File.identical?" do
+ it_behaves_like :file_identical, :identical?, File
+end
diff --git a/spec/ruby/core/file/initialize_spec.rb b/spec/ruby/core/file/initialize_spec.rb
new file mode 100644
index 0000000000..9a76a95260
--- /dev/null
+++ b/spec/ruby/core/file/initialize_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "File#initialize" do
+ after :each do
+ @io.close if @io
+ end
+
+ it "accepts encoding options in mode parameter" do
+ @io = File.new(__FILE__, 'r:UTF-8:iso-8859-1')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "accepts encoding options as a hash parameter" do
+ @io = File.new(__FILE__, 'r', encoding: 'UTF-8:iso-8859-1')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == 'ISO-8859-1'
+ end
+end
diff --git a/spec/ruby/core/file/inspect_spec.rb b/spec/ruby/core/file/inspect_spec.rb
new file mode 100644
index 0000000000..148e789c62
--- /dev/null
+++ b/spec/ruby/core/file/inspect_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "File#inspect" do
+ before :each do
+ @name = tmp("file_inspect.txt")
+ @file = File.open @name, "w"
+ end
+
+ after :each do
+ @file.close unless @file.closed?
+ rm_r @name
+ end
+
+ it "returns a String" do
+ @file.inspect.should be_an_instance_of(String)
+ end
+end
diff --git a/spec/ruby/core/file/join_spec.rb b/spec/ruby/core/file/join_spec.rb
new file mode 100644
index 0000000000..0feedbae93
--- /dev/null
+++ b/spec/ruby/core/file/join_spec.rb
@@ -0,0 +1,148 @@
+require_relative '../../spec_helper'
+
+describe "File.join" do
+ # see [ruby-core:46804] for the 4 following rules
+ it "changes only boundaries separators" do
+ File.join("file/\\/usr/", "/bin").should == "file/\\/usr/bin"
+ File.join("file://usr", "bin").should == "file://usr/bin"
+ end
+
+ it "respects the given separator if only one part has a boundary separator" do
+ File.join("usr/", "bin").should == "usr/bin"
+ File.join("usr", "/bin").should == "usr/bin"
+ File.join("usr//", "bin").should == "usr//bin"
+ File.join("usr", "//bin").should == "usr//bin"
+ end
+
+ it "joins parts using File::SEPARATOR if there are no boundary separators" do
+ File.join("usr", "bin").should == "usr/bin"
+ end
+
+ it "prefers the separator of the right part if both parts have separators" do
+ File.join("usr/", "//bin").should == "usr//bin"
+ File.join("usr//", "/bin").should == "usr/bin"
+ end
+
+ platform_is :windows do
+ it "respects given separator if only one part has a boundary separator" do
+ File.join("C:\\", 'windows').should == "C:\\windows"
+ File.join("C:", "\\windows").should == "C:\\windows"
+ File.join("\\\\", "usr").should == "\\\\usr"
+ end
+
+ it "prefers the separator of the right part if both parts have separators" do
+ File.join("C:/", "\\windows").should == "C:\\windows"
+ File.join("C:\\", "/windows").should == "C:/windows"
+ end
+ end
+
+ platform_is_not :windows do
+ it "does not treat \\ as a separator on non-Windows" do
+ File.join("usr\\", 'bin').should == "usr\\/bin"
+ File.join("usr", "\\bin").should == "usr/\\bin"
+ File.join("usr/", "\\bin").should == "usr/\\bin"
+ File.join("usr\\", "/bin").should == "usr\\/bin"
+ end
+ end
+
+ it "returns an empty string when given no arguments" do
+ File.join.should == ""
+ end
+
+ it "returns a duplicate string when given a single argument" do
+ str = "usr"
+ File.join(str).should == str
+ File.join(str).should_not equal(str)
+ end
+
+ it "supports any number of arguments" do
+ File.join("a", "b", "c", "d").should == "a/b/c/d"
+ end
+
+ it "flattens nested arrays" do
+ File.join(["a", "b", "c"]).should == "a/b/c"
+ File.join(["a", ["b", ["c"]]]).should == "a/b/c"
+ end
+
+ it "inserts the separator in between empty strings and arrays" do
+ File.join("").should == ""
+ File.join("", "").should == "/"
+ File.join(["", ""]).should == "/"
+ File.join("a", "").should == "a/"
+ File.join("", "a").should == "/a"
+
+ File.join([]).should == ""
+ File.join([], []).should == "/"
+ File.join([[], []]).should == "/"
+ File.join("a", []).should == "a/"
+ File.join([], "a").should == "/a"
+ end
+
+ it "handles leading parts edge cases" do
+ File.join("/bin") .should == "/bin"
+ File.join("", "bin") .should == "/bin"
+ File.join("/", "bin") .should == "/bin"
+ File.join("/", "/bin").should == "/bin"
+ end
+
+ it "handles trailing parts edge cases" do
+ File.join("bin", "") .should == "bin/"
+ File.join("bin/") .should == "bin/"
+ File.join("bin/", "") .should == "bin/"
+ File.join("bin", "/") .should == "bin/"
+ File.join("bin/", "/").should == "bin/"
+ end
+
+ it "handles middle parts edge cases" do
+ File.join("usr", "", "bin") .should == "usr/bin"
+ File.join("usr/", "", "bin") .should == "usr/bin"
+ File.join("usr", "", "/bin").should == "usr/bin"
+ File.join("usr/", "", "/bin").should == "usr/bin"
+ end
+
+ # TODO: See MRI svn r23306. Add patchlevel when there is a release.
+ it "raises an ArgumentError if passed a recursive array" do
+ a = ["a"]
+ a << a
+ -> { File.join a }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError exception when args are nil" do
+ -> { File.join nil }.should raise_error(TypeError)
+ end
+
+ it "calls #to_str" do
+ -> { File.join(mock('x')) }.should raise_error(TypeError)
+
+ bin = mock("bin")
+ bin.should_receive(:to_str).exactly(:twice).and_return("bin")
+ File.join(bin).should == "bin"
+ File.join("usr", bin).should == "usr/bin"
+ end
+
+ it "doesn't mutate the object when calling #to_str" do
+ usr = mock("usr")
+ str = "usr"
+ usr.should_receive(:to_str).and_return(str)
+ File.join(usr, "bin").should == "usr/bin"
+ str.should == "usr"
+ end
+
+ it "calls #to_path" do
+ -> { File.join(mock('x')) }.should raise_error(TypeError)
+
+ bin = mock("bin")
+ bin.should_receive(:to_path).exactly(:twice).and_return("bin")
+ File.join(bin).should == "bin"
+ File.join("usr", bin).should == "usr/bin"
+ end
+
+ it "raises errors for null bytes" do
+ -> { File.join("\x00x", "metadata.gz") }.should raise_error(ArgumentError) { |e|
+ e.message.should == 'string contains null byte'
+ }
+ -> { File.join("metadata.gz", "\x00x") }.should raise_error(ArgumentError) { |e|
+ e.message.should == 'string contains null byte'
+ }
+ end
+end
diff --git a/spec/ruby/core/file/lchmod_spec.rb b/spec/ruby/core/file/lchmod_spec.rb
new file mode 100644
index 0000000000..7420b95e4a
--- /dev/null
+++ b/spec/ruby/core/file/lchmod_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "File.lchmod" do
+ platform_is_not :linux, :windows, :openbsd, :solaris, :aix do
+ before :each do
+ @fname = tmp('file_chmod_test')
+ @lname = @fname + '.lnk'
+
+ touch(@fname) { |f| f.write "rubinius" }
+
+ rm_r @lname
+ File.symlink @fname, @lname
+ end
+
+ after :each do
+ rm_r @lname, @fname
+ end
+
+ it "changes the file mode of the link and not of the file" do
+ File.chmod(0222, @lname).should == 1
+ File.lchmod(0755, @lname).should == 1
+
+ File.lstat(@lname).should.executable?
+ File.lstat(@lname).should.readable?
+ File.lstat(@lname).should.writable?
+
+ File.stat(@lname).should_not.executable?
+ File.stat(@lname).should_not.readable?
+ File.stat(@lname).should.writable?
+ end
+ end
+end
diff --git a/spec/ruby/core/file/lchown_spec.rb b/spec/ruby/core/file/lchown_spec.rb
new file mode 100644
index 0000000000..8d95d287ba
--- /dev/null
+++ b/spec/ruby/core/file/lchown_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+
+as_superuser do
+ describe "File.lchown" do
+ platform_is_not :windows do
+ before :each do
+ @fname = tmp('file_chown_test')
+ @lname = @fname + '.lnk'
+
+ touch(@fname) { |f| f.chown 501, 501 }
+
+ rm_r @lname
+ File.symlink @fname, @lname
+ end
+
+ after :each do
+ rm_r @lname, @fname
+ end
+
+ it "changes the owner id of the file" do
+ File.lchown 502, nil, @lname
+ File.stat(@fname).uid.should == 501
+ File.lstat(@lname).uid.should == 502
+ File.lchown 0, nil, @lname
+ File.stat(@fname).uid.should == 501
+ File.lstat(@lname).uid.should == 0
+ end
+
+ it "changes the group id of the file" do
+ File.lchown nil, 502, @lname
+ File.stat(@fname).gid.should == 501
+ File.lstat(@lname).gid.should == 502
+ File.lchown nil, 0, @lname
+ File.stat(@fname).uid.should == 501
+ File.lstat(@lname).uid.should == 0
+ end
+
+ it "does not modify the owner id of the file if passed nil or -1" do
+ File.lchown 502, nil, @lname
+ File.lchown nil, nil, @lname
+ File.lstat(@lname).uid.should == 502
+ File.lchown nil, -1, @lname
+ File.lstat(@lname).uid.should == 502
+ end
+
+ it "does not modify the group id of the file if passed nil or -1" do
+ File.lchown nil, 502, @lname
+ File.lchown nil, nil, @lname
+ File.lstat(@lname).gid.should == 502
+ File.lchown nil, -1, @lname
+ File.lstat(@lname).gid.should == 502
+ end
+
+ it "returns the number of files processed" do
+ File.lchown(nil, nil, @lname, @lname).should == 2
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/link_spec.rb b/spec/ruby/core/file/link_spec.rb
new file mode 100644
index 0000000000..a5d5b4815f
--- /dev/null
+++ b/spec/ruby/core/file/link_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "File.link" do
+ before :each do
+ @file = tmp("file_link.txt")
+ @link = tmp("file_link.lnk")
+
+ rm_r @link
+ touch @file
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ platform_is_not :windows, :android do
+ it "link a file with another" do
+ File.link(@file, @link).should == 0
+ File.should.exist?(@link)
+ File.identical?(@file, @link).should == true
+ end
+
+ it "raises an Errno::EEXIST if the target already exists" do
+ File.link(@file, @link)
+ -> { File.link(@file, @link) }.should raise_error(Errno::EEXIST)
+ end
+
+ it "raises an ArgumentError if not passed two arguments" do
+ -> { File.link }.should raise_error(ArgumentError)
+ -> { File.link(@file) }.should raise_error(ArgumentError)
+ -> { File.link(@file, @link, @file) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed String types" do
+ -> { File.link(@file, nil) }.should raise_error(TypeError)
+ -> { File.link(@file, 1) }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/file/lstat_spec.rb b/spec/ruby/core/file/lstat_spec.rb
new file mode 100644
index 0000000000..a5ea9d15a5
--- /dev/null
+++ b/spec/ruby/core/file/lstat_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'shared/stat'
+
+describe "File.lstat" do
+ it_behaves_like :file_stat, :lstat
+end
+
+describe "File.lstat" do
+
+ before :each do
+ @file = tmp('i_exist')
+ @link = tmp('i_am_a_symlink')
+ touch(@file) { |f| f.write 'rubinius' }
+ File.symlink(@file, @link)
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ platform_is_not :windows do
+ it "returns a File::Stat object with symlink properties for a symlink" do
+ st = File.lstat(@link)
+
+ st.should.symlink?
+ st.should_not.file?
+ end
+ end
+end
+
+describe "File#lstat" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/file/lutime_spec.rb b/spec/ruby/core/file/lutime_spec.rb
new file mode 100644
index 0000000000..1f0625f61e
--- /dev/null
+++ b/spec/ruby/core/file/lutime_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+
+describe "File.lutime" do
+ platform_is_not :windows do
+ before :each do
+ @atime = Time.utc(2000)
+ @mtime = Time.utc(2001)
+ @file = tmp("specs_lutime_file")
+ @symlink = tmp("specs_lutime_symlink")
+ touch @file
+ File.symlink(@file, @symlink)
+ end
+
+ after :each do
+ rm_r @file, @symlink
+ end
+
+ it "sets the access and modification time for a regular file" do
+ File.lutime(@atime, @mtime, @file)
+ stat = File.stat(@file)
+ stat.atime.should == @atime
+ stat.mtime.should === @mtime
+ end
+
+ it "sets the access and modification time for a symlink" do
+ original = File.stat(@file)
+
+ File.lutime(@atime, @mtime, @symlink)
+ stat = File.lstat(@symlink)
+ stat.atime.should == @atime
+ stat.mtime.should === @mtime
+
+ file = File.stat(@file)
+ file.atime.should == original.atime
+ file.mtime.should == original.mtime
+ end
+ end
+end
diff --git a/spec/ruby/core/file/mkfifo_spec.rb b/spec/ruby/core/file/mkfifo_spec.rb
new file mode 100644
index 0000000000..19298c967c
--- /dev/null
+++ b/spec/ruby/core/file/mkfifo_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+
+describe "File.mkfifo" do
+ platform_is_not :windows do
+ before do
+ @path = tmp('fifo')
+ end
+
+ after do
+ rm_r(@path)
+ end
+
+ context "when path passed responds to :to_path" do
+ it "creates a FIFO file at the path specified" do
+ File.mkfifo(@path)
+ File.ftype(@path).should == "fifo"
+ end
+ end
+
+ context "when path passed is not a String value" do
+ it "raises a TypeError" do
+ -> { File.mkfifo(:"/tmp/fifo") }.should raise_error(TypeError)
+ end
+ end
+
+ context "when path does not exist" do
+ it "raises an Errno::ENOENT exception" do
+ -> { File.mkfifo("/bogus/path") }.should raise_error(Errno::ENOENT)
+ end
+ end
+
+ it "creates a FIFO file at the passed path" do
+ File.mkfifo(@path.to_s)
+ File.ftype(@path).should == "fifo"
+ end
+
+ it "creates a FIFO file with passed mode & ~umask" do
+ File.mkfifo(@path, 0755)
+ File.stat(@path).mode.should == 010755 & ~File.umask
+ end
+
+ it "creates a FIFO file with a default mode of 0666 & ~umask" do
+ File.mkfifo(@path)
+ File.stat(@path).mode.should == 010666 & ~File.umask
+ end
+
+ it "returns 0 after creating the FIFO file" do
+ File.mkfifo(@path).should == 0
+ end
+ end
+end
diff --git a/spec/ruby/core/file/mtime_spec.rb b/spec/ruby/core/file/mtime_spec.rb
new file mode 100644
index 0000000000..6c43265a2c
--- /dev/null
+++ b/spec/ruby/core/file/mtime_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+
+describe "File.mtime" do
+ before :each do
+ @filename = tmp('i_exist')
+ touch(@filename) { @mtime = Time.now }
+ end
+
+ after :each do
+ rm_r @filename
+ end
+
+ it "returns the modification Time of the file" do
+ File.mtime(@filename).should be_kind_of(Time)
+ File.mtime(@filename).should be_close(@mtime, TIME_TOLERANCE)
+ end
+
+ platform_is :linux do
+ unless ENV.key?('TRAVIS') # https://bugs.ruby-lang.org/issues/17926
+ it "returns the modification Time of the file with microseconds" do
+ supports_subseconds = Integer(`stat -c%y '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10)
+ if supports_subseconds != 0
+ expected_time = Time.at(Time.now.to_i + 0.123456)
+ File.utime 0, expected_time, @filename
+ File.mtime(@filename).usec.should == expected_time.usec
+ else
+ File.mtime(__FILE__).usec.should == 0
+ end
+ end
+ end
+ end
+
+ it "raises an Errno::ENOENT exception if the file is not found" do
+ -> { File.mtime('bogus') }.should raise_error(Errno::ENOENT)
+ end
+end
+
+describe "File#mtime" do
+ before :each do
+ @filename = tmp('i_exist')
+ @f = File.open(@filename, 'w')
+ end
+
+ after :each do
+ @f.close
+ rm_r @filename
+ end
+
+ it "returns the modification Time of the file" do
+ @f.mtime.should be_kind_of(Time)
+ end
+
+end
diff --git a/spec/ruby/core/file/new_spec.rb b/spec/ruby/core/file/new_spec.rb
new file mode 100644
index 0000000000..004f78503a
--- /dev/null
+++ b/spec/ruby/core/file/new_spec.rb
@@ -0,0 +1,162 @@
+require_relative '../../spec_helper'
+require_relative 'shared/open'
+
+describe "File.new" do
+ before :each do
+ @file = tmp('test.txt')
+ @fh = nil
+ @flags = File::CREAT | File::TRUNC | File::WRONLY
+ touch @file
+ end
+
+ after :each do
+ @fh.close if @fh
+ rm_r @file
+ end
+
+ it "returns a new File with mode string" do
+ @fh = File.new(@file, 'w')
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new File with mode num" do
+ @fh = File.new(@file, @flags)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new File with modus num and permissions" do
+ rm_r @file
+ File.umask(0011)
+ @fh = File.new(@file, @flags, 0755)
+ @fh.should be_kind_of(File)
+ platform_is_not :windows do
+ File.stat(@file).mode.to_s(8).should == "100744"
+ end
+ File.should.exist?(@file)
+ end
+
+ it "creates the file and returns writable descriptor when called with 'w' mode and r-o permissions" do
+ # it should be possible to write to such a file via returned descriptor,
+ # even though the file permissions are r-r-r.
+
+ rm_r @file
+ begin
+ f = File.new(@file, "w", 0444)
+ -> { f.puts("test") }.should_not raise_error(IOError)
+ ensure
+ f.close
+ end
+ File.should.exist?(@file)
+ File.read(@file).should == "test\n"
+ end
+
+ platform_is_not :windows do
+ it "opens the existing file, does not change permissions even when they are specified" do
+ File.chmod(0644, @file) # r-w perms
+ orig_perms = File.stat(@file).mode & 0777
+ begin
+ f = File.new(@file, "w", 0444) # r-o perms, but they should be ignored
+ f.puts("test")
+ ensure
+ f.close
+ end
+ perms = File.stat(@file).mode & 0777
+ perms.should == orig_perms
+
+ # it should be still possible to read from the file
+ File.read(@file).should == "test\n"
+ end
+ end
+
+ it "returns a new File with modus fd" do
+ @fh = File.new(@file)
+ fh_copy = File.new(@fh.fileno)
+ fh_copy.autoclose = false
+ fh_copy.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "creates a new file when use File::EXCL mode" do
+ @fh = File.new(@file, File::EXCL)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "raises an Errorno::EEXIST if the file exists when create a new file with File::CREAT|File::EXCL" do
+ -> { @fh = File.new(@file, File::CREAT|File::EXCL) }.should raise_error(Errno::EEXIST)
+ end
+
+ it "creates a new file when use File::WRONLY|File::APPEND mode" do
+ @fh = File.new(@file, File::WRONLY|File::APPEND)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new File when use File::APPEND mode" do
+ @fh = File.new(@file, File::APPEND)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new File when use File::RDONLY|File::APPEND mode" do
+ @fh = File.new(@file, File::RDONLY|File::APPEND)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new File when use File::RDONLY|File::WRONLY mode" do
+ @fh = File.new(@file, File::RDONLY|File::WRONLY)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+
+ it "creates a new file when use File::WRONLY|File::TRUNC mode" do
+ @fh = File.new(@file, File::WRONLY|File::TRUNC)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "coerces filename using to_str" do
+ name = mock("file")
+ name.should_receive(:to_str).and_return(@file)
+ @fh = File.new(name, "w")
+ File.should.exist?(@file)
+ end
+
+ it "coerces filename using #to_path" do
+ name = mock("file")
+ name.should_receive(:to_path).and_return(@file)
+ @fh = File.new(name, "w")
+ File.should.exist?(@file)
+ end
+
+ it "raises a TypeError if the first parameter can't be coerced to a string" do
+ -> { File.new(true) }.should raise_error(TypeError)
+ -> { File.new(false) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the first parameter is nil" do
+ -> { File.new(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises an Errno::EBADF if the first parameter is an invalid file descriptor" do
+ -> { File.new(-1) }.should raise_error(Errno::EBADF)
+ end
+
+ platform_is_not :windows do
+ it "can't alter mode or permissions when opening a file" do
+ @fh = File.new(@file)
+ -> {
+ f = File.new(@fh.fileno, @flags)
+ f.autoclose = false
+ }.should raise_error(Errno::EINVAL)
+ end
+ end
+
+ platform_is_not :windows do
+ it_behaves_like :open_directory, :new
+ end
+end
diff --git a/spec/ruby/core/file/null_spec.rb b/spec/ruby/core/file/null_spec.rb
new file mode 100644
index 0000000000..355b72b799
--- /dev/null
+++ b/spec/ruby/core/file/null_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "File::NULL" do
+ platform_is :windows do
+ it "returns NUL as a string" do
+ File::NULL.should == 'NUL'
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns /dev/null as a string" do
+ File::NULL.should == '/dev/null'
+ end
+ end
+end
diff --git a/spec/ruby/core/file/open_spec.rb b/spec/ruby/core/file/open_spec.rb
new file mode 100644
index 0000000000..1729780570
--- /dev/null
+++ b/spec/ruby/core/file/open_spec.rb
@@ -0,0 +1,704 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/open'
+
+describe "File.open" do
+ before :all do
+ @file = tmp("file_open.txt")
+ @unicode_path = tmp("ã“ã‚“ã«ã¡ã¯.txt")
+ @nonexistent = tmp("fake.txt")
+ rm_r @file, @nonexistent
+ end
+
+ before :each do
+ ScratchPad.record []
+
+ @fh = @fd = nil
+ @flags = File::CREAT | File::TRUNC | File::WRONLY
+ touch @file
+ end
+
+ after :each do
+ @fh.close if @fh and not @fh.closed?
+ rm_r @file, @unicode_path, @nonexistent
+ end
+
+ describe "with a block" do
+ it "does not raise error when file is closed inside the block" do
+ @fh = File.open(@file) { |fh| fh.close; fh }
+ @fh.should.closed?
+ end
+
+ it "invokes close on an opened file when exiting the block" do
+ File.open(@file, 'r') { |f| FileSpecs.make_closer f }
+
+ ScratchPad.recorded.should == [:file_opened, :file_closed]
+ end
+
+ it "propagates non-StandardErrors produced by close" do
+ -> {
+ File.open(@file, 'r') { |f| FileSpecs.make_closer f, Exception }
+ }.should raise_error(Exception)
+
+ ScratchPad.recorded.should == [:file_opened, :file_closed]
+ end
+
+ it "propagates StandardErrors produced by close" do
+ -> {
+ File.open(@file, 'r') { |f| FileSpecs.make_closer f, StandardError }
+ }.should raise_error(StandardError)
+
+ ScratchPad.recorded.should == [:file_opened, :file_closed]
+ end
+
+ it "does not propagate IOError with 'closed stream' message produced by close" do
+ File.open(@file, 'r') { |f| FileSpecs.make_closer f, IOError.new('closed stream') }
+
+ ScratchPad.recorded.should == [:file_opened, :file_closed]
+ end
+ end
+
+ it "opens the file (basic case)" do
+ @fh = File.open(@file)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens the file with unicode characters" do
+ @fh = File.open(@unicode_path, "w")
+ @fh.should be_kind_of(File)
+ File.should.exist?(@unicode_path)
+ end
+
+ it "opens a file when called with a block" do
+ File.open(@file) { |fh| }
+ File.should.exist?(@file)
+ end
+
+ it "opens with mode string" do
+ @fh = File.open(@file, 'w')
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file with mode string and block" do
+ File.open(@file, 'w') { |fh| }
+ File.should.exist?(@file)
+ end
+
+ it "opens a file with mode num" do
+ @fh = File.open(@file, @flags)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file with mode num and block" do
+ File.open(@file, 'w') { |fh| }
+ File.should.exist?(@file)
+ end
+
+ it "opens a file with mode and permission as nil" do
+ @fh = File.open(@file, nil, nil)
+ @fh.should be_kind_of(File)
+ end
+
+ # For this test we delete the file first to reset the perms
+ it "opens the file when passed mode, num and permissions" do
+ rm_r @file
+ File.umask(0011)
+ @fh = File.open(@file, @flags, 0755)
+ @fh.should be_kind_of(File)
+ platform_is_not :windows do
+ @fh.lstat.mode.to_s(8).should == "100744"
+ end
+ File.should.exist?(@file)
+ end
+
+ # For this test we delete the file first to reset the perms
+ it "opens the file when passed mode, num, permissions and block" do
+ rm_r @file
+ File.umask(0022)
+ File.open(@file, "w", 0755){ |fh| }
+ platform_is_not :windows do
+ File.stat(@file).mode.to_s(8).should == "100755"
+ end
+ File.should.exist?(@file)
+ end
+
+ it "creates the file and returns writable descriptor when called with 'w' mode and r-o permissions" do
+ # it should be possible to write to such a file via returned descriptor,
+ # even though the file permissions are r-r-r.
+
+ File.open(@file, "w", 0444) { |f| f.write("test") }
+ File.read(@file).should == "test"
+ end
+
+ platform_is_not :windows do
+ it "opens the existing file, does not change permissions even when they are specified" do
+ File.chmod(0664, @file)
+ orig_perms = File.stat(@file).mode.to_s(8)
+ File.open(@file, "w", 0444) { |f| f.write("test") }
+
+ File.stat(@file).mode.to_s(8).should == orig_perms
+ File.read(@file).should == "test"
+ end
+ end
+
+ platform_is_not :windows do
+ as_user do
+ it "creates a new write-only file when invoked with 'w' and '0222'" do
+ rm_r @file
+ File.open(@file, 'w', 0222) {}
+ File.readable?(@file).should == false
+ File.writable?(@file).should == true
+ end
+ end
+ end
+
+ it "opens the file when call with fd" do
+ @fh = File.open(@file)
+ fh_copy = File.open(@fh.fileno)
+ fh_copy.autoclose = false
+ fh_copy.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file that no exists when use File::WRONLY mode" do
+ -> { File.open(@nonexistent, File::WRONLY) }.should raise_error(Errno::ENOENT)
+ end
+
+ it "opens a file that no exists when use File::RDONLY mode" do
+ -> { File.open(@nonexistent, File::RDONLY) }.should raise_error(Errno::ENOENT)
+ end
+
+ it "opens a file that no exists when use 'r' mode" do
+ -> { File.open(@nonexistent, 'r') }.should raise_error(Errno::ENOENT)
+ end
+
+ it "opens a file that no exists when use File::EXCL mode" do
+ -> { File.open(@nonexistent, File::EXCL) }.should raise_error(Errno::ENOENT)
+ end
+
+ it "opens a file that no exists when use File::NONBLOCK mode" do
+ -> { File.open(@nonexistent, File::NONBLOCK) }.should raise_error(Errno::ENOENT)
+ end
+
+ platform_is_not :openbsd, :windows do
+ it "opens a file that no exists when use File::TRUNC mode" do
+ -> { File.open(@nonexistent, File::TRUNC) }.should raise_error(Errno::ENOENT)
+ end
+ end
+
+ platform_is :openbsd, :windows do
+ it "does not open a file that does no exists when using File::TRUNC mode" do
+ -> { File.open(@nonexistent, File::TRUNC) }.should raise_error(Errno::EINVAL)
+ end
+ end
+
+ platform_is_not :windows do
+ it "opens a file that no exists when use File::NOCTTY mode" do
+ -> { File.open(@nonexistent, File::NOCTTY) }.should raise_error(Errno::ENOENT)
+ end
+ end
+
+ it "opens a file that no exists when use File::CREAT mode" do
+ @fh = File.open(@nonexistent, File::CREAT) { |f| f }
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file that no exists when use 'a' mode" do
+ @fh = File.open(@nonexistent, 'a') { |f| f }
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file that no exists when use 'w' mode" do
+ @fh = File.open(@nonexistent, 'w') { |f| f }
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ # Check the grants associated to the different open modes combinations.
+ it "raises an ArgumentError exception when call with an unknown mode" do
+ -> { File.open(@file, "q") }.should raise_error(ArgumentError)
+ end
+
+ it "can read in a block when call open with RDONLY mode" do
+ File.open(@file, File::RDONLY) do |f|
+ f.gets.should == nil
+ end
+ end
+
+ it "can read in a block when call open with 'r' mode" do
+ File.open(@file, "r") do |f|
+ f.gets.should == nil
+ end
+ end
+
+ it "raises an IO exception when write in a block opened with RDONLY mode" do
+ File.open(@file, File::RDONLY) do |f|
+ -> { f.puts "writing ..." }.should raise_error(IOError)
+ end
+ end
+
+ it "raises an IO exception when write in a block opened with 'r' mode" do
+ File.open(@file, "r") do |f|
+ -> { f.puts "writing ..." }.should raise_error(IOError)
+ end
+ end
+
+ it "can't write in a block when call open with File::WRONLY||File::RDONLY mode" do
+ File.open(@file, File::WRONLY|File::RDONLY ) do |f|
+ f.puts("writing").should == nil
+ end
+ end
+
+ it "can't read in a block when call open with File::WRONLY||File::RDONLY mode" do
+ -> {
+ File.open(@file, File::WRONLY|File::RDONLY ) do |f|
+ f.gets.should == nil
+ end
+ }.should raise_error(IOError)
+ end
+
+ it "can write in a block when call open with WRONLY mode" do
+ File.open(@file, File::WRONLY) do |f|
+ f.puts("writing").should == nil
+ end
+ end
+
+ it "can write in a block when call open with 'w' mode" do
+ File.open(@file, "w") do |f|
+ f.puts("writing").should == nil
+ end
+ end
+
+ it "raises an IOError when read in a block opened with WRONLY mode" do
+ File.open(@file, File::WRONLY) do |f|
+ -> { f.gets }.should raise_error(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with 'w' mode" do
+ File.open(@file, "w") do |f|
+ -> { f.gets }.should raise_error(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with 'a' mode" do
+ File.open(@file, "a") do |f|
+ -> { f.gets }.should raise_error(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with 'a' mode" do
+ File.open(@file, "a") do |f|
+ f.puts("writing").should == nil
+ -> { f.gets }.should raise_error(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with 'a' mode" do
+ File.open(@file, File::WRONLY|File::APPEND ) do |f|
+ -> { f.gets }.should raise_error(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with File::WRONLY|File::APPEND mode" do
+ File.open(@file, File::WRONLY|File::APPEND ) do |f|
+ f.puts("writing").should == nil
+ -> { f.gets }.should raise_error(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with File::RDONLY|File::APPEND mode" do
+ -> {
+ File.open(@file, File::RDONLY|File::APPEND ) do |f|
+ f.puts("writing")
+ end
+ }.should raise_error(IOError)
+ end
+
+ it "can read and write in a block when call open with RDWR mode" do
+ File.open(@file, File::RDWR) do |f|
+ f.gets.should == nil
+ f.puts("writing").should == nil
+ f.rewind
+ f.gets.should == "writing\n"
+ end
+ end
+
+ it "can't read in a block when call open with File::EXCL mode" do
+ -> {
+ File.open(@file, File::EXCL) do |f|
+ f.puts("writing").should == nil
+ end
+ }.should raise_error(IOError)
+ end
+
+ it "can read in a block when call open with File::EXCL mode" do
+ File.open(@file, File::EXCL) do |f|
+ f.gets.should == nil
+ end
+ end
+
+ it "can read and write in a block when call open with File::RDWR|File::EXCL mode" do
+ File.open(@file, File::RDWR|File::EXCL) do |f|
+ f.gets.should == nil
+ f.puts("writing").should == nil
+ f.rewind
+ f.gets.should == "writing\n"
+ end
+ end
+
+ it "raises an Errorno::EEXIST if the file exists when open with File::CREAT|File::EXCL" do
+ -> {
+ File.open(@file, File::CREAT|File::EXCL) do |f|
+ f.puts("writing")
+ end
+ }.should raise_error(Errno::EEXIST)
+ end
+
+ it "creates a new file when use File::WRONLY|File::APPEND mode" do
+ @fh = File.open(@file, File::WRONLY|File::APPEND)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file when use File::WRONLY|File::APPEND mode" do
+ File.open(@file, File::WRONLY) do |f|
+ f.puts("hello file")
+ end
+ File.open(@file, File::RDWR|File::APPEND) do |f|
+ f.puts("bye file")
+ f.rewind
+ f.gets.should == "hello file\n"
+ f.gets.should == "bye file\n"
+ f.gets.should == nil
+ end
+ end
+
+ it "raises an IOError if the file exists when open with File::RDONLY|File::APPEND" do
+ -> {
+ File.open(@file, File::RDONLY|File::APPEND) do |f|
+ f.puts("writing").should == nil
+ end
+ }.should raise_error(IOError)
+ end
+
+ platform_is_not :openbsd, :windows do
+ it "truncates the file when passed File::TRUNC mode" do
+ File.open(@file, File::RDWR) { |f| f.puts "hello file" }
+ @fh = File.open(@file, File::TRUNC)
+ @fh.gets.should == nil
+ end
+
+ it "can't read in a block when call open with File::TRUNC mode" do
+ File.open(@file, File::TRUNC) do |f|
+ f.gets.should == nil
+ end
+ end
+ end
+
+ it "opens a file when use File::WRONLY|File::TRUNC mode" do
+ fh1 = File.open(@file, "w")
+ begin
+ @fh = File.open(@file, File::WRONLY|File::TRUNC)
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ ensure
+ fh1.close
+ end
+ end
+
+ platform_is_not :openbsd, :windows do
+ it "can't write in a block when call open with File::TRUNC mode" do
+ -> {
+ File.open(@file, File::TRUNC) do |f|
+ f.puts("writing")
+ end
+ }.should raise_error(IOError)
+ end
+
+ it "raises an Errorno::EEXIST if the file exists when open with File::RDONLY|File::TRUNC" do
+ -> {
+ File.open(@file, File::RDONLY|File::TRUNC) do |f|
+ f.puts("writing").should == nil
+ end
+ }.should raise_error(IOError)
+ end
+ end
+
+ platform_is :openbsd, :windows do
+ it "can't write in a block when call open with File::TRUNC mode" do
+ -> {
+ File.open(@file, File::TRUNC) do |f|
+ f.puts("writing")
+ end
+ }.should raise_error(Errno::EINVAL)
+ end
+
+ it "raises an Errorno::EEXIST if the file exists when open with File::RDONLY|File::TRUNC" do
+ -> {
+ File.open(@file, File::RDONLY|File::TRUNC) do |f|
+ f.puts("writing").should == nil
+ end
+ }.should raise_error(Errno::EINVAL)
+ end
+ end
+
+ platform_is_not :windows do
+ as_user do
+ it "raises an Errno::EACCES when opening non-permitted file" do
+ @fh = File.open(@file, "w")
+ @fh.chmod(000)
+ -> { fh1 = File.open(@file); fh1.close }.should raise_error(Errno::EACCES)
+ end
+ end
+ end
+
+ as_user do
+ it "raises an Errno::EACCES when opening read-only file" do
+ @fh = File.open(@file, "w")
+ @fh.chmod(0444)
+ -> { File.open(@file, "w") }.should raise_error(Errno::EACCES)
+ end
+ end
+
+ it "opens a file for binary read" do
+ @fh = File.open(@file, "rb")
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file for binary write" do
+ @fh = File.open(@file, "wb")
+ @fh.should be_kind_of(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file for read-write and truncate the file" do
+ File.open(@file, "w") { |f| f.puts "testing" }
+ File.size(@file).should > 0
+ File.open(@file, "w+") do |f|
+ f.pos.should == 0
+ f.should.eof?
+ end
+ File.size(@file).should == 0
+ end
+
+ it "opens a file for binary read-write starting at the beginning of the file" do
+ File.open(@file, "w") { |f| f.puts "testing" }
+ File.size(@file).should > 0
+ File.open(@file, "rb+") do |f|
+ f.binmode?.should == true
+ f.external_encoding.should == Encoding::ASCII_8BIT
+ f.pos.should == 0
+ f.should_not.eof?
+ end
+ File.open(@file, "r+b") do |f|
+ f.binmode?.should == true
+ f.external_encoding.should == Encoding::ASCII_8BIT
+ f.pos.should == 0
+ f.should_not.eof?
+ end
+ end
+
+ it "opens a file for binary read-write and truncate the file" do
+ File.open(@file, "w") { |f| f.puts "testing" }
+ File.size(@file).should > 0
+ File.open(@file, "wb+") do |f|
+ f.pos.should == 0
+ f.should.eof?
+ end
+ File.size(@file).should == 0
+ end
+
+ platform_is :linux do
+ guard -> { defined?(File::TMPFILE) } do
+ it "creates an unnamed temporary file with File::TMPFILE" do
+ dir = tmp("tmpfilespec")
+ mkdir_p dir
+ begin
+ Dir["#{dir}/*"].should == []
+ File.open(dir, "r+", flags: File::TMPFILE) do |io|
+ io.write("ruby")
+ io.flush
+ io.rewind
+ io.read.should == "ruby"
+ Dir["#{dir}/*"].should == []
+ end
+ rescue Errno::EOPNOTSUPP
+ skip "no support from the filesystem"
+ rescue Errno::EINVAL, Errno::EISDIR
+ skip "presumably bug in glibc"
+ ensure
+ rm_r dir
+ end
+ end
+ end
+ end
+
+ it "raises a TypeError if passed a filename that is not a String or Integer type" do
+ -> { File.open(true) }.should raise_error(TypeError)
+ -> { File.open(false) }.should raise_error(TypeError)
+ -> { File.open(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a SystemCallError if passed an invalid Integer type" do
+ -> { File.open(-1) }.should raise_error(SystemCallError)
+ end
+
+ it "raises an ArgumentError if passed the wrong number of arguments" do
+ -> { File.open(@file, File::CREAT, 0755, 'test') }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed an invalid string for mode" do
+ -> { File.open(@file, 'fake') }.should raise_error(ArgumentError)
+ end
+
+ it "defaults external_encoding to BINARY for binary modes" do
+ File.open(@file, 'rb') {|f| f.external_encoding.should == Encoding::BINARY}
+ File.open(@file, 'wb+') {|f| f.external_encoding.should == Encoding::BINARY}
+ end
+
+ it "uses the second argument as an options Hash" do
+ @fh = File.open(@file, mode: "r")
+ @fh.should be_an_instance_of(File)
+ end
+
+ it "calls #to_hash to convert the second argument to a Hash" do
+ options = mock("file open options")
+ options.should_receive(:to_hash).and_return({ mode: "r" })
+
+ @fh = File.open(@file, **options)
+ end
+
+ it "accepts extra flags as a keyword argument and combine with a string mode" do
+ -> {
+ File.open(@file, "w", flags: File::EXCL) { }
+ }.should raise_error(Errno::EEXIST)
+
+ -> {
+ File.open(@file, mode: "w", flags: File::EXCL) { }
+ }.should raise_error(Errno::EEXIST)
+ end
+
+ it "accepts extra flags as a keyword argument and combine with an integer mode" do
+ -> {
+ File.open(@file, File::WRONLY | File::CREAT, flags: File::EXCL) { }
+ }.should raise_error(Errno::EEXIST)
+ end
+
+ platform_is_not :windows do
+ describe "on a FIFO" do
+ before :each do
+ @fifo = tmp("File_open_fifo")
+ File.mkfifo(@fifo)
+ end
+
+ after :each do
+ rm_r @fifo
+ end
+
+ it "opens it as a normal file" do
+ file_w, file_r, read_bytes, written_length = nil
+
+ # open in threads, due to blocking open and writes
+ writer = Thread.new do
+ file_w = File.open(@fifo, 'w')
+ written_length = file_w.syswrite('hello')
+ end
+ reader = Thread.new do
+ file_r = File.open(@fifo, 'r')
+ read_bytes = file_r.sysread(5)
+ end
+
+ begin
+ writer.join
+ reader.join
+
+ written_length.should == 5
+ read_bytes.should == 'hello'
+ ensure
+ file_w.close if file_w
+ file_r.close if file_r
+ end
+ end
+ end
+ end
+
+ it "raises ArgumentError if mixing :newline and binary mode" do
+ -> {
+ File.open(@file, "rb", newline: :universal) {}
+ }.should raise_error(ArgumentError, "newline decorator with binary mode")
+ end
+
+ context "'x' flag" do
+ before :each do
+ @xfile = tmp("x-flag")
+ rm_r @xfile
+ end
+
+ after :each do
+ rm_r @xfile
+ end
+
+ it "does nothing if the file doesn't exist" do
+ File.open(@xfile, "wx") { |f| f.write("content") }
+ File.read(@xfile).should == "content"
+ end
+
+ it "throws a Errno::EEXIST error if the file exists" do
+ touch @xfile
+ -> { File.open(@xfile, "wx") }.should raise_error(Errno::EEXIST)
+ end
+
+ it "can't be used with 'r' and 'a' flags" do
+ -> { File.open(@xfile, "rx") }.should raise_error(ArgumentError, 'invalid access mode rx')
+ -> { File.open(@xfile, "ax") }.should raise_error(ArgumentError, 'invalid access mode ax')
+ end
+ end
+end
+
+describe "File.open when passed a file descriptor" do
+ before do
+ @content = "File#open when passed a file descriptor"
+ @name = tmp("file_open_with_fd.txt")
+ @fd = new_fd @name, "w:utf-8"
+ @file = nil
+ end
+
+ after do
+ @file.close if @file and not @file.closed?
+ rm_r @name
+ end
+
+ it "opens a file" do
+ @file = File.open(@fd, "w")
+ @file.should be_an_instance_of(File)
+ @file.fileno.should equal(@fd)
+ @file.write @content
+ @file.flush
+ File.read(@name).should == @content
+ end
+
+ it "opens a file when passed a block" do
+ @file = File.open(@fd, "w") do |f|
+ f.should be_an_instance_of(File)
+ f.fileno.should equal(@fd)
+ f.write @content
+ f
+ end
+ File.read(@name).should == @content
+ end
+end
+
+platform_is_not :windows do
+ describe "File.open" do
+ it_behaves_like :open_directory, :open
+ end
+end
diff --git a/spec/ruby/core/file/owned_spec.rb b/spec/ruby/core/file/owned_spec.rb
new file mode 100644
index 0000000000..06d6796da9
--- /dev/null
+++ b/spec/ruby/core/file/owned_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/owned'
+
+describe "File.owned?" do
+ it_behaves_like :file_owned, :owned?, File
+end
+
+describe "File.owned?" do
+ before :each do
+ @filename = tmp("i_exist")
+ touch(@filename)
+ end
+
+ after :each do
+ rm_r @filename
+ end
+
+ it "returns false if file does not exist" do
+ File.owned?("I_am_a_bogus_file").should == false
+ end
+
+ it "returns true if the file exist and is owned by the user" do
+ File.owned?(@filename).should == true
+ end
+
+ platform_is_not :windows do
+ as_user do
+ it "returns false when the file is not owned by the user" do
+ system_file = '/etc/passwd'
+ File.owned?(system_file).should == false
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/core/file/path_spec.rb b/spec/ruby/core/file/path_spec.rb
new file mode 100644
index 0000000000..dfa0c4ec02
--- /dev/null
+++ b/spec/ruby/core/file/path_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'shared/path'
+
+describe "File#path" do
+ it_behaves_like :file_path, :path
+end
+
+describe "File.path" do
+ before :each do
+ @name = tmp("file_path")
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "returns the string argument without any change" do
+ File.path("abc").should == "abc"
+ File.path("./abc").should == "./abc"
+ File.path("../abc").should == "../abc"
+ File.path("/./a/../bc").should == "/./a/../bc"
+ end
+
+ it "returns path for File argument" do
+ File.open(@name, "w") do |f|
+ File.path(f).should == @name
+ end
+ end
+
+ it "returns path for Pathname argument" do
+ require "pathname"
+ File.path(Pathname.new(@name)).should == @name
+ end
+
+ it "calls #to_path for non-string argument and returns result" do
+ path = mock("path")
+ path.should_receive(:to_path).and_return("abc")
+ File.path(path).should == "abc"
+ end
+end
diff --git a/spec/ruby/core/file/pipe_spec.rb b/spec/ruby/core/file/pipe_spec.rb
new file mode 100644
index 0000000000..01d72dbe85
--- /dev/null
+++ b/spec/ruby/core/file/pipe_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/pipe'
+
+describe "File.pipe?" do
+ it_behaves_like :file_pipe, :pipe?, File
+end
+
+describe "File.pipe?" do
+ it "returns false if file does not exist" do
+ File.pipe?("I_am_a_bogus_file").should == false
+ end
+
+ it "returns false if the file is not a pipe" do
+ filename = tmp("i_exist")
+ touch(filename)
+
+ File.pipe?(filename).should == false
+
+ rm_r filename
+ end
+
+ platform_is_not :windows do
+ it "returns true if the file is a pipe" do
+ filename = tmp("i_am_a_pipe")
+ File.mkfifo(filename)
+
+ File.pipe?(filename).should == true
+
+ rm_r filename
+ end
+ end
+end
diff --git a/spec/ruby/core/file/printf_spec.rb b/spec/ruby/core/file/printf_spec.rb
new file mode 100644
index 0000000000..2530419fc7
--- /dev/null
+++ b/spec/ruby/core/file/printf_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative '../kernel/shared/sprintf'
+
+describe "File#printf" do
+ it_behaves_like :kernel_sprintf, -> format, *args {
+ begin
+ @filename = tmp("printf.txt")
+
+ File.open(@filename, "w", encoding: "utf-8") do |f|
+ f.printf(format, *args)
+ end
+
+ File.read(@filename, encoding: "utf-8")
+ ensure
+ rm_r @filename
+ end
+ }
+end
diff --git a/spec/ruby/core/file/read_spec.rb b/spec/ruby/core/file/read_spec.rb
new file mode 100644
index 0000000000..67a3325cbd
--- /dev/null
+++ b/spec/ruby/core/file/read_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/read'
+
+describe "File.read" do
+ it_behaves_like :file_read_directory, :read, File
+end
diff --git a/spec/ruby/core/file/readable_real_spec.rb b/spec/ruby/core/file/readable_real_spec.rb
new file mode 100644
index 0000000000..524466cd96
--- /dev/null
+++ b/spec/ruby/core/file/readable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/readable_real'
+
+describe "File.readable_real?" do
+ it_behaves_like :file_readable_real, :readable_real?, File
+ it_behaves_like :file_readable_real_missing, :readable_real?, File
+end
diff --git a/spec/ruby/core/file/readable_spec.rb b/spec/ruby/core/file/readable_spec.rb
new file mode 100644
index 0000000000..ed75a23f39
--- /dev/null
+++ b/spec/ruby/core/file/readable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/readable'
+
+describe "File.readable?" do
+ it_behaves_like :file_readable, :readable?, File
+ it_behaves_like :file_readable_missing, :readable?, File
+end
diff --git a/spec/ruby/core/file/readlink_spec.rb b/spec/ruby/core/file/readlink_spec.rb
new file mode 100644
index 0000000000..20741ba121
--- /dev/null
+++ b/spec/ruby/core/file/readlink_spec.rb
@@ -0,0 +1,86 @@
+require_relative '../../spec_helper'
+
+describe "File.readlink" do
+ # symlink/readlink are not supported on Windows
+ platform_is_not :windows do
+ describe "with absolute paths" do
+ before :each do
+ @file = tmp('file_readlink.txt')
+ @link = tmp('file_readlink.lnk')
+
+ File.symlink(@file, @link)
+ end
+
+ after :each do
+ rm_r @file, @link
+ end
+
+ it "returns the name of the file referenced by the given link" do
+ touch @file
+ File.readlink(@link).should == @file
+ end
+
+ it "returns the name of the file referenced by the given link when the file does not exist" do
+ File.readlink(@link).should == @file
+ end
+
+ it "raises an Errno::ENOENT if there is no such file" do
+ # TODO: missing_file
+ -> { File.readlink("/this/surely/does/not/exist") }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises an Errno::EINVAL if called with a normal file" do
+ touch @file
+ -> { File.readlink(@file) }.should raise_error(Errno::EINVAL)
+ end
+ end
+
+ describe "with paths containing unicode characters" do
+ before :each do
+ @file = tmp('tàrget.txt')
+ @link = tmp('lïnk.lnk')
+ File.symlink(@file, @link)
+ end
+
+ after :each do
+ rm_r @file, @link
+ end
+
+ it "returns the name of the file referenced by the given link" do
+ touch @file
+ result = File.readlink(@link)
+ result.encoding.should equal Encoding.find('filesystem')
+ result.should == @file.dup.force_encoding(Encoding.find('filesystem'))
+ end
+ end
+
+ describe "when changing the working directory" do
+ before :each do
+ @cwd = Dir.pwd
+ @tmpdir = tmp("/readlink")
+ Dir.mkdir @tmpdir
+ Dir.chdir @tmpdir
+
+ @link = 'readlink_link'
+ @file = 'readlink_file'
+
+ File.symlink(@file, @link)
+ end
+
+ after :each do
+ rm_r @file, @link
+ Dir.chdir @cwd
+ Dir.rmdir @tmpdir
+ end
+
+ it "returns the name of the file referenced by the given link" do
+ touch @file
+ File.readlink(@link).should == @file
+ end
+
+ it "returns the name of the file referenced by the given link when the file does not exist" do
+ File.readlink(@link).should == @file
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/realdirpath_spec.rb b/spec/ruby/core/file/realdirpath_spec.rb
new file mode 100644
index 0000000000..74053afce3
--- /dev/null
+++ b/spec/ruby/core/file/realdirpath_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ describe "File.realdirpath" do
+ before :each do
+ @real_dir = tmp('dir_realdirpath_real')
+ @fake_dir = tmp('dir_realdirpath_fake')
+ @link_dir = tmp('dir_realdirpath_link')
+
+ mkdir_p @real_dir
+ File.symlink(@real_dir, @link_dir)
+
+ @file = File.join(@real_dir, 'file')
+ @link = File.join(@link_dir, 'link')
+
+ touch @file
+ File.symlink(@file, @link)
+
+ @fake_file_in_real_dir = File.join(@real_dir, 'fake_file_in_real_dir')
+ @fake_file_in_fake_dir = File.join(@fake_dir, 'fake_file_in_fake_dir')
+ @fake_link_to_real_dir = File.join(@link_dir, 'fake_link_to_real_dir')
+ @fake_link_to_fake_dir = File.join(@link_dir, 'fake_link_to_fake_dir')
+
+ File.symlink(@fake_file_in_real_dir, @fake_link_to_real_dir)
+ File.symlink(@fake_file_in_fake_dir, @fake_link_to_fake_dir)
+
+ @dir_for_relative_link = File.join(@real_dir, 'dir1')
+ mkdir_p @dir_for_relative_link
+
+ @relative_path_to_file = File.join('..', 'file')
+ @relative_symlink = File.join(@dir_for_relative_link, 'link')
+ File.symlink(@relative_path_to_file, @relative_symlink)
+ end
+
+ after :each do
+ rm_r @file, @link, @fake_link_to_real_dir, @fake_link_to_fake_dir, @real_dir, @link_dir
+ end
+
+ it "returns '/' when passed '/'" do
+ File.realdirpath('/').should == '/'
+ end
+
+ it "returns the real (absolute) pathname not containing symlinks" do
+ File.realdirpath(@link).should == @file
+ end
+
+ it "uses base directory for interpreting relative pathname" do
+ File.realdirpath(File.basename(@link), @link_dir).should == @file
+ end
+
+ it "uses current directory for interpreting relative pathname" do
+ Dir.chdir @link_dir do
+ File.realdirpath(File.basename(@link)).should == @file
+ end
+ end
+
+ it "uses link directory for expanding relative links" do
+ File.realdirpath(@relative_symlink).should == @file
+ end
+
+ it "raises an Errno::ELOOP if the symlink points to itself" do
+ File.unlink @link
+ File.symlink(@link, @link)
+ -> { File.realdirpath(@link) }.should raise_error(Errno::ELOOP)
+ end
+
+ it "returns the real (absolute) pathname if the file is absent" do
+ File.realdirpath(@fake_file_in_real_dir).should == @fake_file_in_real_dir
+ end
+
+ it "raises Errno::ENOENT if the directory is absent" do
+ -> { File.realdirpath(@fake_file_in_fake_dir) }.should raise_error(Errno::ENOENT)
+ end
+
+ it "returns the real (absolute) pathname if the symlink points to an absent file" do
+ File.realdirpath(@fake_link_to_real_dir).should == @fake_file_in_real_dir
+ end
+
+ it "raises Errno::ENOENT if the symlink points to an absent directory" do
+ -> { File.realdirpath(@fake_link_to_fake_dir) }.should raise_error(Errno::ENOENT)
+ end
+ end
+end
+
+platform_is :windows do
+ describe "File.realdirpath" do
+ before :each do
+ @file = tmp("realdirpath")
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the same path" do
+ touch @file
+ File.realdirpath(@file).should == @file
+ end
+
+ it "returns the same path even if the last component does not exist" do
+ File.realdirpath(@file).should == @file
+ end
+ end
+end
diff --git a/spec/ruby/core/file/realpath_spec.rb b/spec/ruby/core/file/realpath_spec.rb
new file mode 100644
index 0000000000..bd27e09da6
--- /dev/null
+++ b/spec/ruby/core/file/realpath_spec.rb
@@ -0,0 +1,94 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ describe "File.realpath" do
+ before :each do
+ @real_dir = tmp('dir_realpath_real')
+ @link_dir = tmp('dir_realpath_link')
+
+ mkdir_p @real_dir
+ File.symlink(@real_dir, @link_dir)
+
+ @file = File.join(@real_dir, 'file')
+ @link = File.join(@link_dir, 'link')
+
+ touch @file
+ File.symlink(@file, @link)
+
+ @fake_file = File.join(@real_dir, 'fake_file')
+ @fake_link = File.join(@link_dir, 'fake_link')
+
+ File.symlink(@fake_file, @fake_link)
+
+ @dir_for_relative_link = File.join(@real_dir, 'dir1')
+ mkdir_p @dir_for_relative_link
+
+ @relative_path_to_file = File.join('..', 'file')
+ @relative_symlink = File.join(@dir_for_relative_link, 'link')
+ File.symlink(@relative_path_to_file, @relative_symlink)
+ end
+
+ after :each do
+ rm_r @file, @link, @fake_link, @real_dir, @link_dir
+ end
+
+ it "returns '/' when passed '/'" do
+ File.realpath('/').should == '/'
+ end
+
+ it "returns the real (absolute) pathname not containing symlinks" do
+ File.realpath(@link).should == @file
+ end
+
+ it "uses base directory for interpreting relative pathname" do
+ File.realpath(File.basename(@link), @link_dir).should == @file
+ end
+
+ it "uses current directory for interpreting relative pathname" do
+ Dir.chdir @link_dir do
+ File.realpath(File.basename(@link)).should == @file
+ end
+ end
+
+ it "uses link directory for expanding relative links" do
+ File.realpath(@relative_symlink).should == @file
+ end
+
+ it "raises an Errno::ELOOP if the symlink points to itself" do
+ File.unlink @link
+ File.symlink(@link, @link)
+ -> { File.realpath(@link) }.should raise_error(Errno::ELOOP)
+ end
+
+ it "raises Errno::ENOENT if the file is absent" do
+ -> { File.realpath(@fake_file) }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises Errno::ENOENT if the symlink points to an absent file" do
+ -> { File.realpath(@fake_link) }.should raise_error(Errno::ENOENT)
+ end
+
+ it "converts the argument with #to_path" do
+ path = mock("path")
+ path.should_receive(:to_path).and_return(__FILE__)
+ File.realpath(path).should == File.realpath(__FILE__ )
+ end
+ end
+end
+
+platform_is :windows do
+ describe "File.realpath" do
+ before :each do
+ @file = tmp("realpath")
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the same path" do
+ File.realpath(@file).should == @file
+ end
+ end
+end
diff --git a/spec/ruby/core/file/rename_spec.rb b/spec/ruby/core/file/rename_spec.rb
new file mode 100644
index 0000000000..f2c18d4905
--- /dev/null
+++ b/spec/ruby/core/file/rename_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+describe "File.rename" do
+ before :each do
+ @old = tmp("file_rename.txt")
+ @new = tmp("file_rename.new")
+
+ rm_r @new
+ touch(@old) { |f| f.puts "hello" }
+ end
+
+ after :each do
+ rm_r @old, @new
+ end
+
+ it "renames a file" do
+ File.should.exist?(@old)
+ File.should_not.exist?(@new)
+ File.rename(@old, @new)
+ File.should_not.exist?(@old)
+ File.should.exist?(@new)
+ end
+
+ it "raises an Errno::ENOENT if the source does not exist" do
+ rm_r @old
+ -> { File.rename(@old, @new) }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises an ArgumentError if not passed two arguments" do
+ -> { File.rename }.should raise_error(ArgumentError)
+ -> { File.rename(@file) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed String types" do
+ -> { File.rename(1, 2) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/file/reopen_spec.rb b/spec/ruby/core/file/reopen_spec.rb
new file mode 100644
index 0000000000..858d424c67
--- /dev/null
+++ b/spec/ruby/core/file/reopen_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "File#reopen" do
+ before :each do
+ @name_a = tmp("file_reopen_a.txt")
+ @name_b = tmp("file_reopen_b.txt")
+ @content_a = "File#reopen a"
+ @content_b = "File#reopen b"
+
+ touch(@name_a) { |f| f.write @content_a }
+ touch(@name_b) { |f| f.write @content_b }
+
+ @file = nil
+ end
+
+ after :each do
+ @file.close if @file and not @file.closed?
+ rm_r @name_a, @name_b
+ end
+
+ it "resets the stream to a new file path" do
+ file = File.new @name_a, "r"
+ file.read.should == @content_a
+ @file = file.reopen(@name_b, "r")
+ @file.read.should == @content_b
+ end
+
+ it "calls #to_path to convert an Object" do
+ @file = File.new(@name_a).reopen(mock_to_path(@name_b), "r")
+ @file.read.should == @content_b
+ end
+end
diff --git a/spec/ruby/core/file/setgid_spec.rb b/spec/ruby/core/file/setgid_spec.rb
new file mode 100644
index 0000000000..f5df5390f5
--- /dev/null
+++ b/spec/ruby/core/file/setgid_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/setgid'
+
+describe "File.setgid?" do
+ it_behaves_like :file_setgid, :setgid?, File
+end
+
+describe "File.setgid?" do
+ before :each do
+ @name = tmp('test.txt')
+ touch @name
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "returns false if the file was just made" do
+ File.setgid?(@name).should == false
+ end
+
+ it "returns false if the file does not exist" do
+ rm_r @name # delete it prematurely, just for this part
+ File.setgid?(@name).should == false
+ end
+
+ as_superuser do
+ platform_is_not :windows do
+ it "returns true when the gid bit is set" do
+ system "chmod g+s #{@name}"
+
+ File.setgid?(@name).should == true
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/setuid_spec.rb b/spec/ruby/core/file/setuid_spec.rb
new file mode 100644
index 0000000000..281ef01ab9
--- /dev/null
+++ b/spec/ruby/core/file/setuid_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/setuid'
+
+describe "File.setuid?" do
+ it_behaves_like :file_setuid, :setuid?, File
+end
+
+describe "File.setuid?" do
+ before :each do
+ @name = tmp('test.txt')
+ touch @name
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "returns false if the file was just made" do
+ File.setuid?(@name).should == false
+ end
+
+ it "returns false if the file does not exist" do
+ rm_r @name # delete it prematurely, just for this part
+ File.setuid?(@name).should == false
+ end
+
+ platform_is_not :windows do
+ it "returns true when the gid bit is set" do
+ platform_is :solaris do
+ # Solaris requires execute bit before setting suid
+ system "chmod u+x #{@name}"
+ end
+ system "chmod u+s #{@name}"
+
+ File.setuid?(@name).should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/file/shared/fnmatch.rb b/spec/ruby/core/file/shared/fnmatch.rb
new file mode 100644
index 0000000000..94f22144b0
--- /dev/null
+++ b/spec/ruby/core/file/shared/fnmatch.rb
@@ -0,0 +1,249 @@
+describe :file_fnmatch, shared: true do
+ it "matches entire strings" do
+ File.send(@method, 'cat', 'cat').should == true
+ end
+
+ it "does not match partial strings" do
+ File.send(@method, 'cat', 'category').should == false
+ end
+
+ it "does not support { } patterns by default" do
+ File.send(@method, 'c{at,ub}s', 'cats').should == false
+ File.send(@method, 'c{at,ub}s', 'c{at,ub}s').should == true
+ end
+
+ it "supports some { } patterns when File::FNM_EXTGLOB is passed" do
+ File.send(@method, "{a,b}", "a", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{a,b}", "b", File::FNM_EXTGLOB).should == true
+ File.send(@method, "c{at,ub}s", "cats", File::FNM_EXTGLOB).should == true
+ File.send(@method, "c{at,ub}s", "cubs", File::FNM_EXTGLOB).should == true
+ File.send(@method, "-c{at,ub}s-", "-cats-", File::FNM_EXTGLOB).should == true
+ File.send(@method, "-c{at,ub}s-", "-cubs-", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{a,b,c}{d,e,f}{g,h}", "adg", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{a,b,c}{d,e,f}{g,h}", "bdg", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{a,b,c}{d,e,f}{g,h}", "ceh", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{aa,bb,cc,dd}", "aa", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{aa,bb,cc,dd}", "bb", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{aa,bb,cc,dd}", "cc", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{aa,bb,cc,dd}", "dd", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{1,5{a,b{c,d}}}", "1", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{1,5{a,b{c,d}}}", "5a", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{1,5{a,b{c,d}}}", "5bc", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{1,5{a,b{c,d}}}", "5bd", File::FNM_EXTGLOB).should == true
+ File.send(@method, "\\\\{a\\,b,b\\}c}", "\\a,b", File::FNM_EXTGLOB).should == true
+ File.send(@method, "\\\\{a\\,b,b\\}c}", "\\b}c", File::FNM_EXTGLOB).should == true
+ end
+
+ it "doesn't support some { } patterns even when File::FNM_EXTGLOB is passed" do
+ File.send(@method, "a{0..3}b", "a0b", File::FNM_EXTGLOB).should == false
+ File.send(@method, "a{0..3}b", "a1b", File::FNM_EXTGLOB).should == false
+ File.send(@method, "a{0..3}b", "a2b", File::FNM_EXTGLOB).should == false
+ File.send(@method, "a{0..3}b", "a3b", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{0..12}", "0", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{0..12}", "6", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{0..12}", "12", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{3..-2}", "3", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{3..-2}", "0", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{3..-2}", "-2", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{a..g}", "a", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{a..g}", "d", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{a..g}", "g", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{g..a}", "a", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{g..a}", "d", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{g..a}", "g", File::FNM_EXTGLOB).should == false
+ File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: {", File::FNM_EXTGLOB).should == false
+ File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: ,", File::FNM_EXTGLOB).should == false
+ File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: }", File::FNM_EXTGLOB).should == false
+ File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: {", File::FNM_EXTGLOB).should == false
+ end
+
+ it "doesn't match an extra } when File::FNM_EXTGLOB is passed" do
+ File.send(@method, 'c{at,ub}}s', 'cats', File::FNM_EXTGLOB).should == false
+ end
+
+ it "matches when both FNM_EXTGLOB and FNM_PATHNAME are passed" do
+ File.send(@method, "?.md", "a.md", File::FNM_EXTGLOB | File::FNM_PATHNAME).should == true
+ end
+
+ it "matches a single character for each ? character" do
+ File.send(@method, 'c?t', 'cat').should == true
+ File.send(@method, 'c??t', 'cat').should == false
+ end
+
+ it "matches zero or more characters for each * character" do
+ File.send(@method, 'c*', 'cats').should == true
+ File.send(@method, 'c*t', 'c/a/b/t').should == true
+ end
+
+ it "does not match unterminated range of characters" do
+ File.send(@method, 'abc[de', 'abcd').should == false
+ end
+
+ it "does not match unterminated range of characters as a literal" do
+ File.send(@method, 'abc[de', 'abc[de').should == false
+ end
+
+ it "matches ranges of characters using bracket expression (e.g. [a-z])" do
+ File.send(@method, 'ca[a-z]', 'cat').should == true
+ end
+
+ it "matches ranges of characters using bracket expression, taking case into account" do
+ File.send(@method, '[a-z]', 'D').should == false
+ File.send(@method, '[^a-z]', 'D').should == true
+ File.send(@method, '[A-Z]', 'd').should == false
+ File.send(@method, '[^A-Z]', 'd').should == true
+ File.send(@method, '[a-z]', 'D', File::FNM_CASEFOLD).should == true
+ end
+
+ it "does not match characters outside of the range of the bracket expression" do
+ File.send(@method, 'ca[x-z]', 'cat').should == false
+ File.send(@method, '/ca[s][s-t]/rul[a-b]/[z]he/[x-Z]orld', '/cats/rule/the/World').should == false
+ end
+
+ it "matches ranges of characters using exclusive bracket expression (e.g. [^t] or [!t])" do
+ File.send(@method, 'ca[^t]', 'cat').should == false
+ File.send(@method, 'ca[!t]', 'cat').should == false
+ end
+
+ it "matches characters with a case sensitive comparison" do
+ File.send(@method, 'cat', 'CAT').should == false
+ end
+
+ it "matches characters with case insensitive comparison when flags includes FNM_CASEFOLD" do
+ File.send(@method, 'cat', 'CAT', File::FNM_CASEFOLD).should == true
+ end
+
+ platform_is_not :windows do
+ it "doesn't match case sensitive characters on platforms with case sensitive paths, when flags include FNM_SYSCASE" do
+ File.send(@method, 'cat', 'CAT', File::FNM_SYSCASE).should == false
+ end
+ end
+
+ platform_is :windows do
+ it "matches case sensitive characters on platforms with case insensitive paths, when flags include FNM_SYSCASE" do
+ File.send(@method, 'cat', 'CAT', File::FNM_SYSCASE).should == true
+ end
+ end
+
+ it "does not match '/' characters with ? or * when flags includes FNM_PATHNAME" do
+ File.send(@method, '?', '/', File::FNM_PATHNAME).should == false
+ File.send(@method, '*', '/', File::FNM_PATHNAME).should == false
+ end
+
+ it "does not match '/' characters inside bracket expressions when flags includes FNM_PATHNAME" do
+ File.send(@method, '[/]', '/', File::FNM_PATHNAME).should == false
+ end
+
+ it "matches literal ? or * in path when pattern includes \\? or \\*" do
+ File.send(@method, '\?', '?').should == true
+ File.send(@method, '\?', 'a').should == false
+
+ File.send(@method, '\*', '*').should == true
+ File.send(@method, '\*', 'a').should == false
+ end
+
+ it "matches literal character (e.g. 'a') in path when pattern includes escaped character (e.g. \\a)" do
+ File.send(@method, '\a', 'a').should == true
+ File.send(@method, 'this\b', 'thisb').should == true
+ end
+
+ it "matches '\\' characters in path when flags includes FNM_NOESACPE" do
+ File.send(@method, '\a', '\a', File::FNM_NOESCAPE).should == true
+ File.send(@method, '\a', 'a', File::FNM_NOESCAPE).should == false
+ File.send(@method, '\[foo\]\[bar\]', '[foo][bar]', File::FNM_NOESCAPE).should == false
+ end
+
+ it "escapes special characters inside bracket expression" do
+ File.send(@method, '[\?]', '?').should == true
+ File.send(@method, '[\*]', '*').should == true
+ end
+
+ it "does not match leading periods in filenames with wildcards by default" do
+ File.should_not.send(@method, '*', '.profile')
+ File.should.send(@method, '*', 'home/.profile')
+ File.should.send(@method, '*/*', 'home/.profile')
+ File.should_not.send(@method, '*/*', 'dave/.profile', File::FNM_PATHNAME)
+ end
+
+ it "matches patterns with leading periods to dotfiles by default" do
+ File.send(@method, '.*', '.profile').should == true
+ File.send(@method, ".*file", "nondotfile").should == false
+ end
+
+ it "matches leading periods in filenames when flags includes FNM_DOTMATCH" do
+ File.send(@method, '*', '.profile', File::FNM_DOTMATCH).should == true
+ File.send(@method, '*', 'home/.profile', File::FNM_DOTMATCH).should == true
+ end
+
+ it "matches multiple directories with ** and *" do
+ files = '**/*.rb'
+ File.send(@method, files, 'main.rb').should == false
+ File.send(@method, files, './main.rb').should == false
+ File.send(@method, files, 'lib/song.rb').should == true
+ File.send(@method, '**.rb', 'main.rb').should == true
+ File.send(@method, '**.rb', './main.rb').should == false
+ File.send(@method, '**.rb', 'lib/song.rb').should == true
+ File.send(@method, '*', 'dave/.profile').should == true
+ end
+
+ it "matches multiple directories with ** when flags includes File::FNM_PATHNAME" do
+ files = '**/*.rb'
+ flags = File::FNM_PATHNAME
+
+ File.send(@method, files, 'main.rb', flags).should == true
+ File.send(@method, files, 'one/two/three/main.rb', flags).should == true
+ File.send(@method, files, './main.rb', flags).should == false
+
+ flags = File::FNM_PATHNAME | File::FNM_DOTMATCH
+
+ File.send(@method, files, './main.rb', flags).should == true
+ File.send(@method, files, 'one/two/.main.rb', flags).should == true
+
+ File.send(@method, "**/best/*", 'lib/my/best/song.rb').should == true
+ end
+
+ it "returns false if '/' in pattern do not match '/' in path when flags includes FNM_PATHNAME" do
+ pattern = '*/*'
+ File.send(@method, pattern, 'dave/.profile', File::FNM_PATHNAME).should be_false
+
+ pattern = '**/foo'
+ File.send(@method, pattern, 'a/.b/c/foo', File::FNM_PATHNAME).should be_false
+ end
+
+ it "returns true if '/' in pattern match '/' in path when flags includes FNM_PATHNAME" do
+ pattern = '*/*'
+ File.send(@method, pattern, 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true
+
+ pattern = '**/foo'
+ File.send(@method, pattern, 'a/b/c/foo', File::FNM_PATHNAME).should be_true
+ File.send(@method, pattern, '/a/b/c/foo', File::FNM_PATHNAME).should be_true
+ File.send(@method, pattern, 'c:/a/b/c/foo', File::FNM_PATHNAME).should be_true
+ File.send(@method, pattern, 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.send(@method, '\*', mock_to_path('a')).should == false
+ end
+
+ it "raises a TypeError if the first and second arguments are not string-like" do
+ -> { File.send(@method, nil, nil, 0, 0) }.should raise_error(ArgumentError)
+ -> { File.send(@method, 1, 'some/thing') }.should raise_error(TypeError)
+ -> { File.send(@method, 'some/thing', 1) }.should raise_error(TypeError)
+ -> { File.send(@method, 1, 1) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the third argument is not an Integer" do
+ -> { File.send(@method, "*/place", "path/to/file", "flags") }.should raise_error(TypeError)
+ -> { File.send(@method, "*/place", "path/to/file", nil) }.should raise_error(TypeError)
+ end
+
+ it "does not raise a TypeError if the third argument can be coerced to an Integer" do
+ flags = mock("flags")
+ flags.should_receive(:to_int).and_return(10)
+ -> { File.send(@method, "*/place", "path/to/file", flags) }.should_not raise_error
+ end
+
+ it "matches multibyte characters" do
+ File.fnmatch("*/ä/ø/ñ", "a/ä/ø/ñ").should == true
+ end
+end
diff --git a/spec/ruby/core/file/shared/open.rb b/spec/ruby/core/file/shared/open.rb
new file mode 100644
index 0000000000..677a82a351
--- /dev/null
+++ b/spec/ruby/core/file/shared/open.rb
@@ -0,0 +1,12 @@
+require_relative '../../dir/fixtures/common'
+
+describe :open_directory, shared: true do
+ it "opens directories" do
+ file = File.send(@method, tmp(""))
+ begin
+ file.should be_kind_of(File)
+ ensure
+ file.close
+ end
+ end
+end
diff --git a/spec/ruby/core/file/shared/path.rb b/spec/ruby/core/file/shared/path.rb
new file mode 100644
index 0000000000..ee8109ba05
--- /dev/null
+++ b/spec/ruby/core/file/shared/path.rb
@@ -0,0 +1,94 @@
+describe :file_path, shared: true do
+ before :each do
+ @name = "file_to_path"
+ @path = tmp(@name)
+ touch @path
+ end
+
+ after :each do
+ @file.close if @file and !@file.closed?
+ rm_r @path
+ end
+
+ it "returns a String" do
+ @file = File.new @path
+ @file.send(@method).should be_an_instance_of(String)
+ end
+
+ it "returns a different String on every call" do
+ @file = File.new @path
+ path1 = @file.send(@method)
+ path2 = @file.send(@method)
+ path1.should == path2
+ path1.should_not.equal?(path2)
+ end
+
+ it "returns a mutable String" do
+ @file = File.new @path.dup.freeze
+ path = @file.send(@method)
+ path.should == @path
+ path.should_not.frozen?
+ path << "test"
+ @file.send(@method).should == @path
+ end
+
+ it "calls to_str on argument and returns exact value" do
+ path = mock('path')
+ path.should_receive(:to_str).and_return(@path)
+ @file = File.new path
+ @file.send(@method).should == @path
+ end
+
+ it "does not normalise the path it returns" do
+ Dir.chdir(tmp("")) do
+ unorm = "./#{@name}"
+ @file = File.new unorm
+ @file.send(@method).should == unorm
+ end
+ end
+
+ it "does not canonicalize the path it returns" do
+ dir = File.basename tmp("")
+ path = "#{tmp("")}../#{dir}/#{@name}"
+ @file = File.new path
+ @file.send(@method).should == path
+ end
+
+ it "does not absolute-ise the path it returns" do
+ Dir.chdir(tmp("")) do
+ @file = File.new @name
+ @file.send(@method).should == @name
+ end
+ end
+
+ it "preserves the encoding of the path" do
+ path = @path.force_encoding("euc-jp")
+ @file = File.new path
+ @file.send(@method).encoding.should == Encoding.find("euc-jp")
+ end
+
+ platform_is :linux do
+ guard -> { defined?(File::TMPFILE) } do
+ before :each do
+ @dir = tmp("tmpfilespec")
+ mkdir_p @dir
+ end
+
+ after :each do
+ rm_r @dir
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "raises IOError if file was opened with File::TMPFILE" do
+ begin
+ File.open(@dir, File::RDWR | File::TMPFILE) do |f|
+ -> { f.send(@method) }.should raise_error(IOError)
+ end
+ rescue Errno::EOPNOTSUPP, Errno::EINVAL, Errno::EISDIR
+ skip "no support from the filesystem"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/shared/read.rb b/spec/ruby/core/file/shared/read.rb
new file mode 100644
index 0000000000..f232235298
--- /dev/null
+++ b/spec/ruby/core/file/shared/read.rb
@@ -0,0 +1,15 @@
+require_relative '../../dir/fixtures/common'
+
+describe :file_read_directory, shared: true do
+ platform_is :darwin, :linux, :freebsd, :openbsd, :windows do
+ it "raises an Errno::EISDIR when passed a path that is a directory" do
+ -> { @object.send(@method, ".") }.should raise_error(Errno::EISDIR)
+ end
+ end
+
+ platform_is :netbsd do
+ it "does not raises any exception when passed a path that is a directory" do
+ -> { @object.send(@method, ".") }.should_not raise_error
+ end
+ end
+end
diff --git a/spec/ruby/core/file/shared/stat.rb b/spec/ruby/core/file/shared/stat.rb
new file mode 100644
index 0000000000..fdaf97ea61
--- /dev/null
+++ b/spec/ruby/core/file/shared/stat.rb
@@ -0,0 +1,32 @@
+describe :file_stat, shared: true do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns a File::Stat object if the given file exists" do
+ st = File.send(@method, @file)
+ st.should be_an_instance_of(File::Stat)
+ end
+
+ it "returns a File::Stat object when called on an instance of File" do
+ File.open(@file) do |f|
+ st = f.send(@method)
+ st.should be_an_instance_of(File::Stat)
+ end
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.send(@method, mock_to_path(@file))
+ end
+
+ it "raises an Errno::ENOENT if the file does not exist" do
+ -> {
+ File.send(@method, "fake_file")
+ }.should raise_error(Errno::ENOENT)
+ end
+end
diff --git a/spec/ruby/core/file/shared/unlink.rb b/spec/ruby/core/file/shared/unlink.rb
new file mode 100644
index 0000000000..e339e93271
--- /dev/null
+++ b/spec/ruby/core/file/shared/unlink.rb
@@ -0,0 +1,61 @@
+describe :file_unlink, shared: true do
+ before :each do
+ @file1 = tmp('test.txt')
+ @file2 = tmp('test2.txt')
+
+ touch @file1
+ touch @file2
+ end
+
+ after :each do
+ File.send(@method, @file1) if File.exist?(@file1)
+ File.send(@method, @file2) if File.exist?(@file2)
+
+ @file1 = nil
+ @file2 = nil
+ end
+
+ it "returns 0 when called without arguments" do
+ File.send(@method).should == 0
+ end
+
+ it "deletes a single file" do
+ File.send(@method, @file1).should == 1
+ File.should_not.exist?(@file1)
+ end
+
+ it "deletes multiple files" do
+ File.send(@method, @file1, @file2).should == 2
+ File.should_not.exist?(@file1)
+ File.should_not.exist?(@file2)
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { File.send(@method, 1) }.should raise_error(TypeError)
+ end
+
+ it "raises an Errno::ENOENT when the given file doesn't exist" do
+ -> { File.send(@method, 'bogus') }.should raise_error(Errno::ENOENT)
+ end
+
+ it "coerces a given parameter into a string if possible" do
+ mock = mock("to_str")
+ mock.should_receive(:to_str).and_return(@file1)
+ File.send(@method, mock).should == 1
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.send(@method, mock_to_path(@file1)).should == 1
+ end
+
+ platform_is :windows do
+ it "allows deleting an open file with File::SHARE_DELETE" do
+ path = tmp("share_delete.txt")
+ File.open(path, mode: File::CREAT | File::WRONLY | File::BINARY | File::SHARE_DELETE) do |f|
+ File.should.exist?(path)
+ File.send(@method, path)
+ end
+ File.should_not.exist?(path)
+ end
+ end
+end
diff --git a/spec/ruby/core/file/size_spec.rb b/spec/ruby/core/file/size_spec.rb
new file mode 100644
index 0000000000..11d20cbacb
--- /dev/null
+++ b/spec/ruby/core/file/size_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/size'
+
+describe "File.size?" do
+ it_behaves_like :file_size, :size?, File
+end
+
+describe "File.size?" do
+ it_behaves_like :file_size_to_io, :size?, File
+end
+
+describe "File.size?" do
+ it_behaves_like :file_size_nil_when_missing, :size?, File
+end
+
+describe "File.size?" do
+ it_behaves_like :file_size_nil_when_empty, :size?, File
+end
+
+describe "File.size?" do
+ it_behaves_like :file_size_with_file_argument, :size?, File
+end
+
+describe "File.size" do
+ it_behaves_like :file_size, :size, File
+end
+
+describe "File.size" do
+ it_behaves_like :file_size_to_io, :size, File
+end
+
+describe "File.size" do
+ it_behaves_like :file_size_raise_when_missing, :size, File
+end
+
+describe "File.size" do
+ it_behaves_like :file_size_0_when_empty, :size, File
+end
+
+describe "File.size" do
+ it_behaves_like :file_size_with_file_argument, :size, File
+end
+
+describe "File#size" do
+
+ before :each do
+ @name = tmp('i_exist')
+ touch(@name) { |f| f.write 'rubinius' }
+ @file = File.new @name
+ @file_org = @file
+ end
+
+ after :each do
+ @file_org.close unless @file_org.closed?
+ rm_r @name
+ end
+
+ it "is an instance method" do
+ @file.respond_to?(:size).should be_true
+ end
+
+ it "returns the file's size as an Integer" do
+ @file.size.should be_an_instance_of(Integer)
+ end
+
+ it "returns the file's size in bytes" do
+ @file.size.should == 8
+ end
+
+ platform_is_not :windows do # impossible to remove opened file on Windows
+ it "returns the cached size of the file if subsequently deleted" do
+ rm_r @file.path
+ @file.size.should == 8
+ end
+ end
+
+ it "returns the file's current size even if modified" do
+ File.open(@file.path,'a') {|f| f.write '!'}
+ @file.size.should == 9
+ end
+
+ it "raises an IOError on a closed file" do
+ @file.close
+ -> { @file.size }.should raise_error(IOError)
+ end
+
+ platform_is_not :windows do
+ it "follows symlinks if necessary" do
+ ln_file = tmp('i_exist_ln')
+ rm_r ln_file
+
+ begin
+ File.symlink(@file.path, ln_file).should == 0
+ file = File.new(ln_file)
+ file.size.should == 8
+ ensure
+ file.close if file && !file.closed?
+ rm_r ln_file
+ end
+ end
+ end
+end
+
+describe "File#size for an empty file" do
+ before :each do
+ @name = tmp('empty')
+ touch(@name)
+ @file = File.new @name
+ end
+
+ after :each do
+ @file.close unless @file.closed?
+ rm_r @name
+ end
+
+ it "returns 0" do
+ @file.size.should == 0
+ end
+end
diff --git a/spec/ruby/core/file/socket_spec.rb b/spec/ruby/core/file/socket_spec.rb
new file mode 100644
index 0000000000..5d12e21f55
--- /dev/null
+++ b/spec/ruby/core/file/socket_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/socket'
+require 'socket'
+
+describe "File.socket?" do
+ it_behaves_like :file_socket, :socket?, File
+end
+
+describe "File.socket?" do
+ it "returns false if file does not exist" do
+ File.socket?("I_am_a_bogus_file").should == false
+ end
+
+ it "returns false if the file is not a socket" do
+ filename = tmp("i_exist")
+ touch(filename)
+
+ File.socket?(filename).should == false
+
+ rm_r filename
+ end
+end
+
+platform_is_not :windows do
+ describe "File.socket?" do
+ before :each do
+ # We need a really short name here.
+ # On Linux the path length is limited to 107, see unix(7).
+ @name = tmp("s")
+ @server = UNIXServer.new @name
+ end
+
+ after :each do
+ @server.close
+ rm_r @name
+ end
+
+ it "returns true if the file is a socket" do
+ File.socket?(@name).should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/file/split_spec.rb b/spec/ruby/core/file/split_spec.rb
new file mode 100644
index 0000000000..7b958621b9
--- /dev/null
+++ b/spec/ruby/core/file/split_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+
+describe "File.split" do
+ before :each do
+ @backslash_ext = "C:\\foo\\bar\\baz.rb"
+ @backslash = "C:\\foo\\bar\\baz"
+ end
+
+ it "splits the string at the last '/' when the last component does not have an extension" do
+ File.split("/foo/bar/baz").should == ["/foo/bar", "baz"]
+ File.split("C:/foo/bar/baz").should == ["C:/foo/bar", "baz"]
+ end
+
+ it "splits the string at the last '/' when the last component has an extension" do
+ File.split("/foo/bar/baz.rb").should == ["/foo/bar", "baz.rb"]
+ File.split("C:/foo/bar/baz.rb").should == ["C:/foo/bar", "baz.rb"]
+ end
+
+ it "splits an empty string into a '.' and an empty string" do
+ File.split("").should == [".", ""]
+ end
+
+ platform_is_not :windows do
+ it "collapses multiple '/' characters and strips trailing ones" do
+ File.split("//foo////").should == ["/", "foo"]
+ end
+ end
+
+ platform_is_not :windows do
+ it "does not split a string that contains '\\'" do
+ File.split(@backslash).should == [".", "C:\\foo\\bar\\baz"]
+ File.split(@backslash_ext).should == [".", "C:\\foo\\bar\\baz.rb"]
+ end
+ end
+
+ platform_is :windows do
+ it "splits the string at the last '\\' when the last component does not have an extension" do
+ File.split(@backslash).should == ["C:\\foo\\bar", "baz"]
+ end
+
+ it "splits the string at the last '\\' when the last component has an extension" do
+ File.split(@backslash_ext).should == ["C:\\foo\\bar", "baz.rb"]
+ end
+ end
+
+ it "raises an ArgumentError when not passed a single argument" do
+ -> { File.split }.should raise_error(ArgumentError)
+ -> { File.split('string', 'another string') }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if the argument is not a String type" do
+ -> { File.split(1) }.should raise_error(TypeError)
+ end
+
+ it "coerces the argument with to_str if it is not a String type" do
+ obj = mock("str")
+ obj.should_receive(:to_str).and_return("/one/two/three")
+ File.split(obj).should == ["/one/two", "three"]
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.split(mock_to_path("")).should == [".", ""]
+ end
+end
diff --git a/spec/ruby/core/file/stat/atime_spec.rb b/spec/ruby/core/file/stat/atime_spec.rb
new file mode 100644
index 0000000000..9f1111ced1
--- /dev/null
+++ b/spec/ruby/core/file/stat/atime_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#atime" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the atime of a File::Stat object" do
+ st = File.stat(@file)
+ st.atime.should be_kind_of(Time)
+ st.atime.should <= Time.now
+ end
+end
diff --git a/spec/ruby/core/file/stat/birthtime_spec.rb b/spec/ruby/core/file/stat/birthtime_spec.rb
new file mode 100644
index 0000000000..a727bbe566
--- /dev/null
+++ b/spec/ruby/core/file/stat/birthtime_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#birthtime" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ platform_is :windows, :darwin, :freebsd, :netbsd do
+ it "returns the birthtime of a File::Stat object" do
+ st = File.stat(@file)
+ st.birthtime.should be_kind_of(Time)
+ st.birthtime.should <= Time.now
+ end
+ end
+
+ platform_is :linux, :openbsd do
+ it "raises an NotImplementedError" do
+ st = File.stat(@file)
+ -> { st.birthtime }.should raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/blksize_spec.rb b/spec/ruby/core/file/stat/blksize_spec.rb
new file mode 100644
index 0000000000..4d85b05e4d
--- /dev/null
+++ b/spec/ruby/core/file/stat/blksize_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#blksize" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ platform_is_not :windows do
+ it "returns the blksize of a File::Stat object" do
+ st = File.stat(@file)
+ st.blksize.is_a?(Integer).should == true
+ st.blksize.should > 0
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ st = File.stat(@file)
+ st.blksize.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/blockdev_spec.rb b/spec/ruby/core/file/stat/blockdev_spec.rb
new file mode 100644
index 0000000000..f986c18125
--- /dev/null
+++ b/spec/ruby/core/file/stat/blockdev_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/blockdev'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#blockdev?" do
+ it_behaves_like :file_blockdev, :blockdev?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/blocks_spec.rb b/spec/ruby/core/file/stat/blocks_spec.rb
new file mode 100644
index 0000000000..f3f903d0f7
--- /dev/null
+++ b/spec/ruby/core/file/stat/blocks_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#blocks" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ platform_is_not :windows do
+ it "returns a non-negative integer" do
+ st = File.stat(@file)
+ st.blocks.is_a?(Integer).should == true
+ st.blocks.should >= 0
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ st = File.stat(@file)
+ st.blocks.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/chardev_spec.rb b/spec/ruby/core/file/stat/chardev_spec.rb
new file mode 100644
index 0000000000..622fb2052d
--- /dev/null
+++ b/spec/ruby/core/file/stat/chardev_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/chardev'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#chardev?" do
+ it_behaves_like :file_chardev, :chardev?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/comparison_spec.rb b/spec/ruby/core/file/stat/comparison_spec.rb
new file mode 100644
index 0000000000..faa3b6bf62
--- /dev/null
+++ b/spec/ruby/core/file/stat/comparison_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#<=>" do
+ before :each do
+ @name1 = tmp("i_exist")
+ @name2 = tmp("i_exist_too")
+ touch @name1
+ touch @name2
+ end
+
+ after :each do
+ rm_r @name1, @name2
+ end
+
+ it "is able to compare files by the same modification times" do
+ now = Time.now - 1 # 1 second ago to avoid NFS cache issue
+ File.utime(now, now, @name1)
+ File.utime(now, now, @name2)
+
+ File.open(@name1) { |file1|
+ File.open(@name2) { |file2|
+ (file1.stat <=> file2.stat).should == 0
+ }
+ }
+ end
+
+ it "is able to compare files by different modification times" do
+ now = Time.now
+ File.utime(now, now + 100, @name2)
+
+ File.open(@name1) { |file1|
+ File.open(@name2) { |file2|
+ (file1.stat <=> file2.stat).should == -1
+ }
+ }
+
+ File.utime(now, now - 100, @name2)
+
+ File.open(@name1) { |file1|
+ File.open(@name2) { |file2|
+ (file1.stat <=> file2.stat).should == 1
+ }
+ }
+ end
+
+ # TODO: Fix
+ it "includes Comparable and #== shows mtime equality between two File::Stat objects" do
+ File.open(@name1) { |file1|
+ File.open(@name2) { |file2|
+ (file1.stat == file1.stat).should == true
+ (file2.stat == file2.stat).should == true
+ }
+ }
+
+ now = Time.now
+ File.utime(now, now + 100, @name2)
+
+ File.open(@name1) { |file1|
+ File.open(@name2) { |file2|
+ (file1.stat == file2.stat).should == false
+ (file1.stat == file1.stat).should == true
+ (file2.stat == file2.stat).should == true
+ }
+ }
+ end
+end
diff --git a/spec/ruby/core/file/stat/ctime_spec.rb b/spec/ruby/core/file/stat/ctime_spec.rb
new file mode 100644
index 0000000000..fd50487a0a
--- /dev/null
+++ b/spec/ruby/core/file/stat/ctime_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#ctime" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the ctime of a File::Stat object" do
+ st = File.stat(@file)
+ st.ctime.should be_kind_of(Time)
+ st.ctime.should <= Time.now
+ end
+end
diff --git a/spec/ruby/core/file/stat/dev_major_spec.rb b/spec/ruby/core/file/stat/dev_major_spec.rb
new file mode 100644
index 0000000000..4966d609e2
--- /dev/null
+++ b/spec/ruby/core/file/stat/dev_major_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#dev_major" do
+ before :each do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+ after :each do
+ rm_r @name
+ end
+
+ platform_is_not :windows do
+ it "returns the major part of File::Stat#dev" do
+ File.stat(@name).dev_major.should be_kind_of(Integer)
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ File.stat(@name).dev_major.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/dev_minor_spec.rb b/spec/ruby/core/file/stat/dev_minor_spec.rb
new file mode 100644
index 0000000000..ea79c12b99
--- /dev/null
+++ b/spec/ruby/core/file/stat/dev_minor_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#dev_minor" do
+ before :each do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+ after :each do
+ rm_r @name
+ end
+
+ platform_is_not :windows do
+ it "returns the minor part of File::Stat#dev" do
+ File.stat(@name).dev_minor.should be_kind_of(Integer)
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ File.stat(@name).dev_minor.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/dev_spec.rb b/spec/ruby/core/file/stat/dev_spec.rb
new file mode 100644
index 0000000000..e953fcaa58
--- /dev/null
+++ b/spec/ruby/core/file/stat/dev_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#dev" do
+ before :each do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+ after :each do
+ rm_r @name
+ end
+
+ it "returns the number of the device on which the file exists" do
+ File.stat(@name).dev.should be_kind_of(Integer)
+ end
+end
diff --git a/spec/ruby/core/file/stat/directory_spec.rb b/spec/ruby/core/file/stat/directory_spec.rb
new file mode 100644
index 0000000000..c03610388b
--- /dev/null
+++ b/spec/ruby/core/file/stat/directory_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/directory'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#directory?" do
+ it_behaves_like :file_directory, :directory?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/executable_real_spec.rb b/spec/ruby/core/file/stat/executable_real_spec.rb
new file mode 100644
index 0000000000..23bffe89c5
--- /dev/null
+++ b/spec/ruby/core/file/stat/executable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/executable_real'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#executable_real?" do
+ it_behaves_like :file_executable_real, :executable_real?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/executable_spec.rb b/spec/ruby/core/file/stat/executable_spec.rb
new file mode 100644
index 0000000000..422975d14b
--- /dev/null
+++ b/spec/ruby/core/file/stat/executable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/executable'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#executable?" do
+ it_behaves_like :file_executable, :executable?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/file_spec.rb b/spec/ruby/core/file/stat/file_spec.rb
new file mode 100644
index 0000000000..d141536b4b
--- /dev/null
+++ b/spec/ruby/core/file/stat/file_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/file'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#file?" do
+ it_behaves_like :file_file, :file?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/fixtures/classes.rb b/spec/ruby/core/file/stat/fixtures/classes.rb
new file mode 100644
index 0000000000..4fe9a2a30f
--- /dev/null
+++ b/spec/ruby/core/file/stat/fixtures/classes.rb
@@ -0,0 +1,5 @@
+class FileStat
+ def self.method_missing(meth, file)
+ File.lstat(file).send(meth)
+ end
+end
diff --git a/spec/ruby/core/file/stat/ftype_spec.rb b/spec/ruby/core/file/stat/ftype_spec.rb
new file mode 100644
index 0000000000..eb892eae5f
--- /dev/null
+++ b/spec/ruby/core/file/stat/ftype_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/file_types'
+
+describe "File::Stat#ftype" do
+ before :all do
+ FileSpecs.configure_types
+ end
+
+ it "returns a String" do
+ FileSpecs.normal_file do |file|
+ File.lstat(file).ftype.should be_kind_of(String)
+ end
+ end
+
+ it "returns 'file' when the file is a file" do
+ FileSpecs.normal_file do |file|
+ File.lstat(file).ftype.should == 'file'
+ end
+ end
+
+ it "returns 'directory' when the file is a dir" do
+ FileSpecs.directory do |dir|
+ File.lstat(dir).ftype.should == 'directory'
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns 'characterSpecial' when the file is a char" do
+ FileSpecs.character_device do |char|
+ File.lstat(char).ftype.should == 'characterSpecial'
+ end
+ end
+ end
+
+ platform_is_not :freebsd do # FreeBSD does not have block devices
+ with_block_device do
+ it "returns 'blockSpecial' when the file is a block" do
+ FileSpecs.block_device do |block|
+ File.lstat(block).ftype.should == 'blockSpecial'
+ end
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns 'link' when the file is a link" do
+ FileSpecs.symlink do |link|
+ File.lstat(link).ftype.should == 'link'
+ end
+ end
+
+ it "returns fifo when the file is a fifo" do
+ FileSpecs.fifo do |fifo|
+ File.lstat(fifo).ftype.should == 'fifo'
+ end
+ end
+
+ it "returns 'socket' when the file is a socket" do
+ FileSpecs.socket do |socket|
+ File.lstat(socket).ftype.should == 'socket'
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/gid_spec.rb b/spec/ruby/core/file/stat/gid_spec.rb
new file mode 100644
index 0000000000..3bba65bc82
--- /dev/null
+++ b/spec/ruby/core/file/stat/gid_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#gid" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ File.chown(nil, Process.gid, @file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the group owner attribute of a File::Stat object" do
+ st = File.stat(@file)
+ st.gid.is_a?(Integer).should == true
+ st.gid.should == Process.gid
+ end
+end
diff --git a/spec/ruby/core/file/stat/grpowned_spec.rb b/spec/ruby/core/file/stat/grpowned_spec.rb
new file mode 100644
index 0000000000..e7278e229b
--- /dev/null
+++ b/spec/ruby/core/file/stat/grpowned_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/grpowned'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#grpowned?" do
+ it_behaves_like :file_grpowned, :grpowned?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/ino_spec.rb b/spec/ruby/core/file/stat/ino_spec.rb
new file mode 100644
index 0000000000..42370aecb7
--- /dev/null
+++ b/spec/ruby/core/file/stat/ino_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#ino" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ platform_is_not :windows do
+ it "returns the ino of a File::Stat object" do
+ st = File.stat(@file)
+ st.ino.should be_kind_of(Integer)
+ st.ino.should > 0
+ end
+ end
+
+ platform_is :windows do
+ it "returns BY_HANDLE_FILE_INFORMATION.nFileIndexHigh/Low of a File::Stat object" do
+ st = File.stat(@file)
+ st.ino.should be_kind_of(Integer)
+ st.ino.should > 0
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/inspect_spec.rb b/spec/ruby/core/file/stat/inspect_spec.rb
new file mode 100644
index 0000000000..1613b427d0
--- /dev/null
+++ b/spec/ruby/core/file/stat/inspect_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#inspect" do
+
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "produces a nicely formatted description of a File::Stat object" do
+ st = File.stat(@file)
+ expected = "#<File::Stat dev=0x#{st.dev.to_s(16)}, ino=#{st.ino}, mode=#{sprintf("%07o", st.mode)}, nlink=#{st.nlink}"
+ expected << ", uid=#{st.uid}, gid=#{st.gid}, rdev=0x#{st.rdev.to_s(16)}, size=#{st.size}, blksize=#{st.blksize.inspect}"
+ expected << ", blocks=#{st.blocks.inspect}, atime=#{st.atime.inspect}, mtime=#{st.mtime.inspect}, ctime=#{st.ctime.inspect}"
+ platform_is :netbsd, :freebsd, :darwin do
+ # Windows has File.birthtime but it's not here since already shown by ctime.
+ expected << ", birthtime=#{st.birthtime.inspect}"
+ end
+ expected << ">"
+ st.inspect.should == expected
+ end
+end
diff --git a/spec/ruby/core/file/stat/mode_spec.rb b/spec/ruby/core/file/stat/mode_spec.rb
new file mode 100644
index 0000000000..c85fb85a58
--- /dev/null
+++ b/spec/ruby/core/file/stat/mode_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#mode" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ File.chmod(0644, @file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the mode of a File::Stat object" do
+ st = File.stat(@file)
+ st.mode.is_a?(Integer).should == true
+ (st.mode & 0777).should == 0644
+ end
+end
diff --git a/spec/ruby/core/file/stat/mtime_spec.rb b/spec/ruby/core/file/stat/mtime_spec.rb
new file mode 100644
index 0000000000..08a2b83463
--- /dev/null
+++ b/spec/ruby/core/file/stat/mtime_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#mtime" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the mtime of a File::Stat object" do
+ st = File.stat(@file)
+ st.mtime.should be_kind_of(Time)
+ st.mtime.should <= Time.now
+ end
+end
diff --git a/spec/ruby/core/file/stat/new_spec.rb b/spec/ruby/core/file/stat/new_spec.rb
new file mode 100644
index 0000000000..c0d9432ac8
--- /dev/null
+++ b/spec/ruby/core/file/stat/new_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#initialize" do
+
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ File.chmod(0755, @file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "raises an exception if the file doesn't exist" do
+ -> {
+ File::Stat.new(tmp("i_am_a_dummy_file_that_doesnt_exist"))
+ }.should raise_error(Errno::ENOENT)
+ end
+
+ it "creates a File::Stat object for the given file" do
+ st = File::Stat.new(@file)
+ st.should be_kind_of(File::Stat)
+ st.ftype.should == 'file'
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return @file
+ File::Stat.new p
+ end
+end
diff --git a/spec/ruby/core/file/stat/nlink_spec.rb b/spec/ruby/core/file/stat/nlink_spec.rb
new file mode 100644
index 0000000000..7143923cfc
--- /dev/null
+++ b/spec/ruby/core/file/stat/nlink_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#nlink" do
+ before :each do
+ @file = tmp("stat_nlink")
+ @link = @file + ".lnk"
+ touch @file
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ platform_is_not :windows, :android do
+ it "returns the number of links to a file" do
+ File::Stat.new(@file).nlink.should == 1
+ File.link(@file, @link)
+ File::Stat.new(@file).nlink.should == 2
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/owned_spec.rb b/spec/ruby/core/file/stat/owned_spec.rb
new file mode 100644
index 0000000000..a23ad850c5
--- /dev/null
+++ b/spec/ruby/core/file/stat/owned_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/owned'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#owned?" do
+ it_behaves_like :file_owned, :owned?, FileStat
+end
+
+describe "File::Stat#owned?" do
+ before :each do
+ @file = tmp("i_exist")
+ touch(@file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns true if the file is owned by the user" do
+ st = File.stat(@file)
+ st.should.owned?
+ end
+
+ platform_is_not :windows, :android do
+ as_user do
+ it "returns false if the file is not owned by the user" do
+ system_file = '/etc/passwd'
+ st = File.stat(system_file)
+ st.should_not.owned?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/pipe_spec.rb b/spec/ruby/core/file/stat/pipe_spec.rb
new file mode 100644
index 0000000000..692dfbf42a
--- /dev/null
+++ b/spec/ruby/core/file/stat/pipe_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/pipe'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#pipe?" do
+ it_behaves_like :file_pipe, :pipe?, FileStat
+end
+
+describe "File::Stat#pipe?" do
+ it "returns false if the file is not a pipe" do
+ filename = tmp("i_exist")
+ touch(filename)
+
+ st = File.stat(filename)
+ st.should_not.pipe?
+
+ rm_r filename
+ end
+
+ platform_is_not :windows do
+ it "returns true if the file is a pipe" do
+ filename = tmp("i_am_a_pipe")
+ File.mkfifo(filename)
+
+ st = File.stat(filename)
+ st.should.pipe?
+
+ rm_r filename
+ end
+ end
+
+end
diff --git a/spec/ruby/core/file/stat/rdev_major_spec.rb b/spec/ruby/core/file/stat/rdev_major_spec.rb
new file mode 100644
index 0000000000..f8a8d1b107
--- /dev/null
+++ b/spec/ruby/core/file/stat/rdev_major_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#rdev_major" do
+ before :each do
+ platform_is :solaris do
+ @name = "/dev/zfs"
+ end
+ platform_is_not :solaris do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+ end
+
+ after :each do
+ platform_is_not :solaris do
+ rm_r @name
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns the major part of File::Stat#rdev" do
+ File.stat(@name).rdev_major.should be_kind_of(Integer)
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ File.stat(@name).rdev_major.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/rdev_minor_spec.rb b/spec/ruby/core/file/stat/rdev_minor_spec.rb
new file mode 100644
index 0000000000..dc30c1f56c
--- /dev/null
+++ b/spec/ruby/core/file/stat/rdev_minor_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#rdev_minor" do
+ before :each do
+ platform_is :solaris do
+ @name = "/dev/zfs"
+ end
+ platform_is_not :solaris do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+ end
+
+ after :each do
+ platform_is_not :solaris do
+ rm_r @name
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns the minor part of File::Stat#rdev" do
+ File.stat(@name).rdev_minor.should be_kind_of(Integer)
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ File.stat(@name).rdev_minor.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/rdev_spec.rb b/spec/ruby/core/file/stat/rdev_spec.rb
new file mode 100644
index 0000000000..9e1aee692d
--- /dev/null
+++ b/spec/ruby/core/file/stat/rdev_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#rdev" do
+ before :each do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+ after :each do
+ rm_r @name
+ end
+
+ it "returns the number of the device this file represents which the file exists" do
+ File.stat(@name).rdev.should be_kind_of(Integer)
+ end
+end
diff --git a/spec/ruby/core/file/stat/readable_real_spec.rb b/spec/ruby/core/file/stat/readable_real_spec.rb
new file mode 100644
index 0000000000..f138fd7b00
--- /dev/null
+++ b/spec/ruby/core/file/stat/readable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/readable_real'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#readable_real?" do
+ it_behaves_like :file_readable_real, :readable_real?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/readable_spec.rb b/spec/ruby/core/file/stat/readable_spec.rb
new file mode 100644
index 0000000000..e99e48feed
--- /dev/null
+++ b/spec/ruby/core/file/stat/readable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/readable'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#readable?" do
+ it_behaves_like :file_readable, :readable?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/setgid_spec.rb b/spec/ruby/core/file/stat/setgid_spec.rb
new file mode 100644
index 0000000000..c0748ede57
--- /dev/null
+++ b/spec/ruby/core/file/stat/setgid_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/setgid'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#setgid?" do
+ it_behaves_like :file_setgid, :setgid?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/setuid_spec.rb b/spec/ruby/core/file/stat/setuid_spec.rb
new file mode 100644
index 0000000000..6408120fc4
--- /dev/null
+++ b/spec/ruby/core/file/stat/setuid_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/setuid'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#setuid?" do
+ it_behaves_like :file_setuid, :setuid?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/size_spec.rb b/spec/ruby/core/file/stat/size_spec.rb
new file mode 100644
index 0000000000..4b4f57f8c8
--- /dev/null
+++ b/spec/ruby/core/file/stat/size_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/size'
+require_relative 'fixtures/classes'
+
+describe "File::Stat.size?" do
+ it_behaves_like :file_size, :size?, FileStat
+ it_behaves_like :file_size_nil_when_empty, :size?, FileStat
+end
+
+describe "File::Stat.size" do
+ it_behaves_like :file_size, :size, FileStat
+ it_behaves_like :file_size_0_when_empty, :size, FileStat
+end
+
+describe "File::Stat#size" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "File::Stat#size?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/file/stat/socket_spec.rb b/spec/ruby/core/file/stat/socket_spec.rb
new file mode 100644
index 0000000000..09740be110
--- /dev/null
+++ b/spec/ruby/core/file/stat/socket_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/socket'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#socket?" do
+ it_behaves_like :file_socket, :socket?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/sticky_spec.rb b/spec/ruby/core/file/stat/sticky_spec.rb
new file mode 100644
index 0000000000..7083e644e9
--- /dev/null
+++ b/spec/ruby/core/file/stat/sticky_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/sticky'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#sticky?" do
+ it_behaves_like :file_sticky, :sticky?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/symlink_spec.rb b/spec/ruby/core/file/stat/symlink_spec.rb
new file mode 100644
index 0000000000..0def832a4c
--- /dev/null
+++ b/spec/ruby/core/file/stat/symlink_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/symlink'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#symlink?" do
+ it_behaves_like :file_symlink, :symlink?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/uid_spec.rb b/spec/ruby/core/file/stat/uid_spec.rb
new file mode 100644
index 0000000000..b97147db21
--- /dev/null
+++ b/spec/ruby/core/file/stat/uid_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#uid" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the owner attribute of a File::Stat object" do
+ st = File.stat(@file)
+ st.uid.is_a?(Integer).should == true
+ st.uid.should == Process.uid
+ end
+end
diff --git a/spec/ruby/core/file/stat/world_readable_spec.rb b/spec/ruby/core/file/stat/world_readable_spec.rb
new file mode 100644
index 0000000000..d94a02205e
--- /dev/null
+++ b/spec/ruby/core/file/stat/world_readable_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/world_readable'
+require_relative 'fixtures/classes'
+
+describe "File::Stat.world_readable?" do
+ it_behaves_like :file_world_readable, :world_readable?, FileStat
+end
+
+describe "File::Stat#world_readable?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/file/stat/world_writable_spec.rb b/spec/ruby/core/file/stat/world_writable_spec.rb
new file mode 100644
index 0000000000..8100008344
--- /dev/null
+++ b/spec/ruby/core/file/stat/world_writable_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/world_writable'
+require_relative 'fixtures/classes'
+
+describe "File::Stat.world_writable?" do
+ it_behaves_like :file_world_writable, :world_writable?, FileStat
+end
+
+describe "File::Stat#world_writable?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/file/stat/writable_real_spec.rb b/spec/ruby/core/file/stat/writable_real_spec.rb
new file mode 100644
index 0000000000..4c9e78eb70
--- /dev/null
+++ b/spec/ruby/core/file/stat/writable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/writable_real'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#writable_real?" do
+ it_behaves_like :file_writable_real, :writable_real?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/writable_spec.rb b/spec/ruby/core/file/stat/writable_spec.rb
new file mode 100644
index 0000000000..551268751f
--- /dev/null
+++ b/spec/ruby/core/file/stat/writable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/writable'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#writable?" do
+ it_behaves_like :file_writable, :writable?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/zero_spec.rb b/spec/ruby/core/file/stat/zero_spec.rb
new file mode 100644
index 0000000000..74facac66a
--- /dev/null
+++ b/spec/ruby/core/file/stat/zero_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/zero'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#zero?" do
+ it_behaves_like :file_zero, :zero?, FileStat
+end
diff --git a/spec/ruby/core/file/stat_spec.rb b/spec/ruby/core/file/stat_spec.rb
new file mode 100644
index 0000000000..6365500057
--- /dev/null
+++ b/spec/ruby/core/file/stat_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+require_relative 'shared/stat'
+
+describe "File.stat" do
+ it_behaves_like :file_stat, :stat
+end
+
+platform_is_not :windows do
+ describe "File.stat" do
+ before :each do
+ @file = tmp('i_exist')
+ @link = tmp('i_am_a_symlink')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ it "returns information for a file that has been deleted but is still open" do
+ File.open(@file) do |f|
+ rm_r @file
+
+ st = f.stat
+
+ st.should.file?
+ st.should_not.zero?
+ st.size.should == 8
+ st.size?.should == 8
+ st.blksize.should >= 0
+ st.atime.should be_kind_of(Time)
+ st.ctime.should be_kind_of(Time)
+ st.mtime.should be_kind_of(Time)
+ end
+ end
+
+ it "returns a File::Stat object with file properties for a symlink" do
+ File.symlink(@file, @link)
+ st = File.stat(@link)
+
+ st.should.file?
+ st.should_not.symlink?
+ end
+
+ it "returns an error when given missing non-ASCII path" do
+ missing_path = "/missingfilepath\xE3E4".b
+ -> {
+ File.stat(missing_path)
+ }.should raise_error(SystemCallError) { |e|
+ [Errno::ENOENT, Errno::EILSEQ].should include(e.class)
+ e.message.should include(missing_path)
+ }
+ end
+ end
+end
diff --git a/spec/ruby/core/file/sticky_spec.rb b/spec/ruby/core/file/sticky_spec.rb
new file mode 100644
index 0000000000..5f7b2d93eb
--- /dev/null
+++ b/spec/ruby/core/file/sticky_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/sticky'
+
+describe "File.sticky?" do
+ it_behaves_like :file_sticky, :sticky?, File
+ it_behaves_like :file_sticky_missing, :sticky?, File
+end
+
+describe "File.sticky?" do
+ platform_is_not :windows do
+ it "returns false if file does not exist" do
+ File.sticky?("I_am_a_bogus_file").should == false
+ end
+
+ it "returns false if the file has not sticky bit set" do
+ filename = tmp("i_exist")
+ touch(filename)
+
+ File.sticky?(filename).should == false
+
+ rm_r filename
+ end
+ end
+
+ platform_is :linux, :darwin do
+ it "returns true if the file has sticky bit set" do
+ filename = tmp("i_exist")
+ touch(filename)
+ system "chmod +t #{filename}"
+
+ File.sticky?(filename).should == true
+
+ rm_r filename
+ end
+ end
+
+ platform_is :bsd do
+ # FreeBSD and NetBSD can't set sticky bit to a normal file
+ it "cannot set sticky bit to a normal file" do
+ filename = tmp("i_exist")
+ touch(filename)
+ stat = File.stat(filename)
+ mode = stat.mode
+ raise_error(Errno::EFTYPE){File.chmod(mode|01000, filename)}
+ File.sticky?(filename).should == false
+
+ rm_r filename
+ end
+ end
+end
diff --git a/spec/ruby/core/file/symlink_spec.rb b/spec/ruby/core/file/symlink_spec.rb
new file mode 100644
index 0000000000..0e8b0a5a20
--- /dev/null
+++ b/spec/ruby/core/file/symlink_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/symlink'
+
+describe "File.symlink" do
+ before :each do
+ @file = tmp("file_symlink.txt")
+ @link = tmp("file_symlink.lnk")
+
+ rm_r @link
+ touch @file
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ platform_is_not :windows do
+ it "creates a symlink between a source and target file" do
+ File.symlink(@file, @link).should == 0
+ File.identical?(@file, @link).should == true
+ end
+
+ it "creates a symbolic link" do
+ File.symlink(@file, @link)
+ File.symlink?(@link).should == true
+ end
+
+ it "accepts args that have #to_path methods" do
+ File.symlink(mock_to_path(@file), mock_to_path(@link))
+ File.symlink?(@link).should == true
+ end
+
+ it "raises an Errno::EEXIST if the target already exists" do
+ File.symlink(@file, @link)
+ -> { File.symlink(@file, @link) }.should raise_error(Errno::EEXIST)
+ end
+
+ it "raises an ArgumentError if not called with two arguments" do
+ -> { File.symlink }.should raise_error(ArgumentError)
+ -> { File.symlink(@file) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not called with String types" do
+ -> { File.symlink(@file, nil) }.should raise_error(TypeError)
+ -> { File.symlink(@file, 1) }.should raise_error(TypeError)
+ -> { File.symlink(1, 1) }.should raise_error(TypeError)
+ end
+ end
+end
+
+describe "File.symlink?" do
+ it_behaves_like :file_symlink, :symlink?, File
+end
+
+describe "File.symlink?" do
+ it_behaves_like :file_symlink_nonexistent, :symlink?, File
+end
diff --git a/spec/ruby/core/file/to_path_spec.rb b/spec/ruby/core/file/to_path_spec.rb
new file mode 100644
index 0000000000..6d168a065c
--- /dev/null
+++ b/spec/ruby/core/file/to_path_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/path'
+
+describe "File#to_path" do
+ it_behaves_like :file_path, :to_path
+end
diff --git a/spec/ruby/core/file/truncate_spec.rb b/spec/ruby/core/file/truncate_spec.rb
new file mode 100644
index 0000000000..b4a2e3e577
--- /dev/null
+++ b/spec/ruby/core/file/truncate_spec.rb
@@ -0,0 +1,177 @@
+require_relative '../../spec_helper'
+
+describe "File.truncate" do
+ before :each do
+ @name = tmp("test.txt")
+ touch(@name) { |f| f.write("1234567890") }
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "truncates a file" do
+ File.size(@name).should == 10
+
+ File.truncate(@name, 5)
+ File.size(@name).should == 5
+
+ File.open(@name, "r") do |f|
+ f.read(99).should == "12345"
+ f.should.eof?
+ end
+ end
+
+ it "truncate a file size to 0" do
+ File.truncate(@name, 0).should == 0
+ IO.read(@name).should == ""
+ end
+
+ it "truncate a file size to 5" do
+ File.size(@name).should == 10
+ File.truncate(@name, 5)
+ File.size(@name).should == 5
+ IO.read(@name).should == "12345"
+ end
+
+ it "truncates to a larger file size than the original file" do
+ File.truncate(@name, 12)
+ File.size(@name).should == 12
+ IO.read(@name).should == "1234567890\000\000"
+ end
+
+ it "truncates to the same size as the original file" do
+ File.truncate(@name, File.size(@name))
+ File.size(@name).should == 10
+ IO.read(@name).should == "1234567890"
+ end
+
+ it "raises an Errno::ENOENT if the file does not exist" do
+ # TODO: missing_file
+ not_existing_file = tmp("file-does-not-exist-for-sure.txt")
+
+ # make sure it doesn't exist for real
+ rm_r not_existing_file
+
+ begin
+ -> { File.truncate(not_existing_file, 5) }.should raise_error(Errno::ENOENT)
+ ensure
+ rm_r not_existing_file
+ end
+ end
+
+ it "raises an ArgumentError if not passed two arguments" do
+ -> { File.truncate }.should raise_error(ArgumentError)
+ -> { File.truncate(@name) }.should raise_error(ArgumentError)
+ end
+
+ platform_is_not :netbsd, :openbsd do
+ it "raises an Errno::EINVAL if the length argument is not valid" do
+ -> { File.truncate(@name, -1) }.should raise_error(Errno::EINVAL) # May fail
+ end
+ end
+
+ it "raises a TypeError if not passed a String type for the first argument" do
+ -> { File.truncate(1, 1) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if not passed an Integer type for the second argument" do
+ -> { File.truncate(@name, nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.truncate(mock_to_path(@name), 0).should == 0
+ end
+end
+
+
+describe "File#truncate" do
+ before :each do
+ @name = tmp("test.txt")
+ @file = File.open @name, 'w'
+ @file.write "1234567890"
+ @file.flush
+ end
+
+ after :each do
+ @file.close unless @file.closed?
+ rm_r @name
+ end
+
+ it "does not move the file write pointer to the specified byte offset" do
+ @file.truncate(3)
+ @file.write "abc"
+ @file.close
+ File.read(@name).should == "123\x00\x00\x00\x00\x00\x00\x00abc"
+ end
+
+ it "does not move the file read pointer to the specified byte offset" do
+ File.open(@name, "r+") do |f|
+ f.read(1).should == "1"
+ f.truncate(0)
+ f.read(1).should == nil
+ end
+ end
+
+ it "truncates a file" do
+ File.size(@name).should == 10
+
+ @file.truncate(5)
+ File.size(@name).should == 5
+ File.open(@name, "r") do |f|
+ f.read(99).should == "12345"
+ f.should.eof?
+ end
+ end
+
+ it "truncates a file size to 0" do
+ @file.truncate(0).should == 0
+ IO.read(@name).should == ""
+ end
+
+ it "truncates a file size to 5" do
+ File.size(@name).should == 10
+ @file.truncate(5)
+ File.size(@name).should == 5
+ IO.read(@name).should == "12345"
+ end
+
+ it "truncates a file to a larger size than the original file" do
+ @file.truncate(12)
+ File.size(@name).should == 12
+ IO.read(@name).should == "1234567890\000\000"
+ end
+
+ it "truncates a file to the same size as the original file" do
+ @file.truncate(File.size(@name))
+ File.size(@name).should == 10
+ IO.read(@name).should == "1234567890"
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { @file.truncate }.should raise_error(ArgumentError)
+ -> { @file.truncate(1) }.should_not raise_error(ArgumentError)
+ end
+
+ platform_is_not :netbsd do
+ it "raises an Errno::EINVAL if the length argument is not valid" do
+ -> { @file.truncate(-1) }.should raise_error(Errno::EINVAL) # May fail
+ end
+ end
+
+ it "raises an IOError if file is closed" do
+ @file.close
+ @file.should.closed?
+ -> { @file.truncate(42) }.should raise_error(IOError)
+ end
+
+ it "raises an IOError if file is not opened for writing" do
+ File.open(@name, 'r') do |file|
+ -> { file.truncate(42) }.should raise_error(IOError)
+ end
+ end
+
+ it "raises a TypeError if not passed an Integer type for the for the argument" do
+ -> { @file.truncate(nil) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/file/umask_spec.rb b/spec/ruby/core/file/umask_spec.rb
new file mode 100644
index 0000000000..7f7e40abbc
--- /dev/null
+++ b/spec/ruby/core/file/umask_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+
+describe "File.umask" do
+ before :each do
+ @orig_umask = File.umask
+ @file = tmp('test.txt')
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ File.umask(@orig_umask)
+ end
+
+ it "returns an Integer" do
+ File.umask.should be_kind_of(Integer)
+ end
+
+ platform_is_not :windows do
+ it "returns the current umask value for the process" do
+ File.umask(022)
+ File.umask(006).should == 022
+ File.umask.should == 006
+ end
+
+ it "invokes to_int on non-integer argument" do
+ (obj = mock(022)).should_receive(:to_int).any_number_of_times.and_return(022)
+ File.umask(obj)
+ File.umask(obj).should == 022
+ end
+ end
+
+ platform_is :windows do
+ it "returns the current umask value for this process (basic)" do
+ File.umask.should == 0
+ File.umask(022).should == 0
+ File.umask(044).should == 0
+ end
+
+ # The value used here is the value of _S_IWRITE.
+ it "returns the current umask value for this process" do
+ File.umask(0000200)
+ File.umask.should == 0000200
+ File.umask(0006)
+ File.umask.should == 0
+ end
+ end
+
+ it "raises RangeError with too large values" do
+ -> { File.umask(2**64) }.should raise_error(RangeError)
+ -> { File.umask(-2**63 - 1) }.should raise_error(RangeError)
+ end
+
+ it "raises ArgumentError when more than one argument is provided" do
+ -> { File.umask(022, 022) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/file/unlink_spec.rb b/spec/ruby/core/file/unlink_spec.rb
new file mode 100644
index 0000000000..28872d55ed
--- /dev/null
+++ b/spec/ruby/core/file/unlink_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/unlink'
+
+describe "File.unlink" do
+ it_behaves_like :file_unlink, :unlink
+end
diff --git a/spec/ruby/core/file/utime_spec.rb b/spec/ruby/core/file/utime_spec.rb
new file mode 100644
index 0000000000..cf7a0aec20
--- /dev/null
+++ b/spec/ruby/core/file/utime_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+
+describe "File.utime" do
+
+ before :all do
+ @time_is_float = platform_is :windows
+ end
+
+ before :each do
+ @atime = Time.now
+ @mtime = Time.now
+ @file1 = tmp("specs_file_utime1")
+ @file2 = tmp("specs_file_utime2")
+ touch @file1
+ touch @file2
+ end
+
+ after :each do
+ rm_r @file1, @file2
+ end
+
+ platform_is_not :windows do
+ it "sets the access and modification time of each file" do
+ File.utime(@atime, @mtime, @file1, @file2)
+ if @time_is_float
+ File.atime(@file1).should be_close(@atime, 0.0001)
+ File.mtime(@file1).should be_close(@mtime, 0.0001)
+ File.atime(@file2).should be_close(@atime, 0.0001)
+ File.mtime(@file2).should be_close(@mtime, 0.0001)
+ else
+ File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
+ File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
+ File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
+ File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
+ end
+ end
+ end
+
+ it "uses the current times if two nil values are passed" do
+ tn = Time.now
+ File.utime(nil, nil, @file1, @file2)
+ if @time_is_float
+ File.atime(@file1).should be_close(tn, 0.050)
+ File.mtime(@file1).should be_close(tn, 0.050)
+ File.atime(@file2).should be_close(tn, 0.050)
+ File.mtime(@file2).should be_close(tn, 0.050)
+ else
+ File.atime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
+ File.mtime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
+ File.atime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
+ File.mtime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
+ end
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.utime(@atime, @mtime, mock_to_path(@file1), mock_to_path(@file2))
+ end
+
+ it "accepts numeric atime and mtime arguments" do
+ if @time_is_float
+ File.utime(@atime.to_f, @mtime.to_f, @file1, @file2)
+ File.atime(@file1).should be_close(@atime, 0.0001)
+ File.mtime(@file1).should be_close(@mtime, 0.0001)
+ File.atime(@file2).should be_close(@atime, 0.0001)
+ File.mtime(@file2).should be_close(@mtime, 0.0001)
+ else
+ File.utime(@atime.to_i, @mtime.to_i, @file1, @file2)
+ File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
+ File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
+ File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
+ File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
+ end
+ end
+
+ it "may set nanosecond precision" do
+ t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789r)
+ File.utime(t, t, @file1)
+ File.atime(@file1).nsec.should.between?(0, 123500000)
+ File.mtime(@file1).nsec.should.between?(0, 123500000)
+ end
+
+ it "returns the number of filenames in the arguments" do
+ File.utime(@atime.to_f, @mtime.to_f, @file1, @file2).should == 2
+ end
+
+ platform_is :linux do
+ platform_is wordsize: 64 do
+ it "allows Time instances in the far future to set mtime and atime (but some filesystems limit it up to 2446-05-10 or 2038-01-19 or 2486-07-02)" do
+ # https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Timestamps
+ # "Therefore, timestamps should not overflow until May 2446."
+ # https://lwn.net/Articles/804382/
+ # "On-disk timestamps hitting the y2038 limit..."
+ # The problem seems to be being improved, but currently it actually fails on XFS on RHEL8
+ # https://rubyci.org/logs/rubyci.s3.amazonaws.com/rhel8/ruby-master/log/20201112T123004Z.fail.html.gz
+ # Amazon Linux 2023 returns 2486-07-02 in this example
+ # http://rubyci.s3.amazonaws.com/amazon2023/ruby-master/log/20230322T063004Z.fail.html.gz
+ time = Time.at(1<<44)
+ File.utime(time, time, @file1)
+ [559444, 2486, 2446, 2038].should.include? File.atime(@file1).year
+ [559444, 2486, 2446, 2038].should.include? File.mtime(@file1).year
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/world_readable_spec.rb b/spec/ruby/core/file/world_readable_spec.rb
new file mode 100644
index 0000000000..11b8e67d0b
--- /dev/null
+++ b/spec/ruby/core/file/world_readable_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/world_readable'
+
+describe "File.world_readable?" do
+ it_behaves_like :file_world_readable, :world_readable?, File
+
+ it "returns nil if the file does not exist" do
+ file = rand.to_s + $$.to_s
+ File.should_not.exist?(file)
+ File.world_readable?(file).should be_nil
+ end
+end
diff --git a/spec/ruby/core/file/world_writable_spec.rb b/spec/ruby/core/file/world_writable_spec.rb
new file mode 100644
index 0000000000..d378cf2eb9
--- /dev/null
+++ b/spec/ruby/core/file/world_writable_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/world_writable'
+
+describe "File.world_writable?" do
+ it_behaves_like :file_world_writable, :world_writable?, File
+
+ it "returns nil if the file does not exist" do
+ file = rand.to_s + $$.to_s
+ File.should_not.exist?(file)
+ File.world_writable?(file).should be_nil
+ end
+end
diff --git a/spec/ruby/core/file/writable_real_spec.rb b/spec/ruby/core/file/writable_real_spec.rb
new file mode 100644
index 0000000000..bea4c4c262
--- /dev/null
+++ b/spec/ruby/core/file/writable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/writable_real'
+
+describe "File.writable_real?" do
+ it_behaves_like :file_writable_real, :writable_real?, File
+ it_behaves_like :file_writable_real_missing, :writable_real?, File
+end
diff --git a/spec/ruby/core/file/writable_spec.rb b/spec/ruby/core/file/writable_spec.rb
new file mode 100644
index 0000000000..519837b0d1
--- /dev/null
+++ b/spec/ruby/core/file/writable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/writable'
+
+describe "File.writable?" do
+ it_behaves_like :file_writable, :writable?, File
+ it_behaves_like :file_writable_missing, :writable?, File
+end
diff --git a/spec/ruby/core/file/zero_spec.rb b/spec/ruby/core/file/zero_spec.rb
new file mode 100644
index 0000000000..63dd85ee46
--- /dev/null
+++ b/spec/ruby/core/file/zero_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/zero'
+
+describe "File.zero?" do
+ it_behaves_like :file_zero, :zero?, File
+ it_behaves_like :file_zero_missing, :zero?, File
+
+ platform_is :solaris do
+ it "returns false for /dev/null" do
+ File.zero?('/dev/null').should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/filetest/blockdev_spec.rb b/spec/ruby/core/filetest/blockdev_spec.rb
new file mode 100644
index 0000000000..4f32991c4a
--- /dev/null
+++ b/spec/ruby/core/filetest/blockdev_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/blockdev'
+
+describe "FileTest.blockdev?" do
+ it_behaves_like :file_blockdev, :blockdev?, FileTest
+end
diff --git a/spec/ruby/core/filetest/chardev_spec.rb b/spec/ruby/core/filetest/chardev_spec.rb
new file mode 100644
index 0000000000..59c48bb2d5
--- /dev/null
+++ b/spec/ruby/core/filetest/chardev_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/chardev'
+
+describe "FileTest.chardev?" do
+ it_behaves_like :file_chardev, :chardev?, FileTest
+end
diff --git a/spec/ruby/core/filetest/directory_spec.rb b/spec/ruby/core/filetest/directory_spec.rb
new file mode 100644
index 0000000000..8f9d0e3901
--- /dev/null
+++ b/spec/ruby/core/filetest/directory_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/directory'
+
+describe "FileTest.directory?" do
+ it_behaves_like :file_directory, :directory?, FileTest
+end
+
+describe "FileTest.directory?" do
+ it_behaves_like :file_directory_io, :directory?, FileTest
+end
diff --git a/spec/ruby/core/filetest/executable_real_spec.rb b/spec/ruby/core/filetest/executable_real_spec.rb
new file mode 100644
index 0000000000..da65245785
--- /dev/null
+++ b/spec/ruby/core/filetest/executable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/executable_real'
+
+describe "FileTest.executable_real?" do
+ it_behaves_like :file_executable_real, :executable_real?, FileTest
+ it_behaves_like :file_executable_real_missing, :executable_real?, FileTest
+end
diff --git a/spec/ruby/core/filetest/executable_spec.rb b/spec/ruby/core/filetest/executable_spec.rb
new file mode 100644
index 0000000000..03056669f6
--- /dev/null
+++ b/spec/ruby/core/filetest/executable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/executable'
+
+describe "FileTest.executable?" do
+ it_behaves_like :file_executable, :executable?, FileTest
+ it_behaves_like :file_executable_missing, :executable?, FileTest
+end
diff --git a/spec/ruby/core/filetest/exist_spec.rb b/spec/ruby/core/filetest/exist_spec.rb
new file mode 100644
index 0000000000..4d14bea231
--- /dev/null
+++ b/spec/ruby/core/filetest/exist_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/exist'
+
+describe "FileTest.exist?" do
+ it_behaves_like :file_exist, :exist?, FileTest
+end
diff --git a/spec/ruby/core/filetest/file_spec.rb b/spec/ruby/core/filetest/file_spec.rb
new file mode 100644
index 0000000000..0c0cb82f96
--- /dev/null
+++ b/spec/ruby/core/filetest/file_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/file'
+
+describe "File.file?" do
+ it_behaves_like :file_file, :file?, File
+end
+
+describe "FileTest.file?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/filetest/grpowned_spec.rb b/spec/ruby/core/filetest/grpowned_spec.rb
new file mode 100644
index 0000000000..d073cb9770
--- /dev/null
+++ b/spec/ruby/core/filetest/grpowned_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/grpowned'
+
+describe "FileTest.grpowned?" do
+ it_behaves_like :file_grpowned, :grpowned?, FileTest
+
+ it "returns false if the file doesn't exist" do
+ FileTest.grpowned?("xxx-tmp-doesnt_exist-blah").should be_false
+ end
+end
diff --git a/spec/ruby/core/filetest/identical_spec.rb b/spec/ruby/core/filetest/identical_spec.rb
new file mode 100644
index 0000000000..b00c5b75e8
--- /dev/null
+++ b/spec/ruby/core/filetest/identical_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/identical'
+
+describe "FileTest.identical?" do
+ it_behaves_like :file_identical, :identical?, FileTest
+end
diff --git a/spec/ruby/core/filetest/owned_spec.rb b/spec/ruby/core/filetest/owned_spec.rb
new file mode 100644
index 0000000000..b26165f98d
--- /dev/null
+++ b/spec/ruby/core/filetest/owned_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/owned'
+
+describe "FileTest.owned?" do
+ it_behaves_like :file_owned, :owned?, FileTest
+end
diff --git a/spec/ruby/core/filetest/pipe_spec.rb b/spec/ruby/core/filetest/pipe_spec.rb
new file mode 100644
index 0000000000..8ce67568fb
--- /dev/null
+++ b/spec/ruby/core/filetest/pipe_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/pipe'
+
+describe "FileTest.pipe?" do
+ it_behaves_like :file_pipe, :pipe?, FileTest
+end
diff --git a/spec/ruby/core/filetest/readable_real_spec.rb b/spec/ruby/core/filetest/readable_real_spec.rb
new file mode 100644
index 0000000000..82c62fe8f0
--- /dev/null
+++ b/spec/ruby/core/filetest/readable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/readable_real'
+
+describe "FileTest.readable_real?" do
+ it_behaves_like :file_readable_real, :readable_real?, FileTest
+ it_behaves_like :file_readable_real_missing, :readable_real?, FileTest
+end
diff --git a/spec/ruby/core/filetest/readable_spec.rb b/spec/ruby/core/filetest/readable_spec.rb
new file mode 100644
index 0000000000..039ca56ca3
--- /dev/null
+++ b/spec/ruby/core/filetest/readable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/readable'
+
+describe "FileTest.readable?" do
+ it_behaves_like :file_readable, :readable?, FileTest
+ it_behaves_like :file_readable_missing, :readable?, FileTest
+end
diff --git a/spec/ruby/core/filetest/setgid_spec.rb b/spec/ruby/core/filetest/setgid_spec.rb
new file mode 100644
index 0000000000..c83ffccb2a
--- /dev/null
+++ b/spec/ruby/core/filetest/setgid_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/setgid'
+
+describe "FileTest.setgid?" do
+ it_behaves_like :file_setgid, :setgid?, FileTest
+end
diff --git a/spec/ruby/core/filetest/setuid_spec.rb b/spec/ruby/core/filetest/setuid_spec.rb
new file mode 100644
index 0000000000..1c1fd2efe4
--- /dev/null
+++ b/spec/ruby/core/filetest/setuid_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/setuid'
+
+describe "FileTest.setuid?" do
+ it_behaves_like :file_setuid, :setuid?, FileTest
+end
diff --git a/spec/ruby/core/filetest/size_spec.rb b/spec/ruby/core/filetest/size_spec.rb
new file mode 100644
index 0000000000..dc3ddb127f
--- /dev/null
+++ b/spec/ruby/core/filetest/size_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/size'
+
+describe "FileTest.size?" do
+ it_behaves_like :file_size, :size?, FileTest
+end
+
+describe "FileTest.size?" do
+ it_behaves_like :file_size_nil_when_missing, :size?, FileTest
+end
+
+describe "FileTest.size?" do
+ it_behaves_like :file_size_nil_when_empty, :size?, FileTest
+end
+
+describe "FileTest.size?" do
+ it_behaves_like :file_size_with_file_argument, :size?, FileTest
+end
+
+describe "FileTest.size" do
+ it_behaves_like :file_size, :size, FileTest
+end
+
+describe "FileTest.size" do
+ it_behaves_like :file_size_raise_when_missing, :size, FileTest
+end
+
+describe "FileTest.size" do
+ it_behaves_like :file_size_0_when_empty, :size, FileTest
+end
+
+describe "FileTest.size" do
+ it_behaves_like :file_size_with_file_argument, :size, FileTest
+end
diff --git a/spec/ruby/core/filetest/socket_spec.rb b/spec/ruby/core/filetest/socket_spec.rb
new file mode 100644
index 0000000000..63a6a31ecb
--- /dev/null
+++ b/spec/ruby/core/filetest/socket_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/socket'
+
+describe "FileTest.socket?" do
+ it_behaves_like :file_socket, :socket?, FileTest
+end
diff --git a/spec/ruby/core/filetest/sticky_spec.rb b/spec/ruby/core/filetest/sticky_spec.rb
new file mode 100644
index 0000000000..8b776b6672
--- /dev/null
+++ b/spec/ruby/core/filetest/sticky_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/sticky'
+
+describe "FileTest.sticky?" do
+ it_behaves_like :file_sticky, :sticky?, FileTest
+ it_behaves_like :file_sticky_missing, :sticky?, FileTest
+end
diff --git a/spec/ruby/core/filetest/symlink_spec.rb b/spec/ruby/core/filetest/symlink_spec.rb
new file mode 100644
index 0000000000..41c924dc1a
--- /dev/null
+++ b/spec/ruby/core/filetest/symlink_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/symlink'
+
+describe "FileTest.symlink?" do
+ it_behaves_like :file_symlink, :symlink?, FileTest
+end
+
+describe "FileTest.symlink?" do
+ it_behaves_like :file_symlink_nonexistent, :symlink?, File
+end
diff --git a/spec/ruby/core/filetest/world_readable_spec.rb b/spec/ruby/core/filetest/world_readable_spec.rb
new file mode 100644
index 0000000000..72abdd9e03
--- /dev/null
+++ b/spec/ruby/core/filetest/world_readable_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "FileTest.world_readable?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/filetest/world_writable_spec.rb b/spec/ruby/core/filetest/world_writable_spec.rb
new file mode 100644
index 0000000000..533f698fd3
--- /dev/null
+++ b/spec/ruby/core/filetest/world_writable_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "FileTest.world_writable?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/filetest/writable_real_spec.rb b/spec/ruby/core/filetest/writable_real_spec.rb
new file mode 100644
index 0000000000..64abe4cd3f
--- /dev/null
+++ b/spec/ruby/core/filetest/writable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/writable_real'
+
+describe "FileTest.writable_real?" do
+ it_behaves_like :file_writable_real, :writable_real?, FileTest
+ it_behaves_like :file_writable_real_missing, :writable_real?, FileTest
+end
diff --git a/spec/ruby/core/filetest/writable_spec.rb b/spec/ruby/core/filetest/writable_spec.rb
new file mode 100644
index 0000000000..e921a5887b
--- /dev/null
+++ b/spec/ruby/core/filetest/writable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/writable'
+
+describe "FileTest.writable?" do
+ it_behaves_like :file_writable, :writable?, FileTest
+ it_behaves_like :file_writable_missing, :writable?, FileTest
+end
diff --git a/spec/ruby/core/filetest/zero_spec.rb b/spec/ruby/core/filetest/zero_spec.rb
new file mode 100644
index 0000000000..dd6a164ec9
--- /dev/null
+++ b/spec/ruby/core/filetest/zero_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/zero'
+
+describe "FileTest.zero?" do
+ it_behaves_like :file_zero, :zero?, FileTest
+ it_behaves_like :file_zero_missing, :zero?, FileTest
+
+ platform_is :solaris do
+ it "returns false for /dev/null" do
+ File.zero?('/dev/null').should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/float/abs_spec.rb b/spec/ruby/core/float/abs_spec.rb
new file mode 100644
index 0000000000..a08601926d
--- /dev/null
+++ b/spec/ruby/core/float/abs_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/abs'
+
+describe "Float#abs" do
+ it_behaves_like :float_abs, :abs
+end
diff --git a/spec/ruby/core/float/angle_spec.rb b/spec/ruby/core/float/angle_spec.rb
new file mode 100644
index 0000000000..c07249aa97
--- /dev/null
+++ b/spec/ruby/core/float/angle_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Float#angle" do
+ it_behaves_like :float_arg, :angle
+end
diff --git a/spec/ruby/core/float/arg_spec.rb b/spec/ruby/core/float/arg_spec.rb
new file mode 100644
index 0000000000..d3a50668f8
--- /dev/null
+++ b/spec/ruby/core/float/arg_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Float#arg" do
+ it_behaves_like :float_arg, :arg
+end
diff --git a/spec/ruby/core/float/case_compare_spec.rb b/spec/ruby/core/float/case_compare_spec.rb
new file mode 100644
index 0000000000..b902fbea18
--- /dev/null
+++ b/spec/ruby/core/float/case_compare_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal'
+
+describe "Float#===" do
+ it_behaves_like :float_equal, :===
+end
diff --git a/spec/ruby/core/float/ceil_spec.rb b/spec/ruby/core/float/ceil_spec.rb
new file mode 100644
index 0000000000..7fc18d304c
--- /dev/null
+++ b/spec/ruby/core/float/ceil_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Float#ceil" do
+ it "returns the smallest Integer greater than or equal to self" do
+ -1.2.ceil.should eql( -1)
+ -1.0.ceil.should eql( -1)
+ 0.0.ceil.should eql( 0 )
+ 1.3.ceil.should eql( 2 )
+ 3.0.ceil.should eql( 3 )
+ -9223372036854775808.1.ceil.should eql(-9223372036854775808)
+ +9223372036854775808.1.ceil.should eql(+9223372036854775808)
+ end
+
+ it "returns the smallest number greater than or equal to self with an optionally given precision" do
+ 2.1679.ceil(0).should eql(3)
+ 214.94.ceil(-1).should eql(220)
+ 7.0.ceil(1).should eql(7.0)
+ -1.234.ceil(2).should eql(-1.23)
+ 5.123812.ceil(4).should eql(5.1239)
+ end
+end
diff --git a/spec/ruby/core/float/coerce_spec.rb b/spec/ruby/core/float/coerce_spec.rb
new file mode 100644
index 0000000000..baa831dcf6
--- /dev/null
+++ b/spec/ruby/core/float/coerce_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe "Float#coerce" do
+ it "returns [other, self] both as Floats" do
+ 1.2.coerce(1).should == [1.0, 1.2]
+ 5.28.coerce(1.0).should == [1.0, 5.28]
+ 1.0.coerce(1).should == [1.0, 1.0]
+ 1.0.coerce("2.5").should == [2.5, 1.0]
+ 1.0.coerce(3.14).should == [3.14, 1.0]
+
+ a, b = -0.0.coerce(bignum_value)
+ a.should be_close(18446744073709551616.0, TOLERANCE)
+ b.should be_close(-0.0, TOLERANCE)
+ a, b = 1.0.coerce(bignum_value)
+ a.should be_close(18446744073709551616.0, TOLERANCE)
+ b.should be_close(1.0, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/float/comparison_spec.rb b/spec/ruby/core/float/comparison_spec.rb
new file mode 100644
index 0000000000..1373b3a1fb
--- /dev/null
+++ b/spec/ruby/core/float/comparison_spec.rb
@@ -0,0 +1,113 @@
+require_relative '../../spec_helper'
+
+describe "Float#<=>" do
+ it "returns -1, 0, 1 when self is less than, equal, or greater than other" do
+ (1.5 <=> 5).should == -1
+ (2.45 <=> 2.45).should == 0
+ ((bignum_value*1.1) <=> bignum_value).should == 1
+ end
+
+ it "returns nil if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value <=> n).should == nil
+ (n <=> nan_value).should == nil
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value <=> n).should == 1
+ (n <=> infinity_value).should == -1
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (-infinity_value <=> n).should == -1
+ (n <=> -infinity_value).should == 1
+ }
+ end
+
+ it "returns nil when the given argument is not a Float" do
+ (1.0 <=> "1").should be_nil
+ (1.0 <=> "1".freeze).should be_nil
+ (1.0 <=> :one).should be_nil
+ (1.0 <=> true).should be_nil
+ end
+
+ it "compares using #coerce when argument is not a Float" do
+ klass = Class.new do
+ attr_reader :call_count
+ def coerce(other)
+ @call_count ||= 0
+ @call_count += 1
+ [other, 42.0]
+ end
+ end
+
+ coercible = klass.new
+ (2.33 <=> coercible).should == -1
+ (42.0 <=> coercible).should == 0
+ (43.0 <=> coercible).should == 1
+ coercible.call_count.should == 3
+ end
+
+ it "raises TypeError when #coerce misbehaves" do
+ klass = Class.new do
+ def coerce(other)
+ :incorrect
+ end
+ end
+
+ bad_coercible = klass.new
+ -> {
+ 4.2 <=> bad_coercible
+ }.should raise_error(TypeError, "coerce must return [x, y]")
+ end
+
+ it "returns the correct result when one side is infinite" do
+ (infinity_value <=> Float::MAX.to_i*2).should == 1
+ (-Float::MAX.to_i*2 <=> infinity_value).should == -1
+ (-infinity_value <=> -Float::MAX.to_i*2).should == -1
+ (-Float::MAX.to_i*2 <=> -infinity_value).should == 1
+ end
+
+ it "returns 0 when self is Infinity and other other is infinite?=1" do
+ obj = Object.new
+ def obj.infinite?
+ 1
+ end
+ (infinity_value <=> obj).should == 0
+ end
+
+ it "returns 1 when self is Infinity and other is infinite?=-1" do
+ obj = Object.new
+ def obj.infinite?
+ -1
+ end
+ (infinity_value <=> obj).should == 1
+ end
+
+ it "returns 1 when self is Infinity and other is infinite?=nil (which means finite)" do
+ obj = Object.new
+ def obj.infinite?
+ nil
+ end
+ (infinity_value <=> obj).should == 1
+ end
+
+ it "returns 0 for -0.0 and 0.0" do
+ (-0.0 <=> 0.0).should == 0
+ (0.0 <=> -0.0).should == 0
+ end
+
+ it "returns 0 for -0.0 and 0" do
+ (-0.0 <=> 0).should == 0
+ (0 <=> -0.0).should == 0
+ end
+
+ it "returns 0 for 0.0 and 0" do
+ (0.0 <=> 0).should == 0
+ (0 <=> 0.0).should == 0
+ end
+end
diff --git a/spec/ruby/core/float/constants_spec.rb b/spec/ruby/core/float/constants_spec.rb
new file mode 100644
index 0000000000..497e0ae188
--- /dev/null
+++ b/spec/ruby/core/float/constants_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+
+describe "Float constant" do
+ it "DIG is 15" do
+ Float::DIG.should == 15
+ end
+
+ it "EPSILON is 2.220446049250313e-16" do
+ Float::EPSILON.should == 2.0 ** -52
+ Float::EPSILON.should == 2.220446049250313e-16
+ end
+
+ it "MANT_DIG is 53" do
+ Float::MANT_DIG.should == 53
+ end
+
+ it "MAX_10_EXP is 308" do
+ Float::MAX_10_EXP.should == 308
+ end
+
+ it "MIN_10_EXP is -308" do
+ Float::MIN_10_EXP.should == -307
+ end
+
+ it "MAX_EXP is 1024" do
+ Float::MAX_EXP.should == 1024
+ end
+
+ it "MIN_EXP is -1021" do
+ Float::MIN_EXP.should == -1021
+ end
+
+ it "MAX is 1.7976931348623157e+308" do
+ # See https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Double-precision_examples
+ Float::MAX.should == (1 + (1 - (2 ** -52))) * (2.0 ** 1023)
+ Float::MAX.should == 1.7976931348623157e+308
+ end
+
+ it "MIN is 2.2250738585072014e-308" do
+ Float::MIN.should == (2.0 ** -1022)
+ Float::MIN.should == 2.2250738585072014e-308
+ end
+
+ it "RADIX is 2" do
+ Float::RADIX.should == 2
+ end
+
+ it "INFINITY is the positive infinity" do
+ Float::INFINITY.infinite?.should == 1
+ end
+
+ it "NAN is 'not a number'" do
+ Float::NAN.nan?.should be_true
+ end
+end
diff --git a/spec/ruby/core/float/denominator_spec.rb b/spec/ruby/core/float/denominator_spec.rb
new file mode 100644
index 0000000000..6f4fcfcf23
--- /dev/null
+++ b/spec/ruby/core/float/denominator_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Float#denominator" do
+ before :each do
+ @numbers = [
+ 0.0,
+ 29871.22736282,
+ 7772222663.0,
+ 1.4592,
+ ].map {|n| [0-n, n]}.flatten
+ end
+
+ it "returns an Integer" do
+ @numbers.each do |number|
+ number.denominator.should be_kind_of(Integer)
+ end
+ end
+
+ it "converts self to a Rational and returns the denominator" do
+ @numbers.each do |number|
+ number.denominator.should == Rational(number).denominator
+ end
+ end
+
+ it "returns 1 for NaN and Infinity" do
+ nan_value.denominator.should == 1
+ infinity_value.denominator.should == 1
+ end
+end
diff --git a/spec/ruby/core/float/divide_spec.rb b/spec/ruby/core/float/divide_spec.rb
new file mode 100644
index 0000000000..72ab7527bd
--- /dev/null
+++ b/spec/ruby/core/float/divide_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/coerce'
+require_relative 'shared/arithmetic_exception_in_coerce'
+
+describe "Float#/" do
+ it_behaves_like :float_arithmetic_exception_in_coerce, :/
+
+ it "returns self divided by other" do
+ (5.75 / -2).should be_close(-2.875,TOLERANCE)
+ (451.0 / 9.3).should be_close(48.494623655914,TOLERANCE)
+ (91.1 / -0xffffffff).should be_close(-2.12108716418061e-08, TOLERANCE)
+ end
+
+ it "properly coerces objects" do
+ (5.0 / FloatSpecs::CanCoerce.new(5)).should be_close(0, TOLERANCE)
+ end
+
+ it "returns +Infinity when dividing non-zero by zero of the same sign" do
+ (1.0 / 0.0).should be_positive_infinity
+ (-1.0 / -0.0).should be_positive_infinity
+ end
+
+ it "returns -Infinity when dividing non-zero by zero of opposite sign" do
+ (-1.0 / 0.0).should be_negative_infinity
+ (1.0 / -0.0).should be_negative_infinity
+ end
+
+ it "returns NaN when dividing zero by zero" do
+ (0.0 / 0.0).should be_nan
+ (-0.0 / 0.0).should be_nan
+ (0.0 / -0.0).should be_nan
+ (-0.0 / -0.0).should be_nan
+ end
+
+ it "raises a TypeError when given a non-Numeric" do
+ -> { 13.0 / "10" }.should raise_error(TypeError)
+ -> { 13.0 / :symbol }.should raise_error(TypeError)
+ end
+
+ it "divides correctly by Rational numbers" do
+ (1.2345678901234567 / Rational(1, 10000000000000000000)).should == 1.2345678901234567e+19
+ end
+end
diff --git a/spec/ruby/core/float/divmod_spec.rb b/spec/ruby/core/float/divmod_spec.rb
new file mode 100644
index 0000000000..dad45a9b89
--- /dev/null
+++ b/spec/ruby/core/float/divmod_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "Float#divmod" do
+ it "returns an [quotient, modulus] from dividing self by other" do
+ values = 3.14.divmod(2)
+ values[0].should eql(1)
+ values[1].should be_close(1.14, TOLERANCE)
+ values = 2.8284.divmod(3.1415)
+ values[0].should eql(0)
+ values[1].should be_close(2.8284, TOLERANCE)
+ values = -1.0.divmod(bignum_value)
+ values[0].should eql(-1)
+ values[1].should be_close(18446744073709551616.0, TOLERANCE)
+ values = -1.0.divmod(1)
+ values[0].should eql(-1)
+ values[1].should eql(0.0)
+ end
+
+ # Behaviour established as correct in r23953
+ it "raises a FloatDomainError if self is NaN" do
+ -> { nan_value.divmod(1) }.should raise_error(FloatDomainError)
+ end
+
+ # Behaviour established as correct in r23953
+ it "raises a FloatDomainError if other is NaN" do
+ -> { 1.0.divmod(nan_value) }.should raise_error(FloatDomainError)
+ end
+
+ # Behaviour established as correct in r23953
+ it "raises a FloatDomainError if self is Infinity" do
+ -> { infinity_value.divmod(1) }.should raise_error(FloatDomainError)
+ end
+
+ it "raises a ZeroDivisionError if other is zero" do
+ -> { 1.0.divmod(0) }.should raise_error(ZeroDivisionError)
+ -> { 1.0.divmod(0.0) }.should raise_error(ZeroDivisionError)
+ end
+
+ # redmine #5276"
+ it "returns the correct [quotient, modulus] even for large quotient" do
+ 0.59.divmod(7.761021455128987e-11).first.should eql(7602092113)
+ end
+end
diff --git a/spec/ruby/core/float/dup_spec.rb b/spec/ruby/core/float/dup_spec.rb
new file mode 100644
index 0000000000..294da8e2bc
--- /dev/null
+++ b/spec/ruby/core/float/dup_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "Float#dup" do
+ it "returns self" do
+ float = 2.4
+ float.dup.should equal(float)
+ end
+end
diff --git a/spec/ruby/core/float/eql_spec.rb b/spec/ruby/core/float/eql_spec.rb
new file mode 100644
index 0000000000..6b5f91db33
--- /dev/null
+++ b/spec/ruby/core/float/eql_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Float#eql?" do
+ it "returns true if other is a Float equal to self" do
+ 0.0.eql?(0.0).should be_true
+ end
+
+ it "returns false if other is a Float not equal to self" do
+ 1.0.eql?(1.1).should be_false
+ end
+
+ it "returns false if other is not a Float" do
+ 1.0.eql?(1).should be_false
+ 1.0.eql?(:one).should be_false
+ end
+end
diff --git a/spec/ruby/core/float/equal_value_spec.rb b/spec/ruby/core/float/equal_value_spec.rb
new file mode 100644
index 0000000000..03eea5108e
--- /dev/null
+++ b/spec/ruby/core/float/equal_value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal'
+
+describe "Float#==" do
+ it_behaves_like :float_equal, :==
+end
diff --git a/spec/ruby/core/float/exponent_spec.rb b/spec/ruby/core/float/exponent_spec.rb
new file mode 100644
index 0000000000..a4c03469a7
--- /dev/null
+++ b/spec/ruby/core/float/exponent_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "Float#**" do
+ it "returns self raise to the other power" do
+ (2.3 ** 3).should be_close(12.167,TOLERANCE)
+ (5.2 ** -1).should be_close(0.192307692307692,TOLERANCE)
+ (9.5 ** 0.5).should be_close(3.08220700148449, TOLERANCE)
+ (9.5 ** 0xffffffff).to_s.should == 'Infinity'
+ end
+
+ it "returns a complex number when negative and raised to a fractional power" do
+ ((-8.0) ** (1.0/3)) .should be_close(Complex(1, 1.73205), TOLERANCE)
+ ((-8.0) ** Rational(1,3)).should be_close(Complex(1, 1.73205), TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/float/fdiv_spec.rb b/spec/ruby/core/float/fdiv_spec.rb
new file mode 100644
index 0000000000..be25ee283b
--- /dev/null
+++ b/spec/ruby/core/float/fdiv_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/quo'
+
+describe "Float#fdiv" do
+ it_behaves_like :float_quo, :fdiv
+end
diff --git a/spec/ruby/core/float/finite_spec.rb b/spec/ruby/core/float/finite_spec.rb
new file mode 100644
index 0000000000..d839b30e32
--- /dev/null
+++ b/spec/ruby/core/float/finite_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Float#finite?" do
+ it "returns true for finite values" do
+ 3.14159.should.finite?
+ end
+
+ it "returns false for positive infinity" do
+ infinity_value.should_not.finite?
+ end
+
+ it "returns false for negative infinity" do
+ (-infinity_value).should_not.finite?
+ end
+
+ it "returns false for NaN" do
+ nan_value.should_not.finite?
+ end
+end
diff --git a/spec/ruby/core/float/fixtures/classes.rb b/spec/ruby/core/float/fixtures/classes.rb
new file mode 100644
index 0000000000..2d80184e7d
--- /dev/null
+++ b/spec/ruby/core/float/fixtures/classes.rb
@@ -0,0 +1,4 @@
+module FloatSpecs
+ class CoerceError < StandardError
+ end
+end
diff --git a/spec/ruby/core/float/fixtures/coerce.rb b/spec/ruby/core/float/fixtures/coerce.rb
new file mode 100644
index 0000000000..2cf155be95
--- /dev/null
+++ b/spec/ruby/core/float/fixtures/coerce.rb
@@ -0,0 +1,15 @@
+module FloatSpecs
+ class CanCoerce
+ def initialize(a)
+ @a = a
+ end
+
+ def coerce(b)
+ [self.class.new(b), @a]
+ end
+
+ def /(b)
+ @a.to_i % b.to_i
+ end
+ end
+end
diff --git a/spec/ruby/core/float/float_spec.rb b/spec/ruby/core/float/float_spec.rb
new file mode 100644
index 0000000000..263ae82079
--- /dev/null
+++ b/spec/ruby/core/float/float_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Float" do
+ it "includes Comparable" do
+ Float.include?(Comparable).should == true
+ end
+
+ it ".allocate raises a TypeError" do
+ -> do
+ Float.allocate
+ end.should raise_error(TypeError)
+ end
+
+ it ".new is undefined" do
+ -> do
+ Float.new
+ end.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/float/floor_spec.rb b/spec/ruby/core/float/floor_spec.rb
new file mode 100644
index 0000000000..046216d36d
--- /dev/null
+++ b/spec/ruby/core/float/floor_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Float#floor" do
+ it "returns the largest Integer less than or equal to self" do
+ -1.2.floor.should eql( -2)
+ -1.0.floor.should eql( -1)
+ 0.0.floor.should eql( 0 )
+ 1.0.floor.should eql( 1 )
+ 5.9.floor.should eql( 5 )
+ -9223372036854775808.1.floor.should eql(-9223372036854775808)
+ +9223372036854775808.1.floor.should eql(+9223372036854775808)
+ end
+
+ it "returns the largest number less than or equal to self with an optionally given precision" do
+ 2.1679.floor(0).should eql(2)
+ 214.94.floor(-1).should eql(210)
+ 7.0.floor(1).should eql(7.0)
+ -1.234.floor(2).should eql(-1.24)
+ 5.123812.floor(4).should eql(5.1238)
+ end
+end
diff --git a/spec/ruby/core/float/gt_spec.rb b/spec/ruby/core/float/gt_spec.rb
new file mode 100644
index 0000000000..33078e07ce
--- /dev/null
+++ b/spec/ruby/core/float/gt_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_exception_in_coerce'
+
+describe "Float#>" do
+ it_behaves_like :float_comparison_exception_in_coerce, :>
+
+ it "returns true if self is greater than other" do
+ (1.5 > 1).should == true
+ (2.5 > 3).should == false
+ (45.91 > bignum_value).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Numeric" do
+ -> { 5.0 > "4" }.should raise_error(ArgumentError)
+ -> { 5.0 > mock('x') }.should raise_error(ArgumentError)
+ end
+
+ it "returns false if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value > n).should == false
+ (n > nan_value).should == false
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value > n).should == true
+ (n > infinity_value).should == false
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (-infinity_value > n).should == false
+ (n > -infinity_value).should == true
+ }
+ end
+end
diff --git a/spec/ruby/core/float/gte_spec.rb b/spec/ruby/core/float/gte_spec.rb
new file mode 100644
index 0000000000..44c0a81b43
--- /dev/null
+++ b/spec/ruby/core/float/gte_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_exception_in_coerce'
+
+describe "Float#>=" do
+ it_behaves_like :float_comparison_exception_in_coerce, :>=
+
+ it "returns true if self is greater than or equal to other" do
+ (5.2 >= 5.2).should == true
+ (9.71 >= 1).should == true
+ (5.55382 >= 0xfabdafbafcab).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Numeric" do
+ -> { 5.0 >= "4" }.should raise_error(ArgumentError)
+ -> { 5.0 >= mock('x') }.should raise_error(ArgumentError)
+ end
+
+ it "returns false if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value >= n).should == false
+ (n >= nan_value).should == false
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value >= n).should == true
+ (n >= infinity_value).should == false
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (-infinity_value >= n).should == false
+ (n >= -infinity_value).should == true
+ }
+ end
+end
diff --git a/spec/ruby/core/float/hash_spec.rb b/spec/ruby/core/float/hash_spec.rb
new file mode 100644
index 0000000000..5f77e3b4a1
--- /dev/null
+++ b/spec/ruby/core/float/hash_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Float#hash" do
+ it "is provided" do
+ 0.0.respond_to?(:hash).should == true
+ end
+
+ it "is stable" do
+ 1.0.hash.should == 1.0.hash
+ end
+end
diff --git a/spec/ruby/core/float/infinite_spec.rb b/spec/ruby/core/float/infinite_spec.rb
new file mode 100644
index 0000000000..901c2738aa
--- /dev/null
+++ b/spec/ruby/core/float/infinite_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Float#infinite?" do
+ it "returns nil for finite values" do
+ 1.0.infinite?.should == nil
+ end
+
+ it "returns 1 for positive infinity" do
+ infinity_value.infinite?.should == 1
+ end
+
+ it "returns -1 for negative infinity" do
+ (-infinity_value).infinite?.should == -1
+ end
+
+ it "returns nil for NaN" do
+ nan_value.infinite?.should == nil
+ end
+end
diff --git a/spec/ruby/core/float/inspect_spec.rb b/spec/ruby/core/float/inspect_spec.rb
new file mode 100644
index 0000000000..4be1927d84
--- /dev/null
+++ b/spec/ruby/core/float/inspect_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Float#inspect" do
+ it_behaves_like :float_to_s, :inspect
+end
diff --git a/spec/ruby/core/float/lt_spec.rb b/spec/ruby/core/float/lt_spec.rb
new file mode 100644
index 0000000000..94dcfc42f8
--- /dev/null
+++ b/spec/ruby/core/float/lt_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_exception_in_coerce'
+
+describe "Float#<" do
+ it_behaves_like :float_comparison_exception_in_coerce, :<
+
+ it "returns true if self is less than other" do
+ (71.3 < 91.8).should == true
+ (192.6 < -500).should == false
+ (-0.12 < 0x4fffffff).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Numeric" do
+ -> { 5.0 < "4" }.should raise_error(ArgumentError)
+ -> { 5.0 < mock('x') }.should raise_error(ArgumentError)
+ end
+
+ it "returns false if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value < n).should == false
+ (n < nan_value).should == false
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value < n).should == false
+ (n < infinity_value).should == true
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (-infinity_value < n).should == true
+ (n < -infinity_value).should == false
+ }
+ end
+end
diff --git a/spec/ruby/core/float/lte_spec.rb b/spec/ruby/core/float/lte_spec.rb
new file mode 100644
index 0000000000..7b5a86ee76
--- /dev/null
+++ b/spec/ruby/core/float/lte_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_exception_in_coerce'
+
+describe "Float#<=" do
+ it_behaves_like :float_comparison_exception_in_coerce, :>=
+
+ it "returns true if self is less than or equal to other" do
+ (2.0 <= 3.14159).should == true
+ (-2.7183 <= -24).should == false
+ (0.0 <= 0.0).should == true
+ (9_235.9 <= bignum_value).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Numeric" do
+ -> { 5.0 <= "4" }.should raise_error(ArgumentError)
+ -> { 5.0 <= mock('x') }.should raise_error(ArgumentError)
+ end
+
+ it "returns false if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value <= n).should == false
+ (n <= nan_value).should == false
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value <= n).should == false
+ (n <= infinity_value).should == true
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (-infinity_value <= n).should == true
+ (n <= -infinity_value).should == false
+ }
+ end
+end
diff --git a/spec/ruby/core/float/magnitude_spec.rb b/spec/ruby/core/float/magnitude_spec.rb
new file mode 100644
index 0000000000..db577c15c5
--- /dev/null
+++ b/spec/ruby/core/float/magnitude_spec.rb
@@ -0,0 +1,5 @@
+require_relative 'shared/abs'
+
+describe "Float#magnitude" do
+ it_behaves_like :float_abs, :magnitude
+end
diff --git a/spec/ruby/core/float/minus_spec.rb b/spec/ruby/core/float/minus_spec.rb
new file mode 100644
index 0000000000..a4281a397b
--- /dev/null
+++ b/spec/ruby/core/float/minus_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_exception_in_coerce'
+
+describe "Float#-" do
+ it_behaves_like :float_arithmetic_exception_in_coerce, :-
+
+ it "returns self minus other" do
+ (9_237_212.5280 - 5_280).should be_close(9231932.528, TOLERANCE)
+ (2_560_496.1691 - bignum_value).should be_close(-18446744073706991616.0, TOLERANCE)
+ (5.5 - 5.5).should be_close(0.0,TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/float/modulo_spec.rb b/spec/ruby/core/float/modulo_spec.rb
new file mode 100644
index 0000000000..8ae80a0b05
--- /dev/null
+++ b/spec/ruby/core/float/modulo_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/modulo'
+
+describe "Float#%" do
+ it_behaves_like :float_modulo, :%
+end
+
+describe "Float#modulo" do
+ it_behaves_like :float_modulo, :modulo
+end
diff --git a/spec/ruby/core/float/multiply_spec.rb b/spec/ruby/core/float/multiply_spec.rb
new file mode 100644
index 0000000000..2adb8796cd
--- /dev/null
+++ b/spec/ruby/core/float/multiply_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_exception_in_coerce'
+
+describe "Float#*" do
+ it_behaves_like :float_arithmetic_exception_in_coerce, :*
+
+ it "returns self multiplied by other" do
+ (4923.98221 * 2).should be_close(9847.96442, TOLERANCE)
+ (6712.5 * 0.25).should be_close(1678.125, TOLERANCE)
+ (256.4096 * bignum_value).should be_close(4729922269242236862464.0, TOLERANCE)
+ end
+
+ it "raises a TypeError when given a non-Numeric" do
+ -> { 13.0 * "10" }.should raise_error(TypeError)
+ -> { 13.0 * :symbol }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/float/nan_spec.rb b/spec/ruby/core/float/nan_spec.rb
new file mode 100644
index 0000000000..c1043ef21b
--- /dev/null
+++ b/spec/ruby/core/float/nan_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Float#nan?" do
+ it "returns true if self is not a valid IEEE floating-point number" do
+ 0.0.should_not.nan?
+ -1.5.should_not.nan?
+ nan_value.should.nan?
+ end
+end
diff --git a/spec/ruby/core/float/negative_spec.rb b/spec/ruby/core/float/negative_spec.rb
new file mode 100644
index 0000000000..511d92ade6
--- /dev/null
+++ b/spec/ruby/core/float/negative_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "Float#negative?" do
+ describe "on positive numbers" do
+ it "returns false" do
+ 0.1.negative?.should be_false
+ end
+ end
+
+ describe "on zero" do
+ it "returns false" do
+ 0.0.negative?.should be_false
+ end
+ end
+
+ describe "on negative zero" do
+ it "returns false" do
+ -0.0.negative?.should be_false
+ end
+ end
+
+ describe "on negative numbers" do
+ it "returns true" do
+ -0.1.negative?.should be_true
+ end
+ end
+
+ describe "on NaN" do
+ it "returns false" do
+ nan_value.negative?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/core/float/next_float_spec.rb b/spec/ruby/core/float/next_float_spec.rb
new file mode 100644
index 0000000000..29e2d31146
--- /dev/null
+++ b/spec/ruby/core/float/next_float_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Float#next_float" do
+ it "returns a float the smallest possible step greater than the receiver" do
+ barely_positive = 0.0.next_float
+ barely_positive.should == 0.0.next_float
+
+ barely_positive.should > 0.0
+ barely_positive.should < barely_positive.next_float
+
+ midpoint = barely_positive / 2
+ [0.0, barely_positive].should include midpoint
+ end
+
+ it "returns Float::INFINITY for Float::INFINITY" do
+ Float::INFINITY.next_float.should == Float::INFINITY
+ end
+
+ it "steps directly between MAX and INFINITY" do
+ (-Float::INFINITY).next_float.should == -Float::MAX
+ Float::MAX.next_float.should == Float::INFINITY
+ end
+
+ it "steps directly between 1.0 and 1.0 + EPSILON" do
+ 1.0.next_float.should == 1.0 + Float::EPSILON
+ end
+
+ it "steps directly between -1.0 and -1.0 + EPSILON/2" do
+ (-1.0).next_float.should == -1.0 + Float::EPSILON/2
+ end
+
+ it "reverses the effect of prev_float for all Floats except INFINITY and +0.0" do
+ num = -rand
+ num.prev_float.next_float.should == num
+ end
+
+ it "returns negative zero when stepping upward from just below zero" do
+ x = (-0.0).prev_float.next_float
+ (1/x).should == -Float::INFINITY
+ end
+
+ it "gives the same result for -0.0 as for +0.0" do
+ (-0.0).next_float.should == (0.0).next_float
+ end
+
+ it "returns NAN if NAN was the receiver" do
+ Float::NAN.next_float.should.nan?
+ end
+end
diff --git a/spec/ruby/core/float/numerator_spec.rb b/spec/ruby/core/float/numerator_spec.rb
new file mode 100644
index 0000000000..7832e8f056
--- /dev/null
+++ b/spec/ruby/core/float/numerator_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "Float#numerator" do
+ before :all do
+ @numbers = [
+ 29871.2722891,
+ 999.1**99.928888,
+ -72628191273.22,
+ 29282.2827,
+ -2927.00091,
+ 12.0,
+ Float::MAX,
+ ]
+ end
+
+ it "converts self to a Rational object then returns its numerator" do
+ @numbers.each do |number|
+ number.infinite?.should be_nil
+ number.numerator.should == Rational(number).numerator
+ end
+ end
+
+ it "returns 0 for 0.0" do
+ 0.0.numerator.should == 0
+ end
+
+ it "returns NaN for NaN" do
+ nan_value.numerator.nan?.should be_true
+ end
+
+ it "returns Infinity for Infinity" do
+ infinity_value.numerator.infinite?.should == 1
+ end
+
+ it "returns -Infinity for -Infinity" do
+ (-infinity_value).numerator.infinite?.should == -1
+ end
+
+end
diff --git a/spec/ruby/core/float/phase_spec.rb b/spec/ruby/core/float/phase_spec.rb
new file mode 100644
index 0000000000..2aa84024b4
--- /dev/null
+++ b/spec/ruby/core/float/phase_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Float#phase" do
+ it_behaves_like :float_arg, :phase
+end
diff --git a/spec/ruby/core/float/plus_spec.rb b/spec/ruby/core/float/plus_spec.rb
new file mode 100644
index 0000000000..e3e19d7f39
--- /dev/null
+++ b/spec/ruby/core/float/plus_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_exception_in_coerce'
+
+describe "Float#+" do
+ it_behaves_like :float_arithmetic_exception_in_coerce, :+
+
+ it "returns self plus other" do
+ (491.213 + 2).should be_close(493.213, TOLERANCE)
+ (9.99 + bignum_value).should be_close(18446744073709551616.0, TOLERANCE)
+ (1001.99 + 5.219).should be_close(1007.209, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/float/positive_spec.rb b/spec/ruby/core/float/positive_spec.rb
new file mode 100644
index 0000000000..575f92a720
--- /dev/null
+++ b/spec/ruby/core/float/positive_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "Float#positive?" do
+ describe "on positive numbers" do
+ it "returns true" do
+ 0.1.positive?.should be_true
+ end
+ end
+
+ describe "on zero" do
+ it "returns false" do
+ 0.0.positive?.should be_false
+ end
+ end
+
+ describe "on negative zero" do
+ it "returns false" do
+ -0.0.positive?.should be_false
+ end
+ end
+
+ describe "on negative numbers" do
+ it "returns false" do
+ -0.1.positive?.should be_false
+ end
+ end
+
+ describe "on NaN" do
+ it "returns false" do
+ nan_value.positive?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/core/float/prev_float_spec.rb b/spec/ruby/core/float/prev_float_spec.rb
new file mode 100644
index 0000000000..5e50269da7
--- /dev/null
+++ b/spec/ruby/core/float/prev_float_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Float#prev_float" do
+ it "returns a float the smallest possible step smaller than the receiver" do
+ barely_negative = 0.0.prev_float
+ barely_negative.should == 0.0.prev_float
+
+ barely_negative.should < 0.0
+ barely_negative.should > barely_negative.prev_float
+
+ midpoint = barely_negative / 2
+ [0.0, barely_negative].should include midpoint
+ end
+
+ it "returns -Float::INFINITY for -Float::INFINITY" do
+ (-Float::INFINITY).prev_float.should == -Float::INFINITY
+ end
+
+ it "steps directly between MAX and INFINITY" do
+ Float::INFINITY.prev_float.should == Float::MAX
+ (-Float::MAX).prev_float.should == -Float::INFINITY
+ end
+
+ it "steps directly between 1.0 and 1.0 - EPSILON/2" do
+ 1.0.prev_float.should == 1.0 - Float::EPSILON/2
+ end
+
+ it "steps directly between -1.0 and -1.0 - EPSILON" do
+ (-1.0).prev_float.should == -1.0 - Float::EPSILON
+ end
+
+ it "reverses the effect of next_float for all Floats except -INFINITY and -0.0" do
+ num = rand
+ num.next_float.prev_float.should == num
+ end
+
+ it "returns positive zero when stepping downward from just above zero" do
+ x = 0.0.next_float.prev_float
+ (1/x).should == Float::INFINITY
+ end
+
+ it "gives the same result for -0.0 as for +0.0" do
+ (0.0).prev_float.should == (-0.0).prev_float
+ end
+
+ it "returns NAN if NAN was the receiver" do
+ Float::NAN.prev_float.should.nan?
+ end
+end
diff --git a/spec/ruby/core/float/quo_spec.rb b/spec/ruby/core/float/quo_spec.rb
new file mode 100644
index 0000000000..b5c64f9d87
--- /dev/null
+++ b/spec/ruby/core/float/quo_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/quo'
+
+describe "Float#quo" do
+ it_behaves_like :float_quo, :quo
+end
diff --git a/spec/ruby/core/float/rationalize_spec.rb b/spec/ruby/core/float/rationalize_spec.rb
new file mode 100644
index 0000000000..0c5bef7ac4
--- /dev/null
+++ b/spec/ruby/core/float/rationalize_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "Float#rationalize" do
+ it "returns self as a simplified Rational with no argument" do
+ (3382729202.92822).rationalize.should == Rational(4806858197361, 1421)
+ end
+
+ # FIXME: These specs need reviewing by somebody familiar with the
+ # algorithm used by #rationalize
+ it "simplifies self to the degree specified by a Rational argument" do
+ f = 0.3
+ f.rationalize(Rational(1,10)).should == Rational(1,3)
+ f.rationalize(Rational(-1,10)).should == Rational(1,3)
+
+ f = -f
+ f.rationalize(Rational(1,10)).should == Rational(-1,3)
+ f.rationalize(Rational(-1,10)).should == Rational(-1,3)
+
+ end
+
+ it "simplifies self to the degree specified by a Float argument" do
+ f = 0.3
+ f.rationalize(0.05).should == Rational(1,3)
+ f.rationalize(0.001).should == Rational(3, 10)
+
+ f = -f
+ f.rationalize(0.05).should == Rational(-1,3)
+ f.rationalize(0.001).should == Rational(-3,10)
+ end
+
+ it "raises a FloatDomainError for Infinity" do
+ -> {infinity_value.rationalize}.should raise_error(FloatDomainError)
+ end
+
+ it "raises a FloatDomainError for NaN" do
+ -> { nan_value.rationalize }.should raise_error(FloatDomainError)
+ end
+
+ it "raises ArgumentError when passed more than one argument" do
+ -> { 0.3.rationalize(0.1, 0.1) }.should raise_error(ArgumentError)
+ -> { 0.3.rationalize(0.1, 0.1, 2) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/float/round_spec.rb b/spec/ruby/core/float/round_spec.rb
new file mode 100644
index 0000000000..e5a8f534e7
--- /dev/null
+++ b/spec/ruby/core/float/round_spec.rb
@@ -0,0 +1,194 @@
+require_relative '../../spec_helper'
+
+describe "Float#round" do
+ it "returns the nearest Integer" do
+ 5.5.round.should == 6
+ 0.4.round.should == 0
+ 0.6.round.should == 1
+ -1.4.round.should == -1
+ -2.8.round.should == -3
+ 0.0.round.should == 0
+ end
+
+ it "returns the nearest Integer for Float near the limit" do
+ 0.49999999999999994.round.should == 0
+ -0.49999999999999994.round.should == 0
+ end
+
+ it "raises FloatDomainError for exceptional values" do
+ -> { (+infinity_value).round }.should raise_error(FloatDomainError)
+ -> { (-infinity_value).round }.should raise_error(FloatDomainError)
+ -> { nan_value.round }.should raise_error(FloatDomainError)
+ end
+
+ it "rounds self to an optionally given precision" do
+ 5.5.round(0).should eql(6)
+ 5.7.round(1).should eql(5.7)
+ 1.2345678.round(2).should == 1.23
+ 123456.78.round(-2).should eql(123500) # rounded up
+ -123456.78.round(-2).should eql(-123500)
+ 12.345678.round(3.999).should == 12.346
+ end
+
+ it "returns zero when passed a negative argument with magnitude greater than magnitude of the whole number portion of the Float" do
+ 0.8346268.round(-1).should eql(0)
+ end
+
+ it "raises a TypeError when its argument can not be converted to an Integer" do
+ -> { 1.0.round("4") }.should raise_error(TypeError)
+ -> { 1.0.round(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises FloatDomainError for exceptional values when passed a non-positive precision" do
+ -> { Float::INFINITY.round( 0) }.should raise_error(FloatDomainError)
+ -> { Float::INFINITY.round(-2) }.should raise_error(FloatDomainError)
+ -> { (-Float::INFINITY).round( 0) }.should raise_error(FloatDomainError)
+ -> { (-Float::INFINITY).round(-2) }.should raise_error(FloatDomainError)
+ end
+
+ it "raises RangeError for NAN when passed a non-positive precision" do
+ -> { Float::NAN.round(0) }.should raise_error(RangeError)
+ -> { Float::NAN.round(-2) }.should raise_error(RangeError)
+ end
+
+ it "returns self for exceptional values when passed a non-negative precision" do
+ Float::INFINITY.round(2).should == Float::INFINITY
+ (-Float::INFINITY).round(2).should == -Float::INFINITY
+ Float::NAN.round(2).should be_nan
+ end
+
+ # redmine:5227
+ it "works for corner cases" do
+ 42.0.round(308).should eql(42.0)
+ 1.0e307.round(2).should eql(1.0e307)
+ end
+
+ # redmine:5271
+ it "returns rounded values for big argument" do
+ 0.42.round(2.0**30).should == 0.42
+ end
+
+ it "returns big values rounded to nearest" do
+ +2.5e20.round(-20).should eql( +3 * 10 ** 20 )
+ -2.5e20.round(-20).should eql( -3 * 10 ** 20 )
+ end
+
+ # redmine #5272
+ it "returns rounded values for big values" do
+ +2.4e20.round(-20).should eql( +2 * 10 ** 20 )
+ -2.4e20.round(-20).should eql( -2 * 10 ** 20 )
+ +2.5e200.round(-200).should eql( +3 * 10 ** 200 )
+ +2.4e200.round(-200).should eql( +2 * 10 ** 200 )
+ -2.5e200.round(-200).should eql( -3 * 10 ** 200 )
+ -2.4e200.round(-200).should eql( -2 * 10 ** 200 )
+ end
+
+ it "returns different rounded values depending on the half option" do
+ 2.5.round(half: nil).should eql(3)
+ 2.5.round(half: :up).should eql(3)
+ 2.5.round(half: :down).should eql(2)
+ 2.5.round(half: :even).should eql(2)
+ 3.5.round(half: nil).should eql(4)
+ 3.5.round(half: :up).should eql(4)
+ 3.5.round(half: :down).should eql(3)
+ 3.5.round(half: :even).should eql(4)
+ (-2.5).round(half: nil).should eql(-3)
+ (-2.5).round(half: :up).should eql(-3)
+ (-2.5).round(half: :down).should eql(-2)
+ (-2.5).round(half: :even).should eql(-2)
+ end
+
+ it "rounds self to an optionally given precision with a half option" do
+ 5.55.round(1, half: nil).should eql(5.6)
+ 5.55.round(1, half: :up).should eql(5.6)
+ 5.55.round(1, half: :down).should eql(5.5)
+ 5.55.round(1, half: :even).should eql(5.6)
+ -5.55.round(1, half: nil).should eql(-5.6)
+ -5.55.round(1, half: :up).should eql(-5.6)
+ -5.55.round(1, half: :down).should eql(-5.5)
+ -5.55.round(1, half: :even).should eql(-5.6)
+ end
+
+ it "preserves cases where neighbouring floating pointer number increase the decimal places" do
+ 4.8100000000000005.round(5, half: nil).should eql(4.81)
+ 4.8100000000000005.round(5, half: :up).should eql(4.81)
+ 4.8100000000000005.round(5, half: :down).should eql(4.81)
+ 4.8100000000000005.round(5, half: :even).should eql(4.81)
+ -4.8100000000000005.round(5, half: nil).should eql(-4.81)
+ -4.8100000000000005.round(5, half: :up).should eql(-4.81)
+ -4.8100000000000005.round(5, half: :down).should eql(-4.81)
+ -4.8100000000000005.round(5, half: :even).should eql(-4.81)
+ 4.81.round(5, half: nil).should eql(4.81)
+ 4.81.round(5, half: :up).should eql(4.81)
+ 4.81.round(5, half: :down).should eql(4.81)
+ 4.81.round(5, half: :even).should eql(4.81)
+ -4.81.round(5, half: nil).should eql(-4.81)
+ -4.81.round(5, half: :up).should eql(-4.81)
+ -4.81.round(5, half: :down).should eql(-4.81)
+ -4.81.round(5, half: :even).should eql(-4.81)
+ 4.809999999999999.round(5, half: nil).should eql(4.81)
+ 4.809999999999999.round(5, half: :up).should eql(4.81)
+ 4.809999999999999.round(5, half: :down).should eql(4.81)
+ 4.809999999999999.round(5, half: :even).should eql(4.81)
+ -4.809999999999999.round(5, half: nil).should eql(-4.81)
+ -4.809999999999999.round(5, half: :up).should eql(-4.81)
+ -4.809999999999999.round(5, half: :down).should eql(-4.81)
+ -4.809999999999999.round(5, half: :even).should eql(-4.81)
+ end
+
+ ruby_bug "", ""..."3.3" do
+ # These numbers are neighbouring floating point numbers round a
+ # precise value. They test that the rounding modes work correctly
+ # round that value and precision is not lost which might cause
+ # incorrect results.
+ it "does not lose precision during the rounding process" do
+ 767573.1875850001.round(5, half: nil).should eql(767573.18759)
+ 767573.1875850001.round(5, half: :up).should eql(767573.18759)
+ 767573.1875850001.round(5, half: :down).should eql(767573.18759)
+ 767573.1875850001.round(5, half: :even).should eql(767573.18759)
+ -767573.1875850001.round(5, half: nil).should eql(-767573.18759)
+ -767573.1875850001.round(5, half: :up).should eql(-767573.18759)
+ -767573.1875850001.round(5, half: :down).should eql(-767573.18759)
+ -767573.1875850001.round(5, half: :even).should eql(-767573.18759)
+ 767573.187585.round(5, half: nil).should eql(767573.18759)
+ 767573.187585.round(5, half: :up).should eql(767573.18759)
+ 767573.187585.round(5, half: :down).should eql(767573.18758)
+ 767573.187585.round(5, half: :even).should eql(767573.18758)
+ -767573.187585.round(5, half: nil).should eql(-767573.18759)
+ -767573.187585.round(5, half: :up).should eql(-767573.18759)
+ -767573.187585.round(5, half: :down).should eql(-767573.18758)
+ -767573.187585.round(5, half: :even).should eql(-767573.18758)
+ 767573.1875849998.round(5, half: nil).should eql(767573.18758)
+ 767573.1875849998.round(5, half: :up).should eql(767573.18758)
+ 767573.1875849998.round(5, half: :down).should eql(767573.18758)
+ 767573.1875849998.round(5, half: :even).should eql(767573.18758)
+ -767573.1875849998.round(5, half: nil).should eql(-767573.18758)
+ -767573.1875849998.round(5, half: :up).should eql(-767573.18758)
+ -767573.1875849998.round(5, half: :down).should eql(-767573.18758)
+ -767573.1875849998.round(5, half: :even).should eql(-767573.18758)
+ end
+ end
+
+ it "raises FloatDomainError for exceptional values with a half option" do
+ -> { (+infinity_value).round(half: :up) }.should raise_error(FloatDomainError)
+ -> { (-infinity_value).round(half: :down) }.should raise_error(FloatDomainError)
+ -> { nan_value.round(half: :even) }.should raise_error(FloatDomainError)
+ end
+
+ it "raise for a non-existent round mode" do
+ -> { 14.2.round(half: :nonsense) }.should raise_error(ArgumentError, "invalid rounding mode: nonsense")
+ end
+
+ describe "when 0.0 is given" do
+ it "returns self for positive ndigits" do
+ (0.0).round(5).inspect.should == "0.0"
+ (-0.0).round(1).inspect.should == "-0.0"
+ end
+
+ it "returns 0 for 0 or undefined ndigits" do
+ (0.0).round.should == 0
+ (-0.0).round(0).should == 0
+ (0.0).round(half: :up) == 0
+ end
+ end
+end
diff --git a/spec/ruby/core/float/shared/abs.rb b/spec/ruby/core/float/shared/abs.rb
new file mode 100644
index 0000000000..607983322d
--- /dev/null
+++ b/spec/ruby/core/float/shared/abs.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+describe :float_abs, shared: true do
+ it "returns the absolute value" do
+ -99.1.send(@method).should be_close(99.1, TOLERANCE)
+ 4.5.send(@method).should be_close(4.5, TOLERANCE)
+ 0.0.send(@method).should be_close(0.0, TOLERANCE)
+ end
+
+ it "returns 0.0 if -0.0" do
+ (-0.0).send(@method).should be_positive_zero
+ end
+
+ it "returns Infinity if -Infinity" do
+ (-infinity_value).send(@method).infinite?.should == 1
+ end
+
+ it "returns NaN if NaN" do
+ nan_value.send(@method).nan?.should be_true
+ end
+end
diff --git a/spec/ruby/core/float/shared/arg.rb b/spec/ruby/core/float/shared/arg.rb
new file mode 100644
index 0000000000..136cf19ec8
--- /dev/null
+++ b/spec/ruby/core/float/shared/arg.rb
@@ -0,0 +1,36 @@
+describe :float_arg, shared: true do
+ it "returns NaN if NaN" do
+ f = nan_value
+ f.send(@method).nan?.should be_true
+ end
+
+ it "returns self if NaN" do
+ f = nan_value
+ f.send(@method).should equal(f)
+ end
+
+ it "returns 0 if positive" do
+ 1.0.send(@method).should == 0
+ end
+
+ it "returns 0 if +0.0" do
+ 0.0.send(@method).should == 0
+ end
+
+ it "returns 0 if +Infinity" do
+ infinity_value.send(@method).should == 0
+ end
+
+ it "returns Pi if negative" do
+ (-1.0).send(@method).should == Math::PI
+ end
+
+ # This was established in r23960
+ it "returns Pi if -0.0" do
+ (-0.0).send(@method).should == Math::PI
+ end
+
+ it "returns Pi if -Infinity" do
+ (-infinity_value).send(@method).should == Math::PI
+ end
+end
diff --git a/spec/ruby/core/float/shared/arithmetic_exception_in_coerce.rb b/spec/ruby/core/float/shared/arithmetic_exception_in_coerce.rb
new file mode 100644
index 0000000000..eec92d858f
--- /dev/null
+++ b/spec/ruby/core/float/shared/arithmetic_exception_in_coerce.rb
@@ -0,0 +1,11 @@
+require_relative '../fixtures/classes'
+
+describe :float_arithmetic_exception_in_coerce, shared: true do
+ it "does not rescue exception raised in other#coerce" do
+ b = mock("numeric with failed #coerce")
+ b.should_receive(:coerce).and_raise(FloatSpecs::CoerceError)
+
+ # e.g. 1.0 > b
+ -> { 1.0.send(@method, b) }.should raise_error(FloatSpecs::CoerceError)
+ end
+end
diff --git a/spec/ruby/core/float/shared/comparison_exception_in_coerce.rb b/spec/ruby/core/float/shared/comparison_exception_in_coerce.rb
new file mode 100644
index 0000000000..3e2c1e28dd
--- /dev/null
+++ b/spec/ruby/core/float/shared/comparison_exception_in_coerce.rb
@@ -0,0 +1,11 @@
+require_relative '../fixtures/classes'
+
+describe :float_comparison_exception_in_coerce, shared: true do
+ it "does not rescue exception raised in other#coerce" do
+ b = mock("numeric with failed #coerce")
+ b.should_receive(:coerce).and_raise(FloatSpecs::CoerceError)
+
+ # e.g. 1.0 > b
+ -> { 1.0.send(@method, b) }.should raise_error(FloatSpecs::CoerceError)
+ end
+end
diff --git a/spec/ruby/core/float/shared/equal.rb b/spec/ruby/core/float/shared/equal.rb
new file mode 100644
index 0000000000..4d524e1cf2
--- /dev/null
+++ b/spec/ruby/core/float/shared/equal.rb
@@ -0,0 +1,38 @@
+describe :float_equal, shared: true do
+ it "returns true if self has the same value as other" do
+ 1.0.send(@method, 1).should == true
+ 2.71828.send(@method, 1.428).should == false
+ -4.2.send(@method, 4.2).should == false
+ end
+
+ it "calls 'other == self' if coercion fails" do
+ x = mock('other')
+ def x.==(other)
+ 2.0 == other
+ end
+
+ 1.0.send(@method, x).should == false
+ 2.0.send(@method, x).should == true
+ end
+
+ it "returns false if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value.send(@method, n)).should == false
+ (n.send(@method, nan_value)).should == false
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value.send(@method, n)).should == false
+ (n.send(@method, infinity_value)).should == false
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ ((-infinity_value).send(@method, n)).should == false
+ (n.send(@method, -infinity_value)).should == false
+ }
+ end
+end
diff --git a/spec/ruby/core/float/shared/modulo.rb b/spec/ruby/core/float/shared/modulo.rb
new file mode 100644
index 0000000000..6700bd4f4e
--- /dev/null
+++ b/spec/ruby/core/float/shared/modulo.rb
@@ -0,0 +1,48 @@
+describe :float_modulo, shared: true do
+ it "returns self modulo other" do
+ 6543.21.send(@method, 137).should be_close(104.21, TOLERANCE)
+ 5667.19.send(@method, bignum_value).should be_close(5667.19, TOLERANCE)
+ 6543.21.send(@method, 137.24).should be_close(92.9299999999996, TOLERANCE)
+
+ -1.0.send(@method, 1).should == 0
+ end
+
+ it "returns self when modulus is +Infinity" do
+ 4.2.send(@method, Float::INFINITY).should == 4.2
+ end
+
+ it "returns -Infinity when modulus is -Infinity" do
+ 4.2.send(@method, -Float::INFINITY).should == -Float::INFINITY
+ end
+
+ it "returns NaN when called on NaN or Infinities" do
+ Float::NAN.send(@method, 42).should be_nan
+ Float::INFINITY.send(@method, 42).should be_nan
+ (-Float::INFINITY).send(@method, 42).should be_nan
+ end
+
+ it "returns NaN when modulus is NaN" do
+ 4.2.send(@method, Float::NAN).should be_nan
+ end
+
+ it "returns -0.0 when called on -0.0 with a non zero modulus" do
+ r = (-0.0).send(@method, 42)
+ r.should == 0
+ (1/r).should < 0
+
+ r = (-0.0).send(@method, Float::INFINITY)
+ r.should == 0
+ (1/r).should < 0
+ end
+
+ it "tries to coerce the modulus" do
+ obj = mock("modulus")
+ obj.should_receive(:coerce).with(1.25).and_return([1.25, 0.5])
+ (1.25 % obj).should == 0.25
+ end
+
+ it "raises a ZeroDivisionError if other is zero" do
+ -> { 1.0.send(@method, 0) }.should raise_error(ZeroDivisionError)
+ -> { 1.0.send(@method, 0.0) }.should raise_error(ZeroDivisionError)
+ end
+end
diff --git a/spec/ruby/core/float/shared/quo.rb b/spec/ruby/core/float/shared/quo.rb
new file mode 100644
index 0000000000..0c90171b87
--- /dev/null
+++ b/spec/ruby/core/float/shared/quo.rb
@@ -0,0 +1,59 @@
+describe :float_quo, shared: true do
+ it "performs floating-point division between self and an Integer" do
+ 8.9.send(@method, 7).should == 1.2714285714285716
+ end
+
+ it "performs floating-point division between self and an Integer" do
+ 8.9.send(@method, 9999999999999**9).should == 8.900000000008011e-117
+ end
+
+ it "performs floating-point division between self and a Float" do
+ 2827.22.send(@method, 872.111111).should == 3.2418116961704433
+ end
+
+ it "returns NaN when the argument is NaN" do
+ -1819.999999.send(@method, nan_value).nan?.should be_true
+ 11109.1981271.send(@method, nan_value).nan?.should be_true
+ end
+
+ it "returns Infinity when the argument is 0.0" do
+ 2827.22.send(@method, 0.0).infinite?.should == 1
+ end
+
+ it "returns -Infinity when the argument is 0.0 and self is negative" do
+ -48229.282.send(@method, 0.0).infinite?.should == -1
+ end
+
+ it "returns Infinity when the argument is 0" do
+ 2827.22.send(@method, 0).infinite?.should == 1
+ end
+
+ it "returns -Infinity when the argument is 0 and self is negative" do
+ -48229.282.send(@method, 0).infinite?.should == -1
+ end
+
+ it "returns 0.0 when the argument is Infinity" do
+ 47292.2821.send(@method, infinity_value).should == 0.0
+ end
+
+ it "returns -0.0 when the argument is -Infinity" do
+ 1.9999918.send(@method, -infinity_value).should == -0.0
+ end
+
+ it "performs floating-point division between self and a Rational" do
+ 74620.09.send(@method, Rational(2,3)).should == 111930.135
+ end
+
+ it "performs floating-point division between self and a Complex" do
+ 74620.09.send(@method, Complex(8,2)).should == Complex(
+ 8778.834117647059, -2194.7085294117646)
+ end
+
+ it "raises a TypeError when argument isn't numeric" do
+ -> { 27292.2.send(@method, mock('non-numeric')) }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError when passed multiple arguments" do
+ -> { 272.221.send(@method, 6,0.2) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/float/shared/to_i.rb b/spec/ruby/core/float/shared/to_i.rb
new file mode 100644
index 0000000000..33b32ca533
--- /dev/null
+++ b/spec/ruby/core/float/shared/to_i.rb
@@ -0,0 +1,14 @@
+describe :float_to_i, shared: true do
+ it "returns self truncated to an Integer" do
+ 899.2.send(@method).should eql(899)
+ -1.122256e-45.send(@method).should eql(0)
+ 5_213_451.9201.send(@method).should eql(5213451)
+ 1.233450999123389e+12.send(@method).should eql(1233450999123)
+ -9223372036854775808.1.send(@method).should eql(-9223372036854775808)
+ 9223372036854775808.1.send(@method).should eql(9223372036854775808)
+ end
+
+ it "raises a FloatDomainError for NaN" do
+ -> { nan_value.send(@method) }.should raise_error(FloatDomainError)
+ end
+end
diff --git a/spec/ruby/core/float/shared/to_s.rb b/spec/ruby/core/float/shared/to_s.rb
new file mode 100644
index 0000000000..0925efc0f7
--- /dev/null
+++ b/spec/ruby/core/float/shared/to_s.rb
@@ -0,0 +1,308 @@
+describe :float_to_s, shared: true do
+ it "returns 'NaN' for NaN" do
+ nan_value().send(@method).should == 'NaN'
+ end
+
+ it "returns 'Infinity' for positive infinity" do
+ infinity_value().send(@method).should == 'Infinity'
+ end
+
+ it "returns '-Infinity' for negative infinity" do
+ (-infinity_value()).send(@method).should == '-Infinity'
+ end
+
+ it "returns '0.0' for 0.0" do
+ 0.0.send(@method).should == "0.0"
+ end
+
+ platform_is_not :openbsd do
+ it "emits '-' for -0.0" do
+ -0.0.send(@method).should == "-0.0"
+ end
+ end
+
+ it "emits a '-' for negative values" do
+ -3.14.send(@method).should == "-3.14"
+ end
+
+ it "emits a trailing '.0' for a whole number" do
+ 50.0.send(@method).should == "50.0"
+ end
+
+ it "emits a trailing '.0' for the mantissa in e format" do
+ 1.0e20.send(@method).should == "1.0e+20"
+ end
+
+ it "uses non-e format for a positive value with fractional part having 5 significant figures" do
+ 0.0001.send(@method).should == "0.0001"
+ end
+
+ it "uses non-e format for a negative value with fractional part having 5 significant figures" do
+ -0.0001.send(@method).should == "-0.0001"
+ end
+
+ it "uses e format for a positive value with fractional part having 6 significant figures" do
+ 0.00001.send(@method).should == "1.0e-05"
+ end
+
+ it "uses e format for a negative value with fractional part having 6 significant figures" do
+ -0.00001.send(@method).should == "-1.0e-05"
+ end
+
+ it "uses non-e format for a positive value with whole part having 15 significant figures" do
+ 10000000000000.0.send(@method).should == "10000000000000.0"
+ end
+
+ it "uses non-e format for a negative value with whole part having 15 significant figures" do
+ -10000000000000.0.send(@method).should == "-10000000000000.0"
+ end
+
+ it "uses non-e format for a positive value with whole part having 16 significant figures" do
+ 100000000000000.0.send(@method).should == "100000000000000.0"
+ end
+
+ it "uses non-e format for a negative value with whole part having 16 significant figures" do
+ -100000000000000.0.send(@method).should == "-100000000000000.0"
+ end
+
+ it "uses e format for a positive value with whole part having 18 significant figures" do
+ 10000000000000000.0.send(@method).should == "1.0e+16"
+ end
+
+ it "uses e format for a negative value with whole part having 18 significant figures" do
+ -10000000000000000.0.send(@method).should == "-1.0e+16"
+ end
+
+ it "uses e format for a positive value with whole part having 17 significant figures" do
+ 1000000000000000.0.send(@method).should == "1.0e+15"
+ end
+
+ it "uses e format for a negative value with whole part having 17 significant figures" do
+ -1000000000000000.0.send(@method).should == "-1.0e+15"
+ end
+
+ # #3273
+ it "outputs the minimal, unique form necessary to recreate the value" do
+ value = 0.21611564636388508
+ string = "0.21611564636388508"
+
+ value.send(@method).should == string
+ string.to_f.should == value
+ end
+
+ it "outputs the minimal, unique form to represent the value" do
+ 0.56.send(@method).should == "0.56"
+ end
+
+ describe "matches" do
+ it "random examples in all ranges" do
+ # 50.times do
+ # bytes = (0...8).map { rand(256) }
+ # string = bytes.pack('C8')
+ # float = string.unpack('D').first
+ # puts "#{'%.20g' % float}.send(@method).should == #{float.send(@method).inspect}"
+ # end
+
+ 2.5540217314354050325e+163.send(@method).should == "2.554021731435405e+163"
+ 2.5492588360356597544e-172.send(@method).should == "2.5492588360356598e-172"
+ 1.742770260934704852e-82.send(@method).should == "1.7427702609347049e-82"
+ 6.2108093676180883209e-104.send(@method).should == "6.210809367618088e-104"
+ -3.3448803488331067402e-143.send(@method).should == "-3.3448803488331067e-143"
+ -2.2740074343500832557e-168.send(@method).should == "-2.2740074343500833e-168"
+ 7.0587971678048535732e+191.send(@method).should == "7.058797167804854e+191"
+ -284438.88327586348169.send(@method).should == "-284438.8832758635"
+ 3.953272468476091301e+105.send(@method).should == "3.9532724684760913e+105"
+ -3.6361359552959847853e+100.send(@method).should == "-3.636135955295985e+100"
+ -1.3222325865575206185e-31.send(@method).should == "-1.3222325865575206e-31"
+ 1.1440138916932761366e+130.send(@method).should == "1.1440138916932761e+130"
+ 4.8750891560387561157e-286.send(@method).should == "4.875089156038756e-286"
+ 5.6101113356591453525e-257.send(@method).should == "5.610111335659145e-257"
+ -3.829644279545809575e-100.send(@method).should == "-3.8296442795458096e-100"
+ 1.5342839401396406117e-194.send(@method).should == "1.5342839401396406e-194"
+ 2.2284972755169921402e-144.send(@method).should == "2.228497275516992e-144"
+ 2.1825655917065601737e-61.send(@method).should == "2.1825655917065602e-61"
+ -2.6672271363524338322e-62.send(@method).should == "-2.667227136352434e-62"
+ -1.9257995160119059415e+21.send(@method).should == "-1.925799516011906e+21"
+ -8.9096732962887121718e-198.send(@method).should == "-8.909673296288712e-198"
+ 2.0202075376548644959e-90.send(@method).should == "2.0202075376548645e-90"
+ -7.7341602581786258961e-266.send(@method).should == "-7.734160258178626e-266"
+ 3.5134482598733635046e+98.send(@method).should == "3.5134482598733635e+98"
+ -2.124411722371029134e+154.send(@method).should == "-2.124411722371029e+154"
+ -4.573908787355718687e+110.send(@method).should == "-4.573908787355719e+110"
+ -1.9344425934170969879e-232.send(@method).should == "-1.934442593417097e-232"
+ -1.3274227399979271095e+171.send(@method).should == "-1.3274227399979271e+171"
+ 9.3495270482104442383e-283.send(@method).should == "9.349527048210444e-283"
+ -4.2046059371986483233e+307.send(@method).should == "-4.2046059371986483e+307"
+ 3.6133547278583543004e-117.send(@method).should == "3.613354727858354e-117"
+ 4.9247416523566613499e-08.send(@method).should == "4.9247416523566613e-08"
+ 1.6936145488250064007e-71.send(@method).should == "1.6936145488250064e-71"
+ 2.4455483206829433098e+96.send(@method).should == "2.4455483206829433e+96"
+ 7.9797449851436455384e+124.send(@method).should == "7.979744985143646e+124"
+ -1.3873689634457876774e-129.send(@method).should == "-1.3873689634457877e-129"
+ 3.9761102037533483075e+284.send(@method).should == "3.976110203753348e+284"
+ -4.2819791952139402486e-303.send(@method).should == "-4.28197919521394e-303"
+ -5.7981017546689831298e-116.send(@method).should == "-5.798101754668983e-116"
+ -3.953266497860534199e-28.send(@method).should == "-3.953266497860534e-28"
+ -2.0659852720290440959e-243.send(@method).should == "-2.065985272029044e-243"
+ 8.9670488995878688018e-05.send(@method).should == "8.967048899587869e-05"
+ -1.2317943708113061768e-98.send(@method).should == "-1.2317943708113062e-98"
+ -3.8930768307633080463e+248.send(@method).should == "-3.893076830763308e+248"
+ 6.5854032671803925627e-239.send(@method).should == "6.5854032671803926e-239"
+ 4.6257022188980878952e+177.send(@method).should == "4.625702218898088e+177"
+ -1.9397155125507235603e-187.send(@method).should == "-1.9397155125507236e-187"
+ 8.5752156951245705056e+117.send(@method).should == "8.57521569512457e+117"
+ -2.4784875958162501671e-132.send(@method).should == "-2.4784875958162502e-132"
+ -4.4125691841230058457e-203.send(@method).should == "-4.412569184123006e-203"
+ end
+
+ it "random examples in human ranges" do
+ # 50.times do
+ # formatted = ''
+ # rand(1..3).times do
+ # formatted << rand(10).to_s
+ # end
+ # formatted << '.'
+ # rand(1..9).times do
+ # formatted << rand(10).to_s
+ # end
+ # float = formatted.to_f
+ # puts "#{'%.20f' % float}.send(@method).should == #{float.send(@method).inspect}"
+ # end
+
+ 5.17869899999999994122.send(@method).should == "5.178699"
+ 905.62695729999995819526.send(@method).should == "905.6269573"
+ 62.75999999999999801048.send(@method).should == "62.76"
+ 6.93856795800000014651.send(@method).should == "6.938567958"
+ 4.95999999999999996447.send(@method).should == "4.96"
+ 32.77993899999999882766.send(@method).should == "32.779939"
+ 544.12756779999995160324.send(@method).should == "544.1275678"
+ 66.25801119999999855281.send(@method).should == "66.2580112"
+ 7.90000000000000035527.send(@method).should == "7.9"
+ 5.93100000000000004974.send(@method).should == "5.931"
+ 5.21229313600000043749.send(@method).should == "5.212293136"
+ 503.44173809000000119340.send(@method).should == "503.44173809"
+ 79.26000000000000511591.send(@method).should == "79.26"
+ 8.51524999999999998579.send(@method).should == "8.51525"
+ 174.00000000000000000000.send(@method).should == "174.0"
+ 50.39580000000000126192.send(@method).should == "50.3958"
+ 35.28999999999999914735.send(@method).should == "35.29"
+ 5.43136675399999990788.send(@method).should == "5.431366754"
+ 654.07680000000004838512.send(@method).should == "654.0768"
+ 6.07423700000000010846.send(@method).should == "6.074237"
+ 102.25779799999999397642.send(@method).should == "102.257798"
+ 5.08129999999999970584.send(@method).should == "5.0813"
+ 6.00000000000000000000.send(@method).should == "6.0"
+ 8.30000000000000071054.send(@method).should == "8.3"
+ 32.68345999999999662577.send(@method).should == "32.68346"
+ 581.11170000000004165486.send(@method).should == "581.1117"
+ 76.31342999999999676675.send(@method).should == "76.31343"
+ 438.30826000000001840817.send(@method).should == "438.30826"
+ 482.06631994000002805478.send(@method).should == "482.06631994"
+ 55.92721026899999969828.send(@method).should == "55.927210269"
+ 4.00000000000000000000.send(@method).should == "4.0"
+ 55.86693999999999959982.send(@method).should == "55.86694"
+ 787.98299999999994724931.send(@method).should == "787.983"
+ 5.73810511000000023074.send(@method).should == "5.73810511"
+ 74.51926810000000500622.send(@method).should == "74.5192681"
+ 892.89999999999997726263.send(@method).should == "892.9"
+ 68.27299999999999613465.send(@method).should == "68.273"
+ 904.10000000000002273737.send(@method).should == "904.1"
+ 5.23200000000000020606.send(@method).should == "5.232"
+ 4.09628000000000014325.send(@method).should == "4.09628"
+ 46.05152633699999853434.send(@method).should == "46.051526337"
+ 142.12884990599999923688.send(@method).should == "142.128849906"
+ 3.83057023500000015659.send(@method).should == "3.830570235"
+ 11.81684594699999912848.send(@method).should == "11.816845947"
+ 80.50000000000000000000.send(@method).should == "80.5"
+ 382.18215010000000120272.send(@method).should == "382.1821501"
+ 55.38444606899999911320.send(@method).should == "55.384446069"
+ 5.78000000000000024869.send(@method).should == "5.78"
+ 2.88244999999999995666.send(@method).should == "2.88245"
+ 43.27709999999999723741.send(@method).should == "43.2771"
+ end
+
+ it "random values from divisions" do
+ (1.0 / 7).send(@method).should == "0.14285714285714285"
+
+ # 50.times do
+ # a = rand(10)
+ # b = rand(10)
+ # c = rand(10)
+ # d = rand(10)
+ # expression = "#{a}.#{b} / #{c}.#{d}"
+ # puts " (#{expression}).send(@method).should == #{eval(expression).send(@method).inspect}"
+ # end
+
+ (1.1 / 7.1).send(@method).should == "0.15492957746478875"
+ (6.5 / 8.8).send(@method).should == "0.7386363636363635"
+ (4.8 / 4.3).send(@method).should == "1.1162790697674418"
+ (4.0 / 1.9).send(@method).should == "2.1052631578947367"
+ (9.1 / 0.8).send(@method).should == "11.374999999999998"
+ (5.3 / 7.5).send(@method).should == "0.7066666666666667"
+ (2.8 / 1.8).send(@method).should == "1.5555555555555554"
+ (2.1 / 2.5).send(@method).should == "0.8400000000000001"
+ (3.5 / 6.0).send(@method).should == "0.5833333333333334"
+ (4.6 / 0.3).send(@method).should == "15.333333333333332"
+ (0.6 / 2.4).send(@method).should == "0.25"
+ (1.3 / 9.1).send(@method).should == "0.14285714285714288"
+ (0.3 / 5.0).send(@method).should == "0.06"
+ (5.0 / 4.2).send(@method).should == "1.1904761904761905"
+ (3.0 / 2.0).send(@method).should == "1.5"
+ (6.3 / 2.0).send(@method).should == "3.15"
+ (5.4 / 6.0).send(@method).should == "0.9"
+ (9.6 / 8.1).send(@method).should == "1.1851851851851851"
+ (8.7 / 1.6).send(@method).should == "5.437499999999999"
+ (1.9 / 7.8).send(@method).should == "0.24358974358974358"
+ (0.5 / 2.1).send(@method).should == "0.23809523809523808"
+ (9.3 / 5.8).send(@method).should == "1.6034482758620692"
+ (2.7 / 8.0).send(@method).should == "0.3375"
+ (9.7 / 7.8).send(@method).should == "1.2435897435897436"
+ (8.1 / 2.4).send(@method).should == "3.375"
+ (7.7 / 2.7).send(@method).should == "2.8518518518518516"
+ (7.9 / 1.7).send(@method).should == "4.647058823529412"
+ (6.5 / 8.2).send(@method).should == "0.7926829268292683"
+ (7.8 / 9.6).send(@method).should == "0.8125"
+ (2.2 / 4.6).send(@method).should == "0.47826086956521746"
+ (0.0 / 1.0).send(@method).should == "0.0"
+ (8.3 / 2.9).send(@method).should == "2.8620689655172415"
+ (3.1 / 6.1).send(@method).should == "0.5081967213114754"
+ (2.8 / 7.8).send(@method).should == "0.358974358974359"
+ (8.0 / 0.1).send(@method).should == "80.0"
+ (1.7 / 6.4).send(@method).should == "0.265625"
+ (1.8 / 5.4).send(@method).should == "0.3333333333333333"
+ (8.0 / 5.8).send(@method).should == "1.3793103448275863"
+ (5.2 / 4.1).send(@method).should == "1.2682926829268295"
+ (9.8 / 5.8).send(@method).should == "1.6896551724137934"
+ (5.4 / 9.5).send(@method).should == "0.5684210526315789"
+ (8.4 / 4.9).send(@method).should == "1.7142857142857142"
+ (1.7 / 3.5).send(@method).should == "0.4857142857142857"
+ (1.2 / 5.1).send(@method).should == "0.23529411764705882"
+ (1.4 / 2.0).send(@method).should == "0.7"
+ (4.8 / 8.0).send(@method).should == "0.6"
+ (9.0 / 2.5).send(@method).should == "3.6"
+ (0.2 / 0.6).send(@method).should == "0.33333333333333337"
+ (7.8 / 5.2).send(@method).should == "1.5"
+ (9.5 / 5.5).send(@method).should == "1.7272727272727273"
+ end
+ end
+
+ describe 'encoding' do
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ 1.23.send(@method).encoding.should equal(Encoding::US_ASCII)
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do
+ Encoding.default_internal = Encoding::IBM437
+ 5.47.send(@method).encoding.should equal(Encoding::US_ASCII)
+ end
+ end
+end
diff --git a/spec/ruby/core/float/to_f_spec.rb b/spec/ruby/core/float/to_f_spec.rb
new file mode 100644
index 0000000000..6677556cd9
--- /dev/null
+++ b/spec/ruby/core/float/to_f_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Float#to_f" do
+ it "returns self" do
+ -500.3.to_f.should == -500.3
+ 267.51.to_f.should == 267.51
+ 1.1412.to_f.should == 1.1412
+ end
+end
diff --git a/spec/ruby/core/float/to_i_spec.rb b/spec/ruby/core/float/to_i_spec.rb
new file mode 100644
index 0000000000..91d84c5fa3
--- /dev/null
+++ b/spec/ruby/core/float/to_i_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Float#to_i" do
+ it_behaves_like :float_to_i, :to_i
+end
diff --git a/spec/ruby/core/float/to_int_spec.rb b/spec/ruby/core/float/to_int_spec.rb
new file mode 100644
index 0000000000..084a58b431
--- /dev/null
+++ b/spec/ruby/core/float/to_int_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Float#to_int" do
+ it_behaves_like :float_to_i, :to_int
+end
diff --git a/spec/ruby/core/float/to_r_spec.rb b/spec/ruby/core/float/to_r_spec.rb
new file mode 100644
index 0000000000..907ff08f27
--- /dev/null
+++ b/spec/ruby/core/float/to_r_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Float#to_r" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/float/to_s_spec.rb b/spec/ruby/core/float/to_s_spec.rb
new file mode 100644
index 0000000000..6727a883f8
--- /dev/null
+++ b/spec/ruby/core/float/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Float#to_s" do
+ it_behaves_like :float_to_s, :to_s
+end
diff --git a/spec/ruby/core/float/truncate_spec.rb b/spec/ruby/core/float/truncate_spec.rb
new file mode 100644
index 0000000000..2c80145f9f
--- /dev/null
+++ b/spec/ruby/core/float/truncate_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Float#truncate" do
+ it_behaves_like :float_to_i, :truncate
+
+ it "returns self truncated to an optionally given precision" do
+ 2.1679.truncate(0).should eql(2)
+ 7.1.truncate(1).should eql(7.1)
+ 214.94.truncate(-1).should eql(210)
+ -1.234.truncate(2).should eql(-1.23)
+ 5.123812.truncate(4).should eql(5.1238)
+ end
+end
diff --git a/spec/ruby/core/float/uminus_spec.rb b/spec/ruby/core/float/uminus_spec.rb
new file mode 100644
index 0000000000..57bae0fb4b
--- /dev/null
+++ b/spec/ruby/core/float/uminus_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+
+describe "Float#-@" do
+ it "negates self" do
+ (2.221.send(:-@)).should be_close(-2.221, TOLERANCE)
+ -2.01.should be_close(-2.01,TOLERANCE)
+ -2_455_999_221.5512.should be_close(-2455999221.5512, TOLERANCE)
+ (--5.5).should be_close(5.5, TOLERANCE)
+ -8.551.send(:-@).should be_close(8.551, TOLERANCE)
+ end
+
+ it "negates self at Float boundaries" do
+ Float::MAX.send(:-@).should be_close(0.0 - Float::MAX, TOLERANCE)
+ Float::MIN.send(:-@).should be_close(0.0 - Float::MIN, TOLERANCE)
+ end
+
+ it "returns negative infinity for positive infinity" do
+ infinity_value.send(:-@).infinite?.should == -1
+ end
+
+ it "returns positive infinity for negative infinity" do
+ (-infinity_value).send(:-@).infinite?.should == 1
+ end
+
+ it "returns NaN for NaN" do
+ nan_value.send(:-@).should.nan?
+ end
+end
diff --git a/spec/ruby/core/float/uplus_spec.rb b/spec/ruby/core/float/uplus_spec.rb
new file mode 100644
index 0000000000..936123558c
--- /dev/null
+++ b/spec/ruby/core/float/uplus_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Float#+@" do
+ it "returns the same value with same sign (twos complement)" do
+ 34.56.send(:+@).should == 34.56
+ -34.56.send(:+@).should == -34.56
+ 0.0.send(:+@).should eql(0.0)
+ end
+end
diff --git a/spec/ruby/core/float/zero_spec.rb b/spec/ruby/core/float/zero_spec.rb
new file mode 100644
index 0000000000..1f3de27793
--- /dev/null
+++ b/spec/ruby/core/float/zero_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Float#zero?" do
+ it "returns true if self is 0.0" do
+ 0.0.should.zero?
+ 1.0.should_not.zero?
+ -1.0.should_not.zero?
+ end
+end
diff --git a/spec/ruby/core/gc/auto_compact_spec.rb b/spec/ruby/core/gc/auto_compact_spec.rb
new file mode 100644
index 0000000000..4f9d043171
--- /dev/null
+++ b/spec/ruby/core/gc/auto_compact_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.0" do
+ describe "GC.auto_compact" do
+ it "can set and get a boolean value" do
+ begin
+ GC.auto_compact = GC.auto_compact
+ rescue NotImplementedError # platform does not support autocompact
+ skip
+ end
+
+ original = GC.auto_compact
+ begin
+ GC.auto_compact = !original
+ rescue NotImplementedError # platform does not support autocompact
+ skip
+ end
+
+ begin
+ GC.auto_compact.should == !original
+ ensure
+ GC.auto_compact = original
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/gc/count_spec.rb b/spec/ruby/core/gc/count_spec.rb
new file mode 100644
index 0000000000..af2fbbe713
--- /dev/null
+++ b/spec/ruby/core/gc/count_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "GC.count" do
+ it "returns an integer" do
+ GC.count.should be_kind_of(Integer)
+ end
+
+ it "increases as collections are run" do
+ count_before = GC.count
+ i = 0
+ while GC.count <= count_before and i < 10
+ GC.start
+ i += 1
+ end
+ GC.count.should > count_before
+ end
+end
diff --git a/spec/ruby/core/gc/disable_spec.rb b/spec/ruby/core/gc/disable_spec.rb
new file mode 100644
index 0000000000..f89a9d2768
--- /dev/null
+++ b/spec/ruby/core/gc/disable_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe "GC.disable" do
+ after :each do
+ GC.enable
+ end
+
+ it "returns true if and only if the garbage collection was previously disabled" do
+ GC.enable
+ GC.disable.should == false
+ GC.disable.should == true
+ GC.disable.should == true
+ GC.enable
+ GC.disable.should == false
+ GC.disable.should == true
+ end
+
+end
diff --git a/spec/ruby/core/gc/enable_spec.rb b/spec/ruby/core/gc/enable_spec.rb
new file mode 100644
index 0000000000..ca4488547a
--- /dev/null
+++ b/spec/ruby/core/gc/enable_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "GC.enable" do
+
+ it "returns true if and only if the garbage collection was already disabled" do
+ GC.enable
+ GC.enable.should == false
+ GC.disable
+ GC.enable.should == true
+ GC.enable.should == false
+ end
+
+end
diff --git a/spec/ruby/core/gc/garbage_collect_spec.rb b/spec/ruby/core/gc/garbage_collect_spec.rb
new file mode 100644
index 0000000000..f67f0486c8
--- /dev/null
+++ b/spec/ruby/core/gc/garbage_collect_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "GC#garbage_collect" do
+
+ before :each do
+ @obj = Object.new
+ @obj.extend(GC)
+ end
+
+ it "always returns nil" do
+ @obj.garbage_collect.should == nil
+ @obj.garbage_collect.should == nil
+ end
+
+end
diff --git a/spec/ruby/core/gc/measure_total_time_spec.rb b/spec/ruby/core/gc/measure_total_time_spec.rb
new file mode 100644
index 0000000000..05d4598ebc
--- /dev/null
+++ b/spec/ruby/core/gc/measure_total_time_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.1" do
+ describe "GC.measure_total_time" do
+ before :each do
+ @default = GC.measure_total_time
+ end
+
+ after :each do
+ GC.measure_total_time = @default
+ end
+
+ it "can set and get a boolean value" do
+ original = GC.measure_total_time
+ GC.measure_total_time = !original
+ GC.measure_total_time.should == !original
+ end
+ end
+end
diff --git a/spec/ruby/core/gc/profiler/clear_spec.rb b/spec/ruby/core/gc/profiler/clear_spec.rb
new file mode 100644
index 0000000000..95c3d4ed65
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/clear_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.clear" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/gc/profiler/disable_spec.rb b/spec/ruby/core/gc/profiler/disable_spec.rb
new file mode 100644
index 0000000000..74089693eb
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/disable_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.disable" do
+ before do
+ @status = GC::Profiler.enabled?
+ end
+
+ after do
+ @status ? GC::Profiler.enable : GC::Profiler.disable
+ end
+
+ it "disables the profiler" do
+ GC::Profiler.disable
+ GC::Profiler.should_not.enabled?
+ end
+end
diff --git a/spec/ruby/core/gc/profiler/enable_spec.rb b/spec/ruby/core/gc/profiler/enable_spec.rb
new file mode 100644
index 0000000000..313ca3d949
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/enable_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.enable" do
+
+ before do
+ @status = GC::Profiler.enabled?
+ end
+
+ after do
+ @status ? GC::Profiler.enable : GC::Profiler.disable
+ end
+
+ it "enables the profiler" do
+ GC::Profiler.enable
+ GC::Profiler.should.enabled?
+ end
+end
diff --git a/spec/ruby/core/gc/profiler/enabled_spec.rb b/spec/ruby/core/gc/profiler/enabled_spec.rb
new file mode 100644
index 0000000000..23677be555
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/enabled_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.enabled?" do
+ before do
+ @status = GC::Profiler.enabled?
+ end
+
+ after do
+ @status ? GC::Profiler.enable : GC::Profiler.disable
+ end
+
+ it "reports as enabled when enabled" do
+ GC::Profiler.enable
+ GC::Profiler.enabled?.should be_true
+ end
+
+ it "reports as disabled when disabled" do
+ GC::Profiler.disable
+ GC::Profiler.enabled?.should be_false
+ end
+end
diff --git a/spec/ruby/core/gc/profiler/report_spec.rb b/spec/ruby/core/gc/profiler/report_spec.rb
new file mode 100644
index 0000000000..732b1d0f51
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/report_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.report" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/gc/profiler/result_spec.rb b/spec/ruby/core/gc/profiler/result_spec.rb
new file mode 100644
index 0000000000..2ab46a8371
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/result_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.result" do
+ it "returns a string" do
+ GC::Profiler.result.should be_kind_of(String)
+ end
+end
diff --git a/spec/ruby/core/gc/profiler/total_time_spec.rb b/spec/ruby/core/gc/profiler/total_time_spec.rb
new file mode 100644
index 0000000000..7709f168dd
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/total_time_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.total_time" do
+ it "returns an float" do
+ GC::Profiler.total_time.should be_kind_of(Float)
+ end
+end
diff --git a/spec/ruby/core/gc/start_spec.rb b/spec/ruby/core/gc/start_spec.rb
new file mode 100644
index 0000000000..c941058969
--- /dev/null
+++ b/spec/ruby/core/gc/start_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "GC.start" do
+ it "always returns nil" do
+ GC.start.should == nil
+ GC.start.should == nil
+ end
+
+ it "accepts keyword arguments" do
+ GC.start(full_mark: true, immediate_sweep: true).should == nil
+ end
+end
diff --git a/spec/ruby/core/gc/stat_spec.rb b/spec/ruby/core/gc/stat_spec.rb
new file mode 100644
index 0000000000..3b43b28a92
--- /dev/null
+++ b/spec/ruby/core/gc/stat_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+
+describe "GC.stat" do
+ it "returns hash of values" do
+ stat = GC.stat
+ stat.should be_kind_of(Hash)
+ stat.keys.should.include?(:count)
+ end
+
+ it "updates the given hash values" do
+ hash = { count: "hello", __other__: "world" }
+ stat = GC.stat(hash)
+
+ stat.should be_kind_of(Hash)
+ stat.should equal hash
+ stat[:count].should be_kind_of(Integer)
+ stat[:__other__].should == "world"
+ end
+
+ it "the values are all Integer since rb_gc_stat() returns size_t" do
+ GC.stat.values.each { |value| value.should be_kind_of(Integer) }
+ end
+
+ it "can return a single value" do
+ GC.stat(:count).should be_kind_of(Integer)
+ end
+
+ it "increases count after GC is run" do
+ count = GC.stat(:count)
+ GC.start
+ GC.stat(:count).should > count
+ end
+
+ it "increases major_gc_count after GC is run" do
+ count = GC.stat(:major_gc_count)
+ GC.start
+ GC.stat(:major_gc_count).should > count
+ end
+
+ it "provides some number for count" do
+ GC.stat(:count).should be_kind_of(Integer)
+ GC.stat[:count].should be_kind_of(Integer)
+ end
+
+ it "provides some number for heap_free_slots" do
+ GC.stat(:heap_free_slots).should be_kind_of(Integer)
+ GC.stat[:heap_free_slots].should be_kind_of(Integer)
+ end
+
+ it "provides some number for total_allocated_objects" do
+ GC.stat(:total_allocated_objects).should be_kind_of(Integer)
+ GC.stat[:total_allocated_objects].should be_kind_of(Integer)
+ end
+
+ it "raises an error if argument is not nil, a symbol, or a hash" do
+ -> { GC.stat(7) }.should raise_error(TypeError, "non-hash or symbol given")
+ end
+
+ it "raises an error if an unknown key is given" do
+ -> { GC.stat(:foo) }.should raise_error(ArgumentError, "unknown key: foo")
+ end
+end
diff --git a/spec/ruby/core/gc/stress_spec.rb b/spec/ruby/core/gc/stress_spec.rb
new file mode 100644
index 0000000000..ca62c0cb4e
--- /dev/null
+++ b/spec/ruby/core/gc/stress_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+describe "GC.stress" do
+ after :each do
+ # make sure that we never leave these tests in stress enabled GC!
+ GC.stress = false
+ end
+
+ it "returns current status of GC stress mode" do
+ GC.stress.should be_false
+ GC.stress = true
+ GC.stress.should be_true
+ GC.stress = false
+ GC.stress.should be_false
+ end
+end
+
+describe "GC.stress=" do
+ after :each do
+ GC.stress = false
+ end
+
+ it "sets the stress mode" do
+ GC.stress = true
+ GC.stress.should be_true
+ end
+end
diff --git a/spec/ruby/core/gc/total_time_spec.rb b/spec/ruby/core/gc/total_time_spec.rb
new file mode 100644
index 0000000000..fcc8f45a83
--- /dev/null
+++ b/spec/ruby/core/gc/total_time_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.1" do
+ describe "GC.total_time" do
+ it "returns an Integer" do
+ GC.total_time.should be_kind_of(Integer)
+ end
+
+ it "increases as collections are run" do
+ time_before = GC.total_time
+ GC.start
+ GC.total_time.should >= time_before
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/allocate_spec.rb b/spec/ruby/core/hash/allocate_spec.rb
new file mode 100644
index 0000000000..93420de866
--- /dev/null
+++ b/spec/ruby/core/hash/allocate_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "Hash.allocate" do
+ it "returns an instance of Hash" do
+ hsh = Hash.allocate
+ hsh.should be_an_instance_of(Hash)
+ end
+
+ it "returns a fully-formed instance of Hash" do
+ hsh = Hash.allocate
+ hsh.size.should == 0
+ hsh[:a] = 1
+ hsh.should == { a: 1 }
+ end
+end
diff --git a/spec/ruby/core/hash/any_spec.rb b/spec/ruby/core/hash/any_spec.rb
new file mode 100644
index 0000000000..c26dfabde6
--- /dev/null
+++ b/spec/ruby/core/hash/any_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+
+describe "Hash#any?" do
+ describe 'with no block given' do
+ it "checks if there are any members of a Hash" do
+ empty_hash = {}
+ empty_hash.should_not.any?
+
+ hash_with_members = { 'key' => 'value' }
+ hash_with_members.should.any?
+ end
+ end
+
+ describe 'with a block given' do
+ it 'is false if the hash is empty' do
+ empty_hash = {}
+ empty_hash.any? {|k,v| 1 == 1 }.should == false
+ end
+
+ it 'is true if the block returns true for any member of the hash' do
+ hash_with_members = { 'a' => false, 'b' => false, 'c' => true, 'd' => false }
+ hash_with_members.any? {|k,v| v == true}.should == true
+ end
+
+ it 'is false if the block returns false for all members of the hash' do
+ hash_with_members = { 'a' => false, 'b' => false, 'c' => true, 'd' => false }
+ hash_with_members.any? {|k,v| v == 42}.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/assoc_spec.rb b/spec/ruby/core/hash/assoc_spec.rb
new file mode 100644
index 0000000000..64442918d1
--- /dev/null
+++ b/spec/ruby/core/hash/assoc_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+
+describe "Hash#assoc" do
+ before :each do
+ @h = {apple: :green, orange: :orange, grape: :green, banana: :yellow}
+ end
+
+ it "returns an Array if the argument is == to a key of the Hash" do
+ @h.assoc(:apple).should be_an_instance_of(Array)
+ end
+
+ it "returns a 2-element Array if the argument is == to a key of the Hash" do
+ @h.assoc(:grape).size.should == 2
+ end
+
+ it "sets the first element of the Array to the located key" do
+ @h.assoc(:banana).first.should == :banana
+ end
+
+ it "sets the last element of the Array to the value of the located key" do
+ @h.assoc(:banana).last.should == :yellow
+ end
+
+ it "only returns the first matching key-value pair for identity hashes" do
+ # Avoid literal String keys in Hash#[]= due to https://bugs.ruby-lang.org/issues/12855
+ h = {}.compare_by_identity
+ k1 = 'pear'
+ h[k1] = :red
+ k2 = 'pear'
+ h[k2] = :green
+ h.size.should == 2
+ h.keys.grep(/pear/).size.should == 2
+ h.assoc('pear').should == ['pear', :red]
+ end
+
+ it "uses #== to compare the argument to the keys" do
+ @h[1.0] = :value
+ 1.should == 1.0
+ @h.assoc(1).should == [1.0, :value]
+ end
+
+ it "returns nil if the argument is not a key of the Hash" do
+ @h.assoc(:green).should be_nil
+ end
+
+ it "returns nil if the argument is not a key of the Hash even when there is a default" do
+ Hash.new(42).merge!( foo: :bar ).assoc(42).should be_nil
+ Hash.new{|h, k| h[k] = 42}.merge!( foo: :bar ).assoc(42).should be_nil
+ end
+end
diff --git a/spec/ruby/core/hash/clear_spec.rb b/spec/ruby/core/hash/clear_spec.rb
new file mode 100644
index 0000000000..cf05e36ac9
--- /dev/null
+++ b/spec/ruby/core/hash/clear_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#clear" do
+ it "removes all key, value pairs" do
+ h = { 1 => 2, 3 => 4 }
+ h.clear.should equal(h)
+ h.should == {}
+ end
+
+ it "does not remove default values" do
+ h = Hash.new(5)
+ h.clear
+ h.default.should == 5
+
+ h = { "a" => 100, "b" => 200 }
+ h.default = "Go fish"
+ h.clear
+ h["z"].should == "Go fish"
+ end
+
+ it "does not remove default procs" do
+ h = Hash.new { 5 }
+ h.clear
+ h.default_proc.should_not == nil
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.clear }.should raise_error(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.clear }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/clone_spec.rb b/spec/ruby/core/hash/clone_spec.rb
new file mode 100644
index 0000000000..6c96fc0c67
--- /dev/null
+++ b/spec/ruby/core/hash/clone_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "Hash#clone" do
+ it "copies instance variable but not the objects they refer to" do
+ hash = { 'key' => 'value' }
+
+ clone = hash.clone
+
+ clone.should == hash
+ clone.should_not equal hash
+ end
+end
diff --git a/spec/ruby/core/hash/compact_spec.rb b/spec/ruby/core/hash/compact_spec.rb
new file mode 100644
index 0000000000..2989afc8b7
--- /dev/null
+++ b/spec/ruby/core/hash/compact_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#compact" do
+ before :each do
+ @hash = { truthy: true, false: false, nil: nil, nil => true }
+ @initial_pairs = @hash.dup
+ @compact = { truthy: true, false: false, nil => true }
+ end
+
+ it "returns new object that rejects pair has nil value" do
+ ret = @hash.compact
+ ret.should_not equal(@hash)
+ ret.should == @compact
+ end
+
+ it "keeps own pairs" do
+ @hash.compact
+ @hash.should == @initial_pairs
+ end
+end
+
+describe "Hash#compact!" do
+ before :each do
+ @hash = { truthy: true, false: false, nil: nil, nil => true }
+ @initial_pairs = @hash.dup
+ @compact = { truthy: true, false: false, nil => true }
+ end
+
+ it "returns self" do
+ @hash.compact!.should equal(@hash)
+ end
+
+ it "rejects own pair has nil value" do
+ @hash.compact!
+ @hash.should == @compact
+ end
+
+ context "when each pair does not have nil value" do
+ before :each do
+ @hash.compact!
+ end
+
+ it "returns nil" do
+ @hash.compact!.should be_nil
+ end
+ end
+
+ describe "on frozen instance" do
+ before :each do
+ @hash.freeze
+ end
+
+ it "keeps pairs and raises a FrozenError" do
+ ->{ @hash.compact! }.should raise_error(FrozenError)
+ @hash.should == @initial_pairs
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/compare_by_identity_spec.rb b/spec/ruby/core/hash/compare_by_identity_spec.rb
new file mode 100644
index 0000000000..874cd46eb7
--- /dev/null
+++ b/spec/ruby/core/hash/compare_by_identity_spec.rb
@@ -0,0 +1,138 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#compare_by_identity" do
+ before :each do
+ @h = {}
+ @idh = {}.compare_by_identity
+ end
+
+ it "causes future comparisons on the receiver to be made by identity" do
+ @h[[1]] = :a
+ @h[[1]].should == :a
+ @h.compare_by_identity
+ @h[[1].dup].should be_nil
+ end
+
+ it "rehashes internally so that old keys can be looked up" do
+ h = {}
+ (1..10).each { |k| h[k] = k }
+ o = Object.new
+ def o.hash; 123; end
+ h[o] = 1
+ h.compare_by_identity
+ h[o].should == 1
+ end
+
+ it "returns self" do
+ h = {}
+ h[:foo] = :bar
+ h.compare_by_identity.should equal h
+ end
+
+ it "has no effect on an already compare_by_identity hash" do
+ @idh[:foo] = :bar
+ @idh.compare_by_identity.should equal @idh
+ @idh.should.compare_by_identity?
+ @idh[:foo].should == :bar
+ end
+
+ it "uses the semantics of BasicObject#equal? to determine key identity" do
+ [1].should_not equal([1])
+ @idh[[1]] = :c
+ @idh[[1]] = :d
+ :bar.should equal(:bar)
+ @idh[:bar] = :e
+ @idh[:bar] = :f
+ @idh.values.should == [:c, :d, :f]
+ end
+
+ it "uses #equal? semantics, but doesn't actually call #equal? to determine identity" do
+ obj = mock('equal')
+ obj.should_not_receive(:equal?)
+ @idh[:foo] = :glark
+ @idh[obj] = :a
+ @idh[obj].should == :a
+ end
+
+ it "does not call #hash on keys" do
+ key = HashSpecs::ByIdentityKey.new
+ @idh[key] = 1
+ @idh[key].should == 1
+ end
+
+ it "regards #dup'd objects as having different identities" do
+ key = ['foo']
+ @idh[key.dup] = :str
+ @idh[key].should be_nil
+ end
+
+ it "regards #clone'd objects as having different identities" do
+ key = ['foo']
+ @idh[key.clone] = :str
+ @idh[key].should be_nil
+ end
+
+ it "regards references to the same object as having the same identity" do
+ o = Object.new
+ @h[o] = :o
+ @h[:a] = :a
+ @h[o].should == :o
+ end
+
+ it "raises a FrozenError on frozen hashes" do
+ @h = @h.freeze
+ -> { @h.compare_by_identity }.should raise_error(FrozenError)
+ end
+
+ # Behaviour confirmed in bug #1871
+ it "persists over #dups" do
+ @idh['foo'] = :bar
+ @idh['foo'] = :glark
+ @idh.dup.should == @idh
+ @idh.dup.size.should == @idh.size
+ end
+
+ it "persists over #clones" do
+ @idh['foo'] = :bar
+ @idh['foo'] = :glark
+ @idh.clone.should == @idh
+ @idh.clone.size.should == @idh.size
+ end
+
+ it "does not copy string keys" do
+ foo = 'foo'
+ @idh[foo] = true
+ @idh[foo] = true
+ @idh.size.should == 1
+ @idh.keys.first.should equal foo
+ end
+
+ it "gives different identity for string literals" do
+ @idh['foo'] = 1
+ @idh['foo'] = 2
+ @idh.values.should == [1, 2]
+ @idh.size.should == 2
+ end
+end
+
+describe "Hash#compare_by_identity?" do
+ it "returns false by default" do
+ h = {}
+ h.compare_by_identity?.should be_false
+ end
+
+ it "returns true once #compare_by_identity has been invoked on self" do
+ h = {}
+ h.compare_by_identity
+ h.compare_by_identity?.should be_true
+ end
+
+ it "returns true when called multiple times on the same ident hash" do
+ h = {}
+ h.compare_by_identity
+ h.compare_by_identity?.should be_true
+ h.compare_by_identity?.should be_true
+ h.compare_by_identity?.should be_true
+ end
+end
diff --git a/spec/ruby/core/hash/constructor_spec.rb b/spec/ruby/core/hash/constructor_spec.rb
new file mode 100644
index 0000000000..8fba47958f
--- /dev/null
+++ b/spec/ruby/core/hash/constructor_spec.rb
@@ -0,0 +1,110 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash.[]" do
+ describe "passed zero arguments" do
+ it "returns an empty hash" do
+ Hash[].should == {}
+ end
+ end
+
+ it "creates a Hash; values can be provided as the argument list" do
+ Hash[:a, 1, :b, 2].should == { a: 1, b: 2 }
+ Hash[].should == {}
+ Hash[:a, 1, :b, { c: 2 }].should == { a: 1, b: { c: 2 } }
+ end
+
+ it "creates a Hash; values can be provided as one single hash" do
+ Hash[a: 1, b: 2].should == { a: 1, b: 2 }
+ Hash[{1 => 2, 3 => 4}].should == {1 => 2, 3 => 4}
+ Hash[{}].should == {}
+ end
+
+ describe "passed an array" do
+ it "treats elements that are 2 element arrays as key and value" do
+ Hash[[[:a, :b], [:c, :d]]].should == { a: :b, c: :d }
+ end
+
+ it "treats elements that are 1 element arrays as keys with value nil" do
+ Hash[[[:a]]].should == { a: nil }
+ end
+ end
+
+ # #1000 #1385
+ it "creates a Hash; values can be provided as a list of value-pairs in an array" do
+ Hash[[[:a, 1], [:b, 2]]].should == { a: 1, b: 2 }
+ end
+
+ it "coerces a single argument which responds to #to_ary" do
+ ary = mock('to_ary')
+ ary.should_receive(:to_ary).and_return([[:a, :b]])
+
+ Hash[ary].should == { a: :b }
+ end
+
+ it "raises for elements that are not arrays" do
+ -> {
+ Hash[[:a]].should == {}
+ }.should raise_error(ArgumentError)
+ -> {
+ Hash[[:nil]].should == {}
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for arrays of more than 2 elements" do
+ ->{ Hash[[[:a, :b, :c]]].should == {} }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed a list of value-invalid-pairs in an array" do
+ -> {
+ -> {
+ Hash[[[:a, 1], [:b], 42, [:d, 2], [:e, 2, 3], []]]
+ }.should complain(/ignoring wrong elements/)
+ }.should raise_error(ArgumentError)
+ end
+
+ describe "passed a single argument which responds to #to_hash" do
+ it "coerces it and returns a copy" do
+ h = { a: :b, c: :d }
+ to_hash = mock('to_hash')
+ to_hash.should_receive(:to_hash).and_return(h)
+
+ result = Hash[to_hash]
+ result.should == h
+ result.should_not equal(h)
+ end
+ end
+
+ it "raises an ArgumentError when passed an odd number of arguments" do
+ -> { Hash[1, 2, 3] }.should raise_error(ArgumentError)
+ -> { Hash[1, 2, { 3 => 4 }] }.should raise_error(ArgumentError)
+ end
+
+ it "calls to_hash" do
+ obj = mock('x')
+ def obj.to_hash() { 1 => 2, 3 => 4 } end
+ Hash[obj].should == { 1 => 2, 3 => 4 }
+ end
+
+ it "returns an instance of a subclass when passed an Array" do
+ HashSpecs::MyHash[1,2,3,4].should be_an_instance_of(HashSpecs::MyHash)
+ end
+
+ it "returns instances of subclasses" do
+ HashSpecs::MyHash[].should be_an_instance_of(HashSpecs::MyHash)
+ end
+
+ it "returns an instance of the class it's called on" do
+ Hash[HashSpecs::MyHash[1, 2]].class.should == Hash
+ HashSpecs::MyHash[Hash[1, 2]].should be_an_instance_of(HashSpecs::MyHash)
+ end
+
+ it "does not call #initialize on the subclass instance" do
+ HashSpecs::MyInitializerHash[Hash[1, 2]].should be_an_instance_of(HashSpecs::MyInitializerHash)
+ end
+
+ it "removes the default_proc" do
+ hash = Hash.new { |h, k| h[k] = [] }
+ Hash[hash].default_proc.should be_nil
+ end
+end
diff --git a/spec/ruby/core/hash/deconstruct_keys_spec.rb b/spec/ruby/core/hash/deconstruct_keys_spec.rb
new file mode 100644
index 0000000000..bbcd8932e5
--- /dev/null
+++ b/spec/ruby/core/hash/deconstruct_keys_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "Hash#deconstruct_keys" do
+ it "returns self" do
+ hash = {a: 1, b: 2}
+
+ hash.deconstruct_keys([:a, :b]).should equal hash
+ end
+
+ it "requires one argument" do
+ -> {
+ {a: 1}.deconstruct_keys
+ }.should raise_error(ArgumentError, /wrong number of arguments \(given 0, expected 1\)/)
+ end
+
+ it "ignores argument" do
+ hash = {a: 1, b: 2}
+
+ hash.deconstruct_keys([:a]).should == {a: 1, b: 2}
+ hash.deconstruct_keys(0 ).should == {a: 1, b: 2}
+ hash.deconstruct_keys('' ).should == {a: 1, b: 2}
+ end
+end
diff --git a/spec/ruby/core/hash/default_proc_spec.rb b/spec/ruby/core/hash/default_proc_spec.rb
new file mode 100644
index 0000000000..f4e4803632
--- /dev/null
+++ b/spec/ruby/core/hash/default_proc_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#default_proc" do
+ it "returns the block passed to Hash.new" do
+ h = Hash.new { 'Paris' }
+ p = h.default_proc
+ p.call(1).should == 'Paris'
+ end
+
+ it "returns nil if no block was passed to proc" do
+ {}.default_proc.should == nil
+ end
+end
+
+describe "Hash#default_proc=" do
+ it "replaces the block passed to Hash.new" do
+ h = Hash.new { 'Paris' }
+ h.default_proc = Proc.new { 'Montreal' }
+ p = h.default_proc
+ p.call(1).should == 'Montreal'
+ end
+
+ it "uses :to_proc on its argument" do
+ h = Hash.new { 'Paris' }
+ obj = mock('to_proc')
+ obj.should_receive(:to_proc).and_return(Proc.new { 'Montreal' })
+ (h.default_proc = obj).should equal(obj)
+ h[:cool_city].should == 'Montreal'
+ end
+
+ it "overrides the static default" do
+ h = Hash.new(42)
+ h.default_proc = Proc.new { 6 }
+ h.default.should be_nil
+ h.default_proc.call.should == 6
+ end
+
+ it "raises an error if passed stuff not convertible to procs" do
+ ->{{}.default_proc = 42}.should raise_error(TypeError)
+ end
+
+ it "returns the passed Proc" do
+ new_proc = Proc.new {}
+ ({}.default_proc = new_proc).should equal(new_proc)
+ end
+
+ it "clears the default proc if passed nil" do
+ h = Hash.new { 'Paris' }
+ h.default_proc = nil
+ h.default_proc.should == nil
+ h[:city].should == nil
+ end
+
+ it "returns nil if passed nil" do
+ ({}.default_proc = nil).should be_nil
+ end
+
+ it "accepts a lambda with an arity of 2" do
+ h = {}
+ -> do
+ h.default_proc = -> a, b { }
+ end.should_not raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed a lambda with an arity other than 2" do
+ h = {}
+ -> do
+ h.default_proc = -> a { }
+ end.should raise_error(TypeError)
+ -> do
+ h.default_proc = -> a, b, c { }
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ -> { {}.freeze.default_proc = Proc.new {} }.should raise_error(FrozenError)
+ -> { {}.freeze.default_proc = nil }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/default_spec.rb b/spec/ruby/core/hash/default_spec.rb
new file mode 100644
index 0000000000..d8b62ea196
--- /dev/null
+++ b/spec/ruby/core/hash/default_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#default" do
+ it "returns the default value" do
+ h = Hash.new(5)
+ h.default.should == 5
+ h.default(4).should == 5
+ {}.default.should == nil
+ {}.default(4).should == nil
+ end
+
+ it "uses the default proc to compute a default value, passing given key" do
+ h = Hash.new { |*args| args }
+ h.default(nil).should == [h, nil]
+ h.default(5).should == [h, 5]
+ end
+
+ it "calls default proc with nil arg if passed a default proc but no arg" do
+ h = Hash.new { |*args| args }
+ h.default.should == nil
+ end
+end
+
+describe "Hash#default=" do
+ it "sets the default value" do
+ h = {}
+ h.default = 99
+ h.default.should == 99
+ end
+
+ it "unsets the default proc" do
+ [99, nil, -> { 6 }].each do |default|
+ h = Hash.new { 5 }
+ h.default_proc.should_not == nil
+ h.default = default
+ h.default.should == default
+ h.default_proc.should == nil
+ end
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.default = nil }.should raise_error(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.default = nil }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/delete_if_spec.rb b/spec/ruby/core/hash/delete_if_spec.rb
new file mode 100644
index 0000000000..c9e670ffc3
--- /dev/null
+++ b/spec/ruby/core/hash/delete_if_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#delete_if" do
+ it "yields two arguments: key and value" do
+ all_args = []
+ { 1 => 2, 3 => 4 }.delete_if { |*args| all_args << args }
+ all_args.sort.should == [[1, 2], [3, 4]]
+ end
+
+ it "removes every entry for which block is true and returns self" do
+ h = { a: 1, b: 2, c: 3, d: 4 }
+ h.delete_if { |k,v| v % 2 == 1 }.should equal(h)
+ h.should == { b: 2, d: 4 }
+ end
+
+ it "removes all entries if the block is true" do
+ h = { a: 1, b: 2, c: 3 }
+ h.delete_if { |k,v| true }.should equal(h)
+ h.should == {}
+ end
+
+ it "processes entries with the same order as each()" do
+ h = { a: 1, b: 2, c: 3, d: 4 }
+
+ each_pairs = []
+ delete_pairs = []
+
+ h.each_pair { |k,v| each_pairs << [k, v] }
+ h.delete_if { |k,v| delete_pairs << [k,v] }
+
+ each_pairs.should == delete_pairs
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.delete_if { false } }.should raise_error(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.delete_if { true } }.should raise_error(FrozenError)
+ end
+
+ it_behaves_like :hash_iteration_no_block, :delete_if
+ it_behaves_like :enumeratorized_with_origin_size, :delete_if, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/delete_spec.rb b/spec/ruby/core/hash/delete_spec.rb
new file mode 100644
index 0000000000..b262e8846b
--- /dev/null
+++ b/spec/ruby/core/hash/delete_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#delete" do
+ it "removes the entry and returns the deleted value" do
+ h = { a: 5, b: 2 }
+ h.delete(:b).should == 2
+ h.should == { a: 5 }
+ end
+
+ it "calls supplied block if the key is not found" do
+ { a: 1, b: 10, c: 100 }.delete(:d) { 5 }.should == 5
+ Hash.new(:default).delete(:d) { 5 }.should == 5
+ Hash.new { :default }.delete(:d) { 5 }.should == 5
+ end
+
+ it "returns nil if the key is not found when no block is given" do
+ { a: 1, b: 10, c: 100 }.delete(:d).should == nil
+ Hash.new(:default).delete(:d).should == nil
+ Hash.new { :default }.delete(:d).should == nil
+ end
+
+ # MRI explicitly implements this behavior
+ it "allows removing a key while iterating" do
+ h = { a: 1, b: 2 }
+ visited = []
+ h.each_pair { |k,v|
+ visited << k
+ h.delete(k)
+ }
+ visited.should == [:a, :b]
+ h.should == {}
+ end
+
+ it "accepts keys with private #hash method" do
+ key = HashSpecs::KeyWithPrivateHash.new
+ { key => 5 }.delete(key).should == 5
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.delete("foo") }.should raise_error(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.delete("foo") }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/dig_spec.rb b/spec/ruby/core/hash/dig_spec.rb
new file mode 100644
index 0000000000..aa0ecadd2f
--- /dev/null
+++ b/spec/ruby/core/hash/dig_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../spec_helper'
+
+describe "Hash#dig" do
+
+ it "returns #[] with one arg" do
+ h = { 0 => false, a: 1 }
+ h.dig(:a).should == 1
+ h.dig(0).should be_false
+ h.dig(1).should be_nil
+ end
+
+ it "returns the nested value specified by the sequence of keys" do
+ h = { foo: { bar: { baz: 1 } } }
+ h.dig(:foo, :bar, :baz).should == 1
+ h.dig(:foo, :bar, :nope).should be_nil
+ h.dig(:foo, :baz).should be_nil
+ h.dig(:bar, :baz, :foo).should be_nil
+ end
+
+ it "returns the nested value specified if the sequence includes an index" do
+ h = { foo: [1, 2, 3] }
+ h.dig(:foo, 2).should == 3
+ end
+
+ it "returns nil if any intermediate step is nil" do
+ h = { foo: { bar: { baz: 1 } } }
+ h.dig(:foo, :zot, :xyz).should == nil
+ end
+
+ it "raises an ArgumentError if no arguments provided" do
+ -> { { the: 'borg' }.dig() }.should raise_error(ArgumentError)
+ end
+
+ it "handles type-mixed deep digging" do
+ h = {}
+ h[:foo] = [ { bar: [ 1 ] }, [ obj = Object.new, 'str' ] ]
+ def obj.dig(*args); [ 42 ] end
+
+ h.dig(:foo, 0, :bar).should == [ 1 ]
+ h.dig(:foo, 0, :bar, 0).should == 1
+ h.dig(:foo, 1, 1).should == 'str'
+ # MRI does not recurse values returned from `obj.dig`
+ h.dig(:foo, 1, 0, 0).should == [ 42 ]
+ h.dig(:foo, 1, 0, 0, 10).should == [ 42 ]
+ end
+
+ it "raises TypeError if an intermediate element does not respond to #dig" do
+ h = {}
+ h[:foo] = [ { bar: [ 1 ] }, [ nil, 'str' ] ]
+ -> { h.dig(:foo, 0, :bar, 0, 0) }.should raise_error(TypeError)
+ -> { h.dig(:foo, 1, 1, 0) }.should raise_error(TypeError)
+ end
+
+ it "calls #dig on the result of #[] with the remaining arguments" do
+ h = { foo: { bar: { baz: 42 } } }
+ h[:foo].should_receive(:dig).with(:bar, :baz).and_return(42)
+ h.dig(:foo, :bar, :baz).should == 42
+ end
+
+ it "respects Hash's default" do
+ default = {bar: 42}
+ h = Hash.new(default)
+ h.dig(:foo).should equal default
+ h.dig(:foo, :bar).should == 42
+ end
+end
diff --git a/spec/ruby/core/hash/each_key_spec.rb b/spec/ruby/core/hash/each_key_spec.rb
new file mode 100644
index 0000000000..c84dd696d5
--- /dev/null
+++ b/spec/ruby/core/hash/each_key_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#each_key" do
+ it "calls block once for each key, passing key" do
+ r = {}
+ h = { 1 => -1, 2 => -2, 3 => -3, 4 => -4 }
+ h.each_key { |k| r[k] = k }.should equal(h)
+ r.should == { 1 => 1, 2 => 2, 3 => 3, 4 => 4 }
+ end
+
+ it "processes keys in the same order as keys()" do
+ keys = []
+ h = { 1 => -1, 2 => -2, 3 => -3, 4 => -4 }
+ h.each_key { |k| keys << k }
+ keys.should == h.keys
+ end
+
+ it_behaves_like :hash_iteration_no_block, :each_key
+ it_behaves_like :enumeratorized_with_origin_size, :each_key, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/each_pair_spec.rb b/spec/ruby/core/hash/each_pair_spec.rb
new file mode 100644
index 0000000000..eb6656681d
--- /dev/null
+++ b/spec/ruby/core/hash/each_pair_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative 'shared/each'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#each_pair" do
+ it_behaves_like :hash_each, :each_pair
+ it_behaves_like :hash_iteration_no_block, :each_pair
+ it_behaves_like :enumeratorized_with_origin_size, :each_pair, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/each_spec.rb b/spec/ruby/core/hash/each_spec.rb
new file mode 100644
index 0000000000..f0de0bdee5
--- /dev/null
+++ b/spec/ruby/core/hash/each_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative 'shared/each'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#each" do
+ it_behaves_like :hash_each, :each
+ it_behaves_like :hash_iteration_no_block, :each
+ it_behaves_like :enumeratorized_with_origin_size, :each, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/each_value_spec.rb b/spec/ruby/core/hash/each_value_spec.rb
new file mode 100644
index 0000000000..19b076730d
--- /dev/null
+++ b/spec/ruby/core/hash/each_value_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#each_value" do
+ it "calls block once for each key, passing value" do
+ r = []
+ h = { a: -5, b: -3, c: -2, d: -1, e: -1 }
+ h.each_value { |v| r << v }.should equal(h)
+ r.sort.should == [-5, -3, -2, -1, -1]
+ end
+
+ it "processes values in the same order as values()" do
+ values = []
+ h = { a: -5, b: -3, c: -2, d: -1, e: -1 }
+ h.each_value { |v| values << v }
+ values.should == h.values
+ end
+
+ it_behaves_like :hash_iteration_no_block, :each_value
+ it_behaves_like :enumeratorized_with_origin_size, :each_value, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/element_reference_spec.rb b/spec/ruby/core/hash/element_reference_spec.rb
new file mode 100644
index 0000000000..e271f37ea6
--- /dev/null
+++ b/spec/ruby/core/hash/element_reference_spec.rb
@@ -0,0 +1,134 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#[]" do
+ it "returns the value for key" do
+ obj = mock('x')
+ h = { 1 => 2, 3 => 4, "foo" => "bar", obj => obj, [] => "baz" }
+ h[1].should == 2
+ h[3].should == 4
+ h["foo"].should == "bar"
+ h[obj].should == obj
+ h[[]].should == "baz"
+ end
+
+ it "returns nil as default default value" do
+ { 0 => 0 }[5].should == nil
+ end
+
+ it "returns the default (immediate) value for missing keys" do
+ h = Hash.new(5)
+ h[:a].should == 5
+ h[:a] = 0
+ h[:a].should == 0
+ h[:b].should == 5
+ end
+
+ it "calls subclass implementations of default" do
+ h = HashSpecs::DefaultHash.new
+ h[:nothing].should == 100
+ end
+
+ it "does not create copies of the immediate default value" do
+ str = "foo"
+ h = Hash.new(str)
+ a = h[:a]
+ b = h[:b]
+ a << "bar"
+
+ a.should equal(b)
+ a.should == "foobar"
+ b.should == "foobar"
+ end
+
+ it "returns the default (dynamic) value for missing keys" do
+ h = Hash.new { |hsh, k| k.kind_of?(Numeric) ? hsh[k] = k + 2 : hsh[k] = k }
+ h[1].should == 3
+ h['this'].should == 'this'
+ h.should == { 1 => 3, 'this' => 'this' }
+
+ i = 0
+ h = Hash.new { |hsh, key| i += 1 }
+ h[:foo].should == 1
+ h[:foo].should == 2
+ h[:bar].should == 3
+ end
+
+ it "does not return default values for keys with nil values" do
+ h = Hash.new(5)
+ h[:a] = nil
+ h[:a].should == nil
+
+ h = Hash.new { 5 }
+ h[:a] = nil
+ h[:a].should == nil
+ end
+
+ it "compares keys with eql? semantics" do
+ { 1.0 => "x" }[1].should == nil
+ { 1.0 => "x" }[1.0].should == "x"
+ { 1 => "x" }[1.0].should == nil
+ { 1 => "x" }[1].should == "x"
+ end
+
+ it "compares key via hash" do
+ x = mock('0')
+ x.should_receive(:hash).and_return(0)
+
+ h = {}
+ # 1.9 only calls #hash if the hash had at least one entry beforehand.
+ h[:foo] = :bar
+ h[x].should == nil
+ end
+
+ it "does not compare keys with different #hash values via #eql?" do
+ x = mock('x')
+ x.should_not_receive(:eql?)
+ x.stub!(:hash).and_return(0)
+
+ y = mock('y')
+ y.should_not_receive(:eql?)
+ y.stub!(:hash).and_return(1)
+
+ { y => 1 }[x].should == nil
+ end
+
+ it "compares keys with the same #hash value via #eql?" do
+ x = mock('x')
+ x.should_receive(:eql?).and_return(true)
+ x.stub!(:hash).and_return(42)
+
+ y = mock('y')
+ y.should_not_receive(:eql?)
+ y.stub!(:hash).and_return(42)
+
+ { y => 1 }[x].should == 1
+ end
+
+ it "finds a value via an identical key even when its #eql? isn't reflexive" do
+ x = mock('x')
+ x.should_receive(:hash).at_least(1).and_return(42)
+ x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI.
+
+ { x => :x }[x].should == :x
+ end
+
+ it "supports keys with private #hash method" do
+ key = HashSpecs::KeyWithPrivateHash.new
+ { key => 42 }[key].should == 42
+ end
+
+ it "does not dispatch to hash for Boolean, Integer, Float, String, or Symbol" do
+ code = <<-EOC
+ load '#{fixture __FILE__, "name.rb"}'
+ hash = { true => 42, false => 42, 1 => 42, 2.0 => 42, "hello" => 42, :ok => 42 }
+ [true, false, 1, 2.0, "hello", :ok].each do |value|
+ raise "incorrect value" unless hash[value] == 42
+ end
+ puts "Ok."
+ EOC
+ result = ruby_exe(code, args: "2>&1")
+ result.should == "Ok.\n"
+ end
+
+end
diff --git a/spec/ruby/core/hash/element_set_spec.rb b/spec/ruby/core/hash/element_set_spec.rb
new file mode 100644
index 0000000000..67c5a04d73
--- /dev/null
+++ b/spec/ruby/core/hash/element_set_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/store'
+
+describe "Hash#[]=" do
+ it_behaves_like :hash_store, :[]=
+end
diff --git a/spec/ruby/core/hash/empty_spec.rb b/spec/ruby/core/hash/empty_spec.rb
new file mode 100644
index 0000000000..881e1cc34b
--- /dev/null
+++ b/spec/ruby/core/hash/empty_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#empty?" do
+ it "returns true if the hash has no entries" do
+ {}.should.empty?
+ { 1 => 1 }.should_not.empty?
+ end
+
+ it "returns true if the hash has no entries and has a default value" do
+ Hash.new(5).should.empty?
+ Hash.new { 5 }.should.empty?
+ Hash.new { |hsh, k| hsh[k] = k }.should.empty?
+ end
+end
diff --git a/spec/ruby/core/hash/eql_spec.rb b/spec/ruby/core/hash/eql_spec.rb
new file mode 100644
index 0000000000..9013e12ffd
--- /dev/null
+++ b/spec/ruby/core/hash/eql_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/eql'
+
+describe "Hash#eql?" do
+ it_behaves_like :hash_eql, :eql?
+ it_behaves_like :hash_eql_additional, :eql?
+ it_behaves_like :hash_eql_additional_more, :eql?
+end
diff --git a/spec/ruby/core/hash/equal_value_spec.rb b/spec/ruby/core/hash/equal_value_spec.rb
new file mode 100644
index 0000000000..ae13a42679
--- /dev/null
+++ b/spec/ruby/core/hash/equal_value_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/eql'
+
+describe "Hash#==" do
+ it_behaves_like :hash_eql, :==
+ it_behaves_like :hash_eql_additional, :==
+ it_behaves_like :hash_eql_additional_more, :==
+
+ it "compares values with == semantics" do
+ l_val = mock("left")
+ r_val = mock("right")
+
+ l_val.should_receive(:==).with(r_val).and_return(true)
+
+ ({ 1 => l_val } == { 1 => r_val }).should be_true
+ end
+end
diff --git a/spec/ruby/core/hash/except_spec.rb b/spec/ruby/core/hash/except_spec.rb
new file mode 100644
index 0000000000..82cfced72f
--- /dev/null
+++ b/spec/ruby/core/hash/except_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.0" do
+ describe "Hash#except" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns a new duplicate hash without arguments" do
+ ret = @hash.except
+ ret.should_not equal(@hash)
+ ret.should == @hash
+ end
+
+ it "returns a hash without the requested subset" do
+ @hash.except(:c, :a).should == { b: 2 }
+ end
+
+ it "ignores keys not present in the original hash" do
+ @hash.except(:a, :chunky_bacon).should == { b: 2, c: 3 }
+ end
+
+ it "always returns a Hash without a default" do
+ klass = Class.new(Hash)
+ h = klass.new(:default)
+ h[:bar] = 12
+ h[:foo] = 42
+ r = h.except(:foo)
+ r.should == {bar: 12}
+ r.class.should == Hash
+ r.default.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/fetch_spec.rb b/spec/ruby/core/hash/fetch_spec.rb
new file mode 100644
index 0000000000..753167f709
--- /dev/null
+++ b/spec/ruby/core/hash/fetch_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/hash/key_error'
+
+describe "Hash#fetch" do
+ context "when the key is not found" do
+ it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new(a: 5)
+ it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, {}
+ it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new { 5 }
+ it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new(5)
+
+ it "formats the object with #inspect in the KeyError message" do
+ -> {
+ {}.fetch('foo')
+ }.should raise_error(KeyError, 'key not found: "foo"')
+ end
+ end
+
+ it "returns the value for key" do
+ { a: 1, b: -1 }.fetch(:b).should == -1
+ end
+
+ it "returns default if key is not found when passed a default" do
+ {}.fetch(:a, nil).should == nil
+ {}.fetch(:a, 'not here!').should == "not here!"
+ { a: nil }.fetch(:a, 'not here!').should == nil
+ end
+
+ it "returns value of block if key is not found when passed a block" do
+ {}.fetch('a') { |k| k + '!' }.should == "a!"
+ end
+
+ it "gives precedence to the default block over the default argument when passed both" do
+ -> {
+ @result = {}.fetch(9, :foo) { |i| i * i }
+ }.should complain(/block supersedes default value argument/)
+ @result.should == 81
+ end
+
+ it "raises an ArgumentError when not passed one or two arguments" do
+ -> { {}.fetch() }.should raise_error(ArgumentError)
+ -> { {}.fetch(1, 2, 3) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/hash/fetch_values_spec.rb b/spec/ruby/core/hash/fetch_values_spec.rb
new file mode 100644
index 0000000000..af3673f6ef
--- /dev/null
+++ b/spec/ruby/core/hash/fetch_values_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/hash/key_error'
+
+describe "Hash#fetch_values" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ end
+
+ describe "with matched keys" do
+ it "returns the values for keys" do
+ @hash.fetch_values(:a).should == [1]
+ @hash.fetch_values(:a, :c).should == [1, 3]
+ end
+
+ it "returns the values for keys ordered in the order of the requested keys" do
+ @hash.fetch_values(:c, :a).should == [3, 1]
+ end
+ end
+
+ describe "with unmatched keys" do
+ it_behaves_like :key_error, -> obj, key { obj.fetch_values(key) }, Hash.new(a: 5)
+
+ it "returns the default value from block" do
+ @hash.fetch_values(:z) { |key| "`#{key}' is not found" }.should == ["`z' is not found"]
+ @hash.fetch_values(:a, :z) { |key| "`#{key}' is not found" }.should == [1, "`z' is not found"]
+ end
+ end
+
+ describe "without keys" do
+ it "returns an empty Array" do
+ @hash.fetch_values.should == []
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/filter_spec.rb b/spec/ruby/core/hash/filter_spec.rb
new file mode 100644
index 0000000000..7dabe44984
--- /dev/null
+++ b/spec/ruby/core/hash/filter_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/select'
+
+describe "Hash#filter" do
+ it_behaves_like :hash_select, :filter
+end
+
+describe "Hash#filter!" do
+ it_behaves_like :hash_select!, :filter!
+end
diff --git a/spec/ruby/core/hash/fixtures/classes.rb b/spec/ruby/core/hash/fixtures/classes.rb
new file mode 100644
index 0000000000..ae907aaff6
--- /dev/null
+++ b/spec/ruby/core/hash/fixtures/classes.rb
@@ -0,0 +1,75 @@
+module HashSpecs
+ class MyHash < Hash; end
+
+ class MyInitializerHash < Hash
+
+ def initialize
+ raise "Constructor called"
+ end
+
+ end
+
+ class NewHash < Hash
+ def initialize(*args)
+ args.each_with_index do |val, index|
+ self[index] = val
+ end
+ end
+ end
+
+ class SubHashSettingInInitialize < Hash
+ def initialize(*args, &block)
+ self[:foo] = :bar
+ super(*args, &block)
+ end
+ end
+
+ class DefaultHash < Hash
+ def default(key)
+ 100
+ end
+ end
+
+ class ToHashHash < Hash
+ def to_hash
+ { "to_hash" => "was", "called!" => "duh." }
+ end
+ end
+
+ class KeyWithPrivateHash
+ private :hash
+ end
+
+ class ByIdentityKey
+ def hash
+ fail("#hash should not be called on compare_by_identity Hash")
+ end
+ end
+
+ class ByValueKey
+ attr_reader :n
+ def initialize(n)
+ @n = n
+ end
+
+ def hash
+ n
+ end
+
+ def eql? other
+ ByValueKey === other and @n == other.n
+ end
+ end
+
+ def self.empty_frozen_hash
+ @empty ||= {}
+ @empty.freeze
+ @empty
+ end
+
+ def self.frozen_hash
+ @hash ||= { 1 => 2, 3 => 4 }
+ @hash.freeze
+ @hash
+ end
+end
diff --git a/spec/ruby/core/hash/fixtures/name.rb b/spec/ruby/core/hash/fixtures/name.rb
new file mode 100644
index 0000000000..b203bf6ae4
--- /dev/null
+++ b/spec/ruby/core/hash/fixtures/name.rb
@@ -0,0 +1,30 @@
+class TrueClass
+ def hash
+ raise "TrueClass#hash should not be called"
+ end
+end
+class FalseClass
+ def hash
+ raise "FalseClass#hash should not be called"
+ end
+end
+class Integer
+ def hash
+ raise "Integer#hash should not be called"
+ end
+end
+class Float
+ def hash
+ raise "Float#hash should not be called"
+ end
+end
+class String
+ def hash
+ raise "String#hash should not be called"
+ end
+end
+class Symbol
+ def hash
+ raise "Symbol#hash should not be called"
+ end
+end
diff --git a/spec/ruby/core/hash/flatten_spec.rb b/spec/ruby/core/hash/flatten_spec.rb
new file mode 100644
index 0000000000..825da15bfc
--- /dev/null
+++ b/spec/ruby/core/hash/flatten_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+
+describe "Hash#flatten" do
+
+ before :each do
+ @h = {plato: :greek,
+ witgenstein: [:austrian, :british],
+ russell: :welsh}
+ end
+
+ it "returns an Array" do
+ {}.flatten.should be_an_instance_of(Array)
+ end
+
+ it "returns an empty Array for an empty Hash" do
+ {}.flatten.should == []
+ end
+
+ it "sets each even index of the Array to a key of the Hash" do
+ a = @h.flatten
+ a[0].should == :plato
+ a[2].should == :witgenstein
+ a[4].should == :russell
+ end
+
+ it "sets each odd index of the Array to the value corresponding to the previous element" do
+ a = @h.flatten
+ a[1].should == :greek
+ a[3].should == [:austrian, :british]
+ a[5].should == :welsh
+ end
+
+ it "does not recursively flatten Array values when called without arguments" do
+ a = @h.flatten
+ a[3].should == [:austrian, :british]
+ end
+
+ it "does not recursively flatten Hash values when called without arguments" do
+ @h[:russell] = {born: :wales, influenced_by: :mill }
+ a = @h.flatten
+ a[5].should_not == {born: :wales, influenced_by: :mill }.flatten
+ end
+
+ it "recursively flattens Array values when called with an argument >= 2" do
+ a = @h.flatten(2)
+ a[3].should == :austrian
+ a[4].should == :british
+ end
+
+ it "recursively flattens Array values to the given depth" do
+ @h[:russell] = [[:born, :wales], [:influenced_by, :mill]]
+ a = @h.flatten(2)
+ a[6].should == [:born, :wales]
+ a[7].should == [:influenced_by, :mill]
+ end
+
+ it "raises a TypeError if given a non-Integer argument" do
+ -> do
+ @h.flatten(Object.new)
+ end.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/hash/gt_spec.rb b/spec/ruby/core/hash/gt_spec.rb
new file mode 100644
index 0000000000..cd541d4d83
--- /dev/null
+++ b/spec/ruby/core/hash/gt_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison'
+require_relative 'shared/greater_than'
+
+describe "Hash#>" do
+ it_behaves_like :hash_comparison, :>
+ it_behaves_like :hash_greater_than, :>
+
+ it "returns false if both hashes are identical" do
+ h = { a: 1, b: 2 }
+ (h > h).should be_false
+ end
+end
+
+describe "Hash#>" do
+ before :each do
+ @hash = {a:1, b:2}
+ @bigger = {a:1, b:2, c:3}
+ @unrelated = {c:3, d:4}
+ @similar = {a:2, b:3}
+ end
+
+ it "returns false when receiver size is smaller than argument" do
+ (@hash > @bigger).should == false
+ (@unrelated > @bigger).should == false
+ end
+
+ it "returns false when receiver size is the same as argument" do
+ (@hash > @hash).should == false
+ (@hash > @unrelated).should == false
+ (@unrelated > @hash).should == false
+ end
+
+ it "returns true when argument is a subset of receiver" do
+ (@bigger > @hash).should == true
+ end
+
+ it "returns false when keys match but values don't" do
+ (@hash > @similar).should == false
+ (@similar > @hash).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/gte_spec.rb b/spec/ruby/core/hash/gte_spec.rb
new file mode 100644
index 0000000000..99b89e7217
--- /dev/null
+++ b/spec/ruby/core/hash/gte_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison'
+require_relative 'shared/greater_than'
+
+describe "Hash#>=" do
+ it_behaves_like :hash_comparison, :>=
+ it_behaves_like :hash_greater_than, :>=
+
+ it "returns true if both hashes are identical" do
+ h = { a: 1, b: 2 }
+ (h >= h).should be_true
+ end
+end
+
+describe "Hash#>=" do
+ before :each do
+ @hash = {a:1, b:2}
+ @bigger = {a:1, b:2, c:3}
+ @unrelated = {c:3, d:4}
+ @similar = {a:2, b:3}
+ end
+
+ it "returns false when receiver size is smaller than argument" do
+ (@hash >= @bigger).should == false
+ (@unrelated >= @bigger).should == false
+ end
+
+ it "returns false when argument is not a subset or not equals to receiver" do
+ (@hash >= @unrelated).should == false
+ (@unrelated >= @hash).should == false
+ end
+
+ it "returns true when argument is a subset of receiver or equals to receiver" do
+ (@bigger >= @hash).should == true
+ (@hash >= @hash).should == true
+ end
+
+ it "returns false when keys match but values don't" do
+ (@hash >= @similar).should == false
+ (@similar >= @hash).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/has_key_spec.rb b/spec/ruby/core/hash/has_key_spec.rb
new file mode 100644
index 0000000000..4af53579e5
--- /dev/null
+++ b/spec/ruby/core/hash/has_key_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/key'
+
+describe "Hash#has_key?" do
+ it_behaves_like :hash_key_p, :has_key?
+end
diff --git a/spec/ruby/core/hash/has_value_spec.rb b/spec/ruby/core/hash/has_value_spec.rb
new file mode 100644
index 0000000000..39f1627fd3
--- /dev/null
+++ b/spec/ruby/core/hash/has_value_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/value'
+
+describe "Hash#has_value?" do
+ it_behaves_like :hash_value_p, :has_value?
+end
diff --git a/spec/ruby/core/hash/hash_spec.rb b/spec/ruby/core/hash/hash_spec.rb
new file mode 100644
index 0000000000..2ccb483120
--- /dev/null
+++ b/spec/ruby/core/hash/hash_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+
+describe "Hash" do
+ it "includes Enumerable" do
+ Hash.include?(Enumerable).should == true
+ end
+end
+
+describe "Hash#hash" do
+ it "returns a value which doesn't depend on the hash order" do
+ { 0=>2, 11=>1 }.hash.should == { 11=>1, 0=>2 }.hash
+ end
+
+ it "returns a value in which element values do not cancel each other out" do
+ { a: 2, b: 2 }.hash.should_not == { a: 7, b: 7 }.hash
+ end
+
+ it "returns a value in which element keys and values do not cancel each other out" do
+ { :a => :a }.hash.should_not == { :b => :b }.hash
+ end
+
+ it "generates a hash for recursive hash structures" do
+ h = {}
+ h[:a] = h
+ (h.hash == h[:a].hash).should == true
+ end
+
+ it "returns the same hash for recursive hashes" do
+ h = {} ; h[:x] = h
+ h.hash.should == {x: h}.hash
+ h.hash.should == {x: {x: h}}.hash
+ # This is because h.eql?(x: h)
+ # Remember that if two objects are eql?
+ # then the need to have the same hash.
+ # Check the Hash#eql? specs!
+ end
+
+ it "returns the same hash for recursive hashes through arrays" do
+ h = {} ; rec = [h] ; h[:x] = rec
+ h.hash.should == {x: rec}.hash
+ h.hash.should == {x: [h]}.hash
+ # Like above, because h.eql?(x: [h])
+ end
+
+ ruby_version_is "3.1" do
+ it "allows ommiting values" do
+ a = 1
+ b = 2
+
+ eval('{a:, b:}.should == { a: 1, b: 2 }')
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/include_spec.rb b/spec/ruby/core/hash/include_spec.rb
new file mode 100644
index 0000000000..f3959dc589
--- /dev/null
+++ b/spec/ruby/core/hash/include_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/key'
+
+describe "Hash#include?" do
+ it_behaves_like :hash_key_p, :include?
+end
diff --git a/spec/ruby/core/hash/index_spec.rb b/spec/ruby/core/hash/index_spec.rb
new file mode 100644
index 0000000000..be4e2cc6ab
--- /dev/null
+++ b/spec/ruby/core/hash/index_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/index'
+
+ruby_version_is ''...'3.0' do
+ describe "Hash#index" do
+ it_behaves_like :hash_index, :index
+ end
+end
diff --git a/spec/ruby/core/hash/initialize_spec.rb b/spec/ruby/core/hash/initialize_spec.rb
new file mode 100644
index 0000000000..d13496ba3b
--- /dev/null
+++ b/spec/ruby/core/hash/initialize_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#initialize" do
+ it "is private" do
+ Hash.should have_private_instance_method("initialize")
+ end
+
+ it "can be used to reset default_proc" do
+ h = { "foo" => 1, "bar" => 2 }
+ h.default_proc.should == nil
+ h.send(:initialize) { |_, k| k * 2 }
+ h.default_proc.should_not == nil
+ h["a"].should == "aa"
+ end
+
+ it "can be used to reset the default value" do
+ h = {}
+ h.default = 42
+ h.default.should == 42
+ h.send(:initialize, 1)
+ h.default.should == 1
+ h.send(:initialize)
+ h.default.should == nil
+ end
+
+ it "receives the arguments passed to Hash#new" do
+ HashSpecs::NewHash.new(:one, :two)[0].should == :one
+ HashSpecs::NewHash.new(:one, :two)[1].should == :two
+ end
+
+ it "does not change the storage, only the default value or proc" do
+ h = HashSpecs::SubHashSettingInInitialize.new
+ h.to_a.should == [[:foo, :bar]]
+
+ h = HashSpecs::SubHashSettingInInitialize.new(:default)
+ h.to_a.should == [[:foo, :bar]]
+
+ h = HashSpecs::SubHashSettingInInitialize.new { :default_block }
+ h.to_a.should == [[:foo, :bar]]
+ end
+
+ it "returns self" do
+ h = Hash.new
+ h.send(:initialize).should equal(h)
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ block = -> { HashSpecs.frozen_hash.instance_eval { initialize() }}
+ block.should raise_error(FrozenError)
+
+ block = -> { HashSpecs.frozen_hash.instance_eval { initialize(nil) } }
+ block.should raise_error(FrozenError)
+
+ block = -> { HashSpecs.frozen_hash.instance_eval { initialize(5) } }
+ block.should raise_error(FrozenError)
+
+ block = -> { HashSpecs.frozen_hash.instance_eval { initialize { 5 } } }
+ block.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/inspect_spec.rb b/spec/ruby/core/hash/inspect_spec.rb
new file mode 100644
index 0000000000..f41ebb70a6
--- /dev/null
+++ b/spec/ruby/core/hash/inspect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/to_s'
+
+describe "Hash#inspect" do
+ it_behaves_like :hash_to_s, :inspect
+end
diff --git a/spec/ruby/core/hash/invert_spec.rb b/spec/ruby/core/hash/invert_spec.rb
new file mode 100644
index 0000000000..73377a9e97
--- /dev/null
+++ b/spec/ruby/core/hash/invert_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#invert" do
+ it "returns a new hash where keys are values and vice versa" do
+ { 1 => 'a', 2 => 'b', 3 => 'c' }.invert.should ==
+ { 'a' => 1, 'b' => 2, 'c' => 3 }
+ end
+
+ it "handles collisions by overriding with the key coming later in keys()" do
+ h = { a: 1, b: 1 }
+ override_key = h.keys.last
+ h.invert[1].should == override_key
+ end
+
+ it "compares new keys with eql? semantics" do
+ h = { a: 1.0, b: 1 }
+ i = h.invert
+ i[1.0].should == :a
+ i[1].should == :b
+ end
+
+ it "does not return subclass instances for subclasses" do
+ HashSpecs::MyHash[1 => 2, 3 => 4].invert.class.should == Hash
+ HashSpecs::MyHash[].invert.class.should == Hash
+ end
+end
diff --git a/spec/ruby/core/hash/keep_if_spec.rb b/spec/ruby/core/hash/keep_if_spec.rb
new file mode 100644
index 0000000000..d50d969467
--- /dev/null
+++ b/spec/ruby/core/hash/keep_if_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#keep_if" do
+ it "yields two arguments: key and value" do
+ all_args = []
+ { 1 => 2, 3 => 4 }.keep_if { |*args| all_args << args }
+ all_args.should == [[1, 2], [3, 4]]
+ end
+
+ it "keeps every entry for which block is true and returns self" do
+ h = { a: 1, b: 2, c: 3, d: 4 }
+ h.keep_if { |k,v| v % 2 == 0 }.should equal(h)
+ h.should == { b: 2, d: 4 }
+ end
+
+ it "removes all entries if the block is false" do
+ h = { a: 1, b: 2, c: 3 }
+ h.keep_if { |k,v| false }.should equal(h)
+ h.should == {}
+ end
+
+ it "returns self even if unmodified" do
+ h = { 1 => 2, 3 => 4 }
+ h.keep_if { true }.should equal(h)
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.keep_if { true } }.should raise_error(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.keep_if { false } }.should raise_error(FrozenError)
+ end
+
+ it_behaves_like :hash_iteration_no_block, :keep_if
+ it_behaves_like :enumeratorized_with_origin_size, :keep_if, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/key_spec.rb b/spec/ruby/core/hash/key_spec.rb
new file mode 100644
index 0000000000..73eecbc98e
--- /dev/null
+++ b/spec/ruby/core/hash/key_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/key'
+require_relative 'shared/index'
+
+describe "Hash#key?" do
+ it_behaves_like :hash_key_p, :key?
+end
+
+describe "Hash#key" do
+ it_behaves_like :hash_index, :key
+end
diff --git a/spec/ruby/core/hash/keys_spec.rb b/spec/ruby/core/hash/keys_spec.rb
new file mode 100644
index 0000000000..9a067085e5
--- /dev/null
+++ b/spec/ruby/core/hash/keys_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#keys" do
+
+ it "returns an array with the keys in the order they were inserted" do
+ {}.keys.should == []
+ {}.keys.should be_kind_of(Array)
+ Hash.new(5).keys.should == []
+ Hash.new { 5 }.keys.should == []
+ { 1 => 2, 4 => 8, 2 => 4 }.keys.should == [1, 4, 2]
+ { 1 => 2, 2 => 4, 4 => 8 }.keys.should be_kind_of(Array)
+ { nil => nil }.keys.should == [nil]
+ end
+
+ it "uses the same order as #values" do
+ h = { 1 => "1", 2 => "2", 3 => "3", 4 => "4" }
+
+ h.size.times do |i|
+ h[h.keys[i]].should == h.values[i]
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/length_spec.rb b/spec/ruby/core/hash/length_spec.rb
new file mode 100644
index 0000000000..d0af0945df
--- /dev/null
+++ b/spec/ruby/core/hash/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/length'
+
+describe "Hash#length" do
+ it_behaves_like :hash_length, :length
+end
diff --git a/spec/ruby/core/hash/lt_spec.rb b/spec/ruby/core/hash/lt_spec.rb
new file mode 100644
index 0000000000..2219615880
--- /dev/null
+++ b/spec/ruby/core/hash/lt_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison'
+require_relative 'shared/less_than'
+
+describe "Hash#<" do
+ it_behaves_like :hash_comparison, :<
+ it_behaves_like :hash_less_than, :<
+
+ it "returns false if both hashes are identical" do
+ h = { a: 1, b: 2 }
+ (h < h).should be_false
+ end
+end
+
+describe "Hash#<" do
+ before :each do
+ @hash = {a:1, b:2}
+ @bigger = {a:1, b:2, c:3}
+ @unrelated = {c:3, d:4}
+ @similar = {a:2, b:3}
+ end
+
+ it "returns false when receiver size is larger than argument" do
+ (@bigger < @hash).should == false
+ (@bigger < @unrelated).should == false
+ end
+
+ it "returns false when receiver size is the same as argument" do
+ (@hash < @hash).should == false
+ (@hash < @unrelated).should == false
+ (@unrelated < @hash).should == false
+ end
+
+ it "returns true when receiver is a subset of argument" do
+ (@hash < @bigger).should == true
+ end
+
+ it "returns false when keys match but values don't" do
+ (@hash < @similar).should == false
+ (@similar < @hash).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/lte_spec.rb b/spec/ruby/core/hash/lte_spec.rb
new file mode 100644
index 0000000000..a166e5bca4
--- /dev/null
+++ b/spec/ruby/core/hash/lte_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison'
+require_relative 'shared/less_than'
+
+describe "Hash#<=" do
+ it_behaves_like :hash_comparison, :<=
+ it_behaves_like :hash_less_than, :<=
+
+ it "returns true if both hashes are identical" do
+ h = { a: 1, b: 2 }
+ (h <= h).should be_true
+ end
+end
+
+describe "Hash#<=" do
+ before :each do
+ @hash = {a:1, b:2}
+ @bigger = {a:1, b:2, c:3}
+ @unrelated = {c:3, d:4}
+ @similar = {a:2, b:3}
+ end
+
+ it "returns false when receiver size is larger than argument" do
+ (@bigger <= @hash).should == false
+ (@bigger <= @unrelated).should == false
+ end
+
+ it "returns false when receiver size is the same as argument" do
+ (@hash <= @unrelated).should == false
+ (@unrelated <= @hash).should == false
+ end
+
+ it "returns true when receiver is a subset of argument or equals to argument" do
+ (@hash <= @bigger).should == true
+ (@hash <= @hash).should == true
+ end
+
+ it "returns false when keys match but values don't" do
+ (@hash <= @similar).should == false
+ (@similar <= @hash).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/member_spec.rb b/spec/ruby/core/hash/member_spec.rb
new file mode 100644
index 0000000000..37c0414559
--- /dev/null
+++ b/spec/ruby/core/hash/member_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/key'
+
+describe "Hash#member?" do
+ it_behaves_like :hash_key_p, :member?
+end
diff --git a/spec/ruby/core/hash/merge_spec.rb b/spec/ruby/core/hash/merge_spec.rb
new file mode 100644
index 0000000000..5521864297
--- /dev/null
+++ b/spec/ruby/core/hash/merge_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative 'shared/update'
+
+describe "Hash#merge" do
+ it "returns a new hash by combining self with the contents of other" do
+ h = { 1 => :a, 2 => :b, 3 => :c }.merge(a: 1, c: 2)
+ h.should == { c: 2, 1 => :a, 2 => :b, a: 1, 3 => :c }
+
+ hash = { a: 1, b: 2 }
+ {}.merge(hash).should == hash
+ hash.merge({}).should == hash
+
+ h = { 1 => :a, 2 => :b, 3 => :c }.merge(1 => :b)
+ h.should == { 1 => :b, 2 => :b, 3 => :c }
+
+ h = { 1 => :a, 2 => :b }.merge(1 => :b, 3 => :c)
+ h.should == { 1 => :b, 2 => :b, 3 => :c }
+ end
+
+ it "sets any duplicate key to the value of block if passed a block" do
+ h1 = { a: 2, b: 1, d: 5 }
+ h2 = { a: -2, b: 4, c: -3 }
+ r = h1.merge(h2) { |k,x,y| nil }
+ r.should == { a: nil, b: nil, c: -3, d: 5 }
+
+ r = h1.merge(h2) { |k,x,y| "#{k}:#{x+2*y}" }
+ r.should == { a: "a:-2", b: "b:9", c: -3, d: 5 }
+
+ -> {
+ h1.merge(h2) { |k, x, y| raise(IndexError) }
+ }.should raise_error(IndexError)
+
+ r = h1.merge(h1) { |k,x,y| :x }
+ r.should == { a: :x, b: :x, d: :x }
+ end
+
+ it "tries to convert the passed argument to a hash using #to_hash" do
+ obj = mock('{1=>2}')
+ obj.should_receive(:to_hash).and_return({ 1 => 2 })
+ { 3 => 4 }.merge(obj).should == { 1 => 2, 3 => 4 }
+ end
+
+ it "does not call to_hash on hash subclasses" do
+ { 3 => 4 }.merge(HashSpecs::ToHashHash[1 => 2]).should == { 1 => 2, 3 => 4 }
+ end
+
+ it "returns subclass instance for subclasses" do
+ HashSpecs::MyHash[1 => 2, 3 => 4].merge({ 1 => 2 }).should be_an_instance_of(HashSpecs::MyHash)
+ HashSpecs::MyHash[].merge({ 1 => 2 }).should be_an_instance_of(HashSpecs::MyHash)
+
+ { 1 => 2, 3 => 4 }.merge(HashSpecs::MyHash[1 => 2]).class.should == Hash
+ {}.merge(HashSpecs::MyHash[1 => 2]).class.should == Hash
+ end
+
+ it "processes entries with same order as each()" do
+ h = { 1 => 2, 3 => 4, 5 => 6, "x" => nil, nil => 5, [] => [] }
+ merge_pairs = []
+ each_pairs = []
+ h.each_pair { |k, v| each_pairs << [k, v] }
+ h.merge(h) { |k, v1, v2| merge_pairs << [k, v1] }
+ merge_pairs.should == each_pairs
+ end
+
+ it "preserves the order of merged elements" do
+ h1 = { 1 => 2, 3 => 4, 5 => 6 }
+ h2 = { 1 => 7 }
+ merge_pairs = []
+ h1.merge(h2).each_pair { |k, v| merge_pairs << [k, v] }
+ merge_pairs.should == [[1,7], [3, 4], [5, 6]]
+ end
+
+ it "preserves the order of merged elements for large hashes" do
+ h1 = {}
+ h2 = {}
+ merge_pairs = []
+ expected_pairs = []
+ (1..100).each { |x| h1[x] = x; h2[101 - x] = x; expected_pairs << [x, 101 - x] }
+ h1.merge(h2).each_pair { |k, v| merge_pairs << [k, v] }
+ merge_pairs.should == expected_pairs
+ end
+
+ it "accepts multiple hashes" do
+ result = { a: 1 }.merge({ b: 2 }, { c: 3 }, { d: 4 })
+ result.should == { a: 1, b: 2, c: 3, d: 4 }
+ end
+
+ it "accepts zero arguments and returns a copy of self" do
+ hash = { a: 1 }
+ merged = hash.merge
+
+ merged.should eql(hash)
+ merged.should_not equal(hash)
+ end
+end
+
+describe "Hash#merge!" do
+ it_behaves_like :hash_update, :merge!
+end
diff --git a/spec/ruby/core/hash/new_spec.rb b/spec/ruby/core/hash/new_spec.rb
new file mode 100644
index 0000000000..6054b69bdd
--- /dev/null
+++ b/spec/ruby/core/hash/new_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash.new" do
+ it "creates an empty Hash if passed no arguments" do
+ Hash.new.should == {}
+ Hash.new.size.should == 0
+ end
+
+ it "creates a new Hash with default object if passed a default argument" do
+ Hash.new(5).default.should == 5
+ Hash.new({}).default.should == {}
+ end
+
+ it "does not create a copy of the default argument" do
+ str = "foo"
+ Hash.new(str).default.should equal(str)
+ end
+
+ it "creates a Hash with a default_proc if passed a block" do
+ Hash.new.default_proc.should == nil
+
+ h = Hash.new { |x| "Answer to #{x}" }
+ h.default_proc.call(5).should == "Answer to 5"
+ h.default_proc.call("x").should == "Answer to x"
+ end
+
+ it "raises an ArgumentError if more than one argument is passed" do
+ -> { Hash.new(5,6) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed both default argument and default block" do
+ -> { Hash.new(5) { 0 } }.should raise_error(ArgumentError)
+ -> { Hash.new(nil) { 0 } }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/hash/rassoc_spec.rb b/spec/ruby/core/hash/rassoc_spec.rb
new file mode 100644
index 0000000000..f3b2a6de20
--- /dev/null
+++ b/spec/ruby/core/hash/rassoc_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+
+describe "Hash#rassoc" do
+ before :each do
+ @h = {apple: :green, orange: :orange, grape: :green, banana: :yellow}
+ end
+
+ it "returns an Array if the argument is a value of the Hash" do
+ @h.rassoc(:green).should be_an_instance_of(Array)
+ end
+
+ it "returns a 2-element Array if the argument is a value of the Hash" do
+ @h.rassoc(:orange).size.should == 2
+ end
+
+ it "sets the first element of the Array to the key of the located value" do
+ @h.rassoc(:yellow).first.should == :banana
+ end
+
+ it "sets the last element of the Array to the located value" do
+ @h.rassoc(:yellow).last.should == :yellow
+ end
+
+ it "only returns the first matching key-value pair" do
+ @h.rassoc(:green).should == [:apple, :green]
+ end
+
+ it "uses #== to compare the argument to the values" do
+ @h[:key] = 1.0
+ 1.should == 1.0
+ @h.rassoc(1).should eql [:key, 1.0]
+ end
+
+ it "returns nil if the argument is not a value of the Hash" do
+ @h.rassoc(:banana).should be_nil
+ end
+
+ it "returns nil if the argument is not a value of the Hash even when there is a default" do
+ Hash.new(42).merge!( foo: :bar ).rassoc(42).should be_nil
+ Hash.new{|h, k| h[k] = 42}.merge!( foo: :bar ).rassoc(42).should be_nil
+ end
+end
diff --git a/spec/ruby/core/hash/rehash_spec.rb b/spec/ruby/core/hash/rehash_spec.rb
new file mode 100644
index 0000000000..0049080456
--- /dev/null
+++ b/spec/ruby/core/hash/rehash_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#rehash" do
+ it "reorganizes the Hash by recomputing all key hash codes" do
+ k1 = Object.new
+ k2 = Object.new
+ def k1.hash; 0; end
+ def k2.hash; 1; end
+
+ h = {}
+ h[k1] = :v1
+ h[k2] = :v2
+
+ def k1.hash; 1; end
+
+ # The key should no longer be found as the #hash changed.
+ # Hash values 0 and 1 should not conflict, even with 1-bit stored hash.
+ h.key?(k1).should == false
+
+ h.keys.include?(k1).should == true
+
+ h.rehash.should equal(h)
+ h.key?(k1).should == true
+ h[k1].should == :v1
+ end
+
+ it "calls #hash for each key" do
+ k1 = mock('k1')
+ k2 = mock('k2')
+ v1 = mock('v1')
+ v2 = mock('v2')
+
+ v1.should_not_receive(:hash)
+ v2.should_not_receive(:hash)
+
+ h = { k1 => v1, k2 => v2 }
+
+ k1.should_receive(:hash).twice.and_return(0)
+ k2.should_receive(:hash).twice.and_return(0)
+
+ h.rehash
+ h[k1].should == v1
+ h[k2].should == v2
+ end
+
+ it "removes duplicate keys" do
+ a = [1,2]
+ b = [1]
+
+ h = {}
+ h[a] = true
+ h[b] = true
+ b << 2
+ h.size.should == 2
+ h.keys.should == [a, b]
+ h.rehash
+ h.size.should == 1
+ h.keys.should == [a]
+ end
+
+ it "removes duplicate keys for large hashes" do
+ a = [1,2]
+ b = [1]
+
+ h = {}
+ h[a] = true
+ h[b] = true
+ 100.times { |n| h[n] = true }
+ b << 2
+ h.size.should == 102
+ h.keys.should.include? a
+ h.keys.should.include? b
+ h.rehash
+ h.size.should == 101
+ h.keys.should.include? a
+ h.keys.should_not.include? [1]
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.rehash }.should raise_error(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.rehash }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/reject_spec.rb b/spec/ruby/core/hash/reject_spec.rb
new file mode 100644
index 0000000000..dd8e817237
--- /dev/null
+++ b/spec/ruby/core/hash/reject_spec.rb
@@ -0,0 +1,95 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#reject" do
+ it "returns a new hash removing keys for which the block yields true" do
+ h = { 1=>false, 2=>true, 3=>false, 4=>true }
+ h.reject { |k,v| v }.keys.sort.should == [1,3]
+ end
+
+ it "is equivalent to hsh.dup.delete_if" do
+ h = { a: 'a', b: 'b', c: 'd' }
+ h.reject { |k,v| k == 'd' }.should == (h.dup.delete_if { |k, v| k == 'd' })
+
+ all_args_reject = []
+ all_args_delete_if = []
+ h = { 1 => 2, 3 => 4 }
+ h.reject { |*args| all_args_reject << args }
+ h.delete_if { |*args| all_args_delete_if << args }
+ all_args_reject.should == all_args_delete_if
+
+ h = { 1 => 2 }
+ # dup doesn't copy singleton methods
+ def h.to_a() end
+ h.reject { false }.to_a.should == [[1, 2]]
+ end
+
+ context "with extra state" do
+ it "returns Hash instance for subclasses" do
+ HashSpecs::MyHash[1 => 2, 3 => 4].reject { false }.should be_kind_of(Hash)
+ HashSpecs::MyHash[1 => 2, 3 => 4].reject { true }.should be_kind_of(Hash)
+ end
+ end
+
+ it "processes entries with the same order as reject!" do
+ h = { a: 1, b: 2, c: 3, d: 4 }
+
+ reject_pairs = []
+ reject_bang_pairs = []
+ h.dup.reject { |*pair| reject_pairs << pair }
+ h.reject! { |*pair| reject_bang_pairs << pair }
+
+ reject_pairs.should == reject_bang_pairs
+ end
+
+ it_behaves_like :hash_iteration_no_block, :reject
+ it_behaves_like :enumeratorized_with_origin_size, :reject, { 1 => 2, 3 => 4, 5 => 6 }
+end
+
+describe "Hash#reject!" do
+ it "removes keys from self for which the block yields true" do
+ hsh = {}
+ (1 .. 10).each { |k| hsh[k] = (k % 2 == 0) }
+ hsh.reject! { |k,v| v }
+ hsh.keys.sort.should == [1,3,5,7,9]
+ end
+
+ it "removes all entries if the block is true" do
+ h = { a: 1, b: 2, c: 3 }
+ h.reject! { |k,v| true }.should equal(h)
+ h.should == {}
+ end
+
+ it "is equivalent to delete_if if changes are made" do
+ hsh = { a: 1 }
+ hsh.reject! { |k,v| v < 2 }.should == hsh.dup.delete_if { |k, v| v < 2 }
+ end
+
+ it "returns nil if no changes were made" do
+ { a: 1 }.reject! { |k,v| v > 1 }.should == nil
+ end
+
+ it "processes entries with the same order as delete_if" do
+ h = { a: 1, b: 2, c: 3, d: 4 }
+
+ reject_bang_pairs = []
+ delete_if_pairs = []
+ h.dup.reject! { |*pair| reject_bang_pairs << pair }
+ h.dup.delete_if { |*pair| delete_if_pairs << pair }
+
+ reject_bang_pairs.should == delete_if_pairs
+ end
+
+ it "raises a FrozenError if called on a frozen instance that is modified" do
+ -> { HashSpecs.empty_frozen_hash.reject! { true } }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError if called on a frozen instance that would not be modified" do
+ -> { HashSpecs.frozen_hash.reject! { false } }.should raise_error(FrozenError)
+ end
+
+ it_behaves_like :hash_iteration_no_block, :reject!
+ it_behaves_like :enumeratorized_with_origin_size, :reject!, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/replace_spec.rb b/spec/ruby/core/hash/replace_spec.rb
new file mode 100644
index 0000000000..92b2118fd3
--- /dev/null
+++ b/spec/ruby/core/hash/replace_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/replace'
+
+describe "Hash#replace" do
+ it_behaves_like :hash_replace, :replace
+end
diff --git a/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb
new file mode 100644
index 0000000000..e9337b9d1c
--- /dev/null
+++ b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash.ruby2_keywords_hash?" do
+ it "returns false if the Hash is not a keywords Hash" do
+ Hash.ruby2_keywords_hash?({}).should == false
+ end
+
+ it "returns true if the Hash is a keywords Hash marked by Module#ruby2_keywords" do
+ obj = Class.new {
+ ruby2_keywords def m(*args)
+ args.last
+ end
+ }.new
+ Hash.ruby2_keywords_hash?(obj.m(a: 1)).should == true
+ end
+
+ it "raises TypeError for non-Hash" do
+ -> { Hash.ruby2_keywords_hash?(nil) }.should raise_error(TypeError)
+ end
+end
+
+describe "Hash.ruby2_keywords_hash" do
+ it "returns a copy of a Hash and marks the copy as a keywords Hash" do
+ h = {a: 1}.freeze
+ kw = Hash.ruby2_keywords_hash(h)
+ Hash.ruby2_keywords_hash?(h).should == false
+ Hash.ruby2_keywords_hash?(kw).should == true
+ kw.should == h
+ end
+
+ it "returns an instance of the subclass if called on an instance of a subclass of Hash" do
+ h = HashSpecs::MyHash.new
+ h[:a] = 1
+ kw = Hash.ruby2_keywords_hash(h)
+ kw.class.should == HashSpecs::MyHash
+ Hash.ruby2_keywords_hash?(h).should == false
+ Hash.ruby2_keywords_hash?(kw).should == true
+ kw.should == h
+ end
+
+ it "copies instance variables" do
+ h = {a: 1}
+ h.instance_variable_set(:@foo, 42)
+ kw = Hash.ruby2_keywords_hash(h)
+ kw.instance_variable_get(:@foo).should == 42
+ end
+
+ it "copies the hash internals" do
+ h = {a: 1}
+ kw = Hash.ruby2_keywords_hash(h)
+ h[:a] = 2
+ kw[:a].should == 1
+ end
+
+ it "raises TypeError for non-Hash" do
+ -> { Hash.ruby2_keywords_hash(nil) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/hash/select_spec.rb b/spec/ruby/core/hash/select_spec.rb
new file mode 100644
index 0000000000..38b0180b0e
--- /dev/null
+++ b/spec/ruby/core/hash/select_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/select'
+
+describe "Hash#select" do
+ it_behaves_like :hash_select, :select
+end
+
+describe "Hash#select!" do
+ it_behaves_like :hash_select!, :select!
+end
diff --git a/spec/ruby/core/hash/shared/comparison.rb b/spec/ruby/core/hash/shared/comparison.rb
new file mode 100644
index 0000000000..07564e4cec
--- /dev/null
+++ b/spec/ruby/core/hash/shared/comparison.rb
@@ -0,0 +1,15 @@
+describe :hash_comparison, shared: true do
+ it "raises a TypeError if the right operand is not a hash" do
+ -> { { a: 1 }.send(@method, 1) }.should raise_error(TypeError)
+ -> { { a: 1 }.send(@method, nil) }.should raise_error(TypeError)
+ -> { { a: 1 }.send(@method, []) }.should raise_error(TypeError)
+ end
+
+ it "returns false if both hashes have the same keys but different values" do
+ h1 = { a: 1 }
+ h2 = { a: 2 }
+
+ h1.send(@method, h2).should be_false
+ h2.send(@method, h1).should be_false
+ end
+end
diff --git a/spec/ruby/core/hash/shared/each.rb b/spec/ruby/core/hash/shared/each.rb
new file mode 100644
index 0000000000..b2483c8116
--- /dev/null
+++ b/spec/ruby/core/hash/shared/each.rb
@@ -0,0 +1,124 @@
+describe :hash_each, shared: true do
+
+ # This is inconsistent with below, MRI checks the block arity in rb_hash_each_pair()
+ it "yields a [[key, value]] Array for each pair to a block expecting |*args|" do
+ all_args = []
+ { 1 => 2, 3 => 4 }.send(@method) { |*args| all_args << args }
+ all_args.sort.should == [[[1, 2]], [[3, 4]]]
+ end
+
+ it "yields the key and value of each pair to a block expecting |key, value|" do
+ r = {}
+ h = { a: 1, b: 2, c: 3, d: 5 }
+ h.send(@method) { |k,v| r[k.to_s] = v.to_s }.should equal(h)
+ r.should == { "a" => "1", "b" => "2", "c" => "3", "d" => "5" }
+ end
+
+ it "yields the key only to a block expecting |key,|" do
+ ary = []
+ h = { "a" => 1, "b" => 2, "c" => 3 }
+ h.send(@method) { |k,| ary << k }
+ ary.sort.should == ["a", "b", "c"]
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "yields 2 values and not an Array of 2 elements when given a callable of arity 2" do
+ obj = Object.new
+ def obj.foo(key, value)
+ ScratchPad << key << value
+ end
+
+ ScratchPad.record([])
+ { "a" => 1 }.send(@method, &obj.method(:foo))
+ ScratchPad.recorded.should == ["a", 1]
+
+ ScratchPad.record([])
+ { "a" => 1 }.send(@method, &-> key, value { ScratchPad << key << value })
+ ScratchPad.recorded.should == ["a", 1]
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "always yields an Array of 2 elements, even when given a callable of arity 2" do
+ obj = Object.new
+ def obj.foo(key, value)
+ end
+
+ -> {
+ { "a" => 1 }.send(@method, &obj.method(:foo))
+ }.should raise_error(ArgumentError)
+
+ -> {
+ { "a" => 1 }.send(@method, &-> key, value { })
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "yields an Array of 2 elements when given a callable of arity 1" do
+ obj = Object.new
+ def obj.foo(key_value)
+ ScratchPad << key_value
+ end
+
+ ScratchPad.record([])
+ { "a" => 1 }.send(@method, &obj.method(:foo))
+ ScratchPad.recorded.should == [["a", 1]]
+ end
+
+ it "raises an error for a Hash when an arity enforcing callable of arity >2 is passed in" do
+ obj = Object.new
+ def obj.foo(key, value, extra)
+ end
+
+ -> {
+ { "a" => 1 }.send(@method, &obj.method(:foo))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "uses the same order as keys() and values()" do
+ h = { a: 1, b: 2, c: 3, d: 5 }
+ keys = []
+ values = []
+
+ h.send(@method) do |k, v|
+ keys << k
+ values << v
+ end
+
+ keys.should == h.keys
+ values.should == h.values
+ end
+
+ # Confirming the argument-splatting works from child class for both k, v and [k, v]
+ it "properly expands (or not) child class's 'each'-yielded args" do
+ cls1 = Class.new(Hash) do
+ attr_accessor :k_v
+ def each
+ super do |k, v|
+ @k_v = [k, v]
+ yield k, v
+ end
+ end
+ end
+
+ cls2 = Class.new(Hash) do
+ attr_accessor :k_v
+ def each
+ super do |k, v|
+ @k_v = [k, v]
+ yield([k, v])
+ end
+ end
+ end
+
+ obj1 = cls1.new
+ obj1['a'] = 'b'
+ obj1.map {|k, v| [k, v]}.should == [['a', 'b']]
+ obj1.k_v.should == ['a', 'b']
+
+ obj2 = cls2.new
+ obj2['a'] = 'b'
+ obj2.map {|k, v| [k, v]}.should == [['a', 'b']]
+ obj2.k_v.should == ['a', 'b']
+ end
+end
diff --git a/spec/ruby/core/hash/shared/eql.rb b/spec/ruby/core/hash/shared/eql.rb
new file mode 100644
index 0000000000..68db49f76d
--- /dev/null
+++ b/spec/ruby/core/hash/shared/eql.rb
@@ -0,0 +1,204 @@
+describe :hash_eql, shared: true do
+ it "does not compare values when keys don't match" do
+ value = mock('x')
+ value.should_not_receive(:==)
+ value.should_not_receive(:eql?)
+ { 1 => value }.send(@method, { 2 => value }).should be_false
+ end
+
+ it "returns false when the numbers of keys differ without comparing any elements" do
+ obj = mock('x')
+ h = { obj => obj }
+
+ obj.should_not_receive(:==)
+ obj.should_not_receive(:eql?)
+
+ {}.send(@method, h).should be_false
+ h.send(@method, {}).should be_false
+ end
+
+ it "first compares keys via hash" do
+ x = mock('x')
+ x.should_receive(:hash).any_number_of_times.and_return(0)
+ y = mock('y')
+ y.should_receive(:hash).any_number_of_times.and_return(0)
+
+ { x => 1 }.send(@method, { y => 1 }).should be_false
+ end
+
+ it "does not compare keys with different hash codes via eql?" do
+ x = mock('x')
+ y = mock('y')
+ x.should_not_receive(:eql?)
+ y.should_not_receive(:eql?)
+
+ x.should_receive(:hash).any_number_of_times.and_return(0)
+ y.should_receive(:hash).any_number_of_times.and_return(1)
+
+ { x => 1 }.send(@method, { y => 1 }).should be_false
+ end
+
+ it "computes equality for recursive hashes" do
+ h = {}
+ h[:a] = h
+ h.send(@method, h[:a]).should be_true
+ (h == h[:a]).should be_true
+ end
+
+ it "doesn't call to_hash on objects" do
+ mock_hash = mock("fake hash")
+ def mock_hash.to_hash() {} end
+ {}.send(@method, mock_hash).should be_false
+ end
+
+ it "computes equality for complex recursive hashes" do
+ a, b = {}, {}
+ a.merge! self: a, other: b
+ b.merge! self: b, other: a
+ a.send(@method, b).should be_true # they both have the same structure!
+
+ c = {}
+ c.merge! other: c, self: c
+ c.send(@method, a).should be_true # subtle, but they both have the same structure!
+ a[:delta] = c[:delta] = a
+ c.send(@method, a).should be_false # not quite the same structure, as a[:other][:delta] = nil
+ c[:delta] = 42
+ c.send(@method, a).should be_false
+ a[:delta] = 42
+ c.send(@method, a).should be_false
+ b[:delta] = 42
+ c.send(@method, a).should be_true
+ end
+
+ it "computes equality for recursive hashes & arrays" do
+ x, y, z = [], [], []
+ a, b, c = {foo: x, bar: 42}, {foo: y, bar: 42}, {foo: z, bar: 42}
+ x << a
+ y << c
+ z << b
+ b.send(@method, c).should be_true # they clearly have the same structure!
+ y.send(@method, z).should be_true
+ a.send(@method, b).should be_true # subtle, but they both have the same structure!
+ x.send(@method, y).should be_true
+ y << x
+ y.send(@method, z).should be_false
+ z << x
+ y.send(@method, z).should be_true
+
+ a[:foo], a[:bar] = a[:bar], a[:foo]
+ a.send(@method, b).should be_false
+ b[:bar] = b[:foo]
+ b.send(@method, c).should be_false
+ end
+end
+
+describe :hash_eql_additional, shared: true do
+ it "compares values when keys match" do
+ x = mock('x')
+ y = mock('y')
+ def x.==(o) false end
+ def y.==(o) false end
+ def x.eql?(o) false end
+ def y.eql?(o) false end
+ { 1 => x }.send(@method, { 1 => y }).should be_false
+
+ x = mock('x')
+ y = mock('y')
+ def x.==(o) true end
+ def y.==(o) true end
+ def x.eql?(o) true end
+ def y.eql?(o) true end
+ { 1 => x }.send(@method, { 1 => y }).should be_true
+ end
+
+ it "compares keys with eql? semantics" do
+ { 1.0 => "x" }.send(@method, { 1.0 => "x" }).should be_true
+ { 1.0 => "x" }.send(@method, { 1.0 => "x" }).should be_true
+ { 1 => "x" }.send(@method, { 1.0 => "x" }).should be_false
+ { 1.0 => "x" }.send(@method, { 1 => "x" }).should be_false
+ end
+
+ it "returns true if and only if other Hash has the same number of keys and each key-value pair matches" do
+ a = { a: 5 }
+ b = {}
+ a.send(@method, b).should be_false
+
+ b[:a] = 5
+ a.send(@method, b).should be_true
+
+ not_supported_on :opal do
+ c = { "a" => 5 }
+ a.send(@method, c).should be_false
+ end
+
+ c = { "A" => 5 }
+ a.send(@method, c).should be_false
+
+ c = { a: 6 }
+ a.send(@method, c).should be_false
+ end
+
+ it "does not call to_hash on hash subclasses" do
+ { 5 => 6 }.send(@method, HashSpecs::ToHashHash[5 => 6]).should be_true
+ end
+
+ it "ignores hash class differences" do
+ h = { 1 => 2, 3 => 4 }
+ HashSpecs::MyHash[h].send(@method, h).should be_true
+ HashSpecs::MyHash[h].send(@method, HashSpecs::MyHash[h]).should be_true
+ h.send(@method, HashSpecs::MyHash[h]).should be_true
+ end
+
+ # Why isn't this true of eql? too ?
+ it "compares keys with matching hash codes via eql?" do
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ return true if self.equal?(o)
+ false
+ end
+
+ obj
+ end
+
+ { a[0] => 1 }.send(@method, { a[1] => 1 }).should be_false
+
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ true
+ end
+
+ obj
+ end
+
+ { a[0] => 1 }.send(@method, { a[1] => 1 }).should be_true
+ end
+
+ it "compares the values in self to values in other hash" do
+ l_val = mock("left")
+ r_val = mock("right")
+
+ l_val.should_receive(:eql?).with(r_val).and_return(true)
+
+ { 1 => l_val }.eql?({ 1 => r_val }).should be_true
+ end
+end
+
+describe :hash_eql_additional_more, shared: true do
+ it "returns true if other Hash has the same number of keys and each key-value pair matches, even though the default-value are not same" do
+ Hash.new(5).send(@method, Hash.new(1)).should be_true
+ Hash.new {|h, k| 1}.send(@method, Hash.new {}).should be_true
+ Hash.new {|h, k| 1}.send(@method, Hash.new(2)).should be_true
+
+ d = Hash.new {|h, k| 1}
+ e = Hash.new {}
+ d[1] = 2
+ e[1] = 2
+ d.send(@method, e).should be_true
+ end
+end
diff --git a/spec/ruby/core/hash/shared/equal.rb b/spec/ruby/core/hash/shared/equal.rb
new file mode 100644
index 0000000000..43606437fe
--- /dev/null
+++ b/spec/ruby/core/hash/shared/equal.rb
@@ -0,0 +1,90 @@
+describe :hash_equal, shared: true do
+ it "does not compare values when keys don't match" do
+ value = mock('x')
+ value.should_not_receive(:==)
+ value.should_not_receive(:eql?)
+ { 1 => value }.send(@method, { 2 => value }).should be_false
+ end
+
+ it "returns false when the numbers of keys differ without comparing any elements" do
+ obj = mock('x')
+ h = { obj => obj }
+
+ obj.should_not_receive(:==)
+ obj.should_not_receive(:eql?)
+
+ {}.send(@method, h).should be_false
+ h.send(@method, {}).should be_false
+ end
+
+ it "first compares keys via hash" do
+ x = mock('x')
+ x.should_receive(:hash).and_return(0)
+ y = mock('y')
+ y.should_receive(:hash).and_return(0)
+
+ { x => 1 }.send(@method, { y => 1 }).should be_false
+ end
+
+ it "does not compare keys with different hash codes via eql?" do
+ x = mock('x')
+ y = mock('y')
+ x.should_not_receive(:eql?)
+ y.should_not_receive(:eql?)
+
+ x.should_receive(:hash).and_return(0)
+ y.should_receive(:hash).and_return(1)
+
+ def x.hash() 0 end
+ def y.hash() 1 end
+
+ { x => 1 }.send(@method, { y => 1 }).should be_false
+ end
+
+ it "computes equality for recursive hashes" do
+ h = {}
+ h[:a] = h
+ h.send(@method, h[:a]).should be_true
+ (h == h[:a]).should be_true
+ end
+
+ it "computes equality for complex recursive hashes" do
+ a, b = {}, {}
+ a.merge! self: a, other: b
+ b.merge! self: b, other: a
+ a.send(@method, b).should be_true # they both have the same structure!
+
+ c = {}
+ c.merge! other: c, self: c
+ c.send(@method, a).should be_true # subtle, but they both have the same structure!
+ a[:delta] = c[:delta] = a
+ c.send(@method, a).should be_false # not quite the same structure, as a[:other][:delta] = nil
+ c[:delta] = 42
+ c.send(@method, a).should be_false
+ a[:delta] = 42
+ c.send(@method, a).should be_false
+ b[:delta] = 42
+ c.send(@method, a).should be_true
+ end
+
+ it "computes equality for recursive hashes & arrays" do
+ x, y, z = [], [], []
+ a, b, c = {foo: x, bar: 42}, {foo: y, bar: 42}, {foo: z, bar: 42}
+ x << a
+ y << c
+ z << b
+ b.send(@method, c).should be_true # they clearly have the same structure!
+ y.send(@method, z).should be_true
+ a.send(@method, b).should be_true # subtle, but they both have the same structure!
+ x.send(@method, y).should be_true
+ y << x
+ y.send(@method, z).should be_false
+ z << x
+ y.send(@method, z).should be_true
+
+ a[:foo], a[:bar] = a[:bar], a[:foo]
+ a.send(@method, b).should be_false
+ b[:bar] = b[:foo]
+ b.send(@method, c).should be_false
+ end
+end
diff --git a/spec/ruby/core/hash/shared/greater_than.rb b/spec/ruby/core/hash/shared/greater_than.rb
new file mode 100644
index 0000000000..1f8b9fcfb7
--- /dev/null
+++ b/spec/ruby/core/hash/shared/greater_than.rb
@@ -0,0 +1,23 @@
+describe :hash_greater_than, shared: true do
+ before do
+ @h1 = { a: 1, b: 2, c: 3 }
+ @h2 = { a: 1, b: 2 }
+ end
+
+ it "returns true if the other hash is a subset of self" do
+ @h1.send(@method, @h2).should be_true
+ end
+
+ it "returns false if the other hash is not a subset of self" do
+ @h2.send(@method, @h1).should be_false
+ end
+
+ it "converts the right operand to a hash before comparing" do
+ o = Object.new
+ def o.to_hash
+ { a: 1, b: 2 }
+ end
+
+ @h1.send(@method, o).should be_true
+ end
+end
diff --git a/spec/ruby/core/hash/shared/index.rb b/spec/ruby/core/hash/shared/index.rb
new file mode 100644
index 0000000000..7f6a186464
--- /dev/null
+++ b/spec/ruby/core/hash/shared/index.rb
@@ -0,0 +1,37 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :hash_index, shared: true do
+ it "returns the corresponding key for value" do
+ suppress_warning do # for Hash#index
+ { 2 => 'a', 1 => 'b' }.send(@method, 'b').should == 1
+ end
+ end
+
+ it "returns nil if the value is not found" do
+ suppress_warning do # for Hash#index
+ { a: -1, b: 3.14, c: 2.718 }.send(@method, 1).should be_nil
+ end
+ end
+
+ it "doesn't return default value if the value is not found" do
+ suppress_warning do # for Hash#index
+ Hash.new(5).send(@method, 5).should be_nil
+ end
+ end
+
+ it "compares values using ==" do
+ suppress_warning do # for Hash#index
+ { 1 => 0 }.send(@method, 0.0).should == 1
+ { 1 => 0.0 }.send(@method, 0).should == 1
+ end
+
+ needle = mock('needle')
+ inhash = mock('inhash')
+ inhash.should_receive(:==).with(needle).and_return(true)
+
+ suppress_warning do # for Hash#index
+ { 1 => inhash }.send(@method, needle).should == 1
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/shared/iteration.rb b/spec/ruby/core/hash/shared/iteration.rb
new file mode 100644
index 0000000000..d27c2443f8
--- /dev/null
+++ b/spec/ruby/core/hash/shared/iteration.rb
@@ -0,0 +1,19 @@
+describe :hash_iteration_no_block, shared: true do
+ before :each do
+ @hsh = { 1 => 2, 3 => 4, 5 => 6 }
+ @empty = {}
+ end
+
+ it "returns an Enumerator if called on a non-empty hash without a block" do
+ @hsh.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "returns an Enumerator if called on an empty hash without a block" do
+ @empty.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "returns an Enumerator if called on a frozen instance" do
+ @hsh.freeze
+ @hsh.send(@method).should be_an_instance_of(Enumerator)
+ end
+end
diff --git a/spec/ruby/core/hash/shared/key.rb b/spec/ruby/core/hash/shared/key.rb
new file mode 100644
index 0000000000..17f9f81457
--- /dev/null
+++ b/spec/ruby/core/hash/shared/key.rb
@@ -0,0 +1,38 @@
+describe :hash_key_p, shared: true do
+ it "returns true if argument is a key" do
+ h = { a: 1, b: 2, c: 3, 4 => 0 }
+ h.send(@method, :a).should == true
+ h.send(@method, :b).should == true
+ h.send(@method, 2).should == false
+ h.send(@method, 4).should == true
+
+ not_supported_on :opal do
+ h.send(@method, 'b').should == false
+ h.send(@method, 4.0).should == false
+ end
+ end
+
+ it "returns true if the key's matching value was nil" do
+ { xyz: nil }.send(@method, :xyz).should == true
+ end
+
+ it "returns true if the key's matching value was false" do
+ { xyz: false }.send(@method, :xyz).should == true
+ end
+
+ it "returns true if the key is nil" do
+ { nil => 'b' }.send(@method, nil).should == true
+ { nil => nil }.send(@method, nil).should == true
+ end
+
+ it "compares keys with the same #hash value via #eql?" do
+ x = mock('x')
+ x.stub!(:hash).and_return(42)
+
+ y = mock('y')
+ y.stub!(:hash).and_return(42)
+ y.should_receive(:eql?).and_return(false)
+
+ { x => nil }.send(@method, y).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/shared/length.rb b/spec/ruby/core/hash/shared/length.rb
new file mode 100644
index 0000000000..24f5563759
--- /dev/null
+++ b/spec/ruby/core/hash/shared/length.rb
@@ -0,0 +1,12 @@
+describe :hash_length, shared: true do
+ it "returns the number of entries" do
+ { a: 1, b: 'c' }.send(@method).should == 2
+ h = { a: 1, b: 2 }
+ h[:a] = 2
+ h.send(@method).should == 2
+ { a: 1, b: 1, c: 1 }.send(@method).should == 3
+ {}.send(@method).should == 0
+ Hash.new(5).send(@method).should == 0
+ Hash.new { 5 }.send(@method).should == 0
+ end
+end
diff --git a/spec/ruby/core/hash/shared/less_than.rb b/spec/ruby/core/hash/shared/less_than.rb
new file mode 100644
index 0000000000..cdc6f14546
--- /dev/null
+++ b/spec/ruby/core/hash/shared/less_than.rb
@@ -0,0 +1,23 @@
+describe :hash_less_than, shared: true do
+ before do
+ @h1 = { a: 1, b: 2 }
+ @h2 = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns true if self is a subset of the other hash" do
+ @h1.send(@method, @h2).should be_true
+ end
+
+ it "returns false if self is not a subset of the other hash" do
+ @h2.send(@method, @h1).should be_false
+ end
+
+ it "converts the right operand to a hash before comparing" do
+ o = Object.new
+ def o.to_hash
+ { a: 1, b: 2, c: 3 }
+ end
+
+ @h1.send(@method, o).should be_true
+ end
+end
diff --git a/spec/ruby/core/hash/shared/replace.rb b/spec/ruby/core/hash/shared/replace.rb
new file mode 100644
index 0000000000..bea64384bb
--- /dev/null
+++ b/spec/ruby/core/hash/shared/replace.rb
@@ -0,0 +1,51 @@
+describe :hash_replace, shared: true do
+ it "replaces the contents of self with other" do
+ h = { a: 1, b: 2 }
+ h.send(@method, c: -1, d: -2).should equal(h)
+ h.should == { c: -1, d: -2 }
+ end
+
+ it "tries to convert the passed argument to a hash using #to_hash" do
+ obj = mock('{1=>2,3=>4}')
+ obj.should_receive(:to_hash).and_return({ 1 => 2, 3 => 4 })
+
+ h = {}
+ h.send(@method, obj)
+ h.should == { 1 => 2, 3 => 4 }
+ end
+
+ it "calls to_hash on hash subclasses" do
+ h = {}
+ h.send(@method, HashSpecs::ToHashHash[1 => 2])
+ h.should == { 1 => 2 }
+ end
+
+ it "does not transfer default values" do
+ hash_a = {}
+ hash_b = Hash.new(5)
+ hash_a.send(@method, hash_b)
+ hash_a.default.should == 5
+
+ hash_a = {}
+ hash_b = Hash.new { |h, k| k * 2 }
+ hash_a.send(@method, hash_b)
+ hash_a.default(5).should == 10
+
+ hash_a = Hash.new { |h, k| k * 5 }
+ hash_b = Hash.new(-> { raise "Should not invoke lambda" })
+ hash_a.send(@method, hash_b)
+ hash_a.default.should == hash_b.default
+ end
+
+ it "raises a FrozenError if called on a frozen instance that would not be modified" do
+ -> do
+ HashSpecs.frozen_hash.send(@method, HashSpecs.frozen_hash)
+ end.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError if called on a frozen instance that is modified" do
+ -> do
+ HashSpecs.frozen_hash.send(@method, HashSpecs.empty_frozen_hash)
+ end.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/shared/select.rb b/spec/ruby/core/hash/shared/select.rb
new file mode 100644
index 0000000000..5170af50d6
--- /dev/null
+++ b/spec/ruby/core/hash/shared/select.rb
@@ -0,0 +1,91 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/iteration'
+require_relative '../../enumerable/shared/enumeratorized'
+
+describe :hash_select, shared: true do
+ before :each do
+ @hsh = { 1 => 2, 3 => 4, 5 => 6 }
+ @empty = {}
+ end
+
+ it "yields two arguments: key and value" do
+ all_args = []
+ { 1 => 2, 3 => 4 }.send(@method) { |*args| all_args << args }
+ all_args.sort.should == [[1, 2], [3, 4]]
+ end
+
+ it "returns a Hash of entries for which block is true" do
+ a_pairs = { 'a' => 9, 'c' => 4, 'b' => 5, 'd' => 2 }.send(@method) { |k,v| v % 2 == 0 }
+ a_pairs.should be_an_instance_of(Hash)
+ a_pairs.sort.should == [['c', 4], ['d', 2]]
+ end
+
+ it "processes entries with the same order as reject" do
+ h = { a: 9, c: 4, b: 5, d: 2 }
+
+ select_pairs = []
+ reject_pairs = []
+ h.dup.send(@method) { |*pair| select_pairs << pair }
+ h.reject { |*pair| reject_pairs << pair }
+
+ select_pairs.should == reject_pairs
+ end
+
+ it "returns an Enumerator when called on a non-empty hash without a block" do
+ @hsh.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "returns an Enumerator when called on an empty hash without a block" do
+ @empty.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it_should_behave_like :hash_iteration_no_block
+
+ before :each do
+ @object = { 1 => 2, 3 => 4, 5 => 6 }
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+end
+
+describe :hash_select!, shared: true do
+ before :each do
+ @hsh = { 1 => 2, 3 => 4, 5 => 6 }
+ @empty = {}
+ end
+
+ it "is equivalent to keep_if if changes are made" do
+ h = { a: 2 }
+ h.send(@method) { |k,v| v <= 1 }.should equal h
+
+ h = { 1 => 2, 3 => 4 }
+ all_args_select = []
+ h.dup.send(@method) { |*args| all_args_select << args }
+ all_args_select.should == [[1, 2], [3, 4]]
+ end
+
+ it "removes all entries if the block is false" do
+ h = { a: 1, b: 2, c: 3 }
+ h.send(@method) { |k,v| false }.should equal(h)
+ h.should == {}
+ end
+
+ it "returns nil if no changes were made" do
+ { a: 1 }.send(@method) { |k,v| v <= 1 }.should == nil
+ end
+
+ it "raises a FrozenError if called on an empty frozen instance" do
+ -> { HashSpecs.empty_frozen_hash.send(@method) { false } }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError if called on a frozen instance that would not be modified" do
+ -> { HashSpecs.frozen_hash.send(@method) { true } }.should raise_error(FrozenError)
+ end
+
+ it_should_behave_like :hash_iteration_no_block
+
+ before :each do
+ @object = { 1 => 2, 3 => 4, 5 => 6 }
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+end
diff --git a/spec/ruby/core/hash/shared/store.rb b/spec/ruby/core/hash/shared/store.rb
new file mode 100644
index 0000000000..b823ea45ca
--- /dev/null
+++ b/spec/ruby/core/hash/shared/store.rb
@@ -0,0 +1,115 @@
+require_relative '../fixtures/classes'
+
+describe :hash_store, shared: true do
+ it "associates the key with the value and return the value" do
+ h = { a: 1 }
+ h.send(@method, :b, 2).should == 2
+ h.should == { b:2, a:1 }
+ end
+
+ it "duplicates string keys using dup semantics" do
+ # dup doesn't copy singleton methods
+ key = "foo"
+ def key.reverse() "bar" end
+ h = {}
+ h.send(@method, key, 0)
+ h.keys[0].reverse.should == "oof"
+ end
+
+ it "stores unequal keys that hash to the same value" do
+ h = {}
+ k1 = ["x"]
+ k2 = ["y"]
+ # So they end up in the same bucket
+ k1.should_receive(:hash).and_return(0)
+ k2.should_receive(:hash).and_return(0)
+
+ h.send(@method, k1, 1)
+ h.send(@method, k2, 2)
+ h.size.should == 2
+ end
+
+ it "accepts keys with private #hash method" do
+ key = HashSpecs::KeyWithPrivateHash.new
+ h = {}
+ h.send(@method, key, "foo")
+ h[key].should == "foo"
+ end
+
+ it " accepts keys with an Integer hash" do
+ o = mock(hash: 1 << 100)
+ h = {}
+ h[o] = 1
+ h[o].should == 1
+ end
+
+ it "duplicates and freezes string keys" do
+ key = "foo"
+ h = {}
+ h.send(@method, key, 0)
+ key << "bar"
+
+ h.should == { "foo" => 0 }
+ h.keys[0].should.frozen?
+ end
+
+ it "doesn't duplicate and freeze already frozen string keys" do
+ key = "foo".freeze
+ h = {}
+ h.send(@method, key, 0)
+ h.keys[0].should equal(key)
+ end
+
+ it "keeps the existing key in the hash if there is a matching one" do
+ h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 }
+ key1 = HashSpecs::ByValueKey.new(13)
+ key2 = HashSpecs::ByValueKey.new(13)
+ h[key1] = 41
+ key_in_hash = h.keys.last
+ key_in_hash.should equal(key1)
+ h[key2] = 42
+ last_key = h.keys.last
+ last_key.should equal(key_in_hash)
+ last_key.should_not equal(key2)
+ end
+
+ it "keeps the existing String key in the hash if there is a matching one" do
+ h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 }
+ key1 = "foo"
+ key2 = "foo"
+ key1.should_not equal(key2)
+ h[key1] = 41
+ frozen_key = h.keys.last
+ frozen_key.should_not equal(key1)
+ h[key2] = 42
+ h.keys.last.should equal(frozen_key)
+ h.keys.last.should_not equal(key2)
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.send(@method, 1, 2) }.should raise_error(FrozenError)
+ end
+
+ it "does not raise an exception if changing the value of an existing key during iteration" do
+ hash = {1 => 2, 3 => 4, 5 => 6}
+ hash.each { hash.send(@method, 1, :foo) }
+ hash.should == {1 => :foo, 3 => 4, 5 => 6}
+ end
+
+ it "does not dispatch to hash for Boolean, Integer, Float, String, or Symbol" do
+ code = <<-EOC
+ load '#{fixture __FILE__, "name.rb"}'
+ hash = {}
+ [true, false, 1, 2.0, "hello", :ok].each do |value|
+ hash[value] = 42
+ raise "incorrect value" unless hash[value] == 42
+ hash[value] = 43
+ raise "incorrect value" unless hash[value] == 43
+ end
+ puts "OK"
+ puts hash.size
+ EOC
+ result = ruby_exe(code, args: "2>&1")
+ result.should == "OK\n6\n"
+ end
+end
diff --git a/spec/ruby/core/hash/shared/to_s.rb b/spec/ruby/core/hash/shared/to_s.rb
new file mode 100644
index 0000000000..2db3a96583
--- /dev/null
+++ b/spec/ruby/core/hash/shared/to_s.rb
@@ -0,0 +1,89 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :hash_to_s, shared: true do
+ it "returns a string representation with same order as each()" do
+ h = { a: [1, 2], b: -2, d: -6, nil => nil }
+
+ pairs = []
+ h.each do |key, value|
+ pairs << key.inspect + '=>' + value.inspect
+ end
+
+ str = '{' + pairs.join(', ') + '}'
+ h.send(@method).should == str
+ end
+
+ it "calls #inspect on keys and values" do
+ key = mock('key')
+ val = mock('val')
+ key.should_receive(:inspect).and_return('key')
+ val.should_receive(:inspect).and_return('val')
+
+ { key => val }.send(@method).should == '{key=>val}'
+ end
+
+ it "does not call #to_s on a String returned from #inspect" do
+ str = "abc"
+ str.should_not_receive(:to_s)
+
+ { a: str }.send(@method).should == '{:a=>"abc"}'
+ end
+
+ it "calls #to_s on the object returned from #inspect if the Object isn't a String" do
+ obj = mock("Hash#inspect/to_s calls #to_s")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_return("abc")
+
+ { a: obj }.send(@method).should == "{:a=>abc}"
+ end
+
+ it "does not call #to_str on the object returned from #inspect when it is not a String" do
+ obj = mock("Hash#inspect/to_s does not call #to_str")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_not_receive(:to_str)
+
+ { a: obj }.send(@method).should =~ /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/
+ end
+
+ it "does not call #to_str on the object returned from #to_s when it is not a String" do
+ obj = mock("Hash#inspect/to_s does not call #to_str on #to_s result")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_return(obj)
+ obj.should_not_receive(:to_str)
+
+ { a: obj }.send(@method).should =~ /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/
+ end
+
+ it "does not swallow exceptions raised by #to_s" do
+ obj = mock("Hash#inspect/to_s does not swallow #to_s exceptions")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_raise(Exception)
+
+ -> { { a: obj }.send(@method) }.should raise_error(Exception)
+ end
+
+ it "handles hashes with recursive values" do
+ x = {}
+ x[0] = x
+ x.send(@method).should == '{0=>{...}}'
+
+ x = {}
+ y = {}
+ x[0] = y
+ y[1] = x
+ x.send(@method).should == "{0=>{1=>{...}}}"
+ y.send(@method).should == "{1=>{0=>{...}}}"
+ end
+
+ it "does not raise if inspected result is not default external encoding" do
+ utf_16be = mock("utf_16be")
+ utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode!(Encoding::UTF_16BE))
+
+ {a: utf_16be}.send(@method).should == '{:a=>"utf_16be \u3042"}'
+ end
+
+ it "works for keys and values whose #inspect return a frozen String" do
+ { true => false }.to_s.should == "{true=>false}"
+ end
+end
diff --git a/spec/ruby/core/hash/shared/update.rb b/spec/ruby/core/hash/shared/update.rb
new file mode 100644
index 0000000000..1b0eb809bf
--- /dev/null
+++ b/spec/ruby/core/hash/shared/update.rb
@@ -0,0 +1,76 @@
+describe :hash_update, shared: true do
+ it "adds the entries from other, overwriting duplicate keys. Returns self" do
+ h = { _1: 'a', _2: '3' }
+ h.send(@method, _1: '9', _9: 2).should equal(h)
+ h.should == { _1: "9", _2: "3", _9: 2 }
+ end
+
+ it "sets any duplicate key to the value of block if passed a block" do
+ h1 = { a: 2, b: -1 }
+ h2 = { a: -2, c: 1 }
+ h1.send(@method, h2) { |k,x,y| 3.14 }.should equal(h1)
+ h1.should == { c: 1, b: -1, a: 3.14 }
+
+ h1.send(@method, h1) { nil }
+ h1.should == { a: nil, b: nil, c: nil }
+ end
+
+ it "tries to convert the passed argument to a hash using #to_hash" do
+ obj = mock('{1=>2}')
+ obj.should_receive(:to_hash).and_return({ 1 => 2 })
+ { 3 => 4 }.send(@method, obj).should == { 1 => 2, 3 => 4 }
+ end
+
+ it "does not call to_hash on hash subclasses" do
+ { 3 => 4 }.send(@method, HashSpecs::ToHashHash[1 => 2]).should == { 1 => 2, 3 => 4 }
+ end
+
+ it "processes entries with same order as merge()" do
+ h = { 1 => 2, 3 => 4, 5 => 6, "x" => nil, nil => 5, [] => [] }
+ merge_bang_pairs = []
+ merge_pairs = []
+ h.merge(h) { |*arg| merge_pairs << arg }
+ h.send(@method, h) { |*arg| merge_bang_pairs << arg }
+ merge_bang_pairs.should == merge_pairs
+ end
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ -> do
+ HashSpecs.frozen_hash.send(@method, 1 => 2)
+ end.should raise_error(FrozenError)
+ end
+
+ it "checks frozen status before coercing an object with #to_hash" do
+ obj = mock("to_hash frozen")
+ # This is necessary because mock cleanup code cannot run on the frozen
+ # object.
+ def obj.to_hash() raise Exception, "should not receive #to_hash" end
+ obj.freeze
+
+ -> { HashSpecs.frozen_hash.send(@method, obj) }.should raise_error(FrozenError)
+ end
+
+ # see redmine #1571
+ it "raises a FrozenError on a frozen instance that would not be modified" do
+ -> do
+ HashSpecs.frozen_hash.send(@method, HashSpecs.empty_frozen_hash)
+ end.should raise_error(FrozenError)
+ end
+
+ it "does not raise an exception if changing the value of an existing key during iteration" do
+ hash = {1 => 2, 3 => 4, 5 => 6}
+ hash2 = {1 => :foo, 3 => :bar}
+ hash.each { hash.send(@method, hash2) }
+ hash.should == {1 => :foo, 3 => :bar, 5 => 6}
+ end
+
+ it "accepts multiple hashes" do
+ result = { a: 1 }.send(@method, { b: 2 }, { c: 3 }, { d: 4 })
+ result.should == { a: 1, b: 2, c: 3, d: 4 }
+ end
+
+ it "accepts zero arguments" do
+ hash = { a: 1 }
+ hash.send(@method).should eql(hash)
+ end
+end
diff --git a/spec/ruby/core/hash/shared/value.rb b/spec/ruby/core/hash/shared/value.rb
new file mode 100644
index 0000000000..aac76c253e
--- /dev/null
+++ b/spec/ruby/core/hash/shared/value.rb
@@ -0,0 +1,14 @@
+describe :hash_value_p, shared: true do
+ it "returns true if the value exists in the hash" do
+ { a: :b }.send(@method, :a).should == false
+ { 1 => 2 }.send(@method, 2).should == true
+ h = Hash.new(5)
+ h.send(@method, 5).should == false
+ h = Hash.new { 5 }
+ h.send(@method, 5).should == false
+ end
+
+ it "uses == semantics for comparing values" do
+ { 5 => 2.0 }.send(@method, 2).should == true
+ end
+end
diff --git a/spec/ruby/core/hash/shared/values_at.rb b/spec/ruby/core/hash/shared/values_at.rb
new file mode 100644
index 0000000000..ef3b0e8ba0
--- /dev/null
+++ b/spec/ruby/core/hash/shared/values_at.rb
@@ -0,0 +1,9 @@
+describe :hash_values_at, shared: true do
+ it "returns an array of values for the given keys" do
+ h = { a: 9, b: 'a', c: -10, d: nil }
+ h.send(@method).should be_kind_of(Array)
+ h.send(@method).should == []
+ h.send(@method, :a, :d, :b).should be_kind_of(Array)
+ h.send(@method, :a, :d, :b).should == [9, nil, 'a']
+ end
+end
diff --git a/spec/ruby/core/hash/shift_spec.rb b/spec/ruby/core/hash/shift_spec.rb
new file mode 100644
index 0000000000..ea36488a04
--- /dev/null
+++ b/spec/ruby/core/hash/shift_spec.rb
@@ -0,0 +1,101 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#shift" do
+ it "removes a pair from hash and return it" do
+ h = { a: 1, b: 2, "c" => 3, nil => 4, [] => 5 }
+ h2 = h.dup
+
+ h.size.times do |i|
+ r = h.shift
+ r.should be_kind_of(Array)
+ h2[r.first].should == r.last
+ h.size.should == h2.size - i - 1
+ end
+
+ h.should == {}
+ end
+
+ # MRI explicitly implements this behavior
+ it "allows shifting entries while iterating" do
+ h = { a: 1, b: 2, c: 3 }
+ visited = []
+ shifted = []
+ h.each_pair { |k,v|
+ visited << k
+ shifted << h.shift
+ }
+ visited.should == [:a, :b, :c]
+ shifted.should == [[:a, 1], [:b, 2], [:c, 3]]
+ h.should == {}
+ end
+
+ ruby_version_is '3.2' do
+ it "returns nil if the Hash is empty" do
+ h = {}
+ def h.default(key)
+ raise
+ end
+ h.shift.should == nil
+ end
+ end
+
+ ruby_version_is ''...'3.2' do
+ it "calls #default with nil if the Hash is empty" do
+ h = {}
+ def h.default(key)
+ key.should == nil
+ :foo
+ end
+ h.shift.should == :foo
+ end
+ end
+
+ it "returns nil from an empty hash" do
+ {}.shift.should == nil
+ end
+
+ ruby_version_is '3.2' do
+ it "returns nil for empty hashes with defaults and default procs" do
+ Hash.new(5).shift.should == nil
+ h = Hash.new { |*args| args }
+ h.shift.should == nil
+ end
+ end
+
+ ruby_version_is ''...'3.2' do
+ it "returns (computed) default for empty hashes" do
+ Hash.new(5).shift.should == 5
+ h = Hash.new { |*args| args }
+ h.shift.should == [h, nil]
+ end
+ end
+
+ it "preserves Hash invariants when removing the last item" do
+ h = { :a => 1, :b => 2 }
+ h.shift.should == [:a, 1]
+ h.shift.should == [:b, 2]
+ h[:c] = 3
+ h.should == {:c => 3}
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.shift }.should raise_error(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.shift }.should raise_error(FrozenError)
+ end
+
+ it "works when the hash is at capacity" do
+ # We try a wide range of sizes in hopes that this will cover all implementations' base Hash size.
+ results = []
+ 1.upto(100) do |n|
+ h = {}
+ n.times do |i|
+ h[i] = i
+ end
+ h.shift
+ results << h.size
+ end
+
+ results.should == 0.upto(99).to_a
+ end
+end
diff --git a/spec/ruby/core/hash/size_spec.rb b/spec/ruby/core/hash/size_spec.rb
new file mode 100644
index 0000000000..1e8abd8d97
--- /dev/null
+++ b/spec/ruby/core/hash/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/length'
+
+describe "Hash#size" do
+ it_behaves_like :hash_length, :size
+end
diff --git a/spec/ruby/core/hash/slice_spec.rb b/spec/ruby/core/hash/slice_spec.rb
new file mode 100644
index 0000000000..e3046d83d7
--- /dev/null
+++ b/spec/ruby/core/hash/slice_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+
+describe "Hash#slice" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns a new empty hash without arguments" do
+ ret = @hash.slice
+ ret.should_not equal(@hash)
+ ret.should be_an_instance_of(Hash)
+ ret.should == {}
+ end
+
+ it "returns the requested subset" do
+ @hash.slice(:c, :a).should == { c: 3, a: 1 }
+ end
+
+ it "returns a hash ordered in the order of the requested keys" do
+ @hash.slice(:c, :a).keys.should == [:c, :a]
+ end
+
+ it "returns only the keys of the original hash" do
+ @hash.slice(:a, :chunky_bacon).should == { a: 1 }
+ end
+
+ it "returns a Hash instance, even on subclasses" do
+ klass = Class.new(Hash)
+ h = klass.new
+ h[:bar] = 12
+ h[:foo] = 42
+ r = h.slice(:foo)
+ r.should == {foo: 42}
+ r.class.should == Hash
+ end
+
+ it "uses the regular Hash#[] method, even on subclasses that override it" do
+ ScratchPad.record []
+ klass = Class.new(Hash) do
+ def [](value)
+ ScratchPad << :used_subclassed_operator
+ super
+ end
+ end
+
+ h = klass.new
+ h[:bar] = 12
+ h[:foo] = 42
+ h.slice(:foo)
+
+ ScratchPad.recorded.should == []
+ end
+end
diff --git a/spec/ruby/core/hash/sort_spec.rb b/spec/ruby/core/hash/sort_spec.rb
new file mode 100644
index 0000000000..26058c845e
--- /dev/null
+++ b/spec/ruby/core/hash/sort_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#sort" do
+ it "converts self to a nested array of [key, value] arrays and sort with Array#sort" do
+ { 'a' => 'b', '1' => '2', 'b' => 'a' }.sort.should ==
+ [["1", "2"], ["a", "b"], ["b", "a"]]
+ end
+
+ it "works when some of the keys are themselves arrays" do
+ { [1,2] => 5, [1,1] => 5 }.sort.should == [[[1,1],5], [[1,2],5]]
+ end
+
+ it "uses block to sort array if passed a block" do
+ { 1 => 2, 2 => 9, 3 => 4 }.sort { |a,b| b <=> a }.should == [[3, 4], [2, 9], [1, 2]]
+ end
+end
diff --git a/spec/ruby/core/hash/store_spec.rb b/spec/ruby/core/hash/store_spec.rb
new file mode 100644
index 0000000000..7e975380ec
--- /dev/null
+++ b/spec/ruby/core/hash/store_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/store'
+
+describe "Hash#store" do
+ it_behaves_like :hash_store, :store
+end
diff --git a/spec/ruby/core/hash/to_a_spec.rb b/spec/ruby/core/hash/to_a_spec.rb
new file mode 100644
index 0000000000..8b7894a2ba
--- /dev/null
+++ b/spec/ruby/core/hash/to_a_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#to_a" do
+ it "returns a list of [key, value] pairs with same order as each()" do
+ h = { a: 1, 1 => :a, 3 => :b, b: 5 }
+ pairs = []
+
+ h.each_pair do |key, value|
+ pairs << [key, value]
+ end
+
+ h.to_a.should be_kind_of(Array)
+ h.to_a.should == pairs
+ end
+
+ it "is called for Enumerable#entries" do
+ h = { a: 1, 1 => :a, 3 => :b, b: 5 }
+ pairs = []
+
+ h.each_pair do |key, value|
+ pairs << [key, value]
+ end
+
+ ent = h.entries
+ ent.should be_kind_of(Array)
+ ent.should == pairs
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a not tainted array if self is tainted" do
+ {}.taint.to_a.tainted?.should be_false
+ end
+
+ it "returns a trusted array if self is untrusted" do
+ {}.untrust.to_a.untrusted?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/to_h_spec.rb b/spec/ruby/core/hash/to_h_spec.rb
new file mode 100644
index 0000000000..75ebce68b1
--- /dev/null
+++ b/spec/ruby/core/hash/to_h_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#to_h" do
+ it "returns self for Hash instances" do
+ h = {}
+ h.to_h.should equal(h)
+ end
+
+ describe "when called on a subclass of Hash" do
+ before :each do
+ @h = HashSpecs::MyHash.new
+ @h[:foo] = :bar
+ end
+
+ it "returns a new Hash instance" do
+ @h.to_h.should be_an_instance_of(Hash)
+ @h.to_h.should == @h
+ @h[:foo].should == :bar
+ end
+
+ it "copies the default" do
+ @h.default = 42
+ @h.to_h.default.should == 42
+ @h[:hello].should == 42
+ end
+
+ it "copies the default_proc" do
+ @h.default_proc = prc = Proc.new{ |h, k| h[k] = 2 * k }
+ @h.to_h.default_proc.should == prc
+ @h[42].should == 84
+ end
+ end
+
+ context "with block" do
+ it "converts [key, value] pairs returned by the block to a hash" do
+ { a: 1, b: 2 }.to_h { |k, v| [k.to_s, v*v]}.should == { "a" => 1, "b" => 4 }
+ end
+
+ it "raises ArgumentError if block returns longer or shorter array" do
+ -> do
+ { a: 1, b: 2 }.to_h { |k, v| [k.to_s, v*v, 1] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+
+ -> do
+ { a: 1, b: 2 }.to_h { |k, v| [k] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ { a: 1, b: 2 }.to_h { |k, v| "not-array" }
+ end.should raise_error(TypeError, /wrong element type String/)
+ end
+
+ it "coerces returned pair to Array with #to_ary" do
+ x = mock('x')
+ x.stub!(:to_ary).and_return([:b, 'b'])
+
+ { a: 1 }.to_h { |k| x }.should == { :b => 'b' }
+ end
+
+ it "does not coerce returned pair to Array with #to_a" do
+ x = mock('x')
+ x.stub!(:to_a).and_return([:b, 'b'])
+
+ -> do
+ { a: 1 }.to_h { |k| x }
+ end.should raise_error(TypeError, /wrong element type MockObject/)
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/to_hash_spec.rb b/spec/ruby/core/hash/to_hash_spec.rb
new file mode 100644
index 0000000000..f479fa1fb2
--- /dev/null
+++ b/spec/ruby/core/hash/to_hash_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#to_hash" do
+ it "returns self for Hash instances" do
+ h = {}
+ h.to_hash.should equal(h)
+ end
+
+ it "returns self for instances of subclasses of Hash" do
+ h = HashSpecs::MyHash.new
+ h.to_hash.should equal(h)
+ end
+end
diff --git a/spec/ruby/core/hash/to_proc_spec.rb b/spec/ruby/core/hash/to_proc_spec.rb
new file mode 100644
index 0000000000..8f5d21beb5
--- /dev/null
+++ b/spec/ruby/core/hash/to_proc_spec.rb
@@ -0,0 +1,99 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#to_proc" do
+ before :each do
+ @key = Object.new
+ @value = Object.new
+ @hash = { @key => @value }
+ @default = Object.new
+ @unstored = Object.new
+ end
+
+ it "returns an instance of Proc" do
+ @hash.to_proc.should be_an_instance_of Proc
+ end
+
+ describe "the returned proc" do
+ before :each do
+ @proc = @hash.to_proc
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "is not a lambda" do
+ @proc.should_not.lambda?
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "is a lambda" do
+ @proc.should.lambda?
+ end
+
+ it "has an arity of 1" do
+ @proc.arity.should == 1
+ end
+ end
+
+ it "raises ArgumentError if not passed exactly one argument" do
+ -> {
+ @proc.call
+ }.should raise_error(ArgumentError)
+
+ -> {
+ @proc.call 1, 2
+ }.should raise_error(ArgumentError)
+ end
+
+ context "with a stored key" do
+ it "returns the paired value" do
+ @proc.call(@key).should equal(@value)
+ end
+ end
+
+ context "passed as a block" do
+ it "retrieves the hash's values" do
+ [@key].map(&@proc)[0].should equal(@value)
+ end
+
+ context "to instance_exec" do
+ it "always retrieves the original hash's values" do
+ hash = {foo: 1, bar: 2}
+ proc = hash.to_proc
+
+ hash.instance_exec(:foo, &proc).should == 1
+
+ hash2 = {quux: 1}
+ hash2.instance_exec(:foo, &proc).should == 1
+ end
+ end
+ end
+
+ context "with no stored key" do
+ it "returns nil" do
+ @proc.call(@unstored).should be_nil
+ end
+
+ context "when the hash has a default value" do
+ before :each do
+ @hash.default = @default
+ end
+
+ it "returns the default value" do
+ @proc.call(@unstored).should equal(@default)
+ end
+ end
+
+ context "when the hash has a default proc" do
+ it "returns an evaluated value from the default proc" do
+ @hash.default_proc = -> hash, called_with { [hash.keys, called_with] }
+ @proc.call(@unstored).should == [[@key], @unstored]
+ end
+ end
+ end
+
+ it "raises an ArgumentError when calling #call on the Proc with no arguments" do
+ -> { @hash.to_proc.call }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/to_s_spec.rb b/spec/ruby/core/hash/to_s_spec.rb
new file mode 100644
index 0000000000..e52b09962e
--- /dev/null
+++ b/spec/ruby/core/hash/to_s_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/to_s'
+
+describe "Hash#to_s" do
+ it_behaves_like :hash_to_s, :to_s
+end
diff --git a/spec/ruby/core/hash/transform_keys_spec.rb b/spec/ruby/core/hash/transform_keys_spec.rb
new file mode 100644
index 0000000000..361089ca97
--- /dev/null
+++ b/spec/ruby/core/hash/transform_keys_spec.rb
@@ -0,0 +1,147 @@
+require_relative '../../spec_helper'
+
+describe "Hash#transform_keys" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns new hash" do
+ ret = @hash.transform_keys(&:succ)
+ ret.should_not equal(@hash)
+ ret.should be_an_instance_of(Hash)
+ end
+
+ it "sets the result as transformed keys with the given block" do
+ @hash.transform_keys(&:succ).should == { b: 1, c: 2, d: 3 }
+ end
+
+ it "keeps last pair if new keys conflict" do
+ @hash.transform_keys { |_| :a }.should == { a: 3 }
+ end
+
+ it "makes both hashes to share values" do
+ value = [1, 2, 3]
+ new_hash = { a: value }.transform_keys(&:upcase)
+ new_hash[:A].should equal(value)
+ end
+
+ context "when no block is given" do
+ it "returns a sized Enumerator" do
+ enumerator = @hash.transform_keys
+ enumerator.should be_an_instance_of(Enumerator)
+ enumerator.size.should == @hash.size
+ enumerator.each(&:succ).should == { b: 1, c: 2, d: 3 }
+ end
+ end
+
+ it "returns a Hash instance, even on subclasses" do
+ klass = Class.new(Hash)
+ h = klass.new
+ h[:foo] = 42
+ r = h.transform_keys{|v| :"x#{v}"}
+ r.keys.should == [:xfoo]
+ r.class.should == Hash
+ end
+
+ ruby_version_is "3.0" do
+ it "allows a hash argument" do
+ @hash.transform_keys({ a: :A, b: :B, c: :C }).should == { A: 1, B: 2, C: 3 }
+ end
+
+ it "allows a partial transformation of keys when using a hash argument" do
+ @hash.transform_keys({ a: :A, c: :C }).should == { A: 1, b: 2, C: 3 }
+ end
+
+ it "allows a combination of hash and block argument" do
+ @hash.transform_keys({ a: :A }, &:to_s).should == { A: 1, 'b' => 2, 'c' => 3 }
+ end
+ end
+end
+
+describe "Hash#transform_keys!" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3, d: 4 }
+ @initial_pairs = @hash.dup
+ end
+
+ it "returns self" do
+ @hash.transform_keys!(&:succ).should equal(@hash)
+ end
+
+ it "updates self as transformed values with the given block" do
+ @hash.transform_keys!(&:to_s)
+ @hash.should == { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 }
+ end
+
+ it "prevents conflicts between new keys and old ones" do
+ @hash.transform_keys!(&:succ)
+ @hash.should == { b: 1, c: 2, d: 3, e: 4 }
+ end
+
+ ruby_version_is ""..."3.0.2" do # https://bugs.ruby-lang.org/issues/17735
+ it "returns the processed keys if we break from the block" do
+ @hash.transform_keys! do |v|
+ break if v == :c
+ v.succ
+ end
+ @hash.should == { b: 1, c: 2 }
+ end
+ end
+
+ ruby_version_is "3.0.2" do
+ it "returns the processed keys and non evaluated keys if we break from the block" do
+ @hash.transform_keys! do |v|
+ break if v == :c
+ v.succ
+ end
+ @hash.should == { b: 1, c: 2, d: 4 }
+ end
+ end
+
+ it "keeps later pair if new keys conflict" do
+ @hash.transform_keys! { |_| :a }.should == { a: 4 }
+ end
+
+ context "when no block is given" do
+ it "returns a sized Enumerator" do
+ enumerator = @hash.transform_keys!
+ enumerator.should be_an_instance_of(Enumerator)
+ enumerator.size.should == @hash.size
+ enumerator.each(&:upcase).should == { A: 1, B: 2, C: 3, D: 4 }
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "allows a hash argument" do
+ @hash.transform_keys!({ a: :A, b: :B, c: :C, d: :D })
+ @hash.should == { A: 1, B: 2, C: 3, D: 4 }
+ end
+ end
+
+ describe "on frozen instance" do
+ before :each do
+ @hash.freeze
+ end
+
+ it "raises a FrozenError on an empty hash" do
+ ->{ {}.freeze.transform_keys!(&:upcase) }.should raise_error(FrozenError)
+ end
+
+ it "keeps pairs and raises a FrozenError" do
+ ->{ @hash.transform_keys!(&:upcase) }.should raise_error(FrozenError)
+ @hash.should == @initial_pairs
+ end
+
+ ruby_version_is "3.0" do
+ it "raises a FrozenError on hash argument" do
+ ->{ @hash.transform_keys!({ a: :A, b: :B, c: :C }) }.should raise_error(FrozenError)
+ end
+ end
+
+ context "when no block is given" do
+ it "does not raise an exception" do
+ @hash.transform_keys!.should be_an_instance_of(Enumerator)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/transform_values_spec.rb b/spec/ruby/core/hash/transform_values_spec.rb
new file mode 100644
index 0000000000..acb469416a
--- /dev/null
+++ b/spec/ruby/core/hash/transform_values_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+
+describe "Hash#transform_values" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns new hash" do
+ ret = @hash.transform_values(&:succ)
+ ret.should_not equal(@hash)
+ ret.should be_an_instance_of(Hash)
+ end
+
+ it "sets the result as transformed values with the given block" do
+ @hash.transform_values(&:succ).should == { a: 2, b: 3, c: 4 }
+ end
+
+ it "makes both hashes to share keys" do
+ key = [1, 2, 3]
+ new_hash = { key => 1 }.transform_values(&:succ)
+ new_hash[key].should == 2
+ new_hash.keys[0].should equal(key)
+ end
+
+ context "when no block is given" do
+ it "returns a sized Enumerator" do
+ enumerator = @hash.transform_values
+ enumerator.should be_an_instance_of(Enumerator)
+ enumerator.size.should == @hash.size
+ enumerator.each(&:succ).should == { a: 2, b: 3, c: 4 }
+ end
+ end
+
+ it "returns a Hash instance, even on subclasses" do
+ klass = Class.new(Hash)
+ h = klass.new
+ h[:foo] = 42
+ r = h.transform_values{|v| 2 * v}
+ r[:foo].should == 84
+ r.class.should == Hash
+ end
+end
+
+describe "Hash#transform_values!" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ @initial_pairs = @hash.dup
+ end
+
+ it "returns self" do
+ @hash.transform_values!(&:succ).should equal(@hash)
+ end
+
+ it "updates self as transformed values with the given block" do
+ @hash.transform_values!(&:succ)
+ @hash.should == { a: 2, b: 3, c: 4 }
+ end
+
+ it "partially modifies the contents if we broke from the block" do
+ @hash.transform_values! do |v|
+ break if v == 3
+ 100 + v
+ end
+ @hash.should == { a: 101, b: 102, c: 3}
+ end
+
+ context "when no block is given" do
+ it "returns a sized Enumerator" do
+ enumerator = @hash.transform_values!
+ enumerator.should be_an_instance_of(Enumerator)
+ enumerator.size.should == @hash.size
+ enumerator.each(&:succ)
+ @hash.should == { a: 2, b: 3, c: 4 }
+ end
+ end
+
+ describe "on frozen instance" do
+ before :each do
+ @hash.freeze
+ end
+
+ it "raises a FrozenError on an empty hash" do
+ ->{ {}.freeze.transform_values!(&:succ) }.should raise_error(FrozenError)
+ end
+
+ it "keeps pairs and raises a FrozenError" do
+ ->{ @hash.transform_values!(&:succ) }.should raise_error(FrozenError)
+ @hash.should == @initial_pairs
+ end
+
+ context "when no block is given" do
+ it "does not raise an exception" do
+ @hash.transform_values!.should be_an_instance_of(Enumerator)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/try_convert_spec.rb b/spec/ruby/core/hash/try_convert_spec.rb
new file mode 100644
index 0000000000..44195c5010
--- /dev/null
+++ b/spec/ruby/core/hash/try_convert_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash.try_convert" do
+ it "returns the argument if it's a Hash" do
+ x = Hash.new
+ Hash.try_convert(x).should equal(x)
+ end
+
+ it "returns the argument if it's a kind of Hash" do
+ x = HashSpecs::MyHash.new
+ Hash.try_convert(x).should equal(x)
+ end
+
+ it "returns nil when the argument does not respond to #to_hash" do
+ Hash.try_convert(Object.new).should be_nil
+ end
+
+ it "sends #to_hash to the argument and returns the result if it's nil" do
+ obj = mock("to_hash")
+ obj.should_receive(:to_hash).and_return(nil)
+ Hash.try_convert(obj).should be_nil
+ end
+
+ it "sends #to_hash to the argument and returns the result if it's a Hash" do
+ x = Hash.new
+ obj = mock("to_hash")
+ obj.should_receive(:to_hash).and_return(x)
+ Hash.try_convert(obj).should equal(x)
+ end
+
+ it "sends #to_hash to the argument and returns the result if it's a kind of Hash" do
+ x = HashSpecs::MyHash.new
+ obj = mock("to_hash")
+ obj.should_receive(:to_hash).and_return(x)
+ Hash.try_convert(obj).should equal(x)
+ end
+
+ it "sends #to_hash to the argument and raises TypeError if it's not a kind of Hash" do
+ obj = mock("to_hash")
+ obj.should_receive(:to_hash).and_return(Object.new)
+ -> { Hash.try_convert obj }.should raise_error(TypeError)
+ end
+
+ it "does not rescue exceptions raised by #to_hash" do
+ obj = mock("to_hash")
+ obj.should_receive(:to_hash).and_raise(RuntimeError)
+ -> { Hash.try_convert obj }.should raise_error(RuntimeError)
+ end
+end
diff --git a/spec/ruby/core/hash/update_spec.rb b/spec/ruby/core/hash/update_spec.rb
new file mode 100644
index 0000000000..0975045ad1
--- /dev/null
+++ b/spec/ruby/core/hash/update_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/update'
+
+describe "Hash#update" do
+ it_behaves_like :hash_update, :update
+end
diff --git a/spec/ruby/core/hash/value_spec.rb b/spec/ruby/core/hash/value_spec.rb
new file mode 100644
index 0000000000..0ab16a5d1b
--- /dev/null
+++ b/spec/ruby/core/hash/value_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/value'
+
+describe "Hash#value?" do
+ it_behaves_like :hash_value_p, :value?
+end
diff --git a/spec/ruby/core/hash/values_at_spec.rb b/spec/ruby/core/hash/values_at_spec.rb
new file mode 100644
index 0000000000..b620a279ba
--- /dev/null
+++ b/spec/ruby/core/hash/values_at_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/values_at'
+
+describe "Hash#values_at" do
+ it_behaves_like :hash_values_at, :values_at
+end
diff --git a/spec/ruby/core/hash/values_spec.rb b/spec/ruby/core/hash/values_spec.rb
new file mode 100644
index 0000000000..9f2a481a48
--- /dev/null
+++ b/spec/ruby/core/hash/values_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#values" do
+ it "returns an array of values" do
+ h = { 1 => :a, 'a' => :a, 'the' => 'lang' }
+ h.values.should be_kind_of(Array)
+ h.values.sort {|a, b| a.to_s <=> b.to_s}.should == [:a, :a, 'lang']
+ end
+end
diff --git a/spec/ruby/core/integer/abs_spec.rb b/spec/ruby/core/integer/abs_spec.rb
new file mode 100644
index 0000000000..c40356db12
--- /dev/null
+++ b/spec/ruby/core/integer/abs_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/abs'
+
+describe "Integer#abs" do
+ it_behaves_like :integer_abs, :abs
+end
diff --git a/spec/ruby/core/integer/allbits_spec.rb b/spec/ruby/core/integer/allbits_spec.rb
new file mode 100644
index 0000000000..edce4b15e7
--- /dev/null
+++ b/spec/ruby/core/integer/allbits_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+describe "Integer#allbits?" do
+ it "returns true if and only if all the bits of the argument are set in the receiver" do
+ 42.allbits?(42).should == true
+ 0b1010_1010.allbits?(0b1000_0010).should == true
+ 0b1010_1010.allbits?(0b1000_0001).should == false
+ 0b1000_0010.allbits?(0b1010_1010).should == false
+ (0b1010_1010 | bignum_value).allbits?(0b1000_0010 | bignum_value).should == true
+ (0b1010_1010 | bignum_value).allbits?(0b1000_0001 | bignum_value).should == false
+ (0b1000_0010 | bignum_value).allbits?(0b1010_1010 | bignum_value).should == false
+ end
+
+ it "handles negative values using two's complement notation" do
+ (~0b1).allbits?(42).should == true
+ (-42).allbits?(-42).should == true
+ (~0b1010_1010).allbits?(~0b1110_1011).should == true
+ (~0b1010_1010).allbits?(~0b1000_0010).should == false
+ (~(0b1010_1010 | bignum_value)).allbits?(~(0b1110_1011 | bignum_value)).should == true
+ (~(0b1010_1010 | bignum_value)).allbits?(~(0b1000_0010 | bignum_value)).should == false
+ end
+
+ it "coerces the rhs using to_int" do
+ obj = mock("the int 0b10")
+ obj.should_receive(:to_int).and_return(0b10)
+ 0b110.allbits?(obj).should == true
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:coerce).any_number_of_times.and_return([42,10])
+ 13.allbits?(obj)
+ }.should raise_error(TypeError)
+ -> { 13.allbits?("10") }.should raise_error(TypeError)
+ -> { 13.allbits?(:symbol) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/anybits_spec.rb b/spec/ruby/core/integer/anybits_spec.rb
new file mode 100644
index 0000000000..e0449074a2
--- /dev/null
+++ b/spec/ruby/core/integer/anybits_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe "Integer#anybits?" do
+ it "returns true if and only if all the bits of the argument are set in the receiver" do
+ 42.anybits?(42).should == true
+ 0b1010_1010.anybits?(0b1000_0010).should == true
+ 0b1010_1010.anybits?(0b1000_0001).should == true
+ 0b1000_0010.anybits?(0b0010_1100).should == false
+ different_bignum = (2 * bignum_value) & (~bignum_value)
+ (0b1010_1010 | different_bignum).anybits?(0b1000_0010 | bignum_value).should == true
+ (0b1010_1010 | different_bignum).anybits?(0b0010_1100 | bignum_value).should == true
+ (0b1000_0010 | different_bignum).anybits?(0b0010_1100 | bignum_value).should == false
+ end
+
+ it "handles negative values using two's complement notation" do
+ (~42).anybits?(42).should == false
+ (-42).anybits?(-42).should == true
+ (~0b100).anybits?(~0b1).should == true
+ (~(0b100 | bignum_value)).anybits?(~(0b1 | bignum_value)).should == true
+ end
+
+ it "coerces the rhs using to_int" do
+ obj = mock("the int 0b10")
+ obj.should_receive(:to_int).and_return(0b10)
+ 0b110.anybits?(obj).should == true
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:coerce).any_number_of_times.and_return([42,10])
+ 13.anybits?(obj)
+ }.should raise_error(TypeError)
+ -> { 13.anybits?("10") }.should raise_error(TypeError)
+ -> { 13.anybits?(:symbol) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/bit_and_spec.rb b/spec/ruby/core/integer/bit_and_spec.rb
new file mode 100644
index 0000000000..8de5a14aaa
--- /dev/null
+++ b/spec/ruby/core/integer/bit_and_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+
+describe "Integer#&" do
+ context "fixnum" do
+ it "returns self bitwise AND other" do
+ (256 & 16).should == 0
+ (2010 & 5).should == 0
+ (65535 & 1).should == 1
+ (0xffff & bignum_value + 0xffff_ffff).should == 65535
+ end
+
+ it "returns self bitwise AND other when one operand is negative" do
+ ((1 << 33) & -1).should == (1 << 33)
+ (-1 & (1 << 33)).should == (1 << 33)
+
+ ((-(1<<33)-1) & 5).should == 5
+ (5 & (-(1<<33)-1)).should == 5
+ end
+
+ it "returns self bitwise AND other when both operands are negative" do
+ (-5 & -1).should == -5
+ (-3 & -4).should == -4
+ (-12 & -13).should == -16
+ (-13 & -12).should == -16
+ end
+
+ it "returns self bitwise AND a bignum" do
+ (-1 & 2**64).should == 18446744073709551616
+ end
+
+ it "coerces the rhs and calls #coerce" do
+ obj = mock("fixnum bit and")
+ obj.should_receive(:coerce).with(6).and_return([3, 6])
+ (6 & obj).should == 2
+ end
+
+ it "raises a TypeError when passed a Float" do
+ -> { (3 & 3.4) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("fixnum bit and")
+ obj.should_not_receive(:to_int)
+
+ -> { 3 & obj }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(5)
+ end
+
+ it "returns self bitwise AND other" do
+ @bignum = bignum_value(5)
+ (@bignum & 3).should == 1
+ (@bignum & 52).should == 4
+ (@bignum & bignum_value(9921)).should == 18446744073709551617
+
+ ((2*bignum_value) & 1).should == 0
+ ((2*bignum_value) & (2*bignum_value)).should == 36893488147419103232
+ end
+
+ it "returns self bitwise AND other when one operand is negative" do
+ ((2*bignum_value) & -1).should == (2*bignum_value)
+ ((4*bignum_value) & -1).should == (4*bignum_value)
+ (@bignum & -0xffffffffffffff5).should == 18446744073709551617
+ (@bignum & -@bignum).should == 1
+ (@bignum & -0x8000000000000000).should == 18446744073709551616
+ end
+
+ it "returns self bitwise AND other when both operands are negative" do
+ (-@bignum & -0x4000000000000005).should == -23058430092136939525
+ (-@bignum & -@bignum).should == -18446744073709551621
+ (-@bignum & -0x4000000000000000).should == -23058430092136939520
+ end
+
+ it "returns self bitwise AND other when both are negative and a multiple in bitsize of Fixnum::MIN" do
+ val = - ((1 << 93) - 1)
+ (val & val).should == val
+
+ val = - ((1 << 126) - 1)
+ (val & val).should == val
+ end
+
+ it "raises a TypeError when passed a Float" do
+ -> { (@bignum & 3.4) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("bignum bit and")
+ obj.should_not_receive(:to_int)
+
+ -> { @bignum & obj }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/bit_length_spec.rb b/spec/ruby/core/integer/bit_length_spec.rb
new file mode 100644
index 0000000000..827007b7e0
--- /dev/null
+++ b/spec/ruby/core/integer/bit_length_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+
+describe "Integer#bit_length" do
+ context "fixnum" do
+ it "returns the position of the leftmost bit of a positive number" do
+ 0.bit_length.should == 0
+ 1.bit_length.should == 1
+ 2.bit_length.should == 2
+ 3.bit_length.should == 2
+ 4.bit_length.should == 3
+ n = fixnum_max.bit_length
+ fixnum_max[n].should == 0
+ fixnum_max[n - 1].should == 1
+
+ 0.bit_length.should == 0
+ 1.bit_length.should == 1
+ 0xff.bit_length.should == 8
+ 0x100.bit_length.should == 9
+ (2**12 - 1).bit_length.should == 12
+ (2**12).bit_length.should == 13
+ (2**12 + 1).bit_length.should == 13
+ end
+
+ it "returns the position of the leftmost 0 bit of a negative number" do
+ -1.bit_length.should == 0
+ -2.bit_length.should == 1
+ -3.bit_length.should == 2
+ -4.bit_length.should == 2
+ -5.bit_length.should == 3
+ n = fixnum_min.bit_length
+ fixnum_min[n].should == 1
+ fixnum_min[n - 1].should == 0
+
+ (-2**12 - 1).bit_length.should == 13
+ (-2**12).bit_length.should == 12
+ (-2**12 + 1).bit_length.should == 12
+ -0x101.bit_length.should == 9
+ -0x100.bit_length.should == 8
+ -0xff.bit_length.should == 8
+ -2.bit_length.should == 1
+ -1.bit_length.should == 0
+ end
+ end
+
+ context "bignum" do
+ it "returns the position of the leftmost bit of a positive number" do
+ (2**1000-1).bit_length.should == 1000
+ (2**1000).bit_length.should == 1001
+ (2**1000+1).bit_length.should == 1001
+
+ (2**10000-1).bit_length.should == 10000
+ (2**10000).bit_length.should == 10001
+ (2**10000+1).bit_length.should == 10001
+
+ (1 << 100).bit_length.should == 101
+ (1 << 100).succ.bit_length.should == 101
+ (1 << 100).pred.bit_length.should == 100
+ (1 << 10000).bit_length.should == 10001
+ end
+
+ it "returns the position of the leftmost 0 bit of a negative number" do
+ (-2**10000-1).bit_length.should == 10001
+ (-2**10000).bit_length.should == 10000
+ (-2**10000+1).bit_length.should == 10000
+
+ (-2**1000-1).bit_length.should == 1001
+ (-2**1000).bit_length.should == 1000
+ (-2**1000+1).bit_length.should == 1000
+
+ ((-1 << 100)-1).bit_length.should == 101
+ ((-1 << 100)-1).succ.bit_length.should == 100
+ ((-1 << 100)-1).pred.bit_length.should == 101
+ ((-1 << 10000)-1).bit_length.should == 10001
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/bit_or_spec.rb b/spec/ruby/core/integer/bit_or_spec.rb
new file mode 100644
index 0000000000..6f4279c170
--- /dev/null
+++ b/spec/ruby/core/integer/bit_or_spec.rb
@@ -0,0 +1,89 @@
+require_relative '../../spec_helper'
+
+describe "Integer#|" do
+ context "fixnum" do
+ it "returns self bitwise OR other" do
+ (1 | 0).should == 1
+ (5 | 4).should == 5
+ (5 | 6).should == 7
+ (248 | 4096).should == 4344
+ (0xffff | bignum_value + 0xf0f0).should == 0x1_0000_0000_0000_ffff
+ end
+
+ it "returns self bitwise OR other when one operand is negative" do
+ ((1 << 33) | -1).should == -1
+ (-1 | (1 << 33)).should == -1
+
+ ((-(1<<33)-1) | 5).should == -8589934593
+ (5 | (-(1<<33)-1)).should == -8589934593
+ end
+
+ it "returns self bitwise OR other when both operands are negative" do
+ (-5 | -1).should == -1
+ (-3 | -4).should == -3
+ (-12 | -13).should == -9
+ (-13 | -12).should == -9
+ end
+
+ it "returns self bitwise OR a bignum" do
+ (-1 | 2**64).should == -1
+ end
+
+ it "coerces the rhs and calls #coerce" do
+ obj = mock("fixnum bit and")
+ obj.should_receive(:coerce).with(6).and_return([3, 6])
+ (6 & obj).should == 2
+ end
+
+ it "raises a TypeError when passed a Float" do
+ -> { (3 | 3.4) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("integer bit or")
+ obj.should_not_receive(:to_int)
+
+ -> { 3 | obj }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(11)
+ end
+
+ it "returns self bitwise OR other" do
+ (@bignum | 2).should == 18446744073709551627
+ (@bignum | 9).should == 18446744073709551627
+ (@bignum | bignum_value).should == 18446744073709551627
+ end
+
+ it "returns self bitwise OR other when one operand is negative" do
+ (@bignum | -0x40000000000000000).should == -55340232221128654837
+ (@bignum | -@bignum).should == -1
+ (@bignum | -0x8000000000000000).should == -9223372036854775797
+ end
+
+ it "returns self bitwise OR other when both operands are negative" do
+ (-@bignum | -0x4000000000000005).should == -1
+ (-@bignum | -@bignum).should == -18446744073709551627
+ (-@bignum | -0x4000000000000000).should == -11
+ end
+
+ it "raises a TypeError when passed a Float" do
+ not_supported_on :opal do
+ -> {
+ bignum_value | bignum_value(0xffff).to_f
+ }.should raise_error(TypeError)
+ end
+ -> { @bignum | 9.9 }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("bignum bit or")
+ obj.should_not_receive(:to_int)
+
+ -> { @bignum | obj }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/bit_xor_spec.rb b/spec/ruby/core/integer/bit_xor_spec.rb
new file mode 100644
index 0000000000..f1150a20d5
--- /dev/null
+++ b/spec/ruby/core/integer/bit_xor_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../../spec_helper'
+
+describe "Integer#^" do
+ context "fixnum" do
+ it "returns self bitwise EXCLUSIVE OR other" do
+ (3 ^ 5).should == 6
+ (-2 ^ -255).should == 255
+ (5 ^ bignum_value + 0xffff_ffff).should == 0x1_0000_0000_ffff_fffa
+ end
+
+ it "returns self bitwise XOR other when one operand is negative" do
+ ((1 << 33) ^ -1).should == -8589934593
+ (-1 ^ (1 << 33)).should == -8589934593
+
+ ((-(1<<33)-1) ^ 5).should == -8589934598
+ (5 ^ (-(1<<33)-1)).should == -8589934598
+ end
+
+ it "returns self bitwise XOR other when both operands are negative" do
+ (-5 ^ -1).should == 4
+ (-3 ^ -4).should == 1
+ (-12 ^ -13).should == 7
+ (-13 ^ -12).should == 7
+ end
+
+ it "returns self bitwise EXCLUSIVE OR a bignum" do
+ (-1 ^ 2**64).should == -18446744073709551617
+ end
+
+ it "coerces the rhs and calls #coerce" do
+ obj = mock("fixnum bit and")
+ obj.should_receive(:coerce).with(6).and_return([3, 6])
+ (6 ^ obj).should == 5
+ end
+
+ it "raises a TypeError when passed a Float" do
+ -> { (3 ^ 3.4) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("integer bit xor")
+ obj.should_not_receive(:to_int)
+
+ -> { 3 ^ obj }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(18)
+ end
+
+ it "returns self bitwise EXCLUSIVE OR other" do
+ (@bignum ^ 2).should == 18446744073709551632
+ (@bignum ^ @bignum).should == 0
+ (@bignum ^ 14).should == 18446744073709551644
+ end
+
+ it "returns self bitwise EXCLUSIVE OR other when one operand is negative" do
+ (@bignum ^ -0x40000000000000000).should == -55340232221128654830
+ (@bignum ^ -@bignum).should == -4
+ (@bignum ^ -0x8000000000000000).should == -27670116110564327406
+ end
+
+ it "returns self bitwise EXCLUSIVE OR other when both operands are negative" do
+ (-@bignum ^ -0x40000000000000000).should == 55340232221128654830
+ (-@bignum ^ -@bignum).should == 0
+ (-@bignum ^ -0x4000000000000000).should == 23058430092136939502
+ end
+
+ it "returns self bitwise EXCLUSIVE OR other when all bits are 1 and other value is negative" do
+ (9903520314283042199192993791 ^ -1).should == -9903520314283042199192993792
+ (784637716923335095479473677900958302012794430558004314111 ^ -1).should ==
+ -784637716923335095479473677900958302012794430558004314112
+ end
+
+ it "raises a TypeError when passed a Float" do
+ not_supported_on :opal do
+ -> {
+ bignum_value ^ bignum_value(0xffff).to_f
+ }.should raise_error(TypeError)
+ end
+ -> { @bignum ^ 14.5 }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("bignum bit xor")
+ obj.should_not_receive(:to_int)
+
+ -> { @bignum ^ obj }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/case_compare_spec.rb b/spec/ruby/core/integer/case_compare_spec.rb
new file mode 100644
index 0000000000..e5dde2c64a
--- /dev/null
+++ b/spec/ruby/core/integer/case_compare_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal'
+
+describe "Integer#===" do
+ it_behaves_like :integer_equal, :===
+end
diff --git a/spec/ruby/core/integer/ceil_spec.rb b/spec/ruby/core/integer/ceil_spec.rb
new file mode 100644
index 0000000000..13bdaf838d
--- /dev/null
+++ b/spec/ruby/core/integer/ceil_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+require_relative 'shared/integer_rounding'
+
+describe "Integer#ceil" do
+ it_behaves_like :integer_to_i, :ceil
+ it_behaves_like :integer_rounding_positive_precision, :ceil
+
+ context "precision argument specified as part of the ceil method is negative" do
+ it "returns the smallest integer greater than self with at least precision.abs trailing zeros" do
+ 18.ceil(-1).should eql(20)
+ 18.ceil(-2).should eql(100)
+ 18.ceil(-3).should eql(1000)
+ -1832.ceil(-1).should eql(-1830)
+ -1832.ceil(-2).should eql(-1800)
+ -1832.ceil(-3).should eql(-1000)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/chr_spec.rb b/spec/ruby/core/integer/chr_spec.rb
new file mode 100644
index 0000000000..8fe20ff812
--- /dev/null
+++ b/spec/ruby/core/integer/chr_spec.rb
@@ -0,0 +1,257 @@
+require_relative '../../spec_helper'
+
+describe "Integer#chr without argument" do
+ it "returns a String" do
+ 17.chr.should be_an_instance_of(String)
+ end
+
+ it "returns a new String for each call" do
+ 82.chr.should_not equal(82.chr)
+ end
+
+ it "raises a RangeError is self is less than 0" do
+ -> { -1.chr }.should raise_error(RangeError)
+ -> { (-bignum_value).chr }.should raise_error(RangeError)
+ end
+
+ it "raises a RangeError if self is too large" do
+ -> { 2206368128.chr(Encoding::UTF_8) }.should raise_error(RangeError)
+ end
+
+ describe "when Encoding.default_internal is nil" do
+ describe "and self is between 0 and 127 (inclusive)" do
+ it "returns a US-ASCII String" do
+ (0..127).each do |c|
+ c.chr.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ it "returns a String encoding self interpreted as a US-ASCII codepoint" do
+ (0..127).each do |c|
+ c.chr.bytes.to_a.should == [c]
+ end
+ end
+ end
+
+ describe "and self is between 128 and 255 (inclusive)" do
+ it "returns a binary String" do
+ (128..255).each do |c|
+ c.chr.encoding.should == Encoding::BINARY
+ end
+ end
+
+ it "returns a String containing self interpreted as a byte" do
+ (128..255).each do |c|
+ c.chr.bytes.to_a.should == [c]
+ end
+ end
+ end
+
+ it "raises a RangeError is self is greater than 255" do
+ -> { 256.chr }.should raise_error(RangeError)
+ -> { bignum_value.chr }.should raise_error(RangeError)
+ end
+ end
+
+ describe "when Encoding.default_internal is not nil" do
+ before do
+ @default_internal = Encoding.default_internal
+ end
+
+ after do
+ Encoding.default_internal = @default_internal
+ end
+
+ describe "and self is between 0 and 127 (inclusive)" do
+ it "returns a US-ASCII String" do
+ (0..127).each do |c|
+ Encoding.default_internal = Encoding::UTF_8
+ c.chr.encoding.should == Encoding::US_ASCII
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ c.chr.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ it "returns a String encoding self interpreted as a US-ASCII codepoint" do
+ (0..127).each do |c|
+ Encoding.default_internal = Encoding::UTF_8
+ c.chr.bytes.to_a.should == [c]
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ c.chr.bytes.to_a.should == [c]
+ end
+ end
+ end
+
+ describe "and self is between 128 and 255 (inclusive)" do
+ it "returns a binary String" do
+ (128..255).each do |c|
+ Encoding.default_internal = Encoding::UTF_8
+ c.chr.encoding.should == Encoding::BINARY
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ c.chr.encoding.should == Encoding::BINARY
+ end
+ end
+
+ it "returns a String containing self interpreted as a byte" do
+ (128..255).each do |c|
+ Encoding.default_internal = Encoding::UTF_8
+ c.chr.bytes.to_a.should == [c]
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ c.chr.bytes.to_a.should == [c]
+ end
+ end
+ end
+
+ describe "and self is greater than 255" do
+ it "returns a String with the default internal encoding" do
+ Encoding.default_internal = Encoding::UTF_8
+ 0x0100.chr.encoding.should == Encoding::UTF_8
+ 0x3000.chr.encoding.should == Encoding::UTF_8
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ 0x8140.chr.encoding.should == Encoding::SHIFT_JIS
+ 0xFC4B.chr.encoding.should == Encoding::SHIFT_JIS
+ end
+
+ it "returns a String encoding self interpreted as a codepoint in the default internal encoding" do
+ Encoding.default_internal = Encoding::UTF_8
+ 0x0100.chr.bytes.to_a.should == [0xC4, 0x80]
+ 0x3000.chr.bytes.to_a.should == [0xE3, 0x80, 0x80]
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ 0x8140.chr.bytes.to_a.should == [0x81, 0x40] # Smallest assigned CP932 codepoint greater than 255
+ 0xFC4B.chr.bytes.to_a.should == [0xFC, 0x4B] # Largest assigned CP932 codepoint
+ end
+
+ # #5864
+ it "raises RangeError if self is invalid as a codepoint in the default internal encoding" do
+ [ [0x0100, "US-ASCII"],
+ [0x0100, "BINARY"],
+ [0x0100, "EUC-JP"],
+ [0xA1A0, "EUC-JP"],
+ [0x0100, "ISO-8859-9"],
+ [620, "TIS-620"]
+ ].each do |integer, encoding_name|
+ Encoding.default_internal = Encoding.find(encoding_name)
+ -> { integer.chr }.should raise_error(RangeError)
+ end
+ end
+ end
+ end
+end
+
+describe "Integer#chr with an encoding argument" do
+ it "returns a String" do
+ 900.chr(Encoding::UTF_8).should be_an_instance_of(String)
+ end
+
+ it "returns a new String for each call" do
+ 8287.chr(Encoding::UTF_8).should_not equal(8287.chr(Encoding::UTF_8))
+ end
+
+ it "accepts a String as an argument" do
+ -> { 0xA4A2.chr('euc-jp') }.should_not raise_error
+ end
+
+ it "converts a String to an Encoding as Encoding.find does" do
+ ['utf-8', 'UTF-8', 'Utf-8'].each do |encoding|
+ 7894.chr(encoding).encoding.should == Encoding::UTF_8
+ end
+ end
+
+ # http://redmine.ruby-lang.org/issues/4869
+ it "raises a RangeError is self is less than 0" do
+ -> { -1.chr(Encoding::UTF_8) }.should raise_error(RangeError)
+ -> { (-bignum_value).chr(Encoding::EUC_JP) }.should raise_error(RangeError)
+ end
+
+ it "raises a RangeError if self is too large" do
+ -> { 2206368128.chr(Encoding::UTF_8) }.should raise_error(RangeError)
+ end
+
+ it "returns a String with the specified encoding" do
+ 0x0000.chr(Encoding::US_ASCII).encoding.should == Encoding::US_ASCII
+ 0x007F.chr(Encoding::US_ASCII).encoding.should == Encoding::US_ASCII
+
+ 0x0000.chr(Encoding::BINARY).encoding.should == Encoding::BINARY
+ 0x007F.chr(Encoding::BINARY).encoding.should == Encoding::BINARY
+ 0x0080.chr(Encoding::BINARY).encoding.should == Encoding::BINARY
+ 0x00FF.chr(Encoding::BINARY).encoding.should == Encoding::BINARY
+
+ 0x0000.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ 0x007F.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ 0x0080.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ 0x00FF.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ 0x0100.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ 0x3000.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+
+ 0x0000.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ 0x007F.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ 0x00A1.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ 0x00DF.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ 0x8140.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ 0xFC4B.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ end
+
+ it "returns a String encoding self interpreted as a codepoint in the specified encoding" do
+ 0x0000.chr(Encoding::US_ASCII).bytes.to_a.should == [0x00]
+ 0x007F.chr(Encoding::US_ASCII).bytes.to_a.should == [0x7F]
+
+ 0x0000.chr(Encoding::BINARY).bytes.to_a.should == [0x00]
+ 0x007F.chr(Encoding::BINARY).bytes.to_a.should == [0x7F]
+ 0x0080.chr(Encoding::BINARY).bytes.to_a.should == [0x80]
+ 0x00FF.chr(Encoding::BINARY).bytes.to_a.should == [0xFF]
+
+ 0x0000.chr(Encoding::UTF_8).bytes.to_a.should == [0x00]
+ 0x007F.chr(Encoding::UTF_8).bytes.to_a.should == [0x7F]
+ 0x0080.chr(Encoding::UTF_8).bytes.to_a.should == [0xC2, 0x80]
+ 0x00FF.chr(Encoding::UTF_8).bytes.to_a.should == [0xC3, 0xBF]
+ 0x0100.chr(Encoding::UTF_8).bytes.to_a.should == [0xC4, 0x80]
+ 0x3000.chr(Encoding::UTF_8).bytes.to_a.should == [0xE3, 0x80, 0x80]
+
+ 0x0000.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0x00]
+ 0x007F.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0x7F]
+ 0x00A1.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0xA1]
+ 0x00DF.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0xDF]
+ 0x8140.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0x81, 0x40] # Smallest assigned CP932 codepoint greater than 255
+ 0xFC4B.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0xFC, 0x4B] # Largest assigned CP932 codepoint
+ end
+
+ # #5864
+ it "raises RangeError if self is invalid as a codepoint in the specified encoding" do
+ -> { 0x80.chr("US-ASCII") }.should raise_error(RangeError)
+ -> { 0x0100.chr("BINARY") }.should raise_error(RangeError)
+ -> { 0x0100.chr("EUC-JP") }.should raise_error(RangeError)
+ -> { 0xA1A0.chr("EUC-JP") }.should raise_error(RangeError)
+ -> { 0xA1.chr("EUC-JP") }.should raise_error(RangeError)
+ -> { 0x80.chr("SHIFT_JIS") }.should raise_error(RangeError)
+ -> { 0xE0.chr("SHIFT_JIS") }.should raise_error(RangeError)
+ -> { 0x0100.chr("ISO-8859-9") }.should raise_error(RangeError)
+ -> { 620.chr("TIS-620") }.should raise_error(RangeError)
+ # UTF-16 surrogate range
+ -> { 0xD800.chr("UTF-8") }.should raise_error(RangeError)
+ -> { 0xDBFF.chr("UTF-8") }.should raise_error(RangeError)
+ -> { 0xDC00.chr("UTF-8") }.should raise_error(RangeError)
+ -> { 0xDFFF.chr("UTF-8") }.should raise_error(RangeError)
+ # UTF-16 surrogate range
+ -> { 0xD800.chr("UTF-16") }.should raise_error(RangeError)
+ -> { 0xDBFF.chr("UTF-16") }.should raise_error(RangeError)
+ -> { 0xDC00.chr("UTF-16") }.should raise_error(RangeError)
+ -> { 0xDFFF.chr("UTF-16") }.should raise_error(RangeError)
+ end
+
+ it 'returns a String encoding self interpreted as a codepoint in the CESU-8 encoding' do
+ # see more details here https://en.wikipedia.org/wiki/CESU-8
+ # code points from U+0000 to U+FFFF is encoded in the same way as in UTF-8
+ 0x0045.chr(Encoding::CESU_8).bytes.should == 0x0045.chr(Encoding::UTF_8).bytes
+
+ # code points in range from U+10000 to U+10FFFF is CESU-8 data containing a 6-byte surrogate pair,
+ # which decodes to a 4-byte UTF-8 string
+ 0x10400.chr(Encoding::CESU_8).bytes.should != 0x10400.chr(Encoding::UTF_8).bytes
+ 0x10400.chr(Encoding::CESU_8).bytes.to_a.should == [0xED, 0xA0, 0x81, 0xED, 0xB0, 0x80]
+ end
+end
diff --git a/spec/ruby/core/integer/coerce_spec.rb b/spec/ruby/core/integer/coerce_spec.rb
new file mode 100644
index 0000000000..f1f3256032
--- /dev/null
+++ b/spec/ruby/core/integer/coerce_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+
+require 'bigdecimal'
+
+describe "Integer#coerce" do
+ context "fixnum" do
+ describe "when given a Fixnum" do
+ it "returns an array containing two Fixnums" do
+ 1.coerce(2).should == [2, 1]
+ 1.coerce(2).map { |i| i.class }.should == [Integer, Integer]
+ end
+ end
+
+ describe "when given a String" do
+ it "raises an ArgumentError when trying to coerce with a non-number String" do
+ -> { 1.coerce(":)") }.should raise_error(ArgumentError)
+ end
+
+ it "returns an array containing two Floats" do
+ 1.coerce("2").should == [2.0, 1.0]
+ 1.coerce("-2").should == [-2.0, 1.0]
+ end
+ end
+
+ it "raises a TypeError when trying to coerce with nil" do
+ -> { 1.coerce(nil) }.should raise_error(TypeError)
+ end
+
+ it "tries to convert the given Object into a Float by using #to_f" do
+ (obj = mock('1.0')).should_receive(:to_f).and_return(1.0)
+ 2.coerce(obj).should == [1.0, 2.0]
+
+ (obj = mock('0')).should_receive(:to_f).and_return('0')
+ -> { 2.coerce(obj).should == [1.0, 2.0] }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when given an Object that does not respond to #to_f" do
+ -> { 1.coerce(mock('x')) }.should raise_error(TypeError)
+ -> { 1.coerce(1..4) }.should raise_error(TypeError)
+ -> { 1.coerce(:test) }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ it "coerces other to a Bignum and returns [other, self] when passed a Fixnum" do
+ a = bignum_value
+ ary = a.coerce(2)
+
+ ary[0].should be_kind_of(Integer)
+ ary[1].should be_kind_of(Integer)
+ ary.should == [2, a]
+ end
+
+ it "returns [other, self] when passed a Bignum" do
+ a = bignum_value
+ b = bignum_value
+ ary = a.coerce(b)
+
+ ary[0].should be_kind_of(Integer)
+ ary[1].should be_kind_of(Integer)
+ ary.should == [b, a]
+ end
+
+ it "raises a TypeError when not passed a Fixnum or Bignum" do
+ a = bignum_value
+
+ -> { a.coerce(nil) }.should raise_error(TypeError)
+ -> { a.coerce(mock('str')) }.should raise_error(TypeError)
+ -> { a.coerce(1..4) }.should raise_error(TypeError)
+ -> { a.coerce(:test) }.should raise_error(TypeError)
+ end
+
+ it "coerces both values to Floats and returns [other, self] when passed a Float" do
+ a = bignum_value
+ a.coerce(1.2).should == [1.2, a.to_f]
+ end
+
+ it "coerces both values to Floats and returns [other, self] when passed a String" do
+ a = bignum_value
+ a.coerce("123").should == [123.0, a.to_f]
+ end
+
+ it "calls #to_f to coerce other to a Float" do
+ b = mock("bignum value")
+ b.should_receive(:to_f).and_return(1.2)
+
+ a = bignum_value
+ ary = a.coerce(b)
+
+ ary.should == [1.2, a.to_f]
+ end
+ end
+
+ context "bigdecimal" do
+ it "produces Floats" do
+ x, y = 3.coerce(BigDecimal("3.4"))
+ x.class.should == Float
+ x.should == 3.4
+ y.class.should == Float
+ y.should == 3.0
+ end
+ end
+
+end
diff --git a/spec/ruby/core/integer/comparison_spec.rb b/spec/ruby/core/integer/comparison_spec.rb
new file mode 100644
index 0000000000..408c8e52ce
--- /dev/null
+++ b/spec/ruby/core/integer/comparison_spec.rb
@@ -0,0 +1,177 @@
+require_relative '../../spec_helper'
+
+describe "Integer#<=>" do
+ context "fixnum" do
+ it "returns -1 when self is less than the given argument" do
+ (-3 <=> -1).should == -1
+ (-5 <=> 10).should == -1
+ (-5 <=> -4.5).should == -1
+ end
+
+ it "returns 0 when self is equal to the given argument" do
+ (0 <=> 0).should == 0
+ (954 <=> 954).should == 0
+ (954 <=> 954.0).should == 0
+ end
+
+ it "returns 1 when self is greater than the given argument" do
+ (496 <=> 5).should == 1
+ (200 <=> 100).should == 1
+ (51 <=> 50.5).should == 1
+ end
+
+ it "returns nil when the given argument is not an Integer" do
+ (3 <=> mock('x')).should == nil
+ (3 <=> 'test').should == nil
+ end
+ end
+
+ context "bignum" do
+ describe "with a Fixnum" do
+ it "returns -1 when other is larger" do
+ (-bignum_value <=> 2).should == -1
+ end
+
+ it "returns 1 when other is smaller" do
+ (bignum_value <=> 2).should == 1
+ end
+ end
+
+ describe "with a Bignum" do
+ describe "when other is negative" do
+ it "returns -1 when self is negative and other is larger" do
+ (-bignum_value(42) <=> -bignum_value).should == -1
+ end
+
+ it "returns 0 when other is equal" do
+ (-bignum_value <=> -bignum_value).should == 0
+ end
+
+ it "returns 1 when self is negative and other is smaller" do
+ (-bignum_value <=> -bignum_value(94)).should == 1
+ end
+
+ it "returns 1 when self is positive" do
+ (bignum_value <=> -bignum_value).should == 1
+ end
+ end
+
+ describe "when other is positive" do
+ it "returns -1 when self is negative" do
+ (-bignum_value <=> bignum_value).should == -1
+ end
+
+ it "returns -1 when self is positive and other is larger" do
+ (bignum_value <=> bignum_value(38)).should == -1
+ end
+
+ it "returns 0 when other is equal" do
+ (bignum_value <=> bignum_value).should == 0
+ end
+
+ it "returns 1 when other is smaller" do
+ (bignum_value(56) <=> bignum_value).should == 1
+ end
+ end
+ end
+
+ describe "with a Float" do
+ describe "when other is negative" do
+ it "returns -1 when self is negative and other is larger" do
+ (-bignum_value(0xffff) <=> -bignum_value.to_f).should == -1
+ end
+
+ it "returns 0 when other is equal" do
+ (-bignum_value <=> -bignum_value.to_f).should == 0
+ end
+
+ it "returns 1 when self is negative and other is smaller" do
+ (-bignum_value <=> -bignum_value(0xffef).to_f).should == 1
+ end
+
+ it "returns 1 when self is positive" do
+ (bignum_value <=> -bignum_value.to_f).should == 1
+ end
+ end
+
+ describe "when other is positive" do
+ it "returns -1 when self is negative" do
+ (-bignum_value <=> bignum_value.to_f).should == -1
+ end
+
+ it "returns -1 when self is positive and other is larger" do
+ (bignum_value <=> bignum_value(0xfffe).to_f).should == -1
+ end
+
+ it "returns 0 when other is equal" do
+ (bignum_value <=> bignum_value.to_f).should == 0
+ end
+
+ it "returns 1 when other is smaller" do
+ (bignum_value(0xfeff) <=> bignum_value.to_f).should == 1
+ end
+ end
+ end
+
+ describe "with an Object" do
+ before :each do
+ @big = bignum_value
+ @num = mock("value for Integer#<=>")
+ end
+
+ it "calls #coerce on other" do
+ @num.should_receive(:coerce).with(@big).and_return([@big.to_f, 2.5])
+ @big <=> @num
+ end
+
+ it "lets the exception go through if #coerce raises an exception" do
+ @num.should_receive(:coerce).with(@big).and_raise(RuntimeError.new("my error"))
+ -> {
+ @big <=> @num
+ }.should raise_error(RuntimeError, "my error")
+ end
+
+ it "raises an exception if #coerce raises a non-StandardError exception" do
+ @num.should_receive(:coerce).with(@big).and_raise(Exception)
+ -> { @big <=> @num }.should raise_error(Exception)
+ end
+
+ it "returns nil if #coerce does not return an Array" do
+ @num.should_receive(:coerce).with(@big).and_return(nil)
+ (@big <=> @num).should be_nil
+ end
+
+ it "returns -1 if the coerced value is larger" do
+ @num.should_receive(:coerce).with(@big).and_return([@big, bignum_value(10)])
+ (@big <=> @num).should == -1
+ end
+
+ it "returns 0 if the coerced value is equal" do
+ @num.should_receive(:coerce).with(@big).and_return([@big, bignum_value])
+ (@big <=> @num).should == 0
+ end
+
+ it "returns 1 if the coerced value is smaller" do
+ @num.should_receive(:coerce).with(@big).and_return([@big, 22])
+ (@big <=> @num).should == 1
+ end
+ end
+
+ # The tests below are taken from matz's revision 23730 for Ruby trunk
+ it "returns 1 when self is Infinity and other is a Bignum" do
+ (infinity_value <=> Float::MAX.to_i*2).should == 1
+ end
+
+ it "returns -1 when self is negative and other is Infinity" do
+ (-Float::MAX.to_i*2 <=> infinity_value).should == -1
+ end
+
+ it "returns 1 when self is negative and other is -Infinity" do
+ (-Float::MAX.to_i*2 <=> -infinity_value).should == 1
+ end
+
+ it "returns -1 when self is -Infinity and other is negative" do
+ (-infinity_value <=> -Float::MAX.to_i*2).should == -1
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/complement_spec.rb b/spec/ruby/core/integer/complement_spec.rb
new file mode 100644
index 0000000000..baf5e6c457
--- /dev/null
+++ b/spec/ruby/core/integer/complement_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "Integer#~" do
+ context "fixnum" do
+ it "returns self with each bit flipped" do
+ (~0).should == -1
+ (~1221).should == -1222
+ (~-2).should == 1
+ (~-599).should == 598
+ end
+ end
+
+ context "bignum" do
+ it "returns self with each bit flipped" do
+ (~bignum_value(48)).should == -18446744073709551665
+ (~(-bignum_value(21))).should == 18446744073709551636
+ (~bignum_value(1)).should == -18446744073709551618
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/constants_spec.rb b/spec/ruby/core/integer/constants_spec.rb
new file mode 100644
index 0000000000..2077ad451e
--- /dev/null
+++ b/spec/ruby/core/integer/constants_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Fixnum" do
+ ruby_version_is ""..."3.2" do
+ it "is unified into Integer" do
+ suppress_warning do
+ Fixnum.should equal(Integer)
+ end
+ end
+
+ it "is deprecated" do
+ -> { Fixnum }.should complain(/constant ::Fixnum is deprecated/)
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "is no longer defined" do
+ Object.should_not.const_defined?(:Fixnum)
+ end
+ end
+end
+
+describe "Bignum" do
+ ruby_version_is ""..."3.2" do
+ it "is unified into Integer" do
+ suppress_warning do
+ Bignum.should equal(Integer)
+ end
+ end
+
+ it "is deprecated" do
+ -> { Bignum }.should complain(/constant ::Bignum is deprecated/)
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "is no longer defined" do
+ Object.should_not.const_defined?(:Bignum)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/denominator_spec.rb b/spec/ruby/core/integer/denominator_spec.rb
new file mode 100644
index 0000000000..c1477d0757
--- /dev/null
+++ b/spec/ruby/core/integer/denominator_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "Integer#denominator" do
+ # The Numeric child classes override this method, so their behaviour is
+ # specified in the appropriate place
+ before :each do
+ @numbers = [
+ 20, # Integer
+ -2709, # Negative Integer
+ 99999999**99, # Bignum
+ -99999**621, # Negative BigNum
+ 0,
+ 1
+ ]
+ end
+
+ it "returns 1" do
+ @numbers.each {|number| number.denominator.should == 1}
+ end
+end
diff --git a/spec/ruby/core/integer/digits_spec.rb b/spec/ruby/core/integer/digits_spec.rb
new file mode 100644
index 0000000000..3d0a64c25f
--- /dev/null
+++ b/spec/ruby/core/integer/digits_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Integer#digits" do
+ it "returns an array of place values in base-10 by default" do
+ 12345.digits.should == [5,4,3,2,1]
+ end
+
+ it "returns digits by place value of a given radix" do
+ 12345.digits(7).should == [4,6,6,0,5]
+ end
+
+ it "converts the radix with #to_int" do
+ 12345.digits(mock_int(2)).should == [1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1]
+ end
+
+ it "returns [0] when called on 0, regardless of base" do
+ 0.digits.should == [0]
+ 0.digits(7).should == [0]
+ end
+
+ it "raises ArgumentError when calling with a radix less than 2" do
+ -> { 12345.digits(1) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError when calling with a negative radix" do
+ -> { 12345.digits(-2) }.should raise_error(ArgumentError)
+ end
+
+ it "raises Math::DomainError when calling digits on a negative number" do
+ -> { -12345.digits(7) }.should raise_error(Math::DomainError)
+ end
+
+ it "returns integer values > 9 when base is above 10" do
+ 1234.digits(16).should == [2, 13, 4]
+ end
+
+ it "can be used with base > 37" do
+ 1234.digits(100).should == [34, 12]
+ 980099.digits(100).should == [99, 0, 98]
+ end
+end
diff --git a/spec/ruby/core/integer/div_spec.rb b/spec/ruby/core/integer/div_spec.rb
new file mode 100644
index 0000000000..344e095179
--- /dev/null
+++ b/spec/ruby/core/integer/div_spec.rb
@@ -0,0 +1,146 @@
+require_relative '../../spec_helper'
+
+describe "Integer#div" do
+ context "fixnum" do
+ it "returns self divided by the given argument as an Integer" do
+ 2.div(2).should == 1
+ 1.div(2).should == 0
+ 5.div(2).should == 2
+ end
+
+ it "rounds towards -inf" do
+ 8192.div(10).should == 819
+ 8192.div(-10).should == -820
+ (-8192).div(10).should == -820
+ (-8192).div(-10).should == 819
+ end
+
+ it "means (x / y).floor" do
+ 5.div(2).should == (5 / 2).floor
+ 5.div(2.0).should == (5 / 2.0).floor
+ 5.div(-2).should == (5 / -2).floor
+
+ 5.div(100).should == (5 / 100).floor
+ 5.div(100.0).should == (5 / 100.0).floor
+ 5.div(-100).should == (5 / -100).floor
+ end
+
+ it "calls #coerce and #div if argument responds to #coerce" do
+ x = mock("x")
+ y = mock("y")
+ result = mock("result")
+
+ y.should_receive(:coerce).and_return([x, y])
+ x.should_receive(:div).with(y).and_return(result)
+
+ 10.div(y).should == result
+ end
+
+ it "coerces self and the given argument to Floats and returns self divided by other as Integer" do
+ 1.div(0.2).should == 5
+ 1.div(0.16).should == 6
+ 1.div(0.169).should == 5
+ -1.div(50.4).should == -1
+ 1.div(bignum_value).should == 0
+ 1.div(Rational(1, 5)).should == 5
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and a Float" do
+ -> { 0.div(0.0) }.should raise_error(ZeroDivisionError)
+ -> { 10.div(0.0) }.should raise_error(ZeroDivisionError)
+ -> { -10.div(0.0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and not a Float" do
+ -> { 13.div(0) }.should raise_error(ZeroDivisionError)
+ -> { 13.div(-0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when given a non-numeric argument" do
+ -> { 13.div(mock('10')) }.should raise_error(TypeError)
+ -> { 5.div("2") }.should raise_error(TypeError)
+ -> { 5.div(:"2") }.should raise_error(TypeError)
+ -> { 5.div([]) }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(88)
+ end
+
+ it "returns self divided by other" do
+ @bignum.div(4).should == 4611686018427387926
+ @bignum.div(Rational(4, 1)).should == 4611686018427387926
+ @bignum.div(bignum_value(2)).should == 1
+
+ (-(10**50)).div(-(10**40 + 1)).should == 9999999999
+ (10**50).div(10**40 + 1).should == 9999999999
+
+ (-10**50).div(10**40 + 1).should == -10000000000
+ (10**50).div(-(10**40 + 1)).should == -10000000000
+ end
+
+ it "handles fixnum_min / -1" do
+ (fixnum_min / -1).should == -fixnum_min
+ (fixnum_min / -1).should > 0
+
+ int_min = -2147483648
+ (int_min / -1).should == 2147483648
+ end
+
+ it "calls #coerce and #div if argument responds to #coerce" do
+ x = mock("x")
+ y = mock("y")
+ result = mock("result")
+
+ y.should_receive(:coerce).and_return([x, y])
+ x.should_receive(:div).with(y).and_return(result)
+
+ @bignum.div(y).should == result
+ end
+
+ it "means (x / y).floor" do
+ @bignum.div(2).should == (@bignum / 2).floor
+ @bignum.div(-2).should == (@bignum / -2).floor
+
+ @bignum.div(@bignum+1).should == (@bignum / (@bignum+1)).floor
+ @bignum.div(-(@bignum+1)).should == (@bignum / -(@bignum+1)).floor
+
+ @bignum.div(2.0).should == (@bignum / 2.0).floor
+ @bignum.div(100.0).should == (@bignum / 100.0).floor
+ end
+
+ it "looses precision if passed Float argument" do
+ @bignum.div(1).should_not == @bignum.div(1.0)
+ @bignum.div(4).should_not == @bignum.div(4.0)
+ @bignum.div(21).should_not == @bignum.div(21.0)
+ end
+
+ it "raises a TypeError when given a non-numeric" do
+ -> { @bignum.div(mock("10")) }.should raise_error(TypeError)
+ -> { @bignum.div("2") }.should raise_error(TypeError)
+ -> { @bignum.div(:symbol) }.should raise_error(TypeError)
+ end
+
+ it "returns a result of integer division of self by a float argument" do
+ @bignum.div(4294967295.5).should eql(4294967296)
+ not_supported_on :opal do
+ @bignum.div(4294967295.0).should eql(4294967297)
+ @bignum.div(bignum_value(88).to_f).should eql(1)
+ @bignum.div((-bignum_value(88)).to_f).should eql(-1)
+ end
+ end
+
+ # #5490
+ it "raises ZeroDivisionError if the argument is 0 and is a Float" do
+ -> { @bignum.div(0.0) }.should raise_error(ZeroDivisionError)
+ -> { @bignum.div(-0.0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises ZeroDivisionError if the argument is 0 and is not a Float" do
+ -> { @bignum.div(0) }.should raise_error(ZeroDivisionError)
+ -> { @bignum.div(-0) }.should raise_error(ZeroDivisionError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/divide_spec.rb b/spec/ruby/core/integer/divide_spec.rb
new file mode 100644
index 0000000000..a878c4668c
--- /dev/null
+++ b/spec/ruby/core/integer/divide_spec.rb
@@ -0,0 +1,89 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_coerce'
+
+describe "Integer#/" do
+ it_behaves_like :integer_arithmetic_coerce_not_rescue, :/
+
+ context "fixnum" do
+ it "returns self divided by the given argument" do
+ (2 / 2).should == 1
+ (3 / 2).should == 1
+ end
+
+ it "supports dividing negative numbers" do
+ (-1 / 10).should == -1
+ end
+
+ it "returns result the same class as the argument" do
+ (3 / 2).should == 1
+ (3 / 2.0).should == 1.5
+ (3 / Rational(2, 1)).should == Rational(3, 2)
+ end
+
+ it "raises a ZeroDivisionError if the given argument is zero and not a Float" do
+ -> { 1 / 0 }.should raise_error(ZeroDivisionError)
+ end
+
+ it "does NOT raise ZeroDivisionError if the given argument is zero and is a Float" do
+ (1 / 0.0).to_s.should == 'Infinity'
+ (-1 / 0.0).to_s.should == '-Infinity'
+ end
+
+ it "coerces fixnum and return self divided by other" do
+ (-1 / 50.4).should be_close(-0.0198412698412698, TOLERANCE)
+ (1 / bignum_value).should == 0
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { 13 / mock('10') }.should raise_error(TypeError)
+ -> { 13 / "10" }.should raise_error(TypeError)
+ -> { 13 / :symbol }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(88)
+ end
+
+ it "returns self divided by other" do
+ (@bignum / 4).should == 4611686018427387926
+
+ (@bignum / bignum_value(2)).should == 1
+
+ (-(10**50) / -(10**40 + 1)).should == 9999999999
+ ((10**50) / (10**40 + 1)).should == 9999999999
+
+ ((-10**50) / (10**40 + 1)).should == -10000000000
+ ((10**50) / -(10**40 + 1)).should == -10000000000
+ end
+
+ it "returns self divided by Float" do
+ not_supported_on :opal do
+ (bignum_value(88) / 4294967295.0).should be_close(4294967297.0, TOLERANCE)
+ end
+ (bignum_value(88) / 4294967295.5).should be_close(4294967296.5, TOLERANCE)
+ end
+
+ it "returns result the same class as the argument" do
+ (@bignum / 4).should == 4611686018427387926
+ (@bignum / 4.0).should be_close(4611686018427387926, TOLERANCE)
+ (@bignum / Rational(4, 1)).should == Rational(4611686018427387926, 1)
+ end
+
+ it "does NOT raise ZeroDivisionError if other is zero and is a Float" do
+ (bignum_value / 0.0).to_s.should == 'Infinity'
+ (bignum_value / -0.0).to_s.should == '-Infinity'
+ end
+
+ it "raises a ZeroDivisionError if other is zero and not a Float" do
+ -> { @bignum / 0 }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when given a non-numeric" do
+ -> { @bignum / mock('10') }.should raise_error(TypeError)
+ -> { @bignum / "2" }.should raise_error(TypeError)
+ -> { @bignum / :symbol }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/divmod_spec.rb b/spec/ruby/core/integer/divmod_spec.rb
new file mode 100644
index 0000000000..7a489e9344
--- /dev/null
+++ b/spec/ruby/core/integer/divmod_spec.rb
@@ -0,0 +1,117 @@
+require_relative '../../spec_helper'
+
+describe "Integer#divmod" do
+ context "fixnum" do
+ it "returns an Array containing quotient and modulus obtained from dividing self by the given argument" do
+ 13.divmod(4).should == [3, 1]
+ 4.divmod(13).should == [0, 4]
+
+ 13.divmod(4.0).should == [3, 1]
+ 4.divmod(13.0).should == [0, 4]
+
+ 1.divmod(2.0).should == [0, 1.0]
+ 200.divmod(bignum_value).should == [0, 200]
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0" do
+ -> { 13.divmod(0) }.should raise_error(ZeroDivisionError)
+ -> { 0.divmod(0) }.should raise_error(ZeroDivisionError)
+ -> { -10.divmod(0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and a Float" do
+ -> { 0.divmod(0.0) }.should raise_error(ZeroDivisionError)
+ -> { 10.divmod(0.0) }.should raise_error(ZeroDivisionError)
+ -> { -10.divmod(0.0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10)
+ 13.divmod(obj)
+ }.should raise_error(TypeError)
+ -> { 13.divmod("10") }.should raise_error(TypeError)
+ -> { 13.divmod(:symbol) }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(55)
+ end
+
+ # Based on MRI's test/test_integer.rb (test_divmod),
+ # MRI maintains the following property:
+ # if q, r = a.divmod(b) ==>
+ # assert(0 < b ? (0 <= r && r < b) : (b < r && r <= 0))
+ # So, r is always between 0 and b.
+ it "returns an Array containing quotient and modulus obtained from dividing self by the given argument" do
+ @bignum.divmod(4).should == [4611686018427387917, 3]
+ @bignum.divmod(13).should == [1418980313362273205, 6]
+
+ @bignum.divmod(4.5).should == [4099276460824344576, 2.5]
+
+ not_supported_on :opal do
+ @bignum.divmod(4.0).should == [4611686018427387904, 0.0]
+ @bignum.divmod(13.0).should == [1418980313362273280, 3.0]
+
+ @bignum.divmod(2.0).should == [9223372036854775808, 0.0]
+ end
+
+ @bignum.divmod(bignum_value).should == [1, 55]
+
+ (-(10**50)).divmod(-(10**40 + 1)).should == [9999999999, -9999999999999999999999999999990000000001]
+ (10**50).divmod(10**40 + 1).should == [9999999999, 9999999999999999999999999999990000000001]
+
+ (-10**50).divmod(10**40 + 1).should == [-10000000000, 10000000000]
+ (10**50).divmod(-(10**40 + 1)).should == [-10000000000, -10000000000]
+ end
+
+ describe "with q = floor(x/y), a = q*b + r," do
+ it "returns [q,r] when a < 0, b > 0 and |a| < b" do
+ a = -@bignum + 1
+ b = @bignum
+ a.divmod(b).should == [-1, 1]
+ end
+
+ it "returns [q,r] when a > 0, b < 0 and a > |b|" do
+ b = -@bignum + 1
+ a = @bignum
+ a.divmod(b).should == [-2, -@bignum + 2]
+ end
+
+ it "returns [q,r] when a > 0, b < 0 and a < |b|" do
+ a = @bignum - 1
+ b = -@bignum
+ a.divmod(b).should == [-1, -1]
+ end
+
+ it "returns [q,r] when a < 0, b < 0 and |a| < |b|" do
+ a = -@bignum + 1
+ b = -@bignum
+ a.divmod(b).should == [0, -@bignum + 1]
+ end
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0" do
+ -> { @bignum.divmod(0) }.should raise_error(ZeroDivisionError)
+ -> { (-@bignum).divmod(0) }.should raise_error(ZeroDivisionError)
+ end
+
+ # Behaviour established as correct in r23953
+ it "raises a FloatDomainError if other is NaN" do
+ -> { @bignum.divmod(nan_value) }.should raise_error(FloatDomainError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and a Float" do
+ -> { @bignum.divmod(0.0) }.should raise_error(ZeroDivisionError)
+ -> { (-@bignum).divmod(0.0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when the given argument is not an Integer" do
+ -> { @bignum.divmod(mock('10')) }.should raise_error(TypeError)
+ -> { @bignum.divmod("10") }.should raise_error(TypeError)
+ -> { @bignum.divmod(:symbol) }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/downto_spec.rb b/spec/ruby/core/integer/downto_spec.rb
new file mode 100644
index 0000000000..987704b02e
--- /dev/null
+++ b/spec/ruby/core/integer/downto_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+
+describe "Integer#downto [stop] when self and stop are Integers" do
+ it "does not yield when stop is greater than self" do
+ result = []
+ 5.downto(6) { |x| result << x }
+ result.should == []
+ end
+
+ it "yields once when stop equals self" do
+ result = []
+ 5.downto(5) { |x| result << x }
+ result.should == [5]
+ end
+
+ it "yields while decreasing self until it is less than stop" do
+ result = []
+ 5.downto(2) { |x| result << x }
+ result.should == [5, 4, 3, 2]
+ end
+
+ it "yields while decreasing self until it less than ceil for a Float endpoint" do
+ result = []
+ 9.downto(1.3) {|i| result << i}
+ 3.downto(-1.3) {|i| result << i}
+ result.should == [9, 8, 7, 6, 5, 4, 3, 2, 3, 2, 1, 0, -1]
+ end
+
+ it "raises an ArgumentError for invalid endpoints" do
+ -> {1.downto("A") {|x| p x } }.should raise_error(ArgumentError)
+ -> {1.downto(nil) {|x| p x } }.should raise_error(ArgumentError)
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ result = []
+
+ enum = 5.downto(2)
+ enum.each { |i| result << i }
+
+ result.should == [5, 4, 3, 2]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "raises an ArgumentError for invalid endpoints" do
+ enum = 1.downto("A")
+ -> { enum.size }.should raise_error(ArgumentError)
+ enum = 1.downto(nil)
+ -> { enum.size }.should raise_error(ArgumentError)
+ end
+
+ it "returns self - stop + 1" do
+ 10.downto(5).size.should == 6
+ 10.downto(1).size.should == 10
+ 10.downto(0).size.should == 11
+ 0.downto(0).size.should == 1
+ -3.downto(-5).size.should == 3
+ end
+
+ it "returns 0 when stop > self" do
+ 4.downto(5).size.should == 0
+ -5.downto(0).size.should == 0
+ -5.downto(-3).size.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/dup_spec.rb b/spec/ruby/core/integer/dup_spec.rb
new file mode 100644
index 0000000000..7f4d512465
--- /dev/null
+++ b/spec/ruby/core/integer/dup_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Integer#dup" do
+ it "returns self for small integers" do
+ integer = 1_000
+ integer.dup.should equal(integer)
+ end
+
+ it "returns self for large integers" do
+ integer = 4_611_686_018_427_387_905
+ integer.dup.should equal(integer)
+ end
+end
diff --git a/spec/ruby/core/integer/element_reference_spec.rb b/spec/ruby/core/integer/element_reference_spec.rb
new file mode 100644
index 0000000000..cb7e0dc9b0
--- /dev/null
+++ b/spec/ruby/core/integer/element_reference_spec.rb
@@ -0,0 +1,188 @@
+require_relative '../../spec_helper'
+
+describe "Integer#[]" do
+ context "fixnum" do
+ it "behaves like (n >> b) & 1" do
+ 0b101[1].should == 0
+ 0b101[2].should == 1
+ end
+
+ it "returns 1 if the nth bit is set" do
+ 15[1].should == 1
+ end
+
+ it "returns 1 if the nth bit is set (in two's-complement representation)" do
+ (-1)[1].should == 1
+ end
+
+ it "returns 0 if the nth bit is not set" do
+ 8[2].should == 0
+ end
+
+ it "returns 0 if the nth bit is not set (in two's-complement representation)" do
+ (-2)[0].should == 0
+ end
+
+ it "returns 0 if the nth bit is greater than the most significant bit" do
+ 2[3].should == 0
+ end
+
+ it "returns 1 if self is negative and the nth bit is greater than the most significant bit" do
+ (-1)[3].should == 1
+ end
+
+ it "returns 0 when passed a negative argument" do
+ 3[-1].should == 0
+ (-1)[-1].should == 0
+ end
+
+ it "calls #to_int to convert the argument to an Integer and returns 1 if the nth bit is set" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+
+ 2[obj].should == 1
+ end
+
+ it "calls #to_int to convert the argument to an Integer and returns 0 if the nth bit is set" do
+ obj = mock('0')
+ obj.should_receive(:to_int).and_return(0)
+
+ 2[obj].should == 0
+ end
+
+ it "accepts a Float argument and returns 0 if the bit at the truncated value is not set" do
+ 13[1.3].should == 0
+ end
+
+ it "accepts a Float argument and returns 1 if the bit at the truncated value is set" do
+ 13[2.1].should == 1
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { 3["3"] }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock('asdf')
+ obj.should_receive(:to_int).and_return("asdf")
+ -> { 3[obj] }.should raise_error(TypeError)
+ end
+
+ it "calls #to_int to coerce a String to a Bignum and returns 0" do
+ obj = mock('bignum value')
+ obj.should_receive(:to_int).and_return(bignum_value)
+
+ 3[obj].should == 0
+ end
+
+ it "returns 0 when passed a Float in the range of a Bignum" do
+ 3[bignum_value.to_f].should == 0
+ end
+
+ context "when index and length passed" do
+ it "returns specified number of bits from specified position" do
+ 0b101001101[2, 4].should == 0b0011
+ 0b101001101[2, 5].should == 0b10011
+ 0b101001101[2, 7].should == 0b1010011
+ end
+
+ it "ensures n[i, len] equals to (n >> i) & ((1 << len) - 1)" do
+ n = 0b101001101; i = 2; len = 4
+ n[i, len].should == (n >> i) & ((1 << len) - 1)
+ end
+
+ it "moves start position to the most significant bits when negative index passed" do
+ 0b000001[-1, 4].should == 0b10
+ 0b000001[-2, 4].should == 0b100
+ 0b000001[-3, 4].should == 0b1000
+ end
+
+ it "ignores negative length" do
+ 0b101001101[1, -1].should == 0b10100110
+ 0b101001101[2, -1].should == 0b1010011
+ 0b101001101[3, -1].should == 0b101001
+
+ 0b101001101[3, -5].should == 0b101001
+ 0b101001101[3, -15].should == 0b101001
+ 0b101001101[3, -125].should == 0b101001
+ end
+ end
+
+ context "when range passed" do
+ it "returns bits specified by range" do
+ 0b101001101[2..5].should == 0b0011
+ 0b101001101[2..6].should == 0b10011
+ 0b101001101[2..8].should == 0b1010011
+ end
+
+ it "ensures n[i..j] equals to (n >> i) & ((1 << (j - i + 1)) - 1)" do
+ n = 0b101001101; i = 2; j = 5
+ n[i..j].should == (n >> i) & ((1 << (j - i + 1)) - 1)
+ end
+
+ it "ensures n[i..] equals to (n >> i)" do
+ eval("0b101001101[3..]").should == 0b101001101 >> 3
+ end
+
+ it "moves lower boundary to the most significant bits when negative value passed" do
+ 0b000001[-1, 4].should == 0b10
+ 0b000001[-2, 4].should == 0b100
+ 0b000001[-3, 4].should == 0b1000
+ end
+
+ it "ignores upper boundary smaller than lower boundary" do
+ 0b101001101[4..1].should == 0b10100
+ 0b101001101[4..2].should == 0b10100
+ 0b101001101[-4..-5].should == 0b1010011010000
+ end
+
+ it "raises FloatDomainError if any boundary is infinity" do
+ -> { 0x0001[3..Float::INFINITY] }.should raise_error(FloatDomainError, /Infinity/)
+ -> { 0x0001[-Float::INFINITY..3] }.should raise_error(FloatDomainError, /-Infinity/)
+ end
+
+ context "when passed (..i)" do
+ it "returns 0 if all i bits equal 0" do
+ eval("0b10000[..1]").should == 0
+ eval("0b10000[..2]").should == 0
+ eval("0b10000[..3]").should == 0
+ end
+
+ it "raises ArgumentError if any of i bit equals 1" do
+ -> {
+ eval("0b111110[..3]")
+ }.should raise_error(ArgumentError, /The beginless range for Integer#\[\] results in infinity/)
+ end
+ end
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(4996)
+ end
+
+ it "returns the nth bit in the binary representation of self" do
+ @bignum[2].should == 1
+ @bignum[9.2].should == 1
+ @bignum[21].should == 0
+ @bignum[0xffffffff].should == 0
+ @bignum[-0xffffffff].should == 0
+ end
+
+ it "tries to convert the given argument to an Integer using #to_int" do
+ @bignum[1.3].should == @bignum[1]
+
+ (obj = mock('2')).should_receive(:to_int).at_least(1).and_return(2)
+ @bignum[obj].should == 1
+ end
+
+ it "raises a TypeError when the given argument can't be converted to Integer" do
+ obj = mock('asdf')
+ -> { @bignum[obj] }.should raise_error(TypeError)
+
+ obj.should_receive(:to_int).and_return("asdf")
+ -> { @bignum[obj] }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/equal_value_spec.rb b/spec/ruby/core/integer/equal_value_spec.rb
new file mode 100644
index 0000000000..67a73713af
--- /dev/null
+++ b/spec/ruby/core/integer/equal_value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal'
+
+describe "Integer#==" do
+ it_behaves_like :integer_equal, :==
+end
diff --git a/spec/ruby/core/integer/even_spec.rb b/spec/ruby/core/integer/even_spec.rb
new file mode 100644
index 0000000000..a314cc6b19
--- /dev/null
+++ b/spec/ruby/core/integer/even_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+
+describe "Integer#even?" do
+ context "fixnum" do
+ it "returns true for a Fixnum when it is an even number" do
+ (-2).even?.should be_true
+ (-1).even?.should be_false
+
+ 0.even?.should be_true
+ 1.even?.should be_false
+ 2.even?.should be_true
+ end
+
+ it "returns true for a Bignum when it is an even number" do
+ bignum_value(0).even?.should be_true
+ bignum_value(1).even?.should be_false
+
+ (-bignum_value(0)).even?.should be_true
+ (-bignum_value(1)).even?.should be_false
+ end
+ end
+
+ context "bignum" do
+ it "returns true if self is even and positive" do
+ (10000**10).even?.should be_true
+ end
+
+ it "returns true if self is even and negative" do
+ (-10000**10).even?.should be_true
+ end
+
+ it "returns false if self is odd and positive" do
+ (9879**976).even?.should be_false
+ end
+
+ it "returns false if self is odd and negative" do
+ (-9879**976).even?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/exponent_spec.rb b/spec/ruby/core/integer/exponent_spec.rb
new file mode 100644
index 0000000000..c610661aff
--- /dev/null
+++ b/spec/ruby/core/integer/exponent_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/exponent'
+
+describe "Integer#**" do
+ it_behaves_like :integer_exponent, :**
+end
diff --git a/spec/ruby/core/integer/fdiv_spec.rb b/spec/ruby/core/integer/fdiv_spec.rb
new file mode 100644
index 0000000000..d9ea2fdf8d
--- /dev/null
+++ b/spec/ruby/core/integer/fdiv_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../spec_helper'
+
+describe "Integer#fdiv" do
+ it "performs floating-point division between self and a fixnum" do
+ 8.fdiv(7).should be_close(1.14285714285714, TOLERANCE)
+ end
+
+ it "performs floating-point division between self and a bignum" do
+ 8.fdiv(bignum_value).should be_close(8.673617379884035e-19, TOLERANCE)
+ end
+
+ it "performs floating-point division between self bignum and a bignum" do
+ num = 1000000000000000000000000000000000048148248609680896326399448564623182963452541226153892315137780403285956264146010000000000000000000000000000000000048148248609680896326399448564623182963452541226153892315137780403285956264146010000000000000000000000000000000000048148248609680896326399448564623182963452541226153892315137780403285956264146009
+ den = 2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ num.fdiv(den).should == 500.0
+ end
+
+ it "rounds to the correct value for bignums" do
+ den = 9 * 10**342
+
+ num = 1 * 10**344
+ num.fdiv(den).should == 11.11111111111111
+
+ num = 1 * 10**343
+ num.fdiv(den).should == 1.1111111111111112
+
+ num = 1 * 10**342
+ num.fdiv(den).should == 0.1111111111111111
+
+ num = 2 * 10**342
+ num.fdiv(den).should == 0.2222222222222222
+
+ num = 3 * 10**342
+ num.fdiv(den).should == 0.3333333333333333
+
+ num = 4 * 10**342
+ num.fdiv(den).should == 0.4444444444444444
+
+ num = 5 * 10**342
+ num.fdiv(den).should == 0.5555555555555556
+
+ num = 6 * 10**342
+ num.fdiv(den).should == 0.6666666666666666
+
+ num = 7 * 10**342
+ num.fdiv(den).should == 0.7777777777777778
+
+ num = 8 * 10**342
+ num.fdiv(den).should == 0.8888888888888888
+
+ num = 9 * 10**342
+ num.fdiv(den).should == 1.0
+
+ num = -5 * 10**342
+ num.fdiv(den).should == -0.5555555555555556
+ end
+
+ it "rounds to the correct float for bignum denominators" do
+ 1.fdiv(10**324).should == 0.0
+ 1.fdiv(10**323).should == 1.0e-323
+ end
+
+ it "performs floating-point division between self and a Float" do
+ 8.fdiv(9.0).should be_close(0.888888888888889, TOLERANCE)
+ end
+
+ it "returns NaN when the argument is NaN" do
+ -1.fdiv(nan_value).nan?.should be_true
+ 1.fdiv(nan_value).nan?.should be_true
+ end
+
+ it "returns Infinity when the argument is 0" do
+ 1.fdiv(0).infinite?.should == 1
+ end
+
+ it "returns -Infinity when the argument is 0 and self is negative" do
+ -1.fdiv(0).infinite?.should == -1
+ end
+
+ it "returns Infinity when the argument is 0.0" do
+ 1.fdiv(0.0).infinite?.should == 1
+ end
+
+ it "returns -Infinity when the argument is 0.0 and self is negative" do
+ -1.fdiv(0.0).infinite?.should == -1
+ end
+
+ it "raises a TypeError when argument isn't numeric" do
+ -> { 1.fdiv(mock('non-numeric')) }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError when passed multiple arguments" do
+ -> { 1.fdiv(6,0.2) }.should raise_error(ArgumentError)
+ end
+
+ it "follows the coercion protocol" do
+ (obj = mock('10')).should_receive(:coerce).with(1).and_return([1, 10])
+ 1.fdiv(obj).should == 0.1
+ end
+end
diff --git a/spec/ruby/core/integer/fixtures/classes.rb b/spec/ruby/core/integer/fixtures/classes.rb
new file mode 100644
index 0000000000..6ebfbd1565
--- /dev/null
+++ b/spec/ruby/core/integer/fixtures/classes.rb
@@ -0,0 +1,4 @@
+module IntegerSpecs
+ class CoerceError < StandardError
+ end
+end
diff --git a/spec/ruby/core/integer/floor_spec.rb b/spec/ruby/core/integer/floor_spec.rb
new file mode 100644
index 0000000000..aaa816fdc5
--- /dev/null
+++ b/spec/ruby/core/integer/floor_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+require_relative 'shared/integer_rounding'
+
+describe "Integer#floor" do
+ it_behaves_like :integer_to_i, :floor
+ it_behaves_like :integer_rounding_positive_precision, :floor
+
+ context "precision argument specified as part of the floor method is negative" do
+ it "returns the largest integer less than self with at least precision.abs trailing zeros" do
+ 1832.floor(-1).should eql(1830)
+ 1832.floor(-2).should eql(1800)
+ 1832.floor(-3).should eql(1000)
+ -1832.floor(-1).should eql(-1840)
+ -1832.floor(-2).should eql(-1900)
+ -1832.floor(-3).should eql(-2000)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/gcd_spec.rb b/spec/ruby/core/integer/gcd_spec.rb
new file mode 100644
index 0000000000..961f1c5c2e
--- /dev/null
+++ b/spec/ruby/core/integer/gcd_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+
+describe "Integer#gcd" do
+ it "returns self if equal to the argument" do
+ 1.gcd(1).should == 1
+ 398.gcd(398).should == 398
+ end
+
+ it "returns an Integer" do
+ 36.gcd(6).should be_kind_of(Integer)
+ 4.gcd(20981).should be_kind_of(Integer)
+ end
+
+ it "returns the greatest common divisor of self and argument" do
+ 10.gcd(5).should == 5
+ 200.gcd(20).should == 20
+ end
+
+ it "returns a positive integer even if self is negative" do
+ -12.gcd(6).should == 6
+ -100.gcd(100).should == 100
+ end
+
+ it "returns a positive integer even if the argument is negative" do
+ 12.gcd(-6).should == 6
+ 100.gcd(-100).should == 100
+ end
+
+ it "returns a positive integer even if both self and argument are negative" do
+ -12.gcd(-6).should == 6
+ -100.gcd(-100).should == 100
+ end
+
+ it "accepts a Bignum argument" do
+ bignum = 9999**99
+ bignum.should be_kind_of(Integer)
+ 99.gcd(bignum).should == 99
+ end
+
+ it "works if self is a Bignum" do
+ bignum = 9999**99
+ bignum.should be_kind_of(Integer)
+ bignum.gcd(99).should == 99
+ end
+
+ it "doesn't cause an integer overflow" do
+ [2 ** (1.size * 8 - 2), 0x8000000000000000].each do |max|
+ [max - 1, max, max + 1].each do |num|
+ num.gcd(num).should == num
+ (-num).gcd(num).should == num
+ (-num).gcd(-num).should == num
+ num.gcd(-num).should == num
+ end
+ end
+ end
+
+ it "raises an ArgumentError if not given an argument" do
+ -> { 12.gcd }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if given more than one argument" do
+ -> { 12.gcd(30, 20) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError unless the argument is an Integer" do
+ -> { 39.gcd(3.8) }.should raise_error(TypeError)
+ -> { 45872.gcd([]) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/gcdlcm_spec.rb b/spec/ruby/core/integer/gcdlcm_spec.rb
new file mode 100644
index 0000000000..ebf45e55a1
--- /dev/null
+++ b/spec/ruby/core/integer/gcdlcm_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+
+describe "Integer#gcdlcm" do
+ it "returns [self, self] if self is equal to the argument" do
+ 1.gcdlcm(1).should == [1, 1]
+ 398.gcdlcm(398).should == [398, 398]
+ end
+
+ it "returns an Array" do
+ 36.gcdlcm(6).should be_kind_of(Array)
+ 4.gcdlcm(20981).should be_kind_of(Array)
+ end
+
+ it "returns a two-element Array" do
+ 36.gcdlcm(876).size.should == 2
+ 29.gcdlcm(17).size.should == 2
+ end
+
+ it "returns the greatest common divisor of self and argument as the first element" do
+ 10.gcdlcm(5)[0].should == 10.gcd(5)
+ 200.gcdlcm(20)[0].should == 200.gcd(20)
+ end
+
+ it "returns the least common multiple of self and argument as the last element" do
+ 10.gcdlcm(5)[1].should == 10.lcm(5)
+ 200.gcdlcm(20)[1].should == 200.lcm(20)
+ end
+
+ it "accepts a Bignum argument" do
+ bignum = 91999**99
+ bignum.should be_kind_of(Integer)
+ 99.gcdlcm(bignum).should == [99.gcd(bignum), 99.lcm(bignum)]
+ end
+
+ it "works if self is a Bignum" do
+ bignum = 9999**89
+ bignum.should be_kind_of(Integer)
+ bignum.gcdlcm(99).should == [bignum.gcd(99), bignum.lcm(99)]
+ end
+
+ it "raises an ArgumentError if not given an argument" do
+ -> { 12.gcdlcm }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if given more than one argument" do
+ -> { 12.gcdlcm(30, 20) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError unless the argument is an Integer" do
+ -> { 39.gcdlcm(3.8) }.should raise_error(TypeError)
+ -> { 45872.gcdlcm([]) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/gt_spec.rb b/spec/ruby/core/integer/gt_spec.rb
new file mode 100644
index 0000000000..f0179e566d
--- /dev/null
+++ b/spec/ruby/core/integer/gt_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_coerce'
+
+describe "Integer#>" do
+ it_behaves_like :integer_comparison_coerce_not_rescue, :>
+
+ context "fixnum" do
+ it "returns true if self is greater than the given argument" do
+ (13 > 2).should == true
+ (-500 > -600).should == true
+
+ (1 > 5).should == false
+ (5 > 5).should == false
+
+ (900 > bignum_value).should == false
+ (5 > 4.999).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { 5 > "4" }.should raise_error(ArgumentError)
+ -> { 5 > mock('x') }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(732)
+ end
+
+ it "returns true if self is greater than the given argument" do
+ (@bignum > (@bignum - 1)).should == true
+ (@bignum > 14.6).should == true
+ (@bignum > 10).should == true
+
+ (@bignum > (@bignum + 500)).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { @bignum > "4" }.should raise_error(ArgumentError)
+ -> { @bignum > mock('str') }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/gte_spec.rb b/spec/ruby/core/integer/gte_spec.rb
new file mode 100644
index 0000000000..6237fc2c51
--- /dev/null
+++ b/spec/ruby/core/integer/gte_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_coerce'
+
+describe "Integer#>=" do
+ it_behaves_like :integer_comparison_coerce_not_rescue, :>=
+
+ context "fixnum" do
+ it "returns true if self is greater than or equal to the given argument" do
+ (13 >= 2).should == true
+ (-500 >= -600).should == true
+
+ (1 >= 5).should == false
+ (2 >= 2).should == true
+ (5 >= 5).should == true
+
+ (900 >= bignum_value).should == false
+ (5 >= 4.999).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { 5 >= "4" }.should raise_error(ArgumentError)
+ -> { 5 >= mock('x') }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(14)
+ end
+
+ it "returns true if self is greater than or equal to other" do
+ (@bignum >= @bignum).should == true
+ (@bignum >= (@bignum + 2)).should == false
+ (@bignum >= 5664.2).should == true
+ (@bignum >= 4).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { @bignum >= "4" }.should raise_error(ArgumentError)
+ -> { @bignum >= mock('str') }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/integer_spec.rb b/spec/ruby/core/integer/integer_spec.rb
new file mode 100644
index 0000000000..2d5d2e3e92
--- /dev/null
+++ b/spec/ruby/core/integer/integer_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "Integer" do
+ it "includes Comparable" do
+ Integer.include?(Comparable).should == true
+ end
+
+ it "is the class of both small and large integers" do
+ 42.class.should equal(Integer)
+ bignum_value.class.should equal(Integer)
+ end
+end
+
+describe "Integer#integer?" do
+ it "returns true for Integers" do
+ 0.should.integer?
+ -1.should.integer?
+ bignum_value.should.integer?
+ end
+end
diff --git a/spec/ruby/core/integer/lcm_spec.rb b/spec/ruby/core/integer/lcm_spec.rb
new file mode 100644
index 0000000000..296a001c8c
--- /dev/null
+++ b/spec/ruby/core/integer/lcm_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+
+describe "Integer#lcm" do
+ it "returns self if equal to the argument" do
+ 1.lcm(1).should == 1
+ 398.lcm(398).should == 398
+ end
+
+ it "returns an Integer" do
+ 36.lcm(6).should be_kind_of(Integer)
+ 4.lcm(20981).should be_kind_of(Integer)
+ end
+
+ it "returns the least common multiple of self and argument" do
+ 200.lcm(2001).should == 400200
+ 99.lcm(90).should == 990
+ end
+
+ it "returns a positive integer even if self is negative" do
+ -12.lcm(6).should == 12
+ -100.lcm(100).should == 100
+ end
+
+ it "returns a positive integer even if the argument is negative" do
+ 12.lcm(-6).should == 12
+ 100.lcm(-100).should == 100
+ end
+
+ it "returns a positive integer even if both self and argument are negative" do
+ -12.lcm(-6).should == 12
+ -100.lcm(-100).should == 100
+ end
+
+ it "accepts a Bignum argument" do
+ bignum = 9999**99
+ bignum.should be_kind_of(Integer)
+ 99.lcm(bignum).should == bignum
+ end
+
+ it "works if self is a Bignum" do
+ bignum = 9999**99
+ bignum.should be_kind_of(Integer)
+ bignum.lcm(99).should == bignum
+ end
+
+ it "raises an ArgumentError if not given an argument" do
+ -> { 12.lcm }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if given more than one argument" do
+ -> { 12.lcm(30, 20) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError unless the argument is an Integer" do
+ -> { 39.lcm(3.8) }.should raise_error(TypeError)
+ -> { 45872.lcm([]) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/left_shift_spec.rb b/spec/ruby/core/integer/left_shift_spec.rb
new file mode 100644
index 0000000000..135af90421
--- /dev/null
+++ b/spec/ruby/core/integer/left_shift_spec.rb
@@ -0,0 +1,211 @@
+require_relative '../../spec_helper'
+
+describe "Integer#<< (with n << m)" do
+ context "fixnum" do
+ it "returns n shifted left m bits when n > 0, m > 0" do
+ (1 << 1).should == 2
+ end
+
+ it "returns n shifted left m bits when n < 0, m > 0" do
+ (-1 << 1).should == -2
+ (-7 << 1).should == -14
+ (-42 << 2).should == -168
+ end
+
+ it "returns n shifted right m bits when n > 0, m < 0" do
+ (2 << -1).should == 1
+ end
+
+ it "returns n shifted right m bits when n < 0, m < 0" do
+ (-2 << -1).should == -1
+ end
+
+ it "returns 0 when n == 0" do
+ (0 << 1).should == 0
+ end
+
+ it "returns n when n > 0, m == 0" do
+ (1 << 0).should == 1
+ end
+
+ it "returns n when n < 0, m == 0" do
+ (-1 << 0).should == -1
+ end
+
+ it "returns 0 when n > 0, m < 0 and n < 2**-m" do
+ (3 << -2).should == 0
+ (7 << -3).should == 0
+ (127 << -7).should == 0
+
+ # To make sure the exponent is not truncated
+ (7 << -32).should == 0
+ (7 << -64).should == 0
+ end
+
+ it "returns -1 when n < 0, m < 0 and n > -(2**-m)" do
+ (-3 << -2).should == -1
+ (-7 << -3).should == -1
+ (-127 << -7).should == -1
+
+ # To make sure the exponent is not truncated
+ (-7 << -32).should == -1
+ (-7 << -64).should == -1
+ end
+
+ it "returns 0 when m < 0 and m is a Bignum" do
+ (3 << -bignum_value).should == 0
+ end
+
+ it "returns a Bignum == fixnum_max * 2 when fixnum_max << 1 and n > 0" do
+ result = fixnum_max << 1
+ result.should be_an_instance_of(Integer)
+ result.should == fixnum_max * 2
+ end
+
+ it "returns a Bignum == fixnum_min * 2 when fixnum_min << 1 and n < 0" do
+ result = fixnum_min << 1
+ result.should be_an_instance_of(Integer)
+ result.should == fixnum_min * 2
+ end
+
+ it "calls #to_int to convert the argument to an Integer" do
+ obj = mock("4")
+ obj.should_receive(:to_int).and_return(4)
+ (3 << obj).should == 48
+
+ obj = mock("to_int_neg_bignum")
+ obj.should_receive(:to_int).and_return(-bignum_value)
+ (3 << obj).should == 0
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock("a string")
+ obj.should_receive(:to_int).and_return("asdf")
+
+ -> { 3 << obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { 3 << nil }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { 3 << "4" }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value * 8 # 2 ** 67
+ end
+
+ it "returns n shifted left m bits when n > 0, m > 0" do
+ (@bignum << 4).should == 2361183241434822606848
+ end
+
+ it "returns n shifted left m bits when n < 0, m > 0" do
+ (-@bignum << 9).should == -75557863725914323419136
+ end
+
+ it "returns n shifted right m bits when n > 0, m < 0" do
+ (@bignum << -1).should == 73786976294838206464
+ end
+
+ it "returns n shifted right m bits when n < 0, m < 0" do
+ (-@bignum << -2).should == -36893488147419103232
+ end
+
+ it "returns n when n > 0, m == 0" do
+ (@bignum << 0).should == @bignum
+ end
+
+ it "returns n when n < 0, m == 0" do
+ (-@bignum << 0).should == -@bignum
+ end
+
+ it "returns 0 when m < 0 and m == p where 2**p > n >= 2**(p-1)" do
+ (@bignum << -68).should == 0
+ end
+
+ it "returns a Fixnum == fixnum_max when (fixnum_max * 2) << -1 and n > 0" do
+ result = (fixnum_max * 2) << -1
+ result.should be_an_instance_of(Integer)
+ result.should == fixnum_max
+ end
+
+ it "returns a Fixnum == fixnum_min when (fixnum_min * 2) << -1 and n < 0" do
+ result = (fixnum_min * 2) << -1
+ result.should be_an_instance_of(Integer)
+ result.should == fixnum_min
+ end
+
+ it "calls #to_int to convert the argument to an Integer" do
+ obj = mock("4")
+ obj.should_receive(:to_int).and_return(4)
+
+ (@bignum << obj).should == 2361183241434822606848
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock("a string")
+ obj.should_receive(:to_int).and_return("asdf")
+
+ -> { @bignum << obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { @bignum << nil }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { @bignum << "4" }.should raise_error(TypeError)
+ end
+ end
+
+ context "when m is a bignum or larger than int" do
+ it "returns -1 when m < 0 and n < 0" do
+ (-1 << -bignum_value).should == -1
+ (-1 << -(2**40)).should == -1
+
+ (-bignum_value << -bignum_value).should == -1
+ (-bignum_value << -(2**40)).should == -1
+ end
+
+ it "returns 0 when m < 0 and n >= 0" do
+ (0 << -bignum_value).should == 0
+ (1 << -bignum_value).should == 0
+ (bignum_value << -bignum_value).should == 0
+
+ (0 << -(2**40)).should == 0
+ (1 << -(2**40)).should == 0
+ (bignum_value << -(2**40)).should == 0
+ end
+
+ ruby_bug "#18517", ""..."3.2" do
+ it "returns 0 when m > 0 long and n == 0" do
+ (0 << (2**40)).should == 0
+ end
+ end
+
+ it "returns 0 when m > 0 bignum and n == 0" do
+ (0 << bignum_value).should == 0
+ end
+
+ ruby_bug "#18518", ""..."3.3" do
+ it "raises NoMemoryError when m > 0 and n != 0" do
+ coerce_long = mock("long")
+ coerce_long.stub!(:to_int).and_return(2**40)
+ coerce_bignum = mock("bignum")
+ coerce_bignum.stub!(:to_int).and_return(bignum_value)
+ exps = [2**40, bignum_value, coerce_long, coerce_bignum]
+
+ exps.each { |exp|
+ -> { (1 << exp) }.should raise_error(NoMemoryError)
+ -> { (-1 << exp) }.should raise_error(NoMemoryError)
+ -> { (bignum_value << exp) }.should raise_error(NoMemoryError)
+ -> { (-bignum_value << exp) }.should raise_error(NoMemoryError)
+ }
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/lt_spec.rb b/spec/ruby/core/integer/lt_spec.rb
new file mode 100644
index 0000000000..c182f82555
--- /dev/null
+++ b/spec/ruby/core/integer/lt_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_coerce'
+
+describe "Integer#<" do
+ it_behaves_like :integer_comparison_coerce_not_rescue, :<
+
+ context "fixnum" do
+ it "returns true if self is less than the given argument" do
+ (2 < 13).should == true
+ (-600 < -500).should == true
+
+ (5 < 1).should == false
+ (5 < 5).should == false
+
+ (900 < bignum_value).should == true
+ (5 < 4.999).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { 5 < "4" }.should raise_error(ArgumentError)
+ -> { 5 < mock('x') }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(32)
+ end
+
+ it "returns true if self is less than the given argument" do
+ (@bignum < @bignum + 1).should == true
+ (-@bignum < -(@bignum - 1)).should == true
+
+ (@bignum < 1).should == false
+ (@bignum < 5).should == false
+
+ (@bignum < 4.999).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { @bignum < "4" }.should raise_error(ArgumentError)
+ -> { @bignum < mock('str') }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/lte_spec.rb b/spec/ruby/core/integer/lte_spec.rb
new file mode 100644
index 0000000000..65b71d77f2
--- /dev/null
+++ b/spec/ruby/core/integer/lte_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_coerce'
+
+describe "Integer#<=" do
+ it_behaves_like :integer_comparison_coerce_not_rescue, :<=
+
+ context "fixnum" do
+ it "returns true if self is less than or equal to other" do
+ (2 <= 13).should == true
+ (-600 <= -500).should == true
+
+ (5 <= 1).should == false
+ (5 <= 5).should == true
+ (-2 <= -2).should == true
+
+ (900 <= bignum_value).should == true
+ (5 <= 4.999).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { 5 <= "4" }.should raise_error(ArgumentError)
+ -> { 5 <= mock('x') }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(39)
+ end
+
+ it "returns true if self is less than or equal to other" do
+ (@bignum <= @bignum).should == true
+ (-@bignum <= -(@bignum - 1)).should == true
+
+ (@bignum <= 4.999).should == false
+ end
+
+ it "returns false if compares with near float" do
+ (@bignum <= (@bignum + 0.0)).should == false
+ (@bignum <= (@bignum + 0.5)).should == false
+ end
+
+ it "returns true for bignums compare to a bigger float" do
+ (18446744073709551616 <= 1.8446744073709552e+19).should == true
+ (@bignum <= (@bignum + 9999.0)).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { @bignum <= "4" }.should raise_error(ArgumentError)
+ -> { @bignum <= mock('str') }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/magnitude_spec.rb b/spec/ruby/core/integer/magnitude_spec.rb
new file mode 100644
index 0000000000..48cf1a8534
--- /dev/null
+++ b/spec/ruby/core/integer/magnitude_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/abs'
+
+describe "Integer#magnitude" do
+ it_behaves_like :integer_abs, :magnitude
+end
diff --git a/spec/ruby/core/integer/minus_spec.rb b/spec/ruby/core/integer/minus_spec.rb
new file mode 100644
index 0000000000..aadf416a05
--- /dev/null
+++ b/spec/ruby/core/integer/minus_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_coerce'
+
+describe "Integer#-" do
+ it_behaves_like :integer_arithmetic_coerce_not_rescue, :-
+
+ context "fixnum" do
+ it "returns self minus the given Integer" do
+ (5 - 10).should == -5
+ (9237212 - 5_280).should == 9231932
+
+ (781 - 0.5).should == 780.5
+ (2_560_496 - bignum_value).should == -18446744073706991120
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10)
+ 13 - obj
+ }.should raise_error(TypeError)
+ -> { 13 - "10" }.should raise_error(TypeError)
+ -> { 13 - :symbol }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(314)
+ end
+
+ it "returns self minus the given Integer" do
+ (@bignum - 9).should == 18446744073709551921
+ (@bignum - 12.57).should be_close(18446744073709551917.43, TOLERANCE)
+ (@bignum - bignum_value(42)).should == 272
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { @bignum - mock('10') }.should raise_error(TypeError)
+ -> { @bignum - "10" }.should raise_error(TypeError)
+ -> { @bignum - :symbol }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/modulo_spec.rb b/spec/ruby/core/integer/modulo_spec.rb
new file mode 100644
index 0000000000..e263338e38
--- /dev/null
+++ b/spec/ruby/core/integer/modulo_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/modulo'
+
+describe "Integer#%" do
+ it_behaves_like :integer_modulo, :%
+end
+
+describe "Integer#modulo" do
+ it_behaves_like :integer_modulo, :modulo
+end
diff --git a/spec/ruby/core/integer/multiply_spec.rb b/spec/ruby/core/integer/multiply_spec.rb
new file mode 100644
index 0000000000..5037d27458
--- /dev/null
+++ b/spec/ruby/core/integer/multiply_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_coerce'
+
+describe "Integer#*" do
+ it_behaves_like :integer_arithmetic_coerce_not_rescue, :*
+
+ context "fixnum" do
+ it "returns self multiplied by the given Integer" do
+ (4923 * 2).should == 9846
+ (1342177 * 800).should == 1073741600
+ (65536 * 65536).should == 4294967296
+
+ (256 * bignum_value).should == 4722366482869645213696
+ (6712 * 0.25).should == 1678.0
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10)
+ 13 * obj
+ }.should raise_error(TypeError)
+ -> { 13 * "10" }.should raise_error(TypeError)
+ -> { 13 * :symbol }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(772)
+ end
+
+ it "returns self multiplied by the given Integer" do
+ (@bignum * (1/bignum_value(0xffff).to_f)).should be_close(1.0, TOLERANCE)
+ (@bignum * (1/bignum_value(0xffff).to_f)).should be_close(1.0, TOLERANCE)
+ (@bignum * 10).should == 184467440737095523880
+ (@bignum * (@bignum - 40)).should == 340282366920938491207277694290934407024
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { @bignum * mock('10') }.should raise_error(TypeError)
+ -> { @bignum * "10" }.should raise_error(TypeError)
+ -> { @bignum * :symbol }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/next_spec.rb b/spec/ruby/core/integer/next_spec.rb
new file mode 100644
index 0000000000..63c4e67893
--- /dev/null
+++ b/spec/ruby/core/integer/next_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/next'
+
+describe "Integer#next" do
+ it_behaves_like :integer_next, :next
+end
diff --git a/spec/ruby/core/integer/nobits_spec.rb b/spec/ruby/core/integer/nobits_spec.rb
new file mode 100644
index 0000000000..685759ffa3
--- /dev/null
+++ b/spec/ruby/core/integer/nobits_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe "Integer#nobits?" do
+ it "returns true if and only if all no bits of the argument are set in the receiver" do
+ 42.nobits?(42).should == false
+ 0b1010_1010.nobits?(0b1000_0010).should == false
+ 0b1010_1010.nobits?(0b1000_0001).should == false
+ 0b0100_0101.nobits?(0b1010_1010).should == true
+ different_bignum = (2 * bignum_value) & (~bignum_value)
+ (0b1010_1010 | different_bignum).nobits?(0b1000_0010 | bignum_value).should == false
+ (0b1010_1010 | different_bignum).nobits?(0b1000_0001 | bignum_value).should == false
+ (0b0100_0101 | different_bignum).nobits?(0b1010_1010 | bignum_value).should == true
+ end
+
+ it "handles negative values using two's complement notation" do
+ (~0b1101).nobits?(0b1101).should == true
+ (-42).nobits?(-42).should == false
+ (~0b1101).nobits?(~0b10).should == false
+ (~(0b1101 | bignum_value)).nobits?(~(0b10 | bignum_value)).should == false
+ end
+
+ it "coerces the rhs using to_int" do
+ obj = mock("the int 0b10")
+ obj.should_receive(:to_int).and_return(0b10)
+ 0b110.nobits?(obj).should == false
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:coerce).any_number_of_times.and_return([42,10])
+ 13.nobits?(obj)
+ }.should raise_error(TypeError)
+ -> { 13.nobits?("10") }.should raise_error(TypeError)
+ -> { 13.nobits?(:symbol) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/numerator_spec.rb b/spec/ruby/core/integer/numerator_spec.rb
new file mode 100644
index 0000000000..f4149d770e
--- /dev/null
+++ b/spec/ruby/core/integer/numerator_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe "Integer#numerator" do
+ before :all do
+ @numbers = [
+ 0,
+ 29871,
+ 99999999999999**99,
+ 72628191273,
+ ].map{|n| [-n, n]}.flatten
+ end
+
+ it "returns self" do
+ @numbers.each do |number|
+ number.numerator.should == number
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/odd_spec.rb b/spec/ruby/core/integer/odd_spec.rb
new file mode 100644
index 0000000000..dd779fa44b
--- /dev/null
+++ b/spec/ruby/core/integer/odd_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+
+describe "Integer#odd?" do
+ context "fixnum" do
+ it "returns true when self is an odd number" do
+ (-2).odd?.should be_false
+ (-1).odd?.should be_true
+
+ 0.odd?.should be_false
+ 1.odd?.should be_true
+ 2.odd?.should be_false
+
+ bignum_value(0).odd?.should be_false
+ bignum_value(1).odd?.should be_true
+
+ (-bignum_value(0)).odd?.should be_false
+ (-bignum_value(1)).odd?.should be_true
+ end
+ end
+
+ context "bignum" do
+ it "returns true if self is odd and positive" do
+ (987279**19).odd?.should be_true
+ end
+
+ it "returns true if self is odd and negative" do
+ (-9873389**97).odd?.should be_true
+ end
+
+ it "returns false if self is even and positive" do
+ (10000000**10).odd?.should be_false
+ end
+
+ it "returns false if self is even and negative" do
+ (-1000000**100).odd?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/ord_spec.rb b/spec/ruby/core/integer/ord_spec.rb
new file mode 100644
index 0000000000..bcb57bea98
--- /dev/null
+++ b/spec/ruby/core/integer/ord_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "Integer#ord" do
+ it "returns self" do
+ 20.ord.should eql(20)
+ 40.ord.should eql(40)
+
+ 0.ord.should eql(0)
+ (-10).ord.should eql(-10)
+
+ ?a.ord.should eql(97)
+ ?Z.ord.should eql(90)
+
+ bignum_value.ord.should eql(bignum_value)
+ (-bignum_value).ord.should eql(-bignum_value)
+ end
+end
diff --git a/spec/ruby/core/integer/plus_spec.rb b/spec/ruby/core/integer/plus_spec.rb
new file mode 100644
index 0000000000..d01a76ab58
--- /dev/null
+++ b/spec/ruby/core/integer/plus_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_coerce'
+
+describe "Integer#+" do
+ it_behaves_like :integer_arithmetic_coerce_not_rescue, :+
+
+ context "fixnum" do
+ it "returns self plus the given Integer" do
+ (491 + 2).should == 493
+ (90210 + 10).should == 90220
+
+ (9 + bignum_value).should == 18446744073709551625
+ (1001 + 5.219).should == 1006.219
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10)
+ 13 + obj
+ }.should raise_error(TypeError)
+ -> { 13 + "10" }.should raise_error(TypeError)
+ -> { 13 + :symbol }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(76)
+ end
+
+ it "returns self plus the given Integer" do
+ (@bignum + 4).should == 18446744073709551696
+ (@bignum + 4.2).should be_close(18446744073709551696.2, TOLERANCE)
+ (@bignum + bignum_value(3)).should == 36893488147419103311
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { @bignum + mock('10') }.should raise_error(TypeError)
+ -> { @bignum + "10" }.should raise_error(TypeError)
+ -> { @bignum + :symbol}.should raise_error(TypeError)
+ end
+ end
+
+ it "can be redefined" do
+ code = <<~RUBY
+ class Integer
+ alias_method :old_plus, :+
+ def +(other)
+ self - other
+ end
+ end
+ result = 1 + 2
+ Integer.alias_method :+, :old_plus
+ print result
+ RUBY
+ ruby_exe(code).should == "-1"
+ end
+end
diff --git a/spec/ruby/core/integer/pow_spec.rb b/spec/ruby/core/integer/pow_spec.rb
new file mode 100644
index 0000000000..4712911095
--- /dev/null
+++ b/spec/ruby/core/integer/pow_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/exponent'
+
+describe "Integer#pow" do
+ context "one argument is passed" do
+ it_behaves_like :integer_exponent, :pow
+ end
+
+ context "two arguments are passed" do
+ it "returns modulo of self raised to the given power" do
+ 2.pow(5, 12).should == 8
+ 2.pow(6, 13).should == 12
+ 2.pow(7, 14).should == 2
+ 2.pow(8, 15).should == 1
+ end
+
+ it "works well with bignums" do
+ 2.pow(61, 5843009213693951).should eql 3697379018277258
+ 2.pow(62, 5843009213693952).should eql 1551748822859776
+ 2.pow(63, 5843009213693953).should eql 3103497645717974
+ 2.pow(64, 5843009213693954).should eql 363986077738838
+ end
+
+ it "handles sign like #divmod does" do
+ 2.pow(5, 12).should == 8
+ 2.pow(5, -12).should == -4
+ -2.pow(5, 12).should == 4
+ -2.pow(5, -12).should == -8
+ end
+
+ it "ensures all arguments are integers" do
+ -> { 2.pow(5, 12.0) }.should raise_error(TypeError, /2nd argument not allowed unless all arguments are integers/)
+ -> { 2.pow(5, Rational(12, 1)) }.should raise_error(TypeError, /2nd argument not allowed unless all arguments are integers/)
+ end
+
+ it "raises TypeError for non-numeric value" do
+ -> { 2.pow(5, "12") }.should raise_error(TypeError)
+ -> { 2.pow(5, []) }.should raise_error(TypeError)
+ -> { 2.pow(5, nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0" do
+ -> { 2.pow(5, 0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a RangeError when the first argument is negative and the second argument is present" do
+ -> { 2.pow(-5, 1) }.should raise_error(RangeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/pred_spec.rb b/spec/ruby/core/integer/pred_spec.rb
new file mode 100644
index 0000000000..86750ebfd1
--- /dev/null
+++ b/spec/ruby/core/integer/pred_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Integer#pred" do
+ it "returns the Integer equal to self - 1" do
+ 0.pred.should eql(-1)
+ -1.pred.should eql(-2)
+ bignum_value.pred.should eql(bignum_value(-1))
+ fixnum_min.pred.should eql(fixnum_min - 1)
+ 20.pred.should eql(19)
+ end
+end
diff --git a/spec/ruby/core/integer/rationalize_spec.rb b/spec/ruby/core/integer/rationalize_spec.rb
new file mode 100644
index 0000000000..09d741af33
--- /dev/null
+++ b/spec/ruby/core/integer/rationalize_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "Integer#rationalize" do
+ before :all do
+ @numbers = [
+ 0,
+ 29871,
+ 99999999999999**99,
+ -72628191273,
+ ]
+ end
+
+ it "returns a Rational object" do
+ @numbers.each do |number|
+ number.rationalize.should be_an_instance_of(Rational)
+ end
+ end
+
+ it "uses self as the numerator" do
+ @numbers.each do |number|
+ number.rationalize.numerator.should == number
+ end
+ end
+
+ it "uses 1 as the denominator" do
+ @numbers.each do |number|
+ number.rationalize.denominator.should == 1
+ end
+ end
+
+ it "ignores a single argument" do
+ 1.rationalize(0.1).should == Rational(1,1)
+ end
+
+ it "raises ArgumentError when passed more than one argument" do
+ -> { 1.rationalize(0.1, 0.1) }.should raise_error(ArgumentError)
+ -> { 1.rationalize(0.1, 0.1, 2) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/integer/remainder_spec.rb b/spec/ruby/core/integer/remainder_spec.rb
new file mode 100644
index 0000000000..96268b3af5
--- /dev/null
+++ b/spec/ruby/core/integer/remainder_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+
+describe "Integer#remainder" do
+ context "fixnum" do
+ it "returns the remainder of dividing self by other" do
+ 5.remainder(3).should == 2
+ 5.remainder(3.0).should == 2.0
+ 5.remainder(Rational(3, 1)).should == Rational(2, 1)
+ end
+
+ it "means x-y*(x/y).truncate" do
+ 5.remainder(3).should == 2
+ 5.remainder(3.3).should be_close(1.7, TOLERANCE)
+ 5.remainder(3.7).should be_close(1.3, TOLERANCE)
+ end
+
+ it "keeps sign of self" do
+ 5.remainder( 3).should == 2
+ 5.remainder(-3).should == 2
+ -5.remainder( 3).should == -2
+ -5.remainder(-3).should == -2
+ end
+
+ it "raises TypeError if passed non-numeric argument" do
+ -> { 5.remainder("3") }.should raise_error(TypeError)
+ -> { 5.remainder(:"3") }.should raise_error(TypeError)
+ -> { 5.remainder([]) }.should raise_error(TypeError)
+ -> { 5.remainder(nil) }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ it "returns the remainder of dividing self by other" do
+ a = bignum_value(79)
+ a.remainder(2).should == 1
+ a.remainder(97.345).should be_close(93.1349992295444, TOLERANCE)
+ a.remainder(bignum_value).should == 79
+ end
+
+ it "raises a ZeroDivisionError if other is zero and not a Float" do
+ -> { bignum_value(66).remainder(0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "does raises ZeroDivisionError if other is zero and a Float" do
+ a = bignum_value(7)
+ b = bignum_value(32)
+ -> { a.remainder(0.0) }.should raise_error(ZeroDivisionError)
+ -> { b.remainder(-0.0) }.should raise_error(ZeroDivisionError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/right_shift_spec.rb b/spec/ruby/core/integer/right_shift_spec.rb
new file mode 100644
index 0000000000..81405667b2
--- /dev/null
+++ b/spec/ruby/core/integer/right_shift_spec.rb
@@ -0,0 +1,233 @@
+require_relative '../../spec_helper'
+
+describe "Integer#>> (with n >> m)" do
+ context "fixnum" do
+ it "returns n shifted right m bits when n > 0, m > 0" do
+ (2 >> 1).should == 1
+ end
+
+ it "returns n shifted right m bits when n < 0, m > 0" do
+ (-2 >> 1).should == -1
+ (-7 >> 1).should == -4
+ (-42 >> 2).should == -11
+ end
+
+ it "returns n shifted left m bits when n > 0, m < 0" do
+ (1 >> -1).should == 2
+ end
+
+ it "returns n shifted left m bits when n < 0, m < 0" do
+ (-1 >> -1).should == -2
+ end
+
+ it "returns 0 when n == 0" do
+ (0 >> 1).should == 0
+ end
+
+ it "returns n when n > 0, m == 0" do
+ (1 >> 0).should == 1
+ end
+
+ it "returns n when n < 0, m == 0" do
+ (-1 >> 0).should == -1
+ end
+
+ it "returns 0 when n > 0, m > 0 and n < 2**m" do
+ (3 >> 2).should == 0
+ (7 >> 3).should == 0
+ (127 >> 7).should == 0
+
+ # To make sure the exponent is not truncated
+ (7 >> 32).should == 0
+ (7 >> 64).should == 0
+ end
+
+ it "returns -1 when n < 0, m > 0 and n > -(2**m)" do
+ (-3 >> 2).should == -1
+ (-7 >> 3).should == -1
+ (-127 >> 7).should == -1
+
+ # To make sure the exponent is not truncated
+ (-7 >> 32).should == -1
+ (-7 >> 64).should == -1
+ end
+
+ it "returns a Bignum == fixnum_max * 2 when fixnum_max >> -1 and n > 0" do
+ result = fixnum_max >> -1
+ result.should be_an_instance_of(Integer)
+ result.should == fixnum_max * 2
+ end
+
+ it "returns a Bignum == fixnum_min * 2 when fixnum_min >> -1 and n < 0" do
+ result = fixnum_min >> -1
+ result.should be_an_instance_of(Integer)
+ result.should == fixnum_min * 2
+ end
+
+ it "calls #to_int to convert the argument to an Integer" do
+ obj = mock("2")
+ obj.should_receive(:to_int).and_return(2)
+ (8 >> obj).should == 2
+
+ obj = mock("to_int_bignum")
+ obj.should_receive(:to_int).and_return(bignum_value)
+ (8 >> obj).should == 0
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock("a string")
+ obj.should_receive(:to_int).and_return("asdf")
+
+ -> { 3 >> obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { 3 >> nil }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { 3 >> "4" }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value * 8 # 2 ** 67
+ end
+
+ it "returns n shifted right m bits when n > 0, m > 0" do
+ (@bignum >> 1).should == 73786976294838206464
+ end
+
+ it "returns n shifted right m bits when n < 0, m > 0" do
+ (-@bignum >> 2).should == -36893488147419103232
+ end
+
+ it "respects twos complement signed shifting" do
+ # This explicit left hand value is important because it is the
+ # exact bit pattern that matters, so it's important it's right
+ # here to show the significance.
+ #
+
+ (-42949672980000000000000 >> 14).should == -2621440001220703125
+ (-42949672980000000000001 >> 14).should == -2621440001220703126
+ # Note the off by one -------------------- ^^^^^^^^^^^^^^^^^^^^
+ # This is because even though we discard the lowest bit, in twos
+ # complement it would influence the bits to the left of it.
+
+ (-42949672980000000000000 >> 15).should == -1310720000610351563
+ (-42949672980000000000001 >> 15).should == -1310720000610351563
+
+ (-0xfffffffffffffffff >> 32).should == -68719476736
+ end
+
+ it "respects twos complement signed shifting for very large values" do
+ giant = 42949672980000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ neg = -giant
+
+ (giant >> 84).should == 2220446050284288846538547929770901490087453566957265138626098632812
+ (neg >> 84).should == -2220446050284288846538547929770901490087453566957265138626098632813
+ end
+
+ it "returns n shifted left m bits when n > 0, m < 0" do
+ (@bignum >> -2).should == 590295810358705651712
+ end
+
+ it "returns n shifted left m bits when n < 0, m < 0" do
+ (-@bignum >> -3).should == -1180591620717411303424
+ end
+
+ it "returns n when n > 0, m == 0" do
+ (@bignum >> 0).should == @bignum
+ end
+
+ it "returns n when n < 0, m == 0" do
+ (-@bignum >> 0).should == -@bignum
+ end
+
+ it "returns 0 when m > 0 and m == p where 2**p > n >= 2**(p-1)" do
+ (@bignum >> 68).should == 0
+ end
+
+ it "returns a Fixnum == fixnum_max when (fixnum_max * 2) >> 1 and n > 0" do
+ result = (fixnum_max * 2) >> 1
+ result.should be_an_instance_of(Integer)
+ result.should == fixnum_max
+ end
+
+ it "returns a Fixnum == fixnum_min when (fixnum_min * 2) >> 1 and n < 0" do
+ result = (fixnum_min * 2) >> 1
+ result.should be_an_instance_of(Integer)
+ result.should == fixnum_min
+ end
+
+ it "calls #to_int to convert the argument to an Integer" do
+ obj = mock("2")
+ obj.should_receive(:to_int).and_return(2)
+
+ (@bignum >> obj).should == 36893488147419103232
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock("a string")
+ obj.should_receive(:to_int).and_return("asdf")
+
+ -> { @bignum >> obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { @bignum >> nil }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { @bignum >> "4" }.should raise_error(TypeError)
+ end
+ end
+
+ context "when m is a bignum or larger than int" do
+ it "returns -1 when m > 0 and n < 0" do
+ (-1 >> bignum_value).should == -1
+ (-1 >> (2**40)).should == -1
+
+ (-bignum_value >> bignum_value).should == -1
+ (-bignum_value >> (2**40)).should == -1
+ end
+
+ it "returns 0 when m > 0 and n >= 0" do
+ (0 >> bignum_value).should == 0
+ (1 >> bignum_value).should == 0
+ (bignum_value >> bignum_value).should == 0
+
+ (0 >> (2**40)).should == 0
+ (1 >> (2**40)).should == 0
+ (bignum_value >> (2**40)).should == 0
+ end
+
+ ruby_bug "#18517", ""..."3.2" do
+ it "returns 0 when m < 0 long and n == 0" do
+ (0 >> -(2**40)).should == 0
+ end
+ end
+
+ it "returns 0 when m < 0 bignum and n == 0" do
+ (0 >> -bignum_value).should == 0
+ end
+
+ ruby_bug "#18518", ""..."3.3" do
+ it "raises NoMemoryError when m < 0 and n != 0" do
+ coerce_long = mock("long")
+ coerce_long.stub!(:to_int).and_return(-(2**40))
+ coerce_bignum = mock("bignum")
+ coerce_bignum.stub!(:to_int).and_return(-bignum_value)
+ exps = [-(2**40), -bignum_value, coerce_long, coerce_bignum]
+
+ exps.each { |exp|
+ -> { (1 >> exp) }.should raise_error(NoMemoryError)
+ -> { (-1 >> exp) }.should raise_error(NoMemoryError)
+ -> { (bignum_value >> exp) }.should raise_error(NoMemoryError)
+ -> { (-bignum_value >> exp) }.should raise_error(NoMemoryError)
+ }
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/round_spec.rb b/spec/ruby/core/integer/round_spec.rb
new file mode 100644
index 0000000000..45ac126fd3
--- /dev/null
+++ b/spec/ruby/core/integer/round_spec.rb
@@ -0,0 +1,83 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+require_relative 'shared/integer_rounding'
+
+describe "Integer#round" do
+ it_behaves_like :integer_to_i, :round
+ it_behaves_like :integer_rounding_positive_precision, :round
+
+ # redmine:5228
+ it "returns itself rounded if passed a negative value" do
+ +249.round(-2).should eql(+200)
+ -249.round(-2).should eql(-200)
+ (+25 * 10**70 - 1).round(-71).should eql(+20 * 10**70)
+ (-25 * 10**70 + 1).round(-71).should eql(-20 * 10**70)
+ end
+
+ it "returns itself rounded to nearest if passed a negative value" do
+ +250.round(-2).should eql(+300)
+ -250.round(-2).should eql(-300)
+ (+25 * 10**70).round(-71).should eql(+30 * 10**70)
+ (-25 * 10**70).round(-71).should eql(-30 * 10**70)
+ end
+
+ platform_is_not wordsize: 32 do
+ it "raises a RangeError when passed a big negative value" do
+ -> { 42.round(fixnum_min) }.should raise_error(RangeError)
+ end
+ end
+
+ it "raises a RangeError when passed Float::INFINITY" do
+ -> { 42.round(Float::INFINITY) }.should raise_error(RangeError)
+ end
+
+ it "raises a RangeError when passed a beyond signed int" do
+ -> { 42.round(1<<31) }.should raise_error(RangeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { 42.round("4") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when its argument cannot be converted to an Integer" do
+ -> { 42.round(nil) }.should raise_error(TypeError)
+ end
+
+ it "calls #to_int on the argument to convert it to an Integer" do
+ obj = mock("Object")
+ obj.should_receive(:to_int).and_return(0)
+ 42.round(obj)
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock("Object")
+ obj.stub!(:to_int).and_return([])
+ -> { 42.round(obj) }.should raise_error(TypeError)
+ end
+
+ it "returns different rounded values depending on the half option" do
+ 25.round(-1, half: :up).should eql(30)
+ 25.round(-1, half: :down).should eql(20)
+ 25.round(-1, half: :even).should eql(20)
+ 25.round(-1, half: nil).should eql(30)
+ 35.round(-1, half: :up).should eql(40)
+ 35.round(-1, half: :down).should eql(30)
+ 35.round(-1, half: :even).should eql(40)
+ 35.round(-1, half: nil).should eql(40)
+ (-25).round(-1, half: :up).should eql(-30)
+ (-25).round(-1, half: :down).should eql(-20)
+ (-25).round(-1, half: :even).should eql(-20)
+ (-25).round(-1, half: nil).should eql(-30)
+ end
+
+ it "returns itself if passed a positive precision and the half option" do
+ 35.round(1, half: :up).should eql(35)
+ 35.round(1, half: :down).should eql(35)
+ 35.round(1, half: :even).should eql(35)
+ end
+
+ it "raises ArgumentError for an unknown rounding mode" do
+ -> { 42.round(-1, half: :foo) }.should raise_error(ArgumentError, /invalid rounding mode: foo/)
+ -> { 42.round(1, half: :foo) }.should raise_error(ArgumentError, /invalid rounding mode: foo/)
+ end
+end
diff --git a/spec/ruby/core/integer/shared/abs.rb b/spec/ruby/core/integer/shared/abs.rb
new file mode 100644
index 0000000000..43844c9065
--- /dev/null
+++ b/spec/ruby/core/integer/shared/abs.rb
@@ -0,0 +1,18 @@
+describe :integer_abs, shared: true do
+ context "fixnum" do
+ it "returns self's absolute fixnum value" do
+ { 0 => [0, -0, +0], 2 => [2, -2, +2], 100 => [100, -100, +100] }.each do |key, values|
+ values.each do |value|
+ value.send(@method).should == key
+ end
+ end
+ end
+ end
+
+ context "bignum" do
+ it "returns the absolute bignum value" do
+ bignum_value(39).send(@method).should == 18446744073709551655
+ (-bignum_value(18)).send(@method).should == 18446744073709551634
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/shared/arithmetic_coerce.rb b/spec/ruby/core/integer/shared/arithmetic_coerce.rb
new file mode 100644
index 0000000000..4c0cbcb999
--- /dev/null
+++ b/spec/ruby/core/integer/shared/arithmetic_coerce.rb
@@ -0,0 +1,31 @@
+require_relative '../fixtures/classes'
+
+describe :integer_arithmetic_coerce_rescue, shared: true do
+ it "rescues exception (StandardError and subclasses) raised in other#coerce and raises TypeError" do
+ b = mock("numeric with failed #coerce")
+ b.should_receive(:coerce).and_raise(IntegerSpecs::CoerceError)
+
+ # e.g. 1 + b
+ -> { 1.send(@method, b) }.should raise_error(TypeError, /MockObject can't be coerced into Integer/)
+ end
+
+ it "does not rescue Exception and StandardError siblings raised in other#coerce" do
+ [Exception, NoMemoryError].each do |exception|
+ b = mock("numeric with failed #coerce")
+ b.should_receive(:coerce).and_raise(exception)
+
+ # e.g. 1 + b
+ -> { 1.send(@method, b) }.should raise_error(exception)
+ end
+ end
+end
+
+describe :integer_arithmetic_coerce_not_rescue, shared: true do
+ it "does not rescue exception raised in other#coerce" do
+ b = mock("numeric with failed #coerce")
+ b.should_receive(:coerce).and_raise(IntegerSpecs::CoerceError)
+
+ # e.g. 1 + b
+ -> { 1.send(@method, b) }.should raise_error(IntegerSpecs::CoerceError)
+ end
+end
diff --git a/spec/ruby/core/integer/shared/comparison_coerce.rb b/spec/ruby/core/integer/shared/comparison_coerce.rb
new file mode 100644
index 0000000000..af52f5e99b
--- /dev/null
+++ b/spec/ruby/core/integer/shared/comparison_coerce.rb
@@ -0,0 +1,11 @@
+require_relative '../fixtures/classes'
+
+describe :integer_comparison_coerce_not_rescue, shared: true do
+ it "does not rescue exception raised in other#coerce" do
+ b = mock("numeric with failed #coerce")
+ b.should_receive(:coerce).and_raise(IntegerSpecs::CoerceError)
+
+ # e.g. 1 > b
+ -> { 1.send(@method, b) }.should raise_error(IntegerSpecs::CoerceError)
+ end
+end
diff --git a/spec/ruby/core/integer/shared/equal.rb b/spec/ruby/core/integer/shared/equal.rb
new file mode 100644
index 0000000000..ecee17831c
--- /dev/null
+++ b/spec/ruby/core/integer/shared/equal.rb
@@ -0,0 +1,58 @@
+describe :integer_equal, shared: true do
+ context "fixnum" do
+ it "returns true if self has the same value as other" do
+ 1.send(@method, 1).should == true
+ 9.send(@method, 5).should == false
+
+ # Actually, these call Float#==, Integer#== etc.
+ 9.send(@method, 9.0).should == true
+ 9.send(@method, 9.01).should == false
+
+ 10.send(@method, bignum_value).should == false
+ end
+
+ it "calls 'other == self' if the given argument is not an Integer" do
+ 1.send(@method, '*').should == false
+
+ obj = mock('one other')
+ obj.should_receive(:==).any_number_of_times.and_return(false)
+ 1.send(@method, obj).should == false
+
+ obj = mock('another')
+ obj.should_receive(:==).any_number_of_times.and_return(true)
+ 2.send(@method, obj).should == true
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value
+ end
+
+ it "returns true if self has the same value as the given argument" do
+ @bignum.send(@method, @bignum).should == true
+ @bignum.send(@method, @bignum.to_f).should == true
+
+ @bignum.send(@method, @bignum + 1).should == false
+ (@bignum + 1).send(@method, @bignum).should == false
+
+ @bignum.send(@method, 9).should == false
+ @bignum.send(@method, 9.01).should == false
+
+ @bignum.send(@method, bignum_value(10)).should == false
+ end
+
+ it "calls 'other == self' if the given argument is not an Integer" do
+ obj = mock('not integer')
+ obj.should_receive(:==).and_return(true)
+ @bignum.send(@method, obj).should == true
+ end
+
+ it "returns the result of 'other == self' as a boolean" do
+ obj = mock('not integer')
+ obj.should_receive(:==).exactly(2).times.and_return("woot", nil)
+ @bignum.send(@method, obj).should == true
+ @bignum.send(@method, obj).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/shared/exponent.rb b/spec/ruby/core/integer/shared/exponent.rb
new file mode 100644
index 0000000000..15df518b7e
--- /dev/null
+++ b/spec/ruby/core/integer/shared/exponent.rb
@@ -0,0 +1,126 @@
+describe :integer_exponent, shared: true do
+ context "fixnum" do
+ it "returns self raised to the given power" do
+ 2.send(@method, 0).should eql 1
+ 2.send(@method, 1).should eql 2
+ 2.send(@method, 2).should eql 4
+
+ 9.send(@method, 0.5).should eql 3.0
+ 9.send(@method, Rational(1, 2)).should eql 3.0
+ 5.send(@method, -1).to_f.to_s.should == '0.2'
+
+ 2.send(@method, 40).should eql 1099511627776
+ end
+
+ it "overflows the answer to a bignum transparently" do
+ 2.send(@method, 29).should eql 536870912
+ 2.send(@method, 30).should eql 1073741824
+ 2.send(@method, 31).should eql 2147483648
+ 2.send(@method, 32).should eql 4294967296
+
+ 2.send(@method, 61).should eql 2305843009213693952
+ 2.send(@method, 62).should eql 4611686018427387904
+ 2.send(@method, 63).should eql 9223372036854775808
+ 2.send(@method, 64).should eql 18446744073709551616
+ 8.send(@method, 23).should eql 590295810358705651712
+ end
+
+ it "raises negative numbers to the given power" do
+ (-2).send(@method, 29).should eql(-536870912)
+ (-2).send(@method, 30).should eql(1073741824)
+ (-2).send(@method, 31).should eql(-2147483648)
+ (-2).send(@method, 32).should eql(4294967296)
+ (-2).send(@method, 33).should eql(-8589934592)
+
+ (-2).send(@method, 61).should eql(-2305843009213693952)
+ (-2).send(@method, 62).should eql(4611686018427387904)
+ (-2).send(@method, 63).should eql(-9223372036854775808)
+ (-2).send(@method, 64).should eql(18446744073709551616)
+ (-2).send(@method, 65).should eql(-36893488147419103232)
+ end
+
+ it "can raise 1 to a bignum safely" do
+ 1.send(@method, 4611686018427387904).should eql 1
+ end
+
+ it "can raise -1 to a bignum safely" do
+ (-1).send(@method, 4611686018427387904).should eql(1)
+ (-1).send(@method, 4611686018427387905).should eql(-1)
+ end
+
+ it "returns Float::INFINITY when the number is too big" do
+ -> {
+ 2.send(@method, 427387904).should == Float::INFINITY
+ }.should complain(/warning: in a\*\*b, b may be too big/)
+ end
+
+ it "raises a ZeroDivisionError for 0 ** -1" do
+ -> { 0.send(@method, -1) }.should raise_error(ZeroDivisionError)
+ -> { 0.send(@method, Rational(-1, 1)) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "returns Float::INFINITY for 0 ** -1.0" do
+ 0.send(@method, -1.0).should == Float::INFINITY
+ end
+
+ it "raises a TypeError when given a non-numeric power" do
+ -> { 13.send(@method, "10") }.should raise_error(TypeError)
+ -> { 13.send(@method, :symbol) }.should raise_error(TypeError)
+ -> { 13.send(@method, nil) }.should raise_error(TypeError)
+ end
+
+ it "coerces power and calls #**" do
+ num_2 = mock("2")
+ num_13 = mock("13")
+ num_2.should_receive(:coerce).with(13).and_return([num_13, num_2])
+ num_13.should_receive(:**).with(num_2).and_return(169)
+
+ 13.send(@method, num_2).should == 169
+ end
+
+ it "returns Float when power is Float" do
+ 2.send(@method, 2.0).should == 4.0
+ end
+
+ it "returns Rational when power is Rational" do
+ 2.send(@method, Rational(2, 1)).should == Rational(4, 1)
+ end
+
+ it "returns a complex number when negative and raised to a fractional power" do
+ (-8).send(@method, 1.0/3) .should be_close(Complex(1, 1.73205), TOLERANCE)
+ (-8).send(@method, Rational(1, 3)).should be_close(Complex(1, 1.73205), TOLERANCE)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(47)
+ end
+
+ it "returns self raised to other power" do
+ (@bignum.send(@method, 4)).should == 115792089237316196603666111261383895964500887398800252671052694326794607293761
+ (@bignum.send(@method, 1.3)).should be_close(11109528802438156839288832.0, TOLERANCE)
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { @bignum.send(@method, mock('10')) }.should raise_error(TypeError)
+ -> { @bignum.send(@method, "10") }.should raise_error(TypeError)
+ -> { @bignum.send(@method, :symbol) }.should raise_error(TypeError)
+ end
+
+ it "switch to a Float when the values is too big" do
+ flt = nil
+ -> {
+ flt = @bignum.send(@method, @bignum)
+ }.should complain(/warning: in a\*\*b, b may be too big/)
+ flt.should be_kind_of(Float)
+ flt.infinite?.should == 1
+ end
+
+ it "returns a complex number when negative and raised to a fractional power" do
+ (-bignum_value).send(@method, (1.0/2)).should be_close(Complex(0.0, 4294967296.0), TOLERANCE)
+ (-@bignum).send(@method, (1.0/3)) .should be_close(Complex(1321122.9748145656, 2288252.1154253655), TOLERANCE)
+ (-@bignum).send(@method, Rational(1,3)).should be_close(Complex(1321122.9748145656, 2288252.1154253655), TOLERANCE)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/shared/integer_rounding.rb b/spec/ruby/core/integer/shared/integer_rounding.rb
new file mode 100644
index 0000000000..56d1819f84
--- /dev/null
+++ b/spec/ruby/core/integer/shared/integer_rounding.rb
@@ -0,0 +1,19 @@
+describe :integer_rounding_positive_precision, shared: true do
+ it "returns self if not passed a precision" do
+ [2, -4, 10**70, -10**100].each do |v|
+ v.send(@method).should eql(v)
+ end
+ end
+
+ it "returns self if passed a precision of zero" do
+ [2, -4, 10**70, -10**100].each do |v|
+ v.send(@method, 0).should eql(v)
+ end
+ end
+
+ it "returns itself if passed a positive precision" do
+ [2, -4, 10**70, -10**100].each do |v|
+ v.send(@method, 42).should eql(v)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/shared/modulo.rb b/spec/ruby/core/integer/shared/modulo.rb
new file mode 100644
index 0000000000..f678a10806
--- /dev/null
+++ b/spec/ruby/core/integer/shared/modulo.rb
@@ -0,0 +1,74 @@
+describe :integer_modulo, shared: true do
+ context "fixnum" do
+ it "returns the modulus obtained from dividing self by the given argument" do
+ 13.send(@method, 4).should == 1
+ 4.send(@method, 13).should == 4
+
+ 13.send(@method, 4.0).should == 1
+ 4.send(@method, 13.0).should == 4
+
+ (-200).send(@method, 256).should == 56
+ (-1000).send(@method, 512).should == 24
+
+ (-200).send(@method, -256).should == -200
+ (-1000).send(@method, -512).should == -488
+
+ (200).send(@method, -256).should == -56
+ (1000).send(@method, -512).should == -24
+
+ 1.send(@method, 2.0).should == 1.0
+ 200.send(@method, bignum_value).should == 200
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0" do
+ -> { 13.send(@method, 0) }.should raise_error(ZeroDivisionError)
+ -> { 0.send(@method, 0) }.should raise_error(ZeroDivisionError)
+ -> { -10.send(@method, 0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and a Float" do
+ -> { 0.send(@method, 0.0) }.should raise_error(ZeroDivisionError)
+ -> { 10.send(@method, 0.0) }.should raise_error(ZeroDivisionError)
+ -> { -10.send(@method, 0.0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10)
+ 13.send(@method, obj)
+ }.should raise_error(TypeError)
+ -> { 13.send(@method, "10") }.should raise_error(TypeError)
+ -> { 13.send(@method, :symbol) }.should raise_error(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value
+ end
+
+ it "returns the modulus obtained from dividing self by the given argument" do
+ @bignum.send(@method, 5).should == 1
+ @bignum.send(@method, -5).should == -4
+ @bignum.send(@method, -100).should == -84
+ @bignum.send(@method, 2.22).should be_close(1.5603603603605034, TOLERANCE)
+ @bignum.send(@method, bignum_value(10)).should == 18446744073709551616
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0" do
+ -> { @bignum.send(@method, 0) }.should raise_error(ZeroDivisionError)
+ -> { (-@bignum).send(@method, 0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and a Float" do
+ -> { @bignum.send(@method, 0.0) }.should raise_error(ZeroDivisionError)
+ -> { -@bignum.send(@method, 0.0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { @bignum.send(@method, mock('10')) }.should raise_error(TypeError)
+ -> { @bignum.send(@method, "10") }.should raise_error(TypeError)
+ -> { @bignum.send(@method, :symbol) }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/shared/next.rb b/spec/ruby/core/integer/shared/next.rb
new file mode 100644
index 0000000000..85b83d6965
--- /dev/null
+++ b/spec/ruby/core/integer/shared/next.rb
@@ -0,0 +1,25 @@
+describe :integer_next, shared: true do
+ it "returns the next larger positive Fixnum" do
+ 2.send(@method).should == 3
+ end
+
+ it "returns the next larger negative Fixnum" do
+ (-2).send(@method).should == -1
+ end
+
+ it "returns the next larger positive Bignum" do
+ bignum_value.send(@method).should == bignum_value(1)
+ end
+
+ it "returns the next larger negative Bignum" do
+ (-bignum_value(1)).send(@method).should == -bignum_value
+ end
+
+ it "overflows a Fixnum to a Bignum" do
+ fixnum_max.send(@method).should == fixnum_max + 1
+ end
+
+ it "underflows a Bignum to a Fixnum" do
+ (fixnum_min - 1).send(@method).should == fixnum_min
+ end
+end
diff --git a/spec/ruby/core/integer/shared/to_i.rb b/spec/ruby/core/integer/shared/to_i.rb
new file mode 100644
index 0000000000..7b974cd3a7
--- /dev/null
+++ b/spec/ruby/core/integer/shared/to_i.rb
@@ -0,0 +1,8 @@
+describe :integer_to_i, shared: true do
+ it "returns self" do
+ 10.send(@method).should eql(10)
+ (-15).send(@method).should eql(-15)
+ bignum_value.send(@method).should eql(bignum_value)
+ (-bignum_value).send(@method).should eql(-bignum_value)
+ end
+end
diff --git a/spec/ruby/core/integer/size_spec.rb b/spec/ruby/core/integer/size_spec.rb
new file mode 100644
index 0000000000..a134e82384
--- /dev/null
+++ b/spec/ruby/core/integer/size_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "Integer#size" do
+ platform_is wordsize: 32 do
+ it "returns the number of bytes in the machine representation of self" do
+ -1.size.should == 4
+ 0.size.should == 4
+ 4091.size.should == 4
+ end
+ end
+
+ platform_is wordsize: 64 do
+ it "returns the number of bytes in the machine representation of self" do
+ -1.size.should == 8
+ 0.size.should == 8
+ 4091.size.should == 8
+ end
+ end
+
+ context "bignum" do
+ it "returns the number of bytes required to hold the unsigned bignum data" do
+ # that is, n such that 256 * n <= val.abs < 256 * (n+1)
+ (256**7).size.should == 8
+ (256**8).size.should == 9
+ (256**9).size.should == 10
+ (256**10).size.should == 11
+ (256**10-1).size.should == 10
+ (256**11).size.should == 12
+ (256**12).size.should == 13
+ (256**20-1).size.should == 20
+ (256**40-1).size.should == 40
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/sqrt_spec.rb b/spec/ruby/core/integer/sqrt_spec.rb
new file mode 100644
index 0000000000..4149ca2cc9
--- /dev/null
+++ b/spec/ruby/core/integer/sqrt_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Integer.sqrt" do
+ it "returns an integer" do
+ Integer.sqrt(10).should be_kind_of(Integer)
+ end
+
+ it "returns the integer square root of the argument" do
+ Integer.sqrt(0).should == 0
+ Integer.sqrt(1).should == 1
+ Integer.sqrt(24).should == 4
+ Integer.sqrt(25).should == 5
+ Integer.sqrt(10**400).should == 10**200
+ end
+
+ it "raises a Math::DomainError if the argument is negative" do
+ -> { Integer.sqrt(-4) }.should raise_error(Math::DomainError)
+ end
+
+ it "accepts any argument that can be coerced to Integer" do
+ Integer.sqrt(10.0).should == 3
+ end
+
+ it "converts the argument with #to_int" do
+ Integer.sqrt(mock_int(10)).should == 3
+ end
+
+ it "raises a TypeError if the argument cannot be coerced to Integer" do
+ -> { Integer.sqrt("test") }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/succ_spec.rb b/spec/ruby/core/integer/succ_spec.rb
new file mode 100644
index 0000000000..9ae9a14fe7
--- /dev/null
+++ b/spec/ruby/core/integer/succ_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/next'
+
+describe "Integer#succ" do
+ it_behaves_like :integer_next, :succ
+end
diff --git a/spec/ruby/core/integer/times_spec.rb b/spec/ruby/core/integer/times_spec.rb
new file mode 100644
index 0000000000..356340592b
--- /dev/null
+++ b/spec/ruby/core/integer/times_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+
+describe "Integer#times" do
+ it "returns self" do
+ 5.times {}.should == 5
+ 9.times {}.should == 9
+ 9.times { |n| n - 2 }.should == 9
+ end
+
+ it "yields each value from 0 to self - 1" do
+ a = []
+ 9.times { |i| a << i }
+ -2.times { |i| a << i }
+ a.should == [0, 1, 2, 3, 4, 5, 6, 7, 8]
+ end
+
+ it "skips the current iteration when encountering 'next'" do
+ a = []
+ 3.times do |i|
+ next if i == 1
+ a << i
+ end
+ a.should == [0, 2]
+ end
+
+ it "skips all iterations when encountering 'break'" do
+ a = []
+ 5.times do |i|
+ break if i == 3
+ a << i
+ end
+ a.should == [0, 1, 2]
+ end
+
+ it "skips all iterations when encountering break with an argument and returns that argument" do
+ 9.times { break 2 }.should == 2
+ end
+
+ it "executes a nested while loop containing a break expression" do
+ a = [false]
+ b = 1.times do |i|
+ while true
+ a.shift or break
+ end
+ end
+ a.should == []
+ b.should == 1
+ end
+
+ it "executes a nested #times" do
+ a = 0
+ b = 3.times do |i|
+ 2.times { a += 1 }
+ end
+ a.should == 6
+ b.should == 3
+ end
+
+ it "returns an Enumerator" do
+ result = []
+
+ enum = 3.times
+ enum.each { |i| result << i }
+
+ result.should == [0, 1, 2]
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns self" do
+ 5.times.size.should == 5
+ 10.times.size.should == 10
+ 0.times.size.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/to_f_spec.rb b/spec/ruby/core/integer/to_f_spec.rb
new file mode 100644
index 0000000000..9f1df9ada9
--- /dev/null
+++ b/spec/ruby/core/integer/to_f_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "Integer#to_f" do
+ context "fixnum" do
+ it "returns self converted to a Float" do
+ 0.to_f.should == 0.0
+ -500.to_f.should == -500.0
+ 9_641_278.to_f.should == 9641278.0
+ end
+ end
+
+ context "bignum" do
+ it "returns self converted to a Float" do
+ bignum_value(0x4000_0aa0_0bb0_0000).to_f.should eql(23_058_441_774_644_068_352.0)
+ bignum_value(0x8000_0000_0000_0ccc).to_f.should eql(27_670_116_110_564_330_700.0)
+ (-bignum_value(99)).to_f.should eql(-18_446_744_073_709_551_715.0)
+ end
+
+ it "converts number close to Float::MAX without exceeding MAX or producing NaN" do
+ (10**308).to_f.should == 10.0 ** 308
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/to_i_spec.rb b/spec/ruby/core/integer/to_i_spec.rb
new file mode 100644
index 0000000000..1a8e477b3c
--- /dev/null
+++ b/spec/ruby/core/integer/to_i_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Integer#to_i" do
+ it_behaves_like :integer_to_i, :to_i
+end
diff --git a/spec/ruby/core/integer/to_int_spec.rb b/spec/ruby/core/integer/to_int_spec.rb
new file mode 100644
index 0000000000..4b7eccad3a
--- /dev/null
+++ b/spec/ruby/core/integer/to_int_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Integer#to_int" do
+ it_behaves_like :integer_to_i, :to_int
+end
diff --git a/spec/ruby/core/integer/to_r_spec.rb b/spec/ruby/core/integer/to_r_spec.rb
new file mode 100644
index 0000000000..7732bedca1
--- /dev/null
+++ b/spec/ruby/core/integer/to_r_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+describe "Integer#to_r" do
+ it "returns a Rational object" do
+ 309.to_r.should be_an_instance_of(Rational)
+ end
+
+ it "constructs a rational number with self as the numerator" do
+ 34.to_r.numerator.should == 34
+ end
+
+ it "constructs a rational number with 1 as the denominator" do
+ 298.to_r.denominator.should == 1
+ end
+
+ it "works even if self is a Bignum" do
+ bignum = 99999**999
+ bignum.should be_an_instance_of(Integer)
+ bignum.to_r.should == Rational(bignum, 1)
+ end
+
+ it "raises an ArgumentError if given any arguments" do
+ -> { 287.to_r(2) }.should raise_error(ArgumentError)
+ -> { 9102826.to_r(309, [], 71) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/integer/to_s_spec.rb b/spec/ruby/core/integer/to_s_spec.rb
new file mode 100644
index 0000000000..ca08dad95c
--- /dev/null
+++ b/spec/ruby/core/integer/to_s_spec.rb
@@ -0,0 +1,95 @@
+require_relative '../../spec_helper'
+
+describe "Integer#to_s" do
+ context "fixnum" do
+ context "when given a base" do
+ it "returns self converted to a String in the given base" do
+ 12345.to_s(2).should == "11000000111001"
+ 12345.to_s(8).should == "30071"
+ 12345.to_s(10).should == "12345"
+ 12345.to_s(16).should == "3039"
+ 95.to_s(16).should == "5f"
+ 12345.to_s(36).should == "9ix"
+ end
+
+ it "raises an ArgumentError if the base is less than 2 or higher than 36" do
+ -> { 123.to_s(-1) }.should raise_error(ArgumentError)
+ -> { 123.to_s(0) }.should raise_error(ArgumentError)
+ -> { 123.to_s(1) }.should raise_error(ArgumentError)
+ -> { 123.to_s(37) }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "when no base given" do
+ it "returns self converted to a String using base 10" do
+ 255.to_s.should == '255'
+ 3.to_s.should == '3'
+ 0.to_s.should == '0'
+ -9002.to_s.should == '-9002'
+ end
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ 1.to_s.encoding.should equal(Encoding::US_ASCII)
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do
+ Encoding.default_internal = Encoding::IBM437
+ 1.to_s.encoding.should equal(Encoding::US_ASCII)
+ end
+ end
+
+ context "bignum" do
+ describe "when given a base" do
+ it "returns self converted to a String using the given base" do
+ a = 2**64
+ a.to_s(2).should == "10000000000000000000000000000000000000000000000000000000000000000"
+ a.to_s(8).should == "2000000000000000000000"
+ a.to_s(16).should == "10000000000000000"
+ a.to_s(32).should == "g000000000000"
+ end
+
+ it "raises an ArgumentError if the base is less than 2 or higher than 36" do
+ -> { 123.to_s(-1) }.should raise_error(ArgumentError)
+ -> { 123.to_s(0) }.should raise_error(ArgumentError)
+ -> { 123.to_s(1) }.should raise_error(ArgumentError)
+ -> { 123.to_s(37) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "when given no base" do
+ it "returns self converted to a String using base 10" do
+ bignum_value(9).to_s.should == "18446744073709551625"
+ bignum_value.to_s.should == "18446744073709551616"
+ (-bignum_value(675)).to_s.should == "-18446744073709552291"
+ end
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ bignum_value.to_s.encoding.should equal(Encoding::US_ASCII)
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do
+ Encoding.default_internal = Encoding::IBM437
+ bignum_value.to_s.encoding.should equal(Encoding::US_ASCII)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/truncate_spec.rb b/spec/ruby/core/integer/truncate_spec.rb
new file mode 100644
index 0000000000..db16e74be4
--- /dev/null
+++ b/spec/ruby/core/integer/truncate_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+require_relative 'shared/integer_rounding'
+
+describe "Integer#truncate" do
+ it_behaves_like :integer_to_i, :truncate
+ it_behaves_like :integer_rounding_positive_precision, :truncate
+
+ context "precision argument specified as part of the truncate method is negative" do
+ it "returns an integer with at least precision.abs trailing zeros" do
+ 1832.truncate(-1).should eql(1830)
+ 1832.truncate(-2).should eql(1800)
+ 1832.truncate(-3).should eql(1000)
+ -1832.truncate(-1).should eql(-1830)
+ -1832.truncate(-2).should eql(-1800)
+ -1832.truncate(-3).should eql(-1000)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/try_convert_spec.rb b/spec/ruby/core/integer/try_convert_spec.rb
new file mode 100644
index 0000000000..45c66eec79
--- /dev/null
+++ b/spec/ruby/core/integer/try_convert_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1" do
+ describe "Integer.try_convert" do
+ it "returns the argument if it's an Integer" do
+ x = 42
+ Integer.try_convert(x).should equal(x)
+ end
+
+ it "returns nil when the argument does not respond to #to_int" do
+ Integer.try_convert(Object.new).should be_nil
+ end
+
+ it "sends #to_int to the argument and returns the result if it's nil" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(nil)
+ Integer.try_convert(obj).should be_nil
+ end
+
+ it "sends #to_int to the argument and returns the result if it's an Integer" do
+ x = 234
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(x)
+ Integer.try_convert(obj).should equal(x)
+ end
+
+ it "sends #to_int to the argument and raises TypeError if it's not a kind of Integer" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(Object.new)
+ -> { Integer.try_convert obj }.should raise_error(TypeError)
+ end
+
+ it "does not rescue exceptions raised by #to_int" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_raise(RuntimeError)
+ -> { Integer.try_convert obj }.should raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/uminus_spec.rb b/spec/ruby/core/integer/uminus_spec.rb
new file mode 100644
index 0000000000..7a9cfe89bf
--- /dev/null
+++ b/spec/ruby/core/integer/uminus_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+
+describe "Integer#-@" do
+ context "fixnum" do
+ it "returns self as a negative value" do
+ 2.send(:-@).should == -2
+ -2.should == -2
+ -268435455.should == -268435455
+ (--5).should == 5
+ -8.send(:-@).should == 8
+ end
+
+ it "negates self at Fixnum/Bignum boundaries" do
+ (-fixnum_max).should == (0 - fixnum_max)
+ (-fixnum_max).should < 0
+ (-fixnum_min).should == (0 - fixnum_min)
+ (-fixnum_min).should > 0
+ end
+ end
+
+ context "bignum" do
+ it "returns self as a negative value" do
+ bignum_value.send(:-@).should == -18446744073709551616
+ (-bignum_value).send(:-@).should == 18446744073709551616
+
+ bignum_value(921).send(:-@).should == -18446744073709552537
+ (-bignum_value(921).send(:-@)).should == 18446744073709552537
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/upto_spec.rb b/spec/ruby/core/integer/upto_spec.rb
new file mode 100644
index 0000000000..dd6995e94b
--- /dev/null
+++ b/spec/ruby/core/integer/upto_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+
+describe "Integer#upto [stop] when self and stop are Integers" do
+ it "does not yield when stop is less than self" do
+ result = []
+ 5.upto(4) { |x| result << x }
+ result.should == []
+ end
+
+ it "yields once when stop equals self" do
+ result = []
+ 5.upto(5) { |x| result << x }
+ result.should == [5]
+ end
+
+ it "yields while increasing self until it is less than stop" do
+ result = []
+ 2.upto(5) { |x| result << x }
+ result.should == [2, 3, 4, 5]
+ end
+
+ it "yields while increasing self until it is greater than floor of a Float endpoint" do
+ result = []
+ 9.upto(13.3) {|i| result << i}
+ -5.upto(-1.3) {|i| result << i}
+ result.should == [9,10,11,12,13,-5,-4,-3,-2]
+ end
+
+ it "raises an ArgumentError for non-numeric endpoints" do
+ -> { 1.upto("A") {|x| p x} }.should raise_error(ArgumentError)
+ -> { 1.upto(nil) {|x| p x} }.should raise_error(ArgumentError)
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ result = []
+
+ enum = 2.upto(5)
+ enum.each { |i| result << i }
+
+ result.should == [2, 3, 4, 5]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "raises an ArgumentError for non-numeric endpoints" do
+ enum = 1.upto("A")
+ -> { enum.size }.should raise_error(ArgumentError)
+ enum = 1.upto(nil)
+ -> { enum.size }.should raise_error(ArgumentError)
+ end
+
+ it "returns stop - self + 1" do
+ 5.upto(10).size.should == 6
+ 1.upto(10).size.should == 10
+ 0.upto(10).size.should == 11
+ 0.upto(0).size.should == 1
+ -5.upto(-3).size.should == 3
+ end
+
+ it "returns 0 when stop < self" do
+ 5.upto(4).size.should == 0
+ 0.upto(-5).size.should == 0
+ -3.upto(-5).size.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/zero_spec.rb b/spec/ruby/core/integer/zero_spec.rb
new file mode 100644
index 0000000000..2dac50c406
--- /dev/null
+++ b/spec/ruby/core/integer/zero_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Integer#zero?" do
+ it "returns true if self is 0" do
+ 0.should.zero?
+ 1.should_not.zero?
+ -1.should_not.zero?
+ end
+
+ ruby_version_is "3.0" do
+ it "Integer#zero? overrides Numeric#zero?" do
+ 42.method(:zero?).owner.should == Integer
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "Integer#zero? uses Numeric#zero?" do
+ 42.method(:zero?).owner.should == Numeric
+ end
+ end
+end
diff --git a/spec/ruby/core/io/advise_spec.rb b/spec/ruby/core/io/advise_spec.rb
new file mode 100644
index 0000000000..651fc52378
--- /dev/null
+++ b/spec/ruby/core/io/advise_spec.rb
@@ -0,0 +1,86 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#advise" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "raises a TypeError if advise is not a Symbol" do
+ -> {
+ @io.advise("normal")
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if offset cannot be coerced to an Integer" do
+ -> {
+ @io.advise(:normal, "wat")
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if len cannot be coerced to an Integer" do
+ -> {
+ @io.advise(:normal, 0, "wat")
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError if offset is too big" do
+ -> {
+ @io.advise(:normal, 10 ** 32)
+ }.should raise_error(RangeError)
+ end
+
+ it "raises a RangeError if len is too big" do
+ -> {
+ @io.advise(:normal, 0, 10 ** 32)
+ }.should raise_error(RangeError)
+ end
+
+ it "raises a NotImplementedError if advise is not recognized" do
+ ->{
+ @io.advise(:foo)
+ }.should raise_error(NotImplementedError)
+ end
+
+ it "supports the normal advice type" do
+ @io.advise(:normal).should be_nil
+ end
+
+ it "supports the sequential advice type" do
+ @io.advise(:sequential).should be_nil
+ end
+
+ it "supports the random advice type" do
+ @io.advise(:random).should be_nil
+ end
+
+ it "supports the dontneed advice type" do
+ @io.advise(:dontneed).should be_nil
+ end
+
+ it "supports the noreuse advice type" do
+ @io.advise(:noreuse).should be_nil
+ end
+
+ platform_is_not :linux do
+ it "supports the willneed advice type" do
+ @io.advise(:willneed).should be_nil
+ end
+ end
+
+ guard -> { platform_is :linux and kernel_version_is '3.6' } do # [ruby-core:65355] tmpfs is not supported
+ it "supports the willneed advice type" do
+ @io.advise(:willneed).should be_nil
+ end
+ end
+
+ it "raises an IOError if the stream is closed" do
+ @io.close
+ -> { @io.advise(:normal) }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/binmode_spec.rb b/spec/ruby/core/io/binmode_spec.rb
new file mode 100644
index 0000000000..342cac2a9b
--- /dev/null
+++ b/spec/ruby/core/io/binmode_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#binmode" do
+ before :each do
+ @name = tmp("io_binmode.txt")
+ end
+
+ after :each do
+ @io.close if @io and !@io.closed?
+ rm_r @name
+ end
+
+ it "returns self" do
+ @io = new_io(@name)
+ @io.binmode.should equal(@io)
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.binmode }.should raise_error(IOError)
+ end
+
+ it "sets external encoding to binary" do
+ @io = new_io(@name, "w:utf-8")
+ @io.binmode
+ @io.external_encoding.should == Encoding::BINARY
+ end
+
+ it "sets internal encoding to nil" do
+ @io = new_io(@name, "w:utf-8:ISO-8859-1")
+ @io.binmode
+ @io.internal_encoding.should == nil
+ end
+end
+
+describe "IO#binmode?" do
+ before :each do
+ @filename = tmp("IO_binmode_file")
+ @file = File.open(@filename, "w")
+ @duped = nil
+ end
+
+ after :each do
+ @duped.close if @duped
+ @file.close
+ rm_r @filename
+ end
+
+ it "is true after a call to IO#binmode" do
+ @file.binmode?.should be_false
+ @file.binmode
+ @file.binmode?.should be_true
+ end
+
+ it "propagates to dup'ed IO objects" do
+ @file.binmode
+ @duped = @file.dup
+ @duped.binmode?.should == @file.binmode?
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.binmode? }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/binread_spec.rb b/spec/ruby/core/io/binread_spec.rb
new file mode 100644
index 0000000000..a3f752d8f9
--- /dev/null
+++ b/spec/ruby/core/io/binread_spec.rb
@@ -0,0 +1,47 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO.binread" do
+ before :each do
+ @internal = Encoding.default_internal
+
+ @fname = tmp('io_read.txt')
+ @contents = "1234567890"
+ touch(@fname) { |f| f.write @contents }
+ end
+
+ after :each do
+ rm_r @fname
+ Encoding.default_internal = @internal
+ end
+
+ it "reads the contents of a file" do
+ IO.binread(@fname).should == @contents
+ end
+
+ it "reads the contents of a file up to a certain size when specified" do
+ IO.binread(@fname, 5).should == @contents.slice(0..4)
+ end
+
+ it "reads the contents of a file from an offset of a specific size when specified" do
+ IO.binread(@fname, 5, 3).should == @contents.slice(3, 5)
+ end
+
+ it "returns a String in BINARY encoding" do
+ IO.binread(@fname).encoding.should == Encoding::BINARY
+ end
+
+ it "returns a String in BINARY encoding regardless of Encoding.default_internal" do
+ Encoding.default_internal = Encoding::EUC_JP
+ IO.binread(@fname).encoding.should == Encoding::BINARY
+ end
+
+ it "raises an ArgumentError when not passed a valid length" do
+ -> { IO.binread @fname, -1 }.should raise_error(ArgumentError)
+ end
+
+ it "raises an Errno::EINVAL when not passed a valid offset" do
+ -> { IO.binread @fname, 0, -1 }.should raise_error(Errno::EINVAL)
+ end
+end
diff --git a/spec/ruby/core/io/binwrite_spec.rb b/spec/ruby/core/io/binwrite_spec.rb
new file mode 100644
index 0000000000..8ebc86a52e
--- /dev/null
+++ b/spec/ruby/core/io/binwrite_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/binwrite'
+
+describe "IO.binwrite" do
+ it_behaves_like :io_binwrite, :binwrite
+end
diff --git a/spec/ruby/core/io/bytes_spec.rb b/spec/ruby/core/io/bytes_spec.rb
new file mode 100644
index 0000000000..6e328983f2
--- /dev/null
+++ b/spec/ruby/core/io/bytes_spec.rb
@@ -0,0 +1,47 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is ''...'3.0' do
+ describe "IO#bytes" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ @io.close unless @io.closed?
+ end
+
+ it "returns an enumerator of the next bytes from the stream" do
+ enum = @io.bytes
+ enum.should be_an_instance_of(Enumerator)
+ @io.readline.should == "Voici la ligne une.\n"
+ enum.first(5).should == [81, 117, 105, 32, 195]
+ end
+
+ it "yields each byte" do
+ count = 0
+ ScratchPad.record []
+ @io.each_byte do |byte|
+ ScratchPad << byte
+ break if 4 < count += 1
+ end
+
+ ScratchPad.recorded.should == [86, 111, 105, 99, 105]
+ end
+
+ it "raises an IOError on closed stream" do
+ enum = IOSpecs.closed_io.bytes
+ -> { enum.first }.should raise_error(IOError)
+ end
+
+ it "raises an IOError on an enumerator for a stream that has been closed" do
+ enum = @io.bytes
+ enum.first.should == 86
+ @io.close
+ -> { enum.first }.should raise_error(IOError)
+ end
+ end
+end
diff --git a/spec/ruby/core/io/chars_spec.rb b/spec/ruby/core/io/chars_spec.rb
new file mode 100644
index 0000000000..15db595aed
--- /dev/null
+++ b/spec/ruby/core/io/chars_spec.rb
@@ -0,0 +1,30 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/chars'
+
+ruby_version_is ''...'3.0' do
+ describe "IO#chars" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :io_chars, :chars
+ end
+
+ describe "IO#chars" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :io_chars_empty, :chars
+ end
+end
diff --git a/spec/ruby/core/io/close_on_exec_spec.rb b/spec/ruby/core/io/close_on_exec_spec.rb
new file mode 100644
index 0000000000..4e89e08d61
--- /dev/null
+++ b/spec/ruby/core/io/close_on_exec_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+
+describe "IO#close_on_exec=" do
+ before :each do
+ @name = tmp('io_close_on_exec.txt')
+ @io = new_io @name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ guard -> { platform_is_not :windows } do
+ it "sets the close-on-exec flag if true" do
+ @io.close_on_exec = true
+ @io.should.close_on_exec?
+ end
+
+ it "sets the close-on-exec flag if non-false" do
+ @io.close_on_exec = :true
+ @io.should.close_on_exec?
+ end
+
+ it "unsets the close-on-exec flag if false" do
+ @io.close_on_exec = true
+ @io.close_on_exec = false
+ @io.should_not.close_on_exec?
+ end
+
+ it "unsets the close-on-exec flag if nil" do
+ @io.close_on_exec = true
+ @io.close_on_exec = nil
+ @io.should_not.close_on_exec?
+ end
+
+ it "ensures the IO's file descriptor is closed in exec'ed processes" do
+ require 'fcntl'
+ @io.close_on_exec = true
+ (@io.fcntl(Fcntl::F_GETFD) & Fcntl::FD_CLOEXEC).should == Fcntl::FD_CLOEXEC
+ end
+
+ it "raises IOError if called on a closed IO" do
+ @io.close
+ -> { @io.close_on_exec = true }.should raise_error(IOError)
+ end
+ end
+end
+
+describe "IO#close_on_exec?" do
+ before :each do
+ @name = tmp('io_is_close_on_exec.txt')
+ @io = new_io @name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ guard -> { platform_is_not :windows } do
+ it "returns true by default" do
+ @io.should.close_on_exec?
+ end
+
+ it "returns true if set" do
+ @io.close_on_exec = true
+ @io.should.close_on_exec?
+ end
+
+ it "raises IOError if called on a closed IO" do
+ @io.close
+ -> { @io.close_on_exec? }.should raise_error(IOError)
+ end
+ end
+end
diff --git a/spec/ruby/core/io/close_read_spec.rb b/spec/ruby/core/io/close_read_spec.rb
new file mode 100644
index 0000000000..26454bfddd
--- /dev/null
+++ b/spec/ruby/core/io/close_read_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#close_read" do
+
+ before :each do
+ @io = IO.popen 'cat', "r+"
+ @path = tmp('io.close.txt')
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @path
+ end
+
+ it "closes the read end of a duplex I/O stream" do
+ @io.close_read
+
+ -> { @io.read }.should raise_error(IOError)
+ end
+
+ it "does nothing on subsequent invocations" do
+ @io.close_read
+
+ @io.close_read.should be_nil
+ end
+
+ it "allows subsequent invocation of close" do
+ @io.close_read
+
+ -> { @io.close }.should_not raise_error
+ end
+
+ it "raises an IOError if the stream is writable and not duplexed" do
+ io = File.open @path, 'w'
+
+ begin
+ -> { io.close_read }.should raise_error(IOError)
+ ensure
+ io.close unless io.closed?
+ end
+ end
+
+ it "closes the stream if it is neither writable nor duplexed" do
+ io_close_path = @path
+ touch io_close_path
+
+ io = File.open io_close_path
+
+ io.close_read
+
+ io.should.closed?
+ end
+
+ it "does nothing on closed stream" do
+ @io.close
+
+ @io.close_read.should be_nil
+ end
+end
diff --git a/spec/ruby/core/io/close_spec.rb b/spec/ruby/core/io/close_spec.rb
new file mode 100644
index 0000000000..3a44cc8b17
--- /dev/null
+++ b/spec/ruby/core/io/close_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#close" do
+ before :each do
+ @name = tmp('io_close.txt')
+ @io = new_io @name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "closes the stream" do
+ @io.close
+ @io.should.closed?
+ end
+
+ it "returns nil" do
+ @io.close.should == nil
+ end
+
+ it "raises an IOError reading from a closed IO" do
+ @io.close
+ -> { @io.read }.should raise_error(IOError)
+ end
+
+ it "raises an IOError writing to a closed IO" do
+ @io.close
+ -> { @io.write "data" }.should raise_error(IOError)
+ end
+
+ it 'does not close the stream if autoclose is false' do
+ other_io = IO.new(@io.fileno)
+ other_io.autoclose = false
+ other_io.close
+ -> { @io.write "data" }.should_not raise_error(IOError)
+ end
+
+ it "does nothing if already closed" do
+ @io.close
+
+ @io.close.should be_nil
+ end
+
+ it "does not call the #flush method but flushes the stream internally" do
+ @io.should_not_receive(:flush)
+ @io.close
+ @io.should.closed?
+ end
+
+ it 'raises an IOError with a clear message' do
+ matching_exception = nil
+
+ -> do
+ IOSpecs::THREAD_CLOSE_RETRIES.times do
+ read_io, write_io = IO.pipe
+ going_to_read = false
+
+ thread = Thread.new do
+ begin
+ going_to_read = true
+ read_io.read
+ rescue IOError => ioe
+ if ioe.message == IOSpecs::THREAD_CLOSE_ERROR_MESSAGE
+ matching_exception = ioe
+ end
+ # try again
+ end
+ end
+
+ # best attempt to ensure the thread is actually blocked on read
+ Thread.pass until going_to_read && thread.stop?
+ sleep(0.001)
+
+ read_io.close
+ thread.join
+ write_io.close
+
+ matching_exception&.tap {|ex| raise ex}
+ end
+ end.should raise_error(IOError, IOSpecs::THREAD_CLOSE_ERROR_MESSAGE)
+ end
+end
+
+describe "IO#close on an IO.popen stream" do
+
+ it "clears #pid" do
+ io = IO.popen ruby_cmd('r = loop{puts "y"; 0} rescue 1; exit r'), 'r'
+
+ io.pid.should_not == 0
+
+ io.close
+
+ -> { io.pid }.should raise_error(IOError)
+ end
+
+ it "sets $?" do
+ io = IO.popen ruby_cmd('exit 0'), 'r'
+ io.close
+
+ $?.exitstatus.should == 0
+
+ io = IO.popen ruby_cmd('exit 1'), 'r'
+ io.close
+
+ $?.exitstatus.should == 1
+ end
+
+ it "waits for the child to exit" do
+ io = IO.popen ruby_cmd('r = loop{puts "y"; 0} rescue 1; exit r'), 'r'
+ io.close
+
+ $?.exitstatus.should_not == 0
+ end
+
+end
diff --git a/spec/ruby/core/io/close_write_spec.rb b/spec/ruby/core/io/close_write_spec.rb
new file mode 100644
index 0000000000..14835e4e2c
--- /dev/null
+++ b/spec/ruby/core/io/close_write_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#close_write" do
+ before :each do
+ @io = IO.popen 'cat', 'r+'
+ @path = tmp('io.close.txt')
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @path
+ end
+
+ it "closes the write end of a duplex I/O stream" do
+ @io.close_write
+
+ -> { @io.write "attempt to write" }.should raise_error(IOError)
+ end
+
+ it "does nothing on subsequent invocations" do
+ @io.close_write
+
+ @io.close_write.should be_nil
+ end
+
+ it "allows subsequent invocation of close" do
+ @io.close_write
+
+ -> { @io.close }.should_not raise_error
+ end
+
+ it "raises an IOError if the stream is readable and not duplexed" do
+ io = File.open @path, 'w+'
+
+ begin
+ -> { io.close_write }.should raise_error(IOError)
+ ensure
+ io.close unless io.closed?
+ end
+ end
+
+ it "closes the stream if it is neither readable nor duplexed" do
+ io = File.open @path, 'w'
+
+ io.close_write
+
+ io.should.closed?
+ end
+
+ it "flushes and closes the write stream" do
+ @io.puts '12345'
+
+ @io.close_write
+
+ @io.read.should == "12345\n"
+ end
+
+ it "does nothing on closed stream" do
+ @io.close
+
+ @io.close_write.should be_nil
+ end
+end
diff --git a/spec/ruby/core/io/closed_spec.rb b/spec/ruby/core/io/closed_spec.rb
new file mode 100644
index 0000000000..7316546a0d
--- /dev/null
+++ b/spec/ruby/core/io/closed_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#closed?" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close
+ end
+
+ it "returns true on closed stream" do
+ IOSpecs.closed_io.closed?.should be_true
+ end
+
+ it "returns false on open stream" do
+ @io.closed?.should be_false
+ end
+end
diff --git a/spec/ruby/core/io/codepoints_spec.rb b/spec/ruby/core/io/codepoints_spec.rb
new file mode 100644
index 0000000000..04c115dd3f
--- /dev/null
+++ b/spec/ruby/core/io/codepoints_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/codepoints'
+
+ruby_version_is ''...'3.0' do
+
+ # See redmine #1667
+ describe "IO#codepoints" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :io_codepoints, :codepoints
+ end
+
+ describe "IO#codepoints" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ @io.close unless @io.closed?
+ end
+
+ it "calls the given block" do
+ r = []
+ @io.codepoints { |c| r << c }
+ r[24].should == 232
+ r.last.should == 10
+ end
+ end
+end
diff --git a/spec/ruby/core/io/constants_spec.rb b/spec/ruby/core/io/constants_spec.rb
new file mode 100644
index 0000000000..f9dccd08b9
--- /dev/null
+++ b/spec/ruby/core/io/constants_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "IO::SEEK_SET" do
+ it "is defined" do
+ IO.const_defined?(:SEEK_SET).should == true
+ end
+end
+
+describe "IO::SEEK_CUR" do
+ it "is defined" do
+ IO.const_defined?(:SEEK_CUR).should == true
+ end
+end
+
+describe "IO::SEEK_END" do
+ it "is defined" do
+ IO.const_defined?(:SEEK_END).should == true
+ end
+end
diff --git a/spec/ruby/core/io/copy_stream_spec.rb b/spec/ruby/core/io/copy_stream_spec.rb
new file mode 100644
index 0000000000..df9c5c7390
--- /dev/null
+++ b/spec/ruby/core/io/copy_stream_spec.rb
@@ -0,0 +1,322 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :io_copy_stream_to_file, shared: true do
+ it "copies the entire IO contents to the file" do
+ IO.copy_stream(@object.from, @to_name)
+ File.read(@to_name).should == @content
+ IO.copy_stream(@from_bigfile, @to_name)
+ File.read(@to_name).should == @content_bigfile
+ end
+
+ it "returns the number of bytes copied" do
+ IO.copy_stream(@object.from, @to_name).should == @size
+ IO.copy_stream(@from_bigfile, @to_name).should == @size_bigfile
+ end
+
+ it "copies only length bytes when specified" do
+ IO.copy_stream(@object.from, @to_name, 8).should == 8
+ File.read(@to_name).should == "Line one"
+ end
+
+ it "calls #to_path to convert on object to a file name" do
+ obj = mock("io_copy_stream_to")
+ obj.should_receive(:to_path).and_return(@to_name)
+
+ IO.copy_stream(@object.from, obj)
+ File.read(@to_name).should == @content
+ end
+
+ it "raises a TypeError if #to_path does not return a String" do
+ obj = mock("io_copy_stream_to")
+ obj.should_receive(:to_path).and_return(1)
+
+ -> { IO.copy_stream(@object.from, obj) }.should raise_error(TypeError)
+ end
+end
+
+describe :io_copy_stream_to_file_with_offset, shared: true do
+ platform_is_not :windows do
+ it "copies only length bytes from the offset" do
+ IO.copy_stream(@object.from, @to_name, 8, 4).should == 8
+ File.read(@to_name).should == " one\n\nLi"
+ end
+ end
+end
+
+describe :io_copy_stream_to_io, shared: true do
+ it "copies the entire IO contents to the IO" do
+ IO.copy_stream(@object.from, @to_io)
+ File.read(@to_name).should == @content
+ IO.copy_stream(@from_bigfile, @to_io)
+ File.read(@to_name).should == (@content + @content_bigfile)
+ end
+
+ it "returns the number of bytes copied" do
+ IO.copy_stream(@object.from, @to_io).should == @size
+ IO.copy_stream(@from_bigfile, @to_io).should == @size_bigfile
+ end
+
+ it "starts writing at the destination IO's current position" do
+ @to_io.write("prelude ")
+ IO.copy_stream(@object.from, @to_io)
+ File.read(@to_name).should == ("prelude " + @content)
+ end
+
+ it "leaves the destination IO position at the last write" do
+ IO.copy_stream(@object.from, @to_io)
+ @to_io.pos.should == @size
+ end
+
+ it "raises an IOError if the destination IO is not open for writing" do
+ @to_io.close
+ @to_io = new_io @to_name, "r"
+ -> { IO.copy_stream @object.from, @to_io }.should raise_error(IOError)
+ end
+
+ it "does not close the destination IO" do
+ IO.copy_stream(@object.from, @to_io)
+ @to_io.closed?.should be_false
+ end
+
+ it "copies only length bytes when specified" do
+ IO.copy_stream(@object.from, @to_io, 8).should == 8
+ File.read(@to_name).should == "Line one"
+ end
+end
+
+describe :io_copy_stream_to_io_with_offset, shared: true do
+ platform_is_not :windows do
+ it "copies only length bytes from the offset" do
+ IO.copy_stream(@object.from, @to_io, 8, 4).should == 8
+ File.read(@to_name).should == " one\n\nLi"
+ end
+ end
+end
+
+describe "IO.copy_stream" do
+ before :each do
+ @from_name = fixture __FILE__, "copy_stream.txt"
+ @to_name = tmp("io_copy_stream_io_name")
+
+ @content = IO.read(@from_name)
+ @size = @content.size
+
+ @from_bigfile = tmp("io_copy_stream_bigfile")
+ @content_bigfile = "A" * 17_000
+ touch(@from_bigfile){|f| f.print @content_bigfile }
+ @size_bigfile = @content_bigfile.size
+ end
+
+ after :each do
+ rm_r @to_name, @from_bigfile
+ end
+
+ describe "from an IO" do
+ before :each do
+ @from_io = new_io @from_name, "rb"
+ IOSpecs::CopyStream.from = @from_io
+ end
+
+ after :each do
+ @from_io.close
+ end
+
+ it "raises an IOError if the source IO is not open for reading" do
+ @from_io.close
+ @from_io = new_io @from_bigfile, "a"
+ -> { IO.copy_stream @from_io, @to_name }.should raise_error(IOError)
+ end
+
+ it "does not close the source IO" do
+ IO.copy_stream(@from_io, @to_name)
+ @from_io.closed?.should be_false
+ end
+
+ platform_is_not :windows do
+ it "does not change the IO offset when an offset is specified" do
+ @from_io.pos = 10
+ IO.copy_stream(@from_io, @to_name, 8, 4)
+ @from_io.pos.should == 10
+ end
+ end
+
+ it "does change the IO offset when an offset is not specified" do
+ @from_io.pos = 10
+ IO.copy_stream(@from_io, @to_name)
+ @from_io.pos.should == 42
+ end
+
+ describe "to a file name" do
+ it_behaves_like :io_copy_stream_to_file, nil, IOSpecs::CopyStream
+ it_behaves_like :io_copy_stream_to_file_with_offset, nil, IOSpecs::CopyStream
+ end
+
+ describe "to an IO" do
+ before :each do
+ @to_io = new_io @to_name, "wb"
+ end
+
+ after :each do
+ @to_io.close
+ end
+
+ it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream
+ it_behaves_like :io_copy_stream_to_io_with_offset, nil, IOSpecs::CopyStream
+ end
+ end
+
+ describe "from a file name" do
+ before :each do
+ IOSpecs::CopyStream.from = @from_name
+ end
+
+ it "calls #to_path to convert on object to a file name" do
+ obj = mock("io_copy_stream_from")
+ obj.should_receive(:to_path).and_return(@from_name)
+
+ IO.copy_stream(obj, @to_name)
+ File.read(@to_name).should == @content
+ end
+
+ it "raises a TypeError if #to_path does not return a String" do
+ obj = mock("io_copy_stream_from")
+ obj.should_receive(:to_path).and_return(1)
+
+ -> { IO.copy_stream(obj, @to_name) }.should raise_error(TypeError)
+ end
+
+ describe "to a file name" do
+ it_behaves_like :io_copy_stream_to_file, nil, IOSpecs::CopyStream
+ it_behaves_like :io_copy_stream_to_file_with_offset, nil, IOSpecs::CopyStream
+ end
+
+ describe "to an IO" do
+ before :each do
+ @to_io = new_io @to_name, "wb"
+ end
+
+ after :each do
+ @to_io.close
+ end
+
+ it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream
+ it_behaves_like :io_copy_stream_to_io_with_offset, nil, IOSpecs::CopyStream
+ end
+ end
+
+ describe "from a pipe IO" do
+ before :each do
+ @from_io = IOSpecs.pipe_fixture(@content)
+ IOSpecs::CopyStream.from = @from_io
+ end
+
+ after :each do
+ @from_io.close
+ end
+
+ it "does not close the source IO" do
+ IO.copy_stream(@from_io, @to_name)
+ @from_io.closed?.should be_false
+ end
+
+ platform_is_not :windows do
+ it "raises an error when an offset is specified" do
+ -> { IO.copy_stream(@from_io, @to_name, 8, 4) }.should raise_error(Errno::ESPIPE)
+ end
+ end
+
+ describe "to a file name" do
+ it_behaves_like :io_copy_stream_to_file, nil, IOSpecs::CopyStream
+ end
+
+ describe "to an IO" do
+ before :each do
+ @to_io = new_io @to_name, "wb"
+ end
+
+ after :each do
+ @to_io.close
+ end
+
+ it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream
+ end
+ end
+
+ describe "with non-IO Objects" do
+ before do
+ @io = new_io @from_name, "rb"
+ end
+
+ after do
+ @io.close unless @io.closed?
+ end
+
+ it "calls #readpartial on the source Object if defined" do
+ from = IOSpecs::CopyStreamReadPartial.new @io
+
+ IO.copy_stream(from, @to_name)
+ File.read(@to_name).should == @content
+ end
+
+ it "calls #read on the source Object" do
+ from = IOSpecs::CopyStreamRead.new @io
+
+ IO.copy_stream(from, @to_name)
+ File.read(@to_name).should == @content
+ end
+
+ it "calls #write on the destination Object" do
+ to = mock("io_copy_stream_to_object")
+ to.should_receive(:write).with(@content).and_return(@content.size)
+
+ IO.copy_stream(@from_name, to)
+ end
+
+ it "does not call #pos on the source if no offset is given" do
+ @io.should_not_receive(:pos)
+ IO.copy_stream(@io, @to_name)
+ end
+
+ end
+
+
+ describe "with a destination that does partial reads" do
+ before do
+ @from_out, @from_in = IO.pipe
+ @to_out, @to_in = IO.pipe
+ end
+
+ after do
+ [@from_out, @from_in, @to_out, @to_in].each {|io| io.close rescue nil}
+ end
+
+ it "calls #write repeatedly on the destination Object" do
+ @from_in.write "1234"
+ @from_in.close
+
+ th = Thread.new do
+ IO.copy_stream(@from_out, @to_in)
+ end
+
+ copied = ""
+ 4.times do
+ copied += @to_out.read(1)
+ end
+
+ th.join
+
+ copied.should == "1234"
+ end
+
+ end
+end
+
+describe "IO.copy_stream" do
+ it "does not use buffering when writing to STDOUT" do
+ IO.popen([*ruby_exe, fixture(__FILE__ , "copy_in_out.rb")], "r+") do |io|
+ io.write("bar")
+ io.read(3).should == "bar"
+ end
+ end
+end
diff --git a/spec/ruby/core/io/dup_spec.rb b/spec/ruby/core/io/dup_spec.rb
new file mode 100644
index 0000000000..68d538377f
--- /dev/null
+++ b/spec/ruby/core/io/dup_spec.rb
@@ -0,0 +1,106 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#dup" do
+ before :each do
+ @file = tmp("spec_io_dup")
+ @f = File.open @file, 'w+'
+ @i = @f.dup
+
+ @f.sync = true
+ @i.sync = true
+ end
+
+ after :each do
+ @i.close if @i && !@i.closed?
+ @f.close if @f && !@f.closed?
+ rm_r @file
+ end
+
+ it "returns a new IO instance" do
+ @i.class.should == @f.class
+ end
+
+ it "sets a new descriptor on the returned object" do
+ @i.fileno.should_not == @f.fileno
+ end
+
+quarantine! do # This does not appear to be consistent across platforms
+ it "shares the original stream between the two IOs" do
+ start = @f.pos
+ @i.pos.should == start
+
+ s = "Hello, wo.. wait, where am I?\n"
+ s2 = "<evil voice> Muhahahaa!"
+
+ @f.write s
+ @i.pos.should == @f.pos
+
+ @i.rewind
+ @i.gets.should == s
+
+ @i.rewind
+ @i.write s2
+
+ @f.rewind
+ @f.gets.should == "#{s2}\n"
+ end
+end
+
+ it "allows closing the new IO without affecting the original" do
+ @i.close
+ -> { @f.gets }.should_not raise_error(Exception)
+
+ @i.should.closed?
+ @f.should_not.closed?
+ end
+
+ it "allows closing the original IO without affecting the new one" do
+ @f.close
+ -> { @i.gets }.should_not raise_error(Exception)
+
+ @i.should_not.closed?
+ @f.should.closed?
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.dup }.should raise_error(IOError)
+ end
+
+ it "always sets the close-on-exec flag for the new IO object" do
+ @f.close_on_exec = true
+ dup = @f.dup
+ begin
+ dup.should.close_on_exec?
+ ensure
+ dup.close
+ end
+
+ @f.close_on_exec = false
+ dup = @f.dup
+ begin
+ dup.should.close_on_exec?
+ ensure
+ dup.close
+ end
+ end
+
+ it "always sets the autoclose flag for the new IO object" do
+ @f.autoclose = true
+ dup = @f.dup
+ begin
+ dup.should.autoclose?
+ ensure
+ dup.close
+ end
+
+ @f.autoclose = false
+ dup = @f.dup
+ begin
+ dup.should.autoclose?
+ ensure
+ dup.close
+ @f.autoclose = true
+ end
+ end
+end
diff --git a/spec/ruby/core/io/each_byte_spec.rb b/spec/ruby/core/io/each_byte_spec.rb
new file mode 100644
index 0000000000..ea618e8c0c
--- /dev/null
+++ b/spec/ruby/core/io/each_byte_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#each_byte" do
+ before :each do
+ ScratchPad.record []
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.each_byte {} }.should raise_error(IOError)
+ end
+
+ it "yields each byte" do
+ count = 0
+ @io.each_byte do |byte|
+ ScratchPad << byte
+ break if 4 < count += 1
+ end
+
+ ScratchPad.recorded.should == [86, 111, 105, 99, 105]
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ enum = @io.each_byte
+ enum.should be_an_instance_of(Enumerator)
+ enum.first(5).should == [86, 111, 105, 99, 105]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @io.each_byte.size.should == nil
+ end
+ end
+ end
+ end
+end
+
+describe "IO#each_byte" do
+ before :each do
+ @io = IOSpecs.io_fixture "empty.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "returns self on an empty stream" do
+ @io.each_byte { |b| }.should equal(@io)
+ end
+end
diff --git a/spec/ruby/core/io/each_char_spec.rb b/spec/ruby/core/io/each_char_spec.rb
new file mode 100644
index 0000000000..5d460a1e7c
--- /dev/null
+++ b/spec/ruby/core/io/each_char_spec.rb
@@ -0,0 +1,12 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/chars'
+
+describe "IO#each_char" do
+ it_behaves_like :io_chars, :each_char
+end
+
+describe "IO#each_char" do
+ it_behaves_like :io_chars_empty, :each_char
+end
diff --git a/spec/ruby/core/io/each_codepoint_spec.rb b/spec/ruby/core/io/each_codepoint_spec.rb
new file mode 100644
index 0000000000..07a4037c8a
--- /dev/null
+++ b/spec/ruby/core/io/each_codepoint_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/codepoints'
+
+# See redmine #1667
+describe "IO#each_codepoint" do
+ it_behaves_like :io_codepoints, :each_codepoint
+end
+
+describe "IO#each_codepoint" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "calls the given block" do
+ r = []
+ @io.each_codepoint { |c| r << c }
+ r[24].should == 232
+ r.last.should == 10
+ end
+
+ it "returns self" do
+ @io.each_codepoint { |l| l }.should equal(@io)
+ end
+end
+
+describe "IO#each_codepoint" do
+ before :each do
+ @io = IOSpecs.io_fixture("incomplete.txt")
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "raises an exception at incomplete character before EOF when conversion takes place" do
+ -> { @io.each_codepoint {} }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/io/each_line_spec.rb b/spec/ruby/core/io/each_line_spec.rb
new file mode 100644
index 0000000000..58d26b325d
--- /dev/null
+++ b/spec/ruby/core/io/each_line_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/each'
+
+describe "IO#each_line" do
+ it_behaves_like :io_each, :each_line
+end
+
+describe "IO#each_line" do
+ it_behaves_like :io_each_default_separator, :each_line
+end
diff --git a/spec/ruby/core/io/each_spec.rb b/spec/ruby/core/io/each_spec.rb
new file mode 100644
index 0000000000..91ecbd19c8
--- /dev/null
+++ b/spec/ruby/core/io/each_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/each'
+
+describe "IO#each" do
+ it_behaves_like :io_each, :each
+end
+
+describe "IO#each" do
+ it_behaves_like :io_each_default_separator, :each
+end
diff --git a/spec/ruby/core/io/eof_spec.rb b/spec/ruby/core/io/eof_spec.rb
new file mode 100644
index 0000000000..315345d942
--- /dev/null
+++ b/spec/ruby/core/io/eof_spec.rb
@@ -0,0 +1,107 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#eof?" do
+ before :each do
+ @name = tmp("empty.txt")
+ touch @name
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "returns true on an empty stream that has just been opened" do
+ File.open(@name) { |empty| empty.should.eof? }
+ end
+
+ it "raises IOError on stream not opened for reading" do
+ -> do
+ File.open(@name, "w") { |f| f.eof? }
+ end.should raise_error(IOError)
+ end
+end
+
+describe "IO#eof?" do
+ before :each do
+ @name = fixture __FILE__, "lines.txt"
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io && !@io.closed?
+ end
+
+ it "returns false when not at end of file" do
+ @io.read 1
+ @io.should_not.eof?
+ end
+
+ it "returns true after reading with read with no parameters" do
+ @io.read()
+ @io.should.eof?
+ end
+
+ it "returns true after reading with read" do
+ @io.read(File.size(@name))
+ @io.should.eof?
+ end
+
+ it "returns true after reading with sysread" do
+ @io.sysread(File.size(@name))
+ @io.should.eof?
+ end
+
+ it "returns true after reading with readlines" do
+ @io.readlines
+ @io.should.eof?
+ end
+
+ it "returns false on just opened non-empty stream" do
+ @io.should_not.eof?
+ end
+
+ it "does not consume the data from the stream" do
+ @io.should_not.eof?
+ @io.getc.should == 'V'
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.eof? }.should raise_error(IOError)
+ end
+
+ it "raises IOError on stream closed for reading by close_read" do
+ @io.close_read
+ -> { @io.eof? }.should raise_error(IOError)
+ end
+
+ it "returns true on one-byte stream after single-byte read" do
+ File.open(File.dirname(__FILE__) + '/fixtures/one_byte.txt') { |one_byte|
+ one_byte.read(1)
+ one_byte.should.eof?
+ }
+ end
+end
+
+describe "IO#eof?" do
+ after :each do
+ @r.close if @r && !@r.closed?
+ @w.close if @w && !@w.closed?
+ end
+
+ it "returns true on receiving side of Pipe when writing side is closed" do
+ @r, @w = IO.pipe
+ @w.close
+ @r.should.eof?
+ end
+
+ it "returns false on receiving side of Pipe when writing side wrote some data" do
+ @r, @w = IO.pipe
+ @w.puts "hello"
+ @r.should_not.eof?
+ @w.close
+ @r.should_not.eof?
+ @r.read
+ @r.should.eof?
+ end
+end
diff --git a/spec/ruby/core/io/external_encoding_spec.rb b/spec/ruby/core/io/external_encoding_spec.rb
new file mode 100644
index 0000000000..2fcf1c7218
--- /dev/null
+++ b/spec/ruby/core/io/external_encoding_spec.rb
@@ -0,0 +1,225 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :io_external_encoding_write, shared: true do
+ describe "when Encoding.default_internal is nil" do
+ before :each do
+ Encoding.default_internal = nil
+ end
+
+ it "returns nil" do
+ @io = new_io @name, @object
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should be_nil
+ end
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "#{@object}:ibm866"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should equal(Encoding::IBM866)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "#{@object}:ibm866"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should equal(Encoding::EUC_JP)
+ end
+ end
+
+ describe "when Encoding.default_external != Encoding.default_internal" do
+ before :each do
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+ it "returns the value of Encoding.default_external when the instance was created" do
+ @io = new_io @name, @object
+ Encoding.default_external = Encoding::UTF_8
+ @io.external_encoding.should equal(Encoding::IBM437)
+ end
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "#{@object}:ibm866"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should equal(Encoding::IBM866)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "#{@object}:ibm866"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should equal(Encoding::EUC_JP)
+ end
+ end
+
+ describe "when Encoding.default_external == Encoding.default_internal" do
+ before :each do
+ Encoding.default_external = Encoding::IBM866
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+ it "returns the value of Encoding.default_external when the instance was created" do
+ @io = new_io @name, @object
+ Encoding.default_external = Encoding::UTF_8
+ @io.external_encoding.should equal(Encoding::IBM866)
+ end
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "#{@object}:ibm866"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should equal(Encoding::IBM866)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "#{@object}:ibm866"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should equal(Encoding::EUC_JP)
+ end
+ end
+end
+
+describe "IO#external_encoding" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ @name = tmp("io_external_encoding")
+ touch(@name)
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+
+ @io.close if @io
+ rm_r @name
+ end
+
+ ruby_version_is '3.1' do
+ it "can be retrieved from a closed stream" do
+ io = IOSpecs.io_fixture("lines.txt", "r")
+ io.close
+ io.external_encoding.should equal(Encoding.default_external)
+ end
+ end
+
+ describe "with 'r' mode" do
+ describe "when Encoding.default_internal is nil" do
+ before :each do
+ Encoding.default_internal = nil
+ Encoding.default_external = Encoding::IBM866
+ end
+
+ it "returns Encoding.default_external if the external encoding is not set" do
+ @io = new_io @name, "r"
+ @io.external_encoding.should equal(Encoding::IBM866)
+ end
+
+ it "returns Encoding.default_external when that encoding is changed after the instance is created" do
+ @io = new_io @name, "r"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should equal(Encoding::IBM437)
+ end
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "r:utf-8"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should equal(Encoding::UTF_8)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "r:utf-8"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should equal(Encoding::EUC_JP)
+ end
+ end
+
+ describe "when Encoding.default_external == Encoding.default_internal" do
+ before :each do
+ Encoding.default_external = Encoding::IBM866
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+ it "returns the value of Encoding.default_external when the instance was created" do
+ @io = new_io @name, "r"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should equal(Encoding::IBM866)
+ end
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "r:utf-8"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should equal(Encoding::UTF_8)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "r:utf-8"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should equal(Encoding::EUC_JP)
+ end
+ end
+
+ describe "when Encoding.default_external != Encoding.default_internal" do
+ before :each do
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "r:utf-8"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should equal(Encoding::UTF_8)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "r:utf-8"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should equal(Encoding::EUC_JP)
+ end
+ end
+ end
+
+ describe "with 'rb' mode" do
+ it "returns Encoding::BINARY" do
+ @io = new_io @name, "rb"
+ @io.external_encoding.should equal(Encoding::BINARY)
+ end
+
+ it "returns the external encoding specified by the mode argument" do
+ @io = new_io @name, "rb:ibm437"
+ @io.external_encoding.should equal(Encoding::IBM437)
+ end
+ end
+
+ describe "with 'r+' mode" do
+ it_behaves_like :io_external_encoding_write, nil, "r+"
+ end
+
+ describe "with 'w' mode" do
+ it_behaves_like :io_external_encoding_write, nil, "w"
+ end
+
+ describe "with 'wb' mode" do
+ it "returns Encoding::BINARY" do
+ @io = new_io @name, "wb"
+ @io.external_encoding.should equal(Encoding::BINARY)
+ end
+
+ it "returns the external encoding specified by the mode argument" do
+ @io = new_io @name, "wb:ibm437"
+ @io.external_encoding.should equal(Encoding::IBM437)
+ end
+ end
+
+ describe "with 'w+' mode" do
+ it_behaves_like :io_external_encoding_write, nil, "w+"
+ end
+
+ describe "with 'a' mode" do
+ it_behaves_like :io_external_encoding_write, nil, "a"
+ end
+
+ describe "with 'a+' mode" do
+ it_behaves_like :io_external_encoding_write, nil, "a+"
+ end
+end
diff --git a/spec/ruby/core/io/fcntl_spec.rb b/spec/ruby/core/io/fcntl_spec.rb
new file mode 100644
index 0000000000..30b4876fe3
--- /dev/null
+++ b/spec/ruby/core/io/fcntl_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#fcntl" do
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.fcntl(5, 5) }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/fdatasync_spec.rb b/spec/ruby/core/io/fdatasync_spec.rb
new file mode 100644
index 0000000000..6242258ea0
--- /dev/null
+++ b/spec/ruby/core/io/fdatasync_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "IO#fdatasync" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/io/fileno_spec.rb b/spec/ruby/core/io/fileno_spec.rb
new file mode 100644
index 0000000000..647609bf42
--- /dev/null
+++ b/spec/ruby/core/io/fileno_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#fileno" do
+ it "returns the numeric file descriptor of the given IO object" do
+ $stdout.fileno.should == 1
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.fileno }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/fixtures/bom_UTF-16BE.txt b/spec/ruby/core/io/fixtures/bom_UTF-16BE.txt
new file mode 100644
index 0000000000..c7c42e9de4
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/bom_UTF-16BE.txt
Binary files differ
diff --git a/spec/ruby/core/io/fixtures/bom_UTF-16LE.txt b/spec/ruby/core/io/fixtures/bom_UTF-16LE.txt
new file mode 100644
index 0000000000..53642b6984
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/bom_UTF-16LE.txt
Binary files differ
diff --git a/spec/ruby/core/io/fixtures/bom_UTF-32BE.txt b/spec/ruby/core/io/fixtures/bom_UTF-32BE.txt
new file mode 100644
index 0000000000..c5efe6c278
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/bom_UTF-32BE.txt
Binary files differ
diff --git a/spec/ruby/core/io/fixtures/bom_UTF-32LE.txt b/spec/ruby/core/io/fixtures/bom_UTF-32LE.txt
new file mode 100644
index 0000000000..1168384393
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/bom_UTF-32LE.txt
Binary files differ
diff --git a/spec/ruby/core/io/fixtures/bom_UTF-8.txt b/spec/ruby/core/io/fixtures/bom_UTF-8.txt
new file mode 100644
index 0000000000..ca971bef61
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/bom_UTF-8.txt
@@ -0,0 +1 @@
+UTF-8
diff --git a/spec/ruby/core/io/fixtures/classes.rb b/spec/ruby/core/io/fixtures/classes.rb
new file mode 100644
index 0000000000..204a2a101b
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/classes.rb
@@ -0,0 +1,218 @@
+# -*- encoding: utf-8 -*-
+
+module IOSpecs
+ THREAD_CLOSE_RETRIES = 10
+ THREAD_CLOSE_ERROR_MESSAGE = 'stream closed in another thread'
+
+ class SubIO < IO
+ end
+
+ class SubIOWithRedefinedNew < IO
+ def self.new(...)
+ ScratchPad << :redefined_new_called
+ super
+ end
+
+ def initialize(...)
+ ScratchPad << :call_original_initialize
+ super
+ end
+ end
+
+ def self.collector
+ Proc.new { |x| ScratchPad << x }
+ end
+
+ def self.lines
+ [ "Voici la ligne une.\n",
+ "Qui \303\250 la linea due.\n",
+ "\n",
+ "\n",
+ "Aqu\303\255 est\303\241 la l\303\255nea tres.\n",
+ "Hier ist Zeile vier.\n",
+ "\n",
+ "Est\303\241 aqui a linha cinco.\n",
+ "Here is line six.\n" ]
+ end
+
+ def self.lines_without_newline_characters
+ [ "Voici la ligne une.",
+ "Qui \303\250 la linea due.",
+ "",
+ "",
+ "Aqu\303\255 est\303\241 la l\303\255nea tres.",
+ "Hier ist Zeile vier.",
+ "",
+ "Est\303\241 aqui a linha cinco.",
+ "Here is line six." ]
+ end
+
+ def self.lines_limit
+ [ "Voici la l",
+ "igne une.\n",
+ "Qui è la ",
+ "linea due.",
+ "\n",
+ "\n",
+ "\n",
+ "Aquí está",
+ " la línea",
+ " tres.\n",
+ "Hier ist Z",
+ "eile vier.",
+ "\n",
+ "\n",
+ "Está aqui",
+ " a linha c",
+ "inco.\n",
+ "Here is li",
+ "ne six.\n" ]
+ end
+
+ def self.lines_space_separator_limit
+ [ "Voici ",
+ "la ",
+ "ligne ",
+ "une.\nQui ",
+ "è ",
+ "la ",
+ "linea ",
+ "due.\n\n\nAqu",
+ "í ",
+ "está ",
+ "la ",
+ "línea ",
+ "tres.\nHier",
+ " ",
+ "ist ",
+ "Zeile ",
+ "vier.\n\nEst",
+ "á ",
+ "aqui ",
+ "a ",
+ "linha ",
+ "cinco.\nHer",
+ "e ",
+ "is ",
+ "line ",
+ "six.\n" ]
+ end
+
+ def self.lines_r_separator
+ [ "Voici la ligne une.\nQui \303\250 la linea due.\n\n\nAqu\303\255 est\303\241 la l\303\255nea tr",
+ "es.\nHier",
+ " ist Zeile vier",
+ ".\n\nEst\303\241 aqui a linha cinco.\nHer",
+ "e is line six.\n" ]
+ end
+
+ def self.lines_empty_separator
+ [ "Voici la ligne une.\nQui \303\250 la linea due.\n\n",
+ "Aqu\303\255 est\303\241 la l\303\255nea tres.\nHier ist Zeile vier.\n\n",
+ "Est\303\241 aqui a linha cinco.\nHere is line six.\n" ]
+ end
+
+ def self.lines_space_separator
+ [ "Voici ", "la ", "ligne ", "une.\nQui ",
+ "\303\250 ", "la ", "linea ", "due.\n\n\nAqu\303\255 ",
+ "est\303\241 ", "la ", "l\303\255nea ", "tres.\nHier ",
+ "ist ", "Zeile ", "vier.\n\nEst\303\241 ", "aqui ", "a ",
+ "linha ", "cinco.\nHere ", "is ", "line ", "six.\n" ]
+ end
+
+ def self.lines_space_separator_without_trailing_spaces
+ [ "Voici", "la", "ligne", "une.\nQui",
+ "\303\250", "la", "linea", "due.\n\n\nAqu\303\255",
+ "est\303\241", "la", "l\303\255nea", "tres.\nHier",
+ "ist", "Zeile", "vier.\n\nEst\303\241", "aqui", "a",
+ "linha", "cinco.\nHere", "is", "line", "six.\n" ]
+ end
+
+ def self.lines_arbitrary_separator
+ [ "Voici la ligne une.\nQui \303\250",
+ " la linea due.\n\n\nAqu\303\255 est\303\241 la l\303\255nea tres.\nHier ist Zeile vier.\n\nEst\303\241 aqui a linha cinco.\nHere is line six.\n" ]
+ end
+
+ def self.paragraphs
+ [ "Voici la ligne une.\nQui \303\250 la linea due.\n\n",
+ "Aqu\303\255 est\303\241 la l\303\255nea tres.\nHier ist Zeile vier.\n\n",
+ "Est\303\241 aqui a linha cinco.\nHere is line six.\n" ]
+ end
+
+ def self.paragraphs_without_trailing_new_line_characters
+ [ "Voici la ligne une.\nQui \303\250 la linea due.",
+ "Aqu\303\255 est\303\241 la l\303\255nea tres.\nHier ist Zeile vier.",
+ "Est\303\241 aqui a linha cinco.\nHere is line six.\n" ]
+ end
+
+ # Creates an IO instance for an existing fixture file. The
+ # file should obviously not be deleted.
+ def self.io_fixture(name, mode = "r:utf-8")
+ path = fixture __FILE__, name
+ name = path if File.exist? path
+ new_io(name, mode)
+ end
+
+ # Returns a closed instance of IO that was opened to reference
+ # a fixture file (i.e. the IO instance was perfectly valid at
+ # one point but is now closed).
+ def self.closed_io
+ io = io_fixture "lines.txt"
+ io.close
+ io
+ end
+
+ # Creates a pipe-based IO fixture containing the specified
+ # contents
+ def self.pipe_fixture(content)
+ source, sink = IO.pipe
+ sink.write content
+ sink.close
+ source
+ end
+
+ # Defines +method+ on +obj+ using the provided +block+. This
+ # special helper is needed for e.g. IO.open specs to avoid
+ # mock methods preventing IO#close from running.
+ def self.io_mock(obj, method, &block)
+ obj.singleton_class.send(:define_method, method, &block)
+ end
+
+ module CopyStream
+ def self.from=(from)
+ @from = from
+ end
+
+ def self.from
+ @from
+ end
+ end
+
+ class CopyStreamRead
+ def initialize(io)
+ @io = io
+ end
+
+ def read(size, buf)
+ @io.read size, buf
+ end
+
+ def send(*args)
+ raise "send called"
+ end
+ end
+
+ class CopyStreamReadPartial
+ def initialize(io)
+ @io = io
+ end
+
+ def readpartial(size, buf)
+ @io.readpartial size, buf
+ end
+
+ def send(*args)
+ raise "send called"
+ end
+ end
+end
diff --git a/spec/ruby/core/io/fixtures/copy_in_out.rb b/spec/ruby/core/io/fixtures/copy_in_out.rb
new file mode 100644
index 0000000000..b9d4085a47
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/copy_in_out.rb
@@ -0,0 +1,2 @@
+STDOUT.sync = false
+IO.copy_stream(STDIN, STDOUT)
diff --git a/spec/ruby/core/io/fixtures/copy_stream.txt b/spec/ruby/core/io/fixtures/copy_stream.txt
new file mode 100644
index 0000000000..a2d827b351
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/copy_stream.txt
@@ -0,0 +1,6 @@
+Line one
+
+Line three
+Line four
+
+Line last
diff --git a/spec/ruby/core/io/fixtures/empty.txt b/spec/ruby/core/io/fixtures/empty.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/empty.txt
diff --git a/spec/ruby/core/io/fixtures/incomplete.txt b/spec/ruby/core/io/fixtures/incomplete.txt
new file mode 100644
index 0000000000..23d432f642
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/incomplete.txt
@@ -0,0 +1 @@
+ðŸ \ No newline at end of file
diff --git a/spec/ruby/core/io/fixtures/lines.txt b/spec/ruby/core/io/fixtures/lines.txt
new file mode 100644
index 0000000000..0959997e7b
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/lines.txt
@@ -0,0 +1,9 @@
+Voici la ligne une.
+Qui è la linea due.
+
+
+Aquí está la línea tres.
+Hier ist Zeile vier.
+
+Está aqui a linha cinco.
+Here is line six.
diff --git a/spec/ruby/core/io/fixtures/no_bom_UTF-8.txt b/spec/ruby/core/io/fixtures/no_bom_UTF-8.txt
new file mode 100644
index 0000000000..7edc66b06a
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/no_bom_UTF-8.txt
@@ -0,0 +1 @@
+UTF-8
diff --git a/spec/ruby/core/io/fixtures/numbered_lines.txt b/spec/ruby/core/io/fixtures/numbered_lines.txt
new file mode 100644
index 0000000000..70e49a3d98
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/numbered_lines.txt
@@ -0,0 +1,5 @@
+Line 1: One
+Line 2: Two
+Line 3: Three
+Line 4: Four
+Line 5: Five
diff --git a/spec/ruby/core/io/fixtures/one_byte.txt b/spec/ruby/core/io/fixtures/one_byte.txt
new file mode 100644
index 0000000000..56a6051ca2
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/one_byte.txt
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/spec/ruby/core/io/fixtures/read_binary.txt b/spec/ruby/core/io/fixtures/read_binary.txt
new file mode 100644
index 0000000000..fa036dca4b
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/read_binary.txt
@@ -0,0 +1 @@
+abcâdef
diff --git a/spec/ruby/core/io/fixtures/read_euc_jp.txt b/spec/ruby/core/io/fixtures/read_euc_jp.txt
new file mode 100644
index 0000000000..0e17cd717a
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/read_euc_jp.txt
@@ -0,0 +1 @@
+¤¢¤ê¤¬¤È¤¦
diff --git a/spec/ruby/core/io/fixtures/read_text.txt b/spec/ruby/core/io/fixtures/read_text.txt
new file mode 100644
index 0000000000..5a7a80f6e4
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/read_text.txt
@@ -0,0 +1 @@
+abcâdef
diff --git a/spec/ruby/core/io/fixtures/reopen_stdout.rb b/spec/ruby/core/io/fixtures/reopen_stdout.rb
new file mode 100644
index 0000000000..506ba072bd
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/reopen_stdout.rb
@@ -0,0 +1,3 @@
+STDOUT.reopen ARGV[0]
+system "echo from system"
+exec "echo from exec"
diff --git a/spec/ruby/core/io/flush_spec.rb b/spec/ruby/core/io/flush_spec.rb
new file mode 100644
index 0000000000..34cf42c425
--- /dev/null
+++ b/spec/ruby/core/io/flush_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#flush" do
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.flush }.should raise_error(IOError)
+ end
+
+ describe "on a pipe" do
+ before :each do
+ @r, @w = IO.pipe
+ end
+
+ after :each do
+ @r.close
+ begin
+ @w.close
+ rescue Errno::EPIPE
+ end
+ end
+
+ # [ruby-core:90895] MJIT worker may leave fd open in a forked child.
+ # For instance, MJIT creates a worker before @r.close with fork(), @r.close happens,
+ # and the MJIT worker keeps the pipe open until the worker execve().
+ # TODO: consider acquiring GVL from MJIT worker.
+ guard_not -> { defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? } do
+ it "raises Errno::EPIPE if sync=false and the read end is closed" do
+ @w.sync = false
+ @w.write "foo"
+ @r.close
+
+ -> { @w.flush }.should raise_error(Errno::EPIPE, /Broken pipe/)
+ -> { @w.close }.should raise_error(Errno::EPIPE, /Broken pipe/)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/for_fd_spec.rb b/spec/ruby/core/io/for_fd_spec.rb
new file mode 100644
index 0000000000..2d86361b73
--- /dev/null
+++ b/spec/ruby/core/io/for_fd_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/new'
+
+describe "IO.for_fd" do
+ it_behaves_like :io_new, :for_fd
+end
+
+describe "IO.for_fd" do
+ it_behaves_like :io_new_errors, :for_fd
+end
diff --git a/spec/ruby/core/io/foreach_spec.rb b/spec/ruby/core/io/foreach_spec.rb
new file mode 100644
index 0000000000..c2276cf544
--- /dev/null
+++ b/spec/ruby/core/io/foreach_spec.rb
@@ -0,0 +1,81 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/readlines'
+
+describe "IO.foreach" do
+ before :each do
+ @name = fixture __FILE__, "lines.txt"
+ @count = 0
+ ScratchPad.record []
+ end
+
+ it "updates $. with each yield" do
+ IO.foreach(@name) { $..should == @count += 1 }
+ end
+
+ describe "when the filename starts with |" do
+ it "gets data from the standard out of the subprocess" do
+ cmd = "|sh -c 'echo hello;echo line2'"
+ platform_is :windows do
+ cmd = "|cmd.exe /C echo hello&echo line2"
+ end
+ IO.foreach(cmd) { |l| ScratchPad << l }
+ ScratchPad.recorded.should == ["hello\n", "line2\n"]
+ end
+
+ platform_is_not :windows do
+ it "gets data from a fork when passed -" do
+ parent_pid = $$
+
+ IO.foreach("|-") { |l| ScratchPad << l }
+
+ if $$ == parent_pid
+ ScratchPad.recorded.should == ["hello\n", "from a fork\n"]
+ else # child
+ puts "hello"
+ puts "from a fork"
+ exit!
+ end
+ end
+ end
+ end
+end
+
+describe "IO.foreach" do
+ before :each do
+ @external = Encoding.default_external
+ Encoding.default_external = Encoding::UTF_8
+
+ @name = fixture __FILE__, "lines.txt"
+ ScratchPad.record []
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ end
+
+ it "sets $_ to nil" do
+ $_ = "test"
+ IO.foreach(@name) { }
+ $_.should be_nil
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ IO.foreach(@name).should be_an_instance_of(Enumerator)
+ IO.foreach(@name).to_a.should == IOSpecs.lines
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ IO.foreach(@name).size.should == nil
+ end
+ end
+ end
+ end
+
+ it_behaves_like :io_readlines, :foreach, IOSpecs.collector
+ it_behaves_like :io_readlines_options_19, :foreach, IOSpecs.collector
+end
diff --git a/spec/ruby/core/io/fsync_spec.rb b/spec/ruby/core/io/fsync_spec.rb
new file mode 100644
index 0000000000..6e6123de94
--- /dev/null
+++ b/spec/ruby/core/io/fsync_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#fsync" do
+ before :each do
+ @name = tmp("io_fsync.txt")
+ ScratchPad.clear
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.fsync }.should raise_error(IOError)
+ end
+
+ it "writes the buffered data to permanent storage" do
+ File.open(@name, "w") do |f|
+ f.write "one hit wonder"
+ f.fsync.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/core/io/getbyte_spec.rb b/spec/ruby/core/io/getbyte_spec.rb
new file mode 100644
index 0000000000..6ba8f0a3e0
--- /dev/null
+++ b/spec/ruby/core/io/getbyte_spec.rb
@@ -0,0 +1,42 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#getbyte" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "returns the next byte from the stream" do
+ @io.readline.should == "Voici la ligne une.\n"
+ letters = @io.getbyte, @io.getbyte, @io.getbyte, @io.getbyte, @io.getbyte
+ letters.should == [81, 117, 105, 32, 195]
+ end
+
+ it "returns nil when invoked at the end of the stream" do
+ @io.read
+ @io.getbyte.should == nil
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.getbyte }.should raise_error(IOError)
+ end
+end
+
+describe "IO#getbyte" do
+ before :each do
+ @io = IOSpecs.io_fixture "empty.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "returns nil on empty stream" do
+ @io.getbyte.should == nil
+ end
+end
diff --git a/spec/ruby/core/io/getc_spec.rb b/spec/ruby/core/io/getc_spec.rb
new file mode 100644
index 0000000000..3949b5cb28
--- /dev/null
+++ b/spec/ruby/core/io/getc_spec.rb
@@ -0,0 +1,42 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#getc" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "returns the next character from the stream" do
+ @io.readline.should == "Voici la ligne une.\n"
+ letters = @io.getc, @io.getc, @io.getc, @io.getc, @io.getc
+ letters.should == ["Q", "u", "i", " ", "è"]
+ end
+
+ it "returns nil when invoked at the end of the stream" do
+ @io.read
+ @io.getc.should be_nil
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.getc }.should raise_error(IOError)
+ end
+end
+
+describe "IO#getc" do
+ before :each do
+ @io = IOSpecs.io_fixture "empty.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "returns nil on empty stream" do
+ @io.getc.should be_nil
+ end
+end
diff --git a/spec/ruby/core/io/gets_spec.rb b/spec/ruby/core/io/gets_spec.rb
new file mode 100644
index 0000000000..d0c91705af
--- /dev/null
+++ b/spec/ruby/core/io/gets_spec.rb
@@ -0,0 +1,315 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/gets_ascii'
+
+describe "IO#gets" do
+ it_behaves_like :io_gets_ascii, :gets
+end
+
+describe "IO#gets" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ @count = 0
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "assigns the returned line to $_" do
+ IOSpecs.lines.each do |line|
+ @io.gets
+ $_.should == line
+ end
+ end
+
+ it "returns nil if called at the end of the stream" do
+ IOSpecs.lines.length.times { @io.gets }
+ @io.gets.should == nil
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.gets }.should raise_error(IOError)
+ end
+
+ describe "with no separator" do
+ it "returns the next line of string that is separated by $/" do
+ IOSpecs.lines.each { |line| line.should == @io.gets }
+ end
+
+ it "updates lineno with each invocation" do
+ while @io.gets
+ @io.lineno.should == @count += 1
+ end
+ end
+
+ it "updates $. with each invocation" do
+ while @io.gets
+ $..should == @count += 1
+ end
+ end
+ end
+
+ describe "with nil separator" do
+ it "returns the entire contents" do
+ @io.gets(nil).should == IOSpecs.lines.join("")
+ end
+
+ it "updates lineno with each invocation" do
+ while @io.gets(nil)
+ @io.lineno.should == @count += 1
+ end
+ end
+
+ it "updates $. with each invocation" do
+ while @io.gets(nil)
+ $..should == @count += 1
+ end
+ end
+ end
+
+ describe "with an empty String separator" do
+ # Two successive newlines in the input separate paragraphs.
+ # When there are more than two successive newlines, only two are kept.
+ it "returns the next paragraph" do
+ @io.gets("").should == IOSpecs.lines[0,3].join("")
+ @io.gets("").should == IOSpecs.lines[4,3].join("")
+ @io.gets("").should == IOSpecs.lines[7,2].join("")
+ end
+
+ it "reads until the beginning of the next paragraph" do
+ # There are three newlines between the first and second paragraph
+ @io.gets("")
+ @io.gets.should == IOSpecs.lines[4]
+ end
+
+ it "updates lineno with each invocation" do
+ while @io.gets("")
+ @io.lineno.should == @count += 1
+ end
+ end
+
+ it "updates $. with each invocation" do
+ while @io.gets("")
+ $..should == @count += 1
+ end
+ end
+ end
+
+ describe "with an arbitrary String separator" do
+ it "reads up to and including the separator" do
+ @io.gets("la linea").should == "Voici la ligne une.\nQui \303\250 la linea"
+ end
+
+ it "updates lineno with each invocation" do
+ while (@io.gets("la"))
+ @io.lineno.should == @count += 1
+ end
+ end
+
+ it "updates $. with each invocation" do
+ while @io.gets("la")
+ $..should == @count += 1
+ end
+ end
+ end
+
+ describe "when passed chomp" do
+ it "returns the first line without a trailing newline character" do
+ @io.gets(chomp: true).should == IOSpecs.lines_without_newline_characters[0]
+ end
+
+ ruby_version_is "3.0" do
+ it "raises exception when options passed as Hash" do
+ -> { @io.gets({ chomp: true }) }.should raise_error(TypeError)
+
+ -> {
+ @io.gets("\n", 1, { chomp: true })
+ }.should raise_error(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
+ end
+ end
+ end
+end
+
+describe "IO#gets" do
+ before :each do
+ @name = tmp("io_gets")
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "raises an IOError if the stream is opened for append only" do
+ -> { File.open(@name, "a:utf-8") { |f| f.gets } }.should raise_error(IOError)
+ end
+
+ it "raises an IOError if the stream is opened for writing only" do
+ -> { File.open(@name, "w:utf-8") { |f| f.gets } }.should raise_error(IOError)
+ end
+end
+
+describe "IO#gets" do
+ before :each do
+ @name = tmp("io_gets")
+ touch(@name) { |f| f.write "one\n\ntwo\n\nthree\nfour\n" }
+ @io = new_io @name, "r:utf-8"
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ end
+
+ it "calls #to_int to convert a single object argument to an Integer limit" do
+ obj = mock("io gets limit")
+ obj.should_receive(:to_int).and_return(6)
+
+ @io.gets(obj).should == "one\n"
+ end
+
+ it "calls #to_int to convert the second object argument to an Integer limit" do
+ obj = mock("io gets limit")
+ obj.should_receive(:to_int).and_return(2)
+
+ @io.gets(nil, obj).should == "on"
+ end
+
+ it "calls #to_str to convert the first argument to a String when passed a limit" do
+ obj = mock("io gets separator")
+ obj.should_receive(:to_str).and_return($/)
+
+ @io.gets(obj, 5).should == "one\n"
+ end
+
+ it "reads to the default separator when passed a single argument greater than the number of bytes to the separator" do
+ @io.gets(6).should == "one\n"
+ end
+
+ it "reads limit bytes when passed a single argument less than the number of bytes to the default separator" do
+ @io.gets(3).should == "one"
+ end
+
+ it "reads limit bytes when passed nil and a limit" do
+ @io.gets(nil, 6).should == "one\n\nt"
+ end
+
+ it "reads all bytes when the limit is higher than the available bytes" do
+ @io.gets(nil, 100).should == "one\n\ntwo\n\nthree\nfour\n"
+ end
+
+ it "reads until the next paragraph when passed '' and a limit greater than the next paragraph" do
+ @io.gets("", 6).should == "one\n\n"
+ end
+
+ it "reads limit bytes when passed '' and a limit less than the next paragraph" do
+ @io.gets("", 3).should == "one"
+ end
+
+ it "reads all bytes when pass a separator and reading more than all bytes" do
+ @io.gets("\t", 100).should == "one\n\ntwo\n\nthree\nfour\n"
+ end
+
+ it "returns empty string when 0 passed as a limit" do
+ @io.gets(0).should == ""
+ @io.gets(nil, 0).should == ""
+ @io.gets("", 0).should == ""
+ end
+
+ it "does not accept limit that doesn't fit in a C off_t" do
+ -> { @io.gets(2**128) }.should raise_error(RangeError)
+ end
+end
+
+describe "IO#gets" do
+ before :each do
+ @name = tmp("io_gets")
+ # create data "æœæ—¥" + "\xE3\x81" * 100 to avoid utf-8 conflicts
+ data = "æœæ—¥" + ([227,129].pack('C*') * 100).force_encoding('utf-8')
+ touch(@name) { |f| f.write data }
+ @io = new_io @name, "r:utf-8"
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ end
+
+ it "reads limit bytes and extra bytes when limit is reached not at character boundary" do
+ [@io.gets(1), @io.gets(1)].should == ["æœ", "æ—¥"]
+ end
+
+ it "read limit bytes and extra bytes with maximum of 16" do
+ # create str "æœæ—¥\xE3" + "\x81\xE3" * 8 to avoid utf-8 conflicts
+ str = "æœæ—¥" + ([227] + [129,227] * 8).pack('C*').force_encoding('utf-8')
+ @io.gets(7).should == str
+ end
+end
+
+describe "IO#gets" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = nil
+
+ @name = tmp("io_gets")
+ touch(@name) { |f| f.write "line" }
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "uses the default external encoding" do
+ @io = new_io @name, 'r'
+ @io.gets.encoding.should == Encoding::UTF_8
+ end
+
+ it "uses the IO object's external encoding, when set" do
+ @io = new_io @name, 'r'
+ @io.set_encoding Encoding::US_ASCII
+ @io.gets.encoding.should == Encoding::US_ASCII
+ end
+
+ it "transcodes into the default internal encoding" do
+ Encoding.default_internal = Encoding::US_ASCII
+ @io = new_io @name, 'r'
+ @io.gets.encoding.should == Encoding::US_ASCII
+ end
+
+ it "transcodes into the IO object's internal encoding, when set" do
+ Encoding.default_internal = Encoding::US_ASCII
+ @io = new_io @name, 'r'
+ @io.set_encoding Encoding::UTF_8, Encoding::UTF_16
+ @io.gets.encoding.should == Encoding::UTF_16
+ end
+
+ it "overwrites the default external encoding with the IO object's own external encoding" do
+ Encoding.default_external = Encoding::BINARY
+ Encoding.default_internal = Encoding::UTF_8
+ @io = new_io @name, 'r'
+ @io.set_encoding Encoding::IBM866
+ @io.gets.encoding.should == Encoding::UTF_8
+ end
+
+ it "ignores the internal encoding if the default external encoding is BINARY" do
+ Encoding.default_external = Encoding::BINARY
+ Encoding.default_internal = Encoding::UTF_8
+ @io = new_io @name, 'r'
+ @io.gets.encoding.should == Encoding::BINARY
+ end
+
+ it "transcodes to internal encoding if the IO object's external encoding is BINARY" do
+ Encoding.default_external = Encoding::BINARY
+ Encoding.default_internal = Encoding::UTF_8
+ @io = new_io @name, 'r'
+ @io.set_encoding Encoding::BINARY, Encoding::UTF_8
+ @io.gets.encoding.should == Encoding::UTF_8
+ end
+end
diff --git a/spec/ruby/core/io/initialize_spec.rb b/spec/ruby/core/io/initialize_spec.rb
new file mode 100644
index 0000000000..ba5bc117b7
--- /dev/null
+++ b/spec/ruby/core/io/initialize_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#initialize" do
+ before :each do
+ @name = tmp("io_initialize.txt")
+ @io = IO.new(new_fd(@name))
+ @fd = @io.fileno
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ end
+
+ it "reassociates the IO instance with the new descriptor when passed an Integer" do
+ fd = new_fd @name, "r:utf-8"
+ @io.send :initialize, fd, 'r'
+ @io.fileno.should == fd
+ end
+
+ it "calls #to_int to coerce the object passed as an fd" do
+ obj = mock('fileno')
+ fd = new_fd @name, "r:utf-8"
+ obj.should_receive(:to_int).and_return(fd)
+ @io.send :initialize, obj, 'r'
+ @io.fileno.should == fd
+ end
+
+ it "raises a TypeError when passed an IO" do
+ -> { @io.send :initialize, STDOUT, 'w' }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { @io.send :initialize, nil, 'w' }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { @io.send :initialize, "4", 'w' }.should raise_error(TypeError)
+ end
+
+ it "raises IOError on closed stream" do
+ -> { @io.send :initialize, IOSpecs.closed_io.fileno }.should raise_error(IOError)
+ end
+
+ it "raises an Errno::EBADF when given an invalid file descriptor" do
+ -> { @io.send :initialize, -1, 'w' }.should raise_error(Errno::EBADF)
+ end
+end
diff --git a/spec/ruby/core/io/inspect_spec.rb b/spec/ruby/core/io/inspect_spec.rb
new file mode 100644
index 0000000000..c653c307c4
--- /dev/null
+++ b/spec/ruby/core/io/inspect_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "IO#inspect" do
+ after :each do
+ @r.close if @r && !@r.closed?
+ @w.close if @w && !@w.closed?
+ end
+
+ it "contains the file descriptor number" do
+ @r, @w = IO.pipe
+ @r.inspect.should include("fd #{@r.fileno}")
+ end
+
+ it "contains \"(closed)\" if the stream is closed" do
+ @r, @w = IO.pipe
+ @r.close
+ @r.inspect.should include("(closed)")
+ end
+
+ it "reports IO as its Method object's owner" do
+ IO.instance_method(:inspect).owner.should == IO
+ end
+end
diff --git a/spec/ruby/core/io/internal_encoding_spec.rb b/spec/ruby/core/io/internal_encoding_spec.rb
new file mode 100644
index 0000000000..60afaf2ebd
--- /dev/null
+++ b/spec/ruby/core/io/internal_encoding_spec.rb
@@ -0,0 +1,147 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :io_internal_encoding, shared: true do
+ describe "when Encoding.default_internal is not set" do
+ before :each do
+ Encoding.default_internal = nil
+ end
+
+ it "returns nil if the internal encoding is not set" do
+ @io = new_io @name, @object
+ @io.internal_encoding.should be_nil
+ end
+
+ it "returns nil if Encoding.default_internal is changed after the instance is created" do
+ @io = new_io @name, @object
+ Encoding.default_internal = Encoding::IBM437
+ @io.internal_encoding.should be_nil
+ end
+
+ it "returns the value set when the instance was created" do
+ @io = new_io @name, "#{@object}:utf-8:euc-jp"
+ Encoding.default_internal = Encoding::IBM437
+ @io.internal_encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "returns the value set by #set_encoding" do
+ @io = new_io @name, @object
+ @io.set_encoding(Encoding::US_ASCII, Encoding::IBM437)
+ @io.internal_encoding.should equal(Encoding::IBM437)
+ end
+ end
+
+ describe "when Encoding.default_internal == Encoding.default_external" do
+ before :each do
+ Encoding.default_external = Encoding::IBM866
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+ it "returns nil" do
+ @io = new_io @name, @object
+ @io.internal_encoding.should be_nil
+ end
+
+ it "returns nil regardless of Encoding.default_internal changes" do
+ @io = new_io @name, @object
+ Encoding.default_internal = Encoding::IBM437
+ @io.internal_encoding.should be_nil
+ end
+ end
+
+ describe "when Encoding.default_internal != Encoding.default_external" do
+ before :each do
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+ it "returns the value of Encoding.default_internal when the instance was created if the internal encoding is not set" do
+ @io = new_io @name, @object
+ @io.internal_encoding.should equal(Encoding::IBM866)
+ end
+
+ it "does not change when Encoding.default_internal is changed" do
+ @io = new_io @name, @object
+ Encoding.default_internal = Encoding::IBM437
+ @io.internal_encoding.should equal(Encoding::IBM866)
+ end
+
+ it "returns the internal encoding set when the instance was created" do
+ @io = new_io @name, "#{@object}:utf-8:euc-jp"
+ @io.internal_encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "does not change when set and Encoding.default_internal is changed" do
+ @io = new_io @name, "#{@object}:utf-8:euc-jp"
+ Encoding.default_internal = Encoding::IBM437
+ @io.internal_encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "returns the value set by #set_encoding" do
+ @io = new_io @name, @object
+ @io.set_encoding(Encoding::US_ASCII, Encoding::IBM437)
+ @io.internal_encoding.should equal(Encoding::IBM437)
+ end
+
+ it "returns nil when Encoding.default_external is BINARY and the internal encoding is not set" do
+ Encoding.default_external = Encoding::BINARY
+ @io = new_io @name, @object
+ @io.internal_encoding.should be_nil
+ end
+
+ it "returns nil when the external encoding is BINARY and the internal encoding is not set" do
+ @io = new_io @name, "#{@object}:binary"
+ @io.internal_encoding.should be_nil
+ end
+ end
+end
+
+describe "IO#internal_encoding" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ @name = tmp("io_internal_encoding")
+ touch(@name)
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ ruby_version_is '3.1' do
+ it "can be retrieved from a closed stream" do
+ io = IOSpecs.io_fixture("lines.txt", "r")
+ io.close
+ io.internal_encoding.should equal(Encoding.default_internal)
+ end
+ end
+
+ describe "with 'r' mode" do
+ it_behaves_like :io_internal_encoding, nil, "r"
+ end
+
+ describe "with 'r+' mode" do
+ it_behaves_like :io_internal_encoding, nil, "r+"
+ end
+
+ describe "with 'w' mode" do
+ it_behaves_like :io_internal_encoding, nil, "w"
+ end
+
+ describe "with 'w+' mode" do
+ it_behaves_like :io_internal_encoding, nil, "w+"
+ end
+
+ describe "with 'a' mode" do
+ it_behaves_like :io_internal_encoding, nil, "a"
+ end
+
+ describe "with 'a+' mode" do
+ it_behaves_like :io_internal_encoding, nil, "a+"
+ end
+end
diff --git a/spec/ruby/core/io/io_spec.rb b/spec/ruby/core/io/io_spec.rb
new file mode 100644
index 0000000000..0feb1a8774
--- /dev/null
+++ b/spec/ruby/core/io/io_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "IO" do
+ it "includes File::Constants" do
+ IO.include?(File::Constants).should == true
+ end
+
+ it "includes Enumerable" do
+ IO.include?(Enumerable).should == true
+ end
+end
diff --git a/spec/ruby/core/io/ioctl_spec.rb b/spec/ruby/core/io/ioctl_spec.rb
new file mode 100644
index 0000000000..8dcd9eb2c6
--- /dev/null
+++ b/spec/ruby/core/io/ioctl_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#ioctl" do
+ platform_is_not :windows do
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.ioctl(5, 5) }.should raise_error(IOError)
+ end
+ end
+
+ platform_is :linux do
+ guard -> { RUBY_PLATFORM.include?("86") } do # x86 / x86_64
+ it "resizes an empty String to match the output size" do
+ File.open(__FILE__, 'r') do |f|
+ buffer = ''
+ # FIONREAD in /usr/include/asm-generic/ioctls.h
+ f.ioctl 0x541B, buffer
+ buffer.unpack('I').first.should be_kind_of(Integer)
+ end
+ end
+ end
+
+ it "raises a system call error when ioctl fails" do
+ File.open(__FILE__, 'r') do |f|
+ -> {
+ # TIOCGWINSZ in /usr/include/asm-generic/ioctls.h
+ f.ioctl 0x5413, nil
+ }.should raise_error(SystemCallError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/isatty_spec.rb b/spec/ruby/core/io/isatty_spec.rb
new file mode 100644
index 0000000000..3b6c69b190
--- /dev/null
+++ b/spec/ruby/core/io/isatty_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/tty'
+
+describe "IO#isatty" do
+ it_behaves_like :io_tty, :isatty
+end
diff --git a/spec/ruby/core/io/lineno_spec.rb b/spec/ruby/core/io/lineno_spec.rb
new file mode 100644
index 0000000000..9a4ad90880
--- /dev/null
+++ b/spec/ruby/core/io/lineno_spec.rb
@@ -0,0 +1,136 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#lineno" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "raises an IOError on a closed stream" do
+ -> { IOSpecs.closed_io.lineno }.should raise_error(IOError)
+ end
+
+ it "raises an IOError on a write-only stream" do
+ name = tmp("io_lineno.txt")
+ begin
+ File.open(name, 'w') do |f|
+ -> { f.lineno }.should raise_error(IOError)
+ end
+ ensure
+ rm_r name
+ end
+ end
+
+ it "raises an IOError on a duplexed stream with the read side closed" do
+ IO.popen('cat', 'r+') do |p|
+ p.close_read
+ -> { p.lineno }.should raise_error(IOError)
+ end
+ end
+
+ it "returns the current line number" do
+ @io.lineno.should == 0
+
+ count = 0
+ while @io.gets
+ @io.lineno.should == count += 1
+ end
+
+ @io.rewind
+ @io.lineno.should == 0
+ end
+end
+
+describe "IO#lineno=" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "raises an IOError on a closed stream" do
+ -> { IOSpecs.closed_io.lineno = 5 }.should raise_error(IOError)
+ end
+
+ it "raises an IOError on a write-only stream" do
+ name = tmp("io_lineno.txt")
+ begin
+ File.open(name, 'w') do |f|
+ -> { f.lineno = 0 }.should raise_error(IOError)
+ end
+ ensure
+ rm_r name
+ end
+ end
+
+ it "raises an IOError on a duplexed stream with the read side closed" do
+ IO.popen('cat', 'r+') do |p|
+ p.close_read
+ -> { p.lineno = 0 }.should raise_error(IOError)
+ end
+ end
+
+ it "calls #to_int on a non-numeric argument" do
+ obj = mock('123')
+ obj.should_receive(:to_int).and_return(123)
+
+ @io.lineno = obj
+ @io.lineno.should == 123
+ end
+
+ it "truncates a Float argument" do
+ @io.lineno = 1.5
+ @io.lineno.should == 1
+
+ @io.lineno = 92233.72036854775808
+ @io.lineno.should == 92233
+ end
+
+ it "raises TypeError if cannot convert argument to Integer implicitly" do
+ -> { @io.lineno = "1" }.should raise_error(TypeError, 'no implicit conversion of String into Integer')
+ -> { @io.lineno = nil }.should raise_error(TypeError, 'no implicit conversion from nil to integer')
+ end
+
+ it "does not accept Integers that don't fit in a C int" do
+ -> { @io.lineno = 2**32 }.should raise_error(RangeError)
+ end
+
+ it "sets the current line number to the given value" do
+ @io.lineno = count = 500
+
+ while @io.gets
+ @io.lineno.should == count += 1
+ end
+
+ @io.rewind
+ @io.lineno.should == 0
+ end
+
+ it "does not change $." do
+ original_line = $.
+ numbers = [-2**30, -2**16, -2**8, -100, -10, -1, 0, 1, 10, 2**8, 2**16, 2**30]
+ numbers.each do |num|
+ @io.lineno = num
+ @io.lineno.should == num
+ $..should == original_line
+ end
+ end
+
+ it "does not change $. until next read" do
+ $. = 0
+ $..should == 0
+
+ @io.lineno = count = 500
+ $..should == 0
+
+ while @io.gets
+ $..should == count += 1
+ end
+ end
+end
diff --git a/spec/ruby/core/io/lines_spec.rb b/spec/ruby/core/io/lines_spec.rb
new file mode 100644
index 0000000000..5b29a1d07e
--- /dev/null
+++ b/spec/ruby/core/io/lines_spec.rb
@@ -0,0 +1,46 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is ''...'3.0' do
+ describe "IO#lines" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ @io.close if @io
+ end
+
+ it "returns an Enumerator" do
+ @io.lines.should be_an_instance_of(Enumerator)
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ @io.lines.should be_an_instance_of(Enumerator)
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @io.lines.size.should == nil
+ end
+ end
+ end
+ end
+
+ it "returns a line when accessed" do
+ enum = @io.lines
+ enum.first.should == IOSpecs.lines[0]
+ end
+
+ it "yields each line to the passed block" do
+ ScratchPad.record []
+ @io.lines { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines
+ end
+ end
+end
diff --git a/spec/ruby/core/io/new_spec.rb b/spec/ruby/core/io/new_spec.rb
new file mode 100644
index 0000000000..0ef30991fd
--- /dev/null
+++ b/spec/ruby/core/io/new_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'shared/new'
+
+# NOTE: should be syncronized with library/stringio/initialize_spec.rb
+
+describe "IO.new" do
+ it_behaves_like :io_new, :new
+end
+
+describe "IO.new" do
+ it_behaves_like :io_new_errors, :new
+end
diff --git a/spec/ruby/core/io/nonblock_spec.rb b/spec/ruby/core/io/nonblock_spec.rb
new file mode 100644
index 0000000000..e81ac10c58
--- /dev/null
+++ b/spec/ruby/core/io/nonblock_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ describe "IO#nonblock?" do
+ before :all do
+ require 'io/nonblock'
+ end
+
+ it "returns false for a file by default" do
+ File.open(__FILE__) do |f|
+ f.nonblock?.should == false
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "returns false for pipe by default" do
+ r, w = IO.pipe
+ begin
+ r.nonblock?.should == false
+ w.nonblock?.should == false
+ ensure
+ r.close
+ w.close
+ end
+ end
+
+ it "returns false for socket by default" do
+ require 'socket'
+ TCPServer.open(0) do |socket|
+ socket.nonblock?.should == false
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "returns true for pipe by default" do
+ r, w = IO.pipe
+ begin
+ r.nonblock?.should == true
+ w.nonblock?.should == true
+ ensure
+ r.close
+ w.close
+ end
+ end
+
+ it "returns true for socket by default" do
+ require 'socket'
+ TCPServer.open(0) do |socket|
+ socket.nonblock?.should == true
+ end
+ end
+ end
+ end
+
+ describe "IO#nonblock=" do
+ before :all do
+ require 'io/nonblock'
+ end
+
+ it "changes the IO to non-blocking mode" do
+ File.open(__FILE__) do |f|
+ f.nonblock = true
+ f.nonblock?.should == true
+ f.nonblock = false
+ f.nonblock?.should == false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/open_spec.rb b/spec/ruby/core/io/open_spec.rb
new file mode 100644
index 0000000000..d3a3961df7
--- /dev/null
+++ b/spec/ruby/core/io/open_spec.rb
@@ -0,0 +1,86 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/new'
+
+describe "IO.open" do
+ it_behaves_like :io_new, :open
+end
+
+describe "IO.open" do
+ it_behaves_like :io_new_errors, :open
+end
+
+# These specs use a special mock helper to avoid mock
+# methods from preventing IO#close from running and
+# which would prevent the file referenced by @fd from
+# being deleted on Windows.
+
+describe "IO.open" do
+ before :each do
+ @name = tmp("io_open.txt")
+ @fd = new_fd @name
+ ScratchPad.clear
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "calls #close after yielding to the block" do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ ScratchPad.record :called
+ end
+ io.closed?.should be_false
+ end
+ ScratchPad.recorded.should == :called
+ end
+
+ it "propagates an exception raised by #close that is not a StandardError" do
+ -> do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ ScratchPad.record :called
+ raise Exception
+ end
+ end
+ end.should raise_error(Exception)
+ ScratchPad.recorded.should == :called
+ end
+
+ it "propagates an exception raised by #close that is a StandardError" do
+ -> do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ ScratchPad.record :called
+ raise StandardError
+ end
+ end
+ end.should raise_error(StandardError)
+ ScratchPad.recorded.should == :called
+ end
+
+ it "does not propagate an IOError with 'closed stream' message raised by #close" do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ ScratchPad.record :called
+ raise IOError, 'closed stream'
+ end
+ end
+ ScratchPad.recorded.should == :called
+ end
+
+ it "does not set last error when an IOError with 'closed stream' raised by #close" do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ raise IOError, 'closed stream'
+ end
+ end
+ $!.should == nil
+ end
+end
diff --git a/spec/ruby/core/io/output_spec.rb b/spec/ruby/core/io/output_spec.rb
new file mode 100644
index 0000000000..2aafb305f4
--- /dev/null
+++ b/spec/ruby/core/io/output_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#<<" do
+ it "writes an object to the IO stream" do
+ -> {
+ $stderr << "Oh noes, an error!"
+ }.should output_to_fd("Oh noes, an error!", $stderr)
+ end
+
+ it "calls #to_s on the object to print it" do
+ -> {
+ $stderr << 1337
+ }.should output_to_fd("1337", $stderr)
+ end
+
+ it "raises an error if the stream is closed" do
+ io = IOSpecs.closed_io
+ -> { io << "test" }.should raise_error(IOError)
+ end
+
+ it "returns self" do
+ -> {
+ ($stderr << "to_stderr").should == $stderr
+ }.should output(nil, "to_stderr")
+ end
+end
diff --git a/spec/ruby/core/io/path_spec.rb b/spec/ruby/core/io/path_spec.rb
new file mode 100644
index 0000000000..8145c32f39
--- /dev/null
+++ b/spec/ruby/core/io/path_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "IO#path" do
+ ruby_version_is "3.2" do
+ it "returns the path of the file associated with the IO object" do
+ path = tmp("io_path.txt")
+ File.open(path, "w") do |file|
+ IO.new(file.fileno, path: file.path, autoclose: false).path.should == file.path
+ end
+ ensure
+ File.unlink(path)
+ end
+ end
+end
diff --git a/spec/ruby/core/io/pid_spec.rb b/spec/ruby/core/io/pid_spec.rb
new file mode 100644
index 0000000000..bc09fe7c3b
--- /dev/null
+++ b/spec/ruby/core/io/pid_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#pid" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "returns nil for IO not associated with a process" do
+ @io.pid.should == nil
+ end
+end
+
+describe "IO#pid" do
+ before :each do
+ @io = IO.popen ruby_cmd('STDIN.read'), "r+"
+ end
+
+ after :each do
+ @io.close if @io && !@io.closed?
+ end
+
+ it "returns the ID of a process associated with stream" do
+ @io.pid.should_not be_nil
+ end
+
+ it "raises an IOError on closed stream" do
+ @io.close
+ -> { @io.pid }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/pipe_spec.rb b/spec/ruby/core/io/pipe_spec.rb
new file mode 100644
index 0000000000..aee0d9003f
--- /dev/null
+++ b/spec/ruby/core/io/pipe_spec.rb
@@ -0,0 +1,225 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO.pipe" do
+ after :each do
+ @r.close if @r && !@r.closed?
+ @w.close if @w && !@w.closed?
+ end
+
+ it "creates a two-ended pipe" do
+ @r, @w = IO.pipe
+ @w.puts "test_create_pipe\\n"
+ @w.close
+ @r.read(16).should == "test_create_pipe"
+ end
+
+ it "returns two IO objects" do
+ @r, @w = IO.pipe
+ @r.should be_kind_of(IO)
+ @w.should be_kind_of(IO)
+ end
+
+ it "returns instances of a subclass when called on a subclass" do
+ @r, @w = IOSpecs::SubIO.pipe
+ @r.should be_an_instance_of(IOSpecs::SubIO)
+ @w.should be_an_instance_of(IOSpecs::SubIO)
+ end
+
+ it "does not use IO.new method to create pipes and allows its overriding" do
+ ScratchPad.record []
+
+ # so redefined .new is not called, but original #initialize is
+ @r, @w = IOSpecs::SubIOWithRedefinedNew.pipe
+ ScratchPad.recorded.should == [:call_original_initialize, :call_original_initialize] # called 2 times - for each pipe (r and w)
+
+ @r.should be_an_instance_of(IOSpecs::SubIOWithRedefinedNew)
+ @w.should be_an_instance_of(IOSpecs::SubIOWithRedefinedNew)
+ end
+end
+
+describe "IO.pipe" do
+ describe "passed a block" do
+ it "yields two IO objects" do
+ IO.pipe do |r, w|
+ r.should be_kind_of(IO)
+ w.should be_kind_of(IO)
+ end
+ end
+
+ it "returns the result of the block" do
+ IO.pipe { |r, w| :result }.should == :result
+ end
+
+ it "closes both IO objects" do
+ r, w = IO.pipe do |_r, _w|
+ [_r, _w]
+ end
+ r.should.closed?
+ w.should.closed?
+ end
+
+ it "closes both IO objects when the block raises" do
+ r = w = nil
+ -> do
+ IO.pipe do |_r, _w|
+ r = _r
+ w = _w
+ raise RuntimeError
+ end
+ end.should raise_error(RuntimeError)
+ r.should.closed?
+ w.should.closed?
+ end
+
+ it "allows IO objects to be closed within the block" do
+ r, w = IO.pipe do |_r, _w|
+ _r.close
+ _w.close
+ [_r, _w]
+ end
+ r.should.closed?
+ w.should.closed?
+ end
+ end
+end
+
+describe "IO.pipe" do
+ before :each do
+ @default_external = Encoding.default_external
+ @default_internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_external = @default_external
+ Encoding.default_internal = @default_internal
+ end
+
+ it "sets the external encoding of the read end to the default when passed no arguments" do
+ Encoding.default_external = Encoding::ISO_8859_1
+
+ IO.pipe do |r, w|
+ r.external_encoding.should == Encoding::ISO_8859_1
+ r.internal_encoding.should be_nil
+ end
+ end
+
+ it "sets the internal encoding of the read end to the default when passed no arguments" do
+ Encoding.default_external = Encoding::ISO_8859_1
+ Encoding.default_internal = Encoding::UTF_8
+
+ IO.pipe do |r, w|
+ r.external_encoding.should == Encoding::ISO_8859_1
+ r.internal_encoding.should == Encoding::UTF_8
+ end
+ end
+
+ it "sets the internal encoding to nil if the same as the external" do
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = Encoding::UTF_8
+
+ IO.pipe do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should be_nil
+ end
+ end
+
+ it "sets the external encoding of the read end when passed an Encoding argument" do
+ IO.pipe(Encoding::UTF_8) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should be_nil
+ end
+ end
+
+ it "sets the external and internal encodings of the read end when passed two Encoding arguments" do
+ IO.pipe(Encoding::UTF_8, Encoding::UTF_16BE) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::UTF_16BE
+ end
+ end
+
+ it "sets the external encoding of the read end when passed the name of an Encoding" do
+ IO.pipe("UTF-8") do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should be_nil
+ end
+ end
+
+ it "accepts 'bom|' prefix for external encoding" do
+ IO.pipe("BOM|UTF-8") do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should be_nil
+ end
+ end
+
+ it "sets the external and internal encodings specified as a String and separated with a colon" do
+ IO.pipe("UTF-8:ISO-8859-1") do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::ISO_8859_1
+ end
+ end
+
+ it "accepts 'bom|' prefix for external encoding when specifying 'external:internal'" do
+ IO.pipe("BOM|UTF-8:ISO-8859-1") do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::ISO_8859_1
+ end
+ end
+
+ it "sets the external and internal encoding when passed two String arguments" do
+ IO.pipe("UTF-8", "UTF-16BE") do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::UTF_16BE
+ end
+ end
+
+ it "accepts an options Hash with one String encoding argument" do
+ IO.pipe("BOM|UTF-8:ISO-8859-1", invalid: :replace) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::ISO_8859_1
+ end
+ end
+
+ it "accepts an options Hash with two String encoding arguments" do
+ IO.pipe("UTF-8", "ISO-8859-1", invalid: :replace) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::ISO_8859_1
+ end
+ end
+
+ it "calls #to_hash to convert an options argument" do
+ options = mock("io pipe encoding options")
+ options.should_receive(:to_hash).and_return({ invalid: :replace })
+ IO.pipe("UTF-8", "ISO-8859-1", **options) { |r, w| }
+ end
+
+ it "calls #to_str to convert the first argument to a String" do
+ obj = mock("io_pipe_encoding")
+ obj.should_receive(:to_str).and_return("UTF-8:UTF-16BE")
+ IO.pipe(obj) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::UTF_16BE
+ end
+ end
+
+ it "calls #to_str to convert the second argument to a String" do
+ obj = mock("io_pipe_encoding")
+ obj.should_receive(:to_str).at_least(1).times.and_return("UTF-16BE")
+ IO.pipe(Encoding::UTF_8, obj) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::UTF_16BE
+ end
+ end
+
+ it "sets no external encoding for the write end" do
+ IO.pipe(Encoding::UTF_8) do |r, w|
+ w.external_encoding.should be_nil
+ end
+ end
+
+ it "sets no internal encoding for the write end" do
+ IO.pipe(Encoding::UTF_8) do |r, w|
+ w.external_encoding.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/io/popen_spec.rb b/spec/ruby/core/io/popen_spec.rb
new file mode 100644
index 0000000000..e9d32c5c7d
--- /dev/null
+++ b/spec/ruby/core/io/popen_spec.rb
@@ -0,0 +1,271 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../process/fixtures/common'
+
+describe "IO.popen" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :each do
+ @fname = tmp("IO_popen_spec")
+ @io = nil
+ @var = "$FOO"
+ platform_is :windows do
+ @var = "%FOO%"
+ end
+ end
+
+ after :each do
+ @io.close if @io and !@io.closed?
+ rm_r @fname
+ end
+
+ it "returns an open IO" do
+ @io = IO.popen(ruby_cmd('exit'), "r")
+ @io.closed?.should be_false
+ end
+
+ it "reads a read-only pipe" do
+ @io = IO.popen('echo foo', "r")
+ @io.read.should == "foo\n"
+ end
+
+ it "raises IOError when writing a read-only pipe" do
+ @io = IO.popen('echo foo', "r")
+ -> { @io.write('bar') }.should raise_error(IOError)
+ @io.read.should == "foo\n"
+ end
+
+ it "sees an infinitely looping subprocess exit when read pipe is closed" do
+ io = IO.popen ruby_cmd('r = loop{puts "y"; 0} rescue 1; exit r'), 'r'
+ io.close
+
+ $?.exitstatus.should_not == 0
+ end
+
+ it "writes to a write-only pipe" do
+ @io = IO.popen(ruby_cmd('IO.copy_stream(STDIN,STDOUT)', args: "> #{@fname}"), "w")
+ @io.write("bar")
+ @io.close
+
+ File.read(@fname).should == "bar"
+ end
+
+ it "raises IOError when reading a write-only pipe" do
+ @io = IO.popen(ruby_cmd('IO.copy_stream(STDIN,STDOUT)'), "w")
+ -> { @io.read }.should raise_error(IOError)
+ end
+
+ it "reads and writes a read/write pipe" do
+ @io = IO.popen(ruby_cmd('IO.copy_stream(STDIN,STDOUT)'), "r+")
+ @io.write("bar")
+ @io.read(3).should == "bar"
+ end
+
+ it "waits for the child to finish" do
+ @io = IO.popen(ruby_cmd('IO.copy_stream(STDIN,STDOUT)', args: "> #{@fname}"), "w")
+ @io.write("bar")
+ @io.close
+
+ $?.exitstatus.should == 0
+
+ File.read(@fname).should == "bar"
+ end
+
+ it "does not throw an exception if child exited and has been waited for" do
+ @io = IO.popen([*ruby_exe, '-e', 'sleep'])
+ pid = @io.pid
+ Process.kill "KILL", pid
+ @io.close
+ platform_is_not :windows do
+ $?.should.signaled?
+ end
+ platform_is :windows do
+ $?.should.exited?
+ end
+ end
+
+ it "returns an instance of a subclass when called on a subclass" do
+ @io = IOSpecs::SubIO.popen(ruby_cmd('exit'), "r")
+ @io.should be_an_instance_of(IOSpecs::SubIO)
+ end
+
+ it "coerces mode argument with #to_str" do
+ mode = mock("mode")
+ mode.should_receive(:to_str).and_return("r")
+ @io = IO.popen(ruby_cmd('exit 0'), mode)
+ end
+
+ describe "with a block" do
+ it "yields an open IO to the block" do
+ IO.popen(ruby_cmd('exit'), "r") do |io|
+ io.closed?.should be_false
+ end
+ end
+
+ it "yields an instance of a subclass when called on a subclass" do
+ IOSpecs::SubIO.popen(ruby_cmd('exit'), "r") do |io|
+ io.should be_an_instance_of(IOSpecs::SubIO)
+ end
+ end
+
+ it "closes the IO after yielding" do
+ io = IO.popen(ruby_cmd('exit'), "r") { |_io| _io }
+ io.closed?.should be_true
+ end
+
+ it "allows the IO to be closed inside the block" do
+ io = IO.popen(ruby_cmd('exit'), 'r') { |_io| _io.close; _io }
+ io.closed?.should be_true
+ end
+
+ it "returns the value of the block" do
+ IO.popen(ruby_cmd('exit'), "r") { :hello }.should == :hello
+ end
+ end
+
+ platform_is_not :windows do
+ it "starts returns a forked process if the command is -" do
+ io = IO.popen("-")
+
+ if io # parent
+ begin
+ io.gets.should == "hello from child\n"
+ ensure
+ io.close
+ end
+ else # child
+ puts "hello from child"
+ exit!
+ end
+ end
+ end
+
+ it "has the given external encoding" do
+ @io = IO.popen(ruby_cmd('exit'), external_encoding: Encoding::EUC_JP)
+ @io.external_encoding.should == Encoding::EUC_JP
+ end
+
+ it "has the given internal encoding" do
+ @io = IO.popen(ruby_cmd('exit'), internal_encoding: Encoding::EUC_JP)
+ @io.internal_encoding.should == Encoding::EUC_JP
+ end
+
+ it "sets the internal encoding to nil if it's the same as the external encoding" do
+ @io = IO.popen(ruby_cmd('exit'), external_encoding: Encoding::EUC_JP,
+ internal_encoding: Encoding::EUC_JP)
+ @io.internal_encoding.should be_nil
+ end
+
+ context "with a leading ENV Hash" do
+ it "accepts a single String command" do
+ IO.popen({"FOO" => "bar"}, "echo #{@var}") do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts a single String command, and an IO mode" do
+ IO.popen({"FOO" => "bar"}, "echo #{@var}", "r") do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts a single String command with a trailing Hash of Process.exec options" do
+ IO.popen({"FOO" => "bar"}, ruby_cmd('STDERR.puts ENV["FOO"]'),
+ err: [:child, :out]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts a single String command with a trailing Hash of Process.exec options, and an IO mode" do
+ IO.popen({"FOO" => "bar"}, ruby_cmd('STDERR.puts ENV["FOO"]'), "r",
+ err: [:child, :out]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts an Array of command and arguments" do
+ exe, *args = ruby_exe
+ IO.popen({"FOO" => "bar"}, [[exe, "specfu"], *args, "-e", "puts ENV['FOO']"]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts an Array of command and arguments, and an IO mode" do
+ exe, *args = ruby_exe
+ IO.popen({"FOO" => "bar"}, [[exe, "specfu"], *args, "-e", "puts ENV['FOO']"], "r") do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts an Array command with a separate trailing Hash of Process.exec options" do
+ IO.popen({"FOO" => "bar"}, [*ruby_exe, "-e", "STDERR.puts ENV['FOO']"],
+ err: [:child, :out]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts an Array command with a separate trailing Hash of Process.exec options, and an IO mode" do
+ IO.popen({"FOO" => "bar"}, [*ruby_exe, "-e", "STDERR.puts ENV['FOO']"],
+ "r", err: [:child, :out]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+ end
+
+ context "with a leading Array argument" do
+ it "uses the Array as command plus args for the child process" do
+ IO.popen([*ruby_exe, "-e", "puts 'hello'"]) do |io|
+ io.read.should == "hello\n"
+ end
+ end
+
+ it "accepts a leading ENV Hash" do
+ IO.popen([{"FOO" => "bar"}, *ruby_exe, "-e", "puts ENV['FOO']"]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts a trailing Hash of Process.exec options" do
+ IO.popen([*ruby_exe, "does_not_exist", {err: [:child, :out]}]) do |io|
+ io.read.should =~ /LoadError/
+ end
+ end
+
+ it "accepts an IO mode argument following the Array" do
+ IO.popen([*ruby_exe, "does_not_exist", {err: [:child, :out]}], "r") do |io|
+ io.read.should =~ /LoadError/
+ end
+ end
+
+ it "accepts [env, command, arg1, arg2, ..., exec options]" do
+ IO.popen([{"FOO" => "bar"}, *ruby_exe, "-e", "STDERR.puts ENV[:FOO.to_s]",
+ err: [:child, :out]]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts '[env, command, arg1, arg2, ..., exec options], mode'" do
+ IO.popen([{"FOO" => "bar"}, *ruby_exe, "-e", "STDERR.puts ENV[:FOO.to_s]",
+ err: [:child, :out]], "r") do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts '[env, command, arg1, arg2, ..., exec options], mode, IO options'" do
+ IO.popen([{"FOO" => "bar"}, *ruby_exe, "-e", "STDERR.puts ENV[:FOO.to_s]",
+ err: [:child, :out]], "r",
+ internal_encoding: Encoding::EUC_JP) do |io|
+ io.read.should == "bar\n"
+ io.internal_encoding.should == Encoding::EUC_JP
+ end
+ end
+
+ it "accepts '[env, command, arg1, arg2, ...], mode, IO + exec options'" do
+ IO.popen([{"FOO" => "bar"}, *ruby_exe, "-e", "STDERR.puts ENV[:FOO.to_s]"], "r",
+ err: [:child, :out], internal_encoding: Encoding::EUC_JP) do |io|
+ io.read.should == "bar\n"
+ io.internal_encoding.should == Encoding::EUC_JP
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/pos_spec.rb b/spec/ruby/core/io/pos_spec.rb
new file mode 100644
index 0000000000..e6cda2643d
--- /dev/null
+++ b/spec/ruby/core/io/pos_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/pos'
+
+describe "IO#pos" do
+ it_behaves_like :io_pos, :pos
+end
+
+describe "IO#pos=" do
+ it_behaves_like :io_set_pos, :pos=
+end
diff --git a/spec/ruby/core/io/pread_spec.rb b/spec/ruby/core/io/pread_spec.rb
new file mode 100644
index 0000000000..43071d6a31
--- /dev/null
+++ b/spec/ruby/core/io/pread_spec.rb
@@ -0,0 +1,50 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ describe "IO#pread" do
+ before :each do
+ @fname = tmp("io_pread.txt")
+ @contents = "1234567890"
+ touch(@fname) { |f| f.write @contents }
+ @file = File.open(@fname, "r+")
+ end
+
+ after :each do
+ @file.close
+ rm_r @fname
+ end
+
+ it "accepts a length, and an offset" do
+ @file.pread(4, 0).should == "1234"
+ @file.pread(3, 4).should == "567"
+ end
+
+ it "accepts a length, an offset, and an output buffer" do
+ buffer = "foo"
+ @file.pread(3, 4, buffer)
+ buffer.should == "567"
+ end
+
+ it "does not advance the file pointer" do
+ @file.pread(4, 0).should == "1234"
+ @file.read.should == "1234567890"
+ end
+
+ it "raises EOFError if end-of-file is reached" do
+ -> { @file.pread(1, 10) }.should raise_error(EOFError)
+ end
+
+ it "raises IOError when file is not open in read mode" do
+ File.open(@fname, "w") do |file|
+ -> { file.pread(1, 1) }.should raise_error(IOError)
+ end
+ end
+
+ it "raises IOError when file is closed" do
+ file = File.open(@fname, "r+")
+ file.close
+ -> { file.pread(1, 1) }.should raise_error(IOError)
+ end
+ end
+end
diff --git a/spec/ruby/core/io/print_spec.rb b/spec/ruby/core/io/print_spec.rb
new file mode 100644
index 0000000000..085852024c
--- /dev/null
+++ b/spec/ruby/core/io/print_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#print" do
+ before :each do
+ @old_record_separator = $\
+ @old_field_separator = $,
+ suppress_warning {
+ $\ = '->'
+ $, = '^^'
+ }
+ @name = tmp("io_print")
+ end
+
+ after :each do
+ suppress_warning {
+ $\ = @old_record_separator
+ $, = @old_field_separator
+ }
+ rm_r @name
+ end
+
+ it "returns nil" do
+ touch(@name) { |f| f.print.should be_nil }
+ end
+
+ it "writes $_.to_s followed by $\\ (if any) to the stream if no arguments given" do
+ o = mock('o')
+ o.should_receive(:to_s).and_return("mockmockmock")
+ $_ = o
+
+ touch(@name) { |f| f.print }
+ IO.read(@name).should == "mockmockmock#{$\}"
+
+ # Set $_ to something known
+ string = File.open(__FILE__) {|f| f.gets }
+
+ touch(@name) { |f| f.print }
+ IO.read(@name).should == "#{string}#{$\}"
+ end
+
+ it "calls obj.to_s and not obj.to_str then writes the record separator" do
+ o = mock('o')
+ o.should_not_receive(:to_str)
+ o.should_receive(:to_s).and_return("hello")
+
+ touch(@name) { |f| f.print(o) }
+
+ IO.read(@name).should == "hello#{$\}"
+ end
+
+ it "writes each obj.to_s to the stream separated by $, (if any) and appends $\\ (if any) given multiple objects" do
+ o, o2 = Object.new, Object.new
+ def o.to_s(); 'o'; end
+ def o2.to_s(); 'o2'; end
+
+ suppress_warning {
+ touch(@name) { |f| f.print(o, o2) }
+ }
+ IO.read(@name).should == "#{o.to_s}#{$,}#{o2.to_s}#{$\}"
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.print("stuff") }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/printf_spec.rb b/spec/ruby/core/io/printf_spec.rb
new file mode 100644
index 0000000000..baa00f14ce
--- /dev/null
+++ b/spec/ruby/core/io/printf_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#printf" do
+ before :each do
+ @name = tmp("io_printf.txt")
+ @io = new_io @name
+ @io.sync = true
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ end
+
+ it "calls #to_str to convert the format object to a String" do
+ obj = mock("printf format")
+ obj.should_receive(:to_str).and_return("%s")
+
+ @io.printf obj, "printf"
+ File.read(@name).should == "printf"
+ end
+
+ it "writes the #sprintf formatted string" do
+ @io.printf "%d %s", 5, "cookies"
+ File.read(@name).should == "5 cookies"
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.printf("stuff") }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/putc_spec.rb b/spec/ruby/core/io/putc_spec.rb
new file mode 100644
index 0000000000..73473ad821
--- /dev/null
+++ b/spec/ruby/core/io/putc_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/io/putc'
+
+describe "IO#putc" do
+ before :each do
+ @name = tmp("io_putc.txt")
+ @io_object = @io = new_io(@name)
+ end
+
+ it_behaves_like :io_putc, :putc
+end
diff --git a/spec/ruby/core/io/puts_spec.rb b/spec/ruby/core/io/puts_spec.rb
new file mode 100644
index 0000000000..9a708fffef
--- /dev/null
+++ b/spec/ruby/core/io/puts_spec.rb
@@ -0,0 +1,139 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#puts" do
+ before :each do
+ @before_separator = $/
+ @name = tmp("io_puts.txt")
+ @io = new_io @name
+ ScratchPad.record ""
+ def @io.write(str)
+ ScratchPad << str
+ end
+ end
+
+ after :each do
+ ScratchPad.clear
+ @io.close if @io
+ rm_r @name
+ suppress_warning {$/ = @before_separator}
+ end
+
+ it "writes just a newline when given no args" do
+ @io.puts.should == nil
+ ScratchPad.recorded.should == "\n"
+ end
+
+ it "writes just a newline when given just a newline" do
+ -> { $stdout.puts "\n" }.should output_to_fd("\n", $stdout)
+ end
+
+ it "writes empty string with a newline when given nil as an arg" do
+ @io.puts(nil).should == nil
+ ScratchPad.recorded.should == "\n"
+ end
+
+ it "writes empty string with a newline when when given nil as multiple args" do
+ @io.puts(nil, nil).should == nil
+ ScratchPad.recorded.should == "\n\n"
+ end
+
+ it "calls :to_ary before writing non-string objects, regardless of it being implemented in the receiver" do
+ object = mock('hola')
+ object.should_receive(:method_missing).with(:to_ary)
+ object.should_receive(:to_s).and_return("#<Object:0x...>")
+
+ @io.puts(object).should == nil
+ ScratchPad.recorded.should == "#<Object:0x...>\n"
+ end
+
+ it "calls :to_ary before writing non-string objects" do
+ object = mock('hola')
+ object.should_receive(:to_ary).and_return(["hola"])
+
+ @io.puts(object).should == nil
+ ScratchPad.recorded.should == "hola\n"
+ end
+
+ it "calls :to_s before writing non-string objects that don't respond to :to_ary" do
+ object = mock('hola')
+ object.should_receive(:to_s).and_return("hola")
+
+ @io.puts(object).should == nil
+ ScratchPad.recorded.should == "hola\n"
+ end
+
+ it "returns general object info if :to_s does not return a string" do
+ object = mock('hola')
+ object.should_receive(:to_s).and_return(false)
+
+ @io.puts(object).should == nil
+ ScratchPad.recorded.should == object.inspect.split(" ")[0] + ">\n"
+ end
+
+ it "writes each arg if given several" do
+ @io.puts(1, "two", 3).should == nil
+ ScratchPad.recorded.should == "1\ntwo\n3\n"
+ end
+
+ it "flattens a nested array before writing it" do
+ @io.puts([1, 2, [3]]).should == nil
+ ScratchPad.recorded.should == "1\n2\n3\n"
+ end
+
+ it "writes nothing for an empty array" do
+ x = []
+ @io.should_not_receive(:write)
+ @io.puts(x).should == nil
+ end
+
+ it "writes [...] for a recursive array arg" do
+ x = []
+ x << 2 << x
+ @io.puts(x).should == nil
+ ScratchPad.recorded.should == "2\n[...]\n"
+ end
+
+ it "writes a newline after objects that do not end in newlines" do
+ @io.puts(5).should == nil
+ ScratchPad.recorded.should == "5\n"
+ end
+
+ it "does not write a newline after objects that end in newlines" do
+ @io.puts("5\n").should == nil
+ ScratchPad.recorded.should == "5\n"
+ end
+
+ it "ignores the $/ separator global" do
+ suppress_warning {$/ = ":"}
+ @io.puts(5).should == nil
+ ScratchPad.recorded.should == "5\n"
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.puts("stuff") }.should raise_error(IOError)
+ end
+
+ it "writes crlf when IO is opened with newline: :crlf" do
+ File.open(@name, 'wt', newline: :crlf) do |file|
+ file.puts
+ end
+ File.binread(@name).should == "\r\n"
+ end
+
+ it "writes cr when IO is opened with newline: :cr" do
+ File.open(@name, 'wt', newline: :cr) do |file|
+ file.puts
+ end
+ File.binread(@name).should == "\r"
+ end
+
+ platform_is_not :windows do # https://bugs.ruby-lang.org/issues/12436
+ it "writes lf when IO is opened with newline: :lf" do
+ File.open(@name, 'wt', newline: :lf) do |file|
+ file.puts
+ end
+ File.binread(@name).should == "\n"
+ end
+ end
+end
diff --git a/spec/ruby/core/io/pwrite_spec.rb b/spec/ruby/core/io/pwrite_spec.rb
new file mode 100644
index 0000000000..fe29d1e1f6
--- /dev/null
+++ b/spec/ruby/core/io/pwrite_spec.rb
@@ -0,0 +1,43 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ describe "IO#pwrite" do
+ before :each do
+ @fname = tmp("io_pwrite.txt")
+ @file = File.open(@fname, "w+")
+ end
+
+ after :each do
+ @file.close
+ rm_r @fname
+ end
+
+ it "returns the number of bytes written" do
+ @file.pwrite("foo", 0).should == 3
+ end
+
+ it "accepts a string and an offset" do
+ @file.pwrite("foo", 2)
+ @file.pread(3, 2).should == "foo"
+ end
+
+ it "does not advance the pointer in the file" do
+ @file.pwrite("bar", 3)
+ @file.write("foo")
+ @file.pread(6, 0).should == "foobar"
+ end
+
+ it "raises IOError when file is not open in write mode" do
+ File.open(@fname, "r") do |file|
+ -> { file.pwrite("foo", 1) }.should raise_error(IOError)
+ end
+ end
+
+ it "raises IOError when file is closed" do
+ file = File.open(@fname, "w+")
+ file.close
+ -> { file.pwrite("foo", 1) }.should raise_error(IOError)
+ end
+ end
+end
diff --git a/spec/ruby/core/io/read_nonblock_spec.rb b/spec/ruby/core/io/read_nonblock_spec.rb
new file mode 100644
index 0000000000..a62b75274c
--- /dev/null
+++ b/spec/ruby/core/io/read_nonblock_spec.rb
@@ -0,0 +1,148 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#read_nonblock" do
+ before :each do
+ @read, @write = IO.pipe
+ end
+
+ after :each do
+ @read.close if @read && !@read.closed?
+ @write.close if @write && !@write.closed?
+ end
+
+ it "raises an exception extending IO::WaitReadable when there is no data" do
+ -> { @read.read_nonblock(5) }.should raise_error(IO::WaitReadable) { |e|
+ platform_is_not :windows do
+ e.should be_kind_of(Errno::EAGAIN)
+ end
+ platform_is :windows do
+ e.should be_kind_of(Errno::EWOULDBLOCK)
+ end
+ }
+ end
+
+ context "when exception option is set to false" do
+ context "when there is no data" do
+ it "returns :wait_readable" do
+ @read.read_nonblock(5, exception: false).should == :wait_readable
+ end
+ end
+
+ context "when the end is reached" do
+ it "returns nil" do
+ @write << "hello"
+ @write.close
+
+ @read.read_nonblock(5)
+
+ @read.read_nonblock(5, exception: false).should be_nil
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ it 'sets the IO in nonblock mode' do
+ require 'io/nonblock'
+ @write.write "abc"
+ @read.read_nonblock(1).should == "a"
+ @read.should.nonblock?
+ end
+ end
+
+ it "returns at most the number of bytes requested" do
+ @write << "hello"
+ @read.read_nonblock(4).should == "hell"
+ end
+
+ it "reads after ungetc with data in the buffer" do
+ @write.write("foobar")
+ @read.set_encoding(
+ 'utf-8', universal_newline: false
+ )
+ c = @read.getc
+ @read.ungetc(c)
+ @read.read_nonblock(3).should == "foo"
+ @read.read_nonblock(3).should == "bar"
+ end
+
+ it "raises an exception after ungetc with data in the buffer and character conversion enabled" do
+ @write.write("foobar")
+ @read.set_encoding(
+ 'utf-8', universal_newline: true
+ )
+ c = @read.getc
+ @read.ungetc(c)
+ -> { @read.read_nonblock(3).should == "foo" }.should raise_error(IOError)
+ end
+
+ it "returns less data if that is all that is available" do
+ @write << "hello"
+ @read.read_nonblock(10).should == "hello"
+ end
+
+ it "allows for reading 0 bytes before any write" do
+ @read.read_nonblock(0).should == ""
+ end
+
+ it "allows for reading 0 bytes after a write" do
+ @write.write "1"
+ @read.read_nonblock(0).should == ""
+ @read.read_nonblock(1).should == "1"
+ end
+
+ it "raises ArgumentError when length is less than 0" do
+ -> { @read.read_nonblock(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "reads into the passed buffer" do
+ buffer = ""
+ @write.write("1")
+ @read.read_nonblock(1, buffer)
+ buffer.should == "1"
+ end
+
+ it "returns the passed buffer" do
+ buffer = ""
+ @write.write("1")
+ output = @read.read_nonblock(1, buffer)
+ output.should equal(buffer)
+ end
+
+ it "discards the existing buffer content upon successful read" do
+ buffer = "existing content"
+ @write.write("hello world")
+ @write.close
+ @read.read_nonblock(11, buffer)
+ buffer.should == "hello world"
+ end
+
+ it "discards the existing buffer content upon error" do
+ buffer = "existing content"
+ @write.close
+ -> { @read.read_nonblock(1, buffer) }.should raise_error(EOFError)
+ buffer.should be_empty
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.read_nonblock(5) }.should raise_error(IOError)
+ end
+
+ it "raises EOFError when the end is reached" do
+ @write << "hello"
+ @write.close
+
+ @read.read_nonblock(5)
+
+ -> { @read.read_nonblock(5) }.should raise_error(EOFError)
+ end
+
+ it "preserves the encoding of the given buffer" do
+ buffer = ''.encode(Encoding::ISO_8859_1)
+ @write.write("abc")
+ @write.close
+ @read.read_nonblock(10, buffer)
+
+ buffer.encoding.should == Encoding::ISO_8859_1
+ end
+end
diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb
new file mode 100644
index 0000000000..529afbf0ff
--- /dev/null
+++ b/spec/ruby/core/io/read_spec.rb
@@ -0,0 +1,627 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO.read" do
+ before :each do
+ @fname = tmp("io_read.txt")
+ @contents = "1234567890"
+ touch(@fname) { |f| f.write @contents }
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ it "reads the contents of a file" do
+ IO.read(@fname).should == @contents
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(@fname)
+ IO.read(p)
+ end
+
+ it "accepts an empty options Hash" do
+ IO.read(@fname, **{}).should == @contents
+ end
+
+ it "accepts a length, and empty options Hash" do
+ IO.read(@fname, 3, **{}).should == @contents[0, 3]
+ end
+
+ it "accepts a length, offset, and empty options Hash" do
+ IO.read(@fname, 3, 0, **{}).should == @contents[0, 3]
+ end
+
+ it "raises an IOError if the options Hash specifies write mode" do
+ -> { IO.read(@fname, 3, 0, mode: "w") }.should raise_error(IOError)
+ end
+
+ it "raises an IOError if the options Hash specifies append only mode" do
+ -> { IO.read(@fname, mode: "a") }.should raise_error(IOError)
+ end
+
+ it "reads the file if the options Hash includes read mode" do
+ IO.read(@fname, mode: "r").should == @contents
+ end
+
+ it "reads the file if the options Hash includes read/write mode" do
+ IO.read(@fname, mode: "r+").should == @contents
+ end
+
+ it "reads the file if the options Hash includes read/write append mode" do
+ IO.read(@fname, mode: "a+").should == @contents
+ end
+
+ it "treats second nil argument as no length limit" do
+ IO.read(@fname, nil).should == @contents
+ IO.read(@fname, nil, 5).should == IO.read(@fname, @contents.length, 5)
+ end
+
+ it "treats third nil argument as 0" do
+ IO.read(@fname, nil, nil).should == @contents
+ IO.read(@fname, 5, nil).should == IO.read(@fname, 5, 0)
+ end
+
+ it "reads the contents of a file up to a certain size when specified" do
+ IO.read(@fname, 5).should == @contents.slice(0..4)
+ end
+
+ it "reads the contents of a file from an offset of a specific size when specified" do
+ IO.read(@fname, 5, 3).should == @contents.slice(3, 5)
+ end
+
+ it "returns nil at end-of-file when length is passed" do
+ IO.read(@fname, 1, 10).should == nil
+ end
+
+ it "raises an Errno::ENOENT when the requested file does not exist" do
+ rm_r @fname
+ -> { IO.read @fname }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises a TypeError when not passed a String type" do
+ -> { IO.read nil }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError when not passed a valid length" do
+ -> { IO.read @fname, -1 }.should raise_error(ArgumentError)
+ end
+
+ it "raises an Errno::EINVAL when not passed a valid offset" do
+ -> { IO.read @fname, 0, -1 }.should raise_error(Errno::EINVAL)
+ -> { IO.read @fname, -1, -1 }.should raise_error(Errno::EINVAL)
+ end
+
+ it "uses the external encoding specified via the :external_encoding option" do
+ str = IO.read(@fname, external_encoding: Encoding::ISO_8859_1)
+ str.encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "uses the external encoding specified via the :encoding option" do
+ str = IO.read(@fname, encoding: Encoding::ISO_8859_1)
+ str.encoding.should == Encoding::ISO_8859_1
+ end
+
+ platform_is :windows do
+ it "reads the file in text mode" do
+ # 0x1A is CTRL+Z and is EOF in Windows text mode.
+ File.binwrite(@fname, "\x1Abbb")
+ IO.read(@fname).should.empty?
+ end
+ end
+end
+
+describe "IO.read from a pipe" do
+ it "runs the rest as a subprocess and returns the standard output" do
+ cmd = "|sh -c 'echo hello'"
+ platform_is :windows do
+ cmd = "|cmd.exe /C echo hello"
+ end
+ IO.read(cmd).should == "hello\n"
+ end
+
+ platform_is_not :windows do
+ it "opens a pipe to a fork if the rest is -" do
+ str = IO.read("|-")
+ if str # parent
+ str.should == "hello from child\n"
+ else #child
+ puts "hello from child"
+ exit!
+ end
+ end
+ end
+
+ it "reads only the specified number of bytes requested" do
+ cmd = "|sh -c 'echo hello'"
+ platform_is :windows do
+ cmd = "|cmd.exe /C echo hello"
+ end
+ IO.read(cmd, 1).should == "h"
+ end
+
+ platform_is_not :windows do
+ it "raises Errno::ESPIPE if passed an offset" do
+ -> {
+ IO.read("|sh -c 'echo hello'", 1, 1)
+ }.should raise_error(Errno::ESPIPE)
+ end
+ end
+
+quarantine! do # The process tried to write to a nonexistent pipe.
+ platform_is :windows do
+ # TODO: It should raise Errno::ESPIPE on Windows as well
+ # once https://bugs.ruby-lang.org/issues/12230 is fixed.
+ it "raises Errno::EINVAL if passed an offset" do
+ -> {
+ IO.read("|cmd.exe /C echo hello", 1, 1)
+ }.should raise_error(Errno::EINVAL)
+ end
+ end
+end
+end
+
+describe "IO.read on an empty file" do
+ before :each do
+ @fname = tmp("io_read_empty.txt")
+ touch(@fname)
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ it "returns nil when length is passed" do
+ IO.read(@fname, 1).should == nil
+ end
+
+ it "returns an empty string when no length is passed" do
+ IO.read(@fname).should == ""
+ end
+end
+
+describe "IO#read" do
+
+ before :each do
+ @fname = tmp("io_read.txt")
+ @contents = "1234567890"
+ touch(@fname) { |f| f.write @contents }
+
+ @io = open @fname, "r+"
+ end
+
+ after :each do
+ @io.close
+ rm_r @fname
+ end
+
+ it "can be read from consecutively" do
+ @io.read(1).should == '1'
+ @io.read(2).should == '23'
+ @io.read(3).should == '456'
+ @io.read(4).should == '7890'
+ end
+
+ it "clears the output buffer if there is nothing to read" do
+ @io.pos = 10
+
+ buf = 'non-empty string'
+
+ @io.read(10, buf).should == nil
+
+ buf.should == ''
+ end
+
+ it "consumes zero bytes when reading zero bytes" do
+ @io.read(0).should == ''
+ @io.pos.should == 0
+
+ @io.getc.chr.should == '1'
+ end
+
+ it "is at end-of-file when everything has been read" do
+ @io.read
+ @io.should.eof?
+ end
+
+ it "reads the contents of a file" do
+ @io.read.should == @contents
+ end
+
+ it "places the specified number of bytes in the buffer" do
+ buf = ""
+ @io.read 5, buf
+
+ buf.should == "12345"
+ end
+
+ it "expands the buffer when too small" do
+ buf = "ABCDE"
+ @io.read nil, buf
+
+ buf.should == @contents
+ end
+
+ it "overwrites the buffer" do
+ buf = "ABCDEFGHIJ"
+ @io.read nil, buf
+
+ buf.should == @contents
+ end
+
+ it "truncates the buffer when too big" do
+ buf = "ABCDEFGHIJKLMNO"
+ @io.read nil, buf
+ buf.should == @contents
+
+ @io.rewind
+
+ buf = "ABCDEFGHIJKLMNO"
+ @io.read 5, buf
+ buf.should == @contents[0..4]
+ end
+
+ it "returns the given buffer" do
+ buf = ""
+
+ @io.read(nil, buf).should equal buf
+ end
+
+ it "returns the given buffer when there is nothing to read" do
+ buf = ""
+
+ @io.read
+ @io.read(nil, buf).should equal buf
+ end
+
+ it "coerces the second argument to string and uses it as a buffer" do
+ buf = "ABCDE"
+ obj = mock("buff")
+ obj.should_receive(:to_str).any_number_of_times.and_return(buf)
+
+ @io.read(15, obj).should_not equal obj
+ buf.should == @contents
+ end
+
+ it "returns an empty string at end-of-file" do
+ @io.read
+ @io.read.should == ''
+ end
+
+ it "reads the contents of a file when more bytes are specified" do
+ @io.read(@contents.length + 1).should == @contents
+ end
+
+ it "returns an empty string at end-of-file" do
+ @io.read
+ @io.read.should == ''
+ end
+
+ it "returns an empty string when the current pos is bigger than the content size" do
+ @io.pos = 1000
+ @io.read.should == ''
+ end
+
+ it "returns nil at end-of-file with a length" do
+ @io.read
+ @io.read(1).should == nil
+ end
+
+ it "with length argument returns nil when the current pos is bigger than the content size" do
+ @io.pos = 1000
+ @io.read(1).should == nil
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.read }.should raise_error(IOError)
+ end
+
+ it "raises ArgumentError when length is less than 0" do
+ -> { @io.read(-1) }.should raise_error(ArgumentError)
+ end
+
+ platform_is_not :windows do
+ it "raises IOError when stream is closed by another thread" do
+ r, w = IO.pipe
+ t = Thread.new do
+ begin
+ r.read(1)
+ rescue => e
+ e
+ end
+ end
+
+ Thread.pass until t.stop?
+ r.close
+ t.join
+ t.value.should be_kind_of(IOError)
+ w.close
+ end
+ end
+end
+
+platform_is :windows do
+ describe "IO#read on Windows" do
+ before :each do
+ @fname = tmp("io_read.txt")
+ touch(@fname, "wb") { |f| f.write "a\r\nb\r\nc" }
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @fname
+ end
+
+ it "normalizes line endings in text mode" do
+ @io = new_io(@fname, "r")
+ @io.read.should == "a\nb\nc"
+ end
+
+ it "does not normalize line endings in binary mode" do
+ @io = new_io(@fname, "rb")
+ @io.read.should == "a\r\nb\r\nc"
+ end
+ end
+end
+
+describe "IO#read" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "ignores unicode encoding" do
+ @io.readline.should == "Voici la ligne une.\n"
+ # read "Qui è"
+ @io.read(5).should == "Qui " + [195].pack('C*')
+ end
+end
+
+describe "IO#read in binary mode" do
+ before :each do
+ @internal = Encoding.default_internal
+ @name = fixture __FILE__, "read_binary.txt"
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "does not transcode file contents when Encoding.default_internal is set" do
+ Encoding.default_internal = "utf-8"
+
+ result = File.open(@name, "rb") { |f| f.read }.chomp
+
+ result.encoding.should == Encoding::BINARY
+ xE2 = [226].pack('C*')
+ result.should == ("abc" + xE2 + "def").force_encoding(Encoding::BINARY)
+ end
+end
+
+describe "IO#read in text mode" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+ @name = fixture __FILE__, "read_text.txt"
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "reads data according to the internal encoding" do
+ Encoding.default_internal = "utf-8"
+ Encoding.default_external = "utf-8"
+
+ result = File.open(@name, "rt") { |f| f.read }.chomp
+
+ result.encoding.should == Encoding::UTF_8
+ result.should == "abcâdef"
+ end
+end
+
+describe "IO.read with BOM" do
+ it "reads a file without a bom" do
+ name = fixture __FILE__, "no_bom_UTF-8.txt"
+ result = File.read(name, mode: "rb:BOM|utf-8")
+ result.force_encoding("binary").should == "UTF-8\n"
+ end
+
+ it "reads a file with a utf-8 bom" do
+ name = fixture __FILE__, "bom_UTF-8.txt"
+ result = File.read(name, mode: "rb:BOM|utf-16le")
+ result.force_encoding("binary").should == "UTF-8\n"
+ end
+
+ it "reads a file with a utf-16le bom" do
+ name = fixture __FILE__, "bom_UTF-16LE.txt"
+ result = File.read(name, mode: "rb:BOM|utf-8")
+ result.force_encoding("binary").should == "U\x00T\x00F\x00-\x001\x006\x00L\x00E\x00\n\x00"
+ end
+
+ it "reads a file with a utf-16be bom" do
+ name = fixture __FILE__, "bom_UTF-16BE.txt"
+ result = File.read(name, mode: "rb:BOM|utf-8")
+ result.force_encoding("binary").should == "\x00U\x00T\x00F\x00-\x001\x006\x00B\x00E\x00\n"
+ end
+
+ it "reads a file with a utf-32le bom" do
+ name = fixture __FILE__, "bom_UTF-32LE.txt"
+ result = File.read(name, mode: "rb:BOM|utf-8")
+ result.force_encoding("binary").should == "U\x00\x00\x00T\x00\x00\x00F\x00\x00\x00-\x00\x00\x003\x00\x00\x002\x00\x00\x00L\x00\x00\x00E\x00\x00\x00\n\x00\x00\x00"
+ end
+
+ it "reads a file with a utf-32be bom" do
+ name = fixture __FILE__, "bom_UTF-32BE.txt"
+ result = File.read(name, mode: "rb:BOM|utf-8")
+ result.force_encoding("binary").should == "\x00\x00\x00U\x00\x00\x00T\x00\x00\x00F\x00\x00\x00-\x00\x00\x003\x00\x00\x002\x00\x00\x00B\x00\x00\x00E\x00\x00\x00\n"
+ end
+end
+
+describe :io_read_internal_encoding, shared: true do
+ it "returns a transcoded String" do
+ @io.read.should == "ã‚りãŒã¨ã†\n"
+ end
+
+ it "sets the String encoding to the internal encoding" do
+ @io.read.encoding.should equal(Encoding::UTF_8)
+ end
+
+ describe "when passed nil for limit" do
+ it "sets the buffer to a transcoded String" do
+ result = @io.read(nil, buf = "")
+ buf.should equal(result)
+ buf.should == "ã‚りãŒã¨ã†\n"
+ end
+
+ it "sets the buffer's encoding to the internal encoding" do
+ buf = "".force_encoding Encoding::ISO_8859_1
+ @io.read(nil, buf)
+ buf.encoding.should equal(Encoding::UTF_8)
+ end
+ end
+end
+
+describe :io_read_size_internal_encoding, shared: true do
+ it "reads bytes when passed a size" do
+ @io.read(2).should == [164, 162].pack('C*').force_encoding(Encoding::BINARY)
+ end
+
+ it "returns a String in BINARY when passed a size" do
+ @io.read(4).encoding.should equal(Encoding::BINARY)
+ end
+
+ it "does not change the buffer's encoding when passed a limit" do
+ buf = "".force_encoding Encoding::ISO_8859_1
+ @io.read(4, buf)
+ buf.should == [164, 162, 164, 234].pack('C*').force_encoding(Encoding::ISO_8859_1)
+ buf.encoding.should equal(Encoding::ISO_8859_1)
+ end
+
+ it "truncates the buffer but does not change the buffer's encoding when no data remains" do
+ buf = "abc".force_encoding Encoding::ISO_8859_1
+ @io.read
+
+ @io.read(1, buf).should be_nil
+ buf.size.should == 0
+ buf.encoding.should equal(Encoding::ISO_8859_1)
+ end
+end
+
+describe "IO#read" do
+ describe "when IO#external_encoding and IO#internal_encoding are nil" do
+ before :each do
+ @name = tmp("io_read.txt")
+ touch(@name) { |f| f.write "\x00\x01\x02" }
+ @io = new_io @name, "r+"
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ end
+
+ it "sets the String encoding to Encoding.default_external" do
+ @io.read.encoding.should equal(Encoding.default_external)
+ end
+ end
+
+ describe "with internal encoding" do
+ after :each do
+ @io.close if @io
+ end
+
+ describe "not specified" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp"
+ end
+
+ it "does not transcode the String" do
+ @io.read.should == ("ã‚りãŒã¨ã†\n").encode(Encoding::EUC_JP)
+ end
+
+ it "sets the String encoding to the external encoding" do
+ @io.read.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it_behaves_like :io_read_size_internal_encoding, nil
+ end
+
+ describe "specified by open mode" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp:utf-8"
+ end
+
+ it_behaves_like :io_read_internal_encoding, nil
+ it_behaves_like :io_read_size_internal_encoding, nil
+ end
+
+ describe "specified by mode: option" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", mode: "r:euc-jp:utf-8"
+ end
+
+ it_behaves_like :io_read_internal_encoding, nil
+ it_behaves_like :io_read_size_internal_encoding, nil
+ end
+
+ describe "specified by internal_encoding: option" do
+ before :each do
+ options = { mode: "r",
+ internal_encoding: "utf-8",
+ external_encoding: "euc-jp" }
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", options
+ end
+
+ it_behaves_like :io_read_internal_encoding, nil
+ it_behaves_like :io_read_size_internal_encoding, nil
+ end
+
+ describe "specified by encoding: option" do
+ before :each do
+ options = { mode: "r", encoding: "euc-jp:utf-8" }
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", options
+ end
+
+ it_behaves_like :io_read_internal_encoding, nil
+ it_behaves_like :io_read_size_internal_encoding, nil
+ end
+ end
+end
+
+describe "IO#read with large data" do
+ before :each do
+ # TODO: what is the significance of this mystery math?
+ @data_size = 8096 * 2 + 1024
+ @data = "*" * @data_size
+
+ @fname = tmp("io_read.txt")
+ touch(@fname) { |f| f.write @data }
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ it "reads all the data at once" do
+ File.open(@fname, 'r') { |io| ScratchPad.record io.read }
+
+ ScratchPad.recorded.size.should == @data_size
+ ScratchPad.recorded.should == @data
+ end
+
+ it "reads only the requested number of bytes" do
+ read_size = @data_size / 2
+ File.open(@fname, 'r') { |io| ScratchPad.record io.read(read_size) }
+
+ ScratchPad.recorded.size.should == read_size
+ ScratchPad.recorded.should == @data[0, read_size]
+ end
+end
diff --git a/spec/ruby/core/io/readbyte_spec.rb b/spec/ruby/core/io/readbyte_spec.rb
new file mode 100644
index 0000000000..14426c28ac
--- /dev/null
+++ b/spec/ruby/core/io/readbyte_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "IO#readbyte" do
+ before :each do
+ @io = File.open(__FILE__, 'r')
+ end
+
+ after :each do
+ @io.close
+ end
+
+ it "reads one byte from the stream" do
+ byte = @io.readbyte
+ byte.should == ?r.getbyte(0)
+ @io.pos.should == 1
+ end
+
+ it "raises EOFError on EOF" do
+ @io.seek(999999)
+ -> do
+ @io.readbyte
+ end.should raise_error EOFError
+ end
+end
diff --git a/spec/ruby/core/io/readchar_spec.rb b/spec/ruby/core/io/readchar_spec.rb
new file mode 100644
index 0000000000..a66773851a
--- /dev/null
+++ b/spec/ruby/core/io/readchar_spec.rb
@@ -0,0 +1,110 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :io_readchar_internal_encoding, shared: true do
+ it "returns a transcoded String" do
+ @io.readchar.should == "ã‚"
+ end
+
+ it "sets the String encoding to the internal encoding" do
+ @io.readchar.encoding.should equal(Encoding::UTF_8)
+ end
+end
+
+describe "IO#readchar" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "returns the next string from the stream" do
+ @io.readchar.should == 'V'
+ @io.readchar.should == 'o'
+ @io.readchar.should == 'i'
+ # read the rest of line
+ @io.readline.should == "ci la ligne une.\n"
+ @io.readchar.should == 'Q'
+ end
+
+ it "raises an EOFError when invoked at the end of the stream" do
+ @io.read
+ -> { @io.readchar }.should raise_error(EOFError)
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.readchar }.should raise_error(IOError)
+ end
+end
+
+describe "IO#readchar with internal encoding" do
+ after :each do
+ @io.close if @io
+ end
+
+ describe "not specified" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp"
+ end
+
+ it "does not transcode the String" do
+ @io.readchar.should == ("ã‚").encode(Encoding::EUC_JP)
+ end
+
+ it "sets the String encoding to the external encoding" do
+ @io.readchar.encoding.should equal(Encoding::EUC_JP)
+ end
+ end
+
+ describe "specified by open mode" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp:utf-8"
+ end
+
+ it_behaves_like :io_readchar_internal_encoding, nil
+ end
+
+ describe "specified by mode: option" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", mode: "r:euc-jp:utf-8"
+ end
+
+ it_behaves_like :io_readchar_internal_encoding, nil
+ end
+
+ describe "specified by internal_encoding: option" do
+ before :each do
+ options = { mode: "r",
+ internal_encoding: "utf-8",
+ external_encoding: "euc-jp" }
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", options
+ end
+
+ it_behaves_like :io_readchar_internal_encoding, nil
+ end
+
+ describe "specified by encoding: option" do
+ before :each do
+ options = { mode: "r", encoding: "euc-jp:utf-8" }
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", options
+ end
+
+ it_behaves_like :io_readchar_internal_encoding, nil
+ end
+end
+
+describe "IO#readchar" do
+ before :each do
+ @io = IOSpecs.io_fixture "empty.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "raises EOFError on empty stream" do
+ -> { @io.readchar }.should raise_error(EOFError)
+ end
+end
diff --git a/spec/ruby/core/io/readline_spec.rb b/spec/ruby/core/io/readline_spec.rb
new file mode 100644
index 0000000000..cf9f0dfc11
--- /dev/null
+++ b/spec/ruby/core/io/readline_spec.rb
@@ -0,0 +1,86 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#readline" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "returns the next line on the stream" do
+ @io.readline.should == "Voici la ligne une.\n"
+ @io.readline.should == "Qui è la linea due.\n"
+ end
+
+ it "goes back to first position after a rewind" do
+ @io.readline.should == "Voici la ligne une.\n"
+ @io.rewind
+ @io.readline.should == "Voici la ligne une.\n"
+ end
+
+ it "returns characters after the position set by #seek" do
+ @io.seek(1)
+ @io.readline.should == "oici la ligne une.\n"
+ end
+
+ it "raises EOFError on end of stream" do
+ IOSpecs.lines.length.times { @io.readline }
+ -> { @io.readline }.should raise_error(EOFError)
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.readline }.should raise_error(IOError)
+ end
+
+ it "assigns the returned line to $_" do
+ IOSpecs.lines.each do |line|
+ @io.readline
+ $_.should == line
+ end
+ end
+
+ describe "when passed limit" do
+ it "reads limit bytes" do
+ @io.readline(3).should == "Voi"
+ end
+
+ it "returns an empty string when passed 0 as a limit" do
+ @io.readline(0).should == ""
+ end
+
+ it "does not accept Integers that don't fit in a C off_t" do
+ -> { @io.readline(2**128) }.should raise_error(RangeError)
+ end
+ end
+
+ describe "when passed separator and limit" do
+ it "reads limit bytes till the separator" do
+ # Voici la ligne une.\
+ @io.readline(" ", 4).should == "Voic"
+ @io.readline(" ", 4).should == "i "
+ @io.readline(" ", 4).should == "la "
+ @io.readline(" ", 4).should == "lign"
+ @io.readline(" ", 4).should == "e "
+ end
+ end
+
+ describe "when passed chomp" do
+ it "returns the first line without a trailing newline character" do
+ @io.readline(chomp: true).should == IOSpecs.lines_without_newline_characters[0]
+ end
+
+ ruby_version_is "3.0" do
+ it "raises exception when options passed as Hash" do
+ -> { @io.readline({ chomp: true }) }.should raise_error(TypeError)
+
+ -> {
+ @io.readline("\n", 1, { chomp: true })
+ }.should raise_error(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/readlines_spec.rb b/spec/ruby/core/io/readlines_spec.rb
new file mode 100644
index 0000000000..496003002d
--- /dev/null
+++ b/spec/ruby/core/io/readlines_spec.rb
@@ -0,0 +1,236 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/readlines'
+
+describe "IO#readlines" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ @orig_extenc = Encoding.default_external
+ Encoding.default_external = Encoding::UTF_8
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ Encoding.default_external = @orig_extenc
+ end
+
+ it "raises an IOError if the stream is closed" do
+ @io.close
+ -> { @io.readlines }.should raise_error(IOError)
+ end
+
+ describe "when passed no arguments" do
+ before :each do
+ suppress_warning {@sep, $/ = $/, " "}
+ end
+
+ after :each do
+ suppress_warning {$/ = @sep}
+ end
+
+ it "returns an Array containing lines based on $/" do
+ @io.readlines.should == IOSpecs.lines_space_separator
+ end
+ end
+
+ describe "when passed no arguments" do
+ it "updates self's position" do
+ @io.readlines
+ @io.pos.should eql(137)
+ end
+
+ it "updates self's lineno based on the number of lines read" do
+ @io.readlines
+ @io.lineno.should eql(9)
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.readlines
+ $_.should == "test"
+ end
+
+ it "returns an empty Array when self is at the end" do
+ @io.readlines.should == IOSpecs.lines
+ @io.readlines.should == []
+ end
+ end
+
+ describe "when passed nil" do
+ it "returns the remaining content as one line starting at the current position" do
+ @io.readlines(nil).should == [IOSpecs.lines.join]
+ end
+ end
+
+ describe "when passed an empty String" do
+ it "returns an Array containing all paragraphs" do
+ @io.readlines("").should == IOSpecs.paragraphs
+ end
+ end
+
+ describe "when passed a separator" do
+ it "returns an Array containing lines based on the separator" do
+ @io.readlines("r").should == IOSpecs.lines_r_separator
+ end
+
+ it "returns an empty Array when self is at the end" do
+ @io.readlines
+ @io.readlines("r").should == []
+ end
+
+ it "updates self's lineno based on the number of lines read" do
+ @io.readlines("r")
+ @io.lineno.should eql(5)
+ end
+
+ it "updates self's position based on the number of characters read" do
+ @io.readlines("r")
+ @io.pos.should eql(137)
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.readlines("r")
+ $_.should == "test"
+ end
+
+ it "tries to convert the passed separator to a String using #to_str" do
+ obj = mock('to_str')
+ obj.stub!(:to_str).and_return("r")
+ @io.readlines(obj).should == IOSpecs.lines_r_separator
+ end
+ end
+
+ describe "when passed limit" do
+ it "raises ArgumentError when passed 0 as a limit" do
+ -> { @io.readlines(0) }.should raise_error(ArgumentError)
+ end
+
+ it "does not accept Integers that don't fit in a C off_t" do
+ -> { @io.readlines(2**128) }.should raise_error(RangeError)
+ end
+ end
+
+ describe "when passed chomp" do
+ it "returns the first line without a trailing newline character" do
+ @io.readlines(chomp: true).should == IOSpecs.lines_without_newline_characters
+ end
+
+ ruby_version_is "3.0" do
+ it "raises exception when options passed as Hash" do
+ -> { @io.readlines({ chomp: true }) }.should raise_error(TypeError)
+
+ -> {
+ @io.readlines("\n", 1, { chomp: true })
+ }.should raise_error(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
+ end
+ end
+ end
+end
+
+describe "IO#readlines" do
+ before :each do
+ @name = tmp("io_readlines")
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "raises an IOError if the stream is opened for append only" do
+ -> do
+ File.open(@name, "a:utf-8") { |f| f.readlines }
+ end.should raise_error(IOError)
+ end
+
+ it "raises an IOError if the stream is opened for write only" do
+ -> do
+ File.open(@name, "w:utf-8") { |f| f.readlines }
+ end.should raise_error(IOError)
+ end
+end
+
+describe "IO.readlines" do
+ before :each do
+ @external = Encoding.default_external
+ Encoding.default_external = Encoding::UTF_8
+
+ @name = fixture __FILE__, "lines.txt"
+ ScratchPad.record []
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ IO.readlines(@name)
+ $_.should == "test"
+ end
+
+ describe "when passed a string that starts with a |" do
+ it "gets data from the standard out of the subprocess" do
+ cmd = "|sh -c 'echo hello;echo line2'"
+ platform_is :windows do
+ cmd = "|cmd.exe /C echo hello&echo line2"
+ end
+ lines = IO.readlines(cmd)
+ lines.should == ["hello\n", "line2\n"]
+ end
+
+ platform_is_not :windows do
+ it "gets data from a fork when passed -" do
+ lines = IO.readlines("|-")
+
+ if lines # parent
+ lines.should == ["hello\n", "from a fork\n"]
+ else
+ puts "hello"
+ puts "from a fork"
+ exit!
+ end
+ end
+ end
+ end
+
+ it_behaves_like :io_readlines, :readlines
+ it_behaves_like :io_readlines_options_19, :readlines
+end
+
+describe "IO.readlines" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+ @name = fixture __FILE__, "lines.txt"
+ @dollar_slash = $/
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ suppress_warning {$/ = @dollar_slash}
+ end
+
+ it "encodes lines using the default external encoding" do
+ Encoding.default_external = Encoding::UTF_8
+ lines = IO.readlines(@name)
+ lines.all? { |s| s.encoding == Encoding::UTF_8 }.should be_true
+ end
+
+ it "encodes lines using the default internal encoding, when set" do
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = Encoding::UTF_16
+ suppress_warning {$/ = $/.encode Encoding::UTF_16}
+ lines = IO.readlines(@name)
+ lines.all? { |s| s.encoding == Encoding::UTF_16 }.should be_true
+ end
+
+ it "ignores the default internal encoding if the external encoding is BINARY" do
+ Encoding.default_external = Encoding::BINARY
+ Encoding.default_internal = Encoding::UTF_8
+ lines = IO.readlines(@name)
+ lines.all? { |s| s.encoding == Encoding::BINARY }.should be_true
+ end
+end
diff --git a/spec/ruby/core/io/readpartial_spec.rb b/spec/ruby/core/io/readpartial_spec.rb
new file mode 100644
index 0000000000..2901b429c2
--- /dev/null
+++ b/spec/ruby/core/io/readpartial_spec.rb
@@ -0,0 +1,111 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#readpartial" do
+ before :each do
+ @rd, @wr = IO.pipe
+ @rd.binmode
+ @wr.binmode
+ end
+
+ after :each do
+ @rd.close unless @rd.closed?
+ @wr.close unless @wr.closed?
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.readpartial(10) }.should raise_error(IOError)
+
+ @rd.close
+ -> { @rd.readpartial(10) }.should raise_error(IOError)
+ end
+
+ it "reads at most the specified number of bytes" do
+ @wr.write("foobar")
+
+ # buffered read
+ @rd.read(1).should == 'f'
+ # return only specified number, not the whole buffer
+ @rd.readpartial(1).should == "o"
+ end
+
+ it "reads after ungetc with data in the buffer" do
+ @wr.write("foobar")
+ c = @rd.getc
+ @rd.ungetc(c)
+ @rd.readpartial(3).should == "foo"
+ @rd.readpartial(3).should == "bar"
+ end
+
+ it "reads after ungetc with multibyte characters in the buffer" do
+ @wr.write("∂φ/∂x = gaîté")
+ c = @rd.getc
+ @rd.ungetc(c)
+ @rd.readpartial(3).should == "\xE2\x88\x82"
+ @rd.readpartial(3).should == "\xCF\x86/"
+ end
+
+ it "reads after ungetc without data in the buffer" do
+ @wr.write("f")
+ c = @rd.getc
+ @rd.ungetc(c)
+ @rd.readpartial(2).should == "f"
+
+ # now, also check that the ungot char is cleared and
+ # not returned again
+ @wr.write("b")
+ @rd.readpartial(2).should == "b"
+ end
+
+ it "discards the existing buffer content upon successful read" do
+ buffer = "existing content"
+ @wr.write("hello world")
+ @wr.close
+ @rd.readpartial(11, buffer)
+ buffer.should == "hello world"
+ end
+
+ it "raises EOFError on EOF" do
+ @wr.write("abc")
+ @wr.close
+ @rd.readpartial(10).should == 'abc'
+ -> { @rd.readpartial(10) }.should raise_error(EOFError)
+ end
+
+ it "discards the existing buffer content upon error" do
+ buffer = 'hello'
+ @wr.close
+ -> { @rd.readpartial(1, buffer) }.should raise_error(EOFError)
+ buffer.should be_empty
+ end
+
+ it "raises IOError if the stream is closed" do
+ @wr.close
+ -> { @rd.readpartial(1) }.should raise_error(IOError)
+ end
+
+ it "raises ArgumentError if the negative argument is provided" do
+ -> { @rd.readpartial(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "immediately returns an empty string if the length argument is 0" do
+ @rd.readpartial(0).should == ""
+ end
+
+ ruby_bug "#18421", ""..."3.0.4" do
+ it "clears and returns the given buffer if the length argument is 0" do
+ buffer = "existing content"
+ @rd.readpartial(0, buffer).should == buffer
+ buffer.should == ""
+ end
+ end
+
+ it "preserves the encoding of the given buffer" do
+ buffer = ''.encode(Encoding::ISO_8859_1)
+ @wr.write("abc")
+ @wr.close
+ @rd.readpartial(10, buffer)
+ buffer.encoding.should == Encoding::ISO_8859_1
+ end
+end
diff --git a/spec/ruby/core/io/reopen_spec.rb b/spec/ruby/core/io/reopen_spec.rb
new file mode 100644
index 0000000000..8ff0f217f4
--- /dev/null
+++ b/spec/ruby/core/io/reopen_spec.rb
@@ -0,0 +1,313 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+require 'fcntl'
+
+describe "IO#reopen" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ @other_name = tmp("io_reopen_other.txt")
+
+ @io = new_io @name
+ @other_io = File.open @other_name, "w"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ @other_io.close unless @other_io.closed?
+ rm_r @name, @other_name
+ end
+
+ it "calls #to_io to convert an object" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return(@other_io)
+ @io.reopen obj
+ end
+
+ it "changes the class of the instance to the class of the object returned by #to_io" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return(@other_io)
+ @io.reopen(obj).should be_an_instance_of(File)
+ end
+
+ it "raises an IOError if the object returned by #to_io is closed" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return(IOSpecs.closed_io)
+ -> { @io.reopen obj }.should raise_error(IOError)
+ end
+
+ it "raises a TypeError if #to_io does not return an IO instance" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return("something else")
+ -> { @io.reopen obj }.should raise_error(TypeError)
+ end
+
+ it "raises an IOError when called on a closed stream with an object" do
+ @io.close
+ obj = mock("io")
+ obj.should_not_receive(:to_io)
+ -> { @io.reopen(STDOUT) }.should raise_error(IOError)
+ end
+
+ it "raises an IOError if the IO argument is closed" do
+ -> { @io.reopen(IOSpecs.closed_io) }.should raise_error(IOError)
+ end
+
+ it "raises an IOError when called on a closed stream with an IO" do
+ @io.close
+ -> { @io.reopen(STDOUT) }.should raise_error(IOError)
+ end
+end
+
+describe "IO#reopen with a String" do
+ before :each do
+ @name = fixture __FILE__, "numbered_lines.txt"
+ @other_name = tmp("io_reopen.txt")
+ touch @other_name
+ @io = IOSpecs.io_fixture "lines.txt"
+
+ @tmp_file = tmp("reopen")
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @other_name, @tmp_file
+ end
+
+ it "does not raise an exception when called on a closed stream with a path" do
+ @io.close
+ @io.reopen @name, "r"
+ @io.closed?.should be_false
+ @io.gets.should == "Line 1: One\n"
+ end
+
+ it "returns self" do
+ @io.reopen(@name).should equal(@io)
+ end
+
+ it "positions a newly created instance at the beginning of the new stream" do
+ @io.reopen(@name)
+ @io.gets.should == "Line 1: One\n"
+ end
+
+ it "positions an instance that has been read from at the beginning of the new stream" do
+ @io.gets
+ @io.reopen(@name)
+ @io.gets.should == "Line 1: One\n"
+ end
+
+ platform_is_not :windows do
+ it "passes all mode flags through" do
+ @io.reopen(@tmp_file, "ab")
+ (@io.fcntl(Fcntl::F_GETFL) & File::APPEND).should == File::APPEND
+ end
+ end
+
+ platform_is_not :windows do
+ # TODO Should this work on Windows?
+ it "affects exec/system/fork performed after it" do
+ ruby_exe fixture(__FILE__, "reopen_stdout.rb"), args: @tmp_file
+ File.read(@tmp_file).should == "from system\nfrom exec\n"
+ end
+ end
+
+ it "calls #to_path on non-String arguments" do
+ obj = mock('path')
+ obj.should_receive(:to_path).and_return(@other_name)
+ @io.reopen(obj)
+ end
+end
+
+describe "IO#reopen with a String" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ @other_name = tmp("io_reopen_other.txt")
+ @other_io = nil
+
+ rm_r @other_name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ @other_io.close if @other_io and not @other_io.closed?
+ rm_r @name, @other_name
+ end
+
+ it "opens a path after writing to the original file descriptor" do
+ @io = new_io @name, "w"
+
+ @io.print "original data"
+ @io.reopen @other_name
+ @io.print "new data"
+ @io.flush
+
+ File.read(@name).should == "original data"
+ File.read(@other_name).should == "new data"
+ end
+
+ it "always resets the close-on-exec flag to true on non-STDIO objects" do
+ @io = new_io @name, "w"
+
+ @io.close_on_exec = true
+ @io.reopen @other_name
+ @io.should.close_on_exec?
+
+ @io.close_on_exec = false
+ @io.reopen @other_name
+ @io.should.close_on_exec?
+ end
+
+ it "creates the file if it doesn't exist if the IO is opened in write mode" do
+ @io = new_io @name, "w"
+
+ @io.reopen(@other_name)
+ File.should.exist?(@other_name)
+ end
+
+ it "creates the file if it doesn't exist if the IO is opened in write mode" do
+ @io = new_io @name, "a"
+
+ @io.reopen(@other_name)
+ File.should.exist?(@other_name)
+ end
+end
+
+describe "IO#reopen with a String" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ @other_name = tmp("io_reopen_other.txt")
+
+ touch @name
+ rm_r @other_name
+ end
+
+ after :each do
+ @io.close
+ rm_r @name, @other_name
+ end
+
+ it "raises an Errno::ENOENT if the file does not exist and the IO is not opened in write mode" do
+ @io = new_io @name, "r"
+ -> { @io.reopen(@other_name) }.should raise_error(Errno::ENOENT)
+ end
+end
+
+describe "IO#reopen with an IO at EOF" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ touch(@name) { |f| f.puts "a line" }
+ @other_name = tmp("io_reopen_other.txt")
+ touch(@other_name) do |f|
+ f.puts "Line 1"
+ f.puts "Line 2"
+ end
+
+ @io = new_io @name, "r"
+ @other_io = new_io @other_name, "r"
+ @io.read
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ @other_io.close unless @other_io.closed?
+ rm_r @name, @other_name
+ end
+
+ it "resets the EOF status to false" do
+ @io.eof?.should be_true
+ @io.reopen @other_io
+ @io.eof?.should be_false
+ end
+end
+
+describe "IO#reopen with an IO" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ @other_name = tmp("io_reopen_other.txt")
+ touch(@other_name) do |f|
+ f.puts "Line 1"
+ f.puts "Line 2"
+ end
+
+ @io = new_io @name
+ @other_io = IO.new(new_fd(@other_name, "r"), "r")
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ @other_io.close unless @other_io.closed?
+ rm_r @name, @other_name
+ end
+
+ it "does not call #to_io" do
+ # Why do we not use #should_not_receive(:to_io) here? Because
+ # MRI actually changes the class of @io in the call to #reopen
+ # but does not preserve the existing singleton class of @io.
+ def @io.to_io; flunk; end
+ @io.reopen(@other_io).should be_an_instance_of(IO)
+ end
+
+ it "does not change the object_id" do
+ obj_id = @io.object_id
+ @io.reopen @other_io
+ @io.object_id.should == obj_id
+ end
+
+ it "reads from the beginning if the other IO has not been read from" do
+ @io.reopen @other_io
+ @io.gets.should == "Line 1\n"
+ end
+
+ it "reads from the current position of the other IO's stream" do
+ @other_io.gets.should == "Line 1\n"
+ @io.reopen @other_io
+ @io.gets.should == "Line 2\n"
+ end
+end
+
+describe "IO#reopen with an IO" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ @other_name = tmp("io_reopen_other.txt")
+
+ @io = new_io @name
+ @other_io = File.open @other_name, "w"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ @other_io.close unless @other_io.closed?
+ rm_r @name, @other_name
+ end
+
+ it "associates the IO instance with the other IO's stream" do
+ File.read(@other_name).should == ""
+ @io.reopen @other_io
+ @io.print "io data"
+ @io.flush
+ File.read(@name).should == ""
+ File.read(@other_name).should == "io data"
+ end
+
+ it "always resets the close-on-exec flag to true on non-STDIO objects" do
+ @other_io.close_on_exec = true
+ @io.close_on_exec = true
+ @io.reopen @other_io
+ @io.should.close_on_exec?
+
+ @other_io.close_on_exec = false
+ @io.close_on_exec = false
+ @io.reopen @other_io
+ @io.should.close_on_exec?
+ end
+
+ it "may change the class of the instance" do
+ @io.reopen @other_io
+ @io.should be_an_instance_of(File)
+ end
+
+ it "sets path equals to the other IO's path if other IO is File" do
+ @io.reopen @other_io
+ @io.path.should == @other_io.path
+ end
+end
diff --git a/spec/ruby/core/io/rewind_spec.rb b/spec/ruby/core/io/rewind_spec.rb
new file mode 100644
index 0000000000..5579cbd988
--- /dev/null
+++ b/spec/ruby/core/io/rewind_spec.rb
@@ -0,0 +1,53 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#rewind" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "positions the instance to the beginning of input" do
+ @io.readline.should == "Voici la ligne une.\n"
+ @io.readline.should == "Qui è la linea due.\n"
+ @io.rewind
+ @io.readline.should == "Voici la ligne une.\n"
+ end
+
+ it "positions the instance to the beginning of output for write-only IO" do
+ name = tmp("io_rewind_spec")
+ io = File.open(name, "w")
+ io.write("Voici la ligne une.\n")
+ io.rewind
+ io.pos.should == 0
+ ensure
+ io.close
+ rm_r name
+ end
+
+ it "positions the instance to the beginning of input and clears EOF" do
+ value = @io.read
+ @io.rewind
+ @io.should_not.eof?
+ value.should == @io.read
+ end
+
+ it "sets lineno to 0" do
+ @io.readline.should == "Voici la ligne une.\n"
+ @io.lineno.should == 1
+ @io.rewind
+ @io.lineno.should == 0
+ end
+
+ it "returns 0" do
+ @io.rewind.should == 0
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.rewind }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/seek_spec.rb b/spec/ruby/core/io/seek_spec.rb
new file mode 100644
index 0000000000..2fa4a73ac9
--- /dev/null
+++ b/spec/ruby/core/io/seek_spec.rb
@@ -0,0 +1,79 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/pos'
+
+describe "IO#seek" do
+ it_behaves_like :io_set_pos, :seek
+end
+
+describe "IO#seek" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "moves the read position relative to the current position with SEEK_CUR" do
+ -> { @io.seek(-1) }.should raise_error(Errno::EINVAL)
+ @io.seek(10, IO::SEEK_CUR)
+ @io.readline.should == "igne une.\n"
+ @io.seek(-5, IO::SEEK_CUR)
+ @io.readline.should == "une.\n"
+ end
+
+ it "moves the read position relative to the start with SEEK_SET" do
+ @io.seek(1)
+ @io.pos.should == 1
+ @io.rewind
+ @io.seek(43, IO::SEEK_SET)
+ @io.readline.should == "Aquí está la línea tres.\n"
+ @io.seek(5, IO::SEEK_SET)
+ @io.readline.should == " la ligne une.\n"
+ end
+
+ it "moves the read position relative to the end with SEEK_END" do
+ @io.seek(0, IO::SEEK_END)
+ @io.tell.should == 137
+ @io.seek(-25, IO::SEEK_END)
+ @io.readline.should == "cinco.\n"
+ end
+
+ it "moves the read position and clears EOF with SEEK_SET" do
+ value = @io.read
+ @io.seek(0, IO::SEEK_SET)
+ @io.should_not.eof?
+ value.should == @io.read
+ end
+
+ it "moves the read position and clears EOF with SEEK_CUR" do
+ value = @io.read
+ @io.seek(-1, IO::SEEK_CUR)
+ @io.should_not.eof?
+ value[-1].should == @io.read[0]
+ end
+
+ it "moves the read position and clears EOF with SEEK_END" do
+ value = @io.read
+ @io.seek(-1, IO::SEEK_END)
+ @io.should_not.eof?
+ value[-1].should == @io.read[0]
+ end
+
+ platform_is :darwin do
+ it "supports seek offsets greater than 2^32" do
+ begin
+ zero = File.open('/dev/zero')
+ offset = 2**33
+ zero.seek(offset, File::SEEK_SET)
+ pos = zero.pos
+
+ pos.should == offset
+ ensure
+ zero.close rescue nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/select_spec.rb b/spec/ruby/core/io/select_spec.rb
new file mode 100644
index 0000000000..4603c1fbbc
--- /dev/null
+++ b/spec/ruby/core/io/select_spec.rb
@@ -0,0 +1,120 @@
+require_relative '../../spec_helper'
+
+describe "IO.select" do
+ before :each do
+ @rd, @wr = IO.pipe
+ end
+
+ after :each do
+ @rd.close unless @rd.closed?
+ @wr.close unless @wr.closed?
+ end
+
+ it "blocks for duration of timeout and returns nil if there are no objects ready for I/O" do
+ IO.select([@rd], nil, nil, 0.001).should == nil
+ end
+
+ it "returns immediately all objects that are ready for I/O when timeout is 0" do
+ @wr.syswrite("be ready")
+ IO.pipe do |_, wr|
+ result = IO.select [@rd], [wr], nil, 0
+ result.should == [[@rd], [wr], []]
+ end
+ end
+
+ it "returns nil after timeout if there are no objects ready for I/O" do
+ result = IO.select [@rd], nil, nil, 0
+ result.should == nil
+ end
+
+ it "returns supplied objects when they are ready for I/O" do
+ main = Thread.current
+ t = Thread.new {
+ Thread.pass until main.status == "sleep"
+ @wr.write "be ready"
+ }
+ result = IO.select [@rd], nil, nil, nil
+ result.should == [[@rd], [], []]
+ t.join
+ end
+
+ it "leaves out IO objects for which there is no I/O ready" do
+ @wr.write "be ready"
+ platform_is :aix do
+ # In AIX, when a pipe is readable, select(2) returns the write side
+ # of the pipe as "readable", even though you cannot actually read
+ # anything from the write side.
+ result = IO.select [@wr, @rd], nil, nil, nil
+ result.should == [[@wr, @rd], [], []]
+ end
+ platform_is_not :aix do
+ # Order matters here. We want to see that @wr doesn't expand the size
+ # of the returned array, so it must be 1st.
+ result = IO.select [@wr, @rd], nil, nil, nil
+ result.should == [[@rd], [], []]
+ end
+ end
+
+ it "returns supplied objects correctly even when monitoring the same object in different arrays" do
+ filename = tmp("IO_select_pipe_file") + $$.to_s
+ io = File.open(filename, 'w+')
+ result = IO.select [io], [io], nil, 0
+ result.should == [[io], [io], []]
+ io.close
+ rm_r filename
+ end
+
+ it "invokes to_io on supplied objects that are not IO and returns the supplied objects" do
+ # make some data available
+ @wr.write("foobar")
+
+ obj = mock("read_io")
+ obj.should_receive(:to_io).at_least(1).and_return(@rd)
+ IO.select([obj]).should == [[obj], [], []]
+
+ IO.pipe do |_, wr|
+ obj = mock("write_io")
+ obj.should_receive(:to_io).at_least(1).and_return(wr)
+ IO.select(nil, [obj]).should == [[], [obj], []]
+ end
+ end
+
+ it "raises TypeError if supplied objects are not IO" do
+ -> { IO.select([Object.new]) }.should raise_error(TypeError)
+ -> { IO.select(nil, [Object.new]) }.should raise_error(TypeError)
+
+ obj = mock("io")
+ obj.should_receive(:to_io).any_number_of_times.and_return(nil)
+
+ -> { IO.select([obj]) }.should raise_error(TypeError)
+ -> { IO.select(nil, [obj]) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the specified timeout value is not Numeric" do
+ -> { IO.select([@rd], nil, nil, Object.new) }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError if the first three arguments are not Arrays" do
+ -> { IO.select(Object.new)}.should raise_error(TypeError)
+ -> { IO.select(nil, Object.new)}.should raise_error(TypeError)
+ -> { IO.select(nil, nil, Object.new)}.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError when passed a negative timeout" do
+ -> { IO.select(nil, nil, nil, -5)}.should raise_error(ArgumentError)
+ end
+end
+
+describe "IO.select when passed nil for timeout" do
+ it "sleeps forever and sets the thread status to 'sleep'" do
+ t = Thread.new do
+ IO.select(nil, nil, nil, nil)
+ end
+
+ Thread.pass while t.status && t.status != "sleep"
+ t.join unless t.status
+ t.status.should == "sleep"
+ t.kill
+ t.join
+ end
+end
diff --git a/spec/ruby/core/io/set_encoding_by_bom_spec.rb b/spec/ruby/core/io/set_encoding_by_bom_spec.rb
new file mode 100644
index 0000000000..92433d6640
--- /dev/null
+++ b/spec/ruby/core/io/set_encoding_by_bom_spec.rb
@@ -0,0 +1,262 @@
+require_relative '../../spec_helper'
+
+describe "IO#set_encoding_by_bom" do
+ before :each do
+ @name = tmp('io_set_encoding_by_bom.txt')
+ touch(@name)
+ @io = new_io(@name, 'rb')
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "returns nil if not readable" do
+ not_readable_io = new_io(@name, 'wb')
+
+ not_readable_io.set_encoding_by_bom.should be_nil
+ not_readable_io.external_encoding.should == Encoding::ASCII_8BIT
+ ensure
+ not_readable_io.close
+ end
+
+ it "returns the result encoding if found BOM UTF-8 sequence" do
+ File.binwrite(@name, "\u{FEFF}")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_8
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.binwrite(@name, "\u{FEFF}abc")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_8
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.read.b.should == "abc".b
+ end
+
+ it "returns the result encoding if found BOM UTF_16LE sequence" do
+ File.binwrite(@name, "\xFF\xFE")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16LE
+ @io.external_encoding.should == Encoding::UTF_16LE
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.binwrite(@name, "\xFF\xFEabc")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16LE
+ @io.external_encoding.should == Encoding::UTF_16LE
+ @io.read.b.should == "abc".b
+ end
+
+ it "returns the result encoding if found BOM UTF_16BE sequence" do
+ File.binwrite(@name, "\xFE\xFF")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16BE
+ @io.external_encoding.should == Encoding::UTF_16BE
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.binwrite(@name, "\xFE\xFFabc")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16BE
+ @io.external_encoding.should == Encoding::UTF_16BE
+ @io.read.b.should == "abc".b
+ end
+
+ it "returns the result encoding if found BOM UTF_32LE sequence" do
+ File.binwrite(@name, "\xFF\xFE\x00\x00")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_32LE
+ @io.external_encoding.should == Encoding::UTF_32LE
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.binwrite(@name, "\xFF\xFE\x00\x00abc")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_32LE
+ @io.external_encoding.should == Encoding::UTF_32LE
+ @io.read.b.should == "abc".b
+ end
+
+ it "returns the result encoding if found BOM UTF_32BE sequence" do
+ File.binwrite(@name, "\x00\x00\xFE\xFF")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_32BE
+ @io.external_encoding.should == Encoding::UTF_32BE
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.binwrite(@name, "\x00\x00\xFE\xFFabc")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_32BE
+ @io.external_encoding.should == Encoding::UTF_32BE
+ @io.read.b.should == "abc".b
+ end
+
+ it "returns nil if io is empty" do
+ @io.set_encoding_by_bom.should be_nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ end
+
+ it "returns nil if UTF-8 BOM sequence is incomplete" do
+ File.write(@name, "\xEF")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xEF".b
+ @io.rewind
+
+ File.write(@name, "\xEFa")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xEFa".b
+ @io.rewind
+
+ File.write(@name, "\xEF\xBB")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xEF\xBB".b
+ @io.rewind
+
+ File.write(@name, "\xEF\xBBa")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xEF\xBBa".b
+ end
+
+ it "returns nil if UTF-16BE BOM sequence is incomplete" do
+ File.write(@name, "\xFE")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xFE".b
+ @io.rewind
+
+ File.write(@name, "\xFEa")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xFEa".b
+ end
+
+ it "returns nil if UTF-16LE/UTF-32LE BOM sequence is incomplete" do
+ File.write(@name, "\xFF")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xFF".b
+ @io.rewind
+
+ File.write(@name, "\xFFa")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xFFa".b
+ end
+
+ it "returns UTF-16LE if UTF-32LE BOM sequence is incomplete" do
+ File.write(@name, "\xFF\xFE")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16LE
+ @io.external_encoding.should == Encoding::UTF_16LE
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.write(@name, "\xFF\xFE\x00")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16LE
+ @io.external_encoding.should == Encoding::UTF_16LE
+ @io.read.b.should == "\x00".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.write(@name, "\xFF\xFE\x00a")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16LE
+ @io.external_encoding.should == Encoding::UTF_16LE
+ @io.read.b.should == "\x00a".b
+ end
+
+ it "returns nil if UTF-32BE BOM sequence is incomplete" do
+ File.write(@name, "\x00")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00".b
+ @io.rewind
+
+ File.write(@name, "\x00a")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00a".b
+ @io.rewind
+
+ File.write(@name, "\x00\x00")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00\x00".b
+ @io.rewind
+
+ File.write(@name, "\x00\x00a")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00\x00a".b
+ @io.rewind
+
+ File.write(@name, "\x00\x00\xFE")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00\x00\xFE".b
+ @io.rewind
+
+ File.write(@name, "\x00\x00\xFEa")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00\x00\xFEa".b
+ end
+
+ it "returns nil if found BOM sequence not provided" do
+ File.write(@name, "abc")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read(3).should == "abc".b
+ end
+
+ it 'returns exception if io not in binary mode' do
+ not_binary_io = new_io(@name, 'r')
+
+ -> { not_binary_io.set_encoding_by_bom }.should raise_error(ArgumentError, 'ASCII incompatible encoding needs binmode')
+ ensure
+ not_binary_io.close
+ end
+
+ it 'returns exception if encoding already set' do
+ @io.set_encoding("utf-8")
+
+ -> { @io.set_encoding_by_bom }.should raise_error(ArgumentError, 'encoding is set to UTF-8 already')
+ end
+
+ it 'returns exception if encoding conversion is already set' do
+ @io.set_encoding(Encoding::UTF_8, Encoding::UTF_16BE)
+
+ -> { @io.set_encoding_by_bom }.should raise_error(ArgumentError, 'encoding conversion is set')
+ end
+end
diff --git a/spec/ruby/core/io/set_encoding_spec.rb b/spec/ruby/core/io/set_encoding_spec.rb
new file mode 100644
index 0000000000..22d9017635
--- /dev/null
+++ b/spec/ruby/core/io/set_encoding_spec.rb
@@ -0,0 +1,238 @@
+require_relative '../../spec_helper'
+
+describe :io_set_encoding_write, shared: true do
+ it "sets the encodings to nil when they were set previously" do
+ @io = new_io @name, "#{@object}:ibm437:ibm866"
+ @io.set_encoding nil, nil
+
+ @io.external_encoding.should be_nil
+ @io.internal_encoding.should be_nil
+ end
+
+ it "sets the encodings to nil when the IO is built with no explicit encoding" do
+ @io = new_io @name, @object
+
+ # Checking our assumptions first
+ @io.external_encoding.should be_nil
+ @io.internal_encoding.should be_nil
+
+ @io.set_encoding nil, nil
+
+ @io.external_encoding.should be_nil
+ @io.internal_encoding.should be_nil
+ end
+
+ it "prevents the encodings from changing when Encoding defaults are changed" do
+ @io = new_io @name, "#{@object}:utf-8:us-ascii"
+ @io.set_encoding nil, nil
+
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+
+ @io.external_encoding.should be_nil
+ @io.internal_encoding.should be_nil
+ end
+
+ it "sets the encodings to the current Encoding defaults" do
+ @io = new_io @name, @object
+
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+
+ @io.set_encoding nil, nil
+
+ @io.external_encoding.should == Encoding::IBM437
+ @io.internal_encoding.should == Encoding::IBM866
+ end
+end
+
+describe "IO#set_encoding when passed nil, nil" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ # The defaults
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = nil
+
+ @name = tmp('io_set_encoding.txt')
+ touch(@name)
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+
+ @io.close if @io and not @io.closed?
+ rm_r @name
+ end
+
+ describe "with 'r' mode" do
+ it "sets the encodings to the current Encoding defaults" do
+ @io = new_io @name, "r"
+
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+
+ @io.set_encoding nil, nil
+ @io.external_encoding.should equal(Encoding::IBM437)
+ @io.internal_encoding.should equal(Encoding::IBM866)
+ end
+
+ it "prevents the #internal_encoding from changing when Encoding.default_internal is changed" do
+ @io = new_io @name, "r"
+ @io.set_encoding nil, nil
+
+ Encoding.default_internal = Encoding::IBM437
+
+ @io.internal_encoding.should be_nil
+ end
+
+ it "allows the #external_encoding to change when Encoding.default_external is changed" do
+ @io = new_io @name, "r"
+ @io.set_encoding nil, nil
+
+ Encoding.default_external = Encoding::IBM437
+
+ @io.external_encoding.should equal(Encoding::IBM437)
+ end
+ end
+
+ describe "with 'rb' mode" do
+ it "returns Encoding.default_external" do
+ @io = new_io @name, "rb"
+ @io.external_encoding.should equal(Encoding::BINARY)
+
+ @io.set_encoding nil, nil
+ @io.external_encoding.should equal(Encoding.default_external)
+ end
+ end
+
+ describe "with 'r+' mode" do
+ it_behaves_like :io_set_encoding_write, nil, "r+"
+ end
+
+ describe "with 'w' mode" do
+ it_behaves_like :io_set_encoding_write, nil, "w"
+ end
+
+ describe "with 'w+' mode" do
+ it_behaves_like :io_set_encoding_write, nil, "w+"
+ end
+
+ describe "with 'a' mode" do
+ it_behaves_like :io_set_encoding_write, nil, "a"
+ end
+
+ describe "with 'a+' mode" do
+ it_behaves_like :io_set_encoding_write, nil, "a+"
+ end
+
+ describe "with standard IOs" do
+ it "correctly resets them" do
+ STDOUT.external_encoding.should == nil
+ STDOUT.internal_encoding.should == nil
+
+ begin
+ STDOUT.set_encoding(Encoding::US_ASCII, Encoding::ISO_8859_1)
+ ensure
+ STDOUT.set_encoding(nil, nil)
+ end
+
+ STDOUT.external_encoding.should == nil
+ STDOUT.internal_encoding.should == nil
+ end
+ end
+end
+
+describe "IO#set_encoding" do
+ before :each do
+ @name = tmp('io_set_encoding.txt')
+ touch(@name)
+ @io = new_io @name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "returns self" do
+ @io.set_encoding(Encoding::UTF_8).should equal(@io)
+ end
+
+ it "sets the external encoding when passed an Encoding argument" do
+ @io.set_encoding(Encoding::UTF_8)
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should be_nil
+ end
+
+ it "sets the external and internal encoding when passed two Encoding arguments" do
+ @io.set_encoding(Encoding::UTF_8, Encoding::UTF_16BE)
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == Encoding::UTF_16BE
+ end
+
+ it "sets the external encoding when passed the name of an Encoding" do
+ @io.set_encoding("utf-8")
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should be_nil
+ end
+
+ it "ignores the internal encoding if the same as external when passed Encoding objects" do
+ @io.set_encoding(Encoding::UTF_8, Encoding::UTF_8)
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should be_nil
+ end
+
+ it "ignores the internal encoding if the same as external when passed encoding names separated by ':'" do
+ @io.set_encoding("utf-8:utf-8")
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should be_nil
+ end
+
+ it "sets the external and internal encoding when passed the names of Encodings separated by ':'" do
+ @io.set_encoding("utf-8:utf-16be")
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == Encoding::UTF_16BE
+ end
+
+ it "sets the external and internal encoding when passed two String arguments" do
+ @io.set_encoding("utf-8", "utf-16be")
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == Encoding::UTF_16BE
+ end
+
+ it "calls #to_str to convert an abject to a String" do
+ obj = mock("io_set_encoding")
+ obj.should_receive(:to_str).and_return("utf-8:utf-16be")
+ @io.set_encoding(obj)
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == Encoding::UTF_16BE
+ end
+
+ it "calls #to_str to convert the second argument to a String" do
+ obj = mock("io_set_encoding")
+ obj.should_receive(:to_str).at_least(1).times.and_return("utf-16be")
+ @io.set_encoding(Encoding::UTF_8, obj)
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == Encoding::UTF_16BE
+ end
+
+ it "saves encoding options passed as a hash in the last argument" do
+ File.write(@name, "\xff")
+ io = File.open(@name)
+ io.set_encoding(Encoding::EUC_JP, Encoding::SHIFT_JIS, invalid: :replace, replace: ".")
+ io.read.should == "."
+ ensure
+ io.close
+ end
+
+ it "raises ArgumentError when no arguments are given" do
+ -> { @io.set_encoding() }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError when too many arguments are given" do
+ -> { @io.set_encoding(1, 2, 3) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/io/shared/binwrite.rb b/spec/ruby/core/io/shared/binwrite.rb
new file mode 100644
index 0000000000..3649bb47ff
--- /dev/null
+++ b/spec/ruby/core/io/shared/binwrite.rb
@@ -0,0 +1,78 @@
+require_relative '../fixtures/classes'
+
+describe :io_binwrite, shared: true do
+ before :each do
+ @filename = tmp("IO_binwrite_file") + $$.to_s
+ File.open(@filename, "w") do |file|
+ file << "012345678901234567890123456789"
+ end
+ end
+
+ after :each do
+ rm_r @filename
+ end
+
+ it "coerces the argument to a string using to_s" do
+ (obj = mock('test')).should_receive(:to_s).and_return('a string')
+ IO.send(@method, @filename, obj)
+ end
+
+ it "returns the number of bytes written" do
+ IO.send(@method, @filename, "abcde").should == 5
+ end
+
+ it "creates a file if missing" do
+ fn = @filename + "xxx"
+ begin
+ File.should_not.exist?(fn)
+ IO.send(@method, fn, "test")
+ File.should.exist?(fn)
+ ensure
+ rm_r fn
+ end
+ end
+
+ it "creates file if missing even if offset given" do
+ fn = @filename + "xxx"
+ begin
+ File.should_not.exist?(fn)
+ IO.send(@method, fn, "test", 0)
+ File.should.exist?(fn)
+ ensure
+ rm_r fn
+ end
+ end
+
+ it "truncates the file and writes the given string" do
+ IO.send(@method, @filename, "hello, world!")
+ File.read(@filename).should == "hello, world!"
+ end
+
+ it "doesn't truncate the file and writes the given string if an offset is given" do
+ IO.send(@method, @filename, "hello, world!", 0)
+ File.read(@filename).should == "hello, world!34567890123456789"
+ IO.send(@method, @filename, "hello, world!", 20)
+ File.read(@filename).should == "hello, world!3456789hello, world!"
+ end
+
+ it "doesn't truncate and writes at the given offset after passing empty opts" do
+ IO.send(@method, @filename, "hello world!", 1, **{})
+ File.read(@filename).should == "0hello world!34567890123456789"
+ end
+
+ it "accepts a :mode option" do
+ IO.send(@method, @filename, "hello, world!", mode: 'a')
+ File.read(@filename).should == "012345678901234567890123456789hello, world!"
+ IO.send(@method, @filename, "foo", 2, mode: 'w')
+ File.read(@filename).should == "\0\0foo"
+ end
+
+ it "raises an error if readonly mode is specified" do
+ -> { IO.send(@method, @filename, "abcde", mode: "r") }.should raise_error(IOError)
+ end
+
+ it "truncates if empty :opts provided and offset skipped" do
+ IO.send(@method, @filename, "hello, world!", **{})
+ File.read(@filename).should == "hello, world!"
+ end
+end
diff --git a/spec/ruby/core/io/shared/chars.rb b/spec/ruby/core/io/shared/chars.rb
new file mode 100644
index 0000000000..266566f221
--- /dev/null
+++ b/spec/ruby/core/io/shared/chars.rb
@@ -0,0 +1,73 @@
+# -*- encoding: utf-8 -*-
+describe :io_chars, shared: true do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ ScratchPad.record []
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "yields each character" do
+ @io.readline.should == "Voici la ligne une.\n"
+
+ count = 0
+ @io.send(@method) do |c|
+ ScratchPad << c
+ break if 4 < count += 1
+ end
+
+ ScratchPad.recorded.should == ["Q", "u", "i", " ", "è"]
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ enum = @io.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+ enum.first(5).should == ["V", "o", "i", "c", "i"]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @io.send(@method).size.should == nil
+ end
+ end
+ end
+ end
+
+ it "returns itself" do
+ @io.send(@method) { |c| }.should equal(@io)
+ end
+
+ it "returns an enumerator for a closed stream" do
+ IOSpecs.closed_io.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "raises an IOError when an enumerator created on a closed stream is accessed" do
+ -> { IOSpecs.closed_io.send(@method).first }.should raise_error(IOError)
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.send(@method) {} }.should raise_error(IOError)
+ end
+end
+
+describe :io_chars_empty, shared: true do
+ before :each do
+ @name = tmp("io_each_char")
+ @io = new_io @name, "w+:utf-8"
+ ScratchPad.record []
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "does not yield any characters on an empty stream" do
+ @io.send(@method) { |c| ScratchPad << c }
+ ScratchPad.recorded.should == []
+ end
+end
diff --git a/spec/ruby/core/io/shared/codepoints.rb b/spec/ruby/core/io/shared/codepoints.rb
new file mode 100644
index 0000000000..6872846c1a
--- /dev/null
+++ b/spec/ruby/core/io/shared/codepoints.rb
@@ -0,0 +1,54 @@
+# -*- encoding: utf-8 -*-
+require_relative '../fixtures/classes'
+
+describe :io_codepoints, shared: true do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ @enum = @io.send(@method)
+ end
+
+ after :each do
+ @io.close
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ @enum.should be_an_instance_of(Enumerator)
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @enum.size.should == nil
+ end
+ end
+ end
+ end
+
+ it "yields each codepoint" do
+ @enum.first(25).should == [
+ 86, 111, 105, 99, 105, 32, 108, 97, 32, 108, 105, 103, 110,
+ 101, 32, 117, 110, 101, 46, 10, 81, 117, 105, 32, 232
+ ]
+ end
+
+ it "yields each codepoint starting from the current position" do
+ @io.pos = 130
+ @enum.to_a.should == [101, 32, 115, 105, 120, 46, 10]
+ end
+
+ it "raises an error if reading invalid sequence" do
+ @io.pos = 60 # inside of a multibyte sequence
+ -> { @enum.first }.should raise_error(ArgumentError)
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @enum.to_a
+ $_.should == "test"
+ end
+
+ it "raises an IOError when self is not readable" do
+ -> { IOSpecs.closed_io.send(@method).to_a }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/shared/each.rb b/spec/ruby/core/io/shared/each.rb
new file mode 100644
index 0000000000..02bbe19c1a
--- /dev/null
+++ b/spec/ruby/core/io/shared/each.rb
@@ -0,0 +1,267 @@
+# -*- encoding: utf-8 -*-
+require_relative '../fixtures/classes'
+
+describe :io_each, shared: true do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ ScratchPad.record []
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ describe "with no separator" do
+ it "yields each line to the passed block" do
+ @io.send(@method) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines
+ end
+
+ it "yields each line starting from the current position" do
+ @io.pos = 41
+ @io.send(@method) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines[2..-1]
+ end
+
+ it "returns self" do
+ @io.send(@method) { |l| l }.should equal(@io)
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.send(@method) { |s| s }
+ $_.should == "test"
+ end
+
+ it "returns self" do
+ @io.send(@method) { |l| l }.should equal(@io)
+ end
+
+ it "raises an IOError when self is not readable" do
+ -> { IOSpecs.closed_io.send(@method) {} }.should raise_error(IOError)
+ end
+
+ it "makes line count accessible via lineno" do
+ @io.send(@method) { ScratchPad << @io.lineno }
+ ScratchPad.recorded.should == [ 1,2,3,4,5,6,7,8,9 ]
+ end
+
+ it "makes line count accessible via $." do
+ @io.send(@method) { ScratchPad << $. }
+ ScratchPad.recorded.should == [ 1,2,3,4,5,6,7,8,9 ]
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ enum = @io.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+
+ enum.each { |l| ScratchPad << l }
+ ScratchPad.recorded.should == IOSpecs.lines
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @io.send(@method).size.should == nil
+ end
+ end
+ end
+ end
+ end
+
+ describe "with limit" do
+ describe "when limit is 0" do
+ it "raises an ArgumentError" do
+ # must pass block so Enumerator is evaluated and raises
+ -> { @io.send(@method, 0){} }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "does not accept Integers that don't fit in a C off_t" do
+ -> { @io.send(@method, 2**128){} }.should raise_error(RangeError)
+ end
+ end
+
+ describe "when passed a String containing one space as a separator" do
+ it "uses the passed argument as the line separator" do
+ @io.send(@method, " ") { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines_space_separator
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.send(@method, " ") { |s| }
+ $_.should == "test"
+ end
+
+ it "tries to convert the passed separator to a String using #to_str" do
+ obj = mock("to_str")
+ obj.stub!(:to_str).and_return(" ")
+
+ @io.send(@method, obj) { |l| ScratchPad << l }
+ ScratchPad.recorded.should == IOSpecs.lines_space_separator
+ end
+ end
+
+ describe "when passed nil as a separator" do
+ it "yields self's content starting from the current position when the passed separator is nil" do
+ @io.pos = 100
+ @io.send(@method, nil) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"]
+ end
+ end
+
+ describe "when passed an empty String as a separator" do
+ it "yields each paragraph" do
+ @io.send(@method, "") { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.paragraphs
+ end
+
+ it "discards leading newlines" do
+ @io.readline
+ @io.readline
+ @io.send(@method, "") { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.paragraphs[1..-1]
+ end
+ end
+
+ describe "with both separator and limit" do
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ enum = @io.send(@method, nil, 1024)
+ enum.should be_an_instance_of(Enumerator)
+
+ enum.each { |l| ScratchPad << l }
+ ScratchPad.recorded.should == [IOSpecs.lines.join]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @io.send(@method, nil, 1024).size.should == nil
+ end
+ end
+ end
+ end
+
+ describe "when a block is given" do
+ it "accepts an empty block" do
+ @io.send(@method, nil, 1024) {}.should equal(@io)
+ end
+
+ describe "when passed nil as a separator" do
+ it "yields self's content starting from the current position when the passed separator is nil" do
+ @io.pos = 100
+ @io.send(@method, nil, 1024) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"]
+ end
+ end
+
+ describe "when passed an empty String as a separator" do
+ it "yields each paragraph" do
+ @io.send(@method, "", 1024) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.paragraphs
+ end
+
+ it "discards leading newlines" do
+ @io.readline
+ @io.readline
+ @io.send(@method, "", 1024) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.paragraphs[1..-1]
+ end
+ end
+ end
+ end
+
+ describe "when passed chomp" do
+ it "yields each line without trailing newline characters to the passed block" do
+ @io.send(@method, chomp: true) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines_without_newline_characters
+ end
+
+ ruby_version_is "3.0" do
+ it "raises exception when options passed as Hash" do
+ -> {
+ @io.send(@method, { chomp: true }) { |s| }
+ }.should raise_error(TypeError)
+
+ -> {
+ @io.send(@method, "\n", 1, { chomp: true }) { |s| }
+ }.should raise_error(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
+ end
+ end
+ end
+
+ describe "when passed chomp and a separator" do
+ it "yields each line without separator to the passed block" do
+ @io.send(@method, " ", chomp: true) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines_space_separator_without_trailing_spaces
+ end
+ end
+
+ describe "when passed chomp and empty line as a separator" do
+ it "yields each paragraph without trailing new line characters" do
+ @io.send(@method, "", 1024, chomp: true) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.paragraphs_without_trailing_new_line_characters
+ end
+ end
+
+ describe "when passed chomp and nil as a separator" do
+ ruby_version_is "3.2" do
+ it "yields self's content" do
+ @io.pos = 100
+ @io.send(@method, nil, chomp: true) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"]
+ end
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "yields self's content without trailing new line character" do
+ @io.pos = 100
+ @io.send(@method, nil, chomp: true) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six."]
+ end
+ end
+ end
+
+ describe "when passed chomp, nil as a separator, and a limit" do
+ it "yields each line of limit size without truncating trailing new line character" do
+ # 43 - is a size of the 1st paragraph in the file
+ @io.send(@method, nil, 43, chomp: true) { |s| ScratchPad << s }
+
+ ScratchPad.recorded.should == [
+ "Voici la ligne une.\nQui è la linea due.\n\n\n",
+ "Aquí está la línea tres.\n" + "Hier ist Zeile ",
+ "vier.\n\nEstá aqui a linha cinco.\nHere is li",
+ "ne six.\n"
+ ]
+ end
+ end
+
+ describe "when passed too many arguments" do
+ it "raises ArgumentError" do
+ -> {
+ @io.send(@method, "", 1, "excess argument", chomp: true) {}
+ }.should raise_error(ArgumentError)
+ end
+ end
+end
+
+describe :io_each_default_separator, shared: true do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ ScratchPad.record []
+ suppress_warning {@sep, $/ = $/, " "}
+ end
+
+ after :each do
+ @io.close if @io
+ suppress_warning {$/ = @sep}
+ end
+
+ it "uses $/ as the default line separator" do
+ @io.send(@method) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines_space_separator
+ end
+end
diff --git a/spec/ruby/core/io/shared/gets_ascii.rb b/spec/ruby/core/io/shared/gets_ascii.rb
new file mode 100644
index 0000000000..2a8fe3c9a5
--- /dev/null
+++ b/spec/ruby/core/io/shared/gets_ascii.rb
@@ -0,0 +1,19 @@
+# -*- encoding: binary -*-
+describe :io_gets_ascii, shared: true do
+ describe "with ASCII separator" do
+ before :each do
+ @name = tmp("gets_specs.txt")
+ touch(@name, "wb") { |f| f.print "this is a test\xFFtesty\ntestier" }
+
+ File.open(@name, "rb") { |f| @data = f.send(@method, "\xFF") }
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "returns the separator's character representation" do
+ @data.should == "this is a test\xFF"
+ end
+ end
+end
diff --git a/spec/ruby/core/io/shared/new.rb b/spec/ruby/core/io/shared/new.rb
new file mode 100644
index 0000000000..7677aada6e
--- /dev/null
+++ b/spec/ruby/core/io/shared/new.rb
@@ -0,0 +1,404 @@
+require_relative '../fixtures/classes'
+
+# NOTE: should be syncronized with library/stringio/initialize_spec.rb
+
+# This group of specs may ONLY contain specs that do successfully create
+# an IO instance from the file descriptor returned by #new_fd helper.
+describe :io_new, shared: true do
+ before :each do
+ @name = tmp("io_new.txt")
+ @fd = new_fd @name
+ @io = nil
+ end
+
+ after :each do
+ if @io
+ @io.close
+ elsif @fd
+ IO.new(@fd, "w").close
+ end
+ rm_r @name
+ end
+
+ it "creates an IO instance from an Integer argument" do
+ @io = IO.send(@method, @fd, "w")
+ @io.should be_an_instance_of(IO)
+ end
+
+ it "creates an IO instance when STDOUT is closed" do
+ suppress_warning do
+ stdout = STDOUT
+ stdout_file = tmp("stdout.txt")
+
+ begin
+ @io = IO.send(@method, @fd, "w")
+ @io.should be_an_instance_of(IO)
+ ensure
+ STDOUT = stdout
+ rm_r stdout_file
+ end
+ end
+ end
+
+ it "creates an IO instance when STDERR is closed" do
+ suppress_warning do
+ stderr = STDERR
+ stderr_file = tmp("stderr.txt")
+ STDERR = new_io stderr_file
+ STDERR.close
+
+ begin
+ @io = IO.send(@method, @fd, "w")
+ @io.should be_an_instance_of(IO)
+ ensure
+ STDERR = stderr
+ rm_r stderr_file
+ end
+ end
+ end
+
+ it "calls #to_int on an object to convert to an Integer" do
+ obj = mock("file descriptor")
+ obj.should_receive(:to_int).and_return(@fd)
+ @io = IO.send(@method, obj, "w")
+ @io.should be_an_instance_of(IO)
+ end
+
+ it "accepts a :mode option" do
+ @io = IO.send(@method, @fd, mode: "w")
+ @io.write("foo").should == 3
+ end
+
+ it "accepts a mode argument set to nil with a valid :mode option" do
+ @io = IO.send(@method, @fd, nil, mode: "w")
+ @io.write("foo").should == 3
+ end
+
+ it "accepts a mode argument with a :mode option set to nil" do
+ @io = IO.send(@method, @fd, "w", mode: nil)
+ @io.write("foo").should == 3
+ end
+
+ it "uses the external encoding specified in the mode argument" do
+ @io = IO.send(@method, @fd, 'w:utf-8')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ end
+
+ it "uses the external and the internal encoding specified in the mode argument" do
+ @io = IO.send(@method, @fd, 'w:utf-8:ISO-8859-1')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "uses the external encoding specified via the :external_encoding option" do
+ @io = IO.send(@method, @fd, 'w', external_encoding: 'utf-8')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ end
+
+ it "uses the internal encoding specified via the :internal_encoding option" do
+ @io = IO.send(@method, @fd, 'w', internal_encoding: 'ibm866')
+ @io.internal_encoding.to_s.should == 'IBM866'
+ end
+
+ it "uses the colon-separated encodings specified via the :encoding option" do
+ @io = IO.send(@method, @fd, 'w', encoding: 'utf-8:ISO-8859-1')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "uses the :encoding option as the external encoding when only one is given" do
+ @io = IO.send(@method, @fd, 'w', encoding: 'ISO-8859-1')
+ @io.external_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "uses the :encoding options as the external encoding when it's an Encoding object" do
+ @io = IO.send(@method, @fd, 'w', encoding: Encoding::ISO_8859_1)
+ @io.external_encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "ignores the :encoding option when the :external_encoding option is present" do
+ -> {
+ @io = IO.send(@method, @fd, 'w', external_encoding: 'utf-8', encoding: 'iso-8859-1:iso-8859-1')
+ }.should complain(/Ignoring encoding parameter/)
+ @io.external_encoding.to_s.should == 'UTF-8'
+ end
+
+ it "ignores the :encoding option when the :internal_encoding option is present" do
+ -> {
+ @io = IO.send(@method, @fd, 'w', internal_encoding: 'ibm866', encoding: 'iso-8859-1:iso-8859-1')
+ }.should complain(/Ignoring encoding parameter/)
+ @io.internal_encoding.to_s.should == 'IBM866'
+ end
+
+ it "uses the encoding specified via the :mode option hash" do
+ @io = IO.send(@method, @fd, mode: 'w:utf-8:ISO-8859-1')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "ignores the :internal_encoding option when the same as the external encoding" do
+ @io = IO.send(@method, @fd, 'w', external_encoding: 'utf-8', internal_encoding: 'utf-8')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == ''
+ end
+
+ it "sets internal encoding to nil when passed '-'" do
+ @io = IO.send(@method, @fd, 'w', external_encoding: 'utf-8', internal_encoding: '-')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == ''
+ end
+
+ it "sets binmode from mode string" do
+ @io = IO.send(@method, @fd, 'wb')
+ @io.should.binmode?
+ end
+
+ it "does not set binmode without being asked" do
+ @io = IO.send(@method, @fd, 'w')
+ @io.should_not.binmode?
+ end
+
+ it "sets binmode from :binmode option" do
+ @io = IO.send(@method, @fd, 'w', binmode: true)
+ @io.should.binmode?
+ end
+
+ it "does not set binmode from false :binmode" do
+ @io = IO.send(@method, @fd, 'w', binmode: false)
+ @io.should_not.binmode?
+ end
+
+ it "sets external encoding to binary with binmode in mode string" do
+ @io = IO.send(@method, @fd, 'wb')
+ @io.external_encoding.should == Encoding::BINARY
+ end
+
+ # #5917
+ it "sets external encoding to binary with :binmode option" do
+ @io = IO.send(@method, @fd, 'w', binmode: true)
+ @io.external_encoding.should == Encoding::BINARY
+ end
+
+ it "does not use binary encoding when mode encoding is specified" do
+ @io = IO.send(@method, @fd, 'wb:iso-8859-1')
+ @io.external_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "does not use binary encoding when :encoding option is specified" do
+ @io = IO.send(@method, @fd, 'wb', encoding: "iso-8859-1")
+ @io.external_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "does not use binary encoding when :external_encoding option is specified" do
+ @io = IO.send(@method, @fd, 'wb', external_encoding: "iso-8859-1")
+ @io.external_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "does not use binary encoding when :internal_encoding option is specified" do
+ @io = IO.send(@method, @fd, 'wb', internal_encoding: "ibm866")
+ @io.internal_encoding.to_s.should == 'IBM866'
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "accepts nil options" do
+ @io = suppress_keyword_warning do
+ IO.send(@method, @fd, 'w', nil)
+ end
+ @io.write("foo").should == 3
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "raises ArgumentError for nil options" do
+ -> {
+ IO.send(@method, @fd, 'w', nil)
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "coerces mode with #to_str" do
+ mode = mock("mode")
+ mode.should_receive(:to_str).and_return('w')
+ @io = IO.send(@method, @fd, mode)
+ end
+
+ it "coerces mode with #to_int" do
+ mode = mock("mode")
+ mode.should_receive(:to_int).and_return(File::WRONLY)
+ @io = IO.send(@method, @fd, mode)
+ end
+
+ it "coerces mode with #to_str when passed in options" do
+ mode = mock("mode")
+ mode.should_receive(:to_str).and_return('w')
+ @io = IO.send(@method, @fd, mode: mode)
+ end
+
+ it "coerces mode with #to_int when passed in options" do
+ mode = mock("mode")
+ mode.should_receive(:to_int).and_return(File::WRONLY)
+ @io = IO.send(@method, @fd, mode: mode)
+ end
+
+ it "coerces :encoding option with #to_str" do
+ encoding = mock("encoding")
+ encoding.should_receive(:to_str).and_return('utf-8')
+ @io = IO.send(@method, @fd, 'w', encoding: encoding)
+ end
+
+ it "coerces :external_encoding option with #to_str" do
+ encoding = mock("encoding")
+ encoding.should_receive(:to_str).and_return('utf-8')
+ @io = IO.send(@method, @fd, 'w', external_encoding: encoding)
+ end
+
+ it "coerces :internal_encoding option with #to_str" do
+ encoding = mock("encoding")
+ encoding.should_receive(:to_str).at_least(:once).and_return('utf-8')
+ @io = IO.send(@method, @fd, 'w', internal_encoding: encoding)
+ end
+
+ it "coerces options as third argument with #to_hash" do
+ options = mock("options")
+ options.should_receive(:to_hash).and_return({})
+ @io = IO.send(@method, @fd, 'w', **options)
+ end
+
+ it "coerces options as second argument with #to_hash" do
+ options = mock("options")
+ options.should_receive(:to_hash).and_return({})
+ @io = IO.send(@method, @fd, **options)
+ end
+
+ it "accepts an :autoclose option" do
+ @io = IO.send(@method, @fd, 'w', autoclose: false)
+ @io.should_not.autoclose?
+ @io.autoclose = true
+ end
+
+ it "accepts any truthy option :autoclose" do
+ @io = IO.send(@method, @fd, 'w', autoclose: 42)
+ @io.should.autoclose?
+ end
+end
+
+# This group of specs may ONLY contain specs that do not actually create
+# an IO instance from the file descriptor returned by #new_fd helper.
+describe :io_new_errors, shared: true do
+ before :each do
+ @name = tmp("io_new.txt")
+ @fd = new_fd @name
+ end
+
+ after :each do
+ IO.new(@fd, "w").close if @fd
+ rm_r @name
+ end
+
+ it "raises an Errno::EBADF if the file descriptor is not valid" do
+ -> { IO.send(@method, -1, "w") }.should raise_error(Errno::EBADF)
+ end
+
+ it "raises an IOError if passed a closed stream" do
+ -> { IO.send(@method, IOSpecs.closed_io.fileno, 'w') }.should raise_error(IOError)
+ end
+
+ platform_is_not :windows do
+ it "raises an Errno::EINVAL if the new mode is not compatible with the descriptor's current mode" do
+ -> { IO.send(@method, @fd, "r") }.should raise_error(Errno::EINVAL)
+ end
+ end
+
+ it "raises ArgumentError if passed an empty mode string" do
+ -> { IO.send(@method, @fd, "") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error if passed modes two ways" do
+ -> {
+ IO.send(@method, @fd, "w", mode: "w")
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error if passed encodings two ways" do
+ -> {
+ @io = IO.send(@method, @fd, 'w:ISO-8859-1', encoding: 'ISO-8859-1')
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, 'w:ISO-8859-1', external_encoding: 'ISO-8859-1')
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, 'w:ISO-8859-1:UTF-8', internal_encoding: 'ISO-8859-1')
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error if passed matching binary/text mode two ways" do
+ -> {
+ @io = IO.send(@method, @fd, "wb", binmode: true)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, "wt", textmode: true)
+ }.should raise_error(ArgumentError)
+
+ -> {
+ @io = IO.send(@method, @fd, "wb", textmode: false)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, "wt", binmode: false)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error if passed conflicting binary/text mode two ways" do
+ -> {
+ @io = IO.send(@method, @fd, "wb", binmode: false)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, "wt", textmode: false)
+ }.should raise_error(ArgumentError)
+
+ -> {
+ @io = IO.send(@method, @fd, "wb", textmode: true)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, "wt", binmode: true)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error when trying to set both binmode and textmode" do
+ -> {
+ @io = IO.send(@method, @fd, "w", textmode: true, binmode: true)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, File::Constants::WRONLY, textmode: true, binmode: true)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if not passed a hash or nil for options" do
+ -> {
+ @io = IO.send(@method, @fd, 'w', false)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, false, false)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, nil, false)
+ }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "raises TypeError if passed a hash for mode and nil for options" do
+ -> {
+ suppress_keyword_warning do
+ @io = IO.send(@method, @fd, {mode: 'w'}, nil)
+ end
+ }.should raise_error(TypeError)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "raises ArgumentError if passed a hash for mode and nil for options" do
+ -> {
+ @io = IO.send(@method, @fd, {mode: 'w'}, nil)
+ }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/io/shared/pos.rb b/spec/ruby/core/io/shared/pos.rb
new file mode 100644
index 0000000000..3fdd3eb2b3
--- /dev/null
+++ b/spec/ruby/core/io/shared/pos.rb
@@ -0,0 +1,78 @@
+describe :io_pos, shared: true do
+ before :each do
+ @fname = tmp('test.txt')
+ File.open(@fname, 'w') { |f| f.write "123" }
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ it "gets the offset" do
+ File.open @fname do |f|
+ f.send(@method).should == 0
+ f.read 1
+ f.send(@method).should == 1
+ f.read 2
+ f.send(@method).should == 3
+ end
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.send(@method) }.should raise_error(IOError)
+ end
+
+ it "resets #eof?" do
+ open @fname do |io|
+ io.read 1
+ io.read 1
+ io.send(@method)
+ io.should_not.eof?
+ end
+ end
+end
+
+describe :io_set_pos, shared: true do
+ before :each do
+ @fname = tmp('test.txt')
+ File.open(@fname, 'w') { |f| f.write "123" }
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ it "sets the offset" do
+ File.open @fname do |f|
+ val1 = f.read 1
+ f.send @method, 0
+ f.read(1).should == val1
+ end
+ end
+
+ it "converts arguments to Integers" do
+ File.open @fname do |io|
+ o = mock("o")
+ o.should_receive(:to_int).and_return(1)
+
+ io.send @method, o
+ io.pos.should == 1
+ end
+ end
+
+ it "raises TypeError when cannot convert implicitly argument to Integer" do
+ File.open @fname do |io|
+ -> { io.send @method, Object.new }.should raise_error(TypeError, "no implicit conversion of Object into Integer")
+ end
+ end
+
+ it "does not accept Integers that don't fit in a C off_t" do
+ File.open @fname do |io|
+ -> { io.send @method, 2**128 }.should raise_error(RangeError)
+ end
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.send @method, 0 }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/shared/readlines.rb b/spec/ruby/core/io/shared/readlines.rb
new file mode 100644
index 0000000000..7681e1b5c1
--- /dev/null
+++ b/spec/ruby/core/io/shared/readlines.rb
@@ -0,0 +1,263 @@
+describe :io_readlines, shared: true do
+ it "raises TypeError if the first parameter is nil" do
+ -> { IO.send(@method, nil, &@object) }.should raise_error(TypeError)
+ end
+
+ it "raises an Errno::ENOENT if the file does not exist" do
+ name = tmp("nonexistent.txt")
+ -> { IO.send(@method, name, &@object) }.should raise_error(Errno::ENOENT)
+ end
+
+ it "yields a single string with entire content when the separator is nil" do
+ result = IO.send(@method, @name, nil, &@object)
+ (result ? result : ScratchPad.recorded).should == [IO.read(@name)]
+ end
+
+ it "yields a sequence of paragraphs when the separator is an empty string" do
+ result = IO.send(@method, @name, "", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_empty_separator
+ end
+
+ it "yields a sequence of lines without trailing newline characters when chomp is passed" do
+ result = IO.send(@method, @name, chomp: true, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_without_newline_characters
+ end
+end
+
+describe :io_readlines_options_19, shared: true do
+ before :each do
+ @filename = tmp("io readlines options")
+ end
+
+ after :each do
+ rm_r @filename
+ end
+
+ describe "when passed name" do
+ it "calls #to_path to convert the name" do
+ name = mock("io name to_path")
+ name.should_receive(:to_path).and_return(@name)
+ IO.send(@method, name, &@object)
+ end
+
+ it "defaults to $/ as the separator" do
+ result = IO.send(@method, @name, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines
+ end
+ end
+
+ describe "when passed name, object" do
+ it "calls #to_str to convert the object to a separator" do
+ sep = mock("io readlines separator")
+ sep.should_receive(:to_str).at_least(1).and_return(" ")
+ result = IO.send(@method, @name, sep, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator
+ end
+
+ describe "when the object is an Integer" do
+ before :each do
+ @sep = $/
+ end
+
+ after :each do
+ suppress_warning {$/ = @sep}
+ end
+
+ it "defaults to $/ as the separator" do
+ suppress_warning {$/ = " "}
+ result = IO.send(@method, @name, 10, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "uses the object as a limit if it is an Integer" do
+ result = IO.send(@method, @name, 10, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_limit
+ end
+
+ it "ignores the object as a limit if it is negative" do
+ result = IO.send(@method, @name, -2, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines
+ end
+
+ it "does not accept Integers that don't fit in a C off_t" do
+ -> { IO.send(@method, @name, 2**128, &@object) }.should raise_error(RangeError)
+ end
+
+ ruby_bug "#18767", ""..."3.3" do
+ describe "when passed limit" do
+ it "raises ArgumentError when passed 0 as a limit" do
+ -> { IO.send(@method, @name, 0, &@object) }.should raise_error(ArgumentError)
+ end
+ end
+ end
+ end
+
+ describe "when the object is a String" do
+ it "uses the value as the separator" do
+ result = IO.send(@method, @name, " ", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator
+ end
+
+ it "accepts non-ASCII data as separator" do
+ result = IO.send(@method, @name, "\303\250".force_encoding("utf-8"), &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_arbitrary_separator
+ end
+ end
+
+ describe "when the object is an options Hash" do
+ ruby_version_is "3.0" do
+ it "raises TypeError exception" do
+ -> {
+ IO.send(@method, @name, { chomp: true }, &@object)
+ }.should raise_error(TypeError)
+ end
+ end
+ end
+
+ describe "when the object is neither Integer nor String" do
+ it "raises TypeError exception" do
+ obj = mock("not io readlines limit")
+
+ -> {
+ IO.send(@method, @name, obj, &@object)
+ }.should raise_error(TypeError)
+ end
+ end
+ end
+
+ describe "when passed name, keyword arguments" do
+ it "uses the keyword arguments as options" do
+ result = IO.send(@method, @name, mode: "r", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines
+ end
+ end
+
+ describe "when passed name, object, object" do
+ describe "when the first object is a String" do
+ it "uses the second object as a limit if it is an Integer" do
+ result = IO.send(@method, @name, " ", 10, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "calls #to_int to convert the second object" do
+ limit = mock("io readlines limit")
+ limit.should_receive(:to_int).at_least(1).and_return(10)
+ result = IO.send(@method, @name, " ", limit, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+ end
+
+ describe "when the first object is not a String or Integer" do
+ it "calls #to_str to convert the object to a String" do
+ sep = mock("io readlines separator")
+ sep.should_receive(:to_str).at_least(1).and_return(" ")
+ result = IO.send(@method, @name, sep, 10, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "uses the second object as a limit if it is an Integer" do
+ result = IO.send(@method, @name, " ", 10, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "calls #to_int to convert the second object" do
+ limit = mock("io readlines limit")
+ limit.should_receive(:to_int).at_least(1).and_return(10)
+ result = IO.send(@method, @name, " ", limit, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+ end
+
+ describe "when the second object is neither Integer nor String" do
+ it "raises TypeError exception" do
+ obj = mock("not io readlines limit")
+
+ -> {
+ IO.send(@method, @name, " ", obj, &@object)
+ }.should raise_error(TypeError)
+ end
+ end
+
+ describe "when the second object is an options Hash" do
+ ruby_version_is "3.0" do
+ it "raises TypeError exception" do
+ -> {
+ IO.send(@method, @name, "", { chomp: true }, &@object)
+ }.should raise_error(TypeError)
+ end
+ end
+ end
+ end
+
+ describe "when passed name, object, keyword arguments" do
+ describe "when the first object is an Integer" do
+ it "uses the keyword arguments as options" do
+ -> do
+ IO.send(@method, @filename, 10, mode: "w", &@object)
+ end.should raise_error(IOError)
+ end
+ end
+
+ describe "when the first object is a String" do
+ it "uses the keyword arguments as options" do
+ -> do
+ IO.send(@method, @filename, " ", mode: "w", &@object)
+ end.should raise_error(IOError)
+ end
+ end
+
+ describe "when the first object is not a String or Integer" do
+ it "uses the keyword arguments as options" do
+ sep = mock("io readlines separator")
+ sep.should_receive(:to_str).at_least(1).and_return(" ")
+
+ -> do
+ IO.send(@method, @filename, sep, mode: "w", &@object)
+ end.should raise_error(IOError)
+ end
+ end
+ end
+
+ describe "when passed name, separator, limit, keyword arguments" do
+ it "calls #to_path to convert the name object" do
+ name = mock("io name to_path")
+ name.should_receive(:to_path).and_return(@name)
+ result = IO.send(@method, name, " ", 10, mode: "r", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "calls #to_str to convert the separator object" do
+ sep = mock("io readlines separator")
+ sep.should_receive(:to_str).at_least(1).and_return(" ")
+ result = IO.send(@method, @name, sep, 10, mode: "r", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "calls #to_int to convert the limit argument" do
+ limit = mock("io readlines limit")
+ limit.should_receive(:to_int).at_least(1).and_return(10)
+ result = IO.send(@method, @name, " ", limit, mode: "r", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "uses the keyword arguments as options" do
+ -> do
+ IO.send(@method, @filename, " ", 10, mode: "w", &@object)
+ end.should raise_error(IOError)
+ end
+
+ describe "when passed chomp, nil as a separator, and a limit" do
+ it "yields each line of limit size without truncating trailing new line character" do
+ # 43 - is a size of the 1st paragraph in the file
+ result = IO.send(@method, @name, nil, 43, chomp: true, &@object)
+
+ (result ? result : ScratchPad.recorded).should == [
+ "Voici la ligne une.\nQui è la linea due.\n\n\n",
+ "Aquí está la línea tres.\n" + "Hier ist Zeile ",
+ "vier.\n\nEstá aqui a linha cinco.\nHere is li",
+ "ne six.\n"
+ ]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/shared/tty.rb b/spec/ruby/core/io/shared/tty.rb
new file mode 100644
index 0000000000..89ac08ec86
--- /dev/null
+++ b/spec/ruby/core/io/shared/tty.rb
@@ -0,0 +1,24 @@
+require_relative '../fixtures/classes'
+
+describe :io_tty, shared: true do
+ platform_is_not :windows do
+ it "returns true if this stream is a terminal device (TTY)" do
+ begin
+ # check to enabled tty
+ File.open('/dev/tty') {}
+ rescue Errno::ENXIO
+ skip "workaround for not configured environment like OS X"
+ else
+ File.open('/dev/tty') { |f| f.send(@method) }.should == true
+ end
+ end
+ end
+
+ it "returns false if this stream is not a terminal device (TTY)" do
+ File.open(__FILE__) { |f| f.send(@method) }.should == false
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.send @method }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/shared/write.rb b/spec/ruby/core/io/shared/write.rb
new file mode 100644
index 0000000000..9503f94212
--- /dev/null
+++ b/spec/ruby/core/io/shared/write.rb
@@ -0,0 +1,99 @@
+# encoding: utf-8
+require_relative '../fixtures/classes'
+
+describe :io_write, shared: true do
+ before :each do
+ @filename = tmp("IO_syswrite_file") + $$.to_s
+ File.open(@filename, "w") do |file|
+ file.send(@method, "012345678901234567890123456789")
+ end
+ @file = File.open(@filename, "r+")
+ @readonly_file = File.open(@filename)
+ end
+
+ after :each do
+ @readonly_file.close if @readonly_file
+ @file.close if @file
+ rm_r @filename
+ end
+
+ it "coerces the argument to a string using to_s" do
+ (obj = mock('test')).should_receive(:to_s).and_return('a string')
+ @file.send(@method, obj)
+ end
+
+ it "checks if the file is writable if writing more than zero bytes" do
+ -> { @readonly_file.send(@method, "abcde") }.should raise_error(IOError)
+ end
+
+ it "returns the number of bytes written" do
+ written = @file.send(@method, "abcde")
+ written.should == 5
+ end
+
+ it "invokes to_s on non-String argument" do
+ data = "abcdefgh9876"
+ (obj = mock(data)).should_receive(:to_s).and_return(data)
+ @file.send(@method, obj)
+ @file.seek(0)
+ @file.read(data.size).should == data
+ end
+
+ it "writes all of the string's bytes without buffering if mode is sync" do
+ @file.sync = true
+ written = @file.send(@method, "abcde")
+ written.should == 5
+ File.open(@filename) do |file|
+ file.read(10).should == "abcde56789"
+ end
+ end
+
+ it "does not warn if called after IO#read" do
+ @file.read(5)
+ -> { @file.send(@method, "fghij") }.should_not complain
+ end
+
+ it "writes to the current position after IO#read" do
+ @file.read(5)
+ @file.send(@method, "abcd")
+ @file.rewind
+ @file.read.should == "01234abcd901234567890123456789"
+ end
+
+ it "advances the file position by the count of given bytes" do
+ @file.send(@method, "abcde")
+ @file.read(10).should == "5678901234"
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.send(@method, "hello") }.should raise_error(IOError)
+ end
+
+ describe "on a pipe" do
+ before :each do
+ @r, @w = IO.pipe
+ end
+
+ after :each do
+ @r.close
+ @w.close
+ end
+
+ it "writes the given String to the pipe" do
+ @w.send(@method, "foo")
+ @w.close
+ @r.read.should == "foo"
+ end
+
+ # [ruby-core:90895] MJIT worker may leave fd open in a forked child.
+ # For instance, MJIT creates a worker before @r.close with fork(), @r.close happens,
+ # and the MJIT worker keeps the pipe open until the worker execve().
+ # TODO: consider acquiring GVL from MJIT worker.
+ guard_not -> { defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? } do
+ it "raises Errno::EPIPE if the read end is closed and does not die from SIGPIPE" do
+ @r.close
+ -> { @w.send(@method, "foo") }.should raise_error(Errno::EPIPE, /Broken pipe/)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/stat_spec.rb b/spec/ruby/core/io/stat_spec.rb
new file mode 100644
index 0000000000..58eba02b8f
--- /dev/null
+++ b/spec/ruby/core/io/stat_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#stat" do
+ before :each do
+ @io = IO.popen 'cat', "r+"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.stat }.should raise_error(IOError)
+ end
+
+ it "returns a File::Stat object for the stream" do
+ STDOUT.stat.should be_an_instance_of(File::Stat)
+ end
+
+ it "can stat pipes" do
+ @io.stat.should be_an_instance_of(File::Stat)
+ end
+end
diff --git a/spec/ruby/core/io/sync_spec.rb b/spec/ruby/core/io/sync_spec.rb
new file mode 100644
index 0000000000..993b7ee244
--- /dev/null
+++ b/spec/ruby/core/io/sync_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#sync=" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "sets the sync mode to true or false" do
+ @io.sync = true
+ @io.sync.should == true
+ @io.sync = false
+ @io.sync.should == false
+ end
+
+ it "accepts non-boolean arguments" do
+ @io.sync = 10
+ @io.sync.should == true
+ @io.sync = nil
+ @io.sync.should == false
+ @io.sync = Object.new
+ @io.sync.should == true
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.sync = true }.should raise_error(IOError)
+ end
+end
+
+describe "IO#sync" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "returns the current sync mode" do
+ @io.sync.should == false
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.sync }.should raise_error(IOError)
+ end
+end
+
+describe "IO#sync" do
+ it "is false by default for STDIN" do
+ STDIN.sync.should == false
+ end
+
+ it "is false by default for STDOUT" do
+ STDOUT.sync.should == false
+ end
+
+ it "is true by default for STDERR" do
+ STDERR.sync.should == true
+ end
+end
diff --git a/spec/ruby/core/io/sysopen_spec.rb b/spec/ruby/core/io/sysopen_spec.rb
new file mode 100644
index 0000000000..7ad379df3a
--- /dev/null
+++ b/spec/ruby/core/io/sysopen_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+
+describe "IO.sysopen" do
+ before :each do
+ @filename = tmp("rubinius-spec-io-sysopen-#{$$}.txt")
+ @fd = nil
+ end
+
+ after :each do
+ IO.for_fd(@fd).close if @fd
+ rm_r @filename
+ end
+
+ it "returns the file descriptor for a given path" do
+ @fd = IO.sysopen(@filename, "w")
+ @fd.should be_kind_of(Integer)
+ @fd.should_not equal(0)
+ end
+
+ # opening a directory is not supported on Windows
+ platform_is_not :windows do
+ it "works on directories" do
+ @fd = IO.sysopen(tmp("")) # /tmp
+ @fd.should be_kind_of(Integer)
+ @fd.should_not equal(0)
+ end
+ end
+
+ it "calls #to_path to convert an object to a path" do
+ path = mock('sysopen to_path')
+ path.should_receive(:to_path).and_return(@filename)
+ @fd = IO.sysopen(path, 'w')
+ end
+
+ it "accepts a mode as second argument" do
+ -> { @fd = IO.sysopen(@filename, "w") }.should_not raise_error
+ @fd.should_not equal(0)
+ end
+
+ it "accepts permissions as third argument" do
+ @fd = IO.sysopen(@filename, "w", 777)
+ @fd.should_not equal(0)
+ end
+
+ it "accepts mode & permission that are nil" do
+ touch @filename # create the file
+ @fd = IO.sysopen(@filename, nil, nil)
+ @fd.should_not equal(0)
+ end
+end
diff --git a/spec/ruby/core/io/sysread_spec.rb b/spec/ruby/core/io/sysread_spec.rb
new file mode 100644
index 0000000000..e7f63cefec
--- /dev/null
+++ b/spec/ruby/core/io/sysread_spec.rb
@@ -0,0 +1,132 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#sysread on a file" do
+ before :each do
+ @file_name = tmp("IO_sysread_file") + $$.to_s
+ File.open(@file_name, "w") do |f|
+ # write some stuff
+ f.write("012345678901234567890123456789\nabcdef")
+ end
+ @file = File.open(@file_name, "r+")
+ end
+
+ after :each do
+ @file.close
+ rm_r @file_name
+ end
+
+ it "reads the specified number of bytes from the file" do
+ @file.sysread(15).should == "012345678901234"
+ end
+
+ it "reads the specified number of bytes from the file to the buffer" do
+ buf = "" # empty buffer
+ @file.sysread(15, buf).should == buf
+ buf.should == "012345678901234"
+
+ @file.rewind
+
+ buf = "ABCDE" # small buffer
+ @file.sysread(15, buf).should == buf
+ buf.should == "012345678901234"
+
+ @file.rewind
+
+ buf = "ABCDE" * 5 # large buffer
+ @file.sysread(15, buf).should == buf
+ buf.should == "012345678901234"
+ end
+
+ it "coerces the second argument to string and uses it as a buffer" do
+ buf = "ABCDE"
+ (obj = mock("buff")).should_receive(:to_str).any_number_of_times.and_return(buf)
+ @file.sysread(15, obj).should == buf
+ buf.should == "012345678901234"
+ end
+
+ it "advances the position of the file by the specified number of bytes" do
+ @file.sysread(15)
+ @file.sysread(5).should == "56789"
+ end
+
+ it "raises an error when called after buffered reads" do
+ @file.readline
+ -> { @file.sysread(5) }.should raise_error(IOError)
+ end
+
+ it "reads normally even when called immediately after a buffered IO#read" do
+ @file.read(15)
+ @file.sysread(5).should == "56789"
+ end
+
+ it "does not raise error if called after IO#read followed by IO#write" do
+ @file.read(5)
+ @file.write("abcde")
+ -> { @file.sysread(5) }.should_not raise_error(IOError)
+ end
+
+ it "does not raise error if called after IO#read followed by IO#syswrite" do
+ @file.read(5)
+ @file.syswrite("abcde")
+ -> { @file.sysread(5) }.should_not raise_error(IOError)
+ end
+
+ it "reads updated content after the flushed buffered IO#write" do
+ @file.write("abcde")
+ @file.flush
+ @file.sysread(5).should == "56789"
+ File.open(@file_name) do |f|
+ f.sysread(10).should == "abcde56789"
+ end
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.sysread(5) }.should raise_error(IOError)
+ end
+
+ it "immediately returns an empty string if the length argument is 0" do
+ @file.sysread(0).should == ""
+ end
+
+ it "immediately returns the given buffer if the length argument is 0" do
+ buffer = "existing content"
+ @file.sysread(0, buffer).should == buffer
+ buffer.should == "existing content"
+ end
+
+ it "discards the existing buffer content upon successful read" do
+ buffer = "existing content"
+ @file.sysread(11, buffer)
+ buffer.should == "01234567890"
+ end
+
+ it "discards the existing buffer content upon error" do
+ buffer = "existing content"
+ @file.seek(0, :END)
+ -> { @file.sysread(1, buffer) }.should raise_error(EOFError)
+ buffer.should be_empty
+ end
+end
+
+describe "IO#sysread" do
+ before do
+ @read, @write = IO.pipe
+ end
+
+ after do
+ @read.close
+ @write.close
+ end
+
+ it "returns a smaller string if less than size bytes are available" do
+ @write.syswrite "ab"
+ @read.sysread(3).should == "ab"
+ end
+
+ guard_not -> { platform_is :windows and ruby_version_is ""..."3.2" } do # https://bugs.ruby-lang.org/issues/18880
+ it "raises ArgumentError when length is less than 0" do
+ -> { @read.sysread(-1) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/io/sysseek_spec.rb b/spec/ruby/core/io/sysseek_spec.rb
new file mode 100644
index 0000000000..002f2a14eb
--- /dev/null
+++ b/spec/ruby/core/io/sysseek_spec.rb
@@ -0,0 +1,49 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/pos'
+
+describe "IO#sysseek" do
+ it_behaves_like :io_set_pos, :sysseek
+end
+
+describe "IO#sysseek" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "moves the read position relative to the current position with SEEK_CUR" do
+ @io.sysseek(10, IO::SEEK_CUR)
+ @io.readline.should == "igne une.\n"
+ end
+
+ it "raises an error when called after buffered reads" do
+ @io.readline
+ -> { @io.sysseek(-5, IO::SEEK_CUR) }.should raise_error(IOError)
+ end
+
+ it "seeks normally even when called immediately after a buffered IO#read" do
+ @io.read(15)
+ @io.sysseek(-5, IO::SEEK_CUR).should == 10
+ end
+
+ it "moves the read position relative to the start with SEEK_SET" do
+ @io.sysseek(43, IO::SEEK_SET)
+ @io.readline.should == "Aquí está la línea tres.\n"
+ end
+
+ it "moves the read position relative to the end with SEEK_END" do
+ @io.sysseek(1, IO::SEEK_END)
+
+ # this is the safest way of checking the EOF when
+ # sys-* methods are invoked
+ -> { @io.sysread(1) }.should raise_error(EOFError)
+
+ @io.sysseek(-25, IO::SEEK_END)
+ @io.sysread(7).should == "cinco.\n"
+ end
+end
diff --git a/spec/ruby/core/io/syswrite_spec.rb b/spec/ruby/core/io/syswrite_spec.rb
new file mode 100644
index 0000000000..eeb8d302a7
--- /dev/null
+++ b/spec/ruby/core/io/syswrite_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/write'
+
+describe "IO#syswrite on a file" do
+ before :each do
+ @filename = tmp("IO_syswrite_file") + $$.to_s
+ File.open(@filename, "w") do |file|
+ file.syswrite("012345678901234567890123456789")
+ end
+ @file = File.open(@filename, "r+")
+ @readonly_file = File.open(@filename)
+ end
+
+ after :each do
+ @file.close
+ @readonly_file.close
+ rm_r @filename
+ end
+
+ it "writes all of the string's bytes but does not buffer them" do
+ written = @file.syswrite("abcde")
+ written.should == 5
+ File.open(@filename) do |file|
+ file.sysread(10).should == "abcde56789"
+ file.seek(0)
+ @file.fsync
+ file.sysread(10).should == "abcde56789"
+ end
+ end
+
+ it "does not modify the passed argument" do
+ File.open(@filename, "w") do |f|
+ f.set_encoding(Encoding::IBM437)
+ # A character whose codepoint differs between UTF-8 and IBM437
+ f.syswrite("Æ’".freeze)
+ end
+
+ File.binread(@filename).bytes.should == [198, 146]
+ end
+
+ it "warns if called immediately after a buffered IO#write" do
+ @file.write("abcde")
+ -> { @file.syswrite("fghij") }.should complain(/syswrite/)
+ end
+
+ it "does not warn if called after IO#write with intervening IO#sysread" do
+ @file.syswrite("abcde")
+ @file.sysread(5)
+ -> { @file.syswrite("fghij") }.should_not complain
+ end
+
+ it "writes to the actual file position when called after buffered IO#read" do
+ @file.read(5)
+ @file.syswrite("abcde")
+ File.open(@filename) do |file|
+ file.sysread(10).should == "01234abcde"
+ end
+ end
+end
+
+describe "IO#syswrite on a pipe" do
+ it "returns the written bytes if the fd is in nonblock mode and write would block" do
+ require 'io/nonblock'
+ r, w = IO.pipe
+ begin
+ w.nonblock = true
+ larger_than_pipe_capacity = 2 * 1024 * 1024
+ written = w.syswrite("a"*larger_than_pipe_capacity)
+ written.should > 0
+ written.should < larger_than_pipe_capacity
+ ensure
+ w.close
+ r.close
+ end
+ end
+end
+
+describe "IO#syswrite" do
+ it_behaves_like :io_write, :syswrite
+end
diff --git a/spec/ruby/core/io/tell_spec.rb b/spec/ruby/core/io/tell_spec.rb
new file mode 100644
index 0000000000..0d6c6b02d3
--- /dev/null
+++ b/spec/ruby/core/io/tell_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/pos'
+
+describe "IO#tell" do
+ it_behaves_like :io_pos, :tell
+end
diff --git a/spec/ruby/core/io/to_i_spec.rb b/spec/ruby/core/io/to_i_spec.rb
new file mode 100644
index 0000000000..acf138c663
--- /dev/null
+++ b/spec/ruby/core/io/to_i_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#to_i" do
+ it "returns the numeric file descriptor of the given IO object" do
+ $stdout.to_i.should == 1
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.to_i }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/to_io_spec.rb b/spec/ruby/core/io/to_io_spec.rb
new file mode 100644
index 0000000000..55a0977740
--- /dev/null
+++ b/spec/ruby/core/io/to_io_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#to_io" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "returns self for open stream" do
+ @io.to_io.should equal(@io)
+ end
+
+ it "returns self for closed stream" do
+ io = IOSpecs.closed_io
+ io.to_io.should equal(io)
+ end
+end
diff --git a/spec/ruby/core/io/try_convert_spec.rb b/spec/ruby/core/io/try_convert_spec.rb
new file mode 100644
index 0000000000..5fbd10b6fa
--- /dev/null
+++ b/spec/ruby/core/io/try_convert_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO.try_convert" do
+ before :each do
+ @name = tmp("io_try_convert.txt")
+ @io = new_io @name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "returns the passed IO object" do
+ IO.try_convert(@io).should equal(@io)
+ end
+
+ it "does not call #to_io on an IO instance" do
+ @io.should_not_receive(:to_io)
+ IO.try_convert(@io)
+ end
+
+ it "calls #to_io to coerce an object" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return(@io)
+ IO.try_convert(obj).should equal(@io)
+ end
+
+ it "returns nil when the passed object does not respond to #to_io" do
+ IO.try_convert(mock("io")).should be_nil
+ end
+
+ it "return nil when BasicObject is passed" do
+ IO.try_convert(BasicObject.new).should be_nil
+ end
+
+ it "raises a TypeError if the object does not return an IO from #to_io" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return("io")
+ -> { IO.try_convert(obj) }.should raise_error(TypeError)
+ end
+
+ it "propagates an exception raised by #to_io" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_raise(TypeError.new)
+ ->{ IO.try_convert(obj) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/io/tty_spec.rb b/spec/ruby/core/io/tty_spec.rb
new file mode 100644
index 0000000000..3b76c6d2b8
--- /dev/null
+++ b/spec/ruby/core/io/tty_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/tty'
+
+describe "IO#tty?" do
+ it_behaves_like :io_tty, :tty?
+end
diff --git a/spec/ruby/core/io/ungetbyte_spec.rb b/spec/ruby/core/io/ungetbyte_spec.rb
new file mode 100644
index 0000000000..716743a6af
--- /dev/null
+++ b/spec/ruby/core/io/ungetbyte_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+
+describe "IO#ungetbyte" do
+ before :each do
+ @name = tmp("io_ungetbyte")
+ touch(@name) { |f| f.write "a" }
+ @io = new_io @name, "r"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "does nothing when passed nil" do
+ @io.ungetbyte(nil).should be_nil
+ @io.getbyte.should == 97
+ end
+
+ it "puts back each byte in a String argument" do
+ @io.ungetbyte("cat").should be_nil
+ @io.getbyte.should == 99
+ @io.getbyte.should == 97
+ @io.getbyte.should == 116
+ @io.getbyte.should == 97
+ end
+
+ it "calls #to_str to convert the argument" do
+ str = mock("io ungetbyte")
+ str.should_receive(:to_str).and_return("dog")
+
+ @io.ungetbyte(str).should be_nil
+ @io.getbyte.should == 100
+ @io.getbyte.should == 111
+ @io.getbyte.should == 103
+ @io.getbyte.should == 97
+ end
+
+ it "never raises RangeError" do
+ for i in [4095, 0x4f7574206f6620636861722072616e67ff] do
+ @io.ungetbyte(i).should be_nil
+ @io.getbyte.should == 255
+ end
+ end
+
+ it "raises IOError on stream not opened for reading" do
+ -> { STDOUT.ungetbyte(42) }.should raise_error(IOError, "not opened for reading")
+ end
+
+ it "raises an IOError if the IO is closed" do
+ @io.close
+ -> { @io.ungetbyte(42) }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/ungetc_spec.rb b/spec/ruby/core/io/ungetc_spec.rb
new file mode 100644
index 0000000000..41a455c836
--- /dev/null
+++ b/spec/ruby/core/io/ungetc_spec.rb
@@ -0,0 +1,148 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#ungetc" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+
+ @empty = tmp('empty.txt')
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @empty
+ end
+
+ it "pushes back one character onto stream" do
+ @io.getc.should == ?V
+ @io.ungetc(86)
+ @io.getc.should == ?V
+
+ @io.ungetc(10)
+ @io.getc.should == ?\n
+
+ @io.getc.should == ?o
+ @io.getc.should == ?i
+ # read the rest of line
+ @io.readline.should == "ci la ligne une.\n"
+ @io.getc.should == ?Q
+ @io.ungetc(99)
+ @io.getc.should == ?c
+ end
+
+ it "interprets the codepoint in the external encoding" do
+ @io.set_encoding(Encoding::UTF_8)
+ @io.ungetc(233)
+ c = @io.getc
+ c.encoding.should == Encoding::UTF_8
+ c.should == "é"
+ c.bytes.should == [195, 169]
+
+ @io.set_encoding(Encoding::IBM437)
+ @io.ungetc(130)
+ c = @io.getc
+ c.encoding.should == Encoding::IBM437
+ c.bytes.should == [130]
+ c.encode(Encoding::UTF_8).should == "é"
+ end
+
+ it "pushes back one character when invoked at the end of the stream" do
+ # read entire content
+ @io.read
+ @io.ungetc(100)
+ @io.getc.should == ?d
+ end
+
+ it "pushes back one character when invoked at the start of the stream" do
+ @io.read(0)
+ @io.ungetc(100)
+ @io.getc.should == ?d
+ end
+
+ it "pushes back one character when invoked on empty stream" do
+ touch(@empty)
+
+ File.open(@empty) { |empty|
+ empty.getc().should == nil
+ empty.ungetc(10)
+ empty.getc.should == ?\n
+ }
+ end
+
+ it "affects EOF state" do
+ touch(@empty)
+
+ File.open(@empty) { |empty|
+ empty.should.eof?
+ empty.getc.should == nil
+ empty.ungetc(100)
+ empty.should_not.eof?
+ }
+ end
+
+ it "adjusts the stream position" do
+ @io.pos.should == 0
+
+ # read one char
+ c = @io.getc
+ @io.pos.should == 1
+ @io.ungetc(c)
+ @io.pos.should == 0
+
+ # read all
+ @io.read
+ pos = @io.pos
+ @io.ungetc(98)
+ @io.pos.should == pos - 1
+ end
+
+ it "makes subsequent unbuffered operations to raise IOError" do
+ @io.getc
+ @io.ungetc(100)
+ -> { @io.sysread(1) }.should raise_error(IOError)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "does not affect the stream and returns nil when passed nil" do
+ @io.getc.should == ?V
+ @io.ungetc(nil)
+ @io.getc.should == ?o
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "raises TypeError if passed nil" do
+ @io.getc.should == ?V
+ proc{@io.ungetc(nil)}.should raise_error(TypeError)
+ end
+ end
+
+ it "puts one or more characters back in the stream" do
+ @io.gets
+ @io.ungetc("Aquí ").should be_nil
+ @io.gets.chomp.should == "Aquí Qui è la linea due."
+ end
+
+ it "calls #to_str to convert the argument if it is not an Integer" do
+ chars = mock("io ungetc")
+ chars.should_receive(:to_str).and_return("Aquí ")
+
+ @io.ungetc(chars).should be_nil
+ @io.gets.chomp.should == "Aquí Voici la ligne une."
+ end
+
+ it "returns nil when invoked on stream that was not yet read" do
+ @io.ungetc(100).should be_nil
+ end
+
+ it "raises IOError on stream not opened for reading" do
+ -> { STDOUT.ungetc(100) }.should raise_error(IOError, "not opened for reading")
+ end
+
+ it "raises IOError on closed stream" do
+ @io.getc
+ @io.close
+ -> { @io.ungetc(100) }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/write_nonblock_spec.rb b/spec/ruby/core/io/write_nonblock_spec.rb
new file mode 100644
index 0000000000..5532556d8a
--- /dev/null
+++ b/spec/ruby/core/io/write_nonblock_spec.rb
@@ -0,0 +1,95 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/write'
+
+# See https://bugs.ruby-lang.org/issues/5954#note-5
+platform_is_not :windows do
+ describe "IO#write_nonblock on a file" do
+ before :each do
+ @filename = tmp("IO_syswrite_file") + $$.to_s
+ File.open(@filename, "w") do |file|
+ file.write_nonblock("012345678901234567890123456789")
+ end
+ @file = File.open(@filename, "r+")
+ @readonly_file = File.open(@filename)
+ end
+
+ after :each do
+ @file.close if @file
+ @readonly_file.close if @readonly_file
+ rm_r @filename
+ end
+
+ it "writes all of the string's bytes but does not buffer them" do
+ written = @file.write_nonblock("abcde")
+ written.should == 5
+ File.open(@filename) do |file|
+ file.sysread(10).should == "abcde56789"
+ file.seek(0)
+ @file.fsync
+ file.sysread(10).should == "abcde56789"
+ end
+ end
+
+ it "does not modify the passed argument" do
+ File.open(@filename, "w") do |f|
+ f.set_encoding(Encoding::IBM437)
+ # A character whose codepoint differs between UTF-8 and IBM437
+ f.write_nonblock("Æ’".freeze)
+ end
+
+ File.binread(@filename).bytes.should == [198, 146]
+ end
+
+ it "checks if the file is writable if writing zero bytes" do
+ -> {
+ @readonly_file.write_nonblock("")
+ }.should raise_error(IOError)
+ end
+ end
+
+ describe "IO#write_nonblock" do
+ it_behaves_like :io_write, :write_nonblock
+ end
+end
+
+describe 'IO#write_nonblock' do
+ before do
+ @read, @write = IO.pipe
+ end
+
+ after do
+ @read.close
+ @write.close
+ end
+
+ it "raises an exception extending IO::WaitWritable when the write would block" do
+ -> {
+ loop { @write.write_nonblock('a' * 10_000) }
+ }.should raise_error(IO::WaitWritable) { |e|
+ platform_is_not :windows do
+ e.should be_kind_of(Errno::EAGAIN)
+ end
+ platform_is :windows do
+ e.should be_kind_of(Errno::EWOULDBLOCK)
+ end
+ }
+ end
+
+ context "when exception option is set to false" do
+ it "returns :wait_writable when the operation would block" do
+ loop {
+ break if @write.write_nonblock("a" * 10_000, exception: false) == :wait_writable
+ }
+ @write.write_nonblock("a" * 10_000, exception: false).should == :wait_writable
+ end
+ end
+
+ platform_is_not :windows do
+ it 'sets the IO in nonblock mode' do
+ require 'io/nonblock'
+ @write.write_nonblock('a')
+ @write.should.nonblock?
+ end
+ end
+end
diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb
new file mode 100644
index 0000000000..bcc0582d9e
--- /dev/null
+++ b/spec/ruby/core/io/write_spec.rb
@@ -0,0 +1,204 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/write'
+require_relative 'shared/binwrite'
+
+describe "IO#write on a file" do
+ before :each do
+ @filename = tmp("IO_syswrite_file") + $$.to_s
+ File.open(@filename, "w") do |file|
+ file.write("012345678901234567890123456789")
+ end
+ @file = File.open(@filename, "r+")
+ @readonly_file = File.open(@filename)
+ end
+
+ after :each do
+ @file.close
+ @readonly_file.close
+ rm_r @filename
+ end
+
+ it "does not check if the file is writable if writing zero bytes" do
+ -> { @readonly_file.write("") }.should_not raise_error
+ end
+
+ it "returns a length of 0 when writing a blank string" do
+ @file.write('').should == 0
+ end
+
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::UTF_8
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "returns the number of bytes written" do
+ @file.write("hellø").should == 6
+ end
+
+ it "does not modify the passed argument" do
+ File.open(@filename, "w") do |f|
+ f.set_encoding(Encoding::IBM437)
+ # A character whose codepoint differs between UTF-8 and IBM437
+ f.write("Æ’".freeze)
+ end
+
+ File.binread(@filename).bytes.should == [159]
+ end
+
+ it "uses the encoding from the given option for non-ascii encoding" do
+ File.open(@filename, "w", encoding: Encoding::UTF_32LE) do |file|
+ file.write("hi").should == 8
+ end
+ File.binread(@filename).should == "h\u0000\u0000\u0000i\u0000\u0000\u0000"
+ end
+
+ it "uses an :open_args option" do
+ IO.write(@filename, 'hi', open_args: ["w", nil, {encoding: Encoding::UTF_32LE}]).should == 8
+ end
+
+ it "raises a invalid byte sequence error if invalid bytes are being written" do
+ # pack "\xFEhi" to avoid utf-8 conflict
+ xFEhi = ([254].pack('C*') + 'hi').force_encoding('utf-8')
+ File.open(@filename, "w", encoding: Encoding::US_ASCII) do |file|
+ -> { file.write(xFEhi) }.should raise_error(Encoding::InvalidByteSequenceError)
+ end
+ end
+
+ it "writes binary data if no encoding is given" do
+ File.open(@filename, "w") do |file|
+ file.write('Hëllö'.encode('ISO-8859-1'))
+ end
+ ë = ([235].pack('U')).encode('ISO-8859-1')
+ ö = ([246].pack('U')).encode('ISO-8859-1')
+ res = "H#{ë}ll#{ö}"
+ File.binread(@filename).should == res.force_encoding(Encoding::BINARY)
+ end
+end
+
+describe "IO.write" do
+ it_behaves_like :io_binwrite, :write
+
+ it "uses an :open_args option" do
+ IO.write(@filename, 'hi', open_args: ["w", nil, {encoding: Encoding::UTF_32LE}]).should == 8
+ end
+
+ it "disregards other options if :open_args is given" do
+ IO.write(@filename, 'hi', 2, mode: "r", encoding: Encoding::UTF_32LE, open_args: ["w"]).should == 2
+ File.read(@filename).should == "\0\0hi"
+ end
+
+ it "uses the given encoding and returns the number of bytes written" do
+ IO.write(@filename, 'hi', mode: "w", encoding: Encoding::UTF_32LE).should == 8
+ end
+
+ it "writes the file with the permissions in the :perm parameter" do
+ rm_r @filename
+ IO.write(@filename, 'write :perm spec', mode: "w", perm: 0o755).should == 16
+ (File.stat(@filename).mode & 0o777) == 0o755
+ end
+
+ it "writes binary data if no encoding is given" do
+ IO.write(@filename, 'Hëllö'.encode('ISO-8859-1'))
+ xEB = [235].pack('C*')
+ xF6 = [246].pack('C*')
+ File.binread(@filename).should == ("H" + xEB + "ll" + xF6).force_encoding(Encoding::BINARY)
+ end
+
+ platform_is_not :windows do
+ describe "on a FIFO" do
+ before :each do
+ @fifo = tmp("File_open_fifo")
+ File.mkfifo(@fifo)
+ end
+
+ after :each do
+ rm_r @fifo
+ end
+
+ it "writes correctly" do
+ thr = Thread.new do
+ IO.read(@fifo)
+ end
+ begin
+ string = "hi"
+ IO.write(@fifo, string).should == string.length
+ ensure
+ thr.join
+ end
+ end
+ end
+ end
+end
+
+describe "IO#write" do
+ it_behaves_like :io_write, :write
+
+ it "accepts multiple arguments" do
+ IO.pipe do |r, w|
+ w.write("foo", "bar")
+ w.close
+
+ r.read.should == "foobar"
+ end
+ end
+end
+
+platform_is :windows do
+ describe "IO#write on Windows" do
+ before :each do
+ @fname = tmp("io_write.txt")
+ end
+
+ after :each do
+ rm_r @fname
+ @io.close if @io and !@io.closed?
+ end
+
+ it "normalizes line endings in text mode" do
+ @io = new_io(@fname, "wt")
+ @io.write "a\nb\nc"
+ @io.close
+ File.binread(@fname).should == "a\r\nb\r\nc"
+ end
+
+ it "does not normalize line endings in binary mode" do
+ @io = new_io(@fname, "wb")
+ @io.write "a\r\nb\r\nc"
+ @io.close
+ File.binread(@fname).should == "a\r\nb\r\nc"
+ end
+ end
+end
+
+ruby_version_is "3.0" do
+ describe "IO#write on STDOUT" do
+ # https://bugs.ruby-lang.org/issues/14413
+ platform_is_not :windows do
+ it "raises SignalException SIGPIPE if the stream is closed instead of Errno::EPIPE like other IOs" do
+ stderr_file = tmp("stderr")
+ begin
+ IO.popen([*ruby_exe, "-e", "loop { puts :ok }"], "r", err: stderr_file) do |io|
+ io.gets.should == "ok\n"
+ io.close
+ end
+ status = $?
+ status.should_not.success?
+ status.should.signaled?
+ Signal.signame(status.termsig).should == 'PIPE'
+ File.read(stderr_file).should.empty?
+ ensure
+ rm_r stderr_file
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/Array_spec.rb b/spec/ruby/core/kernel/Array_spec.rb
new file mode 100644
index 0000000000..b4a8bb7599
--- /dev/null
+++ b/spec/ruby/core/kernel/Array_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel" do
+ it "has private instance method Array()" do
+ Kernel.should have_private_instance_method(:Array)
+ end
+end
+
+describe :kernel_Array, shared: true do
+ before :each do
+ @array = [1, 2, 3]
+ end
+
+ it "does not call #to_ary on an Array" do
+ @array.should_not_receive(:to_ary)
+ @object.send(@method, @array).should == @array
+ end
+
+ it "calls #to_ary to convert the argument to an Array" do
+ obj = mock("Array([1,2,3])")
+ obj.should_receive(:to_ary).and_return(@array)
+ obj.should_not_receive(:to_a)
+
+ @object.send(@method, obj).should == @array
+ end
+
+ it "does not call #to_a on an Array" do
+ @array.should_not_receive(:to_a)
+ @object.send(@method, @array).should == @array
+ end
+
+ it "calls #to_a if the argument does not respond to #to_ary" do
+ obj = mock("Array([1,2,3])")
+ obj.should_receive(:to_a).and_return(@array)
+
+ @object.send(@method, obj).should == @array
+ end
+
+ it "calls #to_a if #to_ary returns nil" do
+ obj = mock("Array([1,2,3])")
+ obj.should_receive(:to_ary).and_return(nil)
+ obj.should_receive(:to_a).and_return(@array)
+
+ @object.send(@method, obj).should == @array
+ end
+
+ it "returns an Array containing the argument if #to_a returns nil" do
+ obj = mock("Array([1,2,3])")
+ obj.should_receive(:to_a).and_return(nil)
+
+ @object.send(@method, obj).should == [obj]
+ end
+
+ it "calls #to_ary first, even if it's private" do
+ obj = KernelSpecs::PrivateToAry.new
+
+ @object.send(@method, obj).should == [1, 2]
+ end
+
+ it "calls #to_a if #to_ary is not defined, even if it's private" do
+ obj = KernelSpecs::PrivateToA.new
+
+ @object.send(@method, obj).should == [3, 4]
+ end
+
+ it "returns an Array containing the argument if it responds to neither #to_ary nor #to_a" do
+ obj = mock("Array(x)")
+ @object.send(@method, obj).should == [obj]
+ end
+
+ it "returns an empty Array when passed nil" do
+ @object.send(@method, nil).should == []
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("Array() string")
+ obj.should_receive(:to_ary).and_return("string")
+
+ -> { @object.send(@method, obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ obj = mock("Array() string")
+ obj.should_receive(:to_a).and_return("string")
+
+ -> { @object.send(@method, obj) }.should raise_error(TypeError)
+ end
+end
+
+describe "Kernel.Array" do
+ it_behaves_like :kernel_Array, :Array_method, KernelSpecs
+end
+
+describe "Kernel#Array" do
+ it_behaves_like :kernel_Array, :Array_function, KernelSpecs
+end
diff --git a/spec/ruby/core/kernel/Complex_spec.rb b/spec/ruby/core/kernel/Complex_spec.rb
new file mode 100644
index 0000000000..cc8177fa02
--- /dev/null
+++ b/spec/ruby/core/kernel/Complex_spec.rb
@@ -0,0 +1,272 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/complex'
+require_relative 'fixtures/Complex'
+
+describe "Kernel.Complex()" do
+ describe "when passed [Complex, Complex]" do
+ it "returns a new Complex number based on the two given numbers" do
+ Complex(Complex(3, 4), Complex(5, 6)).should == Complex(3 - 6, 4 + 5)
+ Complex(Complex(1.5, 2), Complex(-5, 6.3)).should == Complex(1.5 - 6.3, 2 - 5)
+ end
+ end
+
+ describe "when passed [Complex]" do
+ it "returns the passed Complex number" do
+ Complex(Complex(1, 2)).should == Complex(1, 2)
+ Complex(Complex(-3.4, bignum_value)).should == Complex(-3.4, bignum_value)
+ end
+ end
+
+ describe "when passed [Integer, Integer]" do
+ it "returns a new Complex number" do
+ Complex(1, 2).should be_an_instance_of(Complex)
+ Complex(1, 2).real.should == 1
+ Complex(1, 2).imag.should == 2
+
+ Complex(-3, -5).should be_an_instance_of(Complex)
+ Complex(-3, -5).real.should == -3
+ Complex(-3, -5).imag.should == -5
+
+ Complex(3.5, -4.5).should be_an_instance_of(Complex)
+ Complex(3.5, -4.5).real.should == 3.5
+ Complex(3.5, -4.5).imag.should == -4.5
+
+ Complex(bignum_value, 30).should be_an_instance_of(Complex)
+ Complex(bignum_value, 30).real.should == bignum_value
+ Complex(bignum_value, 30).imag.should == 30
+ end
+ end
+
+ describe "when passed [Integer/Float]" do
+ it "returns a new Complex number with 0 as the imaginary component" do
+ # Guard against the Mathn library
+ guard -> { !defined?(Math.rsqrt) } do
+ Complex(1).should be_an_instance_of(Complex)
+ Complex(1).imag.should == 0
+ Complex(1).real.should == 1
+
+ Complex(-3).should be_an_instance_of(Complex)
+ Complex(-3).imag.should == 0
+ Complex(-3).real.should == -3
+
+ Complex(-4.5).should be_an_instance_of(Complex)
+ Complex(-4.5).imag.should == 0
+ Complex(-4.5).real.should == -4.5
+
+ Complex(bignum_value).should be_an_instance_of(Complex)
+ Complex(bignum_value).imag.should == 0
+ Complex(bignum_value).real.should == bignum_value
+ end
+ end
+ end
+
+ describe "when passed [String]" do
+ it_behaves_like :kernel_complex, :Complex_method, KernelSpecs
+
+ context "invalid argument" do
+ it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do
+ -> {
+ Complex("79+4i".encode("UTF-16"))
+ }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16")
+ end
+
+ it "raises ArgumentError for unrecognised Strings" do
+ -> {
+ Complex("ruby")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "ruby"')
+ end
+
+ it "raises ArgumentError for trailing garbage" do
+ -> {
+ Complex("79+4iruby")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "79+4iruby"')
+ end
+
+ it "does not understand Float::INFINITY" do
+ -> {
+ Complex("Infinity")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "Infinity"')
+
+ -> {
+ Complex("-Infinity")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "-Infinity"')
+ end
+
+ it "does not understand Float::NAN" do
+ -> {
+ Complex("NaN")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "NaN"')
+ end
+
+ it "does not understand a sequence of _" do
+ -> {
+ Complex("7__9+4__0i")
+ }.should raise_error(ArgumentError, 'invalid value for convert(): "7__9+4__0i"')
+ end
+
+ it "does not allow null-byte" do
+ -> {
+ Complex("1-2i\0")
+ }.should raise_error(ArgumentError, "string contains null byte")
+ end
+ end
+
+ context "invalid argument and exception: false passed" do
+ it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do
+ -> {
+ Complex("79+4i".encode("UTF-16"), exception: false)
+ }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16")
+ end
+
+ it "returns nil for unrecognised Strings" do
+ Complex("ruby", exception: false).should == nil
+ end
+
+ it "returns nil when trailing garbage" do
+ Complex("79+4iruby", exception: false).should == nil
+ end
+
+ it "returns nil for Float::INFINITY" do
+ Complex("Infinity", exception: false).should == nil
+ Complex("-Infinity", exception: false).should == nil
+ end
+
+ it "returns nil for Float::NAN" do
+ Complex("NaN", exception: false).should == nil
+ end
+
+ it "returns nil when there is a sequence of _" do
+ Complex("7__9+4__0i", exception: false).should == nil
+ end
+
+ it "returns nil when String contains null-byte" do
+ Complex("1-2i\0", exception: false).should == nil
+ end
+ end
+ end
+
+ describe "when passes [String, String]" do
+ it "needs to be reviewed for spec completeness"
+ end
+
+ describe "when passed an Object which responds to #to_c" do
+ it "returns the passed argument" do
+ obj = Object.new; def obj.to_c; 1i end
+ Complex(obj).should == Complex(0, 1)
+ end
+ end
+
+ describe "when passed a Numeric which responds to #real? with false" do
+ it "returns the passed argument" do
+ n = mock_numeric("unreal")
+ n.should_receive(:real?).any_number_of_times.and_return(false)
+ Complex(n).should equal(n)
+ end
+ end
+
+ describe "when passed a Numeric which responds to #real? with true" do
+ it "returns a Complex with the passed argument as the real component and 0 as the imaginary component" do
+ n = mock_numeric("real")
+ n.should_receive(:real?).any_number_of_times.and_return(true)
+ result = Complex(n)
+ result.real.should equal(n)
+ result.imag.should equal(0)
+ end
+ end
+
+ describe "when passed Numerics n1 and n2 and at least one responds to #real? with false" do
+ [[false, false], [false, true], [true, false]].each do |r1, r2|
+ it "returns n1 + n2 * Complex(0, 1)" do
+ n1 = mock_numeric("n1")
+ n2 = mock_numeric("n2")
+ n3 = mock_numeric("n3")
+ n4 = mock_numeric("n4")
+ n1.should_receive(:real?).any_number_of_times.and_return(r1)
+ n2.should_receive(:real?).any_number_of_times.and_return(r2)
+ n2.should_receive(:*).with(Complex(0, 1)).and_return(n3)
+ n1.should_receive(:+).with(n3).and_return(n4)
+ Complex(n1, n2).should equal(n4)
+ end
+ end
+ end
+
+ describe "when passed two Numerics and both respond to #real? with true" do
+ it "returns a Complex with the passed arguments as real and imaginary components respectively" do
+ n1 = mock_numeric("n1")
+ n2 = mock_numeric("n2")
+ n1.should_receive(:real?).any_number_of_times.and_return(true)
+ n2.should_receive(:real?).any_number_of_times.and_return(true)
+ result = Complex(n1, n2)
+ result.real.should equal(n1)
+ result.imag.should equal(n2)
+ end
+ end
+
+ describe "when passed a single non-Numeric" do
+ it "coerces the passed argument using #to_c" do
+ n = mock("n")
+ c = Complex(0, 0)
+ n.should_receive(:to_c).and_return(c)
+ Complex(n).should equal(c)
+ end
+ end
+
+ describe "when passed a non-Numeric second argument" do
+ it "raises TypeError" do
+ -> { Complex(:sym, :sym) }.should raise_error(TypeError)
+ -> { Complex(0, :sym) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "when passed nil" do
+ it "raises TypeError" do
+ -> { Complex(nil) }.should raise_error(TypeError, "can't convert nil into Complex")
+ -> { Complex(0, nil) }.should raise_error(TypeError, "can't convert nil into Complex")
+ -> { Complex(nil, 0) }.should raise_error(TypeError, "can't convert nil into Complex")
+ end
+ end
+
+ describe "when passed exception: false" do
+ describe "and [Numeric]" do
+ it "returns a complex number" do
+ Complex("123", exception: false).should == Complex(123)
+ end
+ end
+
+ describe "and [non-Numeric]" do
+ it "swallows an error" do
+ Complex(:sym, exception: false).should == nil
+ end
+ end
+
+ describe "and [non-Numeric, Numeric] argument" do
+ it "throws a TypeError" do
+ -> { Complex(:sym, 0, exception: false) }.should raise_error(TypeError, "not a real")
+ end
+ end
+
+ describe "and [anything, non-Numeric] argument" do
+ it "swallows an error" do
+ Complex("a", :sym, exception: false).should == nil
+ Complex(:sym, :sym, exception: false).should == nil
+ Complex(0, :sym, exception: false).should == nil
+ end
+ end
+
+ describe "and non-numeric String arguments" do
+ it "swallows an error" do
+ Complex("a", "b", exception: false).should == nil
+ Complex("a", 0, exception: false).should == nil
+ Complex(0, "b", exception: false).should == nil
+ end
+ end
+
+ describe "and nil arguments" do
+ it "swallows an error" do
+ Complex(nil, exception: false).should == nil
+ Complex(0, nil, exception: false).should == nil
+ Complex(nil, 0, exception: false).should == nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb
new file mode 100644
index 0000000000..015bcb33d6
--- /dev/null
+++ b/spec/ruby/core/kernel/Float_spec.rb
@@ -0,0 +1,345 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :kernel_float, shared: true do
+ it "returns the identical Float for numeric Floats" do
+ float = 1.12
+ float2 = @object.send(:Float, float)
+ float2.should == float
+ float2.should equal float
+ end
+
+ it "returns a Float for Fixnums" do
+ @object.send(:Float, 1).should == 1.0
+ end
+
+ it "returns a Float for Complex with only a real part" do
+ @object.send(:Float, Complex(1)).should == 1.0
+ end
+
+ it "returns a Float for Bignums" do
+ @object.send(:Float, 1000000000000).should == 1000000000000.0
+ end
+
+ it "raises an ArgumentError for nil" do
+ -> { @object.send(:Float, nil) }.should raise_error(TypeError)
+ end
+
+ it "returns the identical NaN for NaN" do
+ nan = nan_value
+ nan.nan?.should be_true
+ nan2 = @object.send(:Float, nan)
+ nan2.nan?.should be_true
+ nan2.should equal(nan)
+ end
+
+ it "returns the same Infinity for Infinity" do
+ infinity = infinity_value
+ infinity2 = @object.send(:Float, infinity)
+ infinity2.should == infinity_value
+ infinity.should equal(infinity2)
+ end
+
+ it "converts Strings to floats without calling #to_f" do
+ string = "10"
+ string.should_not_receive(:to_f)
+ @object.send(:Float, string).should == 10.0
+ end
+
+ it "converts Strings with decimal points into Floats" do
+ @object.send(:Float, "10.0").should == 10.0
+ end
+
+ it "raises an ArgumentError for a String of word characters" do
+ -> { @object.send(:Float, "float") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a String with string in error message" do
+ -> { @object.send(:Float, "foo") }.should raise_error(ArgumentError) { |e|
+ e.message.should == 'invalid value for Float(): "foo"'
+ }
+ end
+
+ it "raises an ArgumentError if there are two decimal points in the String" do
+ -> { @object.send(:Float, "10.0.0") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a String of numbers followed by word characters" do
+ -> { @object.send(:Float, "10D") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a String of word characters followed by numbers" do
+ -> { @object.send(:Float, "D10") }.should raise_error(ArgumentError)
+ end
+
+ it "is strict about the string form even across newlines" do
+ -> { @object.send(:Float, "not a number\n10") }.should raise_error(ArgumentError)
+ -> { @object.send(:Float, "10\nnot a number") }.should raise_error(ArgumentError)
+ end
+
+ it "converts String subclasses to floats without calling #to_f" do
+ my_string = Class.new(String) do
+ def to_f() 1.2 end
+ end
+
+ @object.send(:Float, my_string.new("10")).should == 10.0
+ end
+
+ it "returns a positive Float if the string is prefixed with +" do
+ @object.send(:Float, "+10").should == 10.0
+ @object.send(:Float, " +10").should == 10.0
+ end
+
+ it "returns a negative Float if the string is prefixed with +" do
+ @object.send(:Float, "-10").should == -10.0
+ @object.send(:Float, " -10").should == -10.0
+ end
+
+ it "raises an ArgumentError if a + or - is embedded in a String" do
+ -> { @object.send(:Float, "1+1") }.should raise_error(ArgumentError)
+ -> { @object.send(:Float, "1-1") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if a String has a trailing + or -" do
+ -> { @object.send(:Float, "11+") }.should raise_error(ArgumentError)
+ -> { @object.send(:Float, "11-") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a String with a leading _" do
+ -> { @object.send(:Float, "_1") }.should raise_error(ArgumentError)
+ end
+
+ it "returns a value for a String with an embedded _" do
+ @object.send(:Float, "1_000").should == 1000.0
+ end
+
+ it "raises an ArgumentError for a String with a trailing _" do
+ -> { @object.send(:Float, "10_") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a String of \\0" do
+ -> { @object.send(:Float, "\0") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a String with a leading \\0" do
+ -> { @object.send(:Float, "\01") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a String with an embedded \\0" do
+ -> { @object.send(:Float, "1\01") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a String with a trailing \\0" do
+ -> { @object.send(:Float, "1\0") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a String that is just an empty space" do
+ -> { @object.send(:Float, " ") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a String that with an embedded space" do
+ -> { @object.send(:Float, "1 2") }.should raise_error(ArgumentError)
+ end
+
+ it "returns a value for a String with a leading space" do
+ @object.send(:Float, " 1").should == 1.0
+ end
+
+ it "returns a value for a String with a trailing space" do
+ @object.send(:Float, "1 ").should == 1.0
+ end
+
+ it "returns a value for a String with any leading whitespace" do
+ @object.send(:Float, "\t\n1").should == 1.0
+ end
+
+ it "returns a value for a String with any trailing whitespace" do
+ @object.send(:Float, "1\t\n").should == 1.0
+ end
+
+ %w(e E).each do |e|
+ it "raises an ArgumentError if #{e} is the trailing character" do
+ -> { @object.send(:Float, "2#{e}") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if #{e} is the leading character" do
+ -> { @object.send(:Float, "#{e}2") }.should raise_error(ArgumentError)
+ end
+
+ it "returns Infinity for '2#{e}1000'" do
+ @object.send(:Float, "2#{e}1000").should == Float::INFINITY
+ end
+
+ it "returns 0 for '2#{e}-1000'" do
+ @object.send(:Float, "2#{e}-1000").should == 0
+ end
+
+ it "allows embedded _ in a number on either side of the #{e}" do
+ @object.send(:Float, "2_0#{e}100").should == 20e100
+ @object.send(:Float, "20#{e}1_00").should == 20e100
+ @object.send(:Float, "2_0#{e}1_00").should == 20e100
+ end
+
+ it "raises an exception if a space is embedded on either side of the '#{e}'" do
+ -> { @object.send(:Float, "2 0#{e}100") }.should raise_error(ArgumentError)
+ -> { @object.send(:Float, "20#{e}1 00") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an exception if there's a leading _ on either side of the '#{e}'" do
+ -> { @object.send(:Float, "_20#{e}100") }.should raise_error(ArgumentError)
+ -> { @object.send(:Float, "20#{e}_100") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an exception if there's a trailing _ on either side of the '#{e}'" do
+ -> { @object.send(:Float, "20_#{e}100") }.should raise_error(ArgumentError)
+ -> { @object.send(:Float, "20#{e}100_") }.should raise_error(ArgumentError)
+ end
+
+ it "allows decimal points on the left side of the '#{e}'" do
+ @object.send(:Float, "2.0#{e}2").should == 2e2
+ end
+
+ it "raises an ArgumentError if there's a decimal point on the right side of the '#{e}'" do
+ -> { @object.send(:Float, "20#{e}2.0") }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "for hexadecimal literals with binary exponent" do
+ %w(p P).each do |p|
+ it "interprets the fractional part (on the left side of '#{p}') in hexadecimal" do
+ @object.send(:Float, "0x10#{p}0").should == 16.0
+ end
+
+ it "interprets the exponent (on the right of '#{p}') in decimal" do
+ @object.send(:Float, "0x1#{p}10").should == 1024.0
+ end
+
+ it "raises an ArgumentError if #{p} is the trailing character" do
+ -> { @object.send(:Float, "0x1#{p}") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if #{p} is the leading character" do
+ -> { @object.send(:Float, "0x#{p}1") }.should raise_error(ArgumentError)
+ end
+
+ it "returns Infinity for '0x1#{p}10000'" do
+ @object.send(:Float, "0x1#{p}10000").should == Float::INFINITY
+ end
+
+ it "returns 0 for '0x1#{p}-10000'" do
+ @object.send(:Float, "0x1#{p}-10000").should == 0
+ end
+
+ it "allows embedded _ in a number on either side of the #{p}" do
+ @object.send(:Float, "0x1_0#{p}10").should == 16384.0
+ @object.send(:Float, "0x10#{p}1_0").should == 16384.0
+ @object.send(:Float, "0x1_0#{p}1_0").should == 16384.0
+ end
+
+ it "raises an exception if a space is embedded on either side of the '#{p}'" do
+ -> { @object.send(:Float, "0x1 0#{p}10") }.should raise_error(ArgumentError)
+ -> { @object.send(:Float, "0x10#{p}1 0") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an exception if there's a leading _ on either side of the '#{p}'" do
+ -> { @object.send(:Float, "0x_10#{p}10") }.should raise_error(ArgumentError)
+ -> { @object.send(:Float, "0x10#{p}_10") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an exception if there's a trailing _ on either side of the '#{p}'" do
+ -> { @object.send(:Float, "0x10_#{p}10") }.should raise_error(ArgumentError)
+ -> { @object.send(:Float, "0x10#{p}10_") }.should raise_error(ArgumentError)
+ end
+
+ it "allows hexadecimal points on the left side of the '#{p}'" do
+ @object.send(:Float, "0x1.8#{p}0").should == 1.5
+ end
+
+ it "raises an ArgumentError if there's a decimal point on the right side of the '#{p}'" do
+ -> { @object.send(:Float, "0x1#{p}1.0") }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ it "returns a Float that can be a parameter to #Float again" do
+ float = @object.send(:Float, "10")
+ @object.send(:Float, float).should == 10.0
+ end
+
+ it "otherwise, converts the given argument to a Float by calling #to_f" do
+ (obj = mock('1.2')).should_receive(:to_f).once.and_return(1.2)
+ obj.should_not_receive(:to_i)
+ @object.send(:Float, obj).should == 1.2
+ end
+
+ it "returns the identical NaN if to_f is called and it returns NaN" do
+ nan = nan_value
+ (nan_to_f = mock('NaN')).should_receive(:to_f).once.and_return(nan)
+ nan2 = @object.send(:Float, nan_to_f)
+ nan2.nan?.should be_true
+ nan2.should equal(nan)
+ end
+
+ it "returns the identical Infinity if to_f is called and it returns Infinity" do
+ infinity = infinity_value
+ (infinity_to_f = mock('Infinity')).should_receive(:to_f).once.and_return(infinity)
+ infinity2 = @object.send(:Float, infinity_to_f)
+ infinity2.should equal(infinity)
+ end
+
+ it "raises a TypeError if #to_f is not provided" do
+ -> { @object.send(:Float, mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_f returns a String" do
+ (obj = mock('ha!')).should_receive(:to_f).once.and_return('ha!')
+ -> { @object.send(:Float, obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_f returns an Integer" do
+ (obj = mock('123')).should_receive(:to_f).once.and_return(123)
+ -> { @object.send(:Float, obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError when passed a Complex argument" do
+ c = Complex(2, 3)
+ -> { @object.send(:Float, c) }.should raise_error(RangeError)
+ end
+
+ describe "when passed exception: false" do
+ describe "and valid input" do
+ it "returns a Float number" do
+ @object.send(:Float, 1, exception: false).should == 1.0
+ @object.send(:Float, "1", exception: false).should == 1.0
+ @object.send(:Float, "1.23", exception: false).should == 1.23
+ end
+ end
+
+ describe "and invalid input" do
+ it "swallows an error" do
+ @object.send(:Float, "abc", exception: false).should == nil
+ @object.send(:Float, :sym, exception: false).should == nil
+ end
+ end
+
+ describe "and nil" do
+ it "swallows it" do
+ @object.send(:Float, nil, exception: false).should == nil
+ end
+ end
+ end
+end
+
+describe "Kernel.Float" do
+ it_behaves_like :kernel_float, :Float, Kernel
+end
+
+describe "Kernel#Float" do
+ it_behaves_like :kernel_float, :Float, Object.new
+end
+
+describe "Kernel#Float" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:Float)
+ end
+end
diff --git a/spec/ruby/core/kernel/Hash_spec.rb b/spec/ruby/core/kernel/Hash_spec.rb
new file mode 100644
index 0000000000..cbe098a8ac
--- /dev/null
+++ b/spec/ruby/core/kernel/Hash_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#hash" do
+ it "is provided" do
+ 1.respond_to?(:hash).should == true
+ end
+
+ it "is stable" do
+ 1.hash.should == 1.hash
+ end
+end
+
+describe "Kernel" do
+ it "has private instance method Hash()" do
+ Kernel.should have_private_instance_method(:Hash)
+ end
+end
+
+describe :kernel_Hash, shared: true do
+ before :each do
+ @hash = { a: 1}
+ end
+
+ it "converts nil to a Hash" do
+ @object.send(@method, nil).should == {}
+ end
+
+ it "converts an empty array to a Hash" do
+ @object.send(@method, []).should == {}
+ end
+
+ it "does not call #to_hash on an Hash" do
+ @hash.should_not_receive(:to_hash)
+ @object.send(@method, @hash).should == @hash
+ end
+
+ it "calls #to_hash to convert the argument to an Hash" do
+ obj = mock("Hash(a: 1)")
+ obj.should_receive(:to_hash).and_return(@hash)
+
+ @object.send(@method, obj).should == @hash
+ end
+
+ it "raises a TypeError if it doesn't respond to #to_hash" do
+ -> { @object.send(@method, mock("")) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_hash does not return an Hash" do
+ obj = mock("Hash() string")
+ obj.should_receive(:to_hash).and_return("string")
+
+ -> { @object.send(@method, obj) }.should raise_error(TypeError)
+ end
+end
+
+describe "Kernel.Hash" do
+ it_behaves_like :kernel_Hash, :Hash_method, KernelSpecs
+end
+
+describe "Kernel#Hash" do
+ it_behaves_like :kernel_Hash, :Hash_function, KernelSpecs
+end
diff --git a/spec/ruby/core/kernel/Integer_spec.rb b/spec/ruby/core/kernel/Integer_spec.rb
new file mode 100644
index 0000000000..2c78e27428
--- /dev/null
+++ b/spec/ruby/core/kernel/Integer_spec.rb
@@ -0,0 +1,801 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :kernel_integer, shared: true do
+ it "returns a Bignum for a Bignum" do
+ Integer(2e100).should == 2e100
+ end
+
+ it "returns a Fixnum for a Fixnum" do
+ Integer(100).should == 100
+ end
+
+ it "raises a TypeError when to_int returns not-an-Integer object and to_i returns nil" do
+ obj = mock("object")
+ obj.should_receive(:to_int).and_return("1")
+ obj.should_receive(:to_i).and_return(nil)
+ -> { Integer(obj) }.should raise_error(TypeError)
+ end
+
+ it "return a result of to_i when to_int does not return an Integer" do
+ obj = mock("object")
+ obj.should_receive(:to_int).and_return("1")
+ obj.should_receive(:to_i).and_return(42)
+ Integer(obj).should == 42
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { Integer(nil) }.should raise_error(TypeError)
+ end
+
+ it "returns an Integer object" do
+ Integer(2).should be_an_instance_of(Integer)
+ Integer(9**99).should be_an_instance_of(Integer)
+ end
+
+ it "truncates Floats" do
+ Integer(3.14).should == 3
+ Integer(90.8).should == 90
+ end
+
+ it "calls to_i on Rationals" do
+ Integer(Rational(8,3)).should == 2
+ Integer(3.quo(2)).should == 1
+ end
+
+ it "returns the value of to_int if the result is a Fixnum" do
+ obj = mock("object")
+ obj.should_receive(:to_int).and_return(1)
+ obj.should_not_receive(:to_i)
+ Integer(obj).should == 1
+ end
+
+ it "returns the value of to_int if the result is a Bignum" do
+ obj = mock("object")
+ obj.should_receive(:to_int).and_return(2 * 10**100)
+ obj.should_not_receive(:to_i)
+ Integer(obj).should == 2 * 10**100
+ end
+
+ it "calls to_i on an object whose to_int returns nil" do
+ obj = mock("object")
+ obj.should_receive(:to_int).and_return(nil)
+ obj.should_receive(:to_i).and_return(1)
+ Integer(obj).should == 1
+ end
+
+ it "raises a TypeError if to_i returns a value that is not an Integer" do
+ obj = mock("object")
+ obj.should_receive(:to_i).and_return("1")
+ -> { Integer(obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if no to_int or to_i methods exist" do
+ obj = mock("object")
+ -> { Integer(obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if to_int returns nil and no to_i exists" do
+ obj = mock("object")
+ obj.should_receive(:to_i).and_return(nil)
+ -> { Integer(obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a FloatDomainError when passed NaN" do
+ -> { Integer(nan_value) }.should raise_error(FloatDomainError)
+ end
+
+ it "raises a FloatDomainError when passed Infinity" do
+ -> { Integer(infinity_value) }.should raise_error(FloatDomainError)
+ end
+
+ describe "when passed exception: false" do
+ describe "and to_i returns a value that is not an Integer" do
+ it "swallows an error" do
+ obj = mock("object")
+ obj.should_receive(:to_i).and_return("1")
+ Integer(obj, exception: false).should == nil
+ end
+ end
+
+ describe "and no to_int or to_i methods exist" do
+ it "swallows an error" do
+ obj = mock("object")
+ Integer(obj, exception: false).should == nil
+ end
+ end
+
+ describe "and to_int returns nil and no to_i exists" do
+ it "swallows an error" do
+ obj = mock("object")
+ obj.should_receive(:to_i).and_return(nil)
+ Integer(obj, exception: false).should == nil
+ end
+ end
+
+ describe "and passed NaN" do
+ it "swallows an error" do
+ Integer(nan_value, exception: false).should == nil
+ end
+ end
+
+ describe "and passed Infinity" do
+ it "swallows an error" do
+ Integer(infinity_value, exception: false).should == nil
+ end
+ end
+
+ describe "and passed nil" do
+ it "swallows an error" do
+ Integer(nil, exception: false).should == nil
+ end
+ end
+
+ describe "and passed a String that contains numbers" do
+ it "normally parses it and returns an Integer" do
+ Integer("42", exception: false).should == 42
+ end
+ end
+
+ describe "and passed a String that can't be converted to an Integer" do
+ it "swallows an error" do
+ Integer("abc", exception: false).should == nil
+ end
+ end
+ end
+end
+
+describe "Integer() given a String", shared: true do
+ it "raises an ArgumentError if the String is a null byte" do
+ -> { Integer("\0") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the String starts with a null byte" do
+ -> { Integer("\01") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the String ends with a null byte" do
+ -> { Integer("1\0") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the String contains a null byte" do
+ -> { Integer("1\01") }.should raise_error(ArgumentError)
+ end
+
+ it "ignores leading whitespace" do
+ Integer(" 1").should == 1
+ Integer(" 1").should == 1
+ Integer("\t\n1").should == 1
+ end
+
+ it "ignores trailing whitespace" do
+ Integer("1 ").should == 1
+ Integer("1 ").should == 1
+ Integer("1\t\n").should == 1
+ end
+
+ it "raises an ArgumentError if there are leading _s" do
+ -> { Integer("_1") }.should raise_error(ArgumentError)
+ -> { Integer("___1") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if there are trailing _s" do
+ -> { Integer("1_") }.should raise_error(ArgumentError)
+ -> { Integer("1___") }.should raise_error(ArgumentError)
+ end
+
+ it "ignores an embedded _" do
+ Integer("1_1").should == 11
+ end
+
+ it "raises an ArgumentError if there are multiple embedded _s" do
+ -> { Integer("1__1") }.should raise_error(ArgumentError)
+ -> { Integer("1___1") }.should raise_error(ArgumentError)
+ end
+
+ it "ignores a single leading +" do
+ Integer("+1").should == 1
+ end
+
+ it "raises an ArgumentError if there is a space between the + and number" do
+ -> { Integer("+ 1") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if there are multiple leading +s" do
+ -> { Integer("++1") }.should raise_error(ArgumentError)
+ -> { Integer("+++1") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if there are trailing +s" do
+ -> { Integer("1+") }.should raise_error(ArgumentError)
+ -> { Integer("1+++") }.should raise_error(ArgumentError)
+ end
+
+ it "makes the number negative if there's a leading -" do
+ Integer("-1").should == -1
+ end
+
+ it "raises an ArgumentError if there are multiple leading -s" do
+ -> { Integer("--1") }.should raise_error(ArgumentError)
+ -> { Integer("---1") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if there are trailing -s" do
+ -> { Integer("1-") }.should raise_error(ArgumentError)
+ -> { Integer("1---") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if there is a period" do
+ -> { Integer("0.0") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for an empty String" do
+ -> { Integer("") }.should raise_error(ArgumentError)
+ end
+
+ describe "when passed exception: false" do
+ describe "and multiple leading -s" do
+ it "swallows an error" do
+ Integer("---1", exception: false).should == nil
+ end
+ end
+
+ describe "and multiple trailing -s" do
+ it "swallows an error" do
+ Integer("1---", exception: false).should == nil
+ end
+ end
+
+ describe "and an argument that contains a period" do
+ it "swallows an error" do
+ Integer("0.0", exception: false).should == nil
+ end
+ end
+
+ describe "and an empty string" do
+ it "swallows an error" do
+ Integer("", exception: false).should == nil
+ end
+ end
+ end
+
+ it "parses the value as 0 if the string consists of a single zero character" do
+ Integer("0").should == 0
+ end
+
+ %w(x X).each do |x|
+ it "parses the value as a hex number if there's a leading 0#{x}" do
+ Integer("0#{x}1").should == 0x1
+ Integer("0#{x}dd").should == 0xdd
+ end
+
+ it "is a positive hex number if there's a leading +0#{x}" do
+ Integer("+0#{x}1").should == 0x1
+ Integer("+0#{x}dd").should == 0xdd
+ end
+
+ it "is a negative hex number if there's a leading -0#{x}" do
+ Integer("-0#{x}1").should == -0x1
+ Integer("-0#{x}dd").should == -0xdd
+ end
+
+ it "raises an ArgumentError if the number cannot be parsed as hex" do
+ -> { Integer("0#{x}g") }.should raise_error(ArgumentError)
+ end
+ end
+
+ %w(b B).each do |b|
+ it "parses the value as a binary number if there's a leading 0#{b}" do
+ Integer("0#{b}1").should == 0b1
+ Integer("0#{b}10").should == 0b10
+ end
+
+ it "is a positive binary number if there's a leading +0#{b}" do
+ Integer("+0#{b}1").should == 0b1
+ Integer("+0#{b}10").should == 0b10
+ end
+
+ it "is a negative binary number if there's a leading -0#{b}" do
+ Integer("-0#{b}1").should == -0b1
+ Integer("-0#{b}10").should == -0b10
+ end
+
+ it "raises an ArgumentError if the number cannot be parsed as binary" do
+ -> { Integer("0#{b}2") }.should raise_error(ArgumentError)
+ end
+ end
+
+ ["o", "O", ""].each do |o|
+ it "parses the value as an octal number if there's a leading 0#{o}" do
+ Integer("0#{o}1").should == 0O1
+ Integer("0#{o}10").should == 0O10
+ end
+
+ it "is a positive octal number if there's a leading +0#{o}" do
+ Integer("+0#{o}1").should == 0O1
+ Integer("+0#{o}10").should == 0O10
+ end
+
+ it "is a negative octal number if there's a leading -0#{o}" do
+ Integer("-0#{o}1").should == -0O1
+ Integer("-0#{o}10").should == -0O10
+ end
+
+ it "raises an ArgumentError if the number cannot be parsed as octal" do
+ -> { Integer("0#{o}9") }.should raise_error(ArgumentError)
+ end
+ end
+
+ %w(D d).each do |d|
+ it "parses the value as a decimal number if there's a leading 0#{d}" do
+ Integer("0#{d}1").should == 1
+ Integer("0#{d}10").should == 10
+ end
+
+ it "is a positive decimal number if there's a leading +0#{d}" do
+ Integer("+0#{d}1").should == 1
+ Integer("+0#{d}10").should == 10
+ end
+
+ it "is a negative decimal number if there's a leading -0#{d}" do
+ Integer("-0#{d}1").should == -1
+ Integer("-0#{d}10").should == -10
+ end
+
+ it "raises an ArgumentError if the number cannot be parsed as decimal" do
+ -> { Integer("0#{d}a") }.should raise_error(ArgumentError)
+ end
+ end
+end
+
+describe "Integer() given a String and base", shared: true do
+ it "raises an ArgumentError if the String is a null byte" do
+ -> { Integer("\0", 2) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the String starts with a null byte" do
+ -> { Integer("\01", 3) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the String ends with a null byte" do
+ -> { Integer("1\0", 4) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the String contains a null byte" do
+ -> { Integer("1\01", 5) }.should raise_error(ArgumentError)
+ end
+
+ it "ignores leading whitespace" do
+ Integer(" 16", 16).should == 22
+ Integer(" 16", 16).should == 22
+ Integer("\t\n16", 16).should == 22
+ end
+
+ it "ignores trailing whitespace" do
+ Integer("16 ", 16).should == 22
+ Integer("16 ", 16).should == 22
+ Integer("16\t\n", 16).should == 22
+ end
+
+ it "raises an ArgumentError if there are leading _s" do
+ -> { Integer("_1", 7) }.should raise_error(ArgumentError)
+ -> { Integer("___1", 7) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if there are trailing _s" do
+ -> { Integer("1_", 12) }.should raise_error(ArgumentError)
+ -> { Integer("1___", 12) }.should raise_error(ArgumentError)
+ end
+
+ it "ignores an embedded _" do
+ Integer("1_1", 4).should == 5
+ end
+
+ it "raises an ArgumentError if there are multiple embedded _s" do
+ -> { Integer("1__1", 4) }.should raise_error(ArgumentError)
+ -> { Integer("1___1", 4) }.should raise_error(ArgumentError)
+ end
+
+ it "ignores a single leading +" do
+ Integer("+10", 3).should == 3
+ end
+
+ it "raises an ArgumentError if there is a space between the + and number" do
+ -> { Integer("+ 1", 3) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if there are multiple leading +s" do
+ -> { Integer("++1", 3) }.should raise_error(ArgumentError)
+ -> { Integer("+++1", 3) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if there are trailing +s" do
+ -> { Integer("1+", 3) }.should raise_error(ArgumentError)
+ -> { Integer("1+++", 12) }.should raise_error(ArgumentError)
+ end
+
+ it "makes the number negative if there's a leading -" do
+ Integer("-19", 20).should == -29
+ end
+
+ it "raises an ArgumentError if there are multiple leading -s" do
+ -> { Integer("--1", 9) }.should raise_error(ArgumentError)
+ -> { Integer("---1", 9) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if there are trailing -s" do
+ -> { Integer("1-", 12) }.should raise_error(ArgumentError)
+ -> { Integer("1---", 12) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if there is a period" do
+ -> { Integer("0.0", 3) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for an empty String" do
+ -> { Integer("", 12) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a base of 1" do
+ -> { Integer("1", 1) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for a base of 37" do
+ -> { Integer("1", 37) }.should raise_error(ArgumentError)
+ end
+
+ it "accepts wholly lowercase alphabetic strings for bases > 10" do
+ Integer('ab',12).should == 131
+ Integer('af',20).should == 215
+ Integer('ghj',30).should == 14929
+ end
+
+ it "accepts wholly uppercase alphabetic strings for bases > 10" do
+ Integer('AB',12).should == 131
+ Integer('AF',20).should == 215
+ Integer('GHJ',30).should == 14929
+ end
+
+ it "accepts mixed-case alphabetic strings for bases > 10" do
+ Integer('Ab',12).should == 131
+ Integer('aF',20).should == 215
+ Integer('GhJ',30).should == 14929
+ end
+
+ it "accepts alphanumeric strings for bases > 10" do
+ Integer('a3e',19).should == 3681
+ Integer('12q',31).should == 1049
+ Integer('c00o',29).should == 292692
+ end
+
+ it "raises an ArgumentError for letters invalid in the given base" do
+ -> { Integer('z',19) }.should raise_error(ArgumentError)
+ -> { Integer('c00o',2) }.should raise_error(ArgumentError)
+ end
+
+ %w(x X).each do |x|
+ it "parses the value as a hex number if there's a leading 0#{x} and a base of 16" do
+ Integer("0#{x}10", 16).should == 16
+ Integer("0#{x}dd", 16).should == 221
+ end
+
+ it "is a positive hex number if there's a leading +0#{x} and base of 16" do
+ Integer("+0#{x}1", 16).should == 0x1
+ Integer("+0#{x}dd", 16).should == 0xdd
+ end
+
+ it "is a negative hex number if there's a leading -0#{x} and a base of 16" do
+ Integer("-0#{x}1", 16).should == -0x1
+ Integer("-0#{x}dd", 16).should == -0xdd
+ end
+
+ 2.upto(15) do |base|
+ it "raises an ArgumentError if the number begins with 0#{x} and the base is #{base}" do
+ -> { Integer("0#{x}1", base) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises an ArgumentError if the number cannot be parsed as hex and the base is 16" do
+ -> { Integer("0#{x}g", 16) }.should raise_error(ArgumentError)
+ end
+ end
+
+ %w(b B).each do |b|
+ it "parses the value as a binary number if there's a leading 0#{b} and the base is 2" do
+ Integer("0#{b}1", 2).should == 0b1
+ Integer("0#{b}10", 2).should == 0b10
+ end
+
+ it "is a positive binary number if there's a leading +0#{b} and a base of 2" do
+ Integer("+0#{b}1", 2).should == 0b1
+ Integer("+0#{b}10", 2).should == 0b10
+ end
+
+ it "is a negative binary number if there's a leading -0#{b} and a base of 2" do
+ Integer("-0#{b}1", 2).should == -0b1
+ Integer("-0#{b}10", 2).should == -0b10
+ end
+
+ it "raises an ArgumentError if the number cannot be parsed as binary and the base is 2" do
+ -> { Integer("0#{b}2", 2) }.should raise_error(ArgumentError)
+ end
+ end
+
+ ["o", "O"].each do |o|
+ it "parses the value as an octal number if there's a leading 0#{o} and a base of 8" do
+ Integer("0#{o}1", 8).should == 0O1
+ Integer("0#{o}10", 8).should == 0O10
+ end
+
+ it "is a positive octal number if there's a leading +0#{o} and a base of 8" do
+ Integer("+0#{o}1", 8).should == 0O1
+ Integer("+0#{o}10", 8).should == 0O10
+ end
+
+ it "is a negative octal number if there's a leading -0#{o} and a base of 8" do
+ Integer("-0#{o}1", 8).should == -0O1
+ Integer("-0#{o}10", 8).should == -0O10
+ end
+
+ it "raises an ArgumentError if the number cannot be parsed as octal and the base is 8" do
+ -> { Integer("0#{o}9", 8) }.should raise_error(ArgumentError)
+ end
+
+ 2.upto(7) do |base|
+ it "raises an ArgumentError if the number begins with 0#{o} and the base is #{base}" do
+ -> { Integer("0#{o}1", base) }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ %w(D d).each do |d|
+ it "parses the value as a decimal number if there's a leading 0#{d} and a base of 10" do
+ Integer("0#{d}1", 10).should == 1
+ Integer("0#{d}10",10).should == 10
+ end
+
+ it "is a positive decimal number if there's a leading +0#{d} and a base of 10" do
+ Integer("+0#{d}1", 10).should == 1
+ Integer("+0#{d}10", 10).should == 10
+ end
+
+ it "is a negative decimal number if there's a leading -0#{d} and a base of 10" do
+ Integer("-0#{d}1", 10).should == -1
+ Integer("-0#{d}10", 10).should == -10
+ end
+
+ it "raises an ArgumentError if the number cannot be parsed as decimal and the base is 10" do
+ -> { Integer("0#{d}a", 10) }.should raise_error(ArgumentError)
+ end
+
+ 2.upto(9) do |base|
+ it "raises an ArgumentError if the number begins with 0#{d} and the base is #{base}" do
+ -> { Integer("0#{d}1", base) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises an ArgumentError if a base is given for a non-String value" do
+ -> { Integer(98, 15) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "when passed exception: false" do
+ describe "and valid argument" do
+ it "returns an Integer number" do
+ Integer("100", 10, exception: false).should == 100
+ Integer("100", 2, exception: false).should == 4
+ end
+ end
+
+ describe "and invalid argument" do
+ it "swallows an error" do
+ Integer("999", 2, exception: false).should == nil
+ Integer("abc", 10, exception: false).should == nil
+ end
+ end
+ end
+end
+
+describe :kernel_Integer, shared: true do
+ it "raises an ArgumentError when the String contains digits out of range of radix 2" do
+ str = "23456789abcdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 2) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 3" do
+ str = "3456789abcdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 3) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 4" do
+ str = "456789abcdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 4) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 5" do
+ str = "56789abcdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 5) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 6" do
+ str = "6789abcdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 6) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 7" do
+ str = "789abcdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 7) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 8" do
+ str = "89abcdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 8) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 9" do
+ str = "9abcdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 9) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 10" do
+ str = "abcdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 10) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 11" do
+ str = "bcdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 11) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 12" do
+ str = "cdefghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 12) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 13" do
+ str = "defghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 13) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 14" do
+ str = "efghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 14) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 15" do
+ str = "fghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 15) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 16" do
+ str = "ghijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 16) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 17" do
+ str = "hijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 17) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 18" do
+ str = "ijklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 18) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 19" do
+ str = "jklmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 19) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 20" do
+ str = "klmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 20) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 21" do
+ str = "lmnopqrstuvwxyz"
+ -> { @object.send(@method, str, 21) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 22" do
+ str = "mnopqrstuvwxyz"
+ -> { @object.send(@method, str, 22) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 23" do
+ str = "nopqrstuvwxyz"
+ -> { @object.send(@method, str, 23) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 24" do
+ str = "opqrstuvwxyz"
+ -> { @object.send(@method, str, 24) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 25" do
+ str = "pqrstuvwxyz"
+ -> { @object.send(@method, str, 25) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 26" do
+ str = "qrstuvwxyz"
+ -> { @object.send(@method, str, 26) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 27" do
+ str = "rstuvwxyz"
+ -> { @object.send(@method, str, 27) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 28" do
+ str = "stuvwxyz"
+ -> { @object.send(@method, str, 28) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 29" do
+ str = "tuvwxyz"
+ -> { @object.send(@method, str, 29) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 30" do
+ str = "uvwxyz"
+ -> { @object.send(@method, str, 30) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 31" do
+ str = "vwxyz"
+ -> { @object.send(@method, str, 31) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 32" do
+ str = "wxyz"
+ -> { @object.send(@method, str, 32) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 33" do
+ str = "xyz"
+ -> { @object.send(@method, str, 33) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 34" do
+ str = "yz"
+ -> { @object.send(@method, str, 34) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 35" do
+ str = "z"
+ -> { @object.send(@method, str, 35) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the String contains digits out of range of radix 36" do
+ -> { @object.send(@method, "{", 36) }.should raise_error(ArgumentError)
+ end
+end
+
+describe "Kernel.Integer" do
+ it_behaves_like :kernel_Integer, :Integer_method, KernelSpecs
+
+ # TODO: fix these specs
+ it_behaves_like :kernel_integer, :Integer, Kernel
+ it_behaves_like "Integer() given a String", :Integer
+
+ it_behaves_like "Integer() given a String and base", :Integer
+
+ it "is a public method" do
+ Kernel.Integer(10).should == 10
+ end
+end
+
+describe "Kernel#Integer" do
+ it_behaves_like :kernel_Integer, :Integer_function, KernelSpecs
+
+ # TODO: fix these specs
+ it_behaves_like :kernel_integer, :Integer, Object.new
+ it_behaves_like "Integer() given a String", :Integer
+
+ it_behaves_like "Integer() given a String and base", :Integer
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:Integer)
+ end
+end
diff --git a/spec/ruby/core/kernel/Rational_spec.rb b/spec/ruby/core/kernel/Rational_spec.rb
new file mode 100644
index 0000000000..2d1051db7f
--- /dev/null
+++ b/spec/ruby/core/kernel/Rational_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/rational/Rational'
+
+describe "Kernel.Rational" do
+ it_behaves_like :kernel_Rational, :Rational
+end
diff --git a/spec/ruby/core/kernel/String_spec.rb b/spec/ruby/core/kernel/String_spec.rb
new file mode 100644
index 0000000000..47ee797be5
--- /dev/null
+++ b/spec/ruby/core/kernel/String_spec.rb
@@ -0,0 +1,106 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :kernel_String, shared: true do
+ it "converts nil to a String" do
+ @object.send(@method, nil).should == ""
+ end
+
+ it "converts a Float to a String" do
+ @object.send(@method, 1.12).should == "1.12"
+ end
+
+ it "converts a boolean to a String" do
+ @object.send(@method, false).should == "false"
+ @object.send(@method, true).should == "true"
+ end
+
+ it "converts a constant to a String" do
+ @object.send(@method, Object).should == "Object"
+ end
+
+ it "calls #to_s to convert an arbitrary object to a String" do
+ obj = mock('test')
+ obj.should_receive(:to_s).and_return("test")
+
+ @object.send(@method, obj).should == "test"
+ end
+
+ it "raises a TypeError if #to_s does not exist" do
+ obj = mock('to_s')
+ class << obj
+ undef_method :to_s
+ end
+
+ -> { @object.send(@method, obj) }.should raise_error(TypeError)
+ end
+
+ # #5158
+ it "raises a TypeError if respond_to? returns false for #to_s" do
+ obj = mock("to_s")
+ class << obj
+ def respond_to?(meth, include_private=false)
+ meth == :to_s ? false : super
+ end
+ end
+
+ -> { @object.send(@method, obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_s is not defined, even though #respond_to?(:to_s) returns true" do
+ # cannot use a mock because of how RSpec affects #method_missing
+ obj = Object.new
+ class << obj
+ undef_method :to_s
+ def respond_to?(meth, include_private=false)
+ meth == :to_s ? true : super
+ end
+ end
+
+ -> { @object.send(@method, obj) }.should raise_error(TypeError)
+ end
+
+ it "calls #to_s if #respond_to?(:to_s) returns true" do
+ obj = mock('to_s')
+ class << obj
+ undef_method :to_s
+ def method_missing(meth, *args)
+ meth == :to_s ? "test" : super
+ end
+ end
+
+ @object.send(@method, obj).should == "test"
+ end
+
+ it "raises a TypeError if #to_s does not return a String" do
+ (obj = mock('123')).should_receive(:to_s).and_return(123)
+ -> { @object.send(@method, obj) }.should raise_error(TypeError)
+ end
+
+ it "returns the same object if it is already a String" do
+ string = "Hello"
+ string.should_not_receive(:to_s)
+ string2 = @object.send(@method, string)
+ string.should equal(string2)
+ end
+
+ it "returns the same object if it is an instance of a String subclass" do
+ subklass = Class.new(String)
+ string = subklass.new("Hello")
+ string.should_not_receive(:to_s)
+ string2 = @object.send(@method, string)
+ string.should equal(string2)
+ end
+end
+
+describe "Kernel.String" do
+ it_behaves_like :kernel_String, :String, Kernel
+end
+
+describe "Kernel#String" do
+ it_behaves_like :kernel_String, :String, Object.new
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:String)
+ end
+end
diff --git a/spec/ruby/core/kernel/__callee___spec.rb b/spec/ruby/core/kernel/__callee___spec.rb
new file mode 100644
index 0000000000..3059ea8b57
--- /dev/null
+++ b/spec/ruby/core/kernel/__callee___spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/__callee__'
+
+describe "Kernel.__callee__" do
+ it "returns the current method, even when aliased" do
+ KernelSpecs::CalleeTest.new.f.should == :f
+ end
+
+ it "returns the aliased name when aliased method" do
+ KernelSpecs::CalleeTest.new.g.should == :g
+ end
+
+ it "returns the caller from blocks too" do
+ KernelSpecs::CalleeTest.new.in_block.should == [:in_block, :in_block]
+ end
+
+ it "returns the caller from define_method too" do
+ KernelSpecs::CalleeTest.new.dm.should == :dm
+ end
+
+ it "returns the caller from block inside define_method too" do
+ KernelSpecs::CalleeTest.new.dm_block.should == [:dm_block, :dm_block]
+ end
+
+ it "returns method name even from send" do
+ KernelSpecs::CalleeTest.new.from_send.should == :from_send
+ end
+
+ it "returns method name even from eval" do
+ KernelSpecs::CalleeTest.new.from_eval.should == :from_eval
+ end
+
+ it "returns nil from inside a class body" do
+ KernelSpecs::CalleeTest.new.from_class_body.should == nil
+ end
+
+ it "returns nil when not called from a method" do
+ __callee__.should == nil
+ end
+
+ it "returns the caller from a define_method called from the same class" do
+ c = Class.new do
+ define_method(:f) { 1.times{ break __callee__ } }
+ def g; f end
+ end
+ c.new.g.should == :f
+ end
+end
diff --git a/spec/ruby/core/kernel/__dir___spec.rb b/spec/ruby/core/kernel/__dir___spec.rb
new file mode 100644
index 0000000000..324792a408
--- /dev/null
+++ b/spec/ruby/core/kernel/__dir___spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#__dir__" do
+ it "returns the real name of the directory containing the currently-executing file" do
+ __dir__.should == File.realpath(File.dirname(__FILE__))
+ end
+
+ it "returns the expanded path of the directory when used in the main script" do
+ fixtures_dir = File.dirname(fixture(__FILE__, '__dir__.rb'))
+ Dir.chdir(fixtures_dir) do
+ ruby_exe("__dir__.rb").should == "__dir__.rb\n#{fixtures_dir}\n"
+ end
+ end
+
+ context "when used in eval with a given filename" do
+ it "returns File.dirname(filename)" do
+ eval("__dir__", nil, "foo.rb").should == "."
+ eval("__dir__", nil, "foo/bar.rb").should == "foo"
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ context "when used in eval with top level binding" do
+ it "returns the real name of the directory containing the currently-executing file" do
+ eval("__dir__", binding).should == File.realpath(File.dirname(__FILE__))
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ context "when used in eval with top level binding" do
+ it "returns nil" do
+ eval("__dir__", binding).should == nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/__method___spec.rb b/spec/ruby/core/kernel/__method___spec.rb
new file mode 100644
index 0000000000..578d25640d
--- /dev/null
+++ b/spec/ruby/core/kernel/__method___spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/__method__'
+
+describe "Kernel.__method__" do
+ it "returns the current method, even when aliased" do
+ KernelSpecs::MethodTest.new.f.should == :f
+ end
+
+ it "returns the original name when aliased method" do
+ KernelSpecs::MethodTest.new.g.should == :f
+ end
+
+ it "returns the caller from blocks too" do
+ KernelSpecs::MethodTest.new.in_block.should == [:in_block, :in_block]
+ end
+
+ it "returns the caller from define_method too" do
+ KernelSpecs::MethodTest.new.dm.should == :dm
+ end
+
+ it "returns the caller from block inside define_method too" do
+ KernelSpecs::MethodTest.new.dm_block.should == [:dm_block, :dm_block]
+ end
+
+ it "returns method name even from send" do
+ KernelSpecs::MethodTest.new.from_send.should == :from_send
+ end
+
+ it "returns method name even from eval" do
+ KernelSpecs::MethodTest.new.from_eval.should == :from_eval
+ end
+
+ it "returns nil from inside a class body" do
+ KernelSpecs::MethodTest.new.from_class_body.should == nil
+ end
+
+ it "returns nil when not called from a method" do
+ __method__.should == nil
+ end
+end
diff --git a/spec/ruby/core/kernel/abort_spec.rb b/spec/ruby/core/kernel/abort_spec.rb
new file mode 100644
index 0000000000..f8152718c5
--- /dev/null
+++ b/spec/ruby/core/kernel/abort_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/process/abort'
+
+describe "Kernel#abort" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:abort)
+ end
+
+ it_behaves_like :process_abort, :abort, KernelSpecs::Method.new
+end
+
+describe "Kernel.abort" do
+ it_behaves_like :process_abort, :abort, Kernel
+end
diff --git a/spec/ruby/core/kernel/at_exit_spec.rb b/spec/ruby/core/kernel/at_exit_spec.rb
new file mode 100644
index 0000000000..a784c1ae17
--- /dev/null
+++ b/spec/ruby/core/kernel/at_exit_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel.at_exit" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:at_exit)
+ end
+
+ it "runs after all other code" do
+ ruby_exe("at_exit {print 5}; print 6").should == "65"
+ end
+
+ it "runs in reverse order of registration" do
+ code = "at_exit {print 4};at_exit {print 5}; print 6; at_exit {print 7}"
+ ruby_exe(code).should == "6754"
+ end
+
+ it "allows calling exit inside at_exit handler" do
+ code = "at_exit {print 3}; at_exit {print 4; exit; print 5}; at_exit {print 6}"
+ ruby_exe(code).should == "643"
+ end
+
+ it "gives access to the last raised exception" do
+ code = <<-EOC
+ at_exit do
+ puts "The exception matches: \#{$! == $exception} (message=\#{$!.message})"
+ end
+
+ begin
+ raise "foo"
+ rescue => $exception
+ raise
+ end
+ EOC
+
+ result = ruby_exe(code, args: "2>&1", exit_status: 1)
+ result.lines.should.include?("The exception matches: true (message=foo)\n")
+ end
+
+ it "both exceptions in at_exit and in the main script are printed" do
+ code = 'at_exit { raise "at_exit_error" }; raise "main_script_error"'
+ result = ruby_exe(code, args: "2>&1", exit_status: 1)
+ result.should.include?('at_exit_error (RuntimeError)')
+ result.should.include?('main_script_error (RuntimeError)')
+ end
+
+ it "decides the exit status if both at_exit and the main script raise SystemExit" do
+ ruby_exe('at_exit { exit 43 }; exit 42', args: "2>&1", exit_status: 43)
+ $?.exitstatus.should == 43
+ end
+
+ it "runs all at_exit even if some raise exceptions" do
+ code = 'at_exit { STDERR.puts "last" }; at_exit { exit 43 }; at_exit { STDERR.puts "first" }; exit 42'
+ result = ruby_exe(code, args: "2>&1", exit_status: 43)
+ result.should == "first\nlast\n"
+ $?.exitstatus.should == 43
+ end
+
+ it "runs at_exit handlers even if the main script fails to parse" do
+ script = fixture(__FILE__, "at_exit.rb")
+ result = ruby_exe('{', options: "-r#{script}", args: "2>&1", exit_status: 1)
+ $?.should_not.success?
+ result.should.include?("at_exit ran\n")
+ result.should.include?("syntax error")
+ end
+end
+
+describe "Kernel#at_exit" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/autoload_spec.rb b/spec/ruby/core/kernel/autoload_spec.rb
new file mode 100644
index 0000000000..0404caec6d
--- /dev/null
+++ b/spec/ruby/core/kernel/autoload_spec.rb
@@ -0,0 +1,175 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# These specs only illustrate the basic autoload cases
+# and where toplevel autoload behaves differently from
+# Module#autoload. See those specs for more examples.
+
+autoload :KSAutoloadA, "autoload_a.rb"
+autoload :KSAutoloadB, fixture(__FILE__, "autoload_b.rb")
+autoload :KSAutoloadCallsRequire, "main_autoload_not_exist.rb"
+
+def check_autoload(const)
+ autoload? const
+end
+
+describe "Kernel#autoload" do
+ before :each do
+ @loaded_features = $".dup
+ end
+
+ after :each do
+ $".replace @loaded_features
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:autoload)
+ end
+
+ it "registers a file to load the first time the named constant is accessed" do
+ Object.autoload?(:KSAutoloadA).should == "autoload_a.rb"
+ end
+
+ it "registers a file to load the first time the named constant is accessed" do
+ check_autoload(:KSAutoloadA).should == "autoload_a.rb"
+ end
+
+ it "sets the autoload constant in Object's constant table" do
+ Object.should have_constant(:KSAutoloadA)
+ end
+
+ it "loads the file when the constant is accessed" do
+ KSAutoloadB.loaded.should == :ksautoload_b
+ end
+
+ it "calls main.require(path) to load the file" do
+ main = TOPLEVEL_BINDING.eval("self")
+ main.should_receive(:require).with("main_autoload_not_exist.rb")
+ # The constant won't be defined since require is mocked to do nothing
+ -> { KSAutoloadCallsRequire }.should raise_error(NameError)
+ end
+
+ it "can autoload in instance_eval" do
+ Object.new.instance_eval do
+ autoload :KSAutoloadD, fixture(__FILE__, "autoload_d.rb")
+ KSAutoloadD.loaded.should == :ksautoload_d
+ end
+ end
+
+ describe "inside a Class.new method body" do
+ # NOTE: this spec is being discussed in https://github.com/ruby/spec/pull/839
+ it "should define on the new anonymous class" do
+ cls = Class.new do
+ def go
+ autoload :Object, 'bogus'
+ autoload? :Object
+ end
+ end
+
+ cls.new.go.should == 'bogus'
+ cls.autoload?(:Object).should == 'bogus'
+ end
+ end
+
+ describe "when Object is frozen" do
+ it "raises a FrozenError before defining the constant" do
+ ruby_exe(fixture(__FILE__, "autoload_frozen.rb")).should == "FrozenError - nil"
+ end
+ end
+
+ describe "when called from included module's method" do
+ before :all do
+ @path = fixture(__FILE__, "autoload_from_included_module.rb")
+ KernelSpecs::AutoloadMethodIncluder.new.setup_autoload(@path)
+ end
+
+ it "setups the autoload on the included module" do
+ KernelSpecs::AutoloadMethod.autoload?(:AutoloadFromIncludedModule).should == @path
+ end
+
+ it "the autoload is reachable from the class too" do
+ KernelSpecs::AutoloadMethodIncluder.autoload?(:AutoloadFromIncludedModule).should == @path
+ end
+
+ it "the autoload relative to the included module works" do
+ KernelSpecs::AutoloadMethod::AutoloadFromIncludedModule.loaded.should == :autoload_from_included_module
+ end
+ end
+end
+
+describe "Kernel#autoload?" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:autoload?)
+ end
+
+ it "returns the name of the file that will be autoloaded" do
+ check_autoload(:KSAutoloadA).should == "autoload_a.rb"
+ end
+
+ it "returns nil if no file has been registered for a constant" do
+ check_autoload(:Manualload).should be_nil
+ end
+end
+
+Kernel.autoload :KSAutoloadBB, "no_autoload.rb"
+
+describe "Kernel.autoload" do
+ before :all do
+ @non_existent = fixture __FILE__, "no_autoload.rb"
+ end
+
+ before :each do
+ @loaded_features = $".dup
+
+ ScratchPad.clear
+ end
+
+ after :each do
+ $".replace @loaded_features
+ end
+
+ it "registers a file to load the first time the toplevel constant is accessed" do
+ Kernel.autoload :KSAutoloadAA, @non_existent
+ Kernel.autoload?(:KSAutoloadAA).should == @non_existent
+ end
+
+ it "sets the autoload constant in Object's constant table" do
+ Object.should have_constant(:KSAutoloadBB)
+ end
+
+ it "calls #to_path on non-String filenames" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return @non_existent
+ Kernel.autoload :KSAutoloadAA, p
+ end
+
+ describe "when called from included module's method" do
+ before :all do
+ @path = fixture(__FILE__, "autoload_from_included_module2.rb")
+ KernelSpecs::AutoloadMethodIncluder2.new.setup_autoload(@path)
+ end
+
+ it "setups the autoload on the included module" do
+ KernelSpecs::AutoloadMethod2.autoload?(:AutoloadFromIncludedModule2).should == @path
+ end
+
+ it "the autoload is reachable from the class too" do
+ KernelSpecs::AutoloadMethodIncluder2.autoload?(:AutoloadFromIncludedModule2).should == @path
+ end
+
+ it "the autoload relative to the included module works" do
+ KernelSpecs::AutoloadMethod2::AutoloadFromIncludedModule2.loaded.should == :autoload_from_included_module2
+ end
+ end
+end
+
+describe "Kernel.autoload?" do
+ it "returns the name of the file that will be autoloaded" do
+ Kernel.autoload :KSAutoload, "autoload.rb"
+ Kernel.autoload?(:KSAutoload).should == "autoload.rb"
+ end
+
+ it "returns nil if no file has been registered for a constant" do
+ Kernel.autoload?(:Manualload).should be_nil
+ end
+end
diff --git a/spec/ruby/core/kernel/backtick_spec.rb b/spec/ruby/core/kernel/backtick_spec.rb
new file mode 100644
index 0000000000..834d5636c1
--- /dev/null
+++ b/spec/ruby/core/kernel/backtick_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#`" do
+ before :each do
+ @original_external = Encoding.default_external
+ end
+
+ after :each do
+ Encoding.default_external = @original_external
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:`)
+ end
+
+ it "returns the standard output of the executed sub-process" do
+ ip = 'world'
+ `echo disc #{ip}`.should == "disc world\n"
+ end
+
+ it "lets the standard error stream pass through to the inherited stderr" do
+ cmd = ruby_cmd('STDERR.print "error stream"')
+ -> {
+ `#{cmd}`.should == ""
+ }.should output_to_fd("error stream", STDERR)
+ end
+
+ it "produces a String in the default external encoding" do
+ Encoding.default_external = Encoding::SHIFT_JIS
+ `echo disc`.encoding.should equal(Encoding::SHIFT_JIS)
+ end
+
+ it "raises an Errno::ENOENT if the command is not executable" do
+ -> { `nonexistent_command` }.should raise_error(Errno::ENOENT)
+ end
+
+ platform_is_not :windows do
+ it "handles invalid UTF-8 bytes in command" do
+ `echo "testing\xC2 a non UTF-8 string"`.b.should == "testing\xC2 a non UTF-8 string\n".b
+ end
+
+ it "sets $? to the exit status of the executed sub-process" do
+ ip = 'world'
+ `echo disc #{ip}`
+ $?.should be_kind_of(Process::Status)
+ $?.should_not.stopped?
+ $?.should.exited?
+ $?.exitstatus.should == 0
+ $?.should.success?
+ `echo disc #{ip}; exit 99`
+ $?.should be_kind_of(Process::Status)
+ $?.should_not.stopped?
+ $?.should.exited?
+ $?.exitstatus.should == 99
+ $?.should_not.success?
+ end
+ end
+
+ platform_is :windows do
+ it "sets $? to the exit status of the executed sub-process" do
+ ip = 'world'
+ `echo disc #{ip}`
+ $?.should be_kind_of(Process::Status)
+ $?.should_not.stopped?
+ $?.should.exited?
+ $?.exitstatus.should == 0
+ $?.should.success?
+ `echo disc #{ip}& exit 99`
+ $?.should be_kind_of(Process::Status)
+ $?.should_not.stopped?
+ $?.should.exited?
+ $?.exitstatus.should == 99
+ $?.should_not.success?
+ end
+ end
+end
+
+describe "Kernel.`" do
+ it "tries to convert the given argument to String using #to_str" do
+ (obj = mock('echo test')).should_receive(:to_str).and_return("echo test")
+ Kernel.`(obj).should == "test\n" #` fix vim syntax highlighting
+ end
+end
diff --git a/spec/ruby/core/kernel/binding_spec.rb b/spec/ruby/core/kernel/binding_spec.rb
new file mode 100644
index 0000000000..f1c9c6ec9f
--- /dev/null
+++ b/spec/ruby/core/kernel/binding_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel.binding" do
+ it "returns a binding for the caller" do
+ Kernel.binding.eval("self").should == self
+ end
+end
+
+describe "Kernel#binding" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:binding)
+ end
+
+ before :each do
+ @b1 = KernelSpecs::Binding.new(99).get_binding
+ ScratchPad.clear
+ end
+
+ it "returns a Binding object" do
+ @b1.kind_of?(Binding).should == true
+ end
+
+ it "encapsulates the execution context properly" do
+ eval("@secret", @b1).should == 100
+ eval("a", @b1).should == true
+ eval("b", @b1).should == true
+ eval("@@super_secret", @b1).should == "password"
+
+ eval("square(2)", @b1).should == 4
+ eval("self.square(2)", @b1).should == 4
+
+ eval("a = false", @b1)
+ eval("a", @b1).should == false
+ end
+
+ it "raises a NameError on undefined variable" do
+ -> { eval("a_fake_variable", @b1) }.should raise_error(NameError)
+ end
+
+ it "uses the closure's self as self in the binding" do
+ m = mock(:whatever)
+ eval('self', m.send(:binding)).should == self
+ end
+
+ it "uses the class as self in a Class.new block" do
+ m = mock(:whatever)
+ cls = Class.new { ScratchPad.record eval('self', m.send(:binding)) }
+ ScratchPad.recorded.should == cls
+ end
+end
diff --git a/spec/ruby/core/kernel/block_given_spec.rb b/spec/ruby/core/kernel/block_given_spec.rb
new file mode 100644
index 0000000000..b00bfabfc3
--- /dev/null
+++ b/spec/ruby/core/kernel/block_given_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :kernel_block_given, shared: true do
+ it "returns true if and only if a block is supplied" do
+ @object.accept_block {}.should == true
+ @object.accept_block_as_argument {}.should == true
+
+ @object.accept_block.should == false
+ @object.accept_block_as_argument.should == false
+ end
+
+ # Clarify: Based on http://www.ruby-forum.com/topic/137822 it appears
+ # that Matz wanted this to be true in 1.9.
+ it "returns false when a method defined by define_method is called with a block" do
+ @object.defined_block {}.should == false
+ end
+end
+
+describe "Kernel#block_given?" do
+ it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::BlockGiven
+
+ it "returns false outside of a method" do
+ block_given?.should == false
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:block_given?)
+ end
+end
+
+describe "Kernel.block_given?" do
+ it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::KernelBlockGiven
+end
+
+describe "self.send(:block_given?)" do
+ it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::SelfBlockGiven
+end
diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb
new file mode 100644
index 0000000000..5994b28fa3
--- /dev/null
+++ b/spec/ruby/core/kernel/caller_locations_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/caller_locations'
+
+describe 'Kernel#caller_locations' do
+ it 'is a private method' do
+ Kernel.should have_private_instance_method(:caller_locations)
+ end
+
+ it 'returns an Array of caller locations' do
+ KernelSpecs::CallerLocationsTest.locations.should_not.empty?
+ end
+
+ it 'returns an Array of caller locations using a custom offset' do
+ locations = KernelSpecs::CallerLocationsTest.locations(2)
+
+ locations[0].absolute_path.should.end_with?('mspec.rb')
+ end
+
+ it 'returns an Array of caller locations using a custom limit' do
+ locations = KernelSpecs::CallerLocationsTest.locations(1, 1)
+
+ locations.length.should == 1
+ end
+
+ it "can be called with a range" do
+ locations1 = caller_locations(0)
+ locations2 = caller_locations(2..4)
+ locations1[2..4].map(&:to_s).should == locations2.map(&:to_s)
+ end
+
+ it "works with endless ranges" do
+ locations1 = caller_locations(0)
+ locations2 = caller_locations(eval("(2..)"))
+ locations2.map(&:to_s).should == locations1[2..-1].map(&:to_s)
+ end
+
+ it "works with beginless ranges" do
+ locations1 = caller_locations(0)
+ locations2 = caller_locations((...5))
+ locations2.map(&:to_s)[eval("(2..)")].should == locations1[(...5)].map(&:to_s)[eval("(2..)")]
+ end
+
+ it "can be called with a range whose end is negative" do
+ locations1 = caller_locations(0)
+ locations2 = caller_locations(2..-1)
+ locations3 = caller_locations(2..-2)
+ locations1[2..-1].map(&:to_s).should == locations2.map(&:to_s)
+ locations1[2..-2].map(&:to_s).should == locations3.map(&:to_s)
+ end
+
+ it "must return nil if omitting more locations than available" do
+ caller_locations(100).should == nil
+ caller_locations(100..-1).should == nil
+ end
+
+ it "must return [] if omitting exactly the number of locations available" do
+ omit = caller_locations(0).length
+ caller_locations(omit).should == []
+ end
+
+ it 'returns the locations as Thread::Backtrace::Location instances' do
+ locations = KernelSpecs::CallerLocationsTest.locations
+
+ locations.each do |location|
+ location.kind_of?(Thread::Backtrace::Location).should == true
+ end
+ end
+
+ it "must return the same locations when called with 1..-1 and when called with no arguments" do
+ caller_locations.map(&:to_s).should == caller_locations(1..-1).map(&:to_s)
+ end
+
+ guard -> { Kernel.instance_method(:tap).source_location } do
+ it "includes core library methods defined in Ruby" do
+ file, line = Kernel.instance_method(:tap).source_location
+ file.should.start_with?('<internal:')
+
+ loc = nil
+ tap { loc = caller_locations(1, 1)[0] }
+ loc.label.should == "tap"
+ loc.path.should.start_with? "<internal:"
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/caller_spec.rb b/spec/ruby/core/kernel/caller_spec.rb
new file mode 100644
index 0000000000..f1ff7044b8
--- /dev/null
+++ b/spec/ruby/core/kernel/caller_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/caller'
+
+describe 'Kernel#caller' do
+ it 'is a private method' do
+ Kernel.should have_private_instance_method(:caller)
+ end
+
+ it 'returns an Array of caller locations' do
+ KernelSpecs::CallerTest.locations.should_not.empty?
+ end
+
+ it 'returns an Array of caller locations using a custom offset' do
+ locations = KernelSpecs::CallerTest.locations(2)
+
+ locations[0].should =~ %r{runner/mspec.rb}
+ end
+
+ it 'returns an Array of caller locations using a custom limit' do
+ locations = KernelSpecs::CallerTest.locations(1, 1)
+
+ locations.length.should == 1
+ end
+
+ it 'returns an Array of caller locations using a range' do
+ locations = KernelSpecs::CallerTest.locations(1..1)
+
+ locations.length.should == 1
+ end
+
+ it 'returns the locations as String instances' do
+ locations = KernelSpecs::CallerTest.locations
+ line = __LINE__ - 1
+
+ locations[0].should include("#{__FILE__}:#{line}:in")
+ end
+
+ it "returns an Array with the block given to #at_exit at the base of the stack" do
+ path = fixture(__FILE__, "caller_at_exit.rb")
+ lines = ruby_exe(path).lines
+ lines.should == [
+ "#{path}:6:in `foo'\n",
+ "#{path}:2:in `block in <main>'\n"
+ ]
+ end
+
+ it "works with endless ranges" do
+ locations1 = KernelSpecs::CallerTest.locations(0)
+ locations2 = KernelSpecs::CallerTest.locations(eval("(2..)"))
+ locations2.map(&:to_s).should == locations1[2..-1].map(&:to_s)
+ end
+
+ it "works with beginless ranges" do
+ locations1 = KernelSpecs::CallerTest.locations(0)
+ locations2 = KernelSpecs::CallerTest.locations((..5))
+ locations2.map(&:to_s)[eval("(2..)")].should == locations1[(..5)].map(&:to_s)[eval("(2..)")]
+ end
+
+ guard -> { Kernel.instance_method(:tap).source_location } do
+ it "includes core library methods defined in Ruby" do
+ file, line = Kernel.instance_method(:tap).source_location
+ file.should.start_with?('<internal:')
+
+ loc = nil
+ tap { loc = caller(1, 1)[0] }
+ loc.should.end_with? "in `tap'"
+ loc.should.start_with? "<internal:"
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/case_compare_spec.rb b/spec/ruby/core/kernel/case_compare_spec.rb
new file mode 100644
index 0000000000..b8d30960e8
--- /dev/null
+++ b/spec/ruby/core/kernel/case_compare_spec.rb
@@ -0,0 +1,135 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+
+module Specs
+ module Kernel
+
+ class HasNone
+ end
+
+ class HasOpEqual
+ def ==(other)
+ other.kind_of? HasOpEqual
+ end
+ end
+
+ class HasEqual
+ def equal?(other)
+ false
+ end
+ end
+
+ class HasOppoOpEqual
+ def ==(other)
+ false
+ end
+
+ def equal?(other)
+ false
+ end
+ end
+ end
+end
+
+
+describe "Kernel#=== for a class with default #== and #equal?" do
+ before :each do
+ @o1 = Specs::Kernel::HasNone.new
+ @o2 = @o1.dup
+ end
+
+ it "returns true if other object has same object id" do
+ @o1.object_id.should == @o1.object_id
+ (@o1 === @o1).should == true
+ end
+
+ it "returns false if other object does not have same object id" do
+ @o1.object_id.should_not == @o2.object_id
+ (@o1 === @o2).should == false
+ end
+end
+
+describe "Kernel#=== for a class with #== overridden to consider other object's class" do
+ before :each do
+ @o = Object.new
+ @o1 = Specs::Kernel::HasOpEqual.new
+ @o2 = @o1.dup
+ end
+
+ it "returns true if #== returns true even if #equal? is false" do
+ @o1.should_not equal(@o2)
+ (@o1 == @o2).should == true
+ (@o1 === @o2).should == true
+ end
+
+ it "returns true if #equal? returns true" do
+ @o1.should equal(@o1)
+ (@o1 === @o1).should == true
+ end
+
+ it "returns false if neither #== nor #equal? returns true" do
+ @o1.should_not equal(@o)
+ (@o1 == @o).should == false
+ (@o1 === @o).should == false
+ end
+end
+
+describe "Kernel#=== for a class with #equal? overridden to always be false" do
+ before :each do
+ @o = Object.new
+ @o1 = Specs::Kernel::HasEqual.new
+ @o2 = @o1.dup
+ end
+
+ it "returns true if #== returns true even if #equal? is false" do
+ @o1.should_not equal(@o1)
+ (@o1 == @o1).should == true
+ (@o1 === @o1).should == true
+ end
+
+ it "returns false if neither #== nor #equal? returns true" do
+ @o1.should_not equal(@o)
+ (@o1 == @o).should == false
+ (@o1 === @o).should == false
+ end
+end
+
+describe "Kernel#=== for a class with #== and #equal? overridden to always be false" do
+ before :each do
+ @o = Object.new
+ @o1 = Specs::Kernel::HasOppoOpEqual.new
+ @o2 = @o1.dup
+ end
+
+ it "returns true if the object id is the same even if both #== and #equal? return false" do
+ @o1.object_id.should == @o1.object_id
+
+ @o1.should_not equal(@o1)
+ (@o1 == @o1).should == false
+
+ (@o1 === @o1).should == true
+ end
+
+ it "returns false if the object id is not the same and both #== and #equal? return false" do
+ @o1.object_id.should_not == @o2.object_id
+
+ @o1.should_not equal(@o2)
+ (@o1 == @o2).should == false
+
+ (@o1 === @o2).should == false
+ end
+end
+
+describe "Kernel#=== does not call #object_id nor #equal?" do
+ before :each do
+ @o1 = Object.new
+ @o1.should_not_receive(:object_id)
+ @o1.should_not_receive(:equal?)
+ end
+
+ it "but still returns true for #== or #=== on the same object" do
+ (@o1 == @o1).should == true
+ (@o1 === @o1).should == true
+ end
+end
diff --git a/spec/ruby/core/kernel/catch_spec.rb b/spec/ruby/core/kernel/catch_spec.rb
new file mode 100644
index 0000000000..4060172429
--- /dev/null
+++ b/spec/ruby/core/kernel/catch_spec.rb
@@ -0,0 +1,127 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel.catch" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "executes its block and catches a thrown value matching its argument" do
+ catch :thrown_key do
+ ScratchPad.record :catch_block
+ throw :thrown_key
+ ScratchPad.record :throw_failed
+ end
+ ScratchPad.recorded.should == :catch_block
+ end
+
+ it "returns the second value passed to throw" do
+ catch(:thrown_key) { throw :thrown_key, :catch_value }.should == :catch_value
+ end
+
+ it "returns the last expression evaluated if throw was not called" do
+ catch(:thrown_key) { 1; :catch_block }.should == :catch_block
+ end
+
+ it "passes the given symbol to its block" do
+ catch :thrown_key do |tag|
+ ScratchPad.record tag
+ end
+ ScratchPad.recorded.should == :thrown_key
+ end
+
+ it "raises an ArgumentError if a Symbol is thrown for a String catch value" do
+ -> { catch("exit") { throw :exit } }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if a String with different identity is thrown" do
+ -> { catch("exit") { throw "exit" } }.should raise_error(ArgumentError)
+ end
+
+ it "catches a Symbol when thrown a matching Symbol" do
+ catch :thrown_key do
+ ScratchPad.record :catch_block
+ throw :thrown_key
+ end
+ ScratchPad.recorded.should == :catch_block
+ end
+
+ it "catches a String when thrown a String with the same identity" do
+ key = "thrown_key"
+ catch key do
+ ScratchPad.record :catch_block
+ throw key
+ end
+ ScratchPad.recorded.should == :catch_block
+ end
+
+ it "accepts an object as an argument" do
+ catch(Object.new) { :catch_block }.should == :catch_block
+ end
+
+ it "yields an object when called without arguments" do
+ catch { |tag| tag }.should be_an_instance_of(Object)
+ end
+
+ it "can be used even in a method different from where throw is called" do
+ class CatchSpecs
+ def self.throwing_method
+ throw :blah, :thrown_value
+ end
+ def self.catching_method
+ catch :blah do
+ throwing_method
+ end
+ end
+ end
+ CatchSpecs.catching_method.should == :thrown_value
+ end
+
+ describe "when nested" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "catches across invocation boundaries" do
+ catch :one do
+ ScratchPad << 1
+ catch :two do
+ ScratchPad << 2
+ catch :three do
+ ScratchPad << 3
+ throw :one
+ ScratchPad << 4
+ end
+ ScratchPad << 5
+ end
+ ScratchPad << 6
+ end
+
+ ScratchPad.recorded.should == [1, 2, 3]
+ end
+
+ it "catches in the nested invocation with the same key object" do
+ catch :thrown_key do
+ ScratchPad << 1
+ catch :thrown_key do
+ ScratchPad << 2
+ throw :thrown_key
+ ScratchPad << 3
+ end
+ ScratchPad << 4
+ end
+
+ ScratchPad.recorded.should == [1, 2, 4]
+ end
+ end
+
+ it "raises LocalJumpError if no block is given" do
+ -> { catch :blah }.should raise_error(LocalJumpError)
+ end
+end
+
+describe "Kernel#catch" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:catch)
+ end
+end
diff --git a/spec/ruby/core/kernel/chomp_spec.rb b/spec/ruby/core/kernel/chomp_spec.rb
new file mode 100644
index 0000000000..d30e77c35a
--- /dev/null
+++ b/spec/ruby/core/kernel/chomp_spec.rb
@@ -0,0 +1,65 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :kernel_chomp, shared: true do
+ it "removes the final newline of $_" do
+ KernelSpecs.chomp("abc\n", @method).should == "abc"
+ end
+
+ it "removes the final carriage return of $_" do
+ KernelSpecs.chomp("abc\r", @method).should == "abc"
+ end
+
+ it "removes the final carriage return, newline of $_" do
+ KernelSpecs.chomp("abc\r\n", @method).should == "abc"
+ end
+
+ it "removes only the final newline of $_" do
+ KernelSpecs.chomp("abc\n\n", @method).should == "abc\n"
+ end
+
+ it "removes the value of $/ from the end of $_" do
+ KernelSpecs.chomp("abcde", @method, "cde").should == "ab"
+ end
+end
+
+describe :kernel_chomp_private, shared: true do
+ it "is a private method" do
+ KernelSpecs.has_private_method(@method).should be_true
+ end
+end
+
+describe "Kernel.chomp" do
+ it_behaves_like :kernel_chomp, "Kernel.chomp"
+end
+
+describe "Kernel#chomp" do
+ it_behaves_like :kernel_chomp, "chomp"
+
+ it_behaves_like :kernel_chomp_private, :chomp
+end
+
+describe :kernel_chomp_encoded, shared: true do
+ before :each do
+ @external = Encoding.default_external
+ Encoding.default_external = Encoding::UTF_8
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ end
+
+ it "removes the final carriage return, newline from a multi-byte $_" do
+ script = fixture __FILE__, "#{@method}.rb"
+ KernelSpecs.run_with_dash_n(script).should == "ã‚れ"
+ end
+end
+
+describe "Kernel.chomp" do
+ it_behaves_like :kernel_chomp_encoded, "chomp"
+end
+
+describe "Kernel#chomp" do
+ it_behaves_like :kernel_chomp_encoded, "chomp_f"
+end
diff --git a/spec/ruby/core/kernel/chop_spec.rb b/spec/ruby/core/kernel/chop_spec.rb
new file mode 100644
index 0000000000..9b91c011bc
--- /dev/null
+++ b/spec/ruby/core/kernel/chop_spec.rb
@@ -0,0 +1,53 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :kernel_chop, shared: true do
+ it "removes the final character of $_" do
+ KernelSpecs.chop("abc", @method).should == "ab"
+ end
+
+ it "removes the final carriage return, newline of $_" do
+ KernelSpecs.chop("abc\r\n", @method).should == "abc"
+ end
+end
+
+describe :kernel_chop_private, shared: true do
+ it "is a private method" do
+ KernelSpecs.has_private_method(@method).should be_true
+ end
+end
+
+describe "Kernel.chop" do
+ it_behaves_like :kernel_chop, "Kernel.chop"
+end
+
+describe "Kernel#chop" do
+ it_behaves_like :kernel_chop_private, :chop
+
+ it_behaves_like :kernel_chop, "chop"
+end
+
+describe :kernel_chop_encoded, shared: true do
+ before :each do
+ @external = Encoding.default_external
+ Encoding.default_external = Encoding::UTF_8
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ end
+
+ it "removes the final multi-byte character from $_" do
+ script = fixture __FILE__, "#{@method}.rb"
+ KernelSpecs.run_with_dash_n(script).should == "ã‚"
+ end
+end
+
+describe "Kernel.chop" do
+ it_behaves_like :kernel_chop_encoded, "chop"
+end
+
+describe "Kernel#chop" do
+ it_behaves_like :kernel_chop_encoded, "chop_f"
+end
diff --git a/spec/ruby/core/kernel/class_spec.rb b/spec/ruby/core/kernel/class_spec.rb
new file mode 100644
index 0000000000..2725bde19b
--- /dev/null
+++ b/spec/ruby/core/kernel/class_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#class" do
+ it "returns the class of the object" do
+ Object.new.class.should equal(Object)
+
+ 1.class.should equal(Integer)
+ 3.14.class.should equal(Float)
+ :hello.class.should equal(Symbol)
+ "hello".class.should equal(String)
+ [1, 2].class.should equal(Array)
+ { 1 => 2 }.class.should equal(Hash)
+ end
+
+ it "returns Class for a class" do
+ BasicObject.class.should equal(Class)
+ String.class.should equal(Class)
+ end
+
+ it "returns the first non-singleton class" do
+ a = "hello"
+ def a.my_singleton_method; end
+ a.class.should equal(String)
+ end
+end
diff --git a/spec/ruby/core/kernel/clone_spec.rb b/spec/ruby/core/kernel/clone_spec.rb
new file mode 100644
index 0000000000..a87c7544fe
--- /dev/null
+++ b/spec/ruby/core/kernel/clone_spec.rb
@@ -0,0 +1,209 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/dup_clone'
+
+describe "Kernel#clone" do
+ it_behaves_like :kernel_dup_clone, :clone
+
+ before :each do
+ ScratchPad.clear
+ @obj = KernelSpecs::Duplicate.new 1, :a
+ end
+
+ it "calls #initialize_copy on the new instance" do
+ clone = @obj.clone
+ ScratchPad.recorded.should_not == @obj.object_id
+ ScratchPad.recorded.should == clone.object_id
+ end
+
+ it "uses the internal allocator and does not call #allocate" do
+ klass = Class.new
+ instance = klass.new
+
+ def klass.allocate
+ raise "allocate should not be called"
+ end
+
+ clone = instance.clone
+ clone.class.should equal klass
+ end
+
+ describe "with no arguments" do
+ it "copies frozen state from the original" do
+ o2 = @obj.clone
+ o2.should_not.frozen?
+
+ @obj.freeze
+ o3 = @obj.clone
+ o3.should.frozen?
+ end
+
+ it 'copies frozen?' do
+ o = ''.freeze.clone
+ o.frozen?.should be_true
+ end
+ end
+
+ describe "with freeze: nil" do
+ ruby_version_is ""..."3.0" do
+ it "raises ArgumentError" do
+ -> { @obj.clone(freeze: nil) }.should raise_error(ArgumentError, /unexpected value for freeze: NilClass/)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "copies frozen state from the original, like #clone without arguments" do
+ o2 = @obj.clone(freeze: nil)
+ o2.should_not.frozen?
+
+ @obj.freeze
+ o3 = @obj.clone(freeze: nil)
+ o3.should.frozen?
+ end
+
+ it "copies frozen?" do
+ o = "".freeze.clone(freeze: nil)
+ o.frozen?.should be_true
+ end
+ end
+ end
+
+ describe "with freeze: true" do
+ it 'makes a frozen copy if the original is frozen' do
+ @obj.freeze
+ @obj.clone(freeze: true).should.frozen?
+ end
+
+ ruby_version_is ''...'3.0' do
+ it 'does not freeze the copy even if the original is not frozen' do
+ @obj.clone(freeze: true).should_not.frozen?
+ end
+
+ it "calls #initialize_clone with no kwargs" do
+ obj = KernelSpecs::CloneFreeze.new
+ obj.clone(freeze: true)
+ ScratchPad.recorded.should == [obj, {}]
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it 'freezes the copy even if the original was not frozen' do
+ @obj.clone(freeze: true).should.frozen?
+ end
+
+ it "calls #initialize_clone with kwargs freeze: true" do
+ obj = KernelSpecs::CloneFreeze.new
+ obj.clone(freeze: true)
+ ScratchPad.recorded.should == [obj, { freeze: true }]
+ end
+
+ it "calls #initialize_clone with kwargs freeze: true even if #initialize_clone only takes a single argument" do
+ obj = KernelSpecs::Clone.new
+ -> { obj.clone(freeze: true) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1)')
+ end
+ end
+ end
+
+ describe "with freeze: false" do
+ it 'does not freeze the copy if the original is frozen' do
+ @obj.freeze
+ @obj.clone(freeze: false).should_not.frozen?
+ end
+
+ it 'does not freeze the copy if the original is not frozen' do
+ @obj.clone(freeze: false).should_not.frozen?
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "calls #initialize_clone with no kwargs" do
+ obj = KernelSpecs::CloneFreeze.new
+ obj.clone(freeze: false)
+ ScratchPad.recorded.should == [obj, {}]
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "calls #initialize_clone with kwargs freeze: false" do
+ obj = KernelSpecs::CloneFreeze.new
+ obj.clone(freeze: false)
+ ScratchPad.recorded.should == [obj, { freeze: false }]
+ end
+
+ it "calls #initialize_clone with kwargs freeze: false even if #initialize_clone only takes a single argument" do
+ obj = KernelSpecs::Clone.new
+ -> { obj.clone(freeze: false) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1)')
+ end
+ end
+ end
+
+ describe "with freeze: anything else" do
+ it 'raises ArgumentError when passed not true/false/nil' do
+ -> { @obj.clone(freeze: 1) }.should raise_error(ArgumentError, /unexpected value for freeze: Integer/)
+ -> { @obj.clone(freeze: "") }.should raise_error(ArgumentError, /unexpected value for freeze: String/)
+ -> { @obj.clone(freeze: Object.new) }.should raise_error(ArgumentError, /unexpected value for freeze: Object/)
+ end
+ end
+
+ it "copies instance variables" do
+ clone = @obj.clone
+ clone.one.should == 1
+ clone.two.should == :a
+ end
+
+ it "copies singleton methods" do
+ def @obj.special() :the_one end
+ clone = @obj.clone
+ clone.special.should == :the_one
+ end
+
+ it "copies modules included in the singleton class" do
+ class << @obj
+ include KernelSpecs::DuplicateM
+ end
+
+ clone = @obj.clone
+ clone.repr.should == "KernelSpecs::Duplicate"
+ end
+
+ it "copies constants defined in the singleton class" do
+ class << @obj
+ CLONE = :clone
+ end
+
+ clone = @obj.clone
+ class << clone
+ CLONE.should == :clone
+ end
+ end
+
+ it "replaces a singleton object's metaclass with a new copy with the same superclass" do
+ cls = Class.new do
+ def bar
+ ['a']
+ end
+ end
+
+ object = cls.new
+ object.define_singleton_method(:bar) do
+ ['b', *super()]
+ end
+ object.bar.should == ['b', 'a']
+
+ cloned = object.clone
+
+ cloned.singleton_methods.should == [:bar]
+
+ # bar should replace previous one
+ cloned.define_singleton_method(:bar) do
+ ['c', *super()]
+ end
+ cloned.bar.should == ['c', 'a']
+
+ # bar should be removed and call through to superclass
+ cloned.singleton_class.class_eval do
+ remove_method :bar
+ end
+
+ cloned.bar.should == ['a']
+ end
+end
diff --git a/spec/ruby/core/kernel/comparison_spec.rb b/spec/ruby/core/kernel/comparison_spec.rb
new file mode 100644
index 0000000000..affdc5c00d
--- /dev/null
+++ b/spec/ruby/core/kernel/comparison_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#<=>" do
+ it "returns 0 if self" do
+ obj = Object.new
+ obj.<=>(obj).should == 0
+ end
+
+ it "returns 0 if self is == to the argument" do
+ obj = mock('has ==')
+ obj.should_receive(:==).and_return(true)
+ obj.<=>(Object.new).should == 0
+ end
+
+ it "returns nil if self is eql? but not == to the argument" do
+ obj = mock('has eql?')
+ obj.should_not_receive(:eql?)
+ obj.<=>(Object.new).should be_nil
+ end
+
+ it "returns nil if self.==(arg) returns nil" do
+ obj = mock('wrong ==')
+ obj.should_receive(:==).and_return(nil)
+ obj.<=>(Object.new).should be_nil
+ end
+
+ it "returns nil if self is not == to the argument" do
+ obj = Object.new
+ obj.<=>(3.14).should be_nil
+ end
+end
diff --git a/spec/ruby/core/kernel/define_singleton_method_spec.rb b/spec/ruby/core/kernel/define_singleton_method_spec.rb
new file mode 100644
index 0000000000..2d8b1bf413
--- /dev/null
+++ b/spec/ruby/core/kernel/define_singleton_method_spec.rb
@@ -0,0 +1,114 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#define_singleton_method" do
+ describe "when given an UnboundMethod" do
+ class DefineSingletonMethodSpecClass
+ MY_CONST = 42
+ define_singleton_method(:another_test_method, self.method(:constants))
+ end
+
+ it "correctly calls the new method" do
+ klass = DefineSingletonMethodSpecClass
+ klass.another_test_method.should == klass.constants
+ end
+
+ it "adds the new method to the methods list" do
+ DefineSingletonMethodSpecClass.should have_method(:another_test_method)
+ end
+
+ it "defines any Child class method from any Parent's class methods" do
+ um = KernelSpecs::Parent.method(:parent_class_method).unbind
+ KernelSpecs::Child.send :define_singleton_method, :child_class_method, um
+ KernelSpecs::Child.child_class_method.should == :foo
+ ->{KernelSpecs::Parent.child_class_method}.should raise_error(NoMethodError)
+ end
+
+ it "will raise when attempting to define an object's singleton method from another object's singleton method" do
+ other = KernelSpecs::Parent.new
+ p = KernelSpecs::Parent.new
+ class << p
+ def singleton_method
+ :single
+ end
+ end
+ um = p.method(:singleton_method).unbind
+ ->{ other.send :define_singleton_method, :other_singleton_method, um }.should raise_error(TypeError)
+ end
+
+ end
+
+ it "defines a new method with the given name and the given block as body in self" do
+ class DefineSingletonMethodSpecClass
+ define_singleton_method(:block_test1) { self }
+ define_singleton_method(:block_test2, &-> { self })
+ end
+
+ o = DefineSingletonMethodSpecClass
+ o.block_test1.should == o
+ o.block_test2.should == o
+ end
+
+ it "raises a TypeError when the given method is no Method/Proc" do
+ -> {
+ Class.new { define_singleton_method(:test, "self") }
+ }.should raise_error(TypeError)
+
+ -> {
+ Class.new { define_singleton_method(:test, 1234) }
+ }.should raise_error(TypeError)
+ end
+
+ it "defines a new singleton method for objects" do
+ obj = Object.new
+ obj.define_singleton_method(:test) { "world!" }
+ obj.test.should == "world!"
+ -> {
+ Object.new.test
+ }.should raise_error(NoMethodError)
+ end
+
+ it "maintains the Proc's scope" do
+ class DefineMethodByProcClass
+ in_scope = true
+ method_proc = proc { in_scope }
+
+ define_singleton_method(:proc_test, &method_proc)
+ end
+
+ DefineMethodByProcClass.proc_test.should == true
+ end
+
+ it "raises an ArgumentError when no block is given" do
+ obj = Object.new
+ -> {
+ obj.define_singleton_method(:test)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "does not use the caller block when no block is given" do
+ o = Object.new
+ def o.define(name)
+ define_singleton_method(name)
+ end
+
+ -> {
+ o.define(:foo) { raise "not used" }
+ }.should raise_error(ArgumentError)
+ end
+
+ it "always defines the method with public visibility" do
+ cls = Class.new
+ def cls.define(name, &block)
+ private
+ define_singleton_method(name, &block)
+ end
+
+ -> {
+ suppress_warning do
+ cls.define(:foo) { :ok }
+ end
+ cls.foo.should == :ok
+ }.should_not raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/kernel/display_spec.rb b/spec/ruby/core/kernel/display_spec.rb
new file mode 100644
index 0000000000..9d429a9fac
--- /dev/null
+++ b/spec/ruby/core/kernel/display_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#display" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/dup_spec.rb b/spec/ruby/core/kernel/dup_spec.rb
new file mode 100644
index 0000000000..70198abdb7
--- /dev/null
+++ b/spec/ruby/core/kernel/dup_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/dup_clone'
+
+describe "Kernel#dup" do
+ it_behaves_like :kernel_dup_clone, :dup
+
+ before :each do
+ ScratchPad.clear
+ @obj = KernelSpecs::Duplicate.new 1, :a
+ end
+
+ it "calls #initialize_copy on the new instance" do
+ dup = @obj.dup
+ ScratchPad.recorded.should_not == @obj.object_id
+ ScratchPad.recorded.should == dup.object_id
+ end
+
+ it "uses the internal allocator and does not call #allocate" do
+ klass = Class.new
+ instance = klass.new
+
+ def klass.allocate
+ raise "allocate should not be called"
+ end
+
+ dup = instance.dup
+ dup.class.should equal klass
+ end
+
+ it "does not copy frozen state from the original" do
+ @obj.freeze
+ dup = @obj.dup
+
+ dup.should_not.frozen?
+ end
+
+ it "copies instance variables" do
+ dup = @obj.dup
+ dup.one.should == 1
+ dup.two.should == :a
+ end
+
+ it "does not copy singleton methods" do
+ def @obj.special() :the_one end
+ dup = @obj.dup
+ -> { dup.special }.should raise_error(NameError)
+ end
+
+ it "does not copy modules included in the singleton class" do
+ class << @obj
+ include KernelSpecs::DuplicateM
+ end
+
+ dup = @obj.dup
+ -> { dup.repr }.should raise_error(NameError)
+ end
+
+ it "does not copy constants defined in the singleton class" do
+ class << @obj
+ CLONE = :clone
+ end
+
+ dup = @obj.dup
+ -> { class << dup; CLONE; end }.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/core/kernel/enum_for_spec.rb b/spec/ruby/core/kernel/enum_for_spec.rb
new file mode 100644
index 0000000000..0092e20468
--- /dev/null
+++ b/spec/ruby/core/kernel/enum_for_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#enum_for" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/eql_spec.rb b/spec/ruby/core/kernel/eql_spec.rb
new file mode 100644
index 0000000000..e62a601a79
--- /dev/null
+++ b/spec/ruby/core/kernel/eql_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/equal'
+
+describe "Kernel#eql?" do
+ it "is a public instance method" do
+ Kernel.should have_public_instance_method(:eql?)
+ end
+
+ it_behaves_like :object_equal, :eql?
+end
diff --git a/spec/ruby/core/kernel/equal_value_spec.rb b/spec/ruby/core/kernel/equal_value_spec.rb
new file mode 100644
index 0000000000..2be151263d
--- /dev/null
+++ b/spec/ruby/core/kernel/equal_value_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#==" do
+ it "returns true only if obj and other are the same object" do
+ o1 = mock('o1')
+ o2 = mock('o2')
+ (o1 == o1).should == true
+ (o2 == o2).should == true
+ (o1 == o2).should == false
+ (nil == nil).should == true
+ (o1 == nil).should == false
+ (nil == o2).should == false
+ end
+end
diff --git a/spec/ruby/core/kernel/eval_spec.rb b/spec/ruby/core/kernel/eval_spec.rb
new file mode 100644
index 0000000000..9be0f2dfd3
--- /dev/null
+++ b/spec/ruby/core/kernel/eval_spec.rb
@@ -0,0 +1,439 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+EvalSpecs::A.new.c
+
+describe "Kernel#eval" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:eval)
+ end
+
+ it "is a module function" do
+ Kernel.respond_to?(:eval).should == true
+ end
+
+ it "evaluates the code within" do
+ eval("2 + 3").should == 5
+ end
+
+ it "coerces an object to string" do
+ eval(EvalSpecs::CoercedObject.new).should == 5
+ end
+
+ it "evaluates within the scope of the eval" do
+ EvalSpecs::A::B.name.should == "EvalSpecs::A::B"
+ end
+
+ it "evaluates such that constants are scoped to the class of the eval" do
+ EvalSpecs::A::C.name.should == "EvalSpecs::A::C"
+ end
+
+ it "finds a local in an enclosing scope" do
+ a = 1
+ eval("a").should == 1
+ end
+
+ it "updates a local in an enclosing scope" do
+ a = 1
+ eval("a = 2")
+ a.should == 2
+ end
+
+ it "updates a local in a surrounding block scope" do
+ EvalSpecs.new.f do
+ a = 1
+ eval("a = 2")
+ a.should == 2
+ end
+ end
+
+ it "updates a local in a scope above a surrounding block scope" do
+ a = 1
+ EvalSpecs.new.f do
+ eval("a = 2")
+ a.should == 2
+ end
+ a.should == 2
+ end
+
+ it "updates a local in a scope above when modified in a nested block scope" do
+ a = 1
+ es = EvalSpecs.new
+ eval("es.f { es.f { a = 2 } }")
+ a.should == 2
+ end
+
+ it "finds locals in a nested eval" do
+ eval('test = 10; eval("test")').should == 10
+ end
+
+ it "does not share locals across eval scopes" do
+ code = fixture __FILE__, "eval_locals.rb"
+ ruby_exe(code).chomp.should == "NameError"
+ end
+
+ it "doesn't accept a Proc object as a binding" do
+ x = 1
+ bind = proc {}
+
+ -> { eval("x", bind) }.should raise_error(TypeError)
+ end
+
+ it "does not make Proc locals visible to evaluated code" do
+ bind = proc { inner = 4 }
+ -> { eval("inner", bind.binding) }.should raise_error(NameError)
+ end
+
+ # REWRITE ME: This obscures the real behavior of where locals are stored
+ # in eval bindings.
+ it "allows a binding to be captured inside an eval" do
+ outer_binding = binding
+ level1 = eval("binding", outer_binding)
+ level2 = eval("binding", level1)
+
+ eval("x = 2", outer_binding)
+ eval("y = 3", level1)
+
+ eval("w=1", outer_binding)
+ eval("w", outer_binding).should == 1
+ eval("w=1", level1).should == 1
+ eval("w", level1).should == 1
+ eval("w=1", level2).should == 1
+ eval("w", level2).should == 1
+
+ eval("x", outer_binding).should == 2
+ eval("x=2", level1)
+ eval("x", level1).should == 2
+ eval("x=2", level2)
+ eval("x", level2).should == 2
+
+ eval("y=3", outer_binding)
+ eval("y", outer_binding).should == 3
+ eval("y=3", level1)
+ eval("y", level1).should == 3
+ eval("y=3", level2)
+ eval("y", level2).should == 3
+ end
+
+ it "uses the same scope for local variables when given the same binding" do
+ outer_binding = binding
+
+ eval("if false; a = 1; end", outer_binding)
+ eval("a", outer_binding).should be_nil
+ end
+
+ it "allows creating a new class in a binding" do
+ bind = proc {}
+ eval("class EvalBindingProcA; end; EvalBindingProcA.name", bind.binding).should =~ /EvalBindingProcA$/
+ end
+
+ it "allows creating a new class in a binding created by #eval" do
+ bind = eval "binding"
+ eval("class EvalBindingA; end; EvalBindingA.name", bind).should =~ /EvalBindingA$/
+ end
+
+ it "includes file and line information in syntax error" do
+ expected = 'speccing.rb'
+ -> {
+ eval('if true',TOPLEVEL_BINDING, expected)
+ }.should raise_error(SyntaxError) { |e|
+ e.message.should =~ /#{expected}:1:.+/
+ }
+ end
+
+ it "evaluates string with given filename and negative linenumber" do
+ expected_file = 'speccing.rb'
+ -> {
+ eval('if true',TOPLEVEL_BINDING, expected_file, -100)
+ }.should raise_error(SyntaxError) { |e|
+ e.message.should =~ /#{expected_file}:-100:.+/
+ }
+ end
+
+ it "sets constants at the toplevel from inside a block" do
+ # The class Object bit is needed to workaround some mspec oddness
+ class Object
+ [1].each { eval "Const = 1"}
+ Const.should == 1
+ remove_const :Const
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "uses the filename of the binding if none is provided" do
+ eval("__FILE__").should == "(eval)"
+ suppress_warning {eval("__FILE__", binding)}.should == __FILE__
+ eval("__FILE__", binding, "success").should == "success"
+ suppress_warning {eval("eval '__FILE__', binding")}.should == "(eval)"
+ suppress_warning {eval("eval '__FILE__', binding", binding)}.should == __FILE__
+ suppress_warning {eval("eval '__FILE__', binding", binding, 'success')}.should == 'success'
+ end
+
+ it 'uses the given binding file and line for __FILE__ and __LINE__' do
+ suppress_warning {
+ eval("[__FILE__, __LINE__]", binding).should == [__FILE__, __LINE__]
+ }
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "uses (eval) filename if none is provided" do
+ eval("__FILE__").should == "(eval)"
+ eval("__FILE__", binding).should == "(eval)"
+ eval("__FILE__", binding, "success").should == "success"
+ eval("eval '__FILE__', binding").should == "(eval)"
+ eval("eval '__FILE__', binding", binding).should == "(eval)"
+ eval("eval '__FILE__', binding", binding, 'success').should == '(eval)'
+ eval("eval '__FILE__', binding, 'success'", binding).should == 'success'
+ end
+
+ it 'uses (eval) for __FILE__ and 1 for __LINE__ with a binding argument' do
+ eval("[__FILE__, __LINE__]", binding).should == ["(eval)", 1]
+ end
+ end
+
+ # Found via Rubinius bug github:#149
+ it "does not alter the value of __FILE__ in the binding" do
+ first_time = EvalSpecs.call_eval
+ second_time = EvalSpecs.call_eval
+
+ # This bug is seen by calling the method twice and comparing the values
+ # of __FILE__ each time. If the bug is present, calling eval will set the
+ # value of __FILE__ to the eval's "filename" argument.
+
+ second_time.should_not == "(eval)"
+ first_time.should == second_time
+ end
+
+ it "can be aliased" do
+ alias aliased_eval eval
+ x = 2
+ aliased_eval('x += 40')
+ x.should == 42
+ end
+
+ # See http://jira.codehaus.org/browse/JRUBY-5163
+ it "uses the receiver as self inside the eval" do
+ eval("self").should equal(self)
+ Kernel.eval("self").should equal(Kernel)
+ end
+
+ it "does not pass the block to the method being eval'ed" do
+ -> {
+ eval('KernelSpecs::EvalTest.call_yield') { "content" }
+ }.should raise_error(LocalJumpError)
+ end
+
+ it "returns from the scope calling #eval when evaluating 'return'" do
+ -> { eval("return :eval") }.call.should == :eval
+ end
+
+ it "unwinds through a Proc-style closure and returns from a lambda-style closure in the closure chain" do
+ code = fixture __FILE__, "eval_return_with_lambda.rb"
+ ruby_exe(code).chomp.should == "a,b,c,eval,f"
+ end
+
+ it "raises a LocalJumpError if there is no lambda-style closure in the chain" do
+ code = fixture __FILE__, "eval_return_without_lambda.rb"
+ ruby_exe(code).chomp.should == "a,b,c,e,LocalJumpError,f"
+ end
+
+ it "can be called with Method#call" do
+ method(:eval).call("2 * 3").should == 6
+ end
+
+ it "has the correct default definee when called through Method#call" do
+ class EvalSpecs
+ method(:eval).call("def eval_spec_method_call; end")
+ EvalSpecs.should have_instance_method(:eval_spec_method_call)
+ end
+ end
+
+ # See language/magic_comment_spec.rb for more magic comments specs
+ describe "with a magic encoding comment" do
+ it "uses the magic comment encoding for the encoding of literal strings" do
+ code = "# encoding: UTF-8\n'é'.encoding".b
+ code.encoding.should == Encoding::BINARY
+ eval(code).should == Encoding::UTF_8
+ end
+
+ it "uses the magic comment encoding for parsing constants" do
+ code = <<CODE.b
+# encoding: UTF-8
+class EvalSpecs
+ VÏ€ = 3.14
+end
+CODE
+ code.encoding.should == Encoding::BINARY
+ eval(code)
+ EvalSpecs.constants(false).should include(:"VÏ€")
+ EvalSpecs::VÏ€.should == 3.14
+ end
+
+ it "allows an emacs-style magic comment encoding" do
+ code = <<CODE.b
+# -*- encoding: UTF-8 -*-
+class EvalSpecs
+VÏ€emacs = 3.14
+end
+CODE
+ code.encoding.should == Encoding::BINARY
+ eval(code)
+ EvalSpecs.constants(false).should include(:"VÏ€emacs")
+ EvalSpecs::VÏ€emacs.should == 3.14
+ end
+
+ it "allows spaces before the magic encoding comment" do
+ code = <<CODE.b
+\t \t # encoding: UTF-8
+class EvalSpecs
+ VÏ€spaces = 3.14
+end
+CODE
+ code.encoding.should == Encoding::BINARY
+ eval(code)
+ EvalSpecs.constants(false).should include(:"VÏ€spaces")
+ EvalSpecs::VÏ€spaces.should == 3.14
+ end
+
+ it "allows a shebang line before the magic encoding comment" do
+ code = <<CODE.b
+#!/usr/bin/env ruby
+# encoding: UTF-8
+class EvalSpecs
+ VÏ€shebang = 3.14
+end
+CODE
+ code.encoding.should == Encoding::BINARY
+ eval(code)
+ EvalSpecs.constants(false).should include(:"VÏ€shebang")
+ EvalSpecs::VÏ€shebang.should == 3.14
+ end
+
+ it "allows a shebang line and some spaces before the magic encoding comment" do
+ code = <<CODE.b
+#!/usr/bin/env ruby
+ # encoding: UTF-8
+class EvalSpecs
+ VÏ€shebang_spaces = 3.14
+end
+CODE
+ code.encoding.should == Encoding::BINARY
+ eval(code)
+ EvalSpecs.constants(false).should include(:"VÏ€shebang_spaces")
+ EvalSpecs::VÏ€shebang_spaces.should == 3.14
+ end
+
+ it "allows a magic encoding comment and a subsequent frozen_string_literal magic comment" do
+ # Make sure frozen_string_literal is not default true
+ eval("'foo'".b).frozen?.should be_false
+
+ code = <<CODE.b
+# encoding: UTF-8
+# frozen_string_literal: true
+class EvalSpecs
+ VÏ€string = "frozen"
+end
+CODE
+ code.encoding.should == Encoding::BINARY
+ eval(code)
+ EvalSpecs.constants(false).should include(:"VÏ€string")
+ EvalSpecs::VÏ€string.should == "frozen"
+ EvalSpecs::VÏ€string.encoding.should == Encoding::UTF_8
+ EvalSpecs::VÏ€string.frozen?.should be_true
+ end
+
+ it "allows a magic encoding comment and a frozen_string_literal magic comment on the same line in emacs style" do
+ code = <<CODE.b
+# -*- encoding: UTF-8; frozen_string_literal: true -*-
+class EvalSpecs
+VÏ€same_line = "frozen"
+end
+CODE
+ code.encoding.should == Encoding::BINARY
+ eval(code)
+ EvalSpecs.constants(false).should include(:"VÏ€same_line")
+ EvalSpecs::VÏ€same_line.should == "frozen"
+ EvalSpecs::VÏ€same_line.encoding.should == Encoding::UTF_8
+ EvalSpecs::VÏ€same_line.frozen?.should be_true
+ end
+
+ it "ignores the magic encoding comment if it is after a frozen_string_literal magic comment" do
+ code = <<CODE.b
+# frozen_string_literal: true
+# encoding: UTF-8
+class EvalSpecs
+ VÏ€frozen_first = "frozen"
+end
+CODE
+ code.encoding.should == Encoding::BINARY
+ eval(code)
+ EvalSpecs.constants(false).should_not include(:"VÏ€frozen_first")
+ binary_constant = "VÏ€frozen_first".b.to_sym
+ EvalSpecs.constants(false).should include(binary_constant)
+ value = EvalSpecs.const_get(binary_constant)
+ value.should == "frozen"
+ value.encoding.should == Encoding::BINARY
+ value.frozen?.should be_true
+ end
+
+ it "ignores the frozen_string_literal magic comment if it appears after a token and warns if $VERBOSE is true" do
+ code = <<CODE
+some_token_before_magic_comment = :anything
+# frozen_string_literal: true
+class EvalSpecs
+ VÏ€string_not_frozen = "not frozen"
+end
+CODE
+ -> { eval(code) }.should complain(/warning: `frozen_string_literal' is ignored after any tokens/, verbose: true)
+ EvalSpecs::VÏ€string_not_frozen.frozen?.should be_false
+ EvalSpecs.send :remove_const, :VÏ€string_not_frozen
+
+ -> { eval(code) }.should_not complain(verbose: false)
+ EvalSpecs::VÏ€string_not_frozen.frozen?.should be_false
+ EvalSpecs.send :remove_const, :VÏ€string_not_frozen
+ end
+ end
+
+ describe 'with refinements' do
+ it "activates refinements from the eval scope" do
+ refinery = Module.new do
+ refine EvalSpecs::A do
+ def foo
+ "bar"
+ end
+ end
+ end
+
+ result = nil
+
+ Module.new do
+ using refinery
+
+ result = eval "EvalSpecs::A.new.foo"
+ end
+
+ result.should == "bar"
+ end
+
+ it "activates refinements from the binding" do
+ refinery = Module.new do
+ refine EvalSpecs::A do
+ def foo
+ "bar"
+ end
+ end
+ end
+
+ b = nil
+ m = Module.new do
+ using refinery
+ b = binding
+ end
+
+ result = eval "EvalSpecs::A.new.foo", b
+
+ result.should == "bar"
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/exec_spec.rb b/spec/ruby/core/kernel/exec_spec.rb
new file mode 100644
index 0000000000..1b4a7ae6f4
--- /dev/null
+++ b/spec/ruby/core/kernel/exec_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#exec" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:exec)
+ end
+
+ it "runs the specified command, replacing current process" do
+ ruby_exe('exec "echo hello"; puts "fail"', escape: true).should == "hello\n"
+ end
+end
+
+describe "Kernel.exec" do
+ it "runs the specified command, replacing current process" do
+ ruby_exe('Kernel.exec "echo hello"; puts "fail"', escape: true).should == "hello\n"
+ end
+end
diff --git a/spec/ruby/core/kernel/exit_spec.rb b/spec/ruby/core/kernel/exit_spec.rb
new file mode 100644
index 0000000000..f168cb375e
--- /dev/null
+++ b/spec/ruby/core/kernel/exit_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/process/exit'
+
+describe "Kernel#exit" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:exit)
+ end
+
+ it_behaves_like :process_exit, :exit, KernelSpecs::Method.new
+end
+
+describe "Kernel#exit!" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:exit!)
+ end
+
+ it_behaves_like :process_exit!, :exit!, "self"
+end
+
+describe "Kernel.exit" do
+ it_behaves_like :process_exit, :exit, Kernel
+end
+
+describe "Kernel.exit!" do
+ it_behaves_like :process_exit!, :exit!, Kernel
+end
diff --git a/spec/ruby/core/kernel/extend_spec.rb b/spec/ruby/core/kernel/extend_spec.rb
new file mode 100644
index 0000000000..47b22f3a18
--- /dev/null
+++ b/spec/ruby/core/kernel/extend_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+module KernelSpecs::M
+ def self.extend_object(o)
+ ScratchPad << "extend_object"
+ super
+ end
+
+ def self.extended(o)
+ ScratchPad << "extended"
+ super
+ end
+
+ def self.append_features(o)
+ ScratchPad << "append_features"
+ super
+ end
+end
+
+describe "Kernel#extend" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "requires multiple arguments" do
+ Object.new.method(:extend).arity.should < 0
+ end
+
+ it "calls extend_object on argument" do
+ o = mock('o')
+ o.extend KernelSpecs::M
+ ScratchPad.recorded.include?("extend_object").should == true
+ end
+
+ it "does not calls append_features on arguments metaclass" do
+ o = mock('o')
+ o.extend KernelSpecs::M
+ ScratchPad.recorded.include?("append_features").should == false
+ end
+
+ it "calls extended on argument" do
+ o = mock('o')
+ o.extend KernelSpecs::M
+ ScratchPad.recorded.include?("extended").should == true
+ end
+
+ it "makes the class a kind_of? the argument" do
+ c = Class.new do
+ extend KernelSpecs::M
+ end
+ (c.kind_of? KernelSpecs::M).should == true
+ end
+
+ it "raises an ArgumentError when no arguments given" do
+ -> { Object.new.extend }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError when the argument is not a Module" do
+ o = mock('o')
+ klass = Class.new
+ -> { o.extend(klass) }.should raise_error(TypeError)
+ end
+
+ describe "on frozen instance" do
+ before :each do
+ @frozen = Object.new.freeze
+ @module = KernelSpecs::M
+ end
+
+ it "raises an ArgumentError when no arguments given" do
+ -> { @frozen.extend }.should raise_error(ArgumentError)
+ end
+
+ it "raises a FrozenError" do
+ -> { @frozen.extend @module }.should raise_error(FrozenError)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/fail_spec.rb b/spec/ruby/core/kernel/fail_spec.rb
new file mode 100644
index 0000000000..fab622037e
--- /dev/null
+++ b/spec/ruby/core/kernel/fail_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#fail" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:fail)
+ end
+
+ it "raises a RuntimeError" do
+ -> { fail }.should raise_error(RuntimeError)
+ end
+
+ it "accepts an Object with an exception method returning an Exception" do
+ obj = Object.new
+ def obj.exception(msg)
+ StandardError.new msg
+ end
+ -> { fail obj, "..." }.should raise_error(StandardError, "...")
+ end
+
+ it "instantiates the specified exception class" do
+ error_class = Class.new(RuntimeError)
+ -> { fail error_class }.should raise_error(error_class)
+ end
+
+ it "uses the specified message" do
+ -> {
+ begin
+ fail "the duck is not irish."
+ rescue => e
+ e.message.should == "the duck is not irish."
+ raise
+ else
+ raise Exception
+ end
+ }.should raise_error(RuntimeError)
+ end
+end
+
+describe "Kernel.fail" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/fixtures/Complex.rb b/spec/ruby/core/kernel/fixtures/Complex.rb
new file mode 100644
index 0000000000..bf14d55ad5
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/Complex.rb
@@ -0,0 +1,5 @@
+module KernelSpecs
+ def self.Complex_method(string)
+ Complex(string)
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/__callee__.rb b/spec/ruby/core/kernel/fixtures/__callee__.rb
new file mode 100644
index 0000000000..7138dbc5aa
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/__callee__.rb
@@ -0,0 +1,34 @@
+module KernelSpecs
+ class CalleeTest
+ def f
+ __callee__
+ end
+
+ alias_method :g, :f
+
+ def in_block
+ (1..2).map { __callee__ }
+ end
+
+ define_method(:dm) do
+ __callee__
+ end
+
+ define_method(:dm_block) do
+ (1..2).map { __callee__ }
+ end
+
+ def from_send
+ send "__callee__"
+ end
+
+ def from_eval
+ eval "__callee__"
+ end
+
+ @@method = __callee__
+ def from_class_body
+ @@method
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/__dir__.rb b/spec/ruby/core/kernel/fixtures/__dir__.rb
new file mode 100644
index 0000000000..bf9a15e3c8
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/__dir__.rb
@@ -0,0 +1,2 @@
+puts __FILE__
+puts __dir__
diff --git a/spec/ruby/core/kernel/fixtures/__method__.rb b/spec/ruby/core/kernel/fixtures/__method__.rb
new file mode 100644
index 0000000000..9300366b37
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/__method__.rb
@@ -0,0 +1,34 @@
+module KernelSpecs
+ class MethodTest
+ def f
+ __method__
+ end
+
+ alias_method :g, :f
+
+ def in_block
+ (1..2).map { __method__ }
+ end
+
+ define_method(:dm) do
+ __method__
+ end
+
+ define_method(:dm_block) do
+ (1..2).map { __method__ }
+ end
+
+ def from_send
+ send "__method__"
+ end
+
+ def from_eval
+ eval "__method__"
+ end
+
+ @@method = __method__
+ def from_class_body
+ @@method
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/at_exit.rb b/spec/ruby/core/kernel/fixtures/at_exit.rb
new file mode 100644
index 0000000000..9c11a7ad6c
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/at_exit.rb
@@ -0,0 +1,3 @@
+at_exit do
+ STDERR.puts "at_exit ran"
+end
diff --git a/spec/ruby/core/kernel/fixtures/autoload_b.rb b/spec/ruby/core/kernel/fixtures/autoload_b.rb
new file mode 100644
index 0000000000..e8be221ec7
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/autoload_b.rb
@@ -0,0 +1,5 @@
+module KSAutoloadB
+ def self.loaded
+ :ksautoload_b
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/autoload_d.rb b/spec/ruby/core/kernel/fixtures/autoload_d.rb
new file mode 100644
index 0000000000..552cb5e82c
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/autoload_d.rb
@@ -0,0 +1,5 @@
+module KSAutoloadD
+ def self.loaded
+ :ksautoload_d
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/autoload_from_included_module.rb b/spec/ruby/core/kernel/fixtures/autoload_from_included_module.rb
new file mode 100644
index 0000000000..f5bedc6992
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/autoload_from_included_module.rb
@@ -0,0 +1,9 @@
+module KernelSpecs
+ module AutoloadMethod
+ module AutoloadFromIncludedModule
+ def self.loaded
+ :autoload_from_included_module
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/autoload_from_included_module2.rb b/spec/ruby/core/kernel/fixtures/autoload_from_included_module2.rb
new file mode 100644
index 0000000000..f4f1cfbf7c
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/autoload_from_included_module2.rb
@@ -0,0 +1,9 @@
+module KernelSpecs
+ module AutoloadMethod2
+ module AutoloadFromIncludedModule2
+ def self.loaded
+ :autoload_from_included_module2
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/autoload_frozen.rb b/spec/ruby/core/kernel/fixtures/autoload_frozen.rb
new file mode 100644
index 0000000000..e9dc42912b
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/autoload_frozen.rb
@@ -0,0 +1,7 @@
+Object.freeze
+
+begin
+ autoload :ANY_CONSTANT, "no_autoload.rb"
+rescue Exception => e
+ print e.class, " - ", defined?(ANY_CONSTANT).inspect
+end
diff --git a/spec/ruby/core/kernel/fixtures/caller.rb b/spec/ruby/core/kernel/fixtures/caller.rb
new file mode 100644
index 0000000000..ae3e13e9c9
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/caller.rb
@@ -0,0 +1,7 @@
+module KernelSpecs
+ class CallerTest
+ def self.locations(*args)
+ caller(*args)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/caller_at_exit.rb b/spec/ruby/core/kernel/fixtures/caller_at_exit.rb
new file mode 100644
index 0000000000..ca9d808597
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/caller_at_exit.rb
@@ -0,0 +1,7 @@
+at_exit {
+ foo
+}
+
+def foo
+ puts caller(0)
+end
diff --git a/spec/ruby/core/kernel/fixtures/caller_locations.rb b/spec/ruby/core/kernel/fixtures/caller_locations.rb
new file mode 100644
index 0000000000..cc4e04d38f
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/caller_locations.rb
@@ -0,0 +1,7 @@
+module KernelSpecs
+ class CallerLocationsTest
+ def self.locations(*args)
+ caller_locations(*args)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/chomp.rb b/spec/ruby/core/kernel/fixtures/chomp.rb
new file mode 100644
index 0000000000..f08dbadce5
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/chomp.rb
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+
+$_ = "ã‚れ\r\n"
+print Kernel.chomp
diff --git a/spec/ruby/core/kernel/fixtures/chomp_f.rb b/spec/ruby/core/kernel/fixtures/chomp_f.rb
new file mode 100644
index 0000000000..3c22cb21e7
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/chomp_f.rb
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+
+$_ = "ã‚れ\r\n"
+print chomp
diff --git a/spec/ruby/core/kernel/fixtures/chop.rb b/spec/ruby/core/kernel/fixtures/chop.rb
new file mode 100644
index 0000000000..dfd0626723
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/chop.rb
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+
+$_ = "ã‚れ"
+print Kernel.chop
diff --git a/spec/ruby/core/kernel/fixtures/chop_f.rb b/spec/ruby/core/kernel/fixtures/chop_f.rb
new file mode 100644
index 0000000000..4ec89eb9ec
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/chop_f.rb
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+
+$_ = "ã‚れ"
+print chop
diff --git a/spec/ruby/core/kernel/fixtures/classes.rb b/spec/ruby/core/kernel/fixtures/classes.rb
new file mode 100644
index 0000000000..541a4c075e
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/classes.rb
@@ -0,0 +1,504 @@
+module KernelSpecs
+ def self.Array_function(arg)
+ Array(arg)
+ end
+
+ def self.Array_method(arg)
+ Kernel.Array(arg)
+ end
+
+ def self.Hash_function(arg)
+ Hash(arg)
+ end
+
+ def self.Hash_method(arg)
+ Kernel.Hash(arg)
+ end
+
+ def self.Integer_function(arg)
+ Integer(arg)
+ end
+
+ def self.Integer_method(arg)
+ Kernel.Integer(arg)
+ end
+
+ def self.putc_function(arg)
+ putc arg
+ end
+
+ def self.putc_method(arg)
+ Kernel.putc arg
+ end
+
+ def self.has_private_method(name)
+ IO.popen([*ruby_exe, "-n", "-e", "print Kernel.private_method_defined?(#{name.inspect})"], "r+") do |io|
+ io.puts
+ io.close_write
+ io.read
+ end == "true"
+ end
+
+ def self.chop(str, method)
+ IO.popen([*ruby_exe, "-n", "-e", "$_ = #{str.inspect}; #{method}; print $_"], "r+") do |io|
+ io.puts
+ io.close_write
+ io.read
+ end
+ end
+
+ def self.chomp(str, method, sep="\n")
+ code = "$_ = #{str.inspect}; $/ = #{sep.inspect}; #{method}; print $_"
+ IO.popen([*ruby_exe, "-W0", "-n", "-e", code], "r+") do |io|
+ io.puts
+ io.close_write
+ io.read
+ end
+ end
+
+ def self.run_with_dash_n(file)
+ IO.popen([*ruby_exe, "-n", file], "r+") do |io|
+ io.puts
+ io.close_write
+ io.read
+ end
+ end
+
+ # kind_of?, is_a?, instance_of?
+ module SomeOtherModule; end
+ module AncestorModule; end
+ module MyModule; end
+ module MyPrependedModule; end
+ module MyExtensionModule; end
+
+ class AncestorClass < String
+ include AncestorModule
+ end
+
+ class InstanceClass < AncestorClass
+ include MyModule
+ end
+
+ class KindaClass < AncestorClass
+ include MyModule
+ prepend MyPrependedModule
+
+ def initialize
+ self.extend MyExtensionModule
+ end
+ end
+
+ class Method
+ public :abort, :exit, :exit!, :fork, :system
+ end
+
+ class Methods
+
+ module MetaclassMethods
+ def peekaboo
+ end
+
+ protected
+
+ def nopeeking
+ end
+
+ private
+
+ def shoo
+ end
+ end
+
+ def self.ichi; end
+ def ni; end
+ class << self
+ def san; end
+ end
+
+ private
+
+ def self.shi; end
+ def juu_shi; end
+
+ class << self
+ def roku; end
+
+ private
+
+ def shichi; end
+ end
+
+ protected
+
+ def self.hachi; end
+ def ku; end
+
+ class << self
+ def juu; end
+
+ protected
+
+ def juu_ichi; end
+ end
+
+ public
+
+ def self.juu_ni; end
+ def juu_san; end
+ end
+
+ class PrivateSup
+ def public_in_sub
+ end
+
+ private :public_in_sub
+ end
+
+ class PublicSub < PrivateSup
+ def public_in_sub
+ end
+ end
+
+ class A
+ # There is Kernel#public_method, so we don't want this one to clash
+ def pub_method; :public_method; end
+
+ def undefed_method; :undefed_method; end
+ undef_method :undefed_method
+
+ protected
+ def protected_method; :protected_method; end
+
+ private
+ def private_method; :private_method; end
+
+ public
+ define_method(:defined_method) { :defined }
+ end
+
+ class B < A
+ alias aliased_pub_method pub_method
+ end
+
+ class VisibilityChange
+ class << self
+ private :new
+ end
+ end
+
+ class Binding
+ @@super_secret = "password"
+
+ def initialize(n)
+ @secret = n
+ end
+
+ def square(n)
+ n * n
+ end
+
+ def get_binding
+ a = true
+ @bind = binding
+
+ # Add/Change stuff
+ b = true
+ @secret += 1
+
+ @bind
+ end
+ end
+
+
+ module BlockGiven
+ def self.accept_block
+ block_given?
+ end
+
+ def self.accept_block_as_argument(&block)
+ block_given?
+ end
+
+ class << self
+ define_method(:defined_block) do
+ block_given?
+ end
+ end
+ end
+
+ module SelfBlockGiven
+ def self.accept_block
+ self.send(:block_given?)
+ end
+
+ def self.accept_block_as_argument(&block)
+ self.send(:block_given?)
+ end
+
+ class << self
+ define_method(:defined_block) do
+ self.send(:block_given?)
+ end
+ end
+ end
+
+ module KernelBlockGiven
+ def self.accept_block
+ Kernel.block_given?
+ end
+
+ def self.accept_block_as_argument(&block)
+ Kernel.block_given?
+ end
+
+ class << self
+ define_method(:defined_block) do
+ Kernel.block_given?
+ end
+ end
+ end
+
+ class EvalTest
+ def self.eval_yield_with_binding
+ eval("yield", binding)
+ end
+ def self.call_yield
+ yield
+ end
+ end
+
+ module DuplicateM
+ def repr
+ self.class.name.to_s
+ end
+ end
+
+ class Duplicate
+ attr_accessor :one, :two
+
+ def initialize(one, two)
+ @one = one
+ @two = two
+ end
+
+ def initialize_copy(other, **kw)
+ ScratchPad.record object_id
+ end
+
+ # define to support calling #clone with optional :freeze keyword argument
+ def initialize_clone(other, **kw)
+ super(other) # to call #initialize_copy
+ end
+ end
+
+ class Clone
+ def initialize_clone(other)
+ ScratchPad.record other
+ end
+ end
+
+ class CloneFreeze
+ def initialize_clone(other, **kwargs)
+ ScratchPad.record([other, kwargs])
+ end
+ end
+
+ class Dup
+ def initialize_dup(other)
+ ScratchPad.record other.object_id
+ end
+ end
+
+ module ParentMixin
+ def parent_mixin_method; end
+ end
+
+ class Parent
+ include ParentMixin
+ def parent_method; end
+ def another_parent_method; end
+ def self.parent_class_method; :foo; end
+ end
+
+ class Child < Parent
+ undef_method :parent_method
+ end
+
+ class Grandchild < Child
+ undef_method :parent_mixin_method
+ end
+
+ # for testing lambda
+ class Lambda
+ def outer
+ inner
+ end
+
+ def mp(&b); b; end
+
+ def inner
+ b = mp { return :good }
+
+ pr = -> x { x.call }
+
+ pr.call(b)
+
+ # We shouldn't be here, b should have unwinded through
+ return :bad
+ end
+ end
+
+ module LambdaSpecs
+ module ZSuper
+ def lambda
+ super
+ end
+ end
+
+ class ForwardBlockWithZSuper
+ prepend(ZSuper)
+ end
+
+ module Ampersand
+ def lambda(&block)
+ suppress_warning {super(&block)}
+ end
+ end
+
+ class SuperAmpersand
+ prepend(Ampersand)
+ end
+ end
+
+ class RespondViaMissing
+ def respond_to_missing?(method, priv=false)
+ case method
+ when :handled_publicly
+ true
+ when :handled_privately
+ priv
+ when :not_handled
+ false
+ else
+ raise "Typo in method name: #{method.inspect}"
+ end
+ end
+
+ def method_missing(method, *args)
+ raise "the method name should be a Symbol" unless Symbol === method
+ "Done #{method}(#{args})"
+ end
+ end
+
+ class InstanceVariable
+ def initialize
+ @greeting = "hello"
+ end
+ end
+
+ class PrivateToAry
+ private
+
+ def to_ary
+ [1, 2]
+ end
+
+ def to_a
+ [3, 4]
+ end
+ end
+
+ class PrivateToA
+ private
+
+ def to_a
+ [3, 4]
+ end
+ end
+
+ module AutoloadMethod
+ def setup_autoload(file)
+ autoload :AutoloadFromIncludedModule, file
+ end
+ end
+
+ class AutoloadMethodIncluder
+ include AutoloadMethod
+ end
+
+ module AutoloadMethod2
+ def setup_autoload(file)
+ Kernel.autoload :AutoloadFromIncludedModule2, file
+ end
+ end
+
+ class AutoloadMethodIncluder2
+ include AutoloadMethod2
+ end
+
+ class WarnInNestedCall
+ def f4(s = "", n)
+ f3(s, n)
+ end
+
+ def f3(s, n)
+ f2(s, n)
+ end
+
+ def f2(s, n)
+ f1(s, n)
+ end
+
+ def f1(s, n)
+ warn(s, uplevel: n)
+ end
+
+ def warn_call_lineno; method(:f1).source_location[1] + 1; end
+ def f1_call_lineno; method(:f2).source_location[1] + 1; end
+ def f2_call_lineno; method(:f3).source_location[1] + 1; end
+ def f3_call_lineno; method(:f4).source_location[1] + 1; end
+ end
+
+ CustomRangeInteger = Struct.new(:value) do
+ def to_int; value; end
+ def <=>(other); to_int <=> other.to_int; end
+ def -(other); self.class.new(to_int - other.to_int); end
+ def +(other); self.class.new(to_int + other.to_int); end
+ end
+
+ CustomRangeFloat = Struct.new(:value) do
+ def to_f; value; end
+ def <=>(other); to_f <=> other.to_f; end
+ def -(other); to_f - other.to_f; end
+ def +(other); self.class.new(to_f + other.to_f); end
+ end
+end
+
+class EvalSpecs
+ class A
+ eval "class B; end"
+ def c
+ eval "class C; end"
+ end
+ end
+
+ class CoercedObject
+ def to_str
+ '2 + 3'
+ end
+
+ def hash
+ nil
+ end
+ end
+
+ def f
+ yield
+ end
+
+ def self.call_eval
+ f = __FILE__
+ eval "true", binding, "(eval)", 1
+ return f
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/eval_locals.rb b/spec/ruby/core/kernel/fixtures/eval_locals.rb
new file mode 100644
index 0000000000..ca8b381806
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/eval_locals.rb
@@ -0,0 +1,6 @@
+begin
+ eval("a = 2")
+ eval("p a")
+rescue Object => e
+ puts e.class
+end
diff --git a/spec/ruby/core/kernel/fixtures/eval_return_with_lambda.rb b/spec/ruby/core/kernel/fixtures/eval_return_with_lambda.rb
new file mode 100644
index 0000000000..9e2d045bf3
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/eval_return_with_lambda.rb
@@ -0,0 +1,12 @@
+print "a,"
+x = -> do
+ print "b,"
+ Proc.new do
+ print "c,"
+ eval("return :eval")
+ print "d,"
+ end.call
+ print "e,"
+end.call
+print x, ","
+print "f"
diff --git a/spec/ruby/core/kernel/fixtures/eval_return_without_lambda.rb b/spec/ruby/core/kernel/fixtures/eval_return_without_lambda.rb
new file mode 100644
index 0000000000..fc8b7f1d4a
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/eval_return_without_lambda.rb
@@ -0,0 +1,14 @@
+print "a,"
+begin
+ print "b,"
+ x = Proc.new do
+ print "c,"
+ eval("return :eval")
+ print "d,"
+ end.call
+ print x, ","
+rescue LocalJumpError => e
+ print "e,"
+ print e.class, ","
+end
+print "f"
diff --git a/spec/ruby/core/kernel/fixtures/singleton_methods.rb b/spec/ruby/core/kernel/fixtures/singleton_methods.rb
new file mode 100644
index 0000000000..32ea0adf89
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/singleton_methods.rb
@@ -0,0 +1,13 @@
+module SingletonMethodsSpecs
+ module Prepended
+ def mspec_test_kernel_singleton_methods
+ end
+ public :mspec_test_kernel_singleton_methods
+ end
+
+ ::Module.prepend Prepended
+
+ module SelfExtending
+ extend self
+ end
+end
diff --git a/spec/ruby/core/kernel/fixtures/test.rb b/spec/ruby/core/kernel/fixtures/test.rb
new file mode 100644
index 0000000000..949948606f
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/test.rb
@@ -0,0 +1,362 @@
+def foo1
+end
+
+def foo2
+end
+
+def foo3
+end
+
+def foo4
+end
+
+def foo5
+end
+
+def foo6
+end
+
+def foo7
+end
+
+def foo8
+end
+
+def foo9
+end
+
+def foo10
+end
+
+def foo11
+end
+
+def foo12
+end
+
+def foo13
+end
+
+def foo14
+end
+
+def foo15
+end
+
+def foo16
+end
+
+def foo17
+end
+
+def foo18
+end
+
+def foo19
+end
+
+def foo20
+end
+
+def foo21
+end
+
+def foo22
+end
+
+def foo23
+end
+
+def foo24
+end
+
+def foo25
+end
+
+def foo26
+end
+
+def foo27
+end
+
+def foo28
+end
+
+def foo29
+end
+
+def foo30
+end
+
+def foo31
+end
+
+def foo32
+end
+
+def foo33
+end
+
+def foo34
+end
+
+def foo35
+end
+
+def foo36
+end
+
+def foo37
+end
+
+def foo38
+end
+
+def foo39
+end
+
+def foo40
+end
+
+def foo41
+end
+
+def foo42
+end
+
+def foo43
+end
+
+def foo44
+end
+
+def foo45
+end
+
+def foo46
+end
+
+def foo47
+end
+
+def foo48
+end
+
+def foo49
+end
+
+def foo50
+end
+
+def foo51
+end
+
+def foo52
+end
+
+def foo53
+end
+
+def foo54
+end
+
+def foo55
+end
+
+def foo56
+end
+
+def foo57
+end
+
+def foo58
+end
+
+def foo59
+end
+
+def foo60
+end
+
+def foo61
+end
+
+def foo62
+end
+
+def foo63
+end
+
+def foo64
+end
+
+def foo65
+end
+
+def foo66
+end
+
+def foo67
+end
+
+def foo68
+end
+
+def foo69
+end
+
+def foo70
+end
+
+def foo71
+end
+
+def foo72
+end
+
+def foo73
+end
+
+def foo74
+end
+
+def foo75
+end
+
+def foo76
+end
+
+def foo77
+end
+
+def foo78
+end
+
+def foo79
+end
+
+def foo80
+end
+
+def foo81
+end
+
+def foo82
+end
+
+def foo83
+end
+
+def foo84
+end
+
+def foo85
+end
+
+def foo86
+end
+
+def foo87
+end
+
+def foo88
+end
+
+def foo89
+end
+
+def foo90
+end
+
+def foo91
+end
+
+def foo92
+end
+
+def foo93
+end
+
+def foo94
+end
+
+def foo95
+end
+
+def foo96
+end
+
+def foo97
+end
+
+def foo98
+end
+
+def foo99
+end
+
+def foo100
+end
+
+def foo101
+end
+
+def foo102
+end
+
+def foo103
+end
+
+def foo104
+end
+
+def foo105
+end
+
+def foo106
+end
+
+def foo107
+end
+
+def foo108
+end
+
+def foo109
+end
+
+def foo110
+end
+
+def foo111
+end
+
+def foo112
+end
+
+def foo113
+end
+
+def foo114
+end
+
+def foo115
+end
+
+def foo116
+end
+
+def foo117
+end
+
+def foo118
+end
+
+def foo119
+end
+
+def foo120
+end
+
+def foo121
+end
diff --git a/spec/ruby/core/kernel/fixtures/warn_core_method.rb b/spec/ruby/core/kernel/fixtures/warn_core_method.rb
new file mode 100644
index 0000000000..fd82562404
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/warn_core_method.rb
@@ -0,0 +1,14 @@
+raise 'should be run without RubyGems' if defined?(Gem)
+
+public def deprecated(n=1)
+ # puts nil, caller(0), nil
+ warn "use X instead", uplevel: n
+end
+
+1.times do # to test with a non-empty stack above the reported locations
+ deprecated
+ tap(&:deprecated)
+ tap { deprecated(2) }
+ # eval sources with a <internal: file are also ignored
+ eval "tap(&:deprecated)", nil, "<internal:should-be-skipped-by-warn-uplevel>"
+end
diff --git a/spec/ruby/core/kernel/fixtures/warn_require.rb b/spec/ruby/core/kernel/fixtures/warn_require.rb
new file mode 100644
index 0000000000..c4b0733233
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/warn_require.rb
@@ -0,0 +1 @@
+warn 'warn-require-warning', uplevel: 1
diff --git a/spec/ruby/core/kernel/fixtures/warn_require_caller.rb b/spec/ruby/core/kernel/fixtures/warn_require_caller.rb
new file mode 100644
index 0000000000..35a0f969f9
--- /dev/null
+++ b/spec/ruby/core/kernel/fixtures/warn_require_caller.rb
@@ -0,0 +1,2 @@
+# Use a different line than just 1
+require "#{__dir__}/warn_require"
diff --git a/spec/ruby/core/kernel/fork_spec.rb b/spec/ruby/core/kernel/fork_spec.rb
new file mode 100644
index 0000000000..b37f9980e0
--- /dev/null
+++ b/spec/ruby/core/kernel/fork_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/process/fork'
+
+describe "Kernel#fork" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:fork)
+ end
+
+ it_behaves_like :process_fork, :fork, KernelSpecs::Method.new
+end
+
+describe "Kernel.fork" do
+ it_behaves_like :process_fork, :fork, Kernel
+end
diff --git a/spec/ruby/core/kernel/format_spec.rb b/spec/ruby/core/kernel/format_spec.rb
new file mode 100644
index 0000000000..e8b031e480
--- /dev/null
+++ b/spec/ruby/core/kernel/format_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# NOTE: most specs are in sprintf_spec.rb, this is just an alias
+describe "Kernel#format" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:format)
+ end
+end
+
+describe "Kernel.format" do
+ it "is accessible as a module function" do
+ Kernel.format("%s", "hello").should == "hello"
+ end
+end
diff --git a/spec/ruby/core/kernel/freeze_spec.rb b/spec/ruby/core/kernel/freeze_spec.rb
new file mode 100644
index 0000000000..fa32d321cf
--- /dev/null
+++ b/spec/ruby/core/kernel/freeze_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#freeze" do
+ it "prevents self from being further modified" do
+ o = mock('o')
+ o.frozen?.should be_false
+ o.freeze
+ o.frozen?.should be_true
+ end
+
+ it "returns self" do
+ o = Object.new
+ o.freeze.should equal(o)
+ end
+
+ describe "on integers" do
+ it "has no effect since they are already frozen" do
+ 1.frozen?.should be_true
+ 1.freeze
+
+ bignum = bignum_value
+ bignum.frozen?.should be_true
+ bignum.freeze
+ end
+ end
+
+ describe "on a Float" do
+ it "has no effect since it is already frozen" do
+ 1.2.frozen?.should be_true
+ 1.2.freeze
+ end
+ end
+
+ describe "on a Symbol" do
+ it "has no effect since it is already frozen" do
+ :sym.frozen?.should be_true
+ :sym.freeze
+ end
+ end
+
+ describe "on true, false and nil" do
+ it "has no effect since they are already frozen" do
+ nil.frozen?.should be_true
+ true.frozen?.should be_true
+ false.frozen?.should be_true
+
+ nil.freeze
+ true.freeze
+ false.freeze
+ end
+ end
+
+ describe "on a Complex" do
+ it "has no effect since it is already frozen" do
+ c = Complex(1.3, 3.1)
+ c.frozen?.should be_true
+ c.freeze
+ end
+ end
+
+ describe "on a Rational" do
+ it "has no effect since it is already frozen" do
+ r = Rational(1, 3)
+ r.frozen?.should be_true
+ r.freeze
+ end
+ end
+
+ it "causes mutative calls to raise RuntimeError" do
+ o = Class.new do
+ def mutate; @foo = 1; end
+ end.new
+ o.freeze
+ -> {o.mutate}.should raise_error(RuntimeError)
+ end
+
+ it "causes instance_variable_set to raise RuntimeError" do
+ o = Object.new
+ o.freeze
+ -> {o.instance_variable_set(:@foo, 1)}.should raise_error(RuntimeError)
+ end
+
+ it "freezes an object's singleton class" do
+ o = Object.new
+ c = o.singleton_class
+ c.frozen?.should == false
+ o.freeze
+ c.frozen?.should == true
+ end
+end
diff --git a/spec/ruby/core/kernel/frozen_spec.rb b/spec/ruby/core/kernel/frozen_spec.rb
new file mode 100644
index 0000000000..a4cec4263d
--- /dev/null
+++ b/spec/ruby/core/kernel/frozen_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#frozen?" do
+ it "returns true if self is frozen" do
+ o = mock('o')
+ p = mock('p')
+ p.freeze
+ o.should_not.frozen?
+ p.should.frozen?
+ end
+
+ describe "on true, false and nil" do
+ it "returns true" do
+ true.frozen?.should be_true
+ false.frozen?.should be_true
+ nil.frozen?.should be_true
+ end
+ end
+
+ describe "on integers" do
+ before :each do
+ @fixnum = 1
+ @bignum = bignum_value
+ end
+
+ it "returns true" do
+ @fixnum.frozen?.should be_true
+ @bignum.frozen?.should be_true
+ end
+ end
+
+ describe "on a Float" do
+ before :each do
+ @float = 0.1
+ end
+
+ it "returns true" do
+ @float.frozen?.should be_true
+ end
+ end
+
+ describe "on a Symbol" do
+ before :each do
+ @symbol = :symbol
+ end
+
+ it "returns true" do
+ @symbol.frozen?.should be_true
+ end
+ end
+
+ describe "on a Complex" do
+ it "returns true" do
+ c = Complex(1.3, 3.1)
+ c.frozen?.should be_true
+ end
+
+ it "literal returns true" do
+ c = eval "1.3i"
+ c.frozen?.should be_true
+ end
+ end
+
+ describe "on a Rational" do
+ it "returns true" do
+ r = Rational(1, 3)
+ r.frozen?.should be_true
+ end
+
+ it "literal returns true" do
+ r = eval "1/3r"
+ r.frozen?.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/gets_spec.rb b/spec/ruby/core/kernel/gets_spec.rb
new file mode 100644
index 0000000000..104613dbfa
--- /dev/null
+++ b/spec/ruby/core/kernel/gets_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#gets" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:gets)
+ end
+
+ it "calls ARGF.gets" do
+ ARGF.should_receive(:gets).and_return("spec")
+ gets.should == "spec"
+ end
+end
+
+describe "Kernel.gets" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/global_variables_spec.rb b/spec/ruby/core/kernel/global_variables_spec.rb
new file mode 100644
index 0000000000..8bce8e25b7
--- /dev/null
+++ b/spec/ruby/core/kernel/global_variables_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel.global_variables" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:global_variables)
+ end
+
+ before :all do
+ @i = 0
+ end
+
+ it "finds subset starting with std" do
+ global_variables.grep(/std/).should include(:$stderr, :$stdin, :$stdout)
+ a = global_variables.size
+ gvar_name = "$foolish_global_var#{@i += 1}"
+ global_variables.include?(gvar_name.to_sym).should == false
+ eval("#{gvar_name} = 1")
+ global_variables.size.should == a+1
+ global_variables.should include(gvar_name.to_sym)
+ end
+end
+
+describe "Kernel#global_variables" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/gsub_spec.rb b/spec/ruby/core/kernel/gsub_spec.rb
new file mode 100644
index 0000000000..a0cb9f2a70
--- /dev/null
+++ b/spec/ruby/core/kernel/gsub_spec.rb
@@ -0,0 +1,96 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# FIXME: These methods exist only when the -n or -p option is passed to
+# ruby, but we currently don't have a way of specifying that.
+ruby_version_is ""..."1.9" do
+ describe "Kernel#gsub" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:gsub)
+ end
+
+ it "raises a TypeError if $_ is not a String" do
+ -> {
+ $_ = 123
+ gsub(/./, "!")
+ }.should raise_error(TypeError)
+ end
+
+ it "when matches sets $_ to a new string, leaving the former value unaltered" do
+ orig_value = $_ = "hello"
+ gsub("ello", "ola")
+ $_.should_not equal(orig_value)
+ $_.should == "hola"
+ orig_value.should == "hello"
+ end
+
+ it "returns a string with the same contents as $_ after the operation" do
+ $_ = "bye"
+ gsub("non-match", "?").should == "bye"
+
+ orig_value = $_ = "bye"
+ gsub(/$/, "!").should == "bye!"
+ orig_value.should == "bye"
+ end
+
+ it "accepts Regexps as patterns" do
+ $_ = "food"
+ gsub(/.$/, "l")
+ $_.should == "fool"
+ end
+
+ it "accepts Strings as patterns, treated literally" do
+ $_ = "hello, world."
+ gsub(".", "!")
+ $_.should == "hello, world!"
+ end
+
+ it "accepts objects which respond to #to_str as patterns and treats them as strings" do
+ $_ = "hello, world."
+ stringlike = mock(".")
+ stringlike.should_receive(:to_str).and_return(".")
+ gsub(stringlike, "!")
+ $_.should == "hello, world!"
+ end
+ end
+
+ describe "Kernel#gsub with a pattern and replacement" do
+ it "accepts strings for replacement" do
+ $_ = "hello"
+ gsub(/./, ".")
+ $_.should == "....."
+ end
+
+ it "accepts objects which respond to #to_str for replacement" do
+ o = mock("o")
+ o.should_receive(:to_str).and_return("o")
+ $_ = "ping"
+ gsub("i", o)
+ $_.should == "pong"
+ end
+
+ it "replaces \\1 sequences with the regexp's corresponding capture" do
+ $_ = "hello!"
+ gsub(/(.)(.)/, '\2\1')
+ $_.should == "ehll!o"
+ end
+ end
+
+ describe "Kernel#gsub with pattern and block" do
+ it "acts similarly to using $_.gsub" do
+ $_ = "olleh dlrow"
+ gsub(/(\w+)/){ $1.reverse }
+ $_.should == "hello world"
+ end
+ end
+
+ describe "Kernel#gsub!" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:gsub!)
+ end
+ end
+
+ describe "Kernel.gsub!" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/core/kernel/initialize_clone_spec.rb b/spec/ruby/core/kernel/initialize_clone_spec.rb
new file mode 100644
index 0000000000..2d889f5aad
--- /dev/null
+++ b/spec/ruby/core/kernel/initialize_clone_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#initialize_clone" do
+ it "is a private instance method" do
+ Kernel.should have_private_instance_method(:initialize_clone)
+ end
+
+ it "returns the receiver" do
+ a = Object.new
+ b = Object.new
+ a.send(:initialize_clone, b).should == a
+ end
+
+ it "calls #initialize_copy" do
+ a = Object.new
+ b = Object.new
+ a.should_receive(:initialize_copy).with(b)
+ a.send(:initialize_clone, b)
+ end
+
+ ruby_version_is "3.0" do
+ it "accepts a :freeze keyword argument for obj.clone(freeze: value)" do
+ a = Object.new
+ b = Object.new
+ a.send(:initialize_clone, b, freeze: true).should == a
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/initialize_copy_spec.rb b/spec/ruby/core/kernel/initialize_copy_spec.rb
new file mode 100644
index 0000000000..fe08d184ad
--- /dev/null
+++ b/spec/ruby/core/kernel/initialize_copy_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#initialize_copy" do
+ it "does nothing if the argument is the same as the receiver" do
+ obj = Object.new
+ obj.send(:initialize_copy, obj).should.equal?(obj)
+ obj.freeze
+ obj.send(:initialize_copy, obj).should.equal?(obj)
+ 1.send(:initialize_copy, 1).should.equal?(1)
+ end
+
+ it "raises FrozenError if the receiver is frozen" do
+ -> { Object.new.freeze.send(:initialize_copy, Object.new) }.should raise_error(FrozenError)
+ -> { 1.send(:initialize_copy, Object.new) }.should raise_error(FrozenError)
+ end
+
+ it "raises TypeError if the objects are of different class" do
+ klass = Class.new
+ sub = Class.new(klass)
+ a = klass.new
+ b = sub.new
+ message = 'initialize_copy should take same class object'
+ -> { a.send(:initialize_copy, b) }.should raise_error(TypeError, message)
+ -> { b.send(:initialize_copy, a) }.should raise_error(TypeError, message)
+
+ -> { a.send(:initialize_copy, 1) }.should raise_error(TypeError, message)
+ -> { a.send(:initialize_copy, 1.0) }.should raise_error(TypeError, message)
+ end
+end
diff --git a/spec/ruby/core/kernel/initialize_dup_spec.rb b/spec/ruby/core/kernel/initialize_dup_spec.rb
new file mode 100644
index 0000000000..6dff34b7ad
--- /dev/null
+++ b/spec/ruby/core/kernel/initialize_dup_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#initialize_dup" do
+ it "is a private instance method" do
+ Kernel.should have_private_instance_method(:initialize_dup)
+ end
+
+ it "returns the receiver" do
+ a = Object.new
+ b = Object.new
+ a.send(:initialize_dup, b).should == a
+ end
+
+ it "calls #initialize_copy" do
+ a = Object.new
+ b = Object.new
+ a.should_receive(:initialize_copy).with(b)
+ a.send(:initialize_dup, b)
+ end
+end
diff --git a/spec/ruby/core/kernel/inspect_spec.rb b/spec/ruby/core/kernel/inspect_spec.rb
new file mode 100644
index 0000000000..1f9ce834ab
--- /dev/null
+++ b/spec/ruby/core/kernel/inspect_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#inspect" do
+ it "returns a String" do
+ Object.new.inspect.should be_an_instance_of(String)
+ end
+
+ it "does not call #to_s if it is defined" do
+ # We must use a bare Object here
+ obj = Object.new
+ inspected = obj.inspect
+
+ obj.stub!(:to_s).and_return("to_s'd")
+
+ obj.inspect.should == inspected
+ end
+
+ it "returns a String with the object class and object_id encoded" do
+ obj = Object.new
+ obj.inspect.should =~ /^#<Object:0x[0-9a-f]+>$/
+ end
+
+ it "returns a String for an object without #class method" do
+ obj = Object.new
+ class << obj
+ undef_method :class
+ end
+ obj.inspect.should be_kind_of(String)
+ end
+end
diff --git a/spec/ruby/core/kernel/instance_of_spec.rb b/spec/ruby/core/kernel/instance_of_spec.rb
new file mode 100644
index 0000000000..d1170d5047
--- /dev/null
+++ b/spec/ruby/core/kernel/instance_of_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#instance_of?" do
+ before :each do
+ @o = KernelSpecs::InstanceClass.new
+ end
+
+ it "returns true if given class is object's class" do
+ @o.instance_of?(KernelSpecs::InstanceClass).should == true
+ [].instance_of?(Array).should == true
+ ''.instance_of?(String).should == true
+ end
+
+ it "returns false if given class is object's ancestor class" do
+ @o.instance_of?(KernelSpecs::AncestorClass).should == false
+ end
+
+ it "returns false if given class is not object's class nor object's ancestor class" do
+ @o.instance_of?(Array).should == false
+ end
+
+ it "returns false if given a Module that is included in object's class" do
+ @o.instance_of?(KernelSpecs::MyModule).should == false
+ end
+
+ it "returns false if given a Module that is included one of object's ancestors only" do
+ @o.instance_of?(KernelSpecs::AncestorModule).should == false
+ end
+
+ it "returns false if given a Module that is not included in object's class" do
+ @o.instance_of?(KernelSpecs::SomeOtherModule).should == false
+ end
+
+ it "raises a TypeError if given an object that is not a Class nor a Module" do
+ -> { @o.instance_of?(Object.new) }.should raise_error(TypeError)
+ -> { @o.instance_of?('KernelSpecs::InstanceClass') }.should raise_error(TypeError)
+ -> { @o.instance_of?(1) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/kernel/instance_variable_defined_spec.rb b/spec/ruby/core/kernel/instance_variable_defined_spec.rb
new file mode 100644
index 0000000000..2ebb582b43
--- /dev/null
+++ b/spec/ruby/core/kernel/instance_variable_defined_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#instance_variable_defined?" do
+ before do
+ @instance = KernelSpecs::InstanceVariable.new
+ end
+
+ describe "when passed a String" do
+ it "returns false if the instance variable is not defined" do
+ @instance.instance_variable_defined?("@goodbye").should be_false
+ end
+
+ it "returns true if the instance variable is defined" do
+ @instance.instance_variable_defined?("@greeting").should be_true
+ end
+ end
+
+ describe "when passed a Symbol" do
+ it "returns false if the instance variable is not defined" do
+ @instance.instance_variable_defined?(:@goodbye).should be_false
+ end
+
+ it "returns true if the instance variable is defined" do
+ @instance.instance_variable_defined?(:@greeting).should be_true
+ end
+ end
+
+ it "raises a TypeError if passed an Object not defining #to_str" do
+ -> do
+ obj = mock("kernel instance_variable_defined?")
+ @instance.instance_variable_defined? obj
+ end.should raise_error(TypeError)
+ end
+
+ it "returns false if the instance variable is not defined for different types" do
+ [nil, false, true, 1, 2.0, :test, "test"].each do |obj|
+ obj.instance_variable_defined?("@goodbye").should be_false
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/instance_variable_get_spec.rb b/spec/ruby/core/kernel/instance_variable_get_spec.rb
new file mode 100644
index 0000000000..f1d2a45df8
--- /dev/null
+++ b/spec/ruby/core/kernel/instance_variable_get_spec.rb
@@ -0,0 +1,111 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#instance_variable_get" do
+ before :each do
+ @obj = Object.new
+ @obj.instance_variable_set("@test", :test)
+ end
+
+ it "tries to convert the passed argument to a String using #to_str" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return("@test")
+ @obj.instance_variable_get(obj)
+ end
+
+ it "returns the value of the passed instance variable that is referred to by the conversion result" do
+ obj = mock("to_str")
+ obj.stub!(:to_str).and_return("@test")
+ @obj.instance_variable_get(obj).should == :test
+ end
+
+ it "returns nil when the referred instance variable does not exist" do
+ @obj.instance_variable_get(:@does_not_exist).should be_nil
+ end
+
+ it "raises a TypeError when the passed argument does not respond to #to_str" do
+ -> { @obj.instance_variable_get(Object.new) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the passed argument can't be converted to a String" do
+ obj = mock("to_str")
+ obj.stub!(:to_str).and_return(123)
+ -> { @obj.instance_variable_get(obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a NameError when the conversion result does not start with an '@'" do
+ obj = mock("to_str")
+ obj.stub!(:to_str).and_return("test")
+ -> { @obj.instance_variable_get(obj) }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when passed just '@'" do
+ obj = mock("to_str")
+ obj.stub!(:to_str).and_return('@')
+ -> { @obj.instance_variable_get(obj) }.should raise_error(NameError)
+ end
+end
+
+describe "Kernel#instance_variable_get when passed Symbol" do
+ before :each do
+ @obj = Object.new
+ @obj.instance_variable_set("@test", :test)
+ end
+
+ it "returns the value of the instance variable that is referred to by the passed Symbol" do
+ @obj.instance_variable_get(:@test).should == :test
+ end
+
+ it "raises a NameError when passed :@ as an instance variable name" do
+ -> { @obj.instance_variable_get(:"@") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when the passed Symbol does not start with an '@'" do
+ -> { @obj.instance_variable_get(:test) }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when the passed Symbol is an invalid instance variable name" do
+ -> { @obj.instance_variable_get(:"@0") }.should raise_error(NameError)
+ end
+
+ it "returns nil or raises for frozen objects" do
+ nil.instance_variable_get(:@foo).should == nil
+ -> { nil.instance_variable_get(:foo) }.should raise_error(NameError)
+ :foo.instance_variable_get(:@foo).should == nil
+ end
+end
+
+describe "Kernel#instance_variable_get when passed String" do
+ before :each do
+ @obj = Object.new
+ @obj.instance_variable_set("@test", :test)
+ end
+
+ it "returns the value of the instance variable that is referred to by the passed String" do
+ @obj.instance_variable_get("@test").should == :test
+ end
+
+ it "raises a NameError when the passed String does not start with an '@'" do
+ -> { @obj.instance_variable_get("test") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when the passed String is an invalid instance variable name" do
+ -> { @obj.instance_variable_get("@0") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when passed '@' as an instance variable name" do
+ -> { @obj.instance_variable_get("@") }.should raise_error(NameError)
+ end
+end
+
+describe "Kernel#instance_variable_get when passed Integer" do
+ before :each do
+ @obj = Object.new
+ @obj.instance_variable_set("@test", :test)
+ end
+
+ it "raises a TypeError" do
+ -> { @obj.instance_variable_get(10) }.should raise_error(TypeError)
+ -> { @obj.instance_variable_get(-10) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/kernel/instance_variable_set_spec.rb b/spec/ruby/core/kernel/instance_variable_set_spec.rb
new file mode 100644
index 0000000000..2c25f4366f
--- /dev/null
+++ b/spec/ruby/core/kernel/instance_variable_set_spec.rb
@@ -0,0 +1,105 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#instance_variable_set" do
+ it "sets the value of the specified instance variable" do
+ dog = Class.new do
+ def initialize(p1, p2)
+ @a, @b = p1, p2
+ end
+ end
+ dog.new('cat', 99).instance_variable_set(:@a, 'dog').should == "dog"
+ end
+
+ it "sets the value of the instance variable when no instance variables exist yet" do
+ no_variables = Class.new
+ no_variables.new.instance_variable_set(:@a, "new").should == "new"
+ end
+
+ it "raises a NameError exception if the argument is not of form '@x'" do
+ no_dog = Class.new
+ -> { no_dog.new.instance_variable_set(:c, "cat") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError exception if the argument is an invalid instance variable name" do
+ digit_dog = Class.new
+ -> { digit_dog.new.instance_variable_set(:"@0", "cat") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when the argument is '@'" do
+ dog_at = Class.new
+ -> { dog_at.new.instance_variable_set(:"@", "cat") }.should raise_error(NameError)
+ end
+
+ it "raises a TypeError if the instance variable name is an Integer" do
+ -> { "".instance_variable_set(1, 2) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the instance variable name is an object that does not respond to to_str" do
+ class KernelSpecs::A; end
+ -> { "".instance_variable_set(KernelSpecs::A.new, 3) }.should raise_error(TypeError)
+ end
+
+ it "raises a NameError if the passed object, when coerced with to_str, does not start with @" do
+ class KernelSpecs::B
+ def to_str
+ ":c"
+ end
+ end
+ -> { "".instance_variable_set(KernelSpecs::B.new, 4) }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if pass an object that cannot be a symbol" do
+ -> { "".instance_variable_set(:c, 1) }.should raise_error(NameError)
+ end
+
+ it "accepts as instance variable name any instance of a class that responds to to_str" do
+ class KernelSpecs::C
+ def initialize
+ @a = 1
+ end
+ def to_str
+ "@a"
+ end
+ end
+ KernelSpecs::C.new.instance_variable_set(KernelSpecs::C.new, 2).should == 2
+ end
+
+ describe "on frozen objects" do
+ before :each do
+ klass = Class.new do
+ attr_reader :ivar
+ def initialize
+ @ivar = :origin
+ end
+ end
+
+ @frozen = klass.new.freeze
+ end
+
+ it "keeps stored object after any exceptions" do
+ -> { @frozen.instance_variable_set(:@ivar, :replacement) }.should raise_error(Exception)
+ @frozen.ivar.should equal(:origin)
+ end
+
+ it "raises a FrozenError when passed replacement is identical to stored object" do
+ -> { @frozen.instance_variable_set(:@ivar, :origin) }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError when passed replacement is different from stored object" do
+ -> { @frozen.instance_variable_set(:@ivar, :replacement) }.should raise_error(FrozenError)
+ end
+
+ it "accepts unicode instance variable names" do
+ o = Object.new
+ o.instance_variable_set(:@💙, 42)
+ o.instance_variable_get(:@💙).should == 42
+ end
+
+ it "raises for frozen objects" do
+ -> { nil.instance_variable_set(:@foo, 42) }.should raise_error(FrozenError)
+ -> { nil.instance_variable_set(:foo, 42) }.should raise_error(NameError)
+ -> { :foo.instance_variable_set(:@foo, 42) }.should raise_error(FrozenError)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/instance_variables_spec.rb b/spec/ruby/core/kernel/instance_variables_spec.rb
new file mode 100644
index 0000000000..677d8bb7b2
--- /dev/null
+++ b/spec/ruby/core/kernel/instance_variables_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#instance_variables" do
+ describe "immediate values" do
+ it "returns an empty array if no instance variables are defined" do
+ [0, 0.5, true, false, nil].each do |value|
+ value.instance_variables.should == []
+ end
+ end
+
+ it "returns the correct array if an instance variable is added" do
+ a = 0
+ ->{ a.instance_variable_set("@test", 1) }.should raise_error(RuntimeError)
+ end
+ end
+
+ describe "regular objects" do
+ it "returns an empty array if no instance variables are defined" do
+ Object.new.instance_variables.should == []
+ end
+
+ it "returns the correct array if an instance variable is added" do
+ a = Object.new
+ a.instance_variable_set("@test", 1)
+ a.instance_variables.should == [:@test]
+ end
+
+ it "returns the instances variables in the order declared" do
+ c = Class.new do
+ def initialize
+ @c = 1
+ @a = 2
+ @b = 3
+ end
+ end
+ c.new.instance_variables.should == [:@c, :@a, :@b]
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/is_a_spec.rb b/spec/ruby/core/kernel/is_a_spec.rb
new file mode 100644
index 0000000000..dc69766f83
--- /dev/null
+++ b/spec/ruby/core/kernel/is_a_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/kind_of'
+
+describe "Kernel#is_a?" do
+ it_behaves_like :kernel_kind_of , :is_a?
+end
diff --git a/spec/ruby/core/kernel/iterator_spec.rb b/spec/ruby/core/kernel/iterator_spec.rb
new file mode 100644
index 0000000000..3fe8317f26
--- /dev/null
+++ b/spec/ruby/core/kernel/iterator_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is ""..."3.0" do
+ describe "Kernel#iterator?" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:iterator?)
+ end
+ end
+
+ describe "Kernel.iterator?" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/core/kernel/itself_spec.rb b/spec/ruby/core/kernel/itself_spec.rb
new file mode 100644
index 0000000000..c906d7c3e8
--- /dev/null
+++ b/spec/ruby/core/kernel/itself_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#itself" do
+ it "returns the receiver itself" do
+ foo = Object.new
+ foo.itself.should equal foo
+ end
+end
diff --git a/spec/ruby/core/kernel/kind_of_spec.rb b/spec/ruby/core/kernel/kind_of_spec.rb
new file mode 100644
index 0000000000..734035620c
--- /dev/null
+++ b/spec/ruby/core/kernel/kind_of_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/kind_of'
+
+describe "Kernel#kind_of?" do
+ it_behaves_like :kernel_kind_of , :kind_of?
+end
diff --git a/spec/ruby/core/kernel/lambda_spec.rb b/spec/ruby/core/kernel/lambda_spec.rb
new file mode 100644
index 0000000000..2aa4d4f2fb
--- /dev/null
+++ b/spec/ruby/core/kernel/lambda_spec.rb
@@ -0,0 +1,150 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/lambda'
+
+# The functionality of lambdas is specified in core/proc
+
+describe "Kernel.lambda" do
+ it_behaves_like :kernel_lambda, :lambda
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:lambda)
+ end
+
+ it "creates a lambda-style Proc if given a literal block" do
+ l = lambda { 42 }
+ l.lambda?.should be_true
+ end
+
+ it "creates a lambda-style Proc if given a literal block via #send" do
+ l = send(:lambda) { 42 }
+ l.lambda?.should be_true
+ end
+
+ it "creates a lambda-style Proc if given a literal block via #__send__" do
+ l = __send__(:lambda) { 42 }
+ l.lambda?.should be_true
+ end
+
+ it "creates a lambda-style Proc if given a literal block via Kernel.public_send" do
+ suppress_warning do
+ l = Kernel.public_send(:lambda) { 42 }
+ l.lambda?.should be_true
+ end
+ end
+
+ it "returns the passed Proc if given an existing Proc" do
+ some_proc = proc {}
+ l = suppress_warning {lambda(&some_proc)}
+ l.should equal(some_proc)
+ l.lambda?.should be_false
+ end
+
+ it "creates a lambda-style Proc when called with zsuper" do
+ suppress_warning do
+ l = KernelSpecs::LambdaSpecs::ForwardBlockWithZSuper.new.lambda { 42 }
+ l.lambda?.should be_true
+ l.call.should == 42
+
+ lambda { l.call(:extra) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "returns the passed Proc if given an existing Proc through super" do
+ some_proc = proc { }
+ l = KernelSpecs::LambdaSpecs::SuperAmpersand.new.lambda(&some_proc)
+ l.should equal(some_proc)
+ l.lambda?.should be_false
+ end
+
+ it "does not create lambda-style Procs when captured with #method" do
+ kernel_lambda = method(:lambda)
+ l = suppress_warning {kernel_lambda.call { 42 }}
+ l.lambda?.should be_false
+ l.call(:extra).should == 42
+ end
+
+ it "checks the arity of the call when no args are specified" do
+ l = lambda { :called }
+ l.call.should == :called
+
+ lambda { l.call(1) }.should raise_error(ArgumentError)
+ lambda { l.call(1, 2) }.should raise_error(ArgumentError)
+ end
+
+ it "checks the arity when 1 arg is specified" do
+ l = lambda { |a| :called }
+ l.call(1).should == :called
+
+ lambda { l.call }.should raise_error(ArgumentError)
+ lambda { l.call(1, 2) }.should raise_error(ArgumentError)
+ end
+
+ it "does not check the arity when passing a Proc with &" do
+ l = lambda { || :called }
+ p = proc { || :called }
+
+ lambda { l.call(1) }.should raise_error(ArgumentError)
+ p.call(1).should == :called
+ end
+
+ it "accepts 0 arguments when used with ||" do
+ lambda {
+ lambda { || }.call(1)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "strictly checks the arity when 0 or 2..inf args are specified" do
+ l = lambda { |a,b| }
+
+ lambda {
+ l.call
+ }.should raise_error(ArgumentError)
+
+ lambda {
+ l.call(1)
+ }.should raise_error(ArgumentError)
+
+ lambda {
+ l.call(1,2)
+ }.should_not raise_error(ArgumentError)
+ end
+
+ it "returns from the lambda itself, not the creation site of the lambda" do
+ @reached_end_of_method = nil
+ def test
+ send(:lambda) { return }.call
+ @reached_end_of_method = true
+ end
+ test
+ @reached_end_of_method.should be_true
+ end
+
+ it "allows long returns to flow through it" do
+ KernelSpecs::Lambda.new.outer.should == :good
+ end
+
+ it "treats the block as a Proc when lambda is re-defined" do
+ klass = Class.new do
+ def lambda (&block); block; end
+ def ret
+ lambda { return 1 }.call
+ 2
+ end
+ end
+ klass.new.lambda { 42 }.should be_an_instance_of Proc
+ klass.new.ret.should == 1
+ end
+
+ ruby_version_is "3.0" do
+ context "when called without a literal block" do
+ it "warns when proc isn't a lambda" do
+ -> { lambda(&proc{}) }.should complain("#{__FILE__}:#{__LINE__}: warning: lambda without a literal block is deprecated; use the proc without lambda instead\n")
+ end
+
+ it "doesn't warn when proc is lambda" do
+ -> { lambda(&lambda{}) }.should_not complain(verbose: true)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/load_spec.rb b/spec/ruby/core/kernel/load_spec.rb
new file mode 100644
index 0000000000..a165cc4acd
--- /dev/null
+++ b/spec/ruby/core/kernel/load_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/code_loading'
+require_relative 'shared/load'
+require_relative 'shared/require'
+
+describe "Kernel#load" do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:load)
+ end
+
+ it_behaves_like :kernel_require_basic, :load, CodeLoadingSpecs::Method.new
+end
+
+describe "Kernel#load" do
+ it_behaves_like :kernel_load, :load, CodeLoadingSpecs::Method.new
+end
+
+describe "Kernel.load" do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ it_behaves_like :kernel_require_basic, :load, Kernel
+end
+
+describe "Kernel.load" do
+ it_behaves_like :kernel_load, :load, Kernel
+end
diff --git a/spec/ruby/core/kernel/local_variables_spec.rb b/spec/ruby/core/kernel/local_variables_spec.rb
new file mode 100644
index 0000000000..f6f1e15f52
--- /dev/null
+++ b/spec/ruby/core/kernel/local_variables_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#local_variables" do
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:local_variables)
+ end
+
+ it "contains locals as they are added" do
+ a = 1
+ b = 2
+ local_variables.should include(:a, :b)
+ local_variables.length.should == 2
+ end
+
+ it "is accessible from bindings" do
+ def local_var_foo
+ a = 1
+ b = 2
+ binding
+ end
+ foo_binding = local_var_foo()
+ res = eval("local_variables",foo_binding)
+ res.should include(:a, :b)
+ res.length.should == 2
+ end
+
+ it "is accessible in eval" do
+ eval "a=1; b=2; ScratchPad.record local_variables"
+ ScratchPad.recorded.should include(:a, :b)
+ ScratchPad.recorded.length.should == 2
+ end
+
+ it "includes only unique variable names" do
+ def local_var_method
+ a = 1
+ 1.times do |;a|
+ return local_variables
+ end
+ end
+
+ local_var_method.should == [:a]
+ end
+end
diff --git a/spec/ruby/core/kernel/loop_spec.rb b/spec/ruby/core/kernel/loop_spec.rb
new file mode 100644
index 0000000000..7c76c7d28e
--- /dev/null
+++ b/spec/ruby/core/kernel/loop_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel.loop" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:loop)
+ end
+
+ it "calls block until it is terminated by a break" do
+ i = 0
+ loop do
+ i += 1
+ break if i == 10
+ end
+
+ i.should == 10
+ end
+
+ it "returns value passed to break" do
+ loop do
+ break 123
+ end.should == 123
+ end
+
+ it "returns nil if no value passed to break" do
+ loop do
+ break
+ end.should == nil
+ end
+
+ it "returns an enumerator if no block given" do
+ enum = loop
+ enum.instance_of?(Enumerator).should be_true
+ cnt = 0
+ enum.each do |*args|
+ raise "Args should be empty #{args.inspect}" unless args.empty?
+ cnt += 1
+ break cnt if cnt >= 42
+ end.should == 42
+ end
+
+ it "rescues StopIteration" do
+ loop do
+ raise StopIteration
+ end
+ 42.should == 42
+ end
+
+ it "rescues StopIteration's subclasses" do
+ finish = Class.new StopIteration
+ loop do
+ raise finish
+ end
+ 42.should == 42
+ end
+
+ it "does not rescue other errors" do
+ ->{ loop do raise StandardError end }.should raise_error( StandardError )
+ end
+
+ it "returns StopIteration#result, the result value of a finished iterator" do
+ e = Enumerator.new { |y|
+ y << 1
+ y << 2
+ :stopped
+ }
+ loop { e.next }.should == :stopped
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns Float::INFINITY" do
+ loop.size.should == Float::INFINITY
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/match_spec.rb b/spec/ruby/core/kernel/match_spec.rb
new file mode 100644
index 0000000000..aa25006163
--- /dev/null
+++ b/spec/ruby/core/kernel/match_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#=~" do
+ ruby_version_is ''...'3.2' do
+ it "returns nil matching any object" do
+ o = Object.new
+
+ suppress_warning do
+ (o =~ /Object/).should be_nil
+ (o =~ 'Object').should be_nil
+ (o =~ Object).should be_nil
+ (o =~ Object.new).should be_nil
+ (o =~ nil).should be_nil
+ (o =~ true).should be_nil
+ end
+ end
+
+ it "is deprecated" do
+ -> do
+ Object.new =~ /regexp/
+ end.should complain(/deprecated Object#=~ is called on Object/, verbose: true)
+ end
+ end
+
+ ruby_version_is '3.2' do
+ it "is no longer defined" do
+ Object.new.should_not.respond_to?(:=~)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/method_spec.rb b/spec/ruby/core/kernel/method_spec.rb
new file mode 100644
index 0000000000..caf2ec948b
--- /dev/null
+++ b/spec/ruby/core/kernel/method_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'shared/method'
+require_relative 'fixtures/classes'
+
+describe "Kernel#method" do
+ it_behaves_like :kernel_method, :method
+
+ before :each do
+ @obj = KernelSpecs::A.new
+ end
+
+ it "can be called on a private method" do
+ @obj.send(:private_method).should == :private_method
+ @obj.method(:private_method).should be_an_instance_of(Method)
+ end
+
+ it "can be called on a protected method" do
+ @obj.send(:protected_method).should == :protected_method
+ @obj.method(:protected_method).should be_an_instance_of(Method)
+ end
+
+ it "will see an alias of the original method as == when in a derived class" do
+ obj = KernelSpecs::B.new
+ obj.method(:aliased_pub_method).should == obj.method(:pub_method)
+ end
+
+ it "can call methods created with define_method" do
+ m = @obj.method(:defined_method)
+ m.call.should == :defined
+ end
+
+ it "can be called even if we only repond_to_missing? method, true" do
+ m = KernelSpecs::RespondViaMissing.new.method(:handled_privately)
+ m.should be_an_instance_of(Method)
+ m.call(1, 2, 3).should == "Done handled_privately([1, 2, 3])"
+ end
+
+ it "can call a #method_missing accepting zero or one arguments" do
+ cls = Class.new do
+ def respond_to_missing?(name, *)
+ name == :foo or super
+ end
+ def method_missing
+ :no_args
+ end
+ end
+ m = cls.new.method(:foo)
+ -> { m.call }.should raise_error(ArgumentError)
+
+ cls = Class.new do
+ def respond_to_missing?(name, *)
+ name == :bar or super
+ end
+ def method_missing(m)
+ m
+ end
+ end
+ m = cls.new.method(:bar)
+ m.call.should == :bar
+ end
+end
diff --git a/spec/ruby/core/kernel/methods_spec.rb b/spec/ruby/core/kernel/methods_spec.rb
new file mode 100644
index 0000000000..fb7a7e8be9
--- /dev/null
+++ b/spec/ruby/core/kernel/methods_spec.rb
@@ -0,0 +1,101 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../fixtures/reflection'
+
+# TODO: rewrite
+describe "Kernel#methods" do
+ it "returns singleton methods defined by obj.meth" do
+ KernelSpecs::Methods.methods(false).should include(:ichi)
+ end
+
+ it "returns singleton methods defined in 'class << self'" do
+ KernelSpecs::Methods.methods(false).should include(:san)
+ end
+
+ it "returns private singleton methods defined by obj.meth" do
+ KernelSpecs::Methods.methods(false).should include(:shi)
+ end
+
+ it "returns singleton methods defined in 'class << self' when it follows 'private'" do
+ KernelSpecs::Methods.methods(false).should include(:roku)
+ end
+
+ it "does not return private singleton methods defined in 'class << self'" do
+ KernelSpecs::Methods.methods(false).should_not include(:shichi)
+ end
+
+ it "returns the publicly accessible methods of the object" do
+ meths = KernelSpecs::Methods.methods(false)
+ meths.should include(:hachi, :ichi, :juu, :juu_ichi,
+ :juu_ni, :roku, :san, :shi)
+
+ KernelSpecs::Methods.new.methods(false).should == []
+ end
+
+ it "returns the publicly accessible methods in the object, its ancestors and mixed-in modules" do
+ meths = KernelSpecs::Methods.methods(false) & KernelSpecs::Methods.methods
+ meths.should include(:hachi, :ichi, :juu, :juu_ichi,
+ :juu_ni, :roku, :san, :shi)
+
+ KernelSpecs::Methods.new.methods.should include(:ku, :ni, :juu_san)
+ end
+
+ it "returns methods added to the metaclass through extend" do
+ meth = KernelSpecs::Methods.new
+ meth.methods.should_not include(:peekaboo)
+ meth.extend(KernelSpecs::Methods::MetaclassMethods)
+ meth.methods.should include(:peekaboo)
+ end
+
+ it "does not return undefined singleton methods defined by obj.meth" do
+ o = KernelSpecs::Child.new
+ def o.single; end
+ o.methods.should include(:single)
+
+ class << o; self; end.send :undef_method, :single
+ o.methods.should_not include(:single)
+ end
+
+ it "does not return superclass methods undefined in the object's class" do
+ KernelSpecs::Child.new.methods.should_not include(:parent_method)
+ end
+
+ it "does not return superclass methods undefined in a superclass" do
+ KernelSpecs::Grandchild.new.methods.should_not include(:parent_method)
+ end
+
+ it "does not return included module methods undefined in the object's class" do
+ KernelSpecs::Grandchild.new.methods.should_not include(:parent_mixin_method)
+ end
+end
+
+describe :kernel_methods_supers, shared: true do
+ before :all do
+ @ms = [:pro, :pub]
+ end
+
+ it "returns a unique list for an object extended by a module" do
+ m = ReflectSpecs.oed.methods(*@object)
+ m.select { |x| @ms.include? x }.sort.should == @ms
+ end
+
+ it "returns a unique list for a class including a module" do
+ m = ReflectSpecs::D.new.methods(*@object)
+ m.select { |x| @ms.include? x }.sort.should == @ms
+ end
+
+ it "returns a unique list for a subclass of a class that includes a module" do
+ m = ReflectSpecs::E.new.methods(*@object)
+ m.select { |x| @ms.include? x }.sort.should == @ms
+ end
+end
+
+describe "Kernel#methods" do
+ describe "when not passed an argument" do
+ it_behaves_like :kernel_methods_supers, nil, []
+ end
+
+ describe "when passed true" do
+ it_behaves_like :kernel_methods_supers, nil, true
+ end
+end
diff --git a/spec/ruby/core/kernel/nil_spec.rb b/spec/ruby/core/kernel/nil_spec.rb
new file mode 100644
index 0000000000..7418245f26
--- /dev/null
+++ b/spec/ruby/core/kernel/nil_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Kernel#nil?' do
+ it 'returns false' do
+ Object.should_not.nil?
+ Object.new.should_not.nil?
+ ''.should_not.nil?
+ [].should_not.nil?
+ {}.should_not.nil?
+ end
+end
diff --git a/spec/ruby/core/kernel/not_match_spec.rb b/spec/ruby/core/kernel/not_match_spec.rb
new file mode 100644
index 0000000000..906f18df2c
--- /dev/null
+++ b/spec/ruby/core/kernel/not_match_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#!~" do
+ class KernelSpecs::NotMatch
+ def !~(obj)
+ :foo
+ end
+ end
+
+ it 'calls =~ internally and negates the result' do
+ obj = Object.new
+ obj.should_receive(:=~).and_return(true)
+ (obj !~ :foo).should == false
+ end
+
+ it 'can be overridden in subclasses' do
+ obj = KernelSpecs::NotMatch.new
+ (obj !~ :bar).should == :foo
+ end
+end
diff --git a/spec/ruby/core/kernel/object_id_spec.rb b/spec/ruby/core/kernel/object_id_spec.rb
new file mode 100644
index 0000000000..ef9e80c831
--- /dev/null
+++ b/spec/ruby/core/kernel/object_id_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/object_id'
+
+describe "Kernel#object_id" do
+ it_behaves_like :object_id, :object_id, Object
+end
diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb
new file mode 100644
index 0000000000..fa9299f9d4
--- /dev/null
+++ b/spec/ruby/core/kernel/open_spec.rb
@@ -0,0 +1,167 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#open" do
+ before :each do
+ @name = tmp("kernel_open.txt")
+ @content = "This is a test"
+ touch(@name) { |f| f.write @content }
+ @file = nil
+ end
+
+ after :each do
+ @file.close if @file
+ rm_r @name
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:open)
+ end
+
+ it "opens a file when given a valid filename" do
+ @file = open(@name)
+ @file.should be_kind_of(File)
+ end
+
+ it "opens a file when called with a block" do
+ open(@name, "r") { |f| f.gets }.should == @content
+ end
+
+ platform_is_not :windows, :wasi do
+ it "opens an io when path starts with a pipe" do
+ @io = open("|date")
+ begin
+ @io.should be_kind_of(IO)
+ @io.read
+ ensure
+ @io.close
+ end
+ end
+
+ it "opens an io when called with a block" do
+ @output = open("|date") { |f| f.read }
+ @output.should_not == ''
+ end
+
+ it "opens an io for writing" do
+ -> do
+ bytes = open("|cat", "w") { |io| io.write(".") }
+ bytes.should == 1
+ end.should output_to_fd(".")
+ end
+ end
+
+ platform_is :windows do
+ it "opens an io when path starts with a pipe" do
+ @io = open("|date /t")
+ begin
+ @io.should be_kind_of(IO)
+ @io.read
+ ensure
+ @io.close
+ end
+ end
+
+ it "opens an io when called with a block" do
+ @output = open("|date /t") { |f| f.read }
+ @output.should_not == ''
+ end
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { open }.should raise_error(ArgumentError)
+ end
+
+ describe "when given an object that responds to to_open" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "calls #to_path to covert the argument to a String before calling #to_str" do
+ obj = mock("open to_path")
+ obj.should_receive(:to_path).at_least(1).times.and_return(@name)
+ obj.should_not_receive(:to_str)
+
+ open(obj, "r") { |f| f.gets }.should == @content
+ end
+
+ it "calls #to_str to convert the argument to a String" do
+ obj = mock("open to_str")
+ obj.should_receive(:to_str).at_least(1).times.and_return(@name)
+
+ open(obj, "r") { |f| f.gets }.should == @content
+ end
+
+ it "calls #to_open on argument" do
+ obj = mock('fileish')
+ @file = File.open(@name)
+ obj.should_receive(:to_open).and_return(@file)
+ @file = open(obj)
+ @file.should be_kind_of(File)
+ end
+
+ it "returns the value from #to_open" do
+ obj = mock('to_open')
+ obj.should_receive(:to_open).and_return(:value)
+
+ open(obj).should == :value
+ end
+
+ it "passes its arguments onto #to_open" do
+ obj = mock('to_open')
+ obj.should_receive(:to_open).with(1,2,3)
+
+ open(obj, 1, 2, 3)
+ end
+
+ it "passes the return value from #to_open to a block" do
+ obj = mock('to_open')
+ obj.should_receive(:to_open).and_return(:value)
+
+ open(obj) do |mock|
+ ScratchPad.record(mock)
+ end
+
+ ScratchPad.recorded.should == :value
+ end
+ end
+
+ it "raises a TypeError if passed a non-String that does not respond to #to_open" do
+ obj = mock('non-fileish')
+ -> { open(obj) }.should raise_error(TypeError)
+ -> { open(nil) }.should raise_error(TypeError)
+ -> { open(7) }.should raise_error(TypeError)
+ end
+
+ it "accepts nil for mode and permission" do
+ open(@name, nil, nil) { |f| f.gets }.should == @content
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "works correctly when redefined by open-uri" do
+ code = <<~RUBY
+ require 'open-uri'
+ obj = Object.new
+ def obj.to_open; self; end
+ p open(obj) == obj
+ RUBY
+ ruby_exe(code, args: "2>&1").should == "true\n"
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "is not redefined by open-uri" do
+ code = <<~RUBY
+ before = Kernel.instance_method(:open)
+ require 'open-uri'
+ after = Kernel.instance_method(:open)
+ p before == after
+ RUBY
+ ruby_exe(code, args: "2>&1").should == "true\n"
+ end
+ end
+end
+
+describe "Kernel.open" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/p_spec.rb b/spec/ruby/core/kernel/p_spec.rb
new file mode 100644
index 0000000000..eae191aa54
--- /dev/null
+++ b/spec/ruby/core/kernel/p_spec.rb
@@ -0,0 +1,85 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#p" do
+ before :all do
+ @rs_f, @rs_b, @rs_c = $/, $\, $,
+ end
+
+ after :each do
+ suppress_warning {
+ $/, $\, $, = @rs_f, @rs_b, @rs_c
+ }
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:p)
+ end
+
+ # TODO: fix
+ it "flushes output if receiver is a File" do
+ filename = tmp("Kernel_p_flush") + $$.to_s
+ begin
+ File.open(filename, "w") do |f|
+ begin
+ old_stdout = $stdout
+ $stdout = f
+ p("abcde")
+ ensure
+ $stdout = old_stdout
+ end
+
+ File.open(filename) do |f2|
+ f2.read(7).should == "\"abcde\""
+ end
+ end
+ ensure
+ rm_r filename
+ end
+ end
+
+ it "prints obj.inspect followed by system record separator for each argument given" do
+ o = mock("Inspector Gadget")
+ o.should_receive(:inspect).any_number_of_times.and_return "Next time, Gadget, NEXT TIME!"
+
+ -> { p(o) }.should output("Next time, Gadget, NEXT TIME!\n")
+ -> { p(*[o]) }.should output("Next time, Gadget, NEXT TIME!\n")
+ -> { p(*[o, o]) }.should output("Next time, Gadget, NEXT TIME!\nNext time, Gadget, NEXT TIME!\n")
+ -> { p([o])}.should output("[#{o.inspect}]\n")
+ end
+
+ it "is not affected by setting $\\, $/ or $," do
+ o = mock("Inspector Gadget")
+ o.should_receive(:inspect).any_number_of_times.and_return "Next time, Gadget, NEXT TIME!"
+
+ suppress_warning {
+ $, = " *helicopter sound*\n"
+ }
+ -> { p(o) }.should output_to_fd("Next time, Gadget, NEXT TIME!\n")
+
+ suppress_warning {
+ $\ = " *helicopter sound*\n"
+ }
+ -> { p(o) }.should output_to_fd("Next time, Gadget, NEXT TIME!\n")
+
+ suppress_warning {
+ $/ = " *helicopter sound*\n"
+ }
+ -> { p(o) }.should output_to_fd("Next time, Gadget, NEXT TIME!\n")
+ end
+
+ it "prints nothing if no argument is given" do
+ -> { p }.should output("")
+ end
+
+ it "prints nothing if called splatting an empty Array" do
+ -> { p(*[]) }.should output("")
+ end
+
+ # Not sure how to spec this, but wanted to note the behavior here
+ it "does not flush if receiver is not a TTY or a File"
+end
+
+describe "Kernel.p" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/pp_spec.rb b/spec/ruby/core/kernel/pp_spec.rb
new file mode 100644
index 0000000000..b5b1c98d38
--- /dev/null
+++ b/spec/ruby/core/kernel/pp_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#pp" do
+ it "lazily loads the 'pp' library and delegates the call to that library" do
+ # Run in child process to ensure 'pp' hasn't been loaded yet.
+ output = ruby_exe("pp [1, 2, 3]")
+ output.should == "[1, 2, 3]\n"
+ end
+end
diff --git a/spec/ruby/core/kernel/print_spec.rb b/spec/ruby/core/kernel/print_spec.rb
new file mode 100644
index 0000000000..7e7c9b822d
--- /dev/null
+++ b/spec/ruby/core/kernel/print_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#print" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:print)
+ end
+
+ it "delegates to $stdout" do
+ -> { print :arg }.should output("arg")
+ end
+
+ it "prints $_ when no arguments are given" do
+ orig_value = $_
+ $_ = 'foo'
+ -> { print }.should output("foo")
+ ensure
+ $_ = orig_value
+ end
+end
+
+describe "Kernel.print" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/printf_spec.rb b/spec/ruby/core/kernel/printf_spec.rb
new file mode 100644
index 0000000000..d8f93ce429
--- /dev/null
+++ b/spec/ruby/core/kernel/printf_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/sprintf'
+
+describe "Kernel#printf" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:printf)
+ end
+end
+
+describe "Kernel.printf" do
+ before :each do
+ @stdout = $stdout
+ @name = tmp("kernel_puts.txt")
+ $stdout = new_io @name
+ end
+
+ after :each do
+ $stdout.close
+ $stdout = @stdout
+ rm_r @name
+ end
+
+ it "writes to stdout when a string is the first argument" do
+ $stdout.should_receive(:write).with("string")
+ Kernel.printf("%s", "string")
+ end
+
+ it "calls write on the first argument when it is not a string" do
+ object = mock('io')
+ object.should_receive(:write).with("string")
+ Kernel.printf(object, "%s", "string")
+ end
+end
+
+describe "Kernel.printf" do
+ describe "formatting" do
+ before :each do
+ require "stringio"
+ end
+
+ context "io is specified" do
+ it_behaves_like :kernel_sprintf, -> format, *args {
+ io = StringIO.new(+"")
+ Kernel.printf(io, format, *args)
+ io.string
+ }
+ end
+
+ context "io is not specified" do
+ it_behaves_like :kernel_sprintf, -> format, *args {
+ stdout = $stdout
+ begin
+ $stdout = io = StringIO.new(+"")
+ Kernel.printf(format, *args)
+ io.string
+ ensure
+ $stdout = stdout
+ end
+ }
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/private_methods_spec.rb b/spec/ruby/core/kernel/private_methods_spec.rb
new file mode 100644
index 0000000000..041634d1e5
--- /dev/null
+++ b/spec/ruby/core/kernel/private_methods_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../fixtures/reflection'
+
+# TODO: rewrite
+describe "Kernel#private_methods" do
+ it "returns a list of the names of privately accessible methods in the object" do
+ m = KernelSpecs::Methods.private_methods(false)
+ m.should include(:shichi)
+ m = KernelSpecs::Methods.new.private_methods(false)
+ m.should include(:juu_shi)
+ end
+
+ it "returns a list of the names of privately accessible methods in the object and its ancestors and mixed-in modules" do
+ m = (KernelSpecs::Methods.private_methods(false) & KernelSpecs::Methods.private_methods)
+
+ m.should include(:shichi)
+ m = KernelSpecs::Methods.new.private_methods
+ m.should include(:juu_shi)
+ end
+
+ it "returns private methods mixed in to the metaclass" do
+ m = KernelSpecs::Methods.new
+ m.extend(KernelSpecs::Methods::MetaclassMethods)
+ m.private_methods.should include(:shoo)
+ end
+end
+
+describe :kernel_private_methods_supers, shared: true do
+ it "returns a unique list for an object extended by a module" do
+ m = ReflectSpecs.oed.private_methods(*@object)
+ m.select { |x| x == :pri }.sort.should == [:pri]
+ end
+
+ it "returns a unique list for a class including a module" do
+ m = ReflectSpecs::D.new.private_methods(*@object)
+ m.select { |x| x == :pri }.sort.should == [:pri]
+ end
+
+ it "returns a unique list for a subclass of a class that includes a module" do
+ m = ReflectSpecs::E.new.private_methods(*@object)
+ m.select { |x| x == :pri }.sort.should == [:pri]
+ end
+end
+
+describe :kernel_private_methods_with_falsy, shared: true do
+ it "returns a list of private methods in without its ancestors" do
+ ReflectSpecs::F.private_methods(@object).select{|m|/_pri\z/ =~ m}.sort.should == [:ds_pri, :fs_pri]
+ ReflectSpecs::F.new.private_methods(@object).should == [:f_pri]
+ end
+end
+
+describe "Kernel#private_methods" do
+ describe "when not passed an argument" do
+ it_behaves_like :kernel_private_methods_supers, nil, []
+ end
+
+ describe "when passed true" do
+ it_behaves_like :kernel_private_methods_supers, nil, true
+ end
+
+ describe "when passed false" do
+ it_behaves_like :kernel_private_methods_with_falsy, nil, false
+ end
+
+ describe "when passed nil" do
+ it_behaves_like :kernel_private_methods_with_falsy, nil, nil
+ end
+end
diff --git a/spec/ruby/core/kernel/proc_spec.rb b/spec/ruby/core/kernel/proc_spec.rb
new file mode 100644
index 0000000000..231c1f0dfb
--- /dev/null
+++ b/spec/ruby/core/kernel/proc_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/lambda'
+
+# The functionality of Proc objects is specified in core/proc
+
+describe "Kernel.proc" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:proc)
+ end
+
+ it "creates a proc-style Proc if given a literal block" do
+ l = proc { 42 }
+ l.lambda?.should be_false
+ end
+
+ it "returned the passed Proc if given an existing Proc" do
+ some_lambda = -> {}
+ some_lambda.lambda?.should be_true
+ l = proc(&some_lambda)
+ l.should equal(some_lambda)
+ l.lambda?.should be_true
+ end
+
+ it_behaves_like :kernel_lambda, :proc
+
+ it "returns from the creation site of the proc, not just the proc itself" do
+ @reached_end_of_method = nil
+ def test
+ proc { return }.call
+ @reached_end_of_method = true
+ end
+ test
+ @reached_end_of_method.should be_nil
+ end
+end
+
+describe "Kernel#proc" do
+ def some_method
+ proc
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "can be created when called with no block" do
+ -> {
+ some_method { "hello" }
+ }.should complain(/Capturing the given block using Kernel#proc is deprecated/)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "raises an ArgumentError when passed no block" do
+ -> {
+ some_method { "hello" }
+ }.should raise_error(ArgumentError, 'tried to create Proc object without a block')
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/protected_methods_spec.rb b/spec/ruby/core/kernel/protected_methods_spec.rb
new file mode 100644
index 0000000000..d3334e886b
--- /dev/null
+++ b/spec/ruby/core/kernel/protected_methods_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../fixtures/reflection'
+
+# TODO: rewrite
+
+# The reason why having include() is to show the specification explicitly.
+# You should use have_protected_method() with the exception of this spec.
+describe "Kernel#protected_methods" do
+ it "returns a list of the names of protected methods accessible in the object" do
+ KernelSpecs::Methods.protected_methods(false).sort.should include(:juu_ichi)
+ KernelSpecs::Methods.new.protected_methods(false).should include(:ku)
+ end
+
+ it "returns a list of the names of protected methods accessible in the object and from its ancestors and mixed-in modules" do
+ l1 = KernelSpecs::Methods.protected_methods(false)
+ l2 = KernelSpecs::Methods.protected_methods
+ (l1 & l2).should include(:juu_ichi)
+ KernelSpecs::Methods.new.protected_methods.should include(:ku)
+ end
+
+ it "returns methods mixed in to the metaclass" do
+ m = KernelSpecs::Methods.new
+ m.extend(KernelSpecs::Methods::MetaclassMethods)
+ m.protected_methods.should include(:nopeeking)
+ end
+end
+
+describe :kernel_protected_methods_supers, shared: true do
+ it "returns a unique list for an object extended by a module" do
+ m = ReflectSpecs.oed.protected_methods(*@object)
+ m.select { |x| x == :pro }.sort.should == [:pro]
+ end
+
+ it "returns a unique list for a class including a module" do
+ m = ReflectSpecs::D.new.protected_methods(*@object)
+ m.select { |x| x == :pro }.sort.should == [:pro]
+ end
+
+ it "returns a unique list for a subclass of a class that includes a module" do
+ m = ReflectSpecs::E.new.protected_methods(*@object)
+ m.select { |x| x == :pro }.sort.should == [:pro]
+ end
+end
+
+describe :kernel_protected_methods_with_falsy, shared: true do
+ it "returns a list of protected methods in without its ancestors" do
+ ReflectSpecs::F.protected_methods(@object).select{|m|/_pro\z/ =~ m}.sort.should == [:ds_pro, :fs_pro]
+ ReflectSpecs::F.new.protected_methods(@object).should == [:f_pro]
+ end
+end
+
+describe "Kernel#protected_methods" do
+ describe "when not passed an argument" do
+ it_behaves_like :kernel_protected_methods_supers, nil, []
+ end
+
+ describe "when passed true" do
+ it_behaves_like :kernel_protected_methods_supers, nil, true
+ end
+
+ describe "when passed false" do
+ it_behaves_like :kernel_protected_methods_with_falsy, nil, false
+ end
+
+ describe "when passed nil" do
+ it_behaves_like :kernel_protected_methods_with_falsy, nil, nil
+ end
+end
diff --git a/spec/ruby/core/kernel/public_method_spec.rb b/spec/ruby/core/kernel/public_method_spec.rb
new file mode 100644
index 0000000000..c5d54c777e
--- /dev/null
+++ b/spec/ruby/core/kernel/public_method_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/method'
+
+describe "Kernel#public_method" do
+ it_behaves_like :kernel_method, :public_method
+
+ before :each do
+ @obj = KernelSpecs::A.new
+ end
+
+ it "raises a NameError when called on a private method" do
+ @obj.send(:private_method).should == :private_method
+ -> do
+ @obj.public_method(:private_method)
+ end.should raise_error(NameError)
+ end
+
+ it "raises a NameError when called on a protected method" do
+ @obj.send(:protected_method).should == :protected_method
+ -> {
+ @obj.public_method(:protected_method)
+ }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if we only repond_to_missing? method, true" do
+ obj = KernelSpecs::RespondViaMissing.new
+ -> do
+ obj.public_method(:handled_privately)
+ end.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/core/kernel/public_methods_spec.rb b/spec/ruby/core/kernel/public_methods_spec.rb
new file mode 100644
index 0000000000..a5512784fb
--- /dev/null
+++ b/spec/ruby/core/kernel/public_methods_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../fixtures/reflection'
+
+# TODO: rewrite
+describe "Kernel#public_methods" do
+ it "returns a list of the names of publicly accessible methods in the object" do
+ KernelSpecs::Methods.public_methods(false).sort.should include(:hachi,
+ :ichi, :juu, :juu_ni, :roku, :san, :shi)
+ KernelSpecs::Methods.new.public_methods(false).sort.should include(:juu_san, :ni)
+ end
+
+ it "returns a list of names without protected accessible methods in the object" do
+ KernelSpecs::Methods.public_methods(false).sort.should_not include(:juu_ichi)
+ KernelSpecs::Methods.new.public_methods(false).sort.should_not include(:ku)
+ end
+
+ it "returns a list of the names of publicly accessible methods in the object and its ancestors and mixed-in modules" do
+ (KernelSpecs::Methods.public_methods(false) & KernelSpecs::Methods.public_methods).sort.should include(
+ :hachi, :ichi, :juu, :juu_ni, :roku, :san, :shi)
+ m = KernelSpecs::Methods.new.public_methods
+ m.should include(:ni, :juu_san)
+ end
+
+ it "returns methods mixed in to the metaclass" do
+ m = KernelSpecs::Methods.new
+ m.extend(KernelSpecs::Methods::MetaclassMethods)
+ m.public_methods.should include(:peekaboo)
+ end
+
+ it "returns public methods for immediates" do
+ 10.public_methods.should include(:divmod)
+ end
+end
+
+describe :kernel_public_methods_supers, shared: true do
+ it "returns a unique list for an object extended by a module" do
+ m = ReflectSpecs.oed.public_methods(*@object)
+ m.select { |x| x == :pub }.sort.should == [:pub]
+ end
+
+ it "returns a unique list for a class including a module" do
+ m = ReflectSpecs::D.new.public_methods(*@object)
+ m.select { |x| x == :pub }.sort.should == [:pub]
+ end
+
+ it "returns a unique list for a subclass of a class that includes a module" do
+ m = ReflectSpecs::E.new.public_methods(*@object)
+ m.select { |x| x == :pub }.sort.should == [:pub]
+ end
+end
+
+describe :kernel_public_methods_with_falsy, shared: true do
+ it "returns a list of public methods in without its ancestors" do
+ ReflectSpecs::F.public_methods(@object).select{|m|/_pub\z/ =~ m}.sort.should == [:ds_pub, :fs_pub]
+ ReflectSpecs::F.new.public_methods(@object).should == [:f_pub]
+ end
+end
+
+describe "Kernel#public_methods" do
+ describe "when not passed an argument" do
+ it_behaves_like :kernel_public_methods_supers, nil, []
+ end
+
+ describe "when passed true" do
+ it_behaves_like :kernel_public_methods_supers, nil, true
+ end
+
+ describe "when passed false" do
+ it_behaves_like :kernel_public_methods_with_falsy, nil, false
+ end
+
+ describe "when passed nil" do
+ it_behaves_like :kernel_public_methods_with_falsy, nil, nil
+ end
+end
diff --git a/spec/ruby/core/kernel/public_send_spec.rb b/spec/ruby/core/kernel/public_send_spec.rb
new file mode 100644
index 0000000000..4dae419ff9
--- /dev/null
+++ b/spec/ruby/core/kernel/public_send_spec.rb
@@ -0,0 +1,116 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/basicobject/send'
+
+describe "Kernel#public_send" do
+ it "invokes the named public method" do
+ class KernelSpecs::Foo
+ def bar
+ 'done'
+ end
+ end
+ KernelSpecs::Foo.new.public_send(:bar).should == 'done'
+ end
+
+ it "invokes the named alias of a public method" do
+ class KernelSpecs::Foo
+ def bar
+ 'done'
+ end
+ alias :aka :bar
+ end
+ KernelSpecs::Foo.new.public_send(:aka).should == 'done'
+ end
+
+ it "raises a NoMethodError if the method is protected" do
+ class KernelSpecs::Foo
+ protected
+ def bar
+ 'done'
+ end
+ end
+ -> { KernelSpecs::Foo.new.public_send(:bar)}.should raise_error(NoMethodError)
+ end
+
+ it "raises a NoMethodError if the named method is private" do
+ class KernelSpecs::Foo
+ private
+ def bar
+ 'done2'
+ end
+ end
+ -> {
+ KernelSpecs::Foo.new.public_send(:bar)
+ }.should raise_error(NoMethodError)
+ end
+
+ context 'called from own public method' do
+ before do
+ class << @receiver = Object.new
+ def call_protected_method
+ public_send :protected_method
+ end
+
+ def call_private_method
+ public_send :private_method
+ end
+
+ protected
+
+ def protected_method
+ raise 'Should not called'
+ end
+
+ private
+
+ def private_method
+ raise 'Should not called'
+ end
+ end
+ end
+
+ it "raises a NoMethodError if the method is protected" do
+ -> { @receiver.call_protected_method }.should raise_error(NoMethodError)
+ end
+
+ it "raises a NoMethodError if the method is private" do
+ -> { @receiver.call_private_method }.should raise_error(NoMethodError)
+ end
+ end
+
+ it "raises a NoMethodError if the named method is an alias of a private method" do
+ class KernelSpecs::Foo
+ private
+ def bar
+ 'done2'
+ end
+ alias :aka :bar
+ end
+ -> {
+ KernelSpecs::Foo.new.public_send(:aka)
+ }.should raise_error(NoMethodError)
+ end
+
+ it "raises a NoMethodError if the named method is an alias of a protected method" do
+ class KernelSpecs::Foo
+ protected
+ def bar
+ 'done2'
+ end
+ alias :aka :bar
+ end
+ -> {
+ KernelSpecs::Foo.new.public_send(:aka)
+ }.should raise_error(NoMethodError)
+ end
+
+ it "includes `public_send` in the backtrace when passed not enough arguments" do
+ -> { public_send() }.should raise_error(ArgumentError) { |e| e.backtrace[0].should.include?("`public_send'") }
+ end
+
+ it "includes `public_send` in the backtrace when passed a single incorrect argument" do
+ -> { public_send(Object.new) }.should raise_error(TypeError) { |e| e.backtrace[0].should.include?("`public_send'") }
+ end
+
+ it_behaves_like :basicobject_send, :public_send
+end
diff --git a/spec/ruby/core/kernel/putc_spec.rb b/spec/ruby/core/kernel/putc_spec.rb
new file mode 100644
index 0000000000..74bd3765db
--- /dev/null
+++ b/spec/ruby/core/kernel/putc_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/io/putc'
+
+describe "Kernel#putc" do
+ it "is a private instance method" do
+ Kernel.should have_private_instance_method(:putc)
+ end
+end
+
+describe "Kernel.putc" do
+ before :each do
+ @name = tmp("kernel_putc.txt")
+ @io = new_io @name
+ @io_object = @object
+ @stdout, $stdout = $stdout, @io
+ end
+
+ after :each do
+ $stdout = @stdout
+ end
+
+ it_behaves_like :io_putc, :putc_method, KernelSpecs
+end
+
+describe "Kernel#putc" do
+ before :each do
+ @name = tmp("kernel_putc.txt")
+ @io = new_io @name
+ @io_object = @object
+ @stdout, $stdout = $stdout, @io
+ end
+
+ after :each do
+ $stdout = @stdout
+ end
+
+ it_behaves_like :io_putc, :putc_function, KernelSpecs
+end
diff --git a/spec/ruby/core/kernel/puts_spec.rb b/spec/ruby/core/kernel/puts_spec.rb
new file mode 100644
index 0000000000..6eb38e8fcf
--- /dev/null
+++ b/spec/ruby/core/kernel/puts_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#puts" do
+ before :each do
+ @stdout = $stdout
+ @name = tmp("kernel_puts.txt")
+ $stdout = new_io @name
+ end
+
+ after :each do
+ $stdout.close
+ $stdout = @stdout
+ rm_r @name
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:puts)
+ end
+
+ it "delegates to $stdout.puts" do
+ $stdout.should_receive(:puts).with(:arg)
+ puts :arg
+ end
+end
+
+describe "Kernel.puts" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/raise_spec.rb b/spec/ruby/core/kernel/raise_spec.rb
new file mode 100644
index 0000000000..4f190c120b
--- /dev/null
+++ b/spec/ruby/core/kernel/raise_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/kernel/raise'
+
+describe "Kernel#raise" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:raise)
+ end
+
+ it "re-raises the previously rescued exception if no exception is specified" do
+ ScratchPad.record nil
+
+ -> do
+ begin
+ raise Exception, "outer"
+ ScratchPad.record :no_abort
+ rescue Exception
+ begin
+ raise StandardError, "inner"
+ rescue StandardError
+ end
+
+ raise
+ ScratchPad.record :no_reraise
+ end
+ end.should raise_error(Exception, "outer")
+
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "accepts a cause keyword argument that sets the cause" do
+ cause = StandardError.new
+ -> { raise("error", cause: cause) }.should raise_error(RuntimeError) { |e| e.cause.should == cause }
+ end
+
+ it "accepts a cause keyword argument that overrides the last exception" do
+ begin
+ raise "first raise"
+ rescue => ignored
+ cause = StandardError.new
+ -> { raise("error", cause: cause) }.should raise_error(RuntimeError) { |e| e.cause.should == cause }
+ end
+ end
+
+ it "raises an ArgumentError when only cause is given" do
+ cause = StandardError.new
+ -> { raise(cause: cause) }.should raise_error(ArgumentError)
+ end
+end
+
+describe "Kernel#raise" do
+ it_behaves_like :kernel_raise, :raise, Kernel
+end
+
+describe "Kernel.raise" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/rand_spec.rb b/spec/ruby/core/kernel/rand_spec.rb
new file mode 100644
index 0000000000..355e425792
--- /dev/null
+++ b/spec/ruby/core/kernel/rand_spec.rb
@@ -0,0 +1,197 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#rand" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:rand)
+ end
+
+ it "returns a float if no argument is passed" do
+ rand.should be_kind_of(Float)
+ end
+
+ it "returns an integer for an integer argument" do
+ rand(77).should be_kind_of(Integer)
+ end
+
+ it "returns an integer for a float argument greater than 1" do
+ rand(1.3).should be_kind_of(Integer)
+ end
+
+ it "returns a float for an argument between -1 and 1" do
+ rand(-0.999).should be_kind_of(Float)
+ rand(-0.01).should be_kind_of(Float)
+ rand(0).should be_kind_of(Float)
+ rand(0.01).should be_kind_of(Float)
+ rand(0.999).should be_kind_of(Float)
+ end
+
+ it "ignores the sign of the argument" do
+ [0, 1, 2, 3].should include(rand(-4))
+ end
+
+ it "never returns a value greater or equal to 1.0 with no arguments" do
+ 1000.times do
+ (0...1.0).should include(rand)
+ end
+ end
+
+ it "never returns a value greater or equal to any passed in max argument" do
+ 1000.times do
+ (0...100).to_a.should include(rand(100))
+ end
+ end
+
+ it "calls to_int on its argument" do
+ l = mock('limit')
+ l.should_receive(:to_int).and_return 7
+
+ rand l
+ end
+
+ context "given an exclusive range" do
+ it "returns an Integer between the two Integers" do
+ 1000.times do
+ x = rand(4...6)
+ x.should be_kind_of(Integer)
+ (4...6).should include(x)
+ end
+ end
+
+ it "returns a Float between the given Integer and Float" do
+ 1000.times do
+ x = rand(4...6.5)
+ x.should be_kind_of(Float)
+ (4...6.5).should include(x)
+ end
+ end
+
+ it "returns a Float between the given Float and Integer" do
+ 1000.times do
+ x = rand(3.5...6)
+ x.should be_kind_of(Float)
+ (3.5...6).should include(x)
+ end
+ end
+
+ it "returns a Float between the two given Floats" do
+ 1000.times do
+ x = rand(3.5...6.5)
+ x.should be_kind_of(Float)
+ (3.5...6.5).should include(x)
+ end
+ end
+ end
+
+ context "given an inclusive range" do
+ it "returns an Integer between the two Integers" do
+ 1000.times do
+ x = rand(4..6)
+ x.should be_kind_of(Integer)
+ (4..6).should include(x)
+ end
+ end
+
+ it "returns a Float between the given Integer and Float" do
+ 1000.times do
+ x = rand(4..6.5)
+ x.should be_kind_of(Float)
+ (4..6.5).should include(x)
+ end
+ end
+
+ it "returns a Float between the given Float and Integer" do
+ 1000.times do
+ x = rand(3.5..6)
+ x.should be_kind_of(Float)
+ (3.5..6).should include(x)
+ end
+ end
+
+ it "returns a Float between the two given Floats" do
+ 1000.times do
+ x = rand(3.5..6.5)
+ x.should be_kind_of(Float)
+ (3.5..6.5).should include(x)
+ end
+ end
+ end
+
+ context "given an inclusive range between 0 and 1" do
+ it "returns an Integer between the two Integers" do
+ x = rand(0..1)
+ x.should be_kind_of(Integer)
+ (0..1).should include(x)
+ end
+
+ it "returns a Float if at least one side is Float" do
+ seed = 42
+ x1 = Random.new(seed).rand(0..1.0)
+ x2 = Random.new(seed).rand(0.0..1.0)
+ x3 = Random.new(seed).rand(0.0..1)
+
+ x3.should be_kind_of(Float)
+ x1.should eql(x3)
+ x2.should eql(x3)
+
+ (0.0..1.0).should include(x3)
+ end
+ end
+
+ context "given an exclusive range between 0 and 1" do
+ it "returns zero as an Integer" do
+ x = rand(0...1)
+ x.should be_kind_of(Integer)
+ x.should eql(0)
+ end
+
+ it "returns a Float if at least one side is Float" do
+ seed = 42
+ x1 = Random.new(seed).rand(0...1.0)
+ x2 = Random.new(seed).rand(0.0...1.0)
+ x3 = Random.new(seed).rand(0.0...1)
+
+ x3.should be_kind_of(Float)
+ x1.should eql(x3)
+ x2.should eql(x3)
+
+ (0.0...1.0).should include(x3)
+ end
+ end
+
+ it "returns a numeric for an range argument where max is < 1" do
+ rand(0.25..0.75).should be_kind_of(Numeric)
+ end
+
+ it "returns nil when range is backwards" do
+ rand(1..0).should be_nil
+ end
+
+ it "returns the range start/end when Float range is 0" do
+ rand(1.0..1.0).should eql(1.0)
+ end
+
+ it "returns the range start/end when Integer range is 0" do
+ rand(42..42).should eql(42)
+ end
+
+ it "supports custom object types" do
+ rand(KernelSpecs::CustomRangeInteger.new(1)..KernelSpecs::CustomRangeInteger.new(42)).should be_an_instance_of(KernelSpecs::CustomRangeInteger)
+ rand(KernelSpecs::CustomRangeFloat.new(1.0)..KernelSpecs::CustomRangeFloat.new(42.0)).should be_an_instance_of(KernelSpecs::CustomRangeFloat)
+ rand(Time.now..Time.now).should be_an_instance_of(Time)
+ end
+
+ it "is random on boot" do
+ results = 2.times.map {
+ out = ruby_exe('p rand', options: '--disable-gems')
+ Float(out)
+ }
+ results.size.should == 2
+ # this is technically flaky, but very unlikely in a good distribution
+ results[0].should_not == results[1]
+ end
+end
+
+describe "Kernel.rand" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/readline_spec.rb b/spec/ruby/core/kernel/readline_spec.rb
new file mode 100644
index 0000000000..dce7b03dc8
--- /dev/null
+++ b/spec/ruby/core/kernel/readline_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#readline" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:readline)
+ end
+end
+
+describe "Kernel.readline" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/readlines_spec.rb b/spec/ruby/core/kernel/readlines_spec.rb
new file mode 100644
index 0000000000..2b6d65fff2
--- /dev/null
+++ b/spec/ruby/core/kernel/readlines_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#readlines" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:readlines)
+ end
+end
+
+describe "Kernel.readlines" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/remove_instance_variable_spec.rb b/spec/ruby/core/kernel/remove_instance_variable_spec.rb
new file mode 100644
index 0000000000..4e5ba5e018
--- /dev/null
+++ b/spec/ruby/core/kernel/remove_instance_variable_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :kernel_remove_instance_variable, shared: true do
+ it "returns the instance variable's value" do
+ value = @instance.send :remove_instance_variable, @object
+ value.should == "hello"
+ end
+
+ it "removes the instance variable" do
+ @instance.send :remove_instance_variable, @object
+ @instance.instance_variable_defined?(@object).should be_false
+ end
+end
+
+describe "Kernel#remove_instance_variable" do
+ before do
+ @instance = KernelSpecs::InstanceVariable.new
+ end
+
+ it "is a public method" do
+ Kernel.should have_public_instance_method(:remove_instance_variable, false)
+ end
+
+ it "raises a NameError if the instance variable is not defined" do
+ -> do
+ @instance.send :remove_instance_variable, :@unknown
+ end.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the argument is not a valid instance variable name" do
+ -> do
+ @instance.send :remove_instance_variable, :"@0"
+ end.should raise_error(NameError)
+ end
+
+ it "raises a TypeError if passed an Object not defining #to_str" do
+ -> do
+ obj = mock("kernel remove_instance_variable")
+ @instance.send :remove_instance_variable, obj
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ o = Object.new
+ o.freeze
+ -> { o.remove_instance_variable(:@foo) }.should raise_error(FrozenError)
+ -> { o.remove_instance_variable(:foo) }.should raise_error(NameError)
+ end
+
+ it "raises for frozen objects" do
+ -> { nil.remove_instance_variable(:@foo) }.should raise_error(FrozenError)
+ -> { nil.remove_instance_variable(:foo) }.should raise_error(NameError)
+ -> { :foo.remove_instance_variable(:@foo) }.should raise_error(FrozenError)
+ end
+
+ describe "when passed a String" do
+ it_behaves_like :kernel_remove_instance_variable, nil, "@greeting"
+ end
+
+ describe "when passed a Symbol" do
+ it_behaves_like :kernel_remove_instance_variable, nil, :@greeting
+ end
+
+ describe "when passed an Object" do
+ it "calls #to_str to convert the argument" do
+ name = mock("kernel remove_instance_variable")
+ name.should_receive(:to_str).and_return("@greeting")
+ @instance.send :remove_instance_variable, name
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/require_relative_spec.rb b/spec/ruby/core/kernel/require_relative_spec.rb
new file mode 100644
index 0000000000..5999855de6
--- /dev/null
+++ b/spec/ruby/core/kernel/require_relative_spec.rb
@@ -0,0 +1,437 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/code_loading'
+
+describe "Kernel#require_relative with a relative path" do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ @dir = "../../fixtures/code"
+ @abs_dir = File.realpath(@dir, File.dirname(__FILE__))
+ @path = "#{@dir}/load_fixture.rb"
+ @abs_path = File.realpath(@path, File.dirname(__FILE__))
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ platform_is_not :windows do
+ describe "when file is a symlink" do
+ before :each do
+ @link = tmp("symlink.rb", false)
+ @real_path = "#{@abs_dir}/symlink/symlink1.rb"
+ File.symlink(@real_path, @link)
+ end
+
+ after :each do
+ rm_r @link
+ end
+
+ it "loads a path relative to current file" do
+ require_relative(@link).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+ end
+
+ it "loads a path relative to the current file" do
+ require_relative(@path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ describe "in an #instance_eval with a" do
+
+ it "synthetic file base name loads a file base name relative to the working directory" do
+ Dir.chdir @abs_dir do
+ Object.new.instance_eval("require_relative(#{File.basename(@path).inspect})", "foo.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "synthetic file path loads a relative path relative to the working directory plus the directory of the synthetic path" do
+ Dir.chdir @abs_dir do
+ Object.new.instance_eval("require_relative(File.join('..', #{File.basename(@path).inspect}))", "bar/foo.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ platform_is_not :windows do
+ it "synthetic relative file path with a Windows path separator specified loads a relative path relative to the working directory" do
+ Dir.chdir @abs_dir do
+ Object.new.instance_eval("require_relative(#{File.basename(@path).inspect})", "bar\\foo.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+
+ it "absolute file path loads a path relative to the absolute path" do
+ Object.new.instance_eval("require_relative(#{@path.inspect})", __FILE__).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "absolute file path loads a path relative to the root directory" do
+ root = @abs_path
+ until File.dirname(root) == root
+ root = File.dirname(root)
+ end
+ root_relative = @abs_path[root.size..-1]
+ Object.new.instance_eval("require_relative(#{root_relative.inspect})", "/").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ end
+
+ it "loads a file defining many methods" do
+ require_relative("#{@dir}/methods_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "raises a LoadError if the file does not exist" do
+ -> { require_relative("#{@dir}/nonexistent.rb") }.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+
+ it "raises a LoadError that includes the missing path" do
+ missing_path = "#{@dir}/nonexistent.rb"
+ expanded_missing_path = File.expand_path(missing_path, File.dirname(__FILE__))
+ -> { require_relative(missing_path) }.should raise_error(LoadError) { |e|
+ e.message.should include(expanded_missing_path)
+ e.path.should == expanded_missing_path
+ }
+ ScratchPad.recorded.should == []
+ end
+
+ it "raises a LoadError if basepath does not exist" do
+ -> { eval("require_relative('#{@dir}/nonexistent.rb')") }.should raise_error(LoadError)
+ end
+
+ it "stores the missing path in a LoadError object" do
+ path = "#{@dir}/nonexistent.rb"
+
+ -> {
+ require_relative(path)
+ }.should(raise_error(LoadError) { |e|
+ e.path.should == File.expand_path(path, @abs_dir)
+ })
+ end
+
+ it "calls #to_str on non-String objects" do
+ name = mock("load_fixture.rb mock")
+ name.should_receive(:to_str).and_return(@path)
+ require_relative(name).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "raises a TypeError if argument does not respond to #to_str" do
+ -> { require_relative(nil) }.should raise_error(TypeError)
+ -> { require_relative(42) }.should raise_error(TypeError)
+ -> {
+ require_relative([@path,@path])
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that has #to_s but not #to_str" do
+ name = mock("load_fixture.rb mock")
+ name.stub!(:to_s).and_return(@path)
+ -> { require_relative(name) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ name = mock("#to_str returns nil")
+ name.should_receive(:to_str).at_least(1).times.and_return(nil)
+ -> { require_relative(name) }.should raise_error(TypeError)
+ end
+
+ it "calls #to_path on non-String objects" do
+ name = mock("load_fixture.rb mock")
+ name.should_receive(:to_path).and_return(@path)
+ require_relative(name).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "calls #to_str on non-String objects returned by #to_path" do
+ name = mock("load_fixture.rb mock")
+ to_path = mock("load_fixture_rb #to_path mock")
+ name.should_receive(:to_path).and_return(to_path)
+ to_path.should_receive(:to_str).and_return(@path)
+ require_relative(name).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ describe "(file extensions)" do
+ it "loads a .rb extensioned file when passed a non-extensioned path" do
+ require_relative("#{@dir}/load_fixture").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a .rb extensioned file when a C-extension file of the same name is loaded" do
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.bundle"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.dylib"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.so"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.dll"
+ require_relative(@path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "does not load a C-extension file if a .rb extensioned file is already loaded" do
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.rb"
+ require_relative("#{@dir}/load_fixture").should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "loads a .rb extensioned file when passed a non-.rb extensioned path" do
+ require_relative("#{@dir}/load_fixture.ext").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ $LOADED_FEATURES.should include "#{@abs_dir}/load_fixture.ext.rb"
+ end
+
+ it "loads a .rb extensioned file when a complex-extensioned C-extension file of the same name is loaded" do
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.bundle"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.dylib"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.so"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.dll"
+ require_relative("#{@dir}/load_fixture.ext").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ $LOADED_FEATURES.should include "#{@abs_dir}/load_fixture.ext.rb"
+ end
+
+ it "does not load a C-extension file if a complex-extensioned .rb file is already loaded" do
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.rb"
+ require_relative("#{@dir}/load_fixture.ext").should be_false
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "($LOADED_FEATURES)" do
+ it "stores an absolute path" do
+ require_relative(@path).should be_true
+ $LOADED_FEATURES.should include(@abs_path)
+ end
+
+ platform_is_not :windows, :wasi do
+ describe "with symlinks" do
+ before :each do
+ @symlink_to_code_dir = tmp("codesymlink")
+ File.symlink(CODE_LOADING_DIR, @symlink_to_code_dir)
+ @symlink_basename = File.basename(@symlink_to_code_dir)
+ @requiring_file = tmp("requiring")
+ end
+
+ after :each do
+ rm_r @symlink_to_code_dir, @requiring_file
+ end
+
+ it "does not canonicalize the path and stores a path with symlinks" do
+ symlink_path = "#{@symlink_basename}/load_fixture.rb"
+ absolute_path = "#{tmp("")}#{symlink_path}"
+ canonical_path = "#{CODE_LOADING_DIR}/load_fixture.rb"
+ touch(@requiring_file) { |f|
+ f.puts "require_relative #{symlink_path.inspect}"
+ }
+ load(@requiring_file)
+ ScratchPad.recorded.should == [:loaded]
+
+ features = $LOADED_FEATURES.select { |path| path.end_with?('load_fixture.rb') }
+ features.should include(absolute_path)
+ features.should_not include(canonical_path)
+ end
+
+ it "stores the same path that __FILE__ returns in the required file" do
+ symlink_path = "#{@symlink_basename}/load_fixture_and__FILE__.rb"
+ touch(@requiring_file) { |f|
+ f.puts "require_relative #{symlink_path.inspect}"
+ }
+ load(@requiring_file)
+ loaded_feature = $LOADED_FEATURES.last
+ ScratchPad.recorded.should == [loaded_feature]
+ end
+ end
+ end
+
+ it "does not store the path if the load fails" do
+ saved_loaded_features = $LOADED_FEATURES.dup
+ -> { require_relative("#{@dir}/raise_fixture.rb") }.should raise_error(RuntimeError)
+ $LOADED_FEATURES.should == saved_loaded_features
+ end
+
+ it "does not load an absolute path that is already stored" do
+ $LOADED_FEATURES << @abs_path
+ require_relative(@path).should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "adds the suffix of the resolved filename" do
+ require_relative("#{@dir}/load_fixture").should be_true
+ $LOADED_FEATURES.should include("#{@abs_dir}/load_fixture.rb")
+ end
+
+ it "loads a path for a file already loaded with a relative path" do
+ $LOAD_PATH << File.expand_path(@dir)
+ $LOADED_FEATURES << "load_fixture.rb" << "load_fixture"
+ require_relative(@path).should be_true
+ $LOADED_FEATURES.should include(@abs_path)
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+end
+
+describe "Kernel#require_relative with an absolute path" do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ @dir = File.expand_path "../../fixtures/code", File.dirname(__FILE__)
+ @abs_dir = @dir
+ @path = File.join @dir, "load_fixture.rb"
+ @abs_path = @path
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ it "loads a path relative to the current file" do
+ require_relative(@path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a file defining many methods" do
+ require_relative("#{@dir}/methods_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "raises a LoadError if the file does not exist" do
+ -> { require_relative("#{@dir}/nonexistent.rb") }.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+
+ it "raises a LoadError if basepath does not exist" do
+ -> { eval("require_relative('#{@dir}/nonexistent.rb')") }.should raise_error(LoadError)
+ end
+
+ it "stores the missing path in a LoadError object" do
+ path = "#{@dir}/nonexistent.rb"
+
+ -> {
+ require_relative(path)
+ }.should(raise_error(LoadError) { |e|
+ e.path.should == File.expand_path(path, @abs_dir)
+ })
+ end
+
+ it "calls #to_str on non-String objects" do
+ name = mock("load_fixture.rb mock")
+ name.should_receive(:to_str).and_return(@path)
+ require_relative(name).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "raises a TypeError if argument does not respond to #to_str" do
+ -> { require_relative(nil) }.should raise_error(TypeError)
+ -> { require_relative(42) }.should raise_error(TypeError)
+ -> {
+ require_relative([@path,@path])
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that has #to_s but not #to_str" do
+ name = mock("load_fixture.rb mock")
+ name.stub!(:to_s).and_return(@path)
+ -> { require_relative(name) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ name = mock("#to_str returns nil")
+ name.should_receive(:to_str).at_least(1).times.and_return(nil)
+ -> { require_relative(name) }.should raise_error(TypeError)
+ end
+
+ it "calls #to_path on non-String objects" do
+ name = mock("load_fixture.rb mock")
+ name.should_receive(:to_path).and_return(@path)
+ require_relative(name).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "calls #to_str on non-String objects returned by #to_path" do
+ name = mock("load_fixture.rb mock")
+ to_path = mock("load_fixture_rb #to_path mock")
+ name.should_receive(:to_path).and_return(to_path)
+ to_path.should_receive(:to_str).and_return(@path)
+ require_relative(name).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ describe "(file extensions)" do
+ it "loads a .rb extensioned file when passed a non-extensioned path" do
+ require_relative("#{@dir}/load_fixture").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a .rb extensioned file when a C-extension file of the same name is loaded" do
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.bundle"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.dylib"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.so"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.dll"
+ require_relative(@path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "does not load a C-extension file if a .rb extensioned file is already loaded" do
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.rb"
+ require_relative("#{@dir}/load_fixture").should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "loads a .rb extensioned file when passed a non-.rb extensioned path" do
+ require_relative("#{@dir}/load_fixture.ext").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ $LOADED_FEATURES.should include "#{@abs_dir}/load_fixture.ext.rb"
+ end
+
+ it "loads a .rb extensioned file when a complex-extensioned C-extension file of the same name is loaded" do
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.bundle"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.dylib"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.so"
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.dll"
+ require_relative("#{@dir}/load_fixture.ext").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ $LOADED_FEATURES.should include "#{@abs_dir}/load_fixture.ext.rb"
+ end
+
+ it "does not load a C-extension file if a complex-extensioned .rb file is already loaded" do
+ $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.rb"
+ require_relative("#{@dir}/load_fixture.ext").should be_false
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "($LOAD_FEATURES)" do
+ it "stores an absolute path" do
+ require_relative(@path).should be_true
+ $LOADED_FEATURES.should include(@abs_path)
+ end
+
+ it "does not store the path if the load fails" do
+ saved_loaded_features = $LOADED_FEATURES.dup
+ -> { require_relative("#{@dir}/raise_fixture.rb") }.should raise_error(RuntimeError)
+ $LOADED_FEATURES.should == saved_loaded_features
+ end
+
+ it "does not load an absolute path that is already stored" do
+ $LOADED_FEATURES << @abs_path
+ require_relative(@path).should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "adds the suffix of the resolved filename" do
+ require_relative("#{@dir}/load_fixture").should be_true
+ $LOADED_FEATURES.should include("#{@abs_dir}/load_fixture.rb")
+ end
+
+ it "loads a path for a file already loaded with a relative path" do
+ $LOAD_PATH << File.expand_path(@dir)
+ $LOADED_FEATURES << "load_fixture.rb" << "load_fixture"
+ require_relative(@path).should be_true
+ $LOADED_FEATURES.should include(@abs_path)
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb
new file mode 100644
index 0000000000..dc3da4b7e6
--- /dev/null
+++ b/spec/ruby/core/kernel/require_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/code_loading'
+require_relative 'shared/require'
+
+describe "Kernel#require" do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ # if this fails, update your rubygems
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:require)
+ end
+
+ it_behaves_like :kernel_require_basic, :require, CodeLoadingSpecs::Method.new
+ it_behaves_like :kernel_require, :require, CodeLoadingSpecs::Method.new
+end
+
+describe "Kernel.require" do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ it_behaves_like :kernel_require_basic, :require, Kernel
+ it_behaves_like :kernel_require, :require, Kernel
+end
diff --git a/spec/ruby/core/kernel/respond_to_missing_spec.rb b/spec/ruby/core/kernel/respond_to_missing_spec.rb
new file mode 100644
index 0000000000..cc82031e26
--- /dev/null
+++ b/spec/ruby/core/kernel/respond_to_missing_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#respond_to_missing?" do
+ before :each do
+ @a = KernelSpecs::A.new
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:respond_to_missing?, false)
+ end
+
+ it "is only an instance method" do
+ Kernel.method(:respond_to_missing?).owner.should == Kernel
+ end
+
+ it "is not called when #respond_to? would return true" do
+ obj = mock('object')
+ obj.stub!(:glark)
+ obj.should_not_receive(:respond_to_missing?)
+ obj.respond_to?(:glark).should be_true
+ end
+
+ it "is called with a 2nd argument of false when #respond_to? is" do
+ obj = mock('object')
+ obj.should_receive(:respond_to_missing?).with(:undefined_method, false)
+ obj.respond_to?(:undefined_method, false)
+ end
+
+ it "is called a 2nd argument of false when #respond_to? is called with only 1 argument" do
+ obj = mock('object')
+ obj.should_receive(:respond_to_missing?).with(:undefined_method, false)
+ obj.respond_to?(:undefined_method)
+ end
+
+ it "is called with true as the second argument when #respond_to? is" do
+ obj = mock('object')
+ obj.should_receive(:respond_to_missing?).with(:undefined_method, true)
+ obj.respond_to?(:undefined_method, true)
+ end
+
+ it "is called when #respond_to? would return false" do
+ obj = mock('object')
+ obj.should_receive(:respond_to_missing?).with(:undefined_method, false)
+ obj.respond_to?(:undefined_method)
+ end
+
+ it "causes #respond_to? to return true if called and not returning false" do
+ obj = mock('object')
+ obj.should_receive(:respond_to_missing?).with(:undefined_method, false).and_return(:glark)
+ obj.respond_to?(:undefined_method).should be_true
+ end
+
+ it "causes #respond_to? to return false if called and returning false" do
+ obj = mock('object')
+ obj.should_receive(:respond_to_missing?).with(:undefined_method, false).and_return(false)
+ obj.respond_to?(:undefined_method).should be_false
+ end
+
+ it "causes #respond_to? to return false if called and returning nil" do
+ obj = mock('object')
+ obj.should_receive(:respond_to_missing?).with(:undefined_method, false).and_return(nil)
+ obj.respond_to?(:undefined_method).should be_false
+ end
+
+ it "isn't called when obj responds to the given public method" do
+ @a.should_not_receive(:respond_to_missing?)
+ @a.respond_to?(:pub_method).should be_true
+ end
+
+ it "isn't called when obj responds to the given public method, include_private = true" do
+ @a.should_not_receive(:respond_to_missing?)
+ @a.respond_to?(:pub_method, true).should be_true
+ end
+
+ it "is called when obj responds to the given protected method, include_private = false" do
+ @a.should_receive(:respond_to_missing?)
+ @a.respond_to?(:protected_method, false).should be_false
+ end
+
+ it "isn't called when obj responds to the given protected method, include_private = true" do
+ @a.should_not_receive(:respond_to_missing?)
+ @a.respond_to?(:protected_method, true).should be_true
+ end
+
+ it "is called when obj responds to the given private method, include_private = false" do
+ @a.should_receive(:respond_to_missing?).with(:private_method, false)
+ @a.respond_to?(:private_method)
+ end
+
+ it "isn't called when obj responds to the given private method, include_private = true" do
+ @a.should_not_receive(:respond_to_missing?)
+ @a.respond_to?(:private_method, true).should be_true
+ end
+
+ it "is called for missing class methods" do
+ @a.class.should_receive(:respond_to_missing?).with(:oOoOoO, false)
+ @a.class.respond_to?(:oOoOoO)
+ end
+end
diff --git a/spec/ruby/core/kernel/respond_to_spec.rb b/spec/ruby/core/kernel/respond_to_spec.rb
new file mode 100644
index 0000000000..5b3ea3f651
--- /dev/null
+++ b/spec/ruby/core/kernel/respond_to_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#respond_to?" do
+ before :each do
+ @a = KernelSpecs::A.new
+ end
+
+ it "is a public method" do
+ Kernel.should have_public_instance_method(:respond_to?, false)
+ end
+
+ it "is only an instance method" do
+ Kernel.method(:respond_to?).owner.should == Kernel
+ end
+
+ it "returns false if the given method was undefined" do
+ @a.respond_to?(:undefed_method).should == false
+ @a.respond_to?("undefed_method").should == false
+ end
+
+ it "returns true if obj responds to the given public method" do
+ @a.respond_to?(:pub_method).should == true
+ @a.respond_to?("pub_method").should == true
+ end
+
+ it "throws a type error if argument can't be coerced into a Symbol" do
+ -> { @a.respond_to?(Object.new) }.should raise_error(TypeError, /is not a symbol nor a string/)
+ end
+
+ it "returns false if obj responds to the given protected method" do
+ @a.respond_to?(:protected_method).should == false
+ @a.respond_to?("protected_method").should == false
+ end
+
+ it "returns false if obj responds to the given private method" do
+ @a.respond_to?(:private_method).should == false
+ @a.respond_to?("private_method").should == false
+ end
+
+ it "returns true if obj responds to the given protected method (include_private = true)" do
+ @a.respond_to?(:protected_method, true).should == true
+ @a.respond_to?("protected_method", true).should == true
+ end
+
+ it "returns false if obj responds to the given protected method (include_private = false)" do
+ @a.respond_to?(:protected_method, false).should == false
+ @a.respond_to?("protected_method", false).should == false
+ end
+
+ it "returns false even if obj responds to the given private method (include_private = false)" do
+ @a.respond_to?(:private_method, false).should == false
+ @a.respond_to?("private_method", false).should == false
+ end
+
+ it "returns true if obj responds to the given private method (include_private = true)" do
+ @a.respond_to?(:private_method, true).should == true
+ @a.respond_to?("private_method", true).should == true
+ end
+
+ it "does not change method visibility when finding private method" do
+ KernelSpecs::VisibilityChange.respond_to?(:new, false).should == false
+ KernelSpecs::VisibilityChange.respond_to?(:new, true).should == true
+ -> { KernelSpecs::VisibilityChange.new }.should raise_error(NoMethodError)
+ end
+
+ it "indicates if an object responds to a particular message" do
+ class KernelSpecs::Foo; def bar; 'done'; end; end
+ KernelSpecs::Foo.new.respond_to?(:bar).should == true
+ KernelSpecs::Foo.new.respond_to?(:invalid_and_silly_method_name).should == false
+ end
+end
diff --git a/spec/ruby/core/kernel/select_spec.rb b/spec/ruby/core/kernel/select_spec.rb
new file mode 100644
index 0000000000..e0d82f3079
--- /dev/null
+++ b/spec/ruby/core/kernel/select_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#select" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:select)
+ end
+end
+
+describe "Kernel.select" do
+ it 'does not block when timeout is 0' do
+ IO.pipe do |read, write|
+ IO.select([read], [], [], 0).should == nil
+ write.write 'data'
+ IO.select([read], [], [], 0).should == [[read], [], []]
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/send_spec.rb b/spec/ruby/core/kernel/send_spec.rb
new file mode 100644
index 0000000000..9a4d261964
--- /dev/null
+++ b/spec/ruby/core/kernel/send_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/basicobject/send'
+
+describe "Kernel#send" do
+ it "invokes the named public method" do
+ class KernelSpecs::Foo
+ def bar
+ 'done'
+ end
+ end
+ KernelSpecs::Foo.new.send(:bar).should == 'done'
+ end
+
+ it "invokes the named alias of a public method" do
+ class KernelSpecs::Foo
+ def bar
+ 'done'
+ end
+ alias :aka :bar
+ end
+ KernelSpecs::Foo.new.send(:aka).should == 'done'
+ end
+
+ it "invokes the named protected method" do
+ class KernelSpecs::Foo
+ protected
+ def bar
+ 'done'
+ end
+ end
+ KernelSpecs::Foo.new.send(:bar).should == 'done'
+ end
+
+ it "invokes the named private method" do
+ class KernelSpecs::Foo
+ private
+ def bar
+ 'done2'
+ end
+ end
+ KernelSpecs::Foo.new.send(:bar).should == 'done2'
+ end
+
+ it "invokes the named alias of a private method" do
+ class KernelSpecs::Foo
+ private
+ def bar
+ 'done2'
+ end
+ alias :aka :bar
+ end
+ KernelSpecs::Foo.new.send(:aka).should == 'done2'
+ end
+
+ it "invokes the named alias of a protected method" do
+ class KernelSpecs::Foo
+ protected
+ def bar
+ 'done2'
+ end
+ alias :aka :bar
+ end
+ KernelSpecs::Foo.new.send(:aka).should == 'done2'
+ end
+
+ it_behaves_like :basicobject_send, :send
+end
diff --git a/spec/ruby/core/kernel/set_trace_func_spec.rb b/spec/ruby/core/kernel/set_trace_func_spec.rb
new file mode 100644
index 0000000000..1f43e7a009
--- /dev/null
+++ b/spec/ruby/core/kernel/set_trace_func_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#set_trace_func" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:set_trace_func)
+ end
+end
+
+describe "Kernel.set_trace_func" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/shared/dup_clone.rb b/spec/ruby/core/kernel/shared/dup_clone.rb
new file mode 100644
index 0000000000..4fac6006e1
--- /dev/null
+++ b/spec/ruby/core/kernel/shared/dup_clone.rb
@@ -0,0 +1,91 @@
+class ObjectSpecDup
+ def initialize()
+ @obj = :original
+ end
+
+ attr_accessor :obj
+end
+
+class ObjectSpecDupInitCopy
+ def initialize()
+ @obj = :original
+ end
+
+ attr_accessor :obj, :original
+
+ def initialize_copy(original)
+ @obj = :init_copy
+ @original = original
+ end
+
+ private :initialize_copy
+end
+
+describe :kernel_dup_clone, shared: true do
+ it "returns a new object duplicated from the original" do
+ o = ObjectSpecDup.new
+ o2 = ObjectSpecDup.new
+
+ o.obj = 10
+
+ o3 = o.send(@method)
+
+ o3.obj.should == 10
+ o2.obj.should == :original
+ end
+
+ it "produces a shallow copy, contained objects are not recursively dupped" do
+ o = ObjectSpecDup.new
+ array = [1, 2]
+ o.obj = array
+
+ o2 = o.send(@method)
+ o2.obj.should equal(o.obj)
+ end
+
+ it "calls #initialize_copy on the NEW object if available, passing in original object" do
+ o = ObjectSpecDupInitCopy.new
+ o2 = o.send(@method)
+
+ o.obj.should == :original
+ o2.obj.should == :init_copy
+ o2.original.should equal(o)
+ end
+
+ it "does not preserve the object_id" do
+ o1 = ObjectSpecDupInitCopy.new
+ old_object_id = o1.object_id
+ o2 = o1.send(@method)
+ o2.object_id.should_not == old_object_id
+ end
+
+ it "returns nil for NilClass" do
+ nil.send(@method).should == nil
+ end
+
+ it "returns true for TrueClass" do
+ true.send(@method).should == true
+ end
+
+ it "returns false for FalseClass" do
+ false.send(@method).should == false
+ end
+
+ it "returns the same Integer for Integer" do
+ 1.send(@method).should == 1
+ end
+
+ it "returns the same Symbol for Symbol" do
+ :my_symbol.send(@method).should == :my_symbol
+ end
+
+ it "returns self for Complex" do
+ c = Complex(1.3, 3.1)
+ c.send(@method).should equal c
+ end
+
+ it "returns self for Rational" do
+ r = Rational(1, 3)
+ r.send(@method).should equal r
+ end
+end
diff --git a/spec/ruby/core/kernel/shared/kind_of.rb b/spec/ruby/core/kernel/shared/kind_of.rb
new file mode 100644
index 0000000000..aef6f1c1d8
--- /dev/null
+++ b/spec/ruby/core/kernel/shared/kind_of.rb
@@ -0,0 +1,55 @@
+require_relative '../fixtures/classes'
+
+describe :kernel_kind_of, shared: true do
+ before :each do
+ @o = KernelSpecs::KindaClass.new
+ end
+
+ it "returns true if given class is the object's class" do
+ @o.send(@method, KernelSpecs::KindaClass).should == true
+ end
+
+ it "returns true if given class is an ancestor of the object's class" do
+ @o.send(@method, KernelSpecs::AncestorClass).should == true
+ @o.send(@method, String).should == true
+ @o.send(@method, Object).should == true
+ end
+
+ it "returns false if the given class is not object's class nor an ancestor" do
+ @o.send(@method, Array).should == false
+ end
+
+ it "returns true if given a Module that is included in object's class" do
+ @o.send(@method, KernelSpecs::MyModule).should == true
+ end
+
+ it "returns true if given a Module that is included one of object's ancestors only" do
+ @o.send(@method, KernelSpecs::AncestorModule).should == true
+ end
+
+ it "returns true if given a Module that object has been extended with" do
+ @o.send(@method, KernelSpecs::MyExtensionModule).should == true
+ end
+
+ it "returns true if given a Module that object has been prepended with" do
+ @o.send(@method, KernelSpecs::MyPrependedModule).should == true
+ end
+
+ it "returns false if given a Module not included nor prepended in object's class nor ancestors" do
+ @o.send(@method, KernelSpecs::SomeOtherModule).should == false
+ end
+
+ it "raises a TypeError if given an object that is not a Class nor a Module" do
+ -> { @o.send(@method, 1) }.should raise_error(TypeError)
+ -> { @o.send(@method, 'KindaClass') }.should raise_error(TypeError)
+ -> { @o.send(@method, :KindaClass) }.should raise_error(TypeError)
+ -> { @o.send(@method, Object.new) }.should raise_error(TypeError)
+ end
+
+ it "does not take into account `class` method overriding" do
+ def @o.class; Integer; end
+
+ @o.send(@method, Integer).should == false
+ @o.send(@method, KernelSpecs::KindaClass).should == true
+ end
+end
diff --git a/spec/ruby/core/kernel/shared/lambda.rb b/spec/ruby/core/kernel/shared/lambda.rb
new file mode 100644
index 0000000000..c70640082a
--- /dev/null
+++ b/spec/ruby/core/kernel/shared/lambda.rb
@@ -0,0 +1,11 @@
+describe :kernel_lambda, shared: true do
+ it "returns a Proc object" do
+ send(@method) { true }.kind_of?(Proc).should == true
+ end
+
+ it "raises an ArgumentError when no block is given" do
+ suppress_warning do
+ -> { send(@method) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/shared/load.rb b/spec/ruby/core/kernel/shared/load.rb
new file mode 100644
index 0000000000..5c41c19bf6
--- /dev/null
+++ b/spec/ruby/core/kernel/shared/load.rb
@@ -0,0 +1,207 @@
+main = self
+
+describe :kernel_load, shared: true do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ @path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ it "loads a non-extensioned file as a Ruby source file" do
+ path = File.expand_path "load_fixture", CODE_LOADING_DIR
+ @object.load(path).should be_true
+ ScratchPad.recorded.should == [:no_ext]
+ end
+
+ it "loads a non .rb extensioned file as a Ruby source file" do
+ path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR
+ @object.load(path).should be_true
+ ScratchPad.recorded.should == [:no_rb_ext]
+ end
+
+ it "loads from the current working directory" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.load("load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+
+ it "loads a file that recursively requires itself" do
+ path = File.expand_path "recursive_require_fixture.rb", CODE_LOADING_DIR
+ -> {
+ @object.load(path).should be_true
+ }.should complain(/circular require considered harmful/, verbose: true)
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+
+ it "loads a file that recursively loads itself" do
+ path = File.expand_path "recursive_load_fixture.rb", CODE_LOADING_DIR
+ @object.load(path).should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+
+ it "loads a file each time the method is called" do
+ @object.load(@path).should be_true
+ @object.load(@path).should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+
+ it "loads a file even when the name appears in $LOADED_FEATURES" do
+ $LOADED_FEATURES << @path
+ @object.load(@path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a file that has been loaded by #require" do
+ @object.require(@path).should be_true
+ @object.load(@path).should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+
+ it "loads file even after $LOAD_PATH change" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.load("load_fixture.rb").should be_true
+ $LOAD_PATH.unshift CODE_LOADING_DIR + "/gem"
+ @object.load("load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded_gem]
+ end
+
+ it "does not cause #require with the same path to fail" do
+ @object.load(@path).should be_true
+ @object.require(@path).should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+
+ it "does not add the loaded path to $LOADED_FEATURES" do
+ saved_loaded_features = $LOADED_FEATURES.dup
+ @object.load(@path).should be_true
+ $LOADED_FEATURES.should == saved_loaded_features
+ end
+
+ it "raises a LoadError if passed a non-extensioned path that does not exist but a .rb extensioned path does exist" do
+ path = File.expand_path "load_ext_fixture", CODE_LOADING_DIR
+ -> { @object.load(path) }.should raise_error(LoadError)
+ end
+
+ describe "when passed true for 'wrap'" do
+ it "loads from an existing path" do
+ path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR
+ @object.load(path, true).should be_true
+ end
+
+ it "sets the enclosing scope to an anonymous module" do
+ path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR
+ @object.load(path, true)
+
+ Object.const_defined?(:LoadSpecWrap).should be_false
+
+ wrap_module = ScratchPad.recorded[1]
+ wrap_module.should be_an_instance_of(Module)
+ end
+
+ it "allows referencing outside namespaces" do
+ path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR
+ @object.load(path, true)
+
+ ScratchPad.recorded[0].should equal(String)
+ end
+
+ it "sets self as a copy of the top-level main" do
+ path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR
+ @object.load(path, true)
+
+ top_level = ScratchPad.recorded[2]
+ top_level.to_s.should == "main"
+ top_level.method(:to_s).owner.should == top_level.singleton_class
+ top_level.should_not equal(main)
+ top_level.should be_an_instance_of(Object)
+ end
+
+ it "includes modules included in main's singleton class in self's class" do
+ mod = Module.new
+ main.extend(mod)
+
+ main_ancestors = main.singleton_class.ancestors[1..-1]
+ main_ancestors.first.should == mod
+
+ path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR
+ @object.load(path, true)
+
+ top_level = ScratchPad.recorded[2]
+ top_level_ancestors = top_level.singleton_class.ancestors[-main_ancestors.size..-1]
+ top_level_ancestors.should == main_ancestors
+
+ wrap_module = ScratchPad.recorded[1]
+ top_level.singleton_class.ancestors.should == [top_level.singleton_class, wrap_module, *main_ancestors]
+ end
+
+ describe "with top-level methods" do
+ before :each do
+ path = File.expand_path "load_wrap_method_fixture.rb", CODE_LOADING_DIR
+ @object.load(path, true)
+ end
+
+ it "allows calling top-level methods" do
+ ScratchPad.recorded.last.should == :load_wrap_loaded
+ end
+
+ it "does not pollute the receiver" do
+ -> { @object.send(:top_level_method) }.should raise_error(NameError)
+ end
+ end
+ end
+
+ describe "when passed a module for 'wrap'" do
+ ruby_version_is "3.1" do
+ it "sets the enclosing scope to the supplied module" do
+ path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR
+ mod = Module.new
+ @object.load(path, mod)
+
+ Object.const_defined?(:LoadSpecWrap).should be_false
+ mod.const_defined?(:LoadSpecWrap).should be_true
+
+ wrap_module = ScratchPad.recorded[1]
+ wrap_module.should == mod
+ end
+
+ it "makes constants and instance methods in the source file reachable with the supplied module" do
+ path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR
+ mod = Module.new
+ @object.load(path, mod)
+
+ mod::LOAD_WRAP_SPECS_TOP_LEVEL_CONSTANT.should == 1
+ obj = Object.new
+ obj.extend(mod)
+ obj.send(:load_wrap_specs_top_level_method).should == :load_wrap_specs_top_level_method
+ end
+
+ it "makes instance methods in the source file private" do
+ path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR
+ mod = Module.new
+ @object.load(path, mod)
+
+ mod.private_instance_methods.include?(:load_wrap_specs_top_level_method).should == true
+ end
+ end
+ end
+
+ describe "(shell expansion)" do
+ before :each do
+ @env_home = ENV["HOME"]
+ ENV["HOME"] = CODE_LOADING_DIR
+ end
+
+ after :each do
+ ENV["HOME"] = @env_home
+ end
+
+ it "expands a tilde to the HOME environment variable as the path to load" do
+ @object.require("~/load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/shared/method.rb b/spec/ruby/core/kernel/shared/method.rb
new file mode 100644
index 0000000000..8b6ab23fd3
--- /dev/null
+++ b/spec/ruby/core/kernel/shared/method.rb
@@ -0,0 +1,56 @@
+require_relative '../../../spec_helper'
+
+describe :kernel_method, shared: true do
+ it "returns a method object for a valid method" do
+ class KernelSpecs::Foo; def bar; 'done'; end; end
+ m = KernelSpecs::Foo.new.send(@method, :bar)
+ m.should be_an_instance_of Method
+ m.call.should == 'done'
+ end
+
+ it "returns a method object for a valid singleton method" do
+ class KernelSpecs::Foo; def self.bar; 'class done'; end; end
+ m = KernelSpecs::Foo.send(@method, :bar)
+ m.should be_an_instance_of Method
+ m.call.should == 'class done'
+ end
+
+ it "returns a method object if respond_to_missing?(method) is true" do
+ m = KernelSpecs::RespondViaMissing.new.send(@method, :handled_publicly)
+ m.should be_an_instance_of Method
+ m.call(42).should == "Done handled_publicly([42])"
+ end
+
+ it "the returned method object if respond_to_missing?(method) calls #method_missing with a Symbol name" do
+ m = KernelSpecs::RespondViaMissing.new.send(@method, "handled_publicly")
+ m.should be_an_instance_of Method
+ m.call(42).should == "Done handled_publicly([42])"
+ end
+
+ it "raises a NameError for an invalid method name" do
+ class KernelSpecs::Foo; def bar; 'done'; end; end
+ -> {
+ KernelSpecs::Foo.new.send(@method, :invalid_and_silly_method_name)
+ }.should raise_error(NameError)
+ end
+
+ it "raises a NameError for an invalid singleton method name" do
+ class KernelSpecs::Foo; def self.bar; 'done'; end; end
+ -> { KernelSpecs::Foo.send(@method, :baz) }.should raise_error(NameError)
+ end
+
+ it "changes the method called for super on a target aliased method" do
+ c1 = Class.new do
+ def a; 'a'; end
+ def b; 'b'; end
+ end
+ c2 = Class.new(c1) do
+ def a; super; end
+ alias b a
+ end
+
+ c2.new.a.should == 'a'
+ c2.new.b.should == 'a'
+ c2.new.send(@method, :b).call.should == 'a'
+ end
+end
diff --git a/spec/ruby/core/kernel/shared/require.rb b/spec/ruby/core/kernel/shared/require.rb
new file mode 100644
index 0000000000..ae814aa317
--- /dev/null
+++ b/spec/ruby/core/kernel/shared/require.rb
@@ -0,0 +1,808 @@
+describe :kernel_require_basic, shared: true do
+ describe "(path resolution)" do
+ it "loads an absolute path" do
+ path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR
+ @object.send(@method, path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a non-canonical absolute path" do
+ path = File.join CODE_LOADING_DIR, "..", "code", "load_fixture.rb"
+ @object.send(@method, path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a file defining many methods" do
+ path = File.expand_path "methods_fixture.rb", CODE_LOADING_DIR
+ @object.send(@method, path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "raises a LoadError if the file does not exist" do
+ path = File.expand_path "nonexistent.rb", CODE_LOADING_DIR
+ File.should_not.exist?(path)
+ -> { @object.send(@method, path) }.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+
+ # Can't make a file unreadable on these platforms
+ platform_is_not :windows, :cygwin do
+ as_user do
+ describe "with an unreadable file" do
+ before :each do
+ @path = tmp("unreadable_file.rb")
+ touch @path
+ File.chmod 0000, @path
+ end
+
+ after :each do
+ File.chmod 0666, @path
+ rm_r @path
+ end
+
+ it "raises a LoadError" do
+ File.should.exist?(@path)
+ -> { @object.send(@method, @path) }.should raise_error(LoadError)
+ end
+ end
+ end
+ end
+
+ it "calls #to_str on non-String objects" do
+ path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR
+ name = mock("load_fixture.rb mock")
+ name.should_receive(:to_str).and_return(path)
+ @object.send(@method, name).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "raises a TypeError if passed nil" do
+ -> { @object.send(@method, nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an Integer" do
+ -> { @object.send(@method, 42) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an Array" do
+ -> { @object.send(@method, []) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that does not provide #to_str" do
+ -> { @object.send(@method, mock("not a filename")) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that has #to_s but not #to_str" do
+ name = mock("load_fixture.rb mock")
+ name.stub!(:to_s).and_return("load_fixture.rb")
+ $LOAD_PATH << "."
+ Dir.chdir CODE_LOADING_DIR do
+ -> { @object.send(@method, name) }.should raise_error(TypeError)
+ end
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ name = mock("#to_str returns nil")
+ name.should_receive(:to_str).at_least(1).times.and_return(nil)
+ -> { @object.send(@method, name) }.should raise_error(TypeError)
+ end
+
+ it "calls #to_path on non-String objects" do
+ name = mock("load_fixture.rb mock")
+ name.stub!(:to_path).and_return("load_fixture.rb")
+ $LOAD_PATH << "."
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, name).should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "calls #to_path on a String" do
+ path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR
+ str = mock("load_fixture.rb mock")
+ str.should_receive(:to_path).and_return(path)
+ @object.send(@method, str).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "calls #to_str on non-String objects returned by #to_path" do
+ path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR
+ name = mock("load_fixture.rb mock")
+ to_path = mock("load_fixture_rb #to_path mock")
+ name.should_receive(:to_path).and_return(to_path)
+ to_path.should_receive(:to_str).and_return(path)
+ @object.send(@method, name).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ # "http://redmine.ruby-lang.org/issues/show/2578"
+ it "loads a ./ relative path from the current working directory with empty $LOAD_PATH" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "./load_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a ../ relative path from the current working directory with empty $LOAD_PATH" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "../code/load_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a ./ relative path from the current working directory with non-empty $LOAD_PATH" do
+ $LOAD_PATH << "an_irrelevant_dir"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "./load_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a ../ relative path from the current working directory with non-empty $LOAD_PATH" do
+ $LOAD_PATH << "an_irrelevant_dir"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "../code/load_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a non-canonical path from the current working directory with non-empty $LOAD_PATH" do
+ $LOAD_PATH << "an_irrelevant_dir"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "../code/../code/load_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "resolves a filename against $LOAD_PATH entries" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.send(@method, "load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "accepts an Object with #to_path in $LOAD_PATH" do
+ obj = mock("to_path")
+ obj.should_receive(:to_path).at_least(:once).and_return(CODE_LOADING_DIR)
+ $LOAD_PATH << obj
+ @object.send(@method, "load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "does not require file twice after $LOAD_PATH change" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.require("load_fixture.rb").should be_true
+ $LOAD_PATH.push CODE_LOADING_DIR + "/gem"
+ @object.require("load_fixture.rb").should be_false
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "does not resolve a ./ relative path against $LOAD_PATH entries" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ -> do
+ @object.send(@method, "./load_fixture.rb")
+ end.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not resolve a ../ relative path against $LOAD_PATH entries" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ -> do
+ @object.send(@method, "../code/load_fixture.rb")
+ end.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+
+ it "resolves a non-canonical path against $LOAD_PATH entries" do
+ $LOAD_PATH << File.dirname(CODE_LOADING_DIR)
+ @object.send(@method, "code/../code/load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a path with duplicate path separators" do
+ $LOAD_PATH << "."
+ sep = File::Separator + File::Separator
+ path = ["..", "code", "load_fixture.rb"].join(sep)
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, path).should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+end
+
+describe :kernel_require, shared: true do
+ describe "(path resolution)" do
+ # For reference see [ruby-core:24155] in which matz confirms this feature is
+ # intentional for security reasons.
+ it "does not load a bare filename unless the current working directory is in $LOAD_PATH" do
+ Dir.chdir CODE_LOADING_DIR do
+ -> { @object.require("load_fixture.rb") }.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "does not load a relative path unless the current working directory is in $LOAD_PATH" do
+ Dir.chdir File.dirname(CODE_LOADING_DIR) do
+ -> do
+ @object.require("code/load_fixture.rb")
+ end.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "loads a file that recursively requires itself" do
+ path = File.expand_path "recursive_require_fixture.rb", CODE_LOADING_DIR
+ -> {
+ @object.require(path).should be_true
+ }.should complain(/circular require considered harmful/, verbose: true)
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ ruby_bug "#17340", ''...'3.3' do
+ it "loads a file concurrently" do
+ path = File.expand_path "concurrent_require_fixture.rb", CODE_LOADING_DIR
+ ScratchPad.record(@object)
+ -> {
+ @object.require(path)
+ }.should_not complain(/circular require considered harmful/, verbose: true)
+ ScratchPad.recorded.join
+ end
+ end
+ end
+
+ describe "(non-extensioned path)" do
+ before :each do
+ a = File.expand_path "a", CODE_LOADING_DIR
+ b = File.expand_path "b", CODE_LOADING_DIR
+ $LOAD_PATH.replace [a, b]
+ end
+
+ it "loads a .rb extensioned file when a C-extension file exists on an earlier load path" do
+ @object.require("load_fixture").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ ruby_bug "#16926", ""..."3.0" do
+ it "does not load a feature twice when $LOAD_PATH has been modified" do
+ $LOAD_PATH.replace [CODE_LOADING_DIR]
+ @object.require("load_fixture").should be_true
+ $LOAD_PATH.replace [File.expand_path("b", CODE_LOADING_DIR), CODE_LOADING_DIR]
+ @object.require("load_fixture").should be_false
+ end
+ end
+ end
+
+ describe "(file extensions)" do
+ it "loads a .rb extensioned file when passed a non-extensioned path" do
+ path = File.expand_path "load_fixture", CODE_LOADING_DIR
+ File.should.exist?(path)
+ @object.require(path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a .rb extensioned file when a C-extension file of the same name is loaded" do
+ $LOADED_FEATURES << File.expand_path("load_fixture.bundle", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.dylib", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.so", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.dll", CODE_LOADING_DIR)
+ path = File.expand_path "load_fixture", CODE_LOADING_DIR
+ @object.require(path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "does not load a C-extension file if a .rb extensioned file is already loaded" do
+ $LOADED_FEATURES << File.expand_path("load_fixture.rb", CODE_LOADING_DIR)
+ path = File.expand_path "load_fixture", CODE_LOADING_DIR
+ @object.require(path).should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "loads a .rb extensioned file when passed a non-.rb extensioned path" do
+ path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR
+ File.should.exist?(path)
+ @object.require(path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a .rb extensioned file when a complex-extensioned C-extension file of the same name is loaded" do
+ $LOADED_FEATURES << File.expand_path("load_fixture.ext.bundle", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.ext.dylib", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.ext.so", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.ext.dll", CODE_LOADING_DIR)
+ path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR
+ @object.require(path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "does not load a C-extension file if a complex-extensioned .rb file is already loaded" do
+ $LOADED_FEATURES << File.expand_path("load_fixture.ext.rb", CODE_LOADING_DIR)
+ path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR
+ @object.require(path).should be_false
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "($LOADED_FEATURES)" do
+ before :each do
+ @path = File.expand_path("load_fixture.rb", CODE_LOADING_DIR)
+ end
+
+ it "stores an absolute path" do
+ @object.require(@path).should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ platform_is_not :windows do
+ describe "with symlinks" do
+ before :each do
+ @symlink_to_code_dir = tmp("codesymlink")
+ File.symlink(CODE_LOADING_DIR, @symlink_to_code_dir)
+
+ $LOAD_PATH.delete(CODE_LOADING_DIR)
+ $LOAD_PATH.unshift(@symlink_to_code_dir)
+ end
+
+ after :each do
+ rm_r @symlink_to_code_dir
+ end
+
+ it "does not canonicalize the path and stores a path with symlinks" do
+ symlink_path = "#{@symlink_to_code_dir}/load_fixture.rb"
+ canonical_path = "#{CODE_LOADING_DIR}/load_fixture.rb"
+ @object.require(symlink_path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+
+ features = $LOADED_FEATURES.select { |path| path.end_with?('load_fixture.rb') }
+ features.should include(symlink_path)
+ features.should_not include(canonical_path)
+ end
+
+ it "stores the same path that __FILE__ returns in the required file" do
+ symlink_path = "#{@symlink_to_code_dir}/load_fixture_and__FILE__.rb"
+ @object.require(symlink_path).should be_true
+ loaded_feature = $LOADED_FEATURES.last
+ ScratchPad.recorded.should == [loaded_feature]
+ end
+
+ it "requires only once when a new matching file added to path" do
+ @object.require('load_fixture').should be_true
+ ScratchPad.recorded.should == [:loaded]
+
+ symlink_to_code_dir_two = tmp("codesymlinktwo")
+ File.symlink("#{CODE_LOADING_DIR}/b", symlink_to_code_dir_two)
+ begin
+ $LOAD_PATH.unshift(symlink_to_code_dir_two)
+
+ @object.require('load_fixture').should be_false
+ ensure
+ rm_r symlink_to_code_dir_two
+ end
+ end
+ end
+
+ describe "with symlinks in the required feature and $LOAD_PATH" do
+ before :each do
+ @dir = tmp("realdir")
+ mkdir_p @dir
+ @file = "#{@dir}/realfile.rb"
+ touch(@file) { |f| f.puts 'ScratchPad << __FILE__' }
+
+ @symlink_to_dir = tmp("symdir").freeze
+ File.symlink(@dir, @symlink_to_dir)
+ @symlink_to_file = "#{@dir}/symfile.rb"
+ File.symlink("realfile.rb", @symlink_to_file)
+ end
+
+ after :each do
+ rm_r @dir, @symlink_to_dir
+ end
+
+ it "canonicalizes the entry in $LOAD_PATH but not the filename passed to #require" do
+ $LOAD_PATH.unshift(@symlink_to_dir)
+ @object.require("symfile").should be_true
+ loaded_feature = "#{@dir}/symfile.rb"
+ ScratchPad.recorded.should == [loaded_feature]
+ $".last.should == loaded_feature
+ $LOAD_PATH[0].should == @symlink_to_dir
+ end
+ end
+ end
+
+ it "does not store the path if the load fails" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ saved_loaded_features = $LOADED_FEATURES.dup
+ -> { @object.require("raise_fixture.rb") }.should raise_error(RuntimeError)
+ $LOADED_FEATURES.should == saved_loaded_features
+ end
+
+ it "does not load an absolute path that is already stored" do
+ $LOADED_FEATURES << @path
+ @object.require(@path).should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not load a ./ relative path that is already stored" do
+ $LOADED_FEATURES << "./load_fixture.rb"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("./load_fixture.rb").should be_false
+ end
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not load a ../ relative path that is already stored" do
+ $LOADED_FEATURES << "../load_fixture.rb"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("../load_fixture.rb").should be_false
+ end
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not load a non-canonical path that is already stored" do
+ $LOADED_FEATURES << "code/../code/load_fixture.rb"
+ $LOAD_PATH << File.dirname(CODE_LOADING_DIR)
+ @object.require("code/../code/load_fixture.rb").should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "respects being replaced with a new array" do
+ prev = $LOADED_FEATURES.dup
+
+ @object.require(@path).should be_true
+ $LOADED_FEATURES.should include(@path)
+
+ $LOADED_FEATURES.replace(prev)
+
+ $LOADED_FEATURES.should_not include(@path)
+ @object.require(@path).should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "does not load twice the same file with and without extension" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.require("load_fixture.rb").should be_true
+ @object.require("load_fixture").should be_false
+ end
+
+ describe "when a non-extensioned file is in $LOADED_FEATURES" do
+ before :each do
+ $LOADED_FEATURES << "load_fixture"
+ end
+
+ it "loads a .rb extensioned file when a non extensioned file is in $LOADED_FEATURES" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.require("load_fixture").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a .rb extensioned file from a subdirectory" do
+ $LOAD_PATH << File.dirname(CODE_LOADING_DIR)
+ @object.require("code/load_fixture").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "returns false if the file is not found" do
+ Dir.chdir File.dirname(CODE_LOADING_DIR) do
+ @object.require("load_fixture").should be_false
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "returns false when passed a path and the file is not found" do
+ $LOADED_FEATURES << "code/load_fixture"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("code/load_fixture").should be_false
+ ScratchPad.recorded.should == []
+ end
+ end
+ end
+
+ it "stores ../ relative paths as absolute paths" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("../code/load_fixture.rb").should be_true
+ end
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "stores ./ relative paths as absolute paths" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("./load_fixture.rb").should be_true
+ end
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "collapses duplicate path separators" do
+ $LOAD_PATH << "."
+ sep = File::Separator + File::Separator
+ path = ["..", "code", "load_fixture.rb"].join(sep)
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require(path).should be_true
+ end
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "expands absolute paths containing .." do
+ path = File.join CODE_LOADING_DIR, "..", "code", "load_fixture.rb"
+ @object.require(path).should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "adds the suffix of the resolved filename" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.require("load_fixture").should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "does not load a non-canonical path for a file already loaded" do
+ $LOADED_FEATURES << @path
+ $LOAD_PATH << File.dirname(CODE_LOADING_DIR)
+ @object.require("code/../code/load_fixture.rb").should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not load a ./ relative path for a file already loaded" do
+ $LOADED_FEATURES << @path
+ $LOAD_PATH << "an_irrelevant_dir"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("./load_fixture.rb").should be_false
+ end
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not load a ../ relative path for a file already loaded" do
+ $LOADED_FEATURES << @path
+ $LOAD_PATH << "an_irrelevant_dir"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("../code/load_fixture.rb").should be_false
+ end
+ ScratchPad.recorded.should == []
+ end
+
+ provided = %w[complex enumerator rational thread]
+ provided << 'ruby2_keywords'
+
+ it "#{provided.join(', ')} are already required" do
+ features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems')
+ provided.each { |feature|
+ features.should =~ /\b#{feature}\.(rb|so|jar)$/
+ }
+
+ 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 "unicode_normalize is part of core and not $LOADED_FEATURES" do
+ features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems')
+ features.lines.each { |feature|
+ feature.should_not include("unicode_normalize")
+ }
+
+ -> { @object.require("unicode_normalize") }.should raise_error(LoadError)
+ end
+
+ ruby_version_is "3.0" do
+ it "does not load a file earlier on the $LOAD_PATH when other similar features were already loaded" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "../code/load_fixture").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+
+ $LOAD_PATH.unshift "#{CODE_LOADING_DIR}/b"
+ # This loads because the above load was not on the $LOAD_PATH
+ @object.send(@method, "load_fixture").should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded]
+
+ $LOAD_PATH.unshift "#{CODE_LOADING_DIR}/c"
+ # This does not load because the above load was on the $LOAD_PATH
+ @object.send(@method, "load_fixture").should be_false
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+ end
+ end
+
+ describe "(shell expansion)" do
+ before :each do
+ @path = File.expand_path("load_fixture.rb", CODE_LOADING_DIR)
+ @env_home = ENV["HOME"]
+ ENV["HOME"] = CODE_LOADING_DIR
+ end
+
+ after :each do
+ ENV["HOME"] = @env_home
+ end
+
+ # "#3171"
+ it "performs tilde expansion on a .rb file before storing paths in $LOADED_FEATURES" do
+ @object.require("~/load_fixture.rb").should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "performs tilde expansion on a non-extensioned file before storing paths in $LOADED_FEATURES" do
+ @object.require("~/load_fixture").should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+ end
+
+ describe "(concurrently)" do
+ before :each do
+ ScratchPad.record []
+ @path = File.expand_path "concurrent.rb", CODE_LOADING_DIR
+ @path2 = File.expand_path "concurrent2.rb", CODE_LOADING_DIR
+ @path3 = File.expand_path "concurrent3.rb", CODE_LOADING_DIR
+ end
+
+ after :each do
+ ScratchPad.clear
+ $LOADED_FEATURES.delete @path
+ $LOADED_FEATURES.delete @path2
+ $LOADED_FEATURES.delete @path3
+ end
+
+ # Quick note about these specs:
+ #
+ # The behavior we're spec'ing requires that t2 enter #require, see t1 is
+ # loading @path, grab a lock, and wait on it.
+ #
+ # We do make sure that t2 starts the require once t1 is in the middle
+ # of concurrent.rb, but we then need to get t2 to get far enough into #require
+ # to see t1's lock and try to lock it.
+ it "blocks a second thread from returning while the 1st is still requiring" do
+ fin = false
+
+ t1_res = nil
+ t2_res = nil
+
+ t2 = nil
+ t1 = Thread.new do
+ Thread.pass until t2
+ Thread.current[:wait_for] = t2
+ t1_res = @object.require(@path)
+ Thread.pass until fin
+ ScratchPad.recorded << :t1_post
+ end
+
+ t2 = Thread.new do
+ Thread.pass until t1[:in_concurrent_rb]
+ $VERBOSE, @verbose = nil, $VERBOSE
+ begin
+ t2_res = @object.require(@path)
+ ScratchPad.recorded << :t2_post
+ ensure
+ $VERBOSE = @verbose
+ fin = true
+ end
+ end
+
+ t1.join
+ t2.join
+
+ t1_res.should be_true
+ t2_res.should be_false
+
+ ScratchPad.recorded.should == [:con_pre, :con_post, :t2_post, :t1_post]
+ end
+
+ it "blocks based on the path" do
+ t1_res = nil
+ t2_res = nil
+
+ t2 = nil
+ t1 = Thread.new do
+ Thread.pass until t2
+ Thread.current[:concurrent_require_thread] = t2
+ t1_res = @object.require(@path2)
+ end
+
+ t2 = Thread.new do
+ Thread.pass until t1[:in_concurrent_rb2]
+ t2_res = @object.require(@path3)
+ end
+
+ t1.join
+ t2.join
+
+ t1_res.should be_true
+ t2_res.should be_true
+
+ ScratchPad.recorded.should == [:con2_pre, :con3, :con2_post]
+ end
+
+ it "allows a 2nd require if the 1st raised an exception" do
+ fin = false
+
+ t2_res = nil
+
+ t2 = nil
+ t1 = Thread.new do
+ Thread.pass until t2
+ Thread.current[:wait_for] = t2
+ Thread.current[:con_raise] = true
+
+ -> {
+ @object.require(@path)
+ }.should raise_error(RuntimeError)
+
+ Thread.pass until fin
+ ScratchPad.recorded << :t1_post
+ end
+
+ t2 = Thread.new do
+ Thread.pass until t1[:in_concurrent_rb]
+ $VERBOSE, @verbose = nil, $VERBOSE
+ begin
+ t2_res = @object.require(@path)
+ ScratchPad.recorded << :t2_post
+ ensure
+ $VERBOSE = @verbose
+ fin = true
+ end
+ end
+
+ t1.join
+ t2.join
+
+ t2_res.should be_true
+
+ ScratchPad.recorded.should == [:con_pre, :con_pre, :con_post, :t2_post, :t1_post]
+ end
+
+ # "redmine #5754"
+ it "blocks a 3rd require if the 1st raises an exception and the 2nd is still running" do
+ fin = false
+
+ t1_res = nil
+ t2_res = nil
+
+ raised = false
+
+ t2 = nil
+ t1 = Thread.new do
+ Thread.current[:con_raise] = true
+
+ -> {
+ @object.require(@path)
+ }.should raise_error(RuntimeError)
+
+ raised = true
+
+ # This hits the bug. Because MRI removes its internal lock from a table
+ # when the exception is raised, this #require doesn't see that t2 is in
+ # the middle of requiring the file, so this #require runs when it should not.
+ Thread.pass until t2 && t2[:in_concurrent_rb]
+ t1_res = @object.require(@path)
+
+ Thread.pass until fin
+ ScratchPad.recorded << :t1_post
+ end
+
+ t2 = Thread.new do
+ Thread.pass until raised
+ Thread.current[:wait_for] = t1
+ begin
+ t2_res = @object.require(@path)
+ ScratchPad.recorded << :t2_post
+ ensure
+ fin = true
+ end
+ end
+
+ t1.join
+ t2.join
+
+ t1_res.should be_false
+ t2_res.should be_true
+
+ ScratchPad.recorded.should == [:con_pre, :con_pre, :con_post, :t2_post, :t1_post]
+ end
+ end
+
+ it "stores the missing path in a LoadError object" do
+ path = "abcd1234"
+
+ -> {
+ @object.send(@method, path)
+ }.should raise_error(LoadError) { |e|
+ e.path.should == path
+ }
+ end
+end
diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb
new file mode 100644
index 0000000000..2db50bd686
--- /dev/null
+++ b/spec/ruby/core/kernel/shared/sprintf.rb
@@ -0,0 +1,993 @@
+describe :kernel_sprintf, shared: true do
+ describe "integer formats" do
+ it "converts argument into Integer with to_int" do
+ obj = Object.new
+ def obj.to_i; 10; end
+ def obj.to_int; 10; end
+
+ obj.should_receive(:to_int).and_return(10)
+ @method.call("%b", obj).should == "1010"
+ end
+
+ it "converts argument into Integer with to_i if to_int isn't available" do
+ obj = Object.new
+ def obj.to_i; 10; end
+
+ obj.should_receive(:to_i).and_return(10)
+ @method.call("%b", obj).should == "1010"
+ end
+
+ it "converts String argument with Kernel#Integer" do
+ @method.call("%d", "0b1010").should == "10"
+ @method.call("%d", "112").should == "112"
+ @method.call("%d", "0127").should == "87"
+ @method.call("%d", "0xc4").should == "196"
+ end
+
+ it "raises TypeError exception if cannot convert to Integer" do
+ -> {
+ @method.call("%b", Object.new)
+ }.should raise_error(TypeError)
+ end
+
+ ["b", "B"].each do |f|
+ describe f do
+ it "converts argument as a binary number" do
+ @method.call("%#{f}", 10).should == "1010"
+ end
+
+ it "displays negative number as a two's complement prefixed with '..1'" do
+ @method.call("%#{f}", -10).should == "..1" + "0110"
+ end
+
+ it "collapse negative number representation if it equals 1" do
+ @method.call("%#{f}", -1).should_not == "..11"
+ @method.call("%#{f}", -1).should == "..1"
+ end
+ end
+ end
+
+ ["d", "i", "u"].each do |f|
+ describe f do
+ it "converts argument as a decimal number" do
+ @method.call("%#{f}", 112).should == "112"
+ @method.call("%#{f}", -112).should == "-112"
+ end
+
+ it "works well with large numbers" do
+ @method.call("%#{f}", 1234567890987654321).should == "1234567890987654321"
+ end
+ end
+ end
+
+ describe "o" do
+ it "converts argument as an octal number" do
+ @method.call("%o", 87).should == "127"
+ end
+
+ it "displays negative number as a two's complement prefixed with '..7'" do
+ @method.call("%o", -87).should == "..7" + "651"
+ end
+
+ it "collapse negative number representation if it equals 7" do
+ @method.call("%o", -1).should_not == "..77"
+ @method.call("%o", -1).should == "..7"
+ end
+ end
+
+ describe "x" do
+ it "converts argument as a hexadecimal number" do
+ @method.call("%x", 196).should == "c4"
+ end
+
+ it "displays negative number as a two's complement prefixed with '..f'" do
+ @method.call("%x", -196).should == "..f" + "3c"
+ end
+
+ it "collapse negative number representation if it equals f" do
+ @method.call("%x", -1).should_not == "..ff"
+ @method.call("%x", -1).should == "..f"
+ end
+ end
+
+ describe "X" do
+ it "converts argument as a hexadecimal number with uppercase letters" do
+ @method.call("%X", 196).should == "C4"
+ end
+
+ it "displays negative number as a two's complement prefixed with '..f'" do
+ @method.call("%X", -196).should == "..F" + "3C"
+ end
+
+ it "collapse negative number representation if it equals F" do
+ @method.call("%X", -1).should_not == "..FF"
+ @method.call("%X", -1).should == "..F"
+ end
+ end
+ end
+
+ describe "float formats" do
+ it "converts argument into Float" do
+ obj = mock("float")
+ obj.should_receive(:to_f).and_return(9.6)
+ @method.call("%f", obj).should == "9.600000"
+ end
+
+ it "raises TypeError exception if cannot convert to Float" do
+ -> {
+ @method.call("%f", Object.new)
+ }.should raise_error(TypeError)
+ end
+
+ {"e" => "e", "E" => "E"}.each_pair do |f, exp|
+ describe f do
+ it "converts argument into exponential notation [-]d.dddddde[+-]dd" do
+ @method.call("%#{f}", 109.52).should == "1.095200#{exp}+02"
+ @method.call("%#{f}", -109.52).should == "-1.095200#{exp}+02"
+ @method.call("%#{f}", 0.10952).should == "1.095200#{exp}-01"
+ @method.call("%#{f}", -0.10952).should == "-1.095200#{exp}-01"
+ end
+
+ it "cuts excessive digits and keeps only 6 ones" do
+ @method.call("%#{f}", 1.123456789).should == "1.123457#{exp}+00"
+ end
+
+ it "rounds the last significant digit to the closest one" do
+ @method.call("%#{f}", 1.555555555).should == "1.555556#{exp}+00"
+ @method.call("%#{f}", -1.555555555).should == "-1.555556#{exp}+00"
+ @method.call("%#{f}", 1.444444444).should == "1.444444#{exp}+00"
+ end
+
+ it "displays Float::INFINITY as Inf" do
+ @method.call("%#{f}", Float::INFINITY).should == "Inf"
+ @method.call("%#{f}", -Float::INFINITY).should == "-Inf"
+ end
+
+ it "displays Float::NAN as NaN" do
+ @method.call("%#{f}", Float::NAN).should == "NaN"
+ @method.call("%#{f}", -Float::NAN).should == "NaN"
+ end
+ end
+ end
+
+ describe "f" do
+ it "converts floating point argument as [-]ddd.dddddd" do
+ @method.call("%f", 10.952).should == "10.952000"
+ @method.call("%f", -10.952).should == "-10.952000"
+ end
+
+ it "cuts excessive digits and keeps only 6 ones" do
+ @method.call("%f", 1.123456789).should == "1.123457"
+ end
+
+ it "rounds the last significant digit to the closest one" do
+ @method.call("%f", 1.555555555).should == "1.555556"
+ @method.call("%f", -1.555555555).should == "-1.555556"
+ @method.call("%f", 1.444444444).should == "1.444444"
+ end
+
+ it "displays Float::INFINITY as Inf" do
+ @method.call("%f", Float::INFINITY).should == "Inf"
+ @method.call("%f", -Float::INFINITY).should == "-Inf"
+ end
+
+ it "displays Float::NAN as NaN" do
+ @method.call("%f", Float::NAN).should == "NaN"
+ @method.call("%f", -Float::NAN).should == "NaN"
+ end
+ end
+
+ {"g" => "e", "G" => "E"}.each_pair do |f, exp|
+ describe f do
+ context "the exponent is less than -4" do
+ it "converts a floating point number using exponential form" do
+ @method.call("%#{f}", 0.0000123456).should == "1.23456#{exp}-05"
+ @method.call("%#{f}", -0.0000123456).should == "-1.23456#{exp}-05"
+
+ @method.call("%#{f}", 0.000000000123456).should == "1.23456#{exp}-10"
+ @method.call("%#{f}", -0.000000000123456).should == "-1.23456#{exp}-10"
+ end
+ end
+
+ context "the exponent is greater than or equal to the precision (6 by default)" do
+ it "converts a floating point number using exponential form" do
+ @method.call("%#{f}", 1234567).should == "1.23457#{exp}+06"
+ @method.call("%#{f}", 1234567890123).should == "1.23457#{exp}+12"
+ @method.call("%#{f}", -1234567).should == "-1.23457#{exp}+06"
+ end
+ end
+
+ context "otherwise" do
+ it "converts a floating point number in dd.dddd form" do
+ @method.call("%#{f}", 0.0001).should == "0.0001"
+ @method.call("%#{f}", -0.0001).should == "-0.0001"
+ @method.call("%#{f}", 123456).should == "123456"
+ @method.call("%#{f}", -123456).should == "-123456"
+ end
+
+ it "cuts excessive digits in fractional part and keeps only 4 ones" do
+ @method.call("%#{f}", 12.12341111).should == "12.1234"
+ @method.call("%#{f}", -12.12341111).should == "-12.1234"
+ end
+
+ it "rounds the last significant digit to the closest one in fractional part" do
+ @method.call("%#{f}", 1.555555555).should == "1.55556"
+ @method.call("%#{f}", -1.555555555).should == "-1.55556"
+ @method.call("%#{f}", 1.444444444).should == "1.44444"
+ end
+
+ it "cuts fraction part to have only 6 digits at all" do
+ @method.call("%#{f}", 1.1234567).should == "1.12346"
+ @method.call("%#{f}", 12.1234567).should == "12.1235"
+ @method.call("%#{f}", 123.1234567).should == "123.123"
+ @method.call("%#{f}", 1234.1234567).should == "1234.12"
+ @method.call("%#{f}", 12345.1234567).should == "12345.1"
+ @method.call("%#{f}", 123456.1234567).should == "123456"
+ end
+ end
+
+ it "displays Float::INFINITY as Inf" do
+ @method.call("%#{f}", Float::INFINITY).should == "Inf"
+ @method.call("%#{f}", -Float::INFINITY).should == "-Inf"
+ end
+
+ it "displays Float::NAN as NaN" do
+ @method.call("%#{f}", Float::NAN).should == "NaN"
+ @method.call("%#{f}", -Float::NAN).should == "NaN"
+ end
+ end
+ end
+
+ describe "a" do
+ it "converts floating point argument as [-]0xh.hhhhp[+-]dd" do
+ @method.call("%a", 196).should == "0x1.88p+7"
+ @method.call("%a", -196).should == "-0x1.88p+7"
+ @method.call("%a", 196.1).should == "0x1.8833333333333p+7"
+ @method.call("%a", 0.01).should == "0x1.47ae147ae147bp-7"
+ @method.call("%a", -0.01).should == "-0x1.47ae147ae147bp-7"
+ end
+
+ it "displays Float::INFINITY as Inf" do
+ @method.call("%a", Float::INFINITY).should == "Inf"
+ @method.call("%a", -Float::INFINITY).should == "-Inf"
+ end
+
+ it "displays Float::NAN as NaN" do
+ @method.call("%a", Float::NAN).should == "NaN"
+ @method.call("%a", -Float::NAN).should == "NaN"
+ end
+ end
+
+ describe "A" do
+ it "converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P" do
+ @method.call("%A", 196).should == "0X1.88P+7"
+ @method.call("%A", -196).should == "-0X1.88P+7"
+ @method.call("%A", 196.1).should == "0X1.8833333333333P+7"
+ @method.call("%A", 0.01).should == "0X1.47AE147AE147BP-7"
+ @method.call("%A", -0.01).should == "-0X1.47AE147AE147BP-7"
+ end
+
+ it "displays Float::INFINITY as Inf" do
+ @method.call("%A", Float::INFINITY).should == "Inf"
+ @method.call("%A", -Float::INFINITY).should == "-Inf"
+ end
+
+ it "displays Float::NAN as NaN" do
+ @method.call("%A", Float::NAN).should == "NaN"
+ @method.call("%A", -Float::NAN).should == "NaN"
+ end
+ end
+ end
+
+ describe "other formats" do
+ describe "c" do
+ it "displays character if argument is a numeric code of character" do
+ @method.call("%c", 97).should == "a"
+ end
+
+ it "displays character if argument is a single character string" do
+ @method.call("%c", "a").should == "a"
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "raises ArgumentError if argument is a string of several characters" do
+ -> {
+ @method.call("%c", "abc")
+ }.should raise_error(ArgumentError, /%c requires a character/)
+ end
+
+ it "raises ArgumentError if argument is an empty string" do
+ -> {
+ @method.call("%c", "")
+ }.should raise_error(ArgumentError, /%c requires a character/)
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "displays only the first character if argument is a string of several characters" do
+ @method.call("%c", "abc").should == "a"
+ end
+
+ it "displays no characters if argument is an empty string" do
+ @method.call("%c", "").should == ""
+ end
+ end
+
+ it "raises TypeError if argument is not String or Integer and cannot be converted to them" do
+ -> {
+ @method.call("%c", [])
+ }.should raise_error(TypeError, /no implicit conversion of Array into Integer/)
+ end
+
+ it "raises TypeError if argument is nil" do
+ -> {
+ @method.call("%c", nil)
+ }.should raise_error(TypeError, /no implicit conversion from nil to integer/)
+ end
+
+ it "tries to convert argument to String with to_str" do
+ obj = BasicObject.new
+ def obj.to_str
+ "a"
+ end
+
+ @method.call("%c", obj).should == "a"
+ end
+
+ it "tries to convert argument to Integer with to_int" do
+ obj = BasicObject.new
+ def obj.to_int
+ 90
+ end
+
+ @method.call("%c", obj).should == "Z"
+ end
+
+ it "raises TypeError if converting to String with to_str returns non-String" do
+ obj = BasicObject.new
+ def obj.to_str
+ :foo
+ end
+
+ -> {
+ @method.call("%c", obj)
+ }.should raise_error(TypeError, /can't convert BasicObject to String/)
+ end
+
+ it "raises TypeError if converting to Integer with to_int returns non-Integer" do
+ obj = BasicObject.new
+ def obj.to_str
+ :foo
+ end
+
+ -> {
+ @method.call("%c", obj)
+ }.should raise_error(TypeError, /can't convert BasicObject to String/)
+ end
+ end
+
+ describe "p" do
+ it "displays argument.inspect value" do
+ obj = mock("object")
+ obj.should_receive(:inspect).and_return("<inspect-result>")
+ @method.call("%p", obj).should == "<inspect-result>"
+ end
+ end
+
+ describe "s" do
+ it "substitute argument passes as a string" do
+ @method.call("%s", "abc").should == "abc"
+ end
+
+ it "substitutes '' for nil" do
+ @method.call("%s", nil).should == ""
+ end
+
+ it "converts argument to string with to_s" do
+ obj = mock("string")
+ obj.should_receive(:to_s).and_return("abc")
+ @method.call("%s", obj).should == "abc"
+ end
+
+ it "does not try to convert with to_str" do
+ obj = BasicObject.new
+ def obj.to_str
+ "abc"
+ end
+
+ -> {
+ @method.call("%s", obj)
+ }.should raise_error(NoMethodError)
+ end
+
+ it "formats a partial substring without including omitted characters" do
+ long_string = "aabbccddhelloddccbbaa"
+ sub_string = long_string[8, 5]
+ sprintf("%.#{1 * 3}s", sub_string).should == "hel"
+ end
+
+ it "formats string with precision" do
+ Kernel.format("%.3s", "hello").should == "hel"
+ Kernel.format("%-3.3s", "hello").should == "hel"
+ end
+
+ it "formats string with width" do
+ @method.call("%6s", "abc").should == " abc"
+ @method.call("%6s", "abcdefg").should == "abcdefg"
+ end
+
+ it "formats string with width and precision" do
+ @method.call("%4.6s", "abc").should == " abc"
+ @method.call("%4.6s", "abcdefg").should == "abcdef"
+ end
+
+ it "formats nil with width" do
+ @method.call("%6s", nil).should == " "
+ end
+
+ it "formats nil with precision" do
+ @method.call("%.6s", nil).should == ""
+ end
+
+ it "formats nil with width and precision" do
+ @method.call("%4.6s", nil).should == " "
+ end
+
+ it "formats multibyte string with precision" do
+ Kernel.format("%.2s", "été").should == "ét"
+ end
+
+ it "preserves encoding of the format string" do
+ str = format('%s'.encode(Encoding::UTF_8), 'foobar')
+ str.encoding.should == Encoding::UTF_8
+
+ str = format('%s'.encode(Encoding::US_ASCII), 'foobar')
+ str.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ describe "%" do
+ it "alone raises an ArgumentError" do
+ -> {
+ @method.call("%")
+ }.should raise_error(ArgumentError)
+ end
+
+ it "is escaped by %" do
+ @method.call("%%").should == "%"
+ @method.call("%%d", 10).should == "%d"
+ end
+ end
+ end
+
+ describe "flags" do
+ describe "space" do
+ context "applies to numeric formats bBdiouxXeEfgGaA" do
+ it "leaves a space at the start of non-negative numbers" do
+ @method.call("% b", 10).should == " 1010"
+ @method.call("% B", 10).should == " 1010"
+ @method.call("% d", 112).should == " 112"
+ @method.call("% i", 112).should == " 112"
+ @method.call("% o", 87).should == " 127"
+ @method.call("% u", 112).should == " 112"
+ @method.call("% x", 196).should == " c4"
+ @method.call("% X", 196).should == " C4"
+
+ @method.call("% e", 109.52).should == " 1.095200e+02"
+ @method.call("% E", 109.52).should == " 1.095200E+02"
+ @method.call("% f", 10.952).should == " 10.952000"
+ @method.call("% g", 12.1234).should == " 12.1234"
+ @method.call("% G", 12.1234).should == " 12.1234"
+ @method.call("% a", 196).should == " 0x1.88p+7"
+ @method.call("% A", 196).should == " 0X1.88P+7"
+ end
+
+ it "does not leave a space at the start of negative numbers" do
+ @method.call("% b", -10).should == "-1010"
+ @method.call("% B", -10).should == "-1010"
+ @method.call("% d", -112).should == "-112"
+ @method.call("% i", -112).should == "-112"
+ @method.call("% o", -87).should == "-127"
+ @method.call("% u", -112).should == "-112"
+ @method.call("% x", -196).should == "-c4"
+ @method.call("% X", -196).should == "-C4"
+
+ @method.call("% e", -109.52).should == "-1.095200e+02"
+ @method.call("% E", -109.52).should == "-1.095200E+02"
+ @method.call("% f", -10.952).should == "-10.952000"
+ @method.call("% g", -12.1234).should == "-12.1234"
+ @method.call("% G", -12.1234).should == "-12.1234"
+ @method.call("% a", -196).should == "-0x1.88p+7"
+ @method.call("% A", -196).should == "-0X1.88P+7"
+ end
+
+ it "prevents converting negative argument to two's complement form" do
+ @method.call("% b", -10).should == "-1010"
+ @method.call("% B", -10).should == "-1010"
+ @method.call("% o", -87).should == "-127"
+ @method.call("% x", -196).should == "-c4"
+ @method.call("% X", -196).should == "-C4"
+ end
+
+ it "treats several white spaces as one" do
+ @method.call("% b", 10).should == " 1010"
+ @method.call("% B", 10).should == " 1010"
+ @method.call("% d", 112).should == " 112"
+ @method.call("% i", 112).should == " 112"
+ @method.call("% o", 87).should == " 127"
+ @method.call("% u", 112).should == " 112"
+ @method.call("% x", 196).should == " c4"
+ @method.call("% X", 196).should == " C4"
+
+ @method.call("% e", 109.52).should == " 1.095200e+02"
+ @method.call("% E", 109.52).should == " 1.095200E+02"
+ @method.call("% f", 10.952).should == " 10.952000"
+ @method.call("% g", 12.1234).should == " 12.1234"
+ @method.call("% G", 12.1234).should == " 12.1234"
+ @method.call("% a", 196).should == " 0x1.88p+7"
+ @method.call("% A", 196).should == " 0X1.88P+7"
+ end
+ end
+ end
+
+ describe "(digit)$" do
+ it "specifies the absolute argument number for this field" do
+ @method.call("%2$b", 0, 10).should == "1010"
+ @method.call("%2$B", 0, 10).should == "1010"
+ @method.call("%2$d", 0, 112).should == "112"
+ @method.call("%2$i", 0, 112).should == "112"
+ @method.call("%2$o", 0, 87).should == "127"
+ @method.call("%2$u", 0, 112).should == "112"
+ @method.call("%2$x", 0, 196).should == "c4"
+ @method.call("%2$X", 0, 196).should == "C4"
+
+ @method.call("%2$e", 0, 109.52).should == "1.095200e+02"
+ @method.call("%2$E", 0, 109.52).should == "1.095200E+02"
+ @method.call("%2$f", 0, 10.952).should == "10.952000"
+ @method.call("%2$g", 0, 12.1234).should == "12.1234"
+ @method.call("%2$G", 0, 12.1234).should == "12.1234"
+ @method.call("%2$a", 0, 196).should == "0x1.88p+7"
+ @method.call("%2$A", 0, 196).should == "0X1.88P+7"
+
+ @method.call("%2$c", 1, 97).should == "a"
+ @method.call("%2$p", "a", []).should == "[]"
+ @method.call("%2$s", "-", "abc").should == "abc"
+ end
+
+ it "raises exception if argument number is bigger than actual arguments list" do
+ -> {
+ @method.call("%4$d", 1, 2, 3)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "ignores '-' sign" do
+ @method.call("%2$d", 1, 2, 3).should == "2"
+ @method.call("%-2$d", 1, 2, 3).should == "2"
+ end
+
+ it "raises ArgumentError exception when absolute and relative argument numbers are mixed" do
+ -> {
+ @method.call("%1$d %d", 1, 2)
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "#" do
+ context "applies to format o" do
+ it "increases the precision until the first digit will be `0' if it is not formatted as complements" do
+ @method.call("%#o", 87).should == "0127"
+ end
+
+ it "does nothing for negative argument" do
+ @method.call("%#o", -87).should == "..7651"
+ end
+ end
+
+ context "applies to formats bBxX" do
+ it "prefixes the result with 0x, 0X, 0b and 0B respectively for non-zero argument" do
+ @method.call("%#b", 10).should == "0b1010"
+ @method.call("%#b", -10).should == "0b..10110"
+ @method.call("%#B", 10).should == "0B1010"
+ @method.call("%#B", -10).should == "0B..10110"
+
+ @method.call("%#x", 196).should == "0xc4"
+ @method.call("%#x", -196).should == "0x..f3c"
+ @method.call("%#X", 196).should == "0XC4"
+ @method.call("%#X", -196).should == "0X..F3C"
+ end
+
+ it "does nothing for zero argument" do
+ @method.call("%#b", 0).should == "0"
+ @method.call("%#B", 0).should == "0"
+
+ @method.call("%#o", 0).should == "0"
+
+ @method.call("%#x", 0).should == "0"
+ @method.call("%#X", 0).should == "0"
+ end
+ end
+
+ context "applies to formats aAeEfgG" do
+ it "forces a decimal point to be added, even if no digits follow" do
+ @method.call("%#.0a", 16.25).should == "0x1.p+4"
+ @method.call("%#.0A", 16.25).should == "0X1.P+4"
+
+ @method.call("%#.0e", 100).should == "1.e+02"
+ @method.call("%#.0E", 100).should == "1.E+02"
+
+ @method.call("%#.0f", 123.4).should == "123."
+
+ @method.call("%#g", 123456).should == "123456."
+ @method.call("%#G", 123456).should == "123456."
+ end
+
+ it "changes format from dd.dddd to exponential form for gG" do
+ @method.call("%#.0g", 123.4).should_not == "123."
+ @method.call("%#.0g", 123.4).should == "1.e+02"
+ end
+ end
+
+ context "applies to gG" do
+ it "does not remove trailing zeros" do
+ @method.call("%#g", 123.4).should == "123.400"
+ @method.call("%#g", 123.4).should == "123.400"
+ end
+ end
+ end
+
+ describe "+" do
+ context "applies to numeric formats bBdiouxXaAeEfgG" do
+ it "adds a leading plus sign to non-negative numbers" do
+ @method.call("%+b", 10).should == "+1010"
+ @method.call("%+B", 10).should == "+1010"
+ @method.call("%+d", 112).should == "+112"
+ @method.call("%+i", 112).should == "+112"
+ @method.call("%+o", 87).should == "+127"
+ @method.call("%+u", 112).should == "+112"
+ @method.call("%+x", 196).should == "+c4"
+ @method.call("%+X", 196).should == "+C4"
+
+ @method.call("%+e", 109.52).should == "+1.095200e+02"
+ @method.call("%+E", 109.52).should == "+1.095200E+02"
+ @method.call("%+f", 10.952).should == "+10.952000"
+ @method.call("%+g", 12.1234).should == "+12.1234"
+ @method.call("%+G", 12.1234).should == "+12.1234"
+ @method.call("%+a", 196).should == "+0x1.88p+7"
+ @method.call("%+A", 196).should == "+0X1.88P+7"
+ end
+
+ it "does not use two's complement form for negative numbers for formats bBoxX" do
+ @method.call("%+b", -10).should == "-1010"
+ @method.call("%+B", -10).should == "-1010"
+ @method.call("%+o", -87).should == "-127"
+ @method.call("%+x", -196).should == "-c4"
+ @method.call("%+X", -196).should == "-C4"
+ end
+ end
+ end
+
+ describe "-" do
+ it "left-justifies the result of conversion if width is specified" do
+ @method.call("%-10b", 10).should == "1010 "
+ @method.call("%-10B", 10).should == "1010 "
+ @method.call("%-10d", 112).should == "112 "
+ @method.call("%-10i", 112).should == "112 "
+ @method.call("%-10o", 87).should == "127 "
+ @method.call("%-10u", 112).should == "112 "
+ @method.call("%-10x", 196).should == "c4 "
+ @method.call("%-10X", 196).should == "C4 "
+
+ @method.call("%-20e", 109.52).should == "1.095200e+02 "
+ @method.call("%-20E", 109.52).should == "1.095200E+02 "
+ @method.call("%-20f", 10.952).should == "10.952000 "
+ @method.call("%-20g", 12.1234).should == "12.1234 "
+ @method.call("%-20G", 12.1234).should == "12.1234 "
+ @method.call("%-20a", 196).should == "0x1.88p+7 "
+ @method.call("%-20A", 196).should == "0X1.88P+7 "
+
+ @method.call("%-10c", 97).should == "a "
+ @method.call("%-10p", []).should == "[] "
+ @method.call("%-10s", "abc").should == "abc "
+ end
+ end
+
+ describe "0 (zero)" do
+ context "applies to numeric formats bBdiouxXaAeEfgG and width is specified" do
+ it "pads with zeros, not spaces" do
+ @method.call("%010b", 10).should == "0000001010"
+ @method.call("%010B", 10).should == "0000001010"
+ @method.call("%010d", 112).should == "0000000112"
+ @method.call("%010i", 112).should == "0000000112"
+ @method.call("%010o", 87).should == "0000000127"
+ @method.call("%010u", 112).should == "0000000112"
+ @method.call("%010x", 196).should == "00000000c4"
+ @method.call("%010X", 196).should == "00000000C4"
+
+ @method.call("%020e", 109.52).should == "000000001.095200e+02"
+ @method.call("%020E", 109.52).should == "000000001.095200E+02"
+ @method.call("%020f", 10.952).should == "0000000000010.952000"
+ @method.call("%020g", 12.1234).should == "000000000000012.1234"
+ @method.call("%020G", 12.1234).should == "000000000000012.1234"
+ @method.call("%020a", 196).should == "0x000000000001.88p+7"
+ @method.call("%020A", 196).should == "0X000000000001.88P+7"
+ end
+
+ it "uses radix-1 when displays negative argument as a two's complement" do
+ @method.call("%010b", -10).should == "..11110110"
+ @method.call("%010B", -10).should == "..11110110"
+ @method.call("%010o", -87).should == "..77777651"
+ @method.call("%010x", -196).should == "..ffffff3c"
+ @method.call("%010X", -196).should == "..FFFFFF3C"
+ end
+ end
+ end
+
+ describe "*" do
+ it "uses the previous argument as the field width" do
+ @method.call("%*b", 10, 10).should == " 1010"
+ @method.call("%*B", 10, 10).should == " 1010"
+ @method.call("%*d", 10, 112).should == " 112"
+ @method.call("%*i", 10, 112).should == " 112"
+ @method.call("%*o", 10, 87).should == " 127"
+ @method.call("%*u", 10, 112).should == " 112"
+ @method.call("%*x", 10, 196).should == " c4"
+ @method.call("%*X", 10, 196).should == " C4"
+
+ @method.call("%*e", 20, 109.52).should == " 1.095200e+02"
+ @method.call("%*E", 20, 109.52).should == " 1.095200E+02"
+ @method.call("%*f", 20, 10.952).should == " 10.952000"
+ @method.call("%*g", 20, 12.1234).should == " 12.1234"
+ @method.call("%*G", 20, 12.1234).should == " 12.1234"
+ @method.call("%*a", 20, 196).should == " 0x1.88p+7"
+ @method.call("%*A", 20, 196).should == " 0X1.88P+7"
+
+ @method.call("%*c", 10, 97).should == " a"
+ @method.call("%*p", 10, []).should == " []"
+ @method.call("%*s", 10, "abc").should == " abc"
+ end
+
+ it "left-justifies the result if width is negative" do
+ @method.call("%*b", -10, 10).should == "1010 "
+ @method.call("%*B", -10, 10).should == "1010 "
+ @method.call("%*d", -10, 112).should == "112 "
+ @method.call("%*i", -10, 112).should == "112 "
+ @method.call("%*o", -10, 87).should == "127 "
+ @method.call("%*u", -10, 112).should == "112 "
+ @method.call("%*x", -10, 196).should == "c4 "
+ @method.call("%*X", -10, 196).should == "C4 "
+
+ @method.call("%*e", -20, 109.52).should == "1.095200e+02 "
+ @method.call("%*E", -20, 109.52).should == "1.095200E+02 "
+ @method.call("%*f", -20, 10.952).should == "10.952000 "
+ @method.call("%*g", -20, 12.1234).should == "12.1234 "
+ @method.call("%*G", -20, 12.1234).should == "12.1234 "
+ @method.call("%*a", -20, 196).should == "0x1.88p+7 "
+ @method.call("%*A", -20, 196).should == "0X1.88P+7 "
+
+ @method.call("%*c", -10, 97).should == "a "
+ @method.call("%*p", -10, []).should == "[] "
+ @method.call("%*s", -10, "abc").should == "abc "
+ end
+
+ it "uses the specified argument as the width if * is followed by a number and $" do
+ @method.call("%1$*2$b", 10, 10).should == " 1010"
+ @method.call("%1$*2$B", 10, 10).should == " 1010"
+ @method.call("%1$*2$d", 112, 10).should == " 112"
+ @method.call("%1$*2$i", 112, 10).should == " 112"
+ @method.call("%1$*2$o", 87, 10).should == " 127"
+ @method.call("%1$*2$u", 112, 10).should == " 112"
+ @method.call("%1$*2$x", 196, 10).should == " c4"
+ @method.call("%1$*2$X", 196, 10).should == " C4"
+
+ @method.call("%1$*2$e", 109.52, 20).should == " 1.095200e+02"
+ @method.call("%1$*2$E", 109.52, 20).should == " 1.095200E+02"
+ @method.call("%1$*2$f", 10.952, 20).should == " 10.952000"
+ @method.call("%1$*2$g", 12.1234, 20).should == " 12.1234"
+ @method.call("%1$*2$G", 12.1234, 20).should == " 12.1234"
+ @method.call("%1$*2$a", 196, 20).should == " 0x1.88p+7"
+ @method.call("%1$*2$A", 196, 20).should == " 0X1.88P+7"
+
+ @method.call("%1$*2$c", 97, 10).should == " a"
+ @method.call("%1$*2$p", [], 10).should == " []"
+ @method.call("%1$*2$s", "abc", 10).should == " abc"
+ end
+
+ it "left-justifies the result if specified with $ argument is negative" do
+ @method.call("%1$*2$b", 10, -10).should == "1010 "
+ @method.call("%1$*2$B", 10, -10).should == "1010 "
+ @method.call("%1$*2$d", 112, -10).should == "112 "
+ @method.call("%1$*2$i", 112, -10).should == "112 "
+ @method.call("%1$*2$o", 87, -10).should == "127 "
+ @method.call("%1$*2$u", 112, -10).should == "112 "
+ @method.call("%1$*2$x", 196, -10).should == "c4 "
+ @method.call("%1$*2$X", 196, -10).should == "C4 "
+
+ @method.call("%1$*2$e", 109.52, -20).should == "1.095200e+02 "
+ @method.call("%1$*2$E", 109.52, -20).should == "1.095200E+02 "
+ @method.call("%1$*2$f", 10.952, -20).should == "10.952000 "
+ @method.call("%1$*2$g", 12.1234, -20).should == "12.1234 "
+ @method.call("%1$*2$G", 12.1234, -20).should == "12.1234 "
+ @method.call("%1$*2$a", 196, -20).should == "0x1.88p+7 "
+ @method.call("%1$*2$A", 196, -20).should == "0X1.88P+7 "
+
+ @method.call("%1$*2$c", 97, -10).should == "a "
+ @method.call("%1$*2$p", [], -10).should == "[] "
+ @method.call("%1$*2$s", "abc", -10).should == "abc "
+ end
+
+ it "raises ArgumentError when is mixed with width" do
+ -> {
+ @method.call("%*10d", 10, 112)
+ }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe "width" do
+ it "specifies the minimum number of characters that will be written to the result" do
+ @method.call("%10b", 10).should == " 1010"
+ @method.call("%10B", 10).should == " 1010"
+ @method.call("%10d", 112).should == " 112"
+ @method.call("%10i", 112).should == " 112"
+ @method.call("%10o", 87).should == " 127"
+ @method.call("%10u", 112).should == " 112"
+ @method.call("%10x", 196).should == " c4"
+ @method.call("%10X", 196).should == " C4"
+
+ @method.call("%20e", 109.52).should == " 1.095200e+02"
+ @method.call("%20E", 109.52).should == " 1.095200E+02"
+ @method.call("%20f", 10.952).should == " 10.952000"
+ @method.call("%20g", 12.1234).should == " 12.1234"
+ @method.call("%20G", 12.1234).should == " 12.1234"
+ @method.call("%20a", 196).should == " 0x1.88p+7"
+ @method.call("%20A", 196).should == " 0X1.88P+7"
+
+ @method.call("%10c", 97).should == " a"
+ @method.call("%10p", []).should == " []"
+ @method.call("%10s", "abc").should == " abc"
+ end
+
+ it "is ignored if argument's actual length is greater" do
+ @method.call("%5d", 1234567890).should == "1234567890"
+ end
+ end
+
+ describe "precision" do
+ context "integer types" do
+ it "controls the number of decimal places displayed" do
+ @method.call("%.6b", 10).should == "001010"
+ @method.call("%.6B", 10).should == "001010"
+ @method.call("%.5d", 112).should == "00112"
+ @method.call("%.5i", 112).should == "00112"
+ @method.call("%.5o", 87).should == "00127"
+ @method.call("%.5u", 112).should == "00112"
+
+ @method.call("%.5x", 196).should == "000c4"
+ @method.call("%.5X", 196).should == "000C4"
+ end
+ end
+
+ context "float types" do
+ it "controls the number of decimal places displayed in fraction part" do
+ @method.call("%.10e", 109.52).should == "1.0952000000e+02"
+ @method.call("%.10E", 109.52).should == "1.0952000000E+02"
+ @method.call("%.10f", 10.952).should == "10.9520000000"
+ @method.call("%.10a", 196).should == "0x1.8800000000p+7"
+ @method.call("%.10A", 196).should == "0X1.8800000000P+7"
+ end
+
+ it "does not affect G format" do
+ @method.call("%.10g", 12.1234).should == "12.1234"
+ @method.call("%.10g", 123456789).should == "123456789"
+ end
+ end
+
+ context "string formats" do
+ it "determines the maximum number of characters to be copied from the string" do
+ @method.call("%.1p", [1]).should == "["
+ @method.call("%.2p", [1]).should == "[1"
+ @method.call("%.10p", [1]).should == "[1]"
+ @method.call("%.0p", [1]).should == ""
+
+ @method.call("%.1s", "abc").should == "a"
+ @method.call("%.2s", "abc").should == "ab"
+ @method.call("%.10s", "abc").should == "abc"
+ @method.call("%.0s", "abc").should == ""
+ end
+ end
+ end
+
+ describe "reference by name" do
+ describe "%<name>s style" do
+ it "uses value passed in a hash argument" do
+ @method.call("%<foo>d", foo: 123).should == "123"
+ end
+
+ it "supports flags, width, precision and type" do
+ @method.call("%+20.10<foo>f", foo: 10.952).should == " +10.9520000000"
+ end
+
+ it "allows to place name in any position" do
+ @method.call("%+15.5<foo>f", foo: 10.952).should == " +10.95200"
+ @method.call("%+15<foo>.5f", foo: 10.952).should == " +10.95200"
+ @method.call("%+<foo>15.5f", foo: 10.952).should == " +10.95200"
+ @method.call("%<foo>+15.5f", foo: 10.952).should == " +10.95200"
+ end
+
+ it "cannot be mixed with unnamed style" do
+ -> {
+ @method.call("%d %<foo>d", 1, foo: "123")
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "%{name} style" do
+ it "uses value passed in a hash argument" do
+ @method.call("%{foo}", foo: 123).should == "123"
+ end
+
+ it "does not support type style" do
+ @method.call("%{foo}d", foo: 123).should == "123d"
+ end
+
+ it "supports flags, width and precision" do
+ @method.call("%-20.5{foo}", foo: "123456789").should == "12345 "
+ end
+
+ it "cannot be mixed with unnamed style" do
+ -> {
+ @method.call("%d %{foo}", 1, foo: "123")
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises KeyError when there is no matching key" do
+ -> {
+ @method.call("%{foo}", {})
+ }.should raise_error(KeyError)
+ end
+
+ it "converts value to String with to_s" do
+ obj = Object.new
+ def obj.to_s; end
+ def obj.to_str; end
+
+ obj.should_receive(:to_s).and_return("42")
+ obj.should_not_receive(:to_str)
+
+ @method.call("%{foo}", foo: obj).should == "42"
+ end
+ end
+ end
+
+ describe "faulty key" do
+ before :each do
+ @object = { foooo: 1 }
+ end
+
+ it "raises a KeyError" do
+ -> {
+ @method.call("%<foo>s", @object)
+ }.should raise_error(KeyError)
+ end
+
+ it "sets the Hash as the receiver of KeyError" do
+ -> {
+ @method.call("%<foo>s", @object)
+ }.should raise_error(KeyError) { |err|
+ err.receiver.should equal(@object)
+ }
+ end
+
+ it "sets the unmatched key as the key of KeyError" do
+ -> {
+ @method.call("%<foo>s", @object)
+ }.should raise_error(KeyError) { |err|
+ err.key.to_s.should == 'foo'
+ }
+ end
+ end
+
+ it "does not raise error when passed more arguments than needed" do
+ sprintf("%s %d %c", "string", 2, "c", []).should == "string 2 c"
+ end
+end
diff --git a/spec/ruby/core/kernel/shared/sprintf_encoding.rb b/spec/ruby/core/kernel/shared/sprintf_encoding.rb
new file mode 100644
index 0000000000..9cedb8b662
--- /dev/null
+++ b/spec/ruby/core/kernel/shared/sprintf_encoding.rb
@@ -0,0 +1,67 @@
+# Keep encoding-related specs in a separate shared example to be able to skip them in IO/File/StringIO specs.
+# It's difficult to check result's encoding in the test after writing to a file/io buffer.
+describe :kernel_sprintf_encoding, shared: true do
+ it "can produce a string with valid encoding" do
+ string = @method.call("good day %{valid}", valid: "e")
+ string.encoding.should == Encoding::UTF_8
+ string.valid_encoding?.should be_true
+ end
+
+ it "can produce a string with invalid encoding" do
+ string = @method.call("good day %{invalid}", invalid: "\x80")
+ string.encoding.should == Encoding::UTF_8
+ string.valid_encoding?.should be_false
+ end
+
+ it "returns a String in the same encoding as the format String if compatible" do
+ string = "%s".force_encoding(Encoding::KOI8_U)
+ result = @method.call(string, "dogs")
+ result.encoding.should equal(Encoding::KOI8_U)
+ end
+
+ it "returns a String in the argument's encoding if format encoding is more restrictive" do
+ string = "foo %s".force_encoding(Encoding::US_ASCII)
+ argument = "b\303\274r".force_encoding(Encoding::UTF_8)
+
+ result = @method.call(string, argument)
+ result.encoding.should equal(Encoding::UTF_8)
+ end
+
+ it "raises Encoding::CompatibilityError if both encodings are ASCII compatible and there are not ASCII characters" do
+ string = "Ä %s".encode('windows-1252')
+ argument = "Ђ".encode('windows-1251')
+
+ -> {
+ @method.call(string, argument)
+ }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ describe "%c" do
+ it "supports Unicode characters" do
+ result = @method.call("%c", 1286)
+ result.should == "Ô†"
+ result.bytes.should == [212, 134]
+
+ result = @method.call("%c", "Ø´")
+ result.should == "Ø´"
+ result.bytes.should == [216, 180]
+ end
+
+ it "raises error when a codepoint isn't representable in an encoding of a format string" do
+ format = "%c".encode("ASCII")
+
+ -> {
+ @method.call(format, 1286)
+ }.should raise_error(RangeError, /out of char range/)
+ end
+
+ it "uses the encoding of the format string to interpret codepoints" do
+ format = "%c".force_encoding("euc-jp")
+ result = @method.call(format, 9415601)
+
+ result.encoding.should == Encoding::EUC_JP
+ result.should == "é".encode(Encoding::EUC_JP)
+ result.bytes.should == [143, 171, 177]
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/shared/then.rb b/spec/ruby/core/kernel/shared/then.rb
new file mode 100644
index 0000000000..b52075371f
--- /dev/null
+++ b/spec/ruby/core/kernel/shared/then.rb
@@ -0,0 +1,20 @@
+describe :kernel_then, shared: true do
+ it "yields self" do
+ object = Object.new
+ object.send(@method) { |o| o.should equal object }
+ end
+
+ it "returns the block return value" do
+ object = Object.new
+ object.send(@method) { 42 }.should equal 42
+ end
+
+ it "returns a sized Enumerator when no block given" do
+ object = Object.new
+ enum = object.send(@method)
+ enum.should be_an_instance_of Enumerator
+ enum.size.should equal 1
+ enum.peek.should equal object
+ enum.first.should equal object
+ end
+end
diff --git a/spec/ruby/core/kernel/singleton_class_spec.rb b/spec/ruby/core/kernel/singleton_class_spec.rb
new file mode 100644
index 0000000000..c56fa08cc1
--- /dev/null
+++ b/spec/ruby/core/kernel/singleton_class_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#singleton_class" do
+ it "returns class extended from an object" do
+ x = Object.new
+ xs = class << x; self; end
+ xs.should == x.singleton_class
+ end
+
+ it "returns NilClass for nil" do
+ nil.singleton_class.should == NilClass
+ end
+
+ it "returns TrueClass for true" do
+ true.singleton_class.should == TrueClass
+ end
+
+ it "returns FalseClass for false" do
+ false.singleton_class.should == FalseClass
+ end
+
+ it "raises TypeError for Integer" do
+ -> { 123.singleton_class }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError for Symbol" do
+ -> { :foo.singleton_class }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/kernel/singleton_method_spec.rb b/spec/ruby/core/kernel/singleton_method_spec.rb
new file mode 100644
index 0000000000..0bdf125ad8
--- /dev/null
+++ b/spec/ruby/core/kernel/singleton_method_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#singleton_method" do
+ it "find a method defined on the singleton class" do
+ obj = Object.new
+ def obj.foo; end
+ obj.singleton_method(:foo).should be_an_instance_of(Method)
+ end
+
+ it "returns a Method which can be called" do
+ obj = Object.new
+ def obj.foo; 42; end
+ obj.singleton_method(:foo).call.should == 42
+ end
+
+ it "only looks at singleton methods and not at methods in the class" do
+ klass = Class.new do
+ def foo
+ 42
+ end
+ end
+ obj = klass.new
+ obj.foo.should == 42
+ -> {
+ obj.singleton_method(:foo)
+ }.should raise_error(NameError) { |e|
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+
+ it "raises a NameError if there is no such method" do
+ obj = Object.new
+ -> {
+ obj.singleton_method(:not_existing)
+ }.should raise_error(NameError) { |e|
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+end
diff --git a/spec/ruby/core/kernel/singleton_methods_spec.rb b/spec/ruby/core/kernel/singleton_methods_spec.rb
new file mode 100644
index 0000000000..a127a439de
--- /dev/null
+++ b/spec/ruby/core/kernel/singleton_methods_spec.rb
@@ -0,0 +1,192 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/reflection'
+require_relative 'fixtures/classes'
+
+describe :kernel_singleton_methods, shared: true do
+ it "returns an empty Array for an object with no singleton methods" do
+ ReflectSpecs.o.singleton_methods(*@object).should == []
+ end
+
+ it "returns the names of module methods for a module" do
+ ReflectSpecs::M.singleton_methods(*@object).should include(:ms_pro, :ms_pub)
+ end
+
+ it "does not return private module methods for a module" do
+ ReflectSpecs::M.singleton_methods(*@object).should_not include(:ms_pri)
+ end
+
+ it "returns the names of class methods for a class" do
+ ReflectSpecs::A.singleton_methods(*@object).should include(:as_pro, :as_pub)
+ end
+
+ it "does not return private class methods for a class" do
+ ReflectSpecs::A.singleton_methods(*@object).should_not include(:as_pri)
+ end
+
+ it "returns the names of singleton methods for an object" do
+ ReflectSpecs.os.singleton_methods(*@object).should include(:os_pro, :os_pub)
+ end
+end
+
+describe :kernel_singleton_methods_modules, shared: true do
+ it "does not return any included methods for a module including a module" do
+ ReflectSpecs::N.singleton_methods(*@object).should include(:ns_pro, :ns_pub)
+ end
+
+ it "does not return any included methods for a class including a module" do
+ ReflectSpecs::D.singleton_methods(*@object).should include(:ds_pro, :ds_pub)
+ end
+
+ it "for a module does not return methods in a module prepended to Module itself" do
+ require_relative 'fixtures/singleton_methods'
+ mod = SingletonMethodsSpecs::SelfExtending
+ mod.method(:mspec_test_kernel_singleton_methods).owner.should == SingletonMethodsSpecs::Prepended
+
+ ancestors = mod.singleton_class.ancestors
+ ancestors[0...2].should == [ mod.singleton_class, mod ]
+ ancestors.should include(SingletonMethodsSpecs::Prepended)
+
+ # Do not search prepended modules of `Module`, as that's a non-singleton class
+ mod.singleton_methods.should == []
+ end
+end
+
+describe :kernel_singleton_methods_supers, shared: true do
+ it "returns the names of singleton methods for an object extended with a module" do
+ ReflectSpecs.oe.singleton_methods(*@object).should include(:m_pro, :m_pub)
+ end
+
+ it "returns a unique list for an object extended with a module" do
+ m = ReflectSpecs.oed.singleton_methods(*@object)
+ r = m.select { |x| x == :pub or x == :pro }.sort
+ r.should == [:pro, :pub]
+ end
+
+ it "returns the names of singleton methods for an object extended with two modules" do
+ ReflectSpecs.oee.singleton_methods(*@object).should include(:m_pro, :m_pub, :n_pro, :n_pub)
+ end
+
+ it "returns the names of singleton methods for an object extended with a module including a module" do
+ ReflectSpecs.oei.singleton_methods(*@object).should include(:n_pro, :n_pub, :m_pro, :m_pub)
+ end
+
+ it "returns the names of inherited singleton methods for a subclass" do
+ ReflectSpecs::B.singleton_methods(*@object).should include(:as_pro, :as_pub, :bs_pro, :bs_pub)
+ end
+
+ it "returns a unique list for a subclass" do
+ m = ReflectSpecs::B.singleton_methods(*@object)
+ r = m.select { |x| x == :pub or x == :pro }.sort
+ r.should == [:pro, :pub]
+ end
+
+ it "returns the names of inherited singleton methods for a subclass including a module" do
+ ReflectSpecs::C.singleton_methods(*@object).should include(:as_pro, :as_pub, :cs_pro, :cs_pub)
+ end
+
+ it "returns a unique list for a subclass including a module" do
+ m = ReflectSpecs::C.singleton_methods(*@object)
+ r = m.select { |x| x == :pub or x == :pro }.sort
+ r.should == [:pro, :pub]
+ end
+
+ it "returns the names of inherited singleton methods for a subclass of a class including a module" do
+ ReflectSpecs::E.singleton_methods(*@object).should include(:ds_pro, :ds_pub, :es_pro, :es_pub)
+ end
+
+ it "returns the names of inherited singleton methods for a subclass of a class that includes a module, where the subclass also includes a module" do
+ ReflectSpecs::F.singleton_methods(*@object).should include(:ds_pro, :ds_pub, :fs_pro, :fs_pub)
+ end
+
+ it "returns the names of inherited singleton methods for a class extended with a module" do
+ ReflectSpecs::P.singleton_methods(*@object).should include(:m_pro, :m_pub)
+ end
+end
+
+describe :kernel_singleton_methods_private_supers, shared: true do
+ it "does not return private singleton methods for an object extended with a module" do
+ ReflectSpecs.oe.singleton_methods(*@object).should_not include(:m_pri)
+ end
+
+ it "does not return private singleton methods for an object extended with two modules" do
+ ReflectSpecs.oee.singleton_methods(*@object).should_not include(:m_pri)
+ end
+
+ it "does not return private singleton methods for an object extended with a module including a module" do
+ ReflectSpecs.oei.singleton_methods(*@object).should_not include(:n_pri, :m_pri)
+ end
+
+ it "does not return private singleton methods for a class extended with a module" do
+ ReflectSpecs::P.singleton_methods(*@object).should_not include(:m_pri)
+ end
+
+ it "does not return private inherited singleton methods for a module including a module" do
+ ReflectSpecs::N.singleton_methods(*@object).should_not include(:ns_pri)
+ end
+
+ it "does not return private inherited singleton methods for a class including a module" do
+ ReflectSpecs::D.singleton_methods(*@object).should_not include(:ds_pri)
+ end
+
+ it "does not return private inherited singleton methods for a subclass" do
+ ReflectSpecs::B.singleton_methods(*@object).should_not include(:as_pri, :bs_pri)
+ end
+
+ it "does not return private inherited singleton methods for a subclass including a module" do
+ ReflectSpecs::C.singleton_methods(*@object).should_not include(:as_pri, :cs_pri)
+ end
+
+ it "does not return private inherited singleton methods for a subclass of a class including a module" do
+ ReflectSpecs::E.singleton_methods(*@object).should_not include(:ds_pri, :es_pri)
+ end
+
+ it "does not return private inherited singleton methods for a subclass of a class that includes a module, where the subclass also includes a module" do
+ ReflectSpecs::F.singleton_methods(*@object).should_not include(:ds_pri, :fs_pri)
+ end
+end
+
+describe "Kernel#singleton_methods" do
+ describe "when not passed an argument" do
+ it_behaves_like :kernel_singleton_methods, nil, []
+ it_behaves_like :kernel_singleton_methods_supers, nil, []
+ it_behaves_like :kernel_singleton_methods_modules, nil, []
+ it_behaves_like :kernel_singleton_methods_private_supers, nil, []
+ end
+
+ describe "when passed true" do
+ it_behaves_like :kernel_singleton_methods, nil, true
+ it_behaves_like :kernel_singleton_methods_supers, nil, true
+ it_behaves_like :kernel_singleton_methods_modules, nil, true
+ it_behaves_like :kernel_singleton_methods_private_supers, nil, true
+ end
+
+ describe "when passed false" do
+ it_behaves_like :kernel_singleton_methods, nil, false
+ it_behaves_like :kernel_singleton_methods_modules, nil, false
+ it_behaves_like :kernel_singleton_methods_private_supers, nil, false
+
+ it "returns an empty Array for an object extended with a module" do
+ ReflectSpecs.oe.singleton_methods(false).should == []
+ end
+
+ it "returns an empty Array for an object extended with two modules" do
+ ReflectSpecs.oee.singleton_methods(false).should == []
+ end
+
+ it "returns an empty Array for an object extended with a module including a module" do
+ ReflectSpecs.oei.singleton_methods(false).should == []
+ end
+
+ it "returns the names of singleton methods of the subclass" do
+ ReflectSpecs::B.singleton_methods(false).should include(:bs_pro, :bs_pub)
+ end
+
+ it "does not return names of inherited singleton methods for a subclass" do
+ ReflectSpecs::B.singleton_methods(false).should_not include(:as_pro, :as_pub)
+ end
+
+ it "does not return the names of inherited singleton methods for a class extended with a module" do
+ ReflectSpecs::P.singleton_methods(false).should_not include(:m_pro, :m_pub)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/sleep_spec.rb b/spec/ruby/core/kernel/sleep_spec.rb
new file mode 100644
index 0000000000..32da6344c1
--- /dev/null
+++ b/spec/ruby/core/kernel/sleep_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#sleep" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:sleep)
+ end
+
+ it "returns an Integer" do
+ sleep(0.001).should be_kind_of(Integer)
+ end
+
+ it "accepts a Float" do
+ sleep(0.001).should >= 0
+ end
+
+ it "accepts an Integer" do
+ sleep(0).should >= 0
+ end
+
+ it "accepts a Rational" do
+ sleep(Rational(1, 999)).should >= 0
+ end
+
+ it "accepts any Object that reponds to divmod" do
+ o = Object.new
+ def o.divmod(*); [0, 0.001]; end
+ sleep(o).should >= 0
+ end
+
+ it "raises an ArgumentError when passed a negative duration" do
+ -> { sleep(-0.1) }.should raise_error(ArgumentError)
+ -> { sleep(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { sleep(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { sleep('2') }.should raise_error(TypeError)
+ end
+
+ it "pauses execution indefinitely if not given a duration" do
+ running = false
+ t = Thread.new do
+ running = true
+ sleep
+ 5
+ end
+
+ Thread.pass until running
+ Thread.pass while t.status and t.status != "sleep"
+
+ t.wakeup
+ t.value.should == 5
+ end
+end
+
+describe "Kernel.sleep" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/spawn_spec.rb b/spec/ruby/core/kernel/spawn_spec.rb
new file mode 100644
index 0000000000..ba05b629d5
--- /dev/null
+++ b/spec/ruby/core/kernel/spawn_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# These specs only run a basic usage of #spawn.
+# Process.spawn has more complete specs and they are not
+# run here as it is redundant and takes too long for little gain.
+describe "Kernel#spawn" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:spawn)
+ end
+
+ it "executes the given command" do
+ -> {
+ Process.wait spawn("echo spawn")
+ }.should output_to_fd("spawn\n")
+ end
+end
+
+describe "Kernel.spawn" do
+ it "executes the given command" do
+ -> {
+ Process.wait Kernel.spawn("echo spawn")
+ }.should output_to_fd("spawn\n")
+ end
+end
diff --git a/spec/ruby/core/kernel/sprintf_spec.rb b/spec/ruby/core/kernel/sprintf_spec.rb
new file mode 100644
index 0000000000..7adf71be76
--- /dev/null
+++ b/spec/ruby/core/kernel/sprintf_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/sprintf'
+require_relative 'shared/sprintf_encoding'
+
+describe "Kernel#sprintf" do
+ it_behaves_like :kernel_sprintf, -> format, *args {
+ sprintf(format, *args)
+ }
+
+ it_behaves_like :kernel_sprintf_encoding, -> format, *args {
+ sprintf(format, *args)
+ }
+end
+
+describe "Kernel.sprintf" do
+ it_behaves_like :kernel_sprintf, -> format, *args {
+ Kernel.sprintf(format, *args)
+ }
+
+ it_behaves_like :kernel_sprintf_encoding, -> format, *args {
+ Kernel.sprintf(format, *args)
+ }
+end
diff --git a/spec/ruby/core/kernel/srand_spec.rb b/spec/ruby/core/kernel/srand_spec.rb
new file mode 100644
index 0000000000..95bb406f46
--- /dev/null
+++ b/spec/ruby/core/kernel/srand_spec.rb
@@ -0,0 +1,73 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#srand" do
+ before :each do
+ @seed = srand
+ end
+
+ after :each do
+ srand(@seed)
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:srand)
+ end
+
+ it "returns the previous seed value" do
+ srand(10)
+ srand(20).should == 10
+ end
+
+ it "returns the system-initialized seed value on the first call" do
+ ruby_exe('print srand(10)', options: '--disable-gems').should =~ /\A\d+\z/
+ end
+
+ it "seeds the RNG correctly and repeatably" do
+ srand(10)
+ x = rand
+ srand(10)
+ rand.should == x
+ end
+
+ it "defaults number to a random value" do
+ -> { srand }.should_not raise_error
+ srand.should_not == 0
+ end
+
+ it "accepts and uses a seed of 0" do
+ srand(0)
+ srand.should == 0
+ end
+
+ it "accepts a negative seed" do
+ srand(-17)
+ srand.should == -17
+ end
+
+ it "accepts an Integer as a seed" do
+ srand(0x12345678901234567890)
+ srand.should == 0x12345678901234567890
+ end
+
+ it "calls #to_int on seed" do
+ srand(3.8)
+ srand.should == 3
+
+ s = mock('seed')
+ s.should_receive(:to_int).and_return 0
+ srand(s)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { srand(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { srand("7") }.should raise_error(TypeError)
+ end
+end
+
+describe "Kernel.srand" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/sub_spec.rb b/spec/ruby/core/kernel/sub_spec.rb
new file mode 100644
index 0000000000..9130bd159c
--- /dev/null
+++ b/spec/ruby/core/kernel/sub_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# FIXME: These methods exist only when the -n or -p option is passed to
+# ruby, but we currently don't have a way of specifying that.
+ruby_version_is ""..."1.9" do
+ describe "Kernel#sub" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:sub)
+ end
+ end
+
+ describe "Kernel#sub!" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:sub!)
+ end
+ end
+
+ describe "Kernel.sub" do
+ it "needs to be reviewed for spec completeness"
+ end
+
+ describe "Kernel.sub!" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/core/kernel/syscall_spec.rb b/spec/ruby/core/kernel/syscall_spec.rb
new file mode 100644
index 0000000000..32d07b3ae2
--- /dev/null
+++ b/spec/ruby/core/kernel/syscall_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#syscall" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:syscall)
+ end
+end
+
+describe "Kernel.syscall" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/system_spec.rb b/spec/ruby/core/kernel/system_spec.rb
new file mode 100644
index 0000000000..9671a650cc
--- /dev/null
+++ b/spec/ruby/core/kernel/system_spec.rb
@@ -0,0 +1,115 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :kernel_system, shared: true do
+ it "executes the specified command in a subprocess" do
+ -> { @object.system("echo a") }.should output_to_fd("a\n")
+
+ $?.should be_an_instance_of Process::Status
+ $?.should.success?
+ end
+
+ it "returns true when the command exits with a zero exit status" do
+ @object.system(ruby_cmd('exit 0')).should == true
+
+ $?.should be_an_instance_of Process::Status
+ $?.should.success?
+ $?.exitstatus.should == 0
+ end
+
+ it "returns false when the command exits with a non-zero exit status" do
+ @object.system(ruby_cmd('exit 1')).should == false
+
+ $?.should be_an_instance_of Process::Status
+ $?.should_not.success?
+ $?.exitstatus.should == 1
+ end
+
+ it "raises RuntimeError when `exception: true` is given and the command exits with a non-zero exit status" do
+ -> { @object.system(ruby_cmd('exit 1'), exception: true) }.should raise_error(RuntimeError)
+ end
+
+ it "raises Errno::ENOENT when `exception: true` is given and the specified command does not exist" do
+ -> { @object.system('feature_14386', exception: true) }.should raise_error(Errno::ENOENT)
+ end
+
+ it "returns nil when command execution fails" do
+ @object.system("sad").should be_nil
+
+ $?.should be_an_instance_of Process::Status
+ $?.pid.should be_kind_of(Integer)
+ $?.should_not.success?
+ end
+
+ it "does not write to stderr when command execution fails" do
+ -> { @object.system("sad") }.should output_to_fd("", STDERR)
+ end
+
+ platform_is_not :windows do
+ before :each do
+ @shell = ENV['SHELL']
+ end
+
+ after :each do
+ ENV['SHELL'] = @shell
+ end
+
+ it "executes with `sh` if the command contains shell characters" do
+ -> { @object.system("echo $0") }.should output_to_fd("sh\n")
+ end
+
+ it "ignores SHELL env var and always uses `sh`" do
+ ENV['SHELL'] = "/bin/fakeshell"
+ -> { @object.system("echo $0") }.should output_to_fd("sh\n")
+ end
+ end
+
+ before :each do
+ ENV['TEST_SH_EXPANSION'] = 'foo'
+ @shell_var = '$TEST_SH_EXPANSION'
+ platform_is :windows do
+ @shell_var = '%TEST_SH_EXPANSION%'
+ end
+ end
+
+ after :each do
+ ENV.delete('TEST_SH_EXPANSION')
+ end
+
+ it "expands shell variables when given a single string argument" do
+ -> { @object.system("echo #{@shell_var}") }.should output_to_fd("foo\n")
+ end
+
+ platform_is_not :windows do
+ it "does not expand shell variables when given multiples arguments" do
+ -> { @object.system("echo", @shell_var) }.should output_to_fd("#{@shell_var}\n")
+ end
+ end
+
+ platform_is :windows do
+ it "does expand shell variables when given multiples arguments" do
+ # See https://bugs.ruby-lang.org/issues/12231
+ -> { @object.system("echo", @shell_var) }.should output_to_fd("foo\n")
+ end
+ end
+
+ platform_is :windows do
+ it "runs commands starting with any number of @ using shell" do
+ `#{ruby_cmd("p system 'does_not_exist'")} 2>NUL`.chomp.should == "nil"
+ @object.system('@does_not_exist 2>NUL').should == false
+ @object.system("@@@#{ruby_cmd('exit 0')}").should == true
+ end
+ end
+end
+
+describe "Kernel#system" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:system)
+ end
+
+ it_behaves_like :kernel_system, :system, KernelSpecs::Method.new
+end
+
+describe "Kernel.system" do
+ it_behaves_like :kernel_system, :system, Kernel
+end
diff --git a/spec/ruby/core/kernel/taint_spec.rb b/spec/ruby/core/kernel/taint_spec.rb
new file mode 100644
index 0000000000..9a58bb5f04
--- /dev/null
+++ b/spec/ruby/core/kernel/taint_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#taint" do
+ ruby_version_is ""..."3.0" do
+ it "is a no-op" do
+ o = Object.new
+ o.taint
+ o.should_not.tainted?
+ end
+
+ it "warns in verbose mode" do
+ -> {
+ obj = mock("tainted")
+ obj.taint
+ }.should complain(/Object#taint is deprecated and will be removed in Ruby 3.2/, verbose: true)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/tainted_spec.rb b/spec/ruby/core/kernel/tainted_spec.rb
new file mode 100644
index 0000000000..7511c730c9
--- /dev/null
+++ b/spec/ruby/core/kernel/tainted_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#tainted?" do
+ ruby_version_is ""..."3.0" do
+ it "is a no-op" do
+ o = mock('o')
+ p = mock('p')
+ p.taint
+ o.should_not.tainted?
+ p.should_not.tainted?
+ end
+
+ it "warns in verbose mode" do
+ -> {
+ o = mock('o')
+ o.tainted?
+ }.should complain(/Object#tainted\? is deprecated and will be removed in Ruby 3.2/, verbose: true)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/tap_spec.rb b/spec/ruby/core/kernel/tap_spec.rb
new file mode 100644
index 0000000000..f7720a6dc7
--- /dev/null
+++ b/spec/ruby/core/kernel/tap_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#tap" do
+ it "always yields self and returns self" do
+ a = KernelSpecs::A.new
+ a.tap{|o| o.should equal(a); 42}.should equal(a)
+ end
+
+ it "raises a LocalJumpError when no block given" do
+ -> { 3.tap }.should raise_error(LocalJumpError)
+ end
+end
diff --git a/spec/ruby/core/kernel/test_spec.rb b/spec/ruby/core/kernel/test_spec.rb
new file mode 100644
index 0000000000..abb365aed2
--- /dev/null
+++ b/spec/ruby/core/kernel/test_spec.rb
@@ -0,0 +1,109 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#test" do
+ before :all do
+ @file = File.dirname(__FILE__) + '/fixtures/classes.rb'
+ @dir = File.dirname(__FILE__) + '/fixtures'
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:test)
+ end
+
+ it "returns true when passed ?f if the argument is a regular file" do
+ Kernel.test(?f, @file).should == true
+ end
+
+ it "returns true when passed ?e if the argument is a file" do
+ Kernel.test(?e, @file).should == true
+ end
+
+ it "returns true when passed ?d if the argument is a directory" do
+ Kernel.test(?d, @dir).should == true
+ end
+
+ platform_is_not :windows do
+ it "returns true when passed ?l if the argument is a symlink" do
+ link = tmp("file_symlink.lnk")
+ File.symlink(@file, link)
+ begin
+ Kernel.test(?l, link).should be_true
+ ensure
+ rm_r link
+ end
+ end
+ end
+
+ it "returns true when passed ?r if the argument is readable by the effective uid" do
+ Kernel.test(?r, @file).should be_true
+ end
+
+ it "returns true when passed ?R if the argument is readable by the real uid" do
+ Kernel.test(?R, @file).should be_true
+ end
+
+ context "writable test" do
+ before do
+ @tmp_file = tmp("file.kernel.test")
+ touch(@tmp_file)
+ end
+
+ after do
+ rm_r @tmp_file
+ end
+
+ it "returns true when passed ?w if the argument is readable by the effective uid" do
+ Kernel.test(?w, @tmp_file).should be_true
+ end
+
+ it "returns true when passed ?W if the argument is readable by the real uid" do
+ Kernel.test(?W, @tmp_file).should be_true
+ end
+ end
+
+ context "time commands" do
+ before :each do
+ @tmp_file = File.new(tmp("file.kernel.test"), "w")
+ end
+
+ after :each do
+ @tmp_file.close
+ rm_r @tmp_file
+ end
+
+ it "returns the last access time for the provided file when passed ?A" do
+ Kernel.test(?A, @tmp_file).should == @tmp_file.atime
+ end
+
+ it "returns the time at which the file was created when passed ?C" do
+ Kernel.test(?C, @tmp_file).should == @tmp_file.ctime
+ end
+
+ it "returns the time at which the file was modified when passed ?M" do
+ Kernel.test(?M, @tmp_file).should == @tmp_file.mtime
+ end
+ end
+
+ it "calls #to_path on second argument when passed ?f and a filename" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return @file
+ Kernel.test(?f, p)
+ end
+
+ it "calls #to_path on second argument when passed ?e and a filename" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return @file
+ Kernel.test(?e, p)
+ end
+
+ it "calls #to_path on second argument when passed ?d and a directory" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return @dir
+ Kernel.test(?d, p)
+ end
+end
+
+describe "Kernel.test" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/then_spec.rb b/spec/ruby/core/kernel/then_spec.rb
new file mode 100644
index 0000000000..8109a2960a
--- /dev/null
+++ b/spec/ruby/core/kernel/then_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/then'
+
+describe "Kernel#then" do
+ it_behaves_like :kernel_then, :then
+end
diff --git a/spec/ruby/core/kernel/throw_spec.rb b/spec/ruby/core/kernel/throw_spec.rb
new file mode 100644
index 0000000000..64bfccb413
--- /dev/null
+++ b/spec/ruby/core/kernel/throw_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel.throw" do
+ it "transfers control to the end of the active catch block waiting for symbol" do
+ catch(:blah) do
+ :value
+ throw :blah
+ fail("throw didn't transfer the control")
+ end.should be_nil
+ end
+
+ it "transfers control to the innermost catch block waiting for the same symbol" do
+ one = two = three = 0
+ catch :duplicate do
+ catch :duplicate do
+ catch :duplicate do
+ one = 1
+ throw :duplicate
+ end
+ two = 2
+ throw :duplicate
+ end
+ three = 3
+ throw :duplicate
+ end
+ [one, two, three].should == [1, 2, 3]
+ end
+
+ it "sets the return value of the catch block to nil by default" do
+ res = catch :blah do
+ throw :blah
+ end
+ res.should == nil
+ end
+
+ it "sets the return value of the catch block to a value specified as second parameter" do
+ res = catch :blah do
+ throw :blah, :return_value
+ end
+ res.should == :return_value
+ end
+
+ it "raises an ArgumentError if there is no catch block for the symbol" do
+ -> { throw :blah }.should raise_error(ArgumentError)
+ end
+
+ it "raises an UncaughtThrowError if there is no catch block for the symbol" do
+ -> { throw :blah }.should raise_error(UncaughtThrowError)
+ end
+
+ it "raises ArgumentError if 3 or more arguments provided" do
+ -> {
+ catch :blah do
+ throw :blah, :return_value, 2
+ end
+ }.should raise_error(ArgumentError)
+
+ -> {
+ catch :blah do
+ throw :blah, :return_value, 2, 3, 4, 5
+ end
+ }.should raise_error(ArgumentError)
+ end
+
+ it "can throw an object" do
+ -> {
+ obj = Object.new
+ catch obj do
+ throw obj
+ end
+ }.should_not raise_error(NameError)
+ end
+end
+
+describe "Kernel#throw" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:throw)
+ end
+end
diff --git a/spec/ruby/core/kernel/to_enum_spec.rb b/spec/ruby/core/kernel/to_enum_spec.rb
new file mode 100644
index 0000000000..9d9945450f
--- /dev/null
+++ b/spec/ruby/core/kernel/to_enum_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#to_enum" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/to_s_spec.rb b/spec/ruby/core/kernel/to_s_spec.rb
new file mode 100644
index 0000000000..ea4b00151e
--- /dev/null
+++ b/spec/ruby/core/kernel/to_s_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#to_s" do
+ it "returns a String containing the name of self's class" do
+ Object.new.to_s.should =~ /Object/
+ end
+end
diff --git a/spec/ruby/core/kernel/trace_var_spec.rb b/spec/ruby/core/kernel/trace_var_spec.rb
new file mode 100644
index 0000000000..3c84aa5e60
--- /dev/null
+++ b/spec/ruby/core/kernel/trace_var_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#trace_var" do
+ before :each do
+ $Kernel_trace_var_global = nil
+ end
+
+ after :each do
+ untrace_var :$Kernel_trace_var_global
+
+ $Kernel_trace_var_global = nil
+ $Kernel_trace_var_extra = nil
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:trace_var)
+ end
+
+ it "hooks assignments to a global variable" do
+ captured = nil
+
+ trace_var :$Kernel_trace_var_global do |value|
+ captured = value
+ end
+
+ $Kernel_trace_var_global = 'foo'
+ captured.should == 'foo'
+ end
+
+ it "accepts a proc argument instead of a block" do
+ captured = nil
+
+ trace_var :$Kernel_trace_var_global, proc {|value| captured = value}
+
+ $Kernel_trace_var_global = 'foo'
+ captured.should == 'foo'
+ end
+
+ # String arguments should be evaluated in the context of the caller.
+ it "accepts a String argument instead of a Proc or block" do
+ trace_var :$Kernel_trace_var_global, '$Kernel_trace_var_extra = true'
+
+ $Kernel_trace_var_global = 'foo'
+
+ $Kernel_trace_var_extra.should == true
+ end
+
+ it "raises ArgumentError if no block or proc is provided" do
+ -> do
+ trace_var :$Kernel_trace_var_global
+ end.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/kernel/trap_spec.rb b/spec/ruby/core/kernel/trap_spec.rb
new file mode 100644
index 0000000000..4c801a7215
--- /dev/null
+++ b/spec/ruby/core/kernel/trap_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Kernel#trap" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:trap)
+ end
+
+ # Behaviour is specified for Signal.trap
+end
diff --git a/spec/ruby/core/kernel/trust_spec.rb b/spec/ruby/core/kernel/trust_spec.rb
new file mode 100644
index 0000000000..4665036da6
--- /dev/null
+++ b/spec/ruby/core/kernel/trust_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#trust" do
+ ruby_version_is ""..."3.0" do
+ it "is a no-op" do
+ o = Object.new.untrust
+ o.should_not.untrusted?
+ o.trust
+ o.should_not.untrusted?
+ end
+
+ it "warns in verbose mode" do
+ -> {
+ o = Object.new.untrust
+ o.trust
+ }.should complain(/Object#trust is deprecated and will be removed in Ruby 3.2/, verbose: true)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/untaint_spec.rb b/spec/ruby/core/kernel/untaint_spec.rb
new file mode 100644
index 0000000000..42fe8a4239
--- /dev/null
+++ b/spec/ruby/core/kernel/untaint_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#untaint" do
+ ruby_version_is ""..."3.0" do
+ it "is a no-op" do
+ o = Object.new.taint
+ o.should_not.tainted?
+ o.untaint
+ o.should_not.tainted?
+ end
+
+ it "warns in verbose mode" do
+ -> {
+ o = Object.new.taint
+ o.untaint
+ }.should complain(/Object#untaint is deprecated and will be removed in Ruby 3.2/, verbose: true)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/untrace_var_spec.rb b/spec/ruby/core/kernel/untrace_var_spec.rb
new file mode 100644
index 0000000000..1925a3a836
--- /dev/null
+++ b/spec/ruby/core/kernel/untrace_var_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#untrace_var" do
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:untrace_var)
+ end
+end
+
+describe "Kernel.untrace_var" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/kernel/untrust_spec.rb b/spec/ruby/core/kernel/untrust_spec.rb
new file mode 100644
index 0000000000..ba0e073cf0
--- /dev/null
+++ b/spec/ruby/core/kernel/untrust_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#untrust" do
+ ruby_version_is ""..."3.0" do
+ it "is a no-op" do
+ o = Object.new
+ o.untrust
+ o.should_not.untrusted?
+ end
+
+ it "warns in verbose mode" do
+ -> {
+ o = Object.new
+ o.untrust
+ }.should complain(/Object#untrust is deprecated and will be removed in Ruby 3.2/, verbose: true)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/untrusted_spec.rb b/spec/ruby/core/kernel/untrusted_spec.rb
new file mode 100644
index 0000000000..517bd4711b
--- /dev/null
+++ b/spec/ruby/core/kernel/untrusted_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#untrusted?" do
+ ruby_version_is ""..."3.0" do
+ it "is a no-op" do
+ o = mock('o')
+ o.should_not.untrusted?
+ o.untrust
+ o.should_not.untrusted?
+ end
+
+ it "warns in verbose mode" do
+ -> {
+ o = mock('o')
+ o.untrusted?
+ }.should complain(/Object#untrusted\? is deprecated and will be removed in Ruby 3.2/, verbose: true)
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/warn_spec.rb b/spec/ruby/core/kernel/warn_spec.rb
new file mode 100644
index 0000000000..7df6fa72d1
--- /dev/null
+++ b/spec/ruby/core/kernel/warn_spec.rb
@@ -0,0 +1,309 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel#warn" do
+ before :each do
+ @before_verbose = $VERBOSE
+ @before_separator = $/
+ end
+
+ after :each do
+ $VERBOSE = nil
+ $/ = @before_separator
+ $VERBOSE = @before_verbose
+ end
+
+ it "is a private method" do
+ Kernel.should have_private_instance_method(:warn)
+ end
+
+ it "accepts multiple arguments" do
+ Kernel.method(:warn).arity.should < 0
+ end
+
+ it "does not append line-end if last character is line-end" do
+ -> {
+ $VERBOSE = true
+ warn("this is some simple text with line-end\n")
+ }.should output(nil, "this is some simple text with line-end\n")
+ end
+
+ it "calls #write on $stderr if $VERBOSE is true" do
+ -> {
+ $VERBOSE = true
+ warn("this is some simple text")
+ }.should output(nil, "this is some simple text\n")
+ end
+
+ it "calls #write on $stderr if $VERBOSE is false" do
+ -> {
+ $VERBOSE = false
+ warn("this is some simple text")
+ }.should output(nil, "this is some simple text\n")
+ end
+
+ it "does not call #write on $stderr if $VERBOSE is nil" do
+ -> {
+ $VERBOSE = nil
+ warn("this is some simple text")
+ }.should output(nil, "")
+ end
+
+ it "writes each argument on a line when passed multiple arguments" do
+ -> {
+ $VERBOSE = true
+ warn("line 1", "line 2")
+ }.should output(nil, "line 1\nline 2\n")
+ end
+
+ it "writes each array element on a line when passes an array" do
+ -> {
+ $VERBOSE = true
+ warn(["line 1", "line 2"])
+ }.should output(nil, "line 1\nline 2\n")
+ end
+
+ it "does not write strings when passed no arguments" do
+ -> {
+ $VERBOSE = true
+ warn
+ }.should output("", "")
+ end
+
+ it "writes the default record separator and NOT $/ to $stderr after the warning message" do
+ -> {
+ $VERBOSE = true
+ $/ = 'rs'
+ warn("")
+ }.should output(nil, /\n/)
+ end
+
+ it "writes to_s representation if passed a non-string" do
+ obj = mock("obj")
+ obj.should_receive(:to_s).and_return("to_s called")
+ -> {
+ $VERBOSE = true
+ warn(obj)
+ }.should output(nil, "to_s called\n")
+ end
+
+ describe ":uplevel keyword argument" do
+ before :each do
+ $VERBOSE = true
+ end
+
+ it "prepends a message with specified line from the backtrace" do
+ w = KernelSpecs::WarnInNestedCall.new
+
+ -> { w.f4("foo", 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: foo|)
+ -> { w.f4("foo", 1) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f1_call_lineno}: warning: foo|)
+ -> { w.f4("foo", 2) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f2_call_lineno}: warning: foo|)
+ -> { w.f4("foo", 3) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f3_call_lineno}: warning: foo|)
+ end
+
+ # Test both explicitly without and with RubyGems as RubyGems overrides Kernel#warn
+ it "shows the caller of #require and not #require itself without RubyGems" do
+ file = fixture(__FILE__ , "warn_require_caller.rb")
+ ruby_exe(file, options: "--disable-gems", args: "2>&1").should == "#{file}:2: warning: warn-require-warning\n"
+ end
+
+ it "shows the caller of #require and not #require itself with RubyGems loaded" do
+ file = fixture(__FILE__ , "warn_require_caller.rb")
+ ruby_exe(file, options: "-rrubygems", args: "2>&1").should == "#{file}:2: warning: warn-require-warning\n"
+ end
+
+ guard -> { Kernel.instance_method(:tap).source_location } do
+ it "skips <internal: core library methods defined in Ruby" do
+ file, line = Kernel.instance_method(:tap).source_location
+ file.should.start_with?('<internal:')
+
+ file = fixture(__FILE__ , "warn_core_method.rb")
+ n = 9
+ ruby_exe(file, options: "--disable-gems", args: "2>&1").lines.should == [
+ "#{file}:#{n+0}: warning: use X instead\n",
+ "#{file}:#{n+1}: warning: use X instead\n",
+ "#{file}:#{n+2}: warning: use X instead\n",
+ "#{file}:#{n+4}: warning: use X instead\n",
+ ]
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "accepts :category keyword with a symbol" do
+ -> {
+ $VERBOSE = true
+ warn("message", category: :deprecated)
+ }.should output(nil, "message\n")
+ end
+
+ it "accepts :category keyword with nil" do
+ -> {
+ $VERBOSE = true
+ warn("message", category: nil)
+ }.should output(nil, "message\n")
+ end
+
+ it "accepts :category keyword with object convertible to symbol" do
+ o = Object.new
+ def o.to_sym; :deprecated; end
+ -> {
+ $VERBOSE = true
+ warn("message", category: o)
+ }.should output(nil, "message\n")
+ end
+
+ it "raises if :category keyword is not nil and not convertible to symbol" do
+ -> {
+ $VERBOSE = true
+ warn("message", category: Object.new)
+ }.should raise_error(TypeError)
+ end
+ end
+
+ it "converts first arg using to_s" do
+ w = KernelSpecs::WarnInNestedCall.new
+
+ -> { w.f4(false, 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: false|)
+ -> { w.f4(nil, 1) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f1_call_lineno}: warning: |)
+ obj = mock("obj")
+ obj.should_receive(:to_s).and_return("to_s called")
+ -> { w.f4(obj, 2) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f2_call_lineno}: warning: to_s called|)
+ end
+
+ it "does not prepend caller information if the uplevel argument is too large" do
+ w = KernelSpecs::WarnInNestedCall.new
+ -> { w.f4("foo", 100) }.should output(nil, "warning: foo\n")
+ end
+
+ it "prepends even if a message is empty or nil" do
+ w = KernelSpecs::WarnInNestedCall.new
+
+ -> { w.f4("", 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: \n$|)
+ -> { w.f4(nil, 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: \n$|)
+ end
+
+ it "converts value to Integer" do
+ w = KernelSpecs::WarnInNestedCall.new
+
+ -> { w.f4(0.1) }.should output(nil, %r|classes.rb:#{w.warn_call_lineno}:|)
+ -> { w.f4(Rational(1, 2)) }.should output(nil, %r|classes.rb:#{w.warn_call_lineno}:|)
+ end
+
+ it "raises ArgumentError if passed negative value" do
+ -> { warn "", uplevel: -2 }.should raise_error(ArgumentError)
+ -> { warn "", uplevel: -100 }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if passed -1" do
+ -> { warn "", uplevel: -1 }.should raise_error(ArgumentError)
+ end
+
+ it "raises TypeError if passed not Integer" do
+ -> { warn "", uplevel: "" }.should raise_error(TypeError)
+ -> { warn "", uplevel: [] }.should raise_error(TypeError)
+ -> { warn "", uplevel: {} }.should raise_error(TypeError)
+ -> { warn "", uplevel: Object.new }.should raise_error(TypeError)
+ end
+ end
+
+ it "treats empty hash as no keyword argument" do
+ h = {}
+ -> { warn(**h) }.should_not complain(verbose: true)
+ -> { warn('foo', **h) }.should complain("foo\n")
+ end
+
+ ruby_version_is '3.0' do
+ it "calls Warning.warn without keyword arguments if Warning.warn does not accept keyword arguments" do
+ verbose = $VERBOSE
+ $VERBOSE = false
+ class << Warning
+ alias_method :_warn, :warn
+ def warn(message)
+ ScratchPad.record(message)
+ end
+ end
+
+ begin
+ ScratchPad.clear
+ Kernel.warn("Chunky bacon!")
+ ScratchPad.recorded.should == "Chunky bacon!\n"
+
+ Kernel.warn("Deprecated bacon!", category: :deprecated)
+ ScratchPad.recorded.should == "Deprecated bacon!\n"
+ ensure
+ class << Warning
+ remove_method :warn
+ alias_method :warn, :_warn
+ remove_method :_warn
+ end
+ $VERBOSE = verbose
+ end
+ end
+
+ it "calls Warning.warn with category: nil if Warning.warn accepts keyword arguments" do
+ Warning.should_receive(:warn).with("Chunky bacon!\n", category: nil)
+ verbose = $VERBOSE
+ $VERBOSE = false
+ begin
+ Kernel.warn("Chunky bacon!")
+ ensure
+ $VERBOSE = verbose
+ end
+ end
+
+ it "calls Warning.warn with given category keyword converted to a symbol" do
+ Warning.should_receive(:warn).with("Chunky bacon!\n", category: :deprecated)
+ verbose = $VERBOSE
+ $VERBOSE = false
+ begin
+ Kernel.warn("Chunky bacon!", category: 'deprecated')
+ ensure
+ $VERBOSE = verbose
+ end
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "calls Warning.warn with no keyword arguments" do
+ Warning.should_receive(:warn).with("Chunky bacon!\n")
+ verbose = $VERBOSE
+ $VERBOSE = false
+ begin
+ Kernel.warn("Chunky bacon!")
+ ensure
+ $VERBOSE = verbose
+ end
+ end
+ end
+
+ it "does not call Warning.warn if self is the Warning module" do
+ # RubyGems redefines Kernel#warn so we need to use a subprocess and disable RubyGems here
+ code = <<-RUBY
+ def Warning.warn(*args, **kwargs)
+ raise 'should not be called'
+ end
+ Kernel.instance_method(:warn).bind(Warning).call('Kernel#warn spec edge case')
+ RUBY
+ out = ruby_exe(code, args: "2>&1", options: "--disable-gems")
+ out.should == "Kernel#warn spec edge case\n"
+ $?.should.success?
+ end
+
+ it "avoids recursion if Warning#warn is redefined and calls super" do
+ # This works because of the spec above, which is the workaround for it.
+ # Note that redefining Warning#warn is a mistake which would naturally end in infinite recursion,
+ # Warning.extend Module.new { def warn } should be used instead.
+ # RubyGems redefines Kernel#warn so we need to use a subprocess and disable RubyGems here
+ code = <<-RUBY
+ module Warning
+ def warn(*args, **kwargs)
+ super
+ end
+ end
+ warn "avoid infinite recursion"
+ RUBY
+ out = ruby_exe(code, args: "2>&1", options: "--disable-gems")
+ out.should == "avoid infinite recursion\n"
+ $?.should.success?
+ end
+end
diff --git a/spec/ruby/core/kernel/yield_self_spec.rb b/spec/ruby/core/kernel/yield_self_spec.rb
new file mode 100644
index 0000000000..e311dcee47
--- /dev/null
+++ b/spec/ruby/core/kernel/yield_self_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/then'
+
+describe "Kernel#yield_self" do
+ it_behaves_like :kernel_then, :yield_self
+end
diff --git a/spec/ruby/core/main/define_method_spec.rb b/spec/ruby/core/main/define_method_spec.rb
new file mode 100644
index 0000000000..d85c5e8119
--- /dev/null
+++ b/spec/ruby/core/main/define_method_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+script_binding = binding
+
+describe "main#define_method" do
+ before :each do
+ @code = 'define_method(:boom) { :bam }'
+ end
+
+ after :each do
+ Object.send :remove_method, :boom
+ end
+
+ it 'creates a public method in TOPLEVEL_BINDING' do
+ eval @code, TOPLEVEL_BINDING
+ Object.should have_method :boom
+ end
+
+ it 'creates a public method in script binding' do
+ eval @code, script_binding
+ Object.should have_method :boom
+ end
+
+ it 'returns the method name as symbol' do
+ eval(@code, TOPLEVEL_BINDING).should equal :boom
+ end
+end
diff --git a/spec/ruby/core/main/fixtures/classes.rb b/spec/ruby/core/main/fixtures/classes.rb
new file mode 100644
index 0000000000..757cee4e4a
--- /dev/null
+++ b/spec/ruby/core/main/fixtures/classes.rb
@@ -0,0 +1,26 @@
+module MainSpecs
+ module Module
+ end
+
+ module WrapIncludeModule
+ end
+
+ DATA = {}
+end
+
+
+def main_public_method
+end
+public :main_public_method
+
+def main_public_method2
+end
+public :main_public_method2
+
+def main_private_method
+end
+private :main_private_method
+
+def main_private_method2
+end
+private :main_private_method2
diff --git a/spec/ruby/core/main/fixtures/string_refinement.rb b/spec/ruby/core/main/fixtures/string_refinement.rb
new file mode 100644
index 0000000000..2dc6de52ca
--- /dev/null
+++ b/spec/ruby/core/main/fixtures/string_refinement.rb
@@ -0,0 +1,7 @@
+module StringRefinement
+ refine(String) do
+ def foo
+ 'foo'
+ end
+ end
+end
diff --git a/spec/ruby/core/main/fixtures/string_refinement_user.rb b/spec/ruby/core/main/fixtures/string_refinement_user.rb
new file mode 100644
index 0000000000..48620c325f
--- /dev/null
+++ b/spec/ruby/core/main/fixtures/string_refinement_user.rb
@@ -0,0 +1,11 @@
+using StringRefinement
+
+module MainSpecs
+ DATA[:in_module] = 'hello'.foo
+
+ def self.call_foo(x)
+ x.foo
+ end
+end
+
+MainSpecs::DATA[:toplevel] = 'hello'.foo
diff --git a/spec/ruby/core/main/fixtures/using.rb b/spec/ruby/core/main/fixtures/using.rb
new file mode 100644
index 0000000000..30713ef309
--- /dev/null
+++ b/spec/ruby/core/main/fixtures/using.rb
@@ -0,0 +1 @@
+using Module.new
diff --git a/spec/ruby/core/main/fixtures/using_in_main.rb b/spec/ruby/core/main/fixtures/using_in_main.rb
new file mode 100644
index 0000000000..a4a71c89cc
--- /dev/null
+++ b/spec/ruby/core/main/fixtures/using_in_main.rb
@@ -0,0 +1,5 @@
+MAIN = self
+
+module X
+ MAIN.send(:using, Module.new)
+end
diff --git a/spec/ruby/core/main/fixtures/using_in_method.rb b/spec/ruby/core/main/fixtures/using_in_method.rb
new file mode 100644
index 0000000000..d9ea2e9ef0
--- /dev/null
+++ b/spec/ruby/core/main/fixtures/using_in_method.rb
@@ -0,0 +1,5 @@
+def foo
+ using Module.new
+end
+
+foo
diff --git a/spec/ruby/core/main/fixtures/wrapped_include.rb b/spec/ruby/core/main/fixtures/wrapped_include.rb
new file mode 100644
index 0000000000..307c98b419
--- /dev/null
+++ b/spec/ruby/core/main/fixtures/wrapped_include.rb
@@ -0,0 +1 @@
+include MainSpecs::WrapIncludeModule
diff --git a/spec/ruby/core/main/include_spec.rb b/spec/ruby/core/main/include_spec.rb
new file mode 100644
index 0000000000..9f5a5f54ea
--- /dev/null
+++ b/spec/ruby/core/main/include_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "main#include" do
+ it "includes the given Module in Object" do
+ eval "include MainSpecs::Module", TOPLEVEL_BINDING
+ Object.ancestors.should include(MainSpecs::Module)
+ end
+
+ context "in a file loaded with wrapping" do
+ it "includes the given Module in the load wrapper" do
+ load(File.expand_path("../fixtures/wrapped_include.rb", __FILE__), true)
+ Object.ancestors.should_not include(MainSpecs::WrapIncludeModule)
+ end
+ end
+end
diff --git a/spec/ruby/core/main/private_spec.rb b/spec/ruby/core/main/private_spec.rb
new file mode 100644
index 0000000000..cac0645b40
--- /dev/null
+++ b/spec/ruby/core/main/private_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "main#private" do
+ after :each do
+ Object.send(:public, :main_public_method)
+ Object.send(:public, :main_public_method2)
+ end
+
+ context "when single argument is passed and it is not an array" do
+ it "sets the visibility of the given methods to private" do
+ eval "private :main_public_method", TOPLEVEL_BINDING
+ Object.should have_private_method(:main_public_method)
+ end
+ end
+
+ context "when multiple arguments are passed" do
+ it "sets the visibility of the given methods to private" do
+ eval "private :main_public_method, :main_public_method2", TOPLEVEL_BINDING
+ Object.should have_private_method(:main_public_method)
+ Object.should have_private_method(:main_public_method2)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ context "when single argument is passed and is an array" do
+ it "sets the visibility of the given methods to private" do
+ eval "private [:main_public_method, :main_public_method2]", TOPLEVEL_BINDING
+ Object.should have_private_method(:main_public_method)
+ Object.should have_private_method(:main_public_method2)
+ end
+ end
+ end
+
+ ruby_version_is ''...'3.1' do
+ it "returns Object" do
+ eval("private :main_public_method", TOPLEVEL_BINDING).should equal(Object)
+ end
+ end
+
+ ruby_version_is '3.1' do
+ it "returns argument" do
+ eval("private :main_public_method", TOPLEVEL_BINDING).should equal(:main_public_method)
+ end
+ end
+
+ it "raises a NameError when at least one of given method names is undefined" do
+ -> do
+ eval "private :main_public_method, :main_undefined_method", TOPLEVEL_BINDING
+ end.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/core/main/public_spec.rb b/spec/ruby/core/main/public_spec.rb
new file mode 100644
index 0000000000..91f045dbab
--- /dev/null
+++ b/spec/ruby/core/main/public_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "main#public" do
+ after :each do
+ Object.send(:private, :main_private_method)
+ Object.send(:private, :main_private_method2)
+ end
+
+ context "when single argument is passed and it is not an array" do
+ it "sets the visibility of the given methods to public" do
+ eval "public :main_private_method", TOPLEVEL_BINDING
+ Object.should_not have_private_method(:main_private_method)
+ end
+ end
+
+ context "when multiple arguments are passed" do
+ it "sets the visibility of the given methods to public" do
+ eval "public :main_private_method, :main_private_method2", TOPLEVEL_BINDING
+ Object.should_not have_private_method(:main_private_method)
+ Object.should_not have_private_method(:main_private_method2)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ context "when single argument is passed and is an array" do
+ it "sets the visibility of the given methods to public" do
+ eval "public [:main_private_method, :main_private_method2]", TOPLEVEL_BINDING
+ Object.should_not have_private_method(:main_private_method)
+ Object.should_not have_private_method(:main_private_method2)
+ end
+ end
+ end
+
+ ruby_version_is ''...'3.1' do
+ it "returns Object" do
+ eval("public :main_private_method", TOPLEVEL_BINDING).should equal(Object)
+ end
+ end
+
+ ruby_version_is '3.1' do
+ it "returns argument" do
+ eval("public :main_private_method", TOPLEVEL_BINDING).should equal(:main_private_method)
+ end
+ end
+
+
+ it "raises a NameError when given an undefined name" do
+ -> do
+ eval "public :main_undefined_method", TOPLEVEL_BINDING
+ end.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/core/main/ruby2_keywords_spec.rb b/spec/ruby/core/main/ruby2_keywords_spec.rb
new file mode 100644
index 0000000000..27ceae3253
--- /dev/null
+++ b/spec/ruby/core/main/ruby2_keywords_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "main.ruby2_keywords" do
+ it "is the same as Object.ruby2_keywords" do
+ main = TOPLEVEL_BINDING.receiver
+ main.should have_private_method(:ruby2_keywords)
+ end
+end
diff --git a/spec/ruby/core/main/to_s_spec.rb b/spec/ruby/core/main/to_s_spec.rb
new file mode 100644
index 0000000000..642cfa4433
--- /dev/null
+++ b/spec/ruby/core/main/to_s_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "main#to_s" do
+ it "returns 'main'" do
+ eval('to_s', TOPLEVEL_BINDING).should == "main"
+ end
+end
diff --git a/spec/ruby/core/main/using_spec.rb b/spec/ruby/core/main/using_spec.rb
new file mode 100644
index 0000000000..8a23970c4b
--- /dev/null
+++ b/spec/ruby/core/main/using_spec.rb
@@ -0,0 +1,152 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "main.using" do
+ it "requires one Module argument" do
+ -> do
+ eval('using', TOPLEVEL_BINDING)
+ end.should raise_error(ArgumentError)
+
+ -> do
+ eval('using "foo"', TOPLEVEL_BINDING)
+ end.should raise_error(TypeError)
+ end
+
+ it "uses refinements from the given module only in the target file" do
+ require_relative 'fixtures/string_refinement'
+ load File.expand_path('../fixtures/string_refinement_user.rb', __FILE__)
+ MainSpecs::DATA[:in_module].should == 'foo'
+ MainSpecs::DATA[:toplevel].should == 'foo'
+ -> do
+ 'hello'.foo
+ end.should raise_error(NoMethodError)
+ end
+
+ it "uses refinements from the given module for method calls in the target file" do
+ require_relative 'fixtures/string_refinement'
+ load File.expand_path('../fixtures/string_refinement_user.rb', __FILE__)
+ -> do
+ 'hello'.foo
+ end.should raise_error(NoMethodError)
+ MainSpecs.call_foo('hello').should == 'foo'
+ end
+
+ it "uses refinements from the given module in the eval string" do
+ cls = MainSpecs::DATA[:cls] = Class.new {def foo; 'foo'; end}
+ MainSpecs::DATA[:mod] = Module.new do
+ refine(cls) do
+ def foo; 'bar'; end
+ end
+ end
+ eval(<<-EOS, TOPLEVEL_BINDING).should == 'bar'
+ using MainSpecs::DATA[:mod]
+ MainSpecs::DATA[:cls].new.foo
+ EOS
+ end
+
+ it "does not affect methods defined before it is called" do
+ cls = Class.new {def foo; 'foo'; end}
+ MainSpecs::DATA[:mod] = Module.new do
+ refine(cls) do
+ def foo; 'bar'; end
+ end
+ end
+ x = MainSpecs::DATA[:x] = Object.new
+ eval <<-EOS, TOPLEVEL_BINDING
+ x = MainSpecs::DATA[:x]
+ def x.before_using(obj)
+ obj.foo
+ end
+ using MainSpecs::DATA[:mod]
+ def x.after_using(obj)
+ obj.foo
+ end
+ EOS
+
+ obj = cls.new
+ x.before_using(obj).should == 'foo'
+ x.after_using(obj).should == 'bar'
+ end
+
+ it "propagates refinements added to existing modules after it is called" do
+ cls = Class.new {def foo; 'foo'; end}
+ mod = MainSpecs::DATA[:mod] = Module.new do
+ refine(cls) do
+ def foo; 'quux'; end
+ end
+ end
+ x = MainSpecs::DATA[:x] = Object.new
+ eval <<-EOS, TOPLEVEL_BINDING
+ using MainSpecs::DATA[:mod]
+ x = MainSpecs::DATA[:x]
+ def x.call_foo(obj)
+ obj.foo
+ end
+ def x.call_bar(obj)
+ obj.bar
+ end
+ EOS
+
+ obj = cls.new
+ x.call_foo(obj).should == 'quux'
+
+ mod.module_eval do
+ refine(cls) do
+ def bar; 'quux'; end
+ end
+ end
+
+ x.call_bar(obj).should == 'quux'
+ end
+
+ it "does not propagate refinements of new modules added after it is called" do
+ cls = Class.new {def foo; 'foo'; end}
+ cls2 = Class.new {def bar; 'bar'; end}
+ mod = MainSpecs::DATA[:mod] = Module.new do
+ refine(cls) do
+ def foo; 'quux'; end
+ end
+ end
+ x = MainSpecs::DATA[:x] = Object.new
+ eval <<-EOS, TOPLEVEL_BINDING
+ using MainSpecs::DATA[:mod]
+ x = MainSpecs::DATA[:x]
+ def x.call_foo(obj)
+ obj.foo
+ end
+ def x.call_bar(obj)
+ obj.bar
+ end
+ EOS
+
+ x.call_foo(cls.new).should == 'quux'
+
+ mod.module_eval do
+ refine(cls2) do
+ def bar; 'quux'; end
+ end
+ end
+
+ x.call_bar(cls2.new).should == 'bar'
+ end
+
+ it "raises error when called from method in wrapped script" do
+ -> do
+ load File.expand_path('../fixtures/using_in_method.rb', __FILE__), true
+ end.should raise_error(RuntimeError)
+ end
+
+ it "raises error when called on toplevel from module" do
+ -> do
+ load File.expand_path('../fixtures/using_in_main.rb', __FILE__), true
+ end.should raise_error(RuntimeError)
+ end
+
+ ruby_version_is "3.2" do
+ it "does not raise error when wrapped with module" do
+ -> do
+ load File.expand_path('../fixtures/using.rb', __FILE__), true
+ end.should_not raise_error
+ end
+ end
+end
diff --git a/spec/ruby/core/marshal/dump_spec.rb b/spec/ruby/core/marshal/dump_spec.rb
new file mode 100644
index 0000000000..879ea287ce
--- /dev/null
+++ b/spec/ruby/core/marshal/dump_spec.rb
@@ -0,0 +1,650 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'fixtures/marshal_data'
+
+describe "Marshal.dump" do
+ it "dumps nil" do
+ Marshal.dump(nil).should == "\004\b0"
+ end
+
+ it "dumps true" do
+ Marshal.dump(true).should == "\004\bT"
+ end
+
+ it "dumps false" do
+ Marshal.dump(false).should == "\004\bF"
+ end
+
+ describe "with a Fixnum" do
+ it "dumps a Fixnum" do
+ [ [Marshal, 0, "\004\bi\000"],
+ [Marshal, 5, "\004\bi\n"],
+ [Marshal, 8, "\004\bi\r"],
+ [Marshal, 122, "\004\bi\177"],
+ [Marshal, 123, "\004\bi\001{"],
+ [Marshal, 1234, "\004\bi\002\322\004"],
+ [Marshal, -8, "\004\bi\363"],
+ [Marshal, -123, "\004\bi\200"],
+ [Marshal, -124, "\004\bi\377\204"],
+ [Marshal, -1234, "\004\bi\376.\373"],
+ [Marshal, -4516727, "\004\bi\375\211\024\273"],
+ [Marshal, 2**8, "\004\bi\002\000\001"],
+ [Marshal, 2**16, "\004\bi\003\000\000\001"],
+ [Marshal, 2**24, "\004\bi\004\000\000\000\001"],
+ [Marshal, -2**8, "\004\bi\377\000"],
+ [Marshal, -2**16, "\004\bi\376\000\000"],
+ [Marshal, -2**24, "\004\bi\375\000\000\000"],
+ ].should be_computed_by(:dump)
+ end
+
+ platform_is wordsize: 64 do
+ it "dumps a positive Fixnum > 31 bits as a Bignum" do
+ Marshal.dump(2**31 + 1).should == "\x04\bl+\a\x01\x00\x00\x80"
+ end
+
+ it "dumps a negative Fixnum > 31 bits as a Bignum" do
+ Marshal.dump(-2**31 - 1).should == "\x04\bl-\a\x01\x00\x00\x80"
+ end
+ end
+ end
+
+ describe "with a Symbol" do
+ it "dumps a Symbol" do
+ Marshal.dump(:symbol).should == "\004\b:\vsymbol"
+ end
+
+ it "dumps a big Symbol" do
+ Marshal.dump(('big' * 100).to_sym).should == "\004\b:\002,\001#{'big' * 100}"
+ end
+
+ it "dumps an encoded Symbol" do
+ s = "\u2192"
+ [ [Marshal, s.encode("utf-8").to_sym,
+ "\x04\bI:\b\xE2\x86\x92\x06:\x06ET"],
+ [Marshal, s.encode("utf-16").to_sym,
+ "\x04\bI:\t\xFE\xFF!\x92\x06:\rencoding\"\vUTF-16"],
+ [Marshal, s.encode("utf-16le").to_sym,
+ "\x04\bI:\a\x92!\x06:\rencoding\"\rUTF-16LE"],
+ [Marshal, s.encode("utf-16be").to_sym,
+ "\x04\bI:\a!\x92\x06:\rencoding\"\rUTF-16BE"],
+ [Marshal, s.encode("euc-jp").to_sym,
+ "\x04\bI:\a\xA2\xAA\x06:\rencoding\"\vEUC-JP"],
+ [Marshal, s.encode("sjis").to_sym,
+ "\x04\bI:\a\x81\xA8\x06:\rencoding\"\x10Windows-31J"]
+ ].should be_computed_by(:dump)
+ end
+
+ it "dumps a binary encoded Symbol" do
+ s = "\u2192".force_encoding("binary").to_sym
+ Marshal.dump(s).should == "\x04\b:\b\xE2\x86\x92"
+ end
+
+ it "dumps multiple Symbols sharing the same encoding" do
+ # Note that the encoding is a link for the second Symbol
+ symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET"
+ symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T"
+ value = [
+ "€a".force_encoding(Encoding::UTF_8).to_sym,
+ "€b".force_encoding(Encoding::UTF_8).to_sym
+ ]
+ Marshal.dump(value).should == "\x04\b[\a#{symbol1}#{symbol2}"
+
+ value = [*value, value[0]]
+ Marshal.dump(value).should == "\x04\b[\b#{symbol1}#{symbol2};\x00"
+ end
+ end
+
+ describe "with an object responding to #marshal_dump" do
+ it "dumps the object returned by #marshal_dump" do
+ Marshal.dump(UserMarshal.new).should == "\x04\bU:\x10UserMarshal:\tdata"
+ end
+
+ it "does not use Class#name" do
+ UserMarshal.should_not_receive(:name)
+ Marshal.dump(UserMarshal.new)
+ end
+ end
+
+ describe "with an object responding to #_dump" do
+ it "dumps the object returned by #_dump" do
+ Marshal.dump(UserDefined.new).should == "\004\bu:\020UserDefined\022\004\b[\a:\nstuff;\000"
+ end
+
+ it "raises a TypeError if _dump returns a non-string" do
+ m = mock("marshaled")
+ m.should_receive(:_dump).and_return(0)
+ -> { Marshal.dump(m) }.should raise_error(TypeError)
+ end
+
+ it "favors marshal_dump over _dump" do
+ m = mock("marshaled")
+ m.should_receive(:marshal_dump).and_return(0)
+ m.should_not_receive(:_dump)
+ Marshal.dump(m)
+ end
+
+ it "indexes instance variables of a String returned by #_dump at first and then indexes the object itself" do
+ class MarshalSpec::M1::A
+ def _dump(level)
+ s = "<dump>"
+ s.instance_variable_set(:@foo, "bar")
+ s
+ end
+ end
+
+ a = MarshalSpec::M1::A.new
+
+ # 0-based index of the object a = 2, that is encoded as \x07 and printed as "\a" character.
+ # Objects are serialized in the following order: Array, a, "bar".
+ # But they are indexed in different order: Array (index=0), "bar" (index=1), a (index=2)
+ # So the second occurenc of the object a is encoded as an index 2.
+ reference = "@\a"
+ Marshal.dump([a, a]).should == "\x04\b[\aIu:\x17MarshalSpec::M1::A\v<dump>\x06:\t@foo\"\bbar#{reference}"
+ end
+
+ describe "Core library classes with #_dump returning a String with instance variables" do
+ it "indexes instance variables and then a Time object itself" do
+ t = Time.utc(2022)
+ reference = "@\a"
+
+ Marshal.dump([t, t]).should == "\x04\b[\aIu:\tTime\r \x80\x1E\xC0\x00\x00\x00\x00\x06:\tzoneI\"\bUTC\x06:\x06EF#{reference}"
+ end
+ end
+ end
+
+ describe "with a Class" do
+ it "dumps a builtin Class" do
+ Marshal.dump(String).should == "\004\bc\vString"
+ end
+
+ it "dumps a user Class" do
+ Marshal.dump(UserDefined).should == "\x04\bc\x10UserDefined"
+ end
+
+ it "dumps a nested Class" do
+ Marshal.dump(UserDefined::Nested).should == "\004\bc\030UserDefined::Nested"
+ end
+
+ it "raises TypeError with an anonymous Class" do
+ -> { Marshal.dump(Class.new) }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError with a singleton Class" do
+ -> { Marshal.dump(class << self; self end) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "with a Module" do
+ it "dumps a builtin Module" do
+ Marshal.dump(Marshal).should == "\004\bm\fMarshal"
+ end
+
+ it "raises TypeError with an anonymous Module" do
+ -> { Marshal.dump(Module.new) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "with a Float" do
+ it "dumps a Float" do
+ [ [Marshal, 0.0, "\004\bf\0060"],
+ [Marshal, -0.0, "\004\bf\a-0"],
+ [Marshal, 1.0, "\004\bf\0061"],
+ [Marshal, 123.4567, "\004\bf\r123.4567"],
+ [Marshal, -0.841, "\x04\bf\v-0.841"],
+ [Marshal, -9876.345, "\x04\bf\x0E-9876.345"],
+ [Marshal, infinity_value, "\004\bf\binf"],
+ [Marshal, -infinity_value, "\004\bf\t-inf"],
+ [Marshal, nan_value, "\004\bf\bnan"],
+ ].should be_computed_by(:dump)
+ end
+ end
+
+ describe "with a Bignum" do
+ it "dumps a Bignum" do
+ [ [Marshal, -4611686018427387903, "\004\bl-\t\377\377\377\377\377\377\377?"],
+ [Marshal, -2361183241434822606847, "\004\bl-\n\377\377\377\377\377\377\377\377\177\000"],
+ ].should be_computed_by(:dump)
+ end
+
+ it "dumps a Bignum" do
+ [ [Marshal, 2**64, "\004\bl+\n\000\000\000\000\000\000\000\000\001\000"],
+ [Marshal, 2**90, "\004\bl+\v#{"\000" * 11}\004"],
+ [Marshal, -2**63, "\004\bl-\t\000\000\000\000\000\000\000\200"],
+ [Marshal, -2**64, "\004\bl-\n\000\000\000\000\000\000\000\000\001\000"],
+ ].should be_computed_by(:dump)
+ end
+
+ it "increases the object links counter" do
+ obj = Object.new
+ object_1_link = "\x06" # representing of (0-based) index=1 (by adding 5 for small Integers)
+ object_2_link = "\x07" # representing of index=2
+
+ # objects: Array, Object, Object
+ Marshal.dump([obj, obj]).should == "\x04\b[\ao:\vObject\x00@#{object_1_link}"
+
+ # objects: Array, Bignum, Object, Object
+ Marshal.dump([2**64, obj, obj]).should == "\x04\b[\bl+\n\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00o:\vObject\x00@#{object_2_link}"
+ Marshal.dump([2**48, obj, obj]).should == "\x04\b[\bl+\t\x00\x00\x00\x00\x00\x00\x01\x00o:\vObject\x00@#{object_2_link}"
+ Marshal.dump([2**32, obj, obj]).should == "\x04\b[\bl+\b\x00\x00\x00\x00\x01\x00o:\vObject\x00@#{object_2_link}"
+ end
+ end
+
+ describe "with a String" do
+ it "dumps a blank String" do
+ Marshal.dump("".force_encoding("binary")).should == "\004\b\"\000"
+ end
+
+ it "dumps a short String" do
+ Marshal.dump("short".force_encoding("binary")).should == "\004\b\"\012short"
+ end
+
+ it "dumps a long String" do
+ Marshal.dump(("big" * 100).force_encoding("binary")).should == "\004\b\"\002,\001#{"big" * 100}"
+ end
+
+ it "dumps a String extended with a Module" do
+ Marshal.dump("".extend(Meths).force_encoding("binary")).should == "\004\be:\nMeths\"\000"
+ end
+
+ it "dumps a String subclass" do
+ Marshal.dump(UserString.new.force_encoding("binary")).should == "\004\bC:\017UserString\"\000"
+ end
+
+ it "dumps a String subclass extended with a Module" do
+ Marshal.dump(UserString.new.extend(Meths).force_encoding("binary")).should == "\004\be:\nMethsC:\017UserString\"\000"
+ end
+
+ it "dumps a String with instance variables" do
+ str = ""
+ str.instance_variable_set("@foo", "bar")
+ Marshal.dump(str.force_encoding("binary")).should == "\x04\bI\"\x00\x06:\t@foo\"\bbar"
+ end
+
+ it "dumps a US-ASCII String" do
+ str = "abc".force_encoding("us-ascii")
+ Marshal.dump(str).should == "\x04\bI\"\babc\x06:\x06EF"
+ end
+
+ it "dumps a UTF-8 String" do
+ str = "\x6d\xc3\xb6\x68\x72\x65".force_encoding("utf-8")
+ Marshal.dump(str).should == "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET"
+ end
+
+ it "dumps a String in another encoding" do
+ str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".force_encoding("utf-16le")
+ result = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE"
+ Marshal.dump(str).should == result
+ end
+
+ it "dumps multiple strings using symlinks for the :E (encoding) symbol" do
+ Marshal.dump(["".encode("us-ascii"), "".encode("utf-8")]).should == "\x04\b[\aI\"\x00\x06:\x06EFI\"\x00\x06;\x00T"
+ end
+ end
+
+ describe "with a Regexp" do
+ it "dumps a Regexp" do
+ Marshal.dump(/\A.\Z/).should == "\x04\bI/\n\\A.\\Z\x00\x06:\x06EF"
+ end
+
+ it "dumps a Regexp with flags" do
+ Marshal.dump(//im).should == "\x04\bI/\x00\x05\x06:\x06EF"
+ end
+
+ it "dumps a Regexp with instance variables" do
+ o = Regexp.new("")
+ o.instance_variable_set(:@ivar, :ivar)
+ Marshal.dump(o).should == "\x04\bI/\x00\x00\a:\x06EF:\n@ivar:\tivar"
+ end
+
+ it "dumps an extended Regexp" do
+ Marshal.dump(Regexp.new("").extend(Meths)).should == "\x04\bIe:\nMeths/\x00\x00\x06:\x06EF"
+ end
+
+ it "dumps a Regexp subclass" do
+ Marshal.dump(UserRegexp.new("")).should == "\x04\bIC:\x0FUserRegexp/\x00\x00\x06:\x06EF"
+ end
+
+ it "dumps a binary Regexp" do
+ o = Regexp.new("".force_encoding("binary"), Regexp::FIXEDENCODING)
+ Marshal.dump(o).should == "\x04\b/\x00\x10"
+ end
+
+ it "dumps a UTF-8 Regexp" do
+ o = Regexp.new("".force_encoding("utf-8"), Regexp::FIXEDENCODING)
+ Marshal.dump(o).should == "\x04\bI/\x00\x10\x06:\x06ET"
+ end
+
+ it "dumps a Regexp in another encoding" do
+ o = Regexp.new("".force_encoding("utf-16le"), Regexp::FIXEDENCODING)
+ Marshal.dump(o).should == "\x04\bI/\x00\x10\x06:\rencoding\"\rUTF-16LE"
+ end
+ end
+
+ describe "with an Array" do
+ it "dumps an empty Array" do
+ Marshal.dump([]).should == "\004\b[\000"
+ end
+
+ it "dumps a non-empty Array" do
+ Marshal.dump([:a, 1, 2]).should == "\004\b[\b:\006ai\006i\a"
+ end
+
+ it "dumps an Array subclass" do
+ Marshal.dump(UserArray.new).should == "\004\bC:\016UserArray[\000"
+ end
+
+ it "dumps a recursive Array" do
+ a = []
+ a << a
+ Marshal.dump(a).should == "\x04\b[\x06@\x00"
+ end
+
+ it "dumps an Array with instance variables" do
+ a = []
+ a.instance_variable_set(:@ivar, 1)
+ Marshal.dump(a).should == "\004\bI[\000\006:\n@ivari\006"
+ end
+
+ it "dumps an extended Array" do
+ Marshal.dump([].extend(Meths)).should == "\004\be:\nMeths[\000"
+ end
+ end
+
+ describe "with a Hash" do
+ it "dumps a Hash" do
+ Marshal.dump({}).should == "\004\b{\000"
+ end
+
+ it "dumps a Hash subclass" do
+ Marshal.dump(UserHash.new).should == "\004\bC:\rUserHash{\000"
+ end
+
+ it "dumps a Hash with a default value" do
+ Marshal.dump(Hash.new(1)).should == "\004\b}\000i\006"
+ end
+
+ it "raises a TypeError with hash having default proc" do
+ -> { Marshal.dump(Hash.new {}) }.should raise_error(TypeError)
+ end
+
+ it "dumps a Hash with instance variables" do
+ a = {}
+ a.instance_variable_set(:@ivar, 1)
+ Marshal.dump(a).should == "\004\bI{\000\006:\n@ivari\006"
+ end
+
+ it "dumps an extended Hash" do
+ Marshal.dump({}.extend(Meths)).should == "\004\be:\nMeths{\000"
+ end
+
+ it "dumps an Hash subclass with a parameter to initialize" do
+ Marshal.dump(UserHashInitParams.new(1)).should == "\004\bIC:\027UserHashInitParams{\000\006:\a@ai\006"
+ end
+ end
+
+ describe "with a Struct" do
+ it "dumps a Struct" do
+ Marshal.dump(Struct::Pyramid.new).should == "\004\bS:\024Struct::Pyramid\000"
+ end
+
+ it "dumps a Struct" do
+ Marshal.dump(Struct::Useful.new(1, 2)).should == "\004\bS:\023Struct::Useful\a:\006ai\006:\006bi\a"
+ end
+
+ it "dumps a Struct with instance variables" do
+ st = Struct.new("Thick").new
+ st.instance_variable_set(:@ivar, 1)
+ Marshal.dump(st).should == "\004\bIS:\022Struct::Thick\000\006:\n@ivari\006"
+ Struct.send(:remove_const, :Thick)
+ end
+
+ it "dumps an extended Struct" do
+ obj = Struct.new("Extended", :a, :b).new.extend(Meths)
+ Marshal.dump(obj).should == "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0"
+
+ s = 'hi'
+ obj.a = [:a, s]
+ obj.b = [:Meths, s]
+ Marshal.dump(obj).should == "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a"
+ Struct.send(:remove_const, :Extended)
+ end
+ end
+
+ describe "with an Object" do
+ it "dumps an Object" do
+ Marshal.dump(Object.new).should == "\004\bo:\x0BObject\x00"
+ end
+
+ it "dumps an extended Object" do
+ Marshal.dump(Object.new.extend(Meths)).should == "\004\be:\x0AMethso:\x0BObject\x00"
+ end
+
+ it "dumps an Object with an instance variable" do
+ obj = Object.new
+ obj.instance_variable_set(:@ivar, 1)
+ Marshal.dump(obj).should == "\004\bo:\vObject\006:\n@ivari\006"
+ end
+
+ it "dumps an Object with a non-US-ASCII instance variable" do
+ obj = Object.new
+ ivar = "@é".force_encoding(Encoding::UTF_8).to_sym
+ obj.instance_variable_set(ivar, 1)
+ Marshal.dump(obj).should == "\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06"
+ end
+
+ it "dumps an Object that has had an instance variable added and removed as though it was never set" do
+ obj = Object.new
+ obj.instance_variable_set(:@ivar, 1)
+ obj.send(:remove_instance_variable, :@ivar)
+ Marshal.dump(obj).should == "\004\bo:\x0BObject\x00"
+ end
+
+ it "dumps an Object if it has a singleton class but no singleton methods" do
+ obj = Object.new
+ obj.singleton_class
+ Marshal.dump(obj).should == "\004\bo:\x0BObject\x00"
+ end
+
+ it "raises if an Object has a singleton class and singleton methods" do
+ obj = Object.new
+ def obj.foo; end
+ -> {
+ Marshal.dump(obj)
+ }.should raise_error(TypeError, "singleton can't be dumped")
+ end
+
+ it "dumps a BasicObject subclass if it defines respond_to?" do
+ obj = MarshalSpec::BasicObjectSubWithRespondToFalse.new
+ Marshal.dump(obj).should == "\x04\bo:2MarshalSpec::BasicObjectSubWithRespondToFalse\x00"
+ end
+ end
+
+ describe "with a Range" do
+ it "dumps a Range inclusive of end (with indeterminant order)" do
+ dump = Marshal.dump(1..2)
+ load = Marshal.load(dump)
+ load.should == (1..2)
+ end
+
+ it "dumps a Range exclusive of end (with indeterminant order)" do
+ dump = Marshal.dump(1...2)
+ load = Marshal.load(dump)
+ load.should == (1...2)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "dumps a Range with extra instance variables" do
+ range = (1...3)
+ range.instance_variable_set :@foo, 42
+ dump = Marshal.dump(range)
+ load = Marshal.load(dump)
+ load.should == range
+ load.instance_variable_get(:@foo).should == 42
+ end
+ end
+ end
+
+ describe "with a Time" do
+ before :each do
+ @internal = Encoding.default_internal
+ Encoding.default_internal = Encoding::UTF_8
+
+ @utc = Time.utc(2012, 1, 1)
+ @utc_dump = @utc.send(:_dump)
+
+ with_timezone 'AST', 3 do
+ @t = Time.local(2012, 1, 1)
+ @fract = Time.local(2012, 1, 1, 1, 59, 56.2)
+ @t_dump = @t.send(:_dump)
+ @fract_dump = @fract.send(:_dump)
+ end
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "dumps the zone and the offset" do
+ with_timezone 'AST', 3 do
+ dump = Marshal.dump(@t)
+ base = "\x04\bIu:\tTime\r#{@t_dump}\a"
+ offset = ":\voffseti\x020*"
+ zone = ":\tzoneI\"\bAST\x06:\x06EF" # Last is 'F' (US-ASCII)
+ [ "#{base}#{offset}#{zone}", "#{base}#{zone}#{offset}" ].should include(dump)
+ end
+ end
+
+ it "dumps the zone, but not the offset if zone is UTC" do
+ dump = Marshal.dump(@utc)
+ zone = ":\tzoneI\"\bUTC\x06:\x06EF" # Last is 'F' (US-ASCII)
+ dump.should == "\x04\bIu:\tTime\r#{@utc_dump}\x06#{zone}"
+ end
+ end
+
+ describe "with an Exception" do
+ it "dumps an empty Exception" do
+ Marshal.dump(Exception.new).should == "\x04\bo:\x0EException\a:\tmesg0:\abt0"
+ end
+
+ it "dumps the message for the exception" do
+ Marshal.dump(Exception.new("foo")).should == "\x04\bo:\x0EException\a:\tmesg\"\bfoo:\abt0"
+ end
+
+ it "contains the filename in the backtrace" do
+ obj = Exception.new("foo")
+ obj.set_backtrace(["foo/bar.rb:10"])
+ Marshal.dump(obj).should == "\x04\bo:\x0EException\a:\tmesg\"\bfoo:\abt[\x06\"\x12foo/bar.rb:10"
+ end
+
+ it "dumps instance variables if they exist" do
+ obj = Exception.new("foo")
+ obj.instance_variable_set(:@ivar, 1)
+ Marshal.dump(obj).should == "\x04\bo:\x0EException\b:\tmesg\"\bfoo:\abt0:\n@ivari\x06"
+ end
+
+ it "dumps the cause for the exception" do
+ exc = nil
+ begin
+ raise StandardError, "the cause"
+ rescue StandardError => cause
+ begin
+ raise RuntimeError, "the consequence"
+ rescue RuntimeError => e
+ e.cause.should equal(cause)
+ exc = e
+ end
+ end
+
+ reloaded = Marshal.load(Marshal.dump(exc))
+ reloaded.cause.should be_an_instance_of(StandardError)
+ reloaded.cause.message.should == "the cause"
+ end
+ end
+
+ it "dumps subsequent appearances of a symbol as a link" do
+ Marshal.dump([:a, :a]).should == "\004\b[\a:\006a;\000"
+ end
+
+ it "dumps subsequent appearances of an object as a link" do
+ o = Object.new
+ Marshal.dump([o, o]).should == "\004\b[\ao:\vObject\000@\006"
+ end
+
+ MarshalSpec::DATA_19.each do |description, (object, marshal, attributes)|
+ it "#{description} returns a binary string" do
+ Marshal.dump(object).encoding.should == Encoding::BINARY
+ end
+ end
+
+ it "raises an ArgumentError when the recursion limit is exceeded" do
+ h = {'one' => {'two' => {'three' => 0}}}
+ -> { Marshal.dump(h, 3) }.should raise_error(ArgumentError)
+ -> { Marshal.dump([h], 4) }.should raise_error(ArgumentError)
+ -> { Marshal.dump([], 0) }.should raise_error(ArgumentError)
+ -> { Marshal.dump([[[]]], 1) }.should raise_error(ArgumentError)
+ end
+
+ it "ignores the recursion limit if the limit is negative" do
+ Marshal.dump([], -1).should == "\004\b[\000"
+ Marshal.dump([[]], -1).should == "\004\b[\006[\000"
+ Marshal.dump([[[]]], -1).should == "\004\b[\006[\006[\000"
+ end
+
+ describe "when passed an IO" do
+
+ it "writes the serialized data to the IO-Object" do
+ (obj = mock('test')).should_receive(:write).at_least(1)
+ Marshal.dump("test", obj)
+ end
+
+ it "returns the IO-Object" do
+ (obj = mock('test')).should_receive(:write).at_least(1)
+ Marshal.dump("test", obj).should == obj
+ end
+
+ it "raises an Error when the IO-Object does not respond to #write" do
+ obj = mock('test')
+ -> { Marshal.dump("test", obj) }.should raise_error(TypeError)
+ end
+
+
+ it "calls binmode when it's defined" do
+ obj = mock('test')
+ obj.should_receive(:write).at_least(1)
+ obj.should_receive(:binmode).at_least(1)
+ Marshal.dump("test", obj)
+ end
+
+
+ end
+
+ describe "when passed a StringIO" do
+ it "should raise an error" do
+ require "stringio"
+ -> { Marshal.dump(StringIO.new) }.should raise_error(TypeError)
+ end
+ end
+
+ it "raises a TypeError if marshalling a Method instance" do
+ -> { Marshal.dump(Marshal.method(:dump)) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if marshalling a Proc" do
+ -> { Marshal.dump(proc {}) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if dumping a IO/File instance" do
+ -> { Marshal.dump(STDIN) }.should raise_error(TypeError)
+ -> { File.open(__FILE__) { |f| Marshal.dump(f) } }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if dumping a MatchData instance" do
+ -> { Marshal.dump(/(.)/.match("foo")) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if dumping a Mutex instance" do
+ m = Mutex.new
+ -> { Marshal.dump(m) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/marshal/fixtures/classes.rb b/spec/ruby/core/marshal/fixtures/classes.rb
new file mode 100644
index 0000000000..7c81c64927
--- /dev/null
+++ b/spec/ruby/core/marshal/fixtures/classes.rb
@@ -0,0 +1,4 @@
+module MarshalSpec
+ # empty modules
+ module M1 end
+end
diff --git a/spec/ruby/core/marshal/fixtures/marshal_data.rb b/spec/ruby/core/marshal/fixtures/marshal_data.rb
new file mode 100644
index 0000000000..9373ef7ba8
--- /dev/null
+++ b/spec/ruby/core/marshal/fixtures/marshal_data.rb
@@ -0,0 +1,420 @@
+# -*- encoding: binary -*-
+class UserDefined
+ class Nested
+ def ==(other)
+ other.kind_of? self.class
+ end
+ end
+
+ attr_reader :a, :b
+
+ def initialize
+ @a = 'stuff'
+ @b = @a
+ end
+
+ def _dump(depth)
+ Marshal.dump [:stuff, :stuff]
+ end
+
+ def self._load(data)
+ a, b = Marshal.load data
+
+ obj = allocate
+ obj.instance_variable_set :@a, a
+ obj.instance_variable_set :@b, b
+
+ obj
+ end
+
+ def ==(other)
+ self.class === other and
+ @a == other.a and
+ @b == other.b
+ end
+end
+
+class UserDefinedWithIvar
+ attr_reader :a, :b, :c
+
+ def initialize
+ @a = 'stuff'
+ @a.instance_variable_set :@foo, :UserDefinedWithIvar
+ @b = 'more'
+ @c = @b
+ end
+
+ def _dump(depth)
+ Marshal.dump [:stuff, :more, :more]
+ end
+
+ def self._load(data)
+ a, b, c = Marshal.load data
+
+ obj = allocate
+ obj.instance_variable_set :@a, a
+ obj.instance_variable_set :@b, b
+ obj.instance_variable_set :@c, c
+
+ obj
+ end
+
+ def ==(other)
+ self.class === other and
+ @a == other.a and
+ @b == other.b and
+ @c == other.c and
+ @a.instance_variable_get(:@foo) == other.a.instance_variable_get(:@foo)
+ end
+end
+
+class UserDefinedImmediate
+ def _dump(depth)
+ ''
+ end
+
+ def self._load(data)
+ nil
+ end
+end
+
+class UserPreviouslyDefinedWithInitializedIvar
+ attr_accessor :field1, :field2
+end
+
+class UserMarshal
+ attr_accessor :data
+
+ def initialize
+ @data = 'stuff'
+ end
+ def marshal_dump() :data end
+ def marshal_load(data) @data = data end
+ def ==(other) self.class === other and @data == other.data end
+end
+
+class UserMarshalWithClassName < UserMarshal
+ def self.name
+ "Never::A::Real::Class"
+ end
+end
+
+class UserMarshalWithIvar
+ attr_reader :data
+
+ def initialize
+ @data = 'my data'
+ end
+
+ def marshal_dump
+ [:data]
+ end
+
+ def marshal_load(o)
+ @data = o.first
+ end
+
+ def ==(other)
+ self.class === other and
+ @data = other.data
+ end
+end
+
+class UserArray < Array
+end
+
+class UserHash < Hash
+end
+
+class UserHashInitParams < Hash
+ def initialize(a)
+ @a = a
+ end
+end
+
+class UserObject
+end
+
+class UserRegexp < Regexp
+end
+
+class UserString < String
+end
+
+class UserCustomConstructorString < String
+ def initialize(arg1, arg2)
+ end
+end
+
+module Meths
+ def meths_method() end
+end
+
+module MethsMore
+ def meths_more_method() end
+end
+
+Struct.new "Pyramid"
+Struct.new "Useful", :a, :b
+
+module MarshalSpec
+ class StructWithUserInitialize < Struct.new(:a)
+ THREADLOCAL_KEY = :marshal_load_struct_args
+ def initialize(*args)
+ # using thread-local to avoid ivar marshaling
+ Thread.current[THREADLOCAL_KEY] = args
+ super(*args)
+ end
+ end
+
+ class BasicObjectSubWithRespondToFalse < BasicObject
+ def respond_to?(method_name, include_all=false)
+ false
+ end
+ end
+
+ def self.random_data
+ randomizer = Random.new(42)
+ 1000.times{randomizer.rand} # Make sure we exhaust his first state of 624 random words
+ dump_data = File.binread(fixture(__FILE__, 'random.dump'))
+ [randomizer, dump_data]
+ rescue => e
+ ["Error when building Random marshal data #{e}", ""]
+ end
+
+ SwappedClass = nil
+ def self.set_swapped_class(cls)
+ remove_const(:SwappedClass)
+ const_set(:SwappedClass, cls)
+ end
+
+ def self.reset_swapped_class
+ set_swapped_class(nil)
+ end
+
+ DATA = {
+ "nil" => [nil, "\004\b0"],
+ "1..2" => [(1..2),
+ "\004\bo:\nRange\b:\nbegini\006:\texclF:\bendi\a",
+ { begin: 1, end: 2, :exclude_end? => false }],
+ "1...2" => [(1...2),
+ "\004\bo:\nRange\b:\nbegini\006:\texclT:\bendi\a",
+ { begin: 1, end: 2, :exclude_end? => true }],
+ "'a'..'b'" => [('a'..'b'),
+ "\004\bo:\nRange\b:\nbegin\"\006a:\texclF:\bend\"\006b",
+ { begin: 'a', end: 'b', :exclude_end? => false }],
+ "Struct" => [Struct::Useful.new(1, 2),
+ "\004\bS:\023Struct::Useful\a:\006ai\006:\006bi\a"],
+ "Symbol" => [:symbol,
+ "\004\b:\vsymbol"],
+ "true" => [true,
+ "\004\bT"],
+ "false" => [false,
+ "\004\bF"],
+ "String empty" => ['',
+ "\004\b\"\000"],
+ "String small" => ['small',
+ "\004\b\"\012small"],
+ "String big" => ['big' * 100,
+ "\004\b\"\002,\001#{'big' * 100}"],
+ "String extended" => [''.extend(Meths), # TODO: check for module on load
+ "\004\be:\nMeths\"\000"],
+ "String subclass" => [UserString.new,
+ "\004\bC:\017UserString\"\000"],
+ "String subclass extended" => [UserString.new.extend(Meths),
+ "\004\be:\nMethsC:\017UserString\"\000"],
+ "Symbol small" => [:big,
+ "\004\b:\010big"],
+ "Symbol big" => [('big' * 100).to_sym,
+ "\004\b:\002,\001#{'big' * 100}"],
+ "Integer -2**64" => [-2**64,
+ "\004\bl-\n\000\000\000\000\000\000\000\000\001\000"],
+ "Integer -2**63" => [-2**63,
+ "\004\bl-\t\000\000\000\000\000\000\000\200"],
+ "Integer -2**24" => [-2**24,
+ "\004\bi\375\000\000\000"],
+ "Integer -4516727" => [-4516727,
+ "\004\bi\375\211\024\273"],
+ "Integer -2**16" => [-2**16,
+ "\004\bi\376\000\000"],
+ "Integer -2**8" => [-2**8,
+ "\004\bi\377\000"],
+ "Integer -123" => [-123,
+ "\004\bi\200"],
+ "Integer -124" => [-124, "\004\bi\377\204"],
+ "Integer 0" => [0,
+ "\004\bi\000"],
+ "Integer 5" => [5,
+ "\004\bi\n"],
+ "Integer 122" => [122, "\004\bi\177"],
+ "Integer 123" => [123, "\004\bi\001{"],
+ "Integer 2**8" => [2**8,
+ "\004\bi\002\000\001"],
+ "Integer 2**16" => [2**16,
+ "\004\bi\003\000\000\001"],
+ "Integer 2**24" => [2**24,
+ "\004\bi\004\000\000\000\001"],
+ "Integer 2**64" => [2**64,
+ "\004\bl+\n\000\000\000\000\000\000\000\000\001\000"],
+ "Integer 2**90" => [2**90,
+ "\004\bl+\v#{"\000" * 11}\004"],
+ "Class String" => [String,
+ "\004\bc\vString"],
+ "Module Marshal" => [Marshal,
+ "\004\bm\fMarshal"],
+ "Module nested" => [UserDefined::Nested.new,
+ "\004\bo:\030UserDefined::Nested\000"],
+ "_dump object" => [UserDefinedWithIvar.new,
+ "\004\bu:\030UserDefinedWithIvar5\004\b[\bI\"\nstuff\006:\t@foo:\030UserDefinedWithIvar\"\tmore@\a"],
+ "_dump object extended" => [UserDefined.new.extend(Meths),
+ "\004\bu:\020UserDefined\022\004\b[\a\"\nstuff@\006"],
+ "marshal_dump object" => [UserMarshalWithIvar.new,
+ "\004\bU:\030UserMarshalWithIvar[\006\"\fmy data"],
+ "Regexp" => [/\A.\Z/,
+ "\004\b/\n\\A.\\Z\000"],
+ "Regexp subclass /i" => [UserRegexp.new('', Regexp::IGNORECASE),
+ "\004\bC:\017UserRegexp/\000\001"],
+ "Float 0.0" => [0.0,
+ "\004\bf\0060"],
+ "Float -0.0" => [-0.0,
+ "\004\bf\a-0"],
+ "Float Infinity" => [(1.0 / 0.0),
+ "\004\bf\binf"],
+ "Float -Infinity" => [(-1.0 / 0.0),
+ "\004\bf\t-inf"],
+ "Float 1.0" => [1.0,
+ "\004\bf\0061"],
+ "Float 8323434.342" => [8323434.342,
+ "\004\bf\0328323434.3420000002\000S\370"],
+ "Float 1.0799999999999912" => [1.0799999999999912,
+ "\004\bf\0321.0799999999999912\000\341 "],
+ "Hash" => [Hash.new,
+ "\004\b{\000"],
+ "Hash subclass" => [UserHash.new,
+ "\004\bC:\rUserHash{\000"],
+ "Array" => [Array.new,
+ "\004\b[\000"],
+ "Array subclass" => [UserArray.new,
+ "\004\bC:\016UserArray[\000"],
+ "Struct Pyramid" => [Struct::Pyramid.new,
+ "\004\bS:\024Struct::Pyramid\000"],
+ }
+ DATA_19 = {
+ "nil" => [nil, "\004\b0"],
+ "1..2" => [(1..2),
+ "\004\bo:\nRange\b:\nbegini\006:\texclF:\bendi\a",
+ { begin: 1, end: 2, :exclude_end? => false }],
+ "1...2" => [(1...2),
+ "\004\bo:\nRange\b:\nbegini\006:\texclT:\bendi\a",
+ { begin: 1, end: 2, :exclude_end? => true }],
+ "'a'..'b'" => [('a'..'b'),
+ "\004\bo:\nRange\b:\nbegin\"\006a:\texclF:\bend\"\006b",
+ { begin: 'a', end: 'b', :exclude_end? => false }],
+ "Struct" => [Struct::Useful.new(1, 2),
+ "\004\bS:\023Struct::Useful\a:\006ai\006:\006bi\a"],
+ "Symbol" => [:symbol,
+ "\004\b:\vsymbol"],
+ "true" => [true,
+ "\004\bT"],
+ "false" => [false,
+ "\004\bF"],
+ "String empty" => ['',
+ "\x04\bI\"\x00\x06:\x06EF"],
+ "String small" => ['small',
+ "\x04\bI\"\nsmall\x06:\x06EF"],
+ "String big" => ['big' * 100,
+ "\x04\bI\"\x02,\x01bigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbig\x06:\x06EF"],
+ "String extended" => [''.extend(Meths), # TODO: check for module on load
+ "\x04\bIe:\nMeths\"\x00\x06:\x06EF"],
+ "String subclass" => [UserString.new,
+ "\004\bC:\017UserString\"\000"],
+ "String subclass extended" => [UserString.new.extend(Meths),
+ "\004\be:\nMethsC:\017UserString\"\000"],
+ "Symbol small" => [:big,
+ "\004\b:\010big"],
+ "Symbol big" => [('big' * 100).to_sym,
+ "\004\b:\002,\001#{'big' * 100}"],
+ "Integer -2**64" => [-2**64,
+ "\004\bl-\n\000\000\000\000\000\000\000\000\001\000"],
+ "Integer -2**63" => [-2**63,
+ "\004\bl-\t\000\000\000\000\000\000\000\200"],
+ "Integer -2**24" => [-2**24,
+ "\004\bi\375\000\000\000"],
+ "Integer -2**16" => [-2**16,
+ "\004\bi\376\000\000"],
+ "Integer -2**8" => [-2**8,
+ "\004\bi\377\000"],
+ "Integer -123" => [-123,
+ "\004\bi\200"],
+ "Integer 0" => [0,
+ "\004\bi\000"],
+ "Integer 5" => [5,
+ "\004\bi\n"],
+ "Integer 2**8" => [2**8,
+ "\004\bi\002\000\001"],
+ "Integer 2**16" => [2**16,
+ "\004\bi\003\000\000\001"],
+ "Integer 2**24" => [2**24,
+ "\004\bi\004\000\000\000\001"],
+ "Integer 2**64" => [2**64,
+ "\004\bl+\n\000\000\000\000\000\000\000\000\001\000"],
+ "Integer 2**90" => [2**90,
+ "\004\bl+\v#{"\000" * 11}\004"],
+ "Class String" => [String,
+ "\004\bc\vString"],
+ "Module Marshal" => [Marshal,
+ "\004\bm\fMarshal"],
+ "Module nested" => [UserDefined::Nested.new,
+ "\004\bo:\030UserDefined::Nested\000"],
+ "_dump object" => [UserDefinedWithIvar.new,
+ "\x04\bu:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a"],
+ "_dump object extended" => [UserDefined.new.extend(Meths),
+ "\x04\bu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06"],
+ "marshal_dump object" => [UserMarshalWithIvar.new,
+ "\x04\bU:\x18UserMarshalWithIvar[\x06I\"\fmy data\x06:\x06EF"],
+ "Regexp" => [/\A.\Z/,
+ "\x04\bI/\n\\A.\\Z\x00\x06:\x06EF"],
+ "Regexp subclass /i" => [UserRegexp.new('', Regexp::IGNORECASE),
+ "\x04\bIC:\x0FUserRegexp/\x00\x01\x06:\x06EF"],
+ "Float 0.0" => [0.0,
+ "\004\bf\0060"],
+ "Float -0.0" => [-0.0,
+ "\004\bf\a-0"],
+ "Float Infinity" => [(1.0 / 0.0),
+ "\004\bf\binf"],
+ "Float -Infinity" => [(-1.0 / 0.0),
+ "\004\bf\t-inf"],
+ "Float 1.0" => [1.0,
+ "\004\bf\0061"],
+ "Hash" => [Hash.new,
+ "\004\b{\000"],
+ "Hash subclass" => [UserHash.new,
+ "\004\bC:\rUserHash{\000"],
+ "Array" => [Array.new,
+ "\004\b[\000"],
+ "Array subclass" => [UserArray.new,
+ "\004\bC:\016UserArray[\000"],
+ "Struct Pyramid" => [Struct::Pyramid.new,
+ "\004\bS:\024Struct::Pyramid\000"],
+ "Random" => random_data,
+ }
+end
+
+class ArraySub < Array
+ def initialize(*args)
+ super(args)
+ end
+end
+
+class ArraySubPush < Array
+ def << value
+ raise 'broken'
+ end
+ alias_method :push, :<<
+end
+
+class SameName
+end
+
+module NamespaceTest
+end
diff --git a/spec/ruby/core/marshal/fixtures/random.dump b/spec/ruby/core/marshal/fixtures/random.dump
new file mode 100644
index 0000000000..0af56120aa
--- /dev/null
+++ b/spec/ruby/core/marshal/fixtures/random.dump
Binary files differ
diff --git a/spec/ruby/core/marshal/float_spec.rb b/spec/ruby/core/marshal/float_spec.rb
new file mode 100644
index 0000000000..5793bbd564
--- /dev/null
+++ b/spec/ruby/core/marshal/float_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../spec_helper'
+
+describe "Marshal.dump with Float" do
+ it "represents NaN" do
+ Marshal.dump(nan_value).should == "\004\bf\bnan"
+ end
+
+ it "represents +Infinity" do
+ Marshal.dump(infinity_value).should == "\004\bf\binf"
+ end
+
+ it "represents -Infinity" do
+ Marshal.dump(-infinity_value).should == "\004\bf\t-inf"
+ end
+
+ it "represents zero" do
+ Marshal.dump(0.0).should == "\004\bf\0060"
+ end
+
+ it "represents a Float less than 1" do
+ Marshal.dump(0.666666667).should == "\x04\bf\x100.666666667"
+ end
+
+ it "represents a Float much less than 1" do
+ Marshal.dump(0.000000001234697).should == "\x04\bf\x101.234697e-9"
+ end
+
+ it "represents a Float greater than 1" do
+ Marshal.dump(42.666666667).should == "\x04\bf\x1142.666666667"
+ end
+
+ it "represents a Float much greater than 1" do
+ Marshal.dump(98743561239011.0).should == "\x04\bf\x1398743561239011"
+ end
+
+ it "represents a Float much greater than 1 with a very small fractional part" do
+ Marshal.dump(799346183459.0000000002999312541).should == "\x04\bf\x11799346183459"
+ end
+end
+
+describe "Marshal.load with Float" do
+ it "loads NaN" do
+ Marshal.load("\004\bf\bnan").should be_nan
+ end
+
+ it "loads +Infinity" do
+ Marshal.load("\004\bf\binf").should == infinity_value
+ end
+
+ it "loads -Infinity" do
+ Marshal.load("\004\bf\t-inf").should == -infinity_value
+ end
+
+ it "loads zero" do
+ Marshal.load("\004\bf\0060").should == 0.0
+ end
+
+ it "loads a Float less than 1" do
+ Marshal.load("\x04\bf\x100.666666667").should == 0.666666667
+ end
+
+ it "loads a Float much less than 1" do
+ Marshal.load("\x04\bf\x101.234697e-9").should == 0.000000001234697
+ end
+
+ it "loads a Float greater than 1" do
+ Marshal.load("\x04\bf\x1142.666666667").should == 42.666666667
+ end
+
+ it "loads a Float much greater than 1" do
+ Marshal.load("\x04\bf\x1398743561239011").should == 98743561239011.0
+ end
+
+ it "loads a Float much greater than 1 with a very small fractional part" do
+ Marshal.load("\x04\bf\x16793468359.0002999").should == 793468359.0002999
+ end
+end
diff --git a/spec/ruby/core/marshal/load_spec.rb b/spec/ruby/core/marshal/load_spec.rb
new file mode 100644
index 0000000000..a5bdfbf520
--- /dev/null
+++ b/spec/ruby/core/marshal/load_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/load'
+
+describe "Marshal.load" do
+ it_behaves_like :marshal_load, :load
+end
diff --git a/spec/ruby/core/marshal/major_version_spec.rb b/spec/ruby/core/marshal/major_version_spec.rb
new file mode 100644
index 0000000000..931f1f6c27
--- /dev/null
+++ b/spec/ruby/core/marshal/major_version_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Marshal::MAJOR_VERSION" do
+ it "is 4" do
+ Marshal::MAJOR_VERSION.should == 4
+ end
+end
diff --git a/spec/ruby/core/marshal/minor_version_spec.rb b/spec/ruby/core/marshal/minor_version_spec.rb
new file mode 100644
index 0000000000..f19210c4e1
--- /dev/null
+++ b/spec/ruby/core/marshal/minor_version_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Marshal::MINOR_VERSION" do
+ it "is 8" do
+ Marshal::MINOR_VERSION.should == 8
+ end
+end
diff --git a/spec/ruby/core/marshal/restore_spec.rb b/spec/ruby/core/marshal/restore_spec.rb
new file mode 100644
index 0000000000..7e75d7dea6
--- /dev/null
+++ b/spec/ruby/core/marshal/restore_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/load'
+
+describe "Marshal.restore" do
+ it_behaves_like :marshal_load, :restore
+end
diff --git a/spec/ruby/core/marshal/shared/load.rb b/spec/ruby/core/marshal/shared/load.rb
new file mode 100644
index 0000000000..479c7477ec
--- /dev/null
+++ b/spec/ruby/core/marshal/shared/load.rb
@@ -0,0 +1,987 @@
+# -*- encoding: binary -*-
+require_relative '../fixtures/marshal_data'
+
+describe :marshal_load, shared: true do
+ before :all do
+ @num_self_class = 1
+ end
+
+ it "raises an ArgumentError when the dumped data is truncated" do
+ obj = {first: 1, second: 2, third: 3}
+ -> { Marshal.send(@method, Marshal.dump(obj)[0, 5]) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the dumped class is missing" do
+ Object.send(:const_set, :KaBoom, Class.new)
+ kaboom = Marshal.dump(KaBoom.new)
+ Object.send(:remove_const, :KaBoom)
+
+ -> { Marshal.send(@method, kaboom) }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is "3.1" do
+ describe "when called with freeze: true" do
+ it "returns frozen strings" do
+ string = Marshal.send(@method, Marshal.dump("foo"), freeze: true)
+ string.should == "foo"
+ string.should.frozen?
+
+ utf8_string = "foo".encode(Encoding::UTF_8)
+ string = Marshal.send(@method, Marshal.dump(utf8_string), freeze: true)
+ string.should == utf8_string
+ string.should.frozen?
+ end
+
+ it "returns frozen arrays" do
+ array = Marshal.send(@method, Marshal.dump([1, 2, 3]), freeze: true)
+ array.should == [1, 2, 3]
+ array.should.frozen?
+ end
+
+ it "returns frozen hashes" do
+ hash = Marshal.send(@method, Marshal.dump({foo: 42}), freeze: true)
+ hash.should == {foo: 42}
+ hash.should.frozen?
+ end
+
+ it "returns frozen regexps" do
+ regexp = Marshal.send(@method, Marshal.dump(/foo/), freeze: true)
+ regexp.should == /foo/
+ regexp.should.frozen?
+ end
+
+ it "returns frozen objects" do
+ source_object = Object.new
+ source_object.instance_variable_set(:@foo, "bar")
+
+ object = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
+ object.should.frozen?
+ object.instance_variable_get(:@foo).should.frozen?
+ end
+
+ it "does not freeze modules" do
+ Marshal.send(@method, Marshal.dump(Kernel), freeze: true)
+ Kernel.should_not.frozen?
+ end
+
+ it "does not freeze classes" do
+ Marshal.send(@method, Marshal.dump(Object), freeze: true)
+ Object.should_not.frozen?
+ end
+
+ describe "when called with a proc" do
+ it "call the proc with frozen objects" do
+ arr = []
+ s = 'hi'
+ s.instance_variable_set(:@foo, 5)
+ st = Struct.new("Brittle", :a).new
+ st.instance_variable_set(:@clue, 'none')
+ st.a = 0.0
+ h = Hash.new('def')
+ h['nine'] = 9
+ a = [:a, :b, :c]
+ a.instance_variable_set(:@two, 2)
+ obj = [s, 10, s, s, st, a]
+ obj.instance_variable_set(:@zoo, 'ant')
+ proc = Proc.new { |o| arr << o; o}
+
+ Marshal.send(
+ @method,
+ "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F",
+ proc,
+ freeze: true,
+ )
+
+ arr.should == [
+ false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st,
+ :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]],
+ ]
+
+ arr.each do |v|
+ v.should.frozen?
+ end
+
+ Struct.send(:remove_const, :Brittle)
+ end
+
+ it "does not freeze the object returned by the proc" do
+ string = Marshal.send(@method, Marshal.dump("foo"), proc { |o| o.upcase }, freeze: true)
+ string.should == "FOO"
+ string.should_not.frozen?
+ end
+ end
+ end
+ end
+
+ describe "when called with a proc" do
+ ruby_bug "#18141", ""..."3.1" do
+ it "call the proc with fully initialized strings" do
+ utf8_string = "foo".encode(Encoding::UTF_8)
+ Marshal.send(@method, Marshal.dump(utf8_string), proc { |arg|
+ if arg.is_a?(String)
+ arg.should == utf8_string
+ arg.encoding.should == Encoding::UTF_8
+ end
+ arg
+ })
+ end
+
+ it "no longer mutate the object after it was passed to the proc" do
+ string = Marshal.load(Marshal.dump("foo"), :freeze.to_proc)
+ string.should.frozen?
+ end
+ end
+
+ it "returns the value of the proc" do
+ Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should == [3,4]
+ end
+
+ ruby_bug "#18141", ""..."3.1" do
+ it "calls the proc for recursively visited data" do
+ a = [1]
+ a << a
+ ret = []
+ Marshal.send(@method, Marshal.dump(a), proc { |arg| ret << arg.inspect; arg })
+ ret[0].should == 1.inspect
+ ret[1].should == a.inspect
+ ret.size.should == 2
+ end
+
+ it "loads an Array with proc" do
+ arr = []
+ s = 'hi'
+ s.instance_variable_set(:@foo, 5)
+ st = Struct.new("Brittle", :a).new
+ st.instance_variable_set(:@clue, 'none')
+ st.a = 0.0
+ h = Hash.new('def')
+ h['nine'] = 9
+ a = [:a, :b, :c]
+ a.instance_variable_set(:@two, 2)
+ obj = [s, 10, s, s, st, a]
+ obj.instance_variable_set(:@zoo, 'ant')
+ proc = Proc.new { |o| arr << o.dup; o}
+
+ Marshal.send(@method, "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc)
+
+ arr.should == [
+ false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st,
+ :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]],
+ ]
+ Struct.send(:remove_const, :Brittle)
+ end
+ end
+ end
+
+ describe "when called with nil for the proc argument" do
+ it "behaves as if no proc argument was passed" do
+ a = [1]
+ a << a
+ b = Marshal.send(@method, Marshal.dump(a), nil)
+ b.should == a
+ end
+ end
+
+ describe "when called on objects with custom _dump methods" do
+ it "does not set instance variables of an object with user-defined _dump/_load" do
+ # this string represents: <#UserPreviouslyDefinedWithInitializedIvar @field2=7 @field1=6>
+ dump_str = "\004\bu:-UserPreviouslyDefinedWithInitializedIvar\a:\f@field2i\f:\f@field1i\v"
+
+ UserPreviouslyDefinedWithInitializedIvar.should_receive(:_load).and_return(UserPreviouslyDefinedWithInitializedIvar.new)
+ marshaled_obj = Marshal.send(@method, dump_str)
+
+ marshaled_obj.should be_an_instance_of(UserPreviouslyDefinedWithInitializedIvar)
+ marshaled_obj.field1.should be_nil
+ marshaled_obj.field2.should be_nil
+ end
+
+ describe "that return an immediate value" do
+ it "loads an array containing an instance of the object, followed by multiple instances of another object" do
+ str = "string"
+
+ # this string represents: [<#UserDefinedImmediate A>, <#String "string">, <#String "string">]
+ marshaled_obj = Marshal.send(@method, "\004\b[\bu:\031UserDefinedImmediate\000\"\vstring@\a")
+
+ marshaled_obj.should == [nil, str, str]
+ end
+
+ it "loads any structure with multiple references to the same object, followed by multiple instances of another object" do
+ str = "string"
+
+ # this string represents: {a: <#UserDefinedImmediate A>, b: <#UserDefinedImmediate A>, c: <#String "string">, d: <#String "string">}
+ hash_dump = "\x04\b{\t:\x06aIu:\x19UserDefinedImmediate\x00\x06:\x06ET:\x06b@\x06:\x06cI\"\vstring\x06;\aT:\x06d@\a"
+
+ marshaled_obj = Marshal.send(@method, hash_dump)
+ marshaled_obj.should == {a: nil, b: nil, c: str, d: str}
+
+ # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate A>, <#String "string">, <#String "string">]
+ array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ET@\x06I\"\vstring\x06;\x06T@\a"
+
+ marshaled_obj = Marshal.send(@method, array_dump)
+ marshaled_obj.should == [nil, nil, str, str]
+ end
+
+ it "loads an array containing references to multiple instances of the object, followed by multiple instances of another object" do
+ str = "string"
+
+ # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate B>, <#String "string">, <#String "string">]
+ array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ETIu;\x00\x00\x06;\x06TI\"\vstring\x06;\x06T@\b"
+
+ marshaled_obj = Marshal.send(@method, array_dump)
+ marshaled_obj.should == [nil, nil, str, str]
+ end
+ end
+ end
+
+ ruby_bug "#18141", ""..."3.1" do
+ it "loads an array containing objects having _dump method, and with proc" do
+ arr = []
+ myproc = Proc.new { |o| arr << o.dup; o }
+ o1 = UserDefined.new;
+ o2 = UserDefinedWithIvar.new
+ obj = [o1, o2, o1, o2]
+
+ Marshal.send(@method, "\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc)
+
+ arr[0].should == o1
+ arr[1].should == o2
+ arr[2].should == obj
+ arr.size.should == 3
+ end
+
+ it "loads an array containing objects having marshal_dump method, and with proc" do
+ arr = []
+ proc = Proc.new { |o| arr << o.dup; o }
+ o1 = UserMarshal.new
+ o2 = UserMarshalWithIvar.new
+
+ Marshal.send(@method, "\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc)
+
+ arr[0].should == 'stuff'
+ arr[1].should == o1
+ arr[2].should == 'my data'
+ arr[3].should == ['my data']
+ arr[4].should == o2
+ arr[5].should == [o1, o2, o1, o2]
+
+ arr.size.should == 6
+ end
+ end
+
+ it "assigns classes to nested subclasses of Array correctly" do
+ arr = ArraySub.new(ArraySub.new)
+ arr_dump = Marshal.dump(arr)
+ Marshal.send(@method, arr_dump).class.should == ArraySub
+ end
+
+ it "loads subclasses of Array with overridden << and push correctly" do
+ arr = ArraySubPush.new
+ arr[0] = '1'
+ arr_dump = Marshal.dump(arr)
+ Marshal.send(@method, arr_dump).should == arr
+ end
+
+ it "raises a TypeError with bad Marshal version" do
+ marshal_data = '\xff\xff'
+ marshal_data[0] = (Marshal::MAJOR_VERSION).chr
+ marshal_data[1] = (Marshal::MINOR_VERSION + 1).chr
+
+ -> { Marshal.send(@method, marshal_data) }.should raise_error(TypeError)
+
+ marshal_data = '\xff\xff'
+ marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr
+ marshal_data[1] = (Marshal::MINOR_VERSION).chr
+
+ -> { Marshal.send(@method, marshal_data) }.should raise_error(TypeError)
+ end
+
+ it "raises EOFError on loading an empty file" do
+ temp_file = tmp("marshal.rubyspec.tmp.#{Process.pid}")
+ file = File.new(temp_file, "w+")
+ begin
+ -> { Marshal.send(@method, file) }.should raise_error(EOFError)
+ ensure
+ file.close
+ rm_r temp_file
+ end
+ end
+
+ # Note: Ruby 1.9 should be compatible with older marshal format
+ MarshalSpec::DATA.each do |description, (object, marshal, attributes)|
+ it "loads a #{description}" do
+ Marshal.send(@method, marshal).should == object
+ end
+ end
+
+ MarshalSpec::DATA_19.each do |description, (object, marshal, attributes)|
+ it "loads a #{description}" do
+ Marshal.send(@method, marshal).should == object
+ end
+ end
+
+ describe "for an Array" do
+ it "loads an array containing the same objects" do
+ s = 'oh'
+ b = 'hi'
+ r = //
+ d = [b, :no, s, :go]
+ c = String
+ f = 1.0
+
+ o1 = UserMarshalWithIvar.new; o2 = UserMarshal.new
+
+ obj = [:so, 'hello', 100, :so, :so, d, :so, o2, :so, :no, o2,
+ :go, c, nil, Struct::Pyramid.new, f, :go, :no, s, b, r,
+ :so, 'huh', o1, true, b, b, 99, r, b, s, :so, f, c, :no, o1, d]
+
+ Marshal.send(@method, "\004\b[*:\aso\"\nhelloii;\000;\000[\t\"\ahi:\ano\"\aoh:\ago;\000U:\020UserMarshal\"\nstuff;\000;\006@\n;\ac\vString0S:\024Struct::Pyramid\000f\0061;\a;\006@\t@\b/\000\000;\000\"\bhuhU:\030UserMarshalWithIvar[\006\"\fmy dataT@\b@\bih@\017@\b@\t;\000@\016@\f;\006@\021@\a").should ==
+ obj
+ end
+
+ it "loads an array having ivar" do
+ s = 'well'
+ s.instance_variable_set(:@foo, 10)
+ obj = ['5', s, 'hi'].extend(Meths, MethsMore)
+ obj.instance_variable_set(:@mix, s)
+ new_obj = Marshal.send(@method, "\004\bI[\b\"\0065I\"\twell\006:\t@fooi\017\"\ahi\006:\t@mix@\a")
+ new_obj.should == obj
+ new_obj.instance_variable_get(:@mix).should equal new_obj[1]
+ new_obj[1].instance_variable_get(:@foo).should == 10
+ end
+
+ it "loads an extended Array object containing a user-marshaled object" do
+ obj = [UserMarshal.new, UserMarshal.new].extend(Meths)
+ dump = "\x04\be:\nMeths[\ao:\x10UserMarshal\x06:\n@dataI\"\nstuff\x06:\x06ETo;\x06\x06;\aI\"\nstuff\x06;\bT"
+ new_obj = Marshal.send(@method, dump)
+
+ new_obj.should == obj
+ obj_ancestors = class << obj; ancestors[1..-1]; end
+ new_obj_ancestors = class << new_obj; ancestors[1..-1]; end
+ obj_ancestors.should == new_obj_ancestors
+ end
+ end
+
+ describe "for a Hash" do
+ it "loads an extended_user_hash with a parameter to initialize" do
+ obj = UserHashInitParams.new(:abc).extend(Meths)
+
+ new_obj = Marshal.send(@method, "\004\bIe:\nMethsC:\027UserHashInitParams{\000\006:\a@a:\babc")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class].should == Meths
+ new_obj_metaclass_ancestors[@num_self_class+1].should == UserHashInitParams
+ end
+
+ it "loads an extended hash object containing a user-marshaled object" do
+ obj = {a: UserMarshal.new}.extend(Meths)
+
+ new_obj = Marshal.send(@method, "\004\be:\nMeths{\006:\006aU:\020UserMarshal\"\nstuff")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class].should == Meths
+ new_obj_metaclass_ancestors[@num_self_class+1].should == Hash
+ end
+
+ it "preserves hash ivars when hash contains a string having ivar" do
+ s = 'string'
+ s.instance_variable_set :@string_ivar, 'string ivar'
+ h = { key: s }
+ h.instance_variable_set :@hash_ivar, 'hash ivar'
+
+ unmarshalled = Marshal.send(@method, Marshal.dump(h))
+ unmarshalled.instance_variable_get(:@hash_ivar).should == 'hash ivar'
+ unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar'
+ end
+ end
+
+ describe "for a Symbol" do
+ it "loads a Symbol" do
+ sym = Marshal.send(@method, "\004\b:\vsymbol")
+ sym.should == :symbol
+ sym.encoding.should == Encoding::US_ASCII
+ end
+
+ it "loads a big Symbol" do
+ sym = ('big' * 100).to_sym
+ Marshal.send(@method, "\004\b:\002,\001#{'big' * 100}").should == sym
+ end
+
+ it "loads an encoded Symbol" do
+ s = "\u2192"
+
+ sym = Marshal.send(@method, "\x04\bI:\b\xE2\x86\x92\x06:\x06ET")
+ sym.should == s.encode("utf-8").to_sym
+ sym.encoding.should == Encoding::UTF_8
+
+ sym = Marshal.send(@method, "\x04\bI:\t\xFE\xFF!\x92\x06:\rencoding\"\vUTF-16")
+ sym.should == s.encode("utf-16").to_sym
+ sym.encoding.should == Encoding::UTF_16
+
+ sym = Marshal.send(@method, "\x04\bI:\a\x92!\x06:\rencoding\"\rUTF-16LE")
+ sym.should == s.encode("utf-16le").to_sym
+ sym.encoding.should == Encoding::UTF_16LE
+
+ sym = Marshal.send(@method, "\x04\bI:\a!\x92\x06:\rencoding\"\rUTF-16BE")
+ sym.should == s.encode("utf-16be").to_sym
+ sym.encoding.should == Encoding::UTF_16BE
+
+ sym = Marshal.send(@method, "\x04\bI:\a\xA2\xAA\x06:\rencoding\"\vEUC-JP")
+ sym.should == s.encode("euc-jp").to_sym
+ sym.encoding.should == Encoding::EUC_JP
+
+ sym = Marshal.send(@method, "\x04\bI:\a\x81\xA8\x06:\rencoding\"\x10Windows-31J")
+ sym.should == s.encode("sjis").to_sym
+ sym.encoding.should == Encoding::SJIS
+ end
+
+ it "loads a binary encoded Symbol" do
+ s = "\u2192".force_encoding("binary").to_sym
+ sym = Marshal.send(@method, "\x04\b:\b\xE2\x86\x92")
+ sym.should == s
+ sym.encoding.should == Encoding::BINARY
+ end
+
+ it "loads multiple Symbols sharing the same encoding" do
+ # Note that the encoding is a link for the second Symbol
+ symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET"
+ symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T"
+ dump = "\x04\b[\a#{symbol1}#{symbol2}"
+ value = Marshal.send(@method, dump)
+ value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8]
+ expected = [
+ "€a".force_encoding(Encoding::UTF_8).to_sym,
+ "€b".force_encoding(Encoding::UTF_8).to_sym
+ ]
+ value.should == expected
+
+ value = Marshal.send(@method, "\x04\b[\b#{symbol1}#{symbol2};\x00")
+ value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8]
+ value.should == [*expected, expected[0]]
+ end
+ end
+
+ describe "for a String" do
+ it "loads a string having ivar with ref to self" do
+ obj = 'hi'
+ obj.instance_variable_set(:@self, obj)
+ Marshal.send(@method, "\004\bI\"\ahi\006:\n@self@\000").should == obj
+ end
+
+ it "loads a string through StringIO stream" do
+ require 'stringio'
+ obj = "This is a string which should be unmarshalled through StringIO stream!"
+ Marshal.send(@method, StringIO.new(Marshal.dump(obj))).should == obj
+ end
+
+ it "loads a string with an ivar" do
+ str = Marshal.send(@method, "\x04\bI\"\x00\x06:\t@fooI\"\bbar\x06:\x06EF")
+ str.instance_variable_get("@foo").should == "bar"
+ end
+
+ it "loads a String subclass with custom constructor" do
+ str = Marshal.send(@method, "\x04\bC: UserCustomConstructorString\"\x00")
+ str.should be_an_instance_of(UserCustomConstructorString)
+ end
+
+ it "loads a US-ASCII String" do
+ str = "abc".force_encoding("us-ascii")
+ data = "\x04\bI\"\babc\x06:\x06EF"
+ result = Marshal.send(@method, data)
+ result.should == str
+ result.encoding.should equal(Encoding::US_ASCII)
+ end
+
+ it "loads a UTF-8 String" do
+ str = "\x6d\xc3\xb6\x68\x72\x65".force_encoding("utf-8")
+ data = "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET"
+ result = Marshal.send(@method, data)
+ result.should == str
+ result.encoding.should equal(Encoding::UTF_8)
+ end
+
+ it "loads a String in another encoding" do
+ str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".force_encoding("utf-16le")
+ data = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE"
+ result = Marshal.send(@method, data)
+ result.should == str
+ result.encoding.should equal(Encoding::UTF_16LE)
+ end
+
+ it "loads a String as BINARY if no encoding is specified at the end" do
+ str = "\xC3\xB8".force_encoding("BINARY")
+ data = "\x04\b\"\a\xC3\xB8".force_encoding("UTF-8")
+ result = Marshal.send(@method, data)
+ result.encoding.should == Encoding::BINARY
+ result.should == str
+ end
+ end
+
+ describe "for a Struct" do
+ it "loads a extended_struct having fields with same objects" do
+ s = 'hi'
+ obj = Struct.new("Extended", :a, :b).new.extend(Meths)
+ dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0"
+ Marshal.send(@method, dump).should == obj
+
+ obj.a = [:a, s]
+ obj.b = [:Meths, s]
+ dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a"
+ Marshal.send(@method, dump).should == obj
+ Struct.send(:remove_const, :Extended)
+ end
+
+ it "loads a struct having ivar" do
+ obj = Struct.new("Thick").new
+ obj.instance_variable_set(:@foo, 5)
+ reloaded = Marshal.send(@method, "\004\bIS:\022Struct::Thick\000\006:\t@fooi\n")
+ reloaded.should == obj
+ reloaded.instance_variable_get(:@foo).should == 5
+ Struct.send(:remove_const, :Thick)
+ end
+
+ it "loads a struct having fields" do
+ obj = Struct.new("Ure1", :a, :b).new
+ Marshal.send(@method, "\004\bS:\021Struct::Ure1\a:\006a0:\006b0").should == obj
+ Struct.send(:remove_const, :Ure1)
+ end
+
+ it "does not call initialize on the unmarshaled struct" do
+ threadlocal_key = MarshalSpec::StructWithUserInitialize::THREADLOCAL_KEY
+
+ s = MarshalSpec::StructWithUserInitialize.new('foo')
+ Thread.current[threadlocal_key].should == ['foo']
+ s.a.should == 'foo'
+
+ Thread.current[threadlocal_key] = nil
+
+ dumped = Marshal.dump(s)
+ loaded = Marshal.send(@method, dumped)
+
+ Thread.current[threadlocal_key].should == nil
+ loaded.a.should == 'foo'
+ end
+ end
+
+ describe "for an Exception" do
+ it "loads a marshalled exception with no message" do
+ obj = Exception.new
+ loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg0")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesg0:\abt0")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ end
+
+ it "loads a marshalled exception with a message" do
+ obj = Exception.new("foo")
+ loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg\"\bfoo")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt0")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ end
+
+ it "loads a marshalled exception with a backtrace" do
+ obj = Exception.new("foo")
+ obj.set_backtrace(["foo/bar.rb:10"])
+ loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt[\006\"\022foo/bar.rb:10:\tmesg\"\bfoo")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt[\x06I\"\x12foo/bar.rb:10\x06;\aF")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ end
+
+ it "loads an marshalled exception with ivars" do
+ s = 'hi'
+ arr = [:so, :so, s, s]
+ obj = Exception.new("foo")
+ obj.instance_variable_set :@arr, arr
+
+ loaded = Marshal.send(@method, "\x04\bo:\x0EException\b:\tmesg\"\bfoo:\abt0:\t@arr[\t:\aso;\t\"\ahi@\b")
+ new_arr = loaded.instance_variable_get :@arr
+
+ loaded.message.should == obj.message
+ new_arr.should == arr
+ end
+ end
+
+ describe "for an Object" do
+ it "loads an object" do
+ Marshal.send(@method, "\004\bo:\vObject\000").should be_kind_of(Object)
+ end
+
+ it "loads an extended Object" do
+ obj = Object.new.extend(Meths)
+
+ new_obj = Marshal.send(@method, "\004\be:\nMethso:\vObject\000")
+
+ new_obj.class.should == obj.class
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 2].should == [Meths, Object]
+ end
+
+ it "loads an object having ivar" do
+ s = 'hi'
+ arr = [:so, :so, s, s]
+ obj = Object.new
+ obj.instance_variable_set :@str, arr
+
+ new_obj = Marshal.send(@method, "\004\bo:\vObject\006:\t@str[\t:\aso;\a\"\ahi@\a")
+ new_str = new_obj.instance_variable_get :@str
+
+ new_str.should == arr
+ end
+
+ it "loads an Object with a non-US-ASCII instance variable" do
+ ivar = "@é".force_encoding(Encoding::UTF_8).to_sym
+ obj = Marshal.send(@method, "\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06")
+ obj.instance_variables.should == [ivar]
+ obj.instance_variables[0].encoding.should == Encoding::UTF_8
+ obj.instance_variable_get(ivar).should == 1
+ end
+
+ it "raises ArgumentError if the object from an 'o' stream is not dumpable as 'o' type user class" do
+ -> do
+ Marshal.send(@method, "\x04\bo:\tFile\001\001:\001\005@path\"\x10/etc/passwd")
+ end.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "for an object responding to #marshal_dump and #marshal_load" do
+ it "loads a user-marshaled object" do
+ obj = UserMarshal.new
+ obj.data = :data
+ value = [obj, :data]
+ dump = Marshal.dump(value)
+ dump.should == "\x04\b[\aU:\x10UserMarshal:\tdata;\x06"
+ reloaded = Marshal.load(dump)
+ reloaded.should == value
+ end
+ end
+
+ describe "for a user object" do
+ it "loads a user-marshaled extended object" do
+ obj = UserMarshal.new.extend(Meths)
+
+ new_obj = Marshal.send(@method, "\004\bU:\020UserMarshal\"\nstuff")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class].should == UserMarshal
+ end
+
+ it "loads a UserObject" do
+ Marshal.send(@method, "\004\bo:\017UserObject\000").should be_kind_of(UserObject)
+ end
+
+ describe "that extends a core type other than Object or BasicObject" do
+ after :each do
+ MarshalSpec.reset_swapped_class
+ end
+
+ it "raises ArgumentError if the resulting class does not extend the same type" do
+ MarshalSpec.set_swapped_class(Class.new(Hash))
+ data = Marshal.dump(MarshalSpec::SwappedClass.new)
+
+ MarshalSpec.set_swapped_class(Class.new(Array))
+ -> { Marshal.send(@method, data) }.should raise_error(ArgumentError)
+
+ MarshalSpec.set_swapped_class(Class.new)
+ -> { Marshal.send(@method, data) }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe "for a Regexp" do
+ it "loads an extended Regexp" do
+ obj = /[a-z]/.dup.extend(Meths, MethsMore)
+ new_obj = Marshal.send(@method, "\004\be:\nMethse:\016MethsMore/\n[a-z]\000")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 3].should ==
+ [Meths, MethsMore, Regexp]
+ end
+
+ it "loads a extended_user_regexp having ivar" do
+ obj = UserRegexp.new('').extend(Meths)
+ obj.instance_variable_set(:@noise, 'much')
+
+ new_obj = Marshal.send(@method, "\004\bIe:\nMethsC:\017UserRegexp/\000\000\006:\v@noise\"\tmuch")
+
+ new_obj.should == obj
+ new_obj.instance_variable_get(:@noise).should == 'much'
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 3].should ==
+ [Meths, UserRegexp, Regexp]
+ end
+
+ ruby_bug "#19439", ""..."3.3" do
+ it "restore the regexp instance variables" do
+ obj = Regexp.new("hello")
+ obj.instance_variable_set(:@regexp_ivar, [42])
+
+ new_obj = Marshal.send(@method, "\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/")
+ new_obj.instance_variables.should == [:@regexp_ivar]
+ new_obj.instance_variable_get(:@regexp_ivar).should == [42]
+ end
+ end
+ end
+
+ describe "for a Float" do
+ it "loads a Float NaN" do
+ obj = 0.0 / 0.0
+ Marshal.send(@method, "\004\bf\bnan").to_s.should == obj.to_s
+ end
+
+ it "loads a Float 1.3" do
+ Marshal.send(@method, "\004\bf\v1.3\000\314\315").should == 1.3
+ end
+
+ it "loads a Float -5.1867345e-22" do
+ obj = -5.1867345e-22
+ Marshal.send(@method, "\004\bf\037-5.1867345000000008e-22\000\203_").should be_close(obj, 1e-30)
+ end
+
+ it "loads a Float 1.1867345e+22" do
+ obj = 1.1867345e+22
+ Marshal.send(@method, "\004\bf\0361.1867344999999999e+22\000\344@").should == obj
+ end
+ end
+
+ describe "for an Integer" do
+ it "loads 0" do
+ Marshal.send(@method, "\004\bi\000").should == 0
+ Marshal.send(@method, "\004\bi\005").should == 0
+ end
+
+ it "loads an Integer 8" do
+ Marshal.send(@method, "\004\bi\r" ).should == 8
+ end
+
+ it "loads and Integer -8" do
+ Marshal.send(@method, "\004\bi\363" ).should == -8
+ end
+
+ it "loads an Integer 1234" do
+ Marshal.send(@method, "\004\bi\002\322\004").should == 1234
+ end
+
+ it "loads an Integer -1234" do
+ Marshal.send(@method, "\004\bi\376.\373").should == -1234
+ end
+
+ it "loads an Integer 4611686018427387903" do
+ Marshal.send(@method, "\004\bl+\t\377\377\377\377\377\377\377?").should == 4611686018427387903
+ end
+
+ it "loads an Integer -4611686018427387903" do
+ Marshal.send(@method, "\004\bl-\t\377\377\377\377\377\377\377?").should == -4611686018427387903
+ end
+
+ it "loads an Integer 2361183241434822606847" do
+ Marshal.send(@method, "\004\bl+\n\377\377\377\377\377\377\377\377\177\000").should == 2361183241434822606847
+ end
+
+ it "loads an Integer -2361183241434822606847" do
+ Marshal.send(@method, "\004\bl-\n\377\377\377\377\377\377\377\377\177\000").should == -2361183241434822606847
+ end
+
+ it "raises ArgumentError if the input is too short" do
+ ["\004\bi",
+ "\004\bi\001",
+ "\004\bi\002",
+ "\004\bi\002\0",
+ "\004\bi\003",
+ "\004\bi\003\0",
+ "\004\bi\003\0\0",
+ "\004\bi\004",
+ "\004\bi\004\0",
+ "\004\bi\004\0\0",
+ "\004\bi\004\0\0\0"].each do |invalid|
+ -> { Marshal.send(@method, invalid) }.should raise_error(ArgumentError)
+ end
+ end
+
+ if 0.size == 8 # for platforms like x86_64
+ it "roundtrips 4611686018427387903 from dump/load correctly" do
+ Marshal.send(@method, Marshal.dump(4611686018427387903)).should == 4611686018427387903
+ end
+ end
+ end
+
+ describe "for a Rational" do
+ it "loads" do
+ Marshal.send(@method, Marshal.dump(Rational(1, 3))).should == Rational(1, 3)
+ end
+ end
+
+ describe "for a Complex" do
+ it "loads" do
+ Marshal.send(@method, Marshal.dump(Complex(4, 3))).should == Complex(4, 3)
+ end
+ end
+
+ describe "for a Bignum" do
+ platform_is wordsize: 64 do
+ context "that is Bignum on 32-bit platforms but Fixnum on 64-bit" do
+ it "dumps a Fixnum" do
+ val = Marshal.send(@method, "\004\bl+\ab:wU")
+ val.should == 1433877090
+ val.class.should == Integer
+ end
+
+ it "dumps an array containing multiple references to the Bignum as an array of Fixnum" do
+ arr = Marshal.send(@method, "\004\b[\al+\a\223BwU@\006")
+ arr.should == [1433879187, 1433879187]
+ arr.each { |v| v.class.should == Integer }
+ end
+ end
+ end
+ end
+
+ describe "for a Time" do
+ it "loads" do
+ Marshal.send(@method, Marshal.dump(Time.at(1))).should == Time.at(1)
+ end
+
+ it "loads serialized instance variables" do
+ t = Time.new
+ t.instance_variable_set(:@foo, 'bar')
+
+ Marshal.send(@method, Marshal.dump(t)).instance_variable_get(:@foo).should == 'bar'
+ end
+
+ it "loads Time objects stored as links" do
+ t = Time.new
+
+ t1, t2 = Marshal.send(@method, Marshal.dump([t, t]))
+ t1.should equal t2
+ end
+
+ it "loads the zone" do
+ with_timezone 'AST', 3 do
+ t = Time.local(2012, 1, 1)
+ Marshal.send(@method, Marshal.dump(t)).zone.should == t.zone
+ end
+ end
+
+ it "loads nanoseconds" do
+ t = Time.now
+ Marshal.send(@method, Marshal.dump(t)).nsec.should == t.nsec
+ end
+ end
+
+ describe "for nil" do
+ it "loads" do
+ Marshal.send(@method, "\x04\b0").should be_nil
+ end
+ end
+
+ describe "for true" do
+ it "loads" do
+ Marshal.send(@method, "\x04\bT").should be_true
+ end
+ end
+
+ describe "for false" do
+ it "loads" do
+ Marshal.send(@method, "\x04\bF").should be_false
+ end
+ end
+
+ describe "for a Class" do
+ it "loads" do
+ Marshal.send(@method, "\x04\bc\vString").should == String
+ end
+
+ it "raises ArgumentError if given the name of a non-Module" do
+ -> { Marshal.send(@method, "\x04\bc\vKernel") }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if given a nonexistent class" do
+ -> { Marshal.send(@method, "\x04\bc\vStrung") }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "for a Module" do
+ it "loads a module" do
+ Marshal.send(@method, "\x04\bm\vKernel").should == Kernel
+ end
+
+ it "raises ArgumentError if given the name of a non-Class" do
+ -> { Marshal.send(@method, "\x04\bm\vString") }.should raise_error(ArgumentError)
+ end
+
+ it "loads an old module" do
+ Marshal.send(@method, "\x04\bM\vKernel").should == Kernel
+ end
+ end
+
+ describe "for a wrapped C pointer" do
+ it "loads" do
+ class DumpableDir < Dir
+ def _dump_data
+ path
+ end
+ def _load_data path
+ initialize(path)
+ end
+ end
+
+ data = "\x04\bd:\x10DumpableDirI\"\x06.\x06:\x06ET"
+
+ dir = Marshal.send(@method, data)
+ begin
+ dir.path.should == '.'
+ ensure
+ dir.close
+ end
+ end
+
+ it "raises TypeError when the local class is missing _load_data" do
+ class UnloadableDumpableDir < Dir
+ def _dump_data
+ path
+ end
+ # no _load_data
+ end
+
+ data = "\x04\bd:\x1AUnloadableDumpableDirI\"\x06.\x06:\x06ET"
+
+ -> { Marshal.send(@method, data) }.should raise_error(TypeError)
+ end
+
+ it "raises ArgumentError when the local class is a regular object" do
+ data = "\004\bd:\020UserDefined\0"
+
+ -> { Marshal.send(@method, data) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "when a class does not exist in the namespace" do
+ before :each do
+ NamespaceTest.send(:const_set, :SameName, Class.new)
+ @data = Marshal.dump(NamespaceTest::SameName.new)
+ NamespaceTest.send(:remove_const, :SameName)
+ end
+
+ it "raises an ArgumentError" do
+ message = "undefined class/module NamespaceTest::SameName"
+ -> { Marshal.send(@method, @data) }.should raise_error(ArgumentError, message)
+ end
+ end
+
+ it "raises an ArgumentError with full constant name when the dumped constant is missing" do
+ NamespaceTest.send(:const_set, :KaBoom, Class.new)
+ @data = Marshal.dump(NamespaceTest::KaBoom.new)
+ NamespaceTest.send(:remove_const, :KaBoom)
+
+ -> { Marshal.send(@method, @data) }.should raise_error(ArgumentError, /NamespaceTest::KaBoom/)
+ end
+end
diff --git a/spec/ruby/core/matchdata/allocate_spec.rb b/spec/ruby/core/matchdata/allocate_spec.rb
new file mode 100644
index 0000000000..142ce639c2
--- /dev/null
+++ b/spec/ruby/core/matchdata/allocate_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "MatchData.allocate" do
+ it "is undefined" do
+ # https://bugs.ruby-lang.org/issues/16294
+ -> { MatchData.allocate }.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/matchdata/begin_spec.rb b/spec/ruby/core/matchdata/begin_spec.rb
new file mode 100644
index 0000000000..85c454da56
--- /dev/null
+++ b/spec/ruby/core/matchdata/begin_spec.rb
@@ -0,0 +1,104 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+
+describe "MatchData#begin" do
+ context "when passed an integer argument" do
+ it "returns the character offset of the start of the nth element" do
+ match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
+ match_data.begin(0).should == 1
+ match_data.begin(2).should == 2
+ end
+
+ it "returns nil when the nth match isn't found" do
+ match_data = /something is( not)? (right)/.match("something is right")
+ match_data.begin(1).should be_nil
+ end
+
+ it "returns the character offset for multi-byte strings" do
+ match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.")
+ match_data.begin(0).should == 1
+ match_data.begin(2).should == 2
+ end
+
+ not_supported_on :opal do
+ it "returns the character offset for multi-byte strings with unicode regexp" do
+ match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.")
+ match_data.begin(0).should == 1
+ match_data.begin(2).should == 2
+ end
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+
+ match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
+ match_data.begin(obj).should == 2
+ end
+ end
+
+ context "when passed a String argument" do
+ it "return the character offset of the start of the named capture" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
+ match_data.begin("a").should == 1
+ match_data.begin("b").should == 3
+ end
+
+ it "returns the character offset for multi byte strings" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.")
+ match_data.begin("a").should == 1
+ match_data.begin("b").should == 3
+ end
+
+ not_supported_on :opal do
+ it "returns the character offset for multi byte strings with unicode regexp" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.")
+ match_data.begin("a").should == 1
+ match_data.begin("b").should == 3
+ end
+ end
+
+ it "returns the character offset for the farthest match when multiple named captures use the same name" do
+ match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.")
+ match_data.begin("a").should == 3
+ end
+
+ it "returns the character offset for multi-byte names" do
+ match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
+ match_data.begin("æ").should == 1
+ end
+ end
+
+ context "when passed a Symbol argument" do
+ it "return the character offset of the start of the named capture" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
+ match_data.begin(:a).should == 1
+ match_data.begin(:b).should == 3
+ end
+
+ it "returns the character offset for multi byte strings" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.")
+ match_data.begin(:a).should == 1
+ match_data.begin(:b).should == 3
+ end
+
+ not_supported_on :opal do
+ it "returns the character offset for multi byte strings with unicode regexp" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.")
+ match_data.begin(:a).should == 1
+ match_data.begin(:b).should == 3
+ end
+ end
+
+ it "returns the character offset for the farthest match when multiple named captures use the same name" do
+ match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.")
+ match_data.begin(:a).should == 3
+ end
+
+ it "returns the character offset for multi-byte names" do
+ match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
+ match_data.begin(:æ).should == 1
+ end
+ end
+end
diff --git a/spec/ruby/core/matchdata/captures_spec.rb b/spec/ruby/core/matchdata/captures_spec.rb
new file mode 100644
index 0000000000..58d4620709
--- /dev/null
+++ b/spec/ruby/core/matchdata/captures_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "MatchData#captures" do
+ it "returns an array of the match captures" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.").captures.should == ["H","X","113","8"]
+ end
+
+ ruby_version_is "3.0" do
+ it "returns instances of String when given a String subclass" do
+ str = MatchDataSpecs::MyString.new("THX1138: The Movie")
+ /(.)(.)(\d+)(\d)/.match(str).captures.each { |c| c.should be_an_instance_of(String) }
+ end
+ end
+end
diff --git a/spec/ruby/core/matchdata/dup_spec.rb b/spec/ruby/core/matchdata/dup_spec.rb
new file mode 100644
index 0000000000..70877f07eb
--- /dev/null
+++ b/spec/ruby/core/matchdata/dup_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "MatchData#dup" do
+ it "duplicates the match data" do
+ original = /ll/.match("hello")
+ original.instance_variable_set(:@custom_ivar, 42)
+ duplicate = original.dup
+
+ duplicate.instance_variable_get(:@custom_ivar).should == 42
+ original.regexp.should == duplicate.regexp
+ original.string.should == duplicate.string
+ original.offset(0).should == duplicate.offset(0)
+ end
+end
diff --git a/spec/ruby/core/matchdata/element_reference_spec.rb b/spec/ruby/core/matchdata/element_reference_spec.rb
new file mode 100644
index 0000000000..7c0f089bb4
--- /dev/null
+++ b/spec/ruby/core/matchdata/element_reference_spec.rb
@@ -0,0 +1,116 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "MatchData#[]" do
+ it "acts as normal array indexing [index]" do
+ md = /(.)(.)(\d+)(\d)/.match("THX1138.")
+
+ md[0].should == 'HX1138'
+ md[1].should == 'H'
+ md[2].should == 'X'
+ md[-3].should == 'X'
+ md[10000].should == nil
+ md[-10000].should == nil
+ end
+
+ it "supports accessors [start, length]" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[1, 2].should == %w|H X|
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[-3, 2].should == %w|X 113|
+
+ # negative index is larger than the number of match values
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[-30, 2].should == nil
+
+ # length argument larger than number of match values is capped to match value length
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[3, 10].should == %w|113 8|
+ end
+
+ it "supports ranges [start..end]" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[1..3].should == %w|H X 113|
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[3..10].should == %w|113 8|
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[-30..2].should == nil
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[3..1].should == []
+ end
+
+ it "supports endless ranges [start..]" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[3..].should == %w|113 8|
+ end
+
+ it "supports beginningless ranges [..end]" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[..1].should == %w|HX1138 H|
+ end
+
+ it "supports beginningless endless ranges [nil..nil]" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.")[nil..nil].should == %w|HX1138 H X 113 8|
+ end
+
+ ruby_version_is "3.0" do
+ it "returns instances of String when given a String subclass" do
+ str = MatchDataSpecs::MyString.new("THX1138.")
+ /(.)(.)(\d+)(\d)/.match(str)[0..-1].each { |m| m.should be_an_instance_of(String) }
+ end
+ end
+end
+
+describe "MatchData#[Symbol]" do
+ it "returns the corresponding named match when given a Symbol" do
+ md = 'haystack'.match(/(?<t>t(?<a>ack))/)
+ md[:a].should == 'ack'
+ md[:t].should == 'tack'
+ end
+
+ it "returns the corresponding named match when given a String" do
+ md = 'haystack'.match(/(?<t>t(?<a>ack))/)
+ md['a'].should == 'ack'
+ md['t'].should == 'tack'
+ end
+
+ it "returns the matching version of multiple corresponding named match" do
+ regexp = /(?:
+ A(?<word>\w+)
+ |
+ B(?<word>\w+)
+ )/x
+ md_a = regexp.match("Afoo")
+ md_b = regexp.match("Bfoo")
+
+ md_a[:word].should == "foo"
+ md_b[:word].should == "foo"
+
+ md_a['word'].should == "foo"
+ md_b['word'].should == "foo"
+ end
+
+ it "returns the last match when multiple named matches exist with the same name" do
+ md = /(?<word>hay)(?<word>stack)/.match('haystack')
+ md[:word].should == "stack"
+ md['word'].should == "stack"
+ end
+
+ it "returns nil on non-matching named matches" do
+ regexp = /(?<foo>foo )?(?<bar>bar)/
+ full_match = regexp.match("foo bar")
+ partial_match = regexp.match("bar")
+
+ full_match[:foo].should == "foo "
+ partial_match[:foo].should == nil
+
+ full_match['foo'].should == "foo "
+ partial_match['foo'].should == nil
+ end
+
+ it "raises an IndexError if there is no named match corresponding to the Symbol" do
+ md = 'haystack'.match(/(?<t>t(?<a>ack))/)
+ -> { md[:baz] }.should raise_error(IndexError, /baz/)
+ end
+
+ it "raises an IndexError if there is no named match corresponding to the String" do
+ md = 'haystack'.match(/(?<t>t(?<a>ack))/)
+ -> { md['baz'] }.should raise_error(IndexError, /baz/)
+ end
+
+ it "returns matches in the String's encoding" do
+ rex = /(?<t>t(?<a>ack))/u
+ md = 'haystack'.force_encoding('euc-jp').match(rex)
+ md[:t].encoding.should == Encoding::EUC_JP
+ end
+end
diff --git a/spec/ruby/core/matchdata/end_spec.rb b/spec/ruby/core/matchdata/end_spec.rb
new file mode 100644
index 0000000000..d01b0a8b30
--- /dev/null
+++ b/spec/ruby/core/matchdata/end_spec.rb
@@ -0,0 +1,104 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+
+describe "MatchData#end" do
+ context "when passed an integer argument" do
+ it "returns the character offset of the end of the nth element" do
+ match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
+ match_data.end(0).should == 7
+ match_data.end(2).should == 3
+ end
+
+ it "returns nil when the nth match isn't found" do
+ match_data = /something is( not)? (right)/.match("something is right")
+ match_data.end(1).should be_nil
+ end
+
+ it "returns the character offset for multi-byte strings" do
+ match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.")
+ match_data.end(0).should == 7
+ match_data.end(2).should == 3
+ end
+
+ not_supported_on :opal do
+ it "returns the character offset for multi-byte strings with unicode regexp" do
+ match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.")
+ match_data.end(0).should == 7
+ match_data.end(2).should == 3
+ end
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+
+ match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
+ match_data.end(obj).should == 3
+ end
+ end
+
+ context "when passed a String argument" do
+ it "return the character offset of the start of the named capture" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
+ match_data.end("a").should == 2
+ match_data.end("b").should == 6
+ end
+
+ it "returns the character offset for multi byte strings" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.")
+ match_data.end("a").should == 2
+ match_data.end("b").should == 6
+ end
+
+ not_supported_on :opal do
+ it "returns the character offset for multi byte strings with unicode regexp" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.")
+ match_data.end("a").should == 2
+ match_data.end("b").should == 6
+ end
+ end
+
+ it "returns the character offset for the farthest match when multiple named captures use the same name" do
+ match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.")
+ match_data.end("a").should == 6
+ end
+
+ it "returns the character offset for multi-byte names" do
+ match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
+ match_data.end("æ").should == 2
+ end
+ end
+
+ context "when passed a Symbol argument" do
+ it "return the character offset of the start of the named capture" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
+ match_data.end(:a).should == 2
+ match_data.end(:b).should == 6
+ end
+
+ it "returns the character offset for multi byte strings" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.")
+ match_data.end(:a).should == 2
+ match_data.end(:b).should == 6
+ end
+
+ not_supported_on :opal do
+ it "returns the character offset for multi byte strings with unicode regexp" do
+ match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.")
+ match_data.end(:a).should == 2
+ match_data.end(:b).should == 6
+ end
+ end
+
+ it "returns the character offset for the farthest match when multiple named captures use the same name" do
+ match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.")
+ match_data.end(:a).should == 6
+ end
+
+ it "returns the character offset for multi-byte names" do
+ match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
+ match_data.end(:æ).should == 2
+ end
+ end
+end
diff --git a/spec/ruby/core/matchdata/eql_spec.rb b/spec/ruby/core/matchdata/eql_spec.rb
new file mode 100644
index 0000000000..1d9666ebe1
--- /dev/null
+++ b/spec/ruby/core/matchdata/eql_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eql'
+
+describe "MatchData#eql?" do
+ it_behaves_like :matchdata_eql, :eql?
+end
diff --git a/spec/ruby/core/matchdata/equal_value_spec.rb b/spec/ruby/core/matchdata/equal_value_spec.rb
new file mode 100644
index 0000000000..a58f1277e4
--- /dev/null
+++ b/spec/ruby/core/matchdata/equal_value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eql'
+
+describe "MatchData#==" do
+ it_behaves_like :matchdata_eql, :==
+end
diff --git a/spec/ruby/core/matchdata/fixtures/classes.rb b/spec/ruby/core/matchdata/fixtures/classes.rb
new file mode 100644
index 0000000000..54795636e5
--- /dev/null
+++ b/spec/ruby/core/matchdata/fixtures/classes.rb
@@ -0,0 +1,3 @@
+module MatchDataSpecs
+ class MyString < String; end
+end
diff --git a/spec/ruby/core/matchdata/hash_spec.rb b/spec/ruby/core/matchdata/hash_spec.rb
new file mode 100644
index 0000000000..cef18fdd20
--- /dev/null
+++ b/spec/ruby/core/matchdata/hash_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "MatchData#hash" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/matchdata/inspect_spec.rb b/spec/ruby/core/matchdata/inspect_spec.rb
new file mode 100644
index 0000000000..5315257677
--- /dev/null
+++ b/spec/ruby/core/matchdata/inspect_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "MatchData#inspect" do
+ before :each do
+ @match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
+ end
+
+ it "returns a String" do
+ @match_data.inspect.should be_kind_of(String)
+ end
+
+ it "returns a human readable representation that contains entire matched string and the captures" do
+ # yeah, hardcoding the inspect output is not ideal, but in this case
+ # it makes perfect sense. See JRUBY-4558 for example.
+ @match_data.inspect.should == '#<MatchData "HX1138" 1:"H" 2:"X" 3:"113" 4:"8">'
+ end
+
+ it "returns a human readable representation of named captures" do
+ match_data = "abc def ghi".match(/(?<first>\w+)\s+(?<last>\w+)\s+(\w+)/)
+
+ match_data.inspect.should == '#<MatchData "abc def ghi" first:"abc" last:"def">'
+ end
+end
diff --git a/spec/ruby/core/matchdata/length_spec.rb b/spec/ruby/core/matchdata/length_spec.rb
new file mode 100644
index 0000000000..39df36df4b
--- /dev/null
+++ b/spec/ruby/core/matchdata/length_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+
+describe "MatchData#length" do
+ it_behaves_like :matchdata_length, :length
+end
diff --git a/spec/ruby/core/matchdata/match_length_spec.rb b/spec/ruby/core/matchdata/match_length_spec.rb
new file mode 100644
index 0000000000..f7785ab1a0
--- /dev/null
+++ b/spec/ruby/core/matchdata/match_length_spec.rb
@@ -0,0 +1,34 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+
+ruby_version_is "3.1" do
+ describe "MatchData#match_length" do
+ it "returns the length of the corresponding match when given an Integer" do
+ md = /(.)(.)(\d+)(\d)/.match("THX1138.")
+
+ md.match_length(0).should == 6
+ md.match_length(1).should == 1
+ md.match_length(2).should == 1
+ md.match_length(3).should == 3
+ md.match_length(4).should == 1
+ end
+
+ it "returns nil on non-matching index matches" do
+ md = /\d+(\w)?/.match("THX1138.")
+ md.match_length(1).should == nil
+ end
+
+ it "returns the length of the corresponding named match when given a Symbol" do
+ md = 'haystack'.match(/(?<t>t(?<a>ack))/)
+ md.match_length(:a).should == 3
+ md.match_length(:t).should == 4
+ end
+
+ it "returns nil on non-matching index matches" do
+ md = 'haystack'.match(/(?<t>t)(?<a>all)?/)
+ md.match_length(:t).should == 1
+ md.match_length(:a).should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/matchdata/match_spec.rb b/spec/ruby/core/matchdata/match_spec.rb
new file mode 100644
index 0000000000..545de6f93f
--- /dev/null
+++ b/spec/ruby/core/matchdata/match_spec.rb
@@ -0,0 +1,34 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+
+ruby_version_is "3.1" do
+ describe "MatchData#match" do
+ it "returns the corresponding match when given an Integer" do
+ md = /(.)(.)(\d+)(\d)/.match("THX1138.")
+
+ md.match(0).should == 'HX1138'
+ md.match(1).should == 'H'
+ md.match(2).should == 'X'
+ md.match(3).should == '113'
+ md.match(4).should == '8'
+ end
+
+ it "returns nil on non-matching index matches" do
+ md = /\d+(\w)?/.match("THX1138.")
+ md.match(1).should == nil
+ end
+
+ it "returns the corresponding named match when given a Symbol" do
+ md = 'haystack'.match(/(?<t>t(?<a>ack))/)
+ md.match(:a).should == 'ack'
+ md.match(:t).should == 'tack'
+ end
+
+ it "returns nil on non-matching index matches" do
+ md = 'haystack'.match(/(?<t>t)(?<a>all)?/)
+ md.match(:t).should == 't'
+ md.match(:a).should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/matchdata/named_captures_spec.rb b/spec/ruby/core/matchdata/named_captures_spec.rb
new file mode 100644
index 0000000000..9b1e324a24
--- /dev/null
+++ b/spec/ruby/core/matchdata/named_captures_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe 'MatchData#named_captures' do
+ it 'returns a Hash that has captured name and the matched string pairs' do
+ /(?<a>.)(?<b>.)?/.match('0').named_captures.should == { 'a' => '0', 'b' => nil }
+ end
+
+ it 'prefers later captures' do
+ /\A(?<a>.)(?<b>.)(?<b>.)(?<a>.)\z/.match('0123').named_captures.should == { 'a' => '3', 'b' => '2' }
+ end
+
+ it 'returns the latest matched capture, even if a later one that does not match exists' do
+ /\A(?<a>.)(?<b>.)(?<b>.)(?<a>.)?\z/.match('012').named_captures.should == { 'a' => '0', 'b' => '2' }
+ end
+end
diff --git a/spec/ruby/core/matchdata/names_spec.rb b/spec/ruby/core/matchdata/names_spec.rb
new file mode 100644
index 0000000000..25ca06ced9
--- /dev/null
+++ b/spec/ruby/core/matchdata/names_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "MatchData#names" do
+ it "returns an Array" do
+ md = 'haystack'.match(/(?<yellow>hay)/)
+ md.names.should be_an_instance_of(Array)
+ end
+
+ it "sets each element to a String" do
+ 'haystack'.match(/(?<yellow>hay)/).names.all? do |e|
+ e.should be_an_instance_of(String)
+ end
+ end
+
+ it "returns the names of the named capture groups" do
+ md = 'haystack'.match(/(?<yellow>hay).(?<pin>tack)/)
+ md.names.should == ['yellow', 'pin']
+ end
+
+ it "returns [] if there were no named captures" do
+ 'haystack'.match(/(hay).(tack)/).names.should == []
+ end
+
+ it "returns each name only once" do
+ md = 'haystack'.match(/(?<hay>hay)(?<dot>.)(?<hay>tack)/)
+ md.names.should == ['hay', 'dot']
+ end
+
+ it "equals Regexp#names" do
+ r = /(?<hay>hay)(?<dot>.)(?<hay>tack)/
+ 'haystack'.match(r).names.should == r.names
+ end
+end
diff --git a/spec/ruby/core/matchdata/offset_spec.rb b/spec/ruby/core/matchdata/offset_spec.rb
new file mode 100644
index 0000000000..1ccb54b7a7
--- /dev/null
+++ b/spec/ruby/core/matchdata/offset_spec.rb
@@ -0,0 +1,30 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+
+describe "MatchData#offset" do
+ it "returns a two element array with the begin and end of the nth match" do
+ match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
+ match_data.offset(0).should == [1, 7]
+ match_data.offset(4).should == [6, 7]
+ end
+
+ it "returns [nil, nil] when the nth match isn't found" do
+ match_data = /something is( not)? (right)/.match("something is right")
+ match_data.offset(1).should == [nil, nil]
+ end
+
+ it "returns the offset for multi byte strings" do
+ match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.")
+ match_data.offset(0).should == [1, 7]
+ match_data.offset(4).should == [6, 7]
+ end
+
+ not_supported_on :opal do
+ it "returns the offset for multi byte strings with unicode regexp" do
+ match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.")
+ match_data.offset(0).should == [1, 7]
+ match_data.offset(4).should == [6, 7]
+ end
+ end
+end
diff --git a/spec/ruby/core/matchdata/post_match_spec.rb b/spec/ruby/core/matchdata/post_match_spec.rb
new file mode 100644
index 0000000000..d3aa4c8900
--- /dev/null
+++ b/spec/ruby/core/matchdata/post_match_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "MatchData#post_match" do
+ it "returns the string after the match equiv. special var $'" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").post_match.should == ': The Movie'
+ $'.should == ': The Movie'
+ end
+
+ it "sets the encoding to the encoding of the source String" do
+ str = "abc".force_encoding Encoding::EUC_JP
+ str.match(/b/).post_match.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "sets an empty result to the encoding of the source String" do
+ str = "abc".force_encoding Encoding::ISO_8859_1
+ str.match(/c/).post_match.encoding.should equal(Encoding::ISO_8859_1)
+ end
+
+ ruby_version_is "3.0" do
+ it "returns an instance of String when given a String subclass" do
+ str = MatchDataSpecs::MyString.new("THX1138: The Movie")
+ /(.)(.)(\d+)(\d)/.match(str).post_match.should be_an_instance_of(String)
+ end
+ end
+end
diff --git a/spec/ruby/core/matchdata/pre_match_spec.rb b/spec/ruby/core/matchdata/pre_match_spec.rb
new file mode 100644
index 0000000000..b43be5fb41
--- /dev/null
+++ b/spec/ruby/core/matchdata/pre_match_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "MatchData#pre_match" do
+ it "returns the string before the match, equiv. special var $`" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").pre_match.should == 'T'
+ $`.should == 'T'
+ end
+
+ it "sets the encoding to the encoding of the source String" do
+ str = "abc".force_encoding Encoding::EUC_JP
+ str.match(/b/).pre_match.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "sets an empty result to the encoding of the source String" do
+ str = "abc".force_encoding Encoding::ISO_8859_1
+ str.match(/a/).pre_match.encoding.should equal(Encoding::ISO_8859_1)
+ end
+
+ ruby_version_is "3.0" do
+ it "returns an instance of String when given a String subclass" do
+ str = MatchDataSpecs::MyString.new("THX1138: The Movie")
+ /(.)(.)(\d+)(\d)/.match(str).pre_match.should be_an_instance_of(String)
+ end
+ end
+end
diff --git a/spec/ruby/core/matchdata/regexp_spec.rb b/spec/ruby/core/matchdata/regexp_spec.rb
new file mode 100644
index 0000000000..099b59c559
--- /dev/null
+++ b/spec/ruby/core/matchdata/regexp_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "MatchData#regexp" do
+ it "returns a Regexp object" do
+ m = 'haystack'.match(/hay/)
+ m.regexp.should be_an_instance_of(Regexp)
+ end
+
+ it "returns the pattern used in the match" do
+ m = 'haystack'.match(/hay/)
+ m.regexp.should == /hay/
+ end
+
+ it "returns the same Regexp used to match" do
+ r = /hay/
+ m = 'haystack'.match(r)
+ m.regexp.object_id.should == r.object_id
+ end
+
+ it "returns a Regexp for the result of gsub(String)" do
+ 'he[[o'.gsub('[', ']')
+ $~.regexp.should == /\[/
+ end
+end
diff --git a/spec/ruby/core/matchdata/shared/eql.rb b/spec/ruby/core/matchdata/shared/eql.rb
new file mode 100644
index 0000000000..e021baa178
--- /dev/null
+++ b/spec/ruby/core/matchdata/shared/eql.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+describe :matchdata_eql, shared: true do
+ it "returns true if both operands have equal target strings, patterns, and match positions" do
+ a = 'haystack'.match(/hay/)
+ b = 'haystack'.match(/hay/)
+ a.send(@method, b).should be_true
+ end
+
+ it "returns false if the operands have different target strings" do
+ a = 'hay'.match(/hay/)
+ b = 'haystack'.match(/hay/)
+ a.send(@method, b).should be_false
+ end
+
+ it "returns false if the operands have different patterns" do
+ a = 'haystack'.match(/h.y/)
+ b = 'haystack'.match(/hay/)
+ a.send(@method, b).should be_false
+ end
+
+ it "returns false if the argument is not a MatchData object" do
+ a = 'haystack'.match(/hay/)
+ a.send(@method, Object.new).should be_false
+ end
+end
diff --git a/spec/ruby/core/matchdata/shared/length.rb b/spec/ruby/core/matchdata/shared/length.rb
new file mode 100644
index 0000000000..6312a7ed4c
--- /dev/null
+++ b/spec/ruby/core/matchdata/shared/length.rb
@@ -0,0 +1,5 @@
+describe :matchdata_length, shared: true do
+ it "length should return the number of elements in the match array" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.").send(@method).should == 5
+ end
+end
diff --git a/spec/ruby/core/matchdata/size_spec.rb b/spec/ruby/core/matchdata/size_spec.rb
new file mode 100644
index 0000000000..b4965db3b8
--- /dev/null
+++ b/spec/ruby/core/matchdata/size_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+
+describe "MatchData#size" do
+ it_behaves_like :matchdata_length, :size
+end
diff --git a/spec/ruby/core/matchdata/string_spec.rb b/spec/ruby/core/matchdata/string_spec.rb
new file mode 100644
index 0000000000..420233e1f3
--- /dev/null
+++ b/spec/ruby/core/matchdata/string_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+
+describe "MatchData#string" do
+ it "returns a copy of the match string" do
+ str = /(.)(.)(\d+)(\d)/.match("THX1138.").string
+ str.should == "THX1138."
+ end
+
+ it "returns a frozen copy of the match string" do
+ str = /(.)(.)(\d+)(\d)/.match("THX1138.").string
+ str.should == "THX1138."
+ str.should.frozen?
+ end
+
+ it "returns the same frozen string for every call" do
+ md = /(.)(.)(\d+)(\d)/.match("THX1138.")
+ md.string.should equal(md.string)
+ end
+
+ it "returns a frozen copy of the matched string for gsub(String)" do
+ 'he[[o'.gsub!('[', ']')
+ $~.string.should == 'he[[o'
+ $~.string.should.frozen?
+ end
+end
diff --git a/spec/ruby/core/matchdata/to_a_spec.rb b/spec/ruby/core/matchdata/to_a_spec.rb
new file mode 100644
index 0000000000..50f5a161a5
--- /dev/null
+++ b/spec/ruby/core/matchdata/to_a_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "MatchData#to_a" do
+ it "returns an array of matches" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.").to_a.should == ["HX1138", "H", "X", "113", "8"]
+ end
+
+ ruby_version_is "3.0" do
+ it "returns instances of String when given a String subclass" do
+ str = MatchDataSpecs::MyString.new("THX1138.")
+ /(.)(.)(\d+)(\d)/.match(str)[0..-1].to_a.each { |m| m.should be_an_instance_of(String) }
+ end
+ end
+end
diff --git a/spec/ruby/core/matchdata/to_s_spec.rb b/spec/ruby/core/matchdata/to_s_spec.rb
new file mode 100644
index 0000000000..aab0955ae1
--- /dev/null
+++ b/spec/ruby/core/matchdata/to_s_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "MatchData#to_s" do
+ it "returns the entire matched string" do
+ /(.)(.)(\d+)(\d)/.match("THX1138.").to_s.should == "HX1138"
+ end
+
+ ruby_version_is "3.0" do
+ it "returns an instance of String when given a String subclass" do
+ str = MatchDataSpecs::MyString.new("THX1138.")
+ /(.)(.)(\d+)(\d)/.match(str).to_s.should be_an_instance_of(String)
+ end
+ end
+end
diff --git a/spec/ruby/core/matchdata/values_at_spec.rb b/spec/ruby/core/matchdata/values_at_spec.rb
new file mode 100644
index 0000000000..4fd0bfc42a
--- /dev/null
+++ b/spec/ruby/core/matchdata/values_at_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+
+describe "Struct#values_at" do
+ # Should be synchronized with core/array/values_at_spec.rb and core/struct/values_at_spec.rb
+ #
+ # /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").to_a # => ["HX1138", "H", "X", "113", "8"]
+
+ context "when passed a list of Integers" do
+ it "returns an array containing each value given by one of integers" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(0, 2, -2).should == ["HX1138", "X", "113"]
+ end
+
+ it "returns nil value for any integer that is out of range" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(5).should == [nil]
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(-6).should == [nil]
+ end
+ end
+
+ context "when passed an integer Range" do
+ it "returns an array containing each value given by the elements of the range" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(0..2).should == ["HX1138", "H", "X"]
+ end
+
+ it "fills with nil values for range elements larger than the captured values number" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(0..5).should == ["HX1138", "H", "X", "113", "8", nil]
+ end
+
+ it "raises RangeError if any element of the range is negative and out of range" do
+ -> { /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(-6..3) }.should raise_error(RangeError, "-6..3 out of range")
+ end
+
+ it "supports endless Range" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(0..).should == ["HX1138", "H", "X", "113", "8"]
+ end
+
+ it "supports beginningless Range" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(0..2).should == ["HX1138", "H", "X"]
+ end
+
+ it "returns an empty Array when Range is empty" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(2..0).should == []
+ end
+ end
+
+ context "when passed names" do
+ it 'slices captures with the given names' do
+ /(?<a>.)(?<b>.)(?<c>.)/.match('012').values_at(:c, :a).should == ['2', '0']
+ end
+
+ it 'slices captures with the given String names' do
+ /(?<a>.)(?<b>.)(?<c>.)/.match('012').values_at('c', 'a').should == ['2', '0']
+ end
+ end
+
+ it "supports multiple integer Ranges" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(1..2, 2..3).should == ["H", "X", "X", "113"]
+ end
+
+ it "supports mixing integer Ranges and Integers" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(1..2, 4).should == ["H", "X", "8"]
+ end
+
+ it 'supports mixing of names and indices' do
+ /\A(?<a>.)(?<b>.)\z/.match('01').values_at(0, 1, 2, :a, :b).should == ['01', '0', '1', '0', '1']
+ end
+
+ it "returns a new empty Array if no arguments given" do
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at().should == []
+ end
+
+ it "fails when passed arguments of unsupported types" do
+ -> {
+ /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(Object.new)
+ }.should raise_error(TypeError, "no implicit conversion of Object into Integer")
+ end
+end
diff --git a/spec/ruby/core/math/acos_spec.rb b/spec/ruby/core/math/acos_spec.rb
new file mode 100644
index 0000000000..8b321ab29b
--- /dev/null
+++ b/spec/ruby/core/math/acos_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# arccosine : (-1.0, 1.0) --> (0, PI)
+describe "Math.acos" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "returns a float" do
+ Math.acos(1).should be_kind_of(Float )
+ end
+
+ it "returns the arccosine of the argument" do
+ Math.acos(1).should be_close(0.0, TOLERANCE)
+ Math.acos(0).should be_close(1.5707963267949, TOLERANCE)
+ Math.acos(-1).should be_close(Math::PI,TOLERANCE)
+ Math.acos(0.25).should be_close(1.31811607165282, TOLERANCE)
+ Math.acos(0.50).should be_close(1.0471975511966 , TOLERANCE)
+ Math.acos(0.75).should be_close(0.722734247813416, TOLERANCE)
+ end
+
+ it "raises an Math::DomainError if the argument is greater than 1.0" do
+ -> { Math.acos(1.0001) }.should raise_error(Math::DomainError)
+ end
+
+ it "raises an Math::DomainError if the argument is less than -1.0" do
+ -> { Math.acos(-1.0001) }.should raise_error(Math::DomainError)
+ end
+
+ it "raises a TypeError if the string argument cannot be coerced with Float()" do
+ -> { Math.acos("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.acos(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.acos(MathSpecs::UserClass.new) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.acos(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.acos(MathSpecs::Float.new(0.5)).should be_close(Math.acos(0.5), TOLERANCE)
+ end
+end
+
+describe "Math#acos" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:acos, 0).should be_close(1.5707963267949, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/acosh_spec.rb b/spec/ruby/core/math/acosh_spec.rb
new file mode 100644
index 0000000000..6707de95d3
--- /dev/null
+++ b/spec/ruby/core/math/acosh_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.acosh" do
+ it "returns a float" do
+ Math.acosh(1.0).should be_kind_of(Float)
+ end
+
+ it "returns the principle value of the inverse hyperbolic cosine of the argument" do
+ Math.acosh(14.2).should be_close(3.345146999647, TOLERANCE)
+ Math.acosh(1.0).should be_close(0.0, TOLERANCE)
+ end
+
+ it "raises Math::DomainError if the passed argument is less than -1.0 or greater than 1.0" do
+ -> { Math.acosh(1.0 - TOLERANCE) }.should raise_error(Math::DomainError)
+ -> { Math.acosh(0) }.should raise_error(Math::DomainError)
+ -> { Math.acosh(-1.0) }.should raise_error(Math::DomainError)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.acosh("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.acosh(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.acosh(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.acosh(MathSpecs::Float.new).should == 0.0
+ end
+end
+
+describe "Math#acosh" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:acosh, 1.0).should be_close(0.0, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/asin_spec.rb b/spec/ruby/core/math/asin_spec.rb
new file mode 100644
index 0000000000..3a674a1147
--- /dev/null
+++ b/spec/ruby/core/math/asin_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# arcsine : (-1.0, 1.0) --> (-PI/2, PI/2)
+describe "Math.asin" do
+ it "returns a float" do
+ Math.asin(1).should be_kind_of(Float)
+ end
+
+ it "returns the arcsine of the argument" do
+ Math.asin(1).should be_close(Math::PI/2, TOLERANCE)
+ Math.asin(0).should be_close(0.0, TOLERANCE)
+ Math.asin(-1).should be_close(-Math::PI/2, TOLERANCE)
+ Math.asin(0.25).should be_close(0.252680255142079, TOLERANCE)
+ Math.asin(0.50).should be_close(0.523598775598299, TOLERANCE)
+ Math.asin(0.75).should be_close(0.8480620789814816,TOLERANCE)
+ end
+
+ it "raises an Math::DomainError if the argument is greater than 1.0" do
+ -> { Math.asin(1.0001) }.should raise_error( Math::DomainError)
+ end
+
+ it "raises an Math::DomainError if the argument is less than -1.0" do
+ -> { Math.asin(-1.0001) }.should raise_error( Math::DomainError)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.asin("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.asin(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.asin(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.asin(MathSpecs::Float.new).should be_close(1.5707963267949, TOLERANCE)
+ end
+end
+
+describe "Math#asin" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:asin, 0.5).should be_close(0.523598775598299, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/asinh_spec.rb b/spec/ruby/core/math/asinh_spec.rb
new file mode 100644
index 0000000000..ff8210df0a
--- /dev/null
+++ b/spec/ruby/core/math/asinh_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.asinh" do
+ it "returns a float" do
+ Math.asinh(1.5).should be_kind_of(Float)
+ end
+
+ it "returns the inverse hyperbolic sin of the argument" do
+ Math.asinh(1.5).should be_close(1.19476321728711, TOLERANCE)
+ Math.asinh(-2.97).should be_close(-1.8089166921397, TOLERANCE)
+ Math.asinh(0.0).should == 0.0
+ Math.asinh(-0.0).should == -0.0
+ Math.asinh(1.05367e-08).should be_close(1.05367e-08, TOLERANCE)
+ Math.asinh(-1.05367e-08).should be_close(-1.05367e-08, TOLERANCE)
+ # Default tolerance does not scale right for these...
+ #Math.asinh(94906265.62).should be_close(19.0615, TOLERANCE)
+ #Math.asinh(-94906265.62).should be_close(-19.0615, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.asinh("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.asinh(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.asinh(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.asinh(MathSpecs::Float.new).should be_close(0.881373587019543, TOLERANCE)
+ end
+end
+
+describe "Math#asinh" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:asinh, 19.275).should be_close(3.65262832292466, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/atan2_spec.rb b/spec/ruby/core/math/atan2_spec.rb
new file mode 100644
index 0000000000..d4ef369d2a
--- /dev/null
+++ b/spec/ruby/core/math/atan2_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.atan2" do
+ it "returns a float" do
+ Math.atan2(1.2, 0.5).should be_kind_of(Float)
+ end
+
+ it "returns the arc tangent of y, x" do
+ Math.atan2(4.2, 0.3).should be_close(1.49948886200961, TOLERANCE)
+ Math.atan2(0.0, 1.0).should be_close(0.0, TOLERANCE)
+ Math.atan2(-9.1, 3.2).should be_close(-1.23265379809025, TOLERANCE)
+ Math.atan2(7.22, -3.3).should be_close(1.99950888779256, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.atan2(1.0, "test") }.should raise_error(TypeError)
+ -> { Math.atan2("test", 0.0) }.should raise_error(TypeError)
+ -> { Math.atan2("test", "this") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.atan2(nil, 1.0) }.should raise_error(TypeError)
+ -> { Math.atan2(-1.0, nil) }.should raise_error(TypeError)
+ -> { Math.atan2(nil, nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.atan2(MathSpecs::Float.new, MathSpecs::Float.new).should be_close(0.785398163397448, TOLERANCE)
+ end
+
+ it "returns positive zero when passed 0.0, 0.0" do
+ Math.atan2(0.0, 0.0).should be_positive_zero
+ end
+
+ it "returns negative zero when passed -0.0, 0.0" do
+ Math.atan2(-0.0, 0.0).should be_negative_zero
+ end
+
+ it "returns Pi when passed 0.0, -0.0" do
+ Math.atan2(0.0, -0.0).should == Math::PI
+ end
+
+ it "returns -Pi when passed -0.0, -0.0" do
+ Math.atan2(-0.0, -0.0).should == -Math::PI
+ end
+
+end
+
+describe "Math#atan2" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:atan2, 1.1, 2.2).should be_close(0.463647609000806, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/atan_spec.rb b/spec/ruby/core/math/atan_spec.rb
new file mode 100644
index 0000000000..15edf68c05
--- /dev/null
+++ b/spec/ruby/core/math/atan_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# arctangent : (-Inf, Inf) --> (-PI/2, PI/2)
+describe "Math.atan" do
+ it "returns a float" do
+ Math.atan(1).should be_kind_of(Float)
+ end
+
+ it "returns the arctangent of the argument" do
+ Math.atan(1).should be_close(Math::PI/4, TOLERANCE)
+ Math.atan(0).should be_close(0.0, TOLERANCE)
+ Math.atan(-1).should be_close(-Math::PI/4, TOLERANCE)
+ Math.atan(0.25).should be_close(0.244978663126864, TOLERANCE)
+ Math.atan(0.50).should be_close(0.463647609000806, TOLERANCE)
+ Math.atan(0.75).should be_close(0.643501108793284, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.atan("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.atan(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.atan(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.atan(MathSpecs::Float.new).should be_close(0.785398163397448, TOLERANCE)
+ end
+end
+
+describe "Math#atan" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:atan, 3.1415).should be_close(1.2626187313511, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/atanh_spec.rb b/spec/ruby/core/math/atanh_spec.rb
new file mode 100644
index 0000000000..21fb209941
--- /dev/null
+++ b/spec/ruby/core/math/atanh_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/math/common'
+require_relative '../../shared/math/atanh'
+
+describe "Math.atanh" do
+ it_behaves_like :math_atanh_base, :atanh, Math
+ it_behaves_like :math_atanh_no_complex, :atanh, Math
+end
+
+describe "Math#atanh" do
+ it_behaves_like :math_atanh_private, :atanh
+ it_behaves_like :math_atanh_base, :atanh, IncludesMath.new
+ it_behaves_like :math_atanh_no_complex, :atanh, IncludesMath.new
+end
diff --git a/spec/ruby/core/math/cbrt_spec.rb b/spec/ruby/core/math/cbrt_spec.rb
new file mode 100644
index 0000000000..01cf923c71
--- /dev/null
+++ b/spec/ruby/core/math/cbrt_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.cbrt" do
+ it "returns a float" do
+ Math.cbrt(1).should be_an_instance_of(Float)
+ end
+
+ it "returns the cubic root of the argument" do
+ Math.cbrt(1).should == 1.0
+ Math.cbrt(8.0).should == 2.0
+ Math.cbrt(-8.0).should == -2.0
+ Math.cbrt(3).should be_close(1.44224957030741, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.cbrt("foobar") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.cbrt(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.cbrt(MathSpecs::Float.new).should be_close(1.0, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/constants_spec.rb b/spec/ruby/core/math/constants_spec.rb
new file mode 100644
index 0000000000..b500b21a79
--- /dev/null
+++ b/spec/ruby/core/math/constants_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math::PI" do
+ it "approximates the value of pi" do
+ Math::PI.should be_close(3.14159_26535_89793_23846, TOLERANCE)
+ end
+
+ it "is accessible to a class that includes Math" do
+ IncludesMath::PI.should == Math::PI
+ end
+end
+
+describe "Math::E" do
+ it "approximates the value of Napier's constant" do
+ Math::E.should be_close(2.71828_18284_59045_23536, TOLERANCE)
+ end
+
+ it "is accessible to a class that includes Math" do
+ IncludesMath::E.should == Math::E
+ end
+end
diff --git a/spec/ruby/core/math/cos_spec.rb b/spec/ruby/core/math/cos_spec.rb
new file mode 100644
index 0000000000..3ba7a54c38
--- /dev/null
+++ b/spec/ruby/core/math/cos_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# cosine : (-Inf, Inf) --> (-1.0, 1.0)
+describe "Math.cos" do
+ it "returns a float" do
+ Math.cos(Math::PI).should be_kind_of(Float)
+ end
+
+ it "returns the cosine of the argument expressed in radians" do
+ Math.cos(Math::PI).should be_close(-1.0, TOLERANCE)
+ Math.cos(0).should be_close(1.0, TOLERANCE)
+ Math.cos(Math::PI/2).should be_close(0.0, TOLERANCE)
+ Math.cos(3*Math::PI/2).should be_close(0.0, TOLERANCE)
+ Math.cos(2*Math::PI).should be_close(1.0, TOLERANCE)
+ end
+
+
+ it "raises a TypeError unless the argument is Numeric and has #to_f" do
+ -> { Math.cos("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.cos(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.cos(nil) }.should raise_error(TypeError)
+ end
+
+ it "coerces its argument with #to_f" do
+ f = mock_numeric('8.2')
+ f.should_receive(:to_f).and_return(8.2)
+ Math.cos(f).should == Math.cos(8.2)
+ end
+end
+
+describe "Math#cos" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:cos, 3.1415).should be_close(-0.999999995707656, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/cosh_spec.rb b/spec/ruby/core/math/cosh_spec.rb
new file mode 100644
index 0000000000..049e117e56
--- /dev/null
+++ b/spec/ruby/core/math/cosh_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.cosh" do
+ it "returns a float" do
+ Math.cosh(1.0).should be_kind_of(Float)
+ end
+
+ it "returns the hyperbolic cosine of the argument" do
+ Math.cosh(0.0).should == 1.0
+ Math.cosh(-0.0).should == 1.0
+ Math.cosh(1.5).should be_close(2.35240961524325, TOLERANCE)
+ Math.cosh(-2.99).should be_close(9.96798496414416, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.cosh("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.cosh(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.cosh(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.cosh(MathSpecs::Float.new).should be_close(1.54308063481524, TOLERANCE)
+ end
+end
+
+describe "Math#cosh" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:cos, 3.1415).should be_close(-0.999999995707656, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/erf_spec.rb b/spec/ruby/core/math/erf_spec.rb
new file mode 100644
index 0000000000..b4e6248e43
--- /dev/null
+++ b/spec/ruby/core/math/erf_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# erf method is the "error function" encountered in integrating the normal
+# distribution (which is a normalized form of the Gaussian function).
+describe "Math.erf" do
+ it "returns a float" do
+ Math.erf(1).should be_kind_of(Float)
+ end
+
+ it "returns the error function of the argument" do
+ Math.erf(0).should be_close(0.0, TOLERANCE)
+ Math.erf(1).should be_close(0.842700792949715, TOLERANCE)
+ Math.erf(-1).should be_close(-0.842700792949715, TOLERANCE)
+ Math.erf(0.5).should be_close(0.520499877813047, TOLERANCE)
+ Math.erf(-0.5).should be_close(-0.520499877813047, TOLERANCE)
+ Math.erf(10000).should be_close(1.0, TOLERANCE)
+ Math.erf(-10000).should be_close(-1.0, TOLERANCE)
+ Math.erf(0.00000000000001).should be_close(0.0, TOLERANCE)
+ Math.erf(-0.00000000000001).should be_close(0.0, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.erf("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.erf(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.erf(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.erf(MathSpecs::Float.new).should be_close(0.842700792949715, TOLERANCE)
+ end
+end
+
+describe "Math#erf" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:erf, 3.1415).should be_close(0.999991118444483, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/erfc_spec.rb b/spec/ruby/core/math/erfc_spec.rb
new file mode 100644
index 0000000000..e465f5cf58
--- /dev/null
+++ b/spec/ruby/core/math/erfc_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# erfc is the complementary error function
+describe "Math.erfc" do
+ it "returns a float" do
+ Math.erf(1).should be_kind_of(Float)
+ end
+
+ it "returns the complementary error function of the argument" do
+ Math.erfc(0).should be_close(1.0, TOLERANCE)
+ Math.erfc(1).should be_close(0.157299207050285, TOLERANCE)
+ Math.erfc(-1).should be_close(1.84270079294971, TOLERANCE)
+ Math.erfc(0.5).should be_close(0.479500122186953, TOLERANCE)
+ Math.erfc(-0.5).should be_close(1.52049987781305, TOLERANCE)
+ Math.erfc(10000).should be_close(0.0, TOLERANCE)
+ Math.erfc(-10000).should be_close(2.0, TOLERANCE)
+ Math.erfc(0.00000000000001).should be_close(0.999999999999989, TOLERANCE)
+ Math.erfc(-0.00000000000001).should be_close(1.00000000000001, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.erfc("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.erfc(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.erfc(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.erfc(MathSpecs::Float.new).should be_close(0.157299207050285, TOLERANCE)
+ end
+end
+
+describe "Math#erfc" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:erf, 3.1415).should be_close(0.999991118444483, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/exp_spec.rb b/spec/ruby/core/math/exp_spec.rb
new file mode 100644
index 0000000000..36eb49a8c7
--- /dev/null
+++ b/spec/ruby/core/math/exp_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.exp" do
+ it "returns a float" do
+ Math.exp(1.0).should be_kind_of(Float)
+ end
+
+ it "returns the base-e exponential of the argument" do
+ Math.exp(0.0).should == 1.0
+ Math.exp(-0.0).should == 1.0
+ Math.exp(-1.8).should be_close(0.165298888221587, TOLERANCE)
+ Math.exp(1.25).should be_close(3.49034295746184, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.exp("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.exp(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.exp(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.exp(MathSpecs::Float.new).should be_close(Math::E, TOLERANCE)
+ end
+end
+
+describe "Math#exp" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:exp, 23.1415).should be_close(11226018484.0012, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/fixtures/classes.rb b/spec/ruby/core/math/fixtures/classes.rb
new file mode 100644
index 0000000000..6f2241e739
--- /dev/null
+++ b/spec/ruby/core/math/fixtures/classes.rb
@@ -0,0 +1,28 @@
+class IncludesMath
+ include Math
+end
+
+module MathSpecs
+ class Float < Numeric
+ def initialize(value=1.0)
+ @value = value
+ end
+
+ def to_f
+ @value
+ end
+ end
+
+ class Integer
+ def to_int
+ 2
+ end
+ end
+
+ class UserClass
+ end
+
+ class StringSubClass < String
+ end
+
+end
diff --git a/spec/ruby/core/math/frexp_spec.rb b/spec/ruby/core/math/frexp_spec.rb
new file mode 100644
index 0000000000..7dfb493d20
--- /dev/null
+++ b/spec/ruby/core/math/frexp_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.frexp" do
+ it "returns the normalized fraction and exponent" do
+ frac, exp = Math.frexp(102.83)
+ frac.should be_close(0.803359375, TOLERANCE)
+ exp.should == 7
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.frexp("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ frac, _exp = Math.frexp(nan_value)
+ frac.nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.frexp(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ frac, exp = Math.frexp(MathSpecs::Float.new)
+ frac.should be_close(0.5, TOLERANCE)
+ exp.should == 1
+ end
+end
+
+describe "Math#frexp" do
+ it "is accessible as a private instance method" do
+ frac, exp = IncludesMath.new.send(:frexp, 2.1415)
+ frac.should be_close(0.535375, TOLERANCE)
+ exp.should == 2
+ end
+end
diff --git a/spec/ruby/core/math/gamma_spec.rb b/spec/ruby/core/math/gamma_spec.rb
new file mode 100644
index 0000000000..386162a087
--- /dev/null
+++ b/spec/ruby/core/math/gamma_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+
+describe "Math.gamma" do
+ it "returns +infinity given 0" do
+ Math.gamma(0).should == Float::INFINITY
+ end
+
+ platform_is_not :windows do
+ # https://bugs.ruby-lang.org/issues/12249
+ it "returns -infinity given -0.0" do
+ Math.gamma(-0.0).should == -Float::INFINITY
+ end
+ end
+
+ it "returns Math.sqrt(Math::PI) given 0.5" do
+ Math.gamma(0.5).should be_close(Math.sqrt(Math::PI), TOLERANCE)
+ end
+
+ # stop at n == 23 because 23! cannot be exactly represented by IEEE 754 double
+ it "returns exactly (n-1)! given n for n between 2 and 23" do
+ fact = 1
+ 2.upto(23) do |n|
+ fact *= (n - 1)
+ Math.gamma(n).should == fact
+ end
+ end
+
+ it "returns approximately (n-1)! given n for n between 24 and 30" do
+ fact2 = 1124000727777607680000 # 22!
+ 24.upto(30) do |n|
+ fact2 *= n - 1
+ # compare only the first 12 places, tolerate the rest
+ Math.gamma(n).should be_close(fact2, fact2.to_s[12..-1].to_i)
+ end
+ end
+
+ it "returns good numerical approximation for gamma(3.2)" do
+ Math.gamma(3.2).should be_close(2.423965, TOLERANCE)
+ end
+
+ it "returns good numerical approximation for gamma(-2.15)" do
+ Math.gamma(-2.15).should be_close(-2.999619, TOLERANCE)
+ end
+
+ it "returns good numerical approximation for gamma(0.00001)" do
+ Math.gamma(0.00001).should be_close(99999.422794, TOLERANCE)
+ end
+
+ it "returns good numerical approximation for gamma(-0.00001)" do
+ Math.gamma(-0.00001).should be_close(-100000.577225, TOLERANCE)
+ end
+
+ it "raises Math::DomainError given -1" do
+ -> { Math.gamma(-1) }.should raise_error(Math::DomainError)
+ end
+
+ # See https://bugs.ruby-lang.org/issues/10642
+ it "returns +infinity given +infinity" do
+ Math.gamma(infinity_value).infinite?.should == 1
+ end
+
+ it "raises Math::DomainError given negative infinity" do
+ -> { Math.gamma(-Float::INFINITY) }.should raise_error(Math::DomainError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.gamma(nan_value).nan?.should be_true
+ end
+end
diff --git a/spec/ruby/core/math/hypot_spec.rb b/spec/ruby/core/math/hypot_spec.rb
new file mode 100644
index 0000000000..3e0ce74597
--- /dev/null
+++ b/spec/ruby/core/math/hypot_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.hypot" do
+ it "returns a float" do
+ Math.hypot(3, 4).should be_kind_of(Float)
+ end
+
+ it "returns the length of the hypotenuse of a right triangle with legs given by the arguments" do
+ Math.hypot(0, 0).should be_close(0.0, TOLERANCE)
+ Math.hypot(2, 10).should be_close( 10.1980390271856, TOLERANCE)
+ Math.hypot(5000, 5000).should be_close(7071.06781186548, TOLERANCE)
+ Math.hypot(0.0001, 0.0002).should be_close(0.000223606797749979, TOLERANCE)
+ Math.hypot(-2, -10).should be_close(10.1980390271856, TOLERANCE)
+ Math.hypot(2, 10).should be_close(10.1980390271856, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.hypot("test", "this") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.hypot(nan_value, 0).nan?.should be_true
+ Math.hypot(0, nan_value).nan?.should be_true
+ Math.hypot(nan_value, nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.hypot(nil, nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.hypot(MathSpecs::Float.new, MathSpecs::Float.new).should be_close(1.4142135623731, TOLERANCE)
+ end
+end
+
+describe "Math#hypot" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:hypot, 2, 3.1415).should be_close(3.72411361937307, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/ldexp_spec.rb b/spec/ruby/core/math/ldexp_spec.rb
new file mode 100644
index 0000000000..6dcf94a663
--- /dev/null
+++ b/spec/ruby/core/math/ldexp_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.ldexp" do
+ it "returns a float" do
+ Math.ldexp(1.0, 2).should be_kind_of(Float)
+ end
+
+ it "returns the argument multiplied by 2**n" do
+ Math.ldexp(0.0, 0.0).should == 0.0
+ Math.ldexp(0.0, 1.0).should == 0.0
+ Math.ldexp(-1.25, 2).should be_close(-5.0, TOLERANCE)
+ Math.ldexp(2.1, -3).should be_close(0.2625, TOLERANCE)
+ Math.ldexp(5.7, 4).should be_close(91.2, TOLERANCE)
+ end
+
+ it "raises a TypeError if the first argument cannot be coerced with Float()" do
+ -> { Math.ldexp("test", 2) }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.ldexp(nan_value, 0).nan?.should be_true
+ end
+
+ it "raises RangeError if NaN is given as the second arg" do
+ -> { Math.ldexp(0, nan_value) }.should raise_error(RangeError)
+ end
+
+ it "raises a TypeError if the second argument cannot be coerced with Integer()" do
+ -> { Math.ldexp(3.2, "this") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the first argument is nil" do
+ -> { Math.ldexp(nil, 2) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the second argument is nil" do
+ -> { Math.ldexp(3.1, nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any first argument that can be coerced with Float()" do
+ Math.ldexp(MathSpecs::Float.new, 2).should be_close(4.0, TOLERANCE)
+ end
+
+ it "accepts any second argument that can be coerced with Integer()" do
+ Math.ldexp(3.23, MathSpecs::Integer.new).should be_close(12.92, TOLERANCE)
+ end
+
+ it "returns correct value that closes to the max value of double type" do
+ Math.ldexp(0.5122058490966879, 1024).should == 9.207889385574391e+307
+ Math.ldexp(0.9999999999999999, 1024).should == 1.7976931348623157e+308
+ Math.ldexp(0.99999999999999999, 1024).should == Float::INFINITY
+ end
+end
+
+describe "Math#ldexp" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:ldexp, 3.1415, 2).should be_close(12.566, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/lgamma_spec.rb b/spec/ruby/core/math/lgamma_spec.rb
new file mode 100644
index 0000000000..33e7836448
--- /dev/null
+++ b/spec/ruby/core/math/lgamma_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+
+describe "Math.lgamma" do
+ it "returns [Infinity, 1] when passed 0" do
+ Math.lgamma(0).should == [infinity_value, 1]
+ end
+
+ platform_is_not :windows do
+ it "returns [Infinity, 1] when passed -1" do
+ Math.lgamma(-1).should == [infinity_value, 1]
+ end
+ end
+
+ it "returns [Infinity, -1] when passed -0.0" do
+ Math.lgamma(-0.0).should == [infinity_value, -1]
+ end
+
+ it "returns [log(sqrt(PI)), 1] when passed 0.5" do
+ lg1 = Math.lgamma(0.5)
+ lg1[0].should be_close(Math.log(Math.sqrt(Math::PI)), TOLERANCE)
+ lg1[1].should == 1
+ end
+
+ it "returns [log(2/3*PI, 1] when passed 6.0" do
+ lg2 = Math.lgamma(6.0)
+ lg2[0].should be_close(Math.log(120.0), TOLERANCE)
+ lg2[1].should == 1
+ end
+
+ it "returns an approximate value when passed -0.5" do
+ lg1 = Math.lgamma(-0.5)
+ lg1[0].should be_close(1.2655121, TOLERANCE)
+ lg1[1].should == -1
+ end
+
+ it "returns an approximate value when passed -1.5" do
+ lg2 = Math.lgamma(-1.5)
+ lg2[0].should be_close(0.8600470, TOLERANCE)
+ lg2[1].should == 1
+ end
+
+ it "raises Math::DomainError when passed -Infinity" do
+ -> { Math.lgamma(-infinity_value) }.should raise_error(Math::DomainError)
+ end
+
+ it "returns [Infinity, 1] when passed Infinity" do
+ Math.lgamma(infinity_value).should == [infinity_value, 1]
+ end
+
+ it "returns [NaN, 1] when passed NaN" do
+ Math.lgamma(nan_value)[0].nan?.should be_true
+ Math.lgamma(nan_value)[1].should == 1
+ end
+end
diff --git a/spec/ruby/core/math/log10_spec.rb b/spec/ruby/core/math/log10_spec.rb
new file mode 100644
index 0000000000..c4daedcd5c
--- /dev/null
+++ b/spec/ruby/core/math/log10_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# The common logarithm, having base 10
+describe "Math.log10" do
+ it "returns a float" do
+ Math.log10(1).should be_kind_of(Float)
+ end
+
+ it "returns the base-10 logarithm of the argument" do
+ Math.log10(0.0001).should be_close(-4.0, TOLERANCE)
+ Math.log10(0.000000000001e-15).should be_close(-27.0, TOLERANCE)
+ Math.log10(1).should be_close(0.0, TOLERANCE)
+ Math.log10(10).should be_close(1.0, TOLERANCE)
+ Math.log10(10e15).should be_close(16.0, TOLERANCE)
+ end
+
+ it "raises an Math::DomainError if the argument is less than 0" do
+ -> { Math.log10(-1e-15) }.should raise_error(Math::DomainError)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.log10("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.log10(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.log10(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.log10(MathSpecs::Float.new).should be_close(0.0, TOLERANCE)
+ end
+end
+
+describe "Math#log10" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:log10, 4.15).should be_close(0.618048096712093, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/log2_spec.rb b/spec/ruby/core/math/log2_spec.rb
new file mode 100644
index 0000000000..3d4d41d130
--- /dev/null
+++ b/spec/ruby/core/math/log2_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.log2" do
+ it "returns a float" do
+ Math.log2(5.79).should be_close(2.53356334821451, TOLERANCE)
+ end
+
+ it "returns the natural logarithm of the argument" do
+ Math.log2(1.1).should be_close(0.137503523749935, TOLERANCE)
+ Math.log2(3.14).should be_close(1.6507645591169, TOLERANCE)
+ Math.log2((2**101+45677544234809571)).should be_close(101.00000000000003, TOLERANCE)
+
+ Math.log2((2**10001+45677544234809571)).should == 10001.0
+ Math.log2((2**301+45677544234809571)).should == 301.0
+ end
+
+ it "raises Math::DomainError if the argument is less than 0" do
+ -> { Math.log2(-1e-15) }.should raise_error( Math::DomainError)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.log2("test") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed a numerical argument as a string" do
+ -> { Math.log2("1.0") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.log2(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.log2(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.log2(MathSpecs::Float.new).should be_close(0.0, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/log_spec.rb b/spec/ruby/core/math/log_spec.rb
new file mode 100644
index 0000000000..6c5036ba81
--- /dev/null
+++ b/spec/ruby/core/math/log_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# The natural logarithm, having base Math::E
+describe "Math.log" do
+ it "returns a float" do
+ Math.log(1).should be_kind_of(Float)
+ end
+
+ it "returns the natural logarithm of the argument" do
+ Math.log(0.0001).should be_close(-9.21034037197618, TOLERANCE)
+ Math.log(0.000000000001e-15).should be_close(-62.1697975108392, TOLERANCE)
+ Math.log(1).should be_close(0.0, TOLERANCE)
+ Math.log(10).should be_close( 2.30258509299405, TOLERANCE)
+ Math.log(10e15).should be_close(36.8413614879047, TOLERANCE)
+ end
+
+ it "raises an Math::DomainError if the argument is less than 0" do
+ -> { Math.log(-1e-15) }.should raise_error(Math::DomainError)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.log("test") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for numerical values passed as string" do
+ -> { Math.log("10") }.should raise_error(TypeError)
+ end
+
+ it "accepts a second argument for the base" do
+ Math.log(9, 3).should be_close(2, TOLERANCE)
+ Math.log(8, 1.4142135623730951).should be_close(6, TOLERANCE)
+ end
+
+ it "raises a TypeError when the numerical base cannot be coerced to a float" do
+ -> { Math.log(10, "2") }.should raise_error(TypeError)
+ -> { Math.log(10, nil) }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.log(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.log(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.log(MathSpecs::Float.new).should be_close(0.0, TOLERANCE)
+ end
+end
+
+describe "Math#log" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:log, 5.21).should be_close(1.65057985576528, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/sin_spec.rb b/spec/ruby/core/math/sin_spec.rb
new file mode 100644
index 0000000000..8e944bc95f
--- /dev/null
+++ b/spec/ruby/core/math/sin_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# sine : (-Inf, Inf) --> (-1.0, 1.0)
+describe "Math.sin" do
+ it "returns a float" do
+ Math.sin(Math::PI).should be_kind_of(Float)
+ end
+
+ it "returns the sine of the argument expressed in radians" do
+ Math.sin(Math::PI).should be_close(0.0, TOLERANCE)
+ Math.sin(0).should be_close(0.0, TOLERANCE)
+ Math.sin(Math::PI/2).should be_close(1.0, TOLERANCE)
+ Math.sin(3*Math::PI/2).should be_close(-1.0, TOLERANCE)
+ Math.sin(2*Math::PI).should be_close(0.0, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.sin("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.sin(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.sin(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.sin(MathSpecs::Float.new).should be_close(0.841470984807897, TOLERANCE)
+ end
+end
+
+describe "Math#sin" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:sin, 1.21).should be_close(0.935616001553386, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/sinh_spec.rb b/spec/ruby/core/math/sinh_spec.rb
new file mode 100644
index 0000000000..027c2395a7
--- /dev/null
+++ b/spec/ruby/core/math/sinh_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.sinh" do
+ it "returns a float" do
+ Math.sinh(1.2).should be_kind_of(Float)
+ end
+
+ it "returns the hyperbolic sin of the argument" do
+ Math.sinh(0.0).should == 0.0
+ Math.sinh(-0.0).should == 0.0
+ Math.sinh(1.5).should be_close(2.12927945509482, TOLERANCE)
+ Math.sinh(-2.8).should be_close(-8.19191835423591, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.sinh("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.sinh(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.sinh(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.sinh(MathSpecs::Float.new).should be_close(1.1752011936438, TOLERANCE)
+ end
+end
+
+describe "Math#sinh" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:sinh, 1.99).should be_close(3.58941916843202, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/sqrt_spec.rb b/spec/ruby/core/math/sqrt_spec.rb
new file mode 100644
index 0000000000..918e7c3a17
--- /dev/null
+++ b/spec/ruby/core/math/sqrt_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.sqrt" do
+ it "returns a float" do
+ Math.sqrt(1).should be_kind_of(Float)
+ end
+
+ it "returns the square root of the argument" do
+ Math.sqrt(1).should == 1.0
+ Math.sqrt(4.0).should == 2.0
+ Math.sqrt(15241578780673814.441547445).should be_close(123456789.123457, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.sqrt("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.sqrt(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.sqrt(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.sqrt(MathSpecs::Float.new).should be_close(1.0, TOLERANCE)
+ end
+
+ it "raises a Math::DomainError when given a negative number" do
+ -> { Math.sqrt(-1) }.should raise_error(Math::DomainError)
+ end
+end
+
+describe "Math#sqrt" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:sqrt, 2.23).should be_close(1.49331845230681, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/tan_spec.rb b/spec/ruby/core/math/tan_spec.rb
new file mode 100644
index 0000000000..67307f1e6e
--- /dev/null
+++ b/spec/ruby/core/math/tan_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.tan" do
+ it "returns a float" do
+ Math.tan(1.35).should be_kind_of(Float)
+ end
+
+ it "returns the tangent of the argument" do
+ Math.tan(0.0).should == 0.0
+ Math.tan(-0.0).should == -0.0
+ Math.tan(4.22).should be_close(1.86406937682395, TOLERANCE)
+ Math.tan(-9.65).should be_close(-0.229109052606441, TOLERANCE)
+ end
+
+ it "returns NaN if called with +-Infinity" do
+ Math.tan(infinity_value).should.nan?
+ Math.tan(-infinity_value).should.nan?
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.tan("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.tan(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.tan(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.tan(MathSpecs::Float.new).should be_close(1.5574077246549, TOLERANCE)
+ end
+end
+
+describe "Math#tan" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:tan, 1.0).should be_close(1.5574077246549, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/math/tanh_spec.rb b/spec/ruby/core/math/tanh_spec.rb
new file mode 100644
index 0000000000..568f8dfa77
--- /dev/null
+++ b/spec/ruby/core/math/tanh_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Math.tanh" do
+ it "returns a float" do
+ Math.tanh(0.5).should be_kind_of(Float)
+ end
+
+ it "returns the hyperbolic tangent of the argument" do
+ Math.tanh(0.0).should == 0.0
+ Math.tanh(-0.0).should == -0.0
+ Math.tanh(infinity_value).should == 1.0
+ Math.tanh(-infinity_value).should == -1.0
+ Math.tanh(2.5).should be_close(0.98661429815143, TOLERANCE)
+ Math.tanh(-4.892).should be_close(-0.999887314427707, TOLERANCE)
+ end
+
+ it "raises a TypeError if the argument cannot be coerced with Float()" do
+ -> { Math.tanh("test") }.should raise_error(TypeError)
+ end
+
+ it "returns NaN given NaN" do
+ Math.tanh(nan_value).nan?.should be_true
+ end
+
+ it "raises a TypeError if the argument is nil" do
+ -> { Math.tanh(nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts any argument that can be coerced with Float()" do
+ Math.tanh(MathSpecs::Float.new).should be_close(0.761594155955765, TOLERANCE)
+ end
+end
+
+describe "Math#tanh" do
+ it "is accessible as a private instance method" do
+ IncludesMath.new.send(:tanh, 5.21).should be_close(0.99994034202065, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/method/arity_spec.rb b/spec/ruby/core/method/arity_spec.rb
new file mode 100644
index 0000000000..4bb821735a
--- /dev/null
+++ b/spec/ruby/core/method/arity_spec.rb
@@ -0,0 +1,222 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#arity" do
+ SpecEvaluate.desc = "for method definition"
+
+ context "returns zero" do
+ evaluate <<-ruby do
+ def m() end
+ ruby
+
+ method(:m).arity.should == 0
+ end
+
+ evaluate <<-ruby do
+ def n(&b) end
+ ruby
+
+ method(:n).arity.should == 0
+ end
+ end
+
+ context "returns positive values" do
+ evaluate <<-ruby do
+ def m(a) end
+ def n(a, b) end
+ def o(a, b, c) end
+ def p(a, b, c, d) end
+ ruby
+
+ method(:m).arity.should == 1
+ method(:n).arity.should == 2
+ method(:o).arity.should == 3
+ method(:p).arity.should == 4
+ end
+
+ evaluate <<-ruby do
+ def m(a:) end
+ def n(a:, b:) end
+ def o(a: 1, b:, c:, d: 2) end
+ ruby
+
+ method(:m).arity.should == 1
+ method(:n).arity.should == 1
+ method(:o).arity.should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(a, b:) end
+ def n(a, b:, &l) end
+ ruby
+
+ method(:m).arity.should == 2
+ method(:n).arity.should == 2
+ end
+
+ evaluate <<-ruby do
+ def m(a, b, c:, d: 1) end
+ def n(a, b, c:, d: 1, **k, &l) end
+ ruby
+
+ method(:m).arity.should == 3
+ method(:n).arity.should == 3
+ end
+ end
+
+ context "returns negative values" do
+ evaluate <<-ruby do
+ def m(a=1) end
+ def n(a=1, b=2) end
+ ruby
+
+ method(:m).arity.should == -1
+ method(:n).arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1) end
+ def n(a, b, c=1, d=2) end
+ ruby
+
+ method(:m).arity.should == -2
+ method(:n).arity.should == -3
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, *b) end
+ def n(a=1, b=2, *c) end
+ ruby
+
+ method(:m).arity.should == -1
+ method(:n).arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(*) end
+ def n(*a) end
+ ruby
+
+ method(:m).arity.should == -1
+ method(:n).arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(a, *) end
+ def n(a, *b) end
+ def o(a, b, *c) end
+ def p(a, b, c, *d) end
+ ruby
+
+ method(:m).arity.should == -2
+ method(:n).arity.should == -2
+ method(:o).arity.should == -3
+ method(:p).arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ def m(*a, b) end
+ def n(*a, b, c) end
+ def o(*a, b, c, d) end
+ ruby
+
+ method(:m).arity.should == -2
+ method(:n).arity.should == -3
+ method(:o).arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ def m(a, *b, c) end
+ def n(a, b, *c, d, e) end
+ ruby
+
+ method(:m).arity.should == -3
+ method(:n).arity.should == -5
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1, c=2, *d, e, f) end
+ def n(a, b, c=1, *d, e, f, g) end
+ ruby
+
+ method(:m).arity.should == -4
+ method(:n).arity.should == -6
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1) end
+ def n(a: 1, b: 2) end
+ ruby
+
+ method(:m).arity.should == -1
+ method(:n).arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, b: 2) end
+ def n(*a, b: 1) end
+ def o(a=1, b: 2) end
+ def p(a=1, *b, c: 2, &l) end
+ ruby
+
+ method(:m).arity.should == -1
+ method(:n).arity.should == -1
+ method(:o).arity.should == -1
+ method(:p).arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(**k, &l) end
+ def n(*a, **k) end
+ def o(a: 1, b: 2, **k) end
+ ruby
+
+ method(:m).arity.should == -1
+ method(:n).arity.should == -1
+ method(:o).arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, *b, c:, d: 2, **k, &l) end
+ ruby
+
+ method(:m).arity.should == -2
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1, *c, d, e:, f: 2, **k, &l) end
+ def n(a, b=1, *c, d:, e:, f: 2, **k, &l) end
+ def o(a=0, b=1, *c, d, e:, f: 2, **k, &l) end
+ def p(a=0, b=1, *c, d:, e:, f: 2, **k, &l) end
+ ruby
+
+ method(:m).arity.should == -4
+ method(:n).arity.should == -3
+ method(:o).arity.should == -3
+ method(:p).arity.should == -2
+ end
+ end
+
+ context "for a Method generated by respond_to_missing?" do
+ it "returns -1" do
+ obj = mock("method arity respond_to_missing")
+ obj.should_receive(:respond_to_missing?).and_return(true)
+
+ obj.method(:m).arity.should == -1
+ end
+ end
+
+ context "for a Method generated by attr_reader" do
+ it "return 0" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:reader).arity.should == 0
+ end
+ end
+
+ context "for a Method generated by attr_writer" do
+ it "returns 1" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:writer=).arity.should == 1
+ end
+ end
+end
diff --git a/spec/ruby/core/method/call_spec.rb b/spec/ruby/core/method/call_spec.rb
new file mode 100644
index 0000000000..6d997325fa
--- /dev/null
+++ b/spec/ruby/core/method/call_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/call'
+
+describe "Method#call" do
+ it_behaves_like :method_call, :call
+end
diff --git a/spec/ruby/core/method/case_compare_spec.rb b/spec/ruby/core/method/case_compare_spec.rb
new file mode 100644
index 0000000000..a78953e8ad
--- /dev/null
+++ b/spec/ruby/core/method/case_compare_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/call'
+
+describe "Method#===" do
+ it_behaves_like :method_call, :===
+end
diff --git a/spec/ruby/core/method/clone_spec.rb b/spec/ruby/core/method/clone_spec.rb
new file mode 100644
index 0000000000..3fe4000fb7
--- /dev/null
+++ b/spec/ruby/core/method/clone_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#clone" do
+ it "returns a copy of the method" do
+ m1 = MethodSpecs::Methods.new.method(:foo)
+ m2 = m1.clone
+
+ m1.should == m2
+ m1.should_not equal(m2)
+
+ m1.call.should == m2.call
+ end
+end
diff --git a/spec/ruby/core/method/compose_spec.rb b/spec/ruby/core/method/compose_spec.rb
new file mode 100644
index 0000000000..87cf61f7ad
--- /dev/null
+++ b/spec/ruby/core/method/compose_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../proc/shared/compose'
+
+describe "Method#<<" do
+ it "returns a Proc that is the composition of self and the passed Proc" do
+ succ = MethodSpecs::Composition.new.method(:succ)
+ upcase = proc { |s| s.upcase }
+
+ (succ << upcase).call('Ruby').should == "RUBZ"
+ end
+
+ it "calls passed Proc with arguments and then calls self with result" do
+ pow_2_proc = proc { |x| x * x }
+ double_proc = proc { |x| x + x }
+
+ pow_2_method = MethodSpecs::Composition.new.method(:pow_2)
+ double_method = MethodSpecs::Composition.new.method(:double)
+
+ (pow_2_method << double_proc).call(2).should == 16
+ (double_method << pow_2_proc).call(2).should == 8
+ end
+
+ it "accepts any callable object" do
+ inc = MethodSpecs::Composition.new.method(:inc)
+
+ double = Object.new
+ def double.call(n); n * 2; end
+
+ (inc << double).call(3).should == 7
+ end
+
+ it_behaves_like :proc_compose, :<<, -> { MethodSpecs::Composition.new.method(:upcase) }
+
+ describe "composition" do
+ it "is a lambda" do
+ pow_2 = MethodSpecs::Composition.new.method(:pow_2)
+ double = proc { |x| x + x }
+
+ (pow_2 << double).is_a?(Proc).should == true
+ ruby_version_is(''...'3.0') { (pow_2 << double).should.lambda? }
+ ruby_version_is('3.0') { (pow_2 << double).should_not.lambda? }
+ end
+
+ it "may accept multiple arguments" do
+ inc = MethodSpecs::Composition.new.method(:inc)
+ mul = proc { |n, m| n * m }
+
+ (inc << mul).call(2, 3).should == 7
+ end
+ end
+end
+
+describe "Method#>>" do
+ it "returns a Proc that is the composition of self and the passed Proc" do
+ upcase = proc { |s| s.upcase }
+ succ = MethodSpecs::Composition.new.method(:succ)
+
+ (succ >> upcase).call('Ruby').should == "RUBZ"
+ end
+
+ it "calls passed Proc with arguments and then calls self with result" do
+ pow_2_proc = proc { |x| x * x }
+ double_proc = proc { |x| x + x }
+
+ pow_2_method = MethodSpecs::Composition.new.method(:pow_2)
+ double_method = MethodSpecs::Composition.new.method(:double)
+
+ (pow_2_method >> double_proc).call(2).should == 8
+ (double_method >> pow_2_proc).call(2).should == 16
+ end
+
+ it "accepts any callable object" do
+ inc = MethodSpecs::Composition.new.method(:inc)
+
+ double = Object.new
+ def double.call(n); n * 2; end
+
+ (inc >> double).call(3).should == 8
+ end
+
+ it_behaves_like :proc_compose, :>>, -> { MethodSpecs::Composition.new.method(:upcase) }
+
+ describe "composition" do
+ it "is a lambda" do
+ pow_2 = MethodSpecs::Composition.new.method(:pow_2)
+ double = proc { |x| x + x }
+
+ (pow_2 >> double).is_a?(Proc).should == true
+ (pow_2 >> double).should.lambda?
+ end
+
+ it "may accept multiple arguments" do
+ mul = MethodSpecs::Composition.new.method(:mul)
+ inc = proc { |n| n + 1 }
+
+ (mul >> inc).call(2, 3).should == 7
+ end
+ end
+end
diff --git a/spec/ruby/core/method/curry_spec.rb b/spec/ruby/core/method/curry_spec.rb
new file mode 100644
index 0000000000..219de0f6b5
--- /dev/null
+++ b/spec/ruby/core/method/curry_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#curry" do
+ it "returns a curried proc" do
+ x = Object.new
+ def x.foo(a,b,c); [a,b,c]; end
+
+ c = x.method(:foo).curry
+ c.should be_kind_of(Proc)
+ c.call(1).call(2, 3).should == [1,2,3]
+ end
+
+ describe "with optional arity argument" do
+ before(:each) do
+ @obj = MethodSpecs::Methods.new
+ end
+
+ it "returns a curried proc when given correct arity" do
+ @obj.method(:one_req).curry(1).should be_kind_of(Proc)
+ @obj.method(:zero_with_splat).curry(100).should be_kind_of(Proc)
+ @obj.method(:two_req_with_splat).curry(2).should be_kind_of(Proc)
+ end
+
+ it "raises ArgumentError when the method requires less arguments than the given arity" do
+ -> { @obj.method(:zero).curry(1) }.should raise_error(ArgumentError)
+ -> { @obj.method(:one_req_one_opt).curry(3) }.should raise_error(ArgumentError)
+ -> { @obj.method(:two_req_one_opt_with_block).curry(4) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError when the method requires more arguments than the given arity" do
+ -> { @obj.method(:two_req_with_splat).curry(1) }.should raise_error(ArgumentError)
+ -> { @obj.method(:one_req).curry(0) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/method/element_reference_spec.rb b/spec/ruby/core/method/element_reference_spec.rb
new file mode 100644
index 0000000000..aa6c54d1cb
--- /dev/null
+++ b/spec/ruby/core/method/element_reference_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/call'
+
+describe "Method#[]" do
+ it_behaves_like :method_call, :[]
+end
diff --git a/spec/ruby/core/method/eql_spec.rb b/spec/ruby/core/method/eql_spec.rb
new file mode 100644
index 0000000000..b97c9e4db0
--- /dev/null
+++ b/spec/ruby/core/method/eql_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eql'
+
+describe "Method#eql?" do
+ it_behaves_like :method_equal, :eql?
+end
diff --git a/spec/ruby/core/method/equal_value_spec.rb b/spec/ruby/core/method/equal_value_spec.rb
new file mode 100644
index 0000000000..0431d0c5f6
--- /dev/null
+++ b/spec/ruby/core/method/equal_value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eql'
+
+describe "Method#==" do
+ it_behaves_like :method_equal, :==
+end
diff --git a/spec/ruby/core/method/fixtures/classes.rb b/spec/ruby/core/method/fixtures/classes.rb
new file mode 100644
index 0000000000..464a519aea
--- /dev/null
+++ b/spec/ruby/core/method/fixtures/classes.rb
@@ -0,0 +1,246 @@
+module MethodSpecs
+
+
+ class SourceLocation
+ def self.location # This needs to be on this line
+ :location # for the spec to pass
+ end
+
+ def self.redefined
+ :first
+ end
+
+ def self.redefined
+ :last
+ end
+
+ def original
+ end
+
+ alias :aka :original
+ end
+
+ class Methods
+ def foo
+ true
+ end
+
+ alias bar foo
+ alias baz bar
+
+ def same_as_foo
+ true
+ end
+
+ def respond_to_missing? method, bool
+ [:handled_via_method_missing, :also_handled].include? method
+ end
+
+ def method_missing(method, *arguments)
+ if [:handled_via_method_missing, :also_handled].include? method
+ arguments
+ else
+ super
+ end
+ end
+
+ attr_accessor :attr
+
+ def zero; end
+ def one_req(a); end
+ def two_req(a, b); end
+
+ def one_req_named(a:); end
+
+ def zero_with_block(&blk); end
+ def one_req_with_block(a, &blk); end
+ def two_req_with_block(a, b, &blk); end
+
+ def one_opt(a=nil); end
+ def one_req_one_opt(a, b=nil); end
+ def one_req_two_opt(a, b=nil, c=nil); end
+ def two_req_one_opt(a, b, c=nil); end
+
+ def one_opt_named(a: nil); end
+
+ def one_opt_with_block(a=nil, &blk); end
+ def one_req_one_opt_with_block(a, b=nil, &blk); end
+ def one_req_two_opt_with_block(a, b=nil, c=nil, &blk); end
+ def two_req_one_opt_with_block(a, b, c=nil, &blk); end
+
+ def zero_with_splat(*a); end
+ def one_req_with_splat(a, *b); end
+ def two_req_with_splat(a, b, *c); end
+ def one_req_one_opt_with_splat(a, b=nil, *c); end
+ def two_req_one_opt_with_splat(a, b, c=nil, *d); end
+ def one_req_two_opt_with_splat(a, b=nil, c=nil, *d); end
+
+ def zero_with_double_splat(**a); end
+
+ def zero_with_splat_and_block(*a, &blk); end
+ def one_req_with_splat_and_block(a, *b, &blk); end
+ def two_req_with_splat_and_block(a, b, *c, &blk); end
+ def one_req_one_opt_with_splat_and_block(a, b=nil, *c, &blk); end
+ def two_req_one_opt_with_splat_and_block(a, b, c=nil, *d, &blk); end
+ def one_req_two_opt_with_splat_and_block(a, b=nil, c=nil, *d, &blk); end
+
+ def my_public_method; end
+ def my_protected_method; end
+ def my_private_method; end
+ protected :my_protected_method
+ private :my_private_method
+
+ define_method(:zero_defined_method, Proc.new {||})
+ define_method(:zero_with_splat_defined_method, Proc.new {|*x|})
+ define_method(:one_req_defined_method, Proc.new {|x|})
+ define_method(:two_req_defined_method, Proc.new {|x, y|})
+ define_method(:no_args_defined_method) {}
+ define_method(:two_grouped_defined_method) {|(_x1,_x2)|}
+
+ attr_reader :reader
+ attr_writer :writer
+ end
+
+ module MyMod
+ def bar; :bar; end
+ end
+
+ class MySuper
+ include MyMod
+ end
+
+ class MySub < MySuper; end
+
+ class A
+ def baz(a, b)
+ self.class
+ end
+ def overridden; end
+ end
+
+ class B < A
+ def overridden; end
+ end
+
+ module BetweenBAndC
+ def overridden; end
+ end
+
+ class C < B
+ include BetweenBAndC
+ def overridden; end
+ end
+
+ module OverrideAgain
+ def overridden; end
+ end
+
+ class D
+ def bar() 'done' end
+ end
+
+ class Eql
+
+ def same_body
+ 1 + 1
+ end
+
+ alias :same_body_alias :same_body
+
+ def same_body_with_args(arg)
+ 1 + 1
+ end
+
+ def different_body
+ 1 + 2
+ end
+
+ def same_body_two
+ 1 + 1
+ end
+
+ private
+ def same_body_private
+ 1 + 1
+ end
+ end
+
+ class Eql2
+
+ def same_body
+ 1 + 1
+ end
+
+ end
+
+ class ToProc
+ def method_called(a, b)
+ ScratchPad << [a, b]
+ end
+
+ def to_proc
+ method(:method_called).to_proc
+ end
+ end
+
+ class ToProcBeta
+ def method_called(a)
+ ScratchPad << a
+ a
+ end
+
+ def to_proc
+ method(:method_called).to_proc
+ end
+ end
+
+ class Composition
+ def upcase(s)
+ s.upcase
+ end
+
+ def succ(s)
+ s.succ
+ end
+
+ def pow_2(n)
+ n * n
+ end
+
+ def double(n)
+ n + n
+ end
+
+ def inc(n)
+ n + 1
+ end
+
+ def mul(n, m)
+ n * m
+ end
+ end
+
+ module InheritedMethods
+ module A
+ private
+ def derp(message)
+ 'A'
+ end
+ end
+
+ module B
+ private
+ def derp
+ 'B' + super('superclass')
+ end
+ end
+
+ class C
+ include A
+ include B
+
+ public :derp
+ alias_method :meow, :derp
+ end
+ end
+end
diff --git a/spec/ruby/core/method/hash_spec.rb b/spec/ruby/core/method/hash_spec.rb
new file mode 100644
index 0000000000..d6c8440acc
--- /dev/null
+++ b/spec/ruby/core/method/hash_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#hash" do
+ it "returns the same value for user methods that are eql?" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:foo).hash.should == obj.method(:bar).hash
+ end
+
+ # See also redmine #6048
+ it "returns the same value for builtin methods that are eql?" do
+ obj = [42]
+ obj.method(:to_s).hash.should == obj.method(:inspect).hash
+ end
+end
diff --git a/spec/ruby/core/method/inspect_spec.rb b/spec/ruby/core/method/inspect_spec.rb
new file mode 100644
index 0000000000..e0fe1afdd0
--- /dev/null
+++ b/spec/ruby/core/method/inspect_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Method#inspect" do
+ it_behaves_like :method_to_s, :inspect
+end
diff --git a/spec/ruby/core/method/name_spec.rb b/spec/ruby/core/method/name_spec.rb
new file mode 100644
index 0000000000..de390c6f52
--- /dev/null
+++ b/spec/ruby/core/method/name_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#name" do
+ it "returns the name of the method" do
+ "abc".method(:upcase).name.should == :upcase
+ end
+
+ it "returns the name even when aliased" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:foo).name.should == :foo
+ obj.method(:bar).name.should == :bar
+ obj.method(:bar).unbind.bind(obj).name.should == :bar
+ end
+
+ describe "for a Method generated by respond_to_missing?" do
+ it "returns the name passed to respond_to_missing?" do
+ @m = MethodSpecs::Methods.new
+ @m.method(:handled_via_method_missing).name.should == :handled_via_method_missing
+ end
+ end
+end
diff --git a/spec/ruby/core/method/original_name_spec.rb b/spec/ruby/core/method/original_name_spec.rb
new file mode 100644
index 0000000000..676fdaedb4
--- /dev/null
+++ b/spec/ruby/core/method/original_name_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#original_name" do
+ it "returns the name of the method" do
+ "abc".method(:upcase).original_name.should == :upcase
+ end
+
+ it "returns the original name when aliased" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:foo).original_name.should == :foo
+ obj.method(:bar).original_name.should == :foo
+ obj.method(:bar).unbind.bind(obj).original_name.should == :foo
+ end
+
+ it "returns the original name even when aliased twice" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:foo).original_name.should == :foo
+ obj.method(:baz).original_name.should == :foo
+ obj.method(:baz).unbind.bind(obj).original_name.should == :foo
+ end
+end
diff --git a/spec/ruby/core/method/owner_spec.rb b/spec/ruby/core/method/owner_spec.rb
new file mode 100644
index 0000000000..05422f1697
--- /dev/null
+++ b/spec/ruby/core/method/owner_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#owner" do
+ it "returns the owner of the method" do
+ "abc".method(:upcase).owner.should == String
+ end
+
+ it "returns the same owner when aliased in the same classes" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:foo).owner.should == MethodSpecs::Methods
+ obj.method(:bar).owner.should == MethodSpecs::Methods
+ end
+
+ it "returns the class/module it was defined in" do
+ MethodSpecs::C.new.method(:baz).owner.should == MethodSpecs::A
+ MethodSpecs::MySuper.new.method(:bar).owner.should == MethodSpecs::MyMod
+ end
+
+ describe "for a Method generated by respond_to_missing?" do
+ it "returns the owner of the method" do
+ @m = MethodSpecs::Methods.new
+ @m.method(:handled_via_method_missing).owner.should == MethodSpecs::Methods
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "returns the class on which public was called for a private method in ancestor" do
+ MethodSpecs::InheritedMethods::C.new.method(:derp).owner.should == MethodSpecs::InheritedMethods::C
+ end
+ end
+end
diff --git a/spec/ruby/core/method/parameters_spec.rb b/spec/ruby/core/method/parameters_spec.rb
new file mode 100644
index 0000000000..e6d51d1b4d
--- /dev/null
+++ b/spec/ruby/core/method/parameters_spec.rb
@@ -0,0 +1,270 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#parameters" do
+ class MethodSpecs::Methods
+ def one_key(a: 1); end
+ def one_keyrest(**a); end
+
+ def one_keyreq(a:); end
+
+ def one_splat_one_req(*a,b); end
+ def one_splat_two_req(*a,b,c); end
+ def one_splat_one_req_with_block(*a,b,&blk); end
+
+ def one_opt_with_stabby(a=-> b { true }); end
+
+ def one_unnamed_splat(*); end
+
+ def one_splat_one_block(*args, &block)
+ local_is_not_parameter = {}
+ end
+
+ define_method(:one_optional_defined_method) {|x = 1|}
+ end
+
+ it "returns an empty Array when the method expects no arguments" do
+ MethodSpecs::Methods.instance_method(:zero).parameters.should == []
+ end
+
+ it "returns [[:req,:name]] for a method expecting one required argument called 'name'" do
+ MethodSpecs::Methods.instance_method(:one_req).parameters.should == [[:req,:a]]
+ end
+
+ it "returns [[:req,:a],[:req,:b]] for a method expecting two required arguments called 'a' and 'b''" do
+ m = MethodSpecs::Methods.instance_method(:two_req)
+ m.parameters.should == [[:req,:a], [:req,:b]]
+ end
+
+ it "returns [[:block,:blk]] for a method expecting one block argument called 'a'" do
+ m = MethodSpecs::Methods.instance_method(:zero_with_block)
+ m.parameters.should == [[:block,:blk]]
+ end
+
+ it "returns [[:req,:a],[:block,:b] for a method expecting a required argument ('a') and a block argument ('b')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_with_block)
+ m.parameters.should == [[:req,:a], [:block,:blk]]
+ end
+
+ it "returns [[:req,:a],[:req,:b],[:block,:c] for a method expecting two required arguments ('a','b') and a block argument ('c')" do
+ m = MethodSpecs::Methods.instance_method(:two_req_with_block)
+ m.parameters.should == [[:req,:a], [:req,:b], [:block,:blk]]
+ end
+
+ it "returns [[:opt,:a]] for a method expecting one optional argument ('a')" do
+ m = MethodSpecs::Methods.instance_method(:one_opt)
+ m.parameters.should == [[:opt,:a]]
+ end
+
+ it "returns [[:req,:a],[:opt,:b]] for a method expecting one required argument ('a') and one optional argument ('b')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_one_opt)
+ m.parameters.should == [[:req,:a],[:opt,:b]]
+ end
+
+ it "returns [[:req,:a],[:opt,:b]] for a method expecting one required argument ('a') and one optional argument ('b')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_one_opt)
+ m.parameters.should == [[:req,:a],[:opt,:b]]
+ end
+
+ it "returns [[:req,:a],[:opt,:b],[:opt,:c]] for a method expecting one required argument ('a') and two optional arguments ('b','c')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_two_opt)
+ m.parameters.should == [[:req,:a],[:opt,:b],[:opt,:c]]
+ end
+
+ it "returns [[:req,:a],[:req,:b],[:opt,:c]] for a method expecting two required arguments ('a','b') and one optional arguments ('c')" do
+ m = MethodSpecs::Methods.instance_method(:two_req_one_opt)
+ m.parameters.should == [[:req,:a],[:req,:b],[:opt,:c]]
+ end
+
+ it "returns [[:opt,:a],[:block,:b]] for a method expecting one required argument ('a') and one block argument ('b')" do
+ m = MethodSpecs::Methods.instance_method(:one_opt_with_block)
+ m.parameters.should == [[:opt,:a],[:block,:blk]]
+ end
+
+ it "returns [[:req,:a],[:opt,:b],[:block,:c]] for a method expecting one required argument ('a'), one optional argument ('b'), and a block ('c')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_one_opt_with_block)
+ m.parameters.should == [[:req,:a],[:opt,:b],[:block,:blk]]
+ end
+
+ it "returns [[:req,:a],[:opt,:b],[:opt,:c],[:block,:d]] for a method expecting one required argument ('a'), two optional arguments ('b','c'), and a block ('d')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_two_opt_with_block)
+ m.parameters.should == [[:req,:a],[:opt,:b],[:opt,:c],[:block,:blk]]
+ end
+
+ it "returns [[:rest,:a]] for a method expecting a single splat argument ('a')" do
+ m = MethodSpecs::Methods.instance_method(:zero_with_splat)
+ m.parameters.should == [[:rest,:a]]
+ end
+
+ it "returns [[:req,:a],[:rest,:b]] for a method expecting a splat argument ('a') and a required argument ('b')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_with_splat)
+ m.parameters.should == [[:req,:a],[:rest,:b]]
+ end
+
+ it "returns [[:req,:a],[:req,:b],[:rest,:c]] for a method expecting two required arguments ('a','b') and a splat argument ('c')" do
+ m = MethodSpecs::Methods.instance_method(:two_req_with_splat)
+ m.parameters.should == [[:req,:a],[:req,:b],[:rest,:c]]
+ end
+
+ it "returns [[:req,:a],[:opt,:b],[:rest,:c]] for a method expecting a required argument ('a','b'), an optional argument ('b'), and a splat argument ('c')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_one_opt_with_splat)
+ m.parameters.should == [[:req,:a],[:opt,:b],[:rest,:c]]
+ end
+
+ it "returns [[:req,:a],[:req,:b],[:opt,:b],[:rest,:d]] for a method expecting two required arguments ('a','b'), an optional argument ('c'), and a splat argument ('d')" do
+ m = MethodSpecs::Methods.instance_method(:two_req_one_opt_with_splat)
+ m.parameters.should == [[:req,:a],[:req,:b],[:opt,:c],[:rest,:d]]
+ end
+
+ it "returns [[:req,:a],[:opt,:b],[:opt,:c],[:rest,:d]] for a method expecting a required argument ('a'), two optional arguments ('b','c'), and a splat argument ('d')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_two_opt_with_splat)
+ m.parameters.should == [[:req,:a],[:opt,:b],[:opt,:c],[:rest,:d]]
+ end
+
+ it "returns [[:rest,:a],[:block,:b]] for a method expecting a splat argument ('a') and a block argument ('b')" do
+ m = MethodSpecs::Methods.instance_method(:zero_with_splat_and_block)
+ m.parameters.should == [[:rest,:a],[:block,:blk]]
+ end
+
+ it "returns [[:req,:a],[:rest,:b],[:block,:c]] for a method expecting a required argument ('a'), a splat argument ('b'), and a block ('c')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_with_splat_and_block)
+ m.parameters.should == [[:req,:a],[:rest,:b],[:block,:blk]]
+ end
+
+ it "returns [[:req,:a],[:req,:b],[:rest,:c],[:block,:d]] for a method expecting two required arguments ('a','b'), a splat argument ('c'), and a block ('d')" do
+ m = MethodSpecs::Methods.instance_method(:two_req_with_splat_and_block)
+ m.parameters.should == [[:req,:a],[:req,:b],[:rest,:c],[:block,:blk]]
+ end
+
+ it "returns [[:req,:a],[:opt,:b],[:rest,:c],[:block,:d]] for a method expecting a required argument ('a'), a splat argument ('c'), and a block ('d')" do
+ m = MethodSpecs::Methods.instance_method(:one_req_one_opt_with_splat_and_block)
+ m.parameters.should == [[:req,:a],[:opt,:b],[:rest,:c],[:block,:blk]]
+ end
+
+ it "returns [[:req,:a],[:req,:b],[:opt,:c],[:block,:d]] for a method expecting two required arguments ('a','b'), an optional argument ('c'), a splat argument ('d'), and a block ('e')" do
+ m = MethodSpecs::Methods.instance_method(:two_req_one_opt_with_splat_and_block)
+ m.parameters.should == [[:req,:a],[:req,:b],[:opt,:c],[:rest,:d],[:block,:blk]]
+ end
+
+ it "returns [[:rest,:a],[:req,:b]] for a method expecting a splat argument ('a') and a required argument ('b')" do
+ m = MethodSpecs::Methods.instance_method(:one_splat_one_req)
+ m.parameters.should == [[:rest,:a],[:req,:b]]
+ end
+
+ it "returns [[:rest,:a],[:req,:b],[:req,:c]] for a method expecting a splat argument ('a') and two required arguments ('b','c')" do
+ m = MethodSpecs::Methods.instance_method(:one_splat_two_req)
+ m.parameters.should == [[:rest,:a],[:req,:b],[:req,:c]]
+ end
+
+ it "returns [[:rest,:a],[:req,:b],[:block,:c]] for a method expecting a splat argument ('a'), a required argument ('b'), and a block ('c')" do
+ m = MethodSpecs::Methods.instance_method(:one_splat_one_req_with_block)
+ m.parameters.should == [[:rest,:a],[:req,:b],[:block,:blk]]
+ end
+
+ it "returns [[:key,:a]] for a method with a single optional keyword argument" do
+ m = MethodSpecs::Methods.instance_method(:one_key)
+ m.parameters.should == [[:key,:a]]
+ end
+
+ it "returns [[:keyrest,:a]] for a method with a keyword rest argument" do
+ m = MethodSpecs::Methods.instance_method(:one_keyrest)
+ m.parameters.should == [[:keyrest,:a]]
+ end
+
+ it "returns [[:keyreq,:a]] for a method with a single required keyword argument" do
+ m = MethodSpecs::Methods.instance_method(:one_keyreq)
+ m.parameters.should == [[:keyreq,:a]]
+ end
+
+ it "works with ->(){} as the value of an optional argument" do
+ m = MethodSpecs::Methods.instance_method(:one_opt_with_stabby)
+ m.parameters.should == [[:opt,:a]]
+ end
+
+ # define_method variants
+ it "returns [] for a define_method method with explicit no-args || specification" do
+ m = MethodSpecs::Methods.instance_method(:zero_defined_method)
+ m.parameters.should == []
+ end
+
+ it "returns [[:rest, :x]] for a define_method method with rest arg 'x' only" do
+ m = MethodSpecs::Methods.instance_method(:zero_with_splat_defined_method)
+ m.parameters.should == [[:rest, :x]]
+ end
+
+ it "returns [[:req, :x]] for a define_method method expecting one required argument 'x'" do
+ m = MethodSpecs::Methods.instance_method(:one_req_defined_method)
+ m.parameters.should == [[:req, :x]]
+ end
+
+ it "returns [[:req, :x], [:req, :y]] for a define_method method expecting two required arguments 'x' and 'y'" do
+ m = MethodSpecs::Methods.instance_method(:two_req_defined_method)
+ m.parameters.should == [[:req, :x], [:req, :y]]
+ end
+
+ it "returns [] for a define_method method with no args specification" do
+ m = MethodSpecs::Methods.instance_method(:no_args_defined_method)
+ m.parameters.should == []
+ end
+
+ it "returns [[:req]] for a define_method method with a grouping as its only argument" do
+ m = MethodSpecs::Methods.instance_method(:two_grouped_defined_method)
+ m.parameters.should == [[:req]]
+ end
+
+ it "returns [[:opt, :x]] for a define_method method with an optional argument 'x'" do
+ m = MethodSpecs::Methods.instance_method(:one_optional_defined_method)
+ m.parameters.should == [[:opt, :x]]
+ end
+
+ it "returns [[:rest]] for a Method generated by respond_to_missing?" do
+ m = MethodSpecs::Methods.new
+ m.method(:handled_via_method_missing).parameters.should == [[:rest]]
+ end
+
+ ruby_version_is '3.2' do
+ it "adds * rest arg for \"star\" argument" do
+ m = MethodSpecs::Methods.new
+ m.method(:one_unnamed_splat).parameters.should == [[:rest, :*]]
+ end
+ end
+
+ ruby_version_is ''...'3.2' do
+ it "adds nameless rest arg for \"star\" argument" do
+ m = MethodSpecs::Methods.new
+ m.method(:one_unnamed_splat).parameters.should == [[:rest]]
+ end
+ end
+
+ it "returns the args and block for a splat and block argument" do
+ m = MethodSpecs::Methods.new
+ m.method(:one_splat_one_block).parameters.should == [[:rest, :args], [:block, :block]]
+ end
+
+ it "returns [] for a Method generated by attr_reader" do
+ m = MethodSpecs::Methods.new
+ m.method(:reader).parameters.should == []
+ end
+
+ it "return [[:req]] for a Method generated by attr_writer" do
+ m = MethodSpecs::Methods.new
+ m.method(:writer=).parameters.should == [[:req]]
+ end
+
+ it "returns [[:rest]] for core methods with variable-length argument lists" do
+ # delete! takes rest args
+ "foo".method(:delete!).parameters.should == [[:rest]]
+ end
+
+ it "returns [[:rest]] or [[:opt]] for core methods with optional arguments" do
+ # pop takes 1 optional argument
+ [
+ [[:rest]],
+ [[:opt]]
+ ].should include([].method(:pop).parameters)
+ end
+
+ it "returns [[:req]] for each parameter for core methods with fixed-length argument lists" do
+ "foo".method(:+).parameters.should == [[:req]]
+ end
+end
diff --git a/spec/ruby/core/method/private_spec.rb b/spec/ruby/core/method/private_spec.rb
new file mode 100644
index 0000000000..230a4e9e81
--- /dev/null
+++ b/spec/ruby/core/method/private_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "Method#private?" do
+ it "returns false when the method is public" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_public_method).private?.should == false
+ end
+
+ it "returns false when the method is protected" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_protected_method).private?.should == false
+ end
+
+ it "returns true when the method is private" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_private_method).private?.should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/method/protected_spec.rb b/spec/ruby/core/method/protected_spec.rb
new file mode 100644
index 0000000000..6ee85f7738
--- /dev/null
+++ b/spec/ruby/core/method/protected_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "Method#protected?" do
+ it "returns false when the method is public" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_public_method).protected?.should == false
+ end
+
+ it "returns true when the method is protected" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_protected_method).protected?.should == true
+ end
+
+ it "returns false when the method is private" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_private_method).protected?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/method/public_spec.rb b/spec/ruby/core/method/public_spec.rb
new file mode 100644
index 0000000000..3988468551
--- /dev/null
+++ b/spec/ruby/core/method/public_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "Method#public?" do
+ it "returns true when the method is public" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_public_method).public?.should == true
+ end
+
+ it "returns false when the method is protected" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_protected_method).public?.should == false
+ end
+
+ it "returns false when the method is private" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:my_private_method).public?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/method/receiver_spec.rb b/spec/ruby/core/method/receiver_spec.rb
new file mode 100644
index 0000000000..2b2e11dd2e
--- /dev/null
+++ b/spec/ruby/core/method/receiver_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#receiver" do
+ it "returns the receiver of the method" do
+ s = "abc"
+ s.method(:upcase).receiver.should equal(s)
+ end
+
+ it "returns the right receiver even when aliased" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:foo).receiver.should equal(obj)
+ obj.method(:bar).receiver.should equal(obj)
+ end
+
+ describe "for a Method generated by respond_to_missing?" do
+ it "returns the receiver of the method" do
+ m = MethodSpecs::Methods.new
+ m.method(:handled_via_method_missing).receiver.should equal(m)
+ end
+ end
+end
diff --git a/spec/ruby/core/method/shared/call.rb b/spec/ruby/core/method/shared/call.rb
new file mode 100644
index 0000000000..f26e373695
--- /dev/null
+++ b/spec/ruby/core/method/shared/call.rb
@@ -0,0 +1,51 @@
+describe :method_call, shared: true do
+ it "invokes the method with the specified arguments, returning the method's return value" do
+ m = 12.method("+")
+ m.send(@method, 3).should == 15
+ m.send(@method, 20).should == 32
+
+ m = MethodSpecs::Methods.new.method(:attr=)
+ m.send(@method, 42).should == 42
+ end
+
+ it "raises an ArgumentError when given incorrect number of arguments" do
+ -> {
+ MethodSpecs::Methods.new.method(:two_req).send(@method, 1, 2, 3)
+ }.should raise_error(ArgumentError)
+ -> {
+ MethodSpecs::Methods.new.method(:two_req).send(@method, 1)
+ }.should raise_error(ArgumentError)
+ end
+
+ describe "for a Method generated by respond_to_missing?" do
+ it "invokes method_missing with the specified arguments and returns the result" do
+ @m = MethodSpecs::Methods.new
+ meth = @m.method(:handled_via_method_missing)
+ meth.send(@method, :argument).should == [:argument]
+ end
+
+ it "invokes method_missing with the method name and the specified arguments" do
+ @m = MethodSpecs::Methods.new
+ meth = @m.method(:handled_via_method_missing)
+
+ @m.should_receive(:method_missing).with(:handled_via_method_missing, :argument)
+ meth.send(@method, :argument)
+ end
+
+ it "invokes method_missing dynamically" do
+ @m = MethodSpecs::Methods.new
+ meth = @m.method(:handled_via_method_missing)
+
+ def @m.method_missing(*); :changed; end
+ meth.send(@method, :argument).should == :changed
+ end
+
+ it "does not call the original method name even if it now exists" do
+ @m = MethodSpecs::Methods.new
+ meth = @m.method(:handled_via_method_missing)
+
+ def @m.handled_via_method_missing(*); :not_called; end
+ meth.send(@method, :argument).should == [:argument]
+ end
+ end
+end
diff --git a/spec/ruby/core/method/shared/eql.rb b/spec/ruby/core/method/shared/eql.rb
new file mode 100644
index 0000000000..5c720cbac1
--- /dev/null
+++ b/spec/ruby/core/method/shared/eql.rb
@@ -0,0 +1,94 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :method_equal, shared: true do
+ before :each do
+ @m = MethodSpecs::Methods.new
+ @m_foo = @m.method(:foo)
+ @m2 = MethodSpecs::Methods.new
+ @a = MethodSpecs::A.new
+ end
+
+ it "returns true if methods are the same" do
+ m2 = @m.method(:foo)
+
+ @m_foo.send(@method, @m_foo).should be_true
+ @m_foo.send(@method, m2).should be_true
+ end
+
+ it "returns true on aliased methods" do
+ m_bar = @m.method(:bar)
+
+ m_bar.send(@method, @m_foo).should be_true
+ end
+
+ it "returns true if the two core methods are aliases" do
+ s = "hello"
+ a = s.method(:size)
+ b = s.method(:length)
+ a.send(@method, b).should be_true
+ end
+
+ it "returns false on a method which is neither aliased nor the same method" do
+ m2 = @m.method(:zero)
+
+ @m_foo.send(@method, m2).should be_false
+ end
+
+ it "returns false for a method which is not bound to the same object" do
+ m2_foo = @m2.method(:foo)
+ a_baz = @a.method(:baz)
+
+ @m_foo.send(@method, m2_foo).should be_false
+ @m_foo.send(@method, a_baz).should be_false
+ end
+
+ it "returns false if the two methods are bound to the same object but were defined independently" do
+ m2 = @m.method(:same_as_foo)
+ @m_foo.send(@method, m2).should be_false
+ end
+
+ it "returns true if a method was defined using the other one" do
+ MethodSpecs::Methods.send :define_method, :defined_foo, MethodSpecs::Methods.instance_method(:foo)
+ m2 = @m.method(:defined_foo)
+ @m_foo.send(@method, m2).should be_true
+ end
+
+ it "returns false if comparing a method defined via define_method and def" do
+ defn = @m.method(:zero)
+ defined = @m.method(:zero_defined_method)
+
+ defn.send(@method, defined).should be_false
+ defined.send(@method, defn).should be_false
+ end
+
+ describe 'missing methods' do
+ it "returns true for the same method missing" do
+ miss1 = @m.method(:handled_via_method_missing)
+ miss1bis = @m.method(:handled_via_method_missing)
+ miss2 = @m.method(:also_handled)
+
+ miss1.send(@method, miss1bis).should be_true
+ miss1.send(@method, miss2).should be_false
+ end
+
+ it 'calls respond_to_missing? with true to include private methods' do
+ @m.should_receive(:respond_to_missing?).with(:some_missing_method, true).and_return(true)
+ @m.method(:some_missing_method)
+ end
+ end
+
+ it "returns false if the two methods are bound to different objects, have the same names, and identical bodies" do
+ a = MethodSpecs::Eql.instance_method(:same_body)
+ b = MethodSpecs::Eql2.instance_method(:same_body)
+ a.send(@method, b).should be_false
+ end
+
+ it "returns false if the argument is not a Method object" do
+ String.instance_method(:size).send(@method, 7).should be_false
+ end
+
+ it "returns false if the argument is an unbound version of self" do
+ method(:load).send(@method, method(:load).unbind).should be_false
+ end
+end
diff --git a/spec/ruby/core/method/shared/to_s.rb b/spec/ruby/core/method/shared/to_s.rb
new file mode 100644
index 0000000000..6fdeaaf99c
--- /dev/null
+++ b/spec/ruby/core/method/shared/to_s.rb
@@ -0,0 +1,85 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :method_to_s, shared: true do
+ before :each do
+ @m = MethodSpecs::MySub.new.method :bar
+ @string = @m.send(@method)
+ end
+
+ it "returns a String" do
+ @m.send(@method).should be_kind_of(String)
+ end
+
+ it "returns a String for methods defined with attr_accessor" do
+ m = MethodSpecs::Methods.new.method :attr
+ m.send(@method).should be_kind_of(String)
+ end
+
+ it "returns a String containing 'Method'" do
+ @string.should =~ /\bMethod\b/
+ end
+
+ it "returns a String containing the method name" do
+ @string.should =~ /\#bar/
+ end
+
+ it "returns a String containing method arguments" do
+ obj = MethodSpecs::Methods.new
+ obj.method(:zero).send(@method).should.include?("()")
+ obj.method(:one_req).send(@method).should.include?("(a)")
+ obj.method(:one_req_named).send(@method).should.include?("(a:)")
+ obj.method(:zero_with_block).send(@method).should.include?("(&blk)")
+ obj.method(:one_opt).send(@method).should.include?("(a=...)")
+ obj.method(:one_opt_named).send(@method).should.include?("(a: ...)")
+ obj.method(:zero_with_splat).send(@method).should.include?("(*a)")
+ obj.method(:zero_with_double_splat).send(@method).should.include?("(**a)")
+ obj.method(:one_req_one_opt_with_splat_and_block).send(@method).should.include?("(a, b=..., *c, &blk)")
+ end
+
+ it "returns a String containing the Module the method is defined in" do
+ @string.should =~ /MethodSpecs::MyMod/
+ end
+
+ it "returns a String containing the Module the method is referenced from" do
+ @string.should =~ /MethodSpecs::MySub/
+ end
+
+ it "returns a String including all details" do
+ @string.should.start_with? "#<Method: MethodSpecs::MySub(MethodSpecs::MyMod)#bar"
+ end
+
+ it "does not show the defining module if it is the same as the receiver class" do
+ MethodSpecs::A.new.method(:baz).send(@method).should.start_with? "#<Method: MethodSpecs::A#baz"
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a String containing the Module containing the method if object has a singleton class but method is not defined in the singleton class" do
+ obj = MethodSpecs::MySub.new
+ obj.singleton_class
+ @m = obj.method(:bar)
+ @string = @m.send(@method)
+ @string.should.start_with? "#<Method: MethodSpecs::MySub(MethodSpecs::MyMod)#bar"
+
+ c = MethodSpecs::MySub.dup
+ m = Module.new{def bar; end}
+ c.extend(m)
+ @string = c.method(:bar).send(@method)
+ @string.should.start_with? "#<Method: #<Class:#{c.inspect}>(#{m.inspect})#bar"
+ end
+ end
+
+ it "returns a String containing the singleton class if method is defined in the singleton class" do
+ obj = MethodSpecs::MySub.new
+ def obj.bar; end
+ @m = obj.method(:bar)
+ @string = @m.send(@method).sub(/0x\h+/, '0xXXXXXX')
+ @string.should.start_with? "#<Method: #<MethodSpecs::MySub:0xXXXXXX>.bar"
+ end
+
+ ruby_bug '#17428', ''...'3.0' do
+ it "shows the metaclass and the owner for a Module instance method retrieved from a class" do
+ String.method(:include).inspect.should.start_with?("#<Method: #<Class:String>(Module)#include")
+ end
+ end
+end
diff --git a/spec/ruby/core/method/source_location_spec.rb b/spec/ruby/core/method/source_location_spec.rb
new file mode 100644
index 0000000000..1f476aaa9b
--- /dev/null
+++ b/spec/ruby/core/method/source_location_spec.rb
@@ -0,0 +1,113 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#source_location" do
+ before :each do
+ @method = MethodSpecs::SourceLocation.method(:location)
+ end
+
+ it "returns an Array" do
+ @method.source_location.should be_an_instance_of(Array)
+ end
+
+ it "sets the first value to the path of the file in which the method was defined" do
+ file = @method.source_location.first
+ file.should be_an_instance_of(String)
+ file.should == File.realpath('../fixtures/classes.rb', __FILE__)
+ end
+
+ it "sets the last value to an Integer representing the line on which the method was defined" do
+ line = @method.source_location.last
+ line.should be_an_instance_of(Integer)
+ line.should == 5
+ end
+
+ it "returns the last place the method was defined" do
+ MethodSpecs::SourceLocation.method(:redefined).source_location.last.should == 13
+ end
+
+ it "returns the location of the original method even if it was aliased" do
+ MethodSpecs::SourceLocation.new.method(:aka).source_location.last.should == 17
+ end
+
+ it "works for methods defined with a block" do
+ line = nil
+ klass = Class.new do
+ line = __LINE__ + 1
+ define_method(:f) { }
+ end
+
+ method = klass.new.method(:f)
+ method.source_location[0].should =~ /#{__FILE__}/
+ method.source_location[1].should == line
+ end
+
+ it "works for methods defined with a Method" do
+ line = nil
+ klass = Class.new do
+ line = __LINE__ + 1
+ def f
+ end
+ define_method :g, new.method(:f)
+ end
+
+ method = klass.new.method(:g)
+ method.source_location[0].should =~ /#{__FILE__}/
+ method.source_location[1].should == line
+ end
+
+ it "works for methods defined with an UnboundMethod" do
+ line = nil
+ klass = Class.new do
+ line = __LINE__ + 1
+ def f
+ end
+ define_method :g, instance_method(:f)
+ end
+
+ method = klass.new.method(:g)
+ method.source_location[0].should =~ /#{__FILE__}/
+ method.source_location[1].should == line
+ end
+
+ it "works for methods whose visibility has been overridden in a subclass" do
+ line = nil
+ superclass = Class.new do
+ line = __LINE__ + 1
+ def f
+ end
+ end
+ subclass = Class.new(superclass) do
+ private :f
+ end
+
+ method = subclass.new.method(:f)
+ method.source_location[0].should =~ /#{__FILE__}/
+ method.source_location[1].should == line
+ end
+
+ it "works for core methods where it returns nil or <internal:" do
+ loc = method(:__id__).source_location
+ if loc == nil
+ loc.should == nil
+ else
+ loc[0].should.start_with?('<internal:')
+ loc[1].should be_kind_of(Integer)
+ end
+
+ loc = method(:tap).source_location
+ if loc == nil
+ loc.should == nil
+ else
+ loc[0].should.start_with?('<internal:')
+ loc[1].should be_kind_of(Integer)
+ end
+ end
+
+ describe "for a Method generated by respond_to_missing?" do
+ it "returns nil" do
+ m = MethodSpecs::Methods.new
+ m.method(:handled_via_method_missing).source_location.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/method/super_method_spec.rb b/spec/ruby/core/method/super_method_spec.rb
new file mode 100644
index 0000000000..f9a18f3878
--- /dev/null
+++ b/spec/ruby/core/method/super_method_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#super_method" do
+ it "returns the method that would be called by super in the method" do
+ obj = MethodSpecs::C.new
+ obj.extend MethodSpecs::OverrideAgain
+ meth = obj.method(:overridden)
+
+ s_meth = meth.super_method
+ s_meth.owner.should == MethodSpecs::C
+ s_meth.receiver.should == obj
+ s_meth.name.should == :overridden
+
+ ss_meth = meth.super_method.super_method
+ ss_meth.owner.should == MethodSpecs::BetweenBAndC
+ ss_meth.receiver.should == obj
+ ss_meth.name.should == :overridden
+
+ sss_meth = meth.super_method.super_method.super_method
+ sss_meth.owner.should == MethodSpecs::B
+ sss_meth.receiver.should == obj
+ sss_meth.name.should == :overridden
+ end
+
+ it "returns nil when there's no super method in the parent" do
+ method = Object.new.method(:method)
+ method.super_method.should == nil
+ end
+
+ it "returns nil when the parent's method is removed" do
+ klass = Class.new do
+ def overridden; end
+ end
+ sub = Class.new(klass) do
+ def overridden; end
+ end
+ object = sub.new
+ method = object.method(:overridden)
+
+ klass.class_eval { undef :overridden }
+
+ method.super_method.should == nil
+ end
+
+ # https://github.com/jruby/jruby/issues/7240
+ context "after changing an inherited methods visibility" do
+ it "calls the proper super method" do
+ MethodSpecs::InheritedMethods::C.new.derp.should == 'BA'
+ end
+
+ it "returns the expected super_method" do
+ method = MethodSpecs::InheritedMethods::C.new.method(:derp)
+ method.super_method.owner.should == MethodSpecs::InheritedMethods::A
+ end
+ end
+
+ ruby_version_is "2.7.3" do
+ context "after aliasing an inherited method" do
+ it "returns the expected super_method" do
+ method = MethodSpecs::InheritedMethods::C.new.method(:meow)
+ method.super_method.owner.should == MethodSpecs::InheritedMethods::A
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/method/to_proc_spec.rb b/spec/ruby/core/method/to_proc_spec.rb
new file mode 100644
index 0000000000..29b7bec2b3
--- /dev/null
+++ b/spec/ruby/core/method/to_proc_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#to_proc" do
+ before :each do
+ ScratchPad.record []
+
+ @m = MethodSpecs::Methods.new
+ @meth = @m.method(:foo)
+ end
+
+ it "returns a Proc object corresponding to the method" do
+ @meth.to_proc.kind_of?(Proc).should == true
+ end
+
+ it "returns a Proc which does not depends on the value of self" do
+ 3.instance_exec(4, &5.method(:+)).should == 9
+ end
+
+
+ it "returns a Proc object with the correct arity" do
+ # This may seem redundant but this bug has cropped up in jruby, mri and yarv.
+ # http://jira.codehaus.org/browse/JRUBY-124
+ [ :zero, :one_req, :two_req,
+ :zero_with_block, :one_req_with_block, :two_req_with_block,
+ :one_opt, :one_req_one_opt, :one_req_two_opt, :two_req_one_opt,
+ :one_opt_with_block, :one_req_one_opt_with_block, :one_req_two_opt_with_block, :two_req_one_opt_with_block,
+ :zero_with_splat, :one_req_with_splat, :two_req_with_splat,
+ :one_req_one_opt_with_splat, :one_req_two_opt_with_splat, :two_req_one_opt_with_splat,
+ :zero_with_splat_and_block, :one_req_with_splat_and_block, :two_req_with_splat_and_block,
+ :one_req_one_opt_with_splat_and_block, :one_req_two_opt_with_splat_and_block, :two_req_one_opt_with_splat_and_block
+ ].each do |m|
+ @m.method(m).to_proc.arity.should == @m.method(m).arity
+ end
+ end
+
+ it "returns a proc that can be used by define_method" do
+ x = 'test'
+ to_s = class << x
+ define_method :foo, method(:to_s).to_proc
+ to_s
+ end
+
+ x.foo.should == to_s
+ end
+
+ it "returns a proc that can be yielded to" do
+ x = Object.new
+ def x.foo(*a); a; end
+ def x.bar; yield; end
+ def x.baz(*a); yield(*a); end
+
+ m = x.method :foo
+ x.bar(&m).should == []
+ x.baz(1,2,3,&m).should == [1,2,3]
+ end
+
+ it "returns a proc whose binding has the same receiver as the method" do
+ @meth.receiver.should == @meth.to_proc.binding.receiver
+ end
+
+ # #5926
+ it "returns a proc that can receive a block" do
+ x = Object.new
+ def x.foo; yield 'bar'; end
+
+ m = x.method :foo
+ result = nil
+ m.to_proc.call {|val| result = val}
+ result.should == 'bar'
+ end
+
+ it "can be called directly and not unwrap arguments like a block" do
+ obj = MethodSpecs::ToProcBeta.new
+ obj.to_proc.call([1]).should == [1]
+ end
+
+ it "should correct handle arguments (unwrap)" do
+ obj = MethodSpecs::ToProcBeta.new
+
+ array = [[1]]
+ array.each(&obj)
+ ScratchPad.recorded.should == [[1]]
+ end
+
+ it "executes method with whole array (one argument)" do
+ obj = MethodSpecs::ToProcBeta.new
+
+ array = [[1, 2]]
+ array.each(&obj)
+ ScratchPad.recorded.should == [[1, 2]]
+ end
+
+ it "returns a proc that properly invokes module methods with super" do
+ m1 = Module.new { def foo(ary); ary << :m1; end; }
+ m2 = Module.new { def foo(ary = []); super(ary); ary << :m2; end; }
+ c2 = Class.new do
+ include m1
+ include m2
+ end
+
+ c2.new.method(:foo).to_proc.call.should == %i[m1 m2]
+ end
+end
diff --git a/spec/ruby/core/method/to_s_spec.rb b/spec/ruby/core/method/to_s_spec.rb
new file mode 100644
index 0000000000..9f19011302
--- /dev/null
+++ b/spec/ruby/core/method/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Method#to_s" do
+ it_behaves_like :method_to_s, :to_s
+end
diff --git a/spec/ruby/core/method/unbind_spec.rb b/spec/ruby/core/method/unbind_spec.rb
new file mode 100644
index 0000000000..bdedd513ce
--- /dev/null
+++ b/spec/ruby/core/method/unbind_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Method#unbind" do
+ before :each do
+ @normal = MethodSpecs::Methods.new
+ @normal_m = @normal.method :foo
+ @normal_um = @normal_m.unbind
+ @pop_um = MethodSpecs::MySub.new.method(:bar).unbind
+ @string = @pop_um.inspect.sub(/0x\w+/, '0xXXXXXX')
+ end
+
+ it "returns an UnboundMethod" do
+ @normal_um.should be_kind_of(UnboundMethod)
+ end
+
+ describe "#inspect" do
+ it "returns a String containing 'UnboundMethod'" do
+ @string.should =~ /\bUnboundMethod\b/
+ end
+
+ it "returns a String containing the method name" do
+ @string.should =~ /\#bar/
+ end
+
+ it "returns a String containing the Module the method is defined in" do
+ @string.should =~ /MethodSpecs::MyMod/
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "returns a String containing the Module the method is referenced from" do
+ @string.should =~ /MethodSpecs::MySub/
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "returns a String containing the Module the method is referenced from" do
+ @string.should =~ /MethodSpecs::MyMod/
+ end
+ end
+ end
+
+ it "keeps the origin singleton class if there is one" do
+ obj = Object.new
+ def obj.foo
+ end
+ obj.method(:foo).unbind.inspect.should.start_with?("#<UnboundMethod: #{obj.singleton_class}#foo")
+ end
+
+ specify "rebinding UnboundMethod to Method's obj produces exactly equivalent Methods" do
+ @normal_um.bind(@normal).should == @normal_m
+ @normal_m.should == @normal_um.bind(@normal)
+ end
+end
diff --git a/spec/ruby/core/module/alias_method_spec.rb b/spec/ruby/core/module/alias_method_spec.rb
new file mode 100644
index 0000000000..5d3d0c23d9
--- /dev/null
+++ b/spec/ruby/core/module/alias_method_spec.rb
@@ -0,0 +1,173 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#alias_method" do
+ before :each do
+ @class = Class.new(ModuleSpecs::Aliasing)
+ @object = @class.new
+ end
+
+ it "makes a copy of the method" do
+ @class.make_alias :uno, :public_one
+ @class.make_alias :double, :public_two
+ @object.uno.should == @object.public_one
+ @object.double(12).should == @object.public_two(12)
+ end
+
+ it "creates methods that are == to each other" do
+ @class.make_alias :uno, :public_one
+ @object.method(:uno).should == @object.method(:public_one)
+ end
+
+ it "preserves the arguments information of the original methods" do
+ @class.make_alias :uno, :public_one
+ @class.make_alias :double, :public_two
+ @class.instance_method(:uno).parameters.should == @class.instance_method(:public_one).parameters
+ @class.instance_method(:double).parameters.should == @class.instance_method(:public_two).parameters
+ end
+
+ it "retains method visibility" do
+ @class.make_alias :private_ichi, :private_one
+ -> { @object.private_one }.should raise_error(NameError)
+ -> { @object.private_ichi }.should raise_error(NameError)
+ @class.make_alias :public_ichi, :public_one
+ @object.public_ichi.should == @object.public_one
+ @class.make_alias :protected_ichi, :protected_one
+ -> { @object.protected_ichi }.should raise_error(NameError)
+ end
+
+ it "handles aliasing a stub that changes visibility" do
+ @class.__send__ :public, :private_one
+ @class.make_alias :was_private_one, :private_one
+ @object.was_private_one.should == 1
+ end
+
+ it "handles aliasing a method only present in a refinement" do
+ c = @class
+ Module.new do
+ refine c do
+ def uno_refined_method
+ end
+ alias_method :double_refined_method, :uno_refined_method
+ instance_method(:uno_refined_method).should == instance_method(:double_refined_method)
+ end
+ end
+ end
+
+ it "fails if origin method not found" do
+ -> { @class.make_alias :ni, :san }.should raise_error(NameError) { |e|
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+
+ it "raises FrozenError if frozen" do
+ @class.freeze
+ -> { @class.make_alias :uno, :public_one }.should raise_error(FrozenError)
+ end
+
+ it "converts the names using #to_str" do
+ @class.make_alias "un", "public_one"
+ @class.make_alias :deux, "public_one"
+ @class.make_alias "trois", :public_one
+ @class.make_alias :quatre, :public_one
+ name = mock('cinq')
+ name.should_receive(:to_str).any_number_of_times.and_return("cinq")
+ @class.make_alias name, "public_one"
+ @class.make_alias "cinq", name
+ end
+
+ it "raises a TypeError when the given name can't be converted using to_str" do
+ -> { @class.make_alias mock('x'), :public_one }.should raise_error(TypeError)
+ end
+
+ it "is a public method" do
+ Module.should have_public_instance_method(:alias_method, false)
+ end
+
+ describe "returned value" do
+ ruby_version_is ""..."3.0" do
+ it "returns self" do
+ @class.send(:alias_method, :checking_return_value, :public_one).should equal(@class)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "returns symbol of the defined method name" do
+ @class.send(:alias_method, :checking_return_value, :public_one).should equal(:checking_return_value)
+ @class.send(:alias_method, 'checking_return_value', :public_one).should equal(:checking_return_value)
+ end
+ end
+ end
+
+ it "works in module" do
+ ModuleSpecs::Allonym.new.publish.should == :report
+ end
+
+ it "works on private module methods in a module that has been reopened" do
+ ModuleSpecs::ReopeningModule.foo.should == true
+ -> { ModuleSpecs::ReopeningModule.foo2 }.should_not raise_error(NoMethodError)
+ end
+
+ it "accesses a method defined on Object from Kernel" do
+ Kernel.should_not have_public_instance_method(:module_specs_public_method_on_object)
+
+ Kernel.should have_public_instance_method(:module_specs_alias_on_kernel)
+ Object.should have_public_instance_method(:module_specs_alias_on_kernel)
+ end
+
+ it "can call a method with super aliased twice" do
+ ModuleSpecs::AliasingSuper::Target.new.super_call(1).should == 1
+ end
+
+ it "preserves original super call after alias redefine" do
+ ModuleSpecs::AliasingSuper::RedefineAfterAlias.new.alias_super_call(1).should == 1
+ end
+
+ describe "aliasing special methods" do
+ before :all do
+ @class = ModuleSpecs::Aliasing
+ @subclass = ModuleSpecs::AliasingSubclass
+ end
+
+ it "keeps initialize private when aliasing" do
+ @class.make_alias(:initialize, :public_one)
+ @class.private_instance_methods.include?(:initialize).should be_true
+
+ @subclass.make_alias(:initialize, :public_one)
+ @subclass.private_instance_methods.include?(:initialize).should be_true
+ end
+
+ it "keeps initialize_copy private when aliasing" do
+ @class.make_alias(:initialize_copy, :public_one)
+ @class.private_instance_methods.include?(:initialize_copy).should be_true
+
+ @subclass.make_alias(:initialize_copy, :public_one)
+ @subclass.private_instance_methods.include?(:initialize_copy).should be_true
+ end
+
+ it "keeps initialize_clone private when aliasing" do
+ @class.make_alias(:initialize_clone, :public_one)
+ @class.private_instance_methods.include?(:initialize_clone).should be_true
+
+ @subclass.make_alias(:initialize_clone, :public_one)
+ @subclass.private_instance_methods.include?(:initialize_clone).should be_true
+ end
+
+ it "keeps initialize_dup private when aliasing" do
+ @class.make_alias(:initialize_dup, :public_one)
+ @class.private_instance_methods.include?(:initialize_dup).should be_true
+
+ @subclass.make_alias(:initialize_dup, :public_one)
+ @subclass.private_instance_methods.include?(:initialize_dup).should be_true
+ end
+
+ it "keeps respond_to_missing? private when aliasing" do
+ @class.make_alias(:respond_to_missing?, :public_one)
+ @class.private_instance_methods.include?(:respond_to_missing?).should be_true
+
+ @subclass.make_alias(:respond_to_missing?, :public_one)
+ @subclass.private_instance_methods.include?(:respond_to_missing?).should be_true
+ end
+ end
+end
diff --git a/spec/ruby/core/module/ancestors_spec.rb b/spec/ruby/core/module/ancestors_spec.rb
new file mode 100644
index 0000000000..5e4c196206
--- /dev/null
+++ b/spec/ruby/core/module/ancestors_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#ancestors" do
+ it "returns a list of modules included in self (including self)" do
+ BasicObject.ancestors.should == [BasicObject]
+ ModuleSpecs.ancestors.should == [ModuleSpecs]
+ ModuleSpecs::Basic.ancestors.should == [ModuleSpecs::Basic]
+ ModuleSpecs::Super.ancestors.should == [ModuleSpecs::Super, ModuleSpecs::Basic]
+ ModuleSpecs.without_test_modules(ModuleSpecs::Parent.ancestors).should ==
+ [ModuleSpecs::Parent, Object, Kernel, BasicObject]
+ ModuleSpecs.without_test_modules(ModuleSpecs::Child.ancestors).should ==
+ [ModuleSpecs::Child, ModuleSpecs::Super, ModuleSpecs::Basic, ModuleSpecs::Parent, Object, Kernel, BasicObject]
+ end
+
+ it "returns only modules and classes" do
+ class << ModuleSpecs::Child; self; end.ancestors.should include(ModuleSpecs::Internal, Class, Module, Object, Kernel)
+ end
+
+ it "has 1 entry per module or class" do
+ ModuleSpecs::Parent.ancestors.should == ModuleSpecs::Parent.ancestors.uniq
+ end
+
+ describe "when called on a singleton class" do
+ it "includes the singleton classes of ancestors" do
+ parent = Class.new
+ child = Class.new(parent)
+ schild = child.singleton_class
+
+ schild.ancestors.should include(schild,
+ parent.singleton_class,
+ Object.singleton_class,
+ BasicObject.singleton_class,
+ Class,
+ Module,
+ Object,
+ Kernel,
+ BasicObject)
+
+ end
+
+ describe 'for a standalone module' do
+ it 'does not include Class' do
+ s_mod = ModuleSpecs.singleton_class
+ s_mod.ancestors.should_not include(Class)
+ end
+
+ it 'does not include other singleton classes' do
+ s_standalone_mod = ModuleSpecs.singleton_class
+ s_module = Module.singleton_class
+ s_object = Object.singleton_class
+ s_basic_object = BasicObject.singleton_class
+
+ s_standalone_mod.ancestors.should_not include(s_module, s_object, s_basic_object)
+ end
+
+ it 'includes its own singleton class' do
+ s_mod = ModuleSpecs.singleton_class
+
+ s_mod.ancestors.should include(s_mod)
+ end
+
+ it 'includes standard chain' do
+ s_mod = ModuleSpecs.singleton_class
+
+ s_mod.ancestors.should include(Module, Object, Kernel, BasicObject)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/module/append_features_spec.rb b/spec/ruby/core/module/append_features_spec.rb
new file mode 100644
index 0000000000..1724cde5d6
--- /dev/null
+++ b/spec/ruby/core/module/append_features_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#append_features" do
+ it "is a private method" do
+ Module.should have_private_instance_method(:append_features)
+ end
+
+ describe "on Class" do
+ it "is undefined" do
+ Class.should_not have_private_instance_method(:append_features, true)
+ end
+
+ it "raises a TypeError if calling after rebinded to Class" do
+ -> {
+ Module.instance_method(:append_features).bind(Class.new).call Module.new
+ }.should raise_error(TypeError)
+ end
+ end
+
+ it "gets called when self is included in another module/class" do
+ begin
+ m = Module.new do
+ def self.append_features(mod)
+ $appended_to = mod
+ end
+ end
+
+ c = Class.new do
+ include m
+ end
+
+ $appended_to.should == c
+ ensure
+ $appended_to = nil
+ end
+ end
+
+ it "raises an ArgumentError on a cyclic include" do
+ -> {
+ ModuleSpecs::CyclicAppendA.send(:append_features, ModuleSpecs::CyclicAppendA)
+ }.should raise_error(ArgumentError)
+
+ -> {
+ ModuleSpecs::CyclicAppendB.send(:append_features, ModuleSpecs::CyclicAppendA)
+ }.should raise_error(ArgumentError)
+
+ end
+
+ describe "when other is frozen" do
+ before :each do
+ @receiver = Module.new
+ @other = Module.new.freeze
+ end
+
+ it "raises a FrozenError before appending self" do
+ -> { @receiver.send(:append_features, @other) }.should raise_error(FrozenError)
+ @other.ancestors.should_not include(@receiver)
+ end
+ end
+end
diff --git a/spec/ruby/core/module/attr_accessor_spec.rb b/spec/ruby/core/module/attr_accessor_spec.rb
new file mode 100644
index 0000000000..ba5289cbea
--- /dev/null
+++ b/spec/ruby/core/module/attr_accessor_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#attr_accessor" do
+ it "creates a getter and setter for each given attribute name" do
+ c = Class.new do
+ attr_accessor :a, "b"
+ end
+
+ o = c.new
+
+ ['a','b'].each do |x|
+ o.respond_to?(x).should == true
+ o.respond_to?("#{x}=").should == true
+ end
+
+ o.a = "a"
+ o.a.should == "a"
+
+ o.b = "b"
+ o.b.should == "b"
+ o.a = o.b = nil
+
+ o.send(:a=,"a")
+ o.send(:a).should == "a"
+
+ o.send(:b=, "b")
+ o.send(:b).should == "b"
+ end
+
+ it "not allows creating an attr_accessor on an immediate class" do
+ class TrueClass
+ attr_accessor :spec_attr_accessor
+ end
+
+ -> { true.spec_attr_accessor = "a" }.should raise_error(FrozenError)
+ end
+
+ it "raises FrozenError if the receiver if frozen" do
+ c = Class.new do
+ attr_accessor :foo
+ end
+ obj = c.new
+ obj.foo = 1
+ obj.foo.should == 1
+
+ obj.freeze
+ -> { obj.foo = 42 }.should raise_error(FrozenError)
+ obj.foo.should == 1
+ end
+
+ it "converts non string/symbol names to strings using to_str" do
+ (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test")
+ c = Class.new do
+ attr_accessor o
+ end
+
+ c.new.respond_to?("test").should == true
+ c.new.respond_to?("test=").should == true
+ end
+
+ it "raises a TypeError when the given names can't be converted to strings using to_str" do
+ o = mock('o')
+ -> { Class.new { attr_accessor o } }.should raise_error(TypeError)
+ (o = mock('123')).should_receive(:to_str).and_return(123)
+ -> { Class.new { attr_accessor o } }.should raise_error(TypeError)
+ end
+
+ it "applies current visibility to methods created" do
+ c = Class.new do
+ protected
+ attr_accessor :foo
+ end
+
+ -> { c.new.foo }.should raise_error(NoMethodError)
+ -> { c.new.foo=1 }.should raise_error(NoMethodError)
+ end
+
+ it "is a public method" do
+ Module.should have_public_instance_method(:attr_accessor, false)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "returns nil" do
+ Class.new do
+ (attr_accessor :foo, 'bar').should == nil
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "returns an array of defined method names as symbols" do
+ Class.new do
+ (attr_accessor :foo, 'bar').should == [:foo, :foo=, :bar, :bar=]
+ end
+ end
+ end
+
+ describe "on immediates" do
+ before :each do
+ class Integer
+ attr_accessor :foobar
+ end
+ end
+
+ after :each do
+ if Integer.method_defined?(:foobar)
+ Integer.send(:remove_method, :foobar)
+ end
+ if Integer.method_defined?(:foobar=)
+ Integer.send(:remove_method, :foobar=)
+ end
+ end
+
+ it "can read through the accessor" do
+ 1.foobar.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/module/attr_reader_spec.rb b/spec/ruby/core/module/attr_reader_spec.rb
new file mode 100644
index 0000000000..b0ae906ab5
--- /dev/null
+++ b/spec/ruby/core/module/attr_reader_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#attr_reader" do
+ it "creates a getter for each given attribute name" do
+ c = Class.new do
+ attr_reader :a, "b"
+
+ def initialize
+ @a = "test"
+ @b = "test2"
+ end
+ end
+
+ o = c.new
+ %w{a b}.each do |x|
+ o.respond_to?(x).should == true
+ o.respond_to?("#{x}=").should == false
+ end
+
+ o.a.should == "test"
+ o.b.should == "test2"
+ o.send(:a).should == "test"
+ o.send(:b).should == "test2"
+ end
+
+ it "not allows for adding an attr_reader to an immediate" do
+ class TrueClass
+ attr_reader :spec_attr_reader
+ end
+
+ -> { true.instance_variable_set("@spec_attr_reader", "a") }.should raise_error(RuntimeError)
+ end
+
+ it "converts non string/symbol names to strings using to_str" do
+ (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test")
+ c = Class.new do
+ attr_reader o
+ end
+
+ c.new.respond_to?("test").should == true
+ c.new.respond_to?("test=").should == false
+ end
+
+ it "raises a TypeError when the given names can't be converted to strings using to_str" do
+ o = mock('o')
+ -> { Class.new { attr_reader o } }.should raise_error(TypeError)
+ (o = mock('123')).should_receive(:to_str).and_return(123)
+ -> { Class.new { attr_reader o } }.should raise_error(TypeError)
+ end
+
+ it "applies current visibility to methods created" do
+ c = Class.new do
+ protected
+ attr_reader :foo
+ end
+
+ -> { c.new.foo }.should raise_error(NoMethodError)
+ end
+
+ it "is a public method" do
+ Module.should have_public_instance_method(:attr_reader, false)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "returns nil" do
+ Class.new do
+ (attr_reader :foo, 'bar').should == nil
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "returns an array of defined method names as symbols" do
+ Class.new do
+ (attr_reader :foo, 'bar').should == [:foo, :bar]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/module/attr_spec.rb b/spec/ruby/core/module/attr_spec.rb
new file mode 100644
index 0000000000..33e0eb8628
--- /dev/null
+++ b/spec/ruby/core/module/attr_spec.rb
@@ -0,0 +1,168 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#attr" do
+ before :each do
+ $VERBOSE, @verbose = false, $VERBOSE
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it "creates a getter for the given attribute name" do
+ c = Class.new do
+ attr :attr
+ attr "attr3"
+
+ def initialize
+ @attr, @attr2, @attr3 = "test", "test2", "test3"
+ end
+ end
+
+ o = c.new
+
+ %w{attr attr3}.each do |a|
+ o.respond_to?(a).should == true
+ o.respond_to?("#{a}=").should == false
+ end
+
+ o.attr.should == "test"
+ o.attr3.should == "test3"
+ o.send(:attr).should == "test"
+ o.send(:attr3).should == "test3"
+ end
+
+ it "creates a setter for the given attribute name if writable is true" do
+ c = Class.new do
+ attr :attr, true
+ attr "attr3", true
+
+ def initialize
+ @attr, @attr2, @attr3 = "test", "test2", "test3"
+ end
+ end
+
+ o = c.new
+
+ %w{attr attr3}.each do |a|
+ o.respond_to?(a).should == true
+ o.respond_to?("#{a}=").should == true
+ end
+
+ o.attr = "test updated"
+ o.attr3 = "test3 updated"
+ end
+
+ it "creates a getter and setter for the given attribute name if called with and without writable is true" do
+ c = Class.new do
+ attr :attr, true
+ attr :attr
+
+ attr "attr3", true
+ attr "attr3"
+
+ def initialize
+ @attr, @attr2, @attr3 = "test", "test2", "test3"
+ end
+ end
+
+ o = c.new
+
+ %w{attr attr3}.each do |a|
+ o.respond_to?(a).should == true
+ o.respond_to?("#{a}=").should == true
+ end
+
+ o.attr.should == "test"
+ o.attr = "test updated"
+ o.attr.should == "test updated"
+
+ o.attr3.should == "test3"
+ o.attr3 = "test3 updated"
+ o.attr3.should == "test3 updated"
+ end
+
+ it "applies current visibility to methods created" do
+ c = Class.new do
+ protected
+ attr :foo, true
+ end
+
+ -> { c.new.foo }.should raise_error(NoMethodError)
+ -> { c.new.foo=1 }.should raise_error(NoMethodError)
+ end
+
+ it "creates a getter but no setter for all given attribute names" do
+ c = Class.new do
+ attr :attr, "attr2", "attr3"
+
+ def initialize
+ @attr, @attr2, @attr3 = "test", "test2", "test3"
+ end
+ end
+
+ o = c.new
+
+ %w{attr attr2 attr3}.each do |a|
+ o.respond_to?(a).should == true
+ o.respond_to?("#{a}=").should == false
+ end
+
+ o.attr.should == "test"
+ o.attr2.should == "test2"
+ o.attr3.should == "test3"
+ end
+
+ it "applies current visibility to methods created" do
+ c = Class.new do
+ protected
+ attr :foo, :bar
+ end
+
+ -> { c.new.foo }.should raise_error(NoMethodError)
+ -> { c.new.bar }.should raise_error(NoMethodError)
+ end
+
+ it "converts non string/symbol names to strings using to_str" do
+ (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test")
+ Class.new { attr o }.new.respond_to?("test").should == true
+ end
+
+ it "raises a TypeError when the given names can't be converted to strings using to_str" do
+ o = mock('o')
+ -> { Class.new { attr o } }.should raise_error(TypeError)
+ (o = mock('123')).should_receive(:to_str).and_return(123)
+ -> { Class.new { attr o } }.should raise_error(TypeError)
+ end
+
+ it "with a boolean argument emits a warning when $VERBOSE is true" do
+ -> {
+ Class.new { attr :foo, true }
+ }.should complain(/boolean argument is obsoleted/, verbose: true)
+ end
+
+ it "is a public method" do
+ Module.should have_public_instance_method(:attr, false)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "returns nil" do
+ Class.new do
+ (attr :foo, 'bar').should == nil
+ (attr :baz, false).should == nil
+ (attr :qux, true).should == nil
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "returns an array of defined method names as symbols" do
+ Class.new do
+ (attr :foo, 'bar').should == [:foo, :bar]
+ (attr :baz, false).should == [:baz]
+ (attr :qux, true).should == [:qux, :qux=]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/module/attr_writer_spec.rb b/spec/ruby/core/module/attr_writer_spec.rb
new file mode 100644
index 0000000000..0e9d201317
--- /dev/null
+++ b/spec/ruby/core/module/attr_writer_spec.rb
@@ -0,0 +1,90 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#attr_writer" do
+ it "creates a setter for each given attribute name" do
+ c = Class.new do
+ attr_writer :test1, "test2"
+ end
+ o = c.new
+
+ o.respond_to?("test1").should == false
+ o.respond_to?("test2").should == false
+
+ o.respond_to?("test1=").should == true
+ o.test1 = "test_1"
+ o.instance_variable_get(:@test1).should == "test_1"
+
+ o.respond_to?("test2=").should == true
+ o.test2 = "test_2"
+ o.instance_variable_get(:@test2).should == "test_2"
+ o.send(:test1=,"test_1 updated")
+ o.instance_variable_get(:@test1).should == "test_1 updated"
+ o.send(:test2=,"test_2 updated")
+ o.instance_variable_get(:@test2).should == "test_2 updated"
+ end
+
+ it "not allows for adding an attr_writer to an immediate" do
+ class TrueClass
+ attr_writer :spec_attr_writer
+ end
+
+ -> { true.spec_attr_writer = "a" }.should raise_error(FrozenError)
+ end
+
+ it "raises FrozenError if the receiver if frozen" do
+ c = Class.new do
+ attr_writer :foo
+ end
+ obj = c.new
+ obj.freeze
+
+ -> { obj.foo = 42 }.should raise_error(FrozenError)
+ end
+
+ it "converts non string/symbol names to strings using to_str" do
+ (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test")
+ c = Class.new do
+ attr_writer o
+ end
+
+ c.new.respond_to?("test").should == false
+ c.new.respond_to?("test=").should == true
+ end
+
+ it "raises a TypeError when the given names can't be converted to strings using to_str" do
+ o = mock('test1')
+ -> { Class.new { attr_writer o } }.should raise_error(TypeError)
+ (o = mock('123')).should_receive(:to_str).and_return(123)
+ -> { Class.new { attr_writer o } }.should raise_error(TypeError)
+ end
+
+ it "applies current visibility to methods created" do
+ c = Class.new do
+ protected
+ attr_writer :foo
+ end
+
+ -> { c.new.foo=1 }.should raise_error(NoMethodError)
+ end
+
+ it "is a public method" do
+ Module.should have_public_instance_method(:attr_writer, false)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "returns nil" do
+ Class.new do
+ (attr_writer :foo, 'bar').should == nil
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "returns an array of defined method names as symbols" do
+ Class.new do
+ (attr_writer :foo, 'bar').should == [:foo=, :bar=]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/module/autoload_spec.rb b/spec/ruby/core/module/autoload_spec.rb
new file mode 100644
index 0000000000..af04ab26c8
--- /dev/null
+++ b/spec/ruby/core/module/autoload_spec.rb
@@ -0,0 +1,1012 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/code_loading'
+require_relative 'fixtures/classes'
+require 'thread'
+
+describe "Module#autoload?" do
+ it "returns the name of the file that will be autoloaded" do
+ ModuleSpecs::Autoload.autoload :Autoload, "autoload.rb"
+ ModuleSpecs::Autoload.autoload?(:Autoload).should == "autoload.rb"
+ end
+
+ it "returns nil if no file has been registered for a constant" do
+ ModuleSpecs::Autoload.autoload?(:Manualload).should be_nil
+ end
+
+ it "returns the name of the file that will be autoloaded if an ancestor defined that autoload" do
+ ModuleSpecs::Autoload::Parent.autoload :AnotherAutoload, "another_autoload.rb"
+ ModuleSpecs::Autoload::Child.autoload?(:AnotherAutoload).should == "another_autoload.rb"
+ end
+
+ it "returns nil if an ancestor defined that autoload but recursion is disabled" do
+ ModuleSpecs::Autoload::Parent.autoload :InheritedAutoload, "inherited_autoload.rb"
+ ModuleSpecs::Autoload::Child.autoload?(:InheritedAutoload, false).should be_nil
+ end
+
+ it "returns the name of the file that will be loaded if recursion is disabled but the autoload is defined on the class itself" do
+ ModuleSpecs::Autoload::Child.autoload :ChildAutoload, "child_autoload.rb"
+ ModuleSpecs::Autoload::Child.autoload?(:ChildAutoload, false).should == "child_autoload.rb"
+ end
+end
+
+describe "Module#autoload" do
+ before :all do
+ @non_existent = fixture __FILE__, "no_autoload.rb"
+ CodeLoadingSpecs.preload_rubygems
+ end
+
+ before :each do
+ @loaded_features = $".dup
+
+ ScratchPad.clear
+ @remove = []
+ end
+
+ after :each do
+ $".replace @loaded_features
+ @remove.each { |const|
+ ModuleSpecs::Autoload.send :remove_const, const
+ }
+ end
+
+ it "registers a file to load the first time the named constant is accessed" do
+ ModuleSpecs::Autoload.autoload :A, @non_existent
+ ModuleSpecs::Autoload.autoload?(:A).should == @non_existent
+ end
+
+ it "sets the autoload constant in the constants table" do
+ ModuleSpecs::Autoload.autoload :B, @non_existent
+ ModuleSpecs::Autoload.should have_constant(:B)
+ end
+
+ it "can be overridden with a second autoload on the same constant" do
+ ModuleSpecs::Autoload.autoload :Overridden, @non_existent
+ @remove << :Overridden
+ ModuleSpecs::Autoload.autoload?(:Overridden).should == @non_existent
+
+ path = fixture(__FILE__, "autoload_overridden.rb")
+ ModuleSpecs::Autoload.autoload :Overridden, path
+ ModuleSpecs::Autoload.autoload?(:Overridden).should == path
+
+ ModuleSpecs::Autoload::Overridden.should == :overridden
+ end
+
+ it "loads the registered constant when it is accessed" do
+ ModuleSpecs::Autoload.should_not have_constant(:X)
+ ModuleSpecs::Autoload.autoload :X, fixture(__FILE__, "autoload_x.rb")
+ @remove << :X
+ ModuleSpecs::Autoload::X.should == :x
+ end
+
+ it "loads the registered constant into a dynamically created class" do
+ cls = Class.new { autoload :C, fixture(__FILE__, "autoload_c.rb") }
+ ModuleSpecs::Autoload::DynClass = cls
+ @remove << :DynClass
+
+ ScratchPad.recorded.should be_nil
+ ModuleSpecs::Autoload::DynClass::C.new.loaded.should == :dynclass_c
+ ScratchPad.recorded.should == :loaded
+ end
+
+ it "loads the registered constant into a dynamically created module" do
+ mod = Module.new { autoload :D, fixture(__FILE__, "autoload_d.rb") }
+ ModuleSpecs::Autoload::DynModule = mod
+ @remove << :DynModule
+
+ ScratchPad.recorded.should be_nil
+ ModuleSpecs::Autoload::DynModule::D.new.loaded.should == :dynmodule_d
+ ScratchPad.recorded.should == :loaded
+ end
+
+ it "loads the registered constant when it is opened as a class" do
+ ModuleSpecs::Autoload.autoload :E, fixture(__FILE__, "autoload_e.rb")
+ class ModuleSpecs::Autoload::E
+ end
+ ModuleSpecs::Autoload::E.new.loaded.should == :autoload_e
+ end
+
+ it "loads the registered constant when it is opened as a module" do
+ ModuleSpecs::Autoload.autoload :F, fixture(__FILE__, "autoload_f.rb")
+ module ModuleSpecs::Autoload::F
+ end
+ ModuleSpecs::Autoload::F.loaded.should == :autoload_f
+ end
+
+ it "loads the registered constant when it is inherited from" do
+ ModuleSpecs::Autoload.autoload :G, fixture(__FILE__, "autoload_g.rb")
+ class ModuleSpecs::Autoload::Gsub < ModuleSpecs::Autoload::G
+ end
+ ModuleSpecs::Autoload::Gsub.new.loaded.should == :autoload_g
+ end
+
+ it "loads the registered constant when it is included" do
+ ModuleSpecs::Autoload.autoload :H, fixture(__FILE__, "autoload_h.rb")
+ class ModuleSpecs::Autoload::HClass
+ include ModuleSpecs::Autoload::H
+ end
+ ModuleSpecs::Autoload::HClass.new.loaded.should == :autoload_h
+ end
+
+ it "does not load the file when the constant is already set" do
+ ModuleSpecs::Autoload.autoload :I, fixture(__FILE__, "autoload_i.rb")
+ @remove << :I
+ ModuleSpecs::Autoload.const_set :I, 3
+ ModuleSpecs::Autoload::I.should == 3
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "loads a file with .rb extension when passed the name without the extension" do
+ ModuleSpecs::Autoload.autoload :J, fixture(__FILE__, "autoload_j")
+ ModuleSpecs::Autoload::J.should == :autoload_j
+ end
+
+ it "calls main.require(path) to load the file" do
+ ModuleSpecs::Autoload.autoload :ModuleAutoloadCallsRequire, "module_autoload_not_exist.rb"
+ main = TOPLEVEL_BINDING.eval("self")
+ main.should_receive(:require).with("module_autoload_not_exist.rb")
+ # The constant won't be defined since require is mocked to do nothing
+ -> { ModuleSpecs::Autoload::ModuleAutoloadCallsRequire }.should raise_error(NameError)
+ end
+
+ it "does not load the file if the file is manually required" do
+ filename = fixture(__FILE__, "autoload_k.rb")
+ ModuleSpecs::Autoload.autoload :KHash, filename
+ @remove << :KHash
+
+ require filename
+ ScratchPad.recorded.should == :loaded
+ ScratchPad.clear
+
+ ModuleSpecs::Autoload::KHash.should be_kind_of(Class)
+ ModuleSpecs::Autoload::KHash::K.should == :autoload_k
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "ignores the autoload request if the file is already loaded" do
+ filename = fixture(__FILE__, "autoload_s.rb")
+
+ require filename
+
+ ScratchPad.recorded.should == :loaded
+ ScratchPad.clear
+
+ ModuleSpecs::Autoload.autoload :S, filename
+ @remove << :S
+ ModuleSpecs::Autoload.autoload?(:S).should be_nil
+ end
+
+ it "retains the autoload even if the request to require fails" do
+ filename = fixture(__FILE__, "a_path_that_should_not_exist.rb")
+
+ ModuleSpecs::Autoload.autoload :NotThere, filename
+ ModuleSpecs::Autoload.autoload?(:NotThere).should == filename
+
+ -> {
+ require filename
+ }.should raise_error(LoadError)
+
+ ModuleSpecs::Autoload.autoload?(:NotThere).should == filename
+ end
+
+ it "allows multiple autoload constants for a single file" do
+ filename = fixture(__FILE__, "autoload_lm.rb")
+ ModuleSpecs::Autoload.autoload :L, filename
+ ModuleSpecs::Autoload.autoload :M, filename
+ ModuleSpecs::Autoload::L.should == :autoload_l
+ ModuleSpecs::Autoload::M.should == :autoload_m
+ end
+
+ it "runs for an exception condition class and doesn't trample the exception" do
+ filename = fixture(__FILE__, "autoload_ex1.rb")
+ ModuleSpecs::Autoload.autoload :EX1, filename
+ ModuleSpecs::Autoload.use_ex1.should == :good
+ end
+
+ it "considers an autoload constant as loaded when autoload is called for/from the current file" do
+ filename = fixture(__FILE__, "autoload_during_require_current_file.rb")
+ require filename
+
+ ScratchPad.recorded.should be_nil
+ end
+
+ describe "interacting with defined?" do
+ it "does not load the file when referring to the constant in defined?" do
+ module ModuleSpecs::Autoload::Dog
+ autoload :R, fixture(__FILE__, "autoload_exception.rb")
+ end
+
+ defined?(ModuleSpecs::Autoload::Dog::R).should == "constant"
+ ScratchPad.recorded.should be_nil
+
+ ModuleSpecs::Autoload::Dog.should have_constant(:R)
+ end
+
+ it "loads an autoloaded parent when referencing a nested constant" do
+ module ModuleSpecs::Autoload
+ autoload :GoodParent, fixture(__FILE__, "autoload_nested.rb")
+ end
+ @remove << :GoodParent
+
+ defined?(ModuleSpecs::Autoload::GoodParent::Nested).should == 'constant'
+ ScratchPad.recorded.should == :loaded
+ end
+
+ it "returns nil when it fails to load an autoloaded parent when referencing a nested constant" do
+ module ModuleSpecs::Autoload
+ autoload :BadParent, fixture(__FILE__, "autoload_exception.rb")
+ end
+
+ defined?(ModuleSpecs::Autoload::BadParent::Nested).should be_nil
+ ScratchPad.recorded.should == :exception
+ end
+ end
+
+ describe "the autoload is triggered when the same file is required directly" do
+ before :each do
+ module ModuleSpecs::Autoload
+ autoload :RequiredDirectly, fixture(__FILE__, "autoload_required_directly.rb")
+ end
+ @remove << :RequiredDirectly
+ @path = fixture(__FILE__, "autoload_required_directly.rb")
+ @check = -> {
+ [
+ defined?(ModuleSpecs::Autoload::RequiredDirectly),
+ ModuleSpecs::Autoload.autoload?(:RequiredDirectly)
+ ]
+ }
+ ScratchPad.record @check
+ end
+
+ it "with a full path" do
+ @check.call.should == ["constant", @path]
+ require @path
+ ScratchPad.recorded.should == [nil, nil]
+ @check.call.should == ["constant", nil]
+ end
+
+ it "with a relative path" do
+ @check.call.should == ["constant", @path]
+ $:.push File.dirname(@path)
+ begin
+ require "autoload_required_directly.rb"
+ ensure
+ $:.pop
+ end
+ ScratchPad.recorded.should == [nil, nil]
+ @check.call.should == ["constant", nil]
+ end
+
+ it "in a nested require" do
+ nested = fixture(__FILE__, "autoload_required_directly_nested.rb")
+ nested_require = -> {
+ result = nil
+ ScratchPad.record -> {
+ result = @check.call
+ }
+ require nested
+ result
+ }
+ ScratchPad.record nested_require
+
+ @check.call.should == ["constant", @path]
+ require @path
+ ScratchPad.recorded.should == [nil, nil]
+ @check.call.should == ["constant", nil]
+ end
+
+ it "does not raise an error if the autoload constant was not defined" do
+ module ModuleSpecs::Autoload
+ autoload :RequiredDirectlyNoConstant, fixture(__FILE__, "autoload_required_directly_no_constant.rb")
+ end
+ @path = fixture(__FILE__, "autoload_required_directly_no_constant.rb")
+ @remove << :RequiredDirectlyNoConstant
+ @check = -> {
+ [
+ defined?(ModuleSpecs::Autoload::RequiredDirectlyNoConstant),
+ ModuleSpecs::Autoload.constants(false).include?(:RequiredDirectlyNoConstant),
+ ModuleSpecs::Autoload.const_defined?(:RequiredDirectlyNoConstant),
+ ModuleSpecs::Autoload.autoload?(:RequiredDirectlyNoConstant)
+ ]
+ }
+ ScratchPad.record @check
+ @check.call.should == ["constant", true, true, @path]
+ $:.push File.dirname(@path)
+ begin
+ require "autoload_required_directly_no_constant.rb"
+ ensure
+ $:.pop
+ end
+ ScratchPad.recorded.should == [nil, true, false, nil]
+ @check.call.should == [nil, true, false, nil]
+ end
+ end
+
+ describe "after the autoload is triggered by require" do
+ before :each do
+ @path = tmp("autoload.rb")
+ end
+
+ after :each do
+ rm_r @path
+ end
+
+ it "the mapping feature to autoload is removed, and a new autoload with the same path is considered" do
+ ModuleSpecs::Autoload.autoload :RequireMapping1, @path
+ touch(@path) { |f| f.puts "ModuleSpecs::Autoload::RequireMapping1 = 1" }
+ ModuleSpecs::Autoload::RequireMapping1.should == 1
+
+ $LOADED_FEATURES.delete(@path)
+ ModuleSpecs::Autoload.autoload :RequireMapping2, @path[0...-3]
+ @remove << :RequireMapping2
+ touch(@path) { |f| f.puts "ModuleSpecs::Autoload::RequireMapping2 = 2" }
+ ModuleSpecs::Autoload::RequireMapping2.should == 2
+ end
+ end
+
+ describe "during the autoload before the constant is assigned" do
+ before :each do
+ @path = fixture(__FILE__, "autoload_during_autoload.rb")
+ ModuleSpecs::Autoload.autoload :DuringAutoload, @path
+ @remove << :DuringAutoload
+ raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoload) == @path
+ end
+
+ def check_before_during_thread_after(&check)
+ before = check.call
+ to_autoload_thread, from_autoload_thread = Queue.new, Queue.new
+ ScratchPad.record -> {
+ from_autoload_thread.push check.call
+ to_autoload_thread.pop
+ }
+ t = Thread.new {
+ in_loading_thread = from_autoload_thread.pop
+ in_other_thread = check.call
+ to_autoload_thread.push :done
+ [in_loading_thread, in_other_thread]
+ }
+ in_loading_thread, in_other_thread = nil
+ begin
+ ModuleSpecs::Autoload::DuringAutoload
+ ensure
+ in_loading_thread, in_other_thread = t.value
+ end
+ after = check.call
+ [before, in_loading_thread, in_other_thread, after]
+ end
+
+ it "returns nil in autoload thread and 'constant' otherwise for defined?" do
+ results = check_before_during_thread_after {
+ defined?(ModuleSpecs::Autoload::DuringAutoload)
+ }
+ results.should == ['constant', nil, 'constant', 'constant']
+ end
+
+ it "keeps the constant in Module#constants" do
+ results = check_before_during_thread_after {
+ ModuleSpecs::Autoload.constants(false).include?(:DuringAutoload)
+ }
+ results.should == [true, true, true, true]
+ end
+
+ it "returns false in autoload thread and true otherwise for Module#const_defined?" do
+ results = check_before_during_thread_after {
+ ModuleSpecs::Autoload.const_defined?(:DuringAutoload, false)
+ }
+ results.should == [true, false, true, true]
+ end
+
+ it "returns nil in autoload thread and returns the path in other threads for Module#autoload?" do
+ results = check_before_during_thread_after {
+ ModuleSpecs::Autoload.autoload?(:DuringAutoload)
+ }
+ results.should == [@path, nil, @path, nil]
+ end
+ end
+
+ it "does not remove the constant from Module#constants if load fails and keeps it as an autoload" do
+ ModuleSpecs::Autoload.autoload :Fail, @non_existent
+
+ ModuleSpecs::Autoload.const_defined?(:Fail).should == true
+ ModuleSpecs::Autoload.should have_constant(:Fail)
+ ModuleSpecs::Autoload.autoload?(:Fail).should == @non_existent
+
+ -> { ModuleSpecs::Autoload::Fail }.should raise_error(LoadError)
+
+ ModuleSpecs::Autoload.should have_constant(:Fail)
+ ModuleSpecs::Autoload.const_defined?(:Fail).should == true
+ ModuleSpecs::Autoload.autoload?(:Fail).should == @non_existent
+
+ -> { ModuleSpecs::Autoload::Fail }.should raise_error(LoadError)
+ end
+
+ it "does not remove the constant from Module#constants if load raises a RuntimeError and keeps it as an autoload" do
+ path = fixture(__FILE__, "autoload_raise.rb")
+ ScratchPad.record []
+ ModuleSpecs::Autoload.autoload :Raise, path
+
+ ModuleSpecs::Autoload.const_defined?(:Raise).should == true
+ ModuleSpecs::Autoload.should have_constant(:Raise)
+ ModuleSpecs::Autoload.autoload?(:Raise).should == path
+
+ -> { ModuleSpecs::Autoload::Raise }.should raise_error(RuntimeError)
+ ScratchPad.recorded.should == [:raise]
+
+ ModuleSpecs::Autoload.should have_constant(:Raise)
+ ModuleSpecs::Autoload.const_defined?(:Raise).should == true
+ ModuleSpecs::Autoload.autoload?(:Raise).should == path
+
+ -> { ModuleSpecs::Autoload::Raise }.should raise_error(RuntimeError)
+ ScratchPad.recorded.should == [:raise, :raise]
+ end
+
+ ruby_version_is "3.1" do
+ it "removes the constant from Module#constants if the loaded file does not define it" do
+ path = fixture(__FILE__, "autoload_o.rb")
+ ScratchPad.record []
+ ModuleSpecs::Autoload.autoload :O, path
+
+ ModuleSpecs::Autoload.const_defined?(:O).should == true
+ ModuleSpecs::Autoload.should have_constant(:O)
+ ModuleSpecs::Autoload.autoload?(:O).should == path
+
+ -> { ModuleSpecs::Autoload::O }.should raise_error(NameError)
+
+ ModuleSpecs::Autoload.const_defined?(:O).should == false
+ ModuleSpecs::Autoload.should_not have_constant(:O)
+ ModuleSpecs::Autoload.autoload?(:O).should == nil
+ -> { ModuleSpecs::Autoload.const_get(:O) }.should raise_error(NameError)
+ end
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "does not remove the constant from Module#constants if the loaded file does not define it, but leaves it as 'undefined'" do
+ path = fixture(__FILE__, "autoload_o.rb")
+ ScratchPad.record []
+ ModuleSpecs::Autoload.autoload :O, path
+
+ ModuleSpecs::Autoload.const_defined?(:O).should == true
+ ModuleSpecs::Autoload.should have_constant(:O)
+ ModuleSpecs::Autoload.autoload?(:O).should == path
+
+ -> { ModuleSpecs::Autoload::O }.should raise_error(NameError)
+
+ ModuleSpecs::Autoload.const_defined?(:O).should == false
+ ModuleSpecs::Autoload.should have_constant(:O)
+ ModuleSpecs::Autoload.autoload?(:O).should == nil
+ -> { ModuleSpecs::Autoload.const_get(:O) }.should raise_error(NameError)
+ end
+ end
+
+ it "does not try to load the file again if the loaded file did not define the constant" do
+ path = fixture(__FILE__, "autoload_o.rb")
+ ScratchPad.record []
+ ModuleSpecs::Autoload.autoload :NotDefinedByFile, path
+
+ -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError)
+ ScratchPad.recorded.should == [:loaded]
+ -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError)
+ ScratchPad.recorded.should == [:loaded]
+
+ Thread.new {
+ -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError)
+ }.join
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "returns 'constant' on referring the constant with defined?()" do
+ module ModuleSpecs::Autoload::Q
+ autoload :R, fixture(__FILE__, "autoload.rb")
+ defined?(R).should == 'constant'
+ end
+ ModuleSpecs::Autoload::Q.should have_constant(:R)
+ end
+
+ it "does not load the file when removing an autoload constant" do
+ module ModuleSpecs::Autoload::Q
+ autoload :R, fixture(__FILE__, "autoload.rb")
+ remove_const :R
+ end
+ ModuleSpecs::Autoload::Q.should_not have_constant(:R)
+ end
+
+ it "does not load the file when accessing the constants table of the module" do
+ ModuleSpecs::Autoload.autoload :P, @non_existent
+ ModuleSpecs::Autoload.const_defined?(:P).should be_true
+ ModuleSpecs::Autoload.const_defined?("P").should be_true
+ end
+
+ it "loads the file when opening a module that is the autoloaded constant" do
+ module ModuleSpecs::Autoload::U
+ autoload :V, fixture(__FILE__, "autoload_v.rb")
+
+ class V
+ X = get_value
+ end
+ end
+ @remove << :U
+
+ ModuleSpecs::Autoload::U::V::X.should == :autoload_uvx
+ end
+
+ it "loads the file that defines subclass XX::CS_CONST_AUTOLOAD < CS_CONST_AUTOLOAD and CS_CONST_AUTOLOAD is a top level constant" do
+ module ModuleSpecs::Autoload::XX
+ autoload :CS_CONST_AUTOLOAD, fixture(__FILE__, "autoload_subclass.rb")
+ end
+
+ ModuleSpecs::Autoload::XX::CS_CONST_AUTOLOAD.superclass.should == CS_CONST_AUTOLOAD
+ end
+
+ describe "after autoloading searches for the constant like the original lookup" do
+ it "in lexical scopes if both declared and defined in parent" do
+ module ModuleSpecs::Autoload
+ ScratchPad.record -> {
+ DeclaredAndDefinedInParent = :declared_and_defined_in_parent
+ }
+ autoload :DeclaredAndDefinedInParent, fixture(__FILE__, "autoload_callback.rb")
+ class LexicalScope
+ DeclaredAndDefinedInParent.should == :declared_and_defined_in_parent
+
+ # The constant is really in Autoload, not Autoload::LexicalScope
+ self.should_not have_constant(:DeclaredAndDefinedInParent)
+ -> { const_get(:DeclaredAndDefinedInParent) }.should raise_error(NameError)
+ end
+ DeclaredAndDefinedInParent.should == :declared_and_defined_in_parent
+ end
+ end
+
+ it "in lexical scopes if declared in parent and defined in current" do
+ module ModuleSpecs::Autoload
+ ScratchPad.record -> {
+ class LexicalScope
+ DeclaredInParentDefinedInCurrent = :declared_in_parent_defined_in_current
+ end
+ }
+ autoload :DeclaredInParentDefinedInCurrent, fixture(__FILE__, "autoload_callback.rb")
+
+ class LexicalScope
+ DeclaredInParentDefinedInCurrent.should == :declared_in_parent_defined_in_current
+ LexicalScope::DeclaredInParentDefinedInCurrent.should == :declared_in_parent_defined_in_current
+ end
+
+ # Basically, the parent autoload constant remains in a "undefined" state
+ self.autoload?(:DeclaredInParentDefinedInCurrent).should == nil
+ const_defined?(:DeclaredInParentDefinedInCurrent).should == false
+ -> { DeclaredInParentDefinedInCurrent }.should raise_error(NameError)
+
+ ModuleSpecs::Autoload::LexicalScope.send(:remove_const, :DeclaredInParentDefinedInCurrent)
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "warns once in verbose mode if the constant was defined in a parent scope" do
+ ScratchPad.record -> {
+ ModuleSpecs::DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent
+ }
+
+ module ModuleSpecs
+ module Autoload
+ autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb")
+ self.autoload?(:DeclaredInCurrentDefinedInParent).should == fixture(__FILE__, "autoload_callback.rb")
+ const_defined?(:DeclaredInCurrentDefinedInParent).should == true
+
+ -> {
+ DeclaredInCurrentDefinedInParent
+ }.should complain(
+ /Expected .*autoload_callback.rb to define ModuleSpecs::Autoload::DeclaredInCurrentDefinedInParent but it didn't/,
+ verbose: true,
+ )
+
+ -> {
+ DeclaredInCurrentDefinedInParent
+ }.should_not complain(/.*/, verbose: true)
+ self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil
+ const_defined?(:DeclaredInCurrentDefinedInParent).should == false
+ ModuleSpecs.const_defined?(:DeclaredInCurrentDefinedInParent).should == true
+ end
+ end
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "looks up in parent scope after failed autoload" do
+ @remove << :DeclaredInCurrentDefinedInParent
+ module ModuleSpecs::Autoload
+ ScratchPad.record -> {
+ DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent
+ }
+
+ class LexicalScope
+ autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb")
+ -> { DeclaredInCurrentDefinedInParent }.should_not raise_error(NameError)
+ # Basically, the autoload constant remains in a "undefined" state
+ self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil
+ const_defined?(:DeclaredInCurrentDefinedInParent).should == false
+ -> { const_get(:DeclaredInCurrentDefinedInParent) }.should raise_error(NameError)
+ end
+
+ DeclaredInCurrentDefinedInParent.should == :declared_in_current_defined_in_parent
+ end
+ end
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "and fails when finding the undefined autoload constant in the current scope when declared in current and defined in parent" do
+ @remove << :DeclaredInCurrentDefinedInParent
+ module ModuleSpecs::Autoload
+ ScratchPad.record -> {
+ DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent
+ }
+
+ class LexicalScope
+ autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb")
+ -> { DeclaredInCurrentDefinedInParent }.should raise_error(NameError)
+ # Basically, the autoload constant remains in a "undefined" state
+ self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil
+ const_defined?(:DeclaredInCurrentDefinedInParent).should == false
+ self.should have_constant(:DeclaredInCurrentDefinedInParent)
+ -> { const_get(:DeclaredInCurrentDefinedInParent) }.should raise_error(NameError)
+ end
+
+ DeclaredInCurrentDefinedInParent.should == :declared_in_current_defined_in_parent
+ end
+ end
+ end
+
+ it "in the included modules" do
+ @remove << :DefinedInIncludedModule
+ module ModuleSpecs::Autoload
+ ScratchPad.record -> {
+ module DefinedInIncludedModule
+ Incl = :defined_in_included_module
+ end
+ include DefinedInIncludedModule
+ }
+ autoload :Incl, fixture(__FILE__, "autoload_callback.rb")
+ Incl.should == :defined_in_included_module
+ end
+ end
+
+ it "in the included modules of the superclass" do
+ @remove << :DefinedInSuperclassIncludedModule
+ module ModuleSpecs::Autoload
+ class LookupAfterAutoloadSuper
+ end
+ class LookupAfterAutoloadChild < LookupAfterAutoloadSuper
+ end
+
+ ScratchPad.record -> {
+ module DefinedInSuperclassIncludedModule
+ InclS = :defined_in_superclass_included_module
+ end
+ LookupAfterAutoloadSuper.include DefinedInSuperclassIncludedModule
+ }
+
+ class LookupAfterAutoloadChild
+ autoload :InclS, fixture(__FILE__, "autoload_callback.rb")
+ InclS.should == :defined_in_superclass_included_module
+ end
+ end
+ end
+
+ it "in the prepended modules" do
+ @remove << :DefinedInPrependedModule
+ module ModuleSpecs::Autoload
+ ScratchPad.record -> {
+ module DefinedInPrependedModule
+ Prep = :defined_in_prepended_module
+ end
+ include DefinedInPrependedModule
+ }
+ autoload :Prep, fixture(__FILE__, "autoload_callback.rb")
+ Prep.should == :defined_in_prepended_module
+ end
+ end
+
+ it "in a meta class scope" do
+ module ModuleSpecs::Autoload
+ ScratchPad.record -> {
+ class MetaScope
+ end
+ }
+ autoload :MetaScope, fixture(__FILE__, "autoload_callback.rb")
+ class << self
+ def r
+ MetaScope.new
+ end
+ end
+ end
+ ModuleSpecs::Autoload.r.should be_kind_of(ModuleSpecs::Autoload::MetaScope)
+ end
+ end
+
+ # [ruby-core:19127] [ruby-core:29941]
+ it "does NOT raise a NameError when the autoload file did not define the constant and a module is opened with the same name" do
+ module ModuleSpecs::Autoload
+ class W
+ autoload :Y, fixture(__FILE__, "autoload_w.rb")
+
+ class Y
+ end
+ end
+ end
+ @remove << :W
+
+ ModuleSpecs::Autoload::W::Y.should be_kind_of(Class)
+ ScratchPad.recorded.should == :loaded
+ end
+
+ it "does not call #require a second time and does not warn if already loading the same feature with #require" do
+ main = TOPLEVEL_BINDING.eval("self")
+ main.should_not_receive(:require)
+
+ module ModuleSpecs::Autoload
+ autoload :AutoloadDuringRequire, fixture(__FILE__, "autoload_during_require.rb")
+ end
+
+ -> {
+ Kernel.require fixture(__FILE__, "autoload_during_require.rb")
+ }.should_not complain(verbose: true)
+ ModuleSpecs::Autoload::AutoloadDuringRequire.should be_kind_of(Class)
+ end
+
+ it "does not call #require a second time and does not warn if feature sets and trigger autoload on itself" do
+ main = TOPLEVEL_BINDING.eval("self")
+ main.should_not_receive(:require)
+
+ -> {
+ Kernel.require fixture(__FILE__, "autoload_self_during_require.rb")
+ }.should_not complain(verbose: true)
+ ModuleSpecs::Autoload::AutoloadSelfDuringRequire.should be_kind_of(Class)
+ end
+
+ it "handles multiple autoloads in the same file" do
+ $LOAD_PATH.unshift(File.expand_path('../fixtures/multi', __FILE__))
+ begin
+ require 'foo/bar_baz'
+ ModuleSpecs::Autoload::Foo::Bar.should be_kind_of(Class)
+ ModuleSpecs::Autoload::Foo::Baz.should be_kind_of(Class)
+ ensure
+ $LOAD_PATH.shift
+ end
+ end
+
+ it "calls #to_path on non-string filenames" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return @non_existent
+ ModuleSpecs.autoload :A, p
+ end
+
+ it "raises an ArgumentError when an empty filename is given" do
+ -> { ModuleSpecs.autoload :A, "" }.should raise_error(ArgumentError)
+ end
+
+ it "raises a NameError when the constant name starts with a lower case letter" do
+ -> { ModuleSpecs.autoload "a", @non_existent }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when the constant name starts with a number" do
+ -> { ModuleSpecs.autoload "1two", @non_existent }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when the constant name has a space in it" do
+ -> { ModuleSpecs.autoload "a name", @non_existent }.should raise_error(NameError)
+ end
+
+ it "shares the autoload request across dup'ed copies of modules" do
+ require fixture(__FILE__, "autoload_s.rb")
+ @remove << :S
+ filename = fixture(__FILE__, "autoload_t.rb")
+ mod1 = Module.new { autoload :T, filename }
+ -> {
+ ModuleSpecs::Autoload::S = mod1
+ }.should complain(/already initialized constant/)
+ mod2 = mod1.dup
+
+ mod1.autoload?(:T).should == filename
+ mod2.autoload?(:T).should == filename
+
+ mod1::T.should == :autoload_t
+ -> { mod2::T }.should raise_error(NameError)
+ end
+
+ it "raises a TypeError if opening a class with a different superclass than the class defined in the autoload file" do
+ ModuleSpecs::Autoload.autoload :Z, fixture(__FILE__, "autoload_z.rb")
+ class ModuleSpecs::Autoload::ZZ
+ end
+
+ -> do
+ class ModuleSpecs::Autoload::Z < ModuleSpecs::Autoload::ZZ
+ end
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if not passed a String or object responding to #to_path for the filename" do
+ name = mock("autoload_name.rb")
+
+ -> { ModuleSpecs::Autoload.autoload :Str, name }.should raise_error(TypeError)
+ end
+
+ it "calls #to_path on non-String filename arguments" do
+ name = mock("autoload_name.rb")
+ name.should_receive(:to_path).and_return("autoload_name.rb")
+
+ -> { ModuleSpecs::Autoload.autoload :Str, name }.should_not raise_error
+ end
+
+ describe "on a frozen module" do
+ it "raises a FrozenError before setting the name" do
+ frozen_module = Module.new.freeze
+ -> { frozen_module.autoload :Foo, @non_existent }.should raise_error(FrozenError)
+ frozen_module.should_not have_constant(:Foo)
+ end
+ end
+
+ describe "when changing $LOAD_PATH" do
+ before do
+ $LOAD_PATH.unshift(File.expand_path('../fixtures/path1', __FILE__))
+ end
+
+ after do
+ $LOAD_PATH.shift
+ $LOAD_PATH.shift
+ end
+
+ it "does not reload a file due to a different load path" do
+ ModuleSpecs::Autoload.autoload :LoadPath, "load_path"
+ ModuleSpecs::Autoload::LoadPath.loaded.should == :autoload_load_path
+ end
+ end
+
+ describe "(concurrently)" do
+ it "blocks a second thread while a first is doing the autoload" do
+ ModuleSpecs::Autoload.autoload :Concur, fixture(__FILE__, "autoload_concur.rb")
+ @remove << :Concur
+
+ start = false
+
+ ScratchPad.record []
+
+ t1_val = nil
+ t2_val = nil
+
+ fin = false
+
+ t1 = Thread.new do
+ Thread.pass until start
+ t1_val = ModuleSpecs::Autoload::Concur
+ ScratchPad.recorded << :t1_post
+ fin = true
+ end
+
+ t2_exc = nil
+
+ t2 = Thread.new do
+ Thread.pass until t1 and t1[:in_autoload_rb]
+ begin
+ t2_val = ModuleSpecs::Autoload::Concur
+ rescue Exception => e
+ t2_exc = e
+ else
+ Thread.pass until fin
+ ScratchPad.recorded << :t2_post
+ end
+ end
+
+ start = true
+
+ t1.join
+ t2.join
+
+ ScratchPad.recorded.should == [:con_pre, :con_post, :t1_post, :t2_post]
+
+ t1_val.should == 1
+ t2_val.should == t1_val
+
+ t2_exc.should be_nil
+ end
+
+ # https://bugs.ruby-lang.org/issues/10892
+ it "blocks others threads while doing an autoload" do
+ file_path = fixture(__FILE__, "repeated_concurrent_autoload.rb")
+ autoload_path = file_path.sub(/\.rb\Z/, '')
+ mod_count = 30
+ thread_count = 16
+
+ mod_names = []
+ mod_count.times do |i|
+ mod_name = :"Mod#{i}"
+ Object.autoload mod_name, autoload_path
+ mod_names << mod_name
+ end
+
+ barrier = ModuleSpecs::CyclicBarrier.new thread_count
+ ScratchPad.record ModuleSpecs::ThreadSafeCounter.new
+
+ threads = (1..thread_count).map do
+ Thread.new do
+ mod_names.each do |mod_name|
+ break false unless barrier.enabled?
+
+ was_last_one_in = barrier.await # wait for all threads to finish the iteration
+ # clean up so we can autoload the same file again
+ $LOADED_FEATURES.delete(file_path) if was_last_one_in && $LOADED_FEATURES.include?(file_path)
+ barrier.await # get ready for race
+
+ begin
+ Object.const_get(mod_name).foo
+ rescue NoMethodError
+ barrier.disable!
+ break false
+ end
+ end
+ end
+ end
+
+ # check that no thread got a NoMethodError because of partially loaded module
+ threads.all? {|t| t.value}.should be_true
+
+ # check that the autoloaded file was evaled exactly once
+ ScratchPad.recorded.get.should == mod_count
+
+ mod_names.each do |mod_name|
+ Object.send(:remove_const, mod_name)
+ end
+ end
+
+ it "raises a NameError in each thread if the constant is not set" do
+ file = fixture(__FILE__, "autoload_never_set.rb")
+ start = false
+
+ threads = Array.new(10) do
+ Thread.new do
+ Thread.pass until start
+ begin
+ ModuleSpecs::Autoload.autoload :NeverSetConstant, file
+ Thread.pass
+ ModuleSpecs::Autoload::NeverSetConstant
+ rescue NameError => e
+ e
+ ensure
+ Thread.pass
+ end
+ end
+ end
+
+ start = true
+ threads.each { |t|
+ t.value.should be_an_instance_of(NameError)
+ }
+ end
+
+ it "raises a LoadError in each thread if the file does not exist" do
+ file = fixture(__FILE__, "autoload_does_not_exist.rb")
+ start = false
+
+ threads = Array.new(10) do
+ Thread.new do
+ Thread.pass until start
+ begin
+ ModuleSpecs::Autoload.autoload :FileDoesNotExist, file
+ Thread.pass
+ ModuleSpecs::Autoload::FileDoesNotExist
+ rescue LoadError => e
+ e
+ ensure
+ Thread.pass
+ end
+ end
+ end
+
+ start = true
+ threads.each { |t|
+ t.value.should be_an_instance_of(LoadError)
+ }
+ end
+ end
+
+ it "loads the registered constant even if the constant was already loaded by another thread" do
+ Thread.new {
+ ModuleSpecs::Autoload::FromThread::D.foo
+ }.value.should == :foo
+ end
+end
diff --git a/spec/ruby/core/module/case_compare_spec.rb b/spec/ruby/core/module/case_compare_spec.rb
new file mode 100644
index 0000000000..49ac359f6f
--- /dev/null
+++ b/spec/ruby/core/module/case_compare_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#===" do
+ it "returns true when the given Object is an instance of self or of self's descendants" do
+ (ModuleSpecs::Child === ModuleSpecs::Child.new).should == true
+ (ModuleSpecs::Parent === ModuleSpecs::Parent.new).should == true
+
+ (ModuleSpecs::Parent === ModuleSpecs::Child.new).should == true
+ (Object === ModuleSpecs::Child.new).should == true
+
+ (ModuleSpecs::Child === String.new).should == false
+ (ModuleSpecs::Child === mock('x')).should == false
+ end
+
+ it "returns true when the given Object's class includes self or when the given Object is extended by self" do
+ (ModuleSpecs::Basic === ModuleSpecs::Child.new).should == true
+ (ModuleSpecs::Super === ModuleSpecs::Child.new).should == true
+ (ModuleSpecs::Basic === mock('x').extend(ModuleSpecs::Super)).should == true
+ (ModuleSpecs::Super === mock('y').extend(ModuleSpecs::Super)).should == true
+
+ (ModuleSpecs::Basic === ModuleSpecs::Parent.new).should == false
+ (ModuleSpecs::Super === ModuleSpecs::Parent.new).should == false
+ (ModuleSpecs::Basic === mock('z')).should == false
+ (ModuleSpecs::Super === mock('a')).should == false
+ end
+
+ it "does not let a module singleton class interfere when its on the RHS" do
+ (Class === ModuleSpecs::CaseCompareOnSingleton).should == false
+ end
+end
diff --git a/spec/ruby/core/module/class_eval_spec.rb b/spec/ruby/core/module/class_eval_spec.rb
new file mode 100644
index 0000000000..c6665d5aff
--- /dev/null
+++ b/spec/ruby/core/module/class_eval_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/class_eval'
+
+describe "Module#class_eval" do
+ it_behaves_like :module_class_eval, :class_eval
+end
diff --git a/spec/ruby/core/module/class_exec_spec.rb b/spec/ruby/core/module/class_exec_spec.rb
new file mode 100644
index 0000000000..4acd0169ad
--- /dev/null
+++ b/spec/ruby/core/module/class_exec_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/class_exec'
+
+describe "Module#class_exec" do
+ it_behaves_like :module_class_exec, :class_exec
+end
diff --git a/spec/ruby/core/module/class_variable_defined_spec.rb b/spec/ruby/core/module/class_variable_defined_spec.rb
new file mode 100644
index 0000000000..c0f2072a37
--- /dev/null
+++ b/spec/ruby/core/module/class_variable_defined_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#class_variable_defined?" do
+ it "returns true if a class variable with the given name is defined in self" do
+ c = Class.new { class_variable_set :@@class_var, "test" }
+ c.class_variable_defined?(:@@class_var).should == true
+ c.class_variable_defined?("@@class_var").should == true
+ c.class_variable_defined?(:@@no_class_var).should == false
+ c.class_variable_defined?("@@no_class_var").should == false
+ ModuleSpecs::CVars.class_variable_defined?("@@cls").should == true
+ end
+
+ it "returns true if a class variable with the given name is defined in the metaclass" do
+ ModuleSpecs::CVars.class_variable_defined?("@@meta").should == true
+ end
+
+ it "returns true if the class variable is defined in a metaclass" do
+ obj = mock("metaclass class variable")
+ meta = obj.singleton_class
+ meta.send :class_variable_set, :@@var, 1
+ meta.send(:class_variable_defined?, :@@var).should be_true
+ end
+
+ it "returns false if the class variable is not defined in a metaclass" do
+ obj = mock("metaclass class variable")
+ meta = obj.singleton_class
+ meta.class_variable_defined?(:@@var).should be_false
+ end
+
+ it "returns true if a class variables with the given name is defined in an included module" do
+ c = Class.new { include ModuleSpecs::MVars }
+ c.class_variable_defined?("@@mvar").should == true
+ end
+
+ it "returns false if a class variables with the given name is defined in an extended module" do
+ c = Class.new
+ c.extend ModuleSpecs::MVars
+ c.class_variable_defined?("@@mvar").should == false
+ end
+
+ it "raises a NameError when the given name is not allowed" do
+ c = Class.new
+
+ -> {
+ c.class_variable_defined?(:invalid_name)
+ }.should raise_error(NameError)
+
+ -> {
+ c.class_variable_defined?("@invalid_name")
+ }.should raise_error(NameError)
+ end
+
+ it "converts a non string/symbol name to string using to_str" do
+ c = Class.new { class_variable_set :@@class_var, "test" }
+ (o = mock('@@class_var')).should_receive(:to_str).and_return("@@class_var")
+ c.class_variable_defined?(o).should == true
+ end
+
+ it "raises a TypeError when the given names can't be converted to strings using to_str" do
+ c = Class.new { class_variable_set :@@class_var, "test" }
+ o = mock('123')
+ -> {
+ c.class_variable_defined?(o)
+ }.should raise_error(TypeError)
+
+ o.should_receive(:to_str).and_return(123)
+ -> {
+ c.class_variable_defined?(o)
+ }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/module/class_variable_get_spec.rb b/spec/ruby/core/module/class_variable_get_spec.rb
new file mode 100644
index 0000000000..e5d06731ec
--- /dev/null
+++ b/spec/ruby/core/module/class_variable_get_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#class_variable_get" do
+ it "returns the value of the class variable with the given name" do
+ c = Class.new { class_variable_set :@@class_var, "test" }
+ c.send(:class_variable_get, :@@class_var).should == "test"
+ c.send(:class_variable_get, "@@class_var").should == "test"
+ end
+
+ it "returns the value of a class variable with the given name defined in an included module" do
+ c = Class.new { include ModuleSpecs::MVars }
+ c.send(:class_variable_get, "@@mvar").should == :mvar
+ end
+
+ it "raises a NameError for a class variable named '@@'" do
+ c = Class.new
+ -> { c.send(:class_variable_get, "@@") }.should raise_error(NameError)
+ -> { c.send(:class_variable_get, :"@@") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError for a class variables with the given name defined in an extended module" do
+ c = Class.new
+ c.extend ModuleSpecs::MVars
+ -> {
+ c.send(:class_variable_get, "@@mvar")
+ }.should raise_error(NameError)
+ end
+
+ it "returns class variables defined in the class body and accessed in the metaclass" do
+ ModuleSpecs::CVars.cls.should == :class
+ end
+
+ it "returns class variables defined in the metaclass and accessed by class methods" do
+ ModuleSpecs::CVars.meta.should == :metainfo
+ end
+
+ it "returns class variables defined in the metaclass and accessed by instance methods" do
+ ModuleSpecs::CVars.new.meta.should == :metainfo
+ end
+
+ it "returns a class variable defined in a metaclass" do
+ obj = mock("metaclass class variable")
+ meta = obj.singleton_class
+ meta.send :class_variable_set, :@@var, :cvar_value
+ meta.send(:class_variable_get, :@@var).should == :cvar_value
+ end
+
+ it "raises a NameError when an uninitialized class variable is accessed" do
+ c = Class.new
+ [:@@no_class_var, "@@no_class_var"].each do |cvar|
+ -> { c.send(:class_variable_get, cvar) }.should raise_error(NameError)
+ end
+ end
+
+ it "raises a NameError when the given name is not allowed" do
+ c = Class.new
+
+ -> { c.send(:class_variable_get, :invalid_name) }.should raise_error(NameError)
+ -> { c.send(:class_variable_get, "@invalid_name") }.should raise_error(NameError)
+ end
+
+ it "converts a non string/symbol name to string using to_str" do
+ c = Class.new { class_variable_set :@@class_var, "test" }
+ (o = mock('@@class_var')).should_receive(:to_str).and_return("@@class_var")
+ c.send(:class_variable_get, o).should == "test"
+ end
+
+ it "raises a TypeError when the given names can't be converted to strings using to_str" do
+ c = Class.new { class_variable_set :@@class_var, "test" }
+ o = mock('123')
+ -> { c.send(:class_variable_get, o) }.should raise_error(TypeError)
+ o.should_receive(:to_str).and_return(123)
+ -> { c.send(:class_variable_get, o) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/module/class_variable_set_spec.rb b/spec/ruby/core/module/class_variable_set_spec.rb
new file mode 100644
index 0000000000..63f32f5389
--- /dev/null
+++ b/spec/ruby/core/module/class_variable_set_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#class_variable_set" do
+ it "sets the class variable with the given name to the given value" do
+ c = Class.new
+
+ c.send(:class_variable_set, :@@test, "test")
+ c.send(:class_variable_set, "@@test3", "test3")
+
+ c.send(:class_variable_get, :@@test).should == "test"
+ c.send(:class_variable_get, :@@test3).should == "test3"
+ end
+
+ it "sets a class variable on a metaclass" do
+ obj = mock("metaclass class variable")
+ meta = obj.singleton_class
+ meta.send(:class_variable_set, :@@var, :cvar_value).should == :cvar_value
+ meta.send(:class_variable_get, :@@var).should == :cvar_value
+ end
+
+ it "sets the value of a class variable with the given name defined in an included module" do
+ c = Class.new { include ModuleSpecs::MVars.dup }
+ c.send(:class_variable_set, "@@mvar", :new_mvar).should == :new_mvar
+ c.send(:class_variable_get, "@@mvar").should == :new_mvar
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ -> {
+ Class.new.freeze.send(:class_variable_set, :@@test, "test")
+ }.should raise_error(FrozenError)
+ -> {
+ Module.new.freeze.send(:class_variable_set, :@@test, "test")
+ }.should raise_error(FrozenError)
+ end
+
+ it "raises a NameError when the given name is not allowed" do
+ c = Class.new
+
+ -> {
+ c.send(:class_variable_set, :invalid_name, "test")
+ }.should raise_error(NameError)
+ -> {
+ c.send(:class_variable_set, "@invalid_name", "test")
+ }.should raise_error(NameError)
+ end
+
+ it "converts a non string/symbol name to string using to_str" do
+ (o = mock('@@class_var')).should_receive(:to_str).and_return("@@class_var")
+ c = Class.new
+ c.send(:class_variable_set, o, "test")
+ c.send(:class_variable_get, :@@class_var).should == "test"
+ end
+
+ it "raises a TypeError when the given names can't be converted to strings using to_str" do
+ c = Class.new { class_variable_set :@@class_var, "test" }
+ o = mock('123')
+ -> { c.send(:class_variable_set, o, "test") }.should raise_error(TypeError)
+ o.should_receive(:to_str).and_return(123)
+ -> { c.send(:class_variable_set, o, "test") }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/module/class_variables_spec.rb b/spec/ruby/core/module/class_variables_spec.rb
new file mode 100644
index 0000000000..e155f1deac
--- /dev/null
+++ b/spec/ruby/core/module/class_variables_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#class_variables" do
+ it "returns an Array with the names of class variables of self" do
+ ModuleSpecs::ClassVars::A.class_variables.should include(:@@a_cvar)
+ ModuleSpecs::ClassVars::M.class_variables.should include(:@@m_cvar)
+ end
+
+ it "returns an Array of Symbols of class variable names defined in a metaclass" do
+ obj = mock("metaclass class variable")
+ meta = obj.singleton_class
+ meta.send :class_variable_set, :@@var, :cvar_value
+ meta.class_variables.should == [:@@var]
+ end
+
+ it "returns an Array with names of class variables defined in metaclasses" do
+ ModuleSpecs::CVars.class_variables.should include(:@@cls, :@@meta)
+ end
+
+ it "does not return class variables defined in extended modules" do
+ c = Class.new
+ c.extend ModuleSpecs::MVars
+ c.class_variables.should_not include(:@@mvar)
+ end
+
+ it "returns the correct class variables when inherit is given" do
+ ModuleSpecs::SubCVars.class_variables(false).should == [:@@sub]
+ ModuleSpecs::SubCVars.new.singleton_class.class_variables(false).should == []
+
+ ModuleSpecs::SubCVars.class_variables(true).should == [:@@sub, :@@cls, :@@meta]
+ ModuleSpecs::SubCVars.new.singleton_class.class_variables(true).should == [:@@sub, :@@cls, :@@meta]
+ end
+end
diff --git a/spec/ruby/core/module/comparison_spec.rb b/spec/ruby/core/module/comparison_spec.rb
new file mode 100644
index 0000000000..86ee5db22a
--- /dev/null
+++ b/spec/ruby/core/module/comparison_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#<=>" do
+ it "returns -1 if self is a subclass of or includes the given module" do
+ (ModuleSpecs::Child <=> ModuleSpecs::Parent).should == -1
+ (ModuleSpecs::Child <=> ModuleSpecs::Basic).should == -1
+ (ModuleSpecs::Child <=> ModuleSpecs::Super).should == -1
+ (ModuleSpecs::Super <=> ModuleSpecs::Basic).should == -1
+ end
+
+ it "returns 0 if self is the same as the given module" do
+ (ModuleSpecs::Child <=> ModuleSpecs::Child).should == 0
+ (ModuleSpecs::Parent <=> ModuleSpecs::Parent).should == 0
+ (ModuleSpecs::Basic <=> ModuleSpecs::Basic).should == 0
+ (ModuleSpecs::Super <=> ModuleSpecs::Super).should == 0
+ end
+
+ it "returns +1 if self is a superclass of or included by the given module" do
+ (ModuleSpecs::Parent <=> ModuleSpecs::Child).should == +1
+ (ModuleSpecs::Basic <=> ModuleSpecs::Child).should == +1
+ (ModuleSpecs::Super <=> ModuleSpecs::Child).should == +1
+ (ModuleSpecs::Basic <=> ModuleSpecs::Super).should == +1
+ end
+
+ it "returns nil if self and the given module are not related" do
+ (ModuleSpecs::Parent <=> ModuleSpecs::Basic).should == nil
+ (ModuleSpecs::Parent <=> ModuleSpecs::Super).should == nil
+ (ModuleSpecs::Basic <=> ModuleSpecs::Parent).should == nil
+ (ModuleSpecs::Super <=> ModuleSpecs::Parent).should == nil
+ end
+
+ it "returns nil if the argument is not a class/module" do
+ (ModuleSpecs::Parent <=> mock('x')).should == nil
+ end
+end
diff --git a/spec/ruby/core/module/const_added_spec.rb b/spec/ruby/core/module/const_added_spec.rb
new file mode 100644
index 0000000000..31ac6eb105
--- /dev/null
+++ b/spec/ruby/core/module/const_added_spec.rb
@@ -0,0 +1,125 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#const_added" do
+ ruby_version_is "3.2" do
+ it "is a private instance method" do
+ Module.should have_private_instance_method(:const_added)
+ end
+
+ it "returns nil in the default implementation" do
+ Module.new do
+ const_added(:TEST).should == nil
+ end
+ end
+
+ it "is called when a new constant is assigned on self" do
+ ScratchPad.record []
+
+ mod = Module.new do
+ def self.const_added(name)
+ ScratchPad << name
+ end
+ end
+
+ mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
+ TEST = 1
+ RUBY
+
+ ScratchPad.recorded.should == [:TEST]
+ end
+
+ it "is called when a new constant is assigned on self through const_set" do
+ ScratchPad.record []
+
+ mod = Module.new do
+ def self.const_added(name)
+ ScratchPad << name
+ end
+ end
+
+ mod.const_set(:TEST, 1)
+
+ ScratchPad.recorded.should == [:TEST]
+ end
+
+ it "is called when a new module is defined under self" do
+ ScratchPad.record []
+
+ mod = Module.new do
+ def self.const_added(name)
+ ScratchPad << name
+ end
+ end
+
+ mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
+ module SubModule
+ end
+
+ module SubModule
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [:SubModule]
+ end
+
+ it "is called when a new class is defined under self" do
+ ScratchPad.record []
+
+ mod = Module.new do
+ def self.const_added(name)
+ ScratchPad << name
+ end
+ end
+
+ mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
+ class SubClass
+ end
+
+ class SubClass
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [:SubClass]
+ end
+
+ it "is called when an autoload is defined" do
+ ScratchPad.record []
+
+ mod = Module.new do
+ def self.const_added(name)
+ ScratchPad << name
+ end
+ end
+
+ mod.autoload :Autoload, "foo"
+ ScratchPad.recorded.should == [:Autoload]
+ end
+
+ it "is called with a precise caller location with the line of definition" do
+ ScratchPad.record []
+
+ mod = Module.new do
+ def self.const_added(name)
+ location = caller_locations(1, 1)[0]
+ ScratchPad << location.lineno
+ end
+ end
+
+ line = __LINE__
+ mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
+ TEST = 1
+
+ module SubModule
+ end
+
+ class SubClass
+ end
+ RUBY
+
+ mod.const_set(:CONST_SET, 1)
+
+ ScratchPad.recorded.should == [line + 2, line + 4, line + 7, line + 11]
+ end
+ end
+end
diff --git a/spec/ruby/core/module/const_defined_spec.rb b/spec/ruby/core/module/const_defined_spec.rb
new file mode 100644
index 0000000000..0c15629c08
--- /dev/null
+++ b/spec/ruby/core/module/const_defined_spec.rb
@@ -0,0 +1,154 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+require_relative '../../fixtures/constants'
+require_relative 'fixtures/constant_unicode'
+
+describe "Module#const_defined?" do
+ it "returns true if the given Symbol names a constant defined in the receiver" do
+ ConstantSpecs.const_defined?(:CS_CONST2).should == true
+ ConstantSpecs.const_defined?(:ModuleA).should == true
+ ConstantSpecs.const_defined?(:ClassA).should == true
+ ConstantSpecs::ContainerA.const_defined?(:ChildA).should == true
+ end
+
+ it "returns true if the constant is defined in the receiver's superclass" do
+ # CS_CONST4 is defined in the superclass of ChildA
+ ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4).should be_true
+ end
+
+ it "returns true if the constant is defined in a mixed-in module of the receiver's parent" do
+ # CS_CONST10 is defined in a module included by ChildA
+ ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST10).should be_true
+ end
+
+ it "returns true if the constant is defined in a mixed-in module (with prepends) of the receiver" do
+ # CS_CONST11 is defined in the module included by ContainerPrepend
+ ConstantSpecs::ContainerPrepend.const_defined?(:CS_CONST11).should be_true
+ end
+
+ it "returns true if the constant is defined in Object and the receiver is a module" do
+ # CS_CONST1 is defined in Object
+ ConstantSpecs::ModuleA.const_defined?(:CS_CONST1).should be_true
+ end
+
+ it "returns true if the constant is defined in Object and the receiver is a class that has Object among its ancestors" do
+ # CS_CONST1 is defined in Object
+ ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST1).should be_true
+ end
+
+ it "returns false if the constant is defined in the receiver's superclass and the inherit flag is false" do
+ ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, false).should be_false
+ end
+
+ it "returns true if the constant is defined in the receiver's superclass and the inherit flag is true" do
+ ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, true).should be_true
+ end
+
+ it "coerces the inherit flag to a boolean" do
+ ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, nil).should be_false
+ ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, :true).should be_true
+ end
+
+ it "returns true if the given String names a constant defined in the receiver" do
+ ConstantSpecs.const_defined?("CS_CONST2").should == true
+ ConstantSpecs.const_defined?("ModuleA").should == true
+ ConstantSpecs.const_defined?("ClassA").should == true
+ ConstantSpecs::ContainerA.const_defined?("ChildA").should == true
+ end
+
+ it "returns true when passed a constant name with unicode characters" do
+ ConstantUnicodeSpecs.const_defined?("CS_CONSTλ").should be_true
+ end
+
+ it "returns true when passed a constant name with EUC-JP characters" do
+ str = "CS_CONSTλ".encode("euc-jp")
+ ConstantSpecs.const_set str, 1
+ ConstantSpecs.const_defined?(str).should be_true
+ end
+
+ it "returns false if the constant is not defined in the receiver, its superclass, or any included modules" do
+ # The following constant isn't defined at all.
+ ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4726).should be_false
+ # DETACHED_CONSTANT is defined in ConstantSpecs::Detached, which isn't
+ # included by or inherited from ParentA
+ ConstantSpecs::ParentA.const_defined?(:DETACHED_CONSTANT).should be_false
+ end
+
+ it "does not call #const_missing if the constant is not defined in the receiver" do
+ ConstantSpecs::ClassA.should_not_receive(:const_missing)
+ ConstantSpecs::ClassA.const_defined?(:CS_CONSTX).should == false
+ end
+
+ it "calls #to_str to convert the given name to a String" do
+ name = mock("ClassA")
+ name.should_receive(:to_str).and_return("ClassA")
+ ConstantSpecs.const_defined?(name).should == true
+ end
+
+ it "special cases Object and checks it's included Modules" do
+ Object.const_defined?(:CS_CONST10).should be_true
+ end
+
+ it "returns true for toplevel constant when the name begins with '::'" do
+ ConstantSpecs.const_defined?("::Array").should be_true
+ end
+
+ it "returns true when passed a scoped constant name" do
+ ConstantSpecs.const_defined?("ClassC::CS_CONST1").should be_true
+ end
+
+ it "returns true when passed a scoped constant name for a constant in the inheritance hierarchy and the inherited flag is default" do
+ ConstantSpecs::ClassD.const_defined?("ClassE::CS_CONST2").should be_true
+ end
+
+ it "returns true when passed a scoped constant name for a constant in the inheritance hierarchy and the inherited flag is true" do
+ ConstantSpecs::ClassD.const_defined?("ClassE::CS_CONST2", true).should be_true
+ end
+
+ it "returns false when passed a scoped constant name for a constant in the inheritance hierarchy and the inherited flag is false" do
+ ConstantSpecs::ClassD.const_defined?("ClassE::CS_CONST2", false).should be_false
+ end
+
+ it "returns false when the name begins with '::' and the toplevel constant does not exist" do
+ ConstantSpecs.const_defined?("::Name").should be_false
+ end
+
+ it "raises a NameError if the name does not start with a capital letter" do
+ -> { ConstantSpecs.const_defined? "name" }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name starts with '_'" do
+ -> { ConstantSpecs.const_defined? "__CONSTX__" }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name starts with '@'" do
+ -> { ConstantSpecs.const_defined? "@Name" }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name starts with '!'" do
+ -> { ConstantSpecs.const_defined? "!Name" }.should raise_error(NameError)
+ end
+
+ it "returns true or false for the nested name" do
+ ConstantSpecs.const_defined?("NotExist::Name").should == false
+ ConstantSpecs.const_defined?("::Name").should == false
+ ConstantSpecs.const_defined?("::Object").should == true
+ ConstantSpecs.const_defined?("ClassA::CS_CONST10").should == true
+ ConstantSpecs.const_defined?("ClassA::CS_CONST10_").should == false
+ end
+
+ it "raises a NameError if the name contains non-alphabetic characters except '_'" do
+ ConstantSpecs.const_defined?("CS_CONSTX").should == false
+ -> { ConstantSpecs.const_defined? "Name=" }.should raise_error(NameError)
+ -> { ConstantSpecs.const_defined? "Name?" }.should raise_error(NameError)
+ end
+
+ it "raises a TypeError if conversion to a String by calling #to_str fails" do
+ name = mock('123')
+ -> { ConstantSpecs.const_defined? name }.should raise_error(TypeError)
+
+ name.should_receive(:to_str).and_return(123)
+ -> { ConstantSpecs.const_defined? name }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/module/const_get_spec.rb b/spec/ruby/core/module/const_get_spec.rb
new file mode 100644
index 0000000000..69f181cf51
--- /dev/null
+++ b/spec/ruby/core/module/const_get_spec.rb
@@ -0,0 +1,251 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/constants'
+require_relative 'fixtures/constants_autoload'
+
+describe "Module#const_get" do
+ it "accepts a String or Symbol name" do
+ Object.const_get(:CS_CONST1).should == :const1
+ Object.const_get("CS_CONST1").should == :const1
+ end
+
+ it "raises a NameError if no constant is defined in the search path" do
+ -> { ConstantSpecs.const_get :CS_CONSTX }.should raise_error(NameError)
+ end
+
+ it "raises a NameError with the not found constant symbol" do
+ error_inspection = -> e { e.name.should == :CS_CONSTX }
+ -> { ConstantSpecs.const_get :CS_CONSTX }.should raise_error(NameError, &error_inspection)
+ end
+
+ it "raises a NameError if the name does not start with a capital letter" do
+ -> { ConstantSpecs.const_get "name" }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name starts with a non-alphabetic character" do
+ -> { ConstantSpecs.const_get "__CONSTX__" }.should raise_error(NameError)
+ -> { ConstantSpecs.const_get "@CS_CONST1" }.should raise_error(NameError)
+ -> { ConstantSpecs.const_get "!CS_CONST1" }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name contains non-alphabetic characters except '_'" do
+ Object.const_get("CS_CONST1").should == :const1
+ -> { ConstantSpecs.const_get "CS_CONST1=" }.should raise_error(NameError)
+ -> { ConstantSpecs.const_get "CS_CONST1?" }.should raise_error(NameError)
+ end
+
+ it "calls #to_str to convert the given name to a String" do
+ name = mock("ClassA")
+ name.should_receive(:to_str).and_return("ClassA")
+ ConstantSpecs.const_get(name).should == ConstantSpecs::ClassA
+ end
+
+ it "raises a TypeError if conversion to a String by calling #to_str fails" do
+ name = mock('123')
+ -> { ConstantSpecs.const_get(name) }.should raise_error(TypeError)
+
+ name.should_receive(:to_str).and_return(123)
+ -> { ConstantSpecs.const_get(name) }.should raise_error(TypeError)
+ end
+
+ it "calls #const_missing on the receiver if unable to locate the constant" do
+ ConstantSpecs::ContainerA.should_receive(:const_missing).with(:CS_CONSTX)
+ ConstantSpecs::ContainerA.const_get(:CS_CONSTX)
+ end
+
+ it "does not search the singleton class of a Class or Module" do
+ -> do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST14)
+ end.should raise_error(NameError)
+ -> { ConstantSpecs.const_get(:CS_CONST14) }.should raise_error(NameError)
+ end
+
+ it "does not search the containing scope" do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST20).should == :const20_2
+ -> do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST5)
+ end.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the constant is defined in the receiver's superclass and the inherit flag is false" do
+ -> do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST4, false)
+ end.should raise_error(NameError)
+ end
+
+ it "searches into the receiver superclasses if the inherit flag is true" do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST4, true).should == :const4
+ end
+
+ it "raises a NameError when the receiver is a Module, the constant is defined at toplevel and the inherit flag is false" do
+ -> do
+ ConstantSpecs::ModuleA.const_get(:CS_CONST1, false)
+ end.should raise_error(NameError)
+ end
+
+ it "raises a NameError when the receiver is a Class, the constant is defined at toplevel and the inherit flag is false" do
+ -> do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST1, false)
+ end.should raise_error(NameError)
+ end
+
+ it "coerces the inherit flag to a boolean" do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST4, :true).should == :const4
+
+ -> do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST1, nil)
+ end.should raise_error(NameError)
+ end
+
+ it "accepts a toplevel scope qualifier" do
+ ConstantSpecs.const_get("::CS_CONST1").should == :const1
+ end
+
+ it "accepts a toplevel scope qualifier when inherit is false" do
+ ConstantSpecs.const_get("::CS_CONST1", false).should == :const1
+ -> { ConstantSpecs.const_get("CS_CONST1", false) }.should raise_error(NameError)
+ end
+
+ it "returns a constant whose module is defined the the toplevel" do
+ ConstantSpecs.const_get("ConstantSpecsTwo::Foo").should == :cs_two_foo
+ ConstantSpecsThree.const_get("ConstantSpecsTwo::Foo").should == :cs_three_foo
+ end
+
+ it "accepts a scoped constant name" do
+ ConstantSpecs.const_get("ClassA::CS_CONST10").should == :const10_10
+ end
+
+ it "raises a NameError if the name includes two successive scope separators" do
+ -> { ConstantSpecs.const_get("ClassA::::CS_CONST10") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if only '::' is passed" do
+ -> { ConstantSpecs.const_get("::") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if a Symbol has a toplevel scope qualifier" do
+ -> { ConstantSpecs.const_get(:'::CS_CONST1') }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if a Symbol is a scoped constant name" do
+ -> { ConstantSpecs.const_get(:'ClassA::CS_CONST10') }.should raise_error(NameError)
+ end
+
+ it "does read private constants" do
+ ConstantSpecs.const_get(:CS_PRIVATE).should == :cs_private
+ end
+
+ it 'does autoload a constant' do
+ Object.const_get('CSAutoloadA').name.should == 'CSAutoloadA'
+ end
+
+ it 'does autoload a constant with a toplevel scope qualifier' do
+ Object.const_get('::CSAutoloadB').name.should == 'CSAutoloadB'
+ end
+
+ it 'does autoload a module and resolve a constant within' do
+ Object.const_get('CSAutoloadC::CONST').should == 7
+ end
+
+ it 'does autoload a non-toplevel module' do
+ Object.const_get('CSAutoloadD::InnerModule').name.should == 'CSAutoloadD::InnerModule'
+ end
+
+ it "raises a NameError when the nested constant does not exist on the module but exists in Object" do
+ -> { Object.const_get('ConstantSpecs::CS_CONST1') }.should raise_error(NameError)
+ end
+
+ describe "with statically assigned constants" do
+ it "searches the immediate class or module first" do
+ ConstantSpecs::ClassA.const_get(:CS_CONST10).should == :const10_10
+ ConstantSpecs::ModuleA.const_get(:CS_CONST10).should == :const10_1
+ ConstantSpecs::ParentA.const_get(:CS_CONST10).should == :const10_5
+ ConstantSpecs::ContainerA.const_get(:CS_CONST10).should == :const10_2
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST10).should == :const10_3
+ end
+
+ it "searches a module included in the immediate class before the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST15).should == :const15_1
+ end
+
+ it "searches the superclass before a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST11).should == :const11_1
+ end
+
+ it "searches a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST12).should == :const12_1
+ end
+
+ it "searches the superclass chain" do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST13).should == :const13
+ end
+
+ it "returns a toplevel constant when the receiver is a Class" do
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST1).should == :const1
+ end
+
+ it "returns a toplevel constant when the receiver is a Module" do
+ ConstantSpecs.const_get(:CS_CONST1).should == :const1
+ ConstantSpecs::ModuleA.const_get(:CS_CONST1).should == :const1
+ end
+ end
+
+ describe "with dynamically assigned constants" do
+ it "searches the immediate class or module first" do
+ ConstantSpecs::ClassA::CS_CONST301 = :const301_1
+ ConstantSpecs::ClassA.const_get(:CS_CONST301).should == :const301_1
+
+ ConstantSpecs::ModuleA::CS_CONST301 = :const301_2
+ ConstantSpecs::ModuleA.const_get(:CS_CONST301).should == :const301_2
+
+ ConstantSpecs::ParentA::CS_CONST301 = :const301_3
+ ConstantSpecs::ParentA.const_get(:CS_CONST301).should == :const301_3
+
+ ConstantSpecs::ContainerA::ChildA::CS_CONST301 = :const301_5
+ ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST301).should == :const301_5
+ end
+
+ it "searches a module included in the immediate class before the superclass" do
+ ConstantSpecs::ParentB::CS_CONST302 = :const302_1
+ ConstantSpecs::ModuleF::CS_CONST302 = :const302_2
+ ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST302).should == :const302_2
+ end
+
+ it "searches the superclass before a module included in the superclass" do
+ ConstantSpecs::ModuleE::CS_CONST303 = :const303_1
+ ConstantSpecs::ParentB::CS_CONST303 = :const303_2
+ ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST303).should == :const303_2
+ end
+
+ it "searches a module included in the superclass" do
+ ConstantSpecs::ModuleA::CS_CONST304 = :const304_1
+ ConstantSpecs::ModuleE::CS_CONST304 = :const304_2
+ ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST304).should == :const304_2
+ end
+
+ it "searches the superclass chain" do
+ ConstantSpecs::ModuleA::CS_CONST305 = :const305
+ ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST305).should == :const305
+ end
+
+ it "returns a toplevel constant when the receiver is a Class" do
+ Object::CS_CONST306 = :const306
+ ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST306).should == :const306
+ end
+
+ it "returns a toplevel constant when the receiver is a Module" do
+ Object::CS_CONST308 = :const308
+ ConstantSpecs.const_get(:CS_CONST308).should == :const308
+ ConstantSpecs::ModuleA.const_get(:CS_CONST308).should == :const308
+ end
+
+ it "returns the updated value of a constant" do
+ ConstantSpecs::ClassB::CS_CONST309 = :const309_1
+ ConstantSpecs::ClassB.const_get(:CS_CONST309).should == :const309_1
+
+ -> {
+ ConstantSpecs::ClassB::CS_CONST309 = :const309_2
+ }.should complain(/already initialized constant/)
+ ConstantSpecs::ClassB.const_get(:CS_CONST309).should == :const309_2
+ end
+ end
+end
diff --git a/spec/ruby/core/module/const_missing_spec.rb b/spec/ruby/core/module/const_missing_spec.rb
new file mode 100644
index 0000000000..742218281c
--- /dev/null
+++ b/spec/ruby/core/module/const_missing_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/constants'
+
+describe "Module#const_missing" do
+ it "is called when an undefined constant is referenced via literal form" do
+ ConstantSpecs::ClassA::CS_CONSTX.should == :CS_CONSTX
+ end
+
+ it "is called when an undefined constant is referenced via #const_get" do
+ ConstantSpecs::ClassA.const_get(:CS_CONSTX).should == :CS_CONSTX
+ end
+
+ it "raises NameError and includes the name of the value that wasn't found" do
+ -> {
+ ConstantSpecs.const_missing("HelloMissing")
+ }.should raise_error(NameError, /ConstantSpecs::HelloMissing/)
+ end
+
+ it "raises NameError and does not include toplevel Object" do
+ begin
+ Object.const_missing("HelloMissing")
+ rescue NameError => e
+ e.message.should_not =~ / Object::/
+ end
+ end
+
+ it "is called regardless of visibility" do
+ klass = Class.new do
+ def self.const_missing(name)
+ "Found:#{name}"
+ end
+ private_class_method :const_missing
+ end
+ klass::Hello.should == 'Found:Hello'
+ end
+end
diff --git a/spec/ruby/core/module/const_set_spec.rb b/spec/ruby/core/module/const_set_spec.rb
new file mode 100644
index 0000000000..ba7810d17b
--- /dev/null
+++ b/spec/ruby/core/module/const_set_spec.rb
@@ -0,0 +1,142 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/constants'
+
+describe "Module#const_set" do
+ it "sets the constant specified by a String or Symbol to the given value" do
+ ConstantSpecs.const_set :CS_CONST401, :const401
+ ConstantSpecs::CS_CONST401.should == :const401
+
+ ConstantSpecs.const_set "CS_CONST402", :const402
+ ConstantSpecs.const_get(:CS_CONST402).should == :const402
+ end
+
+ it "returns the value set" do
+ ConstantSpecs.const_set(:CS_CONST403, :const403).should == :const403
+ end
+
+ it "sets the name of an anonymous module" do
+ m = Module.new
+ ConstantSpecs.const_set(:CS_CONST1000, m)
+ m.name.should == "ConstantSpecs::CS_CONST1000"
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "does not set the name of a module scoped by an anonymous module" do
+ a, b = Module.new, Module.new
+ a.const_set :B, b
+ b.name.should be_nil
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "sets the name of a module scoped by an anonymous module" do
+ a, b = Module.new, Module.new
+ a.const_set :B, b
+ b.name.should.end_with? '::B'
+ end
+ end
+
+ it "sets the name of contained modules when assigning a toplevel anonymous module" do
+ a, b, c, d = Module.new, Module.new, Module.new, Module.new
+ a::B = b
+ a::B::C = c
+ a::B::C::E = c
+ a::D = d
+
+ Object.const_set :ModuleSpecs_CS3, a
+ a.name.should == "ModuleSpecs_CS3"
+ b.name.should == "ModuleSpecs_CS3::B"
+ c.name.should == "ModuleSpecs_CS3::B::C"
+ d.name.should == "ModuleSpecs_CS3::D"
+ end
+
+ it "raises a NameError if the name does not start with a capital letter" do
+ -> { ConstantSpecs.const_set "name", 1 }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name starts with a non-alphabetic character" do
+ -> { ConstantSpecs.const_set "__CONSTX__", 1 }.should raise_error(NameError)
+ -> { ConstantSpecs.const_set "@Name", 1 }.should raise_error(NameError)
+ -> { ConstantSpecs.const_set "!Name", 1 }.should raise_error(NameError)
+ -> { ConstantSpecs.const_set "::Name", 1 }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name contains non-alphabetic characters except '_'" do
+ ConstantSpecs.const_set("CS_CONST404", :const404).should == :const404
+ -> { ConstantSpecs.const_set "Name=", 1 }.should raise_error(NameError)
+ -> { ConstantSpecs.const_set "Name?", 1 }.should raise_error(NameError)
+ end
+
+ it "calls #to_str to convert the given name to a String" do
+ name = mock("CS_CONST405")
+ name.should_receive(:to_str).and_return("CS_CONST405")
+ ConstantSpecs.const_set(name, :const405).should == :const405
+ ConstantSpecs::CS_CONST405.should == :const405
+ end
+
+ it "raises a TypeError if conversion to a String by calling #to_str fails" do
+ name = mock('123')
+ -> { ConstantSpecs.const_set name, 1 }.should raise_error(TypeError)
+
+ name.should_receive(:to_str).and_return(123)
+ -> { ConstantSpecs.const_set name, 1 }.should raise_error(TypeError)
+ end
+
+ describe "when overwriting an existing constant" do
+ it "warns if the previous value was a normal value" do
+ mod = Module.new
+ mod.const_set :Foo, 42
+ -> {
+ mod.const_set :Foo, 1
+ }.should complain(/already initialized constant/)
+ mod.const_get(:Foo).should == 1
+ end
+
+ it "does not warn if the previous value was an autoload" do
+ mod = Module.new
+ mod.autoload :Foo, "not-existing"
+ -> {
+ mod.const_set :Foo, 1
+ }.should_not complain
+ mod.const_get(:Foo).should == 1
+ end
+
+ it "does not warn after a failed autoload" do
+ path = fixture(__FILE__, "autoload_o.rb")
+ ScratchPad.record []
+ mod = Module.new
+
+ mod.autoload :Foo, path
+ -> { mod::Foo }.should raise_error(NameError)
+
+ mod.const_defined?(:Foo).should == false
+ mod.autoload?(:Foo).should == nil
+
+ -> {
+ mod.const_set :Foo, 1
+ }.should_not complain
+ mod.const_get(:Foo).should == 1
+ end
+
+ it "does not warn if the new value is an autoload" do
+ mod = Module.new
+ mod.const_set :Foo, 42
+ -> {
+ mod.autoload :Foo, "not-existing"
+ }.should_not complain
+ mod.const_get(:Foo).should == 42
+ end
+ end
+
+ describe "on a frozen module" do
+ before :each do
+ @frozen = Module.new.freeze
+ @name = :Foo
+ end
+
+ it "raises a FrozenError before setting the name" do
+ -> { @frozen.const_set @name, nil }.should raise_error(FrozenError)
+ @frozen.should_not have_constant(@name)
+ end
+ end
+end
diff --git a/spec/ruby/core/module/const_source_location_spec.rb b/spec/ruby/core/module/const_source_location_spec.rb
new file mode 100644
index 0000000000..11a2e74756
--- /dev/null
+++ b/spec/ruby/core/module/const_source_location_spec.rb
@@ -0,0 +1,225 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/constants'
+
+describe "Module#const_source_location" do
+ before do
+ @constants_fixture_path = File.expand_path('../../fixtures/constants.rb', __dir__)
+ end
+
+ describe "with dynamically assigned constants" do
+ it "searches a path in the immediate class or module first" do
+ ConstantSpecs::ClassA::CSL_CONST301 = :const301_1
+ ConstantSpecs::ClassA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1]
+
+ ConstantSpecs::ModuleA::CSL_CONST301 = :const301_2
+ ConstantSpecs::ModuleA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1]
+
+ ConstantSpecs::ParentA::CSL_CONST301 = :const301_3
+ ConstantSpecs::ParentA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1]
+
+ ConstantSpecs::ContainerA::ChildA::CSL_CONST301 = :const301_5
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1]
+ end
+
+ it "searches a path in a module included in the immediate class before the superclass" do
+ ConstantSpecs::ParentB::CSL_CONST302 = :const302_1
+ ConstantSpecs::ModuleF::CSL_CONST302 = :const302_2
+ ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST302).should == [__FILE__, __LINE__ - 1]
+ end
+
+ it "searches a path in the superclass before a module included in the superclass" do
+ ConstantSpecs::ModuleE::CSL_CONST303 = :const303_1
+ ConstantSpecs::ParentB::CSL_CONST303 = :const303_2
+ ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST303).should == [__FILE__, __LINE__ - 1]
+ end
+
+ it "searches a path in a module included in the superclass" do
+ ConstantSpecs::ModuleA::CSL_CONST304 = :const304_1
+ ConstantSpecs::ModuleE::CSL_CONST304 = :const304_2
+ ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST304).should == [__FILE__, __LINE__ - 1]
+ end
+
+ it "searches a path in the superclass chain" do
+ ConstantSpecs::ModuleA::CSL_CONST305 = :const305
+ ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST305).should == [__FILE__, __LINE__ - 1]
+ end
+
+ it "returns path to a toplevel constant when the receiver is a Class" do
+ Object::CSL_CONST306 = :const306
+ ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST306).should == [__FILE__, __LINE__ - 1]
+ end
+
+ it "returns path to a toplevel constant when the receiver is a Module" do
+ Object::CSL_CONST308 = :const308
+ ConstantSpecs.const_source_location(:CSL_CONST308).should == [__FILE__, __LINE__ - 1]
+ ConstantSpecs::ModuleA.const_source_location(:CSL_CONST308).should == [__FILE__, __LINE__ - 2]
+ end
+
+ it "returns path to the updated value of a constant" do
+ ConstantSpecs::ClassB::CSL_CONST309 = :const309_1
+ ConstantSpecs::ClassB.const_source_location(:CSL_CONST309).should == [__FILE__, __LINE__ - 1]
+
+ -> {
+ ConstantSpecs::ClassB::CSL_CONST309 = :const309_2
+ }.should complain(/already initialized constant/)
+ ConstantSpecs::ClassB.const_source_location(:CSL_CONST309).should == [__FILE__, __LINE__ - 2]
+ end
+ end
+
+ describe "with statically assigned constants" do
+ it "searches location path the immediate class or module first" do
+ ConstantSpecs::ClassA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ClassA::CS_CONST10_LINE]
+ ConstantSpecs::ModuleA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ModuleA::CS_CONST10_LINE]
+ ConstantSpecs::ParentA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST10_LINE]
+ ConstantSpecs::ContainerA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ContainerA::CS_CONST10_LINE]
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ContainerA::ChildA::CS_CONST10_LINE]
+ end
+
+ it "searches location path a module included in the immediate class before the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST15).should == [@constants_fixture_path, ConstantSpecs::ModuleC::CS_CONST15_LINE]
+ end
+
+ it "searches location path the superclass before a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST11).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST11_LINE]
+ end
+
+ it "searches location path a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST12).should == [@constants_fixture_path, ConstantSpecs::ModuleB::CS_CONST12_LINE]
+ end
+
+ it "searches location path the superclass chain" do
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST13).should == [@constants_fixture_path, ConstantSpecs::ModuleA::CS_CONST13_LINE]
+ end
+
+ it "returns location path a toplevel constant when the receiver is a Class" do
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE]
+ end
+
+ it "returns location path a toplevel constant when the receiver is a Module" do
+ ConstantSpecs.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE]
+ ConstantSpecs::ModuleA.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE]
+ end
+ end
+
+ it "return empty path if constant defined in C code" do
+ Object.const_source_location(:String).should == []
+ end
+
+ it "accepts a String or Symbol name" do
+ Object.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE]
+ Object.const_source_location("CS_CONST1").should == [@constants_fixture_path, CS_CONST1_LINE]
+ end
+
+ it "returns nil if no constant is defined in the search path" do
+ ConstantSpecs.const_source_location(:CS_CONSTX).should == nil
+ end
+
+ it "raises a NameError if the name does not start with a capital letter" do
+ -> { ConstantSpecs.const_source_location "name" }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name starts with a non-alphabetic character" do
+ -> { ConstantSpecs.const_source_location "__CONSTX__" }.should raise_error(NameError)
+ -> { ConstantSpecs.const_source_location "@CS_CONST1" }.should raise_error(NameError)
+ -> { ConstantSpecs.const_source_location "!CS_CONST1" }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name contains non-alphabetic characters except '_'" do
+ Object.const_source_location("CS_CONST1").should == [@constants_fixture_path, CS_CONST1_LINE]
+ -> { ConstantSpecs.const_source_location "CS_CONST1=" }.should raise_error(NameError)
+ -> { ConstantSpecs.const_source_location "CS_CONST1?" }.should raise_error(NameError)
+ end
+
+ it "calls #to_str to convert the given name to a String" do
+ name = mock("ClassA")
+ name.should_receive(:to_str).and_return("ClassA")
+ ConstantSpecs.const_source_location(name).should == [@constants_fixture_path, ConstantSpecs::ClassA::CS_CLASS_A_LINE]
+ end
+
+ it "raises a TypeError if conversion to a String by calling #to_str fails" do
+ name = mock('123')
+ -> { ConstantSpecs.const_source_location(name) }.should raise_error(TypeError)
+
+ name.should_receive(:to_str).and_return(123)
+ -> { ConstantSpecs.const_source_location(name) }.should raise_error(TypeError)
+ end
+
+ it "does not search the singleton class of a Class or Module" do
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST14).should == nil
+ ConstantSpecs.const_source_location(:CS_CONST14).should == nil
+ end
+
+ it "does not search the containing scope" do
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST20).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST20_LINE]
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST5) == nil
+ end
+
+ it "returns nil if the constant is defined in the receiver's superclass and the inherit flag is false" do
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST4, false).should == nil
+ end
+
+ it "searches into the receiver superclasses if the inherit flag is true" do
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST4, true).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST4_LINE]
+ end
+
+ it "returns nil when the receiver is a Module, the constant is defined at toplevel and the inherit flag is false" do
+ ConstantSpecs::ModuleA.const_source_location(:CS_CONST1, false).should == nil
+ end
+
+ it "returns nil when the receiver is a Class, the constant is defined at toplevel and the inherit flag is false" do
+ ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST1, false).should == nil
+ end
+
+ it "accepts a toplevel scope qualifier" do
+ ConstantSpecs.const_source_location("::CS_CONST1").should == [@constants_fixture_path, CS_CONST1_LINE]
+ end
+
+ it "accepts a scoped constant name" do
+ ConstantSpecs.const_source_location("ClassA::CS_CONST10").should == [@constants_fixture_path, ConstantSpecs::ClassA::CS_CONST10_LINE]
+ end
+
+ it "returns updated location from const_set" do
+ mod = Module.new
+ const_line = __LINE__ + 1
+ mod.const_set :Foo, 1
+ mod.const_source_location(:Foo).should == [__FILE__, const_line]
+ end
+
+ it "raises a NameError if the name includes two successive scope separators" do
+ -> { ConstantSpecs.const_source_location("ClassA::::CS_CONST10") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if only '::' is passed" do
+ -> { ConstantSpecs.const_source_location("::") }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if a Symbol has a toplevel scope qualifier" do
+ -> { ConstantSpecs.const_source_location(:'::CS_CONST1') }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if a Symbol is a scoped constant name" do
+ -> { ConstantSpecs.const_source_location(:'ClassA::CS_CONST10') }.should raise_error(NameError)
+ end
+
+ it "does search private constants path" do
+ ConstantSpecs.const_source_location(:CS_PRIVATE).should == [@constants_fixture_path, ConstantSpecs::CS_PRIVATE_LINE]
+ end
+
+ context 'autoload' do
+ before :all do
+ ConstantSpecs.autoload :CSL_CONST1, "#{__dir__}/notexisting.rb"
+ @line = __LINE__ - 1
+ end
+
+ it 'returns the autoload location while not resolved' do
+ ConstantSpecs.const_source_location('CSL_CONST1').should == [__FILE__, @line]
+ end
+
+ it 'returns where the constant was resolved when resolved' do
+ file = fixture(__FILE__, 'autoload_location.rb')
+ ConstantSpecs.autoload :CONST_LOCATION, file
+ line = ConstantSpecs::CONST_LOCATION
+ ConstantSpecs.const_source_location('CONST_LOCATION').should == [file, line]
+ end
+ end
+end
diff --git a/spec/ruby/core/module/constants_spec.rb b/spec/ruby/core/module/constants_spec.rb
new file mode 100644
index 0000000000..330da1cc88
--- /dev/null
+++ b/spec/ruby/core/module/constants_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/constants'
+require_relative 'fixtures/classes'
+
+describe "Module.constants" do
+ it "returns an array of the names of all toplevel constants" do
+ count = Module.constants.size
+ module ConstantSpecsAdded
+ end
+ Module.constants.size.should == count + 1
+ Object.send(:remove_const, :ConstantSpecsAdded)
+ end
+
+ it "returns an array of Symbol names" do
+ # This in NOT an exhaustive list
+ Module.constants.should include(:Array, :Class, :Comparable, :Dir,
+ :Enumerable, :ENV, :Exception, :FalseClass,
+ :File, :Float, :Hash, :Integer, :IO,
+ :Kernel, :Math, :Method, :Module, :NilClass,
+ :Numeric, :Object, :Range, :Regexp, :String,
+ :Symbol, :Thread, :Time, :TrueClass)
+ end
+
+ it "returns Module's constants when given a parameter" do
+ direct = Module.constants(false)
+ indirect = Module.constants(true)
+ module ConstantSpecsIncludedModule
+ MODULE_CONSTANTS_SPECS_INDIRECT = :foo
+ end
+
+ class Module
+ MODULE_CONSTANTS_SPECS_DIRECT = :bar
+ include ConstantSpecsIncludedModule
+ end
+ (Module.constants(false) - direct).should == [:MODULE_CONSTANTS_SPECS_DIRECT]
+ (Module.constants(true) - indirect).sort.should == [:MODULE_CONSTANTS_SPECS_DIRECT, :MODULE_CONSTANTS_SPECS_INDIRECT]
+
+ Module.send(:remove_const, :MODULE_CONSTANTS_SPECS_DIRECT)
+ ConstantSpecsIncludedModule.send(:remove_const, :MODULE_CONSTANTS_SPECS_INDIRECT)
+ end
+end
+
+describe "Module#constants" do
+ it "returns an array of Symbol names of all constants defined in the module and all included modules" do
+ ConstantSpecs::ContainerA.constants.sort.should == [
+ :CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA
+ ]
+ end
+
+ it "returns all constants including inherited when passed true" do
+ ConstantSpecs::ContainerA.constants(true).sort.should == [
+ :CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA
+ ]
+ end
+
+ it "returns all constants including inherited when passed some object" do
+ ConstantSpecs::ContainerA.constants(Object.new).sort.should == [
+ :CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA
+ ]
+ end
+
+ it "doesn't returns inherited constants when passed false" do
+ ConstantSpecs::ContainerA.constants(false).sort.should == [
+ :CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST5, :ChildA
+ ]
+ end
+
+ it "doesn't returns inherited constants when passed nil" do
+ ConstantSpecs::ContainerA.constants(nil).sort.should == [
+ :CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST5, :ChildA
+ ]
+ end
+
+ it "returns only public constants" do
+ ModuleSpecs::PrivConstModule.constants.should == [:PUBLIC_CONSTANT]
+ end
+
+ it "returns only constants starting with an uppercase letter" do
+ # e.g. fatal, IO::generic_readable and IO::generic_writable should not be returned by Module#constants
+ Object.constants.each { |c| c[0].should == c[0].upcase }
+ IO.constants.each { |c| c[0].should == c[0].upcase }
+ end
+end
+
+describe "Module#constants" do
+ before :each do
+ ConstantSpecs::ModuleM::CS_CONST251 = :const251
+ end
+
+ after :each do
+ ConstantSpecs::ModuleM.send(:remove_const, :CS_CONST251)
+ end
+
+ it "includes names of constants defined after a module is included" do
+ ConstantSpecs::ContainerA.constants.should include(:CS_CONST251)
+ end
+end
diff --git a/spec/ruby/core/module/define_method_spec.rb b/spec/ruby/core/module/define_method_spec.rb
new file mode 100644
index 0000000000..ce94436bfd
--- /dev/null
+++ b/spec/ruby/core/module/define_method_spec.rb
@@ -0,0 +1,805 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+class DefineMethodSpecClass
+end
+
+describe "passed { |a, b = 1| } creates a method that" do
+ before :each do
+ @klass = Class.new do
+ define_method(:m) { |a, b = 1| return a, b }
+ end
+ end
+
+ it "raises an ArgumentError when passed zero arguments" do
+ -> { @klass.new.m }.should raise_error(ArgumentError)
+ end
+
+ it "has a default value for b when passed one argument" do
+ @klass.new.m(1).should == [1, 1]
+ end
+
+ it "overrides the default argument when passed two arguments" do
+ @klass.new.m(1, 2).should == [1, 2]
+ end
+
+ it "raises an ArgumentError when passed three arguments" do
+ -> { @klass.new.m(1, 2, 3) }.should raise_error(ArgumentError)
+ end
+end
+
+describe "Module#define_method when given an UnboundMethod" do
+ it "passes the given arguments to the new method" do
+ klass = Class.new do
+ def test_method(arg1, arg2)
+ [arg1, arg2]
+ end
+ define_method(:another_test_method, instance_method(:test_method))
+ end
+
+ klass.new.another_test_method(1, 2).should == [1, 2]
+ end
+
+ it "adds the new method to the methods list" do
+ klass = Class.new do
+ def test_method(arg1, arg2)
+ [arg1, arg2]
+ end
+ define_method(:another_test_method, instance_method(:test_method))
+ end
+ klass.new.should have_method(:another_test_method)
+ end
+
+ describe "defining a method on a singleton class" do
+ before do
+ klass = Class.new
+ class << klass
+ def test_method
+ :foo
+ end
+ end
+ child = Class.new(klass)
+ sc = class << child; self; end
+ sc.send :define_method, :another_test_method, klass.method(:test_method).unbind
+
+ @class = child
+ end
+
+ it "doesn't raise TypeError when calling the method" do
+ @class.another_test_method.should == :foo
+ end
+ end
+
+ it "sets the new method's visibility to the current frame's visibility" do
+ foo = Class.new do
+ def ziggy
+ 'piggy'
+ end
+ private :ziggy
+
+ # make sure frame visibility is public
+ public
+
+ define_method :piggy, instance_method(:ziggy)
+ end
+
+ -> { foo.new.ziggy }.should raise_error(NoMethodError)
+ foo.new.piggy.should == 'piggy'
+ end
+end
+
+describe "Module#define_method" do
+ describe "when the default definee is not the same as the module" do
+ it "sets the visibility of the method to public" do
+ klass = Class.new
+ class << klass
+ private
+ define_method(:meta) do
+ define_method(:foo) { :foo }
+ end
+ end
+
+ klass.send :meta
+ klass.new.foo.should == :foo
+ end
+ end
+end
+
+describe "Module#define_method when name is not a special private name" do
+ describe "given an UnboundMethod" do
+ describe "and called from the target module" do
+ it "sets the visibility of the method to the current visibility" do
+ klass = Class.new do
+ define_method(:bar, ModuleSpecs::EmptyFooMethod)
+ private
+ define_method(:baz, ModuleSpecs::EmptyFooMethod)
+ end
+
+ klass.should have_public_instance_method(:bar)
+ klass.should have_private_instance_method(:baz)
+ end
+ end
+
+ describe "and called from another module" do
+ it "sets the visibility of the method to public" do
+ klass = Class.new
+ Class.new do
+ klass.send(:define_method, :bar, ModuleSpecs::EmptyFooMethod)
+ private
+ klass.send(:define_method, :baz, ModuleSpecs::EmptyFooMethod)
+ end
+
+ klass.should have_public_instance_method(:bar)
+ klass.should have_public_instance_method(:baz)
+ end
+ end
+ end
+
+ describe "passed a block" do
+ describe "and called from the target module" do
+ it "sets the visibility of the method to the current visibility" do
+ klass = Class.new do
+ define_method(:bar) {}
+ private
+ define_method(:baz) {}
+ end
+
+ klass.should have_public_instance_method(:bar)
+ klass.should have_private_instance_method(:baz)
+ end
+ end
+
+ describe "and called from another module" do
+ it "sets the visibility of the method to public" do
+ klass = Class.new
+ Class.new do
+ klass.send(:define_method, :bar) {}
+ private
+ klass.send(:define_method, :baz) {}
+ end
+
+ klass.should have_public_instance_method(:bar)
+ klass.should have_public_instance_method(:baz)
+ end
+ end
+ end
+end
+
+describe "Module#define_method when name is :initialize" do
+ describe "passed a block" do
+ it "sets visibility to private when method name is :initialize" do
+ klass = Class.new do
+ define_method(:initialize) { }
+ end
+ klass.should have_private_instance_method(:initialize)
+ end
+ end
+
+ describe "given an UnboundMethod" do
+ it "sets the visibility to private when method is named :initialize" do
+ klass = Class.new do
+ def test_method
+ end
+ define_method(:initialize, instance_method(:test_method))
+ end
+ klass.should have_private_instance_method(:initialize)
+ end
+ end
+end
+
+describe "Module#define_method" do
+ it "defines the given method as an instance method with the given name in self" do
+ class DefineMethodSpecClass
+ def test1
+ "test"
+ end
+ define_method(:another_test, instance_method(:test1))
+ end
+
+ o = DefineMethodSpecClass.new
+ o.test1.should == o.another_test
+ end
+
+ it "calls #method_added after the method is added to the Module" do
+ DefineMethodSpecClass.should_receive(:method_added).with(:test_ma)
+
+ class DefineMethodSpecClass
+ define_method(:test_ma) { true }
+ end
+ end
+
+ it "defines a new method with the given name and the given block as body in self" do
+ class DefineMethodSpecClass
+ define_method(:block_test1) { self }
+ define_method(:block_test2, &-> { self })
+ end
+
+ o = DefineMethodSpecClass.new
+ o.block_test1.should == o
+ o.block_test2.should == o
+ end
+
+ it "raises TypeError if name cannot converted to String" do
+ -> {
+ Class.new { define_method(1001, -> {}) }
+ }.should raise_error(TypeError, /is not a symbol nor a string/)
+
+ -> {
+ Class.new { define_method([], -> {}) }
+ }.should raise_error(TypeError, /is not a symbol nor a string/)
+ end
+
+ it "converts non-String name to String with #to_str" do
+ obj = Object.new
+ def obj.to_str() "foo" end
+
+ new_class = Class.new { define_method(obj, -> { :called }) }
+ new_class.new.foo.should == :called
+ end
+
+ it "raises TypeError when #to_str called on non-String name returns non-String value" do
+ obj = Object.new
+ def obj.to_str() [] end
+
+ -> {
+ Class.new { define_method(obj, -> {}) }
+ }.should raise_error(TypeError, /can't convert Object to String/)
+ end
+
+ it "raises a TypeError when the given method is no Method/Proc" do
+ -> {
+ Class.new { define_method(:test, "self") }
+ }.should raise_error(TypeError, "wrong argument type String (expected Proc/Method/UnboundMethod)")
+
+ -> {
+ Class.new { define_method(:test, 1234) }
+ }.should raise_error(TypeError, "wrong argument type Integer (expected Proc/Method/UnboundMethod)")
+
+ -> {
+ Class.new { define_method(:test, nil) }
+ }.should raise_error(TypeError, "wrong argument type NilClass (expected Proc/Method/UnboundMethod)")
+ end
+
+ it "uses provided Method/Proc even if block is specified" do
+ new_class = Class.new do
+ define_method(:test, -> { :method_is_called }) do
+ :block_is_called
+ end
+ end
+
+ new_class.new.test.should == :method_is_called
+ end
+
+ it "raises an ArgumentError when no block is given" do
+ -> {
+ Class.new { define_method(:test) }
+ }.should raise_error(ArgumentError)
+ end
+
+ it "does not use the caller block when no block is given" do
+ o = Object.new
+ def o.define(name)
+ self.class.class_eval do
+ define_method(name)
+ end
+ end
+
+ -> {
+ o.define(:foo) { raise "not used" }
+ }.should raise_error(ArgumentError)
+ end
+
+ it "does not change the arity check style of the original proc" do
+ class DefineMethodSpecClass
+ prc = Proc.new { || true }
+ define_method("proc_style_test", &prc)
+ end
+
+ obj = DefineMethodSpecClass.new
+ -> { obj.proc_style_test :arg }.should raise_error(ArgumentError)
+ end
+
+ it "raises a FrozenError if frozen" do
+ -> {
+ Class.new { freeze; define_method(:foo) {} }
+ }.should raise_error(FrozenError)
+ end
+
+ it "accepts a Method (still bound)" do
+ class DefineMethodSpecClass
+ attr_accessor :data
+ def inspect_data
+ "data is #{@data}"
+ end
+ end
+ o = DefineMethodSpecClass.new
+ o.data = :foo
+ m = o.method(:inspect_data)
+ m.should be_an_instance_of(Method)
+ klass = Class.new(DefineMethodSpecClass)
+ klass.send(:define_method,:other_inspect, m)
+ c = klass.new
+ c.data = :bar
+ c.other_inspect.should == "data is bar"
+ ->{o.other_inspect}.should raise_error(NoMethodError)
+ end
+
+ it "raises a TypeError when a Method from a singleton class is defined on another class" do
+ c = Class.new do
+ class << self
+ def foo
+ end
+ end
+ end
+ m = c.method(:foo)
+
+ -> {
+ Class.new { define_method :bar, m }
+ }.should raise_error(TypeError, /can't bind singleton method to a different class/)
+ end
+
+ it "raises a TypeError when a Method from one class is defined on an unrelated class" do
+ c = Class.new do
+ def foo
+ end
+ end
+ m = c.new.method(:foo)
+
+ -> {
+ Class.new { define_method :bar, m }
+ }.should raise_error(TypeError)
+ end
+
+ it "accepts an UnboundMethod from an attr_accessor method" do
+ class DefineMethodSpecClass
+ attr_accessor :accessor_method
+ end
+
+ m = DefineMethodSpecClass.instance_method(:accessor_method)
+ o = DefineMethodSpecClass.new
+
+ DefineMethodSpecClass.send(:undef_method, :accessor_method)
+ -> { o.accessor_method }.should raise_error(NoMethodError)
+
+ DefineMethodSpecClass.send(:define_method, :accessor_method, m)
+
+ o.accessor_method = :abc
+ o.accessor_method.should == :abc
+ end
+
+ it "accepts a proc from a method" do
+ class ProcFromMethod
+ attr_accessor :data
+ def cool_method
+ "data is #{@data}"
+ end
+ end
+
+ object1 = ProcFromMethod.new
+ object1.data = :foo
+
+ method_proc = object1.method(:cool_method).to_proc
+ klass = Class.new(ProcFromMethod)
+ klass.send(:define_method, :other_cool_method, &method_proc)
+
+ object2 = klass.new
+ object2.data = :bar
+ object2.other_cool_method.should == "data is foo"
+ end
+
+ it "accepts a proc from a Symbol" do
+ symbol_proc = :+.to_proc
+ klass = Class.new do
+ define_method :foo, &symbol_proc
+ end
+ klass.new.foo(1, 2).should == 3
+ end
+
+ it "maintains the Proc's scope" do
+ class DefineMethodByProcClass
+ in_scope = true
+ method_proc = proc { in_scope }
+
+ define_method(:proc_test, &method_proc)
+ end
+
+ o = DefineMethodByProcClass.new
+ o.proc_test.should be_true
+ end
+
+ it "accepts a String method name" do
+ klass = Class.new do
+ define_method("string_test") do
+ "string_test result"
+ end
+ end
+
+ klass.new.string_test.should == "string_test result"
+ end
+
+ it "is a public method" do
+ Module.should have_public_instance_method(:define_method)
+ end
+
+ it "returns its symbol" do
+ class DefineMethodSpecClass
+ method = define_method("return_test") { true }
+ method.should == :return_test
+ end
+ end
+
+ it "allows an UnboundMethod from a module to be defined on a class" do
+ klass = Class.new {
+ define_method :bar, ModuleSpecs::UnboundMethodTest.instance_method(:foo)
+ }
+ klass.new.should respond_to(:bar)
+ end
+
+ it "allows an UnboundMethod from a parent class to be defined on a child class" do
+ parent = Class.new { define_method(:foo) { :bar } }
+ child = Class.new(parent) {
+ define_method :baz, parent.instance_method(:foo)
+ }
+ child.new.should respond_to(:baz)
+ end
+
+ it "allows an UnboundMethod from a module to be defined on another unrelated module" do
+ mod = Module.new {
+ define_method :bar, ModuleSpecs::UnboundMethodTest.instance_method(:foo)
+ }
+ klass = Class.new { include mod }
+ klass.new.should respond_to(:bar)
+ end
+
+
+ it "allows an UnboundMethod of a Kernel method retrieved from Object to defined on a BasicObject subclass" do
+ klass = Class.new(BasicObject) do
+ define_method :instance_of?, ::Object.instance_method(:instance_of?)
+ end
+ klass.new.instance_of?(klass).should == true
+ end
+
+ it "raises a TypeError when an UnboundMethod from a child class is defined on a parent class" do
+ -> {
+ ParentClass = Class.new { define_method(:foo) { :bar } }
+ ChildClass = Class.new(ParentClass) { define_method(:foo) { :baz } }
+ ParentClass.send :define_method, :foo, ChildClass.instance_method(:foo)
+ }.should raise_error(TypeError, /bind argument must be a subclass of ChildClass/)
+ end
+
+ it "raises a TypeError when an UnboundMethod from one class is defined on an unrelated class" do
+ -> {
+ DestinationClass = Class.new {
+ define_method :bar, ModuleSpecs::InstanceMeth.instance_method(:foo)
+ }
+ }.should raise_error(TypeError, /bind argument must be a subclass of ModuleSpecs::InstanceMeth/)
+ end
+
+ it "raises a TypeError when an UnboundMethod from a singleton class is defined on another class" do
+ c = Class.new do
+ class << self
+ def foo
+ end
+ end
+ end
+ m = c.method(:foo).unbind
+
+ -> {
+ Class.new { define_method :bar, m }
+ }.should raise_error(TypeError, /can't bind singleton method to a different class/)
+ end
+end
+
+describe "Module#define_method" do
+ describe "passed { } creates a method that" do
+ before :each do
+ @klass = Class.new do
+ define_method(:m) { :called }
+ end
+ end
+
+ it "returns the value computed by the block when passed zero arguments" do
+ @klass.new.m().should == :called
+ end
+
+ it "raises an ArgumentError when passed one argument" do
+ -> { @klass.new.m 1 }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed two arguments" do
+ -> { @klass.new.m 1, 2 }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "passed { || } creates a method that" do
+ before :each do
+ @klass = Class.new do
+ define_method(:m) { || :called }
+ end
+ end
+
+ it "returns the value computed by the block when passed zero arguments" do
+ @klass.new.m().should == :called
+ end
+
+ it "raises an ArgumentError when passed one argument" do
+ -> { @klass.new.m 1 }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed two arguments" do
+ -> { @klass.new.m 1, 2 }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "passed { |a| } creates a method that" do
+ before :each do
+ @klass = Class.new do
+ define_method(:m) { |a| a }
+ end
+ end
+
+ it "raises an ArgumentError when passed zero arguments" do
+ -> { @klass.new.m }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed zero arguments and a block" do
+ -> { @klass.new.m { :computed } }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed two arguments" do
+ -> { @klass.new.m 1, 2 }.should raise_error(ArgumentError)
+ end
+
+ it "receives the value passed as the argument when passed one argument" do
+ @klass.new.m(1).should == 1
+ end
+ end
+
+ describe "passed { |a,| } creates a method that" do
+ before :each do
+ @klass = Class.new do
+ define_method(:m) { |a,| a }
+ end
+ end
+
+ it "raises an ArgumentError when passed zero arguments" do
+ -> { @klass.new.m }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed zero arguments and a block" do
+ -> { @klass.new.m { :computed } }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed two arguments" do
+ -> { @klass.new.m 1, 2 }.should raise_error(ArgumentError)
+ end
+
+ it "receives the value passed as the argument when passed one argument" do
+ @klass.new.m(1).should == 1
+ end
+
+ it "does not destructure the passed argument" do
+ @klass.new.m([1, 2]).should == [1, 2]
+ # for comparison:
+ proc { |a,| a }.call([1, 2]).should == 1
+ end
+ end
+
+ describe "passed { |*a| } creates a method that" do
+ before :each do
+ @klass = Class.new do
+ define_method(:m) { |*a| a }
+ end
+ end
+
+ it "receives an empty array as the argument when passed zero arguments" do
+ @klass.new.m().should == []
+ end
+
+ it "receives the value in an array when passed one argument" do
+ @klass.new.m(1).should == [1]
+ end
+
+ it "receives the values in an array when passed two arguments" do
+ @klass.new.m(1, 2).should == [1, 2]
+ end
+ end
+
+ describe "passed { |a, *b| } creates a method that" do
+ before :each do
+ @klass = Class.new do
+ define_method(:m) { |a, *b| return a, b }
+ end
+ end
+
+ it "raises an ArgumentError when passed zero arguments" do
+ -> { @klass.new.m }.should raise_error(ArgumentError)
+ end
+
+ it "returns the value computed by the block when passed one argument" do
+ @klass.new.m(1).should == [1, []]
+ end
+
+ it "returns the value computed by the block when passed two arguments" do
+ @klass.new.m(1, 2).should == [1, [2]]
+ end
+
+ it "returns the value computed by the block when passed three arguments" do
+ @klass.new.m(1, 2, 3).should == [1, [2, 3]]
+ end
+ end
+
+ describe "passed { |a, b| } creates a method that" do
+ before :each do
+ @klass = Class.new do
+ define_method(:m) { |a, b| return a, b }
+ end
+ end
+
+ it "returns the value computed by the block when passed two arguments" do
+ @klass.new.m(1, 2).should == [1, 2]
+ end
+
+ it "raises an ArgumentError when passed zero arguments" do
+ -> { @klass.new.m }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed one argument" do
+ -> { @klass.new.m 1 }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed one argument and a block" do
+ -> { @klass.new.m(1) { } }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed three arguments" do
+ -> { @klass.new.m 1, 2, 3 }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "passed { |a, b, *c| } creates a method that" do
+ before :each do
+ @klass = Class.new do
+ define_method(:m) { |a, b, *c| return a, b, c }
+ end
+ end
+
+ it "raises an ArgumentError when passed zero arguments" do
+ -> { @klass.new.m }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed one argument" do
+ -> { @klass.new.m 1 }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed one argument and a block" do
+ -> { @klass.new.m(1) { } }.should raise_error(ArgumentError)
+ end
+
+ it "receives an empty array as the third argument when passed two arguments" do
+ @klass.new.m(1, 2).should == [1, 2, []]
+ end
+
+ it "receives the third argument in an array when passed three arguments" do
+ @klass.new.m(1, 2, 3).should == [1, 2, [3]]
+ end
+ end
+end
+
+describe "Method#define_method when passed a Method object" do
+ before :each do
+ @klass = Class.new do
+ def m(a, b, *c)
+ :m
+ end
+ end
+
+ @obj = @klass.new
+ m = @obj.method :m
+
+ @klass.class_exec do
+ define_method :n, m
+ end
+ end
+
+ it "defines a method with the same #arity as the original" do
+ @obj.method(:n).arity.should == @obj.method(:m).arity
+ end
+
+ it "defines a method with the same #parameters as the original" do
+ @obj.method(:n).parameters.should == @obj.method(:m).parameters
+ end
+end
+
+describe "Method#define_method when passed an UnboundMethod object" do
+ before :each do
+ @klass = Class.new do
+ def m(a, b, *c)
+ :m
+ end
+ end
+
+ @obj = @klass.new
+ m = @klass.instance_method :m
+
+ @klass.class_exec do
+ define_method :n, m
+ end
+ end
+
+ it "defines a method with the same #arity as the original" do
+ @obj.method(:n).arity.should == @obj.method(:m).arity
+ end
+
+ it "defines a method with the same #parameters as the original" do
+ @obj.method(:n).parameters.should == @obj.method(:m).parameters
+ end
+end
+
+describe "Method#define_method when passed a Proc object" do
+ describe "and a method is defined inside" do
+ it "defines the nested method in the default definee where the Proc was created" do
+ prc = nil
+ t = Class.new do
+ prc = -> {
+ def nested_method_in_proc_for_define_method
+ 42
+ end
+ }
+ end
+
+ c = Class.new do
+ define_method(:test, prc)
+ end
+
+ o = c.new
+ o.test
+ o.should_not have_method :nested_method_in_proc_for_define_method
+
+ t.new.nested_method_in_proc_for_define_method.should == 42
+ end
+ end
+end
+
+describe "Method#define_method when passed a block" do
+ describe "behaves exactly like a lambda" do
+ it "for return" do
+ Class.new do
+ define_method(:foo) do
+ return 42
+ end
+ end.new.foo.should == 42
+ end
+
+ it "for break" do
+ Class.new do
+ define_method(:foo) do
+ break 42
+ end
+ end.new.foo.should == 42
+ end
+
+ it "for next" do
+ Class.new do
+ define_method(:foo) do
+ next 42
+ end
+ end.new.foo.should == 42
+ end
+
+ it "for redo" do
+ Class.new do
+ result = []
+ define_method(:foo) do
+ if result.empty?
+ result << :first
+ redo
+ else
+ result << :second
+ result
+ end
+ end
+ end.new.foo.should == [:first, :second]
+ end
+ end
+end
diff --git a/spec/ruby/core/module/define_singleton_method_spec.rb b/spec/ruby/core/module/define_singleton_method_spec.rb
new file mode 100644
index 0000000000..eb5cb89ed1
--- /dev/null
+++ b/spec/ruby/core/module/define_singleton_method_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "Module#define_singleton_method" do
+ it "defines the given method as an class method with the given name in self" do
+ klass = Module.new do
+ define_singleton_method :a do
+ 42
+ end
+ define_singleton_method(:b, -> x { 2*x })
+ end
+
+ klass.a.should == 42
+ klass.b(10).should == 20
+ end
+end
diff --git a/spec/ruby/core/module/deprecate_constant_spec.rb b/spec/ruby/core/module/deprecate_constant_spec.rb
new file mode 100644
index 0000000000..aabef934c4
--- /dev/null
+++ b/spec/ruby/core/module/deprecate_constant_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+
+describe "Module#deprecate_constant" do
+ before :each do
+ @module = Module.new
+ @value = :value
+ @module::PUBLIC1 = @value
+ @module::PUBLIC2 = @value
+ @module::PRIVATE = @value
+ @module.private_constant :PRIVATE
+ @module.deprecate_constant :PRIVATE
+ end
+
+ describe "when accessing the deprecated module" do
+ it "passes the accessing" do
+ @module.deprecate_constant :PUBLIC1
+
+ value = nil
+ -> {
+ value = @module::PUBLIC1
+ }.should complain(/warning: constant .+::PUBLIC1 is deprecated/)
+ value.should equal(@value)
+
+ -> { @module::PRIVATE }.should raise_error(NameError)
+ end
+
+ it "warns with a message" do
+ @module.deprecate_constant :PUBLIC1
+
+ -> { @module::PUBLIC1 }.should complain(/warning: constant .+::PUBLIC1 is deprecated/)
+ -> { @module.const_get :PRIVATE }.should complain(/warning: constant .+::PRIVATE is deprecated/)
+ end
+
+ it "does not warn if Warning[:deprecated] is false" do
+ @module.deprecate_constant :PUBLIC1
+
+ deprecated = Warning[:deprecated]
+ begin
+ Warning[:deprecated] = false
+ -> { @module::PUBLIC1 }.should_not complain
+ ensure
+ Warning[:deprecated] = deprecated
+ end
+ end
+ end
+
+ it "accepts multiple symbols and strings as constant names" do
+ @module.deprecate_constant "PUBLIC1", :PUBLIC2
+
+ -> { @module::PUBLIC1 }.should complain(/warning: constant .+::PUBLIC1 is deprecated/)
+ -> { @module::PUBLIC2 }.should complain(/warning: constant .+::PUBLIC2 is deprecated/)
+ end
+
+ it "returns self" do
+ @module.deprecate_constant(:PUBLIC1).should equal(@module)
+ end
+
+ it "raises a NameError when given an undefined name" do
+ -> { @module.deprecate_constant :UNDEFINED }.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/core/module/eql_spec.rb b/spec/ruby/core/module/eql_spec.rb
new file mode 100644
index 0000000000..76bb271d8d
--- /dev/null
+++ b/spec/ruby/core/module/eql_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/equal_value'
+
+describe "Module#eql?" do
+ it_behaves_like :module_equal, :eql?
+end
diff --git a/spec/ruby/core/module/equal_spec.rb b/spec/ruby/core/module/equal_spec.rb
new file mode 100644
index 0000000000..01ab06152d
--- /dev/null
+++ b/spec/ruby/core/module/equal_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/equal_value'
+
+describe "Module#equal?" do
+ it_behaves_like :module_equal, :equal?
+end
diff --git a/spec/ruby/core/module/equal_value_spec.rb b/spec/ruby/core/module/equal_value_spec.rb
new file mode 100644
index 0000000000..a7191cd755
--- /dev/null
+++ b/spec/ruby/core/module/equal_value_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/equal_value'
+
+describe "Module#==" do
+ it_behaves_like :module_equal, :==
+end
diff --git a/spec/ruby/core/module/extend_object_spec.rb b/spec/ruby/core/module/extend_object_spec.rb
new file mode 100644
index 0000000000..1fd1abc0b5
--- /dev/null
+++ b/spec/ruby/core/module/extend_object_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#extend_object" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is a private method" do
+ Module.should have_private_instance_method(:extend_object)
+ end
+
+ describe "on Class" do
+ it "is undefined" do
+ Class.should_not have_private_instance_method(:extend_object, true)
+ end
+
+ it "raises a TypeError if calling after rebinded to Class" do
+ -> {
+ Module.instance_method(:extend_object).bind(Class.new).call Object.new
+ }.should raise_error(TypeError)
+ end
+ end
+
+ it "is called when #extend is called on an object" do
+ ModuleSpecs::ExtendObject.should_receive(:extend_object)
+ obj = mock("extended object")
+ obj.extend ModuleSpecs::ExtendObject
+ end
+
+ it "extends the given object with its constants and methods by default" do
+ obj = mock("extended direct")
+ ModuleSpecs::ExtendObject.send :extend_object, obj
+
+ obj.test_method.should == "hello test"
+ obj.singleton_class.const_get(:C).should == :test
+ end
+
+ it "is called even when private" do
+ obj = mock("extended private")
+ obj.extend ModuleSpecs::ExtendObjectPrivate
+ ScratchPad.recorded.should == :extended
+ end
+
+ describe "when given a frozen object" do
+ before :each do
+ @receiver = Module.new
+ @object = Object.new.freeze
+ end
+
+ it "raises a RuntimeError before extending the object" do
+ -> { @receiver.send(:extend_object, @object) }.should raise_error(RuntimeError)
+ @object.should_not be_kind_of(@receiver)
+ end
+ end
+end
diff --git a/spec/ruby/core/module/extended_spec.rb b/spec/ruby/core/module/extended_spec.rb
new file mode 100644
index 0000000000..c6300ffa0b
--- /dev/null
+++ b/spec/ruby/core/module/extended_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#extended" do
+ it "is called when an object gets extended with self" do
+ begin
+ m = Module.new do
+ def self.extended(o)
+ $extended_object = o
+ end
+ end
+
+ (o = mock('x')).extend(m)
+
+ $extended_object.should == o
+ ensure
+ $extended_object = nil
+ end
+ end
+
+ it "is called after Module#extend_object" do
+ begin
+ m = Module.new do
+ def self.extend_object(o)
+ $extended_object = nil
+ end
+
+ def self.extended(o)
+ $extended_object = o
+ end
+ end
+
+ (o = mock('x')).extend(m)
+
+ $extended_object.should == o
+ ensure
+ $extended_object = nil
+ end
+ end
+
+ it "is private in its default implementation" do
+ Module.new.private_methods.should include(:extended)
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload.rb b/spec/ruby/core/module/fixtures/autoload.rb
new file mode 100644
index 0000000000..5a77a2d9d4
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload.rb
@@ -0,0 +1 @@
+$m.const_set(:AAA, "test") unless $m.nil?
diff --git a/spec/ruby/core/module/fixtures/autoload_abc.rb b/spec/ruby/core/module/fixtures/autoload_abc.rb
new file mode 100644
index 0000000000..ffaec91cfe
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_abc.rb
@@ -0,0 +1,11 @@
+module ModuleSpecs::Autoload::FromThread
+ module A
+ class B
+ class C
+ def self.foo
+ :foo
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_c.rb b/spec/ruby/core/module/fixtures/autoload_c.rb
new file mode 100644
index 0000000000..ff2bcc548c
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_c.rb
@@ -0,0 +1,11 @@
+module ModuleSpecs::Autoload
+ class DynClass
+ class C
+ def loaded
+ :dynclass_c
+ end
+ end
+ end
+end
+
+ScratchPad.record :loaded
diff --git a/spec/ruby/core/module/fixtures/autoload_callback.rb b/spec/ruby/core/module/fixtures/autoload_callback.rb
new file mode 100644
index 0000000000..51d53eb580
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_callback.rb
@@ -0,0 +1,2 @@
+block = ScratchPad.recorded
+block.call
diff --git a/spec/ruby/core/module/fixtures/autoload_concur.rb b/spec/ruby/core/module/fixtures/autoload_concur.rb
new file mode 100644
index 0000000000..0585e36880
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_concur.rb
@@ -0,0 +1,9 @@
+ScratchPad.recorded << :con_pre
+Thread.current[:in_autoload_rb] = true
+sleep 0.1
+
+module ModuleSpecs::Autoload
+ Concur = 1
+end
+
+ScratchPad.recorded << :con_post
diff --git a/spec/ruby/core/module/fixtures/autoload_d.rb b/spec/ruby/core/module/fixtures/autoload_d.rb
new file mode 100644
index 0000000000..6f5eee741c
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_d.rb
@@ -0,0 +1,11 @@
+module ModuleSpecs::Autoload
+ module DynModule
+ class D
+ def loaded
+ :dynmodule_d
+ end
+ end
+ end
+end
+
+ScratchPad.record :loaded
diff --git a/spec/ruby/core/module/fixtures/autoload_during_autoload.rb b/spec/ruby/core/module/fixtures/autoload_during_autoload.rb
new file mode 100644
index 0000000000..5202bd8b23
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_during_autoload.rb
@@ -0,0 +1,7 @@
+block = ScratchPad.recorded
+ScratchPad.record(block.call)
+
+module ModuleSpecs::Autoload
+ class DuringAutoload
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_during_require.rb b/spec/ruby/core/module/fixtures/autoload_during_require.rb
new file mode 100644
index 0000000000..6fd81592e3
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_during_require.rb
@@ -0,0 +1,4 @@
+module ModuleSpecs::Autoload
+ class AutoloadDuringRequire
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_during_require_current_file.rb b/spec/ruby/core/module/fixtures/autoload_during_require_current_file.rb
new file mode 100644
index 0000000000..5aa8595065
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_during_require_current_file.rb
@@ -0,0 +1,5 @@
+module ModuleSpecs::Autoload
+ autoload(:AutoloadCurrentFile, __FILE__)
+
+ ScratchPad.record autoload?(:AutoloadCurrentFile)
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_e.rb b/spec/ruby/core/module/fixtures/autoload_e.rb
new file mode 100644
index 0000000000..fb78c6cbd4
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_e.rb
@@ -0,0 +1,7 @@
+module ModuleSpecs::Autoload
+ class E
+ def loaded
+ :autoload_e
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_empty.rb b/spec/ruby/core/module/fixtures/autoload_empty.rb
new file mode 100644
index 0000000000..d7116f3049
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_empty.rb
@@ -0,0 +1 @@
+# This file is left empty on purpose
diff --git a/spec/ruby/core/module/fixtures/autoload_ex1.rb b/spec/ruby/core/module/fixtures/autoload_ex1.rb
new file mode 100644
index 0000000000..a90092389c
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_ex1.rb
@@ -0,0 +1,16 @@
+
+class ModuleSpecs::Autoload::EX1 < Exception
+ def self.trample1
+ 1.times { return }
+ end
+
+ def self.trample2
+ begin
+ raise "hello"
+ rescue
+ end
+ end
+
+ trample1
+ trample2
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_exception.rb b/spec/ruby/core/module/fixtures/autoload_exception.rb
new file mode 100644
index 0000000000..09acf9f537
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_exception.rb
@@ -0,0 +1,3 @@
+ScratchPad.record(:exception)
+
+raise 'intentional error to test failure conditions during autoloading'
diff --git a/spec/ruby/core/module/fixtures/autoload_f.rb b/spec/ruby/core/module/fixtures/autoload_f.rb
new file mode 100644
index 0000000000..54c2b05b7b
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_f.rb
@@ -0,0 +1,7 @@
+module ModuleSpecs::Autoload
+ module F
+ def self.loaded
+ :autoload_f
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_g.rb b/spec/ruby/core/module/fixtures/autoload_g.rb
new file mode 100644
index 0000000000..a1c4e429d9
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_g.rb
@@ -0,0 +1,7 @@
+module ModuleSpecs::Autoload
+ class G
+ def loaded
+ :autoload_g
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_h.rb b/spec/ruby/core/module/fixtures/autoload_h.rb
new file mode 100644
index 0000000000..53988c5382
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_h.rb
@@ -0,0 +1,7 @@
+module ModuleSpecs::Autoload
+ module H
+ def loaded
+ :autoload_h
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_i.rb b/spec/ruby/core/module/fixtures/autoload_i.rb
new file mode 100644
index 0000000000..f7f720516e
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_i.rb
@@ -0,0 +1,5 @@
+module ModuleSpecs::Autoload
+ I = :autoloaded
+end
+
+ScratchPad.record :loaded
diff --git a/spec/ruby/core/module/fixtures/autoload_j.rb b/spec/ruby/core/module/fixtures/autoload_j.rb
new file mode 100644
index 0000000000..da6d35d43d
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_j.rb
@@ -0,0 +1,3 @@
+module ModuleSpecs::Autoload
+ J = :autoload_j
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_k.rb b/spec/ruby/core/module/fixtures/autoload_k.rb
new file mode 100644
index 0000000000..431602bf80
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_k.rb
@@ -0,0 +1,7 @@
+module ModuleSpecs::Autoload
+ class KHash < Hash
+ K = :autoload_k
+ end
+end
+
+ScratchPad.record :loaded
diff --git a/spec/ruby/core/module/fixtures/autoload_lm.rb b/spec/ruby/core/module/fixtures/autoload_lm.rb
new file mode 100644
index 0000000000..d93d783cd0
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_lm.rb
@@ -0,0 +1,4 @@
+module ModuleSpecs::Autoload
+ L = :autoload_l
+ M = :autoload_m
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_location.rb b/spec/ruby/core/module/fixtures/autoload_location.rb
new file mode 100644
index 0000000000..318851b2df
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_location.rb
@@ -0,0 +1,3 @@
+module ConstantSpecs
+ CONST_LOCATION = __LINE__
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_nested.rb b/spec/ruby/core/module/fixtures/autoload_nested.rb
new file mode 100644
index 0000000000..073cec0dce
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_nested.rb
@@ -0,0 +1,8 @@
+module ModuleSpecs::Autoload
+ module GoodParent
+ class Nested
+ end
+ end
+end
+
+ScratchPad.record(:loaded)
diff --git a/spec/ruby/core/module/fixtures/autoload_never_set.rb b/spec/ruby/core/module/fixtures/autoload_never_set.rb
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_never_set.rb
@@ -0,0 +1 @@
+
diff --git a/spec/ruby/core/module/fixtures/autoload_o.rb b/spec/ruby/core/module/fixtures/autoload_o.rb
new file mode 100644
index 0000000000..7d88f969b2
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_o.rb
@@ -0,0 +1,2 @@
+# does not define ModuleSpecs::Autoload::O
+ScratchPad << :loaded
diff --git a/spec/ruby/core/module/fixtures/autoload_overridden.rb b/spec/ruby/core/module/fixtures/autoload_overridden.rb
new file mode 100644
index 0000000000..7062bcfabc
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_overridden.rb
@@ -0,0 +1,3 @@
+module ModuleSpecs::Autoload
+ Overridden = :overridden
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_r.rb b/spec/ruby/core/module/fixtures/autoload_r.rb
new file mode 100644
index 0000000000..34209d20c3
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_r.rb
@@ -0,0 +1,4 @@
+module ModuleSpecs::Autoload
+ class R
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_raise.rb b/spec/ruby/core/module/fixtures/autoload_raise.rb
new file mode 100644
index 0000000000..f6051e3ba2
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_raise.rb
@@ -0,0 +1,2 @@
+ScratchPad << :raise
+raise "exception during autoload"
diff --git a/spec/ruby/core/module/fixtures/autoload_required_directly.rb b/spec/ruby/core/module/fixtures/autoload_required_directly.rb
new file mode 100644
index 0000000000..bed60a71ec
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_required_directly.rb
@@ -0,0 +1,7 @@
+block = ScratchPad.recorded
+ScratchPad.record(block.call)
+
+module ModuleSpecs::Autoload
+ class RequiredDirectly
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_required_directly_nested.rb b/spec/ruby/core/module/fixtures/autoload_required_directly_nested.rb
new file mode 100644
index 0000000000..a9f11c2188
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_required_directly_nested.rb
@@ -0,0 +1 @@
+ScratchPad.recorded.call
diff --git a/spec/ruby/core/module/fixtures/autoload_required_directly_no_constant.rb b/spec/ruby/core/module/fixtures/autoload_required_directly_no_constant.rb
new file mode 100644
index 0000000000..25e08c1129
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_required_directly_no_constant.rb
@@ -0,0 +1,2 @@
+block = ScratchPad.recorded
+ScratchPad.record(block.call)
diff --git a/spec/ruby/core/module/fixtures/autoload_s.rb b/spec/ruby/core/module/fixtures/autoload_s.rb
new file mode 100644
index 0000000000..f5d412ff18
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_s.rb
@@ -0,0 +1,5 @@
+module ModuleSpecs::Autoload
+ S = :autoload_s
+end
+
+ScratchPad.record :loaded
diff --git a/spec/ruby/core/module/fixtures/autoload_self_during_require.rb b/spec/ruby/core/module/fixtures/autoload_self_during_require.rb
new file mode 100644
index 0000000000..f4a514a807
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_self_during_require.rb
@@ -0,0 +1,5 @@
+module ModuleSpecs::Autoload
+ autoload :AutoloadSelfDuringRequire, __FILE__
+ class AutoloadSelfDuringRequire
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_subclass.rb b/spec/ruby/core/module/fixtures/autoload_subclass.rb
new file mode 100644
index 0000000000..8027fa3fcd
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_subclass.rb
@@ -0,0 +1,11 @@
+class CS_CONST_AUTOLOAD
+end
+
+module ModuleSpecs
+ module Autoload
+ module XX
+ class CS_CONST_AUTOLOAD < CS_CONST_AUTOLOAD
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_t.rb b/spec/ruby/core/module/fixtures/autoload_t.rb
new file mode 100644
index 0000000000..4962c97f4c
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_t.rb
@@ -0,0 +1,3 @@
+module ModuleSpecs::Autoload::S
+ T = :autoload_t
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_v.rb b/spec/ruby/core/module/fixtures/autoload_v.rb
new file mode 100644
index 0000000000..2aa8c44169
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_v.rb
@@ -0,0 +1,7 @@
+module ModuleSpecs::Autoload::U
+ class V
+ def self.get_value
+ :autoload_uvx
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_w.rb b/spec/ruby/core/module/fixtures/autoload_w.rb
new file mode 100644
index 0000000000..997273812e
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_w.rb
@@ -0,0 +1,2 @@
+# Fails to define ModuleSpecs::Autoload::W::Y
+ScratchPad.record :loaded
diff --git a/spec/ruby/core/module/fixtures/autoload_w2.rb b/spec/ruby/core/module/fixtures/autoload_w2.rb
new file mode 100644
index 0000000000..a8dbebf322
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_w2.rb
@@ -0,0 +1 @@
+ScratchPad.record :loaded
diff --git a/spec/ruby/core/module/fixtures/autoload_x.rb b/spec/ruby/core/module/fixtures/autoload_x.rb
new file mode 100644
index 0000000000..a6c5842609
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_x.rb
@@ -0,0 +1,3 @@
+module ModuleSpecs::Autoload
+ X = :x
+end
diff --git a/spec/ruby/core/module/fixtures/autoload_z.rb b/spec/ruby/core/module/fixtures/autoload_z.rb
new file mode 100644
index 0000000000..cce1c13f37
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/autoload_z.rb
@@ -0,0 +1,5 @@
+class ModuleSpecs::Autoload::YY
+end
+
+class ModuleSpecs::Autoload::Z < ModuleSpecs::Autoload::YY
+end
diff --git a/spec/ruby/core/module/fixtures/classes.rb b/spec/ruby/core/module/fixtures/classes.rb
new file mode 100644
index 0000000000..bc6b940a6c
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/classes.rb
@@ -0,0 +1,627 @@
+module ModuleSpecs
+ def self.without_test_modules(modules)
+ ignore = %w[MSpecRSpecAdapter PP::ObjectMixin ModuleSpecs::IncludedInObject MainSpecs::Module ConstantSpecs::ModuleA]
+ modules.reject { |k| ignore.include?(k.name) }
+ end
+
+ CONST = :plain_constant
+
+ class NamedClass
+ end
+
+ module PrivConstModule
+ PRIVATE_CONSTANT = 1
+ private_constant :PRIVATE_CONSTANT
+ PUBLIC_CONSTANT = 2
+ end
+
+ class Subclass < Module
+ end
+
+ class SubclassSpec
+ end
+
+ class RemoveClassVariable
+ end
+
+ module LookupModInMod
+ INCS = :ethereal
+ end
+
+ module LookupMod
+ include LookupModInMod
+
+ MODS = :rockers
+ end
+
+ class Lookup
+ include LookupMod
+ LOOKIE = :lookie
+ end
+
+ class LookupChild < Lookup
+ end
+
+ module ModuleWithPrepend
+ prepend LookupMod
+ end
+
+ class WithPrependedModule
+ include ModuleWithPrepend
+ end
+
+ class Parent
+ # For private_class_method spec
+ def self.private_method; end
+ private_class_method :private_method
+
+ def undefed_method() end
+ undef_method :undefed_method
+
+ def parent_method; end
+ def another_parent_method; end
+
+ # For public_class_method spec
+ private
+ def self.public_method; end
+ public_class_method :public_method
+
+ public
+ def public_parent() end
+
+ protected
+ def protected_parent() end
+
+ private
+ def private_parent() end
+ end
+
+ module Basic
+ def public_module() end
+
+ protected
+ def protected_module() end
+
+ private
+ def private_module() end
+ end
+
+ module Super
+ include Basic
+
+ def public_super_module() end
+
+ protected
+ def protected_super_module() end
+
+ private
+ def private_super_module() end
+
+ def super_included_method; end
+
+ class SuperChild
+ end
+ end
+
+ module Internal
+ end
+
+ class Child < Parent
+ include Super
+
+ class << self
+ include Internal
+ end
+ attr_accessor :accessor_method
+
+ def public_child() end
+
+ undef_method :parent_method
+ undef_method :another_parent_method
+
+ protected
+ def protected_child() end
+
+ private
+ def private_child() end
+ end
+
+ class Grandchild < Child
+ undef_method :super_included_method
+ end
+
+ class Child2 < Parent
+ attr_reader :foo
+ end
+
+ # Be careful touching the Counts* classes as there used for testing
+ # private_instance_methods, public_instance_methods, etc. So adding, removing
+ # a method will break those tests.
+ module CountsMixin
+ def public_3; end
+ public :public_3
+
+ def private_3; end
+ private :private_3
+
+ def protected_3; end
+ protected :protected_3
+ end
+
+ class CountsParent
+ include CountsMixin
+
+ def public_2; end
+
+ private
+ def private_2; end
+
+ protected
+ def protected_2; end
+ end
+
+ class CountsChild < CountsParent
+ def public_1; end
+
+ private
+ def private_1; end
+
+ protected
+ def protected_1; end
+ end
+
+ module AddConstant
+ end
+
+ module A
+ CONSTANT_A = :a
+ OVERRIDE = :a
+ def ma(); :a; end
+ def self.cma(); :a; end
+ end
+
+ module B
+ CONSTANT_B = :b
+ OVERRIDE = :b
+ include A
+ def mb(); :b; end
+ def self.cmb(); :b; end
+ end
+
+ class C
+ OVERRIDE = :c
+ include B
+ end
+
+ module Z
+ MODULE_SPEC_TOPLEVEL_CONSTANT = 1
+ end
+
+ module Alias
+ def report() :report end
+ alias publish report
+ end
+
+ class Allonym
+ include ModuleSpecs::Alias
+ end
+
+ class Aliasing
+ def self.make_alias(*a)
+ alias_method(*a)
+ end
+
+ def public_one; 1; end
+
+ def public_two(n); n * 2; end
+
+ private
+ def private_one; 1; end
+
+ protected
+ def protected_one; 1; end
+ end
+
+ class AliasingSubclass < Aliasing
+ end
+
+ module AliasingSuper
+
+ module Parent
+ def super_call(arg)
+ arg
+ end
+ end
+
+ module Child
+ include Parent
+ def super_call(arg)
+ super(arg)
+ end
+ end
+
+ class Target
+ include Child
+ alias_method :alias_super_call, :super_call
+ alias_method :super_call, :alias_super_call
+ end
+
+ class RedefineAfterAlias
+ include Parent
+
+ def super_call(arg)
+ super(arg)
+ end
+
+ alias_method :alias_super_call, :super_call
+
+ def super_call(arg)
+ :wrong
+ end
+ end
+ end
+
+
+ module ReopeningModule
+ def foo; true; end
+ module_function :foo
+ private :foo
+ end
+
+ # Yes, we want to re-open the module
+ module ReopeningModule
+ alias :foo2 :foo
+ module_function :foo2
+ end
+
+ module Nesting
+ @tests = {}
+ def self.[](name); @tests[name]; end
+ def self.[]=(name, val); @tests[name] = val; end
+ def self.meta; class << self; self; end; end
+
+ Nesting[:basic] = Module.nesting
+
+ module ::ModuleSpecs
+ Nesting[:open_first_level] = Module.nesting
+ end
+
+ class << self
+ Nesting[:open_meta] = Module.nesting
+ end
+
+ def self.called_from_module_method
+ Module.nesting
+ end
+
+ class NestedClass
+ Nesting[:nest_class] = Module.nesting
+
+ def self.called_from_class_method
+ Module.nesting
+ end
+
+ def called_from_inst_method
+ Module.nesting
+ end
+ end
+
+ end
+
+ Nesting[:first_level] = Module.nesting
+
+ module InstanceMethMod
+ def bar(); :bar; end
+ end
+
+ class InstanceMeth
+ include InstanceMethMod
+ def foo(); :foo; end
+ end
+
+ class InstanceMethChild < InstanceMeth
+ end
+
+ module ClassVars
+ class A
+ @@a_cvar = :a_cvar
+ end
+
+ module M
+ @@m_cvar = :m_cvar
+ end
+
+ class B < A
+ include M
+
+ @@b_cvar = :b_cvar
+ end
+ end
+
+ class CVars
+ @@cls = :class
+
+ # Singleton class lexical scopes are ignored for class variables
+ class << self
+ def cls
+ # This looks in the parent lexical scope, class CVars
+ @@cls
+ end
+ # This actually adds it to the parent lexical scope, class CVars
+ @@meta = :metainfo
+ end
+
+ def self.meta
+ @@meta
+ end
+
+ def meta
+ @@meta
+ end
+ end
+
+ class SubCVars < CVars
+ @@sub = :sub
+ end
+
+ module MVars
+ @@mvar = :mvar
+ end
+
+ class SubModule < Module
+ attr_reader :special
+ def initialize
+ @special = 10
+ end
+ end
+
+ module MA; end
+ module MB
+ include MA
+ end
+ module MC; end
+
+ class MultipleIncludes
+ include MB
+ end
+
+ # empty modules
+ module M1; end
+ module M2; end
+ module M3; end
+
+ module Autoload
+ def self.use_ex1
+ begin
+ begin
+ raise "test exception"
+ rescue ModuleSpecs::Autoload::EX1
+ end
+ rescue RuntimeError
+ return :good
+ end
+ end
+
+ class Parent
+ end
+
+ class Child < Parent
+ end
+
+ module FromThread
+ module A
+ autoload :B, fixture(__FILE__, "autoload_empty.rb")
+
+ class B
+ autoload :C, fixture(__FILE__, "autoload_abc.rb")
+
+ def self.foo
+ C.foo
+ end
+ end
+ end
+
+ class D < A::B; end
+ end
+ end
+
+ # This class isn't inherited from or included in anywhere.
+ # It exists to test the constant scoping rules.
+ class Detached
+ DETACHED_CONSTANT = :d
+ end
+
+ class ParentPrivateMethodRedef
+ private
+ def private_method_redefined
+ :before_redefinition
+ end
+ end
+
+ class ChildPrivateMethodMadePublic < ParentPrivateMethodRedef
+ public :private_method_redefined
+ end
+
+ class ParentPrivateMethodRedef
+ def private_method_redefined
+ :after_redefinition
+ end
+ end
+
+ module CyclicAppendA
+ end
+
+ module CyclicAppendB
+ include CyclicAppendA
+ end
+
+ module CyclicPrepend
+ end
+
+ module ExtendObject
+ C = :test
+ def test_method
+ "hello test"
+ end
+ end
+
+ module ExtendObjectPrivate
+ class << self
+ def extend_object(obj)
+ ScratchPad.record :extended
+ end
+ private :extend_object
+ end
+ end
+
+ class CyclicBarrier
+ def initialize(count = 1)
+ @count = count
+ @state = 0
+ @mutex = Mutex.new
+ @cond = ConditionVariable.new
+ end
+
+ def await
+ @mutex.synchronize do
+ @state += 1
+ if @state >= @count
+ @state = 0
+ @cond.broadcast
+ true
+ else
+ @cond.wait @mutex
+ false
+ end
+ end
+ end
+
+ def enabled?
+ @mutex.synchronize { @count != -1 }
+ end
+
+ def disable!
+ @mutex.synchronize do
+ @count = -1
+ @cond.broadcast
+ end
+ end
+ end
+
+ class ThreadSafeCounter
+ def initialize(value = 0)
+ @value = 0
+ @mutex = Mutex.new
+ end
+
+ def get
+ @mutex.synchronize { @value }
+ end
+
+ def increment_and_get
+ @mutex.synchronize do
+ prev_value = @value
+ @value += 1
+ prev_value
+ end
+ end
+ end
+
+ module ShadowingOuter
+ module M
+ SHADOW = 123
+ end
+
+ module N
+ SHADOW = 456
+ end
+ end
+
+ module UnboundMethodTest
+ def foo
+ 'bar'
+ end
+ end
+
+ module ClassEvalTest
+ def self.get_constant_from_scope
+ module_eval("Lookup")
+ end
+
+ def self.get_constant_from_scope_with_send(method)
+ send(method, "Lookup")
+ end
+ end
+
+ class RecordIncludedModules
+ def self.inherited(base)
+ ScratchPad.record base
+ end
+ end
+
+ module SingletonOnModuleCase
+ module Foo
+ class << Foo
+ def included(base)
+ base.included_called
+ super
+ end
+ end
+ end
+
+ class Bar
+ @included_called = false
+
+ class << self
+ def included_called
+ @included_called = true
+ end
+
+ def included_called?
+ @included_called
+ end
+ end
+ end
+ end
+
+ module CaseCompareOnSingleton
+ def self.===(*)
+ raise 'method contents are irrelevant to test'
+ end
+ end
+
+ m = Module.new do
+ def foo
+ end
+ private :foo
+ end
+ EmptyFooMethod = m.instance_method(:foo)
+end
+
+class Object
+ def module_specs_public_method_on_object; end
+
+ def module_specs_private_method_on_object; end
+ private :module_specs_private_method_on_object
+
+ def module_specs_protected_method_on_object; end
+ protected :module_specs_private_method_on_object
+
+ def module_specs_private_method_on_object_for_kernel_public; end
+ private :module_specs_private_method_on_object_for_kernel_public
+
+ def module_specs_public_method_on_object_for_kernel_protected; end
+ def module_specs_public_method_on_object_for_kernel_private; end
+end
+
+module Kernel
+ def module_specs_public_method_on_kernel; end
+
+ alias_method :module_specs_alias_on_kernel, :module_specs_public_method_on_object
+
+ public :module_specs_private_method_on_object_for_kernel_public
+ protected :module_specs_public_method_on_object_for_kernel_protected
+ private :module_specs_public_method_on_object_for_kernel_private
+end
+
+ModuleSpecs::Nesting[:root_level] = Module.nesting
diff --git a/spec/ruby/core/module/fixtures/constant_unicode.rb b/spec/ruby/core/module/fixtures/constant_unicode.rb
new file mode 100644
index 0000000000..415911576d
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/constant_unicode.rb
@@ -0,0 +1,5 @@
+# encoding: utf-8
+
+module ConstantUnicodeSpecs
+ CS_CONSTλ = :const_unicode
+end
diff --git a/spec/ruby/core/module/fixtures/constants_autoload.rb b/spec/ruby/core/module/fixtures/constants_autoload.rb
new file mode 100644
index 0000000000..8e9aa8de0c
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/constants_autoload.rb
@@ -0,0 +1,6 @@
+autoload :CSAutoloadA, fixture(__FILE__, 'constants_autoload_a.rb')
+autoload :CSAutoloadB, fixture(__FILE__, 'constants_autoload_b.rb')
+autoload :CSAutoloadC, fixture(__FILE__, 'constants_autoload_c.rb')
+module CSAutoloadD
+ autoload :InnerModule, fixture(__FILE__, 'constants_autoload_d.rb')
+end
diff --git a/spec/ruby/core/module/fixtures/constants_autoload_a.rb b/spec/ruby/core/module/fixtures/constants_autoload_a.rb
new file mode 100644
index 0000000000..48d3b63681
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/constants_autoload_a.rb
@@ -0,0 +1,2 @@
+module CSAutoloadA
+end
diff --git a/spec/ruby/core/module/fixtures/constants_autoload_b.rb b/spec/ruby/core/module/fixtures/constants_autoload_b.rb
new file mode 100644
index 0000000000..29cd742d03
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/constants_autoload_b.rb
@@ -0,0 +1,2 @@
+module CSAutoloadB
+end
diff --git a/spec/ruby/core/module/fixtures/constants_autoload_c.rb b/spec/ruby/core/module/fixtures/constants_autoload_c.rb
new file mode 100644
index 0000000000..9d6a6bf4d7
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/constants_autoload_c.rb
@@ -0,0 +1,3 @@
+module CSAutoloadC
+ CONST = 7
+end
diff --git a/spec/ruby/core/module/fixtures/constants_autoload_d.rb b/spec/ruby/core/module/fixtures/constants_autoload_d.rb
new file mode 100644
index 0000000000..52d550bab0
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/constants_autoload_d.rb
@@ -0,0 +1,4 @@
+module CSAutoloadD
+ module InnerModule
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/module.rb b/spec/ruby/core/module/fixtures/module.rb
new file mode 100644
index 0000000000..9050a272ec
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/module.rb
@@ -0,0 +1,4 @@
+module ModuleSpecs
+ module Anonymous
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/multi/foo.rb b/spec/ruby/core/module/fixtures/multi/foo.rb
new file mode 100644
index 0000000000..549996f08f
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/multi/foo.rb
@@ -0,0 +1,6 @@
+module ModuleSpecs::Autoload
+ module Foo
+ autoload :Bar, 'foo/bar_baz'
+ autoload :Baz, 'foo/bar_baz'
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/multi/foo/bar_baz.rb b/spec/ruby/core/module/fixtures/multi/foo/bar_baz.rb
new file mode 100644
index 0000000000..53d3849e1f
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/multi/foo/bar_baz.rb
@@ -0,0 +1,11 @@
+require 'foo'
+
+module ModuleSpecs::Autoload
+ module Foo
+ class Bar
+ end
+
+ class Baz
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/name.rb b/spec/ruby/core/module/fixtures/name.rb
new file mode 100644
index 0000000000..fb9e66c309
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/name.rb
@@ -0,0 +1,10 @@
+# -*- encoding: utf-8 -*-
+module ModuleSpecs
+ class NameEncoding
+ class Cß
+ end
+ def name
+ Cß.name
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/path1/load_path.rb b/spec/ruby/core/module/fixtures/path1/load_path.rb
new file mode 100644
index 0000000000..d4c45463dc
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/path1/load_path.rb
@@ -0,0 +1,9 @@
+$LOAD_PATH.unshift(File.expand_path('../../path2', __FILE__))
+
+module ModuleSpecs::Autoload
+ module LoadPath
+ def self.loaded
+ :autoload_load_path
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/path2/load_path.rb b/spec/ruby/core/module/fixtures/path2/load_path.rb
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/path2/load_path.rb
@@ -0,0 +1 @@
+
diff --git a/spec/ruby/core/module/fixtures/refine.rb b/spec/ruby/core/module/fixtures/refine.rb
new file mode 100644
index 0000000000..e8215aa640
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/refine.rb
@@ -0,0 +1,25 @@
+module ModuleSpecs
+ class ClassWithFoo
+ def foo; "foo" end
+ end
+
+ class ClassWithSuperFoo
+ def foo; [:C] end
+ end
+
+ module PrependedModule
+ def foo; "foo from prepended module"; end
+ end
+
+ module IncludedModule
+ def foo; "foo from included module"; end
+ end
+
+ def self.build_refined_class(for_super: false)
+ if for_super
+ Class.new(ClassWithSuperFoo)
+ else
+ Class.new(ClassWithFoo)
+ end
+ end
+end
diff --git a/spec/ruby/core/module/fixtures/repeated_concurrent_autoload.rb b/spec/ruby/core/module/fixtures/repeated_concurrent_autoload.rb
new file mode 100644
index 0000000000..32b770e6cf
--- /dev/null
+++ b/spec/ruby/core/module/fixtures/repeated_concurrent_autoload.rb
@@ -0,0 +1,8 @@
+prev_value = ScratchPad.recorded.increment_and_get
+eval <<-RUBY_EVAL
+ module Mod#{prev_value}
+ sleep(0.05)
+ def self.foo
+ end
+ end
+RUBY_EVAL
diff --git a/spec/ruby/core/module/freeze_spec.rb b/spec/ruby/core/module/freeze_spec.rb
new file mode 100644
index 0000000000..fd76141431
--- /dev/null
+++ b/spec/ruby/core/module/freeze_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#freeze" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/module/gt_spec.rb b/spec/ruby/core/module/gt_spec.rb
new file mode 100644
index 0000000000..b8a73c9ff9
--- /dev/null
+++ b/spec/ruby/core/module/gt_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#>" do
+ it "returns false if self is a subclass of or includes the given module" do
+ (ModuleSpecs::Child > ModuleSpecs::Parent).should be_false
+ (ModuleSpecs::Child > ModuleSpecs::Basic).should be_false
+ (ModuleSpecs::Child > ModuleSpecs::Super).should be_false
+ (ModuleSpecs::Super > ModuleSpecs::Basic).should be_false
+ end
+
+ it "returns true if self is a superclass of or included by the given module" do
+ (ModuleSpecs::Parent > ModuleSpecs::Child).should == true
+ (ModuleSpecs::Basic > ModuleSpecs::Child).should == true
+ (ModuleSpecs::Super > ModuleSpecs::Child).should == true
+ (ModuleSpecs::Basic > ModuleSpecs::Super).should == true
+ end
+
+ it "returns false if self is the same as the given module" do
+ (ModuleSpecs::Child > ModuleSpecs::Child).should == false
+ (ModuleSpecs::Parent > ModuleSpecs::Parent).should == false
+ (ModuleSpecs::Basic > ModuleSpecs::Basic).should == false
+ (ModuleSpecs::Super > ModuleSpecs::Super).should == false
+ end
+
+ it "returns nil if self is not related to the given module" do
+ (ModuleSpecs::Parent > ModuleSpecs::Basic).should == nil
+ (ModuleSpecs::Parent > ModuleSpecs::Super).should == nil
+ (ModuleSpecs::Basic > ModuleSpecs::Parent).should == nil
+ (ModuleSpecs::Super > ModuleSpecs::Parent).should == nil
+ end
+
+ it "raises a TypeError if the argument is not a class/module" do
+ -> { ModuleSpecs::Parent > mock('x') }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/module/gte_spec.rb b/spec/ruby/core/module/gte_spec.rb
new file mode 100644
index 0000000000..18c60ba586
--- /dev/null
+++ b/spec/ruby/core/module/gte_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#>=" do
+ it "returns true if self is a superclass of, the same as or included by given module" do
+ (ModuleSpecs::Parent >= ModuleSpecs::Child).should == true
+ (ModuleSpecs::Basic >= ModuleSpecs::Child).should == true
+ (ModuleSpecs::Super >= ModuleSpecs::Child).should == true
+ (ModuleSpecs::Basic >= ModuleSpecs::Super).should == true
+ (ModuleSpecs::Child >= ModuleSpecs::Child).should == true
+ (ModuleSpecs::Parent >= ModuleSpecs::Parent).should == true
+ (ModuleSpecs::Basic >= ModuleSpecs::Basic).should == true
+ (ModuleSpecs::Super >= ModuleSpecs::Super).should == true
+ end
+
+ it "returns nil if self is not related to the given module" do
+ (ModuleSpecs::Parent >= ModuleSpecs::Basic).should == nil
+ (ModuleSpecs::Parent >= ModuleSpecs::Super).should == nil
+ (ModuleSpecs::Basic >= ModuleSpecs::Parent).should == nil
+ (ModuleSpecs::Super >= ModuleSpecs::Parent).should == nil
+ end
+
+ it "returns false if self is a subclass of or includes the given module" do
+ (ModuleSpecs::Child >= ModuleSpecs::Parent).should == false
+ (ModuleSpecs::Child >= ModuleSpecs::Basic).should == false
+ (ModuleSpecs::Child >= ModuleSpecs::Super).should == false
+ (ModuleSpecs::Super >= ModuleSpecs::Basic).should == false
+ end
+
+ it "raises a TypeError if the argument is not a class/module" do
+ -> { ModuleSpecs::Parent >= mock('x') }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/module/include_spec.rb b/spec/ruby/core/module/include_spec.rb
new file mode 100644
index 0000000000..c073bc31ca
--- /dev/null
+++ b/spec/ruby/core/module/include_spec.rb
@@ -0,0 +1,577 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#include" do
+ it "is a public method" do
+ Module.should have_public_instance_method(:include, false)
+ end
+
+ it "calls #append_features(self) in reversed order on each module" do
+ $appended_modules = []
+
+ m = Module.new do
+ def self.append_features(mod)
+ $appended_modules << [ self, mod ]
+ end
+ end
+
+ m2 = Module.new do
+ def self.append_features(mod)
+ $appended_modules << [ self, mod ]
+ end
+ end
+
+ m3 = Module.new do
+ def self.append_features(mod)
+ $appended_modules << [ self, mod ]
+ end
+ end
+
+ c = Class.new { include(m, m2, m3) }
+
+ $appended_modules.should == [ [ m3, c], [ m2, c ], [ m, c ] ]
+ end
+
+ it "adds all ancestor modules when a previously included module is included again" do
+ ModuleSpecs::MultipleIncludes.ancestors.should include(ModuleSpecs::MA, ModuleSpecs::MB)
+ ModuleSpecs::MB.include(ModuleSpecs::MC)
+ ModuleSpecs::MultipleIncludes.include(ModuleSpecs::MB)
+ ModuleSpecs::MultipleIncludes.ancestors.should include(ModuleSpecs::MA, ModuleSpecs::MB, ModuleSpecs::MC)
+ end
+
+ it "raises a TypeError when the argument is not a Module" do
+ -> { ModuleSpecs::Basic.include(Class.new) }.should raise_error(TypeError)
+ end
+
+ it "does not raise a TypeError when the argument is an instance of a subclass of Module" do
+ -> { ModuleSpecs::SubclassSpec.include(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError)
+ end
+
+ it "imports constants to modules and classes" do
+ ModuleSpecs::A.constants.should include(:CONSTANT_A)
+ ModuleSpecs::B.constants.should include(:CONSTANT_A, :CONSTANT_B)
+ ModuleSpecs::C.constants.should include(:CONSTANT_A, :CONSTANT_B)
+ end
+
+ it "shadows constants from ancestors" do
+ klass = Class.new
+ klass.include ModuleSpecs::ShadowingOuter::M
+ klass::SHADOW.should == 123
+ klass.include ModuleSpecs::ShadowingOuter::N
+ klass::SHADOW.should == 456
+ end
+
+ it "does not override existing constants in modules and classes" do
+ ModuleSpecs::A::OVERRIDE.should == :a
+ ModuleSpecs::B::OVERRIDE.should == :b
+ ModuleSpecs::C::OVERRIDE.should == :c
+ end
+
+ it "imports instance methods to modules and classes" do
+ ModuleSpecs::A.instance_methods.should include(:ma)
+ ModuleSpecs::B.instance_methods.should include(:ma,:mb)
+ ModuleSpecs::C.instance_methods.should include(:ma,:mb)
+ end
+
+ it "does not import methods to modules and classes" do
+ ModuleSpecs::A.methods.include?(:cma).should == true
+ ModuleSpecs::B.methods.include?(:cma).should == false
+ ModuleSpecs::B.methods.include?(:cmb).should == true
+ ModuleSpecs::C.methods.include?(:cma).should == false
+ ModuleSpecs::C.methods.include?(:cmb).should == false
+ end
+
+ it "attaches the module as the caller's immediate ancestor" do
+ module IncludeSpecsTop
+ def value; 5; end
+ end
+
+ module IncludeSpecsMiddle
+ include IncludeSpecsTop
+ def value; 6; end
+ end
+
+ class IncludeSpecsClass
+ include IncludeSpecsMiddle
+ end
+
+ IncludeSpecsClass.new.value.should == 6
+ end
+
+ it "doesn't include module if it is included in a super class" do
+ module ModuleSpecs::M1
+ module M; end
+ class A; include M; end
+ class B < A; include M; end
+
+ all = [A, B, M]
+
+ (B.ancestors.filter { |a| all.include?(a) }).should == [B, A, M]
+ end
+ end
+
+ it "recursively includes new mixins" do
+ module ModuleSpecs::M1
+ module U; end
+ module V; end
+ module W; end
+ module X; end
+ module Y; end
+ class A; include X; end;
+ class B < A; include U, V, W; end;
+
+ # update V
+ module V; include X, U, Y; end
+
+ # This code used to use Array#& and then compare 2 arrays, but
+ # the ordering from Array#& is undefined, as it uses Hash internally.
+ #
+ # Loop is more verbose, but more explicit in what we're testing.
+
+ anc = B.ancestors
+ [B, U, V, W, A, X].each do |i|
+ anc.include?(i).should be_true
+ end
+
+ class B; include V; end
+
+ # the only new module is Y, it is added after U since it follows U in V mixin list:
+ anc = B.ancestors
+ [B, U, Y, V, W, A, X].each do |i|
+ anc.include?(i).should be_true
+ end
+ end
+ end
+
+ it "preserves ancestor order" do
+ module ModuleSpecs::M2
+ module M1; end
+ module M2; end
+ module M3; include M2; end
+
+ module M2; include M1; end
+ module M3; include M2; end
+
+ M3.ancestors.should == [M3, M2, M1]
+
+ end
+ end
+
+ it "detects cyclic includes" do
+ -> {
+ module ModuleSpecs::M
+ include ModuleSpecs::M
+ end
+ }.should raise_error(ArgumentError)
+ end
+
+ it "doesn't accept no-arguments" do
+ -> {
+ Module.new do
+ include
+ end
+ }.should raise_error(ArgumentError)
+ end
+
+ it "returns the class it's included into" do
+ m = Module.new
+ r = nil
+ c = Class.new { r = include m }
+ r.should == c
+ end
+
+ it "ignores modules it has already included via module mutual inclusion" do
+ module ModuleSpecs::AlreadyInc
+ module M0
+ end
+
+ module M
+ include M0
+ end
+
+ class K
+ include M
+ include M
+ end
+
+ K.ancestors[0].should == K
+ K.ancestors[1].should == M
+ K.ancestors[2].should == M0
+ end
+ end
+
+ it "clears any caches" do
+ module ModuleSpecs::M3
+ module M1
+ def foo
+ :m1
+ end
+ end
+
+ module M2
+ def foo
+ :m2
+ end
+ end
+
+ class C
+ include M1
+
+ def get
+ foo
+ end
+ end
+
+ c = C.new
+ c.get.should == :m1
+
+ class C
+ include M2
+ end
+
+ c.get.should == :m2
+
+ remove_const :C
+ end
+ end
+
+ it "updates the method when an included module is updated" do
+ a_class = Class.new do
+ def foo
+ 'a'
+ end
+ end
+
+ m_module = Module.new
+
+ b_class = Class.new(a_class) do
+ include m_module
+ end
+
+ b = b_class.new
+
+ foo = -> { b.foo }
+
+ foo.call.should == 'a'
+
+ m_module.module_eval do
+ def foo
+ 'm'
+ end
+ end
+
+ foo.call.should == 'm'
+ end
+
+
+ it "updates the method when a module included after a call is later updated" do
+ m_module = Module.new
+ a_class = Class.new do
+ def foo
+ 'a'
+ end
+ end
+ b_class = Class.new(a_class)
+ b = b_class.new
+ foo = -> { b.foo }
+ foo.call.should == 'a'
+
+ b_class.include m_module
+ foo.call.should == 'a'
+
+ m_module.module_eval do
+ def foo
+ "m"
+ end
+ end
+ foo.call.should == 'm'
+ end
+
+ it "updates the method when a nested included module is updated" do
+ a_class = Class.new do
+ def foo
+ 'a'
+ end
+ end
+
+ n_module = Module.new
+
+ m_module = Module.new do
+ include n_module
+ end
+
+ b_class = Class.new(a_class) do
+ include m_module
+ end
+
+ b = b_class.new
+
+ foo = -> { b.foo }
+
+ foo.call.should == 'a'
+
+ n_module.module_eval do
+ def foo
+ 'n'
+ end
+ end
+
+ foo.call.should == 'n'
+ end
+
+ it "updates the method when a new module is included" do
+ a_class = Class.new do
+ def foo
+ 'a'
+ end
+ end
+
+ m_module = Module.new do
+ def foo
+ 'm'
+ end
+ end
+
+ b_class = Class.new(a_class)
+ b = b_class.new
+
+ foo = -> { b.foo }
+
+ foo.call.should == 'a'
+
+ b_class.class_eval do
+ include m_module
+ end
+
+ foo.call.should == 'm'
+ end
+
+ it "updates the method when a new module with nested module is included" do
+ a_class = Class.new do
+ def foo
+ 'a'
+ end
+ end
+
+ n_module = Module.new do
+ def foo
+ 'n'
+ end
+ end
+
+ m_module = Module.new do
+ include n_module
+ end
+
+ b_class = Class.new(a_class)
+ b = b_class.new
+
+ foo = -> { b.foo }
+
+ foo.call.should == 'a'
+
+ b_class.class_eval do
+ include m_module
+ end
+
+ foo.call.should == 'n'
+ end
+
+ it "updates the constant when an included module is updated" do
+ module ModuleSpecs::ConstUpdated
+ module A
+ FOO = 'a'
+ end
+
+ module M
+ end
+
+ module B
+ include A
+ include M
+ def self.foo
+ FOO
+ end
+ end
+
+ B.foo.should == 'a'
+
+ M.const_set(:FOO, 'm')
+ B.foo.should == 'm'
+ end
+ end
+
+ it "updates the constant when a module included after a call is later updated" do
+ module ModuleSpecs::ConstLaterUpdated
+ module A
+ FOO = 'a'
+ end
+
+ module B
+ include A
+ def self.foo
+ FOO
+ end
+ end
+
+ B.foo.should == 'a'
+
+ module M
+ end
+ B.include M
+
+ B.foo.should == 'a'
+
+ M.const_set(:FOO, 'm')
+ B.foo.should == 'm'
+ end
+ end
+
+ it "updates the constant when a module included in another module after a call is later updated" do
+ module ModuleSpecs::ConstModuleLaterUpdated
+ module A
+ FOO = 'a'
+ end
+
+ module B
+ include A
+ def self.foo
+ FOO
+ end
+ end
+
+ B.foo.should == 'a'
+
+ module M
+ end
+ B.include M
+
+ B.foo.should == 'a'
+
+ M.const_set(:FOO, 'm')
+ B.foo.should == 'm'
+ end
+ end
+
+ it "updates the constant when a nested included module is updated" do
+ module ModuleSpecs::ConstUpdatedNestedIncludeUpdated
+ module A
+ FOO = 'a'
+ end
+
+ module N
+ end
+
+ module M
+ include N
+ end
+
+ module B
+ include A
+ include M
+ def self.foo
+ FOO
+ end
+ end
+
+ B.foo.should == 'a'
+
+ N.const_set(:FOO, 'n')
+ B.foo.should == 'n'
+ end
+ end
+
+ it "updates the constant when a new module is included" do
+ module ModuleSpecs::ConstUpdatedNewInclude
+ module A
+ FOO = 'a'
+ end
+
+ module M
+ FOO = 'm'
+ end
+
+ module B
+ include A
+ def self.foo
+ FOO
+ end
+ end
+
+ B.foo.should == 'a'
+
+ B.include(M)
+ B.foo.should == 'm'
+ end
+ end
+
+ it "updates the constant when a new module with nested module is included" do
+ module ModuleSpecs::ConstUpdatedNestedIncluded
+ module A
+ FOO = 'a'
+ end
+
+ module N
+ FOO = 'n'
+ end
+
+ module M
+ include N
+ end
+
+ module B
+ include A
+ def self.foo
+ FOO
+ end
+ end
+
+ B.foo.should == 'a'
+
+ B.include M
+ B.foo.should == 'n'
+ end
+ end
+
+ it "overrides a previous super method call" do
+ c1 = Class.new do
+ def foo
+ [:c1]
+ end
+ end
+ c2 = Class.new(c1) do
+ def foo
+ [:c2] + super
+ end
+ end
+ c2.new.foo.should == [:c2, :c1]
+ m = Module.new do
+ def foo
+ [:m1]
+ end
+ end
+ c2.include(m)
+ c2.new.foo.should == [:c2, :m1]
+ end
+end
+
+describe "Module#include?" do
+ it "returns true if the given module is included by self or one of it's ancestors" do
+ ModuleSpecs::Super.include?(ModuleSpecs::Basic).should == true
+ ModuleSpecs::Child.include?(ModuleSpecs::Basic).should == true
+ ModuleSpecs::Child.include?(ModuleSpecs::Super).should == true
+ ModuleSpecs::Child.include?(Kernel).should == true
+
+ ModuleSpecs::Parent.include?(ModuleSpecs::Basic).should == false
+ ModuleSpecs::Basic.include?(ModuleSpecs::Super).should == false
+ end
+
+ it "returns false if given module is equal to self" do
+ ModuleSpecs.include?(ModuleSpecs).should == false
+ end
+
+ it "raises a TypeError when no module was given" do
+ -> { ModuleSpecs::Child.include?("Test") }.should raise_error(TypeError)
+ -> { ModuleSpecs::Child.include?(ModuleSpecs::Parent) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/module/included_modules_spec.rb b/spec/ruby/core/module/included_modules_spec.rb
new file mode 100644
index 0000000000..ce94ed1285
--- /dev/null
+++ b/spec/ruby/core/module/included_modules_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#included_modules" do
+ it "returns a list of modules included in self" do
+ ModuleSpecs.included_modules.should == []
+
+ ModuleSpecs::Child.included_modules.should include(ModuleSpecs::Super, ModuleSpecs::Basic, Kernel)
+ ModuleSpecs::Parent.included_modules.should include(Kernel)
+ ModuleSpecs::Basic.included_modules.should == []
+ ModuleSpecs::Super.included_modules.should include(ModuleSpecs::Basic)
+ ModuleSpecs::WithPrependedModule.included_modules.should include(ModuleSpecs::ModuleWithPrepend)
+ end
+end
diff --git a/spec/ruby/core/module/included_spec.rb b/spec/ruby/core/module/included_spec.rb
new file mode 100644
index 0000000000..2fdd4325f2
--- /dev/null
+++ b/spec/ruby/core/module/included_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#included" do
+ it "is invoked when self is included in another module or class" do
+ begin
+ m = Module.new do
+ def self.included(o)
+ $included_by = o
+ end
+ end
+
+ c = Class.new { include m }
+
+ $included_by.should == c
+ ensure
+ $included_by = nil
+ end
+ end
+
+ it "allows extending self with the object into which it is being included" do
+ m = Module.new do
+ def self.included(o)
+ o.extend(self)
+ end
+
+ def test
+ :passed
+ end
+ end
+
+ c = Class.new{ include(m) }
+ c.test.should == :passed
+ end
+
+ it "is private in its default implementation" do
+ Module.should have_private_instance_method(:included)
+ end
+
+ it "works with super using a singleton class" do
+ ModuleSpecs::SingletonOnModuleCase::Bar.include ModuleSpecs::SingletonOnModuleCase::Foo
+ ModuleSpecs::SingletonOnModuleCase::Bar.should.included_called?
+ end
+end
diff --git a/spec/ruby/core/module/initialize_copy_spec.rb b/spec/ruby/core/module/initialize_copy_spec.rb
new file mode 100644
index 0000000000..7ae48f85a9
--- /dev/null
+++ b/spec/ruby/core/module/initialize_copy_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe "Module#initialize_copy" do
+ it "should retain singleton methods when duped" do
+ mod = Module.new
+ def mod.hello
+ end
+ mod.dup.methods(false).should == [:hello]
+ end
+
+ # jruby/jruby#5245, https://bugs.ruby-lang.org/issues/3461
+ it "should produce a duped module with inspectable class methods" do
+ mod = Module.new
+ def mod.hello
+ end
+ mod.dup.method(:hello).inspect.should =~ /Module.*hello/
+ end
+end
diff --git a/spec/ruby/core/module/initialize_spec.rb b/spec/ruby/core/module/initialize_spec.rb
new file mode 100644
index 0000000000..99e41e4619
--- /dev/null
+++ b/spec/ruby/core/module/initialize_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#initialize" do
+ it "accepts a block" do
+ m = Module.new do
+ const_set :A, "A"
+ end
+ m.const_get("A").should == "A"
+ end
+
+ it "is called on subclasses" do
+ m = ModuleSpecs::SubModule.new
+ m.special.should == 10
+ m.methods.should_not == nil
+ m.constants.should_not == nil
+ end
+end
diff --git a/spec/ruby/core/module/instance_method_spec.rb b/spec/ruby/core/module/instance_method_spec.rb
new file mode 100644
index 0000000000..8d006e647e
--- /dev/null
+++ b/spec/ruby/core/module/instance_method_spec.rb
@@ -0,0 +1,111 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#instance_method" do
+ before :all do
+ @parent_um = ModuleSpecs::InstanceMeth.instance_method(:foo)
+ @child_um = ModuleSpecs::InstanceMethChild.instance_method(:foo)
+ @mod_um = ModuleSpecs::InstanceMethChild.instance_method(:bar)
+ end
+
+ it "is a public method" do
+ Module.should have_public_instance_method(:instance_method, false)
+ end
+
+ it "requires an argument" do
+ Module.new.method(:instance_method).arity.should == 1
+ end
+
+ it "returns an UnboundMethod corresponding to the given name" do
+ @parent_um.should be_kind_of(UnboundMethod)
+ @parent_um.bind(ModuleSpecs::InstanceMeth.new).call.should == :foo
+ end
+
+ it "returns an UnboundMethod corresponding to the given name from a superclass" do
+ @child_um.should be_kind_of(UnboundMethod)
+ @child_um.bind(ModuleSpecs::InstanceMethChild.new).call.should == :foo
+ end
+
+ it "returns an UnboundMethod corresponding to the given name from an included Module" do
+ @mod_um.should be_kind_of(UnboundMethod)
+ @mod_um.bind(ModuleSpecs::InstanceMethChild.new).call.should == :bar
+ end
+
+ it "returns an UnboundMethod when given a protected method name" do
+ ModuleSpecs::Basic.instance_method(:protected_module).should be_an_instance_of(UnboundMethod)
+ end
+
+ it "returns an UnboundMethod when given a private method name" do
+ ModuleSpecs::Basic.instance_method(:private_module).should be_an_instance_of(UnboundMethod)
+ end
+
+ it "gives UnboundMethod method name, Module defined in and Module extracted from" do
+ @parent_um.inspect.should =~ /\bfoo\b/
+ @parent_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/
+ @parent_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/
+ @child_um.inspect.should =~ /\bfoo\b/
+ @child_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/
+
+ @mod_um.inspect.should =~ /\bbar\b/
+ @mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethMod\b/
+
+ ruby_version_is ""..."3.2" do
+ @child_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/
+ @mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/
+ end
+ end
+
+ it "raises a TypeError if the given name is not a String/Symbol" do
+ -> { Object.instance_method([]) }.should raise_error(TypeError, /is not a symbol nor a string/)
+ -> { Object.instance_method(0) }.should raise_error(TypeError, /is not a symbol nor a string/)
+ -> { Object.instance_method(nil) }.should raise_error(TypeError, /is not a symbol nor a string/)
+ -> { Object.instance_method(mock('x')) }.should raise_error(TypeError, /is not a symbol nor a string/)
+ end
+
+ it "accepts String name argument" do
+ method = ModuleSpecs::InstanceMeth.instance_method(:foo)
+ method.should be_kind_of(UnboundMethod)
+ end
+
+ it "accepts Symbol name argument" do
+ method = ModuleSpecs::InstanceMeth.instance_method("foo")
+ method.should be_kind_of(UnboundMethod)
+ end
+
+ it "converts non-String name by calling #to_str method" do
+ obj = Object.new
+ def obj.to_str() "foo" end
+
+ method = ModuleSpecs::InstanceMeth.instance_method(obj)
+ method.should be_kind_of(UnboundMethod)
+ end
+
+ it "raises TypeError when passed non-String name and #to_str returns non-String value" do
+ obj = Object.new
+ def obj.to_str() [] end
+
+ -> { ModuleSpecs::InstanceMeth.instance_method(obj) }.should raise_error(TypeError, /can't convert Object to String/)
+ end
+
+ it "raises a NameError if the method has been undefined" do
+ child = Class.new(ModuleSpecs::InstanceMeth)
+ child.send :undef_method, :foo
+ um = ModuleSpecs::InstanceMeth.instance_method(:foo)
+ um.should == @parent_um
+ -> do
+ child.instance_method(:foo)
+ end.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the method does not exist" do
+ -> { Object.instance_method(:missing) }.should raise_error(NameError)
+ end
+
+ it "sets the NameError#name attribute to the name of the missing method" do
+ begin
+ Object.instance_method(:missing)
+ rescue NameError => e
+ e.name.should == :missing
+ end
+ end
+end
diff --git a/spec/ruby/core/module/instance_methods_spec.rb b/spec/ruby/core/module/instance_methods_spec.rb
new file mode 100644
index 0000000000..d2d38cdda2
--- /dev/null
+++ b/spec/ruby/core/module/instance_methods_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#instance_methods" do
+ it "does not return methods undefined in a superclass" do
+ methods = ModuleSpecs::Parent.instance_methods(false)
+ methods.should_not include(:undefed_method)
+ end
+
+ it "only includes module methods on an included module" do
+ methods = ModuleSpecs::Basic.instance_methods(false)
+ methods.should include(:public_module)
+ # Child is an including class
+ methods = ModuleSpecs::Child.instance_methods(false)
+ methods.should include(:public_child)
+ methods.should_not include(:public_module)
+ end
+
+ it "does not return methods undefined in a subclass" do
+ methods = ModuleSpecs::Grandchild.instance_methods
+ methods.should_not include(:parent_method, :another_parent_method)
+ end
+
+ it "does not return methods undefined in the current class" do
+ class ModuleSpecs::Child
+ def undefed_child
+ end
+ end
+ ModuleSpecs::Child.send(:undef_method, :undefed_child)
+ methods = ModuleSpecs::Child.instance_methods
+ methods.should_not include(:undefed_method, :undefed_child)
+ end
+
+ it "does not return methods from an included module that are undefined in the class" do
+ ModuleSpecs::Grandchild.instance_methods.should_not include(:super_included_method)
+ end
+
+ it "returns the public and protected methods of self if include_super is false" do
+ methods = ModuleSpecs::Parent.instance_methods(false)
+ methods.should include(:protected_parent, :public_parent)
+
+ methods = ModuleSpecs::Child.instance_methods(false)
+ methods.should include(:protected_child, :public_child)
+ end
+
+ it "returns the public and protected methods of self and it's ancestors" do
+ methods = ModuleSpecs::Basic.instance_methods
+ methods.should include(:protected_module, :public_module)
+
+ methods = ModuleSpecs::Super.instance_methods
+ methods.should include(:protected_module, :protected_super_module,
+ :public_module, :public_super_module)
+ end
+
+ it "makes a private Object instance method public in Kernel" do
+ methods = Kernel.instance_methods
+ methods.should include(:module_specs_private_method_on_object_for_kernel_public)
+ methods = Object.instance_methods
+ methods.should_not include(:module_specs_private_method_on_object_for_kernel_public)
+ end
+end
diff --git a/spec/ruby/core/module/lt_spec.rb b/spec/ruby/core/module/lt_spec.rb
new file mode 100644
index 0000000000..d7771e07a8
--- /dev/null
+++ b/spec/ruby/core/module/lt_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#<" do
+ it "returns true if self is a subclass of or includes the given module" do
+ (ModuleSpecs::Child < ModuleSpecs::Parent).should == true
+ (ModuleSpecs::Child < ModuleSpecs::Basic).should == true
+ (ModuleSpecs::Child < ModuleSpecs::Super).should == true
+ (ModuleSpecs::Super < ModuleSpecs::Basic).should == true
+ end
+
+ it "returns false if self is a superclass of or included by the given module" do
+ (ModuleSpecs::Parent < ModuleSpecs::Child).should be_false
+ (ModuleSpecs::Basic < ModuleSpecs::Child).should be_false
+ (ModuleSpecs::Super < ModuleSpecs::Child).should be_false
+ (ModuleSpecs::Basic < ModuleSpecs::Super).should be_false
+ end
+
+ it "returns false if self is the same as the given module" do
+ (ModuleSpecs::Child < ModuleSpecs::Child).should == false
+ (ModuleSpecs::Parent < ModuleSpecs::Parent).should == false
+ (ModuleSpecs::Basic < ModuleSpecs::Basic).should == false
+ (ModuleSpecs::Super < ModuleSpecs::Super).should == false
+ end
+
+ it "returns nil if self is not related to the given module" do
+ (ModuleSpecs::Parent < ModuleSpecs::Basic).should == nil
+ (ModuleSpecs::Parent < ModuleSpecs::Super).should == nil
+ (ModuleSpecs::Basic < ModuleSpecs::Parent).should == nil
+ (ModuleSpecs::Super < ModuleSpecs::Parent).should == nil
+ end
+
+ it "raises a TypeError if the argument is not a class/module" do
+ -> { ModuleSpecs::Parent < mock('x') }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/module/lte_spec.rb b/spec/ruby/core/module/lte_spec.rb
new file mode 100644
index 0000000000..7a0e8496b8
--- /dev/null
+++ b/spec/ruby/core/module/lte_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#<=" do
+ it "returns true if self is a subclass of, the same as or includes the given module" do
+ (ModuleSpecs::Child <= ModuleSpecs::Parent).should == true
+ (ModuleSpecs::Child <= ModuleSpecs::Basic).should == true
+ (ModuleSpecs::Child <= ModuleSpecs::Super).should == true
+ (ModuleSpecs::Super <= ModuleSpecs::Basic).should == true
+ (ModuleSpecs::Child <= ModuleSpecs::Child).should == true
+ (ModuleSpecs::Parent <= ModuleSpecs::Parent).should == true
+ (ModuleSpecs::Basic <= ModuleSpecs::Basic).should == true
+ (ModuleSpecs::Super <= ModuleSpecs::Super).should == true
+ end
+
+ it "returns nil if self is not related to the given module" do
+ (ModuleSpecs::Parent <= ModuleSpecs::Basic).should == nil
+ (ModuleSpecs::Parent <= ModuleSpecs::Super).should == nil
+ (ModuleSpecs::Basic <= ModuleSpecs::Parent).should == nil
+ (ModuleSpecs::Super <= ModuleSpecs::Parent).should == nil
+ end
+
+ it "returns false if self is a superclass of or is included by the given module" do
+ (ModuleSpecs::Parent <= ModuleSpecs::Child).should == false
+ (ModuleSpecs::Basic <= ModuleSpecs::Child).should == false
+ (ModuleSpecs::Super <= ModuleSpecs::Child).should == false
+ (ModuleSpecs::Basic <= ModuleSpecs::Super).should == false
+ end
+
+ it "raises a TypeError if the argument is not a class/module" do
+ -> { ModuleSpecs::Parent <= mock('x') }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/module/method_added_spec.rb b/spec/ruby/core/module/method_added_spec.rb
new file mode 100644
index 0000000000..b983c8da76
--- /dev/null
+++ b/spec/ruby/core/module/method_added_spec.rb
@@ -0,0 +1,83 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#method_added" do
+ it "is a private instance method" do
+ Module.should have_private_instance_method(:method_added)
+ end
+
+ it "returns nil in the default implementation" do
+ Module.new do
+ method_added(:test).should == nil
+ end
+ end
+
+ it "is called when a new instance method is defined in self" do
+ ScratchPad.record []
+
+ Module.new do
+ def self.method_added(name)
+ ScratchPad << name
+ end
+
+ def test() end
+ def test2() end
+ def test() end
+ alias_method :aliased_test, :test
+ alias aliased_test2 test
+ end
+
+ ScratchPad.recorded.should == [:test, :test2, :test, :aliased_test, :aliased_test2]
+ end
+
+ it "is not called when a singleton method is added" do
+ # obj.singleton_method_added is called instead
+ ScratchPad.record []
+
+ klass = Class.new
+ def klass.method_added(name)
+ ScratchPad << name
+ end
+
+ obj = klass.new
+ def obj.new_singleton_method
+ end
+
+ ScratchPad.recorded.should == []
+ end
+
+ it "is not called when a method is undefined in self" do
+ m = Module.new do
+ def method_to_undef
+ end
+
+ def self.method_added(name)
+ fail("method_added called by undef_method")
+ end
+
+ undef_method :method_to_undef
+ end
+ m.should_not have_method(:method_to_undef)
+ end
+
+ it "is called with a precise caller location with the line of the 'def'" do
+ ScratchPad.record []
+ line = nil
+
+ Module.new do
+ def self.method_added(name)
+ location = caller_locations(1, 1)[0]
+ ScratchPad << location.lineno
+ end
+
+ line = __LINE__
+ def first
+ end
+
+ def second
+ end
+ end
+
+ ScratchPad.recorded.should == [line + 1, line + 4]
+ end
+end
diff --git a/spec/ruby/core/module/method_defined_spec.rb b/spec/ruby/core/module/method_defined_spec.rb
new file mode 100644
index 0000000000..bc5b420e11
--- /dev/null
+++ b/spec/ruby/core/module/method_defined_spec.rb
@@ -0,0 +1,98 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#method_defined?" do
+ it "returns true if a public or private method with the given name is defined in self, self's ancestors or one of self's included modules" do
+ # Defined in Child
+ ModuleSpecs::Child.method_defined?(:public_child).should == true
+ ModuleSpecs::Child.method_defined?("private_child").should == false
+ ModuleSpecs::Child.method_defined?(:accessor_method).should == true
+
+ # Defined in Parent
+ ModuleSpecs::Child.method_defined?("public_parent").should == true
+ ModuleSpecs::Child.method_defined?(:private_parent).should == false
+
+ # Defined in Module
+ ModuleSpecs::Child.method_defined?(:public_module).should == true
+ ModuleSpecs::Child.method_defined?(:protected_module).should == true
+ ModuleSpecs::Child.method_defined?(:private_module).should == false
+
+ # Defined in SuperModule
+ ModuleSpecs::Child.method_defined?(:public_super_module).should == true
+ ModuleSpecs::Child.method_defined?(:protected_super_module).should == true
+ ModuleSpecs::Child.method_defined?(:private_super_module).should == false
+ end
+
+ # unlike alias_method, module_function, public, and friends,
+ it "does not search Object or Kernel when called on a module" do
+ m = Module.new
+
+ m.method_defined?(:module_specs_public_method_on_kernel).should be_false
+ end
+
+ it "raises a TypeError when the given object is not a string/symbol" do
+ c = Class.new
+ o = mock('123')
+
+ -> { c.method_defined?(o) }.should raise_error(TypeError)
+
+ o.should_receive(:to_str).and_return(123)
+ -> { c.method_defined?(o) }.should raise_error(TypeError)
+ end
+
+ it "converts the given name to a string using to_str" do
+ c = Class.new { def test(); end }
+ (o = mock('test')).should_receive(:to_str).and_return("test")
+
+ c.method_defined?(o).should == true
+ end
+
+ # works as method_defined?(method_name)
+ describe "when passed true as a second optional argument" do
+ it "performs a lookup in ancestors" do
+ ModuleSpecs::Child.method_defined?(:public_child, true).should == true
+ ModuleSpecs::Child.method_defined?(:protected_child, true).should == true
+ ModuleSpecs::Child.method_defined?(:accessor_method, true).should == true
+ ModuleSpecs::Child.method_defined?(:private_child, true).should == false
+
+ # Defined in Parent
+ ModuleSpecs::Child.method_defined?(:public_parent, true).should == true
+ ModuleSpecs::Child.method_defined?(:protected_parent, true).should == true
+ ModuleSpecs::Child.method_defined?(:private_parent, true).should == false
+
+ # Defined in Module
+ ModuleSpecs::Child.method_defined?(:public_module, true).should == true
+ ModuleSpecs::Child.method_defined?(:protected_module, true).should == true
+ ModuleSpecs::Child.method_defined?(:private_module, true).should == false
+
+ # Defined in SuperModule
+ ModuleSpecs::Child.method_defined?(:public_super_module, true).should == true
+ ModuleSpecs::Child.method_defined?(:protected_super_module, true).should == true
+ ModuleSpecs::Child.method_defined?(:private_super_module, true).should == false
+ end
+ end
+
+ describe "when passed false as a second optional argument" do
+ it "checks only the class itself" do
+ ModuleSpecs::Child.method_defined?(:public_child, false).should == true
+ ModuleSpecs::Child.method_defined?(:protected_child, false).should == true
+ ModuleSpecs::Child.method_defined?(:accessor_method, false).should == true
+ ModuleSpecs::Child.method_defined?(:private_child, false).should == false
+
+ # Defined in Parent
+ ModuleSpecs::Child.method_defined?(:public_parent, false).should == false
+ ModuleSpecs::Child.method_defined?(:protected_parent, false).should == false
+ ModuleSpecs::Child.method_defined?(:private_parent, false).should == false
+
+ # Defined in Module
+ ModuleSpecs::Child.method_defined?(:public_module, false).should == false
+ ModuleSpecs::Child.method_defined?(:protected_module, false).should == false
+ ModuleSpecs::Child.method_defined?(:private_module, false).should == false
+
+ # Defined in SuperModule
+ ModuleSpecs::Child.method_defined?(:public_super_module, false).should == false
+ ModuleSpecs::Child.method_defined?(:protected_super_module, false).should == false
+ ModuleSpecs::Child.method_defined?(:private_super_module, false).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/module/method_removed_spec.rb b/spec/ruby/core/module/method_removed_spec.rb
new file mode 100644
index 0000000000..9b39eb77a6
--- /dev/null
+++ b/spec/ruby/core/module/method_removed_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#method_removed" do
+ it "is a private instance method" do
+ Module.should have_private_instance_method(:method_removed)
+ end
+
+ it "returns nil in the default implementation" do
+ Module.new do
+ method_removed(:test).should == nil
+ end
+ end
+
+ it "is called when a method is removed from self" do
+ begin
+ Module.new do
+ def self.method_removed(name)
+ $method_removed = name
+ end
+
+ def test
+ "test"
+ end
+ remove_method :test
+ end
+
+ $method_removed.should == :test
+ ensure
+ $method_removed = nil
+ end
+ end
+end
diff --git a/spec/ruby/core/module/method_undefined_spec.rb b/spec/ruby/core/module/method_undefined_spec.rb
new file mode 100644
index 0000000000..a94388fe0a
--- /dev/null
+++ b/spec/ruby/core/module/method_undefined_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#method_undefined" do
+ it "is a private instance method" do
+ Module.should have_private_instance_method(:method_undefined)
+ end
+
+ it "returns nil in the default implementation" do
+ Module.new do
+ method_undefined(:test).should == nil
+ end
+ end
+
+ it "is called when a method is undefined from self" do
+ begin
+ Module.new do
+ def self.method_undefined(name)
+ $method_undefined = name
+ end
+
+ def test
+ "test"
+ end
+ undef_method :test
+ end
+
+ $method_undefined.should == :test
+ ensure
+ $method_undefined = nil
+ end
+ end
+end
diff --git a/spec/ruby/core/module/module_eval_spec.rb b/spec/ruby/core/module/module_eval_spec.rb
new file mode 100644
index 0000000000..e9e9fda28d
--- /dev/null
+++ b/spec/ruby/core/module/module_eval_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/class_eval'
+
+describe "Module#module_eval" do
+ it_behaves_like :module_class_eval, :module_eval
+end
diff --git a/spec/ruby/core/module/module_exec_spec.rb b/spec/ruby/core/module/module_exec_spec.rb
new file mode 100644
index 0000000000..47cdf7ef52
--- /dev/null
+++ b/spec/ruby/core/module/module_exec_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/class_exec'
+
+describe "Module#module_exec" do
+ it_behaves_like :module_class_exec, :module_exec
+end
diff --git a/spec/ruby/core/module/module_function_spec.rb b/spec/ruby/core/module/module_function_spec.rb
new file mode 100644
index 0000000000..0602e95ca9
--- /dev/null
+++ b/spec/ruby/core/module/module_function_spec.rb
@@ -0,0 +1,285 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#module_function" do
+ it "is a private method" do
+ Module.should have_private_instance_method(:module_function)
+ end
+
+ describe "on Class" do
+ it "is undefined" do
+ Class.should_not have_private_instance_method(:module_function, true)
+ end
+
+ it "raises a TypeError if calling after rebinded to Class" do
+ -> {
+ Module.instance_method(:module_function).bind(Class.new).call
+ }.should raise_error(TypeError)
+
+ -> {
+ Module.instance_method(:module_function).bind(Class.new).call :foo
+ }.should raise_error(TypeError)
+ end
+ end
+end
+
+describe "Module#module_function with specific method names" do
+ it "creates duplicates of the given instance methods on the Module object" do
+ m = Module.new do
+ def test() end
+ def test2() end
+ def test3() end
+
+ module_function :test, :test2
+ end
+
+ m.respond_to?(:test).should == true
+ m.respond_to?(:test2).should == true
+ m.respond_to?(:test3).should == false
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "returns self" do
+ Module.new do
+ def foo; end
+ module_function(:foo).should equal(self)
+ end
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "returns argument or arguments if given" do
+ Module.new do
+ def foo; end
+ module_function(:foo).should equal(:foo)
+ module_function(:foo, :foo).should == [:foo, :foo]
+ end
+ end
+ end
+
+ it "creates an independent copy of the method, not a redirect" do
+ module Mixin
+ def test
+ "hello"
+ end
+ module_function :test
+ end
+
+ class BaseClass
+ include Mixin
+ def call_test
+ test
+ end
+ end
+
+ Mixin.test.should == "hello"
+ c = BaseClass.new
+ c.call_test.should == "hello"
+
+ module Mixin
+ def test
+ "goodbye"
+ end
+ end
+
+ Mixin.test.should == "hello"
+ c.call_test.should == "goodbye"
+ end
+
+ it "makes the instance methods private" do
+ m = Module.new do
+ def test() "hello" end
+ module_function :test
+ end
+
+ (o = mock('x')).extend(m)
+ o.respond_to?(:test).should == false
+ m.should have_private_instance_method(:test)
+ o.send(:test).should == "hello"
+ -> { o.test }.should raise_error(NoMethodError)
+ end
+
+ it "makes the new Module methods public" do
+ m = Module.new do
+ def test() "hello" end
+ module_function :test
+ end
+
+ m.public_methods.map {|me| me.to_s }.include?('test').should == true
+ end
+
+ it "tries to convert the given names to strings using to_str" do
+ (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test")
+ (o2 = mock('test2')).should_receive(:to_str).any_number_of_times.and_return("test2")
+
+ m = Module.new do
+ def test() end
+ def test2() end
+ module_function o, o2
+ end
+
+ m.respond_to?(:test).should == true
+ m.respond_to?(:test2).should == true
+ end
+
+ it "raises a TypeError when the given names can't be converted to string using to_str" do
+ o = mock('123')
+
+ -> { Module.new { module_function(o) } }.should raise_error(TypeError)
+
+ o.should_receive(:to_str).and_return(123)
+ -> { Module.new { module_function(o) } }.should raise_error(TypeError)
+ end
+
+ it "can make accessible private methods" do # JRUBY-4214
+ m = Module.new do
+ module_function :require
+ end
+ m.respond_to?(:require).should be_true
+ end
+
+ it "creates Module methods that super up the singleton class of the module" do
+ super_m = Module.new do
+ def foo
+ "super_m"
+ end
+ end
+
+ m = Module.new do
+ extend super_m
+ module_function
+ def foo
+ ["m", super]
+ end
+ end
+
+ m.foo.should == ["m", "super_m"]
+ end
+end
+
+describe "Module#module_function as a toggle (no arguments) in a Module body" do
+ it "makes any subsequently defined methods module functions with the normal semantics" do
+ m = Module.new {
+ module_function
+ def test1() end
+ def test2() end
+ }
+
+ m.respond_to?(:test1).should == true
+ m.respond_to?(:test2).should == true
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "returns self" do
+ Module.new do
+ module_function.should equal(self)
+ end
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "returns nil" do
+ Module.new do
+ module_function.should equal(nil)
+ end
+ end
+ end
+
+ it "stops creating module functions if the body encounters another toggle " \
+ "like public/protected/private without arguments" do
+ m = Module.new {
+ module_function
+ def test1() end
+ def test2() end
+ public
+ def test3() end
+ }
+
+ m.respond_to?(:test1).should == true
+ m.respond_to?(:test2).should == true
+ m.respond_to?(:test3).should == false
+ end
+
+ it "does not stop creating module functions if the body encounters " \
+ "public/protected/private WITH arguments" do
+ m = Module.new {
+ def foo() end
+ module_function
+ def test1() end
+ def test2() end
+ public :foo
+ def test3() end
+ }
+
+ m.respond_to?(:test1).should == true
+ m.respond_to?(:test2).should == true
+ m.respond_to?(:test3).should == true
+ end
+
+ it "does not affect module_evaled method definitions also if outside the eval itself" do
+ m = Module.new {
+ module_function
+ module_eval { def test1() end }
+ module_eval " def test2() end "
+ }
+
+ m.respond_to?(:test1).should == false
+ m.respond_to?(:test2).should == false
+ end
+
+ it "has no effect if inside a module_eval if the definitions are outside of it" do
+ m = Module.new {
+ module_eval { module_function }
+ def test1() end
+ def test2() end
+ }
+
+ m.respond_to?(:test1).should == false
+ m.respond_to?(:test2).should == false
+ end
+
+ it "functions normally if both toggle and definitions inside a module_eval" do
+ m = Module.new {
+ module_eval {
+ module_function
+ def test1() end
+ def test2() end
+ }
+ }
+
+ m.respond_to?(:test1).should == true
+ m.respond_to?(:test2).should == true
+ end
+
+ it "affects evaled method definitions also even when outside the eval itself" do
+ m = Module.new {
+ module_function
+ eval "def test1() end"
+ }
+
+ m.respond_to?(:test1).should == true
+ end
+
+ it "doesn't affect definitions when inside an eval even if the definitions are outside of it" do
+ m = Module.new {
+ eval "module_function"
+ def test1() end
+ }
+
+ m.respond_to?(:test1).should == false
+ end
+
+ it "functions normally if both toggle and definitions inside a eval" do
+ m = Module.new {
+ eval <<-CODE
+ module_function
+
+ def test1() end
+ def test2() end
+ CODE
+ }
+
+ m.respond_to?(:test1).should == true
+ m.respond_to?(:test2).should == true
+ end
+end
diff --git a/spec/ruby/core/module/name_spec.rb b/spec/ruby/core/module/name_spec.rb
new file mode 100644
index 0000000000..b78bbfcc80
--- /dev/null
+++ b/spec/ruby/core/module/name_spec.rb
@@ -0,0 +1,130 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/module'
+
+describe "Module#name" do
+ it "is nil for an anonymous module" do
+ Module.new.name.should be_nil
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "is nil when assigned to a constant in an anonymous module" do
+ m = Module.new
+ m::N = Module.new
+ m::N.name.should be_nil
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "is not nil when assigned to a constant in an anonymous module" do
+ m = Module.new
+ m::N = Module.new
+ m::N.name.should.end_with? '::N'
+ end
+ end
+
+ it "is not nil for a nested module created with the module keyword" do
+ m = Module.new
+ module m::N; end
+ m::N.name.should =~ /\A#<Module:0x[0-9a-f]+>::N\z/
+ end
+
+ it "returns nil for a singleton class" do
+ Module.new.singleton_class.name.should be_nil
+ String.singleton_class.name.should be_nil
+ Object.new.singleton_class.name.should be_nil
+ end
+
+ it "changes when the module is reachable through a constant path" do
+ m = Module.new
+ module m::N; end
+ m::N.name.should =~ /\A#<Module:0x\h+>::N\z/
+ ModuleSpecs::Anonymous::WasAnnon = m::N
+ m::N.name.should == "ModuleSpecs::Anonymous::WasAnnon"
+ end
+
+ it "is set after it is removed from a constant" do
+ module ModuleSpecs
+ module ModuleToRemove
+ end
+
+ mod = ModuleToRemove
+ remove_const(:ModuleToRemove)
+ mod.name.should == "ModuleSpecs::ModuleToRemove"
+ end
+ end
+
+ it "is set after it is removed from a constant under an anonymous module" do
+ m = Module.new
+ module m::Child; end
+ child = m::Child
+ m.send(:remove_const, :Child)
+ child.name.should =~ /\A#<Module:0x\h+>::Child\z/
+ end
+
+ it "is set when opened with the module keyword" do
+ ModuleSpecs.name.should == "ModuleSpecs"
+ end
+
+ it "is set when a nested module is opened with the module keyword" do
+ ModuleSpecs::Anonymous.name.should == "ModuleSpecs::Anonymous"
+ end
+
+ it "is set when assigning to a constant" do
+ m = Module.new
+ ModuleSpecs::Anonymous::A = m
+ m.name.should == "ModuleSpecs::Anonymous::A"
+ end
+
+ it "is not modified when assigning to a new constant after it has been accessed" do
+ m = Module.new
+ ModuleSpecs::Anonymous::B = m
+ m.name.should == "ModuleSpecs::Anonymous::B"
+ ModuleSpecs::Anonymous::C = m
+ m.name.should == "ModuleSpecs::Anonymous::B"
+ end
+
+ it "is not modified when assigned to a different anonymous module" do
+ m = Module.new
+ module m::M; end
+ first_name = m::M.name.dup
+ module m::N; end
+ m::N::F = m::M
+ m::M.name.should == first_name
+ end
+
+ # http://bugs.ruby-lang.org/issues/6067
+ it "is set with a conditional assignment to a nested constant" do
+ eval("ModuleSpecs::Anonymous::F ||= Module.new")
+ ModuleSpecs::Anonymous::F.name.should == "ModuleSpecs::Anonymous::F"
+ end
+
+ it "is set with a conditional assignment to a constant" do
+ module ModuleSpecs::Anonymous
+ D ||= Module.new
+ end
+ ModuleSpecs::Anonymous::D.name.should == "ModuleSpecs::Anonymous::D"
+ end
+
+ # http://redmine.ruby-lang.org/issues/show/1833
+ it "preserves the encoding in which the class was defined" do
+ require fixture(__FILE__, "name")
+ ModuleSpecs::NameEncoding.new.name.encoding.should == Encoding::UTF_8
+ end
+
+ it "is set when the anonymous outer module name is set" do
+ m = Module.new
+ m::N = Module.new
+ ModuleSpecs::Anonymous::E = m
+ m::N.name.should == "ModuleSpecs::Anonymous::E::N"
+ end
+
+ it "returns a frozen String" do
+ ModuleSpecs.name.should.frozen?
+ end
+
+ it "always returns the same String for a given Module" do
+ s1 = ModuleSpecs.name
+ s2 = ModuleSpecs.name
+ s1.should equal(s2)
+ end
+end
diff --git a/spec/ruby/core/module/nesting_spec.rb b/spec/ruby/core/module/nesting_spec.rb
new file mode 100644
index 0000000000..d0611b3efe
--- /dev/null
+++ b/spec/ruby/core/module/nesting_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module::Nesting" do
+
+ it "returns the list of Modules nested at the point of call" do
+ ModuleSpecs::Nesting[:root_level].should == []
+ ModuleSpecs::Nesting[:first_level].should == [ModuleSpecs]
+ ModuleSpecs::Nesting[:basic].should == [ModuleSpecs::Nesting, ModuleSpecs]
+ ModuleSpecs::Nesting[:open_first_level].should ==
+ [ModuleSpecs, ModuleSpecs::Nesting, ModuleSpecs]
+ ModuleSpecs::Nesting[:open_meta].should ==
+ [ModuleSpecs::Nesting.meta, ModuleSpecs::Nesting, ModuleSpecs]
+ ModuleSpecs::Nesting[:nest_class].should ==
+ [ModuleSpecs::Nesting::NestedClass, ModuleSpecs::Nesting, ModuleSpecs]
+ end
+
+ it "returns the nesting for module/class declaring the called method" do
+ ModuleSpecs::Nesting.called_from_module_method.should ==
+ [ModuleSpecs::Nesting, ModuleSpecs]
+ ModuleSpecs::Nesting::NestedClass.called_from_class_method.should ==
+ [ModuleSpecs::Nesting::NestedClass, ModuleSpecs::Nesting, ModuleSpecs]
+ ModuleSpecs::Nesting::NestedClass.new.called_from_inst_method.should ==
+ [ModuleSpecs::Nesting::NestedClass, ModuleSpecs::Nesting, ModuleSpecs]
+ end
+
+end
+
+describe "Module.nesting" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/module/new_spec.rb b/spec/ruby/core/module/new_spec.rb
new file mode 100644
index 0000000000..da7f3b8720
--- /dev/null
+++ b/spec/ruby/core/module/new_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module.new" do
+ it "creates a new anonymous Module" do
+ Module.new.is_a?(Module).should == true
+ end
+
+ it "creates a new Module and passes it to the provided block" do
+ test_mod = nil
+ m = Module.new do |mod|
+ mod.should_not == nil
+ self.should == mod
+ test_mod = mod
+ mod.is_a?(Module).should == true
+ Object.new # trying to return something
+ end
+ test_mod.should == m
+ end
+
+ it "evaluates a passed block in the context of the module" do
+ fred = Module.new do
+ def hello() "hello" end
+ def bye() "bye" end
+ end
+
+ (o = mock('x')).extend(fred)
+ o.hello.should == "hello"
+ o.bye.should == "bye"
+ end
+end
diff --git a/spec/ruby/core/module/prepend_features_spec.rb b/spec/ruby/core/module/prepend_features_spec.rb
new file mode 100644
index 0000000000..09c15c5c15
--- /dev/null
+++ b/spec/ruby/core/module/prepend_features_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#prepend_features" do
+ it "is a private method" do
+ Module.should have_private_instance_method(:prepend_features, true)
+ end
+
+ it "gets called when self is included in another module/class" do
+ ScratchPad.record []
+
+ m = Module.new do
+ def self.prepend_features(mod)
+ ScratchPad << mod
+ end
+ end
+
+ c = Class.new do
+ prepend m
+ end
+
+ ScratchPad.recorded.should == [c]
+ end
+
+ it "raises an ArgumentError on a cyclic prepend" do
+ -> {
+ ModuleSpecs::CyclicPrepend.send(:prepend_features, ModuleSpecs::CyclicPrepend)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "clears caches of the given module" do
+ parent = Class.new do
+ def bar; :bar; end
+ end
+
+ child = Class.new(parent) do
+ def foo; :foo; end
+ def bar; super; end
+ end
+
+ mod = Module.new do
+ def foo; :fooo; end
+ end
+
+ child.new.foo
+ child.new.bar
+
+ child.prepend(mod)
+
+ child.new.bar.should == :bar
+ end
+
+ describe "on Class" do
+ it "is undefined" do
+ Class.should_not have_private_instance_method(:prepend_features, true)
+ end
+
+ it "raises a TypeError if calling after rebinded to Class" do
+ -> {
+ Module.instance_method(:prepend_features).bind(Class.new).call Module.new
+ }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/module/prepend_spec.rb b/spec/ruby/core/module/prepend_spec.rb
new file mode 100644
index 0000000000..976b09b105
--- /dev/null
+++ b/spec/ruby/core/module/prepend_spec.rb
@@ -0,0 +1,761 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#prepend" do
+ it "is a public method" do
+ Module.should have_public_instance_method(:prepend, false)
+ end
+
+ it "does not affect the superclass" do
+ Class.new { prepend Module.new }.superclass.should == Object
+ end
+
+ it "calls #prepend_features(self) in reversed order on each module" do
+ ScratchPad.record []
+
+ m = Module.new do
+ def self.prepend_features(mod)
+ ScratchPad << [ self, mod ]
+ end
+ end
+
+ m2 = Module.new do
+ def self.prepend_features(mod)
+ ScratchPad << [ self, mod ]
+ end
+ end
+
+ m3 = Module.new do
+ def self.prepend_features(mod)
+ ScratchPad << [ self, mod ]
+ end
+ end
+
+ c = Class.new { prepend(m, m2, m3) }
+
+ ScratchPad.recorded.should == [ [ m3, c], [ m2, c ], [ m, c ] ]
+ end
+
+ it "updates the method when a module is prepended" do
+ m_module = Module.new do
+ def foo
+ "m"
+ end
+ end
+ a_class = Class.new do
+ def foo
+ 'a'
+ end
+ end
+ a = a_class.new
+ foo = -> { a.foo }
+ foo.call.should == 'a'
+ a_class.class_eval do
+ prepend m_module
+ end
+ foo.call.should == 'm'
+ end
+
+ it "updates the method when a prepended module is updated" do
+ m_module = Module.new
+ a_class = Class.new do
+ prepend m_module
+ def foo
+ 'a'
+ end
+ end
+ a = a_class.new
+ foo = -> { a.foo }
+ foo.call.should == 'a'
+ m_module.module_eval do
+ def foo
+ "m"
+ end
+ end
+ foo.call.should == 'm'
+ end
+
+ it "updates the method when there is a base included method and the prepended module overrides it" do
+ base_module = Module.new do
+ def foo
+ 'a'
+ end
+ end
+ a_class = Class.new do
+ include base_module
+ end
+ a = a_class.new
+ foo = -> { a.foo }
+ foo.call.should == 'a'
+
+ m_module = Module.new do
+ def foo
+ "m"
+ end
+ end
+ a_class.prepend m_module
+ foo.call.should == 'm'
+ end
+
+ it "updates the method when there is a base included method and the prepended module is later updated" do
+ base_module = Module.new do
+ def foo
+ 'a'
+ end
+ end
+ a_class = Class.new do
+ include base_module
+ end
+ a = a_class.new
+ foo = -> { a.foo }
+ foo.call.should == 'a'
+
+ m_module = Module.new
+ a_class.prepend m_module
+ foo.call.should == 'a'
+
+ m_module.module_eval do
+ def foo
+ "m"
+ end
+ end
+ foo.call.should == 'm'
+ end
+
+ it "updates the method when a module prepended after a call is later updated" do
+ m_module = Module.new
+ a_class = Class.new do
+ def foo
+ 'a'
+ end
+ end
+ a = a_class.new
+ foo = -> { a.foo }
+ foo.call.should == 'a'
+
+ a_class.prepend m_module
+ foo.call.should == 'a'
+
+ m_module.module_eval do
+ def foo
+ "m"
+ end
+ end
+ foo.call.should == 'm'
+ end
+
+ it "updates the method when a module is prepended after another and the method is defined later on that module" do
+ m_module = Module.new do
+ def foo
+ 'a'
+ end
+ end
+ a_class = Class.new
+ a_class.prepend m_module
+ a = a_class.new
+ foo = -> { a.foo }
+ foo.call.should == 'a'
+
+ n_module = Module.new
+ a_class.prepend n_module
+ foo.call.should == 'a'
+
+ n_module.module_eval do
+ def foo
+ "n"
+ end
+ end
+ foo.call.should == 'n'
+ end
+
+ it "updates the method when a module is included in a prepended module and the method is defined later" do
+ a_class = Class.new
+ base_module = Module.new do
+ def foo
+ 'a'
+ end
+ end
+ a_class.prepend base_module
+ a = a_class.new
+ foo = -> { a.foo }
+ foo.call.should == 'a'
+
+ m_module = Module.new
+ n_module = Module.new
+ m_module.include n_module
+ a_class.prepend m_module
+
+ n_module.module_eval do
+ def foo
+ "n"
+ end
+ end
+ foo.call.should == 'n'
+ end
+
+ it "updates the method when a new module with an included module is prepended" do
+ a_class = Class.new do
+ def foo
+ 'a'
+ end
+ end
+
+ n_module = Module.new do
+ def foo
+ 'n'
+ end
+ end
+
+ m_module = Module.new do
+ include n_module
+ end
+
+ a = a_class.new
+ foo = -> { a.foo }
+
+ foo.call.should == 'a'
+
+ a_class.class_eval do
+ prepend m_module
+ end
+
+ foo.call.should == 'n'
+ end
+
+ it "updates the constant when a module is prepended" do
+ module ModuleSpecs::ConstUpdatePrepended
+ module M
+ FOO = 'm'
+ end
+ module A
+ FOO = 'a'
+ end
+ module B
+ include A
+ def self.foo
+ FOO
+ end
+ end
+
+ B.foo.should == 'a'
+ B.prepend M
+ B.foo.should == 'm'
+ end
+ end
+
+ it "updates the constant when a prepended module is updated" do
+ module ModuleSpecs::ConstPrependedUpdated
+ module M
+ end
+ module A
+ FOO = 'a'
+ end
+ module B
+ include A
+ prepend M
+ def self.foo
+ FOO
+ end
+ end
+ B.foo.should == 'a'
+ M.const_set(:FOO, 'm')
+ B.foo.should == 'm'
+ end
+ end
+
+ it "updates the constant when there is a base included constant and the prepended module overrides it" do
+ module ModuleSpecs::ConstIncludedPrependedOverride
+ module Base
+ FOO = 'a'
+ end
+ module A
+ include Base
+ def self.foo
+ FOO
+ end
+ end
+ A.foo.should == 'a'
+
+ module M
+ FOO = 'm'
+ end
+ A.prepend M
+ A.foo.should == 'm'
+ end
+ end
+
+ it "updates the constant when there is a base included constant and the prepended module is later updated" do
+ module ModuleSpecs::ConstIncludedPrependedLaterUpdated
+ module Base
+ FOO = 'a'
+ end
+ module A
+ include Base
+ def self.foo
+ FOO
+ end
+ end
+ A.foo.should == 'a'
+
+ module M
+ end
+ A.prepend M
+ A.foo.should == 'a'
+
+ M.const_set(:FOO, 'm')
+ A.foo.should == 'm'
+ end
+ end
+
+ it "updates the constant when a module prepended after a constant is later updated" do
+ module ModuleSpecs::ConstUpdatedPrependedAfterLaterUpdated
+ module M
+ end
+ module A
+ FOO = 'a'
+ end
+ module B
+ include A
+ def self.foo
+ FOO
+ end
+ end
+ B.foo.should == 'a'
+
+ B.prepend M
+ B.foo.should == 'a'
+
+ M.const_set(:FOO, 'm')
+ B.foo.should == 'm'
+ end
+ end
+
+ it "updates the constant when a module is prepended after another and the constant is defined later on that module" do
+ module ModuleSpecs::ConstUpdatedPrependedAfterConstDefined
+ module M
+ FOO = 'm'
+ end
+ module A
+ prepend M
+ def self.foo
+ FOO
+ end
+ end
+
+ A.foo.should == 'm'
+
+ module N
+ end
+ A.prepend N
+ A.foo.should == 'm'
+
+ N.const_set(:FOO, 'n')
+ A.foo.should == 'n'
+ end
+ end
+
+ it "updates the constant when a module is included in a prepended module and the constant is defined later" do
+ module ModuleSpecs::ConstUpdatedIncludedInPrependedConstDefinedLater
+ module A
+ def self.foo
+ FOO
+ end
+ end
+ module Base
+ FOO = 'a'
+ end
+
+ A.prepend Base
+ A.foo.should == 'a'
+
+ module N
+ end
+ module M
+ include N
+ end
+
+ A.prepend M
+
+ N.const_set(:FOO, 'n')
+ A.foo.should == 'n'
+ end
+ end
+
+ it "updates the constant when a new module with an included module is prepended" do
+ module ModuleSpecs::ConstUpdatedNewModuleIncludedPrepended
+ module A
+ FOO = 'a'
+ end
+ module B
+ include A
+ def self.foo
+ FOO
+ end
+ end
+ module N
+ FOO = 'n'
+ end
+
+ module M
+ include N
+ end
+
+ B.foo.should == 'a'
+
+ B.prepend M
+ B.foo.should == 'n'
+ end
+ end
+
+ it "raises a TypeError when the argument is not a Module" do
+ -> { ModuleSpecs::Basic.prepend(Class.new) }.should raise_error(TypeError)
+ end
+
+ it "does not raise a TypeError when the argument is an instance of a subclass of Module" do
+ -> { ModuleSpecs::SubclassSpec.prepend(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError)
+ end
+
+ it "imports constants" do
+ m1 = Module.new
+ m1::MY_CONSTANT = 1
+ m2 = Module.new { prepend(m1) }
+ m2.constants.should include(:MY_CONSTANT)
+ end
+
+ it "imports instance methods" do
+ Module.new { prepend ModuleSpecs::A }.instance_methods.should include(:ma)
+ end
+
+ it "does not import methods to modules and classes" do
+ Module.new { prepend ModuleSpecs::A }.methods.should_not include(:ma)
+ end
+
+ it "allows wrapping methods" do
+ m = Module.new { def calc(x) super + 3 end }
+ c = Class.new { def calc(x) x*2 end }
+ c.prepend(m)
+ c.new.calc(1).should == 5
+ end
+
+ it "also prepends included modules" do
+ a = Module.new { def calc(x) x end }
+ b = Module.new { include a }
+ c = Class.new { prepend b }
+ c.new.calc(1).should == 1
+ end
+
+ it "prepends multiple modules in the right order" do
+ m1 = Module.new { def chain; super << :m1; end }
+ m2 = Module.new { def chain; super << :m2; end; prepend(m1) }
+ c = Class.new { def chain; [:c]; end; prepend(m2) }
+ c.new.chain.should == [:c, :m2, :m1]
+ end
+
+ it "includes prepended modules in ancestors" do
+ m = Module.new
+ Class.new { prepend(m) }.ancestors.should include(m)
+ end
+
+ it "reports the prepended module as the method owner" do
+ m = Module.new { def meth; end }
+ c = Class.new { def meth; end; prepend(m) }
+ c.new.method(:meth).owner.should == m
+ end
+
+ it "reports the prepended module as the unbound method owner" do
+ m = Module.new { def meth; end }
+ c = Class.new { def meth; end; prepend(m) }
+ c.instance_method(:meth).owner.should == m
+ c.public_instance_method(:meth).owner.should == m
+ end
+
+ it "causes the prepended module's method to be aliased by alias_method" do
+ m = Module.new { def meth; :m end }
+ c = Class.new { def meth; :c end; prepend(m); alias_method :alias, :meth }
+ c.new.alias.should == :m
+ end
+
+ it "reports the class for the owner of an aliased method on the class" do
+ m = Module.new
+ c = Class.new { prepend(m); def meth; :c end; alias_method :alias, :meth }
+ c.instance_method(:alias).owner.should == c
+ end
+
+ it "reports the class for the owner of a method aliased from the prepended module" do
+ m = Module.new { def meth; :m end }
+ c = Class.new { prepend(m); alias_method :alias, :meth }
+ c.instance_method(:alias).owner.should == c
+ end
+
+ it "sees an instance of a prepended class as kind of the prepended module" do
+ m = Module.new
+ c = Class.new { prepend(m) }
+ c.new.should be_kind_of(m)
+ end
+
+ it "keeps the module in the chain when dupping the class" do
+ m = Module.new
+ c = Class.new { prepend(m) }
+ c.dup.new.should be_kind_of(m)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "keeps the module in the chain when dupping an intermediate module" do
+ m1 = Module.new { def calc(x) x end }
+ m2 = Module.new { prepend(m1) }
+ c1 = Class.new { prepend(m2) }
+ m2dup = m2.dup
+ m2dup.ancestors.should == [m2dup,m1,m2]
+ c2 = Class.new { prepend(m2dup) }
+ c1.ancestors[0,3].should == [m1,m2,c1]
+ c1.new.should be_kind_of(m1)
+ c2.ancestors[0,4].should == [m2dup,m1,m2,c2]
+ c2.new.should be_kind_of(m1)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "uses only new module when dupping the module" do
+ m1 = Module.new { def calc(x) x end }
+ m2 = Module.new { prepend(m1) }
+ c1 = Class.new { prepend(m2) }
+ m2dup = m2.dup
+ m2dup.ancestors.should == [m1,m2dup]
+ c2 = Class.new { prepend(m2dup) }
+ c1.ancestors[0,3].should == [m1,m2,c1]
+ c1.new.should be_kind_of(m1)
+ c2.ancestors[0,3].should == [m1,m2dup,c2]
+ c2.new.should be_kind_of(m1)
+ end
+ end
+
+ it "depends on prepend_features to add the module" do
+ m = Module.new { def self.prepend_features(mod) end }
+ Class.new { prepend(m) }.ancestors.should_not include(m)
+ end
+
+ it "adds the module in the subclass chains" do
+ parent = Class.new { def chain; [:parent]; end }
+ child = Class.new(parent) { def chain; super << :child; end }
+ mod = Module.new { def chain; super << :mod; end }
+ parent.prepend(mod)
+ parent.ancestors[0,2].should == [mod, parent]
+ child.ancestors[0,3].should == [child, mod, parent]
+
+ parent.new.chain.should == [:parent, :mod]
+ child.new.chain.should == [:parent, :mod, :child]
+ end
+
+ it "inserts a later prepended module into the chain" do
+ m1 = Module.new { def chain; super << :m1; end }
+ m2 = Module.new { def chain; super << :m2; end }
+ c1 = Class.new { def chain; [:c1]; end; prepend m1 }
+ c2 = Class.new(c1) { def chain; super << :c2; end }
+ c2.new.chain.should == [:c1, :m1, :c2]
+ c1.prepend(m2)
+ c2.new.chain.should == [:c1, :m1, :m2, :c2]
+ end
+
+ it "works with subclasses" do
+ m = Module.new do
+ def chain
+ super << :module
+ end
+ end
+
+ c = Class.new do
+ prepend m
+ def chain
+ [:class]
+ end
+ end
+
+ s = Class.new(c) do
+ def chain
+ super << :subclass
+ end
+ end
+
+ s.new.chain.should == [:class, :module, :subclass]
+ end
+
+ it "throws a NoMethodError when there is no more superclass" do
+ m = Module.new do
+ def chain
+ super << :module
+ end
+ end
+
+ c = Class.new do
+ prepend m
+ def chain
+ super << :class
+ end
+ end
+ -> { c.new.chain }.should raise_error(NoMethodError)
+ end
+
+ it "calls prepended after prepend_features" do
+ ScratchPad.record []
+
+ m = Module.new do
+ def self.prepend_features(klass)
+ ScratchPad << [:prepend_features, klass]
+ end
+ def self.prepended(klass)
+ ScratchPad << [:prepended, klass]
+ end
+ end
+
+ c = Class.new { prepend(m) }
+ ScratchPad.recorded.should == [[:prepend_features, c], [:prepended, c]]
+ end
+
+ it "prepends a module if it is included in a super class" do
+ module ModuleSpecs::M3
+ module M; end
+ class A; include M; end
+ class B < A; prepend M; end
+
+ all = [A, B, M]
+
+ (B.ancestors.filter { |a| all.include?(a) }).should == [M, B, A, M]
+ end
+ end
+
+ it "detects cyclic prepends" do
+ -> {
+ module ModuleSpecs::P
+ prepend ModuleSpecs::P
+ end
+ }.should raise_error(ArgumentError)
+ end
+
+ it "doesn't accept no-arguments" do
+ -> {
+ Module.new do
+ prepend
+ end
+ }.should raise_error(ArgumentError)
+ end
+
+ it "returns the class it's included into" do
+ m = Module.new
+ r = nil
+ c = Class.new { r = prepend m }
+ r.should == c
+ end
+
+ it "clears any caches" do
+ module ModuleSpecs::M3
+ module PM1
+ def foo
+ :m1
+ end
+ end
+
+ module PM2
+ def foo
+ :m2
+ end
+ end
+
+ klass = Class.new do
+ prepend PM1
+
+ def get
+ foo
+ end
+ end
+
+ o = klass.new
+ o.get.should == :m1
+
+ klass.class_eval do
+ prepend PM2
+ end
+
+ o.get.should == :m2
+ end
+ end
+
+ it "supports super when the module is prepended into a singleton class" do
+ ScratchPad.record []
+
+ mod = Module.new do
+ def self.inherited(base)
+ super
+ end
+ end
+
+ module_with_singleton_class_prepend = Module.new do
+ singleton_class.prepend(mod)
+ end
+
+ klass = Class.new(ModuleSpecs::RecordIncludedModules) do
+ include module_with_singleton_class_prepend
+ end
+
+ ScratchPad.recorded.should == klass
+ end
+
+ it "supports super when the module is prepended into a singleton class with a class super" do
+ ScratchPad.record []
+
+ base_class = Class.new(ModuleSpecs::RecordIncludedModules) do
+ def self.inherited(base)
+ super
+ end
+ end
+
+ prepended_module = Module.new
+ base_class.singleton_class.prepend(prepended_module)
+
+ child_class = Class.new(base_class)
+ ScratchPad.recorded.should == child_class
+ end
+
+ it "does not interfere with a define_method super in the original class" do
+ base_class = Class.new do
+ def foo(ary)
+ ary << 1
+ end
+ end
+
+ child_class = Class.new(base_class) do
+ define_method :foo do |ary|
+ ary << 2
+ super(ary)
+ end
+ end
+
+ prep_mod = Module.new do
+ def foo(ary)
+ ary << 3
+ super(ary)
+ end
+ end
+
+ child_class.prepend(prep_mod)
+
+ ary = []
+ child_class.new.foo(ary)
+ ary.should == [3, 2, 1]
+ end
+
+ describe "called on a module" do
+ describe "included into a class"
+ it "does not obscure the module's methods from reflective access" do
+ mod = Module.new do
+ def foo; end
+ end
+ cls = Class.new do
+ include mod
+ end
+ pre = Module.new
+ mod.prepend pre
+
+ cls.instance_methods.should include(:foo)
+ end
+ end
+end
diff --git a/spec/ruby/core/module/prepended_spec.rb b/spec/ruby/core/module/prepended_spec.rb
new file mode 100644
index 0000000000..bd95d8fd05
--- /dev/null
+++ b/spec/ruby/core/module/prepended_spec.rb
@@ -0,0 +1,25 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../spec_helper'
+
+describe "Module#prepended" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is a private method" do
+ Module.should have_private_instance_method(:prepended, true)
+ end
+
+ it "is invoked when self is prepended to another module or class" do
+ m = Module.new do
+ def self.prepended(o)
+ ScratchPad.record o
+ end
+ end
+
+ c = Class.new { prepend m }
+
+ ScratchPad.recorded.should == c
+ end
+end
diff --git a/spec/ruby/core/module/private_class_method_spec.rb b/spec/ruby/core/module/private_class_method_spec.rb
new file mode 100644
index 0000000000..407779cccc
--- /dev/null
+++ b/spec/ruby/core/module/private_class_method_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#private_class_method" do
+ before :each do
+ # This is not in classes.rb because after marking a class method private it
+ # will stay private.
+ class << ModuleSpecs::Parent
+ public
+ def private_method_1; end
+ def private_method_2; end
+ end
+ end
+
+ after :each do
+ class << ModuleSpecs::Parent
+ remove_method :private_method_1
+ remove_method :private_method_2
+ end
+ end
+
+ it "makes an existing class method private" do
+ ModuleSpecs::Parent.private_method_1.should == nil
+ ModuleSpecs::Parent.private_class_method :private_method_1
+ -> { ModuleSpecs::Parent.private_method_1 }.should raise_error(NoMethodError)
+
+ # Technically above we're testing the Singleton classes, class method(right?).
+ # Try a "real" class method set private.
+ -> { ModuleSpecs::Parent.private_method }.should raise_error(NoMethodError)
+ end
+
+ it "makes an existing class method private up the inheritance tree" do
+ ModuleSpecs::Child.public_class_method :private_method_1
+ ModuleSpecs::Child.private_method_1.should == nil
+ ModuleSpecs::Child.private_class_method :private_method_1
+
+ -> { ModuleSpecs::Child.private_method_1 }.should raise_error(NoMethodError)
+ -> { ModuleSpecs::Child.private_method }.should raise_error(NoMethodError)
+ end
+
+ it "accepts more than one method at a time" do
+ ModuleSpecs::Parent.private_method_1.should == nil
+ ModuleSpecs::Parent.private_method_2.should == nil
+
+ ModuleSpecs::Child.private_class_method :private_method_1, :private_method_2
+
+ -> { ModuleSpecs::Child.private_method_1 }.should raise_error(NoMethodError)
+ -> { ModuleSpecs::Child.private_method_2 }.should raise_error(NoMethodError)
+ end
+
+ it "raises a NameError if class method doesn't exist" do
+ -> do
+ ModuleSpecs.private_class_method :no_method_here
+ end.should raise_error(NameError)
+ end
+
+ it "makes a class method private" do
+ c = Class.new do
+ def self.foo() "foo" end
+ private_class_method :foo
+ end
+ -> { c.foo }.should raise_error(NoMethodError)
+ end
+
+ it "raises a NameError when the given name is not a method" do
+ -> do
+ Class.new do
+ private_class_method :foo
+ end
+ end.should raise_error(NameError)
+ end
+
+ it "raises a NameError when the given name is an instance method" do
+ -> do
+ Class.new do
+ def foo() "foo" end
+ private_class_method :foo
+ end
+ end.should raise_error(NameError)
+ end
+
+ ruby_version_is "3.0" do
+ context "when single argument is passed and is an array" do
+ it "sets the visibility of the given methods to private" do
+ c = Class.new do
+ def self.foo() "foo" end
+ private_class_method [:foo]
+ end
+ -> { c.foo }.should raise_error(NoMethodError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/module/private_constant_spec.rb b/spec/ruby/core/module/private_constant_spec.rb
new file mode 100644
index 0000000000..3a91b3c3cd
--- /dev/null
+++ b/spec/ruby/core/module/private_constant_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "Module#private_constant" do
+ it "can only be passed constant names defined in the target (self) module" do
+ cls1 = Class.new
+ cls1.const_set :Foo, true
+ cls2 = Class.new(cls1)
+
+ -> do
+ cls2.send :private_constant, :Foo
+ end.should raise_error(NameError)
+ end
+
+ it "accepts strings as constant names" do
+ cls = Class.new
+ cls.const_set :Foo, true
+ cls.send :private_constant, "Foo"
+
+ -> { cls::Foo }.should raise_error(NameError)
+ end
+
+ it "accepts multiple names" do
+ mod = Module.new
+ mod.const_set :Foo, true
+ mod.const_set :Bar, true
+
+ mod.send :private_constant, :Foo, :Bar
+
+ -> {mod::Foo}.should raise_error(NameError)
+ -> {mod::Bar}.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/core/module/private_instance_methods_spec.rb b/spec/ruby/core/module/private_instance_methods_spec.rb
new file mode 100644
index 0000000000..cce0f001bf
--- /dev/null
+++ b/spec/ruby/core/module/private_instance_methods_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../fixtures/reflection'
+
+# TODO: rewrite
+describe "Module#private_instance_methods" do
+ it "returns a list of private methods in module and its ancestors" do
+ ModuleSpecs::CountsMixin.should have_private_instance_method(:private_3)
+
+ ModuleSpecs::CountsParent.should have_private_instance_method(:private_2)
+ ModuleSpecs::CountsParent.should have_private_instance_method(:private_3)
+
+ ModuleSpecs::CountsChild.should have_private_instance_method(:private_1)
+ ModuleSpecs::CountsChild.should have_private_instance_method(:private_2)
+ ModuleSpecs::CountsChild.should have_private_instance_method(:private_3)
+ end
+
+ it "when passed false as a parameter, should return only methods defined in that module" do
+ ModuleSpecs::CountsMixin.should have_private_instance_method(:private_3, false)
+ ModuleSpecs::CountsParent.should have_private_instance_method(:private_2, false)
+ ModuleSpecs::CountsChild.should have_private_instance_method(:private_1, false)
+ end
+
+ it "default list should be the same as passing true as an argument" do
+ ModuleSpecs::CountsMixin.private_instance_methods(true).should ==
+ ModuleSpecs::CountsMixin.private_instance_methods
+ ModuleSpecs::CountsParent.private_instance_methods(true).should ==
+ ModuleSpecs::CountsParent.private_instance_methods
+ ModuleSpecs::CountsChild.private_instance_methods(true).should ==
+ ModuleSpecs::CountsChild.private_instance_methods
+ end
+end
+
+describe :module_private_instance_methods_supers, shared: true do
+ it "returns a unique list for a class including a module" do
+ m = ReflectSpecs::D.private_instance_methods(*@object)
+ m.select { |x| x == :pri }.sort.should == [:pri]
+ end
+
+ it "returns a unique list for a subclass" do
+ m = ReflectSpecs::E.private_instance_methods(*@object)
+ m.select { |x| x == :pri }.sort.should == [:pri]
+ end
+end
+
+describe "Module#private_instance_methods" do
+ describe "when not passed an argument" do
+ it_behaves_like :module_private_instance_methods_supers, nil, []
+ end
+
+ describe "when passed true" do
+ it_behaves_like :module_private_instance_methods_supers, nil, true
+ end
+end
diff --git a/spec/ruby/core/module/private_method_defined_spec.rb b/spec/ruby/core/module/private_method_defined_spec.rb
new file mode 100644
index 0000000000..01fc8f8fb9
--- /dev/null
+++ b/spec/ruby/core/module/private_method_defined_spec.rb
@@ -0,0 +1,120 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#private_method_defined?" do
+ it "returns true if the named private method is defined by module or its ancestors" do
+ ModuleSpecs::CountsMixin.private_method_defined?("private_3").should == true
+
+ ModuleSpecs::CountsParent.private_method_defined?("private_3").should == true
+ ModuleSpecs::CountsParent.private_method_defined?("private_2").should == true
+
+ ModuleSpecs::CountsChild.private_method_defined?("private_3").should == true
+ ModuleSpecs::CountsChild.private_method_defined?("private_2").should == true
+ ModuleSpecs::CountsChild.private_method_defined?("private_1").should == true
+ end
+
+ it "returns false if method is not a private method" do
+ ModuleSpecs::CountsChild.private_method_defined?("public_3").should == false
+ ModuleSpecs::CountsChild.private_method_defined?("public_2").should == false
+ ModuleSpecs::CountsChild.private_method_defined?("public_1").should == false
+
+ ModuleSpecs::CountsChild.private_method_defined?("protected_3").should == false
+ ModuleSpecs::CountsChild.private_method_defined?("protected_2").should == false
+ ModuleSpecs::CountsChild.private_method_defined?("protected_1").should == false
+ end
+
+ it "returns false if the named method is not defined by the module or its ancestors" do
+ ModuleSpecs::CountsMixin.private_method_defined?(:private_10).should == false
+ end
+
+ it "accepts symbols for the method name" do
+ ModuleSpecs::CountsMixin.private_method_defined?(:private_3).should == true
+ end
+
+ it "raises a TypeError if passed an Integer" do
+ -> do
+ ModuleSpecs::CountsMixin.private_method_defined?(1)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed nil" do
+ -> do
+ ModuleSpecs::CountsMixin.private_method_defined?(nil)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed false" do
+ -> do
+ ModuleSpecs::CountsMixin.private_method_defined?(false)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that does not defined #to_str" do
+ -> do
+ ModuleSpecs::CountsMixin.private_method_defined?(mock('x'))
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that defines #to_sym" do
+ sym = mock('symbol')
+ def sym.to_sym() :private_3 end
+
+ -> do
+ ModuleSpecs::CountsMixin.private_method_defined?(sym)
+ end.should raise_error(TypeError)
+ end
+
+ it "calls #to_str to convert an Object" do
+ str = mock('string')
+ def str.to_str() 'private_3' end
+ ModuleSpecs::CountsMixin.private_method_defined?(str).should == true
+ end
+
+ describe "when passed true as a second optional argument" do
+ it "performs a lookup in ancestors" do
+ ModuleSpecs::Child.private_method_defined?(:public_child, true).should == false
+ ModuleSpecs::Child.private_method_defined?(:protected_child, true).should == false
+ ModuleSpecs::Child.private_method_defined?(:accessor_method, true).should == false
+ ModuleSpecs::Child.private_method_defined?(:private_child, true).should == true
+
+ # Defined in Parent
+ ModuleSpecs::Child.private_method_defined?(:public_parent, true).should == false
+ ModuleSpecs::Child.private_method_defined?(:protected_parent, true).should == false
+ ModuleSpecs::Child.private_method_defined?(:private_parent, true).should == true
+
+ # Defined in Module
+ ModuleSpecs::Child.private_method_defined?(:public_module, true).should == false
+ ModuleSpecs::Child.private_method_defined?(:protected_module, true).should == false
+ ModuleSpecs::Child.private_method_defined?(:private_module, true).should == true
+
+ # Defined in SuperModule
+ ModuleSpecs::Child.private_method_defined?(:public_super_module, true).should == false
+ ModuleSpecs::Child.private_method_defined?(:protected_super_module, true).should == false
+ ModuleSpecs::Child.private_method_defined?(:private_super_module, true).should == true
+ end
+ end
+
+ describe "when passed false as a second optional argument" do
+ it "checks only the class itself" do
+ ModuleSpecs::Child.private_method_defined?(:public_child, false).should == false
+ ModuleSpecs::Child.private_method_defined?(:protected_child, false).should == false
+ ModuleSpecs::Child.private_method_defined?(:accessor_method, false).should == false
+ ModuleSpecs::Child.private_method_defined?(:private_child, false).should == true
+
+ # Defined in Parent
+ ModuleSpecs::Child.private_method_defined?(:public_parent, false).should == false
+ ModuleSpecs::Child.private_method_defined?(:protected_parent, false).should == false
+ ModuleSpecs::Child.private_method_defined?(:private_parent, false).should == false
+
+ # Defined in Module
+ ModuleSpecs::Child.private_method_defined?(:public_module, false).should == false
+ ModuleSpecs::Child.private_method_defined?(:protected_module, false).should == false
+ ModuleSpecs::Child.private_method_defined?(:private_module, false).should == false
+
+ # Defined in SuperModule
+ ModuleSpecs::Child.private_method_defined?(:public_super_module, false).should == false
+ ModuleSpecs::Child.private_method_defined?(:protected_super_module, false).should == false
+ ModuleSpecs::Child.private_method_defined?(:private_super_module, false).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/module/private_spec.rb b/spec/ruby/core/module/private_spec.rb
new file mode 100644
index 0000000000..ead806637c
--- /dev/null
+++ b/spec/ruby/core/module/private_spec.rb
@@ -0,0 +1,107 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/set_visibility'
+
+describe "Module#private" do
+ it_behaves_like :set_visibility, :private
+
+ it "makes the target method uncallable from other types" do
+ obj = Object.new
+ class << obj
+ def foo; true; end
+ end
+
+ obj.foo.should == true
+
+ class << obj
+ private :foo
+ end
+
+ -> { obj.foo }.should raise_error(NoMethodError)
+ end
+
+ it "makes a public Object instance method private in a new module" do
+ m = Module.new do
+ private :module_specs_public_method_on_object
+ end
+
+ m.should have_private_instance_method(:module_specs_public_method_on_object)
+
+ # Ensure we did not change Object's method
+ Object.should_not have_private_instance_method(:module_specs_public_method_on_object)
+ end
+
+ it "makes a public Object instance method private in Kernel" do
+ Kernel.should have_private_instance_method(
+ :module_specs_public_method_on_object_for_kernel_private)
+ Object.should_not have_private_instance_method(
+ :module_specs_public_method_on_object_for_kernel_private)
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "returns self" do
+ (class << Object.new; self; end).class_eval do
+ def foo; end
+ private(:foo).should equal(self)
+ private.should equal(self)
+ end
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "returns argument or arguments if given" do
+ (class << Object.new; self; end).class_eval do
+ def foo; end
+ private(:foo).should equal(:foo)
+ private([:foo, :foo]).should == [:foo, :foo]
+ private(:foo, :foo).should == [:foo, :foo]
+ private.should equal(nil)
+ end
+ end
+ end
+
+ it "raises a NameError when given an undefined name" do
+ -> do
+ Module.new.send(:private, :undefined)
+ end.should raise_error(NameError)
+ end
+
+ it "only makes the method private in the class it is called on" do
+ base = Class.new do
+ def wrapped
+ 1
+ end
+ end
+
+ klass = Class.new(base) do
+ def wrapped
+ super + 1
+ end
+ private :wrapped
+ end
+
+ base.new.wrapped.should == 1
+ -> do
+ klass.new.wrapped
+ end.should raise_error(NameError)
+ end
+
+ it "continues to allow a prepended module method to call super" do
+ wrapper = Module.new do
+ def wrapped
+ super + 1
+ end
+ end
+
+ klass = Class.new do
+ prepend wrapper
+
+ def wrapped
+ 1
+ end
+ private :wrapped
+ end
+
+ klass.new.wrapped.should == 2
+ end
+end
diff --git a/spec/ruby/core/module/protected_instance_methods_spec.rb b/spec/ruby/core/module/protected_instance_methods_spec.rb
new file mode 100644
index 0000000000..78ce7e788f
--- /dev/null
+++ b/spec/ruby/core/module/protected_instance_methods_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../fixtures/reflection'
+
+# TODO: rewrite
+describe "Module#protected_instance_methods" do
+ it "returns a list of protected methods in module and its ancestors" do
+ methods = ModuleSpecs::CountsMixin.protected_instance_methods
+ methods.should include(:protected_3)
+
+ methods = ModuleSpecs::CountsParent.protected_instance_methods
+ methods.should include(:protected_3)
+ methods.should include(:protected_2)
+
+ methods = ModuleSpecs::CountsChild.protected_instance_methods
+ methods.should include(:protected_3)
+ methods.should include(:protected_2)
+ methods.should include(:protected_1)
+ end
+
+ it "when passed false as a parameter, should return only methods defined in that module" do
+ ModuleSpecs::CountsMixin.protected_instance_methods(false).should == [:protected_3]
+ ModuleSpecs::CountsParent.protected_instance_methods(false).should == [:protected_2]
+ ModuleSpecs::CountsChild.protected_instance_methods(false).should == [:protected_1]
+ end
+
+ it "default list should be the same as passing true as an argument" do
+ ModuleSpecs::CountsMixin.protected_instance_methods(true).should ==
+ ModuleSpecs::CountsMixin.protected_instance_methods
+ ModuleSpecs::CountsParent.protected_instance_methods(true).should ==
+ ModuleSpecs::CountsParent.protected_instance_methods
+ ModuleSpecs::CountsChild.protected_instance_methods(true).should ==
+ ModuleSpecs::CountsChild.protected_instance_methods
+ end
+end
+
+describe :module_protected_instance_methods_supers, shared: true do
+ it "returns a unique list for a class including a module" do
+ m = ReflectSpecs::D.protected_instance_methods(*@object)
+ m.select { |x| x == :pro }.sort.should == [:pro]
+ end
+
+ it "returns a unique list for a subclass" do
+ m = ReflectSpecs::E.protected_instance_methods(*@object)
+ m.select { |x| x == :pro }.sort.should == [:pro]
+ end
+end
+
+describe "Module#protected_instance_methods" do
+ describe "when not passed an argument" do
+ it_behaves_like :module_protected_instance_methods_supers, nil, []
+ end
+
+ describe "when passed true" do
+ it_behaves_like :module_protected_instance_methods_supers, nil, true
+ end
+end
diff --git a/spec/ruby/core/module/protected_method_defined_spec.rb b/spec/ruby/core/module/protected_method_defined_spec.rb
new file mode 100644
index 0000000000..31e24a16c1
--- /dev/null
+++ b/spec/ruby/core/module/protected_method_defined_spec.rb
@@ -0,0 +1,120 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#protected_method_defined?" do
+ it "returns true if the named protected method is defined by module or its ancestors" do
+ ModuleSpecs::CountsMixin.protected_method_defined?("protected_3").should == true
+
+ ModuleSpecs::CountsParent.protected_method_defined?("protected_3").should == true
+ ModuleSpecs::CountsParent.protected_method_defined?("protected_2").should == true
+
+ ModuleSpecs::CountsChild.protected_method_defined?("protected_3").should == true
+ ModuleSpecs::CountsChild.protected_method_defined?("protected_2").should == true
+ ModuleSpecs::CountsChild.protected_method_defined?("protected_1").should == true
+ end
+
+ it "returns false if method is not a protected method" do
+ ModuleSpecs::CountsChild.protected_method_defined?("public_3").should == false
+ ModuleSpecs::CountsChild.protected_method_defined?("public_2").should == false
+ ModuleSpecs::CountsChild.protected_method_defined?("public_1").should == false
+
+ ModuleSpecs::CountsChild.protected_method_defined?("private_3").should == false
+ ModuleSpecs::CountsChild.protected_method_defined?("private_2").should == false
+ ModuleSpecs::CountsChild.protected_method_defined?("private_1").should == false
+ end
+
+ it "returns false if the named method is not defined by the module or its ancestors" do
+ ModuleSpecs::CountsMixin.protected_method_defined?(:protected_10).should == false
+ end
+
+ it "accepts symbols for the method name" do
+ ModuleSpecs::CountsMixin.protected_method_defined?(:protected_3).should == true
+ end
+
+ it "raises a TypeError if passed an Integer" do
+ -> do
+ ModuleSpecs::CountsMixin.protected_method_defined?(1)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed nil" do
+ -> do
+ ModuleSpecs::CountsMixin.protected_method_defined?(nil)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed false" do
+ -> do
+ ModuleSpecs::CountsMixin.protected_method_defined?(false)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that does not defined #to_str" do
+ -> do
+ ModuleSpecs::CountsMixin.protected_method_defined?(mock('x'))
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that defines #to_sym" do
+ sym = mock('symbol')
+ def sym.to_sym() :protected_3 end
+
+ -> do
+ ModuleSpecs::CountsMixin.protected_method_defined?(sym)
+ end.should raise_error(TypeError)
+ end
+
+ it "calls #to_str to convert an Object" do
+ str = mock('protected_3')
+ str.should_receive(:to_str).and_return("protected_3")
+ ModuleSpecs::CountsMixin.protected_method_defined?(str).should == true
+ end
+
+ describe "when passed true as a second optional argument" do
+ it "performs a lookup in ancestors" do
+ ModuleSpecs::Child.protected_method_defined?(:public_child, true).should == false
+ ModuleSpecs::Child.protected_method_defined?(:protected_child, true).should == true
+ ModuleSpecs::Child.protected_method_defined?(:accessor_method, true).should == false
+ ModuleSpecs::Child.protected_method_defined?(:private_child, true).should == false
+
+ # Defined in Parent
+ ModuleSpecs::Child.protected_method_defined?(:public_parent, true).should == false
+ ModuleSpecs::Child.protected_method_defined?(:protected_parent, true).should == true
+ ModuleSpecs::Child.protected_method_defined?(:private_parent, true).should == false
+
+ # Defined in Module
+ ModuleSpecs::Child.protected_method_defined?(:public_module, true).should == false
+ ModuleSpecs::Child.protected_method_defined?(:protected_module, true).should == true
+ ModuleSpecs::Child.protected_method_defined?(:private_module, true).should == false
+
+ # Defined in SuperModule
+ ModuleSpecs::Child.protected_method_defined?(:public_super_module, true).should == false
+ ModuleSpecs::Child.protected_method_defined?(:protected_super_module, true).should == true
+ ModuleSpecs::Child.protected_method_defined?(:private_super_module, true).should == false
+ end
+ end
+
+ describe "when passed false as a second optional argument" do
+ it "checks only the class itself" do
+ ModuleSpecs::Child.protected_method_defined?(:public_child, false).should == false
+ ModuleSpecs::Child.protected_method_defined?(:protected_child, false).should == true
+ ModuleSpecs::Child.protected_method_defined?(:accessor_method, false).should == false
+ ModuleSpecs::Child.protected_method_defined?(:private_child, false).should == false
+
+ # Defined in Parent
+ ModuleSpecs::Child.protected_method_defined?(:public_parent, false).should == false
+ ModuleSpecs::Child.protected_method_defined?(:protected_parent, false).should == false
+ ModuleSpecs::Child.protected_method_defined?(:private_parent, false).should == false
+
+ # Defined in Module
+ ModuleSpecs::Child.protected_method_defined?(:public_module, false).should == false
+ ModuleSpecs::Child.protected_method_defined?(:protected_module, false).should == false
+ ModuleSpecs::Child.protected_method_defined?(:private_module, false).should == false
+
+ # Defined in SuperModule
+ ModuleSpecs::Child.protected_method_defined?(:public_super_module, false).should == false
+ ModuleSpecs::Child.protected_method_defined?(:protected_super_module, false).should == false
+ ModuleSpecs::Child.protected_method_defined?(:private_super_module, false).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/module/protected_spec.rb b/spec/ruby/core/module/protected_spec.rb
new file mode 100644
index 0000000000..058d49d751
--- /dev/null
+++ b/spec/ruby/core/module/protected_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/set_visibility'
+
+describe "Module#protected" do
+ before :each do
+ class << ModuleSpecs::Parent
+ def protected_method_1; 5; end
+ end
+ end
+
+ it_behaves_like :set_visibility, :protected
+
+ it "makes an existing class method protected" do
+ ModuleSpecs::Parent.protected_method_1.should == 5
+
+ class << ModuleSpecs::Parent
+ protected :protected_method_1
+ end
+
+ -> { ModuleSpecs::Parent.protected_method_1 }.should raise_error(NoMethodError)
+ end
+
+ it "makes a public Object instance method protected in a new module" do
+ m = Module.new do
+ protected :module_specs_public_method_on_object
+ end
+
+ m.should have_protected_instance_method(:module_specs_public_method_on_object)
+
+ # Ensure we did not change Object's method
+ Object.should_not have_protected_instance_method(:module_specs_public_method_on_object)
+ end
+
+ it "makes a public Object instance method protected in Kernel" do
+ Kernel.should have_protected_instance_method(
+ :module_specs_public_method_on_object_for_kernel_protected)
+ Object.should_not have_protected_instance_method(
+ :module_specs_public_method_on_object_for_kernel_protected)
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "returns self" do
+ (class << Object.new; self; end).class_eval do
+ def foo; end
+ protected(:foo).should equal(self)
+ protected.should equal(self)
+ end
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "returns argument or arguments if given" do
+ (class << Object.new; self; end).class_eval do
+ def foo; end
+ protected(:foo).should equal(:foo)
+ protected([:foo, :foo]).should == [:foo, :foo]
+ protected(:foo, :foo).should == [:foo, :foo]
+ protected.should equal(nil)
+ end
+ end
+ end
+
+ it "raises a NameError when given an undefined name" do
+ -> do
+ Module.new.send(:protected, :undefined)
+ end.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/core/module/public_class_method_spec.rb b/spec/ruby/core/module/public_class_method_spec.rb
new file mode 100644
index 0000000000..b5d76e7b7a
--- /dev/null
+++ b/spec/ruby/core/module/public_class_method_spec.rb
@@ -0,0 +1,96 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#public_class_method" do
+ before :each do
+ class << ModuleSpecs::Parent
+ private
+ def public_method_1; end
+ def public_method_2; end
+ end
+ end
+
+ after :each do
+ class << ModuleSpecs::Parent
+ remove_method :public_method_1
+ remove_method :public_method_2
+ end
+ end
+
+ it "makes an existing class method public" do
+ -> { ModuleSpecs::Parent.public_method_1 }.should raise_error(NoMethodError)
+ ModuleSpecs::Parent.public_class_method :public_method_1
+ ModuleSpecs::Parent.public_method_1.should == nil
+
+ # Technically above we're testing the Singleton classes, class method(right?).
+ # Try a "real" class method set public.
+ ModuleSpecs::Parent.public_method.should == nil
+ end
+
+ it "makes an existing class method public up the inheritance tree" do
+ ModuleSpecs::Child.private_class_method :public_method_1
+ -> { ModuleSpecs::Child.public_method_1 }.should raise_error(NoMethodError)
+ ModuleSpecs::Child.public_class_method :public_method_1
+
+ ModuleSpecs::Child.public_method_1.should == nil
+ ModuleSpecs::Child.public_method.should == nil
+ end
+
+ it "accepts more than one method at a time" do
+ -> { ModuleSpecs::Parent.public_method_1 }.should raise_error(NameError)
+ -> { ModuleSpecs::Parent.public_method_2 }.should raise_error(NameError)
+
+ ModuleSpecs::Child.public_class_method :public_method_1, :public_method_2
+
+ ModuleSpecs::Child.public_method_1.should == nil
+ ModuleSpecs::Child.public_method_2.should == nil
+ end
+
+ it "raises a NameError if class method doesn't exist" do
+ -> do
+ ModuleSpecs.public_class_method :no_method_here
+ end.should raise_error(NameError)
+ end
+
+ it "makes a class method public" do
+ c = Class.new do
+ def self.foo() "foo" end
+ public_class_method :foo
+ end
+
+ c.foo.should == "foo"
+ end
+
+ it "raises a NameError when the given name is not a method" do
+ -> do
+ Class.new do
+ public_class_method :foo
+ end
+ end.should raise_error(NameError)
+ end
+
+ it "raises a NameError when the given name is an instance method" do
+ -> do
+ Class.new do
+ def foo() "foo" end
+ public_class_method :foo
+ end
+ end.should raise_error(NameError)
+ end
+
+ ruby_version_is "3.0" do
+ context "when single argument is passed and is an array" do
+ it "makes a class method public" do
+ c = Class.new do
+ class << self
+ private
+ def foo() "foo" end
+ end
+ public_class_method [:foo]
+ end
+
+ c.foo.should == "foo"
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/module/public_constant_spec.rb b/spec/ruby/core/module/public_constant_spec.rb
new file mode 100644
index 0000000000..e624d45fd2
--- /dev/null
+++ b/spec/ruby/core/module/public_constant_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+
+describe "Module#public_constant" do
+ it "can only be passed constant names defined in the target (self) module" do
+ cls1 = Class.new
+ cls1.const_set :Foo, true
+ cls2 = Class.new(cls1)
+
+ -> do
+ cls2.send :public_constant, :Foo
+ end.should raise_error(NameError)
+ end
+
+ it "accepts strings as constant names" do
+ cls = Class.new
+ cls.const_set :Foo, true
+
+ cls.send :private_constant, :Foo
+ cls.send :public_constant, "Foo"
+
+ cls::Foo.should == true
+ end
+
+ # [ruby-list:48558]
+ it "accepts multiple names" do
+ mod = Module.new
+ mod.const_set :Foo, true
+ mod.const_set :Bar, true
+
+ mod.send :private_constant, :Foo
+ mod.send :private_constant, :Bar
+
+ mod.send :public_constant, :Foo, :Bar
+
+ mod::Foo.should == true
+ mod::Bar.should == true
+ end
+end
diff --git a/spec/ruby/core/module/public_instance_method_spec.rb b/spec/ruby/core/module/public_instance_method_spec.rb
new file mode 100644
index 0000000000..ba19ad0404
--- /dev/null
+++ b/spec/ruby/core/module/public_instance_method_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#public_instance_method" do
+ it "is a public method" do
+ Module.should have_public_instance_method(:public_instance_method, false)
+ end
+
+ it "requires an argument" do
+ Module.new.method(:public_instance_method).arity.should == 1
+ end
+
+ describe "when given a public method name" do
+ it "returns an UnboundMethod corresponding to the defined Module" do
+ ret = ModuleSpecs::Super.public_instance_method(:public_module)
+ ret.should be_an_instance_of(UnboundMethod)
+ ret.owner.should equal(ModuleSpecs::Basic)
+
+ ret = ModuleSpecs::Super.public_instance_method(:public_super_module)
+ ret.should be_an_instance_of(UnboundMethod)
+ ret.owner.should equal(ModuleSpecs::Super)
+ end
+
+ it "accepts if the name is a Symbol or String" do
+ ret = ModuleSpecs::Basic.public_instance_method(:public_module)
+ ModuleSpecs::Basic.public_instance_method("public_module").should == ret
+ end
+ end
+
+ it "raises a TypeError when given a name is not Symbol or String" do
+ -> { Module.new.public_instance_method(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a NameError when given a protected method name" do
+ -> do
+ ModuleSpecs::Basic.public_instance_method(:protected_module)
+ end.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the method is private" do
+ -> do
+ ModuleSpecs::Basic.public_instance_method(:private_module)
+ end.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the method has been undefined" do
+ -> do
+ ModuleSpecs::Parent.public_instance_method(:undefed_method)
+ end.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the method does not exist" do
+ -> do
+ Module.new.public_instance_method(:missing)
+ end.should raise_error(NameError)
+ end
+
+ it "sets the NameError#name attribute to the name of the missing method" do
+ begin
+ Module.new.public_instance_method(:missing)
+ rescue NameError => e
+ e.name.should == :missing
+ end
+ end
+end
diff --git a/spec/ruby/core/module/public_instance_methods_spec.rb b/spec/ruby/core/module/public_instance_methods_spec.rb
new file mode 100644
index 0000000000..ae7d9b5ffb
--- /dev/null
+++ b/spec/ruby/core/module/public_instance_methods_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../fixtures/reflection'
+
+# TODO: rewrite
+
+describe "Module#public_instance_methods" do
+ it "returns a list of public methods in module and its ancestors" do
+ methods = ModuleSpecs::CountsMixin.public_instance_methods
+ methods.should include(:public_3)
+
+ methods = ModuleSpecs::CountsParent.public_instance_methods
+ methods.should include(:public_3)
+ methods.should include(:public_2)
+
+ methods = ModuleSpecs::CountsChild.public_instance_methods
+ methods.should include(:public_3)
+ methods.should include(:public_2)
+ methods.should include(:public_1)
+
+ methods = ModuleSpecs::Child2.public_instance_methods
+ methods.should include(:foo)
+ end
+
+ it "when passed false as a parameter, should return only methods defined in that module" do
+ ModuleSpecs::CountsMixin.public_instance_methods(false).should == [:public_3]
+ ModuleSpecs::CountsParent.public_instance_methods(false).should == [:public_2]
+ ModuleSpecs::CountsChild.public_instance_methods(false).should == [:public_1]
+ end
+
+ it "default list should be the same as passing true as an argument" do
+ ModuleSpecs::CountsMixin.public_instance_methods(true).should ==
+ ModuleSpecs::CountsMixin.public_instance_methods
+ ModuleSpecs::CountsParent.public_instance_methods(true).should ==
+ ModuleSpecs::CountsParent.public_instance_methods
+ ModuleSpecs::CountsChild.public_instance_methods(true).should ==
+ ModuleSpecs::CountsChild.public_instance_methods
+ end
+end
+
+describe :module_public_instance_methods_supers, shared: true do
+ it "returns a unique list for a class including a module" do
+ m = ReflectSpecs::D.public_instance_methods(*@object)
+ m.select { |x| x == :pub }.sort.should == [:pub]
+ end
+
+ it "returns a unique list for a subclass" do
+ m = ReflectSpecs::E.public_instance_methods(*@object)
+ m.select { |x| x == :pub }.sort.should == [:pub]
+ end
+end
+
+describe "Module#public_instance_methods" do
+ describe "when not passed an argument" do
+ it_behaves_like :module_public_instance_methods_supers, nil, []
+ end
+
+ describe "when passed true" do
+ it_behaves_like :module_public_instance_methods_supers, nil, true
+ end
+end
diff --git a/spec/ruby/core/module/public_method_defined_spec.rb b/spec/ruby/core/module/public_method_defined_spec.rb
new file mode 100644
index 0000000000..5c9bdf1ccc
--- /dev/null
+++ b/spec/ruby/core/module/public_method_defined_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#public_method_defined?" do
+ it "returns true if the named public method is defined by module or its ancestors" do
+ ModuleSpecs::CountsMixin.public_method_defined?("public_3").should == true
+
+ ModuleSpecs::CountsParent.public_method_defined?("public_3").should == true
+ ModuleSpecs::CountsParent.public_method_defined?("public_2").should == true
+
+ ModuleSpecs::CountsChild.public_method_defined?("public_3").should == true
+ ModuleSpecs::CountsChild.public_method_defined?("public_2").should == true
+ ModuleSpecs::CountsChild.public_method_defined?("public_1").should == true
+ end
+
+ it "returns false if method is not a public method" do
+ ModuleSpecs::CountsChild.public_method_defined?("private_3").should == false
+ ModuleSpecs::CountsChild.public_method_defined?("private_2").should == false
+ ModuleSpecs::CountsChild.public_method_defined?("private_1").should == false
+
+ ModuleSpecs::CountsChild.public_method_defined?("protected_3").should == false
+ ModuleSpecs::CountsChild.public_method_defined?("protected_2").should == false
+ ModuleSpecs::CountsChild.public_method_defined?("protected_1").should == false
+ end
+
+ it "returns false if the named method is not defined by the module or its ancestors" do
+ ModuleSpecs::CountsMixin.public_method_defined?(:public_10).should == false
+ end
+
+ it "accepts symbols for the method name" do
+ ModuleSpecs::CountsMixin.public_method_defined?(:public_3).should == true
+ end
+
+ it "raises a TypeError if passed an Integer" do
+ -> do
+ ModuleSpecs::CountsMixin.public_method_defined?(1)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed nil" do
+ -> do
+ ModuleSpecs::CountsMixin.public_method_defined?(nil)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed false" do
+ -> do
+ ModuleSpecs::CountsMixin.public_method_defined?(false)
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that does not defined #to_str" do
+ -> do
+ ModuleSpecs::CountsMixin.public_method_defined?(mock('x'))
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that defines #to_sym" do
+ sym = mock('symbol')
+ def sym.to_sym() :public_3 end
+
+ -> do
+ ModuleSpecs::CountsMixin.public_method_defined?(sym)
+ end.should raise_error(TypeError)
+ end
+
+ it "calls #to_str to convert an Object" do
+ str = mock('public_3')
+ def str.to_str() 'public_3' end
+ ModuleSpecs::CountsMixin.public_method_defined?(str).should == true
+ end
+end
diff --git a/spec/ruby/core/module/public_spec.rb b/spec/ruby/core/module/public_spec.rb
new file mode 100644
index 0000000000..e3b183f228
--- /dev/null
+++ b/spec/ruby/core/module/public_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/set_visibility'
+
+describe "Module#public" do
+ it_behaves_like :set_visibility, :public
+
+ it "on a superclass method calls the redefined method" do
+ ModuleSpecs::ChildPrivateMethodMadePublic.new.private_method_redefined.should == :after_redefinition
+ end
+
+ it "makes a private Object instance method public in a new module" do
+ m = Module.new do
+ public :module_specs_private_method_on_object
+ end
+
+ m.should have_public_instance_method(:module_specs_private_method_on_object)
+
+ # Ensure we did not change Object's method
+ Object.should_not have_public_instance_method(:module_specs_private_method_on_object)
+ end
+
+ it "makes a private Object instance method public in Kernel" do
+ Kernel.should have_public_instance_method(
+ :module_specs_private_method_on_object_for_kernel_public)
+ Object.should_not have_public_instance_method(
+ :module_specs_private_method_on_object_for_kernel_public)
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "returns self" do
+ (class << Object.new; self; end).class_eval do
+ def foo; end
+ public(:foo).should equal(self)
+ public.should equal(self)
+ end
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "returns argument or arguments if given" do
+ (class << Object.new; self; end).class_eval do
+ def foo; end
+ public(:foo).should equal(:foo)
+ public([:foo, :foo]).should == [:foo, :foo]
+ public(:foo, :foo).should == [:foo, :foo]
+ public.should equal(nil)
+ end
+ end
+ end
+
+ it "raises a NameError when given an undefined name" do
+ -> do
+ Module.new.send(:public, :undefined)
+ end.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/core/module/refine_spec.rb b/spec/ruby/core/module/refine_spec.rb
new file mode 100644
index 0000000000..841900cf87
--- /dev/null
+++ b/spec/ruby/core/module/refine_spec.rb
@@ -0,0 +1,1051 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/refine'
+
+describe "Module#refine" do
+ it "runs its block in an anonymous module" do
+ inner_self = nil
+ mod = Module.new do
+ refine String do
+ inner_self = self
+ end
+ end
+
+ mod.should_not == inner_self
+ inner_self.should be_kind_of(Module)
+ inner_self.name.should == nil
+ end
+
+ it "uses the same anonymous module for future refines of the same class" do
+ selves = []
+ mod = Module.new do
+ refine String do
+ selves << self
+ end
+ end
+
+ mod.module_eval do
+ refine String do
+ selves << self
+ end
+ end
+
+ selves[0].should == selves[1]
+ end
+
+ it "adds methods defined in its block to the anonymous module's public instance methods" do
+ inner_self = nil
+ mod = Module.new do
+ refine String do
+ def blah
+ "blah"
+ end
+ inner_self = self
+ end
+ end
+
+ inner_self.public_instance_methods.should include(:blah)
+ end
+
+ it "returns created anonymous module" do
+ inner_self = nil
+ result = nil
+ mod = Module.new do
+ result = refine String do
+ inner_self = self
+ end
+ end
+
+ result.should == inner_self
+ end
+
+ it "raises ArgumentError if not passed an argument" do
+ -> do
+ Module.new do
+ refine {}
+ end
+ end.should raise_error(ArgumentError)
+ end
+
+ it "raises TypeError if not passed a class" do
+ -> do
+ Module.new do
+ refine("foo") {}
+ end
+ end.should raise_error(TypeError)
+ end
+
+ it "accepts a module as argument" do
+ inner_self = nil
+ Module.new do
+ refine(Enumerable) do
+ def blah
+ end
+ inner_self = self
+ end
+ end
+
+ inner_self.public_instance_methods.should include(:blah)
+ end
+
+ it "applies refinements to the module" do
+ refinement = Module.new do
+ refine(Enumerable) do
+ def foo?
+ self.any? ? "yes" : "no"
+ end
+ end
+ end
+
+ foo = Class.new do
+ using refinement
+
+ def initialize(items)
+ @items = items
+ end
+
+ def result
+ @items.foo?
+ end
+ end
+
+ foo.new([]).result.should == "no"
+ foo.new([1]).result.should == "yes"
+ end
+
+ it "raises ArgumentError if not given a block" do
+ -> do
+ Module.new do
+ refine String
+ end
+ end.should raise_error(ArgumentError)
+ end
+
+ it "applies refinements to calls in the refine block" do
+ result = nil
+ Module.new do
+ refine(String) do
+ def foo; "foo"; end
+ result = "hello".foo
+ end
+ end
+ result.should == "foo"
+ end
+
+ it "doesn't apply refinements outside the refine block" do
+ Module.new do
+ refine(String) {def foo; "foo"; end}
+ -> {
+ "hello".foo
+ }.should raise_error(NoMethodError)
+ end
+ end
+
+ it "does not apply refinements to external scopes not using the module" do
+ Module.new do
+ refine(String) {def foo; 'foo'; end}
+ end
+
+ -> {"hello".foo}.should raise_error(NoMethodError)
+ end
+
+ # When defining multiple refinements in the same module,
+ # inside a refine block all refinements from the same
+ # module are active when a refined method is called
+ it "makes available all refinements from the same module" do
+ refinement = Module.new do
+ refine Integer do
+ def to_json_format
+ to_s
+ end
+ end
+
+ refine Array do
+ def to_json_format
+ "[" + map { |i| i.to_json_format }.join(", ") + "]"
+ end
+ end
+
+ refine Hash do
+ def to_json_format
+ "{" + map { |k, v| k.to_s.dump + ": " + v.to_json_format }.join(", ") + "}"
+ end
+ end
+ end
+
+ result = nil
+
+ Module.new do
+ using refinement
+
+ result = [{1 => 2}, {3 => 4}].to_json_format
+ end
+
+ result.should == '[{"1": 2}, {"3": 4}]'
+ end
+
+ it "does not make available methods from another refinement module" do
+ refinery_integer = Module.new do
+ refine Integer do
+ def to_json_format
+ to_s
+ end
+ end
+ end
+
+ refinery_array = Module.new do
+ refine Array do
+ def to_json_format
+ "[" + map { |i| i.to_json_format }.join(",") + "]"
+ end
+ end
+ end
+
+ result = nil
+
+ -> {
+ Module.new do
+ using refinery_integer
+ using refinery_array
+
+ [1, 2].to_json_format
+ end
+ }.should raise_error(NoMethodError)
+ end
+
+ # method lookup:
+ # * The prepended modules from the refinement for C
+ # * The refinement for C
+ # * The included modules from the refinement for C
+ # * The prepended modules of C
+ # * C
+ # * The included modules of C
+ describe "method lookup" do
+ it "looks in the object singleton class first" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement = Module.new do
+ refine refined_class do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+
+ obj = refined_class.new
+ class << obj
+ def foo; "foo from singleton class"; end
+ end
+ result = obj.foo
+ end
+
+ result.should == "foo from singleton class"
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "looks in the included modules for builtin methods" do
+ result = ruby_exe(<<-RUBY)
+ a = Module.new do
+ def /(other) quo(other) end
+ end
+
+ refinement = Module.new do
+ refine Integer do
+ include a
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = 1 / 2
+ end
+
+ print result.class
+ RUBY
+
+ result.should == 'Rational'
+ end
+ end
+
+ it "looks in later included modules of the refined module first" do
+ a = Module.new do
+ def foo
+ "foo from A"
+ end
+ end
+
+ include_me_later = Module.new do
+ def foo
+ "foo from IncludeMeLater"
+ end
+ end
+
+ c = Class.new do
+ include a
+ end
+
+ refinement = Module.new do
+ refine c do; end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ c.include include_me_later
+ result = c.new.foo
+ end
+
+ result.should == "foo from IncludeMeLater"
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "looks in prepended modules from the refinement first" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement = Module.new do
+ refine refined_class do
+ include ModuleSpecs::IncludedModule
+ prepend ModuleSpecs::PrependedModule
+
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = refined_class.new.foo
+ end
+
+ result.should == "foo from prepended module"
+ end
+
+ it "looks in refinement then" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement = Module.new do
+ refine(refined_class) do
+ include ModuleSpecs::IncludedModule
+
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = refined_class.new.foo
+ end
+
+ result.should == "foo from refinement"
+ end
+
+ it "looks in included modules from the refinement then" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement = Module.new do
+ refine refined_class do
+ include ModuleSpecs::IncludedModule
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = refined_class.new.foo
+ end
+
+ result.should == "foo from included module"
+ end
+ end
+
+ it "looks in the class then" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement = Module.new do
+ refine(refined_class) { }
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = refined_class.new.foo
+ end
+
+ result.should == "foo"
+ end
+ end
+
+
+ # methods in a subclass have priority over refinements in a superclass
+ it "does not override methods in subclasses" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ subclass = Class.new(refined_class) do
+ def foo; "foo from subclass"; end
+ end
+
+ refinement = Module.new do
+ refine refined_class do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = subclass.new.foo
+ end
+
+ result.should == "foo from subclass"
+ end
+
+ context "for methods accessed indirectly" do
+ it "is honored by Kernel#send" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement = Module.new do
+ refine refined_class do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = refined_class.new.send :foo
+ end
+
+ result.should == "foo from refinement"
+ end
+
+ it "is honored by BasicObject#__send__" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement = Module.new do
+ refine refined_class do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = refined_class.new.__send__ :foo
+ end
+
+ result.should == "foo from refinement"
+ end
+
+ it "is honored by Symbol#to_proc" do
+ refinement = Module.new do
+ refine Integer do
+ def to_s
+ "(#{super})"
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = [1, 2, 3].map(&:to_s)
+ end
+
+ result.should == ["(1)", "(2)", "(3)"]
+ end
+
+ it "is honored by Kernel#public_send" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement = Module.new do
+ refine refined_class do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = refined_class.new.public_send :foo
+ end
+
+ result.should == "foo from refinement"
+ end
+
+ it "is honored by string interpolation" do
+ refinement = Module.new do
+ refine Integer do
+ def to_s
+ "foo"
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = "#{1}"
+ end
+
+ result.should == "foo"
+ end
+
+ it "is honored by Kernel#binding" do
+ refinement = Module.new do
+ refine String do
+ def to_s
+ "hello from refinement"
+ end
+ end
+ end
+
+ klass = Class.new do
+ using refinement
+
+ def foo
+ "foo".to_s
+ end
+
+ def get_binding
+ binding
+ end
+ end
+
+ result = Kernel.eval("self.foo()", klass.new.get_binding)
+ result.should == "hello from refinement"
+ end
+
+ it "is honored by Kernel#method" do
+ klass = Class.new
+ refinement = Module.new do
+ refine klass do
+ def foo; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = klass.new.method(:foo).class
+ end
+
+ result.should == Method
+ end
+
+ it "is honored by Kernel#public_method" do
+ klass = Class.new
+ refinement = Module.new do
+ refine klass do
+ def foo; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = klass.new.public_method(:foo).class
+ end
+
+ result.should == Method
+ end
+
+ it "is honored by Kernel#instance_method" do
+ klass = Class.new
+ refinement = Module.new do
+ refine klass do
+ def foo; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = klass.instance_method(:foo).class
+ end
+
+ result.should == UnboundMethod
+ end
+
+ it "is honored by Kernel#respond_to?" do
+ klass = Class.new
+ refinement = Module.new do
+ refine klass do
+ def foo; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = klass.new.respond_to?(:foo)
+ end
+
+ result.should == true
+ end
+
+ it "is honored by &" do
+ refinement = Module.new do
+ refine String do
+ def to_proc(*args)
+ -> * { 'foo' }
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = ["hola"].map(&"upcase")
+ end
+
+ result.should == ['foo']
+ end
+ end
+
+ context "when super is called in a refinement" do
+ ruby_version_is ""..."3.1" do
+ it "looks in the included to refinery module" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement = Module.new do
+ refine refined_class do
+ include ModuleSpecs::IncludedModule
+
+ def foo
+ super
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = refined_class.new.foo
+ end
+
+ result.should == "foo from included module"
+ end
+ end
+
+ it "looks in the refined class" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement = Module.new do
+ refine refined_class do
+ def foo
+ super
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = refined_class.new.foo
+ end
+
+ result.should == "foo"
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "looks in the refined class from included module" do
+ refined_class = ModuleSpecs.build_refined_class(for_super: true)
+
+ a = Module.new do
+ def foo
+ [:A] + super
+ end
+ end
+
+ refinement = Module.new do
+ refine refined_class do
+ include a
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+
+ result = refined_class.new.foo
+ end
+
+ result.should == [:A, :C]
+ end
+
+ it "looks in the refined ancestors from included module" do
+ refined_class = ModuleSpecs.build_refined_class(for_super: true)
+ subclass = Class.new(refined_class)
+
+ a = Module.new do
+ def foo
+ [:A] + super
+ end
+ end
+
+ refinement = Module.new do
+ refine refined_class do
+ include a
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+
+ result = subclass.new.foo
+ end
+
+ result.should == [:A, :C]
+ end
+ end
+
+ # super in a method of a refinement invokes the method in the refined
+ # class even if there is another refinement which has been activated
+ # in the same context.
+ it "looks in the refined class first if called from refined method" do
+ refined_class = ModuleSpecs.build_refined_class(for_super: true)
+
+ refinement = Module.new do
+ refine refined_class do
+ def foo
+ [:R1]
+ end
+ end
+ end
+
+ refinement_with_super = Module.new do
+ refine refined_class do
+ def foo
+ [:R2] + super
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ using refinement_with_super
+ result = refined_class.new.foo
+ end
+
+ result.should == [:R2, :C]
+ end
+
+ it "looks only in the refined class even if there is another active refinement" do
+ refined_class = ModuleSpecs.build_refined_class(for_super: true)
+
+ refinement = Module.new do
+ refine refined_class do
+ def bar
+ "you cannot see me from super because I belong to another active R"
+ end
+ end
+ end
+
+ refinement_with_super = Module.new do
+ refine refined_class do
+ def bar
+ super
+ end
+ end
+ end
+
+
+ Module.new do
+ using refinement
+ using refinement_with_super
+ -> {
+ refined_class.new.bar
+ }.should raise_error(NoMethodError)
+ end
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "does't have access to active refinements for C from included module" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ a = Module.new do
+ def foo
+ super + bar
+ end
+ end
+
+ refinement = Module.new do
+ refine refined_class do
+ include a
+
+ def bar
+ "bar is not seen from A methods"
+ end
+ end
+ end
+
+ Module.new do
+ using refinement
+ -> {
+ refined_class.new.foo
+ }.should raise_error(NameError) { |e| e.name.should == :bar }
+ end
+ end
+
+ it "does't have access to other active refinements from included module" do
+ refined_class = ModuleSpecs.build_refined_class
+
+ refinement_integer = Module.new do
+ refine Integer do
+ def bar
+ "bar is not seen from A methods"
+ end
+ end
+ end
+
+ a = Module.new do
+ def foo
+ super + 1.bar
+ end
+ end
+
+ refinement = Module.new do
+ refine refined_class do
+ include a
+ end
+ end
+
+ Module.new do
+ using refinement
+ using refinement_integer
+ -> {
+ refined_class.new.foo
+ }.should raise_error(NameError) { |e| e.name.should == :bar }
+ end
+ end
+
+ # https://bugs.ruby-lang.org/issues/16977
+ it "looks in the another active refinement if super called from included modules" do
+ refined_class = ModuleSpecs.build_refined_class(for_super: true)
+
+ a = Module.new do
+ def foo
+ [:A] + super
+ end
+ end
+
+ b = Module.new do
+ def foo
+ [:B] + super
+ end
+ end
+
+ refinement_a = Module.new do
+ refine refined_class do
+ include a
+ end
+ end
+
+ refinement_b = Module.new do
+ refine refined_class do
+ include b
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement_a
+ using refinement_b
+ result = refined_class.new.foo
+ end
+
+ result.should == [:B, :A, :C]
+ end
+
+ it "looks in the current active refinement from included modules" do
+ refined_class = ModuleSpecs.build_refined_class(for_super: true)
+
+ a = Module.new do
+ def foo
+ [:A] + super
+ end
+ end
+
+ b = Module.new do
+ def foo
+ [:B] + super
+ end
+ end
+
+ refinement = Module.new do
+ refine refined_class do
+ def foo
+ [:LAST] + super
+ end
+ end
+ end
+
+ refinement_a_b = Module.new do
+ refine refined_class do
+ include a
+ include b
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ using refinement_a_b
+ result = refined_class.new.foo
+ end
+
+ result.should == [:B, :A, :LAST, :C]
+ end
+
+ it "looks in the lexical scope refinements before other active refinements" do
+ refined_class = ModuleSpecs.build_refined_class(for_super: true)
+
+ refinement_local = Module.new do
+ refine refined_class do
+ def foo
+ [:LOCAL] + super
+ end
+ end
+ end
+
+ a = Module.new do
+ using refinement_local
+
+ def foo
+ [:A] + super
+ end
+ end
+
+ refinement = Module.new do
+ refine refined_class do
+ include a
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = refined_class.new.foo
+ end
+
+ result.should == [:A, :LOCAL, :C]
+ end
+ end
+ end
+
+ it 'and alias aliases a method within a refinement module, but not outside it' do
+ Module.new do
+ using Module.new {
+ refine Array do
+ alias :orig_count :count
+ end
+ }
+ [1,2].orig_count.should == 2
+ end
+ -> { [1,2].orig_count }.should raise_error(NoMethodError)
+ end
+
+ it 'and alias_method aliases a method within a refinement module, but not outside it' do
+ Module.new do
+ using Module.new {
+ refine Array do
+ alias_method :orig_count, :count
+ end
+ }
+ [1,2].orig_count.should == 2
+ end
+ -> { [1,2].orig_count }.should raise_error(NoMethodError)
+ end
+
+ it "and instance_methods returns a list of methods including those of the refined module" do
+ methods = Array.instance_methods
+ methods_2 = []
+ Module.new do
+ refine Array do
+ methods_2 = instance_methods
+ end
+ end
+ methods.should == methods_2
+ end
+
+ # Refinements are inherited by module inclusion.
+ # That is, using activates all refinements in the ancestors of the specified module.
+ # Refinements in a descendant have priority over refinements in an ancestor.
+ context "module inclusion" do
+ it "activates all refinements from all ancestors" do
+ refinement_included = Module.new do
+ refine Integer do
+ def to_json_format
+ to_s
+ end
+ end
+ end
+
+ refinement = Module.new do
+ include refinement_included
+
+ refine Array do
+ def to_json_format
+ "[" + map { |i| i.to_s }.join(", ") + "]"
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = [5.to_json_format, [1, 2, 3].to_json_format]
+ end
+
+ result.should == ["5", "[1, 2, 3]"]
+ end
+
+ it "overrides methods of ancestors by methods in descendants" do
+ refinement_included = Module.new do
+ refine Integer do
+ def to_json_format
+ to_s
+ end
+ end
+ end
+
+ refinement = Module.new do
+ include refinement_included
+
+ refine Integer do
+ def to_json_format
+ "hello from refinement"
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = 5.to_json_format
+ end
+
+ result.should == "hello from refinement"
+ end
+ end
+
+ it 'does not list methods defined only in refinement' do
+ refine_object = Module.new do
+ refine Object do
+ def refinement_only_method
+ end
+ end
+ end
+ spec = self
+ klass = Class.new { instance_methods.should_not spec.send(:include, :refinement_only_method) }
+ instance = klass.new
+ instance.methods.should_not include :refinement_only_method
+ instance.respond_to?(:refinement_only_method).should == false
+ -> { instance.method :refinement_only_method }.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/core/module/remove_class_variable_spec.rb b/spec/ruby/core/module/remove_class_variable_spec.rb
new file mode 100644
index 0000000000..ab9514adf6
--- /dev/null
+++ b/spec/ruby/core/module/remove_class_variable_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#remove_class_variable" do
+ it "removes class variable" do
+ m = ModuleSpecs::MVars.dup
+ m.send(:remove_class_variable, :@@mvar)
+ m.class_variable_defined?(:@@mvar).should == false
+ end
+
+ it "returns the value of removing class variable" do
+ m = ModuleSpecs::MVars.dup
+ m.send(:remove_class_variable, :@@mvar).should == :mvar
+ end
+
+ it "removes a class variable defined in a metaclass" do
+ obj = mock("metaclass class variable")
+ meta = obj.singleton_class
+ meta.send :class_variable_set, :@@var, 1
+ meta.send(:remove_class_variable, :@@var).should == 1
+ meta.class_variable_defined?(:@@var).should be_false
+ end
+
+ it "raises a NameError when removing class variable declared in included module" do
+ c = ModuleSpecs::RemoveClassVariable.new { include ModuleSpecs::MVars.dup }
+ -> { c.send(:remove_class_variable, :@@mvar) }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when passed a symbol with one leading @" do
+ -> { ModuleSpecs::MVars.send(:remove_class_variable, :@mvar) }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when passed a symbol with no leading @" do
+ -> { ModuleSpecs::MVars.send(:remove_class_variable, :mvar) }.should raise_error(NameError)
+ end
+
+ it "raises a NameError when an uninitialized class variable is given" do
+ -> { ModuleSpecs::MVars.send(:remove_class_variable, :@@nonexisting_class_variable) }.should raise_error(NameError)
+ end
+
+ it "is public" do
+ Module.should_not have_private_instance_method(:remove_class_variable)
+ end
+end
diff --git a/spec/ruby/core/module/remove_const_spec.rb b/spec/ruby/core/module/remove_const_spec.rb
new file mode 100644
index 0000000000..0ac23f05a5
--- /dev/null
+++ b/spec/ruby/core/module/remove_const_spec.rb
@@ -0,0 +1,105 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/constants'
+
+describe "Module#remove_const" do
+ it "removes the constant specified by a String or Symbol from the receiver's constant table" do
+ ConstantSpecs::ModuleM::CS_CONST252 = :const252
+ ConstantSpecs::ModuleM::CS_CONST252.should == :const252
+
+ ConstantSpecs::ModuleM.send :remove_const, :CS_CONST252
+ -> { ConstantSpecs::ModuleM::CS_CONST252 }.should raise_error(NameError)
+
+ ConstantSpecs::ModuleM::CS_CONST253 = :const253
+ ConstantSpecs::ModuleM::CS_CONST253.should == :const253
+
+ ConstantSpecs::ModuleM.send :remove_const, "CS_CONST253"
+ -> { ConstantSpecs::ModuleM::CS_CONST253 }.should raise_error(NameError)
+ end
+
+ it "returns the value of the removed constant" do
+ ConstantSpecs::ModuleM::CS_CONST254 = :const254
+ ConstantSpecs::ModuleM.send(:remove_const, :CS_CONST254).should == :const254
+ end
+
+ it "raises a NameError and does not call #const_missing if the constant is not defined" do
+ ConstantSpecs.should_not_receive(:const_missing)
+ -> { ConstantSpecs.send(:remove_const, :Nonexistent) }.should raise_error(NameError)
+ end
+
+ it "raises a NameError and does not call #const_missing if the constant is not defined directly in the module" do
+ begin
+ ConstantSpecs::ModuleM::CS_CONST255 = :const255
+ ConstantSpecs::ContainerA::CS_CONST255.should == :const255
+ ConstantSpecs::ContainerA.should_not_receive(:const_missing)
+
+ -> do
+ ConstantSpecs::ContainerA.send :remove_const, :CS_CONST255
+ end.should raise_error(NameError)
+ ensure
+ ConstantSpecs::ModuleM.send :remove_const, "CS_CONST255"
+ end
+ end
+
+ it "raises a NameError if the name does not start with a capital letter" do
+ -> { ConstantSpecs.send :remove_const, "name" }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name starts with a non-alphabetic character" do
+ -> { ConstantSpecs.send :remove_const, "__CONSTX__" }.should raise_error(NameError)
+ -> { ConstantSpecs.send :remove_const, "@Name" }.should raise_error(NameError)
+ -> { ConstantSpecs.send :remove_const, "!Name" }.should raise_error(NameError)
+ -> { ConstantSpecs.send :remove_const, "::Name" }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the name contains non-alphabetic characters except '_'" do
+ ConstantSpecs::ModuleM::CS_CONST256 = :const256
+ ConstantSpecs::ModuleM.send :remove_const, "CS_CONST256"
+ -> { ConstantSpecs.send :remove_const, "Name=" }.should raise_error(NameError)
+ -> { ConstantSpecs.send :remove_const, "Name?" }.should raise_error(NameError)
+ end
+
+ it "calls #to_str to convert the given name to a String" do
+ ConstantSpecs::CS_CONST257 = :const257
+ name = mock("CS_CONST257")
+ name.should_receive(:to_str).and_return("CS_CONST257")
+ ConstantSpecs.send(:remove_const, name).should == :const257
+ end
+
+ it "raises a TypeError if conversion to a String by calling #to_str fails" do
+ name = mock('123')
+ -> { ConstantSpecs.send :remove_const, name }.should raise_error(TypeError)
+
+ name.should_receive(:to_str).and_return(123)
+ -> { ConstantSpecs.send :remove_const, name }.should raise_error(TypeError)
+ end
+
+ it "is a private method" do
+ Module.private_methods.should include(:remove_const)
+ end
+
+ it "returns nil when removing autoloaded constant" do
+ ConstantSpecs.autoload :AutoloadedConstant, 'a_file'
+ ConstantSpecs.send(:remove_const, :AutoloadedConstant).should be_nil
+ end
+
+ it "updates the constant value" do
+ module ConstantSpecs::RemovedConstantUpdate
+ module M
+ FOO = 'm'
+ end
+
+ module A
+ include M
+ FOO = 'a'
+ def self.foo
+ FOO
+ end
+ end
+
+ A.foo.should == 'a'
+
+ A.send(:remove_const,:FOO)
+ A.foo.should == 'm'
+ end
+ end
+end
diff --git a/spec/ruby/core/module/remove_method_spec.rb b/spec/ruby/core/module/remove_method_spec.rb
new file mode 100644
index 0000000000..94b255df62
--- /dev/null
+++ b/spec/ruby/core/module/remove_method_spec.rb
@@ -0,0 +1,131 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+module ModuleSpecs
+ class Parent
+ def method_to_remove; 1; end
+ end
+
+ class First
+ def method_to_remove; 1; end
+ end
+
+ class Second < First
+ def method_to_remove; 2; end
+ end
+end
+
+describe "Module#remove_method" do
+ before :each do
+ @module = Module.new { def method_to_remove; end }
+ end
+
+ it "is a public method" do
+ Module.should have_public_instance_method(:remove_method, false)
+ end
+
+ it "removes the method from a class" do
+ klass = Class.new do
+ def method_to_remove; 1; end
+ end
+ x = klass.new
+ klass.send(:remove_method, :method_to_remove)
+ x.respond_to?(:method_to_remove).should == false
+ end
+
+ it "removes method from subclass, but not parent" do
+ child = Class.new(ModuleSpecs::Parent) do
+ def method_to_remove; 2; end
+ remove_method :method_to_remove
+ end
+ x = child.new
+ x.respond_to?(:method_to_remove).should == true
+ x.method_to_remove.should == 1
+ end
+
+ it "updates the method implementation" do
+ m_module = Module.new do
+ def foo
+ 'm'
+ end
+ end
+
+ a_class = Class.new do
+ include m_module
+
+ def foo
+ 'a'
+ end
+ end
+
+ a = a_class.new
+ foo = -> { a.foo }
+ foo.call.should == 'a'
+ a_class.remove_method(:foo)
+ foo.call.should == 'm'
+ end
+
+ it "removes multiple methods with 1 call" do
+ klass = Class.new do
+ def method_to_remove_1; 1; end
+ def method_to_remove_2; 2; end
+ remove_method :method_to_remove_1, :method_to_remove_2
+ end
+ x = klass.new
+ x.respond_to?(:method_to_remove_1).should == false
+ x.respond_to?(:method_to_remove_2).should == false
+ end
+
+ it "accepts multiple arguments" do
+ Module.instance_method(:remove_method).arity.should < 0
+ end
+
+ it "does not remove any instance methods when argument not given" do
+ before = @module.instance_methods(true) + @module.private_instance_methods(true)
+ @module.send :remove_method
+ after = @module.instance_methods(true) + @module.private_instance_methods(true)
+ before.sort.should == after.sort
+ end
+
+ it "returns self" do
+ @module.send(:remove_method, :method_to_remove).should equal(@module)
+ end
+
+ it "raises a NameError when attempting to remove method further up the inheritance tree" do
+ Class.new(ModuleSpecs::Second) do
+ -> {
+ remove_method :method_to_remove
+ }.should raise_error(NameError)
+ end
+ end
+
+ it "raises a NameError when attempting to remove a missing method" do
+ Class.new(ModuleSpecs::Second) do
+ -> {
+ remove_method :blah
+ }.should raise_error(NameError)
+ end
+ end
+
+ describe "on frozen instance" do
+ before :each do
+ @frozen = @module.dup.freeze
+ end
+
+ it "raises a FrozenError when passed a name" do
+ -> { @frozen.send :remove_method, :method_to_remove }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError when passed a missing name" do
+ -> { @frozen.send :remove_method, :not_exist }.should raise_error(FrozenError)
+ end
+
+ it "raises a TypeError when passed a not name" do
+ -> { @frozen.send :remove_method, Object.new }.should raise_error(TypeError)
+ end
+
+ it "does not raise exceptions when no arguments given" do
+ @frozen.send(:remove_method).should equal(@frozen)
+ end
+ end
+end
diff --git a/spec/ruby/core/module/ruby2_keywords_spec.rb b/spec/ruby/core/module/ruby2_keywords_spec.rb
new file mode 100644
index 0000000000..80a99e2624
--- /dev/null
+++ b/spec/ruby/core/module/ruby2_keywords_spec.rb
@@ -0,0 +1,319 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#ruby2_keywords" do
+ class << self
+ ruby2_keywords def mark(*args)
+ args
+ end
+ end
+
+ it "marks the final hash argument as keyword hash" do
+ last = mark(1, 2, a: "a").last
+ Hash.ruby2_keywords_hash?(last).should == true
+ end
+
+ it "makes a copy of the hash and only marks the copy as keyword hash" do
+ obj = Object.new
+ obj.singleton_class.class_exec do
+ def regular(*args)
+ args.last
+ end
+ end
+
+ h = {a: 1}
+ ruby_version_is "3.0" do
+ obj.regular(**h).should.equal?(h)
+ end
+
+ last = mark(**h).last
+ Hash.ruby2_keywords_hash?(last).should == true
+ Hash.ruby2_keywords_hash?(h).should == false
+
+ last2 = mark(**last).last # last is already marked
+ Hash.ruby2_keywords_hash?(last2).should == true
+ Hash.ruby2_keywords_hash?(last).should == true
+ last2.should_not.equal?(last)
+ Hash.ruby2_keywords_hash?(h).should == false
+ end
+
+ it "makes a copy and unmark the Hash when calling a method taking (arg)" do
+ obj = Object.new
+ obj.singleton_class.class_exec do
+ def single(arg)
+ arg
+ end
+ end
+
+ h = { a: 1 }
+ args = mark(**h)
+ marked = args.last
+ Hash.ruby2_keywords_hash?(marked).should == true
+
+ after_usage = obj.single(*args)
+ after_usage.should == h
+ after_usage.should_not.equal?(h)
+ after_usage.should_not.equal?(marked)
+ Hash.ruby2_keywords_hash?(after_usage).should == false
+ Hash.ruby2_keywords_hash?(marked).should == true
+ end
+
+ it "makes a copy and unmark the Hash when calling a method taking (**kw)" do
+ obj = Object.new
+ obj.singleton_class.class_exec do
+ def kwargs(**kw)
+ kw
+ end
+ end
+
+ h = { a: 1 }
+ args = mark(**h)
+ marked = args.last
+ Hash.ruby2_keywords_hash?(marked).should == true
+
+ after_usage = obj.kwargs(*args)
+ after_usage.should == h
+ after_usage.should_not.equal?(h)
+ after_usage.should_not.equal?(marked)
+ Hash.ruby2_keywords_hash?(after_usage).should == false
+ Hash.ruby2_keywords_hash?(marked).should == true
+ end
+
+ ruby_version_is "3.2" do
+ it "makes a copy and unmark the Hash when calling a method taking (*args)" do
+ obj = Object.new
+ obj.singleton_class.class_exec do
+ def splat(*args)
+ args.last
+ end
+
+ def splat1(arg, *args)
+ args.last
+ end
+
+ def proc_call(*args)
+ -> *a { a.last }.call(*args)
+ end
+ end
+
+ h = { a: 1 }
+ args = mark(**h)
+ marked = args.last
+ Hash.ruby2_keywords_hash?(marked).should == true
+
+ after_usage = obj.splat(*args)
+ after_usage.should == h
+ after_usage.should_not.equal?(h)
+ after_usage.should_not.equal?(marked)
+ Hash.ruby2_keywords_hash?(after_usage).should == false
+ Hash.ruby2_keywords_hash?(marked).should == true
+
+ args = mark(1, **h)
+ marked = args.last
+ after_usage = obj.splat1(*args)
+ after_usage.should == h
+ after_usage.should_not.equal?(h)
+ after_usage.should_not.equal?(marked)
+ Hash.ruby2_keywords_hash?(after_usage).should == false
+ Hash.ruby2_keywords_hash?(marked).should == true
+
+ args = mark(**h)
+ marked = args.last
+ after_usage = obj.proc_call(*args)
+ after_usage.should == h
+ after_usage.should_not.equal?(h)
+ after_usage.should_not.equal?(marked)
+ Hash.ruby2_keywords_hash?(after_usage).should == false
+ Hash.ruby2_keywords_hash?(marked).should == true
+
+ args = mark(**h)
+ marked = args.last
+ after_usage = obj.send(:splat, *args)
+ after_usage.should == h
+ after_usage.should_not.equal?(h)
+ after_usage.should_not.equal?(marked)
+ Hash.ruby2_keywords_hash?(after_usage).should == false
+ Hash.ruby2_keywords_hash?(marked).should == true
+ end
+ end
+
+ ruby_version_is ""..."3.2" do
+ # https://bugs.ruby-lang.org/issues/18625
+ it "does NOT copy the Hash when calling a method taking (*args)" do
+ obj = Object.new
+ obj.singleton_class.class_exec do
+ def splat(*args)
+ args.last
+ end
+
+ def splat1(arg, *args)
+ args.last
+ end
+
+ def proc_call(*args)
+ -> *a { a.last }.call(*args)
+ end
+ end
+
+ h = { a: 1 }
+ args = mark(**h)
+ marked = args.last
+ Hash.ruby2_keywords_hash?(marked).should == true
+
+ after_usage = obj.splat(*args)
+ after_usage.should == h
+ after_usage.should_not.equal?(h)
+ after_usage.should.equal?(marked) # https://bugs.ruby-lang.org/issues/18625
+ Hash.ruby2_keywords_hash?(after_usage).should == true # https://bugs.ruby-lang.org/issues/18625
+ Hash.ruby2_keywords_hash?(marked).should == true
+
+ args = mark(1, **h)
+ marked = args.last
+ after_usage = obj.splat1(*args)
+ after_usage.should == h
+ after_usage.should_not.equal?(h)
+ after_usage.should.equal?(marked) # https://bugs.ruby-lang.org/issues/18625
+ Hash.ruby2_keywords_hash?(after_usage).should == true # https://bugs.ruby-lang.org/issues/18625
+ Hash.ruby2_keywords_hash?(marked).should == true
+
+ args = mark(**h)
+ marked = args.last
+ after_usage = obj.proc_call(*args)
+ after_usage.should == h
+ after_usage.should_not.equal?(h)
+ after_usage.should.equal?(marked) # https://bugs.ruby-lang.org/issues/18625
+ Hash.ruby2_keywords_hash?(after_usage).should == true # https://bugs.ruby-lang.org/issues/18625
+ Hash.ruby2_keywords_hash?(marked).should == true
+
+ args = mark(**h)
+ marked = args.last
+ after_usage = obj.send(:splat, *args)
+ after_usage.should == h
+ after_usage.should_not.equal?(h)
+ send_copies = RUBY_ENGINE == "ruby" # inconsistent with Proc#call above for CRuby
+ after_usage.equal?(marked).should == !send_copies
+ Hash.ruby2_keywords_hash?(after_usage).should == !send_copies
+ Hash.ruby2_keywords_hash?(marked).should == true
+ end
+ end
+
+ it "applies to the underlying method and applies across aliasing" do
+ obj = Object.new
+
+ obj.singleton_class.class_exec do
+ def foo(*a) a.last end
+ alias_method :bar, :foo
+ ruby2_keywords :foo
+
+ def baz(*a) a.last end
+ ruby2_keywords :baz
+ alias_method :bob, :baz
+ end
+
+ last = obj.foo(1, 2, a: "a")
+ Hash.ruby2_keywords_hash?(last).should == true
+
+ last = obj.bar(1, 2, a: "a")
+ Hash.ruby2_keywords_hash?(last).should == true
+
+ last = obj.baz(1, 2, a: "a")
+ Hash.ruby2_keywords_hash?(last).should == true
+
+ last = obj.bob(1, 2, a: "a")
+ Hash.ruby2_keywords_hash?(last).should == true
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "fixes delegation warnings when calling a method accepting keywords" do
+ obj = Object.new
+
+ obj.singleton_class.class_exec do
+ def foo(*a) bar(*a) end
+ def bar(*a, **b) end
+ end
+
+ -> { obj.foo(1, 2, {a: "a"}) }.should complain(/Using the last argument as keyword parameters is deprecated/)
+
+ obj.singleton_class.class_exec do
+ ruby2_keywords :foo
+ end
+
+ -> { obj.foo(1, 2, {a: "a"}) }.should_not complain
+ end
+ end
+
+ it "returns nil" do
+ obj = Object.new
+
+ obj.singleton_class.class_exec do
+ def foo(*a) end
+
+ ruby2_keywords(:foo).should == nil
+ end
+ end
+
+ it "raises NameError when passed not existing method name" do
+ obj = Object.new
+
+ -> {
+ obj.singleton_class.class_exec do
+ ruby2_keywords :not_existing
+ end
+ }.should raise_error(NameError, /undefined method `not_existing'/)
+ end
+
+ it "accepts String as well" do
+ obj = Object.new
+
+ obj.singleton_class.class_exec do
+ def foo(*a) a.last end
+ ruby2_keywords "foo"
+ end
+
+ last = obj.foo(1, 2, a: "a")
+ Hash.ruby2_keywords_hash?(last).should == true
+ end
+
+ it "raises TypeError when passed not Symbol or String" do
+ obj = Object.new
+
+ -> {
+ obj.singleton_class.class_exec do
+ ruby2_keywords Object.new
+ end
+ }.should raise_error(TypeError, /is not a symbol nor a string/)
+ end
+
+ it "prints warning when a method does not accept argument splat" do
+ obj = Object.new
+ def obj.foo(a, b, c) end
+
+ -> {
+ obj.singleton_class.class_exec do
+ ruby2_keywords :foo
+ end
+ }.should complain(/Skipping set of ruby2_keywords flag for/)
+ end
+
+ it "prints warning when a method accepts keywords" do
+ obj = Object.new
+ def obj.foo(a:, b:) end
+
+ -> {
+ obj.singleton_class.class_exec do
+ ruby2_keywords :foo
+ end
+ }.should complain(/Skipping set of ruby2_keywords flag for/)
+ end
+
+ it "prints warning when a method accepts keyword splat" do
+ obj = Object.new
+ def obj.foo(**a) end
+
+ -> {
+ obj.singleton_class.class_exec do
+ ruby2_keywords :foo
+ end
+ }.should complain(/Skipping set of ruby2_keywords flag for/)
+ end
+end
diff --git a/spec/ruby/core/module/shared/class_eval.rb b/spec/ruby/core/module/shared/class_eval.rb
new file mode 100644
index 0000000000..9ef7b5be44
--- /dev/null
+++ b/spec/ruby/core/module/shared/class_eval.rb
@@ -0,0 +1,168 @@
+describe :module_class_eval, shared: true do
+ # TODO: This should probably be replaced with a "should behave like" that uses
+ # the many scoping/binding specs from kernel/eval_spec, since most of those
+ # behaviors are the same for instance_eval. See also module_eval/class_eval.
+
+ it "evaluates a given string in the context of self" do
+ ModuleSpecs.send(@method, "self").should == ModuleSpecs
+ ModuleSpecs.send(@method, "1 + 1").should == 2
+ end
+
+ it "does not add defined methods to other classes" do
+ FalseClass.send(@method) do
+ def foo
+ 'foo'
+ end
+ end
+ -> {42.foo}.should raise_error(NoMethodError)
+ end
+
+ it "resolves constants in the caller scope" do
+ ModuleSpecs::ClassEvalTest.get_constant_from_scope.should == ModuleSpecs::Lookup
+ end
+
+ it "resolves constants in the caller scope ignoring send" do
+ ModuleSpecs::ClassEvalTest.get_constant_from_scope_with_send(@method).should == ModuleSpecs::Lookup
+ end
+
+ it "resolves constants in the receiver's scope" do
+ ModuleSpecs.send(@method, "Lookup").should == ModuleSpecs::Lookup
+ ModuleSpecs.send(@method, "Lookup::LOOKIE").should == ModuleSpecs::Lookup::LOOKIE
+ end
+
+ it "defines constants in the receiver's scope" do
+ ModuleSpecs.send(@method, "module NewEvaluatedModule;end")
+ ModuleSpecs.const_defined?(:NewEvaluatedModule, false).should == true
+ end
+
+ it "evaluates a given block in the context of self" do
+ ModuleSpecs.send(@method) { self }.should == ModuleSpecs
+ ModuleSpecs.send(@method) { 1 + 1 }.should == 2
+ end
+
+ it "passes the module as the first argument of the block" do
+ given = nil
+ ModuleSpecs.send(@method) do |block_parameter|
+ given = block_parameter
+ end
+ given.should equal ModuleSpecs
+ end
+
+ it "uses the optional filename and lineno parameters for error messages" do
+ ModuleSpecs.send(@method, "[__FILE__, __LINE__]", "test", 102).should == ["test", 102]
+ end
+
+ it "converts a non-string filename to a string using to_str" do
+ (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__)
+ ModuleSpecs.send(@method, "1+1", file)
+
+ (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__)
+ ModuleSpecs.send(@method, "1+1", file, 15)
+ end
+
+ it "raises a TypeError when the given filename can't be converted to string using to_str" do
+ (file = mock('123')).should_receive(:to_str).and_return(123)
+ -> { ModuleSpecs.send(@method, "1+1", file) }.should raise_error(TypeError, /can't convert MockObject to String/)
+ end
+
+ it "converts non string eval-string to string using to_str" do
+ (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1")
+ ModuleSpecs.send(@method, o).should == 2
+
+ (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1")
+ ModuleSpecs.send(@method, o, "file.rb").should == 2
+
+ (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1")
+ ModuleSpecs.send(@method, o, "file.rb", 15).should == 2
+ end
+
+ it "raises a TypeError when the given eval-string can't be converted to string using to_str" do
+ o = mock('x')
+ -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError, "no implicit conversion of MockObject into String")
+
+ (o = mock('123')).should_receive(:to_str).and_return(123)
+ -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError, /can't convert MockObject to String/)
+ end
+
+ it "raises an ArgumentError when no arguments and no block are given" do
+ -> { ModuleSpecs.send(@method) }.should raise_error(ArgumentError, "wrong number of arguments (given 0, expected 1..3)")
+ end
+
+ it "raises an ArgumentError when more than 3 arguments are given" do
+ -> {
+ ModuleSpecs.send(@method, "1 + 1", "some file", 0, "bogus")
+ }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 1..3)")
+ end
+
+ it "raises an ArgumentError when a block and normal arguments are given" do
+ -> {
+ ModuleSpecs.send(@method, "1 + 1") { 1 + 1 }
+ }.should raise_error(ArgumentError, "wrong number of arguments (given 1, expected 0)")
+ end
+
+ # This case was found because Rubinius was caching the compiled
+ # version of the string and not duping the methods within the
+ # eval, causing the method addition to change the static scope
+ # of the shared CompiledCode.
+ it "adds methods respecting the lexical constant scope" do
+ code = "def self.attribute; C; end"
+
+ a = Class.new do
+ self::C = "A"
+ end
+
+ b = Class.new do
+ self::C = "B"
+ end
+
+ a.send @method, code
+ b.send @method, code
+
+ a.attribute.should == "A"
+ b.attribute.should == "B"
+ end
+
+ it "activates refinements from the eval scope" do
+ refinery = Module.new do
+ refine ModuleSpecs::NamedClass do
+ def foo
+ "bar"
+ end
+ end
+ end
+
+ mid = @method
+ result = nil
+
+ Class.new do
+ using refinery
+
+ result = send(mid, "ModuleSpecs::NamedClass.new.foo")
+ end
+
+ result.should == "bar"
+ end
+
+ it "activates refinements from the eval scope with block" do
+ refinery = Module.new do
+ refine ModuleSpecs::NamedClass do
+ def foo
+ "bar"
+ end
+ end
+ end
+
+ mid = @method
+ result = nil
+
+ Class.new do
+ using refinery
+
+ result = send(mid) do
+ ModuleSpecs::NamedClass.new.foo
+ end
+ end
+
+ result.should == "bar"
+ end
+end
diff --git a/spec/ruby/core/module/shared/class_exec.rb b/spec/ruby/core/module/shared/class_exec.rb
new file mode 100644
index 0000000000..c7a9e5297f
--- /dev/null
+++ b/spec/ruby/core/module/shared/class_exec.rb
@@ -0,0 +1,29 @@
+describe :module_class_exec, shared: true do
+ it "does not add defined methods to other classes" do
+ FalseClass.send(@method) do
+ def foo
+ 'foo'
+ end
+ end
+ -> {42.foo}.should raise_error(NoMethodError)
+ end
+
+ it "defines method in the receiver's scope" do
+ ModuleSpecs::Subclass.send(@method) { def foo; end }
+ ModuleSpecs::Subclass.new.respond_to?(:foo).should == true
+ end
+
+ it "evaluates a given block in the context of self" do
+ ModuleSpecs::Subclass.send(@method) { self }.should == ModuleSpecs::Subclass
+ ModuleSpecs::Subclass.new.send(@method) { 1 + 1 }.should == 2
+ end
+
+ it "raises a LocalJumpError when no block is given" do
+ -> { ModuleSpecs::Subclass.send(@method) }.should raise_error(LocalJumpError)
+ end
+
+ it "passes arguments to the block" do
+ a = ModuleSpecs::Subclass
+ a.send(@method, 1) { |b| b }.should equal(1)
+ end
+end
diff --git a/spec/ruby/core/module/shared/equal_value.rb b/spec/ruby/core/module/shared/equal_value.rb
new file mode 100644
index 0000000000..f1227d873c
--- /dev/null
+++ b/spec/ruby/core/module/shared/equal_value.rb
@@ -0,0 +1,14 @@
+describe :module_equal, shared: true do
+ it "returns true if self and the given module are the same" do
+ ModuleSpecs.send(@method, ModuleSpecs).should == true
+ ModuleSpecs::Child.send(@method, ModuleSpecs::Child).should == true
+ ModuleSpecs::Parent.send(@method, ModuleSpecs::Parent).should == true
+ ModuleSpecs::Basic.send(@method, ModuleSpecs::Basic).should == true
+ ModuleSpecs::Super.send(@method, ModuleSpecs::Super).should == true
+
+ ModuleSpecs::Child.send(@method, ModuleSpecs).should == false
+ ModuleSpecs::Child.send(@method, ModuleSpecs::Parent).should == false
+ ModuleSpecs::Child.send(@method, ModuleSpecs::Basic).should == false
+ ModuleSpecs::Child.send(@method, ModuleSpecs::Super).should == false
+ end
+end
diff --git a/spec/ruby/core/module/shared/set_visibility.rb b/spec/ruby/core/module/shared/set_visibility.rb
new file mode 100644
index 0000000000..9f31e230ca
--- /dev/null
+++ b/spec/ruby/core/module/shared/set_visibility.rb
@@ -0,0 +1,186 @@
+# -*- encoding: us-ascii -*-
+
+describe :set_visibility, shared: true do
+ it "is a private method" do
+ Module.should have_private_instance_method(@method, false)
+ end
+
+ describe "with argument" do
+ describe "one or more arguments" do
+ it "sets visibility of given method names" do
+ visibility = @method
+ old_visibility = [:protected, :private].find {|vis| vis != visibility }
+
+ mod = Module.new {
+ send old_visibility
+ def test1() end
+ def test2() end
+ send visibility, :test1, :test2
+ }
+ mod.should send(:"have_#{visibility}_instance_method", :test1, false)
+ mod.should send(:"have_#{visibility}_instance_method", :test2, false)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ describe "array as a single argument" do
+ it "sets visibility of given method names" do
+ visibility = @method
+ old_visibility = [:protected, :private].find {|vis| vis != visibility }
+
+ mod = Module.new {
+ send old_visibility
+ def test1() end
+ def test2() end
+ send visibility, [:test1, :test2]
+ }
+ mod.should send(:"have_#{visibility}_instance_method", :test1, false)
+ mod.should send(:"have_#{visibility}_instance_method", :test2, false)
+ end
+ end
+ end
+
+ it "does not clone method from the ancestor when setting to the same visibility in a child" do
+ visibility = @method
+ parent = Module.new {
+ def test_method; end
+ send(visibility, :test_method)
+ }
+
+ child = Module.new {
+ include parent
+ send(visibility, :test_method)
+ }
+
+ child.should_not send(:"have_#{visibility}_instance_method", :test_method, false)
+ end
+ end
+
+ describe "without arguments" do
+ it "sets visibility to following method definitions" do
+ visibility = @method
+ mod = Module.new {
+ send visibility
+
+ def test1() end
+ def test2() end
+ }
+
+ mod.should send(:"have_#{@method}_instance_method", :test1, false)
+ mod.should send(:"have_#{@method}_instance_method", :test2, false)
+ end
+
+ it "stops setting visibility if the body encounters other visibility setters without arguments" do
+ visibility = @method
+ new_visibility = nil
+ mod = Module.new {
+ send visibility
+ new_visibility = [:protected, :private].find {|vis| vis != visibility }
+ send new_visibility
+ def test1() end
+ }
+
+ mod.should send(:"have_#{new_visibility}_instance_method", :test1, false)
+ end
+
+ it "continues setting visibility if the body encounters other visibility setters with arguments" do
+ visibility = @method
+ mod = Module.new {
+ send visibility
+ def test1() end
+ send([:protected, :private].find {|vis| vis != visibility }, :test1)
+ def test2() end
+ }
+
+ mod.should send(:"have_#{@method}_instance_method", :test2, false)
+ end
+
+ it "does not affect module_evaled method definitions when itself is outside the eval" do
+ visibility = @method
+ mod = Module.new {
+ send visibility
+
+ module_eval { def test1() end }
+ module_eval " def test2() end "
+ }
+
+ mod.should have_public_instance_method(:test1, false)
+ mod.should have_public_instance_method(:test2, false)
+ end
+
+ it "does not affect outside method definitions when itself is inside a module_eval" do
+ visibility = @method
+ mod = Module.new {
+ module_eval { send visibility }
+
+ def test1() end
+ }
+
+ mod.should have_public_instance_method(:test1, false)
+ end
+
+ it "affects normally if itself and method definitions are inside a module_eval" do
+ visibility = @method
+ mod = Module.new {
+ module_eval {
+ send visibility
+
+ def test1() end
+ }
+ }
+
+ mod.should send(:"have_#{@method}_instance_method", :test1, false)
+ end
+
+ it "does not affect method definitions when itself is inside an eval and method definitions are outside" do
+ visibility = @method
+ initialized_visibility = [:public, :protected, :private].find {|sym| sym != visibility }
+ mod = Module.new {
+ send initialized_visibility
+ eval visibility.to_s
+
+ def test1() end
+ }
+
+ mod.should send(:"have_#{initialized_visibility}_instance_method", :test1, false)
+ end
+
+ it "affects evaled method definitions when itself is outside the eval" do
+ visibility = @method
+ mod = Module.new {
+ send visibility
+
+ eval "def test1() end"
+ }
+
+ mod.should send(:"have_#{@method}_instance_method", :test1, false)
+ end
+
+ it "affects normally if itself and following method definitions are inside a eval" do
+ visibility = @method
+ mod = Module.new {
+ eval <<-CODE
+ #{visibility}
+
+ def test1() end
+ CODE
+ }
+
+ mod.should send(:"have_#{@method}_instance_method", :test1, false)
+ end
+
+ describe "within a closure" do
+ it "sets the visibility outside the closure" do
+ visibility = @method
+ mod = Module.new {
+ 1.times {
+ send visibility
+ }
+ def test1() end
+ }
+
+ mod.should send(:"have_#{@method}_instance_method", :test1, false)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/module/singleton_class_spec.rb b/spec/ruby/core/module/singleton_class_spec.rb
new file mode 100644
index 0000000000..052755b73b
--- /dev/null
+++ b/spec/ruby/core/module/singleton_class_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+describe "Module#singleton_class?" do
+ it "returns true for singleton classes" do
+ xs = self.singleton_class
+ xs.should.singleton_class?
+ end
+
+ it "returns false for other classes" do
+ c = Class.new
+ c.should_not.singleton_class?
+ end
+
+ describe "with singleton values" do
+ it "returns false for nil's singleton class" do
+ NilClass.should_not.singleton_class?
+ end
+
+ it "returns false for true's singleton class" do
+ TrueClass.should_not.singleton_class?
+ end
+
+ it "returns false for false's singleton class" do
+ FalseClass.should_not.singleton_class?
+ end
+ end
+end
diff --git a/spec/ruby/core/module/to_s_spec.rb b/spec/ruby/core/module/to_s_spec.rb
new file mode 100644
index 0000000000..6b1a615ef9
--- /dev/null
+++ b/spec/ruby/core/module/to_s_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Module#to_s" do
+ it 'returns the name of the module if it has a name' do
+ Enumerable.to_s.should == 'Enumerable'
+ String.to_s.should == 'String'
+ end
+
+ it "returns the full constant path leading to the module" do
+ ModuleSpecs::LookupMod.to_s.should == "ModuleSpecs::LookupMod"
+ end
+
+ it "works with an anonymous module" do
+ m = Module.new
+ m.to_s.should =~ /\A#<Module:0x\h+>\z/
+ end
+
+ it "works with an anonymous class" do
+ c = Class.new
+ c.to_s.should =~ /\A#<Class:0x\h+>\z/
+ end
+
+ it 'for the singleton class of an object of an anonymous class' do
+ klass = Class.new
+ obj = klass.new
+ sclass = obj.singleton_class
+ sclass.to_s.should == "#<Class:#{obj}>"
+ sclass.to_s.should =~ /\A#<Class:#<#{klass}:0x\h+>>\z/
+ sclass.to_s.should =~ /\A#<Class:#<#<Class:0x\h+>:0x\h+>>\z/
+ end
+
+ it 'for a singleton class of a module includes the module name' do
+ ModuleSpecs.singleton_class.to_s.should == '#<Class:ModuleSpecs>'
+ end
+
+ it 'for a metaclass includes the class name' do
+ ModuleSpecs::NamedClass.singleton_class.to_s.should == '#<Class:ModuleSpecs::NamedClass>'
+ end
+
+ it 'for objects includes class name and object ID' do
+ obj = ModuleSpecs::NamedClass.new
+ obj.singleton_class.to_s.should =~ /\A#<Class:#<ModuleSpecs::NamedClass:0x\h+>>\z/
+ end
+
+ it "always show the refinement name, even if the module is named" do
+ module ModuleSpecs::RefinementInspect
+ R = refine String do
+ end
+ end
+
+ ModuleSpecs::RefinementInspect::R.name.should == 'ModuleSpecs::RefinementInspect::R'
+ ModuleSpecs::RefinementInspect::R.to_s.should == '#<refinement:String@ModuleSpecs::RefinementInspect>'
+ end
+
+ it 'does not call #inspect or #to_s for singleton classes' do
+ klass = Class.new
+ obj = klass.new
+ def obj.to_s
+ "to_s"
+ end
+ def obj.inspect
+ "inspect"
+ end
+ sclass = obj.singleton_class
+ sclass.to_s.should =~ /\A#<Class:#<#{Regexp.escape klass.to_s}:0x\h+>>\z/
+ end
+end
diff --git a/spec/ruby/core/module/undef_method_spec.rb b/spec/ruby/core/module/undef_method_spec.rb
new file mode 100644
index 0000000000..c2ad200536
--- /dev/null
+++ b/spec/ruby/core/module/undef_method_spec.rb
@@ -0,0 +1,181 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+module ModuleSpecs
+ class Parent
+ def method_to_undef() 1 end
+ def another_method_to_undef() 1 end
+ end
+
+ class Ancestor
+ def method_to_undef() 1 end
+ def another_method_to_undef() 1 end
+ end
+end
+
+describe "Module#undef_method" do
+ before :each do
+ @module = Module.new { def method_to_undef; end }
+ end
+
+ it "is a public method" do
+ Module.should have_public_instance_method(:undef_method, false)
+ end
+
+ it "requires multiple arguments" do
+ Module.instance_method(:undef_method).arity.should < 0
+ end
+
+ it "allows multiple methods to be removed at once" do
+ klass = Class.new do
+ def method_to_undef() 1 end
+ def another_method_to_undef() 1 end
+ end
+ x = klass.new
+ klass.send(:undef_method, :method_to_undef, :another_method_to_undef)
+
+ -> { x.method_to_undef }.should raise_error(NoMethodError)
+ -> { x.another_method_to_undef }.should raise_error(NoMethodError)
+ end
+
+ it "does not undef any instance methods when argument not given" do
+ before = @module.instance_methods(true) + @module.private_instance_methods(true)
+ @module.send :undef_method
+ after = @module.instance_methods(true) + @module.private_instance_methods(true)
+ before.sort.should == after.sort
+ end
+
+ it "returns self" do
+ @module.send(:undef_method, :method_to_undef).should equal(@module)
+ end
+
+ it "raises a NameError when passed a missing name for a module" do
+ -> { @module.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for module `#{@module}'/) { |e|
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+
+ it "raises a NameError when passed a missing name for a class" do
+ klass = Class.new
+ -> { klass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for class `#{klass}'/) { |e|
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+
+ it "raises a NameError when passed a missing name for a singleton class" do
+ klass = Class.new
+ obj = klass.new
+ sclass = obj.singleton_class
+
+ -> { sclass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for class `#{sclass}'/) { |e|
+ e.message.should include('`#<Class:#<#<Class:')
+
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+
+ it "raises a NameError when passed a missing name for a metaclass" do
+ klass = String.singleton_class
+ -> { klass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for class `String'/) { |e|
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+
+ describe "on frozen instance" do
+ before :each do
+ @frozen = @module.dup.freeze
+ end
+
+ it "raises a FrozenError when passed a name" do
+ -> { @frozen.send :undef_method, :method_to_undef }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError when passed a missing name" do
+ -> { @frozen.send :undef_method, :not_exist }.should raise_error(FrozenError)
+ end
+
+ it "raises a TypeError when passed a not name" do
+ -> { @frozen.send :undef_method, Object.new }.should raise_error(TypeError)
+ end
+
+ it "does not raise exceptions when no arguments given" do
+ @frozen.send(:undef_method).should equal(@frozen)
+ end
+ end
+end
+
+describe "Module#undef_method with symbol" do
+ it "removes a method defined in a class" do
+ klass = Class.new do
+ def method_to_undef() 1 end
+ def another_method_to_undef() 1 end
+ end
+ x = klass.new
+
+ x.method_to_undef.should == 1
+
+ klass.send :undef_method, :method_to_undef
+
+ -> { x.method_to_undef }.should raise_error(NoMethodError)
+ end
+
+ it "removes a method defined in a super class" do
+ child_class = Class.new(ModuleSpecs::Parent)
+ child = child_class.new
+ child.method_to_undef.should == 1
+
+ child_class.send :undef_method, :method_to_undef
+
+ -> { child.method_to_undef }.should raise_error(NoMethodError)
+ end
+
+ it "does not remove a method defined in a super class when removed from a subclass" do
+ descendant = Class.new(ModuleSpecs::Ancestor)
+ ancestor = ModuleSpecs::Ancestor.new
+ ancestor.method_to_undef.should == 1
+
+ descendant.send :undef_method, :method_to_undef
+
+ ancestor.method_to_undef.should == 1
+ end
+end
+
+describe "Module#undef_method with string" do
+ it "removes a method defined in a class" do
+ klass = Class.new do
+ def method_to_undef() 1 end
+ def another_method_to_undef() 1 end
+ end
+ x = klass.new
+
+ x.another_method_to_undef.should == 1
+
+ klass.send :undef_method, 'another_method_to_undef'
+
+ -> { x.another_method_to_undef }.should raise_error(NoMethodError)
+ end
+
+ it "removes a method defined in a super class" do
+ child_class = Class.new(ModuleSpecs::Parent)
+ child = child_class.new
+ child.another_method_to_undef.should == 1
+
+ child_class.send :undef_method, 'another_method_to_undef'
+
+ -> { child.another_method_to_undef }.should raise_error(NoMethodError)
+ end
+
+ it "does not remove a method defined in a super class when removed from a subclass" do
+ descendant = Class.new(ModuleSpecs::Ancestor)
+ ancestor = ModuleSpecs::Ancestor.new
+ ancestor.another_method_to_undef.should == 1
+
+ descendant.send :undef_method, 'another_method_to_undef'
+
+ ancestor.another_method_to_undef.should == 1
+ end
+end
diff --git a/spec/ruby/core/module/using_spec.rb b/spec/ruby/core/module/using_spec.rb
new file mode 100644
index 0000000000..4781b99bb7
--- /dev/null
+++ b/spec/ruby/core/module/using_spec.rb
@@ -0,0 +1,377 @@
+require_relative '../../spec_helper'
+
+describe "Module#using" do
+ it "imports class refinements from module into the current class/module" do
+ refinement = Module.new do
+ refine Integer do
+ def foo; "foo"; end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinement
+ result = 1.foo
+ end
+
+ result.should == "foo"
+ end
+
+ it "accepts module as argument" do
+ refinement = Module.new do
+ refine Integer do
+ def foo; "foo"; end
+ end
+ end
+
+ -> {
+ Module.new do
+ using refinement
+ end
+ }.should_not raise_error
+ end
+
+ it "accepts module without refinements" do
+ mod = Module.new
+
+ -> {
+ Module.new do
+ using mod
+ end
+ }.should_not raise_error
+ end
+
+ it "does not accept class" do
+ klass = Class.new
+
+ -> {
+ Module.new do
+ using klass
+ end
+ }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError if passed something other than module" do
+ -> {
+ Module.new do
+ using "foo"
+ end
+ }.should raise_error(TypeError)
+ end
+
+ it "returns self" do
+ refinement = Module.new
+
+ result = nil
+ mod = Module.new do
+ result = using refinement
+ end
+
+ result.should equal(mod)
+ end
+
+ it "works in classes too" do
+ refinement = Module.new do
+ refine Integer do
+ def foo; "foo"; end
+ end
+ end
+
+ result = nil
+ Class.new do
+ using refinement
+ result = 1.foo
+ end
+
+ result.should == "foo"
+ end
+
+ it "raises error in method scope" do
+ mod = Module.new do
+ def self.foo
+ using Module.new {}
+ end
+ end
+
+ -> {
+ mod.foo
+ }.should raise_error(RuntimeError, /Module#using is not permitted in methods/)
+ end
+
+ it "activates refinement even for existed objects" do
+ result = nil
+
+ Module.new do
+ klass = Class.new do
+ def foo; "foo"; end
+ end
+
+ refinement = Module.new do
+ refine klass do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ obj = klass.new
+ using refinement
+ result = obj.foo
+ end
+
+ result.should == "foo from refinement"
+ end
+
+ it "activates updates when refinement reopens later" do
+ result = nil
+
+ Module.new do
+ klass = Class.new do
+ def foo; "foo"; end
+ end
+
+ refinement = Module.new do
+ refine klass do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ using refinement
+
+ refinement.class_eval do
+ refine klass do
+ def foo; "foo from reopened refinement"; end
+ end
+ end
+
+ obj = klass.new
+ result = obj.foo
+ end
+
+ result.should == "foo from reopened refinement"
+ end
+
+ describe "scope of refinement" do
+ it "is active until the end of current class/module" do
+ ScratchPad.record []
+
+ Module.new do
+ Class.new do
+ using Module.new {
+ refine String do
+ def to_s; "hello from refinement"; end
+ end
+ }
+ ScratchPad << "1".to_s
+ end
+
+ ScratchPad << "1".to_s
+ end
+
+ ScratchPad.recorded.should == ["hello from refinement", "1"]
+ end
+
+ # Refinements are lexical in scope.
+ # Refinements are only active within a scope after the call to using.
+ # Any code before the using statement will not have the refinement activated.
+ it "is not active before the `using` call" do
+ ScratchPad.record []
+
+ Module.new do
+ Class.new do
+ ScratchPad << "1".to_s
+ using Module.new {
+ refine String do
+ def to_s; "hello from refinement"; end
+ end
+ }
+ ScratchPad << "1".to_s
+ end
+ end
+
+ ScratchPad.recorded.should == ["1", "hello from refinement"]
+ end
+
+ # If you call a method that is defined outside the current scope
+ # the refinement will be deactivated
+ it "is not active for code defined outside the current scope" do
+ result = nil
+
+ Module.new do
+ klass = Class.new do
+ def foo; "foo"; end
+ end
+
+ refinement = Module.new do
+ refine klass do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ def self.call_foo(c)
+ c.foo
+ end
+
+ using refinement
+
+ result = call_foo(klass.new)
+ end
+
+ result.should == "foo"
+ end
+
+ # If a method is defined in a scope where a refinement is active
+ # the refinement will be active when the method is called.
+ it "is active for method defined in a scope wherever it's called" do
+ klass = Class.new do
+ def foo; "foo"; end
+ end
+
+ mod = Module.new do
+ refinement = Module.new do
+ refine klass do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ using refinement
+
+ def self.call_foo(c)
+ c.foo
+ end
+ end
+
+ c = klass.new
+ mod.call_foo(c).should == "foo from refinement"
+ end
+
+ it "is active for module defined via Module.new {}" do
+ refinement = Module.new do
+ refine Integer do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ result = nil
+
+ Module.new do
+ using refinement
+
+ Module.new do
+ result = 1.foo
+ end
+ end
+
+ result.should == "foo from refinement"
+ end
+
+ it "is active for class defined via Class.new {}" do
+ refinement = Module.new do
+ refine Integer do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ result = nil
+
+ Module.new do
+ using refinement
+
+ Class.new do
+ result = 1.foo
+ end
+ end
+
+ result.should == "foo from refinement"
+ end
+
+ it "is active for block called via instance_exec" do
+ refinement = Module.new do
+ refine Integer do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ c = Class.new do
+ using refinement
+
+ def abc
+ block = -> {
+ 1.foo
+ }
+
+ self.instance_exec(&block)
+ end
+ end
+
+ c.new.abc.should == "foo from refinement"
+ end
+
+ it "is active for block called via instance_eval" do
+ refinement = Module.new do
+ refine String do
+ def foo; "foo from refinement"; end
+ end
+ end
+
+ c = Class.new do
+ using refinement
+
+ def initialize
+ @a = "1703"
+
+ @a.instance_eval do
+ def abc
+ "#{self}: #{self.foo}"
+ end
+ end
+ end
+
+ def abc
+ @a.abc
+ end
+ end
+
+ c.new.abc.should == "1703: foo from refinement"
+ end
+
+ it "is not active if `using` call is not evaluated" do
+ result = nil
+
+ Module.new do
+ if false
+ using Module.new {
+ refine String do
+ def to_s; "hello from refinement"; end
+ end
+ }
+ end
+ result = "1".to_s
+ end
+
+ result.should == "1"
+ end
+
+ # The refinements in module are not activated automatically
+ # if the class is reopened later
+ it "is not active when class/module reopens" do
+ refinement = Module.new do
+ refine String do
+ def to_s
+ "hello from refinement"
+ end
+ end
+ end
+
+ result = []
+ klass = Class.new do
+ using refinement
+ result << "1".to_s
+ end
+
+ klass.class_eval do
+ result << "1".to_s
+ end
+
+ result.should == ["hello from refinement", "1"]
+ end
+ end
+end
diff --git a/spec/ruby/core/mutex/lock_spec.rb b/spec/ruby/core/mutex/lock_spec.rb
new file mode 100644
index 0000000000..7a39817b11
--- /dev/null
+++ b/spec/ruby/core/mutex/lock_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "Mutex#lock" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "returns self" do
+ m = Mutex.new
+ m.lock.should == m
+ m.unlock
+ end
+
+ it "blocks the caller if already locked" do
+ m = Mutex.new
+ m.lock
+ -> { m.lock }.should block_caller
+ end
+
+ it "does not block the caller if not locked" do
+ m = Mutex.new
+ -> { m.lock }.should_not block_caller
+ end
+
+ # Unable to find a specific ticket but behavior change may be
+ # related to this ML thread.
+ it "raises a ThreadError when used recursively" do
+ m = Mutex.new
+ m.lock
+ -> {
+ m.lock
+ }.should raise_error(ThreadError)
+ end
+end
diff --git a/spec/ruby/core/mutex/locked_spec.rb b/spec/ruby/core/mutex/locked_spec.rb
new file mode 100644
index 0000000000..1bf3ed6394
--- /dev/null
+++ b/spec/ruby/core/mutex/locked_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe "Mutex#locked?" do
+ it "returns true if locked" do
+ m = Mutex.new
+ m.lock
+ m.locked?.should be_true
+ end
+
+ it "returns false if unlocked" do
+ m = Mutex.new
+ m.locked?.should be_false
+ end
+
+ it "returns the status of the lock" do
+ m1 = Mutex.new
+ m2 = Mutex.new
+
+ m2.lock # hold th with only m1 locked
+ m1_locked = false
+
+ th = Thread.new do
+ m1.lock
+ m1_locked = true
+ m2.lock
+ end
+
+ Thread.pass until m1_locked
+
+ m1.locked?.should be_true
+ m2.unlock # release th
+ th.join
+ # A Thread releases its locks upon termination
+ m1.locked?.should be_false
+ end
+end
diff --git a/spec/ruby/core/mutex/owned_spec.rb b/spec/ruby/core/mutex/owned_spec.rb
new file mode 100644
index 0000000000..1f843cd576
--- /dev/null
+++ b/spec/ruby/core/mutex/owned_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+
+describe "Mutex#owned?" do
+ describe "when unlocked" do
+ it "returns false" do
+ m = Mutex.new
+ m.owned?.should be_false
+ end
+ end
+
+ describe "when locked by the current thread" do
+ it "returns true" do
+ m = Mutex.new
+ m.lock
+ m.owned?.should be_true
+ end
+ end
+
+ describe "when locked by another thread" do
+ before :each do
+ @checked = false
+ end
+
+ after :each do
+ @checked = true
+ @th.join
+ end
+
+ it "returns false" do
+ m = Mutex.new
+ locked = false
+
+ @th = Thread.new do
+ m.lock
+ locked = true
+ Thread.pass until @checked
+ end
+
+ Thread.pass until locked
+ m.owned?.should be_false
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "is held per Fiber" do
+ m = Mutex.new
+ m.lock
+
+ Fiber.new do
+ m.locked?.should == true
+ m.owned?.should == false
+ end.resume
+ end
+ end
+end
diff --git a/spec/ruby/core/mutex/sleep_spec.rb b/spec/ruby/core/mutex/sleep_spec.rb
new file mode 100644
index 0000000000..9832e3125e
--- /dev/null
+++ b/spec/ruby/core/mutex/sleep_spec.rb
@@ -0,0 +1,103 @@
+require_relative '../../spec_helper'
+
+describe "Mutex#sleep" do
+ describe "when not locked by the current thread" do
+ it "raises a ThreadError" do
+ m = Mutex.new
+ -> { m.sleep }.should raise_error(ThreadError)
+ end
+
+ it "raises an ArgumentError if passed a negative duration" do
+ m = Mutex.new
+ -> { m.sleep(-0.1) }.should raise_error(ArgumentError)
+ -> { m.sleep(-1) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises an ArgumentError if passed a negative duration" do
+ m = Mutex.new
+ m.lock
+ -> { m.sleep(-0.1) }.should raise_error(ArgumentError)
+ -> { m.sleep(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "pauses execution for approximately the duration requested" do
+ m = Mutex.new
+ m.lock
+ duration = 0.001
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ m.sleep duration
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ (now - start).should >= 0
+ (now - start).should < (duration + TIME_TOLERANCE)
+ end
+
+ it "unlocks the mutex while sleeping" do
+ m = Mutex.new
+ locked = false
+ th = Thread.new { m.lock; locked = true; m.sleep }
+ Thread.pass until locked
+ Thread.pass until th.stop?
+ m.locked?.should be_false
+ th.run
+ th.join
+ end
+
+ it "relocks the mutex when woken" do
+ m = Mutex.new
+ m.lock
+ m.sleep(0.001)
+ m.locked?.should be_true
+ end
+
+ it "relocks the mutex when woken by an exception being raised" do
+ m = Mutex.new
+ locked = false
+ th = Thread.new do
+ m.lock
+ locked = true
+ begin
+ m.sleep
+ rescue Exception
+ m.locked?
+ end
+ end
+ Thread.pass until locked
+ Thread.pass until th.stop?
+ th.raise(Exception)
+ th.value.should be_true
+ end
+
+ it "returns the rounded number of seconds asleep" do
+ m = Mutex.new
+ locked = false
+ th = Thread.start do
+ m.lock
+ locked = true
+ m.sleep
+ end
+ Thread.pass until locked
+ Thread.pass until th.stop?
+ th.wakeup
+ th.value.should be_kind_of(Integer)
+ end
+
+ it "wakes up when requesting sleep times near or equal to zero" do
+ times = []
+ val = 1
+
+ # power of two divisor so we eventually get near zero
+ loop do
+ val = val / 16.0
+ times << val
+ break if val == 0.0
+ end
+
+ m = Mutex.new
+ m.lock
+ times.each do |time|
+ # just testing that sleep completes
+ -> {m.sleep(time)}.should_not raise_error
+ end
+ end
+end
diff --git a/spec/ruby/core/mutex/synchronize_spec.rb b/spec/ruby/core/mutex/synchronize_spec.rb
new file mode 100644
index 0000000000..7942885197
--- /dev/null
+++ b/spec/ruby/core/mutex/synchronize_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../spec_helper'
+
+describe "Mutex#synchronize" do
+ it "wraps the lock/unlock pair in an ensure" do
+ m1 = Mutex.new
+ m2 = Mutex.new
+ m2.lock
+ synchronized = false
+
+ th = Thread.new do
+ -> do
+ m1.synchronize do
+ synchronized = true
+ m2.lock
+ raise Exception
+ end
+ end.should raise_error(Exception)
+ end
+
+ Thread.pass until synchronized
+
+ m1.locked?.should be_true
+ m2.unlock
+ th.join
+ m1.locked?.should be_false
+ end
+
+ it "blocks the caller if already locked" do
+ m = Mutex.new
+ m.lock
+ -> { m.synchronize { } }.should block_caller
+ end
+
+ it "does not block the caller if not locked" do
+ m = Mutex.new
+ -> { m.synchronize { } }.should_not block_caller
+ end
+
+ it "blocks the caller if another thread is also in the synchronize block" do
+ m = Mutex.new
+ q1 = Queue.new
+ q2 = Queue.new
+
+ t = Thread.new {
+ m.synchronize {
+ q1.push :ready
+ q2.pop
+ }
+ }
+
+ q1.pop.should == :ready
+
+ -> { m.synchronize { } }.should block_caller
+
+ q2.push :done
+ t.join
+ end
+
+ it "is not recursive" do
+ m = Mutex.new
+
+ m.synchronize do
+ -> { m.synchronize { } }.should raise_error(ThreadError)
+ end
+ end
+end
diff --git a/spec/ruby/core/mutex/try_lock_spec.rb b/spec/ruby/core/mutex/try_lock_spec.rb
new file mode 100644
index 0000000000..8d521f4c6b
--- /dev/null
+++ b/spec/ruby/core/mutex/try_lock_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "Mutex#try_lock" do
+ describe "when unlocked" do
+ it "returns true" do
+ m = Mutex.new
+ m.try_lock.should be_true
+ end
+
+ it "locks the mutex" do
+ m = Mutex.new
+ m.try_lock
+ m.locked?.should be_true
+ end
+ end
+
+ describe "when locked by the current thread" do
+ it "returns false" do
+ m = Mutex.new
+ m.lock
+ m.try_lock.should be_false
+ end
+ end
+
+ describe "when locked by another thread" do
+ it "returns false" do
+ m = Mutex.new
+ m.lock
+ Thread.new { m.try_lock }.value.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/core/mutex/unlock_spec.rb b/spec/ruby/core/mutex/unlock_spec.rb
new file mode 100644
index 0000000000..d999e66842
--- /dev/null
+++ b/spec/ruby/core/mutex/unlock_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+
+describe "Mutex#unlock" do
+ it "raises ThreadError unless Mutex is locked" do
+ mutex = Mutex.new
+ -> { mutex.unlock }.should raise_error(ThreadError)
+ end
+
+ it "raises ThreadError unless thread owns Mutex" do
+ mutex = Mutex.new
+ wait = Mutex.new
+ wait.lock
+ th = Thread.new do
+ mutex.lock
+ wait.lock
+ end
+
+ # avoid race on mutex.lock
+ Thread.pass until mutex.locked?
+ Thread.pass until th.stop?
+
+ -> { mutex.unlock }.should raise_error(ThreadError)
+
+ wait.unlock
+ th.join
+ end
+
+ it "raises ThreadError if previously locking thread is gone" do
+ mutex = Mutex.new
+ th = Thread.new do
+ mutex.lock
+ end
+
+ th.join
+
+ -> { mutex.unlock }.should raise_error(ThreadError)
+ end
+end
diff --git a/spec/ruby/core/nil/and_spec.rb b/spec/ruby/core/nil/and_spec.rb
new file mode 100644
index 0000000000..cd25aff1de
--- /dev/null
+++ b/spec/ruby/core/nil/and_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#&" do
+ it "returns false" do
+ (nil & nil).should == false
+ (nil & true).should == false
+ (nil & false).should == false
+ (nil & "").should == false
+ (nil & mock('x')).should == false
+ end
+end
diff --git a/spec/ruby/core/nil/case_compare_spec.rb b/spec/ruby/core/nil/case_compare_spec.rb
new file mode 100644
index 0000000000..142560c6f5
--- /dev/null
+++ b/spec/ruby/core/nil/case_compare_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#===" do
+ it "returns true for nil" do
+ (nil === nil).should == true
+ end
+
+ it "returns false for non-nil object" do
+ (nil === 1).should == false
+ (nil === "").should == false
+ (nil === Object).should == false
+ end
+end
diff --git a/spec/ruby/core/nil/dup_spec.rb b/spec/ruby/core/nil/dup_spec.rb
new file mode 100644
index 0000000000..0324b3f1f4
--- /dev/null
+++ b/spec/ruby/core/nil/dup_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#dup" do
+ it "returns self" do
+ nil.dup.should equal(nil)
+ end
+end
diff --git a/spec/ruby/core/nil/inspect_spec.rb b/spec/ruby/core/nil/inspect_spec.rb
new file mode 100644
index 0000000000..1babc3d062
--- /dev/null
+++ b/spec/ruby/core/nil/inspect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#inspect" do
+ it "returns the string 'nil'" do
+ nil.inspect.should == "nil"
+ end
+end
diff --git a/spec/ruby/core/nil/match_spec.rb b/spec/ruby/core/nil/match_spec.rb
new file mode 100644
index 0000000000..bc1c591793
--- /dev/null
+++ b/spec/ruby/core/nil/match_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#=~" do
+ it "returns nil matching any object" do
+ o = nil
+
+ suppress_warning do
+ (o =~ /Object/).should be_nil
+ (o =~ 'Object').should be_nil
+ (o =~ Object).should be_nil
+ (o =~ Object.new).should be_nil
+ (o =~ nil).should be_nil
+ (o =~ false).should be_nil
+ (o =~ true).should be_nil
+ end
+ end
+
+ it "should not warn" do
+ -> { nil =~ /a/ }.should_not complain(verbose: true)
+ end
+end
diff --git a/spec/ruby/core/nil/nil_spec.rb b/spec/ruby/core/nil/nil_spec.rb
new file mode 100644
index 0000000000..2cf97621c6
--- /dev/null
+++ b/spec/ruby/core/nil/nil_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#nil?" do
+ it "returns true" do
+ nil.should.nil?
+ end
+end
diff --git a/spec/ruby/core/nil/nilclass_spec.rb b/spec/ruby/core/nil/nilclass_spec.rb
new file mode 100644
index 0000000000..7f6d8af25d
--- /dev/null
+++ b/spec/ruby/core/nil/nilclass_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "NilClass" do
+ it ".allocate raises a TypeError" do
+ -> do
+ NilClass.allocate
+ end.should raise_error(TypeError)
+ end
+
+ it ".new is undefined" do
+ -> do
+ NilClass.new
+ end.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/nil/or_spec.rb b/spec/ruby/core/nil/or_spec.rb
new file mode 100644
index 0000000000..473a833d71
--- /dev/null
+++ b/spec/ruby/core/nil/or_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#|" do
+ it "returns false if other is nil or false, otherwise true" do
+ (nil | nil).should == false
+ (nil | true).should == true
+ (nil | false).should == false
+ (nil | "").should == true
+ (nil | mock('x')).should == true
+ end
+end
diff --git a/spec/ruby/core/nil/rationalize_spec.rb b/spec/ruby/core/nil/rationalize_spec.rb
new file mode 100644
index 0000000000..84d6e6f056
--- /dev/null
+++ b/spec/ruby/core/nil/rationalize_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#rationalize" do
+ it "returns 0/1" do
+ nil.rationalize.should == Rational(0, 1)
+ end
+
+ it "ignores a single argument" do
+ nil.rationalize(0.1).should == Rational(0, 1)
+ end
+
+ it "raises ArgumentError when passed more than one argument" do
+ -> { nil.rationalize(0.1, 0.1) }.should raise_error(ArgumentError)
+ -> { nil.rationalize(0.1, 0.1, 2) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/nil/to_a_spec.rb b/spec/ruby/core/nil/to_a_spec.rb
new file mode 100644
index 0000000000..b8b339e330
--- /dev/null
+++ b/spec/ruby/core/nil/to_a_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#to_a" do
+ it "returns an empty array" do
+ nil.to_a.should == []
+ end
+end
diff --git a/spec/ruby/core/nil/to_c_spec.rb b/spec/ruby/core/nil/to_c_spec.rb
new file mode 100644
index 0000000000..e0052be5bd
--- /dev/null
+++ b/spec/ruby/core/nil/to_c_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#to_c" do
+ it "returns Complex(0, 0)" do
+ nil.to_c.should eql(Complex(0, 0))
+ end
+end
diff --git a/spec/ruby/core/nil/to_f_spec.rb b/spec/ruby/core/nil/to_f_spec.rb
new file mode 100644
index 0000000000..a5f24ba3bf
--- /dev/null
+++ b/spec/ruby/core/nil/to_f_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#to_f" do
+ it "returns 0.0" do
+ nil.to_f.should == 0.0
+ end
+
+ it "does not cause NilClass to be coerced to Float" do
+ (0.0 == nil).should == false
+ end
+end
diff --git a/spec/ruby/core/nil/to_h_spec.rb b/spec/ruby/core/nil/to_h_spec.rb
new file mode 100644
index 0000000000..5c8d5dc25a
--- /dev/null
+++ b/spec/ruby/core/nil/to_h_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#to_h" do
+ it "returns an empty hash" do
+ nil.to_h.should == {}
+ nil.to_h.default.should == nil
+ end
+end
diff --git a/spec/ruby/core/nil/to_i_spec.rb b/spec/ruby/core/nil/to_i_spec.rb
new file mode 100644
index 0000000000..d3d088e999
--- /dev/null
+++ b/spec/ruby/core/nil/to_i_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#to_i" do
+ it "returns 0" do
+ nil.to_i.should == 0
+ end
+
+ it "does not cause NilClass to be coerced to Integer" do
+ (0 == nil).should == false
+ end
+end
diff --git a/spec/ruby/core/nil/to_r_spec.rb b/spec/ruby/core/nil/to_r_spec.rb
new file mode 100644
index 0000000000..8be43baf00
--- /dev/null
+++ b/spec/ruby/core/nil/to_r_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#to_r" do
+ it "returns 0/1" do
+ nil.to_r.should == Rational(0, 1)
+ end
+end
diff --git a/spec/ruby/core/nil/to_s_spec.rb b/spec/ruby/core/nil/to_s_spec.rb
new file mode 100644
index 0000000000..fa0b929677
--- /dev/null
+++ b/spec/ruby/core/nil/to_s_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#to_s" do
+ it "returns the string ''" do
+ nil.to_s.should == ""
+ end
+
+ it "returns a frozen string" do
+ nil.to_s.should.frozen?
+ end
+
+ it "always returns the same string" do
+ nil.to_s.should equal(nil.to_s)
+ end
+end
diff --git a/spec/ruby/core/nil/xor_spec.rb b/spec/ruby/core/nil/xor_spec.rb
new file mode 100644
index 0000000000..b45da9d443
--- /dev/null
+++ b/spec/ruby/core/nil/xor_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "NilClass#^" do
+ it "returns false if other is nil or false, otherwise true" do
+ (nil ^ nil).should == false
+ (nil ^ true).should == true
+ (nil ^ false).should == false
+ (nil ^ "").should == true
+ (nil ^ mock('x')).should == true
+ end
+end
diff --git a/spec/ruby/core/numeric/abs2_spec.rb b/spec/ruby/core/numeric/abs2_spec.rb
new file mode 100644
index 0000000000..0e60cd0ae7
--- /dev/null
+++ b/spec/ruby/core/numeric/abs2_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#abs2" do
+ before :each do
+ @numbers = [
+ 0,
+ 0.0,
+ 1,
+ 20,
+ bignum_value,
+ 278202.292871,
+ 72829,
+ 3.333333333333,
+ 0.1,
+ infinity_value
+ ].map { |n| [-n, n] }.flatten
+ end
+
+ it "returns the square of the absolute value of self" do
+ @numbers.each do |number|
+ number.abs2.should eql(number.abs ** 2)
+ end
+ end
+
+ it "calls #* on self" do
+ number = mock_numeric('numeric')
+ number.should_receive(:*).and_return(:result)
+ number.abs2.should == :result
+ end
+
+ it "returns NaN when self is NaN" do
+ nan_value.abs2.nan?.should be_true
+ end
+end
diff --git a/spec/ruby/core/numeric/abs_spec.rb b/spec/ruby/core/numeric/abs_spec.rb
new file mode 100644
index 0000000000..8bec50e337
--- /dev/null
+++ b/spec/ruby/core/numeric/abs_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/abs'
+
+describe "Numeric#abs" do
+ it_behaves_like :numeric_abs, :abs
+end
diff --git a/spec/ruby/core/numeric/angle_spec.rb b/spec/ruby/core/numeric/angle_spec.rb
new file mode 100644
index 0000000000..bb38165777
--- /dev/null
+++ b/spec/ruby/core/numeric/angle_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Numeric#angle" do
+ it_behaves_like :numeric_arg, :angle
+end
diff --git a/spec/ruby/core/numeric/arg_spec.rb b/spec/ruby/core/numeric/arg_spec.rb
new file mode 100644
index 0000000000..ba3b57c687
--- /dev/null
+++ b/spec/ruby/core/numeric/arg_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Numeric#arg" do
+ it_behaves_like :numeric_arg, :arg
+end
diff --git a/spec/ruby/core/numeric/ceil_spec.rb b/spec/ruby/core/numeric/ceil_spec.rb
new file mode 100644
index 0000000000..00c856e79b
--- /dev/null
+++ b/spec/ruby/core/numeric/ceil_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#ceil" do
+ it "converts self to a Float (using #to_f) and returns the #ceil'ed result" do
+ o = mock_numeric("ceil")
+ o.should_receive(:to_f).and_return(1 + TOLERANCE)
+ o.ceil.should == 2
+
+ o2 = mock_numeric("ceil")
+ v = -1 - TOLERANCE
+ o2.should_receive(:to_f).and_return(v)
+ o2.ceil.should == -1
+ end
+end
diff --git a/spec/ruby/core/numeric/clone_spec.rb b/spec/ruby/core/numeric/clone_spec.rb
new file mode 100644
index 0000000000..c3b06ca0c9
--- /dev/null
+++ b/spec/ruby/core/numeric/clone_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#clone" do
+ it "returns self" do
+ value = 1
+ value.clone.should equal(value)
+
+ subclass = Class.new(Numeric)
+ value = subclass.new
+ value.clone.should equal(value)
+ end
+
+ it "does not change frozen status" do
+ 1.clone.frozen?.should == true
+ end
+
+ it "accepts optonal keyword argument :freeze" do
+ value = 1
+ value.clone(freeze: true).should equal(value)
+ end
+
+ it "raises ArgumentError if passed freeze: false" do
+ -> { 1.clone(freeze: false) }.should raise_error(ArgumentError, /can't unfreeze/)
+ end
+
+ ruby_version_is "3.0" do
+ it "does not change frozen status if passed freeze: nil" do
+ value = 1
+ value.clone(freeze: nil).should equal(value)
+ end
+ end
+end
diff --git a/spec/ruby/core/numeric/coerce_spec.rb b/spec/ruby/core/numeric/coerce_spec.rb
new file mode 100644
index 0000000000..4c4416d30b
--- /dev/null
+++ b/spec/ruby/core/numeric/coerce_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#coerce" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ @obj.should_receive(:to_f).any_number_of_times.and_return(10.5)
+ end
+
+ it "returns [other, self] if self and other are instances of the same class" do
+ a = NumericSpecs::Subclass.new
+ b = NumericSpecs::Subclass.new
+
+ a.coerce(b).should == [b, a]
+ end
+
+ # I (emp) think that this behavior is actually a bug in MRI. It's here as documentation
+ # of the behavior until we find out if it's a bug.
+ quarantine! do
+ it "considers the presence of a metaclass when checking the class of the objects" do
+ a = NumericSpecs::Subclass.new
+ b = NumericSpecs::Subclass.new
+
+ # inject a metaclass on a
+ class << a; true; end
+
+ # watch it explode
+ -> { a.coerce(b) }.should raise_error(TypeError)
+ end
+ end
+
+ it "returns [other.to_f, self.to_f] if self and other are instances of different classes" do
+ @obj.coerce(2.5).should == [2.5, 10.5]
+ @obj.coerce(3).should == [3.0, 10.5]
+ @obj.coerce("4.4").should == [4.4, 10.5]
+ @obj.coerce(bignum_value).should == [bignum_value.to_f, 10.5]
+ end
+
+ it "raise TypeError if they are instances of different classes and other does not respond to #to_f" do
+ other = mock("numeric")
+ -> { @obj.coerce(other) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { @obj.coerce(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a boolean" do
+ -> { @obj.coerce(false) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a Symbol" do
+ -> { @obj.coerce(:symbol) }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError when passed a non-numeric String" do
+ -> { @obj.coerce("test") }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/numeric/comparison_spec.rb b/spec/ruby/core/numeric/comparison_spec.rb
new file mode 100644
index 0000000000..4b4d52501a
--- /dev/null
+++ b/spec/ruby/core/numeric/comparison_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#<=>" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "returns 0 if self equals other" do
+ (@obj <=> @obj).should == 0
+ end
+
+ it "returns nil if self does not equal other" do
+ (@obj <=> NumericSpecs::Subclass.new).should == nil
+ (@obj <=> 10).should == nil
+ (@obj <=> -3.5).should == nil
+ (@obj <=> bignum_value).should == nil
+ end
+
+ describe "with subclasses of Numeric" do
+ before :each do
+ @a = NumericSpecs::Comparison.new
+ @b = NumericSpecs::Comparison.new
+
+ ScratchPad.clear
+ end
+
+ it "is called when instances are compared with #<" do
+ (@a < @b).should be_false
+ ScratchPad.recorded.should == :numeric_comparison
+ end
+
+ it "is called when instances are compared with #<=" do
+ (@a <= @b).should be_false
+ ScratchPad.recorded.should == :numeric_comparison
+ end
+
+ it "is called when instances are compared with #>" do
+ (@a > @b).should be_true
+ ScratchPad.recorded.should == :numeric_comparison
+ end
+
+ it "is called when instances are compared with #>=" do
+ (@a >= @b).should be_true
+ ScratchPad.recorded.should == :numeric_comparison
+ end
+ end
+end
diff --git a/spec/ruby/core/numeric/conj_spec.rb b/spec/ruby/core/numeric/conj_spec.rb
new file mode 100644
index 0000000000..7d4777ca60
--- /dev/null
+++ b/spec/ruby/core/numeric/conj_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/conj'
+
+describe "Numeric#conj" do
+ it_behaves_like :numeric_conj, :conj
+end
diff --git a/spec/ruby/core/numeric/conjugate_spec.rb b/spec/ruby/core/numeric/conjugate_spec.rb
new file mode 100644
index 0000000000..99854766e7
--- /dev/null
+++ b/spec/ruby/core/numeric/conjugate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/conj'
+
+describe "Numeric#conjugate" do
+ it_behaves_like :numeric_conj, :conjugate
+end
diff --git a/spec/ruby/core/numeric/denominator_spec.rb b/spec/ruby/core/numeric/denominator_spec.rb
new file mode 100644
index 0000000000..34729446a2
--- /dev/null
+++ b/spec/ruby/core/numeric/denominator_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#denominator" do
+ # The Numeric child classes override this method, so their behaviour is
+ # specified in the appropriate place
+ before :each do
+ @numbers = [
+ 20, # Integer
+ 99999999**99, # Bignum
+ ]
+ end
+
+ it "returns 1" do
+ @numbers.each {|number| number.denominator.should == 1}
+ end
+
+ it "works with Numeric subclasses" do
+ rational = mock_numeric('rational')
+ rational.should_receive(:denominator).and_return(:denominator)
+ numeric = mock_numeric('numeric')
+ numeric.should_receive(:to_r).and_return(rational)
+ numeric.denominator.should == :denominator
+ end
+end
diff --git a/spec/ruby/core/numeric/div_spec.rb b/spec/ruby/core/numeric/div_spec.rb
new file mode 100644
index 0000000000..53917b84c9
--- /dev/null
+++ b/spec/ruby/core/numeric/div_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#div" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "calls self#/ with other, then returns the #floor'ed result" do
+ result = mock("Numeric#div result")
+ result.should_receive(:floor).and_return(12)
+ @obj.should_receive(:/).with(10).and_return(result)
+
+ @obj.div(10).should == 12
+ end
+
+ it "raises ZeroDivisionError for 0" do
+ -> { @obj.div(0) }.should raise_error(ZeroDivisionError)
+ -> { @obj.div(0.0) }.should raise_error(ZeroDivisionError)
+ -> { @obj.div(Complex(0,0)) }.should raise_error(ZeroDivisionError)
+ end
+end
diff --git a/spec/ruby/core/numeric/divmod_spec.rb b/spec/ruby/core/numeric/divmod_spec.rb
new file mode 100644
index 0000000000..8d5259bbcd
--- /dev/null
+++ b/spec/ruby/core/numeric/divmod_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#divmod" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "returns [quotient, modulus], with quotient being obtained as in Numeric#div then #floor and modulus being obtained by calling self#- with quotient * other" do
+ @obj.should_receive(:/).twice.with(10).and_return(13 - TOLERANCE, 13 - TOLERANCE)
+ @obj.should_receive(:-).with(120).and_return(3)
+
+ @obj.divmod(10).should == [12, 3]
+ end
+end
diff --git a/spec/ruby/core/numeric/dup_spec.rb b/spec/ruby/core/numeric/dup_spec.rb
new file mode 100644
index 0000000000..189a7ef44d
--- /dev/null
+++ b/spec/ruby/core/numeric/dup_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#dup" do
+ it "returns self" do
+ value = 1
+ value.dup.should equal(value)
+
+ subclass = Class.new(Numeric)
+ value = subclass.new
+ value.dup.should equal(value)
+ end
+
+ it "does not change frozen status" do
+ 1.dup.frozen?.should == true
+ end
+end
diff --git a/spec/ruby/core/numeric/eql_spec.rb b/spec/ruby/core/numeric/eql_spec.rb
new file mode 100644
index 0000000000..b33e00e51f
--- /dev/null
+++ b/spec/ruby/core/numeric/eql_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#eql?" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "returns false if self's and other's types don't match" do
+ @obj.should_not eql(1)
+ @obj.should_not eql(-1.5)
+ @obj.should_not eql(bignum_value)
+ @obj.should_not eql(:sym)
+ end
+
+ it "returns the result of calling self#== with other when self's and other's types match" do
+ other = NumericSpecs::Subclass.new
+ @obj.should_receive(:==).with(other).and_return("result", nil)
+ @obj.should eql(other)
+ @obj.should_not eql(other)
+ end
+end
diff --git a/spec/ruby/core/numeric/fdiv_spec.rb b/spec/ruby/core/numeric/fdiv_spec.rb
new file mode 100644
index 0000000000..907e5d343c
--- /dev/null
+++ b/spec/ruby/core/numeric/fdiv_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'shared/quo'
+
+describe "Numeric#fdiv" do
+ it "coerces self with #to_f" do
+ numeric = mock_numeric('numeric')
+ numeric.should_receive(:to_f).and_return(3.0)
+ numeric.fdiv(0.5).should == 6.0
+ end
+
+ it "coerces other with #to_f" do
+ numeric = mock_numeric('numeric')
+ numeric.should_receive(:to_f).and_return(3.0)
+ 6.fdiv(numeric).should == 2.0
+ end
+
+ it "performs floating-point division" do
+ 3.fdiv(2).should == 1.5
+ end
+
+ it "returns a Float" do
+ bignum_value.fdiv(Float::MAX).should be_an_instance_of(Float)
+ end
+
+ it "returns Infinity if other is 0" do
+ 8121.92821.fdiv(0).infinite?.should == 1
+ end
+
+ it "returns NaN if other is NaN" do
+ 3334.fdiv(nan_value).nan?.should be_true
+ end
+end
diff --git a/spec/ruby/core/numeric/finite_spec.rb b/spec/ruby/core/numeric/finite_spec.rb
new file mode 100644
index 0000000000..05b5eebbd6
--- /dev/null
+++ b/spec/ruby/core/numeric/finite_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#finite?" do
+ it "returns true by default" do
+ o = mock_numeric("finite")
+ o.finite?.should be_true
+ end
+end
diff --git a/spec/ruby/core/numeric/fixtures/classes.rb b/spec/ruby/core/numeric/fixtures/classes.rb
new file mode 100644
index 0000000000..1505584889
--- /dev/null
+++ b/spec/ruby/core/numeric/fixtures/classes.rb
@@ -0,0 +1,17 @@
+module NumericSpecs
+ class Comparison < Numeric
+ # This method is used because we cannot define
+ # singleton methods on subclasses of Numeric,
+ # which is needed for a.should_receive to work.
+ def <=>(other)
+ ScratchPad.record :numeric_comparison
+ 1
+ end
+ end
+
+ class Subclass < Numeric
+ # Allow methods to be mocked
+ def singleton_method_added(val)
+ end
+ end
+end
diff --git a/spec/ruby/core/numeric/floor_spec.rb b/spec/ruby/core/numeric/floor_spec.rb
new file mode 100644
index 0000000000..80a4868e4d
--- /dev/null
+++ b/spec/ruby/core/numeric/floor_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#floor" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "converts self to a Float (using #to_f) and returns the #floor'ed result" do
+ @obj.should_receive(:to_f).and_return(2 - TOLERANCE, TOLERANCE - 2)
+ @obj.floor.should == 1
+ @obj.floor.should == -2
+ end
+end
diff --git a/spec/ruby/core/numeric/i_spec.rb b/spec/ruby/core/numeric/i_spec.rb
new file mode 100644
index 0000000000..621ecc09ec
--- /dev/null
+++ b/spec/ruby/core/numeric/i_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#i" do
+ it "returns a Complex object" do
+ 34.i.should be_an_instance_of(Complex)
+ end
+
+ it "sets the real part to 0" do
+ 7342.i.real.should == 0
+ end
+
+ it "sets the imaginary part to self" do
+ 62.81.i.imag.should == 62.81
+ end
+end
diff --git a/spec/ruby/core/numeric/imag_spec.rb b/spec/ruby/core/numeric/imag_spec.rb
new file mode 100644
index 0000000000..b9e343cee9
--- /dev/null
+++ b/spec/ruby/core/numeric/imag_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/imag'
+
+describe "Numeric#imag" do
+ it_behaves_like :numeric_imag, :imag
+end
diff --git a/spec/ruby/core/numeric/imaginary_spec.rb b/spec/ruby/core/numeric/imaginary_spec.rb
new file mode 100644
index 0000000000..ec708cb505
--- /dev/null
+++ b/spec/ruby/core/numeric/imaginary_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/imag'
+
+describe "Numeric#imaginary" do
+ it_behaves_like :numeric_imag, :imaginary
+end
diff --git a/spec/ruby/core/numeric/infinite_spec.rb b/spec/ruby/core/numeric/infinite_spec.rb
new file mode 100644
index 0000000000..3ea7825c8c
--- /dev/null
+++ b/spec/ruby/core/numeric/infinite_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#infinite?" do
+ it "returns nil by default" do
+ o = mock_numeric("infinite")
+ o.infinite?.should == nil
+ end
+end
diff --git a/spec/ruby/core/numeric/integer_spec.rb b/spec/ruby/core/numeric/integer_spec.rb
new file mode 100644
index 0000000000..adbac4d7aa
--- /dev/null
+++ b/spec/ruby/core/numeric/integer_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#integer?" do
+ it "returns false" do
+ NumericSpecs::Subclass.new.should_not.integer?
+ end
+end
diff --git a/spec/ruby/core/numeric/magnitude_spec.rb b/spec/ruby/core/numeric/magnitude_spec.rb
new file mode 100644
index 0000000000..7a3290b036
--- /dev/null
+++ b/spec/ruby/core/numeric/magnitude_spec.rb
@@ -0,0 +1,5 @@
+require_relative 'shared/abs'
+
+describe "Numeric#magnitude" do
+ it_behaves_like :numeric_abs, :magnitude
+end
diff --git a/spec/ruby/core/numeric/modulo_spec.rb b/spec/ruby/core/numeric/modulo_spec.rb
new file mode 100644
index 0000000000..e3dc7e56f3
--- /dev/null
+++ b/spec/ruby/core/numeric/modulo_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :numeric_modulo_19, shared: true do
+ it "returns self - other * self.div(other)" do
+ s = mock_numeric('self')
+ o = mock_numeric('other')
+ n3 = mock_numeric('n3')
+ n4 = mock_numeric('n4')
+ n5 = mock_numeric('n5')
+ s.should_receive(:div).with(o).and_return(n3)
+ o.should_receive(:*).with(n3).and_return(n4)
+ s.should_receive(:-).with(n4).and_return(n5)
+ s.send(@method, o).should == n5
+ end
+end
+
+describe "Numeric#modulo" do
+ it_behaves_like :numeric_modulo_19, :modulo
+end
+
+describe "Numeric#%" do
+ it_behaves_like :numeric_modulo_19, :%
+end
diff --git a/spec/ruby/core/numeric/negative_spec.rb b/spec/ruby/core/numeric/negative_spec.rb
new file mode 100644
index 0000000000..9c6f95fd87
--- /dev/null
+++ b/spec/ruby/core/numeric/negative_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#negative?" do
+ describe "on positive numbers" do
+ it "returns false" do
+ 1.negative?.should be_false
+ 0.1.negative?.should be_false
+ end
+ end
+
+ describe "on zero" do
+ it "returns false" do
+ 0.negative?.should be_false
+ 0.0.negative?.should be_false
+ end
+ end
+
+ describe "on negative numbers" do
+ it "returns true" do
+ -1.negative?.should be_true
+ -0.1.negative?.should be_true
+ end
+ end
+end
+
+describe "Numeric#negative?" do
+ before(:each) do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "returns true if self is less than 0" do
+ @obj.should_receive(:<).with(0).and_return(true)
+ @obj.should.negative?
+ end
+
+ it "returns false if self is greater than 0" do
+ @obj.should_receive(:<).with(0).and_return(false)
+ @obj.should_not.negative?
+ end
+end
diff --git a/spec/ruby/core/numeric/nonzero_spec.rb b/spec/ruby/core/numeric/nonzero_spec.rb
new file mode 100644
index 0000000000..464ed4f4f8
--- /dev/null
+++ b/spec/ruby/core/numeric/nonzero_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#nonzero?" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "returns self if self#zero? is false" do
+ @obj.should_receive(:zero?).and_return(false)
+ @obj.nonzero?.should == @obj
+ end
+
+ it "returns nil if self#zero? is true" do
+ @obj.should_receive(:zero?).and_return(true)
+ @obj.nonzero?.should == nil
+ end
+end
diff --git a/spec/ruby/core/numeric/numerator_spec.rb b/spec/ruby/core/numeric/numerator_spec.rb
new file mode 100644
index 0000000000..668df8b797
--- /dev/null
+++ b/spec/ruby/core/numeric/numerator_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#numerator" do
+ before :all do
+ @numbers = [
+ 0,
+ 29871,
+ 99999999999999**99,
+ -72628191273,
+ 29282.2827,
+ -2927.00091,
+ 0.0,
+ 12.0,
+ Float::MAX,
+ ]
+ end
+
+ # This isn't entirely true, as NaN.numerator works, whereas
+ # Rational(NaN) raises an exception, but we test this in Float#numerator
+ it "converts self to a Rational object then returns its numerator" do
+ @numbers.each do |number|
+ number.numerator.should == Rational(number).numerator
+ end
+ end
+
+ it "works with Numeric subclasses" do
+ rational = mock_numeric('rational')
+ rational.should_receive(:numerator).and_return(:numerator)
+ numeric = mock_numeric('numeric')
+ numeric.should_receive(:to_r).and_return(rational)
+ numeric.numerator.should == :numerator
+ end
+end
diff --git a/spec/ruby/core/numeric/numeric_spec.rb b/spec/ruby/core/numeric/numeric_spec.rb
new file mode 100644
index 0000000000..2bcf2a1175
--- /dev/null
+++ b/spec/ruby/core/numeric/numeric_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Numeric" do
+ it "includes Comparable" do
+ Numeric.include?(Comparable).should == true
+ end
+end
diff --git a/spec/ruby/core/numeric/phase_spec.rb b/spec/ruby/core/numeric/phase_spec.rb
new file mode 100644
index 0000000000..bc1995303f
--- /dev/null
+++ b/spec/ruby/core/numeric/phase_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Numeric#phase" do
+ it_behaves_like :numeric_arg, :phase
+end
diff --git a/spec/ruby/core/numeric/polar_spec.rb b/spec/ruby/core/numeric/polar_spec.rb
new file mode 100644
index 0000000000..b594e408b2
--- /dev/null
+++ b/spec/ruby/core/numeric/polar_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#polar" do
+ before :each do
+ @pos_numbers = [
+ 1,
+ 3898172610**9,
+ 987.18273,
+ Float::MAX,
+ Rational(13,7),
+ infinity_value,
+ ]
+ @neg_numbers = @pos_numbers.map {|n| -n}
+ @numbers = @pos_numbers + @neg_numbers
+ @numbers.push(0, 0.0)
+ end
+
+ it "returns a two-element Array" do
+ @numbers.each do |number|
+ number.polar.should be_an_instance_of(Array)
+ number.polar.size.should == 2
+ end
+ end
+
+ it "sets the first value to the absolute value of self" do
+ @numbers.each do |number|
+ number.polar.first.should == number.abs
+ end
+ end
+
+ it "sets the last value to 0 if self is positive" do
+ (@numbers - @neg_numbers).each do |number|
+ number.should >= 0
+ number.polar.last.should == 0
+ end
+ end
+
+ it "sets the last value to Pi if self is negative" do
+ @neg_numbers.each do |number|
+ number.should < 0
+ number.polar.last.should == Math::PI
+ end
+ end
+
+ it "returns [NaN, NaN] if self is NaN" do
+ nan_value.polar.size.should == 2
+ nan_value.polar.first.nan?.should be_true
+ nan_value.polar.last.nan?.should be_true
+ end
+end
diff --git a/spec/ruby/core/numeric/positive_spec.rb b/spec/ruby/core/numeric/positive_spec.rb
new file mode 100644
index 0000000000..3b831b4d34
--- /dev/null
+++ b/spec/ruby/core/numeric/positive_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#positive?" do
+ describe "on positive numbers" do
+ it "returns true" do
+ 1.positive?.should be_true
+ 0.1.positive?.should be_true
+ end
+ end
+
+ describe "on zero" do
+ it "returns false" do
+ 0.positive?.should be_false
+ 0.0.positive?.should be_false
+ end
+ end
+
+ describe "on negative numbers" do
+ it "returns false" do
+ -1.positive?.should be_false
+ -0.1.positive?.should be_false
+ end
+ end
+end
+
+describe "Numeric#positive?" do
+ before(:each) do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "returns true if self is greater than 0" do
+ @obj.should_receive(:>).with(0).and_return(true)
+ @obj.should.positive?
+ end
+
+ it "returns false if self is less than 0" do
+ @obj.should_receive(:>).with(0).and_return(false)
+ @obj.should_not.positive?
+ end
+end
diff --git a/spec/ruby/core/numeric/quo_spec.rb b/spec/ruby/core/numeric/quo_spec.rb
new file mode 100644
index 0000000000..67bacee9b5
--- /dev/null
+++ b/spec/ruby/core/numeric/quo_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/quo'
+
+describe "Numeric#quo" do
+ it "returns the result of self divided by the given Integer as a Rational" do
+ 5.quo(2).should eql(Rational(5,2))
+ end
+
+ it "returns the result of self divided by the given Float as a Float" do
+ 2.quo(2.5).should eql(0.8)
+ end
+
+ it "returns the result of self divided by the given Bignum as a Float" do
+ 45.quo(bignum_value).should be_close(1.04773789668636e-08, TOLERANCE)
+ end
+
+ it "raises a ZeroDivisionError when the given Integer is 0" do
+ -> { 0.quo(0) }.should raise_error(ZeroDivisionError)
+ -> { 10.quo(0) }.should raise_error(ZeroDivisionError)
+ -> { -10.quo(0) }.should raise_error(ZeroDivisionError)
+ -> { bignum_value.quo(0) }.should raise_error(ZeroDivisionError)
+ -> { (-bignum_value).quo(0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "calls #to_r to convert the object to a Rational" do
+ obj = NumericSpecs::Subclass.new
+ obj.should_receive(:to_r).and_return(Rational(1))
+
+ obj.quo(19).should == Rational(1, 19)
+ end
+
+ it "raises a TypeError of #to_r does not return a Rational" do
+ obj = NumericSpecs::Subclass.new
+ obj.should_receive(:to_r).and_return(1)
+
+ -> { obj.quo(19) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('x')).should_not_receive(:to_int)
+ 13.quo(obj)
+ }.should raise_error(TypeError)
+ -> { 13.quo("10") }.should raise_error(TypeError)
+ -> { 13.quo(:symbol) }.should raise_error(TypeError)
+ end
+
+ it "returns the result of calling self#/ with other" do
+ obj = NumericSpecs::Subclass.new
+ obj.should_receive(:to_r).and_return(19.quo(20))
+
+ obj.quo(19).should == 1.quo(20)
+ end
+
+ it "raises a ZeroDivisionError if the given argument is zero and not a Float" do
+ -> { 1.quo(0) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "returns infinity if the given argument is zero and is a Float" do
+ (1.quo(0.0)).to_s.should == 'Infinity'
+ (-1.quo(0.0)).to_s.should == '-Infinity'
+ end
+end
diff --git a/spec/ruby/core/numeric/real_spec.rb b/spec/ruby/core/numeric/real_spec.rb
new file mode 100644
index 0000000000..2d2499bcce
--- /dev/null
+++ b/spec/ruby/core/numeric/real_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#real" do
+ before :each do
+ @numbers = [
+ 20, # Integer
+ 398.72, # Float
+ Rational(3, 4), # Rational
+ bignum_value, # Bignum
+ infinity_value,
+ nan_value
+ ].map{ |n| [n, -n] }.flatten
+ end
+
+ it "returns self" do
+ @numbers.each do |number|
+ if number.to_f.nan?
+ number.real.nan?.should be_true
+ else
+ number.real.should == number
+ end
+ end
+ end
+
+ it "raises an ArgumentError if given any arguments" do
+ @numbers.each do |number|
+ -> { number.real(number) }.should raise_error(ArgumentError)
+ end
+ end
+end
+
+describe "Numeric#real?" do
+ it "returns true" do
+ NumericSpecs::Subclass.new.should.real?
+ end
+end
diff --git a/spec/ruby/core/numeric/rect_spec.rb b/spec/ruby/core/numeric/rect_spec.rb
new file mode 100644
index 0000000000..79a144c5a4
--- /dev/null
+++ b/spec/ruby/core/numeric/rect_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/rect'
+
+describe "Numeric#rect" do
+ it_behaves_like :numeric_rect, :rect
+end
diff --git a/spec/ruby/core/numeric/rectangular_spec.rb b/spec/ruby/core/numeric/rectangular_spec.rb
new file mode 100644
index 0000000000..2c68985a16
--- /dev/null
+++ b/spec/ruby/core/numeric/rectangular_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/rect'
+
+describe "Numeric#rectangular" do
+ it_behaves_like :numeric_rect, :rectangular
+end
diff --git a/spec/ruby/core/numeric/remainder_spec.rb b/spec/ruby/core/numeric/remainder_spec.rb
new file mode 100644
index 0000000000..1e2f5f3a96
--- /dev/null
+++ b/spec/ruby/core/numeric/remainder_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#remainder" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ @result = mock("Numeric#% result")
+ @other = mock("Passed Object")
+ end
+
+ it "returns the result of calling self#% with other if self is 0" do
+ @obj.should_receive(:%).with(@other).and_return(@result)
+ @result.should_receive(:==).with(0).and_return(true)
+
+ @obj.remainder(@other).should equal(@result)
+ end
+
+ it "returns the result of calling self#% with other if self and other are greater than 0" do
+ @obj.should_receive(:%).with(@other).and_return(@result)
+ @result.should_receive(:==).with(0).and_return(false)
+
+ @obj.should_receive(:<).with(0).and_return(false)
+
+ @obj.should_receive(:>).with(0).and_return(true)
+ @other.should_receive(:<).with(0).and_return(false)
+
+ @obj.remainder(@other).should equal(@result)
+ end
+
+ it "returns the result of calling self#% with other if self and other are less than 0" do
+ @obj.should_receive(:%).with(@other).and_return(@result)
+ @result.should_receive(:==).with(0).and_return(false)
+
+ @obj.should_receive(:<).with(0).and_return(true)
+ @other.should_receive(:>).with(0).and_return(false)
+
+ @obj.should_receive(:>).with(0).and_return(false)
+
+ @obj.remainder(@other).should equal(@result)
+ end
+
+ it "returns the result of calling self#% with other - other if self is greater than 0 and other is less than 0" do
+ @obj.should_receive(:%).with(@other).and_return(@result)
+ @result.should_receive(:==).with(0).and_return(false)
+
+ @obj.should_receive(:<).with(0).and_return(false)
+
+ @obj.should_receive(:>).with(0).and_return(true)
+ @other.should_receive(:<).with(0).and_return(true)
+
+ @result.should_receive(:-).with(@other).and_return(:result)
+
+ @obj.remainder(@other).should == :result
+ end
+
+ it "returns the result of calling self#% with other - other if self is less than 0 and other is greater than 0" do
+ @obj.should_receive(:%).with(@other).and_return(@result)
+ @result.should_receive(:==).with(0).and_return(false)
+
+ @obj.should_receive(:<).with(0).and_return(true)
+ @other.should_receive(:>).with(0).and_return(true)
+
+ @result.should_receive(:-).with(@other).and_return(:result)
+
+ @obj.remainder(@other).should == :result
+ end
+end
diff --git a/spec/ruby/core/numeric/round_spec.rb b/spec/ruby/core/numeric/round_spec.rb
new file mode 100644
index 0000000000..47c5837693
--- /dev/null
+++ b/spec/ruby/core/numeric/round_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#round" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "converts self to a Float (using #to_f) and returns the #round'ed result" do
+ @obj.should_receive(:to_f).and_return(2 - TOLERANCE, TOLERANCE - 2)
+ @obj.round.should == 2
+ @obj.round.should == -2
+ end
+end
diff --git a/spec/ruby/core/numeric/shared/abs.rb b/spec/ruby/core/numeric/shared/abs.rb
new file mode 100644
index 0000000000..c3dadccfd6
--- /dev/null
+++ b/spec/ruby/core/numeric/shared/abs.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :numeric_abs, shared: true do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "returns self when self is greater than 0" do
+ @obj.should_receive(:<).with(0).and_return(false)
+ @obj.send(@method).should == @obj
+ end
+
+ it "returns self\#@- when self is less than 0" do
+ @obj.should_receive(:<).with(0).and_return(true)
+ @obj.should_receive(:-@).and_return(:absolute_value)
+ @obj.send(@method).should == :absolute_value
+ end
+end
diff --git a/spec/ruby/core/numeric/shared/arg.rb b/spec/ruby/core/numeric/shared/arg.rb
new file mode 100644
index 0000000000..c8e7ad8333
--- /dev/null
+++ b/spec/ruby/core/numeric/shared/arg.rb
@@ -0,0 +1,38 @@
+require_relative '../../../spec_helper'
+
+describe :numeric_arg, shared: true do
+ before :each do
+ @numbers = [
+ 20,
+ Rational(3, 4),
+ bignum_value,
+ infinity_value
+ ]
+ end
+
+ it "returns 0 if positive" do
+ @numbers.each do |number|
+ number.send(@method).should == 0
+ end
+ end
+
+ it "returns Pi if negative" do
+ @numbers.each do |number|
+ (0-number).send(@method).should == Math::PI
+ end
+ end
+
+ describe "with a Numeric subclass" do
+ it "returns 0 if self#<(0) returns false" do
+ numeric = mock_numeric('positive')
+ numeric.should_receive(:<).with(0).and_return(false)
+ numeric.send(@method).should == 0
+ end
+
+ it "returns Pi if self#<(0) returns true" do
+ numeric = mock_numeric('positive')
+ numeric.should_receive(:<).with(0).and_return(true)
+ numeric.send(@method).should == Math::PI
+ end
+ end
+end
diff --git a/spec/ruby/core/numeric/shared/conj.rb b/spec/ruby/core/numeric/shared/conj.rb
new file mode 100644
index 0000000000..6d5197ecab
--- /dev/null
+++ b/spec/ruby/core/numeric/shared/conj.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+describe :numeric_conj, shared: true do
+ before :each do
+ @numbers = [
+ 20, # Integer
+ 398.72, # Float
+ Rational(3, 4), # Rational
+ bignum_value,
+ infinity_value,
+ nan_value
+ ]
+ end
+
+ it "returns self" do
+ @numbers.each do |number|
+ number.send(@method).should equal(number)
+ end
+ end
+end
diff --git a/spec/ruby/core/numeric/shared/imag.rb b/spec/ruby/core/numeric/shared/imag.rb
new file mode 100644
index 0000000000..ac2da40a3b
--- /dev/null
+++ b/spec/ruby/core/numeric/shared/imag.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+describe :numeric_imag, shared: true do
+ before :each do
+ @numbers = [
+ 20, # Integer
+ 398.72, # Float
+ Rational(3, 4), # Rational
+ bignum_value, # Bignum
+ infinity_value,
+ nan_value
+ ].map{|n| [n,-n]}.flatten
+ end
+
+ it "returns 0" do
+ @numbers.each do |number|
+ number.send(@method).should == 0
+ end
+ end
+
+ it "raises an ArgumentError if given any arguments" do
+ @numbers.each do |number|
+ -> { number.send(@method, number) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/numeric/shared/quo.rb b/spec/ruby/core/numeric/shared/quo.rb
new file mode 100644
index 0000000000..2392636fe7
--- /dev/null
+++ b/spec/ruby/core/numeric/shared/quo.rb
@@ -0,0 +1,7 @@
+describe :numeric_quo_18, shared: true do
+ it "returns the result of calling self#/ with other" do
+ obj = mock_numeric('numeric')
+ obj.should_receive(:/).with(19).and_return(:result)
+ obj.send(@method, 19).should == :result
+ end
+end
diff --git a/spec/ruby/core/numeric/shared/rect.rb b/spec/ruby/core/numeric/shared/rect.rb
new file mode 100644
index 0000000000..9cde19a398
--- /dev/null
+++ b/spec/ruby/core/numeric/shared/rect.rb
@@ -0,0 +1,48 @@
+require_relative '../../../spec_helper'
+
+describe :numeric_rect, shared: true do
+ before :each do
+ @numbers = [
+ 20, # Integer
+ 398.72, # Float
+ Rational(3, 4), # Rational
+ 99999999**99, # Bignum
+ infinity_value,
+ nan_value
+ ]
+ end
+
+ it "returns an Array" do
+ @numbers.each do |number|
+ number.send(@method).should be_an_instance_of(Array)
+ end
+ end
+
+ it "returns a two-element Array" do
+ @numbers.each do |number|
+ number.send(@method).size.should == 2
+ end
+ end
+
+ it "returns self as the first element" do
+ @numbers.each do |number|
+ if Float === number and number.nan?
+ number.send(@method).first.nan?.should be_true
+ else
+ number.send(@method).first.should == number
+ end
+ end
+ end
+
+ it "returns 0 as the last element" do
+ @numbers.each do |number|
+ number.send(@method).last.should == 0
+ end
+ end
+
+ it "raises an ArgumentError if given any arguments" do
+ @numbers.each do |number|
+ -> { number.send(@method, number) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/numeric/shared/step.rb b/spec/ruby/core/numeric/shared/step.rb
new file mode 100644
index 0000000000..8b1a7bf307
--- /dev/null
+++ b/spec/ruby/core/numeric/shared/step.rb
@@ -0,0 +1,416 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+# Describes Numeric#step shared specs between different argument styles.
+# To be able to do it, the @step ivar must contain a Proc that transforms
+# the step call arguments passed as positional arguments to the style of
+# arguments pretended to test.
+describe :numeric_step, :shared => true do
+ before :each do
+ ScratchPad.record []
+ @prc = -> x { ScratchPad << x }
+ end
+
+ it "defaults to step = 1" do
+ @step.call(1, 5, &@prc)
+ ScratchPad.recorded.should eql [1, 2, 3, 4, 5]
+ end
+
+ it "defaults to an infinite limit with a step size of 1 for Integers" do
+ 1.step.first(5).should == [1, 2, 3, 4, 5]
+ end
+
+ it "defaults to an infinite limit with a step size of 1.0 for Floats" do
+ 1.0.step.first(5).should == [1.0, 2.0, 3.0, 4.0, 5.0]
+ end
+
+ describe "when self, stop and step are Integers" do
+ it "yields only Integers" do
+ @step.call(1, 5, 1) { |x| x.should be_an_instance_of(Integer) }
+ end
+
+ describe "with a positive step" do
+ it "yields while increasing self by step until stop is reached" do
+ @step.call(1, 5, 1, &@prc)
+ ScratchPad.recorded.should eql [1, 2, 3, 4, 5]
+ end
+
+ it "yields once when self equals stop" do
+ @step.call(1, 1, 1, &@prc)
+ ScratchPad.recorded.should eql [1]
+ end
+
+ it "does not yield when self is greater than stop" do
+ @step.call(2, 1, 1, &@prc)
+ ScratchPad.recorded.should eql []
+ end
+ end
+
+ describe "with a negative step" do
+ it "yields while decreasing self by step until stop is reached" do
+ @step.call(5, 1, -1, &@prc)
+ ScratchPad.recorded.should eql [5, 4, 3, 2, 1]
+ end
+
+ it "yields once when self equals stop" do
+ @step.call(5, 5, -1, &@prc)
+ ScratchPad.recorded.should eql [5]
+ end
+
+ it "does not yield when self is less than stop" do
+ @step.call(1, 5, -1, &@prc)
+ ScratchPad.recorded.should == []
+ end
+ end
+ end
+
+ describe "when at least one of self, stop or step is a Float" do
+ it "yields Floats even if only self is a Float" do
+ @step.call(1.5, 5, 1) { |x| x.should be_an_instance_of(Float) }
+ end
+
+ it "yields Floats even if only stop is a Float" do
+ @step.call(1, 5.0, 1) { |x| x.should be_an_instance_of(Float) }
+ end
+
+ it "yields Floats even if only step is a Float" do
+ @step.call(1, 5, 1.0) { |x| x.should be_an_instance_of(Float) }
+ end
+
+ describe "with a positive step" do
+ it "yields while increasing self by step while < stop" do
+ @step.call(1.5, 5, 1, &@prc)
+ ScratchPad.recorded.should eql [1.5, 2.5, 3.5, 4.5]
+ end
+
+ it "yields once when self equals stop" do
+ @step.call(1.5, 1.5, 1, &@prc)
+ ScratchPad.recorded.should eql [1.5]
+ end
+
+ it "does not yield when self is greater than stop" do
+ @step.call(2.5, 1.5, 1, &@prc)
+ ScratchPad.recorded.should == []
+ end
+
+ it "is careful about not yielding a value greater than limit" do
+ # As 9*1.3+1.0 == 12.700000000000001 > 12.7, we test:
+ @step.call(1.0, 12.7, 1.3, &@prc)
+ ScratchPad.recorded.should eql [1.0, 2.3, 3.6, 4.9, 6.2, 7.5, 8.8, 10.1, 11.4, 12.7]
+ end
+ end
+
+ describe "with a negative step" do
+ it "yields while decreasing self by step while self > stop" do
+ @step.call(5, 1.5, -1, &@prc)
+ ScratchPad.recorded.should eql [5.0, 4.0, 3.0, 2.0]
+ end
+
+ it "yields once when self equals stop" do
+ @step.call(1.5, 1.5, -1, &@prc)
+ ScratchPad.recorded.should eql [1.5]
+ end
+
+ it "does not yield when self is less than stop" do
+ @step.call(1, 5, -1.5, &@prc)
+ ScratchPad.recorded.should == []
+ end
+
+ it "is careful about not yielding a value smaller than limit" do
+ # As -9*1.3-1.0 == -12.700000000000001 < -12.7, we test:
+ @step.call(-1.0, -12.7, -1.3, &@prc)
+ ScratchPad.recorded.should eql [-1.0, -2.3, -3.6, -4.9, -6.2, -7.5, -8.8, -10.1, -11.4, -12.7]
+ end
+ end
+
+ describe "with a positive Infinity step" do
+ it "yields once if self < stop" do
+ @step.call(42, 100, infinity_value, &@prc)
+ ScratchPad.recorded.should eql [42.0]
+ end
+
+ it "yields once when stop is Infinity" do
+ @step.call(42, infinity_value, infinity_value, &@prc)
+ ScratchPad.recorded.should eql [42.0]
+ end
+
+ it "yields once when self equals stop" do
+ @step.call(42, 42, infinity_value, &@prc)
+ ScratchPad.recorded.should eql [42.0]
+ end
+
+ it "yields once when self and stop are Infinity" do
+ # @step.call(infinity_value, infinity_value, infinity_value, &@prc)
+ @step.call(infinity_value, infinity_value, infinity_value, &@prc)
+ ScratchPad.recorded.should == [infinity_value]
+ end
+
+ it "does not yield when self > stop" do
+ @step.call(100, 42, infinity_value, &@prc)
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not yield when stop is -Infinity" do
+ @step.call(42, -infinity_value, infinity_value, &@prc)
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "with a negative Infinity step" do
+ it "yields once if self > stop" do
+ @step.call(42, 6, -infinity_value, &@prc)
+ ScratchPad.recorded.should eql [42.0]
+ end
+
+ it "yields once if stop is -Infinity" do
+ @step.call(42, -infinity_value, -infinity_value, &@prc)
+ ScratchPad.recorded.should eql [42.0]
+ end
+
+ it "yields once when self equals stop" do
+ @step.call(42, 42, -infinity_value, &@prc)
+ ScratchPad.recorded.should eql [42.0]
+ end
+
+ it "yields once when self and stop are Infinity" do
+ @step.call(infinity_value, infinity_value, -infinity_value, &@prc)
+ ScratchPad.recorded.should == [infinity_value]
+ end
+
+ it "does not yield when self > stop" do
+ @step.call(42, 100, -infinity_value, &@prc)
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not yield when stop is Infinity" do
+ @step.call(42, infinity_value, -infinity_value, &@prc)
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "with a Infinity stop and a positive step" do
+ it "does not yield when self is infinity" do
+ @step.call(infinity_value, infinity_value, 1, &@prc)
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "with a Infinity stop and a negative step" do
+ it "does not yield when self is negative infinity" do
+ @step.call(-infinity_value, infinity_value, -1, &@prc)
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not yield when self is positive infinity" do
+ @step.call(infinity_value, infinity_value, -1, &@prc)
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "with a negative Infinity stop and a positive step" do
+ it "does not yield when self is negative infinity" do
+ @step.call(-infinity_value, -infinity_value, 1, &@prc)
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "with a negative Infinity stop and a negative step" do
+ it "does not yield when self is negative infinity" do
+ @step.call(-infinity_value, -infinity_value, -1, &@prc)
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ end
+
+ describe "when step is a String" do
+ describe "with self and stop as Integers" do
+ it "raises an ArgumentError when step is a numeric representation" do
+ -> { @step.call(1, 5, "1") {} }.should raise_error(ArgumentError)
+ -> { @step.call(1, 5, "0.1") {} }.should raise_error(ArgumentError)
+ -> { @step.call(1, 5, "1/3") {} }.should raise_error(ArgumentError)
+ end
+ it "raises an ArgumentError with step as an alphanumeric string" do
+ -> { @step.call(1, 5, "foo") {} }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with self and stop as Floats" do
+ it "raises an ArgumentError when step is a numeric representation" do
+ -> { @step.call(1.1, 5.1, "1") {} }.should raise_error(ArgumentError)
+ -> { @step.call(1.1, 5.1, "0.1") {} }.should raise_error(ArgumentError)
+ -> { @step.call(1.1, 5.1, "1/3") {} }.should raise_error(ArgumentError)
+ end
+ it "raises an ArgumentError with step as an alphanumeric string" do
+ -> { @step.call(1.1, 5.1, "foo") {} }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ it "does not rescue ArgumentError exceptions" do
+ -> { @step.call(1, 2) { raise ArgumentError, "" }}.should raise_error(ArgumentError)
+ end
+
+ it "does not rescue TypeError exceptions" do
+ -> { @step.call(1, 2) { raise TypeError, "" } }.should raise_error(TypeError)
+ end
+
+ describe "when no block is given" do
+ step_enum_class = Enumerator::ArithmeticSequence
+
+ ruby_version_is ""..."3.0" do
+ it "returns an #{step_enum_class} when step is 0" do
+ @step.call(1, 2, 0).should be_an_instance_of(step_enum_class)
+ end
+ end
+
+ it "returns an #{step_enum_class} when not passed a block and self > stop" do
+ @step.call(1, 0, 2).should be_an_instance_of(step_enum_class)
+ end
+
+ it "returns an #{step_enum_class} when not passed a block and self < stop" do
+ @step.call(1, 2, 3).should be_an_instance_of(step_enum_class)
+ end
+
+ it "returns an #{step_enum_class} that uses the given step" do
+ @step.call(0, 5, 2).to_a.should eql [0, 2, 4]
+ end
+
+ describe "when step is a String" do
+ describe "with self and stop as Integers" do
+ it "returns an Enumerator" do
+ @step.call(1, 5, "foo").should be_an_instance_of(Enumerator)
+ end
+ end
+
+ describe "with self and stop as Floats" do
+ it "returns an Enumerator" do
+ @step.call(1.1, 5.1, "foo").should be_an_instance_of(Enumerator)
+ end
+ end
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ describe "when step is a String" do
+ describe "with self and stop as Integers" do
+ it "raises an ArgumentError when step is a numeric representation" do
+ -> { @step.call(1, 5, "1").size }.should raise_error(ArgumentError)
+ -> { @step.call(1, 5, "0.1").size }.should raise_error(ArgumentError)
+ -> { @step.call(1, 5, "1/3").size }.should raise_error(ArgumentError)
+ end
+ it "raises an ArgumentError with step as an alphanumeric string" do
+ -> { @step.call(1, 5, "foo").size }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with self and stop as Floats" do
+ it "raises an ArgumentError when step is a numeric representation" do
+ -> { @step.call(1.1, 5.1, "1").size }.should raise_error(ArgumentError)
+ -> { @step.call(1.1, 5.1, "0.1").size }.should raise_error(ArgumentError)
+ -> { @step.call(1.1, 5.1, "1/3").size }.should raise_error(ArgumentError)
+ end
+ it "raises an ArgumentError with step as an alphanumeric string" do
+ -> { @step.call(1.1, 5.1, "foo").size }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe "when self, stop and step are Integers and step is positive" do
+ it "returns the difference between self and stop divided by the number of steps" do
+ @step.call(5, 10, 11).size.should == 1
+ @step.call(5, 10, 6).size.should == 1
+ @step.call(5, 10, 5).size.should == 2
+ @step.call(5, 10, 4).size.should == 2
+ @step.call(5, 10, 2).size.should == 3
+ @step.call(5, 10, 1).size.should == 6
+ @step.call(5, 10).size.should == 6
+ @step.call(10, 10, 1).size.should == 1
+ end
+
+ it "returns 0 if value > limit" do
+ @step.call(11, 10, 1).size.should == 0
+ end
+ end
+
+ describe "when self, stop and step are Integers and step is negative" do
+ it "returns the difference between self and stop divided by the number of steps" do
+ @step.call(10, 5, -11).size.should == 1
+ @step.call(10, 5, -6).size.should == 1
+ @step.call(10, 5, -5).size.should == 2
+ @step.call(10, 5, -4).size.should == 2
+ @step.call(10, 5, -2).size.should == 3
+ @step.call(10, 5, -1).size.should == 6
+ @step.call(10, 10, -1).size.should == 1
+ end
+
+ it "returns 0 if value < limit" do
+ @step.call(10, 11, -1).size.should == 0
+ end
+ end
+
+ describe "when self, stop or step is a Float" do
+ describe "and step is positive" do
+ it "returns the difference between self and stop divided by the number of steps" do
+ @step.call(5, 10, 11.0).size.should == 1
+ @step.call(5, 10, 6.0).size.should == 1
+ @step.call(5, 10, 5.0).size.should == 2
+ @step.call(5, 10, 4.0).size.should == 2
+ @step.call(5, 10, 2.0).size.should == 3
+ @step.call(5, 10, 0.5).size.should == 11
+ @step.call(5, 10, 1.0).size.should == 6
+ @step.call(5, 10.5).size.should == 6
+ @step.call(10, 10, 1.0).size.should == 1
+ end
+
+ it "returns 0 if value > limit" do
+ @step.call(10, 5.5).size.should == 0
+ @step.call(11, 10, 1.0).size.should == 0
+ @step.call(11, 10, 1.5).size.should == 0
+ @step.call(10, 5, infinity_value).size.should == 0
+ end
+
+ it "returns 1 if step is infinity_value" do
+ @step.call(5, 10, infinity_value).size.should == 1
+ end
+ end
+
+ describe "and step is negative" do
+ it "returns the difference between self and stop divided by the number of steps" do
+ @step.call(10, 5, -11.0).size.should == 1
+ @step.call(10, 5, -6.0).size.should == 1
+ @step.call(10, 5, -5.0).size.should == 2
+ @step.call(10, 5, -4.0).size.should == 2
+ @step.call(10, 5, -2.0).size.should == 3
+ @step.call(10, 5, -0.5).size.should == 11
+ @step.call(10, 5, -1.0).size.should == 6
+ @step.call(10, 10, -1.0).size.should == 1
+ end
+
+ it "returns 0 if value < limit" do
+ @step.call(10, 11, -1.0).size.should == 0
+ @step.call(10, 11, -1.5).size.should == 0
+ @step.call(5, 10, -infinity_value).size.should == 0
+ end
+
+ it "returns 1 if step is infinity_value" do
+ @step.call(10, 5, -infinity_value).size.should == 1
+ end
+ end
+ end
+
+ describe "when stop is not passed" do
+ it "returns infinity_value" do
+ @step.call(1).size.should == infinity_value
+ end
+ end
+
+ describe "when stop is nil" do
+ it "returns infinity_value" do
+ @step.call(1, nil, 5).size.should == infinity_value
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/numeric/singleton_method_added_spec.rb b/spec/ruby/core/numeric/singleton_method_added_spec.rb
new file mode 100644
index 0000000000..2091398e5d
--- /dev/null
+++ b/spec/ruby/core/numeric/singleton_method_added_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#singleton_method_added" do
+ before :all do
+ class ::NumericSpecs::Subclass
+ # We want restore default Numeric behaviour for this particular test
+ remove_method :singleton_method_added
+ end
+ end
+
+ after :all do
+ class ::NumericSpecs::Subclass
+ # Allow mocking methods again
+ def singleton_method_added(val)
+ end
+ end
+ end
+
+ it "raises a TypeError when trying to define a singleton method on a Numeric" do
+ -> do
+ a = NumericSpecs::Subclass.new
+ def a.test; end
+ end.should raise_error(TypeError)
+
+ -> do
+ a = 1
+ def a.test; end
+ end.should raise_error(TypeError)
+
+ -> do
+ a = 1.5
+ def a.test; end
+ end.should raise_error(TypeError)
+
+ -> do
+ a = bignum_value
+ def a.test; end
+ end.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/numeric/step_spec.rb b/spec/ruby/core/numeric/step_spec.rb
new file mode 100644
index 0000000000..095c474fec
--- /dev/null
+++ b/spec/ruby/core/numeric/step_spec.rb
@@ -0,0 +1,198 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/step'
+
+describe "Numeric#step" do
+
+ describe 'with positional args' do
+ it "raises an ArgumentError when step is 0" do
+ -> { 1.step(5, 0) {} }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when step is 0.0" do
+ -> { 1.step(2, 0.0) {} }.should raise_error(ArgumentError)
+ end
+
+ before :all do
+ # This lambda definition limits to return the arguments it receives.
+ # It's needed to test numeric_step behaviour with positional arguments.
+ @step = -> receiver, *args, &block { receiver.step(*args, &block) }
+ end
+ it_behaves_like :numeric_step, :step
+
+ describe "when no block is given" do
+ step_enum_class = Enumerator::ArithmeticSequence
+
+ ruby_version_is ""..."3.0" do
+ it "returns an #{step_enum_class} when step is 0" do
+ 1.step(5, 0).should be_an_instance_of(step_enum_class)
+ end
+
+ it "returns an #{step_enum_class} when step is 0.0" do
+ 1.step(2, 0.0).should be_an_instance_of(step_enum_class)
+ end
+ end
+
+ describe "returned #{step_enum_class}" do
+ describe "size" do
+ ruby_version_is ""..."3.0" do
+ it "is infinity when step is 0" do
+ enum = 1.step(5, 0)
+ enum.size.should == Float::INFINITY
+ end
+
+ it "is infinity when step is 0.0" do
+ enum = 1.step(2, 0.0)
+ enum.size.should == Float::INFINITY
+ end
+ end
+
+ it "defaults to an infinite size" do
+ enum = 1.step
+ enum.size.should == Float::INFINITY
+ end
+ end
+
+ describe "type" do
+ it "returns an instance of Enumerator::ArithmeticSequence" do
+ 1.step(10).class.should == Enumerator::ArithmeticSequence
+ end
+ end
+ end
+ end
+ end
+
+ describe 'with keyword arguments' do
+ ruby_version_is ""..."3.0" do
+ it "doesn't raise an error when step is 0" do
+ -> { 1.step(to: 5, by: 0) { break } }.should_not raise_error
+ end
+
+ it "doesn't raise an error when step is 0.0" do
+ -> { 1.step(to: 2, by: 0.0) { break } }.should_not raise_error
+ end
+
+ it "should loop over self when step is 0 or 0.0" do
+ 1.step(to: 2, by: 0.0).take(5).should eql [1.0, 1.0, 1.0, 1.0, 1.0]
+ 1.step(to: 2, by: 0).take(5).should eql [1, 1, 1, 1, 1]
+ 1.1.step(to: 2, by: 0).take(5).should eql [1.1, 1.1, 1.1, 1.1, 1.1]
+ end
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return infinity_value when limit is nil" do
+ 1.step(by: 42).size.should == infinity_value
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "should return infinity_value when step is 0" do
+ 1.step(to: 5, by: 0).size.should == infinity_value
+ end
+
+ it "should return infinity_value when step is 0.0" do
+ 1.step(to: 2, by: 0.0).size.should == infinity_value
+ end
+ end
+
+ it "should return infinity_value when ascending towards a limit of Float::INFINITY" do
+ 1.step(to: Float::INFINITY, by: 42).size.should == infinity_value
+ end
+
+ it "should return infinity_value when descending towards a limit of -Float::INFINITY" do
+ 1.step(to: -Float::INFINITY, by: -42).size.should == infinity_value
+ end
+
+ it "should return 1 when the both limit and step are Float::INFINITY" do
+ 1.step(to: Float::INFINITY, by: Float::INFINITY).size.should == 1
+ end
+
+ it "should return 1 when the both limit and step are -Float::INFINITY" do
+ 1.step(to: -Float::INFINITY, by: -Float::INFINITY).size.should == 1
+ end
+ end
+ end
+ end
+
+ before :all do
+ # This lambda transforms a positional step method args into keyword arguments.
+ # It's needed to test numeric_step behaviour with keyword arguments.
+ @step = -> receiver, *args, &block do
+ kw_args = { to: args[0] }
+ kw_args[:by] = args[1] if args.size == 2
+ receiver.step(**kw_args, &block)
+ end
+ end
+ it_behaves_like :numeric_step, :step
+ end
+
+ describe 'with mixed arguments' do
+ ruby_version_is ""..."3.0" do
+ it "doesn't raise an error when step is 0" do
+ -> { 1.step(5, by: 0) { break } }.should_not raise_error
+ end
+
+ it "doesn't raise an error when step is 0.0" do
+ -> { 1.step(2, by: 0.0) { break } }.should_not raise_error
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it " raises an ArgumentError when step is 0" do
+ -> { 1.step(5, by: 0) { break } }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when step is 0.0" do
+ -> { 1.step(2, by: 0.0) { break } }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises a ArgumentError when limit and to are defined" do
+ -> { 1.step(5, 1, to: 5) { break } }.should raise_error(ArgumentError)
+ end
+
+ it "raises a ArgumentError when step and by are defined" do
+ -> { 1.step(5, 1, by: 5) { break } }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "should loop over self when step is 0 or 0.0" do
+ 1.step(2, by: 0.0).take(5).should eql [1.0, 1.0, 1.0, 1.0, 1.0]
+ 1.step(2, by: 0).take(5).should eql [1, 1, 1, 1, 1]
+ 1.1.step(2, by: 0).take(5).should eql [1.1, 1.1, 1.1, 1.1, 1.1]
+ end
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ ruby_version_is ""..."3.0" do
+ it "should return infinity_value when step is 0" do
+ 1.step(5, by: 0).size.should == infinity_value
+ end
+
+ it "should return infinity_value when step is 0.0" do
+ 1.step(2, by: 0.0).size.should == infinity_value
+ end
+ end
+ end
+ end
+ end
+
+ before :all do
+ # This lambda definition transforms a positional step method args into
+ # a mix of positional and keyword arguments.
+ # It's needed to test numeric_step behaviour with positional mixed with
+ # keyword arguments.
+ @step = -> receiver, *args, &block do
+ if args.size == 2
+ receiver.step(args[0], by: args[1], &block)
+ else
+ receiver.step(*args, &block)
+ end
+ end
+ end
+ it_behaves_like :numeric_step, :step
+ end
+end
diff --git a/spec/ruby/core/numeric/to_c_spec.rb b/spec/ruby/core/numeric/to_c_spec.rb
new file mode 100644
index 0000000000..75245a612e
--- /dev/null
+++ b/spec/ruby/core/numeric/to_c_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#to_c" do
+ before :all do
+ @numbers = [
+ 0,
+ 29871,
+ 99999999999999**99,
+ -72628191273,
+ Rational(2,3),
+ Rational(1.898),
+ Rational(-238),
+ 29282.2827,
+ -2927.00091,
+ 0.0,
+ 12.0,
+ Float::MAX,
+ infinity_value,
+ nan_value
+ ]
+ end
+
+ it "returns a Complex object" do
+ @numbers.each do |number|
+ number.to_c.should be_an_instance_of(Complex)
+ end
+ end
+
+ it "uses self as the real component" do
+ @numbers.each do |number|
+ real = number.to_c.real
+ if Float === number and number.nan?
+ real.nan?.should be_true
+ else
+ real.should == number
+ end
+ end
+ end
+
+ it "uses 0 as the imaginary component" do
+ @numbers.each do |number|
+ number.to_c.imag.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/core/numeric/to_int_spec.rb b/spec/ruby/core/numeric/to_int_spec.rb
new file mode 100644
index 0000000000..3cc39a6d40
--- /dev/null
+++ b/spec/ruby/core/numeric/to_int_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#to_int" do
+ it "returns self#to_i" do
+ obj = NumericSpecs::Subclass.new
+ obj.should_receive(:to_i).and_return(:result)
+ obj.to_int.should == :result
+ end
+end
diff --git a/spec/ruby/core/numeric/truncate_spec.rb b/spec/ruby/core/numeric/truncate_spec.rb
new file mode 100644
index 0000000000..f1592334c5
--- /dev/null
+++ b/spec/ruby/core/numeric/truncate_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#truncate" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "converts self to a Float (using #to_f) and returns the #truncate'd result" do
+ @obj.should_receive(:to_f).and_return(2.5555, -2.3333)
+ @obj.truncate.should == 2
+ @obj.truncate.should == -2
+ end
+end
diff --git a/spec/ruby/core/numeric/uminus_spec.rb b/spec/ruby/core/numeric/uminus_spec.rb
new file mode 100644
index 0000000000..39065fa392
--- /dev/null
+++ b/spec/ruby/core/numeric/uminus_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Numeric#-@" do
+ it "returns the same value with opposite sign (integers)" do
+ 0.send(:-@).should == 0
+ 100.send(:-@).should == -100
+ -100.send(:-@).should == 100
+ end
+
+ it "returns the same value with opposite sign (floats)" do
+ 34.56.send(:-@).should == -34.56
+ -34.56.send(:-@).should == 34.56
+ end
+
+ it "returns the same value with opposite sign (two complement)" do
+ 2147483648.send(:-@).should == -2147483648
+ -2147483648.send(:-@).should == 2147483648
+ 9223372036854775808.send(:-@).should == -9223372036854775808
+ -9223372036854775808.send(:-@).should == 9223372036854775808
+ end
+
+ describe "with a Numeric subclass" do
+ it "calls #coerce(0) on self, then subtracts the second element of the result from the first" do
+ ten = mock_numeric('10')
+ zero = mock_numeric('0')
+ ten.should_receive(:coerce).with(0).and_return([zero, ten])
+ zero.should_receive(:-).with(ten).and_return(-10)
+ ten.send(:-@).should == -10
+ end
+ end
+end
diff --git a/spec/ruby/core/numeric/uplus_spec.rb b/spec/ruby/core/numeric/uplus_spec.rb
new file mode 100644
index 0000000000..88cf5e037b
--- /dev/null
+++ b/spec/ruby/core/numeric/uplus_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#+@" do
+ it "returns self" do
+ obj = NumericSpecs::Subclass.new
+ obj.send(:+@).should == obj
+ end
+end
diff --git a/spec/ruby/core/numeric/zero_spec.rb b/spec/ruby/core/numeric/zero_spec.rb
new file mode 100644
index 0000000000..0fb7619bcd
--- /dev/null
+++ b/spec/ruby/core/numeric/zero_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Numeric#zero?" do
+ before :each do
+ @obj = NumericSpecs::Subclass.new
+ end
+
+ it "returns true if self is 0" do
+ @obj.should_receive(:==).with(0).and_return(true)
+ @obj.should.zero?
+ end
+
+ it "returns false if self is not 0" do
+ @obj.should_receive(:==).with(0).and_return(false)
+ @obj.should_not.zero?
+ end
+end
diff --git a/spec/ruby/core/objectspace/_id2ref_spec.rb b/spec/ruby/core/objectspace/_id2ref_spec.rb
new file mode 100644
index 0000000000..c088ae2743
--- /dev/null
+++ b/spec/ruby/core/objectspace/_id2ref_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace._id2ref" do
+ it "converts an object id to a reference to the object" do
+ s = "I am a string"
+ r = ObjectSpace._id2ref(s.object_id)
+ r.should == s
+ end
+
+ it "retrieves true by object_id" do
+ ObjectSpace._id2ref(true.object_id).should == true
+ end
+
+ it "retrieves false by object_id" do
+ ObjectSpace._id2ref(false.object_id).should == false
+ end
+
+ it "retrieves nil by object_id" do
+ ObjectSpace._id2ref(nil.object_id).should == nil
+ end
+
+ it "retrieves a small Integer by object_id" do
+ ObjectSpace._id2ref(1.object_id).should == 1
+ ObjectSpace._id2ref((-42).object_id).should == -42
+ end
+
+ it "retrieves a large Integer by object_id" do
+ obj = 1 << 88
+ ObjectSpace._id2ref(obj.object_id).should.equal?(obj)
+ end
+
+ it "retrieves a Symbol by object_id" do
+ ObjectSpace._id2ref(:sym.object_id).should.equal?(:sym)
+ end
+
+ it "retrieves a String by object_id" do
+ obj = "str"
+ ObjectSpace._id2ref(obj.object_id).should.equal?(obj)
+ end
+
+ it "retrieves a frozen literal String by object_id" do
+ ObjectSpace._id2ref("frozen string literal _id2ref".freeze.object_id).should.equal?("frozen string literal _id2ref".freeze)
+ end
+
+ it "retrieves an Encoding by object_id" do
+ ObjectSpace._id2ref(Encoding::UTF_8.object_id).should.equal?(Encoding::UTF_8)
+ end
+
+ it 'raises RangeError when an object could not be found' do
+ proc { ObjectSpace._id2ref(1 << 60) }.should raise_error(RangeError)
+ end
+end
diff --git a/spec/ruby/core/objectspace/add_finalizer_spec.rb b/spec/ruby/core/objectspace/add_finalizer_spec.rb
new file mode 100644
index 0000000000..3540ac0413
--- /dev/null
+++ b/spec/ruby/core/objectspace/add_finalizer_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace.add_finalizer" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/objectspace/call_finalizer_spec.rb b/spec/ruby/core/objectspace/call_finalizer_spec.rb
new file mode 100644
index 0000000000..6dce92ddd6
--- /dev/null
+++ b/spec/ruby/core/objectspace/call_finalizer_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace.call_finalizer" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/objectspace/count_objects_spec.rb b/spec/ruby/core/objectspace/count_objects_spec.rb
new file mode 100644
index 0000000000..e9831a3a42
--- /dev/null
+++ b/spec/ruby/core/objectspace/count_objects_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace.count_objects" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/objectspace/define_finalizer_spec.rb b/spec/ruby/core/objectspace/define_finalizer_spec.rb
new file mode 100644
index 0000000000..d9db027e0b
--- /dev/null
+++ b/spec/ruby/core/objectspace/define_finalizer_spec.rb
@@ -0,0 +1,194 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# Why do we not test that finalizers are run by the GC? The documentation
+# says that finalizers are never guaranteed to be run, so we can't
+# spec that they are. On some implementations of Ruby the finalizers may
+# run asynchronously, meaning that we can't predict when they'll run,
+# even if they were guaranteed to do so. Even on MRI finalizers can be
+# very unpredictable, due to conservative stack scanning and references
+# left in unused memory.
+
+describe "ObjectSpace.define_finalizer" do
+ it "raises an ArgumentError if the action does not respond to call" do
+ -> {
+ ObjectSpace.define_finalizer(Object.new, mock("ObjectSpace.define_finalizer no #call"))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "accepts an object and a proc" do
+ handler = -> id { id }
+ ObjectSpace.define_finalizer(Object.new, handler).should == [0, handler]
+ end
+
+ it "accepts an object and a bound method" do
+ handler = mock("callable")
+ def handler.finalize(id) end
+ finalize = handler.method(:finalize)
+ ObjectSpace.define_finalizer(Object.new, finalize).should == [0, finalize]
+ end
+
+ it "accepts an object and a callable" do
+ handler = mock("callable")
+ def handler.call(id) end
+ ObjectSpace.define_finalizer(Object.new, handler).should == [0, handler]
+ end
+
+ it "accepts an object and a block" do
+ handler = -> id { id }
+ ObjectSpace.define_finalizer(Object.new, &handler).should == [0, handler]
+ end
+
+ it "raises ArgumentError trying to define a finalizer on a non-reference" do
+ -> {
+ ObjectSpace.define_finalizer(:blah) { 1 }
+ }.should raise_error(ArgumentError)
+ end
+
+ # see [ruby-core:24095]
+ it "calls finalizer on process termination" do
+ code = <<-RUBY
+ def scoped
+ Proc.new { puts "finalizer run" }
+ end
+ handler = scoped
+ obj = "Test"
+ ObjectSpace.define_finalizer(obj, handler)
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("finalizer run\n")
+ end
+
+ ruby_version_is "3.0" do
+ it "warns if the finalizer has the object as the receiver" do
+ code = <<-RUBY
+ class CapturesSelf
+ def initialize
+ ObjectSpace.define_finalizer(self, proc {
+ puts "finalizer run"
+ })
+ end
+ end
+ CapturesSelf.new
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("warning: finalizer references object to be finalized\n")
+ end
+
+ it "warns if the finalizer is a method bound to the receiver" do
+ code = <<-RUBY
+ class CapturesSelf
+ def initialize
+ ObjectSpace.define_finalizer(self, method(:finalize))
+ end
+ def finalize(id)
+ puts "finalizer run"
+ end
+ end
+ CapturesSelf.new
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("warning: finalizer references object to be finalized\n")
+ end
+
+ it "warns if the finalizer was a block in the receiver" do
+ code = <<-RUBY
+ class CapturesSelf
+ def initialize
+ ObjectSpace.define_finalizer(self) do
+ puts "finalizer run"
+ end
+ end
+ end
+ CapturesSelf.new
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("warning: finalizer references object to be finalized\n")
+ end
+ end
+
+ it "calls a finalizer at exit even if it is self-referencing" do
+ code = <<-RUBY
+ obj = "Test"
+ handler = Proc.new { puts "finalizer run" }
+ ObjectSpace.define_finalizer(obj, handler)
+ exit 0
+ RUBY
+
+ ruby_exe(code).should include("finalizer run\n")
+ end
+
+ it "calls a finalizer at exit even if it is indirectly self-referencing" do
+ code = <<-RUBY
+ class CapturesSelf
+ def initialize
+ ObjectSpace.define_finalizer(self, finalizer(self))
+ end
+ def finalizer(zelf)
+ proc do
+ puts "finalizer run"
+ end
+ end
+ end
+ CapturesSelf.new
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("finalizer run\n")
+ end
+
+ it "calls a finalizer defined in a finalizer running at exit" do
+ code = <<-RUBY
+ obj = "Test"
+ handler = Proc.new do
+ obj2 = "Test"
+ handler2 = Proc.new { puts "finalizer 2 run" }
+ ObjectSpace.define_finalizer(obj2, handler2)
+ exit 0
+ end
+ ObjectSpace.define_finalizer(obj, handler)
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("finalizer 2 run\n")
+ end
+
+ it "allows multiple finalizers with different 'callables' to be defined" do
+ code = <<-RUBY
+ obj = Object.new
+
+ ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized1\n" })
+ ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized2\n" })
+
+ exit 0
+ RUBY
+
+ ruby_exe(code).lines.sort.should == ["finalized1\n", "finalized2\n"]
+ end
+
+ ruby_version_is "3.1" do
+ describe "when $VERBOSE is not nil" do
+ it "warns if an exception is raised in finalizer" do
+ code = <<-RUBY
+ ObjectSpace.define_finalizer(Object.new) { raise "finalizing" }
+ RUBY
+
+ ruby_exe(code, args: "2>&1").should include("warning: Exception in finalizer", "finalizing")
+ end
+ end
+
+ describe "when $VERBOSE is nil" do
+ it "does not warn even if an exception is raised in finalizer" do
+ code = <<-RUBY
+ ObjectSpace.define_finalizer(Object.new) { raise "finalizing" }
+ RUBY
+
+ ruby_exe(code, args: "2>&1", options: "-W0").should == ""
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/each_object_spec.rb b/spec/ruby/core/objectspace/each_object_spec.rb
new file mode 100644
index 0000000000..09a582afaf
--- /dev/null
+++ b/spec/ruby/core/objectspace/each_object_spec.rb
@@ -0,0 +1,213 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "ObjectSpace.each_object" do
+ it "calls the block once for each living, non-immediate object in the Ruby process" do
+ klass = Class.new
+ new_obj = klass.new
+
+ yields = 0
+ count = ObjectSpace.each_object(klass) do |obj|
+ obj.should == new_obj
+ yields += 1
+ end
+ count.should == 1
+ yields.should == 1
+
+ # this is needed to prevent the new_obj from being GC'd too early
+ new_obj.should_not == nil
+ end
+
+ it "calls the block once for each class, module in the Ruby process" do
+ klass = Class.new
+ mod = Module.new
+
+ [klass, mod].each do |k|
+ yields = 0
+ got_it = false
+ count = ObjectSpace.each_object(k.class) do |obj|
+ got_it = true if obj == k
+ yields += 1
+ end
+ got_it.should == true
+ count.should == yields
+ end
+ end
+
+ it "returns an enumerator if not given a block" do
+ klass = Class.new
+ new_obj = klass.new
+
+ counter = ObjectSpace.each_object(klass)
+ counter.should be_an_instance_of(Enumerator)
+ counter.each{}.should == 1
+ # this is needed to prevent the new_obj from being GC'd too early
+ new_obj.should_not == nil
+ end
+
+ it "finds an object stored in a global variable" do
+ $object_space_global_variable = ObjectSpaceFixtures::ObjectToBeFound.new(:global)
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:global)
+ end
+
+ it "finds an object stored in a top-level constant" do
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:top_level_constant)
+ end
+
+ it "finds an object stored in a second-level constant" do
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:second_level_constant)
+ end
+
+ it "finds an object stored in a local variable" do
+ local = ObjectSpaceFixtures::ObjectToBeFound.new(:local)
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local)
+ end
+
+ it "finds an object stored in a local variable captured in a block explicitly" do
+ proc = Proc.new {
+ local_in_block = ObjectSpaceFixtures::ObjectToBeFound.new(:local_in_block_explicit)
+ Proc.new { local_in_block }
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local_in_block_explicit)
+ end
+
+ it "finds an object stored in a local variable captured in a block implicitly" do
+ proc = Proc.new {
+ local_in_block = ObjectSpaceFixtures::ObjectToBeFound.new(:local_in_block_implicit)
+ Proc.new { }
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local_in_block_implicit)
+ end
+
+ it "finds an object stored in a local variable captured in by a method defined with a block" do
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:captured_by_define_method)
+ end
+
+ it "finds an object stored in a local variable captured in a Proc#binding" do
+ binding = Proc.new {
+ local_in_proc_binding = ObjectSpaceFixtures::ObjectToBeFound.new(:local_in_proc_binding)
+ Proc.new { }.binding
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local_in_proc_binding)
+ end
+
+ it "finds an object stored in a local variable captured in a Kernel#binding" do
+ b = Proc.new {
+ local_in_kernel_binding = ObjectSpaceFixtures::ObjectToBeFound.new(:local_in_kernel_binding)
+ binding
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local_in_kernel_binding)
+ end
+
+ it "finds an object stored in a local variable set in a binding manually" do
+ b = binding
+ b.eval("local = ObjectSpaceFixtures::ObjectToBeFound.new(:local_in_manual_binding)")
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local_in_manual_binding)
+ end
+
+ it "finds an object stored in an array" do
+ array = [ObjectSpaceFixtures::ObjectToBeFound.new(:array)]
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:array)
+ end
+
+ it "finds an object stored in a hash key" do
+ hash = {ObjectSpaceFixtures::ObjectToBeFound.new(:hash_key) => :value}
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:hash_key)
+ end
+
+ it "finds an object stored in a hash value" do
+ hash = {a: ObjectSpaceFixtures::ObjectToBeFound.new(:hash_value)}
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:hash_value)
+ end
+
+ it "finds an object stored in an instance variable" do
+ local = ObjectSpaceFixtures::ObjectWithInstanceVariable.new
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:instance_variable)
+ end
+
+ it "finds an object stored in a thread local" do
+ thread = Thread.new {}
+ thread.thread_variable_set(:object_space_thread_local, ObjectSpaceFixtures::ObjectToBeFound.new(:thread_local))
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:thread_local)
+ thread.join
+ end
+
+ it "finds an object stored in a fiber local" do
+ Thread.current[:object_space_fiber_local] = ObjectSpaceFixtures::ObjectToBeFound.new(:fiber_local)
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:fiber_local)
+ end
+
+ it "finds an object captured in an at_exit handler" do
+ Proc.new {
+ local = ObjectSpaceFixtures::ObjectToBeFound.new(:at_exit)
+
+ at_exit do
+ local
+ end
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:at_exit)
+ end
+
+ it "finds an object captured in finalizer" do
+ alive = Object.new
+
+ Proc.new {
+ local = ObjectSpaceFixtures::ObjectToBeFound.new(:finalizer)
+
+ ObjectSpace.define_finalizer(alive, Proc.new {
+ local
+ })
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:finalizer)
+
+ alive.should_not be_nil
+ end
+
+ describe "on singleton classes" do
+ before :each do
+ @klass = Class.new
+ instance = @klass.new
+ @sclass = instance.singleton_class
+ @meta = @klass.singleton_class
+ end
+
+ it "does not walk hidden metaclasses" do
+ klass = Class.new.singleton_class
+ ancestors = ObjectSpace.each_object(Class).select { |c| klass.is_a? c }
+ hidden = ancestors.find { |h| h.inspect.include? klass.inspect }
+ hidden.should == nil
+ end
+
+ it "walks singleton classes" do
+ @sclass.should be_kind_of(@meta)
+ ObjectSpace.each_object(@meta).to_a.should include(@sclass)
+ end
+ end
+
+ it "walks a class and its normal descendants when passed the class's singleton class" do
+ a = Class.new
+ b = Class.new(a)
+ c = Class.new(a)
+ d = Class.new(b)
+
+ c_instance = c.new
+ c_sclass = c_instance.singleton_class
+
+ expected = [ a, b, c, d ]
+
+ expected << c_sclass
+ c_sclass.should be_kind_of(a.singleton_class)
+
+ b.extend Enumerable # included modules should not be walked
+
+ classes = ObjectSpace.each_object(a.singleton_class).to_a
+
+ classes.sort_by(&:object_id).should == expected.sort_by(&:object_id)
+ end
+end
diff --git a/spec/ruby/core/objectspace/finalizers_spec.rb b/spec/ruby/core/objectspace/finalizers_spec.rb
new file mode 100644
index 0000000000..e7f20fc8a0
--- /dev/null
+++ b/spec/ruby/core/objectspace/finalizers_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace.finalizers" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/objectspace/fixtures/classes.rb b/spec/ruby/core/objectspace/fixtures/classes.rb
new file mode 100644
index 0000000000..612156c180
--- /dev/null
+++ b/spec/ruby/core/objectspace/fixtures/classes.rb
@@ -0,0 +1,64 @@
+module ObjectSpaceFixtures
+ def self.garbage
+ blah
+ end
+
+ def self.blah
+ o = "hello"
+ @garbage_objid = o.object_id
+ return o
+ end
+
+ @last_objid = nil
+
+ def self.last_objid
+ @last_objid
+ end
+
+ def self.garbage_objid
+ @garbage_objid
+ end
+
+ def self.make_finalizer
+ proc { |obj_id| @last_objid = obj_id }
+ end
+
+ def self.define_finalizer
+ handler = -> obj { ScratchPad.record :finalized }
+ ObjectSpace.define_finalizer "#{rand 5}", handler
+ end
+
+ def self.scoped(wr)
+ return Proc.new { wr.write "finalized"; wr.close }
+ end
+
+ class ObjectToBeFound
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+ end
+
+ class ObjectWithInstanceVariable
+ def initialize
+ @instance_variable = ObjectToBeFound.new(:instance_variable)
+ end
+ end
+
+ def self.to_be_found_symbols
+ ObjectSpace.each_object(ObjectToBeFound).map do |o|
+ o.name
+ end
+ end
+
+ o = ObjectToBeFound.new(:captured_by_define_method)
+ define_method :capturing_method do
+ o
+ end
+
+ SECOND_LEVEL_CONSTANT = ObjectToBeFound.new(:second_level_constant)
+
+end
+
+OBJECT_SPACE_TOP_LEVEL_CONSTANT = ObjectSpaceFixtures::ObjectToBeFound.new(:top_level_constant)
diff --git a/spec/ruby/core/objectspace/garbage_collect_spec.rb b/spec/ruby/core/objectspace/garbage_collect_spec.rb
new file mode 100644
index 0000000000..521eaa8785
--- /dev/null
+++ b/spec/ruby/core/objectspace/garbage_collect_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace.garbage_collect" do
+
+ it "can be invoked without any exceptions" do
+ -> { ObjectSpace.garbage_collect }.should_not raise_error
+ end
+
+ it "accepts keyword arguments" do
+ ObjectSpace.garbage_collect(full_mark: true, immediate_sweep: true).should == nil
+ end
+
+ it "ignores the supplied block" do
+ -> { ObjectSpace.garbage_collect {} }.should_not raise_error
+ end
+
+ it "always returns nil" do
+ ObjectSpace.garbage_collect.should == nil
+ ObjectSpace.garbage_collect.should == nil
+ end
+
+end
diff --git a/spec/ruby/core/objectspace/remove_finalizer_spec.rb b/spec/ruby/core/objectspace/remove_finalizer_spec.rb
new file mode 100644
index 0000000000..0b2b8cf16b
--- /dev/null
+++ b/spec/ruby/core/objectspace/remove_finalizer_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace.remove_finalizer" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/objectspace/undefine_finalizer_spec.rb b/spec/ruby/core/objectspace/undefine_finalizer_spec.rb
new file mode 100644
index 0000000000..11d43121f8
--- /dev/null
+++ b/spec/ruby/core/objectspace/undefine_finalizer_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace.undefine_finalizer" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/objectspace/weakmap/each_key_spec.rb b/spec/ruby/core/objectspace/weakmap/each_key_spec.rb
new file mode 100644
index 0000000000..df971deeb9
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/each_key_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+require_relative 'shared/each'
+
+describe "ObjectSpace::WeakMap#each_key" do
+ it_behaves_like :weakmap_members, -> map { a = []; map.each_key{ |k| a << k }; a }, %w[A B]
+end
+
+describe "ObjectSpace::WeakMap#each_key" do
+ it_behaves_like :weakmap_each, :each_key
+end
diff --git a/spec/ruby/core/objectspace/weakmap/each_pair_spec.rb b/spec/ruby/core/objectspace/weakmap/each_pair_spec.rb
new file mode 100644
index 0000000000..ea29edbd2f
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/each_pair_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+require_relative 'shared/each'
+
+describe "ObjectSpace::WeakMap#each_pair" do
+ it_behaves_like :weakmap_members, -> map { a = []; map.each_pair{ |k,v| a << "#{k}#{v}" }; a }, %w[Ax By]
+end
+
+describe "ObjectSpace::WeakMap#each_key" do
+ it_behaves_like :weakmap_each, :each_pair
+end
diff --git a/spec/ruby/core/objectspace/weakmap/each_spec.rb b/spec/ruby/core/objectspace/weakmap/each_spec.rb
new file mode 100644
index 0000000000..46fcb66a6f
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/each_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+require_relative 'shared/each'
+
+describe "ObjectSpace::WeakMap#each" do
+ it_behaves_like :weakmap_members, -> map { a = []; map.each{ |k,v| a << "#{k}#{v}" }; a }, %w[Ax By]
+end
+
+describe "ObjectSpace::WeakMap#each_key" do
+ it_behaves_like :weakmap_each, :each
+end
diff --git a/spec/ruby/core/objectspace/weakmap/each_value_spec.rb b/spec/ruby/core/objectspace/weakmap/each_value_spec.rb
new file mode 100644
index 0000000000..65a1a7f6fe
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/each_value_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+require_relative 'shared/each'
+
+describe "ObjectSpace::WeakMap#each_value" do
+ it_behaves_like :weakmap_members, -> map { a = []; map.each_value{ |k| a << k }; a }, %w[x y]
+end
+
+describe "ObjectSpace::WeakMap#each_key" do
+ it_behaves_like :weakmap_each, :each_value
+end
diff --git a/spec/ruby/core/objectspace/weakmap/element_reference_spec.rb b/spec/ruby/core/objectspace/weakmap/element_reference_spec.rb
new file mode 100644
index 0000000000..cb3174cbfa
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/element_reference_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+describe "ObjectSpace::WeakMap#[]" do
+ it "is faithful to the map's content" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+ map[key1] = ref1
+ map[key1].should == ref1
+ map[key1] = ref1
+ map[key1].should == ref1
+ map[key2] = ref2
+ map[key1].should == ref1
+ map[key2].should == ref2
+ end
+
+ it "matches using identity semantics" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a a].map(&:upcase)
+ ref = "x"
+ map[key1] = ref
+ map[key2].should == nil
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/element_set_spec.rb b/spec/ruby/core/objectspace/weakmap/element_set_spec.rb
new file mode 100644
index 0000000000..8588877158
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/element_set_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../../spec_helper'
+
+describe "ObjectSpace::WeakMap#[]=" do
+ def should_accept(map, key, value)
+ (map[key] = value).should == value
+ map.should.key?(key)
+ map[key].should == value
+ end
+
+ it "is correct" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+ should_accept(map, key1, ref1)
+ should_accept(map, key1, ref1)
+ should_accept(map, key2, ref2)
+ map[key1].should == ref1
+ end
+
+ it "accepts primitive or frozen keys or values" do
+ map = ObjectSpace::WeakMap.new
+ x = Object.new
+ should_accept(map, true, x)
+ should_accept(map, false, x)
+ should_accept(map, nil, x)
+ should_accept(map, 42, x)
+ should_accept(map, :foo, x)
+
+ should_accept(map, x, true)
+ should_accept(map, x, false)
+ should_accept(map, x, 42)
+ should_accept(map, x, :foo)
+
+ y = Object.new.freeze
+ should_accept(map, x, y)
+ should_accept(map, y, x)
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/include_spec.rb b/spec/ruby/core/objectspace/weakmap/include_spec.rb
new file mode 100644
index 0000000000..54ca6b3030
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/include_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/include'
+
+describe "ObjectSpace::WeakMap#include?" do
+ it_behaves_like :weakmap_include?, :include?
+end
diff --git a/spec/ruby/core/objectspace/weakmap/inspect_spec.rb b/spec/ruby/core/objectspace/weakmap/inspect_spec.rb
new file mode 100644
index 0000000000..f064f6e3ea
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/inspect_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+
+describe "ObjectSpace::WeakMap#inspect" do
+ it "displays object pointers in output" do
+ map = ObjectSpace::WeakMap.new
+ # important to test with BasicObject (without Kernel) here to test edge cases
+ key1, key2 = [BasicObject.new, Object.new]
+ ref1, ref2 = [BasicObject.new, Object.new]
+ map.inspect.should =~ /\A\#<ObjectSpace::WeakMap:0x\h+>\z/
+ map[key1] = ref1
+ map.inspect.should =~ /\A\#<ObjectSpace::WeakMap:0x\h+: \#<BasicObject:0x\h+> => \#<BasicObject:0x\h+>>\z/
+ map[key1] = ref1
+ map.inspect.should =~ /\A\#<ObjectSpace::WeakMap:0x\h+: \#<BasicObject:0x\h+> => \#<BasicObject:0x\h+>>\z/
+ map[key2] = ref2
+
+ regexp1 = /\A\#<ObjectSpace::WeakMap:0x\h+: \#<BasicObject:0x\h+> => \#<BasicObject:0x\h+>, \#<Object:0x\h+> => \#<Object:0x\h+>>\z/
+ regexp2 = /\A\#<ObjectSpace::WeakMap:0x\h+: \#<Object:0x\h+> => \#<Object:0x\h+>, \#<BasicObject:0x\h+> => \#<BasicObject:0x\h+>>\z/
+ str = map.inspect
+ if str =~ regexp1
+ str.should =~ regexp1
+ else
+ str.should =~ regexp2
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/key_spec.rb b/spec/ruby/core/objectspace/weakmap/key_spec.rb
new file mode 100644
index 0000000000..999685ff95
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/key_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/include'
+
+describe "ObjectSpace::WeakMap#key?" do
+ it_behaves_like :weakmap_include?, :key?
+end
diff --git a/spec/ruby/core/objectspace/weakmap/keys_spec.rb b/spec/ruby/core/objectspace/weakmap/keys_spec.rb
new file mode 100644
index 0000000000..7b1494bdd7
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/keys_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+
+describe "ObjectSpace::WeakMap#keys" do
+ it_behaves_like :weakmap_members, -> map { map.keys }, %w[A B]
+end
diff --git a/spec/ruby/core/objectspace/weakmap/length_spec.rb b/spec/ruby/core/objectspace/weakmap/length_spec.rb
new file mode 100644
index 0000000000..3a935648b1
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/length_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/size'
+
+describe "ObjectSpace::WeakMap#length" do
+ it_behaves_like :weakmap_size, :length
+end
diff --git a/spec/ruby/core/objectspace/weakmap/member_spec.rb b/spec/ruby/core/objectspace/weakmap/member_spec.rb
new file mode 100644
index 0000000000..cefb190ce7
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/member_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/include'
+
+describe "ObjectSpace::WeakMap#member?" do
+ it_behaves_like :weakmap_include?, :member?
+end
diff --git a/spec/ruby/core/objectspace/weakmap/shared/each.rb b/spec/ruby/core/objectspace/weakmap/shared/each.rb
new file mode 100644
index 0000000000..3d43a19347
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/shared/each.rb
@@ -0,0 +1,10 @@
+describe :weakmap_each, shared: true do
+ it "must take a block, except when empty" do
+ map = ObjectSpace::WeakMap.new
+ key = "a".upcase
+ ref = "x"
+ map.send(@method).should == map
+ map[key] = ref
+ -> { map.send(@method) }.should raise_error(LocalJumpError)
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/shared/include.rb b/spec/ruby/core/objectspace/weakmap/shared/include.rb
new file mode 100644
index 0000000000..1770eeac8b
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/shared/include.rb
@@ -0,0 +1,30 @@
+describe :weakmap_include?, shared: true do
+ it "recognizes keys in use" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+
+ map[key1] = ref1
+ map.send(@method, key1).should == true
+ map[key1] = ref1
+ map.send(@method, key1).should == true
+ map[key2] = ref2
+ map.send(@method, key2).should == true
+ end
+
+ it "matches using identity semantics" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a a].map(&:upcase)
+ ref = "x"
+ map[key1] = ref
+ map.send(@method, key2).should == false
+ end
+
+ it "reports true if the pair exists and the value is nil" do
+ map = ObjectSpace::WeakMap.new
+ key = Object.new
+ map[key] = nil
+ map.size.should == 1
+ map.send(@method, key).should == true
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/shared/members.rb b/spec/ruby/core/objectspace/weakmap/shared/members.rb
new file mode 100644
index 0000000000..57226c8d7a
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/shared/members.rb
@@ -0,0 +1,14 @@
+describe :weakmap_members, shared: true do
+ it "is correct" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+ @method.call(map).should == []
+ map[key1] = ref1
+ @method.call(map).should == @object[0..0]
+ map[key1] = ref1
+ @method.call(map).should == @object[0..0]
+ map[key2] = ref2
+ @method.call(map).sort.should == @object
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/shared/size.rb b/spec/ruby/core/objectspace/weakmap/shared/size.rb
new file mode 100644
index 0000000000..1064f99d1b
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/shared/size.rb
@@ -0,0 +1,14 @@
+describe :weakmap_size, shared: true do
+ it "is correct" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+ map.send(@method).should == 0
+ map[key1] = ref1
+ map.send(@method).should == 1
+ map[key1] = ref1
+ map.send(@method).should == 1
+ map[key2] = ref2
+ map.send(@method).should == 2
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/size_spec.rb b/spec/ruby/core/objectspace/weakmap/size_spec.rb
new file mode 100644
index 0000000000..1446abaa24
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/size_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/size'
+
+describe "ObjectSpace::WeakMap#size" do
+ it_behaves_like :weakmap_size, :size
+end
diff --git a/spec/ruby/core/objectspace/weakmap/values_spec.rb b/spec/ruby/core/objectspace/weakmap/values_spec.rb
new file mode 100644
index 0000000000..6f6f90d0ba
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/values_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+
+describe "ObjectSpace::WeakMap#values" do
+ it_behaves_like :weakmap_members, -> map { map.values }, %w[x y]
+end
diff --git a/spec/ruby/core/objectspace/weakmap_spec.rb b/spec/ruby/core/objectspace/weakmap_spec.rb
new file mode 100644
index 0000000000..2f3f93c291
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace::WeakMap" do
+
+ # Note that we can't really spec the most important aspect of this class: that entries get removed when the values
+ # become unreachable. This is because Ruby does not offer a way to reliable invoke GC (GC.start is not enough, neither
+ # on MRI or on alternative implementations).
+
+ it "includes Enumerable" do
+ ObjectSpace::WeakMap.include?(Enumerable).should == true
+ end
+end
diff --git a/spec/ruby/core/proc/allocate_spec.rb b/spec/ruby/core/proc/allocate_spec.rb
new file mode 100644
index 0000000000..54e1b69df9
--- /dev/null
+++ b/spec/ruby/core/proc/allocate_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Proc.allocate" do
+ it "raises a TypeError" do
+ -> {
+ Proc.allocate
+ }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/proc/arity_spec.rb b/spec/ruby/core/proc/arity_spec.rb
new file mode 100644
index 0000000000..f7cb5ad0f8
--- /dev/null
+++ b/spec/ruby/core/proc/arity_spec.rb
@@ -0,0 +1,640 @@
+require_relative '../../spec_helper'
+
+describe "Proc#arity" do
+ SpecEvaluate.desc = "for definition"
+
+ context "for instances created with -> () { }" do
+ context "returns zero" do
+ evaluate <<-ruby do
+ @a = -> () {}
+ ruby
+
+ @a.arity.should == 0
+ end
+
+ evaluate <<-ruby do
+ @a = -> (&b) {}
+ ruby
+
+ @a.arity.should == 0
+ end
+ end
+
+ context "returns positive values" do
+ evaluate <<-ruby do
+ @a = -> (a) { }
+ @b = -> (a, b) { }
+ @c = -> (a, b, c) { }
+ @d = -> (a, b, c, d) { }
+ ruby
+
+ @a.arity.should == 1
+ @b.arity.should == 2
+ @c.arity.should == 3
+ @d.arity.should == 4
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a:) { }
+ @b = -> (a:, b:) { }
+ @c = -> (a: 1, b:, c:, d: 2) { }
+ ruby
+
+ @a.arity.should == 1
+ @b.arity.should == 1
+ @c.arity.should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, b:) { }
+ @b = -> (a, b:, &l) { }
+ ruby
+
+ @a.arity.should == 2
+ @b.arity.should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, b, c:, d: 1) { }
+ @b = -> (a, b, c:, d: 1, **k, &l) { }
+ ruby
+
+ @a.arity.should == 3
+ @b.arity.should == 3
+ end
+
+ evaluate <<-ruby do
+ @a = -> ((a, (*b, c))) { }
+ @b = -> (a, (*b, c), d, (*e), (*)) { }
+ ruby
+
+ @a.arity.should == 1
+ @b.arity.should == 5
+ end
+ end
+
+ context "returns negative values" do
+ evaluate <<-ruby do
+ @a = -> (a=1) { }
+ @b = -> (a=1, b=2) { }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, b=1) { }
+ @b = -> (a, b, c=1, d=2) { }
+ ruby
+
+ @a.arity.should == -2
+ @b.arity.should == -3
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a=1, *b) { }
+ @b = -> (a=1, b=2, *c) { }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (*) { }
+ @b = -> (*a) { }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, *) { }
+ @b = -> (a, *b) { }
+ @c = -> (a, b, *c) { }
+ @d = -> (a, b, c, *d) { }
+ ruby
+
+ @a.arity.should == -2
+ @b.arity.should == -2
+ @c.arity.should == -3
+ @d.arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ @a = -> (*a, b) { }
+ @b = -> (*a, b, c) { }
+ @c = -> (*a, b, c, d) { }
+ ruby
+
+ @a.arity.should == -2
+ @b.arity.should == -3
+ @c.arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, *b, c) { }
+ @b = -> (a, b, *c, d, e) { }
+ ruby
+
+ @a.arity.should == -3
+ @b.arity.should == -5
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, b=1, c=2, *d, e, f) { }
+ @b = -> (a, b, c=1, *d, e, f, g) { }
+ ruby
+
+ @a.arity.should == -4
+ @b.arity.should == -6
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a: 1) { }
+ @b = -> (a: 1, b: 2) { }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a=1, b: 2) { }
+ @b = -> (*a, b: 1) { }
+ @c = -> (a=1, b: 2) { }
+ @d = -> (a=1, *b, c: 2, &l) { }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ @c.arity.should == -1
+ @d.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (**k, &l) { }
+ @b= -> (*a, **k) { }
+ @c = ->(a: 1, b: 2, **k) { }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ @c.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a=1, *b, c:, d: 2, **k, &l) { }
+ ruby
+
+ @a.arity.should == -2
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, b=1, *c, d, e:, f: 2, **k, &l) { }
+ @b = -> (a, b=1, *c, d:, e:, f: 2, **k, &l) { }
+ @c = -> (a=0, b=1, *c, d, e:, f: 2, **k, &l) { }
+ @d = -> (a=0, b=1, *c, d:, e:, f: 2, **k, &l) { }
+ ruby
+
+ @a.arity.should == -4
+ @b.arity.should == -3
+ @c.arity.should == -3
+ @d.arity.should == -2
+ end
+ end
+ end
+
+ context "for instances created with lambda { || }" do
+ context "returns zero" do
+ evaluate <<-ruby do
+ @a = lambda { }
+ @b = lambda { || }
+ ruby
+
+ @a.arity.should == 0
+ @b.arity.should == 0
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |&b| }
+ ruby
+
+ @a.arity.should == 0
+ end
+ end
+
+ context "returns positive values" do
+ evaluate <<-ruby do
+ @a = lambda { |a| }
+ @b = lambda { |a, b| }
+ @c = lambda { |a, b, c| }
+ @d = lambda { |a, b, c, d| }
+ ruby
+
+ @a.arity.should == 1
+ @b.arity.should == 2
+ @c.arity.should == 3
+ @d.arity.should == 4
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a:| }
+ @b = lambda { |a:, b:| }
+ @c = lambda { |a: 1, b:, c:, d: 2| }
+ ruby
+
+ @a.arity.should == 1
+ @b.arity.should == 1
+ @c.arity.should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, b:| }
+ @b = lambda { |a, b:, &l| }
+ ruby
+
+ @a.arity.should == 2
+ @b.arity.should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, b, c:, d: 1| }
+ @b = lambda { |a, b, c:, d: 1, **k, &l| }
+ ruby
+
+ @a.arity.should == 3
+ @b.arity.should == 3
+ end
+ end
+
+ context "returns negative values" do
+ evaluate <<-ruby do
+ @a = lambda { |a=1| }
+ @b = lambda { |a=1, b=2| }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, b=1| }
+ @b = lambda { |a, b, c=1, d=2| }
+ ruby
+
+ @a.arity.should == -2
+ @b.arity.should == -3
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a=1, *b| }
+ @b = lambda { |a=1, b=2, *c| }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |*| }
+ @b = lambda { |*a| }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, *| }
+ @b = lambda { |a, *b| }
+ @c = lambda { |a, b, *c| }
+ @d = lambda { |a, b, c, *d| }
+ ruby
+
+ @a.arity.should == -2
+ @b.arity.should == -2
+ @c.arity.should == -3
+ @d.arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |*a, b| }
+ @b = lambda { |*a, b, c| }
+ @c = lambda { |*a, b, c, d| }
+ ruby
+
+ @a.arity.should == -2
+ @b.arity.should == -3
+ @c.arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, *b, c| }
+ @b = lambda { |a, b, *c, d, e| }
+ ruby
+
+ @a.arity.should == -3
+ @b.arity.should == -5
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, b=1, c=2, *d, e, f| }
+ @b = lambda { |a, b, c=1, *d, e, f, g| }
+ ruby
+
+ @a.arity.should == -4
+ @b.arity.should == -6
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a: 1| }
+ @b = lambda { |a: 1, b: 2| }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a=1, b: 2| }
+ @b = lambda { |*a, b: 1| }
+ @c = lambda { |a=1, b: 2| }
+ @d = lambda { |a=1, *b, c: 2, &l| }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ @c.arity.should == -1
+ @d.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |**k, &l| }
+ @b = lambda { |*a, **k| }
+ @c = lambda { |a: 1, b: 2, **k| }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ @c.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a=1, *b, c:, d: 2, **k, &l| }
+ ruby
+
+ @a.arity.should == -2
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |(a, (*b, c)), d=1| }
+ @b = lambda { |a, (*b, c), d, (*e), (*), **k| }
+ @c = lambda { |a, (b, c), *, d:, e: 2, **| }
+ ruby
+
+ @a.arity.should == -2
+ @b.arity.should == -6
+ @c.arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, b=1, *c, d, e:, f: 2, **k, &l| }
+ @b = lambda { |a, b=1, *c, d:, e:, f: 2, **k, &l| }
+ @c = lambda { |a=0, b=1, *c, d, e:, f: 2, **k, &l| }
+ @d = lambda { |a=0, b=1, *c, d:, e:, f: 2, **k, &l| }
+ ruby
+
+ @a.arity.should == -4
+ @b.arity.should == -3
+ @c.arity.should == -3
+ @d.arity.should == -2
+ end
+ end
+ end
+
+ context "for instances created with proc { || }" do
+ context "returns zero" do
+ evaluate <<-ruby do
+ @a = proc { }
+ @b = proc { || }
+ ruby
+
+ @a.arity.should == 0
+ @b.arity.should == 0
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |&b| }
+ ruby
+
+ @a.arity.should == 0
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a=1| }
+ @b = proc { |a=1, b=2| }
+ ruby
+
+ @a.arity.should == 0
+ @b.arity.should == 0
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a: 1| }
+ @b = proc { |a: 1, b: 2| }
+ ruby
+
+ @a.arity.should == 0
+ @b.arity.should == 0
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |**k, &l| }
+ @b = proc { |a: 1, b: 2, **k| }
+ ruby
+
+ @a.arity.should == 0
+ @b.arity.should == 0
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a=1, b: 2| }
+ @b = proc { |a=1, b: 2| }
+ ruby
+
+ @a.arity.should == 0
+ @b.arity.should == 0
+ end
+ end
+
+ context "returns positive values" do
+ evaluate <<-ruby do
+ @a = proc { |a| }
+ @b = proc { |a, b| }
+ @c = proc { |a, b, c| }
+ @d = proc { |a, b, c, d| }
+ ruby
+
+ @a.arity.should == 1
+ @b.arity.should == 2
+ @c.arity.should == 3
+ @d.arity.should == 4
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a, b=1| }
+ @b = proc { |a, b, c=1, d=2| }
+ ruby
+
+ @a.arity.should == 1
+ @b.arity.should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a:| }
+ @b = lambda { |a:, b:| }
+ @c = lambda { |a: 1, b:, c:, d: 2| }
+ ruby
+
+ @a.arity.should == 1
+ @b.arity.should == 1
+ @c.arity.should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a, b:| }
+ @b = proc { |a, b:, &l| }
+ ruby
+
+ @a.arity.should == 2
+ @b.arity.should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a, b, c:, d: 1| }
+ @b = proc { |a, b, c:, d: 1, **k, &l| }
+ ruby
+
+ @a.arity.should == 3
+ @b.arity.should == 3
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |(a, (*b, c)), d=1| }
+ @b = proc { |a, (*b, c), d, (*e), (*), **k| }
+ ruby
+
+ @a.arity.should == 1
+ @b.arity.should == 5
+ end
+ end
+
+ context "returns negative values" do
+ evaluate <<-ruby do
+ @a = proc { |a=1, *b| }
+ @b = proc { |a=1, b=2, *c| }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |*| }
+ @b = proc { |*a| }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a, *| }
+ @b = proc { |a, *b| }
+ @c = proc { |a, b, *c| }
+ @d = proc { |a, b, c, *d| }
+ ruby
+
+ @a.arity.should == -2
+ @b.arity.should == -2
+ @c.arity.should == -3
+ @d.arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |*a, b| }
+ @b = proc { |*a, b, c| }
+ @c = proc { |*a, b, c, d| }
+ ruby
+
+ @a.arity.should == -2
+ @b.arity.should == -3
+ @c.arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a, *b, c| }
+ @b = proc { |a, b, *c, d, e| }
+ ruby
+
+ @a.arity.should == -3
+ @b.arity.should == -5
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a, b=1, c=2, *d, e, f| }
+ @b = proc { |a, b, c=1, *d, e, f, g| }
+ ruby
+
+ @a.arity.should == -4
+ @b.arity.should == -6
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |*a, b: 1| }
+ @b = proc { |a=1, *b, c: 2, &l| }
+ ruby
+
+ @a.arity.should == -1
+ @b.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |*a, **k| }
+ ruby
+
+ @a.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a=1, *b, c:, d: 2, **k, &l| }
+ ruby
+
+ @a.arity.should == -2
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a, (b, c), *, d:, e: 2, **| }
+ ruby
+
+ @a.arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ @a = proc { |a, b=1, *c, d, e:, f: 2, **k, &l| }
+ @b = proc { |a, b=1, *c, d:, e:, f: 2, **k, &l| }
+ @c = proc { |a=0, b=1, *c, d, e:, f: 2, **k, &l| }
+ @d = proc { |a=0, b=1, *c, d:, e:, f: 2, **k, &l| }
+ ruby
+
+ @a.arity.should == -4
+ @b.arity.should == -3
+ @c.arity.should == -3
+ @d.arity.should == -2
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/proc/binding_spec.rb b/spec/ruby/core/proc/binding_spec.rb
new file mode 100644
index 0000000000..86ab6bd400
--- /dev/null
+++ b/spec/ruby/core/proc/binding_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Proc#binding" do
+ it "returns a Binding instance" do
+ [Proc.new{}, -> {}, proc {}].each { |p|
+ p.binding.should be_kind_of(Binding)
+ }
+ end
+
+ it "returns the binding associated with self" do
+ obj = mock('binding')
+ def obj.test_binding(some, params)
+ -> {}
+ end
+
+ lambdas_binding = obj.test_binding(1, 2).binding
+
+ eval("some", lambdas_binding).should == 1
+ eval("params", lambdas_binding).should == 2
+ end
+end
diff --git a/spec/ruby/core/proc/block_pass_spec.rb b/spec/ruby/core/proc/block_pass_spec.rb
new file mode 100644
index 0000000000..411c0bf3db
--- /dev/null
+++ b/spec/ruby/core/proc/block_pass_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Proc as a block pass argument" do
+ def revivify(&b)
+ b
+ end
+
+ it "remains the same object if re-vivified by the target method" do
+ p = Proc.new {}
+ p2 = revivify(&p)
+ p.should equal p2
+ p.should == p2
+ end
+
+ it "remains the same object if reconstructed with Proc.new" do
+ p = Proc.new {}
+ p2 = Proc.new(&p)
+ p.should equal p2
+ p.should == p2
+ end
+end
diff --git a/spec/ruby/core/proc/call_spec.rb b/spec/ruby/core/proc/call_spec.rb
new file mode 100644
index 0000000000..6ec2fc8682
--- /dev/null
+++ b/spec/ruby/core/proc/call_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'shared/call'
+require_relative 'shared/call_arguments'
+
+describe "Proc#call" do
+ it_behaves_like :proc_call, :call
+ it_behaves_like :proc_call_block_args, :call
+end
+
+describe "Proc#call on a Proc created with Proc.new" do
+ it_behaves_like :proc_call_on_proc_new, :call
+end
+
+describe "Proc#call on a Proc created with Kernel#lambda or Kernel#proc" do
+ it_behaves_like :proc_call_on_proc_or_lambda, :call
+end
diff --git a/spec/ruby/core/proc/case_compare_spec.rb b/spec/ruby/core/proc/case_compare_spec.rb
new file mode 100644
index 0000000000..f11513cdb9
--- /dev/null
+++ b/spec/ruby/core/proc/case_compare_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'shared/call'
+require_relative 'shared/call_arguments'
+
+describe "Proc#===" do
+ it_behaves_like :proc_call, :===
+ it_behaves_like :proc_call_block_args, :===
+end
+
+describe "Proc#=== on a Proc created with Proc.new" do
+ it_behaves_like :proc_call_on_proc_new, :===
+end
+
+describe "Proc#=== on a Proc created with Kernel#lambda or Kernel#proc" do
+ it_behaves_like :proc_call_on_proc_or_lambda, :===
+end
diff --git a/spec/ruby/core/proc/clone_spec.rb b/spec/ruby/core/proc/clone_spec.rb
new file mode 100644
index 0000000000..a1a1292654
--- /dev/null
+++ b/spec/ruby/core/proc/clone_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/dup'
+
+describe "Proc#clone" do
+ it_behaves_like :proc_dup, :clone
+end
diff --git a/spec/ruby/core/proc/compose_spec.rb b/spec/ruby/core/proc/compose_spec.rb
new file mode 100644
index 0000000000..94814d11bc
--- /dev/null
+++ b/spec/ruby/core/proc/compose_spec.rb
@@ -0,0 +1,162 @@
+require_relative '../../spec_helper'
+require_relative 'shared/compose'
+
+describe "Proc#<<" do
+ it "returns a Proc that is the composition of self and the passed Proc" do
+ upcase = proc { |s| s.upcase }
+ succ = proc { |s| s.succ }
+
+ (succ << upcase).call('Ruby').should == "RUBZ"
+ end
+
+ it "calls passed Proc with arguments and then calls self with result" do
+ f = proc { |x| x * x }
+ g = proc { |x| x + x }
+
+ (f << g).call(2).should == 16
+ (g << f).call(2).should == 8
+ end
+
+ it "accepts any callable object" do
+ inc = proc { |n| n + 1 }
+
+ double = Object.new
+ def double.call(n); n * 2; end
+
+ (inc << double).call(3).should == 7
+ end
+
+ it_behaves_like :proc_compose, :<<, -> { proc { |s| s.upcase } }
+
+ describe "composition" do
+ it "is a Proc" do
+ f = proc { |x| x * x }
+ g = proc { |x| x + x }
+
+ (f << g).is_a?(Proc).should == true
+ (f << g).should_not.lambda?
+ end
+
+ ruby_version_is(''...'3.0') do
+ it "is a Proc when other is lambda" do
+ f = proc { |x| x * x }
+ g = -> x { x + x }
+
+ (f << g).is_a?(Proc).should == true
+ (f << g).should_not.lambda?
+ end
+
+ it "is a lambda when self is lambda" do
+ f = -> x { x * x }
+ g = proc { |x| x + x }
+
+ (f << g).is_a?(Proc).should == true
+ (f << g).should.lambda?
+ end
+ end
+
+ ruby_version_is('3.0') do
+ it "is a lambda when parameter is lambda" do
+ f = -> x { x * x }
+ g = proc { |x| x + x }
+ lambda_proc = -> x { x }
+
+ # lambda << proc
+ (f << g).is_a?(Proc).should == true
+ (f << g).should_not.lambda?
+
+ # lambda << lambda
+ (f << lambda_proc).is_a?(Proc).should == true
+ (f << lambda_proc).should.lambda?
+
+ # proc << lambda
+ (g << f).is_a?(Proc).should == true
+ (g << f).should.lambda?
+ end
+ end
+
+ it "may accept multiple arguments" do
+ inc = proc { |n| n + 1 }
+ mul = proc { |n, m| n * m }
+
+ (inc << mul).call(2, 3).should == 7
+ end
+
+ it "passes blocks to the second proc" do
+ ScratchPad.record []
+ one = proc { |&arg| arg.call :one if arg }
+ two = proc { |&arg| arg.call :two if arg }
+ (one << two).call { |x| ScratchPad << x }
+ ScratchPad.recorded.should == [:two]
+ end
+ end
+end
+
+describe "Proc#>>" do
+ it "returns a Proc that is the composition of self and the passed Proc" do
+ upcase = proc { |s| s.upcase }
+ succ = proc { |s| s.succ }
+
+ (succ >> upcase).call('Ruby').should == "RUBZ"
+ end
+
+ it "calls passed Proc with arguments and then calls self with result" do
+ f = proc { |x| x * x }
+ g = proc { |x| x + x }
+
+ (f >> g).call(2).should == 8
+ (g >> f).call(2).should == 16
+ end
+
+ it "accepts any callable object" do
+ inc = proc { |n| n + 1 }
+
+ double = Object.new
+ def double.call(n); n * 2; end
+
+ (inc >> double).call(3).should == 8
+ end
+
+ it_behaves_like :proc_compose, :>>, -> { proc { |s| s.upcase } }
+
+ describe "composition" do
+ it "is a Proc" do
+ f = proc { |x| x * x }
+ g = proc { |x| x + x }
+
+ (f >> g).is_a?(Proc).should == true
+ (f >> g).should_not.lambda?
+ end
+
+ it "is a Proc when other is lambda" do
+ f = proc { |x| x * x }
+ g = -> x { x + x }
+
+ (f >> g).is_a?(Proc).should == true
+ (f >> g).should_not.lambda?
+ end
+
+ it "is a lambda when self is lambda" do
+ f = -> x { x * x }
+ g = proc { |x| x + x }
+
+ (f >> g).is_a?(Proc).should == true
+ (f >> g).should.lambda?
+ end
+
+ it "may accept multiple arguments" do
+ inc = proc { |n| n + 1 }
+ mul = proc { |n, m| n * m }
+
+ (mul >> inc).call(2, 3).should == 7
+ end
+
+ it "passes blocks to the first proc" do
+ ScratchPad.record []
+ one = proc { |&arg| arg.call :one if arg }
+ two = proc { |&arg| arg.call :two if arg }
+ (one >> two).call { |x| ScratchPad << x }
+ ScratchPad.recorded.should == [:one]
+ end
+ end
+end
diff --git a/spec/ruby/core/proc/curry_spec.rb b/spec/ruby/core/proc/curry_spec.rb
new file mode 100644
index 0000000000..24df2a8a72
--- /dev/null
+++ b/spec/ruby/core/proc/curry_spec.rb
@@ -0,0 +1,180 @@
+require_relative '../../spec_helper'
+
+describe "Proc#curry" do
+ before :each do
+ @proc_add = Proc.new {|x,y,z| (x||0) + (y||0) + (z||0) }
+ @lambda_add = -> x, y, z { (x||0) + (y||0) + (z||0) }
+ end
+
+ it "returns a Proc when called on a proc" do
+ p = proc { true }
+ p.curry.should be_an_instance_of(Proc)
+ end
+
+ it "returns a Proc when called on a lambda" do
+ p = -> { true }
+ p.curry.should be_an_instance_of(Proc)
+ end
+
+ it "calls the curried proc with the arguments if sufficient arguments have been given" do
+ @proc_add.curry[1][2][3].should == 6
+ @lambda_add.curry[1][2][3].should == 6
+ end
+
+ it "returns a Proc that consumes the remainder of the arguments unless sufficient arguments have been given" do
+ proc2 = @proc_add.curry[1][2]
+ proc2.should be_an_instance_of(Proc)
+ proc2.call(3).should == 6
+
+ lambda2 = @lambda_add.curry[1][2]
+ lambda2.should be_an_instance_of(Proc)
+ lambda2.call(3).should == 6
+
+ @proc_add.curry.call(1,2,3).should == 6
+ @lambda_add.curry.call(1,2,3).should == 6
+ end
+
+ it "can be called multiple times on the same Proc" do
+ @proc_add.curry
+ -> { @proc_add.curry }.should_not raise_error
+
+ @lambda_add.curry
+ -> { @lambda_add.curry }.should_not raise_error
+ end
+
+ it "can be passed superfluous arguments if created from a proc" do
+ @proc_add.curry[1,2,3,4].should == 6
+
+ @proc_add.curry[1,2].curry[3,4,5,6].should == 6
+ end
+
+ it "raises an ArgumentError if passed superfluous arguments when created from a lambda" do
+ -> { @lambda_add.curry[1,2,3,4] }.should raise_error(ArgumentError)
+ -> { @lambda_add.curry[1,2].curry[3,4,5,6] }.should raise_error(ArgumentError)
+ end
+
+ it "returns Procs with arities of -1" do
+ @proc_add.curry.arity.should == -1
+ @lambda_add.curry.arity.should == -1
+ l = -> *a { }
+ l.curry.arity.should == -1
+ end
+
+ it "produces Procs that raise ArgumentError for #binding" do
+ -> do
+ @proc_add.curry.binding
+ end.should raise_error(ArgumentError)
+ end
+
+ it "produces Procs that return [[:rest]] for #parameters" do
+ @proc_add.curry.parameters.should == [[:rest]]
+ end
+
+ it "produces Procs that return nil for #source_location" do
+ @proc_add.curry.source_location.should == nil
+ end
+
+ it "produces Procs that can be passed as the block for instance_exec" do
+ curried = @proc_add.curry.call(1, 2)
+
+ instance_exec(3, &curried).should == 6
+ end
+
+ it "combines arguments and calculates incoming arity accurately for successively currying" do
+ l = -> a, b, c { a+b+c }
+ l1 = l.curry.call(1)
+ # the l1 currying seems unnecessary, but it triggered the original issue
+ l2 = l1.curry.call(2)
+
+ l2.curry.call(3).should == 6
+ l1.curry.call(2,3).should == 6
+ end
+end
+
+describe "Proc#curry with arity argument" do
+ before :each do
+ @proc_add = proc { |x,y,z| (x||0) + (y||0) + (z||0) }
+ @lambda_add = -> x, y, z { (x||0) + (y||0) + (z||0) }
+ end
+
+ it "accepts an optional Integer argument for the arity" do
+ -> { @proc_add.curry(3) }.should_not raise_error
+ -> { @lambda_add.curry(3) }.should_not raise_error
+ end
+
+ it "returns a Proc when called on a proc" do
+ @proc_add.curry(3).should be_an_instance_of(Proc)
+ end
+
+ it "returns a Proc when called on a lambda" do
+ @lambda_add.curry(3).should be_an_instance_of(Proc)
+ end
+
+ # [ruby-core:24127]
+ it "retains the lambda-ness of the Proc on which its called" do
+ @lambda_add.curry(3).lambda?.should be_true
+ @proc_add.curry(3).lambda?.should be_false
+ end
+
+ it "raises an ArgumentError if called on a lambda that requires more than _arity_ arguments" do
+ -> { @lambda_add.curry(2) }.should raise_error(ArgumentError)
+ -> { -> x, y, z, *more{}.curry(2) }.should raise_error(ArgumentError)
+ end
+
+ it 'returns a Proc if called on a lambda that requires fewer than _arity_ arguments but may take more' do
+ -> a, b, c, d=nil, e=nil {}.curry(4).should be_an_instance_of(Proc)
+ -> a, b, c, d=nil, *e {}.curry(4).should be_an_instance_of(Proc)
+ -> a, b, c, *d {}.curry(4).should be_an_instance_of(Proc)
+ end
+
+ it "raises an ArgumentError if called on a lambda that requires fewer than _arity_ arguments" do
+ -> { @lambda_add.curry(4) }.should raise_error(ArgumentError)
+ -> { -> { true }.curry(1) }.should raise_error(ArgumentError)
+ -> { -> a, b=nil {}.curry(5) }.should raise_error(ArgumentError)
+ -> { -> a, &b {}.curry(2) }.should raise_error(ArgumentError)
+ -> { -> a, b=nil, &c {}.curry(3) }.should raise_error(ArgumentError)
+ end
+
+ it "calls the curried proc with the arguments if _arity_ arguments have been given" do
+ @proc_add.curry(3)[1][2][3].should == 6
+ @lambda_add.curry(3)[1][2][3].should == 6
+ end
+
+ it "returns a Proc that consumes the remainder of the arguments when fewer than _arity_ arguments are given" do
+ proc2 = @proc_add.curry(3)[1][2]
+ proc2.should be_an_instance_of(Proc)
+ proc2.call(3).should == 6
+
+ lambda2 = @lambda_add.curry(3)[1][2]
+ lambda2.should be_an_instance_of(Proc)
+ lambda2.call(3).should == 6
+ end
+
+ it "can be specified multiple times on the same Proc" do
+ @proc_add.curry(2)
+ -> { @proc_add.curry(1) }.should_not raise_error
+
+ @lambda_add.curry(3)
+ -> { @lambda_add.curry(3) }.should_not raise_error
+ end
+
+ it "can be passed more than _arity_ arguments if created from a proc" do
+ -> { @proc_add.curry(3)[1,2,3,4].should == 6 }.should_not
+ raise_error(ArgumentError)
+ -> { @proc_add.curry(1)[1,2].curry(3)[3,4,5,6].should == 6 }.should_not
+ raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed more than _arity_ arguments when created from a lambda" do
+ -> { @lambda_add.curry(3)[1,2,3,4] }.should raise_error(ArgumentError)
+ -> { @lambda_add.curry(1)[1,2].curry(3)[3,4,5,6] }.should raise_error(ArgumentError)
+ end
+
+ it "returns Procs with arities of -1 regardless of the value of _arity_" do
+ @proc_add.curry(1).arity.should == -1
+ @proc_add.curry(2).arity.should == -1
+ @lambda_add.curry(3).arity.should == -1
+ l = -> *a { }
+ l.curry(3).arity.should == -1
+ end
+end
diff --git a/spec/ruby/core/proc/dup_spec.rb b/spec/ruby/core/proc/dup_spec.rb
new file mode 100644
index 0000000000..6da2f3080c
--- /dev/null
+++ b/spec/ruby/core/proc/dup_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/dup'
+
+describe "Proc#dup" do
+ it_behaves_like :proc_dup, :dup
+end
diff --git a/spec/ruby/core/proc/element_reference_spec.rb b/spec/ruby/core/proc/element_reference_spec.rb
new file mode 100644
index 0000000000..9077e44c34
--- /dev/null
+++ b/spec/ruby/core/proc/element_reference_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'shared/call'
+require_relative 'shared/call_arguments'
+require_relative 'fixtures/proc_aref'
+require_relative 'fixtures/proc_aref_frozen'
+
+describe "Proc#[]" do
+ it_behaves_like :proc_call, :[]
+ it_behaves_like :proc_call_block_args, :[]
+end
+
+describe "Proc#call on a Proc created with Proc.new" do
+ it_behaves_like :proc_call_on_proc_new, :call
+end
+
+describe "Proc#call on a Proc created with Kernel#lambda or Kernel#proc" do
+ it_behaves_like :proc_call_on_proc_or_lambda, :call
+end
+
+describe "Proc#[] with frozen_string_literals" do
+ it "doesn't duplicate frozen strings" do
+ ProcArefSpecs.aref.frozen?.should be_false
+ ProcArefSpecs.aref_freeze.frozen?.should be_true
+ ProcArefFrozenSpecs.aref.frozen?.should be_true
+ ProcArefFrozenSpecs.aref_freeze.frozen?.should be_true
+ end
+end
diff --git a/spec/ruby/core/proc/eql_spec.rb b/spec/ruby/core/proc/eql_spec.rb
new file mode 100644
index 0000000000..06aee272e5
--- /dev/null
+++ b/spec/ruby/core/proc/eql_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal'
+
+describe "Proc#eql?" do
+ ruby_version_is ""..."3.0" do
+ it_behaves_like :proc_equal_undefined, :eql?
+ end
+
+ ruby_version_is "3.0" do
+ it_behaves_like :proc_equal, :eql?
+ end
+end
diff --git a/spec/ruby/core/proc/equal_value_spec.rb b/spec/ruby/core/proc/equal_value_spec.rb
new file mode 100644
index 0000000000..ee88c0537d
--- /dev/null
+++ b/spec/ruby/core/proc/equal_value_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal'
+
+describe "Proc#==" do
+ ruby_version_is ""..."3.0" do
+ it_behaves_like :proc_equal_undefined, :==
+ end
+
+ ruby_version_is "3.0" do
+ it_behaves_like :proc_equal, :==
+ end
+end
diff --git a/spec/ruby/core/proc/fixtures/common.rb b/spec/ruby/core/proc/fixtures/common.rb
new file mode 100644
index 0000000000..6e27a2dee7
--- /dev/null
+++ b/spec/ruby/core/proc/fixtures/common.rb
@@ -0,0 +1,51 @@
+module ProcSpecs
+ class ToAryAsNil
+ def to_ary
+ nil
+ end
+ end
+ def self.new_proc_in_method
+ Proc.new
+ end
+
+ def self.new_proc_from_amp(&block)
+ block
+ end
+
+ def self.proc_for_1
+ proc { 1 }
+ end
+
+ class ProcSubclass < Proc
+ end
+
+ def self.new_proc_subclass_in_method
+ ProcSubclass.new
+ end
+
+ class MyProc < Proc
+ end
+
+ class MyProc2 < Proc
+ def initialize(a, b)
+ @first = a
+ @second = b
+ end
+
+ attr_reader :first, :second
+ end
+
+ class Arity
+ def arity_check(&block)
+ pn = Proc.new(&block).arity
+ pr = proc(&block).arity
+ lm = lambda(&block).arity
+
+ if pn == pr and pr == lm
+ return pn
+ else
+ return :arity_check_failed
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/proc/fixtures/proc_aref.rb b/spec/ruby/core/proc/fixtures/proc_aref.rb
new file mode 100644
index 0000000000..a305667797
--- /dev/null
+++ b/spec/ruby/core/proc/fixtures/proc_aref.rb
@@ -0,0 +1,9 @@
+module ProcArefSpecs
+ def self.aref
+ proc {|a| a }["sometext"]
+ end
+
+ def self.aref_freeze
+ proc {|a| a }["sometext".freeze]
+ end
+end
diff --git a/spec/ruby/core/proc/fixtures/proc_aref_frozen.rb b/spec/ruby/core/proc/fixtures/proc_aref_frozen.rb
new file mode 100644
index 0000000000..50a330ba4f
--- /dev/null
+++ b/spec/ruby/core/proc/fixtures/proc_aref_frozen.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+module ProcArefFrozenSpecs
+ def self.aref
+ proc {|a| a }["sometext"]
+ end
+
+ def self.aref_freeze
+ proc {|a| a }["sometext".freeze]
+ end
+end
diff --git a/spec/ruby/core/proc/fixtures/source_location.rb b/spec/ruby/core/proc/fixtures/source_location.rb
new file mode 100644
index 0000000000..5572094630
--- /dev/null
+++ b/spec/ruby/core/proc/fixtures/source_location.rb
@@ -0,0 +1,55 @@
+module ProcSpecs
+ class SourceLocation
+ def self.my_proc
+ proc { true }
+ end
+
+ def self.my_lambda
+ -> { true }
+ end
+
+ def self.my_proc_new
+ Proc.new { true }
+ end
+
+ def self.my_method
+ method(__method__).to_proc
+ end
+
+ def self.my_multiline_proc
+ proc do
+ 'a'.upcase
+ 1 + 22
+ end
+ end
+
+ def self.my_multiline_lambda
+ -> do
+ 'a'.upcase
+ 1 + 22
+ end
+ end
+
+ def self.my_multiline_proc_new
+ Proc.new do
+ 'a'.upcase
+ 1 + 22
+ end
+ end
+
+ def self.my_detached_proc
+ body = proc { true }
+ proc(&body)
+ end
+
+ def self.my_detached_lambda
+ body = -> { true }
+ suppress_warning {lambda(&body)}
+ end
+
+ def self.my_detached_proc_new
+ body = Proc.new { true }
+ Proc.new(&body)
+ end
+ end
+end
diff --git a/spec/ruby/core/proc/hash_spec.rb b/spec/ruby/core/proc/hash_spec.rb
new file mode 100644
index 0000000000..ebe0fde1a0
--- /dev/null
+++ b/spec/ruby/core/proc/hash_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "Proc#hash" do
+ it "is provided" do
+ proc {}.respond_to?(:hash).should be_true
+ -> {}.respond_to?(:hash).should be_true
+ end
+
+ it "returns an Integer" do
+ proc { 1 + 489 }.hash.should be_kind_of(Integer)
+ end
+
+ it "is stable" do
+ body = proc { :foo }
+ proc(&body).hash.should == proc(&body).hash
+ end
+end
diff --git a/spec/ruby/core/proc/inspect_spec.rb b/spec/ruby/core/proc/inspect_spec.rb
new file mode 100644
index 0000000000..f53d34116f
--- /dev/null
+++ b/spec/ruby/core/proc/inspect_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Proc#inspect" do
+ it_behaves_like :proc_to_s, :inspect
+end
diff --git a/spec/ruby/core/proc/lambda_spec.rb b/spec/ruby/core/proc/lambda_spec.rb
new file mode 100644
index 0000000000..b2d3f50350
--- /dev/null
+++ b/spec/ruby/core/proc/lambda_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Proc#lambda?" do
+ it "returns true if the Proc was created from a block with the lambda keyword" do
+ -> {}.lambda?.should be_true
+ end
+
+ it "returns false if the Proc was created from a block with the proc keyword" do
+ proc {}.lambda?.should be_false
+ end
+
+ it "returns false if the Proc was created from a block with Proc.new" do
+ Proc.new {}.lambda?.should be_false
+ end
+
+ it "is preserved when passing a Proc with & to the lambda keyword" do
+ suppress_warning {lambda(&->{})}.lambda?.should be_true
+ suppress_warning {lambda(&proc{})}.lambda?.should be_false
+ end
+
+ it "is preserved when passing a Proc with & to the proc keyword" do
+ proc(&->{}).lambda?.should be_true
+ proc(&proc{}).lambda?.should be_false
+ end
+
+ it "is preserved when passing a Proc with & to Proc.new" do
+ Proc.new(&->{}).lambda?.should be_true
+ Proc.new(&proc{}).lambda?.should be_false
+ end
+
+ it "returns false if the Proc was created from a block with &" do
+ ProcSpecs.new_proc_from_amp{}.lambda?.should be_false
+ end
+
+ it "is preserved when the Proc was passed using &" do
+ ProcSpecs.new_proc_from_amp(&->{}).lambda?.should be_true
+ ProcSpecs.new_proc_from_amp(&proc{}).lambda?.should be_false
+ ProcSpecs.new_proc_from_amp(&Proc.new{}).lambda?.should be_false
+ end
+
+ it "returns true for a Method converted to a Proc" do
+ m = :foo.method(:to_s)
+ m.to_proc.lambda?.should be_true
+ ProcSpecs.new_proc_from_amp(&m).lambda?.should be_true
+ end
+
+ # [ruby-core:24127]
+ it "is preserved when a Proc is curried" do
+ ->{}.curry.lambda?.should be_true
+ proc{}.curry.lambda?.should be_false
+ Proc.new{}.curry.lambda?.should be_false
+ end
+
+ it "is preserved when a curried Proc is called without enough arguments" do
+ -> x, y{}.curry.call(42).lambda?.should be_true
+ proc{|x,y|}.curry.call(42).lambda?.should be_false
+ Proc.new{|x,y|}.curry.call(42).lambda?.should be_false
+ end
+end
diff --git a/spec/ruby/core/proc/new_spec.rb b/spec/ruby/core/proc/new_spec.rb
new file mode 100644
index 0000000000..cb52e94f44
--- /dev/null
+++ b/spec/ruby/core/proc/new_spec.rb
@@ -0,0 +1,201 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Proc.new with an associated block" do
+ it "returns a proc that represents the block" do
+ Proc.new { }.call.should == nil
+ Proc.new { "hello" }.call.should == "hello"
+ end
+
+ describe "called on a subclass of Proc" do
+ before :each do
+ @subclass = Class.new(Proc) do
+ attr_reader :ok
+ def initialize
+ @ok = true
+ super
+ end
+ end
+ end
+
+ it "returns an instance of the subclass" do
+ proc = @subclass.new {"hello"}
+
+ proc.class.should == @subclass
+ proc.call.should == "hello"
+ proc.ok.should == true
+ end
+
+ # JRUBY-5026
+ describe "using a reified block parameter" do
+ it "returns an instance of the subclass" do
+ cls = Class.new do
+ def self.subclass=(subclass)
+ @subclass = subclass
+ end
+ def self.foo(&block)
+ @subclass.new(&block)
+ end
+ end
+ cls.subclass = @subclass
+ proc = cls.foo {"hello"}
+
+ proc.class.should == @subclass
+ proc.call.should == "hello"
+ proc.ok.should == true
+ end
+ end
+ end
+
+ # JRUBY-5261; Proc sets up the block during .new, not in #initialize
+ describe "called on a subclass of Proc that does not 'super' in 'initialize'" do
+ before :each do
+ @subclass = Class.new(Proc) do
+ attr_reader :ok
+ def initialize
+ @ok = true
+ end
+ end
+ end
+
+ it "still constructs a functional proc" do
+ proc = @subclass.new {'ok'}
+ proc.call.should == 'ok'
+ proc.ok.should == true
+ end
+ end
+
+ it "raises a LocalJumpError when context of the block no longer exists" do
+ def some_method
+ Proc.new { return }
+ end
+ res = some_method()
+
+ -> { res.call }.should raise_error(LocalJumpError)
+ end
+
+ it "returns from within enclosing method when 'return' is used in the block" do
+ # we essentially verify that the created instance behaves like proc,
+ # not like lambda.
+ def some_method
+ Proc.new { return :proc_return_value }.call
+ :method_return_value
+ end
+ some_method.should == :proc_return_value
+ end
+
+ it "returns a subclass of Proc" do
+ obj = ProcSpecs::MyProc.new { }
+ obj.should be_kind_of(ProcSpecs::MyProc)
+ end
+
+ it "calls initialize on the Proc object" do
+ obj = ProcSpecs::MyProc2.new(:a, 2) { }
+ obj.first.should == :a
+ obj.second.should == 2
+ end
+end
+
+describe "Proc.new with a block argument" do
+ it "returns the passed proc created from a block" do
+ passed_prc = Proc.new { "hello".size }
+ prc = Proc.new(&passed_prc)
+
+ prc.should equal(passed_prc)
+ prc.call.should == 5
+ end
+
+ it "returns the passed proc created from a method" do
+ method = "hello".method(:size)
+ passed_prc = Proc.new(&method)
+ prc = Proc.new(&passed_prc)
+
+ prc.should equal(passed_prc)
+ prc.call.should == 5
+ end
+
+ it "returns the passed proc created from a symbol" do
+ passed_prc = Proc.new(&:size)
+ prc = Proc.new(&passed_prc)
+
+ prc.should equal(passed_prc)
+ prc.call("hello").should == 5
+ end
+end
+
+describe "Proc.new with a block argument called indirectly from a subclass" do
+ it "returns the passed proc created from a block" do
+ passed_prc = ProcSpecs::MyProc.new { "hello".size }
+ passed_prc.class.should == ProcSpecs::MyProc
+ prc = ProcSpecs::MyProc.new(&passed_prc)
+
+ prc.should equal(passed_prc)
+ prc.call.should == 5
+ end
+
+ it "returns the passed proc created from a method" do
+ method = "hello".method(:size)
+ passed_prc = ProcSpecs::MyProc.new(&method)
+ passed_prc.class.should == ProcSpecs::MyProc
+ prc = ProcSpecs::MyProc.new(&passed_prc)
+
+ prc.should equal(passed_prc)
+ prc.call.should == 5
+ end
+
+ it "returns the passed proc created from a symbol" do
+ passed_prc = ProcSpecs::MyProc.new(&:size)
+ passed_prc.class.should == ProcSpecs::MyProc
+ prc = ProcSpecs::MyProc.new(&passed_prc)
+
+ prc.should equal(passed_prc)
+ prc.call("hello").should == 5
+ end
+end
+
+describe "Proc.new without a block" do
+ it "raises an ArgumentError" do
+ -> { Proc.new }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if invoked from within a method with no block" do
+ -> { ProcSpecs.new_proc_in_method }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if invoked on a subclass from within a method with no block" do
+ -> { ProcSpecs.new_proc_subclass_in_method }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "can be created if invoked from within a method with a block" do
+ -> { ProcSpecs.new_proc_in_method { "hello" } }.should complain(/Capturing the given block using Proc.new is deprecated/)
+ end
+
+ it "can be created if invoked on a subclass from within a method with a block" do
+ -> { ProcSpecs.new_proc_subclass_in_method { "hello" } }.should complain(/Capturing the given block using Proc.new is deprecated/)
+ end
+
+
+ it "can be create when called with no block" do
+ def some_method
+ Proc.new
+ end
+
+ -> {
+ some_method { "hello" }
+ }.should complain(/Capturing the given block using Proc.new is deprecated/)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "raises an ArgumentError when passed no block" do
+ def some_method
+ Proc.new
+ end
+
+ -> { ProcSpecs.new_proc_in_method { "hello" } }.should raise_error(ArgumentError, 'tried to create Proc object without a block')
+ -> { ProcSpecs.new_proc_subclass_in_method { "hello" } }.should raise_error(ArgumentError, 'tried to create Proc object without a block')
+ -> { some_method { "hello" } }.should raise_error(ArgumentError, 'tried to create Proc object without a block')
+ end
+ end
+end
diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb
new file mode 100644
index 0000000000..3a56b613cd
--- /dev/null
+++ b/spec/ruby/core/proc/parameters_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../../spec_helper'
+
+describe "Proc#parameters" do
+ it "returns an empty Array for a proc expecting no parameters" do
+ proc {}.parameters.should == []
+ end
+
+ it "returns an Array of Arrays for a proc expecting parameters" do
+ p = proc {|x| }
+ p.parameters.should be_an_instance_of(Array)
+ p.parameters.first.should be_an_instance_of(Array)
+ end
+
+ it "sets the first element of each sub-Array to :opt for optional arguments" do
+ proc {|x| }.parameters.first.first.should == :opt
+ proc {|y,*x| }.parameters.first.first.should == :opt
+ end
+
+ it "regards named parameters in procs as optional" do
+ proc {|x| }.parameters.first.first.should == :opt
+ end
+
+ ruby_version_is "3.2" do
+ it "sets the first element of each sub-Array to :req if argument would be required if a lambda if lambda keyword used" do
+ proc {|x| }.parameters(lambda: true).first.first.should == :req
+ proc {|y,*x| }.parameters(lambda: true).first.first.should == :req
+ end
+
+ it "regards named parameters in procs as required if lambda keyword used" do
+ proc {|x| }.parameters(lambda: true).first.first.should == :req
+ end
+
+ it "regards named parameters in lambda as optional if lambda: false keyword used" do
+ -> x { }.parameters(lambda: false).first.first.should == :opt
+ end
+ end
+
+ it "regards optional keyword parameters in procs as optional" do
+ proc {|x: :y| }.parameters.first.first.should == :key
+ end
+
+ it "regards parameters with default values as optional" do
+ -> x=1 { }.parameters.first.first.should == :opt
+ proc {|x=1| }.parameters.first.first.should == :opt
+ end
+
+ it "sets the first element of each sub-Array to :req for required arguments" do
+ -> x, y=[] { }.parameters.first.first.should == :req
+ -> y, *x { }.parameters.first.first.should == :req
+ end
+
+ it "regards named parameters in lambdas as required" do
+ -> x { }.parameters.first.first.should == :req
+ end
+
+ it "regards keyword parameters in lambdas as required" do
+ eval("lambda {|x:| }").parameters.first.first.should == :keyreq
+ end
+
+ it "sets the first element of each sub-Array to :rest for parameters prefixed with asterisks" do
+ -> *x { }.parameters.first.first.should == :rest
+ -> x, *y { }.parameters.last.first.should == :rest
+ proc {|*x| }.parameters.first.first.should == :rest
+ proc {|x,*y| }.parameters.last.first.should == :rest
+ end
+
+ it "sets the first element of each sub-Array to :keyrest for parameters prefixed with double asterisks" do
+ -> **x { }.parameters.first.first.should == :keyrest
+ -> x, **y { }.parameters.last.first.should == :keyrest
+ proc {|**x| }.parameters.first.first.should == :keyrest
+ proc {|x,**y| }.parameters.last.first.should == :keyrest
+ end
+
+ it "sets the first element of each sub-Array to :block for parameters prefixed with ampersands" do
+ -> &x { }.parameters.first.first.should == :block
+ -> x, &y { }.parameters.last.first.should == :block
+ proc {|&x| }.parameters.first.first.should == :block
+ proc {|x,&y| }.parameters.last.first.should == :block
+ end
+
+ it "sets the second element of each sub-Array to the name of the argument" do
+ -> x { }.parameters.first.last.should == :x
+ -> x=Math::PI { }.parameters.first.last.should == :x
+ -> an_argument, glark, &foo { }.parameters[1].last.should == :glark
+ -> *rest { }.parameters.first.last.should == :rest
+ -> &block { }.parameters.first.last.should == :block
+ proc {|x| }.parameters.first.last.should == :x
+ proc {|x=Math::PI| }.parameters.first.last.should == :x
+ proc {|an_argument, glark, &foo| }.parameters[1].last.should == :glark
+ proc {|*rest| }.parameters.first.last.should == :rest
+ proc {|&block| }.parameters.first.last.should == :block
+ end
+
+ it "ignores unnamed rest args" do
+ -> x {}.parameters.should == [[:req, :x]]
+ end
+
+ ruby_version_is '3.2' do
+ it "adds * rest arg for \"star\" argument" do
+ -> x, * {}.parameters.should == [[:req, :x], [:rest, :*]]
+ end
+ end
+
+ ruby_version_is ''...'3.2' do
+ it "adds nameless rest arg for \"star\" argument" do
+ -> x, * {}.parameters.should == [[:req, :x], [:rest]]
+ end
+ end
+
+ it "does not add locals as block options with a block and splat" do
+ -> *args, &blk do
+ local_is_not_parameter = {}
+ end.parameters.should == [[:rest, :args], [:block, :blk]]
+ proc do |*args, &blk|
+ local_is_not_parameter = {}
+ end.parameters.should == [[:rest, :args], [:block, :blk]]
+ end
+end
diff --git a/spec/ruby/core/proc/ruby2_keywords_spec.rb b/spec/ruby/core/proc/ruby2_keywords_spec.rb
new file mode 100644
index 0000000000..c6eb03e693
--- /dev/null
+++ b/spec/ruby/core/proc/ruby2_keywords_spec.rb
@@ -0,0 +1,78 @@
+require_relative '../../spec_helper'
+
+describe "Proc#ruby2_keywords" do
+ it "marks the final hash argument as keyword hash" do
+ f = -> *a { a.last }
+ f.ruby2_keywords
+
+ last = f.call(1, 2, a: "a")
+ Hash.ruby2_keywords_hash?(last).should == true
+ end
+
+ it "applies to the underlying method and applies across duplication" do
+ f1 = -> *a { a.last }
+ f1.ruby2_keywords
+ f2 = f1.dup
+
+ Hash.ruby2_keywords_hash?(f1.call(1, 2, a: "a")).should == true
+ Hash.ruby2_keywords_hash?(f2.call(1, 2, a: "a")).should == true
+
+ f3 = -> *a { a.last }
+ f4 = f3.dup
+ f3.ruby2_keywords
+
+ Hash.ruby2_keywords_hash?(f3.call(1, 2, a: "a")).should == true
+ Hash.ruby2_keywords_hash?(f4.call(1, 2, a: "a")).should == true
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "fixes delegation warnings when calling a method accepting keywords" do
+ obj = Object.new
+ def obj.foo(*a, **b) end
+
+ f = -> *a { obj.foo(*a) }
+
+ -> { f.call(1, 2, {a: "a"}) }.should complain(/Using the last argument as keyword parameters is deprecated/)
+ f.ruby2_keywords
+ -> { f.call(1, 2, {a: "a"}) }.should_not complain
+ end
+
+ it "fixes delegation warnings when calling a proc accepting keywords" do
+ g = -> *a, **b { }
+ f = -> *a { g.call(*a) }
+
+ -> { f.call(1, 2, {a: "a"}) }.should complain(/Using the last argument as keyword parameters is deprecated/)
+ f.ruby2_keywords
+ -> { f.call(1, 2, {a: "a"}) }.should_not complain
+ end
+ end
+
+ it "returns self" do
+ f = -> *a { }
+ f.ruby2_keywords.should equal f
+ end
+
+ it "prints warning when a proc does not accept argument splat" do
+ f = -> a, b, c { }
+
+ -> {
+ f.ruby2_keywords
+ }.should complain(/Skipping set of ruby2_keywords flag for/)
+ end
+
+ it "prints warning when a proc accepts keywords" do
+ f = -> a:, b: { }
+
+ -> {
+ f.ruby2_keywords
+ }.should complain(/Skipping set of ruby2_keywords flag for/)
+ end
+
+ it "prints warning when a proc accepts keyword splat" do
+ f = -> **a { }
+
+ -> {
+ f.ruby2_keywords
+ }.should complain(/Skipping set of ruby2_keywords flag for/)
+ end
+end
diff --git a/spec/ruby/core/proc/shared/call.rb b/spec/ruby/core/proc/shared/call.rb
new file mode 100644
index 0000000000..dbec34df4b
--- /dev/null
+++ b/spec/ruby/core/proc/shared/call.rb
@@ -0,0 +1,99 @@
+require_relative '../fixtures/common'
+
+describe :proc_call, shared: true do
+ it "invokes self" do
+ Proc.new { "test!" }.send(@method).should == "test!"
+ -> { "test!" }.send(@method).should == "test!"
+ proc { "test!" }.send(@method).should == "test!"
+ end
+
+ it "sets self's parameters to the given values" do
+ Proc.new { |a, b| a + b }.send(@method, 1, 2).should == 3
+ Proc.new { |*args| args }.send(@method, 1, 2, 3, 4).should == [1, 2, 3, 4]
+ Proc.new { |_, *args| args }.send(@method, 1, 2, 3).should == [2, 3]
+
+ -> a, b { a + b }.send(@method, 1, 2).should == 3
+ -> *args { args }.send(@method, 1, 2, 3, 4).should == [1, 2, 3, 4]
+ -> _, *args { args }.send(@method, 1, 2, 3).should == [2, 3]
+
+ proc { |a, b| a + b }.send(@method, 1, 2).should == 3
+ proc { |*args| args }.send(@method, 1, 2, 3, 4).should == [1, 2, 3, 4]
+ proc { |_, *args| args }.send(@method, 1, 2, 3).should == [2, 3]
+ end
+end
+
+
+describe :proc_call_on_proc_new, shared: true do
+ it "replaces missing arguments with nil" do
+ Proc.new { |a, b| [a, b] }.send(@method).should == [nil, nil]
+ Proc.new { |a, b| [a, b] }.send(@method, 1).should == [1, nil]
+ end
+
+ it "silently ignores extra arguments" do
+ Proc.new { |a, b| a + b }.send(@method, 1, 2, 5).should == 3
+ end
+
+ it "auto-explodes a single Array argument" do
+ p = Proc.new { |a, b| [a, b] }
+ p.send(@method, 1, 2).should == [1, 2]
+ p.send(@method, [1, 2]).should == [1, 2]
+ p.send(@method, [1, 2, 3]).should == [1, 2]
+ p.send(@method, [1, 2, 3], 4).should == [[1, 2, 3], 4]
+ end
+end
+
+describe :proc_call_on_proc_or_lambda, shared: true do
+ it "ignores excess arguments when self is a proc" do
+ a = proc {|x| x}.send(@method, 1, 2)
+ a.should == 1
+
+ a = proc {|x| x}.send(@method, 1, 2, 3)
+ a.should == 1
+
+ a = proc {|x:| x}.send(@method, 2, x: 1)
+ a.should == 1
+ end
+
+ it "will call #to_ary on argument and return self if return is nil" do
+ argument = ProcSpecs::ToAryAsNil.new
+ result = proc { |x, _| x }.send(@method, argument)
+ result.should == argument
+ end
+
+ it "substitutes nil for missing arguments when self is a proc" do
+ proc {|x,y| [x,y]}.send(@method).should == [nil,nil]
+
+ a = proc {|x,y| [x, y]}.send(@method, 1)
+ a.should == [1,nil]
+ end
+
+ it "raises an ArgumentError on excess arguments when self is a lambda" do
+ -> {
+ -> x { x }.send(@method, 1, 2)
+ }.should raise_error(ArgumentError)
+
+ -> {
+ -> x { x }.send(@method, 1, 2, 3)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError on missing arguments when self is a lambda" do
+ -> {
+ -> x { x }.send(@method)
+ }.should raise_error(ArgumentError)
+
+ -> {
+ -> x, y { [x,y] }.send(@method, 1)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "treats a single Array argument as a single argument when self is a lambda" do
+ -> a { a }.send(@method, [1, 2]).should == [1, 2]
+ -> a, b { [a, b] }.send(@method, [1, 2], 3).should == [[1,2], 3]
+ end
+
+ it "treats a single Array argument as a single argument when self is a proc" do
+ proc { |a| a }.send(@method, [1, 2]).should == [1, 2]
+ proc { |a, b| [a, b] }.send(@method, [1, 2], 3).should == [[1,2], 3]
+ end
+end
diff --git a/spec/ruby/core/proc/shared/call_arguments.rb b/spec/ruby/core/proc/shared/call_arguments.rb
new file mode 100644
index 0000000000..91ada3439e
--- /dev/null
+++ b/spec/ruby/core/proc/shared/call_arguments.rb
@@ -0,0 +1,29 @@
+describe :proc_call_block_args, shared: true do
+ it "can receive block arguments" do
+ Proc.new {|&b| b.send(@method)}.send(@method) {1 + 1}.should == 2
+ -> &b { b.send(@method)}.send(@method) {1 + 1}.should == 2
+ proc {|&b| b.send(@method)}.send(@method) {1 + 1}.should == 2
+ end
+
+ it "yields to the block given at declaration and not to the block argument" do
+ proc_creator = Object.new
+ def proc_creator.create
+ Proc.new do |&b|
+ yield
+ end
+ end
+ a_proc = proc_creator.create { 7 }
+ a_proc.send(@method) { 3 }.should == 7
+ end
+
+ it "can call its block argument declared with a block argument" do
+ proc_creator = Object.new
+ def proc_creator.create(method_name)
+ Proc.new do |&b|
+ yield + b.send(method_name)
+ end
+ end
+ a_proc = proc_creator.create(@method) { 7 }
+ a_proc.call { 3 }.should == 10
+ end
+end
diff --git a/spec/ruby/core/proc/shared/compose.rb b/spec/ruby/core/proc/shared/compose.rb
new file mode 100644
index 0000000000..3d3f3b310d
--- /dev/null
+++ b/spec/ruby/core/proc/shared/compose.rb
@@ -0,0 +1,22 @@
+describe :proc_compose, shared: true do
+ it "raises TypeError if passed not callable object" do
+ lhs = @object.call
+ not_callable = Object.new
+
+ -> {
+ lhs.send(@method, not_callable)
+ }.should raise_error(TypeError, "callable object is expected")
+
+ end
+
+ it "does not try to coerce argument with #to_proc" do
+ lhs = @object.call
+
+ succ = Object.new
+ def succ.to_proc(s); s.succ; end
+
+ -> {
+ lhs.send(@method, succ)
+ }.should raise_error(TypeError, "callable object is expected")
+ end
+end
diff --git a/spec/ruby/core/proc/shared/dup.rb b/spec/ruby/core/proc/shared/dup.rb
new file mode 100644
index 0000000000..eda1d6929d
--- /dev/null
+++ b/spec/ruby/core/proc/shared/dup.rb
@@ -0,0 +1,10 @@
+describe :proc_dup, shared: true do
+ it "returns a copy of self" do
+ a = -> { "hello" }
+ b = a.send(@method)
+
+ a.should_not equal(b)
+
+ a.call.should == b.call
+ end
+end
diff --git a/spec/ruby/core/proc/shared/equal.rb b/spec/ruby/core/proc/shared/equal.rb
new file mode 100644
index 0000000000..0c0020ca7f
--- /dev/null
+++ b/spec/ruby/core/proc/shared/equal.rb
@@ -0,0 +1,100 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe :proc_equal, shared: true do
+ it "is a public method" do
+ Proc.should have_public_instance_method(@method, false)
+ end
+
+ it "returns true if self and other are the same object" do
+ p = proc { :foo }
+ p.send(@method, p).should be_true
+
+ p = Proc.new { :foo }
+ p.send(@method, p).should be_true
+
+ p = -> { :foo }
+ p.send(@method, p).should be_true
+ end
+
+ it "returns true if other is a dup of the original" do
+ p = proc { :foo }
+ p.send(@method, p.dup).should be_true
+
+ p = Proc.new { :foo }
+ p.send(@method, p.dup).should be_true
+
+ p = -> { :foo }
+ p.send(@method, p.dup).should be_true
+ end
+
+ # identical here means the same method invocation.
+ it "returns false when bodies are the same but capture env is not identical" do
+ a = ProcSpecs.proc_for_1
+ b = ProcSpecs.proc_for_1
+
+ a.send(@method, b).should be_false
+ end
+
+ it "returns false if procs are distinct but have the same body and environment" do
+ p = proc { :foo }
+ p2 = proc { :foo }
+ p.send(@method, p2).should be_false
+ end
+
+ it "returns false if lambdas are distinct but have same body and environment" do
+ x = -> { :foo }
+ x2 = -> { :foo }
+ x.send(@method, x2).should be_false
+ end
+
+ it "returns false if using comparing lambda to proc, even with the same body and env" do
+ p = -> { :foo }
+ p2 = proc { :foo }
+ p.send(@method, p2).should be_false
+
+ x = proc { :bar }
+ x2 = -> { :bar }
+ x.send(@method, x2).should be_false
+ end
+
+ it "returns false if other is not a Proc" do
+ p = proc { :foo }
+ p.send(@method, []).should be_false
+
+ p = Proc.new { :foo }
+ p.send(@method, Object.new).should be_false
+
+ p = -> { :foo }
+ p.send(@method, :foo).should be_false
+ end
+
+ it "returns false if self and other are both procs but have different bodies" do
+ p = proc { :bar }
+ p2 = proc { :foo }
+ p.send(@method, p2).should be_false
+ end
+
+ it "returns false if self and other are both lambdas but have different bodies" do
+ p = -> { :foo }
+ p2 = -> { :bar }
+ p.send(@method, p2).should be_false
+ end
+end
+
+describe :proc_equal_undefined, shared: true do
+ it "is not defined" do
+ Proc.should_not have_instance_method(@method, false)
+ end
+
+ it "returns false if other is a dup of the original" do
+ p = proc { :foo }
+ p.send(@method, p.dup).should be_false
+
+ p = Proc.new { :foo }
+ p.send(@method, p.dup).should be_false
+
+ p = -> { :foo }
+ p.send(@method, p.dup).should be_false
+ end
+end
diff --git a/spec/ruby/core/proc/shared/to_s.rb b/spec/ruby/core/proc/shared/to_s.rb
new file mode 100644
index 0000000000..f1e2f416fc
--- /dev/null
+++ b/spec/ruby/core/proc/shared/to_s.rb
@@ -0,0 +1,60 @@
+describe :proc_to_s, shared: true do
+ describe "for a proc created with Proc.new" do
+ it "returns a description including file and line number" do
+ Proc.new { "hello" }.send(@method).should =~ /^#<Proc:([^ ]*?) #{Regexp.escape __FILE__}:#{__LINE__ }>$/
+ end
+
+ it "has a binary encoding" do
+ Proc.new { "hello" }.send(@method).encoding.should == Encoding::BINARY
+ end
+ end
+
+ describe "for a proc created with lambda" do
+ it "returns a description including '(lambda)' and including file and line number" do
+ -> { "hello" }.send(@method).should =~ /^#<Proc:([^ ]*?) #{Regexp.escape __FILE__}:#{__LINE__ } \(lambda\)>$/
+ end
+
+ it "has a binary encoding" do
+ -> { "hello" }.send(@method).encoding.should == Encoding::BINARY
+ end
+ end
+
+ describe "for a proc created with proc" do
+ it "returns a description including file and line number" do
+ proc { "hello" }.send(@method).should =~ /^#<Proc:([^ ]*?) #{Regexp.escape __FILE__}:#{__LINE__ }>$/
+ end
+
+ it "has a binary encoding" do
+ proc { "hello" }.send(@method).encoding.should == Encoding::BINARY
+ end
+ end
+
+ describe "for a proc created with UnboundMethod#to_proc" do
+ it "returns a description including '(lambda)' and optionally including file and line number" do
+ def hello; end
+ s = method("hello").to_proc.send(@method)
+ if s.include? __FILE__
+ s.should =~ /^#<Proc:([^ ]*?) #{Regexp.escape __FILE__}:#{__LINE__ - 3} \(lambda\)>$/
+ else
+ s.should =~ /^#<Proc:([^ ]*?) \(lambda\)>$/
+ end
+ end
+
+ it "has a binary encoding" do
+ def hello; end
+ method("hello").to_proc.send(@method).encoding.should == Encoding::BINARY
+ end
+ end
+
+ describe "for a proc created with Symbol#to_proc" do
+ it "returns a description including '(&:symbol)'" do
+ proc = :foobar.to_proc
+ proc.send(@method).should.include?('(&:foobar)')
+ end
+
+ it "has a binary encoding" do
+ proc = :foobar.to_proc
+ proc.send(@method).encoding.should == Encoding::BINARY
+ end
+ end
+end
diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb
new file mode 100644
index 0000000000..f268499b82
--- /dev/null
+++ b/spec/ruby/core/proc/source_location_spec.rb
@@ -0,0 +1,86 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/source_location'
+
+describe "Proc#source_location" do
+ before :each do
+ @proc = ProcSpecs::SourceLocation.my_proc
+ @lambda = ProcSpecs::SourceLocation.my_lambda
+ @proc_new = ProcSpecs::SourceLocation.my_proc_new
+ @method = ProcSpecs::SourceLocation.my_method
+ end
+
+ it "returns an Array" do
+ @proc.source_location.should be_an_instance_of(Array)
+ @proc_new.source_location.should be_an_instance_of(Array)
+ @lambda.source_location.should be_an_instance_of(Array)
+ @method.source_location.should be_an_instance_of(Array)
+ end
+
+ it "sets the first value to the path of the file in which the proc was defined" do
+ file = @proc.source_location.first
+ file.should be_an_instance_of(String)
+ file.should == File.realpath('../fixtures/source_location.rb', __FILE__)
+
+ file = @proc_new.source_location.first
+ file.should be_an_instance_of(String)
+ file.should == File.realpath('../fixtures/source_location.rb', __FILE__)
+
+ file = @lambda.source_location.first
+ file.should be_an_instance_of(String)
+ file.should == File.realpath('../fixtures/source_location.rb', __FILE__)
+
+ file = @method.source_location.first
+ file.should be_an_instance_of(String)
+ file.should == File.realpath('../fixtures/source_location.rb', __FILE__)
+ end
+
+ it "sets the last value to an Integer representing the line on which the proc was defined" do
+ line = @proc.source_location.last
+ line.should be_an_instance_of(Integer)
+ line.should == 4
+
+ line = @proc_new.source_location.last
+ line.should be_an_instance_of(Integer)
+ line.should == 12
+
+ line = @lambda.source_location.last
+ line.should be_an_instance_of(Integer)
+ line.should == 8
+
+ line = @method.source_location.last
+ line.should be_an_instance_of(Integer)
+ line.should == 15
+ end
+
+ it "works even if the proc was created on the same line" do
+ proc { true }.source_location.should == [__FILE__, __LINE__]
+ Proc.new { true }.source_location.should == [__FILE__, __LINE__]
+ -> { true }.source_location.should == [__FILE__, __LINE__]
+ end
+
+ it "returns the first line of a multi-line proc (i.e. the line containing 'proc do')" do
+ ProcSpecs::SourceLocation.my_multiline_proc.source_location.last.should == 20
+ ProcSpecs::SourceLocation.my_multiline_proc_new.source_location.last.should == 34
+ ProcSpecs::SourceLocation.my_multiline_lambda.source_location.last.should == 27
+ end
+
+ it "returns the location of the proc's body; not necessarily the proc itself" do
+ ProcSpecs::SourceLocation.my_detached_proc.source_location.last.should == 41
+ ProcSpecs::SourceLocation.my_detached_proc_new.source_location.last.should == 51
+ ProcSpecs::SourceLocation.my_detached_lambda.source_location.last.should == 46
+ end
+
+ it "returns the same value for a proc-ified method as the method reports" do
+ method = ProcSpecs::SourceLocation.method(:my_proc)
+ proc = method.to_proc
+
+ method.source_location.should == proc.source_location
+ end
+
+ it "returns nil for a core method that has been proc-ified" do
+ method = [].method(:<<)
+ proc = method.to_proc
+
+ proc.source_location.should == nil
+ end
+end
diff --git a/spec/ruby/core/proc/to_proc_spec.rb b/spec/ruby/core/proc/to_proc_spec.rb
new file mode 100644
index 0000000000..ffaa34929b
--- /dev/null
+++ b/spec/ruby/core/proc/to_proc_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Proc#to_proc" do
+ it "returns self" do
+ [Proc.new {}, -> {}, proc {}].each { |p|
+ p.to_proc.should equal(p)
+ }
+ end
+end
diff --git a/spec/ruby/core/proc/to_s_spec.rb b/spec/ruby/core/proc/to_s_spec.rb
new file mode 100644
index 0000000000..5e9c46b6b8
--- /dev/null
+++ b/spec/ruby/core/proc/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Proc#to_s" do
+ it_behaves_like :proc_to_s, :to_s
+end
diff --git a/spec/ruby/core/proc/yield_spec.rb b/spec/ruby/core/proc/yield_spec.rb
new file mode 100644
index 0000000000..365d5b04bd
--- /dev/null
+++ b/spec/ruby/core/proc/yield_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'shared/call'
+require_relative 'shared/call_arguments'
+
+describe "Proc#yield" do
+ it_behaves_like :proc_call, :yield
+ it_behaves_like :proc_call_block_args, :yield
+end
+
+describe "Proc#yield on a Proc created with Proc.new" do
+ it_behaves_like :proc_call_on_proc_new, :yield
+end
+
+describe "Proc#yield on a Proc created with Kernel#lambda or Kernel#proc" do
+ it_behaves_like :proc_call_on_proc_or_lambda, :yield
+end
diff --git a/spec/ruby/core/process/_fork_spec.rb b/spec/ruby/core/process/_fork_spec.rb
new file mode 100644
index 0000000000..6f711ad2dd
--- /dev/null
+++ b/spec/ruby/core/process/_fork_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.1" do
+ describe "Process._fork" do
+ it "for #respond_to? returns the same as Process.respond_to?(:fork)" do
+ Process.respond_to?(:_fork).should == Process.respond_to?(:fork)
+ end
+
+ guard_not -> { Process.respond_to?(:fork) } do
+ it "raises a NotImplementedError when called" do
+ -> { Process._fork }.should raise_error(NotImplementedError)
+ end
+ end
+
+ guard -> { Process.respond_to?(:fork) } do
+ it "is called by Process#fork" do
+ Process.should_receive(:_fork).once.and_return(42)
+
+ pid = Process.fork {}
+ pid.should equal(42)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/abort_spec.rb b/spec/ruby/core/process/abort_spec.rb
new file mode 100644
index 0000000000..1b6ad1da43
--- /dev/null
+++ b/spec/ruby/core/process/abort_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/process/abort'
+
+describe "Process.abort" do
+ it_behaves_like :process_abort, :abort, Process
+end
diff --git a/spec/ruby/core/process/clock_getres_spec.rb b/spec/ruby/core/process/clock_getres_spec.rb
new file mode 100644
index 0000000000..85aa2b25f1
--- /dev/null
+++ b/spec/ruby/core/process/clock_getres_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "Process.clock_getres" do
+ # These are documented
+
+ it "with :GETTIMEOFDAY_BASED_CLOCK_REALTIME reports 1 microsecond" do
+ Process.clock_getres(:GETTIMEOFDAY_BASED_CLOCK_REALTIME, :nanosecond).should == 1_000
+ end
+
+ it "with :TIME_BASED_CLOCK_REALTIME reports 1 second" do
+ Process.clock_getres(:TIME_BASED_CLOCK_REALTIME, :nanosecond).should == 1_000_000_000
+ end
+
+ platform_is_not :windows do
+ it "with :GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID reports 1 microsecond" do
+ Process.clock_getres(:GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID, :nanosecond).should == 1_000
+ end
+ end
+
+ # These are observed
+
+ platform_is :linux, :darwin, :windows do
+ it "with Process::CLOCK_REALTIME reports at least 10 millisecond" do
+ Process.clock_getres(Process::CLOCK_REALTIME, :nanosecond).should <= 10_000_000
+ end
+ end
+
+ platform_is :linux, :darwin, :windows do
+ it "with Process::CLOCK_MONOTONIC reports at least 10 millisecond" do
+ Process.clock_getres(Process::CLOCK_MONOTONIC, :nanosecond).should <= 10_000_000
+ end
+ end
+end
diff --git a/spec/ruby/core/process/clock_gettime_spec.rb b/spec/ruby/core/process/clock_gettime_spec.rb
new file mode 100644
index 0000000000..6c1a52f21e
--- /dev/null
+++ b/spec/ruby/core/process/clock_gettime_spec.rb
@@ -0,0 +1,152 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/clocks'
+
+describe "Process.clock_gettime" do
+ ProcessSpecs.clock_constants.each do |name, value|
+ it "can be called with Process::#{name}" do
+ Process.clock_gettime(value).should be_an_instance_of(Float)
+ end
+ end
+
+ describe 'time units' do
+ it 'handles a fixed set of time units' do
+ [:nanosecond, :microsecond, :millisecond, :second].each do |unit|
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, unit).should be_kind_of(Integer)
+ end
+
+ [:float_microsecond, :float_millisecond, :float_second].each do |unit|
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, unit).should be_an_instance_of(Float)
+ end
+ end
+
+ it 'raises an ArgumentError for an invalid time unit' do
+ -> { Process.clock_gettime(Process::CLOCK_MONOTONIC, :bad) }.should raise_error(ArgumentError)
+ end
+
+ it 'defaults to :float_second' do
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
+
+ t1.should be_an_instance_of(Float)
+ t2.should be_an_instance_of(Float)
+ t2.should be_close(t1, TIME_TOLERANCE)
+ end
+
+ it 'uses the default time unit (:float_second) when passed nil' do
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC, nil)
+ t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
+
+ t1.should be_an_instance_of(Float)
+ t2.should be_an_instance_of(Float)
+ t2.should be_close(t1, TIME_TOLERANCE)
+ end
+ end
+
+ describe "supports the platform clocks mentioned in the documentation" do
+ it "CLOCK_REALTIME" do
+ Process.clock_gettime(Process::CLOCK_REALTIME).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_MONOTONIC" do
+ Process.clock_gettime(Process::CLOCK_MONOTONIC).should be_an_instance_of(Float)
+ end
+
+ # These specs need macOS 10.12+ / darwin 16+
+ guard -> { platform_is_not(:darwin) or kernel_version_is '16' } do
+ platform_is :linux, :openbsd, :darwin do
+ it "CLOCK_PROCESS_CPUTIME_ID" do
+ Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID).should be_an_instance_of(Float)
+ end
+ end
+
+ platform_is :linux, :freebsd, :openbsd, :darwin do
+ it "CLOCK_THREAD_CPUTIME_ID" do
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID).should be_an_instance_of(Float)
+ end
+ end
+
+ platform_is :linux, :darwin do
+ it "CLOCK_MONOTONIC_RAW" do
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_RAW).should be_an_instance_of(Float)
+ end
+ end
+
+ platform_is :darwin do
+ it "CLOCK_MONOTONIC_RAW_APPROX" do
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_RAW_APPROX).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_UPTIME_RAW and CLOCK_UPTIME_RAW_APPROX" do
+ Process.clock_gettime(Process::CLOCK_UPTIME_RAW).should be_an_instance_of(Float)
+ Process.clock_gettime(Process::CLOCK_UPTIME_RAW_APPROX).should be_an_instance_of(Float)
+ end
+ end
+ end
+
+ platform_is :freebsd do
+ it "CLOCK_VIRTUAL" do
+ Process.clock_gettime(Process::CLOCK_VIRTUAL).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_PROF" do
+ Process.clock_gettime(Process::CLOCK_PROF).should be_an_instance_of(Float)
+ end
+ end
+
+ platform_is :freebsd, :openbsd do
+ it "CLOCK_UPTIME" do
+ Process.clock_gettime(Process::CLOCK_UPTIME).should be_an_instance_of(Float)
+ end
+ end
+
+ platform_is :freebsd do
+ it "CLOCK_REALTIME_FAST and CLOCK_REALTIME_PRECISE" do
+ Process.clock_gettime(Process::CLOCK_REALTIME_FAST).should be_an_instance_of(Float)
+ Process.clock_gettime(Process::CLOCK_REALTIME_PRECISE).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_MONOTONIC_FAST and CLOCK_MONOTONIC_PRECISE" do
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_FAST).should be_an_instance_of(Float)
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_PRECISE).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_UPTIME_FAST and CLOCK_UPTIME_PRECISE" do
+ Process.clock_gettime(Process::CLOCK_UPTIME_FAST).should be_an_instance_of(Float)
+ Process.clock_gettime(Process::CLOCK_UPTIME_PRECISE).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_SECOND" do
+ Process.clock_gettime(Process::CLOCK_SECOND).should be_an_instance_of(Float)
+ end
+ end
+
+ guard -> { platform_is :linux and kernel_version_is '2.6.32' } do
+ it "CLOCK_REALTIME_COARSE" do
+ Process.clock_gettime(Process::CLOCK_REALTIME_COARSE).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_MONOTONIC_COARSE" do
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_COARSE).should be_an_instance_of(Float)
+ end
+ end
+
+ guard -> { platform_is :linux and kernel_version_is '2.6.39' } do
+ it "CLOCK_BOOTTIME" do
+ skip "No Process::CLOCK_BOOTTIME" unless defined?(Process::CLOCK_BOOTTIME)
+ Process.clock_gettime(Process::CLOCK_BOOTTIME).should be_an_instance_of(Float)
+ end
+ end
+
+ guard -> { platform_is "x86_64-linux" and kernel_version_is '3.0' } do
+ it "CLOCK_REALTIME_ALARM" do
+ skip "No Process::CLOCK_REALTIME_ALARM" unless defined?(Process::CLOCK_REALTIME_ALARM)
+ Process.clock_gettime(Process::CLOCK_REALTIME_ALARM).should be_an_instance_of(Float)
+ end
+
+ it "CLOCK_BOOTTIME_ALARM" do
+ skip "No Process::CLOCK_BOOTTIME_ALARM" unless defined?(Process::CLOCK_BOOTTIME_ALARM)
+ Process.clock_gettime(Process::CLOCK_BOOTTIME_ALARM).should be_an_instance_of(Float)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/constants_spec.rb b/spec/ruby/core/process/constants_spec.rb
new file mode 100644
index 0000000000..4130bb58a5
--- /dev/null
+++ b/spec/ruby/core/process/constants_spec.rb
@@ -0,0 +1,86 @@
+require_relative '../../spec_helper'
+
+describe "Process::Constants" do
+ platform_is :darwin, :netbsd, :freebsd do
+ it "has the correct constant values on BSD-like systems" do
+ Process::WNOHANG.should == 1
+ Process::WUNTRACED.should == 2
+ Process::PRIO_PROCESS.should == 0
+ Process::PRIO_PGRP.should == 1
+ Process::PRIO_USER.should == 2
+ Process::RLIM_INFINITY.should == 9223372036854775807
+ Process::RLIMIT_CPU.should == 0
+ Process::RLIMIT_FSIZE.should == 1
+ Process::RLIMIT_DATA.should == 2
+ Process::RLIMIT_STACK.should == 3
+ Process::RLIMIT_CORE.should == 4
+ Process::RLIMIT_RSS.should == 5
+ Process::RLIMIT_MEMLOCK.should == 6
+ Process::RLIMIT_NPROC.should == 7
+ Process::RLIMIT_NOFILE.should == 8
+ end
+ end
+
+ platform_is :darwin do
+ it "has the correct constant values on Darwin" do
+ Process::RLIM_SAVED_MAX.should == 9223372036854775807
+ Process::RLIM_SAVED_CUR.should == 9223372036854775807
+ Process::RLIMIT_AS.should == 5
+ end
+ end
+
+ platform_is :linux do
+ it "has the correct constant values on Linux" do
+ Process::WNOHANG.should == 1
+ Process::WUNTRACED.should == 2
+ Process::PRIO_PROCESS.should == 0
+ Process::PRIO_PGRP.should == 1
+ Process::PRIO_USER.should == 2
+ Process::RLIMIT_CPU.should == 0
+ Process::RLIMIT_FSIZE.should == 1
+ Process::RLIMIT_DATA.should == 2
+ Process::RLIMIT_STACK.should == 3
+ Process::RLIMIT_CORE.should == 4
+ Process::RLIMIT_RSS.should == 5
+ Process::RLIMIT_NPROC.should == 6
+ Process::RLIMIT_NOFILE.should == 7
+ Process::RLIMIT_MEMLOCK.should == 8
+ Process::RLIMIT_AS.should == 9
+
+ # These values appear to change according to the platform.
+ values = [4294967295, 9223372036854775807, 18446744073709551615]
+ values.include?(Process::RLIM_INFINITY).should be_true
+ values.include?(Process::RLIM_SAVED_MAX).should be_true
+ values.include?(Process::RLIM_SAVED_CUR).should be_true
+ end
+ end
+
+ platform_is :netbsd, :freebsd do
+ it "Process::RLIMIT_SBSIZE" do
+ Process::RLIMIT_SBSIZE.should == 9 # FIXME: what's it equal?
+ Process::RLIMIT_AS.should == 10
+ end
+ end
+
+ platform_is :windows do
+ it "does not define RLIMIT constants" do
+ %i[
+ RLIMIT_CPU
+ RLIMIT_FSIZE
+ RLIMIT_DATA
+ RLIMIT_STACK
+ RLIMIT_CORE
+ RLIMIT_RSS
+ RLIMIT_NPROC
+ RLIMIT_NOFILE
+ RLIMIT_MEMLOCK
+ RLIMIT_AS
+ RLIM_INFINITY
+ RLIM_SAVED_MAX
+ RLIM_SAVED_CUR
+ ].each do |const|
+ Process.const_defined?(const).should be_false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/daemon_spec.rb b/spec/ruby/core/process/daemon_spec.rb
new file mode 100644
index 0000000000..20b0d743b9
--- /dev/null
+++ b/spec/ruby/core/process/daemon_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+platform_is_not :windows do
+ # macOS 15 is not working this examples
+ return if /darwin/ =~ RUBY_PLATFORM && /15/ =~ `sw_vers -productVersion`
+
+ describe :process_daemon_keep_stdio_open_false, shared: true do
+ it "redirects stdout to /dev/null" do
+ @daemon.invoke("keep_stdio_open_false_stdout", @object).should == ""
+ end
+
+ it "redirects stderr to /dev/null" do
+ @daemon.invoke("keep_stdio_open_false_stderr", @object).should == ""
+ end
+
+ it "redirects stdin to /dev/null" do
+ @daemon.invoke("keep_stdio_open_false_stdin", @object).should == ""
+ end
+
+ it "does not close open files" do
+ @daemon.invoke("keep_stdio_open_files", @object).should == "false"
+ end
+ end
+
+ describe :process_daemon_keep_stdio_open_true, shared: true do
+ it "does not redirect stdout to /dev/null" do
+ @daemon.invoke("keep_stdio_open_true_stdout", @object).should == "writing to stdout"
+ end
+
+ it "does not redirect stderr to /dev/null" do
+ @daemon.invoke("keep_stdio_open_true_stderr", @object).should == "writing to stderr"
+ end
+
+ it "does not redirect stdin to /dev/null" do
+ @daemon.invoke("keep_stdio_open_true_stdin", @object).should == "reading from stdin"
+ end
+
+ it "does not close open files" do
+ @daemon.invoke("keep_stdio_open_files", @object).should == "false"
+ end
+ end
+
+ describe "Process.daemon" do
+ before :each do
+ @invoke_dir = Dir.pwd
+ @daemon = ProcessSpecs::Daemonizer.new
+ end
+
+ after :each do
+ rm_r @daemon.input, @daemon.data if @daemon
+ end
+
+ it "returns 0" do
+ @daemon.invoke("return_value").should == "0"
+ end
+
+ it "has a different PID after daemonizing" do
+ parent, daemon = @daemon.invoke("pid").split(":")
+ parent.should_not == daemon
+ end
+
+ it "has a different process group after daemonizing" do
+ parent, daemon = @daemon.invoke("process_group").split(":")
+ parent.should_not == daemon
+ end
+
+ it "does not run existing at_exit handlers when daemonizing" do
+ @daemon.invoke("daemonizing_at_exit").should == "not running at_exit"
+ end
+
+ it "runs at_exit handlers when the daemon exits" do
+ @daemon.invoke("daemon_at_exit").should == "running at_exit"
+ end
+
+ it "changes directory to the root directory if the first argument is not given" do
+ @daemon.invoke("stay_in_dir").should == "/"
+ end
+
+ it "changes directory to the root directory if the first argument is false" do
+ @daemon.invoke("stay_in_dir", [false]).should == "/"
+ end
+
+ it "changes directory to the root directory if the first argument is nil" do
+ @daemon.invoke("stay_in_dir", [nil]).should == "/"
+ end
+
+ it "does not change to the root directory if the first argument is true" do
+ @daemon.invoke("stay_in_dir", [true]).should == @invoke_dir
+ end
+
+ describe "when the second argument is not given" do
+ it_behaves_like :process_daemon_keep_stdio_open_false, nil, [false]
+ end
+
+ describe "when the second argument is false" do
+ it_behaves_like :process_daemon_keep_stdio_open_false, nil, [false, false]
+ end
+
+ describe "when the second argument is nil" do
+ it_behaves_like :process_daemon_keep_stdio_open_false, nil, [false, nil]
+ end
+
+ describe "when the second argument is true" do
+ it_behaves_like :process_daemon_keep_stdio_open_true, nil, [false, true]
+ end
+ end
+end
+
+platform_is :windows do
+ describe "Process.daemon" do
+ it "raises a NotImplementedError" do
+ -> {
+ Process.daemon
+ }.should raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/ruby/core/process/detach_spec.rb b/spec/ruby/core/process/detach_spec.rb
new file mode 100644
index 0000000000..91661afcea
--- /dev/null
+++ b/spec/ruby/core/process/detach_spec.rb
@@ -0,0 +1,75 @@
+require_relative '../../spec_helper'
+
+describe "Process.detach" do
+ platform_is_not :windows do
+ it "returns a thread" do
+ pid = Process.fork { Process.exit! }
+ thr = Process.detach(pid)
+ thr.should be_kind_of(Thread)
+ thr.join
+ end
+
+ it "produces the exit Process::Status as the thread value" do
+ pid = Process.fork { Process.exit! }
+ thr = Process.detach(pid)
+ thr.join
+
+ status = thr.value
+ status.should be_kind_of(Process::Status)
+ status.pid.should == pid
+ end
+
+ platform_is_not :openbsd do
+ it "reaps the child process's status automatically" do
+ pid = Process.fork { Process.exit! }
+ Process.detach(pid).join
+ -> { Process.waitpid(pid) }.should raise_error(Errno::ECHILD)
+ end
+ end
+
+ it "sets the :pid thread-local to the PID" do
+ pid = Process.fork { Process.exit! }
+ thr = Process.detach(pid)
+ thr.join
+
+ thr[:pid].should == pid
+ end
+
+ it "provides a #pid method on the returned thread which returns the PID" do
+ pid = Process.fork { Process.exit! }
+ thr = Process.detach(pid)
+ thr.join
+
+ thr.pid.should == pid
+ end
+
+ it "tolerates not existing child process pid" do
+ # ensure there is no child process with this hardcoded pid
+ # `kill 0 pid` for existing process returns "1" and raises Errno::ESRCH if process doesn't exist
+ -> { Process.kill(0, 100500) }.should raise_error(Errno::ESRCH)
+
+ thr = Process.detach(100500)
+ thr.join
+
+ thr.should be_kind_of(Thread)
+ end
+
+ it "calls #to_int to implicitly convert non-Integer pid to Integer" do
+ pid = MockObject.new('mock-enumerable')
+ pid.should_receive(:to_int).and_return(100500)
+
+ Process.detach(pid).join
+ end
+
+ it "raises TypeError when pid argument does not have #to_int method" do
+ -> { Process.detach(Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into Integer")
+ end
+
+ it "raises TypeError when #to_int returns non-Integer value" do
+ pid = MockObject.new('mock-enumerable')
+ pid.should_receive(:to_int).and_return(:symbol)
+
+ -> { Process.detach(pid) }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives Symbol)")
+ end
+ end
+end
diff --git a/spec/ruby/core/process/egid_spec.rb b/spec/ruby/core/process/egid_spec.rb
new file mode 100644
index 0000000000..a67b623d5c
--- /dev/null
+++ b/spec/ruby/core/process/egid_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+
+describe "Process.egid" do
+ it "returns the effective group ID for this process" do
+ Process.egid.should be_kind_of(Integer)
+ end
+
+ it "also goes by Process::GID.eid" do
+ Process::GID.eid.should == Process.egid
+ end
+
+ it "also goes by Process::Sys.getegid" do
+ Process::Sys.getegid.should == Process.egid
+ end
+end
+
+describe "Process.egid=" do
+
+ platform_is_not :windows do
+ it "raises TypeError if not passed an Integer or String" do
+ -> { Process.egid = Object.new }.should raise_error(TypeError)
+ end
+
+ it "sets the effective group id to its own gid if given the username corresponding to its own gid" do
+ raise unless Process.gid == Process.egid
+
+ require "etc"
+ group = Etc.getgrgid(Process.gid).name
+
+ Process.egid = group
+ Process.egid.should == Process.gid
+ end
+
+ as_user do
+ it "raises Errno::ERPERM if run by a non superuser trying to set the root group id" do
+ -> { Process.egid = 0 }.should raise_error(Errno::EPERM)
+ end
+
+ platform_is :linux do
+ it "raises Errno::ERPERM if run by a non superuser trying to set the group id from group name" do
+ -> { Process.egid = "root" }.should raise_error(Errno::EPERM)
+ end
+ end
+ end
+
+ as_superuser do
+ context "when ran by a superuser" do
+ it "sets the effective group id for the current process if run by a superuser" do
+ code = <<-RUBY
+ Process.egid = 1
+ puts Process.egid
+ RUBY
+ ruby_exe(code).should == "1\n"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/euid_spec.rb b/spec/ruby/core/process/euid_spec.rb
new file mode 100644
index 0000000000..c1ec4171d0
--- /dev/null
+++ b/spec/ruby/core/process/euid_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+
+describe "Process.euid" do
+ it "returns the effective user ID for this process" do
+ Process.euid.should be_kind_of(Integer)
+ end
+
+ it "also goes by Process::UID.eid" do
+ Process::UID.eid.should == Process.euid
+ end
+
+ it "also goes by Process::Sys.geteuid" do
+ Process::Sys.geteuid.should == Process.euid
+ end
+end
+
+describe "Process.euid=" do
+
+ platform_is_not :windows do
+ it "raises TypeError if not passed an Integer" do
+ -> { Process.euid = Object.new }.should raise_error(TypeError)
+ end
+
+ it "sets the effective user id to its own uid if given the username corresponding to its own uid" do
+ raise unless Process.uid == Process.euid
+
+ require "etc"
+ user = Etc.getpwuid(Process.uid).name
+
+ Process.euid = user
+ Process.euid.should == Process.uid
+ end
+
+ as_user do
+ it "raises Errno::ERPERM if run by a non superuser trying to set the superuser id" do
+ -> { Process.euid = 0 }.should raise_error(Errno::EPERM)
+ end
+
+ it "raises Errno::ERPERM if run by a non superuser trying to set the superuser id from username" do
+ -> { Process.euid = "root" }.should raise_error(Errno::EPERM)
+ end
+ end
+
+ as_superuser do
+ describe "if run by a superuser" do
+ it "sets the effective user id for the current process if run by a superuser" do
+ code = <<-RUBY
+ Process.euid = 1
+ puts Process.euid
+ RUBY
+ ruby_exe(code).should == "1\n"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/exec_spec.rb b/spec/ruby/core/process/exec_spec.rb
new file mode 100644
index 0000000000..deb8913b6b
--- /dev/null
+++ b/spec/ruby/core/process/exec_spec.rb
@@ -0,0 +1,241 @@
+require_relative '../../spec_helper'
+
+describe "Process.exec" do
+ it "raises Errno::ENOENT for an empty string" do
+ -> { Process.exec "" }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises Errno::ENOENT for a command which does not exist" do
+ -> { Process.exec "bogus-noent-script.sh" }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises an ArgumentError if the command includes a null byte" do
+ -> { Process.exec "\000" }.should raise_error(ArgumentError)
+ end
+
+ unless File.executable?(__FILE__) # Some FS (e.g. vboxfs) locate all files executable
+ platform_is_not :windows do
+ it "raises Errno::EACCES when the file does not have execute permissions" do
+ -> { Process.exec __FILE__ }.should raise_error(Errno::EACCES)
+ end
+ end
+
+ platform_is :windows do
+ it "raises Errno::EACCES or Errno::ENOEXEC when the file is not an executable file" do
+ -> { Process.exec __FILE__ }.should raise_error(SystemCallError) { |e|
+ [Errno::EACCES, Errno::ENOEXEC].should include(e.class)
+ }
+ end
+ end
+ end
+
+ it "raises Errno::EACCES when passed a directory" do
+ -> { Process.exec File.dirname(__FILE__) }.should raise_error(Errno::EACCES)
+ end
+
+ it "runs the specified command, replacing current process" do
+ ruby_exe('Process.exec "echo hello"; puts "fail"', escape: true).should == "hello\n"
+ end
+
+ it "sets the current directory when given the :chdir option" do
+ tmpdir = tmp("")[0..-2]
+ platform_is_not :windows do
+ ruby_exe("Process.exec(\"pwd\", chdir: #{tmpdir.inspect})", escape: true).should == "#{tmpdir}\n"
+ end
+ platform_is :windows do
+ ruby_exe("Process.exec(\"cd\", chdir: #{tmpdir.inspect})", escape: true).tr('\\', '/').should == "#{tmpdir}\n"
+ end
+ end
+
+ it "flushes STDOUT upon exit when it's not set to sync" do
+ ruby_exe("STDOUT.sync = false; STDOUT.write 'hello'").should == "hello"
+ end
+
+ it "flushes STDERR upon exit when it's not set to sync" do
+ ruby_exe("STDERR.sync = false; STDERR.write 'hello'", args: "2>&1").should == "hello"
+ end
+
+ describe "with a single argument" do
+ before :each do
+ @dir = tmp("exec_with_dir", false)
+ Dir.mkdir @dir
+
+ @name = "some_file"
+ @path = tmp("exec_with_dir/#{@name}", false)
+ touch @path
+ end
+
+ after :each do
+ rm_r @path
+ rm_r @dir
+ end
+
+ platform_is_not :windows do
+ it "subjects the specified command to shell expansion" do
+ result = Dir.chdir(@dir) do
+ ruby_exe('Process.exec "echo *"', escape: true)
+ end
+ result.chomp.should == @name
+ end
+
+ it "creates an argument array with shell parsing semantics for whitespace" do
+ ruby_exe('Process.exec "echo a b c d"', escape: true).should == "a b c d\n"
+ end
+ end
+
+ platform_is :windows do
+ # There is no shell expansion on Windows
+ it "does not subject the specified command to shell expansion on Windows" do
+ result = Dir.chdir(@dir) do
+ ruby_exe('Process.exec "echo *"', escape: true)
+ end
+ result.should == "*\n"
+ end
+
+ it "does not create an argument array with shell parsing semantics for whitespace on Windows" do
+ ruby_exe('Process.exec "echo a b c d"', escape: true).should == "a b c d\n"
+ end
+ end
+
+ end
+
+ describe "with multiple arguments" do
+ it "does not subject the arguments to shell expansion" do
+ cmd = '"echo", "*"'
+ platform_is :windows do
+ cmd = '"cmd.exe", "/C", "echo", "*"'
+ end
+ ruby_exe("Process.exec #{cmd}", escape: true).should == "*\n"
+ end
+ end
+
+ describe "(environment variables)" do
+ before :each do
+ ENV["FOO"] = "FOO"
+ end
+
+ after :each do
+ ENV["FOO"] = nil
+ end
+
+ var = '$FOO'
+ platform_is :windows do
+ var = '%FOO%'
+ end
+
+ it "sets environment variables in the child environment" do
+ ruby_exe('Process.exec({"FOO" => "BAR"}, "echo ' + var + '")', escape: true).should == "BAR\n"
+ end
+
+ it "unsets environment variables whose value is nil" do
+ platform_is_not :windows do
+ ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")', escape: true).should == "\n"
+ end
+ platform_is :windows do
+ # On Windows, echo-ing a non-existent env var is treated as echo-ing any other string of text
+ ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")', escape: true).should == var + "\n"
+ end
+ end
+
+ it "coerces environment argument using to_hash" do
+ ruby_exe('o = Object.new; def o.to_hash; {"FOO" => "BAR"}; end; Process.exec(o, "echo ' + var + '")', escape: true).should == "BAR\n"
+ end
+
+ it "unsets other environment variables when given a true :unsetenv_others option" do
+ platform_is_not :windows do
+ ruby_exe('Process.exec("echo ' + var + '", unsetenv_others: true)', escape: true).should == "\n"
+ end
+ platform_is :windows do
+ ruby_exe('Process.exec("' + ENV['COMSPEC'].gsub('\\', '\\\\\\') + ' /C echo ' + var + '", unsetenv_others: true)', escape: true).should == var + "\n"
+ end
+ end
+ end
+
+ describe "with a command array" do
+ it "uses the first element as the command name and the second as the argv[0] value" do
+ platform_is_not :windows do
+ ruby_exe('Process.exec(["/bin/sh", "argv_zero"], "-c", "echo $0")', escape: true).should == "argv_zero\n"
+ end
+ platform_is :windows do
+ ruby_exe('Process.exec(["cmd.exe", "/C"], "/C", "echo", "argv_zero")', escape: true).should == "argv_zero\n"
+ end
+ end
+
+ it "coerces the argument using to_ary" do
+ platform_is_not :windows do
+ ruby_exe('o = Object.new; def o.to_ary; ["/bin/sh", "argv_zero"]; end; Process.exec(o, "-c", "echo $0")', escape: true).should == "argv_zero\n"
+ end
+ platform_is :windows do
+ ruby_exe('o = Object.new; def o.to_ary; ["cmd.exe", "/C"]; end; Process.exec(o, "/C", "echo", "argv_zero")', escape: true).should == "argv_zero\n"
+ end
+ end
+
+ it "raises an ArgumentError if the Array does not have exactly two elements" do
+ -> { Process.exec([]) }.should raise_error(ArgumentError)
+ -> { Process.exec([:a]) }.should raise_error(ArgumentError)
+ -> { Process.exec([:a, :b, :c]) }.should raise_error(ArgumentError)
+ end
+ end
+
+ platform_is_not :windows do
+ describe "with an options Hash" do
+ describe "with Integer option keys" do
+ before :each do
+ @name = tmp("exec_fd_map.txt")
+ @child_fd_file = tmp("child_fd_file.txt")
+ end
+
+ after :each do
+ rm_r @name, @child_fd_file
+ end
+
+ it "maps the key to a file descriptor in the child that inherits the file descriptor from the parent specified by the value" do
+ map_fd_fixture = fixture __FILE__, "map_fd.rb"
+ cmd = <<-EOC
+ f = File.open(#{@name.inspect}, "w+")
+ File.open(#{__FILE__.inspect}, "r") do |io|
+ child_fd = io.fileno
+ File.open(#{@child_fd_file.inspect}, "w") { |io| io.print child_fd }
+ Process.exec "#{ruby_cmd(map_fd_fixture)} \#{child_fd}", { child_fd => f }
+ end
+ EOC
+
+ ruby_exe(cmd, escape: true)
+ child_fd = IO.read(@child_fd_file).to_i
+ child_fd.to_i.should > STDERR.fileno
+
+ File.read(@name).should == "writing to fd: #{child_fd}"
+ end
+
+ it "lets the process after exec have specified file descriptor despite close_on_exec" do
+ map_fd_fixture = fixture __FILE__, "map_fd.rb"
+ cmd = <<-EOC
+ f = File.open(#{@name.inspect}, 'w+')
+ puts(f.fileno, f.close_on_exec?)
+ STDOUT.flush
+ Process.exec("#{ruby_cmd(map_fd_fixture)} \#{f.fileno}", f.fileno => f.fileno)
+ EOC
+
+ output = ruby_exe(cmd, escape: true)
+ child_fd, close_on_exec = output.split
+
+ child_fd.to_i.should > STDERR.fileno
+ close_on_exec.should == 'true'
+ File.read(@name).should == "writing to fd: #{child_fd}"
+ end
+
+ it "sets close_on_exec to false on specified fd even when it fails" do
+ cmd = <<-EOC
+ f = File.open(#{__FILE__.inspect}, 'r')
+ puts(f.close_on_exec?)
+ Process.exec('/', f.fileno => f.fileno) rescue nil
+ puts(f.close_on_exec?)
+ EOC
+
+ output = ruby_exe(cmd, escape: true)
+ output.split.should == ['true', 'false']
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/exit_spec.rb b/spec/ruby/core/process/exit_spec.rb
new file mode 100644
index 0000000000..70d79d789d
--- /dev/null
+++ b/spec/ruby/core/process/exit_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/process/exit'
+
+describe "Process.exit" do
+ it_behaves_like :process_exit, :exit, Process
+end
+
+describe "Process.exit!" do
+ it_behaves_like :process_exit!, :exit!, Process
+end
diff --git a/spec/ruby/core/process/fixtures/clocks.rb b/spec/ruby/core/process/fixtures/clocks.rb
new file mode 100644
index 0000000000..f043f6ac1f
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/clocks.rb
@@ -0,0 +1,18 @@
+module ProcessSpecs
+ def self.clock_constants
+ clocks = []
+
+ platform_is_not :windows, :solaris do
+ clocks += Process.constants.select { |c| c.to_s.start_with?('CLOCK_') }
+
+ # These require CAP_WAKE_ALARM and are not documented in
+ # Process#clock_gettime. They return EINVAL if the permission
+ # is not granted.
+ clocks -= [:CLOCK_BOOTTIME_ALARM, :CLOCK_REALTIME_ALARM]
+ end
+
+ clocks.sort.map { |c|
+ [c, Process.const_get(c)]
+ }
+ end
+end
diff --git a/spec/ruby/core/process/fixtures/common.rb b/spec/ruby/core/process/fixtures/common.rb
new file mode 100644
index 0000000000..f49513d262
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/common.rb
@@ -0,0 +1,88 @@
+module ProcessSpecs
+ def self.use_system_ruby(context)
+ if defined?(MSpecScript::SYSTEM_RUBY)
+ context.send(:before, :all) do
+ @ruby = ::RUBY_EXE
+ suppress_warning {
+ Object.const_set(:RUBY_EXE, MSpecScript::SYSTEM_RUBY)
+ }
+ end
+
+ context.send(:after, :all) do
+ suppress_warning {
+ Object.const_set(:RUBY_EXE, @ruby)
+ }
+ end
+ end
+ end
+
+ class Daemonizer
+ attr_reader :input, :data
+
+ def initialize
+ # Fast feedback for implementations without Process.daemon
+ raise NotImplementedError, "Process.daemon is not implemented" unless Process.respond_to? :daemon
+
+ @script = fixture __FILE__, "daemon.rb"
+ @input = tmp("process_daemon_input_file")
+ @data = tmp("process_daemon_data_file")
+ @args = []
+ end
+
+ def wait_for_daemon
+ sleep 0.001 until File.exist?(@data) and File.size?(@data)
+ end
+
+ def invoke(behavior, arguments=[])
+ args = Marshal.dump(arguments).unpack("H*")
+ args << @input << @data << behavior
+
+ ruby_exe @script, args: args
+
+ wait_for_daemon
+
+ return unless File.exist? @data
+
+ File.open(@data, "rb") { |f| return f.read.chomp }
+ end
+ end
+
+ class Signalizer
+ attr_reader :pid_file, :pid
+
+ def initialize(scenario=nil)
+ platform_is :windows do
+ fail "not supported on windows"
+ end
+ @script = fixture __FILE__, "kill.rb"
+ @pid = nil
+ @pid_file = tmp("process_kill_signal_file")
+ rm_r @pid_file
+
+ @thread = Thread.new do
+ Thread.current.abort_on_exception = true
+ args = [@pid_file]
+ args << scenario if scenario
+ @result = ruby_exe @script, args: args
+ end
+ Thread.pass while @thread.status and !File.exist?(@pid_file)
+ while @thread.status && (@pid.nil? || @pid == 0)
+ @pid = IO.read(@pid_file).chomp.to_i
+ end
+ end
+
+ def wait_on_result
+ @thread.join
+ end
+
+ def cleanup
+ wait_on_result
+ rm_r pid_file
+ end
+
+ def result
+ wait_on_result
+ @result.chomp if @result
+ end
+ end
+end
diff --git a/spec/ruby/core/process/fixtures/daemon.rb b/spec/ruby/core/process/fixtures/daemon.rb
new file mode 100644
index 0000000000..772df2d09e
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/daemon.rb
@@ -0,0 +1,111 @@
+module ProcessSpecs
+ class Daemon
+ def initialize(argv)
+ args, @input, @data, @behavior = argv
+ @args = Marshal.load [args].pack("H*")
+ @no_at_exit = false
+ end
+
+ def run
+ send @behavior
+
+ # Exit without running any at_exit handlers
+ exit!(0) if @no_at_exit
+ end
+
+ def write(data)
+ File.open(@data, "wb") { |f| f.puts data }
+ end
+
+ def daemonizing_at_exit
+ at_exit do
+ write "running at_exit"
+ end
+
+ @no_at_exit = true
+ Process.daemon
+ write "not running at_exit"
+ end
+
+ def return_value
+ write Process.daemon.to_s
+ end
+
+ def pid
+ parent = Process.pid
+ Process.daemon
+ daemon = Process.pid
+ write "#{parent}:#{daemon}"
+ end
+
+ def process_group
+ parent = Process.getpgrp
+ Process.daemon
+ daemon = Process.getpgrp
+ write "#{parent}:#{daemon}"
+ end
+
+ def daemon_at_exit
+ at_exit do
+ write "running at_exit"
+ end
+
+ Process.daemon
+ end
+
+ def stay_in_dir
+ Process.daemon(*@args)
+ write Dir.pwd
+ end
+
+ def keep_stdio_open_false_stdout
+ Process.daemon(*@args)
+ $stdout.write "writing to stdout"
+ write ""
+ end
+
+ def keep_stdio_open_false_stderr
+ Process.daemon(*@args)
+ $stderr.write "writing to stderr"
+ write ""
+ end
+
+ def keep_stdio_open_false_stdin
+ Process.daemon(*@args)
+
+ # Reading from /dev/null will return right away. If STDIN were not
+ # /dev/null, reading would block and the spec would hang. This is not a
+ # perfect way to spec the behavior but it works.
+ write $stdin.read
+ end
+
+ def keep_stdio_open_true_stdout
+ $stdout.reopen @data
+ Process.daemon(*@args)
+ $stdout.write "writing to stdout"
+ end
+
+ def keep_stdio_open_true_stderr
+ $stderr.reopen @data
+ Process.daemon(*@args)
+ $stderr.write "writing to stderr"
+ end
+
+ def keep_stdio_open_true_stdin
+ File.open(@input, "w") { |f| f.puts "reading from stdin" }
+
+ $stdin.reopen @input, "r"
+ Process.daemon(*@args)
+ write $stdin.read
+ end
+
+ def keep_stdio_open_files
+ file = File.open @input, "w"
+
+ Process.daemon(*@args)
+ write file.closed?
+ end
+ end
+end
+
+ProcessSpecs::Daemon.new(ARGV).run
diff --git a/spec/ruby/core/process/fixtures/in.txt b/spec/ruby/core/process/fixtures/in.txt
new file mode 100644
index 0000000000..cf52303bdc
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/in.txt
@@ -0,0 +1 @@
+stdin
diff --git a/spec/ruby/core/process/fixtures/kill.rb b/spec/ruby/core/process/fixtures/kill.rb
new file mode 100644
index 0000000000..0b88f8ee1f
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/kill.rb
@@ -0,0 +1,45 @@
+require 'thread'
+
+pid_file = ARGV.shift
+scenario = ARGV.shift
+
+# We must do this first otherwise there will be a race with the process that
+# creates this process and the TERM signal below could go to that process
+# instead, which will likely abort the specs process.
+Process.setsid if scenario
+
+mutex = Mutex.new
+
+Signal.trap(:TERM) do
+ if mutex.try_lock
+ STDOUT.puts "signaled"
+ STDOUT.flush
+ $signaled = true
+ end
+end
+
+File.open(pid_file, "wb") { |f| f.puts Process.pid }
+
+if scenario
+ # We are sending a signal to the process group
+ process = "Process.getpgrp"
+
+ case scenario
+ when "self"
+ signal = %["SIGTERM"]
+ process = "0"
+ when "group_numeric"
+ signal = %[-Signal.list["TERM"]]
+ when "group_short_string"
+ signal = %["-TERM"]
+ when "group_full_string"
+ signal = %["-SIGTERM"]
+ else
+ raise "unknown scenario: #{scenario.inspect}"
+ end
+
+ code = "Process.kill(#{signal}, #{process})"
+ system(ENV["RUBY_EXE"], *ENV["RUBY_FLAGS"].split(' '), "-e", code)
+end
+
+sleep 0.001 until mutex.locked? and $signaled
diff --git a/spec/ruby/core/process/fixtures/map_fd.rb b/spec/ruby/core/process/fixtures/map_fd.rb
new file mode 100644
index 0000000000..3ed887486b
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/map_fd.rb
@@ -0,0 +1,9 @@
+fd = ARGV.shift.to_i
+
+f = File.for_fd(fd)
+f.autoclose = false
+begin
+ f.write "writing to fd: #{fd}"
+ensure
+ f.close
+end
diff --git a/spec/ruby/core/process/fixtures/setpriority.rb b/spec/ruby/core/process/fixtures/setpriority.rb
new file mode 100644
index 0000000000..cf22d85b12
--- /dev/null
+++ b/spec/ruby/core/process/fixtures/setpriority.rb
@@ -0,0 +1,12 @@
+case ARGV[0]
+when "process"
+ which = Process::PRIO_PROCESS
+when "group"
+ Process.setpgrp
+ which = Process::PRIO_PGRP
+end
+
+priority = Process.getpriority(which, 0)
+p priority
+p Process.setpriority(which, 0, priority + 1)
+p Process.getpriority(which, 0)
diff --git a/spec/ruby/core/process/fork_spec.rb b/spec/ruby/core/process/fork_spec.rb
new file mode 100644
index 0000000000..a4f765247d
--- /dev/null
+++ b/spec/ruby/core/process/fork_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/process/fork'
+
+describe "Process.fork" do
+ it_behaves_like :process_fork, :fork, Process
+end
diff --git a/spec/ruby/core/process/getpgid_spec.rb b/spec/ruby/core/process/getpgid_spec.rb
new file mode 100644
index 0000000000..c1dd007b16
--- /dev/null
+++ b/spec/ruby/core/process/getpgid_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "Process.getpgid" do
+ platform_is_not :windows do
+ it "coerces the argument to an Integer" do
+ Process.getpgid(mock_int(Process.pid)).should == Process.getpgrp
+ end
+
+ it "returns the process group ID for the given process id" do
+ Process.getpgid(Process.pid).should == Process.getpgrp
+ end
+
+ it "returns the process group ID for the calling process id when passed 0" do
+ Process.getpgid(0).should == Process.getpgrp
+ end
+ end
+end
diff --git a/spec/ruby/core/process/getpgrp_spec.rb b/spec/ruby/core/process/getpgrp_spec.rb
new file mode 100644
index 0000000000..e1d1c5f92d
--- /dev/null
+++ b/spec/ruby/core/process/getpgrp_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+# see setpgrp_spec.rb
+
+describe "Process.getpgrp" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/getpriority_spec.rb b/spec/ruby/core/process/getpriority_spec.rb
new file mode 100644
index 0000000000..a35e5956a5
--- /dev/null
+++ b/spec/ruby/core/process/getpriority_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "Process.getpriority" do
+ platform_is_not :windows do
+
+ it "coerces arguments to Integers" do
+ ret = Process.getpriority mock_int(Process::PRIO_PROCESS), mock_int(0)
+ ret.should be_kind_of(Integer)
+ end
+
+ it "gets the scheduling priority for a specified process" do
+ Process.getpriority(Process::PRIO_PROCESS, 0).should be_kind_of(Integer)
+ end
+
+ it "gets the scheduling priority for a specified process group" do
+ Process.getpriority(Process::PRIO_PGRP, 0).should be_kind_of(Integer)
+ end
+
+ it "gets the scheduling priority for a specified user" do
+ Process.getpriority(Process::PRIO_USER, 0).should be_kind_of(Integer)
+ end
+ end
+end
diff --git a/spec/ruby/core/process/getrlimit_spec.rb b/spec/ruby/core/process/getrlimit_spec.rb
new file mode 100644
index 0000000000..883b8fac48
--- /dev/null
+++ b/spec/ruby/core/process/getrlimit_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../spec_helper'
+
+platform_is :aix do
+ # In AIX, if getrlimit(2) is called multiple times with RLIMIT_DATA,
+ # the first call and the subsequent calls return slightly different
+ # values of rlim_cur, even if the process does nothing between
+ # the calls. This behavior causes some of the tests in this spec
+ # to fail, so call Process.getrlimit(:DATA) once and discard the result.
+ # Subsequent calls to Process.getrlimit(:DATA) should return
+ # a consistent value of rlim_cur.
+ Process.getrlimit(:DATA)
+end
+
+describe "Process.getrlimit" do
+ platform_is_not :windows do
+ it "returns a two-element Array of Integers" do
+ result = Process.getrlimit Process::RLIMIT_CORE
+ result.size.should == 2
+ result.first.should be_kind_of(Integer)
+ result.last.should be_kind_of(Integer)
+ end
+
+ context "when passed an Object" do
+ before do
+ @resource = Process::RLIMIT_CORE
+ end
+
+ it "calls #to_int to convert to an Integer" do
+ obj = mock("process getrlimit integer")
+ obj.should_receive(:to_int).and_return(@resource)
+
+ Process.getrlimit(obj).should == Process.getrlimit(@resource)
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ obj = mock("process getrlimit integer")
+ obj.should_receive(:to_int).and_return(nil)
+
+ -> { Process.getrlimit(obj) }.should raise_error(TypeError)
+ end
+ end
+
+ context "when passed a Symbol" do
+ it "coerces the short name into the full RLIMIT_ prefixed name" do
+ Process.constants.grep(/\ARLIMIT_/) do |fullname|
+ short = fullname[/\ARLIMIT_(.+)/, 1]
+ Process.getrlimit(short.to_sym).should == Process.getrlimit(Process.const_get(fullname))
+ end
+ end
+
+ it "raises ArgumentError when passed an unknown resource" do
+ -> { Process.getrlimit(:FOO) }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "when passed a String" do
+ it "coerces the short name into the full RLIMIT_ prefixed name" do
+ Process.constants.grep(/\ARLIMIT_/) do |fullname|
+ short = fullname[/\ARLIMIT_(.+)/, 1]
+ Process.getrlimit(short).should == Process.getrlimit(Process.const_get(fullname))
+ end
+ end
+
+ it "raises ArgumentError when passed an unknown resource" do
+ -> { Process.getrlimit("FOO") }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "when passed on Object" do
+ before do
+ @resource = Process::RLIMIT_CORE
+ end
+
+ it "calls #to_str to convert to a String" do
+ obj = mock("process getrlimit string")
+ obj.should_receive(:to_str).and_return("CORE")
+ obj.should_not_receive(:to_int)
+
+ Process.getrlimit(obj).should == Process.getrlimit(@resource)
+ end
+
+ it "calls #to_int if #to_str does not return a String" do
+ obj = mock("process getrlimit string")
+ obj.should_receive(:to_str).and_return(nil)
+ obj.should_receive(:to_int).and_return(@resource)
+
+ Process.getrlimit(obj).should == Process.getrlimit(@resource)
+ end
+ end
+ end
+
+ platform_is :windows do
+ it "is not implemented" do
+ Process.respond_to?(:getrlimit).should be_false
+ -> do
+ Process.getrlimit(nil)
+ end.should raise_error NotImplementedError
+ end
+ end
+end
diff --git a/spec/ruby/core/process/gid/change_privilege_spec.rb b/spec/ruby/core/process/gid/change_privilege_spec.rb
new file mode 100644
index 0000000000..4ada277514
--- /dev/null
+++ b/spec/ruby/core/process/gid/change_privilege_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::GID.change_privilege" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/gid/eid_spec.rb b/spec/ruby/core/process/gid/eid_spec.rb
new file mode 100644
index 0000000000..3f2186bb6a
--- /dev/null
+++ b/spec/ruby/core/process/gid/eid_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+describe "Process::GID.eid" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "Process::GID.eid=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/gid/grant_privilege_spec.rb b/spec/ruby/core/process/gid/grant_privilege_spec.rb
new file mode 100644
index 0000000000..10da7f8a26
--- /dev/null
+++ b/spec/ruby/core/process/gid/grant_privilege_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::GID.grant_privilege" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/gid/re_exchange_spec.rb b/spec/ruby/core/process/gid/re_exchange_spec.rb
new file mode 100644
index 0000000000..9642c81c5b
--- /dev/null
+++ b/spec/ruby/core/process/gid/re_exchange_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::GID.re_exchange" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/gid/re_exchangeable_spec.rb b/spec/ruby/core/process/gid/re_exchangeable_spec.rb
new file mode 100644
index 0000000000..1c38f903d5
--- /dev/null
+++ b/spec/ruby/core/process/gid/re_exchangeable_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::GID.re_exchangeable?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/gid/rid_spec.rb b/spec/ruby/core/process/gid/rid_spec.rb
new file mode 100644
index 0000000000..ad66c94e72
--- /dev/null
+++ b/spec/ruby/core/process/gid/rid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::GID.rid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/gid/sid_available_spec.rb b/spec/ruby/core/process/gid/sid_available_spec.rb
new file mode 100644
index 0000000000..8d86b3e9d2
--- /dev/null
+++ b/spec/ruby/core/process/gid/sid_available_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::GID.sid_available?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/gid/switch_spec.rb b/spec/ruby/core/process/gid/switch_spec.rb
new file mode 100644
index 0000000000..b162f1e782
--- /dev/null
+++ b/spec/ruby/core/process/gid/switch_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::GID.switch" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/gid_spec.rb b/spec/ruby/core/process/gid_spec.rb
new file mode 100644
index 0000000000..07221da420
--- /dev/null
+++ b/spec/ruby/core/process/gid_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+describe "Process.gid" do
+ platform_is_not :windows do
+ it "returns the correct gid for the user executing this process" do
+ current_gid_according_to_unix = `id -gr`.to_i
+ Process.gid.should == current_gid_according_to_unix
+ end
+ end
+
+ it "also goes by Process::GID.rid" do
+ Process::GID.rid.should == Process.gid
+ end
+
+ it "also goes by Process::Sys.getgid" do
+ Process::Sys.getgid.should == Process.gid
+ end
+end
+
+describe "Process.gid=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/groups_spec.rb b/spec/ruby/core/process/groups_spec.rb
new file mode 100644
index 0000000000..33e0f9d7b3
--- /dev/null
+++ b/spec/ruby/core/process/groups_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../../spec_helper'
+
+describe "Process.groups" do
+ platform_is_not :windows do
+ it "gets an Array of the gids of groups in the supplemental group access list" do
+ groups = `id -G`.scan(/\d+/).map { |i| i.to_i }
+ # Include the standard `id` command output. On macOS, GNU
+ # coreutils `id` is limited to NGROUPS_MAX groups, because of
+ # the backward compatibility of getgroups(2).
+ (groups |= `/usr/bin/id -G`.scan(/\d+/).map { |i| i.to_i }) rescue nil
+ gid = Process.gid
+
+ expected = (groups.sort - [gid]).uniq.sort
+ actual = (Process.groups - [gid]).uniq.sort
+ actual.should == expected
+ end
+ end
+end
+
+describe "Process.groups=" do
+ platform_is_not :windows, :android do
+ as_superuser do
+ it "sets the list of gids of groups in the supplemental group access list" do
+ groups = Process.groups
+ Process.groups = []
+ Process.groups.should == []
+ Process.groups = groups
+ Process.groups.sort.should == groups.sort
+ end
+ end
+
+ as_user do
+ platform_is :aix do
+ it "sets the list of gids of groups in the supplemental group access list" do
+ # setgroups() is not part of the POSIX standard,
+ # so its behavior varies from OS to OS. AIX allows a non-root
+ # process to set the supplementary group IDs, as long as
+ # they are presently in its supplementary group IDs.
+ # The order of the following tests matters.
+ # After this process executes "Process.groups = []"
+ # it should no longer be able to set any supplementary
+ # group IDs, even if it originally belonged to them.
+ # It should only be able to set its primary group ID.
+ groups = Process.groups
+ Process.groups = groups
+ Process.groups.sort.should == groups.sort
+ Process.groups = []
+ Process.groups.should == []
+ Process.groups = [ Process.gid ]
+ Process.groups.should == [ Process.gid ]
+ supplementary = groups - [ Process.gid ]
+ if supplementary.length > 0
+ -> { Process.groups = supplementary }.should raise_error(Errno::EPERM)
+ end
+ end
+ end
+
+ platform_is_not :aix do
+ it "raises Errno::EPERM" do
+ -> {
+ Process.groups = [0]
+ }.should raise_error(Errno::EPERM)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/initgroups_spec.rb b/spec/ruby/core/process/initgroups_spec.rb
new file mode 100644
index 0000000000..ffc7f282b6
--- /dev/null
+++ b/spec/ruby/core/process/initgroups_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+describe "Process.initgroups" do
+ platform_is_not :windows, :android do
+ as_user do
+ it "initializes the supplemental group access list" do
+ name = `id -un`.strip
+ groups = Process.groups
+ gid = groups.max.to_i + 1
+ augmented_groups = `id -G`.scan(/\d+/).map {|i| i.to_i} << gid
+ if Process.uid == 0
+ Process.groups = []
+ Process.initgroups(name, gid).sort.should == augmented_groups.sort
+ Process.groups.sort.should == augmented_groups.sort
+ Process.groups = groups
+ else
+ -> { Process.initgroups(name, gid) }.should raise_error(Errno::EPERM)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/kill_spec.rb b/spec/ruby/core/process/kill_spec.rb
new file mode 100644
index 0000000000..af9c9e6deb
--- /dev/null
+++ b/spec/ruby/core/process/kill_spec.rb
@@ -0,0 +1,132 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Process.kill" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :each do
+ @pid = Process.pid
+ end
+
+ it "raises an ArgumentError for unknown signals" do
+ -> { Process.kill("FOO", @pid) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed a lowercase signal name" do
+ -> { Process.kill("term", @pid) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if signal is not an Integer or String" do
+ signal = mock("process kill signal")
+ signal.should_not_receive(:to_int)
+
+ -> { Process.kill(signal, @pid) }.should raise_error(ArgumentError)
+ end
+
+ it "raises Errno::ESRCH if the process does not exist" do
+ pid = Process.spawn(*ruby_exe, "-e", "sleep 10")
+ Process.kill("SIGKILL", pid)
+ Process.wait(pid)
+ -> {
+ Process.kill("SIGKILL", pid)
+ }.should raise_error(Errno::ESRCH)
+ end
+
+ it "checks for existence and permissions to signal a process, but does not actually signal it, when using signal 0" do
+ Process.kill(0, @pid).should == 1
+ end
+end
+
+platform_is_not :windows do
+ describe "Process.kill" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :each do
+ @sp = ProcessSpecs::Signalizer.new
+ end
+
+ after :each do
+ @sp.cleanup
+ end
+
+ it "accepts a Symbol as a signal name" do
+ Process.kill(:SIGTERM, @sp.pid)
+ @sp.result.should == "signaled"
+ end
+
+ it "accepts a String as signal name" do
+ Process.kill("SIGTERM", @sp.pid)
+ @sp.result.should == "signaled"
+ end
+
+ it "accepts a signal name without the 'SIG' prefix" do
+ Process.kill("TERM", @sp.pid)
+ @sp.result.should == "signaled"
+ end
+
+ it "accepts a signal name with the 'SIG' prefix" do
+ Process.kill("SIGTERM", @sp.pid)
+ @sp.result.should == "signaled"
+ end
+
+ it "accepts an Integer as a signal value" do
+ Process.kill(15, @sp.pid)
+ @sp.result.should == "signaled"
+ end
+
+ it "calls #to_int to coerce the pid to an Integer" do
+ Process.kill("SIGTERM", mock_int(@sp.pid))
+ @sp.result.should == "signaled"
+ end
+ end
+
+ describe "Process.kill" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :each do
+ @sp1 = ProcessSpecs::Signalizer.new
+ @sp2 = ProcessSpecs::Signalizer.new
+ end
+
+ after :each do
+ @sp1.cleanup
+ @sp2.cleanup
+ end
+
+ it "signals multiple processes" do
+ Process.kill("SIGTERM", @sp1.pid, @sp2.pid)
+ @sp1.result.should == "signaled"
+ @sp2.result.should == "signaled"
+ end
+
+ it "returns the number of processes signaled" do
+ Process.kill("SIGTERM", @sp1.pid, @sp2.pid).should == 2
+ end
+ end
+
+ describe "Process.kill" do
+ after :each do
+ @sp.cleanup if @sp
+ end
+
+ it "signals the process group if the PID is zero" do
+ @sp = ProcessSpecs::Signalizer.new "self"
+ @sp.result.should == "signaled"
+ end
+
+ it "signals the process group if the signal number is negative" do
+ @sp = ProcessSpecs::Signalizer.new "group_numeric"
+ @sp.result.should == "signaled"
+ end
+
+ it "signals the process group if the short signal name starts with a minus sign" do
+ @sp = ProcessSpecs::Signalizer.new "group_short_string"
+ @sp.result.should == "signaled"
+ end
+
+ it "signals the process group if the full signal name starts with a minus sign" do
+ @sp = ProcessSpecs::Signalizer.new "group_full_string"
+ @sp.result.should == "signaled"
+ end
+ end
+end
diff --git a/spec/ruby/core/process/last_status_spec.rb b/spec/ruby/core/process/last_status_spec.rb
new file mode 100644
index 0000000000..2372f2aae3
--- /dev/null
+++ b/spec/ruby/core/process/last_status_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe 'Process#last_status' do
+ it 'returns the status of the last executed child process in the current thread' do
+ pid = Process.wait Process.spawn("exit 0")
+ Process.last_status.pid.should == pid
+ end
+
+ it 'returns nil if no child process has been ever executed in the current thread' do
+ Thread.new do
+ Process.last_status.should == nil
+ end.join
+ end
+
+ it 'raises an ArgumentError if any arguments are provided' do
+ -> { Process.last_status(1) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/process/maxgroups_spec.rb b/spec/ruby/core/process/maxgroups_spec.rb
new file mode 100644
index 0000000000..2549a7a971
--- /dev/null
+++ b/spec/ruby/core/process/maxgroups_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ describe "Process.maxgroups" do
+ it "returns the maximum number of gids allowed in the supplemental group access list" do
+ Process.maxgroups.should be_kind_of(Integer)
+ end
+
+ it "sets the maximum number of gids allowed in the supplemental group access list" do
+ n = Process.maxgroups
+ begin
+ Process.maxgroups = n - 1
+ Process.maxgroups.should == n - 1
+ ensure
+ Process.maxgroups = n
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/pid_spec.rb b/spec/ruby/core/process/pid_spec.rb
new file mode 100644
index 0000000000..2d008bc4b7
--- /dev/null
+++ b/spec/ruby/core/process/pid_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Process.pid" do
+ it "returns the process id of this process" do
+ pid = Process.pid
+ pid.should be_kind_of(Integer)
+ Process.pid.should == pid
+ end
+end
diff --git a/spec/ruby/core/process/ppid_spec.rb b/spec/ruby/core/process/ppid_spec.rb
new file mode 100644
index 0000000000..47c32a8591
--- /dev/null
+++ b/spec/ruby/core/process/ppid_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Process.ppid" do
+ platform_is_not :windows do
+ it "returns the process id of the parent of this process" do
+ ruby_exe("puts Process.ppid").should == "#{Process.pid}\n"
+ end
+ end
+end
diff --git a/spec/ruby/core/process/set_proctitle_spec.rb b/spec/ruby/core/process/set_proctitle_spec.rb
new file mode 100644
index 0000000000..d022f2021c
--- /dev/null
+++ b/spec/ruby/core/process/set_proctitle_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+# Note that there's no way to get the current process title defined as a spec
+# somewhere. Process.setproctitle explicitly does not change `$0` so the only
+# way to get the process title is to shell out.
+describe 'Process.setproctitle' do
+ platform_is :linux, :darwin do
+ before :each do
+ @old_title = $0
+ end
+
+ after :each do
+ Process.setproctitle(@old_title)
+ end
+
+ it 'should set the process title' do
+ title = 'rubyspec-proctitle-test'
+
+ Process.setproctitle(title).should == title
+ `ps -ocommand= -p#{$$}`.should include(title)
+ end
+ end
+end
diff --git a/spec/ruby/core/process/setpgid_spec.rb b/spec/ruby/core/process/setpgid_spec.rb
new file mode 100644
index 0000000000..be724e9007
--- /dev/null
+++ b/spec/ruby/core/process/setpgid_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Process.setpgid" do
+ platform_is_not :windows do
+ # Must use fork as setpgid(2) gives EACCESS after execve()
+ it "sets the process group id of the specified process" do
+ rd, wr = IO.pipe
+
+ pid = Process.fork do
+ wr.close
+ rd.read
+ rd.close
+ Process.exit!
+ end
+
+ rd.close
+
+ begin
+ Process.getpgid(pid).should == Process.getpgrp
+ Process.setpgid(mock_int(pid), mock_int(pid)).should == 0
+ Process.getpgid(pid).should == pid
+ ensure
+ wr.write ' '
+ wr.close
+ Process.wait pid
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/setpgrp_spec.rb b/spec/ruby/core/process/setpgrp_spec.rb
new file mode 100644
index 0000000000..800668008d
--- /dev/null
+++ b/spec/ruby/core/process/setpgrp_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+# TODO: put these in the right files.
+describe "Process.setpgrp and Process.getpgrp" do
+ platform_is_not :windows do
+ it "sets and gets the process group ID of the calling process" do
+ # there are two synchronization points here:
+ # One for the child to let the parent know that it has finished
+ # setting its process group;
+ # and another for the parent to let the child know that it's ok to die.
+ read1, write1 = IO.pipe
+ read2, write2 = IO.pipe
+ pid = Process.fork do
+ read1.close
+ write2.close
+ Process.setpgrp
+ write1 << Process.getpgrp
+ write1.close
+ read2.read(1)
+ read2.close
+ Process.exit!
+ end
+ write1.close
+ read2.close
+ pgid = read1.read # wait for child to change process groups
+ read1.close
+
+ begin
+ Process.getpgid(pid).should == pgid.to_i
+ ensure
+ write2 << "!"
+ write2.close
+ Process.wait pid
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/setpriority_spec.rb b/spec/ruby/core/process/setpriority_spec.rb
new file mode 100644
index 0000000000..4d60973429
--- /dev/null
+++ b/spec/ruby/core/process/setpriority_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+
+describe "Process.setpriority" do
+ platform_is_not :windows do
+ it "sets the scheduling priority for a specified process" do
+ priority = Process.getpriority(Process::PRIO_PROCESS, 0)
+
+ out = ruby_exe(fixture(__FILE__, "setpriority.rb"), args: "process")
+ out = out.lines.map { |l| Integer(l) }
+ pr = out[0]
+ out.should == [pr, 0, pr+1]
+
+ Process.getpriority(Process::PRIO_PROCESS, 0).should == priority
+ end
+
+ # Darwin and FreeBSD don't seem to handle these at all, getting all out of
+ # whack with either permission errors or just the wrong value
+ platform_is_not :darwin, :freebsd do
+ it "sets the scheduling priority for a specified process group" do
+ priority = Process.getpriority(Process::PRIO_PGRP, 0)
+
+ out = ruby_exe(fixture(__FILE__, "setpriority.rb"), args: "group")
+ out = out.lines.map { |l| Integer(l) }
+ pr = out[0]
+ out.should == [pr, 0, pr+1]
+
+ Process.getpriority(Process::PRIO_PGRP, 0).should == priority
+ end
+ end
+
+ as_superuser do
+ guard -> {
+ prio = Process.getpriority(Process::PRIO_USER, 0)
+ # The nice value is a value in the range -20 to 19.
+ # This test tries to change the nice value to +-1, so it cannot run if prio == -20 || prio == 19.
+ if -20 < prio && prio < 19
+ begin
+ # Check if we can lower the nice value or not.
+ #
+ # We are not always able to do it even as a root.
+ # Docker container is not always able to do it depending upon the configuration,
+ # which cannot know from the container itself.
+ Process.setpriority(Process::PRIO_USER, 0, prio - 1)
+ Process.setpriority(Process::PRIO_USER, 0, prio)
+ true
+ rescue Errno::EACCES
+ false
+ end
+ end
+ } do
+ it "sets the scheduling priority for a specified user" do
+ prio = Process.getpriority(Process::PRIO_USER, 0)
+ Process.setpriority(Process::PRIO_USER, 0, prio + 1).should == 0
+ Process.getpriority(Process::PRIO_USER, 0).should == (prio + 1)
+ Process.setpriority(Process::PRIO_USER, 0, prio).should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/setrlimit_spec.rb b/spec/ruby/core/process/setrlimit_spec.rb
new file mode 100644
index 0000000000..b92f98fd40
--- /dev/null
+++ b/spec/ruby/core/process/setrlimit_spec.rb
@@ -0,0 +1,241 @@
+require_relative '../../spec_helper'
+
+describe "Process.setrlimit" do
+ platform_is_not :windows do
+ context "when passed an Object" do
+ before do
+ @resource = Process::RLIMIT_CORE
+ @limit, @max = Process.getrlimit @resource
+ end
+
+ it "calls #to_int to convert resource to an Integer" do
+ Process.setrlimit(mock_int(@resource), @limit, @max).should be_nil
+ end
+
+ it "raises a TypeError if #to_int for resource does not return an Integer" do
+ obj = mock("process getrlimit integer")
+ obj.should_receive(:to_int).and_return(nil)
+
+ -> { Process.setrlimit(obj, @limit, @max) }.should raise_error(TypeError)
+ end
+
+ it "calls #to_int to convert the soft limit to an Integer" do
+ Process.setrlimit(@resource, mock_int(@limit), @max).should be_nil
+ end
+
+ it "raises a TypeError if #to_int for resource does not return an Integer" do
+ obj = mock("process getrlimit integer")
+ obj.should_receive(:to_int).and_return(nil)
+
+ -> { Process.setrlimit(@resource, obj, @max) }.should raise_error(TypeError)
+ end
+
+ it "calls #to_int to convert the hard limit to an Integer" do
+ Process.setrlimit(@resource, @limit, mock_int(@max)).should be_nil
+ end
+
+ it "raises a TypeError if #to_int for resource does not return an Integer" do
+ obj = mock("process getrlimit integer")
+ obj.should_receive(:to_int).and_return(nil)
+
+ -> { Process.setrlimit(@resource, @limit, obj) }.should raise_error(TypeError)
+ end
+ end
+
+ context "when passed a Symbol" do
+ platform_is_not :openbsd do
+ it "coerces :AS into RLIMIT_AS" do
+ Process.setrlimit(:AS, *Process.getrlimit(Process::RLIMIT_AS)).should be_nil
+ end
+ end
+
+ it "coerces :CORE into RLIMIT_CORE" do
+ Process.setrlimit(:CORE, *Process.getrlimit(Process::RLIMIT_CORE)).should be_nil
+ end
+
+ it "coerces :CPU into RLIMIT_CPU" do
+ Process.setrlimit(:CPU, *Process.getrlimit(Process::RLIMIT_CPU)).should be_nil
+ end
+
+ it "coerces :DATA into RLIMIT_DATA" do
+ Process.setrlimit(:DATA, *Process.getrlimit(Process::RLIMIT_DATA)).should be_nil
+ end
+
+ it "coerces :FSIZE into RLIMIT_FSIZE" do
+ Process.setrlimit(:FSIZE, *Process.getrlimit(Process::RLIMIT_FSIZE)).should be_nil
+ end
+
+ it "coerces :NOFILE into RLIMIT_NOFILE" do
+ Process.setrlimit(:NOFILE, *Process.getrlimit(Process::RLIMIT_NOFILE)).should be_nil
+ end
+
+ it "coerces :STACK into RLIMIT_STACK" do
+ Process.setrlimit(:STACK, *Process.getrlimit(Process::RLIMIT_STACK)).should be_nil
+ end
+
+ platform_is_not :solaris, :aix do
+ it "coerces :MEMLOCK into RLIMIT_MEMLOCK" do
+ Process.setrlimit(:MEMLOCK, *Process.getrlimit(Process::RLIMIT_MEMLOCK)).should be_nil
+ end
+ end
+
+ platform_is_not :solaris do
+ it "coerces :NPROC into RLIMIT_NPROC" do
+ Process.setrlimit(:NPROC, *Process.getrlimit(Process::RLIMIT_NPROC)).should be_nil
+ end
+
+ it "coerces :RSS into RLIMIT_RSS" do
+ Process.setrlimit(:RSS, *Process.getrlimit(Process::RLIMIT_RSS)).should be_nil
+ end
+ end
+
+ platform_is :netbsd, :freebsd do
+ it "coerces :SBSIZE into RLIMIT_SBSIZE" do
+ Process.setrlimit(:SBSIZE, *Process.getrlimit(Process::RLIMIT_SBSIZE)).should be_nil
+ end
+ end
+
+ platform_is :linux do
+ it "coerces :RTPRIO into RLIMIT_RTPRIO" do
+ Process.setrlimit(:RTPRIO, *Process.getrlimit(Process::RLIMIT_RTPRIO)).should be_nil
+ end
+
+ guard -> { defined?(Process::RLIMIT_RTTIME) } do
+ it "coerces :RTTIME into RLIMIT_RTTIME" do
+ Process.setrlimit(:RTTIME, *Process.getrlimit(Process::RLIMIT_RTTIME)).should be_nil
+ end
+ end
+
+ it "coerces :SIGPENDING into RLIMIT_SIGPENDING" do
+ Process.setrlimit(:SIGPENDING, *Process.getrlimit(Process::RLIMIT_SIGPENDING)).should be_nil
+ end
+
+ it "coerces :MSGQUEUE into RLIMIT_MSGQUEUE" do
+ Process.setrlimit(:MSGQUEUE, *Process.getrlimit(Process::RLIMIT_MSGQUEUE)).should be_nil
+ end
+
+ it "coerces :NICE into RLIMIT_NICE" do
+ Process.setrlimit(:NICE, *Process.getrlimit(Process::RLIMIT_NICE)).should be_nil
+ end
+ end
+
+ it "raises ArgumentError when passed an unknown resource" do
+ -> { Process.setrlimit(:FOO, 1, 1) }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "when passed a String" do
+ platform_is_not :openbsd do
+ it "coerces 'AS' into RLIMIT_AS" do
+ Process.setrlimit("AS", *Process.getrlimit(Process::RLIMIT_AS)).should be_nil
+ end
+ end
+
+ it "coerces 'CORE' into RLIMIT_CORE" do
+ Process.setrlimit("CORE", *Process.getrlimit(Process::RLIMIT_CORE)).should be_nil
+ end
+
+ it "coerces 'CPU' into RLIMIT_CPU" do
+ Process.setrlimit("CPU", *Process.getrlimit(Process::RLIMIT_CPU)).should be_nil
+ end
+
+ it "coerces 'DATA' into RLIMIT_DATA" do
+ Process.setrlimit("DATA", *Process.getrlimit(Process::RLIMIT_DATA)).should be_nil
+ end
+
+ it "coerces 'FSIZE' into RLIMIT_FSIZE" do
+ Process.setrlimit("FSIZE", *Process.getrlimit(Process::RLIMIT_FSIZE)).should be_nil
+ end
+
+ it "coerces 'NOFILE' into RLIMIT_NOFILE" do
+ Process.setrlimit("NOFILE", *Process.getrlimit(Process::RLIMIT_NOFILE)).should be_nil
+ end
+
+ it "coerces 'STACK' into RLIMIT_STACK" do
+ Process.setrlimit("STACK", *Process.getrlimit(Process::RLIMIT_STACK)).should be_nil
+ end
+
+ platform_is_not :solaris, :aix do
+ it "coerces 'MEMLOCK' into RLIMIT_MEMLOCK" do
+ Process.setrlimit("MEMLOCK", *Process.getrlimit(Process::RLIMIT_MEMLOCK)).should be_nil
+ end
+ end
+
+ platform_is_not :solaris do
+ it "coerces 'NPROC' into RLIMIT_NPROC" do
+ Process.setrlimit("NPROC", *Process.getrlimit(Process::RLIMIT_NPROC)).should be_nil
+ end
+
+ it "coerces 'RSS' into RLIMIT_RSS" do
+ Process.setrlimit("RSS", *Process.getrlimit(Process::RLIMIT_RSS)).should be_nil
+ end
+ end
+
+ platform_is :netbsd, :freebsd do
+ it "coerces 'SBSIZE' into RLIMIT_SBSIZE" do
+ Process.setrlimit("SBSIZE", *Process.getrlimit(Process::RLIMIT_SBSIZE)).should be_nil
+ end
+ end
+
+ platform_is :linux do
+ it "coerces 'RTPRIO' into RLIMIT_RTPRIO" do
+ Process.setrlimit("RTPRIO", *Process.getrlimit(Process::RLIMIT_RTPRIO)).should be_nil
+ end
+
+ guard -> { defined?(Process::RLIMIT_RTTIME) } do
+ it "coerces 'RTTIME' into RLIMIT_RTTIME" do
+ Process.setrlimit("RTTIME", *Process.getrlimit(Process::RLIMIT_RTTIME)).should be_nil
+ end
+ end
+
+ it "coerces 'SIGPENDING' into RLIMIT_SIGPENDING" do
+ Process.setrlimit("SIGPENDING", *Process.getrlimit(Process::RLIMIT_SIGPENDING)).should be_nil
+ end
+
+ it "coerces 'MSGQUEUE' into RLIMIT_MSGQUEUE" do
+ Process.setrlimit("MSGQUEUE", *Process.getrlimit(Process::RLIMIT_MSGQUEUE)).should be_nil
+ end
+
+ it "coerces 'NICE' into RLIMIT_NICE" do
+ Process.setrlimit("NICE", *Process.getrlimit(Process::RLIMIT_NICE)).should be_nil
+ end
+ end
+
+ it "raises ArgumentError when passed an unknown resource" do
+ -> { Process.setrlimit("FOO", 1, 1) }.should raise_error(ArgumentError)
+ end
+ end
+
+ context "when passed on Object" do
+ before do
+ @resource = Process::RLIMIT_CORE
+ @limit, @max = Process.getrlimit @resource
+ end
+
+ it "calls #to_str to convert to a String" do
+ obj = mock("process getrlimit string")
+ obj.should_receive(:to_str).and_return("CORE")
+ obj.should_not_receive(:to_int)
+
+ Process.setrlimit(obj, @limit, @max).should be_nil
+ end
+
+ it "calls #to_int if #to_str does not return a String" do
+ obj = mock("process getrlimit string")
+ obj.should_receive(:to_str).and_return(nil)
+ obj.should_receive(:to_int).and_return(@resource)
+
+ Process.setrlimit(obj, @limit, @max).should be_nil
+ end
+ end
+ end
+
+ platform_is :windows do
+ it "is not implemented" do
+ Process.respond_to?(:setrlimit).should be_false
+ -> do
+ Process.setrlimit(nil, nil)
+ end.should raise_error NotImplementedError
+ end
+ end
+end
diff --git a/spec/ruby/core/process/setsid_spec.rb b/spec/ruby/core/process/setsid_spec.rb
new file mode 100644
index 0000000000..c83f912066
--- /dev/null
+++ b/spec/ruby/core/process/setsid_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Process.setsid" do
+ platform_is_not :windows do
+ it "establishes this process as a new session and process group leader" do
+ sid = Process.getsid
+
+ out = ruby_exe("p Process.getsid; p Process.setsid; p Process.getsid").lines
+ out[0].should == "#{sid}\n"
+ out[1].should == out[2]
+ out[2].should_not == "#{sid}\n"
+
+ sid.should == Process.getsid
+ end
+ end
+end
diff --git a/spec/ruby/core/process/spawn_spec.rb b/spec/ruby/core/process/spawn_spec.rb
new file mode 100644
index 0000000000..c8a58c4d04
--- /dev/null
+++ b/spec/ruby/core/process/spawn_spec.rb
@@ -0,0 +1,756 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+newline = "\n"
+platform_is :windows do
+ newline = "\r\n"
+end
+
+describe :process_spawn_does_not_close_std_streams, shared: true do
+ it "does not close STDIN" do
+ code = "puts STDIN.read"
+ cmd = "Process.wait Process.spawn(#{ruby_cmd(code).inspect}, #{@options.inspect})"
+ ruby_exe(cmd, args: "< #{fixture(__FILE__, "in.txt")} > #{@name}")
+ File.binread(@name).should == %[stdin#{newline}]
+ end
+
+ it "does not close STDOUT" do
+ code = "STDOUT.puts 'hello'"
+ cmd = "Process.wait Process.spawn(#{ruby_cmd(code).inspect}, #{@options.inspect})"
+ ruby_exe(cmd, args: "> #{@name}")
+ File.binread(@name).should == "hello#{newline}"
+ end
+
+ it "does not close STDERR" do
+ code = "STDERR.puts 'hello'"
+ cmd = "Process.wait Process.spawn(#{ruby_cmd(code).inspect}, #{@options.inspect})"
+ ruby_exe(cmd, args: "2> #{@name}")
+ File.binread(@name).should =~ /hello#{newline}/
+ end
+end
+
+describe "Process.spawn" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :each do
+ @name = tmp("process_spawn.txt")
+ @var = "$FOO"
+ platform_is :windows do
+ @var = "%FOO%"
+ end
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "executes the given command" do
+ -> { Process.wait Process.spawn("echo spawn") }.should output_to_fd("spawn\n")
+ end
+
+ it "returns the process ID of the new process as an Integer" do
+ pid = Process.spawn(*ruby_exe, "-e", "exit")
+ Process.wait pid
+ pid.should be_an_instance_of(Integer)
+ end
+
+ it "returns immediately" do
+ start = Time.now
+ pid = Process.spawn(*ruby_exe, "-e", "sleep 10")
+ (Time.now - start).should < 5
+ Process.kill :KILL, pid
+ Process.wait pid
+ end
+
+ # argv processing
+
+ describe "with a single argument" do
+ platform_is_not :windows do
+ it "subjects the specified command to shell expansion" do
+ -> { Process.wait Process.spawn("echo *") }.should_not output_to_fd("*\n")
+ end
+
+ it "creates an argument array with shell parsing semantics for whitespace" do
+ -> { Process.wait Process.spawn("echo a b c d") }.should output_to_fd("a b c d\n")
+ end
+ end
+
+ platform_is :windows do
+ # There is no shell expansion on Windows
+ it "does not subject the specified command to shell expansion on Windows" do
+ -> { Process.wait Process.spawn("echo *") }.should output_to_fd("*\n")
+ end
+
+ it "does not create an argument array with shell parsing semantics for whitespace on Windows" do
+ -> { Process.wait Process.spawn("echo a b c d") }.should output_to_fd("a b c d\n")
+ end
+ end
+
+ it "calls #to_str to convert the argument to a String" do
+ o = mock("to_str")
+ o.should_receive(:to_str).and_return("echo foo")
+ -> { Process.wait Process.spawn(o) }.should output_to_fd("foo\n")
+ end
+
+ it "raises an ArgumentError if the command includes a null byte" do
+ -> { Process.spawn "\000" }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if the argument does not respond to #to_str" do
+ -> { Process.spawn :echo }.should raise_error(TypeError)
+ end
+ end
+
+ describe "with multiple arguments" do
+ it "does not subject the arguments to shell expansion" do
+ -> { Process.wait Process.spawn("echo", "*") }.should output_to_fd("*\n")
+ end
+
+ it "preserves whitespace in passed arguments" do
+ out = "a b c d\n"
+ platform_is :windows do
+ # The echo command on Windows takes quotes literally
+ out = "\"a b c d\"\n"
+ end
+ -> { Process.wait Process.spawn("echo", "a b c d") }.should output_to_fd(out)
+ end
+
+ it "calls #to_str to convert the arguments to Strings" do
+ o = mock("to_str")
+ o.should_receive(:to_str).and_return("foo")
+ -> { Process.wait Process.spawn("echo", o) }.should output_to_fd("foo\n")
+ end
+
+ it "raises an ArgumentError if an argument includes a null byte" do
+ -> { Process.spawn "echo", "\000" }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if an argument does not respond to #to_str" do
+ -> { Process.spawn "echo", :foo }.should raise_error(TypeError)
+ end
+ end
+
+ describe "with a command array" do
+ it "uses the first element as the command name and the second as the argv[0] value" do
+ platform_is_not :windows do
+ -> { Process.wait Process.spawn(["/bin/sh", "argv_zero"], "-c", "echo $0") }.should output_to_fd("argv_zero\n")
+ end
+ platform_is :windows do
+ -> { Process.wait Process.spawn(["cmd.exe", "/C"], "/C", "echo", "argv_zero") }.should output_to_fd("argv_zero\n")
+ end
+ end
+
+ it "does not subject the arguments to shell expansion" do
+ -> { Process.wait Process.spawn(["echo", "echo"], "*") }.should output_to_fd("*\n")
+ end
+
+ it "preserves whitespace in passed arguments" do
+ out = "a b c d\n"
+ platform_is :windows do
+ # The echo command on Windows takes quotes literally
+ out = "\"a b c d\"\n"
+ end
+ -> { Process.wait Process.spawn(["echo", "echo"], "a b c d") }.should output_to_fd(out)
+ end
+
+ it "calls #to_ary to convert the argument to an Array" do
+ o = mock("to_ary")
+ platform_is_not :windows do
+ o.should_receive(:to_ary).and_return(["/bin/sh", "argv_zero"])
+ -> { Process.wait Process.spawn(o, "-c", "echo $0") }.should output_to_fd("argv_zero\n")
+ end
+ platform_is :windows do
+ o.should_receive(:to_ary).and_return(["cmd.exe", "/C"])
+ -> { Process.wait Process.spawn(o, "/C", "echo", "argv_zero") }.should output_to_fd("argv_zero\n")
+ end
+ end
+
+ it "calls #to_str to convert the first element to a String" do
+ o = mock("to_str")
+ o.should_receive(:to_str).and_return("echo")
+ -> { Process.wait Process.spawn([o, "echo"], "foo") }.should output_to_fd("foo\n")
+ end
+
+ it "calls #to_str to convert the second element to a String" do
+ o = mock("to_str")
+ o.should_receive(:to_str).and_return("echo")
+ -> { Process.wait Process.spawn(["echo", o], "foo") }.should output_to_fd("foo\n")
+ end
+
+ it "raises an ArgumentError if the Array does not have exactly two elements" do
+ -> { Process.spawn([]) }.should raise_error(ArgumentError)
+ -> { Process.spawn([:a]) }.should raise_error(ArgumentError)
+ -> { Process.spawn([:a, :b, :c]) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the Strings in the Array include a null byte" do
+ -> { Process.spawn ["\000", "echo"] }.should raise_error(ArgumentError)
+ -> { Process.spawn ["echo", "\000"] }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if an element in the Array does not respond to #to_str" do
+ -> { Process.spawn ["echo", :echo] }.should raise_error(TypeError)
+ -> { Process.spawn [:echo, "echo"] }.should raise_error(TypeError)
+ end
+ end
+
+ # env handling
+
+ after :each do
+ ENV.delete("FOO")
+ end
+
+ it "sets environment variables in the child environment" do
+ Process.wait Process.spawn({"FOO" => "BAR"}, "echo #{@var}>#{@name}")
+ File.read(@name).should == "BAR\n"
+ end
+
+ it "unsets environment variables whose value is nil" do
+ ENV["FOO"] = "BAR"
+ -> do
+ Process.wait Process.spawn({"FOO" => nil}, ruby_cmd("p ENV['FOO']"))
+ end.should output_to_fd("nil\n")
+ end
+
+ platform_is_not :windows do
+ it "uses the passed env['PATH'] to search the executable" do
+ dir = tmp("spawn_path_dir")
+ mkdir_p dir
+ begin
+ exe = 'process-spawn-executable-in-path'
+ path = "#{dir}/#{exe}"
+ File.write(path, "#!/bin/sh\necho $1")
+ File.chmod(0755, path)
+
+ env = { "PATH" => "#{dir}#{File::PATH_SEPARATOR}#{ENV['PATH']}" }
+ Process.wait Process.spawn(env, exe, 'OK', out: @name)
+ $?.should.success?
+ File.read(@name).should == "OK\n"
+ ensure
+ rm_r dir
+ end
+ end
+ end
+
+ it "calls #to_hash to convert the environment" do
+ o = mock("to_hash")
+ o.should_receive(:to_hash).and_return({"FOO" => "BAR"})
+ Process.wait Process.spawn(o, "echo #{@var}>#{@name}")
+ File.read(@name).should == "BAR\n"
+ end
+
+ it "calls #to_str to convert the environment keys" do
+ o = mock("to_str")
+ o.should_receive(:to_str).and_return("FOO")
+ Process.wait Process.spawn({o => "BAR"}, "echo #{@var}>#{@name}")
+ File.read(@name).should == "BAR\n"
+ end
+
+ it "calls #to_str to convert the environment values" do
+ o = mock("to_str")
+ o.should_receive(:to_str).and_return("BAR")
+ Process.wait Process.spawn({"FOO" => o}, "echo #{@var}>#{@name}")
+ File.read(@name).should == "BAR\n"
+ end
+
+ it "raises an ArgumentError if an environment key includes an equals sign" do
+ -> do
+ Process.spawn({"FOO=" => "BAR"}, "echo #{@var}>#{@name}")
+ end.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if an environment key includes a null byte" do
+ -> do
+ Process.spawn({"\000" => "BAR"}, "echo #{@var}>#{@name}")
+ end.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if an environment value includes a null byte" do
+ -> do
+ Process.spawn({"FOO" => "\000"}, "echo #{@var}>#{@name}")
+ end.should raise_error(ArgumentError)
+ end
+
+ # :unsetenv_others
+
+ before :each do
+ @minimal_env = {
+ "PATH" => ENV["PATH"],
+ "HOME" => ENV["HOME"]
+ }
+ @common_env_spawn_args = [@minimal_env, "echo #{@var}>#{@name}"]
+ end
+
+ platform_is_not :windows do
+ it "unsets other environment variables when given a true :unsetenv_others option" do
+ ENV["FOO"] = "BAR"
+ Process.wait Process.spawn(*@common_env_spawn_args, unsetenv_others: true)
+ $?.success?.should be_true
+ File.read(@name).should == "\n"
+ end
+ end
+
+ it "does not unset other environment variables when given a false :unsetenv_others option" do
+ ENV["FOO"] = "BAR"
+ Process.wait Process.spawn(*@common_env_spawn_args, unsetenv_others: false)
+ $?.success?.should be_true
+ File.read(@name).should == "BAR\n"
+ end
+
+ platform_is_not :windows do
+ it "does not unset environment variables included in the environment hash" do
+ env = @minimal_env.merge({"FOO" => "BAR"})
+ Process.wait Process.spawn(env, "echo #{@var}>#{@name}", unsetenv_others: true)
+ $?.success?.should be_true
+ File.read(@name).should == "BAR\n"
+ end
+ end
+
+ # :pgroup
+
+ platform_is_not :windows do
+ it "joins the current process group by default" do
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"))
+ end.should output_to_fd(Process.getpgid(Process.pid).to_s)
+ end
+
+ it "joins the current process if pgroup: false" do
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: false)
+ end.should output_to_fd(Process.getpgid(Process.pid).to_s)
+ end
+
+ it "joins the current process if pgroup: nil" do
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: nil)
+ end.should output_to_fd(Process.getpgid(Process.pid).to_s)
+ end
+
+ it "joins a new process group if pgroup: true" do
+ process = -> do
+ Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: true)
+ end
+
+ process.should_not output_to_fd(Process.getpgid(Process.pid).to_s)
+ process.should output_to_fd(/\d+/)
+ end
+
+ it "joins a new process group if pgroup: 0" do
+ process = -> do
+ Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: 0)
+ end
+
+ process.should_not output_to_fd(Process.getpgid(Process.pid).to_s)
+ process.should output_to_fd(/\d+/)
+ end
+
+ it "joins the specified process group if pgroup: pgid" do
+ pgid = Process.getpgid(Process.pid)
+ # The process group is not available on all platforms.
+ # See "man proc" - /proc/[pid]/stat - (5) pgrp
+ # In Travis aarch64 environment, the value is 0.
+ #
+ # $ cat /proc/[pid]/stat
+ # 19179 (ruby) S 19160 0 0 ...
+ unless pgid.zero?
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print Process.getpgid(Process.pid)"), pgroup: pgid)
+ end.should output_to_fd(pgid.to_s)
+ else
+ skip "The process group is not available."
+ end
+ end
+
+ it "raises an ArgumentError if given a negative :pgroup option" do
+ -> { Process.spawn("echo", pgroup: -1) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if given a symbol as :pgroup option" do
+ -> { Process.spawn("echo", pgroup: :true) }.should raise_error(TypeError)
+ end
+ end
+
+ platform_is :windows do
+ it "raises an ArgumentError if given :pgroup option" do
+ -> { Process.spawn("echo", pgroup: false) }.should raise_error(ArgumentError)
+ end
+ end
+
+ # :rlimit_core
+ # :rlimit_cpu
+ # :rlimit_data
+
+ # :chdir
+
+ it "uses the current working directory as its working directory" do
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print Dir.pwd"))
+ end.should output_to_fd(Dir.pwd)
+ end
+
+ describe "when passed :chdir" do
+ before do
+ @dir = tmp("spawn_chdir", false)
+ Dir.mkdir @dir
+ end
+
+ after do
+ rm_r @dir
+ end
+
+ it "changes to the directory passed for :chdir" do
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: @dir)
+ end.should output_to_fd(@dir)
+ end
+
+ it "calls #to_path to convert the :chdir value" do
+ dir = mock("spawn_to_path")
+ dir.should_receive(:to_path).and_return(@dir)
+
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: dir)
+ end.should output_to_fd(@dir)
+ end
+ end
+
+ # chdir
+
+ platform_is :linux do
+ describe "inside Dir.chdir" do
+ def child_pids(pid)
+ `pgrep -P #{pid}`.lines.map { |child| Integer(child) }
+ end
+
+ it "does not create extra process without chdir" do
+ pid = Process.spawn("sleep 10")
+ begin
+ child_pids(pid).size.should == 0
+ ensure
+ Process.kill("TERM", pid)
+ Process.wait(pid)
+ end
+ end
+
+ it "kills extra chdir processes" do
+ pid = nil
+ Dir.chdir("/") do
+ pid = Process.spawn("sleep 10")
+ end
+
+ children = child_pids(pid)
+ children.size.should <= 1
+
+ Process.kill("TERM", pid)
+ Process.wait(pid)
+
+ if children.size > 0
+ # wait a bit for children to die
+ sleep(1)
+
+ children.each do |child|
+ -> do
+ Process.kill("TERM", child)
+ end.should raise_error(Errno::ESRCH)
+ end
+ end
+ end
+ end
+ end
+
+ # :umask
+
+ it "uses the current umask by default" do
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print File.umask"))
+ end.should output_to_fd(File.umask.to_s)
+ end
+
+ platform_is_not :windows do
+ it "sets the umask if given the :umask option" do
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print File.umask"), umask: 146)
+ end.should output_to_fd("146")
+ end
+ end
+
+ # redirection
+
+ it 'redirects to the wrapped IO using wrapped_io.to_io if out: wrapped_io' do
+ File.open(@name, 'w') do |file|
+ -> do
+ wrapped_io = mock('wrapped IO')
+ wrapped_io.should_receive(:to_io).and_return(file)
+ Process.wait Process.spawn('echo Hello World', out: wrapped_io)
+ end.should output_to_fd("Hello World\n", file)
+ end
+ end
+
+ it "redirects STDOUT to the given file descriptor if out: Integer" do
+ File.open(@name, 'w') do |file|
+ -> do
+ Process.wait Process.spawn("echo glark", out: file.fileno)
+ end.should output_to_fd("glark\n", file)
+ end
+ end
+
+ it "redirects STDOUT to the given file if out: IO" do
+ File.open(@name, 'w') do |file|
+ -> do
+ Process.wait Process.spawn("echo glark", out: file)
+ end.should output_to_fd("glark\n", file)
+ end
+ end
+
+ it "redirects STDOUT to the given file if out: String" do
+ Process.wait Process.spawn("echo glark", out: @name)
+ File.read(@name).should == "glark\n"
+ end
+
+ it "redirects STDOUT to the given file if out: [String name, String mode]" do
+ Process.wait Process.spawn("echo glark", out: [@name, 'w'])
+ File.read(@name).should == "glark\n"
+ end
+
+ it "redirects STDERR to the given file descriptor if err: Integer" do
+ File.open(@name, 'w') do |file|
+ -> do
+ Process.wait Process.spawn("echo glark>&2", err: file.fileno)
+ end.should output_to_fd("glark\n", file)
+ end
+ end
+
+ it "redirects STDERR to the given file descriptor if err: IO" do
+ File.open(@name, 'w') do |file|
+ -> do
+ Process.wait Process.spawn("echo glark>&2", err: file)
+ end.should output_to_fd("glark\n", file)
+ end
+ end
+
+ it "redirects STDERR to the given file if err: String" do
+ Process.wait Process.spawn("echo glark>&2", err: @name)
+ File.read(@name).should == "glark\n"
+ end
+
+ it "redirects STDERR to child STDOUT if :err => [:child, :out]" do
+ File.open(@name, 'w') do |file|
+ -> do
+ Process.wait Process.spawn("echo glark>&2", :out => file, :err => [:child, :out])
+ end.should output_to_fd("glark\n", file)
+ end
+ end
+
+ it "redirects both STDERR and STDOUT to the given file descriptor" do
+ File.open(@name, 'w') do |file|
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print(:glark); STDOUT.flush; STDERR.print(:bang)"),
+ [:out, :err] => file.fileno)
+ end.should output_to_fd("glarkbang", file)
+ end
+ end
+
+ it "redirects both STDERR and STDOUT to the given IO" do
+ File.open(@name, 'w') do |file|
+ -> do
+ Process.wait Process.spawn(ruby_cmd("print(:glark); STDOUT.flush; STDERR.print(:bang)"),
+ [:out, :err] => file)
+ end.should output_to_fd("glarkbang", file)
+ end
+ end
+
+ it "redirects both STDERR and STDOUT at the time to the given name" do
+ touch @name
+ Process.wait Process.spawn(ruby_cmd("print(:glark); STDOUT.flush; STDERR.print(:bang)"), [:out, :err] => @name)
+ File.read(@name).should == "glarkbang"
+ end
+
+ platform_is_not :windows, :android do
+ it "closes STDERR in the child if :err => :close" do
+ File.open(@name, 'w') do |file|
+ -> do
+ code = "begin; STDOUT.puts 'out'; STDERR.puts 'hello'; rescue => e; puts 'rescued'; end"
+ Process.wait Process.spawn(ruby_cmd(code), :out => file, :err => :close)
+ end.should output_to_fd("out\nrescued\n", file)
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ it "redirects non-default file descriptor to itself" do
+ File.open(@name, 'w') do |file|
+ -> do
+ Process.wait Process.spawn(
+ ruby_cmd("f = IO.new(#{file.fileno}, 'w'); f.print(:bang); f.flush"), file.fileno => file.fileno)
+ end.should output_to_fd("bang", file)
+ end
+ end
+ end
+
+ it "redirects default file descriptor to itself" do
+ -> do
+ Process.wait Process.spawn(
+ ruby_cmd("f = IO.new(#{STDOUT.fileno}, 'w'); f.print(:bang); f.flush"), STDOUT.fileno => STDOUT.fileno)
+ end.should output_to_fd("bang", STDOUT)
+ end
+
+ # :close_others
+
+ platform_is_not :windows do
+ context "defaults :close_others to" do
+ it "false" do
+ IO.pipe do |r, w|
+ w.close_on_exec = false
+ code = "io = IO.new(#{w.fileno}); io.puts('inherited'); io.close"
+ pid = Process.spawn(ruby_cmd(code))
+ w.close
+ Process.wait(pid)
+ r.read.should == "inherited\n"
+ end
+ end
+ end
+
+ context "when passed close_others: true" do
+ before :each do
+ @options = { close_others: true }
+ end
+
+ it "closes file descriptors >= 3 in the child process even if fds are set close_on_exec=false" do
+ touch @name
+ IO.pipe do |r, w|
+ r.close_on_exec = false
+ w.close_on_exec = false
+
+ begin
+ pid = Process.spawn(ruby_cmd("while File.exist? '#{@name}'; sleep 0.1; end"), @options)
+ w.close
+ r.read(1).should == nil
+ ensure
+ rm_r @name
+ Process.wait(pid) if pid
+ end
+ end
+ end
+
+ it_should_behave_like :process_spawn_does_not_close_std_streams
+ end
+
+ context "when passed close_others: false" do
+ before :each do
+ @options = { close_others: false }
+ end
+
+ it "closes file descriptors >= 3 in the child process because they are set close_on_exec by default" do
+ touch @name
+ IO.pipe do |r, w|
+ begin
+ pid = Process.spawn(ruby_cmd("while File.exist? '#{@name}'; sleep 0.1; end"), @options)
+ w.close
+ r.read(1).should == nil
+ ensure
+ rm_r @name
+ Process.wait(pid) if pid
+ end
+ end
+ end
+
+ it "does not close file descriptors >= 3 in the child process if fds are set close_on_exec=false" do
+ IO.pipe do |r, w|
+ r.close_on_exec = false
+ w.close_on_exec = false
+
+ code = "fd = IO.for_fd(#{w.fileno}); fd.autoclose = false; fd.write 'abc'; fd.close"
+ pid = Process.spawn(ruby_cmd(code), @options)
+ begin
+ w.close
+ r.read.should == 'abc'
+ ensure
+ Process.wait(pid)
+ end
+ end
+ end
+
+ it_should_behave_like :process_spawn_does_not_close_std_streams
+ end
+ end
+
+ # error handling
+
+ it "raises an ArgumentError if passed no command arguments" do
+ -> { Process.spawn }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed env or options but no command arguments" do
+ -> { Process.spawn({}) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed env and options but no command arguments" do
+ -> { Process.spawn({}, {}) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an Errno::ENOENT for an empty string" do
+ -> { Process.spawn "" }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises an Errno::ENOENT if the command does not exist" do
+ -> { Process.spawn "nonesuch" }.should raise_error(Errno::ENOENT)
+ end
+
+ unless File.executable?(__FILE__) # Some FS (e.g. vboxfs) locate all files executable
+ platform_is_not :windows do
+ it "raises an Errno::EACCES when the file does not have execute permissions" do
+ -> { Process.spawn __FILE__ }.should raise_error(Errno::EACCES)
+ end
+ end
+
+ platform_is :windows do
+ it "raises Errno::EACCES or Errno::ENOEXEC when the file is not an executable file" do
+ -> { Process.spawn __FILE__ }.should raise_error(SystemCallError) { |e|
+ [Errno::EACCES, Errno::ENOEXEC].should include(e.class)
+ }
+ end
+ end
+ end
+
+ it "raises an Errno::EACCES or Errno::EISDIR when passed a directory" do
+ -> { Process.spawn File.dirname(__FILE__) }.should raise_error(SystemCallError) { |e|
+ [Errno::EACCES, Errno::EISDIR].should include(e.class)
+ }
+ end
+
+ it "raises an ArgumentError when passed a string key in options" do
+ -> { Process.spawn("echo", "chdir" => Dir.pwd) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed an unknown option key" do
+ -> { Process.spawn("echo", nonesuch: :foo) }.should raise_error(ArgumentError)
+ end
+
+ platform_is_not :windows, :aix do
+ describe "with Integer option keys" do
+ before :each do
+ @name = tmp("spawn_fd_map.txt")
+ @io = new_io @name, "w+"
+ @io.sync = true
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "maps the key to a file descriptor in the child that inherits the file descriptor from the parent specified by the value" do
+ File.open(__FILE__, "r") do |f|
+ child_fd = f.fileno
+ args = ruby_cmd(fixture(__FILE__, "map_fd.rb"), args: [child_fd.to_s])
+ pid = Process.spawn(*args, { child_fd => @io })
+ Process.waitpid pid
+ @io.rewind
+
+ @io.read.should == "writing to fd: #{child_fd}"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/status/bit_and_spec.rb b/spec/ruby/core/process/status/bit_and_spec.rb
new file mode 100644
index 0000000000..97f768fdc1
--- /dev/null
+++ b/spec/ruby/core/process/status/bit_and_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#&" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/status/coredump_spec.rb b/spec/ruby/core/process/status/coredump_spec.rb
new file mode 100644
index 0000000000..fbbaf926f7
--- /dev/null
+++ b/spec/ruby/core/process/status/coredump_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#coredump?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/status/equal_value_spec.rb b/spec/ruby/core/process/status/equal_value_spec.rb
new file mode 100644
index 0000000000..d8a2be26b8
--- /dev/null
+++ b/spec/ruby/core/process/status/equal_value_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#==" do
+ it "returns true when compared to the integer status of an exited child" do
+ ruby_exe("exit(29)", exit_status: 29)
+ $?.to_i.should == $?
+ $?.should == $?.to_i
+ end
+
+ it "returns true when compared to the integer status of a terminated child" do
+ ruby_exe("Process.kill(:KILL, $$); exit(29)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ $?.to_i.should == $?
+ $?.should == $?.to_i
+ end
+end
diff --git a/spec/ruby/core/process/status/exited_spec.rb b/spec/ruby/core/process/status/exited_spec.rb
new file mode 100644
index 0000000000..a61292b146
--- /dev/null
+++ b/spec/ruby/core/process/status/exited_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#exited?" do
+ describe "for a child that exited normally" do
+ before :each do
+ ruby_exe("exit(0)")
+ end
+
+ it "returns true" do
+ $?.exited?.should be_true
+ end
+ end
+
+
+ describe "for a terminated child" do
+ before :each do
+ ruby_exe("Process.kill(:KILL, $$); exit(42)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ end
+
+ platform_is_not :windows do
+ it "returns false" do
+ $?.exited?.should be_false
+ end
+ end
+
+ platform_is :windows do
+ it "always returns true" do
+ $?.exited?.should be_true
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/status/exitstatus_spec.rb b/spec/ruby/core/process/status/exitstatus_spec.rb
new file mode 100644
index 0000000000..5c86c2b3c8
--- /dev/null
+++ b/spec/ruby/core/process/status/exitstatus_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#exitstatus" do
+ before :each do
+ ruby_exe("exit(42)", exit_status: 42)
+ end
+
+ it "returns the process exit code" do
+ $?.exitstatus.should == 42
+ end
+
+ describe "for a child that raised SignalException" do
+ before :each do
+ ruby_exe("Process.kill(:KILL, $$); exit(42)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ end
+
+ platform_is_not :windows do
+ # The exitstatus is not set in these cases. See the termsig_spec
+ # for info on where the signal number (SIGTERM) is available.
+ it "returns nil" do
+ $?.exitstatus.should == nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/status/inspect_spec.rb b/spec/ruby/core/process/status/inspect_spec.rb
new file mode 100644
index 0000000000..03f0479f2c
--- /dev/null
+++ b/spec/ruby/core/process/status/inspect_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#inspect" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/status/pid_spec.rb b/spec/ruby/core/process/status/pid_spec.rb
new file mode 100644
index 0000000000..9965fc3bdf
--- /dev/null
+++ b/spec/ruby/core/process/status/pid_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+platform_is_not :windows do
+ describe "Process::Status#pid" do
+
+ before :each do
+ @pid = ruby_exe("print $$").to_i
+ end
+
+ it "returns the pid of the process" do
+ $?.pid.should == @pid
+ end
+
+ end
+end
diff --git a/spec/ruby/core/process/status/right_shift_spec.rb b/spec/ruby/core/process/status/right_shift_spec.rb
new file mode 100644
index 0000000000..e9dda437e8
--- /dev/null
+++ b/spec/ruby/core/process/status/right_shift_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#>>" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/status/signaled_spec.rb b/spec/ruby/core/process/status/signaled_spec.rb
new file mode 100644
index 0000000000..c0de7b8006
--- /dev/null
+++ b/spec/ruby/core/process/status/signaled_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#signaled?" do
+ describe "for a cleanly exited child" do
+ before :each do
+ ruby_exe("exit(0)")
+ end
+
+ it "returns false" do
+ $?.signaled?.should be_false
+ end
+ end
+
+ describe "for a terminated child" do
+ before :each do
+ ruby_exe("Process.kill(:KILL, $$); exit(42)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ end
+
+ platform_is_not :windows do
+ it "returns true" do
+ $?.signaled?.should be_true
+ end
+ end
+
+ platform_is :windows do
+ it "always returns false" do
+ $?.signaled?.should be_false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/status/stopped_spec.rb b/spec/ruby/core/process/status/stopped_spec.rb
new file mode 100644
index 0000000000..bebd441d6f
--- /dev/null
+++ b/spec/ruby/core/process/status/stopped_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#stopped?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/status/stopsig_spec.rb b/spec/ruby/core/process/status/stopsig_spec.rb
new file mode 100644
index 0000000000..b2a7c5d9e2
--- /dev/null
+++ b/spec/ruby/core/process/status/stopsig_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#stopsig" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/status/success_spec.rb b/spec/ruby/core/process/status/success_spec.rb
new file mode 100644
index 0000000000..3589cc611f
--- /dev/null
+++ b/spec/ruby/core/process/status/success_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#success?" do
+ describe "for a child that exited normally" do
+ before :each do
+ ruby_exe("exit(0)")
+ end
+
+ it "returns true" do
+ $?.success?.should be_true
+ end
+ end
+
+ describe "for a child that exited with a non zero status" do
+ before :each do
+ ruby_exe("exit(42)", exit_status: 42)
+ end
+
+ it "returns false" do
+ $?.success?.should be_false
+ end
+ end
+
+ describe "for a child that was terminated" do
+ before :each do
+ ruby_exe("Process.kill(:KILL, $$); exit(42)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ end
+
+ platform_is_not :windows do
+ it "returns nil" do
+ $?.success?.should be_nil
+ end
+ end
+
+ platform_is :windows do
+ it "always returns true" do
+ $?.success?.should be_true
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/status/termsig_spec.rb b/spec/ruby/core/process/status/termsig_spec.rb
new file mode 100644
index 0000000000..5d286950f8
--- /dev/null
+++ b/spec/ruby/core/process/status/termsig_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#termsig" do
+ describe "for a child that exited normally" do
+ before :each do
+ ruby_exe("exit(0)")
+ end
+
+ it "returns true" do
+ $?.termsig.should be_nil
+ end
+ end
+
+ describe "for a child that raised SignalException" do
+ before :each do
+ ruby_exe("raise SignalException, 'SIGTERM'", exit_status: :SIGTERM)
+ end
+
+ platform_is_not :windows do
+ it "returns the signal" do
+ $?.termsig.should == Signal.list["TERM"]
+ end
+ end
+ end
+
+ describe "for a child that was sent a signal" do
+ before :each do
+ ruby_exe("Process.kill(:KILL, $$); exit(42)", exit_status: platform_is(:windows) ? 0 : :SIGKILL)
+ end
+
+ platform_is_not :windows do
+ it "returns the signal" do
+ $?.termsig.should == Signal.list["KILL"]
+ end
+ end
+
+ platform_is :windows do
+ it "always returns nil" do
+ $?.termsig.should be_nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/status/to_i_spec.rb b/spec/ruby/core/process/status/to_i_spec.rb
new file mode 100644
index 0000000000..39f8e2d84c
--- /dev/null
+++ b/spec/ruby/core/process/status/to_i_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#to_i" do
+ it "returns an integer when the child exits" do
+ ruby_exe('exit 48', exit_status: 48)
+ $?.to_i.should be_an_instance_of(Integer)
+ end
+
+ it "returns an integer when the child is signaled" do
+ ruby_exe('raise SignalException, "TERM"', exit_status: platform_is(:windows) ? 3 : :SIGTERM)
+ $?.to_i.should be_an_instance_of(Integer)
+ end
+end
diff --git a/spec/ruby/core/process/status/to_int_spec.rb b/spec/ruby/core/process/status/to_int_spec.rb
new file mode 100644
index 0000000000..fb596c1bfb
--- /dev/null
+++ b/spec/ruby/core/process/status/to_int_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#to_int" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/status/to_s_spec.rb b/spec/ruby/core/process/status/to_s_spec.rb
new file mode 100644
index 0000000000..5c0d4cd30c
--- /dev/null
+++ b/spec/ruby/core/process/status/to_s_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Status#to_s" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/status/wait_spec.rb b/spec/ruby/core/process/status/wait_spec.rb
new file mode 100644
index 0000000000..b9d80e31f4
--- /dev/null
+++ b/spec/ruby/core/process/status/wait_spec.rb
@@ -0,0 +1,102 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+ruby_version_is "3.0" do
+ describe "Process::Status.wait" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :all do
+ begin
+ leaked = Process.waitall
+ # Ruby-space should not see PIDs used by mjit
+ raise "subprocesses leaked before wait specs: #{leaked}" unless leaked.empty?
+ rescue NotImplementedError
+ end
+ end
+
+ it "returns a status with pid -1 if there are no child processes" do
+ Process::Status.wait.pid.should == -1
+ end
+
+ platform_is_not :windows do
+ it "returns a status with its child pid" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ status = Process::Status.wait
+ status.should be_an_instance_of(Process::Status)
+ status.pid.should == pid
+ end
+
+ it "should not set $? to the Process::Status" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ status = Process::Status.wait
+ $?.should_not equal(status)
+ end
+
+ it "should not change the value of $?" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ Process.wait
+ status = $?
+ Process::Status.wait
+ status.should equal($?)
+ end
+
+ it "waits for any child process if no pid is given" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ Process::Status.wait.pid.should == pid
+ -> { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
+ end
+
+ it "waits for a specific child if a pid is given" do
+ pid1 = Process.spawn(ruby_cmd('exit'))
+ pid2 = Process.spawn(ruby_cmd('exit'))
+ Process::Status.wait(pid2).pid.should == pid2
+ Process::Status.wait(pid1).pid.should == pid1
+ -> { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
+ -> { Process.kill(0, pid2) }.should raise_error(Errno::ESRCH)
+ end
+
+ it "coerces the pid to an Integer" do
+ pid1 = Process.spawn(ruby_cmd('exit'))
+ Process::Status.wait(mock_int(pid1)).pid.should == pid1
+ -> { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
+ end
+
+ # This spec is probably system-dependent.
+ it "waits for a child whose process group ID is that of the calling process" do
+ pid1 = Process.spawn(ruby_cmd('exit'), pgroup: true)
+ pid2 = Process.spawn(ruby_cmd('exit'))
+
+ Process::Status.wait(0).pid.should == pid2
+ Process::Status.wait.pid.should == pid1
+ end
+
+ # This spec is probably system-dependent.
+ it "doesn't block if no child is available when WNOHANG is used" do
+ read, write = IO.pipe
+ pid = Process.fork do
+ read.close
+ Signal.trap("TERM") { Process.exit! }
+ write << 1
+ write.close
+ sleep
+ end
+
+ Process::Status.wait(pid, Process::WNOHANG).should be_nil
+
+ # wait for the child to setup its TERM handler
+ write.close
+ read.read(1)
+ read.close
+
+ Process.kill("TERM", pid)
+ Process::Status.wait.pid.should == pid
+ end
+
+ it "always accepts flags=0" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ Process::Status.wait(-1, 0).pid.should == pid
+ -> { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/sys/getegid_spec.rb b/spec/ruby/core/process/sys/getegid_spec.rb
new file mode 100644
index 0000000000..5b8588f147
--- /dev/null
+++ b/spec/ruby/core/process/sys/getegid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.getegid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/geteuid_spec.rb b/spec/ruby/core/process/sys/geteuid_spec.rb
new file mode 100644
index 0000000000..0ce7fc5459
--- /dev/null
+++ b/spec/ruby/core/process/sys/geteuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.geteuid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/getgid_spec.rb b/spec/ruby/core/process/sys/getgid_spec.rb
new file mode 100644
index 0000000000..05c0fd4c0b
--- /dev/null
+++ b/spec/ruby/core/process/sys/getgid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.getgid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/getuid_spec.rb b/spec/ruby/core/process/sys/getuid_spec.rb
new file mode 100644
index 0000000000..56bcef01cc
--- /dev/null
+++ b/spec/ruby/core/process/sys/getuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.getuid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/issetugid_spec.rb b/spec/ruby/core/process/sys/issetugid_spec.rb
new file mode 100644
index 0000000000..4fc7dc3bc6
--- /dev/null
+++ b/spec/ruby/core/process/sys/issetugid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.issetugid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/setegid_spec.rb b/spec/ruby/core/process/sys/setegid_spec.rb
new file mode 100644
index 0000000000..8f20330b94
--- /dev/null
+++ b/spec/ruby/core/process/sys/setegid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.setegid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/seteuid_spec.rb b/spec/ruby/core/process/sys/seteuid_spec.rb
new file mode 100644
index 0000000000..57d3a4800d
--- /dev/null
+++ b/spec/ruby/core/process/sys/seteuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.seteuid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/setgid_spec.rb b/spec/ruby/core/process/sys/setgid_spec.rb
new file mode 100644
index 0000000000..cc712e0102
--- /dev/null
+++ b/spec/ruby/core/process/sys/setgid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.setgid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/setregid_spec.rb b/spec/ruby/core/process/sys/setregid_spec.rb
new file mode 100644
index 0000000000..57d491a707
--- /dev/null
+++ b/spec/ruby/core/process/sys/setregid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.setregid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/setresgid_spec.rb b/spec/ruby/core/process/sys/setresgid_spec.rb
new file mode 100644
index 0000000000..9be06612c1
--- /dev/null
+++ b/spec/ruby/core/process/sys/setresgid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.setresgid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/setresuid_spec.rb b/spec/ruby/core/process/sys/setresuid_spec.rb
new file mode 100644
index 0000000000..1092b349cb
--- /dev/null
+++ b/spec/ruby/core/process/sys/setresuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.setresuid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/setreuid_spec.rb b/spec/ruby/core/process/sys/setreuid_spec.rb
new file mode 100644
index 0000000000..f3451c63c8
--- /dev/null
+++ b/spec/ruby/core/process/sys/setreuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.setreuid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/setrgid_spec.rb b/spec/ruby/core/process/sys/setrgid_spec.rb
new file mode 100644
index 0000000000..27eea2ed86
--- /dev/null
+++ b/spec/ruby/core/process/sys/setrgid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.setrgid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/setruid_spec.rb b/spec/ruby/core/process/sys/setruid_spec.rb
new file mode 100644
index 0000000000..9dbd84bf68
--- /dev/null
+++ b/spec/ruby/core/process/sys/setruid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.setruid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/sys/setuid_spec.rb b/spec/ruby/core/process/sys/setuid_spec.rb
new file mode 100644
index 0000000000..e06c3588a5
--- /dev/null
+++ b/spec/ruby/core/process/sys/setuid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::Sys.setuid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/times_spec.rb b/spec/ruby/core/process/times_spec.rb
new file mode 100644
index 0000000000..6142cd257c
--- /dev/null
+++ b/spec/ruby/core/process/times_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "Process.times" do
+ it "returns a Process::Tms" do
+ Process.times.should be_kind_of(Process::Tms)
+ end
+
+ # TODO: Intel C Compiler does not work this example
+ # http://rubyci.s3.amazonaws.com/icc-x64/ruby-master/log/20221013T030005Z.fail.html.gz
+ unless RbConfig::CONFIG['CC']&.include?("icx")
+ it "returns current cpu times" do
+ t = Process.times
+ user = t.utime
+
+ 1 until Process.times.utime > user
+ Process.times.utime.should > user
+ end
+ end
+
+ platform_is_not :windows do
+ it "uses getrusage when available to improve precision beyond milliseconds" do
+ max = 10_000
+ has_getrusage = max.times.find do
+ time = Process.clock_gettime(:GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID)
+ ('%.6f' % time).end_with?('000')
+ end
+ unless has_getrusage
+ skip "getrusage is not supported on this environment"
+ end
+
+ found = (max * 100).times.find do
+ time = Process.times.utime
+ ('%.6f' % time).end_with?('000')
+ end
+
+ found.should_not == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/process/uid/change_privilege_spec.rb b/spec/ruby/core/process/uid/change_privilege_spec.rb
new file mode 100644
index 0000000000..e4b552dd94
--- /dev/null
+++ b/spec/ruby/core/process/uid/change_privilege_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::UID.change_privilege" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/uid/eid_spec.rb b/spec/ruby/core/process/uid/eid_spec.rb
new file mode 100644
index 0000000000..f0bb9ce762
--- /dev/null
+++ b/spec/ruby/core/process/uid/eid_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+describe "Process::UID.eid" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "Process::UID.eid=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/uid/grant_privilege_spec.rb b/spec/ruby/core/process/uid/grant_privilege_spec.rb
new file mode 100644
index 0000000000..2b8a5c9102
--- /dev/null
+++ b/spec/ruby/core/process/uid/grant_privilege_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::UID.grant_privilege" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/uid/re_exchange_spec.rb b/spec/ruby/core/process/uid/re_exchange_spec.rb
new file mode 100644
index 0000000000..c0f10f33c4
--- /dev/null
+++ b/spec/ruby/core/process/uid/re_exchange_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::UID.re_exchange" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/uid/re_exchangeable_spec.rb b/spec/ruby/core/process/uid/re_exchangeable_spec.rb
new file mode 100644
index 0000000000..8200d7ecb7
--- /dev/null
+++ b/spec/ruby/core/process/uid/re_exchangeable_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::UID.re_exchangeable?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/uid/rid_spec.rb b/spec/ruby/core/process/uid/rid_spec.rb
new file mode 100644
index 0000000000..e865cbfef6
--- /dev/null
+++ b/spec/ruby/core/process/uid/rid_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::UID.rid" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/uid/sid_available_spec.rb b/spec/ruby/core/process/uid/sid_available_spec.rb
new file mode 100644
index 0000000000..be7912eb68
--- /dev/null
+++ b/spec/ruby/core/process/uid/sid_available_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::UID.sid_available?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/uid/switch_spec.rb b/spec/ruby/core/process/uid/switch_spec.rb
new file mode 100644
index 0000000000..4191b97db4
--- /dev/null
+++ b/spec/ruby/core/process/uid/switch_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Process::UID.switch" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/uid_spec.rb b/spec/ruby/core/process/uid_spec.rb
new file mode 100644
index 0000000000..a068b1a2c1
--- /dev/null
+++ b/spec/ruby/core/process/uid_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+
+describe "Process.uid" do
+ platform_is_not :windows do
+ it "returns the correct uid for the user executing this process" do
+ current_uid_according_to_unix = `id -ur`.to_i
+ Process.uid.should == current_uid_according_to_unix
+ end
+ end
+
+ it "also goes by Process::UID.rid" do
+ Process::UID.rid.should == Process.uid
+ end
+
+ it "also goes by Process::Sys.getuid" do
+ Process::Sys.getuid.should == Process.uid
+ end
+end
+
+describe "Process.uid=" do
+ platform_is_not :windows do
+ it "raises TypeError if not passed an Integer" do
+ -> { Process.uid = Object.new }.should raise_error(TypeError)
+ end
+
+ as_user do
+ it "raises Errno::ERPERM if run by a non privileged user trying to set the superuser id" do
+ -> { (Process.uid = 0)}.should raise_error(Errno::EPERM)
+ end
+
+ it "raises Errno::ERPERM if run by a non privileged user trying to set the superuser id from username" do
+ -> { Process.uid = "root" }.should raise_error(Errno::EPERM)
+ end
+ end
+
+ as_superuser do
+ describe "if run by a superuser" do
+ it "sets the real user id for the current process" do
+ code = <<-RUBY
+ Process.uid = 1
+ puts Process.uid
+ RUBY
+ ruby_exe(code).should == "1\n"
+ end
+
+ it "sets the real user id if preceded by Process.euid=id" do
+ code = <<-RUBY
+ Process.euid = 1
+ Process.uid = 1
+ puts Process.uid
+ RUBY
+ ruby_exe(code).should == "1\n"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/process/wait2_spec.rb b/spec/ruby/core/process/wait2_spec.rb
new file mode 100644
index 0000000000..673d3cdb9d
--- /dev/null
+++ b/spec/ruby/core/process/wait2_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "Process.wait2" do
+ before :all do
+ # HACK: this kludge is temporarily necessary because some
+ # misbehaving spec somewhere else does not clear processes
+ # Note: background processes are unavoidable with MJIT,
+ # but we shouldn't reap them from Ruby-space
+ begin
+ Process.wait(-1, Process::WNOHANG)
+ $stderr.puts "Leaked process before wait2 specs! Waiting for it"
+ leaked = Process.waitall
+ $stderr.puts "leaked before wait2 specs: #{leaked}" unless leaked.empty?
+ # Ruby-space should not see PIDs used by mjit
+ leaked.should be_empty
+ rescue Errno::ECHILD # No child processes
+ rescue NotImplementedError
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns the pid and status of child process" do
+ pidf = Process.fork { Process.exit! 99 }
+ results = Process.wait2
+ results.size.should == 2
+ pidw, status = results
+ pidf.should == pidw
+ status.exitstatus.should == 99
+ end
+ end
+
+ it "raises a StandardError if no child processes exist" do
+ -> { Process.wait2 }.should raise_error(Errno::ECHILD)
+ -> { Process.wait2 }.should raise_error(StandardError)
+ end
+
+ it "returns nil if the child process is still running when given the WNOHANG flag" do
+ IO.popen(ruby_cmd('STDIN.getbyte'), "w") do |io|
+ pid, status = Process.wait2(io.pid, Process::WNOHANG)
+ pid.should be_nil
+ status.should be_nil
+ io.write('a')
+ end
+ end
+end
diff --git a/spec/ruby/core/process/wait_spec.rb b/spec/ruby/core/process/wait_spec.rb
new file mode 100644
index 0000000000..44c95d6304
--- /dev/null
+++ b/spec/ruby/core/process/wait_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Process.wait" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :all do
+ begin
+ leaked = Process.waitall
+ # Ruby-space should not see PIDs used by mjit
+ raise "subprocesses leaked before wait specs: #{leaked}" unless leaked.empty?
+ rescue NotImplementedError
+ end
+ end
+
+ it "raises an Errno::ECHILD if there are no child processes" do
+ -> { Process.wait }.should raise_error(Errno::ECHILD)
+ end
+
+ platform_is_not :windows do
+ it "returns its child pid" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ Process.wait.should == pid
+ end
+
+ it "sets $? to a Process::Status" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ Process.wait
+ $?.should be_kind_of(Process::Status)
+ $?.pid.should == pid
+ end
+
+ it "waits for any child process if no pid is given" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ Process.wait.should == pid
+ -> { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
+ end
+
+ it "waits for a specific child if a pid is given" do
+ pid1 = Process.spawn(ruby_cmd('exit'))
+ pid2 = Process.spawn(ruby_cmd('exit'))
+ Process.wait(pid2).should == pid2
+ Process.wait(pid1).should == pid1
+ -> { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
+ -> { Process.kill(0, pid2) }.should raise_error(Errno::ESRCH)
+ end
+
+ it "coerces the pid to an Integer" do
+ pid1 = Process.spawn(ruby_cmd('exit'))
+ Process.wait(mock_int(pid1)).should == pid1
+ -> { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
+ end
+
+ # This spec is probably system-dependent.
+ it "waits for a child whose process group ID is that of the calling process" do
+ pid1 = Process.spawn(ruby_cmd('exit'), pgroup: true)
+ pid2 = Process.spawn(ruby_cmd('exit'))
+
+ Process.wait(0).should == pid2
+ Process.wait.should == pid1
+ end
+
+ # This spec is probably system-dependent.
+ it "doesn't block if no child is available when WNOHANG is used" do
+ read, write = IO.pipe
+ pid = Process.fork do
+ read.close
+ Signal.trap("TERM") { Process.exit! }
+ write << 1
+ write.close
+ sleep
+ end
+
+ Process.wait(pid, Process::WNOHANG).should be_nil
+
+ # wait for the child to setup its TERM handler
+ write.close
+ read.read(1)
+ read.close
+
+ Process.kill("TERM", pid)
+ Process.wait.should == pid
+ end
+
+ it "always accepts flags=0" do
+ pid = Process.spawn(ruby_cmd('exit'))
+ Process.wait(-1, 0).should == pid
+ -> { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
+ end
+ end
+end
diff --git a/spec/ruby/core/process/waitall_spec.rb b/spec/ruby/core/process/waitall_spec.rb
new file mode 100644
index 0000000000..6cf4e32bc9
--- /dev/null
+++ b/spec/ruby/core/process/waitall_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+
+describe "Process.waitall" do
+ before :all do
+ begin
+ Process.waitall
+ rescue NotImplementedError
+ end
+ end
+
+ it "returns an empty array when there are no children" do
+ Process.waitall.should == []
+ end
+
+ it "takes no arguments" do
+ -> { Process.waitall(0) }.should raise_error(ArgumentError)
+ end
+
+ platform_is_not :windows do
+ it "waits for all children" do
+ pids = []
+ pids << Process.fork { Process.exit! 2 }
+ pids << Process.fork { Process.exit! 1 }
+ pids << Process.fork { Process.exit! 0 }
+ Process.waitall
+ pids.each { |pid|
+ -> { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
+ }
+ end
+
+ it "returns an array of pid/status pairs" do
+ pids = []
+ pids << Process.fork { Process.exit! 2 }
+ pids << Process.fork { Process.exit! 1 }
+ pids << Process.fork { Process.exit! 0 }
+ a = Process.waitall
+ a.should be_kind_of(Array)
+ a.size.should == 3
+ pids.each { |pid|
+ pid_status = a.assoc(pid)
+ pid_status.should be_kind_of(Array)
+ pid_status.size.should == 2
+ pid_status.first.should == pid
+ pid_status.last.should be_kind_of(Process::Status)
+ }
+ end
+ end
+end
diff --git a/spec/ruby/core/process/waitpid2_spec.rb b/spec/ruby/core/process/waitpid2_spec.rb
new file mode 100644
index 0000000000..45513af667
--- /dev/null
+++ b/spec/ruby/core/process/waitpid2_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Process.waitpid2" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/process/waitpid_spec.rb b/spec/ruby/core/process/waitpid_spec.rb
new file mode 100644
index 0000000000..f7cf1a45a8
--- /dev/null
+++ b/spec/ruby/core/process/waitpid_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Process.waitpid" do
+ it "returns nil when the process has not yet completed and WNOHANG is specified" do
+ pid = spawn("sleep 5")
+ begin
+ Process.waitpid(pid, Process::WNOHANG).should == nil
+ Process.kill("KILL", pid)
+ ensure
+ Process.wait(pid)
+ end
+ end
+end
diff --git a/spec/ruby/core/queue/append_spec.rb b/spec/ruby/core/queue/append_spec.rb
new file mode 100644
index 0000000000..34165a7506
--- /dev/null
+++ b/spec/ruby/core/queue/append_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/enque'
+
+describe "Queue#<<" do
+ it_behaves_like :queue_enq, :<<, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/clear_spec.rb b/spec/ruby/core/queue/clear_spec.rb
new file mode 100644
index 0000000000..3245e4cb83
--- /dev/null
+++ b/spec/ruby/core/queue/clear_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/clear'
+
+describe "Queue#clear" do
+ it_behaves_like :queue_clear, :clear, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/close_spec.rb b/spec/ruby/core/queue/close_spec.rb
new file mode 100644
index 0000000000..c0d774cd74
--- /dev/null
+++ b/spec/ruby/core/queue/close_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/close'
+
+describe "Queue#close" do
+ it_behaves_like :queue_close, :close, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/closed_spec.rb b/spec/ruby/core/queue/closed_spec.rb
new file mode 100644
index 0000000000..10d552996d
--- /dev/null
+++ b/spec/ruby/core/queue/closed_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/closed'
+
+describe "Queue#closed?" do
+ it_behaves_like :queue_closed?, :closed?, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/deq_spec.rb b/spec/ruby/core/queue/deq_spec.rb
new file mode 100644
index 0000000000..9510978eac
--- /dev/null
+++ b/spec/ruby/core/queue/deq_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/deque'
+
+describe "Queue#deq" do
+ it_behaves_like :queue_deq, :deq, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/empty_spec.rb b/spec/ruby/core/queue/empty_spec.rb
new file mode 100644
index 0000000000..55ca777466
--- /dev/null
+++ b/spec/ruby/core/queue/empty_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/empty'
+
+describe "Queue#empty?" do
+ it_behaves_like :queue_empty?, :empty?, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/enq_spec.rb b/spec/ruby/core/queue/enq_spec.rb
new file mode 100644
index 0000000000..c69c496fbc
--- /dev/null
+++ b/spec/ruby/core/queue/enq_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/enque'
+
+describe "Queue#enq" do
+ it_behaves_like :queue_enq, :enq, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/initialize_spec.rb b/spec/ruby/core/queue/initialize_spec.rb
new file mode 100644
index 0000000000..c45abcd29d
--- /dev/null
+++ b/spec/ruby/core/queue/initialize_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Queue#initialize" do
+ it "can be passed no arguments for an empty Queue" do
+ q = Queue.new
+ q.size.should == 0
+ q.should.empty?
+ end
+
+ it "is a private method" do
+ Queue.private_instance_methods.include?(:initialize).should == true
+ end
+
+ ruby_version_is '3.1' do
+ it "adds all elements of the passed Enumerable to self" do
+ q = Queue.new([1, 2, 3])
+ q.size.should == 3
+ q.should_not.empty?
+ q.pop.should == 1
+ q.pop.should == 2
+ q.pop.should == 3
+ q.should.empty?
+ end
+
+ it "uses #to_a on the provided Enumerable" do
+ enumerable = MockObject.new('mock-enumerable')
+ enumerable.should_receive(:to_a).and_return([1, 2, 3])
+ q = Queue.new(enumerable)
+ q.size.should == 3
+ q.should_not.empty?
+ q.pop.should == 1
+ q.pop.should == 2
+ q.pop.should == 3
+ q.should.empty?
+ end
+
+ it "raises TypeError if the provided Enumerable does not respond to #to_a" do
+ enumerable = MockObject.new('mock-enumerable')
+ -> { Queue.new(enumerable) }.should raise_error(TypeError, "can't convert MockObject into Array")
+ end
+
+ it "raises TypeError if #to_a does not return Array" do
+ enumerable = MockObject.new('mock-enumerable')
+ enumerable.should_receive(:to_a).and_return("string")
+
+ -> { Queue.new(enumerable) }.should raise_error(TypeError, "can't convert MockObject to Array (MockObject#to_a gives String)")
+ end
+ end
+end
diff --git a/spec/ruby/core/queue/length_spec.rb b/spec/ruby/core/queue/length_spec.rb
new file mode 100644
index 0000000000..25399b2b76
--- /dev/null
+++ b/spec/ruby/core/queue/length_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/length'
+
+describe "Queue#length" do
+ it_behaves_like :queue_length, :length, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/num_waiting_spec.rb b/spec/ruby/core/queue/num_waiting_spec.rb
new file mode 100644
index 0000000000..edc0c37a82
--- /dev/null
+++ b/spec/ruby/core/queue/num_waiting_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/num_waiting'
+
+describe "Queue#num_waiting" do
+ it_behaves_like :queue_num_waiting, :num_waiting, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/pop_spec.rb b/spec/ruby/core/queue/pop_spec.rb
new file mode 100644
index 0000000000..1ce9231685
--- /dev/null
+++ b/spec/ruby/core/queue/pop_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/deque'
+
+describe "Queue#pop" do
+ it_behaves_like :queue_deq, :pop, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/push_spec.rb b/spec/ruby/core/queue/push_spec.rb
new file mode 100644
index 0000000000..e936f9d282
--- /dev/null
+++ b/spec/ruby/core/queue/push_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/enque'
+
+describe "Queue#push" do
+ it_behaves_like :queue_enq, :push, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/shift_spec.rb b/spec/ruby/core/queue/shift_spec.rb
new file mode 100644
index 0000000000..f84058e1df
--- /dev/null
+++ b/spec/ruby/core/queue/shift_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/deque'
+
+describe "Queue#shift" do
+ it_behaves_like :queue_deq, :shift, -> { Queue.new }
+end
diff --git a/spec/ruby/core/queue/size_spec.rb b/spec/ruby/core/queue/size_spec.rb
new file mode 100644
index 0000000000..f528dfe797
--- /dev/null
+++ b/spec/ruby/core/queue/size_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/length'
+
+describe "Queue#size" do
+ it_behaves_like :queue_length, :size, -> { Queue.new }
+end
diff --git a/spec/ruby/core/random/bytes_spec.rb b/spec/ruby/core/random/bytes_spec.rb
new file mode 100644
index 0000000000..ed1b3a7b41
--- /dev/null
+++ b/spec/ruby/core/random/bytes_spec.rb
@@ -0,0 +1,30 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'shared/bytes'
+
+describe "Random#bytes" do
+ it_behaves_like :random_bytes, :bytes, Random.new
+
+ it "returns the same output for a given seed" do
+ Random.new(33).bytes(2).should == Random.new(33).bytes(2)
+ end
+
+ # Should double check this is official spec
+ it "returns the same numeric output for a given seed across all implementations and platforms" do
+ rnd = Random.new(33)
+ rnd.bytes(2).should == "\x14\\"
+ rnd.bytes(1000) # skip some
+ rnd.bytes(2).should == "\xA1p"
+ end
+
+ it "returns the same numeric output for a given huge seed across all implementations and platforms" do
+ rnd = Random.new(2 ** (63 * 4))
+ rnd.bytes(2).should == "_\x91"
+ rnd.bytes(1000) # skip some
+ rnd.bytes(2).should == "\x17\x12"
+ end
+end
+
+describe "Random.bytes" do
+ it_behaves_like :random_bytes, :bytes, Random
+end
diff --git a/spec/ruby/core/random/default_spec.rb b/spec/ruby/core/random/default_spec.rb
new file mode 100644
index 0000000000..b4ffcb81f4
--- /dev/null
+++ b/spec/ruby/core/random/default_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "Random::DEFAULT" do
+ ruby_version_is ''...'3.2' do
+ it "returns a random number generator" do
+ suppress_warning do
+ Random::DEFAULT.should respond_to(:rand)
+ end
+ end
+
+ it "changes seed on reboot" do
+ seed1 = ruby_exe('p Random::DEFAULT.seed', options: '--disable-gems')
+ seed2 = ruby_exe('p Random::DEFAULT.seed', options: '--disable-gems')
+ seed1.should != seed2
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a Random instance" do
+ suppress_warning do
+ Random::DEFAULT.should be_an_instance_of(Random)
+ end
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "refers to the Random class" do
+ suppress_warning do
+ Random::DEFAULT.should.equal?(Random)
+ end
+ end
+
+ it "is deprecated" do
+ -> {
+ Random::DEFAULT.should.equal?(Random)
+ }.should complain(/constant Random::DEFAULT is deprecated/)
+ end
+ end
+ end
+
+ ruby_version_is '3.2' do
+ it "is no longer defined" do
+ Random.should_not.const_defined?(:DEFAULT)
+ end
+ end
+end
diff --git a/spec/ruby/core/random/equal_value_spec.rb b/spec/ruby/core/random/equal_value_spec.rb
new file mode 100644
index 0000000000..5f470d6a4c
--- /dev/null
+++ b/spec/ruby/core/random/equal_value_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+describe "Random#==" do
+ it "returns true if the two objects have the same state" do
+ a = Random.new(42)
+ b = Random.new(42)
+ a.send(:state).should == b.send(:state)
+ a.should == b
+ end
+
+ it "returns false if the two objects have different state" do
+ a = Random.new
+ b = Random.new
+ a.send(:state).should_not == b.send(:state)
+ a.should_not == b
+ end
+
+ it "returns true if the two objects have the same seed" do
+ a = Random.new(42)
+ b = Random.new(42.5)
+ a.seed.should == b.seed
+ a.should == b
+ end
+
+ it "returns false if the two objects have a different seed" do
+ a = Random.new(42)
+ b = Random.new(41)
+ a.seed.should_not == b.seed
+ a.should_not == b
+ end
+
+ it "returns false if the other object is not a Random" do
+ a = Random.new(42)
+ a.should_not == 42
+ a.should_not == [a]
+ end
+end
diff --git a/spec/ruby/core/random/fixtures/classes.rb b/spec/ruby/core/random/fixtures/classes.rb
new file mode 100644
index 0000000000..9c8d113e24
--- /dev/null
+++ b/spec/ruby/core/random/fixtures/classes.rb
@@ -0,0 +1,15 @@
+module RandomSpecs
+ CustomRangeInteger = Struct.new(:value) do
+ def to_int; value; end
+ def <=>(other); to_int <=> other.to_int; end
+ def -(other); self.class.new(to_int - other.to_int); end
+ def +(other); self.class.new(to_int + other.to_int); end
+ end
+
+ CustomRangeFloat = Struct.new(:value) do
+ def to_f; value; end
+ def <=>(other); to_f <=> other.to_f; end
+ def -(other); to_f - other.to_f; end
+ def +(other); self.class.new(to_f + other.to_f); end
+ end
+end
diff --git a/spec/ruby/core/random/new_seed_spec.rb b/spec/ruby/core/random/new_seed_spec.rb
new file mode 100644
index 0000000000..7a93da99aa
--- /dev/null
+++ b/spec/ruby/core/random/new_seed_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "Random.new_seed" do
+ it "returns an Integer" do
+ Random.new_seed.should be_an_instance_of(Integer)
+ end
+
+ it "returns an arbitrary seed value each time" do
+ bigs = 200.times.map { Random.new_seed }
+ bigs.uniq.size.should == 200
+ end
+
+ it "is not affected by Kernel#srand" do
+ begin
+ srand 25
+ a = Random.new_seed
+ srand 25
+ b = Random.new_seed
+ a.should_not == b
+ ensure
+ srand Random.new_seed
+ end
+ end
+end
diff --git a/spec/ruby/core/random/new_spec.rb b/spec/ruby/core/random/new_spec.rb
new file mode 100644
index 0000000000..4280b5b9c3
--- /dev/null
+++ b/spec/ruby/core/random/new_spec.rb
@@ -0,0 +1,37 @@
+describe "Random.new" do
+ it "returns a new instance of Random" do
+ Random.new.should be_an_instance_of(Random)
+ end
+
+ it "uses a random seed value if none is supplied" do
+ Random.new.seed.should be_an_instance_of(Integer)
+ end
+
+ it "returns Random instances initialized with different seeds" do
+ first = Random.new
+ second = Random.new
+ (0..20).map { first.rand } .should_not == (0..20).map { second.rand }
+ end
+
+ it "accepts an Integer seed value as an argument" do
+ Random.new(2).seed.should == 2
+ end
+
+ it "accepts (and truncates) a Float seed value as an argument" do
+ Random.new(3.4).seed.should == 3
+ end
+
+ it "accepts (and converts to Integer) a Rational seed value as an argument" do
+ Random.new(Rational(20,2)).seed.should == 10
+ end
+
+ it "accepts (and converts to Integer) a Complex (without imaginary part) seed value as an argument" do
+ Random.new(Complex(20)).seed.should == 20
+ end
+
+ it "raises a RangeError if passed a Complex (with imaginary part) seed value as an argument" do
+ -> do
+ Random.new(Complex(20,2))
+ end.should raise_error(RangeError)
+ end
+end
diff --git a/spec/ruby/core/random/rand_spec.rb b/spec/ruby/core/random/rand_spec.rb
new file mode 100644
index 0000000000..9244177ab5
--- /dev/null
+++ b/spec/ruby/core/random/rand_spec.rb
@@ -0,0 +1,224 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/rand'
+
+describe "Random.rand" do
+ it_behaves_like :random_number, :rand, Random.new
+ it_behaves_like :random_number, :rand, Random
+
+ it "returns a Float >= 0 if no max argument is passed" do
+ floats = 200.times.map { Random.rand }
+ floats.min.should >= 0
+ end
+
+ it "returns a Float < 1 if no max argument is passed" do
+ floats = 200.times.map { Random.rand }
+ floats.max.should < 1
+ end
+
+ it "returns the same sequence for a given seed if no max argument is passed" do
+ Random.srand 33
+ floats_a = 20.times.map { Random.rand }
+ Random.srand 33
+ floats_b = 20.times.map { Random.rand }
+ floats_a.should == floats_b
+ end
+
+ it "returns an Integer >= 0 if an Integer argument is passed" do
+ ints = 200.times.map { Random.rand(34) }
+ ints.min.should >= 0
+ end
+
+ it "returns an Integer < the max argument if an Integer argument is passed" do
+ ints = 200.times.map { Random.rand(55) }
+ ints.max.should < 55
+ end
+
+ it "returns the same sequence for a given seed if an Integer argument is passed" do
+ Random.srand 33
+ floats_a = 20.times.map { Random.rand(90) }
+ Random.srand 33
+ floats_b = 20.times.map { Random.rand(90) }
+ floats_a.should == floats_b
+ end
+
+ it "coerces arguments to Integers with #to_int" do
+ obj = mock_numeric('int')
+ obj.should_receive(:to_int).and_return(99)
+ Random.rand(obj).should be_kind_of(Integer)
+ end
+end
+
+describe "Random#rand with Fixnum" do
+ it "returns an Integer" do
+ Random.new.rand(20).should be_an_instance_of(Integer)
+ end
+
+ it "returns a Fixnum greater than or equal to 0" do
+ prng = Random.new
+ ints = 20.times.map { prng.rand(5) }
+ ints.min.should >= 0
+ end
+
+ it "returns a Fixnum less than the argument" do
+ prng = Random.new
+ ints = 20.times.map { prng.rand(5) }
+ ints.max.should <= 4
+ end
+
+ it "returns the same sequence for a given seed" do
+ prng = Random.new 33
+ a = 20.times.map { prng.rand(90) }
+ prng = Random.new 33
+ b = 20.times.map { prng.rand(90) }
+ a.should == b
+ end
+
+ it "eventually returns all possible values" do
+ prng = Random.new 33
+ 100.times.map{ prng.rand(10) }.uniq.sort.should == (0...10).to_a
+ end
+
+ it "raises an ArgumentError when the argument is 0" do
+ -> do
+ Random.new.rand(0)
+ end.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the argument is negative" do
+ -> do
+ Random.new.rand(-12)
+ end.should raise_error(ArgumentError)
+ end
+end
+
+describe "Random#rand with Bignum" do
+ it "typically returns a Bignum" do
+ rnd = Random.new(1)
+ 10.times.map{ rnd.rand(bignum_value*2) }.max.should be_an_instance_of(Integer)
+ end
+
+ it "returns a Bignum greater than or equal to 0" do
+ prng = Random.new
+ bigs = 20.times.map { prng.rand(bignum_value) }
+ bigs.min.should >= 0
+ end
+
+ it "returns a Bignum less than the argument" do
+ prng = Random.new
+ bigs = 20.times.map { prng.rand(bignum_value) }
+ bigs.max.should < bignum_value
+ end
+
+ it "returns the same sequence for a given seed" do
+ prng = Random.new 33
+ a = 20.times.map { prng.rand(bignum_value) }
+ prng = Random.new 33
+ b = 20.times.map { prng.rand(bignum_value) }
+ a.should == b
+ end
+
+ it "raises an ArgumentError when the argument is negative" do
+ -> do
+ Random.new.rand(-bignum_value)
+ end.should raise_error(ArgumentError)
+ end
+end
+
+describe "Random#rand with Float" do
+ it "returns a Float" do
+ Random.new.rand(20.43).should be_an_instance_of(Float)
+ end
+
+ it "returns a Float greater than or equal to 0.0" do
+ prng = Random.new
+ floats = 20.times.map { prng.rand(5.2) }
+ floats.min.should >= 0.0
+ end
+
+ it "returns a Float less than the argument" do
+ prng = Random.new
+ floats = 20.times.map { prng.rand(4.30) }
+ floats.max.should < 4.30
+ end
+
+ it "returns the same sequence for a given seed" do
+ prng = Random.new 33
+ a = 20.times.map { prng.rand(89.2928) }
+ prng = Random.new 33
+ b = 20.times.map { prng.rand(89.2928) }
+ a.should == b
+ end
+
+ it "raises an ArgumentError when the argument is negative" do
+ -> do
+ Random.new.rand(-1.234567)
+ end.should raise_error(ArgumentError)
+ end
+end
+
+describe "Random#rand with Range" do
+ it "returns an element from the Range" do
+ Random.new.rand(20..43).should be_an_instance_of(Integer)
+ end
+
+ it "supports custom object types" do
+ rand(RandomSpecs::CustomRangeInteger.new(1)..RandomSpecs::CustomRangeInteger.new(42)).should be_an_instance_of(RandomSpecs::CustomRangeInteger)
+ rand(RandomSpecs::CustomRangeFloat.new(1.0)..RandomSpecs::CustomRangeFloat.new(42.0)).should be_an_instance_of(RandomSpecs::CustomRangeFloat)
+ rand(Time.now..Time.now).should be_an_instance_of(Time)
+ end
+
+ it "returns an object that is a member of the Range" do
+ prng = Random.new
+ r = 20..30
+ 20.times { r.member?(prng.rand(r)).should be_true }
+ end
+
+ it "works with inclusive ranges" do
+ prng = Random.new 33
+ r = 3..5
+ 40.times.map { prng.rand(r) }.uniq.sort.should == [3,4,5]
+ end
+
+ it "works with exclusive ranges" do
+ prng = Random.new 33
+ r = 3...5
+ 20.times.map { prng.rand(r) }.uniq.sort.should == [3,4]
+ end
+
+ it "returns the same sequence for a given seed" do
+ prng = Random.new 33
+ a = 20.times.map { prng.rand(76890.028..800000.00) }
+ prng = Random.new 33
+ b = 20.times.map { prng.rand(76890.028..800000.00) }
+ a.should == b
+ end
+
+ it "eventually returns all possible values" do
+ prng = Random.new 33
+ 100.times.map{ prng.rand(10..20) }.uniq.sort.should == (10..20).to_a
+ 100.times.map{ prng.rand(10...20) }.uniq.sort.should == (10...20).to_a
+ end
+
+ it "considers Integers as Floats if one end point is a float" do
+ Random.new(42).rand(0.0..1).should be_kind_of(Float)
+ Random.new(42).rand(0..1.0).should be_kind_of(Float)
+ end
+
+ it "returns a float within a given float range" do
+ Random.new(42).rand(0.0...100.0).should == 37.454011884736246
+ Random.new(42).rand(-100.0...0.0).should == -62.545988115263754
+ end
+
+ it "raises an ArgumentError when the startpoint lacks #+ and #- methods" do
+ -> do
+ Random.new.rand(Object.new..67)
+ end.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the endpoint lacks #+ and #- methods" do
+ -> do
+ Random.new.rand(68..Object.new)
+ end.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/random/random_number_spec.rb b/spec/ruby/core/random/random_number_spec.rb
new file mode 100644
index 0000000000..bad81aeff6
--- /dev/null
+++ b/spec/ruby/core/random/random_number_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'shared/rand'
+
+describe "Random.random_number" do
+ it_behaves_like :random_number, :random_number, Random.new
+
+ it_behaves_like :random_number, :random_number, Random
+end
diff --git a/spec/ruby/core/random/seed_spec.rb b/spec/ruby/core/random/seed_spec.rb
new file mode 100644
index 0000000000..bf4524fdd9
--- /dev/null
+++ b/spec/ruby/core/random/seed_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Random#seed" do
+ it "returns an Integer" do
+ Random.new.seed.should be_kind_of(Integer)
+ end
+
+ it "returns an arbitrary seed if the constructor was called without arguments" do
+ Random.new.seed.should_not == Random.new.seed
+ end
+
+ it "returns the same generated seed when repeatedly called on the same object" do
+ prng = Random.new
+ prng.seed.should == prng.seed
+ end
+
+ it "returns the seed given in the constructor" do
+ prng = Random.new(36788)
+ prng.seed.should == prng.seed
+ prng.seed.should == 36788
+ end
+
+ it "returns the given seed coerced with #to_int" do
+ obj = mock_numeric('int')
+ obj.should_receive(:to_int).and_return(34)
+ prng = Random.new(obj)
+ prng.seed.should == 34
+ end
+end
diff --git a/spec/ruby/core/random/shared/bytes.rb b/spec/ruby/core/random/shared/bytes.rb
new file mode 100644
index 0000000000..9afad3b7f8
--- /dev/null
+++ b/spec/ruby/core/random/shared/bytes.rb
@@ -0,0 +1,17 @@
+describe :random_bytes, shared: true do
+ it "returns a String" do
+ @object.send(@method, 1).should be_an_instance_of(String)
+ end
+
+ it "returns a String of the length given as argument" do
+ @object.send(@method, 15).length.should == 15
+ end
+
+ it "returns a binary String" do
+ @object.send(@method, 15).encoding.should == Encoding::BINARY
+ end
+
+ it "returns a random binary String" do
+ @object.send(@method, 12).should_not == @object.send(@method, 12)
+ end
+end
diff --git a/spec/ruby/core/random/shared/rand.rb b/spec/ruby/core/random/shared/rand.rb
new file mode 100644
index 0000000000..d3b24b8851
--- /dev/null
+++ b/spec/ruby/core/random/shared/rand.rb
@@ -0,0 +1,9 @@
+describe :random_number, shared: true do
+ it "returns a Float if no max argument is passed" do
+ @object.send(@method).should be_kind_of(Float)
+ end
+
+ it "returns an Integer if an Integer argument is passed" do
+ @object.send(@method, 20).should be_kind_of(Integer)
+ end
+end
diff --git a/spec/ruby/core/random/srand_spec.rb b/spec/ruby/core/random/srand_spec.rb
new file mode 100644
index 0000000000..1ef8e32cfb
--- /dev/null
+++ b/spec/ruby/core/random/srand_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "Random.srand" do
+ it "returns an arbitrary seed if .srand wasn't called previously with an argument and no argument is supplied this time" do
+ Random.srand # Reset to random seed in case .srand was called previously
+ Random.srand.should_not == Random.srand
+ end
+
+ it "returns the previous argument to .srand if one was given and no argument is supplied" do
+ Random.srand 34
+ Random.srand.should == 34
+ end
+
+ it "returns an arbitrary seed if .srand wasn't called previously with an argument and 0 is supplied this time" do
+ Random.srand # Reset to random seed in case .srand was called previously
+ Random.srand(0).should_not == Random.srand(0)
+ end
+
+ it "returns the previous argument to .srand if one was given and 0 is supplied" do
+ Random.srand 34
+ Random.srand(0).should == 34
+ end
+
+ it "seeds Random.rand such that its return value is deterministic" do
+ Random.srand 176542
+ a = 20.times.map { Random.rand }
+ Random.srand 176542
+ b = 20.times.map { Random.rand }
+ a.should == b
+ end
+
+ it "seeds Kernel.rand such that its return value is deterministic" do
+ Random.srand 176542
+ a = 20.times.map { Kernel.rand }
+ Random.srand 176542
+ b = 20.times.map { Kernel.rand }
+ a.should == b
+ end
+end
diff --git a/spec/ruby/core/random/urandom_spec.rb b/spec/ruby/core/random/urandom_spec.rb
new file mode 100644
index 0000000000..6f180e54ac
--- /dev/null
+++ b/spec/ruby/core/random/urandom_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+
+describe "Random.urandom" do
+ it "returns a String" do
+ Random.urandom(1).should be_an_instance_of(String)
+ end
+
+ it "returns a String of the length given as argument" do
+ Random.urandom(15).length.should == 15
+ end
+
+ it "raises an ArgumentError on a negative size" do
+ -> {
+ Random.urandom(-1)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "returns a binary String" do
+ Random.urandom(15).encoding.should == Encoding::BINARY
+ end
+
+ it "returns a random binary String" do
+ Random.urandom(12).should_not == Random.urandom(12)
+ end
+end
diff --git a/spec/ruby/core/range/begin_spec.rb b/spec/ruby/core/range/begin_spec.rb
new file mode 100644
index 0000000000..ab82b45b7e
--- /dev/null
+++ b/spec/ruby/core/range/begin_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/begin'
+
+describe "Range#begin" do
+ it_behaves_like :range_begin, :begin
+end
diff --git a/spec/ruby/core/range/bsearch_spec.rb b/spec/ruby/core/range/bsearch_spec.rb
new file mode 100644
index 0000000000..9c93671d85
--- /dev/null
+++ b/spec/ruby/core/range/bsearch_spec.rb
@@ -0,0 +1,436 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Range#bsearch" do
+ it "returns an Enumerator when not passed a block" do
+ (0..1).bsearch.should be_an_instance_of(Enumerator)
+ end
+
+ it_behaves_like :enumeratorized_with_unknown_size, :bsearch, (1..3)
+
+ it "raises a TypeError if the block returns an Object" do
+ -> { (0..1).bsearch { Object.new } }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the block returns a String" do
+ -> { (0..1).bsearch { "1" } }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the Range has Object values" do
+ value = mock("range bsearch")
+ r = Range.new value, value
+
+ -> { r.bsearch { true } }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the Range has String values" do
+ -> { ("a".."e").bsearch { true } }.should raise_error(TypeError)
+ end
+
+ context "with Integer values" do
+ context "with a block returning true or false" do
+ it "returns nil if the block returns false for every element" do
+ (0...3).bsearch { |x| x > 3 }.should be_nil
+ end
+
+ it "returns nil if the block returns nil for every element" do
+ (0..3).bsearch { |x| nil }.should be_nil
+ end
+
+ it "returns minimum element if the block returns true for every element" do
+ (-2..4).bsearch { |x| x < 4 }.should == -2
+ end
+
+ it "returns the smallest element for which block returns true" do
+ (0..4).bsearch { |x| x >= 2 }.should == 2
+ (-1..4).bsearch { |x| x >= 1 }.should == 1
+ end
+
+ it "returns the last element if the block returns true for the last element" do
+ (0..4).bsearch { |x| x >= 4 }.should == 4
+ (0...4).bsearch { |x| x >= 3 }.should == 3
+ end
+ end
+
+ context "with a block returning negative, zero, positive numbers" do
+ it "returns nil if the block returns less than zero for every element" do
+ (0..3).bsearch { |x| x <=> 5 }.should be_nil
+ end
+
+ it "returns nil if the block returns greater than zero for every element" do
+ (0..3).bsearch { |x| x <=> -1 }.should be_nil
+
+ end
+
+ it "returns nil if the block never returns zero" do
+ (0..3).bsearch { |x| x < 2 ? 1 : -1 }.should be_nil
+ end
+
+ it "accepts (+/-)Float::INFINITY from the block" do
+ (0..4).bsearch { |x| Float::INFINITY }.should be_nil
+ (0..4).bsearch { |x| -Float::INFINITY }.should be_nil
+ end
+
+ it "returns an element at an index for which block returns 0.0" do
+ result = (0..4).bsearch { |x| x < 2 ? 1.0 : x > 2 ? -1.0 : 0.0 }
+ result.should == 2
+ end
+
+ it "returns an element at an index for which block returns 0" do
+ result = (0..4).bsearch { |x| x < 1 ? 1 : x > 3 ? -1 : 0 }
+ [1, 2].should include(result)
+ end
+ end
+
+ it "returns nil for empty ranges" do
+ (0...0).bsearch { true }.should == nil
+ (0...0).bsearch { false }.should == nil
+ (0...0).bsearch { 1 }.should == nil
+ (0...0).bsearch { 0 }.should == nil
+ (0...0).bsearch { -1 }.should == nil
+
+ (4..2).bsearch { true }.should == nil
+ (4..2).bsearch { 1 }.should == nil
+ (4..2).bsearch { 0 }.should == nil
+ (4..2).bsearch { -1 }.should == nil
+ end
+ end
+
+ context "with Float values" do
+ context "with a block returning true or false" do
+ it "returns nil if the block returns false for every element" do
+ (0.1...2.3).bsearch { |x| x > 3 }.should be_nil
+ end
+
+ it "returns nil if the block returns nil for every element" do
+ (-0.0..2.3).bsearch { |x| nil }.should be_nil
+ end
+
+ it "returns minimum element if the block returns true for every element" do
+ (-0.2..4.8).bsearch { |x| x < 5 }.should == -0.2
+ end
+
+ it "returns the smallest element for which block returns true" do
+ (0..4.2).bsearch { |x| x >= 2 }.should == 2
+ (-1.2..4.3).bsearch { |x| x >= 1 }.should == 1
+ end
+
+ it "returns a boundary element if appropriate" do
+ (1.0..3.0).bsearch { |x| x >= 3.0 }.should == 3.0
+ (1.0...3.0).bsearch { |x| x >= 3.0.prev_float }.should == 3.0.prev_float
+ (1.0..3.0).bsearch { |x| x >= 1.0 }.should == 1.0
+ (1.0...3.0).bsearch { |x| x >= 1.0 }.should == 1.0
+ end
+
+ it "works with infinity bounds" do
+ inf = Float::INFINITY
+ (0..inf).bsearch { |x| x == inf }.should == inf
+ (0...inf).bsearch { |x| x == inf }.should == nil
+ (-inf..0).bsearch { |x| x != -inf }.should == -Float::MAX
+ (-inf...0).bsearch { |x| x != -inf }.should == -Float::MAX
+ (inf..inf).bsearch { |x| true }.should == inf
+ (inf...inf).bsearch { |x| true }.should == nil
+ (-inf..-inf).bsearch { |x| true }.should == -inf
+ (-inf...-inf).bsearch { |x| true }.should == nil
+ (inf..0).bsearch { true }.should == nil
+ (inf...0).bsearch { true }.should == nil
+ (0..-inf).bsearch { true }.should == nil
+ (0...-inf).bsearch { true }.should == nil
+ (inf..-inf).bsearch { true }.should == nil
+ (inf...-inf).bsearch { true }.should == nil
+ (0..inf).bsearch { |x| x >= 3 }.should == 3.0
+ (0...inf).bsearch { |x| x >= 3 }.should == 3.0
+ (-inf..0).bsearch { |x| x >= -3 }.should == -3.0
+ (-inf...0).bsearch { |x| x >= -3 }.should == -3.0
+ (-inf..inf).bsearch { |x| x >= 3 }.should == 3.0
+ (-inf...inf).bsearch { |x| x >= 3 }.should == 3.0
+ (0..inf).bsearch { |x| x >= Float::MAX }.should == Float::MAX
+ (0...inf).bsearch { |x| x >= Float::MAX }.should == Float::MAX
+ end
+ end
+
+ context "with a block returning negative, zero, positive numbers" do
+ it "returns nil if the block returns less than zero for every element" do
+ (-2.0..3.2).bsearch { |x| x <=> 5 }.should be_nil
+ end
+
+ it "returns nil if the block returns greater than zero for every element" do
+ (0.3..3.0).bsearch { |x| x <=> -1 }.should be_nil
+
+ end
+
+ it "returns nil if the block never returns zero" do
+ (0.2..2.3).bsearch { |x| x < 2 ? 1 : -1 }.should be_nil
+ end
+
+ it "accepts (+/-)Float::INFINITY from the block" do
+ (0.1..4.5).bsearch { |x| Float::INFINITY }.should be_nil
+ (-5.0..4.0).bsearch { |x| -Float::INFINITY }.should be_nil
+ end
+
+ it "returns an element at an index for which block returns 0.0" do
+ result = (0.0..4.0).bsearch { |x| x < 2 ? 1.0 : x > 2 ? -1.0 : 0.0 }
+ result.should == 2
+ end
+
+ it "returns an element at an index for which block returns 0" do
+ result = (0.1..4.9).bsearch { |x| x < 1 ? 1 : x > 3 ? -1 : 0 }
+ result.should >= 1
+ result.should <= 3
+ end
+
+ it "returns an element at an index for which block returns 0 (small numbers)" do
+ result = (0.1..0.3).bsearch { |x| x < 0.1 ? 1 : x > 0.3 ? -1 : 0 }
+ result.should >= 0.1
+ result.should <= 0.3
+ end
+
+ it "returns a boundary element if appropriate" do
+ (1.0..3.0).bsearch { |x| 3.0 - x }.should == 3.0
+ (1.0...3.0).bsearch { |x| 3.0.prev_float - x }.should == 3.0.prev_float
+ (1.0..3.0).bsearch { |x| 1.0 - x }.should == 1.0
+ (1.0...3.0).bsearch { |x| 1.0 - x }.should == 1.0
+ end
+
+ it "works with infinity bounds" do
+ inf = Float::INFINITY
+ (0..inf).bsearch { |x| x == inf ? 0 : 1 }.should == inf
+ (0...inf).bsearch { |x| x == inf ? 0 : 1 }.should == nil
+ (-inf...0).bsearch { |x| x == -inf ? 0 : -1 }.should == -inf
+ (-inf..0).bsearch { |x| x == -inf ? 0 : -1 }.should == -inf
+ (inf..inf).bsearch { 0 }.should == inf
+ (inf...inf).bsearch { 0 }.should == nil
+ (-inf..-inf).bsearch { 0 }.should == -inf
+ (-inf...-inf).bsearch { 0 }.should == nil
+ (inf..0).bsearch { 0 }.should == nil
+ (inf...0).bsearch { 0 }.should == nil
+ (0..-inf).bsearch { 0 }.should == nil
+ (0...-inf).bsearch { 0 }.should == nil
+ (inf..-inf).bsearch { 0 }.should == nil
+ (inf...-inf).bsearch { 0 }.should == nil
+ (-inf..inf).bsearch { |x| 3 - x }.should == 3.0
+ (-inf...inf).bsearch { |x| 3 - x }.should == 3.0
+ (0...inf).bsearch { |x| x >= Float::MAX ? 0 : 1 }.should == Float::MAX
+ end
+ end
+ end
+
+ context "with endless ranges and Integer values" do
+ context "with a block returning true or false" do
+ it "returns minimum element if the block returns true for every element" do
+ eval("(-2..)").bsearch { |x| true }.should == -2
+ end
+
+ it "returns the smallest element for which block returns true" do
+ eval("(0..)").bsearch { |x| x >= 2 }.should == 2
+ eval("(-1..)").bsearch { |x| x >= 1 }.should == 1
+ end
+ end
+
+ context "with a block returning negative, zero, positive numbers" do
+ it "returns nil if the block returns less than zero for every element" do
+ eval("(0..)").bsearch { |x| -1 }.should be_nil
+ end
+
+ it "returns nil if the block never returns zero" do
+ eval("(0..)").bsearch { |x| x > 5 ? -1 : 1 }.should be_nil
+ end
+
+ it "accepts -Float::INFINITY from the block" do
+ eval("(0..)").bsearch { |x| -Float::INFINITY }.should be_nil
+ end
+
+ it "returns an element at an index for which block returns 0.0" do
+ result = eval("(0..)").bsearch { |x| x < 2 ? 1.0 : x > 2 ? -1.0 : 0.0 }
+ result.should == 2
+ end
+
+ it "returns an element at an index for which block returns 0" do
+ result = eval("(0..)").bsearch { |x| x < 1 ? 1 : x > 3 ? -1 : 0 }
+ [1, 2, 3].should include(result)
+ end
+ end
+ end
+
+ context "with endless ranges and Float values" do
+ context "with a block returning true or false" do
+ it "returns nil if the block returns false for every element" do
+ eval("(0.1..)").bsearch { |x| x < 0.0 }.should be_nil
+ eval("(0.1...)").bsearch { |x| x < 0.0 }.should be_nil
+ end
+
+ it "returns nil if the block returns nil for every element" do
+ eval("(-0.0..)").bsearch { |x| nil }.should be_nil
+ eval("(-0.0...)").bsearch { |x| nil }.should be_nil
+ end
+
+ it "returns minimum element if the block returns true for every element" do
+ eval("(-0.2..)").bsearch { |x| true }.should == -0.2
+ eval("(-0.2...)").bsearch { |x| true }.should == -0.2
+ end
+
+ it "returns the smallest element for which block returns true" do
+ eval("(0..)").bsearch { |x| x >= 2 }.should == 2
+ eval("(-1.2..)").bsearch { |x| x >= 1 }.should == 1
+ end
+
+ it "works with infinity bounds" do
+ inf = Float::INFINITY
+ eval("(inf..)").bsearch { |x| true }.should == inf
+ eval("(inf...)").bsearch { |x| true }.should == nil
+ eval("(-inf..)").bsearch { |x| true }.should == -inf
+ eval("(-inf...)").bsearch { |x| true }.should == -inf
+ end
+ end
+
+ context "with a block returning negative, zero, positive numbers" do
+ it "returns nil if the block returns less than zero for every element" do
+ eval("(-2.0..)").bsearch { |x| -1 }.should be_nil
+ eval("(-2.0...)").bsearch { |x| -1 }.should be_nil
+ end
+
+ it "returns nil if the block returns greater than zero for every element" do
+ eval("(0.3..)").bsearch { |x| 1 }.should be_nil
+ eval("(0.3...)").bsearch { |x| 1 }.should be_nil
+ end
+
+ it "returns nil if the block never returns zero" do
+ eval("(0.2..)").bsearch { |x| x < 2 ? 1 : -1 }.should be_nil
+ end
+
+ it "accepts (+/-)Float::INFINITY from the block" do
+ eval("(0.1..)").bsearch { |x| Float::INFINITY }.should be_nil
+ eval("(-5.0..)").bsearch { |x| -Float::INFINITY }.should be_nil
+ end
+
+ it "returns an element at an index for which block returns 0.0" do
+ result = eval("(0.0..)").bsearch { |x| x < 2 ? 1.0 : x > 2 ? -1.0 : 0.0 }
+ result.should == 2
+ end
+
+ it "returns an element at an index for which block returns 0" do
+ result = eval("(0.1..)").bsearch { |x| x < 1 ? 1 : x > 3 ? -1 : 0 }
+ result.should >= 1
+ result.should <= 3
+ end
+
+ it "works with infinity bounds" do
+ inf = Float::INFINITY
+ eval("(inf..)").bsearch { |x| 1 }.should == nil
+ eval("(inf...)").bsearch { |x| 1 }.should == nil
+ eval("(inf..)").bsearch { |x| x == inf ? 0 : 1 }.should == inf
+ eval("(inf...)").bsearch { |x| x == inf ? 0 : 1 }.should == nil
+ eval("(-inf..)").bsearch { |x| x == -inf ? 0 : -1 }.should == -inf
+ eval("(-inf...)").bsearch { |x| x == -inf ? 0 : -1 }.should == -inf
+ eval("(-inf..)").bsearch { |x| 3 - x }.should == 3
+ eval("(-inf...)").bsearch { |x| 3 - x }.should == 3
+ eval("(0.0...)").bsearch { 0 }.should != inf
+ end
+ end
+ end
+
+
+ context "with beginless ranges and Integer values" do
+ context "with a block returning true or false" do
+ it "returns the smallest element for which block returns true" do
+ (..10).bsearch { |x| x >= 2 }.should == 2
+ (...-1).bsearch { |x| x >= -10 }.should == -10
+ end
+ end
+
+ context "with a block returning negative, zero, positive numbers" do
+ it "returns nil if the block returns greater than zero for every element" do
+ (..0).bsearch { |x| 1 }.should be_nil
+ end
+
+ it "returns nil if the block never returns zero" do
+ (..0).bsearch { |x| x > 5 ? -1 : 1 }.should be_nil
+ end
+
+ it "accepts Float::INFINITY from the block" do
+ (..0).bsearch { |x| Float::INFINITY }.should be_nil
+ end
+
+ it "returns an element at an index for which block returns 0.0" do
+ result = (..10).bsearch { |x| x < 2 ? 1.0 : x > 2 ? -1.0 : 0.0 }
+ result.should == 2
+ end
+
+ it "returns an element at an index for which block returns 0" do
+ result = (...10).bsearch { |x| x < 1 ? 1 : x > 3 ? -1 : 0 }
+ [1, 2, 3].should include(result)
+ end
+ end
+ end
+
+ context "with beginless ranges and Float values" do
+ context "with a block returning true or false" do
+ it "returns nil if the block returns true for every element" do
+ (..-0.1).bsearch { |x| x > 0.0 }.should be_nil
+ (...-0.1).bsearch { |x| x > 0.0 }.should be_nil
+ end
+
+ it "returns nil if the block returns nil for every element" do
+ (..-0.1).bsearch { |x| nil }.should be_nil
+ (...-0.1).bsearch { |x| nil }.should be_nil
+ end
+
+ it "returns the smallest element for which block returns true" do
+ (..10).bsearch { |x| x >= 2 }.should == 2
+ (..10).bsearch { |x| x >= 1 }.should == 1
+ end
+
+ it "works with infinity bounds" do
+ inf = Float::INFINITY
+ (..inf).bsearch { |x| true }.should == -inf
+ (...inf).bsearch { |x| true }.should == -inf
+ (..-inf).bsearch { |x| true }.should == -inf
+ (...-inf).bsearch { |x| true }.should == nil
+ end
+ end
+
+ context "with a block returning negative, zero, positive numbers" do
+ it "returns nil if the block returns less than zero for every element" do
+ (..5.0).bsearch { |x| -1 }.should be_nil
+ (...5.0).bsearch { |x| -1 }.should be_nil
+ end
+
+ it "returns nil if the block returns greater than zero for every element" do
+ (..1.1).bsearch { |x| 1 }.should be_nil
+ (...1.1).bsearch { |x| 1 }.should be_nil
+ end
+
+ it "returns nil if the block never returns zero" do
+ (..6.3).bsearch { |x| x < 2 ? 1 : -1 }.should be_nil
+ end
+
+ it "accepts (+/-)Float::INFINITY from the block" do
+ (..5.0).bsearch { |x| Float::INFINITY }.should be_nil
+ (..7.0).bsearch { |x| -Float::INFINITY }.should be_nil
+ end
+
+ it "returns an element at an index for which block returns 0.0" do
+ result = (..8.0).bsearch { |x| x < 2 ? 1.0 : x > 2 ? -1.0 : 0.0 }
+ result.should == 2
+ end
+
+ it "returns an element at an index for which block returns 0" do
+ result = (..8.0).bsearch { |x| x < 1 ? 1 : x > 3 ? -1 : 0 }
+ result.should >= 1
+ result.should <= 3
+ end
+
+ it "works with infinity bounds" do
+ inf = Float::INFINITY
+ (..-inf).bsearch { |x| 1 }.should == nil
+ (...-inf).bsearch { |x| 1 }.should == nil
+ (..inf).bsearch { |x| x == inf ? 0 : 1 }.should == inf
+ (...inf).bsearch { |x| x == inf ? 0 : 1 }.should == nil
+ (..-inf).bsearch { |x| x == -inf ? 0 : -1 }.should == -inf
+ (...-inf).bsearch { |x| x == -inf ? 0 : -1 }.should == nil
+ (..inf).bsearch { |x| 3 - x }.should == 3
+ (...inf).bsearch { |x| 3 - x }.should == 3
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/range/case_compare_spec.rb b/spec/ruby/core/range/case_compare_spec.rb
new file mode 100644
index 0000000000..65878aaabe
--- /dev/null
+++ b/spec/ruby/core/range/case_compare_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'shared/cover_and_include'
+require_relative 'shared/cover'
+
+describe "Range#===" do
+ it "returns the result of calling #cover? on self" do
+ range = RangeSpecs::WithoutSucc.new(0)..RangeSpecs::WithoutSucc.new(10)
+ (range === RangeSpecs::WithoutSucc.new(2)).should == true
+ end
+
+ it_behaves_like :range_cover_and_include, :===
+ it_behaves_like :range_cover, :===
+
+ ruby_bug "#19533", "3.2"..."3.3" do
+ it "returns true on any value if begin and end are both nil" do
+ (nil..nil).should === 1
+ end
+ end
+end
diff --git a/spec/ruby/core/range/clone_spec.rb b/spec/ruby/core/range/clone_spec.rb
new file mode 100644
index 0000000000..cf6ce74da0
--- /dev/null
+++ b/spec/ruby/core/range/clone_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+describe "Range#clone" do
+ it "duplicates the range" do
+ original = (1..3)
+ copy = original.clone
+ copy.begin.should == 1
+ copy.end.should == 3
+ copy.should_not.exclude_end?
+ copy.should_not.equal? original
+
+ original = ("a"..."z")
+ copy = original.clone
+ copy.begin.should == "a"
+ copy.end.should == "z"
+ copy.should.exclude_end?
+ copy.should_not.equal? original
+ end
+
+ it "maintains the frozen state" do
+ (1..2).clone.frozen?.should == (1..2).frozen?
+ (1..).clone.frozen?.should == (1..).frozen?
+ Range.new(1, 2).clone.frozen?.should == Range.new(1, 2).frozen?
+ Class.new(Range).new(1, 2).clone.frozen?.should == Class.new(Range).new(1, 2).frozen?
+ end
+end
diff --git a/spec/ruby/core/range/count_spec.rb b/spec/ruby/core/range/count_spec.rb
new file mode 100644
index 0000000000..24d4a9caf3
--- /dev/null
+++ b/spec/ruby/core/range/count_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "Range#count" do
+ it "returns Infinity for beginless ranges without arguments or blocks" do
+ inf = Float::INFINITY
+ eval("('a'...)").count.should == inf
+ eval("(7..)").count.should == inf
+ (...'a').count.should == inf
+ (...nil).count.should == inf
+ (..10.0).count.should == inf
+ end
+end
diff --git a/spec/ruby/core/range/cover_spec.rb b/spec/ruby/core/range/cover_spec.rb
new file mode 100644
index 0000000000..fa881607e9
--- /dev/null
+++ b/spec/ruby/core/range/cover_spec.rb
@@ -0,0 +1,10 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'shared/cover_and_include'
+require_relative 'shared/cover'
+
+describe "Range#cover?" do
+ it_behaves_like :range_cover_and_include, :cover?
+ it_behaves_like :range_cover, :cover?
+ it_behaves_like :range_cover_subrange, :cover?
+end
diff --git a/spec/ruby/core/range/dup_spec.rb b/spec/ruby/core/range/dup_spec.rb
new file mode 100644
index 0000000000..fab3c3f1b2
--- /dev/null
+++ b/spec/ruby/core/range/dup_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "Range#dup" do
+ it "duplicates the range" do
+ original = (1..3)
+ copy = original.dup
+ copy.begin.should == 1
+ copy.end.should == 3
+ copy.should_not.exclude_end?
+ copy.should_not.equal?(original)
+
+ copy = ("a"..."z").dup
+ copy.begin.should == "a"
+ copy.end.should == "z"
+ copy.should.exclude_end?
+ end
+
+ it "creates an unfrozen range" do
+ (1..2).dup.should_not.frozen?
+ (1..).dup.should_not.frozen?
+ Range.new(1, 2).dup.should_not.frozen?
+ end
+end
diff --git a/spec/ruby/core/range/each_spec.rb b/spec/ruby/core/range/each_spec.rb
new file mode 100644
index 0000000000..ecae17c881
--- /dev/null
+++ b/spec/ruby/core/range/each_spec.rb
@@ -0,0 +1,114 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Range#each" do
+ it "passes each element to the given block by using #succ" do
+ a = []
+ (-5..5).each { |i| a << i }
+ a.should == [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]
+
+ a = []
+ ('A'..'D').each { |i| a << i }
+ a.should == ['A','B','C','D']
+
+ a = []
+ ('A'...'D').each { |i| a << i }
+ a.should == ['A','B','C']
+
+ a = []
+ (0xfffd...0xffff).each { |i| a << i }
+ a.should == [0xfffd, 0xfffe]
+
+ y = mock('y')
+ x = mock('x')
+ x.should_receive(:<=>).with(y).any_number_of_times.and_return(-1)
+ x.should_receive(:<=>).with(x).any_number_of_times.and_return(0)
+ x.should_receive(:succ).any_number_of_times.and_return(y)
+ y.should_receive(:<=>).with(x).any_number_of_times.and_return(1)
+ y.should_receive(:<=>).with(y).any_number_of_times.and_return(0)
+
+ a = []
+ (x..y).each { |i| a << i }
+ a.should == [x, y]
+ end
+
+ it "works for non-ASCII ranges" do
+ a = []
+ ('Σ'..'Ω').each { |i| a << i }
+ a.should == ["Σ", "Τ", "Υ", "Φ", "Χ", "Ψ", "Ω"]
+ end
+
+ it "works with endless ranges" do
+ a = []
+ eval("(-2..)").each { |x| break if x > 2; a << x }
+ a.should == [-2, -1, 0, 1, 2]
+
+ a = []
+ eval("(-2...)").each { |x| break if x > 2; a << x }
+ a.should == [-2, -1, 0, 1, 2]
+ end
+
+ it "works with String endless ranges" do
+ a = []
+ eval("('A'..)").each { |x| break if x > "D"; a << x }
+ a.should == ["A", "B", "C", "D"]
+
+ a = []
+ eval("('A'...)").each { |x| break if x > "D"; a << x }
+ a.should == ["A", "B", "C", "D"]
+ end
+
+ it "raises a TypeError beginless ranges" do
+ -> { (..2).each { |x| x } }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the first element does not respond to #succ" do
+ -> { (0.5..2.4).each { |i| i } }.should raise_error(TypeError)
+
+ b = mock('x')
+ (a = mock('1')).should_receive(:<=>).with(b).and_return(1)
+
+ -> { (a..b).each { |i| i } }.should raise_error(TypeError)
+ end
+
+ it "returns self" do
+ range = 1..10
+ range.each{}.should equal(range)
+ end
+
+ it "returns an enumerator when no block given" do
+ enum = (1..3).each
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == [1, 2, 3]
+ end
+
+ ruby_version_is "3.1" do
+ it "supports Time objects that respond to #succ" do
+ t = Time.utc(1970)
+ def t.succ; self + 1 end
+ t_succ = t.succ
+ def t_succ.succ; self + 1; end
+
+ (t..t_succ).to_a.should == [Time.utc(1970), Time.utc(1970, nil, nil, nil, nil, 1)]
+ (t...t_succ).to_a.should == [Time.utc(1970)]
+ end
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "raises a TypeError if the first element is a Time object even if it responds to #succ" do
+ t = Time.utc(1970)
+ def t.succ; self + 1 end
+ t_succ = t.succ
+ def t_succ.succ; self + 1; end
+
+ -> { (t..t_succ).each { |i| i } }.should raise_error(TypeError)
+ end
+ end
+
+ it "passes each Symbol element by using #succ" do
+ (:aa..:ac).each.to_a.should == [:aa, :ab, :ac]
+ (:aa...:ac).each.to_a.should == [:aa, :ab]
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :each, (1..3)
+end
diff --git a/spec/ruby/core/range/end_spec.rb b/spec/ruby/core/range/end_spec.rb
new file mode 100644
index 0000000000..9e5e6f7d43
--- /dev/null
+++ b/spec/ruby/core/range/end_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/end'
+
+describe "Range#end" do
+ it_behaves_like :range_end, :end
+end
diff --git a/spec/ruby/core/range/eql_spec.rb b/spec/ruby/core/range/eql_spec.rb
new file mode 100644
index 0000000000..fa6c71840e
--- /dev/null
+++ b/spec/ruby/core/range/eql_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal_value'
+
+describe "Range#eql?" do
+ it_behaves_like :range_eql, :eql?
+
+ it "returns false if the endpoints are not eql?" do
+ (0..1).should_not eql(0..1.0)
+ end
+end
diff --git a/spec/ruby/core/range/equal_value_spec.rb b/spec/ruby/core/range/equal_value_spec.rb
new file mode 100644
index 0000000000..83dcf5cec8
--- /dev/null
+++ b/spec/ruby/core/range/equal_value_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal_value'
+
+describe "Range#==" do
+ it_behaves_like :range_eql, :==
+
+ it "returns true if the endpoints are ==" do
+ (0..1).should == (0..1.0)
+ end
+
+ it "returns true if the endpoints are == for endless ranges" do
+ eval("(1.0..)").should == eval("(1.0..)")
+ end
+
+ it "returns true if the endpoints are == for beginless ranges" do
+ (...10).should == (...10)
+ end
+end
diff --git a/spec/ruby/core/range/exclude_end_spec.rb b/spec/ruby/core/range/exclude_end_spec.rb
new file mode 100644
index 0000000000..c4006fea78
--- /dev/null
+++ b/spec/ruby/core/range/exclude_end_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Range#exclude_end?" do
+ it "returns false if the range does not exclude the end value" do
+ (-2..2).should_not.exclude_end?
+ ('A'..'B').should_not.exclude_end?
+ (0.5..2.4).should_not.exclude_end?
+ (0xfffd..0xffff).should_not.exclude_end?
+ Range.new(0, 1).should_not.exclude_end?
+ end
+
+ it "returns true if the range excludes the end value" do
+ (0...5).should.exclude_end?
+ ('A'...'B').should.exclude_end?
+ (0.5...2.4).should.exclude_end?
+ (0xfffd...0xffff).should.exclude_end?
+ Range.new(0, 1, true).should.exclude_end?
+ end
+end
diff --git a/spec/ruby/core/range/first_spec.rb b/spec/ruby/core/range/first_spec.rb
new file mode 100644
index 0000000000..2af935f439
--- /dev/null
+++ b/spec/ruby/core/range/first_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative 'shared/begin'
+
+describe "Range#first" do
+ it_behaves_like :range_begin, :first
+
+ it "returns the specified number of elements from the beginning" do
+ (0..2).first(2).should == [0, 1]
+ end
+
+ it "returns an empty array for an empty Range" do
+ (0...0).first(2).should == []
+ end
+
+ it "returns an empty array when passed zero" do
+ (0..2).first(0).should == []
+ end
+
+ it "returns all elements in the range when count exceeds the number of elements" do
+ (0..2).first(4).should == [0, 1, 2]
+ end
+
+ it "raises an ArgumentError when count is negative" do
+ -> { (0..2).first(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "calls #to_int to convert the argument" do
+ obj = mock_int(2)
+ (3..7).first(obj).should == [3, 4]
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return("1")
+ -> { (2..3).first(obj) }.should raise_error(TypeError)
+ end
+
+ it "truncates the value when passed a Float" do
+ (2..9).first(2.8).should == [2, 3]
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { (2..3).first(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { (2..3).first("1") }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError when called on an beginless range" do
+ -> { (..1).first }.should raise_error(RangeError)
+ end
+end
diff --git a/spec/ruby/core/range/fixtures/classes.rb b/spec/ruby/core/range/fixtures/classes.rb
new file mode 100644
index 0000000000..3a1df010b2
--- /dev/null
+++ b/spec/ruby/core/range/fixtures/classes.rb
@@ -0,0 +1,90 @@
+module RangeSpecs
+ class TenfoldSucc
+ include Comparable
+
+ attr_reader :n
+
+ def initialize(n)
+ @n = n
+ end
+
+ def <=>(other)
+ @n <=> other.n
+ end
+
+ def succ
+ self.class.new(@n * 10)
+ end
+ end
+
+ # Custom Range classes Xs and Ys
+ class Custom
+ include Comparable
+ attr_reader :length
+
+ def initialize(n)
+ @length = n
+ end
+
+ def eql?(other)
+ inspect.eql? other.inspect
+ end
+ alias :== :eql?
+
+ def inspect
+ 'custom'
+ end
+
+ def <=>(other)
+ @length <=> other.length
+ end
+ end
+
+ class WithoutSucc
+ include Comparable
+ attr_reader :n
+
+ def initialize(n)
+ @n = n
+ end
+
+ def eql?(other)
+ inspect.eql? other.inspect
+ end
+ alias :== :eql?
+
+ def inspect
+ "WithoutSucc(#{@n})"
+ end
+
+ def <=>(other)
+ @n <=> other.n
+ end
+ end
+
+ class Xs < Custom # represent a string of 'x's
+ def succ
+ Xs.new(@length + 1)
+ end
+
+ def inspect
+ 'x' * @length
+ end
+ end
+
+ class Ys < Custom # represent a string of 'y's
+ def succ
+ Ys.new(@length + 1)
+ end
+
+ def inspect
+ 'y' * @length
+ end
+ end
+
+ class MyRange < Range
+ end
+
+ class ComparisonError < RuntimeError
+ end
+end
diff --git a/spec/ruby/core/range/frozen_spec.rb b/spec/ruby/core/range/frozen_spec.rb
new file mode 100644
index 0000000000..298ffc87cb
--- /dev/null
+++ b/spec/ruby/core/range/frozen_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+# There is no Range#frozen? method but this feels like the best place for these specs
+describe "Range#frozen?" do
+ ruby_version_is "3.0" do
+ it "is true for literal ranges" do
+ (1..2).should.frozen?
+ (1..).should.frozen?
+ (..1).should.frozen?
+ end
+
+ it "is true for Range.new" do
+ Range.new(1, 2).should.frozen?
+ Range.new(1, nil).should.frozen?
+ Range.new(nil, 1).should.frozen?
+ end
+
+ it "is false for instances of a subclass of Range" do
+ sub_range = Class.new(Range).new(1, 2)
+ sub_range.should_not.frozen?
+ end
+
+ it "is false for Range.allocate" do
+ Range.allocate.should_not.frozen?
+ end
+ end
+end
diff --git a/spec/ruby/core/range/hash_spec.rb b/spec/ruby/core/range/hash_spec.rb
new file mode 100644
index 0000000000..4f2681e86e
--- /dev/null
+++ b/spec/ruby/core/range/hash_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "Range#hash" do
+ it "is provided" do
+ (0..1).respond_to?(:hash).should == true
+ ('A'..'Z').respond_to?(:hash).should == true
+ (0xfffd..0xffff).respond_to?(:hash).should == true
+ (0.5..2.4).respond_to?(:hash).should == true
+ end
+
+ it "generates the same hash values for Ranges with the same start, end and exclude_end? values" do
+ (0..1).hash.should == (0..1).hash
+ (0...10).hash.should == (0...10).hash
+ (0..10).hash.should_not == (0...10).hash
+ end
+
+ it "generates an Integer for the hash value" do
+ (0..0).hash.should be_an_instance_of(Integer)
+ (0..1).hash.should be_an_instance_of(Integer)
+ (0...10).hash.should be_an_instance_of(Integer)
+ (0..10).hash.should be_an_instance_of(Integer)
+ end
+
+end
diff --git a/spec/ruby/core/range/include_spec.rb b/spec/ruby/core/range/include_spec.rb
new file mode 100644
index 0000000000..b2c7a54545
--- /dev/null
+++ b/spec/ruby/core/range/include_spec.rb
@@ -0,0 +1,10 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'shared/cover_and_include'
+require_relative 'shared/include'
+require_relative 'shared/cover'
+
+describe "Range#include?" do
+ it_behaves_like :range_cover_and_include, :include?
+ it_behaves_like :range_include, :include?
+end
diff --git a/spec/ruby/core/range/initialize_spec.rb b/spec/ruby/core/range/initialize_spec.rb
new file mode 100644
index 0000000000..8a6ca65daa
--- /dev/null
+++ b/spec/ruby/core/range/initialize_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+
+describe "Range#initialize" do
+ before do
+ @range = Range.allocate
+ end
+
+ it "is private" do
+ Range.should have_private_instance_method("initialize")
+ end
+
+ it "initializes correctly the Range object when given 2 arguments" do
+ -> { @range.send(:initialize, 0, 1) }.should_not raise_error
+ end
+
+ it "initializes correctly the Range object when given 3 arguments" do
+ -> { @range.send(:initialize, 0, 1, true) }.should_not raise_error
+ end
+
+ it "raises an ArgumentError if passed without or with only one argument" do
+ -> { @range.send(:initialize) }.should raise_error(ArgumentError)
+ -> { @range.send(:initialize, 1) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed with four or more arguments" do
+ -> { @range.send(:initialize, 1, 3, 5, 7) }.should raise_error(ArgumentError)
+ -> { @range.send(:initialize, 1, 3, 5, 7, 9) }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "raises a NameError if called on an already initialized Range" do
+ -> { (0..1).send(:initialize, 1, 3) }.should raise_error(NameError)
+ -> { (0..1).send(:initialize, 1, 3, true) }.should raise_error(NameError)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "raises a FrozenError if called on an already initialized Range" do
+ -> { (0..1).send(:initialize, 1, 3) }.should raise_error(FrozenError)
+ -> { (0..1).send(:initialize, 1, 3, true) }.should raise_error(FrozenError)
+ end
+ end
+
+ it "raises an ArgumentError if arguments don't respond to <=>" do
+ o1 = Object.new
+ o2 = Object.new
+
+ -> { @range.send(:initialize, o1, o2) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/range/inspect_spec.rb b/spec/ruby/core/range/inspect_spec.rb
new file mode 100644
index 0000000000..072de123b7
--- /dev/null
+++ b/spec/ruby/core/range/inspect_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Range#inspect" do
+ it "provides a printable form, using #inspect to convert the start and end objects" do
+ ('A'..'Z').inspect.should == '"A".."Z"'
+ ('A'...'Z').inspect.should == '"A"..."Z"'
+
+ (0..21).inspect.should == "0..21"
+ (-8..0).inspect.should == "-8..0"
+ (-411..959).inspect.should == "-411..959"
+ (0xfff..0xfffff).inspect.should == "4095..1048575"
+ (0.5..2.4).inspect.should == "0.5..2.4"
+ end
+
+ it "works for endless ranges" do
+ eval("(1..)").inspect.should == "1.."
+ eval("(0.1...)").inspect.should == "0.1..."
+ end
+
+ it "works for beginless ranges" do
+ (..1).inspect.should == "..1"
+ (...0.1).inspect.should == "...0.1"
+ end
+
+ it "works for nil ... nil ranges" do
+ (..nil).inspect.should == "nil..nil"
+ eval("(nil...)").inspect.should == "nil...nil"
+ end
+end
diff --git a/spec/ruby/core/range/last_spec.rb b/spec/ruby/core/range/last_spec.rb
new file mode 100644
index 0000000000..6698686dd5
--- /dev/null
+++ b/spec/ruby/core/range/last_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+require_relative 'shared/end'
+
+describe "Range#last" do
+ it_behaves_like :range_end, :last
+
+ it "returns the specified number of elements from the end" do
+ (1..5).last(3).should == [3, 4, 5]
+ end
+
+ ruby_bug '#18994', '2.7'...'3.2' do
+ it "returns the specified number if elements for single element inclusive range" do
+ (1..1).last(1).should == [1]
+ end
+ end
+
+ it "returns an empty array for an empty Range" do
+ (0...0).last(2).should == []
+ end
+
+ it "returns an empty array when passed zero" do
+ (0..2).last(0).should == []
+ end
+
+ it "returns all elements in the range when count exceeds the number of elements" do
+ (2..4).last(5).should == [2, 3, 4]
+ end
+
+ it "raises an ArgumentError when count is negative" do
+ -> { (0..2).last(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "calls #to_int to convert the argument" do
+ obj = mock_int(2)
+ (3..7).last(obj).should == [6, 7]
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return("1")
+ -> { (2..3).last(obj) }.should raise_error(TypeError)
+ end
+
+ it "truncates the value when passed a Float" do
+ (2..9).last(2.8).should == [8, 9]
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { (2..3).last(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { (2..3).last("1") }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError when called on an endless range" do
+ -> { eval("(1..)").last }.should raise_error(RangeError)
+ end
+end
diff --git a/spec/ruby/core/range/max_spec.rb b/spec/ruby/core/range/max_spec.rb
new file mode 100644
index 0000000000..6c9ada2a3c
--- /dev/null
+++ b/spec/ruby/core/range/max_spec.rb
@@ -0,0 +1,103 @@
+require_relative '../../spec_helper'
+
+describe "Range#max" do
+ it "returns the maximum value in the range when called with no arguments" do
+ (1..10).max.should == 10
+ (1...10).max.should == 9
+ (0...2**64).max.should == 18446744073709551615
+ ('f'..'l').max.should == 'l'
+ ('a'...'f').max.should == 'e'
+ end
+
+ it "returns the maximum value in the Float range when called with no arguments" do
+ (303.20..908.1111).max.should == 908.1111
+ end
+
+ it "raises TypeError when called on an exclusive range and a non Integer value" do
+ -> { (303.20...908.1111).max }.should raise_error(TypeError)
+ end
+
+ it "returns nil when the endpoint is less than the start point" do
+ (100..10).max.should be_nil
+ ('z'..'l').max.should be_nil
+ end
+
+ it "returns nil when the endpoint equals the start point and the range is exclusive" do
+ (5...5).max.should be_nil
+ end
+
+ it "returns the endpoint when the endpoint equals the start point and the range is inclusive" do
+ (5..5).max.should equal(5)
+ end
+
+ it "returns nil when the endpoint is less than the start point in a Float range" do
+ (3003.20..908.1111).max.should be_nil
+ end
+
+ it "returns end point when the range is Time..Time(included end point)" do
+ time_start = Time.now
+ time_end = Time.now + 1.0
+ (time_start..time_end).max.should equal(time_end)
+ end
+
+ it "raises TypeError when called on a Time...Time(excluded end point)" do
+ time_start = Time.now
+ time_end = Time.now + 1.0
+ -> { (time_start...time_end).max }.should raise_error(TypeError)
+ end
+
+ it "raises RangeError when called on an endless range" do
+ -> { eval("(1..)").max }.should raise_error(RangeError)
+ end
+
+ ruby_version_is "3.0" do
+ it "returns the end point for beginless ranges" do
+ (..1).max.should == 1
+ (..1.0).max.should == 1.0
+ end
+
+ it "raises for an exclusive beginless range" do
+ -> {
+ (...1).max
+ }.should raise_error(TypeError, 'cannot exclude end value with non Integer begin value')
+ end
+ end
+end
+
+describe "Range#max given a block" do
+ it "passes each pair of values in the range to the block" do
+ acc = []
+ (1..10).max {|a,b| acc << [a,b]; a }
+ acc.flatten!
+ (1..10).each do |value|
+ acc.include?(value).should be_true
+ end
+ end
+
+ it "passes each pair of elements to the block in reversed order" do
+ acc = []
+ (1..5).max {|a,b| acc << [a,b]; a }
+ acc.should == [[2,1],[3,2], [4,3], [5, 4]]
+ end
+
+ it "calls #> and #< on the return value of the block" do
+ obj = mock('obj')
+ obj.should_receive(:>).exactly(2).times
+ obj.should_receive(:<).exactly(2).times
+ (1..3).max {|a,b| obj }
+ end
+
+ it "returns the element the block determines to be the maximum" do
+ (1..3).max {|a,b| -3 }.should == 1
+ end
+
+ it "returns nil when the endpoint is less than the start point" do
+ (100..10).max {|x,y| x <=> y}.should be_nil
+ ('z'..'l').max {|x,y| x <=> y}.should be_nil
+ (5...5).max {|x,y| x <=> y}.should be_nil
+ end
+
+ it "raises RangeError when called with custom comparison method on an beginless range" do
+ -> { (..1).max {|a, b| a} }.should raise_error(RangeError)
+ end
+end
diff --git a/spec/ruby/core/range/member_spec.rb b/spec/ruby/core/range/member_spec.rb
new file mode 100644
index 0000000000..ab61f92951
--- /dev/null
+++ b/spec/ruby/core/range/member_spec.rb
@@ -0,0 +1,10 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'shared/cover_and_include'
+require_relative 'shared/include'
+require_relative 'shared/cover'
+
+describe "Range#member?" do
+ it_behaves_like :range_cover_and_include, :member?
+ it_behaves_like :range_include, :member?
+end
diff --git a/spec/ruby/core/range/min_spec.rb b/spec/ruby/core/range/min_spec.rb
new file mode 100644
index 0000000000..89310ee589
--- /dev/null
+++ b/spec/ruby/core/range/min_spec.rb
@@ -0,0 +1,88 @@
+require_relative '../../spec_helper'
+
+describe "Range#min" do
+ it "returns the minimum value in the range when called with no arguments" do
+ (1..10).min.should == 1
+ ('f'..'l').min.should == 'f'
+ end
+
+ it "returns the minimum value in the Float range when called with no arguments" do
+ (303.20..908.1111).min.should == 303.20
+ end
+
+ it "returns nil when the start point is greater than the endpoint" do
+ (100..10).min.should be_nil
+ ('z'..'l').min.should be_nil
+ end
+
+ it "returns nil when the endpoint equals the start point and the range is exclusive" do
+ (7...7).min.should be_nil
+ end
+
+ it "returns the start point when the endpoint equals the start point and the range is inclusive" do
+ (7..7).min.should equal(7)
+ end
+
+ it "returns nil when the start point is greater than the endpoint in a Float range" do
+ (3003.20..908.1111).min.should be_nil
+ end
+
+ it "returns start point when the range is Time..Time(included end point)" do
+ time_start = Time.now
+ time_end = Time.now + 1.0
+ (time_start..time_end).min.should equal(time_start)
+ end
+
+ it "returns start point when the range is Time...Time(excluded end point)" do
+ time_start = Time.now
+ time_end = Time.now + 1.0
+ (time_start...time_end).min.should equal(time_start)
+ end
+
+ it "returns the start point for endless ranges" do
+ eval("(1..)").min.should == 1
+ eval("(1.0...)").min.should == 1.0
+ end
+
+ it "raises RangeError when called on an beginless range" do
+ -> { (..1).min }.should raise_error(RangeError)
+ end
+end
+
+describe "Range#min given a block" do
+ it "passes each pair of values in the range to the block" do
+ acc = []
+ (1..10).min {|a,b| acc << [a,b]; a }
+ acc.flatten!
+ (1..10).each do |value|
+ acc.include?(value).should be_true
+ end
+ end
+
+ it "passes each pair of elements to the block where the first argument is the current element, and the last is the first element" do
+ acc = []
+ (1..5).min {|a,b| acc << [a,b]; a }
+ acc.should == [[2, 1], [3, 1], [4, 1], [5, 1]]
+ end
+
+ it "calls #> and #< on the return value of the block" do
+ obj = mock('obj')
+ obj.should_receive(:>).exactly(2).times
+ obj.should_receive(:<).exactly(2).times
+ (1..3).min {|a,b| obj }
+ end
+
+ it "returns the element the block determines to be the minimum" do
+ (1..3).min {|a,b| -3 }.should == 3
+ end
+
+ it "returns nil when the start point is greater than the endpoint" do
+ (100..10).min {|x,y| x <=> y}.should be_nil
+ ('z'..'l').min {|x,y| x <=> y}.should be_nil
+ (7...7).min {|x,y| x <=> y}.should be_nil
+ end
+
+ it "raises RangeError when called with custom comparison method on an endless range" do
+ -> { eval("(1..)").min {|a, b| a} }.should raise_error(RangeError)
+ end
+end
diff --git a/spec/ruby/core/range/minmax_spec.rb b/spec/ruby/core/range/minmax_spec.rb
new file mode 100644
index 0000000000..b2b4fd61a1
--- /dev/null
+++ b/spec/ruby/core/range/minmax_spec.rb
@@ -0,0 +1,132 @@
+require_relative '../../spec_helper'
+
+describe 'Range#minmax' do
+ before(:each) do
+ @x = mock('x')
+ @y = mock('y')
+
+ @x.should_receive(:<=>).with(@y).any_number_of_times.and_return(-1) # x < y
+ @x.should_receive(:<=>).with(@x).any_number_of_times.and_return(0) # x == x
+ @y.should_receive(:<=>).with(@x).any_number_of_times.and_return(1) # y > x
+ @y.should_receive(:<=>).with(@y).any_number_of_times.and_return(0) # y == y
+ end
+
+ describe 'on an inclusive range' do
+ it 'should raise RangeError on an endless range without iterating the range' do
+ @x.should_not_receive(:succ)
+
+ range = (@x..)
+
+ -> { range.minmax }.should raise_error(RangeError, 'cannot get the maximum of endless range')
+ end
+
+ it 'raises RangeError or ArgumentError on a beginless range' do
+ range = (..@x)
+
+ -> { range.minmax }.should raise_error(StandardError) { |e|
+ if RangeError === e
+ # error from #min
+ -> { raise e }.should raise_error(RangeError, 'cannot get the minimum of beginless range')
+ else
+ # error from #max
+ -> { raise e }.should raise_error(ArgumentError, 'comparison of NilClass with MockObject failed')
+ end
+ }
+ end
+
+ it 'should return beginning of range if beginning and end are equal without iterating the range' do
+ @x.should_not_receive(:succ)
+
+ (@x..@x).minmax.should == [@x, @x]
+ end
+
+ it 'should return nil pair if beginning is greater than end without iterating the range' do
+ @y.should_not_receive(:succ)
+
+ (@y..@x).minmax.should == [nil, nil]
+ end
+
+ it 'should return the minimum and maximum values for a non-numeric range without iterating the range' do
+ @x.should_not_receive(:succ)
+
+ (@x..@y).minmax.should == [@x, @y]
+ end
+
+ it 'should return the minimum and maximum values for a numeric range' do
+ (1..3).minmax.should == [1, 3]
+ end
+
+ it 'should return the minimum and maximum values for a numeric range without iterating the range' do
+ # We cannot set expectations on integers,
+ # so we "prevent" iteration by picking a value that would iterate until the spec times out.
+ range_end = Float::INFINITY
+
+ (1..range_end).minmax.should == [1, range_end]
+ end
+
+ it 'should return the minimum and maximum values according to the provided block by iterating the range' do
+ @x.should_receive(:succ).once.and_return(@y)
+
+ (@x..@y).minmax { |x, y| - (x <=> y) }.should == [@y, @x]
+ end
+ end
+
+ describe 'on an exclusive range' do
+ it 'should raise RangeError on an endless range' do
+ @x.should_not_receive(:succ)
+ range = (@x...)
+
+ -> { range.minmax }.should raise_error(RangeError, 'cannot get the maximum of endless range')
+ end
+
+ it 'should raise RangeError on a beginless range' do
+ range = (...@x)
+
+ -> { range.minmax }.should raise_error(RangeError,
+ /cannot get the maximum of beginless range with custom comparison method|cannot get the minimum of beginless range/)
+ end
+
+ ruby_bug "#17014", ""..."3.0" do
+ it 'should return nil pair if beginning and end are equal without iterating the range' do
+ @x.should_not_receive(:succ)
+
+ (@x...@x).minmax.should == [nil, nil]
+ end
+
+ it 'should return nil pair if beginning is greater than end without iterating the range' do
+ @y.should_not_receive(:succ)
+
+ (@y...@x).minmax.should == [nil, nil]
+ end
+
+ it 'should return the minimum and maximum values for a non-numeric range by iterating the range' do
+ @x.should_receive(:succ).once.and_return(@y)
+
+ (@x...@y).minmax.should == [@x, @x]
+ end
+ end
+
+ it 'should return the minimum and maximum values for a numeric range' do
+ (1...3).minmax.should == [1, 2]
+ end
+
+ it 'should return the minimum and maximum values for a numeric range without iterating the range' do
+ # We cannot set expectations on integers,
+ # so we "prevent" iteration by picking a value that would iterate until the spec times out.
+ range_end = bignum_value
+
+ (1...range_end).minmax.should == [1, range_end - 1]
+ end
+
+ it 'raises TypeError if the end value is not an integer' do
+ range = (0...Float::INFINITY)
+ -> { range.minmax }.should raise_error(TypeError, 'cannot exclude non Integer end value')
+ end
+
+ it 'should return the minimum and maximum values according to the provided block by iterating the range' do
+ @x.should_receive(:succ).once.and_return(@y)
+
+ (@x...@y).minmax { |x, y| - (x <=> y) }.should == [@x, @x]
+ end
+ end
+end
diff --git a/spec/ruby/core/range/new_spec.rb b/spec/ruby/core/range/new_spec.rb
new file mode 100644
index 0000000000..40df914b83
--- /dev/null
+++ b/spec/ruby/core/range/new_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Range.new" do
+ it "constructs a range using the given start and end" do
+ range = Range.new('a', 'c')
+ range.should == ('a'..'c')
+
+ range.first.should == 'a'
+ range.last.should == 'c'
+ end
+
+ it "includes the end object when the third parameter is omitted or false" do
+ Range.new('a', 'c').to_a.should == ['a', 'b', 'c']
+ Range.new(1, 3).to_a.should == [1, 2, 3]
+
+ Range.new('a', 'c', false).to_a.should == ['a', 'b', 'c']
+ Range.new(1, 3, false).to_a.should == [1, 2, 3]
+
+ Range.new('a', 'c', true).to_a.should == ['a', 'b']
+ Range.new(1, 3, 1).to_a.should == [1, 2]
+
+ Range.new(1, 3, mock('[1,2]')).to_a.should == [1, 2]
+ Range.new(1, 3, :test).to_a.should == [1, 2]
+ end
+
+ it "raises an ArgumentError when the given start and end can't be compared by using #<=>" do
+ -> { Range.new(1, mock('x')) }.should raise_error(ArgumentError)
+ -> { Range.new(mock('x'), mock('y')) }.should raise_error(ArgumentError)
+
+ b = mock('x')
+ (a = mock('nil')).should_receive(:<=>).with(b).and_return(nil)
+ -> { Range.new(a, b) }.should raise_error(ArgumentError)
+ end
+
+ it "does not rescue exception raised in #<=> when compares the given start and end" do
+ b = mock('a')
+ a = mock('b')
+ a.should_receive(:<=>).with(b).and_raise(RangeSpecs::ComparisonError)
+
+ -> { Range.new(a, b) }.should raise_error(RangeSpecs::ComparisonError)
+ end
+
+ describe "beginless/endless range" do
+ it "allows beginless left boundary" do
+ range = Range.new(nil, 1)
+ range.begin.should == nil
+ end
+
+ it "distinguishes ranges with included and excluded right boundary" do
+ range_exclude = Range.new(nil, 1, true)
+ range_include = Range.new(nil, 1, false)
+
+ range_exclude.should_not == range_include
+ end
+
+ it "allows endless right boundary" do
+ range = Range.new(1, nil)
+ range.end.should == nil
+ end
+
+ it "distinguishes ranges with included and excluded right boundary" do
+ range_exclude = Range.new(1, nil, true)
+ range_include = Range.new(1, nil, false)
+
+ range_exclude.should_not == range_include
+ end
+
+ ruby_version_is "3.0" do
+ it "creates a frozen range if the class is Range.class" do
+ Range.new(1, 2).should.frozen?
+ end
+
+ it "does not create a frozen range if the class is not Range.class" do
+ Class.new(Range).new(1, 2).should_not.frozen?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/range/percent_spec.rb b/spec/ruby/core/range/percent_spec.rb
new file mode 100644
index 0000000000..5ec6770ddb
--- /dev/null
+++ b/spec/ruby/core/range/percent_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Range#%" do
+ it "works as a Range#step" do
+ aseq = (1..10) % 2
+ aseq.class.should == Enumerator::ArithmeticSequence
+ aseq.begin.should == 1
+ aseq.end.should == 10
+ aseq.step.should == 2
+ aseq.to_a.should == [1, 3, 5, 7, 9]
+ end
+
+ it "produces an arithmetic sequence with a percent sign in #inspect" do
+ ((1..10) % 2).inspect.should == "((1..10).%(2))"
+ end
+end
diff --git a/spec/ruby/core/range/range_spec.rb b/spec/ruby/core/range/range_spec.rb
new file mode 100644
index 0000000000..8e9433f8c1
--- /dev/null
+++ b/spec/ruby/core/range/range_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Range" do
+ it "includes Enumerable" do
+ Range.include?(Enumerable).should == true
+ end
+end
diff --git a/spec/ruby/core/range/shared/begin.rb b/spec/ruby/core/range/shared/begin.rb
new file mode 100644
index 0000000000..f660e3faf9
--- /dev/null
+++ b/spec/ruby/core/range/shared/begin.rb
@@ -0,0 +1,10 @@
+describe :range_begin, shared: true do
+ it "returns the first element of self" do
+ (-1..1).send(@method).should == -1
+ (0..1).send(@method).should == 0
+ (0xffff...0xfffff).send(@method).should == 65535
+ ('Q'..'T').send(@method).should == 'Q'
+ ('Q'...'T').send(@method).should == 'Q'
+ (0.5..2.4).send(@method).should == 0.5
+ end
+end
diff --git a/spec/ruby/core/range/shared/cover.rb b/spec/ruby/core/range/shared/cover.rb
new file mode 100644
index 0000000000..0b41a26455
--- /dev/null
+++ b/spec/ruby/core/range/shared/cover.rb
@@ -0,0 +1,193 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :range_cover, shared: true do
+ it "uses the range element's <=> to make the comparison" do
+ a = mock('a')
+ a.should_receive(:<=>).twice.and_return(-1,-1)
+ (a..'z').send(@method, 'b').should be_true
+ end
+
+ it "uses a continuous inclusion test" do
+ ('a'..'f').send(@method, 'aa').should be_true
+ ('a'..'f').send(@method, 'babe').should be_true
+ ('a'..'f').send(@method, 'baby').should be_true
+ ('a'..'f').send(@method, 'ga').should be_false
+ (-10..-2).send(@method, -2.5).should be_true
+ end
+
+ describe "on string elements" do
+ it "returns true if other is matched by element.succ" do
+ ('a'..'c').send(@method, 'b').should be_true
+ ('a'...'c').send(@method, 'b').should be_true
+ end
+
+ it "returns true if other is not matched by element.succ" do
+ ('a'..'c').send(@method, 'bc').should be_true
+ ('a'...'c').send(@method, 'bc').should be_true
+ end
+ end
+
+ describe "with weird succ" do
+ describe "when included end value" do
+ before :each do
+ @range = RangeSpecs::TenfoldSucc.new(1)..RangeSpecs::TenfoldSucc.new(99)
+ end
+
+ it "returns false if other is less than first element" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(0)).should be_false
+ end
+
+ it "returns true if other is equal as first element" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(1)).should be_true
+ end
+
+ it "returns true if other is matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(10)).should be_true
+ end
+
+ it "returns true if other is not matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(2)).should be_true
+ end
+
+ it "returns true if other is equal as last element but not matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(99)).should be_true
+ end
+
+ it "returns false if other is greater than last element but matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(100)).should be_false
+ end
+ end
+
+ describe "when excluded end value" do
+ before :each do
+ @range = RangeSpecs::TenfoldSucc.new(1)...RangeSpecs::TenfoldSucc.new(99)
+ end
+
+ it "returns false if other is less than first element" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(0)).should be_false
+ end
+
+ it "returns true if other is equal as first element" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(1)).should be_true
+ end
+
+ it "returns true if other is matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(10)).should be_true
+ end
+
+ it "returns true if other is not matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(2)).should be_true
+ end
+
+ it "returns false if other is equal as last element but not matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(99)).should be_false
+ end
+
+ it "returns false if other is greater than last element but matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(100)).should be_false
+ end
+ end
+ end
+end
+
+describe :range_cover_subrange, shared: true do
+ context "range argument" do
+ it "accepts range argument" do
+ (0..10).send(@method, (3..7)).should be_true
+ (0..10).send(@method, (3..15)).should be_false
+ (0..10).send(@method, (-2..7)).should be_false
+
+ (1.1..7.9).send(@method, (2.5..6.5)).should be_true
+ (1.1..7.9).send(@method, (2.5..8.5)).should be_false
+ (1.1..7.9).send(@method, (0.5..6.5)).should be_false
+
+ ('c'..'i').send(@method, ('d'..'f')).should be_true
+ ('c'..'i').send(@method, ('d'..'z')).should be_false
+ ('c'..'i').send(@method, ('a'..'f')).should be_false
+
+ range_10_100 = RangeSpecs::TenfoldSucc.new(10)..RangeSpecs::TenfoldSucc.new(100)
+ range_20_90 = RangeSpecs::TenfoldSucc.new(20)..RangeSpecs::TenfoldSucc.new(90)
+ range_20_110 = RangeSpecs::TenfoldSucc.new(20)..RangeSpecs::TenfoldSucc.new(110)
+ range_0_90 = RangeSpecs::TenfoldSucc.new(0)..RangeSpecs::TenfoldSucc.new(90)
+
+ range_10_100.send(@method, range_20_90).should be_true
+ range_10_100.send(@method, range_20_110).should be_false
+ range_10_100.send(@method, range_0_90).should be_false
+ end
+
+ it "supports boundaries of different comparable types" do
+ (0..10).send(@method, (3.1..7.9)).should be_true
+ (0..10).send(@method, (3.1..15.9)).should be_false
+ (0..10).send(@method, (-2.1..7.9)).should be_false
+ end
+
+ it "returns false if types are not comparable" do
+ (0..10).send(@method, ('a'..'z')).should be_false
+ (0..10).send(@method, (RangeSpecs::TenfoldSucc.new(0)..RangeSpecs::TenfoldSucc.new(100))).should be_false
+ end
+
+ it "honors exclusion of right boundary (:exclude_end option)" do
+ # Integer
+ (0..10).send(@method, (0..10)).should be_true
+ (0...10).send(@method, (0...10)).should be_true
+
+ (0..10).send(@method, (0...10)).should be_true
+ (0...10).send(@method, (0..10)).should be_false
+
+ (0...11).send(@method, (0..10)).should be_true
+ (0..10).send(@method, (0...11)).should be_true
+
+ # Float
+ (0..10.1).send(@method, (0..10.1)).should be_true
+ (0...10.1).send(@method, (0...10.1)).should be_true
+
+ (0..10.1).send(@method, (0...10.1)).should be_true
+ (0...10.1).send(@method, (0..10.1)).should be_false
+
+ (0...11.1).send(@method, (0..10.1)).should be_true
+ (0..10.1).send(@method, (0...11.1)).should be_false
+ end
+ end
+
+ it "allows self to be a beginless range" do
+ (...10).send(@method, (3..7)).should be_true
+ (...10).send(@method, (3..15)).should be_false
+
+ (..7.9).send(@method, (2.5..6.5)).should be_true
+ (..7.9).send(@method, (2.5..8.5)).should be_false
+
+ (..'i').send(@method, ('d'..'f')).should be_true
+ (..'i').send(@method, ('d'..'z')).should be_false
+ end
+
+ it "allows self to be a endless range" do
+ eval("(0...)").send(@method, (3..7)).should be_true
+ eval("(5...)").send(@method, (3..15)).should be_false
+
+ eval("(1.1..)").send(@method, (2.5..6.5)).should be_true
+ eval("(3.3..)").send(@method, (2.5..8.5)).should be_false
+
+ eval("('a'..)").send(@method, ('d'..'f')).should be_true
+ eval("('p'..)").send(@method, ('d'..'z')).should be_false
+ end
+
+ it "accepts beginless range argument" do
+ (..10).send(@method, (...10)).should be_true
+ (0..10).send(@method, (...10)).should be_false
+
+ (1.1..7.9).send(@method, (...10.5)).should be_false
+
+ ('c'..'i').send(@method, (..'i')).should be_false
+ end
+
+ it "accepts endless range argument" do
+ eval("(0..)").send(@method, eval("(0...)")).should be_true
+ (0..10).send(@method, eval("(0...)")).should be_false
+
+ (1.1..7.9).send(@method, eval("(0.8...)")).should be_false
+
+ ('c'..'i').send(@method, eval("('a'..)")).should be_false
+ end
+end
diff --git a/spec/ruby/core/range/shared/cover_and_include.rb b/spec/ruby/core/range/shared/cover_and_include.rb
new file mode 100644
index 0000000000..7028afaa89
--- /dev/null
+++ b/spec/ruby/core/range/shared/cover_and_include.rb
@@ -0,0 +1,76 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+
+describe :range_cover_and_include, shared: true do
+ it "returns true if other is an element of self" do
+ (0..5).send(@method, 2).should == true
+ (-5..5).send(@method, 0).should == true
+ (-1...1).send(@method, 10.5).should == false
+ (-10..-2).send(@method, -2.5).should == true
+ ('C'..'X').send(@method, 'M').should == true
+ ('C'..'X').send(@method, 'A').should == false
+ ('B'...'W').send(@method, 'W').should == false
+ ('B'...'W').send(@method, 'Q').should == true
+ (0xffff..0xfffff).send(@method, 0xffffd).should == true
+ (0xffff..0xfffff).send(@method, 0xfffd).should == false
+ (0.5..2.4).send(@method, 2).should == true
+ (0.5..2.4).send(@method, 2.5).should == false
+ (0.5..2.4).send(@method, 2.4).should == true
+ (0.5...2.4).send(@method, 2.4).should == false
+ end
+
+ it "returns true if other is an element of self for endless ranges" do
+ eval("(1..)").send(@method, 2.4).should == true
+ eval("(0.5...)").send(@method, 2.4).should == true
+ end
+
+ it "returns true if other is an element of self for beginless ranges" do
+ (..10).send(@method, 2.4).should == true
+ (...10.5).send(@method, 2.4).should == true
+ end
+
+ it "compares values using <=>" do
+ rng = (1..5)
+ m = mock("int")
+ m.should_receive(:coerce).and_return([1, 2])
+ m.should_receive(:<=>).and_return(1)
+
+ rng.send(@method, m).should be_false
+ end
+
+ it "raises an ArgumentError without exactly one argument" do
+ ->{ (1..2).send(@method) }.should raise_error(ArgumentError)
+ ->{ (1..2).send(@method, 1, 2) }.should raise_error(ArgumentError)
+ end
+
+ it "returns true if argument is equal to the first value of the range" do
+ (0..5).send(@method, 0).should be_true
+ ('f'..'s').send(@method, 'f').should be_true
+ end
+
+ it "returns true if argument is equal to the last value of the range" do
+ (0..5).send(@method, 5).should be_true
+ (0...5).send(@method, 4).should be_true
+ ('f'..'s').send(@method, 's').should be_true
+ end
+
+ it "returns true if argument is less than the last value of the range and greater than the first value" do
+ (20..30).send(@method, 28).should be_true
+ ('e'..'h').send(@method, 'g').should be_true
+ ("\u{999}".."\u{9999}").send @method, "\u{9995}"
+ end
+
+ it "returns true if argument is sole element in the range" do
+ (30..30).send(@method, 30).should be_true
+ end
+
+ it "returns false if range is empty" do
+ (30...30).send(@method, 30).should be_false
+ (30...30).send(@method, nil).should be_false
+ end
+
+ it "returns false if the range does not contain the argument" do
+ ('A'..'C').send(@method, 20.9).should be_false
+ ('A'...'C').send(@method, 'C').should be_false
+ end
+end
diff --git a/spec/ruby/core/range/shared/end.rb b/spec/ruby/core/range/shared/end.rb
new file mode 100644
index 0000000000..b26394fe31
--- /dev/null
+++ b/spec/ruby/core/range/shared/end.rb
@@ -0,0 +1,10 @@
+describe :range_end, shared: true do
+ it "end returns the last element of self" do
+ (-1..1).send(@method).should == 1
+ (0..1).send(@method).should == 1
+ ("A".."Q").send(@method).should == "Q"
+ ("A"..."Q").send(@method).should == "Q"
+ (0xffff...0xfffff).send(@method).should == 1048575
+ (0.5..2.4).send(@method).should == 2.4
+ end
+end
diff --git a/spec/ruby/core/range/shared/equal_value.rb b/spec/ruby/core/range/shared/equal_value.rb
new file mode 100644
index 0000000000..363c6be558
--- /dev/null
+++ b/spec/ruby/core/range/shared/equal_value.rb
@@ -0,0 +1,51 @@
+require_relative '../fixtures/classes'
+
+describe :range_eql, shared: true do
+ it "returns true if other has same begin, end, and exclude_end? values" do
+ (0..2).send(@method, 0..2).should == true
+ ('G'..'M').send(@method,'G'..'M').should == true
+ (0.5..2.4).send(@method, 0.5..2.4).should == true
+ (5..10).send(@method, Range.new(5,10)).should == true
+ ('D'..'V').send(@method, Range.new('D','V')).should == true
+ (0.5..2.4).send(@method, Range.new(0.5, 2.4)).should == true
+ (0xffff..0xfffff).send(@method, 0xffff..0xfffff).should == true
+ (0xffff..0xfffff).send(@method, Range.new(0xffff,0xfffff)).should == true
+
+ a = RangeSpecs::Xs.new(3)..RangeSpecs::Xs.new(5)
+ b = Range.new(RangeSpecs::Xs.new(3), RangeSpecs::Xs.new(5))
+ a.send(@method, b).should == true
+ end
+
+ it "returns false if one of the attributes differs" do
+ ('Q'..'X').send(@method, 'A'..'C').should == false
+ ('Q'...'X').send(@method, 'Q'..'W').should == false
+ ('Q'..'X').send(@method, 'Q'...'X').should == false
+ (0.5..2.4).send(@method, 0.5...2.4).should == false
+ (1482..1911).send(@method, 1482...1911).should == false
+ (0xffff..0xfffff).send(@method, 0xffff...0xfffff).should == false
+
+ a = RangeSpecs::Xs.new(3)..RangeSpecs::Xs.new(5)
+ b = Range.new(RangeSpecs::Ys.new(3), RangeSpecs::Ys.new(5))
+ a.send(@method, b).should == false
+ end
+
+ it "returns false if other is not a Range" do
+ (1..10).send(@method, 1).should == false
+ (1..10).send(@method, 'a').should == false
+ (1..10).send(@method, mock('x')).should == false
+ end
+
+ it "returns true for subclasses of Range" do
+ Range.new(1, 2).send(@method, RangeSpecs::MyRange.new(1, 2)).should == true
+
+ a = Range.new(RangeSpecs::Xs.new(3), RangeSpecs::Xs.new(5))
+ b = RangeSpecs::MyRange.new(RangeSpecs::Xs.new(3), RangeSpecs::Xs.new(5))
+ a.send(@method, b).should == true
+ end
+
+ it "works for endless Ranges" do
+ eval("(1..)").send(@method, eval("(1..)")).should == true
+ eval("(0.5...)").send(@method, eval("(0.5...)")).should == true
+ eval("(1..)").send(@method, eval("(1...)")).should == false
+ end
+end
diff --git a/spec/ruby/core/range/shared/include.rb b/spec/ruby/core/range/shared/include.rb
new file mode 100644
index 0000000000..c6c5c2becf
--- /dev/null
+++ b/spec/ruby/core/range/shared/include.rb
@@ -0,0 +1,91 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :range_include, shared: true do
+ describe "on string elements" do
+ it "returns true if other is matched by element.succ" do
+ ('a'..'c').send(@method, 'b').should be_true
+ ('a'...'c').send(@method, 'b').should be_true
+ end
+
+ it "returns false if other is not matched by element.succ" do
+ ('a'..'c').send(@method, 'bc').should be_false
+ ('a'...'c').send(@method, 'bc').should be_false
+ end
+ end
+
+ describe "with weird succ" do
+ describe "when included end value" do
+ before :each do
+ @range = RangeSpecs::TenfoldSucc.new(1)..RangeSpecs::TenfoldSucc.new(99)
+ end
+
+ it "returns false if other is less than first element" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(0)).should be_false
+ end
+
+ it "returns true if other is equal as first element" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(1)).should be_true
+ end
+
+ it "returns true if other is matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(10)).should be_true
+ end
+
+ it "returns false if other is not matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(2)).should be_false
+ end
+
+ it "returns false if other is equal as last element but not matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(99)).should be_false
+ end
+
+ it "returns false if other is greater than last element but matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(100)).should be_false
+ end
+ end
+
+ describe "when excluded end value" do
+ before :each do
+ @range = RangeSpecs::TenfoldSucc.new(1)...RangeSpecs::TenfoldSucc.new(99)
+ end
+
+ it "returns false if other is less than first element" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(0)).should be_false
+ end
+
+ it "returns true if other is equal as first element" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(1)).should be_true
+ end
+
+ it "returns true if other is matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(10)).should be_true
+ end
+
+ it "returns false if other is not matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(2)).should be_false
+ end
+
+ it "returns false if other is equal as last element but not matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(99)).should be_false
+ end
+
+ it "returns false if other is greater than last element but matched by element.succ" do
+ @range.send(@method, RangeSpecs::TenfoldSucc.new(100)).should be_false
+ end
+ end
+ end
+
+ describe "with Time endpoints" do
+ it "uses cover? logic" do
+ now = Time.now
+ range = (now..(now + 60))
+
+ range.include?(now).should == true
+ range.include?(now - 1).should == false
+ range.include?(now + 60).should == true
+ range.include?(now + 61).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/range/size_spec.rb b/spec/ruby/core/range/size_spec.rb
new file mode 100644
index 0000000000..9b625c9963
--- /dev/null
+++ b/spec/ruby/core/range/size_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../../spec_helper'
+
+describe "Range#size" do
+ it "returns the number of elements in the range" do
+ (1..16).size.should == 16
+ (1...16).size.should == 15
+
+ (1.0..16.0).size.should == 16
+ (1.0...16.0).size.should == 15
+ (1.0..15.9).size.should == 15
+ (1.1..16.0).size.should == 15
+ (1.1..15.9).size.should == 15
+ end
+
+ it "returns 0 if last is less than first" do
+ (16..0).size.should == 0
+ (16.0..0.0).size.should == 0
+ (Float::INFINITY..0).size.should == 0
+ end
+
+ it 'returns Float::INFINITY for increasing, infinite ranges' do
+ (0..Float::INFINITY).size.should == Float::INFINITY
+ (-Float::INFINITY..0).size.should == Float::INFINITY
+ (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY
+ end
+
+ it 'returns Float::INFINITY for endless ranges if the start is numeric' do
+ eval("(1..)").size.should == Float::INFINITY
+ eval("(0.5...)").size.should == Float::INFINITY
+ end
+
+ it 'returns nil for endless ranges if the start is not numeric' do
+ eval("('z'..)").size.should == nil
+ eval("([]...)").size.should == nil
+ end
+
+ ruby_version_is ""..."3.2" do
+ it 'returns Float::INFINITY for all beginless ranges' do
+ (..1).size.should == Float::INFINITY
+ (...0.5).size.should == Float::INFINITY
+ (..nil).size.should == Float::INFINITY
+ (...'o').size.should == Float::INFINITY
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it 'returns Float::INFINITY for all beginless ranges if the start is numeric' do
+ (..1).size.should == Float::INFINITY
+ (...0.5).size.should == Float::INFINITY
+ end
+
+ it 'returns nil for all beginless ranges if the start is numeric' do
+ (...'o').size.should == nil
+ end
+
+ it 'returns nil if the start and the end is both nil' do
+ (nil..nil).size.should == nil
+ end
+ end
+
+ it "returns nil if first and last are not Numeric" do
+ (:a..:z).size.should be_nil
+ ('a'..'z').size.should be_nil
+ end
+end
diff --git a/spec/ruby/core/range/step_spec.rb b/spec/ruby/core/range/step_spec.rb
new file mode 100644
index 0000000000..61ddc5205d
--- /dev/null
+++ b/spec/ruby/core/range/step_spec.rb
@@ -0,0 +1,514 @@
+require_relative '../../spec_helper'
+
+describe "Range#step" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "returns self" do
+ r = 1..2
+ r.step { }.should equal(r)
+ end
+
+ it "raises TypeError if step" do
+ obj = mock("mock")
+ -> { (1..10).step(obj) { } }.should raise_error(TypeError)
+ end
+
+ it "calls #to_int to coerce step to an Integer" do
+ obj = mock("Range#step")
+ obj.should_receive(:to_int).and_return(1)
+
+ (1..2).step(obj) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([1, 2])
+ end
+
+ it "raises a TypeError if step does not respond to #to_int" do
+ obj = mock("Range#step non-integer")
+
+ -> { (1..2).step(obj) { } }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ obj = mock("Range#step non-integer")
+ obj.should_receive(:to_int).and_return("1")
+
+ -> { (1..2).step(obj) { } }.should raise_error(TypeError)
+ end
+
+ it "coerces the argument to integer by invoking to_int" do
+ (obj = mock("2")).should_receive(:to_int).and_return(2)
+ res = []
+ (1..10).step(obj) {|x| res << x}
+ res.should == [1, 3, 5, 7, 9]
+ end
+
+ it "raises a TypeError if the first element does not respond to #succ" do
+ obj = mock("Range#step non-comparable")
+ obj.should_receive(:<=>).with(obj).and_return(1)
+
+ -> { (obj..obj).step { |x| x } }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError if step is 0" do
+ -> { (-1..1).step(0) { |x| x } }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if step is 0.0" do
+ -> { (-1..1).step(0.0) { |x| x } }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if step is negative" do
+ -> { (-1..1).step(-2) { |x| x } }.should raise_error(ArgumentError)
+ end
+
+ describe "with inclusive end" do
+ describe "and Integer values" do
+ it "yields Integer values incremented by 1 and less than or equal to end when not passed a step" do
+ (-2..2).step { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-2, -1, 0, 1, 2])
+ end
+
+ it "yields Integer values incremented by an Integer step" do
+ (-5..5).step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-5, -3, -1, 1, 3, 5])
+ end
+
+ it "yields Float values incremented by a Float step" do
+ (-2..2).step(1.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
+ end
+ end
+
+ describe "and Float values" do
+ it "yields Float values incremented by 1 and less than or equal to end when not passed a step" do
+ (-2.0..2.0).step { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0, 2.0])
+ end
+
+ it "yields Float values incremented by an Integer step" do
+ (-5.0..5.0).step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
+ end
+
+ it "yields Float values incremented by a Float step" do
+ (-1.0..1.0).step(0.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5, 1.0])
+ end
+
+ it "returns Float values of 'step * n + begin <= end'" do
+ (1.0..6.4).step(1.8) { |x| ScratchPad << x }
+ (1.0..12.7).step(1.3) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([1.0, 2.8, 4.6, 6.4, 1.0, 2.3, 3.6,
+ 4.9, 6.2, 7.5, 8.8, 10.1, 11.4, 12.7])
+ end
+
+ it "handles infinite values at either end" do
+ (-Float::INFINITY..0.0).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
+ ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
+
+ ScratchPad.record []
+ (0.0..Float::INFINITY).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
+ ScratchPad.recorded.should eql([0.0, 2.0, 4.0])
+ end
+ end
+
+ describe "and Integer, Float values" do
+ it "yields Float values incremented by 1 and less than or equal to end when not passed a step" do
+ (-2..2.0).step { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0, 2.0])
+ end
+
+ it "yields Float values incremented by an Integer step" do
+ (-5..5.0).step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
+ end
+
+ it "yields Float values incremented by a Float step" do
+ (-1..1.0).step(0.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5, 1.0])
+ end
+ end
+
+ describe "and Float, Integer values" do
+ it "yields Float values incremented by 1 and less than or equal to end when not passed a step" do
+ (-2.0..2).step { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0, 2.0])
+ end
+
+ it "yields Float values incremented by an Integer step" do
+ (-5.0..5).step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
+ end
+
+ it "yields Float values incremented by a Float step" do
+ (-1.0..1).step(0.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5, 1.0])
+ end
+ end
+
+ describe "and String values" do
+ it "yields String values incremented by #succ and less than or equal to end when not passed a step" do
+ ("A".."E").step { |x| ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "B", "C", "D", "E"]
+ end
+
+ it "yields String values incremented by #succ called Integer step times" do
+ ("A".."G").step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "C", "E", "G"]
+ end
+
+ it "raises a TypeError when passed a Float step" do
+ -> { ("A".."G").step(2.0) { } }.should raise_error(TypeError)
+ end
+
+ it "calls #succ on begin and each element returned by #succ" do
+ obj = mock("Range#step String start")
+ obj.should_receive(:<=>).exactly(3).times.and_return(-1, -1, -1, 0)
+ obj.should_receive(:succ).exactly(2).times.and_return(obj)
+
+ (obj..obj).step { |x| ScratchPad << x }
+ ScratchPad.recorded.should == [obj, obj, obj]
+ end
+ end
+ end
+
+ describe "with exclusive end" do
+ describe "and Integer values" do
+ it "yields Integer values incremented by 1 and less than end when not passed a step" do
+ (-2...2).step { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-2, -1, 0, 1])
+ end
+
+ it "yields Integer values incremented by an Integer step" do
+ (-5...5).step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-5, -3, -1, 1, 3])
+ end
+
+ it "yields Float values incremented by a Float step" do
+ (-2...2).step(1.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
+ end
+ end
+
+ describe "and Float values" do
+ it "yields Float values incremented by 1 and less than end when not passed a step" do
+ (-2.0...2.0).step { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
+ end
+
+ it "yields Float values incremented by an Integer step" do
+ (-5.0...5.0).step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
+ end
+
+ it "yields Float values incremented by a Float step" do
+ (-1.0...1.0).step(0.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
+ end
+
+ it "returns Float values of 'step * n + begin < end'" do
+ (1.0...6.4).step(1.8) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([1.0, 2.8, 4.6])
+ end
+
+ ruby_version_is '3.1' do
+ it "correctly handles values near the upper limit" do # https://bugs.ruby-lang.org/issues/16612
+ (1.0...55.6).step(18.2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([1.0, 19.2, 37.4, 55.599999999999994])
+
+ (1.0...55.6).step(18.2).size.should == 4
+ end
+ end
+
+ it "handles infinite values at either end" do
+ (-Float::INFINITY...0.0).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
+ ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
+
+ ScratchPad.record []
+ (0.0...Float::INFINITY).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
+ ScratchPad.recorded.should eql([0.0, 2.0, 4.0])
+ end
+ end
+
+ describe "and Integer, Float values" do
+ it "yields Float values incremented by 1 and less than end when not passed a step" do
+ (-2...2.0).step { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
+ end
+
+ it "yields Float values incremented by an Integer step" do
+ (-5...5.0).step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
+ end
+
+ it "yields an Float and then Float values incremented by a Float step" do
+ (-1...1.0).step(0.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
+ end
+ end
+
+ describe "and Float, Integer values" do
+ it "yields Float values incremented by 1 and less than end when not passed a step" do
+ (-2.0...2).step { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
+ end
+
+ it "yields Float values incremented by an Integer step" do
+ (-5.0...5).step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
+ end
+
+ it "yields Float values incremented by a Float step" do
+ (-1.0...1).step(0.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
+ end
+ end
+
+ describe "and String values" do
+ it "yields String values incremented by #succ and less than or equal to end when not passed a step" do
+ ("A"..."E").step { |x| ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "B", "C", "D"]
+ end
+
+ it "yields String values incremented by #succ called Integer step times" do
+ ("A"..."G").step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "C", "E"]
+ end
+
+ it "raises a TypeError when passed a Float step" do
+ -> { ("A"..."G").step(2.0) { } }.should raise_error(TypeError)
+ end
+ end
+ end
+
+ describe "with an endless range" do
+ describe "and Integer values" do
+ it "yield Integer values incremented by 1 when not passed a step" do
+ eval("(-2..)").step { |x| break if x > 2; ScratchPad << x }
+ ScratchPad.recorded.should eql([-2, -1, 0, 1, 2])
+
+ ScratchPad.record []
+ eval("(-2...)").step { |x| break if x > 2; ScratchPad << x }
+ ScratchPad.recorded.should eql([-2, -1, 0, 1, 2])
+ end
+
+ it "yields Integer values incremented by an Integer step" do
+ eval("(-5..)").step(2) { |x| break if x > 3; ScratchPad << x }
+ ScratchPad.recorded.should eql([-5, -3, -1, 1, 3])
+
+ ScratchPad.record []
+ eval("(-5...)").step(2) { |x| break if x > 3; ScratchPad << x }
+ ScratchPad.recorded.should eql([-5, -3, -1, 1, 3])
+ end
+
+ it "yields Float values incremented by a Float step" do
+ eval("(-2..)").step(1.5) { |x| break if x > 1.0; ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
+
+ ScratchPad.record []
+ eval("(-2..)").step(1.5) { |x| break if x > 1.0; ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
+ end
+ end
+
+ describe "and Float values" do
+ it "yields Float values incremented by 1 and less than end when not passed a step" do
+ eval("(-2.0..)").step { |x| break if x > 1.5; ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
+
+ ScratchPad.record []
+ eval("(-2.0...)").step { |x| break if x > 1.5; ScratchPad << x }
+ ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
+ end
+
+ it "yields Float values incremented by an Integer step" do
+ eval("(-5.0..)").step(2) { |x| break if x > 3.5; ScratchPad << x }
+ ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
+
+ ScratchPad.record []
+ eval("(-5.0...)").step(2) { |x| break if x > 3.5; ScratchPad << x }
+ ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
+ end
+
+ it "yields Float values incremented by a Float step" do
+ eval("(-1.0..)").step(0.5) { |x| break if x > 0.6; ScratchPad << x }
+ ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
+
+ ScratchPad.record []
+ eval("(-1.0...)").step(0.5) { |x| break if x > 0.6; ScratchPad << x }
+ ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
+ end
+
+ it "handles infinite values at the start" do
+ eval("(-Float::INFINITY..)").step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
+ ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
+
+ ScratchPad.record []
+ eval("(-Float::INFINITY...)").step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
+ ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
+ end
+ end
+
+ describe "and String values" do
+ it "yields String values incremented by #succ and less than or equal to end when not passed a step" do
+ eval("('A'..)").step { |x| break if x > "D"; ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "B", "C", "D"]
+
+ ScratchPad.record []
+ eval("('A'...)").step { |x| break if x > "D"; ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "B", "C", "D"]
+ end
+
+ it "yields String values incremented by #succ called Integer step times" do
+ eval("('A'..)").step(2) { |x| break if x > "F"; ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "C", "E"]
+
+ ScratchPad.record []
+ eval("('A'...)").step(2) { |x| break if x > "F"; ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "C", "E"]
+ end
+
+ it "raises a TypeError when passed a Float step" do
+ -> { eval("('A'..)").step(2.0) { } }.should raise_error(TypeError)
+ -> { eval("('A'...)").step(2.0) { } }.should raise_error(TypeError)
+ end
+ end
+ end
+
+ describe "when no block is given" do
+ ruby_version_is "3.0" do
+ it "raises an ArgumentError if step is 0" do
+ -> { (-1..1).step(0) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ ruby_version_is ""..."3.0" do
+ it "raises a TypeError if step does not respond to #to_int" do
+ obj = mock("Range#step non-integer")
+ enum = (1..2).step(obj)
+ -> { enum.size }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ obj = mock("Range#step non-integer")
+ obj.should_receive(:to_int).and_return("1")
+ enum = (1..2).step(obj)
+
+ -> { enum.size }.should raise_error(TypeError)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "raises a TypeError if step does not respond to #to_int" do
+ obj = mock("Range#step non-integer")
+ -> { (1..2).step(obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ obj = mock("Range#step non-integer")
+ obj.should_receive(:to_int).and_return("1")
+ -> { (1..2).step(obj) }.should raise_error(TypeError)
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "returns Float::INFINITY for zero step" do
+ (-1..1).step(0).size.should == Float::INFINITY
+ (-1..1).step(0.0).size.should == Float::INFINITY
+ end
+ end
+
+ it "returns the ceil of range size divided by the number of steps" do
+ (1..10).step(4).size.should == 3
+ (1..10).step(3).size.should == 4
+ (1..10).step(2).size.should == 5
+ (1..10).step(1).size.should == 10
+ (-5..5).step(2).size.should == 6
+ (1...10).step(4).size.should == 3
+ (1...10).step(3).size.should == 3
+ (1...10).step(2).size.should == 5
+ (1...10).step(1).size.should == 9
+ (-5...5).step(2).size.should == 5
+ end
+
+ it "returns the ceil of range size divided by the number of steps even if step is negative" do
+ (-1..1).step(-1).size.should == 0
+ (1..-1).step(-1).size.should == 3
+ end
+
+ it "returns the correct number of steps when one of the arguments is a float" do
+ (-1..1.0).step(0.5).size.should == 5
+ (-1.0...1.0).step(0.5).size.should == 4
+ end
+
+ it "returns the range size when there's no step_size" do
+ (-2..2).step.size.should == 5
+ (-2.0..2.0).step.size.should == 5
+ (-2..2.0).step.size.should == 5
+ (-2.0..2).step.size.should == 5
+ (1.0..6.4).step(1.8).size.should == 4
+ (1.0..12.7).step(1.3).size.should == 10
+ (-2...2).step.size.should == 4
+ (-2.0...2.0).step.size.should == 4
+ (-2...2.0).step.size.should == 4
+ (-2.0...2).step.size.should == 4
+ (1.0...6.4).step(1.8).size.should == 3
+ end
+
+ it "returns nil with begin and end are String" do
+ ("A".."E").step(2).size.should == nil
+ ("A"..."E").step(2).size.should == nil
+ ("A".."E").step.size.should == nil
+ ("A"..."E").step.size.should == nil
+ end
+
+ it "return nil and not raises a TypeError if the first element does not respond to #succ" do
+ obj = mock("Range#step non-comparable")
+ obj.should_receive(:<=>).with(obj).and_return(1)
+ enum = (obj..obj).step
+ -> { enum.size }.should_not raise_error
+ enum.size.should == nil
+ end
+ end
+
+ describe "type" do
+ context "when both begin and end are numerics" do
+ it "returns an instance of Enumerator::ArithmeticSequence" do
+ (1..10).step.class.should == Enumerator::ArithmeticSequence
+ end
+ end
+
+ context "when begin is not defined and end is numeric" do
+ it "returns an instance of Enumerator::ArithmeticSequence" do
+ (..10).step.class.should == Enumerator::ArithmeticSequence
+ end
+ end
+
+ context "when range is endless" do
+ it "returns an instance of Enumerator::ArithmeticSequence when begin is numeric" do
+ (1..).step.class.should == Enumerator::ArithmeticSequence
+ end
+
+ it "returns an instance of Enumerator when begin is not numeric" do
+ ("a"..).step.class.should == Enumerator
+ end
+ end
+
+ context "when range is beginless and endless" do
+ it "returns an instance of Enumerator" do
+ Range.new(nil, nil).step.class.should == Enumerator
+ end
+ end
+
+ context "when begin and end are not numerics" do
+ it "returns an instance of Enumerator" do
+ ("a".."z").step.class.should == Enumerator
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/range/to_a_spec.rb b/spec/ruby/core/range/to_a_spec.rb
new file mode 100644
index 0000000000..b1d3de32db
--- /dev/null
+++ b/spec/ruby/core/range/to_a_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "Range#to_a" do
+ it "converts self to an array" do
+ (-5..5).to_a.should == [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]
+ ('A'..'D').to_a.should == ['A','B','C','D']
+ ('A'...'D').to_a.should == ['A','B','C']
+ (0xfffd...0xffff).to_a.should == [0xfffd,0xfffe]
+ -> { (0.5..2.4).to_a }.should raise_error(TypeError)
+ end
+
+ it "returns empty array for descending-ordered" do
+ (5..-5).to_a.should == []
+ ('D'..'A').to_a.should == []
+ ('D'...'A').to_a.should == []
+ (0xffff...0xfffd).to_a.should == []
+ end
+
+ it "works with Ranges of 64-bit integers" do
+ large = 1 << 40
+ (large..large+1).to_a.should == [1099511627776, 1099511627777]
+ end
+
+ it "works with Ranges of Symbols" do
+ (:A..:z).to_a.size.should == 58
+ end
+
+ it "works for non-ASCII ranges" do
+ ('Σ'..'Ω').to_a.should == ["Σ", "Τ", "Υ", "Φ", "Χ", "Ψ", "Ω"]
+ end
+
+ it "throws an exception for endless ranges" do
+ -> { eval("(1..)").to_a }.should raise_error(RangeError)
+ end
+
+ it "throws an exception for beginless ranges" do
+ -> { (..1).to_a }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/range/to_s_spec.rb b/spec/ruby/core/range/to_s_spec.rb
new file mode 100644
index 0000000000..460c330912
--- /dev/null
+++ b/spec/ruby/core/range/to_s_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "Range#to_s" do
+ it "provides a printable form of self" do
+ (0..21).to_s.should == "0..21"
+ (-8..0).to_s.should == "-8..0"
+ (-411..959).to_s.should == "-411..959"
+ ('A'..'Z').to_s.should == 'A..Z'
+ ('A'...'Z').to_s.should == 'A...Z'
+ (0xfff..0xfffff).to_s.should == "4095..1048575"
+ (0.5..2.4).to_s.should == "0.5..2.4"
+ end
+
+ it "can show endless ranges" do
+ eval("(1..)").to_s.should == "1.."
+ eval("(1.0...)").to_s.should == "1.0..."
+ end
+
+ it "can show beginless ranges" do
+ (..1).to_s.should == "..1"
+ (...1.0).to_s.should == "...1.0"
+ end
+end
diff --git a/spec/ruby/core/rational/abs_spec.rb b/spec/ruby/core/rational/abs_spec.rb
new file mode 100644
index 0000000000..aed7713058
--- /dev/null
+++ b/spec/ruby/core/rational/abs_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/abs'
+
+describe "Rational#abs" do
+ it_behaves_like :rational_abs, :abs
+end
diff --git a/spec/ruby/core/rational/ceil_spec.rb b/spec/ruby/core/rational/ceil_spec.rb
new file mode 100644
index 0000000000..5b0ca4a9d6
--- /dev/null
+++ b/spec/ruby/core/rational/ceil_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/ceil'
+
+describe "Rational#ceil" do
+ it_behaves_like :rational_ceil, :ceil
+end
diff --git a/spec/ruby/core/rational/coerce_spec.rb b/spec/ruby/core/rational/coerce_spec.rb
new file mode 100644
index 0000000000..3f78f0bcd6
--- /dev/null
+++ b/spec/ruby/core/rational/coerce_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/coerce'
+
+describe "Rational#coerce" do
+ it_behaves_like :rational_coerce, :coerce
+end
diff --git a/spec/ruby/core/rational/comparison_spec.rb b/spec/ruby/core/rational/comparison_spec.rb
new file mode 100644
index 0000000000..9d8e7fd7ee
--- /dev/null
+++ b/spec/ruby/core/rational/comparison_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../shared/rational/comparison'
+
+describe "Rational#<=> when passed a Rational object" do
+ it_behaves_like :rational_cmp_rat, :<=>
+end
+
+describe "Rational#<=> when passed an Integer object" do
+ it_behaves_like :rational_cmp_int, :<=>
+end
+
+describe "Rational#<=> when passed a Float object" do
+ it_behaves_like :rational_cmp_float, :<=>
+end
+
+describe "Rational#<=> when passed an Object that responds to #coerce" do
+ it_behaves_like :rational_cmp_coerce, :<=>
+ it_behaves_like :rational_cmp_coerce_exception, :<=>
+end
+
+describe "Rational#<=> when passed a non-Numeric Object that doesn't respond to #coerce" do
+ it_behaves_like :rational_cmp_other, :<=>
+end
diff --git a/spec/ruby/core/rational/denominator_spec.rb b/spec/ruby/core/rational/denominator_spec.rb
new file mode 100644
index 0000000000..6214b40587
--- /dev/null
+++ b/spec/ruby/core/rational/denominator_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/denominator'
+
+describe "Rational#denominator" do
+ it_behaves_like :rational_denominator, :denominator
+end
diff --git a/spec/ruby/core/rational/div_spec.rb b/spec/ruby/core/rational/div_spec.rb
new file mode 100644
index 0000000000..1cd8606b90
--- /dev/null
+++ b/spec/ruby/core/rational/div_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../shared/rational/div'
+
+describe "Rational#div" do
+ it_behaves_like :rational_div, :div
+end
+
+describe "Rational#div passed a Rational" do
+ it_behaves_like :rational_div_rat, :div
+end
+
+describe "Rational#div passed an Integer" do
+ it_behaves_like :rational_div_int, :div
+end
+
+describe "Rational#div passed a Float" do
+ it_behaves_like :rational_div_float, :div
+end
diff --git a/spec/ruby/core/rational/divide_spec.rb b/spec/ruby/core/rational/divide_spec.rb
new file mode 100644
index 0000000000..d8e3a44dc2
--- /dev/null
+++ b/spec/ruby/core/rational/divide_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../shared/rational/divide'
+require_relative '../../shared/rational/arithmetic_exception_in_coerce'
+
+describe "Rational#/" do
+ it_behaves_like :rational_divide, :/
+ it_behaves_like :rational_arithmetic_exception_in_coerce, :/
+end
+
+describe "Rational#/ when passed an Integer" do
+ it_behaves_like :rational_divide_int, :/
+end
+
+describe "Rational#/ when passed a Rational" do
+ it_behaves_like :rational_divide_rat, :/
+end
+
+describe "Rational#/ when passed a Float" do
+ it_behaves_like :rational_divide_float, :/
+end
diff --git a/spec/ruby/core/rational/divmod_spec.rb b/spec/ruby/core/rational/divmod_spec.rb
new file mode 100644
index 0000000000..6be1f8bd73
--- /dev/null
+++ b/spec/ruby/core/rational/divmod_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../shared/rational/divmod'
+
+describe "Rational#divmod when passed a Rational" do
+ it_behaves_like :rational_divmod_rat, :divmod
+end
+
+describe "Rational#divmod when passed an Integer" do
+ it_behaves_like :rational_divmod_int, :divmod
+end
+
+describe "Rational#divmod when passed a Float" do
+ it_behaves_like :rational_divmod_float, :divmod
+end
diff --git a/spec/ruby/core/rational/equal_value_spec.rb b/spec/ruby/core/rational/equal_value_spec.rb
new file mode 100644
index 0000000000..8e7acb1354
--- /dev/null
+++ b/spec/ruby/core/rational/equal_value_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../shared/rational/equal_value'
+
+describe "Rational#==" do
+ it_behaves_like :rational_equal_value, :==
+end
+
+describe "Rational#== when passed a Rational" do
+ it_behaves_like :rational_equal_value_rat, :==
+end
+
+describe "Rational#== when passed a Float" do
+ it_behaves_like :rational_equal_value_float, :==
+end
+
+describe "Rational#== when passed an Integer" do
+ it_behaves_like :rational_equal_value_int, :==
+end
diff --git a/spec/ruby/core/rational/exponent_spec.rb b/spec/ruby/core/rational/exponent_spec.rb
new file mode 100644
index 0000000000..622cf22782
--- /dev/null
+++ b/spec/ruby/core/rational/exponent_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/exponent'
+
+describe "Rational#**" do
+ it_behaves_like :rational_exponent, :**
+end
diff --git a/spec/ruby/core/rational/fdiv_spec.rb b/spec/ruby/core/rational/fdiv_spec.rb
new file mode 100644
index 0000000000..bfb321abaa
--- /dev/null
+++ b/spec/ruby/core/rational/fdiv_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/fdiv'
+
+describe "Rational#fdiv" do
+ it_behaves_like :rational_fdiv, :fdiv
+end
diff --git a/spec/ruby/core/rational/floor_spec.rb b/spec/ruby/core/rational/floor_spec.rb
new file mode 100644
index 0000000000..752a2d8815
--- /dev/null
+++ b/spec/ruby/core/rational/floor_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/floor'
+
+describe "Rational#floor" do
+ it_behaves_like :rational_floor, :floor
+end
diff --git a/spec/ruby/core/rational/hash_spec.rb b/spec/ruby/core/rational/hash_spec.rb
new file mode 100644
index 0000000000..84cd31518a
--- /dev/null
+++ b/spec/ruby/core/rational/hash_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/hash'
+
+describe "Rational#hash" do
+ it_behaves_like :rational_hash, :hash
+end
diff --git a/spec/ruby/core/rational/inspect_spec.rb b/spec/ruby/core/rational/inspect_spec.rb
new file mode 100644
index 0000000000..ef337ef0ce
--- /dev/null
+++ b/spec/ruby/core/rational/inspect_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/inspect'
+
+describe "Rational#inspect" do
+ it_behaves_like :rational_inspect, :inspect
+end
diff --git a/spec/ruby/core/rational/integer_spec.rb b/spec/ruby/core/rational/integer_spec.rb
new file mode 100644
index 0000000000..0f9a3bdead
--- /dev/null
+++ b/spec/ruby/core/rational/integer_spec.rb
@@ -0,0 +1,12 @@
+describe "Rational#integer?" do
+ # Guard against the Mathn library
+ guard -> { !defined?(Math.rsqrt) } do
+ it "returns false for a rational with a numerator and no denominator" do
+ Rational(20).integer?.should be_false
+ end
+ end
+
+ it "returns false for a rational with a numerator and a denominator" do
+ Rational(20,3).integer?.should be_false
+ end
+end
diff --git a/spec/ruby/core/rational/magnitude_spec.rb b/spec/ruby/core/rational/magnitude_spec.rb
new file mode 100644
index 0000000000..878fc8f879
--- /dev/null
+++ b/spec/ruby/core/rational/magnitude_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/abs'
+
+describe "Rational#abs" do
+ it_behaves_like :rational_abs, :magnitude
+end
diff --git a/spec/ruby/core/rational/marshal_dump_spec.rb b/spec/ruby/core/rational/marshal_dump_spec.rb
new file mode 100644
index 0000000000..17a6107cd5
--- /dev/null
+++ b/spec/ruby/core/rational/marshal_dump_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Rational#marshal_dump" do
+ it "is a private method" do
+ Rational.should have_private_instance_method(:marshal_dump, false)
+ end
+
+ it "dumps numerator and denominator" do
+ Rational(1, 2).send(:marshal_dump).should == [1, 2]
+ end
+end
diff --git a/spec/ruby/core/rational/minus_spec.rb b/spec/ruby/core/rational/minus_spec.rb
new file mode 100644
index 0000000000..a61b62ebe6
--- /dev/null
+++ b/spec/ruby/core/rational/minus_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/rational/arithmetic_exception_in_coerce'
+
+describe "Rational#-" do
+ it_behaves_like :rational_arithmetic_exception_in_coerce, :-
+
+ it "calls #coerce on the passed argument with self" do
+ rational = Rational(3, 4)
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(rational).and_return([1, 2])
+
+ rational - obj
+ end
+
+ it "calls #- on the coerced Rational with the coerced Object" do
+ rational = Rational(3, 4)
+
+ coerced_rational = mock("Coerced Rational")
+ coerced_rational.should_receive(:-).and_return(:result)
+
+ coerced_obj = mock("Coerced Object")
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj])
+
+ (rational - obj).should == :result
+ end
+end
+
+describe "Rational#- passed a Rational" do
+ it "returns the result of subtracting other from self as a Rational" do
+ (Rational(3, 4) - Rational(0, 1)).should eql(Rational(3, 4))
+ (Rational(3, 4) - Rational(1, 4)).should eql(Rational(1, 2))
+
+ (Rational(3, 4) - Rational(2, 1)).should eql(Rational(-5, 4))
+ end
+end
+
+describe "Rational#- passed a Float" do
+ it "returns the result of subtracting other from self as a Float" do
+ (Rational(3, 4) - 0.2).should eql(0.55)
+ (Rational(3, 4) - 2.5).should eql(-1.75)
+ end
+end
+
+describe "Rational#- passed an Integer" do
+ it "returns the result of subtracting other from self as a Rational" do
+ (Rational(3, 4) - 1).should eql(Rational(-1, 4))
+ (Rational(3, 4) - 2).should eql(Rational(-5, 4))
+ end
+end
diff --git a/spec/ruby/core/rational/modulo_spec.rb b/spec/ruby/core/rational/modulo_spec.rb
new file mode 100644
index 0000000000..c43f7788e3
--- /dev/null
+++ b/spec/ruby/core/rational/modulo_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/modulo'
+
+describe "Rational#%" do
+ it_behaves_like :rational_modulo, :%
+end
diff --git a/spec/ruby/core/rational/multiply_spec.rb b/spec/ruby/core/rational/multiply_spec.rb
new file mode 100644
index 0000000000..ea644074e9
--- /dev/null
+++ b/spec/ruby/core/rational/multiply_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../shared/rational/multiply'
+require_relative '../../shared/rational/arithmetic_exception_in_coerce'
+
+describe "Rational#*" do
+ it_behaves_like :rational_multiply, :*
+ it_behaves_like :rational_arithmetic_exception_in_coerce, :*
+end
+
+describe "Rational#* passed a Rational" do
+ it_behaves_like :rational_multiply_rat, :*
+end
+
+describe "Rational#* passed a Float" do
+ it_behaves_like :rational_multiply_float, :*
+end
+
+describe "Rational#* passed an Integer" do
+ it_behaves_like :rational_multiply_int, :*
+end
diff --git a/spec/ruby/core/rational/numerator_spec.rb b/spec/ruby/core/rational/numerator_spec.rb
new file mode 100644
index 0000000000..85b2ed9e86
--- /dev/null
+++ b/spec/ruby/core/rational/numerator_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/numerator'
+
+describe "Rational#numerator" do
+ it_behaves_like :rational_numerator, :numerator
+end
diff --git a/spec/ruby/core/rational/plus_spec.rb b/spec/ruby/core/rational/plus_spec.rb
new file mode 100644
index 0000000000..e7ef3a8f92
--- /dev/null
+++ b/spec/ruby/core/rational/plus_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../shared/rational/plus'
+require_relative '../../shared/rational/arithmetic_exception_in_coerce'
+
+describe "Rational#+" do
+ it_behaves_like :rational_plus, :+
+ it_behaves_like :rational_arithmetic_exception_in_coerce, :+
+end
+
+describe "Rational#+ with a Rational" do
+ it_behaves_like :rational_plus_rat, :+
+end
+describe "Rational#+ with a Float" do
+ it_behaves_like :rational_plus_float, :+
+end
+
+describe "Rational#+ with an Integer" do
+ it_behaves_like :rational_plus_int, :+
+end
diff --git a/spec/ruby/core/rational/quo_spec.rb b/spec/ruby/core/rational/quo_spec.rb
new file mode 100644
index 0000000000..119aca1955
--- /dev/null
+++ b/spec/ruby/core/rational/quo_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/divide'
+
+describe "Rational#quo" do
+ it_behaves_like :rational_divide, :quo
+end
diff --git a/spec/ruby/core/rational/rational_spec.rb b/spec/ruby/core/rational/rational_spec.rb
new file mode 100644
index 0000000000..482deab38d
--- /dev/null
+++ b/spec/ruby/core/rational/rational_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Rational" do
+ it "includes Comparable" do
+ Rational.include?(Comparable).should == true
+ end
+
+ it "does not respond to new" do
+ -> { Rational.new(1) }.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/rational/rationalize_spec.rb b/spec/ruby/core/rational/rationalize_spec.rb
new file mode 100644
index 0000000000..3db027d446
--- /dev/null
+++ b/spec/ruby/core/rational/rationalize_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe "Rational#rationalize" do
+ it "returns self with no argument" do
+ Rational(12,3).rationalize.should == Rational(12,3)
+ Rational(-45,7).rationalize.should == Rational(-45,7)
+ end
+
+ # FIXME: These specs need reviewing by somebody familiar with the
+ # algorithm used by #rationalize
+ it "simplifies self to the degree specified by a Rational argument" do
+ r = Rational(5404319552844595,18014398509481984)
+ r.rationalize(Rational(1,10)).should == Rational(1,3)
+ r.rationalize(Rational(-1,10)).should == Rational(1,3)
+
+ r = Rational(-5404319552844595,18014398509481984)
+ r.rationalize(Rational(1,10)).should == Rational(-1,3)
+ r.rationalize(Rational(-1,10)).should == Rational(-1,3)
+
+ end
+
+ it "simplifies self to the degree specified by a Float argument" do
+ r = Rational(5404319552844595,18014398509481984)
+ r.rationalize(0.05).should == Rational(1,3)
+ r.rationalize(0.001).should == Rational(3, 10)
+
+ r = Rational(-5404319552844595,18014398509481984)
+ r.rationalize(0.05).should == Rational(-1,3)
+ r.rationalize(0.001).should == Rational(-3,10)
+ end
+
+ it "raises ArgumentError when passed more than one argument" do
+ -> { Rational(1,1).rationalize(0.1, 0.1) }.should raise_error(ArgumentError)
+ -> { Rational(1,1).rationalize(0.1, 0.1, 2) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/rational/remainder_spec.rb b/spec/ruby/core/rational/remainder_spec.rb
new file mode 100644
index 0000000000..0f9442f6f5
--- /dev/null
+++ b/spec/ruby/core/rational/remainder_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/remainder'
+
+describe "Rational#remainder" do
+ it_behaves_like :rational_remainder, :remainder
+end
diff --git a/spec/ruby/core/rational/round_spec.rb b/spec/ruby/core/rational/round_spec.rb
new file mode 100644
index 0000000000..36614a552d
--- /dev/null
+++ b/spec/ruby/core/rational/round_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/rational/round'
+
+describe "Rational#round" do
+ it_behaves_like :rational_round, :round
+end
diff --git a/spec/ruby/core/rational/to_f_spec.rb b/spec/ruby/core/rational/to_f_spec.rb
new file mode 100644
index 0000000000..15bf1e88dc
--- /dev/null
+++ b/spec/ruby/core/rational/to_f_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/to_f'
+
+describe "Rational#to_f" do
+ it_behaves_like :rational_to_f, :to_f
+end
diff --git a/spec/ruby/core/rational/to_i_spec.rb b/spec/ruby/core/rational/to_i_spec.rb
new file mode 100644
index 0000000000..3deb3664e1
--- /dev/null
+++ b/spec/ruby/core/rational/to_i_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/to_i'
+
+describe "Rational#to_i" do
+ it_behaves_like :rational_to_i, :to_i
+end
diff --git a/spec/ruby/core/rational/to_r_spec.rb b/spec/ruby/core/rational/to_r_spec.rb
new file mode 100644
index 0000000000..cc704c965e
--- /dev/null
+++ b/spec/ruby/core/rational/to_r_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../shared/rational/to_r'
+
+describe "Rational#to_r" do
+ it_behaves_like :rational_to_r, :to_r
+
+ it "raises TypeError trying to convert BasicObject" do
+ obj = BasicObject.new
+ -> { Rational(obj) }.should raise_error(TypeError)
+ end
+
+ it "works when a BasicObject has to_r" do
+ obj = BasicObject.new; def obj.to_r; 1 / 2.to_r end
+ Rational(obj).should == Rational('1/2')
+ end
+
+ it "fails when a BasicObject's to_r does not return a Rational" do
+ obj = BasicObject.new; def obj.to_r; 1 end
+ -> { Rational(obj) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/rational/to_s_spec.rb b/spec/ruby/core/rational/to_s_spec.rb
new file mode 100644
index 0000000000..c5c419787c
--- /dev/null
+++ b/spec/ruby/core/rational/to_s_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/to_s'
+
+describe "Rational#to_s" do
+ it_behaves_like :rational_to_s, :to_s
+end
diff --git a/spec/ruby/core/rational/truncate_spec.rb b/spec/ruby/core/rational/truncate_spec.rb
new file mode 100644
index 0000000000..4e72339752
--- /dev/null
+++ b/spec/ruby/core/rational/truncate_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../shared/rational/truncate'
+
+describe "Rational#truncate" do
+ it_behaves_like :rational_truncate, :truncate
+end
diff --git a/spec/ruby/core/rational/zero_spec.rb b/spec/ruby/core/rational/zero_spec.rb
new file mode 100644
index 0000000000..e6dd751922
--- /dev/null
+++ b/spec/ruby/core/rational/zero_spec.rb
@@ -0,0 +1,13 @@
+describe "Rational#zero?" do
+ it "returns true if the numerator is 0" do
+ Rational(0,26).zero?.should be_true
+ end
+
+ it "returns true if the numerator is 0.0" do
+ Rational(0.0,26).zero?.should be_true
+ end
+
+ it "returns false if the numerator isn't 0" do
+ Rational(26).zero?.should be_false
+ end
+end
diff --git a/spec/ruby/core/refinement/append_features_spec.rb b/spec/ruby/core/refinement/append_features_spec.rb
new file mode 100644
index 0000000000..fb84f245bd
--- /dev/null
+++ b/spec/ruby/core/refinement/append_features_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Refinement#append_features" do
+ ruby_version_is "3.2" do
+ it "is not defined" do
+ Refinement.should_not have_private_instance_method(:append_features)
+ end
+
+ it "is not called by Module#include" do
+ c = Class.new
+ Module.new do
+ refine c do
+ called = false
+ define_method(:append_features){called = true}
+ proc{c.include(self)}.should raise_error(TypeError)
+ called.should == false
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/refinement/extend_object_spec.rb b/spec/ruby/core/refinement/extend_object_spec.rb
new file mode 100644
index 0000000000..e44e9f46d8
--- /dev/null
+++ b/spec/ruby/core/refinement/extend_object_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Refinement#extend_object" do
+ ruby_version_is "3.2" do
+ it "is not defined" do
+ Refinement.should_not have_private_instance_method(:extend_object)
+ end
+
+ it "is not called by Object#extend" do
+ c = Class.new
+ Module.new do
+ refine c do
+ called = false
+ define_method(:extend_object){called = true}
+ proc{c.extend(self)}.should raise_error(TypeError)
+ called.should == false
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/refinement/import_methods_spec.rb b/spec/ruby/core/refinement/import_methods_spec.rb
new file mode 100644
index 0000000000..1c526f5822
--- /dev/null
+++ b/spec/ruby/core/refinement/import_methods_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "Refinement#import_methods" do
+ ruby_version_is "3.1" do
+ context "when methods are defined in Ruby code" do
+ it "imports methods" do
+ str_utils = Module.new do
+ def indent(level)
+ " " * level + self
+ end
+ end
+
+ Module.new do
+ refine String do
+ import_methods str_utils
+ "foo".indent(3).should == " foo"
+ end
+ end
+ end
+ end
+
+ context "when methods are not defined in Ruby code" do
+ it "raises ArgumentError" do
+ Module.new do
+ refine String do
+ -> {
+ import_methods Kernel
+ }.should raise_error(ArgumentError)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/refinement/include_spec.rb b/spec/ruby/core/refinement/include_spec.rb
new file mode 100644
index 0000000000..25a53f0ec7
--- /dev/null
+++ b/spec/ruby/core/refinement/include_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+describe "Refinement#include" do
+ ruby_version_is "3.1"..."3.2" do
+ it "warns about deprecation" do
+ Module.new do
+ refine String do
+ -> {
+ include Module.new
+ }.should complain(/warning: Refinement#include is deprecated and will be removed in Ruby 3.2/)
+ end
+ end
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "raises a TypeError" do
+ Module.new do
+ refine String do
+ -> {
+ include Module.new
+ }.should raise_error(TypeError, "Refinement#include has been removed")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/refinement/prepend_features_spec.rb b/spec/ruby/core/refinement/prepend_features_spec.rb
new file mode 100644
index 0000000000..9fdea199a2
--- /dev/null
+++ b/spec/ruby/core/refinement/prepend_features_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Refinement#prepend_features" do
+ ruby_version_is "3.2" do
+ it "is not defined" do
+ Refinement.should_not have_private_instance_method(:prepend_features)
+ end
+
+ it "is not called by Module#prepend" do
+ c = Class.new
+ Module.new do
+ refine c do
+ called = false
+ define_method(:prepend_features){called = true}
+ proc{c.prepend(self)}.should raise_error(TypeError)
+ called.should == false
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/refinement/prepend_spec.rb b/spec/ruby/core/refinement/prepend_spec.rb
new file mode 100644
index 0000000000..27b70d392a
--- /dev/null
+++ b/spec/ruby/core/refinement/prepend_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+describe "Refinement#prepend" do
+ ruby_version_is "3.1"..."3.2" do
+ it "warns about deprecation" do
+ Module.new do
+ refine String do
+ -> {
+ prepend Module.new
+ }.should complain(/warning: Refinement#prepend is deprecated and will be removed in Ruby 3.2/)
+ end
+ end
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "raises a TypeError" do
+ Module.new do
+ refine String do
+ -> {
+ prepend Module.new
+ }.should raise_error(TypeError, "Refinement#prepend has been removed")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/regexp/case_compare_spec.rb b/spec/ruby/core/regexp/case_compare_spec.rb
new file mode 100644
index 0000000000..5ae8b56c6a
--- /dev/null
+++ b/spec/ruby/core/regexp/case_compare_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "Regexp#===" do
+ it "is true if there is a match" do
+ (/abc/ === "aabcc").should be_true
+ end
+
+ it "is false if there is no match" do
+ (/abc/ === "xyz").should be_false
+ end
+
+ it "returns true if it matches a Symbol" do
+ (/a/ === :a).should be_true
+ end
+
+ it "returns false if it does not match a Symbol" do
+ (/a/ === :b).should be_false
+ end
+
+ # mirroring https://github.com/ruby/ruby/blob/master/test/ruby/test_regexp.rb
+ it "returns false if the other value cannot be coerced to a string" do
+ (/abc/ === nil).should be_false
+ (/abc/ === /abc/).should be_false
+ end
+
+ it "uses #to_str on string-like objects" do
+ stringlike = Class.new do
+ def to_str
+ "abc"
+ end
+ end.new
+
+ (/abc/ === stringlike).should be_true
+ end
+end
diff --git a/spec/ruby/core/regexp/casefold_spec.rb b/spec/ruby/core/regexp/casefold_spec.rb
new file mode 100644
index 0000000000..d36467a989
--- /dev/null
+++ b/spec/ruby/core/regexp/casefold_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "Regexp#casefold?" do
+ it "returns the value of the case-insensitive flag" do
+ /abc/i.should.casefold?
+ /xyz/.should_not.casefold?
+ end
+end
diff --git a/spec/ruby/core/regexp/compile_spec.rb b/spec/ruby/core/regexp/compile_spec.rb
new file mode 100644
index 0000000000..c41399cfbb
--- /dev/null
+++ b/spec/ruby/core/regexp/compile_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'shared/new'
+
+describe "Regexp.compile" do
+ it_behaves_like :regexp_new, :compile
+end
+
+describe "Regexp.compile given a String" do
+ it_behaves_like :regexp_new_string, :compile
+ it_behaves_like :regexp_new_string_binary, :compile
+end
+
+describe "Regexp.compile given a Regexp" do
+ it_behaves_like :regexp_new_regexp, :compile
+end
+
+describe "Regexp.new given a non-String/Regexp" do
+ it_behaves_like :regexp_new_non_string_or_regexp, :compile
+end
diff --git a/spec/ruby/core/regexp/encoding_spec.rb b/spec/ruby/core/regexp/encoding_spec.rb
new file mode 100644
index 0000000000..dfc835b4e4
--- /dev/null
+++ b/spec/ruby/core/regexp/encoding_spec.rb
@@ -0,0 +1,62 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "Regexp#encoding" do
+ it "returns an Encoding object" do
+ /glar/.encoding.should be_an_instance_of(Encoding)
+ end
+
+ it "defaults to US-ASCII if the Regexp contains only US-ASCII character" do
+ /ASCII/.encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns US_ASCII if the 'n' modifier is supplied and only US-ASCII characters are present" do
+ /ASCII/n.encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns BINARY if the 'n' modifier is supplied and non-US-ASCII characters are present" do
+ /\xc2\xa1/n.encoding.should == Encoding::BINARY
+ end
+
+ it "defaults to UTF-8 if \\u escapes appear" do
+ /\u{9879}/.encoding.should == Encoding::UTF_8
+ end
+
+ it "defaults to UTF-8 if a literal UTF-8 character appears" do
+ /Â¥/.encoding.should == Encoding::UTF_8
+ end
+
+ it "returns UTF-8 if the 'u' modifier is supplied" do
+ /ASCII/u.encoding.should == Encoding::UTF_8
+ end
+
+ it "returns Windows-31J if the 's' modifier is supplied" do
+ /ASCII/s.encoding.should == Encoding::Windows_31J
+ end
+
+ it "returns EUC_JP if the 'e' modifier is supplied" do
+ /ASCII/e.encoding.should == Encoding::EUC_JP
+ end
+
+ it "upgrades the encoding to that of an embedded String" do
+ str = "文字化ã‘".encode('euc-jp')
+ /#{str}/.encoding.should == Encoding::EUC_JP
+ end
+
+ it "ignores the encoding and uses US-ASCII if the string has only ASCII characters" do
+ str = "abc".encode('euc-jp')
+ str.encoding.should == Encoding::EUC_JP
+ /#{str}/.encoding.should == Encoding::US_ASCII
+ end
+
+ it "ignores the default_internal encoding" do
+ old_internal = Encoding.default_internal
+ Encoding.default_internal = Encoding::EUC_JP
+ /foo/.encoding.should_not == Encoding::EUC_JP
+ Encoding.default_internal = old_internal
+ end
+
+ it "allows otherwise invalid characters if NOENCODING is specified" do
+ Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE | Regexp::NOENCODING).encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/regexp/eql_spec.rb b/spec/ruby/core/regexp/eql_spec.rb
new file mode 100644
index 0000000000..bd5ae43eb2
--- /dev/null
+++ b/spec/ruby/core/regexp/eql_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal_value'
+
+describe "Regexp#eql?" do
+ it_behaves_like :regexp_eql, :eql?
+end
diff --git a/spec/ruby/core/regexp/equal_value_spec.rb b/spec/ruby/core/regexp/equal_value_spec.rb
new file mode 100644
index 0000000000..5455a30598
--- /dev/null
+++ b/spec/ruby/core/regexp/equal_value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal_value'
+
+describe "Regexp#==" do
+ it_behaves_like :regexp_eql, :==
+end
diff --git a/spec/ruby/core/regexp/escape_spec.rb b/spec/ruby/core/regexp/escape_spec.rb
new file mode 100644
index 0000000000..6b06ab1cbc
--- /dev/null
+++ b/spec/ruby/core/regexp/escape_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/quote'
+
+describe "Regexp.escape" do
+ it_behaves_like :regexp_quote, :escape
+end
diff --git a/spec/ruby/core/regexp/fixed_encoding_spec.rb b/spec/ruby/core/regexp/fixed_encoding_spec.rb
new file mode 100644
index 0000000000..29d0a22c53
--- /dev/null
+++ b/spec/ruby/core/regexp/fixed_encoding_spec.rb
@@ -0,0 +1,36 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "Regexp#fixed_encoding?" do
+ it "returns false by default" do
+ /needle/.fixed_encoding?.should be_false
+ end
+
+ it "returns false if the 'n' modifier was supplied to the Regexp" do
+ /needle/n.fixed_encoding?.should be_false
+ end
+
+ it "returns true if the 'u' modifier was supplied to the Regexp" do
+ /needle/u.fixed_encoding?.should be_true
+ end
+
+ it "returns true if the 's' modifier was supplied to the Regexp" do
+ /needle/s.fixed_encoding?.should be_true
+ end
+
+ it "returns true if the 'e' modifier was supplied to the Regexp" do
+ /needle/e.fixed_encoding?.should be_true
+ end
+
+ it "returns true if the Regexp contains a \\u escape" do
+ /needle \u{8768}/.fixed_encoding?.should be_true
+ end
+
+ it "returns true if the Regexp contains a UTF-8 literal" do
+ /文字化ã‘/.fixed_encoding?.should be_true
+ end
+
+ it "returns true if the Regexp was created with the Regexp::FIXEDENCODING option" do
+ Regexp.new("", Regexp::FIXEDENCODING).fixed_encoding?.should be_true
+ end
+end
diff --git a/spec/ruby/core/regexp/hash_spec.rb b/spec/ruby/core/regexp/hash_spec.rb
new file mode 100644
index 0000000000..2d42e288e6
--- /dev/null
+++ b/spec/ruby/core/regexp/hash_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "Regexp#hash" do
+ it "is provided" do
+ Regexp.new('').respond_to?(:hash).should == true
+ end
+
+ it "is based on the text and options of Regexp" do
+ (/cat/.hash == /dog/.hash).should == false
+ (/dog/m.hash == /dog/m.hash).should == true
+ not_supported_on :opal do
+ (/cat/ix.hash == /cat/ixn.hash).should == true
+ (/cat/.hash == /cat/ix.hash).should == false
+ end
+ end
+
+ it "returns the same value for two Regexps differing only in the /n option" do
+ (//.hash == //n.hash).should == true
+ end
+end
diff --git a/spec/ruby/core/regexp/initialize_spec.rb b/spec/ruby/core/regexp/initialize_spec.rb
new file mode 100644
index 0000000000..a1583384af
--- /dev/null
+++ b/spec/ruby/core/regexp/initialize_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "Regexp#initialize" do
+ it "is a private method" do
+ Regexp.should have_private_instance_method(:initialize)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "raises a SecurityError on a Regexp literal" do
+ -> { //.send(:initialize, "") }.should raise_error(SecurityError)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "raises a FrozenError on a Regexp literal" do
+ -> { //.send(:initialize, "") }.should raise_error(FrozenError)
+ end
+ end
+
+ it "raises a TypeError on an initialized non-literal Regexp" do
+ -> { Regexp.new("").send(:initialize, "") }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/regexp/inspect_spec.rb b/spec/ruby/core/regexp/inspect_spec.rb
new file mode 100644
index 0000000000..f4e39234f5
--- /dev/null
+++ b/spec/ruby/core/regexp/inspect_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+
+describe "Regexp#inspect" do
+ it "returns a formatted string that would eval to the same regexp" do
+ not_supported_on :opal do
+ /ab+c/ix.inspect.should == "/ab+c/ix"
+ /a(.)+s/n.inspect.should =~ %r|/a(.)+s/n?| # Default 'n' may not appear
+ end
+ # 1.9 doesn't round-trip the encoding flags, such as 'u'. This is
+ # seemingly by design.
+ /a(.)+s/m.inspect.should == "/a(.)+s/m" # But a specified one does
+ end
+
+ it "returns options in the order 'mixn'" do
+ //nixm.inspect.should == "//mixn"
+ end
+
+ it "does not include the 'o' option" do
+ //o.inspect.should == "//"
+ end
+
+ it "does not include a character set code" do
+ //u.inspect.should == "//"
+ //s.inspect.should == "//"
+ //e.inspect.should == "//"
+ end
+
+ it "correctly escapes forward slashes /" do
+ Regexp.new("/foo/bar").inspect.should == "/\\/foo\\/bar/"
+ Regexp.new("/foo/bar[/]").inspect.should == "/\\/foo\\/bar[\\/]/"
+ end
+
+ it "doesn't over escape forward slashes" do
+ /\/foo\/bar/.inspect.should == '/\/foo\/bar/'
+ end
+
+ it "escapes 2 slashes in a row properly" do
+ Regexp.new("//").inspect.should == '/\/\//'
+ end
+
+ it "does not over escape" do
+ Regexp.new('\\\/').inspect.should == "/\\\\\\//"
+ end
+end
diff --git a/spec/ruby/core/regexp/last_match_spec.rb b/spec/ruby/core/regexp/last_match_spec.rb
new file mode 100644
index 0000000000..0bfed32051
--- /dev/null
+++ b/spec/ruby/core/regexp/last_match_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+
+describe "Regexp.last_match" do
+ it "returns MatchData instance when not passed arguments" do
+ /c(.)t/ =~ 'cat'
+
+ Regexp.last_match.should be_kind_of(MatchData)
+ end
+
+ it "returns the nth field in this MatchData when passed an Integer" do
+ /c(.)t/ =~ 'cat'
+ Regexp.last_match(1).should == 'a'
+ end
+
+ it "returns nil when there is no match" do
+ /foo/ =~ "TEST123"
+ Regexp.last_match(:test).should == nil
+ Regexp.last_match(1).should == nil
+ Regexp.last_match(Object.new).should == nil
+ Regexp.last_match("test").should == nil
+ end
+
+ describe "when given a Symbol" do
+ it "returns a named capture" do
+ /(?<test>[A-Z]+.*)/ =~ "TEST123"
+ Regexp.last_match(:test).should == "TEST123"
+ end
+
+ it "raises an IndexError when given a missing name" do
+ /(?<test>[A-Z]+.*)/ =~ "TEST123"
+ -> { Regexp.last_match(:missing) }.should raise_error(IndexError)
+ end
+ end
+
+ describe "when given a String" do
+ it "returns a named capture" do
+ /(?<test>[A-Z]+.*)/ =~ "TEST123"
+ Regexp.last_match("test").should == "TEST123"
+ end
+ end
+
+ describe "when given an Object" do
+ it "coerces argument to an index using #to_int" do
+ obj = mock("converted to int")
+ obj.should_receive(:to_int).and_return(1)
+ /(?<test>[A-Z]+.*)/ =~ "TEST123"
+ Regexp.last_match(obj).should == "TEST123"
+ end
+
+ it "raises a TypeError when unable to coerce" do
+ obj = Object.new
+ /(?<test>[A-Z]+.*)/ =~ "TEST123"
+ -> { Regexp.last_match(obj) }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/regexp/match_spec.rb b/spec/ruby/core/regexp/match_spec.rb
new file mode 100644
index 0000000000..80dbfb4c10
--- /dev/null
+++ b/spec/ruby/core/regexp/match_spec.rb
@@ -0,0 +1,146 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe :regexp_match, shared: true do
+ it "returns nil if there is no match" do
+ /xyz/.send(@method,"abxyc").should be_nil
+ end
+
+ it "returns nil if the object is nil" do
+ /\w+/.send(@method, nil).should be_nil
+ end
+end
+
+describe "Regexp#=~" do
+ it_behaves_like :regexp_match, :=~
+
+ it "returns the index of the first character of the matching region" do
+ (/(.)(.)(.)/ =~ "abc").should == 0
+ end
+
+ it "returns the index too, when argument is a Symbol" do
+ (/(.)(.)(.)/ =~ :abc).should == 0
+ end
+end
+
+describe "Regexp#match" do
+ it_behaves_like :regexp_match, :match
+
+ it "returns a MatchData object" do
+ /(.)(.)(.)/.match("abc").should be_kind_of(MatchData)
+ end
+
+ it "returns a MatchData object, when argument is a Symbol" do
+ /(.)(.)(.)/.match(:abc).should be_kind_of(MatchData)
+ end
+
+ it "raises a TypeError on an uninitialized Regexp" do
+ -> { Regexp.allocate.match('foo') }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError on an uninitialized Regexp" do
+ -> { Regexp.allocate.match('foo'.encode("UTF-16LE")) }.should raise_error(TypeError)
+ end
+
+ describe "with [string, position]" do
+ describe "when given a positive position" do
+ it "matches the input at a given position" do
+ /(.).(.)/.match("01234", 1).captures.should == ["1", "3"]
+ end
+
+ it "uses the start as a character offset" do
+ /(.).(.)/.match("零一二三四", 1).captures.should == ["一", "三"]
+ end
+
+ it "raises an ArgumentError for an invalid encoding" do
+ x96 = ([150].pack('C')).force_encoding('utf-8')
+ -> { /(.).(.)/.match("Hello, #{x96} world!", 1) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "when given a negative position" do
+ it "matches the input at a given position" do
+ /(.).(.)/.match("01234", -4).captures.should == ["1", "3"]
+ end
+
+ it "uses the start as a character offset" do
+ /(.).(.)/.match("零一二三四", -4).captures.should == ["一", "三"]
+ end
+
+ it "raises an ArgumentError for an invalid encoding" do
+ x96 = ([150].pack('C')).force_encoding('utf-8')
+ -> { /(.).(.)/.match("Hello, #{x96} world!", -1) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "when passed a block" do
+ it "yields the MatchData" do
+ /./.match("abc") {|m| ScratchPad.record m }
+ ScratchPad.recorded.should be_kind_of(MatchData)
+ end
+
+ it "returns the block result" do
+ /./.match("abc") { :result }.should == :result
+ end
+
+ it "does not yield if there is no match" do
+ ScratchPad.record []
+ /a/.match("b") {|m| ScratchPad << m }
+ ScratchPad.recorded.should == []
+ end
+ end
+ end
+
+ it "resets $~ if passed nil" do
+ # set $~
+ /./.match("a")
+ $~.should be_kind_of(MatchData)
+
+ /1/.match(nil)
+ $~.should be_nil
+ end
+
+ it "raises TypeError when the given argument cannot be coerced to String" do
+ f = 1
+ -> { /foo/.match(f)[0] }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError when the given argument is an Exception" do
+ f = Exception.new("foo")
+ -> { /foo/.match(f)[0] }.should raise_error(TypeError)
+ end
+end
+
+describe "Regexp#match?" do
+ before :each do
+ # Resetting Regexp.last_match
+ /DONTMATCH/.match ''
+ end
+
+ context "when matches the given value" do
+ it "returns true but does not set Regexp.last_match" do
+ /string/i.match?('string').should be_true
+ Regexp.last_match.should be_nil
+ end
+ end
+
+ it "returns false when does not match the given value" do
+ /STRING/.match?('string').should be_false
+ end
+
+ it "takes matching position as the 2nd argument" do
+ /str/i.match?('string', 0).should be_true
+ /str/i.match?('string', 1).should be_false
+ end
+
+ it "returns false when given nil" do
+ /./.match?(nil).should be_false
+ end
+end
+
+describe "Regexp#~" do
+ it "matches against the contents of $_" do
+ $_ = "input data"
+ (~ /at/).should == 7
+ end
+end
diff --git a/spec/ruby/core/regexp/named_captures_spec.rb b/spec/ruby/core/regexp/named_captures_spec.rb
new file mode 100644
index 0000000000..1a68d7877b
--- /dev/null
+++ b/spec/ruby/core/regexp/named_captures_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "Regexp#named_captures" do
+ it "returns a Hash" do
+ /foo/.named_captures.should be_an_instance_of(Hash)
+ end
+
+ it "returns an empty Hash when there are no capture groups" do
+ /foo/.named_captures.should == {}
+ end
+
+ it "sets the keys of the Hash to the names of the capture groups" do
+ rex = /this (?<is>is) [aA] (?<pat>pate?rn)/
+ rex.named_captures.keys.should == ['is','pat']
+ end
+
+ it "sets the values of the Hash to Arrays" do
+ rex = /this (?<is>is) [aA] (?<pat>pate?rn)/
+ rex.named_captures.values.each do |value|
+ value.should be_an_instance_of(Array)
+ end
+ end
+
+ it "sets each element of the Array to the corresponding group's index" do
+ rex = /this (?<is>is) [aA] (?<pat>pate?rn)/
+ rex.named_captures['is'].should == [1]
+ rex.named_captures['pat'].should == [2]
+ end
+
+ it "works with duplicate capture group names" do
+ rex = /this (?<is>is) [aA] (?<pat>pate?(?<is>rn))/
+ rex.named_captures['is'].should == [1,3]
+ rex.named_captures['pat'].should == [2]
+ end
+end
diff --git a/spec/ruby/core/regexp/names_spec.rb b/spec/ruby/core/regexp/names_spec.rb
new file mode 100644
index 0000000000..099768fd26
--- /dev/null
+++ b/spec/ruby/core/regexp/names_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Regexp#names" do
+ it "returns an Array" do
+ /foo/.names.should be_an_instance_of(Array)
+ end
+
+ it "returns an empty Array if there are no named captures" do
+ /needle/.names.should == []
+ end
+
+ it "returns each named capture as a String" do
+ /n(?<cap>ee)d(?<ture>le)/.names.each do |name|
+ name.should be_an_instance_of(String)
+ end
+ end
+
+ it "returns all of the named captures" do
+ /n(?<cap>ee)d(?<ture>le)/.names.should == ['cap', 'ture']
+ end
+
+ it "works with nested named captures" do
+ /n(?<cap>eed(?<ture>le))/.names.should == ['cap', 'ture']
+ end
+
+ it "returns each capture name only once" do
+ /n(?<cap>ee)d(?<cap>le)/.names.should == ['cap']
+ end
+end
diff --git a/spec/ruby/core/regexp/new_spec.rb b/spec/ruby/core/regexp/new_spec.rb
new file mode 100644
index 0000000000..65f612df55
--- /dev/null
+++ b/spec/ruby/core/regexp/new_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'shared/new'
+
+describe "Regexp.new" do
+ it_behaves_like :regexp_new, :new
+end
+
+describe "Regexp.new given a String" do
+ it_behaves_like :regexp_new_string, :new
+end
+
+describe "Regexp.new given a Regexp" do
+ it_behaves_like :regexp_new_regexp, :new
+ it_behaves_like :regexp_new_string_binary, :new
+end
+
+describe "Regexp.new given a non-String/Regexp" do
+ it_behaves_like :regexp_new_non_string_or_regexp, :new
+end
diff --git a/spec/ruby/core/regexp/options_spec.rb b/spec/ruby/core/regexp/options_spec.rb
new file mode 100644
index 0000000000..527b51a3b2
--- /dev/null
+++ b/spec/ruby/core/regexp/options_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+
+describe "Regexp#options" do
+ it "returns an Integer bitvector of regexp options for the Regexp object" do
+ /cat/.options.should be_kind_of(Integer)
+ not_supported_on :opal do
+ /cat/ix.options.should be_kind_of(Integer)
+ end
+ end
+
+ it "allows checking for presence of a certain option with bitwise &" do
+ (/cat/.options & Regexp::IGNORECASE).should == 0
+ (/cat/i.options & Regexp::IGNORECASE).should_not == 0
+ (/cat/.options & Regexp::MULTILINE).should == 0
+ (/cat/m.options & Regexp::MULTILINE).should_not == 0
+ not_supported_on :opal do
+ (/cat/.options & Regexp::EXTENDED).should == 0
+ (/cat/x.options & Regexp::EXTENDED).should_not == 0
+ (/cat/mx.options & Regexp::MULTILINE).should_not == 0
+ (/cat/mx.options & Regexp::EXTENDED).should_not == 0
+ (/cat/xi.options & Regexp::IGNORECASE).should_not == 0
+ (/cat/xi.options & Regexp::EXTENDED).should_not == 0
+ end
+ end
+
+ it "returns 0 for a Regexp literal without options" do
+ //.options.should == 0
+ /abc/.options.should == 0
+ end
+
+ it "raises a TypeError on an uninitialized Regexp" do
+ -> { Regexp.allocate.options }.should raise_error(TypeError)
+ end
+
+ it "includes Regexp::FIXEDENCODING for a Regexp literal with the 'u' option" do
+ (//u.options & Regexp::FIXEDENCODING).should_not == 0
+ end
+
+ it "includes Regexp::FIXEDENCODING for a Regexp literal with the 'e' option" do
+ (//e.options & Regexp::FIXEDENCODING).should_not == 0
+ end
+
+ it "includes Regexp::FIXEDENCODING for a Regexp literal with the 's' option" do
+ (//s.options & Regexp::FIXEDENCODING).should_not == 0
+ end
+
+ it "does not include Regexp::FIXEDENCODING for a Regexp literal with the 'n' option" do
+ (//n.options & Regexp::FIXEDENCODING).should == 0
+ end
+
+ it "includes Regexp::NOENCODING for a Regexp literal with the 'n' option" do
+ (//n.options & Regexp::NOENCODING).should_not == 0
+ end
+end
diff --git a/spec/ruby/core/regexp/quote_spec.rb b/spec/ruby/core/regexp/quote_spec.rb
new file mode 100644
index 0000000000..370ab13e30
--- /dev/null
+++ b/spec/ruby/core/regexp/quote_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/quote'
+
+describe "Regexp.quote" do
+ it_behaves_like :regexp_quote, :quote
+end
diff --git a/spec/ruby/core/regexp/shared/equal_value.rb b/spec/ruby/core/regexp/shared/equal_value.rb
new file mode 100644
index 0000000000..803988de9e
--- /dev/null
+++ b/spec/ruby/core/regexp/shared/equal_value.rb
@@ -0,0 +1,31 @@
+describe :regexp_eql, shared: true do
+ it "is true if self and other have the same pattern" do
+ /abc/.send(@method, /abc/).should == true
+ /abc/.send(@method, /abd/).should == false
+ end
+
+ not_supported_on :opal do
+ it "is true if self and other have the same character set code" do
+ /abc/.send(@method, /abc/x).should == false
+ /abc/x.send(@method, /abc/x).should == true
+ /abc/u.send(@method, /abc/n).should == false
+ /abc/u.send(@method, /abc/u).should == true
+ /abc/n.send(@method, /abc/n).should == true
+ end
+ end
+
+ it "is true if other has the same #casefold? values" do
+ /abc/.send(@method, /abc/i).should == false
+ /abc/i.send(@method, /abc/i).should == true
+ end
+
+ not_supported_on :opal do
+ it "is true if self does not specify /n option and other does" do
+ //.send(@method, //n).should == true
+ end
+
+ it "is true if self specifies /n option and other does not" do
+ //n.send(@method, //).should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/regexp/shared/new.rb b/spec/ruby/core/regexp/shared/new.rb
new file mode 100644
index 0000000000..058a51b1aa
--- /dev/null
+++ b/spec/ruby/core/regexp/shared/new.rb
@@ -0,0 +1,609 @@
+# -*- encoding: binary -*-
+
+describe :regexp_new, shared: true do
+ it "requires one argument and creates a new regular expression object" do
+ Regexp.send(@method, '').is_a?(Regexp).should == true
+ end
+
+ it "works by default for subclasses with overridden #initialize" do
+ class RegexpSpecsSubclass < Regexp
+ def initialize(*args)
+ super
+ @args = args
+ end
+
+ attr_accessor :args
+ end
+
+ class RegexpSpecsSubclassTwo < Regexp; end
+
+ RegexpSpecsSubclass.send(@method, "hi").should be_kind_of(RegexpSpecsSubclass)
+ RegexpSpecsSubclass.send(@method, "hi").args.first.should == "hi"
+
+ RegexpSpecsSubclassTwo.send(@method, "hi").should be_kind_of(RegexpSpecsSubclassTwo)
+ end
+end
+
+describe :regexp_new_non_string_or_regexp, shared: true do
+ it "calls #to_str method for non-String/Regexp argument" do
+ obj = Object.new
+ def obj.to_str() "a" end
+
+ Regexp.send(@method, obj).should == /a/
+ end
+
+ it "raises TypeError if there is no #to_str method for non-String/Regexp argument" do
+ obj = Object.new
+ -> { Regexp.send(@method, obj) }.should raise_error(TypeError, "no implicit conversion of Object into String")
+
+ -> { Regexp.send(@method, 1) }.should raise_error(TypeError, "no implicit conversion of Integer into String")
+ -> { Regexp.send(@method, 1.0) }.should raise_error(TypeError, "no implicit conversion of Float into String")
+ -> { Regexp.send(@method, :symbol) }.should raise_error(TypeError, "no implicit conversion of Symbol into String")
+ -> { Regexp.send(@method, []) }.should raise_error(TypeError, "no implicit conversion of Array into String")
+ end
+
+ it "raises TypeError if #to_str returns non-String value" do
+ obj = Object.new
+ def obj.to_str() [] end
+
+ -> { Regexp.send(@method, obj) }.should raise_error(TypeError, /can't convert Object to String/)
+ end
+end
+
+describe :regexp_new_string, shared: true do
+ it "uses the String argument as an unescaped literal to construct a Regexp object" do
+ Regexp.send(@method, "^hi{2,3}fo.o$").should == /^hi{2,3}fo.o$/
+ end
+
+ it "raises a RegexpError when passed an incorrect regexp" do
+ -> { Regexp.send(@method, "^[$", 0) }.should raise_error(RegexpError)
+ end
+
+ it "does not set Regexp options if only given one argument" do
+ r = Regexp.send(@method, 'Hi')
+ (r.options & Regexp::IGNORECASE).should == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+ end
+
+ it "does not set Regexp options if second argument is nil or false" do
+ r = Regexp.send(@method, 'Hi', nil)
+ (r.options & Regexp::IGNORECASE).should == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+
+ r = Regexp.send(@method, 'Hi', false)
+ (r.options & Regexp::IGNORECASE).should == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+ end
+
+ it "sets options from second argument if it is true" do
+ r = Regexp.send(@method, 'Hi', true)
+ (r.options & Regexp::IGNORECASE).should_not == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+ end
+
+ it "sets options from second argument if it is one of the Integer option constants" do
+ r = Regexp.send(@method, 'Hi', Regexp::IGNORECASE)
+ (r.options & Regexp::IGNORECASE).should_not == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+
+ r = Regexp.send(@method, 'Hi', Regexp::MULTILINE)
+ (r.options & Regexp::IGNORECASE).should == 0
+ (r.options & Regexp::MULTILINE).should_not == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+
+ not_supported_on :opal do
+ r = Regexp.send(@method, 'Hi', Regexp::EXTENDED)
+ (r.options & Regexp::IGNORECASE).should == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ (r.options & Regexp::EXTENDED).should_not == 1
+ end
+ end
+
+ it "accepts an Integer of two or more options ORed together as the second argument" do
+ r = Regexp.send(@method, 'Hi', Regexp::IGNORECASE | Regexp::EXTENDED)
+ (r.options & Regexp::IGNORECASE).should_not == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ (r.options & Regexp::EXTENDED).should_not == 0
+ end
+
+ it "does not try to convert the second argument to Integer with #to_int method call" do
+ ScratchPad.clear
+ obj = Object.new
+ def obj.to_int() ScratchPad.record(:called) end
+
+ Regexp.send(@method, "Hi", obj)
+
+ ScratchPad.recorded.should == nil
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "treats any non-Integer, non-nil, non-false second argument as IGNORECASE" do
+ r = Regexp.send(@method, 'Hi', Object.new)
+ (r.options & Regexp::IGNORECASE).should_not == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "warns any non-Integer, non-nil, non-false second argument" do
+ r = nil
+ -> {
+ r = Regexp.send(@method, 'Hi', Object.new)
+ }.should complain(/expected true or false as ignorecase/, {verbose: true})
+ (r.options & Regexp::IGNORECASE).should_not == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+ end
+
+ it "accepts a String of supported flags as the second argument" do
+ r = Regexp.send(@method, 'Hi', 'i')
+ (r.options & Regexp::IGNORECASE).should_not == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+
+ r = Regexp.send(@method, 'Hi', 'imx')
+ (r.options & Regexp::IGNORECASE).should_not == 0
+ (r.options & Regexp::MULTILINE).should_not == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should_not == 0
+ end
+
+ r = Regexp.send(@method, 'Hi', 'mimi')
+ (r.options & Regexp::IGNORECASE).should_not == 0
+ (r.options & Regexp::MULTILINE).should_not == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+
+ r = Regexp.send(@method, 'Hi', '')
+ (r.options & Regexp::IGNORECASE).should == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+ end
+
+ it "raises an Argument error if the second argument contains unsupported chars" do
+ -> { Regexp.send(@method, 'Hi', 'e') }.should raise_error(ArgumentError)
+ -> { Regexp.send(@method, 'Hi', 'n') }.should raise_error(ArgumentError)
+ -> { Regexp.send(@method, 'Hi', 's') }.should raise_error(ArgumentError)
+ -> { Regexp.send(@method, 'Hi', 'u') }.should raise_error(ArgumentError)
+ -> { Regexp.send(@method, 'Hi', 'j') }.should raise_error(ArgumentError)
+ -> { Regexp.send(@method, 'Hi', 'mjx') }.should raise_error(ArgumentError)
+ end
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "ignores the third argument if it is 'e' or 'euc' (case-insensitive)" do
+ -> {
+ Regexp.send(@method, 'Hi', nil, 'e').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'euc').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'E').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'EUC').encoding.should == Encoding::US_ASCII
+ }.should complain(/encoding option is ignored/)
+ end
+
+ it "ignores the third argument if it is 's' or 'sjis' (case-insensitive)" do
+ -> {
+ Regexp.send(@method, 'Hi', nil, 's').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'sjis').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'S').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'SJIS').encoding.should == Encoding::US_ASCII
+ }.should complain(/encoding option is ignored/)
+ end
+
+ it "ignores the third argument if it is 'u' or 'utf8' (case-insensitive)" do
+ -> {
+ Regexp.send(@method, 'Hi', nil, 'u').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'utf8').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'U').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'UTF8').encoding.should == Encoding::US_ASCII
+ }.should complain(/encoding option is ignored/)
+ end
+
+ it "uses US_ASCII encoding if third argument is 'n' or 'none' (case insensitive) and only ascii characters" do
+ Regexp.send(@method, 'Hi', nil, 'n').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'none').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'N').encoding.should == Encoding::US_ASCII
+ Regexp.send(@method, 'Hi', nil, 'NONE').encoding.should == Encoding::US_ASCII
+ end
+
+ it "uses ASCII_8BIT encoding if third argument is 'n' or 'none' (case insensitive) and non-ascii characters" do
+ a = "(?:[\x8E\xA1-\xFE])"
+ str = "\A(?:#{a}|x*)\z"
+
+ Regexp.send(@method, str, nil, 'N').encoding.should == Encoding::BINARY
+ Regexp.send(@method, str, nil, 'n').encoding.should == Encoding::BINARY
+ Regexp.send(@method, str, nil, 'none').encoding.should == Encoding::BINARY
+ Regexp.send(@method, str, nil, 'NONE').encoding.should == Encoding::BINARY
+ end
+ end
+
+ describe "with escaped characters" do
+ it "raises a Regexp error if there is a trailing backslash" do
+ -> { Regexp.send(@method, "\\") }.should raise_error(RegexpError)
+ end
+
+ it "does not raise a Regexp error if there is an escaped trailing backslash" do
+ -> { Regexp.send(@method, "\\\\") }.should_not raise_error(RegexpError)
+ end
+
+ it "accepts a backspace followed by a character" do
+ Regexp.send(@method, "\\N").should == /#{"\x5c"+"N"}/
+ end
+
+ it "accepts a one-digit octal value" do
+ Regexp.send(@method, "\0").should == /#{"\x00"}/
+ end
+
+ it "accepts a two-digit octal value" do
+ Regexp.send(@method, "\11").should == /#{"\x09"}/
+ end
+
+ it "accepts a one-digit hexadecimal value" do
+ Regexp.send(@method, "\x9n").should == /#{"\x09n"}/
+ end
+
+ it "accepts a two-digit hexadecimal value" do
+ Regexp.send(@method, "\x23").should == /#{"\x23"}/
+ end
+
+ it "interprets a digit following a two-digit hexadecimal value as a character" do
+ Regexp.send(@method, "\x420").should == /#{"\x420"}/
+ end
+
+ it "raises a RegexpError if \\x is not followed by any hexadecimal digits" do
+ -> { Regexp.send(@method, "\\" + "xn") }.should raise_error(RegexpError)
+ end
+
+ it "accepts an escaped string interpolation" do
+ Regexp.send(@method, "\#{abc}").should == /#{"\#{abc}"}/
+ end
+
+ it "accepts '\\n'" do
+ Regexp.send(@method, "\n").should == /#{"\x0a"}/
+ end
+
+ it "accepts '\\t'" do
+ Regexp.send(@method, "\t").should == /#{"\x09"}/
+ end
+
+ it "accepts '\\r'" do
+ Regexp.send(@method, "\r").should == /#{"\x0d"}/
+ end
+
+ it "accepts '\\f'" do
+ Regexp.send(@method, "\f").should == /#{"\x0c"}/
+ end
+
+ it "accepts '\\v'" do
+ Regexp.send(@method, "\v").should == /#{"\x0b"}/
+ end
+
+ it "accepts '\\a'" do
+ Regexp.send(@method, "\a").should == /#{"\x07"}/
+ end
+
+ it "accepts '\\e'" do
+ Regexp.send(@method, "\e").should == /#{"\x1b"}/
+ end
+
+ it "accepts '\\C-\\n'" do
+ Regexp.send(@method, "\C-\n").should == /#{"\x0a"}/
+ end
+
+ it "accepts '\\C-\\t'" do
+ Regexp.send(@method, "\C-\t").should == /#{"\x09"}/
+ end
+
+ it "accepts '\\C-\\r'" do
+ Regexp.send(@method, "\C-\r").should == /#{"\x0d"}/
+ end
+
+ it "accepts '\\C-\\f'" do
+ Regexp.send(@method, "\C-\f").should == /#{"\x0c"}/
+ end
+
+ it "accepts '\\C-\\v'" do
+ Regexp.send(@method, "\C-\v").should == /#{"\x0b"}/
+ end
+
+ it "accepts '\\C-\\a'" do
+ Regexp.send(@method, "\C-\a").should == /#{"\x07"}/
+ end
+
+ it "accepts '\\C-\\e'" do
+ Regexp.send(@method, "\C-\e").should == /#{"\x1b"}/
+ end
+
+ it "accepts multiple consecutive '\\' characters" do
+ Regexp.send(@method, "\\\\\\N").should == /#{"\\\\\\"+"N"}/
+ end
+
+ it "accepts characters and escaped octal digits" do
+ Regexp.send(@method, "abc\076").should == /#{"abc\x3e"}/
+ end
+
+ it "accepts escaped octal digits and characters" do
+ Regexp.send(@method, "\076abc").should == /#{"\x3eabc"}/
+ end
+
+ it "accepts characters and escaped hexadecimal digits" do
+ Regexp.send(@method, "abc\x42").should == /#{"abc\x42"}/
+ end
+
+ it "accepts escaped hexadecimal digits and characters" do
+ Regexp.send(@method, "\x3eabc").should == /#{"\x3eabc"}/
+ end
+
+ it "accepts escaped hexadecimal and octal digits" do
+ Regexp.send(@method, "\061\x42").should == /#{"\x31\x42"}/
+ end
+
+ it "accepts \\u{H} for a single Unicode codepoint" do
+ Regexp.send(@method, "\u{f}").should == /#{"\x0f"}/
+ end
+
+ it "accepts \\u{HH} for a single Unicode codepoint" do
+ Regexp.send(@method, "\u{7f}").should == /#{"\x7f"}/
+ end
+
+ it "accepts \\u{HHH} for a single Unicode codepoint" do
+ Regexp.send(@method, "\u{07f}").should == /#{"\x7f"}/
+ end
+
+ it "accepts \\u{HHHH} for a single Unicode codepoint" do
+ Regexp.send(@method, "\u{0000}").should == /#{"\x00"}/
+ end
+
+ it "accepts \\u{HHHHH} for a single Unicode codepoint" do
+ Regexp.send(@method, "\u{00001}").should == /#{"\x01"}/
+ end
+
+ it "accepts \\u{HHHHHH} for a single Unicode codepoint" do
+ Regexp.send(@method, "\u{000000}").should == /#{"\x00"}/
+ end
+
+ it "accepts characters followed by \\u{HHHH}" do
+ Regexp.send(@method, "abc\u{3042}").should == /#{"abc\u3042"}/
+ end
+
+ it "accepts \\u{HHHH} followed by characters" do
+ Regexp.send(@method, "\u{3042}abc").should == /#{"\u3042abc"}/
+ end
+
+ it "accepts escaped hexadecimal digits followed by \\u{HHHH}" do
+ Regexp.send(@method, "\x42\u{3042}").should == /#{"\x42\u3042"}/
+ end
+
+ it "accepts escaped octal digits followed by \\u{HHHH}" do
+ Regexp.send(@method, "\056\u{3042}").should == /#{"\x2e\u3042"}/
+ end
+
+ it "accepts a combination of escaped octal and hexadecimal digits and \\u{HHHH}" do
+ Regexp.send(@method, "\056\x42\u{3042}\x52\076").should == /#{"\x2e\x42\u3042\x52\x3e"}/
+ end
+
+ it "accepts \\uHHHH for a single Unicode codepoint" do
+ Regexp.send(@method, "\u3042").should == /#{"\u3042"}/
+ end
+
+ it "accepts characters followed by \\uHHHH" do
+ Regexp.send(@method, "abc\u3042").should == /#{"abc\u3042"}/
+ end
+
+ it "accepts \\uHHHH followed by characters" do
+ Regexp.send(@method, "\u3042abc").should == /#{"\u3042abc"}/
+ end
+
+ it "accepts escaped hexadecimal digits followed by \\uHHHH" do
+ Regexp.send(@method, "\x42\u3042").should == /#{"\x42\u3042"}/
+ end
+
+ it "accepts escaped octal digits followed by \\uHHHH" do
+ Regexp.send(@method, "\056\u3042").should == /#{"\x2e\u3042"}/
+ end
+
+ it "accepts a combination of escaped octal and hexadecimal digits and \\uHHHH" do
+ Regexp.send(@method, "\056\x42\u3042\x52\076").should == /#{"\x2e\x42\u3042\x52\x3e"}/
+ end
+
+ it "raises a RegexpError if less than four digits are given for \\uHHHH" do
+ -> { Regexp.send(@method, "\\" + "u304") }.should raise_error(RegexpError)
+ end
+
+ it "raises a RegexpError if the \\u{} escape is empty" do
+ -> { Regexp.send(@method, "\\" + "u{}") }.should raise_error(RegexpError)
+ end
+
+ it "raises a RegexpError if more than six hexadecimal digits are given" do
+ -> { Regexp.send(@method, "\\" + "u{0ffffff}") }.should raise_error(RegexpError)
+ end
+
+ it "returns a Regexp with US-ASCII encoding if only 7-bit ASCII characters are present regardless of the input String's encoding" do
+ Regexp.send(@method, "abc").encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns a Regexp with source String having US-ASCII encoding if only 7-bit ASCII characters are present regardless of the input String's encoding" do
+ Regexp.send(@method, "abc").source.encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns a Regexp with US-ASCII encoding if UTF-8 escape sequences using only 7-bit ASCII are present" do
+ Regexp.send(@method, "\u{61}").encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns a Regexp with source String having US-ASCII encoding if UTF-8 escape sequences using only 7-bit ASCII are present" do
+ Regexp.send(@method, "\u{61}").source.encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns a Regexp with UTF-8 encoding if any UTF-8 escape sequences outside 7-bit ASCII are present" do
+ Regexp.send(@method, "\u{ff}").encoding.should == Encoding::UTF_8
+ end
+
+ it "returns a Regexp with source String having UTF-8 encoding if any UTF-8 escape sequences outside 7-bit ASCII are present" do
+ Regexp.send(@method, "\u{ff}").source.encoding.should == Encoding::UTF_8
+ end
+
+ it "returns a Regexp with the input String's encoding" do
+ str = "\x82\xa0".force_encoding(Encoding::Shift_JIS)
+ Regexp.send(@method, str).encoding.should == Encoding::Shift_JIS
+ end
+
+ it "returns a Regexp with source String having the input String's encoding" do
+ str = "\x82\xa0".force_encoding(Encoding::Shift_JIS)
+ Regexp.send(@method, str).source.encoding.should == Encoding::Shift_JIS
+ end
+ end
+end
+
+describe :regexp_new_string_binary, shared: true do
+ describe "with escaped characters" do
+ it "accepts a three-digit octal value" do
+ Regexp.send(@method, "\315").should == /#{"\xcd"}/
+ end
+
+ it "interprets a digit following a three-digit octal value as a character" do
+ Regexp.send(@method, "\3762").should == /#{"\xfe2"}/
+ end
+
+ it "accepts '\\M-\\n'" do
+ Regexp.send(@method, "\M-\n").should == /#{"\x8a"}/
+ end
+
+ it "accepts '\\M-\\t'" do
+ Regexp.send(@method, "\M-\t").should == /#{"\x89"}/
+ end
+
+ it "accepts '\\M-\\r'" do
+ Regexp.send(@method, "\M-\r").should == /#{"\x8d"}/
+ end
+
+ it "accepts '\\M-\\f'" do
+ Regexp.send(@method, "\M-\f").should == /#{"\x8c"}/
+ end
+
+ it "accepts '\\M-\\v'" do
+ Regexp.send(@method, "\M-\v").should == /#{"\x8b"}/
+ end
+
+ it "accepts '\\M-\\a'" do
+ Regexp.send(@method, "\M-\a").should == /#{"\x87"}/
+ end
+
+ it "accepts '\\M-\\e'" do
+ Regexp.send(@method, "\M-\e").should == /#{"\x9b"}/
+ end
+
+ it "accepts '\\M-\\C-\\n'" do
+ Regexp.send(@method, "\M-\C-\n").should == /#{"\x8a"}/
+ end
+
+ it "accepts '\\M-\\C-\\t'" do
+ Regexp.send(@method, "\M-\C-\t").should == /#{"\x89"}/
+ end
+
+ it "accepts '\\M-\\C-\\r'" do
+ Regexp.send(@method, "\M-\C-\r").should == /#{"\x8d"}/
+ end
+
+ it "accepts '\\M-\\C-\\f'" do
+ Regexp.send(@method, "\M-\C-\f").should == /#{"\x8c"}/
+ end
+
+ it "accepts '\\M-\\C-\\v'" do
+ Regexp.send(@method, "\M-\C-\v").should == /#{"\x8b"}/
+ end
+
+ it "accepts '\\M-\\C-\\a'" do
+ Regexp.send(@method, "\M-\C-\a").should == /#{"\x87"}/
+ end
+
+ it "accepts '\\M-\\C-\\e'" do
+ Regexp.send(@method, "\M-\C-\e").should == /#{"\x9b"}/
+ end
+ end
+end
+
+describe :regexp_new_regexp, shared: true do
+ it "uses the argument as a literal to construct a Regexp object" do
+ Regexp.send(@method, /^hi{2,3}fo.o$/).should == /^hi{2,3}fo.o$/
+ end
+
+ it "preserves any options given in the Regexp literal" do
+ (Regexp.send(@method, /Hi/i).options & Regexp::IGNORECASE).should_not == 0
+ (Regexp.send(@method, /Hi/m).options & Regexp::MULTILINE).should_not == 0
+ not_supported_on :opal do
+ (Regexp.send(@method, /Hi/x).options & Regexp::EXTENDED).should_not == 0
+ end
+
+ not_supported_on :opal do
+ r = Regexp.send @method, /Hi/imx
+ (r.options & Regexp::IGNORECASE).should_not == 0
+ (r.options & Regexp::MULTILINE).should_not == 0
+ (r.options & Regexp::EXTENDED).should_not == 0
+ end
+
+ r = Regexp.send @method, /Hi/
+ (r.options & Regexp::IGNORECASE).should == 0
+ (r.options & Regexp::MULTILINE).should == 0
+ not_supported_on :opal do
+ (r.options & Regexp::EXTENDED).should == 0
+ end
+ end
+
+ it "does not honour options given as additional arguments" do
+ r = nil
+ -> {
+ r = Regexp.send @method, /hi/, Regexp::IGNORECASE
+ }.should complain(/flags ignored/)
+ (r.options & Regexp::IGNORECASE).should == 0
+ end
+
+ not_supported_on :opal do
+ it "sets the encoding to UTF-8 if the Regexp literal has the 'u' option" do
+ Regexp.send(@method, /Hi/u).encoding.should == Encoding::UTF_8
+ end
+
+ it "sets the encoding to EUC-JP if the Regexp literal has the 'e' option" do
+ Regexp.send(@method, /Hi/e).encoding.should == Encoding::EUC_JP
+ end
+
+ it "sets the encoding to Windows-31J if the Regexp literal has the 's' option" do
+ Regexp.send(@method, /Hi/s).encoding.should == Encoding::Windows_31J
+ end
+
+ it "sets the encoding to US-ASCII if the Regexp literal has the 'n' option and the source String is ASCII only" do
+ Regexp.send(@method, /Hi/n).encoding.should == Encoding::US_ASCII
+ end
+
+ ruby_version_is ''...'3.2' do
+ it "sets the encoding to source String's encoding if the Regexp literal has the 'n' option and the source String is not ASCII only" do
+ Regexp.send(@method, Regexp.new("\\xff", nil, 'n')).encoding.should == Encoding::BINARY
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/regexp/shared/quote.rb b/spec/ruby/core/regexp/shared/quote.rb
new file mode 100644
index 0000000000..9533102766
--- /dev/null
+++ b/spec/ruby/core/regexp/shared/quote.rb
@@ -0,0 +1,41 @@
+# -*- encoding: binary -*-
+
+describe :regexp_quote, shared: true do
+ it "escapes any characters with special meaning in a regular expression" do
+ Regexp.send(@method, '\*?{}.+^[]()- ').should == '\\\\\*\?\{\}\.\+\^\[\]\(\)\-\\ '
+ Regexp.send(@method, "\*?{}.+^[]()- ").should == '\\*\\?\\{\\}\\.\\+\\^\\[\\]\\(\\)\\-\\ '
+ Regexp.send(@method, '\n\r\f\t').should == '\\\\n\\\\r\\\\f\\\\t'
+ Regexp.send(@method, "\n\r\f\t").should == '\\n\\r\\f\\t'
+ end
+
+ it "works with symbols" do
+ Regexp.send(@method, :symbol).should == 'symbol'
+ end
+
+ it "works with substrings" do
+ str = ".+[]()"[1...-1]
+ Regexp.send(@method, str).should == '\+\[\]\('
+ end
+
+ it "works for broken strings" do
+ Regexp.send(@method, "a.\x85b.".force_encoding("US-ASCII")).should =="a\\.\x85b\\.".force_encoding("US-ASCII")
+ Regexp.send(@method, "a.\x80".force_encoding("UTF-8")).should == "a\\.\x80".force_encoding("UTF-8")
+ end
+
+ it "sets the encoding of the result to US-ASCII if there are only US-ASCII characters present in the input String" do
+ str = "abc".force_encoding("euc-jp")
+ Regexp.send(@method, str).encoding.should == Encoding::US_ASCII
+ end
+
+ it "sets the encoding of the result to the encoding of the String if any non-US-ASCII characters are present in an input String with valid encoding" do
+ str = "ã‚りãŒã¨ã†".force_encoding("utf-8")
+ str.valid_encoding?.should be_true
+ Regexp.send(@method, str).encoding.should == Encoding::UTF_8
+ end
+
+ it "sets the encoding of the result to BINARY if any non-US-ASCII characters are present in an input String with invalid encoding" do
+ str = "\xff".force_encoding "us-ascii"
+ str.valid_encoding?.should be_false
+ Regexp.send(@method, "\xff").encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/regexp/source_spec.rb b/spec/ruby/core/regexp/source_spec.rb
new file mode 100644
index 0000000000..5f253da9ea
--- /dev/null
+++ b/spec/ruby/core/regexp/source_spec.rb
@@ -0,0 +1,47 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "Regexp#source" do
+ it "returns the original string of the pattern" do
+ not_supported_on :opal do
+ /ab+c/ix.source.should == "ab+c"
+ end
+ /x(.)xz/.source.should == "x(.)xz"
+ end
+
+ it "keeps escape sequences as is" do
+ /\x20\+/.source.should == '\x20\+'
+ end
+
+ describe "escaping" do
+ it "keeps escaping of metacharacter" do
+ /\$/.source.should == "\\$"
+ end
+
+ it "keeps escaping of metacharacter used as a terminator" do
+ %r+\++.source.should == "\\+"
+ end
+
+ it "removes escaping of non-metacharacter used as a terminator" do
+ %r@\@@.source.should == "@"
+ end
+
+ it "keeps escaping of non-metacharacter not used as a terminator" do
+ /\@/.source.should == "\\@"
+ end
+ end
+
+ not_supported_on :opal do
+ it "has US-ASCII encoding when created from an ASCII-only \\u{} literal" do
+ re = /[\u{20}-\u{7E}]/
+ re.source.encoding.should equal(Encoding::US_ASCII)
+ end
+ end
+
+ not_supported_on :opal do
+ it "has UTF-8 encoding when created from a non-ASCII-only \\u{} literal" do
+ re = /[\u{20}-\u{7EE}]/
+ re.source.encoding.should equal(Encoding::UTF_8)
+ end
+ end
+end
diff --git a/spec/ruby/core/regexp/timeout_spec.rb b/spec/ruby/core/regexp/timeout_spec.rb
new file mode 100644
index 0000000000..6fce261814
--- /dev/null
+++ b/spec/ruby/core/regexp/timeout_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.2" do
+ describe "Regexp.timeout" do
+ after :each do
+ Regexp.timeout = nil
+ end
+
+ it "returns global timeout" do
+ Regexp.timeout = 3
+ Regexp.timeout.should == 3
+ end
+
+ it "raises Regexp::TimeoutError after global timeout elapsed" do
+ Regexp.timeout = 0.001
+ Regexp.timeout.should == 0.001
+
+ -> {
+ # A typical ReDoS case
+ /^(a*)*$/ =~ "a" * 1000000 + "x"
+ }.should raise_error(Regexp::TimeoutError, "regexp match timeout")
+ end
+
+ it "raises Regexp::TimeoutError after timeout keyword value elapsed" do
+ Regexp.timeout = 3 # This should be ignored
+ Regexp.timeout.should == 3
+
+ re = Regexp.new("^a*b?a*$", timeout: 0.001)
+
+ -> {
+ re =~ "a" * 1000000 + "x"
+ }.should raise_error(Regexp::TimeoutError, "regexp match timeout")
+ end
+ end
+end
diff --git a/spec/ruby/core/regexp/to_s_spec.rb b/spec/ruby/core/regexp/to_s_spec.rb
new file mode 100644
index 0000000000..798eaee6c2
--- /dev/null
+++ b/spec/ruby/core/regexp/to_s_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+
+describe "Regexp#to_s" do
+ not_supported_on :opal do
+ it "displays options if included" do
+ /abc/mxi.to_s.should == "(?mix:abc)"
+ end
+ end
+
+ it "shows non-included options after a - sign" do
+ /abc/i.to_s.should == "(?i-mx:abc)"
+ end
+
+ it "shows all options as excluded if none are selected" do
+ /abc/.to_s.should == "(?-mix:abc)"
+ end
+
+ it "shows the pattern after the options" do
+ not_supported_on :opal do
+ /ab+c/mix.to_s.should == "(?mix:ab+c)"
+ end
+ /xyz/.to_s.should == "(?-mix:xyz)"
+ end
+
+ not_supported_on :opal do
+ it "displays groups with options" do
+ /(?ix:foo)(?m:bar)/.to_s.should == "(?-mix:(?ix:foo)(?m:bar))"
+ /(?ix:foo)bar/m.to_s.should == "(?m-ix:(?ix:foo)bar)"
+ end
+
+ it "displays single group with same options as main regex as the main regex" do
+ /(?i:nothing outside this group)/.to_s.should == "(?i-mx:nothing outside this group)"
+ end
+ end
+
+ not_supported_on :opal do
+ it "deals properly with uncaptured groups" do
+ /whatever(?:0d)/ix.to_s.should == "(?ix-m:whatever(?:0d))"
+ end
+ end
+
+ it "deals properly with the two types of lookahead groups" do
+ /(?=5)/.to_s.should == "(?-mix:(?=5))"
+ /(?!5)/.to_s.should == "(?-mix:(?!5))"
+ end
+
+ it "returns a string in (?xxx:yyy) notation" do
+ not_supported_on :opal do
+ /ab+c/ix.to_s.should == "(?ix-m:ab+c)"
+ /jis/s.to_s.should == "(?-mix:jis)"
+ /(?i:.)/.to_s.should == "(?i-mx:.)"
+ end
+ /(?:.)/.to_s.should == "(?-mix:.)"
+ end
+
+ not_supported_on :opal do
+ it "handles abusive option groups" do
+ /(?mmmmix-miiiix:)/.to_s.should == '(?-mix:)'
+ end
+ end
+
+end
diff --git a/spec/ruby/core/regexp/try_convert_spec.rb b/spec/ruby/core/regexp/try_convert_spec.rb
new file mode 100644
index 0000000000..be567e2130
--- /dev/null
+++ b/spec/ruby/core/regexp/try_convert_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Regexp.try_convert" do
+ not_supported_on :opal do
+ it "returns the argument if given a Regexp" do
+ Regexp.try_convert(/foo/s).should == /foo/s
+ end
+ end
+
+ it "returns nil if given an argument that can't be converted to a Regexp" do
+ ['', 'glark', [], Object.new, :pat].each do |arg|
+ Regexp.try_convert(arg).should be_nil
+ end
+ end
+
+ it "tries to coerce the argument by calling #to_regexp" do
+ rex = mock('regexp')
+ rex.should_receive(:to_regexp).and_return(/(p(a)t[e]rn)/)
+ Regexp.try_convert(rex).should == /(p(a)t[e]rn)/
+ end
+end
diff --git a/spec/ruby/core/regexp/union_spec.rb b/spec/ruby/core/regexp/union_spec.rb
new file mode 100644
index 0000000000..8076836471
--- /dev/null
+++ b/spec/ruby/core/regexp/union_spec.rb
@@ -0,0 +1,159 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+
+describe "Regexp.union" do
+ it "returns /(?!)/ when passed no arguments" do
+ Regexp.union.should == /(?!)/
+ end
+
+ it "returns a regular expression that will match passed arguments" do
+ Regexp.union("penzance").should == /penzance/
+ Regexp.union("skiing", "sledding").should == /skiing|sledding/
+ not_supported_on :opal do
+ Regexp.union(/dogs/, /cats/i).should == /(?-mix:dogs)|(?i-mx:cats)/
+ end
+ end
+
+ it "quotes any string arguments" do
+ Regexp.union("n", ".").should == /n|\./
+ end
+
+ it "returns a Regexp with the encoding of an ASCII-incompatible String argument" do
+ Regexp.union("a".encode("UTF-16LE")).encoding.should == Encoding::UTF_16LE
+ end
+
+ it "returns a Regexp with the encoding of a String containing non-ASCII-compatible characters" do
+ Regexp.union("\u00A9".encode("ISO-8859-1")).encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "returns a Regexp with US-ASCII encoding if all arguments are ASCII-only" do
+ Regexp.union("a".encode("UTF-8"), "b".encode("SJIS")).encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns a Regexp with the encoding of multiple non-conflicting ASCII-incompatible String arguments" do
+ Regexp.union("a".encode("UTF-16LE"), "b".encode("UTF-16LE")).encoding.should == Encoding::UTF_16LE
+ end
+
+ it "returns a Regexp with the encoding of multiple non-conflicting Strings containing non-ASCII-compatible characters" do
+ Regexp.union("\u00A9".encode("ISO-8859-1"), "\u00B0".encode("ISO-8859-1")).encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "returns a Regexp with the encoding of a String containing non-ASCII-compatible characters and another ASCII-only String" do
+ Regexp.union("\u00A9".encode("ISO-8859-1"), "a".encode("UTF-8")).encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "returns a Regexp with UTF-8 if one part is UTF-8" do
+ Regexp.union(/probl[éeè]me/i, /help/i).encoding.should == Encoding::UTF_8
+ end
+
+ it "returns a Regexp if an array of string with special characters is passed" do
+ Regexp.union(["+","-"]).should == /\+|\-/
+ end
+
+ it "raises ArgumentError if the arguments include conflicting ASCII-incompatible Strings" do
+ -> {
+ Regexp.union("a".encode("UTF-16LE"), "b".encode("UTF-16BE"))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include conflicting ASCII-incompatible Regexps" do
+ -> {
+ Regexp.union(Regexp.new("a".encode("UTF-16LE")),
+ Regexp.new("b".encode("UTF-16BE")))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include conflicting fixed encoding Regexps" do
+ -> {
+ Regexp.union(Regexp.new("a".encode("UTF-8"), Regexp::FIXEDENCODING),
+ Regexp.new("b".encode("US-ASCII"), Regexp::FIXEDENCODING))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include a fixed encoding Regexp and a String containing non-ASCII-compatible characters in a different encoding" do
+ -> {
+ Regexp.union(Regexp.new("a".encode("UTF-8"), Regexp::FIXEDENCODING),
+ "\u00A9".encode("ISO-8859-1"))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include a String containing non-ASCII-compatible characters and a fixed encoding Regexp in a different encoding" do
+ -> {
+ Regexp.union("\u00A9".encode("ISO-8859-1"),
+ Regexp.new("a".encode("UTF-8"), Regexp::FIXEDENCODING))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include an ASCII-incompatible String and an ASCII-only String" do
+ -> {
+ Regexp.union("a".encode("UTF-16LE"), "b".encode("UTF-8"))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and an ASCII-only String" do
+ -> {
+ Regexp.union(Regexp.new("a".encode("UTF-16LE")), "b".encode("UTF-8"))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include an ASCII-incompatible String and an ASCII-only Regexp" do
+ -> {
+ Regexp.union("a".encode("UTF-16LE"), Regexp.new("b".encode("UTF-8")))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and an ASCII-only Regexp" do
+ -> {
+ Regexp.union(Regexp.new("a".encode("UTF-16LE")), Regexp.new("b".encode("UTF-8")))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include an ASCII-incompatible String and a String containing non-ASCII-compatible characters in a different encoding" do
+ -> {
+ Regexp.union("a".encode("UTF-16LE"), "\u00A9".encode("ISO-8859-1"))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and a String containing non-ASCII-compatible characters in a different encoding" do
+ -> {
+ Regexp.union(Regexp.new("a".encode("UTF-16LE")), "\u00A9".encode("ISO-8859-1"))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include an ASCII-incompatible String and a Regexp containing non-ASCII-compatible characters in a different encoding" do
+ -> {
+ Regexp.union("a".encode("UTF-16LE"), Regexp.new("\u00A9".encode("ISO-8859-1")))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and a Regexp containing non-ASCII-compatible characters in a different encoding" do
+ -> {
+ Regexp.union(Regexp.new("a".encode("UTF-16LE")), Regexp.new("\u00A9".encode("ISO-8859-1")))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "uses to_str to convert arguments (if not Regexp)" do
+ obj = mock('pattern')
+ obj.should_receive(:to_str).and_return('foo')
+ Regexp.union(obj, "bar").should == /foo|bar/
+ end
+
+ it "uses to_regexp to convert argument" do
+ obj = mock('pattern')
+ obj.should_receive(:to_regexp).and_return(/foo/)
+ Regexp.union(obj).should == /foo/
+ end
+
+ it "accepts a Symbol as argument" do
+ Regexp.union(:foo).should == /foo/
+ end
+
+ it "accepts a single array of patterns as arguments" do
+ Regexp.union(["skiing", "sledding"]).should == /skiing|sledding/
+ not_supported_on :opal do
+ Regexp.union([/dogs/, /cats/i]).should == /(?-mix:dogs)|(?i-mx:cats)/
+ end
+ ->{Regexp.union(["skiing", "sledding"], [/dogs/, /cats/i])}.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/signal/fixtures/trap_all.rb b/spec/ruby/core/signal/fixtures/trap_all.rb
new file mode 100644
index 0000000000..afa00b498d
--- /dev/null
+++ b/spec/ruby/core/signal/fixtures/trap_all.rb
@@ -0,0 +1,15 @@
+cannot_be_trapped = %w[KILL STOP] # See man 2 signal
+
+(Signal.list.keys - cannot_be_trapped).each do |signal|
+ begin
+ Signal.trap(signal, -> {})
+ rescue ArgumentError => e
+ unless /can't trap reserved signal|Signal already used by VM or OS/ =~ e.message
+ raise e
+ end
+ else
+ Signal.trap(signal, "DEFAULT")
+ end
+end
+
+puts "OK"
diff --git a/spec/ruby/core/signal/list_spec.rb b/spec/ruby/core/signal/list_spec.rb
new file mode 100644
index 0000000000..56ad6828fe
--- /dev/null
+++ b/spec/ruby/core/signal/list_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+
+describe "Signal.list" do
+ RUBY_SIGNALS = %w{
+ EXIT
+ HUP
+ INT
+ QUIT
+ ILL
+ TRAP
+ IOT
+ ABRT
+ EMT
+ FPE
+ KILL
+ BUS
+ SEGV
+ SYS
+ PIPE
+ ALRM
+ TERM
+ URG
+ STOP
+ TSTP
+ CONT
+ CHLD
+ CLD
+ TTIN
+ TTOU
+ IO
+ XCPU
+ XFSZ
+ VTALRM
+ PROF
+ WINCH
+ USR1
+ USR2
+ LOST
+ MSG
+ PWR
+ POLL
+ DANGER
+ MIGRATE
+ PRE
+ GRANT
+ RETRACT
+ SOUND
+ INFO
+ }
+
+ it "doesn't contain other signals than the known list" do
+ (Signal.list.keys - RUBY_SIGNALS).should == []
+ end
+
+ if Signal.list["CHLD"]
+ it "redefines CLD with CHLD if defined" do
+ Signal.list["CLD"].should == Signal.list["CHLD"]
+ end
+ end
+
+ it "includes the EXIT key with a value of zero" do
+ Signal.list["EXIT"].should == 0
+ end
+
+ it "includes the KILL key with a value of nine" do
+ Signal.list["KILL"].should == 9
+ end
+end
diff --git a/spec/ruby/core/signal/signame_spec.rb b/spec/ruby/core/signal/signame_spec.rb
new file mode 100644
index 0000000000..b66de9fc85
--- /dev/null
+++ b/spec/ruby/core/signal/signame_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+describe "Signal.signame" do
+ it "takes a signal name with a well known signal number" do
+ Signal.signame(0).should == "EXIT"
+ end
+
+ it "returns nil if the argument is an invalid signal number" do
+ Signal.signame(-1).should == nil
+ end
+
+ it "raises a TypeError when the passed argument can't be coerced to Integer" do
+ -> { Signal.signame("hello") }.should raise_error(TypeError)
+ end
+
+ platform_is_not :windows do
+ it "the original should take precedence over alias when looked up by number" do
+ Signal.signame(Signal.list["ABRT"]).should == "ABRT"
+ Signal.signame(Signal.list["CHLD"]).should == "CHLD"
+ end
+ end
+end
diff --git a/spec/ruby/core/signal/trap_spec.rb b/spec/ruby/core/signal/trap_spec.rb
new file mode 100644
index 0000000000..10e122e072
--- /dev/null
+++ b/spec/ruby/core/signal/trap_spec.rb
@@ -0,0 +1,293 @@
+require_relative '../../spec_helper'
+
+describe "Signal.trap" do
+ platform_is_not :windows do
+ before :each do
+ ScratchPad.clear
+ @proc = -> {}
+ @saved_trap = Signal.trap(:HUP, @proc)
+ @hup_number = Signal.list["HUP"]
+ end
+
+ after :each do
+ Signal.trap(:HUP, @saved_trap) if @saved_trap
+ end
+
+ it "returns the previous handler" do
+ Signal.trap(:HUP, @saved_trap).should equal(@proc)
+ end
+
+ it "accepts a block" do
+ done = false
+
+ Signal.trap(:HUP) do |signo|
+ signo.should == @hup_number
+ ScratchPad.record :block_trap
+ done = true
+ end
+
+ Process.kill :HUP, Process.pid
+ Thread.pass until done
+
+ ScratchPad.recorded.should == :block_trap
+ end
+
+ it "accepts a proc" do
+ done = false
+
+ handler = -> signo {
+ signo.should == @hup_number
+ ScratchPad.record :proc_trap
+ done = true
+ }
+
+ Signal.trap(:HUP, handler)
+
+ Process.kill :HUP, Process.pid
+ Thread.pass until done
+
+ ScratchPad.recorded.should == :proc_trap
+ end
+
+ it "accepts a method" do
+ done = false
+
+ handler_class = Class.new
+ hup_number = @hup_number
+
+ handler_class.define_method :handler_method do |signo|
+ signo.should == hup_number
+ ScratchPad.record :method_trap
+ done = true
+ end
+
+ handler_method = handler_class.new.method(:handler_method)
+
+ Signal.trap(:HUP, handler_method)
+
+ Process.kill :HUP, Process.pid
+ Thread.pass until done
+
+ ScratchPad.recorded.should == :method_trap
+ end
+
+ it "accepts anything you can call" do
+ done = false
+
+ callable = Object.new
+ hup_number = @hup_number
+
+ callable.singleton_class.define_method :call do |signo|
+ signo.should == hup_number
+ ScratchPad.record :callable_trap
+ done = true
+ end
+
+ Signal.trap(:HUP, callable)
+
+ Process.kill :HUP, Process.pid
+ Thread.pass until done
+
+ ScratchPad.recorded.should == :callable_trap
+ end
+
+ it "raises an exception for a non-callable at the point of use" do
+ not_callable = Object.new
+ Signal.trap(:HUP, not_callable)
+ -> {
+ Process.kill :HUP, Process.pid
+ loop { Thread.pass }
+ }.should raise_error(NoMethodError)
+ end
+
+ it "accepts a non-callable that becomes callable when used" do
+ done = false
+
+ late_callable = Object.new
+ hup_number = @hup_number
+
+ Signal.trap(:HUP, late_callable)
+
+ late_callable.singleton_class.define_method :call do |signo|
+ signo.should == hup_number
+ ScratchPad.record :late_callable_trap
+ done = true
+ end
+
+ Process.kill :HUP, Process.pid
+ Thread.pass until done
+
+ ScratchPad.recorded.should == :late_callable_trap
+ end
+
+ it "is possible to create a new Thread when the handler runs" do
+ done = false
+
+ Signal.trap(:HUP) do
+ thr = Thread.new { }
+ thr.join
+ ScratchPad.record(thr.group == Thread.main.group)
+
+ done = true
+ end
+
+ Process.kill :HUP, Process.pid
+ Thread.pass until done
+
+ ScratchPad.recorded.should be_true
+ end
+
+ it "registers an handler doing nothing with :IGNORE" do
+ Signal.trap :HUP, :IGNORE
+ Signal.trap(:HUP, @saved_trap).should == "IGNORE"
+ end
+
+ it "can register a new handler after :IGNORE" do
+ Signal.trap :HUP, :IGNORE
+
+ done = false
+ Signal.trap(:HUP) do
+ ScratchPad.record :block_trap
+ done = true
+ end
+
+ Process.kill(:HUP, Process.pid).should == 1
+ Thread.pass until done
+ ScratchPad.recorded.should == :block_trap
+ end
+
+ it "ignores the signal when passed nil" do
+ Signal.trap :HUP, nil
+ Signal.trap(:HUP, @saved_trap).should be_nil
+ end
+
+ it "accepts :DEFAULT in place of a proc" do
+ Signal.trap :HUP, :DEFAULT
+ Signal.trap(:HUP, @saved_trap).should == "DEFAULT"
+ end
+
+ it "accepts :SIG_DFL in place of a proc" do
+ Signal.trap :HUP, :SIG_DFL
+ Signal.trap(:HUP, @saved_trap).should == "DEFAULT"
+ end
+
+ it "accepts :SIG_IGN in place of a proc" do
+ Signal.trap :HUP, :SIG_IGN
+ Signal.trap(:HUP, @saved_trap).should == "IGNORE"
+ end
+
+ it "accepts :IGNORE in place of a proc" do
+ Signal.trap :HUP, :IGNORE
+ Signal.trap(:HUP, @saved_trap).should == "IGNORE"
+ end
+
+ it "accepts 'SIG_DFL' in place of a proc" do
+ Signal.trap :HUP, "SIG_DFL"
+ Signal.trap(:HUP, @saved_trap).should == "DEFAULT"
+ end
+
+ it "accepts 'DEFAULT' in place of a proc" do
+ Signal.trap :HUP, "DEFAULT"
+ Signal.trap(:HUP, @saved_trap).should == "DEFAULT"
+ end
+
+ it "accepts 'SIG_IGN' in place of a proc" do
+ Signal.trap :HUP, "SIG_IGN"
+ Signal.trap(:HUP, @saved_trap).should == "IGNORE"
+ end
+
+ it "accepts 'IGNORE' in place of a proc" do
+ Signal.trap :HUP, "IGNORE"
+ Signal.trap(:HUP, @saved_trap).should == "IGNORE"
+ end
+
+ it "accepts long names as Strings" do
+ Signal.trap "SIGHUP", @proc
+ Signal.trap("SIGHUP", @saved_trap).should equal(@proc)
+ end
+
+ it "accepts short names as Strings" do
+ Signal.trap "HUP", @proc
+ Signal.trap("HUP", @saved_trap).should equal(@proc)
+ end
+
+ it "accepts long names as Symbols" do
+ Signal.trap :SIGHUP, @proc
+ Signal.trap(:SIGHUP, @saved_trap).should equal(@proc)
+ end
+
+ it "accepts short names as Symbols" do
+ Signal.trap :HUP, @proc
+ Signal.trap(:HUP, @saved_trap).should equal(@proc)
+ end
+
+ it "raises ArgumentError when passed unknown signal" do
+ -> { Signal.trap(300) { } }.should raise_error(ArgumentError, "invalid signal number (300)")
+ -> { Signal.trap("USR10") { } }.should raise_error(ArgumentError, "unsupported signal `SIGUSR10'")
+ -> { Signal.trap("SIGUSR10") { } }.should raise_error(ArgumentError, "unsupported signal `SIGUSR10'")
+ end
+
+ it "raises ArgumentError when passed signal is not Integer, String or Symbol" do
+ -> { Signal.trap(nil) { } }.should raise_error(ArgumentError, "bad signal type NilClass")
+ -> { Signal.trap(100.0) { } }.should raise_error(ArgumentError, "bad signal type Float")
+ -> { Signal.trap(Rational(100)) { } }.should raise_error(ArgumentError, "bad signal type Rational")
+ end
+
+ # See man 2 signal
+ %w[KILL STOP].each do |signal|
+ it "raises ArgumentError or Errno::EINVAL for SIG#{signal}" do
+ -> {
+ Signal.trap(signal, -> {})
+ }.should raise_error(StandardError) { |e|
+ [ArgumentError, Errno::EINVAL].should include(e.class)
+ e.message.should =~ /Invalid argument|Signal already used by VM or OS/
+ }
+ end
+ end
+
+ it "allows to register a handler for all known signals, except reserved signals for which it raises ArgumentError" do
+ out = ruby_exe(fixture(__FILE__, "trap_all.rb"), args: "2>&1")
+ out.should == "OK\n"
+ $?.exitstatus.should == 0
+ end
+
+ it "returns 'DEFAULT' for the initial SIGINT handler" do
+ ruby_exe("print Signal.trap(:INT) { abort }").should == 'DEFAULT'
+ end
+
+ it "returns SYSTEM_DEFAULT if passed DEFAULT and no handler was ever set" do
+ Signal.trap("PROF", "DEFAULT").should == "SYSTEM_DEFAULT"
+ end
+
+ it "accepts 'SYSTEM_DEFAULT' and uses the OS handler for SIGPIPE" do
+ code = <<-RUBY
+ p Signal.trap('PIPE', 'SYSTEM_DEFAULT')
+ r, w = IO.pipe
+ r.close
+ loop { w.write("a"*1024) }
+ RUBY
+ out = ruby_exe(code, exit_status: :SIGPIPE)
+ status = $?
+ out.should == "nil\n"
+ status.should.signaled?
+ end
+ end
+
+ describe "the special EXIT signal code" do
+ it "accepts the EXIT code" do
+ code = "Signal.trap(:EXIT, proc { print 1 })"
+ ruby_exe(code).should == "1"
+ end
+
+ it "runs the proc before at_exit handlers" do
+ code = "at_exit {print 1}; Signal.trap(:EXIT, proc {print 2}); at_exit {print 3}"
+ ruby_exe(code).should == "231"
+ end
+
+ it "can unset the handler" do
+ code = "Signal.trap(:EXIT, proc { print 1 }); Signal.trap(:EXIT, 'DEFAULT')"
+ ruby_exe(code).should == ""
+ end
+ end
+
+end
diff --git a/spec/ruby/core/sizedqueue/append_spec.rb b/spec/ruby/core/sizedqueue/append_spec.rb
new file mode 100644
index 0000000000..ca79068930
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/append_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/enque'
+require_relative '../../shared/sizedqueue/enque'
+
+describe "SizedQueue#<<" do
+ it_behaves_like :queue_enq, :<<, -> { SizedQueue.new(10) }
+end
+
+describe "SizedQueue#<<" do
+ it_behaves_like :sizedqueue_enq, :<<, -> n { SizedQueue.new(n) }
+end
diff --git a/spec/ruby/core/sizedqueue/clear_spec.rb b/spec/ruby/core/sizedqueue/clear_spec.rb
new file mode 100644
index 0000000000..abae01c6c0
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/clear_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/clear'
+
+describe "SizedQueue#clear" do
+ it_behaves_like :queue_clear, :clear, -> { SizedQueue.new(10) }
+end
diff --git a/spec/ruby/core/sizedqueue/close_spec.rb b/spec/ruby/core/sizedqueue/close_spec.rb
new file mode 100644
index 0000000000..0e0af851cb
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/close_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/close'
+
+describe "SizedQueue#close" do
+ it_behaves_like :queue_close, :close, -> { SizedQueue.new(10) }
+end
diff --git a/spec/ruby/core/sizedqueue/closed_spec.rb b/spec/ruby/core/sizedqueue/closed_spec.rb
new file mode 100644
index 0000000000..4b90da1faa
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/closed_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/closed'
+
+describe "SizedQueue#closed?" do
+ it_behaves_like :queue_closed?, :closed?, -> { SizedQueue.new(10) }
+end
diff --git a/spec/ruby/core/sizedqueue/deq_spec.rb b/spec/ruby/core/sizedqueue/deq_spec.rb
new file mode 100644
index 0000000000..5e1bd9f746
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/deq_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/deque'
+
+describe "SizedQueue#deq" do
+ it_behaves_like :queue_deq, :deq, -> { SizedQueue.new(10) }
+end
diff --git a/spec/ruby/core/sizedqueue/empty_spec.rb b/spec/ruby/core/sizedqueue/empty_spec.rb
new file mode 100644
index 0000000000..9b0d4ff013
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/empty_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/empty'
+
+describe "SizedQueue#empty?" do
+ it_behaves_like :queue_empty?, :empty?, -> { SizedQueue.new(10) }
+end
diff --git a/spec/ruby/core/sizedqueue/enq_spec.rb b/spec/ruby/core/sizedqueue/enq_spec.rb
new file mode 100644
index 0000000000..3821afac95
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/enq_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/enque'
+require_relative '../../shared/sizedqueue/enque'
+
+describe "SizedQueue#enq" do
+ it_behaves_like :queue_enq, :enq, -> { SizedQueue.new(10) }
+end
+
+describe "SizedQueue#enq" do
+ it_behaves_like :sizedqueue_enq, :enq, -> n { SizedQueue.new(n) }
+end
diff --git a/spec/ruby/core/sizedqueue/length_spec.rb b/spec/ruby/core/sizedqueue/length_spec.rb
new file mode 100644
index 0000000000..b93e7f8997
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/length_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/length'
+
+describe "SizedQueue#length" do
+ it_behaves_like :queue_length, :length, -> { SizedQueue.new(10) }
+end
diff --git a/spec/ruby/core/sizedqueue/max_spec.rb b/spec/ruby/core/sizedqueue/max_spec.rb
new file mode 100644
index 0000000000..cbabb5dc10
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/max_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/sizedqueue/max'
+
+describe "SizedQueue#max" do
+ it_behaves_like :sizedqueue_max, :max, -> n { SizedQueue.new(n) }
+end
+
+describe "SizedQueue#max=" do
+ it_behaves_like :sizedqueue_max=, :max=, -> n { SizedQueue.new(n) }
+end
diff --git a/spec/ruby/core/sizedqueue/new_spec.rb b/spec/ruby/core/sizedqueue/new_spec.rb
new file mode 100644
index 0000000000..d5704732c3
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/new_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/sizedqueue/new'
+
+describe "SizedQueue.new" do
+ it_behaves_like :sizedqueue_new, :new, -> *n { SizedQueue.new(*n) }
+end
diff --git a/spec/ruby/core/sizedqueue/num_waiting_spec.rb b/spec/ruby/core/sizedqueue/num_waiting_spec.rb
new file mode 100644
index 0000000000..9aef25e33d
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/num_waiting_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/sizedqueue/num_waiting'
+
+describe "SizedQueue#num_waiting" do
+ it_behaves_like :sizedqueue_num_waiting, :new, -> n { SizedQueue.new(n) }
+end
diff --git a/spec/ruby/core/sizedqueue/pop_spec.rb b/spec/ruby/core/sizedqueue/pop_spec.rb
new file mode 100644
index 0000000000..a0cf6f509c
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/pop_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/deque'
+
+describe "SizedQueue#pop" do
+ it_behaves_like :queue_deq, :pop, -> { SizedQueue.new(10) }
+end
diff --git a/spec/ruby/core/sizedqueue/push_spec.rb b/spec/ruby/core/sizedqueue/push_spec.rb
new file mode 100644
index 0000000000..bba9be9e3f
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/push_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/enque'
+require_relative '../../shared/sizedqueue/enque'
+
+describe "SizedQueue#push" do
+ it_behaves_like :queue_enq, :push, -> { SizedQueue.new(10) }
+end
+
+describe "SizedQueue#push" do
+ it_behaves_like :sizedqueue_enq, :push, -> n { SizedQueue.new(n) }
+end
diff --git a/spec/ruby/core/sizedqueue/shift_spec.rb b/spec/ruby/core/sizedqueue/shift_spec.rb
new file mode 100644
index 0000000000..5138e68258
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/shift_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/deque'
+
+describe "SizedQueue#shift" do
+ it_behaves_like :queue_deq, :shift, -> { SizedQueue.new(10) }
+end
diff --git a/spec/ruby/core/sizedqueue/size_spec.rb b/spec/ruby/core/sizedqueue/size_spec.rb
new file mode 100644
index 0000000000..dfa76faabe
--- /dev/null
+++ b/spec/ruby/core/sizedqueue/size_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/queue/length'
+
+describe "SizedQueue#size" do
+ it_behaves_like :queue_length, :size, -> { SizedQueue.new(10) }
+end
diff --git a/spec/ruby/core/string/allocate_spec.rb b/spec/ruby/core/string/allocate_spec.rb
new file mode 100644
index 0000000000..30d5f60594
--- /dev/null
+++ b/spec/ruby/core/string/allocate_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "String.allocate" do
+ it "returns an instance of String" do
+ str = String.allocate
+ str.should be_an_instance_of(String)
+ end
+
+ it "returns a fully-formed String" do
+ str = String.allocate
+ str.size.should == 0
+ str << "more"
+ str.should == "more"
+ end
+
+ it "returns a binary String" do
+ String.allocate.encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/string/append_spec.rb b/spec/ruby/core/string/append_spec.rb
new file mode 100644
index 0000000000..e001257621
--- /dev/null
+++ b/spec/ruby/core/string/append_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/concat'
+
+describe "String#<<" do
+ it_behaves_like :string_concat, :<<
+ it_behaves_like :string_concat_encoding, :<<
+
+ it "raises an ArgumentError when given the incorrect number of arguments" do
+ -> { "hello".send(:<<) }.should raise_error(ArgumentError)
+ -> { "hello".send(:<<, "one", "two") }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/ascii_only_spec.rb b/spec/ruby/core/string/ascii_only_spec.rb
new file mode 100644
index 0000000000..c7e02fd874
--- /dev/null
+++ b/spec/ruby/core/string/ascii_only_spec.rb
@@ -0,0 +1,83 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#ascii_only?" do
+ describe "with ASCII only characters" do
+ it "returns true if the encoding is UTF-8" do
+ [ ["hello", true],
+ ["hello".encode('UTF-8'), true],
+ ["hello".force_encoding('UTF-8'), true],
+ ].should be_computed_by(:ascii_only?)
+ end
+
+ it "returns true if the encoding is US-ASCII" do
+ "hello".force_encoding(Encoding::US_ASCII).ascii_only?.should be_true
+ "hello".encode(Encoding::US_ASCII).ascii_only?.should be_true
+ end
+
+ it "returns true for all single-character UTF-8 Strings" do
+ 0.upto(127) do |n|
+ n.chr.ascii_only?.should be_true
+ end
+ end
+ end
+
+ describe "with non-ASCII only characters" do
+ it "returns false if the encoding is BINARY" do
+ chr = 128.chr
+ chr.encoding.should == Encoding::BINARY
+ chr.ascii_only?.should be_false
+ end
+
+ it "returns false if the String contains any non-ASCII characters" do
+ [ ["\u{6666}", false],
+ ["hello, \u{6666}", false],
+ ["\u{6666}".encode('UTF-8'), false],
+ ["\u{6666}".force_encoding('UTF-8'), false],
+ ].should be_computed_by(:ascii_only?)
+ end
+
+ it "returns false if the encoding is US-ASCII" do
+ [ ["\u{6666}".force_encoding(Encoding::US_ASCII), false],
+ ["hello, \u{6666}".force_encoding(Encoding::US_ASCII), false],
+ ].should be_computed_by(:ascii_only?)
+ end
+ end
+
+ it "returns true for the empty String with an ASCII-compatible encoding" do
+ "".ascii_only?.should be_true
+ "".encode('UTF-8').ascii_only?.should be_true
+ end
+
+ it "returns false for the empty String with a non-ASCII-compatible encoding" do
+ "".force_encoding('UTF-16LE').ascii_only?.should be_false
+ "".encode('UTF-16BE').ascii_only?.should be_false
+ end
+
+ it "returns false for a non-empty String with non-ASCII-compatible encoding" do
+ "\x78\x00".force_encoding("UTF-16LE").ascii_only?.should be_false
+ end
+
+ it "returns false when interpolating non ascii strings" do
+ base = "EU currency is"
+ base.force_encoding(Encoding::US_ASCII)
+ euro = "\u20AC"
+ interp = "#{base} #{euro}"
+ euro.ascii_only?.should be_false
+ base.ascii_only?.should be_true
+ interp.ascii_only?.should be_false
+ end
+
+ it "returns false after appending non ASCII characters to an empty String" do
+ ("" << "λ").ascii_only?.should be_false
+ end
+
+ it "returns false when concatenating an ASCII and non-ASCII String" do
+ "".concat("λ").ascii_only?.should be_false
+ end
+
+ it "returns false when replacing an ASCII String with a non-ASCII String" do
+ "".replace("λ").ascii_only?.should be_false
+ end
+end
diff --git a/spec/ruby/core/string/b_spec.rb b/spec/ruby/core/string/b_spec.rb
new file mode 100644
index 0000000000..37c7994700
--- /dev/null
+++ b/spec/ruby/core/string/b_spec.rb
@@ -0,0 +1,15 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "String#b" do
+ it "returns a binary encoded string" do
+ "Hello".b.should == "Hello".force_encoding(Encoding::BINARY)
+ "ã“ã‚“ã¡ã«ã¯".b.should == "ã“ã‚“ã¡ã«ã¯".force_encoding(Encoding::BINARY)
+ end
+
+ it "returns new string without modifying self" do
+ str = "ã“ã‚“ã¡ã«ã¯"
+ str.b.should_not equal(str)
+ str.should == "ã“ã‚“ã¡ã«ã¯"
+ end
+end
diff --git a/spec/ruby/core/string/byteindex_spec.rb b/spec/ruby/core/string/byteindex_spec.rb
new file mode 100644
index 0000000000..af70c729e8
--- /dev/null
+++ b/spec/ruby/core/string/byteindex_spec.rb
@@ -0,0 +1,16 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#byteindex with Regexp" do
+ ruby_version_is "3.2" do
+ it "always clear $~" do
+ "a".byteindex(/a/)
+ $~.should_not == nil
+
+ string = "blablabla"
+ string.byteindex(/bla/, string.bytesize + 1)
+ $~.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/string/bytes_spec.rb b/spec/ruby/core/string/bytes_spec.rb
new file mode 100644
index 0000000000..859b346550
--- /dev/null
+++ b/spec/ruby/core/string/bytes_spec.rb
@@ -0,0 +1,55 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "String#bytes" do
+ before :each do
+ @utf8 = "æ±äº¬"
+ @ascii = 'Tokyo'
+ @utf8_ascii = @utf8 + @ascii
+ end
+
+ it "returns an Array when no block is given" do
+ @utf8.bytes.should be_an_instance_of(Array)
+ end
+
+ it "yields each byte to a block if one is given, returning self" do
+ bytes = []
+ @utf8.bytes {|b| bytes << b}.should == @utf8
+ bytes.should == @utf8.bytes.to_a
+ end
+
+ it "returns #bytesize bytes" do
+ @utf8_ascii.bytes.to_a.size.should == @utf8_ascii.bytesize
+ end
+
+ it "returns bytes as Integers" do
+ @ascii.bytes.to_a.each {|b| b.should be_an_instance_of(Integer)}
+ @utf8_ascii.bytes { |b| b.should be_an_instance_of(Integer) }
+ end
+
+ it "agrees with #unpack('C*')" do
+ @utf8_ascii.bytes.to_a.should == @utf8_ascii.unpack("C*")
+ end
+
+ it "yields/returns no bytes for the empty string" do
+ ''.bytes.to_a.should == []
+ end
+end
+
+describe "String#bytes" do
+ before :each do
+ @utf8 = "æ±äº¬"
+ @ascii = 'Tokyo'
+ @utf8_ascii = @utf8 + @ascii
+ end
+
+ it "agrees with #getbyte" do
+ @utf8_ascii.bytes.to_a.each_with_index do |byte,index|
+ byte.should == @utf8_ascii.getbyte(index)
+ end
+ end
+
+ it "is unaffected by #force_encoding" do
+ @utf8.force_encoding('ASCII').bytes.to_a.should == @utf8.bytes.to_a
+ end
+end
diff --git a/spec/ruby/core/string/bytesize_spec.rb b/spec/ruby/core/string/bytesize_spec.rb
new file mode 100644
index 0000000000..a31f3ae671
--- /dev/null
+++ b/spec/ruby/core/string/bytesize_spec.rb
@@ -0,0 +1,33 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#bytesize" do
+ it "returns the length of self in bytes" do
+ "hello".bytesize.should == 5
+ " ".bytesize.should == 1
+ end
+
+ it "works with strings containing single UTF-8 characters" do
+ "\u{6666}".bytesize.should == 3
+ end
+
+ it "works with pseudo-ASCII strings containing single UTF-8 characters" do
+ "\u{6666}".force_encoding('ASCII').bytesize.should == 3
+ end
+
+ it "works with strings containing UTF-8 characters" do
+ "c \u{6666}".force_encoding('UTF-8').bytesize.should == 5
+ "c \u{6666}".bytesize.should == 5
+ end
+
+ it "works with pseudo-ASCII strings containing UTF-8 characters" do
+ "c \u{6666}".force_encoding('ASCII').bytesize.should == 5
+ end
+
+ it "returns 0 for the empty string" do
+ "".bytesize.should == 0
+ "".force_encoding('ASCII').bytesize.should == 0
+ "".force_encoding('UTF-8').bytesize.should == 0
+ end
+end
diff --git a/spec/ruby/core/string/byteslice_spec.rb b/spec/ruby/core/string/byteslice_spec.rb
new file mode 100644
index 0000000000..312229523d
--- /dev/null
+++ b/spec/ruby/core/string/byteslice_spec.rb
@@ -0,0 +1,33 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/slice'
+
+describe "String#byteslice" do
+ it "needs to reviewed for spec completeness"
+
+ it_behaves_like :string_slice, :byteslice
+end
+
+describe "String#byteslice with index, length" do
+ it_behaves_like :string_slice_index_length, :byteslice
+end
+
+describe "String#byteslice with Range" do
+ it_behaves_like :string_slice_range, :byteslice
+end
+
+describe "String#byteslice on on non ASCII strings" do
+ it "returns byteslice of unicode strings" do
+ "\u3042".byteslice(1).should == "\x81".force_encoding("UTF-8")
+ "\u3042".byteslice(1, 2).should == "\x81\x82".force_encoding("UTF-8")
+ "\u3042".byteslice(1..2).should == "\x81\x82".force_encoding("UTF-8")
+ "\u3042".byteslice(-1).should == "\x82".force_encoding("UTF-8")
+ end
+
+ it "returns a String in the same encoding as self" do
+ "ruby".encode("UTF-8").slice(0).encoding.should == Encoding::UTF_8
+ "ruby".encode("US-ASCII").slice(0).encoding.should == Encoding::US_ASCII
+ "ruby".encode("Windows-1251").slice(0).encoding.should == Encoding::Windows_1251
+ end
+end
diff --git a/spec/ruby/core/string/capitalize_spec.rb b/spec/ruby/core/string/capitalize_spec.rb
new file mode 100644
index 0000000000..3f85cf5ae4
--- /dev/null
+++ b/spec/ruby/core/string/capitalize_spec.rb
@@ -0,0 +1,216 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#capitalize" do
+ it "returns a copy of self with the first character converted to uppercase and the remainder to lowercase" do
+ "".capitalize.should == ""
+ "h".capitalize.should == "H"
+ "H".capitalize.should == "H"
+ "hello".capitalize.should == "Hello"
+ "HELLO".capitalize.should == "Hello"
+ "123ABC".capitalize.should == "123abc"
+ "abcdef"[1...-1].capitalize.should == "Bcde"
+ end
+
+ describe "full Unicode case mapping" do
+ it "works for all of Unicode with no option" do
+ "äöÜ".capitalize.should == "Äöü"
+ end
+
+ it "only capitalizes the first resulting character when upcasing a character produces a multi-character sequence" do
+ "ß".capitalize.should == "Ss"
+ end
+
+ it "updates string metadata" do
+ capitalized = "ßeT".capitalize
+
+ capitalized.should == "Sset"
+ capitalized.size.should == 4
+ capitalized.bytesize.should == 4
+ capitalized.ascii_only?.should be_true
+ end
+ end
+
+ describe "ASCII-only case mapping" do
+ it "does not capitalize non-ASCII characters" do
+ "ßet".capitalize(:ascii).should == "ßet"
+ end
+
+ it "handles non-ASCII substrings properly" do
+ "garçon"[1...-1].capitalize(:ascii).should == "Arço"
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Turkic languages" do
+ it "capitalizes ASCII characters according to Turkic semantics" do
+ "iSa".capitalize(:turkic).should == "İsa"
+ end
+
+ it "allows Lithuanian as an extra option" do
+ "iSa".capitalize(:turkic, :lithuanian).should == "İsa"
+ end
+
+ it "does not allow any other additional option" do
+ -> { "iSa".capitalize(:turkic, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Lithuanian" do
+ it "currently works the same as full Unicode case mapping" do
+ "iß".capitalize(:lithuanian).should == "Iß"
+ end
+
+ it "allows Turkic as an extra option (and applies Turkic semantics)" do
+ "iß".capitalize(:lithuanian, :turkic).should == "İß"
+ end
+
+ it "does not allow any other additional option" do
+ -> { "iß".capitalize(:lithuanian, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "does not allow the :fold option for upcasing" do
+ -> { "abc".capitalize(:fold) }.should raise_error(ArgumentError)
+ end
+
+ it "does not allow invalid options" do
+ -> { "abc".capitalize(:invalid_option) }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("hello").capitalize.should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("Hello").capitalize.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("hello").capitalize.should be_an_instance_of(String)
+ StringSpecs::MyString.new("Hello").capitalize.should be_an_instance_of(String)
+ end
+ end
+
+ it "returns a String in the same encoding as self" do
+ "h".encode("US-ASCII").capitalize.encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "String#capitalize!" do
+ it "capitalizes self in place" do
+ a = "hello"
+ a.capitalize!.should equal(a)
+ a.should == "Hello"
+ end
+
+ it "modifies self in place for non-ascii-compatible encodings" do
+ a = "heLLo".encode("utf-16le")
+ a.capitalize!
+ a.should == "Hello".encode("utf-16le")
+ end
+
+ describe "full Unicode case mapping" do
+ it "modifies self in place for all of Unicode with no option" do
+ a = "äöÜ"
+ a.capitalize!
+ a.should == "Äöü"
+ end
+
+ it "only capitalizes the first resulting character when upcasing a character produces a multi-character sequence" do
+ a = "ß"
+ a.capitalize!
+ a.should == "Ss"
+ end
+
+ it "works for non-ascii-compatible encodings" do
+ a = "äöü".encode("utf-16le")
+ a.capitalize!
+ a.should == "Äöü".encode("utf-16le")
+ end
+
+ it "updates string metadata" do
+ capitalized = "ßeT"
+ capitalized.capitalize!
+
+ capitalized.should == "Sset"
+ capitalized.size.should == 4
+ capitalized.bytesize.should == 4
+ capitalized.ascii_only?.should be_true
+ end
+ end
+
+ describe "modifies self in place for ASCII-only case mapping" do
+ it "does not capitalize non-ASCII characters" do
+ a = "ßet"
+ a.capitalize!(:ascii)
+ a.should == "ßet"
+ end
+
+ it "works for non-ascii-compatible encodings" do
+ a = "aBc".encode("utf-16le")
+ a.capitalize!(:ascii)
+ a.should == "Abc".encode("utf-16le")
+ end
+ end
+
+ describe "modifies self in place for full Unicode case mapping adapted for Turkic languages" do
+ it "capitalizes ASCII characters according to Turkic semantics" do
+ a = "iSa"
+ a.capitalize!(:turkic)
+ a.should == "İsa"
+ end
+
+ it "allows Lithuanian as an extra option" do
+ a = "iSa"
+ a.capitalize!(:turkic, :lithuanian)
+ a.should == "İsa"
+ end
+
+ it "does not allow any other additional option" do
+ -> { a = "iSa"; a.capitalize!(:turkic, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "modifies self in place for full Unicode case mapping adapted for Lithuanian" do
+ it "currently works the same as full Unicode case mapping" do
+ a = "iß"
+ a.capitalize!(:lithuanian)
+ a.should == "Iß"
+ end
+
+ it "allows Turkic as an extra option (and applies Turkic semantics)" do
+ a = "iß"
+ a.capitalize!(:lithuanian, :turkic)
+ a.should == "İß"
+ end
+
+ it "does not allow any other additional option" do
+ -> { a = "iß"; a.capitalize!(:lithuanian, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "does not allow the :fold option for upcasing" do
+ -> { a = "abc"; a.capitalize!(:fold) }.should raise_error(ArgumentError)
+ end
+
+ it "does not allow invalid options" do
+ -> { a = "abc"; a.capitalize!(:invalid_option) }.should raise_error(ArgumentError)
+ end
+
+ it "returns nil when no changes are made" do
+ a = "Hello"
+ a.capitalize!.should == nil
+ a.should == "Hello"
+
+ "".capitalize!.should == nil
+ "H".capitalize!.should == nil
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ ["", "Hello", "hello"].each do |a|
+ a.freeze
+ -> { a.capitalize! }.should raise_error(FrozenError)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/case_compare_spec.rb b/spec/ruby/core/string/case_compare_spec.rb
new file mode 100644
index 0000000000..b83d1adb91
--- /dev/null
+++ b/spec/ruby/core/string/case_compare_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eql'
+require_relative 'shared/equal_value'
+
+describe "String#===" do
+ it_behaves_like :string_eql_value, :===
+ it_behaves_like :string_equal_value, :===
+end
diff --git a/spec/ruby/core/string/casecmp_spec.rb b/spec/ruby/core/string/casecmp_spec.rb
new file mode 100644
index 0000000000..986fbc8718
--- /dev/null
+++ b/spec/ruby/core/string/casecmp_spec.rb
@@ -0,0 +1,194 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#casecmp independent of case" do
+ it "returns -1 when less than other" do
+ "a".casecmp("b").should == -1
+ "A".casecmp("b").should == -1
+ end
+
+ it "returns 0 when equal to other" do
+ "a".casecmp("a").should == 0
+ "A".casecmp("a").should == 0
+ end
+
+ it "returns 1 when greater than other" do
+ "b".casecmp("a").should == 1
+ "B".casecmp("a").should == 1
+ end
+
+ it "tries to convert other to string using to_str" do
+ other = mock('x')
+ other.should_receive(:to_str).and_return("abc")
+
+ "abc".casecmp(other).should == 0
+ end
+
+ it "returns nil if other can't be converted to a string" do
+ "abc".casecmp(mock('abc')).should be_nil
+ end
+
+ it "returns nil if incompatible encodings" do
+ "ã‚れ".casecmp("れ".encode(Encoding::EUC_JP)).should be_nil
+ end
+
+ describe "in UTF-8 mode" do
+ describe "for non-ASCII characters" do
+ before :each do
+ @upper_a_tilde = "Ã"
+ @lower_a_tilde = "ã"
+ @upper_a_umlaut = "Ä"
+ @lower_a_umlaut = "ä"
+ end
+
+ it "returns -1 when numerically less than other" do
+ @upper_a_tilde.casecmp(@lower_a_tilde).should == -1
+ @upper_a_tilde.casecmp(@upper_a_umlaut).should == -1
+ end
+
+ it "returns 0 when numerically equal to other" do
+ @upper_a_tilde.casecmp(@upper_a_tilde).should == 0
+ end
+
+ it "returns 1 when numerically greater than other" do
+ @lower_a_umlaut.casecmp(@upper_a_umlaut).should == 1
+ @lower_a_umlaut.casecmp(@lower_a_tilde).should == 1
+ end
+ end
+
+ describe "for ASCII characters" do
+ it "returns -1 when less than other" do
+ "a".casecmp("b").should == -1
+ "A".casecmp("b").should == -1
+ end
+
+ it "returns 0 when equal to other" do
+ "a".casecmp("a").should == 0
+ "A".casecmp("a").should == 0
+ end
+
+ it "returns 1 when greater than other" do
+ "b".casecmp("a").should == 1
+ "B".casecmp("a").should == 1
+ end
+ end
+ end
+
+ describe "for non-ASCII characters" do
+ before :each do
+ @upper_a_tilde = "\xc3"
+ @lower_a_tilde = "\xe3"
+ end
+
+ it "returns -1 when numerically less than other" do
+ @upper_a_tilde.casecmp(@lower_a_tilde).should == -1
+ end
+
+ it "returns 0 when equal to other" do
+ @upper_a_tilde.casecmp("\xc3").should == 0
+ end
+
+ it "returns 1 when numerically greater than other" do
+ @lower_a_tilde.casecmp(@upper_a_tilde).should == 1
+ end
+
+ it "does not case fold" do
+ "ß".casecmp("ss").should == 1
+ end
+ end
+
+ describe "when comparing a subclass instance" do
+ it "returns -1 when less than other" do
+ b = StringSpecs::MyString.new "b"
+ "a".casecmp(b).should == -1
+ "A".casecmp(b).should == -1
+ end
+
+ it "returns 0 when equal to other" do
+ a = StringSpecs::MyString.new "a"
+ "a".casecmp(a).should == 0
+ "A".casecmp(a).should == 0
+ end
+
+ it "returns 1 when greater than other" do
+ a = StringSpecs::MyString.new "a"
+ "b".casecmp(a).should == 1
+ "B".casecmp(a).should == 1
+ end
+ end
+end
+
+describe 'String#casecmp? independent of case' do
+ it 'returns true when equal to other' do
+ 'abc'.casecmp?('abc').should == true
+ 'abc'.casecmp?('ABC').should == true
+ end
+
+ it 'returns false when not equal to other' do
+ 'abc'.casecmp?('DEF').should == false
+ 'abc'.casecmp?('def').should == false
+ end
+
+ it "tries to convert other to string using to_str" do
+ other = mock('x')
+ other.should_receive(:to_str).and_return("abc")
+
+ "abc".casecmp?(other).should == true
+ end
+
+ it "returns nil if incompatible encodings" do
+ "ã‚れ".casecmp?("れ".encode(Encoding::EUC_JP)).should be_nil
+ end
+
+ describe 'for UNICODE characters' do
+ it 'returns true when downcase(:fold) on unicode' do
+ 'äöü'.casecmp?('ÄÖÜ').should == true
+ end
+ end
+
+ describe "when comparing a subclass instance" do
+ it 'returns true when equal to other' do
+ a = StringSpecs::MyString.new "a"
+ 'a'.casecmp?(a).should == true
+ 'A'.casecmp?(a).should == true
+ end
+
+ it 'returns false when not equal to other' do
+ b = StringSpecs::MyString.new "a"
+ 'b'.casecmp?(b).should == false
+ 'B'.casecmp?(b).should == false
+ end
+ end
+
+ describe "in UTF-8 mode" do
+ describe "for non-ASCII characters" do
+ before :each do
+ @upper_a_tilde = "Ã"
+ @lower_a_tilde = "ã"
+ @upper_a_umlaut = "Ä"
+ @lower_a_umlaut = "ä"
+ end
+
+ it "returns true when they are the same with normalized case" do
+ @upper_a_tilde.casecmp?(@lower_a_tilde).should == true
+ end
+
+ it "returns false when they are unrelated" do
+ @upper_a_tilde.casecmp?(@upper_a_umlaut).should == false
+ end
+
+ it "returns true when they have the same bytes" do
+ @upper_a_tilde.casecmp?(@upper_a_tilde).should == true
+ end
+ end
+ end
+
+ it "case folds" do
+ "ß".casecmp?("ss").should be_true
+ end
+
+ it "returns nil if other can't be converted to a string" do
+ "abc".casecmp?(mock('abc')).should be_nil
+ end
+end
diff --git a/spec/ruby/core/string/center_spec.rb b/spec/ruby/core/string/center_spec.rb
new file mode 100644
index 0000000000..76da6e1e09
--- /dev/null
+++ b/spec/ruby/core/string/center_spec.rb
@@ -0,0 +1,130 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#center with length, padding" do
+ it "returns a new string of specified length with self centered and padded with padstr" do
+ "one".center(9, '.').should == "...one..."
+ "hello".center(20, '123').should == "1231231hello12312312"
+ "middle".center(13, '-').should == "---middle----"
+
+ "".center(1, "abcd").should == "a"
+ "".center(2, "abcd").should == "aa"
+ "".center(3, "abcd").should == "aab"
+ "".center(4, "abcd").should == "abab"
+ "".center(6, "xy").should == "xyxxyx"
+ "".center(11, "12345").should == "12345123451"
+
+ "|".center(2, "abcd").should == "|a"
+ "|".center(3, "abcd").should == "a|a"
+ "|".center(4, "abcd").should == "a|ab"
+ "|".center(5, "abcd").should == "ab|ab"
+ "|".center(6, "xy").should == "xy|xyx"
+ "|".center(7, "xy").should == "xyx|xyx"
+ "|".center(11, "12345").should == "12345|12345"
+ "|".center(12, "12345").should == "12345|123451"
+
+ "||".center(3, "abcd").should == "||a"
+ "||".center(4, "abcd").should == "a||a"
+ "||".center(5, "abcd").should == "a||ab"
+ "||".center(6, "abcd").should == "ab||ab"
+ "||".center(8, "xy").should == "xyx||xyx"
+ "||".center(12, "12345").should == "12345||12345"
+ "||".center(13, "12345").should == "12345||123451"
+ end
+
+ it "pads with whitespace if no padstr is given" do
+ "two".center(5).should == " two "
+ "hello".center(20).should == " hello "
+ end
+
+ it "returns self if it's longer than or as long as the specified length" do
+ "".center(0).should == ""
+ "".center(-1).should == ""
+ "hello".center(4).should == "hello"
+ "hello".center(-1).should == "hello"
+ "this".center(3).should == "this"
+ "radiology".center(8, '-').should == "radiology"
+ end
+
+ it "calls #to_int to convert length to an integer" do
+ "_".center(3.8, "^").should == "^_^"
+
+ obj = mock('3')
+ obj.should_receive(:to_int).and_return(3)
+
+ "_".center(obj, "o").should == "o_o"
+ end
+
+ it "raises a TypeError when length can't be converted to an integer" do
+ -> { "hello".center("x") }.should raise_error(TypeError)
+ -> { "hello".center("x", "y") }.should raise_error(TypeError)
+ -> { "hello".center([]) }.should raise_error(TypeError)
+ -> { "hello".center(mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "calls #to_str to convert padstr to a String" do
+ padstr = mock('123')
+ padstr.should_receive(:to_str).and_return("123")
+
+ "hello".center(20, padstr).should == "1231231hello12312312"
+ end
+
+ it "raises a TypeError when padstr can't be converted to a string" do
+ -> { "hello".center(20, 100) }.should raise_error(TypeError)
+ -> { "hello".center(20, []) }.should raise_error(TypeError)
+ -> { "hello".center(20, mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError if padstr is empty" do
+ -> { "hello".center(10, "") }.should raise_error(ArgumentError)
+ -> { "hello".center(0, "") }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on subclasses" do
+ StringSpecs::MyString.new("").center(10).should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("foo").center(10).should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("foo").center(10, StringSpecs::MyString.new("x")).should be_an_instance_of(StringSpecs::MyString)
+
+ "".center(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ "foo".center(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on subclasses" do
+ StringSpecs::MyString.new("").center(10).should be_an_instance_of(String)
+ StringSpecs::MyString.new("foo").center(10).should be_an_instance_of(String)
+ StringSpecs::MyString.new("foo").center(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+
+ "".center(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ "foo".center(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ end
+ end
+
+ describe "with width" do
+ it "returns a String in the same encoding as the original" do
+ str = "abc".force_encoding Encoding::IBM437
+ result = str.center 6
+ result.should == " abc "
+ result.encoding.should equal(Encoding::IBM437)
+ end
+ end
+
+ describe "with width, pattern" do
+ it "returns a String in the compatible encoding" do
+ str = "abc".force_encoding Encoding::IBM437
+ result = str.center 6, "ã‚"
+ result.should == "ã‚abcã‚ã‚"
+ result.encoding.should equal(Encoding::UTF_8)
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are incompatible" do
+ pat = "ã‚¢".encode Encoding::EUC_JP
+ -> do
+ "ã‚れ".center 5, pat
+ end.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/chars_spec.rb b/spec/ruby/core/string/chars_spec.rb
new file mode 100644
index 0000000000..715e65dc90
--- /dev/null
+++ b/spec/ruby/core/string/chars_spec.rb
@@ -0,0 +1,15 @@
+require_relative 'shared/chars'
+
+describe "String#chars" do
+ it_behaves_like :string_chars, :chars
+
+ it "returns an array when no block given" do
+ "hello".chars.should == ['h', 'e', 'l', 'l', 'o']
+ end
+
+ it "returns Strings in the same encoding as self" do
+ "hello".encode("US-ASCII").chars.each do |c|
+ c.encoding.should == Encoding::US_ASCII
+ end
+ end
+end
diff --git a/spec/ruby/core/string/chomp_spec.rb b/spec/ruby/core/string/chomp_spec.rb
new file mode 100644
index 0000000000..d0508d938f
--- /dev/null
+++ b/spec/ruby/core/string/chomp_spec.rb
@@ -0,0 +1,374 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#chomp" do
+ describe "when passed no argument" do
+ before do
+ # Ensure that $/ is set to the default value
+ @verbose, $VERBOSE = $VERBOSE, nil
+ @dollar_slash, $/ = $/, "\n"
+ end
+
+ after do
+ $/ = @dollar_slash
+ $VERBOSE = @verbose
+ end
+
+ it "does not modify a String with no trailing carriage return or newline" do
+ "abc".chomp.should == "abc"
+ end
+
+ it "returns a copy of the String when it is not modified" do
+ str = "abc"
+ str.chomp.should_not equal(str)
+ end
+
+ it "removes one trailing newline" do
+ "abc\n\n".chomp.should == "abc\n"
+ end
+
+ it "removes one trailing carriage return" do
+ "abc\r\r".chomp.should == "abc\r"
+ end
+
+ it "removes one trailing carriage return, newline pair" do
+ "abc\r\n\r\n".chomp.should == "abc\r\n"
+ end
+
+ it "returns an empty String when self is empty" do
+ "".chomp.should == ""
+ end
+
+ it "returns a String in the same encoding as self" do
+ "abc\n\n".encode("US-ASCII").chomp.encoding.should == Encoding::US_ASCII
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ str = StringSpecs::MyString.new("hello\n").chomp
+ str.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ str = StringSpecs::MyString.new("hello\n").chomp
+ str.should be_an_instance_of(String)
+ end
+ end
+
+ it "removes trailing characters that match $/ when it has been assigned a value" do
+ $/ = "cdef"
+ "abcdef".chomp.should == "ab"
+ end
+
+ it "removes one trailing newline for string with invalid encoding" do
+ "\xa0\xa1\n".chomp.should == "\xa0\xa1"
+ end
+ end
+
+ describe "when passed nil" do
+ it "does not modify the String" do
+ "abc\r\n".chomp(nil).should == "abc\r\n"
+ end
+
+ it "returns a copy of the String" do
+ str = "abc"
+ str.chomp(nil).should_not equal(str)
+ end
+
+ it "returns an empty String when self is empty" do
+ "".chomp(nil).should == ""
+ end
+ end
+
+ describe "when passed ''" do
+ it "removes a final newline" do
+ "abc\n".chomp("").should == "abc"
+ end
+
+ it "removes a final carriage return, newline" do
+ "abc\r\n".chomp("").should == "abc"
+ end
+
+ it "does not remove a final carriage return" do
+ "abc\r".chomp("").should == "abc\r"
+ end
+
+ it "removes more than one trailing newlines" do
+ "abc\n\n\n".chomp("").should == "abc"
+ end
+
+ it "removes more than one trailing carriage return, newline pairs" do
+ "abc\r\n\r\n\r\n".chomp("").should == "abc"
+ end
+
+ it "returns an empty String when self is empty" do
+ "".chomp("").should == ""
+ end
+
+ it "removes one trailing newline for string with invalid encoding" do
+ "\xa0\xa1\n".chomp("").should == "\xa0\xa1"
+ end
+ end
+
+ describe "when passed '\\n'" do
+ it "removes one trailing newline" do
+ "abc\n\n".chomp("\n").should == "abc\n"
+ end
+
+ it "removes one trailing carriage return" do
+ "abc\r\r".chomp("\n").should == "abc\r"
+ end
+
+ it "removes one trailing carriage return, newline pair" do
+ "abc\r\n\r\n".chomp("\n").should == "abc\r\n"
+ end
+
+ it "returns an empty String when self is empty" do
+ "".chomp("\n").should == ""
+ end
+ end
+
+ describe "when passed an Object" do
+ it "calls #to_str to convert to a String" do
+ arg = mock("string chomp")
+ arg.should_receive(:to_str).and_return("bc")
+ "abc".chomp(arg).should == "a"
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ arg = mock("string chomp")
+ arg.should_receive(:to_str).and_return(1)
+ -> { "abc".chomp(arg) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "when passed a String" do
+ it "removes the trailing characters if they match the argument" do
+ "abcabc".chomp("abc").should == "abc"
+ end
+
+ it "does not modify the String if the argument does not match the trailing characters" do
+ "abc".chomp("def").should == "abc"
+ end
+
+ it "returns an empty String when self is empty" do
+ "".chomp("abc").should == ""
+ end
+
+ it "returns an empty String when the argument equals self" do
+ "abc".chomp("abc").should == ""
+ end
+ end
+end
+
+describe "String#chomp!" do
+ describe "when passed no argument" do
+ before do
+ # Ensure that $/ is set to the default value
+ @verbose, $VERBOSE = $VERBOSE, nil
+ @dollar_slash, $/ = $/, "\n"
+ end
+
+ after do
+ $/ = @dollar_slash
+ $VERBOSE = @verbose
+ end
+
+ it "modifies self" do
+ str = "abc\n"
+ str.chomp!.should equal(str)
+ end
+
+ it "returns nil if self is not modified" do
+ "abc".chomp!.should be_nil
+ end
+
+ it "removes one trailing newline" do
+ "abc\n\n".chomp!.should == "abc\n"
+ end
+
+ it "removes one trailing carriage return" do
+ "abc\r\r".chomp!.should == "abc\r"
+ end
+
+ it "removes one trailing carriage return, newline pair" do
+ "abc\r\n\r\n".chomp!.should == "abc\r\n"
+ end
+
+ it "returns nil when self is empty" do
+ "".chomp!.should be_nil
+ end
+
+ it "returns subclass instances when called on a subclass" do
+ str = StringSpecs::MyString.new("hello\n").chomp!
+ str.should be_an_instance_of(StringSpecs::MyString)
+ end
+
+ it "removes trailing characters that match $/ when it has been assigned a value" do
+ $/ = "cdef"
+ "abcdef".chomp!.should == "ab"
+ end
+ end
+
+ describe "when passed nil" do
+ it "returns nil" do
+ "abc\r\n".chomp!(nil).should be_nil
+ end
+
+ it "returns nil when self is empty" do
+ "".chomp!(nil).should be_nil
+ end
+ end
+
+ describe "when passed ''" do
+ it "removes a final newline" do
+ "abc\n".chomp!("").should == "abc"
+ end
+
+ it "removes a final carriage return, newline" do
+ "abc\r\n".chomp!("").should == "abc"
+ end
+
+ it "does not remove a final carriage return" do
+ "abc\r".chomp!("").should be_nil
+ end
+
+ it "removes more than one trailing newlines" do
+ "abc\n\n\n".chomp!("").should == "abc"
+ end
+
+ it "removes more than one trailing carriage return, newline pairs" do
+ "abc\r\n\r\n\r\n".chomp!("").should == "abc"
+ end
+
+ it "returns nil when self is empty" do
+ "".chomp!("").should be_nil
+ end
+ end
+
+ describe "when passed '\\n'" do
+ it "removes one trailing newline" do
+ "abc\n\n".chomp!("\n").should == "abc\n"
+ end
+
+ it "removes one trailing carriage return" do
+ "abc\r\r".chomp!("\n").should == "abc\r"
+ end
+
+ it "removes one trailing carriage return, newline pair" do
+ "abc\r\n\r\n".chomp!("\n").should == "abc\r\n"
+ end
+
+ it "returns nil when self is empty" do
+ "".chomp!("\n").should be_nil
+ end
+ end
+
+ describe "when passed an Object" do
+ it "calls #to_str to convert to a String" do
+ arg = mock("string chomp")
+ arg.should_receive(:to_str).and_return("bc")
+ "abc".chomp!(arg).should == "a"
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ arg = mock("string chomp")
+ arg.should_receive(:to_str).and_return(1)
+ -> { "abc".chomp!(arg) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "when passed a String" do
+ it "removes the trailing characters if they match the argument" do
+ "abcabc".chomp!("abc").should == "abc"
+ end
+
+ it "returns nil if the argument does not match the trailing characters" do
+ "abc".chomp!("def").should be_nil
+ end
+
+ it "returns nil when self is empty" do
+ "".chomp!("abc").should be_nil
+ end
+ end
+
+ it "raises a FrozenError on a frozen instance when it is modified" do
+ a = "string\n\r"
+ a.freeze
+
+ -> { a.chomp! }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on a frozen instance when it would not be modified" do
+ a = "string\n\r"
+ a.freeze
+ -> { a.chomp!(nil) }.should raise_error(FrozenError)
+ -> { a.chomp!("x") }.should raise_error(FrozenError)
+ end
+end
+
+describe "String#chomp" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ @before_separator = $/
+ end
+
+ after :each do
+ $/ = @before_separator
+ $VERBOSE = @verbose
+ end
+
+ it "does not modify a multi-byte character" do
+ "ã‚れ".chomp.should == "ã‚れ"
+ end
+
+ it "removes the final carriage return, newline from a multibyte String" do
+ "ã‚れ\r\n".chomp.should == "ã‚れ"
+ end
+
+ it "removes the final carriage return, newline from a non-ASCII String" do
+ str = "abc\r\n".encode "utf-32be"
+ str.chomp.should == "abc".encode("utf-32be")
+ end
+
+ it "removes the final carriage return, newline from a non-ASCII String when the record separator is changed" do
+ $/ = "\n".encode("utf-8")
+ str = "abc\r\n".encode "utf-32be"
+ str.chomp.should == "abc".encode("utf-32be")
+ end
+end
+
+describe "String#chomp!" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ @before_separator = $/
+ end
+
+ after :each do
+ $/ = @before_separator
+ $VERBOSE = @verbose
+ end
+
+ it "returns nil when the String is not modified" do
+ "ã‚れ".chomp!.should be_nil
+ end
+
+ it "removes the final carriage return, newline from a multibyte String" do
+ "ã‚れ\r\n".chomp!.should == "ã‚れ"
+ end
+
+ it "removes the final carriage return, newline from a non-ASCII String" do
+ str = "abc\r\n".encode "utf-32be"
+ str.chomp!.should == "abc".encode("utf-32be")
+ end
+
+ it "removes the final carriage return, newline from a non-ASCII String when the record separator is changed" do
+ $/ = "\n".encode("utf-8")
+ str = "abc\r\n".encode "utf-32be"
+ str.chomp!.should == "abc".encode("utf-32be")
+ end
+end
diff --git a/spec/ruby/core/string/chop_spec.rb b/spec/ruby/core/string/chop_spec.rb
new file mode 100644
index 0000000000..f598d34bc8
--- /dev/null
+++ b/spec/ruby/core/string/chop_spec.rb
@@ -0,0 +1,126 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#chop" do
+ it "removes the final character" do
+ "abc".chop.should == "ab"
+ end
+
+ it "removes the final carriage return" do
+ "abc\r".chop.should == "abc"
+ end
+
+ it "removes the final newline" do
+ "abc\n".chop.should == "abc"
+ end
+
+ it "removes the final carriage return, newline" do
+ "abc\r\n".chop.should == "abc"
+ end
+
+ it "removes the carriage return, newline if they are the only characters" do
+ "\r\n".chop.should == ""
+ end
+
+ it "does not remove more than the final carriage return, newline" do
+ "abc\r\n\r\n".chop.should == "abc\r\n"
+ end
+
+ it "removes a multi-byte character" do
+ "ã‚れ".chop.should == "ã‚"
+ end
+
+ it "removes the final carriage return, newline from a multibyte String" do
+ "ã‚れ\r\n".chop.should == "ã‚れ"
+ end
+
+ it "removes the final carriage return, newline from a non-ASCII String" do
+ str = "abc\r\n".encode "utf-32be"
+ str.chop.should == "abc".encode("utf-32be")
+ end
+
+ it "returns an empty string when applied to an empty string" do
+ "".chop.should == ""
+ end
+
+ it "returns a new string when applied to an empty string" do
+ s = ""
+ s.chop.should_not equal(s)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("hello\n").chop.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("hello\n").chop.should be_an_instance_of(String)
+ end
+ end
+
+ it "returns a String in the same encoding as self" do
+ "abc\n\n".encode("US-ASCII").chop.encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "String#chop!" do
+ it "removes the final character" do
+ "abc".chop!.should == "ab"
+ end
+
+ it "removes the final carriage return" do
+ "abc\r".chop!.should == "abc"
+ end
+
+ it "removes the final newline" do
+ "abc\n".chop!.should == "abc"
+ end
+
+ it "removes the final carriage return, newline" do
+ "abc\r\n".chop!.should == "abc"
+ end
+
+ it "removes the carriage return, newline if they are the only characters" do
+ "\r\n".chop!.should == ""
+ end
+
+ it "does not remove more than the final carriage return, newline" do
+ "abc\r\n\r\n".chop!.should == "abc\r\n"
+ end
+
+ it "removes a multi-byte character" do
+ "ã‚れ".chop!.should == "ã‚"
+ end
+
+ it "removes the final carriage return, newline from a multibyte String" do
+ "ã‚れ\r\n".chop!.should == "ã‚れ"
+ end
+
+ it "removes the final carriage return, newline from a non-ASCII String" do
+ str = "abc\r\n".encode "utf-32be"
+ str.chop!.should == "abc".encode("utf-32be")
+ end
+
+ it "returns self if modifications were made" do
+ str = "hello"
+ str.chop!.should equal(str)
+ end
+
+ it "returns nil when called on an empty string" do
+ "".chop!.should be_nil
+ end
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ -> { "string\n\r".freeze.chop! }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on a frozen instance that would not be modified" do
+ a = ""
+ a.freeze
+ -> { a.chop! }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/chr_spec.rb b/spec/ruby/core/string/chr_spec.rb
new file mode 100644
index 0000000000..9ed29542e6
--- /dev/null
+++ b/spec/ruby/core/string/chr_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+
+describe "String#chr" do
+ it "returns a copy of self" do
+ s = 'e'
+ s.should_not equal s.chr
+ end
+
+ it "returns a String" do
+ 'glark'.chr.should be_an_instance_of(String)
+ end
+
+ it "returns an empty String if self is an empty String" do
+ "".chr.should == ""
+ end
+
+ it "returns a 1-character String" do
+ "glark".chr.size.should == 1
+ end
+
+ it "returns the character at the start of the String" do
+ "Goodbye, world".chr.should == "G"
+ end
+
+ it "returns a String in the same encoding as self" do
+ "\x24".encode(Encoding::US_ASCII).chr.encoding.should == Encoding::US_ASCII
+ end
+
+ it "understands multi-byte characters" do
+ s = "\u{9879}"
+ s.bytesize.should == 3
+ s.chr.should == s
+ end
+
+ it "understands Strings that contain a mixture of character widths" do
+ three = "\u{8082}"
+ three.bytesize.should == 3
+ four = "\u{77082}"
+ four.bytesize.should == 4
+ "#{three}#{four}".chr.should == three
+ end
+end
diff --git a/spec/ruby/core/string/clear_spec.rb b/spec/ruby/core/string/clear_spec.rb
new file mode 100644
index 0000000000..e1d68e03bd
--- /dev/null
+++ b/spec/ruby/core/string/clear_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+describe "String#clear" do
+ before :each do
+ @s = "Jolene"
+ end
+
+ it "sets self equal to the empty String" do
+ @s.clear
+ @s.should == ""
+ end
+
+ it "returns self after emptying it" do
+ cleared = @s.clear
+ cleared.should == ""
+ cleared.should equal @s
+ end
+
+ it "preserves its encoding" do
+ @s.encode!(Encoding::SHIFT_JIS)
+ @s.encoding.should == Encoding::SHIFT_JIS
+ @s.clear.encoding.should == Encoding::SHIFT_JIS
+ @s.encoding.should == Encoding::SHIFT_JIS
+ end
+
+ it "works with multibyte Strings" do
+ s = "\u{9765}\u{876}"
+ s.clear
+ s.should == ""
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ @s.freeze
+ -> { @s.clear }.should raise_error(FrozenError)
+ -> { "".freeze.clear }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/clone_spec.rb b/spec/ruby/core/string/clone_spec.rb
new file mode 100644
index 0000000000..a2ba2f9877
--- /dev/null
+++ b/spec/ruby/core/string/clone_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#clone" do
+ before :each do
+ ScratchPad.clear
+ @obj = StringSpecs::InitializeString.new "string"
+ end
+
+ it "calls #initialize_copy on the new instance" do
+ clone = @obj.clone
+ ScratchPad.recorded.should_not == @obj.object_id
+ ScratchPad.recorded.should == clone.object_id
+ end
+
+ it "copies instance variables" do
+ clone = @obj.clone
+ clone.ivar.should == 1
+ end
+
+ it "copies singleton methods" do
+ def @obj.special() :the_one end
+ clone = @obj.clone
+ clone.special.should == :the_one
+ end
+
+ it "copies modules included in the singleton class" do
+ class << @obj
+ include StringSpecs::StringModule
+ end
+
+ clone = @obj.clone
+ clone.repr.should == 1
+ end
+
+ it "copies constants defined in the singleton class" do
+ class << @obj
+ CLONE = :clone
+ end
+
+ clone = @obj.clone
+ (class << clone; CLONE; end).should == :clone
+ end
+
+ it "copies frozen state" do
+ @obj.freeze.clone.frozen?.should be_true
+ "".freeze.clone.frozen?.should be_true
+ end
+
+ it "does not modify the original string when changing cloned string" do
+ orig = "string"[0..100]
+ clone = orig.clone
+ orig[0] = 'x'
+ orig.should == "xtring"
+ clone.should == "string"
+ end
+
+ it "returns a String in the same encoding as self" do
+ "a".encode("US-ASCII").clone.encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/string/codepoints_spec.rb b/spec/ruby/core/string/codepoints_spec.rb
new file mode 100644
index 0000000000..0b6cde82f7
--- /dev/null
+++ b/spec/ruby/core/string/codepoints_spec.rb
@@ -0,0 +1,18 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'shared/codepoints'
+require_relative 'shared/each_codepoint_without_block'
+
+describe "String#codepoints" do
+ it_behaves_like :string_codepoints, :codepoints
+
+ it "returns an Array when no block is given" do
+ "abc".codepoints.should == [?a.ord, ?b.ord, ?c.ord]
+ end
+
+ it "raises an ArgumentError when no block is given if self has an invalid encoding" do
+ s = "\xDF".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.codepoints }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/comparison_spec.rb b/spec/ruby/core/string/comparison_spec.rb
new file mode 100644
index 0000000000..91cfdca25a
--- /dev/null
+++ b/spec/ruby/core/string/comparison_spec.rb
@@ -0,0 +1,112 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#<=> with String" do
+ it "compares individual characters based on their ascii value" do
+ ascii_order = Array.new(256) { |x| x.chr }
+ sort_order = ascii_order.sort
+ sort_order.should == ascii_order
+ end
+
+ it "returns -1 when self is less than other" do
+ ("this" <=> "those").should == -1
+ end
+
+ it "returns 0 when self is equal to other" do
+ ("yep" <=> "yep").should == 0
+ end
+
+ it "returns 1 when self is greater than other" do
+ ("yoddle" <=> "griddle").should == 1
+ end
+
+ it "considers string that comes lexicographically first to be less if strings have same size" do
+ ("aba" <=> "abc").should == -1
+ ("abc" <=> "aba").should == 1
+ end
+
+ it "doesn't consider shorter string to be less if longer string starts with shorter one" do
+ ("abc" <=> "abcd").should == -1
+ ("abcd" <=> "abc").should == 1
+ end
+
+ it "compares shorter string with corresponding number of first chars of longer string" do
+ ("abx" <=> "abcd").should == 1
+ ("abcd" <=> "abx").should == -1
+ end
+
+ it "ignores subclass differences" do
+ a = "hello"
+ b = StringSpecs::MyString.new("hello")
+
+ (a <=> b).should == 0
+ (b <=> a).should == 0
+ end
+
+ it "returns 0 if self and other are bytewise identical and have the same encoding" do
+ ("ÄÖÜ" <=> "ÄÖÜ").should == 0
+ end
+
+ it "returns 0 if self and other are bytewise identical and have the same encoding" do
+ ("ÄÖÜ" <=> "ÄÖÜ").should == 0
+ end
+
+ it "returns -1 if self is bytewise less than other" do
+ ("ÄÖÛ" <=> "ÄÖÜ").should == -1
+ end
+
+ it "returns 1 if self is bytewise greater than other" do
+ ("ÄÖÜ" <=> "ÄÖÛ").should == 1
+ end
+
+ it "ignores encoding difference" do
+ ("ÄÖÛ".force_encoding("utf-8") <=> "ÄÖÜ".force_encoding("iso-8859-1")).should == -1
+ ("ÄÖÜ".force_encoding("utf-8") <=> "ÄÖÛ".force_encoding("iso-8859-1")).should == 1
+ end
+
+ it "returns 0 with identical ASCII-compatible bytes of different encodings" do
+ ("abc".force_encoding("utf-8") <=> "abc".force_encoding("iso-8859-1")).should == 0
+ end
+
+ it "compares the indices of the encodings when the strings have identical non-ASCII-compatible bytes" do
+ xff_1 = [0xFF].pack('C').force_encoding("utf-8")
+ xff_2 = [0xFF].pack('C').force_encoding("iso-8859-1")
+ (xff_1 <=> xff_2).should == -1
+ (xff_2 <=> xff_1).should == 1
+ end
+
+ it "returns 0 when comparing 2 empty strings but one is not ASCII-compatible" do
+ ("" <=> "".force_encoding('iso-2022-jp')).should == 0
+ end
+end
+
+# Note: This is inconsistent with Array#<=> which calls #to_ary instead of
+# just using it as an indicator.
+describe "String#<=>" do
+ it "returns nil if its argument provides neither #to_str nor #<=>" do
+ ("abc" <=> mock('x')).should be_nil
+ end
+
+ it "uses the result of calling #to_str for comparison when #to_str is defined" do
+ obj = mock('x')
+ obj.should_receive(:to_str).and_return("aaa")
+
+ ("abc" <=> obj).should == 1
+ end
+
+ it "uses the result of calling #<=> on its argument when #<=> is defined but #to_str is not" do
+ obj = mock('x')
+ obj.should_receive(:<=>).and_return(-1)
+
+ ("abc" <=> obj).should == 1
+ end
+
+ it "returns nil if argument also uses an inverse comparison for <=>" do
+ obj = mock('x')
+ def obj.<=>(other); other <=> self; end
+ obj.should_receive(:<=>).once
+
+ ("abc" <=> obj).should be_nil
+ end
+end
diff --git a/spec/ruby/core/string/concat_spec.rb b/spec/ruby/core/string/concat_spec.rb
new file mode 100644
index 0000000000..5f6daadad7
--- /dev/null
+++ b/spec/ruby/core/string/concat_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/concat'
+
+describe "String#concat" do
+ it_behaves_like :string_concat, :concat
+ it_behaves_like :string_concat_encoding, :concat
+
+ it "takes multiple arguments" do
+ str = "hello "
+ str.concat "wo", "", "rld"
+ str.should == "hello world"
+ end
+
+ it "concatenates the initial value when given arguments contain 2 self" do
+ str = "hello"
+ str.concat str, str
+ str.should == "hellohellohello"
+ end
+
+ it "returns self when given no arguments" do
+ str = "hello"
+ str.concat.should equal(str)
+ str.should == "hello"
+ end
+end
diff --git a/spec/ruby/core/string/count_spec.rb b/spec/ruby/core/string/count_spec.rb
new file mode 100644
index 0000000000..06ba5a4f0e
--- /dev/null
+++ b/spec/ruby/core/string/count_spec.rb
@@ -0,0 +1,105 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#count" do
+ it "counts occurrences of chars from the intersection of the specified sets" do
+ s = "hello\nworld\x00\x00"
+
+ s.count(s).should == s.size
+ s.count("lo").should == 5
+ s.count("eo").should == 3
+ s.count("l").should == 3
+ s.count("\n").should == 1
+ s.count("\x00").should == 2
+
+ s.count("").should == 0
+ "".count("").should == 0
+
+ s.count("l", "lo").should == s.count("l")
+ s.count("l", "lo", "o").should == s.count("")
+ s.count("helo", "hel", "h").should == s.count("h")
+ s.count("helo", "", "x").should == 0
+ end
+
+ it "raises an ArgumentError when given no arguments" do
+ -> { "hell yeah".count }.should raise_error(ArgumentError)
+ end
+
+ it "negates sets starting with ^" do
+ s = "^hello\nworld\x00\x00"
+
+ s.count("^").should == 1 # no negation, counts ^
+
+ s.count("^leh").should == 9
+ s.count("^o").should == 12
+
+ s.count("helo", "^el").should == s.count("ho")
+ s.count("aeiou", "^e").should == s.count("aiou")
+
+ "^_^".count("^^").should == 1
+ "oa^_^o".count("a^").should == 3
+ end
+
+ it "counts all chars in a sequence" do
+ s = "hel-[()]-lo012^"
+
+ s.count("\x00-\xFF").should == s.size
+ s.count("ej-m").should == 3
+ s.count("e-h").should == 2
+
+ # no sequences
+ s.count("-").should == 2
+ s.count("e-").should == s.count("e") + s.count("-")
+ s.count("-h").should == s.count("h") + s.count("-")
+
+ s.count("---").should == s.count("-")
+
+ # see an ASCII table for reference
+ s.count("--2").should == s.count("-./012")
+ s.count("(--").should == s.count("()*+,-")
+ s.count("A-a").should == s.count("A-Z[\\]^_`a")
+
+ # negated sequences
+ s.count("^e-h").should == s.size - s.count("e-h")
+ s.count("^^-^").should == s.size - s.count("^")
+ s.count("^---").should == s.size - s.count("-")
+
+ "abcdefgh".count("a-ce-fh").should == 6
+ "abcdefgh".count("he-fa-c").should == 6
+ "abcdefgh".count("e-fha-c").should == 6
+
+ "abcde".count("ac-e").should == 4
+ "abcde".count("^ac-e").should == 1
+ end
+
+ it "raises if the given sequences are invalid" do
+ s = "hel-[()]-lo012^"
+
+ -> { s.count("h-e") }.should raise_error(ArgumentError)
+ -> { s.count("^h-e") }.should raise_error(ArgumentError)
+ end
+
+ it 'returns the number of occurrences of a multi-byte character' do
+ str = "\u{2605}"
+ str.count(str).should == 1
+ "asd#{str}zzz#{str}ggg".count(str).should == 2
+ end
+
+ it "calls #to_str to convert each set arg to a String" do
+ other_string = mock('lo')
+ other_string.should_receive(:to_str).and_return("lo")
+
+ other_string2 = mock('o')
+ other_string2.should_receive(:to_str).and_return("o")
+
+ s = "hello world"
+ s.count(other_string, other_string2).should == s.count("o")
+ end
+
+ it "raises a TypeError when a set arg can't be converted to a string" do
+ -> { "hello world".count(100) }.should raise_error(TypeError)
+ -> { "hello world".count([]) }.should raise_error(TypeError)
+ -> { "hello world".count(mock('x')) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/string/crypt_spec.rb b/spec/ruby/core/string/crypt_spec.rb
new file mode 100644
index 0000000000..06f84c70a4
--- /dev/null
+++ b/spec/ruby/core/string/crypt_spec.rb
@@ -0,0 +1,92 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#crypt" do
+ platform_is :openbsd do
+ it "returns a cryptographic hash of self by applying the bcrypt algorithm with the specified salt" do
+ "mypassword".crypt("$2a$04$0WVaz0pV3jzfZ5G5tpmHWu").should == "$2a$04$0WVaz0pV3jzfZ5G5tpmHWuBQGbkjzgtSc3gJbmdy0GAGMa45MFM2."
+
+ # Only uses first 72 characters of string
+ ("12345678"*9).crypt("$2a$04$0WVaz0pV3jzfZ5G5tpmHWu").should == "$2a$04$0WVaz0pV3jzfZ5G5tpmHWukj/ORBnsMjCGpST/zCJnAypc7eAbutK"
+ ("12345678"*10).crypt("$2a$04$0WVaz0pV3jzfZ5G5tpmHWu").should == "$2a$04$0WVaz0pV3jzfZ5G5tpmHWukj/ORBnsMjCGpST/zCJnAypc7eAbutK"
+
+ # Only uses first 29 characters of salt
+ "mypassword".crypt("$2a$04$0WVaz0pV3jzfZ5G5tpmHWuB").should == "$2a$04$0WVaz0pV3jzfZ5G5tpmHWuBQGbkjzgtSc3gJbmdy0GAGMa45MFM2."
+ end
+
+ it "raises Errno::EINVAL when the salt is shorter than 29 characters" do
+ -> { "mypassword".crypt("$2a$04$0WVaz0pV3jzfZ5G5tpmHW") }.should raise_error(Errno::EINVAL)
+ end
+
+ it "calls #to_str to converts the salt arg to a String" do
+ obj = mock('$2a$04$0WVaz0pV3jzfZ5G5tpmHWu')
+ obj.should_receive(:to_str).and_return("$2a$04$0WVaz0pV3jzfZ5G5tpmHWu")
+
+ "mypassword".crypt(obj).should == "$2a$04$0WVaz0pV3jzfZ5G5tpmHWuBQGbkjzgtSc3gJbmdy0GAGMa45MFM2."
+ end
+
+ it "doesn't return subclass instances" do
+ StringSpecs::MyString.new("mypassword").crypt("$2a$04$0WVaz0pV3jzfZ5G5tpmHWu").should be_an_instance_of(String)
+ "mypassword".crypt(StringSpecs::MyString.new("$2a$04$0WVaz0pV3jzfZ5G5tpmHWu")).should be_an_instance_of(String)
+ StringSpecs::MyString.new("mypassword").crypt(StringSpecs::MyString.new("$2a$04$0WVaz0pV3jzfZ5G5tpmHWu")).should be_an_instance_of(String)
+ end
+ end
+
+ platform_is_not :openbsd do
+ # Note: MRI's documentation just says that the C stdlib function crypt() is
+ # called.
+ #
+ # I'm not sure if crypt() is guaranteed to produce the same result across
+ # different platforms. It seems that there is one standard UNIX implementation
+ # of crypt(), but that alternative implementations are possible. See
+ # http://www.unix.org.ua/orelly/networking/puis/ch08_06.htm
+ it "returns a cryptographic hash of self by applying the UNIX crypt algorithm with the specified salt" do
+ "".crypt("aa").should == "aaQSqAReePlq6"
+ "nutmeg".crypt("Mi").should == "MiqkFWCm1fNJI"
+ "ellen1".crypt("ri").should == "ri79kNd7V6.Sk"
+ "Sharon".crypt("./").should == "./UY9Q7TvYJDg"
+ "norahs".crypt("am").should == "amfIADT2iqjA."
+ "norahs".crypt("7a").should == "7azfT5tIdyh0I"
+
+ # Only uses first 8 chars of string
+ "01234567".crypt("aa").should == "aa4c4gpuvCkSE"
+ "012345678".crypt("aa").should == "aa4c4gpuvCkSE"
+ "0123456789".crypt("aa").should == "aa4c4gpuvCkSE"
+
+ # Only uses first 2 chars of salt
+ "hello world".crypt("aa").should == "aayPz4hyPS1wI"
+ "hello world".crypt("aab").should == "aayPz4hyPS1wI"
+ "hello world".crypt("aabc").should == "aayPz4hyPS1wI"
+ end
+
+ it "raises an ArgumentError when the string contains NUL character" do
+ -> { "poison\0null".crypt("aa") }.should raise_error(ArgumentError)
+ end
+
+ it "calls #to_str to converts the salt arg to a String" do
+ obj = mock('aa')
+ obj.should_receive(:to_str).and_return("aa")
+
+ "".crypt(obj).should == "aaQSqAReePlq6"
+ end
+
+ it "doesn't return subclass instances" do
+ StringSpecs::MyString.new("hello").crypt("aa").should be_an_instance_of(String)
+ "hello".crypt(StringSpecs::MyString.new("aa")).should be_an_instance_of(String)
+ StringSpecs::MyString.new("hello").crypt(StringSpecs::MyString.new("aa")).should be_an_instance_of(String)
+ end
+
+ it "raises an ArgumentError when the salt is shorter than two characters" do
+ -> { "hello".crypt("") }.should raise_error(ArgumentError)
+ -> { "hello".crypt("f") }.should raise_error(ArgumentError)
+ -> { "hello".crypt("\x00\x00") }.should raise_error(ArgumentError)
+ -> { "hello".crypt("\x00a") }.should raise_error(ArgumentError)
+ -> { "hello".crypt("a\x00") }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises a type error when the salt arg can't be converted to a string" do
+ -> { "".crypt(5) }.should raise_error(TypeError)
+ -> { "".crypt(mock('x')) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/string/dedup_spec.rb b/spec/ruby/core/string/dedup_spec.rb
new file mode 100644
index 0000000000..919d440c51
--- /dev/null
+++ b/spec/ruby/core/string/dedup_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'shared/dedup'
+
+describe 'String#dedup' do
+ ruby_version_is '3.2'do
+ it_behaves_like :string_dedup, :dedup
+ end
+end
diff --git a/spec/ruby/core/string/delete_prefix_spec.rb b/spec/ruby/core/string/delete_prefix_spec.rb
new file mode 100644
index 0000000000..238de85f05
--- /dev/null
+++ b/spec/ruby/core/string/delete_prefix_spec.rb
@@ -0,0 +1,91 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#delete_prefix" do
+ it "returns a copy of the string, with the given prefix removed" do
+ 'hello'.delete_prefix('hell').should == 'o'
+ 'hello'.delete_prefix('hello').should == ''
+ end
+
+ it "returns a copy of the string, when the prefix isn't found" do
+ s = 'hello'
+ r = s.delete_prefix('hello!')
+ r.should_not equal s
+ r.should == s
+ r = s.delete_prefix('ell')
+ r.should_not equal s
+ r.should == s
+ r = s.delete_prefix('')
+ r.should_not equal s
+ r.should == s
+ end
+
+ it "does not remove partial bytes, only full characters" do
+ "\xe3\x81\x82".delete_prefix("\xe3").should == "\xe3\x81\x82"
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+
+ 'hello'.delete_prefix('hell')
+ $~.should == nil
+ end
+
+ it "calls to_str on its argument" do
+ o = mock('x')
+ o.should_receive(:to_str).and_return 'hell'
+ 'hello'.delete_prefix(o).should == 'o'
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a subclass instance when called on a subclass instance" do
+ s = StringSpecs::MyString.new('hello')
+ s.delete_prefix('hell').should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a String instance when called on a subclass instance" do
+ s = StringSpecs::MyString.new('hello')
+ s.delete_prefix('hell').should be_an_instance_of(String)
+ end
+ end
+
+ it "returns a String in the same encoding as self" do
+ 'hello'.encode("US-ASCII").delete_prefix('hell').encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "String#delete_prefix!" do
+ it "removes the found prefix" do
+ s = 'hello'
+ s.delete_prefix!('hell').should equal(s)
+ s.should == 'o'
+ end
+
+ it "returns nil if no change is made" do
+ s = 'hello'
+ s.delete_prefix!('ell').should == nil
+ s.delete_prefix!('').should == nil
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+
+ 'hello'.delete_prefix!('hell')
+ $~.should == nil
+ end
+
+ it "calls to_str on its argument" do
+ o = mock('x')
+ o.should_receive(:to_str).and_return 'hell'
+ 'hello'.delete_prefix!(o).should == 'o'
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ -> { 'hello'.freeze.delete_prefix!('hell') }.should raise_error(FrozenError)
+ -> { 'hello'.freeze.delete_prefix!('') }.should raise_error(FrozenError)
+ -> { ''.freeze.delete_prefix!('') }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/delete_spec.rb b/spec/ruby/core/string/delete_spec.rb
new file mode 100644
index 0000000000..87831a9d19
--- /dev/null
+++ b/spec/ruby/core/string/delete_spec.rb
@@ -0,0 +1,124 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#delete" do
+ it "returns a new string with the chars from the intersection of sets removed" do
+ s = "hello"
+ s.delete("lo").should == "he"
+ s.should == "hello"
+
+ "hello".delete("l", "lo").should == "heo"
+
+ "hell yeah".delete("").should == "hell yeah"
+ end
+
+ it "raises an ArgumentError when given no arguments" do
+ -> { "hell yeah".delete }.should raise_error(ArgumentError)
+ end
+
+ it "negates sets starting with ^" do
+ "hello".delete("aeiou", "^e").should == "hell"
+ "hello".delete("^leh").should == "hell"
+ "hello".delete("^o").should == "o"
+ "hello".delete("^").should == "hello"
+ "^_^".delete("^^").should == "^^"
+ "oa^_^o".delete("a^").should == "o_o"
+ end
+
+ it "deletes all chars in a sequence" do
+ "hello".delete("ej-m").should == "ho"
+ "hello".delete("e-h").should == "llo"
+ "hel-lo".delete("e-").should == "hllo"
+ "hel-lo".delete("-h").should == "ello"
+ "hel-lo".delete("---").should == "hello"
+ "hel-012".delete("--2").should == "hel"
+ "hel-()".delete("(--").should == "hel"
+ "hello".delete("^e-h").should == "he"
+ "hello^".delete("^^-^").should == "^"
+ "hel--lo".delete("^---").should == "--"
+
+ "abcdefgh".delete("a-ce-fh").should == "dg"
+ "abcdefgh".delete("he-fa-c").should == "dg"
+ "abcdefgh".delete("e-fha-c").should == "dg"
+
+ "abcde".delete("ac-e").should == "b"
+ "abcde".delete("^ac-e").should == "acde"
+
+ "ABCabc[]".delete("A-a").should == "bc"
+ end
+
+ it "deletes multibyte characters" do
+ "四月".delete("月").should == "四"
+ '哥哥我倒'.delete('哥').should == "我倒"
+ end
+
+ it "respects backslash for escaping a -" do
+ 'Non-Authoritative Information'.delete(' \-\'').should ==
+ 'NonAuthoritativeInformation'
+ end
+
+ it "raises if the given ranges are invalid" do
+ not_supported_on :opal do
+ xFF = [0xFF].pack('C')
+ range = "\x00 - #{xFF}".force_encoding('utf-8')
+ -> { "hello".delete(range).should == "" }.should raise_error(ArgumentError)
+ end
+ -> { "hello".delete("h-e") }.should raise_error(ArgumentError)
+ -> { "hello".delete("^h-e") }.should raise_error(ArgumentError)
+ end
+
+ it "tries to convert each set arg to a string using to_str" do
+ other_string = mock('lo')
+ other_string.should_receive(:to_str).and_return("lo")
+
+ other_string2 = mock('o')
+ other_string2.should_receive(:to_str).and_return("o")
+
+ "hello world".delete(other_string, other_string2).should == "hell wrld"
+ end
+
+ it "raises a TypeError when one set arg can't be converted to a string" do
+ -> { "hello world".delete(100) }.should raise_error(TypeError)
+ -> { "hello world".delete([]) }.should raise_error(TypeError)
+ -> { "hello world".delete(mock('x')) }.should raise_error(TypeError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("oh no!!!").delete("!").should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("oh no!!!").delete("!").should be_an_instance_of(String)
+ end
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hello".encode("US-ASCII").delete("lo").encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "String#delete!" do
+ it "modifies self in place and returns self" do
+ a = "hello"
+ a.delete!("aeiou", "^e").should equal(a)
+ a.should == "hell"
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "hello"
+ a.delete!("z").should == nil
+ a.should == "hello"
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ a = "hello"
+ a.freeze
+
+ -> { a.delete!("") }.should raise_error(FrozenError)
+ -> { a.delete!("aeiou", "^e") }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/delete_suffix_spec.rb b/spec/ruby/core/string/delete_suffix_spec.rb
new file mode 100644
index 0000000000..6883d6938c
--- /dev/null
+++ b/spec/ruby/core/string/delete_suffix_spec.rb
@@ -0,0 +1,91 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#delete_suffix" do
+ it "returns a copy of the string, with the given suffix removed" do
+ 'hello'.delete_suffix('ello').should == 'h'
+ 'hello'.delete_suffix('hello').should == ''
+ end
+
+ it "returns a copy of the string, when the suffix isn't found" do
+ s = 'hello'
+ r = s.delete_suffix('!hello')
+ r.should_not equal s
+ r.should == s
+ r = s.delete_suffix('ell')
+ r.should_not equal s
+ r.should == s
+ r = s.delete_suffix('')
+ r.should_not equal s
+ r.should == s
+ end
+
+ it "does not remove partial bytes, only full characters" do
+ "\xe3\x81\x82".delete_suffix("\x82").should == "\xe3\x81\x82"
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+
+ 'hello'.delete_suffix('ello')
+ $~.should == nil
+ end
+
+ it "calls to_str on its argument" do
+ o = mock('x')
+ o.should_receive(:to_str).and_return 'ello'
+ 'hello'.delete_suffix(o).should == 'h'
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a subclass instance when called on a subclass instance" do
+ s = StringSpecs::MyString.new('hello')
+ s.delete_suffix('ello').should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a String instance when called on a subclass instance" do
+ s = StringSpecs::MyString.new('hello')
+ s.delete_suffix('ello').should be_an_instance_of(String)
+ end
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hello".encode("US-ASCII").delete_suffix("ello").encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "String#delete_suffix!" do
+ it "removes the found prefix" do
+ s = 'hello'
+ s.delete_suffix!('ello').should equal(s)
+ s.should == 'h'
+ end
+
+ it "returns nil if no change is made" do
+ s = 'hello'
+ s.delete_suffix!('ell').should == nil
+ s.delete_suffix!('').should == nil
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+
+ 'hello'.delete_suffix!('ello')
+ $~.should == nil
+ end
+
+ it "calls to_str on its argument" do
+ o = mock('x')
+ o.should_receive(:to_str).and_return 'ello'
+ 'hello'.delete_suffix!(o).should == 'h'
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ -> { 'hello'.freeze.delete_suffix!('ello') }.should raise_error(FrozenError)
+ -> { 'hello'.freeze.delete_suffix!('') }.should raise_error(FrozenError)
+ -> { ''.freeze.delete_suffix!('') }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/downcase_spec.rb b/spec/ruby/core/string/downcase_spec.rb
new file mode 100644
index 0000000000..153b4ce191
--- /dev/null
+++ b/spec/ruby/core/string/downcase_spec.rb
@@ -0,0 +1,202 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#downcase" do
+ it "returns a copy of self with all uppercase letters downcased" do
+ "hELLO".downcase.should == "hello"
+ "hello".downcase.should == "hello"
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hELLO".encode("US-ASCII").downcase.encoding.should == Encoding::US_ASCII
+ end
+
+ describe "full Unicode case mapping" do
+ it "works for all of Unicode with no option" do
+ "ÄÖÜ".downcase.should == "äöü"
+ end
+
+ it "updates string metadata" do
+ downcased = "\u{212A}ING".downcase
+
+ downcased.should == "king"
+ downcased.size.should == 4
+ downcased.bytesize.should == 4
+ downcased.ascii_only?.should be_true
+ end
+ end
+
+ describe "ASCII-only case mapping" do
+ it "does not downcase non-ASCII characters" do
+ "Câ„«R".downcase(:ascii).should == "câ„«r"
+ end
+
+ it "works with substrings" do
+ "prefix TÉ"[-2..-1].downcase(:ascii).should == "tÉ"
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Turkic languages" do
+ it "downcases characters according to Turkic semantics" do
+ "İ".downcase(:turkic).should == "i"
+ end
+
+ it "allows Lithuanian as an extra option" do
+ "İ".downcase(:turkic, :lithuanian).should == "i"
+ end
+
+ it "does not allow any other additional option" do
+ -> { "İ".downcase(:turkic, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Lithuanian" do
+ it "currently works the same as full Unicode case mapping" do
+ "İS".downcase(:lithuanian).should == "i\u{307}s"
+ end
+
+ it "allows Turkic as an extra option (and applies Turkic semantics)" do
+ "İS".downcase(:lithuanian, :turkic).should == "is"
+ end
+
+ it "does not allow any other additional option" do
+ -> { "İS".downcase(:lithuanian, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "case folding" do
+ it "case folds special characters" do
+ "ß".downcase.should == "ß"
+ "ß".downcase(:fold).should == "ss"
+ end
+ end
+
+ it "does not allow invalid options" do
+ -> { "ABC".downcase(:invalid_option) }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a subclass instance for subclasses" do
+ StringSpecs::MyString.new("FOObar").downcase.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a String instance for subclasses" do
+ StringSpecs::MyString.new("FOObar").downcase.should be_an_instance_of(String)
+ end
+ end
+end
+
+describe "String#downcase!" do
+ it "modifies self in place" do
+ a = "HeLlO"
+ a.downcase!.should equal(a)
+ a.should == "hello"
+ end
+
+ it "modifies self in place for non-ascii-compatible encodings" do
+ a = "HeLlO".encode("utf-16le")
+ a.downcase!
+ a.should == "hello".encode("utf-16le")
+ end
+
+ describe "full Unicode case mapping" do
+ it "modifies self in place for all of Unicode with no option" do
+ a = "ÄÖÜ"
+ a.downcase!
+ a.should == "äöü"
+ end
+
+ it "updates string metadata" do
+ downcased = "\u{212A}ING"
+ downcased.downcase!
+
+ downcased.should == "king"
+ downcased.size.should == 4
+ downcased.bytesize.should == 4
+ downcased.ascii_only?.should be_true
+ end
+ end
+
+ describe "ASCII-only case mapping" do
+ it "does not downcase non-ASCII characters" do
+ a = "Câ„«R"
+ a.downcase!(:ascii)
+ a.should == "câ„«r"
+ end
+
+ it "works for non-ascii-compatible encodings" do
+ a = "ABC".encode("utf-16le")
+ a.downcase!(:ascii)
+ a.should == "abc".encode("utf-16le")
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Turkic languages" do
+ it "downcases characters according to Turkic semantics" do
+ a = "İ"
+ a.downcase!(:turkic)
+ a.should == "i"
+ end
+
+ it "allows Lithuanian as an extra option" do
+ a = "İ"
+ a.downcase!(:turkic, :lithuanian)
+ a.should == "i"
+ end
+
+ it "does not allow any other additional option" do
+ -> { a = "İ"; a.downcase!(:turkic, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Lithuanian" do
+ it "currently works the same as full Unicode case mapping" do
+ a = "İS"
+ a.downcase!(:lithuanian)
+ a.should == "i\u{307}s"
+ end
+
+ it "allows Turkic as an extra option (and applies Turkic semantics)" do
+ a = "İS"
+ a.downcase!(:lithuanian, :turkic)
+ a.should == "is"
+ end
+
+ it "does not allow any other additional option" do
+ -> { a = "İS"; a.downcase!(:lithuanian, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "case folding" do
+ it "case folds special characters" do
+ a = "ß"
+ a.downcase!
+ a.should == "ß"
+
+ a.downcase!(:fold)
+ a.should == "ss"
+ end
+ end
+
+ it "does not allow invalid options" do
+ -> { a = "ABC"; a.downcase!(:invalid_option) }.should raise_error(ArgumentError)
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "hello"
+ a.downcase!.should == nil
+ a.should == "hello"
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ -> { "HeLlo".freeze.downcase! }.should raise_error(FrozenError)
+ -> { "hello".freeze.downcase! }.should raise_error(FrozenError)
+ end
+
+ it "sets the result String encoding to the source String encoding" do
+ "ABC".downcase.encoding.should equal(Encoding::UTF_8)
+ end
+end
diff --git a/spec/ruby/core/string/dump_spec.rb b/spec/ruby/core/string/dump_spec.rb
new file mode 100644
index 0000000000..81de0cfae4
--- /dev/null
+++ b/spec/ruby/core/string/dump_spec.rb
@@ -0,0 +1,404 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#dump" do
+ it "does not take into account if a string is frozen" do
+ "foo".freeze.dump.should_not.frozen?
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a subclass instance" do
+ StringSpecs::MyString.new.dump.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a String instance" do
+ StringSpecs::MyString.new.dump.should be_an_instance_of(String)
+ end
+ end
+
+ it "wraps string with \"" do
+ "foo".dump.should == '"foo"'
+ end
+
+ it "returns a string with special characters replaced with \\<char> notation" do
+ [ ["\a", '"\\a"'],
+ ["\b", '"\\b"'],
+ ["\t", '"\\t"'],
+ ["\n", '"\\n"'],
+ ["\v", '"\\v"'],
+ ["\f", '"\\f"'],
+ ["\r", '"\\r"'],
+ ["\e", '"\\e"']
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with \" and \\ escaped with a backslash" do
+ [ ["\"", '"\\""'],
+ ["\\", '"\\\\"']
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with \\#<char> when # is followed by $, @, @@, {" do
+ [ ["\#$PATH", '"\\#$PATH"'],
+ ["\#@a", '"\\#@a"'],
+ ["\#@@a", '"\\#@@a"'],
+ ["\#{a}", '"\\#{a}"']
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with # not escaped when followed by any other character" do
+ [ ["#", '"#"'],
+ ["#1", '"#1"']
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with printable non-alphanumeric characters unescaped" do
+ [ [" ", '" "'],
+ ["!", '"!"'],
+ ["$", '"$"'],
+ ["%", '"%"'],
+ ["&", '"&"'],
+ ["'", '"\'"'],
+ ["(", '"("'],
+ [")", '")"'],
+ ["*", '"*"'],
+ ["+", '"+"'],
+ [",", '","'],
+ ["-", '"-"'],
+ [".", '"."'],
+ ["/", '"/"'],
+ [":", '":"'],
+ [";", '";"'],
+ ["<", '"<"'],
+ ["=", '"="'],
+ [">", '">"'],
+ ["?", '"?"'],
+ ["@", '"@"'],
+ ["[", '"["'],
+ ["]", '"]"'],
+ ["^", '"^"'],
+ ["_", '"_"'],
+ ["`", '"`"'],
+ ["{", '"{"'],
+ ["|", '"|"'],
+ ["}", '"}"'],
+ ["~", '"~"']
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with numeric characters unescaped" do
+ [ ["0", '"0"'],
+ ["1", '"1"'],
+ ["2", '"2"'],
+ ["3", '"3"'],
+ ["4", '"4"'],
+ ["5", '"5"'],
+ ["6", '"6"'],
+ ["7", '"7"'],
+ ["8", '"8"'],
+ ["9", '"9"'],
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with upper-case alpha characters unescaped" do
+ [ ["A", '"A"'],
+ ["B", '"B"'],
+ ["C", '"C"'],
+ ["D", '"D"'],
+ ["E", '"E"'],
+ ["F", '"F"'],
+ ["G", '"G"'],
+ ["H", '"H"'],
+ ["I", '"I"'],
+ ["J", '"J"'],
+ ["K", '"K"'],
+ ["L", '"L"'],
+ ["M", '"M"'],
+ ["N", '"N"'],
+ ["O", '"O"'],
+ ["P", '"P"'],
+ ["Q", '"Q"'],
+ ["R", '"R"'],
+ ["S", '"S"'],
+ ["T", '"T"'],
+ ["U", '"U"'],
+ ["V", '"V"'],
+ ["W", '"W"'],
+ ["X", '"X"'],
+ ["Y", '"Y"'],
+ ["Z", '"Z"']
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with lower-case alpha characters unescaped" do
+ [ ["a", '"a"'],
+ ["b", '"b"'],
+ ["c", '"c"'],
+ ["d", '"d"'],
+ ["e", '"e"'],
+ ["f", '"f"'],
+ ["g", '"g"'],
+ ["h", '"h"'],
+ ["i", '"i"'],
+ ["j", '"j"'],
+ ["k", '"k"'],
+ ["l", '"l"'],
+ ["m", '"m"'],
+ ["n", '"n"'],
+ ["o", '"o"'],
+ ["p", '"p"'],
+ ["q", '"q"'],
+ ["r", '"r"'],
+ ["s", '"s"'],
+ ["t", '"t"'],
+ ["u", '"u"'],
+ ["v", '"v"'],
+ ["w", '"w"'],
+ ["x", '"x"'],
+ ["y", '"y"'],
+ ["z", '"z"']
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with non-printing ASCII characters replaced by \\x notation" do
+ # Avoid the file encoding by computing the string with #chr.
+ [ [0000.chr, '"\\x00"'],
+ [0001.chr, '"\\x01"'],
+ [0002.chr, '"\\x02"'],
+ [0003.chr, '"\\x03"'],
+ [0004.chr, '"\\x04"'],
+ [0005.chr, '"\\x05"'],
+ [0006.chr, '"\\x06"'],
+ [0016.chr, '"\\x0E"'],
+ [0017.chr, '"\\x0F"'],
+ [0020.chr, '"\\x10"'],
+ [0021.chr, '"\\x11"'],
+ [0022.chr, '"\\x12"'],
+ [0023.chr, '"\\x13"'],
+ [0024.chr, '"\\x14"'],
+ [0025.chr, '"\\x15"'],
+ [0026.chr, '"\\x16"'],
+ [0027.chr, '"\\x17"'],
+ [0030.chr, '"\\x18"'],
+ [0031.chr, '"\\x19"'],
+ [0032.chr, '"\\x1A"'],
+ [0034.chr, '"\\x1C"'],
+ [0035.chr, '"\\x1D"'],
+ [0036.chr, '"\\x1E"'],
+ [0037.chr, '"\\x1F"'],
+ [0177.chr, '"\\x7F"'],
+ [0200.chr, '"\\x80"'],
+ [0201.chr, '"\\x81"'],
+ [0202.chr, '"\\x82"'],
+ [0203.chr, '"\\x83"'],
+ [0204.chr, '"\\x84"'],
+ [0205.chr, '"\\x85"'],
+ [0206.chr, '"\\x86"'],
+ [0207.chr, '"\\x87"'],
+ [0210.chr, '"\\x88"'],
+ [0211.chr, '"\\x89"'],
+ [0212.chr, '"\\x8A"'],
+ [0213.chr, '"\\x8B"'],
+ [0214.chr, '"\\x8C"'],
+ [0215.chr, '"\\x8D"'],
+ [0216.chr, '"\\x8E"'],
+ [0217.chr, '"\\x8F"'],
+ [0220.chr, '"\\x90"'],
+ [0221.chr, '"\\x91"'],
+ [0222.chr, '"\\x92"'],
+ [0223.chr, '"\\x93"'],
+ [0224.chr, '"\\x94"'],
+ [0225.chr, '"\\x95"'],
+ [0226.chr, '"\\x96"'],
+ [0227.chr, '"\\x97"'],
+ [0230.chr, '"\\x98"'],
+ [0231.chr, '"\\x99"'],
+ [0232.chr, '"\\x9A"'],
+ [0233.chr, '"\\x9B"'],
+ [0234.chr, '"\\x9C"'],
+ [0235.chr, '"\\x9D"'],
+ [0236.chr, '"\\x9E"'],
+ [0237.chr, '"\\x9F"'],
+ [0240.chr, '"\\xA0"'],
+ [0241.chr, '"\\xA1"'],
+ [0242.chr, '"\\xA2"'],
+ [0243.chr, '"\\xA3"'],
+ [0244.chr, '"\\xA4"'],
+ [0245.chr, '"\\xA5"'],
+ [0246.chr, '"\\xA6"'],
+ [0247.chr, '"\\xA7"'],
+ [0250.chr, '"\\xA8"'],
+ [0251.chr, '"\\xA9"'],
+ [0252.chr, '"\\xAA"'],
+ [0253.chr, '"\\xAB"'],
+ [0254.chr, '"\\xAC"'],
+ [0255.chr, '"\\xAD"'],
+ [0256.chr, '"\\xAE"'],
+ [0257.chr, '"\\xAF"'],
+ [0260.chr, '"\\xB0"'],
+ [0261.chr, '"\\xB1"'],
+ [0262.chr, '"\\xB2"'],
+ [0263.chr, '"\\xB3"'],
+ [0264.chr, '"\\xB4"'],
+ [0265.chr, '"\\xB5"'],
+ [0266.chr, '"\\xB6"'],
+ [0267.chr, '"\\xB7"'],
+ [0270.chr, '"\\xB8"'],
+ [0271.chr, '"\\xB9"'],
+ [0272.chr, '"\\xBA"'],
+ [0273.chr, '"\\xBB"'],
+ [0274.chr, '"\\xBC"'],
+ [0275.chr, '"\\xBD"'],
+ [0276.chr, '"\\xBE"'],
+ [0277.chr, '"\\xBF"'],
+ [0300.chr, '"\\xC0"'],
+ [0301.chr, '"\\xC1"'],
+ [0302.chr, '"\\xC2"'],
+ [0303.chr, '"\\xC3"'],
+ [0304.chr, '"\\xC4"'],
+ [0305.chr, '"\\xC5"'],
+ [0306.chr, '"\\xC6"'],
+ [0307.chr, '"\\xC7"'],
+ [0310.chr, '"\\xC8"'],
+ [0311.chr, '"\\xC9"'],
+ [0312.chr, '"\\xCA"'],
+ [0313.chr, '"\\xCB"'],
+ [0314.chr, '"\\xCC"'],
+ [0315.chr, '"\\xCD"'],
+ [0316.chr, '"\\xCE"'],
+ [0317.chr, '"\\xCF"'],
+ [0320.chr, '"\\xD0"'],
+ [0321.chr, '"\\xD1"'],
+ [0322.chr, '"\\xD2"'],
+ [0323.chr, '"\\xD3"'],
+ [0324.chr, '"\\xD4"'],
+ [0325.chr, '"\\xD5"'],
+ [0326.chr, '"\\xD6"'],
+ [0327.chr, '"\\xD7"'],
+ [0330.chr, '"\\xD8"'],
+ [0331.chr, '"\\xD9"'],
+ [0332.chr, '"\\xDA"'],
+ [0333.chr, '"\\xDB"'],
+ [0334.chr, '"\\xDC"'],
+ [0335.chr, '"\\xDD"'],
+ [0336.chr, '"\\xDE"'],
+ [0337.chr, '"\\xDF"'],
+ [0340.chr, '"\\xE0"'],
+ [0341.chr, '"\\xE1"'],
+ [0342.chr, '"\\xE2"'],
+ [0343.chr, '"\\xE3"'],
+ [0344.chr, '"\\xE4"'],
+ [0345.chr, '"\\xE5"'],
+ [0346.chr, '"\\xE6"'],
+ [0347.chr, '"\\xE7"'],
+ [0350.chr, '"\\xE8"'],
+ [0351.chr, '"\\xE9"'],
+ [0352.chr, '"\\xEA"'],
+ [0353.chr, '"\\xEB"'],
+ [0354.chr, '"\\xEC"'],
+ [0355.chr, '"\\xED"'],
+ [0356.chr, '"\\xEE"'],
+ [0357.chr, '"\\xEF"'],
+ [0360.chr, '"\\xF0"'],
+ [0361.chr, '"\\xF1"'],
+ [0362.chr, '"\\xF2"'],
+ [0363.chr, '"\\xF3"'],
+ [0364.chr, '"\\xF4"'],
+ [0365.chr, '"\\xF5"'],
+ [0366.chr, '"\\xF6"'],
+ [0367.chr, '"\\xF7"'],
+ [0370.chr, '"\\xF8"'],
+ [0371.chr, '"\\xF9"'],
+ [0372.chr, '"\\xFA"'],
+ [0373.chr, '"\\xFB"'],
+ [0374.chr, '"\\xFC"'],
+ [0375.chr, '"\\xFD"'],
+ [0376.chr, '"\\xFE"'],
+ [0377.chr, '"\\xFF"']
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with non-printing single-byte UTF-8 characters replaced by \\x notation" do
+ [ [0000.chr('utf-8'), '"\x00"'],
+ [0001.chr('utf-8'), '"\x01"'],
+ [0002.chr('utf-8'), '"\x02"'],
+ [0003.chr('utf-8'), '"\x03"'],
+ [0004.chr('utf-8'), '"\x04"'],
+ [0005.chr('utf-8'), '"\x05"'],
+ [0006.chr('utf-8'), '"\x06"'],
+ [0016.chr('utf-8'), '"\x0E"'],
+ [0017.chr('utf-8'), '"\x0F"'],
+ [0020.chr('utf-8'), '"\x10"'],
+ [0021.chr('utf-8'), '"\x11"'],
+ [0022.chr('utf-8'), '"\x12"'],
+ [0023.chr('utf-8'), '"\x13"'],
+ [0024.chr('utf-8'), '"\x14"'],
+ [0025.chr('utf-8'), '"\x15"'],
+ [0026.chr('utf-8'), '"\x16"'],
+ [0027.chr('utf-8'), '"\x17"'],
+ [0030.chr('utf-8'), '"\x18"'],
+ [0031.chr('utf-8'), '"\x19"'],
+ [0032.chr('utf-8'), '"\x1A"'],
+ [0034.chr('utf-8'), '"\x1C"'],
+ [0035.chr('utf-8'), '"\x1D"'],
+ [0036.chr('utf-8'), '"\x1E"'],
+ [0037.chr('utf-8'), '"\x1F"'],
+ [0177.chr('utf-8'), '"\x7F"']
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with multi-byte UTF-8 characters less than or equal 0xFFFF replaced by \\uXXXX notation with upper-case hex digits" do
+ [ [0200.chr('utf-8'), '"\u0080"'],
+ [0201.chr('utf-8'), '"\u0081"'],
+ [0202.chr('utf-8'), '"\u0082"'],
+ [0203.chr('utf-8'), '"\u0083"'],
+ [0204.chr('utf-8'), '"\u0084"'],
+ [0206.chr('utf-8'), '"\u0086"'],
+ [0207.chr('utf-8'), '"\u0087"'],
+ [0210.chr('utf-8'), '"\u0088"'],
+ [0211.chr('utf-8'), '"\u0089"'],
+ [0212.chr('utf-8'), '"\u008A"'],
+ [0213.chr('utf-8'), '"\u008B"'],
+ [0214.chr('utf-8'), '"\u008C"'],
+ [0215.chr('utf-8'), '"\u008D"'],
+ [0216.chr('utf-8'), '"\u008E"'],
+ [0217.chr('utf-8'), '"\u008F"'],
+ [0220.chr('utf-8'), '"\u0090"'],
+ [0221.chr('utf-8'), '"\u0091"'],
+ [0222.chr('utf-8'), '"\u0092"'],
+ [0223.chr('utf-8'), '"\u0093"'],
+ [0224.chr('utf-8'), '"\u0094"'],
+ [0225.chr('utf-8'), '"\u0095"'],
+ [0226.chr('utf-8'), '"\u0096"'],
+ [0227.chr('utf-8'), '"\u0097"'],
+ [0230.chr('utf-8'), '"\u0098"'],
+ [0231.chr('utf-8'), '"\u0099"'],
+ [0232.chr('utf-8'), '"\u009A"'],
+ [0233.chr('utf-8'), '"\u009B"'],
+ [0234.chr('utf-8'), '"\u009C"'],
+ [0235.chr('utf-8'), '"\u009D"'],
+ [0236.chr('utf-8'), '"\u009E"'],
+ [0237.chr('utf-8'), '"\u009F"'],
+ [0177777.chr('utf-8'), '"\uFFFF"'],
+ ].should be_computed_by(:dump)
+ end
+
+ it "returns a string with multi-byte UTF-8 characters greater than 0xFFFF replaced by \\u{XXXXXX} notation with upper-case hex digits" do
+ 0x10000.chr('utf-8').dump.should == '"\u{10000}"'
+ 0x10FFFF.chr('utf-8').dump.should == '"\u{10FFFF}"'
+ end
+
+ it "includes .force_encoding(name) if the encoding isn't ASCII compatible" do
+ "\u{876}".encode('utf-16be').dump.should.end_with?(".force_encoding(\"UTF-16BE\")")
+ "\u{876}".encode('utf-16le').dump.should.end_with?(".force_encoding(\"UTF-16LE\")")
+ end
+
+ it "returns a String in the same encoding as self" do
+ "foo".encode("ISO-8859-1").dump.encoding.should == Encoding::ISO_8859_1
+ "foo".encode('windows-1251').dump.encoding.should == Encoding::Windows_1251
+ 1.chr.dump.encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/string/dup_spec.rb b/spec/ruby/core/string/dup_spec.rb
new file mode 100644
index 0000000000..73f71b8ffc
--- /dev/null
+++ b/spec/ruby/core/string/dup_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#dup" do
+ before :each do
+ ScratchPad.clear
+ @obj = StringSpecs::InitializeString.new "string"
+ end
+
+ it "calls #initialize_copy on the new instance" do
+ dup = @obj.dup
+ ScratchPad.recorded.should_not == @obj.object_id
+ ScratchPad.recorded.should == dup.object_id
+ end
+
+ it "copies instance variables" do
+ dup = @obj.dup
+ dup.ivar.should == 1
+ end
+
+ it "does not copy singleton methods" do
+ def @obj.special() :the_one end
+ dup = @obj.dup
+ -> { dup.special }.should raise_error(NameError)
+ end
+
+ it "does not copy modules included in the singleton class" do
+ class << @obj
+ include StringSpecs::StringModule
+ end
+
+ dup = @obj.dup
+ -> { dup.repr }.should raise_error(NameError)
+ end
+
+ it "does not copy constants defined in the singleton class" do
+ class << @obj
+ CLONE = :clone
+ end
+
+ dup = @obj.dup
+ -> { class << dup; CLONE; end }.should raise_error(NameError)
+ end
+
+ it "does not modify the original string when changing dupped string" do
+ orig = "string"[0..100]
+ dup = orig.dup
+ orig[0] = 'x'
+ orig.should == "xtring"
+ dup.should == "string"
+ end
+
+ it "does not modify the original setbyte-mutated string when changing dupped string" do
+ orig = "a"
+ orig.setbyte 0, "b".ord
+ copy = orig.dup
+ orig.setbyte 0, "c".ord
+ orig.should == "c"
+ copy.should == "b"
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hello".encode("US-ASCII").dup.encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/string/each_byte_spec.rb b/spec/ruby/core/string/each_byte_spec.rb
new file mode 100644
index 0000000000..e04dca807f
--- /dev/null
+++ b/spec/ruby/core/string/each_byte_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#each_byte" do
+ it "passes each byte in self to the given block" do
+ a = []
+ "hello\x00".each_byte { |c| a << c }
+ a.should == [104, 101, 108, 108, 111, 0]
+ end
+
+ it "keeps iterating from the old position (to new string end) when self changes" do
+ r = ""
+ s = "hello world"
+ s.each_byte do |c|
+ r << c
+ s.insert(0, "<>") if r.size < 3
+ end
+ r.should == "h><>hello world"
+
+ r = ""
+ s = "hello world"
+ s.each_byte { |c| s.slice!(-1); r << c }
+ r.should == "hello "
+
+ r = ""
+ s = "hello world"
+ s.each_byte { |c| s.slice!(0); r << c }
+ r.should == "hlowrd"
+
+ r = ""
+ s = "hello world"
+ s.each_byte { |c| s.slice!(0..-1); r << c }
+ r.should == "h"
+ end
+
+ it "returns self" do
+ s = "hello"
+ (s.each_byte {}).should equal(s)
+ end
+
+ describe "when no block is given" do
+ it "returns an enumerator" do
+ enum = "hello".each_byte
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == [104, 101, 108, 108, 111]
+ end
+
+ describe "returned enumerator" do
+ describe "size" do
+ it "should return the bytesize of the string" do
+ str = "hello"
+ str.each_byte.size.should == str.bytesize
+ str = "ola"
+ str.each_byte.size.should == str.bytesize
+ str = "\303\207\342\210\202\303\251\306\222g"
+ str.each_byte.size.should == str.bytesize
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/string/each_char_spec.rb b/spec/ruby/core/string/each_char_spec.rb
new file mode 100644
index 0000000000..aff98c0a5c
--- /dev/null
+++ b/spec/ruby/core/string/each_char_spec.rb
@@ -0,0 +1,7 @@
+require_relative 'shared/chars'
+require_relative 'shared/each_char_without_block'
+
+describe "String#each_char" do
+ it_behaves_like :string_chars, :each_char
+ it_behaves_like :string_each_char_without_block, :each_char
+end
diff --git a/spec/ruby/core/string/each_codepoint_spec.rb b/spec/ruby/core/string/each_codepoint_spec.rb
new file mode 100644
index 0000000000..c11cb1beae
--- /dev/null
+++ b/spec/ruby/core/string/each_codepoint_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'shared/codepoints'
+require_relative 'shared/each_codepoint_without_block'
+
+describe "String#each_codepoint" do
+ it_behaves_like :string_codepoints, :each_codepoint
+ it_behaves_like :string_each_codepoint_without_block, :each_codepoint
+end
diff --git a/spec/ruby/core/string/each_grapheme_cluster_spec.rb b/spec/ruby/core/string/each_grapheme_cluster_spec.rb
new file mode 100644
index 0000000000..b45d89ecb0
--- /dev/null
+++ b/spec/ruby/core/string/each_grapheme_cluster_spec.rb
@@ -0,0 +1,17 @@
+require_relative 'shared/chars'
+require_relative 'shared/grapheme_clusters'
+require_relative 'shared/each_char_without_block'
+
+describe "String#each_grapheme_cluster" do
+ it_behaves_like :string_chars, :each_grapheme_cluster
+ it_behaves_like :string_grapheme_clusters, :each_grapheme_cluster
+ it_behaves_like :string_each_char_without_block, :each_grapheme_cluster
+
+ ruby_version_is '3.0' do
+ it "yields String instances for subclasses" do
+ a = []
+ StringSpecs::MyString.new("abc").each_grapheme_cluster { |s| a << s.class }
+ a.should == [String, String, String]
+ end
+ end
+end
diff --git a/spec/ruby/core/string/each_line_spec.rb b/spec/ruby/core/string/each_line_spec.rb
new file mode 100644
index 0000000000..90fc920bf1
--- /dev/null
+++ b/spec/ruby/core/string/each_line_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/each_line'
+require_relative 'shared/each_line_without_block'
+
+describe "String#each_line" do
+ it_behaves_like :string_each_line, :each_line
+ it_behaves_like :string_each_line_without_block, :each_line
+end
diff --git a/spec/ruby/core/string/element_reference_spec.rb b/spec/ruby/core/string/element_reference_spec.rb
new file mode 100644
index 0000000000..f6e1750c93
--- /dev/null
+++ b/spec/ruby/core/string/element_reference_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/slice'
+
+describe "String#[]" do
+ it_behaves_like :string_slice, :[]
+end
+
+describe "String#[] with index, length" do
+ it_behaves_like :string_slice_index_length, :[]
+end
+
+describe "String#[] with Range" do
+ it_behaves_like :string_slice_range, :[]
+end
+
+describe "String#[] with Regexp" do
+ it_behaves_like :string_slice_regexp, :[]
+end
+
+describe "String#[] with Regexp, index" do
+ it_behaves_like :string_slice_regexp_index, :[]
+end
+
+describe "String#[] with Regexp, group" do
+ it_behaves_like :string_slice_regexp_group, :[]
+end
+
+describe "String#[] with String" do
+ it_behaves_like :string_slice_string, :[]
+end
+
+describe "String#[] with Symbol" do
+ it_behaves_like :string_slice_symbol, :[]
+end
diff --git a/spec/ruby/core/string/element_set_spec.rb b/spec/ruby/core/string/element_set_spec.rb
new file mode 100644
index 0000000000..fa041fa31d
--- /dev/null
+++ b/spec/ruby/core/string/element_set_spec.rb
@@ -0,0 +1,588 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# TODO: Add missing String#[]= specs:
+# String#[re, idx] = obj
+
+describe "String#[]= with Integer index" do
+ it "replaces the char at idx with other_str" do
+ a = "hello"
+ a[0] = "bam"
+ a.should == "bamello"
+ a[-2] = ""
+ a.should == "bamelo"
+ end
+
+ it "raises an IndexError without changing self if idx is outside of self" do
+ str = "hello"
+
+ -> { str[20] = "bam" }.should raise_error(IndexError)
+ str.should == "hello"
+
+ -> { str[-20] = "bam" }.should raise_error(IndexError)
+ str.should == "hello"
+
+ -> { ""[-1] = "bam" }.should raise_error(IndexError)
+ end
+
+ # Behaviour is verified by matz in
+ # http://redmine.ruby-lang.org/issues/show/1750
+ it "allows assignment to the zero'th element of an empty String" do
+ str = ""
+ str[0] = "bam"
+ str.should == "bam"
+ end
+
+ it "raises IndexError if the string index doesn't match a position in the string" do
+ str = "hello"
+ -> { str['y'] = "bam" }.should raise_error(IndexError)
+ str.should == "hello"
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ a = "hello"
+ a.freeze
+
+ -> { a[0] = "bam" }.should raise_error(FrozenError)
+ end
+
+ it "calls to_int on index" do
+ str = "hello"
+ str[0.5] = "hi "
+ str.should == "hi ello"
+
+ obj = mock('-1')
+ obj.should_receive(:to_int).and_return(-1)
+ str[obj] = "!"
+ str.should == "hi ell!"
+ end
+
+ it "calls #to_str to convert other to a String" do
+ other_str = mock('-test-')
+ other_str.should_receive(:to_str).and_return("-test-")
+
+ a = "abc"
+ a[1] = other_str
+ a.should == "a-test-c"
+ end
+
+ it "raises a TypeError if other_str can't be converted to a String" do
+ -> { "test"[1] = [] }.should raise_error(TypeError)
+ -> { "test"[1] = mock('x') }.should raise_error(TypeError)
+ -> { "test"[1] = nil }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an Integer replacement" do
+ -> { "abc"[1] = 65 }.should raise_error(TypeError)
+ end
+
+ it "raises an IndexError if the index is greater than character size" do
+ -> { "ã‚れ"[4] = "a" }.should raise_error(IndexError)
+ end
+
+ it "calls #to_int to convert the index" do
+ index = mock("string element set")
+ index.should_receive(:to_int).and_return(1)
+
+ str = "ã‚れ"
+ str[index] = "a"
+ str.should == "ã‚a"
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ index = mock("string element set")
+ index.should_receive(:to_int).and_return('1')
+
+ -> { "abc"[index] = "d" }.should raise_error(TypeError)
+ end
+
+ it "raises an IndexError if #to_int returns a value out of range" do
+ index = mock("string element set")
+ index.should_receive(:to_int).and_return(4)
+
+ -> { "ab"[index] = "c" }.should raise_error(IndexError)
+ end
+
+ it "replaces a character with a multibyte character" do
+ str = "ã‚りãŒã¨u"
+ str[4] = "ã†"
+ str.should == "ã‚りãŒã¨ã†"
+ end
+
+ it "replaces a multibyte character with a character" do
+ str = "ã‚りãŒã¨ã†"
+ str[4] = "u"
+ str.should == "ã‚りãŒã¨u"
+ end
+
+ it "replaces a multibyte character with a multibyte character" do
+ str = "ã‚りãŒã¨ãŠ"
+ str[4] = "ã†"
+ str.should == "ã‚りãŒã¨ã†"
+ end
+
+ it "encodes the String in an encoding compatible with the replacement" do
+ str = " ".force_encoding Encoding::US_ASCII
+ rep = [160].pack('C').force_encoding Encoding::BINARY
+ str[0] = rep
+ str.encoding.should equal(Encoding::BINARY)
+ end
+
+ it "updates the string to a compatible encoding" do
+ str = " "
+ str[1] = [0xB9].pack("C*")
+ str.encoding.should == Encoding::ASCII_8BIT
+ end
+
+ it "raises an Encoding::CompatibilityError if the replacement encoding is incompatible" do
+ str = "ã‚れ"
+ rep = "ãŒ".encode Encoding::EUC_JP
+ -> { str[0] = rep }.should raise_error(Encoding::CompatibilityError)
+ end
+end
+
+describe "String#[]= with String index" do
+ it "replaces fewer characters with more characters" do
+ str = "abcde"
+ str["cd"] = "ghi"
+ str.should == "abghie"
+ end
+
+ it "replaces more characters with fewer characters" do
+ str = "abcde"
+ str["bcd"] = "f"
+ str.should == "afe"
+ end
+
+ it "replaces characters with no characters" do
+ str = "abcde"
+ str["cd"] = ""
+ str.should == "abe"
+ end
+
+ it "raises an IndexError if the search String is not found" do
+ str = "abcde"
+ -> { str["g"] = "h" }.should raise_error(IndexError)
+ end
+
+ it "replaces characters with a multibyte character" do
+ str = "ã‚りgaã¨ã†"
+ str["ga"] = "ãŒ"
+ str.should == "ã‚りãŒã¨ã†"
+ end
+
+ it "replaces multibyte characters with characters" do
+ str = "ã‚りãŒã¨ã†"
+ str["ãŒ"] = "ga"
+ str.should == "ã‚りgaã¨ã†"
+ end
+
+ it "replaces multibyte characters with multibyte characters" do
+ str = "ã‚りãŒã¨ã†"
+ str["ãŒ"] = "ã‹"
+ str.should == "ã‚りã‹ã¨ã†"
+ end
+
+ it "encodes the String in an encoding compatible with the replacement" do
+ str = " ".force_encoding Encoding::US_ASCII
+ rep = [160].pack('C').force_encoding Encoding::BINARY
+ str[" "] = rep
+ str.encoding.should equal(Encoding::BINARY)
+ end
+
+ it "raises an Encoding::CompatibilityError if the replacement encoding is incompatible" do
+ str = "ã‚れ"
+ rep = "ãŒ".encode Encoding::EUC_JP
+ -> { str["れ"] = rep }.should raise_error(Encoding::CompatibilityError)
+ end
+end
+
+describe "String#[]= with a Regexp index" do
+ it "replaces the matched text with the rhs" do
+ str = "hello"
+ str[/lo/] = "x"
+ str.should == "helx"
+ end
+
+ it "raises IndexError if the regexp index doesn't match a position in the string" do
+ str = "hello"
+ -> { str[/y/] = "bam" }.should raise_error(IndexError)
+ str.should == "hello"
+ end
+
+ it "calls #to_str to convert the replacement" do
+ rep = mock("string element set regexp")
+ rep.should_receive(:to_str).and_return("b")
+
+ str = "abc"
+ str[/ab/] = rep
+ str.should == "bc"
+ end
+
+ it "checks the match before calling #to_str to convert the replacement" do
+ rep = mock("string element set regexp")
+ rep.should_not_receive(:to_str)
+
+ -> { "abc"[/def/] = rep }.should raise_error(IndexError)
+ end
+
+ describe "with 3 arguments" do
+ it "calls #to_int to convert the second object" do
+ ref = mock("string element set regexp ref")
+ ref.should_receive(:to_int).and_return(1)
+
+ str = "abc"
+ str[/a(b)/, ref] = "x"
+ str.should == "axc"
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ ref = mock("string element set regexp ref")
+ ref.should_receive(:to_int).and_return(nil)
+
+ -> { "abc"[/a(b)/, ref] = "x" }.should raise_error(TypeError)
+ end
+
+ it "uses the 2nd of 3 arguments as which capture should be replaced" do
+ str = "aaa bbb ccc"
+ str[/a (bbb) c/, 1] = "ddd"
+ str.should == "aaa ddd ccc"
+ end
+
+ it "allows the specified capture to be negative and count from the end" do
+ str = "abcd"
+ str[/(a)(b)(c)(d)/, -2] = "e"
+ str.should == "abed"
+ end
+
+ it "checks the match index before calling #to_str to convert the replacement" do
+ rep = mock("string element set regexp")
+ rep.should_not_receive(:to_str)
+
+ -> { "abc"[/a(b)/, 2] = rep }.should raise_error(IndexError)
+ end
+
+ it "raises IndexError if the specified capture isn't available" do
+ str = "aaa bbb ccc"
+ -> { str[/a (bbb) c/, 2] = "ddd" }.should raise_error(IndexError)
+ -> { str[/a (bbb) c/, -2] = "ddd" }.should raise_error(IndexError)
+ end
+
+ describe "when the optional capture does not match" do
+ it "raises an IndexError before setting the replacement" do
+ str1 = "a b c"
+ str2 = str1.dup
+ -> { str2[/a (b) (Z)?/, 2] = "d" }.should raise_error(IndexError)
+ str2.should == str1
+ end
+ end
+ end
+
+ it "replaces characters with a multibyte character" do
+ str = "ã‚りgaã¨ã†"
+ str[/ga/] = "ãŒ"
+ str.should == "ã‚りãŒã¨ã†"
+ end
+
+ it "replaces multibyte characters with characters" do
+ str = "ã‚りãŒã¨ã†"
+ str[/ãŒ/] = "ga"
+ str.should == "ã‚りgaã¨ã†"
+ end
+
+ it "replaces multibyte characters with multibyte characters" do
+ str = "ã‚りãŒã¨ã†"
+ str[/ãŒ/] = "ã‹"
+ str.should == "ã‚りã‹ã¨ã†"
+ end
+
+ it "encodes the String in an encoding compatible with the replacement" do
+ str = " ".force_encoding Encoding::US_ASCII
+ rep = [160].pack('C').force_encoding Encoding::BINARY
+ str[/ /] = rep
+ str.encoding.should equal(Encoding::BINARY)
+ end
+
+ it "raises an Encoding::CompatibilityError if the replacement encoding is incompatible" do
+ str = "ã‚れ"
+ rep = "ãŒ".encode Encoding::EUC_JP
+ -> { str[/れ/] = rep }.should raise_error(Encoding::CompatibilityError)
+ end
+end
+
+describe "String#[]= with a Range index" do
+ describe "with an empty replacement" do
+ it "does not replace a character with a zero-index, zero exclude-end range" do
+ str = "abc"
+ str[0...0] = ""
+ str.should == "abc"
+ end
+
+ it "does not replace a character with a zero exclude-end range" do
+ str = "abc"
+ str[1...1] = ""
+ str.should == "abc"
+ end
+
+ it "replaces a character with zero-index, zero non-exclude-end range" do
+ str = "abc"
+ str[0..0] = ""
+ str.should == "bc"
+ end
+
+ it "replaces a character with a zero non-exclude-end range" do
+ str = "abc"
+ str[1..1] = ""
+ str.should == "ac"
+ end
+ end
+
+ it "replaces the contents with a shorter String" do
+ str = "abcde"
+ str[0..-1] = "hg"
+ str.should == "hg"
+ end
+
+ it "replaces the contents with a longer String" do
+ str = "abc"
+ str[0...4] = "uvwxyz"
+ str.should == "uvwxyz"
+ end
+
+ it "replaces a partial string" do
+ str = "abcde"
+ str[1..3] = "B"
+ str.should == "aBe"
+ end
+
+ it "raises a RangeError if negative Range begin is out of range" do
+ -> { "abc"[-4..-2] = "x" }.should raise_error(RangeError, "-4..-2 out of range")
+ end
+
+ it "raises a RangeError if positive Range begin is greater than String size" do
+ -> { "abc"[4..2] = "x" }.should raise_error(RangeError, "4..2 out of range")
+ end
+
+ it "uses the Range end as an index rather than a count" do
+ str = "abcdefg"
+ str[-5..3] = "xyz"
+ str.should == "abxyzefg"
+ end
+
+ it "treats a negative out-of-range Range end with a positive Range begin as a zero count" do
+ str = "abc"
+ str[1..-4] = "x"
+ str.should == "axbc"
+ end
+
+ it "treats a negative out-of-range Range end with a negative Range begin as a zero count" do
+ str = "abcd"
+ str[-1..-4] = "x"
+ str.should == "abcxd"
+ end
+
+ it "replaces characters with a multibyte character" do
+ str = "ã‚りgaã¨ã†"
+ str[2..3] = "ãŒ"
+ str.should == "ã‚りãŒã¨ã†"
+ end
+
+ it "replaces multibyte characters with characters" do
+ str = "ã‚りãŒã¨ã†"
+ str[2...3] = "ga"
+ str.should == "ã‚りgaã¨ã†"
+ end
+
+ it "replaces multibyte characters by negative indexes" do
+ str = "ã‚りãŒã¨ã†"
+ str[-3...-2] = "ga"
+ str.should == "ã‚りgaã¨ã†"
+ end
+
+ it "replaces multibyte characters with multibyte characters" do
+ str = "ã‚りãŒã¨ã†"
+ str[2..2] = "ã‹"
+ str.should == "ã‚りã‹ã¨ã†"
+ end
+
+ it "deletes a multibyte character" do
+ str = "ã‚りã¨ã†"
+ str[2..3] = ""
+ str.should == "ã‚り"
+ end
+
+ it "inserts a multibyte character" do
+ str = "ã‚りã¨ã†"
+ str[2...2] = "ãŒ"
+ str.should == "ã‚りãŒã¨ã†"
+ end
+
+ it "encodes the String in an encoding compatible with the replacement" do
+ str = " ".force_encoding Encoding::US_ASCII
+ rep = [160].pack('C').force_encoding Encoding::BINARY
+ str[0..1] = rep
+ str.encoding.should equal(Encoding::BINARY)
+ end
+
+ it "raises an Encoding::CompatibilityError if the replacement encoding is incompatible" do
+ str = "ã‚れ"
+ rep = "ãŒ".encode Encoding::EUC_JP
+ -> { str[0..1] = rep }.should raise_error(Encoding::CompatibilityError)
+ end
+end
+
+describe "String#[]= with Integer index, count" do
+ it "starts at idx and overwrites count characters before inserting the rest of other_str" do
+ a = "hello"
+ a[0, 2] = "xx"
+ a.should == "xxllo"
+ a = "hello"
+ a[0, 2] = "jello"
+ a.should == "jellollo"
+ end
+
+ it "counts negative idx values from end of the string" do
+ a = "hello"
+ a[-1, 0] = "bob"
+ a.should == "hellbobo"
+ a = "hello"
+ a[-5, 0] = "bob"
+ a.should == "bobhello"
+ end
+
+ it "overwrites and deletes characters if count is more than the length of other_str" do
+ a = "hello"
+ a[0, 4] = "x"
+ a.should == "xo"
+ a = "hello"
+ a[0, 5] = "x"
+ a.should == "x"
+ end
+
+ it "deletes characters if other_str is an empty string" do
+ a = "hello"
+ a[0, 2] = ""
+ a.should == "llo"
+ end
+
+ it "deletes characters up to the maximum length of the existing string" do
+ a = "hello"
+ a[0, 6] = "x"
+ a.should == "x"
+ a = "hello"
+ a[0, 100] = ""
+ a.should == ""
+ end
+
+ it "appends other_str to the end of the string if idx == the length of the string" do
+ a = "hello"
+ a[5, 0] = "bob"
+ a.should == "hellobob"
+ end
+
+ it "calls #to_int to convert the index and count objects" do
+ index = mock("string element set index")
+ index.should_receive(:to_int).and_return(-4)
+
+ count = mock("string element set count")
+ count.should_receive(:to_int).and_return(2)
+
+ str = "abcde"
+ str[index, count] = "xyz"
+ str.should == "axyzde"
+ end
+
+ it "raises a TypeError if #to_int for index does not return an Integer" do
+ index = mock("string element set index")
+ index.should_receive(:to_int).and_return("1")
+
+ -> { "abc"[index, 2] = "xyz" }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if #to_int for count does not return an Integer" do
+ count = mock("string element set count")
+ count.should_receive(:to_int).and_return("1")
+
+ -> { "abc"[1, count] = "xyz" }.should raise_error(TypeError)
+ end
+
+ it "calls #to_str to convert the replacement object" do
+ r = mock("string element set replacement")
+ r.should_receive(:to_str).and_return("xyz")
+
+ str = "abcde"
+ str[2, 2] = r
+ str.should == "abxyze"
+ end
+
+ it "raises a TypeError of #to_str does not return a String" do
+ r = mock("string element set replacement")
+ r.should_receive(:to_str).and_return(nil)
+
+ -> { "abc"[1, 1] = r }.should raise_error(TypeError)
+ end
+
+ it "raises an IndexError if |idx| is greater than the length of the string" do
+ -> { "hello"[6, 0] = "bob" }.should raise_error(IndexError)
+ -> { "hello"[-6, 0] = "bob" }.should raise_error(IndexError)
+ end
+
+ it "raises an IndexError if count < 0" do
+ -> { "hello"[0, -1] = "bob" }.should raise_error(IndexError)
+ -> { "hello"[1, -1] = "bob" }.should raise_error(IndexError)
+ end
+
+ it "raises a TypeError if other_str is a type other than String" do
+ -> { "hello"[0, 2] = nil }.should raise_error(TypeError)
+ -> { "hello"[0, 2] = [] }.should raise_error(TypeError)
+ -> { "hello"[0, 2] = 33 }.should raise_error(TypeError)
+ end
+
+ it "replaces characters with a multibyte character" do
+ str = "ã‚りgaã¨ã†"
+ str[2, 2] = "ãŒ"
+ str.should == "ã‚りãŒã¨ã†"
+ end
+
+ it "replaces multibyte characters with characters" do
+ str = "ã‚りãŒã¨ã†"
+ str[2, 1] = "ga"
+ str.should == "ã‚りgaã¨ã†"
+ end
+
+ it "replaces multibyte characters with multibyte characters" do
+ str = "ã‚りãŒã¨ã†"
+ str[2, 1] = "ã‹"
+ str.should == "ã‚りã‹ã¨ã†"
+ end
+
+ it "deletes a multibyte character" do
+ str = "ã‚りã¨ã†"
+ str[2, 2] = ""
+ str.should == "ã‚り"
+ end
+
+ it "inserts a multibyte character" do
+ str = "ã‚りã¨ã†"
+ str[2, 0] = "ãŒ"
+ str.should == "ã‚りãŒã¨ã†"
+ end
+
+ it "raises an IndexError if the character index is out of range of a multibyte String" do
+ -> { "ã‚れ"[3, 0] = "り" }.should raise_error(IndexError)
+ end
+
+ it "encodes the String in an encoding compatible with the replacement" do
+ str = " ".force_encoding Encoding::US_ASCII
+ rep = [160].pack('C').force_encoding Encoding::BINARY
+ str[0, 1] = rep
+ str.encoding.should equal(Encoding::BINARY)
+ end
+
+ it "raises an Encoding::CompatibilityError if the replacement encoding is incompatible" do
+ str = "ã‚れ"
+ rep = "ãŒ".encode Encoding::EUC_JP
+ -> { str[0, 1] = rep }.should raise_error(Encoding::CompatibilityError)
+ end
+end
diff --git a/spec/ruby/core/string/empty_spec.rb b/spec/ruby/core/string/empty_spec.rb
new file mode 100644
index 0000000000..8e53a16afc
--- /dev/null
+++ b/spec/ruby/core/string/empty_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#empty?" do
+ it "returns true if the string has a length of zero" do
+ "hello".should_not.empty?
+ " ".should_not.empty?
+ "\x00".should_not.empty?
+ "".should.empty?
+ StringSpecs::MyString.new("").should.empty?
+ end
+end
diff --git a/spec/ruby/core/string/encode_spec.rb b/spec/ruby/core/string/encode_spec.rb
new file mode 100644
index 0000000000..5604ab7210
--- /dev/null
+++ b/spec/ruby/core/string/encode_spec.rb
@@ -0,0 +1,226 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'shared/encode'
+
+describe "String#encode" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it_behaves_like :string_encode, :encode
+
+ describe "when passed no options" do
+ it "returns a copy when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ str = "ã‚"
+ encoded = str.encode
+ encoded.should_not equal(str)
+ encoded.should == str
+ end
+
+ it "returns a copy for a ASCII-only String when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ str = "abc"
+ encoded = str.encode
+ encoded.should_not equal(str)
+ encoded.should == str
+ end
+
+ it "encodes an ascii substring of a binary string to UTF-8" do
+ x82 = [0x82].pack('C')
+ str = "#{x82}foo".force_encoding("binary")[1..-1].encode("utf-8")
+ str.should == "foo".force_encoding("utf-8")
+ str.encoding.should equal(Encoding::UTF_8)
+ end
+ end
+
+ describe "when passed to encoding" do
+ it "returns a copy when passed the same encoding as the String" do
+ str = "ã‚"
+ encoded = str.encode(Encoding::UTF_8)
+ encoded.should_not equal(str)
+ encoded.should == str
+ end
+
+ it "round trips a String" do
+ str = "abc def".force_encoding Encoding::US_ASCII
+ str.encode("utf-32be").encode("ascii").should == "abc def"
+ end
+ end
+
+ describe "when passed options" do
+ it "returns a copy when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ str = "ã‚"
+ str.encode(invalid: :replace).should_not equal(str)
+ end
+
+ it "normalizes newlines" do
+ "\r\nfoo".encode(universal_newline: true).should == "\nfoo"
+
+ "\rfoo".encode(universal_newline: true).should == "\nfoo"
+ end
+
+ it "replaces invalid encoding in source with default replacement" do
+ encoded = "ã¡\xE3\x81\xFF".encode("UTF-16LE", invalid: :replace)
+ encoded.should == "\u3061\ufffd\ufffd".encode("UTF-16LE")
+ encoded.encode("UTF-8").should == "ã¡\ufffd\ufffd"
+ end
+
+ it "replaces invalid encoding in source with a specified replacement" do
+ encoded = "ã¡\xE3\x81\xFF".encode("UTF-16LE", invalid: :replace, replace: "foo")
+ encoded.should == "\u3061foofoo".encode("UTF-16LE")
+ encoded.encode("UTF-8").should == "ã¡foofoo"
+ end
+
+ it "replaces invalid encoding in source using a specified replacement even when a fallback is given" do
+ encoded = "ã¡\xE3\x81\xFF".encode("UTF-16LE", invalid: :replace, replace: "foo", fallback: -> c { "bar" })
+ encoded.should == "\u3061foofoo".encode("UTF-16LE")
+ encoded.encode("UTF-8").should == "ã¡foofoo"
+ end
+
+ it "replaces undefined encoding in destination with default replacement" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, undef: :replace)
+ encoded.should == "B?".encode(Encoding::US_ASCII)
+ encoded.encode("UTF-8").should == "B?"
+ end
+
+ it "replaces undefined encoding in destination with a specified replacement" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, undef: :replace, replace: "foo")
+ encoded.should == "Bfoo".encode(Encoding::US_ASCII)
+ encoded.encode("UTF-8").should == "Bfoo"
+ end
+
+ it "replaces undefined encoding in destination with a specified replacement even if a fallback is given" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, undef: :replace, replace: "foo", fallback: proc {|x| "bar"})
+ encoded.should == "Bfoo".encode(Encoding::US_ASCII)
+ encoded.encode("UTF-8").should == "Bfoo"
+ end
+
+ it "replaces undefined encoding in destination using a fallback proc" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: proc {|x| "bar"})
+ encoded.should == "Bbar".encode(Encoding::US_ASCII)
+ encoded.encode("UTF-8").should == "Bbar"
+ end
+
+ it "replaces invalid encoding in source using replace even when fallback is given as proc" do
+ encoded = "ã¡\xE3\x81\xFF".encode("UTF-16LE", invalid: :replace, replace: "foo", fallback: proc {|x| "bar"})
+ encoded.should == "\u3061foofoo".encode("UTF-16LE")
+ encoded.encode("UTF-8").should == "ã¡foofoo"
+ end
+ end
+
+ describe "when passed to, from" do
+ it "returns a copy in the destination encoding when both encodings are the same" do
+ str = "ã‚"
+ str.force_encoding("binary")
+ encoded = str.encode("utf-8", "utf-8")
+
+ encoded.should_not equal(str)
+ encoded.should == str.force_encoding("utf-8")
+ encoded.encoding.should == Encoding::UTF_8
+ end
+
+ it "returns the transcoded string" do
+ str = "\x00\x00\x00\x1F"
+ str.encode(Encoding::UTF_8, Encoding::UTF_16BE).should == "\u0000\u001f"
+ end
+ end
+
+ describe "when passed to, options" do
+ it "returns a copy when the destination encoding is the same as the String encoding" do
+ str = "ã‚"
+ encoded = str.encode(Encoding::UTF_8, undef: :replace)
+ encoded.should_not equal(str)
+ encoded.should == str
+ end
+ end
+
+ describe "when passed to, from, options" do
+ it "returns a copy when both encodings are the same" do
+ str = "ã‚"
+ encoded = str.encode("utf-8", "utf-8", invalid: :replace)
+ encoded.should_not equal(str)
+ encoded.should == str
+ end
+
+ it "returns a copy in the destination encoding when both encodings are the same" do
+ str = "ã‚"
+ str.force_encoding("binary")
+ encoded = str.encode("utf-8", "utf-8", invalid: :replace)
+
+ encoded.should_not equal(str)
+ encoded.should == str.force_encoding("utf-8")
+ encoded.encoding.should == Encoding::UTF_8
+ end
+ end
+end
+
+describe "String#encode!" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it_behaves_like :string_encode, :encode!
+
+ it "raises a FrozenError when called on a frozen String" do
+ -> { "foo".freeze.encode!("euc-jp") }.should raise_error(FrozenError)
+ end
+
+ # http://redmine.ruby-lang.org/issues/show/1836
+ it "raises a FrozenError when called on a frozen String when it's a no-op" do
+ -> { "foo".freeze.encode!("utf-8") }.should raise_error(FrozenError)
+ end
+
+ describe "when passed no options" do
+ it "returns self when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ str = "ã‚"
+ str.encode!.should equal(str)
+ end
+
+ it "returns self for a ASCII-only String when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ str = "abc"
+ str.encode!.should equal(str)
+ end
+ end
+
+ describe "when passed options" do
+ it "returns self for ASCII-only String when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ str = "abc"
+ str.encode!(invalid: :replace).should equal(str)
+ end
+ end
+
+ describe "when passed to encoding" do
+ it "returns self" do
+ str = "abc"
+ result = str.encode!(Encoding::BINARY)
+ result.encoding.should equal(Encoding::BINARY)
+ result.should equal(str)
+ end
+ end
+
+ describe "when passed to, from" do
+ it "returns self" do
+ str = "ã‚ã‚"
+ result = str.encode!("euc-jp", "utf-8")
+ result.encoding.should equal(Encoding::EUC_JP)
+ result.should equal(str)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/encoding_spec.rb b/spec/ruby/core/string/encoding_spec.rb
new file mode 100644
index 0000000000..574a1e2f92
--- /dev/null
+++ b/spec/ruby/core/string/encoding_spec.rb
@@ -0,0 +1,188 @@
+# -*- encoding: us-ascii -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/iso-8859-9-encoding'
+
+describe "String#encoding" do
+ it "returns an Encoding object" do
+ String.new.encoding.should be_an_instance_of(Encoding)
+ end
+
+ it "is equal to the source encoding by default" do
+ s = StringSpecs::ISO88599Encoding.new
+ s.cedilla.encoding.should == s.source_encoding
+ s.cedilla.encode("utf-8").should == 350.chr(Encoding::UTF_8) # S-cedilla
+ end
+
+ it "returns the given encoding if #force_encoding has been called" do
+ "a".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ end
+
+ it "returns the given encoding if #encode!has been called" do
+ "a".encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ end
+end
+
+describe "String#encoding for US-ASCII Strings" do
+ it "returns US-ASCII if self is US-ASCII" do
+ "a".encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns US-ASCII if self is US-ASCII only, despite the default internal encoding being different" do
+ default_internal = Encoding.default_internal
+ Encoding.default_internal = Encoding::UTF_8
+ "a".encoding.should == Encoding::US_ASCII
+ Encoding.default_internal = default_internal
+ end
+
+ it "returns US-ASCII if self is US-ASCII only, despite the default external encoding being different" do
+ default_external = Encoding.default_external
+ Encoding.default_external = Encoding::UTF_8
+ "a".encoding.should == Encoding::US_ASCII
+ Encoding.default_external = default_external
+ end
+
+ it "returns US-ASCII if self is US-ASCII only, despite the default internal and external encodings being different" do
+ default_internal = Encoding.default_internal
+ default_external = Encoding.default_external
+ Encoding.default_internal = Encoding::UTF_8
+ Encoding.default_external = Encoding::UTF_8
+ "a".encoding.should == Encoding::US_ASCII
+ Encoding.default_external = default_external
+ Encoding.default_internal = default_internal
+ end
+
+ it "returns US-ASCII if self is US-ASCII only, despite the default encodings being different" do
+ default_internal = Encoding.default_internal
+ default_external = Encoding.default_external
+ Encoding.default_internal = Encoding::UTF_8
+ Encoding.default_external = Encoding::UTF_8
+ "a".encoding.should == Encoding::US_ASCII
+ Encoding.default_external = default_external
+ Encoding.default_internal = default_internal
+ end
+
+end
+
+describe "String#encoding for Strings with \\u escapes" do
+ it "returns UTF-8" do
+ "\u{4040}".encoding.should == Encoding::UTF_8
+ end
+
+ it "returns US-ASCII if self is US-ASCII only" do
+ s = "\u{40}"
+ s.ascii_only?.should be_true
+ s.encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns UTF-8 if self isn't US-ASCII only" do
+ s = "\u{4076}\u{619}"
+ s.ascii_only?.should be_false
+ s.encoding.should == Encoding::UTF_8
+ end
+
+ it "is not affected by the default internal encoding" do
+ default_internal = Encoding.default_internal
+ Encoding.default_internal = Encoding::ISO_8859_15
+ "\u{5050}".encoding.should == Encoding::UTF_8
+ "\u{50}".encoding.should == Encoding::US_ASCII
+ Encoding.default_internal = default_internal
+ end
+
+ it "is not affected by the default external encoding" do
+ default_external = Encoding.default_external
+ Encoding.default_external = Encoding::SHIFT_JIS
+ "\u{50}".encoding.should == Encoding::US_ASCII
+ "\u{5050}".encoding.should == Encoding::UTF_8
+ Encoding.default_external = default_external
+ end
+
+ it "is not affected by both the default internal and external encoding being set at the same time" do
+ default_internal = Encoding.default_internal
+ default_external = Encoding.default_external
+ Encoding.default_internal = Encoding::EUC_JP
+ Encoding.default_external = Encoding::SHIFT_JIS
+ "\u{50}".encoding.should == Encoding::US_ASCII
+ "\u{507}".encoding.should == Encoding::UTF_8
+ Encoding.default_external = default_external
+ Encoding.default_internal = default_internal
+ end
+
+ it "returns the given encoding if #force_encoding has been called" do
+ "\u{20}".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ "\u{2020}".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ end
+
+ it "returns the given encoding if #encode!has been called" do
+ "\u{20}".encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ "\u{2020}".encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ end
+end
+
+describe "String#encoding for Strings with \\x escapes" do
+
+ it "returns US-ASCII if self is US-ASCII only" do
+ s = "\x61"
+ s.ascii_only?.should be_true
+ s.encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns BINARY when an escape creates a byte with the 8th bit set if the source encoding is US-ASCII" do
+ __ENCODING__.should == Encoding::US_ASCII
+ str = " "
+ str.encoding.should == Encoding::US_ASCII
+ str += [0xDF].pack('C')
+ str.ascii_only?.should be_false
+ str.encoding.should == Encoding::BINARY
+ end
+
+ # TODO: Deal with case when the byte in question isn't valid in the source
+ # encoding?
+ it "returns the source encoding when an escape creates a byte with the 8th bit set if the source encoding isn't US-ASCII" do
+ fixture = StringSpecs::ISO88599Encoding.new
+ fixture.source_encoding.should == Encoding::ISO8859_9
+ fixture.x_escape.ascii_only?.should be_false
+ fixture.x_escape.encoding.should == Encoding::ISO8859_9
+ end
+
+ it "is not affected by the default internal encoding" do
+ default_internal = Encoding.default_internal
+ Encoding.default_internal = Encoding::ISO_8859_15
+ "\x50".encoding.should == Encoding::US_ASCII
+ "\x50".encoding.should == Encoding::US_ASCII
+ Encoding.default_internal = default_internal
+ end
+
+ it "is not affected by the default external encoding" do
+ default_external = Encoding.default_external
+ Encoding.default_external = Encoding::SHIFT_JIS
+ "\x50".encoding.should == Encoding::US_ASCII
+ [0xD4].pack('C').encoding.should == Encoding::BINARY
+ Encoding.default_external = default_external
+ end
+
+ it "is not affected by both the default internal and external encoding being set at the same time" do
+ default_internal = Encoding.default_internal
+ default_external = Encoding.default_external
+ Encoding.default_internal = Encoding::EUC_JP
+ Encoding.default_external = Encoding::SHIFT_JIS
+ x50 = "\x50"
+ x50.encoding.should == Encoding::US_ASCII
+ [0xD4].pack('C').encoding.should == Encoding::BINARY
+ Encoding.default_external = default_external
+ Encoding.default_internal = default_internal
+ end
+
+ it "returns the given encoding if #force_encoding has been called" do
+ x50 = "\x50"
+ x50.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ xD4 = [212].pack('C')
+ xD4.force_encoding(Encoding::ISO_8859_9).encoding.should == Encoding::ISO_8859_9
+ end
+
+ it "returns the given encoding if #encode!has been called" do
+ x50 = "\x50"
+ x50.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ x00 = "x\00"
+ x00.encode!(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ end
+end
diff --git a/spec/ruby/core/string/end_with_spec.rb b/spec/ruby/core/string/end_with_spec.rb
new file mode 100644
index 0000000000..ac4fff72ad
--- /dev/null
+++ b/spec/ruby/core/string/end_with_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/string/end_with'
+
+describe "String#end_with?" do
+ it_behaves_like :end_with, :to_s
+end
diff --git a/spec/ruby/core/string/eql_spec.rb b/spec/ruby/core/string/eql_spec.rb
new file mode 100644
index 0000000000..397974d9fb
--- /dev/null
+++ b/spec/ruby/core/string/eql_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eql'
+
+describe "String#eql?" do
+ it_behaves_like :string_eql_value, :eql?
+
+ describe "when given a non-String" do
+ it "returns false" do
+ 'hello'.should_not eql(5)
+ not_supported_on :opal do
+ 'hello'.should_not eql(:hello)
+ end
+ 'hello'.should_not eql(mock('x'))
+ end
+
+ it "does not try to call #to_str on the given argument" do
+ (obj = mock('x')).should_not_receive(:to_str)
+ 'hello'.should_not eql(obj)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/equal_value_spec.rb b/spec/ruby/core/string/equal_value_spec.rb
new file mode 100644
index 0000000000..b9c9c372f8
--- /dev/null
+++ b/spec/ruby/core/string/equal_value_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eql'
+require_relative 'shared/equal_value'
+
+describe "String#==" do
+ it_behaves_like :string_eql_value, :==
+ it_behaves_like :string_equal_value, :==
+end
diff --git a/spec/ruby/core/string/fixtures/classes.rb b/spec/ruby/core/string/fixtures/classes.rb
new file mode 100644
index 0000000000..26fcd51b5d
--- /dev/null
+++ b/spec/ruby/core/string/fixtures/classes.rb
@@ -0,0 +1,60 @@
+class Object
+ # This helper is defined here rather than in MSpec because
+ # it is only used in #unpack specs.
+ def unpack_format(count=nil, repeat=nil)
+ format = "#{instance_variable_get(:@method)}#{count}"
+ format *= repeat if repeat
+ format.dup # because it may then become tainted
+ end
+end
+
+module StringSpecs
+ class MyString < String; end
+ class MyArray < Array; end
+ class MyRange < Range; end
+
+ class SubString < String
+ attr_reader :special
+
+ def initialize(str=nil)
+ @special = str
+ end
+ end
+
+ class InitializeString < String
+ attr_reader :ivar
+
+ def initialize(other)
+ super
+ @ivar = 1
+ end
+
+ def initialize_copy(other)
+ ScratchPad.record object_id
+ end
+ end
+
+ module StringModule
+ def repr
+ 1
+ end
+ end
+
+ class StringWithRaisingConstructor < String
+ def initialize(str)
+ raise ArgumentError.new('constructor was called') unless str == 'silly:string'
+ self.replace(str)
+ end
+ end
+
+ class SpecialVarProcessor
+ def process(match)
+ if $~ != nil
+ str = $~[0]
+ else
+ str = "unset"
+ end
+ "<#{str}>"
+ end
+ end
+end
diff --git a/spec/ruby/core/string/fixtures/freeze_magic_comment.rb b/spec/ruby/core/string/fixtures/freeze_magic_comment.rb
new file mode 100644
index 0000000000..2b87a16328
--- /dev/null
+++ b/spec/ruby/core/string/fixtures/freeze_magic_comment.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+print (+ 'frozen string').frozen? ? 'immutable' : 'mutable'
diff --git a/spec/ruby/core/string/fixtures/iso-8859-9-encoding.rb b/spec/ruby/core/string/fixtures/iso-8859-9-encoding.rb
new file mode 100644
index 0000000000..cfa91dedc3
--- /dev/null
+++ b/spec/ruby/core/string/fixtures/iso-8859-9-encoding.rb
@@ -0,0 +1,9 @@
+# -*- encoding: iso-8859-9 -*-
+module StringSpecs
+ class ISO88599Encoding
+ def source_encoding; __ENCODING__; end
+ def x_escape; [0xDF].pack('C').force_encoding("iso-8859-9"); end
+ def ascii_only; "glark"; end
+ def cedilla; "Þ"; end # S-cedilla
+ end
+end
diff --git a/spec/ruby/core/string/fixtures/to_c.rb b/spec/ruby/core/string/fixtures/to_c.rb
new file mode 100644
index 0000000000..7776933263
--- /dev/null
+++ b/spec/ruby/core/string/fixtures/to_c.rb
@@ -0,0 +1,5 @@
+module StringSpecs
+ def self.to_c_method(string)
+ string.to_c
+ end
+end
diff --git a/spec/ruby/core/string/fixtures/utf-8-encoding.rb b/spec/ruby/core/string/fixtures/utf-8-encoding.rb
new file mode 100644
index 0000000000..fd243ec522
--- /dev/null
+++ b/spec/ruby/core/string/fixtures/utf-8-encoding.rb
@@ -0,0 +1,7 @@
+# -*- encoding: utf-8 -*-
+module StringSpecs
+ class UTF8Encoding
+ def self.source_encoding; __ENCODING__; end
+ def self.egrave; "é"; end
+ end
+end
diff --git a/spec/ruby/core/string/force_encoding_spec.rb b/spec/ruby/core/string/force_encoding_spec.rb
new file mode 100644
index 0000000000..f37aaf9eb4
--- /dev/null
+++ b/spec/ruby/core/string/force_encoding_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+
+describe "String#force_encoding" do
+ it "accepts a String as the name of an Encoding" do
+ "abc".force_encoding('shift_jis').encoding.should == Encoding::Shift_JIS
+ end
+
+ describe "with a special encoding name" do
+ before :each do
+ @original_encoding = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @original_encoding
+ end
+
+ it "accepts valid special encoding names" do
+ Encoding.default_internal = "US-ASCII"
+ "abc".force_encoding("internal").encoding.should == Encoding::US_ASCII
+ end
+
+ it "defaults to BINARY if special encoding name is not set" do
+ Encoding.default_internal = nil
+ "abc".force_encoding("internal").encoding.should == Encoding::BINARY
+ end
+ end
+
+ it "accepts an Encoding instance" do
+ "abc".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::Shift_JIS
+ end
+
+ it "calls #to_str to convert an object to an encoding name" do
+ obj = mock("force_encoding")
+ obj.should_receive(:to_str).and_return("utf-8")
+
+ "abc".force_encoding(obj).encoding.should == Encoding::UTF_8
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock("force_encoding")
+ obj.should_receive(:to_str).and_return(1)
+
+ -> { "abc".force_encoding(obj) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed nil" do
+ -> { "abc".force_encoding(nil) }.should raise_error(TypeError)
+ end
+
+ it "returns self" do
+ str = "abc"
+ str.force_encoding('utf-8').should equal(str)
+ end
+
+ it "sets the encoding even if the String contents are invalid in that encoding" do
+ str = "\u{9765}"
+ str.force_encoding('euc-jp')
+ str.encoding.should == Encoding::EUC_JP
+ str.valid_encoding?.should be_false
+ end
+
+ it "does not transcode self" do
+ str = "é"
+ str.dup.force_encoding('utf-16le').should_not == str.encode('utf-16le')
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ str = "abcd".freeze
+ -> { str.force_encoding(str.encoding) }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/freeze_spec.rb b/spec/ruby/core/string/freeze_spec.rb
new file mode 100644
index 0000000000..04d1e9513c
--- /dev/null
+++ b/spec/ruby/core/string/freeze_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "String#freeze" do
+
+ it "produces the same object whenever called on an instance of a literal in the source" do
+ "abc".freeze.should equal "abc".freeze
+ end
+
+ it "doesn't produce the same object for different instances of literals in the source" do
+ "abc".should_not equal "abc"
+ end
+
+ it "being a special form doesn't change the value of defined?" do
+ defined?("abc".freeze).should == "method"
+ end
+
+end
diff --git a/spec/ruby/core/string/getbyte_spec.rb b/spec/ruby/core/string/getbyte_spec.rb
new file mode 100644
index 0000000000..27b7d826ea
--- /dev/null
+++ b/spec/ruby/core/string/getbyte_spec.rb
@@ -0,0 +1,69 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "String#getbyte" do
+ it "returns an Integer if given a valid index" do
+ "a".getbyte(0).should be_kind_of(Integer)
+ end
+
+ it "starts indexing at 0" do
+ "b".getbyte(0).should == 98
+
+ # copy-on-write case
+ _str1, str2 = "fooXbar".split("X")
+ str2.getbyte(0).should == 98
+ end
+
+ it "counts from the end of the String if given a negative argument" do
+ "glark".getbyte(-1).should == "glark".getbyte(4)
+
+ # copy-on-write case
+ _str1, str2 = "fooXbar".split("X")
+ str2.getbyte(-1).should == 114
+ end
+
+ it "returns an Integer between 0 and 255" do
+ "\x00".getbyte(0).should == 0
+ [0xFF].pack('C').getbyte(0).should == 255
+ 256.chr('utf-8').getbyte(0).should == 196
+ 256.chr('utf-8').getbyte(1).should == 128
+ end
+
+ it "regards a multi-byte character as having multiple bytes" do
+ chr = "\u{998}"
+ chr.bytesize.should == 3
+ chr.getbyte(0).should == 224
+ chr.getbyte(1).should == 166
+ chr.getbyte(2).should == 152
+ end
+
+ it "mirrors the output of #bytes" do
+ xDE = [0xDE].pack('C').force_encoding('utf-8')
+ str = "UTF-8 (\u{9865}} characters and hex escapes (#{xDE})"
+ str.bytes.to_a.each_with_index do |byte, index|
+ str.getbyte(index).should == byte
+ end
+ end
+
+ it "interprets bytes relative to the String's encoding" do
+ str = "\u{333}"
+ str.encode('utf-8').getbyte(0).should_not == str.encode('utf-16le').getbyte(0)
+ end
+
+ it "returns nil for out-of-bound indexes" do
+ "g".getbyte(1).should be_nil
+ end
+
+ it "regards the empty String as containing no bytes" do
+ "".getbyte(0).should be_nil
+ end
+
+ it "raises an ArgumentError unless given one argument" do
+ -> { "glark".getbyte }.should raise_error(ArgumentError)
+ -> { "food".getbyte(0,0) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError unless its argument can be coerced into an Integer" do
+ -> { "a".getbyte('a') }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/string/grapheme_clusters_spec.rb b/spec/ruby/core/string/grapheme_clusters_spec.rb
new file mode 100644
index 0000000000..3046265a12
--- /dev/null
+++ b/spec/ruby/core/string/grapheme_clusters_spec.rb
@@ -0,0 +1,13 @@
+require_relative 'shared/chars'
+require_relative 'shared/grapheme_clusters'
+
+describe "String#grapheme_clusters" do
+ it_behaves_like :string_chars, :grapheme_clusters
+ it_behaves_like :string_grapheme_clusters, :grapheme_clusters
+
+ it "returns an array when no block given" do
+ string = "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}"
+ string.grapheme_clusters.should == ['a', 'b', "\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}", "\u{1F43E}"]
+
+ end
+end
diff --git a/spec/ruby/core/string/gsub_spec.rb b/spec/ruby/core/string/gsub_spec.rb
new file mode 100644
index 0000000000..c87a566591
--- /dev/null
+++ b/spec/ruby/core/string/gsub_spec.rb
@@ -0,0 +1,625 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :string_gsub_named_capture, shared: true do
+ it "replaces \\k named backreferences with the regexp's corresponding capture" do
+ str = "hello"
+
+ str.gsub(/(?<foo>[aeiou])/, '<\k<foo>>').should == "h<e>ll<o>"
+ str.gsub(/(?<foo>.)/, '\k<foo>\k<foo>').should == "hheelllloo"
+ end
+end
+
+describe "String#gsub with pattern and replacement" do
+ it "inserts the replacement around every character when the pattern collapses" do
+ "hello".gsub(//, ".").should == ".h.e.l.l.o."
+ end
+
+ it "respects unicode when the pattern collapses" do
+ str = "ã“ã«ã¡ã‚"
+ reg = %r!!
+
+ str.gsub(reg, ".").should == ".ã“.ã«.ã¡.ã‚."
+ end
+
+ it "doesn't freak out when replacing ^" do
+ "Text\n".gsub(/^/, ' ').should == " Text\n"
+ "Text\nFoo".gsub(/^/, ' ').should == " Text\n Foo"
+ end
+
+ it "returns a copy of self with all occurrences of pattern replaced with replacement" do
+ "hello".gsub(/[aeiou]/, '*').should == "h*ll*"
+
+ str = "hello homely world. hah!"
+ str.gsub(/\Ah\S+\s*/, "huh? ").should == "huh? homely world. hah!"
+
+ str = "¿por qué?"
+ str.gsub(/([a-z\d]*)/, "*").should == "*¿** **é*?*"
+ end
+
+ it "ignores a block if supplied" do
+ "food".gsub(/f/, "g") { "w" }.should == "good"
+ end
+
+ it "supports \\G which matches at the beginning of the remaining (non-matched) string" do
+ str = "hello homely world. hah!"
+ str.gsub(/\Gh\S+\s*/, "huh? ").should == "huh? huh? world. hah!"
+ end
+
+ it "supports /i for ignoring case" do
+ str = "Hello. How happy are you?"
+ str.gsub(/h/i, "j").should == "jello. jow jappy are you?"
+ str.gsub(/H/i, "j").should == "jello. jow jappy are you?"
+ end
+
+ it "doesn't interpret regexp metacharacters if pattern is a string" do
+ "12345".gsub('\d', 'a').should == "12345"
+ '\d'.gsub('\d', 'a').should == "a"
+ end
+
+ it "replaces \\1 sequences with the regexp's corresponding capture" do
+ str = "hello"
+
+ str.gsub(/([aeiou])/, '<\1>').should == "h<e>ll<o>"
+ str.gsub(/(.)/, '\1\1').should == "hheelllloo"
+
+ str.gsub(/.(.?)/, '<\0>(\1)').should == "<he>(e)<ll>(l)<o>()"
+
+ str.gsub(/.(.)+/, '\1').should == "o"
+
+ str = "ABCDEFGHIJKLabcdefghijkl"
+ re = /#{"(.)" * 12}/
+ str.gsub(re, '\1').should == "Aa"
+ str.gsub(re, '\9').should == "Ii"
+ # Only the first 9 captures can be accessed in MRI
+ str.gsub(re, '\10').should == "A0a0"
+ end
+
+ it "treats \\1 sequences without corresponding captures as empty strings" do
+ str = "hello!"
+
+ str.gsub("", '<\1>').should == "<>h<>e<>l<>l<>o<>!<>"
+ str.gsub("h", '<\1>').should == "<>ello!"
+
+ str.gsub(//, '<\1>').should == "<>h<>e<>l<>l<>o<>!<>"
+ str.gsub(/./, '\1\2\3').should == ""
+ str.gsub(/.(.{20})?/, '\1').should == ""
+ end
+
+ it "replaces \\& and \\0 with the complete match" do
+ str = "hello!"
+
+ str.gsub("", '<\0>').should == "<>h<>e<>l<>l<>o<>!<>"
+ str.gsub("", '<\&>').should == "<>h<>e<>l<>l<>o<>!<>"
+ str.gsub("he", '<\0>').should == "<he>llo!"
+ str.gsub("he", '<\&>').should == "<he>llo!"
+ str.gsub("l", '<\0>').should == "he<l><l>o!"
+ str.gsub("l", '<\&>').should == "he<l><l>o!"
+
+ str.gsub(//, '<\0>').should == "<>h<>e<>l<>l<>o<>!<>"
+ str.gsub(//, '<\&>').should == "<>h<>e<>l<>l<>o<>!<>"
+ str.gsub(/../, '<\0>').should == "<he><ll><o!>"
+ str.gsub(/../, '<\&>').should == "<he><ll><o!>"
+ str.gsub(/(.)./, '<\0>').should == "<he><ll><o!>"
+ end
+
+ it "replaces \\` with everything before the current match" do
+ str = "hello!"
+
+ str.gsub("", '<\`>').should == "<>h<h>e<he>l<hel>l<hell>o<hello>!<hello!>"
+ str.gsub("h", '<\`>').should == "<>ello!"
+ str.gsub("l", '<\`>').should == "he<he><hel>o!"
+ str.gsub("!", '<\`>').should == "hello<hello>"
+
+ str.gsub(//, '<\`>').should == "<>h<h>e<he>l<hel>l<hell>o<hello>!<hello!>"
+ str.gsub(/../, '<\`>').should == "<><he><hell>"
+ end
+
+ it "replaces \\' with everything after the current match" do
+ str = "hello!"
+
+ str.gsub("", '<\\\'>').should == "<hello!>h<ello!>e<llo!>l<lo!>l<o!>o<!>!<>"
+ str.gsub("h", '<\\\'>').should == "<ello!>ello!"
+ str.gsub("ll", '<\\\'>').should == "he<o!>o!"
+ str.gsub("!", '<\\\'>').should == "hello<>"
+
+ str.gsub(//, '<\\\'>').should == "<hello!>h<ello!>e<llo!>l<lo!>l<o!>o<!>!<>"
+ str.gsub(/../, '<\\\'>').should == "<llo!><o!><>"
+ end
+
+ it "replaces \\+ with the last paren that actually matched" do
+ str = "hello!"
+
+ str.gsub(/(.)(.)/, '\+').should == "el!"
+ str.gsub(/(.)(.)+/, '\+').should == "!"
+ str.gsub(/(.)()/, '\+').should == ""
+ str.gsub(/(.)(.{20})?/, '<\+>').should == "<h><e><l><l><o><!>"
+
+ str = "ABCDEFGHIJKLabcdefghijkl"
+ re = /#{"(.)" * 12}/
+ str.gsub(re, '\+').should == "Ll"
+ end
+
+ it "treats \\+ as an empty string if there was no captures" do
+ "hello!".gsub(/./, '\+').should == ""
+ end
+
+ it "maps \\\\ in replacement to \\" do
+ "hello".gsub(/./, '\\\\').should == '\\' * 5
+ end
+
+ it "leaves unknown \\x escapes in replacement untouched" do
+ "hello".gsub(/./, '\\x').should == '\\x' * 5
+ "hello".gsub(/./, '\\y').should == '\\y' * 5
+ end
+
+ it "leaves \\ at the end of replacement untouched" do
+ "hello".gsub(/./, 'hah\\').should == 'hah\\' * 5
+ end
+
+ it_behaves_like :string_gsub_named_capture, :gsub
+
+ it "handles pattern collapse" do
+ str = "ã“ã«ã¡ã‚"
+ reg = %r!!
+ str.gsub(reg, ".").should == ".ã“.ã«.ã¡.ã‚."
+ end
+
+ it "tries to convert pattern to a string using to_str" do
+ pattern = mock('.')
+ def pattern.to_str() "." end
+
+ "hello.".gsub(pattern, "!").should == "hello!"
+ end
+
+ it "raises a TypeError when pattern can't be converted to a string" do
+ -> { "hello".gsub([], "x") }.should raise_error(TypeError)
+ -> { "hello".gsub(Object.new, "x") }.should raise_error(TypeError)
+ -> { "hello".gsub(nil, "x") }.should raise_error(TypeError)
+ end
+
+ it "tries to convert replacement to a string using to_str" do
+ replacement = mock('hello_replacement')
+ def replacement.to_str() "hello_replacement" end
+
+ "hello".gsub(/hello/, replacement).should == "hello_replacement"
+ end
+
+ it "raises a TypeError when replacement can't be converted to a string" do
+ -> { "hello".gsub(/[aeiou]/, []) }.should raise_error(TypeError)
+ -> { "hello".gsub(/[aeiou]/, Object.new) }.should raise_error(TypeError)
+ -> { "hello".gsub(/[aeiou]/, nil) }.should raise_error(TypeError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("").gsub(//, "").should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("").gsub(/foo/, "").should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("foo").gsub(/foo/, "").should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("foo").gsub("foo", "").should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("").gsub(//, "").should be_an_instance_of(String)
+ StringSpecs::MyString.new("").gsub(/foo/, "").should be_an_instance_of(String)
+ StringSpecs::MyString.new("foo").gsub(/foo/, "").should be_an_instance_of(String)
+ StringSpecs::MyString.new("foo").gsub("foo", "").should be_an_instance_of(String)
+ end
+ end
+
+ it "sets $~ to MatchData of last match and nil when there's none" do
+ 'hello.'.gsub('hello', 'x')
+ $~[0].should == 'hello'
+
+ 'hello.'.gsub('not', 'x')
+ $~.should == nil
+
+ 'hello.'.gsub(/.(.)/, 'x')
+ $~[0].should == 'o.'
+
+ 'hello.'.gsub(/not/, 'x')
+ $~.should == nil
+ end
+
+ it "handles a pattern in a superset encoding" do
+ result = 'abc'.force_encoding(Encoding::US_ASCII).gsub('é', 'è')
+ result.should == 'abc'
+ result.encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ result = 'été'.gsub('t'.force_encoding(Encoding::US_ASCII), 'u')
+ result.should == 'éué'
+ result.encoding.should == Encoding::UTF_8
+ end
+end
+
+describe "String#gsub with pattern and Hash" do
+ it "returns a copy of self with all occurrences of pattern replaced with the value of the corresponding hash key" do
+ "hello".gsub(/./, 'l' => 'L').should == "LL"
+ "hello!".gsub(/(.)(.)/, 'he' => 'she ', 'll' => 'said').should == 'she said'
+ "hello".gsub('l', 'l' => 'el').should == 'heelelo'
+ end
+
+ it "ignores keys that don't correspond to matches" do
+ "hello".gsub(/./, 'z' => 'L', 'h' => 'b', 'o' => 'ow').should == "bow"
+ end
+
+ it "returns an empty string if the pattern matches but the hash specifies no replacements" do
+ "hello".gsub(/./, 'z' => 'L').should == ""
+ end
+
+ it "ignores non-String keys" do
+ "tattoo".gsub(/(tt)/, 'tt' => 'b', tt: 'z').should == "taboo"
+ end
+
+ it "uses a key's value as many times as needed" do
+ "food".gsub(/o/, 'o' => '0').should == "f00d"
+ end
+
+ it "uses the hash's default value for missing keys" do
+ hsh = {}
+ hsh.default='?'
+ hsh['o'] = '0'
+ "food".gsub(/./, hsh).should == "?00?"
+ end
+
+ it "coerces the hash values with #to_s" do
+ hsh = {}
+ hsh.default=[]
+ hsh['o'] = 0
+ obj = mock('!')
+ obj.should_receive(:to_s).and_return('!')
+ hsh['!'] = obj
+ "food!".gsub(/./, hsh).should == "[]00[]!"
+ end
+
+ it "uses the hash's value set from default_proc for missing keys" do
+ hsh = {}
+ hsh.default_proc = -> k, v { 'lamb' }
+ "food!".gsub(/./, hsh).should == "lamblamblamblamblamb"
+ end
+
+ it "sets $~ to MatchData of last match and nil when there's none for access from outside" do
+ 'hello.'.gsub('l', 'l' => 'L')
+ $~.begin(0).should == 3
+ $~[0].should == 'l'
+
+ 'hello.'.gsub('not', 'ot' => 'to')
+ $~.should == nil
+
+ 'hello.'.gsub(/.(.)/, 'o' => ' hole')
+ $~[0].should == 'o.'
+
+ 'hello.'.gsub(/not/, 'z' => 'glark')
+ $~.should == nil
+ end
+
+ it "doesn't interpolate special sequences like \\1 for the block's return value" do
+ repl = '\& \0 \1 \` \\\' \+ \\\\ foo'
+ "hello".gsub(/(.+)/, 'hello' => repl ).should == repl
+ end
+end
+
+describe "String#gsub! with pattern and Hash" do
+
+ it "returns self with all occurrences of pattern replaced with the value of the corresponding hash key" do
+ "hello".gsub!(/./, 'l' => 'L').should == "LL"
+ "hello!".gsub!(/(.)(.)/, 'he' => 'she ', 'll' => 'said').should == 'she said'
+ "hello".gsub!('l', 'l' => 'el').should == 'heelelo'
+ end
+
+ it "ignores keys that don't correspond to matches" do
+ "hello".gsub!(/./, 'z' => 'L', 'h' => 'b', 'o' => 'ow').should == "bow"
+ end
+
+ it "replaces self with an empty string if the pattern matches but the hash specifies no replacements" do
+ "hello".gsub!(/./, 'z' => 'L').should == ""
+ end
+
+ it "ignores non-String keys" do
+ "hello".gsub!(/(ll)/, 'll' => 'r', ll: 'z').should == "hero"
+ end
+
+ it "uses a key's value as many times as needed" do
+ "food".gsub!(/o/, 'o' => '0').should == "f00d"
+ end
+
+ it "uses the hash's default value for missing keys" do
+ hsh = {}
+ hsh.default='?'
+ hsh['o'] = '0'
+ "food".gsub!(/./, hsh).should == "?00?"
+ end
+
+ it "coerces the hash values with #to_s" do
+ hsh = {}
+ hsh.default=[]
+ hsh['o'] = 0
+ obj = mock('!')
+ obj.should_receive(:to_s).and_return('!')
+ hsh['!'] = obj
+ "food!".gsub!(/./, hsh).should == "[]00[]!"
+ end
+
+ it "uses the hash's value set from default_proc for missing keys" do
+ hsh = {}
+ hsh.default_proc = -> k, v { 'lamb' }
+ "food!".gsub!(/./, hsh).should == "lamblamblamblamblamb"
+ end
+
+ it "sets $~ to MatchData of last match and nil when there's none for access from outside" do
+ 'hello.'.gsub!('l', 'l' => 'L')
+ $~.begin(0).should == 3
+ $~[0].should == 'l'
+
+ 'hello.'.gsub!('not', 'ot' => 'to')
+ $~.should == nil
+
+ 'hello.'.gsub!(/.(.)/, 'o' => ' hole')
+ $~[0].should == 'o.'
+
+ 'hello.'.gsub!(/not/, 'z' => 'glark')
+ $~.should == nil
+ end
+
+ it "doesn't interpolate special sequences like \\1 for the block's return value" do
+ repl = '\& \0 \1 \` \\\' \+ \\\\ foo'
+ "hello".gsub!(/(.+)/, 'hello' => repl ).should == repl
+ end
+end
+
+describe "String#gsub with pattern and block" do
+ it "returns a copy of self with all occurrences of pattern replaced with the block's return value" do
+ "hello".gsub(/./) { |s| s.succ + ' ' }.should == "i f m m p "
+ "hello!".gsub(/(.)(.)/) { |*a| a.inspect }.should == '["he"]["ll"]["o!"]'
+ "hello".gsub('l') { 'x'}.should == 'hexxo'
+ end
+
+ it "sets $~ for access from the block" do
+ str = "hello"
+ str.gsub(/([aeiou])/) { "<#{$~[1]}>" }.should == "h<e>ll<o>"
+ str.gsub(/([aeiou])/) { "<#{$1}>" }.should == "h<e>ll<o>"
+ str.gsub("l") { "<#{$~[0]}>" }.should == "he<l><l>o"
+
+ offsets = []
+
+ str.gsub(/([aeiou])/) do
+ md = $~
+ md.string.should == str
+ offsets << md.offset(0)
+ str
+ end.should == "hhellollhello"
+
+ offsets.should == [[1, 2], [4, 5]]
+ end
+
+ it "does not set $~ for procs created from methods" do
+ str = "hello"
+ str.gsub("l", &StringSpecs::SpecialVarProcessor.new.method(:process)).should == "he<unset><unset>o"
+ end
+
+ it "restores $~ after leaving the block" do
+ [/./, "l"].each do |pattern|
+ old_md = nil
+ "hello".gsub(pattern) do
+ old_md = $~
+ "ok".match(/./)
+ "x"
+ end
+
+ $~[0].should == old_md[0]
+ $~.string.should == "hello"
+ end
+ end
+
+ it "sets $~ to MatchData of last match and nil when there's none for access from outside" do
+ 'hello.'.gsub('l') { 'x' }
+ $~.begin(0).should == 3
+ $~[0].should == 'l'
+
+ 'hello.'.gsub('not') { 'x' }
+ $~.should == nil
+
+ 'hello.'.gsub(/.(.)/) { 'x' }
+ $~[0].should == 'o.'
+
+ 'hello.'.gsub(/not/) { 'x' }
+ $~.should == nil
+ end
+
+ it "doesn't interpolate special sequences like \\1 for the block's return value" do
+ repl = '\& \0 \1 \` \\\' \+ \\\\ foo'
+ "hello".gsub(/(.+)/) { repl }.should == repl
+ end
+
+ it "converts the block's return value to a string using to_s" do
+ replacement = mock('hello_replacement')
+ def replacement.to_s() "hello_replacement" end
+
+ "hello".gsub(/hello/) { replacement }.should == "hello_replacement"
+
+ obj = mock('ok')
+ def obj.to_s() "ok" end
+
+ "hello".gsub(/.+/) { obj }.should == "ok"
+ end
+
+ it "uses the compatible encoding if they are compatible" do
+ s = "hello"
+ s2 = "#{195.chr}#{192.chr}#{195.chr}"
+
+ s.gsub(/l/) { |bar| 195.chr }.encoding.should == Encoding::BINARY
+ s2.gsub("#{192.chr}") { |bar| "hello" }.encoding.should == Encoding::BINARY
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are not compatible" do
+ s = "hllëllo"
+ s2 = "hellö"
+
+ -> { s.gsub(/l/) { |bar| "РуÑÑкий".force_encoding("iso-8859-5") } }.should raise_error(Encoding::CompatibilityError)
+ -> { s2.gsub(/l/) { |bar| "РуÑÑкий".force_encoding("iso-8859-5") } }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "replaces the incompatible part properly even if the encodings are not compatible" do
+ s = "hllëllo"
+
+ s.gsub(/ë/) { |bar| "РуÑÑкий".force_encoding("iso-8859-5") }.encoding.should == Encoding::ISO_8859_5
+ end
+
+ not_supported_on :opal do
+ it "raises an ArgumentError if encoding is not valid" do
+ x92 = [0x92].pack('C').force_encoding('utf-8')
+ -> { "a#{x92}b".gsub(/[^\x00-\x7f]/u, '') }.should raise_error(ArgumentError)
+ end
+ end
+end
+
+describe "String#gsub with pattern and without replacement and block" do
+ it "returns an enumerator" do
+ enum = "abca".gsub(/a/)
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == ["a", "a"]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ "abca".gsub(/a/).size.should == nil
+ end
+ end
+ end
+end
+
+describe "String#gsub with a string pattern" do
+ it "handles multibyte characters" do
+ "é".gsub("é", "â").should == "â"
+ "aé".gsub("é", "â").should == "aâ"
+ "éa".gsub("é", "â").should == "âa"
+ end
+end
+
+describe "String#gsub! with pattern and replacement" do
+ it "modifies self in place and returns self" do
+ a = "hello"
+ a.gsub!(/[aeiou]/, '*').should equal(a)
+ a.should == "h*ll*"
+ end
+
+ it "modifies self in place with multi-byte characters and returns self" do
+ a = "¿por qué?"
+ a.gsub!(/([a-z\d]*)/, "*").should equal(a)
+ a.should == "*¿** **é*?*"
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "hello"
+ a.gsub!(/z/, '*').should == nil
+ a.gsub!(/z/, 'z').should == nil
+ a.should == "hello"
+ end
+
+ # See [ruby-core:23666]
+ it "raises a FrozenError when self is frozen" do
+ s = "hello"
+ s.freeze
+
+ -> { s.gsub!(/ROAR/, "x") }.should raise_error(FrozenError)
+ -> { s.gsub!(/e/, "e") }.should raise_error(FrozenError)
+ -> { s.gsub!(/[aeiou]/, '*') }.should raise_error(FrozenError)
+ end
+
+ it "handles a pattern in a superset encoding" do
+ string = 'abc'.force_encoding(Encoding::US_ASCII)
+
+ result = string.gsub!('é', 'è')
+
+ result.should == nil
+ string.should == 'abc'
+ string.encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ string = 'été'
+ pattern = 't'.force_encoding(Encoding::US_ASCII)
+
+ result = string.gsub!(pattern, 'u')
+
+ result.should == string
+ string.should == 'éué'
+ string.encoding.should == Encoding::UTF_8
+ end
+end
+
+describe "String#gsub! with pattern and block" do
+ it "modifies self in place and returns self" do
+ a = "hello"
+ a.gsub!(/[aeiou]/) { '*' }.should equal(a)
+ a.should == "h*ll*"
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "hello"
+ a.gsub!(/z/) { '*' }.should == nil
+ a.gsub!(/z/) { 'z' }.should == nil
+ a.should == "hello"
+ end
+
+ # See [ruby-core:23663]
+ it "raises a FrozenError when self is frozen" do
+ s = "hello"
+ s.freeze
+
+ -> { s.gsub!(/ROAR/) { "x" } }.should raise_error(FrozenError)
+ -> { s.gsub!(/e/) { "e" } }.should raise_error(FrozenError)
+ -> { s.gsub!(/[aeiou]/) { '*' } }.should raise_error(FrozenError)
+ end
+
+ it "uses the compatible encoding if they are compatible" do
+ s = "hello"
+ s2 = "#{195.chr}#{192.chr}#{195.chr}"
+
+ s.gsub!(/l/) { |bar| 195.chr }.encoding.should == Encoding::BINARY
+ s2.gsub!("#{192.chr}") { |bar| "hello" }.encoding.should == Encoding::BINARY
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are not compatible" do
+ s = "hllëllo"
+ s2 = "hellö"
+
+ -> { s.gsub!(/l/) { |bar| "РуÑÑкий".force_encoding("iso-8859-5") } }.should raise_error(Encoding::CompatibilityError)
+ -> { s2.gsub!(/l/) { |bar| "РуÑÑкий".force_encoding("iso-8859-5") } }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "replaces the incompatible part properly even if the encodings are not compatible" do
+ s = "hllëllo"
+
+ s.gsub!(/ë/) { |bar| "РуÑÑкий".force_encoding("iso-8859-5") }.encoding.should == Encoding::ISO_8859_5
+ end
+
+ not_supported_on :opal do
+ it "raises an ArgumentError if encoding is not valid" do
+ x92 = [0x92].pack('C').force_encoding('utf-8')
+ -> { "a#{x92}b".gsub!(/[^\x00-\x7f]/u, '') }.should raise_error(ArgumentError)
+ end
+ end
+end
+
+describe "String#gsub! with pattern and without replacement and block" do
+ it "returns an enumerator" do
+ enum = "abca".gsub!(/a/)
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == ["a", "a"]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ "abca".gsub!(/a/).size.should == nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/string/hash_spec.rb b/spec/ruby/core/string/hash_spec.rb
new file mode 100644
index 0000000000..0b26214b55
--- /dev/null
+++ b/spec/ruby/core/string/hash_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#hash" do
+ it "returns a hash based on a string's length and content" do
+ "abc".hash.should == "abc".hash
+ "abc".hash.should_not == "cba".hash
+ end
+end
diff --git a/spec/ruby/core/string/hex_spec.rb b/spec/ruby/core/string/hex_spec.rb
new file mode 100644
index 0000000000..364e915681
--- /dev/null
+++ b/spec/ruby/core/string/hex_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# TODO: Move actual results to String#to_int() and spec in terms of it
+describe "String#hex" do
+ it "treats leading characters of self as a string of hex digits" do
+ "0a".hex.should == 10
+ "0o".hex.should == 0
+ "0x".hex.should == 0
+ "A_BAD_BABE".hex.should == 0xABADBABE
+ "0b1010".hex.should == "b1010".hex
+ "0d500".hex.should == "d500".hex
+ "abcdefG".hex.should == 0xabcdef
+ end
+
+ it "does not accept a sequence of underscores as part of a number" do
+ "a__b".hex.should == 0xa
+ "a____b".hex.should == 0xa
+ "a___f".hex.should == 0xa
+ end
+
+ it "takes an optional sign" do
+ "-1234".hex.should == -4660
+ "+1234".hex.should == 4660
+ end
+
+ it "takes an optional 0x" do
+ "0x0a".hex.should == 10
+ "0a".hex.should == 10
+ end
+
+ it "requires that the sign is in front of the 0x if present" do
+ "-0x1".hex.should == -1
+ "0x-1".hex.should == 0
+ end
+
+ it "returns 0 on error" do
+ "".hex.should == 0
+ "+-5".hex.should == 0
+ "wombat".hex.should == 0
+ "0x0x42".hex.should == 0
+ end
+
+ it "returns 0 if sequence begins with underscore" do
+ "_a".hex.should == 0
+ "___b".hex.should == 0
+ "___0xc".hex.should == 0
+ end
+end
diff --git a/spec/ruby/core/string/include_spec.rb b/spec/ruby/core/string/include_spec.rb
new file mode 100644
index 0000000000..23e1e134ec
--- /dev/null
+++ b/spec/ruby/core/string/include_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#include? with String" do
+ it "returns true if self contains other_str" do
+ "hello".include?("lo").should == true
+ "hello".include?("ol").should == false
+ end
+
+ it "ignores subclass differences" do
+ "hello".include?(StringSpecs::MyString.new("lo")).should == true
+ StringSpecs::MyString.new("hello").include?("lo").should == true
+ StringSpecs::MyString.new("hello").include?(StringSpecs::MyString.new("lo")).should == true
+ end
+
+ it "returns true if both strings are empty" do
+ "".should.include?("")
+ "".force_encoding("EUC-JP").should.include?("")
+ "".should.include?("".force_encoding("EUC-JP"))
+ "".force_encoding("EUC-JP").should.include?("".force_encoding("EUC-JP"))
+ end
+
+ it "returns true if the RHS is empty" do
+ "a".should.include?("")
+ "a".force_encoding("EUC-JP").should.include?("")
+ "a".should.include?("".force_encoding("EUC-JP"))
+ "a".force_encoding("EUC-JP").should.include?("".force_encoding("EUC-JP"))
+ end
+
+ it "tries to convert other to string using to_str" do
+ other = mock('lo')
+ other.should_receive(:to_str).and_return("lo")
+
+ "hello".include?(other).should == true
+ end
+
+ it "raises a TypeError if other can't be converted to string" do
+ -> { "hello".include?([]) }.should raise_error(TypeError)
+ -> { "hello".include?('h'.ord) }.should raise_error(TypeError)
+ -> { "hello".include?(mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are incompatible" do
+ pat = "ã‚¢".encode Encoding::EUC_JP
+ -> do
+ "ã‚れ".include?(pat)
+ end.should raise_error(Encoding::CompatibilityError)
+ end
+end
diff --git a/spec/ruby/core/string/index_spec.rb b/spec/ruby/core/string/index_spec.rb
new file mode 100644
index 0000000000..2eeee9be87
--- /dev/null
+++ b/spec/ruby/core/string/index_spec.rb
@@ -0,0 +1,321 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#index" do
+ it "raises a TypeError if passed nil" do
+ -> { "abc".index nil }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed a boolean" do
+ -> { "abc".index true }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed a Symbol" do
+ -> { "abc".index :a }.should raise_error(TypeError)
+ end
+
+ it "calls #to_str to convert the first argument" do
+ char = mock("string index char")
+ char.should_receive(:to_str).and_return("b")
+ "abc".index(char).should == 1
+ end
+
+ it "calls #to_int to convert the second argument" do
+ offset = mock("string index offset")
+ offset.should_receive(:to_int).and_return(1)
+ "abc".index("c", offset).should == 2
+ end
+
+ it "raises a TypeError if passed an Integer" do
+ -> { "abc".index 97 }.should raise_error(TypeError)
+ end
+end
+
+describe "String#index with String" do
+ it "behaves the same as String#index(char) for one-character strings" do
+ "blablabla hello cruel world...!".split("").uniq.each do |str|
+ chr = str[0]
+ str.index(str).should == str.index(chr)
+
+ 0.upto(str.size + 1) do |start|
+ str.index(str, start).should == str.index(chr, start)
+ end
+
+ (-str.size - 1).upto(-1) do |start|
+ str.index(str, start).should == str.index(chr, start)
+ end
+ end
+ end
+
+ it "returns the index of the first occurrence of the given substring" do
+ "blablabla".index("").should == 0
+ "blablabla".index("b").should == 0
+ "blablabla".index("bla").should == 0
+ "blablabla".index("blabla").should == 0
+ "blablabla".index("blablabla").should == 0
+
+ "blablabla".index("l").should == 1
+ "blablabla".index("la").should == 1
+ "blablabla".index("labla").should == 1
+ "blablabla".index("lablabla").should == 1
+
+ "blablabla".index("a").should == 2
+ "blablabla".index("abla").should == 2
+ "blablabla".index("ablabla").should == 2
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+
+ 'hello.'.index('ll')
+ $~.should == nil
+ end
+
+ it "ignores string subclasses" do
+ "blablabla".index(StringSpecs::MyString.new("bla")).should == 0
+ StringSpecs::MyString.new("blablabla").index("bla").should == 0
+ StringSpecs::MyString.new("blablabla").index(StringSpecs::MyString.new("bla")).should == 0
+ end
+
+ it "starts the search at the given offset" do
+ "blablabla".index("bl", 0).should == 0
+ "blablabla".index("bl", 1).should == 3
+ "blablabla".index("bl", 2).should == 3
+ "blablabla".index("bl", 3).should == 3
+
+ "blablabla".index("bla", 0).should == 0
+ "blablabla".index("bla", 1).should == 3
+ "blablabla".index("bla", 2).should == 3
+ "blablabla".index("bla", 3).should == 3
+
+ "blablabla".index("blab", 0).should == 0
+ "blablabla".index("blab", 1).should == 3
+ "blablabla".index("blab", 2).should == 3
+ "blablabla".index("blab", 3).should == 3
+
+ "blablabla".index("la", 1).should == 1
+ "blablabla".index("la", 2).should == 4
+ "blablabla".index("la", 3).should == 4
+ "blablabla".index("la", 4).should == 4
+
+ "blablabla".index("lab", 1).should == 1
+ "blablabla".index("lab", 2).should == 4
+ "blablabla".index("lab", 3).should == 4
+ "blablabla".index("lab", 4).should == 4
+
+ "blablabla".index("ab", 2).should == 2
+ "blablabla".index("ab", 3).should == 5
+ "blablabla".index("ab", 4).should == 5
+ "blablabla".index("ab", 5).should == 5
+
+ "blablabla".index("", 0).should == 0
+ "blablabla".index("", 1).should == 1
+ "blablabla".index("", 2).should == 2
+ "blablabla".index("", 7).should == 7
+ "blablabla".index("", 8).should == 8
+ "blablabla".index("", 9).should == 9
+ end
+
+ it "starts the search at offset + self.length if offset is negative" do
+ str = "blablabla"
+
+ ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle|
+ (-str.length .. -1).each do |offset|
+ str.index(needle, offset).should ==
+ str.index(needle, offset + str.length)
+ end
+ end
+ end
+
+ it "returns nil if the substring isn't found" do
+ "blablabla".index("B").should == nil
+ "blablabla".index("z").should == nil
+ "blablabla".index("BLA").should == nil
+ "blablabla".index("blablablabla").should == nil
+ "blablabla".index("", 10).should == nil
+
+ "hello".index("he", 1).should == nil
+ "hello".index("he", 2).should == nil
+ "I’ve got a multibyte character.\n".index("\n\n").should == nil
+ end
+
+ it "returns the character index of a multibyte character" do
+ "ã‚りãŒã¨ã†".index("ãŒ").should == 2
+ end
+
+ it "returns the character index after offset" do
+ "ã‚れã‚れ".index("ã‚", 1).should == 2
+ "ã‚りãŒã¨ã†ã‚りãŒã¨ã†".index("ãŒ", 3).should == 7
+ end
+
+ it "returns the character index after a partial first match" do
+ "</</h".index("</h").should == 2
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are incompatible" do
+ char = "れ".encode Encoding::EUC_JP
+ -> do
+ "ã‚れ".index char
+ end.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "handles a substring in a superset encoding" do
+ 'abc'.force_encoding(Encoding::US_ASCII).index('é').should == nil
+ end
+
+ it "handles a substring in a subset encoding" do
+ 'été'.index('t'.force_encoding(Encoding::US_ASCII)).should == 1
+ end
+end
+
+describe "String#index with Regexp" do
+ it "behaves the same as String#index(string) for escaped string regexps" do
+ ["blablabla", "hello cruel world...!"].each do |str|
+ ["", "b", "bla", "lab", "o c", "d."].each do |needle|
+ regexp = Regexp.new(Regexp.escape(needle))
+ str.index(regexp).should == str.index(needle)
+
+ 0.upto(str.size + 1) do |start|
+ str.index(regexp, start).should == str.index(needle, start)
+ end
+
+ (-str.size - 1).upto(-1) do |start|
+ str.index(regexp, start).should == str.index(needle, start)
+ end
+ end
+ end
+ end
+
+ it "returns the index of the first match of regexp" do
+ "blablabla".index(/bla/).should == 0
+ "blablabla".index(/BLA/i).should == 0
+
+ "blablabla".index(/.{0}/).should == 0
+ "blablabla".index(/.{6}/).should == 0
+ "blablabla".index(/.{9}/).should == 0
+
+ "blablabla".index(/.*/).should == 0
+ "blablabla".index(/.+/).should == 0
+
+ "blablabla".index(/lab|b/).should == 0
+
+ not_supported_on :opal do
+ "blablabla".index(/\A/).should == 0
+ "blablabla".index(/\Z/).should == 9
+ "blablabla".index(/\z/).should == 9
+ "blablabla\n".index(/\Z/).should == 9
+ "blablabla\n".index(/\z/).should == 10
+ end
+
+ "blablabla".index(/^/).should == 0
+ "\nblablabla".index(/^/).should == 0
+ "b\nablabla".index(/$/).should == 1
+ "bl\nablabla".index(/$/).should == 2
+
+ "blablabla".index(/.l./).should == 0
+ end
+
+ it "sets $~ to MatchData of match and nil when there's none" do
+ 'hello.'.index(/.(.)/)
+ $~[0].should == 'he'
+
+ 'hello.'.index(/not/)
+ $~.should == nil
+ end
+
+ it "starts the search at the given offset" do
+ "blablabla".index(/.{0}/, 5).should == 5
+ "blablabla".index(/.{1}/, 5).should == 5
+ "blablabla".index(/.{2}/, 5).should == 5
+ "blablabla".index(/.{3}/, 5).should == 5
+ "blablabla".index(/.{4}/, 5).should == 5
+
+ "blablabla".index(/.{0}/, 3).should == 3
+ "blablabla".index(/.{1}/, 3).should == 3
+ "blablabla".index(/.{2}/, 3).should == 3
+ "blablabla".index(/.{5}/, 3).should == 3
+ "blablabla".index(/.{6}/, 3).should == 3
+
+ "blablabla".index(/.l./, 0).should == 0
+ "blablabla".index(/.l./, 1).should == 3
+ "blablabla".index(/.l./, 2).should == 3
+ "blablabla".index(/.l./, 3).should == 3
+
+ "xblaxbla".index(/x./, 0).should == 0
+ "xblaxbla".index(/x./, 1).should == 4
+ "xblaxbla".index(/x./, 2).should == 4
+
+ not_supported_on :opal do
+ "blablabla\n".index(/\Z/, 9).should == 9
+ end
+ end
+
+ it "starts the search at offset + self.length if offset is negative" do
+ str = "blablabla"
+
+ ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle|
+ (-str.length .. -1).each do |offset|
+ str.index(needle, offset).should ==
+ str.index(needle, offset + str.length)
+ end
+ end
+ end
+
+ it "returns nil if the substring isn't found" do
+ "blablabla".index(/BLA/).should == nil
+
+ "blablabla".index(/.{10}/).should == nil
+ "blaxbla".index(/.x/, 3).should == nil
+ "blaxbla".index(/..x/, 2).should == nil
+ end
+
+ it "returns nil if the Regexp matches the empty string and the offset is out of range" do
+ "ruby".index(//,12).should be_nil
+ end
+
+ it "supports \\G which matches at the given start offset" do
+ "helloYOU.".index(/\GYOU/, 5).should == 5
+ "helloYOU.".index(/\GYOU/).should == nil
+
+ re = /\G.+YOU/
+ # The # marks where \G will match.
+ [
+ ["#hi!YOUall.", 0],
+ ["h#i!YOUall.", 1],
+ ["hi#!YOUall.", 2],
+ ["hi!#YOUall.", nil]
+ ].each do |spec|
+
+ start = spec[0].index("#")
+ str = spec[0].delete("#")
+
+ str.index(re, start).should == spec[1]
+ end
+ end
+
+ it "converts start_offset to an integer via to_int" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ "RWOARW".index(/R./, obj).should == 4
+ end
+
+ it "returns the character index of a multibyte character" do
+ "ã‚りãŒã¨ã†".index(/ãŒ/).should == 2
+ end
+
+ it "returns the character index after offset" do
+ "ã‚れã‚れ".index(/ã‚/, 1).should == 2
+ end
+
+ it "treats the offset as a character index" do
+ "ã‚れã‚ã‚れ".index(/ã‚/, 3).should == 3
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are incompatible" do
+ re = Regexp.new "れ".encode(Encoding::EUC_JP)
+ -> do
+ "ã‚れ".index re
+ end.should raise_error(Encoding::CompatibilityError)
+ end
+end
diff --git a/spec/ruby/core/string/initialize_spec.rb b/spec/ruby/core/string/initialize_spec.rb
new file mode 100644
index 0000000000..08734cc916
--- /dev/null
+++ b/spec/ruby/core/string/initialize_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/replace'
+
+describe "String#initialize" do
+ it "is a private method" do
+ String.should have_private_instance_method(:initialize)
+ end
+
+ describe "with no arguments" do
+ it "does not change self" do
+ s = "some string"
+ s.send :initialize
+ s.should == "some string"
+ end
+
+ it "does not raise an exception when frozen" do
+ a = "hello".freeze
+ a.send(:initialize).should equal(a)
+ end
+ end
+
+ describe "with an argument" do
+ it_behaves_like :string_replace, :initialize
+ end
+end
diff --git a/spec/ruby/core/string/insert_spec.rb b/spec/ruby/core/string/insert_spec.rb
new file mode 100644
index 0000000000..0c87df3a95
--- /dev/null
+++ b/spec/ruby/core/string/insert_spec.rb
@@ -0,0 +1,81 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#insert with index, other" do
+ it "inserts other before the character at the given index" do
+ "abcd".insert(0, 'X').should == "Xabcd"
+ "abcd".insert(3, 'X').should == "abcXd"
+ "abcd".insert(4, 'X').should == "abcdX"
+ end
+
+ it "modifies self in place" do
+ a = "abcd"
+ a.insert(4, 'X').should == "abcdX"
+ a.should == "abcdX"
+ end
+
+ it "inserts after the given character on an negative count" do
+ "abcd".insert(-5, 'X').should == "Xabcd"
+ "abcd".insert(-3, 'X').should == "abXcd"
+ "abcd".insert(-1, 'X').should == "abcdX"
+ end
+
+ it "raises an IndexError if the index is beyond string" do
+ -> { "abcd".insert(5, 'X') }.should raise_error(IndexError)
+ -> { "abcd".insert(-6, 'X') }.should raise_error(IndexError)
+ end
+
+ it "converts index to an integer using to_int" do
+ other = mock('-3')
+ other.should_receive(:to_int).and_return(-3)
+
+ "abcd".insert(other, "XYZ").should == "abXYZcd"
+ end
+
+ it "converts other to a string using to_str" do
+ other = mock('XYZ')
+ other.should_receive(:to_str).and_return("XYZ")
+
+ "abcd".insert(-3, other).should == "abXYZcd"
+ end
+
+ it "raises a TypeError if other can't be converted to string" do
+ -> { "abcd".insert(-6, Object.new)}.should raise_error(TypeError)
+ -> { "abcd".insert(-6, []) }.should raise_error(TypeError)
+ -> { "abcd".insert(-6, mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ str = "abcd".freeze
+ -> { str.insert(4, '') }.should raise_error(FrozenError)
+ -> { str.insert(4, 'X') }.should raise_error(FrozenError)
+ end
+
+ it "inserts a character into a multibyte encoded string" do
+ "ã‚りãŒã¨ã†".insert(1, 'ü').should == "ã‚üりãŒã¨ã†"
+ end
+
+ it "returns a String in the compatible encoding" do
+ str = "".force_encoding(Encoding::US_ASCII)
+ str.insert(0, "ã‚りãŒã¨ã†")
+ str.encoding.should == Encoding::UTF_8
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are incompatible" do
+ pat = "ã‚¢".encode Encoding::EUC_JP
+ -> do
+ "ã‚れ".insert 0, pat
+ end.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "should not call subclassed string methods" do
+ cls = Class.new(String) do
+ def replace(arg)
+ raise "should not call replace"
+ end
+ end
+ cls.new("abcd").insert(0, 'X').should == "Xabcd"
+ end
+end
diff --git a/spec/ruby/core/string/inspect_spec.rb b/spec/ruby/core/string/inspect_spec.rb
new file mode 100644
index 0000000000..8bf3d3161f
--- /dev/null
+++ b/spec/ruby/core/string/inspect_spec.rb
@@ -0,0 +1,520 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#inspect" do
+ it "does not return a subclass instance" do
+ StringSpecs::MyString.new.inspect.should be_an_instance_of(String)
+ end
+
+ it "returns a string with special characters replaced with \\<char> notation" do
+ [ ["\a", '"\\a"'],
+ ["\b", '"\\b"'],
+ ["\t", '"\\t"'],
+ ["\n", '"\\n"'],
+ ["\v", '"\\v"'],
+ ["\f", '"\\f"'],
+ ["\r", '"\\r"'],
+ ["\e", '"\\e"']
+ ].should be_computed_by(:inspect)
+ end
+
+ it "returns a string with special characters replaced with \\<char> notation for UTF-16" do
+ pairs = [
+ ["\a", '"\\a"'],
+ ["\b", '"\\b"'],
+ ["\t", '"\\t"'],
+ ["\n", '"\\n"'],
+ ["\v", '"\\v"'],
+ ["\f", '"\\f"'],
+ ["\r", '"\\r"'],
+ ["\e", '"\\e"']
+ ].map { |str, result| [str.encode('UTF-16LE'), result] }
+
+ pairs.should be_computed_by(:inspect)
+ end
+
+ it "returns a string with \" and \\ escaped with a backslash" do
+ [ ["\"", '"\\""'],
+ ["\\", '"\\\\"']
+ ].should be_computed_by(:inspect)
+ end
+
+ it "returns a string with \\#<char> when # is followed by $, @, {" do
+ [ ["\#$", '"\\#$"'],
+ ["\#@", '"\\#@"'],
+ ["\#{", '"\\#{"']
+ ].should be_computed_by(:inspect)
+ end
+
+ it "returns a string with # not escaped when followed by any other character" do
+ [ ["#", '"#"'],
+ ["#1", '"#1"']
+ ].should be_computed_by(:inspect)
+ end
+
+ it "returns a string with printable non-alphanumeric characters unescaped" do
+ [ [" ", '" "'],
+ ["!", '"!"'],
+ ["$", '"$"'],
+ ["%", '"%"'],
+ ["&", '"&"'],
+ ["'", '"\'"'],
+ ["(", '"("'],
+ [")", '")"'],
+ ["*", '"*"'],
+ ["+", '"+"'],
+ [",", '","'],
+ ["-", '"-"'],
+ [".", '"."'],
+ ["/", '"/"'],
+ [":", '":"'],
+ [";", '";"'],
+ ["<", '"<"'],
+ ["=", '"="'],
+ [">", '">"'],
+ ["?", '"?"'],
+ ["@", '"@"'],
+ ["[", '"["'],
+ ["]", '"]"'],
+ ["^", '"^"'],
+ ["_", '"_"'],
+ ["`", '"`"'],
+ ["{", '"{"'],
+ ["|", '"|"'],
+ ["}", '"}"'],
+ ["~", '"~"']
+ ].should be_computed_by(:inspect)
+ end
+
+ it "returns a string with numeric characters unescaped" do
+ [ ["0", '"0"'],
+ ["1", '"1"'],
+ ["2", '"2"'],
+ ["3", '"3"'],
+ ["4", '"4"'],
+ ["5", '"5"'],
+ ["6", '"6"'],
+ ["7", '"7"'],
+ ["8", '"8"'],
+ ["9", '"9"'],
+ ].should be_computed_by(:inspect)
+ end
+
+ it "returns a string with upper-case alpha characters unescaped" do
+ [ ["A", '"A"'],
+ ["B", '"B"'],
+ ["C", '"C"'],
+ ["D", '"D"'],
+ ["E", '"E"'],
+ ["F", '"F"'],
+ ["G", '"G"'],
+ ["H", '"H"'],
+ ["I", '"I"'],
+ ["J", '"J"'],
+ ["K", '"K"'],
+ ["L", '"L"'],
+ ["M", '"M"'],
+ ["N", '"N"'],
+ ["O", '"O"'],
+ ["P", '"P"'],
+ ["Q", '"Q"'],
+ ["R", '"R"'],
+ ["S", '"S"'],
+ ["T", '"T"'],
+ ["U", '"U"'],
+ ["V", '"V"'],
+ ["W", '"W"'],
+ ["X", '"X"'],
+ ["Y", '"Y"'],
+ ["Z", '"Z"']
+ ].should be_computed_by(:inspect)
+ end
+
+ it "returns a string with lower-case alpha characters unescaped" do
+ [ ["a", '"a"'],
+ ["b", '"b"'],
+ ["c", '"c"'],
+ ["d", '"d"'],
+ ["e", '"e"'],
+ ["f", '"f"'],
+ ["g", '"g"'],
+ ["h", '"h"'],
+ ["i", '"i"'],
+ ["j", '"j"'],
+ ["k", '"k"'],
+ ["l", '"l"'],
+ ["m", '"m"'],
+ ["n", '"n"'],
+ ["o", '"o"'],
+ ["p", '"p"'],
+ ["q", '"q"'],
+ ["r", '"r"'],
+ ["s", '"s"'],
+ ["t", '"t"'],
+ ["u", '"u"'],
+ ["v", '"v"'],
+ ["w", '"w"'],
+ ["x", '"x"'],
+ ["y", '"y"'],
+ ["z", '"z"']
+ ].should be_computed_by(:inspect)
+ end
+
+ it "returns a string with non-printing characters replaced by \\x notation" do
+ # Avoid the file encoding by computing the string with #chr.
+ [ [0001.chr, '"\\x01"'],
+ [0002.chr, '"\\x02"'],
+ [0003.chr, '"\\x03"'],
+ [0004.chr, '"\\x04"'],
+ [0005.chr, '"\\x05"'],
+ [0006.chr, '"\\x06"'],
+ [0016.chr, '"\\x0E"'],
+ [0017.chr, '"\\x0F"'],
+ [0020.chr, '"\\x10"'],
+ [0021.chr, '"\\x11"'],
+ [0022.chr, '"\\x12"'],
+ [0023.chr, '"\\x13"'],
+ [0024.chr, '"\\x14"'],
+ [0025.chr, '"\\x15"'],
+ [0026.chr, '"\\x16"'],
+ [0027.chr, '"\\x17"'],
+ [0030.chr, '"\\x18"'],
+ [0031.chr, '"\\x19"'],
+ [0032.chr, '"\\x1A"'],
+ [0034.chr, '"\\x1C"'],
+ [0035.chr, '"\\x1D"'],
+ [0036.chr, '"\\x1E"'],
+ [0037.chr, '"\\x1F"'],
+ [0177.chr, '"\\x7F"'],
+ [0200.chr, '"\\x80"'],
+ [0201.chr, '"\\x81"'],
+ [0202.chr, '"\\x82"'],
+ [0203.chr, '"\\x83"'],
+ [0204.chr, '"\\x84"'],
+ [0205.chr, '"\\x85"'],
+ [0206.chr, '"\\x86"'],
+ [0207.chr, '"\\x87"'],
+ [0210.chr, '"\\x88"'],
+ [0211.chr, '"\\x89"'],
+ [0212.chr, '"\\x8A"'],
+ [0213.chr, '"\\x8B"'],
+ [0214.chr, '"\\x8C"'],
+ [0215.chr, '"\\x8D"'],
+ [0216.chr, '"\\x8E"'],
+ [0217.chr, '"\\x8F"'],
+ [0220.chr, '"\\x90"'],
+ [0221.chr, '"\\x91"'],
+ [0222.chr, '"\\x92"'],
+ [0223.chr, '"\\x93"'],
+ [0224.chr, '"\\x94"'],
+ [0225.chr, '"\\x95"'],
+ [0226.chr, '"\\x96"'],
+ [0227.chr, '"\\x97"'],
+ [0230.chr, '"\\x98"'],
+ [0231.chr, '"\\x99"'],
+ [0232.chr, '"\\x9A"'],
+ [0233.chr, '"\\x9B"'],
+ [0234.chr, '"\\x9C"'],
+ [0235.chr, '"\\x9D"'],
+ [0236.chr, '"\\x9E"'],
+ [0237.chr, '"\\x9F"'],
+ [0240.chr, '"\\xA0"'],
+ [0241.chr, '"\\xA1"'],
+ [0242.chr, '"\\xA2"'],
+ [0243.chr, '"\\xA3"'],
+ [0244.chr, '"\\xA4"'],
+ [0245.chr, '"\\xA5"'],
+ [0246.chr, '"\\xA6"'],
+ [0247.chr, '"\\xA7"'],
+ [0250.chr, '"\\xA8"'],
+ [0251.chr, '"\\xA9"'],
+ [0252.chr, '"\\xAA"'],
+ [0253.chr, '"\\xAB"'],
+ [0254.chr, '"\\xAC"'],
+ [0255.chr, '"\\xAD"'],
+ [0256.chr, '"\\xAE"'],
+ [0257.chr, '"\\xAF"'],
+ [0260.chr, '"\\xB0"'],
+ [0261.chr, '"\\xB1"'],
+ [0262.chr, '"\\xB2"'],
+ [0263.chr, '"\\xB3"'],
+ [0264.chr, '"\\xB4"'],
+ [0265.chr, '"\\xB5"'],
+ [0266.chr, '"\\xB6"'],
+ [0267.chr, '"\\xB7"'],
+ [0270.chr, '"\\xB8"'],
+ [0271.chr, '"\\xB9"'],
+ [0272.chr, '"\\xBA"'],
+ [0273.chr, '"\\xBB"'],
+ [0274.chr, '"\\xBC"'],
+ [0275.chr, '"\\xBD"'],
+ [0276.chr, '"\\xBE"'],
+ [0277.chr, '"\\xBF"'],
+ [0300.chr, '"\\xC0"'],
+ [0301.chr, '"\\xC1"'],
+ [0302.chr, '"\\xC2"'],
+ [0303.chr, '"\\xC3"'],
+ [0304.chr, '"\\xC4"'],
+ [0305.chr, '"\\xC5"'],
+ [0306.chr, '"\\xC6"'],
+ [0307.chr, '"\\xC7"'],
+ [0310.chr, '"\\xC8"'],
+ [0311.chr, '"\\xC9"'],
+ [0312.chr, '"\\xCA"'],
+ [0313.chr, '"\\xCB"'],
+ [0314.chr, '"\\xCC"'],
+ [0315.chr, '"\\xCD"'],
+ [0316.chr, '"\\xCE"'],
+ [0317.chr, '"\\xCF"'],
+ [0320.chr, '"\\xD0"'],
+ [0321.chr, '"\\xD1"'],
+ [0322.chr, '"\\xD2"'],
+ [0323.chr, '"\\xD3"'],
+ [0324.chr, '"\\xD4"'],
+ [0325.chr, '"\\xD5"'],
+ [0326.chr, '"\\xD6"'],
+ [0327.chr, '"\\xD7"'],
+ [0330.chr, '"\\xD8"'],
+ [0331.chr, '"\\xD9"'],
+ [0332.chr, '"\\xDA"'],
+ [0333.chr, '"\\xDB"'],
+ [0334.chr, '"\\xDC"'],
+ [0335.chr, '"\\xDD"'],
+ [0336.chr, '"\\xDE"'],
+ [0337.chr, '"\\xDF"'],
+ [0340.chr, '"\\xE0"'],
+ [0341.chr, '"\\xE1"'],
+ [0342.chr, '"\\xE2"'],
+ [0343.chr, '"\\xE3"'],
+ [0344.chr, '"\\xE4"'],
+ [0345.chr, '"\\xE5"'],
+ [0346.chr, '"\\xE6"'],
+ [0347.chr, '"\\xE7"'],
+ [0350.chr, '"\\xE8"'],
+ [0351.chr, '"\\xE9"'],
+ [0352.chr, '"\\xEA"'],
+ [0353.chr, '"\\xEB"'],
+ [0354.chr, '"\\xEC"'],
+ [0355.chr, '"\\xED"'],
+ [0356.chr, '"\\xEE"'],
+ [0357.chr, '"\\xEF"'],
+ [0360.chr, '"\\xF0"'],
+ [0361.chr, '"\\xF1"'],
+ [0362.chr, '"\\xF2"'],
+ [0363.chr, '"\\xF3"'],
+ [0364.chr, '"\\xF4"'],
+ [0365.chr, '"\\xF5"'],
+ [0366.chr, '"\\xF6"'],
+ [0367.chr, '"\\xF7"'],
+ [0370.chr, '"\\xF8"'],
+ [0371.chr, '"\\xF9"'],
+ [0372.chr, '"\\xFA"'],
+ [0373.chr, '"\\xFB"'],
+ [0374.chr, '"\\xFC"'],
+ [0375.chr, '"\\xFD"'],
+ [0376.chr, '"\\xFE"'],
+ [0377.chr, '"\\xFF"']
+ ].should be_computed_by(:inspect)
+ end
+
+ it "returns a string with a NUL character replaced by \\x notation" do
+ 0.chr.inspect.should == '"\\x00"'
+ end
+
+ it "uses \\x notation for broken UTF-8 sequences" do
+ "\xF0\x9F".inspect.should == '"\\xF0\\x9F"'
+ end
+
+ it "works for broken US-ASCII strings" do
+ s = "©".force_encoding("US-ASCII")
+ s.inspect.should == '"\xC2\xA9"'
+ end
+
+ describe "when default external is UTF-8" do
+ before :each do
+ @extenc, Encoding.default_external = Encoding.default_external, Encoding::UTF_8
+ end
+
+ after :each do
+ Encoding.default_external = @extenc
+ end
+
+ it "returns a string with non-printing characters replaced by \\u notation for Unicode strings" do
+ [ [0001.chr('utf-8'), '"\u0001"'],
+ [0002.chr('utf-8'), '"\u0002"'],
+ [0003.chr('utf-8'), '"\u0003"'],
+ [0004.chr('utf-8'), '"\u0004"'],
+ [0005.chr('utf-8'), '"\u0005"'],
+ [0006.chr('utf-8'), '"\u0006"'],
+ [0016.chr('utf-8'), '"\u000E"'],
+ [0017.chr('utf-8'), '"\u000F"'],
+ [0020.chr('utf-8'), '"\u0010"'],
+ [0021.chr('utf-8'), '"\u0011"'],
+ [0022.chr('utf-8'), '"\u0012"'],
+ [0023.chr('utf-8'), '"\u0013"'],
+ [0024.chr('utf-8'), '"\u0014"'],
+ [0025.chr('utf-8'), '"\u0015"'],
+ [0026.chr('utf-8'), '"\u0016"'],
+ [0027.chr('utf-8'), '"\u0017"'],
+ [0030.chr('utf-8'), '"\u0018"'],
+ [0031.chr('utf-8'), '"\u0019"'],
+ [0032.chr('utf-8'), '"\u001A"'],
+ [0034.chr('utf-8'), '"\u001C"'],
+ [0035.chr('utf-8'), '"\u001D"'],
+ [0036.chr('utf-8'), '"\u001E"'],
+ [0037.chr('utf-8'), '"\u001F"'],
+ [0177.chr('utf-8'), '"\u007F"'],
+ [0200.chr('utf-8'), '"\u0080"'],
+ [0201.chr('utf-8'), '"\u0081"'],
+ [0202.chr('utf-8'), '"\u0082"'],
+ [0203.chr('utf-8'), '"\u0083"'],
+ [0204.chr('utf-8'), '"\u0084"'],
+ [0206.chr('utf-8'), '"\u0086"'],
+ [0207.chr('utf-8'), '"\u0087"'],
+ [0210.chr('utf-8'), '"\u0088"'],
+ [0211.chr('utf-8'), '"\u0089"'],
+ [0212.chr('utf-8'), '"\u008A"'],
+ [0213.chr('utf-8'), '"\u008B"'],
+ [0214.chr('utf-8'), '"\u008C"'],
+ [0215.chr('utf-8'), '"\u008D"'],
+ [0216.chr('utf-8'), '"\u008E"'],
+ [0217.chr('utf-8'), '"\u008F"'],
+ [0220.chr('utf-8'), '"\u0090"'],
+ [0221.chr('utf-8'), '"\u0091"'],
+ [0222.chr('utf-8'), '"\u0092"'],
+ [0223.chr('utf-8'), '"\u0093"'],
+ [0224.chr('utf-8'), '"\u0094"'],
+ [0225.chr('utf-8'), '"\u0095"'],
+ [0226.chr('utf-8'), '"\u0096"'],
+ [0227.chr('utf-8'), '"\u0097"'],
+ [0230.chr('utf-8'), '"\u0098"'],
+ [0231.chr('utf-8'), '"\u0099"'],
+ [0232.chr('utf-8'), '"\u009A"'],
+ [0233.chr('utf-8'), '"\u009B"'],
+ [0234.chr('utf-8'), '"\u009C"'],
+ [0235.chr('utf-8'), '"\u009D"'],
+ [0236.chr('utf-8'), '"\u009E"'],
+ [0237.chr('utf-8'), '"\u009F"'],
+ ].should be_computed_by(:inspect)
+ end
+
+ it "returns a string with a NUL character replaced by \\u notation" do
+ 0.chr('utf-8').inspect.should == '"\\u0000"'
+ end
+
+ it "returns a string with extended characters for Unicode strings" do
+ [ [0240.chr('utf-8'), '" "'],
+ [0241.chr('utf-8'), '"¡"'],
+ [0242.chr('utf-8'), '"¢"'],
+ [0243.chr('utf-8'), '"£"'],
+ [0244.chr('utf-8'), '"¤"'],
+ [0245.chr('utf-8'), '"Â¥"'],
+ [0246.chr('utf-8'), '"¦"'],
+ [0247.chr('utf-8'), '"§"'],
+ [0250.chr('utf-8'), '"¨"'],
+ [0251.chr('utf-8'), '"©"'],
+ [0252.chr('utf-8'), '"ª"'],
+ [0253.chr('utf-8'), '"«"'],
+ [0254.chr('utf-8'), '"¬"'],
+ [0255.chr('utf-8'), '"­"'],
+ [0256.chr('utf-8'), '"®"'],
+ [0257.chr('utf-8'), '"¯"'],
+ [0260.chr('utf-8'), '"°"'],
+ [0261.chr('utf-8'), '"±"'],
+ [0262.chr('utf-8'), '"²"'],
+ [0263.chr('utf-8'), '"³"'],
+ [0264.chr('utf-8'), '"´"'],
+ [0265.chr('utf-8'), '"µ"'],
+ [0266.chr('utf-8'), '"¶"'],
+ [0267.chr('utf-8'), '"·"'],
+ [0270.chr('utf-8'), '"¸"'],
+ [0271.chr('utf-8'), '"¹"'],
+ [0272.chr('utf-8'), '"º"'],
+ [0273.chr('utf-8'), '"»"'],
+ [0274.chr('utf-8'), '"¼"'],
+ [0275.chr('utf-8'), '"½"'],
+ [0276.chr('utf-8'), '"¾"'],
+ [0277.chr('utf-8'), '"¿"'],
+ [0300.chr('utf-8'), '"À"'],
+ [0301.chr('utf-8'), '"Ã"'],
+ [0302.chr('utf-8'), '"Â"'],
+ [0303.chr('utf-8'), '"Ã"'],
+ [0304.chr('utf-8'), '"Ä"'],
+ [0305.chr('utf-8'), '"Ã…"'],
+ [0306.chr('utf-8'), '"Æ"'],
+ [0307.chr('utf-8'), '"Ç"'],
+ [0310.chr('utf-8'), '"È"'],
+ [0311.chr('utf-8'), '"É"'],
+ [0312.chr('utf-8'), '"Ê"'],
+ [0313.chr('utf-8'), '"Ë"'],
+ [0314.chr('utf-8'), '"Ì"'],
+ [0315.chr('utf-8'), '"Ã"'],
+ [0316.chr('utf-8'), '"ÃŽ"'],
+ [0317.chr('utf-8'), '"Ã"'],
+ [0320.chr('utf-8'), '"Ã"'],
+ [0321.chr('utf-8'), '"Ñ"'],
+ [0322.chr('utf-8'), '"Ã’"'],
+ [0323.chr('utf-8'), '"Ó"'],
+ [0324.chr('utf-8'), '"Ô"'],
+ [0325.chr('utf-8'), '"Õ"'],
+ [0326.chr('utf-8'), '"Ö"'],
+ [0327.chr('utf-8'), '"×"'],
+ [0330.chr('utf-8'), '"Ø"'],
+ [0331.chr('utf-8'), '"Ù"'],
+ [0332.chr('utf-8'), '"Ú"'],
+ [0333.chr('utf-8'), '"Û"'],
+ [0334.chr('utf-8'), '"Ü"'],
+ [0335.chr('utf-8'), '"Ã"'],
+ [0336.chr('utf-8'), '"Þ"'],
+ [0337.chr('utf-8'), '"ß"'],
+ [0340.chr('utf-8'), '"à"'],
+ [0341.chr('utf-8'), '"á"'],
+ [0342.chr('utf-8'), '"â"'],
+ [0343.chr('utf-8'), '"ã"'],
+ [0344.chr('utf-8'), '"ä"'],
+ [0345.chr('utf-8'), '"Ã¥"'],
+ [0346.chr('utf-8'), '"æ"'],
+ [0347.chr('utf-8'), '"ç"'],
+ [0350.chr('utf-8'), '"è"'],
+ [0351.chr('utf-8'), '"é"'],
+ [0352.chr('utf-8'), '"ê"'],
+ [0353.chr('utf-8'), '"ë"'],
+ [0354.chr('utf-8'), '"ì"'],
+ [0355.chr('utf-8'), '"í"'],
+ [0356.chr('utf-8'), '"î"'],
+ [0357.chr('utf-8'), '"ï"'],
+ [0360.chr('utf-8'), '"ð"'],
+ [0361.chr('utf-8'), '"ñ"'],
+ [0362.chr('utf-8'), '"ò"'],
+ [0363.chr('utf-8'), '"ó"'],
+ [0364.chr('utf-8'), '"ô"'],
+ [0365.chr('utf-8'), '"õ"'],
+ [0366.chr('utf-8'), '"ö"'],
+ [0367.chr('utf-8'), '"÷"'],
+ [0370.chr('utf-8'), '"ø"'],
+ [0371.chr('utf-8'), '"ù"'],
+ [0372.chr('utf-8'), '"ú"'],
+ [0373.chr('utf-8'), '"û"'],
+ [0374.chr('utf-8'), '"ü"'],
+ [0375.chr('utf-8'), '"ý"'],
+ [0376.chr('utf-8'), '"þ"'],
+ [0377.chr('utf-8'), '"ÿ"']
+ ].should be_computed_by(:inspect)
+ end
+ end
+
+ describe "when the string's encoding is different than the result's encoding" do
+ describe "and the string's encoding is ASCII-compatible but the characters are non-ASCII" do
+ it "returns a string with the non-ASCII characters replaced by \\x notation" do
+ "\u{3042}".encode("EUC-JP").inspect.should == '"\\x{A4A2}"'
+ end
+ end
+
+ describe "and the string has both ASCII-compatible and ASCII-incompatible chars" do
+ it "returns a string with the non-ASCII characters replaced by \\u notation" do
+ "hello привет".encode("utf-16le").inspect.should == '"hello \\u043F\\u0440\\u0438\\u0432\\u0435\\u0442"'
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/string/intern_spec.rb b/spec/ruby/core/string/intern_spec.rb
new file mode 100644
index 0000000000..cd7dad4359
--- /dev/null
+++ b/spec/ruby/core/string/intern_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/to_sym'
+
+describe "String#intern" do
+ it_behaves_like :string_to_sym, :intern
+end
diff --git a/spec/ruby/core/string/length_spec.rb b/spec/ruby/core/string/length_spec.rb
new file mode 100644
index 0000000000..98cee1f03d
--- /dev/null
+++ b/spec/ruby/core/string/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/length'
+
+describe "String#length" do
+ it_behaves_like :string_length, :length
+end
diff --git a/spec/ruby/core/string/lines_spec.rb b/spec/ruby/core/string/lines_spec.rb
new file mode 100644
index 0000000000..40ab5f71d8
--- /dev/null
+++ b/spec/ruby/core/string/lines_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/each_line'
+
+describe "String#lines" do
+ it_behaves_like :string_each_line, :lines
+
+ it "returns an array when no block given" do
+ ary = "hello world".lines(' ')
+ ary.should == ["hello ", "world"]
+ end
+
+ context "when `chomp` keyword argument is passed" do
+ it "removes new line characters" do
+ "hello \nworld\n".lines(chomp: true).should == ["hello ", "world"]
+ "hello \r\nworld\r\n".lines(chomp: true).should == ["hello ", "world"]
+ end
+ end
+end
diff --git a/spec/ruby/core/string/ljust_spec.rb b/spec/ruby/core/string/ljust_spec.rb
new file mode 100644
index 0000000000..9a25d3abd4
--- /dev/null
+++ b/spec/ruby/core/string/ljust_spec.rb
@@ -0,0 +1,113 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#ljust with length, padding" do
+ it "returns a new string of specified length with self left justified and padded with padstr" do
+ "hello".ljust(20, '1234').should == "hello123412341234123"
+
+ "".ljust(1, "abcd").should == "a"
+ "".ljust(2, "abcd").should == "ab"
+ "".ljust(3, "abcd").should == "abc"
+ "".ljust(4, "abcd").should == "abcd"
+ "".ljust(6, "abcd").should == "abcdab"
+
+ "OK".ljust(3, "abcd").should == "OKa"
+ "OK".ljust(4, "abcd").should == "OKab"
+ "OK".ljust(6, "abcd").should == "OKabcd"
+ "OK".ljust(8, "abcd").should == "OKabcdab"
+ end
+
+ it "pads with whitespace if no padstr is given" do
+ "hello".ljust(20).should == "hello "
+ end
+
+ it "returns self if it's longer than or as long as the specified length" do
+ "".ljust(0).should == ""
+ "".ljust(-1).should == ""
+ "hello".ljust(4).should == "hello"
+ "hello".ljust(-1).should == "hello"
+ "this".ljust(3).should == "this"
+ "radiology".ljust(8, '-').should == "radiology"
+ end
+
+ it "tries to convert length to an integer using to_int" do
+ "^".ljust(3.8, "_^").should == "^_^"
+
+ obj = mock('3')
+ obj.should_receive(:to_int).and_return(3)
+
+ "o".ljust(obj, "_o").should == "o_o"
+ end
+
+ it "raises a TypeError when length can't be converted to an integer" do
+ -> { "hello".ljust("x") }.should raise_error(TypeError)
+ -> { "hello".ljust("x", "y") }.should raise_error(TypeError)
+ -> { "hello".ljust([]) }.should raise_error(TypeError)
+ -> { "hello".ljust(mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "tries to convert padstr to a string using to_str" do
+ padstr = mock('123')
+ padstr.should_receive(:to_str).and_return("123")
+
+ "hello".ljust(10, padstr).should == "hello12312"
+ end
+
+ it "raises a TypeError when padstr can't be converted" do
+ -> { "hello".ljust(20, []) }.should raise_error(TypeError)
+ -> { "hello".ljust(20, Object.new)}.should raise_error(TypeError)
+ -> { "hello".ljust(20, mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError when padstr is empty" do
+ -> { "hello".ljust(10, '') }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on subclasses" do
+ StringSpecs::MyString.new("").ljust(10).should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("foo").ljust(10).should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("foo").ljust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(StringSpecs::MyString)
+
+ "".ljust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ "foo".ljust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on subclasses" do
+ StringSpecs::MyString.new("").ljust(10).should be_an_instance_of(String)
+ StringSpecs::MyString.new("foo").ljust(10).should be_an_instance_of(String)
+ StringSpecs::MyString.new("foo").ljust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+
+ "".ljust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ "foo".ljust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ end
+ end
+
+ describe "with width" do
+ it "returns a String in the same encoding as the original" do
+ str = "abc".force_encoding Encoding::IBM437
+ result = str.ljust 5
+ result.should == "abc "
+ result.encoding.should equal(Encoding::IBM437)
+ end
+ end
+
+ describe "with width, pattern" do
+ it "returns a String in the compatible encoding" do
+ str = "abc".force_encoding Encoding::IBM437
+ result = str.ljust 5, "ã‚"
+ result.should == "abcã‚ã‚"
+ result.encoding.should equal(Encoding::UTF_8)
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are incompatible" do
+ pat = "ã‚¢".encode Encoding::EUC_JP
+ -> do
+ "ã‚れ".ljust 5, pat
+ end.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/lstrip_spec.rb b/spec/ruby/core/string/lstrip_spec.rb
new file mode 100644
index 0000000000..75434613f1
--- /dev/null
+++ b/spec/ruby/core/string/lstrip_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/strip'
+
+describe "String#lstrip" do
+ it_behaves_like :string_strip, :lstrip
+
+ it "returns a copy of self with leading whitespace removed" do
+ " hello ".lstrip.should == "hello "
+ " hello world ".lstrip.should == "hello world "
+ "\n\r\t\n\v\r hello world ".lstrip.should == "hello world "
+ "hello".lstrip.should == "hello"
+ " ã“ã«ã¡ã‚".lstrip.should == "ã“ã«ã¡ã‚"
+ end
+
+ it "works with lazy substrings" do
+ " hello "[1...-1].lstrip.should == "hello "
+ " hello world "[1...-1].lstrip.should == "hello world "
+ "\n\r\t\n\v\r hello world "[1...-1].lstrip.should == "hello world "
+ " ã“ã«ã¡ã‚ "[1...-1].lstrip.should == "ã“ã«ã¡ã‚"
+ end
+
+ ruby_version_is '3.0' do
+ it "strips leading \\0" do
+ "\x00hello".lstrip.should == "hello"
+ "\000 \000hello\000 \000".lstrip.should == "hello\000 \000"
+ end
+ end
+end
+
+describe "String#lstrip!" do
+ it "modifies self in place and returns self" do
+ a = " hello "
+ a.lstrip!.should equal(a)
+ a.should == "hello "
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "hello"
+ a.lstrip!.should == nil
+ a.should == "hello"
+ end
+
+ it "makes a string empty if it is only whitespace" do
+ "".lstrip!.should == nil
+ " ".lstrip.should == ""
+ " ".lstrip.should == ""
+ end
+
+ ruby_version_is '3.0' do
+ it "removes leading NULL bytes and whitespace" do
+ a = "\000 \000hello\000 \000"
+ a.lstrip!
+ a.should == "hello\000 \000"
+ end
+ end
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ -> { " hello ".freeze.lstrip! }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23657]
+ it "raises a FrozenError on a frozen instance that would not be modified" do
+ -> { "hello".freeze.lstrip! }.should raise_error(FrozenError)
+ -> { "".freeze.lstrip! }.should raise_error(FrozenError)
+ end
+
+ it "raises an ArgumentError if the first non-space codepoint is invalid" do
+ s = "\xDFabc".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.lstrip! }.should raise_error(ArgumentError)
+
+ s = " \xDFabc".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.lstrip! }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/match_spec.rb b/spec/ruby/core/string/match_spec.rb
new file mode 100644
index 0000000000..5e988f34ca
--- /dev/null
+++ b/spec/ruby/core/string/match_spec.rb
@@ -0,0 +1,167 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :string_match_escaped_literal, shared: true do
+ not_supported_on :opal do
+ it "matches a literal Regexp that uses ASCII-only UTF-8 escape sequences" do
+ "a b".match(/([\u{20}-\u{7e}])/)[0].should == "a"
+ end
+ end
+end
+
+describe "String#=~" do
+ it "behaves the same way as index() when given a regexp" do
+ ("rudder" =~ /udder/).should == "rudder".index(/udder/)
+ ("boat" =~ /[^fl]oat/).should == "boat".index(/[^fl]oat/)
+ ("bean" =~ /bag/).should == "bean".index(/bag/)
+ ("true" =~ /false/).should == "true".index(/false/)
+ end
+
+ it "raises a TypeError if a obj is a string" do
+ -> { "some string" =~ "another string" }.should raise_error(TypeError)
+ -> { "a" =~ StringSpecs::MyString.new("b") }.should raise_error(TypeError)
+ end
+
+ it "invokes obj.=~ with self if obj is neither a string nor regexp" do
+ str = "w00t"
+ obj = mock('x')
+
+ obj.should_receive(:=~).with(str).any_number_of_times.and_return(true)
+ str.should =~ obj
+
+ obj = mock('y')
+ obj.should_receive(:=~).with(str).any_number_of_times.and_return(false)
+ str.should_not =~ obj
+ end
+
+ it "sets $~ to MatchData when there is a match and nil when there's none" do
+ 'hello' =~ /./
+ $~[0].should == 'h'
+
+ 'hello' =~ /not/
+ $~.should == nil
+ end
+
+ it "returns the character index of a found match" do
+ ("ã“ã«ã¡ã‚" =~ /ã«/).should == 1
+ end
+
+end
+
+describe "String#match" do
+ it "matches the pattern against self" do
+ 'hello'.match(/(.)\1/)[0].should == 'll'
+ end
+
+ it_behaves_like :string_match_escaped_literal, :match
+
+ describe "with [pattern, position]" do
+ describe "when given a positive position" do
+ it "matches the pattern against self starting at an optional index" do
+ "01234".match(/(.).(.)/, 1).captures.should == ["1", "3"]
+ end
+
+ it "uses the start as a character offset" do
+ "零一二三四".match(/(.).(.)/, 1).captures.should == ["一", "三"]
+ end
+ end
+
+ describe "when given a negative position" do
+ it "matches the pattern against self starting at an optional index" do
+ "01234".match(/(.).(.)/, -4).captures.should == ["1", "3"]
+ end
+
+ it "uses the start as a character offset" do
+ "零一二三四".match(/(.).(.)/, -4).captures.should == ["一", "三"]
+ end
+ end
+ end
+
+ describe "when passed a block" do
+ it "yields the MatchData" do
+ "abc".match(/./) {|m| ScratchPad.record m }
+ ScratchPad.recorded.should be_kind_of(MatchData)
+ end
+
+ it "returns the block result" do
+ "abc".match(/./) { :result }.should == :result
+ end
+
+ it "does not yield if there is no match" do
+ ScratchPad.record []
+ "b".match(/a/) {|m| ScratchPad << m }
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "tries to convert pattern to a string via to_str" do
+ obj = mock('.')
+ def obj.to_str() "." end
+ "hello".match(obj)[0].should == "h"
+
+ obj = mock('.')
+ def obj.respond_to?(type, *) true end
+ def obj.method_missing(*args) "." end
+ "hello".match(obj)[0].should == "h"
+ end
+
+ it "raises a TypeError if pattern is not a regexp or a string" do
+ -> { 'hello'.match(10) }.should raise_error(TypeError)
+ not_supported_on :opal do
+ -> { 'hello'.match(:ell) }.should raise_error(TypeError)
+ end
+ end
+
+ it "converts string patterns to regexps without escaping" do
+ 'hello'.match('(.)\1')[0].should == 'll'
+ end
+
+ it "returns nil if there's no match" do
+ 'hello'.match('xx').should == nil
+ end
+
+ it "matches \\G at the start of the string" do
+ 'hello'.match(/\Gh/)[0].should == 'h'
+ 'hello'.match(/\Go/).should == nil
+ end
+
+ it "sets $~ to MatchData of match or nil when there is none" do
+ 'hello'.match(/./)
+ $~[0].should == 'h'
+ Regexp.last_match[0].should == 'h'
+
+ 'hello'.match(/X/)
+ $~.should == nil
+ Regexp.last_match.should == nil
+ end
+
+ it "calls match on the regular expression" do
+ regexp = /./.dup
+ regexp.should_receive(:match).and_return(:foo)
+ 'hello'.match(regexp).should == :foo
+ end
+end
+
+describe "String#match?" do
+ before :each do
+ # Resetting Regexp.last_match
+ /DONTMATCH/.match ''
+ end
+
+ context "when matches the given regex" do
+ it "returns true but does not set Regexp.last_match" do
+ 'string'.match?(/string/i).should be_true
+ Regexp.last_match.should be_nil
+ end
+ end
+
+ it "returns false when does not match the given regex" do
+ 'string'.match?(/STRING/).should be_false
+ end
+
+ it "takes matching position as the 2nd argument" do
+ 'string'.match?(/str/i, 0).should be_true
+ 'string'.match?(/str/i, 1).should be_false
+ end
+end
diff --git a/spec/ruby/core/string/modulo_spec.rb b/spec/ruby/core/string/modulo_spec.rb
new file mode 100644
index 0000000000..bf96a82874
--- /dev/null
+++ b/spec/ruby/core/string/modulo_spec.rb
@@ -0,0 +1,778 @@
+require_relative '../../spec_helper'
+require_relative '../kernel/shared/sprintf'
+require_relative '../kernel/shared/sprintf_encoding'
+require_relative 'fixtures/classes'
+require_relative '../../shared/hash/key_error'
+
+describe "String#%" do
+ it_behaves_like :kernel_sprintf, -> format, *args {
+ format % args
+ }
+
+ it_behaves_like :kernel_sprintf_encoding, -> format, *args {
+ format % args
+ }
+end
+
+# TODO: these specs are mostly redundant with kernel/shared/sprintf.rb specs.
+# These specs should be moved there and deduplicated.
+describe "String#%" do
+ context "when key is missing from passed-in hash" do
+ it_behaves_like :key_error, -> obj, key { "%{#{key}}" % obj }, { a: 5 }
+ end
+
+ it "formats multiple expressions" do
+ ("%b %x %d %s" % [10, 10, 10, 10]).should == "1010 a 10 10"
+ end
+
+ it "formats expressions mid string" do
+ ("hello %s!" % "world").should == "hello world!"
+ end
+
+ it "formats %% into %" do
+ ("%d%% %s" % [10, "of chickens!"]).should == "10% of chickens!"
+ end
+
+ describe "output's encoding" do
+ it "is the same as the format string if passed value is encoding-compatible" do
+ [Encoding::BINARY, Encoding::US_ASCII, Encoding::UTF_8, Encoding::SHIFT_JIS].each do |encoding|
+ ("hello %s!".encode(encoding) % "world").encoding.should == encoding
+ end
+ end
+
+ it "negotiates a compatible encoding if necessary" do
+ ("hello %s" % 195.chr).encoding.should == Encoding::BINARY
+ ("hello %s".encode("shift_jis") % "wörld").encoding.should == Encoding::UTF_8
+ end
+
+ it "raises if a compatible encoding can't be found" do
+ -> { "hello %s".encode("utf-8") % "world".encode("UTF-16LE") }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+
+ it "raises an error if single % appears at the end" do
+ -> { ("%" % []) }.should raise_error(ArgumentError)
+ -> { ("foo%" % [])}.should raise_error(ArgumentError)
+ end
+
+ it "formats single % character before a newline as literal %" do
+ ("%\n" % []).should == "%\n"
+ ("foo%\n" % []).should == "foo%\n"
+ ("%\n.3f" % 1.2).should == "%\n.3f"
+ end
+
+ it "formats single % character before a NUL as literal %" do
+ ("%\0" % []).should == "%\0"
+ ("foo%\0" % []).should == "foo%\0"
+ ("%\0.3f" % 1.2).should == "%\0.3f"
+ end
+
+ it "raises an error if single % appears anywhere else" do
+ -> { (" % " % []) }.should raise_error(ArgumentError)
+ -> { ("foo%quux" % []) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error if NULL or \\n appear anywhere else in the format string" do
+ begin
+ old_debug, $DEBUG = $DEBUG, false
+
+ -> { "%.\n3f" % 1.2 }.should raise_error(ArgumentError)
+ -> { "%.3\nf" % 1.2 }.should raise_error(ArgumentError)
+ -> { "%.\03f" % 1.2 }.should raise_error(ArgumentError)
+ -> { "%.3\0f" % 1.2 }.should raise_error(ArgumentError)
+ ensure
+ $DEBUG = old_debug
+ end
+ end
+
+ it "ignores unused arguments when $DEBUG is false" do
+ begin
+ old_debug = $DEBUG
+ $DEBUG = false
+
+ ("" % [1, 2, 3]).should == ""
+ ("%s" % [1, 2, 3]).should == "1"
+ ensure
+ $DEBUG = old_debug
+ end
+ end
+
+ it "raises an ArgumentError for unused arguments when $DEBUG is true" do
+ begin
+ old_debug = $DEBUG
+ $DEBUG = true
+ s = $stderr
+ $stderr = IOStub.new
+
+ -> { "" % [1, 2, 3] }.should raise_error(ArgumentError)
+ -> { "%s" % [1, 2, 3] }.should raise_error(ArgumentError)
+ ensure
+ $DEBUG = old_debug
+ $stderr = s
+ end
+ end
+
+ it "always allows unused arguments when positional argument style is used" do
+ begin
+ old_debug = $DEBUG
+ $DEBUG = false
+
+ ("%2$s" % [1, 2, 3]).should == "2"
+ $DEBUG = true
+ ("%2$s" % [1, 2, 3]).should == "2"
+ ensure
+ $DEBUG = old_debug
+ end
+ end
+
+ it "replaces trailing absolute argument specifier without type with percent sign" do
+ ("hello %1$" % "foo").should == "hello %"
+ end
+
+ it "raises an ArgumentError when given invalid argument specifiers" do
+ -> { "%1" % [] }.should raise_error(ArgumentError)
+ -> { "%+" % [] }.should raise_error(ArgumentError)
+ -> { "%-" % [] }.should raise_error(ArgumentError)
+ -> { "%#" % [] }.should raise_error(ArgumentError)
+ -> { "%0" % [] }.should raise_error(ArgumentError)
+ -> { "%*" % [] }.should raise_error(ArgumentError)
+ -> { "%." % [] }.should raise_error(ArgumentError)
+ -> { "%_" % [] }.should raise_error(ArgumentError)
+ -> { "%0$s" % "x" }.should raise_error(ArgumentError)
+ -> { "%*0$s" % [5, "x"] }.should raise_error(ArgumentError)
+ -> { "%*1$.*0$1$s" % [1, 2, 3] }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when multiple positional argument tokens are given for one format specifier" do
+ -> { "%1$1$s" % "foo" }.should raise_error(ArgumentError)
+ end
+
+ it "respects positional arguments and precision tokens given for one format specifier" do
+ ("%2$1d" % [1, 0]).should == "0"
+ ("%2$1d" % [0, 1]).should == "1"
+
+ ("%2$.2f" % [1, 0]).should == "0.00"
+ ("%2$.2f" % [0, 1]).should == "1.00"
+ end
+
+ it "allows more than one digit of position" do
+ ("%50$d" % (0..100).to_a).should == "49"
+ end
+
+ it "raises an ArgumentError when multiple width star tokens are given for one format specifier" do
+ -> { "%**s" % [5, 5, 5] }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when a width star token is seen after a width token" do
+ -> { "%5*s" % [5, 5] }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when multiple precision tokens are given" do
+ -> { "%.5.5s" % 5 }.should raise_error(ArgumentError)
+ -> { "%.5.*s" % [5, 5] }.should raise_error(ArgumentError)
+ -> { "%.*.5s" % [5, 5] }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when there are less arguments than format specifiers" do
+ ("foo" % []).should == "foo"
+ -> { "%s" % [] }.should raise_error(ArgumentError)
+ -> { "%s %s" % [1] }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when absolute and relative argument numbers are mixed" do
+ -> { "%s %1$s" % "foo" }.should raise_error(ArgumentError)
+ -> { "%1$s %s" % "foo" }.should raise_error(ArgumentError)
+
+ -> { "%s %2$s" % ["foo", "bar"] }.should raise_error(ArgumentError)
+ -> { "%2$s %s" % ["foo", "bar"] }.should raise_error(ArgumentError)
+
+ -> { "%*2$s" % [5, 5, 5] }.should raise_error(ArgumentError)
+ -> { "%*.*2$s" % [5, 5, 5] }.should raise_error(ArgumentError)
+ -> { "%*2$.*2$s" % [5, 5, 5] }.should raise_error(ArgumentError)
+ -> { "%*.*2$s" % [5, 5, 5] }.should raise_error(ArgumentError)
+ end
+
+ it "allows reuse of the one argument multiple via absolute argument numbers" do
+ ("%1$s %1$s" % "foo").should == "foo foo"
+ ("%1$s %2$s %1$s %2$s" % ["foo", "bar"]).should == "foo bar foo bar"
+ end
+
+ it "always interprets an array argument as a list of argument parameters" do
+ -> { "%p" % [] }.should raise_error(ArgumentError)
+ ("%p" % [1]).should == "1"
+ ("%p %p" % [1, 2]).should == "1 2"
+ end
+
+ it "always interprets an array subclass argument as a list of argument parameters" do
+ -> { "%p" % StringSpecs::MyArray[] }.should raise_error(ArgumentError)
+ ("%p" % StringSpecs::MyArray[1]).should == "1"
+ ("%p %p" % StringSpecs::MyArray[1, 2]).should == "1 2"
+ end
+
+ it "allows positional arguments for width star and precision star arguments" do
+ ("%*1$.*2$3$d" % [10, 5, 1]).should == " 00001"
+ end
+
+ it "allows negative width to imply '-' flag" do
+ ("%*1$.*2$3$d" % [-10, 5, 1]).should == "00001 "
+ ("%-*1$.*2$3$d" % [10, 5, 1]).should == "00001 "
+ ("%-*1$.*2$3$d" % [-10, 5, 1]).should == "00001 "
+ end
+
+ it "ignores negative precision" do
+ ("%*1$.*2$3$d" % [10, -5, 1]).should == " 1"
+ end
+
+ it "allows a star to take an argument number to use as the width" do
+ ("%1$*2$s" % ["a", 8]).should == " a"
+ ("%1$*10$s" % ["a",0,0,0,0,0,0,0,0,8]).should == " a"
+ end
+
+ it "calls to_int on width star and precision star tokens" do
+ w = mock('10')
+ w.should_receive(:to_int).and_return(10)
+
+ p = mock('5')
+ p.should_receive(:to_int).and_return(5)
+
+ ("%*.*f" % [w, p, 1]).should == " 1.00000"
+
+
+ w = mock('10')
+ w.should_receive(:to_int).and_return(10)
+
+ p = mock('5')
+ p.should_receive(:to_int).and_return(5)
+
+ ("%*.*d" % [w, p, 1]).should == " 00001"
+ end
+
+ it "does not call #to_a to convert the argument" do
+ x = mock("string modulo to_a")
+ x.should_not_receive(:to_a)
+ x.should_receive(:to_s).and_return("x")
+
+ ("%s" % x).should == "x"
+ end
+
+ it "calls #to_ary to convert the argument" do
+ x = mock("string modulo to_ary")
+ x.should_not_receive(:to_s)
+ x.should_receive(:to_ary).and_return(["x"])
+
+ ("%s" % x).should == "x"
+ end
+
+ it "wraps the object in an Array if #to_ary returns nil" do
+ x = mock("string modulo to_ary")
+ x.should_receive(:to_ary).and_return(nil)
+ x.should_receive(:to_s).and_return("x")
+
+ ("%s" % x).should == "x"
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ x = mock("string modulo to_ary")
+ x.should_receive(:to_ary).and_return("x")
+
+ -> { "%s" % x }.should raise_error(TypeError)
+ end
+
+ it "tries to convert the argument to Array by calling #to_ary" do
+ obj = mock('[1,2]')
+ def obj.to_ary() [1, 2] end
+ def obj.to_s() "obj" end
+ ("%s %s" % obj).should == "1 2"
+ ("%s" % obj).should == "1"
+ end
+
+ it "doesn't return subclass instances when called on a subclass" do
+ universal = mock('0')
+ def universal.to_int() 0 end
+ def universal.to_str() "0" end
+ def universal.to_f() 0.0 end
+
+ [
+ "", "foo",
+ "%b", "%B", "%c", "%d", "%e", "%E",
+ "%f", "%g", "%G", "%i", "%o", "%p",
+ "%s", "%u", "%x", "%X"
+ ].each do |format|
+ (StringSpecs::MyString.new(format) % universal).should be_an_instance_of(String)
+ end
+ end
+
+ it "supports binary formats using %b for positive numbers" do
+ ("%b" % 10).should == "1010"
+ ("% b" % 10).should == " 1010"
+ ("%1$b" % [10, 20]).should == "1010"
+ ("%#b" % 10).should == "0b1010"
+ ("%+b" % 10).should == "+1010"
+ ("%-9b" % 10).should == "1010 "
+ ("%05b" % 10).should == "01010"
+ ("%*b" % [10, 6]).should == " 110"
+ ("%*b" % [-10, 6]).should == "110 "
+ ("%.4b" % 2).should == "0010"
+ ("%.32b" % 2147483648).should == "10000000000000000000000000000000"
+ end
+
+ it "supports binary formats using %b for negative numbers" do
+ ("%b" % -5).should == "..1011"
+ ("%0b" % -5).should == "..1011"
+ ("%.1b" % -5).should == "..1011"
+ ("%.7b" % -5).should == "..11011"
+ ("%.10b" % -5).should == "..11111011"
+ ("% b" % -5).should == "-101"
+ ("%+b" % -5).should == "-101"
+ not_supported_on :opal do
+ ("%b" % -(2 ** 64 + 5)).should ==
+ "..101111111111111111111111111111111111111111111111111111111111111011"
+ end
+ end
+
+ it "supports binary formats using %B with same behaviour as %b except for using 0B instead of 0b for #" do
+ ("%B" % 10).should == ("%b" % 10)
+ ("% B" % 10).should == ("% b" % 10)
+ ("%1$B" % [10, 20]).should == ("%1$b" % [10, 20])
+ ("%+B" % 10).should == ("%+b" % 10)
+ ("%-9B" % 10).should == ("%-9b" % 10)
+ ("%05B" % 10).should == ("%05b" % 10)
+ ("%*B" % [10, 6]).should == ("%*b" % [10, 6])
+ ("%*B" % [-10, 6]).should == ("%*b" % [-10, 6])
+
+ ("%B" % -5).should == ("%b" % -5)
+ ("%0B" % -5).should == ("%0b" % -5)
+ ("%.1B" % -5).should == ("%.1b" % -5)
+ ("%.7B" % -5).should == ("%.7b" % -5)
+ ("%.10B" % -5).should == ("%.10b" % -5)
+ ("% B" % -5).should == ("% b" % -5)
+ ("%+B" % -5).should == ("%+b" % -5)
+ not_supported_on :opal do
+ ("%B" % -(2 ** 64 + 5)).should == ("%b" % -(2 ** 64 + 5))
+ end
+
+ ("%#B" % 10).should == "0B1010"
+ end
+
+ it "supports character formats using %c" do
+ ("%c" % 10).should == "\n"
+ ("%2$c" % [10, 11, 14]).should == "\v"
+ ("%-4c" % 10).should == "\n "
+ ("%*c" % [10, 3]).should == " \003"
+ ("%c" % 42).should == "*"
+
+ -> { "%c" % Object }.should raise_error(TypeError)
+ end
+
+ it "supports single character strings as argument for %c" do
+ ("%c" % 'A').should == "A"
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "raises an exception for multiple character strings as argument for %c" do
+ -> { "%c" % 'AA' }.should raise_error(ArgumentError)
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "supports only the first character as argument for %c" do
+ ("%c" % 'AA').should == "A"
+ end
+ end
+
+ it "calls to_str on argument for %c formats" do
+ obj = mock('A')
+ obj.should_receive(:to_str).and_return('A')
+
+ ("%c" % obj).should == "A"
+ end
+
+ it "calls #to_ary on argument for %c formats" do
+ obj = mock('65')
+ obj.should_receive(:to_ary).and_return([65])
+ ("%c" % obj).should == ("%c" % [65])
+ end
+
+ it "calls #to_int on argument for %c formats, if the argument does not respond to #to_ary" do
+ obj = mock('65')
+ obj.should_receive(:to_int).and_return(65)
+
+ ("%c" % obj).should == ("%c" % 65)
+ end
+
+ %w(d i).each do |f|
+ format = "%" + f
+
+ it "supports integer formats using #{format}" do
+ ("%#{f}" % 10).should == "10"
+ ("% #{f}" % 10).should == " 10"
+ ("%1$#{f}" % [10, 20]).should == "10"
+ ("%+#{f}" % 10).should == "+10"
+ ("%-7#{f}" % 10).should == "10 "
+ ("%04#{f}" % 10).should == "0010"
+ ("%*#{f}" % [10, 4]).should == " 4"
+ ("%6.4#{f}" % 123).should == " 0123"
+ end
+
+ it "supports negative integers using #{format}" do
+ ("%#{f}" % -5).should == "-5"
+ ("%3#{f}" % -5).should == " -5"
+ ("%03#{f}" % -5).should == "-05"
+ ("%+03#{f}" % -5).should == "-05"
+ ("%+.2#{f}" % -5).should == "-05"
+ ("%-3#{f}" % -5).should == "-5 "
+ ("%6.4#{f}" % -123).should == " -0123"
+ end
+
+ it "supports negative integers using #{format}, giving priority to `-`" do
+ ("%-03#{f}" % -5).should == "-5 "
+ ("%+-03#{f}" % -5).should == "-5 "
+ end
+ end
+
+ it "supports float formats using %e" do
+ ("%e" % 10).should == "1.000000e+01"
+ ("% e" % 10).should == " 1.000000e+01"
+ ("%1$e" % 10).should == "1.000000e+01"
+ ("%#e" % 10).should == "1.000000e+01"
+ ("%+e" % 10).should == "+1.000000e+01"
+ ("%-7e" % 10).should == "1.000000e+01"
+ ("%05e" % 10).should == "1.000000e+01"
+ ("%*e" % [10, 9]).should == "9.000000e+00"
+ end
+
+ it "supports float formats using %e, but Inf, -Inf, and NaN are not floats" do
+ ("%e" % 1e1020).should == "Inf"
+ ("%e" % -1e1020).should == "-Inf"
+ ("%e" % -Float::NAN).should == "NaN"
+ ("%e" % Float::NAN).should == "NaN"
+ end
+
+ it "supports float formats using %E, but Inf, -Inf, and NaN are not floats" do
+ ("%E" % 1e1020).should == "Inf"
+ ("%E" % -1e1020).should == "-Inf"
+ ("%-10E" % 1e1020).should == "Inf "
+ ("%10E" % 1e1020).should == " Inf"
+ ("%+E" % 1e1020).should == "+Inf"
+ ("% E" % 1e1020).should == " Inf"
+ ("%E" % Float::NAN).should == "NaN"
+ ("%E" % -Float::NAN).should == "NaN"
+ end
+
+ it "supports float formats using %E" do
+ ("%E" % 10).should == "1.000000E+01"
+ ("% E" % 10).should == " 1.000000E+01"
+ ("%1$E" % 10).should == "1.000000E+01"
+ ("%#E" % 10).should == "1.000000E+01"
+ ("%+E" % 10).should == "+1.000000E+01"
+ ("%-7E" % 10).should == "1.000000E+01"
+ ("%05E" % 10).should == "1.000000E+01"
+ ("%*E" % [10, 9]).should == "9.000000E+00"
+ end
+
+ it "pads with spaces for %E with Inf, -Inf, and NaN" do
+ ("%010E" % -1e1020).should == " -Inf"
+ ("%010E" % 1e1020).should == " Inf"
+ ("%010E" % Float::NAN).should == " NaN"
+ end
+
+ it "supports float formats using %f" do
+ ("%f" % 10).should == "10.000000"
+ ("% f" % 10).should == " 10.000000"
+ ("%1$f" % 10).should == "10.000000"
+ ("%#f" % 10).should == "10.000000"
+ ("%#0.3f" % 10).should == "10.000"
+ ("%+f" % 10).should == "+10.000000"
+ ("%-7f" % 10).should == "10.000000"
+ ("%05f" % 10).should == "10.000000"
+ ("%0.5f" % 10).should == "10.00000"
+ ("%*f" % [10, 9]).should == " 9.000000"
+ end
+
+ it "supports float formats using %g" do
+ ("%g" % 10).should == "10"
+ ("% g" % 10).should == " 10"
+ ("%1$g" % 10).should == "10"
+ ("%#g" % 10).should == "10.0000"
+ ("%#.3g" % 10).should == "10.0"
+ ("%+g" % 10).should == "+10"
+ ("%-7g" % 10).should == "10 "
+ ("%05g" % 10).should == "00010"
+ ("%g" % 10**10).should == "1e+10"
+ ("%*g" % [10, 9]).should == " 9"
+ end
+
+ it "supports float formats using %G" do
+ ("%G" % 10).should == "10"
+ ("% G" % 10).should == " 10"
+ ("%1$G" % 10).should == "10"
+ ("%#G" % 10).should == "10.0000"
+ ("%#.3G" % 10).should == "10.0"
+ ("%+G" % 10).should == "+10"
+ ("%-7G" % 10).should == "10 "
+ ("%05G" % 10).should == "00010"
+ ("%G" % 10**10).should == "1E+10"
+ ("%*G" % [10, 9]).should == " 9"
+ end
+
+ it "supports octal formats using %o for positive numbers" do
+ ("%o" % 10).should == "12"
+ ("% o" % 10).should == " 12"
+ ("%1$o" % [10, 20]).should == "12"
+ ("%#o" % 10).should == "012"
+ ("%+o" % 10).should == "+12"
+ ("%-9o" % 10).should == "12 "
+ ("%05o" % 10).should == "00012"
+ ("%*o" % [10, 6]).should == " 6"
+ end
+
+ it "supports octal formats using %o for negative numbers" do
+ # These are incredibly wrong. -05 == -5, not 7177777...whatever
+ ("%o" % -5).should == "..73"
+ ("%0o" % -5).should == "..73"
+ ("%.4o" % 20).should == "0024"
+ ("%.1o" % -5).should == "..73"
+ ("%.7o" % -5).should == "..77773"
+ ("%.10o" % -5).should == "..77777773"
+
+ ("% o" % -26).should == "-32"
+ ("%+o" % -26).should == "-32"
+ not_supported_on :opal do
+ ("%o" % -(2 ** 64 + 5)).should == "..75777777777777777777773"
+ end
+ end
+
+ it "supports inspect formats using %p" do
+ ("%p" % 10).should == "10"
+ ("%1$p" % [10, 5]).should == "10"
+ ("%-22p" % 10).should == "10 "
+ ("%*p" % [10, 10]).should == " 10"
+ ("%p" % {capture: 1}).should == "{:capture=>1}"
+ ("%p" % "str").should == "\"str\""
+ end
+
+ it "calls inspect on arguments for %p format" do
+ obj = mock('obj')
+ def obj.inspect() "obj" end
+ ("%p" % obj).should == "obj"
+
+ # undef is not working
+ # obj = mock('obj')
+ # class << obj; undef :inspect; end
+ # def obj.method_missing(*args) "obj" end
+ # ("%p" % obj).should == "obj"
+ end
+
+ it "supports string formats using %s" do
+ ("%s" % "hello").should == "hello"
+ ("%s" % "").should == ""
+ ("%s" % 10).should == "10"
+ ("%1$s" % [10, 8]).should == "10"
+ ("%-5s" % 10).should == "10 "
+ ("%*s" % [10, 9]).should == " 9"
+ end
+
+ it "respects a space padding request not as part of the width" do
+ x = "% -5s" % ["foo"]
+ x.should == "foo "
+ end
+
+ it "calls to_s on non-String arguments for %s format" do
+ obj = mock('obj')
+ def obj.to_s() "obj" end
+
+ ("%s" % obj).should == "obj"
+
+ # undef doesn't work
+ # obj = mock('obj')
+ # class << obj; undef :to_s; end
+ # def obj.method_missing(*args) "obj" end
+ #
+ # ("%s" % obj).should == "obj"
+ end
+
+ # MRI crashes on this one.
+ # See http://groups.google.com/group/ruby-core-google/t/c285c18cd94c216d
+ it "raises an ArgumentError for huge precisions for %s" do
+ block = -> { "%.25555555555555555555555555555555555555s" % "hello world" }
+ block.should raise_error(ArgumentError)
+ end
+
+ # Note: %u has been changed to an alias for %d in 1.9.
+ it "supports unsigned formats using %u" do
+ ("%u" % 10).should == "10"
+ ("% u" % 10).should == " 10"
+ ("%1$u" % [10, 20]).should == "10"
+ ("%+u" % 10).should == "+10"
+ ("%-7u" % 10).should == "10 "
+ ("%04u" % 10).should == "0010"
+ ("%*u" % [10, 4]).should == " 4"
+ end
+
+ it "formats negative values with a leading sign using %u" do
+ ("% u" % -26).should == "-26"
+ ("%+u" % -26).should == "-26"
+ end
+
+ it "supports negative bignums with %u or %d" do
+ ("%u" % -(2 ** 64 + 5)).should == "-18446744073709551621"
+ ("%d" % -(2 ** 64 + 5)).should == "-18446744073709551621"
+ end
+
+ it "supports hex formats using %x for positive numbers" do
+ ("%x" % 10).should == "a"
+ ("% x" % 10).should == " a"
+ ("%1$x" % [10, 20]).should == "a"
+ ("%#x" % 10).should == "0xa"
+ ("%+x" % 10).should == "+a"
+ ("%-9x" % 10).should == "a "
+ ("%05x" % 10).should == "0000a"
+ ("%*x" % [10, 6]).should == " 6"
+ ("%.4x" % 20).should == "0014"
+ ("%x" % 0xFFFFFFFF).should == "ffffffff"
+ end
+
+ it "supports hex formats using %x for negative numbers" do
+ ("%x" % -5).should == "..fb"
+ ("%0x" % -5).should == "..fb"
+ ("%.1x" % -5).should == "..fb"
+ ("%.7x" % -5).should == "..ffffb"
+ ("%.10x" % -5).should == "..fffffffb"
+ ("% x" % -26).should == "-1a"
+ ("%+x" % -26).should == "-1a"
+ not_supported_on :opal do
+ ("%x" % -(2 ** 64 + 5)).should == "..fefffffffffffffffb"
+ end
+ end
+
+ it "supports hex formats using %X for positive numbers" do
+ ("%X" % 10).should == "A"
+ ("% X" % 10).should == " A"
+ ("%1$X" % [10, 20]).should == "A"
+ ("%#X" % 10).should == "0XA"
+ ("%+X" % 10).should == "+A"
+ ("%-9X" % 10).should == "A "
+ ("%05X" % 10).should == "0000A"
+ ("%*X" % [10, 6]).should == " 6"
+ ("%X" % 0xFFFFFFFF).should == "FFFFFFFF"
+ end
+
+ it "supports hex formats using %X for negative numbers" do
+ ("%X" % -5).should == "..FB"
+ ("%0X" % -5).should == "..FB"
+ ("%.1X" % -5).should == "..FB"
+ ("%.7X" % -5).should == "..FFFFB"
+ ("%.10X" % -5).should == "..FFFFFFFB"
+ ("% X" % -26).should == "-1A"
+ ("%+X" % -26).should == "-1A"
+ not_supported_on :opal do
+ ("%X" % -(2 ** 64 + 5)).should == "..FEFFFFFFFFFFFFFFFB"
+ end
+ end
+
+ it "formats zero without prefix using %#x" do
+ ("%#x" % 0).should == "0"
+ end
+
+ it "formats zero without prefix using %#X" do
+ ("%#X" % 0).should == "0"
+ end
+
+ %w(b d i o u x X).each do |f|
+ format = "%" + f
+
+ it "behaves as if calling Kernel#Integer for #{format} argument, if it does not respond to #to_ary" do
+ (format % "10").should == (format % Kernel.Integer("10"))
+ (format % "0x42").should == (format % Kernel.Integer("0x42"))
+ (format % "0b1101").should == (format % Kernel.Integer("0b1101"))
+ (format % "0b1101_0000").should == (format % Kernel.Integer("0b1101_0000"))
+ (format % "0777").should == (format % Kernel.Integer("0777"))
+ -> {
+ # see [ruby-core:14139] for more details
+ (format % "0777").should == (format % Kernel.Integer("0777"))
+ }.should_not raise_error(ArgumentError)
+
+ -> { format % "0__7_7_7" }.should raise_error(ArgumentError)
+
+ -> { format % "" }.should raise_error(ArgumentError)
+ -> { format % "x" }.should raise_error(ArgumentError)
+ -> { format % "5x" }.should raise_error(ArgumentError)
+ -> { format % "08" }.should raise_error(ArgumentError)
+ -> { format % "0b2" }.should raise_error(ArgumentError)
+ -> { format % "123__456" }.should raise_error(ArgumentError)
+
+ obj = mock('5')
+ obj.should_receive(:to_i).and_return(5)
+ (format % obj).should == (format % 5)
+
+ obj = mock('6')
+ obj.stub!(:to_i).and_return(5)
+ obj.should_receive(:to_int).and_return(6)
+ (format % obj).should == (format % 6)
+ end
+ end
+
+ %w(e E f g G).each do |f|
+ format = "%" + f
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('3.14')
+ obj.should_receive(:to_ary).and_return([3.14])
+ (format % obj).should == (format % [3.14])
+ end
+
+ it "behaves as if calling Kernel#Float for #{format} arguments, when the passed argument does not respond to #to_ary" do
+ (format % 10).should == (format % 10.0)
+ (format % "-10.4e-20").should == (format % -10.4e-20)
+ (format % ".5").should == (format % 0.5)
+ (format % "-.5").should == (format % -0.5)
+ # Something's strange with this spec:
+ # it works just fine in individual mode, but not when run as part of a group
+ (format % "10_1_0.5_5_5").should == (format % 1010.555)
+
+ (format % "0777").should == (format % 777)
+
+ -> { format % "" }.should raise_error(ArgumentError)
+ -> { format % "x" }.should raise_error(ArgumentError)
+ -> { format % "." }.should raise_error(ArgumentError)
+ -> { format % "10." }.should raise_error(ArgumentError)
+ -> { format % "5x" }.should raise_error(ArgumentError)
+ -> { format % "0b1" }.should raise_error(ArgumentError)
+ -> { format % "10e10.5" }.should raise_error(ArgumentError)
+ -> { format % "10__10" }.should raise_error(ArgumentError)
+ -> { format % "10.10__10" }.should raise_error(ArgumentError)
+
+ obj = mock('5.0')
+ obj.should_receive(:to_f).and_return(5.0)
+ (format % obj).should == (format % 5.0)
+ end
+
+ it "behaves as if calling Kernel#Float for #{format} arguments, when the passed argument is hexadecimal string" do
+ (format % "0xA").should == (format % 0xA)
+ end
+ end
+
+ describe "when format string contains %{} sections" do
+ it "replaces %{} sections with values from passed-in hash" do
+ ("%{foo}bar" % {foo: 'oof'}).should == "oofbar"
+ end
+
+ it "should raise ArgumentError if no hash given" do
+ -> {"%{foo}" % []}.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "when format string contains %<> formats" do
+ it "uses the named argument for the format's value" do
+ ("%<foo>d" % {foo: 1}).should == "1"
+ end
+
+ it "raises KeyError if key is missing from passed-in hash" do
+ -> {"%<foo>d" % {}}.should raise_error(KeyError)
+ end
+
+ it "should raise ArgumentError if no hash given" do
+ -> {"%<foo>" % []}.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/multiply_spec.rb b/spec/ruby/core/string/multiply_spec.rb
new file mode 100644
index 0000000000..c15f670c46
--- /dev/null
+++ b/spec/ruby/core/string/multiply_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/string/times'
+
+describe "String#*" do
+ it_behaves_like :string_times, :*, -> str, times { str * times }
+end
diff --git a/spec/ruby/core/string/new_spec.rb b/spec/ruby/core/string/new_spec.rb
new file mode 100644
index 0000000000..ca678f5323
--- /dev/null
+++ b/spec/ruby/core/string/new_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String.new" do
+ it "returns an instance of String" do
+ str = String.new
+ str.should be_an_instance_of(String)
+ end
+
+ it "accepts an encoding argument" do
+ xA4xA2 = [0xA4, 0xA2].pack('CC').force_encoding 'utf-8'
+ str = String.new(xA4xA2, encoding: 'euc-jp')
+ str.encoding.should == Encoding::EUC_JP
+ end
+
+ it "accepts a capacity argument" do
+ String.new("", capacity: 100_000).should == ""
+ String.new("abc", capacity: 100_000).should == "abc"
+ end
+
+ it "returns a fully-formed String" do
+ str = String.new
+ str.size.should == 0
+ str << "more"
+ str.should == "more"
+ end
+
+ it "returns a new string given a string argument" do
+ str1 = "test"
+ str = String.new(str1)
+ str.should be_an_instance_of(String)
+ str.should == str1
+ str << "more"
+ str.should == "testmore"
+ end
+
+ it "returns an instance of a subclass" do
+ a = StringSpecs::MyString.new("blah")
+ a.should be_an_instance_of(StringSpecs::MyString)
+ a.should == "blah"
+ end
+
+ it "is called on subclasses" do
+ s = StringSpecs::SubString.new
+ s.special.should == nil
+ s.should == ""
+
+ s = StringSpecs::SubString.new "subclass"
+ s.special.should == "subclass"
+ s.should == ""
+ end
+
+ it "raises TypeError on inconvertible object" do
+ -> { String.new 5 }.should raise_error(TypeError)
+ -> { String.new nil }.should raise_error(TypeError)
+ end
+
+ it "returns a binary String" do
+ String.new.encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/string/next_spec.rb b/spec/ruby/core/string/next_spec.rb
new file mode 100644
index 0000000000..fcd3e5ef90
--- /dev/null
+++ b/spec/ruby/core/string/next_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/succ'
+
+describe "String#next" do
+ it_behaves_like :string_succ, :next
+end
+
+describe "String#next!" do
+ it_behaves_like :string_succ_bang, :"next!"
+end
diff --git a/spec/ruby/core/string/oct_spec.rb b/spec/ruby/core/string/oct_spec.rb
new file mode 100644
index 0000000000..7637692217
--- /dev/null
+++ b/spec/ruby/core/string/oct_spec.rb
@@ -0,0 +1,88 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# Note: We can't completely spec this in terms of to_int() because hex()
+# allows the base to be changed by a base specifier in the string.
+# See http://groups.google.com/group/ruby-core-google/browse_frm/thread/b53e9c2003425703
+describe "String#oct" do
+ it "treats numeric digits as base-8 digits by default" do
+ "0".oct.should == 0
+ "77".oct.should == 077
+ "077".oct.should == 077
+ end
+
+ it "accepts numbers formatted as binary" do
+ "0b1010".oct.should == 0b1010
+ end
+
+ it "accepts numbers formatted as hexadecimal" do
+ "0xFF".oct.should == 0xFF
+ end
+
+ it "accepts numbers formatted as decimal" do
+ "0d500".oct.should == 500
+ end
+
+ describe "with a leading minus sign" do
+ it "treats numeric digits as base-8 digits by default" do
+ "-12348".oct.should == -01234
+ end
+
+ it "accepts numbers formatted as binary" do
+ "-0b0101".oct.should == -0b0101
+ end
+
+ it "accepts numbers formatted as hexadecimal" do
+ "-0xEE".oct.should == -0xEE
+ end
+
+ it "accepts numbers formatted as decimal" do
+ "-0d500".oct.should == -500
+ end
+ end
+
+ describe "with a leading plus sign" do
+ it "treats numeric digits as base-8 digits by default" do
+ "+12348".oct.should == 01234
+ end
+
+ it "accepts numbers formatted as binary" do
+ "+0b1010".oct.should == 0b1010
+ end
+
+ it "accepts numbers formatted as hexadecimal" do
+ "+0xFF".oct.should == 0xFF
+ end
+
+ it "accepts numbers formatted as decimal" do
+ "+0d500".oct.should == 500
+ end
+ end
+
+ it "accepts a single underscore separating digits" do
+ "755_333".oct.should == 0755_333
+ end
+
+ it "does not accept a sequence of underscores as part of a number" do
+ "7__3".oct.should == 07
+ "7___3".oct.should == 07
+ "7__5".oct.should == 07
+ end
+
+ it "ignores characters that are incorrect for the base-8 digits" do
+ "0o".oct.should == 0
+ "5678".oct.should == 0567
+ end
+
+ it "returns 0 if no characters can be interpreted as a base-8 number" do
+ "".oct.should == 0
+ "+-5".oct.should == 0
+ "wombat".oct.should == 0
+ end
+
+ it "returns 0 for strings with leading underscores" do
+ "_7".oct.should == 0
+ "_07".oct.should == 0
+ " _7".oct.should == 0
+ end
+end
diff --git a/spec/ruby/core/string/ord_spec.rb b/spec/ruby/core/string/ord_spec.rb
new file mode 100644
index 0000000000..4cf26990fe
--- /dev/null
+++ b/spec/ruby/core/string/ord_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "String#ord" do
+ it "returns an Integer" do
+ 'a'.ord.should be_an_instance_of(Integer)
+ end
+
+ it "returns the codepoint of the first character in the String" do
+ 'a'.ord.should == 97
+ end
+
+
+ it "ignores subsequent characters" do
+ "\u{287}a".ord.should == "\u{287}".ord
+ end
+
+ it "understands multibyte characters" do
+ "\u{9879}".ord.should == 39033
+ end
+
+ it "is equivalent to #codepoints.first" do
+ "\u{981}\u{982}".ord.should == "\u{981}\u{982}".codepoints.first
+ end
+
+ it "raises an ArgumentError if called on an empty String" do
+ -> { ''.ord }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the character is broken" do
+ s = "©".force_encoding("US-ASCII")
+ -> { s.ord }.should raise_error(ArgumentError, "invalid byte sequence in US-ASCII")
+ end
+end
diff --git a/spec/ruby/core/string/partition_spec.rb b/spec/ruby/core/string/partition_spec.rb
new file mode 100644
index 0000000000..9cb3672881
--- /dev/null
+++ b/spec/ruby/core/string/partition_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/partition'
+
+describe "String#partition with String" do
+ it_behaves_like :string_partition, :partition
+
+ it "returns an array of substrings based on splitting on the given string" do
+ "hello world".partition("o").should == ["hell", "o", " world"]
+ end
+
+ it "always returns 3 elements" do
+ "hello".partition("x").should == ["hello", "", ""]
+ "hello".partition("hello").should == ["", "hello", ""]
+ end
+
+ it "accepts regexp" do
+ "hello!".partition(/l./).should == ["he", "ll", "o!"]
+ end
+
+ it "sets global vars if regexp used" do
+ "hello!".partition(/(.l)(.o)/)
+ $1.should == "el"
+ $2.should == "lo"
+ end
+
+ it "converts its argument using :to_str" do
+ find = mock('l')
+ find.should_receive(:to_str).and_return("l")
+ "hello".partition(find).should == ["he","l","lo"]
+ end
+
+ it "raises an error if not convertible to string" do
+ ->{ "hello".partition(5) }.should raise_error(TypeError)
+ ->{ "hello".partition(nil) }.should raise_error(TypeError)
+ end
+
+ it "takes precedence over a given block" do
+ "hello world".partition("o") { true }.should == ["hell", "o", " world"]
+ end
+
+ it "handles a pattern in a superset encoding" do
+ string = "hello".force_encoding(Encoding::US_ASCII)
+
+ result = string.partition("é")
+
+ result.should == ["hello", "", ""]
+ result[0].encoding.should == Encoding::US_ASCII
+ result[1].encoding.should == Encoding::US_ASCII
+ result[2].encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ pattern = "o".force_encoding(Encoding::US_ASCII)
+
+ result = "héllo world".partition(pattern)
+
+ result.should == ["héll", "o", " world"]
+ result[0].encoding.should == Encoding::UTF_8
+ result[1].encoding.should == Encoding::US_ASCII
+ result[2].encoding.should == Encoding::UTF_8
+ end
+end
diff --git a/spec/ruby/core/string/plus_spec.rb b/spec/ruby/core/string/plus_spec.rb
new file mode 100644
index 0000000000..5ff198f07e
--- /dev/null
+++ b/spec/ruby/core/string/plus_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/concat'
+
+describe "String#+" do
+ it "returns a new string containing the given string concatenated to self" do
+ ("" + "").should == ""
+ ("" + "Hello").should == "Hello"
+ ("Hello" + "").should == "Hello"
+ ("Ruby !" + "= Rubinius").should == "Ruby != Rubinius"
+ end
+
+ it "converts any non-String argument with #to_str" do
+ c = mock 'str'
+ c.should_receive(:to_str).any_number_of_times.and_return(' + 1 = 2')
+
+ ("1" + c).should == '1 + 1 = 2'
+ end
+
+ it "raises a TypeError when given any object that fails #to_str" do
+ -> { "" + Object.new }.should raise_error(TypeError)
+ -> { "" + 65 }.should raise_error(TypeError)
+ end
+
+ it "doesn't return subclass instances" do
+ (StringSpecs::MyString.new("hello") + "").should be_an_instance_of(String)
+ (StringSpecs::MyString.new("hello") + "foo").should be_an_instance_of(String)
+ (StringSpecs::MyString.new("hello") + StringSpecs::MyString.new("foo")).should be_an_instance_of(String)
+ (StringSpecs::MyString.new("hello") + StringSpecs::MyString.new("")).should be_an_instance_of(String)
+ (StringSpecs::MyString.new("") + StringSpecs::MyString.new("")).should be_an_instance_of(String)
+ ("hello" + StringSpecs::MyString.new("foo")).should be_an_instance_of(String)
+ ("hello" + StringSpecs::MyString.new("")).should be_an_instance_of(String)
+ end
+
+ it_behaves_like :string_concat_encoding, :+
+end
diff --git a/spec/ruby/core/string/prepend_spec.rb b/spec/ruby/core/string/prepend_spec.rb
new file mode 100644
index 0000000000..a0393d4760
--- /dev/null
+++ b/spec/ruby/core/string/prepend_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#prepend" do
+ it "prepends the given argument to self and returns self" do
+ str = "world"
+ str.prepend("hello ").should equal(str)
+ str.should == "hello world"
+ end
+
+ it "converts the given argument to a String using to_str" do
+ obj = mock("hello")
+ obj.should_receive(:to_str).and_return("hello")
+ a = " world!".prepend(obj)
+ a.should == "hello world!"
+ end
+
+ it "raises a TypeError if the given argument can't be converted to a String" do
+ -> { "hello ".prepend [] }.should raise_error(TypeError)
+ -> { 'hello '.prepend mock('x') }.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ a = "hello"
+ a.freeze
+
+ -> { a.prepend "" }.should raise_error(FrozenError)
+ -> { a.prepend "test" }.should raise_error(FrozenError)
+ end
+
+ it "works when given a subclass instance" do
+ a = " world"
+ a.prepend StringSpecs::MyString.new("hello")
+ a.should == "hello world"
+ end
+
+ it "takes multiple arguments" do
+ str = " world"
+ str.prepend "he", "", "llo"
+ str.should == "hello world"
+ end
+
+ it "prepends the initial value when given arguments contain 2 self" do
+ str = "hello"
+ str.prepend str, str
+ str.should == "hellohellohello"
+ end
+
+ it "returns self when given no arguments" do
+ str = "hello"
+ str.prepend.should equal(str)
+ str.should == "hello"
+ end
+end
diff --git a/spec/ruby/core/string/replace_spec.rb b/spec/ruby/core/string/replace_spec.rb
new file mode 100644
index 0000000000..ef9bab4f3c
--- /dev/null
+++ b/spec/ruby/core/string/replace_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/replace'
+
+describe "String#replace" do
+ it_behaves_like :string_replace, :replace
+end
diff --git a/spec/ruby/core/string/reverse_spec.rb b/spec/ruby/core/string/reverse_spec.rb
new file mode 100644
index 0000000000..73526256ef
--- /dev/null
+++ b/spec/ruby/core/string/reverse_spec.rb
@@ -0,0 +1,79 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#reverse" do
+ it "returns a new string with the characters of self in reverse order" do
+ "stressed".reverse.should == "desserts"
+ "m".reverse.should == "m"
+ "".reverse.should == ""
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("stressed").reverse.should be_an_instance_of(String)
+ StringSpecs::MyString.new("m").reverse.should be_an_instance_of(String)
+ StringSpecs::MyString.new("").reverse.should be_an_instance_of(String)
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("stressed").reverse.should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("m").reverse.should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("").reverse.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ it "reverses a string with multi byte characters" do
+ "微軟正黑體".reverse.should == "體黑正軟微"
+ end
+
+ it "works with a broken string" do
+ str = "微軟\xDF\xDE正黑體".force_encoding(Encoding::UTF_8)
+
+ str.valid_encoding?.should be_false
+
+ str.reverse.should == "體黑正\xDE\xDF軟微"
+ end
+
+ it "returns a String in the same encoding as self" do
+ "stressed".encode("US-ASCII").reverse.encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "String#reverse!" do
+ it "reverses self in place and always returns self" do
+ a = "stressed"
+ a.reverse!.should equal(a)
+ a.should == "desserts"
+
+ "".reverse!.should == ""
+ end
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ -> { "anna".freeze.reverse! }.should raise_error(FrozenError)
+ -> { "hello".freeze.reverse! }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on a frozen instance that would not be modified" do
+ -> { "".freeze.reverse! }.should raise_error(FrozenError)
+ end
+
+ it "reverses a string with multi byte characters" do
+ str = "微軟正黑體"
+ str.reverse!
+ str.should == "體黑正軟微"
+ end
+
+ it "works with a broken string" do
+ str = "微軟\xDF\xDE正黑體".force_encoding(Encoding::UTF_8)
+
+ str.valid_encoding?.should be_false
+ str.reverse!
+
+ str.should == "體黑正\xDE\xDF軟微"
+ end
+end
diff --git a/spec/ruby/core/string/rindex_spec.rb b/spec/ruby/core/string/rindex_spec.rb
new file mode 100644
index 0000000000..cd2aabba34
--- /dev/null
+++ b/spec/ruby/core/string/rindex_spec.rb
@@ -0,0 +1,387 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'fixtures/utf-8-encoding'
+
+describe "String#rindex with object" do
+ it "raises a TypeError if obj isn't a String or Regexp" do
+ not_supported_on :opal do
+ -> { "hello".rindex(:sym) }.should raise_error(TypeError)
+ end
+ -> { "hello".rindex(mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if obj is an Integer" do
+ -> { "hello".rindex(42) }.should raise_error(TypeError)
+ end
+
+ it "doesn't try to convert obj to an integer via to_int" do
+ obj = mock('x')
+ obj.should_not_receive(:to_int)
+ -> { "hello".rindex(obj) }.should raise_error(TypeError)
+ end
+
+ it "tries to convert obj to a string via to_str" do
+ obj = mock('lo')
+ def obj.to_str() "lo" end
+ "hello".rindex(obj).should == "hello".rindex("lo")
+
+ obj = mock('o')
+ def obj.respond_to?(arg, *) true end
+ def obj.method_missing(*args) "o" end
+ "hello".rindex(obj).should == "hello".rindex("o")
+ end
+end
+
+describe "String#rindex with String" do
+ it "behaves the same as String#rindex(char) for one-character strings" do
+ "blablabla hello cruel world...!".split("").uniq.each do |str|
+ chr = str[0]
+ str.rindex(str).should == str.rindex(chr)
+
+ 0.upto(str.size + 1) do |start|
+ str.rindex(str, start).should == str.rindex(chr, start)
+ end
+
+ (-str.size - 1).upto(-1) do |start|
+ str.rindex(str, start).should == str.rindex(chr, start)
+ end
+ end
+ end
+
+ it "behaves the same as String#rindex(?char) for one-character strings" do
+ "blablabla hello cruel world...!".split("").uniq.each do |str|
+ chr = str[0] =~ / / ? str[0] : eval("?#{str[0]}")
+ str.rindex(str).should == str.rindex(chr)
+
+ 0.upto(str.size + 1) do |start|
+ str.rindex(str, start).should == str.rindex(chr, start)
+ end
+
+ (-str.size - 1).upto(-1) do |start|
+ str.rindex(str, start).should == str.rindex(chr, start)
+ end
+ end
+ end
+
+ it "returns the index of the last occurrence of the given substring" do
+ "blablabla".rindex("").should == 9
+ "blablabla".rindex("a").should == 8
+ "blablabla".rindex("la").should == 7
+ "blablabla".rindex("bla").should == 6
+ "blablabla".rindex("abla").should == 5
+ "blablabla".rindex("labla").should == 4
+ "blablabla".rindex("blabla").should == 3
+ "blablabla".rindex("ablabla").should == 2
+ "blablabla".rindex("lablabla").should == 1
+ "blablabla".rindex("blablabla").should == 0
+
+ "blablabla".rindex("l").should == 7
+ "blablabla".rindex("bl").should == 6
+ "blablabla".rindex("abl").should == 5
+ "blablabla".rindex("labl").should == 4
+ "blablabla".rindex("blabl").should == 3
+ "blablabla".rindex("ablabl").should == 2
+ "blablabla".rindex("lablabl").should == 1
+ "blablabla".rindex("blablabl").should == 0
+
+ "blablabla".rindex("b").should == 6
+ "blablabla".rindex("ab").should == 5
+ "blablabla".rindex("lab").should == 4
+ "blablabla".rindex("blab").should == 3
+ "blablabla".rindex("ablab").should == 2
+ "blablabla".rindex("lablab").should == 1
+ "blablabla".rindex("blablab").should == 0
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+
+ 'hello.'.rindex('ll')
+ $~.should == nil
+ end
+
+ it "ignores string subclasses" do
+ "blablabla".rindex(StringSpecs::MyString.new("bla")).should == 6
+ StringSpecs::MyString.new("blablabla").rindex("bla").should == 6
+ StringSpecs::MyString.new("blablabla").rindex(StringSpecs::MyString.new("bla")).should == 6
+ end
+
+ it "starts the search at the given offset" do
+ "blablabla".rindex("bl", 0).should == 0
+ "blablabla".rindex("bl", 1).should == 0
+ "blablabla".rindex("bl", 2).should == 0
+ "blablabla".rindex("bl", 3).should == 3
+
+ "blablabla".rindex("bla", 0).should == 0
+ "blablabla".rindex("bla", 1).should == 0
+ "blablabla".rindex("bla", 2).should == 0
+ "blablabla".rindex("bla", 3).should == 3
+
+ "blablabla".rindex("blab", 0).should == 0
+ "blablabla".rindex("blab", 1).should == 0
+ "blablabla".rindex("blab", 2).should == 0
+ "blablabla".rindex("blab", 3).should == 3
+ "blablabla".rindex("blab", 6).should == 3
+ "blablablax".rindex("blab", 6).should == 3
+
+ "blablabla".rindex("la", 1).should == 1
+ "blablabla".rindex("la", 2).should == 1
+ "blablabla".rindex("la", 3).should == 1
+ "blablabla".rindex("la", 4).should == 4
+
+ "blablabla".rindex("lab", 1).should == 1
+ "blablabla".rindex("lab", 2).should == 1
+ "blablabla".rindex("lab", 3).should == 1
+ "blablabla".rindex("lab", 4).should == 4
+
+ "blablabla".rindex("ab", 2).should == 2
+ "blablabla".rindex("ab", 3).should == 2
+ "blablabla".rindex("ab", 4).should == 2
+ "blablabla".rindex("ab", 5).should == 5
+
+ "blablabla".rindex("", 0).should == 0
+ "blablabla".rindex("", 1).should == 1
+ "blablabla".rindex("", 2).should == 2
+ "blablabla".rindex("", 7).should == 7
+ "blablabla".rindex("", 8).should == 8
+ "blablabla".rindex("", 9).should == 9
+ "blablabla".rindex("", 10).should == 9
+ end
+
+ it "starts the search at offset + self.length if offset is negative" do
+ str = "blablabla"
+
+ ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle|
+ (-str.length .. -1).each do |offset|
+ str.rindex(needle, offset).should ==
+ str.rindex(needle, offset + str.length)
+ end
+ end
+ end
+
+ it "returns nil if the substring isn't found" do
+ "blablabla".rindex("B").should == nil
+ "blablabla".rindex("z").should == nil
+ "blablabla".rindex("BLA").should == nil
+ "blablabla".rindex("blablablabla").should == nil
+
+ "hello".rindex("lo", 0).should == nil
+ "hello".rindex("lo", 1).should == nil
+ "hello".rindex("lo", 2).should == nil
+
+ "hello".rindex("llo", 0).should == nil
+ "hello".rindex("llo", 1).should == nil
+
+ "hello".rindex("el", 0).should == nil
+ "hello".rindex("ello", 0).should == nil
+
+ "hello".rindex("", -6).should == nil
+ "hello".rindex("", -7).should == nil
+
+ "hello".rindex("h", -6).should == nil
+ end
+
+ it "tries to convert start_offset to an integer via to_int" do
+ obj = mock('5')
+ def obj.to_int() 5 end
+ "str".rindex("st", obj).should == 0
+
+ obj = mock('5')
+ def obj.respond_to?(arg, *) true end
+ def obj.method_missing(*args) 5 end
+ "str".rindex("st", obj).should == 0
+ end
+
+ it "raises a TypeError when given offset is nil" do
+ -> { "str".rindex("st", nil) }.should raise_error(TypeError)
+ end
+
+ it "handles a substring in a superset encoding" do
+ 'abc'.force_encoding(Encoding::US_ASCII).rindex('é').should == nil
+ end
+
+ it "handles a substring in a subset encoding" do
+ 'été'.rindex('t'.force_encoding(Encoding::US_ASCII)).should == 1
+ end
+end
+
+describe "String#rindex with Regexp" do
+ it "behaves the same as String#rindex(string) for escaped string regexps" do
+ ["blablabla", "hello cruel world...!"].each do |str|
+ ["", "b", "bla", "lab", "o c", "d."].each do |needle|
+ regexp = Regexp.new(Regexp.escape(needle))
+ str.rindex(regexp).should == str.rindex(needle)
+
+ 0.upto(str.size + 1) do |start|
+ str.rindex(regexp, start).should == str.rindex(needle, start)
+ end
+
+ (-str.size - 1).upto(-1) do |start|
+ str.rindex(regexp, start).should == str.rindex(needle, start)
+ end
+ end
+ end
+ end
+
+ it "returns the index of the first match from the end of string of regexp" do
+ "blablabla".rindex(/bla/).should == 6
+ "blablabla".rindex(/BLA/i).should == 6
+
+ "blablabla".rindex(/.{0}/).should == 9
+ "blablabla".rindex(/.{1}/).should == 8
+ "blablabla".rindex(/.{2}/).should == 7
+ "blablabla".rindex(/.{6}/).should == 3
+ "blablabla".rindex(/.{9}/).should == 0
+
+ "blablabla".rindex(/.*/).should == 9
+ "blablabla".rindex(/.+/).should == 8
+
+ "blablabla".rindex(/bla|a/).should == 8
+
+ not_supported_on :opal do
+ "blablabla".rindex(/\A/).should == 0
+ "blablabla".rindex(/\Z/).should == 9
+ "blablabla".rindex(/\z/).should == 9
+ "blablabla\n".rindex(/\Z/).should == 10
+ "blablabla\n".rindex(/\z/).should == 10
+ end
+
+ "blablabla".rindex(/^/).should == 0
+ not_supported_on :opal do
+ "\nblablabla".rindex(/^/).should == 1
+ "b\nlablabla".rindex(/^/).should == 2
+ end
+ "blablabla".rindex(/$/).should == 9
+
+ "blablabla".rindex(/.l./).should == 6
+ end
+
+ it "sets $~ to MatchData of match and nil when there's none" do
+ 'hello.'.rindex(/.(.)/)
+ $~[0].should == 'o.'
+
+ 'hello.'.rindex(/not/)
+ $~.should == nil
+ end
+
+ it "always clear $~" do
+ "a".rindex(/a/)
+ $~.should_not == nil
+
+ string = "blablabla"
+ string.rindex(/bla/, -(string.length + 1))
+ $~.should == nil
+ end
+
+ it "starts the search at the given offset" do
+ "blablabla".rindex(/.{0}/, 5).should == 5
+ "blablabla".rindex(/.{1}/, 5).should == 5
+ "blablabla".rindex(/.{2}/, 5).should == 5
+ "blablabla".rindex(/.{3}/, 5).should == 5
+ "blablabla".rindex(/.{4}/, 5).should == 5
+
+ "blablabla".rindex(/.{0}/, 3).should == 3
+ "blablabla".rindex(/.{1}/, 3).should == 3
+ "blablabla".rindex(/.{2}/, 3).should == 3
+ "blablabla".rindex(/.{5}/, 3).should == 3
+ "blablabla".rindex(/.{6}/, 3).should == 3
+
+ "blablabla".rindex(/.l./, 0).should == 0
+ "blablabla".rindex(/.l./, 1).should == 0
+ "blablabla".rindex(/.l./, 2).should == 0
+ "blablabla".rindex(/.l./, 3).should == 3
+
+ "blablablax".rindex(/.x/, 10).should == 8
+ "blablablax".rindex(/.x/, 9).should == 8
+ "blablablax".rindex(/.x/, 8).should == 8
+
+ "blablablax".rindex(/..x/, 10).should == 7
+ "blablablax".rindex(/..x/, 9).should == 7
+ "blablablax".rindex(/..x/, 8).should == 7
+ "blablablax".rindex(/..x/, 7).should == 7
+
+ not_supported_on :opal do
+ "blablabla\n".rindex(/\Z/, 9).should == 9
+ end
+ end
+
+ it "starts the search at offset + self.length if offset is negative" do
+ str = "blablabla"
+
+ ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle|
+ (-str.length .. -1).each do |offset|
+ str.rindex(needle, offset).should ==
+ str.rindex(needle, offset + str.length)
+ end
+ end
+ end
+
+ it "returns nil if the substring isn't found" do
+ "blablabla".rindex(/BLA/).should == nil
+ "blablabla".rindex(/.{10}/).should == nil
+ "blablablax".rindex(/.x/, 7).should == nil
+ "blablablax".rindex(/..x/, 6).should == nil
+
+ not_supported_on :opal do
+ "blablabla".rindex(/\Z/, 5).should == nil
+ "blablabla".rindex(/\z/, 5).should == nil
+ "blablabla\n".rindex(/\z/, 9).should == nil
+ end
+ end
+
+ not_supported_on :opal do
+ it "supports \\G which matches at the given start offset" do
+ "helloYOU.".rindex(/YOU\G/, 8).should == 5
+ "helloYOU.".rindex(/YOU\G/).should == nil
+
+ idx = "helloYOUall!".index("YOU")
+ re = /YOU.+\G.+/
+ # The # marks where \G will match.
+ [
+ ["helloYOU#all.", nil],
+ ["helloYOUa#ll.", idx],
+ ["helloYOUal#l.", idx],
+ ["helloYOUall#.", idx],
+ ["helloYOUall.#", nil]
+ ].each do |i|
+ start = i[0].index("#")
+ str = i[0].delete("#")
+
+ str.rindex(re, start).should == i[1]
+ end
+ end
+ end
+
+ it "tries to convert start_offset to an integer via to_int" do
+ obj = mock('5')
+ def obj.to_int() 5 end
+ "str".rindex(/../, obj).should == 1
+
+ obj = mock('5')
+ def obj.respond_to?(arg, *) true end
+ def obj.method_missing(*args); 5; end
+ "str".rindex(/../, obj).should == 1
+ end
+
+ it "raises a TypeError when given offset is nil" do
+ -> { "str".rindex(/../, nil) }.should raise_error(TypeError)
+ end
+
+ it "returns the reverse character index of a multibyte character" do
+ "ã‚りãŒã‚ŠãŒã¨ã†".rindex("ãŒ").should == 4
+ "ã‚りãŒã‚ŠãŒã¨ã†".rindex(/ãŒ/).should == 4
+ end
+
+ it "returns the character index before the finish" do
+ "ã‚りãŒã‚ŠãŒã¨ã†".rindex("ãŒ", 3).should == 2
+ "ã‚りãŒã‚ŠãŒã¨ã†".rindex(/ãŒ/, 3).should == 2
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are incompatible" do
+ re = Regexp.new "れ".encode(Encoding::EUC_JP)
+ -> do
+ "ã‚れ".rindex re
+ end.should raise_error(Encoding::CompatibilityError)
+ end
+end
diff --git a/spec/ruby/core/string/rjust_spec.rb b/spec/ruby/core/string/rjust_spec.rb
new file mode 100644
index 0000000000..d067b7bdb3
--- /dev/null
+++ b/spec/ruby/core/string/rjust_spec.rb
@@ -0,0 +1,113 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#rjust with length, padding" do
+ it "returns a new string of specified length with self right justified and padded with padstr" do
+ "hello".rjust(20, '1234').should == "123412341234123hello"
+
+ "".rjust(1, "abcd").should == "a"
+ "".rjust(2, "abcd").should == "ab"
+ "".rjust(3, "abcd").should == "abc"
+ "".rjust(4, "abcd").should == "abcd"
+ "".rjust(6, "abcd").should == "abcdab"
+
+ "OK".rjust(3, "abcd").should == "aOK"
+ "OK".rjust(4, "abcd").should == "abOK"
+ "OK".rjust(6, "abcd").should == "abcdOK"
+ "OK".rjust(8, "abcd").should == "abcdabOK"
+ end
+
+ it "pads with whitespace if no padstr is given" do
+ "hello".rjust(20).should == " hello"
+ end
+
+ it "returns self if it's longer than or as long as the specified length" do
+ "".rjust(0).should == ""
+ "".rjust(-1).should == ""
+ "hello".rjust(4).should == "hello"
+ "hello".rjust(-1).should == "hello"
+ "this".rjust(3).should == "this"
+ "radiology".rjust(8, '-').should == "radiology"
+ end
+
+ it "tries to convert length to an integer using to_int" do
+ "^".rjust(3.8, "^_").should == "^_^"
+
+ obj = mock('3')
+ obj.should_receive(:to_int).and_return(3)
+
+ "o".rjust(obj, "o_").should == "o_o"
+ end
+
+ it "raises a TypeError when length can't be converted to an integer" do
+ -> { "hello".rjust("x") }.should raise_error(TypeError)
+ -> { "hello".rjust("x", "y") }.should raise_error(TypeError)
+ -> { "hello".rjust([]) }.should raise_error(TypeError)
+ -> { "hello".rjust(mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "tries to convert padstr to a string using to_str" do
+ padstr = mock('123')
+ padstr.should_receive(:to_str).and_return("123")
+
+ "hello".rjust(10, padstr).should == "12312hello"
+ end
+
+ it "raises a TypeError when padstr can't be converted" do
+ -> { "hello".rjust(20, []) }.should raise_error(TypeError)
+ -> { "hello".rjust(20, Object.new)}.should raise_error(TypeError)
+ -> { "hello".rjust(20, mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError when padstr is empty" do
+ -> { "hello".rjust(10, '') }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on subclasses" do
+ StringSpecs::MyString.new("").rjust(10).should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("foo").rjust(10).should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("foo").rjust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(StringSpecs::MyString)
+
+ "".rjust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ "foo".rjust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on subclasses" do
+ StringSpecs::MyString.new("").rjust(10).should be_an_instance_of(String)
+ StringSpecs::MyString.new("foo").rjust(10).should be_an_instance_of(String)
+ StringSpecs::MyString.new("foo").rjust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+
+ "".rjust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ "foo".rjust(10, StringSpecs::MyString.new("x")).should be_an_instance_of(String)
+ end
+ end
+
+ describe "with width" do
+ it "returns a String in the same encoding as the original" do
+ str = "abc".force_encoding Encoding::IBM437
+ result = str.rjust 5
+ result.should == " abc"
+ result.encoding.should equal(Encoding::IBM437)
+ end
+ end
+
+ describe "with width, pattern" do
+ it "returns a String in the compatible encoding" do
+ str = "abc".force_encoding Encoding::IBM437
+ result = str.rjust 5, "ã‚"
+ result.should == "ã‚ã‚abc"
+ result.encoding.should equal(Encoding::UTF_8)
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are incompatible" do
+ pat = "ã‚¢".encode Encoding::EUC_JP
+ -> do
+ "ã‚れ".rjust 5, pat
+ end.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/rpartition_spec.rb b/spec/ruby/core/string/rpartition_spec.rb
new file mode 100644
index 0000000000..21e87f530a
--- /dev/null
+++ b/spec/ruby/core/string/rpartition_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/partition'
+
+describe "String#rpartition with String" do
+ it_behaves_like :string_partition, :rpartition
+
+ it "returns an array of substrings based on splitting on the given string" do
+ "hello world".rpartition("o").should == ["hello w", "o", "rld"]
+ end
+
+ it "always returns 3 elements" do
+ "hello".rpartition("x").should == ["", "", "hello"]
+ "hello".rpartition("hello").should == ["", "hello", ""]
+ end
+
+ it "returns original string if regexp doesn't match" do
+ "hello".rpartition("/x/").should == ["", "", "hello"]
+ end
+
+ it "returns new object if doesn't match" do
+ str = "hello"
+ str.rpartition("/no_match/").last.should_not.equal?(str)
+ end
+
+ it "handles multibyte string correctly" do
+ "ユーザ@ドメイン".rpartition(/@/).should == ["ユーザ", "@", "ドメイン"]
+ end
+
+ it "accepts regexp" do
+ "hello!".rpartition(/l./).should == ["hel", "lo", "!"]
+ end
+
+ it "affects $~" do
+ matched_string = "hello!".rpartition(/l./)[1]
+ matched_string.should == $~[0]
+ end
+
+ it "converts its argument using :to_str" do
+ find = mock('l')
+ find.should_receive(:to_str).and_return("l")
+ "hello".rpartition(find).should == ["hel","l","o"]
+ end
+
+ it "raises an error if not convertible to string" do
+ ->{ "hello".rpartition(5) }.should raise_error(TypeError)
+ ->{ "hello".rpartition(nil) }.should raise_error(TypeError)
+ end
+
+ it "handles a pattern in a superset encoding" do
+ string = "hello".force_encoding(Encoding::US_ASCII)
+
+ result = string.rpartition("é")
+
+ result.should == ["", "", "hello"]
+ result[0].encoding.should == Encoding::US_ASCII
+ result[1].encoding.should == Encoding::US_ASCII
+ result[2].encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ pattern = "o".force_encoding(Encoding::US_ASCII)
+
+ result = "héllo world".rpartition(pattern)
+
+ result.should == ["héllo w", "o", "rld"]
+ result[0].encoding.should == Encoding::UTF_8
+ result[1].encoding.should == Encoding::US_ASCII
+ result[2].encoding.should == Encoding::UTF_8
+ end
+end
diff --git a/spec/ruby/core/string/rstrip_spec.rb b/spec/ruby/core/string/rstrip_spec.rb
new file mode 100644
index 0000000000..e96ce4120f
--- /dev/null
+++ b/spec/ruby/core/string/rstrip_spec.rb
@@ -0,0 +1,95 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/strip'
+
+describe "String#rstrip" do
+ it_behaves_like :string_strip, :rstrip
+
+ it "returns a copy of self with trailing whitespace removed" do
+ " hello ".rstrip.should == " hello"
+ " hello world ".rstrip.should == " hello world"
+ " hello world \n\r\t\n\v\r".rstrip.should == " hello world"
+ "hello".rstrip.should == "hello"
+ "hello\x00".rstrip.should == "hello"
+ "ã“ã«ã¡ã‚ ".rstrip.should == "ã“ã«ã¡ã‚"
+ end
+
+ it "works with lazy substrings" do
+ " hello "[1...-1].rstrip.should == " hello"
+ " hello world "[1...-1].rstrip.should == " hello world"
+ " hello world \n\r\t\n\v\r"[1...-1].rstrip.should == " hello world"
+ " ã“ã«ã¡ã‚ "[1...-1].rstrip.should == "ã“ã«ã¡ã‚"
+ end
+
+ it "returns a copy of self with all trailing whitespace and NULL bytes removed" do
+ "\x00 \x00hello\x00 \x00".rstrip.should == "\x00 \x00hello"
+ end
+end
+
+describe "String#rstrip!" do
+ it "modifies self in place and returns self" do
+ a = " hello "
+ a.rstrip!.should equal(a)
+ a.should == " hello"
+ end
+
+ it "modifies self removing trailing NULL bytes and whitespace" do
+ a = "\x00 \x00hello\x00 \x00"
+ a.rstrip!
+ a.should == "\x00 \x00hello"
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "hello"
+ a.rstrip!.should == nil
+ a.should == "hello"
+ end
+
+ it "makes a string empty if it is only whitespace" do
+ "".rstrip!.should == nil
+ " ".rstrip.should == ""
+ " ".rstrip.should == ""
+ end
+
+ ruby_version_is '3.0' do
+ it "removes trailing NULL bytes and whitespace" do
+ a = "\000 goodbye \000"
+ a.rstrip!
+ a.should == "\000 goodbye"
+ end
+ end
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ -> { " hello ".freeze.rstrip! }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on a frozen instance that would not be modified" do
+ -> { "hello".freeze.rstrip! }.should raise_error(FrozenError)
+ -> { "".freeze.rstrip! }.should raise_error(FrozenError)
+ end
+
+ ruby_version_is "3.2" do
+ it "raises an Encoding::CompatibilityError if the last non-space codepoint is invalid" do
+ s = "abc\xDF".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.rstrip! }.should raise_error(Encoding::CompatibilityError)
+
+ s = "abc\xDF ".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.rstrip! }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "raises an ArgumentError if the last non-space codepoint is invalid" do
+ s = "abc\xDF".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.rstrip! }.should raise_error(ArgumentError)
+
+ s = "abc\xDF ".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.rstrip! }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/scan_spec.rb b/spec/ruby/core/string/scan_spec.rb
new file mode 100644
index 0000000000..a2d1815132
--- /dev/null
+++ b/spec/ruby/core/string/scan_spec.rb
@@ -0,0 +1,175 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#scan" do
+ it "returns an array containing all matches" do
+ "cruel world".scan(/\w+/).should == ["cruel", "world"]
+ "cruel world".scan(/.../).should == ["cru", "el ", "wor"]
+
+ # Edge case
+ "hello".scan(//).should == ["", "", "", "", "", ""]
+ "".scan(//).should == [""]
+ end
+
+ it "respects unicode when the pattern collapses to nothing" do
+ str = "ã“ã«ã¡ã‚"
+ reg = %r!!
+ str.scan(reg).should == ["", "", "", "", ""]
+ end
+
+ it "stores groups as arrays in the returned arrays" do
+ "hello".scan(/()/).should == [[""]] * 6
+ "hello".scan(/()()/).should == [["", ""]] * 6
+ "cruel world".scan(/(...)/).should == [["cru"], ["el "], ["wor"]]
+ "cruel world".scan(/(..)(..)/).should == [["cr", "ue"], ["l ", "wo"]]
+ end
+
+ it "scans for occurrences of the string if pattern is a string" do
+ "one two one two".scan('one').should == ["one", "one"]
+ "hello.".scan('.').should == ['.']
+ end
+
+ it "sets $~ to MatchData of last match and nil when there's none" do
+ 'hello.'.scan(/.(.)/)
+ $~[0].should == 'o.'
+
+ 'hello.'.scan(/not/)
+ $~.should == nil
+
+ 'hello.'.scan('l')
+ $~.begin(0).should == 3
+ $~[0].should == 'l'
+
+ 'hello.'.scan('not')
+ $~.should == nil
+ end
+
+ it "supports \\G which matches the end of the previous match / string start for first match" do
+ "one two one two".scan(/\G\w+/).should == ["one"]
+ "one two one two".scan(/\G\w+\s*/).should == ["one ", "two ", "one ", "two"]
+ "one two one two".scan(/\G\s*\w+/).should == ["one", " two", " one", " two"]
+ end
+
+ it "tries to convert pattern to a string via to_str" do
+ obj = mock('o')
+ obj.should_receive(:to_str).and_return("o")
+ "o_o".scan(obj).should == ["o", "o"]
+ end
+
+ it "raises a TypeError if pattern isn't a Regexp and can't be converted to a String" do
+ -> { "cruel world".scan(5) }.should raise_error(TypeError)
+ not_supported_on :opal do
+ -> { "cruel world".scan(:test) }.should raise_error(TypeError)
+ end
+ -> { "cruel world".scan(mock('x')) }.should raise_error(TypeError)
+ end
+
+ # jruby/jruby#5513
+ it "does not raise any errors when passed a multi-byte string" do
+ "ã‚ã‚ã‚aaaã‚ã‚ã‚".scan("ã‚ã‚ã‚").should == ["ã‚ã‚ã‚", "ã‚ã‚ã‚"]
+ end
+
+ it "returns Strings in the same encoding as self" do
+ "cruel world".encode("US-ASCII").scan(/\w+/).each do |s|
+ s.encoding.should == Encoding::US_ASCII
+ end
+ end
+end
+
+describe "String#scan with pattern and block" do
+ it "returns self" do
+ s = "foo"
+ s.scan(/./) {}.should equal(s)
+ s.scan(/roar/) {}.should equal(s)
+ end
+
+ it "passes each match to the block as one argument: an array" do
+ a = []
+ "cruel world".scan(/\w+/) { |*w| a << w }
+ a.should == [["cruel"], ["world"]]
+ end
+
+ it "passes groups to the block as one argument: an array" do
+ a = []
+ "cruel world".scan(/(..)(..)/) { |w| a << w }
+ a.should == [["cr", "ue"], ["l ", "wo"]]
+ end
+
+ it "sets $~ for access from the block" do
+ str = "hello"
+
+ matches = []
+ offsets = []
+
+ str.scan(/([aeiou])/) do
+ md = $~
+ md.string.should == str
+ matches << md.to_a
+ offsets << md.offset(0)
+ str
+ end
+
+ matches.should == [["e", "e"], ["o", "o"]]
+ offsets.should == [[1, 2], [4, 5]]
+
+ matches = []
+ offsets = []
+
+ str.scan("l") do
+ md = $~
+ md.string.should == str
+ matches << md.to_a
+ offsets << md.offset(0)
+ str
+ end
+
+ matches.should == [["l"], ["l"]]
+ offsets.should == [[2, 3], [3, 4]]
+ end
+
+ it "restores $~ after leaving the block" do
+ [/./, "l"].each do |pattern|
+ old_md = nil
+ "hello".scan(pattern) do
+ old_md = $~
+ "ok".match(/./)
+ "x"
+ end
+
+ $~[0].should == old_md[0]
+ $~.string.should == "hello"
+ end
+ end
+
+ it "sets $~ to MatchData of last match and nil when there's none for access from outside" do
+ 'hello.'.scan('l') { 'x' }
+ $~.begin(0).should == 3
+ $~[0].should == 'l'
+
+ 'hello.'.scan('not') { 'x' }
+ $~.should == nil
+
+ 'hello.'.scan(/.(.)/) { 'x' }
+ $~[0].should == 'o.'
+
+ 'hello.'.scan(/not/) { 'x' }
+ $~.should == nil
+ end
+
+ it "passes block arguments as individual arguments when blocks are provided" do
+ "a b c\na b c\na b c".scan(/(\w*) (\w*) (\w*)/) do |first,second,third|
+ first.should == 'a';
+ second.should == 'b';
+ third.should == 'c';
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "yields String instances for subclasses" do
+ a = []
+ StringSpecs::MyString.new("abc").scan(/./) { |s| a << s.class }
+ a.should == [String, String, String]
+ end
+ end
+end
diff --git a/spec/ruby/core/string/scrub_spec.rb b/spec/ruby/core/string/scrub_spec.rb
new file mode 100644
index 0000000000..a51fbd020a
--- /dev/null
+++ b/spec/ruby/core/string/scrub_spec.rb
@@ -0,0 +1,175 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#scrub with a default replacement" do
+ it "returns self for valid strings" do
+ input = "foo"
+
+ input.scrub.should == input
+ end
+
+ it "replaces invalid byte sequences" do
+ x81 = [0x81].pack('C').force_encoding('utf-8')
+ "abc\u3042#{x81}".scrub.should == "abc\u3042\uFFFD"
+ end
+
+ it "replaces invalid byte sequences in lazy substrings" do
+ x81 = [0x81].pack('C').force_encoding('utf-8')
+ "abc\u3042#{x81}def"[1...-1].scrub.should == "bc\u3042\uFFFDde"
+ end
+
+ it "returns a copy of self when the input encoding is BINARY" do
+ input = "foo".encode('BINARY')
+
+ input.scrub.should == "foo"
+ end
+
+ it "replaces invalid byte sequences when using ASCII as the input encoding" do
+ xE3x80 = [0xE3, 0x80].pack('CC').force_encoding 'utf-8'
+ input = "abc\u3042#{xE3x80}".force_encoding('ASCII')
+ input.scrub.should == "abc?????"
+ end
+
+ it "returns a String in the same encoding as self" do
+ x81 = [0x81].pack('C').force_encoding('utf-8')
+ "abc\u3042#{x81}".scrub.encoding.should == Encoding::UTF_8
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("foo").scrub.should be_an_instance_of(String)
+ input = [0x81].pack('C').force_encoding('utf-8')
+ StringSpecs::MyString.new(input).scrub.should be_an_instance_of(String)
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("foo").scrub.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+end
+
+describe "String#scrub with a custom replacement" do
+ it "returns self for valid strings" do
+ input = "foo"
+
+ input.scrub("*").should == input
+ end
+
+ it "replaces invalid byte sequences" do
+ x81 = [0x81].pack('C').force_encoding('utf-8')
+ "abc\u3042#{x81}".scrub("*").should == "abc\u3042*"
+ end
+
+ it "replaces invalid byte sequences in frozen strings" do
+ x81 = [0x81].pack('C').force_encoding('utf-8')
+ (-"abc\u3042#{x81}").scrub("*").should == "abc\u3042*"
+
+ leading_surrogate = [0x00, 0xD8]
+ utf16_str = ("abc".encode('UTF-16LE').bytes + leading_surrogate).pack('c*').force_encoding('UTF-16LE')
+ (-(utf16_str)).scrub("*".encode('UTF-16LE')).should == "abc*".encode('UTF-16LE')
+ end
+
+ it "replaces an incomplete character at the end with a single replacement" do
+ xE3x80 = [0xE3, 0x80].pack('CC').force_encoding 'utf-8'
+ xE3x80.scrub("*").should == "*"
+ end
+
+ it "raises ArgumentError for replacements with an invalid encoding" do
+ x81 = [0x81].pack('C').force_encoding('utf-8')
+ xE4 = [0xE4].pack('C').force_encoding('utf-8')
+ block = -> { "foo#{x81}".scrub(xE4) }
+
+ block.should raise_error(ArgumentError)
+ end
+
+ it "returns a String in the same encoding as self" do
+ x81 = [0x81].pack('C').force_encoding('utf-8')
+ "abc\u3042#{x81}".scrub("*").encoding.should == Encoding::UTF_8
+ end
+
+ it "raises TypeError when a non String replacement is given" do
+ x81 = [0x81].pack('C').force_encoding('utf-8')
+ block = -> { "foo#{x81}".scrub(1) }
+
+ block.should raise_error(TypeError)
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("foo").scrub("*").should be_an_instance_of(String)
+ input = [0x81].pack('C').force_encoding('utf-8')
+ StringSpecs::MyString.new(input).scrub("*").should be_an_instance_of(String)
+ end
+ end
+end
+
+describe "String#scrub with a block" do
+ it "returns self for valid strings" do
+ input = "foo"
+
+ input.scrub { |b| "*" }.should == input
+ end
+
+ it "replaces invalid byte sequences" do
+ xE3x80 = [0xE3, 0x80].pack('CC').force_encoding 'utf-8'
+ replaced = "abc\u3042#{xE3x80}".scrub { |b| "<#{b.unpack("H*")[0]}>" }
+
+ replaced.should == "abc\u3042<e380>"
+ end
+
+ it "replaces invalid byte sequences using a custom encoding" do
+ x80x80 = [0x80, 0x80].pack('CC').force_encoding 'utf-8'
+ replaced = x80x80.scrub do |bad|
+ bad.encode(Encoding::UTF_8, Encoding::Windows_1252)
+ end
+
+ replaced.should == "€€"
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("foo").scrub { |b| "*" }.should be_an_instance_of(String)
+ input = [0x81].pack('C').force_encoding('utf-8')
+ StringSpecs::MyString.new(input).scrub { |b| "<#{b.unpack("H*")[0]}>" }.should be_an_instance_of(String)
+ end
+ end
+end
+
+describe "String#scrub!" do
+ it "modifies self for valid strings" do
+ x81 = [0x81].pack('C').force_encoding('utf-8')
+ input = "a#{x81}"
+ input.scrub!
+ input.should == "a\uFFFD"
+ end
+
+ it "accepts blocks" do
+ x81 = [0x81].pack('C').force_encoding('utf-8')
+ input = "a#{x81}"
+ input.scrub! { |b| "<?>" }
+ input.should == "a<?>"
+ end
+
+ it "maintains the state of frozen strings that are already valid" do
+ input = "a"
+ input.freeze
+ input.scrub!
+ input.frozen?.should be_true
+ end
+
+ it "preserves the instance variables of already valid strings" do
+ input = "a"
+ input.instance_variable_set(:@a, 'b')
+ input.scrub!
+ input.instance_variable_get(:@a).should == 'b'
+ end
+
+ it "accepts a frozen string as a replacement" do
+ input = "a\xE2"
+ input.scrub!('.'.freeze)
+ input.should == 'a.'
+ end
+end
diff --git a/spec/ruby/core/string/setbyte_spec.rb b/spec/ruby/core/string/setbyte_spec.rb
new file mode 100644
index 0000000000..77bff64038
--- /dev/null
+++ b/spec/ruby/core/string/setbyte_spec.rb
@@ -0,0 +1,111 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "String#setbyte" do
+ it "returns an Integer" do
+ "a".setbyte(0,1).should be_kind_of(Integer)
+ end
+
+ it "modifies the receiver" do
+ str = "glark"
+ old_id = str.object_id
+ str.setbyte(0,88)
+ str.object_id.should == old_id
+ end
+
+ it "changes the byte at the given index to the new byte" do
+ str = "a"
+ str.setbyte(0,98)
+ str.should == 'b'
+
+ # copy-on-write case
+ str1, str2 = "fooXbar".split("X")
+ str2.setbyte(0, 50)
+ str2.should == "2ar"
+ str1.should == "foo"
+ end
+
+ it "allows changing bytes in multi-byte characters" do
+ str = "\u{915}"
+ str.setbyte(1,254)
+ str.getbyte(1).should == 254
+ end
+
+ it "can invalidate a String's encoding" do
+ str = "glark"
+ str.valid_encoding?.should be_true
+ str.setbyte(2,253)
+ str.valid_encoding?.should be_false
+
+ str = "ABC"
+ str.setbyte(0, 0x20) # ' '
+ str.should.valid_encoding?
+ str.setbyte(0, 0xE3)
+ str.should_not.valid_encoding?
+ end
+
+ it "regards a negative index as counting from the end of the String" do
+ str = "hedgehog"
+ str.setbyte(-3, 108)
+ str.should == "hedgelog"
+
+ # copy-on-write case
+ str1, str2 = "fooXbar".split("X")
+ str2.setbyte(-1, 50)
+ str2.should == "ba2"
+ str1.should == "foo"
+ end
+
+ it "raises an IndexError if the index is greater than the String bytesize" do
+ -> { "?".setbyte(1, 97) }.should raise_error(IndexError)
+ end
+
+ it "raises an IndexError if the negative index is greater magnitude than the String bytesize" do
+ -> { "???".setbyte(-5, 97) }.should raise_error(IndexError)
+ end
+
+ it "sets a byte at an index greater than String size" do
+ chr = "\u{998}"
+ chr.bytesize.should == 3
+ chr.setbyte(2, 150)
+ chr.should == "\xe0\xa6\x96"
+ end
+
+ it "does not modify the original string when using String.new" do
+ str1 = "hedgehog"
+ str2 = String.new(str1)
+ str2.setbyte(0, 108)
+ str2.should == "ledgehog"
+ str2.should_not == "hedgehog"
+ str1.should == "hedgehog"
+ str1.should_not == "ledgehog"
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ str = "cold".freeze
+ str.frozen?.should be_true
+ -> { str.setbyte(3,96) }.should raise_error(FrozenError)
+ end
+
+ it "raises a TypeError unless the second argument is an Integer" do
+ -> { "a".setbyte(0,'a') }.should raise_error(TypeError)
+ end
+
+ it "calls #to_int to convert the index" do
+ index = mock("setbyte index")
+ index.should_receive(:to_int).and_return(1)
+
+ str = "hat"
+ str.setbyte(index, "i".ord)
+ str.should == "hit"
+ end
+
+ it "calls to_int to convert the value" do
+ value = mock("setbyte value")
+ value.should_receive(:to_int).and_return("i".ord)
+
+ str = "hat"
+ str.setbyte(1, value)
+ str.should == "hit"
+ end
+end
diff --git a/spec/ruby/core/string/shared/chars.rb b/spec/ruby/core/string/shared/chars.rb
new file mode 100644
index 0000000000..e9fdf89fd6
--- /dev/null
+++ b/spec/ruby/core/string/shared/chars.rb
@@ -0,0 +1,66 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_chars, shared: true do
+ it "passes each char in self to the given block" do
+ a = []
+ "hello".send(@method) { |c| a << c }
+ a.should == ['h', 'e', 'l', 'l', 'o']
+ end
+
+ it "returns self" do
+ s = StringSpecs::MyString.new "hello"
+ s.send(@method){}.should equal(s)
+ end
+
+
+ it "is unicode aware" do
+ "\303\207\342\210\202\303\251\306\222g".send(@method).to_a.should ==
+ ["\303\207", "\342\210\202", "\303\251", "\306\222", "g"]
+ end
+
+ it "returns characters in the same encoding as self" do
+ "&%".force_encoding('Shift_JIS').send(@method).to_a.all? {|c| c.encoding.name.should == 'Shift_JIS'}
+ "&%".encode('BINARY').send(@method).to_a.all? {|c| c.encoding.should == Encoding::BINARY }
+ end
+
+ it "works with multibyte characters" do
+ s = "\u{8987}".force_encoding("UTF-8")
+ s.bytesize.should == 3
+ s.send(@method).to_a.should == [s]
+ end
+
+ it "works if the String's contents is invalid for its encoding" do
+ xA4 = [0xA4].pack('C')
+ xA4.force_encoding('UTF-8')
+ xA4.valid_encoding?.should be_false
+ xA4.send(@method).to_a.should == [xA4.force_encoding("UTF-8")]
+ end
+
+ it "returns a different character if the String is transcoded" do
+ s = "\u{20AC}".force_encoding('UTF-8')
+ s.encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".force_encoding('UTF-8')]
+ s.encode('iso-8859-15').send(@method).to_a.should == [[0xA4].pack('C').force_encoding('iso-8859-15')]
+ s.encode('iso-8859-15').encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".force_encoding('UTF-8')]
+ end
+
+ it "uses the String's encoding to determine what characters it contains" do
+ s = "\u{24B62}"
+
+ s.force_encoding('UTF-8').send(@method).to_a.should == [
+ s.force_encoding('UTF-8')
+ ]
+ s.force_encoding('BINARY').send(@method).to_a.should == [
+ [0xF0].pack('C').force_encoding('BINARY'),
+ [0xA4].pack('C').force_encoding('BINARY'),
+ [0xAD].pack('C').force_encoding('BINARY'),
+ [0xA2].pack('C').force_encoding('BINARY')
+ ]
+ s.force_encoding('SJIS').send(@method).to_a.should == [
+ [0xF0,0xA4].pack('CC').force_encoding('SJIS'),
+ [0xAD].pack('C').force_encoding('SJIS'),
+ [0xA2].pack('C').force_encoding('SJIS')
+ ]
+ end
+end
diff --git a/spec/ruby/core/string/shared/codepoints.rb b/spec/ruby/core/string/shared/codepoints.rb
new file mode 100644
index 0000000000..0b2e078e0a
--- /dev/null
+++ b/spec/ruby/core/string/shared/codepoints.rb
@@ -0,0 +1,62 @@
+# -*- encoding: binary -*-
+describe :string_codepoints, shared: true do
+ it "returns self" do
+ s = "foo"
+ result = s.send(@method) {}
+ result.should equal s
+ end
+
+ it "raises an ArgumentError when self has an invalid encoding and a method is called on the returned Enumerator" do
+ s = "\xDF".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.send(@method).to_a }.should raise_error(ArgumentError)
+ end
+
+ it "yields each codepoint to the block if one is given" do
+ codepoints = []
+ "abcd".send(@method) do |codepoint|
+ codepoints << codepoint
+ end
+ codepoints.should == [97, 98, 99, 100]
+ end
+
+ it "raises an ArgumentError if self's encoding is invalid and a block is given" do
+ s = "\xDF".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.send(@method) { } }.should raise_error(ArgumentError)
+ end
+
+ it "yields codepoints as Integers" do
+ "glark\u{20}".send(@method).to_a.each do |codepoint|
+ codepoint.should be_an_instance_of(Integer)
+ end
+ end
+
+ it "yields one codepoint for each character" do
+ s = "\u{9876}\u{28}\u{1987}"
+ s.send(@method).to_a.size.should == s.chars.to_a.size
+ end
+
+ it "works for multibyte characters" do
+ s = "\u{9819}"
+ s.bytesize.should == 3
+ s.send(@method).to_a.should == [38937]
+ end
+
+ it "yields the codepoints corresponding to the character's position in the String's encoding" do
+ "\u{787}".send(@method).to_a.should == [1927]
+ end
+
+ it "round-trips to the original String using Integer#chr" do
+ s = "\u{13}\u{7711}\u{1010}"
+ s2 = ""
+ s.send(@method) {|n| s2 << n.chr(Encoding::UTF_8)}
+ s.should == s2
+ end
+
+ it "is synonymous with #bytes for Strings which are single-byte optimizable" do
+ s = "(){}".encode('ascii')
+ s.ascii_only?.should be_true
+ s.send(@method).to_a.should == s.bytes.to_a
+ end
+end
diff --git a/spec/ruby/core/string/shared/concat.rb b/spec/ruby/core/string/shared/concat.rb
new file mode 100644
index 0000000000..54ac1035d3
--- /dev/null
+++ b/spec/ruby/core/string/shared/concat.rb
@@ -0,0 +1,150 @@
+describe :string_concat, shared: true do
+ it "concatenates the given argument to self and returns self" do
+ str = 'hello '
+ str.send(@method, 'world').should equal(str)
+ str.should == "hello world"
+ end
+
+ it "converts the given argument to a String using to_str" do
+ obj = mock('world!')
+ obj.should_receive(:to_str).and_return("world!")
+ a = 'hello '.send(@method, obj)
+ a.should == 'hello world!'
+ end
+
+ it "raises a TypeError if the given argument can't be converted to a String" do
+ -> { 'hello '.send(@method, []) }.should raise_error(TypeError)
+ -> { 'hello '.send(@method, mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ a = "hello"
+ a.freeze
+
+ -> { a.send(@method, "") }.should raise_error(FrozenError)
+ -> { a.send(@method, "test") }.should raise_error(FrozenError)
+ end
+
+ it "returns a String when given a subclass instance" do
+ a = "hello"
+ a.send(@method, StringSpecs::MyString.new(" world"))
+ a.should == "hello world"
+ a.should be_an_instance_of(String)
+ end
+
+ it "returns an instance of same class when called on a subclass" do
+ str = StringSpecs::MyString.new("hello")
+ str.send(@method, " world")
+ str.should == "hello world"
+ str.should be_an_instance_of(StringSpecs::MyString)
+ end
+
+ describe "with Integer" do
+ it "concatenates the argument interpreted as a codepoint" do
+ b = "".send(@method, 33)
+ b.should == "!"
+
+ b.encode!(Encoding::UTF_8)
+ b.send(@method, 0x203D)
+ b.should == "!\u203D"
+ end
+
+ # #5855
+ it "returns a BINARY string if self is US-ASCII and the argument is between 128-255 (inclusive)" do
+ a = ("".encode(Encoding::US_ASCII).send(@method, 128))
+ a.encoding.should == Encoding::BINARY
+ a.should == 128.chr
+
+ a = ("".encode(Encoding::US_ASCII).send(@method, 255))
+ a.encoding.should == Encoding::BINARY
+ a.should == 255.chr
+ end
+
+ it "raises RangeError if the argument is an invalid codepoint for self's encoding" do
+ -> { "".encode(Encoding::US_ASCII).send(@method, 256) }.should raise_error(RangeError)
+ -> { "".encode(Encoding::EUC_JP).send(@method, 0x81) }.should raise_error(RangeError)
+ end
+
+ it "raises RangeError if the argument is negative" do
+ -> { "".send(@method, -200) }.should raise_error(RangeError)
+ -> { "".send(@method, -bignum_value) }.should raise_error(RangeError)
+ end
+
+ it "doesn't call to_int on its argument" do
+ x = mock('x')
+ x.should_not_receive(:to_int)
+
+ -> { "".send(@method, x) }.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ a = "hello"
+ a.freeze
+
+ -> { a.send(@method, 0) }.should raise_error(FrozenError)
+ -> { a.send(@method, 33) }.should raise_error(FrozenError)
+ end
+ end
+end
+
+describe :string_concat_encoding, shared: true do
+ describe "when self is in an ASCII-incompatible encoding incompatible with the argument's encoding" do
+ it "uses self's encoding if both are empty" do
+ "".encode("UTF-16LE").send(@method, "").encoding.should == Encoding::UTF_16LE
+ end
+
+ it "uses self's encoding if the argument is empty" do
+ "x".encode("UTF-16LE").send(@method, "").encoding.should == Encoding::UTF_16LE
+ end
+
+ it "uses the argument's encoding if self is empty" do
+ "".encode("UTF-16LE").send(@method, "x".encode("UTF-8")).encoding.should == Encoding::UTF_8
+ end
+
+ it "raises Encoding::CompatibilityError if neither are empty" do
+ -> { "x".encode("UTF-16LE").send(@method, "y".encode("UTF-8")) }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+
+ describe "when the argument is in an ASCII-incompatible encoding incompatible with self's encoding" do
+ it "uses self's encoding if both are empty" do
+ "".encode("UTF-8").send(@method, "".encode("UTF-16LE")).encoding.should == Encoding::UTF_8
+ end
+
+ it "uses self's encoding if the argument is empty" do
+ "x".encode("UTF-8").send(@method, "".encode("UTF-16LE")).encoding.should == Encoding::UTF_8
+ end
+
+ it "uses the argument's encoding if self is empty" do
+ "".encode("UTF-8").send(@method, "x".encode("UTF-16LE")).encoding.should == Encoding::UTF_16LE
+ end
+
+ it "raises Encoding::CompatibilityError if neither are empty" do
+ -> { "x".encode("UTF-8").send(@method, "y".encode("UTF-16LE")) }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+
+ describe "when self and the argument are in different ASCII-compatible encodings" do
+ it "uses self's encoding if both are ASCII-only" do
+ "abc".encode("UTF-8").send(@method, "123".encode("SHIFT_JIS")).encoding.should == Encoding::UTF_8
+ end
+
+ it "uses self's encoding if the argument is ASCII-only" do
+ "\u00E9".encode("UTF-8").send(@method, "123".encode("ISO-8859-1")).encoding.should == Encoding::UTF_8
+ end
+
+ it "uses the argument's encoding if self is ASCII-only" do
+ "abc".encode("UTF-8").send(@method, "\u00E9".encode("ISO-8859-1")).encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "raises Encoding::CompatibilityError if neither are ASCII-only" do
+ -> { "\u00E9".encode("UTF-8").send(@method, "\u00E9".encode("ISO-8859-1")) }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+
+ describe "when self is BINARY and argument is US-ASCII" do
+ it "uses BINARY encoding" do
+ "abc".encode("BINARY").send(@method, "123".encode("US-ASCII")).encoding.should == Encoding::BINARY
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/dedup.rb b/spec/ruby/core/string/shared/dedup.rb
new file mode 100644
index 0000000000..6ffcb9b045
--- /dev/null
+++ b/spec/ruby/core/string/shared/dedup.rb
@@ -0,0 +1,57 @@
+describe :string_dedup, shared: true do
+ it 'returns self if the String is frozen' do
+ input = 'foo'.freeze
+ output = input.send(@method)
+
+ output.should equal(input)
+ output.should.frozen?
+ end
+
+ it 'returns a frozen copy if the String is not frozen' do
+ input = 'foo'
+ output = input.send(@method)
+
+ output.should.frozen?
+ output.should_not equal(input)
+ output.should == 'foo'
+ end
+
+ it "returns the same object for equal unfrozen strings" do
+ origin = "this is a string"
+ dynamic = %w(this is a string).join(' ')
+
+ origin.should_not equal(dynamic)
+ origin.send(@method).should equal(dynamic.send(@method))
+ end
+
+ it "returns the same object when it's called on the same String literal" do
+ "unfrozen string".send(@method).should equal("unfrozen string".send(@method))
+ "unfrozen string".send(@method).should_not equal("another unfrozen string".send(@method))
+ end
+
+ it "deduplicates frozen strings" do
+ dynamic = %w(this string is frozen).join(' ').freeze
+
+ dynamic.should_not equal("this string is frozen".freeze)
+
+ dynamic.send(@method).should equal("this string is frozen".freeze)
+ dynamic.send(@method).should equal("this string is frozen".send(@method).freeze)
+ end
+
+ it "does not deduplicate a frozen string when it has instance variables" do
+ dynamic = %w(this string is frozen).join(' ')
+ dynamic.instance_variable_set(:@a, 1)
+ dynamic.freeze
+
+ dynamic.send(@method).should_not equal("this string is frozen".freeze)
+ dynamic.send(@method).should_not equal("this string is frozen".send(@method).freeze)
+ dynamic.send(@method).should equal(dynamic)
+ end
+
+ ruby_version_is "3.0" do
+ it "interns the provided string if it is frozen" do
+ dynamic = "this string is unique and frozen #{rand}".freeze
+ dynamic.send(@method).should equal(dynamic)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/each_char_without_block.rb b/spec/ruby/core/string/shared/each_char_without_block.rb
new file mode 100644
index 0000000000..397100ce0e
--- /dev/null
+++ b/spec/ruby/core/string/shared/each_char_without_block.rb
@@ -0,0 +1,26 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_each_char_without_block, shared: true do
+ describe "when no block is given" do
+ it "returns an enumerator" do
+ enum = "hello".send(@method)
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == ['h', 'e', 'l', 'l', 'o']
+ end
+
+ describe "returned enumerator" do
+ describe "size" do
+ it "should return the size of the string" do
+ str = "hello"
+ str.send(@method).size.should == str.size
+ str = "ola"
+ str.send(@method).size.should == str.size
+ str = "\303\207\342\210\202\303\251\306\222g"
+ str.send(@method).size.should == str.size
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/each_codepoint_without_block.rb b/spec/ruby/core/string/shared/each_codepoint_without_block.rb
new file mode 100644
index 0000000000..92b7f76032
--- /dev/null
+++ b/spec/ruby/core/string/shared/each_codepoint_without_block.rb
@@ -0,0 +1,33 @@
+# -*- encoding: binary -*-
+describe :string_each_codepoint_without_block, shared: true do
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ "".send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "returns an Enumerator even when self has an invalid encoding" do
+ s = "\xDF".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ s.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return the size of the string" do
+ str = "hello"
+ str.send(@method).size.should == str.size
+ str = "ola"
+ str.send(@method).size.should == str.size
+ str = "\303\207\342\210\202\303\251\306\222g"
+ str.send(@method).size.should == str.size
+ end
+
+ it "should return the size of the string even when the string has an invalid encoding" do
+ s = "\xDF".force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ s.send(@method).size.should == 1
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/each_line.rb b/spec/ruby/core/string/shared/each_line.rb
new file mode 100644
index 0000000000..df78bd2186
--- /dev/null
+++ b/spec/ruby/core/string/shared/each_line.rb
@@ -0,0 +1,172 @@
+describe :string_each_line, shared: true do
+ it "splits using default newline separator when none is specified" do
+ a = []
+ "one\ntwo\r\nthree".send(@method) { |s| a << s }
+ a.should == ["one\n", "two\r\n", "three"]
+
+ b = []
+ "hello\n\n\nworld".send(@method) { |s| b << s }
+ b.should == ["hello\n", "\n", "\n", "world"]
+
+ c = []
+ "\n\n\n\n\n".send(@method) {|s| c << s}
+ c.should == ["\n", "\n", "\n", "\n", "\n"]
+ end
+
+ it "splits self using the supplied record separator and passes each substring to the block" do
+ a = []
+ "one\ntwo\r\nthree".send(@method, "\n") { |s| a << s }
+ a.should == ["one\n", "two\r\n", "three"]
+
+ b = []
+ "hello\nworld".send(@method, 'l') { |s| b << s }
+ b.should == [ "hel", "l", "o\nworl", "d" ]
+
+ c = []
+ "hello\n\n\nworld".send(@method, "\n") { |s| c << s }
+ c.should == ["hello\n", "\n", "\n", "world"]
+ end
+
+ it "splits strings containing multibyte characters" do
+ s = <<~EOS
+ foo
+ 🤡🤡🤡🤡🤡🤡🤡
+ bar
+ baz
+ EOS
+
+ b = []
+ s.send(@method) { |part| b << part }
+ b.should == ["foo\n", "🤡🤡🤡🤡🤡🤡🤡\n", "bar\n", "baz\n"]
+ end
+
+ it "passes self as a whole to the block if the separator is nil" do
+ a = []
+ "one\ntwo\r\nthree".send(@method, nil) { |s| a << s }
+ a.should == ["one\ntwo\r\nthree"]
+ end
+
+ it "yields paragraphs (broken by 2 or more successive newlines) when passed '' and replaces multiple newlines with only two ones" do
+ a = []
+ "hello\nworld\n\n\nand\nuniverse\n\n\n\n\n".send(@method, '') { |s| a << s }
+ a.should == ["hello\nworld\n\n", "and\nuniverse\n\n"]
+
+ a = []
+ "hello\nworld\n\n\nand\nuniverse\n\n\n\n\ndog".send(@method, '') { |s| a << s }
+ a.should == ["hello\nworld\n\n", "and\nuniverse\n\n", "dog"]
+ end
+
+ describe "uses $/" do
+ before :each do
+ @before_separator = $/
+ end
+
+ after :each do
+ suppress_warning {$/ = @before_separator}
+ end
+
+ it "as the separator when none is given" do
+ [
+ "", "x", "x\ny", "x\ry", "x\r\ny", "x\n\r\r\ny",
+ "hello hullo bello"
+ ].each do |str|
+ ["", "llo", "\n", "\r", nil].each do |sep|
+ expected = []
+ str.send(@method, sep) { |x| expected << x }
+
+ suppress_warning {$/ = sep}
+
+ actual = []
+ suppress_warning {str.send(@method) { |x| actual << x }}
+
+ actual.should == expected
+ end
+ end
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "yields subclass instances for subclasses" do
+ a = []
+ StringSpecs::MyString.new("hello\nworld").send(@method) { |s| a << s.class }
+ a.should == [StringSpecs::MyString, StringSpecs::MyString]
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "yields String instances for subclasses" do
+ a = []
+ StringSpecs::MyString.new("hello\nworld").send(@method) { |s| a << s.class }
+ a.should == [String, String]
+ end
+ end
+
+ it "returns self" do
+ s = "hello\nworld"
+ (s.send(@method) {}).should equal(s)
+ end
+
+ it "tries to convert the separator to a string using to_str" do
+ separator = mock('l')
+ separator.should_receive(:to_str).and_return("l")
+
+ a = []
+ "hello\nworld".send(@method, separator) { |s| a << s }
+ a.should == [ "hel", "l", "o\nworl", "d" ]
+ end
+
+ it "does not care if the string is modified while substituting" do
+ str = "hello\nworld."
+ out = []
+ str.send(@method){|x| out << x; str[-1] = '!' }.should == "hello\nworld!"
+ out.should == ["hello\n", "world."]
+ end
+
+ it "returns Strings in the same encoding as self" do
+ "one\ntwo\r\nthree".encode("US-ASCII").send(@method) do |s|
+ s.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ it "raises a TypeError when the separator can't be converted to a string" do
+ -> { "hello world".send(@method, false) {} }.should raise_error(TypeError)
+ -> { "hello world".send(@method, mock('x')) {} }.should raise_error(TypeError)
+ end
+
+ it "accepts a string separator" do
+ "hello world".send(@method, ?o).to_a.should == ["hello", " wo", "rld"]
+ end
+
+ it "raises a TypeError when the separator is a symbol" do
+ -> { "hello world".send(@method, :o).to_a }.should raise_error(TypeError)
+ end
+
+ context "when `chomp` keyword argument is passed" do
+ it "removes new line characters when separator is not specified" do
+ a = []
+ "hello \nworld\n".send(@method, chomp: true) { |s| a << s }
+ a.should == ["hello ", "world"]
+
+ a = []
+ "hello \r\nworld\r\n".send(@method, chomp: true) { |s| a << s }
+ a.should == ["hello ", "world"]
+ end
+
+ it "removes only specified separator" do
+ a = []
+ "hello world".send(@method, ' ', chomp: true) { |s| a << s }
+ a.should == ["hello", "world"]
+ end
+
+ # https://bugs.ruby-lang.org/issues/14257
+ it "ignores new line characters when separator is specified" do
+ a = []
+ "hello\n world\n".send(@method, ' ', chomp: true) { |s| a << s }
+ a.should == ["hello\n", "world\n"]
+
+ a = []
+ "hello\r\n world\r\n".send(@method, ' ', chomp: true) { |s| a << s }
+ a.should == ["hello\r\n", "world\r\n"]
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/each_line_without_block.rb b/spec/ruby/core/string/shared/each_line_without_block.rb
new file mode 100644
index 0000000000..8e08b0390c
--- /dev/null
+++ b/spec/ruby/core/string/shared/each_line_without_block.rb
@@ -0,0 +1,17 @@
+describe :string_each_line_without_block, shared: true do
+ describe "when no block is given" do
+ it "returns an enumerator" do
+ enum = "hello world".send(@method, ' ')
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == ["hello ", "world"]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ "hello world".send(@method, ' ').size.should == nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/encode.rb b/spec/ruby/core/string/shared/encode.rb
new file mode 100644
index 0000000000..a73de5b943
--- /dev/null
+++ b/spec/ruby/core/string/shared/encode.rb
@@ -0,0 +1,247 @@
+# -*- encoding: utf-8 -*-
+describe :string_encode, shared: true do
+ describe "when passed no options" do
+ it "transcodes to Encoding.default_internal when set" do
+ Encoding.default_internal = Encoding::UTF_8
+ str = [0xA4, 0xA2].pack('CC').force_encoding Encoding::EUC_JP
+ str.send(@method).should == "ã‚"
+ end
+
+ it "transcodes a 7-bit String despite no generic converting being available" do
+ -> do
+ Encoding::Converter.new Encoding::Emacs_Mule, Encoding::BINARY
+ end.should raise_error(Encoding::ConverterNotFoundError)
+
+ Encoding.default_internal = Encoding::Emacs_Mule
+ str = "\x79".force_encoding Encoding::BINARY
+
+ str.send(@method).should == "y".force_encoding(Encoding::BINARY)
+ end
+
+ it "raises an Encoding::ConverterNotFoundError when no conversion is possible" do
+ Encoding.default_internal = Encoding::Emacs_Mule
+ str = [0x80].pack('C').force_encoding Encoding::BINARY
+ -> { str.send(@method) }.should raise_error(Encoding::ConverterNotFoundError)
+ end
+ end
+
+ describe "when passed to encoding" do
+ it "accepts a String argument" do
+ str = [0xA4, 0xA2].pack('CC').force_encoding Encoding::EUC_JP
+ str.send(@method, "utf-8").should == "ã‚"
+ end
+
+ it "calls #to_str to convert the object to an Encoding" do
+ enc = mock("string encode encoding")
+ enc.should_receive(:to_str).and_return("utf-8")
+
+ str = [0xA4, 0xA2].pack('CC').force_encoding Encoding::EUC_JP
+ str.send(@method, enc).should == "ã‚"
+ end
+
+ it "transcodes to the passed encoding" do
+ str = [0xA4, 0xA2].pack('CC').force_encoding Encoding::EUC_JP
+ str.send(@method, Encoding::UTF_8).should == "ã‚"
+ end
+
+ it "transcodes Japanese multibyte characters" do
+ str = "ã‚ã„ã†ãˆãŠ"
+ str.send(@method, Encoding::ISO_2022_JP).should ==
+ "\e\x24\x42\x24\x22\x24\x24\x24\x26\x24\x28\x24\x2A\e\x28\x42".force_encoding(Encoding::ISO_2022_JP)
+ end
+
+ it "transcodes a 7-bit String despite no generic converting being available" do
+ -> do
+ Encoding::Converter.new Encoding::Emacs_Mule, Encoding::BINARY
+ end.should raise_error(Encoding::ConverterNotFoundError)
+
+ str = "\x79".force_encoding Encoding::BINARY
+ str.send(@method, Encoding::Emacs_Mule).should == "y".force_encoding(Encoding::BINARY)
+ end
+
+ it "raises an Encoding::ConverterNotFoundError when no conversion is possible" do
+ str = [0x80].pack('C').force_encoding Encoding::BINARY
+ -> do
+ str.send(@method, Encoding::Emacs_Mule)
+ end.should raise_error(Encoding::ConverterNotFoundError)
+ end
+
+ it "raises an Encoding::ConverterNotFoundError for an invalid encoding" do
+ -> do
+ "abc".send(@method, "xyz")
+ end.should raise_error(Encoding::ConverterNotFoundError)
+ end
+ end
+
+ describe "when passed options" do
+ it "does not process transcoding options if not transcoding" do
+ result = "ã‚\ufffdã‚".send(@method, undef: :replace)
+ result.should == "ã‚\ufffdã‚"
+ end
+
+ it "calls #to_hash to convert the object" do
+ options = mock("string encode options")
+ options.should_receive(:to_hash).and_return({ undef: :replace })
+
+ result = "ã‚\ufffdã‚".send(@method, **options)
+ result.should == "ã‚\ufffdã‚"
+ end
+
+ it "transcodes to Encoding.default_internal when set" do
+ Encoding.default_internal = Encoding::UTF_8
+ str = [0xA4, 0xA2].pack('CC').force_encoding Encoding::EUC_JP
+ str.send(@method, invalid: :replace).should == "ã‚"
+ end
+
+ it "raises an Encoding::ConverterNotFoundError when no conversion is possible despite 'invalid: :replace, undef: :replace'" do
+ Encoding.default_internal = Encoding::Emacs_Mule
+ str = [0x80].pack('C').force_encoding Encoding::BINARY
+ -> do
+ str.send(@method, invalid: :replace, undef: :replace)
+ end.should raise_error(Encoding::ConverterNotFoundError)
+ end
+
+ it "replaces invalid characters when replacing Emacs-Mule encoded strings" do
+ got = [0x80].pack('C').force_encoding('Emacs-Mule').send(@method, invalid: :replace)
+
+ got.should == "?".encode('Emacs-Mule')
+ end
+ end
+
+ describe "when passed to, from" do
+ it "transcodes between the encodings ignoring the String encoding" do
+ str = "ã‚"
+ result = [0xA6, 0xD0, 0x8F, 0xAB, 0xE4, 0x8F, 0xAB, 0xB1].pack('C8')
+ result.force_encoding Encoding::EUC_JP
+ str.send(@method, "euc-jp", "ibm437").should == result
+ end
+
+ it "calls #to_str to convert the from object to an Encoding" do
+ enc = mock("string encode encoding")
+ enc.should_receive(:to_str).and_return("ibm437")
+
+ str = "ã‚"
+ result = [0xA6, 0xD0, 0x8F, 0xAB, 0xE4, 0x8F, 0xAB, 0xB1].pack('C8')
+ result.force_encoding Encoding::EUC_JP
+
+ str.send(@method, "euc-jp", enc).should == result
+ end
+ end
+
+ describe "when passed to, options" do
+ it "replaces undefined characters in the destination encoding" do
+ result = "ã‚?ã‚".send(@method, Encoding::EUC_JP, undef: :replace)
+ # testing for: "\xA4\xA2?\xA4\xA2"
+ xA4xA2 = [0xA4, 0xA2].pack('CC')
+ result.should == "#{xA4xA2}?#{xA4xA2}".force_encoding("euc-jp")
+ end
+
+ it "replaces invalid characters in the destination encoding" do
+ xFF = [0xFF].pack('C').force_encoding('utf-8')
+ "ab#{xFF}c".send(@method, Encoding::ISO_8859_1, invalid: :replace).should == "ab?c"
+ end
+
+ it "calls #to_hash to convert the options object" do
+ options = mock("string encode options")
+ options.should_receive(:to_hash).and_return({ undef: :replace })
+
+ result = "ã‚?ã‚".send(@method, Encoding::EUC_JP, **options)
+ xA4xA2 = [0xA4, 0xA2].pack('CC').force_encoding('utf-8')
+ result.should == "#{xA4xA2}?#{xA4xA2}".force_encoding("euc-jp")
+ end
+ end
+
+ describe "when passed to, from, options" do
+ it "replaces undefined characters in the destination encoding" do
+ str = "ã‚?ã‚".force_encoding Encoding::BINARY
+ result = str.send(@method, "euc-jp", "utf-8", undef: :replace)
+ xA4xA2 = [0xA4, 0xA2].pack('CC').force_encoding('utf-8')
+ result.should == "#{xA4xA2}?#{xA4xA2}".force_encoding("euc-jp")
+ end
+
+ it "replaces invalid characters in the destination encoding" do
+ xFF = [0xFF].pack('C').force_encoding('utf-8')
+ str = "ab#{xFF}c".force_encoding Encoding::BINARY
+ str.send(@method, "iso-8859-1", "utf-8", invalid: :replace).should == "ab?c"
+ end
+
+ it "calls #to_str to convert the to object to an encoding" do
+ to = mock("string encode to encoding")
+ to.should_receive(:to_str).and_return("iso-8859-1")
+
+ xFF = [0xFF].pack('C').force_encoding('utf-8')
+ str = "ab#{xFF}c".force_encoding Encoding::BINARY
+ str.send(@method, to, "utf-8", invalid: :replace).should == "ab?c"
+ end
+
+ it "calls #to_str to convert the from object to an encoding" do
+ from = mock("string encode to encoding")
+ from.should_receive(:to_str).and_return("utf-8")
+
+ xFF = [0xFF].pack('C').force_encoding('utf-8')
+ str = "ab#{xFF}c".force_encoding Encoding::BINARY
+ str.send(@method, "iso-8859-1", from, invalid: :replace).should == "ab?c"
+ end
+
+ it "calls #to_hash to convert the options object" do
+ options = mock("string encode options")
+ options.should_receive(:to_hash).and_return({ invalid: :replace })
+
+ xFF = [0xFF].pack('C').force_encoding('utf-8')
+ str = "ab#{xFF}c".force_encoding Encoding::BINARY
+ str.send(@method, "iso-8859-1", "utf-8", **options).should == "ab?c"
+ end
+ end
+
+ describe "given the xml: :text option" do
+ it "replaces all instances of '&' with '&amp;'" do
+ '& and &'.send(@method, "UTF-8", xml: :text).should == '&amp; and &amp;'
+ end
+
+ it "replaces all instances of '<' with '&lt;'" do
+ '< and <'.send(@method, "UTF-8", xml: :text).should == '&lt; and &lt;'
+ end
+
+ it "replaces all instances of '>' with '&gt;'" do
+ '> and >'.send(@method, "UTF-8", xml: :text).should == '&gt; and &gt;'
+ end
+
+ it "does not replace '\"'" do
+ '" and "'.send(@method, "UTF-8", xml: :text).should == '" and "'
+ end
+
+ it "replaces undefined characters with their upper-case hexadecimal numeric character references" do
+ 'ürst'.send(@method, Encoding::US_ASCII, xml: :text).should == '&#xFC;rst'
+ end
+ end
+
+ describe "given the xml: :attr option" do
+ it "surrounds the encoded text with double-quotes" do
+ 'abc'.send(@method, "UTF-8", xml: :attr).should == '"abc"'
+ end
+
+ it "replaces all instances of '&' with '&amp;'" do
+ '& and &'.send(@method, "UTF-8", xml: :attr).should == '"&amp; and &amp;"'
+ end
+
+ it "replaces all instances of '<' with '&lt;'" do
+ '< and <'.send(@method, "UTF-8", xml: :attr).should == '"&lt; and &lt;"'
+ end
+
+ it "replaces all instances of '>' with '&gt;'" do
+ '> and >'.send(@method, "UTF-8", xml: :attr).should == '"&gt; and &gt;"'
+ end
+
+ it "replaces all instances of '\"' with '&quot;'" do
+ '" and "'.send(@method, "UTF-8", xml: :attr).should == '"&quot; and &quot;"'
+ end
+
+ it "replaces undefined characters with their upper-case hexadecimal numeric character references" do
+ 'ürst'.send(@method, Encoding::US_ASCII, xml: :attr).should == '"&#xFC;rst"'
+ end
+ end
+
+ it "raises ArgumentError if the value of the :xml option is not :text or :attr" do
+ -> { ''.send(@method, "UTF-8", xml: :other) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/shared/eql.rb b/spec/ruby/core/string/shared/eql.rb
new file mode 100644
index 0000000000..6f268c929c
--- /dev/null
+++ b/spec/ruby/core/string/shared/eql.rb
@@ -0,0 +1,38 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_eql_value, shared: true do
+ it "returns true if self <=> string returns 0" do
+ 'hello'.send(@method, 'hello').should be_true
+ end
+
+ it "returns false if self <=> string does not return 0" do
+ "more".send(@method, "MORE").should be_false
+ "less".send(@method, "greater").should be_false
+ end
+
+ it "ignores encoding difference of compatible string" do
+ "hello".force_encoding("utf-8").send(@method, "hello".force_encoding("iso-8859-1")).should be_true
+ end
+
+ it "considers encoding difference of incompatible string" do
+ "\xff".force_encoding("utf-8").send(@method, "\xff".force_encoding("iso-8859-1")).should be_false
+ end
+
+ it "considers encoding compatibility" do
+ "abcd".force_encoding("utf-8").send(@method, "abcd".force_encoding("utf-32le")).should be_false
+ end
+
+ it "ignores subclass differences" do
+ a = "hello"
+ b = StringSpecs::MyString.new("hello")
+
+ a.send(@method, b).should be_true
+ b.send(@method, a).should be_true
+ end
+
+ it "returns true when comparing 2 empty strings but one is not ASCII-compatible" do
+ "".send(@method, "".force_encoding('iso-2022-jp')).should == true
+ end
+end
diff --git a/spec/ruby/core/string/shared/equal_value.rb b/spec/ruby/core/string/shared/equal_value.rb
new file mode 100644
index 0000000000..fccafb5821
--- /dev/null
+++ b/spec/ruby/core/string/shared/equal_value.rb
@@ -0,0 +1,29 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_equal_value, shared: true do
+ it "returns false if obj does not respond to to_str" do
+ 'hello'.send(@method, 5).should be_false
+ not_supported_on :opal do
+ 'hello'.send(@method, :hello).should be_false
+ end
+ 'hello'.send(@method, mock('x')).should be_false
+ end
+
+ it "returns obj == self if obj responds to to_str" do
+ obj = Object.new
+
+ # String#== merely checks if #to_str is defined. It does
+ # not call it.
+ obj.stub!(:to_str)
+
+ # Don't use @method for :== in `obj.should_receive(:==)`
+ obj.should_receive(:==).and_return(true)
+
+ 'hello'.send(@method, obj).should be_true
+ end
+
+ it "is not fooled by NUL characters" do
+ "abc\0def".send(@method, "abc\0xyz").should be_false
+ end
+end
diff --git a/spec/ruby/core/string/shared/grapheme_clusters.rb b/spec/ruby/core/string/shared/grapheme_clusters.rb
new file mode 100644
index 0000000000..8b666868b1
--- /dev/null
+++ b/spec/ruby/core/string/shared/grapheme_clusters.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_grapheme_clusters, shared: true do
+ it "passes each grapheme cluster in self to the given block" do
+ a = []
+ # test string: abc[rainbow flag emoji][paw prints]
+ "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}".send(@method) { |c| a << c }
+ a.should == ['a', 'b', "\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}", "\u{1F43E}"]
+ end
+
+ it "returns self" do
+ s = StringSpecs::MyString.new "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}"
+ s.send(@method) {}.should equal(s)
+ end
+end
diff --git a/spec/ruby/core/string/shared/length.rb b/spec/ruby/core/string/shared/length.rb
new file mode 100644
index 0000000000..94e5ec135b
--- /dev/null
+++ b/spec/ruby/core/string/shared/length.rb
@@ -0,0 +1,55 @@
+# encoding: utf-8
+
+describe :string_length, shared: true do
+ it "returns the length of self" do
+ "".send(@method).should == 0
+ "\x00".send(@method).should == 1
+ "one".send(@method).should == 3
+ "two".send(@method).should == 3
+ "three".send(@method).should == 5
+ "four".send(@method).should == 4
+ end
+
+ it "returns the length of a string in different encodings" do
+ utf8_str = 'ã“ã«ã¡ã‚' * 100
+ utf8_str.send(@method).should == 400
+ utf8_str.encode(Encoding::UTF_32BE).send(@method).should == 400
+ utf8_str.encode(Encoding::SHIFT_JIS).send(@method).should == 400
+ end
+
+ it "returns the length of the new self after encoding is changed" do
+ str = 'ã“ã«ã¡ã‚'
+ str.send(@method)
+
+ str.force_encoding('BINARY').send(@method).should == 12
+ end
+
+ it "returns the correct length after force_encoding(BINARY)" do
+ utf8 = "ã‚"
+ ascii = "a"
+ concat = utf8 + ascii
+
+ concat.encoding.should == Encoding::UTF_8
+ concat.bytesize.should == 4
+
+ concat.send(@method).should == 2
+ concat.force_encoding(Encoding::ASCII_8BIT)
+ concat.send(@method).should == 4
+ end
+
+ it "adds 1 for every invalid byte in UTF-8" do
+ "\xF4\x90\x80\x80".send(@method).should == 4
+ "a\xF4\x90\x80\x80b".send(@method).should == 6
+ "é\xF4\x90\x80\x80è".send(@method).should == 6
+ end
+
+ it "adds 1 (and not 2) for a incomplete surrogate in UTF-16" do
+ "\x00\xd8".force_encoding("UTF-16LE").send(@method).should == 1
+ "\xd8\x00".force_encoding("UTF-16BE").send(@method).should == 1
+ end
+
+ it "adds 1 for a broken sequence in UTF-32" do
+ "\x04\x03\x02\x01".force_encoding("UTF-32LE").send(@method).should == 1
+ "\x01\x02\x03\x04".force_encoding("UTF-32BE").send(@method).should == 1
+ end
+end
diff --git a/spec/ruby/core/string/shared/partition.rb b/spec/ruby/core/string/shared/partition.rb
new file mode 100644
index 0000000000..41b3c7e0c9
--- /dev/null
+++ b/spec/ruby/core/string/shared/partition.rb
@@ -0,0 +1,51 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_partition, shared: true do
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("hello").send(@method, "l").each do |item|
+ item.should be_an_instance_of(String)
+ end
+
+ StringSpecs::MyString.new("hello").send(@method, "x").each do |item|
+ item.should be_an_instance_of(String)
+ end
+
+ StringSpecs::MyString.new("hello").send(@method, /l./).each do |item|
+ item.should be_an_instance_of(String)
+ end
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("hello").send(@method, StringSpecs::MyString.new("l")).each do |item|
+ item.should be_an_instance_of(StringSpecs::MyString)
+ end
+
+ StringSpecs::MyString.new("hello").send(@method, "x").each do |item|
+ item.should be_an_instance_of(StringSpecs::MyString)
+ end
+
+ StringSpecs::MyString.new("hello").send(@method, /l./).each do |item|
+ item.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+ end
+
+ it "returns before- and after- parts in the same encoding as self" do
+ strings = "hello".encode("US-ASCII").send(@method, "ello")
+ strings[0].encoding.should == Encoding::US_ASCII
+ strings[2].encoding.should == Encoding::US_ASCII
+
+ strings = "hello".encode("US-ASCII").send(@method, /ello/)
+ strings[0].encoding.should == Encoding::US_ASCII
+ strings[2].encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns the matching part in the separator's encoding" do
+ strings = "hello".encode("US-ASCII").send(@method, "ello")
+ strings[1].encoding.should == Encoding::UTF_8
+ end
+end
diff --git a/spec/ruby/core/string/shared/replace.rb b/spec/ruby/core/string/shared/replace.rb
new file mode 100644
index 0000000000..a5108d9e7c
--- /dev/null
+++ b/spec/ruby/core/string/shared/replace.rb
@@ -0,0 +1,47 @@
+describe :string_replace, shared: true do
+ it "returns self" do
+ a = "a"
+ a.send(@method, "b").should equal(a)
+ end
+
+ it "replaces the content of self with other" do
+ a = "some string"
+ a.send(@method, "another string")
+ a.should == "another string"
+ end
+
+ it "replaces the encoding of self with that of other" do
+ a = "".encode("UTF-16LE")
+ b = "".encode("UTF-8")
+ a.send(@method, b)
+ a.encoding.should == Encoding::UTF_8
+ end
+
+ it "carries over the encoding invalidity" do
+ a = "\u{8765}".force_encoding('ascii')
+ "".send(@method, a).valid_encoding?.should be_false
+ end
+
+ it "tries to convert other to string using to_str" do
+ other = mock('x')
+ other.should_receive(:to_str).and_return("converted to a string")
+ "hello".send(@method, other).should == "converted to a string"
+ end
+
+ it "raises a TypeError if other can't be converted to string" do
+ -> { "hello".send(@method, 123) }.should raise_error(TypeError)
+ -> { "hello".send(@method, []) }.should raise_error(TypeError)
+ -> { "hello".send(@method, mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ a = "hello".freeze
+ -> { a.send(@method, "world") }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on a frozen instance when self-replacing" do
+ a = "hello".freeze
+ -> { a.send(@method, a) }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/shared/slice.rb b/spec/ruby/core/string/shared/slice.rb
new file mode 100644
index 0000000000..a7c1d05b56
--- /dev/null
+++ b/spec/ruby/core/string/shared/slice.rb
@@ -0,0 +1,562 @@
+describe :string_slice, shared: true do
+ it "returns the character code of the character at the given index" do
+ "hello".send(@method, 0).should == ?h
+ "hello".send(@method, -1).should == ?o
+ end
+
+ it "returns nil if index is outside of self" do
+ "hello".send(@method, 20).should == nil
+ "hello".send(@method, -20).should == nil
+
+ "".send(@method, 0).should == nil
+ "".send(@method, -1).should == nil
+ end
+
+ it "calls to_int on the given index" do
+ "hello".send(@method, 0.5).should == ?h
+
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ "hello".send(@method, obj).should == ?e
+ end
+
+ it "raises a TypeError if the given index is nil" do
+ -> { "hello".send(@method, nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the given index can't be converted to an Integer" do
+ -> { "hello".send(@method, mock('x')) }.should raise_error(TypeError)
+ -> { "hello".send(@method, {}) }.should raise_error(TypeError)
+ -> { "hello".send(@method, []) }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError if the index is too big" do
+ -> { "hello".send(@method, bignum_value) }.should raise_error(RangeError)
+ end
+end
+
+describe :string_slice_index_length, shared: true do
+ it "returns the substring starting at the given index with the given length" do
+ "hello there".send(@method, 0,0).should == ""
+ "hello there".send(@method, 0,1).should == "h"
+ "hello there".send(@method, 0,3).should == "hel"
+ "hello there".send(@method, 0,6).should == "hello "
+ "hello there".send(@method, 0,9).should == "hello the"
+ "hello there".send(@method, 0,12).should == "hello there"
+
+ "hello there".send(@method, 1,0).should == ""
+ "hello there".send(@method, 1,1).should == "e"
+ "hello there".send(@method, 1,3).should == "ell"
+ "hello there".send(@method, 1,6).should == "ello t"
+ "hello there".send(@method, 1,9).should == "ello ther"
+ "hello there".send(@method, 1,12).should == "ello there"
+
+ "hello there".send(@method, 3,0).should == ""
+ "hello there".send(@method, 3,1).should == "l"
+ "hello there".send(@method, 3,3).should == "lo "
+ "hello there".send(@method, 3,6).should == "lo the"
+ "hello there".send(@method, 3,9).should == "lo there"
+
+ "hello there".send(@method, 4,0).should == ""
+ "hello there".send(@method, 4,3).should == "o t"
+ "hello there".send(@method, 4,6).should == "o ther"
+ "hello there".send(@method, 4,9).should == "o there"
+
+ "foo".send(@method, 2,1).should == "o"
+ "foo".send(@method, 3,0).should == ""
+ "foo".send(@method, 3,1).should == ""
+
+ "".send(@method, 0,0).should == ""
+ "".send(@method, 0,1).should == ""
+
+ "x".send(@method, 0,0).should == ""
+ "x".send(@method, 0,1).should == "x"
+ "x".send(@method, 1,0).should == ""
+ "x".send(@method, 1,1).should == ""
+
+ "x".send(@method, -1,0).should == ""
+ "x".send(@method, -1,1).should == "x"
+
+ "hello there".send(@method, -3,2).should == "er"
+ end
+
+ it "returns a string with the same encoding as self" do
+ s = "hello there"
+ s.send(@method, 1, 9).encoding.should == s.encoding
+
+ a = "hello".force_encoding("binary")
+ b = " there".force_encoding("ISO-8859-1")
+ c = (a + b).force_encoding(Encoding::US_ASCII)
+
+ c.send(@method, 0, 5).encoding.should == Encoding::US_ASCII
+ c.send(@method, 5, 6).encoding.should == Encoding::US_ASCII
+ c.send(@method, 1, 3).encoding.should == Encoding::US_ASCII
+ c.send(@method, 8, 2).encoding.should == Encoding::US_ASCII
+ c.send(@method, 1, 10).encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns nil if the offset falls outside of self" do
+ "hello there".send(@method, 20,3).should == nil
+ "hello there".send(@method, -20,3).should == nil
+
+ "".send(@method, 1,0).should == nil
+ "".send(@method, 1,1).should == nil
+
+ "".send(@method, -1,0).should == nil
+ "".send(@method, -1,1).should == nil
+
+ "x".send(@method, 2,0).should == nil
+ "x".send(@method, 2,1).should == nil
+
+ "x".send(@method, -2,0).should == nil
+ "x".send(@method, -2,1).should == nil
+
+ "x".send(@method, fixnum_max, 1).should == nil
+ end
+
+ it "returns nil if the length is negative" do
+ "hello there".send(@method, 4,-3).should == nil
+ "hello there".send(@method, -4,-3).should == nil
+ end
+
+ it "calls to_int on the given index and the given length" do
+ "hello".send(@method, 0.5, 1).should == "h"
+ "hello".send(@method, 0.5, 2.5).should == "he"
+ "hello".send(@method, 1, 2.5).should == "el"
+
+ obj = mock('2')
+ obj.should_receive(:to_int).exactly(4).times.and_return(2)
+
+ "hello".send(@method, obj, 1).should == "l"
+ "hello".send(@method, obj, obj).should == "ll"
+ "hello".send(@method, 0, obj).should == "he"
+ end
+
+ it "raises a TypeError when idx or length can't be converted to an integer" do
+ -> { "hello".send(@method, mock('x'), 0) }.should raise_error(TypeError)
+ -> { "hello".send(@method, 0, mock('x')) }.should raise_error(TypeError)
+
+ # I'm deliberately including this here.
+ # It means that str.send(@method, other, idx) isn't supported.
+ -> { "hello".send(@method, "", 0) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the given index or the given length is nil" do
+ -> { "hello".send(@method, 1, nil) }.should raise_error(TypeError)
+ -> { "hello".send(@method, nil, 1) }.should raise_error(TypeError)
+ -> { "hello".send(@method, nil, nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError if the index or length is too big" do
+ -> { "hello".send(@method, bignum_value, 1) }.should raise_error(RangeError)
+ -> { "hello".send(@method, 0, bignum_value) }.should raise_error(RangeError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, 0,0).should be_an_instance_of(StringSpecs::MyString)
+ s.send(@method, 0,4).should be_an_instance_of(StringSpecs::MyString)
+ s.send(@method, 1,4).should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, 0,0).should be_an_instance_of(String)
+ s.send(@method, 0,4).should be_an_instance_of(String)
+ s.send(@method, 1,4).should be_an_instance_of(String)
+ end
+ end
+
+ it "handles repeated application" do
+ "hello world".send(@method, 6, 5).send(@method, 0, 1).should == 'w'
+ "hello world".send(@method, 6, 5).send(@method, 0, 5).should == 'world'
+
+ "hello world".send(@method, 6, 5).send(@method, 1, 1).should == 'o'
+ "hello world".send(@method, 6, 5).send(@method, 1, 4).should == 'orld'
+
+ "hello world".send(@method, 6, 5).send(@method, 4, 1).should == 'd'
+ "hello world".send(@method, 6, 5).send(@method, 5, 0).should == ''
+
+ "hello world".send(@method, 6, 0).send(@method, -1, 0).should == nil
+ "hello world".send(@method, 6, 0).send(@method, 1, 1).should == nil
+ end
+end
+
+describe :string_slice_range, shared: true do
+ it "returns the substring given by the offsets of the range" do
+ "hello there".send(@method, 1..1).should == "e"
+ "hello there".send(@method, 1..3).should == "ell"
+ "hello there".send(@method, 1...3).should == "el"
+ "hello there".send(@method, -4..-2).should == "her"
+ "hello there".send(@method, -4...-2).should == "he"
+ "hello there".send(@method, 5..-1).should == " there"
+ "hello there".send(@method, 5...-1).should == " ther"
+
+ "".send(@method, 0..0).should == ""
+
+ "x".send(@method, 0..0).should == "x"
+ "x".send(@method, 0..1).should == "x"
+ "x".send(@method, 0...1).should == "x"
+ "x".send(@method, 0..-1).should == "x"
+
+ "x".send(@method, 1..1).should == ""
+ "x".send(@method, 1..-1).should == ""
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hello there".encode("US-ASCII").send(@method, 1..1).encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns nil if the beginning of the range falls outside of self" do
+ "hello there".send(@method, 12..-1).should == nil
+ "hello there".send(@method, 20..25).should == nil
+ "hello there".send(@method, 20..1).should == nil
+ "hello there".send(@method, -20..1).should == nil
+ "hello there".send(@method, -20..-1).should == nil
+
+ "".send(@method, -1..-1).should == nil
+ "".send(@method, -1...-1).should == nil
+ "".send(@method, -1..0).should == nil
+ "".send(@method, -1...0).should == nil
+ end
+
+ it "returns an empty string if range.begin is inside self and > real end" do
+ "hello there".send(@method, 1...1).should == ""
+ "hello there".send(@method, 4..2).should == ""
+ "hello".send(@method, 4..-4).should == ""
+ "hello there".send(@method, -5..-6).should == ""
+ "hello there".send(@method, -2..-4).should == ""
+ "hello there".send(@method, -5..-6).should == ""
+ "hello there".send(@method, -5..2).should == ""
+
+ "".send(@method, 0...0).should == ""
+ "".send(@method, 0..-1).should == ""
+ "".send(@method, 0...-1).should == ""
+
+ "x".send(@method, 0...0).should == ""
+ "x".send(@method, 0...-1).should == ""
+ "x".send(@method, 1...1).should == ""
+ "x".send(@method, 1...-1).should == ""
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, 0...0).should be_an_instance_of(StringSpecs::MyString)
+ s.send(@method, 0..4).should be_an_instance_of(StringSpecs::MyString)
+ s.send(@method, 1..4).should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, 0...0).should be_an_instance_of(String)
+ s.send(@method, 0..4).should be_an_instance_of(String)
+ s.send(@method, 1..4).should be_an_instance_of(String)
+ end
+ end
+
+ it "calls to_int on range arguments" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ from.should_receive(:<=>).twice.and_return(0)
+
+ from.should_receive(:to_int).twice.and_return(1)
+ to.should_receive(:to_int).twice.and_return(-2)
+
+ "hello there".send(@method, from..to).should == "ello ther"
+ "hello there".send(@method, from...to).should == "ello the"
+ end
+
+ it "works with Range subclasses" do
+ a = "GOOD"
+ range_incl = StringSpecs::MyRange.new(1, 2)
+ range_excl = StringSpecs::MyRange.new(-3, -1, true)
+
+ a.send(@method, range_incl).should == "OO"
+ a.send(@method, range_excl).should == "OO"
+ end
+
+ it "handles repeated application" do
+ "hello world".send(@method, 6..11).send(@method, 0..0).should == 'w'
+ "hello world".send(@method, 6..11).send(@method, 0..4).should == 'world'
+
+ "hello world".send(@method, 6..11).send(@method, 1..1).should == 'o'
+ "hello world".send(@method, 6..11).send(@method, 1..4).should == 'orld'
+
+ "hello world".send(@method, 6..11).send(@method, 4..4).should == 'd'
+ "hello world".send(@method, 6..11).send(@method, 5..4).should == ''
+
+ "hello world".send(@method, 6..5).send(@method, -1..-1).should == nil
+ "hello world".send(@method, 6..5).send(@method, 1..1).should == nil
+ end
+
+ it "raises a type error if a range is passed with a length" do
+ ->{ "hello".send(@method, 1..2, 1) }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError if one of the bound is too big" do
+ -> { "hello".send(@method, bignum_value..(bignum_value + 1)) }.should raise_error(RangeError)
+ -> { "hello".send(@method, 0..bignum_value) }.should raise_error(RangeError)
+ end
+
+ it "works with endless ranges" do
+ "hello there".send(@method, eval("(2..)")).should == "llo there"
+ "hello there".send(@method, eval("(2...)")).should == "llo there"
+ "hello there".send(@method, eval("(-4..)")).should == "here"
+ "hello there".send(@method, eval("(-4...)")).should == "here"
+ end
+
+ it "works with beginless ranges" do
+ "hello there".send(@method, (..5)).should == "hello "
+ "hello there".send(@method, (...5)).should == "hello"
+ "hello there".send(@method, (..-4)).should == "hello th"
+ "hello there".send(@method, (...-4)).should == "hello t"
+ "hello there".send(@method, (...nil)).should == "hello there"
+ end
+end
+
+describe :string_slice_regexp, shared: true do
+ it "returns the matching portion of self" do
+ "hello there".send(@method, /[aeiou](.)\1/).should == "ell"
+ "".send(@method, //).should == ""
+ end
+
+ it "returns nil if there is no match" do
+ "hello there".send(@method, /xyz/).should == nil
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hello there".encode("US-ASCII").send(@method, /[aeiou](.)\1/).encoding.should == Encoding::US_ASCII
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, //).should be_an_instance_of(StringSpecs::MyString)
+ s.send(@method, /../).should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, //).should be_an_instance_of(String)
+ s.send(@method, /../).should be_an_instance_of(String)
+ end
+ end
+
+ it "sets $~ to MatchData when there is a match and nil when there's none" do
+ 'hello'.send(@method, /./)
+ $~[0].should == 'h'
+
+ 'hello'.send(@method, /not/)
+ $~.should == nil
+ end
+end
+
+describe :string_slice_regexp_index, shared: true do
+ it "returns the capture for the given index" do
+ "hello there".send(@method, /[aeiou](.)\1/, 0).should == "ell"
+ "hello there".send(@method, /[aeiou](.)\1/, 1).should == "l"
+ "hello there".send(@method, /[aeiou](.)\1/, -1).should == "l"
+
+ "har".send(@method, /(.)(.)(.)/, 0).should == "har"
+ "har".send(@method, /(.)(.)(.)/, 1).should == "h"
+ "har".send(@method, /(.)(.)(.)/, 2).should == "a"
+ "har".send(@method, /(.)(.)(.)/, 3).should == "r"
+ "har".send(@method, /(.)(.)(.)/, -1).should == "r"
+ "har".send(@method, /(.)(.)(.)/, -2).should == "a"
+ "har".send(@method, /(.)(.)(.)/, -3).should == "h"
+ end
+
+ it "returns nil if there is no match" do
+ "hello there".send(@method, /(what?)/, 1).should == nil
+ end
+
+ it "returns nil if the index is larger than the number of captures" do
+ "hello there".send(@method, /hello (.)/, 2).should == nil
+ # You can't refer to 0 using negative indices
+ "hello there".send(@method, /hello (.)/, -2).should == nil
+ end
+
+ it "returns nil if there is no capture for the given index" do
+ "hello there".send(@method, /[aeiou](.)\1/, 2).should == nil
+ end
+
+ it "returns nil if the given capture group was not matched but still sets $~" do
+ "test".send(@method, /te(z)?/, 1).should == nil
+ $~[0].should == "te"
+ $~[1].should == nil
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hello there".encode("US-ASCII").send(@method, /[aeiou](.)\1/, 0).encoding.should == Encoding::US_ASCII
+ end
+
+ it "calls to_int on the given index" do
+ obj = mock('2')
+ obj.should_receive(:to_int).and_return(2)
+
+ "har".send(@method, /(.)(.)(.)/, 1.5).should == "h"
+ "har".send(@method, /(.)(.)(.)/, obj).should == "a"
+ end
+
+ it "raises a TypeError when the given index can't be converted to Integer" do
+ -> { "hello".send(@method, /(.)(.)(.)/, mock('x')) }.should raise_error(TypeError)
+ -> { "hello".send(@method, /(.)(.)(.)/, {}) }.should raise_error(TypeError)
+ -> { "hello".send(@method, /(.)(.)(.)/, []) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the given index is nil" do
+ -> { "hello".send(@method, /(.)(.)(.)/, nil) }.should raise_error(TypeError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, /(.)(.)/, 0).should be_an_instance_of(StringSpecs::MyString)
+ s.send(@method, /(.)(.)/, 1).should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, /(.)(.)/, 0).should be_an_instance_of(String)
+ s.send(@method, /(.)(.)/, 1).should be_an_instance_of(String)
+ end
+ end
+
+ it "sets $~ to MatchData when there is a match and nil when there's none" do
+ 'hello'.send(@method, /.(.)/, 0)
+ $~[0].should == 'he'
+
+ 'hello'.send(@method, /.(.)/, 1)
+ $~[1].should == 'e'
+
+ 'hello'.send(@method, /not/, 0)
+ $~.should == nil
+ end
+end
+
+describe :string_slice_string, shared: true do
+ it "returns other_str if it occurs in self" do
+ s = "lo"
+ "hello there".send(@method, s).should == s
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+
+ 'hello'.send(@method, 'll')
+ $~.should == nil
+ end
+
+ it "returns nil if there is no match" do
+ "hello there".send(@method, "bye").should == nil
+ end
+
+ it "doesn't call to_str on its argument" do
+ o = mock('x')
+ o.should_not_receive(:to_str)
+
+ -> { "hello".send(@method, o) }.should raise_error(TypeError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a subclass instance when given a subclass instance" do
+ s = StringSpecs::MyString.new("el")
+ r = "hello".send(@method, s)
+ r.should == "el"
+ r.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a String instance when given a subclass instance" do
+ s = StringSpecs::MyString.new("el")
+ r = "hello".send(@method, s)
+ r.should == "el"
+ r.should be_an_instance_of(String)
+ end
+ end
+end
+
+describe :string_slice_regexp_group, shared: true do
+ not_supported_on :opal do
+ it "returns the capture for the given name" do
+ "hello there".send(@method, /(?<g>[aeiou](.))/, 'g').should == "el"
+ "hello there".send(@method, /[aeiou](?<g>.)/, 'g').should == "l"
+
+ "har".send(@method, /(?<g>(.)(.)(.))/, 'g').should == "har"
+ "har".send(@method, /(?<h>.)(.)(.)/, 'h').should == "h"
+ "har".send(@method, /(.)(?<a>.)(.)/, 'a').should == "a"
+ "har".send(@method, /(.)(.)(?<r>.)/, 'r').should == "r"
+ "har".send(@method, /(?<h>.)(?<a>.)(?<r>.)/, 'r').should == "r"
+ end
+
+ it "returns the last capture for duplicate names" do
+ "hello there".send(@method, /(?<g>h)(?<g>.)/, 'g').should == "e"
+ "hello there".send(@method, /(?<g>h)(?<g>.)(?<f>.)/, 'g').should == "e"
+ end
+
+ it "returns the innermost capture for nested duplicate names" do
+ "hello there".send(@method, /(?<g>h(?<g>.))/, 'g').should == "e"
+ end
+
+ it "returns nil if there is no match" do
+ "hello there".send(@method, /(?<whut>what?)/, 'whut').should be_nil
+ end
+
+ it "raises an IndexError if there is no capture for the given name" do
+ -> do
+ "hello there".send(@method, /[aeiou](.)\1/, 'non')
+ end.should raise_error(IndexError)
+ end
+
+ it "raises a TypeError when the given name is not a String" do
+ -> { "hello".send(@method, /(?<q>.)/, mock('x')) }.should raise_error(TypeError)
+ -> { "hello".send(@method, /(?<q>.)/, {}) }.should raise_error(TypeError)
+ -> { "hello".send(@method, /(?<q>.)/, []) }.should raise_error(TypeError)
+ end
+
+ it "raises an IndexError when given the empty String as a group name" do
+ -> { "hello".send(@method, /(?<q>)/, '') }.should raise_error(IndexError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, /(?<q>.)/, 'q').should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, /(?<q>.)/, 'q').should be_an_instance_of(String)
+ end
+ end
+
+ it "sets $~ to MatchData when there is a match and nil when there's none" do
+ 'hello'.send(@method, /(?<hi>.(.))/, 'hi')
+ $~[0].should == 'he'
+
+ 'hello'.send(@method, /(?<non>not)/, 'non')
+ $~.should be_nil
+ end
+ end
+end
+
+describe :string_slice_symbol, shared: true do
+ it "raises TypeError" do
+ -> { 'hello'.send(@method, :hello) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/string/shared/strip.rb b/spec/ruby/core/string/shared/strip.rb
new file mode 100644
index 0000000000..0c0aae20f3
--- /dev/null
+++ b/spec/ruby/core/string/shared/strip.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_strip, shared: true do
+ it "returns a String in the same encoding as self" do
+ " hello ".encode("US-ASCII").send(@method).encoding.should == Encoding::US_ASCII
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new(" hello ").send(@method).should be_an_instance_of(String)
+ StringSpecs::MyString.new(" ").send(@method).should be_an_instance_of(String)
+ StringSpecs::MyString.new("").send(@method).should be_an_instance_of(String)
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new(" hello ").send(@method).should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new(" ").send(@method).should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("").send(@method).should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/succ.rb b/spec/ruby/core/string/shared/succ.rb
new file mode 100644
index 0000000000..3605fa99a2
--- /dev/null
+++ b/spec/ruby/core/string/shared/succ.rb
@@ -0,0 +1,96 @@
+# -*- encoding: binary -*-
+describe :string_succ, shared: true do
+ it "returns an empty string for empty strings" do
+ "".send(@method).should == ""
+ end
+
+ it "returns the successor by increasing the rightmost alphanumeric (digit => digit, letter => letter with same case)" do
+ "abcd".send(@method).should == "abce"
+ "THX1138".send(@method).should == "THX1139"
+
+ "<<koala>>".send(@method).should == "<<koalb>>"
+ "==A??".send(@method).should == "==B??"
+ end
+
+ it "increases non-alphanumerics (via ascii rules) if there are no alphanumerics" do
+ "***".send(@method).should == "**+"
+ "**`".send(@method).should == "**a"
+ end
+
+ it "increases the next best alphanumeric (jumping over non-alphanumerics) if there is a carry" do
+ "dz".send(@method).should == "ea"
+ "HZ".send(@method).should == "IA"
+ "49".send(@method).should == "50"
+
+ "izz".send(@method).should == "jaa"
+ "IZZ".send(@method).should == "JAA"
+ "699".send(@method).should == "700"
+
+ "6Z99z99Z".send(@method).should == "7A00a00A"
+
+ "1999zzz".send(@method).should == "2000aaa"
+ "NZ/[]ZZZ9999".send(@method).should == "OA/[]AAA0000"
+ end
+
+ it "increases the next best character if there is a carry for non-alphanumerics" do
+ "(\xFF".send(@method).should == ")\x00"
+ "`\xFF".send(@method).should == "a\x00"
+ "<\xFF\xFF".send(@method).should == "=\x00\x00"
+ end
+
+ it "adds an additional character (just left to the last increased one) if there is a carry and no character left to increase" do
+ "z".send(@method).should == "aa"
+ "Z".send(@method).should == "AA"
+ "9".send(@method).should == "10"
+
+ "zz".send(@method).should == "aaa"
+ "ZZ".send(@method).should == "AAA"
+ "99".send(@method).should == "100"
+
+ "9Z99z99Z".send(@method).should == "10A00a00A"
+
+ "ZZZ9999".send(@method).should == "AAAA0000"
+ "/[]9999".send(@method).should == "/[]10000"
+ "/[]ZZZ9999".send(@method).should == "/[]AAAA0000"
+ "Z/[]ZZZ9999".send(@method).should == "AA/[]AAA0000"
+
+ # non-alphanumeric cases
+ "\xFF".send(@method).should == "\x01\x00"
+ "\xFF\xFF".send(@method).should == "\x01\x00\x00"
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("").send(@method).should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("a").send(@method).should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("z").send(@method).should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("").send(@method).should be_an_instance_of(String)
+ StringSpecs::MyString.new("a").send(@method).should be_an_instance_of(String)
+ StringSpecs::MyString.new("z").send(@method).should be_an_instance_of(String)
+ end
+ end
+
+ it "returns a String in the same encoding as self" do
+ "z".encode("US-ASCII").send(@method).encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe :string_succ_bang, shared: true do
+ it "is equivalent to succ, but modifies self in place (still returns self)" do
+ ["", "abcd", "THX1138"].each do |s|
+ r = s.dup.send(@method)
+ s.send(@method).should equal(s)
+ s.should == r
+ end
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ -> { "".freeze.send(@method) }.should raise_error(FrozenError)
+ -> { "abcd".freeze.send(@method) }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/shared/to_a.rb b/spec/ruby/core/string/shared/to_a.rb
new file mode 100644
index 0000000000..bad3ea6584
--- /dev/null
+++ b/spec/ruby/core/string/shared/to_a.rb
@@ -0,0 +1,9 @@
+describe :string_to_a, shared: true do
+ it "returns an empty array for empty strings" do
+ "".send(@method).should == []
+ end
+
+ it "returns an array containing the string for non-empty strings" do
+ "hello".send(@method).should == ["hello"]
+ end
+end
diff --git a/spec/ruby/core/string/shared/to_s.rb b/spec/ruby/core/string/shared/to_s.rb
new file mode 100644
index 0000000000..4b87a6cbe1
--- /dev/null
+++ b/spec/ruby/core/string/shared/to_s.rb
@@ -0,0 +1,13 @@
+describe :string_to_s, shared: true do
+ it "returns self when self.class == String" do
+ a = "a string"
+ a.should equal(a.send(@method))
+ end
+
+ it "returns a new instance of String when called on a subclass" do
+ a = StringSpecs::MyString.new("a string")
+ s = a.send(@method)
+ s.should == "a string"
+ s.should be_an_instance_of(String)
+ end
+end
diff --git a/spec/ruby/core/string/shared/to_sym.rb b/spec/ruby/core/string/shared/to_sym.rb
new file mode 100644
index 0000000000..ef7c22bb6a
--- /dev/null
+++ b/spec/ruby/core/string/shared/to_sym.rb
@@ -0,0 +1,72 @@
+describe :string_to_sym, shared: true do
+ it "returns the symbol corresponding to self" do
+ "Koala".send(@method).should equal :Koala
+ 'cat'.send(@method).should equal :cat
+ '@cat'.send(@method).should equal :@cat
+ 'cat and dog'.send(@method).should equal :"cat and dog"
+ "abc=".send(@method).should equal :abc=
+ end
+
+ it "does not special case +(binary) and -(binary)" do
+ "+(binary)".send(@method).should equal :"+(binary)"
+ "-(binary)".send(@method).should equal :"-(binary)"
+ end
+
+ it "does not special case certain operators" do
+ "!@".send(@method).should equal :"!@"
+ "~@".send(@method).should equal :"~@"
+ "!(unary)".send(@method).should equal :"!(unary)"
+ "~(unary)".send(@method).should equal :"~(unary)"
+ "+(unary)".send(@method).should equal :"+(unary)"
+ "-(unary)".send(@method).should equal :"-(unary)"
+ end
+
+ it "returns a US-ASCII Symbol for a UTF-8 String containing only US-ASCII characters" do
+ sym = "foobar".send(@method)
+ sym.encoding.should == Encoding::US_ASCII
+ sym.should equal :"foobar"
+ end
+
+ it "returns a US-ASCII Symbol for a binary String containing only US-ASCII characters" do
+ sym = "foobar".b.send(@method)
+ sym.encoding.should == Encoding::US_ASCII
+ sym.should equal :"foobar"
+ end
+
+ it "returns a UTF-8 Symbol for a UTF-8 String containing non US-ASCII characters" do
+ sym = "il était une fois".send(@method)
+ sym.encoding.should == Encoding::UTF_8
+ sym.should equal :"il était une #{'fois'}"
+ end
+
+ it "returns a UTF-16LE Symbol for a UTF-16LE String containing non US-ASCII characters" do
+ utf16_str = "UtéF16".encode(Encoding::UTF_16LE)
+ sym = utf16_str.send(@method)
+ sym.encoding.should == Encoding::UTF_16LE
+ sym.to_s.should == utf16_str
+ end
+
+ it "returns a binary Symbol for a binary String containing non US-ASCII characters" do
+ binary_string = "binarí".b
+ sym = binary_string.send(@method)
+ sym.encoding.should == Encoding::BINARY
+ sym.to_s.should == binary_string
+ end
+
+ it "ignores existing symbols with different encoding" do
+ source = "fée"
+
+ iso_symbol = source.force_encoding(Encoding::ISO_8859_1).send(@method)
+ iso_symbol.encoding.should == Encoding::ISO_8859_1
+ binary_symbol = source.force_encoding(Encoding::BINARY).send(@method)
+ binary_symbol.encoding.should == Encoding::BINARY
+ end
+
+ it "raises an EncodingError for UTF-8 String containing invalid bytes" do
+ invalid_utf8 = "\xC3"
+ invalid_utf8.should_not.valid_encoding?
+ -> {
+ invalid_utf8.send(@method)
+ }.should raise_error(EncodingError, /invalid/)
+ end
+end
diff --git a/spec/ruby/core/string/size_spec.rb b/spec/ruby/core/string/size_spec.rb
new file mode 100644
index 0000000000..9e1f40c5ae
--- /dev/null
+++ b/spec/ruby/core/string/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/length'
+
+describe "String#size" do
+ it_behaves_like :string_length, :size
+end
diff --git a/spec/ruby/core/string/slice_spec.rb b/spec/ruby/core/string/slice_spec.rb
new file mode 100644
index 0000000000..c9e13ed1bc
--- /dev/null
+++ b/spec/ruby/core/string/slice_spec.rb
@@ -0,0 +1,441 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/slice'
+
+describe "String#slice" do
+ it_behaves_like :string_slice, :slice
+end
+
+describe "String#slice with index, length" do
+ it_behaves_like :string_slice_index_length, :slice
+end
+
+describe "String#slice with Range" do
+ it_behaves_like :string_slice_range, :slice
+end
+
+describe "String#slice with Regexp" do
+ it_behaves_like :string_slice_regexp, :slice
+end
+
+describe "String#slice with Regexp, index" do
+ it_behaves_like :string_slice_regexp_index, :slice
+end
+
+describe "String#slice with Regexp, group" do
+ it_behaves_like :string_slice_regexp_group, :slice
+end
+
+describe "String#slice with String" do
+ it_behaves_like :string_slice_string, :slice
+end
+
+describe "String#slice with Symbol" do
+ it_behaves_like :string_slice_symbol, :slice
+end
+
+describe "String#slice! with index" do
+ it "deletes and return the char at the given position" do
+ a = "hello"
+ a.slice!(1).should == ?e
+ a.should == "hllo"
+ a.slice!(-1).should == ?o
+ a.should == "hll"
+ end
+
+ it "returns nil if idx is outside of self" do
+ a = "hello"
+ a.slice!(20).should == nil
+ a.should == "hello"
+ a.slice!(-20).should == nil
+ a.should == "hello"
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ -> { "hello".freeze.slice!(1) }.should raise_error(FrozenError)
+ -> { "hello".freeze.slice!(10) }.should raise_error(FrozenError)
+ -> { "".freeze.slice!(0) }.should raise_error(FrozenError)
+ end
+
+ it "calls to_int on index" do
+ "hello".slice!(0.5).should == ?h
+
+ obj = mock('1')
+ obj.should_receive(:to_int).at_least(1).and_return(1)
+ "hello".slice!(obj).should == ?e
+
+ obj = mock('1')
+ obj.should_receive(:respond_to?).at_least(1).with(:to_int, true).and_return(true)
+ obj.should_receive(:method_missing).at_least(1).with(:to_int).and_return(1)
+ "hello".slice!(obj).should == ?e
+ end
+
+
+ it "returns the character given by the character index" do
+ "hellö there".slice!(1).should == "e"
+ "hellö there".slice!(4).should == "ö"
+ "hellö there".slice!(6).should == "t"
+ end
+
+end
+
+describe "String#slice! with index, length" do
+ it "deletes and returns the substring at idx and the given length" do
+ a = "hello"
+ a.slice!(1, 2).should == "el"
+ a.should == "hlo"
+
+ a.slice!(1, 0).should == ""
+ a.should == "hlo"
+
+ a.slice!(-2, 4).should == "lo"
+ a.should == "h"
+ end
+
+ it "returns nil if the given position is out of self" do
+ a = "hello"
+ a.slice(10, 3).should == nil
+ a.should == "hello"
+
+ a.slice(-10, 20).should == nil
+ a.should == "hello"
+ end
+
+ it "returns nil if the length is negative" do
+ a = "hello"
+ a.slice(4, -3).should == nil
+ a.should == "hello"
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ -> { "hello".freeze.slice!(1, 2) }.should raise_error(FrozenError)
+ -> { "hello".freeze.slice!(10, 3) }.should raise_error(FrozenError)
+ -> { "hello".freeze.slice!(-10, 3)}.should raise_error(FrozenError)
+ -> { "hello".freeze.slice!(4, -3) }.should raise_error(FrozenError)
+ -> { "hello".freeze.slice!(10, 3) }.should raise_error(FrozenError)
+ -> { "hello".freeze.slice!(-10, 3)}.should raise_error(FrozenError)
+ -> { "hello".freeze.slice!(4, -3) }.should raise_error(FrozenError)
+ end
+
+ it "calls to_int on idx and length" do
+ "hello".slice!(0.5, 2.5).should == "he"
+
+ obj = mock('2')
+ def obj.to_int() 2 end
+ "hello".slice!(obj, obj).should == "ll"
+
+ obj = mock('2')
+ def obj.respond_to?(name, *) name == :to_int; end
+ def obj.method_missing(name, *) name == :to_int ? 2 : super; end
+ "hello".slice!(obj, obj).should == "ll"
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.slice!(0, 0).should be_an_instance_of(StringSpecs::MyString)
+ s.slice!(0, 4).should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.slice!(0, 0).should be_an_instance_of(String)
+ s.slice!(0, 4).should be_an_instance_of(String)
+ end
+ end
+
+ it "returns the substring given by the character offsets" do
+ "hellö there".slice!(1,0).should == ""
+ "hellö there".slice!(1,3).should == "ell"
+ "hellö there".slice!(1,6).should == "ellö t"
+ "hellö there".slice!(1,9).should == "ellö ther"
+ end
+
+ it "treats invalid bytes as single bytes" do
+ xE6xCB = [0xE6,0xCB].pack('CC').force_encoding('utf-8')
+ "a#{xE6xCB}b".slice!(1, 2).should == xE6xCB
+ end
+end
+
+describe "String#slice! Range" do
+ it "deletes and return the substring given by the offsets of the range" do
+ a = "hello"
+ a.slice!(1..3).should == "ell"
+ a.should == "ho"
+ a.slice!(0..0).should == "h"
+ a.should == "o"
+ a.slice!(0...0).should == ""
+ a.should == "o"
+
+ # Edge Case?
+ "hello".slice!(-3..-9).should == ""
+ end
+
+ it "returns nil if the given range is out of self" do
+ a = "hello"
+ a.slice!(-6..-9).should == nil
+ a.should == "hello"
+
+ b = "hello"
+ b.slice!(10..20).should == nil
+ b.should == "hello"
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.slice!(0...0).should be_an_instance_of(StringSpecs::MyString)
+ s.slice!(0..4).should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.slice!(0...0).should be_an_instance_of(String)
+ s.slice!(0..4).should be_an_instance_of(String)
+ end
+ end
+
+ it "calls to_int on range arguments" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ def from.to_int() 1 end
+ def to.to_int() -2 end
+
+ "hello there".slice!(from..to).should == "ello ther"
+
+ from = mock('from')
+ to = mock('to')
+
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ def from.respond_to?(name, *) name == :to_int; end
+ def from.method_missing(name) name == :to_int ? 1 : super; end
+ def to.respond_to?(name, *) name == :to_int; end
+ def to.method_missing(name) name == :to_int ? -2 : super; end
+
+ "hello there".slice!(from..to).should == "ello ther"
+ end
+
+ it "works with Range subclasses" do
+ a = "GOOD"
+ range_incl = StringSpecs::MyRange.new(1, 2)
+
+ a.slice!(range_incl).should == "OO"
+ end
+
+
+ it "returns the substring given by the character offsets of the range" do
+ "hellö there".slice!(1..1).should == "e"
+ "hellö there".slice!(1..3).should == "ell"
+ "hellö there".slice!(1...3).should == "el"
+ "hellö there".slice!(-4..-2).should == "her"
+ "hellö there".slice!(-4...-2).should == "he"
+ "hellö there".slice!(5..-1).should == " there"
+ "hellö there".slice!(5...-1).should == " ther"
+ end
+
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ -> { "hello".freeze.slice!(1..3) }.should raise_error(FrozenError)
+ end
+
+ # see redmine #1551
+ it "raises a FrozenError on a frozen instance that would not be modified" do
+ -> { "hello".freeze.slice!(10..20)}.should raise_error(FrozenError)
+ end
+end
+
+describe "String#slice! with Regexp" do
+ it "deletes and returns the first match from self" do
+ s = "this is a string"
+ s.slice!(/s.*t/).should == 's is a st'
+ s.should == 'thiring'
+
+ c = "hello hello"
+ c.slice!(/llo/).should == "llo"
+ c.should == "he hello"
+ end
+
+ it "returns nil if there was no match" do
+ s = "this is a string"
+ s.slice!(/zzz/).should == nil
+ s.should == "this is a string"
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.slice!(//).should be_an_instance_of(StringSpecs::MyString)
+ s.slice!(/../).should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.slice!(//).should be_an_instance_of(String)
+ s.slice!(/../).should be_an_instance_of(String)
+ end
+ end
+
+ it "returns the matching portion of self with a multi byte character" do
+ "hëllo there".slice!(/[ë](.)\1/).should == "ëll"
+ "".slice!(//).should == ""
+ end
+
+ it "sets $~ to MatchData when there is a match and nil when there's none" do
+ 'hello'.slice!(/./)
+ $~[0].should == 'h'
+
+ 'hello'.slice!(/not/)
+ $~.should == nil
+ end
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ -> { "this is a string".freeze.slice!(/s.*t/) }.should raise_error(FrozenError)
+ end
+
+ it "raises a FrozenError on a frozen instance that would not be modified" do
+ -> { "this is a string".freeze.slice!(/zzz/) }.should raise_error(FrozenError)
+ end
+end
+
+describe "String#slice! with Regexp, index" do
+ it "deletes and returns the capture for idx from self" do
+ str = "hello there"
+ str.slice!(/[aeiou](.)\1/, 0).should == "ell"
+ str.should == "ho there"
+ str.slice!(/(t)h/, 1).should == "t"
+ str.should == "ho here"
+ end
+
+ it "returns nil if there was no match" do
+ s = "this is a string"
+ s.slice!(/x(zzz)/, 1).should == nil
+ s.should == "this is a string"
+ end
+
+ it "returns nil if there is no capture for idx" do
+ "hello there".slice!(/[aeiou](.)\1/, 2).should == nil
+ # You can't refer to 0 using negative indices
+ "hello there".slice!(/[aeiou](.)\1/, -2).should == nil
+ end
+
+ it "accepts a Float for capture index" do
+ "har".slice!(/(.)(.)(.)/, 1.5).should == "h"
+ end
+
+ it "calls #to_int to convert an Object to capture index" do
+ obj = mock('2')
+ obj.should_receive(:to_int).at_least(1).times.and_return(2)
+
+ "har".slice!(/(.)(.)(.)/, obj).should == "a"
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.slice!(/(.)(.)/, 0).should be_an_instance_of(StringSpecs::MyString)
+ s.slice!(/(.)(.)/, 1).should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.slice!(/(.)(.)/, 0).should be_an_instance_of(String)
+ s.slice!(/(.)(.)/, 1).should be_an_instance_of(String)
+ end
+ end
+
+ it "returns the encoding aware capture for the given index" do
+ "hår".slice!(/(.)(.)(.)/, 0).should == "hår"
+ "hår".slice!(/(.)(.)(.)/, 1).should == "h"
+ "hår".slice!(/(.)(.)(.)/, 2).should == "å"
+ "hår".slice!(/(.)(.)(.)/, 3).should == "r"
+ "hår".slice!(/(.)(.)(.)/, -1).should == "r"
+ "hår".slice!(/(.)(.)(.)/, -2).should == "å"
+ "hår".slice!(/(.)(.)(.)/, -3).should == "h"
+ end
+
+ it "sets $~ to MatchData when there is a match and nil when there's none" do
+ 'hello'[/.(.)/, 0]
+ $~[0].should == 'he'
+
+ 'hello'[/.(.)/, 1]
+ $~[1].should == 'e'
+
+ 'hello'[/not/, 0]
+ $~.should == nil
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ -> { "this is a string".freeze.slice!(/s.*t/) }.should raise_error(FrozenError)
+ -> { "this is a string".freeze.slice!(/zzz/, 0)}.should raise_error(FrozenError)
+ -> { "this is a string".freeze.slice!(/(.)/, 2)}.should raise_error(FrozenError)
+ end
+end
+
+describe "String#slice! with String" do
+ it "removes and returns the first occurrence of other_str from self" do
+ c = "hello hello"
+ c.slice!('llo').should == "llo"
+ c.should == "he hello"
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+
+ 'hello'.slice!('ll')
+ $~.should == nil
+ end
+
+ it "returns nil if self does not contain other" do
+ a = "hello"
+ a.slice!('zzz').should == nil
+ a.should == "hello"
+ end
+
+ it "doesn't call to_str on its argument" do
+ o = mock('x')
+ o.should_not_receive(:to_str)
+
+ -> { "hello".slice!(o) }.should raise_error(TypeError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a subclass instance when given a subclass instance" do
+ s = StringSpecs::MyString.new("el")
+ r = "hello".slice!(s)
+ r.should == "el"
+ r.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a subclass instance when given a subclass instance" do
+ s = StringSpecs::MyString.new("el")
+ r = "hello".slice!(s)
+ r.should == "el"
+ r.should be_an_instance_of(String)
+ end
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ -> { "hello hello".freeze.slice!('llo') }.should raise_error(FrozenError)
+ -> { "this is a string".freeze.slice!('zzz')}.should raise_error(FrozenError)
+ -> { "this is a string".freeze.slice!('zzz')}.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/split_spec.rb b/spec/ruby/core/string/split_spec.rb
new file mode 100644
index 0000000000..519c5d845d
--- /dev/null
+++ b/spec/ruby/core/string/split_spec.rb
@@ -0,0 +1,614 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#split with String" do
+ it "throws an ArgumentError if the string is not a valid" do
+ s = "\xDF".force_encoding(Encoding::UTF_8)
+
+ -> { s.split }.should raise_error(ArgumentError)
+ -> { s.split(':') }.should raise_error(ArgumentError)
+ end
+
+ it "throws an ArgumentError if the pattern is not a valid string" do
+ str = 'проверка'
+ broken_str = "\xDF".force_encoding(Encoding::UTF_8)
+
+ -> { str.split(broken_str) }.should raise_error(ArgumentError)
+ end
+
+ it "splits on multibyte characters" do
+ "ã‚りãŒã‚ŠãŒã¨ã†".split("ãŒ").should == ["ã‚り", "り", "ã¨ã†"]
+ end
+
+ it "returns an array of substrings based on splitting on the given string" do
+ "mellow yellow".split("ello").should == ["m", "w y", "w"]
+ end
+
+ it "suppresses trailing empty fields when limit isn't given or 0" do
+ "1,2,,3,4,,".split(',').should == ["1", "2", "", "3", "4"]
+ "1,2,,3,4,,".split(',', 0).should == ["1", "2", "", "3", "4"]
+ " a b c\nd ".split(" ").should == ["", "a", "b", "c\nd"]
+ " a ã‚ c\nd ".split(" ").should == ["", "a", "ã‚", "c\nd"]
+ "hai".split("hai").should == []
+ ",".split(",").should == []
+ ",".split(",", 0).should == []
+ "ã‚".split("ã‚").should == []
+ "ã‚".split("ã‚", 0).should == []
+ end
+
+ it "does not suppress trailing empty fields when a positive limit is given" do
+ " 1 2 ".split(" ", 2).should == ["1", "2 "]
+ " 1 2 ".split(" ", 3).should == ["1", "2", ""]
+ " 1 2 ".split(" ", 4).should == ["1", "2", ""]
+ " 1 ã‚ ".split(" ", 2).should == ["1", "ã‚ "]
+ " 1 ã‚ ".split(" ", 3).should == ["1", "ã‚", ""]
+ " 1 ã‚ ".split(" ", 4).should == ["1", "ã‚", ""]
+
+ "1,2,".split(',', 2).should == ["1", "2,"]
+ "1,2,".split(',', 3).should == ["1", "2", ""]
+ "1,2,".split(',', 4).should == ["1", "2", ""]
+ "1,ã‚,".split(',', 2).should == ["1", "ã‚,"]
+ "1,ã‚,".split(',', 3).should == ["1", "ã‚", ""]
+ "1,ã‚,".split(',', 4).should == ["1", "ã‚", ""]
+
+ "1 2 ".split(/ /, 2).should == ["1", "2 "]
+ "1 2 ".split(/ /, 3).should == ["1", "2", ""]
+ "1 2 ".split(/ /, 4).should == ["1", "2", ""]
+ "1 ã‚ ".split(/ /, 2).should == ["1", "ã‚ "]
+ "1 ã‚ ".split(/ /, 3).should == ["1", "ã‚", ""]
+ "1 ã‚ ".split(/ /, 4).should == ["1", "ã‚", ""]
+ end
+
+ it "returns an array with one entry if limit is 1: the original string" do
+ "hai".split("hai", 1).should == ["hai"]
+ "x.y.z".split(".", 1).should == ["x.y.z"]
+ "hello world ".split(" ", 1).should == ["hello world "]
+ "hi!".split("", 1).should == ["hi!"]
+ end
+
+ it "returns at most limit fields when limit > 1" do
+ "hai".split("hai", 2).should == ["", ""]
+
+ "1,2".split(",", 3).should == ["1", "2"]
+
+ "1,2,,3,4,,".split(',', 2).should == ["1", "2,,3,4,,"]
+ "1,2,,3,4,,".split(',', 3).should == ["1", "2", ",3,4,,"]
+ "1,2,,3,4,,".split(',', 4).should == ["1", "2", "", "3,4,,"]
+ "1,2,,3,4,,".split(',', 5).should == ["1", "2", "", "3", "4,,"]
+ "1,2,,3,4,,".split(',', 6).should == ["1", "2", "", "3", "4", ","]
+
+ "x".split('x', 2).should == ["", ""]
+ "xx".split('x', 2).should == ["", "x"]
+ "xx".split('x', 3).should == ["", "", ""]
+ "xxx".split('x', 2).should == ["", "xx"]
+ "xxx".split('x', 3).should == ["", "", "x"]
+ "xxx".split('x', 4).should == ["", "", "", ""]
+ end
+
+ it "doesn't suppress or limit fields when limit is negative" do
+ "1,2,,3,4,,".split(',', -1).should == ["1", "2", "", "3", "4", "", ""]
+ "1,2,,3,4,,".split(',', -5).should == ["1", "2", "", "3", "4", "", ""]
+ " a b c\nd ".split(" ", -1).should == ["", "a", "b", "c\nd", ""]
+ ",".split(",", -1).should == ["", ""]
+ end
+
+ it "raises a RangeError when the limit is larger than int" do
+ -> { "a,b".split(" ", 2147483649) }.should raise_error(RangeError)
+ end
+
+ it "defaults to $; when string isn't given or nil" do
+ suppress_warning do
+ old_fs = $;
+ begin
+ [",", ":", "", "XY", nil].each do |fs|
+ $; = fs
+
+ ["x,y,z,,,", "1:2:", "aXYbXYcXY", ""].each do |str|
+ expected = str.split(fs || " ")
+
+ str.split(nil).should == expected
+ str.split.should == expected
+
+ str.split(nil, -1).should == str.split(fs || " ", -1)
+ str.split(nil, 0).should == str.split(fs || " ", 0)
+ str.split(nil, 2).should == str.split(fs || " ", 2)
+ end
+ end
+ ensure
+ $; = old_fs
+ end
+ end
+
+ context "when $; is not nil" do
+ before do
+ suppress_warning do
+ @old_value, $; = $;, 'foobar'
+ end
+ end
+
+ after do
+ $; = @old_value
+ end
+
+ it "warns" do
+ -> { "".split }.should complain(/warning: \$; is set to non-nil value/)
+ end
+ end
+ end
+
+ it "ignores leading and continuous whitespace when string is a single space" do
+ " now's the time ".split(' ').should == ["now's", "the", "time"]
+ " now's the time ".split(' ', -1).should == ["now's", "the", "time", ""]
+ " now's the time ".split(' ', 3).should == ["now's", "the", "time "]
+
+ "\t\n a\t\tb \n\r\r\nc\v\vd\v ".split(' ').should == ["a", "b", "c", "d"]
+ "a\x00a b".split(' ').should == ["a\x00a", "b"]
+ end
+
+ describe "when limit is zero" do
+ it "ignores leading and continuous whitespace when string is a single space" do
+ " now's the time ".split(' ', 0).should == ["now's", "the", "time"]
+ end
+ end
+
+ it "splits between characters when its argument is an empty string" do
+ "hi!".split("").should == ["h", "i", "!"]
+ "hi!".split("", -1).should == ["h", "i", "!", ""]
+ "hi!".split("", 0).should == ["h", "i", "!"]
+ "hi!".split("", 1).should == ["hi!"]
+ "hi!".split("", 2).should == ["h", "i!"]
+ "hi!".split("", 3).should == ["h", "i", "!"]
+ "hi!".split("", 4).should == ["h", "i", "!", ""]
+ "hi!".split("", 5).should == ["h", "i", "!", ""]
+ end
+
+ it "tries converting its pattern argument to a string via to_str" do
+ obj = mock('::')
+ obj.should_receive(:to_str).and_return("::")
+
+ "hello::world".split(obj).should == ["hello", "world"]
+ end
+
+ it "tries converting limit to an integer via to_int" do
+ obj = mock('2')
+ obj.should_receive(:to_int).and_return(2)
+
+ "1.2.3.4".split(".", obj).should == ["1", "2.3.4"]
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+ "x.y.z".split(".")
+ $~.should == nil
+ end
+
+ it "returns the original string if no matches are found" do
+ "foo".split("bar").should == ["foo"]
+ "foo".split("bar", -1).should == ["foo"]
+ "foo".split("bar", 0).should == ["foo"]
+ "foo".split("bar", 1).should == ["foo"]
+ "foo".split("bar", 2).should == ["foo"]
+ "foo".split("bar", 3).should == ["foo"]
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances based on self" do
+ ["", "x.y.z.", " x y "].each do |str|
+ ["", ".", " "].each do |pat|
+ [-1, 0, 1, 2].each do |limit|
+ StringSpecs::MyString.new(str).split(pat, limit).each do |x|
+ x.should be_an_instance_of(StringSpecs::MyString)
+ end
+
+ str.split(StringSpecs::MyString.new(pat), limit).each do |x|
+ x.should be_an_instance_of(String)
+ end
+ end
+ end
+ end
+ end
+
+ it "does not call constructor on created subclass instances" do
+ # can't call should_not_receive on an object that doesn't yet exist
+ # so failure here is signalled by exception, not expectation failure
+
+ s = StringSpecs::StringWithRaisingConstructor.new('silly:string')
+ s.split(':').first.should == 'silly'
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances based on self" do
+ ["", "x.y.z.", " x y "].each do |str|
+ ["", ".", " "].each do |pat|
+ [-1, 0, 1, 2].each do |limit|
+ StringSpecs::MyString.new(str).split(pat, limit).each do |x|
+ x.should be_an_instance_of(String)
+ end
+
+ str.split(StringSpecs::MyString.new(pat), limit).each do |x|
+ x.should be_an_instance_of(String)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ it "returns an empty array when whitespace is split on whitespace" do
+ " ".split(" ").should == []
+ " \n ".split(" ").should == []
+ " ".split(" ").should == []
+ " \t ".split(" ").should == []
+ end
+
+ it "doesn't split on non-ascii whitespace" do
+ "a\u{2008}b".split(" ").should == ["a\u{2008}b"]
+ end
+
+ it "returns Strings in the same encoding as self" do
+ strings = "hello world".encode("US-ASCII").split(" ")
+
+ strings[0].encoding.should == Encoding::US_ASCII
+ strings[1].encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "String#split with Regexp" do
+ it "throws an ArgumentError if the string is not a valid" do
+ s = "\xDF".force_encoding(Encoding::UTF_8)
+
+ -> { s.split(/./) }.should raise_error(ArgumentError)
+ end
+
+ it "divides self on regexp matches" do
+ " now's the time".split(/ /).should == ["", "now's", "", "the", "time"]
+ " x\ny ".split(/ /).should == ["", "x\ny"]
+ "1, 2.34,56, 7".split(/,\s*/).should == ["1", "2.34", "56", "7"]
+ "1x2X3".split(/x/i).should == ["1", "2", "3"]
+ end
+
+ it "treats negative limits as no limit" do
+ "".split(%r!/+!, -1).should == []
+ end
+
+ it "suppresses trailing empty fields when limit isn't given or 0" do
+ "1,2,,3,4,,".split(/,/).should == ["1", "2", "", "3", "4"]
+ "1,2,,3,4,,".split(/,/, 0).should == ["1", "2", "", "3", "4"]
+ " a b c\nd ".split(/\s+/).should == ["", "a", "b", "c", "d"]
+ "hai".split(/hai/).should == []
+ ",".split(/,/).should == []
+ ",".split(/,/, 0).should == []
+ end
+
+ it "returns an array with one entry if limit is 1: the original string" do
+ "hai".split(/hai/, 1).should == ["hai"]
+ "xAyBzC".split(/[A-Z]/, 1).should == ["xAyBzC"]
+ "hello world ".split(/\s+/, 1).should == ["hello world "]
+ "hi!".split(//, 1).should == ["hi!"]
+ end
+
+ it "returns at most limit fields when limit > 1" do
+ "hai".split(/hai/, 2).should == ["", ""]
+
+ "1,2".split(/,/, 3).should == ["1", "2"]
+
+ "1,2,,3,4,,".split(/,/, 2).should == ["1", "2,,3,4,,"]
+ "1,2,,3,4,,".split(/,/, 3).should == ["1", "2", ",3,4,,"]
+ "1,2,,3,4,,".split(/,/, 4).should == ["1", "2", "", "3,4,,"]
+ "1,2,,3,4,,".split(/,/, 5).should == ["1", "2", "", "3", "4,,"]
+ "1,2,,3,4,,".split(/,/, 6).should == ["1", "2", "", "3", "4", ","]
+
+ "x".split(/x/, 2).should == ["", ""]
+ "xx".split(/x/, 2).should == ["", "x"]
+ "xx".split(/x/, 3).should == ["", "", ""]
+ "xxx".split(/x/, 2).should == ["", "xx"]
+ "xxx".split(/x/, 3).should == ["", "", "x"]
+ "xxx".split(/x/, 4).should == ["", "", "", ""]
+ end
+
+ it "doesn't suppress or limit fields when limit is negative" do
+ "1,2,,3,4,,".split(/,/, -1).should == ["1", "2", "", "3", "4", "", ""]
+ "1,2,,3,4,,".split(/,/, -5).should == ["1", "2", "", "3", "4", "", ""]
+ " a b c\nd ".split(/\s+/, -1).should == ["", "a", "b", "c", "d", ""]
+ ",".split(/,/, -1).should == ["", ""]
+ end
+
+ it "defaults to $; when regexp isn't given or nil" do
+ suppress_warning do
+ old_fs = $;
+ begin
+ [/,/, /:/, //, /XY/, /./].each do |fs|
+ $; = fs
+
+ ["x,y,z,,,", "1:2:", "aXYbXYcXY", ""].each do |str|
+ expected = str.split(fs)
+
+ str.split(nil).should == expected
+ str.split.should == expected
+
+ str.split(nil, -1).should == str.split(fs, -1)
+ str.split(nil, 0).should == str.split(fs, 0)
+ str.split(nil, 2).should == str.split(fs, 2)
+ end
+ end
+ ensure
+ $; = old_fs
+ end
+ end
+ end
+
+ it "splits between characters when regexp matches a zero-length string" do
+ "hello".split(//).should == ["h", "e", "l", "l", "o"]
+ "hello".split(//, -1).should == ["h", "e", "l", "l", "o", ""]
+ "hello".split(//, 0).should == ["h", "e", "l", "l", "o"]
+ "hello".split(//, 1).should == ["hello"]
+ "hello".split(//, 2).should == ["h", "ello"]
+ "hello".split(//, 5).should == ["h", "e", "l", "l", "o"]
+ "hello".split(//, 6).should == ["h", "e", "l", "l", "o", ""]
+ "hello".split(//, 7).should == ["h", "e", "l", "l", "o", ""]
+
+ "hi mom".split(/\s*/).should == ["h", "i", "m", "o", "m"]
+
+ "AABCCBAA".split(/(?=B)/).should == ["AA", "BCC", "BAA"]
+ "AABCCBAA".split(/(?=B)/, -1).should == ["AA", "BCC", "BAA"]
+ "AABCCBAA".split(/(?=B)/, 2).should == ["AA", "BCCBAA"]
+ end
+
+ it "respects unicode when splitting between characters" do
+ str = "ã“ã«ã¡ã‚"
+ reg = %r!!
+ ary = str.split(reg)
+ ary.size.should == 4
+ ary.should == ["ã“", "ã«", "ã¡", "ã‚"]
+ end
+
+ it "respects the encoding of the regexp when splitting between characters" do
+ str = "\303\202"
+ ary = str.split(//u)
+ ary.size.should == 1
+ ary.should == ["\303\202"]
+ end
+
+ it "includes all captures in the result array" do
+ "hello".split(/(el)/).should == ["h", "el", "lo"]
+ "hi!".split(/()/).should == ["h", "", "i", "", "!"]
+ "hi!".split(/()/, -1).should == ["h", "", "i", "", "!", "", ""]
+ "hello".split(/((el))()/).should == ["h", "el", "el", "", "lo"]
+ "AabB".split(/([a-z])+/).should == ["A", "b", "B"]
+ end
+
+ it "applies the limit to the number of split substrings, without counting captures" do
+ "aBaBa".split(/(B)()()/, 2).should == ["a", "B", "", "", "aBa"]
+ end
+
+ it "does not include non-matching captures in the result array" do
+ "hello".split(/(el)|(xx)/).should == ["h", "el", "lo"]
+ end
+
+ it "tries converting limit to an integer via to_int" do
+ obj = mock('2')
+ obj.should_receive(:to_int).and_return(2)
+
+ "1.2.3.4".split(".", obj).should == ["1", "2.3.4"]
+ end
+
+ it "returns a type error if limit can't be converted to an integer" do
+ -> {"1.2.3.4".split(".", "three")}.should raise_error(TypeError)
+ -> {"1.2.3.4".split(".", nil) }.should raise_error(TypeError)
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+ "x:y:z".split(/:/)
+ $~.should == nil
+ end
+
+ it "returns the original string if no matches are found" do
+ "foo".split(/bar/).should == ["foo"]
+ "foo".split(/bar/, -1).should == ["foo"]
+ "foo".split(/bar/, 0).should == ["foo"]
+ "foo".split(/bar/, 1).should == ["foo"]
+ "foo".split(/bar/, 2).should == ["foo"]
+ "foo".split(/bar/, 3).should == ["foo"]
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances based on self" do
+ ["", "x:y:z:", " x y "].each do |str|
+ [//, /:/, /\s+/].each do |pat|
+ [-1, 0, 1, 2].each do |limit|
+ StringSpecs::MyString.new(str).split(pat, limit).each do |x|
+ x.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+ end
+ end
+ end
+
+ it "does not call constructor on created subclass instances" do
+ # can't call should_not_receive on an object that doesn't yet exist
+ # so failure here is signalled by exception, not expectation failure
+
+ s = StringSpecs::StringWithRaisingConstructor.new('silly:string')
+ s.split(/:/).first.should == 'silly'
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances based on self" do
+ ["", "x:y:z:", " x y "].each do |str|
+ [//, /:/, /\s+/].each do |pat|
+ [-1, 0, 1, 2].each do |limit|
+ StringSpecs::MyString.new(str).split(pat, limit).each do |x|
+ x.should be_an_instance_of(String)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ it "returns Strings in the same encoding as self" do
+ ary = "а б в".split
+ encodings = ary.map { |s| s.encoding }
+ encodings.should == [Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8]
+ end
+
+ it "splits a string on each character for a multibyte encoding and empty split" do
+ "That's why efficiency could not be helped".split("").size.should == 39
+ end
+
+ it "returns an ArgumentError if an invalid UTF-8 string is supplied" do
+ broken_str = 'проверка' # in russian, means "test"
+ broken_str.force_encoding('binary')
+ broken_str.chop!
+ broken_str.force_encoding('utf-8')
+ ->{ broken_str.split(/\r\n|\r|\n/) }.should raise_error(ArgumentError)
+ end
+
+ # See https://bugs.ruby-lang.org/issues/12689 and https://github.com/jruby/jruby/issues/4868
+ it "allows concurrent Regexp calls in a shared context" do
+ str = 'a,b,c,d,e'
+
+ p = proc { str.split(/,/) }
+ results = 10.times.map { Thread.new { x = nil; 100.times { x = p.call }; x } }.map(&:value)
+
+ results.should == [%w[a b c d e]] * 10
+ end
+
+ context "when a block is given" do
+ it "yields each split substring with default pattern" do
+ a = []
+ returned_object = "chunky bacon".split { |str| a << str.capitalize }
+
+ returned_object.should == "chunky bacon"
+ a.should == ["Chunky", "Bacon"]
+ end
+
+ it "yields each split substring with default pattern for a lazy substring" do
+ a = []
+ returned_object = "chunky bacon"[1...-1].split { |str| a << str.capitalize }
+
+ returned_object.should == "hunky baco"
+ a.should == ["Hunky", "Baco"]
+ end
+
+ it "yields each split substring with default pattern for a non-ASCII string" do
+ a = []
+ returned_object = "l'été arrive bientôt".split { |str| a << str }
+
+ returned_object.should == "l'été arrive bientôt"
+ a.should == ["l'été", "arrive", "bientôt"]
+ end
+
+ it "yields each split substring with default pattern for a non-ASCII lazy substring" do
+ a = []
+ returned_object = "l'été arrive bientôt"[1...-1].split { |str| a << str }
+
+ returned_object.should == "'été arrive bientô"
+ a.should == ["'été", "arrive", "bientô"]
+ end
+
+ it "yields the string when limit is 1" do
+ a = []
+ returned_object = "chunky bacon".split("", 1) { |str| a << str.capitalize }
+
+ returned_object.should == "chunky bacon"
+ a.should == ["Chunky bacon"]
+ end
+
+ it "yields each split letter" do
+ a = []
+ returned_object = "chunky".split("", 0) { |str| a << str.capitalize }
+
+ returned_object.should == "chunky"
+ a.should == %w(C H U N K Y)
+ end
+
+ it "yields each split substring with a pattern" do
+ a = []
+ returned_object = "chunky-bacon".split("-", 0) { |str| a << str.capitalize }
+
+ returned_object.should == "chunky-bacon"
+ a.should == ["Chunky", "Bacon"]
+ end
+
+ it "yields each split substring with empty regexp pattern" do
+ a = []
+ returned_object = "chunky".split(//) { |str| a << str.capitalize }
+
+ returned_object.should == "chunky"
+ a.should == %w(C H U N K Y)
+ end
+
+ it "yields each split substring with empty regexp pattern and limit" do
+ a = []
+ returned_object = "chunky".split(//, 3) { |str| a << str.capitalize }
+
+ returned_object.should == "chunky"
+ a.should == %w(C H Unky)
+ end
+
+ it "yields each split substring with a regexp pattern" do
+ a = []
+ returned_object = "chunky:bacon".split(/:/) { |str| a << str.capitalize }
+
+ returned_object.should == "chunky:bacon"
+ a.should == ["Chunky", "Bacon"]
+ end
+
+ it "returns a string as is (and doesn't call block) if it is empty" do
+ a = []
+ returned_object = "".split { |str| a << str.capitalize }
+
+ returned_object.should == ""
+ a.should == []
+ end
+ end
+
+ describe "for a String subclass" do
+ ruby_version_is ''...'3.0' do
+ it "yields instances of the same subclass" do
+ a = []
+ StringSpecs::MyString.new("a|b").split("|") { |str| a << str }
+ first, last = a
+
+ first.should be_an_instance_of(StringSpecs::MyString)
+ first.should == "a"
+
+ last.should be_an_instance_of(StringSpecs::MyString)
+ last.should == "b"
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "yields instances of String" do
+ a = []
+ StringSpecs::MyString.new("a|b").split("|") { |str| a << str }
+ first, last = a
+
+ first.should be_an_instance_of(String)
+ first.should == "a"
+
+ last.should be_an_instance_of(String)
+ last.should == "b"
+ end
+ end
+ end
+
+ it "raises a TypeError when not called with nil, String, or Regexp" do
+ -> { "hello".split(42) }.should raise_error(TypeError)
+ -> { "hello".split(:ll) }.should raise_error(TypeError)
+ -> { "hello".split(false) }.should raise_error(TypeError)
+ -> { "hello".split(Object.new) }.should raise_error(TypeError)
+ end
+
+ it "returns Strings in the same encoding as self" do
+ strings = "hello world".encode("US-ASCII").split(/ /)
+
+ strings[0].encoding.should == Encoding::US_ASCII
+ strings[1].encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/string/squeeze_spec.rb b/spec/ruby/core/string/squeeze_spec.rb
new file mode 100644
index 0000000000..2f3fa65745
--- /dev/null
+++ b/spec/ruby/core/string/squeeze_spec.rb
@@ -0,0 +1,118 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# TODO: rewrite all these specs
+
+describe "String#squeeze" do
+ it "returns new string where runs of the same character are replaced by a single character when no args are given" do
+ "yellow moon".squeeze.should == "yelow mon"
+ end
+
+ it "only squeezes chars that are in the intersection of all sets given" do
+ "woot squeeze cheese".squeeze("eost", "queo").should == "wot squeze chese"
+ " now is the".squeeze(" ").should == " now is the"
+ end
+
+ it "negates sets starting with ^" do
+ s = "<<subbookkeeper!!!>>"
+ s.squeeze("beko", "^e").should == s.squeeze("bko")
+ s.squeeze("^<bek!>").should == s.squeeze("o")
+ s.squeeze("^o").should == s.squeeze("<bek!>")
+ s.squeeze("^").should == s
+ "^__^".squeeze("^^").should == "^_^"
+ "((^^__^^))".squeeze("_^").should == "((^_^))"
+ end
+
+ it "squeezes all chars in a sequence" do
+ s = "--subbookkeeper--"
+ s.squeeze("\x00-\xFF").should == s.squeeze
+ s.squeeze("bk-o").should == s.squeeze("bklmno")
+ s.squeeze("b-e").should == s.squeeze("bcde")
+ s.squeeze("e-").should == "-subbookkeper-"
+ s.squeeze("-e").should == "-subbookkeper-"
+ s.squeeze("---").should == "-subbookkeeper-"
+ "ook--001122".squeeze("--2").should == "ook-012"
+ "ook--(())".squeeze("(--").should == "ook-()"
+ s.squeeze("^b-e").should == "-subbokeeper-"
+ "^^__^^".squeeze("^^-^").should == "^^_^^"
+ "^^--^^".squeeze("^---").should == "^--^"
+
+ s.squeeze("b-dk-o-").should == "-subokeeper-"
+ s.squeeze("-b-dk-o").should == "-subokeeper-"
+ s.squeeze("b-d-k-o").should == "-subokeeper-"
+
+ s.squeeze("bc-e").should == "--subookkeper--"
+ s.squeeze("^bc-e").should == "-subbokeeper-"
+
+ "AABBCCaabbcc[[]]".squeeze("A-a").should == "ABCabbcc[]"
+ end
+
+ it "raises an ArgumentError when the parameter is out of sequence" do
+ s = "--subbookkeeper--"
+ -> { s.squeeze("e-b") }.should raise_error(ArgumentError)
+ -> { s.squeeze("^e-b") }.should raise_error(ArgumentError)
+ end
+
+ it "tries to convert each set arg to a string using to_str" do
+ other_string = mock('lo')
+ other_string.should_receive(:to_str).and_return("lo")
+
+ other_string2 = mock('o')
+ other_string2.should_receive(:to_str).and_return("o")
+
+ "hello room".squeeze(other_string, other_string2).should == "hello rom"
+ end
+
+ it "returns a String in the same encoding as self" do
+ "yellow moon".encode("US-ASCII").squeeze.encoding.should == Encoding::US_ASCII
+ "yellow moon".encode("US-ASCII").squeeze("a").encoding.should == Encoding::US_ASCII
+ end
+
+ it "raises a TypeError when one set arg can't be converted to a string" do
+ -> { "hello world".squeeze([]) }.should raise_error(TypeError)
+ -> { "hello world".squeeze(Object.new)}.should raise_error(TypeError)
+ -> { "hello world".squeeze(mock('x')) }.should raise_error(TypeError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("oh no!!!").squeeze("!").should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("oh no!!!").squeeze("!").should be_an_instance_of(String)
+ end
+ end
+end
+
+describe "String#squeeze!" do
+ it "modifies self in place and returns self" do
+ a = "yellow moon"
+ a.squeeze!.should equal(a)
+ a.should == "yelow mon"
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "squeeze"
+ a.squeeze!("u", "sq").should == nil
+ a.squeeze!("q").should == nil
+ a.should == "squeeze"
+ end
+
+ it "raises an ArgumentError when the parameter is out of sequence" do
+ s = "--subbookkeeper--"
+ -> { s.squeeze!("e-b") }.should raise_error(ArgumentError)
+ -> { s.squeeze!("^e-b") }.should raise_error(ArgumentError)
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ a = "yellow moon"
+ a.freeze
+
+ -> { a.squeeze!("") }.should raise_error(FrozenError)
+ -> { a.squeeze! }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/start_with_spec.rb b/spec/ruby/core/string/start_with_spec.rb
new file mode 100644
index 0000000000..3833289f96
--- /dev/null
+++ b/spec/ruby/core/string/start_with_spec.rb
@@ -0,0 +1,18 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/string/start_with'
+
+describe "String#start_with?" do
+ it_behaves_like :start_with, :to_s
+
+ # Here and not in the shared examples because this is invalid as a Symbol
+ it "does not check that we are not starting to match at the head of a character" do
+ "\xA9".should.start_with?("\xA9") # A9 is not a character head for UTF-8
+ end
+
+ it "does not check we are matching only part of a character" do
+ "\xe3\x81\x82".size.should == 1
+ "\xe3\x81\x82".should.start_with?("\xe3")
+ end
+end
diff --git a/spec/ruby/core/string/string_spec.rb b/spec/ruby/core/string/string_spec.rb
new file mode 100644
index 0000000000..cdefbbecbd
--- /dev/null
+++ b/spec/ruby/core/string/string_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "String" do
+ it "includes Comparable" do
+ String.include?(Comparable).should == true
+ end
+end
diff --git a/spec/ruby/core/string/strip_spec.rb b/spec/ruby/core/string/strip_spec.rb
new file mode 100644
index 0000000000..662f13b032
--- /dev/null
+++ b/spec/ruby/core/string/strip_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/strip'
+
+describe "String#strip" do
+ it_behaves_like :string_strip, :strip
+
+ it "returns a new string with leading and trailing whitespace removed" do
+ " hello ".strip.should == "hello"
+ " hello world ".strip.should == "hello world"
+ "\tgoodbye\r\v\n".strip.should == "goodbye"
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a copy of self without leading and trailing NULL bytes and whitespace" do
+ " \x00 goodbye \x00 ".strip.should == "goodbye"
+ end
+ end
+end
+
+describe "String#strip!" do
+ it "modifies self in place and returns self" do
+ a = " hello "
+ a.strip!.should equal(a)
+ a.should == "hello"
+
+ a = "\tgoodbye\r\v\n"
+ a.strip!
+ a.should == "goodbye"
+ end
+
+ it "returns nil if no modifications where made" do
+ a = "hello"
+ a.strip!.should == nil
+ a.should == "hello"
+ end
+
+ it "makes a string empty if it is only whitespace" do
+ "".strip!.should == nil
+ " ".strip.should == ""
+ " ".strip.should == ""
+ end
+
+ ruby_version_is '3.0' do
+ it "removes leading and trailing NULL bytes and whitespace" do
+ a = "\000 goodbye \000"
+ a.strip!
+ a.should == "goodbye"
+ end
+ end
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ -> { " hello ".freeze.strip! }.should raise_error(FrozenError)
+ end
+
+ # see #1552
+ it "raises a FrozenError on a frozen instance that would not be modified" do
+ -> {"hello".freeze.strip! }.should raise_error(FrozenError)
+ -> {"".freeze.strip! }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/sub_spec.rb b/spec/ruby/core/string/sub_spec.rb
new file mode 100644
index 0000000000..99dd7b45a8
--- /dev/null
+++ b/spec/ruby/core/string/sub_spec.rb
@@ -0,0 +1,522 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#sub with pattern, replacement" do
+ it "returns a copy of self when no modification is made" do
+ a = "hello"
+ b = a.sub(/w.*$/, "*")
+
+ b.should_not equal(a)
+ b.should == "hello"
+ end
+
+ it "returns a copy of self with all occurrences of pattern replaced with replacement" do
+ "hello".sub(/[aeiou]/, '*').should == "h*llo"
+ "hello".sub(//, ".").should == ".hello"
+ end
+
+ it "ignores a block if supplied" do
+ "food".sub(/f/, "g") { "w" }.should == "good"
+ end
+
+ it "supports \\G which matches at the beginning of the string" do
+ "hello world!".sub(/\Ghello/, "hi").should == "hi world!"
+ end
+
+ it "supports /i for ignoring case" do
+ "Hello".sub(/h/i, "j").should == "jello"
+ "hello".sub(/H/i, "j").should == "jello"
+ end
+
+ it "doesn't interpret regexp metacharacters if pattern is a string" do
+ "12345".sub('\d', 'a').should == "12345"
+ '\d'.sub('\d', 'a').should == "a"
+ end
+
+ it "replaces \\1 sequences with the regexp's corresponding capture" do
+ str = "hello"
+
+ str.sub(/([aeiou])/, '<\1>').should == "h<e>llo"
+ str.sub(/(.)/, '\1\1').should == "hhello"
+
+ str.sub(/.(.?)/, '<\0>(\1)').should == "<he>(e)llo"
+
+ str.sub(/.(.)+/, '\1').should == "o"
+
+ str = "ABCDEFGHIJKL"
+ re = /#{"(.)" * 12}/
+ str.sub(re, '\1').should == "A"
+ str.sub(re, '\9').should == "I"
+ # Only the first 9 captures can be accessed in MRI
+ str.sub(re, '\10').should == "A0"
+ end
+
+ it "treats \\1 sequences without corresponding captures as empty strings" do
+ str = "hello!"
+
+ str.sub("", '<\1>').should == "<>hello!"
+ str.sub("h", '<\1>').should == "<>ello!"
+
+ str.sub(//, '<\1>').should == "<>hello!"
+ str.sub(/./, '\1\2\3').should == "ello!"
+ str.sub(/.(.{20})?/, '\1').should == "ello!"
+ end
+
+ it "replaces \\& and \\0 with the complete match" do
+ str = "hello!"
+
+ str.sub("", '<\0>').should == "<>hello!"
+ str.sub("", '<\&>').should == "<>hello!"
+ str.sub("he", '<\0>').should == "<he>llo!"
+ str.sub("he", '<\&>').should == "<he>llo!"
+ str.sub("l", '<\0>').should == "he<l>lo!"
+ str.sub("l", '<\&>').should == "he<l>lo!"
+
+ str.sub(//, '<\0>').should == "<>hello!"
+ str.sub(//, '<\&>').should == "<>hello!"
+ str.sub(/../, '<\0>').should == "<he>llo!"
+ str.sub(/../, '<\&>').should == "<he>llo!"
+ str.sub(/(.)./, '<\0>').should == "<he>llo!"
+ end
+
+ it "replaces \\` with everything before the current match" do
+ str = "hello!"
+
+ str.sub("", '<\`>').should == "<>hello!"
+ str.sub("h", '<\`>').should == "<>ello!"
+ str.sub("l", '<\`>').should == "he<he>lo!"
+ str.sub("!", '<\`>').should == "hello<hello>"
+
+ str.sub(//, '<\`>').should == "<>hello!"
+ str.sub(/..o/, '<\`>').should == "he<he>!"
+ end
+
+ it "replaces \\' with everything after the current match" do
+ str = "hello!"
+
+ str.sub("", '<\\\'>').should == "<hello!>hello!"
+ str.sub("h", '<\\\'>').should == "<ello!>ello!"
+ str.sub("ll", '<\\\'>').should == "he<o!>o!"
+ str.sub("!", '<\\\'>').should == "hello<>"
+
+ str.sub(//, '<\\\'>').should == "<hello!>hello!"
+ str.sub(/../, '<\\\'>').should == "<llo!>llo!"
+ end
+
+ it "replaces \\\\\\+ with \\\\+" do
+ "x".sub(/x/, '\\\+').should == "\\+"
+ end
+
+ it "replaces \\+ with the last paren that actually matched" do
+ str = "hello!"
+
+ str.sub(/(.)(.)/, '\+').should == "ello!"
+ str.sub(/(.)(.)+/, '\+').should == "!"
+ str.sub(/(.)()/, '\+').should == "ello!"
+ str.sub(/(.)(.{20})?/, '<\+>').should == "<h>ello!"
+
+ str = "ABCDEFGHIJKL"
+ re = /#{"(.)" * 12}/
+ str.sub(re, '\+').should == "L"
+ end
+
+ it "treats \\+ as an empty string if there was no captures" do
+ "hello!".sub(/./, '\+').should == "ello!"
+ end
+
+ it "maps \\\\ in replacement to \\" do
+ "hello".sub(/./, '\\\\').should == '\\ello'
+ end
+
+ it "leaves unknown \\x escapes in replacement untouched" do
+ "hello".sub(/./, '\\x').should == '\\xello'
+ "hello".sub(/./, '\\y').should == '\\yello'
+ end
+
+ it "leaves \\ at the end of replacement untouched" do
+ "hello".sub(/./, 'hah\\').should == 'hah\\ello'
+ end
+
+ it "tries to convert pattern to a string using to_str" do
+ pattern = mock('.')
+ pattern.should_receive(:to_str).and_return(".")
+
+ "hello.".sub(pattern, "!").should == "hello!"
+ end
+
+ not_supported_on :opal do
+ it "raises a TypeError when pattern is a Symbol" do
+ -> { "hello".sub(:woot, "x") }.should raise_error(TypeError)
+ end
+ end
+
+ it "raises a TypeError when pattern is an Array" do
+ -> { "hello".sub([], "x") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when pattern can't be converted to a string" do
+ -> { "hello".sub(Object.new, nil) }.should raise_error(TypeError)
+ end
+
+ it "tries to convert replacement to a string using to_str" do
+ replacement = mock('hello_replacement')
+ replacement.should_receive(:to_str).and_return("hello_replacement")
+
+ "hello".sub(/hello/, replacement).should == "hello_replacement"
+ end
+
+ it "raises a TypeError when replacement can't be converted to a string" do
+ -> { "hello".sub(/[aeiou]/, []) }.should raise_error(TypeError)
+ -> { "hello".sub(/[aeiou]/, 99) }.should raise_error(TypeError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("").sub(//, "").should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("").sub(/foo/, "").should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("foo").sub(/foo/, "").should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("foo").sub("foo", "").should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("").sub(//, "").should be_an_instance_of(String)
+ StringSpecs::MyString.new("").sub(/foo/, "").should be_an_instance_of(String)
+ StringSpecs::MyString.new("foo").sub(/foo/, "").should be_an_instance_of(String)
+ StringSpecs::MyString.new("foo").sub("foo", "").should be_an_instance_of(String)
+ end
+ end
+
+ it "sets $~ to MatchData of match and nil when there's none" do
+ 'hello.'.sub('hello', 'x')
+ $~[0].should == 'hello'
+
+ 'hello.'.sub('not', 'x')
+ $~.should == nil
+
+ 'hello.'.sub(/.(.)/, 'x')
+ $~[0].should == 'he'
+
+ 'hello.'.sub(/not/, 'x')
+ $~.should == nil
+ end
+
+ it "replaces \\\\\\1 with \\1" do
+ "ababa".sub(/(b)/, '\\\1').should == "a\\1aba"
+ end
+
+ it "replaces \\\\\\\\1 with \\1" do
+ "ababa".sub(/(b)/, '\\\\1').should == "a\\1aba"
+ end
+
+ it "replaces \\\\\\\\\\1 with \\" do
+ "ababa".sub(/(b)/, '\\\\\1').should == "a\\baba"
+ end
+
+ it "handles a pattern in a superset encoding" do
+ result = 'abc'.force_encoding(Encoding::US_ASCII).sub('é', 'è')
+ result.should == 'abc'
+ result.encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ result = 'été'.sub('t'.force_encoding(Encoding::US_ASCII), 'u')
+ result.should == 'éué'
+ result.encoding.should == Encoding::UTF_8
+ end
+end
+
+describe "String#sub with pattern and block" do
+ it "returns a copy of self with the first occurrences of pattern replaced with the block's return value" do
+ "hi".sub(/./) { |s| s + ' ' }.should == "h i"
+ "hi!".sub(/(.)(.)/) { |*a| a.inspect }.should == '["hi"]!'
+ end
+
+ it "sets $~ for access from the block" do
+ str = "hello"
+ str.sub(/([aeiou])/) { "<#{$~[1]}>" }.should == "h<e>llo"
+ str.sub(/([aeiou])/) { "<#{$1}>" }.should == "h<e>llo"
+ str.sub("l") { "<#{$~[0]}>" }.should == "he<l>lo"
+
+ offsets = []
+
+ str.sub(/([aeiou])/) do
+ md = $~
+ md.string.should == str
+ offsets << md.offset(0)
+ str
+ end.should == "hhellollo"
+
+ offsets.should == [[1, 2]]
+ end
+
+ it "sets $~ to MatchData of last match and nil when there's none for access from outside" do
+ 'hello.'.sub('l') { 'x' }
+ $~.begin(0).should == 2
+ $~[0].should == 'l'
+
+ 'hello.'.sub('not') { 'x' }
+ $~.should == nil
+
+ 'hello.'.sub(/.(.)/) { 'x' }
+ $~[0].should == 'he'
+
+ 'hello.'.sub(/not/) { 'x' }
+ $~.should == nil
+ end
+
+ it "doesn't raise a RuntimeError if the string is modified while substituting" do
+ str = "hello"
+ str.sub(//) { str[0] = 'x' }.should == "xhello"
+ str.should == "xello"
+ end
+
+ it "doesn't interpolate special sequences like \\1 for the block's return value" do
+ repl = '\& \0 \1 \` \\\' \+ \\\\ foo'
+ "hello".sub(/(.+)/) { repl }.should == repl
+ end
+
+ it "converts the block's return value to a string using to_s" do
+ obj = mock('hello_replacement')
+ obj.should_receive(:to_s).and_return("hello_replacement")
+ "hello".sub(/hello/) { obj }.should == "hello_replacement"
+
+ obj = mock('ok')
+ obj.should_receive(:to_s).and_return("ok")
+ "hello".sub(/.+/) { obj }.should == "ok"
+ end
+end
+
+describe "String#sub! with pattern, replacement" do
+ it "modifies self in place and returns self" do
+ a = "hello"
+ a.sub!(/[aeiou]/, '*').should equal(a)
+ a.should == "h*llo"
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "hello"
+ a.sub!(/z/, '*').should == nil
+ a.sub!(/z/, 'z').should == nil
+ a.should == "hello"
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ s = "hello"
+ s.freeze
+
+ -> { s.sub!(/ROAR/, "x") }.should raise_error(FrozenError)
+ -> { s.sub!(/e/, "e") }.should raise_error(FrozenError)
+ -> { s.sub!(/[aeiou]/, '*') }.should raise_error(FrozenError)
+ end
+
+ it "handles a pattern in a superset encoding" do
+ string = 'abc'.force_encoding(Encoding::US_ASCII)
+
+ result = string.sub!('é', 'è')
+
+ result.should == nil
+ string.should == 'abc'
+ string.encoding.should == Encoding::US_ASCII
+ end
+
+ it "handles a pattern in a subset encoding" do
+ string = 'été'
+ pattern = 't'.force_encoding(Encoding::US_ASCII)
+
+ result = string.sub!(pattern, 'u')
+
+ result.should == string
+ string.should == 'éué'
+ string.encoding.should == Encoding::UTF_8
+ end
+end
+
+describe "String#sub! with pattern and block" do
+ it "modifies self in place and returns self" do
+ a = "hello"
+ a.sub!(/[aeiou]/) { '*' }.should equal(a)
+ a.should == "h*llo"
+ end
+
+ it "sets $~ for access from the block" do
+ str = "hello"
+ str.dup.sub!(/([aeiou])/) { "<#{$~[1]}>" }.should == "h<e>llo"
+ str.dup.sub!(/([aeiou])/) { "<#{$1}>" }.should == "h<e>llo"
+ str.dup.sub!("l") { "<#{$~[0]}>" }.should == "he<l>lo"
+
+ offsets = []
+
+ str.dup.sub!(/([aeiou])/) do
+ md = $~
+ md.string.should == str
+ offsets << md.offset(0)
+ str
+ end.should == "hhellollo"
+
+ offsets.should == [[1, 2]]
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "hello"
+ a.sub!(/z/) { '*' }.should == nil
+ a.sub!(/z/) { 'z' }.should == nil
+ a.should == "hello"
+ end
+
+ it "raises a RuntimeError if the string is modified while substituting" do
+ str = "hello"
+ -> { str.sub!(//) { str << 'x' } }.should raise_error(RuntimeError)
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ s = "hello"
+ s.freeze
+
+ -> { s.sub!(/ROAR/) { "x" } }.should raise_error(FrozenError)
+ -> { s.sub!(/e/) { "e" } }.should raise_error(FrozenError)
+ -> { s.sub!(/[aeiou]/) { '*' } }.should raise_error(FrozenError)
+ end
+end
+
+describe "String#sub with pattern and Hash" do
+
+ it "returns a copy of self with the first occurrence of pattern replaced with the value of the corresponding hash key" do
+ "hello".sub(/./, 'l' => 'L').should == "ello"
+ "hello!".sub(/(.)(.)/, 'he' => 'she ', 'll' => 'said').should == 'she llo!'
+ "hello".sub('l', 'l' => 'el').should == 'heello'
+ end
+
+ it "removes keys that don't correspond to matches" do
+ "hello".sub(/./, 'z' => 'b', 'o' => 'ow').should == "ello"
+ end
+
+ it "ignores non-String keys" do
+ "tattoo".sub(/(tt)/, 'tt' => 'b', tt: 'z').should == "taboo"
+ end
+
+ it "uses a key's value only a single time" do
+ "food".sub(/o/, 'o' => '0').should == "f0od"
+ end
+
+ it "uses the hash's default value for missing keys" do
+ hsh = {}
+ hsh.default='?'
+ hsh['o'] = '0'
+ "food".sub(/./, hsh).should == "?ood"
+ end
+
+ it "coerces the hash values with #to_s" do
+ hsh = {}
+ hsh.default=[]
+ hsh['o'] = 0
+ obj = mock('!')
+ obj.should_receive(:to_s).and_return('!')
+ hsh['f'] = obj
+ "food!".sub(/./, hsh).should == "!ood!"
+ end
+
+ it "uses the hash's value set from default_proc for missing keys" do
+ hsh = {}
+ hsh.default_proc = -> k, v { 'lamb' }
+ "food!".sub(/./, hsh).should == "lambood!"
+ end
+
+ it "sets $~ to MatchData of first match and nil when there's none for access from outside" do
+ 'hello.'.sub('l', 'l' => 'L')
+ $~.begin(0).should == 2
+ $~[0].should == 'l'
+
+ 'hello.'.sub('not', 'ot' => 'to')
+ $~.should == nil
+
+ 'hello.'.sub(/.(.)/, 'o' => ' hole')
+ $~[0].should == 'he'
+
+ 'hello.'.sub(/not/, 'z' => 'glark')
+ $~.should == nil
+ end
+
+ it "doesn't interpolate special sequences like \\1 for the block's return value" do
+ repl = '\& \0 \1 \` \\\' \+ \\\\ foo'
+ "hello".sub(/(.+)/, 'hello' => repl ).should == repl
+ end
+
+end
+
+describe "String#sub! with pattern and Hash" do
+
+ it "returns self with the first occurrence of pattern replaced with the value of the corresponding hash key" do
+ "hello".sub!(/./, 'l' => 'L').should == "ello"
+ "hello!".sub!(/(.)(.)/, 'he' => 'she ', 'll' => 'said').should == 'she llo!'
+ "hello".sub!('l', 'l' => 'el').should == 'heello'
+ end
+
+ it "removes keys that don't correspond to matches" do
+ "hello".sub!(/./, 'z' => 'b', 'o' => 'ow').should == "ello"
+ end
+
+ it "ignores non-String keys" do
+ "hello".sub!(/(ll)/, 'll' => 'r', ll: 'z').should == "hero"
+ end
+
+ it "uses a key's value only a single time" do
+ "food".sub!(/o/, 'o' => '0').should == "f0od"
+ end
+
+ it "uses the hash's default value for missing keys" do
+ hsh = {}
+ hsh.default='?'
+ hsh['o'] = '0'
+ "food".sub!(/./, hsh).should == "?ood"
+ end
+
+ it "coerces the hash values with #to_s" do
+ hsh = {}
+ hsh.default=[]
+ hsh['o'] = 0
+ obj = mock('!')
+ obj.should_receive(:to_s).and_return('!')
+ hsh['f'] = obj
+ "food!".sub!(/./, hsh).should == "!ood!"
+ end
+
+ it "uses the hash's value set from default_proc for missing keys" do
+ hsh = {}
+ hsh.default_proc = -> k, v { 'lamb' }
+ "food!".sub!(/./, hsh).should == "lambood!"
+ end
+
+ it "sets $~ to MatchData of first match and nil when there's none for access from outside" do
+ 'hello.'.sub!('l', 'l' => 'L')
+ $~.begin(0).should == 2
+ $~[0].should == 'l'
+
+ 'hello.'.sub!('not', 'ot' => 'to')
+ $~.should == nil
+
+ 'hello.'.sub!(/.(.)/, 'o' => ' hole')
+ $~[0].should == 'he'
+
+ 'hello.'.sub!(/not/, 'z' => 'glark')
+ $~.should == nil
+ end
+
+ it "doesn't interpolate special sequences like \\1 for the block's return value" do
+ repl = '\& \0 \1 \` \\\' \+ \\\\ foo'
+ "hello".sub!(/(.+)/, 'hello' => repl ).should == repl
+ end
+end
+
+describe "String#sub with pattern and without replacement and block" do
+ it "raises a ArgumentError" do
+ -> { "abca".sub(/a/) }.should raise_error(ArgumentError)
+ end
+end
+
+describe "String#sub! with pattern and without replacement and block" do
+ it "raises a ArgumentError" do
+ -> { "abca".sub!(/a/) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/succ_spec.rb b/spec/ruby/core/string/succ_spec.rb
new file mode 100644
index 0000000000..65047e0aa2
--- /dev/null
+++ b/spec/ruby/core/string/succ_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/succ'
+
+describe "String#succ" do
+ it_behaves_like :string_succ, :succ
+end
+
+describe "String#succ!" do
+ it_behaves_like :string_succ_bang, :"succ!"
+end
diff --git a/spec/ruby/core/string/sum_spec.rb b/spec/ruby/core/string/sum_spec.rb
new file mode 100644
index 0000000000..c283b7c254
--- /dev/null
+++ b/spec/ruby/core/string/sum_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#sum" do
+ it "returns a basic n-bit checksum of the characters in self" do
+ "ruby".sum.should == 450
+ "ruby".sum(8).should == 194
+ "rubinius".sum(23).should == 881
+ end
+
+ it "tries to convert n to an integer using to_int" do
+ obj = mock('8')
+ obj.should_receive(:to_int).and_return(8)
+
+ "hello".sum(obj).should == "hello".sum(8)
+ end
+
+ it "returns sum of the bytes in self if n less or equal to zero" do
+ "xyz".sum(0).should == 363
+ "xyz".sum(-10).should == 363
+ end
+end
diff --git a/spec/ruby/core/string/swapcase_spec.rb b/spec/ruby/core/string/swapcase_spec.rb
new file mode 100644
index 0000000000..d369ab3e4e
--- /dev/null
+++ b/spec/ruby/core/string/swapcase_spec.rb
@@ -0,0 +1,201 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#swapcase" do
+ it "returns a new string with all uppercase chars from self converted to lowercase and vice versa" do
+ "Hello".swapcase.should == "hELLO"
+ "cYbEr_PuNk11".swapcase.should == "CyBeR_pUnK11"
+ "+++---111222???".swapcase.should == "+++---111222???"
+ end
+
+ it "returns a String in the same encoding as self" do
+ "Hello".encode("US-ASCII").swapcase.encoding.should == Encoding::US_ASCII
+ end
+
+ describe "full Unicode case mapping" do
+ it "works for all of Unicode with no option" do
+ "äÖü".swapcase.should == "ÄöÜ"
+ end
+
+ it "updates string metadata" do
+ swapcased = "Aßet".swapcase
+
+ swapcased.should == "aSSET"
+ swapcased.size.should == 5
+ swapcased.bytesize.should == 5
+ swapcased.ascii_only?.should be_true
+ end
+ end
+
+ describe "ASCII-only case mapping" do
+ it "does not swapcase non-ASCII characters" do
+ "aßet".swapcase(:ascii).should == "AßET"
+ end
+
+ it "works with substrings" do
+ "prefix aTé"[-3..-1].swapcase(:ascii).should == "Até"
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Turkic languages" do
+ it "swaps case of ASCII characters according to Turkic semantics" do
+ "aiS".swapcase(:turkic).should == "Aİs"
+ end
+
+ it "allows Lithuanian as an extra option" do
+ "aiS".swapcase(:turkic, :lithuanian).should == "Aİs"
+ end
+
+ it "does not allow any other additional option" do
+ -> { "aiS".swapcase(:turkic, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Lithuanian" do
+ it "currently works the same as full Unicode case mapping" do
+ "Iß".swapcase(:lithuanian).should == "iSS"
+ end
+
+ it "allows Turkic as an extra option (and applies Turkic semantics)" do
+ "iS".swapcase(:lithuanian, :turkic).should == "İs"
+ end
+
+ it "does not allow any other additional option" do
+ -> { "aiS".swapcase(:lithuanian, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "does not allow the :fold option for upcasing" do
+ -> { "abc".swapcase(:fold) }.should raise_error(ArgumentError)
+ end
+
+ it "does not allow invalid options" do
+ -> { "abc".swapcase(:invalid_option) }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("").swapcase.should be_an_instance_of(StringSpecs::MyString)
+ StringSpecs::MyString.new("hello").swapcase.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("").swapcase.should be_an_instance_of(String)
+ StringSpecs::MyString.new("hello").swapcase.should be_an_instance_of(String)
+ end
+ end
+end
+
+describe "String#swapcase!" do
+ it "modifies self in place" do
+ a = "cYbEr_PuNk11"
+ a.swapcase!.should equal(a)
+ a.should == "CyBeR_pUnK11"
+ end
+
+ it "modifies self in place for non-ascii-compatible encodings" do
+ a = "cYbEr_PuNk11".encode("utf-16le")
+ a.swapcase!
+ a.should == "CyBeR_pUnK11".encode("utf-16le")
+ end
+
+ describe "full Unicode case mapping" do
+ it "modifies self in place for all of Unicode with no option" do
+ a = "äÖü"
+ a.swapcase!
+ a.should == "ÄöÜ"
+ end
+
+ it "works for non-ascii-compatible encodings" do
+ a = "äÖü".encode("utf-16le")
+ a.swapcase!
+ a.should == "ÄöÜ".encode("utf-16le")
+ end
+
+ it "updates string metadata" do
+ swapcased = "Aßet"
+ swapcased.swapcase!
+
+ swapcased.should == "aSSET"
+ swapcased.size.should == 5
+ swapcased.bytesize.should == 5
+ swapcased.ascii_only?.should be_true
+ end
+ end
+
+ describe "modifies self in place for ASCII-only case mapping" do
+ it "does not swapcase non-ASCII characters" do
+ a = "aßet"
+ a.swapcase!(:ascii)
+ a.should == "AßET"
+ end
+
+ it "works for non-ascii-compatible encodings" do
+ a = "aBc".encode("utf-16le")
+ a.swapcase!(:ascii)
+ a.should == "AbC".encode("utf-16le")
+ end
+ end
+
+ describe "modifies self in place for full Unicode case mapping adapted for Turkic languages" do
+ it "swaps case of ASCII characters according to Turkic semantics" do
+ a = "aiS"
+ a.swapcase!(:turkic)
+ a.should == "Aİs"
+ end
+
+ it "allows Lithuanian as an extra option" do
+ a = "aiS"
+ a.swapcase!(:turkic, :lithuanian)
+ a.should == "Aİs"
+ end
+
+ it "does not allow any other additional option" do
+ -> { a = "aiS"; a.swapcase!(:turkic, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Lithuanian" do
+ it "currently works the same as full Unicode case mapping" do
+ a = "Iß"
+ a.swapcase!(:lithuanian)
+ a.should == "iSS"
+ end
+
+ it "allows Turkic as an extra option (and applies Turkic semantics)" do
+ a = "iS"
+ a.swapcase!(:lithuanian, :turkic)
+ a.should == "İs"
+ end
+
+ it "does not allow any other additional option" do
+ -> { a = "aiS"; a.swapcase!(:lithuanian, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "does not allow the :fold option for upcasing" do
+ -> { a = "abc"; a.swapcase!(:fold) }.should raise_error(ArgumentError)
+ end
+
+ it "does not allow invalid options" do
+ -> { a = "abc"; a.swapcase!(:invalid_option) }.should raise_error(ArgumentError)
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "+++---111222???"
+ a.swapcase!.should == nil
+ a.should == "+++---111222???"
+
+ "".swapcase!.should == nil
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ ["", "hello"].each do |a|
+ a.freeze
+ -> { a.swapcase! }.should raise_error(FrozenError)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/to_c_spec.rb b/spec/ruby/core/string/to_c_spec.rb
new file mode 100644
index 0000000000..edc8a4f14f
--- /dev/null
+++ b/spec/ruby/core/string/to_c_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/complex'
+require_relative 'fixtures/to_c'
+
+describe "String#to_c" do
+ it_behaves_like :kernel_complex, :to_c_method, StringSpecs
+end
+
+describe "String#to_c" do
+ it "returns a complex number with 0 as the real part, 0 as the imaginary part for unrecognised Strings" do
+ 'ruby'.to_c.should == Complex(0, 0)
+ end
+
+ it "ignores trailing garbage" do
+ '79+4iruby'.to_c.should == Complex(79, 4)
+ ruby_bug "[Bug #19087]", ""..."3.2" do
+ '7__9+4__0i'.to_c.should == Complex(7, 0)
+ end
+ end
+
+ it "understands Float::INFINITY" do
+ 'Infinity'.to_c.should == Complex(0, 1)
+ '-Infinity'.to_c.should == Complex(0, -1)
+ end
+
+ it "understands Float::NAN" do
+ 'NaN'.to_c.should == Complex(0, 0)
+ end
+
+ it "allows null-byte" do
+ "1-2i\0".to_c.should == Complex(1, -2)
+ "1\0-2i".to_c.should == Complex(1, 0)
+ "\01-2i".to_c.should == Complex(0, 0)
+ end
+
+ it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do
+ -> {
+ '79+4i'.encode("UTF-16").to_c
+ }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16")
+ end
+end
diff --git a/spec/ruby/core/string/to_f_spec.rb b/spec/ruby/core/string/to_f_spec.rb
new file mode 100644
index 0000000000..cf64ecfc5d
--- /dev/null
+++ b/spec/ruby/core/string/to_f_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# src.scan(/[+-]?[\d_]+\.[\d_]+(e[+-]?[\d_]+)?\b|[+-]?[\d_]+e[+-]?[\d_]+\b/i)
+
+describe "String#to_f" do
+ it "treats leading characters of self as a floating point number" do
+ "123.45e1".to_f.should == 1234.5
+ "45.67 degrees".to_f.should == 45.67
+ "0".to_f.should == 0.0
+ "123.45e1".to_f.should == 1234.5
+
+ ".5".to_f.should == 0.5
+ ".5e1".to_f.should == 5.0
+ "5.".to_f.should == 5.0
+ "5e".to_f.should == 5.0
+ "5E".to_f.should == 5.0
+ end
+
+ it "treats special float value strings as characters" do
+ "NaN".to_f.should == 0
+ "Infinity".to_f.should == 0
+ "-Infinity".to_f.should == 0
+ end
+
+ it "allows for varying case" do
+ "123.45e1".to_f.should == 1234.5
+ "123.45E1".to_f.should == 1234.5
+ end
+
+ it "allows for varying signs" do
+ "+123.45e1".to_f.should == +123.45e1
+ "-123.45e1".to_f.should == -123.45e1
+ "123.45e+1".to_f.should == 123.45e+1
+ "123.45e-1".to_f.should == 123.45e-1
+ "+123.45e+1".to_f.should == +123.45e+1
+ "+123.45e-1".to_f.should == +123.45e-1
+ "-123.45e+1".to_f.should == -123.45e+1
+ "-123.45e-1".to_f.should == -123.45e-1
+ end
+
+ it "allows for underscores, even in the decimal side" do
+ "1_234_567.890_1".to_f.should == 1_234_567.890_1
+ end
+
+ it "returns 0 for strings with any non-digit in them" do
+ "blah".to_f.should == 0
+ "0b5".to_f.should == 0
+ "0d5".to_f.should == 0
+ "0o5".to_f.should == 0
+ "0xx5".to_f.should == 0
+ end
+
+ it "returns 0 for strings with leading underscores" do
+ "_9".to_f.should == 0
+ end
+
+ it "takes an optional sign" do
+ "-45.67 degrees".to_f.should == -45.67
+ "+45.67 degrees".to_f.should == 45.67
+ "-5_5e-5_0".to_f.should == -55e-50
+ "-".to_f.should == 0.0
+ (1.0 / "-0".to_f).to_s.should == "-Infinity"
+ end
+
+ it "returns 0.0 if the conversion fails" do
+ "bad".to_f.should == 0.0
+ "thx1138".to_f.should == 0.0
+ end
+end
diff --git a/spec/ruby/core/string/to_i_spec.rb b/spec/ruby/core/string/to_i_spec.rb
new file mode 100644
index 0000000000..e4fa89aab3
--- /dev/null
+++ b/spec/ruby/core/string/to_i_spec.rb
@@ -0,0 +1,337 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#to_i" do
+ it "returns 0 for strings with leading underscores" do
+ "_123".to_i.should == 0
+ end
+
+ it "ignores underscores in between the digits" do
+ "1_2_3asdf".to_i.should == 123
+ end
+
+ it "ignores leading whitespaces" do
+ [ " 123", " 123", "\r\n\r\n123", "\t\t123",
+ "\r\n\t\n123", " \t\n\r\t 123"].each do |str|
+ str.to_i.should == 123
+ end
+ end
+
+ it "ignores subsequent invalid characters" do
+ "123asdf".to_i.should == 123
+ "123#123".to_i.should == 123
+ "123 456".to_i.should == 123
+ end
+
+ it "returns 0 if self is no valid integer-representation" do
+ [ "++2", "+-2", "--2" ].each do |str|
+ str.to_i.should == 0
+ end
+ end
+
+ it "accepts '+' at the beginning of a String" do
+ "+0d56".to_i.should == 56
+ end
+
+ it "interprets leading characters as a number in the given base" do
+ "100110010010".to_i(2).should == 0b100110010010
+ "100110201001".to_i(3).should == 186409
+ "103110201001".to_i(4).should == 5064769
+ "103110241001".to_i(5).should == 55165126
+ "153110241001".to_i(6).should == 697341529
+ "153160241001".to_i(7).should == 3521513430
+ "153160241701".to_i(8).should == 14390739905
+ "853160241701".to_i(9).should == 269716550518
+ "853160241791".to_i(10).should == 853160241791
+
+ "F00D_BE_1337".to_i(16).should == 0xF00D_BE_1337
+ "-hello_world".to_i(32).should == -18306744
+ "abcXYZ".to_i(36).should == 623741435
+
+ ("z" * 24).to_i(36).should == 22452257707354557240087211123792674815
+
+ "5e10".to_i.should == 5
+ end
+
+ it "auto-detects base 8 via leading 0 when base = 0" do
+ "01778".to_i(0).should == 0177
+ "-01778".to_i(0).should == -0177
+ end
+
+ it "auto-detects base 2 via 0b when base = 0" do
+ "0b112".to_i(0).should == 0b11
+ "-0b112".to_i(0).should == -0b11
+ end
+
+ it "auto-detects base 10 via 0d when base = 0" do
+ "0d19A".to_i(0).should == 19
+ "-0d19A".to_i(0).should == -19
+ end
+
+ it "auto-detects base 8 via 0o when base = 0" do
+ "0o178".to_i(0).should == 0o17
+ "-0o178".to_i(0).should == -0o17
+ end
+
+ it "auto-detects base 16 via 0x when base = 0" do
+ "0xFAZ".to_i(0).should == 0xFA
+ "-0xFAZ".to_i(0).should == -0xFA
+ end
+
+ it "auto-detects base 10 with no base specifier when base = 0" do
+ "1234567890ABC".to_i(0).should == 1234567890
+ "-1234567890ABC".to_i(0).should == -1234567890
+ end
+
+ it "doesn't handle foreign base specifiers when base is > 0" do
+ [2, 3, 4, 8, 10].each do |base|
+ "0111".to_i(base).should == "111".to_i(base)
+
+ "0b11".to_i(base).should == (base == 2 ? 0b11 : 0)
+ "0d11".to_i(base).should == (base == 10 ? 0d11 : 0)
+ "0o11".to_i(base).should == (base == 8 ? 0o11 : 0)
+ "0xFA".to_i(base).should == 0
+ end
+
+ "0xD00D".to_i(16).should == 0xD00D
+
+ "0b11".to_i(16).should == 0xb11
+ "0d11".to_i(16).should == 0xd11
+ "0o11".to_i(25).should == 15026
+ "0x11".to_i(34).should == 38183
+
+ "0B11".to_i(16).should == 0xb11
+ "0D11".to_i(16).should == 0xd11
+ "0O11".to_i(25).should == 15026
+ "0X11".to_i(34).should == 38183
+ end
+
+ it "tries to convert the base to an integer using to_int" do
+ obj = mock('8')
+ obj.should_receive(:to_int).and_return(8)
+
+ "777".to_i(obj).should == 0777
+ end
+
+ it "requires that the sign if any appears before the base specifier" do
+ "0b-1".to_i( 2).should == 0
+ "0d-1".to_i(10).should == 0
+ "0o-1".to_i( 8).should == 0
+ "0x-1".to_i(16).should == 0
+
+ "0b-1".to_i(2).should == 0
+ "0o-1".to_i(8).should == 0
+ "0d-1".to_i(10).should == 0
+ "0x-1".to_i(16).should == 0
+ end
+
+ it "raises an ArgumentError for illegal bases (1, < 0 or > 36)" do
+ -> { "".to_i(1) }.should raise_error(ArgumentError)
+ -> { "".to_i(-1) }.should raise_error(ArgumentError)
+ -> { "".to_i(37) }.should raise_error(ArgumentError)
+ end
+
+ it "returns an Integer for long strings with trailing spaces" do
+ "0 ".to_i.should == 0
+ "0 ".to_i.should be_an_instance_of(Integer)
+
+ "10 ".to_i.should == 10
+ "10 ".to_i.should be_an_instance_of(Integer)
+
+ "-10 ".to_i.should == -10
+ "-10 ".to_i.should be_an_instance_of(Integer)
+ end
+
+ it "returns an Integer for long strings with leading spaces" do
+ " 0".to_i.should == 0
+ " 0".to_i.should be_an_instance_of(Integer)
+
+ " 10".to_i.should == 10
+ " 10".to_i.should be_an_instance_of(Integer)
+
+ " -10".to_i.should == -10
+ " -10".to_i.should be_an_instance_of(Integer)
+ end
+
+ it "returns the correct Integer for long strings" do
+ "245789127594125924165923648312749312749327482".to_i.should == 245789127594125924165923648312749312749327482
+ "-245789127594125924165923648312749312749327482".to_i.should == -245789127594125924165923648312749312749327482
+ end
+end
+
+describe "String#to_i with bases" do
+ it "parses a String in base 2" do
+ str = "10" * 50
+ str.to_i(2).to_s(2).should == str
+ end
+
+ it "parses a String in base 3" do
+ str = "120" * 33
+ str.to_i(3).to_s(3).should == str
+ end
+
+ it "parses a String in base 4" do
+ str = "1230" * 25
+ str.to_i(4).to_s(4).should == str
+ end
+
+ it "parses a String in base 5" do
+ str = "12340" * 20
+ str.to_i(5).to_s(5).should == str
+ end
+
+ it "parses a String in base 6" do
+ str = "123450" * 16
+ str.to_i(6).to_s(6).should == str
+ end
+
+ it "parses a String in base 7" do
+ str = "1234560" * 14
+ str.to_i(7).to_s(7).should == str
+ end
+
+ it "parses a String in base 8" do
+ str = "12345670" * 12
+ str.to_i(8).to_s(8).should == str
+ end
+
+ it "parses a String in base 9" do
+ str = "123456780" * 11
+ str.to_i(9).to_s(9).should == str
+ end
+
+ it "parses a String in base 10" do
+ str = "1234567890" * 10
+ str.to_i(10).to_s(10).should == str
+ end
+
+ it "parses a String in base 11" do
+ str = "1234567890a" * 9
+ str.to_i(11).to_s(11).should == str
+ end
+
+ it "parses a String in base 12" do
+ str = "1234567890ab" * 8
+ str.to_i(12).to_s(12).should == str
+ end
+
+ it "parses a String in base 13" do
+ str = "1234567890abc" * 7
+ str.to_i(13).to_s(13).should == str
+ end
+
+ it "parses a String in base 14" do
+ str = "1234567890abcd" * 7
+ str.to_i(14).to_s(14).should == str
+ end
+
+ it "parses a String in base 15" do
+ str = "1234567890abcde" * 6
+ str.to_i(15).to_s(15).should == str
+ end
+
+ it "parses a String in base 16" do
+ str = "1234567890abcdef" * 6
+ str.to_i(16).to_s(16).should == str
+ end
+
+ it "parses a String in base 17" do
+ str = "1234567890abcdefg" * 5
+ str.to_i(17).to_s(17).should == str
+ end
+
+ it "parses a String in base 18" do
+ str = "1234567890abcdefgh" * 5
+ str.to_i(18).to_s(18).should == str
+ end
+
+ it "parses a String in base 19" do
+ str = "1234567890abcdefghi" * 5
+ str.to_i(19).to_s(19).should == str
+ end
+
+ it "parses a String in base 20" do
+ str = "1234567890abcdefghij" * 5
+ str.to_i(20).to_s(20).should == str
+ end
+
+ it "parses a String in base 21" do
+ str = "1234567890abcdefghijk" * 4
+ str.to_i(21).to_s(21).should == str
+ end
+
+ it "parses a String in base 22" do
+ str = "1234567890abcdefghijkl" * 4
+ str.to_i(22).to_s(22).should == str
+ end
+
+ it "parses a String in base 23" do
+ str = "1234567890abcdefghijklm" * 4
+ str.to_i(23).to_s(23).should == str
+ end
+
+ it "parses a String in base 24" do
+ str = "1234567890abcdefghijklmn" * 4
+ str.to_i(24).to_s(24).should == str
+ end
+
+ it "parses a String in base 25" do
+ str = "1234567890abcdefghijklmno" * 4
+ str.to_i(25).to_s(25).should == str
+ end
+
+ it "parses a String in base 26" do
+ str = "1234567890abcdefghijklmnop" * 3
+ str.to_i(26).to_s(26).should == str
+ end
+
+ it "parses a String in base 27" do
+ str = "1234567890abcdefghijklmnopq" * 3
+ str.to_i(27).to_s(27).should == str
+ end
+
+ it "parses a String in base 28" do
+ str = "1234567890abcdefghijklmnopqr" * 3
+ str.to_i(28).to_s(28).should == str
+ end
+
+ it "parses a String in base 29" do
+ str = "1234567890abcdefghijklmnopqrs" * 3
+ str.to_i(29).to_s(29).should == str
+ end
+
+ it "parses a String in base 30" do
+ str = "1234567890abcdefghijklmnopqrst" * 3
+ str.to_i(30).to_s(30).should == str
+ end
+
+ it "parses a String in base 31" do
+ str = "1234567890abcdefghijklmnopqrstu" * 3
+ str.to_i(31).to_s(31).should == str
+ end
+
+ it "parses a String in base 32" do
+ str = "1234567890abcdefghijklmnopqrstuv" * 3
+ str.to_i(32).to_s(32).should == str
+ end
+
+ it "parses a String in base 33" do
+ str = "1234567890abcdefghijklmnopqrstuvw" * 3
+ str.to_i(33).to_s(33).should == str
+ end
+
+ it "parses a String in base 34" do
+ str = "1234567890abcdefghijklmnopqrstuvwx" * 2
+ str.to_i(34).to_s(34).should == str
+ end
+
+ it "parses a String in base 35" do
+ str = "1234567890abcdefghijklmnopqrstuvwxy" * 2
+ str.to_i(35).to_s(35).should == str
+ end
+
+ it "parses a String in base 36" do
+ str = "1234567890abcdefghijklmnopqrstuvwxyz" * 2
+ str.to_i(36).to_s(36).should == str
+ end
+end
diff --git a/spec/ruby/core/string/to_r_spec.rb b/spec/ruby/core/string/to_r_spec.rb
new file mode 100644
index 0000000000..7e1d635d3b
--- /dev/null
+++ b/spec/ruby/core/string/to_r_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+
+describe "String#to_r" do
+ it "returns a Rational object" do
+ String.new.to_r.should be_an_instance_of(Rational)
+ end
+
+ it "returns (0/1) for the empty String" do
+ "".to_r.should == Rational(0, 1)
+ end
+
+ it "returns (n/1) for a String starting with a decimal _n_" do
+ "2".to_r.should == Rational(2, 1)
+ "1765".to_r.should == Rational(1765, 1)
+ end
+
+ it "ignores trailing characters" do
+ "2 foo".to_r.should == Rational(2, 1)
+ "1765, ".to_r.should == Rational(1765, 1)
+ end
+
+ it "ignores leading spaces" do
+ " 2".to_r.should == Rational(2, 1)
+ " 1765, ".to_r.should == Rational(1765, 1)
+ end
+
+ it "does not ignore arbitrary, non-numeric leading characters" do
+ "The rational form of 33 is...".to_r.should_not == Rational(33, 1)
+ "a1765, ".to_r.should_not == Rational(1765, 1)
+ end
+
+ it "treats leading hyphen as minus signs" do
+ "-20".to_r.should == Rational(-20, 1)
+ end
+
+ it "does not treat a leading period without a numeric prefix as a decimal point" do
+ ".9".to_r.should_not == Rational(8106479329266893, 9007199254740992)
+ end
+
+ it "understands decimal points" do
+ "3.33".to_r.should == Rational(333, 100)
+ "-3.33".to_r.should == Rational(-333, 100)
+ end
+
+ it "ignores underscores between numbers" do
+ "190_22".to_r.should == Rational(19022, 1)
+ "-190_22.7".to_r.should == Rational(-190227, 10)
+ end
+
+ it "understands a forward slash as separating the numerator from the denominator" do
+ "20/3".to_r.should == Rational(20, 3)
+ " -19.10/3".to_r.should == Rational(-191, 30)
+ end
+
+ it "returns (0/1) for Strings it can't parse" do
+ "glark".to_r.should == Rational(0,1)
+ end
+end
diff --git a/spec/ruby/core/string/to_s_spec.rb b/spec/ruby/core/string/to_s_spec.rb
new file mode 100644
index 0000000000..e5872745a8
--- /dev/null
+++ b/spec/ruby/core/string/to_s_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/to_s'
+
+describe "String#to_s" do
+ it_behaves_like :string_to_s, :to_s
+end
diff --git a/spec/ruby/core/string/to_str_spec.rb b/spec/ruby/core/string/to_str_spec.rb
new file mode 100644
index 0000000000..e24262a7ae
--- /dev/null
+++ b/spec/ruby/core/string/to_str_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/to_s'
+
+describe "String#to_str" do
+ it_behaves_like :string_to_s, :to_str
+end
diff --git a/spec/ruby/core/string/to_sym_spec.rb b/spec/ruby/core/string/to_sym_spec.rb
new file mode 100644
index 0000000000..f9135211ce
--- /dev/null
+++ b/spec/ruby/core/string/to_sym_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/to_sym'
+
+describe "String#to_sym" do
+ it_behaves_like :string_to_sym, :to_sym
+end
diff --git a/spec/ruby/core/string/tr_s_spec.rb b/spec/ruby/core/string/tr_s_spec.rb
new file mode 100644
index 0000000000..e1bb20ce35
--- /dev/null
+++ b/spec/ruby/core/string/tr_s_spec.rb
@@ -0,0 +1,131 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#tr_s" do
+ it "returns a string processed according to tr with newly duplicate characters removed" do
+ "hello".tr_s('l', 'r').should == "hero"
+ "hello".tr_s('el', '*').should == "h*o"
+ "hello".tr_s('el', 'hx').should == "hhxo"
+ "hello".tr_s('o', '.').should == "hell."
+ end
+
+ it "accepts c1-c2 notation to denote ranges of characters" do
+ "hello".tr_s('a-y', 'b-z').should == "ifmp"
+ "123456789".tr_s("2-5", "abcdefg").should == "1abcd6789"
+ "hello ^--^".tr_s("e-", "__").should == "h_llo ^_^"
+ "hello ^--^".tr_s("---", "_").should == "hello ^_^"
+ end
+
+ it "pads to_str with its last char if it is shorter than from_string" do
+ "this".tr_s("this", "x").should == "x"
+ end
+
+ it "translates chars not in from_string when it starts with a ^" do
+ "hello".tr_s('^aeiou', '*').should == "*e*o"
+ "123456789".tr_s("^345", "abc").should == "c345c"
+ "abcdefghijk".tr_s("^d-g", "9131").should == "1defg1"
+
+ "hello ^_^".tr_s("a-e^e", ".").should == "h.llo ._."
+ "hello ^_^".tr_s("^^", ".").should == ".^.^"
+ "hello ^_^".tr_s("^", "x").should == "hello x_x"
+ "hello ^-^".tr_s("^-^", "x").should == "x^-^"
+ "hello ^-^".tr_s("^^-^", "x").should == "x^x^"
+ "hello ^-^".tr_s("^---", "x").should == "x-x"
+ "hello ^-^".tr_s("^---l-o", "x").should == "xllox-x"
+ end
+
+ it "tries to convert from_str and to_str to strings using to_str" do
+ from_str = mock('ab')
+ from_str.should_receive(:to_str).and_return("ab")
+
+ to_str = mock('AB')
+ to_str.should_receive(:to_str).and_return("AB")
+
+ "bla".tr_s(from_str, to_str).should == "BlA"
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("hello").tr_s("e", "a").should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("hello").tr_s("e", "a").should be_an_instance_of(String)
+ end
+ end
+
+ # http://redmine.ruby-lang.org/issues/show/1839
+ it "can replace a 7-bit ASCII character with a multibyte one" do
+ a = "uber"
+ a.encoding.should == Encoding::UTF_8
+ b = a.tr_s("u","ü")
+ b.should == "über"
+ b.encoding.should == Encoding::UTF_8
+ end
+
+ it "can replace multiple 7-bit ASCII characters with a multibyte one" do
+ a = "uuuber"
+ a.encoding.should == Encoding::UTF_8
+ b = a.tr_s("u","ü")
+ b.should == "über"
+ b.encoding.should == Encoding::UTF_8
+ end
+
+ it "can replace a multibyte character with a single byte one" do
+ a = "über"
+ a.encoding.should == Encoding::UTF_8
+ b = a.tr_s("ü","u")
+ b.should == "uber"
+ b.encoding.should == Encoding::UTF_8
+ end
+
+ it "can replace multiple multibyte characters with a single byte one" do
+ a = "üüüber"
+ a.encoding.should == Encoding::UTF_8
+ b = a.tr_s("ü","u")
+ b.should == "uber"
+ b.encoding.should == Encoding::UTF_8
+ end
+
+ it "does not replace a multibyte character where part of the bytes match the tr string" do
+ str = "æ¤Žåæ·±å¤"
+ a = "\u0080\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008E\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009E\u009F"
+ b = "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“â€â€¢â€“—˜™š›œžŸ"
+ str.tr_s(a, b).should == "æ¤Žåæ·±å¤"
+ end
+
+
+end
+
+describe "String#tr_s!" do
+ it "modifies self in place" do
+ s = "hello"
+ s.tr_s!("l", "r").should == "hero"
+ s.should == "hero"
+ end
+
+ it "returns nil if no modification was made" do
+ s = "hello"
+ s.tr_s!("za", "yb").should == nil
+ s.tr_s!("", "").should == nil
+ s.should == "hello"
+ end
+
+ it "does not modify self if from_str is empty" do
+ s = "hello"
+ s.tr_s!("", "").should == nil
+ s.should == "hello"
+ s.tr_s!("", "yb").should == nil
+ s.should == "hello"
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ s = "hello".freeze
+ -> { s.tr_s!("el", "ar") }.should raise_error(FrozenError)
+ -> { s.tr_s!("l", "r") }.should raise_error(FrozenError)
+ -> { s.tr_s!("", "") }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/tr_spec.rb b/spec/ruby/core/string/tr_spec.rb
new file mode 100644
index 0000000000..72adb9f2eb
--- /dev/null
+++ b/spec/ruby/core/string/tr_spec.rb
@@ -0,0 +1,126 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#tr" do
+ it "returns a new string with the characters from from_string replaced by the ones in to_string" do
+ "hello".tr('aeiou', '*').should == "h*ll*"
+ "hello".tr('el', 'ip').should == "hippo"
+ "Lisp".tr("Lisp", "Ruby").should == "Ruby"
+ end
+
+ it "accepts c1-c2 notation to denote ranges of characters" do
+ "hello".tr('a-y', 'b-z').should == "ifmmp"
+ "123456789".tr("2-5","abcdefg").should == "1abcd6789"
+ "hello ^-^".tr("e-", "__").should == "h_llo ^_^"
+ "hello ^-^".tr("---", "_").should == "hello ^_^"
+ end
+
+ it "pads to_str with its last char if it is shorter than from_string" do
+ "this".tr("this", "x").should == "xxxx"
+ "hello".tr("a-z", "A-H.").should == "HE..."
+ end
+
+ it "raises an ArgumentError a descending range in the replacement as containing just the start character" do
+ -> { "hello".tr("a-y", "z-b") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError a descending range in the source as empty" do
+ -> { "hello".tr("l-a", "z") }.should raise_error(ArgumentError)
+ end
+
+ it "translates chars not in from_string when it starts with a ^" do
+ "hello".tr('^aeiou', '*').should == "*e**o"
+ "123456789".tr("^345", "abc").should == "cc345cccc"
+ "abcdefghijk".tr("^d-g", "9131").should == "111defg1111"
+
+ "hello ^_^".tr("a-e^e", ".").should == "h.llo ._."
+ "hello ^_^".tr("^^", ".").should == "......^.^"
+ "hello ^_^".tr("^", "x").should == "hello x_x"
+ "hello ^-^".tr("^-^", "x").should == "xxxxxx^-^"
+ "hello ^-^".tr("^^-^", "x").should == "xxxxxx^x^"
+ "hello ^-^".tr("^---", "x").should == "xxxxxxx-x"
+ "hello ^-^".tr("^---l-o", "x").should == "xxlloxx-x"
+ end
+
+ it "supports non-injective replacements" do
+ "hello".tr("helo", "1212").should == "12112"
+ end
+
+ it "tries to convert from_str and to_str to strings using to_str" do
+ from_str = mock('ab')
+ from_str.should_receive(:to_str).and_return("ab")
+
+ to_str = mock('AB')
+ to_str.should_receive(:to_str).and_return("AB")
+
+ "bla".tr(from_str, to_str).should == "BlA"
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns subclass instances when called on a subclass" do
+ StringSpecs::MyString.new("hello").tr("e", "a").should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns Stringinstances when called on a subclass" do
+ StringSpecs::MyString.new("hello").tr("e", "a").should be_an_instance_of(String)
+ end
+ end
+
+ # http://redmine.ruby-lang.org/issues/show/1839
+ it "can replace a 7-bit ASCII character with a multibyte one" do
+ a = "uber"
+ a.encoding.should == Encoding::UTF_8
+ b = a.tr("u","ü")
+ b.should == "über"
+ b.encoding.should == Encoding::UTF_8
+ end
+
+ it "can replace a multibyte character with a single byte one" do
+ a = "über"
+ a.encoding.should == Encoding::UTF_8
+ b = a.tr("ü","u")
+ b.should == "uber"
+ b.encoding.should == Encoding::UTF_8
+ end
+
+ it "does not replace a multibyte character where part of the bytes match the tr string" do
+ str = "æ¤Žåæ·±å¤"
+ a = "\u0080\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008E\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009E\u009F"
+ b = "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“â€â€¢â€“—˜™š›œžŸ"
+ str.tr(a, b).should == "æ¤Žåæ·±å¤"
+ end
+
+end
+
+describe "String#tr!" do
+ it "modifies self in place" do
+ s = "abcdefghijklmnopqR"
+ s.tr!("cdefg", "12").should == "ab12222hijklmnopqR"
+ s.should == "ab12222hijklmnopqR"
+ end
+
+ it "returns nil if no modification was made" do
+ s = "hello"
+ s.tr!("za", "yb").should == nil
+ s.tr!("", "").should == nil
+ s.should == "hello"
+ end
+
+ it "does not modify self if from_str is empty" do
+ s = "hello"
+ s.tr!("", "").should == nil
+ s.should == "hello"
+ s.tr!("", "yb").should == nil
+ s.should == "hello"
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ s = "abcdefghijklmnopqR".freeze
+ -> { s.tr!("cdefg", "12") }.should raise_error(FrozenError)
+ -> { s.tr!("R", "S") }.should raise_error(FrozenError)
+ -> { s.tr!("", "") }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/try_convert_spec.rb b/spec/ruby/core/string/try_convert_spec.rb
new file mode 100644
index 0000000000..84415c4a75
--- /dev/null
+++ b/spec/ruby/core/string/try_convert_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String.try_convert" do
+ it "returns the argument if it's a String" do
+ x = String.new
+ String.try_convert(x).should equal(x)
+ end
+
+ it "returns the argument if it's a kind of String" do
+ x = StringSpecs::MyString.new
+ String.try_convert(x).should equal(x)
+ end
+
+ it "returns nil when the argument does not respond to #to_str" do
+ String.try_convert(Object.new).should be_nil
+ end
+
+ it "sends #to_str to the argument and returns the result if it's nil" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(nil)
+ String.try_convert(obj).should be_nil
+ end
+
+ it "sends #to_str to the argument and returns the result if it's a String" do
+ x = String.new
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(x)
+ String.try_convert(obj).should equal(x)
+ end
+
+ it "sends #to_str to the argument and returns the result if it's a kind of String" do
+ x = StringSpecs::MyString.new
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(x)
+ String.try_convert(obj).should equal(x)
+ end
+
+ it "sends #to_str to the argument and raises TypeError if it's not a kind of String" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(Object.new)
+ -> { String.try_convert obj }.should raise_error(TypeError)
+ end
+
+ it "does not rescue exceptions raised by #to_str" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_raise(RuntimeError)
+ -> { String.try_convert obj }.should raise_error(RuntimeError)
+ end
+end
diff --git a/spec/ruby/core/string/uminus_spec.rb b/spec/ruby/core/string/uminus_spec.rb
new file mode 100644
index 0000000000..46d88f6704
--- /dev/null
+++ b/spec/ruby/core/string/uminus_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/dedup'
+
+describe 'String#-@' do
+ it_behaves_like :string_dedup, :-@
+end
diff --git a/spec/ruby/core/string/undump_spec.rb b/spec/ruby/core/string/undump_spec.rb
new file mode 100644
index 0000000000..6ff220161c
--- /dev/null
+++ b/spec/ruby/core/string/undump_spec.rb
@@ -0,0 +1,441 @@
+# encoding: utf-8
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#undump" do
+ it "does not take into account if a string is frozen" do
+ '"foo"'.freeze.undump.should_not.frozen?
+ end
+
+ it "always returns String instance" do
+ StringSpecs::MyString.new('"foo"').undump.should be_an_instance_of(String)
+ end
+
+ it "strips outer \"" do
+ '"foo"'.undump.should == 'foo'
+ end
+
+ it "returns a string with special characters in \\<char> notation replaced with the characters" do
+ [ ['"\\a"', "\a"],
+ ['"\\b"', "\b"],
+ ['"\\t"', "\t"],
+ ['"\\n"', "\n"],
+ ['"\\v"', "\v"],
+ ['"\\f"', "\f"],
+ ['"\\r"', "\r"],
+ ['"\\e"', "\e"]
+ ].should be_computed_by(:undump)
+ end
+
+ it "returns a string with unescaped sequences \" and \\" do
+ [ ['"\\""' , "\""],
+ ['"\\\\"', "\\"]
+ ].should be_computed_by(:undump)
+ end
+
+ it "returns a string with unescaped sequences \\#<char> when # is followed by $, @, {" do
+ [ ['"\\#$PATH"', "\#$PATH"],
+ ['"\\#@a"', "\#@a"],
+ ['"\\#@@a"', "\#@@a"],
+ ['"\\#{a}"', "\#{a}"]
+ ].should be_computed_by(:undump)
+ end
+
+ it "returns a string with # not escaped when followed by any other character" do
+ [ ['"#"', '#'],
+ ['"#1"', '#1']
+ ].should be_computed_by(:undump)
+ end
+
+ it "returns a string with printable non-alphanumeric characters" do
+ [ ['" "', ' '],
+ ['"!"', '!'],
+ ['"$"', '$'],
+ ['"%"', '%'],
+ ['"&"', '&'],
+ ['"\'"', '\''],
+ ['"("', '('],
+ ['")"', ')'],
+ ['"*"', '*'],
+ ['"+"', '+'],
+ ['","', ','],
+ ['"-"', '-'],
+ ['"."', '.'],
+ ['"/"', '/'],
+ ['":"', ':'],
+ ['";"', ';'],
+ ['"<"', '<'],
+ ['"="', '='],
+ ['">"', '>'],
+ ['"?"', '?'],
+ ['"@"', '@'],
+ ['"["', '['],
+ ['"]"', ']'],
+ ['"^"', '^'],
+ ['"_"', '_'],
+ ['"`"', '`'],
+ ['"{"', '{'],
+ ['"|"', '|'],
+ ['"}"', '}'],
+ ['"~"', '~']
+ ].should be_computed_by(:undump)
+ end
+
+ it "returns a string with numeric characters unescaped" do
+ [ ['"0"', "0"],
+ ['"1"', "1"],
+ ['"2"', "2"],
+ ['"3"', "3"],
+ ['"4"', "4"],
+ ['"5"', "5"],
+ ['"6"', "6"],
+ ['"7"', "7"],
+ ['"8"', "8"],
+ ['"9"', "9"],
+ ].should be_computed_by(:undump)
+ end
+
+ it "returns a string with upper-case alpha characters unescaped" do
+ [ ['"A"', 'A'],
+ ['"B"', 'B'],
+ ['"C"', 'C'],
+ ['"D"', 'D'],
+ ['"E"', 'E'],
+ ['"F"', 'F'],
+ ['"G"', 'G'],
+ ['"H"', 'H'],
+ ['"I"', 'I'],
+ ['"J"', 'J'],
+ ['"K"', 'K'],
+ ['"L"', 'L'],
+ ['"M"', 'M'],
+ ['"N"', 'N'],
+ ['"O"', 'O'],
+ ['"P"', 'P'],
+ ['"Q"', 'Q'],
+ ['"R"', 'R'],
+ ['"S"', 'S'],
+ ['"T"', 'T'],
+ ['"U"', 'U'],
+ ['"V"', 'V'],
+ ['"W"', 'W'],
+ ['"X"', 'X'],
+ ['"Y"', 'Y'],
+ ['"Z"', 'Z']
+ ].should be_computed_by(:undump)
+ end
+
+ it "returns a string with lower-case alpha characters unescaped" do
+ [ ['"a"', 'a'],
+ ['"b"', 'b'],
+ ['"c"', 'c'],
+ ['"d"', 'd'],
+ ['"e"', 'e'],
+ ['"f"', 'f'],
+ ['"g"', 'g'],
+ ['"h"', 'h'],
+ ['"i"', 'i'],
+ ['"j"', 'j'],
+ ['"k"', 'k'],
+ ['"l"', 'l'],
+ ['"m"', 'm'],
+ ['"n"', 'n'],
+ ['"o"', 'o'],
+ ['"p"', 'p'],
+ ['"q"', 'q'],
+ ['"r"', 'r'],
+ ['"s"', 's'],
+ ['"t"', 't'],
+ ['"u"', 'u'],
+ ['"v"', 'v'],
+ ['"w"', 'w'],
+ ['"x"', 'x'],
+ ['"y"', 'y'],
+ ['"z"', 'z']
+ ].should be_computed_by(:undump)
+ end
+
+ it "returns a string with \\x notation replaced with non-printing ASCII character" do
+ [ ['"\\x00"', 0000.chr.force_encoding('utf-8')],
+ ['"\\x01"', 0001.chr.force_encoding('utf-8')],
+ ['"\\x02"', 0002.chr.force_encoding('utf-8')],
+ ['"\\x03"', 0003.chr.force_encoding('utf-8')],
+ ['"\\x04"', 0004.chr.force_encoding('utf-8')],
+ ['"\\x05"', 0005.chr.force_encoding('utf-8')],
+ ['"\\x06"', 0006.chr.force_encoding('utf-8')],
+ ['"\\x0E"', 0016.chr.force_encoding('utf-8')],
+ ['"\\x0F"', 0017.chr.force_encoding('utf-8')],
+ ['"\\x10"', 0020.chr.force_encoding('utf-8')],
+ ['"\\x11"', 0021.chr.force_encoding('utf-8')],
+ ['"\\x12"', 0022.chr.force_encoding('utf-8')],
+ ['"\\x13"', 0023.chr.force_encoding('utf-8')],
+ ['"\\x14"', 0024.chr.force_encoding('utf-8')],
+ ['"\\x15"', 0025.chr.force_encoding('utf-8')],
+ ['"\\x16"', 0026.chr.force_encoding('utf-8')],
+ ['"\\x17"', 0027.chr.force_encoding('utf-8')],
+ ['"\\x18"', 0030.chr.force_encoding('utf-8')],
+ ['"\\x19"', 0031.chr.force_encoding('utf-8')],
+ ['"\\x1A"', 0032.chr.force_encoding('utf-8')],
+ ['"\\x1C"', 0034.chr.force_encoding('utf-8')],
+ ['"\\x1D"', 0035.chr.force_encoding('utf-8')],
+ ['"\\x1E"', 0036.chr.force_encoding('utf-8')],
+ ['"\\x1F"', 0037.chr.force_encoding('utf-8')],
+ ['"\\x7F"', 0177.chr.force_encoding('utf-8')],
+ ['"\\x80"', 0200.chr.force_encoding('utf-8')],
+ ['"\\x81"', 0201.chr.force_encoding('utf-8')],
+ ['"\\x82"', 0202.chr.force_encoding('utf-8')],
+ ['"\\x83"', 0203.chr.force_encoding('utf-8')],
+ ['"\\x84"', 0204.chr.force_encoding('utf-8')],
+ ['"\\x85"', 0205.chr.force_encoding('utf-8')],
+ ['"\\x86"', 0206.chr.force_encoding('utf-8')],
+ ['"\\x87"', 0207.chr.force_encoding('utf-8')],
+ ['"\\x88"', 0210.chr.force_encoding('utf-8')],
+ ['"\\x89"', 0211.chr.force_encoding('utf-8')],
+ ['"\\x8A"', 0212.chr.force_encoding('utf-8')],
+ ['"\\x8B"', 0213.chr.force_encoding('utf-8')],
+ ['"\\x8C"', 0214.chr.force_encoding('utf-8')],
+ ['"\\x8D"', 0215.chr.force_encoding('utf-8')],
+ ['"\\x8E"', 0216.chr.force_encoding('utf-8')],
+ ['"\\x8F"', 0217.chr.force_encoding('utf-8')],
+ ['"\\x90"', 0220.chr.force_encoding('utf-8')],
+ ['"\\x91"', 0221.chr.force_encoding('utf-8')],
+ ['"\\x92"', 0222.chr.force_encoding('utf-8')],
+ ['"\\x93"', 0223.chr.force_encoding('utf-8')],
+ ['"\\x94"', 0224.chr.force_encoding('utf-8')],
+ ['"\\x95"', 0225.chr.force_encoding('utf-8')],
+ ['"\\x96"', 0226.chr.force_encoding('utf-8')],
+ ['"\\x97"', 0227.chr.force_encoding('utf-8')],
+ ['"\\x98"', 0230.chr.force_encoding('utf-8')],
+ ['"\\x99"', 0231.chr.force_encoding('utf-8')],
+ ['"\\x9A"', 0232.chr.force_encoding('utf-8')],
+ ['"\\x9B"', 0233.chr.force_encoding('utf-8')],
+ ['"\\x9C"', 0234.chr.force_encoding('utf-8')],
+ ['"\\x9D"', 0235.chr.force_encoding('utf-8')],
+ ['"\\x9E"', 0236.chr.force_encoding('utf-8')],
+ ['"\\x9F"', 0237.chr.force_encoding('utf-8')],
+ ['"\\xA0"', 0240.chr.force_encoding('utf-8')],
+ ['"\\xA1"', 0241.chr.force_encoding('utf-8')],
+ ['"\\xA2"', 0242.chr.force_encoding('utf-8')],
+ ['"\\xA3"', 0243.chr.force_encoding('utf-8')],
+ ['"\\xA4"', 0244.chr.force_encoding('utf-8')],
+ ['"\\xA5"', 0245.chr.force_encoding('utf-8')],
+ ['"\\xA6"', 0246.chr.force_encoding('utf-8')],
+ ['"\\xA7"', 0247.chr.force_encoding('utf-8')],
+ ['"\\xA8"', 0250.chr.force_encoding('utf-8')],
+ ['"\\xA9"', 0251.chr.force_encoding('utf-8')],
+ ['"\\xAA"', 0252.chr.force_encoding('utf-8')],
+ ['"\\xAB"', 0253.chr.force_encoding('utf-8')],
+ ['"\\xAC"', 0254.chr.force_encoding('utf-8')],
+ ['"\\xAD"', 0255.chr.force_encoding('utf-8')],
+ ['"\\xAE"', 0256.chr.force_encoding('utf-8')],
+ ['"\\xAF"', 0257.chr.force_encoding('utf-8')],
+ ['"\\xB0"', 0260.chr.force_encoding('utf-8')],
+ ['"\\xB1"', 0261.chr.force_encoding('utf-8')],
+ ['"\\xB2"', 0262.chr.force_encoding('utf-8')],
+ ['"\\xB3"', 0263.chr.force_encoding('utf-8')],
+ ['"\\xB4"', 0264.chr.force_encoding('utf-8')],
+ ['"\\xB5"', 0265.chr.force_encoding('utf-8')],
+ ['"\\xB6"', 0266.chr.force_encoding('utf-8')],
+ ['"\\xB7"', 0267.chr.force_encoding('utf-8')],
+ ['"\\xB8"', 0270.chr.force_encoding('utf-8')],
+ ['"\\xB9"', 0271.chr.force_encoding('utf-8')],
+ ['"\\xBA"', 0272.chr.force_encoding('utf-8')],
+ ['"\\xBB"', 0273.chr.force_encoding('utf-8')],
+ ['"\\xBC"', 0274.chr.force_encoding('utf-8')],
+ ['"\\xBD"', 0275.chr.force_encoding('utf-8')],
+ ['"\\xBE"', 0276.chr.force_encoding('utf-8')],
+ ['"\\xBF"', 0277.chr.force_encoding('utf-8')],
+ ['"\\xC0"', 0300.chr.force_encoding('utf-8')],
+ ['"\\xC1"', 0301.chr.force_encoding('utf-8')],
+ ['"\\xC2"', 0302.chr.force_encoding('utf-8')],
+ ['"\\xC3"', 0303.chr.force_encoding('utf-8')],
+ ['"\\xC4"', 0304.chr.force_encoding('utf-8')],
+ ['"\\xC5"', 0305.chr.force_encoding('utf-8')],
+ ['"\\xC6"', 0306.chr.force_encoding('utf-8')],
+ ['"\\xC7"', 0307.chr.force_encoding('utf-8')],
+ ['"\\xC8"', 0310.chr.force_encoding('utf-8')],
+ ['"\\xC9"', 0311.chr.force_encoding('utf-8')],
+ ['"\\xCA"', 0312.chr.force_encoding('utf-8')],
+ ['"\\xCB"', 0313.chr.force_encoding('utf-8')],
+ ['"\\xCC"', 0314.chr.force_encoding('utf-8')],
+ ['"\\xCD"', 0315.chr.force_encoding('utf-8')],
+ ['"\\xCE"', 0316.chr.force_encoding('utf-8')],
+ ['"\\xCF"', 0317.chr.force_encoding('utf-8')],
+ ['"\\xD0"', 0320.chr.force_encoding('utf-8')],
+ ['"\\xD1"', 0321.chr.force_encoding('utf-8')],
+ ['"\\xD2"', 0322.chr.force_encoding('utf-8')],
+ ['"\\xD3"', 0323.chr.force_encoding('utf-8')],
+ ['"\\xD4"', 0324.chr.force_encoding('utf-8')],
+ ['"\\xD5"', 0325.chr.force_encoding('utf-8')],
+ ['"\\xD6"', 0326.chr.force_encoding('utf-8')],
+ ['"\\xD7"', 0327.chr.force_encoding('utf-8')],
+ ['"\\xD8"', 0330.chr.force_encoding('utf-8')],
+ ['"\\xD9"', 0331.chr.force_encoding('utf-8')],
+ ['"\\xDA"', 0332.chr.force_encoding('utf-8')],
+ ['"\\xDB"', 0333.chr.force_encoding('utf-8')],
+ ['"\\xDC"', 0334.chr.force_encoding('utf-8')],
+ ['"\\xDD"', 0335.chr.force_encoding('utf-8')],
+ ['"\\xDE"', 0336.chr.force_encoding('utf-8')],
+ ['"\\xDF"', 0337.chr.force_encoding('utf-8')],
+ ['"\\xE0"', 0340.chr.force_encoding('utf-8')],
+ ['"\\xE1"', 0341.chr.force_encoding('utf-8')],
+ ['"\\xE2"', 0342.chr.force_encoding('utf-8')],
+ ['"\\xE3"', 0343.chr.force_encoding('utf-8')],
+ ['"\\xE4"', 0344.chr.force_encoding('utf-8')],
+ ['"\\xE5"', 0345.chr.force_encoding('utf-8')],
+ ['"\\xE6"', 0346.chr.force_encoding('utf-8')],
+ ['"\\xE7"', 0347.chr.force_encoding('utf-8')],
+ ['"\\xE8"', 0350.chr.force_encoding('utf-8')],
+ ['"\\xE9"', 0351.chr.force_encoding('utf-8')],
+ ['"\\xEA"', 0352.chr.force_encoding('utf-8')],
+ ['"\\xEB"', 0353.chr.force_encoding('utf-8')],
+ ['"\\xEC"', 0354.chr.force_encoding('utf-8')],
+ ['"\\xED"', 0355.chr.force_encoding('utf-8')],
+ ['"\\xEE"', 0356.chr.force_encoding('utf-8')],
+ ['"\\xEF"', 0357.chr.force_encoding('utf-8')],
+ ['"\\xF0"', 0360.chr.force_encoding('utf-8')],
+ ['"\\xF1"', 0361.chr.force_encoding('utf-8')],
+ ['"\\xF2"', 0362.chr.force_encoding('utf-8')],
+ ['"\\xF3"', 0363.chr.force_encoding('utf-8')],
+ ['"\\xF4"', 0364.chr.force_encoding('utf-8')],
+ ['"\\xF5"', 0365.chr.force_encoding('utf-8')],
+ ['"\\xF6"', 0366.chr.force_encoding('utf-8')],
+ ['"\\xF7"', 0367.chr.force_encoding('utf-8')],
+ ['"\\xF8"', 0370.chr.force_encoding('utf-8')],
+ ['"\\xF9"', 0371.chr.force_encoding('utf-8')],
+ ['"\\xFA"', 0372.chr.force_encoding('utf-8')],
+ ['"\\xFB"', 0373.chr.force_encoding('utf-8')],
+ ['"\\xFC"', 0374.chr.force_encoding('utf-8')],
+ ['"\\xFD"', 0375.chr.force_encoding('utf-8')],
+ ['"\\xFE"', 0376.chr.force_encoding('utf-8')],
+ ['"\\xFF"', 0377.chr.force_encoding('utf-8')]
+ ].should be_computed_by(:undump)
+ end
+
+ it "returns a string with \\u{} notation replaced with multi-byte UTF-8 characters" do
+ [ ['"\u{80}"', 0200.chr('utf-8')],
+ ['"\u{81}"', 0201.chr('utf-8')],
+ ['"\u{82}"', 0202.chr('utf-8')],
+ ['"\u{83}"', 0203.chr('utf-8')],
+ ['"\u{84}"', 0204.chr('utf-8')],
+ ['"\u{86}"', 0206.chr('utf-8')],
+ ['"\u{87}"', 0207.chr('utf-8')],
+ ['"\u{88}"', 0210.chr('utf-8')],
+ ['"\u{89}"', 0211.chr('utf-8')],
+ ['"\u{8a}"', 0212.chr('utf-8')],
+ ['"\u{8b}"', 0213.chr('utf-8')],
+ ['"\u{8c}"', 0214.chr('utf-8')],
+ ['"\u{8d}"', 0215.chr('utf-8')],
+ ['"\u{8e}"', 0216.chr('utf-8')],
+ ['"\u{8f}"', 0217.chr('utf-8')],
+ ['"\u{90}"', 0220.chr('utf-8')],
+ ['"\u{91}"', 0221.chr('utf-8')],
+ ['"\u{92}"', 0222.chr('utf-8')],
+ ['"\u{93}"', 0223.chr('utf-8')],
+ ['"\u{94}"', 0224.chr('utf-8')],
+ ['"\u{95}"', 0225.chr('utf-8')],
+ ['"\u{96}"', 0226.chr('utf-8')],
+ ['"\u{97}"', 0227.chr('utf-8')],
+ ['"\u{98}"', 0230.chr('utf-8')],
+ ['"\u{99}"', 0231.chr('utf-8')],
+ ['"\u{9a}"', 0232.chr('utf-8')],
+ ['"\u{9b}"', 0233.chr('utf-8')],
+ ['"\u{9c}"', 0234.chr('utf-8')],
+ ['"\u{9d}"', 0235.chr('utf-8')],
+ ['"\u{9e}"', 0236.chr('utf-8')],
+ ['"\u{9f}"', 0237.chr('utf-8')],
+ ].should be_computed_by(:undump)
+ end
+
+ it "returns a string with \\uXXXX notation replaced with multi-byte UTF-8 characters" do
+ [ ['"\u0080"', 0200.chr('utf-8')],
+ ['"\u0081"', 0201.chr('utf-8')],
+ ['"\u0082"', 0202.chr('utf-8')],
+ ['"\u0083"', 0203.chr('utf-8')],
+ ['"\u0084"', 0204.chr('utf-8')],
+ ['"\u0086"', 0206.chr('utf-8')],
+ ['"\u0087"', 0207.chr('utf-8')],
+ ['"\u0088"', 0210.chr('utf-8')],
+ ['"\u0089"', 0211.chr('utf-8')],
+ ['"\u008a"', 0212.chr('utf-8')],
+ ['"\u008b"', 0213.chr('utf-8')],
+ ['"\u008c"', 0214.chr('utf-8')],
+ ['"\u008d"', 0215.chr('utf-8')],
+ ['"\u008e"', 0216.chr('utf-8')],
+ ['"\u008f"', 0217.chr('utf-8')],
+ ['"\u0090"', 0220.chr('utf-8')],
+ ['"\u0091"', 0221.chr('utf-8')],
+ ['"\u0092"', 0222.chr('utf-8')],
+ ['"\u0093"', 0223.chr('utf-8')],
+ ['"\u0094"', 0224.chr('utf-8')],
+ ['"\u0095"', 0225.chr('utf-8')],
+ ['"\u0096"', 0226.chr('utf-8')],
+ ['"\u0097"', 0227.chr('utf-8')],
+ ['"\u0098"', 0230.chr('utf-8')],
+ ['"\u0099"', 0231.chr('utf-8')],
+ ['"\u009a"', 0232.chr('utf-8')],
+ ['"\u009b"', 0233.chr('utf-8')],
+ ['"\u009c"', 0234.chr('utf-8')],
+ ['"\u009d"', 0235.chr('utf-8')],
+ ['"\u009e"', 0236.chr('utf-8')],
+ ['"\u009f"', 0237.chr('utf-8')],
+ ].should be_computed_by(:undump)
+ end
+
+ it "undumps correctly string produced from non ASCII-compatible one" do
+ s = "\u{876}".encode('utf-16be')
+ s.dump.undump.should == s
+
+ '"\\bv".force_encoding("UTF-16BE")'.undump.should == "\u0876".encode('utf-16be')
+ end
+
+ it "returns a String in the same encoding as self" do
+ '"foo"'.encode("ISO-8859-1").undump.encoding.should == Encoding::ISO_8859_1
+ '"foo"'.encode('windows-1251').undump.encoding.should == Encoding::Windows_1251
+ end
+
+ describe "Limitations" do
+ it "cannot undump non ASCII-compatible string" do
+ -> { '"foo"'.encode('utf-16le').undump }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+
+ describe "invalid dump" do
+ it "raises RuntimeError exception if wrapping \" are missing" do
+ -> { 'foo'.undump }.should raise_error(RuntimeError, /invalid dumped string/)
+ -> { '"foo'.undump }.should raise_error(RuntimeError, /unterminated dumped string/)
+ -> { 'foo"'.undump }.should raise_error(RuntimeError, /invalid dumped string/)
+ -> { "'foo'".undump }.should raise_error(RuntimeError, /invalid dumped string/)
+ end
+
+ it "raises RuntimeError if there is incorrect \\x sequence" do
+ -> { '"\x"'.undump }.should raise_error(RuntimeError, /invalid hex escape/)
+ -> { '"\\x3y"'.undump }.should raise_error(RuntimeError, /invalid hex escape/)
+ end
+
+ it "raises RuntimeError in there is incorrect \\u sequence" do
+ -> { '"\\u"'.undump }.should raise_error(RuntimeError, /invalid Unicode escape/)
+ -> { '"\\u{"'.undump }.should raise_error(RuntimeError, /invalid Unicode escape/)
+ -> { '"\\u{3042"'.undump }.should raise_error(RuntimeError, /invalid Unicode escape/)
+ -> { '"\\u"'.undump }.should raise_error(RuntimeError, /invalid Unicode escape/)
+ end
+
+ it "raises RuntimeError if there is malformed dump of non ASCII-compatible string" do
+ -> { '"".force_encoding("BINARY"'.undump }.should raise_error(RuntimeError, /invalid dumped string/)
+ -> { '"".force_encoding("Unknown")'.undump }.should raise_error(RuntimeError, /dumped string has unknown encoding name/)
+ -> { '"".force_encoding()'.undump }.should raise_error(RuntimeError, /invalid dumped string/)
+ end
+
+ it "raises RuntimeError if string contains \0 character" do
+ -> { "\"foo\0\"".undump }.should raise_error(RuntimeError, /string contains null byte/)
+ end
+
+ it "raises RuntimeError if string contains non ASCII character" do
+ -> { "\"\u3042\"".undump }.should raise_error(RuntimeError, /non-ASCII character detected/)
+ end
+
+ it "raises RuntimeError if there are some excessive \"" do
+ -> { '" "" "'.undump }.should raise_error(RuntimeError, /invalid dumped string/)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/unicode_normalize_spec.rb b/spec/ruby/core/string/unicode_normalize_spec.rb
new file mode 100644
index 0000000000..6de7533fc7
--- /dev/null
+++ b/spec/ruby/core/string/unicode_normalize_spec.rb
@@ -0,0 +1,115 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+# Examples taken from http://www.unicode.org/reports/tr15/#Norm_Forms
+
+describe "String#unicode_normalize" do
+ before :each do
+ @accented_f = "\u1e9b\u0323"
+ @angstrom = "\u212b"
+ @ohm = "\u2126"
+ end
+
+ it "normalizes code points in the string according to the form that is specified" do
+ @accented_f.unicode_normalize(:nfc).should == "\u1e9b\u0323"
+ @accented_f.unicode_normalize(:nfd).should == "\u017f\u0323\u0307"
+ @accented_f.unicode_normalize(:nfkc).should == "\u1e69"
+ @accented_f.unicode_normalize(:nfkd).should == "\u0073\u0323\u0307"
+ end
+
+ it "defaults to the nfc normalization form if no forms are specified" do
+ @accented_f.unicode_normalize.should == "\u1e9b\u0323"
+ @angstrom.unicode_normalize.should == "\u00c5"
+ @ohm.unicode_normalize.should == "\u03a9"
+ end
+
+ # http://unicode.org/faq/normalization.html#6
+ context "returns normalized form of string by default" do
+ it "03D3 (Ï“) GREEK UPSILON WITH ACUTE AND HOOK SYMBOL" do
+ "\u03D3".unicode_normalize(:nfc).should == "\u03D3"
+ "\u03D3".unicode_normalize(:nfd).should == "\u03D2\u0301"
+ "\u03D3".unicode_normalize(:nfkc).should == "\u038E"
+ "\u03D3".unicode_normalize(:nfkd).should == "\u03A5\u0301"
+ end
+
+ it "03D4 (Ï”) GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL" do
+ "\u03D4".unicode_normalize(:nfc).should == "\u03D4"
+ "\u03D4".unicode_normalize(:nfd).should == "\u03D2\u0308"
+ "\u03D4".unicode_normalize(:nfkc).should == "\u03AB"
+ "\u03D4".unicode_normalize(:nfkd).should == "\u03A5\u0308"
+ end
+
+ it "1E9B (ẛ) LATIN SMALL LETTER LONG S WITH DOT ABOVE" do
+ "\u1E9B".unicode_normalize(:nfc).should == "\u1E9B"
+ "\u1E9B".unicode_normalize(:nfd).should == "\u017F\u0307"
+ "\u1E9B".unicode_normalize(:nfkc).should == "\u1E61"
+ "\u1E9B".unicode_normalize(:nfkd).should == "\u0073\u0307"
+ end
+ end
+
+ it "raises an Encoding::CompatibilityError if string is not in an unicode encoding" do
+ -> do
+ [0xE0].pack('C').force_encoding("ISO-8859-1").unicode_normalize(:nfd)
+ end.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises an ArgumentError if the specified form is invalid" do
+ -> {
+ @angstrom.unicode_normalize(:invalid_form)
+ }.should raise_error(ArgumentError)
+ end
+end
+
+describe "String#unicode_normalize!" do
+ it "normalizes code points and modifies the receiving string" do
+ angstrom = "\u212b"
+ angstrom.unicode_normalize!
+ angstrom.should == "\u00c5"
+ angstrom.should_not == "\u212b"
+ end
+
+ it "modifies original string (nfc)" do
+ str = "a\u0300"
+ str.unicode_normalize!(:nfc)
+
+ str.should_not == "a\u0300"
+ str.should == "à"
+ end
+
+ it "modifies self in place (nfd)" do
+ str = "\u00E0"
+ str.unicode_normalize!(:nfd)
+
+ str.should_not == "\u00E0"
+ str.should == "a\u0300"
+ end
+
+ it "modifies self in place (nfkc)" do
+ str = "\u1E9B\u0323"
+ str.unicode_normalize!(:nfkc)
+
+ str.should_not == "\u1E9B\u0323"
+ str.should == "\u1E69"
+ end
+
+ it "modifies self in place (nfkd)" do
+ str = "\u1E9B\u0323"
+ str.unicode_normalize!(:nfkd)
+
+ str.should_not == "\u1E9B\u0323"
+ str.should == "s\u0323\u0307"
+ end
+
+ it "raises an Encoding::CompatibilityError if the string is not in an unicode encoding" do
+ -> {
+ [0xE0].pack('C').force_encoding("ISO-8859-1").unicode_normalize!
+ }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises an ArgumentError if the specified form is invalid" do
+ ohm = "\u2126"
+ -> {
+ ohm.unicode_normalize!(:invalid_form)
+ }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/unicode_normalized_spec.rb b/spec/ruby/core/string/unicode_normalized_spec.rb
new file mode 100644
index 0000000000..87f3740459
--- /dev/null
+++ b/spec/ruby/core/string/unicode_normalized_spec.rb
@@ -0,0 +1,74 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "String#unicode_normalized?" do
+ before :each do
+ @nfc_normalized_str = "\u1e9b\u0323"
+ @nfd_normalized_str = "\u017f\u0323\u0307"
+ @nfkc_normalized_str = "\u1e69"
+ @nfkd_normalized_str = "\u0073\u0323\u0307"
+ end
+
+ it "returns true if string is in the specified normalization form" do
+ @nfc_normalized_str.unicode_normalized?(:nfc).should == true
+ @nfd_normalized_str.unicode_normalized?(:nfd).should == true
+ @nfkc_normalized_str.unicode_normalized?(:nfkc).should == true
+ @nfkd_normalized_str.unicode_normalized?(:nfkd).should == true
+ end
+
+ it "returns false if string is not in the supplied normalization form" do
+ @nfd_normalized_str.unicode_normalized?(:nfc).should == false
+ @nfc_normalized_str.unicode_normalized?(:nfd).should == false
+ @nfc_normalized_str.unicode_normalized?(:nfkc).should == false
+ @nfc_normalized_str.unicode_normalized?(:nfkd).should == false
+ end
+
+ it "defaults to the nfc normalization form if no forms are specified" do
+ @nfc_normalized_str.should.unicode_normalized?
+ @nfd_normalized_str.should_not.unicode_normalized?
+ end
+
+ it "returns true if string is empty" do
+ "".should.unicode_normalized?
+ end
+
+ it "returns true if string does not contain any unicode codepoints" do
+ "abc".should.unicode_normalized?
+ end
+
+ it "raises an Encoding::CompatibilityError if the string is not in an unicode encoding" do
+ -> { @nfc_normalized_str.force_encoding("ISO-8859-1").unicode_normalized? }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises an ArgumentError if the specified form is invalid" do
+ -> { @nfc_normalized_str.unicode_normalized?(:invalid_form) }.should raise_error(ArgumentError)
+ end
+
+ it "returns true if str is in Unicode normalization form (nfc)" do
+ str = "a\u0300"
+ str.unicode_normalized?(:nfc).should be_false
+ str.unicode_normalize!(:nfc)
+ str.unicode_normalized?(:nfc).should be_true
+ end
+
+ it "returns true if str is in Unicode normalization form (nfd)" do
+ str = "a\u00E0"
+ str.unicode_normalized?(:nfd).should be_false
+ str.unicode_normalize!(:nfd)
+ str.unicode_normalized?(:nfd).should be_true
+ end
+
+ it "returns true if str is in Unicode normalization form (nfkc)" do
+ str = "a\u0300"
+ str.unicode_normalized?(:nfkc).should be_false
+ str.unicode_normalize!(:nfkc)
+ str.unicode_normalized?(:nfkc).should be_true
+ end
+
+ it "returns true if str is in Unicode normalization form (nfkd)" do
+ str = "a\u00E0"
+ str.unicode_normalized?(:nfkd).should be_false
+ str.unicode_normalize!(:nfkd)
+ str.unicode_normalized?(:nfkd).should be_true
+ end
+end
diff --git a/spec/ruby/core/string/unpack/a_spec.rb b/spec/ruby/core/string/unpack/a_spec.rb
new file mode 100644
index 0000000000..2d83b4c824
--- /dev/null
+++ b/spec/ruby/core/string/unpack/a_spec.rb
@@ -0,0 +1,66 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/string'
+require_relative 'shared/taint'
+
+describe "String#unpack with format 'A'" do
+ it_behaves_like :string_unpack_basic, 'A'
+ it_behaves_like :string_unpack_no_platform, 'A'
+ it_behaves_like :string_unpack_string, 'A'
+ it_behaves_like :string_unpack_Aa, 'A'
+ it_behaves_like :string_unpack_taint, 'A'
+
+ it "removes trailing space and NULL bytes from the decoded string" do
+ [ ["a\x00 b \x00", ["a\x00 b", ""]],
+ ["a\x00 b \x00 ", ["a\x00 b", ""]],
+ ["a\x00 b\x00 ", ["a\x00 b", ""]],
+ ["a\x00 b\x00", ["a\x00 b", ""]],
+ ["a\x00 b ", ["a\x00 b", ""]]
+ ].should be_computed_by(:unpack, "A*A")
+ end
+
+ it "does not remove whitespace other than space" do
+ [ ["a\x00 b\x00\f", ["a\x00 b\x00\f"]],
+ ["a\x00 b\x00\n", ["a\x00 b\x00\n"]],
+ ["a\x00 b\x00\r", ["a\x00 b\x00\r"]],
+ ["a\x00 b\x00\t", ["a\x00 b\x00\t"]],
+ ["a\x00 b\x00\v", ["a\x00 b\x00\v"]],
+ ].should be_computed_by(:unpack, "A*")
+ end
+
+ it "decodes into raw (ascii) string values" do
+ str = "str".force_encoding('UTF-8').unpack("A*")[0]
+ str.encoding.should == Encoding::BINARY
+ end
+
+end
+
+describe "String#unpack with format 'a'" do
+ it_behaves_like :string_unpack_basic, 'a'
+ it_behaves_like :string_unpack_no_platform, 'a'
+ it_behaves_like :string_unpack_string, 'a'
+ it_behaves_like :string_unpack_Aa, 'a'
+ it_behaves_like :string_unpack_taint, 'a'
+
+ it "does not remove trailing whitespace or NULL bytes from the decoded string" do
+ [ ["a\x00 b \x00", ["a\x00 b \x00"]],
+ ["a\x00 b \x00 ", ["a\x00 b \x00 "]],
+ ["a\x00 b\x00 ", ["a\x00 b\x00 "]],
+ ["a\x00 b\x00", ["a\x00 b\x00"]],
+ ["a\x00 b ", ["a\x00 b "]],
+ ["a\x00 b\f", ["a\x00 b\f"]],
+ ["a\x00 b\n", ["a\x00 b\n"]],
+ ["a\x00 b\r", ["a\x00 b\r"]],
+ ["a\x00 b\t", ["a\x00 b\t"]],
+ ["a\x00 b\v", ["a\x00 b\v"]]
+ ].should be_computed_by(:unpack, "a*")
+ end
+
+ it "decodes into raw (ascii) string values" do
+ str = "".unpack("a*")[0]
+ str.encoding.should == Encoding::BINARY
+ end
+
+end
diff --git a/spec/ruby/core/string/unpack/at_spec.rb b/spec/ruby/core/string/unpack/at_spec.rb
new file mode 100644
index 0000000000..70b2389d69
--- /dev/null
+++ b/spec/ruby/core/string/unpack/at_spec.rb
@@ -0,0 +1,29 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+
+describe "String#unpack with format '@'" do
+ it_behaves_like :string_unpack_basic, '@'
+ it_behaves_like :string_unpack_no_platform, '@'
+
+ it "moves the read index to the byte specified by the count" do
+ "\x01\x02\x03\x04".unpack("C3@2C").should == [1, 2, 3, 3]
+ end
+
+ it "implicitly has a count of zero when count is not specified" do
+ "\x01\x02\x03\x04".unpack("C2@C").should == [1, 2, 1]
+ end
+
+ it "has no effect when passed the '*' modifier" do
+ "\x01\x02\x03\x04".unpack("C2@*C").should == [1, 2, 3]
+ end
+
+ it "positions the read index one beyond the last readable byte in the String" do
+ "\x01\x02\x03\x04".unpack("C2@4C").should == [1, 2, nil]
+ end
+
+ it "raises an ArgumentError if the count exceeds the size of the String" do
+ -> { "\x01\x02\x03\x04".unpack("C2@5C") }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/unpack/b_spec.rb b/spec/ruby/core/string/unpack/b_spec.rb
new file mode 100644
index 0000000000..2cf5ebad34
--- /dev/null
+++ b/spec/ruby/core/string/unpack/b_spec.rb
@@ -0,0 +1,217 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/taint'
+
+describe "String#unpack with format 'B'" do
+ it_behaves_like :string_unpack_basic, 'B'
+ it_behaves_like :string_unpack_no_platform, 'B'
+ it_behaves_like :string_unpack_taint, 'B'
+
+ it "decodes one bit from each byte for each format character starting with the most significant bit" do
+ [ ["\x00", "B", ["0"]],
+ ["\x80", "B", ["1"]],
+ ["\x0f", "B", ["0"]],
+ ["\x8f", "B", ["1"]],
+ ["\x7f", "B", ["0"]],
+ ["\xff", "B", ["1"]],
+ ["\x80\x00", "BB", ["1", "0"]],
+ ["\x8f\x00", "BB", ["1", "0"]],
+ ["\x80\x0f", "BB", ["1", "0"]],
+ ["\x80\x8f", "BB", ["1", "1"]],
+ ["\x80\x80", "BB", ["1", "1"]],
+ ["\x0f\x80", "BB", ["0", "1"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "decodes only the number of bits in the string when passed a count" do
+ "\x83".unpack("B25").should == ["10000011"]
+ end
+
+ it "decodes multiple differing bit counts from a single string" do
+ str = "\xaa\xaa\xaa\xaa\x55\xaa\xd4\xc3\x6b\xd7\xaa\xd7\xc3\xd4\xaa\x6b\xd7\xaa"
+ array = str.unpack("B5B6B7B8B9B10B13B14B16B17")
+ array.should == ["10101", "101010", "1010101", "10101010", "010101011",
+ "1101010011", "0110101111010", "10101010110101",
+ "1100001111010100", "10101010011010111"]
+ end
+
+ it "decodes a directive with a '*' modifier after a directive with a count modifier" do
+ "\xd4\xc3\x6b\xd7".unpack("B5B*").should == ["11010", "110000110110101111010111"]
+ end
+
+ it "decodes a directive with a count modifier after a directive with a '*' modifier" do
+ "\xd4\xc3\x6b\xd7".unpack("B*B5").should == ["11010100110000110110101111010111", ""]
+ end
+
+ it "decodes the number of bits specified by the count modifier" do
+ [ ["\x00", "B0", [""]],
+ ["\x80", "B1", ["1"]],
+ ["\x7f", "B2", ["01"]],
+ ["\x8f", "B3", ["100"]],
+ ["\x7f", "B4", ["0111"]],
+ ["\xff", "B5", ["11111"]],
+ ["\xf8", "B6", ["111110"]],
+ ["\x9c", "B7", ["1001110"]],
+ ["\xbd", "B8", ["10111101"]],
+ ["\x80\x80", "B9", ["100000001"]],
+ ["\x80\x70", "B10", ["1000000001"]],
+ ["\x80\x20", "B11", ["10000000001"]],
+ ["\x8f\x10", "B12", ["100011110001"]],
+ ["\x8f\x0f", "B13", ["1000111100001"]],
+ ["\x80\x0f", "B14", ["10000000000011"]],
+ ["\x80\x8f", "B15", ["100000001000111"]],
+ ["\x0f\x81", "B16", ["0000111110000001"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "decodes all the bits when passed the '*' modifier" do
+ [ ["", [""]],
+ ["\x00", ["00000000"]],
+ ["\x80", ["10000000"]],
+ ["\x7f", ["01111111"]],
+ ["\x81", ["10000001"]],
+ ["\x0f", ["00001111"]],
+ ["\x80\x80", ["1000000010000000"]],
+ ["\x8f\x10", ["1000111100010000"]],
+ ["\x00\x10", ["0000000000010000"]]
+ ].should be_computed_by(:unpack, "B*")
+ end
+
+ it "adds an empty string for each element requested beyond the end of the String" do
+ [ ["", ["", "", ""]],
+ ["\x80", ["1", "", ""]],
+ ["\x80\x08", ["1", "0", ""]]
+ ].should be_computed_by(:unpack, "BBB")
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "\x80\x00".unpack("B\x00B").should == ["1", "0"]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "\x80\x00".unpack("B\x00B")
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "\x80\x00".unpack("B B").should == ["1", "0"]
+ end
+
+ it "decodes into US-ASCII string values" do
+ str = "s".force_encoding('UTF-8').unpack("B*")[0]
+ str.encoding.name.should == 'US-ASCII'
+ end
+end
+
+describe "String#unpack with format 'b'" do
+ it_behaves_like :string_unpack_basic, 'b'
+ it_behaves_like :string_unpack_no_platform, 'b'
+ it_behaves_like :string_unpack_taint, 'b'
+
+ it "decodes one bit from each byte for each format character starting with the least significant bit" do
+ [ ["\x00", "b", ["0"]],
+ ["\x01", "b", ["1"]],
+ ["\xf0", "b", ["0"]],
+ ["\xf1", "b", ["1"]],
+ ["\xfe", "b", ["0"]],
+ ["\xff", "b", ["1"]],
+ ["\x01\x00", "bb", ["1", "0"]],
+ ["\xf1\x00", "bb", ["1", "0"]],
+ ["\x01\xf0", "bb", ["1", "0"]],
+ ["\x01\xf1", "bb", ["1", "1"]],
+ ["\x01\x01", "bb", ["1", "1"]],
+ ["\xf0\x01", "bb", ["0", "1"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "decodes only the number of bits in the string when passed a count" do
+ "\x83".unpack("b25").should == ["11000001"]
+ end
+
+ it "decodes multiple differing bit counts from a single string" do
+ str = "\xaa\xaa\xaa\xaa\x55\xaa\xd4\xc3\x6b\xd7\xaa\xd7\xc3\xd4\xaa\x6b\xd7\xaa"
+ array = str.unpack("b5b6b7b8b9b10b13b14b16b17")
+ array.should == ["01010", "010101", "0101010", "01010101", "101010100",
+ "0010101111", "1101011011101", "01010101111010",
+ "1100001100101011", "01010101110101101"]
+ end
+
+ it "decodes a directive with a '*' modifier after a directive with a count modifier" do
+ "\xd4\xc3\x6b\xd7".unpack("b5b*").should == ["00101", "110000111101011011101011"]
+ end
+
+ it "decodes a directive with a count modifier after a directive with a '*' modifier" do
+ "\xd4\xc3\x6b\xd7".unpack("b*b5").should == ["00101011110000111101011011101011", ""]
+ end
+
+ it "decodes the number of bits specified by the count modifier" do
+ [ ["\x00", "b0", [""]],
+ ["\x01", "b1", ["1"]],
+ ["\xfe", "b2", ["01"]],
+ ["\xfc", "b3", ["001"]],
+ ["\xf7", "b4", ["1110"]],
+ ["\xff", "b5", ["11111"]],
+ ["\xfe", "b6", ["011111"]],
+ ["\xce", "b7", ["0111001"]],
+ ["\xbd", "b8", ["10111101"]],
+ ["\x01\xff", "b9", ["100000001"]],
+ ["\x01\xfe", "b10", ["1000000001"]],
+ ["\x01\xfc", "b11", ["10000000001"]],
+ ["\xf1\xf8", "b12", ["100011110001"]],
+ ["\xe1\xf1", "b13", ["1000011110001"]],
+ ["\x03\xe0", "b14", ["11000000000001"]],
+ ["\x47\xc0", "b15", ["111000100000001"]],
+ ["\x81\x0f", "b16", ["1000000111110000"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "decodes all the bits when passed the '*' modifier" do
+ [ ["", [""]],
+ ["\x00", ["00000000"]],
+ ["\x80", ["00000001"]],
+ ["\x7f", ["11111110"]],
+ ["\x81", ["10000001"]],
+ ["\x0f", ["11110000"]],
+ ["\x80\x80", ["0000000100000001"]],
+ ["\x8f\x10", ["1111000100001000"]],
+ ["\x00\x10", ["0000000000001000"]]
+ ].should be_computed_by(:unpack, "b*")
+ end
+
+ it "adds an empty string for each element requested beyond the end of the String" do
+ [ ["", ["", "", ""]],
+ ["\x01", ["1", "", ""]],
+ ["\x01\x80", ["1", "0", ""]]
+ ].should be_computed_by(:unpack, "bbb")
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "\x01\x00".unpack("b\x00b").should == ["1", "0"]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "\x01\x00".unpack("b\x00b")
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "\x01\x00".unpack("b b").should == ["1", "0"]
+ end
+
+ it "decodes into US-ASCII string values" do
+ str = "s".force_encoding('UTF-8').unpack("b*")[0]
+ str.encoding.name.should == 'US-ASCII'
+ end
+end
diff --git a/spec/ruby/core/string/unpack/c_spec.rb b/spec/ruby/core/string/unpack/c_spec.rb
new file mode 100644
index 0000000000..dbcbacc74d
--- /dev/null
+++ b/spec/ruby/core/string/unpack/c_spec.rb
@@ -0,0 +1,73 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+
+describe :string_unpack_8bit, shared: true do
+ it "decodes one byte for a single format character" do
+ "abc".unpack(unpack_format()).should == [97]
+ end
+
+ it "decodes two bytes for two format characters" do
+ "abc".unpack(unpack_format(nil, 2)).should == [97, 98]
+ end
+
+ it "decodes the number of bytes requested by the count modifier" do
+ "abc".unpack(unpack_format(2)).should == [97, 98]
+ end
+
+ it "decodes the remaining bytes when passed the '*' modifier" do
+ "abc".unpack(unpack_format('*')).should == [97, 98, 99]
+ end
+
+ it "decodes the remaining bytes when passed the '*' modifier after another directive" do
+ "abc".unpack(unpack_format()+unpack_format('*')).should == [97, 98, 99]
+ end
+
+ it "decodes zero bytes when no bytes remain and the '*' modifier is passed" do
+ "abc".unpack(unpack_format('*', 2)).should == [97, 98, 99]
+ end
+
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["", [nil, nil, nil]],
+ ["a", [97, nil, nil]],
+ ["ab", [97, 98, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "abc".unpack(unpack_format("\000", 2)).should == [97, 98]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "abc".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "abc".unpack(unpack_format(' ', 2)).should == [97, 98]
+ end
+end
+
+describe "String#unpack with format 'C'" do
+ it_behaves_like :string_unpack_basic, 'C'
+ it_behaves_like :string_unpack_8bit, 'C'
+
+ it "decodes a byte with most significant bit set as a positive number" do
+ "\xff\x80\x82".unpack('C*').should == [255, 128, 130]
+ end
+end
+
+describe "String#unpack with format 'c'" do
+ it_behaves_like :string_unpack_basic, 'c'
+ it_behaves_like :string_unpack_8bit, 'c'
+
+ it "decodes a byte with most significant bit set as a negative number" do
+ "\xff\x80\x82".unpack('c*').should == [-1, -128, -126]
+ end
+end
diff --git a/spec/ruby/core/string/unpack/comment_spec.rb b/spec/ruby/core/string/unpack/comment_spec.rb
new file mode 100644
index 0000000000..e18a53df3c
--- /dev/null
+++ b/spec/ruby/core/string/unpack/comment_spec.rb
@@ -0,0 +1,25 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "String#unpack" do
+ it "ignores directives text from '#' to the first newline" do
+ "\x01\x02\x03".unpack("c#this is a comment\nc").should == [1, 2]
+ end
+
+ it "ignores directives text from '#' to the end if no newline is present" do
+ "\x01\x02\x03".unpack("c#this is a comment c").should == [1]
+ end
+
+ it "ignores comments at the start of the directives string" do
+ "\x01\x02\x03".unpack("#this is a comment\nc").should == [1]
+ end
+
+ it "ignores the entire directive string if it is a comment" do
+ "\x01\x02\x03".unpack("#this is a comment c").should == []
+ end
+
+ it "ignores multiple comments" do
+ "\x01\x02\x03".unpack("c#comment\nc#comment\nc#c").should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/string/unpack/d_spec.rb b/spec/ruby/core/string/unpack/d_spec.rb
new file mode 100644
index 0000000000..0e4f57ec04
--- /dev/null
+++ b/spec/ruby/core/string/unpack/d_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/float'
+
+little_endian do
+ describe "String#unpack with format 'D'" do
+ it_behaves_like :string_unpack_basic, 'D'
+ it_behaves_like :string_unpack_double_le, 'D'
+ end
+
+ describe "String#unpack with format 'd'" do
+ it_behaves_like :string_unpack_basic, 'd'
+ it_behaves_like :string_unpack_double_le, 'd'
+ end
+end
+
+big_endian do
+ describe "String#unpack with format 'D'" do
+ it_behaves_like :string_unpack_basic, 'D'
+ it_behaves_like :string_unpack_double_be, 'D'
+ end
+
+ describe "String#unpack with format 'd'" do
+ it_behaves_like :string_unpack_basic, 'd'
+ it_behaves_like :string_unpack_double_be, 'd'
+ end
+end
diff --git a/spec/ruby/core/string/unpack/e_spec.rb b/spec/ruby/core/string/unpack/e_spec.rb
new file mode 100644
index 0000000000..c958be1c8b
--- /dev/null
+++ b/spec/ruby/core/string/unpack/e_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/float'
+
+describe "String#unpack with format 'E'" do
+ it_behaves_like :string_unpack_basic, 'E'
+ it_behaves_like :string_unpack_double_le, 'E'
+end
+
+describe "String#unpack with format 'e'" do
+ it_behaves_like :string_unpack_basic, 'e'
+ it_behaves_like :string_unpack_float_le, 'e'
+end
diff --git a/spec/ruby/core/string/unpack/f_spec.rb b/spec/ruby/core/string/unpack/f_spec.rb
new file mode 100644
index 0000000000..ec8b9d435e
--- /dev/null
+++ b/spec/ruby/core/string/unpack/f_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/float'
+
+little_endian do
+ describe "String#unpack with format 'F'" do
+ it_behaves_like :string_unpack_basic, 'F'
+ it_behaves_like :string_unpack_float_le, 'F'
+ end
+
+ describe "String#unpack with format 'f'" do
+ it_behaves_like :string_unpack_basic, 'f'
+ it_behaves_like :string_unpack_float_le, 'f'
+ end
+end
+
+big_endian do
+ describe "String#unpack with format 'F'" do
+ it_behaves_like :string_unpack_basic, 'F'
+ it_behaves_like :string_unpack_float_be, 'F'
+ end
+
+ describe "String#unpack with format 'f'" do
+ it_behaves_like :string_unpack_basic, 'f'
+ it_behaves_like :string_unpack_float_be, 'f'
+ end
+end
diff --git a/spec/ruby/core/string/unpack/g_spec.rb b/spec/ruby/core/string/unpack/g_spec.rb
new file mode 100644
index 0000000000..ffc423b152
--- /dev/null
+++ b/spec/ruby/core/string/unpack/g_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/float'
+
+describe "String#unpack with format 'G'" do
+ it_behaves_like :string_unpack_basic, 'G'
+ it_behaves_like :string_unpack_double_be, 'G'
+end
+
+describe "String#unpack with format 'g'" do
+ it_behaves_like :string_unpack_basic, 'g'
+ it_behaves_like :string_unpack_float_be, 'g'
+end
diff --git a/spec/ruby/core/string/unpack/h_spec.rb b/spec/ruby/core/string/unpack/h_spec.rb
new file mode 100644
index 0000000000..ee08d20926
--- /dev/null
+++ b/spec/ruby/core/string/unpack/h_spec.rb
@@ -0,0 +1,155 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/taint'
+
+describe "String#unpack with format 'H'" do
+ it_behaves_like :string_unpack_basic, 'H'
+ it_behaves_like :string_unpack_no_platform, 'H'
+ it_behaves_like :string_unpack_taint, 'H'
+
+ it "decodes one nibble from each byte for each format character starting with the most significant bit" do
+ [ ["\x8f", "H", ["8"]],
+ ["\xf8\x0f", "HH", ["f", "0"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "decodes only the number of nibbles in the string when passed a count" do
+ "\xca\xfe".unpack("H5").should == ["cafe"]
+ end
+
+ it "decodes multiple differing nibble counts from a single string" do
+ array = "\xaa\x55\xaa\xd4\xc3\x6b\xd7\xaa\xd7".unpack("HH2H3H4H5")
+ array.should == ["a", "55", "aad", "c36b", "d7aad"]
+ end
+
+ it "decodes a directive with a '*' modifier after a directive with a count modifier" do
+ "\xaa\x55\xaa\xd4\xc3\x6b".unpack("H3H*").should == ["aa5", "aad4c36b"]
+ end
+
+ it "decodes a directive with a count modifier after a directive with a '*' modifier" do
+ "\xaa\x55\xaa\xd4\xc3\x6b".unpack("H*H3").should == ["aa55aad4c36b", ""]
+ end
+
+ it "decodes the number of nibbles specified by the count modifier" do
+ [ ["\xab", "H0", [""]],
+ ["\x00", "H1", ["0"]],
+ ["\x01", "H2", ["01"]],
+ ["\x01\x23", "H3", ["012"]],
+ ["\x01\x23", "H4", ["0123"]],
+ ["\x01\x23\x45", "H5", ["01234"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "decodes all the nibbles when passed the '*' modifier" do
+ [ ["", [""]],
+ ["\xab", ["ab"]],
+ ["\xca\xfe", ["cafe"]],
+ ].should be_computed_by(:unpack, "H*")
+ end
+
+ it "adds an empty string for each element requested beyond the end of the String" do
+ [ ["", ["", "", ""]],
+ ["\x01", ["0", "", ""]],
+ ["\x01\x80", ["0", "8", ""]]
+ ].should be_computed_by(:unpack, "HHH")
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "\x01\x10".unpack("H\x00H").should == ["0", "1"]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "\x01\x10".unpack("H\x00H")
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "\x01\x10".unpack("H H").should == ["0", "1"]
+ end
+
+ it "should make strings with US_ASCII encoding" do
+ "\x01".unpack("H")[0].encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "String#unpack with format 'h'" do
+ it_behaves_like :string_unpack_basic, 'h'
+ it_behaves_like :string_unpack_no_platform, 'h'
+ it_behaves_like :string_unpack_taint, 'h'
+
+ it "decodes one nibble from each byte for each format character starting with the least significant bit" do
+ [ ["\x8f", "h", ["f"]],
+ ["\xf8\x0f", "hh", ["8", "f"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "decodes only the number of nibbles in the string when passed a count" do
+ "\xac\xef".unpack("h5").should == ["cafe"]
+ end
+
+ it "decodes multiple differing nibble counts from a single string" do
+ array = "\xaa\x55\xaa\xd4\xc3\x6b\xd7\xaa\xd7".unpack("hh2h3h4h5")
+ array.should == ["a", "55", "aa4", "3cb6", "7daa7"]
+ end
+
+ it "decodes a directive with a '*' modifier after a directive with a count modifier" do
+ "\xba\x55\xaa\xd4\xc3\x6b".unpack("h3h*").should == ["ab5", "aa4d3cb6"]
+ end
+
+ it "decodes a directive with a count modifier after a directive with a '*' modifier" do
+ "\xba\x55\xaa\xd4\xc3\x6b".unpack("h*h3").should == ["ab55aa4d3cb6", ""]
+ end
+
+ it "decodes the number of nibbles specified by the count modifier" do
+ [ ["\xab", "h0", [""]],
+ ["\x00", "h1", ["0"]],
+ ["\x01", "h2", ["10"]],
+ ["\x01\x23", "h3", ["103"]],
+ ["\x01\x23", "h4", ["1032"]],
+ ["\x01\x23\x45", "h5", ["10325"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "decodes all the nibbles when passed the '*' modifier" do
+ [ ["", [""]],
+ ["\xab", ["ba"]],
+ ["\xac\xef", ["cafe"]],
+ ].should be_computed_by(:unpack, "h*")
+ end
+
+ it "adds an empty string for each element requested beyond the end of the String" do
+ [ ["", ["", "", ""]],
+ ["\x01", ["1", "", ""]],
+ ["\x01\x80", ["1", "0", ""]]
+ ].should be_computed_by(:unpack, "hhh")
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "\x01\x10".unpack("h\x00h").should == ["1", "0"]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "\x01\x10".unpack("h\x00h")
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "\x01\x10".unpack("h h").should == ["1", "0"]
+ end
+
+ it "should make strings with US_ASCII encoding" do
+ "\x01".unpack("h")[0].encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/string/unpack/i_spec.rb b/spec/ruby/core/string/unpack/i_spec.rb
new file mode 100644
index 0000000000..b4bbba1923
--- /dev/null
+++ b/spec/ruby/core/string/unpack/i_spec.rb
@@ -0,0 +1,152 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/integer'
+
+describe "String#unpack with format 'I'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_32bit_le, 'I<'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'I<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'I<_'
+ it_behaves_like :string_unpack_32bit_le, 'I_<'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'I<_'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'I_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'I<!'
+ it_behaves_like :string_unpack_32bit_le, 'I!<'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'I<!'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'I!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_32bit_be, 'I>'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'I>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'I>_'
+ it_behaves_like :string_unpack_32bit_be, 'I_>'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'I>_'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'I_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'I>!'
+ it_behaves_like :string_unpack_32bit_be, 'I!>'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'I>!'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'I!>'
+ end
+end
+
+describe "String#unpack with format 'i'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_32bit_le, 'i<'
+ it_behaves_like :string_unpack_32bit_le_signed, 'i<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'i<_'
+ it_behaves_like :string_unpack_32bit_le, 'i_<'
+ it_behaves_like :string_unpack_32bit_le_signed, 'i<_'
+ it_behaves_like :string_unpack_32bit_le_signed, 'i_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'i<!'
+ it_behaves_like :string_unpack_32bit_le, 'i!<'
+ it_behaves_like :string_unpack_32bit_le_signed, 'i<!'
+ it_behaves_like :string_unpack_32bit_le_signed, 'i!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_32bit_be, 'i>'
+ it_behaves_like :string_unpack_32bit_be_signed, 'i>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'i>_'
+ it_behaves_like :string_unpack_32bit_be, 'i_>'
+ it_behaves_like :string_unpack_32bit_be_signed, 'i>_'
+ it_behaves_like :string_unpack_32bit_be_signed, 'i_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'i>!'
+ it_behaves_like :string_unpack_32bit_be, 'i!>'
+ it_behaves_like :string_unpack_32bit_be_signed, 'i>!'
+ it_behaves_like :string_unpack_32bit_be_signed, 'i!>'
+ end
+end
+
+little_endian do
+ describe "String#unpack with format 'I'" do
+ it_behaves_like :string_unpack_basic, 'I'
+ it_behaves_like :string_unpack_32bit_le, 'I'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'I'
+ end
+
+ describe "String#unpack with format 'I' with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'I_'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'I_'
+ end
+
+ describe "String#unpack with format 'I' with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'I!'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'I!'
+ end
+
+ describe "String#unpack with format 'i'" do
+ it_behaves_like :string_unpack_basic, 'i'
+ it_behaves_like :string_unpack_32bit_le, 'i'
+ it_behaves_like :string_unpack_32bit_le_signed, 'i'
+ end
+
+ describe "String#unpack with format 'i' with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'i_'
+ it_behaves_like :string_unpack_32bit_le_signed, 'i_'
+ end
+
+ describe "String#unpack with format 'i' with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'i!'
+ it_behaves_like :string_unpack_32bit_le_signed, 'i!'
+ end
+end
+
+big_endian do
+ describe "String#unpack with format 'I'" do
+ it_behaves_like :string_unpack_basic, 'I'
+ it_behaves_like :string_unpack_32bit_be, 'I'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'I'
+ end
+
+ describe "String#unpack with format 'I' with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'I_'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'I_'
+ end
+
+ describe "String#unpack with format 'I' with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'I!'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'I!'
+ end
+
+ describe "String#unpack with format 'i'" do
+ it_behaves_like :string_unpack_basic, 'i'
+ it_behaves_like :string_unpack_32bit_be, 'i'
+ it_behaves_like :string_unpack_32bit_be_signed, 'i'
+ end
+
+ describe "String#unpack with format 'i' with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'i_'
+ it_behaves_like :string_unpack_32bit_be_signed, 'i_'
+ end
+
+ describe "String#unpack with format 'i' with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'i!'
+ it_behaves_like :string_unpack_32bit_be_signed, 'i!'
+ end
+end
diff --git a/spec/ruby/core/string/unpack/j_spec.rb b/spec/ruby/core/string/unpack/j_spec.rb
new file mode 100644
index 0000000000..3c2baad642
--- /dev/null
+++ b/spec/ruby/core/string/unpack/j_spec.rb
@@ -0,0 +1,272 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/integer'
+
+platform_is pointer_size: 64 do
+ little_endian do
+ describe "String#unpack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :string_unpack_64bit_le, 'J_'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :string_unpack_64bit_le, 'J!'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'J!'
+ end
+ end
+
+ describe "String#unpack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :string_unpack_64bit_le, 'j_'
+ it_behaves_like :string_unpack_64bit_le_signed, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :string_unpack_64bit_le, 'j!'
+ it_behaves_like :string_unpack_64bit_le_signed, 'j!'
+ end
+ end
+ end
+
+ big_endian do
+ describe "String#unpack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :string_unpack_64bit_be, 'J_'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :string_unpack_64bit_be, 'J!'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'J!'
+ end
+ end
+
+ describe "String#unpack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :string_unpack_64bit_be, 'j_'
+ it_behaves_like :string_unpack_64bit_be_signed, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :string_unpack_64bit_be, 'j!'
+ it_behaves_like :string_unpack_64bit_be_signed, 'j!'
+ end
+ end
+ end
+
+ describe "String#unpack with format 'J'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_64bit_le, 'J<'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'J<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_64bit_be, 'J>'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'J>'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_64bit_le, 'J<_'
+ it_behaves_like :string_unpack_64bit_le, 'J_<'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'J<_'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'J_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_64bit_le, 'J<!'
+ it_behaves_like :string_unpack_64bit_le, 'J!<'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'J<!'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'J!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_64bit_be, 'J>_'
+ it_behaves_like :string_unpack_64bit_be, 'J_>'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'J>_'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'J_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_64bit_be, 'J>!'
+ it_behaves_like :string_unpack_64bit_be, 'J!>'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'J>!'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'J!>'
+ end
+ end
+
+ describe "String#unpack with format 'j'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_64bit_le, 'j<'
+ it_behaves_like :string_unpack_64bit_le_signed, 'j<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_64bit_be, 'j>'
+ it_behaves_like :string_unpack_64bit_be_signed, 'j>'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_64bit_le, 'j<_'
+ it_behaves_like :string_unpack_64bit_le, 'j_<'
+ it_behaves_like :string_unpack_64bit_le_signed, 'j<_'
+ it_behaves_like :string_unpack_64bit_le_signed, 'j_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_64bit_le, 'j<!'
+ it_behaves_like :string_unpack_64bit_le, 'j!<'
+ it_behaves_like :string_unpack_64bit_le_signed, 'j<!'
+ it_behaves_like :string_unpack_64bit_le_signed, 'j!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_64bit_be, 'j>_'
+ it_behaves_like :string_unpack_64bit_be, 'j_>'
+ it_behaves_like :string_unpack_64bit_be_signed, 'j>_'
+ it_behaves_like :string_unpack_64bit_be_signed, 'j_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_64bit_be, 'j>!'
+ it_behaves_like :string_unpack_64bit_be, 'j!>'
+ it_behaves_like :string_unpack_64bit_be_signed, 'j>!'
+ it_behaves_like :string_unpack_64bit_be_signed, 'j!>'
+ end
+ end
+end
+
+platform_is pointer_size: 32 do
+ little_endian do
+ describe "String#unpack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'J_'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'J!'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'J!'
+ end
+ end
+
+ describe "String#unpack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'j_'
+ it_behaves_like :string_unpack_32bit_le_signed, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'j!'
+ it_behaves_like :string_unpack_32bit_le_signed, 'j!'
+ end
+ end
+ end
+
+ big_endian do
+ describe "String#unpack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'J_'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'J!'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'J!'
+ end
+ end
+
+ describe "String#unpack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'j_'
+ it_behaves_like :string_unpack_32bit_be_signed, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'j!'
+ it_behaves_like :string_unpack_32bit_be_signed, 'j!'
+ end
+ end
+ end
+
+ describe "String#unpack with format 'J'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_32bit_le, 'J<'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'J<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_32bit_be, 'J>'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'J>'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'J<_'
+ it_behaves_like :string_unpack_32bit_le, 'J_<'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'J<_'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'J_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'J<!'
+ it_behaves_like :string_unpack_32bit_le, 'J!<'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'J<!'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'J!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'J>_'
+ it_behaves_like :string_unpack_32bit_be, 'J_>'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'J>_'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'J_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'J>!'
+ it_behaves_like :string_unpack_32bit_be, 'J!>'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'J>!'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'J!>'
+ end
+ end
+
+ describe "String#unpack with format 'j'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_32bit_le, 'j<'
+ it_behaves_like :string_unpack_32bit_le_signed, 'j<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_32bit_be, 'j>'
+ it_behaves_like :string_unpack_32bit_be_signed, 'j>'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'j<_'
+ it_behaves_like :string_unpack_32bit_le, 'j_<'
+ it_behaves_like :string_unpack_32bit_le_signed, 'j<_'
+ it_behaves_like :string_unpack_32bit_le_signed, 'j_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'j<!'
+ it_behaves_like :string_unpack_32bit_le, 'j!<'
+ it_behaves_like :string_unpack_32bit_le_signed, 'j<!'
+ it_behaves_like :string_unpack_32bit_le_signed, 'j!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'j>_'
+ it_behaves_like :string_unpack_32bit_be, 'j_>'
+ it_behaves_like :string_unpack_32bit_be_signed, 'j>_'
+ it_behaves_like :string_unpack_32bit_be_signed, 'j_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'j>!'
+ it_behaves_like :string_unpack_32bit_be, 'j!>'
+ it_behaves_like :string_unpack_32bit_be_signed, 'j>!'
+ it_behaves_like :string_unpack_32bit_be_signed, 'j!>'
+ end
+ end
+end
diff --git a/spec/ruby/core/string/unpack/l_spec.rb b/spec/ruby/core/string/unpack/l_spec.rb
new file mode 100644
index 0000000000..18bb68b8d0
--- /dev/null
+++ b/spec/ruby/core/string/unpack/l_spec.rb
@@ -0,0 +1,265 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/integer'
+
+describe "String#unpack with format 'L'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_32bit_le, 'L<'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'L<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_32bit_be, 'L>'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'L>'
+ end
+
+ platform_is wordsize: 32 do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'L<_'
+ it_behaves_like :string_unpack_32bit_le, 'L_<'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'L<_'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'L_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'L<!'
+ it_behaves_like :string_unpack_32bit_le, 'L!<'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'L<!'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'L!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'L>_'
+ it_behaves_like :string_unpack_32bit_be, 'L_>'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'L>_'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'L_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'L>!'
+ it_behaves_like :string_unpack_32bit_be, 'L!>'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'L>!'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'L!>'
+ end
+ end
+
+ platform_is wordsize: 64 do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_64bit_le, 'L<_'
+ it_behaves_like :string_unpack_64bit_le, 'L_<'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'L<_'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'L_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_64bit_le, 'L<!'
+ it_behaves_like :string_unpack_64bit_le, 'L!<'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'L<!'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'L!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_64bit_be, 'L>_'
+ it_behaves_like :string_unpack_64bit_be, 'L_>'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'L>_'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'L_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_64bit_be, 'L>!'
+ it_behaves_like :string_unpack_64bit_be, 'L!>'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'L>!'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'L!>'
+ end
+ end
+end
+
+describe "String#unpack with format 'l'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_32bit_le, 'l<'
+ it_behaves_like :string_unpack_32bit_le_signed, 'l<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_32bit_be, 'l>'
+ it_behaves_like :string_unpack_32bit_be_signed, 'l>'
+ end
+
+ platform_is wordsize: 32 do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'l<_'
+ it_behaves_like :string_unpack_32bit_le, 'l_<'
+ it_behaves_like :string_unpack_32bit_le_signed, 'l<_'
+ it_behaves_like :string_unpack_32bit_le_signed, 'l_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'l<!'
+ it_behaves_like :string_unpack_32bit_le, 'l!<'
+ it_behaves_like :string_unpack_32bit_le_signed, 'l<!'
+ it_behaves_like :string_unpack_32bit_le_signed, 'l!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'l>_'
+ it_behaves_like :string_unpack_32bit_be, 'l_>'
+ it_behaves_like :string_unpack_32bit_be_signed, 'l>_'
+ it_behaves_like :string_unpack_32bit_be_signed, 'l_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'l>!'
+ it_behaves_like :string_unpack_32bit_be, 'l!>'
+ it_behaves_like :string_unpack_32bit_be_signed, 'l>!'
+ it_behaves_like :string_unpack_32bit_be_signed, 'l!>'
+ end
+ end
+
+ platform_is wordsize: 64 do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_64bit_le, 'l<_'
+ it_behaves_like :string_unpack_64bit_le, 'l_<'
+ it_behaves_like :string_unpack_64bit_le_signed, 'l<_'
+ it_behaves_like :string_unpack_64bit_le_signed, 'l_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_64bit_le, 'l<!'
+ it_behaves_like :string_unpack_64bit_le, 'l!<'
+ it_behaves_like :string_unpack_64bit_le_signed, 'l<!'
+ it_behaves_like :string_unpack_64bit_le_signed, 'l!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_64bit_be, 'l>_'
+ it_behaves_like :string_unpack_64bit_be, 'l_>'
+ it_behaves_like :string_unpack_64bit_be_signed, 'l>_'
+ it_behaves_like :string_unpack_64bit_be_signed, 'l_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_64bit_be, 'l>!'
+ it_behaves_like :string_unpack_64bit_be, 'l!>'
+ it_behaves_like :string_unpack_64bit_be_signed, 'l>!'
+ it_behaves_like :string_unpack_64bit_be_signed, 'l!>'
+ end
+ end
+end
+
+little_endian do
+ describe "String#unpack with format 'L'" do
+ it_behaves_like :string_unpack_basic, 'L'
+ it_behaves_like :string_unpack_32bit_le, 'L'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'L'
+ end
+
+ describe "String#unpack with format 'l'" do
+ it_behaves_like :string_unpack_basic, 'l'
+ it_behaves_like :string_unpack_32bit_le, 'l'
+ it_behaves_like :string_unpack_32bit_le_signed, 'l'
+ end
+
+ platform_is wordsize: 32 do
+ describe "String#unpack with format 'L' with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'L_'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'L_'
+ end
+
+ describe "String#unpack with format 'L' with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'L!'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'L!'
+ end
+
+ describe "String#unpack with format 'l' with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_le, 'l_'
+ it_behaves_like :string_unpack_32bit_le_signed, 'l'
+ end
+
+ describe "String#unpack with format 'l' with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_le, 'l!'
+ it_behaves_like :string_unpack_32bit_le_signed, 'l'
+ end
+ end
+
+ platform_is wordsize: 64 do
+ describe "String#unpack with format 'L' with modifier '_'" do
+ it_behaves_like :string_unpack_64bit_le, 'L_'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'L_'
+ end
+
+ describe "String#unpack with format 'L' with modifier '!'" do
+ it_behaves_like :string_unpack_64bit_le, 'L!'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'L!'
+ end
+
+ describe "String#unpack with format 'l' with modifier '_'" do
+ it_behaves_like :string_unpack_64bit_le, 'l_'
+ it_behaves_like :string_unpack_64bit_le_signed, 'l_'
+ end
+
+ describe "String#unpack with format 'l' with modifier '!'" do
+ it_behaves_like :string_unpack_64bit_le, 'l!'
+ it_behaves_like :string_unpack_64bit_le_signed, 'l!'
+ end
+ end
+end
+
+big_endian do
+ describe "String#unpack with format 'L'" do
+ it_behaves_like :string_unpack_basic, 'L'
+ it_behaves_like :string_unpack_32bit_be, 'L'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'L'
+ end
+
+ describe "String#unpack with format 'l'" do
+ it_behaves_like :string_unpack_basic, 'l'
+ it_behaves_like :string_unpack_32bit_be, 'l'
+ it_behaves_like :string_unpack_32bit_be_signed, 'l'
+ end
+
+ platform_is wordsize: 32 do
+ describe "String#unpack with format 'L' with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'L_'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'L_'
+ end
+
+ describe "String#unpack with format 'L' with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'L!'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'L!'
+ end
+
+ describe "String#unpack with format 'l' with modifier '_'" do
+ it_behaves_like :string_unpack_32bit_be, 'l_'
+ it_behaves_like :string_unpack_32bit_be_signed, 'l'
+ end
+
+ describe "String#unpack with format 'l' with modifier '!'" do
+ it_behaves_like :string_unpack_32bit_be, 'l!'
+ it_behaves_like :string_unpack_32bit_be_signed, 'l'
+ end
+ end
+
+ platform_is wordsize: 64 do
+ describe "String#unpack with format 'L' with modifier '_'" do
+ it_behaves_like :string_unpack_64bit_be, 'L_'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'L_'
+ end
+
+ describe "String#unpack with format 'L' with modifier '!'" do
+ it_behaves_like :string_unpack_64bit_be, 'L!'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'L!'
+ end
+
+ describe "String#unpack with format 'l' with modifier '_'" do
+ it_behaves_like :string_unpack_64bit_be, 'l_'
+ it_behaves_like :string_unpack_64bit_be_signed, 'l_'
+ end
+
+ describe "String#unpack with format 'l' with modifier '!'" do
+ it_behaves_like :string_unpack_64bit_be, 'l!'
+ it_behaves_like :string_unpack_64bit_be_signed, 'l!'
+ end
+ end
+
+end
diff --git a/spec/ruby/core/string/unpack/m_spec.rb b/spec/ruby/core/string/unpack/m_spec.rb
new file mode 100644
index 0000000000..c551c755d1
--- /dev/null
+++ b/spec/ruby/core/string/unpack/m_spec.rb
@@ -0,0 +1,192 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/taint'
+
+describe "String#unpack with format 'M'" do
+ it_behaves_like :string_unpack_basic, 'M'
+ it_behaves_like :string_unpack_no_platform, 'M'
+ it_behaves_like :string_unpack_taint, 'M'
+
+ it "decodes an empty string" do
+ "".unpack("M").should == [""]
+ end
+
+ it "decodes the complete string ignoring newlines when given a single directive" do
+ "a=\nb=\nc=\n".unpack("M").should == ["abc"]
+ end
+
+ it "appends empty string to the array for directives exceeding the input size" do
+ "a=\nb=\nc=\n".unpack("MMM").should == ["abc", "", ""]
+ end
+
+ it "ignores the count or '*' modifier and decodes the entire string" do
+ [ ["a=\nb=\nc=\n", "M238", ["abc"]],
+ ["a=\nb=\nc=\n", "M*", ["abc"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "decodes the '=' character" do
+ "=3D=\n".unpack("M").should == ["="]
+ end
+
+ it "decodes an embedded space character" do
+ "a b=\n".unpack("M").should == ["a b"]
+ end
+
+ it "decodes a space at the end of the pre-encoded string" do
+ "a =\n".unpack("M").should == ["a "]
+ end
+
+ it "decodes an embedded tab character" do
+ "a\tb=\n".unpack("M").should == ["a\tb"]
+ end
+
+ it "decodes a tab character at the end of the pre-encoded string" do
+ "a\t=\n".unpack("M").should == ["a\t"]
+ end
+
+ it "decodes an embedded newline" do
+ "a\nb=\n".unpack("M").should == ["a\nb"]
+ end
+
+ it "decodes pre-encoded byte values 33..60" do
+ [ ["!\"\#$%&'()*+,-./=\n", ["!\"\#$%&'()*+,-./"]],
+ ["0123456789=\n", ["0123456789"]],
+ [":;<=\n", [":;<"]]
+ ].should be_computed_by(:unpack, "M")
+ end
+
+ it "decodes pre-encoded byte values 62..126" do
+ [ [">?@=\n", [">?@"]],
+ ["ABCDEFGHIJKLMNOPQRSTUVWXYZ=\n", ["ABCDEFGHIJKLMNOPQRSTUVWXYZ"]],
+ ["[\\]^_`=\n", ["[\\]^_`"]],
+ ["abcdefghijklmnopqrstuvwxyz=\n", ["abcdefghijklmnopqrstuvwxyz"]],
+ ["{|}~=\n", ["{|}~"]]
+ ].should be_computed_by(:unpack, "M")
+ end
+
+ it "decodes pre-encoded byte values 0..31 except tab and newline" do
+ [ ["=00=01=02=03=04=05=06=\n", ["\x00\x01\x02\x03\x04\x05\x06"]],
+ ["=07=08=0B=0C=0D=\n", ["\a\b\v\f\r"]],
+ ["=0E=0F=10=11=12=13=14=\n", ["\x0e\x0f\x10\x11\x12\x13\x14"]],
+ ["=15=16=17=18=19=1A=\n", ["\x15\x16\x17\x18\x19\x1a"]],
+ ["=1B=\n", ["\e"]],
+ ["=1C=1D=1E=1F=\n", ["\x1c\x1d\x1e\x1f"]]
+ ].should be_computed_by(:unpack, "M")
+ end
+
+ it "decodes pre-encoded byte values 127..255" do
+ [ ["=7F=80=81=82=83=84=85=86=\n", ["\x7f\x80\x81\x82\x83\x84\x85\x86"]],
+ ["=87=88=89=8A=8B=8C=8D=8E=\n", ["\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"]],
+ ["=8F=90=91=92=93=94=95=96=\n", ["\x8f\x90\x91\x92\x93\x94\x95\x96"]],
+ ["=97=98=99=9A=9B=9C=9D=9E=\n", ["\x97\x98\x99\x9a\x9b\x9c\x9d\x9e"]],
+ ["=9F=A0=A1=A2=A3=A4=A5=A6=\n", ["\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6"]],
+ ["=A7=A8=A9=AA=AB=AC=AD=AE=\n", ["\xa7\xa8\xa9\xaa\xab\xac\xad\xae"]],
+ ["=AF=B0=B1=B2=B3=B4=B5=B6=\n", ["\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6"]],
+ ["=B7=B8=B9=BA=BB=BC=BD=BE=\n", ["\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe"]],
+ ["=BF=C0=C1=C2=C3=C4=C5=C6=\n", ["\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6"]],
+ ["=C7=C8=C9=CA=CB=CC=CD=CE=\n", ["\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce"]],
+ ["=CF=D0=D1=D2=D3=D4=D5=D6=\n", ["\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6"]],
+ ["=D7=D8=D9=DA=DB=DC=DD=DE=\n", ["\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde"]],
+ ["=DF=E0=E1=E2=E3=E4=E5=E6=\n", ["\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6"]],
+ ["=E7=E8=E9=EA=EB=EC=ED=EE=\n", ["\xe7\xe8\xe9\xea\xeb\xec\xed\xee"]],
+ ["=EF=F0=F1=F2=F3=F4=F5=F6=\n", ["\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"]],
+ ["=F7=F8=F9=FA=FB=FC=FD=FE=\n", ["\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe"]],
+ ["=FF=\n", ["\xff"]]
+ ].should be_computed_by(:unpack, "M")
+ end
+
+ it "unpacks incomplete escape sequences as literal characters" do
+ "foo=".unpack("M").should == ["foo="]
+ "foo=4".unpack("M").should == ["foo=4"]
+ end
+end
+
+describe "String#unpack with format 'm'" do
+ it_behaves_like :string_unpack_basic, 'm'
+ it_behaves_like :string_unpack_no_platform, 'm'
+ it_behaves_like :string_unpack_taint, 'm'
+
+ it "decodes an empty string" do
+ "".unpack("m").should == [""]
+ end
+
+ it "decodes the complete string ignoring newlines when given a single directive" do
+ "YWJj\nREVG\n".unpack("m").should == ["abcDEF"]
+ end
+
+ it "ignores the count or '*' modifier and decodes the entire string" do
+ [ ["YWJj\nREVG\n", "m238", ["abcDEF"]],
+ ["YWJj\nREVG\n", "m*", ["abcDEF"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "appends empty string to the array for directives exceeding the input size" do
+ "YWJj\nREVG\n".unpack("mmm").should == ["abcDEF", "", ""]
+ end
+
+ it "decodes all pre-encoded ascii byte values" do
+ [ ["AAECAwQFBg==\n", ["\x00\x01\x02\x03\x04\x05\x06"]],
+ ["BwgJCgsMDQ==\n", ["\a\b\t\n\v\f\r"]],
+ ["Dg8QERITFBUW\n", ["\x0E\x0F\x10\x11\x12\x13\x14\x15\x16"]],
+ ["FxgZGhscHR4f\n", ["\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f"]],
+ ["ISIjJCUmJygpKissLS4v\n", ["!\"\#$%&'()*+,-./"]],
+ ["MDEyMzQ1Njc4OQ==\n", ["0123456789"]],
+ ["Ojs8PT4/QA==\n", [":;<=>?@"]],
+ ["QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=\n", ["ABCDEFGHIJKLMNOPQRSTUVWXYZ"]],
+ ["W1xdXl9g\n", ["[\\]^_`"]],
+ ["YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=\n", ["abcdefghijklmnopqrstuvwxyz"]],
+ ["e3x9fg==\n", ["{|}~"]],
+ ["f8KAwoHCgsKD\n", ["\x7f\xc2\x80\xc2\x81\xc2\x82\xc2\x83"]],
+ ["woTChcKGwofC\n", ["\xc2\x84\xc2\x85\xc2\x86\xc2\x87\xc2"]],
+ ["iMKJworCi8KM\n", ["\x88\xc2\x89\xc2\x8a\xc2\x8b\xc2\x8c"]],
+ ["wo3CjsKPwpDC\n", ["\xc2\x8d\xc2\x8e\xc2\x8f\xc2\x90\xc2"]],
+ ["kcKSwpPClMKV\n", ["\x91\xc2\x92\xc2\x93\xc2\x94\xc2\x95"]],
+ ["wpbCl8KYwpnC\n", ["\xc2\x96\xc2\x97\xc2\x98\xc2\x99\xc2"]],
+ ["msKbwpzCncKe\n", ["\x9a\xc2\x9b\xc2\x9c\xc2\x9d\xc2\x9e"]],
+ ["wp/CoMKhwqLC\n", ["\xc2\x9f\xc2\xa0\xc2\xa1\xc2\xa2\xc2"]],
+ ["o8KkwqXCpsKn\n", ["\xa3\xc2\xa4\xc2\xa5\xc2\xa6\xc2\xa7"]],
+ ["wqjCqcKqwqvC\n", ["\xc2\xa8\xc2\xa9\xc2\xaa\xc2\xab\xc2"]],
+ ["rMKtwq7Cr8Kw\n", ["\xac\xc2\xad\xc2\xae\xc2\xaf\xc2\xb0"]],
+ ["wrHCssKzwrTC\n", ["\xc2\xb1\xc2\xb2\xc2\xb3\xc2\xb4\xc2"]],
+ ["tcK2wrfCuMK5\n", ["\xb5\xc2\xb6\xc2\xb7\xc2\xb8\xc2\xb9"]],
+ ["wrrCu8K8wr3C\n", ["\xc2\xba\xc2\xbb\xc2\xbc\xc2\xbd\xc2"]],
+ ["vsK/w4DDgcOC\n", ["\xbe\xc2\xbf\xc3\x80\xc3\x81\xc3\x82"]],
+ ["w4PDhMOFw4bD\n", ["\xc3\x83\xc3\x84\xc3\x85\xc3\x86\xc3"]],
+ ["h8OIw4nDisOL\n", ["\x87\xc3\x88\xc3\x89\xc3\x8a\xc3\x8b"]],
+ ["w4zDjcOOw4/D\n", ["\xc3\x8c\xc3\x8d\xc3\x8e\xc3\x8f\xc3"]],
+ ["kMORw5LDk8OU\n", ["\x90\xc3\x91\xc3\x92\xc3\x93\xc3\x94"]],
+ ["w5XDlsOXw5jD\n", ["\xc3\x95\xc3\x96\xc3\x97\xc3\x98\xc3"]],
+ ["mcOaw5vDnMOd\n", ["\x99\xc3\x9a\xc3\x9b\xc3\x9c\xc3\x9d"]],
+ ["w57Dn8Ogw6HD\n", ["\xc3\x9e\xc3\x9f\xc3\xa0\xc3\xa1\xc3"]],
+ ["osOjw6TDpcOm\n", ["\xa2\xc3\xa3\xc3\xa4\xc3\xa5\xc3\xa6"]],
+ ["w6fDqMOpw6rD\n", ["\xc3\xa7\xc3\xa8\xc3\xa9\xc3\xaa\xc3"]],
+ ["q8Osw63DrsOv\n", ["\xab\xc3\xac\xc3\xad\xc3\xae\xc3\xaf"]],
+ ["w7DDscOyw7PD\n", ["\xc3\xb0\xc3\xb1\xc3\xb2\xc3\xb3\xc3"]],
+ ["tMO1w7bDt8O4\n", ["\xb4\xc3\xb5\xc3\xb6\xc3\xb7\xc3\xb8"]],
+ ["w7nDusO7w7zD\n", ["\xc3\xb9\xc3\xba\xc3\xbb\xc3\xbc\xc3"]],
+ ["vcO+w78=\n", ["\xbd\xc3\xbe\xc3\xbf"]]
+ ].should be_computed_by(:unpack, "m")
+ end
+
+ it "produces binary strings" do
+ "".unpack("m").first.encoding.should == Encoding::BINARY
+ "Ojs8PT4/QA==\n".unpack("m").first.encoding.should == Encoding::BINARY
+ end
+
+ it "does not raise an error for an invalid base64 character" do
+ "dGV%zdA==".unpack("m").should == ["test"]
+ end
+
+ describe "when given count 0" do
+ it "decodes base64" do
+ "dGVzdA==".unpack("m0").should == ["test"]
+ end
+
+ it "raises an ArgumentError for an invalid base64 character" do
+ -> { "dGV%zdA==".unpack("m0") }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/string/unpack/n_spec.rb b/spec/ruby/core/string/unpack/n_spec.rb
new file mode 100644
index 0000000000..09173f4fcb
--- /dev/null
+++ b/spec/ruby/core/string/unpack/n_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/integer'
+
+describe "String#unpack with format 'N'" do
+ it_behaves_like :string_unpack_basic, 'N'
+ it_behaves_like :string_unpack_32bit_be, 'N'
+ it_behaves_like :string_unpack_32bit_be_unsigned, 'N'
+ it_behaves_like :string_unpack_no_platform, 'N'
+end
+
+describe "String#unpack with format 'n'" do
+ it_behaves_like :string_unpack_basic, 'n'
+ it_behaves_like :string_unpack_16bit_be, 'n'
+ it_behaves_like :string_unpack_16bit_be_unsigned, 'n'
+ it_behaves_like :string_unpack_no_platform, 'n'
+end
diff --git a/spec/ruby/core/string/unpack/p_spec.rb b/spec/ruby/core/string/unpack/p_spec.rb
new file mode 100644
index 0000000000..cd48c0523d
--- /dev/null
+++ b/spec/ruby/core/string/unpack/p_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/taint'
+
+describe "String#unpack with format 'P'" do
+ it_behaves_like :string_unpack_basic, 'P'
+ it_behaves_like :string_unpack_taint, 'P'
+
+ it "round-trips a string through pack and unpack" do
+ ["hello"].pack("P").unpack("P5").should == ["hello"]
+ end
+
+ it "cannot unpack a string except from the same object that created it, or a duplicate of it" do
+ packed = ["hello"].pack("P")
+ packed.unpack("P5").should == ["hello"]
+ packed.dup.unpack("P5").should == ["hello"]
+ -> { packed.to_sym.to_s.unpack("P5") }.should raise_error(ArgumentError, /no associated pointer/)
+ end
+
+ it "reads as many characters as specified" do
+ ["hello"].pack("P").unpack("P1").should == ["h"]
+ end
+
+ it "reads only as far as a NUL character" do
+ ["hello"].pack("P").unpack("P10").should == ["hello"]
+ end
+end
+
+describe "String#unpack with format 'p'" do
+ it_behaves_like :string_unpack_basic, 'p'
+ it_behaves_like :string_unpack_taint, 'p'
+
+ it "round-trips a string through pack and unpack" do
+ ["hello"].pack("p").unpack("p").should == ["hello"]
+ end
+
+ it "cannot unpack a string except from the same object that created it, or a duplicate of it" do
+ packed = ["hello"].pack("p")
+ packed.unpack("p").should == ["hello"]
+ packed.dup.unpack("p").should == ["hello"]
+ -> { packed.to_sym.to_s.unpack("p") }.should raise_error(ArgumentError, /no associated pointer/)
+ end
+end
diff --git a/spec/ruby/core/string/unpack/percent_spec.rb b/spec/ruby/core/string/unpack/percent_spec.rb
new file mode 100644
index 0000000000..0e27663195
--- /dev/null
+++ b/spec/ruby/core/string/unpack/percent_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+
+describe "String#unpack with format '%'" do
+ it "raises an Argument Error" do
+ -> { "abc".unpack("%") }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/unpack/q_spec.rb b/spec/ruby/core/string/unpack/q_spec.rb
new file mode 100644
index 0000000000..2f667d6c4d
--- /dev/null
+++ b/spec/ruby/core/string/unpack/q_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/integer'
+
+describe "String#unpack with format 'Q'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_64bit_le, 'Q<'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'Q<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_64bit_be, 'Q>'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'Q>'
+ end
+end
+
+describe "String#unpack with format 'q'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_64bit_le, 'q<'
+ it_behaves_like :string_unpack_64bit_le_signed, 'q<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_64bit_be, 'q>'
+ it_behaves_like :string_unpack_64bit_be_signed, 'q>'
+ end
+end
+
+describe "String#unpack with format 'Q'" do
+ it_behaves_like :string_unpack_basic, 'Q'
+end
+
+describe "String#unpack with format 'q'" do
+ it_behaves_like :string_unpack_basic, 'q'
+end
+
+little_endian do
+ describe "String#unpack with format 'Q'" do
+ it_behaves_like :string_unpack_64bit_le, 'Q'
+ it_behaves_like :string_unpack_64bit_le_extra, 'Q'
+ it_behaves_like :string_unpack_64bit_le_unsigned, 'Q'
+ end
+
+ describe "String#unpack with format 'q'" do
+ it_behaves_like :string_unpack_64bit_le, 'q'
+ it_behaves_like :string_unpack_64bit_le_extra, 'q'
+ it_behaves_like :string_unpack_64bit_le_signed, 'q'
+ end
+end
+
+big_endian do
+ describe "String#unpack with format 'Q'" do
+ it_behaves_like :string_unpack_64bit_be, 'Q'
+ it_behaves_like :string_unpack_64bit_be_extra, 'Q'
+ it_behaves_like :string_unpack_64bit_be_unsigned, 'Q'
+ end
+
+ describe "String#unpack with format 'q'" do
+ it_behaves_like :string_unpack_64bit_be, 'q'
+ it_behaves_like :string_unpack_64bit_be_extra, 'q'
+ it_behaves_like :string_unpack_64bit_be_signed, 'q'
+ end
+end
diff --git a/spec/ruby/core/string/unpack/s_spec.rb b/spec/ruby/core/string/unpack/s_spec.rb
new file mode 100644
index 0000000000..d331fd720e
--- /dev/null
+++ b/spec/ruby/core/string/unpack/s_spec.rb
@@ -0,0 +1,152 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/integer'
+
+describe "String#unpack with format 'S'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_16bit_le, 'S<'
+ it_behaves_like :string_unpack_16bit_le_unsigned, 'S<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_16bit_le, 'S<_'
+ it_behaves_like :string_unpack_16bit_le, 'S_<'
+ it_behaves_like :string_unpack_16bit_le_unsigned, 'S_<'
+ it_behaves_like :string_unpack_16bit_le_unsigned, 'S<_'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_16bit_le, 'S<!'
+ it_behaves_like :string_unpack_16bit_le, 'S!<'
+ it_behaves_like :string_unpack_16bit_le_unsigned, 'S!<'
+ it_behaves_like :string_unpack_16bit_le_unsigned, 'S<!'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_16bit_be, 'S>'
+ it_behaves_like :string_unpack_16bit_be_unsigned, 'S>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_16bit_be, 'S>_'
+ it_behaves_like :string_unpack_16bit_be, 'S_>'
+ it_behaves_like :string_unpack_16bit_be_unsigned, 'S>_'
+ it_behaves_like :string_unpack_16bit_be_unsigned, 'S_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_16bit_be, 'S>!'
+ it_behaves_like :string_unpack_16bit_be, 'S!>'
+ it_behaves_like :string_unpack_16bit_be_unsigned, 'S>!'
+ it_behaves_like :string_unpack_16bit_be_unsigned, 'S!>'
+ end
+end
+
+describe "String#unpack with format 's'" do
+ describe "with modifier '<'" do
+ it_behaves_like :string_unpack_16bit_le, 's<'
+ it_behaves_like :string_unpack_16bit_le_signed, 's<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :string_unpack_16bit_le, 's<_'
+ it_behaves_like :string_unpack_16bit_le, 's_<'
+ it_behaves_like :string_unpack_16bit_le_signed, 's<_'
+ it_behaves_like :string_unpack_16bit_le_signed, 's_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :string_unpack_16bit_le, 's<!'
+ it_behaves_like :string_unpack_16bit_le, 's!<'
+ it_behaves_like :string_unpack_16bit_le_signed, 's<!'
+ it_behaves_like :string_unpack_16bit_le_signed, 's!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :string_unpack_16bit_be, 's>'
+ it_behaves_like :string_unpack_16bit_be_signed, 's>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :string_unpack_16bit_be, 's>_'
+ it_behaves_like :string_unpack_16bit_be, 's_>'
+ it_behaves_like :string_unpack_16bit_be_signed, 's>_'
+ it_behaves_like :string_unpack_16bit_be_signed, 's_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :string_unpack_16bit_be, 's>!'
+ it_behaves_like :string_unpack_16bit_be, 's!>'
+ it_behaves_like :string_unpack_16bit_be_signed, 's>!'
+ it_behaves_like :string_unpack_16bit_be_signed, 's!>'
+ end
+end
+
+little_endian do
+ describe "String#unpack with format 'S'" do
+ it_behaves_like :string_unpack_basic, 'S'
+ it_behaves_like :string_unpack_16bit_le, 'S'
+ it_behaves_like :string_unpack_16bit_le_unsigned, 'S'
+ end
+
+ describe "String#unpack with format 'S' with modifier '_'" do
+ it_behaves_like :string_unpack_16bit_le, 'S_'
+ it_behaves_like :string_unpack_16bit_le_unsigned, 'S_'
+ end
+
+ describe "String#unpack with format 'S' with modifier '!'" do
+ it_behaves_like :string_unpack_16bit_le, 'S!'
+ it_behaves_like :string_unpack_16bit_le_unsigned, 'S!'
+ end
+
+ describe "String#unpack with format 's'" do
+ it_behaves_like :string_unpack_basic, 's'
+ it_behaves_like :string_unpack_16bit_le, 's'
+ it_behaves_like :string_unpack_16bit_le_signed, 's'
+ end
+
+ describe "String#unpack with format 's' with modifier '_'" do
+ it_behaves_like :string_unpack_16bit_le, 's_'
+ it_behaves_like :string_unpack_16bit_le_signed, 's_'
+ end
+
+ describe "String#unpack with format 's' with modifier '!'" do
+ it_behaves_like :string_unpack_16bit_le, 's!'
+ it_behaves_like :string_unpack_16bit_le_signed, 's!'
+ end
+end
+
+big_endian do
+ describe "String#unpack with format 'S'" do
+ it_behaves_like :string_unpack_basic, 'S'
+ it_behaves_like :string_unpack_16bit_be, 'S'
+ it_behaves_like :string_unpack_16bit_be_unsigned, 'S'
+ end
+
+ describe "String#unpack with format 'S' with modifier '_'" do
+ it_behaves_like :string_unpack_16bit_be, 'S_'
+ it_behaves_like :string_unpack_16bit_be_unsigned, 'S_'
+ end
+
+ describe "String#unpack with format 'S' with modifier '!'" do
+ it_behaves_like :string_unpack_16bit_be, 'S!'
+ it_behaves_like :string_unpack_16bit_be_unsigned, 'S!'
+ end
+
+ describe "String#unpack with format 's'" do
+ it_behaves_like :string_unpack_basic, 's'
+ it_behaves_like :string_unpack_16bit_be, 's'
+ it_behaves_like :string_unpack_16bit_be_signed, 's'
+ end
+
+ describe "String#unpack with format 's' with modifier '_'" do
+ it_behaves_like :string_unpack_16bit_be, 's_'
+ it_behaves_like :string_unpack_16bit_be_signed, 's_'
+ end
+
+ describe "String#unpack with format 's' with modifier '!'" do
+ it_behaves_like :string_unpack_16bit_be, 's!'
+ it_behaves_like :string_unpack_16bit_be_signed, 's!'
+ end
+end
diff --git a/spec/ruby/core/string/unpack/shared/basic.rb b/spec/ruby/core/string/unpack/shared/basic.rb
new file mode 100644
index 0000000000..bb5302edc5
--- /dev/null
+++ b/spec/ruby/core/string/unpack/shared/basic.rb
@@ -0,0 +1,21 @@
+describe :string_unpack_basic, shared: true do
+ it "ignores whitespace in the format string" do
+ "abc".unpack("a \t\n\v\f\r"+unpack_format).should be_an_instance_of(Array)
+ end
+
+ it "calls #to_str to coerce the directives string" do
+ d = mock("unpack directive")
+ d.should_receive(:to_str).and_return("a"+unpack_format)
+ "abc".unpack(d).should be_an_instance_of(Array)
+ end
+end
+
+describe :string_unpack_no_platform, shared: true do
+ it "raises an ArgumentError when the format modifier is '_'" do
+ -> { "abcdefgh".unpack(unpack_format("_")) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the format modifier is '!'" do
+ -> { "abcdefgh".unpack(unpack_format("!")) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/unpack/shared/float.rb b/spec/ruby/core/string/unpack/shared/float.rb
new file mode 100644
index 0000000000..ccddf94f99
--- /dev/null
+++ b/spec/ruby/core/string/unpack/shared/float.rb
@@ -0,0 +1,311 @@
+# -*- encoding: binary -*-
+
+describe :string_unpack_float_le, shared: true do
+ it "decodes one float for a single format character" do
+ "\x8f\xc2\xb5?".unpack(unpack_format).should == [1.4199999570846558]
+ end
+
+ it "decodes a negative float" do
+ "\xcd\xcc\x08\xc2".unpack(unpack_format).should == [-34.200000762939453]
+ end
+
+ it "decodes two floats for two format characters" do
+ array = "\x9a\x999@33\xb3?".unpack(unpack_format(nil, 2))
+ array.should == [2.9000000953674316, 1.399999976158142]
+ end
+
+ it "decodes the number of floats requested by the count modifier" do
+ array = "\x9a\x999@33\xb3?33\x03A".unpack(unpack_format(3))
+ array.should == [2.9000000953674316, 1.399999976158142, 8.199999809265137]
+ end
+
+ it "decodes the remaining floats when passed the '*' modifier" do
+ array = "\x9a\x999@33\xb3?33\x03A".unpack(unpack_format("*"))
+ array.should == [2.9000000953674316, 1.399999976158142, 8.199999809265137]
+ end
+
+ it "decodes the remaining floats when passed the '*' modifier after another directive" do
+ array = "\x9a\x99\xa9@33\x13A".unpack(unpack_format()+unpack_format('*'))
+ array.should == [5.300000190734863, 9.199999809265137]
+ end
+
+ it "does not decode a float when fewer bytes than a float remain and the '*' modifier is passed" do
+ [ ["\xff", []],
+ ["\xff\x00", []],
+ ["\xff\x00\xff", []]
+ ].should be_computed_by(:unpack, unpack_format("*"))
+ end
+
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["abc", [nil, nil, nil]],
+ ["\x8f\xc2\xb5?abc", [1.4199999570846558, nil, nil]],
+ ["\x9a\x999@33\xb3?abc", [2.9000000953674316, 1.399999976158142, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+
+ it "decodes positive Infinity" do
+ "\x00\x00\x80\x7f".unpack(unpack_format).should == [infinity_value]
+ end
+
+ it "decodes negative Infinity" do
+ "\x00\x00\x80\xff".unpack(unpack_format).should == [-infinity_value]
+ end
+
+ it "decodes NaN" do
+ # mumble mumble NaN mumble https://bugs.ruby-lang.org/issues/5884
+ [nan_value].pack(unpack_format).unpack(unpack_format).first.nan?.should be_true
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ array = "\x9a\x999@33\xb3?".unpack(unpack_format("\000", 2))
+ array.should == [2.9000000953674316, 1.399999976158142]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "\x9a\x999@33\xb3?".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ array = "\x9a\x999@33\xb3?".unpack(unpack_format(' ', 2))
+ array.should == [2.9000000953674316, 1.399999976158142]
+ end
+end
+
+describe :string_unpack_float_be, shared: true do
+ it "decodes one float for a single format character" do
+ "?\xb5\xc2\x8f".unpack(unpack_format).should == [1.4199999570846558]
+ end
+
+ it "decodes a negative float" do
+ "\xc2\x08\xcc\xcd".unpack(unpack_format).should == [-34.200000762939453]
+ end
+
+ it "decodes two floats for two format characters" do
+ array = "@9\x99\x9a?\xb333".unpack(unpack_format(nil, 2))
+ array.should == [2.9000000953674316, 1.399999976158142]
+ end
+
+ it "decodes the number of floats requested by the count modifier" do
+ array = "@9\x99\x9a?\xb333A\x0333".unpack(unpack_format(3))
+ array.should == [2.9000000953674316, 1.399999976158142, 8.199999809265137]
+ end
+
+ it "decodes the remaining floats when passed the '*' modifier" do
+ array = "@9\x99\x9a?\xb333A\x0333".unpack(unpack_format("*"))
+ array.should == [2.9000000953674316, 1.399999976158142, 8.199999809265137]
+ end
+
+ it "decodes the remaining floats when passed the '*' modifier after another directive" do
+ array = "@\xa9\x99\x9aA\x1333".unpack(unpack_format()+unpack_format('*'))
+ array.should == [5.300000190734863, 9.199999809265137]
+ end
+
+ it "does not decode a float when fewer bytes than a float remain and the '*' modifier is passed" do
+ [ ["\xff", []],
+ ["\xff\x00", []],
+ ["\xff\x00\xff", []]
+ ].should be_computed_by(:unpack, unpack_format("*"))
+ end
+
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["abc", [nil, nil, nil]],
+ ["?\xb5\xc2\x8fabc", [1.4199999570846558, nil, nil]],
+ ["@9\x99\x9a?\xb333abc", [2.9000000953674316, 1.399999976158142, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+
+ it "decodes positive Infinity" do
+ "\x7f\x80\x00\x00".unpack(unpack_format).should == [infinity_value]
+ end
+
+ it "decodes negative Infinity" do
+ "\xff\x80\x00\x00".unpack(unpack_format).should == [-infinity_value]
+ end
+
+ it "decodes NaN" do
+ # mumble mumble NaN mumble https://bugs.ruby-lang.org/issues/5884
+ [nan_value].pack(unpack_format).unpack(unpack_format).first.nan?.should be_true
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ array = "@9\x99\x9a?\xb333".unpack(unpack_format("\000", 2))
+ array.should == [2.9000000953674316, 1.399999976158142]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "@9\x99\x9a?\xb333".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ array = "@9\x99\x9a?\xb333".unpack(unpack_format(' ', 2))
+ array.should == [2.9000000953674316, 1.399999976158142]
+ end
+end
+
+describe :string_unpack_double_le, shared: true do
+ it "decodes one double for a single format character" do
+ "\xb8\x1e\x85\xebQ\xb8\xf6?".unpack(unpack_format).should == [1.42]
+ end
+
+ it "decodes a negative double" do
+ "\x9a\x99\x99\x99\x99\x19A\xc0".unpack(unpack_format).should == [-34.2]
+ end
+
+ it "decodes two doubles for two format characters" do
+ "333333\x07@ffffff\xf6?".unpack(unpack_format(nil, 2)).should == [2.9, 1.4]
+ end
+
+ it "decodes the number of doubles requested by the count modifier" do
+ array = "333333\x07@ffffff\xf6?ffffff\x20@".unpack(unpack_format(3))
+ array.should == [2.9, 1.4, 8.2]
+ end
+
+ it "decodes the remaining doubles when passed the '*' modifier" do
+ array = "333333\x07@ffffff\xf6?ffffff\x20@".unpack(unpack_format("*"))
+ array.should == [2.9, 1.4, 8.2]
+ end
+
+ it "decodes the remaining doubles when passed the '*' modifier after another directive" do
+ array = "333333\x15@ffffff\x22@".unpack(unpack_format()+unpack_format('*'))
+ array.should == [5.3, 9.2]
+ end
+
+ it "does not decode a double when fewer bytes than a double remain and the '*' modifier is passed" do
+ [ ["\xff", []],
+ ["\xff\x00", []],
+ ["\xff\x00\xff", []],
+ ["\xff\x00\xff\x00", []],
+ ["\xff\x00\xff\x00\xff", []],
+ ["\xff\x00\xff\x00\xff\x00", []],
+ ["\xff\x00\xff\x00\xff\x00\xff", []]
+ ].should be_computed_by(:unpack, unpack_format("*"))
+ end
+
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["\xff\x00\xff\x00\xff\x00\xff", [nil, nil, nil]],
+ ["\xb8\x1e\x85\xebQ\xb8\xf6?abc", [1.42, nil, nil]],
+ ["333333\x07@ffffff\xf6?abcd", [2.9, 1.4, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+
+ it "decodes positive Infinity" do
+ "\x00\x00\x00\x00\x00\x00\xf0\x7f".unpack(unpack_format).should == [infinity_value]
+ end
+
+ it "decodes negative Infinity" do
+ "\x00\x00\x00\x00\x00\x00\xf0\xff".unpack(unpack_format).should == [-infinity_value]
+ end
+
+ it "decodes NaN" do
+ # mumble mumble NaN mumble https://bugs.ruby-lang.org/issues/5884
+ [nan_value].pack(unpack_format).unpack(unpack_format).first.nan?.should be_true
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "333333\x07@ffffff\xf6?".unpack(unpack_format("\000", 2)).should == [2.9, 1.4]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "333333\x07@ffffff\xf6?".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "333333\x07@ffffff\xf6?".unpack(unpack_format(' ', 2)).should == [2.9, 1.4]
+ end
+end
+
+describe :string_unpack_double_be, shared: true do
+ it "decodes one double for a single format character" do
+ "?\xf6\xb8Q\xeb\x85\x1e\xb8".unpack(unpack_format).should == [1.42]
+ end
+
+ it "decodes a negative double" do
+ "\xc0A\x19\x99\x99\x99\x99\x9a".unpack(unpack_format).should == [-34.2]
+ end
+
+ it "decodes two doubles for two format characters" do
+ "@\x07333333?\xf6ffffff".unpack(unpack_format(nil, 2)).should == [2.9, 1.4]
+ end
+
+ it "decodes the number of doubles requested by the count modifier" do
+ array = "@\x07333333?\xf6ffffff@\x20ffffff".unpack(unpack_format(3))
+ array.should == [2.9, 1.4, 8.2]
+ end
+
+ it "decodes the remaining doubles when passed the '*' modifier" do
+ array = "@\x07333333?\xf6ffffff@\x20ffffff".unpack(unpack_format("*"))
+ array.should == [2.9, 1.4, 8.2]
+ end
+
+ it "decodes the remaining doubles when passed the '*' modifier after another directive" do
+ array = "@\x15333333@\x22ffffff".unpack(unpack_format()+unpack_format('*'))
+ array.should == [5.3, 9.2]
+ end
+
+ it "does not decode a double when fewer bytes than a double remain and the '*' modifier is passed" do
+ [ ["\xff", []],
+ ["\xff\x00", []],
+ ["\xff\x00\xff", []],
+ ["\xff\x00\xff\x00", []],
+ ["\xff\x00\xff\x00\xff", []],
+ ["\xff\x00\xff\x00\xff\x00", []],
+ ["\xff\x00\xff\x00\xff\x00\xff", []]
+ ].should be_computed_by(:unpack, unpack_format("*"))
+ end
+
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["abcdefg", [nil, nil, nil]],
+ ["?\xf6\xb8Q\xeb\x85\x1e\xb8abc", [1.42, nil, nil]],
+ ["@\x07333333?\xf6ffffffabcd", [2.9, 1.4, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+
+ it "decodes positive Infinity" do
+ "\x7f\xf0\x00\x00\x00\x00\x00\x00".unpack(unpack_format).should == [infinity_value]
+ end
+
+ it "decodes negative Infinity" do
+ "\xff\xf0\x00\x00\x00\x00\x00\x00".unpack(unpack_format).should == [-infinity_value]
+ end
+
+ it "decodes NaN" do
+ # mumble mumble NaN mumble https://bugs.ruby-lang.org/issues/5884
+ [nan_value].pack(unpack_format).unpack(unpack_format).first.nan?.should be_true
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "@\x07333333?\xf6ffffff".unpack(unpack_format("\000", 2)).should == [2.9, 1.4]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "@\x07333333?\xf6ffffff".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "@\x07333333?\xf6ffffff".unpack(unpack_format(' ', 2)).should == [2.9, 1.4]
+ end
+end
diff --git a/spec/ruby/core/string/unpack/shared/integer.rb b/spec/ruby/core/string/unpack/shared/integer.rb
new file mode 100644
index 0000000000..ba4f149dad
--- /dev/null
+++ b/spec/ruby/core/string/unpack/shared/integer.rb
@@ -0,0 +1,399 @@
+# -*- encoding: binary -*-
+
+describe :string_unpack_16bit_le, shared: true do
+ it "decodes one short for a single format character" do
+ "ab".unpack(unpack_format).should == [25185]
+ end
+
+ it "decodes two shorts for two format characters" do
+ "abcd".unpack(unpack_format(nil, 2)).should == [25185, 25699]
+ end
+
+ it "decodes the number of shorts requested by the count modifier" do
+ "abcdef".unpack(unpack_format(3)).should == [25185, 25699, 26213]
+ end
+
+ it "decodes the remaining shorts when passed the '*' modifier" do
+ "abcd".unpack(unpack_format('*')).should == [25185, 25699]
+ end
+
+ it "decodes the remaining shorts when passed the '*' modifier after another directive" do
+ "abcd".unpack(unpack_format()+unpack_format('*')).should == [25185, 25699]
+ end
+
+ it "does not decode a short when fewer bytes than a short remain and the '*' modifier is passed" do
+ "\xff".unpack(unpack_format('*')).should == []
+ end
+
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["", [nil, nil, nil]],
+ ["abc", [25185, nil, nil]],
+ ["abcd", [25185, 25699, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "abcd".unpack(unpack_format("\000", 2)).should == [25185, 25699]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "abcd".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "abcd".unpack(unpack_format(' ', 2)).should == [25185, 25699]
+ end
+end
+
+describe :string_unpack_16bit_le_signed, shared: true do
+ it "decodes a short with most significant bit set as a negative number" do
+ "\x00\xff".unpack(unpack_format()).should == [-256]
+ end
+end
+
+describe :string_unpack_16bit_le_unsigned, shared: true do
+ it "decodes a short with most significant bit set as a positive number" do
+ "\x00\xff".unpack(unpack_format()).should == [65280]
+ end
+end
+
+describe :string_unpack_16bit_be, shared: true do
+ it "decodes one short for a single format character" do
+ "ba".unpack(unpack_format).should == [25185]
+ end
+
+ it "decodes two shorts for two format characters" do
+ "badc".unpack(unpack_format(nil, 2)).should == [25185, 25699]
+ end
+
+ it "decodes the number of shorts requested by the count modifier" do
+ "badcfe".unpack(unpack_format(3)).should == [25185, 25699, 26213]
+ end
+
+ it "decodes the remaining shorts when passed the '*' modifier" do
+ "badc".unpack(unpack_format('*')).should == [25185, 25699]
+ end
+
+ it "decodes the remaining shorts when passed the '*' modifier after another directive" do
+ "badc".unpack(unpack_format()+unpack_format('*')).should == [25185, 25699]
+ end
+
+ it "does not decode a short when fewer bytes than a short remain and the '*' modifier is passed" do
+ "\xff".unpack(unpack_format('*')).should == []
+ end
+
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["", [nil, nil, nil]],
+ ["bac", [25185, nil, nil]],
+ ["badc", [25185, 25699, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "badc".unpack(unpack_format("\000", 2)).should == [25185, 25699]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "badc".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "badc".unpack(unpack_format(' ', 2)).should == [25185, 25699]
+ end
+end
+
+describe :string_unpack_16bit_be_signed, shared: true do
+ it "decodes a short with most significant bit set as a negative number" do
+ "\xff\x00".unpack(unpack_format()).should == [-256]
+ end
+end
+
+describe :string_unpack_16bit_be_unsigned, shared: true do
+ it "decodes a short with most significant bit set as a positive number" do
+ "\xff\x00".unpack(unpack_format()).should == [65280]
+ end
+end
+
+describe :string_unpack_32bit_le, shared: true do
+ it "decodes one int for a single format character" do
+ "abcd".unpack(unpack_format).should == [1684234849]
+ end
+
+ it "decodes two ints for two format characters" do
+ "abghefcd".unpack(unpack_format(nil, 2)).should == [1751605857, 1684235877]
+ end
+
+ it "decodes the number of ints requested by the count modifier" do
+ "abcedfgh".unpack(unpack_format(2)).should == [1701012065, 1751606884]
+ end
+
+ it "decodes the remaining ints when passed the '*' modifier" do
+ "acbdegfh".unpack(unpack_format('*')).should == [1684169569, 1751541605]
+ end
+
+ it "decodes the remaining ints when passed the '*' modifier after another directive" do
+ "abcdefgh".unpack(unpack_format()+unpack_format('*')).should == [1684234849, 1751606885]
+ end
+
+ it "does not decode an int when fewer bytes than an int remain and the '*' modifier is passed" do
+ "abc".unpack(unpack_format('*')).should == []
+ end
+
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["", [nil, nil, nil]],
+ ["abcde", [1684234849, nil, nil]],
+ ["abcdefg", [1684234849, nil, nil]],
+ ["abcdefgh", [1684234849, 1751606885, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "abcdefgh".unpack(unpack_format("\000", 2)).should == [1684234849, 1751606885]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "abcdefgh".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "abcdefgh".unpack(unpack_format(' ', 2)).should == [1684234849, 1751606885]
+ end
+end
+
+describe :string_unpack_32bit_le_signed, shared: true do
+ it "decodes an int with most significant bit set as a negative number" do
+ "\x00\xaa\x00\xff".unpack(unpack_format()).should == [-16733696]
+ end
+end
+
+describe :string_unpack_32bit_le_unsigned, shared: true do
+ it "decodes an int with most significant bit set as a positive number" do
+ "\x00\xaa\x00\xff".unpack(unpack_format()).should == [4278233600]
+ end
+end
+
+describe :string_unpack_32bit_be, shared: true do
+ it "decodes one int for a single format character" do
+ "dcba".unpack(unpack_format).should == [1684234849]
+ end
+
+ it "decodes two ints for two format characters" do
+ "hgbadcfe".unpack(unpack_format(nil, 2)).should == [1751605857, 1684235877]
+ end
+
+ it "decodes the number of ints requested by the count modifier" do
+ "ecbahgfd".unpack(unpack_format(2)).should == [1701012065, 1751606884]
+ end
+
+ it "decodes the remaining ints when passed the '*' modifier" do
+ "dbcahfge".unpack(unpack_format('*')).should == [1684169569, 1751541605]
+ end
+
+ it "decodes the remaining ints when passed the '*' modifier after another directive" do
+ "dcbahgfe".unpack(unpack_format()+unpack_format('*')).should == [1684234849, 1751606885]
+ end
+
+ it "does not decode an int when fewer bytes than an int remain and the '*' modifier is passed" do
+ "abc".unpack(unpack_format('*')).should == []
+ end
+
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["", [nil, nil, nil]],
+ ["dcbae", [1684234849, nil, nil]],
+ ["dcbaefg", [1684234849, nil, nil]],
+ ["dcbahgfe", [1684234849, 1751606885, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "dcbahgfe".unpack(unpack_format("\000", 2)).should == [1684234849, 1751606885]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "dcbahgfe".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "dcbahgfe".unpack(unpack_format(' ', 2)).should == [1684234849, 1751606885]
+ end
+end
+
+describe :string_unpack_32bit_be_signed, shared: true do
+ it "decodes an int with most significant bit set as a negative number" do
+ "\xff\x00\xaa\x00".unpack(unpack_format()).should == [-16733696]
+ end
+end
+
+describe :string_unpack_32bit_be_unsigned, shared: true do
+ it "decodes an int with most significant bit set as a positive number" do
+ "\xff\x00\xaa\x00".unpack(unpack_format()).should == [4278233600]
+ end
+end
+
+describe :string_unpack_64bit_le, shared: true do
+ it "decodes one long for a single format character" do
+ "abcdefgh".unpack(unpack_format).should == [7523094288207667809]
+ end
+
+ it "decodes two longs for two format characters" do
+ array = "abghefcdghefabcd".unpack(unpack_format(nil, 2))
+ array.should == [7233738012216484449, 7233733596956420199]
+ end
+
+ it "decodes the number of longs requested by the count modifier" do
+ array = "abcedfghefcdghef".unpack(unpack_format(2))
+ array.should == [7523094283929477729, 7378418357791581797]
+ end
+
+ it "decodes the remaining longs when passed the '*' modifier" do
+ array = "acbdegfhdegfhacb".unpack(unpack_format('*'))
+ array.should == [7522813912742519649, 7089617339433837924]
+ end
+
+ it "decodes the remaining longs when passed the '*' modifier after another directive" do
+ array = "bcahfgedhfgedbca".unpack(unpack_format()+unpack_format('*'))
+ array.should == [7234302065976107874, 7017560827710891624]
+ end
+
+ it "does not decode a long when fewer bytes than a long remain and the '*' modifier is passed" do
+ "abc".unpack(unpack_format('*')).should == []
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ array = "abcdefghabghefcd".unpack(unpack_format("\000", 2))
+ array.should == [7523094288207667809, 7233738012216484449]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "badc".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ array = "abcdefghabghefcd".unpack(unpack_format(' ', 2))
+ array.should == [7523094288207667809, 7233738012216484449]
+ end
+end
+
+describe :string_unpack_64bit_le_extra, shared: true do
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["", [nil, nil, nil]],
+ ["abcdefgh", [7523094288207667809, nil, nil]],
+ ["abcdefghcdefab", [7523094288207667809, nil, nil]],
+ ["abcdefghcdefabde", [7523094288207667809, 7306072665971057763, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+end
+
+describe :string_unpack_64bit_le_signed, shared: true do
+ it "decodes a long with most significant bit set as a negative number" do
+ "\x00\xcc\x00\xbb\x00\xaa\x00\xff".unpack(unpack_format()).should == [-71870673923814400]
+ end
+end
+
+describe :string_unpack_64bit_le_unsigned, shared: true do
+ it "decodes a long with most significant bit set as a positive number" do
+ "\x00\xcc\x00\xbb\x00\xaa\x00\xff".unpack(unpack_format()).should == [18374873399785737216]
+ end
+end
+
+describe :string_unpack_64bit_be, shared: true do
+ it "decodes one long for a single format character" do
+ "hgfedcba".unpack(unpack_format).should == [7523094288207667809]
+ end
+
+ it "decodes two longs for two format characters" do
+ array = "dcfehgbadcbafehg".unpack(unpack_format(nil, 2))
+ array.should == [7233738012216484449, 7233733596956420199]
+ end
+
+ it "decodes the number of longs requested by the count modifier" do
+ array = "hgfdecbafehgdcfe".unpack(unpack_format(2))
+ array.should == [7523094283929477729, 7378418357791581797]
+ end
+
+ it "decodes the remaining longs when passed the '*' modifier" do
+ array = "hfgedbcabcahfged".unpack(unpack_format('*'))
+ array.should == [7522813912742519649, 7089617339433837924]
+ end
+
+ it "decodes the remaining longs when passed the '*' modifier after another directive" do
+ array = "degfhacbacbdegfh".unpack(unpack_format()+unpack_format('*'))
+ array.should == [7234302065976107874, 7017560827710891624]
+ end
+
+ it "does not decode a long when fewer bytes than a long remain and the '*' modifier is passed" do
+ "abc".unpack(unpack_format('*')).should == []
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ array = "hgfedcbadcfehgba".unpack(unpack_format("\000", 2))
+ array.should == [7523094288207667809, 7233738012216484449]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "hgfedcbadcfehgba".unpack(unpack_format("\000", 2))
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ array = "hgfedcbadcfehgba".unpack(unpack_format(' ', 2))
+ array.should == [7523094288207667809, 7233738012216484449]
+ end
+end
+
+describe :string_unpack_64bit_be_extra, shared: true do
+ it "adds nil for each element requested beyond the end of the String" do
+ [ ["", [nil, nil, nil]],
+ ["hgfedcba", [7523094288207667809, nil, nil]],
+ ["hgfedcbacdefab", [7523094288207667809, nil, nil]],
+ ["hgfedcbaedbafedc", [7523094288207667809, 7306072665971057763, nil]]
+ ].should be_computed_by(:unpack, unpack_format(3))
+ end
+end
+
+describe :string_unpack_64bit_be_signed, shared: true do
+ it "decodes a long with most significant bit set as a negative number" do
+ "\xff\x00\xaa\x00\xbb\x00\xcc\x00".unpack(unpack_format()).should == [-71870673923814400]
+ end
+end
+
+describe :string_unpack_64bit_be_unsigned, shared: true do
+ it "decodes a long with most significant bit set as a positive number" do
+ "\xff\x00\xaa\x00\xbb\x00\xcc\x00".unpack(unpack_format()).should == [18374873399785737216]
+ end
+end
diff --git a/spec/ruby/core/string/unpack/shared/string.rb b/spec/ruby/core/string/unpack/shared/string.rb
new file mode 100644
index 0000000000..9d85eedf26
--- /dev/null
+++ b/spec/ruby/core/string/unpack/shared/string.rb
@@ -0,0 +1,51 @@
+describe :string_unpack_string, shared: true do
+ it "returns an empty string if the input is empty" do
+ "".unpack(unpack_format).should == [""]
+ end
+
+ it "returns empty strings for repeated formats if the input is empty" do
+ "".unpack(unpack_format(nil, 3)).should == ["", "", ""]
+ end
+
+ it "returns an empty string and does not decode any bytes when the count modifier is zero" do
+ "abc".unpack(unpack_format(0)+unpack_format).should == ["", "a"]
+ end
+
+ it "implicitly has a count of one when no count is specified" do
+ "abc".unpack(unpack_format).should == ["a"]
+ end
+
+ it "decodes the number of bytes specified by the count modifier" do
+ "abc".unpack(unpack_format(3)).should == ["abc"]
+ end
+
+ it "decodes the number of bytes specified by the count modifier including whitespace bytes" do
+ [ ["a bc", ["a b", "c"]],
+ ["a\fbc", ["a\fb", "c"]],
+ ["a\nbc", ["a\nb", "c"]],
+ ["a\rbc", ["a\rb", "c"]],
+ ["a\tbc", ["a\tb", "c"]],
+ ["a\vbc", ["a\vb", "c"]]
+ ].should be_computed_by(:unpack, unpack_format(3)+unpack_format)
+ end
+
+ it "decodes past whitespace bytes when passed the '*' modifier" do
+ [ ["a b c", ["a b c"]],
+ ["a\fb c", ["a\fb c"]],
+ ["a\nb c", ["a\nb c"]],
+ ["a\rb c", ["a\rb c"]],
+ ["a\tb c", ["a\tb c"]],
+ ["a\vb c", ["a\vb c"]],
+ ].should be_computed_by(:unpack, unpack_format("*"))
+ end
+end
+
+describe :string_unpack_Aa, shared: true do
+ it "decodes the number of bytes specified by the count modifier including NULL bytes" do
+ "a\x00bc".unpack(unpack_format(3)+unpack_format).should == ["a\x00b", "c"]
+ end
+
+ it "decodes past NULL bytes when passed the '*' modifier" do
+ "a\x00b c".unpack(unpack_format("*")).should == ["a\x00b c"]
+ end
+end
diff --git a/spec/ruby/core/string/unpack/shared/taint.rb b/spec/ruby/core/string/unpack/shared/taint.rb
new file mode 100644
index 0000000000..79c7251f01
--- /dev/null
+++ b/spec/ruby/core/string/unpack/shared/taint.rb
@@ -0,0 +1,2 @@
+describe :string_unpack_taint, shared: true do
+end
diff --git a/spec/ruby/core/string/unpack/shared/unicode.rb b/spec/ruby/core/string/unpack/shared/unicode.rb
new file mode 100644
index 0000000000..ce1f29fe87
--- /dev/null
+++ b/spec/ruby/core/string/unpack/shared/unicode.rb
@@ -0,0 +1,70 @@
+# -*- encoding: utf-8 -*-
+
+describe :string_unpack_unicode, shared: true do
+ it "decodes Unicode codepoints as ASCII values" do
+ [ ["\x00", [0]],
+ ["\x01", [1]],
+ ["\x08", [8]],
+ ["\x0f", [15]],
+ ["\x18", [24]],
+ ["\x1f", [31]],
+ ["\x7f", [127]],
+ ["\xc2\x80", [128]],
+ ["\xc2\x81", [129]],
+ ["\xc3\xbf", [255]]
+ ].should be_computed_by(:unpack, "U")
+ end
+
+ it "decodes the number of characters specified by the count modifier" do
+ [ ["\xc2\x80\xc2\x81\xc2\x82\xc2\x83", "U1", [0x80]],
+ ["\xc2\x80\xc2\x81\xc2\x82\xc2\x83", "U2", [0x80, 0x81]],
+ ["\xc2\x80\xc2\x81\xc2\x82\xc2\x83", "U3", [0x80, 0x81, 0x82]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "implicitly has a count of one when no count modifier is passed" do
+ "\xc2\x80\xc2\x81\xc2\x82\xc2\x83".unpack("U1").should == [0x80]
+ end
+
+ it "decodes all remaining characters when passed the '*' modifier" do
+ "\xc2\x80\xc2\x81\xc2\x82\xc2\x83".unpack("U*").should == [0x80, 0x81, 0x82, 0x83]
+ end
+
+ it "decodes UTF-8 BMP codepoints" do
+ [ ["\xc2\x80", [0x80]],
+ ["\xdf\xbf", [0x7ff]],
+ ["\xe0\xa0\x80", [0x800]],
+ ["\xef\xbf\xbf", [0xffff]]
+ ].should be_computed_by(:unpack, "U")
+ end
+
+ it "decodes UTF-8 max codepoints" do
+ [ ["\xf0\x90\x80\x80", [0x10000]],
+ ["\xf3\xbf\xbf\xbf", [0xfffff]],
+ ["\xf4\x80\x80\x80", [0x100000]],
+ ["\xf4\x8f\xbf\xbf", [0x10ffff]]
+ ].should be_computed_by(:unpack, "U")
+ end
+
+ it "does not decode any items for directives exceeding the input string size" do
+ "\xc2\x80".unpack("UUUU").should == [0x80]
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "\x01\x02".unpack("U\x00U").should == [1, 2]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "\x01\x02".unpack("U\x00U")
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "\x01\x02".unpack("U U").should == [1, 2]
+ end
+end
diff --git a/spec/ruby/core/string/unpack/u_spec.rb b/spec/ruby/core/string/unpack/u_spec.rb
new file mode 100644
index 0000000000..7845e6d5f2
--- /dev/null
+++ b/spec/ruby/core/string/unpack/u_spec.rb
@@ -0,0 +1,97 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/unicode'
+require_relative 'shared/taint'
+
+describe "String#unpack with format 'U'" do
+ it_behaves_like :string_unpack_basic, 'U'
+ it_behaves_like :string_unpack_no_platform, 'U'
+ it_behaves_like :string_unpack_unicode, 'U'
+ it_behaves_like :string_unpack_taint, 'U'
+
+ it "raises ArgumentError on a malformed byte sequence" do
+ -> { "\xE3".unpack('U') }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError on a malformed byte sequence and doesn't continue when used with the * modifier" do
+ -> { "\xE3".unpack('U*') }.should raise_error(ArgumentError)
+ end
+end
+
+describe "String#unpack with format 'u'" do
+ it_behaves_like :string_unpack_basic, 'u'
+ it_behaves_like :string_unpack_no_platform, 'u'
+ it_behaves_like :string_unpack_taint, 'u'
+
+ it "decodes an empty string as an empty string" do
+ "".unpack("u").should == [""]
+ end
+
+ it "decodes into raw (ascii) string values" do
+ str = "".unpack("u")[0]
+ str.encoding.should == Encoding::BINARY
+
+ str = "1".force_encoding('UTF-8').unpack("u")[0]
+ str.encoding.should == Encoding::BINARY
+ end
+
+ it "decodes the complete string ignoring newlines when given a single directive" do
+ "#86)C\n#1$5&\n".unpack("u").should == ["abcDEF"]
+ end
+
+ it "appends empty string to the array for directives exceeding the input size" do
+ "#86)C\n#1$5&\n".unpack("uuu").should == ["abcDEF", "", ""]
+ end
+
+ it "ignores the count or '*' modifier and decodes the entire string" do
+ [ ["#86)C\n#1$5&\n", "u238", ["abcDEF"]],
+ ["#86)C\n#1$5&\n", "u*", ["abcDEF"]]
+ ].should be_computed_by(:unpack)
+ end
+
+ it "decodes all ascii characters" do
+ [ ["'``$\"`P0%!@``\n", ["\x00\x01\x02\x03\x04\x05\x06"]],
+ ["'!P@)\"@L,#0``\n", ["\a\b\t\n\v\f\r"]],
+ [")\#@\\0$1(3%!46\n", ["\x0E\x0F\x10\x11\x12\x13\x14\x15\x16"]],
+ [")%Q@9&AL<'1X?\n", ["\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f"]],
+ ["/(2(C)\"4F)R@I*BLL+2XO\n", ["!\"\#$%&'()*+,-./"]],
+ ["*,\#$R,S0U-C<X.0``\n", ["0123456789"]],
+ ["'.CL\\/3X_0```\n", [":;<=>?@"]],
+ [":04)#1$5&1TA)2DM,34Y/4%%24U155E=865H`\n", ["ABCDEFGHIJKLMNOPQRSTUVWXYZ"]],
+ ["&6UQ=7E]@\n", ["[\\]^_`"]],
+ [":86)C9&5F9VAI:FML;6YO<'%R<W1U=G=X>7H`\n", ["abcdefghijklmnopqrstuvwxyz"]],
+ ["$>WQ]?@``\n", ["{|}~"]],
+ [")?\\*`PH'\"@L*#\n", ["\x7f\xc2\x80\xc2\x81\xc2\x82\xc2\x83"]],
+ [")PH3\"A<*&PH?\"\n", ["\xc2\x84\xc2\x85\xc2\x86\xc2\x87\xc2"]],
+ [")B,*)PHK\"B\\*,\n", ["\x88\xc2\x89\xc2\x8a\xc2\x8b\xc2\x8c"]],
+ [")PHW\"CL*/PI#\"\n", ["\xc2\x8d\xc2\x8e\xc2\x8f\xc2\x90\xc2"]],
+ [")D<*2PI/\"E,*5\n", ["\x91\xc2\x92\xc2\x93\xc2\x94\xc2\x95"]],
+ [")PI;\"E\\*8PIG\"\n", ["\xc2\x96\xc2\x97\xc2\x98\xc2\x99\xc2"]],
+ [")FL*;PIS\"G<*>\n", ["\x9a\xc2\x9b\xc2\x9c\xc2\x9d\xc2\x9e"]],
+ [")PI_\"H,*APJ+\"\n", ["\xc2\x9f\xc2\xa0\xc2\xa1\xc2\xa2\xc2"]],
+ [")H\\*DPJ7\"IL*G\n", ["\xa3\xc2\xa4\xc2\xa5\xc2\xa6\xc2\xa7"]],
+ [")PJC\"J<*JPJO\"\n", ["\xc2\xa8\xc2\xa9\xc2\xaa\xc2\xab\xc2"]],
+ [")K,*MPJ[\"K\\*P\n", ["\xac\xc2\xad\xc2\xae\xc2\xaf\xc2\xb0"]],
+ [")PK'\"LL*SPK3\"\n", ["\xc2\xb1\xc2\xb2\xc2\xb3\xc2\xb4\xc2"]],
+ [")M<*VPK?\"N,*Y\n", ["\xb5\xc2\xb6\xc2\xb7\xc2\xb8\xc2\xb9"]],
+ [")PKK\"N\\*\\PKW\"\n", ["\xc2\xba\xc2\xbb\xc2\xbc\xc2\xbd\xc2"]],
+ [")OL*_PX#\#@<.\"\n", ["\xbe\xc2\xbf\xc3\x80\xc3\x81\xc3\x82"]],
+ [")PX/#A,.%PX;#\n", ["\xc3\x83\xc3\x84\xc3\x85\xc3\x86\xc3"]],
+ [")A\\.(PXG#BL.+\n", ["\x87\xc3\x88\xc3\x89\xc3\x8a\xc3\x8b"]],
+ [")PXS#C<..PX_#\n", ["\xc3\x8c\xc3\x8d\xc3\x8e\xc3\x8f\xc3"]],
+ [")D,.1PY+#D\\.4\n", ["\x90\xc3\x91\xc3\x92\xc3\x93\xc3\x94"]],
+ [")PY7#EL.7PYC#\n", ["\xc3\x95\xc3\x96\xc3\x97\xc3\x98\xc3"]],
+ [")F<.:PYO#G,.=\n", ["\x99\xc3\x9a\xc3\x9b\xc3\x9c\xc3\x9d"]],
+ [")PY[#G\\.@PZ'#\n", ["\xc3\x9e\xc3\x9f\xc3\xa0\xc3\xa1\xc3"]],
+ [")HL.CPZ3#I<.F\n", ["\xa2\xc3\xa3\xc3\xa4\xc3\xa5\xc3\xa6"]],
+ [")PZ?#J,.IPZK#\n", ["\xc3\xa7\xc3\xa8\xc3\xa9\xc3\xaa\xc3"]],
+ [")J\\.LPZW#KL.O\n", ["\xab\xc3\xac\xc3\xad\xc3\xae\xc3\xaf"]],
+ [")P[##L<.RP[/#\n", ["\xc3\xb0\xc3\xb1\xc3\xb2\xc3\xb3\xc3"]],
+ [")M,.UP[;#M\\.X\n", ["\xb4\xc3\xb5\xc3\xb6\xc3\xb7\xc3\xb8"]],
+ [")P[G#NL.[P[S#\n", ["\xc3\xb9\xc3\xba\xc3\xbb\xc3\xbc\xc3"]],
+ ["%O<.^P[\\`\n", ["\xbd\xc3\xbe\xc3\xbf"]]
+ ].should be_computed_by(:unpack, "u")
+ end
+end
diff --git a/spec/ruby/core/string/unpack/v_spec.rb b/spec/ruby/core/string/unpack/v_spec.rb
new file mode 100644
index 0000000000..929e8712cb
--- /dev/null
+++ b/spec/ruby/core/string/unpack/v_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/integer'
+
+describe "String#unpack with format 'V'" do
+ it_behaves_like :string_unpack_basic, 'V'
+ it_behaves_like :string_unpack_32bit_le, 'V'
+ it_behaves_like :string_unpack_32bit_le_unsigned, 'V'
+ it_behaves_like :string_unpack_no_platform, 'V'
+end
+
+describe "String#unpack with format 'v'" do
+ it_behaves_like :string_unpack_basic, 'v'
+ it_behaves_like :string_unpack_16bit_le, 'v'
+ it_behaves_like :string_unpack_16bit_le_unsigned, 'v'
+ it_behaves_like :string_unpack_no_platform, 'v'
+end
diff --git a/spec/ruby/core/string/unpack/w_spec.rb b/spec/ruby/core/string/unpack/w_spec.rb
new file mode 100644
index 0000000000..b213b32921
--- /dev/null
+++ b/spec/ruby/core/string/unpack/w_spec.rb
@@ -0,0 +1,45 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+
+describe "String#unpack with directive 'w'" do
+ it_behaves_like :string_unpack_basic, 'w'
+ it_behaves_like :string_unpack_no_platform, 'w'
+
+ it "decodes a BER-compressed integer" do
+ [ ["\x00", [0]],
+ ["\x01", [1]],
+ ["\xce\x0f", [9999]],
+ ["\x84\x80\x80\x80\x80\x80\x80\x80\x80\x00", [2**65]]
+ ].should be_computed_by(:unpack, "w")
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "ignores NULL bytes between directives" do
+ "\x01\x02\x03".unpack("w\x00w").should == [1, 2]
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ "\x01\x02\x03".unpack("w\x00w")
+ }.should raise_error(ArgumentError, /unknown unpack directive/)
+ end
+ end
+
+ it "ignores spaces between directives" do
+ "\x01\x02\x03".unpack("w w").should == [1, 2]
+ end
+end
+
+describe "String#unpack with directive 'w*'" do
+
+ it "decodes BER-compressed integers" do
+ "\x01\x02\x03\x04".unpack("w*").should == [1, 2, 3, 4]
+ "\x00\xCE\x0F\x84\x80\x80\x80\x80\x80\x80\x80\x80\x00\x01\x00".unpack("w*").should == [0, 9999, 2**65, 1, 0]
+ "\x81\x80\x80\x80\x80\x80\x80\x80\x80\x00\x90\x80\x80\x80\x80\x80\x80\x80\x03\x01\x02".unpack("w*").should == [2**63, (2**60 + 3), 1, 2]
+ end
+
+end
diff --git a/spec/ruby/core/string/unpack/x_spec.rb b/spec/ruby/core/string/unpack/x_spec.rb
new file mode 100644
index 0000000000..5e248de77e
--- /dev/null
+++ b/spec/ruby/core/string/unpack/x_spec.rb
@@ -0,0 +1,62 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+
+describe "String#unpack with format 'X'" do
+ it_behaves_like :string_unpack_basic, 'X'
+ it_behaves_like :string_unpack_no_platform, 'X'
+
+ it "moves the read index back by the number of bytes specified by count" do
+ "\x01\x02\x03\x04".unpack("C3X2C").should == [1, 2, 3, 2]
+ end
+
+ it "does not change the read index when passed a count of zero" do
+ "\x01\x02\x03\x04".unpack("C3X0C").should == [1, 2, 3, 4]
+ end
+
+ it "implicitly has a count of one when count is not specified" do
+ "\x01\x02\x03\x04".unpack("C3XC").should == [1, 2, 3, 3]
+ end
+
+ it "moves the read index back by the remaining bytes when passed the '*' modifier" do
+ "abcd".unpack("C3X*C").should == [97, 98, 99, 99]
+ end
+
+ it "raises an ArgumentError when passed the '*' modifier if the remaining bytes exceed the bytes from the index to the start of the String" do
+ -> { "abcd".unpack("CX*C") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the count exceeds the bytes from current index to the start of the String" do
+ -> { "\x01\x02\x03\x04".unpack("C3X4C") }.should raise_error(ArgumentError)
+ end
+end
+
+describe "String#unpack with format 'x'" do
+ it_behaves_like :string_unpack_basic, 'x'
+ it_behaves_like :string_unpack_no_platform, 'x'
+
+ it "moves the read index forward by the number of bytes specified by count" do
+ "\x01\x02\x03\x04".unpack("Cx2C").should == [1, 4]
+ end
+
+ it "implicitly has a count of one when count is not specified" do
+ "\x01\x02\x03\x04".unpack("CxC").should == [1, 3]
+ end
+
+ it "does not change the read index when passed a count of zero" do
+ "\x01\x02\x03\x04".unpack("Cx0C").should == [1, 2]
+ end
+
+ it "moves the read index to the end of the string when passed the '*' modifier" do
+ "\x01\x02\x03\x04".unpack("Cx*C").should == [1, nil]
+ end
+
+ it "positions the read index one beyond the last readable byte in the String" do
+ "\x01\x02\x03\x04".unpack("C2x2C").should == [1, 2, nil]
+ end
+
+ it "raises an ArgumentError if the count exceeds the size of the String" do
+ -> { "\x01\x02\x03\x04".unpack("C2x3C") }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/unpack/z_spec.rb b/spec/ruby/core/string/unpack/z_spec.rb
new file mode 100644
index 0000000000..ce8da4b29e
--- /dev/null
+++ b/spec/ruby/core/string/unpack/z_spec.rb
@@ -0,0 +1,28 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/string'
+require_relative 'shared/taint'
+
+describe "String#unpack with format 'Z'" do
+ it_behaves_like :string_unpack_basic, 'Z'
+ it_behaves_like :string_unpack_no_platform, 'Z'
+ it_behaves_like :string_unpack_string, 'Z'
+ it_behaves_like :string_unpack_taint, 'Z'
+
+ it "stops decoding at NULL bytes when passed the '*' modifier" do
+ "a\x00\x00 b \x00c".unpack('Z*Z*Z*Z*').should == ["a", "", " b ", "c"]
+ end
+
+ it "decodes the number of bytes specified by the count modifier and truncates the decoded string at the first NULL byte" do
+ [ ["a\x00 \x00b c", ["a", " "]],
+ ["\x00a\x00 bc \x00", ["", "c"]]
+ ].should be_computed_by(:unpack, "Z5Z")
+ end
+
+ it "does not advance past the null byte when given a 'Z' format specifier" do
+ "a\x00\x0f".unpack('Zxc').should == ['a', 15]
+ "a\x00\x0f".unpack('Zcc').should == ['a', 0, 15]
+ end
+end
diff --git a/spec/ruby/core/string/unpack1_spec.rb b/spec/ruby/core/string/unpack1_spec.rb
new file mode 100644
index 0000000000..df830916a3
--- /dev/null
+++ b/spec/ruby/core/string/unpack1_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe "String#unpack1" do
+ it "returns the first value of #unpack" do
+ "ABCD".unpack1('x3C').should == "ABCD".unpack('x3C')[0]
+ "\u{3042 3044 3046}".unpack1("U*").should == 0x3042
+ "aG9nZWZ1Z2E=".unpack1("m").should == "hogefuga"
+ "A".unpack1("B*").should == "01000001"
+ end
+
+ ruby_version_is "3.1" do
+ it "starts unpacking from the given offset" do
+ "ZZABCD".unpack1('x3C', offset: 2).should == "ABCD".unpack('x3C')[0]
+ "ZZZZaG9nZWZ1Z2E=".unpack1("m", offset: 4).should == "hogefuga"
+ "ZA".unpack1("B*", offset: 1).should == "01000001"
+ end
+
+ it "traits offset as a bytes offset" do
+ "؈".unpack("CC").should == [216, 136]
+ "؈".unpack1("C").should == 216
+ "؈".unpack1("C", offset: 1).should == 136
+ end
+
+ it "raises an ArgumentError when the offset is negative" do
+ -> { "a".unpack1("C", offset: -1) }.should raise_error(ArgumentError, "offset can't be negative")
+ end
+
+ it "returns nil if the offset is at the end of the string" do
+ "a".unpack1("C", offset: 1).should == nil
+ end
+
+ it "raises an ArgumentError when the offset is larger than the string bytesize" do
+ -> { "a".unpack1("C", offset: 2) }.should raise_error(ArgumentError, "offset outside of string")
+ end
+ end
+end
diff --git a/spec/ruby/core/string/unpack_spec.rb b/spec/ruby/core/string/unpack_spec.rb
new file mode 100644
index 0000000000..4ff7d07460
--- /dev/null
+++ b/spec/ruby/core/string/unpack_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "String#unpack" do
+ it "raises a TypeError when passed nil" do
+ -> { "abc".unpack(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed an Integer" do
+ -> { "abc".unpack(1) }.should raise_error(TypeError)
+ end
+
+ ruby_version_is "3.1" do
+ it "starts unpacking from the given offset" do
+ "abc".unpack("CC", offset: 1).should == [98, 99]
+ end
+
+ it "traits offset as a bytes offset" do
+ "؈".unpack("CC").should == [216, 136]
+ "؈".unpack("CC", offset: 1).should == [136, nil]
+ end
+
+ it "raises an ArgumentError when the offset is negative" do
+ -> { "a".unpack("C", offset: -1) }.should raise_error(ArgumentError, "offset can't be negative")
+ end
+
+ it "returns nil if the offset is at the end of the string" do
+ "a".unpack("C", offset: 1).should == [nil]
+ end
+
+ it "raises an ArgumentError when the offset is larget than the string" do
+ -> { "a".unpack("C", offset: 2) }.should raise_error(ArgumentError, "offset outside of string")
+ end
+ end
+end \ No newline at end of file
diff --git a/spec/ruby/core/string/upcase_spec.rb b/spec/ruby/core/string/upcase_spec.rb
new file mode 100644
index 0000000000..5ce7b0b95f
--- /dev/null
+++ b/spec/ruby/core/string/upcase_spec.rb
@@ -0,0 +1,194 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#upcase" do
+ it "returns a copy of self with all lowercase letters upcased" do
+ "Hello".upcase.should == "HELLO"
+ "hello".upcase.should == "HELLO"
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hello".encode("US-ASCII").upcase.encoding.should == Encoding::US_ASCII
+ end
+
+ describe "full Unicode case mapping" do
+ it "works for all of Unicode with no option" do
+ "äöü".upcase.should == "ÄÖÜ"
+ end
+
+ it "updates string metadata" do
+ upcased = "aßet".upcase
+
+ upcased.should == "ASSET"
+ upcased.size.should == 5
+ upcased.bytesize.should == 5
+ upcased.ascii_only?.should be_true
+ end
+ end
+
+ describe "ASCII-only case mapping" do
+ it "does not upcase non-ASCII characters" do
+ "aßet".upcase(:ascii).should == "AßET"
+ end
+
+ it "works with substrings" do
+ "prefix té"[-2..-1].upcase(:ascii).should == "Té"
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Turkic languages" do
+ it "upcases ASCII characters according to Turkic semantics" do
+ "i".upcase(:turkic).should == "İ"
+ end
+
+ it "allows Lithuanian as an extra option" do
+ "i".upcase(:turkic, :lithuanian).should == "İ"
+ end
+
+ it "does not allow any other additional option" do
+ -> { "i".upcase(:turkic, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "full Unicode case mapping adapted for Lithuanian" do
+ it "currently works the same as full Unicode case mapping" do
+ "iß".upcase(:lithuanian).should == "ISS"
+ end
+
+ it "allows Turkic as an extra option (and applies Turkic semantics)" do
+ "iß".upcase(:lithuanian, :turkic).should == "İSS"
+ end
+
+ it "does not allow any other additional option" do
+ -> { "iß".upcase(:lithuanian, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "does not allow the :fold option for upcasing" do
+ -> { "abc".upcase(:fold) }.should raise_error(ArgumentError)
+ end
+
+ it "does not allow invalid options" do
+ -> { "abc".upcase(:invalid_option) }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "returns a subclass instance for subclasses" do
+ StringSpecs::MyString.new("fooBAR").upcase.should be_an_instance_of(StringSpecs::MyString)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "returns a String instance for subclasses" do
+ StringSpecs::MyString.new("fooBAR").upcase.should be_an_instance_of(String)
+ end
+ end
+end
+
+describe "String#upcase!" do
+ it "modifies self in place" do
+ a = "HeLlO"
+ a.upcase!.should equal(a)
+ a.should == "HELLO"
+ end
+
+ it "modifies self in place for non-ascii-compatible encodings" do
+ a = "HeLlO".encode("utf-16le")
+ a.upcase!
+ a.should == "HELLO".encode("utf-16le")
+ end
+
+ describe "full Unicode case mapping" do
+ it "modifies self in place for all of Unicode with no option" do
+ a = "äöü"
+ a.upcase!
+ a.should == "ÄÖÜ"
+ end
+
+ it "works for non-ascii-compatible encodings" do
+ a = "äöü".encode("utf-16le")
+ a.upcase!
+ a.should == "ÄÖÜ".encode("utf-16le")
+ end
+
+ it "updates string metadata for self" do
+ upcased = "aßet"
+ upcased.upcase!
+
+ upcased.should == "ASSET"
+ upcased.size.should == 5
+ upcased.bytesize.should == 5
+ upcased.ascii_only?.should be_true
+ end
+ end
+
+ describe "modifies self in place for ASCII-only case mapping" do
+ it "does not upcase non-ASCII characters" do
+ a = "aßet"
+ a.upcase!(:ascii)
+ a.should == "AßET"
+ end
+
+ it "works for non-ascii-compatible encodings" do
+ a = "abc".encode("utf-16le")
+ a.upcase!(:ascii)
+ a.should == "ABC".encode("utf-16le")
+ end
+ end
+
+ describe "modifies self in place for full Unicode case mapping adapted for Turkic languages" do
+ it "upcases ASCII characters according to Turkic semantics" do
+ a = "i"
+ a.upcase!(:turkic)
+ a.should == "İ"
+ end
+
+ it "allows Lithuanian as an extra option" do
+ a = "i"
+ a.upcase!(:turkic, :lithuanian)
+ a.should == "İ"
+ end
+
+ it "does not allow any other additional option" do
+ -> { a = "i"; a.upcase!(:turkic, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "modifies self in place for full Unicode case mapping adapted for Lithuanian" do
+ it "currently works the same as full Unicode case mapping" do
+ a = "iß"
+ a.upcase!(:lithuanian)
+ a.should == "ISS"
+ end
+
+ it "allows Turkic as an extra option (and applies Turkic semantics)" do
+ a = "iß"
+ a.upcase!(:lithuanian, :turkic)
+ a.should == "İSS"
+ end
+
+ it "does not allow any other additional option" do
+ -> { a = "iß"; a.upcase!(:lithuanian, :ascii) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "does not allow the :fold option for upcasing" do
+ -> { a = "abc"; a.upcase!(:fold) }.should raise_error(ArgumentError)
+ end
+
+ it "does not allow invalid options" do
+ -> { a = "abc"; a.upcase!(:invalid_option) }.should raise_error(ArgumentError)
+ end
+
+ it "returns nil if no modifications were made" do
+ a = "HELLO"
+ a.upcase!.should == nil
+ a.should == "HELLO"
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ -> { "HeLlo".freeze.upcase! }.should raise_error(FrozenError)
+ -> { "HELLO".freeze.upcase! }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/uplus_spec.rb b/spec/ruby/core/string/uplus_spec.rb
new file mode 100644
index 0000000000..038b283c90
--- /dev/null
+++ b/spec/ruby/core/string/uplus_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+describe 'String#+@' do
+ it 'returns an unfrozen copy of a frozen String' do
+ input = 'foo'.freeze
+ output = +input
+
+ output.should_not.frozen?
+ output.should == 'foo'
+ end
+
+ it 'returns self if the String is not frozen' do
+ input = 'foo'
+ output = +input
+
+ output.equal?(input).should == true
+ end
+
+ it 'returns mutable copy despite freeze-magic-comment in file' do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment.rb")).should == 'mutable'
+ end
+end
diff --git a/spec/ruby/core/string/upto_spec.rb b/spec/ruby/core/string/upto_spec.rb
new file mode 100644
index 0000000000..f8529b1d2b
--- /dev/null
+++ b/spec/ruby/core/string/upto_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "String#upto" do
+ it "passes successive values, starting at self and ending at other_string, to the block" do
+ a = []
+ "*+".upto("*3") { |s| a << s }
+ a.should == ["*+", "*,", "*-", "*.", "*/", "*0", "*1", "*2", "*3"]
+ end
+
+ it "calls the block once even when start equals stop" do
+ a = []
+ "abc".upto("abc") { |s| a << s }
+ a.should == ["abc"]
+ end
+
+ it "doesn't call block with self even if self is less than stop but stop length is less than self length" do
+ a = []
+ "25".upto("5") { |s| a << s }
+ a.should == []
+ end
+
+ it "doesn't call block if stop is less than self and stop length is less than self length" do
+ a = []
+ "25".upto("1") { |s| a << s }
+ a.should == []
+ end
+
+ it "doesn't call the block if self is greater than stop" do
+ a = []
+ "5".upto("2") { |s| a << s }
+ a.should == []
+ end
+
+ it "stops iterating as soon as the current value's character count gets higher than stop's" do
+ a = []
+ "96".upto("AA") { |s| a << s }
+ a.should == ["96", "97", "98", "99"]
+ end
+
+ it "returns self" do
+ "abc".upto("abd") { }.should == "abc"
+ "5".upto("2") { |i| i }.should == "5"
+ end
+
+ it "tries to convert other to string using to_str" do
+ other = mock('abd')
+ def other.to_str() "abd" end
+
+ a = []
+ "abc".upto(other) { |s| a << s }
+ a.should == ["abc", "abd"]
+ end
+
+ it "raises a TypeError if other can't be converted to a string" do
+ -> { "abc".upto(123) { } }.should raise_error(TypeError)
+ -> { "abc".upto(mock('x')){ } }.should raise_error(TypeError)
+ end
+
+
+ it "does not work with symbols" do
+ -> { "a".upto(:c).to_a }.should raise_error(TypeError)
+ end
+
+ it "returns non-alphabetic characters in the ASCII range for single letters" do
+ "9".upto("A").to_a.should == ["9", ":", ";", "<", "=", ">", "?", "@", "A"]
+ "Z".upto("a").to_a.should == ["Z", "[", "\\", "]", "^", "_", "`", "a"]
+ "z".upto("~").to_a.should == ["z", "{", "|", "}", "~"]
+ end
+
+ it "stops before the last value if exclusive" do
+ a = []
+ "a".upto("d", true) { |s| a << s}
+ a.should == ["a", "b", "c"]
+ end
+
+ it "works with non-ASCII ranges" do
+ a = []
+ 'Σ'.upto('Ω') { |s| a << s }
+ a.should == ["Σ", "Τ", "Υ", "Φ", "Χ", "Ψ", "Ω"]
+ end
+
+ describe "on sequence of numbers" do
+ it "calls the block as Integer#upto" do
+ "8".upto("11").to_a.should == 8.upto(11).map(&:to_s)
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an enumerator" do
+ enum = "aaa".upto("baa", true)
+ enum.should be_an_instance_of(Enumerator)
+ enum.count.should == 26**2
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ "a".upto("b").size.should == nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/string/valid_encoding/utf_8_spec.rb b/spec/ruby/core/string/valid_encoding/utf_8_spec.rb
new file mode 100644
index 0000000000..a14c3af830
--- /dev/null
+++ b/spec/ruby/core/string/valid_encoding/utf_8_spec.rb
@@ -0,0 +1,214 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../../spec_helper'
+
+describe "String#valid_encoding? and UTF-8" do
+ def utf8(bytes)
+ bytes.pack("C*").force_encoding("UTF-8")
+ end
+
+ describe "1-byte character" do
+ it "is valid if is in format 0xxxxxxx" do
+ utf8([0b00000000]).valid_encoding?.should == true
+ utf8([0b01111111]).valid_encoding?.should == true
+ end
+
+ it "is not valid if is not in format 0xxxxxxx" do
+ utf8([0b10000000]).valid_encoding?.should == false
+ utf8([0b11111111]).valid_encoding?.should == false
+ end
+ end
+
+ describe "2-bytes character" do
+ it "is valid if in format [110xxxxx 10xxxxx]" do
+ utf8([0b11000010, 0b10000000]).valid_encoding?.should == true
+ utf8([0b11000010, 0b10111111]).valid_encoding?.should == true
+
+ utf8([0b11011111, 0b10000000]).valid_encoding?.should == true
+ utf8([0b11011111, 0b10111111]).valid_encoding?.should == true
+ end
+
+ it "is not valid if the first byte is not in format 110xxxxx" do
+ utf8([0b00000010, 0b10000000]).valid_encoding?.should == false
+ utf8([0b00100010, 0b10000000]).valid_encoding?.should == false
+ utf8([0b01000010, 0b10000000]).valid_encoding?.should == false
+ utf8([0b01100010, 0b10000000]).valid_encoding?.should == false
+ utf8([0b10000010, 0b10000000]).valid_encoding?.should == false
+ utf8([0b10100010, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11000010, 0b10000000]).valid_encoding?.should == true # correct bytes
+ utf8([0b11100010, 0b10000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the second byte is not in format 10xxxxxx" do
+ utf8([0b11000010, 0b00000000]).valid_encoding?.should == false
+ utf8([0b11000010, 0b01000000]).valid_encoding?.should == false
+ utf8([0b11000010, 0b11000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if is smaller than [xxxxxx10 xx000000] (codepoints < U+007F, that are encoded with the 1-byte format)" do
+ utf8([0b11000000, 0b10111111]).valid_encoding?.should == false
+ utf8([0b11000001, 0b10111111]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the first byte is missing" do
+ bytes = [0b11000010, 0b10000000]
+ utf8(bytes[1..1]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the second byte is missing" do
+ bytes = [0b11000010, 0b10000000]
+ utf8(bytes[0..0]).valid_encoding?.should == false
+ end
+ end
+
+ describe "3-bytes character" do
+ it "is valid if in format [1110xxxx 10xxxxxx 10xxxxxx]" do
+ utf8([0b11100000, 0b10100000, 0b10000000]).valid_encoding?.should == true
+ utf8([0b11100000, 0b10100000, 0b10111111]).valid_encoding?.should == true
+ utf8([0b11100000, 0b10111111, 0b10111111]).valid_encoding?.should == true
+ utf8([0b11101111, 0b10111111, 0b10111111]).valid_encoding?.should == true
+ end
+
+ it "is not valid if the first byte is not in format 1110xxxx" do
+ utf8([0b00000000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b00010000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b00100000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b00110000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b01000000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b01010000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b01100000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b01110000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b10000000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b10010000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b10100000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b10110000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11000000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11010000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11100000, 0b10100000, 0b10000000]).valid_encoding?.should == true # correct bytes
+ utf8([0b11110000, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the second byte is not in format 10xxxxxx" do
+ utf8([0b11100000, 0b00100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11100000, 0b01100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11100000, 0b11100000, 0b10000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the third byte is not in format 10xxxxxx" do
+ utf8([0b11100000, 0b10100000, 0b00000000]).valid_encoding?.should == false
+ utf8([0b11100000, 0b10100000, 0b01000000]).valid_encoding?.should == false
+ utf8([0b11100000, 0b10100000, 0b01000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if is smaller than [xxxx0000 xx100000 xx000000] (codepoints < U+07FF that are encoded with the 2-byte format)" do
+ utf8([0b11100000, 0b10010000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11100000, 0b10001000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11100000, 0b10000100, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11100000, 0b10000010, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11100000, 0b10000001, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11100000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if in range [xxxx1101 xx100000 xx000000] - [xxxx1101 xx111111 xx111111] (codepoints U+D800 - U+DFFF)" do
+ utf8([0b11101101, 0b10100000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11101101, 0b10100000, 0b10000001]).valid_encoding?.should == false
+ utf8([0b11101101, 0b10111111, 0b10111111]).valid_encoding?.should == false
+
+ utf8([0b11101101, 0b10011111, 0b10111111]).valid_encoding?.should == true # lower boundary - 1
+ utf8([0b11101110, 0b10000000, 0b10000000]).valid_encoding?.should == true # upper boundary + 1
+ end
+
+ it "is not valid if the first byte is missing" do
+ bytes = [0b11100000, 0b10100000, 0b10000000]
+ utf8(bytes[2..3]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the second byte is missing" do
+ bytes = [0b11100000, 0b10100000, 0b10000000]
+ utf8([bytes[0], bytes[2]]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the second and the third bytes are missing" do
+ bytes = [0b11100000, 0b10100000, 0b10000000]
+ utf8(bytes[0..0]).valid_encoding?.should == false
+ end
+ end
+
+ describe "4-bytes character" do
+ it "is valid if in format [11110xxx 10xxxxxx 10xxxxxx 10xxxxxx]" do
+ utf8([0b11110000, 0b10010000, 0b10000000, 0b10000000]).valid_encoding?.should == true
+ utf8([0b11110000, 0b10010000, 0b10000000, 0b10111111]).valid_encoding?.should == true
+ utf8([0b11110000, 0b10010000, 0b10111111, 0b10111111]).valid_encoding?.should == true
+ utf8([0b11110000, 0b10111111, 0b10111111, 0b10111111]).valid_encoding?.should == true
+ utf8([0b11110100, 0b10001111, 0b10111111, 0b10111111]).valid_encoding?.should == true
+ end
+
+ it "is not valid if the first byte is not in format 11110xxx" do
+ utf8([0b11100000, 0b10010000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11010000, 0b10010000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b10110000, 0b10010000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b01110000, 0b10010000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the second byte is not in format 10xxxxxx" do
+ utf8([0b11110000, 0b00010000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b01010000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10010000, 0b10000000, 0b10000000]).valid_encoding?.should == true # correct bytes
+ utf8([0b11110000, 0b11010000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the third byte is not in format 10xxxxxx" do
+ utf8([0b11110000, 0b10010000, 0b00000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10010000, 0b01000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10010000, 0b10000000, 0b10000000]).valid_encoding?.should == true # correct bytes
+ utf8([0b11110000, 0b10010000, 0b11000000, 0b10000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the forth byte is not in format 10xxxxxx" do
+ utf8([0b11110000, 0b10010000, 0b10000000, 0b00000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10010000, 0b10000000, 0b01000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10010000, 0b10000000, 0b10000000]).valid_encoding?.should == true # correct bytes
+ utf8([0b11110000, 0b10010000, 0b10000000, 0b11000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if is smaller than [xxxxx000 xx001000 xx000000 xx000000] (codepoint < U+10000)" do
+ utf8([0b11110000, 0b10000111, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10000110, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10000101, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10000100, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10000011, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10000010, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10000001, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110000, 0b10000000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ end
+
+ it "is not valid if is greater than [xxxxx100 xx001111 xx111111 xx111111] (codepoint > U+10FFFF)" do
+ utf8([0b11110100, 0b10010000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110100, 0b10100000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+ utf8([0b11110100, 0b10110000, 0b10000000, 0b10000000]).valid_encoding?.should == false
+
+ utf8([0b11110101, 0b10001111, 0b10111111, 0b10111111]).valid_encoding?.should == false
+ utf8([0b11110110, 0b10001111, 0b10111111, 0b10111111]).valid_encoding?.should == false
+ utf8([0b11110111, 0b10001111, 0b10111111, 0b10111111]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the first byte is missing" do
+ bytes = [0b11110000, 0b10010000, 0b10000000, 0b10000000]
+ utf8(bytes[1..3]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the second byte is missing" do
+ bytes = [0b11110000, 0b10010000, 0b10000000, 0b10000000]
+ utf8([bytes[0], bytes[2], bytes[3]]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the second and the third bytes are missing" do
+ bytes = [0b11110000, 0b10010000, 0b10000000, 0b10000000]
+ utf8([bytes[0], bytes[3]]).valid_encoding?.should == false
+ end
+
+ it "is not valid if the second, the third and the fourth bytes are missing" do
+ bytes = [0b11110000, 0b10010000, 0b10000000, 0b10000000]
+ utf8(bytes[0..0]).valid_encoding?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/string/valid_encoding_spec.rb b/spec/ruby/core/string/valid_encoding_spec.rb
new file mode 100644
index 0000000000..be7cef7a8e
--- /dev/null
+++ b/spec/ruby/core/string/valid_encoding_spec.rb
@@ -0,0 +1,135 @@
+require_relative '../../spec_helper'
+
+describe "String#valid_encoding?" do
+ it "returns true if the String's encoding is valid" do
+ "a".valid_encoding?.should be_true
+ "\u{8365}\u{221}".valid_encoding?.should be_true
+ end
+
+ it "returns true if self is valid in the current encoding and other encodings" do
+ str = "\x77"
+ str.force_encoding('utf-8').valid_encoding?.should be_true
+ str.force_encoding('binary').valid_encoding?.should be_true
+ end
+
+ it "returns true for all encodings self is valid in" do
+ str = "\xE6\x9D\x94"
+ str.force_encoding('BINARY').valid_encoding?.should be_true
+ str.force_encoding('UTF-8').valid_encoding?.should be_true
+ str.force_encoding('US-ASCII').valid_encoding?.should be_false
+ str.force_encoding('Big5').valid_encoding?.should be_false
+ str.force_encoding('CP949').valid_encoding?.should be_false
+ str.force_encoding('Emacs-Mule').valid_encoding?.should be_false
+ str.force_encoding('EUC-JP').valid_encoding?.should be_false
+ str.force_encoding('EUC-KR').valid_encoding?.should be_false
+ str.force_encoding('EUC-TW').valid_encoding?.should be_false
+ str.force_encoding('GB18030').valid_encoding?.should be_false
+ str.force_encoding('GBK').valid_encoding?.should be_false
+ str.force_encoding('ISO-8859-1').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-2').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-3').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-4').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-5').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-6').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-7').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-8').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-9').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-10').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-11').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-13').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-14').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-15').valid_encoding?.should be_true
+ str.force_encoding('ISO-8859-16').valid_encoding?.should be_true
+ str.force_encoding('KOI8-R').valid_encoding?.should be_true
+ str.force_encoding('KOI8-U').valid_encoding?.should be_true
+ str.force_encoding('Shift_JIS').valid_encoding?.should be_false
+ "\xD8\x00".force_encoding('UTF-16BE').valid_encoding?.should be_false
+ "\x00\xD8".force_encoding('UTF-16LE').valid_encoding?.should be_false
+ "\x04\x03\x02\x01".force_encoding('UTF-32BE').valid_encoding?.should be_false
+ "\x01\x02\x03\x04".force_encoding('UTF-32LE').valid_encoding?.should be_false
+ str.force_encoding('Windows-1251').valid_encoding?.should be_true
+ str.force_encoding('IBM437').valid_encoding?.should be_true
+ str.force_encoding('IBM737').valid_encoding?.should be_true
+ str.force_encoding('IBM775').valid_encoding?.should be_true
+ str.force_encoding('CP850').valid_encoding?.should be_true
+ str.force_encoding('IBM852').valid_encoding?.should be_true
+ str.force_encoding('CP852').valid_encoding?.should be_true
+ str.force_encoding('IBM855').valid_encoding?.should be_true
+ str.force_encoding('CP855').valid_encoding?.should be_true
+ str.force_encoding('IBM857').valid_encoding?.should be_true
+ str.force_encoding('IBM860').valid_encoding?.should be_true
+ str.force_encoding('IBM861').valid_encoding?.should be_true
+ str.force_encoding('IBM862').valid_encoding?.should be_true
+ str.force_encoding('IBM863').valid_encoding?.should be_true
+ str.force_encoding('IBM864').valid_encoding?.should be_true
+ str.force_encoding('IBM865').valid_encoding?.should be_true
+ str.force_encoding('IBM866').valid_encoding?.should be_true
+ str.force_encoding('IBM869').valid_encoding?.should be_true
+ str.force_encoding('Windows-1258').valid_encoding?.should be_true
+ str.force_encoding('GB1988').valid_encoding?.should be_true
+ str.force_encoding('macCentEuro').valid_encoding?.should be_true
+ str.force_encoding('macCroatian').valid_encoding?.should be_true
+ str.force_encoding('macCyrillic').valid_encoding?.should be_true
+ str.force_encoding('macGreek').valid_encoding?.should be_true
+ str.force_encoding('macIceland').valid_encoding?.should be_true
+ str.force_encoding('macRoman').valid_encoding?.should be_true
+ str.force_encoding('macRomania').valid_encoding?.should be_true
+ str.force_encoding('macThai').valid_encoding?.should be_true
+ str.force_encoding('macTurkish').valid_encoding?.should be_true
+ str.force_encoding('macUkraine').valid_encoding?.should be_true
+ str.force_encoding('stateless-ISO-2022-JP').valid_encoding?.should be_false
+ str.force_encoding('eucJP-ms').valid_encoding?.should be_false
+ str.force_encoding('CP51932').valid_encoding?.should be_false
+ str.force_encoding('GB2312').valid_encoding?.should be_false
+ str.force_encoding('GB12345').valid_encoding?.should be_false
+ str.force_encoding('ISO-2022-JP').valid_encoding?.should be_true
+ str.force_encoding('ISO-2022-JP-2').valid_encoding?.should be_true
+ str.force_encoding('CP50221').valid_encoding?.should be_true
+ str.force_encoding('Windows-1252').valid_encoding?.should be_true
+ str.force_encoding('Windows-1250').valid_encoding?.should be_true
+ str.force_encoding('Windows-1256').valid_encoding?.should be_true
+ str.force_encoding('Windows-1253').valid_encoding?.should be_true
+ str.force_encoding('Windows-1255').valid_encoding?.should be_true
+ str.force_encoding('Windows-1254').valid_encoding?.should be_true
+ str.force_encoding('TIS-620').valid_encoding?.should be_true
+ str.force_encoding('Windows-874').valid_encoding?.should be_true
+ str.force_encoding('Windows-1257').valid_encoding?.should be_true
+ str.force_encoding('Windows-31J').valid_encoding?.should be_false
+ str.force_encoding('MacJapanese').valid_encoding?.should be_false
+ str.force_encoding('UTF-7').valid_encoding?.should be_true
+ str.force_encoding('UTF8-MAC').valid_encoding?.should be_true
+ end
+
+ ruby_version_is '3.0' do
+ it "returns true for IBM720 encoding self is valid in" do
+ str = "\xE6\x9D\x94"
+ str.force_encoding('IBM720').valid_encoding?.should be_true
+ str.force_encoding('CP720').valid_encoding?.should be_true
+ end
+ end
+
+ it "returns false if self is valid in one encoding, but invalid in the one it's tagged with" do
+ str = "\u{8765}"
+ str.valid_encoding?.should be_true
+ str = str.force_encoding('ascii')
+ str.valid_encoding?.should be_false
+ end
+
+ it "returns false if self contains a character invalid in the associated encoding" do
+ "abc#{[0x80].pack('C')}".force_encoding('ascii').valid_encoding?.should be_false
+ end
+
+ it "returns false if a valid String had an invalid character appended to it" do
+ str = "a"
+ str.valid_encoding?.should be_true
+ str << [0xDD].pack('C').force_encoding('utf-8')
+ str.valid_encoding?.should be_false
+ end
+
+ it "returns true if an invalid string is appended another invalid one but both make a valid string" do
+ str = [0xD0].pack('C').force_encoding('utf-8')
+ str.valid_encoding?.should be_false
+ str << [0xBF].pack('C').force_encoding('utf-8')
+ str.valid_encoding?.should be_true
+ end
+end
diff --git a/spec/ruby/core/struct/clone_spec.rb b/spec/ruby/core/struct/clone_spec.rb
new file mode 100644
index 0000000000..40c4d52d57
--- /dev/null
+++ b/spec/ruby/core/struct/clone_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/dup'
+
+describe "Struct-based class#clone" do
+ it_behaves_like :struct_dup, :clone
+end
diff --git a/spec/ruby/core/struct/deconstruct_keys_spec.rb b/spec/ruby/core/struct/deconstruct_keys_spec.rb
new file mode 100644
index 0000000000..088803d028
--- /dev/null
+++ b/spec/ruby/core/struct/deconstruct_keys_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+
+describe "Struct#deconstruct_keys" do
+ it "returns a hash of attributes" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct_keys([:x, :y]).should == {x: 1, y: 2}
+ end
+
+ it "requires one argument" do
+ struct = Struct.new(:x)
+ obj = struct.new(1)
+
+ -> {
+ obj.deconstruct_keys
+ }.should raise_error(ArgumentError, /wrong number of arguments \(given 0, expected 1\)/)
+ end
+
+ it "returns only specified keys" do
+ struct = Struct.new(:x, :y, :z)
+ s = struct.new(1, 2, 3)
+
+ s.deconstruct_keys([:x, :y]).should == {x: 1, y: 2}
+ s.deconstruct_keys([:x] ).should == {x: 1}
+ s.deconstruct_keys([] ).should == {}
+ end
+
+ it "accepts string attribute names" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct_keys(['x', 'y']).should == {'x' => 1, 'y' => 2}
+ end
+
+ it "accepts argument position number as well but returns them as keys" do
+ struct = Struct.new(:x, :y, :z)
+ s = struct.new(10, 20, 30)
+
+ s.deconstruct_keys([0, 1, 2]).should == {0 => 10, 1 => 20, 2 => 30}
+ s.deconstruct_keys([0, 1] ).should == {0 => 10, 1 => 20}
+ s.deconstruct_keys([0] ).should == {0 => 10}
+ end
+
+ it "returns an empty hash when there are more keys than attributes" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct_keys([:x, :y, :a]).should == {}
+ end
+
+ it "returns at first not existing attribute name" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct_keys([:a, :x]).should == {}
+ s.deconstruct_keys([:x, :a]).should == {x: 1}
+ end
+
+ it "accepts nil argument and return all the attributes" do
+ struct = Struct.new(:x, :y)
+ obj = struct.new(1, 2)
+
+ obj.deconstruct_keys(nil).should == {x: 1, y: 2}
+ end
+
+ it "raise TypeError if passed anything accept nil or array" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ -> { s.deconstruct_keys('x') }.should raise_error(TypeError, /expected Array or nil/)
+ -> { s.deconstruct_keys(1) }.should raise_error(TypeError, /expected Array or nil/)
+ -> { s.deconstruct_keys(:x) }.should raise_error(TypeError, /expected Array or nil/)
+ -> { s.deconstruct_keys({}) }.should raise_error(TypeError, /expected Array or nil/)
+ end
+end
diff --git a/spec/ruby/core/struct/deconstruct_spec.rb b/spec/ruby/core/struct/deconstruct_spec.rb
new file mode 100644
index 0000000000..32d4f6bac4
--- /dev/null
+++ b/spec/ruby/core/struct/deconstruct_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+describe "Struct#deconstruct" do
+ it "returns an array of attribute values" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct.should == [1, 2]
+ end
+end
diff --git a/spec/ruby/core/struct/dig_spec.rb b/spec/ruby/core/struct/dig_spec.rb
new file mode 100644
index 0000000000..93a52dbbe1
--- /dev/null
+++ b/spec/ruby/core/struct/dig_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+
+describe "Struct#dig" do
+ before(:each) do
+ @klass = Struct.new(:a)
+ @instance = @klass.new(@klass.new({ b: [1, 2, 3] }))
+ end
+
+ it "returns the nested value specified by the sequence of keys" do
+ @instance.dig(:a, :a).should == { b: [1, 2, 3] }
+ end
+
+ it "accepts String keys" do
+ @instance.dig('a', 'a').should == { b: [1, 2, 3] }
+ end
+
+ it "returns the value by the index" do
+ instance = Struct.new(:a, :b).new(:one, :two)
+ instance.dig(0).should == :one
+ instance.dig(1).should == :two
+ end
+
+ it "returns the nested value specified if the sequence includes an index" do
+ @instance.dig(:a, :a, :b, 0).should == 1
+ end
+
+ it "returns nil if any intermediate step is nil" do
+ @instance.dig(:b, 0).should == nil
+ end
+
+ it "raises a TypeError if any intermediate step does not respond to #dig" do
+ instance = @klass.new(1)
+ -> {
+ instance.dig(:a, 3)
+ }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError if no arguments provided" do
+ -> { @instance.dig }.should raise_error(ArgumentError)
+ end
+
+ it "calls #dig on any intermediate step with the rest of the sequence as arguments" do
+ obj = Object.new
+ instance = @klass.new(obj)
+
+ def obj.dig(*args)
+ {dug: args}
+ end
+
+ instance.dig(:a, :bar, :baz).should == { dug: [:bar, :baz] }
+ end
+end
diff --git a/spec/ruby/core/struct/dup_spec.rb b/spec/ruby/core/struct/dup_spec.rb
new file mode 100644
index 0000000000..8b50c39014
--- /dev/null
+++ b/spec/ruby/core/struct/dup_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/dup'
+
+describe "Struct-based class#dup" do
+
+ it_behaves_like :struct_dup, :dup
+
+ # From https://github.com/jruby/jruby/issues/3686
+ it "retains an included module in the ancestor chain for the struct's singleton class" do
+ klass = Struct.new(:foo)
+ mod = Module.new do
+ def hello
+ "hello"
+ end
+ end
+
+ klass.extend(mod)
+ klass_dup = klass.dup
+ klass_dup.hello.should == "hello"
+ end
+
+end
diff --git a/spec/ruby/core/struct/each_pair_spec.rb b/spec/ruby/core/struct/each_pair_spec.rb
new file mode 100644
index 0000000000..1230ca9026
--- /dev/null
+++ b/spec/ruby/core/struct/each_pair_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/accessor'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Struct#each_pair" do
+ before :each do
+ @car = StructClasses::Car.new('Ford', 'Ranger', 2001)
+ end
+
+ it "passes each key value pair to the given block" do
+ @car.each_pair do |key, value|
+ value.should == @car[key]
+ end
+ end
+
+ context "with a block variable" do
+ it "passes an array to the given block" do
+ @car.each_pair.map { |var| var }.should == StructClasses::Car.members.zip(@car.values)
+ end
+ end
+
+ it "returns self if passed a block" do
+ @car.each_pair {}.should equal(@car)
+ end
+
+ it "returns an Enumerator if not passed a block" do
+ @car.each_pair.should be_an_instance_of(Enumerator)
+ end
+
+ it_behaves_like :struct_accessor, :each_pair
+ it_behaves_like :enumeratorized_with_origin_size, :each_pair, StructClasses::Car.new('Ford', 'Ranger')
+end
diff --git a/spec/ruby/core/struct/each_spec.rb b/spec/ruby/core/struct/each_spec.rb
new file mode 100644
index 0000000000..41c0fbd4d0
--- /dev/null
+++ b/spec/ruby/core/struct/each_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/accessor'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Struct#each" do
+ it "passes each value to the given block" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ i = -1
+ car.each do |value|
+ value.should == car[i += 1]
+ end
+ end
+
+ it "returns self if passed a block" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ car.each {}.should == car
+ end
+
+ it "returns an Enumerator if not passed a block" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ car.each.should be_an_instance_of(Enumerator)
+ end
+
+ it_behaves_like :struct_accessor, :each
+ it_behaves_like :enumeratorized_with_origin_size, :each, StructClasses::Car.new('Ford', 'Ranger')
+end
diff --git a/spec/ruby/core/struct/element_reference_spec.rb b/spec/ruby/core/struct/element_reference_spec.rb
new file mode 100644
index 0000000000..0f6d547f66
--- /dev/null
+++ b/spec/ruby/core/struct/element_reference_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Struct[]" do
+ it "is a synonym for new" do
+ StructClasses::Ruby['2.0', 'i686'].should be_kind_of(StructClasses::Ruby)
+ end
+end
+
+describe "Struct#[]" do
+ it "returns the attribute referenced" do
+ car = StructClasses::Car.new('Ford', 'Ranger', 1983)
+ car['make'].should == 'Ford'
+ car['model'].should == 'Ranger'
+ car['year'].should == 1983
+ car[:make].should == 'Ford'
+ car[:model].should == 'Ranger'
+ car[:year].should == 1983
+ car[0].should == 'Ford'
+ car[1].should == 'Ranger'
+ car[2].should == 1983
+ car[-3].should == 'Ford'
+ car[-2].should == 'Ranger'
+ car[-1].should == 1983
+ end
+
+ it "fails when it does not know about the requested attribute" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ -> { car[3] }.should raise_error(IndexError)
+ -> { car[-4] }.should raise_error(IndexError)
+ -> { car[:body] }.should raise_error(NameError)
+ -> { car['wheels'] }.should raise_error(NameError)
+ end
+
+ it "fails if passed too many arguments" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ -> { car[:make, :model] }.should raise_error(ArgumentError)
+ end
+
+ it "fails if not passed a string, symbol, or integer" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ -> { car[Object.new] }.should raise_error(TypeError)
+ end
+
+ it "returns attribute names that contain hyphens" do
+ klass = Struct.new(:'current-state')
+ tuple = klass.new(0)
+ tuple['current-state'].should == 0
+ tuple[:'current-state'].should == 0
+ tuple[0].should == 0
+ end
+end
diff --git a/spec/ruby/core/struct/element_set_spec.rb b/spec/ruby/core/struct/element_set_spec.rb
new file mode 100644
index 0000000000..6ba7b081a9
--- /dev/null
+++ b/spec/ruby/core/struct/element_set_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Struct#[]=" do
+ it "assigns the passed value" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+
+ car[:model] = 'Escape'
+ car[:model].should == 'Escape'
+
+ car['model'] = 'Fusion'
+ car[:model].should == 'Fusion'
+
+ car[1] = 'Excursion'
+ car[:model].should == 'Excursion'
+
+ car[-1] = '2000-2005'
+ car[:year].should == '2000-2005'
+ end
+
+ it "fails when trying to assign attributes which don't exist" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+
+ -> { car[:something] = true }.should raise_error(NameError)
+ -> { car[3] = true }.should raise_error(IndexError)
+ -> { car[-4] = true }.should raise_error(IndexError)
+ -> { car[Object.new] = true }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/struct/eql_spec.rb b/spec/ruby/core/struct/eql_spec.rb
new file mode 100644
index 0000000000..c864b2b943
--- /dev/null
+++ b/spec/ruby/core/struct/eql_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/equal_value'
+
+describe "Struct#eql?" do
+ it_behaves_like :struct_equal_value, :eql?
+
+ it "returns false if any corresponding elements are not #eql?" do
+ car = StructClasses::Car.new("Honda", "Accord", 1998)
+ similar_car = StructClasses::Car.new("Honda", "Accord", 1998.0)
+ car.should_not eql(similar_car)
+ end
+end
diff --git a/spec/ruby/core/struct/equal_value_spec.rb b/spec/ruby/core/struct/equal_value_spec.rb
new file mode 100644
index 0000000000..0c0f7ba570
--- /dev/null
+++ b/spec/ruby/core/struct/equal_value_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/equal_value'
+
+describe "Struct#==" do
+ it_behaves_like :struct_equal_value, :==
+end
diff --git a/spec/ruby/core/struct/filter_spec.rb b/spec/ruby/core/struct/filter_spec.rb
new file mode 100644
index 0000000000..0ccd8ad6b2
--- /dev/null
+++ b/spec/ruby/core/struct/filter_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/select'
+require_relative 'shared/accessor'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Struct#filter" do
+ it_behaves_like :struct_select, :filter
+ it_behaves_like :struct_accessor, :filter
+ it_behaves_like :enumeratorized_with_origin_size, :filter, Struct.new(:foo).new
+end
diff --git a/spec/ruby/core/struct/fixtures/classes.rb b/spec/ruby/core/struct/fixtures/classes.rb
new file mode 100644
index 0000000000..6d620f9060
--- /dev/null
+++ b/spec/ruby/core/struct/fixtures/classes.rb
@@ -0,0 +1,26 @@
+module StructClasses
+
+ class Apple < Struct; end
+
+ Ruby = Struct.new(:version, :platform)
+
+ Car = Struct.new(:make, :model, :year)
+
+ class Honda < Car
+ def initialize(*args)
+ self.make = "Honda"
+ super(*args)
+ end
+ end
+
+ class SubclassX < Struct
+ end
+
+ class SubclassX
+ attr_reader :key
+ def initialize(*)
+ @key = :value
+ super
+ end
+ end
+end
diff --git a/spec/ruby/core/struct/hash_spec.rb b/spec/ruby/core/struct/hash_spec.rb
new file mode 100644
index 0000000000..53361eb7a9
--- /dev/null
+++ b/spec/ruby/core/struct/hash_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/accessor'
+
+describe "Struct#hash" do
+
+ it "returns the same integer for structs with the same content" do
+ [StructClasses::Ruby.new("1.8.6", "PPC"),
+ StructClasses::Car.new("Hugo", "Foo", "1972")].each do |stc|
+ stc.hash.should == stc.dup.hash
+ stc.hash.should be_kind_of(Integer)
+ end
+ end
+
+ it "returns the same value if structs are #eql?" do
+ car = StructClasses::Car.new("Honda", "Accord", "1998")
+ similar_car = StructClasses::Car.new("Honda", "Accord", "1998")
+ car.should eql(similar_car)
+ car.hash.should == similar_car.hash
+ end
+
+ it "returns different hashes for structs with different values" do
+ s1 = StructClasses::Ruby.new('2.7.0', 'linux')
+ s2 = StructClasses::Ruby.new('2.7.0', 'macos')
+ s1.hash.should_not == s2.hash
+ end
+
+ it "returns different hashes for structs with different values when using keyword_init: true" do
+ key = :"1 non symbol member"
+ struct_class = Struct.new(key, keyword_init: true)
+ t1 = struct_class.new(key => 1)
+ t2 = struct_class.new(key => 2)
+ t1.hash.should_not == t2.hash
+ end
+
+ it "allows for overriding methods in an included module" do
+ mod = Module.new do
+ def hash
+ "different"
+ end
+ end
+ s = Struct.new(:arg) do
+ include mod
+ end
+ s.new.hash.should == "different"
+ end
+
+ it "returns the same hash for recursive structs" do
+ car = StructClasses::Car.new("Honda", "Accord", "1998")
+ similar_car = StructClasses::Car.new("Honda", "Accord", "1998")
+ car[:make] = car
+ similar_car[:make] = car
+ car.hash.should == similar_car.hash
+ # This is because car.eql?(similar_car).
+ # Objects that are eql? must return the same hash.
+ # See the Struct#eql? specs
+ end
+
+ it "returns different hashes for different struct classes" do
+ Struct.new(:x).new(1).hash.should != Struct.new(:y).new(1).hash
+ end
+
+ it_behaves_like :struct_accessor, :hash
+end
diff --git a/spec/ruby/core/struct/initialize_spec.rb b/spec/ruby/core/struct/initialize_spec.rb
new file mode 100644
index 0000000000..cfb302209e
--- /dev/null
+++ b/spec/ruby/core/struct/initialize_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Struct#initialize" do
+
+ it "is private" do
+ StructClasses::Car.should have_private_instance_method(:initialize)
+ end
+
+ it 'allows valid Ruby method names for members' do
+ valid_method_names = [
+ :method1,
+ :method_1,
+ :method_1?,
+ :method_1!,
+ :a_method
+ ]
+ valid_method_names.each do |method_name|
+ klass = Struct.new(method_name)
+ instance = klass.new(:value)
+ instance.send(method_name).should == :value
+ writer_method = "#{method_name}=".to_sym
+ result = instance.send(writer_method, :new_value)
+ result.should == :new_value
+ instance.send(method_name).should == :new_value
+ end
+ end
+
+ it "does nothing when passed a set of fields equal to self" do
+ car = same_car = StructClasses::Car.new("Honda", "Accord", "1998")
+ car.instance_eval { initialize("Honda", "Accord", "1998") }
+ car.should == same_car
+ end
+
+ it "explicitly sets instance variables to nil when args not provided to initialize" do
+ car = StructClasses::Honda.new
+ car.make.should == nil # still nil despite override in Honda#initialize b/c of super order
+ end
+
+ it "can be overridden" do
+ StructClasses::SubclassX.new(:y).new.key.should == :value
+ end
+
+ ruby_version_is "3.1"..."3.2" do
+ it "warns about passing only keyword arguments" do
+ -> {
+ StructClasses::Ruby.new(version: "3.1", platform: "OS")
+ }.should complain(/warning: Passing only keyword arguments/)
+ end
+ end
+end
diff --git a/spec/ruby/core/struct/inspect_spec.rb b/spec/ruby/core/struct/inspect_spec.rb
new file mode 100644
index 0000000000..83e13597ba
--- /dev/null
+++ b/spec/ruby/core/struct/inspect_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/inspect'
+
+describe "Struct#inspect" do
+ it "returns a string representation showing members and values" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ car.inspect.should == '#<struct StructClasses::Car make="Ford", model="Ranger", year=nil>'
+ end
+
+ it_behaves_like :struct_inspect, :inspect
+end
diff --git a/spec/ruby/core/struct/instance_variable_get_spec.rb b/spec/ruby/core/struct/instance_variable_get_spec.rb
new file mode 100644
index 0000000000..e4a3ea87dc
--- /dev/null
+++ b/spec/ruby/core/struct/instance_variable_get_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Struct#instance_variable_get" do
+ it "returns nil for attributes" do
+ car = StructClasses::Car.new("Hugo", "Foo", "1972")
+ car.instance_variable_get(:@make).should be_nil
+ end
+
+ it "returns a user value for variables with the same name as attributes" do
+ car = StructClasses::Car.new("Hugo", "Foo", "1972")
+ car.instance_variable_set :@make, "explicit"
+ car.instance_variable_get(:@make).should == "explicit"
+ car.make.should == "Hugo"
+ end
+end
diff --git a/spec/ruby/core/struct/instance_variables_spec.rb b/spec/ruby/core/struct/instance_variables_spec.rb
new file mode 100644
index 0000000000..f6d30ea97e
--- /dev/null
+++ b/spec/ruby/core/struct/instance_variables_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Struct#instance_variables" do
+ it "returns an empty array if only attributes are defined" do
+ car = StructClasses::Car.new("Hugo", "Foo", "1972")
+ car.instance_variables.should == []
+ end
+
+ it "returns an array with one name if an instance variable is added" do
+ car = StructClasses::Car.new("Hugo", "Foo", "1972")
+ car.instance_variables.should == []
+ car.instance_variable_set("@test", 1)
+ car.instance_variables.should == [:@test]
+ end
+end
diff --git a/spec/ruby/core/struct/keyword_init_spec.rb b/spec/ruby/core/struct/keyword_init_spec.rb
new file mode 100644
index 0000000000..061f4c56e0
--- /dev/null
+++ b/spec/ruby/core/struct/keyword_init_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.1" do
+ # See https://bugs.ruby-lang.org/issues/18008
+ describe "StructClass#keyword_init?" do
+ it "returns true for a struct that accepts keyword arguments to initialize" do
+ struct = Struct.new(:arg, keyword_init: true)
+ struct.keyword_init?.should be_true
+ end
+
+ it "returns false for a struct that does not accept keyword arguments to initialize" do
+ struct = Struct.new(:arg, keyword_init: false)
+ struct.keyword_init?.should be_false
+ end
+
+ it "returns nil for a struct that did not explicitly specify keyword_init" do
+ struct = Struct.new(:arg)
+ struct.keyword_init?.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/struct/length_spec.rb b/spec/ruby/core/struct/length_spec.rb
new file mode 100644
index 0000000000..1143676122
--- /dev/null
+++ b/spec/ruby/core/struct/length_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/accessor'
+
+describe "Struct#length" do
+ it "returns the number of attributes" do
+ StructClasses::Car.new('Cadillac', 'DeVille').length.should == 3
+ StructClasses::Car.new.length.should == 3
+ end
+
+ it_behaves_like :struct_accessor, :length
+end
diff --git a/spec/ruby/core/struct/members_spec.rb b/spec/ruby/core/struct/members_spec.rb
new file mode 100644
index 0000000000..1f2ff950d9
--- /dev/null
+++ b/spec/ruby/core/struct/members_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/accessor'
+
+describe "Struct#members" do
+ it "returns an array of attribute names" do
+ StructClasses::Car.new.members.should == [:make, :model, :year]
+ StructClasses::Car.new('Cadillac').members.should == [:make, :model, :year]
+ StructClasses::Ruby.members.should == [:version, :platform]
+ end
+
+ it_behaves_like :struct_accessor, :members
+end
diff --git a/spec/ruby/core/struct/new_spec.rb b/spec/ruby/core/struct/new_spec.rb
new file mode 100644
index 0000000000..7b4a4f7980
--- /dev/null
+++ b/spec/ruby/core/struct/new_spec.rb
@@ -0,0 +1,234 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Struct.new" do
+ it "creates a constant in Struct namespace with string as first argument" do
+ struct = Struct.new('Animal', :name, :legs, :eyeballs)
+ struct.should == Struct::Animal
+ struct.name.should == "Struct::Animal"
+ end
+
+ it "overwrites previously defined constants with string as first argument" do
+ first = Struct.new('Person', :height, :weight)
+ first.should == Struct::Person
+
+ second = nil
+ -> {
+ second = Struct.new('Person', :hair, :sex)
+ }.should complain(/constant/)
+ second.should == Struct::Person
+
+ first.members.should_not == second.members
+ end
+
+ it "calls to_str on its first argument (constant name)" do
+ obj = mock('Foo')
+ def obj.to_str() "Foo" end
+ struct = Struct.new(obj)
+ struct.should == Struct::Foo
+ struct.name.should == "Struct::Foo"
+ end
+
+ it "creates a new anonymous class with nil first argument" do
+ struct = Struct.new(nil, :foo)
+ struct.new("bar").foo.should == "bar"
+ struct.should be_kind_of(Class)
+ struct.name.should be_nil
+ end
+
+ it "creates a new anonymous class with symbol arguments" do
+ struct = Struct.new(:make, :model)
+ struct.should be_kind_of(Class)
+ struct.name.should == nil
+ end
+
+ it "does not create a constant with symbol as first argument" do
+ Struct.new(:Animal2, :name, :legs, :eyeballs)
+ Struct.const_defined?("Animal2").should be_false
+ end
+
+
+ it "fails with invalid constant name as first argument" do
+ -> { Struct.new('animal', :name, :legs, :eyeballs) }.should raise_error(NameError)
+ end
+
+ it "raises a TypeError if object doesn't respond to to_sym" do
+ -> { Struct.new(:animal, mock('giraffe')) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, 1.0) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, Time.now) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, Class) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, nil) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, true) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, ['chris', 'evan']) }.should raise_error(TypeError)
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "raises a TypeError or ArgumentError if passed a Hash with an unknown key" do
+ # CRuby < 3.2 raises ArgumentError: unknown keyword: :name, but that seems a bug:
+ # https://bugs.ruby-lang.org/issues/18632
+ -> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(StandardError) { |e|
+ [ArgumentError, TypeError].should.include?(e.class)
+ }
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "raises a TypeError if passed a Hash with an unknown key" do
+ -> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(TypeError)
+ end
+ end
+
+ it "raises ArgumentError when there is a duplicate member" do
+ -> { Struct.new(:foo, :foo) }.should raise_error(ArgumentError, "duplicate member: foo")
+ end
+
+ it "raises a TypeError if object is not a Symbol" do
+ obj = mock(':ruby')
+ def obj.to_sym() :ruby end
+ -> { Struct.new(:animal, obj) }.should raise_error(TypeError)
+ end
+
+ it "processes passed block with instance_eval" do
+ klass = Struct.new(:something) { @something_else = 'something else entirely!' }
+ klass.instance_variables.should include(:@something_else)
+ end
+
+ context "with a block" do
+ it "allows class to be modified via the block" do
+ klass = Struct.new(:version) do
+ def platform
+ :ruby
+ end
+ end
+ instance = klass.new('2.2')
+
+ instance.version.should == '2.2'
+ instance.platform.should == :ruby
+ end
+
+ it "passes same struct class to the block" do
+ given = nil
+ klass = Struct.new(:attr) do |block_parameter|
+ given = block_parameter
+ end
+ klass.should equal(given)
+ end
+ end
+
+ context "on subclasses" do
+ it "creates a constant in subclass' namespace" do
+ struct = StructClasses::Apple.new('Computer', :size)
+ struct.should == StructClasses::Apple::Computer
+ end
+
+ it "creates an instance" do
+ StructClasses::Ruby.new.kind_of?(StructClasses::Ruby).should == true
+ end
+
+ it "creates reader methods" do
+ StructClasses::Ruby.new.should have_method(:version)
+ StructClasses::Ruby.new.should have_method(:platform)
+ end
+
+ it "creates writer methods" do
+ StructClasses::Ruby.new.should have_method(:version=)
+ StructClasses::Ruby.new.should have_method(:platform=)
+ end
+
+ it "fails with too many arguments" do
+ -> { StructClasses::Ruby.new('2.0', 'i686', true) }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.1' do
+ it "passes a hash as a normal argument" do
+ type = Struct.new(:args)
+
+ obj = suppress_warning {type.new(keyword: :arg)}
+ obj2 = type.new(*[{keyword: :arg}])
+
+ obj.should == obj2
+ obj.args.should == {keyword: :arg}
+ obj2.args.should == {keyword: :arg}
+ end
+ end
+
+ ruby_version_is '3.2' do
+ it "accepts keyword arguments to initialize" do
+ type = Struct.new(:args)
+
+ obj = type.new(args: 42)
+ obj2 = type.new(42)
+
+ obj.should == obj2
+ obj.args.should == 42
+ obj2.args.should == 42
+ end
+ end
+ end
+
+ context "keyword_init: true option" do
+ before :all do
+ @struct_with_kwa = Struct.new(:name, :legs, keyword_init: true)
+ end
+
+ it "creates a class that accepts keyword arguments to initialize" do
+ obj = @struct_with_kwa.new(name: "elefant", legs: 4)
+ obj.name.should == "elefant"
+ obj.legs.should == 4
+ end
+
+ it "raises when there is a duplicate member" do
+ -> { Struct.new(:foo, :foo, keyword_init: true) }.should raise_error(ArgumentError, "duplicate member: foo")
+ end
+
+ describe "new class instantiation" do
+ it "accepts arguments as hash as well" do
+ obj = @struct_with_kwa.new({name: "elefant", legs: 4})
+ obj.name.should == "elefant"
+ obj.legs.should == 4
+ end
+
+ it "allows missing arguments" do
+ obj = @struct_with_kwa.new(name: "elefant")
+ obj.name.should == "elefant"
+ obj.legs.should be_nil
+ end
+
+ it "allows no arguments" do
+ obj = @struct_with_kwa.new
+ obj.name.should be_nil
+ obj.legs.should be_nil
+ end
+
+ it "raises ArgumentError when passed not declared keyword argument" do
+ -> {
+ @struct_with_kwa.new(name: "elefant", legs: 4, foo: "bar")
+ }.should raise_error(ArgumentError, /unknown keywords: foo/)
+ end
+
+ it "raises ArgumentError when passed a list of arguments" do
+ -> {
+ @struct_with_kwa.new("elefant", 4)
+ }.should raise_error(ArgumentError, /wrong number of arguments/)
+ end
+
+ it "raises ArgumentError when passed a single non-hash argument" do
+ -> {
+ @struct_with_kwa.new("elefant")
+ }.should raise_error(ArgumentError, /wrong number of arguments/)
+ end
+ end
+ end
+
+ context "keyword_init: false option" do
+ before :all do
+ @struct_without_kwa = Struct.new(:name, :legs, keyword_init: false)
+ end
+
+ it "behaves like it does without :keyword_init option" do
+ obj = @struct_without_kwa.new("elefant", 4)
+ obj.name.should == "elefant"
+ obj.legs.should == 4
+ end
+ end
+end
diff --git a/spec/ruby/core/struct/select_spec.rb b/spec/ruby/core/struct/select_spec.rb
new file mode 100644
index 0000000000..ee846ec45f
--- /dev/null
+++ b/spec/ruby/core/struct/select_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/select'
+require_relative 'shared/accessor'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Struct#select" do
+ it_behaves_like :struct_select, :select
+ it_behaves_like :struct_accessor, :select
+ it_behaves_like :enumeratorized_with_origin_size, :select, Struct.new(:foo).new
+end
diff --git a/spec/ruby/core/struct/shared/accessor.rb b/spec/ruby/core/struct/shared/accessor.rb
new file mode 100644
index 0000000000..dbf5e78f43
--- /dev/null
+++ b/spec/ruby/core/struct/shared/accessor.rb
@@ -0,0 +1,7 @@
+describe :struct_accessor, shared: true do
+ it "does not override the instance accessor method" do
+ struct = Struct.new(@method.to_sym)
+ instance = struct.new 42
+ instance.send(@method).should == 42
+ end
+end
diff --git a/spec/ruby/core/struct/shared/dup.rb b/spec/ruby/core/struct/shared/dup.rb
new file mode 100644
index 0000000000..994f3f443e
--- /dev/null
+++ b/spec/ruby/core/struct/shared/dup.rb
@@ -0,0 +1,9 @@
+describe :struct_dup, shared: true do
+ it "duplicates members" do
+ klass = Struct.new(:foo, :bar)
+ instance = klass.new(14, 2)
+ duped = instance.send(@method)
+ duped.foo.should == 14
+ duped.bar.should == 2
+ end
+end
diff --git a/spec/ruby/core/struct/shared/equal_value.rb b/spec/ruby/core/struct/shared/equal_value.rb
new file mode 100644
index 0000000000..a7e0856df5
--- /dev/null
+++ b/spec/ruby/core/struct/shared/equal_value.rb
@@ -0,0 +1,37 @@
+describe :struct_equal_value, shared: true do
+ it "returns true if the other is the same object" do
+ car = same_car = StructClasses::Car.new("Honda", "Accord", "1998")
+ car.send(@method, same_car).should == true
+ end
+
+ it "returns true if the other has all the same fields" do
+ car = StructClasses::Car.new("Honda", "Accord", "1998")
+ similar_car = StructClasses::Car.new("Honda", "Accord", "1998")
+ car.send(@method, similar_car).should == true
+ end
+
+ it "returns false if the other is a different object or has different fields" do
+ car = StructClasses::Car.new("Honda", "Accord", "1998")
+ different_car = StructClasses::Car.new("Honda", "Accord", "1995")
+ car.send(@method, different_car).should == false
+ end
+
+ it "returns false if other is of a different class" do
+ car = StructClasses::Car.new("Honda", "Accord", "1998")
+ klass = Struct.new(:make, :model, :year)
+ clone = klass.new("Honda", "Accord", "1998")
+ car.send(@method, clone).should == false
+ end
+
+ it "handles recursive structures by returning false if a difference can be found" do
+ x = StructClasses::Car.new("Honda", "Accord", "1998")
+ x[:make] = x
+ stepping = StructClasses::Car.new("Honda", "Accord", "1998")
+ stone = StructClasses::Car.new(stepping, "Accord", "1998")
+ stepping[:make] = stone
+ x.send(@method, stepping).should == true
+
+ stone[:year] = "1999" # introduce a difference
+ x.send(@method, stepping).should == false
+ end
+end
diff --git a/spec/ruby/core/struct/shared/inspect.rb b/spec/ruby/core/struct/shared/inspect.rb
new file mode 100644
index 0000000000..90594a5452
--- /dev/null
+++ b/spec/ruby/core/struct/shared/inspect.rb
@@ -0,0 +1,5 @@
+describe :struct_inspect, shared: true do
+ it "returns a string representation without the class name for anonymous structs" do
+ Struct.new(:a).new("").send(@method).should == '#<struct a="">'
+ end
+end
diff --git a/spec/ruby/core/struct/shared/select.rb b/spec/ruby/core/struct/shared/select.rb
new file mode 100644
index 0000000000..35abee461b
--- /dev/null
+++ b/spec/ruby/core/struct/shared/select.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :struct_select, shared: true do
+ it "raises an ArgumentError if given any non-block arguments" do
+ struct = StructClasses::Car.new
+ -> { struct.send(@method, 1) { } }.should raise_error(ArgumentError)
+ end
+
+ it "returns a new array of elements for which block is true" do
+ struct = StructClasses::Car.new("Toyota", "Tercel", "2000")
+ struct.send(@method) { |i| i == "2000" }.should == [ "2000" ]
+ end
+
+ it "returns an instance of Array" do
+ struct = StructClasses::Car.new("Ford", "Escort", "1995")
+ struct.send(@method) { true }.should be_an_instance_of(Array)
+ end
+
+ describe "without block" do
+ it "returns an instance of Enumerator" do
+ struct = Struct.new(:foo).new
+ struct.send(@method).should be_an_instance_of(Enumerator)
+ end
+ end
+end
diff --git a/spec/ruby/core/struct/size_spec.rb b/spec/ruby/core/struct/size_spec.rb
new file mode 100644
index 0000000000..09f260cf20
--- /dev/null
+++ b/spec/ruby/core/struct/size_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/accessor'
+
+describe "Struct#size" do
+ it "is a synonym for length" do
+ StructClasses::Car.new.size.should == StructClasses::Car.new.length
+ end
+
+ it_behaves_like :struct_accessor, :size
+end
diff --git a/spec/ruby/core/struct/struct_spec.rb b/spec/ruby/core/struct/struct_spec.rb
new file mode 100644
index 0000000000..8817dc1a58
--- /dev/null
+++ b/spec/ruby/core/struct/struct_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Struct" do
+ it "includes Enumerable" do
+ Struct.include?(Enumerable).should == true
+ end
+end
+
+describe "Struct anonymous class instance methods" do
+ it "includes Enumerable" do
+ StructClasses::Car.include?(Enumerable).should == true
+ end
+
+ it "reader method should be a synonym for []" do
+ klass = Struct.new(:clock, :radio)
+ alarm = klass.new(true)
+ alarm.clock.should == alarm[:clock]
+ alarm.radio.should == alarm['radio']
+ end
+
+ it "reader method should not interfere with undefined methods" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ -> { car.something_weird }.should raise_error(NoMethodError)
+ end
+
+ it "writer method be a synonym for []=" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ car.model.should == 'Ranger'
+ car.model = 'F150'
+ car.model.should == 'F150'
+ car[:model].should == 'F150'
+ car['model'].should == 'F150'
+ car[1].should == 'F150'
+ end
+end
+
+describe "Struct subclasses" do
+ it "can be subclassed" do
+ compact = Class.new StructClasses::Car
+ compact.new.class.should == compact
+ end
+end
diff --git a/spec/ruby/core/struct/to_a_spec.rb b/spec/ruby/core/struct/to_a_spec.rb
new file mode 100644
index 0000000000..cb61dc45cc
--- /dev/null
+++ b/spec/ruby/core/struct/to_a_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/accessor'
+
+describe "Struct#to_a" do
+ it "returns the values for this instance as an array" do
+ StructClasses::Car.new('Geo', 'Metro', 1995).to_a.should == ['Geo', 'Metro', 1995]
+ StructClasses::Car.new('Ford').to_a.should == ['Ford', nil, nil]
+ end
+
+ it_behaves_like :struct_accessor, :to_a
+end
diff --git a/spec/ruby/core/struct/to_h_spec.rb b/spec/ruby/core/struct/to_h_spec.rb
new file mode 100644
index 0000000000..bfb0af07ba
--- /dev/null
+++ b/spec/ruby/core/struct/to_h_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Struct#to_h" do
+ it "returns a Hash with members as keys" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ car.to_h.should == {make: "Ford", model: "Ranger", year: nil}
+ end
+
+ it "returns a Hash that is independent from the struct" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ car.to_h[:make] = 'Suzuki'
+ car.make.should == 'Ford'
+ end
+
+ context "with block" do
+ it "converts [key, value] pairs returned by the block to a hash" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+
+ h = car.to_h { |k, v| [k.to_s, "#{v}".downcase] }
+ h.should == { "make" => "ford", "model" => "ranger", "year" => "" }
+ end
+
+ it "raises ArgumentError if block returns longer or shorter array" do
+ -> do
+ StructClasses::Car.new.to_h { |k, v| [k.to_s, "#{v}".downcase, 1] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+
+ -> do
+ StructClasses::Car.new.to_h { |k, v| [k] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ StructClasses::Car.new.to_h { |k, v| "not-array" }
+ end.should raise_error(TypeError, /wrong element type String/)
+ end
+
+ it "coerces returned pair to Array with #to_ary" do
+ x = mock('x')
+ x.stub!(:to_ary).and_return([:b, 'b'])
+
+ StructClasses::Car.new.to_h { |k| x }.should == { :b => 'b' }
+ end
+
+ it "does not coerce returned pair to Array with #to_a" do
+ x = mock('x')
+ x.stub!(:to_a).and_return([:b, 'b'])
+
+ -> do
+ StructClasses::Car.new.to_h { |k| x }
+ end.should raise_error(TypeError, /wrong element type MockObject/)
+ end
+ end
+end
diff --git a/spec/ruby/core/struct/to_s_spec.rb b/spec/ruby/core/struct/to_s_spec.rb
new file mode 100644
index 0000000000..94c672d3d5
--- /dev/null
+++ b/spec/ruby/core/struct/to_s_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/inspect'
+
+describe "Struct#to_s" do
+ it "is a synonym for inspect" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ car.inspect.should == car.to_s
+ end
+
+ it_behaves_like :struct_inspect, :to_s
+end
diff --git a/spec/ruby/core/struct/values_at_spec.rb b/spec/ruby/core/struct/values_at_spec.rb
new file mode 100644
index 0000000000..5e5a496600
--- /dev/null
+++ b/spec/ruby/core/struct/values_at_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# Should be synchronized with core/array/values_at_spec.rb
+describe "Struct#values_at" do
+ before do
+ clazz = Struct.new(:name, :director, :year)
+ @movie = clazz.new('Sympathy for Mr. Vengeance', 'Chan-wook Park', 2002)
+ end
+
+ context "when passed a list of Integers" do
+ it "returns an array containing each value given by one of integers" do
+ @movie.values_at(0, 1).should == ['Sympathy for Mr. Vengeance', 'Chan-wook Park']
+ end
+
+ it "raises IndexError if any of integers is out of range" do
+ -> { @movie.values_at(3) }.should raise_error(IndexError, "offset 3 too large for struct(size:3)")
+ -> { @movie.values_at(-4) }.should raise_error(IndexError, "offset -4 too small for struct(size:3)")
+ end
+ end
+
+ context "when passed an integer Range" do
+ it "returns an array containing each value given by the elements of the range" do
+ @movie.values_at(0..2).should == ['Sympathy for Mr. Vengeance', 'Chan-wook Park', 2002]
+ end
+
+ it "fills with nil values for range elements larger than the structure" do
+ @movie.values_at(0..3).should == ['Sympathy for Mr. Vengeance', 'Chan-wook Park', 2002, nil]
+ end
+
+ it "raises RangeError if any element of the range is negative and out of range" do
+ -> { @movie.values_at(-4..3) }.should raise_error(RangeError, "-4..3 out of range")
+ end
+
+ it "supports endless Range" do
+ @movie.values_at(0..).should == ["Sympathy for Mr. Vengeance", "Chan-wook Park", 2002]
+ end
+
+ it "supports beginningless Range" do
+ @movie.values_at(..2).should == ["Sympathy for Mr. Vengeance", "Chan-wook Park", 2002]
+ end
+ end
+
+ it "supports multiple integer Ranges" do
+ @movie.values_at(0..2, 1..2).should == ['Sympathy for Mr. Vengeance', 'Chan-wook Park', 2002, 'Chan-wook Park', 2002]
+ end
+
+ it "supports mixing integer Ranges and Integers" do
+ @movie.values_at(0..2, 2).should == ['Sympathy for Mr. Vengeance', 'Chan-wook Park', 2002, 2002]
+ end
+
+ it "returns a new empty Array if no arguments given" do
+ @movie.values_at().should == []
+ end
+
+ it "fails when passed unsupported types" do
+ -> { @movie.values_at('make') }.should raise_error(TypeError, "no implicit conversion of String into Integer")
+ end
+end
diff --git a/spec/ruby/core/struct/values_spec.rb b/spec/ruby/core/struct/values_spec.rb
new file mode 100644
index 0000000000..b2d11725b9
--- /dev/null
+++ b/spec/ruby/core/struct/values_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Struct#values" do
+ it "is a synonym for to_a" do
+ car = StructClasses::Car.new('Nissan', 'Maxima')
+ car.values.should == car.to_a
+
+ StructClasses::Car.new.values.should == StructClasses::Car.new.to_a
+ end
+end
diff --git a/spec/ruby/core/symbol/all_symbols_spec.rb b/spec/ruby/core/symbol/all_symbols_spec.rb
new file mode 100644
index 0000000000..1e21809093
--- /dev/null
+++ b/spec/ruby/core/symbol/all_symbols_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Symbol.all_symbols" do
+ it "returns an array of Symbols" do
+ all_symbols = Symbol.all_symbols
+ all_symbols.should be_an_instance_of(Array)
+ all_symbols.each { |s| s.should be_an_instance_of(Symbol) }
+ end
+
+ it "includes symbols that are strongly referenced" do
+ symbol = "symbol_specs_#{rand(5_000_000)}".to_sym
+ Symbol.all_symbols.should include(symbol)
+ end
+
+ it "includes symbols that are referenced in source code but not yet executed" do
+ Symbol.all_symbols.any? { |s| s.to_s == 'symbol_specs_referenced_in_source_code' }.should be_true
+ :symbol_specs_referenced_in_source_code
+ end
+end
diff --git a/spec/ruby/core/symbol/capitalize_spec.rb b/spec/ruby/core/symbol/capitalize_spec.rb
new file mode 100644
index 0000000000..a84bcf280a
--- /dev/null
+++ b/spec/ruby/core/symbol/capitalize_spec.rb
@@ -0,0 +1,41 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "Symbol#capitalize" do
+ it "returns a Symbol" do
+ :glark.capitalize.should be_an_instance_of(Symbol)
+ end
+
+ it "converts the first character to uppercase if it is ASCII" do
+ :lower.capitalize.should == :Lower
+ end
+
+ it "leaves the first character alone if it is not an alphabetical character" do
+ :"£1.20".capitalize.should == :"£1.20"
+ end
+
+ it "capitalizes the first character if it is Unicode" do
+ :"äöü".capitalize.should == :"Äöü"
+ :"aou".capitalize.should == :"Aou"
+ end
+
+ it "converts subsequent uppercase ASCII characters to their lowercase equivalents" do
+ :lOWER.capitalize.should == :Lower
+ end
+
+ it "leaves ASCII characters already in the correct case as they were" do
+ :Title.capitalize.should == :Title
+ end
+
+ it "works with both upper- and lowercase ASCII characters in the same Symbol" do
+ :mIxEd.capitalize.should == :Mixed
+ end
+
+ it "leaves lowercase Unicode characters (except in first position) as they were" do
+ "a\u{00DF}C".to_sym.capitalize.should == :"Aßc"
+ end
+
+ it "leaves non-alphabetic ASCII characters as they were" do
+ "Glark?!?".to_sym.capitalize.should == :"Glark?!?"
+ end
+end
diff --git a/spec/ruby/core/symbol/case_compare_spec.rb b/spec/ruby/core/symbol/case_compare_spec.rb
new file mode 100644
index 0000000000..0c6bc1eda5
--- /dev/null
+++ b/spec/ruby/core/symbol/case_compare_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Symbol#===" do
+ it "returns true when the argument is a Symbol" do
+ (Symbol === :ruby).should == true
+ end
+
+ it "returns false when the argument is a String" do
+ (Symbol === 'ruby').should == false
+ end
+end
diff --git a/spec/ruby/core/symbol/casecmp_spec.rb b/spec/ruby/core/symbol/casecmp_spec.rb
new file mode 100644
index 0000000000..80ea51e910
--- /dev/null
+++ b/spec/ruby/core/symbol/casecmp_spec.rb
@@ -0,0 +1,144 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "Symbol#casecmp with Symbol" do
+ it "compares symbols without regard to case" do
+ :abcdef.casecmp(:abcde).should == 1
+ :aBcDeF.casecmp(:abcdef).should == 0
+ :abcdef.casecmp(:abcdefg).should == -1
+ :abcdef.casecmp(:ABCDEF).should == 0
+ end
+
+ it "doesn't consider non-ascii characters equal that aren't" do
+ # -- Latin-1 --
+ upper_a_tilde = "\xC3".b.to_sym
+ upper_a_umlaut = "\xC4".b.to_sym
+ lower_a_tilde = "\xE3".b.to_sym
+ lower_a_umlaut = "\xE4".b.to_sym
+
+ lower_a_tilde.casecmp(lower_a_umlaut).should_not == 0
+ lower_a_umlaut.casecmp(lower_a_tilde).should_not == 0
+ upper_a_tilde.casecmp(upper_a_umlaut).should_not == 0
+ upper_a_umlaut.casecmp(upper_a_tilde).should_not == 0
+
+ # -- UTF-8 --
+ upper_a_tilde = :"Ã"
+ lower_a_tilde = :"ã"
+ upper_a_umlaut = :"Ä"
+ lower_a_umlaut = :"ä"
+
+ lower_a_tilde.casecmp(lower_a_umlaut).should_not == 0
+ lower_a_umlaut.casecmp(lower_a_tilde).should_not == 0
+ upper_a_tilde.casecmp(upper_a_umlaut).should_not == 0
+ upper_a_umlaut.casecmp(upper_a_tilde).should_not == 0
+ end
+
+ it "doesn't do case mapping for non-ascii characters" do
+ # -- Latin-1 --
+ upper_a_tilde = "\xC3".b.to_sym
+ upper_a_umlaut = "\xC4".b.to_sym
+ lower_a_tilde = "\xE3".b.to_sym
+ lower_a_umlaut = "\xE4".b.to_sym
+
+ upper_a_tilde.casecmp(lower_a_tilde).should == -1
+ upper_a_umlaut.casecmp(lower_a_umlaut).should == -1
+ lower_a_tilde.casecmp(upper_a_tilde).should == 1
+ lower_a_umlaut.casecmp(upper_a_umlaut).should == 1
+
+ # -- UTF-8 --
+ upper_a_tilde = :"Ã"
+ lower_a_tilde = :"ã"
+ upper_a_umlaut = :"Ä"
+ lower_a_umlaut = :"ä"
+
+ upper_a_tilde.casecmp(lower_a_tilde).should == -1
+ upper_a_umlaut.casecmp(lower_a_umlaut).should == -1
+ lower_a_tilde.casecmp(upper_a_tilde).should == 1
+ lower_a_umlaut.casecmp(upper_a_umlaut).should == 1
+ end
+end
+
+describe "Symbol#casecmp" do
+ it "returns nil if other is a String" do
+ :abc.casecmp("abc").should be_nil
+ end
+
+ it "returns nil if other is an Integer" do
+ :abc.casecmp(1).should be_nil
+ end
+
+ it "returns nil if other is an object" do
+ obj = mock("string <=>")
+ :abc.casecmp(obj).should be_nil
+ end
+end
+
+describe 'Symbol#casecmp?' do
+ it "compares symbols without regard to case" do
+ :abcdef.casecmp?(:abcde).should == false
+ :aBcDeF.casecmp?(:abcdef).should == true
+ :abcdef.casecmp?(:abcdefg).should == false
+ :abcdef.casecmp?(:ABCDEF).should == true
+ end
+
+ it "doesn't consider non-ascii characters equal that aren't" do
+ # -- Latin-1 --
+ upper_a_tilde = "\xC3".b.to_sym
+ upper_a_umlaut = "\xC4".b.to_sym
+ lower_a_tilde = "\xE3".b.to_sym
+ lower_a_umlaut = "\xE4".b.to_sym
+
+ lower_a_tilde.casecmp?(lower_a_umlaut).should_not == true
+ lower_a_umlaut.casecmp?(lower_a_tilde).should_not == true
+ upper_a_tilde.casecmp?(upper_a_umlaut).should_not == true
+ upper_a_umlaut.casecmp?(upper_a_tilde).should_not == true
+
+ # -- UTF-8 --
+ upper_a_tilde = :"Ã"
+ lower_a_tilde = :"ã"
+ upper_a_umlaut = :"Ä"
+ lower_a_umlaut = :"ä"
+
+ lower_a_tilde.casecmp?(lower_a_umlaut).should_not == true
+ lower_a_umlaut.casecmp?(lower_a_tilde).should_not == true
+ upper_a_tilde.casecmp?(upper_a_umlaut).should_not == true
+ upper_a_umlaut.casecmp?(upper_a_tilde).should_not == true
+ end
+
+ it "doesn't do case mapping for non-ascii and non-unicode characters" do
+ # -- Latin-1 --
+ upper_a_tilde = "\xC3".b.to_sym
+ upper_a_umlaut = "\xC4".b.to_sym
+ lower_a_tilde = "\xE3".b.to_sym
+ lower_a_umlaut = "\xE4".b.to_sym
+
+ upper_a_tilde.casecmp?(lower_a_tilde).should == false
+ upper_a_umlaut.casecmp?(lower_a_umlaut).should == false
+ lower_a_tilde.casecmp?(upper_a_tilde).should == false
+ lower_a_umlaut.casecmp?(upper_a_umlaut).should == false
+ end
+
+ it 'does case mapping for unicode characters' do
+ # -- UTF-8 --
+ upper_a_tilde = :"Ã"
+ lower_a_tilde = :"ã"
+ upper_a_umlaut = :"Ä"
+ lower_a_umlaut = :"ä"
+
+ upper_a_tilde.casecmp?(lower_a_tilde).should == true
+ upper_a_umlaut.casecmp?(lower_a_umlaut).should == true
+ lower_a_tilde.casecmp?(upper_a_tilde).should == true
+ lower_a_umlaut.casecmp?(upper_a_umlaut).should == true
+ end
+
+ it 'returns nil when comparing characters with different encodings' do
+ # -- Latin-1 --
+ upper_a_tilde = "\xC3".b.to_sym
+
+ # -- UTF-8 --
+ lower_a_tilde = :"ã"
+
+ upper_a_tilde.casecmp?(lower_a_tilde).should == nil
+ lower_a_tilde.casecmp?(upper_a_tilde).should == nil
+ end
+end
diff --git a/spec/ruby/core/symbol/comparison_spec.rb b/spec/ruby/core/symbol/comparison_spec.rb
new file mode 100644
index 0000000000..613177be05
--- /dev/null
+++ b/spec/ruby/core/symbol/comparison_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+
+describe "Symbol#<=> with Symbol" do
+ it "compares individual characters based on their ascii value" do
+ ascii_order = Array.new(256) { |x| x.chr.to_sym }
+ sort_order = ascii_order.sort
+ sort_order.should == ascii_order
+ end
+
+ it "returns -1 when self is less than other" do
+ (:this <=> :those).should == -1
+ end
+
+ it "returns 0 when self is equal to other" do
+ (:yep <=> :yep).should == 0
+ end
+
+ it "returns 1 when self is greater than other" do
+ (:yoddle <=> :griddle).should == 1
+ end
+
+ it "considers symbol that comes lexicographically first to be less if the symbols have same size" do
+ (:aba <=> :abc).should == -1
+ (:abc <=> :aba).should == 1
+ end
+
+ it "doesn't consider shorter string to be less if longer string starts with shorter one" do
+ (:abc <=> :abcd).should == -1
+ (:abcd <=> :abc).should == 1
+ end
+
+ it "compares shorter string with corresponding number of first chars of longer string" do
+ (:abx <=> :abcd).should == 1
+ (:abcd <=> :abx).should == -1
+ end
+end
+
+describe "Symbol#<=>" do
+ it "returns nil if other is a String" do
+ (:abc <=> "abc").should be_nil
+ end
+
+ it "returns nil if other is an Integer" do
+ (:abc <=> 1).should be_nil
+ end
+
+ it "returns nil if other is an object" do
+ obj = mock("string <=>")
+ (:abc <=> obj).should be_nil
+ end
+end
diff --git a/spec/ruby/core/symbol/downcase_spec.rb b/spec/ruby/core/symbol/downcase_spec.rb
new file mode 100644
index 0000000000..7e94c669cc
--- /dev/null
+++ b/spec/ruby/core/symbol/downcase_spec.rb
@@ -0,0 +1,25 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "Symbol#downcase" do
+ it "returns a Symbol" do
+ :glark.downcase.should be_an_instance_of(Symbol)
+ end
+
+ it "converts uppercase ASCII characters to their lowercase equivalents" do
+ :lOwEr.downcase.should == :lower
+ end
+
+ it "leaves lowercase Unicode characters as they were" do
+ "\u{E0}Bc".to_sym.downcase.should == :"àbc"
+ end
+
+ it "uncapitalizes all Unicode characters" do
+ "ÄÖÜ".to_sym.downcase.should == :"äöü"
+ "AOU".to_sym.downcase.should == :"aou"
+ end
+
+ it "leaves non-alphabetic ASCII characters as they were" do
+ "Glark?!?".to_sym.downcase.should == :"glark?!?"
+ end
+end
diff --git a/spec/ruby/core/symbol/dup_spec.rb b/spec/ruby/core/symbol/dup_spec.rb
new file mode 100644
index 0000000000..8b35917c27
--- /dev/null
+++ b/spec/ruby/core/symbol/dup_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Symbol#dup" do
+ it "returns self" do
+ :a_symbol.dup.should equal(:a_symbol)
+ end
+end
diff --git a/spec/ruby/core/symbol/element_reference_spec.rb b/spec/ruby/core/symbol/element_reference_spec.rb
new file mode 100644
index 0000000000..df6bc15ddb
--- /dev/null
+++ b/spec/ruby/core/symbol/element_reference_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/slice'
+
+describe "Symbol#[]" do
+ it_behaves_like :symbol_slice, :[]
+end
diff --git a/spec/ruby/core/symbol/empty_spec.rb b/spec/ruby/core/symbol/empty_spec.rb
new file mode 100644
index 0000000000..19c23cfe5f
--- /dev/null
+++ b/spec/ruby/core/symbol/empty_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Symbol#empty?" do
+ it "returns true if self is empty" do
+ :"".empty?.should be_true
+ end
+
+ it "returns false if self is non-empty" do
+ :"a".empty?.should be_false
+ end
+end
diff --git a/spec/ruby/core/symbol/encoding_spec.rb b/spec/ruby/core/symbol/encoding_spec.rb
new file mode 100644
index 0000000000..732fd62e26
--- /dev/null
+++ b/spec/ruby/core/symbol/encoding_spec.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+
+describe "Symbol#encoding for ASCII symbols" do
+ it "is US-ASCII" do
+ :foo.encoding.name.should == "US-ASCII"
+ end
+
+ it "is US-ASCII after converting to string" do
+ :foo.to_s.encoding.name.should == "US-ASCII"
+ end
+end
+
+describe "Symbol#encoding for UTF-8 symbols" do
+ it "is UTF-8" do
+ :åäö.encoding.name.should == "UTF-8"
+ end
+
+ it "is UTF-8 after converting to string" do
+ :åäö.to_s.encoding.name.should == "UTF-8"
+ end
+end
diff --git a/spec/ruby/core/symbol/end_with_spec.rb b/spec/ruby/core/symbol/end_with_spec.rb
new file mode 100644
index 0000000000..4b9f5a4996
--- /dev/null
+++ b/spec/ruby/core/symbol/end_with_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+require_relative '../../shared/string/end_with'
+
+describe "Symbol#end_with?" do
+ it_behaves_like :end_with, :to_sym
+end
diff --git a/spec/ruby/core/symbol/equal_value_spec.rb b/spec/ruby/core/symbol/equal_value_spec.rb
new file mode 100644
index 0000000000..3fe997d02a
--- /dev/null
+++ b/spec/ruby/core/symbol/equal_value_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "Symbol#==" do
+ it "only returns true when the other is exactly the same symbol" do
+ (:ruby == :ruby).should == true
+ (:ruby == :"ruby").should == true
+ (:ruby == :'ruby').should == true
+ (:@ruby == :@ruby).should == true
+
+ (:ruby == :@ruby).should == false
+ (:foo == :bar).should == false
+ (:ruby == 'ruby').should == false
+ end
+end
diff --git a/spec/ruby/core/symbol/fixtures/classes.rb b/spec/ruby/core/symbol/fixtures/classes.rb
new file mode 100644
index 0000000000..6552f6ee38
--- /dev/null
+++ b/spec/ruby/core/symbol/fixtures/classes.rb
@@ -0,0 +1,3 @@
+module SymbolSpecs
+ class MyRange < Range; end
+end
diff --git a/spec/ruby/core/symbol/id2name_spec.rb b/spec/ruby/core/symbol/id2name_spec.rb
new file mode 100644
index 0000000000..2caa89fc37
--- /dev/null
+++ b/spec/ruby/core/symbol/id2name_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/id2name'
+
+describe "Symbol#id2name" do
+ it_behaves_like :symbol_id2name, :id2name
+end
diff --git a/spec/ruby/core/symbol/inspect_spec.rb b/spec/ruby/core/symbol/inspect_spec.rb
new file mode 100644
index 0000000000..58402ab261
--- /dev/null
+++ b/spec/ruby/core/symbol/inspect_spec.rb
@@ -0,0 +1,105 @@
+require_relative '../../spec_helper'
+
+describe "Symbol#inspect" do
+ symbols = {
+ fred: ":fred",
+ :fred? => ":fred?",
+ :fred! => ":fred!",
+ :$ruby => ":$ruby",
+ :@ruby => ":@ruby",
+ :@@ruby => ":@@ruby",
+ :"$ruby!" => ":\"$ruby!\"",
+ :"$ruby?" => ":\"$ruby?\"",
+ :"@ruby!" => ":\"@ruby!\"",
+ :"@ruby?" => ":\"@ruby?\"",
+ :"@@ruby!" => ":\"@@ruby!\"",
+ :"@@ruby?" => ":\"@@ruby?\"",
+
+ :$-w => ":$-w",
+ :"$-ww" => ":\"$-ww\"",
+ :"$+" => ":$+",
+ :"$~" => ":$~",
+ :"$:" => ":$:",
+ :"$?" => ":$?",
+ :"$<" => ":$<",
+ :"$_" => ":$_",
+ :"$/" => ":$/",
+ :"$'" => ":$'",
+ :"$\"" => ":$\"",
+ :"$$" => ":$$",
+ :"$." => ":$.",
+ :"$," => ":$,",
+ :"$`" => ":$`",
+ :"$!" => ":$!",
+ :"$;" => ":$;",
+ :"$\\" => ":$\\",
+ :"$=" => ":$=",
+ :"$*" => ":$*",
+ :"$>" => ":$>",
+ :"$&" => ":$&",
+ :"$@" => ":$@",
+ :"$1234" => ":$1234",
+
+ :-@ => ":-@",
+ :+@ => ":+@",
+ :% => ":%",
+ :& => ":&",
+ :* => ":*",
+ :** => ":**",
+ :"/" => ":/", # lhs quoted for emacs happiness
+ :< => ":<",
+ :<= => ":<=",
+ :<=> => ":<=>",
+ :== => ":==",
+ :=== => ":===",
+ :=~ => ":=~",
+ :> => ":>",
+ :>= => ":>=",
+ :>> => ":>>",
+ :[] => ":[]",
+ :[]= => ":[]=",
+ :"\<\<" => ":\<\<",
+ :^ => ":^",
+ :"`" => ":`", # for emacs, and justice!
+ :~ => ":~",
+ :| => ":|",
+
+ :"!" => [":\"!\"", ":!" ],
+ :"!=" => [":\"!=\"", ":!="],
+ :"!~" => [":\"!~\"", ":!~"],
+ :"\$" => ":\"$\"", # for justice!
+ :"&&" => ":\"&&\"",
+ :"'" => ":\"\'\"",
+ :"," => ":\",\"",
+ :"." => ":\".\"",
+ :".." => ":\"..\"",
+ :"..." => ":\"...\"",
+ :":" => ":\":\"",
+ :"::" => ":\"::\"",
+ :";" => ":\";\"",
+ :"=" => ":\"=\"",
+ :"=>" => ":\"=>\"",
+ :"\?" => ":\"?\"", # rawr!
+ :"@" => ":\"@\"",
+ :"||" => ":\"||\"",
+ :"|||" => ":\"|||\"",
+ :"++" => ":\"++\"",
+
+ :"\"" => ":\"\\\"\"",
+ :"\"\"" => ":\"\\\"\\\"\"",
+
+ :"9" => ":\"9\"",
+ :"foo bar" => ":\"foo bar\"",
+ :"*foo" => ":\"*foo\"",
+ :"foo " => ":\"foo \"",
+ :" foo" => ":\" foo\"",
+ :" " => ":\" \"",
+ }
+
+ symbols.each do |input, expected|
+ expected = expected[1] if expected.is_a?(Array)
+ it "returns self as a symbol literal for #{expected}" do
+ input.inspect.should == expected
+ end
+ end
+end
diff --git a/spec/ruby/core/symbol/intern_spec.rb b/spec/ruby/core/symbol/intern_spec.rb
new file mode 100644
index 0000000000..ea04b87e8a
--- /dev/null
+++ b/spec/ruby/core/symbol/intern_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Symbol#intern" do
+ it "returns self" do
+ :foo.intern.should == :foo
+ end
+
+ it "returns a Symbol" do
+ :foo.intern.should be_kind_of(Symbol)
+ end
+end
diff --git a/spec/ruby/core/symbol/length_spec.rb b/spec/ruby/core/symbol/length_spec.rb
new file mode 100644
index 0000000000..27bee575ef
--- /dev/null
+++ b/spec/ruby/core/symbol/length_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+
+describe "Symbol#length" do
+ it_behaves_like :symbol_length, :length
+end
diff --git a/spec/ruby/core/symbol/match_spec.rb b/spec/ruby/core/symbol/match_spec.rb
new file mode 100644
index 0000000000..41e058f977
--- /dev/null
+++ b/spec/ruby/core/symbol/match_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../spec_helper'
+
+describe :symbol_match, shared: true do
+ it "returns the index of the beginning of the match" do
+ :abc.send(@method, /b/).should == 1
+ end
+
+ it "returns nil if there is no match" do
+ :a.send(@method, /b/).should be_nil
+ end
+
+ it "sets the last match pseudo-variables" do
+ :a.send(@method, /(.)/).should == 0
+ $1.should == "a"
+ end
+end
+
+describe "Symbol#=~" do
+ it_behaves_like :symbol_match, :=~
+end
+
+describe "Symbol#match" do
+ it "returns the MatchData" do
+ result = :abc.match(/b/)
+ result.should be_kind_of(MatchData)
+ result[0].should == 'b'
+ end
+
+ it "returns nil if there is no match" do
+ :a.match(/b/).should be_nil
+ end
+
+ it "sets the last match pseudo-variables" do
+ :a.match(/(.)/)[0].should == 'a'
+ $1.should == "a"
+ end
+
+ describe "when passed a block" do
+ it "yields the MatchData" do
+ :abc.match(/./) {|m| ScratchPad.record m }
+ ScratchPad.recorded.should be_kind_of(MatchData)
+ end
+
+ it "returns the block result" do
+ :abc.match(/./) { :result }.should == :result
+ end
+
+ it "does not yield if there is no match" do
+ ScratchPad.record []
+ :b.match(/a/) {|m| ScratchPad << m }
+ ScratchPad.recorded.should == []
+ end
+ end
+end
+
+describe "Symbol#match?" do
+ before :each do
+ # Resetting Regexp.last_match
+ /DONTMATCH/.match ''
+ end
+
+ context "when matches the given regex" do
+ it "returns true but does not set Regexp.last_match" do
+ :string.match?(/string/i).should be_true
+ Regexp.last_match.should be_nil
+ end
+ end
+
+ it "returns false when does not match the given regex" do
+ :string.match?(/STRING/).should be_false
+ end
+
+ it "takes matching position as the 2nd argument" do
+ :string.match?(/str/i, 0).should be_true
+ :string.match?(/str/i, 1).should be_false
+ end
+end
diff --git a/spec/ruby/core/symbol/name_spec.rb b/spec/ruby/core/symbol/name_spec.rb
new file mode 100644
index 0000000000..15b9aa75e9
--- /dev/null
+++ b/spec/ruby/core/symbol/name_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.0" do
+ describe "Symbol#name" do
+ it "returns string" do
+ :ruby.name.should == "ruby"
+ :ルビー.name.should == "ルビー"
+ end
+
+ it "returns same string instance" do
+ :"ruby_3".name.should.equal?(:ruby_3.name)
+ :"ruby_#{1+2}".name.should.equal?(:ruby_3.name)
+ end
+
+ it "returns frozen string" do
+ :symbol.name.should.frozen?
+ end
+ end
+end
diff --git a/spec/ruby/core/symbol/next_spec.rb b/spec/ruby/core/symbol/next_spec.rb
new file mode 100644
index 0000000000..97fe913739
--- /dev/null
+++ b/spec/ruby/core/symbol/next_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/succ'
+
+describe "Symbol#next" do
+ it_behaves_like :symbol_succ, :next
+end
diff --git a/spec/ruby/core/symbol/shared/id2name.rb b/spec/ruby/core/symbol/shared/id2name.rb
new file mode 100644
index 0000000000..d012b7634e
--- /dev/null
+++ b/spec/ruby/core/symbol/shared/id2name.rb
@@ -0,0 +1,16 @@
+describe :symbol_id2name, shared: true do
+ it "returns the string corresponding to self" do
+ :rubinius.send(@method).should == "rubinius"
+ :squash.send(@method).should == "squash"
+ :[].send(@method).should == "[]"
+ :@ruby.send(@method).should == "@ruby"
+ :@@ruby.send(@method).should == "@@ruby"
+ end
+
+ it "returns a String in the same encoding as self" do
+ string = "ruby".encode("US-ASCII")
+ symbol = string.to_sym
+
+ symbol.send(@method).encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/symbol/shared/length.rb b/spec/ruby/core/symbol/shared/length.rb
new file mode 100644
index 0000000000..692e8c57e3
--- /dev/null
+++ b/spec/ruby/core/symbol/shared/length.rb
@@ -0,0 +1,23 @@
+# -*- encoding: utf-8 -*-
+
+describe :symbol_length, shared: true do
+ it "returns 0 for empty name" do
+ :''.send(@method).should == 0
+ end
+
+ it "returns 1 for name formed by a NUL character" do
+ :"\x00".send(@method).should == 1
+ end
+
+ it "returns 3 for name formed by 3 ASCII characters" do
+ :one.send(@method).should == 3
+ end
+
+ it "returns 4 for name formed by 4 ASCII characters" do
+ :four.send(@method).should == 4
+ end
+
+ it "returns 4 for name formed by 1 multibyte and 3 ASCII characters" do
+ :"\xC3\x9Cber".send(@method).should == 4
+ end
+end
diff --git a/spec/ruby/core/symbol/shared/slice.rb b/spec/ruby/core/symbol/shared/slice.rb
new file mode 100644
index 0000000000..0df87e183d
--- /dev/null
+++ b/spec/ruby/core/symbol/shared/slice.rb
@@ -0,0 +1,262 @@
+require_relative '../fixtures/classes'
+
+describe :symbol_slice, shared: true do
+ describe "with an Integer index" do
+ it "returns the character code of the element at the index" do
+ :symbol.send(@method, 1).should == ?y
+ end
+
+ it "returns nil if the index starts from the end and is greater than the length" do
+ :symbol.send(@method, -10).should be_nil
+ end
+
+ it "returns nil if the index is greater than the length" do
+ :symbol.send(@method, 42).should be_nil
+ end
+ end
+
+ describe "with an Integer index and length" do
+ describe "and a positive index and length" do
+ it "returns a slice" do
+ :symbol.send(@method, 1,3).should == "ymb"
+ end
+
+ it "returns a blank slice if the length is 0" do
+ :symbol.send(@method, 0,0).should == ""
+ :symbol.send(@method, 1,0).should == ""
+ end
+
+ it "returns a slice of all remaining characters if the given length is greater than the actual length" do
+ :symbol.send(@method, 1,100).should == "ymbol"
+ end
+
+ it "returns nil if the index is greater than the length" do
+ :symbol.send(@method, 10,1).should be_nil
+ end
+ end
+
+ describe "and a positive index and negative length" do
+ it "returns nil" do
+ :symbol.send(@method, 0,-1).should be_nil
+ :symbol.send(@method, 1,-1).should be_nil
+ end
+ end
+
+ describe "and a negative index and positive length" do
+ it "returns a slice starting from the end upto the length" do
+ :symbol.send(@method, -3,2).should == "bo"
+ end
+
+ it "returns a blank slice if the length is 0" do
+ :symbol.send(@method, -1,0).should == ""
+ end
+
+ it "returns a slice of all remaining characters if the given length is larger than the actual length" do
+ :symbol.send(@method, -4,100).should == "mbol"
+ end
+
+ it "returns nil if the index is past the start" do
+ :symbol.send(@method, -10,1).should be_nil
+ end
+ end
+
+ describe "and a negative index and negative length" do
+ it "returns nil" do
+ :symbol.send(@method, -1,-1).should be_nil
+ end
+ end
+
+ describe "and a Float length" do
+ it "converts the length to an Integer" do
+ :symbol.send(@method, 2,2.5).should == "mb"
+ end
+ end
+
+ describe "and a nil length" do
+ it "raises a TypeError" do
+ -> { :symbol.send(@method, 1,nil) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "and a length that cannot be converted into an Integer" do
+ it "raises a TypeError when given an Array" do
+ -> { :symbol.send(@method, 1,Array.new) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when given an Hash" do
+ -> { :symbol.send(@method, 1,Hash.new) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when given an Object" do
+ -> { :symbol.send(@method, 1,Object.new) }.should raise_error(TypeError)
+ end
+ end
+ end
+
+ describe "with a Float index" do
+ it "converts the index to an Integer" do
+ :symbol.send(@method, 1.5).should == ?y
+ end
+ end
+
+ describe "with a nil index" do
+ it "raises a TypeError" do
+ -> { :symbol.send(@method, nil) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "with an index that cannot be converted into an Integer" do
+ it "raises a TypeError when given an Array" do
+ -> { :symbol.send(@method, Array.new) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when given an Hash" do
+ -> { :symbol.send(@method, Hash.new) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when given an Object" do
+ -> { :symbol.send(@method, Object.new) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "with a Range slice" do
+ describe "that is within bounds" do
+ it "returns a slice if both range values begin at the start and are within bounds" do
+ :symbol.send(@method, 1..4).should == "ymbo"
+ end
+
+ it "returns a slice if the first range value begins at the start and the last begins at the end" do
+ :symbol.send(@method, 1..-1).should == "ymbol"
+ end
+
+ it "returns a slice if the first range value begins at the end and the last begins at the end" do
+ :symbol.send(@method, -4..-1).should == "mbol"
+ end
+ end
+
+ describe "that is out of bounds" do
+ it "returns nil if the first range value begins past the end" do
+ :symbol.send(@method, 10..12).should be_nil
+ end
+
+ it "returns a blank string if the first range value is within bounds and the last range value is not" do
+ :symbol.send(@method, -2..-10).should == ""
+ :symbol.send(@method, 2..-10).should == ""
+ end
+
+ it "returns nil if the first range value starts from the end and is within bounds and the last value starts from the end and is greater than the length" do
+ :symbol.send(@method, -10..-12).should be_nil
+ end
+
+ it "returns nil if the first range value starts from the end and is out of bounds and the last value starts from the end and is less than the length" do
+ :symbol.send(@method, -10..-2).should be_nil
+ end
+ end
+
+ describe "with Float values" do
+ it "converts the first value to an Integer" do
+ :symbol.send(@method, 0.5..2).should == "sym"
+ end
+
+ it "converts the last value to an Integer" do
+ :symbol.send(@method, 0..2.5).should == "sym"
+ end
+ end
+ end
+
+ describe "with a Range subclass slice" do
+ it "returns a slice" do
+ range = SymbolSpecs::MyRange.new(1, 4)
+ :symbol.send(@method, range).should == "ymbo"
+ end
+ end
+
+ describe "with a Regex slice" do
+ describe "without a capture index" do
+ it "returns a string of the match" do
+ :symbol.send(@method, /[^bol]+/).should == "sym"
+ end
+
+ it "returns nil if the expression does not match" do
+ :symbol.send(@method, /0-9/).should be_nil
+ end
+
+ it "sets $~ to the MatchData if there is a match" do
+ :symbol.send(@method, /[^bol]+/)
+ $~[0].should == "sym"
+ end
+
+ it "does not set $~ if there if there is not a match" do
+ :symbol.send(@method, /[0-9]+/)
+ $~.should be_nil
+ end
+ end
+
+ describe "with a capture index" do
+ it "returns a string of the complete match if the capture index is 0" do
+ :symbol.send(@method, /(sy)(mb)(ol)/, 0).should == "symbol"
+ end
+
+ it "returns a string for the matched capture at the given index" do
+ :symbol.send(@method, /(sy)(mb)(ol)/, 1).should == "sy"
+ :symbol.send(@method, /(sy)(mb)(ol)/, -1).should == "ol"
+ end
+
+ it "returns nil if there is no capture for the index" do
+ :symbol.send(@method, /(sy)(mb)(ol)/, 4).should be_nil
+ :symbol.send(@method, /(sy)(mb)(ol)/, -4).should be_nil
+ end
+
+ it "converts the index to an Integer" do
+ :symbol.send(@method, /(sy)(mb)(ol)/, 1.5).should == "sy"
+ end
+
+ describe "and an index that cannot be converted to an Integer" do
+ it "raises a TypeError when given an Hash" do
+ -> { :symbol.send(@method, /(sy)(mb)(ol)/, Hash.new) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when given an Array" do
+ -> { :symbol.send(@method, /(sy)(mb)(ol)/, Array.new) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when given an Object" do
+ -> { :symbol.send(@method, /(sy)(mb)(ol)/, Object.new) }.should raise_error(TypeError)
+ end
+ end
+
+ it "raises a TypeError if the index is nil" do
+ -> { :symbol.send(@method, /(sy)(mb)(ol)/, nil) }.should raise_error(TypeError)
+ end
+
+ it "sets $~ to the MatchData if there is a match" do
+ :symbol.send(@method, /(sy)(mb)(ol)/, 0)
+ $~[0].should == "symbol"
+ $~[1].should == "sy"
+ $~[2].should == "mb"
+ $~[3].should == "ol"
+ end
+
+ it "does not set $~ to the MatchData if there is not a match" do
+ :symbol.send(@method, /0-9/, 0)
+ $~.should be_nil
+ end
+ end
+ end
+
+ describe "with a String slice" do
+ it "does not set $~" do
+ $~ = nil
+ :symbol.send(@method, "sym")
+ $~.should be_nil
+ end
+
+ it "returns a string if there is match" do
+ :symbol.send(@method, "ymb").should == "ymb"
+ end
+
+ it "returns nil if there is not a match" do
+ :symbol.send(@method, "foo").should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/symbol/shared/succ.rb b/spec/ruby/core/symbol/shared/succ.rb
new file mode 100644
index 0000000000..dde298207e
--- /dev/null
+++ b/spec/ruby/core/symbol/shared/succ.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe :symbol_succ, shared: true do
+ it "returns a successor" do
+ :abcd.send(@method).should == :abce
+ :THX1138.send(@method).should == :THX1139
+ end
+
+ it "propagates a 'carry'" do
+ :"1999zzz".send(@method).should == :"2000aaa"
+ :ZZZ9999.send(@method).should == :AAAA0000
+ end
+
+ it "increments non-alphanumeric characters when no alphanumeric characters are present" do
+ :"<<koala>>".send(@method).should == :"<<koalb>>"
+ :"***".send(@method).should == :"**+"
+ end
+end
diff --git a/spec/ruby/core/symbol/size_spec.rb b/spec/ruby/core/symbol/size_spec.rb
new file mode 100644
index 0000000000..5e2aa8d4d2
--- /dev/null
+++ b/spec/ruby/core/symbol/size_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+
+describe "Symbol#size" do
+ it_behaves_like :symbol_length, :size
+end
diff --git a/spec/ruby/core/symbol/slice_spec.rb b/spec/ruby/core/symbol/slice_spec.rb
new file mode 100644
index 0000000000..d2421c474c
--- /dev/null
+++ b/spec/ruby/core/symbol/slice_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/slice'
+
+describe "Symbol#slice" do
+ it_behaves_like :symbol_slice, :slice
+end
diff --git a/spec/ruby/core/symbol/start_with_spec.rb b/spec/ruby/core/symbol/start_with_spec.rb
new file mode 100644
index 0000000000..cd43279003
--- /dev/null
+++ b/spec/ruby/core/symbol/start_with_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/string/start_with'
+
+describe "Symbol#start_with?" do
+ it_behaves_like :start_with, :to_sym
+end
diff --git a/spec/ruby/core/symbol/succ_spec.rb b/spec/ruby/core/symbol/succ_spec.rb
new file mode 100644
index 0000000000..694bfff862
--- /dev/null
+++ b/spec/ruby/core/symbol/succ_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/succ'
+
+describe "Symbol#succ" do
+ it_behaves_like :symbol_succ, :succ
+end
diff --git a/spec/ruby/core/symbol/swapcase_spec.rb b/spec/ruby/core/symbol/swapcase_spec.rb
new file mode 100644
index 0000000000..24709cac30
--- /dev/null
+++ b/spec/ruby/core/symbol/swapcase_spec.rb
@@ -0,0 +1,29 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "Symbol#swapcase" do
+ it "returns a Symbol" do
+ :glark.swapcase.should be_an_instance_of(Symbol)
+ end
+
+ it "converts lowercase ASCII characters to their uppercase equivalents" do
+ :lower.swapcase.should == :LOWER
+ end
+
+ it "converts uppercase ASCII characters to their lowercase equivalents" do
+ :UPPER.swapcase.should == :upper
+ end
+
+ it "works with both upper- and lowercase ASCII characters in the same Symbol" do
+ :mIxEd.swapcase.should == :MiXeD
+ end
+
+ it "swaps the case for Unicode characters" do
+ "äÖü".to_sym.swapcase.should == :"ÄöÜ"
+ "aOu".to_sym.swapcase.should == :"AoU"
+ end
+
+ it "leaves non-alphabetic ASCII characters as they were" do
+ "Glark?!?".to_sym.swapcase.should == :"gLARK?!?"
+ end
+end
diff --git a/spec/ruby/core/symbol/symbol_spec.rb b/spec/ruby/core/symbol/symbol_spec.rb
new file mode 100644
index 0000000000..cefe70bc99
--- /dev/null
+++ b/spec/ruby/core/symbol/symbol_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Symbol" do
+ it "includes Comparable" do
+ Symbol.include?(Comparable).should == true
+ end
+
+ it ".allocate raises a TypeError" do
+ -> do
+ Symbol.allocate
+ end.should raise_error(TypeError)
+ end
+
+ it ".new is undefined" do
+ -> do
+ Symbol.new
+ end.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/symbol/to_proc_spec.rb b/spec/ruby/core/symbol/to_proc_spec.rb
new file mode 100644
index 0000000000..6d9c4bc622
--- /dev/null
+++ b/spec/ruby/core/symbol/to_proc_spec.rb
@@ -0,0 +1,99 @@
+require_relative '../../spec_helper'
+
+describe "Symbol#to_proc" do
+ it "returns a new Proc" do
+ proc = :to_s.to_proc
+ proc.should be_kind_of(Proc)
+ end
+
+ it "sends self to arguments passed when calling #call on the Proc" do
+ obj = mock("Receiving #to_s")
+ obj.should_receive(:to_s).and_return("Received #to_s")
+ :to_s.to_proc.call(obj).should == "Received #to_s"
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "returns a Proc with #lambda? false" do
+ pr = :to_s.to_proc
+ pr.should_not.lambda?
+ end
+
+ it "produces a Proc with arity -1" do
+ pr = :to_s.to_proc
+ pr.arity.should == -1
+ end
+
+ it "produces a Proc that always returns [[:rest]] for #parameters" do
+ pr = :to_s.to_proc
+ pr.parameters.should == [[:rest]]
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "returns a Proc with #lambda? true" do
+ pr = :to_s.to_proc
+ pr.should.lambda?
+ end
+
+ it "produces a Proc with arity -2" do
+ pr = :to_s.to_proc
+ pr.arity.should == -2
+ end
+
+ it "produces a Proc that always returns [[:req], [:rest]] for #parameters" do
+ pr = :to_s.to_proc
+ pr.parameters.should == [[:req], [:rest]]
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "only calls public methods" do
+ body = proc do
+ public def pub; @a << :pub end
+ protected def pro; @a << :pro end
+ private def pri; @a << :pri end
+ attr_reader :a
+ end
+
+ @a = []
+ singleton_class.class_eval(&body)
+ tap(&:pub)
+ proc{tap(&:pro)}.should raise_error(NoMethodError, /protected method `pro' called/)
+ proc{tap(&:pri)}.should raise_error(NoMethodError, /private method `pri' called/)
+ @a.should == [:pub]
+
+ @a = []
+ c = Class.new(&body)
+ o = c.new
+ o.instance_variable_set(:@a, [])
+ o.tap(&:pub)
+ proc{tap(&:pro)}.should raise_error(NoMethodError, /protected method `pro' called/)
+ proc{o.tap(&:pri)}.should raise_error(NoMethodError, /private method `pri' called/)
+ o.a.should == [:pub]
+ end
+ end
+
+ it "raises an ArgumentError when calling #call on the Proc without receiver" do
+ -> {
+ :object_id.to_proc.call
+ }.should raise_error(ArgumentError, /no receiver given|wrong number of arguments \(given 0, expected 1\+\)/)
+ end
+
+ it "passes along the block passed to Proc#call" do
+ klass = Class.new do
+ def m
+ yield
+ end
+
+ def to_proc
+ :m.to_proc.call(self) { :value }
+ end
+ end
+ klass.new.to_proc.should == :value
+ end
+
+ it "produces a proc with source location nil" do
+ pr = :to_s.to_proc
+ pr.source_location.should == nil
+ end
+end
diff --git a/spec/ruby/core/symbol/to_s_spec.rb b/spec/ruby/core/symbol/to_s_spec.rb
new file mode 100644
index 0000000000..cd963faa28
--- /dev/null
+++ b/spec/ruby/core/symbol/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/id2name'
+
+describe "Symbol#to_s" do
+ it_behaves_like :symbol_id2name, :to_s
+end
diff --git a/spec/ruby/core/symbol/to_sym_spec.rb b/spec/ruby/core/symbol/to_sym_spec.rb
new file mode 100644
index 0000000000..e75f3d48a8
--- /dev/null
+++ b/spec/ruby/core/symbol/to_sym_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Symbol#to_sym" do
+ it "returns self" do
+ [:rubinius, :squash, :[], :@ruby, :@@ruby].each do |sym|
+ sym.to_sym.should == sym
+ end
+ end
+end
diff --git a/spec/ruby/core/symbol/upcase_spec.rb b/spec/ruby/core/symbol/upcase_spec.rb
new file mode 100644
index 0000000000..f704bdcbf3
--- /dev/null
+++ b/spec/ruby/core/symbol/upcase_spec.rb
@@ -0,0 +1,21 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "Symbol#upcase" do
+ it "returns a Symbol" do
+ :glark.upcase.should be_an_instance_of(Symbol)
+ end
+
+ it "converts lowercase ASCII characters to their uppercase equivalents" do
+ :lOwEr.upcase.should == :LOWER
+ end
+
+ it "capitalizes all Unicode characters" do
+ "äöü".to_sym.upcase.should == :"ÄÖÜ"
+ "aou".to_sym.upcase.should == :"AOU"
+ end
+
+ it "leaves non-alphabetic ASCII characters as they were" do
+ "Glark?!?".to_sym.upcase.should == :"GLARK?!?"
+ end
+end
diff --git a/spec/ruby/core/systemexit/initialize_spec.rb b/spec/ruby/core/systemexit/initialize_spec.rb
new file mode 100644
index 0000000000..2cebaa7993
--- /dev/null
+++ b/spec/ruby/core/systemexit/initialize_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+describe "SystemExit#initialize" do
+ it "accepts a status" do
+ s = SystemExit.new 1
+ s.status.should == 1
+ s.message.should == 'SystemExit'
+ end
+
+ it "accepts a message" do
+ s = SystemExit.new 'message'
+ s.status.should == 0
+ s.message.should == 'message'
+ end
+
+ it "accepts a status and message" do
+ s = SystemExit.new 10, 'message'
+ s.status.should == 10
+ s.message.should == 'message'
+ end
+
+ it "sets the status to 0 by default" do
+ s = SystemExit.new
+ s.status.should == 0
+ end
+end
diff --git a/spec/ruby/core/systemexit/success_spec.rb b/spec/ruby/core/systemexit/success_spec.rb
new file mode 100644
index 0000000000..ba2fd22ded
--- /dev/null
+++ b/spec/ruby/core/systemexit/success_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "SystemExit#success?" do
+ it "returns true when the status is 0" do
+ s = SystemExit.new 0
+ s.should.success?
+ end
+
+ it "returns false when the status is not 0" do
+ s = SystemExit.new 1
+ s.should_not.success?
+ end
+end
diff --git a/spec/ruby/core/thread/abort_on_exception_spec.rb b/spec/ruby/core/thread/abort_on_exception_spec.rb
new file mode 100644
index 0000000000..34b648ca0f
--- /dev/null
+++ b/spec/ruby/core/thread/abort_on_exception_spec.rb
@@ -0,0 +1,106 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#abort_on_exception" do
+ before do
+ ThreadSpecs.clear_state
+ @thread = Thread.new { Thread.pass until ThreadSpecs.state == :exit }
+ end
+
+ after do
+ ThreadSpecs.state = :exit
+ @thread.join
+ end
+
+ it "is false by default" do
+ @thread.abort_on_exception.should be_false
+ end
+
+ it "returns true when #abort_on_exception= is passed true" do
+ @thread.abort_on_exception = true
+ @thread.abort_on_exception.should be_true
+ end
+end
+
+describe :thread_abort_on_exception, shared: true do
+ before do
+ @thread = Thread.new do
+ Thread.pass until ThreadSpecs.state == :run
+ raise RuntimeError, "Thread#abort_on_exception= specs"
+ end
+ end
+
+ it "causes the main thread to raise the exception raised in the thread" do
+ begin
+ ScratchPad << :before
+
+ @thread.abort_on_exception = true if @object
+ -> do
+ ThreadSpecs.state = :run
+ # Wait for the main thread to be interrupted
+ sleep
+ end.should raise_error(RuntimeError, "Thread#abort_on_exception= specs")
+
+ ScratchPad << :after
+ rescue Exception => e
+ ScratchPad << [:rescue, e]
+ end
+
+ ScratchPad.recorded.should == [:before, :after]
+ end
+end
+
+describe "Thread#abort_on_exception=" do
+ describe "when enabled and the thread dies due to an exception" do
+ before do
+ ScratchPad.record []
+ ThreadSpecs.clear_state
+ @stderr, $stderr = $stderr, IOStub.new
+ end
+
+ after do
+ $stderr = @stderr
+ end
+
+ it_behaves_like :thread_abort_on_exception, nil, true
+ end
+end
+
+describe "Thread.abort_on_exception" do
+ before do
+ @abort_on_exception = Thread.abort_on_exception
+ end
+
+ after do
+ Thread.abort_on_exception = @abort_on_exception
+ end
+
+ it "is false by default" do
+ Thread.abort_on_exception.should == false
+ end
+
+ it "returns true when .abort_on_exception= is passed true" do
+ Thread.abort_on_exception = true
+ Thread.abort_on_exception.should be_true
+ end
+end
+
+describe "Thread.abort_on_exception=" do
+ describe "when enabled and a non-main thread dies due to an exception" do
+ before :each do
+ ScratchPad.record []
+ ThreadSpecs.clear_state
+ @stderr, $stderr = $stderr, IOStub.new
+
+ @abort_on_exception = Thread.abort_on_exception
+ Thread.abort_on_exception = true
+ end
+
+ after :each do
+ Thread.abort_on_exception = @abort_on_exception
+ $stderr = @stderr
+ end
+
+ it_behaves_like :thread_abort_on_exception, nil, false
+ end
+end
diff --git a/spec/ruby/core/thread/add_trace_func_spec.rb b/spec/ruby/core/thread/add_trace_func_spec.rb
new file mode 100644
index 0000000000..0abae81a78
--- /dev/null
+++ b/spec/ruby/core/thread/add_trace_func_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Thread#add_trace_func" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/thread/alive_spec.rb b/spec/ruby/core/thread/alive_spec.rb
new file mode 100644
index 0000000000..c2f5f5371d
--- /dev/null
+++ b/spec/ruby/core/thread/alive_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#alive?" do
+ it "can check it's own status" do
+ ThreadSpecs.status_of_current_thread.should.alive?
+ end
+
+ it "describes a running thread" do
+ ThreadSpecs.status_of_running_thread.should.alive?
+ end
+
+ it "describes a sleeping thread" do
+ ThreadSpecs.status_of_sleeping_thread.should.alive?
+ end
+
+ it "describes a blocked thread" do
+ ThreadSpecs.status_of_blocked_thread.should.alive?
+ end
+
+ it "describes a completed thread" do
+ ThreadSpecs.status_of_completed_thread.should_not.alive?
+ end
+
+ it "describes a killed thread" do
+ ThreadSpecs.status_of_killed_thread.should_not.alive?
+ end
+
+ it "describes a thread with an uncaught exception" do
+ ThreadSpecs.status_of_thread_with_uncaught_exception.should_not.alive?
+ end
+
+ it "describes a dying running thread" do
+ ThreadSpecs.status_of_dying_running_thread.should.alive?
+ end
+
+ it "describes a dying sleeping thread" do
+ ThreadSpecs.status_of_dying_sleeping_thread.should.alive?
+ end
+
+ it "returns true for a killed but still running thread" do
+ exit = false
+ t = Thread.new do
+ begin
+ sleep
+ ensure
+ Thread.pass until exit
+ end
+ end
+
+ ThreadSpecs.spin_until_sleeping(t)
+
+ t.kill
+ t.should.alive?
+ exit = true
+ t.join
+ end
+end
diff --git a/spec/ruby/core/thread/allocate_spec.rb b/spec/ruby/core/thread/allocate_spec.rb
new file mode 100644
index 0000000000..cfd556812f
--- /dev/null
+++ b/spec/ruby/core/thread/allocate_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Thread.allocate" do
+ it "raises a TypeError" do
+ -> {
+ Thread.allocate
+ }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/limit_spec.rb b/spec/ruby/core/thread/backtrace/limit_spec.rb
new file mode 100644
index 0000000000..26a87a806c
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/limit_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is "3.1" do
+ describe "Thread::Backtrace.limit" do
+ it "returns maximum backtrace length set by --backtrace-limit command-line option" do
+ out = ruby_exe("print Thread::Backtrace.limit", options: "--backtrace-limit=2")
+ out.should == "2"
+ end
+
+ it "returns -1 when --backtrace-limit command-line option is not set" do
+ out = ruby_exe("print Thread::Backtrace.limit")
+ out.should == "-1"
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb
new file mode 100644
index 0000000000..e35e1fc0b4
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb
@@ -0,0 +1,102 @@
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Thread::Backtrace::Location#absolute_path' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.locations[0]
+ end
+
+ it 'returns the absolute path of the call frame' do
+ @frame.absolute_path.should == File.realpath(__FILE__)
+ end
+
+ it 'returns an absolute path when using a relative main script path' do
+ script = fixture(__FILE__, 'absolute_path_main.rb')
+ Dir.chdir(File.dirname(script)) do
+ ruby_exe('absolute_path_main.rb').should == "absolute_path_main.rb\n#{script}\n"
+ end
+ end
+
+ it 'returns the correct absolute path when using a relative main script path and changing CWD' do
+ script = fixture(__FILE__, 'subdir/absolute_path_main_chdir.rb')
+ sibling = fixture(__FILE__, 'subdir/sibling.rb')
+ subdir = File.dirname script
+ Dir.chdir(fixture(__FILE__)) do
+ ruby_exe('subdir/absolute_path_main_chdir.rb').should == "subdir/absolute_path_main_chdir.rb\n#{subdir}\n#{subdir}\n#{script}\n#{sibling}\n"
+ end
+ end
+
+ context "when used in eval with a given filename" do
+ code = "caller_locations(0)[0].absolute_path"
+
+ ruby_version_is ""..."3.1" do
+ it "returns filename with absolute_path" do
+ eval(code, nil, "foo.rb").should == "foo.rb"
+ eval(code, nil, "foo/bar.rb").should == "foo/bar.rb"
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "returns nil with absolute_path" do
+ eval(code, nil, "foo.rb").should == nil
+ eval(code, nil, "foo/bar.rb").should == nil
+ end
+ end
+ end
+
+ context "when used in #method_added" do
+ it "returns the user filename that defined the method" do
+ path = fixture(__FILE__, "absolute_path_method_added.rb")
+ load path
+ locations = ScratchPad.recorded
+ locations[0].absolute_path.should == path
+ # Make sure it's from the class body, not from the file top-level
+ locations[0].label.should include 'MethodAddedAbsolutePath'
+ end
+ end
+
+ context "when used in a core method" do
+ it "returns nil" do
+ location = nil
+ tap { location = caller_locations(1, 1)[0] }
+ location.label.should == "tap"
+ if location.path.start_with?("<internal:")
+ location.absolute_path.should == nil
+ else
+ location.absolute_path.should == File.realpath(__FILE__)
+ end
+ end
+ end
+
+ context "canonicalization" do
+ platform_is_not :windows do
+ before :each do
+ @file = fixture(__FILE__, "absolute_path.rb")
+ @symlink = tmp("symlink.rb")
+ File.symlink(@file, @symlink)
+ ScratchPad.record []
+ end
+
+ after :each do
+ rm_r @symlink
+ end
+
+ it "returns a canonical path without symlinks, even when __FILE__ does not" do
+ realpath = File.realpath(@symlink)
+ realpath.should_not == @symlink
+
+ load @symlink
+ ScratchPad.recorded.should == [@symlink, realpath]
+ end
+
+ it "returns a canonical path without symlinks, even when __FILE__ is removed" do
+ realpath = File.realpath(@symlink)
+ realpath.should_not == @symlink
+
+ ScratchPad << -> { rm_r(@symlink) }
+ load @symlink
+ ScratchPad.recorded.should == [@symlink, realpath]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/base_label_spec.rb b/spec/ruby/core/thread/backtrace/location/base_label_spec.rb
new file mode 100644
index 0000000000..739f62f42f
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/base_label_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Thread::Backtrace::Location#base_label' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.locations[0]
+ end
+
+ it 'returns the base label of the call frame' do
+ @frame.base_label.should == '<top (required)>'
+ end
+
+ describe 'when call frame is inside a block' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.block_location[0]
+ end
+
+ it 'returns the name of the method that contains the block' do
+ @frame.base_label.should == 'block_location'
+ end
+ end
+
+ it "is <module:A> for a module body" do
+ module ThreadBacktraceLocationSpecs
+ module ModuleLabel
+ ScratchPad.record caller_locations(0, 1)[0].base_label
+ end
+ end
+ ScratchPad.recorded.should == '<module:ModuleLabel>'
+ end
+
+ it "is <class:A> for a class body" do
+ module ThreadBacktraceLocationSpecs
+ class ClassLabel
+ ScratchPad.record caller_locations(0, 1)[0].base_label
+ end
+ end
+ ScratchPad.recorded.should == '<class:ClassLabel>'
+ end
+
+ it "is 'singleton class' for a singleton class body" do
+ module ThreadBacktraceLocationSpecs
+ class << Object.new
+ ScratchPad.record caller_locations(0, 1)[0].base_label
+ end
+ end
+ ScratchPad.recorded.should =~ /\A(singleton class|<singleton class>)\z/
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb
new file mode 100644
index 0000000000..875e97ffac
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb
@@ -0,0 +1,4 @@
+action = ScratchPad.recorded.pop
+ScratchPad << __FILE__
+action.call if action
+ScratchPad << caller_locations(0)[0].absolute_path
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb
new file mode 100644
index 0000000000..d2b23393d4
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb
@@ -0,0 +1,2 @@
+puts __FILE__
+puts caller_locations(0)[0].absolute_path
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_method_added.rb b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_method_added.rb
new file mode 100644
index 0000000000..26d6298a19
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_method_added.rb
@@ -0,0 +1,10 @@
+module ThreadBacktraceLocationSpecs
+ class MethodAddedAbsolutePath
+ def self.method_added(name)
+ ScratchPad.record caller_locations
+ end
+
+ def foo
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb b/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb
new file mode 100644
index 0000000000..e903c3e450
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb
@@ -0,0 +1,35 @@
+module ThreadBacktraceLocationSpecs
+ MODULE_LOCATION = caller_locations(0) rescue nil
+
+ def self.locations
+ caller_locations
+ end
+
+ def self.method_location
+ caller_locations(0)
+ end
+
+ def self.block_location
+ 1.times do
+ return caller_locations(0)
+ end
+ end
+
+ def self.locations_inside_nested_blocks
+ first_level_location = nil
+ second_level_location = nil
+ third_level_location = nil
+
+ 1.times do
+ first_level_location = locations[0]
+ 1.times do
+ second_level_location = locations[0]
+ 1.times do
+ third_level_location = locations[0]
+ end
+ end
+ end
+
+ [first_level_location, second_level_location, third_level_location]
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_main.rb b/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_main.rb
new file mode 100644
index 0000000000..b124c8161c
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_main.rb
@@ -0,0 +1,5 @@
+1.times do
+ puts Thread.current.backtrace_locations(1..1)[0].label
+end
+
+require_relative 'locations_in_required'
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_required.rb b/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_required.rb
new file mode 100644
index 0000000000..5f5ed89e98
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_required.rb
@@ -0,0 +1,3 @@
+1.times do
+ puts Thread.current.backtrace_locations(1..1)[0].label
+end
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/main.rb b/spec/ruby/core/thread/backtrace/location/fixtures/main.rb
new file mode 100644
index 0000000000..bde208a059
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/main.rb
@@ -0,0 +1,5 @@
+def backtrace_location_example
+ caller_locations[0].path
+end
+
+print backtrace_location_example
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/path.rb b/spec/ruby/core/thread/backtrace/location/fixtures/path.rb
new file mode 100644
index 0000000000..fba34cb0bc
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/path.rb
@@ -0,0 +1,2 @@
+ScratchPad << __FILE__
+ScratchPad << caller_locations(0)[0].path
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb
new file mode 100644
index 0000000000..33c8fb36ef
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb
@@ -0,0 +1,11 @@
+puts __FILE__
+puts __dir__
+Dir.chdir __dir__
+
+# Check __dir__ is still correct after chdir
+puts __dir__
+
+puts caller_locations(0)[0].absolute_path
+
+# require_relative also needs to know the absolute path of the current file so we test it here too
+require_relative 'sibling'
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb
new file mode 100644
index 0000000000..2a854ddccd
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb
@@ -0,0 +1 @@
+puts __FILE__
diff --git a/spec/ruby/core/thread/backtrace/location/inspect_spec.rb b/spec/ruby/core/thread/backtrace/location/inspect_spec.rb
new file mode 100644
index 0000000000..20e477a5a6
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/inspect_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Thread::Backtrace::Location#inspect' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.locations[0]
+ @line = __LINE__ - 1
+ end
+
+ it 'converts the call frame to a String' do
+ @frame.inspect.should include("#{__FILE__}:#{@line}:in ")
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/label_spec.rb b/spec/ruby/core/thread/backtrace/location/label_spec.rb
new file mode 100644
index 0000000000..7312d017e5
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/label_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Thread::Backtrace::Location#label' do
+ it 'returns the base label of the call frame' do
+ ThreadBacktraceLocationSpecs.locations[0].label.should include('<top (required)>')
+ end
+
+ it 'returns the method name for a method location' do
+ ThreadBacktraceLocationSpecs.method_location[0].label.should == "method_location"
+ end
+
+ it 'returns the block name for a block location' do
+ ThreadBacktraceLocationSpecs.block_location[0].label.should == "block in block_location"
+ end
+
+ it 'returns the module name for a module location' do
+ ThreadBacktraceLocationSpecs::MODULE_LOCATION[0].label.should include "ThreadBacktraceLocationSpecs"
+ end
+
+ it 'includes the nesting level of a block as part of the location label' do
+ first_level_location, second_level_location, third_level_location =
+ ThreadBacktraceLocationSpecs.locations_inside_nested_blocks
+
+ first_level_location.label.should == 'block in locations_inside_nested_blocks'
+ second_level_location.label.should == 'block (2 levels) in locations_inside_nested_blocks'
+ third_level_location.label.should == 'block (3 levels) in locations_inside_nested_blocks'
+ end
+
+ it 'sets the location label for a top-level block differently depending on it being in the main file or a required file' do
+ path = fixture(__FILE__, "locations_in_main.rb")
+ main_label, required_label = ruby_exe(path).lines
+
+ main_label.should == "block in <main>\n"
+ required_label.should == "block in <top (required)>\n"
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/lineno_spec.rb b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb
new file mode 100644
index 0000000000..d14cf17514
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Thread::Backtrace::Location#lineno' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.locations[0]
+ @line = __LINE__ - 1
+ end
+
+ it 'returns the absolute path of the call frame' do
+ @frame.lineno.should == @line
+ end
+
+ it 'should be the same line number as in #to_s, including for core methods' do
+ # Get the caller_locations from a call made into a core library method
+ locations = [:non_empty].map { caller_locations }[0]
+
+ locations.each do |location|
+ line_number = location.to_s[/:(\d+):/, 1]
+ location.lineno.should == Integer(line_number)
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/path_spec.rb b/spec/ruby/core/thread/backtrace/location/path_spec.rb
new file mode 100644
index 0000000000..7863c055d3
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/path_spec.rb
@@ -0,0 +1,124 @@
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Thread::Backtrace::Location#path' do
+ context 'outside a main script' do
+ it 'returns an absolute path' do
+ frame = ThreadBacktraceLocationSpecs.locations[0]
+
+ frame.path.should == __FILE__
+ end
+ end
+
+ context 'in a main script' do
+ before do
+ @script = fixture(__FILE__, 'main.rb')
+ end
+
+ context 'when the script is in the working directory' do
+ before do
+ @directory = File.dirname(@script)
+ end
+
+ context 'when using a relative script path' do
+ it 'returns a path relative to the working directory' do
+ Dir.chdir(@directory) {
+ ruby_exe('main.rb')
+ }.should == 'main.rb'
+ end
+ end
+
+ context 'when using an absolute script path' do
+ it 'returns an absolute path' do
+ Dir.chdir(@directory) {
+ ruby_exe(@script)
+ }.should == @script
+ end
+ end
+ end
+
+ context 'when the script is in a sub directory of the working directory' do
+ context 'when using a relative script path' do
+ it 'returns a path relative to the working directory' do
+ path = 'fixtures/main.rb'
+ directory = File.dirname(__FILE__)
+ Dir.chdir(directory) {
+ ruby_exe(path)
+ }.should == path
+ end
+ end
+
+ context 'when using an absolute script path' do
+ it 'returns an absolute path' do
+ ruby_exe(@script).should == @script
+ end
+ end
+ end
+
+ context 'when the script is outside of the working directory' do
+ before :each do
+ @parent_dir = tmp('path_outside_pwd')
+ @sub_dir = File.join(@parent_dir, 'sub')
+ @script = File.join(@parent_dir, 'main.rb')
+ source = fixture(__FILE__, 'main.rb')
+
+ mkdir_p(@sub_dir)
+
+ cp(source, @script)
+ end
+
+ after :each do
+ rm_r(@parent_dir)
+ end
+
+ context 'when using a relative script path' do
+ it 'returns a path relative to the working directory' do
+ Dir.chdir(@sub_dir) {
+ ruby_exe('../main.rb')
+ }.should == '../main.rb'
+ end
+ end
+
+ context 'when using an absolute path' do
+ it 'returns an absolute path' do
+ ruby_exe(@script).should == @script
+ end
+ end
+ end
+ end
+
+ it 'should be the same path as in #to_s, including for core methods' do
+ # Get the caller_locations from a call made into a core library method
+ locations = [:non_empty].map { caller_locations }[0]
+
+ locations.each do |location|
+ filename = location.to_s[/^(.+):\d+:/, 1]
+ path = location.path
+
+ path.should == filename
+ end
+ end
+
+ context "canonicalization" do
+ platform_is_not :windows do
+ before :each do
+ @file = fixture(__FILE__, "path.rb")
+ @symlink = tmp("symlink.rb")
+ File.symlink(@file, @symlink)
+ ScratchPad.record []
+ end
+
+ after :each do
+ rm_r @symlink
+ end
+
+ it "returns a non-canonical path with symlinks, the same as __FILE__" do
+ realpath = File.realpath(@symlink)
+ realpath.should_not == @symlink
+
+ load @symlink
+ ScratchPad.recorded.should == [@symlink, @symlink]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/to_s_spec.rb b/spec/ruby/core/thread/backtrace/location/to_s_spec.rb
new file mode 100644
index 0000000000..5911cdced0
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/to_s_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Thread::Backtrace::Location#to_s' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.locations[0]
+ @line = __LINE__ - 1
+ end
+
+ it 'converts the call frame to a String' do
+ @frame.to_s.should include("#{__FILE__}:#{@line}:in ")
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace_locations_spec.rb b/spec/ruby/core/thread/backtrace_locations_spec.rb
new file mode 100644
index 0000000000..c970ae023b
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace_locations_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+
+describe "Thread#backtrace_locations" do
+ it "returns an Array" do
+ locations = Thread.current.backtrace_locations
+ locations.should be_an_instance_of(Array)
+ locations.should_not be_empty
+ end
+
+ it "sets each element to a Thread::Backtrace::Location" do
+ locations = Thread.current.backtrace_locations
+ locations.each { |loc| loc.should be_an_instance_of(Thread::Backtrace::Location) }
+ end
+
+ it "can be called on any Thread" do
+ locations = Thread.new { Thread.current.backtrace_locations }.value
+ locations.should be_an_instance_of(Array)
+ locations.should_not be_empty
+ locations.each { |loc| loc.should be_an_instance_of(Thread::Backtrace::Location) }
+ end
+
+ it "can be called with a number of locations to omit" do
+ locations1 = Thread.current.backtrace_locations
+ locations2 = Thread.current.backtrace_locations(2)
+ locations2.length.should == locations1[2..-1].length
+ locations2.map(&:to_s).should == locations1[2..-1].map(&:to_s)
+ end
+
+ it "can be called with a maximum number of locations to return as second parameter" do
+ locations1 = Thread.current.backtrace_locations
+ locations2 = Thread.current.backtrace_locations(2, 3)
+ locations2.map(&:to_s).should == locations1[2..4].map(&:to_s)
+ end
+
+ it "can be called with a range" do
+ locations1 = Thread.current.backtrace_locations
+ locations2 = Thread.current.backtrace_locations(2..4)
+ locations2.map(&:to_s).should == locations1[2..4].map(&:to_s)
+ end
+
+ it "can be called with a range whose end is negative" do
+ Thread.current.backtrace_locations(2..-1).map(&:to_s).should == Thread.current.backtrace_locations[2..-1].map(&:to_s)
+ Thread.current.backtrace_locations(2..-2).map(&:to_s).should == Thread.current.backtrace_locations[2..-2].map(&:to_s)
+ end
+
+ it "can be called with an endless range" do
+ locations1 = Thread.current.backtrace_locations(0)
+ locations2 = Thread.current.backtrace_locations(eval("(2..)"))
+ locations2.map(&:to_s).should == locations1[2..-1].map(&:to_s)
+ end
+
+ it "can be called with an beginless range" do
+ locations1 = Thread.current.backtrace_locations(0)
+ locations2 = Thread.current.backtrace_locations((..5))
+ locations2.map(&:to_s)[eval("(2..)")].should == locations1[(..5)].map(&:to_s)[eval("(2..)")]
+ end
+
+ it "returns nil if omitting more locations than available" do
+ Thread.current.backtrace_locations(100).should == nil
+ Thread.current.backtrace_locations(100..-1).should == nil
+ end
+
+ it "returns [] if omitting exactly the number of locations available" do
+ omit = Thread.current.backtrace_locations.length
+ Thread.current.backtrace_locations(omit).should == []
+ end
+
+ it "without argument is the same as showing all locations with 0..-1" do
+ Thread.current.backtrace_locations.map(&:to_s).should == Thread.current.backtrace_locations(0..-1).map(&:to_s)
+ end
+
+ it "the first location reports the call to #backtrace_locations" do
+ Thread.current.backtrace_locations(0..0)[0].to_s.should == "#{__FILE__ }:#{__LINE__ }:in `backtrace_locations'"
+ end
+
+ it "[1..-1] is the same as #caller_locations(0..-1) for Thread.current" do
+ Thread.current.backtrace_locations(1..-1).map(&:to_s).should == caller_locations(0..-1).map(&:to_s)
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace_spec.rb b/spec/ruby/core/thread/backtrace_spec.rb
new file mode 100644
index 0000000000..9001b1b7eb
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+
+describe "Thread#backtrace" do
+ it "returns the current backtrace of a thread" do
+ t = Thread.new do
+ begin
+ sleep
+ rescue
+ end
+ end
+
+ Thread.pass while t.status && t.status != 'sleep'
+
+ backtrace = t.backtrace
+ backtrace.should be_kind_of(Array)
+ backtrace.first.should =~ /`sleep'/
+
+ t.raise 'finish the thread'
+ t.join
+ end
+
+ it "returns nil for dead thread" do
+ t = Thread.new {}
+ t.join
+ t.backtrace.should == nil
+ end
+
+ it "returns an array (which may be empty) immediately after the thread is created" do
+ t = Thread.new { sleep }
+ backtrace = t.backtrace
+ t.kill
+ t.join
+ backtrace.should be_kind_of(Array)
+ end
+
+ it "can be called with a number of locations to omit" do
+ locations1 = Thread.current.backtrace
+ locations2 = Thread.current.backtrace(2)
+ locations1[2..-1].length.should == locations2.length
+ locations1[2..-1].map(&:to_s).should == locations2.map(&:to_s)
+ end
+
+ it "can be called with a maximum number of locations to return as second parameter" do
+ locations1 = Thread.current.backtrace
+ locations2 = Thread.current.backtrace(2, 3)
+ locations1[2..4].map(&:to_s).should == locations2.map(&:to_s)
+ end
+
+ it "can be called with a range" do
+ locations1 = Thread.current.backtrace
+ locations2 = Thread.current.backtrace(2..4)
+ locations1[2..4].map(&:to_s).should == locations2.map(&:to_s)
+ end
+
+ it "can be called with a range whose end is negative" do
+ Thread.current.backtrace(2..-1).should == Thread.current.backtrace[2..-1]
+ Thread.current.backtrace(2..-2).should == Thread.current.backtrace[2..-2]
+ end
+
+ it "returns nil if omitting more locations than available" do
+ Thread.current.backtrace(100).should == nil
+ Thread.current.backtrace(100..-1).should == nil
+ end
+
+ it "returns [] if omitting exactly the number of locations available" do
+ omit = Thread.current.backtrace.length
+ Thread.current.backtrace(omit).should == []
+ end
+end
diff --git a/spec/ruby/core/thread/current_spec.rb b/spec/ruby/core/thread/current_spec.rb
new file mode 100644
index 0000000000..f5ed1d95cd
--- /dev/null
+++ b/spec/ruby/core/thread/current_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread.current" do
+ it "returns a thread" do
+ current = Thread.current
+ current.should be_kind_of(Thread)
+ end
+
+ it "returns the current thread" do
+ t = Thread.new { Thread.current }
+ t.value.should equal(t)
+ Thread.current.should_not equal(t.value)
+ end
+
+ it "returns the correct thread in a Fiber" do
+ # This catches a bug where Fibers are running on a thread-pool
+ # and Fibers from a different Ruby Thread reuse the same native thread.
+ # Caching the Ruby Thread based on the native thread is not correct in that case.
+ 2.times do
+ t = Thread.new {
+ cur = Thread.current
+ Fiber.new {
+ Thread.current
+ }.resume.should equal cur
+ cur
+ }
+ t.value.should equal t
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/element_reference_spec.rb b/spec/ruby/core/thread/element_reference_spec.rb
new file mode 100644
index 0000000000..85280cb287
--- /dev/null
+++ b/spec/ruby/core/thread/element_reference_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#[]" do
+ it "gives access to thread local values" do
+ th = Thread.new do
+ Thread.current[:value] = 5
+ end
+ th.join
+ th[:value].should == 5
+ Thread.current[:value].should == nil
+ end
+
+ it "is not shared across threads" do
+ t1 = Thread.new do
+ Thread.current[:value] = 1
+ end
+ t2 = Thread.new do
+ Thread.current[:value] = 2
+ end
+ [t1,t2].each {|x| x.join}
+ t1[:value].should == 1
+ t2[:value].should == 2
+ end
+
+ it "is accessible using strings or symbols" do
+ t1 = Thread.new do
+ Thread.current[:value] = 1
+ end
+ t2 = Thread.new do
+ Thread.current["value"] = 2
+ end
+ [t1,t2].each {|x| x.join}
+ t1[:value].should == 1
+ t1["value"].should == 1
+ t2[:value].should == 2
+ t2["value"].should == 2
+ end
+
+ it "raises exceptions on the wrong type of keys" do
+ -> { Thread.current[nil] }.should raise_error(TypeError)
+ -> { Thread.current[5] }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/thread/element_set_spec.rb b/spec/ruby/core/thread/element_set_spec.rb
new file mode 100644
index 0000000000..c7498f7ac9
--- /dev/null
+++ b/spec/ruby/core/thread/element_set_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#[]=" do
+ after :each do
+ Thread.current[:value] = nil
+ end
+
+ it "raises a FrozenError if the thread is frozen" do
+ Thread.new do
+ th = Thread.current
+ th.freeze
+ -> {
+ th[:foo] = "bar"
+ }.should raise_error(FrozenError, /frozen/)
+ end.join
+ end
+
+ it "raises exceptions on the wrong type of keys" do
+ -> { Thread.current[nil] = true }.should raise_error(TypeError)
+ -> { Thread.current[5] = true }.should raise_error(TypeError)
+ end
+
+ it "is not shared across fibers" do
+ fib = Fiber.new do
+ Thread.current[:value] = 1
+ Fiber.yield
+ Thread.current[:value].should == 1
+ end
+ fib.resume
+ Thread.current[:value].should be_nil
+ Thread.current[:value] = 2
+ fib.resume
+ Thread.current[:value] = 2
+ end
+
+ it "stores a local in another thread when in a fiber" do
+ fib = Fiber.new do
+ t = Thread.new do
+ sleep
+ Thread.current[:value].should == 1
+ end
+
+ Thread.pass while t.status and t.status != "sleep"
+ t[:value] = 1
+ t.wakeup
+ t.join
+ end
+ fib.resume
+ end
+end
diff --git a/spec/ruby/core/thread/exclusive_spec.rb b/spec/ruby/core/thread/exclusive_spec.rb
new file mode 100644
index 0000000000..37c4b19d1a
--- /dev/null
+++ b/spec/ruby/core/thread/exclusive_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ describe "Thread.exclusive" do
+ before :each do
+ ScratchPad.clear
+ $VERBOSE, @verbose = nil, $VERBOSE
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it "yields to the block" do
+ Thread.exclusive { ScratchPad.record true }
+ ScratchPad.recorded.should == true
+ end
+
+ it "returns the result of yielding" do
+ Thread.exclusive { :result }.should == :result
+ end
+
+ it "blocks the caller if another thread is also in an exclusive block" do
+ m = Mutex.new
+ q1 = Queue.new
+ q2 = Queue.new
+
+ t = Thread.new {
+ Thread.exclusive {
+ q1.push :ready
+ q2.pop
+ }
+ }
+
+ q1.pop.should == :ready
+
+ -> { Thread.exclusive { } }.should block_caller
+
+ q2.push :done
+ t.join
+ end
+
+ it "is not recursive" do
+ Thread.exclusive do
+ -> { Thread.exclusive { } }.should raise_error(ThreadError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/exit_spec.rb b/spec/ruby/core/thread/exit_spec.rb
new file mode 100644
index 0000000000..c3f710920e
--- /dev/null
+++ b/spec/ruby/core/thread/exit_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/exit'
+
+describe "Thread#exit!" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "Thread.exit" do
+ it "causes the current thread to exit" do
+ thread = Thread.new { Thread.exit; sleep }
+ thread.join
+ thread.status.should be_false
+ end
+end
diff --git a/spec/ruby/core/thread/fetch_spec.rb b/spec/ruby/core/thread/fetch_spec.rb
new file mode 100644
index 0000000000..6b37d4cfc5
--- /dev/null
+++ b/spec/ruby/core/thread/fetch_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe 'Thread#fetch' do
+ describe 'with 2 arguments' do
+ it 'returns the value of the fiber-local variable if value has been assigned' do
+ th = Thread.new { Thread.current[:cat] = 'meow' }
+ th.join
+ th.fetch(:cat, true).should == 'meow'
+ end
+
+ it "returns the default value if fiber-local variable hasn't been assigned" do
+ th = Thread.new {}
+ th.join
+ th.fetch(:cat, true).should == true
+ end
+ end
+
+ describe 'with 1 argument' do
+ it 'raises a KeyError when the Thread does not have a fiber-local variable of the same name' do
+ th = Thread.new {}
+ th.join
+ -> { th.fetch(:cat) }.should raise_error(KeyError)
+ end
+
+ it 'returns the value of the fiber-local variable if value has been assigned' do
+ th = Thread.new { Thread.current[:cat] = 'meow' }
+ th.join
+ th.fetch(:cat).should == 'meow'
+ end
+ end
+
+ it 'raises an ArgumentError when not passed one or two arguments' do
+ -> { Thread.current.fetch() }.should raise_error(ArgumentError)
+ -> { Thread.current.fetch(1, 2, 3) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/thread/fixtures/classes.rb b/spec/ruby/core/thread/fixtures/classes.rb
new file mode 100644
index 0000000000..23a090feb0
--- /dev/null
+++ b/spec/ruby/core/thread/fixtures/classes.rb
@@ -0,0 +1,297 @@
+module ThreadSpecs
+
+ class SubThread < Thread
+ def initialize(*args)
+ super { args.first << 1 }
+ end
+ end
+
+ class Status
+ attr_reader :thread, :inspect, :status, :to_s
+ def initialize(thread)
+ @thread = thread
+ @alive = thread.alive?
+ @inspect = thread.inspect
+ @to_s = thread.to_s
+ @status = thread.status
+ @stop = thread.stop?
+ end
+
+ def alive?
+ @alive
+ end
+
+ def stop?
+ @stop
+ end
+ end
+
+ # TODO: In the great Thread spec rewrite, abstract this
+ class << self
+ attr_accessor :state
+ end
+
+ def self.clear_state
+ @state = nil
+ end
+
+ def self.spin_until_sleeping(t)
+ Thread.pass while t.status and t.status != "sleep"
+ end
+
+ def self.sleeping_thread
+ Thread.new do
+ begin
+ sleep
+ ScratchPad.record :woken
+ rescue Object => e
+ ScratchPad.record e
+ end
+ end
+ end
+
+ def self.running_thread
+ Thread.new do
+ begin
+ ThreadSpecs.state = :running
+ loop { Thread.pass }
+ ScratchPad.record :woken
+ rescue Object => e
+ ScratchPad.record e
+ end
+ end
+ end
+
+ def self.completed_thread
+ Thread.new {}
+ end
+
+ def self.status_of_current_thread
+ Thread.new { Status.new(Thread.current) }.value
+ end
+
+ def self.status_of_running_thread
+ t = running_thread
+ Thread.pass while t.status and t.status != "run"
+ status = Status.new t
+ t.kill
+ t.join
+ status
+ end
+
+ def self.status_of_completed_thread
+ t = completed_thread
+ t.join
+ Status.new t
+ end
+
+ def self.status_of_sleeping_thread
+ t = sleeping_thread
+ Thread.pass while t.status and t.status != 'sleep'
+ status = Status.new t
+ t.run
+ t.join
+ status
+ end
+
+ def self.status_of_blocked_thread
+ m = Mutex.new
+ m.lock
+ t = Thread.new { m.lock }
+ Thread.pass while t.status and t.status != 'sleep'
+ status = Status.new t
+ m.unlock
+ t.join
+ status
+ end
+
+ def self.status_of_killed_thread
+ t = Thread.new { sleep }
+ Thread.pass while t.status and t.status != 'sleep'
+ t.kill
+ t.join
+ Status.new t
+ end
+
+ def self.status_of_thread_with_uncaught_exception
+ t = Thread.new {
+ Thread.current.report_on_exception = false
+ raise "error"
+ }
+ begin
+ t.join
+ rescue RuntimeError
+ end
+ Status.new t
+ end
+
+ def self.status_of_dying_running_thread
+ status = nil
+ t = dying_thread_ensures { status = Status.new Thread.current }
+ t.join
+ status
+ end
+
+ def self.status_of_dying_sleeping_thread
+ t = dying_thread_ensures { Thread.stop; }
+ Thread.pass while t.status and t.status != 'sleep'
+ status = Status.new t
+ t.wakeup
+ t.join
+ status
+ end
+
+ def self.status_of_dying_thread_after_sleep
+ status = nil
+ t = dying_thread_ensures {
+ Thread.stop
+ status = Status.new(Thread.current)
+ }
+ Thread.pass while t.status and t.status != 'sleep'
+ t.wakeup
+ Thread.pass while t.status and t.status == 'sleep'
+ t.join
+ status
+ end
+
+ def self.dying_thread_ensures(kill_method_name=:kill)
+ Thread.new do
+ Thread.current.report_on_exception = false
+ begin
+ Thread.current.send(kill_method_name)
+ ensure
+ yield
+ end
+ end
+ end
+
+ def self.dying_thread_with_outer_ensure(kill_method_name=:kill)
+ Thread.new do
+ Thread.current.report_on_exception = false
+ begin
+ begin
+ Thread.current.send(kill_method_name)
+ ensure
+ raise "In dying thread"
+ end
+ ensure
+ yield
+ end
+ end
+ end
+
+ def self.join_dying_thread_with_outer_ensure(kill_method_name=:kill)
+ t = dying_thread_with_outer_ensure(kill_method_name) { yield }
+ -> { t.join }.should raise_error(RuntimeError, "In dying thread")
+ return t
+ end
+
+ def self.wakeup_dying_sleeping_thread(kill_method_name=:kill)
+ t = ThreadSpecs.dying_thread_ensures(kill_method_name) { yield }
+ Thread.pass while t.status and t.status != 'sleep'
+ t.wakeup
+ t.join
+ end
+
+ def self.critical_is_reset
+ # Create another thread to verify that it can call Thread.critical=
+ t = Thread.new do
+ initial_critical = Thread.critical
+ Thread.critical = true
+ Thread.critical = false
+ initial_critical == false && Thread.critical == false
+ end
+ v = t.value
+ t.join
+ v
+ end
+
+ def self.counter
+ @@counter
+ end
+
+ def self.counter= c
+ @@counter = c
+ end
+
+ def self.increment_counter(incr)
+ incr.times do
+ begin
+ Thread.critical = true
+ @@counter += 1
+ ensure
+ Thread.critical = false
+ end
+ end
+ end
+
+ def self.critical_thread1
+ Thread.critical = true
+ Thread.current.key?(:thread_specs).should == false
+ end
+
+ def self.critical_thread2(is_thread_stop)
+ Thread.current[:thread_specs].should == 101
+ Thread.critical.should == !is_thread_stop
+ unless is_thread_stop
+ Thread.critical = false
+ end
+ end
+
+ def self.main_thread1(critical_thread, is_thread_sleep, is_thread_stop)
+ # Thread.stop resets Thread.critical. Also, with native threads, the Thread.Stop may not have executed yet
+ # since the main thread will race with the critical thread
+ unless is_thread_stop
+ Thread.critical.should == true
+ end
+ critical_thread[:thread_specs] = 101
+ if is_thread_sleep or is_thread_stop
+ # Thread#wakeup calls are not queued up. So we need to ensure that the thread is sleeping before calling wakeup
+ Thread.pass while critical_thread.status and critical_thread.status != "sleep"
+ critical_thread.wakeup
+ end
+ end
+
+ def self.main_thread2(critical_thread)
+ Thread.pass # The join below seems to cause a deadlock with CRuby unless Thread.pass is called first
+ critical_thread.join
+ Thread.critical.should == false
+ end
+
+ def self.critical_thread_yields_to_main_thread(is_thread_sleep=false, is_thread_stop=false)
+ @@after_first_sleep = false
+
+ critical_thread = Thread.new do
+ Thread.pass while Thread.main.status and Thread.main.status != "sleep"
+ critical_thread1()
+ Thread.main.wakeup
+ yield
+ Thread.pass while @@after_first_sleep != true # Need to ensure that the next statement does not see the first sleep itself
+ Thread.pass while Thread.main.status and Thread.main.status != "sleep"
+ critical_thread2(is_thread_stop)
+ Thread.main.wakeup
+ end
+
+ sleep 5
+ @@after_first_sleep = true
+ main_thread1(critical_thread, is_thread_sleep, is_thread_stop)
+ sleep 5
+ main_thread2(critical_thread)
+ end
+
+ def self.create_critical_thread
+ Thread.new do
+ Thread.critical = true
+ yield
+ Thread.critical = false
+ end
+ end
+
+ def self.create_and_kill_critical_thread(pass_after_kill=false)
+ ThreadSpecs.create_critical_thread do
+ Thread.current.kill
+ Thread.pass if pass_after_kill
+ ScratchPad.record("status=" + Thread.current.status)
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/fork_spec.rb b/spec/ruby/core/thread/fork_spec.rb
new file mode 100644
index 0000000000..a2f4181298
--- /dev/null
+++ b/spec/ruby/core/thread/fork_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/start'
+
+describe "Thread.fork" do
+ describe "Thread.start" do
+ it_behaves_like :thread_start, :fork
+ end
+end
diff --git a/spec/ruby/core/thread/group_spec.rb b/spec/ruby/core/thread/group_spec.rb
new file mode 100644
index 0000000000..59f5ac37c8
--- /dev/null
+++ b/spec/ruby/core/thread/group_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+describe "Thread#group" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/thread/handle_interrupt_spec.rb b/spec/ruby/core/thread/handle_interrupt_spec.rb
new file mode 100644
index 0000000000..ea7e81cb98
--- /dev/null
+++ b/spec/ruby/core/thread/handle_interrupt_spec.rb
@@ -0,0 +1,125 @@
+require_relative '../../spec_helper'
+
+describe "Thread.handle_interrupt" do
+ def make_handle_interrupt_thread(interrupt_config, blocking = true)
+ interrupt_class = Class.new(RuntimeError)
+
+ ScratchPad.record []
+
+ in_handle_interrupt = Queue.new
+ can_continue = Queue.new
+
+ thread = Thread.new do
+ begin
+ Thread.handle_interrupt(interrupt_config) do
+ begin
+ in_handle_interrupt << true
+ if blocking
+ Thread.pass # Make it clearer the other thread needs to wait for this one to be in #pop
+ can_continue.pop
+ else
+ begin
+ can_continue.pop(true)
+ rescue ThreadError
+ Thread.pass
+ retry
+ end
+ end
+ rescue interrupt_class
+ ScratchPad << :interrupted
+ end
+ end
+ rescue interrupt_class
+ ScratchPad << :deferred
+ end
+ end
+
+ in_handle_interrupt.pop
+ if blocking
+ # Ensure the thread is inside Thread#pop, as if thread.raise is done before it would be deferred
+ Thread.pass until thread.stop?
+ end
+ thread.raise interrupt_class, "interrupt"
+ can_continue << true
+ thread.join
+
+ ScratchPad.recorded
+ end
+
+ before :each do
+ Thread.pending_interrupt?.should == false # sanity check
+ end
+
+ it "with :never defers interrupts until exiting the handle_interrupt block" do
+ make_handle_interrupt_thread(RuntimeError => :never).should == [:deferred]
+ end
+
+ it "with :on_blocking defers interrupts until the next blocking call" do
+ make_handle_interrupt_thread(RuntimeError => :on_blocking).should == [:interrupted]
+ make_handle_interrupt_thread({ RuntimeError => :on_blocking }, false).should == [:deferred]
+ end
+
+ it "with :immediate handles interrupts immediately" do
+ make_handle_interrupt_thread(RuntimeError => :immediate).should == [:interrupted]
+ end
+
+ it "with :immediate immediately runs pending interrupts, before the block" do
+ Thread.handle_interrupt(RuntimeError => :never) do
+ current = Thread.current
+ Thread.new {
+ current.raise "interrupt immediate"
+ }.join
+
+ Thread.pending_interrupt?.should == true
+ -> {
+ Thread.handle_interrupt(RuntimeError => :immediate) {
+ flunk "not reached"
+ }
+ }.should raise_error(RuntimeError, "interrupt immediate")
+ Thread.pending_interrupt?.should == false
+ end
+ end
+
+ it "also works with suspended Fibers and does not duplicate interrupts" do
+ fiber = Fiber.new { Fiber.yield }
+ fiber.resume
+
+ Thread.handle_interrupt(RuntimeError => :never) do
+ current = Thread.current
+ Thread.new {
+ current.raise "interrupt with fibers"
+ }.join
+
+ Thread.pending_interrupt?.should == true
+ -> {
+ Thread.handle_interrupt(RuntimeError => :immediate) {
+ flunk "not reached"
+ }
+ }.should raise_error(RuntimeError, "interrupt with fibers")
+ Thread.pending_interrupt?.should == false
+ end
+
+ fiber.resume
+ end
+
+ it "runs pending interrupts at the end of the block, even if there was an exception raised in the block" do
+ executed = false
+ -> {
+ Thread.handle_interrupt(RuntimeError => :never) do
+ current = Thread.current
+ Thread.new {
+ current.raise "interrupt exception"
+ }.join
+
+ Thread.pending_interrupt?.should == true
+ executed = true
+ raise "regular exception"
+ end
+ }.should raise_error(RuntimeError, "interrupt exception")
+ executed.should == true
+ end
+
+ it "supports multiple pairs in the Hash" do
+ make_handle_interrupt_thread(ArgumentError => :never, RuntimeError => :never).should == [:deferred]
+ end
+end
diff --git a/spec/ruby/core/thread/ignore_deadlock_spec.rb b/spec/ruby/core/thread/ignore_deadlock_spec.rb
new file mode 100644
index 0000000000..53cc2a7f5b
--- /dev/null
+++ b/spec/ruby/core/thread/ignore_deadlock_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.0" do
+ describe "Thread.ignore_deadlock" do
+ it "returns false by default" do
+ Thread.ignore_deadlock.should == false
+ end
+ end
+
+ describe "Thread.ignore_deadlock=" do
+ it "changes the value of Thread.ignore_deadlock" do
+ ignore_deadlock = Thread.ignore_deadlock
+ Thread.ignore_deadlock = true
+ begin
+ Thread.ignore_deadlock.should == true
+ ensure
+ Thread.ignore_deadlock = ignore_deadlock
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/initialize_spec.rb b/spec/ruby/core/thread/initialize_spec.rb
new file mode 100644
index 0000000000..4fca900cd8
--- /dev/null
+++ b/spec/ruby/core/thread/initialize_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#initialize" do
+
+ describe "already initialized" do
+
+ before do
+ @t = Thread.new { sleep }
+ end
+
+ after do
+ @t.kill
+ @t.join
+ end
+
+ it "raises a ThreadError" do
+ -> {
+ @t.instance_eval do
+ initialize {}
+ end
+ }.should raise_error(ThreadError)
+ end
+
+ end
+
+end
diff --git a/spec/ruby/core/thread/inspect_spec.rb b/spec/ruby/core/thread/inspect_spec.rb
new file mode 100644
index 0000000000..bd6e0c31fc
--- /dev/null
+++ b/spec/ruby/core/thread/inspect_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Thread#inspect" do
+ it_behaves_like :thread_to_s, :inspect
+end
diff --git a/spec/ruby/core/thread/join_spec.rb b/spec/ruby/core/thread/join_spec.rb
new file mode 100644
index 0000000000..213fe2e505
--- /dev/null
+++ b/spec/ruby/core/thread/join_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#join" do
+ it "returns the thread when it is finished" do
+ t = Thread.new {}
+ t.join.should equal(t)
+ end
+
+ it "returns the thread when it is finished when given a timeout" do
+ t = Thread.new {}
+ t.join
+ t.join(0).should equal(t)
+ end
+
+ it "coerces timeout to a Float if it is not nil" do
+ t = Thread.new {}
+ t.join
+ t.join(0).should equal(t)
+ t.join(0.0).should equal(t)
+ t.join(nil).should equal(t)
+ end
+
+ it "raises TypeError if the argument is not a valid timeout" do
+ t = Thread.new { }
+ t.join
+ -> { t.join(:foo) }.should raise_error TypeError
+ -> { t.join("bar") }.should raise_error TypeError
+ end
+
+ it "returns nil if it is not finished when given a timeout" do
+ q = Queue.new
+ t = Thread.new { q.pop }
+ begin
+ t.join(0).should == nil
+ ensure
+ q << true
+ end
+ t.join.should == t
+ end
+
+ it "accepts a floating point timeout length" do
+ q = Queue.new
+ t = Thread.new { q.pop }
+ begin
+ t.join(0.01).should == nil
+ ensure
+ q << true
+ end
+ t.join.should == t
+ end
+
+ it "raises any exceptions encountered in the thread body" do
+ t = Thread.new {
+ Thread.current.report_on_exception = false
+ raise NotImplementedError.new("Just kidding")
+ }
+ -> { t.join }.should raise_error(NotImplementedError)
+ end
+
+ it "returns the dead thread" do
+ t = Thread.new { Thread.current.kill }
+ t.join.should equal(t)
+ end
+
+ it "raises any uncaught exception encountered in ensure block" do
+ t = ThreadSpecs.dying_thread_ensures { raise NotImplementedError.new("Just kidding") }
+ -> { t.join }.should raise_error(NotImplementedError)
+ end
+end
diff --git a/spec/ruby/core/thread/key_spec.rb b/spec/ruby/core/thread/key_spec.rb
new file mode 100644
index 0000000000..6940cf5f28
--- /dev/null
+++ b/spec/ruby/core/thread/key_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#key?" do
+ before :each do
+ @th = Thread.new do
+ Thread.current[:oliver] = "a"
+ end
+ @th.join
+ end
+
+ it "tests for existence of thread local variables using symbols or strings" do
+ @th.key?(:oliver).should == true
+ @th.key?("oliver").should == true
+ @th.key?(:stanley).should == false
+ @th.key?(:stanley.to_s).should == false
+ end
+
+ it "raises exceptions on the wrong type of keys" do
+ -> { Thread.current.key? nil }.should raise_error(TypeError)
+ -> { Thread.current.key? 5 }.should raise_error(TypeError)
+ end
+
+ it "is not shared across fibers" do
+ fib = Fiber.new do
+ Thread.current[:val1] = 1
+ Fiber.yield
+ Thread.current.key?(:val1).should be_true
+ Thread.current.key?(:val2).should be_false
+ end
+ Thread.current.key?(:val1).should_not be_true
+ fib.resume
+ Thread.current[:val2] = 2
+ fib.resume
+ Thread.current.key?(:val1).should be_false
+ Thread.current.key?(:val2).should be_true
+ end
+
+ it "stores a local in another thread when in a fiber" do
+ fib = Fiber.new do
+ t = Thread.new do
+ sleep
+ Thread.current.key?(:value).should be_true
+ end
+
+ Thread.pass while t.status and t.status != "sleep"
+ t[:value] = 1
+ t.wakeup
+ t.join
+ end
+ fib.resume
+ end
+end
diff --git a/spec/ruby/core/thread/keys_spec.rb b/spec/ruby/core/thread/keys_spec.rb
new file mode 100644
index 0000000000..15efda51d6
--- /dev/null
+++ b/spec/ruby/core/thread/keys_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#keys" do
+ it "returns an array of the names of the thread-local variables as symbols" do
+ th = Thread.new do
+ Thread.current["cat"] = 'woof'
+ Thread.current[:cat] = 'meow'
+ Thread.current[:dog] = 'woof'
+ end
+ th.join
+ th.keys.sort_by {|x| x.to_s}.should == [:cat,:dog]
+ end
+
+ it "is not shared across fibers" do
+ fib = Fiber.new do
+ Thread.current[:val1] = 1
+ Fiber.yield
+ Thread.current.keys.should include(:val1)
+ Thread.current.keys.should_not include(:val2)
+ end
+ Thread.current.keys.should_not include(:val1)
+ fib.resume
+ Thread.current[:val2] = 2
+ fib.resume
+ Thread.current.keys.should include(:val2)
+ Thread.current.keys.should_not include(:val1)
+ end
+
+ it "stores a local in another thread when in a fiber" do
+ fib = Fiber.new do
+ t = Thread.new do
+ sleep
+ Thread.current.keys.should include(:value)
+ end
+
+ Thread.pass while t.status and t.status != "sleep"
+ t[:value] = 1
+ t.wakeup
+ t.join
+ end
+ fib.resume
+ end
+end
diff --git a/spec/ruby/core/thread/kill_spec.rb b/spec/ruby/core/thread/kill_spec.rb
new file mode 100644
index 0000000000..f932bf5232
--- /dev/null
+++ b/spec/ruby/core/thread/kill_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/exit'
+
+# This spec randomly kills mspec worker like: https://ci.appveyor.com/project/ruby/ruby/builds/19473223/job/f69derxnlo09xhuj
+# TODO: Investigate the cause or at least print helpful logs, and remove this `platform_is_not` guard.
+platform_is_not :mingw do
+ describe "Thread#kill" do
+ it_behaves_like :thread_exit, :kill
+ end
+
+ describe "Thread#kill!" do
+ it "needs to be reviewed for spec completeness"
+ end
+
+ describe "Thread.kill" do
+ it "causes the given thread to exit" do
+ thread = Thread.new { sleep }
+ Thread.pass while thread.status and thread.status != "sleep"
+ Thread.kill(thread).should == thread
+ thread.join
+ thread.status.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/list_spec.rb b/spec/ruby/core/thread/list_spec.rb
new file mode 100644
index 0000000000..3c6f70c13e
--- /dev/null
+++ b/spec/ruby/core/thread/list_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread.list" do
+ it "includes the current and main thread" do
+ Thread.list.should include(Thread.current)
+ Thread.list.should include(Thread.main)
+ end
+
+ it "includes threads of non-default thread groups" do
+ t = Thread.new { sleep }
+ begin
+ ThreadGroup.new.add(t)
+ Thread.list.should include(t)
+ ensure
+ t.kill
+ t.join
+ end
+ end
+
+ it "does not include deceased threads" do
+ t = Thread.new { 1; }
+ t.join
+ Thread.list.should_not include(t)
+ end
+
+ it "includes waiting threads" do
+ q = Queue.new
+ t = Thread.new { q.pop }
+ begin
+ Thread.pass while t.status and t.status != 'sleep'
+ Thread.list.should include(t)
+ ensure
+ q << nil
+ t.join
+ end
+ end
+
+ it "returns instances of Thread and not null or nil values" do
+ spawner = Thread.new do
+ Array.new(100) do
+ Thread.new {}
+ end
+ end
+
+ begin
+ Thread.list.each { |th|
+ th.should be_kind_of(Thread)
+ }
+ end while spawner.alive?
+
+ threads = spawner.value
+ threads.each(&:join)
+ end
+end
diff --git a/spec/ruby/core/thread/main_spec.rb b/spec/ruby/core/thread/main_spec.rb
new file mode 100644
index 0000000000..ec91709576
--- /dev/null
+++ b/spec/ruby/core/thread/main_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread.main" do
+ it "returns the main thread" do
+ Thread.new { @main = Thread.main ; @current = Thread.current}.join
+ @main.should_not == @current
+ @main.should == Thread.current
+ end
+end
diff --git a/spec/ruby/core/thread/name_spec.rb b/spec/ruby/core/thread/name_spec.rb
new file mode 100644
index 0000000000..9b3d2f4b09
--- /dev/null
+++ b/spec/ruby/core/thread/name_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+
+describe "Thread#name" do
+ before :each do
+ @thread = Thread.new {}
+ end
+
+ after :each do
+ @thread.join
+ end
+
+ it "is nil initially" do
+ @thread.name.should == nil
+ end
+
+ it "returns the thread name" do
+ @thread.name = "thread_name"
+ @thread.name.should == "thread_name"
+ end
+end
+
+describe "Thread#name=" do
+ before :each do
+ @thread = Thread.new {}
+ end
+
+ after :each do
+ @thread.join
+ end
+
+ it "can be set to a String" do
+ @thread.name = "new thread name"
+ @thread.name.should == "new thread name"
+ end
+
+ it "raises an ArgumentError if the name includes a null byte" do
+ -> {
+ @thread.name = "new thread\0name"
+ }.should raise_error(ArgumentError)
+ end
+
+ it "can be reset to nil" do
+ @thread.name = nil
+ @thread.name.should == nil
+ end
+
+ it "calls #to_str to convert name to String" do
+ name = mock("Thread#name")
+ name.should_receive(:to_str).and_return("a thread name")
+
+ @thread.name = name
+ @thread.name.should == "a thread name"
+ end
+end
diff --git a/spec/ruby/core/thread/native_thread_id_spec.rb b/spec/ruby/core/thread/native_thread_id_spec.rb
new file mode 100644
index 0000000000..d6cc332bf6
--- /dev/null
+++ b/spec/ruby/core/thread/native_thread_id_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+if ruby_version_is "3.1" and Thread.method_defined?(:native_thread_id)
+ # This method is very platform specific
+
+ describe "Thread#native_thread_id" do
+ it "returns an integer when the thread is alive" do
+ Thread.current.native_thread_id.should be_kind_of(Integer)
+ end
+
+ it "returns nil when the thread is not running" do
+ t = Thread.new {}
+ t.join
+ t.native_thread_id.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/new_spec.rb b/spec/ruby/core/thread/new_spec.rb
new file mode 100644
index 0000000000..47a836201c
--- /dev/null
+++ b/spec/ruby/core/thread/new_spec.rb
@@ -0,0 +1,83 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread.new" do
+ it "creates a thread executing the given block" do
+ q = Queue.new
+ Thread.new { q << true }.join
+ q << false
+ q.pop.should == true
+ end
+
+ it "can pass arguments to the thread block" do
+ arr = []
+ a, b, c = 1, 2, 3
+ t = Thread.new(a,b,c) {|d,e,f| arr << d << e << f }
+ t.join
+ arr.should == [a,b,c]
+ end
+
+ it "raises an exception when not given a block" do
+ -> { Thread.new }.should raise_error(ThreadError)
+ end
+
+ it "creates a subclass of thread calls super with a block in initialize" do
+ arr = []
+ t = ThreadSpecs::SubThread.new(arr)
+ t.join
+ arr.should == [1]
+ end
+
+ it "calls #initialize and raises an error if super not used" do
+ c = Class.new(Thread) do
+ def initialize
+ end
+ end
+
+ -> {
+ c.new
+ }.should raise_error(ThreadError)
+ end
+
+ it "calls and respects #initialize for the block to use" do
+ c = Class.new(Thread) do
+ def initialize
+ ScratchPad.record [:good]
+ super { ScratchPad << :in_thread }
+ end
+ end
+
+ t = c.new
+ t.join
+
+ ScratchPad.recorded.should == [:good, :in_thread]
+ end
+
+ it "releases Mutexes held by the Thread when the Thread finishes" do
+ m1 = Mutex.new
+ m2 = Mutex.new
+ t = Thread.new {
+ m1.lock
+ m1.should.locked?
+ m2.lock
+ m2.should.locked?
+ }
+ t.join
+ m1.should_not.locked?
+ m2.should_not.locked?
+ end
+
+ it "releases Mutexes held by the Thread when the Thread finishes, also with Mutex#synchronize" do
+ m = Mutex.new
+ t = Thread.new {
+ m.synchronize {
+ m.unlock
+ m.lock
+ }
+ m.lock
+ m.should.locked?
+ }
+ t.join
+ m.should_not.locked?
+ end
+end
diff --git a/spec/ruby/core/thread/pass_spec.rb b/spec/ruby/core/thread/pass_spec.rb
new file mode 100644
index 0000000000..a5ac11a58c
--- /dev/null
+++ b/spec/ruby/core/thread/pass_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread.pass" do
+ it "returns nil" do
+ Thread.pass.should == nil
+ end
+end
diff --git a/spec/ruby/core/thread/pending_interrupt_spec.rb b/spec/ruby/core/thread/pending_interrupt_spec.rb
new file mode 100644
index 0000000000..cd565d92a4
--- /dev/null
+++ b/spec/ruby/core/thread/pending_interrupt_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "Thread.pending_interrupt?" do
+ it "returns false if there are no pending interrupts, e.g., outside any Thread.handle_interrupt block" do
+ Thread.pending_interrupt?.should == false
+ end
+
+ it "returns true if there are pending interrupts, e.g., Thread#raise inside Thread.handle_interrupt" do
+ executed = false
+ -> {
+ Thread.handle_interrupt(RuntimeError => :never) do
+ Thread.pending_interrupt?.should == false
+
+ current = Thread.current
+ Thread.new {
+ current.raise "interrupt"
+ }.join
+
+ Thread.pending_interrupt?.should == true
+ executed = true
+ end
+ }.should raise_error(RuntimeError, "interrupt")
+ executed.should == true
+ Thread.pending_interrupt?.should == false
+ end
+end
+
+describe "Thread#pending_interrupt?" do
+ it "returns whether the given threads has pending interrupts" do
+ Thread.current.pending_interrupt?.should == false
+ end
+end
diff --git a/spec/ruby/core/thread/priority_spec.rb b/spec/ruby/core/thread/priority_spec.rb
new file mode 100644
index 0000000000..e13ad478b5
--- /dev/null
+++ b/spec/ruby/core/thread/priority_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#priority" do
+ before :each do
+ @current_priority = Thread.current.priority
+ ThreadSpecs.clear_state
+ @thread = Thread.new { Thread.pass until ThreadSpecs.state == :exit }
+ Thread.pass until @thread.alive?
+ end
+
+ after :each do
+ ThreadSpecs.state = :exit
+ @thread.join
+ end
+
+ it "inherits the priority of the current thread while running" do
+ @thread.alive?.should be_true
+ @thread.priority.should == @current_priority
+ end
+
+ it "maintain the priority of the current thread after death" do
+ ThreadSpecs.state = :exit
+ @thread.join
+ @thread.alive?.should be_false
+ @thread.priority.should == @current_priority
+ end
+
+ it "returns an integer" do
+ @thread.priority.should be_kind_of(Integer)
+ end
+end
+
+describe "Thread#priority=" do
+ before :each do
+ ThreadSpecs.clear_state
+ @thread = Thread.new { Thread.pass until ThreadSpecs.state == :exit }
+ Thread.pass until @thread.alive?
+ end
+
+ after :each do
+ ThreadSpecs.state = :exit
+ @thread.join
+ end
+
+ describe "when set with an integer" do
+ it "returns an integer" do
+ value = (@thread.priority = 3)
+ value.should == 3
+ end
+
+ it "clamps the priority to -3..3" do
+ @thread.priority = 42
+ @thread.priority.should == 3
+ @thread.priority = -42
+ @thread.priority.should == -3
+ end
+ end
+
+ describe "when set with a non-integer" do
+ it "raises a type error" do
+ ->{ @thread.priority = Object.new }.should raise_error(TypeError)
+ end
+ end
+
+ it "sets priority even when the thread has died" do
+ thread = Thread.new {}
+ thread.join
+ thread.priority = 3
+ thread.priority.should == 3
+ end
+end
diff --git a/spec/ruby/core/thread/raise_spec.rb b/spec/ruby/core/thread/raise_spec.rb
new file mode 100644
index 0000000000..49323cf270
--- /dev/null
+++ b/spec/ruby/core/thread/raise_spec.rb
@@ -0,0 +1,232 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/kernel/raise'
+
+describe "Thread#raise" do
+ it "ignores dead threads and returns nil" do
+ t = Thread.new { :dead }
+ Thread.pass while t.alive?
+ t.raise("Kill the thread").should == nil
+ t.join
+ end
+end
+
+describe "Thread#raise on a sleeping thread" do
+ before :each do
+ ScratchPad.clear
+ @thr = ThreadSpecs.sleeping_thread
+ Thread.pass while @thr.status and @thr.status != "sleep"
+ end
+
+ after :each do
+ @thr.kill
+ @thr.join
+ end
+
+ it "raises a RuntimeError if no exception class is given" do
+ @thr.raise
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should be_kind_of(RuntimeError)
+ end
+
+ it "raises the given exception" do
+ @thr.raise Exception
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should be_kind_of(Exception)
+ end
+
+ it "raises the given exception with the given message" do
+ @thr.raise Exception, "get to work"
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should be_kind_of(Exception)
+ ScratchPad.recorded.message.should == "get to work"
+ end
+
+ it "raises the given exception and the backtrace is the one of the interrupted thread" do
+ @thr.raise Exception
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should be_kind_of(Exception)
+ ScratchPad.recorded.backtrace[0].should include("sleep")
+ end
+
+ it "is captured and raised by Thread#value" do
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ sleep
+ end
+
+ ThreadSpecs.spin_until_sleeping(t)
+
+ t.raise
+ -> { t.value }.should raise_error(RuntimeError)
+ end
+
+ it "raises a RuntimeError when called with no arguments inside rescue" do
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ sleep
+ end
+ end
+ begin
+ raise RangeError
+ rescue
+ ThreadSpecs.spin_until_sleeping(t)
+ t.raise
+ end
+ -> { t.value }.should raise_error(RuntimeError)
+ end
+
+ it "re-raises a previously rescued exception without overwriting the backtrace" do
+ t = Thread.new do
+ -> { # To make sure there is at least one entry in the call stack
+ begin
+ sleep
+ rescue => e
+ e
+ end
+ }.call
+ end
+
+ ThreadSpecs.spin_until_sleeping(t)
+
+ begin
+ initial_raise_line = __LINE__; raise 'raised'
+ rescue => raised
+ raise_again_line = __LINE__; t.raise raised
+ raised_again = t.value
+
+ raised_again.backtrace.first.should include("#{__FILE__}:#{initial_raise_line}:")
+ raised_again.backtrace.first.should_not include("#{__FILE__}:#{raise_again_line}:")
+ end
+ end
+
+ it "calls #exception in both the caller and in the target thread" do
+ cls = Class.new(Exception) do
+ attr_accessor :log
+ def initialize(*args)
+ @log = [] # This is shared because the super #exception uses a shallow clone
+ super
+ end
+
+ def exception(*args)
+ @log << [self, Thread.current, args]
+ super
+ end
+ end
+ exc = cls.new
+
+ @thr.raise exc, "Thread#raise #exception spec"
+ @thr.join
+ ScratchPad.recorded.should.is_a?(cls)
+ exc.log.should == [
+ [exc, Thread.current, ["Thread#raise #exception spec"]],
+ [ScratchPad.recorded, @thr, []]
+ ]
+ end
+end
+
+describe "Thread#raise on a running thread" do
+ before :each do
+ ScratchPad.clear
+ ThreadSpecs.clear_state
+
+ @thr = ThreadSpecs.running_thread
+ Thread.pass until ThreadSpecs.state == :running
+ end
+
+ after :each do
+ @thr.kill
+ @thr.join
+ end
+
+ it "raises a RuntimeError if no exception class is given" do
+ @thr.raise
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should be_kind_of(RuntimeError)
+ end
+
+ it "raises the given exception" do
+ @thr.raise Exception
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should be_kind_of(Exception)
+ end
+
+ it "raises the given exception with the given message" do
+ @thr.raise Exception, "get to work"
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should be_kind_of(Exception)
+ ScratchPad.recorded.message.should == "get to work"
+ end
+
+ it "can go unhandled" do
+ q = Queue.new
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ q << true
+ loop { Thread.pass }
+ end
+
+ q.pop # wait for `report_on_exception = false`.
+ t.raise
+ -> { t.value }.should raise_error(RuntimeError)
+ end
+
+ it "raises the given argument even when there is an active exception" do
+ raised = false
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ raised = true
+ loop { Thread.pass }
+ end
+ end
+ begin
+ raise "Create an active exception for the current thread too"
+ rescue
+ Thread.pass until raised
+ t.raise RangeError
+ -> { t.value }.should raise_error(RangeError)
+ end
+ end
+
+ it "raises a RuntimeError when called with no arguments inside rescue" do
+ raised = false
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ raised = true
+ loop { Thread.pass }
+ end
+ end
+ begin
+ raise RangeError
+ rescue
+ Thread.pass until raised
+ t.raise
+ end
+ -> { t.value }.should raise_error(RuntimeError)
+ end
+end
+
+describe "Thread#raise on same thread" do
+ it_behaves_like :kernel_raise, :raise, Thread.current
+
+ it "raises a RuntimeError when called with no arguments inside rescue" do
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ Thread.current.raise
+ end
+ end
+ -> { t.value }.should raise_error(RuntimeError, '')
+ end
+end
diff --git a/spec/ruby/core/thread/report_on_exception_spec.rb b/spec/ruby/core/thread/report_on_exception_spec.rb
new file mode 100644
index 0000000000..9279fa1da5
--- /dev/null
+++ b/spec/ruby/core/thread/report_on_exception_spec.rb
@@ -0,0 +1,157 @@
+require_relative '../../spec_helper'
+
+describe "Thread.report_on_exception" do
+ it "defaults to true" do
+ ruby_exe("p Thread.report_on_exception").should == "true\n"
+ end
+end
+
+describe "Thread.report_on_exception=" do
+ before :each do
+ @report_on_exception = Thread.report_on_exception
+ end
+
+ after :each do
+ Thread.report_on_exception = @report_on_exception
+ end
+
+ it "changes the default value for new threads" do
+ Thread.report_on_exception = true
+ Thread.report_on_exception.should == true
+ t = Thread.new {}
+ t.join
+ t.report_on_exception.should == true
+ end
+end
+
+describe "Thread#report_on_exception" do
+ it "returns true for the main Thread" do
+ Thread.current.report_on_exception.should == true
+ end
+
+ it "returns true for new Threads" do
+ Thread.new { Thread.current.report_on_exception }.value.should == true
+ end
+
+ it "returns whether the Thread will print a backtrace if it exits with an exception" do
+ t = Thread.new { Thread.current.report_on_exception = true }
+ t.join
+ t.report_on_exception.should == true
+
+ t = Thread.new { Thread.current.report_on_exception = false }
+ t.join
+ t.report_on_exception.should == false
+ end
+end
+
+describe "Thread#report_on_exception=" do
+ describe "when set to true" do
+ it "prints a backtrace on $stderr if it terminates with an exception" do
+ t = nil
+ -> {
+ t = Thread.new {
+ Thread.current.report_on_exception = true
+ raise RuntimeError, "Thread#report_on_exception specs"
+ }
+ Thread.pass while t.alive?
+ }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception specs/m)
+
+ -> {
+ t.join
+ }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
+ end
+
+ ruby_version_is "3.0" do
+ it "prints a backtrace on $stderr in the regular backtrace order" do
+ line_raise = __LINE__ + 2
+ def foo
+ raise RuntimeError, "Thread#report_on_exception specs backtrace order"
+ end
+
+ line_call_foo = __LINE__ + 5
+ go = false
+ t = Thread.new {
+ Thread.current.report_on_exception = true
+ Thread.pass until go
+ foo
+ }
+
+ -> {
+ go = true
+ Thread.pass while t.alive?
+ }.should output("", <<ERR)
+#{t.inspect} terminated with exception (report_on_exception is true):
+#{__FILE__}:#{line_raise}:in `foo': Thread#report_on_exception specs backtrace order (RuntimeError)
+\tfrom #{__FILE__}:#{line_call_foo}:in `block (5 levels) in <top (required)>'
+ERR
+
+ -> {
+ t.join
+ }.should raise_error(RuntimeError, "Thread#report_on_exception specs backtrace order")
+ end
+ end
+
+ it "prints the backtrace even if the thread was killed just after Thread#raise" do
+ t = nil
+ ready = false
+ -> {
+ t = Thread.new {
+ Thread.current.report_on_exception = true
+ ready = true
+ sleep
+ }
+
+ Thread.pass until ready and t.stop?
+ t.raise RuntimeError, "Thread#report_on_exception before kill spec"
+ t.kill
+ Thread.pass while t.alive?
+ }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception before kill spec/m)
+
+ -> {
+ t.join
+ }.should raise_error(RuntimeError, "Thread#report_on_exception before kill spec")
+ end
+ end
+
+ describe "when set to false" do
+ it "lets the thread terminates silently with an exception" do
+ t = nil
+ -> {
+ t = Thread.new {
+ Thread.current.report_on_exception = false
+ raise RuntimeError, "Thread#report_on_exception specs"
+ }
+ Thread.pass while t.alive?
+ }.should output("", "")
+
+ -> {
+ t.join
+ }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
+ end
+ end
+
+ describe "when used in conjunction with Thread#abort_on_exception" do
+ it "first reports then send the exception back to the main Thread" do
+ t = nil
+ mutex = Mutex.new
+ mutex.lock
+ -> {
+ t = Thread.new {
+ Thread.current.abort_on_exception = true
+ Thread.current.report_on_exception = true
+ mutex.lock
+ mutex.unlock
+ raise RuntimeError, "Thread#report_on_exception specs"
+ }
+
+ -> {
+ mutex.sleep(5)
+ }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
+ }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception specs/m)
+
+ -> {
+ t.join
+ }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/run_spec.rb b/spec/ruby/core/thread/run_spec.rb
new file mode 100644
index 0000000000..f86f793489
--- /dev/null
+++ b/spec/ruby/core/thread/run_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+require_relative 'shared/wakeup'
+
+describe "Thread#run" do
+ it_behaves_like :thread_wakeup, :run
+end
diff --git a/spec/ruby/core/thread/set_trace_func_spec.rb b/spec/ruby/core/thread/set_trace_func_spec.rb
new file mode 100644
index 0000000000..e5d8298ae0
--- /dev/null
+++ b/spec/ruby/core/thread/set_trace_func_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Thread#set_trace_func" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/thread/shared/exit.rb b/spec/ruby/core/thread/shared/exit.rb
new file mode 100644
index 0000000000..3663827579
--- /dev/null
+++ b/spec/ruby/core/thread/shared/exit.rb
@@ -0,0 +1,200 @@
+describe :thread_exit, shared: true do
+ before :each do
+ ScratchPad.clear
+ end
+
+ # This spec randomly kills mspec worker like: https://ci.appveyor.com/project/ruby/ruby/builds/19390874/job/wv1bsm8skd4e1pxl
+ # TODO: Investigate the cause or at least print helpful logs, and remove this `platform_is_not` guard.
+ platform_is_not :mingw do
+
+ it "kills sleeping thread" do
+ sleeping_thread = Thread.new do
+ sleep
+ ScratchPad.record :after_sleep
+ end
+ Thread.pass while sleeping_thread.status and sleeping_thread.status != "sleep"
+ sleeping_thread.send(@method)
+ sleeping_thread.join
+ ScratchPad.recorded.should == nil
+ end
+
+ it "kills current thread" do
+ thread = Thread.new do
+ Thread.current.send(@method)
+ ScratchPad.record :after_sleep
+ end
+ thread.join
+ ScratchPad.recorded.should == nil
+ end
+
+ it "runs ensure clause" do
+ thread = ThreadSpecs.dying_thread_ensures(@method) { ScratchPad.record :in_ensure_clause }
+ thread.join
+ ScratchPad.recorded.should == :in_ensure_clause
+ end
+
+ it "runs nested ensure clauses" do
+ ScratchPad.record []
+ @outer = Thread.new do
+ begin
+ @inner = Thread.new do
+ begin
+ sleep
+ ensure
+ ScratchPad << :inner_ensure_clause
+ end
+ end
+ sleep
+ ensure
+ ScratchPad << :outer_ensure_clause
+ @inner.send(@method)
+ @inner.join
+ end
+ end
+ Thread.pass while @outer.status and @outer.status != "sleep"
+ Thread.pass until @inner
+ Thread.pass while @inner.status and @inner.status != "sleep"
+ @outer.send(@method)
+ @outer.join
+ ScratchPad.recorded.should include(:inner_ensure_clause)
+ ScratchPad.recorded.should include(:outer_ensure_clause)
+ end
+
+ it "does not set $!" do
+ thread = ThreadSpecs.dying_thread_ensures(@method) { ScratchPad.record $! }
+ thread.join
+ ScratchPad.recorded.should == nil
+ end
+
+ it "does not reset $!" do
+ ScratchPad.record []
+
+ exc = RuntimeError.new("foo")
+ thread = Thread.new do
+ begin
+ raise exc
+ ensure
+ ScratchPad << $!
+ begin
+ Thread.current.send(@method)
+ ensure
+ ScratchPad << $!
+ end
+ end
+ end
+ thread.join
+ ScratchPad.recorded.should == [exc, exc]
+ end
+
+ it "cannot be rescued" do
+ thread = Thread.new do
+ begin
+ Thread.current.send(@method)
+ rescue Exception
+ ScratchPad.record :in_rescue
+ end
+ ScratchPad.record :end_of_thread_block
+ end
+
+ thread.join
+ ScratchPad.recorded.should == nil
+ end
+
+ it "kills the entire thread when a fiber is active" do
+ t = Thread.new do
+ Fiber.new do
+ sleep
+ end.resume
+ ScratchPad.record :fiber_resumed
+ end
+ Thread.pass while t.status and t.status != "sleep"
+ t.send(@method)
+ t.join
+ ScratchPad.recorded.should == nil
+ end
+
+ # This spec is a mess. It fails randomly, it hangs on MRI, it needs to be removed
+ quarantine! do
+ it "killing dying running does nothing" do
+ in_ensure_clause = false
+ exit_loop = true
+ t = ThreadSpecs.dying_thread_ensures do
+ in_ensure_clause = true
+ loop { if exit_loop then break end }
+ ScratchPad.record :after_stop
+ end
+
+ Thread.pass until in_ensure_clause == true
+ 10.times { t.send(@method); Thread.pass }
+ exit_loop = true
+ t.join
+ ScratchPad.recorded.should == :after_stop
+ end
+ end
+
+ quarantine! do
+
+ it "propagates inner exception to Thread.join if there is an outer ensure clause" do
+ thread = ThreadSpecs.dying_thread_with_outer_ensure(@method) { }
+ -> { thread.join }.should raise_error(RuntimeError, "In dying thread")
+ end
+
+ it "runs all outer ensure clauses even if inner ensure clause raises exception" do
+ ThreadSpecs.join_dying_thread_with_outer_ensure(@method) { ScratchPad.record :in_outer_ensure_clause }
+ ScratchPad.recorded.should == :in_outer_ensure_clause
+ end
+
+ it "sets $! in outer ensure clause if inner ensure clause raises exception" do
+ ThreadSpecs.join_dying_thread_with_outer_ensure(@method) { ScratchPad.record $! }
+ ScratchPad.recorded.to_s.should == "In dying thread"
+ end
+ end
+
+ it "can be rescued by outer rescue clause when inner ensure clause raises exception" do
+ thread = Thread.new do
+ begin
+ begin
+ Thread.current.send(@method)
+ ensure
+ raise "In dying thread"
+ end
+ rescue Exception
+ ScratchPad.record $!
+ end
+ :end_of_thread_block
+ end
+
+ thread.value.should == :end_of_thread_block
+ ScratchPad.recorded.to_s.should == "In dying thread"
+ end
+
+ it "is deferred if ensure clause does Thread.stop" do
+ ThreadSpecs.wakeup_dying_sleeping_thread(@method) { Thread.stop; ScratchPad.record :after_sleep }
+ ScratchPad.recorded.should == :after_sleep
+ end
+
+ # Hangs on 1.8.6.114 OS X, possibly also on Linux
+ quarantine! do
+ it "is deferred if ensure clause sleeps" do
+ ThreadSpecs.wakeup_dying_sleeping_thread(@method) { sleep; ScratchPad.record :after_sleep }
+ ScratchPad.recorded.should == :after_sleep
+ end
+ end
+
+ # This case occurred in JRuby where native threads are used to provide
+ # the same behavior as MRI green threads. Key to this issue was the fact
+ # that the thread which called #exit in its block was also being explicitly
+ # sent #join from outside the thread. The 100.times provides a certain
+ # probability that the deadlock will occur. It was sufficient to reliably
+ # reproduce the deadlock in JRuby.
+ it "does not deadlock when called from within the thread while being joined from without" do
+ 100.times do
+ t = Thread.new { Thread.stop; Thread.current.send(@method) }
+ Thread.pass while t.status and t.status != "sleep"
+ t.wakeup.should == t
+ t.join.should == t
+ end
+ end
+
+ end # platform_is_not :mingw
+end
diff --git a/spec/ruby/core/thread/shared/start.rb b/spec/ruby/core/thread/shared/start.rb
new file mode 100644
index 0000000000..2ba926bf00
--- /dev/null
+++ b/spec/ruby/core/thread/shared/start.rb
@@ -0,0 +1,41 @@
+describe :thread_start, shared: true do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "raises an ArgumentError if not passed a block" do
+ -> {
+ Thread.send(@method)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "spawns a new Thread running the block" do
+ run = false
+ t = Thread.send(@method) { run = true }
+ t.should be_kind_of(Thread)
+ t.join
+
+ run.should be_true
+ end
+
+ it "respects Thread subclasses" do
+ c = Class.new(Thread)
+ t = c.send(@method) { }
+ t.should be_kind_of(c)
+
+ t.join
+ end
+
+ it "does not call #initialize" do
+ c = Class.new(Thread) do
+ def initialize
+ ScratchPad.record :bad
+ end
+ end
+
+ t = c.send(@method) { }
+ t.join
+
+ ScratchPad.recorded.should == nil
+ end
+end
diff --git a/spec/ruby/core/thread/shared/to_s.rb b/spec/ruby/core/thread/shared/to_s.rb
new file mode 100644
index 0000000000..43640deb33
--- /dev/null
+++ b/spec/ruby/core/thread/shared/to_s.rb
@@ -0,0 +1,53 @@
+require_relative '../fixtures/classes'
+
+describe :thread_to_s, shared: true do
+ it "returns a description including file and line number" do
+ thread, line = Thread.new { "hello" }, __LINE__
+ thread.join
+ thread.send(@method).should =~ /^#<Thread:([^ ]*?) #{Regexp.escape __FILE__}:#{line} \w+>$/
+ end
+
+ it "has a binary encoding" do
+ ThreadSpecs.status_of_current_thread.send(@method).encoding.should == Encoding::BINARY
+ end
+
+ it "can check it's own status" do
+ ThreadSpecs.status_of_current_thread.send(@method).should include('run')
+ end
+
+ it "describes a running thread" do
+ ThreadSpecs.status_of_running_thread.send(@method).should include('run')
+ end
+
+ it "describes a sleeping thread" do
+ ThreadSpecs.status_of_sleeping_thread.send(@method).should include('sleep')
+ end
+
+ it "describes a blocked thread" do
+ ThreadSpecs.status_of_blocked_thread.send(@method).should include('sleep')
+ end
+
+ it "describes a completed thread" do
+ ThreadSpecs.status_of_completed_thread.send(@method).should include('dead')
+ end
+
+ it "describes a killed thread" do
+ ThreadSpecs.status_of_killed_thread.send(@method).should include('dead')
+ end
+
+ it "describes a thread with an uncaught exception" do
+ ThreadSpecs.status_of_thread_with_uncaught_exception.send(@method).should include('dead')
+ end
+
+ it "describes a dying sleeping thread" do
+ ThreadSpecs.status_of_dying_sleeping_thread.send(@method).should include('sleep')
+ end
+
+ it "reports aborting on a killed thread" do
+ ThreadSpecs.status_of_dying_running_thread.send(@method).should include('aborting')
+ end
+
+ it "reports aborting on a killed thread after sleep" do
+ ThreadSpecs.status_of_dying_thread_after_sleep.send(@method).should include('aborting')
+ end
+end
diff --git a/spec/ruby/core/thread/shared/wakeup.rb b/spec/ruby/core/thread/shared/wakeup.rb
new file mode 100644
index 0000000000..6f010fea25
--- /dev/null
+++ b/spec/ruby/core/thread/shared/wakeup.rb
@@ -0,0 +1,62 @@
+describe :thread_wakeup, shared: true do
+ it "can interrupt Kernel#sleep" do
+ exit_loop = false
+ after_sleep1 = false
+ after_sleep2 = false
+
+ t = Thread.new do
+ while true
+ break if exit_loop == true
+ Thread.pass
+ end
+
+ sleep
+ after_sleep1 = true
+
+ sleep
+ after_sleep2 = true
+ end
+
+ 10.times { t.send(@method); Thread.pass }
+ t.status.should_not == "sleep"
+
+ exit_loop = true
+
+ 10.times { sleep 0.1 if t.status and t.status != "sleep" }
+ after_sleep1.should == false # t should be blocked on the first sleep
+ t.send(@method)
+
+ 10.times { sleep 0.1 if after_sleep1 != true }
+ 10.times { sleep 0.1 if t.status and t.status != "sleep" }
+ after_sleep2.should == false # t should be blocked on the second sleep
+ t.send(@method)
+
+ t.join
+ end
+
+ it "does not result in a deadlock" do
+ t = Thread.new do
+ 10.times { Thread.stop }
+ end
+
+ while t.status
+ begin
+ t.send(@method)
+ rescue ThreadError
+ # The thread might die right after.
+ t.status.should == false
+ end
+ Thread.pass
+ sleep 0.001
+ end
+
+ t.status.should == false
+ t.join
+ end
+
+ it "raises a ThreadError when trying to wake up a dead thread" do
+ t = Thread.new { 1 }
+ t.join
+ -> { t.send @method }.should raise_error(ThreadError)
+ end
+end
diff --git a/spec/ruby/core/thread/start_spec.rb b/spec/ruby/core/thread/start_spec.rb
new file mode 100644
index 0000000000..3dd040f98b
--- /dev/null
+++ b/spec/ruby/core/thread/start_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/start'
+
+describe "Thread.start" do
+ describe "Thread.start" do
+ it_behaves_like :thread_start, :start
+ end
+end
diff --git a/spec/ruby/core/thread/status_spec.rb b/spec/ruby/core/thread/status_spec.rb
new file mode 100644
index 0000000000..4fde663c91
--- /dev/null
+++ b/spec/ruby/core/thread/status_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#status" do
+ it "can check it's own status" do
+ ThreadSpecs.status_of_current_thread.status.should == 'run'
+ end
+
+ it "describes a running thread" do
+ ThreadSpecs.status_of_running_thread.status.should == 'run'
+ end
+
+ it "describes a sleeping thread" do
+ ThreadSpecs.status_of_sleeping_thread.status.should == 'sleep'
+ end
+
+ it "describes a blocked thread" do
+ ThreadSpecs.status_of_blocked_thread.status.should == 'sleep'
+ end
+
+ it "describes a completed thread" do
+ ThreadSpecs.status_of_completed_thread.status.should == false
+ end
+
+ it "describes a killed thread" do
+ ThreadSpecs.status_of_killed_thread.status.should == false
+ end
+
+ it "describes a thread with an uncaught exception" do
+ ThreadSpecs.status_of_thread_with_uncaught_exception.status.should == nil
+ end
+
+ it "describes a dying sleeping thread" do
+ ThreadSpecs.status_of_dying_sleeping_thread.status.should == 'sleep'
+ end
+
+ it "reports aborting on a killed thread" do
+ ThreadSpecs.status_of_dying_running_thread.status.should == 'aborting'
+ end
+
+ it "reports aborting on a killed thread after sleep" do
+ ThreadSpecs.status_of_dying_thread_after_sleep.status.should == 'aborting'
+ end
+
+ it "reports aborting on an externally killed thread that sleeps" do
+ q = Queue.new
+ t = Thread.new do
+ begin
+ q.push nil
+ sleep
+ ensure
+ q.push Thread.current.status
+ end
+ end
+ q.pop
+ t.kill
+ t.join
+ q.pop.should == 'aborting'
+ end
+end
diff --git a/spec/ruby/core/thread/stop_spec.rb b/spec/ruby/core/thread/stop_spec.rb
new file mode 100644
index 0000000000..084ab46ef6
--- /dev/null
+++ b/spec/ruby/core/thread/stop_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread.stop" do
+ it "causes the current thread to sleep indefinitely" do
+ t = Thread.new { Thread.stop; 5 }
+ Thread.pass while t.status and t.status != 'sleep'
+ t.status.should == 'sleep'
+ t.run
+ t.value.should == 5
+ end
+end
+
+describe "Thread#stop?" do
+ it "can check it's own status" do
+ ThreadSpecs.status_of_current_thread.should_not.stop?
+ end
+
+ it "describes a running thread" do
+ ThreadSpecs.status_of_running_thread.should_not.stop?
+ end
+
+ it "describes a sleeping thread" do
+ ThreadSpecs.status_of_sleeping_thread.should.stop?
+ end
+
+ it "describes a blocked thread" do
+ ThreadSpecs.status_of_blocked_thread.should.stop?
+ end
+
+ it "describes a completed thread" do
+ ThreadSpecs.status_of_completed_thread.should.stop?
+ end
+
+ it "describes a killed thread" do
+ ThreadSpecs.status_of_killed_thread.should.stop?
+ end
+
+ it "describes a thread with an uncaught exception" do
+ ThreadSpecs.status_of_thread_with_uncaught_exception.should.stop?
+ end
+
+ it "describes a dying running thread" do
+ ThreadSpecs.status_of_dying_running_thread.should_not.stop?
+ end
+
+ it "describes a dying sleeping thread" do
+ ThreadSpecs.status_of_dying_sleeping_thread.should.stop?
+ end
+
+ it "describes a dying thread after sleep" do
+ ThreadSpecs.status_of_dying_thread_after_sleep.should_not.stop?
+ end
+end
diff --git a/spec/ruby/core/thread/terminate_spec.rb b/spec/ruby/core/thread/terminate_spec.rb
new file mode 100644
index 0000000000..cf6cab472b
--- /dev/null
+++ b/spec/ruby/core/thread/terminate_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/exit'
+
+describe "Thread#terminate" do
+ it_behaves_like :thread_exit, :terminate
+end
diff --git a/spec/ruby/core/thread/thread_variable_get_spec.rb b/spec/ruby/core/thread/thread_variable_get_spec.rb
new file mode 100644
index 0000000000..38f90d5830
--- /dev/null
+++ b/spec/ruby/core/thread/thread_variable_get_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+
+describe "Thread#thread_variable_get" do
+ before :each do
+ @t = Thread.new { }
+ end
+
+ after :each do
+ @t.join
+ end
+
+ it "returns nil if the variable is not set" do
+ @t.thread_variable_get(:a).should be_nil
+ end
+
+ it "returns the value previously set by #[]=" do
+ @t.thread_variable_set :a, 49
+ @t.thread_variable_get(:a).should == 49
+ end
+
+ it "returns a value private to self" do
+ @t.thread_variable_set :thread_variable_get_spec, 82
+ Thread.current.thread_variable_get(:thread_variable_get_spec).should be_nil
+ end
+end
diff --git a/spec/ruby/core/thread/thread_variable_set_spec.rb b/spec/ruby/core/thread/thread_variable_set_spec.rb
new file mode 100644
index 0000000000..1338c306c7
--- /dev/null
+++ b/spec/ruby/core/thread/thread_variable_set_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+describe "Thread#thread_variable_set" do
+ before :each do
+ @t = Thread.new { }
+ end
+
+ after :each do
+ @t.join
+ end
+
+ it "returns the value set" do
+ (@t.thread_variable_set :a, 2).should == 2
+ end
+
+ it "sets a value that will be returned by #thread_variable_get" do
+ @t.thread_variable_set :a, 49
+ @t.thread_variable_get(:a).should == 49
+ end
+
+ it "sets a value private to self" do
+ @t.thread_variable_set :thread_variable_get_spec, 82
+ @t.thread_variable_get(:thread_variable_get_spec).should == 82
+ Thread.current.thread_variable_get(:thread_variable_get_spec).should be_nil
+ end
+end
diff --git a/spec/ruby/core/thread/thread_variable_spec.rb b/spec/ruby/core/thread/thread_variable_spec.rb
new file mode 100644
index 0000000000..6bd1950c04
--- /dev/null
+++ b/spec/ruby/core/thread/thread_variable_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Thread#thread_variable?" do
+ before :each do
+ @t = Thread.new { }
+ end
+
+ after :each do
+ @t.join
+ end
+
+ it "returns false if the thread variables do not contain 'key'" do
+ @t.thread_variable_set :a, 2
+ @t.thread_variable?(:b).should be_false
+ end
+
+ it "returns true if the thread variables contain 'key'" do
+ @t.thread_variable_set :a, 2
+ @t.thread_variable?(:a).should be_true
+ end
+end
diff --git a/spec/ruby/core/thread/thread_variables_spec.rb b/spec/ruby/core/thread/thread_variables_spec.rb
new file mode 100644
index 0000000000..1bd68b17f1
--- /dev/null
+++ b/spec/ruby/core/thread/thread_variables_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Thread#thread_variables" do
+ before :each do
+ @t = Thread.new { }
+ end
+
+ after :each do
+ @t.join
+ end
+
+ it "returns the keys of all the values set" do
+ @t.thread_variable_set :a, 2
+ @t.thread_variable_set :b, 4
+ @t.thread_variable_set :c, 6
+ @t.thread_variables.sort.should == [:a, :b, :c]
+ end
+
+ it "sets a value private to self" do
+ @t.thread_variable_set :a, 82
+ @t.thread_variable_set :b, 82
+ Thread.current.thread_variables.should_not include(:a, :b)
+ end
+
+ it "only contains user thread variables and is empty initially" do
+ Thread.current.thread_variables.should == []
+ @t.thread_variables.should == []
+ end
+end
diff --git a/spec/ruby/core/thread/to_s_spec.rb b/spec/ruby/core/thread/to_s_spec.rb
new file mode 100644
index 0000000000..cb182a017f
--- /dev/null
+++ b/spec/ruby/core/thread/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Thread#to_s" do
+ it_behaves_like :thread_to_s, :to_s
+end
diff --git a/spec/ruby/core/thread/value_spec.rb b/spec/ruby/core/thread/value_spec.rb
new file mode 100644
index 0000000000..30e43abd1a
--- /dev/null
+++ b/spec/ruby/core/thread/value_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Thread#value" do
+ it "returns the result of the block" do
+ Thread.new { 3 }.value.should == 3
+ end
+
+ it "re-raises an error for an uncaught exception" do
+ t = Thread.new {
+ Thread.current.report_on_exception = false
+ raise "Hello"
+ }
+ -> { t.value }.should raise_error(RuntimeError, "Hello")
+ end
+
+ it "is nil for a killed thread" do
+ t = Thread.new { Thread.current.exit }
+ t.value.should == nil
+ end
+
+ it "returns when the thread finished" do
+ q = Queue.new
+ t = Thread.new {
+ q.pop
+ }
+ -> { t.value }.should block_caller
+ q.push :result
+ t.value.should == :result
+ end
+end
diff --git a/spec/ruby/core/thread/wakeup_spec.rb b/spec/ruby/core/thread/wakeup_spec.rb
new file mode 100644
index 0000000000..da5dfea377
--- /dev/null
+++ b/spec/ruby/core/thread/wakeup_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/wakeup'
+
+describe "Thread#wakeup" do
+ it_behaves_like :thread_wakeup, :wakeup
+end
diff --git a/spec/ruby/core/threadgroup/add_spec.rb b/spec/ruby/core/threadgroup/add_spec.rb
new file mode 100644
index 0000000000..2921c1ab22
--- /dev/null
+++ b/spec/ruby/core/threadgroup/add_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "ThreadGroup#add" do
+ before :each do
+ @q1, @q2 = Queue.new, Queue.new
+ @thread = Thread.new { @q1 << :go; @q2.pop }
+ @q1.pop
+ end
+
+ after :each do
+ @q2 << :done
+ @thread.join
+ end
+
+ # This spec randomly kills mspec worker like: https://ci.appveyor.com/project/ruby/ruby/build/9806/job/37tx2atojy96227m
+ # TODO: Investigate the cause or at least print helpful logs, and remove this `platform_is_not` guard.
+ platform_is_not :mingw do
+ it "adds the given thread to a group and returns self" do
+ @thread.group.should_not == nil
+
+ tg = ThreadGroup.new
+ tg.add(@thread).should == tg
+ @thread.group.should == tg
+ tg.list.include?(@thread).should == true
+ end
+
+ it "removes itself from any other threadgroup" do
+ tg1 = ThreadGroup.new
+ tg2 = ThreadGroup.new
+
+ tg1.add(@thread)
+ @thread.group.should == tg1
+ tg2.add(@thread)
+ @thread.group.should == tg2
+ tg2.list.include?(@thread).should == true
+ tg1.list.include?(@thread).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/threadgroup/default_spec.rb b/spec/ruby/core/threadgroup/default_spec.rb
new file mode 100644
index 0000000000..d7d4726cc2
--- /dev/null
+++ b/spec/ruby/core/threadgroup/default_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "ThreadGroup::Default" do
+ it "is a ThreadGroup instance" do
+ ThreadGroup::Default.should be_kind_of(ThreadGroup)
+ end
+
+ it "is the ThreadGroup of the main thread" do
+ ThreadGroup::Default.should == Thread.main.group
+ end
+end
diff --git a/spec/ruby/core/threadgroup/enclose_spec.rb b/spec/ruby/core/threadgroup/enclose_spec.rb
new file mode 100644
index 0000000000..dd9a7a362d
--- /dev/null
+++ b/spec/ruby/core/threadgroup/enclose_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "ThreadGroup#enclose" do
+ before :each do
+ @q1, @q2 = Queue.new, Queue.new
+ @thread = Thread.new { @q1 << :go; @q2.pop }
+ @q1.pop
+ end
+
+ after :each do
+ @q2 << :done
+ @thread.join
+ end
+
+ it "raises a ThreadError if attempting to move a Thread from an enclosed ThreadGroup" do
+ thread_group = ThreadGroup.new
+ default_group = @thread.group
+ thread_group.add(@thread)
+ thread_group.enclose
+ -> do
+ default_group.add(@thread)
+ end.should raise_error(ThreadError)
+ end
+end
diff --git a/spec/ruby/core/threadgroup/enclosed_spec.rb b/spec/ruby/core/threadgroup/enclosed_spec.rb
new file mode 100644
index 0000000000..a734256a64
--- /dev/null
+++ b/spec/ruby/core/threadgroup/enclosed_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "ThreadGroup#enclosed?" do
+ it "returns false when a ThreadGroup has not been enclosed (default state)" do
+ thread_group = ThreadGroup.new
+ thread_group.enclosed?.should be_false
+ end
+
+ it "returns true when a ThreadGroup is enclosed" do
+ thread_group = ThreadGroup.new
+ thread_group.enclose
+ thread_group.enclosed?.should be_true
+ end
+end
diff --git a/spec/ruby/core/threadgroup/list_spec.rb b/spec/ruby/core/threadgroup/list_spec.rb
new file mode 100644
index 0000000000..b2ac64324a
--- /dev/null
+++ b/spec/ruby/core/threadgroup/list_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "ThreadGroup#list" do
+ it "returns the list of threads in the group" do
+ q = Queue.new
+ th1 = Thread.new { q << :go; sleep }
+ q.pop.should == :go
+ tg = ThreadGroup.new
+ tg.add(th1)
+ tg.list.should include(th1)
+
+ th2 = Thread.new { q << :go; sleep }
+ q.pop.should == :go
+
+ tg.add(th2)
+ (tg.list & [th1, th2]).should include(th1, th2)
+
+ Thread.pass while th1.status and th1.status != 'sleep'
+ Thread.pass while th2.status and th2.status != 'sleep'
+ th1.run; th1.join
+ th2.run; th2.join
+ end
+end
diff --git a/spec/ruby/core/time/_dump_spec.rb b/spec/ruby/core/time/_dump_spec.rb
new file mode 100644
index 0000000000..4dc1c43cd2
--- /dev/null
+++ b/spec/ruby/core/time/_dump_spec.rb
@@ -0,0 +1,55 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+
+describe "Time#_dump" do
+ before :each do
+ @local = Time.at(946812800)
+ @t = Time.at(946812800)
+ @t = @t.gmtime
+ @s = @t.send(:_dump)
+ end
+
+ it "is a private method" do
+ Time.should have_private_instance_method(:_dump, false)
+ end
+
+ # http://redmine.ruby-lang.org/issues/show/627
+ it "preserves the GMT flag" do
+ @t.should.gmt?
+ dump = @t.send(:_dump).unpack("VV").first
+ ((dump >> 30) & 0x1).should == 1
+
+ @local.should_not.gmt?
+ dump = @local.send(:_dump).unpack("VV").first
+ ((dump >> 30) & 0x1).should == 0
+ end
+
+ it "dumps a Time object to a bytestring" do
+ @s.should be_an_instance_of(String)
+ @s.should == [3222863947, 2235564032].pack("VV")
+ end
+
+ it "dumps an array with a date as first element" do
+ high = 1 << 31 |
+ (@t.gmt? ? 1 : 0) << 30 |
+ (@t.year - 1900) << 14 |
+ (@t.mon - 1) << 10 |
+ @t.mday << 5 |
+ @t.hour
+
+ high.should == @s.unpack("VV").first
+ end
+
+ it "dumps an array with a time as second element" do
+ low = @t.min << 26 |
+ @t.sec << 20 |
+ @t.usec
+ low.should == @s.unpack("VV").last
+ end
+
+ it "dumps like MRI's marshaled time format" do
+ t = Time.utc(2000, 1, 15, 20, 1, 1, 203).localtime
+
+ t.send(:_dump).should == "\364\001\031\200\313\000\020\004"
+ end
+end
diff --git a/spec/ruby/core/time/_load_spec.rb b/spec/ruby/core/time/_load_spec.rb
new file mode 100644
index 0000000000..152934370f
--- /dev/null
+++ b/spec/ruby/core/time/_load_spec.rb
@@ -0,0 +1,52 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+
+describe "Time._load" do
+ it "is a private method" do
+ Time.should have_private_method(:_load, false)
+ end
+
+ # http://redmine.ruby-lang.org/issues/show/627
+ it "loads a time object in the new format" do
+ t = Time.local(2000, 1, 15, 20, 1, 1)
+ t = t.gmtime
+
+ high = 1 << 31 |
+ (t.gmt? ? 1 : 0) << 30 |
+ (t.year - 1900) << 14 |
+ (t.mon - 1) << 10 |
+ t.mday << 5 |
+ t.hour
+
+ low = t.min << 26 |
+ t.sec << 20 |
+ t.usec
+
+ Time.send(:_load, [high, low].pack("VV")).should == t
+ end
+
+ it "loads a time object in the old UNIX timestamp based format" do
+ t = Time.local(2000, 1, 15, 20, 1, 1, 203)
+ timestamp = t.to_i
+
+ high = timestamp & ((1 << 31) - 1)
+
+ low = t.usec
+
+ Time.send(:_load, [high, low].pack("VV")).should == t
+ end
+
+ it "loads MRI's marshaled time format" do
+ t = Marshal.load("\004\bu:\tTime\r\320\246\e\200\320\001\r\347")
+ t.utc
+
+ t.to_s.should == "2010-10-22 16:57:48 UTC"
+ end
+
+ it "treats the data as binary data" do
+ data = "\x04\bu:\tTime\r\fM\x1C\xC0\x00\x00\xD0\xBE"
+ data.force_encoding Encoding::UTF_8
+ t = Marshal.load(data)
+ t.to_s.should == "2013-04-08 12:47:45 UTC"
+ end
+end
diff --git a/spec/ruby/core/time/asctime_spec.rb b/spec/ruby/core/time/asctime_spec.rb
new file mode 100644
index 0000000000..a41ee531e4
--- /dev/null
+++ b/spec/ruby/core/time/asctime_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/asctime'
+
+describe "Time#asctime" do
+ it_behaves_like :time_asctime, :asctime
+end
diff --git a/spec/ruby/core/time/at_spec.rb b/spec/ruby/core/time/at_spec.rb
new file mode 100644
index 0000000000..0459589f01
--- /dev/null
+++ b/spec/ruby/core/time/at_spec.rb
@@ -0,0 +1,291 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Time.at" do
+ describe "passed Numeric" do
+ it "returns a Time object representing the given number of Integer seconds since 1970-01-01 00:00:00 UTC" do
+ Time.at(1184027924).getgm.asctime.should == "Tue Jul 10 00:38:44 2007"
+ end
+
+ it "returns a Time object representing the given number of Float seconds since 1970-01-01 00:00:00 UTC" do
+ t = Time.at(10.5)
+ t.usec.should == 500000.0
+ t.should_not == Time.at(10)
+ end
+
+ it "returns a non-UTC Time" do
+ Time.at(1184027924).should_not.utc?
+ end
+
+ it "returns a subclass instance on a Time subclass" do
+ c = Class.new(Time)
+ t = c.at(0)
+ t.should be_an_instance_of(c)
+ end
+
+ it "roundtrips a Rational produced by #to_r" do
+ t = Time.now()
+ t2 = Time.at(t.to_r)
+
+ t2.should == t
+ t2.usec.should == t.usec
+ t2.nsec.should == t.nsec
+ end
+
+ describe "passed BigDecimal" do
+ it "doesn't round input value" do
+ require 'bigdecimal'
+ Time.at(BigDecimal('1.1')).to_f.should == 1.1
+ end
+ end
+
+ describe "passed Rational" do
+ it "returns Time with correct microseconds" do
+ t = Time.at(Rational(1_486_570_508_539_759, 1_000_000))
+ t.usec.should == 539_759
+ t.nsec.should == 539_759_000
+ end
+
+ it "returns Time with correct nanoseconds" do
+ t = Time.at(Rational(1_486_570_508_539_759_123, 1_000_000_000))
+ t.usec.should == 539_759
+ t.nsec.should == 539_759_123
+ end
+ end
+ end
+
+ describe "passed Time" do
+ it "creates a new time object with the value given by time" do
+ t = Time.now
+ Time.at(t).inspect.should == t.inspect
+ end
+
+ it "creates a dup time object with the value given by time" do
+ t1 = Time.new
+ t2 = Time.at(t1)
+ t1.should_not equal t2
+ end
+
+ it "returns a UTC time if the argument is UTC" do
+ t = Time.now.getgm
+ Time.at(t).should.utc?
+ end
+
+ it "returns a non-UTC time if the argument is non-UTC" do
+ t = Time.now
+ Time.at(t).should_not.utc?
+ end
+
+ it "returns a subclass instance" do
+ c = Class.new(Time)
+ t = c.at(Time.now)
+ t.should be_an_instance_of(c)
+ end
+ end
+
+ describe "passed non-Time, non-Numeric" do
+ it "raises a TypeError with a String argument" do
+ -> { Time.at("0") }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError with a nil argument" do
+ -> { Time.at(nil) }.should raise_error(TypeError)
+ end
+
+ describe "with an argument that responds to #to_int" do
+ it "coerces using #to_int" do
+ o = mock('integer')
+ o.should_receive(:to_int).and_return(0)
+ Time.at(o).should == Time.at(0)
+ end
+ end
+
+ describe "with an argument that responds to #to_r" do
+ it "coerces using #to_r" do
+ o = mock_numeric('rational')
+ o.should_receive(:to_r).and_return(Rational(5, 2))
+ Time.at(o).should == Time.at(Rational(5, 2))
+ end
+
+ it "needs for the argument to respond to #to_int too" do
+ o = mock('rational-but-no-to_int')
+ o.should_receive(:to_r).and_return(Rational(5, 2))
+ -> { Time.at(o) }.should raise_error(TypeError)
+ end
+ end
+ end
+
+ describe "passed [Integer, Numeric]" do
+ it "returns a Time object representing the given number of seconds and Integer microseconds since 1970-01-01 00:00:00 UTC" do
+ t = Time.at(10, 500000)
+ t.tv_sec.should == 10
+ t.tv_usec.should == 500000
+ end
+
+ it "returns a Time object representing the given number of seconds and Float microseconds since 1970-01-01 00:00:00 UTC" do
+ t = Time.at(10, 500.500)
+ t.tv_sec.should == 10
+ t.tv_nsec.should == 500500
+ end
+ end
+
+ describe "with a second argument that responds to #to_int" do
+ it "coerces using #to_int" do
+ o = mock('integer')
+ o.should_receive(:to_int).and_return(10)
+ Time.at(0, o).should == Time.at(0, 10)
+ end
+ end
+
+ describe "with a second argument that responds to #to_r" do
+ it "coerces using #to_r" do
+ o = mock_numeric('rational')
+ o.should_receive(:to_r).and_return(Rational(5, 2))
+ Time.at(0, o).should == Time.at(0, Rational(5, 2))
+ end
+ end
+
+ describe "passed [Integer, nil]" do
+ it "raises a TypeError" do
+ -> { Time.at(0, nil) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "passed [Integer, String]" do
+ it "raises a TypeError" do
+ -> { Time.at(0, "0") }.should raise_error(TypeError)
+ end
+ end
+
+ describe "passed [Time, Integer]" do
+ # #8173
+ it "raises a TypeError" do
+ -> { Time.at(Time.now, 500000) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "passed [Time, Numeric, format]" do
+ context ":nanosecond format" do
+ it "treats second argument as nanoseconds" do
+ Time.at(0, 123456789, :nanosecond).nsec.should == 123456789
+ end
+ end
+
+ context ":nsec format" do
+ it "treats second argument as nanoseconds" do
+ Time.at(0, 123456789, :nsec).nsec.should == 123456789
+ end
+ end
+
+ context ":microsecond format" do
+ it "treats second argument as microseconds" do
+ Time.at(0, 123456, :microsecond).nsec.should == 123456000
+ end
+ end
+
+ context ":usec format" do
+ it "treats second argument as microseconds" do
+ Time.at(0, 123456, :usec).nsec.should == 123456000
+ end
+ end
+
+ context ":millisecond format" do
+ it "treats second argument as milliseconds" do
+ Time.at(0, 123, :millisecond).nsec.should == 123000000
+ end
+ end
+
+ context "not supported format" do
+ it "raises ArgumentError" do
+ -> { Time.at(0, 123456, 2) }.should raise_error(ArgumentError)
+ -> { Time.at(0, 123456, nil) }.should raise_error(ArgumentError)
+ -> { Time.at(0, 123456, :invalid) }.should raise_error(ArgumentError)
+ end
+
+ it "does not try to convert format to Symbol with #to_sym" do
+ format = "usec"
+ format.should_not_receive(:to_sym)
+ -> { Time.at(0, 123456, format) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "supports Float second argument" do
+ Time.at(0, 123456789.500, :nanosecond).nsec.should == 123456789
+ Time.at(0, 123456789.500, :nsec).nsec.should == 123456789
+ Time.at(0, 123456.500, :microsecond).nsec.should == 123456500
+ Time.at(0, 123456.500, :usec).nsec.should == 123456500
+ Time.at(0, 123.500, :millisecond).nsec.should == 123500000
+ end
+ end
+
+ describe ":in keyword argument" do
+ before do
+ @epoch_time = Time.now.to_i
+ end
+
+ it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do
+ time = Time.at(@epoch_time, in: "+05:00")
+
+ time.utc_offset.should == 5*60*60
+ time.zone.should == nil
+ time.to_i.should == @epoch_time
+
+ time = Time.at(@epoch_time, in: "-09:00")
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == nil
+ time.to_i.should == @epoch_time
+ end
+
+ it "could be UTC offset as a number of seconds" do
+ time = Time.at(@epoch_time, in: 5*60*60)
+
+ time.utc_offset.should == 5*60*60
+ time.zone.should == nil
+ time.to_i.should == @epoch_time
+
+ time = Time.at(@epoch_time, in: -9*60*60)
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == nil
+ time.to_i.should == @epoch_time
+ end
+
+ it "could be UTC offset as a 'UTC' String" do
+ time = Time.at(@epoch_time, in: "UTC")
+
+ time.utc_offset.should == 0
+ time.zone.should == "UTC"
+ time.to_i.should == @epoch_time
+ end
+
+ it "could be UTC offset as a military zone A-Z" do
+ time = Time.at(@epoch_time, in: "B")
+
+ time.utc_offset.should == 3600 * 2
+ time.zone.should == nil
+ time.to_i.should == @epoch_time
+ end
+
+ it "could be a timezone object" do
+ zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo")
+ time = Time.at(@epoch_time, in: zone)
+
+ time.utc_offset.should == 5*3600+30*60
+ time.zone.should == zone
+ time.to_i.should == @epoch_time
+
+ zone = TimeSpecs::TimezoneWithName.new(name: "PST")
+ time = Time.at(@epoch_time, in: zone)
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == zone
+ time.to_i.should == @epoch_time
+ end
+
+ it "raises ArgumentError if format is invalid" do
+ -> { Time.at(@epoch_time, in: "+09:99") }.should raise_error(ArgumentError)
+ -> { Time.at(@epoch_time, in: "ABC") }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/time/ceil_spec.rb b/spec/ruby/core/time/ceil_spec.rb
new file mode 100644
index 0000000000..9d624a1ed0
--- /dev/null
+++ b/spec/ruby/core/time/ceil_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+
+describe "Time#ceil" do
+ before do
+ @time = Time.utc(2010, 3, 30, 5, 43, "25.0123456789".to_r)
+ end
+
+ it "defaults to ceiling to 0 places" do
+ @time.ceil.should == Time.utc(2010, 3, 30, 5, 43, 26.to_r)
+ end
+
+ it "ceils to 0 decimal places with an explicit argument" do
+ @time.ceil(0).should == Time.utc(2010, 3, 30, 5, 43, 26.to_r)
+ end
+
+ it "ceils to 2 decimal places with an explicit argument" do
+ @time.ceil(2).should == Time.utc(2010, 3, 30, 5, 43, "25.02".to_r)
+ end
+
+ it "ceils to 4 decimal places with an explicit argument" do
+ @time.ceil(4).should == Time.utc(2010, 3, 30, 5, 43, "25.0124".to_r)
+ end
+
+ it "ceils to 7 decimal places with an explicit argument" do
+ @time.ceil(7).should == Time.utc(2010, 3, 30, 5, 43, "25.0123457".to_r)
+ end
+
+ it "returns an instance of Time, even if #ceil is called on a subclass" do
+ subclass = Class.new(Time)
+ instance = subclass.at(0)
+ instance.class.should equal subclass
+ instance.ceil.should be_an_instance_of(Time)
+ end
+
+ it "copies own timezone to the returning value" do
+ @time.zone.should == @time.ceil.zone
+
+ time = with_timezone "JST-9" do
+ Time.at 0, 1
+ end
+
+ time.zone.should == time.ceil.zone
+ end
+end
diff --git a/spec/ruby/core/time/comparison_spec.rb b/spec/ruby/core/time/comparison_spec.rb
new file mode 100644
index 0000000000..5b53c9fb50
--- /dev/null
+++ b/spec/ruby/core/time/comparison_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+
+describe "Time#<=>" do
+ it "returns 1 if the first argument is a point in time after the second argument" do
+ (Time.now <=> Time.at(0)).should == 1
+ end
+
+ it "returns 1 if the first argument is a point in time after the second argument (down to a millisecond)" do
+ (Time.at(0, 1000) <=> Time.at(0, 0)).should == 1
+ (Time.at(1202778512, 1000) <=> Time.at(1202778512, 999)).should == 1
+ end
+
+ it "returns 1 if the first argument is a point in time after the second argument (down to a microsecond)" do
+ (Time.at(0, 100) <=> Time.at(0, 0)).should == 1
+ (Time.at(1202778512, 100) <=> Time.at(1202778512, 99)).should == 1
+ end
+
+ it "returns 0 if time is the same as other" do
+ (Time.at(1202778513) <=> Time.at(1202778513)).should == 0
+ (Time.at(100, 100) <=> Time.at(100, 100)).should == 0
+ end
+
+ it "returns -1 if the first argument is a point in time before the second argument" do
+ (Time.at(0) <=> Time.now).should == -1
+ (Time.at(100, 100) <=> Time.at(101, 100)).should == -1
+ end
+
+ it "returns -1 if the first argument is a point in time before the second argument (down to a millisecond)" do
+ (Time.at(0, 0) <=> Time.at(0, 1000)).should == -1
+ end
+
+ it "returns -1 if the first argument is a point in time before the second argument (down to a microsecond)" do
+ (Time.at(0, 0) <=> Time.at(0, 100)).should == -1
+ end
+
+ it "returns 1 if the first argument is a fraction of a microsecond after the second argument" do
+ (Time.at(100, Rational(1,1000)) <=> Time.at(100, 0)).should == 1
+ end
+
+ it "returns 0 if time is the same as other, including fractional microseconds" do
+ (Time.at(100, Rational(1,1000)) <=> Time.at(100, Rational(1,1000))).should == 0
+ end
+
+ it "returns -1 if the first argument is a fraction of a microsecond before the second argument" do
+ (Time.at(100, 0) <=> Time.at(100, Rational(1,1000))).should == -1
+ end
+
+ it "returns nil when compared to an Integer because Time does not respond to #coerce" do
+ time = Time.at(1)
+ time.respond_to?(:coerce).should == false
+ time.should_receive(:respond_to?).exactly(2).and_return(false)
+ -> {
+ (time <=> 2).should == nil
+ (2 <=> time).should == nil
+ }.should_not complain
+ end
+
+ describe "given a non-Time argument" do
+ it "returns nil if argument <=> self returns nil" do
+ t = Time.now
+ obj = mock('time')
+ obj.should_receive(:<=>).with(t).and_return(nil)
+ (t <=> obj).should == nil
+ end
+
+ it "returns -1 if argument <=> self is greater than 0" do
+ t = Time.now
+ r = mock('r')
+ r.should_receive(:>).with(0).and_return(true)
+ obj = mock('time')
+ obj.should_receive(:<=>).with(t).and_return(r)
+ (t <=> obj).should == -1
+ end
+
+ it "returns 1 if argument <=> self is not greater than 0 and is less than 0" do
+ t = Time.now
+ r = mock('r')
+ r.should_receive(:>).with(0).and_return(false)
+ r.should_receive(:<).with(0).and_return(true)
+ obj = mock('time')
+ obj.should_receive(:<=>).with(t).and_return(r)
+ (t <=> obj).should == 1
+ end
+
+ it "returns 0 if argument <=> self is neither greater than 0 nor less than 0" do
+ t = Time.now
+ r = mock('r')
+ r.should_receive(:>).with(0).and_return(false)
+ r.should_receive(:<).with(0).and_return(false)
+ obj = mock('time')
+ obj.should_receive(:<=>).with(t).and_return(r)
+ (t <=> obj).should == 0
+ end
+
+ it "returns nil if argument also uses an inverse comparison for <=>" do
+ t = Time.now
+ r = mock('r')
+ def r.<=>(other); other <=> self; end
+ r.should_receive(:<=>).once
+
+ (t <=> r).should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/core/time/ctime_spec.rb b/spec/ruby/core/time/ctime_spec.rb
new file mode 100644
index 0000000000..57e7cfd9ce
--- /dev/null
+++ b/spec/ruby/core/time/ctime_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/asctime'
+
+describe "Time#ctime" do
+ it_behaves_like :time_asctime, :ctime
+end
diff --git a/spec/ruby/core/time/day_spec.rb b/spec/ruby/core/time/day_spec.rb
new file mode 100644
index 0000000000..895bcd7a86
--- /dev/null
+++ b/spec/ruby/core/time/day_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/day'
+
+describe "Time#day" do
+ it_behaves_like :time_day, :day
+end
diff --git a/spec/ruby/core/time/dst_spec.rb b/spec/ruby/core/time/dst_spec.rb
new file mode 100644
index 0000000000..436240aae5
--- /dev/null
+++ b/spec/ruby/core/time/dst_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/isdst'
+
+describe "Time#dst?" do
+ it_behaves_like :time_isdst, :dst?
+end
diff --git a/spec/ruby/core/time/dup_spec.rb b/spec/ruby/core/time/dup_spec.rb
new file mode 100644
index 0000000000..5d6621beaa
--- /dev/null
+++ b/spec/ruby/core/time/dup_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+
+describe "Time#dup" do
+ it "returns a Time object that represents the same time" do
+ t = Time.at(100)
+ t.dup.tv_sec.should == t.tv_sec
+ end
+
+ it "copies the gmt state flag" do
+ Time.now.gmtime.dup.should.gmt?
+ end
+
+ it "returns an independent Time object" do
+ t = Time.now
+ t2 = t.dup
+ t.gmtime
+
+ t2.should_not.gmt?
+ end
+
+ it "returns a subclass instance" do
+ c = Class.new(Time)
+ t = c.now
+
+ t.should be_an_instance_of(c)
+ t.dup.should be_an_instance_of(c)
+ end
+
+ it "returns a clone of Time instance" do
+ c = Time.dup
+ t = c.now
+
+ t.should be_an_instance_of(c)
+ t.should_not be_an_instance_of(Time)
+
+ t.dup.should be_an_instance_of(c)
+ t.dup.should_not be_an_instance_of(Time)
+ end
+
+ it "does not copy frozen status from the original" do
+ t = Time.now
+ t.freeze
+ t2 = t.dup
+ t2.frozen?.should be_false
+ end
+end
diff --git a/spec/ruby/core/time/eql_spec.rb b/spec/ruby/core/time/eql_spec.rb
new file mode 100644
index 0000000000..2ffb4eec96
--- /dev/null
+++ b/spec/ruby/core/time/eql_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Time#eql?" do
+ it "returns true if self and other have the same whole number of seconds" do
+ Time.at(100).should eql(Time.at(100))
+ end
+
+ it "returns false if self and other have differing whole numbers of seconds" do
+ Time.at(100).should_not eql(Time.at(99))
+ end
+
+ it "returns true if self and other have the same number of microseconds" do
+ Time.at(100, 100).should eql(Time.at(100, 100))
+ end
+
+ it "returns false if self and other have differing numbers of microseconds" do
+ Time.at(100, 100).should_not eql(Time.at(100, 99))
+ end
+
+ it "returns false if self and other have differing fractional microseconds" do
+ Time.at(100, Rational(100,1000)).should_not eql(Time.at(100, Rational(99,1000)))
+ end
+
+ it "returns false when given a non-time value" do
+ Time.at(100, 100).should_not eql("100")
+ Time.at(100, 100).should_not eql(100)
+ Time.at(100, 100).should_not eql(100.1)
+ end
+end
diff --git a/spec/ruby/core/time/fixtures/classes.rb b/spec/ruby/core/time/fixtures/classes.rb
new file mode 100644
index 0000000000..1a9511b261
--- /dev/null
+++ b/spec/ruby/core/time/fixtures/classes.rb
@@ -0,0 +1,106 @@
+module TimeSpecs
+
+ class SubTime < Time; end
+
+ class MethodHolder
+ class << self
+ define_method(:now, &Time.method(:now))
+ define_method(:new, &Time.method(:new))
+ end
+ end
+
+ class Timezone
+ def initialize(options)
+ @offset = options[:offset]
+ end
+
+ def local_to_utc(t)
+ t - @offset
+ end
+
+ def utc_to_local(t)
+ t + @offset
+ end
+ end
+
+ class TimezoneMethodCallRecorder < Timezone
+ def initialize(options, &blk)
+ super(options)
+ @blk = blk
+ end
+
+ def local_to_utc(t)
+ @blk.call(t)
+ super
+ end
+
+ def utc_to_local(t)
+ @blk.call(t)
+ super
+ end
+ end
+
+ class TimeLikeArgumentRecorder
+ def self.result
+ arguments = []
+
+ zone = TimeSpecs::TimezoneMethodCallRecorder.new(offset: 0) do |obj|
+ arguments << obj
+ end
+
+ # ensure timezone's methods are called at least once
+ Time.new(2000, 1, 1, 12, 0, 0, zone)
+
+ return arguments[0]
+ end
+ end
+
+ Z = Struct.new(:offset, :abbr)
+ Zone = Struct.new(:std, :dst, :dst_range)
+ Zones = {
+ "Asia/Colombo" => Zone[Z[5*3600+30*60, "MMT"], nil, nil],
+ "Europe/Kiev" => Zone[Z[2*3600, "EET"], Z[3*3600, "EEST"], 4..10],
+ "PST" => Zone[Z[(-9*60*60), "PST"], nil, nil],
+ }
+
+ class TimezoneWithName < Timezone
+ attr_reader :name
+
+ def initialize(options)
+ @name = options[:name]
+ @std, @dst, @dst_range = *Zones[@name]
+ end
+
+ def dst?(t)
+ @dst_range&.cover?(t.mon)
+ end
+
+ def zone(t)
+ (dst?(t) ? @dst : @std)
+ end
+
+ def utc_offset(t)
+ zone(t)&.offset || 0
+ end
+
+ def abbr(t)
+ zone(t)&.abbr
+ end
+
+ def local_to_utc(t)
+ t - utc_offset(t)
+ end
+
+ def utc_to_local(t)
+ t + utc_offset(t)
+ end
+ end
+
+ class TimeWithFindTimezone < Time
+ def self.find_timezone(name)
+ TimezoneWithName.new(name: name.to_s)
+ end
+ end
+
+ TimezoneWithAbbr = TimezoneWithName
+end
diff --git a/spec/ruby/core/time/floor_spec.rb b/spec/ruby/core/time/floor_spec.rb
new file mode 100644
index 0000000000..b0003469c9
--- /dev/null
+++ b/spec/ruby/core/time/floor_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe "Time#floor" do
+ before do
+ @time = Time.utc(2010, 3, 30, 5, 43, "25.123456789".to_r)
+ end
+
+ it "defaults to flooring to 0 places" do
+ @time.floor.should == Time.utc(2010, 3, 30, 5, 43, 25.to_r)
+ end
+
+ it "floors to 0 decimal places with an explicit argument" do
+ @time.floor(0).should == Time.utc(2010, 3, 30, 5, 43, 25.to_r)
+ end
+
+ it "floors to 7 decimal places with an explicit argument" do
+ @time.floor(7).should == Time.utc(2010, 3, 30, 5, 43, "25.1234567".to_r)
+ end
+
+ it "returns an instance of Time, even if #floor is called on a subclass" do
+ subclass = Class.new(Time)
+ instance = subclass.at(0)
+ instance.class.should equal subclass
+ instance.floor.should be_an_instance_of(Time)
+ end
+
+ it "copies own timezone to the returning value" do
+ @time.zone.should == @time.floor.zone
+
+ time = with_timezone "JST-9" do
+ Time.at 0, 1
+ end
+
+ time.zone.should == time.floor.zone
+ end
+end
diff --git a/spec/ruby/core/time/friday_spec.rb b/spec/ruby/core/time/friday_spec.rb
new file mode 100644
index 0000000000..8bee7f7558
--- /dev/null
+++ b/spec/ruby/core/time/friday_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Time#friday?" do
+ it "returns true if time represents Friday" do
+ Time.local(2000, 1, 7).should.friday?
+ end
+
+ it "returns false if time doesn't represent Friday" do
+ Time.local(2000, 1, 1).should_not.friday?
+ end
+end
diff --git a/spec/ruby/core/time/getgm_spec.rb b/spec/ruby/core/time/getgm_spec.rb
new file mode 100644
index 0000000000..b5d45b1d9f
--- /dev/null
+++ b/spec/ruby/core/time/getgm_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/getgm'
+
+describe "Time#getgm" do
+ it_behaves_like :time_getgm, :getgm
+end
diff --git a/spec/ruby/core/time/getlocal_spec.rb b/spec/ruby/core/time/getlocal_spec.rb
new file mode 100644
index 0000000000..926a6dbf45
--- /dev/null
+++ b/spec/ruby/core/time/getlocal_spec.rb
@@ -0,0 +1,167 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Time#getlocal" do
+ it "returns a new time which is the local representation of time" do
+ # Testing with America/Regina here because it doesn't have DST.
+ with_timezone("CST", -6) do
+ t = Time.gm(2007, 1, 9, 12, 0, 0)
+ t.localtime.should == Time.local(2007, 1, 9, 6, 0, 0)
+ end
+ end
+
+ it "returns a Time with UTC offset specified as an Integer number of seconds" do
+ t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal(3630)
+ t.should == Time.new(2007, 1, 9, 13, 0, 30, 3630)
+ t.utc_offset.should == 3630
+ end
+
+ platform_is_not :windows do
+ it "returns a new time with the correct utc_offset according to the set timezone" do
+ t = Time.new(2005, 2, 27, 22, 50, 0, -3600)
+ t.utc_offset.should == -3600
+
+ with_timezone("America/New_York") do
+ t.getlocal.utc_offset.should == -18000
+ end
+ end
+ end
+
+ describe "with an argument that responds to #to_int" do
+ it "coerces using #to_int" do
+ o = mock('integer')
+ o.should_receive(:to_int).and_return(3630)
+ t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal(o)
+ t.should == Time.new(2007, 1, 9, 13, 0, 30, 3630)
+ t.utc_offset.should == 3630
+ end
+ end
+
+ it "returns a Time with a UTC offset of the specified number of Rational seconds" do
+ t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal(Rational(7201, 2))
+ t.should == Time.new(2007, 1, 9, 13, 0, Rational(1, 2), Rational(7201, 2))
+ t.utc_offset.should eql(Rational(7201, 2))
+ end
+
+ describe "with an argument that responds to #to_r" do
+ it "coerces using #to_r" do
+ o = mock_numeric('rational')
+ o.should_receive(:to_r).and_return(Rational(7201, 2))
+ t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal(o)
+ t.should == Time.new(2007, 1, 9, 13, 0, Rational(1, 2), Rational(7201, 2))
+ t.utc_offset.should eql(Rational(7201, 2))
+ end
+ end
+
+ it "returns a Time with a UTC offset specified as +HH:MM" do
+ t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("+01:00")
+ t.should == Time.new(2007, 1, 9, 13, 0, 0, 3600)
+ t.utc_offset.should == 3600
+ end
+
+ it "returns a Time with a UTC offset specified as -HH:MM" do
+ t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("-01:00")
+ t.should == Time.new(2007, 1, 9, 11, 0, 0, -3600)
+ t.utc_offset.should == -3600
+ end
+
+ describe "with an argument that responds to #to_str" do
+ it "coerces using #to_str" do
+ o = mock('string')
+ o.should_receive(:to_str).and_return("+01:00")
+ t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal(o)
+ t.should == Time.new(2007, 1, 9, 13, 0, 0, 3600)
+ t.utc_offset.should == 3600
+ end
+ end
+
+ it "raises ArgumentError if the String argument is not of the form (+|-)HH:MM" do
+ t = Time.now
+ -> { t.getlocal("3600") }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the String argument is not in an ASCII-compatible encoding" do
+ t = Time.now
+ -> { t.getlocal("-01:00".encode("UTF-16LE")) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the argument represents a value less than or equal to -86400 seconds" do
+ t = Time.new
+ t.getlocal(-86400 + 1).utc_offset.should == (-86400 + 1)
+ -> { t.getlocal(-86400) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the argument represents a value greater than or equal to 86400 seconds" do
+ t = Time.new
+ t.getlocal(86400 - 1).utc_offset.should == (86400 - 1)
+ -> { t.getlocal(86400) }.should raise_error(ArgumentError)
+ end
+
+ describe "with a timezone argument" do
+ it "returns a Time in the timezone" do
+ zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60))
+ time = Time.utc(2000, 1, 1, 12, 0, 0).getlocal(zone)
+
+ time.zone.should == zone
+ time.utc_offset.should == 5*3600+30*60
+ end
+
+ it "accepts timezone argument that must have #local_to_utc and #utc_to_local methods" do
+ zone = Object.new
+ def zone.utc_to_local(time)
+ time
+ end
+ def zone.local_to_utc(time)
+ time
+ end
+
+ -> {
+ Time.utc(2000, 1, 1, 12, 0, 0).getlocal(zone).should be_kind_of(Time)
+ }.should_not raise_error
+ end
+
+ it "raises TypeError if timezone does not implement #utc_to_local method" do
+ zone = Object.new
+ def zone.local_to_utc(time)
+ time
+ end
+
+ -> {
+ Time.utc(2000, 1, 1, 12, 0, 0).getlocal(zone)
+ }.should raise_error(TypeError, /can't convert \w+ into an exact number/)
+ end
+
+ it "does not raise exception if timezone does not implement #local_to_utc method" do
+ zone = Object.new
+ def zone.utc_to_local(time)
+ time
+ end
+
+ -> {
+ Time.utc(2000, 1, 1, 12, 0, 0).getlocal(zone).should be_kind_of(Time)
+ }.should_not raise_error
+ end
+
+ context "subject's class implements .find_timezone method" do
+ it "calls .find_timezone to build a time object if passed zone name as a timezone argument" do
+ time = TimeSpecs::TimeWithFindTimezone.utc(2000, 1, 1, 12, 0, 0).getlocal("Asia/Colombo")
+ time.zone.should be_kind_of TimeSpecs::TimezoneWithName
+ time.zone.name.should == "Asia/Colombo"
+
+ time = TimeSpecs::TimeWithFindTimezone.utc(2000, 1, 1, 12, 0, 0).getlocal("some invalid zone name")
+ time.zone.should be_kind_of TimeSpecs::TimezoneWithName
+ time.zone.name.should == "some invalid zone name"
+ end
+
+ it "does not call .find_timezone if passed any not string/numeric/timezone timezone argument" do
+ [Object.new, [], {}, :"some zone"].each do |zone|
+ time = TimeSpecs::TimeWithFindTimezone.utc(2000, 1, 1, 12, 0, 0)
+
+ -> {
+ time.getlocal(zone)
+ }.should raise_error(TypeError, /can't convert \w+ into an exact number/)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/time/getutc_spec.rb b/spec/ruby/core/time/getutc_spec.rb
new file mode 100644
index 0000000000..0cd9c17b00
--- /dev/null
+++ b/spec/ruby/core/time/getutc_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/getgm'
+
+describe "Time#getutc" do
+ it_behaves_like :time_getgm, :getutc
+end
diff --git a/spec/ruby/core/time/gm_spec.rb b/spec/ruby/core/time/gm_spec.rb
new file mode 100644
index 0000000000..26dffbcedc
--- /dev/null
+++ b/spec/ruby/core/time/gm_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/gm'
+require_relative 'shared/time_params'
+
+describe "Time.gm" do
+ it_behaves_like :time_gm, :gm
+ it_behaves_like :time_params, :gm
+ it_behaves_like :time_params_10_arg, :gm
+ it_behaves_like :time_params_microseconds, :gm
+end
diff --git a/spec/ruby/core/time/gmt_offset_spec.rb b/spec/ruby/core/time/gmt_offset_spec.rb
new file mode 100644
index 0000000000..df417e6e4e
--- /dev/null
+++ b/spec/ruby/core/time/gmt_offset_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/gmt_offset'
+
+describe "Time#gmt_offset" do
+ it_behaves_like :time_gmt_offset, :gmt_offset
+end
diff --git a/spec/ruby/core/time/gmt_spec.rb b/spec/ruby/core/time/gmt_spec.rb
new file mode 100644
index 0000000000..840f59e0e8
--- /dev/null
+++ b/spec/ruby/core/time/gmt_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "Time#gmt?" do
+ it "returns true if time represents a time in UTC (GMT)" do
+ Time.now.should_not.gmt?
+ Time.now.gmtime.should.gmt?
+ end
+end
diff --git a/spec/ruby/core/time/gmtime_spec.rb b/spec/ruby/core/time/gmtime_spec.rb
new file mode 100644
index 0000000000..d965cd541d
--- /dev/null
+++ b/spec/ruby/core/time/gmtime_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/gmtime'
+
+describe "Time#gmtime" do
+ it_behaves_like :time_gmtime, :gmtime
+end
diff --git a/spec/ruby/core/time/gmtoff_spec.rb b/spec/ruby/core/time/gmtoff_spec.rb
new file mode 100644
index 0000000000..fa28520e9e
--- /dev/null
+++ b/spec/ruby/core/time/gmtoff_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/gmt_offset'
+
+describe "Time#gmtoff" do
+ it_behaves_like :time_gmt_offset, :gmtoff
+end
diff --git a/spec/ruby/core/time/hash_spec.rb b/spec/ruby/core/time/hash_spec.rb
new file mode 100644
index 0000000000..4f4d11a2cd
--- /dev/null
+++ b/spec/ruby/core/time/hash_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Time#hash" do
+ it "returns an Integer" do
+ Time.at(100).hash.should be_an_instance_of(Integer)
+ end
+
+ it "is stable" do
+ Time.at(1234).hash.should == Time.at(1234).hash
+ end
+end
diff --git a/spec/ruby/core/time/hour_spec.rb b/spec/ruby/core/time/hour_spec.rb
new file mode 100644
index 0000000000..ca69c25adb
--- /dev/null
+++ b/spec/ruby/core/time/hour_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "Time#hour" do
+ it "returns the hour of the day (0..23) for a local Time" do
+ with_timezone("CET", 1) do
+ Time.local(1970, 1, 1, 1).hour.should == 1
+ end
+ end
+
+ it "returns the hour of the day for a UTC Time" do
+ Time.utc(1970, 1, 1, 0).hour.should == 0
+ end
+
+ it "returns the hour of the day for a Time with a fixed offset" do
+ Time.new(2012, 1, 1, 0, 0, 0, -3600).hour.should == 0
+ end
+end
diff --git a/spec/ruby/core/time/inspect_spec.rb b/spec/ruby/core/time/inspect_spec.rb
new file mode 100644
index 0000000000..c3a4519a24
--- /dev/null
+++ b/spec/ruby/core/time/inspect_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'shared/inspect'
+
+describe "Time#inspect" do
+ it_behaves_like :inspect, :inspect
+
+ it "preserves microseconds" do
+ t = Time.utc(2007, 11, 1, 15, 25, 0, 123456)
+ t.inspect.should == "2007-11-01 15:25:00.123456 UTC"
+ end
+
+ it "omits trailing zeros from microseconds" do
+ t = Time.utc(2007, 11, 1, 15, 25, 0, 100000)
+ t.inspect.should == "2007-11-01 15:25:00.1 UTC"
+ end
+
+ it "uses the correct time zone without microseconds" do
+ t = Time.utc(2000, 1, 1)
+ t = t.localtime(9*3600)
+ t.inspect.should == "2000-01-01 09:00:00 +0900"
+ end
+
+ it "uses the correct time zone with microseconds" do
+ t = Time.utc(2000, 1, 1, 0, 0, 0, 123456)
+ t = t.localtime(9*3600)
+ t.inspect.should == "2000-01-01 09:00:00.123456 +0900"
+ end
+
+ it "preserves nanoseconds" do
+ t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789r)
+ t.inspect.should == "2007-11-01 15:25:00.123456789 UTC"
+ end
+end
diff --git a/spec/ruby/core/time/isdst_spec.rb b/spec/ruby/core/time/isdst_spec.rb
new file mode 100644
index 0000000000..173230ca07
--- /dev/null
+++ b/spec/ruby/core/time/isdst_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/isdst'
+
+describe "Time#isdst" do
+ it_behaves_like :time_isdst, :isdst
+end
diff --git a/spec/ruby/core/time/local_spec.rb b/spec/ruby/core/time/local_spec.rb
new file mode 100644
index 0000000000..581ed171d5
--- /dev/null
+++ b/spec/ruby/core/time/local_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/local'
+require_relative 'shared/time_params'
+
+describe "Time.local" do
+ it_behaves_like :time_local, :local
+ it_behaves_like :time_local_10_arg, :local
+ it_behaves_like :time_params, :local
+ it_behaves_like :time_params_10_arg, :local
+ it_behaves_like :time_params_microseconds, :local
+end
diff --git a/spec/ruby/core/time/localtime_spec.rb b/spec/ruby/core/time/localtime_spec.rb
new file mode 100644
index 0000000000..609b6532a1
--- /dev/null
+++ b/spec/ruby/core/time/localtime_spec.rb
@@ -0,0 +1,152 @@
+require_relative '../../spec_helper'
+
+describe "Time#localtime" do
+ it "converts self to local time, modifying the receiver" do
+ # Testing with America/Regina here because it doesn't have DST.
+ with_timezone("CST", -6) do
+ t = Time.gm(2007, 1, 9, 12, 0, 0)
+ t.localtime
+ t.should == Time.local(2007, 1, 9, 6, 0, 0)
+ end
+ end
+
+ it "returns self" do
+ t = Time.gm(2007, 1, 9, 12, 0, 0)
+ t.localtime.should equal(t)
+ end
+
+ it "converts time to the UTC offset specified as an Integer number of seconds" do
+ t = Time.gm(2007, 1, 9, 12, 0, 0)
+ t.localtime(3630)
+ t.should == Time.new(2007, 1, 9, 13, 0, 30, 3630)
+ t.utc_offset.should == 3630
+ end
+
+ describe "on a frozen time" do
+ it "does not raise an error if already in the right time zone" do
+ time = Time.now
+ time.freeze
+ time.localtime.should equal(time)
+ end
+
+ it "raises a FrozenError if the time has a different time zone" do
+ time = Time.gm(2007, 1, 9, 12, 0, 0)
+ time.freeze
+ -> { time.localtime }.should raise_error(FrozenError)
+ end
+ end
+
+ describe "with an argument that responds to #to_int" do
+ it "coerces using #to_int" do
+ o = mock('integer')
+ o.should_receive(:to_int).and_return(3630)
+ t = Time.gm(2007, 1, 9, 12, 0, 0)
+ t.localtime(o)
+ t.should == Time.new(2007, 1, 9, 13, 0, 30, 3630)
+ t.utc_offset.should == 3630
+ end
+ end
+
+ it "returns a Time with a UTC offset of the specified number of Rational seconds" do
+ t = Time.gm(2007, 1, 9, 12, 0, 0)
+ t.localtime(Rational(7201, 2))
+ t.should == Time.new(2007, 1, 9, 13, 0, Rational(1, 2), Rational(7201, 2))
+ t.utc_offset.should eql(Rational(7201, 2))
+ end
+
+ describe "with an argument that responds to #to_r" do
+ it "coerces using #to_r" do
+ o = mock_numeric('rational')
+ o.should_receive(:to_r).and_return(Rational(7201, 2))
+ t = Time.gm(2007, 1, 9, 12, 0, 0)
+ t.localtime(o)
+ t.should == Time.new(2007, 1, 9, 13, 0, Rational(1, 2), Rational(7201, 2))
+ t.utc_offset.should eql(Rational(7201, 2))
+ end
+ end
+
+ it "returns a Time with a UTC offset specified as +HH:MM" do
+ t = Time.gm(2007, 1, 9, 12, 0, 0)
+ t.localtime("+01:00")
+ t.should == Time.new(2007, 1, 9, 13, 0, 0, 3600)
+ t.utc_offset.should == 3600
+ end
+
+ it "returns a Time with a UTC offset specified as -HH:MM" do
+ t = Time.gm(2007, 1, 9, 12, 0, 0)
+ t.localtime("-01:00")
+ t.should == Time.new(2007, 1, 9, 11, 0, 0, -3600)
+ t.utc_offset.should == -3600
+ end
+
+ it "returns a Time with a UTC offset specified as UTC" do
+ t = Time.new(2007, 1, 9, 12, 0, 0, 3600)
+ t.localtime("UTC")
+ t.utc_offset.should == 0
+ end
+
+ it "returns a Time with a UTC offset specified as A-Z military zone" do
+ t = Time.new(2007, 1, 9, 12, 0, 0, 3600)
+ t.localtime("B")
+ t.utc_offset.should == 3600 * 2
+ end
+
+ platform_is_not :windows do
+ it "changes the timezone according to the set one" do
+ t = Time.new(2005, 2, 27, 22, 50, 0, -3600)
+ t.utc_offset.should == -3600
+
+ with_timezone("America/New_York") do
+ t.localtime
+ end
+
+ t.utc_offset.should == -18000
+ end
+
+ it "does nothing if already in a local time zone" do
+ time = with_timezone("America/New_York") do
+ Time.new(2005, 2, 27, 22, 50, 0)
+ end
+ zone = time.zone
+
+ with_timezone("Europe/Amsterdam") do
+ time.localtime
+ end
+
+ time.zone.should == zone
+ end
+ end
+
+ describe "with an argument that responds to #to_str" do
+ it "coerces using #to_str" do
+ o = mock('string')
+ o.should_receive(:to_str).and_return("+01:00")
+ t = Time.gm(2007, 1, 9, 12, 0, 0)
+ t.localtime(o)
+ t.should == Time.new(2007, 1, 9, 13, 0, 0, 3600)
+ t.utc_offset.should == 3600
+ end
+ end
+
+ it "raises ArgumentError if the String argument is not of the form (+|-)HH:MM" do
+ t = Time.now
+ -> { t.localtime("3600") }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the String argument is not in an ASCII-compatible encoding" do
+ t = Time.now
+ -> { t.localtime("-01:00".encode("UTF-16LE")) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the argument represents a value less than or equal to -86400 seconds" do
+ t = Time.new
+ t.localtime(-86400 + 1).utc_offset.should == (-86400 + 1)
+ -> { t.localtime(-86400) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the argument represents a value greater than or equal to 86400 seconds" do
+ t = Time.new
+ t.localtime(86400 - 1).utc_offset.should == (86400 - 1)
+ -> { t.localtime(86400) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/time/mday_spec.rb b/spec/ruby/core/time/mday_spec.rb
new file mode 100644
index 0000000000..3c21939890
--- /dev/null
+++ b/spec/ruby/core/time/mday_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/day'
+
+describe "Time#mday" do
+ it_behaves_like :time_day, :mday
+end
diff --git a/spec/ruby/core/time/min_spec.rb b/spec/ruby/core/time/min_spec.rb
new file mode 100644
index 0000000000..7d087d4046
--- /dev/null
+++ b/spec/ruby/core/time/min_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "Time#min" do
+ it "returns the minute of the hour (0..59) for a local Time" do
+ with_timezone("CET", 1) do
+ Time.local(1970, 1, 1, 0, 0).min.should == 0
+ end
+ end
+
+ it "returns the minute of the hour for a UTC Time" do
+ Time.utc(1970, 1, 1, 0, 0).min.should == 0
+ end
+
+ it "returns the minute of the hour for a Time with a fixed offset" do
+ Time.new(2012, 1, 1, 0, 0, 0, -3600).min.should == 0
+ end
+end
diff --git a/spec/ruby/core/time/minus_spec.rb b/spec/ruby/core/time/minus_spec.rb
new file mode 100644
index 0000000000..8449778465
--- /dev/null
+++ b/spec/ruby/core/time/minus_spec.rb
@@ -0,0 +1,121 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Time#-" do
+ it "decrements the time by the specified amount" do
+ (Time.at(100) - 100).should == Time.at(0)
+ (Time.at(100) - Time.at(99)).should == 1.0
+ end
+
+ it "understands negative subtractions" do
+ t = Time.at(100) - -1.3
+ t.usec.should == 300000
+ t.to_i.should == 101
+ end
+
+ #see [ruby-dev:38446]
+ it "accepts arguments that can be coerced into Rational" do
+ (obj = mock_numeric('10')).should_receive(:to_r).and_return(Rational(10))
+ (Time.at(100) - obj).should == Time.at(90)
+ end
+
+ it "raises a TypeError if given argument is a coercible String" do
+ -> { Time.now - "1" }.should raise_error(TypeError)
+ -> { Time.now - "0.1" }.should raise_error(TypeError)
+ -> { Time.now - "1/3" }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError on argument that can't be coerced" do
+ -> { Time.now - Object.new }.should raise_error(TypeError)
+ -> { Time.now - "stuff" }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError on nil argument" do
+ -> { Time.now - nil }.should raise_error(TypeError)
+ end
+
+ it "tracks microseconds" do
+ time = Time.at(0.777777)
+ time -= 0.654321
+ time.usec.should == 123456
+ time -= 1
+ time.usec.should == 123456
+ end
+
+ it "tracks microseconds from a Rational" do
+ time = Time.at(Rational(777_777, 1_000_000))
+ time -= Rational(654_321, 1_000_000)
+ time.usec.should == 123_456
+ time -= Rational(123_456, 1_000_000)
+ time.usec.should == 0
+ end
+
+ it "tracks nanoseconds" do
+ time = Time.at(Rational(999_999_999, 1_000_000_000))
+ time -= Rational(876_543_210, 1_000_000_000)
+ time.nsec.should == 123_456_789
+ time -= Rational(123_456_789, 1_000_000_000)
+ time.nsec.should == 0
+ end
+
+ it "maintains precision" do
+ time = Time.at(10) - Rational(1_000_000_000_000_001, 1_000_000_000_000_000)
+ time.should_not == Time.at(9)
+ end
+
+ it "maintains microseconds precision" do
+ time = Time.at(10) - Rational(1, 1_000_000)
+ time.usec.should == 999_999
+ end
+
+ it "maintains nanoseconds precision" do
+ time = Time.at(10) - Rational(1, 1_000_000_000)
+ time.nsec.should == 999_999_999
+ end
+
+ it "maintains subseconds precision" do
+ time = Time.at(0) - Rational(1_000_000_000_000_001, 1_000_000_000_000_000)
+ time.subsec.should == Rational(999_999_999_999_999, 1_000_000_000_000_000)
+ end
+
+ it "returns a UTC time if self is UTC" do
+ (Time.utc(2012) - 10).should.utc?
+ end
+
+ it "returns a non-UTC time if self is non-UTC" do
+ (Time.local(2012) - 10).should_not.utc?
+ end
+
+ it "returns a time with the same fixed offset as self" do
+ (Time.new(2012, 1, 1, 0, 0, 0, 3600) - 10).utc_offset.should == 3600
+ end
+
+ it "preserves time zone" do
+ time_with_zone = Time.now.utc
+ time_with_zone.zone.should == (time_with_zone - 1).zone
+
+ time_with_zone = Time.now
+ time_with_zone.zone.should == (time_with_zone - 1).zone
+ end
+
+ context "zone is a timezone object" do
+ it "preserves time zone" do
+ zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60))
+ time = Time.new(2012, 1, 1, 12, 0, 0, zone) - 1
+
+ time.zone.should == zone
+ end
+ end
+
+ it "does not return a subclass instance" do
+ c = Class.new(Time)
+ x = c.now + 1
+ x.should be_an_instance_of(Time)
+ end
+
+ it "returns a time with nanoseconds precision between two time objects" do
+ time1 = Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000))
+ time2 = Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
+ (time1 - time2).should == 86_399.999999998
+ end
+end
diff --git a/spec/ruby/core/time/mktime_spec.rb b/spec/ruby/core/time/mktime_spec.rb
new file mode 100644
index 0000000000..78a6a6e772
--- /dev/null
+++ b/spec/ruby/core/time/mktime_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/local'
+require_relative 'shared/time_params'
+
+describe "Time.mktime" do
+ it_behaves_like :time_local, :mktime
+ it_behaves_like :time_local_10_arg, :mktime
+ it_behaves_like :time_params, :mktime
+ it_behaves_like :time_params_10_arg, :mktime
+ it_behaves_like :time_params_microseconds, :mktime
+end
diff --git a/spec/ruby/core/time/mon_spec.rb b/spec/ruby/core/time/mon_spec.rb
new file mode 100644
index 0000000000..f41b39648b
--- /dev/null
+++ b/spec/ruby/core/time/mon_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/month'
+
+describe "Time#mon" do
+ it_behaves_like :time_month, :mon
+end
diff --git a/spec/ruby/core/time/monday_spec.rb b/spec/ruby/core/time/monday_spec.rb
new file mode 100644
index 0000000000..47ecaeb1db
--- /dev/null
+++ b/spec/ruby/core/time/monday_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Time#monday?" do
+ it "returns true if time represents Monday" do
+ Time.local(2000, 1, 3).should.monday?
+ end
+
+ it "returns false if time doesn't represent Monday" do
+ Time.local(2000, 1, 1).should_not.monday?
+ end
+end
diff --git a/spec/ruby/core/time/month_spec.rb b/spec/ruby/core/time/month_spec.rb
new file mode 100644
index 0000000000..81e20384ab
--- /dev/null
+++ b/spec/ruby/core/time/month_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/month'
+
+describe "Time#month" do
+ it_behaves_like :time_month, :month
+end
diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb
new file mode 100644
index 0000000000..727fdf92c2
--- /dev/null
+++ b/spec/ruby/core/time/new_spec.rb
@@ -0,0 +1,458 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/now'
+require_relative 'shared/local'
+require_relative 'shared/time_params'
+
+describe "Time.new" do
+ it_behaves_like :time_now, :new
+end
+
+describe "Time.new" do
+ it_behaves_like :time_local, :new
+ it_behaves_like :time_params, :new
+end
+
+describe "Time.new with a utc_offset argument" do
+ it "returns a non-UTC time" do
+ Time.new(2000, 1, 1, 0, 0, 0, 0).should_not.utc?
+ end
+
+ it "returns a Time with a UTC offset of the specified number of Integer seconds" do
+ Time.new(2000, 1, 1, 0, 0, 0, 123).utc_offset.should == 123
+ end
+
+ describe "with an argument that responds to #to_int" do
+ it "coerces using #to_int" do
+ o = mock('integer')
+ o.should_receive(:to_int).and_return(123)
+ Time.new(2000, 1, 1, 0, 0, 0, o).utc_offset.should == 123
+ end
+ end
+
+ it "returns a Time with a UTC offset of the specified number of Rational seconds" do
+ Time.new(2000, 1, 1, 0, 0, 0, Rational(5, 2)).utc_offset.should eql(Rational(5, 2))
+ end
+
+ describe "with an argument that responds to #to_r" do
+ it "coerces using #to_r" do
+ o = mock_numeric('rational')
+ o.should_receive(:to_r).and_return(Rational(5, 2))
+ Time.new(2000, 1, 1, 0, 0, 0, o).utc_offset.should eql(Rational(5, 2))
+ end
+ end
+
+ it "returns a Time with a UTC offset specified as +HH:MM" do
+ Time.new(2000, 1, 1, 0, 0, 0, "+05:30").utc_offset.should == 19800
+ end
+
+ it "returns a Time with a UTC offset specified as -HH:MM" do
+ Time.new(2000, 1, 1, 0, 0, 0, "-04:10").utc_offset.should == -15000
+ end
+
+ it "returns a Time with a UTC offset specified as +HH:MM:SS" do
+ Time.new(2000, 1, 1, 0, 0, 0, "+05:30:37").utc_offset.should == 19837
+ end
+
+ it "returns a Time with a UTC offset specified as -HH:MM" do
+ Time.new(2000, 1, 1, 0, 0, 0, "-04:10:43").utc_offset.should == -15043
+ end
+
+ ruby_bug '#13669', '3.0'...'3.1' do
+ it "returns a Time with a UTC offset specified as +HH" do
+ Time.new(2000, 1, 1, 0, 0, 0, "+05").utc_offset.should == 3600 * 5
+ end
+
+ it "returns a Time with a UTC offset specified as -HH" do
+ Time.new(2000, 1, 1, 0, 0, 0, "-05").utc_offset.should == -3600 * 5
+ end
+
+ it "returns a Time with a UTC offset specified as +HHMM" do
+ Time.new(2000, 1, 1, 0, 0, 0, "+0530").utc_offset.should == 19800
+ end
+
+ it "returns a Time with a UTC offset specified as -HHMM" do
+ Time.new(2000, 1, 1, 0, 0, 0, "-0530").utc_offset.should == -19800
+ end
+
+ it "returns a Time with a UTC offset specified as +HHMMSS" do
+ Time.new(2000, 1, 1, 0, 0, 0, "+053037").utc_offset.should == 19837
+ end
+
+ it "returns a Time with a UTC offset specified as -HHMMSS" do
+ Time.new(2000, 1, 1, 0, 0, 0, "-053037").utc_offset.should == -19837
+ end
+ end
+
+ describe "with an argument that responds to #to_str" do
+ it "coerces using #to_str" do
+ o = mock('string')
+ o.should_receive(:to_str).and_return("+05:30")
+ Time.new(2000, 1, 1, 0, 0, 0, o).utc_offset.should == 19800
+ end
+ end
+
+ it "returns a Time with UTC offset specified as UTC" do
+ Time.new(2000, 1, 1, 0, 0, 0, "UTC").utc_offset.should == 0
+ end
+
+ it "returns a Time with UTC offset specified as a single letter military timezone" do
+ [
+ ["A", 3600],
+ ["B", 3600 * 2],
+ ["C", 3600 * 3],
+ ["D", 3600 * 4],
+ ["E", 3600 * 5],
+ ["F", 3600 * 6],
+ ["G", 3600 * 7],
+ ["H", 3600 * 8],
+ ["I", 3600 * 9],
+ # J is not supported
+ ["K", 3600 * 10],
+ ["L", 3600 * 11],
+ ["M", 3600 * 12],
+ ["N", 3600 * -1],
+ ["O", 3600 * -2],
+ ["P", 3600 * -3],
+ ["Q", 3600 * -4],
+ ["R", 3600 * -5],
+ ["S", 3600 * -6],
+ ["T", 3600 * -7],
+ ["U", 3600 * -8],
+ ["V", 3600 * -9],
+ ["W", 3600 * -10],
+ ["X", 3600 * -11],
+ ["Y", 3600 * -12],
+ ["Z", 0]
+ ].each do |letter, offset|
+ Time.new(2000, 1, 1, 0, 0, 0, letter).utc_offset.should == offset
+ end
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "raises ArgumentError if the string argument is J" do
+ message = '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset'
+ -> { Time.new(2000, 1, 1, 0, 0, 0, "J") }.should raise_error(ArgumentError, message)
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "raises ArgumentError if the string argument is J" do
+ message = '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: J'
+ -> { Time.new(2000, 1, 1, 0, 0, 0, "J") }.should raise_error(ArgumentError, message)
+ end
+ end
+
+ it "returns a local Time if the argument is nil" do
+ with_timezone("PST", -8) do
+ t = Time.new(2000, 1, 1, 0, 0, 0, nil)
+ t.utc_offset.should == -28800
+ t.zone.should == "PST"
+ end
+ end
+
+ # [Bug #8679], r47676
+ it "disallows a value for minutes greater than 59" do
+ -> {
+ Time.new(2000, 1, 1, 0, 0, 0, "+01:60")
+ }.should raise_error(ArgumentError)
+ -> {
+ Time.new(2000, 1, 1, 0, 0, 0, "+01:99")
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the String argument is not of the form (+|-)HH:MM" do
+ -> { Time.new(2000, 1, 1, 0, 0, 0, "3600") }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the hour value is greater than 23" do
+ -> { Time.new(2000, 1, 1, 0, 0, 0, "+24:00") }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the String argument is not in an ASCII-compatible encoding" do
+ # Don't check exception message - it was changed in previous CRuby versions:
+ # - "string contains null byte"
+ # - '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset'
+ -> {
+ Time.new(2000, 1, 1, 0, 0, 0, "-04:10".encode("UTF-16LE"))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the argument represents a value less than or equal to -86400 seconds" do
+ Time.new(2000, 1, 1, 0, 0, 0, -86400 + 1).utc_offset.should == (-86400 + 1)
+ -> { Time.new(2000, 1, 1, 0, 0, 0, -86400) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the argument represents a value greater than or equal to 86400 seconds" do
+ Time.new(2000, 1, 1, 0, 0, 0, 86400 - 1).utc_offset.should == (86400 - 1)
+ -> { Time.new(2000, 1, 1, 0, 0, 0, 86400) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the utc_offset argument is greater than or equal to 10e9" do
+ -> { Time.new(2000, 1, 1, 0, 0, 0, 1000000000) }.should raise_error(ArgumentError)
+ end
+end
+
+describe "Time.new with a timezone argument" do
+ it "returns a Time in the timezone" do
+ zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60))
+ time = Time.new(2000, 1, 1, 12, 0, 0, zone)
+
+ time.zone.should == zone
+ time.utc_offset.should == 5*3600+30*60
+ ruby_version_is "3.0" do
+ time.wday.should == 6
+ time.yday.should == 1
+ end
+ end
+
+ it "accepts timezone argument that must have #local_to_utc and #utc_to_local methods" do
+ zone = Object.new
+ def zone.utc_to_local(time)
+ time
+ end
+ def zone.local_to_utc(time)
+ time
+ end
+
+ -> {
+ Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time)
+ }.should_not raise_error
+ end
+
+ it "raises TypeError if timezone does not implement #local_to_utc method" do
+ zone = Object.new
+ def zone.utc_to_local(time)
+ time
+ end
+
+ -> {
+ Time.new(2000, 1, 1, 12, 0, 0, zone)
+ }.should raise_error(TypeError, /can't convert \w+ into an exact number/)
+ end
+
+ it "does not raise exception if timezone does not implement #utc_to_local method" do
+ zone = Object.new
+ def zone.local_to_utc(time)
+ time
+ end
+
+ -> {
+ Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time)
+ }.should_not raise_error
+ end
+
+ # The result also should be a Time or Time-like object (not necessary to be the same class)
+ # The zone of the result is just ignored
+ describe "returned value by #utc_to_local and #local_to_utc methods" do
+ it "could be Time instance" do
+ zone = Object.new
+ def zone.local_to_utc(t)
+ Time.utc(t.year, t.mon, t.day, t.hour - 1, t.min, t.sec)
+ end
+
+ -> {
+ Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time)
+ Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60
+ }.should_not raise_error
+ end
+
+ it "could be Time subclass instance" do
+ zone = Object.new
+ def zone.local_to_utc(t)
+ Class.new(Time).utc(t.year, t.mon, t.day, t.hour - 1, t.min, t.sec)
+ end
+
+ -> {
+ Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time)
+ Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60
+ }.should_not raise_error
+ end
+
+ it "could be any object with #to_i method" do
+ zone = Object.new
+ def zone.local_to_utc(time)
+ Struct.new(:to_i).new(time.to_i - 60*60)
+ end
+
+ -> {
+ Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time)
+ Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60
+ }.should_not raise_error
+ end
+
+ it "could have any #zone and #utc_offset because they are ignored" do
+ zone = Object.new
+ def zone.local_to_utc(time)
+ Struct.new(:to_i, :zone, :utc_offset).new(time.to_i, 'America/New_York', -5*60*60)
+ end
+ Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 0
+
+ zone = Object.new
+ def zone.local_to_utc(time)
+ Struct.new(:to_i, :zone, :utc_offset).new(time.to_i, 'Asia/Tokyo', 9*60*60)
+ end
+ Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 0
+ end
+
+ it "leads to raising Argument error if difference between argument and result is too large" do
+ zone = Object.new
+ def zone.local_to_utc(t)
+ Time.utc(t.year, t.mon, t.day + 1, t.hour, t.min, t.sec)
+ end
+
+ -> {
+ Time.new(2000, 1, 1, 12, 0, 0, zone)
+ }.should raise_error(ArgumentError, "utc_offset out of range")
+ end
+ end
+
+ # https://github.com/ruby/ruby/blob/v2_6_0/time.c#L5330
+ #
+ # Time-like argument to these methods is similar to a Time object in UTC without sub-second;
+ # it has attribute readers for the parts, e.g. year, month, and so on, and epoch time readers, to_i
+ #
+ # The sub-second attributes are fixed as 0, and utc_offset, zone, isdst, and their aliases are same as a Time object in UTC
+ describe "Time-like argument of #utc_to_local and #local_to_utc methods" do
+ before do
+ @obj = TimeSpecs::TimeLikeArgumentRecorder.result
+ @obj.should_not == nil
+ end
+
+ it "implements subset of Time methods" do
+ [
+ :year, :mon, :month, :mday, :hour, :min, :sec,
+ :tv_sec, :tv_usec, :usec, :tv_nsec, :nsec, :subsec,
+ :to_i, :to_f, :to_r, :+, :-,
+ :isdst, :dst?, :zone, :gmtoff, :gmt_offset, :utc_offset, :utc?, :gmt?,
+ :to_s, :inspect, :to_a, :to_time,
+ ].each do |name|
+ @obj.respond_to?(name).should == true
+ end
+ end
+
+ it "has attribute values the same as a Time object in UTC" do
+ @obj.usec.should == 0
+ @obj.nsec.should == 0
+ @obj.subsec.should == 0
+ @obj.tv_usec.should == 0
+ @obj.tv_nsec.should == 0
+
+ @obj.utc_offset.should == 0
+ @obj.zone.should == "UTC"
+ @obj.isdst.should == Time.new.utc.isdst
+ end
+ end
+
+ context "#name method" do
+ it "uses the optional #name method for marshaling" do
+ zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo")
+ time = Time.new(2000, 1, 1, 12, 0, 0, zone)
+ time_loaded = Marshal.load(Marshal.dump(time))
+
+ time_loaded.zone.should == "Asia/Colombo"
+ time_loaded.utc_offset.should == 5*3600+30*60
+ end
+
+ it "cannot marshal Time if #name method isn't implemented" do
+ zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60))
+ time = Time.new(2000, 1, 1, 12, 0, 0, zone)
+
+ -> {
+ Marshal.dump(time)
+ }.should raise_error(NoMethodError, /undefined method `name' for/)
+ end
+ end
+
+ it "the #abbr method is used by '%Z' in #strftime" do
+ zone = TimeSpecs::TimezoneWithAbbr.new(name: "Asia/Colombo")
+ time = Time.new(2000, 1, 1, 12, 0, 0, zone)
+
+ time.strftime("%Z").should == "MMT"
+ end
+
+ # At loading marshaled data, a timezone name will be converted to a timezone object
+ # by find_timezone class method, if the method is defined.
+ # Similarly, that class method will be called when a timezone argument does not have
+ # the necessary methods mentioned above.
+ context "subject's class implements .find_timezone method" do
+ it "calls .find_timezone to build a time object at loading marshaled data" do
+ zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo")
+ time = TimeSpecs::TimeWithFindTimezone.new(2000, 1, 1, 12, 0, 0, zone)
+ time_loaded = Marshal.load(Marshal.dump(time))
+
+ time_loaded.zone.should be_kind_of TimeSpecs::TimezoneWithName
+ time_loaded.zone.name.should == "Asia/Colombo"
+ time_loaded.utc_offset.should == 5*3600+30*60
+ end
+
+ it "calls .find_timezone to build a time object if passed zone name as a timezone argument" do
+ time = TimeSpecs::TimeWithFindTimezone.new(2000, 1, 1, 12, 0, 0, "Asia/Colombo")
+ time.zone.should be_kind_of TimeSpecs::TimezoneWithName
+ time.zone.name.should == "Asia/Colombo"
+
+ time = TimeSpecs::TimeWithFindTimezone.new(2000, 1, 1, 12, 0, 0, "some invalid zone name")
+ time.zone.should be_kind_of TimeSpecs::TimezoneWithName
+ time.zone.name.should == "some invalid zone name"
+ end
+
+ it "does not call .find_timezone if passed any not string/numeric/timezone timezone argument" do
+ [Object.new, [], {}, :"some zone"].each do |zone|
+ -> {
+ TimeSpecs::TimeWithFindTimezone.new(2000, 1, 1, 12, 0, 0, zone)
+ }.should raise_error(TypeError, /can't convert \w+ into an exact number/)
+ end
+ end
+ end
+
+ ruby_version_is '3.1' do # https://bugs.ruby-lang.org/issues/17485
+ describe ":in keyword argument" do
+ it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: "+05:00")
+
+ time.utc_offset.should == 5*60*60
+ time.zone.should == nil
+
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: "-09:00")
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == nil
+ end
+
+ it "could be UTC offset as a number of seconds" do
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: 5*60*60)
+
+ time.utc_offset.should == 5*60*60
+ time.zone.should == nil
+
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: -9*60*60)
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == nil
+ end
+
+ it "could be a timezone object" do
+ zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo")
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: zone)
+
+ time.utc_offset.should == 5*3600+30*60
+ time.zone.should == zone
+
+ zone = TimeSpecs::TimezoneWithName.new(name: "PST")
+ time = Time.new(2000, 1, 1, 12, 0, 0, in: zone)
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == zone
+ end
+
+ it "raises ArgumentError if format is invalid" do
+ -> { Time.new(2000, 1, 1, 12, 0, 0, in: "+09:99") }.should raise_error(ArgumentError)
+ -> { Time.new(2000, 1, 1, 12, 0, 0, in: "ABC") }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if two offset arguments are given" do
+ -> { Time.new(2000, 1, 1, 12, 0, 0, "+05:00", in: "+05:00") }.should raise_error(ArgumentError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/time/now_spec.rb b/spec/ruby/core/time/now_spec.rb
new file mode 100644
index 0000000000..2b2e53a17c
--- /dev/null
+++ b/spec/ruby/core/time/now_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'shared/now'
+
+describe "Time.now" do
+ it_behaves_like :time_now, :now
+
+ describe ":in keyword argument" do
+ it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do
+ time = Time.now(in: "+05:00")
+
+ time.utc_offset.should == 5*60*60
+ time.zone.should == nil
+
+ time = Time.now(in: "-09:00")
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == nil
+ end
+
+ it "could be UTC offset as a number of seconds" do
+ time = Time.now(in: 5*60*60)
+
+ time.utc_offset.should == 5*60*60
+ time.zone.should == nil
+
+ time = Time.now(in: -9*60*60)
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == nil
+ end
+
+ it "could be a timezone object" do
+ zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo")
+ time = Time.now(in: zone)
+
+ time.utc_offset.should == 5*3600+30*60
+ time.zone.should == zone
+
+ zone = TimeSpecs::TimezoneWithName.new(name: "PST")
+ time = Time.now(in: zone)
+
+ time.utc_offset.should == -9*60*60
+ time.zone.should == zone
+ end
+
+ it "raises ArgumentError if format is invalid" do
+ -> { Time.now(in: "+09:99") }.should raise_error(ArgumentError)
+ -> { Time.now(in: "ABC") }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/time/nsec_spec.rb b/spec/ruby/core/time/nsec_spec.rb
new file mode 100644
index 0000000000..9338eb435a
--- /dev/null
+++ b/spec/ruby/core/time/nsec_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Time#nsec" do
+ it "returns 0 for a Time constructed with a whole number of seconds" do
+ Time.at(100).nsec.should == 0
+ end
+
+ it "returns the nanoseconds part of a Time constructed with a Float number of seconds" do
+ Time.at(10.75).nsec.should == 750_000_000
+ end
+
+ it "returns the nanoseconds part of a Time constructed with an Integer number of microseconds" do
+ Time.at(0, 999_999).nsec.should == 999_999_000
+ end
+
+ it "returns the nanoseconds part of a Time constructed with an Float number of microseconds" do
+ Time.at(0, 3.75).nsec.should == 3750
+ end
+
+ it "returns the nanoseconds part of a Time constructed with a Rational number of seconds" do
+ Time.at(Rational(3, 2)).nsec.should == 500_000_000
+ end
+
+ it "returns the nanoseconds part of a Time constructed with an Rational number of microseconds" do
+ Time.at(0, Rational(99, 10)).nsec.should == 9900
+ end
+
+ it "returns a positive value for dates before the epoch" do
+ Time.utc(1969, 11, 12, 13, 18, 57, 404240).nsec.should == 404240000
+ end
+end
diff --git a/spec/ruby/core/time/plus_spec.rb b/spec/ruby/core/time/plus_spec.rb
new file mode 100644
index 0000000000..642393b615
--- /dev/null
+++ b/spec/ruby/core/time/plus_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Time#+" do
+ it "increments the time by the specified amount" do
+ (Time.at(0) + 100).should == Time.at(100)
+ end
+
+ it "is a commutative operator" do
+ (Time.at(1.1) + 0.9).should == Time.at(0.9) + 1.1
+ end
+
+ it "adds a negative Float" do
+ t = Time.at(100) + -1.3
+ t.usec.should == 699999
+ t.to_i.should == 98
+ end
+
+ it "raises a TypeError if given argument is a coercible String" do
+ -> { Time.now + "1" }.should raise_error(TypeError)
+ -> { Time.now + "0.1" }.should raise_error(TypeError)
+ -> { Time.now + "1/3" }.should raise_error(TypeError)
+ end
+
+ it "increments the time by the specified amount as rational numbers" do
+ (Time.at(Rational(11, 10)) + Rational(9, 10)).should == Time.at(2)
+ end
+
+ it "accepts arguments that can be coerced into Rational" do
+ (obj = mock_numeric('10')).should_receive(:to_r).and_return(Rational(10))
+ (Time.at(100) + obj).should == Time.at(110)
+ end
+
+ it "raises TypeError on argument that can't be coerced into Rational" do
+ -> { Time.now + Object.new }.should raise_error(TypeError)
+ -> { Time.now + "stuff" }.should raise_error(TypeError)
+ end
+
+ it "returns a UTC time if self is UTC" do
+ (Time.utc(2012) + 10).should.utc?
+ end
+
+ it "returns a non-UTC time if self is non-UTC" do
+ (Time.local(2012) + 10).should_not.utc?
+ end
+
+ it "returns a time with the same fixed offset as self" do
+ (Time.new(2012, 1, 1, 0, 0, 0, 3600) + 10).utc_offset.should == 3600
+ end
+
+ it "preserves time zone" do
+ time_with_zone = Time.now.utc
+ time_with_zone.zone.should == (time_with_zone + 1).zone
+
+ time_with_zone = Time.now
+ time_with_zone.zone.should == (time_with_zone + 1).zone
+ end
+
+ context "zone is a timezone object" do
+ it "preserves time zone" do
+ zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60))
+ time = Time.new(2012, 1, 1, 12, 0, 0, zone) + 1
+
+ time.zone.should == zone
+ end
+ end
+
+ it "does not return a subclass instance" do
+ c = Class.new(Time)
+ x = c.now + 1
+ x.should be_an_instance_of(Time)
+ end
+
+ it "raises TypeError on Time argument" do
+ -> { Time.now + Time.now }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError on nil argument" do
+ -> { Time.now + nil }.should raise_error(TypeError)
+ end
+
+ #see [ruby-dev:38446]
+ it "tracks microseconds" do
+ time = Time.at(0)
+ time += Rational(123_456, 1_000_000)
+ time.usec.should == 123_456
+ time += Rational(654_321, 1_000_000)
+ time.usec.should == 777_777
+ end
+
+ it "tracks nanoseconds" do
+ time = Time.at(0)
+ time += Rational(123_456_789, 1_000_000_000)
+ time.nsec.should == 123_456_789
+ time += Rational(876_543_210, 1_000_000_000)
+ time.nsec.should == 999_999_999
+ end
+
+ it "maintains precision" do
+ t = Time.at(0) + Rational(8_999_999_999_999_999, 1_000_000_000_000_000)
+ t.should_not == Time.at(9)
+ end
+
+ it "maintains microseconds precision" do
+ time = Time.at(0) + Rational(8_999_999_999_999_999, 1_000_000_000_000_000)
+ time.usec.should == 999_999
+ end
+
+ it "maintains nanoseconds precision" do
+ time = Time.at(0) + Rational(8_999_999_999_999_999, 1_000_000_000_000_000)
+ time.nsec.should == 999_999_999
+ end
+
+ it "maintains subseconds precision" do
+ time = Time.at(0) + Rational(8_999_999_999_999_999, 1_000_000_000_000_000)
+ time.subsec.should == Rational(999_999_999_999_999, 1_000_000_000_000_000)
+ end
+end
diff --git a/spec/ruby/core/time/round_spec.rb b/spec/ruby/core/time/round_spec.rb
new file mode 100644
index 0000000000..0cbed04ade
--- /dev/null
+++ b/spec/ruby/core/time/round_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "Time#round" do
+ before do
+ @time = Time.utc(2010, 3, 30, 5, 43, "25.123456789".to_r)
+ end
+
+ it "defaults to rounding to 0 places" do
+ @time.round.should == Time.utc(2010, 3, 30, 5, 43, 25.to_r)
+ end
+
+ it "rounds to 0 decimal places with an explicit argument" do
+ @time.round(0).should == Time.utc(2010, 3, 30, 5, 43, 25.to_r)
+ end
+
+ it "rounds to 7 decimal places with an explicit argument" do
+ @time.round(7).should == Time.utc(2010, 3, 30, 5, 43, "25.1234568".to_r)
+ end
+
+ it "returns an instance of Time, even if #round is called on a subclass" do
+ subclass = Class.new(Time)
+ instance = subclass.at(0)
+ instance.class.should equal subclass
+ instance.round.should be_an_instance_of(Time)
+ end
+
+ it "copies own timezone to the returning value" do
+ @time.zone.should == @time.round.zone
+
+ with_timezone "JST-9" do
+ time = Time.at 0, 1
+ time.zone.should == time.round.zone
+ end
+ end
+end
diff --git a/spec/ruby/core/time/saturday_spec.rb b/spec/ruby/core/time/saturday_spec.rb
new file mode 100644
index 0000000000..0e51407366
--- /dev/null
+++ b/spec/ruby/core/time/saturday_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Time#saturday?" do
+ it "returns true if time represents Saturday" do
+ Time.local(2000, 1, 1).should.saturday?
+ end
+
+ it "returns false if time doesn't represent Saturday" do
+ Time.local(2000, 1, 2).should_not.saturday?
+ end
+end
diff --git a/spec/ruby/core/time/sec_spec.rb b/spec/ruby/core/time/sec_spec.rb
new file mode 100644
index 0000000000..73fc5ce1fc
--- /dev/null
+++ b/spec/ruby/core/time/sec_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Time#sec" do
+ it "returns the second of the minute(0..60) for time" do
+ Time.at(0).sec.should == 0
+ end
+end
diff --git a/spec/ruby/core/time/shared/asctime.rb b/spec/ruby/core/time/shared/asctime.rb
new file mode 100644
index 0000000000..d096666863
--- /dev/null
+++ b/spec/ruby/core/time/shared/asctime.rb
@@ -0,0 +1,6 @@
+describe :time_asctime, shared: true do
+ it "returns a canonical string representation of time" do
+ t = Time.now
+ t.send(@method).should == t.strftime("%a %b %e %H:%M:%S %Y")
+ end
+end
diff --git a/spec/ruby/core/time/shared/day.rb b/spec/ruby/core/time/shared/day.rb
new file mode 100644
index 0000000000..472dc959c1
--- /dev/null
+++ b/spec/ruby/core/time/shared/day.rb
@@ -0,0 +1,15 @@
+describe :time_day, shared: true do
+ it "returns the day of the month (1..n) for a local Time" do
+ with_timezone("CET", 1) do
+ Time.local(1970, 1, 1).send(@method).should == 1
+ end
+ end
+
+ it "returns the day of the month for a UTC Time" do
+ Time.utc(1970, 1, 1).send(@method).should == 1
+ end
+
+ it "returns the day of the month for a Time with a fixed offset" do
+ Time.new(2012, 1, 1, 0, 0, 0, -3600).send(@method).should == 1
+ end
+end
diff --git a/spec/ruby/core/time/shared/getgm.rb b/spec/ruby/core/time/shared/getgm.rb
new file mode 100644
index 0000000000..3576365772
--- /dev/null
+++ b/spec/ruby/core/time/shared/getgm.rb
@@ -0,0 +1,9 @@
+describe :time_getgm, shared: true do
+ it "returns a new time which is the utc representation of time" do
+ # Testing with America/Regina here because it doesn't have DST.
+ with_timezone("CST", -6) do
+ t = Time.local(2007, 1, 9, 6, 0, 0)
+ t.send(@method).should == Time.gm(2007, 1, 9, 12, 0, 0)
+ end
+ end
+end
diff --git a/spec/ruby/core/time/shared/gm.rb b/spec/ruby/core/time/shared/gm.rb
new file mode 100644
index 0000000000..0ee602c837
--- /dev/null
+++ b/spec/ruby/core/time/shared/gm.rb
@@ -0,0 +1,70 @@
+describe :time_gm, shared: true do
+ it "creates a time based on given values, interpreted as UTC (GMT)" do
+ Time.send(@method, 2000,"jan",1,20,15,1).inspect.should == "2000-01-01 20:15:01 UTC"
+ end
+
+ it "creates a time based on given C-style gmtime arguments, interpreted as UTC (GMT)" do
+ time = Time.send(@method, 1, 15, 20, 1, 1, 2000, :ignored, :ignored, :ignored, :ignored)
+ time.inspect.should == "2000-01-01 20:15:01 UTC"
+ end
+
+ it "interprets pre-Gregorian reform dates using Gregorian proleptic calendar" do
+ Time.send(@method, 1582, 10, 4, 12).to_i.should == -12220200000 # 2299150j
+ end
+
+ it "interprets Julian-Gregorian gap dates using Gregorian proleptic calendar" do
+ Time.send(@method, 1582, 10, 14, 12).to_i.should == -12219336000 # 2299160j
+ end
+
+ it "interprets post-Gregorian reform dates using Gregorian calendar" do
+ Time.send(@method, 1582, 10, 15, 12).to_i.should == -12219249600 # 2299161j
+ end
+
+ it "handles fractional usec close to rounding limit" do
+ time = Time.send(@method, 2000, 1, 1, 12, 30, 0, 9999r/10000)
+
+ time.usec.should == 0
+ time.nsec.should == 999
+ end
+
+ guard -> {
+ with_timezone 'right/UTC' do
+ (Time.gm(1972, 6, 30, 23, 59, 59) + 1).sec == 60
+ end
+ } do
+ it "handles real leap seconds in zone 'right/UTC'" do
+ with_timezone 'right/UTC' do
+ time = Time.send(@method, 1972, 6, 30, 23, 59, 60)
+
+ time.sec.should == 60
+ time.min.should == 59
+ time.hour.should == 23
+ time.day.should == 30
+ time.month.should == 6
+ end
+ end
+ end
+
+ it "handles bad leap seconds by carrying values forward" do
+ with_timezone 'UTC' do
+ time = Time.send(@method, 2017, 7, 5, 23, 59, 60)
+ time.sec.should == 0
+ time.min.should == 0
+ time.hour.should == 0
+ time.day.should == 6
+ time.month.should == 7
+ end
+ end
+
+ it "handles a value of 60 for seconds by carrying values forward in zone 'UTC'" do
+ with_timezone 'UTC' do
+ time = Time.send(@method, 1972, 6, 30, 23, 59, 60)
+
+ time.sec.should == 0
+ time.min.should == 0
+ time.hour.should == 0
+ time.day.should == 1
+ time.month.should == 7
+ end
+ end
+end
diff --git a/spec/ruby/core/time/shared/gmt_offset.rb b/spec/ruby/core/time/shared/gmt_offset.rb
new file mode 100644
index 0000000000..839566c249
--- /dev/null
+++ b/spec/ruby/core/time/shared/gmt_offset.rb
@@ -0,0 +1,59 @@
+describe :time_gmt_offset, shared: true do
+ it "returns the offset in seconds between the timezone of time and UTC" do
+ with_timezone("AST", 3) do
+ Time.new.send(@method).should == 10800
+ end
+ end
+
+ it "returns 0 when the date is UTC" do
+ with_timezone("AST", 3) do
+ Time.new.utc.send(@method).should == 0
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns the correct offset for US Eastern time zone around daylight savings time change" do
+ # "2010-03-14 01:59:59 -0500" + 1 ==> "2010-03-14 03:00:00 -0400"
+ with_timezone("EST5EDT") do
+ t = Time.local(2010,3,14,1,59,59)
+ t.send(@method).should == -5*60*60
+ (t + 1).send(@method).should == -4*60*60
+ end
+ end
+
+ it "returns the correct offset for Hawaii around daylight savings time change" do
+ # "2010-03-14 01:59:59 -1000" + 1 ==> "2010-03-14 02:00:00 -1000"
+ with_timezone("Pacific/Honolulu") do
+ t = Time.local(2010,3,14,1,59,59)
+ t.send(@method).should == -10*60*60
+ (t + 1).send(@method).should == -10*60*60
+ end
+ end
+
+ it "returns the correct offset for New Zealand around daylight savings time change" do
+ # "2010-04-04 02:59:59 +1300" + 1 ==> "2010-04-04 02:00:00 +1200"
+ with_timezone("Pacific/Auckland") do
+ t = Time.local(2010,4,4,1,59,59) + (60 * 60)
+ t.send(@method).should == 13*60*60
+ (t + 1).send(@method).should == 12*60*60
+ end
+ end
+ end
+
+ it "returns offset as Rational" do
+ Time.new(2010,4,4,1,59,59,7245).send(@method).should == 7245
+ Time.new(2010,4,4,1,59,59,7245.5).send(@method).should == Rational(14491,2)
+ end
+
+ context 'given positive offset' do
+ it 'returns a positive offset' do
+ Time.new(2013,3,17,nil,nil,nil,"+03:00").send(@method).should == 10800
+ end
+ end
+
+ context 'given negative offset' do
+ it 'returns a negative offset' do
+ Time.new(2013,3,17,nil,nil,nil,"-03:00").send(@method).should == -10800
+ end
+ end
+end
diff --git a/spec/ruby/core/time/shared/gmtime.rb b/spec/ruby/core/time/shared/gmtime.rb
new file mode 100644
index 0000000000..bae19da462
--- /dev/null
+++ b/spec/ruby/core/time/shared/gmtime.rb
@@ -0,0 +1,33 @@
+describe :time_gmtime, shared: true do
+ it "converts self to UTC, modifying the receiver" do
+ # Testing with America/Regina here because it doesn't have DST.
+ with_timezone("CST", -6) do
+ t = Time.local(2007, 1, 9, 6, 0, 0)
+ t.send(@method)
+ t.should == Time.gm(2007, 1, 9, 12, 0, 0)
+ end
+ end
+
+ it "returns self" do
+ with_timezone("CST", -6) do
+ t = Time.local(2007, 1, 9, 12, 0, 0)
+ t.send(@method).should equal(t)
+ end
+ end
+
+ describe "on a frozen time" do
+ it "does not raise an error if already in UTC" do
+ time = Time.gm(2007, 1, 9, 12, 0, 0)
+ time.freeze
+ time.send(@method).should equal(time)
+ end
+
+ it "raises a FrozenError if the time is not UTC" do
+ with_timezone("CST", -6) do
+ time = Time.now
+ time.freeze
+ -> { time.send(@method) }.should raise_error(FrozenError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/time/shared/inspect.rb b/spec/ruby/core/time/shared/inspect.rb
new file mode 100644
index 0000000000..4133671924
--- /dev/null
+++ b/spec/ruby/core/time/shared/inspect.rb
@@ -0,0 +1,21 @@
+# -*- encoding: us-ascii -*-
+
+describe :inspect, shared: true do
+ it "formats the local time following the pattern 'yyyy-MM-dd HH:mm:ss Z'" do
+ with_timezone("PST", +1) do
+ Time.local(2000, 1, 1, 20, 15, 1).send(@method).should == "2000-01-01 20:15:01 +0100"
+ end
+ end
+
+ it "formats the UTC time following the pattern 'yyyy-MM-dd HH:mm:ss UTC'" do
+ Time.utc(2000, 1, 1, 20, 15, 1).send(@method).should == "2000-01-01 20:15:01 UTC"
+ end
+
+ it "formats the fixed offset time following the pattern 'yyyy-MM-dd HH:mm:ss +/-HHMM'" do
+ Time.new(2000, 1, 1, 20, 15, 01, 3600).send(@method).should == "2000-01-01 20:15:01 +0100"
+ end
+
+ it "returns a US-ASCII encoded string" do
+ Time.now.send(@method).encoding.should equal(Encoding::US_ASCII)
+ end
+end
diff --git a/spec/ruby/core/time/shared/isdst.rb b/spec/ruby/core/time/shared/isdst.rb
new file mode 100644
index 0000000000..bc6d139230
--- /dev/null
+++ b/spec/ruby/core/time/shared/isdst.rb
@@ -0,0 +1,8 @@
+describe :time_isdst, shared: true do
+ it "dst? returns whether time is during daylight saving time" do
+ with_timezone("America/Los_Angeles") do
+ Time.local(2007, 9, 9, 0, 0, 0).send(@method).should == true
+ Time.local(2007, 1, 9, 0, 0, 0).send(@method).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/time/shared/local.rb b/spec/ruby/core/time/shared/local.rb
new file mode 100644
index 0000000000..068e314999
--- /dev/null
+++ b/spec/ruby/core/time/shared/local.rb
@@ -0,0 +1,42 @@
+describe :time_local, shared: true do
+ it "creates a time based on given values, interpreted in the local time zone" do
+ with_timezone("PST", -8) do
+ Time.send(@method, 2000, "jan", 1, 20, 15, 1).to_a.should ==
+ [1, 15, 20, 1, 1, 2000, 6, 1, false, "PST"]
+ end
+ end
+
+ platform_is_not :windows do
+ it "uses the 'CET' timezone with TZ=Europe/Amsterdam in 1970" do
+ with_timezone("Europe/Amsterdam") do
+ Time.send(@method, 1970, 5, 16).to_a.should ==
+ [0, 0, 0, 16, 5, 1970, 6, 136, false, "CET"]
+ end
+ end
+ end
+end
+
+describe :time_local_10_arg, shared: true do
+ it "creates a time based on given C-style gmtime arguments, interpreted in the local time zone" do
+ with_timezone("PST", -8) do
+ Time.send(@method, 1, 15, 20, 1, 1, 2000, :ignored, :ignored, :ignored, :ignored).to_a.should ==
+ [1, 15, 20, 1, 1, 2000, 6, 1, false, "PST"]
+ end
+ end
+
+ platform_is_not :windows do
+ it "creates the correct time just before dst change" do
+ with_timezone("America/New_York") do
+ time = Time.send(@method, 0, 30, 1, 30, 10, 2005, 0, 0, true, ENV['TZ'])
+ time.utc_offset.should == -4 * 3600
+ end
+ end
+
+ it "creates the correct time just after dst change" do
+ with_timezone("America/New_York") do
+ time = Time.send(@method, 0, 30, 1, 30, 10, 2005, 0, 0, false, ENV['TZ'])
+ time.utc_offset.should == -5 * 3600
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/time/shared/month.rb b/spec/ruby/core/time/shared/month.rb
new file mode 100644
index 0000000000..31ca679557
--- /dev/null
+++ b/spec/ruby/core/time/shared/month.rb
@@ -0,0 +1,15 @@
+describe :time_month, shared: true do
+ it "returns the month of the year for a local Time" do
+ with_timezone("CET", 1) do
+ Time.local(1970, 1).send(@method).should == 1
+ end
+ end
+
+ it "returns the month of the year for a UTC Time" do
+ Time.utc(1970, 1).send(@method).should == 1
+ end
+
+ it "returns the four digit year for a Time with a fixed offset" do
+ Time.new(2012, 1, 1, 0, 0, 0, -3600).send(@method).should == 1
+ end
+end
diff --git a/spec/ruby/core/time/shared/now.rb b/spec/ruby/core/time/shared/now.rb
new file mode 100644
index 0000000000..f4018d72f4
--- /dev/null
+++ b/spec/ruby/core/time/shared/now.rb
@@ -0,0 +1,33 @@
+require_relative '../fixtures/classes'
+
+describe :time_now, shared: true do
+ it "creates a subclass instance if called on a subclass" do
+ TimeSpecs::SubTime.send(@method).should be_an_instance_of(TimeSpecs::SubTime)
+ TimeSpecs::MethodHolder.send(@method).should be_an_instance_of(Time)
+ end
+
+ it "sets the current time" do
+ now = TimeSpecs::MethodHolder.send(@method)
+ now.to_f.should be_close(Process.clock_gettime(Process::CLOCK_REALTIME), TIME_TOLERANCE)
+ end
+
+ it "uses the local timezone" do
+ with_timezone("PDT", -8) do
+ now = TimeSpecs::MethodHolder.send(@method)
+ now.utc_offset.should == (-8 * 60 * 60)
+ end
+ end
+
+ it "has at least microsecond precision" do
+ # The clock should not be less accurate than expected (times should
+ # not all be a multiple of the next precision up, assuming precisions
+ # are multiples of ten.)
+ expected = 1_000
+ t = 0
+ 10_000.times.find do
+ t = Time.now.nsec
+ t % (expected * 10) != 0
+ end
+ (t % (expected * 10)).should != 0
+ end
+end
diff --git a/spec/ruby/core/time/shared/time_params.rb b/spec/ruby/core/time/shared/time_params.rb
new file mode 100644
index 0000000000..b6a6c88c8e
--- /dev/null
+++ b/spec/ruby/core/time/shared/time_params.rb
@@ -0,0 +1,267 @@
+describe :time_params, shared: true do
+ it "accepts 1 argument (year)" do
+ Time.send(@method, 2000).should ==
+ Time.send(@method, 2000, 1, 1, 0, 0, 0)
+ end
+
+ it "accepts 2 arguments (year, month)" do
+ Time.send(@method, 2000, 2).should ==
+ Time.send(@method, 2000, 2, 1, 0, 0, 0)
+ end
+
+ it "accepts 3 arguments (year, month, day)" do
+ Time.send(@method, 2000, 2, 3).should ==
+ Time.send(@method, 2000, 2, 3, 0, 0, 0)
+ end
+
+ it "accepts 4 arguments (year, month, day, hour)" do
+ Time.send(@method, 2000, 2, 3, 4).should ==
+ Time.send(@method, 2000, 2, 3, 4, 0, 0)
+ end
+
+ it "accepts 5 arguments (year, month, day, hour, minute)" do
+ Time.send(@method, 2000, 2, 3, 4, 5).should ==
+ Time.send(@method, 2000, 2, 3, 4, 5, 0)
+ end
+
+ it "accepts a too big day of the month by going to the next month" do
+ Time.send(@method, 1999, 2, 31).should ==
+ Time.send(@method, 1999, 3, 3)
+ end
+
+ it "raises a TypeError if the year is nil" do
+ -> { Time.send(@method, nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts nil month, day, hour, minute, and second" do
+ Time.send(@method, 2000, nil, nil, nil, nil, nil).should ==
+ Time.send(@method, 2000)
+ end
+
+ it "handles a String year" do
+ Time.send(@method, "2000").should ==
+ Time.send(@method, 2000)
+ end
+
+ it "coerces the year with #to_int" do
+ m = mock(:int)
+ m.should_receive(:to_int).and_return(1)
+ Time.send(@method, m).should == Time.send(@method, 1)
+ end
+
+ it "handles a String month given as a numeral" do
+ Time.send(@method, 2000, "12").should ==
+ Time.send(@method, 2000, 12)
+ end
+
+ it "handles a String month given as a short month name" do
+ Time.send(@method, 2000, "dec").should ==
+ Time.send(@method, 2000, 12)
+ end
+
+ it "coerces the month with #to_str" do
+ (obj = mock('12')).should_receive(:to_str).and_return("12")
+ Time.send(@method, 2008, obj).should ==
+ Time.send(@method, 2008, 12)
+ end
+
+ it "coerces the month with #to_int" do
+ m = mock(:int)
+ m.should_receive(:to_int).and_return(1)
+ Time.send(@method, 2008, m).should == Time.send(@method, 2008, 1)
+ end
+
+ it "handles a String day" do
+ Time.send(@method, 2000, 12, "15").should ==
+ Time.send(@method, 2000, 12, 15)
+ end
+
+ it "coerces the day with #to_int" do
+ m = mock(:int)
+ m.should_receive(:to_int).and_return(1)
+ Time.send(@method, 2008, 1, m).should == Time.send(@method, 2008, 1, 1)
+ end
+
+ it "handles a String hour" do
+ Time.send(@method, 2000, 12, 1, "5").should ==
+ Time.send(@method, 2000, 12, 1, 5)
+ end
+
+ it "coerces the hour with #to_int" do
+ m = mock(:int)
+ m.should_receive(:to_int).and_return(1)
+ Time.send(@method, 2008, 1, 1, m).should == Time.send(@method, 2008, 1, 1, 1)
+ end
+
+ it "handles a String minute" do
+ Time.send(@method, 2000, 12, 1, 1, "8").should ==
+ Time.send(@method, 2000, 12, 1, 1, 8)
+ end
+
+ it "coerces the minute with #to_int" do
+ m = mock(:int)
+ m.should_receive(:to_int).and_return(1)
+ Time.send(@method, 2008, 1, 1, 0, m).should == Time.send(@method, 2008, 1, 1, 0, 1)
+ end
+
+ it "handles a String second" do
+ Time.send(@method, 2000, 12, 1, 1, 1, "8").should ==
+ Time.send(@method, 2000, 12, 1, 1, 1, 8)
+ end
+
+ it "coerces the second with #to_int" do
+ m = mock(:int)
+ m.should_receive(:to_int).and_return(1)
+ Time.send(@method, 2008, 1, 1, 0, 0, m).should == Time.send(@method, 2008, 1, 1, 0, 0, 1)
+ end
+
+ it "interprets all numerals as base 10" do
+ Time.send(@method, "2000", "08", "08", "08", "08", "08").should == Time.send(@method, 2000, 8, 8, 8, 8, 8)
+ Time.send(@method, "2000", "09", "09", "09", "09", "09").should == Time.send(@method, 2000, 9, 9, 9, 9, 9)
+ end
+
+ it "handles fractional seconds as a Float" do
+ t = Time.send(@method, 2000, 1, 1, 20, 15, 1.75)
+ t.sec.should == 1
+ t.usec.should == 750000
+ end
+
+ it "handles fractional seconds as a Rational" do
+ t = Time.send(@method, 2000, 1, 1, 20, 15, Rational(99, 10))
+ t.sec.should == 9
+ t.usec.should == 900000
+ end
+
+ it "handles years from 0 as such" do
+ 0.upto(2100) do |year|
+ t = Time.send(@method, year)
+ t.year.should == year
+ end
+ end
+
+ it "accepts various year ranges" do
+ Time.send(@method, 1801, 12, 31, 23, 59, 59).wday.should == 4
+ Time.send(@method, 3000, 12, 31, 23, 59, 59).wday.should == 3
+ end
+
+ it "raises an ArgumentError for out of range month" do
+ # For some reason MRI uses a different message for month in 13-15 and month>=16
+ -> {
+ Time.send(@method, 2008, 16, 31, 23, 59, 59)
+ }.should raise_error(ArgumentError, /(mon|argument) out of range/)
+ end
+
+ it "raises an ArgumentError for out of range day" do
+ -> {
+ Time.send(@method, 2008, 12, 32, 23, 59, 59)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for out of range hour" do
+ -> {
+ Time.send(@method, 2008, 12, 31, 25, 59, 59)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for out of range minute" do
+ -> {
+ Time.send(@method, 2008, 12, 31, 23, 61, 59)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError for out of range second" do
+ # For some reason MRI uses different messages for seconds 61-63 and seconds >= 64
+ -> {
+ Time.send(@method, 2008, 12, 31, 23, 59, 61)
+ }.should raise_error(ArgumentError, /(sec|argument) out of range/)
+ -> {
+ Time.send(@method, 2008, 12, 31, 23, 59, -1)
+ }.should raise_error(ArgumentError, "argument out of range")
+ end
+
+ it "raises ArgumentError when given 9 arguments" do
+ -> { Time.send(@method, *[0]*9) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError when given 11 arguments" do
+ -> { Time.send(@method, *[0]*11) }.should raise_error(ArgumentError)
+ end
+
+ it "returns subclass instances" do
+ c = Class.new(Time)
+ c.send(@method, 2008, "12").should be_an_instance_of(c)
+ end
+end
+
+describe :time_params_10_arg, shared: true do
+ it "handles string arguments" do
+ Time.send(@method, "1", "15", "20", "1", "1", "2000", :ignored, :ignored,
+ :ignored, :ignored).should ==
+ Time.send(@method, 1, 15, 20, 1, 1, 2000, :ignored, :ignored, :ignored, :ignored)
+ end
+
+ it "handles float arguments" do
+ Time.send(@method, 1.0, 15.0, 20.0, 1.0, 1.0, 2000.0, :ignored, :ignored,
+ :ignored, :ignored).should ==
+ Time.send(@method, 1, 15, 20, 1, 1, 2000, :ignored, :ignored, :ignored, :ignored)
+ end
+
+ it "raises an ArgumentError for out of range values" do
+ -> {
+ Time.send(@method, 61, 59, 23, 31, 12, 2008, :ignored, :ignored, :ignored, :ignored)
+ }.should raise_error(ArgumentError) # sec
+
+ -> {
+ Time.send(@method, 59, 61, 23, 31, 12, 2008, :ignored, :ignored, :ignored, :ignored)
+ }.should raise_error(ArgumentError) # min
+
+ -> {
+ Time.send(@method, 59, 59, 25, 31, 12, 2008, :ignored, :ignored, :ignored, :ignored)
+ }.should raise_error(ArgumentError) # hour
+
+ -> {
+ Time.send(@method, 59, 59, 23, 32, 12, 2008, :ignored, :ignored, :ignored, :ignored)
+ }.should raise_error(ArgumentError) # day
+
+ -> {
+ Time.send(@method, 59, 59, 23, 31, 13, 2008, :ignored, :ignored, :ignored, :ignored)
+ }.should raise_error(ArgumentError) # month
+ end
+end
+
+describe :time_params_microseconds, shared: true do
+ it "handles microseconds" do
+ t = Time.send(@method, 2000, 1, 1, 20, 15, 1, 123)
+ t.usec.should == 123
+ end
+
+ it "raises an ArgumentError for out of range microsecond" do
+ -> { Time.send(@method, 2000, 1, 1, 20, 15, 1, 1000000) }.should raise_error(ArgumentError)
+ end
+
+ it "handles fractional microseconds as a Float" do
+ t = Time.send(@method, 2000, 1, 1, 20, 15, 1, 1.75)
+ t.usec.should == 1
+ t.nsec.should == 1750
+ end
+
+ it "handles fractional microseconds as a Rational" do
+ t = Time.send(@method, 2000, 1, 1, 20, 15, 1, Rational(99, 10))
+ t.usec.should == 9
+ t.nsec.should == 9900
+ end
+
+ it "ignores fractional seconds if a passed whole number of microseconds" do
+ t = Time.send(@method, 2000, 1, 1, 20, 15, 1.75, 2)
+ t.sec.should == 1
+ t.usec.should == 2
+ t.nsec.should == 2000
+ end
+
+ it "ignores fractional seconds if a passed fractional number of microseconds" do
+ t = Time.send(@method, 2000, 1, 1, 20, 15, 1.75, Rational(99, 10))
+ t.sec.should == 1
+ t.usec.should == 9
+ t.nsec.should == 9900
+ end
+end
diff --git a/spec/ruby/core/time/shared/to_i.rb b/spec/ruby/core/time/shared/to_i.rb
new file mode 100644
index 0000000000..06c966b708
--- /dev/null
+++ b/spec/ruby/core/time/shared/to_i.rb
@@ -0,0 +1,16 @@
+describe :time_to_i, shared: true do
+ it "returns the value of time as an integer number of seconds since epoch" do
+ Time.at(0).send(@method).should == 0
+ end
+
+ it "doesn't return an actual number of seconds in time" do
+ Time.at(65.5).send(@method).should == 65
+ end
+
+ it "rounds fractional seconds toward zero" do
+ t = Time.utc(1960, 1, 1, 0, 0, 0, 999_999)
+
+ t.to_f.to_i.should == -315619199
+ t.to_i.should == -315619200
+ end
+end
diff --git a/spec/ruby/core/time/strftime_spec.rb b/spec/ruby/core/time/strftime_spec.rb
new file mode 100644
index 0000000000..4cb300c916
--- /dev/null
+++ b/spec/ruby/core/time/strftime_spec.rb
@@ -0,0 +1,93 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+require_relative '../../shared/time/strftime_for_date'
+require_relative '../../shared/time/strftime_for_time'
+
+describe "Time#strftime" do
+ before :all do
+ @new_date = -> y, m, d { Time.gm(y,m,d) }
+ @new_time = -> *args { Time.gm(*args) }
+ @new_time_in_zone = -> zone, offset, *args {
+ with_timezone(zone, offset) do
+ Time.new(*args)
+ end
+ }
+ @new_time_with_offset = -> y, m, d, h, min, s, offset {
+ Time.new(y,m,d,h,min,s,offset)
+ }
+
+ @time = @new_time[2001, 2, 3, 4, 5, 6]
+ end
+
+ it_behaves_like :strftime_date, :strftime
+ it_behaves_like :strftime_time, :strftime
+
+ # Differences with date
+ it "requires an argument" do
+ -> { @time.strftime }.should raise_error(ArgumentError)
+ end
+
+ # %Z is zone name or empty for Time
+ it "should be able to show the timezone if available" do
+ @time.strftime("%Z").should == @time.zone
+ with_timezone("UTC", 0) do
+ Time.gm(2000).strftime("%Z").should == "UTC"
+ end
+
+ Time.new(2000, 1, 1, 0, 0, 0, 42).strftime("%Z").should == ""
+ end
+
+ # %v is %e-%^b-%Y for Time
+ it "should be able to show the commercial week" do
+ @time.strftime("%v").should == " 3-FEB-2001"
+ @time.strftime("%v").should == @time.strftime('%e-%^b-%Y')
+ end
+
+ # Date/DateTime round at creation time, but Time does it in strftime.
+ it "rounds an offset to the nearest second when formatting with %z" do
+ time = @new_time_with_offset[2012, 1, 1, 0, 0, 0, Rational(36645, 10)]
+ time.strftime("%::z").should == "+01:01:05"
+ end
+
+ ruby_version_is "3.1" do
+ it "supports RFC 3339 UTC for unknown offset local time, -0000, as %-z" do
+ time = Time.gm(2022)
+
+ time.strftime("%z").should == "+0000"
+ time.strftime("%-z").should == "-0000"
+ time.strftime("%-:z").should == "-00:00"
+ time.strftime("%-::z").should == "-00:00:00"
+ end
+
+ it "applies '-' flag to UTC time" do
+ time = Time.utc(2022)
+ time.strftime("%-z").should == "-0000"
+
+ time = Time.gm(2022)
+ time.strftime("%-z").should == "-0000"
+
+ time = Time.new(2022, 1, 1, 0, 0, 0, "Z")
+ time.strftime("%-z").should == "-0000"
+
+ time = Time.new(2022, 1, 1, 0, 0, 0, "-00:00")
+ time.strftime("%-z").should == "-0000"
+
+ time = Time.new(2022, 1, 1, 0, 0, 0, "+03:00").utc
+ time.strftime("%-z").should == "-0000"
+ end
+
+ it "ignores '-' flag for non-UTC time" do
+ time = Time.new(2022, 1, 1, 0, 0, 0, "+03:00")
+ time.strftime("%-z").should == "+0300"
+ end
+
+ it "works correctly with width, _ and 0 flags, and :" do
+ Time.now.utc.strftime("%-_10z").should == " -000"
+ Time.now.utc.strftime("%-10z").should == "-000000000"
+ Time.now.utc.strftime("%-010:z").should == "-000000:00"
+ Time.now.utc.strftime("%-_10:z").should == " -0:00"
+ Time.now.utc.strftime("%-_10::z").should == " -0:00:00"
+ end
+ end
+end
diff --git a/spec/ruby/core/time/subsec_spec.rb b/spec/ruby/core/time/subsec_spec.rb
new file mode 100644
index 0000000000..0f2c4eb856
--- /dev/null
+++ b/spec/ruby/core/time/subsec_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+describe "Time#subsec" do
+ it "returns 0 as an Integer for a Time with a whole number of seconds" do
+ Time.at(100).subsec.should eql(0)
+ end
+
+ it "returns the fractional seconds as a Rational for a Time constructed with a Rational number of seconds" do
+ Time.at(Rational(3, 2)).subsec.should eql(Rational(1, 2))
+ end
+
+ it "returns the fractional seconds as a Rational for a Time constructed with a Float number of seconds" do
+ Time.at(10.75).subsec.should eql(Rational(3, 4))
+ end
+
+ it "returns the fractional seconds as a Rational for a Time constructed with an Integer number of microseconds" do
+ Time.at(0, 999999).subsec.should eql(Rational(999999, 1000000))
+ end
+
+ it "returns the fractional seconds as a Rational for a Time constructed with an Rational number of microseconds" do
+ Time.at(0, Rational(9, 10)).subsec.should eql(Rational(9, 10000000))
+ end
+
+ it "returns the fractional seconds as a Rational for a Time constructed with an Float number of microseconds" do
+ Time.at(0, 0.75).subsec.should eql(Rational(3, 4000000))
+ end
+end
diff --git a/spec/ruby/core/time/succ_spec.rb b/spec/ruby/core/time/succ_spec.rb
new file mode 100644
index 0000000000..e8249059d1
--- /dev/null
+++ b/spec/ruby/core/time/succ_spec.rb
@@ -0,0 +1,39 @@
+ruby_version_is ""..."3.0" do
+ require_relative '../../spec_helper'
+ require_relative 'fixtures/classes'
+
+ describe "Time#succ" do
+ it "returns a new time one second later than time" do
+ suppress_warning {
+ @result = Time.at(100).succ
+ }
+
+ @result.should == Time.at(101)
+ end
+
+ it "returns a new instance" do
+ time = Time.at(100)
+
+ suppress_warning {
+ @result = time.succ
+ }
+
+ @result.should_not equal time
+ end
+
+ it "is obsolete" do
+ -> {
+ Time.at(100).succ
+ }.should complain(/Time#succ is obsolete/)
+ end
+
+ context "zone is a timezone object" do
+ it "preserves time zone" do
+ zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60))
+ time = Time.new(2012, 1, 1, 12, 0, 0, zone) - 1
+
+ time.zone.should == zone
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/time/sunday_spec.rb b/spec/ruby/core/time/sunday_spec.rb
new file mode 100644
index 0000000000..0d46421132
--- /dev/null
+++ b/spec/ruby/core/time/sunday_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Time#sunday?" do
+ it "returns true if time represents Sunday" do
+ Time.local(2000, 1, 2).should.sunday?
+ end
+
+ it "returns false if time doesn't represent Sunday" do
+ Time.local(2000, 1, 1).should_not.sunday?
+ end
+end
diff --git a/spec/ruby/core/time/thursday_spec.rb b/spec/ruby/core/time/thursday_spec.rb
new file mode 100644
index 0000000000..c11e79d2fa
--- /dev/null
+++ b/spec/ruby/core/time/thursday_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Time#thursday?" do
+ it "returns true if time represents Thursday" do
+ Time.local(2000, 1, 6).should.thursday?
+ end
+
+ it "returns false if time doesn't represent Thursday" do
+ Time.local(2000, 1, 1).should_not.thursday?
+ end
+end
diff --git a/spec/ruby/core/time/time_spec.rb b/spec/ruby/core/time/time_spec.rb
new file mode 100644
index 0000000000..b0803a7f21
--- /dev/null
+++ b/spec/ruby/core/time/time_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Time" do
+ it "includes Comparable" do
+ Time.include?(Comparable).should == true
+ end
+end
diff --git a/spec/ruby/core/time/to_a_spec.rb b/spec/ruby/core/time/to_a_spec.rb
new file mode 100644
index 0000000000..3728b8c526
--- /dev/null
+++ b/spec/ruby/core/time/to_a_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "Time#to_a" do
+ platform_is_not :windows do
+ it "returns a 10 element array representing the deconstructed time" do
+ # Testing with America/Regina here because it doesn't have DST.
+ with_timezone("America/Regina") do
+ Time.at(0).to_a.should == [0, 0, 18, 31, 12, 1969, 3, 365, false, "CST"]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/time/to_f_spec.rb b/spec/ruby/core/time/to_f_spec.rb
new file mode 100644
index 0000000000..6101dcf871
--- /dev/null
+++ b/spec/ruby/core/time/to_f_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Time#to_f" do
+ it "returns the float number of seconds + usecs since the epoch" do
+ Time.at(100, 100).to_f.should == 100.0001
+ end
+end
diff --git a/spec/ruby/core/time/to_i_spec.rb b/spec/ruby/core/time/to_i_spec.rb
new file mode 100644
index 0000000000..54929d1e18
--- /dev/null
+++ b/spec/ruby/core/time/to_i_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Time#to_i" do
+ it_behaves_like :time_to_i, :to_i
+end
diff --git a/spec/ruby/core/time/to_r_spec.rb b/spec/ruby/core/time/to_r_spec.rb
new file mode 100644
index 0000000000..6af2d9b7ea
--- /dev/null
+++ b/spec/ruby/core/time/to_r_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Time#to_r" do
+ it "returns the a Rational representing seconds and subseconds since the epoch" do
+ Time.at(Rational(11, 10)).to_r.should eql(Rational(11, 10))
+ end
+
+ it "returns a Rational even for a whole number of seconds" do
+ Time.at(2).to_r.should eql(Rational(2))
+ end
+end
diff --git a/spec/ruby/core/time/to_s_spec.rb b/spec/ruby/core/time/to_s_spec.rb
new file mode 100644
index 0000000000..ac6c0908ac
--- /dev/null
+++ b/spec/ruby/core/time/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/inspect'
+
+describe "Time#to_s" do
+ it_behaves_like :inspect, :to_s
+end
diff --git a/spec/ruby/core/time/tuesday_spec.rb b/spec/ruby/core/time/tuesday_spec.rb
new file mode 100644
index 0000000000..0e7b9e7506
--- /dev/null
+++ b/spec/ruby/core/time/tuesday_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Time#tuesday?" do
+ it "returns true if time represents Tuesday" do
+ Time.local(2000, 1, 4).should.tuesday?
+ end
+
+ it "returns false if time doesn't represent Tuesday" do
+ Time.local(2000, 1, 1).should_not.tuesday?
+ end
+end
diff --git a/spec/ruby/core/time/tv_nsec_spec.rb b/spec/ruby/core/time/tv_nsec_spec.rb
new file mode 100644
index 0000000000..feb7998b16
--- /dev/null
+++ b/spec/ruby/core/time/tv_nsec_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Time#tv_nsec" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/time/tv_sec_spec.rb b/spec/ruby/core/time/tv_sec_spec.rb
new file mode 100644
index 0000000000..f83e1fbfdd
--- /dev/null
+++ b/spec/ruby/core/time/tv_sec_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Time#tv_sec" do
+ it_behaves_like :time_to_i, :tv_sec
+end
diff --git a/spec/ruby/core/time/tv_usec_spec.rb b/spec/ruby/core/time/tv_usec_spec.rb
new file mode 100644
index 0000000000..f0de4f4a9c
--- /dev/null
+++ b/spec/ruby/core/time/tv_usec_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Time#tv_usec" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/time/usec_spec.rb b/spec/ruby/core/time/usec_spec.rb
new file mode 100644
index 0000000000..6ea52f5e79
--- /dev/null
+++ b/spec/ruby/core/time/usec_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "Time#usec" do
+ it "returns 0 for a Time constructed with a whole number of seconds" do
+ Time.at(100).usec.should == 0
+ end
+
+ it "returns the microseconds part of a Time constructed with a Float number of seconds" do
+ Time.at(10.75).usec.should == 750_000
+ end
+
+ it "returns the microseconds part of a Time constructed with an Integer number of microseconds" do
+ Time.at(0, 999_999).usec.should == 999_999
+ end
+
+ it "returns the microseconds part of a Time constructed with an Float number of microseconds > 1" do
+ Time.at(0, 3.75).usec.should == 3
+ end
+
+ it "returns 0 for a Time constructed with an Float number of microseconds < 1" do
+ Time.at(0, 0.75).usec.should == 0
+ end
+
+ it "returns the microseconds part of a Time constructed with a Rational number of seconds" do
+ Time.at(Rational(3, 2)).usec.should == 500_000
+ end
+
+ it "returns the microseconds part of a Time constructed with an Rational number of microseconds > 1" do
+ Time.at(0, Rational(99, 10)).usec.should == 9
+ end
+
+ it "returns 0 for a Time constructed with an Rational number of microseconds < 1" do
+ Time.at(0, Rational(9, 10)).usec.should == 0
+ end
+
+ it "returns the microseconds for time created by Time#local" do
+ Time.local(1,2,3,4,5,Rational(6.78)).usec.should == 780000
+ end
+
+ it "returns a positive value for dates before the epoch" do
+ Time.utc(1969, 11, 12, 13, 18, 57, 404240).usec.should == 404240
+ end
+end
diff --git a/spec/ruby/core/time/utc_offset_spec.rb b/spec/ruby/core/time/utc_offset_spec.rb
new file mode 100644
index 0000000000..17c031b8c6
--- /dev/null
+++ b/spec/ruby/core/time/utc_offset_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/gmt_offset'
+
+describe "Time#utc_offset" do
+ it_behaves_like :time_gmt_offset, :utc_offset
+end
diff --git a/spec/ruby/core/time/utc_spec.rb b/spec/ruby/core/time/utc_spec.rb
new file mode 100644
index 0000000000..809accc809
--- /dev/null
+++ b/spec/ruby/core/time/utc_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative 'shared/gm'
+require_relative 'shared/gmtime'
+require_relative 'shared/time_params'
+
+describe "Time#utc?" do
+ it "returns true only if time represents a time in UTC (GMT)" do
+ Time.now.utc?.should == false
+ Time.now.utc.utc?.should == true
+ end
+
+ it "treats time as UTC what was created in different ways" do
+ Time.now.utc.utc?.should == true
+ Time.now.gmtime.utc?.should == true
+ Time.now.getgm.utc?.should == true
+ Time.now.getutc.utc?.should == true
+ Time.utc(2022).utc?.should == true
+ end
+
+ it "does treat time with 'UTC' offset as UTC" do
+ Time.new(2022, 1, 1, 0, 0, 0, "UTC").utc?.should == true
+ Time.now.localtime("UTC").utc?.should == true
+ Time.at(Time.now, in: 'UTC').utc?.should == true
+ end
+
+ it "does treat time with Z offset as UTC" do
+ Time.new(2022, 1, 1, 0, 0, 0, "Z").utc?.should == true
+ Time.now.localtime("Z").utc?.should == true
+ Time.at(Time.now, in: 'Z').utc?.should == true
+ end
+
+ ruby_version_is "3.1" do
+ it "does treat time with -00:00 offset as UTC" do
+ Time.new(2022, 1, 1, 0, 0, 0, "-00:00").utc?.should == true
+ Time.now.localtime("-00:00").utc?.should == true
+ Time.at(Time.now, in: '-00:00').utc?.should == true
+ end
+ end
+
+ it "does not treat time with +00:00 offset as UTC" do
+ Time.new(2022, 1, 1, 0, 0, 0, "+00:00").utc?.should == false
+ end
+
+ it "does not treat time with 0 offset as UTC" do
+ Time.new(2022, 1, 1, 0, 0, 0, 0).utc?.should == false
+ end
+end
+
+describe "Time.utc" do
+ it_behaves_like :time_gm, :utc
+ it_behaves_like :time_params, :utc
+ it_behaves_like :time_params_10_arg, :utc
+ it_behaves_like :time_params_microseconds, :utc
+end
+
+describe "Time#utc" do
+ it_behaves_like :time_gmtime, :utc
+end
diff --git a/spec/ruby/core/time/wday_spec.rb b/spec/ruby/core/time/wday_spec.rb
new file mode 100644
index 0000000000..9f63f67de9
--- /dev/null
+++ b/spec/ruby/core/time/wday_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Time#wday" do
+ it "returns an integer representing the day of the week, 0..6, with Sunday being 0" do
+ with_timezone("GMT", 0) do
+ Time.at(0).wday.should == 4
+ end
+ end
+end
diff --git a/spec/ruby/core/time/wednesday_spec.rb b/spec/ruby/core/time/wednesday_spec.rb
new file mode 100644
index 0000000000..cc686681d7
--- /dev/null
+++ b/spec/ruby/core/time/wednesday_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Time#wednesday?" do
+ it "returns true if time represents Wednesday" do
+ Time.local(2000, 1, 5).should.wednesday?
+ end
+
+ it "returns false if time doesn't represent Wednesday" do
+ Time.local(2000, 1, 1).should_not.wednesday?
+ end
+end
diff --git a/spec/ruby/core/time/yday_spec.rb b/spec/ruby/core/time/yday_spec.rb
new file mode 100644
index 0000000000..6ea5ff8f1b
--- /dev/null
+++ b/spec/ruby/core/time/yday_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "Time#yday" do
+ it "returns an integer representing the day of the year, 1..366" do
+ with_timezone("UTC") do
+ Time.at(9999999).yday.should == 116
+ end
+ end
+
+ it 'returns the correct value for each day of each month' do
+ mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
+
+ yday = 1
+ mdays.each_with_index do |days, month|
+ days.times do |day|
+ Time.new(2014, month+1, day+1).yday.should == yday
+ yday += 1
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/time/year_spec.rb b/spec/ruby/core/time/year_spec.rb
new file mode 100644
index 0000000000..d2d50062c5
--- /dev/null
+++ b/spec/ruby/core/time/year_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "Time#year" do
+ it "returns the four digit year for a local Time as an Integer" do
+ with_timezone("CET", 1) do
+ Time.local(1970).year.should == 1970
+ end
+ end
+
+ it "returns the four digit year for a UTC Time as an Integer" do
+ Time.utc(1970).year.should == 1970
+ end
+
+ it "returns the four digit year for a Time with a fixed offset" do
+ Time.new(2012, 1, 1, 0, 0, 0, -3600).year.should == 2012
+ end
+end
diff --git a/spec/ruby/core/time/zone_spec.rb b/spec/ruby/core/time/zone_spec.rb
new file mode 100644
index 0000000000..cbb0977f24
--- /dev/null
+++ b/spec/ruby/core/time/zone_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+
+describe "Time#zone" do
+ platform_is_not :windows do
+ it "returns the time zone used for time" do
+ with_timezone("America/New_York") do
+ Time.new(2001, 1, 1, 0, 0, 0).zone.should == "EST"
+ Time.new(2001, 7, 1, 0, 0, 0).zone.should == "EDT"
+ %w[EST EDT].should include Time.now.zone
+ end
+ end
+ end
+
+ it "returns nil for a Time with a fixed offset" do
+ Time.new(2001, 1, 1, 0, 0, 0, "+05:00").zone.should == nil
+ end
+
+ platform_is_not :windows do
+ it "returns the correct timezone for a local time" do
+ t = Time.new(2005, 2, 27, 22, 50, 0, -3600)
+
+ with_timezone("America/New_York") do
+ t.getlocal.zone.should == "EST"
+ end
+ end
+ end
+
+ it "returns nil when getting the local time with a fixed offset" do
+ t = Time.new(2005, 2, 27, 22, 50, 0, -3600)
+
+ with_timezone("America/New_York") do
+ t.getlocal("+05:00").zone.should be_nil
+ end
+ end
+
+ describe "Encoding.default_internal is set" do
+ before :each do
+ @encoding = Encoding.default_internal
+ Encoding.default_internal = Encoding::UTF_8
+ end
+
+ after :each do
+ Encoding.default_internal = @encoding
+ end
+
+ it "returns an ASCII string" do
+ t = Time.new(2005, 2, 27, 22, 50, 0, -3600)
+
+ with_timezone("America/New_York") do
+ t.getlocal.zone.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ it "doesn't raise errors for a Time with a fixed offset" do
+ Time.new(2001, 1, 1, 0, 0, 0, "+05:00").zone.should == nil
+ end
+ end
+
+ it "returns UTC when called on a UTC time" do
+ Time.now.utc.zone.should == "UTC"
+ Time.now.gmtime.zone.should == "UTC"
+ Time.now.getgm.zone.should == "UTC"
+ Time.now.getutc.zone.should == "UTC"
+ Time.utc(2022).zone.should == "UTC"
+ Time.new(2022, 1, 1, 0, 0, 0, "UTC").zone.should == "UTC"
+ Time.new(2022, 1, 1, 0, 0, 0, "Z").zone.should == "UTC"
+ Time.now.localtime("UTC").zone.should == "UTC"
+ Time.now.localtime("Z").zone.should == "UTC"
+ Time.at(Time.now, in: 'UTC').zone.should == "UTC"
+ Time.at(Time.now, in: 'Z').zone.should == "UTC"
+
+ ruby_version_is "3.1" do
+ Time.new(2022, 1, 1, 0, 0, 0, "-00:00").zone.should == "UTC"
+ Time.now.localtime("-00:00").zone.should == "UTC"
+ Time.at(Time.now, in: '-00:00').zone.should == "UTC"
+ end
+ end
+
+ platform_is_not :aix, :windows do
+ it "defaults to UTC when bad zones given" do
+ with_timezone("hello-foo") do
+ Time.now.utc_offset.should == 0
+ end
+ with_timezone("1,2") do
+ Time.now.utc_offset.should == 0
+ end
+ with_timezone("Sun,Fri,2") do
+ Time.now.utc_offset.should == 0
+ end
+ end
+ end
+
+ platform_is :windows do
+ # See https://bugs.ruby-lang.org/issues/13591#note-11
+ it "defaults to UTC when bad zones given" do
+ with_timezone("1,2") do
+ Time.now.utc_offset.should == 0
+ end
+ with_timezone("12") do
+ Time.now.utc_offset.should == 0
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/tracepoint/allow_reentry_spec.rb b/spec/ruby/core/tracepoint/allow_reentry_spec.rb
new file mode 100644
index 0000000000..6bff1bed76
--- /dev/null
+++ b/spec/ruby/core/tracepoint/allow_reentry_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1" do
+ describe 'TracePoint.allow_reentry' do
+ it 'allows the reentrance in a given block' do
+ event_lines = []
+ l1 = l2 = l3 = l4 = nil
+ TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+
+ event_lines << tp.lineno
+ next if (__LINE__ + 2 .. __LINE__ + 4).cover?(tp.lineno)
+ TracePoint.allow_reentry do
+ a = 1; l3 = __LINE__
+ b = 2; l4 = __LINE__
+ end
+ end.enable do
+ c = 3; l1 = __LINE__
+ d = 4; l2 = __LINE__
+ end
+
+ event_lines.should == [l1, l3, l4, l2, l3, l4]
+ end
+
+ it 'raises RuntimeError when not called inside a TracePoint' do
+ -> {
+ TracePoint.allow_reentry{}
+ }.should raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/tracepoint/binding_spec.rb b/spec/ruby/core/tracepoint/binding_spec.rb
new file mode 100644
index 0000000000..6a7ef5f85a
--- /dev/null
+++ b/spec/ruby/core/tracepoint/binding_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#binding' do
+ def test
+ secret = 42
+ end
+
+ it 'return the generated binding object from event' do
+ bindings = []
+ TracePoint.new(:return) { |tp|
+ next unless TracePointSpec.target_thread?
+ bindings << tp.binding
+ }.enable {
+ test
+ }
+ bindings.size.should == 1
+ bindings[0].should be_kind_of(Binding)
+ bindings[0].local_variables.should == [:secret]
+ end
+end
diff --git a/spec/ruby/core/tracepoint/callee_id_spec.rb b/spec/ruby/core/tracepoint/callee_id_spec.rb
new file mode 100644
index 0000000000..cc08a45504
--- /dev/null
+++ b/spec/ruby/core/tracepoint/callee_id_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "TracePoint#callee_id" do
+ it "returns the called name of the method being called" do
+ a = []
+ obj = TracePointSpec::ClassWithMethodAlias.new
+
+ TracePoint.new(:call) do |tp|
+ next unless TracePointSpec.target_thread?
+ a << tp.callee_id
+ end.enable do
+ obj.m_alias
+ end
+
+ a.should == [:m_alias]
+ end
+end
diff --git a/spec/ruby/core/tracepoint/defined_class_spec.rb b/spec/ruby/core/tracepoint/defined_class_spec.rb
new file mode 100644
index 0000000000..4593db6d1f
--- /dev/null
+++ b/spec/ruby/core/tracepoint/defined_class_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#defined_class' do
+ it 'returns class or module of the method being called' do
+ last_class_name = nil
+ TracePoint.new(:call) do |tp|
+ next unless TracePointSpec.target_thread?
+ last_class_name = tp.defined_class
+ end.enable do
+ TracePointSpec::B.new.foo
+ last_class_name.should equal(TracePointSpec::B)
+
+ TracePointSpec::B.new.bar
+ last_class_name.should equal(TracePointSpec::A)
+
+ c = TracePointSpec::C.new
+ last_class_name.should equal(TracePointSpec::C)
+
+ c.foo
+ last_class_name.should equal(TracePointSpec::B)
+
+ c.bar
+ last_class_name.should equal(TracePointSpec::A)
+ end
+ end
+end
diff --git a/spec/ruby/core/tracepoint/disable_spec.rb b/spec/ruby/core/tracepoint/disable_spec.rb
new file mode 100644
index 0000000000..73a31b3b81
--- /dev/null
+++ b/spec/ruby/core/tracepoint/disable_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#disable' do
+ it 'returns true if trace was enabled' do
+ called = false
+ trace = TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+ called = true
+ end
+
+ trace.enable
+ begin
+ line_event = true
+ ensure
+ ret = trace.disable
+ ret.should == true
+ end
+ called.should == true
+
+ # Check the TracePoint is disabled
+ called = false
+ line_event = true
+ called.should == false
+ end
+
+ it 'returns false if trace was disabled' do
+ called = false
+ trace = TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+ called = true
+ end
+
+ line_event = true
+ trace.disable.should == false
+ line_event = true
+ called.should == false
+ end
+
+ it 'is disabled within a block & is enabled outside the block' do
+ enabled = nil
+ trace = TracePoint.new(:line) {}
+ trace.enable
+ begin
+ trace.disable { enabled = trace.enabled? }
+ enabled.should == false
+ trace.should.enabled?
+ ensure
+ trace.disable
+ end
+ end
+
+ it 'returns the return value of the block' do
+ trace = TracePoint.new(:line) {}
+ trace.enable
+ begin
+ trace.disable { 42 }.should == 42
+ trace.should.enabled?
+ ensure
+ trace.disable
+ end
+ end
+
+ it 'can accept param within a block but it should not yield arguments' do
+ trace = TracePoint.new(:line) {}
+ trace.enable
+ begin
+ trace.disable do |*args|
+ args.should == []
+ end
+ trace.should.enabled?
+ ensure
+ trace.disable
+ end
+ end
+end
diff --git a/spec/ruby/core/tracepoint/enable_spec.rb b/spec/ruby/core/tracepoint/enable_spec.rb
new file mode 100644
index 0000000000..24f6070b97
--- /dev/null
+++ b/spec/ruby/core/tracepoint/enable_spec.rb
@@ -0,0 +1,574 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#enable' do
+ describe 'without a block' do
+ it 'returns false if trace was disabled' do
+ called = false
+ trace = TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+ called = true
+ end
+
+ line_event = true
+ called.should == false
+
+ ret = trace.enable
+ begin
+ ret.should == false
+ line_event = true
+ called.should == true
+ ensure
+ trace.disable
+ end
+ end
+
+ it 'returns true if trace was already enabled' do
+ called = false
+ trace = TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+ called = true
+ end
+
+ line_event = true
+ called.should == false
+
+ ret = trace.enable
+ begin
+ ret.should == false
+
+ trace.enable.should == true
+
+ line_event = true
+ called.should == true
+ ensure
+ trace.disable
+ trace.should_not.enabled?
+ end
+ end
+ end
+
+ describe 'with a block' do
+ it 'enables the trace object within a block' do
+ event_name = nil
+ TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+ event_name = tp.event
+ end.enable { event_name.should equal(:line) }
+ end
+
+ ruby_version_is '3.2' do
+ it 'enables the trace object only for the current thread' do
+ threads = []
+ trace = TracePoint.new(:line) do |tp|
+ # Runs on purpose on any Thread
+ threads << Thread.current
+ end
+
+ thread = nil
+ trace.enable do
+ line_event = true
+ thread = Thread.new do
+ event_in_other_thread = true
+ end
+ thread.join
+ end
+
+ threads = threads.uniq
+ threads.should.include?(Thread.current)
+ threads.should_not.include?(thread)
+ end
+ end
+
+ ruby_version_is ''...'3.2' do
+ it 'enables the trace object for any thread' do
+ threads = []
+ trace = TracePoint.new(:line) do |tp|
+ # Runs on purpose on any Thread
+ threads << Thread.current
+ end
+
+ thread = nil
+ trace.enable do
+ line_event = true
+ thread = Thread.new do
+ event_in_other_thread = true
+ end
+ thread.join
+ end
+
+ threads = threads.uniq
+ threads.should.include?(Thread.current)
+ threads.should.include?(thread)
+ end
+ end
+
+ it 'can accept arguments within a block but it should not yield arguments' do
+ event_name = nil
+ trace = TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+ event_name = tp.event
+ end
+ trace.enable do |*args|
+ event_name.should equal(:line)
+ args.should == []
+ end
+ trace.should_not.enabled?
+ end
+
+ it 'enables trace object on calling with a block if it was already enabled' do
+ enabled = nil
+ trace = TracePoint.new(:line) {}
+ trace.enable
+ begin
+ trace.enable { enabled = trace.enabled? }
+ enabled.should == true
+ ensure
+ trace.disable
+ end
+ end
+
+ it 'returns the return value of the block' do
+ trace = TracePoint.new(:line) {}
+ trace.enable { 42 }.should == 42
+ end
+
+ it 'disables the trace object outside the block' do
+ called = false
+ trace = TracePoint.new(:line) do
+ next unless TracePointSpec.target_thread?
+ called = true
+ end
+ trace.enable {
+ line_event = true
+ }
+ called.should == true
+ trace.should_not.enabled?
+ end
+ end
+
+ describe "when nested" do
+ before do
+ ruby_version_is ""..."3.0" do
+ @path_prefix = '@'
+ end
+
+ ruby_version_is "3.0" do
+ @path_prefix = ' '
+ end
+ end
+
+ it "enables both TracePoints but only calls the respective callbacks" do
+ called = false
+ first = TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+ called = true
+ end
+
+ all = []
+ inspects = []
+ second = TracePoint.new(:line) { |tp|
+ next unless TracePointSpec.target_thread?
+ all << tp
+ inspects << tp.inspect
+ }
+
+ line = nil
+ first.enable do
+ second.enable do
+ line = __LINE__
+ end
+ end
+
+ all.uniq.should == [second]
+ inspects.uniq.should == ["#<TracePoint:line#{@path_prefix}#{__FILE__}:#{line}>"]
+ called.should == true
+ end
+ end
+
+ describe 'target: option' do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it 'enables trace point for specific location' do
+ trace = TracePoint.new(:call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << tp.method_id
+ end
+
+ obj = Object.new
+ def obj.foo; end
+ def obj.bar; end
+
+ trace.enable(target: obj.method(:foo)) do
+ obj.foo
+ obj.bar
+ end
+
+ ScratchPad.recorded.should == [:foo]
+ end
+
+ it 'traces all the events triggered in specified location' do
+ trace = TracePoint.new(:line, :call, :return, :b_call, :b_return) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << tp.event
+ end
+
+ obj = Object.new
+ def obj.foo
+ bar
+ -> {}.call
+ end
+ def obj.bar; end
+
+ trace.enable(target: obj.method(:foo)) do
+ obj.foo
+ end
+
+ ScratchPad.recorded.uniq.sort.should == [:call, :return, :b_call, :b_return, :line].sort
+ end
+
+ it 'does not trace events in nested locations' do
+ trace = TracePoint.new(:call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << tp.method_id
+ end
+
+ obj = Object.new
+ def obj.foo
+ bar
+ end
+ def obj.bar
+ baz
+ end
+ def obj.baz
+ end
+
+ trace.enable(target: obj.method(:foo)) do
+ obj.foo
+ end
+
+ ScratchPad.recorded.should == [:foo]
+ end
+
+ it "traces some events in nested blocks" do
+ klass = Class.new do
+ def foo
+ 1.times do
+ 1.times do
+ bar do
+ end
+ end
+ end
+ end
+
+ def bar(&blk)
+ blk.call
+ end
+ end
+
+ trace = TracePoint.new(:b_call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << tp.lineno
+ end
+
+ obj = klass.new
+ _, lineno = obj.method(:foo).source_location
+
+ trace.enable(target: obj.method(:foo)) do
+ obj.foo
+ end
+
+ ScratchPad.recorded.should == (lineno+1..lineno+3).to_a
+ end
+
+ describe 'option value' do
+ it 'accepts Method' do
+ trace = TracePoint.new(:call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << tp.method_id
+ end
+
+ obj = Object.new
+ def obj.foo; end
+
+ trace.enable(target: obj.method(:foo)) do
+ obj.foo
+ end
+
+ ScratchPad.recorded.should == [:foo]
+ end
+
+ it 'accepts UnboundMethod' do
+ trace = TracePoint.new(:call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << tp.method_id
+ end
+
+ klass = Class.new do
+ def foo; end
+ end
+
+ unbound_method = klass.instance_method(:foo)
+ trace.enable(target: unbound_method) do
+ klass.new.foo
+ end
+
+ ScratchPad.recorded.should == [:foo]
+ end
+
+ it 'accepts Proc' do
+ trace = TracePoint.new(:b_call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << tp.lineno
+ end
+
+ block = proc {}
+ _, lineno = block.source_location
+
+ trace.enable(target: block) do
+ block.call
+ end
+
+ ScratchPad.recorded.should == [lineno]
+ lineno.should be_kind_of(Integer)
+ end
+ end
+
+ it "raises ArgumentError if target object cannot trigger specified event" do
+ trace = TracePoint.new(:call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << tp.method_id
+ end
+
+ block = proc {}
+
+ -> {
+ trace.enable(target: block) do
+ block.call # triggers :b_call and :b_return events
+ end
+ }.should raise_error(ArgumentError, /can not enable any hooks/)
+ end
+
+ it "raises ArgumentError if passed not Method/UnboundMethod/Proc" do
+ trace = TracePoint.new(:call) {}
+
+ -> {
+ trace.enable(target: Object.new) do
+ end
+ }.should raise_error(ArgumentError, /specified target is not supported/)
+ end
+
+ context "nested enabling and disabling" do
+ it "raises ArgumentError if trace point already enabled with target is re-enabled with target" do
+ trace = TracePoint.new(:b_call) {}
+
+ -> {
+ trace.enable(target: -> {}) do
+ trace.enable(target: -> {}) do
+ end
+ end
+ }.should raise_error(ArgumentError, /can't nest-enable a targett?ing TracePoint/)
+ end
+
+ it "raises ArgumentError if trace point already enabled without target is re-enabled with target" do
+ trace = TracePoint.new(:b_call) {}
+
+ -> {
+ trace.enable do
+ trace.enable(target: -> {}) do
+ end
+ end
+ }.should raise_error(ArgumentError, /can't nest-enable a targett?ing TracePoint/)
+ end
+
+ it "raises ArgumentError if trace point already enabled with target is re-enabled without target" do
+ trace = TracePoint.new(:b_call) {}
+
+ -> {
+ trace.enable(target: -> {}) do
+ trace.enable do
+ end
+ end
+ }.should raise_error(ArgumentError, /can't nest-enable a targett?ing TracePoint/)
+ end
+
+ it "raises ArgumentError if trace point already enabled with target is disabled with block" do
+ trace = TracePoint.new(:b_call) {}
+
+ -> {
+ trace.enable(target: -> {}) do
+ trace.disable do
+ end
+ end
+ }.should raise_error(ArgumentError, /can't disable a targett?ing TracePoint in a block/)
+ end
+
+ it "traces events when trace point with target is enabled in another trace point enabled without target" do
+ trace_outer = TracePoint.new(:b_call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << :outer
+ end
+
+ trace_inner = TracePoint.new(:b_call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << :inner
+ end
+
+ target = -> {}
+
+ trace_outer.enable do
+ trace_inner.enable(target: target) do
+ target.call
+ end
+ end
+
+ ScratchPad.recorded.should == [:outer, :outer, :outer, :inner]
+ end
+
+ it "traces events when trace point with target is enabled in another trace point enabled with target" do
+ trace_outer = TracePoint.new(:b_call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << :outer
+ end
+
+ trace_inner = TracePoint.new(:b_call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << :inner
+ end
+
+ target = -> {}
+
+ trace_outer.enable(target: target) do
+ trace_inner.enable(target: target) do
+ target.call
+ end
+ end
+
+ ScratchPad.recorded.should == [:inner, :outer]
+ end
+
+ it "traces events when trace point without target is enabled in another trace point enabled with target" do
+ trace_outer = TracePoint.new(:b_call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << :outer
+ end
+
+ trace_inner = TracePoint.new(:b_call) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << :inner
+ end
+
+ target = -> {}
+
+ trace_outer.enable(target: target) do
+ trace_inner.enable do
+ target.call
+ end
+ end
+
+ ScratchPad.recorded.should == [:inner, :inner, :outer]
+ end
+ end
+ end
+
+ describe 'target_line: option' do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "traces :line events only on specified line of code" do
+ trace = TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << tp.lineno
+ end
+
+ target = -> {
+ x = 1
+ y = 2 # <= this line is target
+ z = x + y
+ }
+ _, lineno = target.source_location
+ target_line = lineno + 2
+
+ trace.enable(target_line: target_line, target: target) do
+ target.call
+ end
+
+ ScratchPad.recorded.should == [target_line]
+ end
+
+ it "raises ArgumentError if :target option isn't specified" do
+ trace = TracePoint.new(:line) {}
+
+ -> {
+ trace.enable(target_line: 67) do
+ end
+ }.should raise_error(ArgumentError, /only target_line is specified/)
+ end
+
+ it "raises ArgumentError if :line event isn't registered" do
+ trace = TracePoint.new(:call) {}
+
+ target = -> {
+ x = 1
+ y = 2 # <= this line is target
+ z = x + y
+ }
+ _, lineno = target.source_location
+ target_line = lineno + 2
+
+ -> {
+ trace.enable(target_line: target_line, target: target) do
+ end
+ }.should raise_error(ArgumentError, /target_line is specified, but line event is not specified/)
+ end
+
+ it "raises ArgumentError if :target_line value is out of target code lines range" do
+ trace = TracePoint.new(:line) {}
+
+ -> {
+ trace.enable(target_line: 1, target: -> { }) do
+ end
+ }.should raise_error(ArgumentError, /can not enable any hooks/)
+ end
+
+ it "raises TypeError if :target_line value couldn't be coerced to Integer" do
+ trace = TracePoint.new(:line) {}
+
+ -> {
+ trace.enable(target_line: Object.new, target: -> { }) do
+ end
+ }.should raise_error(TypeError, /no implicit conversion of \w+? into Integer/)
+ end
+
+ it "raises ArgumentError if :target_line value is negative" do
+ trace = TracePoint.new(:line) {}
+
+ -> {
+ trace.enable(target_line: -2, target: -> { }) do
+ end
+ }.should raise_error(ArgumentError, /can not enable any hooks/)
+ end
+
+ it "accepts value that could be coerced to Integer" do
+ trace = TracePoint.new(:line) do |tp|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << tp.lineno
+ end
+
+ target = -> {
+ x = 1 # <= this line is target
+ }
+ _, lineno = target.source_location
+ target_line = lineno + 1
+
+ trace.enable(target_line: target_line.to_r, target: target) do
+ target.call
+ end
+
+ ScratchPad.recorded.should == [target_line]
+ end
+ end
+end
diff --git a/spec/ruby/core/tracepoint/enabled_spec.rb b/spec/ruby/core/tracepoint/enabled_spec.rb
new file mode 100644
index 0000000000..0e9566a02c
--- /dev/null
+++ b/spec/ruby/core/tracepoint/enabled_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#enabled?' do
+ it 'returns true when current status of the trace is enable' do
+ trace = TracePoint.new(:line) {}
+ trace.enable do
+ trace.should.enabled?
+ end
+ end
+
+ it 'returns false when current status of the trace is disabled' do
+ TracePoint.new(:line) {}.should_not.enabled?
+ end
+end
diff --git a/spec/ruby/core/tracepoint/eval_script_spec.rb b/spec/ruby/core/tracepoint/eval_script_spec.rb
new file mode 100644
index 0000000000..7ec53e7094
--- /dev/null
+++ b/spec/ruby/core/tracepoint/eval_script_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "TracePoint#eval_script" do
+ it "is the evald source code" do
+ ScratchPad.record []
+
+ script = <<-CODE
+ def foo
+ p :hello
+ end
+ CODE
+
+ TracePoint.new(:script_compiled) do |e|
+ next unless TracePointSpec.target_thread?
+ ScratchPad << e.eval_script
+ end.enable do
+ eval script
+ end
+
+ ScratchPad.recorded.should == [script]
+ end
+end
diff --git a/spec/ruby/core/tracepoint/event_spec.rb b/spec/ruby/core/tracepoint/event_spec.rb
new file mode 100644
index 0000000000..9dea24d125
--- /dev/null
+++ b/spec/ruby/core/tracepoint/event_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#event' do
+ it 'returns the type of event' do
+ event_name = nil
+ TracePoint.new(:end, :call) do |tp|
+ next unless TracePointSpec.target_thread?
+ event_name = tp.event
+ end.enable do
+ TracePointSpec.test
+ event_name.should equal(:call)
+
+ TracePointSpec::B.new.foo
+ event_name.should equal(:call)
+
+ class TracePointSpec::B; end
+ event_name.should equal(:end)
+ end
+
+ end
+end
diff --git a/spec/ruby/core/tracepoint/fixtures/classes.rb b/spec/ruby/core/tracepoint/fixtures/classes.rb
new file mode 100644
index 0000000000..3ab1b00b16
--- /dev/null
+++ b/spec/ruby/core/tracepoint/fixtures/classes.rb
@@ -0,0 +1,40 @@
+module TracePointSpec
+ @thread = Thread.current
+
+ def self.target_thread?
+ Thread.current == @thread
+ end
+
+ class ClassWithMethodAlias
+ def m
+ end
+ alias_method :m_alias, :m
+ end
+
+ module A
+ def bar; end
+ end
+
+ class B
+ include A
+
+ def foo; end;
+ end
+
+ class C < B
+ def initialize
+ end
+
+ def foo
+ super
+ end
+
+ def bar
+ super
+ end
+ end
+
+ def self.test
+ 'test'
+ end
+end
diff --git a/spec/ruby/core/tracepoint/inspect_spec.rb b/spec/ruby/core/tracepoint/inspect_spec.rb
new file mode 100644
index 0000000000..151a08e7b4
--- /dev/null
+++ b/spec/ruby/core/tracepoint/inspect_spec.rb
@@ -0,0 +1,134 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#inspect' do
+ before do
+ ruby_version_is ""..."3.0" do
+ @path_prefix = '@'
+ end
+
+ ruby_version_is "3.0" do
+ @path_prefix = ' '
+ end
+ end
+
+ it 'returns a string containing a human-readable TracePoint status' do
+ TracePoint.new(:line) {}.inspect.should == '#<TracePoint:disabled>'
+ end
+
+ it "shows only whether it's enabled when outside the TracePoint handler" do
+ trace = TracePoint.new(:line) {}
+ trace.enable
+
+ trace.inspect.should == '#<TracePoint:enabled>'
+
+ trace.disable
+ end
+
+ it 'returns a String showing the event, path and line' do
+ inspect = nil
+ line = nil
+ TracePoint.new(:line) { |tp|
+ next unless TracePointSpec.target_thread?
+ inspect ||= tp.inspect
+ }.enable do
+ line = __LINE__
+ end
+
+ inspect.should == "#<TracePoint:line#{@path_prefix}#{__FILE__}:#{line}>"
+ end
+
+ it 'returns a String showing the event, method, path and line for a :call event' do
+ inspect = nil
+ line = nil
+ TracePoint.new(:call) { |tp|
+ next unless TracePointSpec.target_thread?
+ inspect ||= tp.inspect
+ }.enable do
+ line = __LINE__ + 1
+ def trace_point_spec_test_call; end
+ trace_point_spec_test_call
+ end
+
+ inspect.should == "#<TracePoint:call `trace_point_spec_test_call'#{@path_prefix}#{__FILE__}:#{line}>"
+ end
+
+ it 'returns a String showing the event, method, path and line for a :return event' do
+ inspect = nil
+ line = nil
+ TracePoint.new(:return) { |tp|
+ next unless TracePointSpec.target_thread?
+ inspect ||= tp.inspect
+ }.enable do
+ line = __LINE__ + 4
+ def trace_point_spec_test_return
+ a = 1
+ return a
+ end
+ trace_point_spec_test_return
+ end
+
+ inspect.should == "#<TracePoint:return `trace_point_spec_test_return'#{@path_prefix}#{__FILE__}:#{line}>"
+ end
+
+ it 'returns a String showing the event, method, path and line for a :c_call event' do
+ inspect = nil
+ tracepoint = TracePoint.new(:c_call) { |tp|
+ next unless TracePointSpec.target_thread?
+ inspect ||= tp.inspect
+ }
+ line = __LINE__ + 2
+ tracepoint.enable do
+ [0, 1].max
+ end
+
+ inspect.should == "#<TracePoint:c_call `max'#{@path_prefix}#{__FILE__}:#{line}>"
+ end
+
+ it 'returns a String showing the event, path and line for a :class event' do
+ inspect = nil
+ line = nil
+ TracePoint.new(:class) { |tp|
+ next unless TracePointSpec.target_thread?
+ inspect ||= tp.inspect
+ }.enable do
+ line = __LINE__ + 1
+ class TracePointSpec::C
+ end
+ end
+
+ inspect.should == "#<TracePoint:class#{@path_prefix}#{__FILE__}:#{line}>"
+ end
+
+ it 'returns a String showing the event and thread for :thread_begin event' do
+ inspect = nil
+ thread = nil
+ thread_inspection = nil
+ TracePoint.new(:thread_begin) { |tp|
+ next unless Thread.current == thread
+ inspect ||= tp.inspect
+ }.enable(target_thread: nil) do
+ thread = Thread.new {}
+ thread_inspection = thread.inspect
+ thread.join
+ end
+
+ inspect.should == "#<TracePoint:thread_begin #{thread_inspection}>"
+ end
+
+ it 'returns a String showing the event and thread for :thread_end event' do
+ inspect = nil
+ thread = nil
+ thread_inspection = nil
+ TracePoint.new(:thread_end) { |tp|
+ next unless Thread.current == thread
+ inspect ||= tp.inspect
+ }.enable(target_thread: nil) do
+ thread = Thread.new {}
+ thread_inspection = thread.inspect
+ thread.join
+ end
+
+ inspect.should == "#<TracePoint:thread_end #{thread_inspection}>"
+ end
+end
diff --git a/spec/ruby/core/tracepoint/lineno_spec.rb b/spec/ruby/core/tracepoint/lineno_spec.rb
new file mode 100644
index 0000000000..77b3ac8b54
--- /dev/null
+++ b/spec/ruby/core/tracepoint/lineno_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#lineno' do
+ it 'returns the line number of the event' do
+ lineno = nil
+ TracePoint.new(:line) { |tp|
+ next unless TracePointSpec.target_thread?
+ lineno = tp.lineno
+ }.enable do
+ line_event = true
+ end
+ lineno.should == __LINE__ - 2
+ end
+
+ it 'raises RuntimeError if accessed from outside' do
+ tp = TracePoint.new(:line) {}
+ -> { tp.lineno }.should raise_error(RuntimeError, 'access from outside')
+ end
+end
diff --git a/spec/ruby/core/tracepoint/method_id_spec.rb b/spec/ruby/core/tracepoint/method_id_spec.rb
new file mode 100644
index 0000000000..43e23248b7
--- /dev/null
+++ b/spec/ruby/core/tracepoint/method_id_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#method_id' do
+ it 'returns the name at the definition of the method being called' do
+ method_name = nil
+ TracePoint.new(:call) { |tp|
+ next unless TracePointSpec.target_thread?
+ method_name = tp.method_id
+ }.enable do
+ TracePointSpec.test
+ method_name.should equal(:test)
+ end
+ end
+end
diff --git a/spec/ruby/core/tracepoint/new_spec.rb b/spec/ruby/core/tracepoint/new_spec.rb
new file mode 100644
index 0000000000..e53c2b04a2
--- /dev/null
+++ b/spec/ruby/core/tracepoint/new_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint.new' do
+ it 'returns a new TracePoint object, not enabled by default' do
+ TracePoint.new(:line) {}.enabled?.should be_false
+ end
+
+ it 'includes :line event when event is not specified' do
+ event_name = nil
+ TracePoint.new { |tp|
+ next unless TracePointSpec.target_thread?
+ event_name = tp.event
+ }.enable do
+ event_name.should equal(:line)
+
+ event_name = nil
+ TracePointSpec.test
+ event_name.should equal(:line)
+
+ event_name = nil
+ TracePointSpec::B.new.foo
+ event_name.should equal(:line)
+ end
+ end
+
+ it 'converts given event name as string into symbol using to_sym' do
+ event_name = nil
+ (o = mock('line')).should_receive(:to_sym).and_return(:line)
+
+ TracePoint.new(o) { |tp|
+ next unless TracePointSpec.target_thread?
+ event_name = tp.event
+ }.enable do
+ line_event = true
+ event_name.should == :line
+ end
+ end
+
+ it 'includes multiple events when multiple event names are passed as params' do
+ event_name = nil
+ TracePoint.new(:end, :call) do |tp|
+ next unless TracePointSpec.target_thread?
+ event_name = tp.event
+ end.enable do
+ TracePointSpec.test
+ event_name.should equal(:call)
+
+ TracePointSpec::B.new.foo
+ event_name.should equal(:call)
+
+ class TracePointSpec::B; end
+ event_name.should equal(:end)
+ end
+ end
+
+ it 'raises a TypeError when the given object is not a string/symbol' do
+ o = mock('123')
+ -> { TracePoint.new(o) {} }.should raise_error(TypeError)
+
+ o.should_receive(:to_sym).and_return(123)
+ -> { TracePoint.new(o) {} }.should raise_error(TypeError)
+ end
+
+ it 'expects to be called with a block' do
+ -> { TracePoint.new(:line) }.should raise_error(ArgumentError, "must be called with a block")
+ end
+
+ it "raises a Argument error when the given argument doesn't match an event name" do
+ -> { TracePoint.new(:test) }.should raise_error(ArgumentError, "unknown event: test")
+ end
+end
diff --git a/spec/ruby/core/tracepoint/parameters_spec.rb b/spec/ruby/core/tracepoint/parameters_spec.rb
new file mode 100644
index 0000000000..82aee3caa4
--- /dev/null
+++ b/spec/ruby/core/tracepoint/parameters_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#parameters' do
+ it 'returns the parameters of block' do
+ f = proc {|x, y, z| }
+ parameters = nil
+ TracePoint.new(:b_call) { |tp|
+ next unless TracePointSpec.target_thread?
+ parameters = tp.parameters
+ }.enable do
+ f.call
+ parameters.should == [[:opt, :x], [:opt, :y], [:opt, :z]]
+ end
+ end
+
+ it 'returns the parameters of lambda block' do
+ f = -> x, y, z { }
+ parameters = nil
+ TracePoint.new(:b_call) { |tp|
+ next unless TracePointSpec.target_thread?
+ parameters = tp.parameters
+ }.enable do
+ f.call(1, 2, 3)
+ parameters.should == [[:req, :x], [:req, :y], [:req, :z]]
+ end
+ end
+end
diff --git a/spec/ruby/core/tracepoint/path_spec.rb b/spec/ruby/core/tracepoint/path_spec.rb
new file mode 100644
index 0000000000..5b6c6d4cfc
--- /dev/null
+++ b/spec/ruby/core/tracepoint/path_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#path' do
+ it 'returns the path of the file being run' do
+ path = nil
+ TracePoint.new(:line) { |tp|
+ next unless TracePointSpec.target_thread?
+ path = tp.path
+ }.enable do
+ line_event = true
+ end
+ path.should == "#{__FILE__}"
+ end
+
+ it 'equals (eval) inside an eval for :end event' do
+ path = nil
+ TracePoint.new(:end) { |tp|
+ next unless TracePointSpec.target_thread?
+ path = tp.path
+ }.enable do
+ eval("module TracePointSpec; end")
+ end
+ path.should == '(eval)'
+ end
+end
diff --git a/spec/ruby/core/tracepoint/raised_exception_spec.rb b/spec/ruby/core/tracepoint/raised_exception_spec.rb
new file mode 100644
index 0000000000..ca2f50abb3
--- /dev/null
+++ b/spec/ruby/core/tracepoint/raised_exception_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#raised_exception' do
+ it 'returns value from exception raised on the :raise event' do
+ raised_exception, error_result = nil
+ trace = TracePoint.new(:raise) { |tp|
+ next unless TracePointSpec.target_thread?
+ raised_exception = tp.raised_exception
+ }
+ trace.enable do
+ begin
+ raise StandardError
+ rescue => e
+ error_result = e
+ end
+ raised_exception.should equal(error_result)
+ end
+ end
+end
diff --git a/spec/ruby/core/tracepoint/return_value_spec.rb b/spec/ruby/core/tracepoint/return_value_spec.rb
new file mode 100644
index 0000000000..e84c7dd762
--- /dev/null
+++ b/spec/ruby/core/tracepoint/return_value_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#return_value' do
+ def test; 'test' end
+
+ it 'returns value from :return event' do
+ trace_value = nil
+ TracePoint.new(:return) { |tp|
+ next unless TracePointSpec.target_thread?
+ trace_value = tp.return_value
+ }.enable do
+ test
+ trace_value.should == 'test'
+ end
+ end
+end
diff --git a/spec/ruby/core/tracepoint/self_spec.rb b/spec/ruby/core/tracepoint/self_spec.rb
new file mode 100644
index 0000000000..2098860e59
--- /dev/null
+++ b/spec/ruby/core/tracepoint/self_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint#self' do
+ it 'return the trace object from event' do
+ trace = nil
+ TracePoint.new(:line) { |tp|
+ next unless TracePointSpec.target_thread?
+ trace = tp.self
+ }.enable do
+ trace.equal?(self).should be_true
+ end
+ end
+
+ it 'return the class object from a class event' do
+ trace = nil
+ TracePoint.new(:class) { |tp|
+ next unless TracePointSpec.target_thread?
+ trace = tp.self
+ }.enable do
+ class TracePointSpec::C
+ end
+ end
+ trace.should equal TracePointSpec::C
+ end
+end
diff --git a/spec/ruby/core/tracepoint/trace_spec.rb b/spec/ruby/core/tracepoint/trace_spec.rb
new file mode 100644
index 0000000000..167f594bb9
--- /dev/null
+++ b/spec/ruby/core/tracepoint/trace_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'TracePoint.trace' do
+ it 'activates the trace automatically' do
+ trace = TracePoint.trace(:line) {}
+ trace.should.enabled?
+ trace.disable
+ end
+end
diff --git a/spec/ruby/core/true/and_spec.rb b/spec/ruby/core/true/and_spec.rb
new file mode 100644
index 0000000000..99e69d3ae0
--- /dev/null
+++ b/spec/ruby/core/true/and_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "TrueClass#&" do
+ it "returns false if other is nil or false, otherwise true" do
+ (true & true).should == true
+ (true & false).should == false
+ (true & nil).should == false
+ (true & "").should == true
+ (true & mock('x')).should == true
+ end
+end
diff --git a/spec/ruby/core/true/case_compare_spec.rb b/spec/ruby/core/true/case_compare_spec.rb
new file mode 100644
index 0000000000..dee6dd0227
--- /dev/null
+++ b/spec/ruby/core/true/case_compare_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "TrueClass#===" do
+ it "returns true for true" do
+ (true === true).should == true
+ end
+
+ it "returns false for non-true object" do
+ (true === 1).should == false
+ (true === "").should == false
+ (true === Object).should == false
+ end
+end
diff --git a/spec/ruby/core/true/dup_spec.rb b/spec/ruby/core/true/dup_spec.rb
new file mode 100644
index 0000000000..351457ed22
--- /dev/null
+++ b/spec/ruby/core/true/dup_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "TrueClass#dup" do
+ it "returns self" do
+ true.dup.should equal(true)
+ end
+end
diff --git a/spec/ruby/core/true/inspect_spec.rb b/spec/ruby/core/true/inspect_spec.rb
new file mode 100644
index 0000000000..09d1914856
--- /dev/null
+++ b/spec/ruby/core/true/inspect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "TrueClass#inspect" do
+ it "returns the string 'true'" do
+ true.inspect.should == "true"
+ end
+end
diff --git a/spec/ruby/core/true/or_spec.rb b/spec/ruby/core/true/or_spec.rb
new file mode 100644
index 0000000000..9bf76a62b8
--- /dev/null
+++ b/spec/ruby/core/true/or_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "TrueClass#|" do
+ it "returns true" do
+ (true | true).should == true
+ (true | false).should == true
+ (true | nil).should == true
+ (true | "").should == true
+ (true | mock('x')).should == true
+ end
+end
diff --git a/spec/ruby/core/true/to_s_spec.rb b/spec/ruby/core/true/to_s_spec.rb
new file mode 100644
index 0000000000..fa1b53a580
--- /dev/null
+++ b/spec/ruby/core/true/to_s_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "TrueClass#to_s" do
+ it "returns the string 'true'" do
+ true.to_s.should == "true"
+ end
+
+ it "returns a frozen string" do
+ true.to_s.should.frozen?
+ end
+
+ it "always returns the same string" do
+ true.to_s.should equal(true.to_s)
+ end
+end
diff --git a/spec/ruby/core/true/trueclass_spec.rb b/spec/ruby/core/true/trueclass_spec.rb
new file mode 100644
index 0000000000..02af649d09
--- /dev/null
+++ b/spec/ruby/core/true/trueclass_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "TrueClass" do
+ it ".allocate raises a TypeError" do
+ -> do
+ TrueClass.allocate
+ end.should raise_error(TypeError)
+ end
+
+ it ".new is undefined" do
+ -> do
+ TrueClass.new
+ end.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/true/xor_spec.rb b/spec/ruby/core/true/xor_spec.rb
new file mode 100644
index 0000000000..8f5ecd5075
--- /dev/null
+++ b/spec/ruby/core/true/xor_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "TrueClass#^" do
+ it "returns true if other is nil or false, otherwise false" do
+ (true ^ true).should == false
+ (true ^ false).should == true
+ (true ^ nil).should == true
+ (true ^ "").should == false
+ (true ^ mock('x')).should == false
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/arity_spec.rb b/spec/ruby/core/unboundmethod/arity_spec.rb
new file mode 100644
index 0000000000..cd700b9f9b
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/arity_spec.rb
@@ -0,0 +1,207 @@
+require_relative '../../spec_helper'
+
+describe "UnboundMethod#arity" do
+ SpecEvaluate.desc = "for method definition"
+
+ context "returns zero" do
+ evaluate <<-ruby do
+ def m() end
+ ruby
+
+ method(:m).unbind.arity.should == 0
+ end
+
+ evaluate <<-ruby do
+ def n(&b) end
+ ruby
+
+ method(:n).unbind.arity.should == 0
+ end
+ end
+
+ context "returns positive values" do
+ evaluate <<-ruby do
+ def m(a) end
+ def n(a, b) end
+ def o(a, b, c) end
+ def p(a, b, c, d) end
+ ruby
+
+ method(:m).unbind.arity.should == 1
+ method(:n).unbind.arity.should == 2
+ method(:o).unbind.arity.should == 3
+ method(:p).unbind.arity.should == 4
+ end
+
+ evaluate <<-ruby do
+ def m(a:) end
+ def n(a:, b:) end
+ def o(a: 1, b:, c:, d: 2) end
+ ruby
+
+ method(:m).unbind.arity.should == 1
+ method(:n).unbind.arity.should == 1
+ method(:o).unbind.arity.should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(a, b:) end
+ def n(a, b:, &l) end
+ ruby
+
+ method(:m).unbind.arity.should == 2
+ method(:n).unbind.arity.should == 2
+ end
+
+ evaluate <<-ruby do
+ def m(a, b, c:, d: 1) end
+ def n(a, b, c:, d: 1, **k, &l) end
+ ruby
+
+ method(:m).unbind.arity.should == 3
+ method(:n).unbind.arity.should == 3
+ end
+ end
+
+ context "returns negative values" do
+ evaluate <<-ruby do
+ def m(a=1) end
+ def n(a=1, b=2) end
+ ruby
+
+ method(:m).unbind.arity.should == -1
+ method(:n).unbind.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1) end
+ def n(a, b, c=1, d=2) end
+ ruby
+
+ method(:m).unbind.arity.should == -2
+ method(:n).unbind.arity.should == -3
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, *b) end
+ def n(a=1, b=2, *c) end
+ ruby
+
+ method(:m).unbind.arity.should == -1
+ method(:n).unbind.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(*) end
+ def n(*a) end
+ ruby
+
+ method(:m).unbind.arity.should == -1
+ method(:n).unbind.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(a, *) end
+ def n(a, *b) end
+ def o(a, b, *c) end
+ def p(a, b, c, *d) end
+ ruby
+
+ method(:m).unbind.arity.should == -2
+ method(:n).unbind.arity.should == -2
+ method(:o).unbind.arity.should == -3
+ method(:p).unbind.arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ def m(*a, b) end
+ def n(*a, b, c) end
+ def o(*a, b, c, d) end
+ ruby
+
+ method(:m).unbind.arity.should == -2
+ method(:n).unbind.arity.should == -3
+ method(:o).unbind.arity.should == -4
+ end
+
+ evaluate <<-ruby do
+ def m(a, *b, c) end
+ def n(a, b, *c, d, e) end
+ ruby
+
+ method(:m).unbind.arity.should == -3
+ method(:n).unbind.arity.should == -5
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1, c=2, *d, e, f) end
+ def n(a, b, c=1, *d, e, f, g) end
+ ruby
+
+ method(:m).unbind.arity.should == -4
+ method(:n).unbind.arity.should == -6
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1) end
+ def n(a: 1, b: 2) end
+ ruby
+
+ method(:m).unbind.arity.should == -1
+ method(:n).unbind.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, b: 2) end
+ def n(*a, b: 1) end
+ def o(a=1, b: 2) end
+ def p(a=1, *b, c: 2, &l) end
+ ruby
+
+ method(:m).unbind.arity.should == -1
+ method(:n).unbind.arity.should == -1
+ method(:o).unbind.arity.should == -1
+ method(:p).unbind.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(**k, &l) end
+ def n(*a, **k) end
+ def o(a: 1, b: 2, **k) end
+ ruby
+
+ method(:m).unbind.arity.should == -1
+ method(:n).unbind.arity.should == -1
+ method(:o).unbind.arity.should == -1
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, *b, c:, d: 2, **k, &l) end
+ ruby
+
+ method(:m).unbind.arity.should == -2
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1, *c, d, e:, f: 2, **k, &l) end
+ def n(a, b=1, *c, d:, e:, f: 2, **k, &l) end
+ def o(a=0, b=1, *c, d, e:, f: 2, **k, &l) end
+ def p(a=0, b=1, *c, d:, e:, f: 2, **k, &l) end
+ ruby
+
+ method(:m).unbind.arity.should == -4
+ method(:n).unbind.arity.should == -3
+ method(:o).unbind.arity.should == -3
+ method(:p).unbind.arity.should == -2
+ end
+ end
+
+ context "for a Method generated by respond_to_missing?" do
+ it "returns -1" do
+ obj = mock("method arity respond_to_missing")
+ obj.should_receive(:respond_to_missing?).and_return(true)
+
+ obj.method(:m).unbind.arity.should == -1
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/bind_call_spec.rb b/spec/ruby/core/unboundmethod/bind_call_spec.rb
new file mode 100644
index 0000000000..8f25f8bd0d
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/bind_call_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "UnboundMethod#bind_call" do
+ before :each do
+ @normal_um = UnboundMethodSpecs::Methods.new.method(:foo).unbind
+ @parent_um = UnboundMethodSpecs::Parent.new.method(:foo).unbind
+ @child1_um = UnboundMethodSpecs::Child1.new.method(:foo).unbind
+ @child2_um = UnboundMethodSpecs::Child2.new.method(:foo).unbind
+ end
+
+ it "raises TypeError if object is not kind_of? the Module the method defined in" do
+ -> { @normal_um.bind_call(UnboundMethodSpecs::B.new) }.should raise_error(TypeError)
+ end
+
+ it "binds and calls the method if object is kind_of the Module the method defined in" do
+ @normal_um.bind_call(UnboundMethodSpecs::Methods.new).should == true
+ end
+
+ it "binds and calls the method on any object when UnboundMethod is unbound from a module" do
+ UnboundMethodSpecs::Mod.instance_method(:from_mod).bind_call(Object.new).should == nil
+ end
+
+ it "binds and calls the method for any object kind_of? the Module the method is defined in" do
+ @parent_um.bind_call(UnboundMethodSpecs::Child1.new).should == nil
+ @child1_um.bind_call(UnboundMethodSpecs::Parent.new).should == nil
+ @child2_um.bind_call(UnboundMethodSpecs::Child1.new).should == nil
+ end
+
+ it "binds and calls a Kernel method retrieved from Object on BasicObject" do
+ Object.instance_method(:instance_of?).bind_call(BasicObject.new, BasicObject).should == true
+ end
+
+ it "binds and calls a Parent's class method to any Child's class methods" do
+ um = UnboundMethodSpecs::Parent.method(:class_method).unbind
+ um.bind_call(UnboundMethodSpecs::Child1).should == "I am UnboundMethodSpecs::Child1"
+ end
+
+ it "will raise when binding a an object singleton's method to another object" do
+ other = UnboundMethodSpecs::Parent.new
+ p = UnboundMethodSpecs::Parent.new
+ class << p
+ def singleton_method
+ :single
+ end
+ end
+ um = p.method(:singleton_method).unbind
+ ->{ um.bind_call(other) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/bind_spec.rb b/spec/ruby/core/unboundmethod/bind_spec.rb
new file mode 100644
index 0000000000..03aaa22e74
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/bind_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "UnboundMethod#bind" do
+ before :each do
+ @normal_um = UnboundMethodSpecs::Methods.new.method(:foo).unbind
+ @parent_um = UnboundMethodSpecs::Parent.new.method(:foo).unbind
+ @child1_um = UnboundMethodSpecs::Child1.new.method(:foo).unbind
+ @child2_um = UnboundMethodSpecs::Child2.new.method(:foo).unbind
+ end
+
+ it "raises TypeError if object is not kind_of? the Module the method defined in" do
+ -> { @normal_um.bind(UnboundMethodSpecs::B.new) }.should raise_error(TypeError)
+ end
+
+ it "returns Method for any object that is kind_of? the Module method was extracted from" do
+ @normal_um.bind(UnboundMethodSpecs::Methods.new).should be_kind_of(Method)
+ end
+
+ it "returns Method on any object when UnboundMethod is unbound from a module" do
+ UnboundMethodSpecs::Mod.instance_method(:from_mod).bind(Object.new).should be_kind_of(Method)
+ end
+
+ it "the returned Method is equal to the one directly returned by obj.method" do
+ obj = UnboundMethodSpecs::Methods.new
+ @normal_um.bind(obj).should == obj.method(:foo)
+ end
+
+ it "returns Method for any object kind_of? the Module the method is defined in" do
+ @parent_um.bind(UnboundMethodSpecs::Child1.new).should be_kind_of(Method)
+ @child1_um.bind(UnboundMethodSpecs::Parent.new).should be_kind_of(Method)
+ @child2_um.bind(UnboundMethodSpecs::Child1.new).should be_kind_of(Method)
+ end
+
+ it "allows binding a Kernel method retrieved from Object on BasicObject" do
+ Object.instance_method(:instance_of?).bind(BasicObject.new).call(BasicObject).should == true
+ end
+
+ it "returns a callable method" do
+ obj = UnboundMethodSpecs::Methods.new
+ @normal_um.bind(obj).call.should == obj.foo
+ end
+
+ it "binds a Parent's class method to any Child's class methods" do
+ m = UnboundMethodSpecs::Parent.method(:class_method).unbind.bind(UnboundMethodSpecs::Child1)
+ m.should be_an_instance_of(Method)
+ m.call.should == "I am UnboundMethodSpecs::Child1"
+ end
+
+ it "will raise when binding a an object singleton's method to another object" do
+ other = UnboundMethodSpecs::Parent.new
+ p = UnboundMethodSpecs::Parent.new
+ class << p
+ def singleton_method
+ :single
+ end
+ end
+ um = p.method(:singleton_method).unbind
+ ->{ um.bind(other) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/clone_spec.rb b/spec/ruby/core/unboundmethod/clone_spec.rb
new file mode 100644
index 0000000000..098ee61476
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/clone_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "UnboundMethod#clone" do
+ it "returns a copy of the UnboundMethod" do
+ um1 = UnboundMethodSpecs::Methods.instance_method(:foo)
+ um2 = um1.clone
+
+ (um1 == um2).should == true
+ um1.bind(UnboundMethodSpecs::Methods.new).call.should == um2.bind(UnboundMethodSpecs::Methods.new).call
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/eql_spec.rb b/spec/ruby/core/unboundmethod/eql_spec.rb
new file mode 100644
index 0000000000..cf2c6b5aae
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/eql_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "UnboundMethod#eql?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/unboundmethod/equal_value_spec.rb b/spec/ruby/core/unboundmethod/equal_value_spec.rb
new file mode 100644
index 0000000000..036c6b7f8c
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/equal_value_spec.rb
@@ -0,0 +1,157 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+context "Creating UnboundMethods" do
+ specify "there is no difference between Method#unbind and Module#instance_method" do
+ UnboundMethodSpecs::Methods.instance_method(:foo).should be_kind_of(UnboundMethod)
+ UnboundMethodSpecs::Methods.new.method(:foo).unbind.should be_kind_of(UnboundMethod)
+ end
+end
+
+describe "UnboundMethod#==" do
+ before :all do
+ @from_module = UnboundMethodSpecs::Methods.instance_method(:foo)
+ @from_unbind = UnboundMethodSpecs::Methods.new.method(:foo).unbind
+
+ @with_block = UnboundMethodSpecs::Methods.instance_method(:with_block)
+
+ @includee = UnboundMethodSpecs::Mod.instance_method(:from_mod)
+ @includer = UnboundMethodSpecs::Methods.instance_method(:from_mod)
+
+ @alias_1 = UnboundMethodSpecs::Methods.instance_method(:alias_1)
+ @alias_2 = UnboundMethodSpecs::Methods.instance_method(:alias_2)
+
+ @original_body = UnboundMethodSpecs::Methods.instance_method(:original_body)
+ @identical_body = UnboundMethodSpecs::Methods.instance_method(:identical_body)
+
+ @parent = UnboundMethodSpecs::Parent.instance_method(:foo)
+ @child1 = UnboundMethodSpecs::Child1.instance_method(:foo)
+ @child2 = UnboundMethodSpecs::Child2.instance_method(:foo)
+
+ @child1_alt = UnboundMethodSpecs::Child1.instance_method(:foo)
+
+ @discard_1 = UnboundMethodSpecs::Methods.instance_method(:discard_1)
+ @discard_2 = UnboundMethodSpecs::Methods.instance_method(:discard_2)
+
+ @method_one = UnboundMethodSpecs::Methods.instance_method(:one)
+ @method_two = UnboundMethodSpecs::Methods.instance_method(:two)
+ end
+
+ it "returns true if objects refer to the same method" do
+ (@from_module == @from_module).should == true
+ (@from_unbind == @from_unbind).should == true
+ (@from_module == @from_unbind).should == true
+ (@from_unbind == @from_module).should == true
+ (@with_block == @with_block).should == true
+ end
+
+ it "returns true if either is an alias for the other" do
+ (@from_module == @alias_1).should == true
+ (@alias_1 == @from_module).should == true
+ end
+
+ it "returns true if both are aliases for a third method" do
+ (@from_module == @alias_1).should == true
+ (@alias_1 == @from_module).should == true
+
+ (@from_module == @alias_2).should == true
+ (@alias_2 == @from_module).should == true
+
+ (@alias_1 == @alias_2).should == true
+ (@alias_2 == @alias_1).should == true
+ end
+
+ it "returns true if same method is extracted from the same subclass" do
+ (@child1 == @child1_alt).should == true
+ (@child1_alt == @child1).should == true
+ end
+
+ it "returns false if UnboundMethods are different methods" do
+ (@method_one == @method_two).should == false
+ (@method_two == @method_one).should == false
+ end
+
+ it "returns false if both have identical body but are not the same" do
+ (@original_body == @identical_body).should == false
+ (@identical_body == @original_body).should == false
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "returns false if same method but one extracted from a subclass" do
+ (@parent == @child1).should == false
+ (@child1 == @parent).should == false
+ end
+
+ it "returns false if same method but extracted from two different subclasses" do
+ (@child2 == @child1).should == false
+ (@child1 == @child2).should == false
+ end
+
+ it "returns false if methods are the same but added from an included Module" do
+ (@includee == @includer).should == false
+ (@includer == @includee).should == false
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "returns true if same method but one extracted from a subclass" do
+ (@parent == @child1).should == true
+ (@child1 == @parent).should == true
+ end
+
+ it "returns false if same method but extracted from two different subclasses" do
+ (@child2 == @child1).should == true
+ (@child1 == @child2).should == true
+ end
+
+ it "returns true if methods are the same but added from an included Module" do
+ (@includee == @includer).should == true
+ (@includer == @includee).should == true
+ end
+ end
+
+ it "returns false if both have same Module, same name, identical body but not the same" do
+ class UnboundMethodSpecs::Methods
+ def discard_1; :discard; end
+ end
+
+ (@discard_1 == UnboundMethodSpecs::Methods.instance_method(:discard_1)).should == false
+ end
+
+ it "considers methods through aliasing equal" do
+ c = Class.new do
+ class << self
+ alias_method :n, :new
+ end
+ end
+
+ c.method(:new).should == c.method(:n)
+ c.method(:n).should == Class.instance_method(:new).bind(c)
+ end
+
+ # On CRuby < 3.2, the 2 specs below pass due to method/instance_method skipping zsuper methods.
+ # We are interested in the general pattern working, i.e. the combination of method/instance_method
+ # and #== exposes the wanted behavior.
+ it "considers methods through visibility change equal" do
+ c = Class.new do
+ class << self
+ private :new
+ end
+ end
+
+ c.method(:new).should == Class.instance_method(:new).bind(c)
+ end
+
+ it "considers methods through aliasing and visibility change equal" do
+ c = Class.new do
+ class << self
+ alias_method :n, :new
+ private :new
+ end
+ end
+
+ c.method(:new).should == c.method(:n)
+ c.method(:n).should == Class.instance_method(:new).bind(c)
+ c.method(:new).should == Class.instance_method(:new).bind(c)
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/fixtures/classes.rb b/spec/ruby/core/unboundmethod/fixtures/classes.rb
new file mode 100644
index 0000000000..6ab958d447
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/fixtures/classes.rb
@@ -0,0 +1,103 @@
+module UnboundMethodSpecs
+
+
+ class SourceLocation
+ def self.location # This needs to be on this line
+ :location # for the spec to pass
+ end
+
+ def self.redefined
+ :first
+ end
+
+ def self.redefined
+ :last
+ end
+
+ def original
+ end
+
+ alias :aka :original
+ end
+
+ module Mod
+ def from_mod; end
+ end
+
+ class Methods
+ include Mod
+
+ def foo
+ true
+ end
+
+ def with_block(&block); end
+
+ alias bar foo
+ alias baz bar
+ alias alias_1 foo
+ alias alias_2 foo
+
+ def original_body(); :this; end
+ def identical_body(); :this; end
+
+ def one; end
+ def two(a); end
+ def three(a, b); end
+ def four(a, b, &c); end
+
+ def neg_one(*a); end
+ def neg_two(a, *b); end
+ def neg_three(a, b, *c); end
+ def neg_four(a, b, *c, &d); end
+
+ def discard_1(); :discard; end
+ def discard_2(); :discard; end
+
+ def my_public_method; end
+ def my_protected_method; end
+ def my_private_method; end
+ protected :my_protected_method
+ private :my_private_method
+ end
+
+ class Parent
+ def foo; end
+ def self.class_method
+ "I am #{name}"
+ end
+ end
+
+ class Child1 < Parent; end
+ class Child2 < Parent; end
+ class Child3 < Parent
+ class << self
+ alias_method :another_class_method, :class_method
+ end
+ end
+
+ class A
+ def baz(a, b)
+ return [__FILE__, self.class]
+ end
+ def overridden; end
+ end
+
+ class B < A
+ def overridden; end
+ end
+
+ class C < B
+ def overridden; end
+ end
+
+ module HashSpecs
+ class SuperClass
+ def foo
+ end
+ end
+
+ class SubClass < SuperClass
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/hash_spec.rb b/spec/ruby/core/unboundmethod/hash_spec.rb
new file mode 100644
index 0000000000..6888675bc1
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/hash_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "UnboundMethod#hash" do
+ it "returns the same value for user methods that are eql?" do
+ foo, bar = UnboundMethodSpecs::Methods.instance_method(:foo), UnboundMethodSpecs::Methods.instance_method(:bar)
+ foo.hash.should == bar.hash
+ end
+
+ # See also redmine #6048
+ it "returns the same value for builtin methods that are eql?" do
+ to_s, inspect = Array.instance_method(:to_s), Array.instance_method(:inspect)
+ to_s.hash.should == inspect.hash
+ end
+
+ it "equals a hash of the same method in the superclass" do
+ foo_in_superclass = UnboundMethodSpecs::HashSpecs::SuperClass.instance_method(:foo)
+ foo = UnboundMethodSpecs::HashSpecs::SubClass.instance_method(:foo)
+
+ foo.hash.should == foo_in_superclass.hash
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/inspect_spec.rb b/spec/ruby/core/unboundmethod/inspect_spec.rb
new file mode 100644
index 0000000000..cecf542fcd
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/inspect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/to_s'
+
+describe "UnboundMethod#inspect" do
+ it_behaves_like :unboundmethod_to_s, :inspect
+end
diff --git a/spec/ruby/core/unboundmethod/name_spec.rb b/spec/ruby/core/unboundmethod/name_spec.rb
new file mode 100644
index 0000000000..4d0fb34fc8
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/name_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "UnboundMethod#name" do
+ it "returns the name of the method" do
+ String.instance_method(:upcase).name.should == :upcase
+ end
+
+ it "returns the name even when aliased" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:foo).unbind.name.should == :foo
+ obj.method(:bar).unbind.name.should == :bar
+ UnboundMethodSpecs::Methods.instance_method(:bar).name.should == :bar
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/original_name_spec.rb b/spec/ruby/core/unboundmethod/original_name_spec.rb
new file mode 100644
index 0000000000..7280dcb2b4
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/original_name_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "UnboundMethod#original_name" do
+ it "returns the name of the method" do
+ String.instance_method(:upcase).original_name.should == :upcase
+ end
+
+ it "returns the original name" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:foo).unbind.original_name.should == :foo
+ obj.method(:bar).unbind.original_name.should == :foo
+ UnboundMethodSpecs::Methods.instance_method(:bar).original_name.should == :foo
+ end
+
+ it "returns the original name even when aliased twice" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:foo).unbind.original_name.should == :foo
+ obj.method(:baz).unbind.original_name.should == :foo
+ UnboundMethodSpecs::Methods.instance_method(:baz).original_name.should == :foo
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/owner_spec.rb b/spec/ruby/core/unboundmethod/owner_spec.rb
new file mode 100644
index 0000000000..e8a734dac4
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/owner_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../method/fixtures/classes'
+
+describe "UnboundMethod#owner" do
+ it "returns the owner of the method" do
+ String.instance_method(:upcase).owner.should == String
+ end
+
+ it "returns the same owner when aliased in the same classes" do
+ UnboundMethodSpecs::Methods.instance_method(:foo).owner.should == UnboundMethodSpecs::Methods
+ UnboundMethodSpecs::Methods.instance_method(:bar).owner.should == UnboundMethodSpecs::Methods
+ end
+
+ it "returns the class/module it was defined in" do
+ UnboundMethodSpecs::C.instance_method(:baz).owner.should == UnboundMethodSpecs::A
+ UnboundMethodSpecs::Methods.instance_method(:from_mod).owner.should == UnboundMethodSpecs::Mod
+ end
+
+ it "returns the new owner for aliased methods on singleton classes" do
+ parent_singleton_class = UnboundMethodSpecs::Parent.singleton_class
+ child_singleton_class = UnboundMethodSpecs::Child3.singleton_class
+
+ child_singleton_class.instance_method(:class_method).owner.should == parent_singleton_class
+ child_singleton_class.instance_method(:another_class_method).owner.should == child_singleton_class
+ end
+
+ ruby_version_is "3.2" do
+ it "returns the class on which public was called for a private method in ancestor" do
+ MethodSpecs::InheritedMethods::C.instance_method(:derp).owner.should == MethodSpecs::InheritedMethods::C
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/parameters_spec.rb b/spec/ruby/core/unboundmethod/parameters_spec.rb
new file mode 100644
index 0000000000..2a3cb18196
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/parameters_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "UnboundMethod#parameters" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/unboundmethod/private_spec.rb b/spec/ruby/core/unboundmethod/private_spec.rb
new file mode 100644
index 0000000000..fa735846bb
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/private_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "UnboundMethod#private?" do
+ it "returns false when the method is public" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_public_method).unbind.private?.should == false
+ end
+
+ it "returns false when the method is protected" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_protected_method).unbind.private?.should == false
+ end
+
+ it "returns true when the method is private" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_private_method).unbind.private?.should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/protected_spec.rb b/spec/ruby/core/unboundmethod/protected_spec.rb
new file mode 100644
index 0000000000..db00e7ef43
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/protected_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "UnboundMethod#protected?" do
+ it "returns false when the method is public" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_public_method).unbind.protected?.should == false
+ end
+
+ it "returns true when the method is protected" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_protected_method).unbind.protected?.should == true
+ end
+
+ it "returns false when the method is private" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_private_method).unbind.protected?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/public_spec.rb b/spec/ruby/core/unboundmethod/public_spec.rb
new file mode 100644
index 0000000000..7b87a03b15
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/public_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1"..."3.2" do
+ describe "UnboundMethod#public?" do
+ it "returns true when the method is public" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_public_method).unbind.public?.should == true
+ end
+
+ it "returns false when the method is protected" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_protected_method).unbind.public?.should == false
+ end
+
+ it "returns false when the method is private" do
+ obj = UnboundMethodSpecs::Methods.new
+ obj.method(:my_private_method).unbind.public?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/shared/to_s.rb b/spec/ruby/core/unboundmethod/shared/to_s.rb
new file mode 100644
index 0000000000..b92bb0b207
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/shared/to_s.rb
@@ -0,0 +1,44 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :unboundmethod_to_s, shared: true do
+ before :each do
+ @from_module = UnboundMethodSpecs::Methods.instance_method(:from_mod)
+ @from_method = UnboundMethodSpecs::Methods.new.method(:from_mod).unbind
+ end
+
+ it "returns a String" do
+ @from_module.send(@method).should be_kind_of(String)
+ @from_method.send(@method).should be_kind_of(String)
+ end
+
+ it "the String reflects that this is an UnboundMethod object" do
+ @from_module.send(@method).should =~ /\bUnboundMethod\b/
+ @from_method.send(@method).should =~ /\bUnboundMethod\b/
+ end
+
+ it "the String shows the method name, Module defined in and Module extracted from" do
+ @from_module.send(@method).should =~ /\bfrom_mod\b/
+ @from_module.send(@method).should =~ /\bUnboundMethodSpecs::Mod\b/
+
+ ruby_version_is ""..."3.2" do
+ @from_method.send(@method).should =~ /\bUnboundMethodSpecs::Methods\b/
+ end
+ end
+
+ it "returns a String including all details" do
+ ruby_version_is ""..."3.2" do
+ @from_module.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Methods(UnboundMethodSpecs::Mod)#from_mod"
+ @from_method.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Methods(UnboundMethodSpecs::Mod)#from_mod"
+ end
+
+ ruby_version_is "3.2" do
+ @from_module.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Mod#from_mod"
+ @from_method.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Mod#from_mod"
+ end
+ end
+
+ it "does not show the defining module if it is the same as the origin" do
+ UnboundMethodSpecs::A.instance_method(:baz).send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::A#baz"
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/source_location_spec.rb b/spec/ruby/core/unboundmethod/source_location_spec.rb
new file mode 100644
index 0000000000..96933a5d40
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/source_location_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "UnboundMethod#source_location" do
+ before :each do
+ @method = UnboundMethodSpecs::SourceLocation.method(:location).unbind
+ end
+
+ it "sets the first value to the path of the file in which the method was defined" do
+ file = @method.source_location.first
+ file.should be_an_instance_of(String)
+ file.should == File.realpath('../fixtures/classes.rb', __FILE__)
+ end
+
+ it "sets the last value to an Integer representing the line on which the method was defined" do
+ line = @method.source_location.last
+ line.should be_an_instance_of(Integer)
+ line.should == 5
+ end
+
+ it "returns the last place the method was defined" do
+ UnboundMethodSpecs::SourceLocation.method(:redefined).unbind.source_location.last.should == 13
+ end
+
+ it "returns the location of the original method even if it was aliased" do
+ UnboundMethodSpecs::SourceLocation.instance_method(:aka).source_location.last.should == 17
+ end
+
+ it "works for define_method methods" do
+ line = nil
+ cls = Class.new do
+ line = __LINE__ + 1
+ define_method(:foo) { }
+ end
+
+ method = cls.instance_method(:foo)
+ method.source_location[0].should =~ /#{__FILE__}/
+ method.source_location[1].should == line
+ end
+
+ it "works for define_singleton_method methods" do
+ line = nil
+ cls = Class.new do
+ line = __LINE__ + 1
+ define_singleton_method(:foo) { }
+ end
+
+ method = cls.method(:foo)
+ method.source_location[0].should =~ /#{__FILE__}/
+ method.source_location[1].should == line
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/super_method_spec.rb b/spec/ruby/core/unboundmethod/super_method_spec.rb
new file mode 100644
index 0000000000..101c83b8b3
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/super_method_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../method/fixtures/classes'
+
+describe "UnboundMethod#super_method" do
+ it "returns the method that would be called by super in the method" do
+ meth = UnboundMethodSpecs::C.instance_method(:overridden)
+ meth = meth.super_method
+ meth.should == UnboundMethodSpecs::B.instance_method(:overridden)
+ meth = meth.super_method
+ meth.should == UnboundMethodSpecs::A.instance_method(:overridden)
+ end
+
+ it "returns nil when there's no super method in the parent" do
+ method = Kernel.instance_method(:method)
+ method.super_method.should == nil
+ end
+
+ it "returns nil when the parent's method is removed" do
+ parent = Class.new { def foo; end }
+ child = Class.new(parent) { def foo; end }
+
+ method = child.instance_method(:foo)
+
+ parent.send(:undef_method, :foo)
+
+ method.super_method.should == nil
+ end
+
+ # https://github.com/jruby/jruby/issues/7240
+ context "after changing an inherited methods visibility" do
+ it "calls the proper super method" do
+ method = MethodSpecs::InheritedMethods::C.instance_method(:derp)
+ method.bind(MethodSpecs::InheritedMethods::C.new).call.should == 'BA'
+ end
+
+ it "returns the expected super_method" do
+ method = MethodSpecs::InheritedMethods::C.instance_method(:derp)
+ method.super_method.owner.should == MethodSpecs::InheritedMethods::A
+ end
+ end
+
+ ruby_version_is "2.7.3" do
+ context "after aliasing an inherited method" do
+ it "returns the expected super_method" do
+ method = MethodSpecs::InheritedMethods::C.instance_method(:meow)
+ method.super_method.owner.should == MethodSpecs::InheritedMethods::A
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/unboundmethod/to_s_spec.rb b/spec/ruby/core/unboundmethod/to_s_spec.rb
new file mode 100644
index 0000000000..a508229b49
--- /dev/null
+++ b/spec/ruby/core/unboundmethod/to_s_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/to_s'
+
+describe "UnboundMethod#to_s" do
+ it_behaves_like :unboundmethod_to_s, :to_s
+end
diff --git a/spec/ruby/core/warning/element_reference_spec.rb b/spec/ruby/core/warning/element_reference_spec.rb
new file mode 100644
index 0000000000..67728ca0f6
--- /dev/null
+++ b/spec/ruby/core/warning/element_reference_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "Warning.[]" do
+ ruby_version_is '2.7.2' do
+ it "returns default values for categories :deprecated and :experimental" do
+ ruby_exe('p Warning[:deprecated]').chomp.should == "false"
+ ruby_exe('p Warning[:experimental]').chomp.should == "true"
+ end
+ end
+
+ it "raises for unknown category" do
+ -> { Warning[:noop] }.should raise_error(ArgumentError, /unknown category: noop/)
+ end
+
+ it "raises for non-Symbol category" do
+ -> { Warning[42] }.should raise_error(TypeError)
+ -> { Warning[false] }.should raise_error(TypeError)
+ -> { Warning["noop"] }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/warning/element_set_spec.rb b/spec/ruby/core/warning/element_set_spec.rb
new file mode 100644
index 0000000000..d20ee215ad
--- /dev/null
+++ b/spec/ruby/core/warning/element_set_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "Warning.[]=" do
+ it "emits and suppresses warnings for :deprecated" do
+ ruby_exe('Warning[:deprecated] = true; $; = ""', args: "2>&1").should =~ /is deprecated/
+ ruby_exe('Warning[:deprecated] = false; $; = ""', args: "2>&1").should == ""
+ end
+
+ describe ":experimental" do
+ before do
+ ruby_version_is ""..."3.0" do
+ @src = 'case [0, 1]; in [a, b]; end'
+ end
+
+ ruby_version_is "3.0" do
+ @src = 'warn "This is experimental warning.", category: :experimental'
+ end
+ end
+
+ it "emits and suppresses warnings for :experimental" do
+ ruby_exe("Warning[:experimental] = true; eval('#{@src}')", args: "2>&1").should =~ /is experimental/
+ ruby_exe("Warning[:experimental] = false; eval('#{@src}')", args: "2>&1").should == ""
+ end
+ end
+
+ it "raises for unknown category" do
+ -> { Warning[:noop] = false }.should raise_error(ArgumentError, /unknown category: noop/)
+ end
+
+ it "raises for non-Symbol category" do
+ -> { Warning[42] = false }.should raise_error(TypeError)
+ -> { Warning[false] = false }.should raise_error(TypeError)
+ -> { Warning["noop"] = false }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/warning/warn_spec.rb b/spec/ruby/core/warning/warn_spec.rb
new file mode 100644
index 0000000000..5ccff3aa2b
--- /dev/null
+++ b/spec/ruby/core/warning/warn_spec.rb
@@ -0,0 +1,90 @@
+require_relative '../../spec_helper'
+
+describe "Warning.warn" do
+ it "complains" do
+ -> {
+ Warning.warn("Chunky bacon!")
+ }.should complain("Chunky bacon!")
+ end
+
+ it "does not add a newline" do
+ ruby_exe("Warning.warn('test')", args: "2>&1").should == "test"
+ end
+
+ it "returns nil" do
+ ruby_exe("p Warning.warn('test')", args: "2>&1").should == "testnil\n"
+ end
+
+ it "extends itself" do
+ Warning.singleton_class.ancestors.should include(Warning)
+ end
+
+ it "has Warning as the method owner" do
+ ruby_exe("p Warning.method(:warn).owner").should == "Warning\n"
+ end
+
+ it "can be overridden" do
+ code = <<-RUBY
+ $stdout.sync = true
+ $stderr.sync = true
+ def Warning.warn(msg)
+ if msg.start_with?("A")
+ puts msg.upcase
+ else
+ super
+ end
+ end
+ Warning.warn("A warning!")
+ Warning.warn("warning from stderr\n")
+ RUBY
+ ruby_exe(code, args: "2>&1").should == %Q[A WARNING!\nwarning from stderr\n]
+ end
+
+ it "is called by parser warnings" do
+ Warning.should_receive(:warn)
+ verbose = $VERBOSE
+ $VERBOSE = false
+ begin
+ eval "{ key: :value, key: :value2 }"
+ ensure
+ $VERBOSE = verbose
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "is called by Kernel.warn with nil category keyword" do
+ Warning.should_receive(:warn).with("Chunky bacon!\n", category: nil)
+ verbose = $VERBOSE
+ $VERBOSE = false
+ begin
+ Kernel.warn("Chunky bacon!")
+ ensure
+ $VERBOSE = verbose
+ end
+ end
+
+ it "is called by Kernel.warn with given category keyword converted to a symbol" do
+ Warning.should_receive(:warn).with("Chunky bacon!\n", category: :deprecated)
+ verbose = $VERBOSE
+ $VERBOSE = false
+ begin
+ Kernel.warn("Chunky bacon!", category: "deprecated")
+ ensure
+ $VERBOSE = verbose
+ end
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "is called by Kernel.warn" do
+ Warning.should_receive(:warn).with("Chunky bacon!\n")
+ verbose = $VERBOSE
+ $VERBOSE = false
+ begin
+ Kernel.warn("Chunky bacon!")
+ ensure
+ $VERBOSE = verbose
+ end
+ end
+ end
+end
diff --git a/spec/ruby/default.mspec b/spec/ruby/default.mspec
new file mode 100644
index 0000000000..a0dc69c03d
--- /dev/null
+++ b/spec/ruby/default.mspec
@@ -0,0 +1,50 @@
+# Configuration file for Ruby >= 2.0 implementations.
+
+class MSpecScript
+ # Language features specs
+ set :language, [ 'language' ]
+
+ # Core library specs
+ set :core, [ 'core' ]
+
+ # Standard library specs
+ set :library, [ 'library' ]
+
+ # Command line specs
+ set :command_line, [ 'command_line' ]
+
+ # Security specs
+ set :security, [ 'security' ]
+
+ # C extension API specs
+ set :capi, [ 'optional/capi' ]
+
+ # A list of _all_ optional specs
+ set :optional, get(:capi)
+
+ # An ordered list of the directories containing specs to run
+ set :files, get(:command_line) + get(:language) + get(:core) + get(:library) + get(:security) + get(:optional)
+
+ # This set of files is run by mspec ci
+ set :ci_files, get(:files)
+
+ # The default implementation to run the specs.
+ set :target, 'ruby'
+
+ set :backtrace_filter, /mspec\//
+
+ set :tags_patterns, [
+ [%r(language/), 'tags/language/'],
+ [%r(core/), 'tags/core/'],
+ [%r(command_line/), 'tags/command_line/'],
+ [%r(library/), 'tags/library/'],
+ [%r(security/), 'tags/security/'],
+ [/_spec\.rb$/, '_tags.txt']
+ ]
+
+ set :toplevel_constants_excludes, [
+ /\wSpecs?$/,
+ /^CS_CONST/,
+ /^CSL_CONST/,
+ ]
+end
diff --git a/spec/ruby/fixtures/basicobject/method_missing.rb b/spec/ruby/fixtures/basicobject/method_missing.rb
new file mode 100644
index 0000000000..17a0fe904c
--- /dev/null
+++ b/spec/ruby/fixtures/basicobject/method_missing.rb
@@ -0,0 +1,55 @@
+module KernelSpecs
+ module ModuleNoMM
+ class << self
+ def method_public() :module_public_method end
+
+ def method_protected() :module_private_method end
+ protected :method_protected
+
+ def method_private() :module_private_method end
+ private :method_private
+ end
+ end
+
+ module ModuleMM
+ class << self
+ def method_missing(*args) :module_method_missing end
+
+ def method_public() :module_public_method end
+
+ def method_protected() :module_private_method end
+ protected :method_protected
+
+ def method_private() :module_private_method end
+ private :method_private
+ end
+ end
+
+ class ClassNoMM
+ class << self
+ def method_public() :class_public_method end
+
+ def method_protected() :class_private_method end
+ protected :method_protected
+
+ def method_private() :class_private_method end
+ private :method_private
+ end
+
+ def method_public() :instance_public_method end
+
+ def method_protected() :instance_private_method end
+ protected :method_protected
+
+ def method_private() :instance_private_method end
+ private :method_private
+ end
+
+ class ClassMM < ClassNoMM
+ class << self
+ def method_missing(*args) :class_method_missing end
+ end
+
+ def method_missing(*args) :instance_method_missing end
+ end
+end
diff --git a/spec/ruby/fixtures/class.rb b/spec/ruby/fixtures/class.rb
new file mode 100644
index 0000000000..98cb6c82a2
--- /dev/null
+++ b/spec/ruby/fixtures/class.rb
@@ -0,0 +1,142 @@
+module ClassSpecs
+
+ def self.sclass_with_block
+ eval <<-RUBY
+ class << self
+ yield
+ end
+ RUBY
+ end
+
+ def self.sclass_with_return
+ class << self
+ return :inner
+ end
+ return :outer
+ end
+
+ class A; end
+
+ def self.string_class_variables(obj)
+ obj.class_variables.map { |x| x.to_s }
+ end
+
+ def self.string_instance_variables(obj)
+ obj.instance_variables.map { |x| x.to_s }
+ end
+
+ class B
+ @@cvar = :cvar
+ @ivar = :ivar
+
+ end
+
+ class C
+ def self.make_class_variable
+ @@cvar = :cvar
+ end
+
+ def self.make_class_instance_variable
+ @civ = :civ
+ end
+ end
+
+ class D
+ def make_class_variable
+ @@cvar = :cvar
+ end
+ end
+
+ class E
+ def self.cmeth() :cmeth end
+ def meth() :meth end
+
+ class << self
+ def smeth() :smeth end
+ end
+
+ CONSTANT = :constant!
+ end
+
+ class F; end
+ class F
+ def meth() :meth end
+ end
+ class F
+ def another() :another end
+ end
+
+ class G
+ def override() :nothing end
+ def override() :override end
+ end
+
+ class Container
+ class A; end
+ class B; end
+ end
+
+ O = Object.new
+ class << O
+ def smeth
+ :smeth
+ end
+ end
+
+ class H
+ def self.inherited(sub)
+ track_inherited << sub
+ end
+
+ def self.track_inherited
+ @inherited_modules ||= []
+ end
+ end
+
+ class K < H; end
+
+ class I
+ class J < self
+ end
+ end
+
+ class K
+ def example_instance_method
+ end
+ def self.example_class_method
+ end
+ end
+
+ class L; end
+
+ class M < L; end
+
+ # Can't use a method here because of class definition in method body error
+ ANON_CLASS_FOR_NEW = -> do
+ Class.new do
+ class NamedInModule
+ end
+
+ def self.get_class_name
+ NamedInModule.name
+ end
+ end
+ end
+
+ DEFINE_CLASS = -> do
+ class ::A; end
+ end
+end
+
+class Class
+ def example_instance_method_of_class; end
+ def self.example_class_method_of_class; end
+end
+class << Class
+ def example_instance_method_of_singleton_class; end
+ def self.example_class_method_of_singleton_class; end
+end
+class Object
+ def example_instance_method_of_object; end
+ def self.example_class_method_of_object; end
+end
diff --git a/spec/ruby/fixtures/class_variables.rb b/spec/ruby/fixtures/class_variables.rb
new file mode 100644
index 0000000000..02ca042230
--- /dev/null
+++ b/spec/ruby/fixtures/class_variables.rb
@@ -0,0 +1,58 @@
+module ClassVariablesSpec
+
+ class ClassA
+ @@cvar_a = :cvar_a
+
+ def cvar_a
+ @@cvar_a
+ end
+
+ def cvar_a=(val)
+ @@cvar_a = val
+ end
+ end
+
+ class ClassB < ClassA; end
+
+ # Extended in ClassC
+ module ModuleM
+ @@cvar_m = :value
+
+ def cvar_m
+ @@cvar_m
+ end
+
+ def cvar_m=(val)
+ @@cvar_m = val
+ end
+ end
+
+ # Extended in ModuleO
+ module ModuleN
+ @@cvar_n = :value
+
+ def cvar_n
+ @@cvar_n
+ end
+
+ def cvar_n=(val)
+ @@cvar_n = val
+ end
+ end
+
+ module ModuleO
+ extend ModuleN
+ end
+
+ class ClassC
+ extend ModuleM
+
+ def self.cvar_defined?
+ self.class_variable_defined?(:@@cvar)
+ end
+
+ def self.cvar_c=(val)
+ @@cvar = val
+ end
+ end
+end
diff --git a/spec/ruby/fixtures/code/a/load_fixture.bundle b/spec/ruby/fixtures/code/a/load_fixture.bundle
new file mode 100644
index 0000000000..30b53d7c0c
--- /dev/null
+++ b/spec/ruby/fixtures/code/a/load_fixture.bundle
@@ -0,0 +1 @@
+ScratchPad << :ext_bundle
diff --git a/spec/ruby/fixtures/code/a/load_fixture.dll b/spec/ruby/fixtures/code/a/load_fixture.dll
new file mode 100644
index 0000000000..77c65b315b
--- /dev/null
+++ b/spec/ruby/fixtures/code/a/load_fixture.dll
@@ -0,0 +1 @@
+ScratchPad << :ext_dll
diff --git a/spec/ruby/fixtures/code/a/load_fixture.dylib b/spec/ruby/fixtures/code/a/load_fixture.dylib
new file mode 100644
index 0000000000..31f4b1a7bb
--- /dev/null
+++ b/spec/ruby/fixtures/code/a/load_fixture.dylib
@@ -0,0 +1 @@
+ScratchPad << :ext_dylib
diff --git a/spec/ruby/fixtures/code/a/load_fixture.so b/spec/ruby/fixtures/code/a/load_fixture.so
new file mode 100644
index 0000000000..4d02dea086
--- /dev/null
+++ b/spec/ruby/fixtures/code/a/load_fixture.so
@@ -0,0 +1 @@
+ScratchPad << :ext_so
diff --git a/spec/ruby/fixtures/code/b/load_fixture.rb b/spec/ruby/fixtures/code/b/load_fixture.rb
new file mode 100644
index 0000000000..4a6e9c9601
--- /dev/null
+++ b/spec/ruby/fixtures/code/b/load_fixture.rb
@@ -0,0 +1 @@
+ScratchPad << :loaded
diff --git a/spec/ruby/fixtures/code/c/load_fixture.rb b/spec/ruby/fixtures/code/c/load_fixture.rb
new file mode 100644
index 0000000000..4a6e9c9601
--- /dev/null
+++ b/spec/ruby/fixtures/code/c/load_fixture.rb
@@ -0,0 +1 @@
+ScratchPad << :loaded
diff --git a/spec/ruby/fixtures/code/concurrent.rb b/spec/ruby/fixtures/code/concurrent.rb
new file mode 100644
index 0000000000..b3602a3db4
--- /dev/null
+++ b/spec/ruby/fixtures/code/concurrent.rb
@@ -0,0 +1,12 @@
+ScratchPad.recorded << :con_pre
+Thread.current[:in_concurrent_rb] = true
+
+if t = Thread.current[:wait_for]
+ Thread.pass until t.backtrace && t.backtrace.any? { |call| call.include? 'require' } && t.stop?
+end
+
+if Thread.current[:con_raise]
+ raise "con1"
+end
+
+ScratchPad.recorded << :con_post
diff --git a/spec/ruby/fixtures/code/concurrent2.rb b/spec/ruby/fixtures/code/concurrent2.rb
new file mode 100644
index 0000000000..08a7264023
--- /dev/null
+++ b/spec/ruby/fixtures/code/concurrent2.rb
@@ -0,0 +1,8 @@
+ScratchPad.recorded << :con2_pre
+
+Thread.current[:in_concurrent_rb2] = true
+
+t = Thread.current[:concurrent_require_thread]
+Thread.pass until t[:in_concurrent_rb3]
+
+ScratchPad.recorded << :con2_post
diff --git a/spec/ruby/fixtures/code/concurrent3.rb b/spec/ruby/fixtures/code/concurrent3.rb
new file mode 100644
index 0000000000..edb441d15d
--- /dev/null
+++ b/spec/ruby/fixtures/code/concurrent3.rb
@@ -0,0 +1,2 @@
+ScratchPad.recorded << :con3
+Thread.current[:in_concurrent_rb3] = true
diff --git a/spec/ruby/fixtures/code/concurrent_require_fixture.rb b/spec/ruby/fixtures/code/concurrent_require_fixture.rb
new file mode 100644
index 0000000000..d4ce734183
--- /dev/null
+++ b/spec/ruby/fixtures/code/concurrent_require_fixture.rb
@@ -0,0 +1,4 @@
+object = ScratchPad.recorded
+thread = Thread.new { object.require(__FILE__) }
+Thread.pass until thread.stop?
+ScratchPad.record(thread)
diff --git a/spec/ruby/fixtures/code/file_fixture.rb b/spec/ruby/fixtures/code/file_fixture.rb
new file mode 100644
index 0000000000..27388c7d8d
--- /dev/null
+++ b/spec/ruby/fixtures/code/file_fixture.rb
@@ -0,0 +1 @@
+ScratchPad << __FILE__
diff --git a/spec/ruby/fixtures/code/gem/load_fixture.rb b/spec/ruby/fixtures/code/gem/load_fixture.rb
new file mode 100644
index 0000000000..e1806de201
--- /dev/null
+++ b/spec/ruby/fixtures/code/gem/load_fixture.rb
@@ -0,0 +1 @@
+ScratchPad << :loaded_gem
diff --git a/spec/ruby/fixtures/code/line_fixture.rb b/spec/ruby/fixtures/code/line_fixture.rb
new file mode 100644
index 0000000000..2a2a0568cd
--- /dev/null
+++ b/spec/ruby/fixtures/code/line_fixture.rb
@@ -0,0 +1,5 @@
+ScratchPad << __LINE__
+
+# line 3
+
+ScratchPad << __LINE__
diff --git a/spec/ruby/fixtures/code/load_ext_fixture.rb b/spec/ruby/fixtures/code/load_ext_fixture.rb
new file mode 100644
index 0000000000..4a6e9c9601
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_ext_fixture.rb
@@ -0,0 +1 @@
+ScratchPad << :loaded
diff --git a/spec/ruby/fixtures/code/load_fixture b/spec/ruby/fixtures/code/load_fixture
new file mode 100644
index 0000000000..1b76dc8cad
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture
@@ -0,0 +1 @@
+ScratchPad << :no_ext
diff --git a/spec/ruby/fixtures/code/load_fixture.bundle b/spec/ruby/fixtures/code/load_fixture.bundle
new file mode 100644
index 0000000000..30b53d7c0c
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.bundle
@@ -0,0 +1 @@
+ScratchPad << :ext_bundle
diff --git a/spec/ruby/fixtures/code/load_fixture.dll b/spec/ruby/fixtures/code/load_fixture.dll
new file mode 100644
index 0000000000..77c65b315b
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.dll
@@ -0,0 +1 @@
+ScratchPad << :ext_dll
diff --git a/spec/ruby/fixtures/code/load_fixture.dylib b/spec/ruby/fixtures/code/load_fixture.dylib
new file mode 100644
index 0000000000..31f4b1a7bb
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.dylib
@@ -0,0 +1 @@
+ScratchPad << :ext_dylib
diff --git a/spec/ruby/fixtures/code/load_fixture.ext b/spec/ruby/fixtures/code/load_fixture.ext
new file mode 100644
index 0000000000..f25b8e2951
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.ext
@@ -0,0 +1 @@
+ScratchPad << :no_rb_ext
diff --git a/spec/ruby/fixtures/code/load_fixture.ext.bundle b/spec/ruby/fixtures/code/load_fixture.ext.bundle
new file mode 100644
index 0000000000..30b53d7c0c
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.ext.bundle
@@ -0,0 +1 @@
+ScratchPad << :ext_bundle
diff --git a/spec/ruby/fixtures/code/load_fixture.ext.dll b/spec/ruby/fixtures/code/load_fixture.ext.dll
new file mode 100644
index 0000000000..77c65b315b
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.ext.dll
@@ -0,0 +1 @@
+ScratchPad << :ext_dll
diff --git a/spec/ruby/fixtures/code/load_fixture.ext.dylib b/spec/ruby/fixtures/code/load_fixture.ext.dylib
new file mode 100644
index 0000000000..31f4b1a7bb
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.ext.dylib
@@ -0,0 +1 @@
+ScratchPad << :ext_dylib
diff --git a/spec/ruby/fixtures/code/load_fixture.ext.rb b/spec/ruby/fixtures/code/load_fixture.ext.rb
new file mode 100644
index 0000000000..4a6e9c9601
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.ext.rb
@@ -0,0 +1 @@
+ScratchPad << :loaded
diff --git a/spec/ruby/fixtures/code/load_fixture.ext.so b/spec/ruby/fixtures/code/load_fixture.ext.so
new file mode 100644
index 0000000000..4d02dea086
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.ext.so
@@ -0,0 +1 @@
+ScratchPad << :ext_so
diff --git a/spec/ruby/fixtures/code/load_fixture.rb b/spec/ruby/fixtures/code/load_fixture.rb
new file mode 100644
index 0000000000..4a6e9c9601
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.rb
@@ -0,0 +1 @@
+ScratchPad << :loaded
diff --git a/spec/ruby/fixtures/code/load_fixture.so b/spec/ruby/fixtures/code/load_fixture.so
new file mode 100644
index 0000000000..4d02dea086
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture.so
@@ -0,0 +1 @@
+ScratchPad << :ext_so
diff --git a/spec/ruby/fixtures/code/load_fixture_and__FILE__.rb b/spec/ruby/fixtures/code/load_fixture_and__FILE__.rb
new file mode 100644
index 0000000000..27388c7d8d
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_fixture_and__FILE__.rb
@@ -0,0 +1 @@
+ScratchPad << __FILE__
diff --git a/spec/ruby/fixtures/code/load_wrap_fixture.rb b/spec/ruby/fixtures/code/load_wrap_fixture.rb
new file mode 100644
index 0000000000..526bbf8c82
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_wrap_fixture.rb
@@ -0,0 +1,12 @@
+class LoadSpecWrap
+ ScratchPad << String
+end
+
+LOAD_WRAP_SPECS_TOP_LEVEL_CONSTANT = 1
+
+def load_wrap_specs_top_level_method
+ :load_wrap_specs_top_level_method
+end
+ScratchPad << method(:load_wrap_specs_top_level_method).owner
+
+ScratchPad << self
diff --git a/spec/ruby/fixtures/code/load_wrap_method_fixture.rb b/spec/ruby/fixtures/code/load_wrap_method_fixture.rb
new file mode 100644
index 0000000000..b617473b4d
--- /dev/null
+++ b/spec/ruby/fixtures/code/load_wrap_method_fixture.rb
@@ -0,0 +1,9 @@
+def top_level_method
+ ::ScratchPad << :load_wrap_loaded
+end
+
+begin
+ top_level_method
+rescue NameError
+ ::ScratchPad << :load_wrap_error
+end
diff --git a/spec/ruby/fixtures/code/methods_fixture.rb b/spec/ruby/fixtures/code/methods_fixture.rb
new file mode 100644
index 0000000000..d42b5e4018
--- /dev/null
+++ b/spec/ruby/fixtures/code/methods_fixture.rb
@@ -0,0 +1,364 @@
+def foo1
+end
+
+def foo2
+end
+
+def foo3
+end
+
+def foo4
+end
+
+def foo5
+end
+
+def foo6
+end
+
+def foo7
+end
+
+def foo8
+end
+
+def foo9
+end
+
+def foo10
+end
+
+def foo11
+end
+
+def foo12
+end
+
+def foo13
+end
+
+def foo14
+end
+
+def foo15
+end
+
+def foo16
+end
+
+def foo17
+end
+
+def foo18
+end
+
+def foo19
+end
+
+def foo20
+end
+
+def foo21
+end
+
+def foo22
+end
+
+def foo23
+end
+
+def foo24
+end
+
+def foo25
+end
+
+def foo26
+end
+
+def foo27
+end
+
+def foo28
+end
+
+def foo29
+end
+
+def foo30
+end
+
+def foo31
+end
+
+def foo32
+end
+
+def foo33
+end
+
+def foo34
+end
+
+def foo35
+end
+
+def foo36
+end
+
+def foo37
+end
+
+def foo38
+end
+
+def foo39
+end
+
+def foo40
+end
+
+def foo41
+end
+
+def foo42
+end
+
+def foo43
+end
+
+def foo44
+end
+
+def foo45
+end
+
+def foo46
+end
+
+def foo47
+end
+
+def foo48
+end
+
+def foo49
+end
+
+def foo50
+end
+
+def foo51
+end
+
+def foo52
+end
+
+def foo53
+end
+
+def foo54
+end
+
+def foo55
+end
+
+def foo56
+end
+
+def foo57
+end
+
+def foo58
+end
+
+def foo59
+end
+
+def foo60
+end
+
+def foo61
+end
+
+def foo62
+end
+
+def foo63
+end
+
+def foo64
+end
+
+def foo65
+end
+
+def foo66
+end
+
+def foo67
+end
+
+def foo68
+end
+
+def foo69
+end
+
+def foo70
+end
+
+def foo71
+end
+
+def foo72
+end
+
+def foo73
+end
+
+def foo74
+end
+
+def foo75
+end
+
+def foo76
+end
+
+def foo77
+end
+
+def foo78
+end
+
+def foo79
+end
+
+def foo80
+end
+
+def foo81
+end
+
+def foo82
+end
+
+def foo83
+end
+
+def foo84
+end
+
+def foo85
+end
+
+def foo86
+end
+
+def foo87
+end
+
+def foo88
+end
+
+def foo89
+end
+
+def foo90
+end
+
+def foo91
+end
+
+def foo92
+end
+
+def foo93
+end
+
+def foo94
+end
+
+def foo95
+end
+
+def foo96
+end
+
+def foo97
+end
+
+def foo98
+end
+
+def foo99
+end
+
+def foo100
+end
+
+def foo101
+end
+
+def foo102
+end
+
+def foo103
+end
+
+def foo104
+end
+
+def foo105
+end
+
+def foo106
+end
+
+def foo107
+end
+
+def foo108
+end
+
+def foo109
+end
+
+def foo110
+end
+
+def foo111
+end
+
+def foo112
+end
+
+def foo113
+end
+
+def foo114
+end
+
+def foo115
+end
+
+def foo116
+end
+
+def foo117
+end
+
+def foo118
+end
+
+def foo119
+end
+
+def foo120
+end
+
+def foo121
+end
+
+ScratchPad << :loaded
diff --git a/spec/ruby/fixtures/code/raise_fixture.rb b/spec/ruby/fixtures/code/raise_fixture.rb
new file mode 100644
index 0000000000..9419089a06
--- /dev/null
+++ b/spec/ruby/fixtures/code/raise_fixture.rb
@@ -0,0 +1 @@
+raise "Exception loading a file"
diff --git a/spec/ruby/fixtures/code/recursive_load_fixture.rb b/spec/ruby/fixtures/code/recursive_load_fixture.rb
new file mode 100644
index 0000000000..18b144d44a
--- /dev/null
+++ b/spec/ruby/fixtures/code/recursive_load_fixture.rb
@@ -0,0 +1,5 @@
+ScratchPad << :loaded
+
+if ScratchPad.recorded == [:loaded]
+ load File.expand_path("../recursive_load_fixture.rb", __FILE__)
+end
diff --git a/spec/ruby/fixtures/code/recursive_require_fixture.rb b/spec/ruby/fixtures/code/recursive_require_fixture.rb
new file mode 100644
index 0000000000..ebeba34fce
--- /dev/null
+++ b/spec/ruby/fixtures/code/recursive_require_fixture.rb
@@ -0,0 +1,3 @@
+require_relative 'recursive_require_fixture'
+
+ScratchPad << :loaded
diff --git a/spec/ruby/fixtures/code/symlink/symlink1.rb b/spec/ruby/fixtures/code/symlink/symlink1.rb
new file mode 100644
index 0000000000..6a006eef14
--- /dev/null
+++ b/spec/ruby/fixtures/code/symlink/symlink1.rb
@@ -0,0 +1 @@
+require_relative 'symlink2/symlink2'
diff --git a/spec/ruby/fixtures/code/symlink/symlink2/symlink2.rb b/spec/ruby/fixtures/code/symlink/symlink2/symlink2.rb
new file mode 100644
index 0000000000..4a6e9c9601
--- /dev/null
+++ b/spec/ruby/fixtures/code/symlink/symlink2/symlink2.rb
@@ -0,0 +1 @@
+ScratchPad << :loaded
diff --git a/spec/ruby/fixtures/code_loading.rb b/spec/ruby/fixtures/code_loading.rb
new file mode 100644
index 0000000000..decd56a358
--- /dev/null
+++ b/spec/ruby/fixtures/code_loading.rb
@@ -0,0 +1,41 @@
+module CodeLoadingSpecs
+ # The #require instance method is private, so this class enables
+ # calling #require like obj.require(file). This is used to share
+ # specs between Kernel#require and Kernel.require.
+ class Method
+ def require(name)
+ super name
+ end
+
+ def load(name, wrap=false)
+ super
+ end
+ end
+
+ def self.preload_rubygems
+ # Require RubyGems eagerly, to ensure #require is already the RubyGems
+ # version and RubyGems is only loaded once, before starting #require/#autoload specs
+ # which snapshot $LOADED_FEATURES and could cause RubyGems to load twice.
+ # #require specs also snapshot #require, and could end up redefining #require as the original core Kernel#require.
+ @rubygems ||= begin
+ require "rubygems"
+ true
+ rescue LoadError
+ true
+ end
+ end
+
+ def self.spec_setup
+ preload_rubygems
+
+ @saved_loaded_features = $LOADED_FEATURES.clone
+ @saved_load_path = $LOAD_PATH.clone
+ ScratchPad.record []
+ end
+
+ def self.spec_cleanup
+ $LOADED_FEATURES.replace @saved_loaded_features
+ $LOAD_PATH.replace @saved_load_path
+ ScratchPad.clear
+ end
+end
diff --git a/spec/ruby/fixtures/constants.rb b/spec/ruby/fixtures/constants.rb
new file mode 100644
index 0000000000..47a8e87e56
--- /dev/null
+++ b/spec/ruby/fixtures/constants.rb
@@ -0,0 +1,323 @@
+# Contains all static code examples of all constants behavior in language and
+# library specs. The specs include language/constants_spec.rb and the specs
+# for Module#const_defined?, Module#const_get, Module#const_set, Module#remove_const,
+# Module#const_source_location, Module#const_missing and Module#constants.
+#
+# Rather than defining a class structure for each example, a canonical set of
+# classes is used along with numerous constants, in most cases, a unique
+# constant for each facet of behavior. This potentially leads to some
+# redundancy but hopefully the minimal redundancy that includes reasonable
+# variety in class and module configurations, including hierarchy,
+# containment, inclusion, singletons and toplevel.
+#
+# Constants are numbered for for uniqueness. The CS_ prefix is uniformly used
+# and is to minimize clashes with other toplevel constants (see e.g. ModuleA
+# which is included in Object). Constant values are symbols. A numbered suffix
+# is used to distinguish constants with the same name defined in different
+# areas (e.g. CS_CONST10 has values :const10_1, :const10_2, etc.).
+#
+# Methods are named after the constants they reference (e.g. ClassA.const10
+# references CS_CONST10). Where it is reasonable to do so, both class and
+# instance methods are defined. This is an instance of redundancy (class
+# methods should behave no differently than instance methods) but is useful
+# for ensuring compliance in implementations.
+
+
+# This constant breaks the rule of defining all constants, classes, modules
+# inside a module namespace for the particular specs, however, it is needed
+# for completeness. No other constant of this name should be defined in the
+# specs.
+CS_CONST1 = :const1 # only defined here
+CS_CONST1_LINE = __LINE__ - 1
+
+module ConstantSpecs
+
+ # Included at toplevel
+ module ModuleA
+ CS_CONST10 = :const10_1
+ CS_CONST10_LINE = __LINE__ - 1
+ CS_CONST12 = :const12_2
+ CS_CONST13 = :const13
+ CS_CONST13_LINE = __LINE__ - 1
+ CS_CONST21 = :const21_2
+ end
+
+ # Included in ParentA
+ module ModuleB
+ CS_CONST10 = :const10_9
+ CS_CONST11 = :const11_2
+ CS_CONST12 = :const12_1
+ CS_CONST12_LINE = __LINE__ - 1
+ end
+
+ # Included in ChildA
+ module ModuleC
+ CS_CONST10 = :const10_4
+ CS_CONST15 = :const15_1
+ CS_CONST15_LINE = __LINE__ - 1
+ end
+
+ # Included in ChildA metaclass
+ module ModuleH
+ CS_CONST10 = :const10_7
+ end
+
+ # Included in ModuleD
+ module ModuleM
+ CS_CONST10 = :const10_11
+ CS_CONST24 = :const24
+ end
+
+ # Included in ContainerA
+ module ModuleD
+ include ModuleM
+
+ CS_CONST10 = :const10_8
+ end
+
+ # Included in ContainerA
+ module ModuleIncludePrepended
+ prepend ModuleD
+
+ CS_CONST11 = :const11_8
+ end
+
+ # The following classes/modules have all the constants set "statically".
+ # Contrast with the classes below where the constants are set as the specs
+ # are run.
+
+ class ClassA
+ CS_CLASS_A_LINE = __LINE__ - 1
+ CS_CONST10 = :const10_10
+ CS_CONST10_LINE = __LINE__ - 1
+ CS_CONST16 = :const16
+ CS_CONST17 = :const17_2
+ CS_CONST22 = :const22_1
+
+ def self.const_missing(const)
+ const
+ end
+
+ def self.constx; CS_CONSTX; end
+ def self.const10; CS_CONST10; end
+ def self.const16; ParentA.const16; end
+ def self.const22; ParentA.const22 { CS_CONST22 }; end
+
+ def const10; CS_CONST10; end
+ def constx; CS_CONSTX; end
+ end
+
+ class ParentA
+ include ModuleB
+
+ CS_CONST4 = :const4
+ CS_CONST4_LINE = __LINE__ - 1
+ CS_CONST10 = :const10_5
+ CS_CONST10_LINE = __LINE__ - 1
+ CS_CONST11 = :const11_1
+ CS_CONST11_LINE = __LINE__ - 1
+ CS_CONST15 = :const15_2
+ CS_CONST20 = :const20_2
+ CS_CONST20_LINE = __LINE__ - 1
+ CS_CONST21 = :const21_1
+ CS_CONST22 = :const22_2
+
+ def self.constx; CS_CONSTX; end
+ def self.const10; CS_CONST10; end
+ def self.const16; CS_CONST16; end
+ def self.const22; yield; end
+
+ def const10; CS_CONST10; end
+ def constx; CS_CONSTX; end
+ end
+
+ class ContainerA
+ include ModuleD
+
+ CS_CONST5 = :const5
+ CS_CONST10 = :const10_2
+ CS_CONST10_LINE = __LINE__ - 1
+ CS_CONST23 = :const23
+
+ class ChildA < ParentA
+ include ModuleC
+
+ class << self
+ include ModuleH
+
+ CS_CONST10 = :const10_6
+ CS_CONST14 = :const14_1
+ CS_CONST19 = :const19_1
+
+ def const19; CS_CONST19; end
+ end
+
+ CS_CONST6 = :const6
+ CS_CONST10 = :const10_3
+ CS_CONST10_LINE = __LINE__ - 1
+ CS_CONST19 = :const19_2
+
+ def self.const10; CS_CONST10; end
+ def self.const11; CS_CONST11; end
+ def self.const12; CS_CONST12; end
+ def self.const13; CS_CONST13; end
+ def self.const15; CS_CONST15; end
+ def self.const21; CS_CONST21; end
+
+ def const10; CS_CONST10; end
+ def const11; CS_CONST11; end
+ def const12; CS_CONST12; end
+ def const13; CS_CONST13; end
+ def const15; CS_CONST15; end
+ end
+
+ def self.const10; CS_CONST10; end
+
+ def const10; CS_CONST10; end
+ end
+
+ class ContainerPrepend
+ include ModuleIncludePrepended
+ end
+
+ class ContainerA::ChildA
+ def self.const23; CS_CONST23; end
+ end
+
+ class ::Object
+ CS_CONST20 = :const20_1
+
+ module ConstantSpecs
+ class ContainerA
+ class ChildA
+ def self.const20; CS_CONST20; end
+ end
+ end
+ end
+ end
+
+ # Included in ParentB
+ module ModuleE
+ end
+
+ # Included in ChildB
+ module ModuleF
+ end
+
+ # Included in ContainerB
+ module ModuleG
+ end
+
+ # The following classes/modules have the same structure as the ones above
+ # but the constants are set as the specs are run.
+
+ class ClassB
+ def self.const201; CS_CONST201; end
+ def self.const209; ParentB.const209; end
+ def self.const210; ParentB.const210 { CS_CONST210 }; end
+
+ def const201; CS_CONST201; end
+ end
+
+ class ParentB
+ include ModuleE
+
+ def self.const201; CS_CONST201; end
+ def self.const209; CS_CONST209; end
+ def self.const210; yield; end
+
+ def const201; CS_CONST201; end
+ end
+
+ class ContainerB
+ include ModuleG
+
+ class ChildB < ParentB
+ include ModuleF
+
+ class << self
+ def const206; CS_CONST206; end
+ end
+
+ def self.const201; CS_CONST201; end
+ def self.const202; CS_CONST202; end
+ def self.const203; CS_CONST203; end
+ def self.const204; CS_CONST204; end
+ def self.const205; CS_CONST205; end
+ def self.const212; CS_CONST212; end
+ def self.const213; CS_CONST213; end
+
+ def const201; CS_CONST201; end
+ def const202; CS_CONST202; end
+ def const203; CS_CONST203; end
+ def const204; CS_CONST204; end
+ def const205; CS_CONST205; end
+ def const213; CS_CONST213; end
+ end
+
+ def self.const201; CS_CONST201; end
+ end
+
+ class ContainerB::ChildB
+ def self.const214; CS_CONST214; end
+ end
+
+ class ::Object
+ module ConstantSpecs
+ class ContainerB
+ class ChildB
+ def self.const211; CS_CONST211; end
+ end
+ end
+ end
+ end
+
+ # Constants
+ CS_CONST2 = :const2 # only defined here
+ CS_CONST17 = :const17_1
+
+ class << self
+ CS_CONST14 = :const14_2
+ end
+
+ # Singleton
+ a = ClassA.new
+ def a.const17; CS_CONST17; end
+ CS_CONST18 = a
+
+ b = ClassB.new
+ def b.const207; CS_CONST207; end
+ CS_CONST208 = b
+
+ # Methods
+ def self.get_const; self; end
+
+ def const10; CS_CONST10; end
+
+ class ClassC
+ CS_CONST1 = 1
+
+ class ClassE
+ CS_CONST2 = 2
+ end
+ end
+
+ class ClassD < ClassC
+ end
+
+ CS_PRIVATE = :cs_private
+ CS_PRIVATE_LINE = __LINE__ - 1
+ private_constant :CS_PRIVATE
+end
+
+module ConstantSpecsThree
+ module ConstantSpecsTwo
+ Foo = :cs_three_foo
+ end
+end
+
+module ConstantSpecsTwo
+ Foo = :cs_two_foo
+end
+
+include ConstantSpecs::ModuleA
diff --git a/spec/ruby/fixtures/enumerator/classes.rb b/spec/ruby/fixtures/enumerator/classes.rb
new file mode 100644
index 0000000000..6f285b8efa
--- /dev/null
+++ b/spec/ruby/fixtures/enumerator/classes.rb
@@ -0,0 +1,15 @@
+module EnumSpecs
+ class Numerous
+
+ include Enumerable
+
+ def initialize(*list)
+ @list = list.empty? ? [2, 5, 3, 6, 1, 4] : list
+ end
+
+ def each
+ @list.each { |i| yield i }
+ end
+ end
+
+end
diff --git a/spec/ruby/fixtures/math/common.rb b/spec/ruby/fixtures/math/common.rb
new file mode 100644
index 0000000000..024732fa7a
--- /dev/null
+++ b/spec/ruby/fixtures/math/common.rb
@@ -0,0 +1,3 @@
+class IncludesMath
+ include Math
+end
diff --git a/spec/ruby/fixtures/rational.rb b/spec/ruby/fixtures/rational.rb
new file mode 100644
index 0000000000..844d7f9820
--- /dev/null
+++ b/spec/ruby/fixtures/rational.rb
@@ -0,0 +1,14 @@
+module RationalSpecs
+ class SubNumeric < Numeric
+ def initialize(value)
+ @value = Rational(value)
+ end
+
+ def to_r
+ @value
+ end
+ end
+
+ class CoerceError < StandardError
+ end
+end
diff --git a/spec/ruby/fixtures/reflection.rb b/spec/ruby/fixtures/reflection.rb
new file mode 100644
index 0000000000..fe004f6a82
--- /dev/null
+++ b/spec/ruby/fixtures/reflection.rb
@@ -0,0 +1,352 @@
+# These modules and classes are fixtures used by the Ruby reflection specs.
+# These include specs for methods:
+#
+# Module:
+# instance_methods
+# public_instance_methods
+# protected_instance_methods
+# private_instance_methods
+#
+# Kernel:
+# methods
+# public_methods
+# protected_methods
+# private_methods
+# singleton_methods
+#
+# The following naming scheme is used to keep the method names short and still
+# communicate the relevant facts about the methods:
+#
+# X[s]_VIS
+#
+# where
+#
+# X is the name of the module or class in lower case
+# s is the literal character 's' for singleton methods
+# VIS is the first three letters of the corresponding visibility
+# pub(lic), pro(tected), pri(vate)
+#
+# For example:
+#
+# l_pub is a public method on module L
+# ls_pri is a private singleton method on module L
+
+module ReflectSpecs
+ # An object with no singleton methods.
+ def self.o
+ mock("Object with no singleton methods")
+ end
+
+ # An object with singleton methods.
+ def self.os
+ obj = mock("Object with singleton methods")
+ class << obj
+ def os_pub; :os_pub; end
+
+ def os_pro; :os_pro; end
+ protected :os_pro
+
+ def os_pri; :os_pri; end
+ private :os_pri
+ end
+ obj
+ end
+
+ # An object extended with a module.
+ def self.oe
+ obj = mock("Object extended")
+ obj.extend M
+ obj
+ end
+
+ # An object with duplicate methods extended with a module.
+ def self.oed
+ obj = mock("Object extended")
+ obj.extend M
+
+ class << obj
+ def pub; :pub; end
+
+ def pro; :pro; end
+ protected :pro
+
+ def pri; :pri; end
+ private :pri
+ end
+
+ obj
+ end
+
+ # An object extended with two modules.
+ def self.oee
+ obj = mock("Object extended twice")
+ obj.extend M
+ obj.extend N
+ obj
+ end
+
+ # An object extended with a module including a module.
+ def self.oei
+ obj = mock("Object extended, included")
+ obj.extend N
+ obj
+ end
+
+ # A simple module.
+ module L
+ class << self
+ def ls_pub; :ls_pub; end
+
+ def ls_pro; :ls_pro; end
+ protected :ls_pro
+
+ def ls_pri; :ls_pri; end
+ private :ls_pri
+ end
+
+ def l_pub; :l_pub; end
+
+ def l_pro; :l_pro; end
+ protected :l_pro
+
+ def l_pri; :l_pri; end
+ private :l_pri
+ end
+
+ # A module with no singleton methods.
+ module K
+ end
+
+ # A simple module.
+ module M
+ class << self
+ def ms_pub; :ms_pub; end
+
+ def ms_pro; :ms_pro; end
+ protected :ms_pro
+
+ def ms_pri; :ms_pri; end
+ private :ms_pri
+ end
+
+ def m_pub; :m_pub; end
+
+ def m_pro; :m_pro; end
+ protected :m_pro
+
+ def m_pri; :m_pri; end
+ private :m_pri
+
+ def pub; :pub; end
+
+ def pro; :pro; end
+ protected :pro
+
+ def pri; :pri; end
+ private :pri
+ end
+
+ # A module including a module
+ module N
+ include M
+
+ class << self
+ def ns_pub; :ns_pub; end
+
+ def ns_pro; :ns_pro; end
+ protected :ns_pro
+
+ def ns_pri; :ns_pri; end
+ private :ns_pri
+ end
+
+ def n_pub; :n_pub; end
+
+ def n_pro; :n_pro; end
+ protected :n_pro
+
+ def n_pri; :n_pri; end
+ private :n_pri
+ end
+
+ # A simple class.
+ class A
+ class << self
+ def as_pub; :as_pub; end
+
+ def as_pro; :as_pro; end
+ protected :as_pro
+
+ def as_pri; :as_pri; end
+ private :as_pri
+
+ def pub; :pub; end
+
+ def pro; :pro; end
+ protected :pro
+
+ def pri; :pri; end
+ private :pri
+ end
+
+ def a_pub; :a_pub; end
+
+ def a_pro; :a_pro; end
+ protected :a_pro
+
+ def a_pri; :a_pri; end
+ private :a_pri
+ end
+
+ # A simple subclass.
+ class B < A
+ class << self
+ def bs_pub; :bs_pub; end
+
+ def bs_pro; :bs_pro; end
+ protected :bs_pro
+
+ def bs_pri; :bs_pri; end
+ private :bs_pri
+
+ def pub; :pub; end
+
+ def pro; :pro; end
+ protected :pro
+
+ def pri; :pri; end
+ private :pri
+ end
+
+ def b_pub; :b_pub; end
+
+ def b_pro; :b_pro; end
+ protected :b_pro
+
+ def b_pri; :b_pri; end
+ private :b_pri
+ end
+
+ # A subclass including a module.
+ class C < A
+ include M
+
+ class << self
+ def cs_pub; :cs_pub; end
+
+ def cs_pro; :cs_pro; end
+ protected :cs_pro
+
+ def cs_pri; :cs_pri; end
+ private :cs_pri
+
+ def pub; :pub; end
+
+ def pro; :pro; end
+ protected :pro
+
+ def pri; :pri; end
+ private :pri
+ end
+
+ def c_pub; :c_pub; end
+
+ def c_pro; :c_pro; end
+ protected :c_pro
+
+ def c_pri; :c_pri; end
+ private :c_pri
+ end
+
+ # A simple class including a module
+ class D
+ include M
+
+ class << self
+ def ds_pub; :ds_pub; end
+
+ def ds_pro; :ds_pro; end
+ protected :ds_pro
+
+ def ds_pri; :ds_pri; end
+ private :ds_pri
+ end
+
+ def d_pub; :d_pub; end
+
+ def d_pro; :d_pro; end
+ protected :d_pro
+
+ def d_pri; :d_pri; end
+ private :d_pri
+
+ def pub; :pub; end
+
+ def pro; :pro; end
+ protected :pro
+
+ def pri; :pri; end
+ private :pri
+ end
+
+ # A subclass of a class including a module.
+ class E < D
+ class << self
+ def es_pub; :es_pub; end
+
+ def es_pro; :es_pro; end
+ protected :es_pro
+
+ def es_pri; :es_pri; end
+ private :es_pri
+ end
+
+ def e_pub; :e_pub; end
+
+ def e_pro; :e_pro; end
+ protected :e_pro
+
+ def e_pri; :e_pri; end
+ private :e_pri
+
+ def pub; :pub; end
+
+ def pro; :pro; end
+ protected :pro
+
+ def pri; :pri; end
+ private :pri
+ end
+
+ # A subclass that includes a module of a class including a module.
+ class F < D
+ include L
+
+ class << self
+ def fs_pub; :fs_pub; end
+
+ def fs_pro; :fs_pro; end
+ protected :fs_pro
+
+ def fs_pri; :fs_pri; end
+ private :fs_pri
+ end
+
+ def f_pub; :f_pub; end
+
+ def f_pro; :f_pro; end
+ protected :f_pro
+
+ def f_pri; :f_pri; end
+ private :f_pri
+ end
+
+ # Class with no singleton methods.
+ class O
+ end
+
+ # Class extended with a module.
+ class P
+ end
+ P.extend M
+end
diff --git a/spec/ruby/language/BEGIN_spec.rb b/spec/ruby/language/BEGIN_spec.rb
new file mode 100644
index 0000000000..5aef5a1d7c
--- /dev/null
+++ b/spec/ruby/language/BEGIN_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../spec_helper'
+
+describe "The BEGIN keyword" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "runs in a shared scope" do
+ eval("BEGIN { var_in_begin = 'foo' }; var_in_begin").should == "foo"
+ end
+
+ it "accesses variables outside the eval scope" do
+ outside_var = 'foo'
+ eval("BEGIN { var_in_begin = outside_var }; var_in_begin").should == "foo"
+ end
+
+ it "must appear in a top-level context" do
+ -> { eval "1.times { BEGIN { 1 } }" }.should raise_error(SyntaxError)
+ end
+
+ it "uses top-level for self" do
+ eval("BEGIN { ScratchPad << self.to_s }", TOPLEVEL_BINDING)
+ ScratchPad.recorded.should == ['main']
+ end
+
+ it "runs first in a given code unit" do
+ eval "ScratchPad << 'foo'; BEGIN { ScratchPad << 'bar' }"
+
+ ScratchPad.recorded.should == ['bar', 'foo']
+ end
+
+ it "runs multiple begins in FIFO order" do
+ eval "BEGIN { ScratchPad << 'foo' }; BEGIN { ScratchPad << 'bar' }"
+
+ ScratchPad.recorded.should == ['foo', 'bar']
+ end
+
+ it "returns the top-level script's filename for __FILE__" do
+ ruby_exe(fixture(__FILE__, "begin_file.rb")).chomp.should =~ /begin_file\.rb$/
+ end
+end
diff --git a/spec/ruby/language/END_spec.rb b/spec/ruby/language/END_spec.rb
new file mode 100644
index 0000000000..762a8db0c0
--- /dev/null
+++ b/spec/ruby/language/END_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../spec_helper'
+
+describe "The END keyword" do
+ it "runs only once for multiple calls" do
+ ruby_exe("10.times { END { puts 'foo' }; } ").should == "foo\n"
+ end
+
+ it "runs last in a given code unit" do
+ ruby_exe("END { puts 'bar' }; puts'foo'; ").should == "foo\nbar\n"
+ end
+
+ it "runs multiple ends in LIFO order" do
+ ruby_exe("END { puts 'foo' }; END { puts 'bar' }").should == "bar\nfoo\n"
+ end
+end
diff --git a/spec/ruby/language/README b/spec/ruby/language/README
new file mode 100644
index 0000000000..ae08e17fb1
--- /dev/null
+++ b/spec/ruby/language/README
@@ -0,0 +1,30 @@
+There are numerous possible way of categorizing the entities and concepts that
+make up a programming language. Ruby has a fairly large number of reserved
+words. These words significantly describe major elements of the language,
+including flow control constructs like 'for' and 'while', conditional
+execution like 'if' and 'unless', exceptional execution control like 'rescue',
+etc. There are also literals for the basic "types" like String, Regexp, Array
+and Integer.
+
+Behavioral specifications describe the behavior of concrete entities. Rather
+than using concepts of computation to organize these spec files, we use
+entities of the Ruby language. Consider looking at any syntactic element of a
+Ruby program. With (almost) no ambiguity, one can identify it as a literal,
+reserved word, variable, etc. There is a spec file that corresponds to each
+literal construct and most reserved words, with the exceptions noted below.
+There are also several files that are more difficult to classify: all
+predefined variables, constants, and objects (predefined_spec.rb), the
+precedence of all operators (precedence_spec.rb), the behavior of assignment
+to variables (variables_spec.rb), the behavior of subprocess execution
+(execution_spec.rb), the behavior of the raise method as it impacts the
+execution of a Ruby program (raise_spec.rb), and the block entities like
+'begin', 'do', ' { ... }' (block_spec.rb).
+
+Several reserved words and other entities are combined with the primary
+reserved word or entity to which they are related:
+
+false, true, nil, self predefined_spec.rb
+in for_spec.rb
+then, elsif if_spec.rb
+when case_spec.rb
+catch throw_spec.rb
diff --git a/spec/ruby/language/alias_spec.rb b/spec/ruby/language/alias_spec.rb
new file mode 100644
index 0000000000..c353390679
--- /dev/null
+++ b/spec/ruby/language/alias_spec.rb
@@ -0,0 +1,276 @@
+require_relative '../spec_helper'
+
+class AliasObject
+ attr :foo
+ attr_reader :bar
+ attr_accessor :baz
+
+ def prep; @foo = 3; @bar = 4; end
+ def value; 5; end
+ def false_value; 6; end
+ def self.klass_method; 7; end
+end
+
+describe "The alias keyword" do
+ before :each do
+ @obj = AliasObject.new
+ @meta = class << @obj;self;end
+ end
+
+ it "creates a new name for an existing method" do
+ @meta.class_eval do
+ alias __value value
+ end
+ @obj.__value.should == 5
+ end
+
+ it "works with a simple symbol on the left-hand side" do
+ @meta.class_eval do
+ alias :a value
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with a single quoted symbol on the left-hand side" do
+ @meta.class_eval do
+ alias :'a' value
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with a double quoted symbol on the left-hand side" do
+ @meta.class_eval do
+ alias :"a" value
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with an interpolated symbol on the left-hand side" do
+ @meta.class_eval do
+ alias :"#{'a'}" value
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with a simple symbol on the right-hand side" do
+ @meta.class_eval do
+ alias a :value
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with a single quoted symbol on the right-hand side" do
+ @meta.class_eval do
+ alias a :'value'
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with a double quoted symbol on the right-hand side" do
+ @meta.class_eval do
+ alias a :"value"
+ end
+ @obj.a.should == 5
+ end
+
+ it "works with an interpolated symbol on the right-hand side" do
+ @meta.class_eval do
+ alias a :"#{'value'}"
+ end
+ @obj.a.should == 5
+ end
+
+ it "adds the new method to the list of methods" do
+ original_methods = @obj.methods
+ @meta.class_eval do
+ alias __value value
+ end
+ (@obj.methods - original_methods).map {|m| m.to_s }.should == ["__value"]
+ end
+
+ it "adds the new method to the list of public methods" do
+ original_methods = @obj.public_methods
+ @meta.class_eval do
+ alias __value value
+ end
+ (@obj.public_methods - original_methods).map {|m| m.to_s }.should == ["__value"]
+ end
+
+ it "overwrites an existing method with the target name" do
+ @meta.class_eval do
+ alias false_value value
+ end
+ @obj.false_value.should == 5
+ end
+
+ it "is reversible" do
+ @meta.class_eval do
+ alias __value value
+ alias value false_value
+ end
+ @obj.value.should == 6
+
+ @meta.class_eval do
+ alias value __value
+ end
+ @obj.value.should == 5
+ end
+
+ it "operates on the object's metaclass when used in instance_eval" do
+ @obj.instance_eval do
+ alias __value value
+ end
+
+ @obj.__value.should == 5
+ -> { AliasObject.new.__value }.should raise_error(NoMethodError)
+ end
+
+ it "operates on the class/module metaclass when used in instance_eval" do
+ AliasObject.instance_eval do
+ alias __klass_method klass_method
+ end
+
+ AliasObject.__klass_method.should == 7
+ -> { Object.__klass_method }.should raise_error(NoMethodError)
+ end
+
+ it "operates on the class/module metaclass when used in instance_exec" do
+ AliasObject.instance_exec do
+ alias __klass_method2 klass_method
+ end
+
+ AliasObject.__klass_method2.should == 7
+ -> { Object.__klass_method2 }.should raise_error(NoMethodError)
+ end
+
+ it "operates on methods defined via attr, attr_reader, and attr_accessor" do
+ @obj.prep
+ @obj.instance_eval do
+ alias afoo foo
+ alias abar bar
+ alias abaz baz
+ end
+
+ @obj.afoo.should == 3
+ @obj.abar.should == 4
+ @obj.baz = 5
+ @obj.abaz.should == 5
+ end
+
+ it "operates on methods with splat arguments" do
+ class AliasObject2;end
+ AliasObject2.class_eval do
+ def test(*args)
+ 4
+ end
+ def test_with_check(*args)
+ test_without_check(*args)
+ end
+ alias test_without_check test
+ alias test test_with_check
+ end
+ AliasObject2.new.test(1,2,3,4,5).should == 4
+ end
+
+ it "operates on methods with splat arguments on eigenclasses" do
+ @meta.class_eval do
+ def test(*args)
+ 4
+ end
+ def test_with_check(*args)
+ test_without_check(*args)
+ end
+ alias test_without_check test
+ alias test test_with_check
+ end
+ @obj.test(1,2,3,4,5).should == 4
+ end
+
+ it "operates on methods with splat arguments defined in a superclass" do
+ alias_class = Class.new
+ alias_class.class_eval do
+ def test(*args)
+ 4
+ end
+ def test_with_check(*args)
+ test_without_check(*args)
+ end
+ end
+ sub = Class.new(alias_class) do
+ alias test_without_check test
+ alias test test_with_check
+ end
+ sub.new.test(1,2,3,4,5).should == 4
+ end
+
+ it "operates on methods with splat arguments defined in a superclass using text block for class eval" do
+ subclass = Class.new(AliasObject)
+ AliasObject.class_eval <<-code
+ def test(*args)
+ 4
+ end
+ def test_with_check(*args)
+ test_without_check(*args)
+ end
+ alias test_without_check test
+ alias test test_with_check
+ code
+ subclass.new.test("testing").should == 4
+ end
+
+ it "is not allowed against Integer or String instances" do
+ -> do
+ 1.instance_eval do
+ alias :foo :to_s
+ end
+ end.should raise_error(TypeError)
+
+ -> do
+ :blah.instance_eval do
+ alias :foo :to_s
+ end
+ end.should raise_error(TypeError)
+ end
+
+ it "on top level defines the alias on Object" do
+ # because it defines on the default definee / current module
+ ruby_exe("def foo; end; alias bla foo; print method(:bla).owner", escape: true).should == "Object"
+ end
+
+ it "raises a NameError when passed a missing name" do
+ -> { @meta.class_eval { alias undef_method not_exist } }.should raise_error(NameError) { |e|
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+
+ it "defines the method on the aliased class when the original method is from a parent class" do
+ parent = Class.new do
+ def parent_method
+ end
+ end
+ child = Class.new(parent) do
+ alias parent_method_alias parent_method
+ end
+
+ child.instance_method(:parent_method_alias).owner.should == child
+ child.instance_methods(false).should include(:parent_method_alias)
+ end
+end
+
+describe "The alias keyword" do
+ it "can create a new global variable, synonym of the original" do
+ code = '$a = 1; alias $b $a; p [$a, $b]; $b = 2; p [$a, $b]'
+ ruby_exe(code).should == "[1, 1]\n[2, 2]\n"
+ end
+
+ it "can override an existing global variable and make them synonyms" do
+ code = '$a = 1; $b = 2; alias $b $a; p [$a, $b]; $b = 3; p [$a, $b]'
+ ruby_exe(code).should == "[1, 1]\n[3, 3]\n"
+ end
+
+ it "supports aliasing twice the same global variables" do
+ code = '$a = 1; alias $b $a; alias $b $a; p [$a, $b]'
+ ruby_exe(code).should == "[1, 1]\n"
+ end
+end
diff --git a/spec/ruby/language/and_spec.rb b/spec/ruby/language/and_spec.rb
new file mode 100644
index 0000000000..55a2a3103a
--- /dev/null
+++ b/spec/ruby/language/and_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../spec_helper'
+
+describe "The '&&' statement" do
+
+ it "short-circuits evaluation at the first condition to be false" do
+ x = nil
+ true && false && x = 1
+ x.should be_nil
+ end
+
+ it "evaluates to the first condition not to be true" do
+ value = nil
+ (value && nil).should == nil
+ (value && false).should == nil
+ value = false
+ (value && nil).should == false
+ (value && false).should == false
+
+ ("yes" && 1 && nil && true).should == nil
+ ("yes" && 1 && false && true).should == false
+ end
+
+ it "evaluates to the last condition if all are true" do
+ ("yes" && 1).should == 1
+ (1 && "yes").should == "yes"
+ end
+
+ it "evaluates the full set of chained conditions during assignment" do
+ x, y = nil
+ x = 1 && y = 2
+ # "1 && y = 2" is evaluated and then assigned to x
+ x.should == 2
+ end
+
+ it "treats empty expressions as nil" do
+ (() && true).should be_nil
+ (true && ()).should be_nil
+ (() && ()).should be_nil
+ end
+
+end
+
+describe "The 'and' statement" do
+ it "short-circuits evaluation at the first condition to be false" do
+ x = nil
+ true and false and x = 1
+ x.should be_nil
+ end
+
+ it "evaluates to the first condition not to be true" do
+ value = nil
+ (value and nil).should == nil
+ (value and false).should == nil
+ value = false
+ (value and nil).should == false
+ (value and false).should == false
+
+ ("yes" and 1 and nil and true).should == nil
+ ("yes" and 1 and false and true).should == false
+ end
+
+ it "evaluates to the last condition if all are true" do
+ ("yes" and 1).should == 1
+ (1 and "yes").should == "yes"
+ end
+
+ it "when used in assignment, evaluates and assigns expressions individually" do
+ x, y = nil
+ x = 1 and y = 2
+ # evaluates (x=1) and (y=2)
+ x.should == 1
+ end
+
+ it "treats empty expressions as nil" do
+ (() and true).should be_nil
+ (true and ()).should be_nil
+ (() and ()).should be_nil
+ end
+
+end
diff --git a/spec/ruby/language/array_spec.rb b/spec/ruby/language/array_spec.rb
new file mode 100644
index 0000000000..2583cffbf7
--- /dev/null
+++ b/spec/ruby/language/array_spec.rb
@@ -0,0 +1,162 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/array'
+
+describe "Array literals" do
+ it "[] should return a new array populated with the given elements" do
+ array = [1, 'a', nil]
+ array.should be_kind_of(Array)
+ array[0].should == 1
+ array[1].should == 'a'
+ array[2].should == nil
+ end
+
+ it "[] treats empty expressions as nil elements" do
+ array = [0, (), 2, (), 4]
+ array.should be_kind_of(Array)
+ array[0].should == 0
+ array[1].should == nil
+ array[2].should == 2
+ array[3].should == nil
+ array[4].should == 4
+ end
+
+ it "[] accepts a literal hash without curly braces as its only parameter" do
+ ["foo" => :bar, baz: 42].should == [{"foo" => :bar, baz: 42}]
+ end
+
+ it "[] accepts a literal hash without curly braces as its last parameter" do
+ ["foo", "bar" => :baz].should == ["foo", {"bar" => :baz}]
+ [1, 2, 3 => 6, 4 => 24].should == [1, 2, {3 => 6, 4 => 24}]
+ end
+
+ it "[] treats splatted nil as no element" do
+ [*nil].should == []
+ [1, *nil].should == [1]
+ [1, 2, *nil].should == [1, 2]
+ [1, *nil, 3].should == [1, 3]
+ [*nil, *nil, *nil].should == []
+ end
+
+ it "evaluates each argument exactly once" do
+ se = ArraySpec::SideEffect.new
+ se.array_result(true)
+ se.array_result(false)
+ se.call_count.should == 4
+ end
+end
+
+describe "Bareword array literal" do
+ it "%w() transforms unquoted barewords into an array" do
+ a = 3
+ %w(a #{3+a} 3).should == ["a", '#{3+a}', "3"]
+ end
+
+ it "%W() transforms unquoted barewords into an array, supporting interpolation" do
+ a = 3
+ %W(a #{3+a} 3).should == ["a", '6', "3"]
+ end
+
+ it "%W() always treats interpolated expressions as a single word" do
+ a = "hello world"
+ %W(a b c #{a} d e).should == ["a", "b", "c", "hello world", "d", "e"]
+ end
+
+ it "treats consecutive whitespace characters the same as one" do
+ %w(a b c d).should == ["a", "b", "c", "d"]
+ %W(hello
+ world).should == ["hello", "world"]
+ end
+
+ it "treats whitespace as literals characters when escaped by a backslash" do
+ %w(a b\ c d e).should == ["a", "b c", "d", "e"]
+ %w(a b\
+c d).should == ["a", "b\nc", "d"]
+ %W(a\ b\tc).should == ["a ", "b\tc"]
+ %W(white\ \ \ \ \ space).should == ["white ", " ", " ", " space"]
+ end
+end
+
+describe "The unpacking splat operator (*)" do
+ it "when applied to a literal nested array, unpacks its elements into the containing array" do
+ [1, 2, *[3, 4, 5]].should == [1, 2, 3, 4, 5]
+ end
+
+ it "when applied to a nested referenced array, unpacks its elements into the containing array" do
+ splatted_array = [3, 4, 5]
+ [1, 2, *splatted_array].should == [1, 2, 3, 4, 5]
+ end
+
+ it "returns a new array containing the same values when applied to an array inside an empty array" do
+ splatted_array = [3, 4, 5]
+ [*splatted_array].should == splatted_array
+ [*splatted_array].should_not equal(splatted_array)
+ end
+
+ it "unpacks the start and count arguments in an array slice assignment" do
+ alphabet_1 = ['a'..'z'].to_a
+ alphabet_2 = alphabet_1.dup
+ start_and_count_args = [1, 10]
+
+ alphabet_1[1, 10] = 'a'
+ alphabet_2[*start_and_count_args] = 'a'
+
+ alphabet_1.should == alphabet_2
+ end
+
+ it "unpacks arguments as if they were listed statically" do
+ static = [1,2,3,4]
+ receiver = static.dup
+ args = [0,1]
+ static[0,1] = []
+ static.should == [2,3,4]
+ receiver[*args] = []
+ receiver.should == static
+ end
+
+ it "unpacks a literal array into arguments in a method call" do
+ tester = ArraySpec::Splat.new
+ tester.unpack_3args(*[1, 2, 3]).should == [1, 2, 3]
+ tester.unpack_4args(1, 2, *[3, 4]).should == [1, 2, 3, 4]
+ tester.unpack_4args("a", %w(b c), *%w(d e)).should == ["a", ["b", "c"], "d", "e"]
+ end
+
+ it "unpacks a referenced array into arguments in a method call" do
+ args = [1, 2, 3]
+ tester = ArraySpec::Splat.new
+ tester.unpack_3args(*args).should == [1, 2, 3]
+ tester.unpack_4args(0, *args).should == [0, 1, 2, 3]
+ end
+
+ it "when applied to a non-Array value attempts to coerce it to Array if the object respond_to?(:to_a)" do
+ obj = mock("pseudo-array")
+ obj.should_receive(:to_a).and_return([2, 3, 4])
+ [1, *obj].should == [1, 2, 3, 4]
+ end
+
+ it "when applied to a non-Array value uses it unchanged if it does not respond_to?(:to_a)" do
+ obj = Object.new
+ obj.should_not respond_to(:to_a)
+ [1, *obj].should == [1, obj]
+ end
+
+ it "when applied to a BasicObject coerces it to Array if it respond_to?(:to_a)" do
+ obj = BasicObject.new
+ def obj.to_a; [2, 3, 4]; end
+ [1, *obj].should == [1, 2, 3, 4]
+ end
+
+ it "can be used before other non-splat elements" do
+ a = [1, 2]
+ [0, *a, 3].should == [0, 1, 2, 3]
+ end
+
+ it "can be used multiple times in the same containing array" do
+ a = [1, 2]
+ b = [1, 0]
+ [*a, 3, *a, *b].should == [1, 2, 3, 1, 2, 1, 0]
+ end
+end
+
+describe "The packing splat operator (*)" do
+
+end
diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb
new file mode 100644
index 0000000000..8488b945d5
--- /dev/null
+++ b/spec/ruby/language/block_spec.rb
@@ -0,0 +1,1102 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/block'
+
+describe "A block yielded a single" do
+ before :all do
+ def m(a) yield a end
+ end
+
+ context "Array" do
+ it "assigns the Array to a single argument" do
+ m([1, 2]) { |a| a }.should == [1, 2]
+ end
+
+ it "receives the identical Array object" do
+ ary = [1, 2]
+ m(ary) { |a| a }.should equal(ary)
+ end
+
+ it "assigns the Array to a single rest argument" do
+ m([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]]
+ end
+
+ it "assigns the first element to a single argument with trailing comma" do
+ m([1, 2]) { |a, | a }.should == 1
+ end
+
+ it "assigns elements to required arguments" do
+ m([1, 2, 3]) { |a, b| [a, b] }.should == [1, 2]
+ end
+
+ it "assigns nil to unassigned required arguments" do
+ m([1, 2]) { |a, *b, c, d| [a, b, c, d] }.should == [1, [], 2, nil]
+ end
+
+ it "assigns elements to optional arguments" do
+ m([1, 2]) { |a=5, b=4, c=3| [a, b, c] }.should == [1, 2, 3]
+ end
+
+ it "assigns elements to post arguments" do
+ m([1, 2]) { |a=5, b, c, d| [a, b, c, d] }.should == [5, 1, 2, nil]
+ end
+
+ ruby_version_is "3.2" do
+ it "does not autosplat single argument to required arguments when a keyword rest argument is present" do
+ m([1, 2]) { |a, **k| [a, k] }.should == [[1, 2], {}]
+ end
+ end
+
+ ruby_version_is ''..."3.2" do
+ # https://bugs.ruby-lang.org/issues/18633
+ it "autosplats single argument to required arguments when a keyword rest argument is present" do
+ m([1, 2]) { |a, **k| [a, k] }.should == [1, {}]
+ end
+ end
+
+ ruby_version_is ''..."3.0" do
+ it "assigns elements to mixed argument types" do
+ suppress_keyword_warning do
+ result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] }
+ result.should == [1, 2, [], 3, 2, {x: 9}]
+ end
+ end
+
+ it "assigns symbol keys from a Hash to keyword arguments" do
+ suppress_keyword_warning do
+ result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] }
+ result.should == [{"a" => 1}, a: 10]
+ end
+ end
+
+ it "assigns symbol keys from a Hash returned by #to_hash to keyword arguments" do
+ suppress_keyword_warning do
+ obj = mock("coerce block keyword arguments")
+ obj.should_receive(:to_hash).and_return({"a" => 1, b: 2})
+
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [{"a" => 1}, b: 2]
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "assigns elements to mixed argument types" do
+ result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] }
+ result.should == [1, 2, [3], {x: 9}, 2, {}]
+ end
+
+ it "does not treat final Hash as keyword arguments and does not autosplat" do
+ result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] }
+ result.should == [[{"a" => 1, a: 10}], {}]
+ end
+
+ it "does not call #to_hash on final argument to get keyword arguments and does not autosplat" do
+ suppress_keyword_warning do
+ obj = mock("coerce block keyword arguments")
+ obj.should_not_receive(:to_hash)
+
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [[obj], {}]
+ end
+ end
+ end
+
+ ruby_version_is ""...'3.0' do
+ it "calls #to_hash on the argument but ignores result when optional argument and keyword argument accepted" do
+ obj = mock("coerce block keyword arguments")
+ obj.should_receive(:to_hash).and_return({"a" => 1, "b" => 2})
+
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [obj, {}]
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "does not call #to_hash on the argument when optional argument and keyword argument accepted and does not autosplat" do
+ obj = mock("coerce block keyword arguments")
+ obj.should_not_receive(:to_hash)
+
+ result = m([obj]) { |a=nil, **b| [a, b] }
+ result.should == [[obj], {}]
+ end
+ end
+
+ describe "when non-symbol keys are in a keyword arguments Hash" do
+ ruby_version_is ""..."3.0" do
+ it "separates non-symbol keys and symbol keys" do
+ suppress_keyword_warning do
+ result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] }
+ result.should == [{"a" => 10}, {b: 2}]
+ end
+ end
+ end
+ ruby_version_is "3.0" do
+ it "does not separate non-symbol keys and symbol keys and does not autosplat" do
+ suppress_keyword_warning do
+ result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] }
+ result.should == [[{"a" => 10, b: 2}], {}]
+ end
+ end
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "does not treat hashes with string keys as keyword arguments" do
+ result = m(["a" => 10]) { |a = nil, **b| [a, b] }
+ result.should == [{"a" => 10}, {}]
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "does not treat hashes with string keys as keyword arguments and does not autosplat" do
+ result = m(["a" => 10]) { |a = nil, **b| [a, b] }
+ result.should == [[{"a" => 10}], {}]
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "calls #to_hash on the last element if keyword arguments are present" do
+ suppress_keyword_warning do
+ obj = mock("destructure block keyword arguments")
+ obj.should_receive(:to_hash).and_return({x: 9})
+
+ result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
+ result.should == [1, [2], 3, {x: 9}]
+ end
+ end
+
+ it "assigns the last element to a non-keyword argument if #to_hash returns nil" do
+ suppress_keyword_warning do
+ obj = mock("destructure block keyword arguments")
+ obj.should_receive(:to_hash).and_return(nil)
+
+ result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
+ result.should == [1, [2, 3], obj, {}]
+ end
+ end
+
+ it "calls #to_hash on the last element when there are more arguments than parameters" do
+ suppress_keyword_warning do
+ x = mock("destructure matching block keyword argument")
+ x.should_receive(:to_hash).and_return({x: 9})
+
+ result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] }
+ result.should == [1, 2, 3, {x: 9}]
+ end
+ end
+
+ it "raises a TypeError if #to_hash does not return a Hash" do
+ obj = mock("destructure block keyword arguments")
+ obj.should_receive(:to_hash).and_return(1)
+
+ -> { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(TypeError)
+ end
+
+ it "raises the error raised inside #to_hash" do
+ obj = mock("destructure block keyword arguments")
+ error = RuntimeError.new("error while converting to a hash")
+ obj.should_receive(:to_hash).and_raise(error)
+
+ -> { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(error)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "does not call #to_hash on the last element if keyword arguments are present" do
+ obj = mock("destructure block keyword arguments")
+ obj.should_not_receive(:to_hash)
+
+ result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] }
+ result.should == [1, [2, 3], obj, {}]
+ end
+
+ it "does not call #to_hash on the last element when there are more arguments than parameters" do
+ x = mock("destructure matching block keyword argument")
+ x.should_not_receive(:to_hash)
+
+ result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] }
+ result.should == [1, 2, 3, {}]
+ end
+ end
+
+ it "does not call #to_ary on the Array" do
+ ary = [1, 2]
+ ary.should_not_receive(:to_ary)
+
+ m(ary) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
+ end
+ end
+
+ context "Object" do
+ it "calls #to_ary on the object when taking multiple arguments" do
+ obj = mock("destructure block arguments")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
+ end
+
+ it "does not call #to_ary when not taking any arguments" do
+ obj = mock("destructure block arguments")
+ obj.should_not_receive(:to_ary)
+
+ m(obj) { 1 }.should == 1
+ end
+
+ it "does not call #to_ary on the object when taking a single argument" do
+ obj = mock("destructure block arguments")
+ obj.should_not_receive(:to_ary)
+
+ m(obj) { |a| a }.should == obj
+ end
+
+ it "does not call #to_ary on the object when taking a single rest argument" do
+ obj = mock("destructure block arguments")
+ obj.should_not_receive(:to_ary)
+
+ m(obj) { |*a| a }.should == [obj]
+ end
+
+ it "receives the object if #to_ary returns nil" do
+ obj = mock("destructure block arguments")
+ obj.should_receive(:to_ary).and_return(nil)
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
+ end
+
+ it "receives the object if it does not respond to #to_ary" do
+ obj = Object.new
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
+ end
+
+ it "calls #respond_to? to check if object has method #to_ary" do
+ obj = mock("destructure block arguments")
+ obj.should_receive(:respond_to?).with(:to_ary, true).and_return(true)
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
+ end
+
+ it "receives the object if it does not respond to #respond_to?" do
+ obj = BasicObject.new
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
+ end
+
+ it "calls #to_ary on the object when it is defined dynamically" do
+ obj = Object.new
+ def obj.method_missing(name, *args, &block)
+ if name == :to_ary
+ [1, 2]
+ else
+ super
+ end
+ end
+ def obj.respond_to_missing?(name, include_private)
+ name == :to_ary
+ end
+
+ m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("destructure block arguments")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { m(obj) { |a, b| } }.should raise_error(TypeError)
+ end
+
+ it "raises error transparently if #to_ary raises error on its own" do
+ obj = Object.new
+ def obj.to_ary; raise "Exception raised in #to_ary" end
+
+ -> { m(obj) { |a, b| } }.should raise_error(RuntimeError, "Exception raised in #to_ary")
+ end
+ end
+end
+
+# TODO: rewrite
+describe "A block" do
+ before :each do
+ @y = BlockSpecs::Yielder.new
+ end
+
+ it "captures locals from the surrounding scope" do
+ var = 1
+ @y.z { var }.should == 1
+ end
+
+ it "allows for a leading space before the arguments" do
+ res = @y.s (:a){ 1 }
+ res.should == 1
+ end
+
+ it "allows to define a block variable with the same name as the enclosing block" do
+ o = BlockSpecs::OverwriteBlockVariable.new
+ o.z { 1 }.should == 1
+ end
+
+ it "does not capture a local when an argument has the same name" do
+ var = 1
+ @y.s(2) { |var| var }.should == 2
+ var.should == 1
+ end
+
+ it "does not capture a local when the block argument has the same name" do
+ var = 1
+ proc { |&var|
+ var.call(2)
+ }.call { |x| x }.should == 2
+ var.should == 1
+ end
+
+ describe "taking zero arguments" do
+ it "does not raise an exception when no values are yielded" do
+ @y.z { 1 }.should == 1
+ end
+
+ it "does not raise an exception when values are yielded" do
+ @y.s(0) { 1 }.should == 1
+ end
+
+ it "may include a rescue clause" do
+ eval("@y.z do raise ArgumentError; rescue ArgumentError; 7; end").should == 7
+ end
+ end
+
+ describe "taking || arguments" do
+ it "does not raise an exception when no values are yielded" do
+ @y.z { || 1 }.should == 1
+ end
+
+ it "does not raise an exception when values are yielded" do
+ @y.s(0) { || 1 }.should == 1
+ end
+
+ it "may include a rescue clause" do
+ eval('@y.z do || raise ArgumentError; rescue ArgumentError; 7; end').should == 7
+ end
+ end
+
+ describe "taking |a| arguments" do
+ it "assigns nil to the argument when no values are yielded" do
+ @y.z { |a| a }.should be_nil
+ end
+
+ it "assigns the value yielded to the argument" do
+ @y.s(1) { |a| a }.should == 1
+ end
+
+ it "does not call #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |a| a }.should equal(obj)
+ end
+
+ it "assigns the first value yielded to the argument" do
+ @y.m(1, 2) { |a| a }.should == 1
+ end
+
+ it "does not destructure a single Array value" do
+ @y.s([1, 2]) { |a| a }.should == [1, 2]
+ end
+
+ it "may include a rescue clause" do
+ eval('@y.s(1) do |x| raise ArgumentError; rescue ArgumentError; 7; end').should == 7
+ end
+ end
+
+ describe "taking |a, b| arguments" do
+ it "assigns nil to the arguments when no values are yielded" do
+ @y.z { |a, b| [a, b] }.should == [nil, nil]
+ end
+
+ it "assigns one value yielded to the first argument" do
+ @y.s(1) { |a, b| [a, b] }.should == [1, nil]
+ end
+
+ it "assigns the first two values yielded to the arguments" do
+ @y.m(1, 2, 3) { |a, b| [a, b] }.should == [1, 2]
+ end
+
+ it "does not destructure an Array value as one of several values yielded" do
+ @y.m([1, 2], 3, 4) { |a, b| [a, b] }.should == [[1, 2], 3]
+ end
+
+ it "assigns 'nil' and 'nil' to the arguments when a single, empty Array is yielded" do
+ @y.s([]) { |a, b| [a, b] }.should == [nil, nil]
+ end
+
+ it "assigns the element of a single element Array to the first argument" do
+ @y.s([1]) { |a, b| [a, b] }.should == [1, nil]
+ @y.s([nil]) { |a, b| [a, b] }.should == [nil, nil]
+ @y.s([[]]) { |a, b| [a, b] }.should == [[], nil]
+ end
+
+ it "destructures a single Array value yielded" do
+ @y.s([1, 2, 3]) { |a, b| [a, b] }.should == [1, 2]
+ end
+
+ it "destructures a splatted Array" do
+ @y.r([[]]) { |a, b| [a, b] }.should == [nil, nil]
+ @y.r([[1]]) { |a, b| [a, b] }.should == [1, nil]
+ end
+
+ it "calls #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @y.s(obj) { |a, b| [a, b] }.should == [1, 2]
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |a, b| [a, b] }.should == [1, 2]
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |a, b| [a, b] }.should == [obj, nil]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { @y.s(obj) { |a, b| } }.should raise_error(TypeError)
+ end
+
+ it "raises the original exception if #to_ary raises an exception" do
+ obj = mock("block yield to_ary raising an exception")
+ obj.should_receive(:to_ary).and_raise(ZeroDivisionError)
+
+ -> { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError)
+ end
+
+ end
+
+ describe "taking |a, *b| arguments" do
+ it "assigns 'nil' and '[]' to the arguments when no values are yielded" do
+ @y.z { |a, *b| [a, b] }.should == [nil, []]
+ end
+
+ it "assigns all yielded values after the first to the rest argument" do
+ @y.m(1, 2, 3) { |a, *b| [a, b] }.should == [1, [2, 3]]
+ end
+
+ it "assigns 'nil' and '[]' to the arguments when a single, empty Array is yielded" do
+ @y.s([]) { |a, *b| [a, b] }.should == [nil, []]
+ end
+
+ it "assigns the element of a single element Array to the first argument" do
+ @y.s([1]) { |a, *b| [a, b] }.should == [1, []]
+ @y.s([nil]) { |a, *b| [a, b] }.should == [nil, []]
+ @y.s([[]]) { |a, *b| [a, b] }.should == [[], []]
+ end
+
+ it "destructures a splatted Array" do
+ @y.r([[]]) { |a, *b| [a, b] }.should == [nil, []]
+ @y.r([[1]]) { |a, *b| [a, b] }.should == [1, []]
+ end
+
+ it "destructures a single Array value assigning the remaining values to the rest argument" do
+ @y.s([1, 2, 3]) { |a, *b| [a, b] }.should == [1, [2, 3]]
+ end
+
+ it "calls #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @y.s(obj) { |a, *b| [a, b] }.should == [1, [2]]
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |a, *b| [a, b] }.should == [1, [2]]
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |a, *b| [a, b] }.should == [obj, []]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { @y.s(obj) { |a, *b| } }.should raise_error(TypeError)
+ end
+ end
+
+ describe "taking |*| arguments" do
+ it "does not raise an exception when no values are yielded" do
+ @y.z { |*| 1 }.should == 1
+ end
+
+ it "does not raise an exception when values are yielded" do
+ @y.s(0) { |*| 1 }.should == 1
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |*| 1 }.should == 1
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |*| 1 }.should == 1
+ end
+
+ it "does not call #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |*| 1 }.should == 1
+ end
+ end
+
+ describe "taking |*a| arguments" do
+ it "assigns '[]' to the argument when no values are yielded" do
+ @y.z { |*a| a }.should == []
+ end
+
+ it "assigns a single value yielded to the argument as an Array" do
+ @y.s(1) { |*a| a }.should == [1]
+ end
+
+ it "assigns all the values passed to the argument as an Array" do
+ @y.m(1, 2, 3) { |*a| a }.should == [1, 2, 3]
+ end
+
+ it "assigns '[[]]' to the argument when passed an empty Array" do
+ @y.s([]) { |*a| a }.should == [[]]
+ end
+
+ it "assigns a single Array value passed to the argument by wrapping it in an Array" do
+ @y.s([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]]
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |*a| a }.should == [[1, 2]]
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |*a| a }.should == [obj]
+ end
+
+ it "does not call #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |*a| a }.should == [obj]
+ end
+ end
+
+ describe "taking |a, | arguments" do
+ it "assigns nil to the argument when no values are yielded" do
+ @y.z { |a, | a }.should be_nil
+ end
+
+ it "assigns the argument a single value yielded" do
+ @y.s(1) { |a, | a }.should == 1
+ end
+
+ it "assigns the argument the first value yielded" do
+ @y.m(1, 2) { |a, | a }.should == 1
+ end
+
+ it "assigns the argument the first of several values yielded when it is an Array" do
+ @y.m([1, 2], 3) { |a, | a }.should == [1, 2]
+ end
+
+ it "assigns nil to the argument when passed an empty Array" do
+ @y.s([]) { |a, | a }.should be_nil
+ end
+
+ it "assigns the argument the first element of the Array when passed a single Array" do
+ @y.s([1, 2]) { |a, | a }.should == 1
+ end
+
+ it "calls #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @y.s(obj) { |a, | a }.should == 1
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |a, | a }.should == 1
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |a, | a }.should == obj
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { @y.s(obj) { |a, | } }.should raise_error(TypeError)
+ end
+ end
+
+ describe "taking |(a, b)| arguments" do
+ it "assigns nil to the arguments when yielded no values" do
+ @y.z { |(a, b)| [a, b] }.should == [nil, nil]
+ end
+
+ it "destructures a single Array value yielded" do
+ @y.s([1, 2]) { |(a, b)| [a, b] }.should == [1, 2]
+ end
+
+ it "destructures a single Array value yielded when shadowing an outer variable" do
+ a = 9
+ @y.s([1, 2]) { |(a, b)| [a, b] }.should == [1, 2]
+ end
+
+ it "calls #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @y.s(obj) { |(a, b)| [a, b] }.should == [1, 2]
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |(a, b)| [a, b] }.should == [1, 2]
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |(a, b)| [a, b] }.should == [obj, nil]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { @y.s(obj) { |(a, b)| } }.should raise_error(TypeError)
+ end
+ end
+
+ describe "taking |(a, b), c| arguments" do
+ it "assigns nil to the arguments when yielded no values" do
+ @y.z { |(a, b), c| [a, b, c] }.should == [nil, nil, nil]
+ end
+
+ it "destructures a single one-level Array value yielded" do
+ @y.s([1, 2]) { |(a, b), c| [a, b, c] }.should == [1, nil, 2]
+ end
+
+ it "destructures a single multi-level Array value yielded" do
+ @y.s([[1, 2, 3], 4]) { |(a, b), c| [a, b, c] }.should == [1, 2, 4]
+ end
+
+ it "calls #to_ary to convert a single yielded object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @y.s(obj) { |(a, b), c| [a, b, c] }.should == [1, nil, 2]
+ end
+
+ it "does not call #to_ary if the single yielded object is an Array" do
+ obj = [1, 2]
+ obj.should_not_receive(:to_ary)
+
+ @y.s(obj) { |(a, b), c| [a, b, c] }.should == [1, nil, 2]
+ end
+
+ it "does not call #to_ary if the object does not respond to #to_ary" do
+ obj = mock("block yield no to_ary")
+
+ @y.s(obj) { |(a, b), c| [a, b, c] }.should == [obj, nil, nil]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ -> { @y.s(obj) { |(a, b), c| } }.should raise_error(TypeError)
+ end
+ end
+
+ describe "taking nested |a, (b, (c, d))|" do
+ it "assigns nil to the arguments when yielded no values" do
+ @y.m { |a, (b, (c, d))| [a, b, c, d] }.should == [nil, nil, nil, nil]
+ end
+
+ it "destructures separate yielded values" do
+ @y.m(1, 2) { |a, (b, (c, d))| [a, b, c, d] }.should == [1, 2, nil, nil]
+ end
+
+ it "destructures a nested Array value yielded" do
+ @y.m(1, [2, 3]) { |a, (b, (c, d))| [a, b, c, d] }.should == [1, 2, 3, nil]
+ end
+
+ it "destructures a single multi-level Array value yielded" do
+ @y.m(1, [2, [3, 4]]) { |a, (b, (c, d))| [a, b, c, d] }.should == [1, 2, 3, 4]
+ end
+ end
+
+ describe "taking nested |a, ((b, c), d)|" do
+ it "assigns nil to the arguments when yielded no values" do
+ @y.m { |a, ((b, c), d)| [a, b, c, d] }.should == [nil, nil, nil, nil]
+ end
+
+ it "destructures separate yielded values" do
+ @y.m(1, 2) { |a, ((b, c), d)| [a, b, c, d] }.should == [1, 2, nil, nil]
+ end
+
+ it "destructures a nested value yielded" do
+ @y.m(1, [2, 3]) { |a, ((b, c), d)| [a, b, c, d] }.should == [1, 2, nil, 3]
+ end
+
+ it "destructures a single multi-level Array value yielded" do
+ @y.m(1, [[2, 3], 4]) { |a, ((b, c), d)| [a, b, c, d] }.should == [1, 2, 3, 4]
+ end
+ end
+
+ describe "taking |*a, b:|" do
+ it "merges the hash into the splatted array" do
+ @y.k { |*a, b:| [a, b] }.should == [[], true]
+ end
+ end
+
+ describe "arguments with _" do
+ it "extracts arguments with _" do
+ @y.m([[1, 2, 3], 4]) { |(_, a, _), _| a }.should == 2
+ @y.m([1, [2, 3, 4]]) { |_, (_, a, _)| a }.should == 3
+ end
+
+ it "assigns the first variable named" do
+ @y.m(1, 2) { |_, _| _ }.should == 1
+ end
+ end
+
+ describe "taking identically-named arguments" do
+ it "raises a SyntaxError for standard arguments" do
+ -> { eval "lambda { |x,x| }" }.should raise_error(SyntaxError)
+ -> { eval "->(x,x) {}" }.should raise_error(SyntaxError)
+ -> { eval "Proc.new { |x,x| }" }.should raise_error(SyntaxError)
+ end
+
+ it "accepts unnamed arguments" do
+ eval("lambda { |_,_| }").should be_an_instance_of(Proc)
+ eval("->(_,_) {}").should be_an_instance_of(Proc)
+ eval("Proc.new { |_,_| }").should be_an_instance_of(Proc)
+ end
+ end
+end
+
+describe "Block-local variables" do
+ it "are introduced with a semi-colon in the parameter list" do
+ [1].map {|one; bl| bl }.should == [nil]
+ end
+
+ it "can be specified in a comma-separated list after the semi-colon" do
+ [1].map {|one; bl, bl2| [bl, bl2] }.should == [[nil, nil]]
+ end
+
+ it "can not have the same name as one of the standard parameters" do
+ -> { eval "[1].each {|foo; foo| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|foo, bar; glark, bar| }" }.should raise_error(SyntaxError)
+ end
+
+ it "can not be prefixed with an asterisk" do
+ -> { eval "[1].each {|foo; *bar| }" }.should raise_error(SyntaxError)
+ -> do
+ eval "[1].each {|foo, bar; glark, *fnord| }"
+ end.should raise_error(SyntaxError)
+ end
+
+ it "can not be prefixed with an ampersand" do
+ -> { eval "[1].each {|foo; &bar| }" }.should raise_error(SyntaxError)
+ -> do
+ eval "[1].each {|foo, bar; glark, &fnord| }"
+ end.should raise_error(SyntaxError)
+ end
+
+ it "can not be assigned default values" do
+ -> { eval "[1].each {|foo; bar=1| }" }.should raise_error(SyntaxError)
+ -> do
+ eval "[1].each {|foo, bar; glark, fnord=:fnord| }"
+ end.should raise_error(SyntaxError)
+ end
+
+ it "need not be preceded by standard parameters" do
+ [1].map {|; foo| foo }.should == [nil]
+ [1].map {|; glark, bar| [glark, bar] }.should == [[nil, nil]]
+ end
+
+ it "only allow a single semi-colon in the parameter list" do
+ -> { eval "[1].each {|foo; bar; glark| }" }.should raise_error(SyntaxError)
+ -> { eval "[1].each {|; bar; glark| }" }.should raise_error(SyntaxError)
+ end
+
+ it "override shadowed variables from the outer scope" do
+ out = :out
+ [1].each {|; out| out = :in }
+ out.should == :out
+
+ a = :a
+ b = :b
+ c = :c
+ d = :d
+ {ant: :bee}.each_pair do |a, b; c, d|
+ a = :A
+ b = :B
+ c = :C
+ d = :D
+ end
+ a.should == :a
+ b.should == :b
+ c.should == :c
+ d.should == :d
+ end
+
+ it "are not automatically instantiated in the outer scope" do
+ defined?(glark).should be_nil
+ [1].each {|;glark| 1}
+ defined?(glark).should be_nil
+ end
+
+ it "are automatically instantiated in the block" do
+ [1].each do |;glark|
+ glark.should be_nil
+ end
+ end
+
+ it "are visible in deeper scopes before initialization" do
+ [1].each {|;glark|
+ [1].each {
+ defined?(glark).should_not be_nil
+ glark = 1
+ }
+ glark.should == 1
+ }
+ end
+end
+
+describe "Post-args" do
+ it "appear after a splat" do
+ proc do |*a, b|
+ [a, b]
+ end.call(1, 2, 3).should == [[1, 2], 3]
+
+ proc do |*a, b, c|
+ [a, b, c]
+ end.call(1, 2, 3).should == [[1], 2, 3]
+
+ proc do |*a, b, c, d|
+ [a, b, c, d]
+ end.call(1, 2, 3).should == [[], 1, 2, 3]
+ end
+
+ it "are required for a lambda" do
+ -> {
+ -> *a, b do
+ [a, b]
+ end.call
+ }.should raise_error(ArgumentError)
+ end
+
+ it "are assigned to nil when not enough arguments are given to a proc" do
+ proc do |a, *b, c|
+ [a, b, c]
+ end.call.should == [nil, [], nil]
+ end
+
+ describe "with required args" do
+
+ it "gathers remaining args in the splat" do
+ proc do |a, *b, c|
+ [a, b, c]
+ end.call(1, 2, 3).should == [1, [2], 3]
+ end
+
+ it "has an empty splat when there are no remaining args" do
+ proc do |a, b, *c, d|
+ [a, b, c, d]
+ end.call(1, 2, 3).should == [1, 2, [], 3]
+
+ proc do |a, *b, c, d|
+ [a, b, c, d]
+ end.call(1, 2, 3).should == [1, [], 2, 3]
+ end
+ end
+
+ describe "with optional args" do
+
+ it "gathers remaining args in the splat" do
+ proc do |a=5, *b, c|
+ [a, b, c]
+ end.call(1, 2, 3).should == [1, [2], 3]
+ end
+
+ it "overrides the optional arg before gathering in the splat" do
+ proc do |a=5, *b, c|
+ [a, b, c]
+ end.call(2, 3).should == [2, [], 3]
+
+ proc do |a=5, b=6, *c, d|
+ [a, b, c, d]
+ end.call(1, 2, 3).should == [1, 2, [], 3]
+
+ proc do |a=5, *b, c, d|
+ [a, b, c, d]
+ end.call(1, 2, 3).should == [1, [], 2, 3]
+ end
+
+ it "uses the required arg before the optional and the splat" do
+ proc do |a=5, *b, c|
+ [a, b, c]
+ end.call(3).should == [5, [], 3]
+
+ proc do |a=5, b=6, *c, d|
+ [a, b, c, d]
+ end.call(3).should == [5, 6, [], 3]
+
+ proc do |a=5, *b, c, d|
+ [a, b, c, d]
+ end.call(2, 3).should == [5, [], 2, 3]
+ end
+
+ it "overrides the optional args from left to right before gathering the splat" do
+ proc do |a=5, b=6, *c, d|
+ [a, b, c, d]
+ end.call(2, 3).should == [2, 6, [], 3]
+ end
+
+ describe "with a circular argument reference" do
+ it "raises a SyntaxError if using an existing local with the same name as the argument" do
+ a = 1
+ -> {
+ @proc = eval "proc { |a=a| a }"
+ }.should raise_error(SyntaxError)
+ end
+
+ it "raises a SyntaxError if there is an existing method with the same name as the argument" do
+ def a; 1; end
+ -> {
+ @proc = eval "proc { |a=a| a }"
+ }.should raise_error(SyntaxError)
+ end
+
+ it "calls an existing method with the same name as the argument if explicitly using ()" do
+ def a; 1; end
+ proc { |a=a()| a }.call.should == 1
+ end
+ end
+ end
+
+ describe "with pattern matching" do
+ it "extracts matched blocks with post arguments" do
+ proc do |(a, *b, c), d, e|
+ [a, b, c, d, e]
+ end.call([1, 2, 3, 4], 5, 6).should == [1, [2, 3], 4, 5, 6]
+ end
+
+ it "allows empty splats" do
+ proc do |a, (*), b|
+ [a, b]
+ end.call([1, 2, 3]).should == [1, 3]
+ end
+ end
+end
+
+describe "Anonymous block forwarding" do
+ ruby_version_is "3.1" do
+ it "forwards blocks to other functions that formally declare anonymous blocks" do
+ eval <<-EOF
+ def b(&); c(&) end
+ def c(&); yield :non_null end
+ EOF
+
+ b { |c| c }.should == :non_null
+ end
+
+ it "requires the anonymous block parameter to be declared if directly passing a block" do
+ -> { eval "def a; b(&); end; def b; end" }.should raise_error(SyntaxError)
+ end
+
+ it "works when it's the only declared parameter" do
+ eval <<-EOF
+ def inner; yield end
+ def block_only(&); inner(&) end
+ EOF
+
+ block_only { 1 }.should == 1
+ end
+
+ it "works alongside positional parameters" do
+ eval <<-EOF
+ def inner; yield end
+ def pos(arg1, &); inner(&) end
+ EOF
+
+ pos(:a) { 1 }.should == 1
+ end
+
+ it "works alongside positional arguments and splatted keyword arguments" do
+ eval <<-EOF
+ def inner; yield end
+ def pos_kwrest(arg1, **kw, &); inner(&) end
+ EOF
+
+ pos_kwrest(:a, arg: 3) { 1 }.should == 1
+ end
+
+ it "works alongside positional arguments and disallowed keyword arguments" do
+ eval <<-EOF
+ def inner; yield end
+ def no_kw(arg1, **nil, &); inner(&) end
+ EOF
+
+ no_kw(:a) { 1 }.should == 1
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "works alongside explicit keyword arguments" do
+ eval <<-EOF
+ def inner; yield end
+ def rest_kw(*a, kwarg: 1, &); inner(&) end
+ def kw(kwarg: 1, &); inner(&) end
+ def pos_kw_kwrest(arg1, kwarg: 1, **kw, &); inner(&) end
+ def pos_rkw(arg1, kwarg1:, &); inner(&) end
+ def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &); inner(&) end
+ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &); inner(&) end
+ EOF
+
+ rest_kw { 1 }.should == 1
+ kw { 1 }.should == 1
+ pos_kw_kwrest(:a) { 1 }.should == 1
+ pos_rkw(:a, kwarg1: 3) { 1 }.should == 1
+ all(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1
+ all_kwrest(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1
+ end
+ end
+end
diff --git a/spec/ruby/language/break_spec.rb b/spec/ruby/language/break_spec.rb
new file mode 100644
index 0000000000..627cb4a071
--- /dev/null
+++ b/spec/ruby/language/break_spec.rb
@@ -0,0 +1,383 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/break'
+
+describe "The break statement in a block" do
+ before :each do
+ ScratchPad.record []
+ @program = BreakSpecs::Block.new
+ end
+
+ it "returns nil to method invoking the method yielding to the block when not passed an argument" do
+ @program.break_nil
+ ScratchPad.recorded.should == [:a, :aa, :b, nil, :d]
+ end
+
+ it "returns a value to the method invoking the method yielding to the block" do
+ @program.break_value
+ ScratchPad.recorded.should == [:a, :aa, :b, :break, :d]
+ end
+
+ describe "yielded inside a while" do
+ it "breaks out of the block" do
+ value = @program.break_in_block_in_while
+ ScratchPad.recorded.should == [:aa, :break]
+ value.should == :value
+ end
+ end
+
+ describe "captured and delegated to another method repeatedly" do
+ it "breaks out of the block" do
+ @program.looped_break_in_captured_block
+ ScratchPad.recorded.should == [:begin,
+ :preloop,
+ :predele,
+ :preyield,
+ :prebreak,
+ :postbreak,
+ :postyield,
+ :postdele,
+ :predele,
+ :preyield,
+ :prebreak,
+ :end]
+ end
+ end
+end
+
+describe "The break statement in a captured block" do
+ before :each do
+ ScratchPad.record []
+ @program = BreakSpecs::Block.new
+ end
+
+ describe "when the invocation of the scope creating the block is still active" do
+ it "raises a LocalJumpError when invoking the block from the scope creating the block" do
+ -> { @program.break_in_method }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :xa, :d, :b]
+ end
+
+ it "raises a LocalJumpError when invoking the block from a method" do
+ -> { @program.break_in_nested_method }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
+ end
+
+ it "raises a LocalJumpError when yielding to the block" do
+ -> { @program.break_in_yielding_method }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
+ end
+ end
+
+ describe "from a scope that has returned" do
+ it "raises a LocalJumpError when calling the block from a method" do
+ -> { @program.break_in_method_captured }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :za, :xa, :zd, :zb]
+ end
+
+ it "raises a LocalJumpError when yielding to the block" do
+ -> { @program.break_in_yield_captured }.should raise_error(LocalJumpError)
+ ScratchPad.recorded.should == [:a, :za, :xa, :zd, :aa, :zb]
+ end
+ end
+
+ describe "from another thread" do
+ it "raises a LocalJumpError when getting the value from another thread" do
+ thread_with_break = Thread.new do
+ begin
+ break :break
+ rescue LocalJumpError => e
+ e
+ end
+ end
+ thread_with_break.value.should be_an_instance_of(LocalJumpError)
+ end
+ end
+end
+
+describe "The break statement in a lambda" do
+ before :each do
+ ScratchPad.record []
+ @program = BreakSpecs::Lambda.new
+ end
+
+ it "returns from the lambda" do
+ l = -> {
+ ScratchPad << :before
+ break :foo
+ ScratchPad << :after
+ }
+ l.call.should == :foo
+ ScratchPad.recorded.should == [:before]
+ end
+
+ it "returns from the call site if the lambda is passed as a block" do
+ def mid(&b)
+ -> {
+ ScratchPad << :before
+ b.call
+ ScratchPad << :unreachable1
+ }.call
+ ScratchPad << :unreachable2
+ end
+
+ result = [1].each do |e|
+ mid {
+ break # This breaks from mid
+ ScratchPad << :unreachable3
+ }
+ ScratchPad << :after
+ end
+ result.should == [1]
+ ScratchPad.recorded.should == [:before, :after]
+ end
+
+ describe "when the invocation of the scope creating the lambda is still active" do
+ it "returns nil when not passed an argument" do
+ @program.break_in_defining_scope false
+ ScratchPad.recorded.should == [:a, :b, nil, :d]
+ end
+
+ it "returns a value to the scope creating and calling the lambda" do
+ @program.break_in_defining_scope
+ ScratchPad.recorded.should == [:a, :b, :break, :d]
+ end
+
+ it "returns a value to the method scope below invoking the lambda" do
+ @program.break_in_nested_scope
+ ScratchPad.recorded.should == [:a, :d, :aa, :b, :break, :bb, :e]
+ end
+
+ it "returns a value to a block scope invoking the lambda in a method below" do
+ @program.break_in_nested_scope_block
+ ScratchPad.recorded.should == [:a, :d, :aa, :aaa, :bb, :b, :break, :cc, :bbb, :dd, :e]
+ end
+
+ it "returns from the lambda" do
+ @program.break_in_nested_scope_yield
+ ScratchPad.recorded.should == [:a, :d, :aaa, :b, :bbb, :e]
+ end
+ end
+
+ describe "created at the toplevel" do
+ it "returns a value when invoking from the toplevel" do
+ code = fixture __FILE__, "break_lambda_toplevel.rb"
+ ruby_exe(code).chomp.should == "a,b,break,d"
+ end
+
+ it "returns a value when invoking from a method" do
+ code = fixture __FILE__, "break_lambda_toplevel_method.rb"
+ ruby_exe(code).chomp.should == "a,d,b,break,e,f"
+ end
+
+ it "returns a value when invoking from a block" do
+ code = fixture __FILE__, "break_lambda_toplevel_block.rb"
+ ruby_exe(code).chomp.should == "a,d,f,b,break,g,e,h"
+ end
+ end
+
+ describe "from a scope that has returned" do
+ it "returns a value to the method scope invoking the lambda" do
+ @program.break_in_method
+ ScratchPad.recorded.should == [:a, :la, :ld, :lb, :break, :b]
+ end
+
+ it "returns a value to the block scope invoking the lambda in a method" do
+ @program.break_in_block_in_method
+ ScratchPad.recorded.should == [:a, :aaa, :b, :la, :ld, :lb, :break, :c, :bbb, :d]
+ end
+
+ # By passing a lambda as a block argument, the user is requesting to treat
+ # the lambda as a block, which in this case means breaking to a scope that
+ # has returned. This is a subtle and confusing semantic where a block pass
+ # is removing the lambda-ness of a lambda.
+ it "raises a LocalJumpError when yielding to a lambda passed as a block argument" do
+ @program.break_in_method_yield
+ ScratchPad.recorded.should == [:a, :la, :ld, :aaa, :lb, :bbb, :b]
+ end
+ end
+end
+
+describe "Break inside a while loop" do
+ describe "with a value" do
+ it "exits the loop and returns the value" do
+ a = while true; break; end; a.should == nil
+ a = while true; break nil; end; a.should == nil
+ a = while true; break 1; end; a.should == 1
+ a = while true; break []; end; a.should == []
+ a = while true; break [1]; end; a.should == [1]
+ end
+
+ it "passes the value returned by a method with omitted parenthesis and passed block" do
+ obj = BreakSpecs::Block.new
+ -> { break obj.method :value do |x| x end }.call.should == :value
+ end
+ end
+
+ describe "with a splat" do
+ it "exits the loop and makes the splat an Array" do
+ a = while true; break *[1,2]; end; a.should == [1,2]
+ end
+
+ it "treats nil as an empty array" do
+ a = while true; break *nil; end; a.should == []
+ end
+
+ it "preserves an array as is" do
+ a = while true; break *[]; end; a.should == []
+ a = while true; break *[1,2]; end; a.should == [1,2]
+ a = while true; break *[nil]; end; a.should == [nil]
+ a = while true; break *[[]]; end; a.should == [[]]
+ end
+
+ it "wraps a non-Array in an Array" do
+ a = while true; break *1; end; a.should == [1]
+ end
+ end
+
+ it "stops a while loop when run" do
+ i = 0
+ while true
+ break if i == 2
+ i+=1
+ end
+ i.should == 2
+ end
+
+ it "causes a call with a block to return when run" do
+ at = 0
+ 0.upto(5) do |i|
+ at = i
+ break i if i == 2
+ end.should == 2
+ at.should == 2
+ end
+end
+
+
+# TODO: Rewrite all the specs from here to the end of the file in the style
+# above.
+describe "Executing break from within a block" do
+
+ before :each do
+ ScratchPad.clear
+ end
+
+ # Discovered in JRuby (see JRUBY-2756)
+ it "returns from the original invoking method even in case of chained calls" do
+ class BreakTest
+ # case #1: yield
+ def self.meth_with_yield(&b)
+ yield
+ fail("break returned from yield to wrong place")
+ end
+ def self.invoking_method(&b)
+ meth_with_yield(&b)
+ fail("break returned from 'meth_with_yield' method to wrong place")
+ end
+
+ # case #2: block.call
+ def self.meth_with_block_call(&b)
+ b.call
+ fail("break returned from b.call to wrong place")
+ end
+ def self.invoking_method2(&b)
+ meth_with_block_call(&b)
+ fail("break returned from 'meth_with_block_call' method to wrong place")
+ end
+ end
+
+ # this calls a method that calls another method that yields to the block
+ BreakTest.invoking_method do
+ break
+ fail("break didn't, well, break")
+ end
+
+ # this calls a method that calls another method that calls the block
+ BreakTest.invoking_method2 do
+ break
+ fail("break didn't, well, break")
+ end
+
+ res = BreakTest.invoking_method do
+ break :return_value
+ fail("break didn't, well, break")
+ end
+ res.should == :return_value
+
+ res = BreakTest.invoking_method2 do
+ break :return_value
+ fail("break didn't, well, break")
+ end
+ res.should == :return_value
+
+ end
+
+ class BreakTest2
+ def one
+ two { yield }
+ end
+
+ def two
+ yield
+ ensure
+ ScratchPad << :two_ensure
+ end
+
+ def three
+ begin
+ one { break }
+ ScratchPad << :three_post
+ ensure
+ ScratchPad << :three_ensure
+ end
+ end
+ end
+
+ it "runs ensures when continuing upward" do
+ ScratchPad.record []
+
+ bt2 = BreakTest2.new
+ bt2.one { break }
+ ScratchPad.recorded.should == [:two_ensure]
+ end
+
+ it "runs ensures when breaking from a loop" do
+ ScratchPad.record []
+
+ while true
+ begin
+ ScratchPad << :begin
+ break if true
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "doesn't run ensures in the destination method" do
+ ScratchPad.record []
+
+ bt2 = BreakTest2.new
+ bt2.three
+ ScratchPad.recorded.should == [:two_ensure, :three_post, :three_ensure]
+ end
+
+ it "works when passing through a super call" do
+ cls1 = Class.new { def foo; yield; end }
+ cls2 = Class.new(cls1) { def foo; super { break 1 }; end }
+
+ -> do
+ cls2.new.foo.should == 1
+ end.should_not raise_error
+ end
+
+ it "raises LocalJumpError when converted into a proc during a a super call" do
+ cls1 = Class.new { def foo(&b); b; end }
+ cls2 = Class.new(cls1) { def foo; super { break 1 }.call; end }
+
+ -> do
+ cls2.new.foo
+ end.should raise_error(LocalJumpError)
+ end
+end
diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb
new file mode 100644
index 0000000000..915c032a71
--- /dev/null
+++ b/spec/ruby/language/case_spec.rb
@@ -0,0 +1,445 @@
+require_relative '../spec_helper'
+
+describe "The 'case'-construct" do
+ it "evaluates the body of the when clause matching the case target expression" do
+ case 1
+ when 2; false
+ when 1; true
+ end.should == true
+ end
+
+ it "evaluates the body of the when clause whose array expression includes the case target expression" do
+ case 2
+ when 3, 4; false
+ when 1, 2; true
+ end.should == true
+ end
+
+ it "evaluates the body of the when clause in left-to-right order if it's an array expression" do
+ @calls = []
+ def foo; @calls << :foo; end
+ def bar; @calls << :bar; end
+
+ case true
+ when foo, bar;
+ end
+
+ @calls.should == [:foo, :bar]
+ end
+
+ it "evaluates the body of the when clause whose range expression includes the case target expression" do
+ case 5
+ when 21..30; false
+ when 1..20; true
+ end.should == true
+ end
+
+ it "returns nil when no 'then'-bodies are given" do
+ case "a"
+ when "a"
+ when "b"
+ end.should == nil
+ end
+
+ it "evaluates the 'else'-body when no other expression matches" do
+ case "c"
+ when "a"; 'foo'
+ when "b"; 'bar'
+ else 'zzz'
+ end.should == 'zzz'
+ end
+
+ it "returns nil when no expression matches and 'else'-body is empty" do
+ case "c"
+ when "a"; "a"
+ when "b"; "b"
+ else
+ end.should == nil
+ end
+
+ it "returns 2 when a then body is empty" do
+ case Object.new
+ when Numeric then
+ 1
+ when String then
+ # ok
+ else
+ 2
+ end.should == 2
+ end
+
+ it "returns the statement following 'then'" do
+ case "a"
+ when "a" then 'foo'
+ when "b" then 'bar'
+ end.should == 'foo'
+ end
+
+ it "tests classes with case equality" do
+ case "a"
+ when String
+ 'foo'
+ when Symbol
+ 'bar'
+ end.should == 'foo'
+ end
+
+ it "tests with matching regexps" do
+ case "hello"
+ when /abc/; false
+ when /^hell/; true
+ end.should == true
+ end
+
+ it "tests with matching regexps and sets $~ and captures" do
+ case "foo42"
+ when /oo(\d+)/
+ $~.should be_kind_of(MatchData)
+ $1.should == "42"
+ else
+ flunk
+ end
+ $~.should be_kind_of(MatchData)
+ $1.should == "42"
+ end
+
+ it "tests with a string interpolated in a regexp" do
+ digits = '\d+'
+ case "foo44"
+ when /oo(#{digits})/
+ $~.should be_kind_of(MatchData)
+ $1.should == "44"
+ else
+ flunk
+ end
+ $~.should be_kind_of(MatchData)
+ $1.should == "44"
+ end
+
+ it "tests with a regexp interpolated within another regexp" do
+ digits_regexp = /\d+/
+ case "foo43"
+ when /oo(#{digits_regexp})/
+ $~.should be_kind_of(MatchData)
+ $1.should == "43"
+ else
+ flunk
+ end
+ $~.should be_kind_of(MatchData)
+ $1.should == "43"
+ end
+
+ it "does not test with equality when given classes" do
+ case :symbol.class
+ when Symbol
+ "bar"
+ when String
+ "bar"
+ else
+ "foo"
+ end.should == "foo"
+ end
+
+ it "takes lists of values" do
+ case 'z'
+ when 'a', 'b', 'c', 'd'
+ "foo"
+ when 'x', 'y', 'z'
+ "bar"
+ end.should == "bar"
+
+ case 'b'
+ when 'a', 'b', 'c', 'd'
+ "foo"
+ when 'x', 'y', 'z'
+ "bar"
+ end.should == "foo"
+ end
+
+ it "tests an empty array" do
+ case []
+ when []
+ 'foo'
+ else
+ 'bar'
+ end.should == 'foo'
+ end
+
+ it "expands arrays to lists of values" do
+ case 'z'
+ when *['a', 'b', 'c', 'd']
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
+ end.should == "bar"
+ end
+
+ it "takes an expanded array in addition to a list of values" do
+ case 'f'
+ when 'f', *['a', 'b', 'c', 'd']
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
+ end.should == "foo"
+
+ case 'b'
+ when 'f', *['a', 'b', 'c', 'd']
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
+ end.should == "foo"
+ end
+
+ it "takes an expanded array before additional listed values" do
+ case 'f'
+ when *['a', 'b', 'c', 'd'], 'f'
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
+ end.should == 'foo'
+ end
+
+ it "expands arrays from variables before additional listed values" do
+ a = ['a', 'b', 'c']
+ case 'a'
+ when *a, 'd', 'e'
+ "foo"
+ when 'x'
+ "bar"
+ end.should == "foo"
+ end
+
+ it "expands arrays from variables before a single additional listed value" do
+ a = ['a', 'b', 'c']
+ case 'a'
+ when *a, 'd'
+ "foo"
+ when 'x'
+ "bar"
+ end.should == "foo"
+ end
+
+ it "expands multiple arrays from variables before additional listed values" do
+ a = ['a', 'b', 'c']
+ b = ['d', 'e', 'f']
+
+ case 'f'
+ when *a, *b, 'g', 'h'
+ "foo"
+ when 'x'
+ "bar"
+ end.should == "foo"
+ end
+
+ # MR: critical
+ it "concats arrays before expanding them" do
+ a = ['a', 'b', 'c', 'd']
+ b = ['f']
+
+ case 'f'
+ when 'f', *a|b
+ "foo"
+ when *['x', 'y', 'z']
+ "bar"
+ end.should == "foo"
+ end
+
+ it "never matches when clauses with no values" do
+ case nil
+ when *[]
+ "foo"
+ end.should == nil
+ end
+
+ it "lets you define a method after the case statement" do
+ case (def foo; 'foo'; end; 'f')
+ when 'a'
+ 'foo'
+ when 'f'
+ 'bar'
+ end.should == 'bar'
+ end
+
+ it "raises a SyntaxError when 'else' is used when no 'when' is given" do
+ -> {
+ eval <<-CODE
+ case 4
+ else
+ true
+ end
+ CODE
+ }.should raise_error(SyntaxError)
+ end
+
+ it "raises a SyntaxError when 'else' is used before a 'when' was given" do
+ -> {
+ eval <<-CODE
+ case 4
+ else
+ true
+ when 4; false
+ end
+ CODE
+ }.should raise_error(SyntaxError)
+ end
+
+ it "supports nested case statements" do
+ result = false
+ case :x
+ when Symbol
+ case :y
+ when Symbol
+ result = true
+ end
+ end
+ result.should == true
+ end
+
+ it "supports nested case statements followed by a when with a splatted array" do
+ result = false
+ case :x
+ when Symbol
+ case :y
+ when Symbol
+ result = true
+ end
+ when *[Symbol]
+ result = false
+ end
+ result.should == true
+ end
+
+ it "supports nested case statements followed by a when with a splatted non-array" do
+ result = false
+ case :x
+ when Symbol
+ case :y
+ when Symbol
+ result = true
+ end
+ when *Symbol
+ result = false
+ end
+ result.should == true
+ end
+
+ it "works even if there's only one when statement" do
+ case 1
+ when 1
+ 100
+ end.should == 100
+ end
+end
+
+describe "The 'case'-construct with no target expression" do
+ it "evaluates the body of the first clause when at least one of its condition expressions is true" do
+ case
+ when true, false; 'foo'
+ end.should == 'foo'
+ end
+
+ it "evaluates the body of the first when clause that is not false/nil" do
+ case
+ when false; 'foo'
+ when 2; 'bar'
+ when 1 == 1; 'baz'
+ end.should == 'bar'
+
+ case
+ when false; 'foo'
+ when nil; 'foo'
+ when 1 == 1; 'bar'
+ end.should == 'bar'
+ end
+
+ it "evaluates the body of the else clause if all when clauses are false/nil" do
+ case
+ when false; 'foo'
+ when nil; 'foo'
+ when 1 == 2; 'bar'
+ else 'baz'
+ end.should == 'baz'
+ end
+
+ it "evaluates multiple conditional expressions as a boolean disjunction" do
+ case
+ when true, false; 'foo'
+ else 'bar'
+ end.should == 'foo'
+
+ case
+ when false, true; 'foo'
+ else 'bar'
+ end.should == 'foo'
+ end
+
+ it "evaluates true as only 'true' when true is the first clause" do
+ case 1
+ when true; "bad"
+ when Integer; "good"
+ end.should == "good"
+ end
+
+ it "evaluates false as only 'false' when false is the first clause" do
+ case nil
+ when false; "bad"
+ when nil; "good"
+ end.should == "good"
+ end
+
+ it "treats a literal array as its own when argument, rather than a list of arguments" do
+ case 'foo'
+ when ['foo', 'foo']; 'bad'
+ when 'foo'; 'good'
+ end.should == 'good'
+ end
+
+ it "takes multiple expanded arrays" do
+ a1 = ['f', 'o', 'o']
+ a2 = ['b', 'a', 'r']
+
+ case 'f'
+ when *a1, *['x', 'y', 'z']
+ "foo"
+ when *a2, *['x', 'y', 'z']
+ "bar"
+ end.should == "foo"
+
+ case 'b'
+ when *a1, *['x', 'y', 'z']
+ "foo"
+ when *a2, *['x', 'y', 'z']
+ "bar"
+ end.should == "bar"
+ end
+
+ it "calls === even when private" do
+ klass = Class.new do
+ def ===(o)
+ true
+ end
+ private :===
+ end
+
+ case 1
+ when klass.new
+ :called
+ end.should == :called
+ end
+
+ it "accepts complex expressions within ()" do
+ case 'a'
+ when (raise if 2+2 == 3; /a/)
+ :called
+ end.should == :called
+ end
+
+ # Homogeneous cases are often optimized to avoid === using a jump table, and should be tested separately.
+ # See https://github.com/jruby/jruby/issues/6440
+ it "handles homogeneous cases" do
+ case
+ when 1; 'foo'
+ when 2; 'bar'
+ end.should == 'foo'
+ end
+end
diff --git a/spec/ruby/language/class_spec.rb b/spec/ruby/language/class_spec.rb
new file mode 100644
index 0000000000..877895bf15
--- /dev/null
+++ b/spec/ruby/language/class_spec.rb
@@ -0,0 +1,363 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
+
+ClassSpecsNumber = 12
+
+module ClassSpecs
+ Number = 12
+end
+
+describe "The class keyword" do
+ it "creates a new class with semicolon" do
+ class ClassSpecsKeywordWithSemicolon; end
+ ClassSpecsKeywordWithSemicolon.should be_an_instance_of(Class)
+ end
+
+ it "does not raise a SyntaxError when opening a class without a semicolon" do
+ eval "class ClassSpecsKeywordWithoutSemicolon end"
+ ClassSpecsKeywordWithoutSemicolon.should be_an_instance_of(Class)
+ end
+
+ it "can redefine a class when called from a block" do
+ ClassSpecs::DEFINE_CLASS.call
+ A.should be_an_instance_of(Class)
+
+ Object.send(:remove_const, :A)
+ defined?(A).should be_nil
+
+ ClassSpecs::DEFINE_CLASS.call
+ A.should be_an_instance_of(Class)
+ ensure
+ Object.send(:remove_const, :A) if defined?(::A)
+ end
+end
+
+describe "A class definition" do
+ it "creates a new class" do
+ ClassSpecs::A.should be_kind_of(Class)
+ ClassSpecs::A.new.should be_kind_of(ClassSpecs::A)
+ end
+
+ it "has no class variables" do
+ ClassSpecs::A.class_variables.should == []
+ end
+
+ it "raises TypeError if constant given as class name exists and is not a Module" do
+ -> {
+ class ClassSpecsNumber
+ end
+ }.should raise_error(TypeError)
+ end
+
+ # test case known to be detecting bugs (JRuby, MRI)
+ it "raises TypeError if the constant qualifying the class is nil" do
+ -> {
+ class nil::Foo
+ end
+ }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError if any constant qualifying the class is not a Module" do
+ -> {
+ class ClassSpecs::Number::MyClass
+ end
+ }.should raise_error(TypeError)
+
+ -> {
+ class ClassSpecsNumber::MyClass
+ end
+ }.should raise_error(TypeError)
+ end
+
+ it "inherits from Object by default" do
+ ClassSpecs::A.superclass.should == Object
+ end
+
+ it "raises an error when trying to change the superclass" do
+ module ClassSpecs
+ class SuperclassResetToSubclass < L
+ end
+ -> {
+ class SuperclassResetToSubclass < M
+ end
+ }.should raise_error(TypeError, /superclass mismatch/)
+ end
+ end
+
+ it "raises an error when reopening a class with BasicObject as superclass" do
+ module ClassSpecs
+ class SuperclassReopenedBasicObject < A
+ end
+ SuperclassReopenedBasicObject.superclass.should == A
+
+ -> {
+ class SuperclassReopenedBasicObject < BasicObject
+ end
+ }.should raise_error(TypeError, /superclass mismatch/)
+ SuperclassReopenedBasicObject.superclass.should == A
+ end
+ end
+
+ # [Bug #12367] [ruby-core:75446]
+ it "raises an error when reopening a class with Object as superclass" do
+ module ClassSpecs
+ class SuperclassReopenedObject < A
+ end
+ SuperclassReopenedObject.superclass.should == A
+
+ -> {
+ class SuperclassReopenedObject < Object
+ end
+ }.should raise_error(TypeError, /superclass mismatch/)
+ SuperclassReopenedObject.superclass.should == A
+ end
+ end
+
+ it "allows reopening a class without specifying the superclass" do
+ module ClassSpecs
+ class SuperclassNotGiven < A
+ end
+ SuperclassNotGiven.superclass.should == A
+
+ class SuperclassNotGiven
+ end
+ SuperclassNotGiven.superclass.should == A
+ end
+ end
+
+ it "does not allow to set the superclass even if it was not specified by the first declaration" do
+ module ClassSpecs
+ class NoSuperclassSet
+ end
+
+ -> {
+ class NoSuperclassSet < String
+ end
+ }.should raise_error(TypeError, /superclass mismatch/)
+ end
+ end
+
+ it "allows using self as the superclass if self is a class" do
+ ClassSpecs::I::J.superclass.should == ClassSpecs::I
+
+ -> {
+ class ShouldNotWork < self; end
+ }.should raise_error(TypeError)
+ end
+
+ it "first evaluates the superclass before checking if the class already exists" do
+ module ClassSpecs
+ class SuperclassEvaluatedFirst
+ end
+ a = SuperclassEvaluatedFirst
+
+ class SuperclassEvaluatedFirst < remove_const(:SuperclassEvaluatedFirst)
+ end
+ b = SuperclassEvaluatedFirst
+ b.superclass.should == a
+ end
+ end
+
+ it "raises a TypeError if inheriting from a metaclass" do
+ obj = mock("metaclass super")
+ meta = obj.singleton_class
+ -> { class ClassSpecs::MetaclassSuper < meta; end }.should raise_error(TypeError)
+ end
+
+ it "allows the declaration of class variables in the body" do
+ ClassSpecs.string_class_variables(ClassSpecs::B).should == ["@@cvar"]
+ ClassSpecs::B.send(:class_variable_get, :@@cvar).should == :cvar
+ end
+
+ it "stores instance variables defined in the class body in the class object" do
+ ClassSpecs.string_instance_variables(ClassSpecs::B).should include("@ivar")
+ ClassSpecs::B.instance_variable_get(:@ivar).should == :ivar
+ end
+
+ it "allows the declaration of class variables in a class method" do
+ ClassSpecs::C.class_variables.should == []
+ ClassSpecs::C.make_class_variable
+ ClassSpecs.string_class_variables(ClassSpecs::C).should == ["@@cvar"]
+ ClassSpecs::C.remove_class_variable :@@cvar
+ end
+
+ it "allows the definition of class-level instance variables in a class method" do
+ ClassSpecs.string_instance_variables(ClassSpecs::C).should_not include("@civ")
+ ClassSpecs::C.make_class_instance_variable
+ ClassSpecs.string_instance_variables(ClassSpecs::C).should include("@civ")
+ ClassSpecs::C.remove_instance_variable :@civ
+ end
+
+ it "allows the declaration of class variables in an instance method" do
+ ClassSpecs::D.class_variables.should == []
+ ClassSpecs::D.new.make_class_variable
+ ClassSpecs.string_class_variables(ClassSpecs::D).should == ["@@cvar"]
+ ClassSpecs::D.remove_class_variable :@@cvar
+ end
+
+ it "allows the definition of instance methods" do
+ ClassSpecs::E.new.meth.should == :meth
+ end
+
+ it "allows the definition of class methods" do
+ ClassSpecs::E.cmeth.should == :cmeth
+ end
+
+ it "allows the definition of class methods using class << self" do
+ ClassSpecs::E.smeth.should == :smeth
+ end
+
+ it "allows the definition of Constants" do
+ Object.const_defined?('CONSTANT').should == false
+ ClassSpecs::E.const_defined?('CONSTANT').should == true
+ ClassSpecs::E::CONSTANT.should == :constant!
+ end
+
+ it "returns the value of the last statement in the body" do
+ class ClassSpecs::Empty; end.should == nil
+ class ClassSpecs::Twenty; 20; end.should == 20
+ class ClassSpecs::Plus; 10 + 20; end.should == 30
+ class ClassSpecs::Singleton; class << self; :singleton; end; end.should == :singleton
+ end
+
+ describe "within a block creates a new class in the lexical scope" do
+ it "for named classes at the toplevel" do
+ klass = Class.new do
+ class CS_CONST_CLASS_SPECS
+ end
+
+ def self.get_class_name
+ CS_CONST_CLASS_SPECS.name
+ end
+ end
+
+ klass.get_class_name.should == 'CS_CONST_CLASS_SPECS'
+ ::CS_CONST_CLASS_SPECS.name.should == 'CS_CONST_CLASS_SPECS'
+ end
+
+ it "for named classes in a module" do
+ klass = ClassSpecs::ANON_CLASS_FOR_NEW.call
+
+ ClassSpecs::NamedInModule.name.should == 'ClassSpecs::NamedInModule'
+ klass.get_class_name.should == 'ClassSpecs::NamedInModule'
+ end
+
+ it "for anonymous classes" do
+ klass = Class.new do
+ def self.get_class
+ Class.new do
+ def self.foo
+ 'bar'
+ end
+ end
+ end
+
+ def self.get_result
+ get_class.foo
+ end
+ end
+
+ klass.get_result.should == 'bar'
+ end
+
+ it "for anonymous classes assigned to a constant" do
+ klass = Class.new do
+ AnonWithConstant = Class.new
+
+ def self.get_class_name
+ AnonWithConstant.name
+ end
+ end
+
+ AnonWithConstant.name.should == 'AnonWithConstant'
+ klass.get_class_name.should == 'AnonWithConstant'
+ end
+ end
+end
+
+describe "An outer class definition" do
+ it "contains the inner classes" do
+ ClassSpecs::Container.constants.should include(:A, :B)
+ end
+end
+
+describe "A class definition extending an object (sclass)" do
+ it "allows adding methods" do
+ ClassSpecs::O.smeth.should == :smeth
+ end
+
+ it "raises a TypeError when trying to extend numbers" do
+ -> {
+ eval <<-CODE
+ class << 1
+ def xyz
+ self
+ end
+ end
+ CODE
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when trying to extend non-Class" do
+ error_msg = /superclass must be a.* Class/
+ -> { class TestClass < ""; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < 1; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < :symbol; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < mock('o'); end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < Module.new; end }.should raise_error(TypeError, error_msg)
+ -> { class TestClass < BasicObject.new; end }.should raise_error(TypeError, error_msg)
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "allows accessing the block of the original scope" do
+ suppress_warning do
+ ClassSpecs.sclass_with_block { 123 }.should == 123
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "does not allow accessing the block of the original scope" do
+ -> {
+ ClassSpecs.sclass_with_block { 123 }
+ }.should raise_error(SyntaxError)
+ end
+ end
+
+ it "can use return to cause the enclosing method to return" do
+ ClassSpecs.sclass_with_return.should == :inner
+ end
+end
+
+describe "Reopening a class" do
+ it "extends the previous definitions" do
+ c = ClassSpecs::F.new
+ c.meth.should == :meth
+ c.another.should == :another
+ end
+
+ it "overwrites existing methods" do
+ ClassSpecs::G.new.override.should == :override
+ end
+
+ it "raises a TypeError when superclasses mismatch" do
+ -> { class ClassSpecs::A < Array; end }.should raise_error(TypeError)
+ end
+
+ it "adds new methods to subclasses" do
+ -> { ClassSpecs::M.m }.should raise_error(NoMethodError)
+ class ClassSpecs::L
+ def self.m
+ 1
+ end
+ end
+ ClassSpecs::M.m.should == 1
+ ClassSpecs::L.singleton_class.send(:remove_method, :m)
+ end
+end
+
+describe "class provides hooks" do
+ it "calls inherited when a class is created" do
+ ClassSpecs::H.track_inherited.should == [ClassSpecs::K]
+ end
+end
diff --git a/spec/ruby/language/class_variable_spec.rb b/spec/ruby/language/class_variable_spec.rb
new file mode 100644
index 0000000000..f98deaa081
--- /dev/null
+++ b/spec/ruby/language/class_variable_spec.rb
@@ -0,0 +1,116 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/class_variables'
+
+describe "A class variable" do
+ after :each do
+ ClassVariablesSpec::ClassA.new.cvar_a = :cvar_a
+ end
+
+ it "can be accessed from a subclass" do
+ ClassVariablesSpec::ClassB.new.cvar_a.should == :cvar_a
+ end
+
+ it "is set in the superclass" do
+ a = ClassVariablesSpec::ClassA.new
+ b = ClassVariablesSpec::ClassB.new
+ b.cvar_a = :new_val
+
+ a.cvar_a.should == :new_val
+ end
+end
+
+describe "A class variable defined in a module" do
+ after :each do
+ ClassVariablesSpec::ClassC.cvar_m = :value
+ ClassVariablesSpec::ClassC.remove_class_variable(:@@cvar) if ClassVariablesSpec::ClassC.cvar_defined?
+ end
+
+ it "can be accessed from classes that extend the module" do
+ ClassVariablesSpec::ClassC.cvar_m.should == :value
+ end
+
+ it "is not defined in these classes" do
+ ClassVariablesSpec::ClassC.cvar_defined?.should be_false
+ end
+
+ it "is only updated in the module a method defined in the module is used" do
+ ClassVariablesSpec::ClassC.cvar_m = "new value"
+ ClassVariablesSpec::ClassC.cvar_m.should == "new value"
+
+ ClassVariablesSpec::ClassC.cvar_defined?.should be_false
+ end
+
+ it "is updated in the class when a Method defined in the class is used" do
+ ClassVariablesSpec::ClassC.cvar_c = "new value"
+ ClassVariablesSpec::ClassC.cvar_defined?.should be_true
+ end
+
+ it "can be accessed inside the class using the module methods" do
+ ClassVariablesSpec::ClassC.cvar_c = "new value"
+ ClassVariablesSpec::ClassC.cvar_m.should == :value
+ end
+
+ it "can be accessed from modules that extend the module" do
+ ClassVariablesSpec::ModuleO.cvar_n.should == :value
+ end
+
+ it "is defined in the extended module" do
+ ClassVariablesSpec::ModuleN.class_variable_defined?(:@@cvar_n).should be_true
+ end
+
+ it "is not defined in the extending module" do
+ ClassVariablesSpec::ModuleO.class_variable_defined?(:@@cvar_n).should be_false
+ end
+end
+
+describe 'A class variable definition' do
+ it "is created in a module if any of the parents do not define it" do
+ a = Class.new
+ b = Class.new(a)
+ c = Class.new(b)
+ b.class_variable_set(:@@cv, :value)
+
+ -> { a.class_variable_get(:@@cv) }.should raise_error(NameError)
+ b.class_variable_get(:@@cv).should == :value
+ c.class_variable_get(:@@cv).should == :value
+
+ # updates the same variable
+ c.class_variable_set(:@@cv, :next)
+
+ -> { a.class_variable_get(:@@cv) }.should raise_error(NameError)
+ b.class_variable_get(:@@cv).should == :next
+ c.class_variable_get(:@@cv).should == :next
+ end
+end
+
+ruby_version_is "3.0" do
+ describe 'Accessing a class variable' do
+ it "raises a RuntimeError when accessed from the toplevel scope (not in some module or class)" do
+ -> {
+ eval "@@cvar_toplevel1"
+ }.should raise_error(RuntimeError, 'class variable access from toplevel')
+ -> {
+ eval "@@cvar_toplevel2 = 2"
+ }.should raise_error(RuntimeError, 'class variable access from toplevel')
+ end
+
+ it "does not raise an error when checking if defined from the toplevel scope" do
+ -> {
+ eval "defined?(@@cvar_toplevel1)"
+ }.should_not raise_error
+ end
+
+ it "raises a RuntimeError when a class variable is overtaken in an ancestor class" do
+ parent = Class.new()
+ subclass = Class.new(parent)
+ subclass.class_variable_set(:@@cvar_overtaken, :subclass)
+ parent.class_variable_set(:@@cvar_overtaken, :parent)
+
+ -> {
+ subclass.class_variable_get(:@@cvar_overtaken)
+ }.should raise_error(RuntimeError, /class variable @@cvar_overtaken of .+ is overtaken by .+/)
+
+ parent.class_variable_get(:@@cvar_overtaken).should == :parent
+ end
+ end
+end
diff --git a/spec/ruby/language/comment_spec.rb b/spec/ruby/language/comment_spec.rb
new file mode 100644
index 0000000000..dd788e681c
--- /dev/null
+++ b/spec/ruby/language/comment_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+describe "The comment" do
+ it "can be placed between fluent dot now" do
+ code = <<~CODE
+ 10
+ # some comment
+ .to_s
+ CODE
+
+ eval(code).should == '10'
+ end
+end
diff --git a/spec/ruby/language/constants_spec.rb b/spec/ruby/language/constants_spec.rb
new file mode 100644
index 0000000000..8586e46158
--- /dev/null
+++ b/spec/ruby/language/constants_spec.rb
@@ -0,0 +1,750 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/constants'
+require_relative 'fixtures/constants_sclass'
+require_relative 'fixtures/constant_visibility'
+
+# Read the documentation in fixtures/constants.rb for the guidelines and
+# rationale for the structure and organization of these specs.
+
+describe "Literal (A::X) constant resolution" do
+ describe "with statically assigned constants" do
+ it "searches the immediate class or module scope first" do
+ ConstantSpecs::ClassA::CS_CONST10.should == :const10_10
+ ConstantSpecs::ModuleA::CS_CONST10.should == :const10_1
+ ConstantSpecs::ParentA::CS_CONST10.should == :const10_5
+ ConstantSpecs::ContainerA::CS_CONST10.should == :const10_2
+ ConstantSpecs::ContainerA::ChildA::CS_CONST10.should == :const10_3
+ end
+
+ it "searches a module included in the immediate class before the superclass" do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST15.should == :const15_1
+ end
+
+ it "searches the superclass before a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST11.should == :const11_1
+ end
+
+ it "searches a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST12.should == :const12_1
+ end
+
+ it "searches the superclass chain" do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST13.should == :const13
+ end
+
+ it "searches Object if no class or module qualifier is given" do
+ CS_CONST1.should == :const1
+ CS_CONST10.should == :const10_1
+ end
+
+ it "searches Object after searching other scopes" do
+ module ConstantSpecs::SpecAdded1
+ CS_CONST10.should == :const10_1
+ end
+ end
+
+ it "searches Object if a toplevel qualifier (::X) is given" do
+ ::CS_CONST1.should == :const1
+ ::CS_CONST10.should == :const10_1
+ end
+
+ it "does not search the singleton class of the class or module" do
+ -> do
+ ConstantSpecs::ContainerA::ChildA::CS_CONST14
+ end.should raise_error(NameError)
+ -> { ConstantSpecs::CS_CONST14 }.should raise_error(NameError)
+ end
+ end
+
+ describe "with dynamically assigned constants" do
+ it "searches the immediate class or module scope first" do
+ ConstantSpecs::ClassB::CS_CONST101 = :const101_1
+ ConstantSpecs::ClassB::CS_CONST101.should == :const101_1
+
+ ConstantSpecs::ParentB::CS_CONST101 = :const101_2
+ ConstantSpecs::ParentB::CS_CONST101.should == :const101_2
+
+ ConstantSpecs::ContainerB::CS_CONST101 = :const101_3
+ ConstantSpecs::ContainerB::CS_CONST101.should == :const101_3
+
+ ConstantSpecs::ContainerB::ChildB::CS_CONST101 = :const101_4
+ ConstantSpecs::ContainerB::ChildB::CS_CONST101.should == :const101_4
+
+ ConstantSpecs::ModuleA::CS_CONST101 = :const101_5
+ ConstantSpecs::ModuleA::CS_CONST101.should == :const101_5
+ end
+
+ it "searches a module included in the immediate class before the superclass" do
+ ConstantSpecs::ParentB::CS_CONST102 = :const102_1
+ ConstantSpecs::ModuleF::CS_CONST102 = :const102_2
+ ConstantSpecs::ContainerB::ChildB::CS_CONST102.should == :const102_2
+ end
+
+ it "searches the superclass before a module included in the superclass" do
+ ConstantSpecs::ModuleE::CS_CONST103 = :const103_1
+ ConstantSpecs::ParentB::CS_CONST103 = :const103_2
+ ConstantSpecs::ContainerB::ChildB::CS_CONST103.should == :const103_2
+ end
+
+ it "searches a module included in the superclass" do
+ ConstantSpecs::ModuleA::CS_CONST104 = :const104_1
+ ConstantSpecs::ModuleE::CS_CONST104 = :const104_2
+ ConstantSpecs::ContainerB::ChildB::CS_CONST104.should == :const104_2
+ end
+
+ it "searches the superclass chain" do
+ ConstantSpecs::ModuleA::CS_CONST105 = :const105
+ ConstantSpecs::ContainerB::ChildB::CS_CONST105.should == :const105
+ end
+
+ it "searches Object if no class or module qualifier is given" do
+ CS_CONST106 = :const106
+ CS_CONST106.should == :const106
+ end
+
+ it "searches Object if a toplevel qualifier (::X) is given" do
+ ::CS_CONST107 = :const107
+ ::CS_CONST107.should == :const107
+ end
+
+ it "does not search the singleton class of the class or module" do
+ class << ConstantSpecs::ContainerB::ChildB
+ CS_CONST108 = :const108_1
+ end
+
+ -> do
+ ConstantSpecs::ContainerB::ChildB::CS_CONST108
+ end.should raise_error(NameError)
+
+ module ConstantSpecs
+ class << self
+ CS_CONST108 = :const108_2
+ end
+ end
+
+ -> { ConstantSpecs::CS_CONST108 }.should raise_error(NameError)
+ end
+
+ it "returns the updated value when a constant is reassigned" do
+ ConstantSpecs::ClassB::CS_CONST109 = :const109_1
+ ConstantSpecs::ClassB::CS_CONST109.should == :const109_1
+
+ -> {
+ ConstantSpecs::ClassB::CS_CONST109 = :const109_2
+ }.should complain(/already initialized constant/)
+ ConstantSpecs::ClassB::CS_CONST109.should == :const109_2
+ end
+
+ ruby_version_is "3.2" do
+ it "evaluates left-to-right" do
+ mod = Module.new
+
+ mod.module_eval <<-EOC
+ order = []
+ ConstantSpecsRHS = Module.new
+ (order << :lhs; ConstantSpecsRHS)::B = (order << :rhs)
+ EOC
+
+ mod::ConstantSpecsRHS::B.should == [:lhs, :rhs]
+ end
+ end
+
+ ruby_version_is ""..."3.2" do
+ it "evaluates the right hand side before evaluating a constant path" do
+ mod = Module.new
+
+ mod.module_eval <<-EOC
+ ConstantSpecsRHS::B = begin
+ module ConstantSpecsRHS; end
+
+ "hello"
+ end
+ EOC
+
+ mod::ConstantSpecsRHS::B.should == 'hello'
+ end
+ end
+ end
+
+ it "raises a NameError if no constant is defined in the search path" do
+ -> { ConstantSpecs::ParentA::CS_CONSTX }.should raise_error(NameError)
+ end
+
+ ruby_version_is "3.0" do
+ it "uses the module or class #name to craft the error message" do
+ mod = Module.new do
+ def self.name
+ "ModuleName"
+ end
+
+ def self.inspect
+ "<unusable info>"
+ end
+ end
+
+ -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant ModuleName::DOES_NOT_EXIST/)
+ end
+
+ it "uses the module or class #inspect to craft the error message if they are anonymous" do
+ mod = Module.new do
+ def self.name
+ nil
+ end
+
+ def self.inspect
+ "<unusable info>"
+ end
+ end
+
+ -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant <unusable info>::DOES_NOT_EXIST/)
+ end
+ end
+
+ it "sends #const_missing to the original class or module scope" do
+ ConstantSpecs::ClassA::CS_CONSTX.should == :CS_CONSTX
+ end
+
+ it "evaluates the qualifier" do
+ ConstantSpecs.get_const::CS_CONST2.should == :const2
+ end
+
+ it "raises a TypeError if a non-class or non-module qualifier is given" do
+ -> { CS_CONST1::CS_CONST }.should raise_error(TypeError)
+ -> { 1::CS_CONST }.should raise_error(TypeError)
+ -> { "mod"::CS_CONST }.should raise_error(TypeError)
+ -> { false::CS_CONST }.should raise_error(TypeError)
+ end
+end
+
+describe "Constant resolution within methods" do
+ describe "with statically assigned constants" do
+ it "searches the immediate class or module scope first" do
+ ConstantSpecs::ClassA.const10.should == :const10_10
+ ConstantSpecs::ParentA.const10.should == :const10_5
+ ConstantSpecs::ContainerA.const10.should == :const10_2
+ ConstantSpecs::ContainerA::ChildA.const10.should == :const10_3
+
+ ConstantSpecs::ClassA.new.const10.should == :const10_10
+ ConstantSpecs::ParentA.new.const10.should == :const10_5
+ ConstantSpecs::ContainerA::ChildA.new.const10.should == :const10_3
+ end
+
+ it "searches a module included in the immediate class before the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const15.should == :const15_1
+ ConstantSpecs::ContainerA::ChildA.new.const15.should == :const15_1
+ end
+
+ it "searches the superclass before a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const11.should == :const11_1
+ ConstantSpecs::ContainerA::ChildA.new.const11.should == :const11_1
+ end
+
+ it "searches a module included in the superclass" do
+ ConstantSpecs::ContainerA::ChildA.const12.should == :const12_1
+ ConstantSpecs::ContainerA::ChildA.new.const12.should == :const12_1
+ end
+
+ it "searches the superclass chain" do
+ ConstantSpecs::ContainerA::ChildA.const13.should == :const13
+ ConstantSpecs::ContainerA::ChildA.new.const13.should == :const13
+ end
+
+ it "searches the lexical scope of the method not the receiver's immediate class" do
+ ConstantSpecs::ContainerA::ChildA.const19.should == :const19_1
+ end
+
+ it "searches the lexical scope of a singleton method" do
+ ConstantSpecs::CS_CONST18.const17.should == :const17_1
+ end
+
+ it "does not search the lexical scope of the caller" do
+ -> { ConstantSpecs::ClassA.const16 }.should raise_error(NameError)
+ end
+
+ it "searches the lexical scope of a block" do
+ ConstantSpecs::ClassA.const22.should == :const22_1
+ end
+
+ it "searches Object as a lexical scope only if Object is explicitly opened" do
+ ConstantSpecs::ContainerA::ChildA.const20.should == :const20_1
+ ConstantSpecs::ContainerA::ChildA.const21.should == :const21_1
+ end
+
+ it "does not search the lexical scope of qualifying modules" do
+ -> do
+ ConstantSpecs::ContainerA::ChildA.const23
+ end.should raise_error(NameError)
+ end
+ end
+
+ describe "with dynamically assigned constants" do
+ it "searches the immediate class or module scope first" do
+ ConstantSpecs::ModuleA::CS_CONST201 = :const201_1
+
+ class ConstantSpecs::ClassB; CS_CONST201 = :const201_2; end
+ ConstantSpecs::ParentB::CS_CONST201 = :const201_3
+ ConstantSpecs::ContainerB::CS_CONST201 = :const201_4
+ ConstantSpecs::ContainerB::ChildB::CS_CONST201 = :const201_5
+
+ ConstantSpecs::ClassB.const201.should == :const201_2
+ ConstantSpecs::ParentB.const201.should == :const201_3
+ ConstantSpecs::ContainerB.const201.should == :const201_4
+ ConstantSpecs::ContainerB::ChildB.const201.should == :const201_5
+
+ ConstantSpecs::ClassB.new.const201.should == :const201_2
+ ConstantSpecs::ParentB.new.const201.should == :const201_3
+ ConstantSpecs::ContainerB::ChildB.new.const201.should == :const201_5
+ end
+
+ it "searches a module included in the immediate class before the superclass" do
+ ConstantSpecs::ParentB::CS_CONST202 = :const202_2
+ ConstantSpecs::ContainerB::ChildB::CS_CONST202 = :const202_1
+
+ ConstantSpecs::ContainerB::ChildB.const202.should == :const202_1
+ ConstantSpecs::ContainerB::ChildB.new.const202.should == :const202_1
+ end
+
+ it "searches the superclass before a module included in the superclass" do
+ ConstantSpecs::ParentB::CS_CONST203 = :const203_1
+ ConstantSpecs::ModuleE::CS_CONST203 = :const203_2
+
+ ConstantSpecs::ContainerB::ChildB.const203.should == :const203_1
+ ConstantSpecs::ContainerB::ChildB.new.const203.should == :const203_1
+ end
+
+ it "searches a module included in the superclass" do
+ ConstantSpecs::ModuleA::CS_CONST204 = :const204_2
+ ConstantSpecs::ModuleE::CS_CONST204 = :const204_1
+
+ ConstantSpecs::ContainerB::ChildB.const204.should == :const204_1
+ ConstantSpecs::ContainerB::ChildB.new.const204.should == :const204_1
+ end
+
+ it "searches the superclass chain" do
+ ConstantSpecs::ModuleA::CS_CONST205 = :const205
+
+ ConstantSpecs::ContainerB::ChildB.const205.should == :const205
+ ConstantSpecs::ContainerB::ChildB.new.const205.should == :const205
+ end
+
+ it "searches the lexical scope of the method not the receiver's immediate class" do
+ ConstantSpecs::ContainerB::ChildB::CS_CONST206 = :const206_2
+ class ConstantSpecs::ContainerB::ChildB
+ class << self
+ CS_CONST206 = :const206_1
+ end
+ end
+
+ ConstantSpecs::ContainerB::ChildB.const206.should == :const206_1
+ end
+
+ it "searches the lexical scope of a singleton method" do
+ ConstantSpecs::CS_CONST207 = :const207_1
+ ConstantSpecs::ClassB::CS_CONST207 = :const207_2
+
+ ConstantSpecs::CS_CONST208.const207.should == :const207_1
+ end
+
+ it "does not search the lexical scope of the caller" do
+ ConstantSpecs::ClassB::CS_CONST209 = :const209
+
+ -> { ConstantSpecs::ClassB.const209 }.should raise_error(NameError)
+ end
+
+ it "searches the lexical scope of a block" do
+ ConstantSpecs::ClassB::CS_CONST210 = :const210_1
+ ConstantSpecs::ParentB::CS_CONST210 = :const210_2
+
+ ConstantSpecs::ClassB.const210.should == :const210_1
+ end
+
+ it "searches Object as a lexical scope only if Object is explicitly opened" do
+ Object::CS_CONST211 = :const211_1
+ ConstantSpecs::ParentB::CS_CONST211 = :const211_2
+ ConstantSpecs::ContainerB::ChildB.const211.should == :const211_1
+
+ Object::CS_CONST212 = :const212_2
+ ConstantSpecs::ParentB::CS_CONST212 = :const212_1
+ ConstantSpecs::ContainerB::ChildB.const212.should == :const212_1
+ end
+
+ it "returns the updated value when a constant is reassigned" do
+ ConstantSpecs::ParentB::CS_CONST213 = :const213_1
+ ConstantSpecs::ContainerB::ChildB.const213.should == :const213_1
+ ConstantSpecs::ContainerB::ChildB.new.const213.should == :const213_1
+
+ -> {
+ ConstantSpecs::ParentB::CS_CONST213 = :const213_2
+ }.should complain(/already initialized constant/)
+ ConstantSpecs::ContainerB::ChildB.const213.should == :const213_2
+ ConstantSpecs::ContainerB::ChildB.new.const213.should == :const213_2
+ end
+
+ it "does not search the lexical scope of qualifying modules" do
+ ConstantSpecs::ContainerB::CS_CONST214 = :const214
+
+ -> do
+ ConstantSpecs::ContainerB::ChildB.const214
+ end.should raise_error(NameError)
+ end
+ end
+
+ it "raises a NameError if no constant is defined in the search path" do
+ -> { ConstantSpecs::ParentA.constx }.should raise_error(NameError)
+ end
+
+ it "sends #const_missing to the original class or module scope" do
+ ConstantSpecs::ClassA.constx.should == :CS_CONSTX
+ ConstantSpecs::ClassA.new.constx.should == :CS_CONSTX
+ end
+end
+
+describe "Constant resolution within a singleton class (class << obj)" do
+ it "works like normal classes or modules" do
+ ConstantSpecs::CS_SINGLETON1.foo.should == 1
+ end
+
+ it "uses its own namespace for each object" do
+ a = ConstantSpecs::CS_SINGLETON2[0].foo
+ b = ConstantSpecs::CS_SINGLETON2[1].foo
+ [a, b].should == [1, 2]
+ end
+
+ it "uses its own namespace for nested modules" do
+ a = ConstantSpecs::CS_SINGLETON3[0].x
+ b = ConstantSpecs::CS_SINGLETON3[1].x
+ a.should_not equal(b)
+ end
+
+ it "allows nested modules to have proper resolution" do
+ a = ConstantSpecs::CS_SINGLETON4_CLASSES[0].new
+ b = ConstantSpecs::CS_SINGLETON4_CLASSES[1].new
+ [a.foo, b.foo].should == [1, 2]
+ end
+end
+
+describe "top-level constant lookup" do
+ context "on a class" do
+ it "does not search Object after searching other scopes" do
+ -> { String::Hash }.should raise_error(NameError)
+ end
+ end
+
+ it "searches Object unsuccessfully when searches on a module" do
+ -> { Enumerable::Hash }.should raise_error(NameError)
+ end
+end
+
+describe "Module#private_constant marked constants" do
+
+ it "remain private even when updated" do
+ mod = Module.new
+ mod.const_set :Foo, true
+ mod.send :private_constant, :Foo
+ -> {
+ mod.const_set :Foo, false
+ }.should complain(/already initialized constant/)
+
+ -> {mod::Foo}.should raise_error(NameError)
+ end
+
+ it "sends #const_missing to the original class or module" do
+ mod = Module.new
+ mod.const_set :Foo, true
+ mod.send :private_constant, :Foo
+ def mod.const_missing(name)
+ name == :Foo ? name : super
+ end
+
+ mod::Foo.should == :Foo
+ end
+
+ describe "in a module" do
+ it "cannot be accessed from outside the module" do
+ -> do
+ ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE
+ end.should raise_error(NameError)
+ end
+
+ it "cannot be reopened as a module from scope where constant would be private" do
+ -> do
+ module ConstantVisibility::ModuleContainer::PrivateModule; end
+ end.should raise_error(NameError)
+ end
+
+ it "cannot be reopened as a class from scope where constant would be private" do
+ -> do
+ class ConstantVisibility::ModuleContainer::PrivateClass; end
+ end.should raise_error(NameError)
+ end
+
+ it "can be reopened as a module where constant is not private" do
+ module ::ConstantVisibility::ModuleContainer
+ module PrivateModule
+ X = 1
+ end
+
+ PrivateModule::X.should == 1
+ end
+ end
+
+ it "can be reopened as a class where constant is not private" do
+ module ::ConstantVisibility::ModuleContainer
+ class PrivateClass
+ X = 1
+ end
+
+ PrivateClass::X.should == 1
+ end
+ end
+
+ it "is not defined? with A::B form" do
+ defined?(ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE).should == nil
+ end
+
+ it "can be accessed from the module itself" do
+ ConstantVisibility::PrivConstModule.private_constant_from_self.should be_true
+ end
+
+ it "is defined? from the module itself" do
+ ConstantVisibility::PrivConstModule.defined_from_self.should == "constant"
+ end
+
+ it "can be accessed from lexical scope" do
+ ConstantVisibility::PrivConstModule::Nested.private_constant_from_scope.should be_true
+ end
+
+ it "is defined? from lexical scope" do
+ ConstantVisibility::PrivConstModule::Nested.defined_from_scope.should == "constant"
+ end
+
+ it "can be accessed from classes that include the module" do
+ ConstantVisibility::ClassIncludingPrivConstModule.new.private_constant_from_include.should be_true
+ end
+
+ it "can be accessed from modules that include the module" do
+ ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_from_include.should be_true
+ end
+
+ it "raises a NameError when accessed directly from modules that include the module" do
+ -> do
+ ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_self_from_include
+ end.should raise_error(NameError)
+ -> do
+ ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_named_from_include
+ end.should raise_error(NameError)
+ end
+
+ it "is defined? from classes that include the module" do
+ ConstantVisibility::ClassIncludingPrivConstModule.new.defined_from_include.should == "constant"
+ end
+ end
+
+ describe "in a class" do
+ it "cannot be accessed from outside the class" do
+ -> do
+ ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS
+ end.should raise_error(NameError)
+ end
+
+ it "cannot be reopened as a module" do
+ -> do
+ module ConstantVisibility::ClassContainer::PrivateModule; end
+ end.should raise_error(NameError)
+ end
+
+ it "cannot be reopened as a class" do
+ -> do
+ class ConstantVisibility::ClassContainer::PrivateClass; end
+ end.should raise_error(NameError)
+ end
+
+ it "can be reopened as a module where constant is not private" do
+ class ::ConstantVisibility::ClassContainer
+ module PrivateModule
+ X = 1
+ end
+
+ PrivateModule::X.should == 1
+ end
+ end
+
+ it "can be reopened as a class where constant is not private" do
+ class ::ConstantVisibility::ClassContainer
+ class PrivateClass
+ X = 1
+ end
+
+ PrivateClass::X.should == 1
+ end
+ end
+
+ it "is not defined? with A::B form" do
+ defined?(ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS).should == nil
+ end
+
+ it "can be accessed from the class itself" do
+ ConstantVisibility::PrivConstClass.private_constant_from_self.should be_true
+ end
+
+ it "is defined? from the class itself" do
+ ConstantVisibility::PrivConstClass.defined_from_self.should == "constant"
+ end
+
+ it "can be accessed from lexical scope" do
+ ConstantVisibility::PrivConstClass::Nested.private_constant_from_scope.should be_true
+ end
+
+ it "is defined? from lexical scope" do
+ ConstantVisibility::PrivConstClass::Nested.defined_from_scope.should == "constant"
+ end
+
+ it "can be accessed from subclasses" do
+ ConstantVisibility::PrivConstClassChild.new.private_constant_from_subclass.should be_true
+ end
+
+ it "is defined? from subclasses" do
+ ConstantVisibility::PrivConstClassChild.new.defined_from_subclass.should == "constant"
+ end
+ end
+
+ describe "in Object" do
+ it "cannot be accessed using ::Const form" do
+ -> do
+ ::PRIVATE_CONSTANT_IN_OBJECT
+ end.should raise_error(NameError)
+ end
+
+ it "is not defined? using ::Const form" do
+ defined?(::PRIVATE_CONSTANT_IN_OBJECT).should == nil
+ end
+
+ it "can be accessed through the normal search" do
+ PRIVATE_CONSTANT_IN_OBJECT.should == true
+ end
+
+ it "is defined? through the normal search" do
+ defined?(PRIVATE_CONSTANT_IN_OBJECT).should == "constant"
+ end
+ end
+
+ describe "NameError by #private_constant" do
+ it "has :receiver and :name attributes" do
+ -> do
+ ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS
+ end.should raise_error(NameError) {|e|
+ e.receiver.should == ConstantVisibility::PrivConstClass
+ e.name.should == :PRIVATE_CONSTANT_CLASS
+ }
+
+ -> do
+ ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE
+ end.should raise_error(NameError) {|e|
+ e.receiver.should == ConstantVisibility::PrivConstModule
+ e.name.should == :PRIVATE_CONSTANT_MODULE
+ }
+ end
+
+ it "has the defined class as the :name attribute" do
+ -> do
+ ConstantVisibility::PrivConstClassChild::PRIVATE_CONSTANT_CLASS
+ end.should raise_error(NameError) {|e|
+ e.receiver.should == ConstantVisibility::PrivConstClass
+ e.name.should == :PRIVATE_CONSTANT_CLASS
+ }
+
+ -> do
+ ConstantVisibility::ClassIncludingPrivConstModule::PRIVATE_CONSTANT_MODULE
+ end.should raise_error(NameError) {|e|
+ e.receiver.should == ConstantVisibility::PrivConstModule
+ e.name.should == :PRIVATE_CONSTANT_MODULE
+ }
+ end
+ end
+end
+
+describe "Module#public_constant marked constants" do
+ before :each do
+ @module = ConstantVisibility::PrivConstModule.dup
+ end
+
+ describe "in a module" do
+ it "can be accessed from outside the module" do
+ @module.send :public_constant, :PRIVATE_CONSTANT_MODULE
+ @module::PRIVATE_CONSTANT_MODULE.should == true
+ end
+
+ it "is defined? with A::B form" do
+ @module.send :public_constant, :PRIVATE_CONSTANT_MODULE
+ defined?(@module::PRIVATE_CONSTANT_MODULE).should == "constant"
+ end
+ end
+
+ describe "in a class" do
+ before :each do
+ @class = ConstantVisibility::PrivConstClass.dup
+ end
+
+ it "can be accessed from outside the class" do
+ @class.send :public_constant, :PRIVATE_CONSTANT_CLASS
+ @class::PRIVATE_CONSTANT_CLASS.should == true
+ end
+
+ it "is defined? with A::B form" do
+ @class.send :public_constant, :PRIVATE_CONSTANT_CLASS
+ defined?(@class::PRIVATE_CONSTANT_CLASS).should == "constant"
+ end
+ end
+
+ describe "in Object" do
+ after :each do
+ ConstantVisibility.reset_private_constants
+ end
+
+ it "can be accessed using ::Const form" do
+ Object.send :public_constant, :PRIVATE_CONSTANT_IN_OBJECT
+ ::PRIVATE_CONSTANT_IN_OBJECT.should == true
+ end
+
+ it "is defined? using ::Const form" do
+ Object.send :public_constant, :PRIVATE_CONSTANT_IN_OBJECT
+ defined?(::PRIVATE_CONSTANT_IN_OBJECT).should == "constant"
+ end
+ end
+end
+
+describe 'Allowed characters' do
+ it 'allows not ASCII characters in the middle of a name' do
+ mod = Module.new
+ mod.const_set("BBá¼BB", 1)
+
+ eval("mod::BBá¼BB").should == 1
+ end
+
+ it 'does not allow not ASCII characters that cannot be upcased or lowercased at the beginning' do
+ -> do
+ Module.new.const_set("થBB", 1)
+ end.should raise_error(NameError, /wrong constant name/)
+ end
+
+ it 'allows not ASCII upcased characters at the beginning' do
+ mod = Module.new
+ mod.const_set("á¼BB", 1)
+
+ eval("mod::á¼BB").should == 1
+ end
+end
+
+describe 'Assignment' do
+ context 'dynamic assignment' do
+ it 'raises SyntaxError' do
+ -> do
+ eval <<-CODE
+ def test
+ B = 1
+ end
+ CODE
+ end.should raise_error(SyntaxError, /dynamic constant assignment/)
+ end
+ end
+end
diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb
new file mode 100644
index 0000000000..c8531343c0
--- /dev/null
+++ b/spec/ruby/language/def_spec.rb
@@ -0,0 +1,798 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/def'
+
+# Language-level method behaviour
+describe "Redefining a method" do
+ it "replaces the original method" do
+ def barfoo; 100; end
+ barfoo.should == 100
+
+ def barfoo; 200; end
+ barfoo.should == 200
+ end
+end
+
+describe "Defining a method at the top-level" do
+ it "defines it on Object with private visibility by default" do
+ Object.should have_private_instance_method(:some_toplevel_method, false)
+ end
+
+ it "defines it on Object with public visibility after calling public" do
+ Object.should have_public_instance_method(:public_toplevel_method, false)
+ end
+end
+
+describe "Defining an 'initialize' method" do
+ it "sets the method's visibility to private" do
+ class DefInitializeSpec
+ def initialize
+ end
+ end
+ DefInitializeSpec.should have_private_instance_method(:initialize, false)
+ end
+end
+
+describe "Defining an 'initialize_copy' method" do
+ it "sets the method's visibility to private" do
+ class DefInitializeCopySpec
+ def initialize_copy
+ end
+ end
+ DefInitializeCopySpec.should have_private_instance_method(:initialize_copy, false)
+ end
+end
+
+describe "Defining an 'initialize_dup' method" do
+ it "sets the method's visibility to private" do
+ class DefInitializeDupSpec
+ def initialize_dup
+ end
+ end
+ DefInitializeDupSpec.should have_private_instance_method(:initialize_dup, false)
+ end
+end
+
+describe "Defining an 'initialize_clone' method" do
+ it "sets the method's visibility to private" do
+ class DefInitializeCloneSpec
+ def initialize_clone
+ end
+ end
+ DefInitializeCloneSpec.should have_private_instance_method(:initialize_clone, false)
+ end
+end
+
+describe "Defining a 'respond_to_missing?' method" do
+ it "sets the method's visibility to private" do
+ class DefRespondToMissingPSpec
+ def respond_to_missing?
+ end
+ end
+ DefRespondToMissingPSpec.should have_private_instance_method(:respond_to_missing?, false)
+ end
+end
+
+describe "Defining a method" do
+ it "returns a symbol of the method name" do
+ method_name = def some_method; end
+ method_name.should == :some_method
+ end
+end
+
+describe "An instance method" do
+ it "raises an error with too few arguments" do
+ def foo(a, b); end
+ -> { foo 1 }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 2)')
+ end
+
+ it "raises an error with too many arguments" do
+ def foo(a); end
+ -> { foo 1, 2 }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1)')
+ end
+
+ it "raises FrozenError with the correct class name" do
+ -> {
+ Module.new do
+ self.freeze
+ def foo; end
+ end
+ }.should raise_error(FrozenError) { |e|
+ e.message.should.start_with? "can't modify frozen module"
+ }
+
+ -> {
+ Class.new do
+ self.freeze
+ def foo; end
+ end
+ }.should raise_error(FrozenError){ |e|
+ e.message.should.start_with? "can't modify frozen class"
+ }
+ end
+end
+
+describe "An instance method definition with a splat" do
+ it "accepts an unnamed '*' argument" do
+ def foo(*); end;
+
+ foo.should == nil
+ foo(1, 2).should == nil
+ foo(1, 2, 3, 4, :a, :b, 'c', 'd').should == nil
+ end
+
+ it "accepts a named * argument" do
+ def foo(*a); a; end;
+ foo.should == []
+ foo(1, 2).should == [1, 2]
+ foo([:a]).should == [[:a]]
+ end
+
+ it "accepts non-* arguments before the * argument" do
+ def foo(a, b, c, d, e, *f); [a, b, c, d, e, f]; end
+ foo(1, 2, 3, 4, 5, 6, 7, 8).should == [1, 2, 3, 4, 5, [6, 7, 8]]
+ end
+
+ it "allows only a single * argument" do
+ -> { eval 'def foo(a, *b, *c); end' }.should raise_error(SyntaxError)
+ end
+
+ it "requires the presence of any arguments that precede the *" do
+ def foo(a, b, *c); end
+ -> { foo 1 }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 2+)')
+ end
+end
+
+describe "An instance method with a default argument" do
+ it "evaluates the default when no arguments are passed" do
+ def foo(a = 1)
+ a
+ end
+ foo.should == 1
+ foo(2).should == 2
+ end
+
+ it "evaluates the default empty expression when no arguments are passed" do
+ def foo(a = ())
+ a
+ end
+ foo.should == nil
+ foo(2).should == 2
+ end
+
+ it "assigns an empty Array to an unused splat argument" do
+ def foo(a = 1, *b)
+ [a,b]
+ end
+ foo.should == [1, []]
+ foo(2).should == [2, []]
+ end
+
+ it "evaluates the default when required arguments precede it" do
+ def foo(a, b = 2)
+ [a,b]
+ end
+ -> { foo }.should raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1..2)')
+ foo(1).should == [1, 2]
+ end
+
+ it "prefers to assign to a default argument before a splat argument" do
+ def foo(a, b = 2, *c)
+ [a,b,c]
+ end
+ -> { foo }.should raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1+)')
+ foo(1).should == [1,2,[]]
+ end
+
+ it "prefers to assign to a default argument when there are no required arguments" do
+ def foo(a = 1, *args)
+ [a,args]
+ end
+ foo(2,2).should == [2,[2]]
+ end
+
+ it "does not evaluate the default when passed a value and a * argument" do
+ def foo(a, b = 2, *args)
+ [a,b,args]
+ end
+ foo(2,3,3).should == [2,3,[3]]
+ end
+
+ it "raises a SyntaxError when there is an existing method with the same name as the local variable" do
+ def bar
+ 1
+ end
+ -> {
+ eval "def foo(bar = bar)
+ bar
+ end"
+ }.should raise_error(SyntaxError)
+ end
+
+ it "calls a method with the same name as the local when explicitly using ()" do
+ def bar
+ 1
+ end
+ def foo(bar = bar())
+ bar
+ end
+ foo.should == 1
+ foo(2).should == 2
+ end
+end
+
+describe "A singleton method definition" do
+ it "can be declared for a local variable" do
+ a = Object.new
+ def a.foo
+ 5
+ end
+ a.foo.should == 5
+ end
+
+ it "can be declared for an instance variable" do
+ @a = Object.new
+ def @a.foo
+ 6
+ end
+ @a.foo.should == 6
+ end
+
+ it "can be declared for a global variable" do
+ $__a__ = "hi"
+ def $__a__.foo
+ 7
+ end
+ $__a__.foo.should == 7
+ end
+
+ it "can be declared with an empty method body" do
+ class DefSpec
+ def self.foo;end
+ end
+ DefSpec.foo.should == nil
+ end
+
+ it "can be redefined" do
+ obj = Object.new
+ def obj.==(other)
+ 1
+ end
+ (obj==1).should == 1
+ def obj.==(other)
+ 2
+ end
+ (obj==2).should == 2
+ end
+
+ it "raises FrozenError if frozen" do
+ obj = Object.new
+ obj.freeze
+ -> { def obj.foo; end }.should raise_error(FrozenError)
+ end
+
+ it "raises FrozenError with the correct class name" do
+ obj = Object.new
+ obj.freeze
+ -> { def obj.foo; end }.should raise_error(FrozenError){ |e|
+ e.message.should.start_with? "can't modify frozen object"
+ }
+
+ c = obj.singleton_class
+ -> { def c.foo; end }.should raise_error(FrozenError){ |e|
+ e.message.should.start_with? "can't modify frozen Class"
+ }
+
+ m = Module.new
+ m.freeze
+ -> { def m.foo; end }.should raise_error(FrozenError){ |e|
+ e.message.should.start_with? "can't modify frozen Module"
+ }
+ end
+end
+
+describe "Redefining a singleton method" do
+ it "does not inherit a previously set visibility" do
+ o = Object.new
+
+ class << o; private; def foo; end; end;
+
+ class << o; should have_private_instance_method(:foo); end
+
+ class << o; def foo; end; end;
+
+ class << o; should_not have_private_instance_method(:foo); end
+ class << o; should have_instance_method(:foo); end
+
+ end
+end
+
+describe "Redefining a singleton method" do
+ it "does not inherit a previously set visibility" do
+ o = Object.new
+
+ class << o; private; def foo; end; end;
+
+ class << o; should have_private_instance_method(:foo); end
+
+ class << o; def foo; end; end;
+
+ class << o; should_not have_private_instance_method(:foo); end
+ class << o; should have_instance_method(:foo); end
+
+ end
+end
+
+describe "A method defined with extreme default arguments" do
+ it "can redefine itself when the default is evaluated" do
+ class DefSpecs
+ def foo(x = (def foo; "hello"; end;1));x;end
+ end
+
+ d = DefSpecs.new
+ d.foo(42).should == 42
+ d.foo.should == 1
+ d.foo.should == 'hello'
+ end
+
+ it "may use an fcall as a default" do
+ def bar
+ 1
+ end
+ def foo(x = bar())
+ x
+ end
+ foo.should == 1
+ foo(2).should == 2
+ end
+
+ it "evaluates the defaults in the method's scope" do
+ def foo(x = ($foo_self = self; nil)); end
+ foo
+ $foo_self.should == self
+ end
+
+ it "may use preceding arguments as defaults" do
+ def foo(obj, width=obj.length)
+ width
+ end
+ foo('abcde').should == 5
+ end
+
+ it "may use a lambda as a default" do
+ def foo(output = 'a', prc = -> n { output * n })
+ prc.call(5)
+ end
+ foo.should == 'aaaaa'
+ end
+end
+
+describe "A singleton method defined with extreme default arguments" do
+ it "may use a method definition as a default" do
+ $__a = Object.new
+ def $__a.foo(x = (def $__a.foo; "hello"; end;1));x;end
+
+ $__a.foo(42).should == 42
+ $__a.foo.should == 1
+ $__a.foo.should == 'hello'
+ end
+
+ it "may use an fcall as a default" do
+ a = Object.new
+ def a.bar
+ 1
+ end
+ def a.foo(x = bar())
+ x
+ end
+ a.foo.should == 1
+ a.foo(2).should == 2
+ end
+
+ it "evaluates the defaults in the singleton scope" do
+ a = Object.new
+ def a.foo(x = ($foo_self = self; nil)); 5 ;end
+ a.foo
+ $foo_self.should == a
+ end
+
+ it "may use preceding arguments as defaults" do
+ a = Object.new
+ def a.foo(obj, width=obj.length)
+ width
+ end
+ a.foo('abcde').should == 5
+ end
+
+ it "may use a lambda as a default" do
+ a = Object.new
+ def a.foo(output = 'a', prc = -> n { output * n })
+ prc.call(5)
+ end
+ a.foo.should == 'aaaaa'
+ end
+end
+
+describe "A method definition inside a metaclass scope" do
+ it "can create a class method" do
+ class DefSpecSingleton
+ class << self
+ def a_class_method;self;end
+ end
+ end
+
+ DefSpecSingleton.a_class_method.should == DefSpecSingleton
+ -> { Object.a_class_method }.should raise_error(NoMethodError)
+ end
+
+ it "can create a singleton method" do
+ obj = Object.new
+ class << obj
+ def a_singleton_method;self;end
+ end
+
+ obj.a_singleton_method.should == obj
+ -> { Object.new.a_singleton_method }.should raise_error(NoMethodError)
+ end
+
+ it "raises FrozenError if frozen" do
+ obj = Object.new
+ obj.freeze
+
+ class << obj
+ -> { def foo; end }.should raise_error(FrozenError)
+ end
+ end
+end
+
+describe "A nested method definition" do
+ it "creates an instance method when evaluated in an instance method" do
+ class DefSpecNested
+ def create_instance_method
+ def an_instance_method;self;end
+ an_instance_method
+ end
+ end
+
+ obj = DefSpecNested.new
+ obj.create_instance_method.should == obj
+ obj.an_instance_method.should == obj
+
+ other = DefSpecNested.new
+ other.an_instance_method.should == other
+
+ DefSpecNested.should have_instance_method(:an_instance_method)
+ end
+
+ it "creates a class method when evaluated in a class method" do
+ class DefSpecNested
+ class << self
+ # cleanup
+ remove_method :a_class_method if method_defined? :a_class_method
+ def create_class_method
+ def a_class_method;self;end
+ a_class_method
+ end
+ end
+ end
+
+ -> { DefSpecNested.a_class_method }.should raise_error(NoMethodError)
+ DefSpecNested.create_class_method.should == DefSpecNested
+ DefSpecNested.a_class_method.should == DefSpecNested
+ -> { Object.a_class_method }.should raise_error(NoMethodError)
+ -> { DefSpecNested.new.a_class_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a singleton method when evaluated in the metaclass of an instance" do
+ class DefSpecNested
+ def create_singleton_method
+ class << self
+ def a_singleton_method;self;end
+ end
+ a_singleton_method
+ end
+ end
+
+ obj = DefSpecNested.new
+ obj.create_singleton_method.should == obj
+ obj.a_singleton_method.should == obj
+
+ other = DefSpecNested.new
+ -> { other.a_singleton_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a method in the surrounding context when evaluated in a def expr.method" do
+ class DefSpecNested
+ TARGET = Object.new
+ def TARGET.defs_method
+ def inherited_method;self;end
+ end
+ end
+
+ DefSpecNested::TARGET.defs_method
+ DefSpecNested.should have_instance_method :inherited_method
+ DefSpecNested::TARGET.should_not have_method :inherited_method
+
+ obj = DefSpecNested.new
+ obj.inherited_method.should == obj
+ end
+
+ # See http://yugui.jp/articles/846#label-3
+ it "inside an instance_eval creates a singleton method" do
+ class DefSpecNested
+ OBJ = Object.new
+ OBJ.instance_eval do
+ def create_method_in_instance_eval(a = (def arg_method; end))
+ def body_method; end
+ end
+ end
+ end
+
+ obj = DefSpecNested::OBJ
+ obj.create_method_in_instance_eval
+
+ obj.should have_method :arg_method
+ obj.should have_method :body_method
+
+ DefSpecNested.should_not have_instance_method :arg_method
+ DefSpecNested.should_not have_instance_method :body_method
+ end
+
+ it "creates an instance method inside Class.new" do
+ cls = Class.new do
+ def do_def
+ def new_def
+ 1
+ end
+ end
+ end
+
+ obj = cls.new
+ obj.do_def
+ obj.new_def.should == 1
+
+ cls.new.new_def.should == 1
+
+ -> { Object.new.new_def }.should raise_error(NoMethodError)
+ end
+end
+
+describe "A method definition always resets the visibility to public for nested definitions" do
+ it "in Class.new" do
+ cls = Class.new do
+ private
+ def do_def
+ def new_def
+ 1
+ end
+ end
+ end
+
+ obj = cls.new
+ -> { obj.do_def }.should raise_error(NoMethodError, /private/)
+ obj.send :do_def
+ obj.new_def.should == 1
+
+ cls.new.new_def.should == 1
+
+ -> { Object.new.new_def }.should raise_error(NoMethodError)
+ end
+
+ it "at the toplevel" do
+ obj = Object.new
+ -> { obj.toplevel_define_other_method }.should raise_error(NoMethodError, /private/)
+ toplevel_define_other_method
+ nested_method_in_toplevel_method.should == 42
+
+ Object.new.nested_method_in_toplevel_method.should == 42
+ end
+end
+
+describe "A method definition inside an instance_eval" do
+ it "creates a singleton method" do
+ obj = Object.new
+ obj.instance_eval do
+ def an_instance_eval_method;self;end
+ end
+ obj.an_instance_eval_method.should == obj
+
+ other = Object.new
+ -> { other.an_instance_eval_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a singleton method when evaluated inside a metaclass" do
+ obj = Object.new
+ obj.instance_eval do
+ class << self
+ def a_metaclass_eval_method;self;end
+ end
+ end
+ obj.a_metaclass_eval_method.should == obj
+
+ other = Object.new
+ -> { other.a_metaclass_eval_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method when the receiver is a class" do
+ DefSpecNested.instance_eval do
+ def an_instance_eval_class_method;self;end
+ end
+
+ DefSpecNested.an_instance_eval_class_method.should == DefSpecNested
+ -> { Object.an_instance_eval_class_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method when the receiver is an anonymous class" do
+ m = Class.new
+ m.instance_eval do
+ def klass_method
+ :test
+ end
+ end
+
+ m.klass_method.should == :test
+ -> { Object.klass_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method when instance_eval is within class" do
+ m = Class.new do
+ instance_eval do
+ def klass_method
+ :test
+ end
+ end
+ end
+
+ m.klass_method.should == :test
+ -> { Object.klass_method }.should raise_error(NoMethodError)
+ end
+end
+
+describe "A method definition inside an instance_exec" do
+ it "creates a class method when the receiver is a class" do
+ DefSpecNested.instance_exec(1) do |param|
+ @stuff = param
+
+ def an_instance_exec_class_method; @stuff; end
+ end
+
+ DefSpecNested.an_instance_exec_class_method.should == 1
+ -> { Object.an_instance_exec_class_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method when the receiver is an anonymous class" do
+ m = Class.new
+ m.instance_exec(1) do |param|
+ @stuff = param
+
+ def klass_method
+ @stuff
+ end
+ end
+
+ m.klass_method.should == 1
+ -> { Object.klass_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method when instance_exec is within class" do
+ m = Class.new do
+ instance_exec(2) do |param|
+ @stuff = param
+
+ def klass_method
+ @stuff
+ end
+ end
+ end
+
+ m.klass_method.should == 2
+ -> { Object.klass_method }.should raise_error(NoMethodError)
+ end
+end
+
+describe "A method definition in an eval" do
+ it "creates an instance method" do
+ class DefSpecNested
+ def eval_instance_method
+ eval "def an_eval_instance_method;self;end", binding
+ an_eval_instance_method
+ end
+ end
+
+ obj = DefSpecNested.new
+ obj.eval_instance_method.should == obj
+ obj.an_eval_instance_method.should == obj
+
+ other = DefSpecNested.new
+ other.an_eval_instance_method.should == other
+
+ -> { Object.new.an_eval_instance_method }.should raise_error(NoMethodError)
+ end
+
+ it "creates a class method" do
+ class DefSpecNestedB
+ class << self
+ def eval_class_method
+ eval "def an_eval_class_method;self;end" #, binding
+ an_eval_class_method
+ end
+ end
+ end
+
+ DefSpecNestedB.eval_class_method.should == DefSpecNestedB
+ DefSpecNestedB.an_eval_class_method.should == DefSpecNestedB
+
+ -> { Object.an_eval_class_method }.should raise_error(NoMethodError)
+ -> { DefSpecNestedB.new.an_eval_class_method}.should raise_error(NoMethodError)
+ end
+
+ it "creates a singleton method" do
+ class DefSpecNested
+ def eval_singleton_method
+ class << self
+ eval "def an_eval_singleton_method;self;end", binding
+ end
+ an_eval_singleton_method
+ end
+ end
+
+ obj = DefSpecNested.new
+ obj.eval_singleton_method.should == obj
+ obj.an_eval_singleton_method.should == obj
+
+ other = DefSpecNested.new
+ -> { other.an_eval_singleton_method }.should raise_error(NoMethodError)
+ end
+end
+
+describe "a method definition that sets more than one default parameter all to the same value" do
+ def foo(a=b=c={})
+ [a,b,c]
+ end
+ it "assigns them all the same object by default" do
+ foo.should == [{},{},{}]
+ a, b, c = foo
+ a.should eql(b)
+ a.should eql(c)
+ end
+
+ it "allows the first argument to be given, and sets the rest to null" do
+ foo(1).should == [1,nil,nil]
+ end
+
+ it "assigns the parameters different objects across different default calls" do
+ a, _b, _c = foo
+ d, _e, _f = foo
+ a.should_not equal(d)
+ end
+
+ it "only allows overriding the default value of the first such parameter in each set" do
+ -> { foo(1,2) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 0..1)')
+ end
+
+ def bar(a=b=c=1,d=2)
+ [a,b,c,d]
+ end
+
+ it "treats the argument after the multi-parameter normally" do
+ bar.should == [1,1,1,2]
+ bar(3).should == [3,nil,nil,2]
+ bar(3,4).should == [3,nil,nil,4]
+ -> { bar(3,4,5) }.should raise_error(ArgumentError, 'wrong number of arguments (given 3, expected 0..2)')
+ end
+end
+
+describe "The def keyword" do
+ describe "within a closure" do
+ it "looks outside the closure for the visibility" do
+ module DefSpecsLambdaVisibility
+ private
+
+ -> {
+ def some_method; end
+ }.call
+ end
+
+ DefSpecsLambdaVisibility.should have_private_instance_method("some_method")
+ end
+ end
+end
diff --git a/spec/ruby/language/defined_spec.rb b/spec/ruby/language/defined_spec.rb
new file mode 100644
index 0000000000..ae2bf45bda
--- /dev/null
+++ b/spec/ruby/language/defined_spec.rb
@@ -0,0 +1,1171 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/defined'
+
+describe "The defined? keyword for literals" do
+ it "returns 'self' for self" do
+ ret = defined?(self)
+ ret.should == "self"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'nil' for nil" do
+ ret = defined?(nil)
+ ret.should == "nil"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'true' for true" do
+ ret = defined?(true)
+ ret.should == "true"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'false' for false" do
+ ret = defined?(false)
+ ret.should == "false"
+ ret.frozen?.should == true
+ end
+
+ describe "for a literal Array" do
+
+ it "returns 'expression' if each element is defined" do
+ ret = defined?([Object, Array])
+ ret.should == "expression"
+ ret.frozen?.should == true
+ end
+
+ it "returns nil if one element is not defined" do
+ ret = defined?([NonExistentConstant, Array])
+ ret.should == nil
+ end
+
+ it "returns nil if all elements are not defined" do
+ ret = defined?([NonExistentConstant, AnotherNonExistentConstant])
+ ret.should == nil
+ end
+
+ end
+end
+
+describe "The defined? keyword when called with a method name" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "does not call the method" do
+ defined?(DefinedSpecs.side_effects).should == "method"
+ ScratchPad.recorded.should != :defined_specs_side_effects
+ end
+
+ it "does not execute the arguments" do
+ defined?(DefinedSpecs.any_args(DefinedSpecs.side_effects)).should == "method"
+ ScratchPad.recorded.should != :defined_specs_side_effects
+ end
+
+ describe "without a receiver" do
+ it "returns 'method' if the method is defined" do
+ ret = defined?(puts)
+ ret.should == "method"
+ ret.frozen?.should == true
+ end
+
+ it "returns nil if the method is not defined" do
+ defined?(defined_specs_undefined_method).should be_nil
+ end
+
+ it "returns 'method' if the method is defined and private" do
+ obj = DefinedSpecs::Basic.new
+ obj.private_method_defined.should == "method"
+ end
+
+ it "returns 'method' if the predicate method is defined and private" do
+ obj = DefinedSpecs::Basic.new
+ obj.private_predicate_defined.should == "method"
+ end
+ end
+
+ describe "having a module as receiver" do
+ it "returns 'method' if the method is defined" do
+ defined?(Kernel.puts).should == "method"
+ end
+
+ it "returns nil if the method is private" do
+ defined?(Object.print).should be_nil
+ end
+
+ it "returns nil if the method is protected" do
+ defined?(DefinedSpecs::Basic.new.protected_method).should be_nil
+ end
+
+ it "returns nil if the method is not defined" do
+ defined?(Kernel.defined_specs_undefined_method).should be_nil
+ end
+
+ it "returns nil if the class is not defined" do
+ defined?(DefinedSpecsUndefined.puts).should be_nil
+ end
+
+ it "returns nil if the subclass is not defined" do
+ defined?(DefinedSpecs::Undefined.puts).should be_nil
+ end
+ end
+
+ describe "having a local variable as receiver" do
+ it "returns 'method' if the method is defined" do
+ obj = DefinedSpecs::Basic.new
+ defined?(obj.a_defined_method).should == "method"
+ end
+
+ it "returns nil if the method is not defined" do
+ obj = DefinedSpecs::Basic.new
+ defined?(obj.an_undefined_method).should be_nil
+ end
+
+ it "returns nil if the variable does not exist" do
+ defined?(nonexistent_local_variable.some_method).should be_nil
+ end
+
+ it "calls #respond_to_missing?" do
+ obj = mock("respond_to_missing object")
+ obj.should_receive(:respond_to_missing?).and_return(true)
+ defined?(obj.something_undefined).should == "method"
+ end
+ end
+
+ describe "having an instance variable as receiver" do
+ it "returns 'method' if the method is defined" do
+ @defined_specs_obj = DefinedSpecs::Basic.new
+ defined?(@defined_specs_obj.a_defined_method).should == "method"
+ end
+
+ it "returns nil if the method is not defined" do
+ @defined_specs_obj = DefinedSpecs::Basic.new
+ defined?(@defined_specs_obj.an_undefined_method).should be_nil
+ end
+
+ it "returns nil if the variable does not exist" do
+ defined?(@nonexistent_instance_variable.some_method).should be_nil
+ end
+ end
+
+ describe "having a global variable as receiver" do
+ it "returns 'method' if the method is defined" do
+ $defined_specs_obj = DefinedSpecs::Basic.new
+ defined?($defined_specs_obj.a_defined_method).should == "method"
+ end
+
+ it "returns nil if the method is not defined" do
+ $defined_specs_obj = DefinedSpecs::Basic.new
+ defined?($defined_specs_obj.an_undefined_method).should be_nil
+ end
+
+ it "returns nil if the variable does not exist" do
+ defined?($nonexistent_global_variable.some_method).should be_nil
+ end
+ end
+
+ describe "having a method call as a receiver" do
+ it "returns nil if evaluating the receiver raises an exception" do
+ defined?(DefinedSpecs.exception_method / 2).should be_nil
+ ScratchPad.recorded.should == :defined_specs_exception
+ end
+
+ it "returns nil if the method is not defined on the object the receiver returns" do
+ defined?(DefinedSpecs.side_effects / 2).should be_nil
+ ScratchPad.recorded.should == :defined_specs_side_effects
+ end
+
+ it "returns 'method' if the method is defined on the object the receiver returns" do
+ defined?(DefinedSpecs.fixnum_method / 2).should == "method"
+ ScratchPad.recorded.should == :defined_specs_fixnum_method
+ end
+ end
+end
+
+describe "The defined? keyword for an expression" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "returns 'assignment' for assigning a local variable" do
+ ret = defined?(x = 2)
+ ret.should == "assignment"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'assignment' for assigning an instance variable" do
+ defined?(@defined_specs_x = 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for assigning a global variable" do
+ defined?($defined_specs_x = 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for assigning a class variable" do
+ defined?(@@defined_specs_x = 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for assigning multiple variables" do
+ defined?((a, b = 1, 2)).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '%='" do
+ defined?(x %= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '/='" do
+ defined?(x /= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '-='" do
+ defined?(x -= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '+='" do
+ defined?(x += 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '*='" do
+ defined?(x *= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '|='" do
+ defined?(x |= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '&='" do
+ defined?(x &= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '^='" do
+ defined?(x ^= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '~='" do
+ defined?(x = 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '<<='" do
+ defined?(x <<= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '>>='" do
+ defined?(x >>= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '||='" do
+ defined?(x ||= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '&&='" do
+ defined?(x &&= 2).should == "assignment"
+ end
+
+ it "returns 'assignment' for an expression with '**='" do
+ defined?(x **= 2).should == "assignment"
+ end
+
+ it "returns nil for an expression with == and an undefined method" do
+ defined?(defined_specs_undefined_method == 2).should be_nil
+ end
+
+ it "returns nil for an expression with != and an undefined method" do
+ defined?(defined_specs_undefined_method != 2).should be_nil
+ end
+
+ it "returns nil for an expression with !~ and an undefined method" do
+ defined?(defined_specs_undefined_method !~ 2).should be_nil
+ end
+
+ it "returns 'method' for an expression with '=='" do
+ x = 42
+ defined?(x == 2).should == "method"
+ end
+
+ it "returns 'method' for an expression with '!='" do
+ x = 42
+ defined?(x != 2).should == "method"
+ end
+
+ it "returns 'method' for an expression with '!~'" do
+ x = 42
+ defined?(x !~ 2).should == "method"
+ end
+
+ describe "with logical connectives" do
+ it "returns nil for an expression with '!' and an undefined method" do
+ defined?(!defined_specs_undefined_method).should be_nil
+ end
+
+ it "returns nil for an expression with '!' and an unset class variable" do
+ @result = eval("class singleton_class::A; defined?(!@@doesnt_exist) end", binding, __FILE__, __LINE__)
+ @result.should be_nil
+ end
+
+ it "returns nil for an expression with 'not' and an undefined method" do
+ defined?(not defined_specs_undefined_method).should be_nil
+ end
+
+ it "returns nil for an expression with 'not' and an unset class variable" do
+ @result = eval("class singleton_class::A; defined?(not @@doesnt_exist) end", binding, __FILE__, __LINE__)
+ @result.should be_nil
+ end
+
+ it "does not propagate an exception raised by a method in a 'not' expression" do
+ defined?(not DefinedSpecs.exception_method).should be_nil
+ ScratchPad.recorded.should == :defined_specs_exception
+ end
+
+ it "returns 'expression' for an expression with '&&/and' and an unset global variable" do
+ defined?($defined_specs_undefined_global_variable && true).should == "expression"
+ defined?(true && $defined_specs_undefined_global_variable).should == "expression"
+ defined?($defined_specs_undefined_global_variable and true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression with '&&/and' and an unset instance variable" do
+ defined?(@defined_specs_undefined_instance_variable && true).should == "expression"
+ defined?(true && @defined_specs_undefined_instance_variable).should == "expression"
+ defined?(@defined_specs_undefined_instance_variable and true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression '&&/and' regardless of its truth value" do
+ defined?(true && false).should == "expression"
+ defined?(true and false).should == "expression"
+ end
+
+ it "returns 'expression' for an expression with '||/or' and an unset global variable" do
+ defined?($defined_specs_undefined_global_variable || true).should == "expression"
+ defined?(true || $defined_specs_undefined_global_variable).should == "expression"
+ defined?($defined_specs_undefined_global_variable or true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression with '||/or' and an unset instance variable" do
+ defined?(@defined_specs_undefined_instance_variable || true).should == "expression"
+ defined?(true || @defined_specs_undefined_instance_variable).should == "expression"
+ defined?(@defined_specs_undefined_instance_variable or true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression '||/or' regardless of its truth value" do
+ defined?(true || false).should == "expression"
+ defined?(true or false).should == "expression"
+ end
+
+ it "returns nil for an expression with '!' and an unset global variable" do
+ defined?(!$defined_specs_undefined_global_variable).should be_nil
+ end
+
+ it "returns nil for an expression with '!' and an unset instance variable" do
+ defined?(!@defined_specs_undefined_instance_variable).should be_nil
+ end
+
+ it "returns 'method' for a 'not' expression with a method" do
+ defined?(not DefinedSpecs.side_effects).should == "method"
+ end
+
+ it "calls a method in a 'not' expression and returns 'method'" do
+ defined?(not DefinedSpecs.side_effects).should == "method"
+ ScratchPad.recorded.should == :defined_specs_side_effects
+ end
+
+ it "returns nil for an expression with 'not' and an unset global variable" do
+ defined?(not $defined_specs_undefined_global_variable).should be_nil
+ end
+
+ it "returns nil for an expression with 'not' and an unset instance variable" do
+ defined?(not @defined_specs_undefined_instance_variable).should be_nil
+ end
+
+ it "returns 'expression' for an expression with '&&/and' and an undefined method" do
+ defined?(defined_specs_undefined_method && true).should == "expression"
+ defined?(defined_specs_undefined_method and true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression with '&&/and' and an unset class variable" do
+ defined?(@@defined_specs_undefined_class_variable && true).should == "expression"
+ defined?(@@defined_specs_undefined_class_variable and true).should == "expression"
+ end
+
+ it "does not call a method in an '&&' expression and returns 'expression'" do
+ defined?(DefinedSpecs.side_effects && true).should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "does not call a method in an 'and' expression and returns 'expression'" do
+ defined?(DefinedSpecs.side_effects and true).should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "returns 'expression' for an expression with '||/or' and an undefined method" do
+ defined?(defined_specs_undefined_method || true).should == "expression"
+ defined?(defined_specs_undefined_method or true).should == "expression"
+ end
+
+ it "returns 'expression' for an expression with '||/or' and an unset class variable" do
+ defined?(@@defined_specs_undefined_class_variable || true).should == "expression"
+ defined?(@@defined_specs_undefined_class_variable or true).should == "expression"
+ end
+
+ it "does not call a method in an '||' expression and returns 'expression'" do
+ defined?(DefinedSpecs.side_effects || true).should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "does not call a method in an 'or' expression and returns 'expression'" do
+ defined?(DefinedSpecs.side_effects or true).should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+ end
+
+ it "returns 'expression' when passed a String" do
+ defined?("garble gooble gable").should == "expression"
+ end
+
+ describe "with a dynamic String" do
+ it "returns 'expression' when the String contains a literal" do
+ defined?("garble #{42}").should == "expression"
+ end
+
+ it "returns 'expression' when the String contains a call to a defined method" do
+ defined?("garble #{DefinedSpecs.side_effects}").should == "expression"
+ end
+
+ it "returns 'expression' when the String contains a call to an undefined method" do
+ defined?("garble #{DefinedSpecs.undefined_method}").should == "expression"
+ end
+
+ it "does not call the method in the String" do
+ defined?("garble #{DefinedSpecs.dynamic_string}").should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+ end
+
+ describe "with a dynamic Regexp" do
+ it "returns 'expression' when the Regexp contains a literal" do
+ defined?(/garble #{42}/).should == "expression"
+ end
+
+ it "returns 'expression' when the Regexp contains a call to a defined method" do
+ defined?(/garble #{DefinedSpecs.side_effects}/).should == "expression"
+ end
+
+ it "returns 'expression' when the Regexp contains a call to an undefined method" do
+ defined?(/garble #{DefinedSpecs.undefined_method}/).should == "expression"
+ end
+
+ it "does not call the method in the Regexp" do
+ defined?(/garble #{DefinedSpecs.dynamic_string}/).should == "expression"
+ ScratchPad.recorded.should be_nil
+ end
+ end
+
+ it "returns 'expression' when passed a Fixnum literal" do
+ defined?(42).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Bignum literal" do
+ defined?(0xdead_beef_deed_feed).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Float literal" do
+ defined?(1.5).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Range literal" do
+ defined?(0..2).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Regexp literal" do
+ defined?(/undefined/).should == "expression"
+ end
+
+ it "returns 'expression' when passed an Array literal" do
+ defined?([1, 2]).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Hash literal" do
+ defined?({a: :b}).should == "expression"
+ end
+
+ it "returns 'expression' when passed a Symbol literal" do
+ defined?(:defined_specs).should == "expression"
+ end
+end
+
+describe "The defined? keyword for variables" do
+ it "returns 'local-variable' when called with the name of a local variable" do
+ ret = DefinedSpecs::Basic.new.local_variable_defined
+ ret.should == "local-variable"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'local-variable' when called with the name of a local variable assigned to nil" do
+ DefinedSpecs::Basic.new.local_variable_defined_nil.should == "local-variable"
+ end
+
+ it "returns nil for an instance variable that has not been read" do
+ DefinedSpecs::Basic.new.instance_variable_undefined.should be_nil
+ end
+
+ it "returns nil for an instance variable that has been read but not assigned to" do
+ DefinedSpecs::Basic.new.instance_variable_read.should be_nil
+ end
+
+ it "returns 'instance-variable' for an instance variable that has been assigned" do
+ ret = DefinedSpecs::Basic.new.instance_variable_defined
+ ret.should == "instance-variable"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'instance-variable' for an instance variable that has been assigned to nil" do
+ DefinedSpecs::Basic.new.instance_variable_defined_nil.should == "instance-variable"
+ end
+
+ it "returns nil for a global variable that has not been read" do
+ DefinedSpecs::Basic.new.global_variable_undefined.should be_nil
+ end
+
+ it "returns nil for a global variable that has been read but not assigned to" do
+ DefinedSpecs::Basic.new.global_variable_read.should be_nil
+ end
+
+ it "returns 'global-variable' for a global variable that has been assigned nil" do
+ ret = DefinedSpecs::Basic.new.global_variable_defined_as_nil
+ ret.should == "global-variable"
+ ret.frozen?.should == true
+ end
+
+ # MRI appears to special case defined? for $! and $~ in that it returns
+ # 'global-variable' even when they are not set (or they are always "set"
+ # but the value may be nil). In other words, 'defined?($~)' will return
+ # 'global-variable' even if no match has been done.
+
+ it "returns 'global-variable' for $!" do
+ defined?($!).should == "global-variable"
+ end
+
+ it "returns 'global-variable for $~" do
+ defined?($~).should == "global-variable"
+ end
+
+ describe "when a String does not match a Regexp" do
+ before :each do
+ "mis-matched" =~ /z(z)z/
+ end
+
+ it "returns 'global-variable' for $~" do
+ defined?($~).should == "global-variable"
+ end
+
+ it "returns nil for $&" do
+ defined?($&).should be_nil
+ end
+
+ it "returns nil for $`" do
+ defined?($`).should be_nil
+ end
+
+ it "returns nil for $'" do
+ defined?($').should be_nil
+ end
+
+ it "returns nil for $+" do
+ defined?($+).should be_nil
+ end
+
+ it "returns nil for any last match global" do
+ defined?($1).should be_nil
+ defined?($4).should be_nil
+ defined?($7).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
+ end
+ end
+
+ describe "when a String matches a Regexp" do
+ before :each do
+ "mis-matched" =~ /s(-)m(.)/
+ end
+
+ it "returns 'global-variable' for $~" do
+ defined?($~).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $&" do
+ defined?($&).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $`" do
+ defined?($`).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $'" do
+ defined?($').should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $+" do
+ defined?($+).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for the capture references" do
+ defined?($1).should == "global-variable"
+ defined?($2).should == "global-variable"
+ end
+
+ it "returns nil for non-captures" do
+ defined?($4).should be_nil
+ defined?($7).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
+ end
+ end
+
+ describe "when a Regexp does not match a String" do
+ before :each do
+ /z(z)z/ =~ "mis-matched"
+ end
+
+ it "returns 'global-variable' for $~" do
+ defined?($~).should == "global-variable"
+ end
+
+ it "returns nil for $&" do
+ defined?($&).should be_nil
+ end
+
+ it "returns nil for $`" do
+ defined?($`).should be_nil
+ end
+
+ it "returns nil for $'" do
+ defined?($').should be_nil
+ end
+
+ it "returns nil for $+" do
+ defined?($+).should be_nil
+ end
+
+ it "returns nil for any last match global" do
+ defined?($1).should be_nil
+ defined?($4).should be_nil
+ defined?($7).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
+ end
+ end
+
+ describe "when a Regexp matches a String" do
+ before :each do
+ /s(-)m(.)/ =~ "mis-matched"
+ end
+
+ it "returns 'global-variable' for $~" do
+ defined?($~).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $&" do
+ defined?($&).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $`" do
+ defined?($`).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $'" do
+ defined?($').should == "global-variable"
+ end
+
+ it "returns 'global-variable' for $+" do
+ defined?($+).should == "global-variable"
+ end
+
+ it "returns 'global-variable' for the capture references" do
+ defined?($1).should == "global-variable"
+ defined?($2).should == "global-variable"
+ end
+
+ it "returns nil for non-captures" do
+ defined?($4).should be_nil
+ defined?($7).should be_nil
+ defined?($10).should be_nil
+ defined?($200).should be_nil
+ end
+ end
+ it "returns 'global-variable' for a global variable that has been assigned" do
+ DefinedSpecs::Basic.new.global_variable_defined.should == "global-variable"
+ end
+
+ it "returns nil for a class variable that has not been read" do
+ DefinedSpecs::Basic.new.class_variable_undefined.should be_nil
+ end
+
+ # There is no spec for a class variable that is read before being assigned
+ # to because setting up the code for this raises a NameError before you
+ # get to the defined? call so it really has nothing to do with 'defined?'.
+
+ it "returns 'class variable' when called with the name of a class variable" do
+ ret = DefinedSpecs::Basic.new.class_variable_defined
+ ret.should == "class variable"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'local-variable' when called with the name of a block local" do
+ block = Proc.new { |xxx| defined?(xxx) }
+ block.call(1).should == "local-variable"
+ end
+end
+
+describe "The defined? keyword for a simple constant" do
+ it "returns 'constant' when the constant is defined" do
+ ret = defined?(DefinedSpecs)
+ ret.should == "constant"
+ ret.frozen?.should == true
+ end
+
+ it "returns nil when the constant is not defined" do
+ defined?(DefinedSpecsUndefined).should be_nil
+ end
+
+ it "does not call Object.const_missing if the constant is not defined" do
+ Object.should_not_receive(:const_missing)
+ defined?(DefinedSpecsUndefined).should be_nil
+ end
+
+ it "returns 'constant' for an included module" do
+ DefinedSpecs::Child.module_defined.should == "constant"
+ end
+
+ it "returns 'constant' for a constant defined in an included module" do
+ DefinedSpecs::Child.module_constant_defined.should == "constant"
+ end
+end
+
+describe "The defined? keyword for a top-level constant" do
+ it "returns 'constant' when passed the name of a top-level constant" do
+ defined?(::DefinedSpecs).should == "constant"
+ end
+
+ it "returns nil if the constant is not defined" do
+ defined?(::DefinedSpecsUndefined).should be_nil
+ end
+
+ it "does not call Object.const_missing if the constant is not defined" do
+ Object.should_not_receive(:const_missing)
+ defined?(::DefinedSpecsUndefined).should be_nil
+ end
+end
+
+describe "The defined? keyword for a scoped constant" do
+ it "returns 'constant' when the scoped constant is defined" do
+ defined?(DefinedSpecs::Basic).should == "constant"
+ end
+
+ it "returns nil when the scoped constant is not defined" do
+ defined?(DefinedSpecs::Undefined).should be_nil
+ end
+
+ it "does not call .const_missing if the constant is not defined" do
+ DefinedSpecs.should_not_receive(:const_missing)
+ defined?(DefinedSpecs::UnknownChild).should be_nil
+ end
+
+ it "returns nil when an undefined constant is scoped to a defined constant" do
+ defined?(DefinedSpecs::Child::Undefined).should be_nil
+ end
+
+ it "returns nil when a constant is scoped to an undefined constant" do
+ Object.should_not_receive(:const_missing)
+ defined?(Undefined::Object).should be_nil
+ end
+
+ it "returns nil when the undefined constant is scoped to an undefined constant" do
+ defined?(DefinedSpecs::Undefined::Undefined).should be_nil
+ end
+
+ it "returns nil when a constant is defined on top-level but not on the module" do
+ defined?(DefinedSpecs::String).should be_nil
+ end
+
+ it "returns nil when a constant is defined on top-level but not on the class" do
+ defined?(DefinedSpecs::Basic::String).should be_nil
+ end
+
+ it "returns 'constant' if the scoped-scoped constant is defined" do
+ defined?(DefinedSpecs::Child::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a top-level scoped constant" do
+ it "returns 'constant' when the scoped constant is defined" do
+ defined?(::DefinedSpecs::Basic).should == "constant"
+ end
+
+ it "returns nil when the scoped constant is not defined" do
+ defined?(::DefinedSpecs::Undefined).should be_nil
+ end
+
+ it "returns nil when an undefined constant is scoped to a defined constant" do
+ defined?(::DefinedSpecs::Child::Undefined).should be_nil
+ end
+
+ it "returns nil when the undefined constant is scoped to an undefined constant" do
+ defined?(::DefinedSpecs::Undefined::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the scoped-scoped constant is defined" do
+ defined?(::DefinedSpecs::Child::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a self-send method call scoped constant" do
+ it "returns nil if the constant is not defined in the scope of the method's value" do
+ defined?(defined_specs_method::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the method's value" do
+ defined?(defined_specs_method::Basic).should == "constant"
+ end
+
+ it "returns nil if the last constant is not defined in the scope chain" do
+ defined?(defined_specs_method::Basic::Undefined).should be_nil
+ end
+
+ it "returns nil if the middle constant is not defined in the scope chain" do
+ defined?(defined_specs_method::Undefined::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if all the constants in the scope chain are defined" do
+ defined?(defined_specs_method::Basic::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a receiver method call scoped constant" do
+ it "returns nil if the constant is not defined in the scope of the method's value" do
+ defined?(defined_specs_receiver.defined_method::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the method's value" do
+ defined?(defined_specs_receiver.defined_method::Basic).should == "constant"
+ end
+
+ it "returns nil if the last constant is not defined in the scope chain" do
+ defined?(defined_specs_receiver.defined_method::Basic::Undefined).should be_nil
+ end
+
+ it "returns nil if the middle constant is not defined in the scope chain" do
+ defined?(defined_specs_receiver.defined_method::Undefined::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if all the constants in the scope chain are defined" do
+ defined?(defined_specs_receiver.defined_method::Basic::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a module method call scoped constant" do
+ it "returns nil if the constant is not defined in the scope of the method's value" do
+ defined?(DefinedSpecs.defined_method::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant scoped by the method's value is defined" do
+ defined?(DefinedSpecs.defined_method::Basic).should == "constant"
+ end
+
+ it "returns nil if the last constant in the scope chain is not defined" do
+ defined?(DefinedSpecs.defined_method::Basic::Undefined).should be_nil
+ end
+
+ it "returns nil if the middle constant in the scope chain is not defined" do
+ defined?(DefinedSpecs.defined_method::Undefined::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if all the constants in the scope chain are defined" do
+ defined?(DefinedSpecs.defined_method::Basic::A).should == "constant"
+ end
+
+ it "returns nil if the outer scope constant in the receiver is not defined" do
+ defined?(Undefined::DefinedSpecs.defined_method::Basic).should be_nil
+ end
+
+ it "returns nil if the scoped constant in the receiver is not defined" do
+ defined?(DefinedSpecs::Undefined.defined_method::Basic).should be_nil
+ end
+
+ it "returns 'constant' if all the constants in the receiver are defined" do
+ defined?(Object::DefinedSpecs.defined_method::Basic).should == "constant"
+ end
+
+ it "returns 'constant' if all the constants in the receiver and scope chain are defined" do
+ defined?(Object::DefinedSpecs.defined_method::Basic::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a variable scoped constant" do
+ after :all do
+ if Object.class_variable_defined? :@@defined_specs_obj
+ Object.__send__(:remove_class_variable, :@@defined_specs_obj)
+ end
+ end
+
+ it "returns nil if the instance scoped constant is not defined" do
+ @defined_specs_obj = DefinedSpecs::Basic
+ defined?(@defined_specs_obj::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the instance variable" do
+ @defined_specs_obj = DefinedSpecs::Basic
+ defined?(@defined_specs_obj::A).should == "constant"
+ end
+
+ it "returns nil if the global scoped constant is not defined" do
+ $defined_specs_obj = DefinedSpecs::Basic
+ defined?($defined_specs_obj::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the global variable" do
+ $defined_specs_obj = DefinedSpecs::Basic
+ defined?($defined_specs_obj::A).should == "constant"
+ end
+
+ it "returns nil if the class scoped constant is not defined" do
+ eval(<<-END, binding, __FILE__, __LINE__)
+ class singleton_class::A
+ @@defined_specs_obj = DefinedSpecs::Basic
+ defined?(@@defined_specs_obj::Undefined).should be_nil
+ end
+ END
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the class variable" do
+ eval(<<-END, binding, __FILE__, __LINE__)
+ class singleton_class::A
+ @@defined_specs_obj = DefinedSpecs::Basic
+ defined?(@@defined_specs_obj::A).should == "constant"
+ end
+ END
+ end
+
+ it "returns nil if the local scoped constant is not defined" do
+ defined_specs_obj = DefinedSpecs::Basic
+ defined?(defined_specs_obj::Undefined).should be_nil
+ end
+
+ it "returns 'constant' if the constant is defined in the scope of the local variable" do
+ defined_specs_obj = DefinedSpecs::Basic
+ defined?(defined_specs_obj::A).should == "constant"
+ end
+end
+
+describe "The defined? keyword for a self:: scoped constant" do
+ it "returns 'constant' for a constant explicitly scoped to self:: when set" do
+ defined?(DefinedSpecs::SelfScoped).should == "constant"
+ end
+
+ it "returns 'constant' for a constant explicitly scoped to self:: in subclass's metaclass" do
+ DefinedSpecs::Child.parent_constant_defined.should == "constant"
+ end
+end
+
+describe "The defined? keyword for yield" do
+ it "returns nil if no block is passed to a method not taking a block parameter" do
+ DefinedSpecs::Basic.new.no_yield_block.should be_nil
+ end
+
+ it "returns nil if no block is passed to a method taking a block parameter" do
+ DefinedSpecs::Basic.new.no_yield_block_parameter.should be_nil
+ end
+
+ it "returns 'yield' if a block is passed to a method not taking a block parameter" do
+ ret = DefinedSpecs::Basic.new.yield_block
+ ret.should == "yield"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'yield' if a block is passed to a method taking a block parameter" do
+ DefinedSpecs::Basic.new.yield_block_parameter.should == "yield"
+ end
+
+ it "returns 'yield' when called within a block" do
+ def yielder
+ yield
+ end
+
+ def call_defined
+ yielder { defined?(yield) }
+ end
+
+ call_defined() { }.should == "yield"
+ end
+end
+
+describe "The defined? keyword for super" do
+ it "returns nil when a superclass undef's the method" do
+ DefinedSpecs::ClassWithoutMethod.new.test.should be_nil
+ end
+
+ describe "for a method taking no arguments" do
+ it "returns nil when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_method_no_args.should be_nil
+ end
+
+ it "returns nil from a block when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_method_block_no_args.should be_nil
+ end
+
+ it "returns nil from a #define_method when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_define_method_no_args.should be_nil
+ end
+
+ it "returns nil from a block in a #define_method when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_define_method_block_no_args.should be_nil
+ end
+
+ it "returns 'super' when a superclass method exists" do
+ ret = DefinedSpecs::Super.new.method_no_args
+ ret.should == "super"
+ ret.frozen?.should == true
+ end
+
+ it "returns 'super' from a block when a superclass method exists" do
+ DefinedSpecs::Super.new.method_block_no_args.should == "super"
+ end
+
+ it "returns 'super' from a #define_method when a superclass method exists" do
+ DefinedSpecs::Super.new.define_method_no_args.should == "super"
+ end
+
+ it "returns 'super' from a block in a #define_method when a superclass method exists" do
+ DefinedSpecs::Super.new.define_method_block_no_args.should == "super"
+ end
+
+ it "returns 'super' when the method exists in a supermodule" do
+ DefinedSpecs::SuperWithIntermediateModules.new.method_no_args.should == "super"
+ end
+ end
+
+ describe "for a method taking arguments" do
+ it "returns nil when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_method_args.should be_nil
+ end
+
+ it "returns nil from a block when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_method_block_args.should be_nil
+ end
+
+ it "returns nil from a #define_method when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_define_method_args.should be_nil
+ end
+
+ it "returns nil from a block in a #define_method when no superclass method exists" do
+ DefinedSpecs::Super.new.no_super_define_method_block_args.should be_nil
+ end
+
+ it "returns 'super' when a superclass method exists" do
+ DefinedSpecs::Super.new.method_args.should == "super"
+ end
+
+ it "returns 'super' from a block when a superclass method exists" do
+ DefinedSpecs::Super.new.method_block_args.should == "super"
+ end
+
+ it "returns 'super' from a #define_method when a superclass method exists" do
+ DefinedSpecs::Super.new.define_method_args.should == "super"
+ end
+
+ it "returns 'super' from a block in a #define_method when a superclass method exists" do
+ DefinedSpecs::Super.new.define_method_block_args.should == "super"
+ end
+ end
+
+ describe "within an included module's method" do
+ it "returns 'super' when a superclass method exists in the including hierarchy" do
+ DefinedSpecs::Child.new.defined_super.should == "super"
+ end
+ end
+end
+
+
+describe "The defined? keyword for instance variables" do
+ it "returns 'instance-variable' if assigned" do
+ @assigned_ivar = "some value"
+ defined?(@assigned_ivar).should == "instance-variable"
+ end
+
+ it "returns nil if not assigned" do
+ defined?(@unassigned_ivar).should be_nil
+ end
+end
+
+describe "The defined? keyword for pseudo-variables" do
+ it "returns 'expression' for __FILE__" do
+ defined?(__FILE__).should == "expression"
+ end
+
+ it "returns 'expression' for __LINE__" do
+ defined?(__LINE__).should == "expression"
+ end
+
+ it "returns 'expression' for __ENCODING__" do
+ defined?(__ENCODING__).should == "expression"
+ end
+end
+
+describe "The defined? keyword for conditional expressions" do
+ it "returns 'expression' for an 'if' conditional" do
+ defined?(if x then 'x' else '' end).should == "expression"
+ end
+
+ it "returns 'expression' for an 'unless' conditional" do
+ defined?(unless x then '' else 'x' end).should == "expression"
+ end
+
+ it "returns 'expression' for ternary expressions" do
+ defined?(x ? 'x' : '').should == "expression"
+ end
+end
+
+describe "The defined? keyword for case expressions" do
+ it "returns 'expression'" do
+ defined?(case x; when 'x'; 'y' end).should == "expression"
+ end
+end
+
+describe "The defined? keyword for loop expressions" do
+ it "returns 'expression' for a 'for' expression" do
+ defined?(for n in 1..3 do true end).should == "expression"
+ end
+
+ it "returns 'expression' for a 'while' expression" do
+ defined?(while x do y end).should == "expression"
+ end
+
+ it "returns 'expression' for an 'until' expression" do
+ defined?(until x do y end).should == "expression"
+ end
+
+ it "returns 'expression' for a 'break' expression" do
+ defined?(break).should == "expression"
+ end
+
+ it "returns 'expression' for a 'next' expression" do
+ defined?(next).should == "expression"
+ end
+
+ it "returns 'expression' for a 'redo' expression" do
+ defined?(redo).should == "expression"
+ end
+
+ it "returns 'expression' for a 'retry' expression" do
+ defined?(retry).should == "expression"
+ end
+end
+
+describe "The defined? keyword for return expressions" do
+ it "returns 'expression'" do
+ defined?(return).should == "expression"
+ end
+end
+
+describe "The defined? keyword for exception expressions" do
+ it "returns 'expression'" do
+ defined?(begin 1 end).should == "expression"
+ end
+end
diff --git a/spec/ruby/language/delegation_spec.rb b/spec/ruby/language/delegation_spec.rb
new file mode 100644
index 0000000000..3f24a79d5c
--- /dev/null
+++ b/spec/ruby/language/delegation_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/delegation'
+
+describe "delegation with def(...)" do
+ it "delegates rest and kwargs" do
+ a = Class.new(DelegationSpecs::Target)
+ a.class_eval(<<-RUBY)
+ def delegate(...)
+ target(...)
+ end
+ RUBY
+
+ a.new.delegate(1, b: 2).should == [[1], {b: 2}]
+ end
+
+ it "delegates block" do
+ a = Class.new(DelegationSpecs::Target)
+ a.class_eval(<<-RUBY)
+ def delegate_block(...)
+ target_block(...)
+ end
+ RUBY
+
+ a.new.delegate_block(1, b: 2) { |x| x }.should == [{b: 2}, [1]]
+ end
+
+ it "parses as open endless Range when brackets are omitted" do
+ a = Class.new(DelegationSpecs::Target)
+ suppress_warning do
+ a.class_eval(<<-RUBY)
+ def delegate(...)
+ target ...
+ end
+ RUBY
+ end
+
+ a.new.delegate(1, b: 2).should == Range.new([[], {}], nil, true)
+ end
+end
+
+ruby_version_is "2.7.3" do
+ describe "delegation with def(x, ...)" do
+ it "delegates rest and kwargs" do
+ a = Class.new(DelegationSpecs::Target)
+ a.class_eval(<<-RUBY)
+ def delegate(x, ...)
+ target(...)
+ end
+ RUBY
+
+ a.new.delegate(0, 1, b: 2).should == [[1], {b: 2}]
+ end
+
+ it "delegates block" do
+ a = Class.new(DelegationSpecs::Target)
+ a.class_eval(<<-RUBY)
+ def delegate_block(x, ...)
+ target_block(...)
+ end
+ RUBY
+
+ a.new.delegate_block(0, 1, b: 2) { |x| x }.should == [{b: 2}, [1]]
+ end
+ end
+end
diff --git a/spec/ruby/language/encoding_spec.rb b/spec/ruby/language/encoding_spec.rb
new file mode 100644
index 0000000000..5430c9cb98
--- /dev/null
+++ b/spec/ruby/language/encoding_spec.rb
@@ -0,0 +1,36 @@
+# -*- encoding: us-ascii -*-
+require_relative '../spec_helper'
+require_relative 'fixtures/coding_us_ascii'
+require_relative 'fixtures/coding_utf_8'
+
+describe "The __ENCODING__ pseudo-variable" do
+ it "is an instance of Encoding" do
+ __ENCODING__.should be_kind_of(Encoding)
+ end
+
+ it "is US-ASCII by default" do
+ __ENCODING__.should == Encoding::US_ASCII
+ end
+
+ it "is the evaluated strings's one inside an eval" do
+ eval("__ENCODING__".force_encoding("US-ASCII")).should == Encoding::US_ASCII
+ eval("__ENCODING__".force_encoding("BINARY")).should == Encoding::BINARY
+ end
+
+ it "is the encoding specified by a magic comment inside an eval" do
+ code = "# encoding: BINARY\n__ENCODING__".force_encoding("US-ASCII")
+ eval(code).should == Encoding::BINARY
+
+ code = "# encoding: us-ascii\n__ENCODING__".force_encoding("BINARY")
+ eval(code).should == Encoding::US_ASCII
+ end
+
+ it "is the encoding specified by a magic comment in the file" do
+ CodingUS_ASCII.encoding.should == Encoding::US_ASCII
+ CodingUTF_8.encoding.should == Encoding::UTF_8
+ end
+
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("__ENCODING__ = 1") }.should raise_error(SyntaxError)
+ end
+end
diff --git a/spec/ruby/language/ensure_spec.rb b/spec/ruby/language/ensure_spec.rb
new file mode 100644
index 0000000000..e893904bcb
--- /dev/null
+++ b/spec/ruby/language/ensure_spec.rb
@@ -0,0 +1,331 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/ensure'
+
+describe "An ensure block inside a begin block" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "is executed when an exception is raised in it's corresponding begin block" do
+ -> {
+ begin
+ ScratchPad << :begin
+ raise EnsureSpec::Error
+ ensure
+ ScratchPad << :ensure
+ end
+ }.should raise_error(EnsureSpec::Error)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "is executed when an exception is raised and rescued in it's corresponding begin block" do
+ begin
+ ScratchPad << :begin
+ raise "An exception occurred!"
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+
+ ScratchPad.recorded.should == [:begin, :rescue, :ensure]
+ end
+
+ it "is executed even when a symbol is thrown in it's corresponding begin block" do
+ catch(:symbol) do
+ begin
+ ScratchPad << :begin
+ throw(:symbol)
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "is executed when nothing is raised or thrown in it's corresponding begin block" do
+ begin
+ ScratchPad << :begin
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "has no return value" do
+ begin
+ :begin
+ ensure
+ :ensure
+ end.should == :begin
+ end
+
+ it "sets exception cause if raises exception in block and in ensure" do
+ -> {
+ begin
+ raise "from block"
+ ensure
+ raise "from ensure"
+ end
+ }.should raise_error(RuntimeError, "from ensure") { |e|
+ e.cause.message.should == "from block"
+ }
+ end
+end
+
+describe "The value of an ensure expression," do
+ it "in no-exception scenarios, is the value of the last statement of the protected body" do
+ begin
+ v = 1
+ eval('x=1') # to prevent opts from triggering
+ v
+ ensure
+ v = 2
+ end.should == 1
+ end
+
+ it "when an exception is rescued, is the value of the rescuing block" do
+ begin
+ raise 'foo'
+ rescue
+ v = 3
+ ensure
+ v = 2
+ end.should == 3
+ end
+end
+
+describe "An ensure block inside a method" do
+ before :each do
+ @obj = EnsureSpec::Container.new
+ end
+
+ it "is executed when an exception is raised in the method" do
+ -> { @obj.raise_in_method_with_ensure }.should raise_error(EnsureSpec::Error)
+ @obj.executed.should == [:method, :ensure]
+ end
+
+ it "is executed when an exception is raised and rescued in the method" do
+ @obj.raise_and_rescue_in_method_with_ensure
+ @obj.executed.should == [:method, :rescue, :ensure]
+ end
+
+ it "is executed even when a symbol is thrown in the method" do
+ catch(:symbol) { @obj.throw_in_method_with_ensure }
+ @obj.executed.should == [:method, :ensure]
+ end
+
+ it "has no impact on the method's implicit return value" do
+ @obj.implicit_return_in_method_with_ensure.should == :method
+ end
+
+ it "has an impact on the method's explicit return value" do
+ @obj.explicit_return_in_method_with_ensure.should == :ensure
+ end
+
+ it "has an impact on the method's explicit return value from rescue if returns explicitly" do
+ @obj.explicit_return_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure"
+ end
+
+ it "has no impact on the method's explicit return value from rescue if returns implicitly" do
+ @obj.explicit_return_in_rescue_and_implicit_return_in_ensure.should == "returned in rescue"
+ end
+
+ it "suppresses exception raised in method if returns value explicitly" do
+ @obj.raise_and_explicit_return_in_ensure.should == "returned in ensure"
+ end
+
+ it "suppresses exception raised in rescue if returns value explicitly" do
+ @obj.raise_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure"
+ end
+
+ it "overrides exception raised in rescue if raises exception itself" do
+ -> {
+ @obj.raise_in_rescue_and_raise_in_ensure
+ }.should raise_error(RuntimeError, "raised in ensure")
+ end
+
+ it "suppresses exception raised in method if raises exception itself" do
+ -> {
+ @obj.raise_in_method_and_raise_in_ensure
+ }.should raise_error(RuntimeError, "raised in ensure")
+ end
+end
+
+describe "An ensure block inside a class" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "is executed when an exception is raised" do
+ -> {
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ raise EnsureSpec::Error
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+ }.should raise_error(EnsureSpec::Error)
+
+ ScratchPad.recorded.should == [:class, :ensure]
+ end
+
+ it "is executed when an exception is raised and rescued" do
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ raise
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:class, :rescue, :ensure]
+ end
+
+ it "is executed even when a symbol is thrown" do
+ catch(:symbol) do
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ throw(:symbol)
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+ end
+
+ ScratchPad.recorded.should == [:class, :ensure]
+ end
+
+ it "is executed when nothing is raised or thrown" do
+ eval <<-ruby
+ class EnsureInClassExample
+ ScratchPad << :class
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:class, :ensure]
+ end
+
+ it "has no return value" do
+ result = eval <<-ruby
+ class EnsureInClassExample
+ :class
+ ensure
+ :ensure
+ end
+ ruby
+
+ result.should == :class
+ end
+end
+
+describe "An ensure block inside {} block" do
+ it "is not allowed" do
+ -> {
+ eval <<-ruby
+ lambda {
+ raise
+ ensure
+ }
+ ruby
+ }.should raise_error(SyntaxError)
+ end
+end
+
+describe "An ensure block inside 'do end' block" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "is executed when an exception is raised in it's corresponding begin block" do
+ -> {
+ eval(<<-ruby).call
+ lambda do
+ ScratchPad << :begin
+ raise EnsureSpec::Error
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+ }.should raise_error(EnsureSpec::Error)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "is executed when an exception is raised and rescued in it's corresponding begin block" do
+ eval(<<-ruby).call
+ lambda do
+ ScratchPad << :begin
+ raise "An exception occurred!"
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:begin, :rescue, :ensure]
+ end
+
+ it "is executed even when a symbol is thrown in it's corresponding begin block" do
+ catch(:symbol) do
+ eval(<<-ruby).call
+ lambda do
+ ScratchPad << :begin
+ throw(:symbol)
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "is executed when nothing is raised or thrown in it's corresponding begin block" do
+ eval(<<-ruby).call
+ lambda do
+ ScratchPad << :begin
+ rescue
+ ScratchPad << :rescue
+ ensure
+ ScratchPad << :ensure
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "has no return value" do
+ result = eval(<<-ruby).call
+ lambda do
+ :begin
+ ensure
+ :ensure
+ end
+ ruby
+
+ result.should == :begin
+ end
+end
diff --git a/spec/ruby/language/execution_spec.rb b/spec/ruby/language/execution_spec.rb
new file mode 100644
index 0000000000..4e0310946d
--- /dev/null
+++ b/spec/ruby/language/execution_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../spec_helper'
+
+describe "``" do
+ it "returns the output of the executed sub-process" do
+ ip = 'world'
+ `echo disc #{ip}`.should == "disc world\n"
+ end
+end
+
+describe "%x" do
+ it "is the same as ``" do
+ ip = 'world'
+ %x(echo disc #{ip}).should == "disc world\n"
+ end
+end
diff --git a/spec/ruby/language/file_spec.rb b/spec/ruby/language/file_spec.rb
new file mode 100644
index 0000000000..136b262d07
--- /dev/null
+++ b/spec/ruby/language/file_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/code_loading'
+require_relative 'shared/__FILE__'
+
+describe "The __FILE__ pseudo-variable" do
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("__FILE__ = 1") }.should raise_error(SyntaxError)
+ end
+
+ it "equals (eval) inside an eval" do
+ eval("__FILE__").should == "(eval)"
+ end
+end
+
+describe "The __FILE__ pseudo-variable with require" do
+ it_behaves_like :language___FILE__, :require, Kernel
+end
+
+describe "The __FILE__ pseudo-variable with load" do
+ it_behaves_like :language___FILE__, :load, Kernel
+end
diff --git a/spec/ruby/language/fixtures/argv_encoding.rb b/spec/ruby/language/fixtures/argv_encoding.rb
new file mode 100644
index 0000000000..8192b2d9a0
--- /dev/null
+++ b/spec/ruby/language/fixtures/argv_encoding.rb
@@ -0,0 +1 @@
+p ARGV.map { |a| a.encoding.name }
diff --git a/spec/ruby/language/fixtures/array.rb b/spec/ruby/language/fixtures/array.rb
new file mode 100644
index 0000000000..c1036575ff
--- /dev/null
+++ b/spec/ruby/language/fixtures/array.rb
@@ -0,0 +1,32 @@
+module ArraySpec
+ class Splat
+ def unpack_3args(a, b, c)
+ [a, b, c]
+ end
+
+ def unpack_4args(a, b, c, d)
+ [a, b, c, d]
+ end
+ end
+
+ class SideEffect
+ def initialize()
+ @call_count = 0
+ end
+
+ attr_reader :call_count
+
+ def array_result(a_number)
+ [result(a_number), result(a_number)]
+ end
+
+ def result(a_number)
+ @call_count += 1
+ if a_number
+ 1
+ else
+ :thing
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/begin_file.rb b/spec/ruby/language/fixtures/begin_file.rb
new file mode 100644
index 0000000000..73cae61a08
--- /dev/null
+++ b/spec/ruby/language/fixtures/begin_file.rb
@@ -0,0 +1,3 @@
+BEGIN {
+ puts __FILE__
+}
diff --git a/spec/ruby/language/fixtures/binary_symbol.rb b/spec/ruby/language/fixtures/binary_symbol.rb
new file mode 100644
index 0000000000..2ddf565820
--- /dev/null
+++ b/spec/ruby/language/fixtures/binary_symbol.rb
@@ -0,0 +1,4 @@
+# encoding: binary
+
+p :il_était.to_s.bytes
+puts :il_était.encoding.name
diff --git a/spec/ruby/language/fixtures/block.rb b/spec/ruby/language/fixtures/block.rb
new file mode 100644
index 0000000000..33baac6aeb
--- /dev/null
+++ b/spec/ruby/language/fixtures/block.rb
@@ -0,0 +1,61 @@
+module BlockSpecs
+ class Yielder
+ def z
+ yield
+ end
+
+ def m(*a)
+ yield(*a)
+ end
+
+ def s(a)
+ yield(a)
+ end
+
+ def r(a)
+ yield(*a)
+ end
+
+ def k(*a)
+ yield(*a, b: true)
+ end
+ end
+
+ # TODO: rewrite all specs that use Yield to use Yielder
+
+ class Yield
+ def splat(*args)
+ yield(*args)
+ end
+
+ def two_args
+ yield 1, 2
+ end
+
+ def two_arg_array
+ yield [1, 2]
+ end
+
+ def yield_splat_inside_block
+ [1, 2].send(:each_with_index) {|*args| yield(*args)}
+ end
+
+ def yield_this(obj)
+ yield obj
+ end
+ end
+
+ class OverwriteBlockVariable
+ def initialize
+ @y = Yielder.new
+ end
+
+ def method_missing(method, *args, &block)
+ self.class.send :define_method, method do |*a, &b|
+ @y.send method, *a, &b
+ end
+
+ send method, *args, &block
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/break.rb b/spec/ruby/language/fixtures/break.rb
new file mode 100644
index 0000000000..217c20a2c0
--- /dev/null
+++ b/spec/ruby/language/fixtures/break.rb
@@ -0,0 +1,291 @@
+module BreakSpecs
+ class Driver
+ def initialize(ensures=false)
+ @ensures = ensures
+ end
+
+ def note(value)
+ ScratchPad << value
+ end
+ end
+
+ class Block < Driver
+ def break_nil
+ note :a
+ note yielding {
+ note :b
+ break
+ note :c
+ }
+ note :d
+ end
+
+ def break_value
+ note :a
+ note yielding {
+ note :b
+ break :break
+ note :c
+ }
+ note :d
+ end
+
+ def yielding
+ note :aa
+ note yield
+ note :bb
+ end
+
+ def create_block
+ note :za
+ b = capture_block do
+ note :zb
+ break :break
+ note :zc
+ end
+ note :zd
+ b
+ end
+
+ def capture_block(&b)
+ note :xa
+ b
+ end
+
+ def break_in_method_captured
+ note :a
+ create_block.call
+ note :b
+ end
+
+ def break_in_yield_captured
+ note :a
+ yielding(&create_block)
+ note :b
+ end
+
+ def break_in_method
+ note :a
+ b = capture_block {
+ note :b
+ break :break
+ note :c
+ }
+ note :d
+ note b.call
+ note :e
+ end
+
+ def call_method(b)
+ note :aa
+ note b.call
+ note :bb
+ end
+
+ def break_in_nested_method
+ note :a
+ b = capture_block {
+ note :b
+ break :break
+ note :c
+ }
+ note :cc
+ note call_method(b)
+ note :d
+ end
+
+ def break_in_yielding_method
+ note :a
+ b = capture_block {
+ note :b
+ break :break
+ note :c
+ }
+ note :cc
+ note yielding(&b)
+ note :d
+ end
+
+ def looped_break_in_captured_block
+ note :begin
+ looped_delegate_block do |i|
+ note :prebreak
+ break if i == 1
+ note :postbreak
+ end
+ note :end
+ end
+
+ def looped_delegate_block(&block)
+ note :preloop
+ 2.times do |i|
+ note :predele
+ yield_value(i, &block)
+ note :postdele
+ end
+ note :postloop
+ end
+ private :looped_delegate_block
+
+ def yield_value(value)
+ note :preyield
+ yield value
+ note :postyield
+ end
+ private :yield_value
+
+ def method(v)
+ yield v
+ end
+
+ def invoke_yield_in_while
+ looping = true
+ while looping
+ note :aa
+ yield
+ note :bb
+ looping = false
+ end
+ note :should_not_reach_here
+ end
+
+ def break_in_block_in_while
+ invoke_yield_in_while do
+ note :break
+ break :value
+ note :c
+ end
+ end
+ end
+
+ class Lambda < Driver
+ # Cases for the invocation of the scope defining the lambda still active
+ # on the call stack when the lambda is invoked.
+ def break_in_defining_scope(value=true)
+ note :a
+ note -> {
+ note :b
+ if value
+ break :break
+ else
+ break
+ end
+ note :c
+ }.call
+ note :d
+ end
+
+ def break_in_nested_scope
+ note :a
+ l = -> do
+ note :b
+ break :break
+ note :c
+ end
+ note :d
+
+ invoke_lambda l
+
+ note :e
+ end
+
+ def invoke_lambda(l)
+ note :aa
+ note l.call
+ note :bb
+ end
+
+ def break_in_nested_scope_yield
+ note :a
+ l = -> do
+ note :b
+ break :break
+ note :c
+ end
+ note :d
+
+ invoke_yield(&l)
+
+ note :e
+ end
+
+ def note_invoke_yield
+ note :aa
+ note yield
+ note :bb
+ end
+
+ def break_in_nested_scope_block
+ note :a
+ l = -> do
+ note :b
+ break :break
+ note :c
+ end
+ note :d
+
+ invoke_lambda_block l
+
+ note :e
+ end
+
+ def invoke_yield
+ note :aaa
+ yield
+ note :bbb
+ end
+
+ def invoke_lambda_block(b)
+ note :aa
+ invoke_yield do
+ note :bb
+
+ note b.call
+
+ note :cc
+ end
+ note :dd
+ end
+
+ # Cases for the invocation of the scope defining the lambda NOT still
+ # active on the call stack when the lambda is invoked.
+ def create_lambda
+ note :la
+ l = -> do
+ note :lb
+ break :break
+ note :lc
+ end
+ note :ld
+ l
+ end
+
+ def break_in_method
+ note :a
+
+ note create_lambda.call
+
+ note :b
+ end
+
+ def break_in_block_in_method
+ note :a
+ invoke_yield do
+ note :b
+
+ note create_lambda.call
+
+ note :c
+ end
+ note :d
+ end
+
+ def break_in_method_yield
+ note :a
+
+ invoke_yield(&create_lambda)
+
+ note :b
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel.rb b/spec/ruby/language/fixtures/break_lambda_toplevel.rb
new file mode 100644
index 0000000000..da5abbaf00
--- /dev/null
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel.rb
@@ -0,0 +1,9 @@
+print "a,"
+
+print -> {
+ print "b,"
+ break "break,"
+ print "c,"
+}.call
+
+puts "d"
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb b/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
new file mode 100644
index 0000000000..3dcee62424
--- /dev/null
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel_block.rb
@@ -0,0 +1,23 @@
+print "a,"
+
+l = -> {
+ print "b,"
+ break "break,"
+ print "c,"
+}
+
+def a(l)
+ print "d,"
+ b { l.call }
+ print "e,"
+end
+
+def b
+ print "f,"
+ print yield
+ print "g,"
+end
+
+a(l)
+
+puts "h"
diff --git a/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb b/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
new file mode 100644
index 0000000000..a5936a3d70
--- /dev/null
+++ b/spec/ruby/language/fixtures/break_lambda_toplevel_method.rb
@@ -0,0 +1,17 @@
+print "a,"
+
+l = -> {
+ print "b,"
+ break "break,"
+ print "c,"
+}
+
+def a(l)
+ print "d,"
+ print l.call
+ print "e,"
+end
+
+a(l)
+
+puts "f"
diff --git a/spec/ruby/language/fixtures/bytes_magic_comment.rb b/spec/ruby/language/fixtures/bytes_magic_comment.rb
new file mode 100644
index 0000000000..2bc2bcfb07
--- /dev/null
+++ b/spec/ruby/language/fixtures/bytes_magic_comment.rb
@@ -0,0 +1,2 @@
+# encoding: big5
+$magic_comment_result = '§A¦n'.bytes.inspect
diff --git a/spec/ruby/language/fixtures/case_magic_comment.rb b/spec/ruby/language/fixtures/case_magic_comment.rb
new file mode 100644
index 0000000000..96f35a7c94
--- /dev/null
+++ b/spec/ruby/language/fixtures/case_magic_comment.rb
@@ -0,0 +1,2 @@
+# CoDiNg: bIg5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/classes.rb b/spec/ruby/language/fixtures/classes.rb
new file mode 100644
index 0000000000..eb239e1e29
--- /dev/null
+++ b/spec/ruby/language/fixtures/classes.rb
@@ -0,0 +1,31 @@
+module LanguageSpecs
+ # Regexp support
+
+ def self.paired_delimiters
+ [%w[( )], %w[{ }], %w[< >], ["[", "]"]]
+ end
+
+ def self.non_paired_delimiters
+ %w[~ ! # $ % ^ & * _ + ` - = " ' , . ? / | \\]
+ end
+
+ def self.blanks
+ " \t"
+ end
+
+ def self.white_spaces
+ return blanks + "\f\n\r\v"
+ end
+
+ def self.non_alphanum_non_space
+ '~!@#$%^&*()+-\|{}[]:";\'<>?,./'
+ end
+
+ def self.punctuations
+ ",.?" # TODO - Need to fill in the full list
+ end
+
+ def self.get_regexp_with_substitution o
+ /#{o}/o
+ end
+end
diff --git a/spec/ruby/language/fixtures/coding_us_ascii.rb b/spec/ruby/language/fixtures/coding_us_ascii.rb
new file mode 100644
index 0000000000..7df66109d7
--- /dev/null
+++ b/spec/ruby/language/fixtures/coding_us_ascii.rb
@@ -0,0 +1,11 @@
+# encoding: us-ascii
+
+module CodingUS_ASCII
+ def self.encoding
+ __ENCODING__
+ end
+
+ def self.string_literal
+ "string literal"
+ end
+end
diff --git a/spec/ruby/language/fixtures/coding_utf_8.rb b/spec/ruby/language/fixtures/coding_utf_8.rb
new file mode 100644
index 0000000000..3d8e1d9a34
--- /dev/null
+++ b/spec/ruby/language/fixtures/coding_utf_8.rb
@@ -0,0 +1,11 @@
+# encoding: utf-8
+
+module CodingUTF_8
+ def self.encoding
+ __ENCODING__
+ end
+
+ def self.string_literal
+ "string literal"
+ end
+end
diff --git a/spec/ruby/language/fixtures/constant_visibility.rb b/spec/ruby/language/fixtures/constant_visibility.rb
new file mode 100644
index 0000000000..af38b2d8f2
--- /dev/null
+++ b/spec/ruby/language/fixtures/constant_visibility.rb
@@ -0,0 +1,114 @@
+module ConstantVisibility
+ module ModuleContainer
+ module PrivateModule
+ end
+ private_constant :PrivateModule
+
+ class PrivateClass
+ end
+ private_constant :PrivateClass
+ end
+
+ class ClassContainer
+ module PrivateModule
+ end
+ private_constant :PrivateModule
+
+ class PrivateClass
+ end
+ private_constant :PrivateClass
+ end
+
+ module PrivConstModule
+ PRIVATE_CONSTANT_MODULE = true
+ private_constant :PRIVATE_CONSTANT_MODULE
+
+ def self.private_constant_from_self
+ PRIVATE_CONSTANT_MODULE
+ end
+
+ def self.defined_from_self
+ defined? PRIVATE_CONSTANT_MODULE
+ end
+
+ module Nested
+ def self.private_constant_from_scope
+ PRIVATE_CONSTANT_MODULE
+ end
+
+ def self.defined_from_scope
+ defined? PRIVATE_CONSTANT_MODULE
+ end
+ end
+ end
+
+ class PrivConstClass
+ PRIVATE_CONSTANT_CLASS = true
+ private_constant :PRIVATE_CONSTANT_CLASS
+
+ def self.private_constant_from_self
+ PRIVATE_CONSTANT_CLASS
+ end
+
+ def self.defined_from_self
+ defined? PRIVATE_CONSTANT_CLASS
+ end
+
+ module Nested
+ def self.private_constant_from_scope
+ PRIVATE_CONSTANT_CLASS
+ end
+
+ def self.defined_from_scope
+ defined? PRIVATE_CONSTANT_CLASS
+ end
+ end
+ end
+
+ class ClassIncludingPrivConstModule
+ include PrivConstModule
+
+ def private_constant_from_include
+ PRIVATE_CONSTANT_MODULE
+ end
+
+ def defined_from_include
+ defined? PRIVATE_CONSTANT_MODULE
+ end
+ end
+
+ module ModuleIncludingPrivConstModule
+ include PrivConstModule
+
+ def self.private_constant_from_include
+ PRIVATE_CONSTANT_MODULE
+ end
+
+ def self.private_constant_self_from_include
+ self::PRIVATE_CONSTANT_MODULE
+ end
+
+ def self.private_constant_named_from_include
+ PrivConstModule::PRIVATE_CONSTANT_MODULE
+ end
+ end
+
+ class PrivConstClassChild < PrivConstClass
+ def private_constant_from_subclass
+ PRIVATE_CONSTANT_CLASS
+ end
+
+ def defined_from_subclass
+ defined? PRIVATE_CONSTANT_CLASS
+ end
+ end
+
+ def self.reset_private_constants
+ Object.send :private_constant, :PRIVATE_CONSTANT_IN_OBJECT
+ end
+end
+
+class Object
+ PRIVATE_CONSTANT_IN_OBJECT = true
+ private_constant :PRIVATE_CONSTANT_IN_OBJECT
+end
diff --git a/spec/ruby/language/fixtures/constants_sclass.rb b/spec/ruby/language/fixtures/constants_sclass.rb
new file mode 100644
index 0000000000..21dc4081e2
--- /dev/null
+++ b/spec/ruby/language/fixtures/constants_sclass.rb
@@ -0,0 +1,54 @@
+module ConstantSpecs
+
+ CS_SINGLETON1 = Object.new
+ class << CS_SINGLETON1
+ CONST = 1
+ def foo
+ CONST
+ end
+ end
+
+ CS_SINGLETON2 = [Object.new, Object.new]
+ 2.times do |i|
+ obj = CS_SINGLETON2[i]
+ $spec_i = i
+ class << obj
+ CONST = ($spec_i + 1)
+ def foo
+ CONST
+ end
+ end
+ end
+
+ CS_SINGLETON3 = [Object.new, Object.new]
+ 2.times do |i|
+ obj = CS_SINGLETON3[i]
+ class << obj
+ class X
+ # creates <singleton class::X>
+ end
+
+ def x
+ X
+ end
+ end
+ end
+
+ CS_SINGLETON4 = [Object.new, Object.new]
+ CS_SINGLETON4_CLASSES = []
+ 2.times do |i|
+ obj = CS_SINGLETON4[i]
+ $spec_i = i
+ class << obj
+ class X
+ CS_SINGLETON4_CLASSES << self
+ CONST = ($spec_i + 1)
+
+ def foo
+ CONST
+ end
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/language/fixtures/def.rb b/spec/ruby/language/fixtures/def.rb
new file mode 100644
index 0000000000..e07060ed74
--- /dev/null
+++ b/spec/ruby/language/fixtures/def.rb
@@ -0,0 +1,14 @@
+def toplevel_define_other_method
+ def nested_method_in_toplevel_method
+ 42
+ end
+end
+
+def some_toplevel_method
+end
+
+public
+def public_toplevel_method
+end
+
+private
diff --git a/spec/ruby/language/fixtures/defined.rb b/spec/ruby/language/fixtures/defined.rb
new file mode 100644
index 0000000000..a9552619bf
--- /dev/null
+++ b/spec/ruby/language/fixtures/defined.rb
@@ -0,0 +1,306 @@
+module DefinedSpecs
+ self::SelfScoped = 42
+
+ def self.side_effects
+ ScratchPad.record :defined_specs_side_effects
+ end
+
+ def self.fixnum_method
+ ScratchPad.record :defined_specs_fixnum_method
+ 42
+ end
+
+ def self.exception_method
+ ScratchPad.record :defined_specs_exception
+ raise "defined? specs exception method"
+ end
+
+ def self.defined_method
+ DefinedSpecs
+ end
+
+ def self.any_args(*)
+ end
+
+ class Basic
+ A = 42
+
+ def defined_method
+ DefinedSpecs
+ end
+
+ def a_defined_method
+ end
+
+ def protected_method
+ end
+ protected :protected_method
+
+ def private_method
+ end
+ private :private_method
+
+ def private_method_defined
+ defined? private_method
+ end
+
+ def private_predicate?
+ end
+ private :private_predicate?
+
+ def private_predicate_defined
+ defined? private_predicate?
+ end
+
+ def local_variable_defined
+ x = 2
+ defined? x
+ end
+
+ def local_variable_defined_nil
+ x = nil
+ defined? x
+ end
+
+ def instance_variable_undefined
+ defined? @instance_variable_undefined
+ end
+
+ def instance_variable_read
+ value = @instance_variable_read
+ defined? @instance_variable_read
+ end
+
+ def instance_variable_defined
+ @instance_variable_defined = 1
+ defined? @instance_variable_defined
+ end
+
+ def instance_variable_defined_nil
+ @instance_variable_defined_nil = nil
+ defined? @instance_variable_defined_nil
+ end
+
+ def global_variable_undefined
+ defined? $defined_specs_global_variable_undefined
+ end
+
+ def global_variable_read
+ suppress_warning do
+ value = $defined_specs_global_variable_read
+ end
+ defined? $defined_specs_global_variable_read
+ end
+
+ def global_variable_defined
+ $defined_specs_global_variable_defined = 1
+ defined? $defined_specs_global_variable_defined
+ end
+
+ def global_variable_defined_as_nil
+ $defined_specs_global_variable_defined_as_nil = nil
+ defined? $defined_specs_global_variable_defined_as_nil
+ end
+
+ def class_variable_undefined
+ defined? @@class_variable_undefined
+ end
+
+ def class_variable_defined
+ @@class_variable_defined = 1
+ defined? @@class_variable_defined
+ end
+
+ def yield_defined_method
+ defined? yield
+ end
+
+ def yield_defined_parameter_method(&block)
+ defined? yield
+ end
+
+ def no_yield_block
+ yield_defined_method
+ end
+
+ def no_yield_block_parameter
+ yield_defined_parameter_method
+ end
+
+ def yield_block
+ yield_defined_method { 42 }
+ end
+
+ def yield_block_parameter
+ yield_defined_parameter_method { 42 }
+ end
+ end
+
+ module Mixin
+ MixinConstant = 42
+
+ def defined_super
+ defined? super()
+ end
+ end
+
+ class Parent
+ ParentConstant = 42
+
+ def defined_super; end
+ end
+
+ class Child < Parent
+ include Mixin
+
+ A = 42
+
+ def self.parent_constant_defined
+ defined? self::ParentConstant
+ end
+
+ def self.module_defined
+ defined? Mixin
+ end
+
+ def self.module_constant_defined
+ defined? MixinConstant
+ end
+
+ def defined_super
+ super
+ end
+ end
+
+ class Superclass
+ def yield_method
+ yield
+ end
+
+ def method_no_args
+ end
+
+ def method_args
+ end
+
+ def method_block_no_args
+ end
+
+ def method_block_args
+ end
+
+ def define_method_no_args
+ end
+
+ def define_method_args
+ end
+
+ def define_method_block_no_args
+ end
+
+ def define_method_block_args
+ end
+ end
+
+ class Super < Superclass
+ def no_super_method_no_args
+ defined? super
+ end
+
+ def no_super_method_args
+ defined? super()
+ end
+
+ def method_no_args
+ defined? super
+ end
+
+ def method_args
+ defined? super()
+ end
+
+ def no_super_method_block_no_args
+ yield_method { defined? super }
+ end
+
+ def no_super_method_block_args
+ yield_method { defined? super() }
+ end
+
+ def method_block_no_args
+ yield_method { defined? super }
+ end
+
+ def method_block_args
+ yield_method { defined? super() }
+ end
+
+ define_method(:no_super_define_method_no_args) { defined? super }
+ define_method(:no_super_define_method_args) { defined? super() }
+ define_method(:define_method_no_args) { defined? super }
+ define_method(:define_method_args) { defined? super() }
+
+ define_method(:no_super_define_method_block_no_args) do
+ yield_method { defined? super }
+ end
+
+ define_method(:no_super_define_method_block_args) do
+ yield_method { defined? super() }
+ end
+
+ define_method(:define_method_block_no_args) do
+ yield_method { defined? super }
+ end
+
+ define_method(:define_method_block_args) do
+ yield_method { defined? super() }
+ end
+ end
+
+ class ClassWithMethod
+ def test
+ end
+ end
+
+ class ClassUndefiningMethod < ClassWithMethod
+ undef :test
+ end
+
+ class ClassWithoutMethod < ClassUndefiningMethod
+ # If an undefined method overridden in descendants
+ # define?(super) should return nil
+ def test
+ defined?(super)
+ end
+ end
+
+ module IntermediateModule1
+ def method_no_args
+ end
+ end
+
+ module IntermediateModule2
+ def method_no_args
+ defined?(super)
+ end
+ end
+
+ class SuperWithIntermediateModules
+ include IntermediateModule1
+ include IntermediateModule2
+
+ def method_no_args
+ super
+ end
+ end
+end
+
+class Object
+ def defined_specs_method
+ DefinedSpecs
+ end
+
+ def defined_specs_receiver
+ DefinedSpecs::Basic.new
+ end
+end
diff --git a/spec/ruby/language/fixtures/delegation.rb b/spec/ruby/language/fixtures/delegation.rb
new file mode 100644
index 0000000000..527d928390
--- /dev/null
+++ b/spec/ruby/language/fixtures/delegation.rb
@@ -0,0 +1,11 @@
+module DelegationSpecs
+ class Target
+ def target(*args, **kwargs)
+ [args, kwargs]
+ end
+
+ def target_block(*args, **kwargs)
+ yield [kwargs, args]
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/dollar_zero.rb b/spec/ruby/language/fixtures/dollar_zero.rb
new file mode 100644
index 0000000000..683bce8d4e
--- /dev/null
+++ b/spec/ruby/language/fixtures/dollar_zero.rb
@@ -0,0 +1,6 @@
+puts $0
+puts __FILE__
+
+if $0 == __FILE__
+ print "OK"
+end
diff --git a/spec/ruby/language/fixtures/emacs_magic_comment.rb b/spec/ruby/language/fixtures/emacs_magic_comment.rb
new file mode 100644
index 0000000000..2b09f3e74c
--- /dev/null
+++ b/spec/ruby/language/fixtures/emacs_magic_comment.rb
@@ -0,0 +1,2 @@
+# -*- encoding: big5 -*-
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/ensure.rb b/spec/ruby/language/fixtures/ensure.rb
new file mode 100644
index 0000000000..6047ac5bc0
--- /dev/null
+++ b/spec/ruby/language/fixtures/ensure.rb
@@ -0,0 +1,121 @@
+module EnsureSpec
+ class Container
+ attr_reader :executed
+
+ def initialize
+ @executed = []
+ end
+
+ def raise_in_method_with_ensure
+ @executed << :method
+ raise EnsureSpec::Error
+ ensure
+ @executed << :ensure
+ end
+
+ def raise_and_rescue_in_method_with_ensure
+ @executed << :method
+ raise "An Exception"
+ rescue
+ @executed << :rescue
+ ensure
+ @executed << :ensure
+ end
+
+ def throw_in_method_with_ensure
+ @executed << :method
+ throw(:symbol)
+ ensure
+ @executed << :ensure
+ end
+
+ def implicit_return_in_method_with_ensure
+ :method
+ ensure
+ :ensure
+ end
+
+ def explicit_return_in_method_with_ensure
+ return :method
+ ensure
+ return :ensure
+ end
+
+ def explicit_return_in_rescue_and_explicit_return_in_ensure
+ raise
+ rescue
+ return 2
+ ensure
+ return "returned in ensure"
+ end
+
+ def explicit_return_in_rescue_and_implicit_return_in_ensure
+ raise
+ rescue
+ return "returned in rescue"
+ ensure
+ 3
+ end
+
+ def raise_and_explicit_return_in_ensure
+ raise
+ ensure
+ return "returned in ensure"
+ end
+
+ def raise_in_rescue_and_explicit_return_in_ensure
+ raise
+ rescue
+ raise
+ ensure
+ return "returned in ensure"
+ end
+
+ def raise_in_rescue_and_raise_in_ensure
+ raise
+ rescue
+ raise "raised in rescue"
+ ensure
+ raise "raised in ensure"
+ end
+
+ def raise_in_method_and_raise_in_ensure
+ raise
+ ensure
+ raise "raised in ensure"
+ end
+ end
+end
+
+module EnsureSpec
+
+ class Test
+
+ def initialize
+ @values = []
+ end
+
+ attr_reader :values
+
+ def call_block
+ begin
+ @values << :start
+ yield
+ ensure
+ @values << :end
+ end
+ end
+
+ def do_test
+ call_block do
+ @values << :in_block
+ return :did_test
+ end
+ end
+ end
+end
+
+module EnsureSpec
+ class Error < RuntimeError
+ end
+end
diff --git a/spec/ruby/language/fixtures/file.rb b/spec/ruby/language/fixtures/file.rb
new file mode 100644
index 0000000000..7b862cfe1a
--- /dev/null
+++ b/spec/ruby/language/fixtures/file.rb
@@ -0,0 +1 @@
+ScratchPad.record __FILE__
diff --git a/spec/ruby/language/fixtures/for_scope.rb b/spec/ruby/language/fixtures/for_scope.rb
new file mode 100644
index 0000000000..9c44a23a2c
--- /dev/null
+++ b/spec/ruby/language/fixtures/for_scope.rb
@@ -0,0 +1,15 @@
+module ForSpecs
+ class ForInClassMethod
+ m = :same_variable_set_outside
+
+ def self.foo
+ all = []
+ for m in [:bar, :baz]
+ all << m
+ end
+ all
+ end
+
+ READER = -> { m }
+ end
+end
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb b/spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb
new file mode 100644
index 0000000000..3aed2f29b6
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require_relative 'freeze_magic_comment_required'
+
+p "abc".object_id == $second_literal_id
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb
new file mode 100644
index 0000000000..53ef959970
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require_relative 'freeze_magic_comment_required_diff_enc'
+
+p "abc".object_id != $second_literal_id
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb
new file mode 100644
index 0000000000..fc6cd5bf82
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require_relative 'freeze_magic_comment_required_no_comment'
+
+p "abc".object_id != $second_literal_id
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb b/spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb
new file mode 100644
index 0000000000..d35905b332
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+ids = Array.new(2) { "abc".object_id }
+p ids.first == ids.last
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_required.rb b/spec/ruby/language/fixtures/freeze_magic_comment_required.rb
new file mode 100644
index 0000000000..a4ff4459b1
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_required.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+$second_literal_id = "abc".object_id
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb b/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb
new file mode 100644
index 0000000000..d0558a2251
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb
Binary files differ
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb b/spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb
new file mode 100644
index 0000000000..e09232a5f4
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb
@@ -0,0 +1 @@
+$second_literal_id = "abc".object_id
diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb
new file mode 100644
index 0000000000..cccc5969bd
--- /dev/null
+++ b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+p "abc".equal?("abc")
diff --git a/spec/ruby/language/fixtures/hash_strings_binary.rb b/spec/ruby/language/fixtures/hash_strings_binary.rb
new file mode 100644
index 0000000000..44b99cbf80
--- /dev/null
+++ b/spec/ruby/language/fixtures/hash_strings_binary.rb
@@ -0,0 +1,7 @@
+# encoding: binary
+
+module HashStringsBinary
+ def self.literal_hash
+ {"foo" => "bar"}
+ end
+end
diff --git a/spec/ruby/language/fixtures/hash_strings_usascii.rb b/spec/ruby/language/fixtures/hash_strings_usascii.rb
new file mode 100644
index 0000000000..18cfef7c8c
--- /dev/null
+++ b/spec/ruby/language/fixtures/hash_strings_usascii.rb
@@ -0,0 +1,7 @@
+# encoding: us-ascii
+
+module HashStringsUSASCII
+ def self.literal_hash
+ {"foo" => "bar"}
+ end
+end
diff --git a/spec/ruby/language/fixtures/hash_strings_utf8.rb b/spec/ruby/language/fixtures/hash_strings_utf8.rb
new file mode 100644
index 0000000000..7928090282
--- /dev/null
+++ b/spec/ruby/language/fixtures/hash_strings_utf8.rb
@@ -0,0 +1,7 @@
+# encoding: utf-8
+
+module HashStringsUTF8
+ def self.literal_hash
+ {"foo" => "bar"}
+ end
+end
diff --git a/spec/ruby/language/fixtures/magic_comment.rb b/spec/ruby/language/fixtures/magic_comment.rb
new file mode 100644
index 0000000000..120ef6ff4a
--- /dev/null
+++ b/spec/ruby/language/fixtures/magic_comment.rb
@@ -0,0 +1,2 @@
+# encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/match_operators.rb b/spec/ruby/language/fixtures/match_operators.rb
new file mode 100644
index 0000000000..f04c54d723
--- /dev/null
+++ b/spec/ruby/language/fixtures/match_operators.rb
@@ -0,0 +1,9 @@
+class OperatorImplementor
+ def =~(val)
+ return val
+ end
+
+ def !~(val)
+ return val
+ end
+end
diff --git a/spec/ruby/language/fixtures/metaclass.rb b/spec/ruby/language/fixtures/metaclass.rb
new file mode 100644
index 0000000000..a8f837e701
--- /dev/null
+++ b/spec/ruby/language/fixtures/metaclass.rb
@@ -0,0 +1,33 @@
+module MetaClassSpecs
+
+ def self.metaclass_of obj
+ class << obj
+ self
+ end
+ end
+
+ class A
+ def self.cheese
+ 'edam'
+ end
+ end
+
+ class B < A
+ def self.cheese
+ 'stilton'
+ end
+ end
+
+ class C
+ class << self
+ class << self
+ def ham
+ 'iberico'
+ end
+ end
+ end
+ end
+
+ class D < C; end
+
+end
diff --git a/spec/ruby/language/fixtures/module.rb b/spec/ruby/language/fixtures/module.rb
new file mode 100644
index 0000000000..33d323846e
--- /dev/null
+++ b/spec/ruby/language/fixtures/module.rb
@@ -0,0 +1,24 @@
+module ModuleSpecs
+ module Modules
+ class Klass
+ end
+
+ A = "Module"
+ B = 1
+ C = nil
+ D = true
+ E = false
+ end
+
+ module Anonymous
+ end
+
+ module IncludedInObject
+ module IncludedModuleSpecs
+ end
+ end
+end
+
+class Object
+ include ModuleSpecs::IncludedInObject
+end
diff --git a/spec/ruby/language/fixtures/next.rb b/spec/ruby/language/fixtures/next.rb
new file mode 100644
index 0000000000..fbca842334
--- /dev/null
+++ b/spec/ruby/language/fixtures/next.rb
@@ -0,0 +1,134 @@
+class NextSpecs
+ def self.yielding_method(expected)
+ yield.should == expected
+ :method_return_value
+ end
+
+ def self.yielding
+ yield
+ end
+
+ # The methods below are defined to spec the behavior of the next statement
+ # while specifically isolating whether the statement is in an Iter block or
+ # not. In a normal spec example, the code is always nested inside a block.
+ # Rather than rely on that implicit context in this case, the context is
+ # made explicit because of the interaction of next in a loop nested inside
+ # an Iter block.
+ def self.while_next(arg)
+ x = true
+ while x
+ begin
+ ScratchPad << :begin
+ x = false
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+
+ def self.while_within_iter(arg)
+ yielding do
+ x = true
+ while x
+ begin
+ ScratchPad << :begin
+ x = false
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+ end
+
+ def self.until_next(arg)
+ x = false
+ until x
+ begin
+ ScratchPad << :begin
+ x = true
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+
+ def self.until_within_iter(arg)
+ yielding do
+ x = false
+ until x
+ begin
+ ScratchPad << :begin
+ x = true
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+ end
+
+ def self.loop_next(arg)
+ x = 1
+ loop do
+ break if x == 2
+
+ begin
+ ScratchPad << :begin
+ x += 1
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+
+ def self.loop_within_iter(arg)
+ yielding do
+ x = 1
+ loop do
+ break if x == 2
+
+ begin
+ ScratchPad << :begin
+ x += 1
+ if arg
+ next 42
+ else
+ next
+ end
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+ end
+ end
+
+ class Block
+ def method(v)
+ yield v
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/no_magic_comment.rb b/spec/ruby/language/fixtures/no_magic_comment.rb
new file mode 100644
index 0000000000..743a0f9503
--- /dev/null
+++ b/spec/ruby/language/fixtures/no_magic_comment.rb
@@ -0,0 +1 @@
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/precedence.rb b/spec/ruby/language/fixtures/precedence.rb
new file mode 100644
index 0000000000..d2295c755b
--- /dev/null
+++ b/spec/ruby/language/fixtures/precedence.rb
@@ -0,0 +1,16 @@
+module PrecedenceSpecs
+ class NonUnaryOpTest
+ def add_num(arg)
+ [1].collect { |i| arg + i +1 }
+ end
+ def sub_num(arg)
+ [1].collect { |i| arg + i -1 }
+ end
+ def add_str
+ %w[1].collect { |i| i +'1' }
+ end
+ def add_var
+ [1].collect { |i| i +i }
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb b/spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb
new file mode 100644
index 0000000000..aa82cf4471
--- /dev/null
+++ b/spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb
@@ -0,0 +1,3 @@
+at_exit {
+ print $magic_comment_result
+}
diff --git a/spec/ruby/language/fixtures/private.rb b/spec/ruby/language/fixtures/private.rb
new file mode 100644
index 0000000000..96f73cea3f
--- /dev/null
+++ b/spec/ruby/language/fixtures/private.rb
@@ -0,0 +1,59 @@
+module Private
+ class A
+ def foo
+ "foo"
+ end
+
+ private
+ def bar
+ "bar"
+ end
+ end
+
+ class B
+ def foo
+ "foo"
+ end
+
+ private
+
+ def self.public_defs_method; 0; end
+
+ class C
+ def baz
+ "baz"
+ end
+ end
+
+ class << self
+ def public_class_method1; 1; end
+ private
+ def private_class_method1; 1; end
+ end
+
+ def bar
+ "bar"
+ end
+ end
+
+ module D
+ private
+ def foo
+ "foo"
+ end
+ end
+
+ class E
+ include D
+ end
+
+ class G
+ def foo
+ "foo"
+ end
+ end
+
+ class H < A
+ private :foo
+ end
+end
diff --git a/spec/ruby/language/fixtures/rescue.rb b/spec/ruby/language/fixtures/rescue.rb
new file mode 100644
index 0000000000..b906e17a2f
--- /dev/null
+++ b/spec/ruby/language/fixtures/rescue.rb
@@ -0,0 +1,67 @@
+module RescueSpecs
+ def self.begin_else(raise_exception)
+ begin
+ ScratchPad << :one
+ raise "an error occurred" if raise_exception
+ rescue
+ ScratchPad << :rescue_ran
+ :rescue_val
+ else
+ ScratchPad << :else_ran
+ :val
+ end
+ end
+
+ def self.begin_else_ensure(raise_exception)
+ begin
+ ScratchPad << :one
+ raise "an error occurred" if raise_exception
+ rescue
+ ScratchPad << :rescue_ran
+ :rescue_val
+ else
+ ScratchPad << :else_ran
+ :val
+ ensure
+ ScratchPad << :ensure_ran
+ :ensure_val
+ end
+ end
+
+ def self.begin_else_return(raise_exception)
+ begin
+ ScratchPad << :one
+ raise "an error occurred" if raise_exception
+ rescue
+ ScratchPad << :rescue_ran
+ :rescue_val
+ else
+ ScratchPad << :else_ran
+ :val
+ end
+ ScratchPad << :outside_begin
+ :return_val
+ end
+
+ def self.begin_else_return_ensure(raise_exception)
+ begin
+ ScratchPad << :one
+ raise "an error occurred" if raise_exception
+ rescue
+ ScratchPad << :rescue_ran
+ :rescue_val
+ else
+ ScratchPad << :else_ran
+ :val
+ ensure
+ ScratchPad << :ensure_ran
+ :ensure_val
+ end
+ ScratchPad << :outside_begin
+ :return_val
+ end
+
+ def self.raise_standard_error
+ raise StandardError, "an error occurred"
+ end
+end
diff --git a/spec/ruby/language/fixtures/rescue_captures.rb b/spec/ruby/language/fixtures/rescue_captures.rb
new file mode 100644
index 0000000000..69f9b83904
--- /dev/null
+++ b/spec/ruby/language/fixtures/rescue_captures.rb
@@ -0,0 +1,107 @@
+module RescueSpecs
+ class Captor
+ attr_accessor :captured_error
+
+ def self.should_capture_exception
+ captor = new
+ captor.capture('some text').should == :caught # Ensure rescue body still runs
+ captor.captured_error.message.should == 'some text'
+ end
+ end
+
+ class ClassVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => @@captured_error
+ :caught
+ end
+
+ def captured_error
+ self.class.remove_class_variable(:@@captured_error)
+ end
+ end
+
+ class ConstantCaptor < Captor
+ # Using lambda gets around the dynamic constant assignment warning
+ CAPTURE = -> msg {
+ begin
+ raise msg
+ rescue => CapturedError
+ :caught
+ end
+ }
+
+ def capture(msg)
+ CAPTURE.call(msg)
+ end
+
+ def captured_error
+ self.class.send(:remove_const, :CapturedError)
+ end
+ end
+
+ class GlobalVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => $captured_error
+ :caught
+ end
+
+ def captured_error
+ $captured_error.tap do
+ $captured_error = nil # Can't remove globals, only nil them out
+ end
+ end
+ end
+
+ class InstanceVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => @captured_error
+ :caught
+ end
+ end
+
+ class LocalVariableCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => captured_error
+ @captured_error = captured_error
+ :caught
+ end
+ end
+
+ class SafeNavigationSetterCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => self&.captured_error
+ :caught
+ end
+ end
+
+ class SetterCaptor < Captor
+ def capture(msg)
+ raise msg
+ rescue => self.captured_error
+ :caught
+ end
+ end
+
+ class SquareBracketsCaptor < Captor
+ def capture(msg)
+ @hash = {}
+
+ raise msg
+ rescue => self[:error]
+ :caught
+ end
+
+ def []=(key, value)
+ @hash[key] = value
+ end
+
+ def captured_error
+ @hash[:error]
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/return.rb b/spec/ruby/language/fixtures/return.rb
new file mode 100644
index 0000000000..f6b143f3fa
--- /dev/null
+++ b/spec/ruby/language/fixtures/return.rb
@@ -0,0 +1,135 @@
+module ReturnSpecs
+ class Blocks
+ def yielding_method
+ yield
+ ScratchPad.record :after_yield
+ end
+
+ def enclosing_method
+ yielding_method do
+ ScratchPad.record :before_return
+ return :return_value
+ ScratchPad.record :after_return
+ end
+
+ ScratchPad.record :after_call
+ end
+ end
+
+ class NestedCalls < Blocks
+ def invoking_method(&b)
+ yielding_method(&b)
+ ScratchPad.record :after_invoke
+ end
+
+ def enclosing_method
+ invoking_method do
+ ScratchPad.record :before_return
+ return :return_value
+ ScratchPad.record :after_return
+ end
+ ScratchPad.record :after_invoke
+ end
+ end
+
+ class NestedBlocks < Blocks
+ def enclosing_method
+ yielding_method do
+ yielding_method do
+ ScratchPad.record :before_return
+ return :return_value
+ ScratchPad.record :after_return
+ end
+ ScratchPad.record :after_invoke1
+ end
+ ScratchPad.record :after_invoke2
+ end
+ end
+
+ class SavedInnerBlock
+ def add(&b)
+ @block = b
+ end
+
+ def outer
+ yield
+ @block.call
+ end
+
+ def inner
+ yield
+ end
+
+ def start
+ outer do
+ inner do
+ add do
+ ScratchPad.record :before_return
+ return :return_value
+ end
+ end
+ end
+
+ ScratchPad.record :bottom_of_start
+
+ return false
+ end
+ end
+
+ class ThroughDefineMethod
+ lamb = proc { |x| x.call }
+ define_method :foo, lamb
+
+ def mp(&b); b; end
+
+ def outer
+ pr = mp { return :good }
+
+ foo(pr)
+ return :bad
+ end
+ end
+
+ class DefineMethod
+ lamb = proc { return :good }
+ define_method :foo, lamb
+
+ def outer
+ val = :bad
+
+ # This is tricky, but works. If lamb properly returns, then the
+ # return value will go into val before we run the ensure.
+ #
+ # If lamb's return keeps unwinding incorrectly, val will still
+ # have its old value.
+ #
+ # We can therefore use val to figure out what happened.
+ begin
+ val = foo()
+ ensure
+ return val
+ end
+ end
+ end
+
+ class MethodWithBlock
+ def method1
+ return [2, 3].inject 0 do |a, b|
+ a + b
+ end
+ nil
+ end
+
+ def get_ary(count, &blk)
+ count.times.to_a do |i|
+ blk.call(i) if blk
+ end
+ end
+
+ def method2
+ return get_ary 3 do |i|
+ end
+ nil
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/second_line_magic_comment.rb b/spec/ruby/language/fixtures/second_line_magic_comment.rb
new file mode 100644
index 0000000000..a3dd50393b
--- /dev/null
+++ b/spec/ruby/language/fixtures/second_line_magic_comment.rb
@@ -0,0 +1,3 @@
+
+# encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/second_token_magic_comment.rb b/spec/ruby/language/fixtures/second_token_magic_comment.rb
new file mode 100644
index 0000000000..8d443e68f3
--- /dev/null
+++ b/spec/ruby/language/fixtures/second_token_magic_comment.rb
@@ -0,0 +1,2 @@
+1 + 1 # encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/send.rb b/spec/ruby/language/fixtures/send.rb
new file mode 100644
index 0000000000..918241e171
--- /dev/null
+++ b/spec/ruby/language/fixtures/send.rb
@@ -0,0 +1,141 @@
+module LangSendSpecs
+ module_function
+
+ def fooM0; 100 end
+ def fooM1(a); [a]; end
+ def fooM2(a,b); [a,b]; end
+ def fooM3(a,b,c); [a,b,c]; end
+ def fooM4(a,b,c,d); [a,b,c,d]; end
+ def fooM5(a,b,c,d,e); [a,b,c,d,e]; end
+ def fooM0O1(a=1); [a]; end
+ def fooM1O1(a,b=1); [a,b]; end
+ def fooM2O1(a,b,c=1); [a,b,c]; end
+ def fooM3O1(a,b,c,d=1); [a,b,c,d]; end
+ def fooM4O1(a,b,c,d,e=1); [a,b,c,d,e]; end
+ def fooM0O2(a=1,b=2); [a,b]; end
+ def fooM0R(*r); r; end
+ def fooM1R(a, *r); [a, r]; end
+ def fooM0O1R(a=1, *r); [a, r]; end
+ def fooM1O1R(a, b=1, *r); [a, b, r]; end
+
+ def one(a); a; end
+ def oneb(a,&b); [a,yield(b)]; end
+ def twob(a,b,&c); [a,b,yield(c)]; end
+ def makeproc(&b) b end
+
+ def yield_now; yield; end
+
+ def double(x); x * 2 end
+ def weird_parens
+ # means double((5).to_s)
+ # NOT (double(5)).to_s
+ double (5).to_s
+ end
+
+ def rest_len(*a); a.size; end
+
+ def self.twos(a,b,*c)
+ [c.size, c.last]
+ end
+
+ class PrivateSetter
+ attr_reader :foo
+ attr_writer :foo
+ private :foo=
+
+ def call_self_foo_equals(value)
+ self.foo = value
+ end
+
+ def call_self_foo_equals_masgn(value)
+ a, self.foo = 1, value
+ end
+ end
+
+ class PrivateGetter
+ attr_accessor :foo
+ private :foo
+ private :foo=
+
+ def call_self_foo
+ self.foo
+ end
+
+ def call_self_foo_or_equals(value)
+ self.foo ||= 6
+ end
+ end
+
+ class AttrSet
+ attr_reader :result
+ def []=(a, b, c, d); @result = [a,b,c,d]; end
+ end
+
+ class ToProc
+ def initialize(val)
+ @val = val
+ end
+
+ def to_proc
+ Proc.new { @val }
+ end
+ end
+
+ class ToAry
+ def initialize(obj)
+ @obj = obj
+ end
+
+ def to_ary
+ @obj
+ end
+ end
+
+ class MethodMissing
+ def initialize
+ @message = nil
+ @args = nil
+ end
+
+ attr_reader :message, :args
+
+ def method_missing(m, *a)
+ @message = m
+ @args = a
+ end
+ end
+
+ class Attr19Set
+ attr_reader :result
+ def []=(*args); @result = args; end
+ end
+
+ module_function
+
+ def fooR(*r); r; end
+ def fooM0RQ1(*r, q); [r, q]; end
+ def fooM0RQ2(*r, s, q); [r, s, q]; end
+ def fooM1RQ1(a, *r, q); [a, r, q]; end
+ def fooM1O1RQ1(a, b=9, *r, q); [a, b, r, q]; end
+ def fooM1O1RQ2(a, b=9, *r, q, t); [a, b, r, q, t]; end
+
+ def fooO1Q1(a=1, b); [a,b]; end
+ def fooM1O1Q1(a,b=2,c); [a,b,c]; end
+ def fooM2O1Q1(a,b,c=3,d); [a,b,c,d]; end
+ def fooM2O2Q1(a,b,c=3,d=4,e); [a,b,c,d,e]; end
+ def fooO4Q1(a=1,b=2,c=3,d=4,e); [a,b,c,d,e]; end
+ def fooO4Q2(a=1,b=2,c=3,d=4,e,f); [a,b,c,d,e,f]; end
+
+ def destructure2((a,b)); a+b; end
+ def destructure2b((a,b)); [a,b]; end
+ def destructure4r((a,b,*c,d,e)); [a,b,c,d,e]; end
+ def destructure4o(a=1,(b,c),d,&e); [a,b,c,d]; end
+ def destructure5o(a=1, f=2, (b,c),d,&e); [a,f,b,c,d]; end
+ def destructure7o(a=1, f=2, (b,c),(d,e), &g); [a,f,b,c,d,e]; end
+ def destructure7b(a=1, f=2, (b,c),(d,e), &g); g.call([a,f,b,c,d,e]); end
+ def destructure4os(a=1,(b,*c)); [a,b,c]; end
+end
+
+def lang_send_rest_len(*a)
+ a.size
+end
diff --git a/spec/ruby/language/fixtures/shebang_magic_comment.rb b/spec/ruby/language/fixtures/shebang_magic_comment.rb
new file mode 100755
index 0000000000..f8e5e7d8e4
--- /dev/null
+++ b/spec/ruby/language/fixtures/shebang_magic_comment.rb
@@ -0,0 +1,3 @@
+#!/usr/bin/ruby
+# encoding: big5
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/squiggly_heredoc.rb b/spec/ruby/language/fixtures/squiggly_heredoc.rb
new file mode 100644
index 0000000000..984a629e5b
--- /dev/null
+++ b/spec/ruby/language/fixtures/squiggly_heredoc.rb
@@ -0,0 +1,71 @@
+module SquigglyHeredocSpecs
+ def self.message
+ <<~HEREDOC
+ character density, n.:
+ The number of very weird people in the office.
+ HEREDOC
+ end
+
+ def self.blank
+ <<~HERE
+ HERE
+ end
+
+ def self.unquoted
+ <<~HERE
+ unquoted #{"interpolated"}
+ HERE
+ end
+
+ def self.doublequoted
+ <<~"HERE"
+ doublequoted #{"interpolated"}
+ HERE
+ end
+
+ def self.singlequoted
+ <<~'HERE'
+ singlequoted #{"interpolated"}
+ HERE
+ end
+
+ def self.backslash
+ <<~HERE
+ a
+ b\
+ c
+ HERE
+ end
+
+ def self.least_indented_on_the_first_line
+ <<~HERE
+ a
+ b
+ c
+ HERE
+ end
+
+ def self.least_indented_on_the_last_line
+ <<~HERE
+ a
+ b
+ c
+ HERE
+ end
+
+ def self.least_indented_on_the_first_line_single
+ <<~'HERE'
+ a
+ b
+ c
+ HERE
+ end
+
+ def self.least_indented_on_the_last_line_single
+ <<~'HERE'
+ a
+ b
+ c
+ HERE
+ end
+end
diff --git a/spec/ruby/language/fixtures/super.rb b/spec/ruby/language/fixtures/super.rb
new file mode 100644
index 0000000000..218f1e4970
--- /dev/null
+++ b/spec/ruby/language/fixtures/super.rb
@@ -0,0 +1,742 @@
+module SuperSpecs
+ module S1
+ class A
+ def foo(a)
+ a << "A#foo"
+ bar(a)
+ end
+ def bar(a)
+ a << "A#bar"
+ end
+ end
+ class B < A
+ def foo(a)
+ a << "B#foo"
+ super(a)
+ end
+ def bar(a)
+ a << "B#bar"
+ super(a)
+ end
+ end
+ end
+
+ module S2
+ class A
+ def baz(a)
+ a << "A#baz"
+ end
+ end
+ class B < A
+ def foo(a)
+ a << "B#foo"
+ baz(a)
+ end
+ end
+ class C < B
+ def baz(a)
+ a << "C#baz"
+ super(a)
+ end
+ end
+ end
+
+ module S3
+ class A
+ def foo(a)
+ a << "A#foo"
+ end
+ def self.foo(a)
+ a << "A.foo"
+ end
+ def self.bar(a)
+ a << "A.bar"
+ foo(a)
+ end
+ end
+ class B < A
+ def self.foo(a)
+ a << "B.foo"
+ super(a)
+ end
+ def self.bar(a)
+ a << "B.bar"
+ super(a)
+ end
+ end
+ end
+
+ module S4
+ class A
+ def foo(a)
+ a << "A#foo"
+ end
+ end
+ class B < A
+ def foo(a, b)
+ a << "B#foo(a,#{b})"
+ super(a)
+ end
+ end
+ end
+
+ class S5
+ def here
+ :good
+ end
+ end
+
+ class S6 < S5
+ def under
+ yield
+ end
+
+ def here
+ under {
+ super
+ }
+ end
+ end
+
+ class S7 < S5
+ define_method(:here) { super() }
+ end
+
+ module MS1
+ module ModA
+ def foo(a)
+ a << "ModA#foo"
+ bar(a)
+ end
+ def bar(a)
+ a << "ModA#bar"
+ end
+ end
+ class A
+ include ModA
+ end
+ module ModB
+ def bar(a)
+ a << "ModB#bar"
+ super(a)
+ end
+ end
+ class B < A
+ def foo(a)
+ a << "B#foo"
+ super(a)
+ end
+ include ModB
+ end
+ end
+
+ module MS2
+ class A
+ def baz(a)
+ a << "A#baz"
+ end
+ end
+ module ModB
+ def foo(a)
+ a << "ModB#foo"
+ baz(a)
+ end
+ end
+ class B < A
+ include ModB
+ end
+ class C < B
+ def baz(a)
+ a << "C#baz"
+ super(a)
+ end
+ end
+ end
+
+ module MultiSuperTargets
+ module M
+ def foo
+ super
+ end
+ end
+
+ class BaseA
+ def foo
+ :BaseA
+ end
+ end
+
+ class BaseB
+ def foo
+ :BaseB
+ end
+ end
+
+ class A < BaseA
+ include M
+ end
+
+ class B < BaseB
+ include M
+ end
+ end
+
+ module MS3
+ module ModA
+ def foo(a)
+ a << "ModA#foo"
+ end
+ def bar(a)
+ a << "ModA#bar"
+ foo(a)
+ end
+ end
+ class A
+ def foo(a)
+ a << "A#foo"
+ end
+ class << self
+ include ModA
+ end
+ end
+ class B < A
+ def self.foo(a)
+ a << "B.foo"
+ super(a)
+ end
+ def self.bar(a)
+ a << "B.bar"
+ super(a)
+ end
+ end
+ end
+
+ module MS4
+ module Layer1
+ def example
+ 5
+ end
+ end
+
+ module Layer2
+ include Layer1
+ def example
+ super
+ end
+ end
+
+ class A
+ include Layer2
+ public :example
+ end
+ end
+
+ class MM_A
+ undef_method :is_a?
+ end
+
+ class MM_B < MM_A
+ def is_a?(blah)
+ # should fire the method_missing below
+ super
+ end
+
+ def method_missing(*)
+ false
+ end
+ end
+
+ class Alias1
+ def name
+ [:alias1]
+ end
+ end
+
+ class Alias2 < Alias1
+ def initialize
+ @times = 0
+ end
+
+ def name
+ if @times >= 10
+ raise "runaway super"
+ end
+
+ @times += 1
+
+ # Use this so that we can see collect all supers that we see.
+ # One bug that arises is that we call Alias2#name from Alias2#name
+ # as it's superclass. In that case, either we get a runaway recursion
+ # super OR we get the return value being [:alias2, :alias2, :alias1]
+ # rather than [:alias2, :alias1].
+ #
+ # Which one depends on caches and how super is implemented.
+ [:alias2] + super
+ end
+ end
+
+ class Alias3 < Alias2
+ alias_method :name3, :name
+ # In the method table for Alias3 now should be a special alias entry
+ # that references Alias2 and Alias2#name (probably as an object).
+ #
+ # When name3 is called then, Alias2 (NOT Alias3) is presented as the
+ # current module to Alias2#name, so that when super is called,
+ # Alias2's superclass is next.
+ #
+ # Otherwise, Alias2 is next, which is where name was to begin with,
+ # causing the wrong #name method to be called.
+ end
+
+ module AliasWithSuper
+ module AS1
+ def foo
+ :a
+ end
+ end
+
+ module BS1
+ def foo
+ [:b, super]
+ end
+ end
+
+ class Base
+ extend AS1
+ extend BS1
+ end
+
+ class Trigger < Base
+ class << self
+ def foo_quux
+ foo_baz
+ end
+
+ alias_method :foo_baz, :foo
+ alias_method :foo, :foo_quux
+ end
+ end
+ end
+
+ module RestArgsWithSuper
+ class A
+ def a(*args)
+ args
+ end
+ end
+
+ class B < A
+ def a(*args)
+ args << "foo"
+
+ super
+ end
+ end
+ end
+
+ class AnonymousModuleIncludedTwiceBase
+ def self.whatever
+ mod = Module.new do
+ def a(array)
+ array << "anon"
+ super
+ end
+ end
+
+ include mod
+ end
+
+ def a(array)
+ array << "non-anon"
+ end
+ end
+
+ class AnonymousModuleIncludedTwice < AnonymousModuleIncludedTwiceBase
+ whatever
+ whatever
+ end
+
+ module ZSuperWithBlock
+ class A
+ def a
+ yield
+ end
+
+ def b(&block)
+ block.call
+ end
+
+ def c
+ yield
+ end
+ end
+
+ class B < A
+ def a
+ super { 14 }
+ end
+
+ def b
+ block_ref = -> { 15 }
+ [super { 14 }, super(&block_ref)]
+ end
+
+ def c
+ block_ref = -> { 16 }
+ super(&block_ref)
+ end
+ end
+ end
+
+ module ZSuperWithOptional
+ class A
+ def m(x, y, z)
+ z
+ end
+ end
+
+ class B < A
+ def m(x, y, z = 14)
+ super
+ end
+ end
+
+ class C < A
+ def m(x, y, z = 14)
+ z = 100
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRest
+ class A
+ def m(*args)
+ args
+ end
+
+ def m_modified(*args)
+ args
+ end
+ end
+
+ class B < A
+ def m(*args)
+ super
+ end
+
+ def m_modified(*args)
+ args[1] = 14
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestAndOthers
+ class A
+ def m(a, b, *args)
+ args
+ end
+
+ def m_modified(a, b, *args)
+ args
+ end
+ end
+
+ class B < A
+ def m(a, b, *args)
+ super
+ end
+
+ def m_modified(a, b, *args)
+ args[1] = 14
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestAndPost
+ class A
+ def m(*args, a, b)
+ args
+ end
+
+ def m_modified(*args, a, b)
+ args
+ end
+ end
+
+ class B < A
+ def m(*args, a, b)
+ super
+ end
+
+ def m_modified(*args, a, b)
+ args[1] = 14
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestOthersAndPost
+ class A
+ def m(a, *args, b)
+ args
+ end
+
+ def m_modified(a, *args, b)
+ args
+ end
+ end
+
+ class B < A
+ def m(a, *args, b)
+ super
+ end
+
+ def m_modified(a, *args, b)
+ args[1] = 14
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestReassigned
+ class A
+ def a(*args)
+ args
+ end
+ end
+
+ class B < A
+ def a(*args)
+ args = ["foo"]
+
+ super
+ end
+ end
+ end
+
+ module ZSuperWithRestReassignedWithScalar
+ class A
+ def a(*args)
+ args
+ end
+ end
+
+ class B < A
+ def a(*args)
+ args = "foo"
+
+ super
+ end
+ end
+ end
+
+ module ZSuperWithUnderscores
+ class A
+ def m(*args)
+ args
+ end
+
+ def m_modified(*args)
+ args
+ end
+ end
+
+ class B < A
+ def m(_, _)
+ super
+ end
+
+ def m_modified(_, _)
+ _ = 14
+ super
+ end
+ end
+ end
+
+ module Keywords
+ class Arguments
+ def foo(**args)
+ args
+ end
+ end
+
+ # ----
+
+ class RequiredArguments < Arguments
+ def foo(a:)
+ super
+ end
+ end
+
+ class OptionalArguments < Arguments
+ def foo(b: 'b')
+ super
+ end
+ end
+
+ class PlaceholderArguments < Arguments
+ def foo(**args)
+ super
+ end
+ end
+
+ # ----
+
+ class RequiredAndOptionalArguments < Arguments
+ def foo(a:, b: 'b')
+ super
+ end
+ end
+
+ class RequiredAndPlaceholderArguments < Arguments
+ def foo(a:, **args)
+ super
+ end
+ end
+
+ class OptionalAndPlaceholderArguments < Arguments
+ def foo(b: 'b', **args)
+ super
+ end
+ end
+
+ # ----
+
+ class RequiredAndOptionalAndPlaceholderArguments < Arguments
+ def foo(a:, b: 'b', **args)
+ super
+ end
+ end
+ end
+
+ module RegularAndKeywords
+ class Arguments
+ def foo(a, **options)
+ [a, options]
+ end
+ end
+
+ # -----
+
+ class RequiredArguments < Arguments
+ def foo(a, b:)
+ super
+ end
+ end
+
+ class OptionalArguments < Arguments
+ def foo(a, c: 'c')
+ super
+ end
+ end
+
+ class PlaceholderArguments < Arguments
+ def foo(a, **options)
+ super
+ end
+ end
+
+ # -----
+
+ class RequiredAndOptionalArguments < Arguments
+ def foo(a, b:, c: 'c')
+ super
+ end
+ end
+
+ class RequiredAndPlaceholderArguments < Arguments
+ def foo(a, b:, **options)
+ super
+ end
+ end
+
+ class OptionalAndPlaceholderArguments < Arguments
+ def foo(a, c: 'c', **options)
+ super
+ end
+ end
+
+ # -----
+
+ class RequiredAndOptionalAndPlaceholderArguments < Arguments
+ def foo(a, b:, c: 'c', **options)
+ super
+ end
+ end
+ end
+
+ module SplatAndKeywords
+ class Arguments
+ def foo(*args, **options)
+ [args, options]
+ end
+ end
+
+ class AllArguments < Arguments
+ def foo(*args, **options)
+ super
+ end
+ end
+ end
+
+ module FromBasicObject
+ def __send__(name, *args, &block)
+ super
+ end
+ end
+
+ module IntermediateBasic
+ include FromBasicObject
+ end
+
+ class IncludesFromBasic
+ include FromBasicObject
+
+ def foobar; 43; end
+ end
+
+ class IncludesIntermediate
+ include IntermediateBasic
+
+ def foobar; 42; end
+ end
+
+ module SingletonCase
+ class Base
+ def foobar(array)
+ array << :base
+ end
+ end
+
+ class Foo < Base
+ def foobar(array)
+ array << :foo
+ super
+ end
+ end
+ end
+
+ module SingletonAliasCase
+ class Base
+ def foobar(array)
+ array << :base
+ end
+
+ def alias_on_singleton
+ object = self
+ singleton = (class << object; self; end)
+ singleton.__send__(:alias_method, :new_foobar, :foobar)
+ end
+ end
+
+ class Foo < Base
+ def foobar(array)
+ array << :foo
+ super
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/fixtures/utf16-be-nobom.rb b/spec/ruby/language/fixtures/utf16-be-nobom.rb
new file mode 100644
index 0000000000..99e2ce8ce8
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf16-be-nobom.rb
Binary files differ
diff --git a/spec/ruby/language/fixtures/utf16-le-nobom.rb b/spec/ruby/language/fixtures/utf16-le-nobom.rb
new file mode 100644
index 0000000000..98de9697ca
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf16-le-nobom.rb
Binary files differ
diff --git a/spec/ruby/language/fixtures/utf8-bom.rb b/spec/ruby/language/fixtures/utf8-bom.rb
new file mode 100644
index 0000000000..50c223a922
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf8-bom.rb
@@ -0,0 +1,2 @@
+# encoding: utf-8
+puts 'hello'
diff --git a/spec/ruby/language/fixtures/utf8-nobom.rb b/spec/ruby/language/fixtures/utf8-nobom.rb
new file mode 100644
index 0000000000..75f5563b95
--- /dev/null
+++ b/spec/ruby/language/fixtures/utf8-nobom.rb
@@ -0,0 +1,2 @@
+# encoding: utf-8
+puts 'hello'
diff --git a/spec/ruby/language/fixtures/variables.rb b/spec/ruby/language/fixtures/variables.rb
new file mode 100644
index 0000000000..07265dbb2b
--- /dev/null
+++ b/spec/ruby/language/fixtures/variables.rb
@@ -0,0 +1,85 @@
+module VariablesSpecs
+ class ParAsgn
+ attr_accessor :x
+
+ def initialize
+ @x = 0
+ end
+
+ def inc
+ @x += 1
+ end
+
+ def to_ary
+ [1,2,3,4]
+ end
+ end
+
+ class OpAsgn
+ attr_accessor :a, :b, :side_effect
+
+ def do_side_effect
+ self.side_effect = true
+ return @a
+ end
+
+ def do_more_side_effects
+ @a += 5
+ self
+ end
+
+ def do_bool_side_effects
+ @b += 1
+ self
+ end
+ end
+
+ class Hashalike
+ def [](k) k end
+ def []=(k, v) [k, v] end
+ end
+
+ def self.reverse_foo(a, b)
+ return b, a
+ end
+
+ class ArrayLike
+ def initialize(array)
+ @array = array
+ end
+
+ def to_a
+ @array
+ end
+ end
+
+ class ArraySubclass < Array
+ end
+
+ class PrivateMethods
+ private
+
+ def to_ary
+ [1, 2]
+ end
+
+ def to_a
+ [3, 4]
+ end
+ end
+
+ class ToAryNil
+ def to_ary
+ end
+ end
+
+ class Chain
+ def self.without_parenthesis a
+ a
+ end
+ end
+
+ def self.false
+ false
+ end
+end
diff --git a/spec/ruby/language/fixtures/vim_magic_comment.rb b/spec/ruby/language/fixtures/vim_magic_comment.rb
new file mode 100644
index 0000000000..60cbe7a3bf
--- /dev/null
+++ b/spec/ruby/language/fixtures/vim_magic_comment.rb
@@ -0,0 +1,2 @@
+# vim: filetype=ruby, fileencoding=big5, tabsize=3, shiftwidth=3
+$magic_comment_result = __ENCODING__.name
diff --git a/spec/ruby/language/fixtures/yield.rb b/spec/ruby/language/fixtures/yield.rb
new file mode 100644
index 0000000000..9f7a2ba238
--- /dev/null
+++ b/spec/ruby/language/fixtures/yield.rb
@@ -0,0 +1,41 @@
+module YieldSpecs
+ class Yielder
+ def z
+ yield
+ end
+
+ def ze(&block)
+ block = proc { block }
+ yield
+ end
+
+ def s(a)
+ yield(a)
+ end
+
+ def m(a, b, c)
+ yield(a, b, c)
+ end
+
+ def r(a)
+ yield(*a)
+ end
+
+ def k(a)
+ yield(*a, b: true)
+ end
+
+ def rs(a, b, c)
+ yield(a, b, *c)
+ end
+
+ def self.define_deep(&inned_block)
+ define_method 'deep' do |v|
+ # should yield to inner_block
+ yield v
+ end
+ end
+
+ define_deep { |v| v * 2}
+ end
+end
diff --git a/spec/ruby/language/for_spec.rb b/spec/ruby/language/for_spec.rb
new file mode 100644
index 0000000000..0ad5ea88af
--- /dev/null
+++ b/spec/ruby/language/for_spec.rb
@@ -0,0 +1,182 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/for_scope'
+
+# for name[, name]... in expr [do]
+# body
+# end
+describe "The for expression" do
+ it "iterates over an Enumerable passing each element to the block" do
+ j = 0
+ for i in 1..3
+ j += i
+ end
+ j.should == 6
+ end
+
+ it "iterates over a list of arrays and destructures with empty comma" do
+ for i, in [[1,2]]
+ i.should == 1
+ end
+ end
+
+ it "iterates over an Hash passing each key-value pair to the block" do
+ k = 0
+ l = 0
+
+ for i, j in { 1 => 10, 2 => 20 }
+ k += i
+ l += j
+ end
+
+ k.should == 3
+ l.should == 30
+ end
+
+ it "iterates over any object responding to 'each'" do
+ obj = Object.new
+ def obj.each
+ (0..10).each { |i| yield i }
+ end
+
+ j = 0
+ for i in obj
+ j += i
+ end
+ j.should == 55
+ end
+
+ it "allows an instance variable as an iterator name" do
+ m = [1,2,3]
+ n = 0
+ for @var in m
+ n += 1
+ end
+ @var.should == 3
+ n.should == 3
+ end
+
+ it "allows a class variable as an iterator name" do
+ class OFor
+ m = [1,2,3]
+ n = 0
+ for @@var in m
+ n += 1
+ end
+ @@var.should == 3
+ n.should == 3
+ end
+ end
+
+ it "allows a constant as an iterator name" do
+ class OFor
+ m = [1,2,3]
+ n = 0
+ -> {
+ for CONST in m
+ n += 1
+ end
+ }.should complain(/already initialized constant/)
+ CONST.should == 3
+ n.should == 3
+ end
+ end
+
+ # 1.9 behaviour verified by nobu in
+ # http://redmine.ruby-lang.org/issues/show/2053
+ it "yields only as many values as there are arguments" do
+ class OFor
+ def each
+ [[1,2,3], [4,5,6]].each do |a|
+ yield(a[0],a[1],a[2])
+ end
+ end
+ end
+ o = OFor.new
+ qs = []
+ for q in o
+ qs << q
+ end
+ qs.should == [1, 4]
+ q.should == 4
+ end
+
+ it "optionally takes a 'do' after the expression" do
+ j = 0
+ for i in 1..3 do
+ j += i
+ end
+ j.should == 6
+ end
+
+ it "allows body begin on the same line if do is used" do
+ j = 0
+ for i in 1..3 do j += i
+ end
+ j.should == 6
+ end
+
+ it "executes code in containing variable scope" do
+ for i in 1..2
+ a = 123
+ end
+
+ a.should == 123
+ end
+
+ it "executes code in containing variable scope with 'do'" do
+ for i in 1..2 do
+ a = 123
+ end
+
+ a.should == 123
+ end
+
+ it "does not try to access variables outside the method" do
+ ForSpecs::ForInClassMethod.foo.should == [:bar, :baz]
+ ForSpecs::ForInClassMethod::READER.call.should == :same_variable_set_outside
+ end
+
+ it "returns expr" do
+ for i in 1..3; end.should == (1..3)
+ for i,j in { 1 => 10, 2 => 20 }; end.should == { 1 => 10, 2 => 20 }
+ end
+
+ it "breaks out of a loop upon 'break', returning nil" do
+ j = 0
+ for i in 1..3
+ j += i
+
+ break if i == 2
+ end.should == nil
+
+ j.should == 3
+ end
+
+ it "allows 'break' to have an argument which becomes the value of the for expression" do
+ for i in 1..3
+ break 10 if i == 2
+ end.should == 10
+ end
+
+ it "starts the next iteration with 'next'" do
+ j = 0
+ for i in 1..5
+ next if i == 2
+
+ j += i
+ end
+
+ j.should == 13
+ end
+
+ it "repeats current iteration with 'redo'" do
+ j = 0
+ for i in 1..3
+ j += i
+
+ redo if i == 2 && j < 4
+ end
+
+ j.should == 8
+ end
+end
diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb
new file mode 100644
index 0000000000..c84564d3ea
--- /dev/null
+++ b/spec/ruby/language/hash_spec.rb
@@ -0,0 +1,258 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/hash_strings_binary'
+require_relative 'fixtures/hash_strings_utf8'
+require_relative 'fixtures/hash_strings_usascii'
+
+describe "Hash literal" do
+ it "{} should return an empty hash" do
+ {}.size.should == 0
+ {}.should == {}
+ end
+
+ it "{} should return a new hash populated with the given elements" do
+ h = {a: 'a', 'b' => 3, 44 => 2.3}
+ h.size.should == 3
+ h.should == {a: "a", "b" => 3, 44 => 2.3}
+ end
+
+ it "treats empty expressions as nils" do
+ h = {() => ()}
+ h.keys.should == [nil]
+ h.values.should == [nil]
+ h[nil].should == nil
+
+ h = {() => :value}
+ h.keys.should == [nil]
+ h.values.should == [:value]
+ h[nil].should == :value
+
+ h = {key: ()}
+ h.keys.should == [:key]
+ h.values.should == [nil]
+ h[:key].should == nil
+ end
+
+ it "freezes string keys on initialization" do
+ key = "foo"
+ h = {key => "bar"}
+ key.reverse!
+ h["foo"].should == "bar"
+ h.keys.first.should == "foo"
+ h.keys.first.should.frozen?
+ key.should == "oof"
+ end
+
+ it "checks duplicated keys on initialization" do
+ -> {
+ @h = eval "{foo: :bar, foo: :foo}"
+ }.should complain(/key :foo is duplicated|duplicated key/)
+ @h.keys.size.should == 1
+ @h.should == {foo: :foo}
+ -> {
+ @h = eval "{%q{a} => :bar, %q{a} => :foo}"
+ }.should complain(/key "a" is duplicated|duplicated key/)
+ @h.keys.size.should == 1
+ @h.should == {%q{a} => :foo}
+ -> {
+ @h = eval "{1000 => :bar, 1000 => :foo}"
+ }.should complain(/key 1000 is duplicated|duplicated key/)
+ @h.keys.size.should == 1
+ @h.should == {1000 => :foo}
+ end
+
+ ruby_version_is "3.1" do
+ it "checks duplicated float keys on initialization" do
+ -> {
+ @h = eval "{1.0 => :bar, 1.0 => :foo}"
+ }.should complain(/key 1.0 is duplicated|duplicated key/)
+ @h.keys.size.should == 1
+ @h.should == {1.0 => :foo}
+ end
+ end
+
+ it "accepts a hanging comma" do
+ h = {a: 1, b: 2,}
+ h.size.should == 2
+ h.should == {a: 1, b: 2}
+ end
+
+ it "recognizes '=' at the end of the key" do
+ eval("{:a==>1}").should == {:"a=" => 1}
+ eval("{:a= =>1}").should == {:"a=" => 1}
+ eval("{:a= => 1}").should == {:"a=" => 1}
+ end
+
+ it "with '==>' in the middle raises SyntaxError" do
+ -> { eval("{:a ==> 1}") }.should raise_error(SyntaxError)
+ end
+
+ it "constructs a new hash with the given elements" do
+ {foo: 123}.should == {foo: 123}
+ h = {rbx: :cool, specs: 'fail_sometimes'}
+ {rbx: :cool, specs: 'fail_sometimes'}.should == h
+ end
+
+ it "ignores a hanging comma" do
+ {foo: 123,}.should == {foo: 123}
+ h = {rbx: :cool, specs: 'fail_sometimes'}
+ {rbx: :cool, specs: 'fail_sometimes',}.should == h
+ end
+
+ it "accepts mixed 'key: value' and 'key => value' syntax" do
+ h = {:a => 1, :b => 2, "c" => 3}
+ {a: 1, b: 2, "c" => 3}.should == h
+ end
+
+ it "accepts mixed 'key: value', 'key => value' and '\"key\"': value' syntax" do
+ h = {:a => 1, :b => 2, "c" => 3, :d => 4}
+ eval('{a: 1, :b => 2, "c" => 3, "d": 4}').should == h
+ end
+
+ it "expands an '**{}' element into the containing Hash literal initialization" do
+ {a: 1, **{b: 2}, c: 3}.should == {a: 1, b: 2, c: 3}
+ end
+
+ it "expands an '**obj' element into the containing Hash literal initialization" do
+ h = {b: 2, c: 3}
+ {**h, a: 1}.should == {b: 2, c: 3, a: 1}
+ {a: 1, **h}.should == {a: 1, b: 2, c: 3}
+ {a: 1, **h, c: 4}.should == {a: 1, b: 2, c: 4}
+ end
+
+ it "expands a BasicObject using ** into the containing Hash literal initialization" do
+ h = BasicObject.new
+ def h.to_hash; {:b => 2, :c => 3}; end
+ {**h, a: 1}.should == {b: 2, c: 3, a: 1}
+ {a: 1, **h}.should == {a: 1, b: 2, c: 3}
+ {a: 1, **h, c: 4}.should == {a: 1, b: 2, c: 4}
+ end
+
+ it "expands an '**{}' element with the last key/value pair taking precedence" do
+ -> {
+ @h = eval "{a: 1, **{a: 2, b: 3, c: 1}, c: 3}"
+ }.should complain(/key :a is duplicated|duplicated key/)
+ @h.should == {a: 2, b: 3, c: 3}
+ end
+
+ it "merges multiple nested '**obj' in Hash literals" do
+ -> {
+ @h = eval "{a: 1, **{a: 2, **{b: 3, **{c: 4}}, **{d: 5}, }, **{d: 6}}"
+ }.should complain(/key :a is duplicated|duplicated key/)
+ @h.should == {a: 2, b: 3, c: 4, d: 6}
+ end
+
+ it "calls #to_hash to expand an '**obj' element" do
+ obj = mock("hash splat")
+ obj.should_receive(:to_hash).and_return({b: 2, d: 4})
+
+ {a: 1, **obj, c: 3}.should == {a:1, b: 2, c: 3, d: 4}
+ end
+
+ it "allows splatted elements keys that are not symbols" do
+ h = {1 => 2, b: 3}
+ {a: 1, **h}.should == {a: 1, 1 => 2, b: 3}
+ end
+
+ it "raises a TypeError if #to_hash does not return a Hash" do
+ obj = mock("hash splat")
+ obj.should_receive(:to_hash).and_return(obj)
+
+ -> { {**obj} }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the object does not respond to #to_hash" do
+ obj = 42
+ -> { {**obj} }.should raise_error(TypeError)
+ -> { {a: 1, **obj} }.should raise_error(TypeError)
+ end
+
+ it "does not change encoding of literal string keys during creation" do
+ binary_hash = HashStringsBinary.literal_hash
+ utf8_hash = HashStringsUTF8.literal_hash
+ usascii_hash = HashStringsUSASCII.literal_hash
+
+ binary_hash.keys.first.encoding.should == Encoding::BINARY
+ binary_hash.keys.first.should == utf8_hash.keys.first
+ utf8_hash.keys.first.encoding.should == Encoding::UTF_8
+ utf8_hash.keys.first.should == usascii_hash.keys.first
+ usascii_hash.keys.first.encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "The ** operator" do
+ it "makes a copy when calling a method taking a keyword rest argument" do
+ def m(**h)
+ h.delete(:one); h
+ end
+
+ h = { one: 1, two: 2 }
+ m(**h).should == { two: 2 }
+ m(**h).should_not.equal?(h)
+ h.should == { one: 1, two: 2 }
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "makes a caller-side copy when calling a method taking a positional Hash" do
+ def m(h)
+ h.delete(:one); h
+ end
+
+ h = { one: 1, two: 2 }
+ m(**h).should == { two: 2 }
+ m(**h).should_not.equal?(h)
+ h.should == { one: 1, two: 2 }
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "does not copy when calling a method taking a positional Hash" do
+ def m(h)
+ h.delete(:one); h
+ end
+
+ h = { one: 1, two: 2 }
+ m(**h).should == { two: 2 }
+ m(**h).should.equal?(h)
+ h.should == { two: 2 }
+ end
+ end
+
+ ruby_version_is "3.1" do
+ describe "hash with omitted value" do
+ it "accepts short notation 'key' for 'key: value' syntax" do
+ a, b, c = 1, 2, 3
+ h = eval('{a:}')
+ {a: 1}.should == h
+ h = eval('{a:, b:, c:}')
+ {a: 1, b: 2, c: 3}.should == h
+ end
+
+ it "ignores hanging comma on short notation" do
+ a, b, c = 1, 2, 3
+ h = eval('{a:, b:, c:,}')
+ {a: 1, b: 2, c: 3}.should == h
+ end
+
+ it "accepts mixed syntax" do
+ a, e = 1, 5
+ h = eval('{a:, b: 2, "c" => 3, :d => 4, e:}')
+ eval('{a: 1, :b => 2, "c" => 3, "d": 4, e: 5}').should == h
+ end
+
+ it "works with methods and local vars" do
+ a = Class.new
+ a.class_eval(<<-RUBY)
+ def bar
+ "baz"
+ end
+
+ def foo(val)
+ {bar:, val:}
+ end
+ RUBY
+
+ a.new.foo(1).should == {bar: "baz", val: 1}
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/heredoc_spec.rb b/spec/ruby/language/heredoc_spec.rb
new file mode 100644
index 0000000000..b3b160fd11
--- /dev/null
+++ b/spec/ruby/language/heredoc_spec.rb
@@ -0,0 +1,109 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../spec_helper'
+
+describe "Heredoc string" do
+
+ before :each do
+ @ip = 'xxx' # used for interpolation
+ end
+
+ it "allows HEREDOC with <<identifier, interpolated" do
+ s = <<HERE
+foo bar#{@ip}
+HERE
+ s.should == "foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
+ end
+
+ it 'allow HEREDOC with <<"identifier", interpolated' do
+ s = <<"HERE"
+foo bar#{@ip}
+HERE
+ s.should == "foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
+ end
+
+ it "allows HEREDOC with <<'identifier', no interpolation" do
+ s = <<'HERE'
+foo bar#{@ip}
+HERE
+ s.should == 'foo bar#{@ip}' + "\n"
+ s.encoding.should == Encoding::US_ASCII
+ end
+
+ it "allows HEREDOC with <<-identifier, allowing to indent identifier, interpolated" do
+ s = <<-HERE
+ foo bar#{@ip}
+ HERE
+
+ s.should == " foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
+ end
+
+ it 'allows HEREDOC with <<-"identifier", allowing to indent identifier, interpolated' do
+ s = <<-"HERE"
+ foo bar#{@ip}
+ HERE
+
+ s.should == " foo barxxx\n"
+ s.encoding.should == Encoding::US_ASCII
+ end
+
+ it "allows HEREDOC with <<-'identifier', allowing to indent identifier, no interpolation" do
+ s = <<-'HERE'
+ foo bar#{@ip}
+ HERE
+
+ s.should == ' foo bar#{@ip}' + "\n"
+ s.encoding.should == Encoding::US_ASCII
+ end
+
+ it 'raises SyntaxError if quoted HEREDOC identifier is ending not on same line' do
+ -> {
+ eval %{<<"HERE\n"\nraises syntax error\nHERE}
+ }.should raise_error(SyntaxError)
+ end
+
+ it "allows HEREDOC with <<~'identifier', allowing to indent identifier and content" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.message.should == "character density, n.:\n The number of very weird people in the office.\n"
+ end
+
+ it "trims trailing newline character for blank HEREDOC with <<~'identifier'" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.blank.should == ""
+ end
+
+ it 'allows HEREDOC with <<~identifier, interpolated' do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.unquoted.should == "unquoted interpolated\n"
+ end
+
+ it 'allows HEREDOC with <<~"identifier", interpolated' do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.doublequoted.should == "doublequoted interpolated\n"
+ end
+
+ it "allows HEREDOC with <<~'identifier', no interpolation" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.singlequoted.should == "singlequoted \#{\"interpolated\"}\n"
+ end
+
+ it "allows HEREDOC with <<~'identifier', no interpolation, with backslash" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.backslash.should == "a\nbc\n"
+ end
+
+ it "selects the least-indented line and removes its indentation from all the lines" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.least_indented_on_the_first_line.should == "a\n b\n c\n"
+ SquigglyHeredocSpecs.least_indented_on_the_last_line.should == " a\n b\nc\n"
+ end
+
+ it "selects the least-indented line and removes its indentation from all the lines for <<~'identifier'" do
+ require_relative 'fixtures/squiggly_heredoc'
+ SquigglyHeredocSpecs.least_indented_on_the_first_line_single.should == "a\n b\n c\n"
+ SquigglyHeredocSpecs.least_indented_on_the_last_line_single.should == " a\n b\nc\n"
+ end
+end
diff --git a/spec/ruby/language/if_spec.rb b/spec/ruby/language/if_spec.rb
new file mode 100644
index 0000000000..d1d95c1607
--- /dev/null
+++ b/spec/ruby/language/if_spec.rb
@@ -0,0 +1,371 @@
+require_relative '../spec_helper'
+
+describe "The if expression" do
+ describe "accepts multiple assignments in conditional expression" do
+ before(:each) { ScratchPad.record([]) }
+ after(:each) { ScratchPad.clear }
+
+ it 'with non-nil values' do
+ ary = [1, 2]
+ eval "if (a, b = ary); ScratchPad.record [a, b]; end"
+ ScratchPad.recorded.should == [1, 2]
+ end
+
+ it 'with nil values' do
+ ary = nil
+ eval "if (a, b = ary); else; ScratchPad.record [a, b]; end"
+ ScratchPad.recorded.should == [nil, nil]
+ end
+ end
+
+ it "evaluates body if expression is true" do
+ a = []
+ if true
+ a << 123
+ end
+ a.should == [123]
+ end
+
+ it "does not evaluate body if expression is false" do
+ a = []
+ if false
+ a << 123
+ end
+ a.should == []
+ end
+
+ it "does not evaluate body if expression is empty" do
+ a = []
+ if ()
+ a << 123
+ end
+ a.should == []
+ end
+
+ it "does not evaluate else-body if expression is true" do
+ a = []
+ if true
+ a << 123
+ else
+ a << 456
+ end
+ a.should == [123]
+ end
+
+ it "evaluates only else-body if expression is false" do
+ a = []
+ if false
+ a << 123
+ else
+ a << 456
+ end
+ a.should == [456]
+ end
+
+ it "returns result of then-body evaluation if expression is true" do
+ if true
+ 123
+ end.should == 123
+ end
+
+ it "returns result of last statement in then-body if expression is true" do
+ if true
+ 'foo'
+ 'bar'
+ 'baz'
+ end.should == 'baz'
+ end
+
+ it "returns result of then-body evaluation if expression is true and else part is present" do
+ if true
+ 123
+ else
+ 456
+ end.should == 123
+ end
+
+ it "returns result of else-body evaluation if expression is false" do
+ if false
+ 123
+ else
+ 456
+ end.should == 456
+ end
+
+ it "returns nil if then-body is empty and expression is true" do
+ if true
+ end.should == nil
+ end
+
+ it "returns nil if then-body is empty, expression is true and else part is present" do
+ if true
+ else
+ 456
+ end.should == nil
+ end
+
+ it "returns nil if then-body is empty, expression is true and else part is empty" do
+ if true
+ else
+ end.should == nil
+ end
+
+ it "returns nil if else-body is empty and expression is false" do
+ if false
+ 123
+ else
+ end.should == nil
+ end
+
+ it "returns nil if else-body is empty, expression is false and then-body is empty" do
+ if false
+ else
+ end.should == nil
+ end
+
+ it "considers an expression with nil result as false" do
+ if nil
+ 123
+ else
+ 456
+ end.should == 456
+ end
+
+ it "considers a non-nil and non-boolean object in expression result as true" do
+ if mock('x')
+ 123
+ else
+ 456
+ end.should == 123
+ end
+
+ it "considers a zero integer in expression result as true" do
+ if 0
+ 123
+ else
+ 456
+ end.should == 123
+ end
+
+ it "allows starting else-body on the same line" do
+ if false
+ 123
+ else 456
+ end.should == 456
+ end
+
+ it "evaluates subsequent elsif statements and execute body of first matching" do
+ if false
+ 123
+ elsif false
+ 234
+ elsif true
+ 345
+ elsif true
+ 456
+ end.should == 345
+ end
+
+ it "evaluates else-body if no if/elsif statements match" do
+ if false
+ 123
+ elsif false
+ 234
+ elsif false
+ 345
+ else
+ 456
+ end.should == 456
+ end
+
+ it "allows 'then' after expression when then-body is on the next line" do
+ if true then
+ 123
+ end.should == 123
+
+ if true then ; 123; end.should == 123
+ end
+
+ it "allows then-body on the same line separated with 'then'" do
+ if true then 123
+ end.should == 123
+
+ if true then 123; end.should == 123
+ end
+
+ it "returns nil when then-body on the same line separated with 'then' and expression is false" do
+ if false then 123
+ end.should == nil
+
+ if false then 123; end.should == nil
+ end
+
+ it "returns nil when then-body separated by 'then' is empty and expression is true" do
+ if true then
+ end.should == nil
+
+ if true then ; end.should == nil
+ end
+
+ it "returns nil when then-body separated by 'then', expression is false and no else part" do
+ if false then
+ end.should == nil
+
+ if false then ; end.should == nil
+ end
+
+ it "evaluates then-body when then-body separated by 'then', expression is true and else part is present" do
+ if true then 123
+ else 456
+ end.should == 123
+
+ if true then 123; else 456; end.should == 123
+ end
+
+ it "evaluates else-body when then-body separated by 'then' and expression is false" do
+ if false then 123
+ else 456
+ end.should == 456
+
+ if false then 123; else 456; end.should == 456
+ end
+
+ describe "with a boolean range ('flip-flop' operator)" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "mimics an awk conditional with a single-element inclusive-end range" do
+ 10.times { |i| ScratchPad << i if (i == 4)..(i == 4) }
+ ScratchPad.recorded.should == [4]
+ end
+
+ it "mimics an awk conditional with a many-element inclusive-end range" do
+ 10.times { |i| ScratchPad << i if (i == 4)..(i == 7) }
+ ScratchPad.recorded.should == [4, 5, 6, 7]
+ end
+
+ it "mimics a sed conditional with a zero-element exclusive-end range" do
+ eval "10.times { |i| ScratchPad << i if (i == 4)...(i == 4) }"
+ ScratchPad.recorded.should == [4, 5, 6, 7, 8, 9]
+ end
+
+ it "mimics a sed conditional with a many-element exclusive-end range" do
+ 10.times { |i| ScratchPad << i if (i == 4)...(i == 5) }
+ ScratchPad.recorded.should == [4, 5]
+ end
+
+ it "allows combining two flip-flops" do
+ 10.times { |i| ScratchPad << i if (i == 4)...(i == 5) or (i == 7)...(i == 8) }
+ ScratchPad.recorded.should == [4, 5, 7, 8]
+ end
+
+ it "evaluates the first conditions lazily with inclusive-end range" do
+ collector = proc { |i| ScratchPad << i }
+ eval "10.times { |i| i if collector[i]...false }"
+ ScratchPad.recorded.should == [0]
+ end
+
+ it "evaluates the first conditions lazily with exclusive-end range" do
+ collector = proc { |i| ScratchPad << i }
+ eval "10.times { |i| i if collector[i]..false }"
+ ScratchPad.recorded.should == [0]
+ end
+
+ it "evaluates the second conditions lazily with inclusive-end range" do
+ collector = proc { |i| ScratchPad << i }
+ 10.times { |i| i if (i == 4)...collector[i] }
+ ScratchPad.recorded.should == [5]
+ end
+
+ it "evaluates the second conditions lazily with exclusive-end range" do
+ collector = proc { |i| ScratchPad << i }
+ 10.times { |i| i if (i == 4)..collector[i] }
+ ScratchPad.recorded.should == [4]
+ end
+
+ it "scopes state by flip-flop" do
+ store_me = proc { |i| ScratchPad << i if (i == 4)..(i == 7) }
+ store_me[1]
+ store_me[4]
+ proc { store_me[1] }.call
+ store_me[7]
+ store_me[5]
+ ScratchPad.recorded.should == [4, 1, 7]
+ end
+
+ it "keeps flip-flops from interfering" do
+ a = eval "proc { |i| ScratchPad << i if (i == 4)..(i == 7) }"
+ b = eval "proc { |i| ScratchPad << i if (i == 4)..(i == 7) }"
+ 6.times(&a)
+ 6.times(&b)
+ ScratchPad.recorded.should == [4, 5, 4, 5]
+ end
+ end
+end
+
+describe "The postfix if form" do
+ it "evaluates statement if expression is true" do
+ a = []
+ a << 123 if true
+ a.should == [123]
+ end
+
+ it "does not evaluate statement if expression is false" do
+ a = []
+ a << 123 if false
+ a.should == []
+ end
+
+ it "returns result of expression if value is true" do
+ (123 if true).should == 123
+ end
+
+ it "returns nil if expression is false" do
+ (123 if false).should == nil
+ end
+
+ it "considers a nil expression as false" do
+ (123 if nil).should == nil
+ end
+
+ it "considers a non-nil object as true" do
+ (123 if mock('x')).should == 123
+ end
+
+ it "evaluates then-body in containing scope" do
+ a = 123
+ if true
+ b = a+1
+ end
+ b.should == 124
+ end
+
+ it "evaluates else-body in containing scope" do
+ a = 123
+ if false
+ b = a+1
+ else
+ b = a+2
+ end
+ b.should == 125
+ end
+
+ it "evaluates elsif-body in containing scope" do
+ a = 123
+ if false
+ b = a+1
+ elsif false
+ b = a+2
+ elsif true
+ b = a+3
+ else
+ b = a+4
+ end
+ b.should == 126
+ end
+end
diff --git a/spec/ruby/language/keyword_arguments_spec.rb b/spec/ruby/language/keyword_arguments_spec.rb
new file mode 100644
index 0000000000..c47b7b0ae9
--- /dev/null
+++ b/spec/ruby/language/keyword_arguments_spec.rb
@@ -0,0 +1,397 @@
+require_relative '../spec_helper'
+
+ruby_version_is "3.0" do
+ describe "Keyword arguments" do
+ def target(*args, **kwargs)
+ [args, kwargs]
+ end
+
+ it "are separated from positional arguments" do
+ def m(*args, **kwargs)
+ [args, kwargs]
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ m(empty).should == [[{}], {}]
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+ end
+
+ it "when the receiving method has not keyword parameters it treats kwargs as positional" do
+ def m(*a)
+ a
+ end
+
+ m(a: 1).should == [{a: 1}]
+ m({a: 1}).should == [{a: 1}]
+ end
+
+ context "empty kwargs are treated as if they were not passed" do
+ it "when calling a method" do
+ def m(*a)
+ a
+ end
+
+ empty = {}
+ m(**empty).should == []
+ m(empty).should == [{}]
+ end
+
+ it "when yielding to a block" do
+ def y(*args, **kwargs)
+ yield(*args, **kwargs)
+ end
+
+ empty = {}
+ y(**empty) { |*a| a }.should == []
+ y(empty) { |*a| a }.should == [{}]
+ end
+ end
+
+ it "extra keywords are not allowed without **kwrest" do
+ def m(*a, kw:)
+ a
+ end
+
+ m(kw: 1).should == []
+ -> { m(kw: 1, kw2: 2) }.should raise_error(ArgumentError, 'unknown keyword: :kw2')
+ -> { m(kw: 1, true => false) }.should raise_error(ArgumentError, 'unknown keyword: true')
+ -> { m(kw: 1, a: 1, b: 2, c: 3) }.should raise_error(ArgumentError, 'unknown keywords: :a, :b, :c')
+ end
+
+ it "raises ArgumentError exception when required keyword argument is not passed" do
+ def m(a:, b:, c:)
+ [a, b, c]
+ end
+
+ -> { m(a: 1, b: 2) }.should raise_error(ArgumentError, /missing keyword: :c/)
+ -> { m() }.should raise_error(ArgumentError, /missing keywords: :a, :b, :c/)
+ end
+
+ it "raises ArgumentError for missing keyword arguments even if there are extra ones" do
+ def m(a:)
+ a
+ end
+
+ -> { m(b: 1) }.should raise_error(ArgumentError, /missing keyword: :a/)
+ end
+
+ it "handle * and ** at the same call site" do
+ def m(*a)
+ a
+ end
+
+ m(*[], **{}).should == []
+ m(*[], 42, **{}).should == [42]
+ end
+
+ context "**" do
+ it "does not copy a non-empty Hash for a method taking (*args)" do
+ def m(*args)
+ args[0]
+ end
+
+ h = {a: 1}
+ m(**h).should.equal?(h)
+ end
+
+ it "copies the given Hash for a method taking (**kwargs)" do
+ def m(**kw)
+ kw
+ end
+
+ empty = {}
+ m(**empty).should == empty
+ m(**empty).should_not.equal?(empty)
+
+ h = {a: 1}
+ m(**h).should == h
+ m(**h).should_not.equal?(h)
+ end
+ end
+
+ context "delegation" do
+ it "works with (*args, **kwargs)" do
+ def m(*args, **kwargs)
+ target(*args, **kwargs)
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ m(empty).should == [[{}], {}]
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+ end
+
+ it "works with proc { |*args, **kwargs| }" do
+ m = proc do |*args, **kwargs|
+ target(*args, **kwargs)
+ end
+
+ empty = {}
+ m.(**empty).should == [[], {}]
+ m.(empty).should == [[{}], {}]
+
+ m.(a: 1).should == [[], {a: 1}]
+ m.({a: 1}).should == [[{a: 1}], {}]
+
+ # no autosplatting for |*args, **kwargs|
+ m.([1, 2]).should == [[[1, 2]], {}]
+ end
+
+ it "works with -> (*args, **kwargs) {}" do
+ m = -> *args, **kwargs do
+ target(*args, **kwargs)
+ end
+
+ empty = {}
+ m.(**empty).should == [[], {}]
+ m.(empty).should == [[{}], {}]
+
+ m.(a: 1).should == [[], {a: 1}]
+ m.({a: 1}).should == [[{a: 1}], {}]
+ end
+
+ it "works with (...)" do
+ instance_eval <<~DEF
+ def m(...)
+ target(...)
+ end
+ DEF
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ m(empty).should == [[{}], {}]
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+ end
+
+ it "works with call(*ruby2_keyword_args)" do
+ class << self
+ ruby2_keywords def m(*args)
+ target(*args)
+ end
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+ m(empty).should == [[{}], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+
+ kw = {a: 1}
+
+ m(**kw).should == [[], {a: 1}]
+ m(**kw)[1].should == kw
+ m(**kw)[1].should_not.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false
+
+ m(kw).should == [[{a: 1}], {}]
+ m(kw)[0][0].should.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ end
+
+ it "works with super(*ruby2_keyword_args)" do
+ parent = Class.new do
+ def m(*args, **kwargs)
+ [args, kwargs]
+ end
+ end
+
+ child = Class.new(parent) do
+ ruby2_keywords def m(*args)
+ super(*args)
+ end
+ end
+
+ obj = child.new
+
+ empty = {}
+ obj.m(**empty).should == [[], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+ obj.m(empty).should == [[{}], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+
+ obj.m(a: 1).should == [[], {a: 1}]
+ obj.m({a: 1}).should == [[{a: 1}], {}]
+
+ kw = {a: 1}
+
+ obj.m(**kw).should == [[], {a: 1}]
+ obj.m(**kw)[1].should == kw
+ obj.m(**kw)[1].should_not.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ Hash.ruby2_keywords_hash?(obj.m(**kw)[1]).should == false
+
+ obj.m(kw).should == [[{a: 1}], {}]
+ obj.m(kw)[0][0].should.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ end
+
+ it "works with zsuper" do
+ parent = Class.new do
+ def m(*args, **kwargs)
+ [args, kwargs]
+ end
+ end
+
+ child = Class.new(parent) do
+ ruby2_keywords def m(*args)
+ super
+ end
+ end
+
+ obj = child.new
+
+ empty = {}
+ obj.m(**empty).should == [[], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+ obj.m(empty).should == [[{}], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+
+ obj.m(a: 1).should == [[], {a: 1}]
+ obj.m({a: 1}).should == [[{a: 1}], {}]
+
+ kw = {a: 1}
+
+ obj.m(**kw).should == [[], {a: 1}]
+ obj.m(**kw)[1].should == kw
+ obj.m(**kw)[1].should_not.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ Hash.ruby2_keywords_hash?(obj.m(**kw)[1]).should == false
+
+ obj.m(kw).should == [[{a: 1}], {}]
+ obj.m(kw)[0][0].should.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ end
+
+ it "works with yield(*ruby2_keyword_args)" do
+ class << self
+ def y(args)
+ yield(*args)
+ end
+
+ ruby2_keywords def m(*outer_args)
+ y(outer_args, &-> *args, **kwargs { target(*args, **kwargs) })
+ end
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+ m(empty).should == [[{}], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+
+ kw = {a: 1}
+
+ m(**kw).should == [[], {a: 1}]
+ m(**kw)[1].should == kw
+ m(**kw)[1].should_not.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false
+
+ m(kw).should == [[{a: 1}], {}]
+ m(kw)[0][0].should.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ end
+
+ it "does not work with (*args)" do
+ class << self
+ def m(*args)
+ target(*args)
+ end
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ m(empty).should == [[{}], {}]
+
+ m(a: 1).should == [[{a: 1}], {}]
+ m({a: 1}).should == [[{a: 1}], {}]
+ end
+
+ ruby_version_is "3.1" do
+ describe "omitted values" do
+ it "accepts short notation 'key' for 'key: value' syntax" do
+ def m(a:, b:)
+ [a, b]
+ end
+
+ a = 1
+ b = 2
+
+ eval('m(a:, b:).should == [1, 2]')
+ end
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "does not work with call(*ruby2_keyword_args) with missing ruby2_keywords in between" do
+ class << self
+ def n(*args) # Note the missing ruby2_keywords here
+ target(*args)
+ end
+
+ ruby2_keywords def m(*args)
+ n(*args)
+ end
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ m(empty).should == [[{}], {}]
+
+ m(a: 1).should == [[{a: 1}], {}]
+ m({a: 1}).should == [[{a: 1}], {}]
+ end
+ end
+
+ ruby_version_is ""..."3.2" do
+ # https://bugs.ruby-lang.org/issues/18625
+ it "works with call(*ruby2_keyword_args) with missing ruby2_keywords in between due to CRuby bug #18625" do
+ class << self
+ def n(*args) # Note the missing ruby2_keywords here
+ target(*args)
+ end
+
+ ruby2_keywords def m(*args)
+ n(*args)
+ end
+ end
+
+ empty = {}
+ m(**empty).should == [[], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+ m(empty).should == [[{}], {}]
+ Hash.ruby2_keywords_hash?(empty).should == false
+
+ m(a: 1).should == [[], {a: 1}]
+ m({a: 1}).should == [[{a: 1}], {}]
+
+ kw = {a: 1}
+
+ m(**kw).should == [[], {a: 1}]
+ m(**kw)[1].should == kw
+ m(**kw)[1].should_not.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false
+
+ m(kw).should == [[{a: 1}], {}]
+ m(kw)[0][0].should.equal?(kw)
+ Hash.ruby2_keywords_hash?(kw).should == false
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/lambda_spec.rb b/spec/ruby/language/lambda_spec.rb
new file mode 100644
index 0000000000..a3f01ec04b
--- /dev/null
+++ b/spec/ruby/language/lambda_spec.rb
@@ -0,0 +1,620 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "A lambda literal -> () { }" do
+ SpecEvaluate.desc = "for definition"
+
+ it "returns a Proc object when used in a BasicObject method" do
+ klass = Class.new(BasicObject) do
+ def create_lambda
+ -> { }
+ end
+ end
+
+ klass.new.create_lambda.should be_an_instance_of(Proc)
+ end
+
+ it "does not execute the block" do
+ -> { fail }.should be_an_instance_of(Proc)
+ end
+
+ it "returns a lambda" do
+ -> { }.lambda?.should be_true
+ end
+
+ it "may include a rescue clause" do
+ eval('-> do raise ArgumentError; rescue ArgumentError; 7; end').should be_an_instance_of(Proc)
+ end
+
+ it "may include a ensure clause" do
+ eval('-> do 1; ensure; 2; end').should be_an_instance_of(Proc)
+ end
+
+ it "has its own scope for local variables" do
+ l = -> arg {
+ var = arg
+ # this would override var if it was declared outside the lambda
+ l.call(arg-1) if arg > 0
+ var
+ }
+ l.call(1).should == 1
+ end
+
+ context "assigns no local variables" do
+ evaluate <<-ruby do
+ @a = -> { }
+ @b = ->() { }
+ @c = -> () { }
+ @d = -> do end
+ ruby
+
+ @a.().should be_nil
+ @b.().should be_nil
+ @c.().should be_nil
+ @d.().should be_nil
+ end
+ end
+
+ context "assigns variables from parameters" do
+ evaluate <<-ruby do
+ @a = -> (a) { a }
+ ruby
+
+ @a.(1).should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = -> ((a)) { a }
+ ruby
+
+ @a.(1).should == 1
+ @a.([1, 2, 3]).should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = -> ((*a, b)) { [a, b] }
+ ruby
+
+ @a.(1).should == [[], 1]
+ @a.([1, 2, 3]).should == [[1, 2], 3]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a={}) { a }
+ ruby
+
+ @a.().should == {}
+ @a.(2).should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = -> (*) { }
+ ruby
+
+ @a.().should be_nil
+ @a.(1).should be_nil
+ @a.(1, 2, 3).should be_nil
+ end
+
+ evaluate <<-ruby do
+ @a = -> (*a) { a }
+ ruby
+
+ @a.().should == []
+ @a.(1).should == [1]
+ @a.(1, 2, 3).should == [1, 2, 3]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a:) { a }
+ ruby
+
+ -> { @a.() }.should raise_error(ArgumentError)
+ @a.(a: 1).should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a: 1) { a }
+ ruby
+
+ @a.().should == 1
+ @a.(a: 2).should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = -> (**) { }
+ ruby
+
+ @a.().should be_nil
+ @a.(a: 1, b: 2).should be_nil
+ -> { @a.(1) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ @a = -> (**k) { k }
+ ruby
+
+ @a.().should == {}
+ @a.(a: 1, b: 2).should == {a: 1, b: 2}
+ end
+
+ evaluate <<-ruby do
+ @a = -> (&b) { b }
+ ruby
+
+ @a.().should be_nil
+ @a.() { }.should be_an_instance_of(Proc)
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, b) { [a, b] }
+ ruby
+
+ @a.(1, 2).should == [1, 2]
+ -> { @a.() }.should raise_error(ArgumentError)
+ -> { @a.(1) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ @a = -> ((a, b, *c, d), (*e, f, g), (*h)) do
+ [a, b, c, d, e, f, g, h]
+ end
+ ruby
+
+ @a.(1, 2, 3).should == [1, nil, [], nil, [], 2, nil, [3]]
+ result = @a.([1, 2, 3], [4, 5, 6, 7, 8], [9, 10])
+ result.should == [1, 2, [], 3, [4, 5, 6], 7, 8, [9, 10]]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, (b, (c, *d, (e, (*f)), g), (h, (i, j)))) do
+ [a, b, c, d, e, f, g, h, i, j]
+ end
+ ruby
+
+ @a.(1, 2).should == [1, 2, nil, [], nil, [nil], nil, nil, nil, nil]
+ result = @a.(1, [2, [3, 4, 5, [6, [7, 8]], 9], [10, [11, 12]]])
+ result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12]
+ end
+
+ ruby_version_is ''...'3.0' do
+ evaluate <<-ruby do
+ @a = -> (*, **k) { k }
+ ruby
+
+ @a.().should == {}
+ @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+
+ suppress_keyword_warning do
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({a: 1})
+ @a.(h).should == {a: 1}
+ end
+ end
+ end
+
+ ruby_version_is '3.0' do
+ evaluate <<-ruby do
+ @a = -> (*, **k) { k }
+ ruby
+
+ @a.().should == {}
+ @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+
+ h = mock("keyword splat")
+ h.should_not_receive(:to_hash)
+ @a.(h).should == {}
+ end
+ end
+
+ evaluate <<-ruby do
+ @a = -> (*, &b) { b }
+ ruby
+
+ @a.().should be_nil
+ @a.(1, 2, 3, 4).should be_nil
+ @a.(&(l = ->{})).should equal(l)
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a:, b:) { [a, b] }
+ ruby
+
+ @a.(a: 1, b: 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a:, b: 1) { [a, b] }
+ ruby
+
+ @a.(a: 1).should == [1, 1]
+ @a.(a: 1, b: 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a: 1, b:) { [a, b] }
+ ruby
+
+ @a.(b: 0).should == [1, 0]
+ @a.(b: 2, a: 3).should == [3, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a: @a = -> (a: 1) { a }, b:) do
+ [a, b]
+ end
+ ruby
+
+ @a.(a: 2, b: 3).should == [2, 3]
+ @a.(b: 1).should == [@a, 1]
+
+ # Note the default value of a: in the original method.
+ @a.().should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a: 1, b: 2) { [a, b] }
+ ruby
+
+ @a.().should == [1, 2]
+ @a.(b: 3, a: 4).should == [4, 3]
+ end
+
+ evaluate <<-ruby do
+ @a = -> (a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l) do
+ [a, b, c, d, e, f, g, h, k, l]
+ end
+ ruby
+
+ result = @a.(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{}))
+ result.should == [9, 8, [7], [], 6, 5, 4, 3, {}, l]
+ end
+
+ evaluate <<-ruby do
+ @a = -> a, b=1, *c, d, e:, f: 2, g:, **k, &l do
+ [a, b, c, d, e, f, g, k, l]
+ end
+ ruby
+
+ result = @a.(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{}))
+ result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l]
+ end
+
+ describe "with circular optional argument reference" do
+ it "raises a SyntaxError if using an existing local with the same name as the argument" do
+ a = 1
+ -> {
+ @proc = eval "-> (a=a) { a }"
+ }.should raise_error(SyntaxError)
+ end
+
+ it "raises a SyntaxError if there is an existing method with the same name as the argument" do
+ def a; 1; end
+ -> {
+ @proc = eval "-> (a=a) { a }"
+ }.should raise_error(SyntaxError)
+ end
+
+ it "calls an existing method with the same name as the argument if explicitly using ()" do
+ def a; 1; end
+ -> a=a() { a }.call.should == 1
+ end
+ end
+ end
+end
+
+describe "A lambda expression 'lambda { ... }'" do
+ SpecEvaluate.desc = "for definition"
+
+ it "calls the #lambda method" do
+ obj = mock("lambda definition")
+ obj.should_receive(:lambda).and_return(obj)
+
+ def obj.define
+ lambda { }
+ end
+
+ obj.define.should equal(obj)
+ end
+
+ it "does not execute the block" do
+ lambda { fail }.should be_an_instance_of(Proc)
+ end
+
+ it "returns a lambda" do
+ lambda { }.lambda?.should be_true
+ end
+
+ it "requires a block" do
+ suppress_warning do
+ lambda { lambda }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "may include a rescue clause" do
+ eval('lambda do raise ArgumentError; rescue ArgumentError; 7; end').should be_an_instance_of(Proc)
+ end
+
+ context "with an implicit block" do
+ before do
+ def meth; lambda; end
+ end
+
+ it "raises ArgumentError" do
+ implicit_lambda = nil
+ suppress_warning do
+ -> {
+ meth { 1 }
+ }.should raise_error(ArgumentError, /tried to create Proc object without a block/)
+ end
+ end
+ end
+
+ context "assigns no local variables" do
+ evaluate <<-ruby do
+ @a = lambda { }
+ @b = lambda { || }
+ ruby
+
+ @a.().should be_nil
+ @b.().should be_nil
+ end
+ end
+
+ context "assigns variables from parameters" do
+ evaluate <<-ruby do
+ @a = lambda { |a| a }
+ ruby
+
+ @a.(1).should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(*a) yield(*a) end
+ @a = lambda { |a| a }
+ ruby
+
+ lambda { m(&@a) }.should raise_error(ArgumentError)
+ lambda { m(1, 2, &@a) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, | a }
+ ruby
+
+ @a.(1).should == 1
+ @a.([1, 2]).should == [1, 2]
+
+ lambda { @a.() }.should raise_error(ArgumentError)
+ lambda { @a.(1, 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a) yield a end
+ def m2() yield end
+
+ @a = lambda { |a, | a }
+ ruby
+
+ m(1, &@a).should == 1
+ m([1, 2], &@a).should == [1, 2]
+
+ lambda { m2(&@a) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |(a)| a }
+ ruby
+
+ @a.(1).should == 1
+ @a.([1, 2, 3]).should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |(*a, b)| [a, b] }
+ ruby
+
+ @a.(1).should == [[], 1]
+ @a.([1, 2, 3]).should == [[1, 2], 3]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a={}| a }
+ ruby
+
+ @a.().should == {}
+ @a.(2).should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |*| }
+ ruby
+
+ @a.().should be_nil
+ @a.(1).should be_nil
+ @a.(1, 2, 3).should be_nil
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |*a| a }
+ ruby
+
+ @a.().should == []
+ @a.(1).should == [1]
+ @a.(1, 2, 3).should == [1, 2, 3]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a:| a }
+ ruby
+
+ lambda { @a.() }.should raise_error(ArgumentError)
+ @a.(a: 1).should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a: 1| a }
+ ruby
+
+ @a.().should == 1
+ @a.(a: 2).should == 2
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |**| }
+ ruby
+
+ @a.().should be_nil
+ @a.(a: 1, b: 2).should be_nil
+ lambda { @a.(1) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |**k| k }
+ ruby
+
+ @a.().should == {}
+ @a.(a: 1, b: 2).should == {a: 1, b: 2}
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |&b| b }
+ ruby
+
+ @a.().should be_nil
+ @a.() { }.should be_an_instance_of(Proc)
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a, b| [a, b] }
+ ruby
+
+ @a.(1, 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda do |(a, b, *c, d), (*e, f, g), (*h)|
+ [a, b, c, d, e, f, g, h]
+ end
+ ruby
+
+ @a.(1, 2, 3).should == [1, nil, [], nil, [], 2, nil, [3]]
+ result = @a.([1, 2, 3], [4, 5, 6, 7, 8], [9, 10])
+ result.should == [1, 2, [], 3, [4, 5, 6], 7, 8, [9, 10]]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda do |a, (b, (c, *d, (e, (*f)), g), (h, (i, j)))|
+ [a, b, c, d, e, f, g, h, i, j]
+ end
+ ruby
+
+ @a.(1, 2).should == [1, 2, nil, [], nil, [nil], nil, nil, nil, nil]
+ result = @a.(1, [2, [3, 4, 5, [6, [7, 8]], 9], [10, [11, 12]]])
+ result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12]
+ end
+
+ ruby_version_is ''...'3.0' do
+ evaluate <<-ruby do
+ @a = lambda { |*, **k| k }
+ ruby
+
+ @a.().should == {}
+ @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+
+ suppress_keyword_warning do
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({a: 1})
+ @a.(h).should == {a: 1}
+ end
+ end
+ end
+
+ ruby_version_is '3.0' do
+ evaluate <<-ruby do
+ @a = lambda { |*, **k| k }
+ ruby
+
+ @a.().should == {}
+ @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+
+ h = mock("keyword splat")
+ h.should_not_receive(:to_hash)
+ @a.(h).should == {}
+ end
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |*, &b| b }
+ ruby
+
+ @a.().should be_nil
+ @a.(1, 2, 3, 4).should be_nil
+ @a.(&(l = ->{})).should equal(l)
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a:, b:| [a, b] }
+ ruby
+
+ @a.(a: 1, b: 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a:, b: 1| [a, b] }
+ ruby
+
+ @a.(a: 1).should == [1, 1]
+ @a.(a: 1, b: 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a: 1, b:| [a, b] }
+ ruby
+
+ @a.(b: 0).should == [1, 0]
+ @a.(b: 2, a: 3).should == [3, 2]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda do |a: (@a = -> (a: 1) { a }), b:|
+ [a, b]
+ end
+ ruby
+
+ @a.(a: 2, b: 3).should == [2, 3]
+ @a.(b: 1).should == [@a, 1]
+
+ # Note the default value of a: in the original method.
+ @a.().should == 1
+ end
+
+ evaluate <<-ruby do
+ @a = lambda { |a: 1, b: 2| [a, b] }
+ ruby
+
+ @a.().should == [1, 2]
+ @a.(b: 3, a: 4).should == [4, 3]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda do |a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l|
+ [a, b, c, d, e, f, g, h, k, l]
+ end
+ ruby
+
+ result = @a.(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{}))
+ result.should == [9, 8, [7], [], 6, 5, 4, 3, {}, l]
+ end
+
+ evaluate <<-ruby do
+ @a = lambda do |a, b=1, *c, d, e:, f: 2, g:, **k, &l|
+ [a, b, c, d, e, f, g, k, l]
+ end
+ ruby
+
+ result = @a.(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{}))
+ result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l]
+ end
+ end
+end
diff --git a/spec/ruby/language/line_spec.rb b/spec/ruby/language/line_spec.rb
new file mode 100644
index 0000000000..fcadaa71d7
--- /dev/null
+++ b/spec/ruby/language/line_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/code_loading'
+require_relative 'shared/__LINE__'
+
+describe "The __LINE__ pseudo-variable" do
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("__LINE__ = 1") }.should raise_error(SyntaxError)
+ end
+
+ before :each do
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "equals the line number of the text inside an eval" do
+ eval <<-EOC
+ScratchPad << __LINE__
+
+# line 3
+
+ScratchPad << __LINE__
+ EOC
+
+ ScratchPad.recorded.should == [1, 5]
+ end
+end
+
+describe "The __LINE__ pseudo-variable" do
+ it_behaves_like :language___LINE__, :require, CodeLoadingSpecs::Method.new
+end
+
+describe "The __LINE__ pseudo-variable" do
+ it_behaves_like :language___LINE__, :require, Kernel
+end
+
+describe "The __LINE__ pseudo-variable" do
+ it_behaves_like :language___LINE__, :load, CodeLoadingSpecs::Method.new
+end
+
+describe "The __LINE__ pseudo-variable" do
+ it_behaves_like :language___LINE__, :load, Kernel
+end
diff --git a/spec/ruby/language/loop_spec.rb b/spec/ruby/language/loop_spec.rb
new file mode 100644
index 0000000000..fd17b53910
--- /dev/null
+++ b/spec/ruby/language/loop_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../spec_helper'
+
+describe "The loop expression" do
+ it "repeats the given block until a break is called" do
+ outer_loop = 0
+ loop do
+ outer_loop += 1
+ break if outer_loop == 10
+ end
+ outer_loop.should == 10
+ end
+
+ it "executes code in its own scope" do
+ loop do
+ inner_loop = 123
+ break
+ end
+ -> { inner_loop }.should raise_error(NameError)
+ end
+
+ it "returns the value passed to break if interrupted by break" do
+ loop do
+ break 123
+ end.should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ loop do
+ break
+ end.should == nil
+ end
+
+ it "skips to end of body with next" do
+ a = []
+ i = 0
+ loop do
+ break if (i+=1) >= 5
+ next if i == 3
+ a << i
+ end
+ a.should == [1, 2, 4]
+ end
+
+ it "restarts the current iteration with redo" do
+ a = []
+ loop do
+ a << 1
+ redo if a.size < 2
+ a << 2
+ break if a.size == 3
+ end
+ a.should == [1, 1, 2]
+ end
+
+ it "uses a spaghetti nightmare of redo, next and break" do
+ a = []
+ loop do
+ a << 1
+ redo if a.size == 1
+ a << 2
+ next if a.size == 3
+ a << 3
+ break if a.size > 6
+ end
+ a.should == [1, 1, 2, 1, 2, 3, 1, 2, 3]
+ end
+end
diff --git a/spec/ruby/language/magic_comment_spec.rb b/spec/ruby/language/magic_comment_spec.rb
new file mode 100644
index 0000000000..f2bf3a08e5
--- /dev/null
+++ b/spec/ruby/language/magic_comment_spec.rb
@@ -0,0 +1,92 @@
+require_relative '../spec_helper'
+
+# See core/kernel/eval_spec.rb for more magic comments specs for eval()
+describe :magic_comments, shared: true do
+ before :each do
+ @default = @method == :locale ? Encoding.find('locale') : Encoding::UTF_8
+ end
+
+ it "are optional" do
+ @object.call('no_magic_comment.rb').should == @default.name
+ end
+
+ it "are case-insensitive" do
+ @object.call('case_magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "must be at the first line" do
+ @object.call('second_line_magic_comment.rb').should == @default.name
+ end
+
+ it "must be the first token of the line" do
+ @object.call('second_token_magic_comment.rb').should == @default.name
+ end
+
+ it "can be after the shebang" do
+ @object.call('shebang_magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "can take Emacs style" do
+ @object.call('emacs_magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "can take vim style" do
+ @object.call('vim_magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "determine __ENCODING__" do
+ @object.call('magic_comment.rb').should == Encoding::Big5.name
+ end
+
+ it "do not cause bytes to be mangled by passing them through the wrong encoding" do
+ @object.call('bytes_magic_comment.rb').should == [167, 65, 166, 110].inspect
+ end
+end
+
+describe "Magic comments" do
+ describe "in stdin" do
+ it_behaves_like :magic_comments, :locale, -> file {
+ print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb")
+ ruby_exe(nil, args: "< #{fixture(__FILE__, file)}", options: "-r#{print_at_exit}")
+ }
+ end
+
+ platform_is_not :windows do
+ describe "in an -e argument" do
+ it_behaves_like :magic_comments, :locale, -> file {
+ print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb")
+ # Use UTF-8, as it is the default source encoding for files
+ code = File.read(fixture(__FILE__, file), encoding: 'utf-8')
+ IO.popen([*ruby_exe, "-r", print_at_exit, "-e", code], &:read)
+ }
+ end
+ end
+
+ describe "in the main file" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb")
+ ruby_exe(fixture(__FILE__, file), options: "-r#{print_at_exit}")
+ }
+ end
+
+ describe "in a loaded file" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ load fixture(__FILE__, file)
+ $magic_comment_result
+ }
+ end
+
+ describe "in a required file" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ require fixture(__FILE__, file)
+ $magic_comment_result
+ }
+ end
+
+ describe "in an eval" do
+ it_behaves_like :magic_comments, :UTF8, -> file {
+ # Use UTF-8, as it is the default source encoding for files
+ eval(File.read(fixture(__FILE__, file), encoding: 'utf-8'))
+ }
+ end
+end
diff --git a/spec/ruby/language/match_spec.rb b/spec/ruby/language/match_spec.rb
new file mode 100644
index 0000000000..ebf677cabc
--- /dev/null
+++ b/spec/ruby/language/match_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/match_operators'
+
+describe "The !~ operator" do
+ before :each do
+ @obj = OperatorImplementor.new
+ end
+
+ it "evaluates as a call to !~" do
+ expected = "hello world"
+
+ opval = (@obj !~ expected)
+ methodval = @obj.send(:"!~", expected)
+
+ opval.should == expected
+ methodval.should == expected
+ end
+end
+
+describe "The =~ operator" do
+ before :each do
+ @impl = OperatorImplementor.new
+ end
+
+ it "calls the =~ method" do
+ expected = "hello world"
+
+ opval = (@obj =~ expected)
+ methodval = @obj.send(:"=~", expected)
+
+ opval.should == expected
+ methodval.should == expected
+ end
+end
+
+describe "The =~ operator with named captures" do
+ before :each do
+ @regexp = /(?<matched>foo)(?<unmatched>bar)?/
+ @string = "foofoo"
+ end
+
+ describe "on syntax of /regexp/ =~ string_variable" do
+ it "sets local variables by the captured pairs" do
+ /(?<matched>foo)(?<unmatched>bar)?/ =~ @string
+ local_variables.should == [:matched, :unmatched]
+ matched.should == "foo"
+ unmatched.should == nil
+ end
+ end
+
+ describe "on syntax of 'string_literal' =~ /regexp/" do
+ it "does not set local variables" do
+ 'string literal' =~ /(?<matched>str)(?<unmatched>lit)?/
+ local_variables.should == []
+ end
+ end
+
+ describe "on syntax of string_variable =~ /regexp/" do
+ it "does not set local variables" do
+ @string =~ /(?<matched>foo)(?<unmatched>bar)?/
+ local_variables.should == []
+ end
+ end
+
+ describe "on syntax of regexp_variable =~ string_variable" do
+ it "does not set local variables" do
+ @regexp =~ @string
+ local_variables.should == []
+ end
+ end
+
+ describe "on the method calling" do
+ it "does not set local variables" do
+ @regexp.=~(@string)
+ local_variables.should == []
+
+ @regexp.send :=~, @string
+ local_variables.should == []
+ end
+ end
+end
diff --git a/spec/ruby/language/metaclass_spec.rb b/spec/ruby/language/metaclass_spec.rb
new file mode 100644
index 0000000000..fc83067977
--- /dev/null
+++ b/spec/ruby/language/metaclass_spec.rb
@@ -0,0 +1,143 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
+require_relative 'fixtures/metaclass'
+
+describe "self in a metaclass body (class << obj)" do
+ it "is TrueClass for true" do
+ class << true; self; end.should == TrueClass
+ end
+
+ it "is FalseClass for false" do
+ class << false; self; end.should == FalseClass
+ end
+
+ it "is NilClass for nil" do
+ class << nil; self; end.should == NilClass
+ end
+
+ it "raises a TypeError for numbers" do
+ -> { class << 1; self; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for symbols" do
+ -> { class << :symbol; self; end }.should raise_error(TypeError)
+ end
+
+ it "is a singleton Class instance" do
+ cls = class << mock('x'); self; end
+ cls.is_a?(Class).should == true
+ cls.should_not equal(Object)
+ end
+end
+
+describe "A constant on a metaclass" do
+ before :each do
+ @object = Object.new
+ class << @object
+ CONST = self
+ end
+ end
+
+ it "can be accessed after the metaclass body is reopened" do
+ class << @object
+ CONST.should == self
+ end
+ end
+
+ it "can be accessed via self::CONST" do
+ class << @object
+ self::CONST.should == self
+ end
+ end
+
+ it "can be accessed via const_get" do
+ class << @object
+ const_get(:CONST).should == self
+ end
+ end
+
+ it "is not defined on the object's class" do
+ @object.class.const_defined?(:CONST).should be_false
+ end
+
+ it "is not defined in the metaclass opener's scope" do
+ class << @object
+ CONST
+ end
+ -> { CONST }.should raise_error(NameError)
+ end
+
+ it "cannot be accessed via object::CONST" do
+ -> do
+ @object::CONST
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a NameError for anonymous_module::CONST" do
+ @object = Class.new
+ class << @object
+ CONST = 100
+ end
+
+ -> do
+ @object::CONST
+ end.should raise_error(NameError)
+ end
+
+ it "appears in the metaclass constant list" do
+ constants = class << @object; constants; end
+ constants.should include(:CONST)
+ end
+
+ it "does not appear in the object's class constant list" do
+ @object.class.constants.should_not include(:CONST)
+ end
+
+ it "is not preserved when the object is duped" do
+ @object = @object.dup
+
+ -> do
+ class << @object; CONST; end
+ end.should raise_error(NameError)
+ end
+
+ it "is preserved when the object is cloned" do
+ @object = @object.clone
+
+ class << @object
+ CONST.should_not be_nil
+ end
+ end
+end
+
+describe "calling methods on the metaclass" do
+
+ it "calls a method on the metaclass" do
+ MetaClassSpecs::A.cheese.should == 'edam'
+ MetaClassSpecs::B.cheese.should == 'stilton'
+ end
+
+ it "calls a method on the instance's metaclass" do
+ b = MetaClassSpecs::B.new
+ b_meta = MetaClassSpecs.metaclass_of b
+ b_meta.send(:define_method, :cheese) {'cheshire'}
+ b.cheese.should == 'cheshire'
+ end
+
+ it "calls a method in deeper chains of metaclasses" do
+ b = MetaClassSpecs::B.new
+ b_meta = MetaClassSpecs.metaclass_of b
+ b_meta_meta = MetaClassSpecs.metaclass_of b_meta
+ b_meta_meta.send(:define_method, :cheese) {'gouda'}
+ b_meta.cheese.should == 'gouda'
+
+ b_meta_meta_meta = MetaClassSpecs.metaclass_of b_meta_meta
+ b_meta_meta_meta.send(:define_method, :cheese) {'wensleydale'}
+ b_meta_meta.cheese.should == 'wensleydale'
+ end
+
+ it "calls a method defined on the metaclass of the metaclass" do
+ d_meta = MetaClassSpecs::D.singleton_class
+ d_meta.ham.should == 'iberico'
+ end
+end
diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb
new file mode 100644
index 0000000000..b80b314f6f
--- /dev/null
+++ b/spec/ruby/language/method_spec.rb
@@ -0,0 +1,1856 @@
+require_relative '../spec_helper'
+
+describe "A method send" do
+ evaluate <<-ruby do
+ def m(a) a end
+ ruby
+
+ a = b = m 1
+ a.should == 1
+ b.should == 1
+ end
+
+ context "with a single splatted Object argument" do
+ before :all do
+ def m(a) a end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ m(*x).should equal(x)
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1])
+
+ m(*x).should == 1
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ m(*x).should == x
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { m(*x) }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a leading splatted Object argument" do
+ before :all do
+ def m(a, b, *c, d, e) [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ m(*x, 1, 2, 3).should == [x, 1, [], 2, 3]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1])
+
+ m(*x, 2, 3, 4).should == [1, 2, [], 3, 4]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ m(*x, 2, 3, 4).should == [x, 2, [], 3, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { m(*x, 2, 3) }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a middle splatted Object argument" do
+ before :all do
+ def m(a, b, *c, d, e) [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ m(1, 2, *x, 3, 4).should == [1, 2, [x], 3, 4]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([5, 6, 7])
+
+ m(1, 2, *x, 3).should == [1, 2, [5, 6], 7, 3]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ m(1, 2, *x, 4).should == [1, 2, [], x, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { m(1, *x, 2, 3) }.should raise_error(TypeError)
+ end
+
+ it "copies the splatted array" do
+ args = [3, 4]
+ m(1, 2, *args, 4, 5).should == [1, 2, [3, 4], 4, 5]
+ m(1, 2, *args, 4, 5)[2].should_not equal(args)
+ end
+
+ it "allows an array being splatted to be modified by another argument" do
+ args = [3, 4]
+ m(1, args.shift, *args, 4, 5).should == [1, 3, [4], 4, 5]
+ end
+ end
+
+ context "with a trailing splatted Object argument" do
+ before :all do
+ def m(a, *b, c) [a, b, c] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ m(1, 2, *x).should == [1, [2], x]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([5, 6, 7])
+
+ m(1, 2, *x).should == [1, [2, 5, 6], 7]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ m(1, 2, *x, 4).should == [1, [2, x], 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { m(1, 2, *x) }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a block argument" do
+ before :all do
+ def m(x)
+ if block_given?
+ [true, yield(x + 'b')]
+ else
+ [false]
+ end
+ end
+ end
+
+ it "that refers to a proc passes the proc as the block" do
+ m('a', &-> y { y + 'c'}).should == [true, 'abc']
+ end
+
+ it "that is nil passes no block" do
+ m('a', &nil).should == [false]
+ end
+ end
+end
+
+describe "An element assignment method send" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ context "with a single splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.[]=(a, b) ScratchPad.record [a, b] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o[*x] = 1).should == 1
+ ScratchPad.recorded.should == [x, 1]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1])
+
+ (@o[*x] = 2).should == 2
+ ScratchPad.recorded.should == [1, 2]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o[*x] = 1).should == 1
+ ScratchPad.recorded.should == [x, 1]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o[*x] = 1 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a leading splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o[*x, 2, 3, 4] = 1).should == 1
+ ScratchPad.recorded.should == [x, 2, [3], 4, 1]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1, 2, 3])
+
+ (@o[*x, 4, 5] = 6).should == 6
+ ScratchPad.recorded.should == [1, 2, [3, 4], 5, 6]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o[*x, 2, 3, 4] = 5).should == 5
+ ScratchPad.recorded.should == [x, 2, [3], 4, 5]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o[*x, 2, 3] = 4 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a middle splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o[1, *x, 2, 3] = 4).should == 4
+ ScratchPad.recorded.should == [1, x, [2], 3, 4]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([2, 3])
+
+ (@o[1, *x, 4] = 5).should == 5
+ ScratchPad.recorded.should == [1, 2, [3], 4, 5]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o[1, 2, *x, 3] = 4).should == 4
+ ScratchPad.recorded.should == [1, 2, [x], 3, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o[1, 2, *x, 3] = 4 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a trailing splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o[1, 2, 3, 4, *x] = 5).should == 5
+ ScratchPad.recorded.should == [1, 2, [3, 4], x, 5]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([4, 5])
+
+ (@o[1, 2, 3, *x] = 6).should == 6
+ ScratchPad.recorded.should == [1, 2, [3, 4], 5, 6]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o[1, 2, 3, *x] = 4).should == 4
+ ScratchPad.recorded.should == [1, 2, [3], x, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o[1, 2, 3, *x] = 4 }.should raise_error(TypeError)
+ end
+ end
+end
+
+describe "An attribute assignment method send" do
+ context "with a single splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.m=(a, b) [a, b] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o.send :m=, *x, 1).should == [x, 1]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1])
+
+ (@o.send :m=, *x, 2).should == [1, 2]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o.send :m=, *x, 1).should == [x, 1]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o.send :m=, *x, 1 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a leading splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o.send :m=, *x, 2, 3, 4, 1).should == [x, 2, [3], 4, 1]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([1, 2, 3])
+
+ (@o.send :m=, *x, 4, 5, 6).should == [1, 2, [3, 4], 5, 6]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o.send :m=, *x, 2, 3, 4, 5).should == [x, 2, [3], 4, 5]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o.send :m=, *x, 2, 3, 4 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a middle splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o.send :m=, 1, *x, 2, 3, 4).should == [1, x, [2], 3, 4]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([2, 3])
+
+ (@o.send :m=, 1, *x, 4, 5).should == [1, 2, [3], 4, 5]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o.send :m=, 1, 2, *x, 3, 4).should == [1, 2, [x], 3, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o.send :m=, 1, 2, *x, 3, 4 }.should raise_error(TypeError)
+ end
+ end
+
+ context "with a trailing splatted Object argument" do
+ before :all do
+ @o = mock("element set receiver")
+ def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end
+ end
+
+ it "does not call #to_ary" do
+ x = mock("splat argument")
+ x.should_not_receive(:to_ary)
+
+ (@o.send :m=, 1, 2, 3, 4, *x, 5).should == [1, 2, [3, 4], x, 5]
+ end
+
+ it "calls #to_a" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return([4, 5])
+
+ (@o.send :m=, 1, 2, 3, *x, 6).should == [1, 2, [3, 4], 5, 6]
+ end
+
+ it "wraps the argument in an Array if #to_a returns nil" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(nil)
+
+ (@o.send :m=, 1, 2, 3, *x, 4).should == [1, 2, [3], x, 4]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("splat argument")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { @o.send :m=, 1, 2, 3, *x, 4 }.should raise_error(TypeError)
+ end
+ end
+end
+
+describe "A method" do
+ SpecEvaluate.desc = "for definition"
+
+ context "assigns no local variables" do
+ evaluate <<-ruby do
+ def m
+ end
+ ruby
+
+ m.should be_nil
+ end
+
+ evaluate <<-ruby do
+ def m()
+ end
+ ruby
+
+ m.should be_nil
+ end
+ end
+
+ context "assigns local variables from method parameters" do
+ evaluate <<-ruby do
+ def m(a) a end
+ ruby
+
+ m((args = 1, 2, 3)).should equal(args)
+ end
+
+ evaluate <<-ruby do
+ def m((a)) a end
+ ruby
+
+ m(1).should == 1
+ m([1, 2, 3]).should == 1
+ end
+
+ evaluate <<-ruby do
+ def m((*a, b)) [a, b] end
+ ruby
+
+ m(1).should == [[], 1]
+ m([1, 2, 3]).should == [[1, 2], 3]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1) a end
+ ruby
+
+ m().should == 1
+ m(2).should == 2
+ end
+
+ evaluate <<-ruby do
+ def m() end
+ ruby
+
+ m().should be_nil
+ m(*[]).should be_nil
+ m(**{}).should be_nil
+ end
+
+ evaluate <<-ruby do
+ def m(*) end
+ ruby
+
+ m().should be_nil
+ m(1).should be_nil
+ m(1, 2, 3).should be_nil
+ end
+
+ evaluate <<-ruby do
+ def m(*a) a end
+ ruby
+
+ m().should == []
+ m(1).should == [1]
+ m(1, 2, 3).should == [1, 2, 3]
+ m(*[]).should == []
+ m(**{}).should == []
+ end
+
+ evaluate <<-ruby do
+ def m(a:) a end
+ ruby
+
+ -> { m() }.should raise_error(ArgumentError)
+ m(a: 1).should == 1
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1) }.should raise_error(ArgumentError)
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a:, **kw) [a, kw] end
+ ruby
+
+ -> { m(b: 1) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1) a end
+ ruby
+
+ m().should == 1
+ m(a: 2).should == 2
+ end
+
+ evaluate <<-ruby do
+ def m(**) end
+ ruby
+
+ m().should be_nil
+ m(a: 1, b: 2).should be_nil
+ -> { m(1) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(**k) k end
+ ruby
+
+ m().should == {}
+ m(a: 1, b: 2).should == { a: 1, b: 2 }
+ m(*[]).should == {}
+ m(**{}).should == {}
+ suppress_warning {
+ eval "m(**{a: 1, b: 2}, **{a: 4, c: 7})"
+ }.should == { a: 4, b: 2, c: 7 }
+ -> { m(2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(**k); k end;
+ ruby
+
+ m("a" => 1).should == { "a" => 1 }
+ end
+
+ evaluate <<-ruby do
+ def m(&b) b end
+ ruby
+
+ m { }.should be_an_instance_of(Proc)
+ end
+
+ evaluate <<-ruby do
+ def m(a, b) [a, b] end
+ ruby
+
+ m(1, 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ def m(a, (b, c)) [a, b, c] end
+ ruby
+
+ m(1, 2).should == [1, 2, nil]
+ m(1, [2, 3, 4]).should == [1, 2, 3]
+ end
+
+ evaluate <<-ruby do
+ def m((a), (b)) [a, b] end
+ ruby
+
+ m(1, 2).should == [1, 2]
+ m([1, 2], [3, 4]).should == [1, 3]
+ end
+
+ evaluate <<-ruby do
+ def m((*), (*)) end
+ ruby
+
+ m(2, 3).should be_nil
+ m([2, 3, 4], [5, 6]).should be_nil
+ -> { m a: 1 }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m((*a), (*b)) [a, b] end
+ ruby
+
+ m(1, 2).should == [[1], [2]]
+ m([1, 2], [3, 4]).should == [[1, 2], [3, 4]]
+ end
+
+ evaluate <<-ruby do
+ def m((a, b), (c, d))
+ [a, b, c, d]
+ end
+ ruby
+
+ m(1, 2).should == [1, nil, 2, nil]
+ m([1, 2, 3], [4, 5, 6]).should == [1, 2, 4, 5]
+ end
+
+ evaluate <<-ruby do
+ def m((a, *b), (*c, d))
+ [a, b, c, d]
+ end
+ ruby
+
+ m(1, 2).should == [1, [], [], 2]
+ m([1, 2, 3], [4, 5, 6]).should == [1, [2, 3], [4, 5], 6]
+ end
+
+ evaluate <<-ruby do
+ def m((a, b, *c, d), (*e, f, g), (*h))
+ [a, b, c, d, e, f, g, h]
+ end
+ ruby
+
+ m(1, 2, 3).should == [1, nil, [], nil, [], 2, nil, [3]]
+ result = m([1, 2, 3], [4, 5, 6, 7, 8], [9, 10])
+ result.should == [1, 2, [], 3, [4, 5, 6], 7, 8, [9, 10]]
+ end
+
+ evaluate <<-ruby do
+ def m(a, (b, (c, *d), *e))
+ [a, b, c, d, e]
+ end
+ ruby
+
+ m(1, 2).should == [1, 2, nil, [], []]
+ m(1, [2, [3, 4, 5], 6, 7, 8]).should == [1, 2, 3, [4, 5], [6, 7, 8]]
+ end
+
+ evaluate <<-ruby do
+ def m(a, (b, (c, *d, (e, (*f)), g), (h, (i, j))))
+ [a, b, c, d, e, f, g, h, i, j]
+ end
+ ruby
+
+ m(1, 2).should == [1, 2, nil, [], nil, [nil], nil, nil, nil, nil]
+ result = m(1, [2, [3, 4, 5, [6, [7, 8]], 9], [10, [11, 12]]])
+ result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12]
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1) [a, b] end
+ ruby
+
+ m(2).should == [2, 1]
+ m(1, 2).should == [1, 2]
+ end
+
+ evaluate <<-ruby do
+ def m(a, *) a end
+ ruby
+
+ m(1).should == 1
+ m(1, 2, 3).should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(a, *b) [a, b] end
+ ruby
+
+ m(1).should == [1, []]
+ m(1, 2, 3).should == [1, [2, 3]]
+ end
+
+ evaluate <<-ruby do
+ def m(a, b:) [a, b] end
+ ruby
+
+ m(1, b: 2).should == [1, 2]
+ suppress_keyword_warning do
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ evaluate <<-ruby do
+ def m(a, b: 1) [a, b] end
+ ruby
+
+ m(2).should == [2, 1]
+ m(1, b: 2).should == [1, 2]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, 1]
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a, **) a end
+ ruby
+
+ m(1).should == 1
+ m(1, a: 2, b: 3).should == 1
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == {"a" => 1, b: 2}
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a, **k) [a, k] end
+ ruby
+
+ m(1).should == [1, {}]
+ m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, {}]
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ evaluate <<-ruby do
+ def m(a, b: 1) [a, b] end
+ ruby
+
+ m(2).should == [2, 1]
+ m(1, b: 2).should == [1, 2]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a, **) a end
+ ruby
+
+ m(1).should == 1
+ m(1, a: 2, b: 3).should == 1
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a, **k) [a, k] end
+ ruby
+
+ m(1).should == [1, {}]
+ m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a, &b) [a, b] end
+ ruby
+
+ m(1).should == [1, nil]
+ m(1, &(l = -> {})).should == [1, l]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, b) [a, b] end
+ ruby
+
+ m(2).should == [1, 2]
+ m(2, 3).should == [2, 3]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, *) a end
+ ruby
+
+ m().should == 1
+ m(2, 3, 4).should == 2
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, *b) [a, b] end
+ ruby
+
+ m().should == [1, []]
+ m(2, 3, 4).should == [2, [3, 4]]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, c)) [a, b, c] end
+ ruby
+
+ m(2).should == [1, 2, nil]
+ m(2, 3).should == [2, 3, nil]
+ m(2, [3, 4, 5]).should == [2, 3, 4]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, (c, *d))) [a, b, c, d] end
+ ruby
+
+ m(2).should == [1, 2, nil, []]
+ m(2, 3).should == [2, 3, nil, []]
+ m(2, [3, [4, 5, 6], 7]).should == [2, 3, 4, [5, 6]]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, (c, *d), *e)) [a, b, c, d, e] end
+ ruby
+
+ m(2).should == [1, 2, nil, [], []]
+ m(2, [3, 4, 5, 6]).should == [2, 3, 4, [], [5, 6]]
+ m(2, [3, [4, 5, 6], 7]).should == [2, 3, 4, [5, 6], [7]]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b), (c)) [a, b, c] end
+ ruby
+
+ m(2, 3).should == [1, 2, 3]
+ m(2, 3, 4).should == [2, 3, 4]
+ m(2, [3, 4], [5, 6, 7]).should == [2, 3, 5]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (*b), (*c)) [a, b, c] end
+ ruby
+
+ -> { m() }.should raise_error(ArgumentError)
+ -> { m(2) }.should raise_error(ArgumentError)
+ m(2, 3).should == [1, [2], [3]]
+ m(2, [3, 4], [5, 6]).should == [2, [3, 4], [5, 6]]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, c), (d, e)) [a, b, c, d, e] end
+ ruby
+
+ m(2, 3).should == [1, 2, nil, 3, nil]
+ m(2, [3, 4, 5], [6, 7, 8]).should == [2, 3, 4, 6, 7]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, *c), (*d, e))
+ [a, b, c, d, e]
+ end
+ ruby
+
+ m(1, 2).should == [1, 1, [], [], 2]
+ m(1, [2, 3], [4, 5, 6]).should == [1, 2, [3], [4, 5], 6]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, (b, *c), (d, (*e, f)))
+ [a, b, c, d, e, f]
+ end
+ ruby
+
+ m(1, 2).should == [1, 1, [], 2, [], nil]
+ m(nil, nil).should == [1, nil, [], nil, [], nil]
+ result = m([1, 2, 3], [4, 5, 6], [7, 8, 9])
+ result.should == [[1, 2, 3], 4, [5, 6], 7, [], 8]
+ end
+
+ ruby_version_is ""..."3.0" do
+ evaluate <<-ruby do
+ def m(a=1, b:) [a, b] end
+ ruby
+
+ m(b: 2).should == [1, 2]
+ m(2, b: 1).should == [2, 1]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [{"a" => 1}, 2]
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, b: 2) [a, b] end
+ ruby
+
+ m().should == [1, 2]
+ m(2).should == [2, 2]
+ m(b: 3).should == [1, 3]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [{"a" => 1}, 2]
+ end
+ end
+ end
+
+ ruby_version_is "3.0" do
+ evaluate <<-ruby do
+ def m(a=1, b:) [a, b] end
+ ruby
+
+ m(b: 2).should == [1, 2]
+ m(2, b: 1).should == [2, 1]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, b: 2) [a, b] end
+ ruby
+
+ m().should == [1, 2]
+ m(2).should == [2, 2]
+ m(b: 3).should == [1, 3]
+ -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError)
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, **) a end
+ ruby
+
+ m().should == 1
+ m(2, a: 1, b: 0).should == 2
+ m("a" => 1, a: 2).should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, **k) [a, k] end
+ ruby
+
+ m().should == [1, {}]
+ m(2, a: 1, b: 2).should == [2, {a: 1, b: 2}]
+ end
+
+ evaluate <<-ruby do
+ def m(a=1, &b) [a, b] end
+ ruby
+
+ m().should == [1, nil]
+ m(&(l = -> {})).should == [1, l]
+
+ p = -> {}
+ l = mock("to_proc")
+ l.should_receive(:to_proc).and_return(p)
+ m(&l).should == [1, p]
+ end
+
+ evaluate <<-ruby do
+ def m(*, a) a end
+ ruby
+
+ m(1).should == 1
+ m(1, 2, 3).should == 3
+ end
+
+ evaluate <<-ruby do
+ def m(*a, b) [a, b] end
+ ruby
+
+ m(1).should == [[], 1]
+ m(1, 2, 3).should == [[1, 2], 3]
+ end
+
+ ruby_version_is ""...'3.0' do
+ evaluate <<-ruby do
+ def m(*, a:) a end
+ ruby
+
+ m(a: 1).should == 1
+ m(1, 2, a: 3).should == 3
+ suppress_keyword_warning do
+ m("a" => 1, a: 2).should == 2
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*a, b:) [a, b] end
+ ruby
+
+ m(b: 1).should == [[], 1]
+ m(1, 2, b: 3).should == [[1, 2], 3]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*, a: 1) a end
+ ruby
+
+ m().should == 1
+ m(1, 2).should == 1
+ m(a: 2).should == 2
+ m(1, a: 2).should == 2
+ suppress_keyword_warning do
+ m("a" => 1, a: 2).should == 2
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*a, b: 1) [a, b] end
+ ruby
+
+ m().should == [[], 1]
+ m(1, 2, 3, b: 4).should == [[1, 2, 3], 4]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
+ end
+
+ a = mock("splat")
+ a.should_not_receive(:to_ary)
+ m(*a).should == [[a], 1]
+ end
+
+ evaluate <<-ruby do
+ def m(*a, **) a end
+ ruby
+
+ m().should == []
+ m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3]
+ m("a" => 1, a: 1).should == []
+ m(1, **{a: 2}).should == [1]
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash)
+ -> { m(**h) }.should raise_error(TypeError)
+ end
+
+ evaluate <<-ruby do
+ def m(*, **k) k end
+ ruby
+
+ m().should == {}
+ m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+ m("a" => 1, a: 1).should == {"a" => 1, a: 1}
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({a: 1})
+ suppress_warning do
+ m(h).should == {a: 1}
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a = nil, **k) [a, k] end
+ ruby
+
+ m().should == [nil, {}]
+ m("a" => 1).should == [nil, {"a" => 1}]
+ m(a: 1).should == [nil, {a: 1}]
+ m("a" => 1, a: 1).should == [nil, {"a" => 1, a: 1}]
+ m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}]
+ suppress_warning do
+ m({a: 1}, {}).should == [{a: 1}, {}]
+
+ h = {"a" => 1, b: 2}
+ m(h).should == [{"a" => 1}, {b: 2}]
+ h.should == {"a" => 1, b: 2}
+
+ h = {"a" => 1}
+ m(h).first.should == h
+
+ h = {}
+ r = m(h)
+ r.first.should be_nil
+ r.last.should == {}
+
+ hh = {}
+ h = mock("keyword splat empty hash")
+ h.should_receive(:to_hash).and_return(hh)
+ r = m(h)
+ r.first.should be_nil
+ r.last.should == {}
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({"a" => 1, a: 2})
+ m(h).should == [{"a" => 1}, {a: 2}]
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*a, **k) [a, k] end
+ ruby
+
+ m().should == [[], {}]
+ m(1).should == [[1], {}]
+ m(a: 1, b: 2).should == [[], {a: 1, b: 2}]
+ m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}]
+
+ m("a" => 1).should == [[], {"a" => 1}]
+ m(a: 1).should == [[], {a: 1}]
+ m("a" => 1, a: 1).should == [[], {"a" => 1, a: 1}]
+ m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}]
+ suppress_warning do
+ m({a: 1}, {}).should == [[{a: 1}], {}]
+ end
+ m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}]
+
+ bo = BasicObject.new
+ def bo.to_a; [1, 2, 3]; end
+ def bo.to_hash; {:b => 2, :c => 3}; end
+
+ m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}]
+ end
+
+ evaluate <<-ruby do
+ def m(*, a:) a end
+ ruby
+
+ m(a: 1).should == 1
+ m(1, 2, a: 3).should == 3
+ suppress_keyword_warning do
+ m("a" => 1, a: 2).should == 2
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*a, b:) [a, b] end
+ ruby
+
+ m(b: 1).should == [[], 1]
+ m(1, 2, b: 3).should == [[1, 2], 3]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*, a: 1) a end
+ ruby
+
+ m().should == 1
+ m(1, 2).should == 1
+ m(a: 2).should == 2
+ m(1, a: 2).should == 2
+ suppress_keyword_warning do
+ m("a" => 1, a: 2).should == 2
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*a, b: 1) [a, b] end
+ ruby
+
+ m().should == [[], 1]
+ m(1, 2, 3, b: 4).should == [[1, 2, 3], 4]
+ suppress_keyword_warning do
+ m("a" => 1, b: 2).should == [[{"a" => 1}], 2]
+ end
+
+ a = mock("splat")
+ a.should_not_receive(:to_ary)
+ m(*a).should == [[a], 1]
+ end
+
+ evaluate <<-ruby do
+ def m(*a, **) a end
+ ruby
+
+ m().should == []
+ m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3]
+ m("a" => 1, a: 1).should == []
+ m(1, **{a: 2}).should == [1]
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash)
+ -> { m(**h) }.should raise_error(TypeError)
+ end
+
+ evaluate <<-ruby do
+ def m(*, **k) k end
+ ruby
+
+ m().should == {}
+ m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5}
+ m("a" => 1, a: 1).should == {"a" => 1, a: 1}
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({a: 1})
+ suppress_keyword_warning do
+ m(h).should == {a: 1}
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a = nil, **k) [a, k] end
+ ruby
+
+ m().should == [nil, {}]
+ m("a" => 1).should == [nil, {"a" => 1}]
+ m(a: 1).should == [nil, {a: 1}]
+ m("a" => 1, a: 1).should == [nil, {"a" => 1, a: 1}]
+ m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}]
+ suppress_keyword_warning do
+ m({a: 1}, {}).should == [{a: 1}, {}]
+ end
+
+ h = {"a" => 1, b: 2}
+ suppress_keyword_warning do
+ m(h).should == [{"a" => 1}, {b: 2}]
+ end
+ h.should == {"a" => 1, b: 2}
+
+ h = {"a" => 1}
+ m(h).first.should == h
+
+ h = {}
+ suppress_keyword_warning do
+ m(h).should == [nil, {}]
+ end
+
+ hh = {}
+ h = mock("keyword splat empty hash")
+ h.should_receive(:to_hash).and_return({a: 1})
+ suppress_keyword_warning do
+ m(h).should == [nil, {a: 1}]
+ end
+
+ h = mock("keyword splat")
+ h.should_receive(:to_hash).and_return({"a" => 1})
+ m(h).should == [h, {}]
+ end
+
+ evaluate <<-ruby do
+ def m(*a, **k) [a, k] end
+ ruby
+
+ m().should == [[], {}]
+ m(1).should == [[1], {}]
+ m(a: 1, b: 2).should == [[], {a: 1, b: 2}]
+ m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}]
+
+ m("a" => 1).should == [[], {"a" => 1}]
+ m(a: 1).should == [[], {a: 1}]
+ m("a" => 1, a: 1).should == [[], {"a" => 1, a: 1}]
+ m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}]
+ suppress_keyword_warning do
+ m({a: 1}, {}).should == [[{a: 1}], {}]
+ end
+ m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}]
+
+ bo = BasicObject.new
+ def bo.to_a; [1, 2, 3]; end
+ def bo.to_hash; {:b => 2, :c => 3}; end
+
+ m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}]
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(*, &b) b end
+ ruby
+
+ m().should be_nil
+ m(1, 2, 3, 4).should be_nil
+ m(&(l = ->{})).should equal(l)
+ end
+
+ evaluate <<-ruby do
+ def m(*a, &b) [a, b] end
+ ruby
+
+ m().should == [[], nil]
+ m(1).should == [[1], nil]
+ m(1, 2, 3, &(l = -> {})).should == [[1, 2, 3], l]
+ end
+
+ evaluate <<-ruby do
+ def m(a:, b:) [a, b] end
+ ruby
+
+ m(a: 1, b: 2).should == [1, 2]
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a:, b: 1) [a, b] end
+ ruby
+
+ m(a: 1).should == [1, 1]
+ m(a: 1, b: 2).should == [1, 2]
+ suppress_keyword_warning do
+ -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError)
+ end
+ end
+
+ evaluate <<-ruby do
+ def m(a:, **) a end
+ ruby
+
+ m(a: 1).should == 1
+ m(a: 1, b: 2).should == 1
+ m("a" => 1, a: 1, b: 2).should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(a:, **k) [a, k] end
+ ruby
+
+ m(a: 1).should == [1, {}]
+ m(a: 1, b: 2, c: 3).should == [1, {b: 2, c: 3}]
+ m("a" => 1, a: 1, b: 2).should == [1, {"a" => 1, b: 2}]
+ end
+
+ evaluate <<-ruby do
+ def m(a:, &b) [a, b] end
+ ruby
+
+ m(a: 1).should == [1, nil]
+ m(a: 1, &(l = ->{})).should == [1, l]
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1, b:) [a, b] end
+ ruby
+
+ m(b: 0).should == [1, 0]
+ m(b: 2, a: 3).should == [3, 2]
+ end
+
+ evaluate <<-ruby do
+ def m(a: def m(a: 1) a end, b:)
+ [a, b]
+ end
+ ruby
+
+ m(a: 2, b: 3).should == [2, 3]
+ m(b: 1).should == [:m, 1]
+
+ # Note the default value of a: in the original method.
+ m().should == 1
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1, b: 2) [a, b] end
+ ruby
+
+ m().should == [1, 2]
+ m(b: 3, a: 4).should == [4, 3]
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1, **) a end
+ ruby
+
+ m().should == 1
+ m(a: 2, b: 1).should == 2
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1, **k) [a, k] end
+ ruby
+
+ m(b: 2, c: 3).should == [1, {b: 2, c: 3}]
+ end
+
+ evaluate <<-ruby do
+ def m(a: 1, &b) [a, b] end
+ ruby
+
+ m(&(l = ->{})).should == [1, l]
+ m().should == [1, nil]
+ end
+
+ evaluate <<-ruby do
+ def m(**, &b) b end
+ ruby
+
+ m(a: 1, b: 2, &(l = ->{})).should == l
+ end
+
+ evaluate <<-ruby do
+ def m(**k, &b) [k, b] end
+ ruby
+
+ m(a: 1, b: 2).should == [{ a: 1, b: 2}, nil]
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l)
+ [a, b, c, d, e, f, g, h, k, l]
+ end
+ ruby
+
+ result = m(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{}))
+ result.should == [9, 8, [7], [], 6, 5, 4, 3, {}, l]
+ end
+
+ evaluate <<-ruby do
+ def m(a, b=1, *c, d, e:, f: 2, g:, **k, &l)
+ [a, b, c, d, e, f, g, k, l]
+ end
+ ruby
+
+ result = m(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{}))
+ result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l]
+ end
+
+ evaluate <<-ruby do
+ def m(a, **nil); a end;
+ ruby
+
+ m({a: 1}).should == {a: 1}
+ m({"a" => 1}).should == {"a" => 1}
+
+ -> { m(a: 1) }.should raise_error(ArgumentError)
+ -> { m(**{a: 1}) }.should raise_error(ArgumentError)
+ -> { m("a" => 1) }.should raise_error(ArgumentError)
+ end
+
+ ruby_version_is ''...'3.0' do
+ evaluate <<-ruby do
+ def m(a, b = nil, c = nil, d, e: nil, **f)
+ [a, b, c, d, e, f]
+ end
+ ruby
+
+ result = m(1, 2)
+ result.should == [1, nil, nil, 2, nil, {}]
+
+ suppress_warning do
+ result = m(1, 2, {foo: :bar})
+ result.should == [1, nil, nil, 2, nil, {foo: :bar}]
+ end
+
+ result = m(1, {foo: :bar})
+ result.should == [1, nil, nil, {foo: :bar}, nil, {}]
+ end
+ end
+
+ ruby_version_is '3.0' do
+ evaluate <<-ruby do
+ def m(a, b = nil, c = nil, d, e: nil, **f)
+ [a, b, c, d, e, f]
+ end
+ ruby
+
+ result = m(1, 2)
+ result.should == [1, nil, nil, 2, nil, {}]
+
+ result = m(1, 2, {foo: :bar})
+ result.should == [1, 2, nil, {foo: :bar}, nil, {}]
+
+ result = m(1, {foo: :bar})
+ result.should == [1, nil, nil, {foo: :bar}, nil, {}]
+ end
+ end
+ end
+
+ context 'when passing an empty keyword splat to a method that does not accept keywords' do
+ evaluate <<-ruby do
+ def m(*a); a; end
+ ruby
+
+ h = {}
+ m(**h).should == []
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ context 'when passing an empty keyword splat to a method that does not accept keywords' do
+ evaluate <<-ruby do
+ def m(a); a; end
+ ruby
+ h = {}
+
+ -> do
+ m(**h).should == {}
+ end.should complain(/warning: Passing the keyword argument as the last hash parameter is deprecated/)
+ end
+ end
+ end
+
+ ruby_version_is ''...'3.0' do
+ context "assigns keyword arguments from a passed Hash without modifying it" do
+ evaluate <<-ruby do
+ def m(a: nil); a; end
+ ruby
+
+ options = {a: 1}.freeze
+ -> do
+ suppress_warning do
+ m(options).should == 1
+ end
+ end.should_not raise_error
+ options.should == {a: 1}
+ end
+ end
+ end
+
+ ruby_version_is '3.0' do
+ context 'when passing an empty keyword splat to a method that does not accept keywords' do
+ evaluate <<-ruby do
+ def m(a); a; end
+ ruby
+ h = {}
+
+ -> do
+ m(**h).should == {}
+ end.should raise_error(ArgumentError)
+ end
+ end
+
+ context "raises ArgumentError if passing hash as keyword arguments" do
+ evaluate <<-ruby do
+ def m(a: nil); a; end
+ ruby
+
+ options = {a: 1}.freeze
+ -> do
+ m(options)
+ end.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ it "assigns the last Hash to the last optional argument if the Hash contains non-Symbol keys and is not passed as keywords" do
+ def m(a = nil, b = {}, v: false)
+ [a, b, v]
+ end
+
+ h = { "key" => "value" }
+ m(:a, h).should == [:a, h, false]
+ m(:a, h, v: true).should == [:a, h, true]
+ m(v: true).should == [nil, {}, true]
+ end
+end
+
+describe "A method call with a space between method name and parentheses" do
+ before(:each) do
+ def m(*args)
+ args
+ end
+
+ def n(value, &block)
+ [value, block.call]
+ end
+ end
+
+ context "when no arguments provided" do
+ it "assigns nil" do
+ args = m ()
+ args.should == [nil]
+ end
+ end
+
+ context "when a single argument provided" do
+ it "assigns it" do
+ args = m (1 == 1 ? true : false)
+ args.should == [true]
+ end
+ end
+
+ context "when 2+ arguments provided" do
+ it "raises a syntax error" do
+ -> {
+ eval("m (1, 2)")
+ }.should raise_error(SyntaxError)
+
+ -> {
+ eval("m (1, 2, 3)")
+ }.should raise_error(SyntaxError)
+ end
+ end
+
+ it "allows to pass a block with curly braces" do
+ args = n () { :block_value }
+ args.should == [nil, :block_value]
+
+ args = n (1) { :block_value }
+ args.should == [1, :block_value]
+ end
+
+ it "allows to pass a block with do/end" do
+ args = n () do
+ :block_value
+ end
+ args.should == [nil, :block_value]
+
+ args = n (1) do
+ :block_value
+ end
+ args.should == [1, :block_value]
+ end
+end
+
+describe "An array-dereference method ([])" do
+ SpecEvaluate.desc = "for definition"
+
+ context "received the passed-in block" do
+ evaluate <<-ruby do
+ def [](*, &b)
+ b.call
+ end
+ ruby
+ pr = proc {:ok}
+
+ self[&pr].should == :ok
+ self['foo', &pr].should == :ok
+ self.[](&pr).should == :ok
+ self.[]('foo', &pr).should == :ok
+ end
+
+ evaluate <<-ruby do
+ def [](*)
+ yield
+ end
+ ruby
+ pr = proc {:ok}
+
+ self[&pr].should == :ok
+ self['foo', &pr].should == :ok
+ self.[](&pr).should == :ok
+ self.[]('foo', &pr).should == :ok
+ end
+ end
+end
+
+ruby_version_is "3.0" do
+ describe "An endless method definition" do
+ context "without arguments" do
+ evaluate <<-ruby do
+ def m() = 42
+ ruby
+
+ m.should == 42
+ end
+
+ context "without parenthesis" do
+ evaluate <<-ruby do
+ def m = 42
+ ruby
+
+ m.should == 42
+ end
+ end
+ end
+
+ context "with arguments" do
+ evaluate <<-ruby do
+ def m(a, b) = a + b
+ ruby
+
+ m(1, 4).should == 5
+ end
+ end
+
+ context "with multiline body" do
+ evaluate <<-ruby do
+ def m(n) =
+ if n > 2
+ m(n - 2) + m(n - 1)
+ else
+ 1
+ end
+ ruby
+
+ m(6).should == 8
+ end
+ end
+
+ context "with args forwarding" do
+ evaluate <<-ruby do
+ def mm(word, num:)
+ word * num
+ end
+
+ def m(...) = mm(...) + mm(...)
+ ruby
+
+ m("meow", num: 2).should == "meow" * 4
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ context "inside 'endless' method definitions" do
+ it "does not allow method calls without parenthesis" do
+ -> {
+ eval("def greet(person) = 'Hi, '.concat person")
+ }.should raise_error(SyntaxError)
+ end
+ end
+ end
+ end
+
+ describe "Keyword arguments are now separated from positional arguments" do
+ context "when the method has only positional parameters" do
+ it "treats incoming keyword arguments as positional for compatibility" do
+ def foo(a, b, c, hsh)
+ hsh[:key]
+ end
+
+ foo(1, 2, 3, key: 42).should == 42
+ end
+ end
+
+ context "when the method takes a ** parameter" do
+ it "captures the passed literal keyword arguments" do
+ def foo(a, b, c, **hsh)
+ hsh[:key]
+ end
+
+ foo(1, 2, 3, key: 42).should == 42
+ end
+
+ it "captures the passed ** keyword arguments" do
+ def foo(a, b, c, **hsh)
+ hsh[:key]
+ end
+
+ h = { key: 42 }
+ foo(1, 2, 3, **h).should == 42
+ end
+
+ it "does not convert a positional Hash to keyword arguments" do
+ def foo(a, b, c, **hsh)
+ hsh[:key]
+ end
+
+ -> {
+ foo(1, 2, 3, { key: 42 })
+ }.should raise_error(ArgumentError, 'wrong number of arguments (given 4, expected 3)')
+ end
+ end
+
+ context "when the method takes a key: parameter" do
+ context "when it's called with a positional Hash and no **" do
+ it "raises ArgumentError" do
+ def foo(a, b, c, key: 1)
+ key
+ end
+
+ -> {
+ foo(1, 2, 3, { key: 42 })
+ }.should raise_error(ArgumentError, 'wrong number of arguments (given 4, expected 3)')
+ end
+ end
+
+ context "when it's called with **" do
+ it "captures the passed keyword arguments" do
+ def foo(a, b, c, key: 1)
+ key
+ end
+
+ h = { key: 42 }
+ foo(1, 2, 3, **h).should == 42
+ end
+ end
+ end
+ end
+end
+
+ruby_version_is "3.1" do
+ describe "kwarg with omitted value in a method call" do
+ context "accepts short notation 'kwarg' in method call" do
+ evaluate <<-ruby do
+ def call(*args, **kwargs) = [args, kwargs]
+ ruby
+
+ a, b, c = 1, 2, 3
+ arr, h = eval('call a:')
+ h.should == {a: 1}
+ arr.should == []
+
+ arr, h = eval('call(a:, b:, c:)')
+ h.should == {a: 1, b: 2, c: 3}
+ arr.should == []
+
+ arr, h = eval('call(a:, b: 10, c:)')
+ h.should == {a: 1, b: 10, c: 3}
+ arr.should == []
+ end
+ end
+
+ context "with methods and local variables" do
+ evaluate <<-ruby do
+ def call(*args, **kwargs) = [args, kwargs]
+
+ def bar
+ "baz"
+ end
+
+ def foo(val)
+ call bar:, val:
+ end
+ ruby
+
+ foo(1).should == [[], {bar: "baz", val: 1}]
+ end
+ end
+ end
+
+ describe "Inside 'endless' method definitions" do
+ it "allows method calls without parenthesis" do
+ eval <<-ruby
+ def greet(person) = "Hi, ".concat person
+ ruby
+
+ greet("Homer").should == "Hi, Homer"
+ end
+ end
+end
diff --git a/spec/ruby/language/module_spec.rb b/spec/ruby/language/module_spec.rb
new file mode 100644
index 0000000000..e361bf58f5
--- /dev/null
+++ b/spec/ruby/language/module_spec.rb
@@ -0,0 +1,110 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/module'
+
+describe "The module keyword" do
+ it "creates a new module without semicolon" do
+ module ModuleSpecsKeywordWithoutSemicolon end
+ ModuleSpecsKeywordWithoutSemicolon.should be_an_instance_of(Module)
+ end
+
+ it "creates a new module with a non-qualified constant name" do
+ module ModuleSpecsToplevel; end
+ ModuleSpecsToplevel.should be_an_instance_of(Module)
+ end
+
+ it "creates a new module with a qualified constant name" do
+ module ModuleSpecs::Nested; end
+ ModuleSpecs::Nested.should be_an_instance_of(Module)
+ end
+
+ it "creates a new module with a variable qualified constant name" do
+ m = Module.new
+ module m::N; end
+ m::N.should be_an_instance_of(Module)
+ end
+
+ it "reopens an existing module" do
+ module ModuleSpecs; Reopened = true; end
+ ModuleSpecs::Reopened.should be_true
+ end
+
+ ruby_version_is '3.2' do
+ it "does not reopen a module included in Object" do
+ module IncludedModuleSpecs; Reopened = true; end
+ ModuleSpecs::IncludedInObject::IncludedModuleSpecs.should_not == Object::IncludedModuleSpecs
+ end
+ end
+
+ ruby_version_is ''...'3.2' do
+ it "reopens a module included in Object" do
+ module IncludedModuleSpecs; Reopened = true; end
+ ModuleSpecs::IncludedInObject::IncludedModuleSpecs::Reopened.should be_true
+ end
+ end
+
+ it "raises a TypeError if the constant is a Class" do
+ -> do
+ module ModuleSpecs::Modules::Klass; end
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is a String" do
+ -> { module ModuleSpecs::Modules::A; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is an Integer" do
+ -> { module ModuleSpecs::Modules::B; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is nil" do
+ -> { module ModuleSpecs::Modules::C; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is true" do
+ -> { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the constant is false" do
+ -> { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError)
+ end
+end
+
+describe "Assigning an anonymous module to a constant" do
+ it "sets the name of the module" do
+ mod = Module.new
+ mod.name.should be_nil
+
+ ::ModuleSpecs_CS1 = mod
+ mod.name.should == "ModuleSpecs_CS1"
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "does not set the name of a module scoped by an anonymous module" do
+ a, b = Module.new, Module.new
+ a::B = b
+ b.name.should be_nil
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "sets the name of a module scoped by an anonymous module" do
+ a, b = Module.new, Module.new
+ a::B = b
+ b.name.should.end_with? '::B'
+ end
+ end
+
+ it "sets the name of contained modules when assigning a toplevel anonymous module" do
+ a, b, c, d = Module.new, Module.new, Module.new, Module.new
+ a::B = b
+ a::B::C = c
+ a::B::C::E = c
+ a::D = d
+
+ ::ModuleSpecs_CS2 = a
+ a.name.should == "ModuleSpecs_CS2"
+ b.name.should == "ModuleSpecs_CS2::B"
+ c.name.should == "ModuleSpecs_CS2::B::C"
+ d.name.should == "ModuleSpecs_CS2::D"
+ end
+end
diff --git a/spec/ruby/language/next_spec.rb b/spec/ruby/language/next_spec.rb
new file mode 100644
index 0000000000..6fbfc4a54d
--- /dev/null
+++ b/spec/ruby/language/next_spec.rb
@@ -0,0 +1,410 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/next'
+
+describe "The next statement from within the block" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "ends block execution" do
+ a = []
+ -> {
+ a << 1
+ next
+ a << 2
+ }.call
+ a.should == [1]
+ end
+
+ it "causes block to return nil if invoked without arguments" do
+ -> { 123; next; 456 }.call.should == nil
+ end
+
+ it "causes block to return nil if invoked with an empty expression" do
+ -> { next (); 456 }.call.should be_nil
+ end
+
+ it "returns the argument passed" do
+ -> { 123; next 234; 345 }.call.should == 234
+ end
+
+ it "returns to the invoking method" do
+ NextSpecs.yielding_method(nil) { next }.should == :method_return_value
+ end
+
+ it "returns to the invoking method, with the specified value" do
+ NextSpecs.yielding_method(nil) {
+ next nil;
+ fail("next didn't end the block execution")
+ }.should == :method_return_value
+
+ NextSpecs.yielding_method(1) {
+ next 1
+ fail("next didn't end the block execution")
+ }.should == :method_return_value
+
+ NextSpecs.yielding_method([1, 2, 3]) {
+ next 1, 2, 3
+ fail("next didn't end the block execution")
+ }.should == :method_return_value
+ end
+
+ it "returns to the currently yielding method in case of chained calls" do
+ class ChainedNextTest
+ def self.meth_with_yield(&b)
+ yield.should == :next_return_value
+ :method_return_value
+ end
+ def self.invoking_method(&b)
+ meth_with_yield(&b)
+ end
+ def self.enclosing_method
+ invoking_method do
+ next :next_return_value
+ :wrong_return_value
+ end
+ end
+ end
+
+ ChainedNextTest.enclosing_method.should == :method_return_value
+ end
+
+ it "causes ensure blocks to run" do
+ [1].each do |i|
+ begin
+ ScratchPad << :begin
+ next
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "skips following code outside an exception block" do
+ 3.times do |i|
+ begin
+ ScratchPad << :begin
+ next if i == 0
+ break if i == 2
+ ScratchPad << :begin_end
+ ensure
+ ScratchPad << :ensure
+ end
+
+ ScratchPad << :after
+ end
+
+ ScratchPad.recorded.should == [
+ :begin, :ensure, :begin, :begin_end, :ensure, :after, :begin, :ensure]
+ end
+
+ it "passes the value returned by a method with omitted parenthesis and passed block" do
+ obj = NextSpecs::Block.new
+ -> { next obj.method :value do |x| x end }.call.should == :value
+ end
+end
+
+describe "The next statement" do
+ describe "in a method" do
+ it "is invalid and raises a SyntaxError" do
+ -> {
+ eval("def m; next; end")
+ }.should raise_error(SyntaxError)
+ end
+ end
+end
+
+describe "The next statement" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ describe "in a while loop" do
+ describe "when not passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.while_next(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.while_within_iter(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ describe "when passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.while_next(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.while_within_iter(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ it "causes nested ensure blocks to run" do
+ x = true
+ while x
+ begin
+ ScratchPad << :outer_begin
+ x = false
+ begin
+ ScratchPad << :inner_begin
+ next
+ ensure
+ ScratchPad << :inner_ensure
+ end
+ ensure
+ ScratchPad << :outer_ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:outer_begin, :inner_begin, :inner_ensure, :outer_ensure]
+ end
+
+ it "causes ensure blocks to run when mixed with break" do
+ x = 1
+ while true
+ begin
+ ScratchPad << :begin
+ break if x > 1
+ x += 1
+ next
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure, :begin, :ensure]
+ end
+ end
+
+ describe "in an until loop" do
+ describe "when not passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.until_next(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.until_within_iter(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ describe "when passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.until_next(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.until_within_iter(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ it "causes nested ensure blocks to run" do
+ x = false
+ until x
+ begin
+ ScratchPad << :outer_begin
+ x = true
+ begin
+ ScratchPad << :inner_begin
+ next
+ ensure
+ ScratchPad << :inner_ensure
+ end
+ ensure
+ ScratchPad << :outer_ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:outer_begin, :inner_begin, :inner_ensure, :outer_ensure]
+ end
+
+ it "causes ensure blocks to run when mixed with break" do
+ x = 1
+ until false
+ begin
+ ScratchPad << :begin
+ break if x > 1
+ x += 1
+ next
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure, :begin, :ensure]
+ end
+ end
+
+ describe "in a loop" do
+ describe "when not passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.loop_next(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.loop_within_iter(false)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ describe "when passed an argument" do
+ it "causes ensure blocks to run" do
+ NextSpecs.loop_next(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "causes ensure blocks to run when nested in an block" do
+ NextSpecs.loop_within_iter(true)
+
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+ end
+
+ it "causes nested ensure blocks to run" do
+ x = 1
+ loop do
+ break if x == 2
+
+ begin
+ ScratchPad << :outer_begin
+ begin
+ ScratchPad << :inner_begin
+ x += 1
+ next
+ ensure
+ ScratchPad << :inner_ensure
+ end
+ ensure
+ ScratchPad << :outer_ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:outer_begin, :inner_begin, :inner_ensure, :outer_ensure]
+ end
+
+ it "causes ensure blocks to run when mixed with break" do
+ x = 1
+ loop do
+ begin
+ ScratchPad << :begin
+ break if x > 1
+ x += 1
+ next
+ ensure
+ ScratchPad << :ensure
+ end
+ end
+
+ ScratchPad.recorded.should == [:begin, :ensure, :begin, :ensure]
+ end
+ end
+end
+
+describe "Assignment via next" do
+ it "assigns objects" do
+ def r(val); a = yield(); val.should == a; end
+ r(nil){next}
+ r(nil){next nil}
+ r(1){next 1}
+ r([]){next []}
+ r([1]){next [1]}
+ r([nil]){next [nil]}
+ r([[]]){next [[]]}
+ r([]){next [*[]]}
+ r([1]){next [*[1]]}
+ r([1,2]){next [*[1,2]]}
+ end
+
+ it "assigns splatted objects" do
+ def r(val); a = yield(); val.should == a; end
+ r([]){next *nil}
+ r([1]){next *1}
+ r([]){next *[]}
+ r([1]){next *[1]}
+ r([nil]){next *[nil]}
+ r([[]]){next *[[]]}
+ r([]){next *[*[]]}
+ r([1]){next *[*[1]]}
+ r([1,2]){next *[*[1,2]]}
+ end
+
+ it "assigns objects to a splatted reference" do
+ def r(val); *a = yield(); val.should == a; end
+ r([nil]){next}
+ r([nil]){next nil}
+ r([1]){next 1}
+ r([]){next []}
+ r([1]){next [1]}
+ r([nil]){next [nil]}
+ r([[]]){next [[]]}
+ r([1,2]){next [1,2]}
+ r([]){next [*[]]}
+ r([1]){next [*[1]]}
+ r([1,2]){next [*[1,2]]}
+ end
+
+ it "assigns splatted objects to a splatted reference via a splatted yield" do
+ def r(val); *a = *yield(); val.should == a; end
+ r([]){next *nil}
+ r([1]){next *1}
+ r([]){next *[]}
+ r([1]){next *[1]}
+ r([nil]){next *[nil]}
+ r([[]]){next *[[]]}
+ r([1,2]){next *[1,2]}
+ r([]){next *[*[]]}
+ r([1]){next *[*[1]]}
+ r([1,2]){next *[*[1,2]]}
+ end
+
+ it "assigns objects to multiple variables" do
+ def r(val); a,b,*c = yield(); val.should == [a,b,c]; end
+ r([nil,nil,[]]){next}
+ r([nil,nil,[]]){next nil}
+ r([1,nil,[]]){next 1}
+ r([nil,nil,[]]){next []}
+ r([1,nil,[]]){next [1]}
+ r([nil,nil,[]]){next [nil]}
+ r([[],nil,[]]){next [[]]}
+ r([1,2,[]]){next [1,2]}
+ r([nil,nil,[]]){next [*[]]}
+ r([1,nil,[]]){next [*[1]]}
+ r([1,2,[]]){next [*[1,2]]}
+ end
+
+ it "assigns splatted objects to multiple variables" do
+ def r(val); a,b,*c = *yield(); val.should == [a,b,c]; end
+ r([nil,nil,[]]){next *nil}
+ r([1,nil,[]]){next *1}
+ r([nil,nil,[]]){next *[]}
+ r([1,nil,[]]){next *[1]}
+ r([nil,nil,[]]){next *[nil]}
+ r([[],nil,[]]){next *[[]]}
+ r([1,2,[]]){next *[1,2]}
+ r([nil,nil,[]]){next *[*[]]}
+ r([1,nil,[]]){next *[*[1]]}
+ r([1,2,[]]){next *[*[1,2]]}
+ end
+end
diff --git a/spec/ruby/language/not_spec.rb b/spec/ruby/language/not_spec.rb
new file mode 100644
index 0000000000..052af9b256
--- /dev/null
+++ b/spec/ruby/language/not_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../spec_helper'
+
+describe "The not keyword" do
+ it "negates a `true' value" do
+ (not true).should be_false
+ (not 'true').should be_false
+ end
+
+ it "negates a `false' value" do
+ (not false).should be_true
+ (not nil).should be_true
+ end
+
+ it "accepts an argument" do
+ not(true).should be_false
+ end
+
+ it "returns false if the argument is true" do
+ (not(true)).should be_false
+ end
+
+ it "returns true if the argument is false" do
+ (not(false)).should be_true
+ end
+
+ it "returns true if the argument is nil" do
+ (not(nil)).should be_true
+ end
+end
+
+describe "The `!' keyword" do
+ it "negates a `true' value" do
+ (!true).should be_false
+ (!'true').should be_false
+ end
+
+ it "negates a `false' value" do
+ (!false).should be_true
+ (!nil).should be_true
+ end
+
+ it "doubled turns a truthful object into `true'" do
+ (!!true).should be_true
+ (!!'true').should be_true
+ end
+
+ it "doubled turns a not truthful object into `false'" do
+ (!!false).should be_false
+ (!!nil).should be_false
+ end
+end
diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb
new file mode 100644
index 0000000000..424d7a06e3
--- /dev/null
+++ b/spec/ruby/language/numbered_parameters_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../spec_helper'
+
+describe "Numbered parameters" do
+ it "provides default parameters _1, _2, ... in a block" do
+ -> { _1 }.call("a").should == "a"
+ proc { _1 }.call("a").should == "a"
+ lambda { _1 }.call("a").should == "a"
+ ["a"].map { _1 }.should == ["a"]
+ end
+
+ it "assigns nil to not passed parameters" do
+ proc { [_1, _2] }.call("a").should == ["a", nil]
+ proc { [_1, _2] }.call("a", "b").should == ["a", "b"]
+ end
+
+ it "supports variables _1-_9 only for the first 9 passed parameters" do
+ block = proc { [_1, _2, _3, _4, _5, _6, _7, _8, _9] }
+ result = block.call(1, 2, 3, 4, 5, 6, 7, 8, 9)
+ result.should == [1, 2, 3, 4, 5, 6, 7, 8, 9]
+ end
+
+ it "does not support more than 9 parameters" do
+ -> {
+ proc { [_10] }.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ }.should raise_error(NameError, /undefined local variable or method `_10'/)
+ end
+
+ it "can not be used in both outer and nested blocks at the same time" do
+ -> {
+ eval("-> { _1; -> { _2 } }")
+ }.should raise_error(SyntaxError, /numbered parameter is already used in/m)
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "can be overwritten with local variable" do
+ suppress_warning do
+ eval <<~CODE
+ _1 = 0
+ proc { _1 }.call("a").should == 0
+ CODE
+ end
+ end
+
+ it "warns when numbered parameter is overwritten with local variable" do
+ -> {
+ eval("_1 = 0")
+ }.should complain(/warning: `_1' is reserved for numbered parameter; consider another name/)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "cannot be overwritten with local variable" do
+ -> {
+ eval <<~CODE
+ _1 = 0
+ proc { _1 }.call("a").should == 0
+ CODE
+ }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/)
+ end
+
+ it "errors when numbered parameter is overwritten with local variable" do
+ -> {
+ eval("_1 = 0")
+ }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/)
+ end
+ end
+
+ it "raises SyntaxError when block parameters are specified explicitly" do
+ -> { eval("-> () { _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("-> (x) { _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { eval("proc { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("proc { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { eval("lambda { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("lambda { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+
+ -> { eval("['a'].map { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ -> { eval("['a'].map { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/)
+ end
+
+ describe "assigning to a numbered parameter" do
+ ruby_version_is ''...'3.0' do
+ it "warns" do
+ -> { eval("proc { _1 = 0 }") }.should complain(/warning: `_1' is reserved for numbered parameter; consider another name/)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "raises SyntaxError" do
+ -> { eval("proc { _1 = 0 }") }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/)
+ end
+ end
+ end
+
+ it "affects block arity" do
+ -> { _1 }.arity.should == 1
+ -> { _2 }.arity.should == 2
+ -> { _3 }.arity.should == 3
+ -> { _4 }.arity.should == 4
+ -> { _5 }.arity.should == 5
+ -> { _6 }.arity.should == 6
+ -> { _7 }.arity.should == 7
+ -> { _8 }.arity.should == 8
+ -> { _9 }.arity.should == 9
+
+ -> { _9 }.arity.should == 9
+ proc { _9 }.arity.should == 9
+ lambda { _9 }.arity.should == 9
+ end
+
+ it "does not work in methods" do
+ obj = Object.new
+ def obj.foo; _1 end
+
+ -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/)
+ end
+end
diff --git a/spec/ruby/language/numbers_spec.rb b/spec/ruby/language/numbers_spec.rb
new file mode 100644
index 0000000000..a8e023efb6
--- /dev/null
+++ b/spec/ruby/language/numbers_spec.rb
@@ -0,0 +1,105 @@
+require_relative '../spec_helper'
+
+describe "A number literal" do
+
+ it "can be a sequence of decimal digits" do
+ 435.should == 435
+ end
+
+ it "can have '_' characters between digits" do
+ 4_3_5_7.should == 4357
+ end
+
+ it "cannot have a leading underscore" do
+ -> { eval("_4_2") }.should raise_error(NameError)
+ end
+
+ it "can have a decimal point" do
+ 4.35.should == 4.35
+ end
+
+ it "must have a digit before the decimal point" do
+ 0.75.should == 0.75
+ -> { eval(".75") }.should raise_error(SyntaxError)
+ -> { eval("-.75") }.should raise_error(SyntaxError)
+ end
+
+ it "can have an exponent" do
+ 1.2e-3.should == 0.0012
+ end
+
+ it "can be a sequence of hexadecimal digits with a leading '0x'" do
+ 0xffff.should == 65535
+ end
+
+ it "can be a sequence of binary digits with a leading '0x'" do
+ 0b01011.should == 11
+ end
+
+ it "can be a sequence of octal digits with a leading '0'" do
+ 0377.should == 255
+ end
+
+ it "can be an integer literal with trailing 'r' to represent a Rational" do
+ eval('3r').should == Rational(3, 1)
+ eval('-3r').should == Rational(-3, 1)
+ end
+
+ it "can be an float literal with trailing 'r' to represent a Rational in a canonical form" do
+ eval('1.0r').should == Rational(1, 1)
+ end
+
+ it "can be a float literal with trailing 'r' to represent a Rational" do
+ eval('0.0174532925199432957r').should == Rational(174532925199432957, 10000000000000000000)
+ end
+
+ it "can be a bignum literal with trailing 'r' to represent a Rational" do
+ eval('1111111111111111111111111111111111111111111111r').should == Rational(1111111111111111111111111111111111111111111111, 1)
+ eval('-1111111111111111111111111111111111111111111111r').should == Rational(-1111111111111111111111111111111111111111111111, 1)
+ end
+
+ it "can be a decimal literal with trailing 'r' to represent a Rational" do
+ eval('0.3r').should == Rational(3, 10)
+ eval('-0.3r').should == Rational(-3, 10)
+ end
+
+ it "can be a hexadecimal literal with trailing 'r' to represent a Rational" do
+ eval('0xffr').should == Rational(255, 1)
+ eval('-0xffr').should == Rational(-255, 1)
+ end
+
+ it "can be an octal literal with trailing 'r' to represent a Rational" do
+ eval('042r').should == Rational(34, 1)
+ eval('-042r').should == Rational(-34, 1)
+ end
+
+ it "can be a binary literal with trailing 'r' to represent a Rational" do
+ eval('0b1111r').should == Rational(15, 1)
+ eval('-0b1111r').should == Rational(-15, 1)
+ end
+
+ it "can be an integer literal with trailing 'i' to represent a Complex" do
+ eval('5i').should == Complex(0, 5)
+ eval('-5i').should == Complex(0, -5)
+ end
+
+ it "can be a decimal literal with trailing 'i' to represent a Complex" do
+ eval('0.6i').should == Complex(0, 0.6)
+ eval('-0.6i').should == Complex(0, -0.6)
+ end
+
+ it "can be a hexadecimal literal with trailing 'i' to represent a Complex" do
+ eval('0xffi').should == Complex(0, 255)
+ eval('-0xffi').should == Complex(0, -255)
+ end
+
+ it "can be a octal literal with trailing 'i' to represent a Complex" do
+ eval("042i").should == Complex(0, 34)
+ eval("-042i").should == Complex(0, -34)
+ end
+
+ it "can be a binary literal with trailing 'i' to represent a Complex" do
+ eval('0b1110i').should == Complex(0, 14)
+ eval('-0b1110i').should == Complex(0, -14)
+ end
+end
diff --git a/spec/ruby/language/optional_assignments_spec.rb b/spec/ruby/language/optional_assignments_spec.rb
new file mode 100644
index 0000000000..02461655d6
--- /dev/null
+++ b/spec/ruby/language/optional_assignments_spec.rb
@@ -0,0 +1,496 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/constants'
+
+describe 'Optional variable assignments' do
+ describe 'using ||=' do
+ describe 'using a single variable' do
+ it 'assigns a new variable' do
+ a ||= 10
+
+ a.should == 10
+ end
+
+ it 're-assigns an existing variable set to false' do
+ a = false
+ a ||= 10
+
+ a.should == 10
+ end
+
+ it 're-assigns an existing variable set to nil' do
+ a = nil
+ a ||= 10
+
+ a.should == 10
+ end
+
+ it 'does not re-assign a variable with a truthy value' do
+ a = 10
+ a ||= 20
+
+ a.should == 10
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ a = 10
+ a ||= raise('should not be executed')
+ a.should == 10
+ end
+
+ it 'does not re-assign a variable with a truthy value when using an inline rescue' do
+ a = 10
+ a ||= 20 rescue 30
+
+ a.should == 10
+ end
+
+ it 'returns the new value if set to false' do
+ a = false
+
+ (a ||= 20).should == 20
+ end
+
+ it 'returns the original value if truthy' do
+ a = 10
+
+ (a ||= 20).should == 10
+ end
+ end
+
+ describe 'using a accessor' do
+ before do
+ klass = Class.new { attr_accessor :b }
+ @a = klass.new
+ end
+
+ it 'assigns a new variable' do
+ @a.b ||= 10
+
+ @a.b.should == 10
+ end
+
+ it 're-assigns an existing variable set to false' do
+ @a.b = false
+ @a.b ||= 10
+
+ @a.b.should == 10
+ end
+
+ it 're-assigns an existing variable set to nil' do
+ @a.b = nil
+ @a.b ||= 10
+
+ @a.b.should == 10
+ end
+
+ it 'does not re-assign a variable with a truthy value' do
+ @a.b = 10
+ @a.b ||= 20
+
+ @a.b.should == 10
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ @a.b = 10
+ @a.b ||= raise('should not be executed')
+ @a.b.should == 10
+ end
+
+ it 'does not re-assign a variable with a truthy value when using an inline rescue' do
+ @a.b = 10
+ @a.b ||= 20 rescue 30
+
+ @a.b.should == 10
+ end
+
+ it 'returns the new value if set to false' do
+ def @a.b=(x)
+ :v
+ end
+
+ @a.b = false
+ (@a.b ||= 20).should == 20
+ end
+
+ it 'returns the original value if truthy' do
+ def @a.b=(x)
+ @b = x
+ :v
+ end
+
+ @a.b = 10
+ (@a.b ||= 20).should == 10
+ end
+
+ it 'works when writer is private' do
+ klass = Class.new do
+ def t
+ self.b = false
+ (self.b ||= 10).should == 10
+ (self.b ||= 20).should == 10
+ end
+
+ def b
+ @b
+ end
+
+ def b=(x)
+ @b = x
+ :v
+ end
+
+ private :b=
+ end
+
+ klass.new.t
+ end
+
+ end
+ end
+
+ describe 'using &&=' do
+ describe 'using a single variable' do
+ it 'leaves new variable unassigned' do
+ a &&= 10
+
+ a.should == nil
+ end
+
+ it 'leaves false' do
+ a = false
+ a &&= 10
+
+ a.should == false
+ end
+
+ it 'leaves nil' do
+ a = nil
+ a &&= 10
+
+ a.should == nil
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ a = nil
+ a &&= raise('should not be executed')
+ a.should == nil
+ end
+
+ it 'does re-assign a variable with a truthy value' do
+ a = 10
+ a &&= 20
+
+ a.should == 20
+ end
+
+ it 'does re-assign a variable with a truthy value when using an inline rescue' do
+ a = 10
+ a &&= 20 rescue 30
+
+ a.should == 20
+ end
+ end
+
+ describe 'using a single variable' do
+ before do
+ klass = Class.new { attr_accessor :b }
+ @a = klass.new
+ end
+
+ it 'leaves new variable unassigned' do
+ @a.b &&= 10
+
+ @a.b.should == nil
+ end
+
+ it 'leaves false' do
+ @a.b = false
+ @a.b &&= 10
+
+ @a.b.should == false
+ end
+
+ it 'leaves nil' do
+ @a.b = nil
+ @a.b &&= 10
+
+ @a.b.should == nil
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ @a.b = nil
+ @a.b &&= raise('should not be executed')
+ @a.b.should == nil
+ end
+
+ it 'does re-assign a variable with a truthy value' do
+ @a.b = 10
+ @a.b &&= 20
+
+ @a.b.should == 20
+ end
+
+ it 'does re-assign a variable with a truthy value when using an inline rescue' do
+ @a.b = 10
+ @a.b &&= 20 rescue 30
+
+ @a.b.should == 20
+ end
+ end
+
+ describe 'using a #[]' do
+ before do
+ @a = {}
+ klass = Class.new do
+ def [](k)
+ @hash ||= {}
+ @hash[k]
+ end
+
+ def []=(k, v)
+ @hash ||= {}
+ @hash[k] = v
+ 7
+ end
+ end
+ @b = klass.new
+ end
+
+ it 'leaves new variable unassigned' do
+ @a[:k] &&= 10
+
+ @a.key?(:k).should == false
+ end
+
+ it 'leaves false' do
+ @a[:k] = false
+ @a[:k] &&= 10
+
+ @a[:k].should == false
+ end
+
+ it 'leaves nil' do
+ @a[:k] = nil
+ @a[:k] &&= 10
+
+ @a[:k].should == nil
+ end
+
+ it 'does not evaluate the right side when not needed' do
+ @a[:k] = nil
+ @a[:k] &&= raise('should not be executed')
+ @a[:k].should == nil
+ end
+
+ it 'does re-assign a variable with a truthy value' do
+ @a[:k] = 10
+ @a[:k] &&= 20
+
+ @a[:k].should == 20
+ end
+
+ it 'does re-assign a variable with a truthy value when using an inline rescue' do
+ @a[:k] = 10
+ @a[:k] &&= 20 rescue 30
+
+ @a[:k].should == 20
+ end
+
+ it 'returns the assigned value, not the result of the []= method with ||=' do
+ (@b[:k] ||= 12).should == 12
+ end
+
+ it 'correctly handles a splatted argument for the index' do
+ (@b[*[:k]] ||= 12).should == 12
+ end
+
+ it "evaluates the index precisely once" do
+ ary = [:x, :y]
+ @a[:x] = 15
+ @a[ary.pop] ||= 25
+ ary.should == [:x]
+ @a.should == { x: 15, y: 25 }
+ end
+
+ it "evaluates the index arguments in the correct order" do
+ ary = Class.new(Array) do
+ def [](x, y)
+ super(x + 3 * y)
+ end
+
+ def []=(x, y, value)
+ super(x + 3 * y, value)
+ end
+ end.new
+ ary[0, 0] = 1
+ ary[1, 0] = 1
+ ary[2, 0] = nil
+ ary[3, 0] = 1
+ ary[4, 0] = 1
+ ary[5, 0] = 1
+ ary[6, 0] = nil
+
+ foo = [0, 2]
+
+ ary[foo.pop, foo.pop] ||= 2
+
+ ary[2, 0].should == 2
+ ary[6, 0].should == nil
+ end
+
+ it 'returns the assigned value, not the result of the []= method with +=' do
+ @b[:k] = 17
+ (@b[:k] += 12).should == 29
+ end
+ end
+ end
+
+ describe 'using compounded constants' do
+ before :each do
+ Object.send(:remove_const, :A) if defined? Object::A
+ end
+
+ after :each do
+ Object.send(:remove_const, :A) if defined? Object::A
+ end
+
+ it 'with ||= assignments' do
+ Object::A ||= 10
+ Object::A.should == 10
+ end
+
+ it 'with ||= do not reassign' do
+ Object::A = 20
+ Object::A ||= 10
+ Object::A.should == 20
+ end
+
+ it 'with &&= assignments' do
+ Object::A = 20
+ -> {
+ Object::A &&= 10
+ }.should complain(/already initialized constant/)
+ Object::A.should == 10
+ end
+
+ it 'with &&= assignments will fail with non-existent constants' do
+ -> { Object::A &&= 10 }.should raise_error(NameError)
+ end
+
+ it 'with operator assignments' do
+ Object::A = 20
+ -> {
+ Object::A += 10
+ }.should complain(/already initialized constant/)
+ Object::A.should == 30
+ end
+
+ it 'with operator assignments will fail with non-existent constants' do
+ -> { Object::A += 10 }.should raise_error(NameError)
+ end
+ end
+end
+
+describe 'Optional constant assignment' do
+ describe 'with ||=' do
+ it "assigns a scoped constant if previously undefined" do
+ ConstantSpecs.should_not have_constant(:OpAssignUndefined)
+ module ConstantSpecs
+ OpAssignUndefined ||= 42
+ end
+ ConstantSpecs::OpAssignUndefined.should == 42
+ ConstantSpecs::OpAssignUndefinedOutside ||= 42
+ ConstantSpecs::OpAssignUndefinedOutside.should == 42
+ ConstantSpecs.send(:remove_const, :OpAssignUndefined)
+ ConstantSpecs.send(:remove_const, :OpAssignUndefinedOutside)
+ end
+
+ it "assigns a global constant if previously undefined" do
+ OpAssignGlobalUndefined ||= 42
+ ::OpAssignGlobalUndefinedExplicitScope ||= 42
+ OpAssignGlobalUndefined.should == 42
+ ::OpAssignGlobalUndefinedExplicitScope.should == 42
+ Object.send :remove_const, :OpAssignGlobalUndefined
+ Object.send :remove_const, :OpAssignGlobalUndefinedExplicitScope
+ end
+
+ it 'correctly defines non-existing constants' do
+ ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT1 ||= :assigned
+ ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT1.should == :assigned
+ end
+
+ it 'correctly overwrites nil constants' do
+ suppress_warning do # already initialized constant
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1 = nil
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1 ||= :assigned
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1.should == :assigned
+ end
+ end
+
+ it 'causes side-effects of the module part to be applied only once (for undefined constant)' do
+ x = 0
+ (x += 1; ConstantSpecs::ClassA)::OR_ASSIGNED_CONSTANT2 ||= :assigned
+ x.should == 1
+ ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT2.should == :assigned
+ end
+
+ it 'causes side-effects of the module part to be applied (for nil constant)' do
+ suppress_warning do # already initialized constant
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT2 = nil
+ x = 0
+ (x += 1; ConstantSpecs::ClassA)::NIL_OR_ASSIGNED_CONSTANT2 ||= :assigned
+ x.should == 1
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT2.should == :assigned
+ end
+ end
+
+ it 'does not evaluate the right-hand side if the module part raises an exception (for undefined constant)' do
+ x = 0
+ y = 0
+
+ -> {
+ (x += 1; raise Exception; ConstantSpecs::ClassA)::OR_ASSIGNED_CONSTANT3 ||= (y += 1; :assigned)
+ }.should raise_error(Exception)
+
+ x.should == 1
+ y.should == 0
+ defined?(ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT3).should == nil
+ end
+
+ it 'does not evaluate the right-hand side if the module part raises an exception (for nil constant)' do
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT3 = nil
+ x = 0
+ y = 0
+
+ -> {
+ (x += 1; raise Exception; ConstantSpecs::ClassA)::NIL_OR_ASSIGNED_CONSTANT3 ||= (y += 1; :assigned)
+ }.should raise_error(Exception)
+
+ x.should == 1
+ y.should == 0
+ ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT3.should == nil
+ end
+ end
+
+ describe "with &&=" do
+ it "re-assigns a scoped constant if already true" do
+ module ConstantSpecs
+ OpAssignTrue = true
+ end
+ suppress_warning do
+ ConstantSpecs::OpAssignTrue &&= 1
+ end
+ ConstantSpecs::OpAssignTrue.should == 1
+ ConstantSpecs.send :remove_const, :OpAssignTrue
+ end
+
+ it "leaves scoped constant if not true" do
+ module ConstantSpecs
+ OpAssignFalse = false
+ end
+ ConstantSpecs::OpAssignFalse &&= 1
+ ConstantSpecs::OpAssignFalse.should == false
+ ConstantSpecs.send :remove_const, :OpAssignFalse
+ end
+ end
+end
diff --git a/spec/ruby/language/or_spec.rb b/spec/ruby/language/or_spec.rb
new file mode 100644
index 0000000000..fb75e788f1
--- /dev/null
+++ b/spec/ruby/language/or_spec.rb
@@ -0,0 +1,90 @@
+require_relative '../spec_helper'
+
+describe "The || operator" do
+ it "evaluates to true if any of its operands are true" do
+ if false || true || nil
+ x = true
+ end
+ x.should == true
+ end
+
+ it "evaluated to false if all of its operands are false" do
+ if false || nil
+ x = true
+ end
+ x.should == nil
+ end
+
+ it "is evaluated before assignment operators" do
+ x = nil || true
+ x.should == true
+ end
+
+ it "has a lower precedence than the && operator" do
+ x = 1 || false && x = 2
+ x.should == 1
+ end
+
+ it "treats empty expressions as nil" do
+ (() || true).should be_true
+ (() || false).should be_false
+ (true || ()).should be_true
+ (false || ()).should be_nil
+ (() || ()).should be_nil
+ end
+
+ it "has a higher precedence than 'break' in 'break true || false'" do
+ # see also 'break true or false' below
+ -> { break false || true }.call.should be_true
+ end
+
+ it "has a higher precedence than 'next' in 'next true || false'" do
+ -> { next false || true }.call.should be_true
+ end
+
+ it "has a higher precedence than 'return' in 'return true || false'" do
+ -> { return false || true }.call.should be_true
+ end
+end
+
+describe "The or operator" do
+ it "evaluates to true if any of its operands are true" do
+ x = nil
+ if false or true
+ x = true
+ end
+ x.should == true
+ end
+
+ it "is evaluated after variables are assigned" do
+ x = nil or true
+ x.should == nil
+ end
+
+ it "has a lower precedence than the || operator" do
+ x,y = nil
+ x = true || false or y = 1
+ y.should == nil
+ end
+
+ it "treats empty expressions as nil" do
+ (() or true).should be_true
+ (() or false).should be_false
+ (true or ()).should be_true
+ (false or ()).should be_nil
+ (() or ()).should be_nil
+ end
+
+ it "has a lower precedence than 'break' in 'break true or false'" do
+ # see also 'break true || false' above
+ -> { eval "break true or false" }.should raise_error(SyntaxError, /void value expression/)
+ end
+
+ it "has a lower precedence than 'next' in 'next true or false'" do
+ -> { eval "next true or false" }.should raise_error(SyntaxError, /void value expression/)
+ end
+
+ it "has a lower precedence than 'return' in 'return true or false'" do
+ -> { eval "return true or false" }.should raise_error(SyntaxError, /void value expression/)
+ end
+end
diff --git a/spec/ruby/language/order_spec.rb b/spec/ruby/language/order_spec.rb
new file mode 100644
index 0000000000..d550f6b3f4
--- /dev/null
+++ b/spec/ruby/language/order_spec.rb
@@ -0,0 +1,75 @@
+require_relative '../spec_helper'
+
+describe "A method call" do
+ before :each do
+ @obj = Object.new
+ def @obj.foo0(&a)
+ [a ? a.call : nil]
+ end
+ def @obj.foo1(a, &b)
+ [a, b ? b.call : nil]
+ end
+ def @obj.foo2(a, b, &c)
+ [a, b, c ? c.call : nil]
+ end
+ def @obj.foo3(a, b, c, &d)
+ [a, b, c, d ? d.call : nil]
+ end
+ def @obj.foo4(a, b, c, d, &e)
+ [a, b, c, d, e ? e.call : nil]
+ end
+ end
+
+ it "evaluates the receiver first" do
+ (obj = @obj).foo1(obj = nil).should == [nil, nil]
+ (obj = @obj).foo2(obj = nil, obj = nil).should == [nil, nil, nil]
+ (obj = @obj).foo3(obj = nil, obj = nil, obj = nil).should == [nil, nil, nil, nil]
+ (obj = @obj).foo4(obj = nil, obj = nil, obj = nil, obj = nil).should == [nil, nil, nil, nil, nil]
+ end
+
+ it "evaluates arguments after receiver" do
+ a = 0
+ (a += 1; @obj).foo1(a).should == [1, nil]
+ (a += 1; @obj).foo2(a, a).should == [2, 2, nil]
+ (a += 1; @obj).foo3(a, a, a).should == [3, 3, 3, nil]
+ (a += 1; @obj).foo4(a, a, a, a).should == [4, 4, 4, 4, nil]
+ a.should == 4
+ end
+
+ it "evaluates arguments left-to-right" do
+ a = 0
+ @obj.foo1(a += 1).should == [1, nil]
+ @obj.foo2(a += 1, a += 1).should == [2, 3, nil]
+ @obj.foo3(a += 1, a += 1, a += 1).should == [4, 5, 6, nil]
+ @obj.foo4(a += 1, a += 1, a += 1, a += 1).should == [7, 8, 9, 10, nil]
+ a.should == 10
+ end
+
+ it "evaluates block pass after arguments" do
+ a = 0
+ p = proc {true}
+ @obj.foo1(a += 1, &(a += 1; p)).should == [1, true]
+ @obj.foo2(a += 1, a += 1, &(a += 1; p)).should == [3, 4, true]
+ @obj.foo3(a += 1, a += 1, a += 1, &(a += 1; p)).should == [6, 7, 8, true]
+ @obj.foo4(a += 1, a += 1, a += 1, a += 1, &(a += 1; p)).should == [10, 11, 12, 13, true]
+ a.should == 14
+ end
+
+ it "evaluates block pass after receiver" do
+ p1 = proc {true}
+ p2 = proc {false}
+ p1.should_not == p2
+
+ p = p1
+ (p = p2; @obj).foo0(&p).should == [false]
+ p = p1
+ (p = p2; @obj).foo1(1, &p).should == [1, false]
+ p = p1
+ (p = p2; @obj).foo2(1, 1, &p).should == [1, 1, false]
+ p = p1
+ (p = p2; @obj).foo3(1, 1, 1, &p).should == [1, 1, 1, false]
+ p = p1
+ (p = p2; @obj).foo4(1, 1, 1, 1, &p).should == [1, 1, 1, 1, false]
+ p = p1
+ end
+end
diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb
new file mode 100644
index 0000000000..f3cc86fa0b
--- /dev/null
+++ b/spec/ruby/language/pattern_matching_spec.rb
@@ -0,0 +1,1403 @@
+require_relative '../spec_helper'
+
+describe "Pattern matching" do
+ # TODO: Remove excessive eval calls when Ruby 3 is the minimum version.
+ # It is best to keep the eval's longer if other Ruby impls cannot parse pattern matching yet.
+
+ before :each do
+ ScratchPad.record []
+ end
+
+ ruby_version_is "3.0" do
+ it "can be standalone assoc operator that deconstructs value" do
+ suppress_warning do
+ eval(<<-RUBY).should == [0, 1]
+ [0, 1] => [a, b]
+ [a, b]
+ RUBY
+ end
+ end
+
+ describe "find pattern" do
+ it "captures preceding elements to the pattern" do
+ eval(<<~RUBY).should == [0, 1]
+ case [0, 1, 2, 3]
+ in [*pre, 2, 3]
+ pre
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "captures following elements to the pattern" do
+ eval(<<~RUBY).should == [2, 3]
+ case [0, 1, 2, 3]
+ in [0, 1, *post]
+ post
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "captures both preceding and following elements to the pattern" do
+ eval(<<~RUBY).should == [[0, 1], [3, 4]]
+ case [0, 1, 2, 3, 4]
+ in [*pre, 2, *post]
+ [pre, post]
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can capture the entirety of the pattern" do
+ eval(<<~RUBY).should == [0, 1, 2, 3, 4]
+ case [0, 1, 2, 3, 4]
+ in [*everything]
+ everything
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "will match an empty Array-like structure" do
+ eval(<<~RUBY).should == []
+ case []
+ in [*everything]
+ everything
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can be nested" do
+ eval(<<~RUBY).should == [[0, [2, 4, 6]], [[4, 16, 64]], 27]
+ case [0, [2, 4, 6], [3, 9, 27], [4, 16, 64]]
+ in [*pre, [*, 9, a], *post]
+ [pre, post, a]
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can be nested with an array pattern" do
+ eval(<<~RUBY).should == [[4, 16, 64]]
+ case [0, [2, 4, 6], [3, 9, 27], [4, 16, 64]]
+ in [_, _, [*, 9, *], *post]
+ post
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can be nested within a hash pattern" do
+ eval(<<~RUBY).should == [27]
+ case {a: [3, 9, 27]}
+ in {a: [*, 9, *post]}
+ post
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can nest hash and array patterns" do
+ eval(<<~RUBY).should == [42, 2]
+ case [0, {a: 42, b: [0, 1]}, {a: 42, b: [1, 2]}]
+ in [*, {a:, b: [1, c]}, *]
+ [a, c]
+ else
+ false
+ end
+ RUBY
+ end
+ end
+ end
+
+ it "extends case expression with case/in construction" do
+ eval(<<~RUBY).should == :bar
+ case [0, 1]
+ in [0]
+ :foo
+ in [0, 1]
+ :bar
+ end
+ RUBY
+ end
+
+ it "allows using then operator" do
+ eval(<<~RUBY).should == :bar
+ case [0, 1]
+ in [0] then :foo
+ in [0, 1] then :bar
+ end
+ RUBY
+ end
+
+ describe "warning" do
+ before :each do
+ @experimental, Warning[:experimental] = Warning[:experimental], true
+ end
+
+ after :each do
+ Warning[:experimental] = @experimental
+ end
+
+ context 'when regular form' do
+ before :each do
+ @src = 'case [0, 1]; in [a, b]; end'
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "warns about pattern matching is experimental feature" do
+ -> { eval @src }.should complain(/pattern matching is experimental, and the behavior may change in future versions of Ruby!/i)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "does not warn about pattern matching is experimental feature" do
+ -> { eval @src }.should_not complain
+ end
+ end
+ end
+
+ context 'when one-line form' do
+ ruby_version_is '3.0' do
+ before :each do
+ @src = '[0, 1] => [a, b]'
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "warns about pattern matching is experimental feature" do
+ -> { eval @src }.should complain(/pattern matching is experimental, and the behavior may change in future versions of Ruby!/i)
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "does not warn about pattern matching is experimental feature" do
+ -> { eval @src }.should_not complain
+ end
+ end
+ end
+ end
+ end
+
+ it "binds variables" do
+ eval(<<~RUBY).should == 1
+ case [0, 1]
+ in [0, a]
+ a
+ end
+ RUBY
+ end
+
+ it "cannot mix in and when operators" do
+ -> {
+ eval <<~RUBY
+ case []
+ when 1 == 1
+ in []
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /syntax error, unexpected `in'/)
+
+ -> {
+ eval <<~RUBY
+ case []
+ in []
+ when 1 == 1
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /syntax error, unexpected `when'/)
+ end
+
+ it "checks patterns until the first matching" do
+ eval(<<~RUBY).should == :bar
+ case [0, 1]
+ in [0]
+ :foo
+ in [0, 1]
+ :bar
+ in [0, 1]
+ :baz
+ end
+ RUBY
+ end
+
+ it "executes else clause if no pattern matches" do
+ eval(<<~RUBY).should == false
+ case [0, 1]
+ in [0]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "raises NoMatchingPatternError if no pattern matches and no else clause" do
+ -> {
+ eval <<~RUBY
+ case [0, 1]
+ in [0]
+ end
+ RUBY
+ }.should raise_error(NoMatchingPatternError, /\[0, 1\]/)
+ end
+
+ it "does not allow calculation or method calls in a pattern" do
+ -> {
+ eval <<~RUBY
+ case 0
+ in 1 + 1
+ true
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /unexpected/)
+ end
+
+ it "evaluates the case expression once for multiple patterns, caching the result" do
+ eval(<<~RUBY).should == true
+ case (ScratchPad << :foo; 1)
+ in 0
+ false
+ in 1
+ true
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [:foo]
+ end
+
+ describe "guards" do
+ it "supports if guard" do
+ eval(<<~RUBY).should == false
+ case 0
+ in 0 if false
+ true
+ else
+ false
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case 0
+ in 0 if true
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "supports unless guard" do
+ eval(<<~RUBY).should == false
+ case 0
+ in 0 unless true
+ true
+ else
+ false
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case 0
+ in 0 unless false
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "makes bound variables visible in guard" do
+ eval(<<~RUBY).should == true
+ case [0, 1]
+ in [a, 1] if a >= 0
+ true
+ end
+ RUBY
+ end
+
+ it "does not evaluate guard if pattern does not match" do
+ eval <<~RUBY
+ case 0
+ in 1 if (ScratchPad << :foo) || true
+ else
+ end
+ RUBY
+
+ ScratchPad.recorded.should == []
+ end
+
+ it "takes guards into account when there are several matching patterns" do
+ eval(<<~RUBY).should == :bar
+ case 0
+ in 0 if false
+ :foo
+ in 0 if true
+ :bar
+ end
+ RUBY
+ end
+
+ it "executes else clause if no guarded pattern matches" do
+ eval(<<~RUBY).should == false
+ case 0
+ in 0 if false
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "raises NoMatchingPatternError if no guarded pattern matches and no else clause" do
+ -> {
+ eval <<~RUBY
+ case [0, 1]
+ in [0, 1] if false
+ end
+ RUBY
+ }.should raise_error(NoMatchingPatternError, /\[0, 1\]/)
+ end
+ end
+
+ describe "value pattern" do
+ it "matches an object such that pattern === object" do
+ eval(<<~RUBY).should == true
+ case 0
+ in 0
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case 0
+ in (-1..1)
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case 0
+ in Integer
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case "0"
+ in /0/
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case "0"
+ in ->(s) { s == "0" }
+ true
+ end
+ RUBY
+ end
+
+ it "allows string literal with interpolation" do
+ x = "x"
+
+ eval(<<~RUBY).should == true
+ case "x"
+ in "#{x + ""}"
+ true
+ end
+ RUBY
+ end
+ end
+
+ describe "variable pattern" do
+ it "matches a value and binds variable name to this value" do
+ eval(<<~RUBY).should == 0
+ case 0
+ in a
+ a
+ end
+ RUBY
+ end
+
+ it "makes bounded variable visible outside a case statement scope" do
+ eval(<<~RUBY).should == 0
+ case 0
+ in a
+ end
+
+ a
+ RUBY
+ end
+
+ it "create local variables even if a pattern doesn't match" do
+ eval(<<~RUBY).should == [0, nil, nil]
+ case 0
+ in a
+ in b
+ in c
+ end
+
+ [a, b, c]
+ RUBY
+ end
+
+ it "allow using _ name to drop values" do
+ eval(<<~RUBY).should == 0
+ case [0, 1]
+ in [a, _]
+ a
+ end
+ RUBY
+ end
+
+ it "supports using _ in a pattern several times" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in [0, _, _]
+ true
+ end
+ RUBY
+ end
+
+ it "supports using any name with _ at the beginning in a pattern several times" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in [0, _x, _x]
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case {a: 0, b: 1, c: 2}
+ in {a: 0, b: _x, c: _x}
+ true
+ end
+ RUBY
+ end
+
+ it "does not support using variable name (except _) several times" do
+ -> {
+ eval <<~RUBY
+ case [0]
+ in [a, a]
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /duplicated variable name/)
+ end
+
+ it "supports existing variables in a pattern specified with ^ operator" do
+ a = 0
+
+ eval(<<~RUBY).should == true
+ case 0
+ in ^a
+ true
+ end
+ RUBY
+ end
+
+ it "allows applying ^ operator to bound variables" do
+ eval(<<~RUBY).should == 1
+ case [1, 1]
+ in [n, ^n]
+ n
+ end
+ RUBY
+
+ eval(<<~RUBY).should == false
+ case [1, 2]
+ in [n, ^n]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "requires bound variable to be specified in a pattern before ^ operator when it relies on a bound variable" do
+ -> {
+ eval <<~RUBY
+ case [1, 2]
+ in [^n, n]
+ true
+ else
+ false
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /n: no such local variable/)
+ end
+ end
+
+ describe "alternative pattern" do
+ it "matches if any of patterns matches" do
+ eval(<<~RUBY).should == true
+ case 0
+ in 0 | 1 | 2
+ true
+ end
+ RUBY
+ end
+
+ it "does not support variable binding" do
+ -> {
+ eval <<~RUBY
+ case [0, 1]
+ in [0, 0] | [0, a]
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /illegal variable in alternative pattern/)
+ end
+
+ it "support underscore prefixed variables in alternation" do
+ eval(<<~RUBY).should == true
+ case [0, 1]
+ in [1, _]
+ false
+ in [0, 0] | [0, _a]
+ true
+ end
+ RUBY
+ end
+
+ it "can be used as a nested pattern" do
+ eval(<<~RUBY).should == true
+ case [[1], ["2"]]
+ in [[0] | nil, _]
+ false
+ in [[1], [1]]
+ false
+ in [[1], [2 | "2"]]
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case [1, 2]
+ in [0, _] | {a: 0}
+ false
+ in {a: 1, b: 2} | [1, 2]
+ true
+ end
+ RUBY
+ end
+ end
+
+ describe "AS pattern" do
+ it "binds a variable to a value if pattern matches" do
+ eval(<<~RUBY).should == 0
+ case 0
+ in Integer => n
+ n
+ end
+ RUBY
+ end
+
+ it "can be used as a nested pattern" do
+ eval(<<~RUBY).should == [2, 3]
+ case [1, [2, 3]]
+ in [1, Array => ary]
+ ary
+ end
+ RUBY
+ end
+ end
+
+ describe "Array pattern" do
+ it "supports form Constant(pat, pat, ...)" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in Array(0, 1, 2)
+ true
+ end
+ RUBY
+ end
+
+ it "supports form Constant[pat, pat, ...]" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in Array[0, 1, 2]
+ true
+ end
+ RUBY
+ end
+
+ it "supports form [pat, pat, ...]" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in [0, 1, 2]
+ true
+ end
+ RUBY
+ end
+
+ it "supports form pat, pat, ..." do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2]
+ in 0, 1, 2
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == 1
+ case [0, 1, 2]
+ in 0, a, 2
+ a
+ end
+ RUBY
+
+ eval(<<~RUBY).should == [1, 2]
+ case [0, 1, 2]
+ in 0, *rest
+ rest
+ end
+ RUBY
+ end
+
+ it "matches an object with #deconstruct method which returns an array and each element in array matches element in pattern" do
+ obj = Object.new
+ def obj.deconstruct; [0, 1] end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in [Integer, Integer]
+ true
+ end
+ RUBY
+ end
+
+ ruby_version_is "3.0" do
+ it "calls #deconstruct once for multiple patterns, caching the result" do
+ obj = Object.new
+
+ def obj.deconstruct
+ ScratchPad << :deconstruct
+ [0, 1]
+ end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in [1, 2]
+ false
+ in [0, 1]
+ true
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [:deconstruct]
+ end
+ end
+
+ it "calls #deconstruct even on objects that are already an array" do
+ obj = [1, 2]
+ def obj.deconstruct
+ ScratchPad << :deconstruct
+ [3, 4]
+ end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in [3, 4]
+ true
+ else
+ false
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [:deconstruct]
+ end
+
+ it "does not match object if Constant === object returns false" do
+ eval(<<~RUBY).should == false
+ case [0, 1, 2]
+ in String[0, 1, 2]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "does not match object without #deconstruct method" do
+ obj = Object.new
+ obj.should_receive(:respond_to?).with(:deconstruct)
+
+ eval(<<~RUBY).should == false
+ case obj
+ in Object[]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "raises TypeError if #deconstruct method does not return array" do
+ obj = Object.new
+ def obj.deconstruct; "" end
+
+ -> {
+ eval <<~RUBY
+ case obj
+ in Object[]
+ else
+ end
+ RUBY
+ }.should raise_error(TypeError, /deconstruct must return Array/)
+ end
+
+ it "accepts a subclass of Array from #deconstruct" do
+ obj = Object.new
+ def obj.deconstruct
+ subarray = Class.new(Array).new(2)
+ def subarray.[](n)
+ n
+ end
+ subarray
+ end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in [1, 2]
+ false
+ in [0, 1]
+ true
+ end
+ RUBY
+ end
+
+ it "does not match object if elements of array returned by #deconstruct method does not match elements in pattern" do
+ obj = Object.new
+ def obj.deconstruct; [1] end
+
+ eval(<<~RUBY).should == false
+ case obj
+ in Object[0]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "binds variables" do
+ eval(<<~RUBY).should == [0, 1, 2]
+ case [0, 1, 2]
+ in [a, b, c]
+ [a, b, c]
+ end
+ RUBY
+ end
+
+ it "supports splat operator *rest" do
+ eval(<<~RUBY).should == [1, 2]
+ case [0, 1, 2]
+ in [0, *rest]
+ rest
+ end
+ RUBY
+ end
+
+ it "does not match partially by default" do
+ eval(<<~RUBY).should == false
+ case [0, 1, 2, 3]
+ in [1, 2]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "does match partially from the array beginning if list + , syntax used" do
+ eval(<<~RUBY).should == true
+ case [0, 1, 2, 3]
+ in [0, 1,]
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case [0, 1, 2, 3]
+ in 0, 1,;
+ true
+ end
+ RUBY
+ end
+
+ it "matches [] with []" do
+ eval(<<~RUBY).should == true
+ case []
+ in []
+ true
+ end
+ RUBY
+ end
+
+ it "matches anything with *" do
+ eval(<<~RUBY).should == true
+ case [0, 1]
+ in *;
+ true
+ end
+ RUBY
+ end
+
+ it "can be used as a nested pattern" do
+ eval(<<~RUBY).should == true
+ case [[1], ["2"]]
+ in [[0] | nil, _]
+ false
+ in [[1], [1]]
+ false
+ in [[1], [2 | "2"]]
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case [1, 2]
+ in [0, _] | {a: 0}
+ false
+ in {a: 1, b: 2} | [1, 2]
+ true
+ end
+ RUBY
+ end
+ end
+
+ describe "Hash pattern" do
+ it "supports form Constant(id: pat, id: pat, ...)" do
+ eval(<<~RUBY).should == true
+ case {a: 0, b: 1}
+ in Hash(a: 0, b: 1)
+ true
+ end
+ RUBY
+ end
+
+ it "supports form Constant[id: pat, id: pat, ...]" do
+ eval(<<~RUBY).should == true
+ case {a: 0, b: 1}
+ in Hash[a: 0, b: 1]
+ true
+ end
+ RUBY
+ end
+
+ it "supports form {id: pat, id: pat, ...}" do
+ eval(<<~RUBY).should == true
+ case {a: 0, b: 1}
+ in {a: 0, b: 1}
+ true
+ end
+ RUBY
+ end
+
+ it "supports form id: pat, id: pat, ..." do
+ eval(<<~RUBY).should == true
+ case {a: 0, b: 1}
+ in a: 0, b: 1
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in a: a, b: b
+ [a, b]
+ end
+ RUBY
+
+ eval(<<~RUBY).should == { b: 1, c: 2 }
+ case {a: 0, b: 1, c: 2}
+ in a: 0, **rest
+ rest
+ end
+ RUBY
+ end
+
+ it "supports a: which means a: a" do
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in Hash(a:, b:)
+ [a, b]
+ end
+ RUBY
+
+ a = b = nil
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in Hash[a:, b:]
+ [a, b]
+ end
+ RUBY
+
+ a = b = nil
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in {a:, b:}
+ [a, b]
+ end
+ RUBY
+
+ a = nil
+ eval(<<~RUBY).should == [0, {b: 1, c: 2}]
+ case {a: 0, b: 1, c: 2}
+ in {a:, **rest}
+ [a, rest]
+ end
+ RUBY
+
+ a = b = nil
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in a:, b:
+ [a, b]
+ end
+ RUBY
+ end
+
+ it "can mix key (a:) and key-value (a: b) declarations" do
+ eval(<<~RUBY).should == [0, 1]
+ case {a: 0, b: 1}
+ in Hash(a:, b: x)
+ [a, x]
+ end
+ RUBY
+ end
+
+ it "supports 'string': key literal" do
+ eval(<<~RUBY).should == true
+ case {a: 0}
+ in {"a": 0}
+ true
+ end
+ RUBY
+ end
+
+ it "does not support non-symbol keys" do
+ -> {
+ eval <<~RUBY
+ case {a: 1}
+ in {"a" => 1}
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /unexpected/)
+ end
+
+ it "does not support string interpolation in keys" do
+ x = "a"
+
+ -> {
+ eval <<~'RUBY'
+ case {a: 1}
+ in {"#{x}": 1}
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /symbol literal with interpolation is not allowed/)
+ end
+
+ it "raise SyntaxError when keys duplicate in pattern" do
+ -> {
+ eval <<~RUBY
+ case {a: 1}
+ in {a: 1, b: 2, a: 3}
+ end
+ RUBY
+ }.should raise_error(SyntaxError, /duplicated key name/)
+ end
+
+ it "matches an object with #deconstruct_keys method which returns a Hash with equal keys and each value in Hash matches value in pattern" do
+ obj = Object.new
+ def obj.deconstruct_keys(*); {a: 1} end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in {a: 1}
+ true
+ end
+ RUBY
+ end
+
+ it "calls #deconstruct_keys per pattern" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*)
+ ScratchPad << :deconstruct_keys
+ {a: 1}
+ end
+
+ eval(<<~RUBY).should == true
+ case obj
+ in {b: 1}
+ false
+ in {a: 1}
+ true
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [:deconstruct_keys, :deconstruct_keys]
+ end
+
+ it "does not match object if Constant === object returns false" do
+ eval(<<~RUBY).should == false
+ case {a: 1}
+ in String[a: 1]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "does not match object without #deconstruct_keys method" do
+ obj = Object.new
+ obj.should_receive(:respond_to?).with(:deconstruct_keys)
+
+ eval(<<~RUBY).should == false
+ case obj
+ in Object[a: 1]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "does not match object if #deconstruct_keys method does not return Hash" do
+ obj = Object.new
+ def obj.deconstruct_keys(*); "" end
+
+ -> {
+ eval <<~RUBY
+ case obj
+ in Object[a: 1]
+ end
+ RUBY
+ }.should raise_error(TypeError, /deconstruct_keys must return Hash/)
+ end
+
+ it "does not match object if #deconstruct_keys method returns Hash with non-symbol keys" do
+ obj = Object.new
+ def obj.deconstruct_keys(*); {"a" => 1} end
+
+ eval(<<~RUBY).should == false
+ case obj
+ in Object[a: 1]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "does not match object if elements of Hash returned by #deconstruct_keys method does not match values in pattern" do
+ obj = Object.new
+ def obj.deconstruct_keys(*); {a: 1} end
+
+ eval(<<~RUBY).should == false
+ case obj
+ in Object[a: 2]
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "passes keys specified in pattern as arguments to #deconstruct_keys method" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*args)
+ ScratchPad << args
+ {a: 1, b: 2, c: 3}
+ end
+
+ eval <<~RUBY
+ case obj
+ in Object[a: 1, b: 2, c: 3]
+ end
+ RUBY
+
+ ScratchPad.recorded.sort.should == [[[:a, :b, :c]]]
+ end
+
+ it "passes keys specified in pattern to #deconstruct_keys method if pattern contains double splat operator **" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*args)
+ ScratchPad << args
+ {a: 1, b: 2, c: 3}
+ end
+
+ eval <<~RUBY
+ case obj
+ in Object[a: 1, b: 2, **]
+ end
+ RUBY
+
+ ScratchPad.recorded.sort.should == [[[:a, :b]]]
+ end
+
+ it "passes nil to #deconstruct_keys method if pattern contains double splat operator **rest" do
+ obj = Object.new
+
+ def obj.deconstruct_keys(*args)
+ ScratchPad << args
+ {a: 1, b: 2}
+ end
+
+ eval <<~RUBY
+ case obj
+ in Object[a: 1, **rest]
+ end
+ RUBY
+
+ ScratchPad.recorded.should == [[nil]]
+ end
+
+ it "binds variables" do
+ eval(<<~RUBY).should == [0, 1, 2]
+ case {a: 0, b: 1, c: 2}
+ in {a: x, b: y, c: z}
+ [x, y, z]
+ end
+ RUBY
+ end
+
+ it "supports double splat operator **rest" do
+ eval(<<~RUBY).should == {b: 1, c: 2}
+ case {a: 0, b: 1, c: 2}
+ in {a: 0, **rest}
+ rest
+ end
+ RUBY
+ end
+
+ it "treats **nil like there should not be any other keys in a matched Hash" do
+ eval(<<~RUBY).should == true
+ case {a: 1, b: 2}
+ in {a: 1, b: 2, **nil}
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == false
+ case {a: 1, b: 2}
+ in {a: 1, **nil}
+ true
+ else
+ false
+ end
+ RUBY
+ end
+
+ it "can match partially" do
+ eval(<<~RUBY).should == true
+ case {a: 1, b: 2}
+ in {a: 1}
+ true
+ end
+ RUBY
+ end
+
+ it "matches {} with {}" do
+ eval(<<~RUBY).should == true
+ case {}
+ in {}
+ true
+ end
+ RUBY
+ end
+
+ it "matches anything with **" do
+ eval(<<~RUBY).should == true
+ case {a: 1}
+ in **;
+ true
+ end
+ RUBY
+ end
+
+ it "can be used as a nested pattern" do
+ eval(<<~RUBY).should == true
+ case {a: {a: 1, b: 1}, b: {a: 1, b: 2}}
+ in {a: {a: 0}}
+ false
+ in {a: {a: 1}, b: {b: 1}}
+ false
+ in {a: {a: 1}, b: {b: 2}}
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case [{a: 1, b: [1]}, {a: 1, c: ["2"]}]
+ in [{a:, c:},]
+ false
+ in [{a: 1, b:}, {a: 1, c: [Integer]}]
+ false
+ in [_, {a: 1, c: [String]}]
+ true
+ end
+ RUBY
+ end
+ end
+
+ describe "refinements" do
+ it "are used for #deconstruct" do
+ refinery = Module.new do
+ refine Array do
+ def deconstruct
+ [0]
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinery
+
+ result = eval(<<~RUBY)
+ case []
+ in [0]
+ true
+ end
+ RUBY
+ end
+
+ result.should == true
+ end
+
+ it "are used for #deconstruct_keys" do
+ refinery = Module.new do
+ refine Hash do
+ def deconstruct_keys(_)
+ {a: 0}
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinery
+
+ result = eval(<<~RUBY)
+ case {}
+ in a: 0
+ true
+ end
+ RUBY
+ end
+
+ result.should == true
+ end
+
+ it "are used for #=== in constant pattern" do
+ refinery = Module.new do
+ refine Array.singleton_class do
+ def ===(obj)
+ obj.is_a?(Hash)
+ end
+ end
+ end
+
+ result = nil
+ Module.new do
+ using refinery
+
+ result = eval(<<~RUBY)
+ case {}
+ in Array
+ true
+ end
+ RUBY
+ end
+
+ result.should == true
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "can omit parentheses in one line pattern matching" do
+ eval(<<~RUBY).should == [1, 2]
+ [1, 2] => a, b
+ [a, b]
+ RUBY
+
+ eval(<<~RUBY).should == 1
+ {a: 1} => a:
+ a
+ RUBY
+ end
+
+ it "supports pinning instance variables" do
+ eval(<<~RUBY).should == true
+ @a = /a/
+ case 'abc'
+ in ^@a
+ true
+ end
+ RUBY
+ end
+
+ it "supports pinning class variables" do
+ result = nil
+ Module.new do
+ result = module_eval(<<~RUBY)
+ @@a = 0..10
+
+ case 2
+ in ^@@a
+ true
+ end
+ RUBY
+ end
+
+ result.should == true
+ end
+
+ it "supports pinning global variables" do
+ eval(<<~RUBY).should == true
+ $a = /a/
+ case 'abc'
+ in ^$a
+ true
+ end
+ RUBY
+ end
+
+ it "supports pinning expressions" do
+ eval(<<~RUBY).should == true
+ case 'abc'
+ in ^(/a/)
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case {name: '2.6', released_at: Time.new(2018, 12, 25)}
+ in {released_at: ^(Time.new(2010)..Time.new(2020))}
+ true
+ end
+ RUBY
+
+ eval(<<~RUBY).should == true
+ case 0
+ in ^(0+0)
+ true
+ end
+ RUBY
+ end
+ end
+end
diff --git a/spec/ruby/language/precedence_spec.rb b/spec/ruby/language/precedence_spec.rb
new file mode 100644
index 0000000000..c5adcca2c0
--- /dev/null
+++ b/spec/ruby/language/precedence_spec.rb
@@ -0,0 +1,445 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/precedence'
+
+# Specifying the behavior of operators in combination could
+# lead to combinatorial explosion. A better way seems to be
+# to use a technique from formal proofs that involve a set of
+# equivalent statements. Suppose you have statements A, B, C.
+# If they are claimed to be equivalent, this can be shown by
+# proving that A implies B, B implies C, and C implies A.
+# (Actually any closed circuit of implications.)
+#
+# Here, we can use a similar technique where we show starting
+# at the top that each level of operator has precedence over
+# the level below (as well as showing associativity within
+# the precedence level).
+
+# Excerpted from 'Programming Ruby: The Pragmatic Programmer's Guide'
+# Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 324
+#
+# Table 22.4. Ruby operators (high to low precedence)
+# Method Operator Description
+# -----------------------------------------------------------------------
+# :: .
+# x* [ ] [ ]= Element reference, element set
+# x ** Exponentiation
+# x ! ~ + - Not, complement, unary plus and minus
+# (method names for the last two are +@ and -@)
+# x * / % Multiply, divide, and modulo
+# x + - Plus and minus
+# x >> << Right and left shift
+# x & “And†(bitwise for integers)
+# x ^ | Exclusive “or†and regular “or†(bitwise for integers)
+# x <= < > >= Comparison operators
+# x <=> == === != =~ !~ Equality and pattern match operators (!=
+# and !~ may not be defined as methods)
+# && Logical “andâ€
+# || Logical “orâ€
+# .. ... Range (inclusive and exclusive)
+# ? : Ternary if-then-else
+# = %= /= -= += |= &= Assignment
+# >>= <<= *= &&= ||= **=
+# defined? Check if symbol defined
+# not Logical negation
+# or and Logical composition
+# if unless while until Expression modifiers
+# begin/end Block expression
+# -----------------------------------------------------------------------
+#
+# * Operators marked with 'x' in the Method column are implemented as methods
+# and can be overridden (except != and !~ as noted). (But see the specs
+# below for implementations that define != and !~ as methods.)
+#
+# ** These are not included in the excerpted table but are shown here for
+# completeness.
+
+# -----------------------------------------------------------------------
+# It seems that this table is not correct anymore
+# The correct table derived from MRI's parse.y is as follows:
+#
+# Operator Assoc Description
+#---------------------------------------------------------------
+# ! ~ + > Not, complement, unary plus
+# ** > Exponentiation
+# - > Unary minus
+# * / % < Multiply, divide, and modulo
+# + - < Plus and minus
+# >> << < Right and left shift
+# & < “And†(bitwise for integers)
+# ^ | < Exclusive “or†and regular “or†(bitwise for integers)
+# <= < > >= < Comparison operators
+# <=> == === != =~ !~ no Equality and pattern match operators (!=
+# and !~ may not be defined as methods)
+# && < Logical “andâ€
+# || < Logical “orâ€
+# .. ... no Range (inclusive and exclusive)
+# ? : > Ternary if-then-else
+# rescue < Rescue modifier
+# = %= /= -= += |= &= > Assignment
+# >>= <<= *= &&= ||= **=
+# defined? no Check if symbol defined
+# not > Logical negation
+# or and < Logical composition
+# if unless while until no Expression modifiers
+# -----------------------------------------------------------------------
+#
+# [] and []= seem to fall out of here, as well as begin/end
+#
+
+# TODO: Resolve these two tables with actual specs. As the comment at the
+# top suggests, these specs need to be reorganized into a single describe
+# block for each operator. The describe block should include an example
+# for associativity (if relevant), an example for any short circuit behavior
+# (e.g. &&, ||, etc.) and an example block for each operator over which the
+# instant operator has immediately higher precedence.
+
+describe "Operators" do
+ it "! ~ + is right-associative" do
+ (!!true).should == true
+ (~~0).should == 0
+ (++2).should == 2
+ end
+
+ it "** is right-associative" do
+ (2**2**3).should == 256
+ end
+
+ it "** has higher precedence than unary minus" do
+ (-2**2).should == -4
+ end
+
+ it "unary minus is right-associative" do
+ (--2).should == 2
+ end
+
+ it "unary minus has higher precedence than * / %" do
+ class UnaryMinusTest; def -@; 50; end; end
+ b = UnaryMinusTest.new
+
+ (-b * 5).should == 250
+ (-b / 5).should == 10
+ (-b % 7).should == 1
+ end
+
+ it "treats +/- as a regular send if the arguments are known locals or block locals" do
+ a = PrecedenceSpecs::NonUnaryOpTest.new
+ a.add_num(1).should == [3]
+ a.sub_num(1).should == [1]
+ a.add_str.should == ['11']
+ a.add_var.should == [2]
+ end
+
+ it "* / % are left-associative" do
+ (2*1/2).should == (2*1)/2
+ # Guard against the Mathn library
+ # TODO: Make these specs not rely on specific behaviour / result values
+ # by using mocks.
+ guard -> { !defined?(Math.rsqrt) } do
+ (2*1/2).should_not == 2*(1/2)
+ end
+
+ (10/7/5).should == (10/7)/5
+ (10/7/5).should_not == 10/(7/5)
+
+ (101 % 55 % 7).should == (101 % 55) % 7
+ (101 % 55 % 7).should_not == 101 % (55 % 7)
+
+ (50*20/7%42).should == ((50*20)/7)%42
+ (50*20/7%42).should_not == 50*(20/(7%42))
+ end
+
+ it "* / % have higher precedence than + -" do
+ (2+2*2).should == 6
+ (1+10/5).should == 3
+ (2+10%5).should == 2
+
+ (2-2*2).should == -2
+ (1-10/5).should == -1
+ (10-10%4).should == 8
+ end
+
+ it "+ - are left-associative" do
+ (2-3-4).should == -5
+ (4-3+2).should == 3
+
+ binary_plus = Class.new(String) do
+ alias_method :plus, :+
+ def +(a)
+ plus(a) + "!"
+ end
+ end
+ s = binary_plus.new("a")
+
+ (s+s+s).should == (s+s)+s
+ (s+s+s).should_not == s+(s+s)
+ end
+
+ it "+ - have higher precedence than >> <<" do
+ (2<<1+2).should == 16
+ (8>>1+2).should == 1
+ (4<<1-3).should == 1
+ (2>>1-3).should == 8
+ end
+
+ it ">> << are left-associative" do
+ (1 << 2 << 3).should == 32
+ (10 >> 1 >> 1).should == 2
+ (10 << 4 >> 1).should == 80
+ end
+
+ it ">> << have higher precedence than &" do
+ (4 & 2 << 1).should == 4
+ (2 & 4 >> 1).should == 2
+ end
+
+ it "& is left-associative" do
+ class BitwiseAndTest; def &(a); a+1; end; end
+ c = BitwiseAndTest.new
+
+ (c & 5 & 2).should == (c & 5) & 2
+ (c & 5 & 2).should_not == c & (5 & 2)
+ end
+
+ it "& has higher precedence than ^ |" do
+ (8 ^ 16 & 16).should == 24
+ (8 | 16 & 16).should == 24
+ end
+
+ it "^ | are left-associative" do
+ class OrAndXorTest; def ^(a); a+10; end; def |(a); a-10; end; end
+ d = OrAndXorTest.new
+
+ (d ^ 13 ^ 16).should == (d ^ 13) ^ 16
+ (d ^ 13 ^ 16).should_not == d ^ (13 ^ 16)
+
+ (d | 13 | 4).should == (d | 13) | 4
+ (d | 13 | 4).should_not == d | (13 | 4)
+ end
+
+ it "^ | have higher precedence than <= < > >=" do
+ (10 <= 7 ^ 7).should == false
+ (10 < 7 ^ 7).should == false
+ (10 > 7 ^ 7).should == true
+ (10 >= 7 ^ 7).should == true
+ (10 <= 7 | 7).should == false
+ (10 < 7 | 7).should == false
+ (10 > 7 | 7).should == true
+ (10 >= 7 | 7).should == true
+ end
+
+ it "<= < > >= are left-associative" do
+ class ComparisonTest
+ def <=(a); 0; end;
+ def <(a); 0; end;
+ def >(a); 0; end;
+ def >=(a); 0; end;
+ end
+
+ e = ComparisonTest.new
+
+ (e <= 0 <= 1).should == (e <= 0) <= 1
+ (e <= 0 <= 1).should_not == e <= (0 <= 1)
+
+ (e < 0 < 1).should == (e < 0) < 1
+ (e < 0 < 1).should_not == e < (0 < 1)
+
+ (e >= 0 >= 1).should == (e >= 0) >= 1
+ (e >= 0 >= 1).should_not == e >= (0 >= 1)
+
+ (e > 0 > 1).should == (e > 0) > 1
+ (e > 0 > 1).should_not == e > (0 > 1)
+ end
+
+ it "<=> == === != =~ !~ are non-associative" do
+ -> { eval("1 <=> 2 <=> 3") }.should raise_error(SyntaxError)
+ -> { eval("1 == 2 == 3") }.should raise_error(SyntaxError)
+ -> { eval("1 === 2 === 3") }.should raise_error(SyntaxError)
+ -> { eval("1 != 2 != 3") }.should raise_error(SyntaxError)
+ -> { eval("1 =~ 2 =~ 3") }.should raise_error(SyntaxError)
+ -> { eval("1 !~ 2 !~ 3") }.should raise_error(SyntaxError)
+ end
+
+ it "<=> == === != =~ !~ have higher precedence than &&" do
+ (false && 2 <=> 3).should == false
+ (false && 3 == false).should == false
+ (false && 3 === false).should == false
+ (false && 3 != true).should == false
+
+ class FalseClass; def =~(o); o == false; end; end
+ (false && true =~ false).should == (false && (true =~ false))
+ (false && true =~ false).should_not == ((false && true) =~ false)
+ class FalseClass; undef_method :=~; end
+
+ (false && true !~ true).should == false
+ end
+
+ # XXX: figure out how to test it
+ # (a && b) && c equals to a && (b && c) for all a,b,c values I can imagine so far
+ it "&& is left-associative"
+
+ it "&& has higher precedence than ||" do
+ (true || false && false).should == true
+ end
+
+ # XXX: figure out how to test it
+ it "|| is left-associative"
+
+ it "|| has higher precedence than .. ..." do
+ (1..false||10).should == (1..10)
+ (1...false||10).should == (1...10)
+ end
+
+ it ".. ... are non-associative" do
+ -> { eval("1..2..3") }.should raise_error(SyntaxError)
+ -> { eval("1...2...3") }.should raise_error(SyntaxError)
+ end
+
+ it ".. ... have higher precedence than ? :" do
+ # Use variables to avoid warnings
+ from = 1
+ to = 2
+ # These are flip-flop, not Range instances
+ (from..to ? 3 : 4).should == 3
+ (from...to ? 3 : 4).should == 3
+ end
+
+ it "? : is right-associative" do
+ (true ? 2 : 3 ? 4 : 5).should == 2
+ end
+
+ def oops; raise end
+
+ it "? : has higher precedence than rescue" do
+ (true ? oops : 0 rescue 10).should == 10
+ end
+
+ # XXX: figure how to test it (problem similar to || associativity)
+ it "rescue is left-associative"
+
+ it "rescue has higher precedence than =" do
+ a = oops rescue 10
+ a.should == 10
+
+ # rescue doesn't have the same sense for %= /= and friends
+ end
+
+ it "= %= /= -= += |= &= >>= <<= *= &&= ||= **= are right-associative" do
+ a = b = 10
+ a.should == 10
+ b.should == 10
+
+ a = b = 10
+ a %= b %= 3
+ a.should == 0
+ b.should == 1
+
+ a = b = 10
+ a /= b /= 2
+ a.should == 2
+ b.should == 5
+
+ a = b = 10
+ a -= b -= 2
+ a.should == 2
+ b.should == 8
+
+ a = b = 10
+ a += b += 2
+ a.should == 22
+ b.should == 12
+
+ a,b = 32,64
+ a |= b |= 2
+ a.should == 98
+ b.should == 66
+
+ a,b = 25,13
+ a &= b &= 7
+ a.should == 1
+ b.should == 5
+
+ a,b=8,2
+ a >>= b >>= 1
+ a.should == 4
+ b.should == 1
+
+ a,b=8,2
+ a <<= b <<= 1
+ a.should == 128
+ b.should == 4
+
+ a,b=8,2
+ a *= b *= 2
+ a.should == 32
+ b.should == 4
+
+ a,b=10,20
+ a &&= b &&= false
+ a.should == false
+ b.should == false
+
+ a,b=nil,nil
+ a ||= b ||= 10
+ a.should == 10
+ b.should == 10
+
+ a,b=2,3
+ a **= b **= 2
+ a.should == 512
+ b.should == 9
+ end
+
+ it "= %= /= -= += |= &= >>= <<= *= &&= ||= **= have higher precedence than defined? operator" do
+ (defined? a = 10).should == "assignment"
+ (defined? a %= 10).should == "assignment"
+ (defined? a /= 10).should == "assignment"
+ (defined? a -= 10).should == "assignment"
+ (defined? a += 10).should == "assignment"
+ (defined? a |= 10).should == "assignment"
+ (defined? a &= 10).should == "assignment"
+ (defined? a >>= 10).should == "assignment"
+ (defined? a <<= 10).should == "assignment"
+ (defined? a *= 10).should == "assignment"
+ (defined? a &&= 10).should == "assignment"
+ (defined? a ||= 10).should == "assignment"
+ (defined? a **= 10).should == "assignment"
+ end
+
+ # XXX: figure out how to test it
+ it "defined? is non-associative"
+
+ it "defined? has higher precedence than not" do
+ # does it have sense?
+ (not defined? qqq).should == true
+ end
+
+ it "not is right-associative" do
+ (not not false).should == false
+ (not not 10).should == true
+ end
+
+ it "not has higher precedence than or/and" do
+ (not false and false).should == false
+ (not false or true).should == true
+ end
+
+ # XXX: figure out how to test it
+ it "or/and are left-associative"
+
+ it "or/and have higher precedence than if unless while until modifiers" do
+ (1 if 2 and 3).should == 1
+ (1 if 2 or 3).should == 1
+
+ (1 unless false and true).should == 1
+ (1 unless false or false).should == 1
+
+ (1 while true and false).should == nil # would hang upon error
+ (1 while false or false).should == nil
+
+ ((raise until true and false) rescue 10).should == 10
+ (1 until false or true).should == nil # would hang upon error
+ end
+
+ # XXX: it seems to me they are right-associative
+ it "if unless while until are non-associative"
+end
diff --git a/spec/ruby/language/predefined/data_spec.rb b/spec/ruby/language/predefined/data_spec.rb
new file mode 100644
index 0000000000..921d236ee9
--- /dev/null
+++ b/spec/ruby/language/predefined/data_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+
+describe "The DATA constant" do
+ it "exists when the main script contains __END__" do
+ ruby_exe(fixture(__FILE__, "data1.rb")).chomp.should == "true"
+ end
+
+ it "does not exist when the main script contains no __END__" do
+ ruby_exe("puts Object.const_defined?(:DATA)").chomp.should == 'false'
+ end
+
+ it "does not exist when an included file has a __END__" do
+ ruby_exe(fixture(__FILE__, "data2.rb")).chomp.should == "false"
+ end
+
+ it "does not change when an included files also has a __END__" do
+ ruby_exe(fixture(__FILE__, "data3.rb")).chomp.should == "data 3"
+ end
+
+ it "is included in an otherwise empty file" do
+ ap = fixture(__FILE__, "print_data.rb")
+ str = ruby_exe(fixture(__FILE__, "data_only.rb"), options: "-r#{ap}")
+ str.chomp.should == "data only"
+ end
+
+ it "returns a File object with the right offset" do
+ ruby_exe(fixture(__FILE__, "data_offset.rb")).should == "File\n121\n"
+ end
+
+ it "is set even if there is no data after __END__" do
+ ruby_exe(fixture(__FILE__, "empty_data.rb")).should == "31\n\"\"\n"
+ end
+
+ it "is set even if there is no newline after __END__" do
+ path = tmp("no_newline_data.rb")
+ code = File.binread(fixture(__FILE__, "empty_data.rb"))
+ touch(path, "wb") { |f| f.write code.chomp }
+ begin
+ ruby_exe(path).should == "30\n\"\"\n"
+ ensure
+ rm_r path
+ end
+ end
+
+ it "rewinds to the head of the main script" do
+ ruby_exe(fixture(__FILE__, "data5.rb")).chomp.should == "DATA.rewind"
+ end
+end
diff --git a/spec/ruby/language/predefined/fixtures/data1.rb b/spec/ruby/language/predefined/fixtures/data1.rb
new file mode 100644
index 0000000000..cb9572255b
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data1.rb
@@ -0,0 +1,4 @@
+puts Object.const_defined?(:DATA)
+
+__END__
+data1
diff --git a/spec/ruby/language/predefined/fixtures/data2.rb b/spec/ruby/language/predefined/fixtures/data2.rb
new file mode 100644
index 0000000000..a764ca56d1
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data2.rb
@@ -0,0 +1,3 @@
+require_relative 'data4'
+
+p Object.const_defined?(:DATA)
diff --git a/spec/ruby/language/predefined/fixtures/data3.rb b/spec/ruby/language/predefined/fixtures/data3.rb
new file mode 100644
index 0000000000..e37313c68b
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data3.rb
@@ -0,0 +1,6 @@
+require_relative 'data4'
+
+puts DATA.read
+
+__END__
+data 3
diff --git a/spec/ruby/language/predefined/fixtures/data4.rb b/spec/ruby/language/predefined/fixtures/data4.rb
new file mode 100644
index 0000000000..139ef80d7b
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data4.rb
@@ -0,0 +1,4 @@
+# nothing
+
+__END__
+data 4
diff --git a/spec/ruby/language/predefined/fixtures/data5.rb b/spec/ruby/language/predefined/fixtures/data5.rb
new file mode 100644
index 0000000000..48f060e1a9
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data5.rb
@@ -0,0 +1,5 @@
+DATA.rewind
+puts DATA.gets
+
+__END__
+data 5
diff --git a/spec/ruby/language/predefined/fixtures/data_offset.rb b/spec/ruby/language/predefined/fixtures/data_offset.rb
new file mode 100644
index 0000000000..9829b3f87f
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data_offset.rb
@@ -0,0 +1,12 @@
+# some comment
+
+foo = <<HEREDOC
+some heredoc to make the
+spec more interesting
+HEREDOC
+
+p DATA.class
+p DATA.pos
+
+__END__
+data offset
diff --git a/spec/ruby/language/predefined/fixtures/data_only.rb b/spec/ruby/language/predefined/fixtures/data_only.rb
new file mode 100644
index 0000000000..004ac62737
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/data_only.rb
@@ -0,0 +1,2 @@
+__END__
+data only
diff --git a/spec/ruby/language/predefined/fixtures/empty_data.rb b/spec/ruby/language/predefined/fixtures/empty_data.rb
new file mode 100644
index 0000000000..c6d9bc6f1f
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/empty_data.rb
@@ -0,0 +1,3 @@
+p DATA.pos
+p DATA.read
+__END__
diff --git a/spec/ruby/language/predefined/fixtures/print_data.rb b/spec/ruby/language/predefined/fixtures/print_data.rb
new file mode 100644
index 0000000000..4a5692e6a7
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/print_data.rb
@@ -0,0 +1,3 @@
+at_exit {
+ puts DATA.read
+}
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb
new file mode 100644
index 0000000000..f7809109fa
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb
@@ -0,0 +1,4 @@
+p TOPLEVEL_BINDING.local_variables.sort
+TOPLEVEL_BINDING.local_variable_set(:dynamic_set_main, 2)
+p TOPLEVEL_BINDING.local_variables.sort
+main_script = 3
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb
new file mode 100644
index 0000000000..7ccf329680
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb
@@ -0,0 +1,2 @@
+TOPLEVEL_BINDING.local_variable_set(:dynamic_set_required, 1)
+p TOPLEVEL_BINDING.local_variables
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb
new file mode 100644
index 0000000000..3626ea1f10
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb
@@ -0,0 +1,4 @@
+a = TOPLEVEL_BINDING.object_id
+require_relative 'toplevel_binding_id_required'
+c = eval('TOPLEVEL_BINDING.object_id')
+p [a, $b, c].uniq.size
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb
new file mode 100644
index 0000000000..b31b6e32a0
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb
@@ -0,0 +1 @@
+$b = TOPLEVEL_BINDING.object_id
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb
new file mode 100644
index 0000000000..58924a5800
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb
@@ -0,0 +1,2 @@
+required = true
+p [:required_before, TOPLEVEL_BINDING.local_variables]
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb
new file mode 100644
index 0000000000..42bd67f347
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb
@@ -0,0 +1,9 @@
+p TOPLEVEL_BINDING.local_variable_get(:a)
+p TOPLEVEL_BINDING.local_variable_get(:b)
+a = 1
+p TOPLEVEL_BINDING.local_variable_get(:a)
+p TOPLEVEL_BINDING.local_variable_get(:b)
+b = 2
+a = 3
+p TOPLEVEL_BINDING.local_variable_get(:a)
+p TOPLEVEL_BINDING.local_variable_get(:b)
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb
new file mode 100644
index 0000000000..151f4340ef
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb
@@ -0,0 +1,4 @@
+main_script = 1
+require_relative 'toplevel_binding_variables_required'
+eval('eval_var = 3')
+p TOPLEVEL_BINDING.local_variables
diff --git a/spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb
new file mode 100644
index 0000000000..614547fe16
--- /dev/null
+++ b/spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb
@@ -0,0 +1,2 @@
+required = 2
+p [:required_after, TOPLEVEL_BINDING.local_variables]
diff --git a/spec/ruby/language/predefined/toplevel_binding_spec.rb b/spec/ruby/language/predefined/toplevel_binding_spec.rb
new file mode 100644
index 0000000000..69ac28618c
--- /dev/null
+++ b/spec/ruby/language/predefined/toplevel_binding_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "The TOPLEVEL_BINDING constant" do
+ it "only includes local variables defined in the main script, not in required files or eval" do
+ binding_toplevel_variables = ruby_exe(fixture(__FILE__, "toplevel_binding_variables.rb"))
+ binding_toplevel_variables.should == "[:required_after, [:main_script]]\n[:main_script]\n"
+ end
+
+ it "has no local variables in files required before the main script" do
+ required = fixture(__FILE__, 'toplevel_binding_required_before.rb')
+ out = ruby_exe("a=1; p TOPLEVEL_BINDING.local_variables.sort; b=2", options: "-r#{required}")
+ out.should == "[:required_before, []]\n[:a, :b]\n"
+ end
+
+ it "merges local variables of the main script with dynamically-defined Binding variables" do
+ required = fixture(__FILE__, 'toplevel_binding_dynamic_required.rb')
+ out = ruby_exe(fixture(__FILE__, 'toplevel_binding_dynamic.rb'), options: "-r#{required}")
+ out.should == <<EOS
+[:dynamic_set_required]
+[:dynamic_set_required, :main_script]
+[:dynamic_set_main, :dynamic_set_required, :main_script]
+EOS
+ end
+
+ it "gets updated variables values as they are defined and set" do
+ out = ruby_exe(fixture(__FILE__, "toplevel_binding_values.rb"))
+ out.should == "nil\nnil\n1\nnil\n3\n2\n"
+ end
+
+ it "is always the same object for all top levels" do
+ binding_toplevel_id = ruby_exe(fixture(__FILE__, "toplevel_binding_id.rb"))
+ binding_toplevel_id.should == "1\n"
+ end
+end
diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb
new file mode 100644
index 0000000000..d1cda25918
--- /dev/null
+++ b/spec/ruby/language/predefined_spec.rb
@@ -0,0 +1,1394 @@
+require_relative '../spec_helper'
+require 'stringio'
+
+# The following tables are excerpted from Programming Ruby: The Pragmatic Programmer's Guide'
+# Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 319-22.
+#
+# Entries marked [r/o] are read-only and an error will be raised of the program attempts to
+# modify them. Entries marked [thread] are thread local.
+
+# Exception Information
+# ---------------------------------------------------------------------------------------------------
+#
+# $! Exception The exception object passed to raise. [thread]
+# $@ Array The stack backtrace generated by the last exception. [thread]
+
+# Pattern Matching Variables
+# ---------------------------------------------------------------------------------------------------
+#
+# These variables are set to nil after an unsuccessful pattern match.
+#
+# $& String The string matched (following a successful pattern match). This variable is
+# local to the current scope. [r/o, thread]
+# $+ String The contents of the highest-numbered group matched following a successful
+# pattern match. Thus, in "cat" =~/(c|a)(t|z)/, $+ will be set to “tâ€. This
+# variable is local to the current scope. [r/o, thread]
+# $` String The string preceding the match in a successful pattern match. This variable
+# is local to the current scope. [r/o, thread]
+# $' String The string following the match in a successful pattern match. This variable
+# is local to the current scope. [r/o, thread]
+# $1 to $<N> String The contents of successive groups matched in a successful pattern match. In
+# "cat" =~/(c|a)(t|z)/, $1 will be set to “a†and $2 to “tâ€. This variable
+# is local to the current scope. [r/o, thread]
+# $~ MatchData An object that encapsulates the results of a successful pattern match. The
+# variables $&, $`, $', and $1 to $<N> are all derived from $~. Assigning to $~
+# changes the values of these derived variables. This variable is local to the
+# current scope. [thread]
+
+
+describe "Predefined global $~" do
+ it "is set to contain the MatchData object of the last match if successful" do
+ md = /foo/.match 'foo'
+ $~.should be_kind_of(MatchData)
+ $~.should equal md
+
+ /bar/ =~ 'bar'
+ $~.should be_kind_of(MatchData)
+ $~.should_not equal md
+ end
+
+ it "is set to nil if the last match was unsuccessful" do
+ /foo/ =~ 'foo'
+ $~.should_not.nil?
+
+ /foo/ =~ 'bar'
+ $~.should.nil?
+ end
+
+ it "is set at the method-scoped level rather than block-scoped" do
+ obj = Object.new
+ def obj.foo; yield; end
+ def obj.foo2(&proc); proc.call; end
+
+ match2 = nil
+ match3 = nil
+ match4 = nil
+
+ match1 = /foo/.match "foo"
+
+ obj.foo { match2 = /bar/.match("bar") }
+
+ match2.should_not == nil
+ $~.should == match2
+
+ eval 'match3 = /baz/.match("baz")'
+
+ match3.should_not == nil
+ $~.should == match3
+
+ obj.foo2 { match4 = /qux/.match("qux") }
+
+ match4.should_not == nil
+ $~.should == match4
+ end
+
+ it "raises an error if assigned an object not nil or instanceof MatchData" do
+ $~ = nil
+ $~.should == nil
+ $~ = /foo/.match("foo")
+ $~.should be_an_instance_of(MatchData)
+
+ -> { $~ = Object.new }.should raise_error(TypeError)
+ -> { $~ = 1 }.should raise_error(TypeError)
+ end
+
+ it "changes the value of derived capture globals when assigned" do
+ "foo" =~ /(f)oo/
+ foo_match = $~
+ "bar" =~ /(b)ar/
+ $~ = foo_match
+ $1.should == "f"
+ end
+
+ it "changes the value of the derived preceding match global" do
+ "foo hello" =~ /hello/
+ foo_match = $~
+ "bar" =~ /(bar)/
+ $~ = foo_match
+ $`.should == "foo "
+ end
+
+ it "changes the value of the derived following match global" do
+ "foo hello" =~ /foo/
+ foo_match = $~
+ "bar" =~ /(bar)/
+ $~ = foo_match
+ $'.should == " hello"
+ end
+
+ it "changes the value of the derived full match global" do
+ "foo hello" =~ /foo/
+ foo_match = $~
+ "bar" =~ /(bar)/
+ $~ = foo_match
+ $&.should == "foo"
+ end
+end
+
+describe "Predefined global $&" do
+ it "is equivalent to MatchData#[0] on the last match $~" do
+ /foo/ =~ 'barfoobaz'
+ $&.should == $~[0]
+ $&.should == 'foo'
+ end
+
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /b/
+ $&.encoding.should equal(Encoding::EUC_JP)
+ end
+end
+
+describe "Predefined global $`" do
+ it "is equivalent to MatchData#pre_match on the last match $~" do
+ /foo/ =~ 'barfoobaz'
+ $`.should == $~.pre_match
+ $`.should == 'bar'
+ end
+
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /b/
+ $`.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "sets an empty result to the encoding of the source String" do
+ "abc".force_encoding(Encoding::ISO_8859_1) =~ /a/
+ $`.encoding.should equal(Encoding::ISO_8859_1)
+ end
+end
+
+describe "Predefined global $'" do
+ it "is equivalent to MatchData#post_match on the last match $~" do
+ /foo/ =~ 'barfoobaz'
+ $'.should == $~.post_match
+ $'.should == 'baz'
+ end
+
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /b/
+ $'.encoding.should equal(Encoding::EUC_JP)
+ end
+
+ it "sets an empty result to the encoding of the source String" do
+ "abc".force_encoding(Encoding::ISO_8859_1) =~ /c/
+ $'.encoding.should equal(Encoding::ISO_8859_1)
+ end
+end
+
+describe "Predefined global $+" do
+ it "is equivalent to $~.captures.last" do
+ /(f(o)o)/ =~ 'barfoobaz'
+ $+.should == $~.captures.last
+ $+.should == 'o'
+ end
+
+ it "captures the last non nil capture" do
+ /(a)|(b)/ =~ 'a'
+ $+.should == 'a'
+ end
+
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/
+ $+.encoding.should equal(Encoding::EUC_JP)
+ end
+end
+
+describe "Predefined globals $1..N" do
+ it "are equivalent to $~[N]" do
+ /(f)(o)(o)/ =~ 'foo'
+ $1.should == $~[1]
+ $2.should == $~[2]
+ $3.should == $~[3]
+ $4.should == $~[4]
+
+ [$1, $2, $3, $4].should == ['f', 'o', 'o', nil]
+ end
+
+ it "are nil unless a match group occurs" do
+ def test(arg)
+ case arg
+ when /-(.)?/
+ $1
+ end
+ end
+ test("-").should == nil
+ end
+
+ it "sets the encoding to the encoding of the source String" do
+ "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/
+ $1.encoding.should equal(Encoding::EUC_JP)
+ end
+end
+
+describe "Predefined global $stdout" do
+ before :each do
+ @old_stdout = $stdout
+ end
+
+ after :each do
+ $stdout = @old_stdout
+ end
+
+ it "raises TypeError error if assigned to nil" do
+ -> { $stdout = nil }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError error if assigned to object that doesn't respond to #write" do
+ obj = mock('object')
+ -> { $stdout = obj }.should raise_error(TypeError)
+
+ obj.stub!(:write)
+ $stdout = obj
+ $stdout.should equal(obj)
+ end
+end
+
+describe "Predefined global $!" do
+ # See http://jira.codehaus.org/browse/JRUBY-5550
+ it "remains nil after a failed core class \"checked\" coercion against a class that defines method_missing" do
+ $!.should == nil
+
+ obj = Class.new do
+ def method_missing(*args)
+ super
+ end
+ end.new
+
+ [obj, 'foo'].join
+
+ $!.should == nil
+ end
+
+ it "should be set to the value of $! before the begin after a successful rescue" do
+ outer = StandardError.new 'outer'
+ inner = StandardError.new 'inner'
+
+ begin
+ raise outer
+ rescue
+ $!.should == outer
+
+ # nested rescue
+ begin
+ $!.should == outer
+ raise inner
+ rescue
+ $!.should == inner
+ ensure
+ $!.should == outer
+ end
+ $!.should == outer
+ end
+ $!.should == nil
+ end
+
+ it "should be set to the value of $! before the begin after a rescue which returns" do
+ def foo
+ outer = StandardError.new 'outer'
+ inner = StandardError.new 'inner'
+
+ begin
+ raise outer
+ rescue
+ $!.should == outer
+
+ # nested rescue
+ begin
+ $!.should == outer
+ raise inner
+ rescue
+ $!.should == inner
+ return
+ ensure
+ $!.should == outer
+ end
+ $!.should == outer
+ end
+ $!.should == nil
+ end
+ foo
+ end
+
+ it "should be set to the value of $! before the begin after a successful rescue within an ensure" do
+ outer = StandardError.new 'outer'
+ inner = StandardError.new 'inner'
+
+ begin
+ begin
+ raise outer
+ ensure
+ $!.should == outer
+
+ # nested rescue
+ begin
+ $!.should == outer
+ raise inner
+ rescue
+ $!.should == inner
+ ensure
+ $!.should == outer
+ end
+ $!.should == outer
+ end
+ flunk "outer should be raised after the ensure"
+ rescue
+ $!.should == outer
+ end
+ $!.should == nil
+ end
+
+ it "should be set to the new exception after a throwing rescue" do
+ outer = StandardError.new 'outer'
+ inner = StandardError.new 'inner'
+
+ begin
+ raise outer
+ rescue
+ $!.should == outer
+
+ begin
+ # nested rescue
+ begin
+ $!.should == outer
+ raise inner
+ rescue # the throwing rescue
+ $!.should == inner
+ raise inner
+ ensure
+ $!.should == inner
+ end
+ rescue # do not make the exception fail the example
+ $!.should == inner
+ end
+ $!.should == outer
+ end
+ $!.should == nil
+ end
+
+ describe "in bodies without ensure" do
+ it "should be cleared when an exception is rescued" do
+ e = StandardError.new 'foo'
+ begin
+ raise e
+ rescue
+ $!.should == e
+ end
+ $!.should == nil
+ end
+
+ it "should be cleared when an exception is rescued even when a non-local return is present" do
+ def foo(e)
+ $!.should == e
+ yield
+ end
+ def bar
+ e = StandardError.new 'foo'
+ begin
+ raise e
+ rescue
+ $!.should == e
+ foo(e) { return }
+ end
+ end
+
+ bar
+ $!.should == nil
+ end
+
+ it "should be cleared when an exception is rescued even when a non-local return from block" do
+ def foo
+ [ 1 ].each do
+ begin
+ raise StandardError.new('err')
+ rescue => e
+ $!.should == e
+ return
+ end
+ end
+ end
+
+ foo
+ $!.should == nil
+ end
+
+ it "should not be cleared when an exception is not rescued" do
+ e = StandardError.new
+ begin
+ begin
+ begin
+ raise e
+ rescue TypeError
+ flunk
+ end
+ ensure
+ $!.should == e
+ end
+ rescue
+ $!.should == e
+ end
+ $!.should == nil
+ end
+
+ it "should not be cleared when an exception is rescued and rethrown" do
+ e = StandardError.new 'foo'
+ begin
+ begin
+ begin
+ raise e
+ rescue => e
+ $!.should == e
+ raise e
+ end
+ ensure
+ $!.should == e
+ end
+ rescue
+ $!.should == e
+ end
+ $!.should == nil
+ end
+ end
+
+ describe "in ensure-protected bodies" do
+ it "should be cleared when an exception is rescued" do
+ e = StandardError.new 'foo'
+ begin
+ raise e
+ rescue
+ $!.should == e
+ ensure
+ $!.should == nil
+ end
+ $!.should == nil
+ end
+
+ it "should not be cleared when an exception is not rescued" do
+ e = StandardError.new
+ begin
+ begin
+ begin
+ raise e
+ rescue TypeError
+ flunk
+ ensure
+ $!.should == e
+ end
+ ensure
+ $!.should == e
+ end
+ rescue
+ $!.should == e
+ end
+ end
+
+ it "should not be cleared when an exception is rescued and rethrown" do
+ e = StandardError.new
+ begin
+ begin
+ begin
+ raise e
+ rescue => e
+ $!.should == e
+ raise e
+ ensure
+ $!.should == e
+ end
+ ensure
+ $!.should == e
+ end
+ rescue
+ $!.should == e
+ end
+ end
+ end
+end
+
+# Input/Output Variables
+# ---------------------------------------------------------------------------------------------------
+#
+# $/ String The input record separator (newline by default). This is the value that rou-
+# tines such as Kernel#gets use to determine record boundaries. If set to
+# nil, gets will read the entire file.
+# $-0 String Synonym for $/.
+# $\ String The string appended to the output of every call to methods such as
+# Kernel#print and IO#write. The default value is nil.
+# $, String The separator string output between the parameters to methods such as
+# Kernel#print and Array#join. Defaults to nil, which adds no text.
+# $. Integer The number of the last line read from the current input file.
+# $; String The default separator pattern used by String#split. May be set from the
+# command line using the -F flag.
+# $< Object An object that provides access to the concatenation of the contents of all
+# the files given as command-line arguments or $stdin (in the case where
+# there are no arguments). $< supports methods similar to a File object:
+# binmode, close, closed?, each, each_byte, each_line, eof, eof?,
+# file, filename, fileno, getc, gets, lineno, lineno=, path, pos, pos=,
+# read, readchar, readline, readlines, rewind, seek, skip, tell, to_a,
+# to_i, to_io, to_s, along with the methods in Enumerable. The method
+# file returns a File object for the file currently being read. This may change
+# as $< reads through the files on the command line. [r/o]
+# $> IO The destination of output for Kernel#print and Kernel#printf. The
+# default value is $stdout.
+# $_ String The last line read by Kernel#gets or Kernel#readline. Many string-
+# related functions in the Kernel module operate on $_ by default. The vari-
+# able is local to the current scope. [thread]
+# $-F String Synonym for $;.
+# $stderr IO The current standard error output.
+# $stdin IO The current standard input.
+# $stdout IO The current standard output. Assignment to $stdout is deprecated: use
+# $stdout.reopen instead.
+
+describe "Predefined global $/" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ @dollar_slash = $/
+ @dollar_dash_zero = $-0
+ end
+
+ after :each do
+ $/ = @dollar_slash
+ $-0 = @dollar_dash_zero
+ $VERBOSE = @verbose
+ end
+
+ it "can be assigned a String" do
+ str = "abc"
+ $/ = str
+ $/.should equal(str)
+ end
+
+ it "can be assigned nil" do
+ $/ = nil
+ $/.should be_nil
+ end
+
+ it "returns the value assigned" do
+ ($/ = "xyz").should == "xyz"
+ end
+
+ it "changes $-0" do
+ $/ = "xyz"
+ $-0.should equal($/)
+ end
+
+ it "does not call #to_str to convert the object to a String" do
+ obj = mock("$/ value")
+ obj.should_not_receive(:to_str)
+
+ -> { $/ = obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if assigned an Integer" do
+ -> { $/ = 1 }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if assigned a boolean" do
+ -> { $/ = true }.should raise_error(TypeError)
+ end
+end
+
+describe "Predefined global $-0" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ @dollar_slash = $/
+ @dollar_dash_zero = $-0
+ end
+
+ after :each do
+ $/ = @dollar_slash
+ $-0 = @dollar_dash_zero
+ $VERBOSE = @verbose
+ end
+
+ it "can be assigned a String" do
+ str = "abc"
+ $-0 = str
+ $-0.should equal(str)
+ end
+
+ it "can be assigned nil" do
+ $-0 = nil
+ $-0.should be_nil
+ end
+
+ it "returns the value assigned" do
+ ($-0 = "xyz").should == "xyz"
+ end
+
+ it "changes $/" do
+ $-0 = "xyz"
+ $/.should equal($-0)
+ end
+
+ it "does not call #to_str to convert the object to a String" do
+ obj = mock("$-0 value")
+ obj.should_not_receive(:to_str)
+
+ -> { $-0 = obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if assigned an Integer" do
+ -> { $-0 = 1 }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if assigned a boolean" do
+ -> { $-0 = true }.should raise_error(TypeError)
+ end
+end
+
+describe "Predefined global $\\" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ @dollar_backslash = $\
+ end
+
+ after :each do
+ $\ = @dollar_backslash
+ $VERBOSE = @verbose
+ end
+
+ it "can be assigned a String" do
+ str = "abc"
+ $\ = str
+ $\.should equal(str)
+ end
+
+ it "can be assigned nil" do
+ $\ = nil
+ $\.should be_nil
+ end
+
+ it "returns the value assigned" do
+ ($\ = "xyz").should == "xyz"
+ end
+
+ it "does not call #to_str to convert the object to a String" do
+ obj = mock("$\\ value")
+ obj.should_not_receive(:to_str)
+
+ -> { $\ = obj }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if assigned not String" do
+ -> { $\ = 1 }.should raise_error(TypeError)
+ -> { $\ = true }.should raise_error(TypeError)
+ end
+end
+
+describe "Predefined global $," do
+ after :each do
+ $, = nil
+ end
+
+ it "defaults to nil" do
+ $,.should be_nil
+ end
+
+ it "raises TypeError if assigned a non-String" do
+ -> { $, = Object.new }.should raise_error(TypeError)
+ end
+
+ it "warns if assigned non-nil" do
+ -> { $, = "_" }.should complain(/warning: `\$,' is deprecated/)
+ end
+end
+
+describe "Predefined global $." do
+ it "can be assigned an Integer" do
+ $. = 123
+ $..should == 123
+ end
+
+ it "can be assigned a Float" do
+ $. = 123.5
+ $..should == 123
+ end
+
+ it "should call #to_int to convert the object to an Integer" do
+ obj = mock("good-value")
+ obj.should_receive(:to_int).and_return(321)
+
+ $. = obj
+ $..should == 321
+ end
+
+ it "raises TypeError if object can't be converted to an Integer" do
+ obj = mock("bad-value")
+ obj.should_receive(:to_int).and_return('abc')
+
+ -> { $. = obj }.should raise_error(TypeError)
+ end
+end
+
+describe "Predefined global $;" do
+ after :each do
+ $; = nil
+ end
+
+ it "warns if assigned non-nil" do
+ -> { $; = "_" }.should complain(/warning: `\$;' is deprecated/)
+ end
+end
+
+describe "Predefined global $_" do
+ it "is set to the last line read by e.g. StringIO#gets" do
+ stdin = StringIO.new("foo\nbar\n", "r")
+
+ read = stdin.gets
+ read.should == "foo\n"
+ $_.should == read
+
+ read = stdin.gets
+ read.should == "bar\n"
+ $_.should == read
+
+ read = stdin.gets
+ read.should == nil
+ $_.should == read
+ end
+
+ it "is set at the method-scoped level rather than block-scoped" do
+ obj = Object.new
+ def obj.foo; yield; end
+ def obj.foo2; yield; end
+
+ stdin = StringIO.new("foo\nbar\nbaz\nqux\n", "r")
+ match = stdin.gets
+
+ obj.foo { match = stdin.gets }
+
+ match.should == "bar\n"
+ $_.should == match
+
+ eval 'match = stdin.gets'
+
+ match.should == "baz\n"
+ $_.should == match
+
+ obj.foo2 { match = stdin.gets }
+
+ match.should == "qux\n"
+ $_.should == match
+ end
+
+ it "is Thread-local" do
+ $_ = nil
+ running = false
+
+ thr = Thread.new do
+ $_ = "last line"
+ running = true
+ end
+
+ Thread.pass until running
+ $_.should be_nil
+
+ thr.join
+ end
+
+ it "can be assigned any value" do
+ $_ = nil
+ $_.should == nil
+ $_ = "foo"
+ $_.should == "foo"
+ o = Object.new
+ $_ = o
+ $_.should == o
+ $_ = 1
+ $_.should == 1
+ end
+end
+
+# Execution Environment Variables
+# ---------------------------------------------------------------------------------------------------
+#
+# $0 String The name of the top-level Ruby program being executed. Typically this will
+# be the program’s filename. On some operating systems, assigning to this
+# variable will change the name of the process reported (for example) by the
+# ps(1) command.
+# $* Array An array of strings containing the command-line options from the invoca-
+# tion of the program. Options used by the Ruby interpreter will have been
+# removed. [r/o]
+# $" Array An array containing the filenames of modules loaded by require. [r/o]
+# $$ Integer The process number of the program being executed. [r/o]
+# $? Process::Status The exit status of the last child process to terminate. [r/o, thread]
+# $: Array An array of strings, where each string specifies a directory to be searched for
+# Ruby scripts and binary extensions used by the load and require methods.
+# The initial value is the value of the arguments passed via the -I command-
+# line option, followed by an installation-defined standard library location, fol-
+# lowed by the current directory (“.â€). This variable may be set from within a
+# program to alter the default search path; typically, programs use $: << dir
+# to append dir to the path. [r/o]
+# $-a Object True if the -a option is specified on the command line. [r/o]
+# $-d Object Synonym for $DEBUG.
+# $DEBUG Object Set to true if the -d command-line option is specified.
+# __FILE__ String The name of the current source file. [r/o]
+# $F Array The array that receives the split input line if the -a command-line option is
+# used.
+# $FILENAME String The name of the current input file. Equivalent to $<.filename. [r/o]
+# $-i String If in-place edit mode is enabled (perhaps using the -i command-line
+# option), $-i holds the extension used when creating the backup file. If you
+# set a value into $-i, enables in-place edit mode.
+# $-I Array Synonym for $:. [r/o]
+# $-K String Sets the multibyte coding system for strings and regular expressions. Equiv-
+# alent to the -K command-line option.
+# $-l Object Set to true if the -l option (which enables line-end processing) is present
+# on the command line. [r/o]
+# __LINE__ String The current line number in the source file. [r/o]
+# $LOAD_PATH Array A synonym for $:. [r/o]
+# $-p Object Set to true if the -p option (which puts an implicit while gets . . . end
+# loop around your program) is present on the command line. [r/o]
+# $VERBOSE Object Set to true if the -v, --version, -W, or -w option is specified on the com-
+# mand line. Set to false if no option, or -W1 is given. Set to nil if -W0
+# was specified. Setting this option to true causes the interpreter and some
+# library routines to report additional information. Setting to nil suppresses
+# all warnings (including the output of Kernel.warn).
+# $-v Object Synonym for $VERBOSE.
+# $-w Object Synonym for $VERBOSE.
+describe "Execution variable $:" do
+ it "is initialized to an array of strings" do
+ $:.is_a?(Array).should == true
+ ($:.length > 0).should == true
+ end
+
+ it "does not include the current directory" do
+ $:.should_not include(".")
+ end
+
+ it "is the same object as $LOAD_PATH and $-I" do
+ $:.__id__.should == $LOAD_PATH.__id__
+ $:.__id__.should == $-I.__id__
+ end
+
+ it "can be changed via <<" do
+ $: << "foo"
+ $:.should include("foo")
+ ensure
+ $:.delete("foo")
+ end
+
+ it "is read-only" do
+ -> {
+ $: = []
+ }.should raise_error(NameError)
+
+ -> {
+ $LOAD_PATH = []
+ }.should raise_error(NameError)
+
+ -> {
+ $-I = []
+ }.should raise_error(NameError)
+ end
+
+ it "default $LOAD_PATH entries until sitelibdir included have @gem_prelude_index set" do
+ skip "no sense in ruby itself" if MSpecScript.instance_variable_defined?(:@testing_ruby)
+
+ $:.should.include?(RbConfig::CONFIG['sitelibdir'])
+ idx = $:.index(RbConfig::CONFIG['sitelibdir'])
+
+ $:[idx..-1].all? { |p| p.instance_variable_defined?(:@gem_prelude_index) }.should be_true
+ $:[0...idx].all? { |p| !p.instance_variable_defined?(:@gem_prelude_index) }.should be_true
+ end
+end
+
+describe "Global variable $\"" do
+ it "is an alias for $LOADED_FEATURES" do
+ $".should equal $LOADED_FEATURES
+ end
+
+ it "is read-only" do
+ -> {
+ $" = []
+ }.should raise_error(NameError)
+
+ -> {
+ $LOADED_FEATURES = []
+ }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $<" do
+ it "is read-only" do
+ -> {
+ $< = nil
+ }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $FILENAME" do
+ it "is read-only" do
+ -> {
+ $FILENAME = "-"
+ }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $?" do
+ it "is read-only" do
+ -> {
+ $? = nil
+ }.should raise_error(NameError)
+ end
+
+ it "is thread-local" do
+ system(ruby_cmd('exit 0'))
+ Thread.new { $?.should be_nil }.join
+ end
+end
+
+describe "Global variable $-a" do
+ it "is read-only" do
+ -> { $-a = true }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $-l" do
+ it "is read-only" do
+ -> { $-l = true }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $-p" do
+ it "is read-only" do
+ -> { $-p = true }.should raise_error(NameError)
+ end
+end
+
+describe "Global variable $-d" do
+ before :each do
+ @debug = $DEBUG
+ end
+
+ after :each do
+ $DEBUG = @debug
+ end
+
+ it "is an alias of $DEBUG" do
+ $DEBUG = true
+ $-d.should be_true
+ $-d = false
+ $DEBUG.should be_false
+ end
+end
+
+describe "Global variable $VERBOSE" do
+ before :each do
+ @verbose = $VERBOSE
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it "converts truthy values to true" do
+ [true, 1, 0, [], ""].each do |true_value|
+ $VERBOSE = true_value
+ $VERBOSE.should be_true
+ end
+ end
+
+ it "allows false" do
+ $VERBOSE = false
+ $VERBOSE.should be_false
+ end
+
+ it "allows nil without coercing to false" do
+ $VERBOSE = nil
+ $VERBOSE.should be_nil
+ end
+end
+
+describe :verbose_global_alias, shared: true do
+ before :each do
+ @verbose = $VERBOSE
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it "is an alias of $VERBOSE" do
+ $VERBOSE = true
+ eval(@method).should be_true
+ eval("#{@method} = false")
+ $VERBOSE.should be_false
+ end
+end
+
+describe "Global variable $-v" do
+ it_behaves_like :verbose_global_alias, '$-v'
+end
+
+describe "Global variable $-w" do
+ it_behaves_like :verbose_global_alias, '$-w'
+end
+
+describe "Global variable $0" do
+ before :each do
+ @orig_program_name = $0
+ end
+
+ after :each do
+ $0 = @orig_program_name
+ end
+
+ it "is the path given as the main script and the same as __FILE__" do
+ script = "fixtures/dollar_zero.rb"
+ Dir.chdir(File.dirname(__FILE__)) do
+ ruby_exe(script).should == "#{script}\n#{script}\nOK"
+ end
+ end
+
+ it "returns the program name" do
+ $0 = "rbx"
+ $0.should == "rbx"
+ end
+
+ platform_is :linux, :darwin do
+ it "actually sets the program name" do
+ title = "rubyspec-dollar0-test"
+ $0 = title
+ `ps -ocommand= -p#{$$}`.should include(title)
+ end
+ end
+
+ it "returns the given value when set" do
+ ($0 = "rbx").should == "rbx"
+ end
+
+ it "raises a TypeError when not given an object that can be coerced to a String" do
+ -> { $0 = nil }.should raise_error(TypeError)
+ end
+end
+
+# Standard Objects
+# ---------------------------------------------------------------------------------------------------
+#
+# ARGF Object A synonym for $<.
+# ARGV Array A synonym for $*.
+# ENV Object A hash-like object containing the program’s environment variables. An
+# instance of class Object, ENV implements the full set of Hash methods. Used
+# to query and set the value of an environment variable, as in ENV["PATH"]
+# and ENV["term"]="ansi".
+# false FalseClass Singleton instance of class FalseClass. [r/o]
+# nil NilClass The singleton instance of class NilClass. The value of uninitialized
+# instance and global variables. [r/o]
+# self Object The receiver (object) of the current method. [r/o]
+# true TrueClass Singleton instance of class TrueClass. [r/o]
+
+describe "The predefined standard objects" do
+ it "includes ARGF" do
+ Object.const_defined?(:ARGF).should == true
+ end
+
+ it "includes ARGV" do
+ Object.const_defined?(:ARGV).should == true
+ end
+
+ it "includes a hash-like object ENV" do
+ Object.const_defined?(:ENV).should == true
+ ENV.respond_to?(:[]).should == true
+ end
+end
+
+describe "The predefined standard object nil" do
+ it "is an instance of NilClass" do
+ nil.should be_kind_of(NilClass)
+ end
+
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("nil = true") }.should raise_error(SyntaxError)
+ end
+end
+
+describe "The predefined standard object true" do
+ it "is an instance of TrueClass" do
+ true.should be_kind_of(TrueClass)
+ end
+
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("true = false") }.should raise_error(SyntaxError)
+ end
+end
+
+describe "The predefined standard object false" do
+ it "is an instance of FalseClass" do
+ false.should be_kind_of(FalseClass)
+ end
+
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("false = nil") }.should raise_error(SyntaxError)
+ end
+end
+
+describe "The self pseudo-variable" do
+ it "raises a SyntaxError if assigned to" do
+ -> { eval("self = 1") }.should raise_error(SyntaxError)
+ end
+end
+
+# Global Constants
+# ---------------------------------------------------------------------------------------------------
+#
+# The following constants are defined by the Ruby interpreter.
+#
+# DATA IO If the main program file contains the directive __END__, then
+# the constant DATA will be initialized so that reading from it will
+# return lines following __END__ from the source file.
+# FALSE FalseClass Synonym for false (deprecated, removed in Ruby 3).
+# NIL NilClass Synonym for nil (deprecated, removed in Ruby 3).
+# RUBY_PLATFORM String The identifier of the platform running this program. This string
+# is in the same form as the platform identifier used by the GNU
+# configure utility (which is not a coincidence).
+# RUBY_RELEASE_DATE String The date of this release.
+# RUBY_VERSION String The version number of the interpreter.
+# STDERR IO The actual standard error stream for the program. The initial
+# value of $stderr.
+# STDIN IO The actual standard input stream for the program. The initial
+# value of $stdin.
+# STDOUT IO The actual standard output stream for the program. The initial
+# value of $stdout.
+# SCRIPT_LINES__ Hash If a constant SCRIPT_LINES__ is defined and references a Hash,
+# Ruby will store an entry containing the contents of each file it
+# parses, with the file’s name as the key and an array of strings as
+# the value.
+# TOPLEVEL_BINDING Binding A Binding object representing the binding at Ruby’s top level—
+# the level where programs are initially executed.
+# TRUE TrueClass Synonym for true (deprecated, removed in Ruby 3).
+
+describe "The predefined global constants" do
+ describe "TRUE" do
+ ruby_version_is "3.0" do
+ it "is no longer defined" do
+ Object.const_defined?(:TRUE).should == false
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "includes TRUE" do
+ Object.const_defined?(:TRUE).should == true
+ -> { TRUE }.should complain(/constant ::TRUE is deprecated/)
+ end
+ end
+ end
+
+ describe "FALSE" do
+ ruby_version_is "3.0" do
+ it "is no longer defined" do
+ Object.const_defined?(:FALSE).should == false
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "includes FALSE" do
+ Object.const_defined?(:FALSE).should == true
+ -> { FALSE }.should complain(/constant ::FALSE is deprecated/)
+ end
+ end
+ end
+
+ describe "NIL" do
+ ruby_version_is "3.0" do
+ it "is no longer defined" do
+ Object.const_defined?(:NIL).should == false
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "includes NIL" do
+ Object.const_defined?(:NIL).should == true
+ -> { NIL }.should complain(/constant ::NIL is deprecated/)
+ end
+ end
+ end
+
+ it "includes STDIN" do
+ Object.const_defined?(:STDIN).should == true
+ end
+
+ it "includes STDOUT" do
+ Object.const_defined?(:STDOUT).should == true
+ end
+
+ it "includes STDERR" do
+ Object.const_defined?(:STDERR).should == true
+ end
+
+ it "includes RUBY_VERSION" do
+ Object.const_defined?(:RUBY_VERSION).should == true
+ end
+
+ it "includes RUBY_RELEASE_DATE" do
+ Object.const_defined?(:RUBY_RELEASE_DATE).should == true
+ end
+
+ it "includes RUBY_PLATFORM" do
+ Object.const_defined?(:RUBY_PLATFORM).should == true
+ end
+
+ it "includes TOPLEVEL_BINDING" do
+ Object.const_defined?(:TOPLEVEL_BINDING).should == true
+ end
+end
+
+describe "The predefined global constant" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ describe "STDIN" do
+ platform_is_not :windows do
+ it "has the same external encoding as Encoding.default_external" do
+ STDIN.external_encoding.should equal(Encoding.default_external)
+ end
+
+ it "has the same external encoding as Encoding.default_external when that encoding is changed" do
+ Encoding.default_external = Encoding::ISO_8859_16
+ STDIN.external_encoding.should equal(Encoding::ISO_8859_16)
+ end
+
+ it "has nil for the internal encoding" do
+ STDIN.internal_encoding.should be_nil
+ end
+
+ it "has nil for the internal encoding despite Encoding.default_internal being changed" do
+ Encoding.default_internal = Encoding::IBM437
+ STDIN.internal_encoding.should be_nil
+ end
+ end
+
+ it "has the encodings set by #set_encoding" do
+ code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
+ end
+
+ it "retains the encoding set by #set_encoding when Encoding.default_external is changed" do
+ code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "Encoding.default_external = Encoding::ISO_8859_16;" \
+ "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
+ end
+ end
+
+ describe "STDOUT" do
+ it "has nil for the external encoding" do
+ STDOUT.external_encoding.should be_nil
+ end
+
+ it "has nil for the external encoding despite Encoding.default_external being changed" do
+ Encoding.default_external = Encoding::ISO_8859_1
+ STDOUT.external_encoding.should be_nil
+ end
+
+ it "has the encodings set by #set_encoding" do
+ code = "STDOUT.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "p [STDOUT.external_encoding.name, STDOUT.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
+ end
+
+ it "has nil for the internal encoding" do
+ STDOUT.internal_encoding.should be_nil
+ end
+
+ it "has nil for the internal encoding despite Encoding.default_internal being changed" do
+ Encoding.default_internal = Encoding::IBM437
+ STDOUT.internal_encoding.should be_nil
+ end
+ end
+
+ describe "STDERR" do
+ it "has nil for the external encoding" do
+ STDERR.external_encoding.should be_nil
+ end
+
+ it "has nil for the external encoding despite Encoding.default_external being changed" do
+ Encoding.default_external = Encoding::ISO_8859_1
+ STDERR.external_encoding.should be_nil
+ end
+
+ it "has the encodings set by #set_encoding" do
+ code = "STDERR.set_encoding Encoding::IBM775, Encoding::IBM866; " \
+ "p [STDERR.external_encoding.name, STDERR.internal_encoding.name]"
+ ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]}
+ end
+
+ it "has nil for the internal encoding" do
+ STDERR.internal_encoding.should be_nil
+ end
+
+ it "has nil for the internal encoding despite Encoding.default_internal being changed" do
+ Encoding.default_internal = Encoding::IBM437
+ STDERR.internal_encoding.should be_nil
+ end
+ end
+
+ describe "ARGV" do
+ it "contains Strings encoded in locale Encoding" do
+ code = fixture __FILE__, "argv_encoding.rb"
+ result = ruby_exe(code, args: "a b")
+ encoding = Encoding.default_external
+ result.chomp.should == %{["#{encoding}", "#{encoding}"]}
+ end
+ end
+end
+
+describe "$LOAD_PATH.resolve_feature_path" do
+ it "returns what will be loaded without actual loading, .rb file" do
+ extension, path = $LOAD_PATH.resolve_feature_path('set')
+ extension.should == :rb
+ path.should.end_with?('/set.rb')
+ end
+
+ it "returns what will be loaded without actual loading, .so file" do
+ require 'rbconfig'
+ skip "no dynamically loadable standard extension" if RbConfig::CONFIG["EXTSTATIC"] == "static"
+
+ extension, path = $LOAD_PATH.resolve_feature_path('etc')
+ extension.should == :so
+ path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}")
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "raises LoadError if feature cannot be found" do
+ -> { $LOAD_PATH.resolve_feature_path('noop') }.should raise_error(LoadError)
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "return nil if feature cannot be found" do
+ $LOAD_PATH.resolve_feature_path('noop').should be_nil
+ end
+ end
+end
+
+# Some other pre-defined global variables
+
+describe "Predefined global $=" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ @dollar_assign = $=
+ end
+
+ after :each do
+ $= = @dollar_assign
+ $VERBOSE = @verbose
+ end
+
+ it "warns when accessed" do
+ -> { a = $= }.should complain(/is no longer effective/)
+ end
+
+ it "warns when assigned" do
+ -> { $= = "_" }.should complain(/is no longer effective/)
+ end
+
+ it "returns the value assigned" do
+ ($= = "xyz").should == "xyz"
+ end
+end
diff --git a/spec/ruby/language/private_spec.rb b/spec/ruby/language/private_spec.rb
new file mode 100644
index 0000000000..ddf185e6d2
--- /dev/null
+++ b/spec/ruby/language/private_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/private'
+
+describe "The private keyword" do
+ it "marks following methods as being private" do
+ a = Private::A.new
+ a.methods.should_not include(:bar)
+ -> { a.bar }.should raise_error(NoMethodError)
+
+ b = Private::B.new
+ b.methods.should_not include(:bar)
+ -> { b.bar }.should raise_error(NoMethodError)
+ end
+
+ # def expr.meth() methods are always public
+ it "has no effect on def expr.meth() methods" do
+ Private::B.public_defs_method.should == 0
+ end
+
+ it "is overridden when a new class is opened" do
+ c = Private::B::C.new
+ c.methods.should include(:baz)
+ c.baz
+ Private::B.public_class_method1.should == 1
+ -> { Private::B.private_class_method1 }.should raise_error(NoMethodError)
+ end
+
+ it "is no longer in effect when the class is closed" do
+ b = Private::B.new
+ b.methods.should include(:foo)
+ b.foo
+ end
+
+ it "changes visibility of previously called method" do
+ klass = Class.new do
+ def foo
+ "foo"
+ end
+ end
+ f = klass.new
+ f.foo
+ klass.class_eval do
+ private :foo
+ end
+ -> { f.foo }.should raise_error(NoMethodError)
+ end
+
+ it "changes visibility of previously called methods with same send/call site" do
+ g = ::Private::G.new
+ -> {
+ 2.times do
+ g.foo
+ module ::Private
+ class G
+ private :foo
+ end
+ end
+ end
+ }.should raise_error(NoMethodError)
+ end
+
+ it "changes the visibility of the existing method in the subclass" do
+ ::Private::A.new.foo.should == 'foo'
+ -> { ::Private::H.new.foo }.should raise_error(NoMethodError)
+ ::Private::H.new.send(:foo).should == 'foo'
+ end
+end
diff --git a/spec/ruby/language/proc_spec.rb b/spec/ruby/language/proc_spec.rb
new file mode 100644
index 0000000000..f8a29962b0
--- /dev/null
+++ b/spec/ruby/language/proc_spec.rb
@@ -0,0 +1,247 @@
+require_relative '../spec_helper'
+
+describe "A Proc" do
+ it "captures locals from the surrounding scope" do
+ var = 1
+ lambda { var }.call.should == 1
+ end
+
+ it "does not capture a local when an argument has the same name" do
+ var = 1
+ lambda { |var| var }.call(2).should == 2
+ var.should == 1
+ end
+
+ describe "taking zero arguments" do
+ before :each do
+ @l = lambda { 1 }
+ end
+
+ it "does not raise an exception if no values are passed" do
+ @l.call.should == 1
+ end
+
+ it "raises an ArgumentError if a value is passed" do
+ lambda { @l.call(0) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "taking || arguments" do
+ before :each do
+ @l = lambda { || 1 }
+ end
+
+ it "does not raise an exception when passed no values" do
+ @l.call.should == 1
+ end
+
+ it "raises an ArgumentError if a value is passed" do
+ lambda { @l.call(0) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "taking |a| arguments" do
+ before :each do
+ @l = lambda { |a| a }
+ end
+
+ it "assigns the value passed to the argument" do
+ @l.call(2).should == 2
+ end
+
+ it "does not destructure a single Array value" do
+ @l.call([1, 2]).should == [1, 2]
+ end
+
+ it "does not call #to_ary to convert a single passed object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @l.call(obj).should equal(obj)
+ end
+
+ it "raises an ArgumentError if no value is passed" do
+ lambda { @l.call }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "taking |a, b| arguments" do
+ before :each do
+ @l = lambda { |a, b| [a, b] }
+ end
+
+ it "raises an ArgumentError if passed no values" do
+ lambda { @l.call }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed one value" do
+ lambda { @l.call(0) }.should raise_error(ArgumentError)
+ end
+
+ it "assigns the values passed to the arguments" do
+ @l.call(1, 2).should == [1, 2]
+ end
+
+ it "does not call #to_ary to convert a single passed object to an Array" do
+ obj = mock("proc call to_ary")
+ obj.should_not_receive(:to_ary)
+
+ lambda { @l.call(obj) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "taking |a, *b| arguments" do
+ before :each do
+ @l = lambda { |a, *b| [a, b] }
+ end
+
+ it "raises an ArgumentError if passed no values" do
+ lambda { @l.call }.should raise_error(ArgumentError)
+ end
+
+ it "does not destructure a single Array value yielded" do
+ @l.call([1, 2, 3]).should == [[1, 2, 3], []]
+ end
+
+ it "assigns all passed values after the first to the rest argument" do
+ @l.call(1, 2, 3).should == [1, [2, 3]]
+ end
+
+ it "does not call #to_ary to convert a single passed object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @l.call(obj).should == [obj, []]
+ end
+ end
+
+ describe "taking |*| arguments" do
+ before :each do
+ @l = lambda { |*| 1 }
+ end
+
+ it "does not raise an exception when passed no values" do
+ @l.call.should == 1
+ end
+
+ it "does not raise an exception when passed multiple values" do
+ @l.call(2, 3, 4).should == 1
+ end
+
+ it "does not call #to_ary to convert a single passed object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @l.call(obj).should == 1
+ end
+ end
+
+ describe "taking |*a| arguments" do
+ before :each do
+ @l = lambda { |*a| a }
+ end
+
+ it "assigns [] to the argument when passed no values" do
+ @l.call.should == []
+ end
+
+ it "assigns the argument an Array wrapping one passed value" do
+ @l.call(1).should == [1]
+ end
+
+ it "assigns the argument an Array wrapping all values passed" do
+ @l.call(1, 2, 3).should == [1, 2, 3]
+ end
+
+ it "does not call #to_ary to convert a single passed object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @l.call(obj).should == [obj]
+ end
+ end
+
+ describe "taking |a, | arguments" do
+ before :each do
+ @l = lambda { |a, | a }
+ end
+
+ it "raises an ArgumentError when passed no values" do
+ lambda { @l.call }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed more than one value" do
+ lambda { @l.call(1, 2) }.should raise_error(ArgumentError)
+ end
+
+ it "assigns the argument the value passed" do
+ @l.call(1).should == 1
+ end
+
+ it "does not destructure when passed a single Array" do
+ @l.call([1,2]).should == [1, 2]
+ end
+
+ it "does not call #to_ary to convert a single passed object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_not_receive(:to_ary)
+
+ @l.call(obj).should == obj
+ end
+ end
+
+ describe "taking |(a, b)| arguments" do
+ before :each do
+ @l = lambda { |(a, b)| [a, b] }
+ end
+
+ it "raises an ArgumentError when passed no values" do
+ lambda { @l.call }.should raise_error(ArgumentError)
+ end
+
+ it "destructures a single Array value yielded" do
+ @l.call([1, 2]).should == [1, 2]
+ end
+
+ it "calls #to_ary to convert a single passed object to an Array" do
+ obj = mock("block yield to_ary")
+ obj.should_receive(:to_ary).and_return([1, 2])
+
+ @l.call(obj).should == [1, 2]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("block yield to_ary invalid")
+ obj.should_receive(:to_ary).and_return(1)
+
+ lambda { @l.call(obj) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "taking |*a, **kw| arguments" do
+ before :each do
+ @p = proc { |*a, **kw| [a, kw] }
+ end
+
+ ruby_version_is ""..."3.0" do
+ it 'autosplats keyword arguments and warns' do
+ -> {
+ @p.call([1, {a: 1}]).should == [[1], {a: 1}]
+ }.should complain(/warning: Using the last argument as keyword parameters is deprecated; maybe \*\* should be added to the call/)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it 'does not autosplat keyword arguments' do
+ @p.call([1, {a: 1}]).should == [[[1, {a: 1}]], {}]
+ end
+ end
+ end
+
+ describe "taking |required keyword arguments, **kw| arguments" do
+ it "raises ArgumentError for missing required argument" do
+ p = proc { |a:, **kw| [a, kw] }
+ -> { p.call() }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/language/range_spec.rb b/spec/ruby/language/range_spec.rb
new file mode 100644
index 0000000000..ccc9f55537
--- /dev/null
+++ b/spec/ruby/language/range_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Literal Ranges" do
+ it "creates range object" do
+ (1..10).should == Range.new(1, 10)
+ end
+
+ it "creates range with excluded right boundary" do
+ (1...10).should == Range.new(1, 10, true)
+ end
+
+ it "creates a simple range as an object literal" do
+ ary = []
+ 2.times do
+ ary.push(1..3)
+ end
+ ary[0].should.equal?(ary[1])
+ end
+
+ it "creates endless ranges" do
+ (1..).should == Range.new(1, nil)
+ (1...).should == Range.new(1, nil, true)
+ end
+
+ it "creates beginless ranges" do
+ (..1).should == Range.new(nil, 1)
+ (...1).should == Range.new(nil, 1, true)
+ end
+end
diff --git a/spec/ruby/language/redo_spec.rb b/spec/ruby/language/redo_spec.rb
new file mode 100644
index 0000000000..57532553b3
--- /dev/null
+++ b/spec/ruby/language/redo_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../spec_helper'
+
+describe "The redo statement" do
+ it "restarts block execution if used within block" do
+ a = []
+ -> {
+ a << 1
+ redo if a.size < 2
+ a << 2
+ }.call
+ a.should == [1, 1, 2]
+ end
+
+ it "re-executes the closest loop" do
+ exist = [2,3]
+ processed = []
+ order = []
+ [1,2,3,4].each do |x|
+ order << x
+ begin
+ processed << x
+ if exist.include?(x)
+ raise StandardError, "included"
+ end
+ rescue StandardError
+ exist.delete(x)
+ redo
+ end
+ end
+ processed.should == [1,2,2,3,3,4]
+ exist.should == []
+ order.should == [1,2,2,3,3,4]
+ end
+
+ it "re-executes the last step in enumeration" do
+ list = []
+ [1,2,3].each do |x|
+ list << x
+ break if list.size == 6
+ redo if x == 3
+ end
+ list.should == [1,2,3,3,3,3]
+ end
+
+ it "triggers ensure block when re-executing a block" do
+ list = []
+ [1,2,3].each do |x|
+ list << x
+ begin
+ list << 10*x
+ redo if list.count(1) == 1
+ ensure
+ list << 100*x
+ end
+ end
+ list.should == [1,10,100,1,10,100,2,20,200,3,30,300]
+ end
+
+ describe "in a method" do
+ it "is invalid and raises a SyntaxError" do
+ -> {
+ eval("def m; redo; end")
+ }.should raise_error(SyntaxError)
+ end
+ end
+end
diff --git a/spec/ruby/language/regexp/anchors_spec.rb b/spec/ruby/language/regexp/anchors_spec.rb
new file mode 100644
index 0000000000..0129e255da
--- /dev/null
+++ b/spec/ruby/language/regexp/anchors_spec.rb
@@ -0,0 +1,179 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with anchors" do
+ it "supports ^ (line start anchor)" do
+ # Basic matching
+ /^foo/.match("foo").to_a.should == ["foo"]
+ /^bar/.match("foo\nbar").to_a.should == ["bar"]
+ # Basic non-matching
+ /^foo/.match(" foo").should be_nil
+ /foo^/.match("foo\n\n\n").should be_nil
+
+ # A bit advanced
+ /^^^foo/.match("foo").to_a.should == ["foo"]
+ (/^[^f]/ =~ "foo\n\n").should == "foo\n".size and $~.to_a.should == ["\n"]
+ (/($^)($^)/ =~ "foo\n\n").should == "foo\n".size and $~.to_a.should == ["", "", ""]
+
+ # Different start of line chars
+ /^bar/.match("foo\rbar").should be_nil
+ /^bar/.match("foo\0bar").should be_nil
+
+ # Trivial
+ /^/.match("foo").to_a.should == [""]
+
+ # Grouping
+ /(^foo)/.match("foo").to_a.should == ["foo", "foo"]
+ /(^)/.match("foo").to_a.should == ["", ""]
+ /(foo\n^)(^bar)/.match("foo\nbar").to_a.should == ["foo\nbar", "foo\n", "bar"]
+ end
+
+ it "does not match ^ after trailing \\n" do
+ /^(?!\A)/.match("foo\n").should be_nil # There is no (empty) line after a trailing \n
+ end
+
+ it "supports $ (line end anchor)" do
+ # Basic matching
+ /foo$/.match("foo").to_a.should == ["foo"]
+ /foo$/.match("foo\nbar").to_a.should == ["foo"]
+ # Basic non-matching
+ /foo$/.match("foo ").should be_nil
+ /$foo/.match("\n\n\nfoo").should be_nil
+
+ # A bit advanced
+ /foo$$$/.match("foo").to_a.should == ["foo"]
+ (/[^o]$/ =~ "foo\n\n").should == ("foo\n".size - 1) and $~.to_a.should == ["\n"]
+
+ # Different end of line chars
+ /foo$/.match("foo\r\nbar").should be_nil
+ /foo$/.match("foo\0bar").should be_nil
+
+ # Trivial
+ (/$/ =~ "foo").should == "foo".size and $~.to_a.should == [""]
+
+ # Grouping
+ /(foo$)/.match("foo").to_a.should == ["foo", "foo"]
+ (/($)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", ""]
+ /(foo$)($\nbar)/.match("foo\nbar").to_a.should == ["foo\nbar", "foo", "\nbar"]
+ end
+
+ it "supports \\A (string start anchor)" do
+ # Basic matching
+ /\Afoo/.match("foo").to_a.should == ["foo"]
+ # Basic non-matching
+ /\Abar/.match("foo\nbar").should be_nil
+ /\Afoo/.match(" foo").should be_nil
+
+ # A bit advanced
+ /\A\A\Afoo/.match("foo").to_a.should == ["foo"]
+ /(\A\Z)(\A\Z)/.match("").to_a.should == ["", "", ""]
+
+ # Different start of line chars
+ /\Abar/.match("foo\0bar").should be_nil
+
+ # Grouping
+ /(\Afoo)/.match("foo").to_a.should == ["foo", "foo"]
+ /(\A)/.match("foo").to_a.should == ["", ""]
+ end
+
+ it "supports \\Z (string end anchor, including before trailing \\n)" do
+ # Basic matching
+ /foo\Z/.match("foo").to_a.should == ["foo"]
+ /foo\Z/.match("foo\n").to_a.should == ["foo"]
+ # Basic non-matching
+ /foo\Z/.match("foo\nbar").should be_nil
+ /foo\Z/.match("foo ").should be_nil
+
+ # A bit advanced
+ /foo\Z\Z\Z/.match("foo\n").to_a.should == ["foo"]
+ (/($\Z)($\Z)/ =~ "foo\n").should == "foo".size and $~.to_a.should == ["", "", ""]
+ (/(\z\Z)(\z\Z)/ =~ "foo\n").should == "foo\n".size and $~.to_a.should == ["", "", ""]
+
+ # Different end of line chars
+ /foo\Z/.match("foo\0bar").should be_nil
+ /foo\Z/.match("foo\r\n").should be_nil
+
+ # Grouping
+ /(foo\Z)/.match("foo").to_a.should == ["foo", "foo"]
+ (/(\Z)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", ""]
+ end
+
+ it "supports \\z (string end anchor)" do
+ # Basic matching
+ /foo\z/.match("foo").to_a.should == ["foo"]
+ # Basic non-matching
+ /foo\z/.match("foo\nbar").should be_nil
+ /foo\z/.match("foo\n").should be_nil
+ /foo\z/.match("foo ").should be_nil
+
+ # A bit advanced
+ /foo\z\z\z/.match("foo").to_a.should == ["foo"]
+ (/($\z)($\z)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", "", ""]
+
+ # Different end of line chars
+ /foo\z/.match("foo\0bar").should be_nil
+ /foo\z/.match("foo\r\nbar").should be_nil
+
+ # Grouping
+ /(foo\z)/.match("foo").to_a.should == ["foo", "foo"]
+ (/(\z)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", ""]
+ end
+
+ it "supports \\b (word boundary)" do
+ # Basic matching
+ /foo\b/.match("foo").to_a.should == ["foo"]
+ /foo\b/.match("foo\n").to_a.should == ["foo"]
+ LanguageSpecs.white_spaces.scan(/./).each do |c|
+ /foo\b/.match("foo" + c).to_a.should == ["foo"]
+ end
+ LanguageSpecs.non_alphanum_non_space.scan(/./).each do |c|
+ /foo\b/.match("foo" + c).to_a.should == ["foo"]
+ end
+ /foo\b/.match("foo\0").to_a.should == ["foo"]
+ # Basic non-matching
+ /foo\b/.match("foobar").should be_nil
+ /foo\b/.match("foo123").should be_nil
+ /foo\b/.match("foo_").should be_nil
+ end
+
+ it "supports \\B (non-word-boundary)" do
+ # Basic matching
+ /foo\B/.match("foobar").to_a.should == ["foo"]
+ /foo\B/.match("foo123").to_a.should == ["foo"]
+ /foo\B/.match("foo_").to_a.should == ["foo"]
+ # Basic non-matching
+ /foo\B/.match("foo").should be_nil
+ /foo\B/.match("foo\n").should be_nil
+ LanguageSpecs.white_spaces.scan(/./).each do |c|
+ /foo\B/.match("foo" + c).should be_nil
+ end
+ LanguageSpecs.non_alphanum_non_space.scan(/./).each do |c|
+ /foo\B/.match("foo" + c).should be_nil
+ end
+ /foo\B/.match("foo\0").should be_nil
+ end
+
+ it "supports (?= ) (positive lookahead)" do
+ /foo.(?=bar)/.match("foo1 foo2bar").to_a.should == ["foo2"]
+ end
+
+ it "supports (?! ) (negative lookahead)" do
+ /foo.(?!bar)/.match("foo1bar foo2").to_a.should == ["foo2"]
+ end
+
+ it "supports (?!<) (negative lookbehind)" do
+ /(?<!foo)bar./.match("foobar1 bar2").to_a.should == ["bar2"]
+ end
+
+ it "supports (?<=) (positive lookbehind)" do
+ /(?<=foo)bar./.match("bar1 foobar2").to_a.should == ["bar2"]
+ end
+
+ it "supports (?<=\\b) (positive lookbehind with word boundary)" do
+ /(?<=\bfoo)bar./.match("1foobar1 foobar2").to_a.should == ["bar2"]
+ end
+
+ it "supports (?!<\\b) (negative lookbehind with word boundary)" do
+ /(?<!\bfoo)bar./.match("foobar1 1foobar2").to_a.should == ["bar2"]
+ end
+end
diff --git a/spec/ruby/language/regexp/back-references_spec.rb b/spec/ruby/language/regexp/back-references_spec.rb
new file mode 100644
index 0000000000..26750c20c5
--- /dev/null
+++ b/spec/ruby/language/regexp/back-references_spec.rb
@@ -0,0 +1,140 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with back-references" do
+ it "saves match data in the $~ pseudo-global variable" do
+ "hello" =~ /l+/
+ $~.to_a.should == ["ll"]
+ end
+
+ it "saves captures in numbered $[1-N] variables" do
+ "1234567890" =~ /(1)(2)(3)(4)(5)(6)(7)(8)(9)(0)/
+ $~.to_a.should == ["1234567890", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
+ $1.should == "1"
+ $2.should == "2"
+ $3.should == "3"
+ $4.should == "4"
+ $5.should == "5"
+ $6.should == "6"
+ $7.should == "7"
+ $8.should == "8"
+ $9.should == "9"
+ $10.should == "0"
+ end
+
+ it "will not clobber capture variables across threads" do
+ cap1, cap2, cap3 = nil
+ "foo" =~ /(o+)/
+ cap1 = [$~.to_a, $1]
+ Thread.new do
+ cap2 = [$~.to_a, $1]
+ "bar" =~ /(a)/
+ cap3 = [$~.to_a, $1]
+ end.join
+ cap4 = [$~.to_a, $1]
+ cap1.should == [["oo", "oo"], "oo"]
+ cap2.should == [[], nil]
+ cap3.should == [["a", "a"], "a"]
+ cap4.should == [["oo", "oo"], "oo"]
+ end
+
+ it "supports \<n> (backreference to previous group match)" do
+ /(foo.)\1/.match("foo1foo1").to_a.should == ["foo1foo1", "foo1"]
+ /(foo.)\1/.match("foo1foo2").should be_nil
+ end
+
+ it "resets nested \<n> backreference before match of outer subexpression" do
+ /(a\1?){2}/.match("aaaa").to_a.should == ["aa", "a"]
+ end
+
+ it "does not reset enclosed capture groups" do
+ /((a)|(b))+/.match("ab").captures.should == [ "b", "a", "b" ]
+ end
+
+ it "can match an optional quote, followed by content, followed by a matching quote, as the whole string" do
+ /^("|)(.*)\1$/.match('x').to_a.should == ["x", "", "x"]
+ end
+
+ it "allows forward references" do
+ /(?:(\2)|(.))+/.match("aa").to_a.should == [ "aa", "a", "a" ]
+ end
+
+ it "disallows forward references >= 10" do
+ (/\10()()()()()()()()()()/ =~ "\x08").should == 0
+ end
+
+ it "fails when trying to match a backreference to an unmatched capture group" do
+ /\1()/.match("").should == nil
+ /(?:(a)|b)\1/.match("b").should == nil
+ end
+
+ it "ignores backreferences > 1000" do
+ /\99999/.match("99999")[0].should == "99999"
+ end
+
+ it "0 is not a valid backreference" do
+ -> { Regexp.new("\\k<0>") }.should raise_error(RegexpError)
+ end
+
+ it "allows numeric conditional backreferences" do
+ /(a)(?(1)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(<1>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?('1')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "allows either <> or '' in named conditional backreferences" do
+ -> { Regexp.new("(?<a>a)(?(a)a|b)") }.should raise_error(RegexpError)
+ /(?<a>a)(?(<a>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a>a)(?('a')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "allows negative numeric backreferences" do
+ /(a)\k<-1>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)\g<-1>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(<-1>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?('-1')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "delimited numeric backreferences can start with 0" do
+ /(a)\k<01>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)\g<01>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(01)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?(<01>)a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ /(a)(?('01')a|b)/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "regular numeric backreferences cannot start with 0" do
+ /(a)\01/.match("aa").should == nil
+ /(a)\01/.match("a\x01").to_a.should == [ "a\x01", "a" ]
+ end
+
+ it "named capture groups invalidate numeric backreferences" do
+ -> { Regexp.new("(?<a>a)\\1") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a>a)\\k<1>") }.should raise_error(RegexpError)
+ -> { Regexp.new("(a)(?<a>a)\\1") }.should raise_error(RegexpError)
+ -> { Regexp.new("(a)(?<a>a)\\k<1>") }.should raise_error(RegexpError)
+ end
+
+ it "treats + or - as the beginning of a level specifier in \\k<> backreferences and (?(...)...|...) conditional backreferences" do
+ -> { Regexp.new("(?<a+>a)\\k<a+>") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+b>a)\\k<a+b>") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+1>a)\\k<a+1>") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a->a)\\k<a->") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-b>a)\\k<a-b>") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-1>a)\\k<a-1>") }.should raise_error(RegexpError)
+
+ -> { Regexp.new("(?<a+>a)(?(<a+>)a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+b>a)(?(<a+b>)a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+1>a)(?(<a+1>)a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a->a)(?(<a->)a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-b>a)(?(<a-b>)a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-1>a)(?(<a-1>)a|b)") }.should raise_error(RegexpError)
+
+ -> { Regexp.new("(?<a+>a)(?('a+')a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+b>a)(?('a+b')a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a+1>a)(?('a+1')a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a->a)(?('a-')a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-b>a)(?('a-b')a|b)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<a-1>a)(?('a-1')a|b)") }.should raise_error(RegexpError)
+ end
+end
diff --git a/spec/ruby/language/regexp/character_classes_spec.rb b/spec/ruby/language/regexp/character_classes_spec.rb
new file mode 100644
index 0000000000..12a51178b2
--- /dev/null
+++ b/spec/ruby/language/regexp/character_classes_spec.rb
@@ -0,0 +1,642 @@
+# coding: utf-8
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexp with character classes" do
+ it "supports \\w (word character)" do
+ /\w/.match("a").to_a.should == ["a"]
+ /\w/.match("1").to_a.should == ["1"]
+ /\w/.match("_").to_a.should == ["_"]
+
+ # Non-matches
+ /\w/.match(LanguageSpecs.white_spaces).should be_nil
+ /\w/.match(LanguageSpecs.non_alphanum_non_space).should be_nil
+ /\w/.match("\0").should be_nil
+ end
+
+ it "supports \\W (non-word character)" do
+ /\W+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces]
+ /\W+/.match(LanguageSpecs.non_alphanum_non_space).to_a.should == [LanguageSpecs.non_alphanum_non_space]
+ /\W/.match("\0").to_a.should == ["\0"]
+
+ # Non-matches
+ /\W/.match("a").should be_nil
+ /\W/.match("1").should be_nil
+ /\W/.match("_").should be_nil
+ end
+
+ it "supports \\s (space character)" do
+ /\s+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces]
+
+ # Non-matches
+ /\s/.match("a").should be_nil
+ /\s/.match("1").should be_nil
+ /\s/.match(LanguageSpecs.non_alphanum_non_space).should be_nil
+ /\s/.match("\0").should be_nil
+ end
+
+ it "supports \\S (non-space character)" do
+ /\S/.match("a").to_a.should == ["a"]
+ /\S/.match("1").to_a.should == ["1"]
+ /\S+/.match(LanguageSpecs.non_alphanum_non_space).to_a.should == [LanguageSpecs.non_alphanum_non_space]
+ /\S/.match("\0").to_a.should == ["\0"]
+
+ # Non-matches
+ /\S/.match(LanguageSpecs.white_spaces).should be_nil
+ end
+
+ it "supports \\d (numeric digit)" do
+ /\d/.match("1").to_a.should == ["1"]
+
+ # Non-matches
+ /\d/.match("a").should be_nil
+ /\d/.match(LanguageSpecs.white_spaces).should be_nil
+ /\d/.match(LanguageSpecs.non_alphanum_non_space).should be_nil
+ /\d/.match("\0").should be_nil
+ end
+
+ it "supports \\D (non-digit)" do
+ /\D/.match("a").to_a.should == ["a"]
+ /\D+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces]
+ /\D+/.match(LanguageSpecs.non_alphanum_non_space).to_a.should == [LanguageSpecs.non_alphanum_non_space]
+ /\D/.match("\0").to_a.should == ["\0"]
+
+ # Non-matches
+ /\D/.match("1").should be_nil
+ end
+
+ it "supports [] (character class)" do
+ /[a-z]+/.match("fooBAR").to_a.should == ["foo"]
+ /[\b]/.match("\b").to_a.should == ["\b"] # \b inside character class is backspace
+ end
+
+ it "supports [[:alpha:][:digit:][:etc:]] (predefined character classes)" do
+ /[[:alnum:]]+/.match("a1").to_a.should == ["a1"]
+ /[[:alpha:]]+/.match("Aa1").to_a.should == ["Aa"]
+ /[[:blank:]]+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.blanks]
+ # /[[:cntrl:]]/.match("").to_a.should == [""] # TODO: what should this match?
+ /[[:digit:]]/.match("1").to_a.should == ["1"]
+ # /[[:graph:]]/.match("").to_a.should == [""] # TODO: what should this match?
+ /[[:lower:]]+/.match("Aa1").to_a.should == ["a"]
+ /[[:print:]]+/.match(LanguageSpecs.white_spaces).to_a.should == [" "] # include all of multibyte encoded characters
+ /[[:punct:]]+/.match(LanguageSpecs.punctuations).to_a.should == [LanguageSpecs.punctuations]
+ /[[:space:]]+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces]
+ /[[:upper:]]+/.match("123ABCabc").to_a.should == ["ABC"]
+ /[[:xdigit:]]+/.match("xyz0123456789ABCDEFabcdefXYZ").to_a.should == ["0123456789ABCDEFabcdef"]
+
+ # Parsing
+ /[[:lower:][:digit:]A-C]+/.match("a1ABCDEF").to_a.should == ["a1ABC"] # can be composed with other constructs in the character class
+ /[^[:lower:]A-C]+/.match("abcABCDEF123def").to_a.should == ["DEF123"] # negated character class
+ /[:alnum:]+/.match("a:l:n:u:m").to_a.should == ["a:l:n:u:m"] # should behave like regular character class composed of the individual letters
+ /[\[:alnum:]+/.match("[:a:l:n:u:m").to_a.should == ["[:a:l:n:u:m"] # should behave like regular character class composed of the individual letters
+ -> { eval('/[[:alpha:]-[:digit:]]/') }.should raise_error(SyntaxError) # can't use character class as a start value of range
+ end
+
+ it "matches ASCII characters with [[:ascii:]]" do
+ "\x00".match(/[[:ascii:]]/).to_a.should == ["\x00"]
+ "\x7F".match(/[[:ascii:]]/).to_a.should == ["\x7F"]
+ end
+
+ not_supported_on :opal do
+ it "doesn't match non-ASCII characters with [[:ascii:]]" do
+ /[[:ascii:]]/.match("\u{80}").should be_nil
+ /[[:ascii:]]/.match("\u{9898}").should be_nil
+ end
+ end
+
+ it "matches Unicode letter characters with [[:alnum:]]" do
+ "à".match(/[[:alnum:]]/).to_a.should == ["à"]
+ end
+
+ it "matches Unicode digits with [[:alnum:]]" do
+ "\u{0660}".match(/[[:alnum:]]/).to_a.should == ["\u{0660}"]
+ end
+
+ it "doesn't matches Unicode marks with [[:alnum:]]" do
+ "\u{36F}".match(/[[:alnum:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:alnum:]]" do
+ "\u{16}".match(/[[:alnum:]]/).to_a.should == []
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:alnum:]]" do
+ "\u{3F}".match(/[[:alnum:]]/).to_a.should == []
+ end
+
+ it "matches Unicode letter characters with [[:alpha:]]" do
+ "à".match(/[[:alpha:]]/).to_a.should == ["à"]
+ end
+
+ it "doesn't match Unicode digits with [[:alpha:]]" do
+ "\u{0660}".match(/[[:alpha:]]/).to_a.should == []
+ end
+
+ it "doesn't matches Unicode marks with [[:alpha:]]" do
+ "\u{36F}".match(/[[:alpha:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:alpha:]]" do
+ "\u{16}".match(/[[:alpha:]]/).to_a.should == []
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:alpha:]]" do
+ "\u{3F}".match(/[[:alpha:]]/).to_a.should == []
+ end
+
+ it "matches Unicode space characters with [[:blank:]]" do
+ "\u{1680}".match(/[[:blank:]]/).to_a.should == ["\u{1680}"]
+ end
+
+ it "doesn't match Unicode control characters with [[:blank:]]" do
+ "\u{16}".match(/[[:blank:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:blank:]]" do
+ "\u{3F}".match(/[[:blank:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode letter characters with [[:blank:]]" do
+ "à".match(/[[:blank:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:blank:]]" do
+ "\u{0660}".match(/[[:blank:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:blank:]]" do
+ "\u{36F}".match(/[[:blank:]]/).should be_nil
+ end
+
+ it "doesn't Unicode letter characters with [[:cntrl:]]" do
+ "à".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:cntrl:]]" do
+ "\u{0660}".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:cntrl:]]" do
+ "\u{36F}".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:cntrl:]]" do
+ "\u{3F}".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "matches Unicode control characters with [[:cntrl:]]" do
+ "\u{16}".match(/[[:cntrl:]]/).to_a.should == ["\u{16}"]
+ end
+
+ it "doesn't match Unicode format characters with [[:cntrl:]]" do
+ "\u{2060}".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:cntrl:]]" do
+ "\u{E001}".match(/[[:cntrl:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode letter characters with [[:digit:]]" do
+ "à".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "matches Unicode digits with [[:digit:]]" do
+ "\u{0660}".match(/[[:digit:]]/).to_a.should == ["\u{0660}"]
+ "\u{FF12}".match(/[[:digit:]]/).to_a.should == ["\u{FF12}"]
+ end
+
+ it "doesn't match Unicode marks with [[:digit:]]" do
+ "\u{36F}".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:digit:]]" do
+ "\u{3F}".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:digit:]]" do
+ "\u{16}".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode format characters with [[:digit:]]" do
+ "\u{2060}".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:digit:]]" do
+ "\u{E001}".match(/[[:digit:]]/).should be_nil
+ end
+
+ it "matches Unicode letter characters with [[:graph:]]" do
+ "à".match(/[[:graph:]]/).to_a.should == ["à"]
+ end
+
+ it "matches Unicode digits with [[:graph:]]" do
+ "\u{0660}".match(/[[:graph:]]/).to_a.should == ["\u{0660}"]
+ "\u{FF12}".match(/[[:graph:]]/).to_a.should == ["\u{FF12}"]
+ end
+
+ it "matches Unicode marks with [[:graph:]]" do
+ "\u{36F}".match(/[[:graph:]]/).to_a.should ==["\u{36F}"]
+ end
+
+ it "matches Unicode punctuation characters with [[:graph:]]" do
+ "\u{3F}".match(/[[:graph:]]/).to_a.should == ["\u{3F}"]
+ end
+
+ it "doesn't match Unicode control characters with [[:graph:]]" do
+ "\u{16}".match(/[[:graph:]]/).should be_nil
+ end
+
+ it "match Unicode format characters with [[:graph:]]" do
+ "\u{2060}".match(/[[:graph:]]/).to_a.should == ["\u2060"]
+ end
+
+ it "match Unicode private-use characters with [[:graph:]]" do
+ "\u{E001}".match(/[[:graph:]]/).to_a.should == ["\u{E001}"]
+ end
+
+ it "matches Unicode lowercase letter characters with [[:lower:]]" do
+ "\u{FF41}".match(/[[:lower:]]/).to_a.should == ["\u{FF41}"]
+ "\u{1D484}".match(/[[:lower:]]/).to_a.should == ["\u{1D484}"]
+ "\u{E8}".match(/[[:lower:]]/).to_a.should == ["\u{E8}"]
+ end
+
+ it "doesn't match Unicode uppercase letter characters with [[:lower:]]" do
+ "\u{100}".match(/[[:lower:]]/).should be_nil
+ "\u{130}".match(/[[:lower:]]/).should be_nil
+ "\u{405}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode title-case characters with [[:lower:]]" do
+ "\u{1F88}".match(/[[:lower:]]/).should be_nil
+ "\u{1FAD}".match(/[[:lower:]]/).should be_nil
+ "\u{01C5}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:lower:]]" do
+ "\u{0660}".match(/[[:lower:]]/).should be_nil
+ "\u{FF12}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:lower:]]" do
+ "\u{36F}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:lower:]]" do
+ "\u{3F}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:lower:]]" do
+ "\u{16}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode format characters with [[:lower:]]" do
+ "\u{2060}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:lower:]]" do
+ "\u{E001}".match(/[[:lower:]]/).should be_nil
+ end
+
+ it "matches Unicode lowercase letter characters with [[:print:]]" do
+ "\u{FF41}".match(/[[:print:]]/).to_a.should == ["\u{FF41}"]
+ "\u{1D484}".match(/[[:print:]]/).to_a.should == ["\u{1D484}"]
+ "\u{E8}".match(/[[:print:]]/).to_a.should == ["\u{E8}"]
+ end
+
+ it "matches Unicode uppercase letter characters with [[:print:]]" do
+ "\u{100}".match(/[[:print:]]/).to_a.should == ["\u{100}"]
+ "\u{130}".match(/[[:print:]]/).to_a.should == ["\u{130}"]
+ "\u{405}".match(/[[:print:]]/).to_a.should == ["\u{405}"]
+ end
+
+ it "matches Unicode title-case characters with [[:print:]]" do
+ "\u{1F88}".match(/[[:print:]]/).to_a.should == ["\u{1F88}"]
+ "\u{1FAD}".match(/[[:print:]]/).to_a.should == ["\u{1FAD}"]
+ "\u{01C5}".match(/[[:print:]]/).to_a.should == ["\u{01C5}"]
+ end
+
+ it "matches Unicode digits with [[:print:]]" do
+ "\u{0660}".match(/[[:print:]]/).to_a.should == ["\u{0660}"]
+ "\u{FF12}".match(/[[:print:]]/).to_a.should == ["\u{FF12}"]
+ end
+
+ it "matches Unicode marks with [[:print:]]" do
+ "\u{36F}".match(/[[:print:]]/).to_a.should == ["\u{36F}"]
+ end
+
+ it "matches Unicode punctuation characters with [[:print:]]" do
+ "\u{3F}".match(/[[:print:]]/).to_a.should == ["\u{3F}"]
+ end
+
+ it "doesn't match Unicode control characters with [[:print:]]" do
+ "\u{16}".match(/[[:print:]]/).should be_nil
+ end
+
+ it "match Unicode format characters with [[:print:]]" do
+ "\u{2060}".match(/[[:print:]]/).to_a.should == ["\u{2060}"]
+ end
+
+ it "match Unicode private-use characters with [[:print:]]" do
+ "\u{E001}".match(/[[:print:]]/).to_a.should == ["\u{E001}"]
+ end
+
+
+ it "doesn't match Unicode lowercase letter characters with [[:punct:]]" do
+ "\u{FF41}".match(/[[:punct:]]/).should be_nil
+ "\u{1D484}".match(/[[:punct:]]/).should be_nil
+ "\u{E8}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode uppercase letter characters with [[:punct:]]" do
+ "\u{100}".match(/[[:punct:]]/).should be_nil
+ "\u{130}".match(/[[:punct:]]/).should be_nil
+ "\u{405}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode title-case characters with [[:punct:]]" do
+ "\u{1F88}".match(/[[:punct:]]/).should be_nil
+ "\u{1FAD}".match(/[[:punct:]]/).should be_nil
+ "\u{01C5}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:punct:]]" do
+ "\u{0660}".match(/[[:punct:]]/).should be_nil
+ "\u{FF12}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:punct:]]" do
+ "\u{36F}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "matches Unicode Pc characters with [[:punct:]]" do
+ "\u{203F}".match(/[[:punct:]]/).to_a.should == ["\u{203F}"]
+ end
+
+ it "matches Unicode Pd characters with [[:punct:]]" do
+ "\u{2E17}".match(/[[:punct:]]/).to_a.should == ["\u{2E17}"]
+ end
+
+ it "matches Unicode Ps characters with [[:punct:]]" do
+ "\u{0F3A}".match(/[[:punct:]]/).to_a.should == ["\u{0F3A}"]
+ end
+
+ it "matches Unicode Pe characters with [[:punct:]]" do
+ "\u{2046}".match(/[[:punct:]]/).to_a.should == ["\u{2046}"]
+ end
+
+ it "matches Unicode Pi characters with [[:punct:]]" do
+ "\u{00AB}".match(/[[:punct:]]/).to_a.should == ["\u{00AB}"]
+ end
+
+ it "matches Unicode Pf characters with [[:punct:]]" do
+ "\u{201D}".match(/[[:punct:]]/).to_a.should == ["\u{201D}"]
+ "\u{00BB}".match(/[[:punct:]]/).to_a.should == ["\u{00BB}"]
+ end
+
+ it "matches Unicode Po characters with [[:punct:]]" do
+ "\u{00BF}".match(/[[:punct:]]/).to_a.should == ["\u{00BF}"]
+ end
+
+ it "doesn't match Unicode format characters with [[:punct:]]" do
+ "\u{2060}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:punct:]]" do
+ "\u{E001}".match(/[[:punct:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode lowercase letter characters with [[:space:]]" do
+ "\u{FF41}".match(/[[:space:]]/).should be_nil
+ "\u{1D484}".match(/[[:space:]]/).should be_nil
+ "\u{E8}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode uppercase letter characters with [[:space:]]" do
+ "\u{100}".match(/[[:space:]]/).should be_nil
+ "\u{130}".match(/[[:space:]]/).should be_nil
+ "\u{405}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode title-case characters with [[:space:]]" do
+ "\u{1F88}".match(/[[:space:]]/).should be_nil
+ "\u{1FAD}".match(/[[:space:]]/).should be_nil
+ "\u{01C5}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:space:]]" do
+ "\u{0660}".match(/[[:space:]]/).should be_nil
+ "\u{FF12}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:space:]]" do
+ "\u{36F}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "matches Unicode Zs characters with [[:space:]]" do
+ "\u{205F}".match(/[[:space:]]/).to_a.should == ["\u{205F}"]
+ end
+
+ it "matches Unicode Zl characters with [[:space:]]" do
+ "\u{2028}".match(/[[:space:]]/).to_a.should == ["\u{2028}"]
+ end
+
+ it "matches Unicode Zp characters with [[:space:]]" do
+ "\u{2029}".match(/[[:space:]]/).to_a.should == ["\u{2029}"]
+ end
+
+ it "doesn't match Unicode format characters with [[:space:]]" do
+ "\u{2060}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:space:]]" do
+ "\u{E001}".match(/[[:space:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode lowercase characters with [[:upper:]]" do
+ "\u{FF41}".match(/[[:upper:]]/).should be_nil
+ "\u{1D484}".match(/[[:upper:]]/).should be_nil
+ "\u{E8}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "matches Unicode uppercase characters with [[:upper:]]" do
+ "\u{100}".match(/[[:upper:]]/).to_a.should == ["\u{100}"]
+ "\u{130}".match(/[[:upper:]]/).to_a.should == ["\u{130}"]
+ "\u{405}".match(/[[:upper:]]/).to_a.should == ["\u{405}"]
+ end
+
+ it "doesn't match Unicode title-case characters with [[:upper:]]" do
+ "\u{1F88}".match(/[[:upper:]]/).should be_nil
+ "\u{1FAD}".match(/[[:upper:]]/).should be_nil
+ "\u{01C5}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode digits with [[:upper:]]" do
+ "\u{0660}".match(/[[:upper:]]/).should be_nil
+ "\u{FF12}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:upper:]]" do
+ "\u{36F}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:upper:]]" do
+ "\u{3F}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:upper:]]" do
+ "\u{16}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode format characters with [[:upper:]]" do
+ "\u{2060}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:upper:]]" do
+ "\u{E001}".match(/[[:upper:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode letter characters [^a-fA-F] with [[:xdigit:]]" do
+ "à".match(/[[:xdigit:]]/).should be_nil
+ "g".match(/[[:xdigit:]]/).should be_nil
+ "X".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "matches Unicode letter characters [a-fA-F] with [[:xdigit:]]" do
+ "a".match(/[[:xdigit:]]/).to_a.should == ["a"]
+ "F".match(/[[:xdigit:]]/).to_a.should == ["F"]
+ end
+
+ it "doesn't match Unicode digits [^0-9] with [[:xdigit:]]" do
+ "\u{0660}".match(/[[:xdigit:]]/).should be_nil
+ "\u{FF12}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode marks with [[:xdigit:]]" do
+ "\u{36F}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode punctuation characters with [[:xdigit:]]" do
+ "\u{3F}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:xdigit:]]" do
+ "\u{16}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode format characters with [[:xdigit:]]" do
+ "\u{2060}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:xdigit:]]" do
+ "\u{E001}".match(/[[:xdigit:]]/).should be_nil
+ end
+
+ it "matches Unicode lowercase characters with [[:word:]]" do
+ "\u{FF41}".match(/[[:word:]]/).to_a.should == ["\u{FF41}"]
+ "\u{1D484}".match(/[[:word:]]/).to_a.should == ["\u{1D484}"]
+ "\u{E8}".match(/[[:word:]]/).to_a.should == ["\u{E8}"]
+ end
+
+ it "matches Unicode uppercase characters with [[:word:]]" do
+ "\u{100}".match(/[[:word:]]/).to_a.should == ["\u{100}"]
+ "\u{130}".match(/[[:word:]]/).to_a.should == ["\u{130}"]
+ "\u{405}".match(/[[:word:]]/).to_a.should == ["\u{405}"]
+ end
+
+ it "matches Unicode title-case characters with [[:word:]]" do
+ "\u{1F88}".match(/[[:word:]]/).to_a.should == ["\u{1F88}"]
+ "\u{1FAD}".match(/[[:word:]]/).to_a.should == ["\u{1FAD}"]
+ "\u{01C5}".match(/[[:word:]]/).to_a.should == ["\u{01C5}"]
+ end
+
+ it "matches Unicode decimal digits with [[:word:]]" do
+ "\u{FF10}".match(/[[:word:]]/).to_a.should == ["\u{FF10}"]
+ "\u{096C}".match(/[[:word:]]/).to_a.should == ["\u{096C}"]
+ end
+
+ it "matches Unicode marks with [[:word:]]" do
+ "\u{36F}".match(/[[:word:]]/).to_a.should == ["\u{36F}"]
+ end
+
+ it "match Unicode Nl characters with [[:word:]]" do
+ "\u{16EE}".match(/[[:word:]]/).to_a.should == ["\u{16EE}"]
+ end
+
+ it "doesn't match Unicode No characters with [[:word:]]" do
+ "\u{17F0}".match(/[[:word:]]/).should be_nil
+ end
+ it "doesn't match Unicode punctuation characters with [[:word:]]" do
+ "\u{3F}".match(/[[:word:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode control characters with [[:word:]]" do
+ "\u{16}".match(/[[:word:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode format characters with [[:word:]]" do
+ "\u{2060}".match(/[[:word:]]/).should be_nil
+ end
+
+ it "doesn't match Unicode private-use characters with [[:word:]]" do
+ "\u{E001}".match(/[[:word:]]/).should be_nil
+ end
+
+ it "matches unicode named character properties" do
+ "a1".match(/\p{Alpha}/).to_a.should == ["a"]
+ end
+
+ it "matches unicode abbreviated character properties" do
+ "a1".match(/\p{L}/).to_a.should == ["a"]
+ end
+
+ it "matches unicode script properties" do
+ "a\u06E9b".match(/\p{Arabic}/).to_a.should == ["\u06E9"]
+ end
+
+ it "matches unicode Han properties" do
+ "æ¾æœ¬è¡Œå¼˜ Ruby".match(/\p{Han}+/u).to_a.should == ["æ¾æœ¬è¡Œå¼˜"]
+ end
+
+ it "matches unicode Hiragana properties" do
+ "Ruby(ルビー)ã€ã¾ã¤ã‚‚ã¨ã‚†ãã²ã‚".match(/\p{Hiragana}+/u).to_a.should == ["ã¾ã¤ã‚‚ã¨ã‚†ãã²ã‚"]
+ end
+
+ it "matches unicode Katakana properties" do
+ "Ruby(ルビー)ã€ã¾ã¤ã‚‚ã¨ã‚†ãã²ã‚".match(/\p{Katakana}+/u).to_a.should == ["ルビ"]
+ end
+
+ it "matches unicode Hangul properties" do
+ "루비(Ruby)".match(/\p{Hangul}+/u).to_a.should == ["루비"]
+ end
+
+ it "supports negated property condition" do
+ "a".match(/\P{L}/).should be_nil
+ "1".match(/\P{N}/).should be_nil
+ end
+
+ ruby_bug "#17340", ''...'3.0' do
+ it "raises a RegexpError for an unterminated unicode property" do
+ -> { Regexp.new('\p{') }.should raise_error(RegexpError)
+ end
+ end
+
+ it "supports \\X (unicode 9.0 with UTR #51 workarounds)" do
+ # simple emoji without any fancy modifier or ZWJ
+ /\X/.match("\u{1F98A}").to_a.should == ["🦊"]
+
+ # skin tone modifier
+ /\X/.match("\u{1F918}\u{1F3FD}").to_a.should == ["🤘ðŸ½"]
+
+ # emoji joined with ZWJ
+ /\X/.match("\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}").to_a.should == ["ðŸ³ï¸â€ðŸŒˆ"]
+ /\X/.match("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}").to_a.should == ["👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦"]
+
+ # without the ZWJ
+ /\X+/.match("\u{1F3F3}\u{FE0F}\u{1F308}").to_a.should == ["ðŸ³ï¸ðŸŒˆ"]
+ /\X+/.match("\u{1F469}\u{1F469}\u{1F467}\u{1F466}").to_a.should == ["👩👩👧👦"]
+
+ # both of the ZWJ combined
+ /\X+/.match("\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}")
+ .to_a.should == ["ðŸ³ï¸â€ðŸŒˆðŸ‘©â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦"]
+ end
+end
diff --git a/spec/ruby/language/regexp/empty_checks_spec.rb b/spec/ruby/language/regexp/empty_checks_spec.rb
new file mode 100644
index 0000000000..391e65b003
--- /dev/null
+++ b/spec/ruby/language/regexp/empty_checks_spec.rb
@@ -0,0 +1,135 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "empty checks in Regexps" do
+
+ it "allow extra empty iterations" do
+ /()?/.match("").to_a.should == ["", ""]
+ /(a*)?/.match("").to_a.should == ["", ""]
+ /(a*)*/.match("").to_a.should == ["", ""]
+ # The bounds are high to avoid DFA-based matchers in implementations
+ # and to check backtracking behavior.
+ /(?:a|()){500,1000}/.match("a" * 500).to_a.should == ["a" * 500, ""]
+
+ # Variations with non-greedy loops.
+ /()??/.match("").to_a.should == ["", nil]
+ /(a*?)?/.match("").to_a.should == ["", ""]
+ /(a*)??/.match("").to_a.should == ["", nil]
+ /(a*?)??/.match("").to_a.should == ["", nil]
+ /(a*?)*/.match("").to_a.should == ["", ""]
+ /(a*)*?/.match("").to_a.should == ["", nil]
+ /(a*?)*?/.match("").to_a.should == ["", nil]
+ end
+
+ it "allow empty iterations in the middle of a loop" do
+ # One empty iteration between a's and b's.
+ /(a|\2b|())*/.match("aaabbb").to_a.should == ["aaabbb", "", ""]
+ /(a|\2b|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", ""]
+
+ # Two empty iterations between a's and b's.
+ /(a|\2b|\3()|())*/.match("aaabbb").to_a.should == ["aaabbb", "", "", ""]
+ /(a|\2b|\3()|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", nil, ""]
+
+ # Check that the empty iteration correctly updates the loop counter.
+ /(a|\2b|()){20,24}/.match("a" * 20 + "b" * 5).to_a.should == ["a" * 20 + "b" * 3, "b", ""]
+
+ # Variations with non-greedy loops.
+ /(a|\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil]
+ /(a|\2b|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", ""]
+ /(a|\2b|\3()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
+ /(a|\2b|\3()|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", nil, ""]
+ /(a|\2b|()){20,24}/.match("a" * 20 + "b" * 5).to_a.should == ["a" * 20 + "b" * 3, "b", ""]
+ end
+
+ it "make the Regexp proceed past the quantified expression on failure" do
+ # If the contents of the ()* quantified group are empty (i.e., they fail
+ # the empty check), the loop will abort. It will not try to backtrack
+ # and try other alternatives (e.g. matching the "a") like in other Regexp
+ # dialects such as ECMAScript.
+ /(?:|a)*/.match("aaa").to_a.should == [""]
+ /(?:()|a)*/.match("aaa").to_a.should == ["", ""]
+ /(|a)*/.match("aaa").to_a.should == ["", ""]
+ /(()|a)*/.match("aaa").to_a.should == ["", "", ""]
+
+ # Same expressions, but with backreferences, to force the use of non-DFA-based
+ # engines.
+ /()\1(?:|a)*/.match("aaa").to_a.should == ["", ""]
+ /()\1(?:()|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()\1(|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()\1(()|a)*/.match("aaa").to_a.should == ["", "", "", ""]
+
+ # Variations with other zero-width contents of the quantified
+ # group: backreferences, capture groups, lookarounds
+ /()(?:\1|a)*/.match("aaa").to_a.should == ["", ""]
+ /()(?:()\1|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()(?:(\1)|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()(?:\1()|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()(\1|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()(()\1|a)*/.match("aaa").to_a.should == ["", "", "", ""]
+ /()((\1)|a)*/.match("aaa").to_a.should == ["", "", "", ""]
+ /()(\1()|a)*/.match("aaa").to_a.should == ["", "", "", ""]
+
+ /(?:(?=a)|a)*/.match("aaa").to_a.should == [""]
+ /(?:(?=a)()|a)*/.match("aaa").to_a.should == ["", ""]
+ /(?:()(?=a)|a)*/.match("aaa").to_a.should == ["", ""]
+ /(?:((?=a))|a)*/.match("aaa").to_a.should == ["", ""]
+ /()\1(?:(?=a)|a)*/.match("aaa").to_a.should == ["", ""]
+ /()\1(?:(?=a)()|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()\1(?:()(?=a)|a)*/.match("aaa").to_a.should == ["", "", ""]
+ /()\1(?:((?=a))|a)*/.match("aaa").to_a.should == ["", "", ""]
+
+ # Variations with non-greedy loops.
+ /(?:|a)*?/.match("aaa").to_a.should == [""]
+ /(?:()|a)*?/.match("aaa").to_a.should == ["", nil]
+ /(|a)*?/.match("aaa").to_a.should == ["", nil]
+ /(()|a)*?/.match("aaa").to_a.should == ["", nil, nil]
+
+ /()\1(?:|a)*?/.match("aaa").to_a.should == ["", ""]
+ /()\1(?:()|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()\1(|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()\1(()|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
+
+ /()(?:\1|a)*?/.match("aaa").to_a.should == ["", ""]
+ /()(?:()\1|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()(?:(\1)|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()(?:\1()|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()(\1|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()(()\1|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
+ /()((\1)|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
+ /()(\1()|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
+
+ /(?:(?=a)|a)*?/.match("aaa").to_a.should == [""]
+ /(?:(?=a)()|a)*?/.match("aaa").to_a.should == ["", nil]
+ /(?:()(?=a)|a)*?/.match("aaa").to_a.should == ["", nil]
+ /(?:((?=a))|a)*?/.match("aaa").to_a.should == ["", nil]
+ /()\1(?:(?=a)|a)*?/.match("aaa").to_a.should == ["", ""]
+ /()\1(?:(?=a)()|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()\1(?:()(?=a)|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ /()\1(?:((?=a))|a)*?/.match("aaa").to_a.should == ["", "", nil]
+ end
+
+ it "shouldn't cause the Regexp parser to get stuck in a loop" do
+ /(|a|\2b|())*/.match("aaabbb").to_a.should == ["", "", nil]
+ /(a||\2b|())*/.match("aaabbb").to_a.should == ["aaa", "", nil]
+ /(a|\2b||())*/.match("aaabbb").to_a.should == ["aaa", "", nil]
+ /(a|\2b|()|)*/.match("aaabbb").to_a.should == ["aaabbb", "", ""]
+ /(()|a|\3b|())*/.match("aaabbb").to_a.should == ["", "", "", nil]
+ /(a|()|\3b|())*/.match("aaabbb").to_a.should == ["aaa", "", "", nil]
+ /(a|\2b|()|())*/.match("aaabbb").to_a.should == ["aaabbb", "", "", nil]
+ /(a|\3b|()|())*/.match("aaabbb").to_a.should == ["aaa", "", "", nil]
+ /(a|()|())*/.match("aaa").to_a.should == ["aaa", "", "", nil]
+ /^(()|a|())*$/.match("aaa").to_a.should == ["aaa", "", "", nil]
+
+ # Variations with non-greedy loops.
+ /(|a|\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil]
+ /(a||\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil]
+ /(a|\2b||())*?/.match("aaabbb").to_a.should == ["", nil, nil]
+ /(a|\2b|()|)*?/.match("aaabbb").to_a.should == ["", nil, nil]
+ /(()|a|\3b|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
+ /(a|()|\3b|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
+ /(a|\2b|()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
+ /(a|\3b|()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
+ /(a|()|())*?/.match("aaa").to_a.should == ["", nil, nil, nil]
+ /^(()|a|())*?$/.match("aaa").to_a.should == ["aaa", "a", "", nil]
+ end
+end
diff --git a/spec/ruby/language/regexp/encoding_spec.rb b/spec/ruby/language/regexp/encoding_spec.rb
new file mode 100644
index 0000000000..febc3fdb37
--- /dev/null
+++ b/spec/ruby/language/regexp/encoding_spec.rb
@@ -0,0 +1,148 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with encoding modifiers" do
+ it "supports /e (EUC encoding)" do
+ match = /./e.match("\303\251".force_encoding(Encoding::EUC_JP))
+ match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)]
+ end
+
+ it "supports /e (EUC encoding) with interpolation" do
+ match = /#{/./}/e.match("\303\251".force_encoding(Encoding::EUC_JP))
+ match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)]
+ end
+
+ it "supports /e (EUC encoding) with interpolation /o" do
+ match = /#{/./}/e.match("\303\251".force_encoding(Encoding::EUC_JP))
+ match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)]
+ end
+
+ it 'uses EUC-JP as /e encoding' do
+ /./e.encoding.should == Encoding::EUC_JP
+ end
+
+ it 'preserves EUC-JP as /e encoding through interpolation' do
+ /#{/./}/e.encoding.should == Encoding::EUC_JP
+ end
+
+ it "supports /n (No encoding)" do
+ /./n.match("\303\251").to_a.should == ["\303"]
+ end
+
+ it "supports /n (No encoding) with interpolation" do
+ /#{/./}/n.match("\303\251").to_a.should == ["\303"]
+ end
+
+ it "supports /n (No encoding) with interpolation /o" do
+ /#{/./}/n.match("\303\251").to_a.should == ["\303"]
+ end
+
+ it "warns when using /n with a match string with non-ASCII characters and an encoding other than ASCII-8BIT" do
+ -> { /./n.match("\303\251".force_encoding('utf-8')) }.should complain(%r{historical binary regexp match /.../n against UTF-8 string})
+ end
+
+ it 'uses US-ASCII as /n encoding if all chars are 7-bit' do
+ /./n.encoding.should == Encoding::US_ASCII
+ end
+
+ it 'uses BINARY when is not initialized' do
+ Regexp.allocate.encoding.should == Encoding::BINARY
+ end
+
+ it 'uses BINARY as /n encoding if not all chars are 7-bit' do
+ /\xFF/n.encoding.should == Encoding::BINARY
+ end
+
+ it 'preserves US-ASCII as /n encoding through interpolation if all chars are 7-bit' do
+ /.#{/./}/n.encoding.should == Encoding::US_ASCII
+ end
+
+ it 'preserves BINARY as /n encoding through interpolation if all chars are 7-bit' do
+ /\xFF#{/./}/n.encoding.should == Encoding::BINARY
+ end
+
+ it "supports /s (Windows_31J encoding)" do
+ match = /./s.match("\303\251".force_encoding(Encoding::Windows_31J))
+ match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)]
+ end
+
+ it "supports /s (Windows_31J encoding) with interpolation" do
+ match = /#{/./}/s.match("\303\251".force_encoding(Encoding::Windows_31J))
+ match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)]
+ end
+
+ it "supports /s (Windows_31J encoding) with interpolation and /o" do
+ match = /#{/./}/s.match("\303\251".force_encoding(Encoding::Windows_31J))
+ match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)]
+ end
+
+ it 'uses Windows-31J as /s encoding' do
+ /./s.encoding.should == Encoding::Windows_31J
+ end
+
+ it 'preserves Windows-31J as /s encoding through interpolation' do
+ /#{/./}/s.encoding.should == Encoding::Windows_31J
+ end
+
+ it "supports /u (UTF8 encoding)" do
+ /./u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"]
+ end
+
+ it "supports /u (UTF8 encoding) with interpolation" do
+ /#{/./}/u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"]
+ end
+
+ it "supports /u (UTF8 encoding) with interpolation and /o" do
+ /#{/./}/u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"]
+ end
+
+ it 'uses UTF-8 as /u encoding' do
+ /./u.encoding.should == Encoding::UTF_8
+ end
+
+ it 'preserves UTF-8 as /u encoding through interpolation' do
+ /#{/./}/u.encoding.should == Encoding::UTF_8
+ end
+
+ it "selects last of multiple encoding specifiers" do
+ /foo/ensuensuens.should == /foo/s
+ end
+
+ it "raises Encoding::CompatibilityError when trying match against different encodings" do
+ -> { /\A[[:space:]]*\z/.match(" ".encode("UTF-16LE")) }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises Encoding::CompatibilityError when trying match? against different encodings" do
+ -> { /\A[[:space:]]*\z/.match?(" ".encode("UTF-16LE")) }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises Encoding::CompatibilityError when trying =~ against different encodings" do
+ -> { /\A[[:space:]]*\z/ =~ " ".encode("UTF-16LE") }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises Encoding::CompatibilityError when the regexp has a fixed, non-ASCII-compatible encoding" do
+ -> { Regexp.new("".force_encoding("UTF-16LE"), Regexp::FIXEDENCODING) =~ " ".encode("UTF-8") }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises Encoding::CompatibilityError when the regexp has a fixed encoding and the match string has non-ASCII characters" do
+ -> { Regexp.new("".force_encoding("US-ASCII"), Regexp::FIXEDENCODING) =~ "\303\251".force_encoding('UTF-8') }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "raises ArgumentError when trying to match a broken String" do
+ s = "\x80".force_encoding('UTF-8')
+ -> { s =~ /./ }.should raise_error(ArgumentError, "invalid byte sequence in UTF-8")
+ end
+
+ it "computes the Regexp Encoding for each interpolated Regexp instance" do
+ make_regexp = -> str { /#{str}/ }
+
+ r = make_regexp.call("été".force_encoding(Encoding::UTF_8))
+ r.should.fixed_encoding?
+ r.encoding.should == Encoding::UTF_8
+
+ r = make_regexp.call("abc".force_encoding(Encoding::UTF_8))
+ r.should_not.fixed_encoding?
+ r.encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/language/regexp/escapes_spec.rb b/spec/ruby/language/regexp/escapes_spec.rb
new file mode 100644
index 0000000000..16a4d8c23b
--- /dev/null
+++ b/spec/ruby/language/regexp/escapes_spec.rb
@@ -0,0 +1,169 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+# TODO: synchronize with spec/core/regexp/new_spec.rb -
+# escaping is also tested there
+describe "Regexps with escape characters" do
+ it "supports escape sequences" do
+ /\t/.match("\t").to_a.should == ["\t"] # horizontal tab
+ /\v/.match("\v").to_a.should == ["\v"] # vertical tab
+ /\n/.match("\n").to_a.should == ["\n"] # newline
+ /\r/.match("\r").to_a.should == ["\r"] # return
+ /\f/.match("\f").to_a.should == ["\f"] # form feed
+ /\a/.match("\a").to_a.should == ["\a"] # bell
+ /\e/.match("\e").to_a.should == ["\e"] # escape
+
+ # \nnn octal char (encoded byte value)
+ end
+
+ it "supports quoting meta-characters via escape sequence" do
+ # parenthesis, etc
+ /\(/.match("(").to_a.should == ["("]
+ /\)/.match(")").to_a.should == [")"]
+ /\[/.match("[").to_a.should == ["["]
+ /\]/.match("]").to_a.should == ["]"]
+ /\{/.match("{").to_a.should == ["{"]
+ /\}/.match("}").to_a.should == ["}"]
+ /\</.match("<").to_a.should == ["<"]
+ /\>/.match(">").to_a.should == [">"]
+ # alternation separator
+ /\|/.match("|").to_a.should == ["|"]
+ # quantifiers
+ /\?/.match("?").to_a.should == ["?"]
+ /\./.match(".").to_a.should == ["."]
+ /\*/.match("*").to_a.should == ["*"]
+ /\+/.match("+").to_a.should == ["+"]
+ # line anchors
+ /\^/.match("^").to_a.should == ["^"]
+ /\$/.match("$").to_a.should == ["$"]
+ end
+
+ it "supports quoting meta-characters via escape sequence when used as a terminator" do
+ # parenthesis, etc
+ # %r[[, %r((, etc literals - are forbidden
+ %r(\().match("(").to_a.should == ["("]
+ %r(\)).match(")").to_a.should == [")"]
+ %r)\().match("(").to_a.should == ["("]
+ %r)\)).match(")").to_a.should == [")"]
+
+ %r[\[].match("[").to_a.should == ["["]
+ %r[\]].match("]").to_a.should == ["]"]
+ %r]\[].match("[").to_a.should == ["["]
+ %r]\]].match("]").to_a.should == ["]"]
+
+ %r{\{}.match("{").to_a.should == ["{"]
+ %r{\}}.match("}").to_a.should == ["}"]
+ %r}\{}.match("{").to_a.should == ["{"]
+ %r}\}}.match("}").to_a.should == ["}"]
+
+ %r<\<>.match("<").to_a.should == ["<"]
+ %r<\>>.match(">").to_a.should == [">"]
+ %r>\<>.match("<").to_a.should == ["<"]
+ %r>\>>.match(">").to_a.should == [">"]
+
+ # alternation separator
+ %r|\||.match("|").to_a.should == ["|"]
+ # quantifiers
+ %r?\??.match("?").to_a.should == ["?"]
+ %r.\...match(".").to_a.should == ["."]
+ %r*\**.match("*").to_a.should == ["*"]
+ %r+\++.match("+").to_a.should == ["+"]
+ # line anchors
+ %r^\^^.match("^").to_a.should == ["^"]
+ %r$\$$.match("$").to_a.should == ["$"]
+ end
+
+ it "supports quoting non-meta-characters via escape sequence when used as a terminator" do
+ non_meta_character_terminators = [
+ '!', '"', '#', '%', '&', "'", ',', '-', ':', ';', '@', '_', '`', '/', '=', '~'
+ ]
+
+ non_meta_character_terminators.each do |c|
+ pattern = eval("%r" + c + "\\" + c + c)
+ pattern.match(c).to_a.should == [c]
+ end
+ end
+
+ it "does not change semantics of escaped non-meta-character when used as a terminator" do
+ all_terminators = [*("!".."/"), *(":".."@"), *("[".."`"), *("{".."~")]
+ meta_character_terminators = ["$", "^", "*", "+", ".", "?", "|", "}", ")", ">", "]"]
+ special_cases = ['(', '{', '[', '<', '\\']
+
+ # it should be equivalent to
+ # [ '!', '"', '#', '%', '&', "'", ',', '-', ':', ';', '@', '_', '`', '/', '=', '~' ]
+ non_meta_character_terminators = all_terminators - meta_character_terminators - special_cases
+
+ non_meta_character_terminators.each do |c|
+ pattern = eval("%r" + c + "\\" + c + c)
+ pattern.should == /#{c}/
+ end
+ end
+
+ it "does not change semantics of escaped meta-character when used as a terminator" do
+ meta_character_terminators = ["$", "^", "*", "+", ".", "?", "|", "}", ")", ">", "]"]
+
+ meta_character_terminators.each do |c|
+ pattern = eval("%r" + c + "\\" + c + c)
+ pattern.should == eval("/\\#{c}/")
+ end
+ end
+
+ it "allows any character to be escaped" do
+ /\y/.match("y").to_a.should == ["y"]
+ end
+
+ it "supports \\x (hex characters)" do
+ /\xA/.match("\nxyz").to_a.should == ["\n"]
+ /\x0A/.match("\n").to_a.should == ["\n"]
+ /\xAA/.match("\nA").should be_nil
+ /\x0AA/.match("\nA").to_a.should == ["\nA"]
+ /\xAG/.match("\nG").to_a.should == ["\nG"]
+ # Non-matches
+ -> { eval('/\xG/') }.should raise_error(SyntaxError)
+
+ # \x{7HHHHHHH} wide hexadecimal char (character code point value)
+ end
+
+ it "supports \\c (control characters)" do
+ #/\c \c@\c`/.match("\00\00\00").to_a.should == ["\00\00\00"]
+ /\c#\cc\cC/.match("\03\03\03").to_a.should == ["\03\03\03"]
+ /\c'\cG\cg/.match("\a\a\a").to_a.should == ["\a\a\a"]
+ /\c(\cH\ch/.match("\b\b\b").to_a.should == ["\b\b\b"]
+ /\c)\cI\ci/.match("\t\t\t").to_a.should == ["\t\t\t"]
+ /\c*\cJ\cj/.match("\n\n\n").to_a.should == ["\n\n\n"]
+ /\c+\cK\ck/.match("\v\v\v").to_a.should == ["\v\v\v"]
+ /\c,\cL\cl/.match("\f\f\f").to_a.should == ["\f\f\f"]
+ /\c-\cM\cm/.match("\r\r\r").to_a.should == ["\r\r\r"]
+
+ /\cJ/.match("\r").should be_nil
+
+ # Parsing precedence
+ /\cJ+/.match("\n\n").to_a.should == ["\n\n"] # Quantifiers apply to entire escape sequence
+ /\\cJ/.match("\\cJ").to_a.should == ["\\cJ"]
+ -> { eval('/[abc\x]/') }.should raise_error(SyntaxError) # \x is treated as a escape sequence even inside a character class
+ # Syntax error
+ -> { eval('/\c/') }.should raise_error(SyntaxError)
+
+ # \cx control char (character code point value)
+ # \C-x control char (character code point value)
+ # \M-x meta (x|0x80) (character code point value)
+ # \M-\C-x meta control char (character code point value)
+ end
+
+ it "handles three digit octal escapes starting with 0" do
+ /[\000-\b]/.match("\x00")[0].should == "\x00"
+ end
+
+ it "handles control escapes with \\C-x syntax" do
+ /\C-*\C-J\C-j/.match("\n\n\n")[0].should == "\n\n\n"
+ end
+
+ it "supports the \\K keep operator" do
+ /a\Kb/.match("ab")[0].should == "b"
+ end
+
+ it "supports the \\R line break escape" do
+ /\R/.match("\n")[0].should == "\n"
+ end
+end
diff --git a/spec/ruby/language/regexp/grouping_spec.rb b/spec/ruby/language/regexp/grouping_spec.rb
new file mode 100644
index 0000000000..2f04a04018
--- /dev/null
+++ b/spec/ruby/language/regexp/grouping_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with grouping" do
+ it "support ()" do
+ /(a)/.match("a").to_a.should == ["a", "a"]
+ end
+
+ it "allows groups to be nested" do
+ md = /(hay(st)a)ck/.match('haystack')
+ md.to_a.should == ['haystack','haysta', 'st']
+ end
+
+ it "raises a SyntaxError when parentheses aren't balanced" do
+ -> { eval "/(hay(st)ack/" }.should raise_error(SyntaxError)
+ end
+
+ it "supports (?: ) (non-capturing group)" do
+ /(?:foo)(bar)/.match("foobar").to_a.should == ["foobar", "bar"]
+ # Parsing precedence
+ /(?:xdigit:)/.match("xdigit:").to_a.should == ["xdigit:"]
+ end
+
+ it "group names cannot start with digits or minus" do
+ -> { Regexp.new("(?<1a>a)") }.should raise_error(RegexpError)
+ -> { Regexp.new("(?<-a>a)") }.should raise_error(RegexpError)
+ end
+
+ it "ignore capture groups in line comments" do
+ /^
+ (a) # there is a capture group on this line
+ b # there is no capture group on this line (not even here)
+ $/x.match("ab").to_a.should == [ "ab", "a" ]
+ end
+
+ it "does not consider # inside a character class as a comment" do
+ # From https://github.com/rubocop/rubocop/blob/39fcf1c568/lib/rubocop/cop/utils/format_string.rb#L18
+ regexp = /
+ % (?<type>%) # line comment
+ | % (?<flags>(?-mix:[ #0+-]|(?-mix:(\d+)\$))*) (?#group comment)
+ (?:
+ (?: (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:<(?<name>\w+)>)?
+ | (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:<(?<name>\w+)>) (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))?
+ | (?-mix:<(?<name>\w+)>) (?<more_flags>(?-mix:[ #0+-]|(?-mix:(\d+)\$))*) (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))?
+ ) (?-mix:(?<type>[bBdiouxXeEfgGaAcps]))
+ | (?-mix:(?<width>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\.(?<precision>(?-mix:\d+|(?-mix:\*(?-mix:(\d+)\$)?))))? (?-mix:\{(?<name>\w+)\})
+ )
+ /x
+ regexp.named_captures.should == {
+ "type" => [1, 13],
+ "flags" => [2],
+ "width" => [3, 6, 11, 14],
+ "precision" => [4, 8, 12, 15],
+ "name" => [5, 7, 9, 16],
+ "more_flags" => [10]
+ }
+ match = regexp.match("%6.3f")
+ match[:width].should == '6'
+ match[:precision].should == '3'
+ match[:type].should == 'f'
+ match.to_a.should == [ "%6.3f", nil, "", "6", "3"] + [nil] * 8 + ["f"] + [nil] * 3
+ end
+end
diff --git a/spec/ruby/language/regexp/interpolation_spec.rb b/spec/ruby/language/regexp/interpolation_spec.rb
new file mode 100644
index 0000000000..6951fd38ca
--- /dev/null
+++ b/spec/ruby/language/regexp/interpolation_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with interpolation" do
+
+ it "allows interpolation of strings" do
+ str = "foo|bar"
+ /#{str}/.should == /foo|bar/
+ end
+
+ it "allows interpolation of literal regexps" do
+ re = /foo|bar/
+ /#{re}/.should == /(?-mix:foo|bar)/
+ end
+
+ it "allows interpolation of any object that responds to to_s" do
+ o = Object.new
+ def o.to_s
+ "object_with_to_s"
+ end
+ /#{o}/.should == /object_with_to_s/
+ end
+
+ it "allows interpolation which mixes modifiers" do
+ re = /foo/i
+ /#{re} bar/m.should == /(?i-mx:foo) bar/m
+ end
+
+ it "allows interpolation to interact with other Regexp constructs" do
+ str = "foo)|(bar"
+ /(#{str})/.should == /(foo)|(bar)/
+
+ str = "a"
+ /[#{str}-z]/.should == /[a-z]/
+ end
+
+ it "gives precedence to escape sequences over substitution" do
+ str = "J"
+ /\c#{str}/.to_s.should include('{str}')
+ end
+
+ it "throws RegexpError for malformed interpolation" do
+ s = ""
+ -> { /(#{s}/ }.should raise_error(RegexpError)
+ s = "("
+ -> { /#{s}/ }.should raise_error(RegexpError)
+ end
+
+ it "allows interpolation in extended mode" do
+ var = "#comment\n foo #comment\n | bar"
+ (/#{var}/x =~ "foo").should == (/foo|bar/ =~ "foo")
+ end
+
+ it "allows escape sequences in interpolated regexps" do
+ escape_seq = %r{"\x80"}n
+ %r{#{escape_seq}}n.should == /(?-mix:"\x80")/n
+ end
+end
diff --git a/spec/ruby/language/regexp/modifiers_spec.rb b/spec/ruby/language/regexp/modifiers_spec.rb
new file mode 100644
index 0000000000..2f5522bc8a
--- /dev/null
+++ b/spec/ruby/language/regexp/modifiers_spec.rb
@@ -0,0 +1,115 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with modifiers" do
+ it "supports /i (case-insensitive)" do
+ /foo/i.match("FOO").to_a.should == ["FOO"]
+ end
+
+ it "supports /m (multiline)" do
+ /foo.bar/m.match("foo\nbar").to_a.should == ["foo\nbar"]
+ /foo.bar/.match("foo\nbar").should be_nil
+ end
+
+ it "supports /x (extended syntax)" do
+ /\d +/x.match("abc123").to_a.should == ["123"] # Quantifiers can be separated from the expression they apply to
+ end
+
+ it "supports /o (once)" do
+ 2.times do |i|
+ /#{i}/o.should == /0/
+ end
+ end
+
+ it "invokes substitutions for /o only once" do
+ ScratchPad.record []
+ o = Object.new
+ def o.to_s
+ ScratchPad << :to_s
+ "class_with_to_s"
+ end
+ eval "2.times { /#{o}/o }"
+ ScratchPad.recorded.should == [:to_s]
+ end
+
+ it "supports modifier combinations" do
+ /foo/imox.match("foo").to_a.should == ["foo"]
+ /foo/imoximox.match("foo").to_a.should == ["foo"]
+
+ -> { eval('/foo/a') }.should raise_error(SyntaxError)
+ end
+
+ it "supports (?~) (absent operator)" do
+ Regexp.new("(?~foo)").match("hello").to_a.should == ["hello"]
+ "foo".scan(Regexp.new("(?~foo)")).should == ["fo","o",""]
+ end
+
+ it "supports (?imx-imx) (inline modifiers)" do
+ /(?i)foo/.match("FOO").to_a.should == ["FOO"]
+ /foo(?i)/.match("FOO").should be_nil
+ # Interaction with /i
+ /(?-i)foo/i.match("FOO").should be_nil
+ /foo(?-i)/i.match("FOO").to_a.should == ["FOO"]
+ # Multiple uses
+ /foo (?i)bar (?-i)baz/.match("foo BAR baz").to_a.should == ["foo BAR baz"]
+ /foo (?i)bar (?-i)baz/.match("foo BAR BAZ").should be_nil
+
+ /(?m)./.match("\n").to_a.should == ["\n"]
+ /.(?m)/.match("\n").should be_nil
+ # Interaction with /m
+ /(?-m)./m.match("\n").should be_nil
+ /.(?-m)/m.match("\n").to_a.should == ["\n"]
+ # Multiple uses
+ /. (?m). (?-m)./.match(". \n .").to_a.should == [". \n ."]
+ /. (?m). (?-m)./.match(". \n \n").should be_nil
+
+ /(?x) foo /.match("foo").to_a.should == ["foo"]
+ / foo (?x)/.match("foo").should be_nil
+ # Interaction with /x
+ /(?-x) foo /x.match("foo").should be_nil
+ / foo (?-x)/x.match("foo").to_a.should == ["foo"]
+ # Multiple uses
+ /( foo )(?x)( bar )(?-x)( baz )/.match(" foo bar baz ").to_a.should == [" foo bar baz ", " foo ", "bar", " baz "]
+ /( foo )(?x)( bar )(?-x)( baz )/.match(" foo barbaz").should be_nil
+
+ # Parsing
+ /(?i-i)foo/.match("FOO").should be_nil
+ /(?ii)foo/.match("FOO").to_a.should == ["FOO"]
+ /(?-)foo/.match("foo").to_a.should == ["foo"]
+ -> { eval('/(?o)/') }.should raise_error(SyntaxError)
+ end
+
+ it "supports (?imx-imx:expr) (scoped inline modifiers)" do
+ /foo (?i:bar) baz/.match("foo BAR baz").to_a.should == ["foo BAR baz"]
+ /foo (?i:bar) baz/.match("foo BAR BAZ").should be_nil
+ /foo (?-i:bar) baz/i.match("foo BAR BAZ").should be_nil
+
+ /. (?m:.) ./.match(". \n .").to_a.should == [". \n ."]
+ /. (?m:.) ./.match(". \n \n").should be_nil
+ /. (?-m:.) ./m.match("\n \n \n").should be_nil
+
+ /( foo )(?x: bar )( baz )/.match(" foo bar baz ").to_a.should == [" foo bar baz ", " foo ", " baz "]
+ /( foo )(?x: bar )( baz )/.match(" foo barbaz").should be_nil
+ /( foo )(?-x: bar )( baz )/x.match("foo bar baz").to_a.should == ["foo bar baz", "foo", "baz"]
+
+ # Parsing
+ /(?i-i:foo)/.match("FOO").should be_nil
+ /(?ii:foo)/.match("FOO").to_a.should == ["FOO"]
+ /(?-:)foo/.match("foo").to_a.should == ["foo"]
+ -> { eval('/(?o:)/') }.should raise_error(SyntaxError)
+ end
+
+ it "supports . with /m" do
+ # Basic matching
+ /./m.match("\n").to_a.should == ["\n"]
+ end
+
+ it "supports ASCII/Unicode modifiers" do
+ eval('/(?a)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a"]
+ eval('/(?d)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a\u3042"]
+ eval('/(?u)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a\u3042"]
+ eval('/(?a)\w+/').match("a\u3042").to_a.should == ["a"]
+ eval('/(?d)\w+/').match("a\u3042").to_a.should == ["a"]
+ eval('/(?u)\w+/').match("a\u3042").to_a.should == ["a\u3042"]
+ end
+end
diff --git a/spec/ruby/language/regexp/repetition_spec.rb b/spec/ruby/language/regexp/repetition_spec.rb
new file mode 100644
index 0000000000..9a191d74e2
--- /dev/null
+++ b/spec/ruby/language/regexp/repetition_spec.rb
@@ -0,0 +1,142 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with repetition" do
+ it "supports * (0 or more of previous subexpression)" do
+ /a*/.match("aaa").to_a.should == ["aaa"]
+ /a*/.match("bbb").to_a.should == [""]
+ /<.*>/.match("<a>foo</a>").to_a.should == ["<a>foo</a>"] # it is greedy
+ end
+
+ it "supports *? (0 or more of previous subexpression - lazy)" do
+ /a*?/.match("aaa").to_a.should == [""]
+ /<.*?>/.match("<a>foo</a>").to_a.should == ["<a>"]
+ end
+
+ it "supports + (1 or more of previous subexpression)" do
+ /a+/.match("aaa").to_a.should == ["aaa"]
+ /a+/.match("bbb").should be_nil
+ /<.+>/.match("<a>foo</a>").to_a.should == ["<a>foo</a>"] # it is greedy
+ end
+
+ it "supports +? (0 or more of previous subexpression - lazy)" do
+ /a+?/.match("aaa").to_a.should == ["a"]
+ /<.+?>/.match("<a>foo</a>").to_a.should == ["<a>"]
+ end
+
+ it "supports {m,n} (m to n of previous subexpression)" do
+ /a{2,4}/.match("aaaaaa").to_a.should == ["aaaa"]
+ /<.{1,}>/.match("<a>foo</a>").to_a.should == ["<a>foo</a>"] # it is greedy
+ end
+
+ it "supports {m,n}? (m to n of previous subexpression) - lazy)" do
+ /<.{1,}?>/.match("<a>foo</a>").to_a.should == ["<a>"]
+ /.([0-9]){3,5}?foo/.match("9876543210foo").to_a.should == ["543210foo", "0"]
+ end
+
+ it "does not treat {m,n}+ as possessive" do
+ -> {
+ @regexp = eval "/foo(A{0,1}+)Abar/"
+ }.should complain(/nested repeat operator/)
+ @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"]
+ end
+
+ it "supports ? (0 or 1 of previous subexpression)" do
+ /a?/.match("aaa").to_a.should == ["a"]
+ /a?/.match("bbb").to_a.should == [""]
+ end
+
+ it "handles incomplete range quantifiers" do
+ /a{}/.match("a{}")[0].should == "a{}"
+ /a{,}/.match("a{,}")[0].should == "a{,}"
+ /a{1/.match("a{1")[0].should == "a{1"
+ /a{1,2/.match("a{1,2")[0].should == "a{1,2"
+ /a{,5}/.match("aaa")[0].should == "aaa"
+ end
+
+ it "lets us use quantifiers on assertions" do
+ /a^?b/.match("ab")[0].should == "ab"
+ /a$?b/.match("ab")[0].should == "ab"
+ /a\A?b/.match("ab")[0].should == "ab"
+ /a\Z?b/.match("ab")[0].should == "ab"
+ /a\z?b/.match("ab")[0].should == "ab"
+ /a\G?b/.match("ab")[0].should == "ab"
+ /a\b?b/.match("ab")[0].should == "ab"
+ /a\B?b/.match("ab")[0].should == "ab"
+ /a(?=c)?b/.match("ab")[0].should == "ab"
+ /a(?!=b)?b/.match("ab")[0].should == "ab"
+ /a(?<=c)?b/.match("ab")[0].should == "ab"
+ /a(?<!a)?b/.match("ab")[0].should == "ab"
+ end
+
+ it "does not delete optional assertions" do
+ /(?=(a))?/.match("a").to_a.should == [ "", "a" ]
+ end
+
+ it "supports nested quantifiers" do
+ suppress_warning do
+ eval <<-RUBY
+ /a***/.match("aaa")[0].should == "aaa"
+
+ # a+?* should not be reduced, it should be equivalent to (a+?)*
+ # NB: the capture group prevents regex engines from reducing the two quantifiers
+ # https://bugs.ruby-lang.org/issues/17341
+ /a+?*/.match("")[0].should == ""
+ /(a+?)*/.match("")[0].should == ""
+
+ /a+?*/.match("a")[0].should == "a"
+ /(a+?)*/.match("a")[0].should == "a"
+
+ ruby_bug '#17341', ''...'3.0' do
+ /a+?*/.match("aa")[0].should == "aa"
+ end
+ /(a+?)*/.match("aa")[0].should == "aa"
+
+ # a+?+ should not be reduced, it should be equivalent to (a+?)+
+ # https://bugs.ruby-lang.org/issues/17341
+ /a+?+/.match("").should == nil
+ /(a+?)+/.match("").should == nil
+
+ /a+?+/.match("a")[0].should == "a"
+ /(a+?)+/.match("a")[0].should == "a"
+
+ ruby_bug '#17341', ''...'3.0' do
+ /a+?+/.match("aa")[0].should == "aa"
+ end
+ /(a+?)+/.match("aa")[0].should == "aa"
+
+ # both a**? and a+*? should be equivalent to (a+)??
+ # this quantifier would rather match nothing, but if that's not possible,
+ # it will greedily take everything
+ /a**?/.match("")[0].should == ""
+ /(a*)*?/.match("")[0].should == ""
+ /a+*?/.match("")[0].should == ""
+ /(a+)*?/.match("")[0].should == ""
+ /(a+)??/.match("")[0].should == ""
+
+ /a**?/.match("aaa")[0].should == ""
+ /(a*)*?/.match("aaa")[0].should == ""
+ /a+*?/.match("aaa")[0].should == ""
+ /(a+)*?/.match("aaa")[0].should == ""
+ /(a+)??/.match("aaa")[0].should == ""
+
+ /b.**?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b(.*)*?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b.+*?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b(.+)*?b/.match("baaabaaab")[0].should == "baaabaaab"
+ /b(.+)??b/.match("baaabaaab")[0].should == "baaabaaab"
+ RUBY
+ end
+ end
+
+ it "treats ? after {n} quantifier as another quantifier, not as non-greedy marker" do
+ /a{2}?/.match("").to_a.should == [""]
+ end
+
+ it "matches zero-width capture groups in optional iterations of loops" do
+ /()?/.match("").to_a.should == ["", ""]
+ /(a*)?/.match("").to_a.should == ["", ""]
+ /(a*)*/.match("").to_a.should == ["", ""]
+ /(?:a|()){500,1000}/.match("a" * 500).to_a.should == ["a" * 500, ""]
+ end
+end
diff --git a/spec/ruby/language/regexp/subexpression_call_spec.rb b/spec/ruby/language/regexp/subexpression_call_spec.rb
new file mode 100644
index 0000000000..16b64cb327
--- /dev/null
+++ b/spec/ruby/language/regexp/subexpression_call_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Regexps with subexpression calls" do
+ it "allows numeric subexpression calls" do
+ /(a)\g<1>/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+
+ it "treats subexpression calls as distinct from simple back-references" do
+ # Back-references only match a string which is equal to the original captured string.
+ /(?<three_digits>[0-9]{3})-\k<three_digits>/.match("123-123")[0].should == "123-123"
+ /(?<three_digits>[0-9]{3})-\k<three_digits>/.match("123-456").should == nil
+ # However, subexpression calls reuse the previous expression and can match a different
+ # string.
+ /(?<three_digits>[0-9]{3})-\g<three_digits>/.match("123-456")[0].should == "123-456"
+ end
+
+ it "allows recursive subexpression calls" do
+ # This pattern matches well-nested parenthesized expression.
+ parens = /^ (?<parens> (?: \( \g<parens> \) | [^()] )* ) $/x
+ parens.match("((a)(b))c(d)")[0].should == "((a)(b))c(d)"
+ parens.match("((a)(b)c(d)").should == nil
+ end
+
+ it "allows access to back-references from the current level" do
+ # Using \\k<first_char-0> accesses the last value captured in first_char
+ # on the current stack level.
+ mirror = /^ (?<mirror> (?: (?<first_char>.) \g<mirror> \k<first_char-0> )? ) $/x
+ mirror.match("abccba")[0].should == "abccba"
+ mirror.match("abccbd").should == nil
+
+ # OTOH, using \\k<first_char> accesses the last value captured in first_char,
+ # regardless of the stack level. Therefore, it can't be used to implement
+ # the mirror language.
+ broken_mirror = /^ (?<mirror> (?: (?<first_char>.) \g<mirror> \k<first_char> )? ) $/x
+ broken_mirror.match("abccba").should == nil
+ # This matches because the 'c' is captured in first_char and that value is
+ # then used for all subsequent back-references, regardless of nesting.
+ broken_mirror.match("abcccc")[0].should == "abcccc"
+ end
+
+ it "allows + and - in group names and referential constructs that don't use levels, i.e. subexpression calls" do
+ /(?<a+>a)\g<a+>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a+b>a)\g<a+b>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a+1>a)\g<a+1>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a->a)\g<a->/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a-b>a)\g<a-b>/.match("aa").to_a.should == [ "aa", "a" ]
+ /(?<a-1>a)\g<a-1>/.match("aa").to_a.should == [ "aa", "a" ]
+ end
+end
diff --git a/spec/ruby/language/regexp_spec.rb b/spec/ruby/language/regexp_spec.rb
new file mode 100644
index 0000000000..d49689349d
--- /dev/null
+++ b/spec/ruby/language/regexp_spec.rb
@@ -0,0 +1,169 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Literal Regexps" do
+ it "matches against $_ (last input) in a conditional if no explicit matchee provided" do
+ -> {
+ eval <<-EOR
+ $_ = nil
+ (true if /foo/).should_not == true
+
+ $_ = "foo"
+ (true if /foo/).should == true
+ EOR
+ }.should complain(/regex literal in condition/)
+ end
+
+ it "yields a Regexp" do
+ /Hello/.should be_kind_of(Regexp)
+ end
+
+ ruby_version_is "3.0" do
+ it "is frozen" do
+ /Hello/.should.frozen?
+ end
+ end
+
+ it "caches the Regexp object" do
+ rs = []
+ 2.times do |i|
+ rs << /foo/
+ end
+ rs[0].should equal(rs[1])
+ end
+
+ it "throws SyntaxError for malformed literals" do
+ -> { eval('/(/') }.should raise_error(SyntaxError)
+ end
+
+ #############################################################################
+ # %r
+ #############################################################################
+
+ it "supports paired delimiters with %r" do
+ LanguageSpecs.paired_delimiters.each do |p0, p1|
+ eval("%r#{p0} foo #{p1}").should == / foo /
+ end
+ end
+
+ it "supports grouping constructs that are also paired delimiters" do
+ LanguageSpecs.paired_delimiters.each do |p0, p1|
+ eval("%r#{p0} () [c]{1} #{p1}").should == / () [c]{1} /
+ end
+ end
+
+ it "allows second part of paired delimiters to be used as non-paired delimiters" do
+ LanguageSpecs.paired_delimiters.each do |p0, p1|
+ eval("%r#{p1} foo #{p1}").should == / foo /
+ end
+ end
+
+ it "disallows first part of paired delimiters to be used as non-paired delimiters" do
+ LanguageSpecs.paired_delimiters.each do |p0, p1|
+ -> { eval("%r#{p0} foo #{p0}") }.should raise_error(SyntaxError)
+ end
+ end
+
+ it "supports non-paired delimiters delimiters with %r" do
+ LanguageSpecs.non_paired_delimiters.each do |c|
+ eval("%r#{c} foo #{c}").should == / foo /
+ end
+ end
+
+ it "disallows alphabets as non-paired delimiter with %r" do
+ -> { eval('%ra foo a') }.should raise_error(SyntaxError)
+ end
+
+ it "disallows spaces after %r and delimiter" do
+ -> { eval('%r !foo!') }.should raise_error(SyntaxError)
+ end
+
+ it "allows unescaped / to be used with %r" do
+ %r[/].to_s.should == /\//.to_s
+ end
+
+
+ #############################################################################
+ # Specs for the matching semantics
+ #############################################################################
+
+ it "supports . (any character except line terminator)" do
+ # Basic matching
+ /./.match("foo").to_a.should == ["f"]
+ # Basic non-matching
+ /./.match("").should be_nil
+ /./.match("\n").should be_nil
+ /./.match("\0").to_a.should == ["\0"]
+ end
+
+ it "supports | (alternations)" do
+ /a|b/.match("a").to_a.should == ["a"]
+ end
+
+ it "supports (?> ) (embedded subexpression)" do
+ /(?>foo)(?>bar)/.match("foobar").to_a.should == ["foobar"]
+ /(?>foo*)obar/.match("foooooooobar").should be_nil # it is possessive
+ end
+
+ it "supports (?# )" do
+ /foo(?#comment)bar/.match("foobar").to_a.should == ["foobar"]
+ /foo(?#)bar/.match("foobar").to_a.should == ["foobar"]
+ end
+
+ it "supports (?<= ) (positive lookbehind)" do
+ /foo.(?<=\d)/.match("fooA foo1").to_a.should == ["foo1"]
+ end
+
+ ruby_bug "#13671", ""..."3.3" do # https://bugs.ruby-lang.org/issues/13671
+ it "handles a lookbehind with ss characters" do
+ r = Regexp.new("(?<!dss)", Regexp::IGNORECASE)
+ r.should =~ "✨"
+ end
+ end
+
+ it "supports (?<! ) (negative lookbehind)" do
+ /foo.(?<!\d)/.match("foo1 fooA").to_a.should == ["fooA"]
+ end
+
+ it "supports \\g (named backreference)" do
+ /(?<foo>foo.)bar\g<foo>/.match("foo1barfoo2").to_a.should == ["foo1barfoo2", "foo2"]
+ end
+
+ it "supports character class composition" do
+ /[a-z&&[^a-c]]+/.match("abcdef").to_a.should == ["def"]
+ /[a-z&&[^d-i&&[^d-f]]]+/.match("abcdefghi").to_a.should == ["abcdef"]
+ end
+
+ it "supports possessive quantifiers" do
+ /fooA++bar/.match("fooAAAbar").to_a.should == ["fooAAAbar"]
+
+ /fooA++Abar/.match("fooAAAbar").should be_nil
+ /fooA?+Abar/.match("fooAAAbar").should be_nil
+ /fooA*+Abar/.match("fooAAAbar").should be_nil
+ end
+
+ it "supports conditional regular expressions with positional capture groups" do
+ pattern = /\A(foo)?(?(1)(T)|(F))\z/
+
+ pattern.should =~ 'fooT'
+ pattern.should =~ 'F'
+ pattern.should_not =~ 'fooF'
+ pattern.should_not =~ 'T'
+ end
+
+ it "supports conditional regular expressions with named capture groups" do
+ pattern = /\A(?<word>foo)?(?(<word>)(T)|(F))\z/
+
+ pattern.should =~ 'fooT'
+ pattern.should =~ 'F'
+ pattern.should_not =~ 'fooF'
+ pattern.should_not =~ 'T'
+ end
+
+ it "support handling unicode 9.0 characters with POSIX bracket expressions" do
+ char_lowercase = "\u{104D8}" # OSAGE SMALL LETTER A
+ /[[:lower:]]/.match(char_lowercase).to_s.should == char_lowercase
+ char_uppercase = "\u{104B0}" # OSAGE CAPITAL LETTER A
+ /[[:upper:]]/.match(char_uppercase).to_s.should == char_uppercase
+ end
+end
diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb
new file mode 100644
index 0000000000..b91b52fa0f
--- /dev/null
+++ b/spec/ruby/language/rescue_spec.rb
@@ -0,0 +1,515 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/rescue'
+
+class SpecificExampleException < StandardError
+end
+class OtherCustomException < StandardError
+end
+class ArbitraryException < StandardError
+end
+
+exception_list = [SpecificExampleException, ArbitraryException]
+
+describe "The rescue keyword" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "can be used to handle a specific exception" do
+ begin
+ raise SpecificExampleException, "Raising this to be handled below"
+ rescue SpecificExampleException
+ :caught
+ end.should == :caught
+ end
+
+ describe 'can capture the raised exception' do
+ before :all do
+ require_relative 'fixtures/rescue_captures'
+ end
+
+ it 'in a local variable' do
+ RescueSpecs::LocalVariableCaptor.should_capture_exception
+ end
+
+ it 'in a class variable' do
+ RescueSpecs::ClassVariableCaptor.should_capture_exception
+ end
+
+ it 'in a constant' do
+ RescueSpecs::ConstantCaptor.should_capture_exception
+ end
+
+ it 'in a global variable' do
+ RescueSpecs::GlobalVariableCaptor.should_capture_exception
+ end
+
+ it 'in an instance variable' do
+ RescueSpecs::InstanceVariableCaptor.should_capture_exception
+ end
+
+ it 'using a safely navigated setter method' do
+ RescueSpecs::SafeNavigationSetterCaptor.should_capture_exception
+ end
+
+ it 'using a setter method' do
+ RescueSpecs::SetterCaptor.should_capture_exception
+ end
+
+ it 'using a square brackets setter' do
+ RescueSpecs::SquareBracketsCaptor.should_capture_exception
+ end
+ end
+
+ it "returns value from `rescue` if an exception was raised" do
+ begin
+ raise
+ rescue
+ :caught
+ end.should == :caught
+ end
+
+ it "returns value from `else` section if no exceptions were raised" do
+ result = begin
+ :begin
+ rescue
+ :rescue
+ else
+ :else
+ ensure
+ :ensure
+ end
+
+ result.should == :else
+ end
+
+ it "can rescue multiple raised exceptions with a single rescue block" do
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].map do |block|
+ begin
+ block.call
+ rescue SpecificExampleException, ArbitraryException
+ :caught
+ end
+ end.should == [:caught, :caught]
+ end
+
+ it "can rescue a splatted list of exceptions" do
+ caught_it = false
+ begin
+ raise SpecificExampleException, "not important"
+ rescue *exception_list
+ caught_it = true
+ end
+ caught_it.should be_true
+ caught = []
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block|
+ begin
+ block.call
+ rescue *exception_list
+ caught << $!
+ end
+ end
+ caught.size.should == 2
+ exception_list.each do |exception_class|
+ caught.map{|e| e.class}.should include(exception_class)
+ end
+ end
+
+ it "converts the splatted list of exceptions using #to_a" do
+ exceptions = mock("to_a")
+ exceptions.should_receive(:to_a).and_return(exception_list)
+ caught_it = false
+ begin
+ raise SpecificExampleException, "not important"
+ rescue *exceptions
+ caught_it = true
+ end
+ caught_it.should be_true
+ end
+
+ it "can combine a splatted list of exceptions with a literal list of exceptions" do
+ caught_it = false
+ begin
+ raise SpecificExampleException, "not important"
+ rescue ArbitraryException, *exception_list
+ caught_it = true
+ end
+ caught_it.should be_true
+ caught = []
+ [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block|
+ begin
+ block.call
+ rescue ArbitraryException, *exception_list
+ caught << $!
+ end
+ end
+ caught.size.should == 2
+ exception_list.each do |exception_class|
+ caught.map{|e| e.class}.should include(exception_class)
+ end
+ end
+
+ it "will only rescue the specified exceptions when doing a splat rescue" do
+ -> do
+ begin
+ raise OtherCustomException, "not rescued!"
+ rescue *exception_list
+ end
+ end.should raise_error(OtherCustomException)
+ end
+
+ it "can rescue different types of exceptions in different ways" do
+ begin
+ raise Exception
+ rescue RuntimeError
+ rescue StandardError
+ rescue Exception
+ ScratchPad << :exception
+ end
+
+ ScratchPad.recorded.should == [:exception]
+ end
+
+ it "rescues exception within the first suitable section in order of declaration" do
+ begin
+ raise StandardError
+ rescue RuntimeError
+ ScratchPad << :runtime_error
+ rescue StandardError
+ ScratchPad << :standard_error
+ rescue Exception
+ ScratchPad << :exception
+ end
+
+ ScratchPad.recorded.should == [:standard_error]
+ end
+
+ it "rescues the exception in the deepest rescue block declared to handle the appropriate exception type" do
+ begin
+ begin
+ RescueSpecs.raise_standard_error
+ rescue ArgumentError
+ end
+ rescue StandardError => e
+ e.backtrace.first.should include ":in `raise_standard_error'"
+ else
+ fail("exception wasn't handled by the correct rescue block")
+ end
+ end
+
+ it "will execute an else block only if no exceptions were raised" do
+ result = begin
+ ScratchPad << :one
+ rescue
+ ScratchPad << :does_not_run
+ else
+ ScratchPad << :two
+ :val
+ end
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :two]
+ end
+
+ it "will execute an else block with ensure only if no exceptions were raised" do
+ result = begin
+ ScratchPad << :one
+ rescue
+ ScratchPad << :does_not_run
+ else
+ ScratchPad << :two
+ :val
+ ensure
+ ScratchPad << :ensure
+ :ensure_val
+ end
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :two, :ensure]
+ end
+
+ it "will execute an else block only if no exceptions were raised in a method" do
+ result = RescueSpecs.begin_else(false)
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :else_ran]
+ end
+
+ it "will execute an else block with ensure only if no exceptions were raised in a method" do
+ result = RescueSpecs.begin_else_ensure(false)
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :else_ran, :ensure_ran]
+ end
+
+ it "will execute an else block but use the outer scope return value in a method" do
+ result = RescueSpecs.begin_else_return(false)
+ result.should == :return_val
+ ScratchPad.recorded.should == [:one, :else_ran, :outside_begin]
+ end
+
+ it "will execute an else block with ensure but use the outer scope return value in a method" do
+ result = RescueSpecs.begin_else_return_ensure(false)
+ result.should == :return_val
+ ScratchPad.recorded.should == [:one, :else_ran, :ensure_ran, :outside_begin]
+ end
+
+ it "raises SyntaxError when else is used without rescue and ensure" do
+ -> {
+ eval <<-ruby
+ begin
+ ScratchPad << :begin
+ else
+ ScratchPad << :else
+ end
+ ruby
+ }.should raise_error(SyntaxError, /else without rescue is useless/)
+ end
+
+ it "will not execute an else block if an exception was raised" do
+ result = begin
+ ScratchPad << :one
+ raise "an error occurred"
+ rescue
+ ScratchPad << :two
+ :val
+ else
+ ScratchPad << :does_not_run
+ end
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :two]
+ end
+
+ it "will not execute an else block with ensure if an exception was raised" do
+ result = begin
+ ScratchPad << :one
+ raise "an error occurred"
+ rescue
+ ScratchPad << :two
+ :val
+ else
+ ScratchPad << :does_not_run
+ ensure
+ ScratchPad << :ensure
+ :ensure_val
+ end
+ result.should == :val
+ ScratchPad.recorded.should == [:one, :two, :ensure]
+ end
+
+ it "will not execute an else block if an exception was raised in a method" do
+ result = RescueSpecs.begin_else(true)
+ result.should == :rescue_val
+ ScratchPad.recorded.should == [:one, :rescue_ran]
+ end
+
+ it "will not execute an else block with ensure if an exception was raised in a method" do
+ result = RescueSpecs.begin_else_ensure(true)
+ result.should == :rescue_val
+ ScratchPad.recorded.should == [:one, :rescue_ran, :ensure_ran]
+ end
+
+ it "will not execute an else block but use the outer scope return value in a method" do
+ result = RescueSpecs.begin_else_return(true)
+ result.should == :return_val
+ ScratchPad.recorded.should == [:one, :rescue_ran, :outside_begin]
+ end
+
+ it "will not execute an else block with ensure but use the outer scope return value in a method" do
+ result = RescueSpecs.begin_else_return_ensure(true)
+ result.should == :return_val
+ ScratchPad.recorded.should == [:one, :rescue_ran, :ensure_ran, :outside_begin]
+ end
+
+ it "will not rescue errors raised in an else block in the rescue block above it" do
+ -> do
+ begin
+ ScratchPad << :one
+ rescue Exception
+ ScratchPad << :does_not_run
+ else
+ ScratchPad << :two
+ raise SpecificExampleException, "an error from else"
+ end
+ end.should raise_error(SpecificExampleException)
+ ScratchPad.recorded.should == [:one, :two]
+ end
+
+ it "parses 'a += b rescue c' as 'a += (b rescue c)'" do
+ a = 'a'
+ c = 'c'
+ a += b rescue c
+ a.should == 'ac'
+ end
+
+ context "without rescue expression" do
+ it "will rescue only StandardError and its subclasses" do
+ begin
+ raise StandardError
+ rescue
+ ScratchPad << :caught
+ end
+
+ ScratchPad.recorded.should == [:caught]
+ end
+
+ it "will not rescue exceptions except StandardError" do
+ [ Exception.new, NoMemoryError.new, ScriptError.new, SecurityError.new,
+ SignalException.new('INT'), SystemExit.new, SystemStackError.new
+ ].each do |exception|
+ -> {
+ begin
+ raise exception
+ rescue
+ ScratchPad << :caught
+ end
+ }.should raise_error(exception.class)
+ end
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "uses === to compare against rescued classes" do
+ rescuer = Class.new
+
+ def rescuer.===(exception)
+ true
+ end
+
+ begin
+ raise Exception
+ rescue rescuer
+ rescued = :success
+ rescue Exception
+ rescued = :failure
+ end
+
+ rescued.should == :success
+ end
+
+ it "only accepts Module or Class in rescue clauses" do
+ rescuer = 42
+ -> {
+ begin
+ raise "error"
+ rescue rescuer
+ end
+ }.should raise_error(TypeError) { |e|
+ e.message.should =~ /class or module required for rescue clause/
+ }
+ end
+
+ it "only accepts Module or Class in splatted rescue clauses" do
+ rescuer = [42]
+ -> {
+ begin
+ raise "error"
+ rescue *rescuer
+ end
+ }.should raise_error(TypeError) { |e|
+ e.message.should =~ /class or module required for rescue clause/
+ }
+ end
+
+ it "evaluates rescue expressions only when needed" do
+ begin
+ ScratchPad << :foo
+ rescue -> { ScratchPad << :bar; StandardError }.call
+ end
+
+ ScratchPad.recorded.should == [:foo]
+ end
+
+ it "suppresses exception from block when raises one from rescue expression" do
+ -> {
+ begin
+ raise "from block"
+ rescue (raise "from rescue expression")
+ end
+ }.should raise_error(RuntimeError, "from rescue expression") { |e|
+ e.cause.message.should == "from block"
+ }
+ end
+
+ it "should splat the handling Error classes" do
+ begin
+ raise "raise"
+ rescue *(RuntimeError) => e
+ :expected
+ end.should == :expected
+ end
+
+ it "allows rescue in class" do
+ eval <<-ruby
+ class RescueInClassExample
+ raise SpecificExampleException
+ rescue SpecificExampleException
+ ScratchPad << :caught
+ end
+ ruby
+
+ ScratchPad.recorded.should == [:caught]
+ end
+
+ it "does not allow rescue in {} block" do
+ -> {
+ eval <<-ruby
+ lambda {
+ raise SpecificExampleException
+ rescue SpecificExampleException
+ :caught
+ }
+ ruby
+ }.should raise_error(SyntaxError)
+ end
+
+ it "allows rescue in 'do end' block" do
+ lambda = eval <<-ruby
+ lambda do
+ raise SpecificExampleException
+ rescue SpecificExampleException
+ ScratchPad << :caught
+ end.call
+ ruby
+
+ ScratchPad.recorded.should == [:caught]
+ end
+
+ it "allows 'rescue' in method arguments" do
+ two = eval '1.+ (raise("Error") rescue 1)'
+ two.should == 2
+ end
+
+ it "requires the 'rescue' in method arguments to be wrapped in parens" do
+ -> { eval '1.+(1 rescue 1)' }.should raise_error(SyntaxError)
+ eval('1.+((1 rescue 1))').should == 2
+ end
+
+ describe "inline form" do
+ it "can be inlined" do
+ a = 1/0 rescue 1
+ a.should == 1
+ end
+
+ it "doesn't except rescue expression" do
+ -> {
+ eval <<-ruby
+ a = 1 rescue RuntimeError 2
+ ruby
+ }.should raise_error(SyntaxError)
+ end
+
+ it "rescues only StandardError and its subclasses" do
+ a = raise(StandardError) rescue 1
+ a.should == 1
+
+ -> {
+ a = raise(Exception) rescue 1
+ }.should raise_error(Exception)
+ end
+
+ it "rescues with multiple assignment" do
+
+ a, b = raise rescue [1, 2]
+
+ a.should == 1
+ b.should == 2
+ end
+ end
+end
diff --git a/spec/ruby/language/retry_spec.rb b/spec/ruby/language/retry_spec.rb
new file mode 100644
index 0000000000..ee5377946f
--- /dev/null
+++ b/spec/ruby/language/retry_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../spec_helper'
+
+describe "The retry statement" do
+ it "re-executes the closest block" do
+ retry_first = true
+ retry_second = true
+ results = []
+ begin
+ results << 1
+ raise
+ rescue
+ results << 2
+ if retry_first
+ results << 3
+ retry_first = false
+ retry
+ end
+ begin
+ results << 4
+ raise
+ rescue
+ results << 5
+ if retry_second
+ results << 6
+ retry_second = false
+ retry
+ end
+ end
+ end
+
+ results.should == [1, 2, 3, 1, 2, 4, 5, 6, 4, 5]
+ end
+
+ it "raises a SyntaxError when used outside of a begin statement" do
+ -> { eval 'retry' }.should raise_error(SyntaxError)
+ end
+end
+
+describe "The retry keyword inside a begin block's rescue block" do
+ it "causes the begin block to be executed again" do
+ counter = 0
+
+ begin
+ counter += 1
+ raise "An exception"
+ rescue
+ retry unless counter == 7
+ end
+
+ counter.should == 7
+ end
+end
diff --git a/spec/ruby/language/return_spec.rb b/spec/ruby/language/return_spec.rb
new file mode 100644
index 0000000000..a62ed1242d
--- /dev/null
+++ b/spec/ruby/language/return_spec.rb
@@ -0,0 +1,490 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/return'
+
+describe "The return keyword" do
+ it "returns any object directly" do
+ def r; return 1; end
+ r().should == 1
+ end
+
+ it "returns an single element array directly" do
+ def r; return [1]; end
+ r().should == [1]
+ end
+
+ it "returns an multi element array directly" do
+ def r; return [1,2]; end
+ r().should == [1,2]
+ end
+
+ it "returns nil by default" do
+ def r; return; end
+ r().should be_nil
+ end
+
+ describe "in a Thread" do
+ it "raises a LocalJumpError if used to exit a thread" do
+ t = Thread.new {
+ begin
+ return
+ rescue LocalJumpError => e
+ e
+ end
+ }
+ t.value.should be_an_instance_of(LocalJumpError)
+ end
+ end
+
+ describe "when passed a splat" do
+ it "returns [] when the ary is empty" do
+ def r; ary = []; return *ary; end
+ r.should == []
+ end
+
+ it "returns the array when the array is size of 1" do
+ def r; ary = [1]; return *ary; end
+ r.should == [1]
+ end
+
+ it "returns the whole array when size is greater than 1" do
+ def r; ary = [1,2]; return *ary; end
+ r.should == [1,2]
+
+ def r; ary = [1,2,3]; return *ary; end
+ r.should == [1,2,3]
+ end
+
+ it "returns an array when used as a splat" do
+ def r; value = 1; return *value; end
+ r.should == [1]
+ end
+
+ it "calls 'to_a' on the splatted value first" do
+ def r
+ obj = Object.new
+ def obj.to_a
+ [1,2]
+ end
+
+ return *obj
+ end
+
+ r().should == [1,2]
+ end
+ end
+
+ describe "within a begin" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "executes ensure before returning" do
+ def f()
+ begin
+ ScratchPad << :begin
+ return :begin
+ ScratchPad << :after_begin
+ ensure
+ ScratchPad << :ensure
+ end
+ ScratchPad << :function
+ end
+ f().should == :begin
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "returns last value returned in ensure" do
+ def f()
+ begin
+ ScratchPad << :begin
+ return :begin
+ ScratchPad << :after_begin
+ ensure
+ ScratchPad << :ensure
+ return :ensure
+ ScratchPad << :after_ensure
+ end
+ ScratchPad << :function
+ end
+ f().should == :ensure
+ ScratchPad.recorded.should == [:begin, :ensure]
+ end
+
+ it "executes nested ensures before returning" do
+ def f()
+ begin
+ begin
+ ScratchPad << :inner_begin
+ return :inner_begin
+ ScratchPad << :after_inner_begin
+ ensure
+ ScratchPad << :inner_ensure
+ end
+ ScratchPad << :outer_begin
+ return :outer_begin
+ ScratchPad << :after_outer_begin
+ ensure
+ ScratchPad << :outer_ensure
+ end
+ ScratchPad << :function
+ end
+ f().should == :inner_begin
+ ScratchPad.recorded.should == [:inner_begin, :inner_ensure, :outer_ensure]
+ end
+
+ it "returns last value returned in nested ensures" do
+ def f()
+ begin
+ begin
+ ScratchPad << :inner_begin
+ return :inner_begin
+ ScratchPad << :after_inner_begin
+ ensure
+ ScratchPad << :inner_ensure
+ return :inner_ensure
+ ScratchPad << :after_inner_ensure
+ end
+ ScratchPad << :outer_begin
+ return :outer_begin
+ ScratchPad << :after_outer_begin
+ ensure
+ ScratchPad << :outer_ensure
+ return :outer_ensure
+ ScratchPad << :after_outer_ensure
+ end
+ ScratchPad << :function
+ end
+ f().should == :outer_ensure
+ ScratchPad.recorded.should == [:inner_begin, :inner_ensure, :outer_ensure]
+ end
+
+ it "executes the ensure clause when begin/ensure are inside a lambda" do
+ -> do
+ begin
+ return
+ ensure
+ ScratchPad.recorded << :ensure
+ end
+ end.call
+ ScratchPad.recorded.should == [:ensure]
+ end
+ end
+
+ describe "within a block" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "causes lambda to return nil if invoked without any arguments" do
+ -> { return; 456 }.call.should be_nil
+ end
+
+ it "causes lambda to return nil if invoked with an empty expression" do
+ -> { return (); 456 }.call.should be_nil
+ end
+
+ it "causes lambda to return the value passed to return" do
+ -> { return 123; 456 }.call.should == 123
+ end
+
+ it "causes the method that lexically encloses the block to return" do
+ ReturnSpecs::Blocks.new.enclosing_method.should == :return_value
+ ScratchPad.recorded.should == :before_return
+ end
+
+ it "returns from the lexically enclosing method even in case of chained calls" do
+ ReturnSpecs::NestedCalls.new.enclosing_method.should == :return_value
+ ScratchPad.recorded.should == :before_return
+ end
+
+ it "returns from the lexically enclosing method even in case of chained calls(in yield)" do
+ ReturnSpecs::NestedBlocks.new.enclosing_method.should == :return_value
+ ScratchPad.recorded.should == :before_return
+ end
+
+ it "causes the method to return even when the immediate parent has already returned" do
+ ReturnSpecs::SavedInnerBlock.new.start.should == :return_value
+ ScratchPad.recorded.should == :before_return
+ end
+
+ # jruby/jruby#3143
+ describe "downstream from a lambda" do
+ it "returns to its own return-capturing lexical enclosure" do
+ def a
+ ->{ yield }.call
+ return 2
+ end
+ def b
+ a { return 1 }
+ end
+
+ b.should == 1
+ end
+ end
+
+ end
+
+ describe "within two blocks" do
+ it "causes the method that lexically encloses the block to return" do
+ def f
+ 1.times { 1.times {return true}; false}; false
+ end
+ f.should be_true
+ end
+ end
+
+ describe "within define_method" do
+ it "goes through the method via a closure" do
+ ReturnSpecs::ThroughDefineMethod.new.outer.should == :good
+ end
+
+ it "stops at the method when the return is used directly" do
+ ReturnSpecs::DefineMethod.new.outer.should == :good
+ end
+ end
+
+ describe "invoked with a method call without parentheses with a block" do
+ it "returns the value returned from the method call" do
+ ReturnSpecs::MethodWithBlock.new.method1.should == 5
+ ReturnSpecs::MethodWithBlock.new.method2.should == [0, 1, 2]
+ end
+ end
+
+ describe "at top level" do
+ before :each do
+ @filename = tmp("top_return.rb")
+ ScratchPad.record []
+ end
+
+ after do
+ rm_r @filename
+ end
+
+ it "stops file execution" do
+ ruby_exe(<<-END_OF_CODE).should == "before return\n"
+ puts "before return"
+ return
+
+ puts "after return"
+ END_OF_CODE
+
+ $?.exitstatus.should == 0
+ end
+
+ describe "within if" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before if"
+ if true
+ return
+ end
+
+ ScratchPad << "after if"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before if"]
+ end
+ end
+
+ describe "within while loop" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before while"
+ while true
+ return
+ end
+
+ ScratchPad << "after while"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before while"]
+ end
+ end
+
+ describe "within a begin" do
+ it "is allowed in begin block" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ return
+ end
+
+ ScratchPad << "after begin"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before begin"]
+ end
+
+ it "is allowed in ensure block" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ ensure
+ return
+ end
+
+ ScratchPad << "after begin"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before begin"]
+ end
+
+ it "is allowed in rescue block" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ raise
+ rescue RuntimeError
+ return
+ end
+
+ ScratchPad << "after begin"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before begin"]
+ end
+
+ it "fires ensure block before returning" do
+ ruby_exe(<<-END_OF_CODE).should == "within ensure\n"
+ begin
+ return
+ ensure
+ puts "within ensure"
+ end
+
+ puts "after begin"
+ END_OF_CODE
+ end
+
+ it "fires ensure block before returning while loads file" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before begin"
+ begin
+ return
+ ensure
+ ScratchPad << "within ensure"
+ end
+
+ ScratchPad << "after begin"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before begin", "within ensure"]
+ end
+
+ it "swallows exception if returns in ensure block" do
+ File.write(@filename, <<-END_OF_CODE)
+ begin
+ raise
+ ensure
+ ScratchPad << "before return"
+ return
+ end
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before return"]
+ end
+ end
+
+ describe "within a block" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before call"
+ proc { return }.call
+
+ ScratchPad << "after call"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before call"]
+ end
+ end
+
+ describe "within a class" do
+ it "raises a SyntaxError" do
+ File.write(@filename, <<-END_OF_CODE)
+ class ReturnSpecs::A
+ ScratchPad << "before return"
+ return
+
+ ScratchPad << "after return"
+ end
+ END_OF_CODE
+
+ -> { load @filename }.should raise_error(SyntaxError)
+ end
+ end
+
+ describe "within a block within a class" do
+ it "is not allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ class ReturnSpecs::A
+ ScratchPad << "before return"
+ 1.times { return }
+ ScratchPad << "after return"
+ end
+ END_OF_CODE
+
+ -> { load @filename }.should raise_error(LocalJumpError)
+ end
+ end
+
+ describe "within BEGIN" do
+ it "is allowed" do
+ File.write(@filename, <<-END_OF_CODE)
+ BEGIN {
+ ScratchPad << "before call"
+ return
+ ScratchPad << "after call"
+ }
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before call"]
+ end
+ end
+
+ describe "file loading" do
+ it "stops file loading and execution" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before return"
+ return
+ ScratchPad << "after return"
+ END_OF_CODE
+
+ load @filename
+ ScratchPad.recorded.should == ["before return"]
+ end
+ end
+
+ describe "file requiring" do
+ it "stops file loading and execution" do
+ File.write(@filename, <<-END_OF_CODE)
+ ScratchPad << "before return"
+ return
+ ScratchPad << "after return"
+ END_OF_CODE
+
+ require @filename
+ ScratchPad.recorded.should == ["before return"]
+ end
+ end
+
+ describe "return with argument" do
+ it "warns but does not affect exit status" do
+ err = ruby_exe(<<-END_OF_CODE, args: "2>&1")
+ return 10
+ END_OF_CODE
+ $?.exitstatus.should == 0
+
+ err.should =~ /warning: argument of top-level return is ignored/
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/safe_navigator_spec.rb b/spec/ruby/language/safe_navigator_spec.rb
new file mode 100644
index 0000000000..c3aecff2dd
--- /dev/null
+++ b/spec/ruby/language/safe_navigator_spec.rb
@@ -0,0 +1,99 @@
+require_relative '../spec_helper'
+
+describe "Safe navigator" do
+ it "requires a method name to be provided" do
+ -> { eval("obj&. {}") }.should raise_error(SyntaxError)
+ end
+
+ context "when context is nil" do
+ it "always returns nil" do
+ eval("nil&.unknown").should == nil
+ eval("[][10]&.unknown").should == nil
+ end
+
+ it "can be chained" do
+ eval("nil&.one&.two&.three").should == nil
+ end
+
+ it "doesn't evaluate arguments" do
+ obj = Object.new
+ obj.should_not_receive(:m)
+ eval("nil&.unknown(obj.m) { obj.m }")
+ end
+ end
+
+ context "when context is false" do
+ it "calls the method" do
+ eval("false&.to_s").should == "false"
+
+ -> { eval("false&.unknown") }.should raise_error(NoMethodError)
+ end
+ end
+
+ context "when context is truthy" do
+ it "calls the method" do
+ eval("1&.to_s").should == "1"
+
+ -> { eval("1&.unknown") }.should raise_error(NoMethodError)
+ end
+ end
+
+ it "takes a list of arguments" do
+ eval("[1,2,3]&.first(2)").should == [1,2]
+ end
+
+ it "takes a block" do
+ eval("[1,2]&.map { |i| i * 2 }").should == [2, 4]
+ end
+
+ it "allows assignment methods" do
+ klass = Class.new do
+ attr_reader :foo
+ def foo=(val)
+ @foo = val
+ 42
+ end
+ end
+ obj = klass.new
+
+ eval("obj&.foo = 3").should == 3
+ obj.foo.should == 3
+
+ obj = nil
+ eval("obj&.foo = 3").should == nil
+ end
+
+ it "allows assignment operators" do
+ klass = Class.new do
+ attr_accessor :m
+
+ def initialize
+ @m = 0
+ end
+ end
+
+ obj = klass.new
+
+ eval("obj&.m += 3")
+ obj.m.should == 3
+
+ obj = nil
+ eval("obj&.m += 3").should == nil
+ end
+
+ it "does not call the operator method lazily with an assignment operator" do
+ klass = Class.new do
+ attr_writer :foo
+ def foo
+ nil
+ end
+ end
+ obj = klass.new
+
+ -> {
+ eval("obj&.foo += 3")
+ }.should raise_error(NoMethodError) { |e|
+ e.name.should == :+
+ }
+ end
+end
diff --git a/spec/ruby/language/safe_spec.rb b/spec/ruby/language/safe_spec.rb
new file mode 100644
index 0000000000..ee5c1b3ccc
--- /dev/null
+++ b/spec/ruby/language/safe_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../spec_helper'
+
+describe "The $SAFE variable" do
+ ruby_version_is ""..."3.0" do
+ it "warn when access" do
+ -> {
+ $SAFE
+ }.should complain(/\$SAFE will become a normal global variable in Ruby 3.0/)
+ end
+
+ it "warn when set" do
+ -> {
+ $SAFE = 1
+ }.should complain(/\$SAFE will become a normal global variable in Ruby 3.0/)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "$SAFE is a regular global variable" do
+ $SAFE.should == nil
+ $SAFE = 42
+ $SAFE.should == 42
+ ensure
+ $SAFE = nil
+ end
+ end
+end
diff --git a/spec/ruby/language/send_spec.rb b/spec/ruby/language/send_spec.rb
new file mode 100644
index 0000000000..5999079d58
--- /dev/null
+++ b/spec/ruby/language/send_spec.rb
@@ -0,0 +1,570 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/send'
+
+# Why so many fixed arg tests? JRuby and I assume other Ruby impls have
+# separate call paths for simple fixed arity methods. Testing up to five
+# will verify special and generic arity code paths for all impls.
+#
+# Method naming conventions:
+# M - Mandatory Args
+# O - Optional Arg
+# R - Rest Arg
+# Q - Post Mandatory Args
+
+specs = LangSendSpecs
+
+describe "Invoking a method" do
+ describe "with zero arguments" do
+ it "requires no arguments passed" do
+ specs.fooM0.should == 100
+ end
+
+ it "raises ArgumentError if the method has a positive arity" do
+ -> {
+ specs.fooM1
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with only mandatory arguments" do
+ it "requires exactly the same number of passed values" do
+ specs.fooM1(1).should == [1]
+ specs.fooM2(1,2).should == [1,2]
+ specs.fooM3(1,2,3).should == [1,2,3]
+ specs.fooM4(1,2,3,4).should == [1,2,3,4]
+ specs.fooM5(1,2,3,4,5).should == [1,2,3,4,5]
+ end
+
+ it "raises ArgumentError if the methods arity doesn't match" do
+ -> {
+ specs.fooM1(1,2)
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with optional arguments" do
+ it "uses the optional argument if none is is passed" do
+ specs.fooM0O1.should == [1]
+ end
+
+ it "uses the passed argument if available" do
+ specs.fooM0O1(2).should == [2]
+ end
+
+ it "raises ArgumentError if extra arguments are passed" do
+ -> {
+ specs.fooM0O1(2,3)
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with mandatory and optional arguments" do
+ it "uses the passed values in left to right order" do
+ specs.fooM1O1(2).should == [2,1]
+ end
+
+ it "raises an ArgumentError if there are no values for the mandatory args" do
+ -> {
+ specs.fooM1O1
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if too many values are passed" do
+ -> {
+ specs.fooM1O1(1,2,3)
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "with a rest argument" do
+ it "is an empty array if there are no additional arguments" do
+ specs.fooM0R().should == []
+ specs.fooM1R(1).should == [1, []]
+ end
+
+ it "gathers unused arguments" do
+ specs.fooM0R(1).should == [1]
+ specs.fooM1R(1,2).should == [1, [2]]
+ end
+ end
+
+ it "with a block makes it available to yield" do
+ specs.oneb(10) { 200 }.should == [10,200]
+ end
+
+ it "with a block converts the block to a Proc" do
+ prc = specs.makeproc { "hello" }
+ prc.should be_kind_of(Proc)
+ prc.call.should == "hello"
+ end
+
+ it "with an object as a block uses 'to_proc' for coercion" do
+ o = LangSendSpecs::ToProc.new(:from_to_proc)
+
+ specs.makeproc(&o).call.should == :from_to_proc
+
+ specs.yield_now(&o).should == :from_to_proc
+ end
+
+ it "raises a SyntaxError with both a literal block and an object as block" do
+ -> {
+ eval "specs.oneb(10, &l){ 42 }"
+ }.should raise_error(SyntaxError)
+ end
+
+ it "with same names as existing variables is ok" do
+ foobar = 100
+
+ def foobar; 200; end
+
+ foobar.should == 100
+ foobar().should == 200
+ end
+
+ it "with splat operator makes the object the direct arguments" do
+ a = [1,2,3]
+ specs.fooM3(*a).should == [1,2,3]
+ end
+
+ it "without parentheses works" do
+ (specs.fooM3 1,2,3).should == [1,2,3]
+ end
+
+ it "with a space separating method name and parenthesis treats expression in parenthesis as first argument" do
+ specs.weird_parens().should == "55"
+ end
+
+ describe "allows []=" do
+ before :each do
+ @obj = LangSendSpecs::AttrSet.new
+ end
+
+ it "with *args in the [] expanded to individual arguments" do
+ ary = [2,3]
+ (@obj[1, *ary] = 4).should == 4
+ @obj.result.should == [1,2,3,4]
+ end
+
+ it "with multiple *args" do
+ ary = [2,3]
+ post = [4,5]
+ (@obj[1, *ary] = *post).should == [4,5]
+ @obj.result.should == [1,2,3,[4,5]]
+ end
+
+ it "with multiple *args and does not unwrap the last splat" do
+ ary = [2,3]
+ post = [4]
+ (@obj[1, *ary] = *post).should == [4]
+ @obj.result.should == [1,2,3,[4]]
+ end
+
+ it "with a *args and multiple rhs args" do
+ ary = [2,3]
+ (@obj[1, *ary] = 4, 5).should == [4,5]
+ @obj.result.should == [1,2,3,[4,5]]
+ end
+ end
+
+ it "passes literal hashes without curly braces as the last parameter" do
+ specs.fooM3('abc', 456, 'rbx' => 'cool',
+ 'specs' => 'fail sometimes', 'oh' => 'weh').should == \
+ ['abc', 456, {'rbx' => 'cool', 'specs' => 'fail sometimes', 'oh' => 'weh'}]
+ end
+
+ it "passes a literal hash without curly braces or parens" do
+ (specs.fooM3 'abc', 456, 'rbx' => 'cool',
+ 'specs' => 'fail sometimes', 'oh' => 'weh').should == \
+ ['abc', 456, { 'rbx' => 'cool', 'specs' => 'fail sometimes', 'oh' => 'weh'}]
+ end
+
+ it "allows to literal hashes without curly braces as the only parameter" do
+ specs.fooM1(rbx: :cool, specs: :fail_sometimes).should ==
+ [{ rbx: :cool, specs: :fail_sometimes }]
+
+ (specs.fooM1 rbx: :cool, specs: :fail_sometimes).should ==
+ [{ rbx: :cool, specs: :fail_sometimes }]
+ end
+
+ describe "when the method is not available" do
+ it "invokes method_missing if it is defined" do
+ o = LangSendSpecs::MethodMissing.new
+ o.not_there(1,2)
+ o.message.should == :not_there
+ o.args.should == [1,2]
+ end
+
+ it "raises NameError if invoked as a vcall" do
+ -> { no_such_method }.should raise_error NameError
+ end
+
+ it "should omit the method_missing call from the backtrace for NameError" do
+ -> { no_such_method }.should raise_error { |e| e.backtrace.first.should_not include("method_missing") }
+ end
+
+ it "raises NoMethodError if invoked as an unambiguous method call" do
+ -> { no_such_method() }.should raise_error NoMethodError
+ -> { no_such_method(1,2,3) }.should raise_error NoMethodError
+ end
+
+ it "should omit the method_missing call from the backtrace for NoMethodError" do
+ -> { no_such_method() }.should raise_error { |e| e.backtrace.first.should_not include("method_missing") }
+ end
+ end
+
+end
+
+describe "Invoking a public setter method" do
+ it 'returns the set value' do
+ klass = Class.new do
+ def foobar=(*)
+ 1
+ end
+ end
+
+ (klass.new.foobar = 'bar').should == 'bar'
+ (klass.new.foobar = 'bar', 'baz').should == ["bar", "baz"]
+ end
+end
+
+describe "Invoking []= methods" do
+ it 'returns the set value' do
+ klass = Class.new do
+ def []=(*)
+ 1
+ end
+ end
+
+ (klass.new[33] = 'bar').should == 'bar'
+ (klass.new[33] = 'bar', 'baz').should == ['bar', 'baz']
+ (klass.new[33, 34] = 'bar', 'baz').should == ['bar', 'baz']
+ end
+end
+
+describe "Invoking a private setter method" do
+ describe "permits self as a receiver" do
+ it "for normal assignment" do
+ receiver = LangSendSpecs::PrivateSetter.new
+ receiver.call_self_foo_equals(42)
+ receiver.foo.should == 42
+ end
+
+ it "for multiple assignment" do
+ receiver = LangSendSpecs::PrivateSetter.new
+ receiver.call_self_foo_equals_masgn(42)
+ receiver.foo.should == 42
+ end
+ end
+end
+
+describe "Invoking a private getter method" do
+ it "permits self as a receiver" do
+ receiver = LangSendSpecs::PrivateGetter.new
+ receiver.call_self_foo_or_equals(6)
+ receiver.call_self_foo.should == 6
+ end
+end
+
+describe "Invoking a method" do
+ describe "with required args after the rest arguments" do
+ it "binds the required arguments first" do
+ specs.fooM0RQ1(1).should == [[], 1]
+ specs.fooM0RQ1(1,2).should == [[1], 2]
+ specs.fooM0RQ1(1,2,3).should == [[1,2], 3]
+
+ specs.fooM1RQ1(1,2).should == [1, [], 2]
+ specs.fooM1RQ1(1,2,3).should == [1, [2], 3]
+ specs.fooM1RQ1(1,2,3,4).should == [1, [2, 3], 4]
+
+ specs.fooM1O1RQ1(1,2).should == [1, 9, [], 2]
+ specs.fooM1O1RQ1(1,2,3).should == [1, 2, [], 3]
+ specs.fooM1O1RQ1(1,2,3,4).should == [1, 2, [3], 4]
+
+ specs.fooM1O1RQ2(1,2,3).should == [1, 9, [], 2, 3]
+ specs.fooM1O1RQ2(1,2,3,4).should == [1, 2, [], 3, 4]
+ specs.fooM1O1RQ2(1,2,3,4,5).should == [1, 2, [3], 4, 5]
+ end
+ end
+
+ describe "with mandatory arguments after optional arguments" do
+ it "binds the required arguments first" do
+ specs.fooO1Q1(0,1).should == [0,1]
+ specs.fooO1Q1(2).should == [1,2]
+
+ specs.fooM1O1Q1(2,3,4).should == [2,3,4]
+ specs.fooM1O1Q1(1,3).should == [1,2,3]
+
+ specs.fooM2O1Q1(1,2,4).should == [1,2,3,4]
+
+ specs.fooM2O2Q1(1,2,3,4,5).should == [1,2,3,4,5]
+ specs.fooM2O2Q1(1,2,3,5).should == [1,2,3,4,5]
+ specs.fooM2O2Q1(1,2,5).should == [1,2,3,4,5]
+
+ specs.fooO4Q1(1,2,3,4,5).should == [1,2,3,4,5]
+ specs.fooO4Q1(1,2,3,5).should == [1,2,3,4,5]
+ specs.fooO4Q1(1,2,5).should == [1,2,3,4,5]
+ specs.fooO4Q1(1,5).should == [1,2,3,4,5]
+ specs.fooO4Q1(5).should == [1,2,3,4,5]
+
+ specs.fooO4Q2(1,2,3,4,5,6).should == [1,2,3,4,5,6]
+ specs.fooO4Q2(1,2,3,5,6).should == [1,2,3,4,5,6]
+ specs.fooO4Q2(1,2,5,6).should == [1,2,3,4,5,6]
+ specs.fooO4Q2(1,5,6).should == [1,2,3,4,5,6]
+ specs.fooO4Q2(5,6).should == [1,2,3,4,5,6]
+ end
+ end
+
+ it "with .() invokes #call" do
+ q = proc { |z| z }
+ q.(1).should == 1
+
+ obj = mock("paren call")
+ obj.should_receive(:call).and_return(:called)
+ obj.().should == :called
+ end
+
+ it "allows a vestigial trailing ',' in the arguments" do
+ specs.fooM1(1,).should == [1]
+ end
+
+ it "with splat operator attempts to coerce it to an Array if the object respond_to?(:to_a)" do
+ ary = [2,3,4]
+ obj = mock("to_a")
+ obj.should_receive(:to_a).and_return(ary).twice
+ specs.fooM0R(*obj).should == ary
+ specs.fooM1R(1,*obj).should == [1, ary]
+ end
+
+ it "with splat operator * and non-Array value uses value unchanged if it does not respond_to?(:to_ary)" do
+ obj = Object.new
+ obj.should_not respond_to(:to_a)
+
+ specs.fooM0R(*obj).should == [obj]
+ specs.fooM1R(1,*obj).should == [1, [obj]]
+ end
+
+ it "accepts additional arguments after splat expansion" do
+ a = [1,2]
+ specs.fooM4(*a,3,4).should == [1,2,3,4]
+ specs.fooM4(0,*a,3).should == [0,1,2,3]
+ end
+
+ it "does not expand final array arguments after a splat expansion" do
+ a = [1, 2]
+ specs.fooM3(*a, [3, 4]).should == [1, 2, [3, 4]]
+ end
+
+ it "accepts final explicit literal Hash arguments after the splat" do
+ a = [1, 2]
+ specs.fooM0RQ1(*a, { a: 1 }).should == [[1, 2], { a: 1 }]
+ end
+
+ it "accepts final implicit literal Hash arguments after the splat" do
+ a = [1, 2]
+ specs.fooM0RQ1(*a, a: 1).should == [[1, 2], { a: 1 }]
+ end
+
+ it "accepts final Hash arguments after the splat" do
+ a = [1, 2]
+ b = { a: 1 }
+ specs.fooM0RQ1(*a, b).should == [[1, 2], { a: 1 }]
+ end
+
+ it "accepts mandatory and explicit literal Hash arguments after the splat" do
+ a = [1, 2]
+ specs.fooM0RQ2(*a, 3, { a: 1 }).should == [[1, 2], 3, { a: 1 }]
+ end
+
+ it "accepts mandatory and implicit literal Hash arguments after the splat" do
+ a = [1, 2]
+ specs.fooM0RQ2(*a, 3, a: 1).should == [[1, 2], 3, { a: 1 }]
+ end
+
+ it "accepts mandatory and Hash arguments after the splat" do
+ a = [1, 2]
+ b = { a: 1 }
+ specs.fooM0RQ2(*a, 3, b).should == [[1, 2], 3, { a: 1 }]
+ end
+
+ it "converts a final splatted explicit Hash to an Array" do
+ a = [1, 2]
+ specs.fooR(*a, 3, *{ a: 1 }).should == [1, 2, 3, [:a, 1]]
+ end
+
+ it "calls #to_a to convert a final splatted Hash object to an Array" do
+ a = [1, 2]
+ b = { a: 1 }
+ b.should_receive(:to_a).and_return([:a, 1])
+
+ specs.fooR(*a, 3, *b).should == [1, 2, 3, :a, 1]
+ end
+
+ it "accepts multiple splat expansions in the same argument list" do
+ a = [1,2,3]
+ b = 7
+ c = mock("pseudo-array")
+ c.should_receive(:to_a).and_return([0,0])
+
+ d = [4,5]
+ specs.rest_len(*a,*d,6,*b).should == 7
+ specs.rest_len(*a,*a,*a).should == 9
+ specs.rest_len(0,*a,4,*5,6,7,*c,-1).should == 11
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "expands the Array elements from the splat after executing the arguments and block if no other arguments follow the splat" do
+ def self.m(*args, &block)
+ [args, block]
+ end
+
+ args = [1, nil]
+ m(*args, &args.pop).should == [[1], nil]
+
+ args = [1, nil]
+ order = []
+ m(*(order << :args; args), &(order << :block; args.pop)).should == [[1], nil]
+ order.should == [:args, :block]
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "expands the Array elements from the splat before applying block argument operations" do
+ def self.m(*args, &block)
+ [args, block]
+ end
+
+ args = [1, nil]
+ m(*args, &args.pop).should == [[1, nil], nil]
+
+ args = [1, nil]
+ order = []
+ m(*(order << :args; args), &(order << :block; args.pop)).should == [[1, nil], nil]
+ order.should == [:args, :block]
+ end
+ end
+
+ it "evaluates the splatted arguments before the block if there are other arguments after the splat" do
+ def self.m(*args, &block)
+ [args, block]
+ end
+
+ args = [1, nil]
+ m(*args, 2, &args.pop).should == [[1, nil, 2], nil]
+ end
+
+ it "expands an array to arguments grouped in parentheses" do
+ specs.destructure2([40,2]).should == 42
+ end
+
+ it "expands an array to arguments grouped in parentheses and ignores any rest arguments in the array" do
+ specs.destructure2([40,2,84]).should == 42
+ end
+
+ it "expands an array to arguments grouped in parentheses and sets not specified arguments to nil" do
+ specs.destructure2b([42]).should == [42, nil]
+ end
+
+ it "expands an array to arguments grouped in parentheses which in turn takes rest arguments" do
+ specs.destructure4r([1, 2, 3]).should == [1, 2, [], 3, nil]
+ specs.destructure4r([1, 2, 3, 4]).should == [1, 2, [], 3, 4]
+ specs.destructure4r([1, 2, 3, 4, 5]).should == [1, 2, [3], 4, 5]
+ end
+
+ it "with optional argument(s), expands an array to arguments grouped in parentheses" do
+ specs.destructure4o(1, [2, 3]).should == [1, 1, nil, [2, 3]]
+ specs.destructure4o(1, [], 2).should == [1, nil, nil, 2]
+ specs.destructure4os(1, [2, 3]).should == [1, 2, [3]]
+ specs.destructure5o(1, [2, 3]).should == [1, 2, 1, nil, [2, 3]]
+ specs.destructure7o(1, [2, 3]).should == [1, 2, 1, nil, 2, 3]
+ specs.destructure7b(1, [2, 3]) do |(a,*b,c)|
+ [a, c]
+ end.should == [1, 3]
+ end
+
+ describe "new-style hash arguments" do
+ describe "as the only parameter" do
+ it "passes without curly braces" do
+ specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "passes without curly braces or parens" do
+ (specs.fooM1 rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "handles a hanging comma without curly braces" do
+ specs.fooM1(abc: 123,).should == [{abc: 123}]
+ specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+ end
+
+ describe "as the last parameter" do
+ it "passes without curly braces" do
+ specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "passes without curly braces or parens" do
+ (specs.fooM3 'abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "handles a hanging comma without curly braces" do
+ specs.fooM3('abc', 123, abc: 123,).should == ['abc', 123, {abc: 123}]
+ specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+ end
+ end
+
+ describe "mixed new- and old-style hash arguments" do
+ describe "as the only parameter" do
+ it "passes without curly braces" do
+ specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "passes without curly braces or parens" do
+ (specs.fooM1 rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "handles a hanging comma without curly braces" do
+ specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
+ [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+ end
+
+ describe "as the last parameter" do
+ it "passes without curly braces" do
+ specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "passes without curly braces or parens" do
+ (specs.fooM3 'abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+
+ it "handles a hanging comma without curly braces" do
+ specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
+ ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
+ end
+ end
+ end
+
+end
+
+describe "allows []= with arguments after splat" do
+ before :each do
+ @obj = LangSendSpecs::Attr19Set.new
+ @ary = ["a"]
+ end
+
+ it "with *args in the [] and post args" do
+ @obj[1,*@ary,123] = 2
+ @obj.result.should == [1, "a", 123, 2]
+ end
+end
diff --git a/spec/ruby/language/shared/__FILE__.rb b/spec/ruby/language/shared/__FILE__.rb
new file mode 100644
index 0000000000..3e4f5c958d
--- /dev/null
+++ b/spec/ruby/language/shared/__FILE__.rb
@@ -0,0 +1,23 @@
+describe :language___FILE__, shared: true do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ @path = File.join(CODE_LOADING_DIR, "file_fixture.rb")
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ it "equals the absolute path of a file loaded by an absolute path" do
+ @object.send(@method, @path).should be_true
+ ScratchPad.recorded.should == [@path]
+ end
+
+ it "equals the absolute path of a file loaded by a relative path" do
+ $LOAD_PATH << "."
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "file_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [@path]
+ end
+end
diff --git a/spec/ruby/language/shared/__LINE__.rb b/spec/ruby/language/shared/__LINE__.rb
new file mode 100644
index 0000000000..076b74b3ba
--- /dev/null
+++ b/spec/ruby/language/shared/__LINE__.rb
@@ -0,0 +1,15 @@
+describe :language___LINE__, shared: true do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ @path = File.expand_path("line_fixture.rb", CODE_LOADING_DIR)
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ it "equals the line number of the text in a loaded file" do
+ @object.send(@method, @path).should be_true
+ ScratchPad.recorded.should == [1, 5]
+ end
+end
diff --git a/spec/ruby/language/singleton_class_spec.rb b/spec/ruby/language/singleton_class_spec.rb
new file mode 100644
index 0000000000..c1fb682ea0
--- /dev/null
+++ b/spec/ruby/language/singleton_class_spec.rb
@@ -0,0 +1,293 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/class'
+
+describe "A singleton class" do
+ it "is TrueClass for true" do
+ true.singleton_class.should == TrueClass
+ end
+
+ it "is FalseClass for false" do
+ false.singleton_class.should == FalseClass
+ end
+
+ it "is NilClass for nil" do
+ nil.singleton_class.should == NilClass
+ end
+
+ it "raises a TypeError for Integer's" do
+ -> { 1.singleton_class }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for symbols" do
+ -> { :symbol.singleton_class }.should raise_error(TypeError)
+ end
+
+ it "is a singleton Class instance" do
+ o = mock('x')
+ o.singleton_class.should be_kind_of(Class)
+ o.singleton_class.should_not equal(Object)
+ o.should be_kind_of(o.singleton_class)
+ end
+
+ it "is a Class for classes" do
+ ClassSpecs::A.singleton_class.should be_kind_of(Class)
+ end
+
+ it "inherits from Class for classes" do
+ Class.should be_ancestor_of(Object.singleton_class)
+ end
+
+ it "is a subclass of Class's singleton class" do
+ ec = ClassSpecs::A.singleton_class
+ ec.should be_kind_of(Class.singleton_class)
+ end
+
+ it "is a subclass of the same level of Class's singleton class" do
+ ecec = ClassSpecs::A.singleton_class.singleton_class
+ class_ec = Class.singleton_class
+
+ ecec.should be_kind_of(class_ec.singleton_class)
+ ecec.should be_kind_of(class_ec)
+ end
+
+ it "is a subclass of a superclass's singleton class" do
+ ClassSpecs::K.singleton_class.superclass.should ==
+ ClassSpecs::H.singleton_class
+ end
+
+ it "is a subclass of the same level of superclass's singleton class" do
+ ClassSpecs::K.singleton_class.singleton_class.superclass.should ==
+ ClassSpecs::H.singleton_class.singleton_class
+ end
+
+ it "for BasicObject has Class as it's superclass" do
+ BasicObject.singleton_class.superclass.should == Class
+ end
+
+ it "for BasicObject has the proper level of superclass for Class" do
+ BasicObject.singleton_class.singleton_class.superclass.should ==
+ Class.singleton_class
+ end
+
+ it "has class String as the superclass of a String instance" do
+ "blah".singleton_class.superclass.should == String
+ end
+
+ it "doesn't have singleton class" do
+ -> { bignum_value.singleton_class }.should raise_error(TypeError)
+ end
+end
+
+describe "A constant on a singleton class" do
+ before :each do
+ @object = Object.new
+ class << @object
+ CONST = self
+ end
+ end
+
+ it "can be accessed after the singleton class body is reopened" do
+ class << @object
+ CONST.should == self
+ end
+ end
+
+ it "can be accessed via self::CONST" do
+ class << @object
+ self::CONST.should == self
+ end
+ end
+
+ it "can be accessed via const_get" do
+ class << @object
+ const_get(:CONST).should == self
+ end
+ end
+
+ it "is not defined on the object's class" do
+ @object.class.const_defined?(:CONST).should be_false
+ end
+
+ it "is not defined in the singleton class opener's scope" do
+ class << @object
+ CONST
+ end
+ -> { CONST }.should raise_error(NameError)
+ end
+
+ it "cannot be accessed via object::CONST" do
+ -> do
+ @object::CONST
+ end.should raise_error(TypeError)
+ end
+
+ it "raises a NameError for anonymous_module::CONST" do
+ @object = Class.new
+ class << @object
+ CONST = 100
+ end
+
+ -> do
+ @object::CONST
+ end.should raise_error(NameError)
+ end
+
+ it "appears in the singleton class constant list" do
+ @object.singleton_class.should have_constant(:CONST)
+ end
+
+ it "does not appear in the object's class constant list" do
+ @object.class.should_not have_constant(:CONST)
+ end
+
+ it "is not preserved when the object is duped" do
+ @object = @object.dup
+
+ -> do
+ class << @object; CONST; end
+ end.should raise_error(NameError)
+ end
+
+ it "is preserved when the object is cloned" do
+ @object = @object.clone
+
+ class << @object
+ CONST.should_not be_nil
+ end
+ end
+end
+
+describe "Defining instance methods on a singleton class" do
+ before :each do
+ @k = ClassSpecs::K.new
+ class << @k
+ def singleton_method; 1 end
+ end
+
+ @k_sc = @k.singleton_class
+ end
+
+ it "defines public methods" do
+ @k_sc.should have_public_instance_method(:singleton_method)
+ end
+end
+
+describe "Instance methods of a singleton class" do
+ before :each do
+ k = ClassSpecs::K.new
+ @k_sc = k.singleton_class
+ @a_sc = ClassSpecs::A.new.singleton_class
+ @a_c_sc = ClassSpecs::A.singleton_class
+ end
+
+ it "include ones of the object's class" do
+ @k_sc.should have_instance_method(:example_instance_method)
+ end
+
+ it "does not include class methods of the object's class" do
+ @k_sc.should_not have_instance_method(:example_class_method)
+ end
+
+ it "include instance methods of Object" do
+ @a_sc.should have_instance_method(:example_instance_method_of_object)
+ end
+
+ it "does not include class methods of Object" do
+ @a_sc.should_not have_instance_method(:example_class_method_of_object)
+ end
+
+ describe "for a class" do
+ it "include instance methods of Class" do
+ @a_c_sc.should have_instance_method(:example_instance_method_of_class)
+ end
+
+ it "does not include class methods of Class" do
+ @a_c_sc.should_not have_instance_method(:example_class_method_of_class)
+ end
+
+ it "does not include instance methods of the singleton class of Class" do
+ @a_c_sc.should_not have_instance_method(:example_instance_method_of_singleton_class)
+ end
+
+ it "does not include class methods of the singleton class of Class" do
+ @a_c_sc.should_not have_instance_method(:example_class_method_of_singleton_class)
+ end
+ end
+
+ describe "for a singleton class" do
+ it "includes instance methods of the singleton class of Class" do
+ @a_c_sc.singleton_class.should have_instance_method(:example_instance_method_of_singleton_class)
+ end
+
+ it "does not include class methods of the singleton class of Class" do
+ @a_c_sc.singleton_class.should_not have_instance_method(:example_class_method_of_singleton_class)
+ end
+ end
+end
+
+describe "Class methods of a singleton class" do
+ before :each do
+ k = ClassSpecs::K.new
+ @k_sc = k.singleton_class
+ @a_sc = ClassSpecs::A.new.singleton_class
+ @a_c_sc = ClassSpecs::A.singleton_class
+ end
+
+ it "include ones of the object's class" do
+ @k_sc.should have_method(:example_class_method)
+ end
+
+ it "does not include instance methods of the object's class" do
+ @k_sc.should_not have_method(:example_instance_method)
+ end
+
+ it "include instance methods of Class" do
+ @a_sc.should have_method(:example_instance_method_of_class)
+ end
+
+ it "does not include class methods of Class" do
+ @a_sc.should_not have_method(:example_class_method_of_class)
+ end
+
+ describe "for a class" do
+ it "include instance methods of Class" do
+ @a_c_sc.should have_method(:example_instance_method_of_class)
+ end
+
+ it "include class methods of Class" do
+ @a_c_sc.should have_method(:example_class_method_of_class)
+ end
+
+ it "include instance methods of the singleton class of Class" do
+ @a_c_sc.should have_method(:example_instance_method_of_singleton_class)
+ end
+
+ it "does not include class methods of the singleton class of Class" do
+ @a_c_sc.should_not have_method(:example_class_method_of_singleton_class)
+ end
+ end
+
+ describe "for a singleton class" do
+ it "include instance methods of the singleton class of Class" do
+ @a_c_sc.singleton_class.should have_method(:example_instance_method_of_singleton_class)
+ end
+
+ it "include class methods of the singleton class of Class" do
+ @a_c_sc.singleton_class.should have_method(:example_class_method_of_singleton_class)
+ end
+ end
+end
+
+describe "Instantiating a singleton class" do
+ it "raises a TypeError when new is called" do
+ -> {
+ Object.new.singleton_class.new
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when allocate is called" do
+ -> {
+ Object.new.singleton_class.allocate
+ }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/language/source_encoding_spec.rb b/spec/ruby/language/source_encoding_spec.rb
new file mode 100644
index 0000000000..19364fc676
--- /dev/null
+++ b/spec/ruby/language/source_encoding_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../spec_helper'
+
+describe "Source files" do
+
+ describe "encoded in UTF-8 without a BOM" do
+ it "can be parsed" do
+ ruby_exe(fixture(__FILE__, "utf8-nobom.rb"), args: "2>&1").should == "hello\n"
+ end
+ end
+
+ describe "encoded in UTF-8 with a BOM" do
+ it "can be parsed" do
+ ruby_exe(fixture(__FILE__, "utf8-bom.rb"), args: "2>&1").should == "hello\n"
+ end
+ end
+
+ describe "encoded in UTF-16 LE without a BOM" do
+ it "are parsed because empty as they contain a NUL byte before the encoding comment" do
+ ruby_exe(fixture(__FILE__, "utf16-le-nobom.rb"), args: "2>&1").should == ""
+ end
+ end
+
+ describe "encoded in UTF-16 LE with a BOM" do
+ it "are invalid because they contain an invalid UTF-8 sequence before the encoding comment" do
+ bom = "\xFF\xFE".b
+ source = "# encoding: utf-16le\nputs 'hello'\n"
+ source = bom + source.bytes.zip([0]*source.bytesize).flatten.pack('C*')
+ path = tmp("utf16-le-bom.rb")
+
+ touch(path, "wb") { |f| f.write source }
+ begin
+ ruby_exe(path, args: "2>&1", exit_status: 1).should =~ /invalid multibyte char/
+ ensure
+ rm_r path
+ end
+ end
+ end
+
+ describe "encoded in UTF-16 BE without a BOM" do
+ it "are parsed as empty because they contain a NUL byte before the encoding comment" do
+ ruby_exe(fixture(__FILE__, "utf16-be-nobom.rb"), args: "2>&1").should == ""
+ end
+ end
+
+ describe "encoded in UTF-16 BE with a BOM" do
+ it "are invalid because they contain an invalid UTF-8 sequence before the encoding comment" do
+ bom = "\xFE\xFF".b
+ source = "# encoding: utf-16be\nputs 'hello'\n"
+ source = bom + ([0]*source.bytesize).zip(source.bytes).flatten.pack('C*')
+ path = tmp("utf16-be-bom.rb")
+
+ touch(path, "wb") { |f| f.write source }
+ begin
+ ruby_exe(path, args: "2>&1", exit_status: 1).should =~ /invalid multibyte char/
+ ensure
+ rm_r path
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb
new file mode 100644
index 0000000000..02e3488a1f
--- /dev/null
+++ b/spec/ruby/language/string_spec.rb
@@ -0,0 +1,299 @@
+# -*- encoding: binary -*-
+
+require_relative '../spec_helper'
+
+# TODO: rewrite these horrid specs. it "are..." seriously?!
+
+describe "Ruby character strings" do
+
+ before :each do
+ @ip = 'xxx' # used for interpolation
+ $ip = 'xxx'
+ end
+
+ it "don't get interpolated when put in single quotes" do
+ '#{@ip}'.should == '#{@ip}'
+ end
+
+ it 'get interpolated with #{} when put in double quotes' do
+ "#{@ip}".should == 'xxx'
+ end
+
+ it "interpolate instance variables just with the # character" do
+ "#@ip".should == 'xxx'
+ end
+
+ it "interpolate global variables just with the # character" do
+ "#$ip".should == 'xxx'
+ end
+
+ it "allows underscore as part of a variable name in a simple interpolation" do
+ @my_ip = 'xxx'
+ "#@my_ip".should == 'xxx'
+ end
+
+ it "does not interpolate invalid variable names" do
+ "#@".should == '#@'
+ "#$%".should == '#$%'
+ end
+
+ it "has characters [.(=?!# end simple # interpolation" do
+ "#@ip[".should == 'xxx['
+ "#@ip.".should == 'xxx.'
+ "#@ip(".should == 'xxx('
+ "#@ip=".should == 'xxx='
+ "#@ip?".should == 'xxx?'
+ "#@ip!".should == 'xxx!'
+ "#@ip#@ip".should == 'xxxxxx'
+ end
+
+ it "don't get confused by partial interpolation character sequences" do
+ "#@".should == '#@'
+ "#@ ".should == '#@ '
+ "#@@".should == '#@@'
+ "#@@ ".should == '#@@ '
+ "#$ ".should == '#$ '
+ "#\$".should == '#$'
+ end
+
+ it "allows using non-alnum characters as string delimiters" do
+ %(hey #{@ip}).should == "hey xxx"
+ %[hey #{@ip}].should == "hey xxx"
+ %{hey #{@ip}}.should == "hey xxx"
+ %<hey #{@ip}>.should == "hey xxx"
+ %!hey #{@ip}!.should == "hey xxx"
+ %@hey #{@ip}@.should == "hey xxx"
+ %#hey hey#.should == "hey hey"
+ %%hey #{@ip}%.should == "hey xxx"
+ %^hey #{@ip}^.should == "hey xxx"
+ %&hey #{@ip}&.should == "hey xxx"
+ %*hey #{@ip}*.should == "hey xxx"
+ %-hey #{@ip}-.should == "hey xxx"
+ %_hey #{@ip}_.should == "hey xxx"
+ %=hey #{@ip}=.should == "hey xxx"
+ %+hey #{@ip}+.should == "hey xxx"
+ %~hey #{@ip}~.should == "hey xxx"
+ %:hey #{@ip}:.should == "hey xxx"
+ %;hey #{@ip};.should == "hey xxx"
+ %"hey #{@ip}".should == "hey xxx"
+ %|hey #{@ip}|.should == "hey xxx"
+ %?hey #{@ip}?.should == "hey xxx"
+ %/hey #{@ip}/.should == "hey xxx"
+ %,hey #{@ip},.should == "hey xxx"
+ %.hey #{@ip}..should == "hey xxx"
+
+ # surprised? huh
+ %'hey #{@ip}'.should == "hey xxx"
+ %\hey #{@ip}\.should == "hey xxx"
+ %`hey #{@ip}`.should == "hey xxx"
+ %$hey #{@ip}$.should == "hey xxx"
+ end
+
+ it "using percent with 'q', stopping interpolation" do
+ %q(#{@ip}).should == '#{@ip}'
+ end
+
+ it "using percent with 'Q' to interpolate" do
+ %Q(#{@ip}).should == 'xxx'
+ end
+
+ # The backslashes :
+ #
+ # \t (tab), \n (newline), \r (carriage return), \f (form feed), \b
+ # (backspace), \a (bell), \e (escape), \s (whitespace), \nnn (octal),
+ # \xnn (hexadecimal), \cx (control x), \C-x (control x), \M-x (meta x),
+ # \M-\C-x (meta control x)
+
+ it "backslashes follow the same rules as interpolation" do
+ "\t\n\r\f\b\a\e\s\075\x62\cx".should == "\t\n\r\f\b\a\e =b\030"
+ '\t\n\r\f\b\a\e =b\030'.should == "\\t\\n\\r\\f\\b\\a\\e =b\\030"
+ end
+
+ it "calls #to_s when the object is not a String" do
+ obj = mock('to_s')
+ obj.stub!(:to_s).and_return('42')
+
+ "#{obj}".should == '42'
+ end
+
+ it "calls #to_s as a private method" do
+ obj = mock('to_s')
+ obj.stub!(:to_s).and_return('42')
+
+ class << obj
+ private :to_s
+ end
+
+ "#{obj}".should == '42'
+ end
+
+ it "uses an internal representation when #to_s doesn't return a String" do
+ obj = mock('to_s')
+ obj.stub!(:to_s).and_return(42)
+
+ # See rubyspec commit 787c132d by yugui. There is value in
+ # ensuring that this behavior works. So rather than removing
+ # this spec completely, the only thing that can be asserted
+ # is that if you interpolate an object that fails to return
+ # a String, you will still get a String and not raise an
+ # exception.
+ "#{obj}".should be_an_instance_of(String)
+ end
+
+ it "allows a dynamic string to parse a nested do...end block as an argument to a call without parens, interpolated" do
+ s = eval 'eval "#{proc do; 1; end.call}"'
+ s.should == 1
+ end
+
+ it "are produced from character shortcuts" do
+ ?z.should == 'z'
+ end
+
+ it "are produced from control character shortcuts" do
+ # Control-Z
+ ?\C-z.should == "\x1A"
+
+ # Meta-Z
+ ?\M-z.should == "\xFA"
+
+ # Meta-Control-Z
+ ?\M-\C-z.should == "\x9A"
+ end
+
+ describe "Unicode escaping" do
+ it "can be done with \\u and four hex digits" do
+ [ ["\u0000", 0x0000],
+ ["\u2020", 0x2020]
+ ].should be_computed_by(:ord)
+ end
+
+ it "can be done with \\u{} and one to six hex digits" do
+ [ ["\u{a}", 0xa],
+ ["\u{ab}", 0xab],
+ ["\u{abc}", 0xabc],
+ ["\u{1abc}", 0x1abc],
+ ["\u{12abc}", 0x12abc],
+ ["\u{100000}", 0x100000]
+ ].should be_computed_by(:ord)
+ end
+
+ # TODO: spec other source encodings
+ describe "with ASCII_8BIT source encoding" do
+ it "produces an ASCII string when escaping ASCII characters via \\u" do
+ "\u0000".encoding.should == Encoding::BINARY
+ end
+
+ it "produces an ASCII string when escaping ASCII characters via \\u{}" do
+ "\u{0000}".encoding.should == Encoding::BINARY
+ end
+
+ it "produces a UTF-8-encoded string when escaping non-ASCII characters via \\u" do
+ "\u1234".encoding.should == Encoding::UTF_8
+ end
+
+ it "produces a UTF-8-encoded string when escaping non-ASCII characters via \\u{}" do
+ "\u{1234}".encoding.should == Encoding::UTF_8
+ end
+ end
+ end
+end
+
+# TODO: rewrite all specs above this
+
+describe "Ruby String literals" do
+ def str_concat
+ "foo" "bar" "baz"
+ end
+
+ def long_string_literals
+ "Beautiful is better than ugly." \
+ "Explicit is better than implicit."
+ end
+
+ it "on a single line with spaces in between are concatenated together" do
+ str_concat.should == "foobarbaz"
+ end
+
+ it "on multiple lines with newlines and backslash in between are concatenated together" do
+ long_string_literals.should == "Beautiful is better than ugly.Explicit is better than implicit."
+ end
+
+ describe "with a magic frozen comment" do
+ it "produce the same object each time" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_one_literal.rb")).chomp.should == "true"
+ end
+
+ it "produce the same object for literals with the same content" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_two_literals.rb")).chomp.should == "true"
+ end
+
+ it "produce the same object for literals with the same content in different files" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files.rb")).chomp.should == "true"
+ end
+
+ it "produce different objects for literals with the same content in different files if the other file doesn't have the comment" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "true"
+ end
+
+ it "produce different objects for literals with the same content in different files if they have different encodings" do
+ ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_diff_enc.rb")).chomp.should == "true"
+ end
+ end
+
+end
+
+describe "Ruby String interpolation" do
+ it "permits an empty expression" do
+ s = "#{}" # rubocop:disable Lint/EmptyInterpolation
+ s.should.empty?
+ s.should_not.frozen?
+ end
+
+ it "returns a string with the source encoding by default" do
+ "a#{"b"}c".encoding.should == Encoding::BINARY
+ eval('"a#{"b"}c"'.force_encoding("us-ascii")).encoding.should == Encoding::US_ASCII
+ eval("# coding: US-ASCII \n 'a#{"b"}c'").encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns a string with the source encoding, even if the components have another encoding" do
+ a = "abc".force_encoding("euc-jp")
+ "#{a}".encoding.should == Encoding::BINARY
+
+ b = "abc".encode("utf-8")
+ "#{b}".encoding.should == Encoding::BINARY
+ end
+
+ it "raises an Encoding::CompatibilityError if the Encodings are not compatible" do
+ a = "\u3042"
+ b = "\xff".force_encoding "binary"
+
+ -> { "#{a} #{b}" }.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "creates a non-frozen String" do
+ code = <<~'RUBY'
+ "a#{6*7}c"
+ RUBY
+ eval(code).should_not.frozen?
+ end
+
+ ruby_version_is "3.0" do
+ it "creates a non-frozen String when # frozen-string-literal: true is used" do
+ code = <<~'RUBY'
+ # frozen-string-literal: true
+ "a#{6*7}c"
+ RUBY
+ eval(code).should_not.frozen?
+ end
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "creates a frozen String when # frozen-string-literal: true is used" do
+ code = <<~'RUBY'
+ # frozen-string-literal: true
+ "a#{6*7}c"
+ RUBY
+ eval(code).should.frozen?
+ end
+ end
+end
diff --git a/spec/ruby/language/super_spec.rb b/spec/ruby/language/super_spec.rb
new file mode 100644
index 0000000000..1ac5c5e1be
--- /dev/null
+++ b/spec/ruby/language/super_spec.rb
@@ -0,0 +1,434 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/super'
+
+describe "The super keyword" do
+ it "calls the method on the calling class" do
+ SuperSpecs::S1::A.new.foo([]).should == ["A#foo","A#bar"]
+ SuperSpecs::S1::A.new.bar([]).should == ["A#bar"]
+ SuperSpecs::S1::B.new.foo([]).should == ["B#foo","A#foo","B#bar","A#bar"]
+ SuperSpecs::S1::B.new.bar([]).should == ["B#bar","A#bar"]
+ end
+
+ it "searches the full inheritance chain" do
+ SuperSpecs::S2::B.new.foo([]).should == ["B#foo","A#baz"]
+ SuperSpecs::S2::B.new.baz([]).should == ["A#baz"]
+ SuperSpecs::S2::C.new.foo([]).should == ["B#foo","C#baz","A#baz"]
+ SuperSpecs::S2::C.new.baz([]).should == ["C#baz","A#baz"]
+ end
+
+ it "searches class methods" do
+ SuperSpecs::S3::A.new.foo([]).should == ["A#foo"]
+ SuperSpecs::S3::A.foo([]).should == ["A.foo"]
+ SuperSpecs::S3::A.bar([]).should == ["A.bar","A.foo"]
+ SuperSpecs::S3::B.new.foo([]).should == ["A#foo"]
+ SuperSpecs::S3::B.foo([]).should == ["B.foo","A.foo"]
+ SuperSpecs::S3::B.bar([]).should == ["B.bar","A.bar","B.foo","A.foo"]
+ end
+
+ it "calls the method on the calling class including modules" do
+ SuperSpecs::MS1::A.new.foo([]).should == ["ModA#foo","ModA#bar"]
+ SuperSpecs::MS1::A.new.bar([]).should == ["ModA#bar"]
+ SuperSpecs::MS1::B.new.foo([]).should == ["B#foo","ModA#foo","ModB#bar","ModA#bar"]
+ SuperSpecs::MS1::B.new.bar([]).should == ["ModB#bar","ModA#bar"]
+ end
+
+ it "searches the full inheritance chain including modules" do
+ SuperSpecs::MS2::B.new.foo([]).should == ["ModB#foo","A#baz"]
+ SuperSpecs::MS2::B.new.baz([]).should == ["A#baz"]
+ SuperSpecs::MS2::C.new.baz([]).should == ["C#baz","A#baz"]
+ SuperSpecs::MS2::C.new.foo([]).should == ["ModB#foo","C#baz","A#baz"]
+ end
+
+ it "can resolve to different methods in an included module method" do
+ SuperSpecs::MultiSuperTargets::A.new.foo.should == :BaseA
+ SuperSpecs::MultiSuperTargets::B.new.foo.should == :BaseB
+ end
+
+ it "searches class methods including modules" do
+ SuperSpecs::MS3::A.new.foo([]).should == ["A#foo"]
+ SuperSpecs::MS3::A.foo([]).should == ["ModA#foo"]
+ SuperSpecs::MS3::A.bar([]).should == ["ModA#bar","ModA#foo"]
+ SuperSpecs::MS3::B.new.foo([]).should == ["A#foo"]
+ SuperSpecs::MS3::B.foo([]).should == ["B.foo","ModA#foo"]
+ SuperSpecs::MS3::B.bar([]).should == ["B.bar","ModA#bar","B.foo","ModA#foo"]
+ end
+
+ it "searches BasicObject from a module for methods defined there" do
+ SuperSpecs::IncludesFromBasic.new.__send__(:foobar).should == 43
+ end
+
+ it "searches BasicObject through another module for methods defined there" do
+ SuperSpecs::IncludesIntermediate.new.__send__(:foobar).should == 42
+ end
+
+ it "calls the correct method when the method visibility is modified" do
+ SuperSpecs::MS4::A.new.example.should == 5
+ end
+
+ it "calls the correct method when the superclass argument list is different from the subclass" do
+ SuperSpecs::S4::A.new.foo([]).should == ["A#foo"]
+ SuperSpecs::S4::B.new.foo([],"test").should == ["B#foo(a,test)", "A#foo"]
+ end
+
+ it "raises an error error when super method does not exist" do
+ sup = Class.new
+ sub_normal = Class.new(sup) do
+ def foo
+ super()
+ end
+ end
+ sub_zsuper = Class.new(sup) do
+ def foo
+ super
+ end
+ end
+
+ -> {sub_normal.new.foo}.should raise_error(NoMethodError, /super/)
+ -> {sub_zsuper.new.foo}.should raise_error(NoMethodError, /super/)
+ end
+
+ it "uses given block even if arguments are passed explicitly" do
+ c1 = Class.new do
+ def m
+ yield
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ super()
+ end
+ end
+
+ c2.new.m(:dump) { :value }.should == :value
+ end
+
+ it "can pass an explicit block" do
+ c1 = Class.new do
+ def m(v)
+ yield(v)
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ block = -> w { yield(w + 'b') }
+ super(v, &block)
+ end
+ end
+
+ c2.new.m('a') { |x| x + 'c' }.should == 'abc'
+ end
+
+ it "can pass no block using &nil" do
+ c1 = Class.new do
+ def m(v)
+ block_given?
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ super(v, &nil)
+ end
+ end
+
+ c2.new.m('a') { raise }.should be_false
+ end
+
+ it "uses block argument given to method when used in a block" do
+ c1 = Class.new do
+ def m
+ yield
+ end
+ end
+ c2 = Class.new(c1) do
+ def m(v)
+ ary = []
+ 1.times do
+ ary << super()
+ end
+ ary
+ end
+ end
+
+ c2.new.m(:dump) { :value }.should == [ :value ]
+ end
+
+ it "calls the superclass method when in a block" do
+ SuperSpecs::S6.new.here.should == :good
+ end
+
+ it "calls the superclass method when initial method is defined_method'd" do
+ SuperSpecs::S7.new.here.should == :good
+ end
+
+ it "can call through a define_method multiple times (caching check)" do
+ obj = SuperSpecs::S7.new
+
+ 2.times do
+ obj.here.should == :good
+ end
+ end
+
+ it "supers up appropriate name even if used for multiple method names" do
+ sup = Class.new do
+ def a; "a"; end
+ def b; "b"; end
+ end
+
+ sub = Class.new(sup) do
+ [:a, :b].each do |name|
+ define_method name do
+ super()
+ end
+ end
+ end
+
+ sub.new.a.should == "a"
+ sub.new.b.should == "b"
+ sub.new.a.should == "a"
+ end
+
+ it "raises a RuntimeError when called with implicit arguments from a method defined with define_method" do
+ super_class = Class.new do
+ def a(arg)
+ arg
+ end
+ end
+
+ klass = Class.new super_class do
+ define_method :a do |arg|
+ super
+ end
+ end
+
+ -> { klass.new.a(:a_called) }.should raise_error(RuntimeError)
+ end
+
+ # Rubinius ticket github#157
+ it "calls method_missing when a superclass method is not found" do
+ SuperSpecs::MM_B.new.is_a?(Hash).should == false
+ end
+
+ # Rubinius ticket github#180
+ it "respects the original module a method is aliased from" do
+ SuperSpecs::Alias3.new.name3.should == [:alias2, :alias1]
+ end
+
+ it "sees the included version of a module a method is alias from" do
+ SuperSpecs::AliasWithSuper::Trigger.foo.should == [:b, :a]
+ end
+
+ it "find super from a singleton class" do
+ obj = SuperSpecs::SingletonCase::Foo.new
+ def obj.foobar(array)
+ array << :singleton
+ super
+ end
+ obj.foobar([]).should == [:singleton, :foo, :base]
+ end
+
+ it "finds super on other objects if a singleton class aliased the method" do
+ orig_obj = SuperSpecs::SingletonAliasCase::Foo.new
+ orig_obj.alias_on_singleton
+ orig_obj.new_foobar([]).should == [:foo, :base]
+ SuperSpecs::SingletonAliasCase::Foo.new.foobar([]).should == [:foo, :base]
+ end
+
+ it "passes along modified rest args when they weren't originally empty" do
+ SuperSpecs::RestArgsWithSuper::B.new.a("bar").should == ["bar", "foo"]
+ end
+
+ it "passes along modified rest args when they were originally empty" do
+ SuperSpecs::RestArgsWithSuper::B.new.a.should == ["foo"]
+ end
+
+ # https://bugs.ruby-lang.org/issues/14279
+ it "passes along reassigned rest args" do
+ SuperSpecs::ZSuperWithRestReassigned::B.new.a("bar").should == ["foo"]
+ end
+
+ # https://bugs.ruby-lang.org/issues/14279
+ it "wraps into array and passes along reassigned rest args with non-array scalar value" do
+ SuperSpecs::ZSuperWithRestReassignedWithScalar::B.new.a("bar").should == ["foo"]
+ end
+
+ it "invokes methods from a chain of anonymous modules" do
+ SuperSpecs::AnonymousModuleIncludedTwice.new.a([]).should == ["anon", "anon", "non-anon"]
+ end
+
+ it "without explicit arguments can accept a block but still pass the original arguments" do
+ SuperSpecs::ZSuperWithBlock::B.new.a.should == 14
+ end
+
+ it "passes along block via reference to method expecting a reference" do
+ SuperSpecs::ZSuperWithBlock::B.new.b.should == [14, 15]
+ end
+
+ it "passes along a block via reference to a method that yields" do
+ SuperSpecs::ZSuperWithBlock::B.new.c.should == 16
+ end
+
+ it "without explicit arguments passes optional arguments that have a default value" do
+ SuperSpecs::ZSuperWithOptional::B.new.m(1, 2).should == 14
+ end
+
+ it "without explicit arguments passes optional arguments that have a non-default value" do
+ SuperSpecs::ZSuperWithOptional::B.new.m(1, 2, 3).should == 3
+ end
+
+ it "without explicit arguments passes optional arguments that have a default value but were modified" do
+ SuperSpecs::ZSuperWithOptional::C.new.m(1, 2).should == 100
+ end
+
+ it "without explicit arguments passes optional arguments that have a non-default value but were modified" do
+ SuperSpecs::ZSuperWithOptional::C.new.m(1, 2, 3).should == 100
+ end
+
+ it "without explicit arguments passes rest arguments" do
+ SuperSpecs::ZSuperWithRest::B.new.m(1, 2, 3).should == [1, 2, 3]
+ end
+
+ it "without explicit arguments passes rest arguments including any modifications" do
+ SuperSpecs::ZSuperWithRest::B.new.m_modified(1, 2, 3).should == [1, 14, 3]
+ end
+
+ it "without explicit arguments passes arguments and rest arguments" do
+ SuperSpecs::ZSuperWithRestAndOthers::B.new.m(1, 2, 3, 4, 5).should == [3, 4, 5]
+ SuperSpecs::ZSuperWithRestAndOthers::B.new.m(1, 2).should == []
+ end
+
+ it "without explicit arguments passes arguments, rest arguments, and post arguments" do
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m(1, 2, 3, 4, 5).should == [1, 2, 3]
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m(1, 2, 3, 4, 5).should == [2, 3, 4]
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m(1, 2).should == []
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m(1, 2).should == []
+ end
+
+ it "without explicit arguments passes arguments, rest arguments including modifications, and post arguments" do
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m_modified(1, 2, 3, 4, 5).should == [1, 14, 3]
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m_modified(1, 2, 3, 4, 5).should == [2, 14, 4]
+ SuperSpecs::ZSuperWithRestAndPost::B.new.m_modified(1, 2).should == [nil, 14]
+ SuperSpecs::ZSuperWithRestOthersAndPost::B.new.m_modified(1, 2).should == [nil, 14]
+ end
+
+ it "without explicit arguments passes arguments and rest arguments including any modifications" do
+ SuperSpecs::ZSuperWithRestAndOthers::B.new.m_modified(1, 2, 3, 4, 5).should == [3, 14, 5]
+ end
+
+ it "without explicit arguments that are '_'" do
+ SuperSpecs::ZSuperWithUnderscores::B.new.m(1, 2).should == [1, 2]
+ end
+
+ it "without explicit arguments that are '_' including any modifications" do
+ SuperSpecs::ZSuperWithUnderscores::B.new.m_modified(1, 2).should == [14, 2]
+ end
+
+ describe 'when using keyword arguments' do
+ before :each do
+ @req = SuperSpecs::Keywords::RequiredArguments.new
+ @opts = SuperSpecs::Keywords::OptionalArguments.new
+ @etc = SuperSpecs::Keywords::PlaceholderArguments.new
+
+ @req_and_opts = SuperSpecs::Keywords::RequiredAndOptionalArguments.new
+ @req_and_etc = SuperSpecs::Keywords::RequiredAndPlaceholderArguments.new
+ @opts_and_etc = SuperSpecs::Keywords::OptionalAndPlaceholderArguments.new
+
+ @req_and_opts_and_etc = SuperSpecs::Keywords::RequiredAndOptionalAndPlaceholderArguments.new
+ end
+
+ it 'does not pass any arguments to the parent when none are given' do
+ @etc.foo.should == {}
+ end
+
+ it 'passes only required arguments to the parent when no optional arguments are given' do
+ [@req, @req_and_etc].each do |obj|
+ obj.foo(a: 'a').should == {a: 'a'}
+ end
+ end
+
+ it 'passes default argument values to the parent' do
+ [@opts, @opts_and_etc].each do |obj|
+ obj.foo.should == {b: 'b'}
+ end
+
+ [@req_and_opts, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo(a: 'a').should == {a: 'a', b: 'b'}
+ end
+ end
+
+ it 'passes any given arguments including optional keyword arguments to the parent' do
+ [@etc, @req_and_opts, @req_and_etc, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo(a: 'a', b: 'b').should == {a: 'a', b: 'b'}
+ end
+
+ [@etc, @req_and_etc, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo(a: 'a', b: 'b', c: 'c').should == {a: 'a', b: 'b', c: 'c'}
+ end
+ end
+ end
+
+ describe 'when using regular and keyword arguments' do
+ before :each do
+ @req = SuperSpecs::RegularAndKeywords::RequiredArguments.new
+ @opts = SuperSpecs::RegularAndKeywords::OptionalArguments.new
+ @etc = SuperSpecs::RegularAndKeywords::PlaceholderArguments.new
+
+ @req_and_opts = SuperSpecs::RegularAndKeywords::RequiredAndOptionalArguments.new
+ @req_and_etc = SuperSpecs::RegularAndKeywords::RequiredAndPlaceholderArguments.new
+ @opts_and_etc = SuperSpecs::RegularAndKeywords::OptionalAndPlaceholderArguments.new
+
+ @req_and_opts_and_etc = SuperSpecs::RegularAndKeywords::RequiredAndOptionalAndPlaceholderArguments.new
+ end
+
+ it 'passes only required regular arguments to the parent when no optional keyword arguments are given' do
+ @etc.foo('a').should == ['a', {}]
+ end
+
+ it 'passes only required regular and keyword arguments to the parent when no optional keyword arguments are given' do
+ [@req, @req_and_etc].each do |obj|
+ obj.foo('a', b: 'b').should == ['a', {b: 'b'}]
+ end
+ end
+
+ it 'passes default argument values to the parent' do
+ [@opts, @opts_and_etc].each do |obj|
+ obj.foo('a').should == ['a', {c: 'c'}]
+ end
+
+ [@req_and_opts, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo('a', b: 'b').should == ['a', {b: 'b', c: 'c'}]
+ end
+ end
+
+ it 'passes any given regular and keyword arguments including optional keyword arguments to the parent' do
+ [@etc, @req_and_opts, @req_and_etc, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo('a', b: 'b', c: 'c').should == ['a', {b: 'b', c: 'c'}]
+ end
+
+ [@etc, @req_and_etc, @opts_and_etc, @req_and_opts_and_etc].each do |obj|
+ obj.foo('a', b: 'b', c: 'c', d: 'd').should == ['a', {b: 'b', c: 'c', d: 'd'}]
+ end
+ end
+ end
+
+ describe 'when using splat and keyword arguments' do
+ before :each do
+ @all = SuperSpecs::SplatAndKeywords::AllArguments.new
+ end
+
+ it 'does not pass any arguments to the parent when none are given' do
+ @all.foo.should == [[], {}]
+ end
+
+ it 'passes only splat arguments to the parent when no keyword arguments are given' do
+ @all.foo('a').should == [['a'], {}]
+ end
+
+ it 'passes only keyword arguments to the parent when no splat arguments are given' do
+ @all.foo(b: 'b').should == [[], {b: 'b'}]
+ end
+
+ it 'passes any given splat and keyword arguments to the parent' do
+ @all.foo('a', b: 'b').should == [['a'], {b: 'b'}]
+ end
+ end
+end
diff --git a/spec/ruby/language/symbol_spec.rb b/spec/ruby/language/symbol_spec.rb
new file mode 100644
index 0000000000..d6a41d3059
--- /dev/null
+++ b/spec/ruby/language/symbol_spec.rb
@@ -0,0 +1,106 @@
+require_relative '../spec_helper'
+
+describe "A Symbol literal" do
+ it "is a ':' followed by any number of valid characters" do
+ a = :foo
+ a.should be_kind_of(Symbol)
+ a.inspect.should == ':foo'
+ end
+
+ it "is a ':' followed by any valid variable, method, or constant name" do
+ # Add more of these?
+ [ :Foo,
+ :foo,
+ :@foo,
+ :@@foo,
+ :$foo,
+ :_,
+ :~,
+ :- ,
+ :FOO,
+ :_Foo,
+ :&,
+ :_9
+ ].each { |s| s.should be_kind_of(Symbol) }
+ end
+
+ it "is a ':' followed by a single- or double-quoted string that may contain otherwise invalid characters" do
+ [ [:'foo bar', ':"foo bar"'],
+ [:'++', ':"++"'],
+ [:'9', ':"9"'],
+ [:"foo #{1 + 1}", ':"foo 2"'],
+ [:"foo\nbar", ':"foo\nbar"'],
+ ].each { |sym, str|
+ sym.should be_kind_of(Symbol)
+ sym.inspect.should == str
+ }
+ end
+
+ it 'inherits the encoding of the magic comment and can have a binary encoding' do
+ ruby_exe(fixture(__FILE__, "binary_symbol.rb"))
+ .should == "[105, 108, 95, 195, 169, 116, 97, 105, 116]\n#{Encoding::BINARY.name}\n"
+ end
+
+ it "may contain '::' in the string" do
+ :'Some::Class'.should be_kind_of(Symbol)
+ end
+
+ it "is converted to a literal, unquoted representation if the symbol contains only valid characters" do
+ a, b, c = :'foo', :'+', :'Foo__9'
+ a.should be_kind_of(Symbol)
+ a.inspect.should == ':foo'
+ b.should be_kind_of(Symbol)
+ b.inspect.should == ':+'
+ c.should be_kind_of(Symbol)
+ c.inspect.should == ':Foo__9'
+ end
+
+ it "can be created by the %s-delimited expression" do
+ a, b = :'foo bar', %s{foo bar}
+ b.should be_kind_of(Symbol)
+ b.inspect.should == ':"foo bar"'
+ b.should == a
+ end
+
+ it "is the same object when created from identical strings" do
+ var = "@@var"
+ [ [:symbol, :symbol],
+ [:'a string', :'a string'],
+ [:"#{var}", :"#{var}"]
+ ].each { |a, b|
+ a.should equal(b)
+ }
+ end
+
+ it "can contain null in the string" do
+ eval(':"\0" ').inspect.should == ':"\\x00"'
+ end
+
+ it "can be an empty string" do
+ c = :''
+ c.should be_kind_of(Symbol)
+ c.inspect.should == ':""'
+ end
+
+ it "can be :!, :!=, or :!~" do
+ %w{'!', '!=', '!~'}.each do |sym|
+ sym.to_sym.to_s.should == sym
+ end
+ end
+
+ it "can be created from list syntax %i{a b c} without interpolation" do
+ %i{a b #{c}}.should == [:a, :b, :"\#{c}"]
+ end
+
+ it "can be created from list syntax %I{a b c} with interpolation" do
+ %I{a b #{"c"}}.should == [:a, :b, :c]
+ end
+
+ it "with invalid bytes raises an EncodingError at parse time" do
+ ScratchPad.record []
+ -> {
+ eval 'ScratchPad << 1; :"\xC3"'
+ }.should raise_error(EncodingError, /invalid/)
+ ScratchPad.recorded.should == []
+ end
+end
diff --git a/spec/ruby/language/throw_spec.rb b/spec/ruby/language/throw_spec.rb
new file mode 100644
index 0000000000..d723843688
--- /dev/null
+++ b/spec/ruby/language/throw_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../spec_helper'
+
+describe "The throw keyword" do
+ it "abandons processing" do
+ i = 0
+ catch(:done) do
+ loop do
+ i += 1
+ throw :done if i > 4
+ end
+ i += 1
+ end
+ i.should == 5
+ end
+
+ it "supports a second parameter" do
+ msg = catch(:exit) do
+ throw :exit,:msg
+ end
+ msg.should == :msg
+ end
+
+ it "uses nil as a default second parameter" do
+ msg = catch(:exit) do
+ throw :exit
+ end
+ msg.should == nil
+ end
+
+ it "clears the current exception" do
+ catch :exit do
+ begin
+ raise "exception"
+ rescue
+ throw :exit
+ end
+ end
+ $!.should be_nil
+ end
+
+ it "allows any object as its argument" do
+ catch(1) { throw 1, 2 }.should == 2
+ o = Object.new
+ catch(o) { throw o, o }.should == o
+ end
+
+ it "does not convert strings to a symbol" do
+ -> { catch(:exit) { throw "exit" } }.should raise_error(ArgumentError)
+ end
+
+ it "unwinds stack from within a method" do
+ def throw_method(handler, val)
+ throw handler, val
+ end
+
+ catch(:exit) do
+ throw_method(:exit, 5)
+ end.should == 5
+ end
+
+ it "unwinds stack from within a lambda" do
+ c = -> { throw :foo, :msg }
+ catch(:foo) { c.call }.should == :msg
+ end
+
+ it "raises an ArgumentError if outside of scope of a matching catch" do
+ -> { throw :test, 5 }.should raise_error(ArgumentError)
+ -> { catch(:different) { throw :test, 5 } }.should raise_error(ArgumentError)
+ end
+
+ it "raises an UncaughtThrowError if used to exit a thread" do
+ catch(:what) do
+ t = Thread.new {
+ -> {
+ throw :what
+ }.should raise_error(UncaughtThrowError)
+ }
+ t.join
+ end
+ end
+end
diff --git a/spec/ruby/language/undef_spec.rb b/spec/ruby/language/undef_spec.rb
new file mode 100644
index 0000000000..4e473b803f
--- /dev/null
+++ b/spec/ruby/language/undef_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../spec_helper'
+
+describe "The undef keyword" do
+ describe "undefines a method" do
+ before :each do
+ @undef_class = Class.new do
+ def meth(o); o; end
+ end
+ @obj = @undef_class.new
+ @obj.meth(5).should == 5
+ end
+
+ it "with an identifier" do
+ @undef_class.class_eval do
+ undef meth
+ end
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a simple symbol" do
+ @undef_class.class_eval do
+ undef :meth
+ end
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a single quoted symbol" do
+ @undef_class.class_eval do
+ undef :'meth'
+ end
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a double quoted symbol" do
+ @undef_class.class_eval do
+ undef :"meth"
+ end
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+
+ it "with a interpolated symbol" do
+ @undef_class.class_eval do
+ undef :"#{'meth'}"
+ end
+ -> { @obj.meth(5) }.should raise_error(NoMethodError)
+ end
+ end
+
+ it "allows undefining multiple methods at a time" do
+ undef_multiple = Class.new do
+ def method1; end
+ def method2; :nope; end
+
+ undef :method1, :method2
+ end
+
+ obj = undef_multiple.new
+ obj.respond_to?(:method1).should == false
+ obj.respond_to?(:method2).should == false
+ end
+
+ it "raises a NameError when passed a missing name" do
+ Class.new do
+ -> {
+ undef not_exist
+ }.should raise_error(NameError) { |e|
+ # a NameError and not a NoMethodError
+ e.class.should == NameError
+ }
+ end
+ end
+end
diff --git a/spec/ruby/language/unless_spec.rb b/spec/ruby/language/unless_spec.rb
new file mode 100644
index 0000000000..98acdc083b
--- /dev/null
+++ b/spec/ruby/language/unless_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../spec_helper'
+
+describe "The unless expression" do
+ it "evaluates the unless body when the expression is false" do
+ unless false
+ a = true
+ else
+ a = false
+ end
+
+ a.should == true
+ end
+
+ it "returns the last statement in the body" do
+ unless false
+ 'foo'
+ 'bar'
+ 'baz'
+ end.should == 'baz'
+ end
+
+ it "evaluates the else body when the expression is true" do
+ unless true
+ 'foo'
+ else
+ 'bar'
+ end.should == 'bar'
+ end
+
+ it "takes an optional then after the expression" do
+ unless false then
+ 'baz'
+ end.should == 'baz'
+ end
+
+ it "does not return a value when the expression is true" do
+ unless true; end.should == nil
+ end
+
+ it "allows expression and body to be on one line (using 'then')" do
+ unless false then 'foo'; else 'bar'; end.should == 'foo'
+ end
+end
diff --git a/spec/ruby/language/until_spec.rb b/spec/ruby/language/until_spec.rb
new file mode 100644
index 0000000000..78c289ff56
--- /dev/null
+++ b/spec/ruby/language/until_spec.rb
@@ -0,0 +1,234 @@
+require_relative '../spec_helper'
+
+# until bool-expr [do]
+# body
+# end
+#
+# begin
+# body
+# end until bool-expr
+#
+# expr until bool-expr
+describe "The until expression" do
+ it "runs while the expression is false" do
+ i = 0
+ until i > 9
+ i += 1
+ end
+
+ i.should == 10
+ end
+
+ it "optionally takes a 'do' after the expression" do
+ i = 0
+ until i > 9 do
+ i += 1
+ end
+
+ i.should == 10
+ end
+
+ it "allows body begin on the same line if do is used" do
+ i = 0
+ until i > 9 do i += 1
+ end
+
+ i.should == 10
+ end
+
+ it "executes code in containing variable scope" do
+ i = 0
+ until i == 1
+ a = 123
+ i = 1
+ end
+
+ a.should == 123
+ end
+
+ it "executes code in containing variable scope with 'do'" do
+ i = 0
+ until i == 1 do
+ a = 123
+ i = 1
+ end
+
+ a.should == 123
+ end
+
+ it "returns nil if ended when condition became true" do
+ i = 0
+ until i > 9
+ i += 1
+ end.should == nil
+ end
+
+ it "evaluates the body if expression is empty" do
+ a = []
+ until ()
+ a << :body_evaluated
+ break
+ end
+ a.should == [:body_evaluated]
+ end
+
+ it "stops running body if interrupted by break" do
+ i = 0
+ until i > 9
+ i += 1
+ break if i > 5
+ end
+ i.should == 6
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ until false
+ break 123
+ end.should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ until false
+ break
+ end.should == nil
+ end
+
+ it "skips to end of body with next" do
+ a = []
+ i = 0
+ until (i+=1)>=5
+ next if i==3
+ a << i
+ end
+ a.should == [1, 2, 4]
+ end
+
+ it "restarts the current iteration without reevaluating condition with redo" do
+ a = []
+ i = 0
+ j = 0
+ until (i+=1)>=3
+ a << i
+ j+=1
+ redo if j<3
+ end
+ a.should == [1, 1, 1, 2]
+ end
+end
+
+describe "The until modifier" do
+ it "runs preceding statement while the condition is false" do
+ i = 0
+ i += 1 until i > 9
+ i.should == 10
+ end
+
+ it "evaluates condition before statement execution" do
+ a = []
+ i = 0
+ a << i until (i+=1) >= 3
+ a.should == [1, 2]
+ end
+
+ it "does not run preceding statement if the condition is true" do
+ i = 0
+ i += 1 until true
+ i.should == 0
+ end
+
+ it "returns nil if ended when condition became true" do
+ i = 0
+ (i += 1 until i>9).should == nil
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ (break 123 until false).should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ (break until false).should == nil
+ end
+
+ it "skips to end of body with next" do
+ i = 0
+ j = 0
+ ((i+=1) == 3 ? next : j+=i) until i > 10
+ j.should == 63
+ end
+
+ it "restarts the current iteration without reevaluating condition with redo" do
+ i = 0
+ j = 0
+ (i+=1) == 4 ? redo : j+=i until (i+=1) > 10
+ j.should == 34
+ end
+end
+
+describe "The until modifier with begin .. end block" do
+ it "runs block while the expression is false" do
+ i = 0
+ begin
+ i += 1
+ end until i > 9
+
+ i.should == 10
+ end
+
+ it "stops running block if interrupted by break" do
+ i = 0
+ begin
+ i += 1
+ break if i > 5
+ end until i > 9
+
+ i.should == 6
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ (begin; break 123; end until false).should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ (begin; break; end until false).should == nil
+ end
+
+ it "runs block at least once (even if the expression is true)" do
+ i = 0
+ begin
+ i += 1
+ end until true
+
+ i.should == 1
+ end
+
+ it "evaluates condition after block execution" do
+ a = []
+ i = 0
+ begin
+ a << i
+ end until (i+=1)>=5
+ a.should == [0, 1, 2, 3, 4]
+ end
+
+ it "skips to end of body with next" do
+ a = []
+ i = 0
+ begin
+ next if i==3
+ a << i
+ end until (i+=1)>=5
+ a.should == [0, 1, 2, 4]
+ end
+
+ it "restart the current iteration without reevaluating condition with redo" do
+ a = []
+ i = 0
+ j = 0
+ begin
+ a << i
+ j+=1
+ redo if j<3
+ end until (i+=1)>=3
+ a.should == [0, 0, 0, 1, 2]
+ end
+end
diff --git a/spec/ruby/language/variables_spec.rb b/spec/ruby/language/variables_spec.rb
new file mode 100644
index 0000000000..c900c03d37
--- /dev/null
+++ b/spec/ruby/language/variables_spec.rb
@@ -0,0 +1,870 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/variables'
+
+describe "Multiple assignment" do
+ context "with a single RHS value" do
+ it "assigns a simple MLHS" do
+ (a, b, c = 1).should == 1
+ [a, b, c].should == [1, nil, nil]
+ end
+
+ it "calls #to_ary to convert an Object RHS when assigning a simple MLHS" do
+ x = mock("multi-assign single RHS")
+ x.should_receive(:to_ary).and_return([1, 2])
+
+ (a, b, c = x).should == x
+ [a, b, c].should == [1, 2, nil]
+ end
+
+ it "calls #to_ary if it is private" do
+ x = mock("multi-assign single RHS")
+ x.should_receive(:to_ary).and_return([1, 2])
+ class << x; private :to_ary; end
+
+ (a, b, c = x).should == x
+ [a, b, c].should == [1, 2, nil]
+ end
+
+ it "does not call #to_ary if #respond_to? returns false" do
+ x = mock("multi-assign single RHS")
+ x.should_receive(:respond_to?).with(:to_ary, true).and_return(false)
+ x.should_not_receive(:to_ary)
+
+ (a, b, c = x).should == x
+ [a, b, c].should == [x, nil, nil]
+ end
+
+ it "wraps the Object in an Array if #to_ary returns nil" do
+ x = mock("multi-assign single RHS")
+ x.should_receive(:to_ary).and_return(nil)
+
+ (a, b, c = x).should == x
+ [a, b, c].should == [x, nil, nil]
+ end
+
+ it "raises a TypeError of #to_ary does not return an Array" do
+ x = mock("multi-assign single RHS")
+ x.should_receive(:to_ary).and_return(1)
+
+ -> { a, b, c = x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_a to convert an Object RHS when assigning a simple MLHS" do
+ x = mock("multi-assign single RHS")
+ x.should_not_receive(:to_a)
+
+ (a, b, c = x).should == x
+ [a, b, c].should == [x, nil, nil]
+ end
+
+ it "does not call #to_ary on an Array instance" do
+ x = [1, 2]
+ x.should_not_receive(:to_ary)
+
+ (a, b = x).should == x
+ [a, b].should == [1, 2]
+ end
+
+ it "does not call #to_a on an Array instance" do
+ x = [1, 2]
+ x.should_not_receive(:to_a)
+
+ (a, b = x).should == x
+ [a, b].should == [1, 2]
+ end
+
+ it "returns the RHS when it is an Array" do
+ ary = [1, 2]
+
+ x = (a, b = ary)
+ x.should equal(ary)
+ end
+
+ it "returns the RHS when it is an Array subclass" do
+ cls = Class.new(Array)
+ ary = cls.new [1, 2]
+
+ x = (a, b = ary)
+ x.should equal(ary)
+ end
+
+ it "does not call #to_ary on an Array subclass instance" do
+ x = Class.new(Array).new [1, 2]
+ x.should_not_receive(:to_ary)
+
+ (a, b = x).should == x
+ [a, b].should == [1, 2]
+ end
+
+ it "does not call #to_a on an Array subclass instance" do
+ x = Class.new(Array).new [1, 2]
+ x.should_not_receive(:to_a)
+
+ (a, b = x).should == x
+ [a, b].should == [1, 2]
+ end
+
+ it "assigns a MLHS with a trailing comma" do
+ a, = 1
+ b, c, = []
+ [a, b, c].should == [1, nil, nil]
+ end
+
+ it "assigns a single LHS splat" do
+ (*a = 1).should == 1
+ a.should == [1]
+ end
+
+ it "calls #to_ary to convert an Object RHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_ary).and_return([1, 2])
+
+ (*a = x).should == x
+ a.should == [1, 2]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_ary).and_return(1)
+
+ -> { *a = x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary on an Array subclass" do
+ cls = Class.new(Array)
+ ary = cls.new [1, 2]
+ ary.should_not_receive(:to_ary)
+
+ (*a = ary).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "assigns an Array when the RHS is an Array subclass" do
+ cls = Class.new(Array)
+ ary = cls.new [1, 2]
+
+ x = (*a = ary)
+ x.should equal(ary)
+ a.should be_an_instance_of(Array)
+ end
+
+ it "calls #to_ary to convert an Object RHS with MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_ary).and_return([1, 2])
+
+ (a, *b, c = x).should == x
+ [a, b, c].should == [1, [], 2]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array with MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_ary).and_return(1)
+
+ -> { a, *b, c = x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_a to convert an Object RHS with a MLHS" do
+ x = mock("multi-assign splat")
+ x.should_not_receive(:to_a)
+
+ (a, *b = x).should == x
+ [a, b].should == [x, []]
+ end
+
+ it "assigns a MLHS with leading splat" do
+ (*a, b, c = 1).should == 1
+ [a, b, c].should == [[], 1, nil]
+ end
+
+ it "assigns a MLHS with a middle splat" do
+ a, b, *c, d, e = 1
+ [a, b, c, d, e].should == [1, nil, [], nil, nil]
+ end
+
+ it "assigns a MLHS with a trailing splat" do
+ a, b, *c = 1
+ [a, b, c].should == [1, nil, []]
+ end
+
+ it "assigns a grouped LHS without splat" do
+ ((a, b), c), (d, (e,), (f, (g, h))) = 1
+ [a, b, c, d, e, f, g, h].should == [1, nil, nil, nil, nil, nil, nil, nil]
+ end
+
+ it "assigns a single grouped LHS splat" do
+ (*a) = nil
+ a.should == [nil]
+ end
+
+ it "assigns a grouped LHS with splats" do
+ (a, *b), c, (*d, (e, *f, g)) = 1
+ [a, b, c, d, e, f, g].should == [1, [], nil, [], nil, [], nil]
+ end
+
+ it "consumes values for an anonymous splat" do
+ (* = 1).should == 1
+ end
+
+ it "consumes values for a grouped anonymous splat" do
+ ((*) = 1).should == 1
+ end
+
+ it "does not mutate a RHS Array" do
+ x = [1, 2, 3, 4]
+ a, *b, c, d = x
+ [a, b, c, d].should == [1, [2], 3, 4]
+ x.should == [1, 2, 3, 4]
+ end
+
+ it "assigns values from a RHS method call" do
+ def x() 1 end
+
+ (a, b = x).should == 1
+ [a, b].should == [1, nil]
+ end
+
+ it "assigns values from a RHS method call with arguments" do
+ def x(a) a end
+
+ (a, b = x []).should == []
+ [a, b].should == [nil, nil]
+ end
+
+ it "assigns values from a RHS method call with receiver" do
+ x = mock("multi-assign attributes")
+ x.should_receive(:m).and_return([1, 2, 3])
+
+ a, b = x.m
+ [a, b].should == [1, 2]
+ end
+
+ it "calls #to_ary on the value returned by the method call" do
+ y = mock("multi-assign method return value")
+ y.should_receive(:to_ary).and_return([1, 2])
+
+ x = mock("multi-assign attributes")
+ x.should_receive(:m).and_return(y)
+
+ (a, b = x.m).should == y
+ [a, b].should == [1, 2]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array on a single RHS" do
+ y = mock("multi-assign method return value")
+ y.should_receive(:to_ary).and_return(1)
+
+ x = mock("multi-assign attributes")
+ x.should_receive(:m).and_return(y)
+
+ -> { a, b = x.m }.should raise_error(TypeError)
+ end
+
+ it "assigns values from a RHS method call with receiver and arguments" do
+ x = mock("multi-assign attributes")
+ x.should_receive(:m).with(1, 2).and_return([1, 2, 3])
+
+ a, b = x.m 1, 2
+ [a, b].should == [1, 2]
+ end
+
+ it "assigns global variables" do
+ $spec_a, $spec_b = 1
+ [$spec_a, $spec_b].should == [1, nil]
+ end
+
+ it "assigns instance variables" do
+ @a, @b = 1
+ [@a, @b].should == [1, nil]
+ end
+
+ it "assigns attributes" do
+ a = mock("multi-assign attributes")
+ a.should_receive(:x=).with(1)
+ a.should_receive(:y=).with(nil)
+
+ a.x, a.y = 1
+ end
+
+ it "assigns indexed elements" do
+ a = []
+ a[1], a[2] = 1
+ a.should == [nil, 1, nil]
+ end
+
+ it "assigns constants" do
+ module VariableSpecs
+ SINGLE_RHS_1, SINGLE_RHS_2 = 1
+ [SINGLE_RHS_1, SINGLE_RHS_2].should == [1, nil]
+ end
+ end
+ end
+
+ context "with a single splatted RHS value" do
+ it "assigns a single grouped LHS splat" do
+ (*a) = *1
+ a.should == [1]
+ end
+
+ it "assigns an empty Array to a single LHS value when passed nil" do
+ (a = *nil).should == []
+ a.should == []
+ end
+
+ it "calls #to_a to convert nil to an empty Array" do
+ nil.should_receive(:to_a).and_return([])
+
+ (*a = *nil).should == []
+ a.should == []
+ end
+
+ it "does not call #to_a on an Array" do
+ ary = [1, 2]
+ ary.should_not_receive(:to_a)
+
+ (a = *ary).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "returns a copy of a splatted Array" do
+ ary = [1, 2]
+
+ (a = *ary).should == [1, 2]
+ a.should_not equal(ary)
+ end
+
+ it "does not call #to_a on an Array subclass" do
+ cls = Class.new(Array)
+ ary = cls.new [1, 2]
+ ary.should_not_receive(:to_a)
+
+ (a = *ary).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "returns an Array when the splatted object is an Array subclass" do
+ cls = Class.new(Array)
+ ary = cls.new [1, 2]
+
+ x = (a = *ary)
+
+ x.should == [1, 2]
+ x.should be_an_instance_of(Array)
+
+ a.should == [1, 2]
+ a.should be_an_instance_of(Array)
+ end
+
+ it "unfreezes the array returned from calling 'to_a' on the splatted value" do
+ obj = Object.new
+ def obj.to_a
+ [1,2].freeze
+ end
+ res = *obj
+ res.should == [1,2]
+ res.should_not.frozen?
+ end
+
+ it "consumes values for an anonymous splat" do
+ a = 1
+ (* = *a).should == [1]
+ end
+
+ it "consumes values for a grouped anonymous splat" do
+ ((*) = *1).should == [1]
+ end
+
+ it "assigns a single LHS splat" do
+ x = 1
+ (*a = *x).should == [1]
+ a.should == [1]
+ end
+
+ it "calls #to_a to convert an Object RHS with a single splat LHS" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:to_a).and_return([1, 2])
+
+ (*a = *x).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "calls #to_a if it is private" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:to_a).and_return([1, 2])
+ class << x; private :to_a; end
+
+ (*a = *x).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "does not call #to_a if #respond_to? returns false" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:respond_to?).with(:to_a, true).and_return(false)
+ x.should_not_receive(:to_a)
+
+ (*a = *x).should == [x]
+ a.should == [x]
+ end
+
+ it "wraps the Object in an Array if #to_a returns nil" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:to_a).and_return(nil)
+
+ (*a = *x).should == [x]
+ a.should == [x]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { *a = *x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary to convert an Object RHS with a single splat LHS" do
+ x = mock("multi-assign RHS splat")
+ x.should_not_receive(:to_ary)
+
+ (*a = *x).should == [x]
+ a.should == [x]
+ end
+
+ it "assigns a MLHS with leading splat" do
+ (*a, b, c = *1).should == [1]
+ [a, b, c].should == [[], 1, nil]
+ end
+
+ it "assigns a MLHS with a middle splat" do
+ a, b, *c, d, e = *1
+ [a, b, c, d, e].should == [1, nil, [], nil, nil]
+ end
+
+ it "assigns a MLHS with a trailing splat" do
+ a, b, *c = *nil
+ [a, b, c].should == [nil, nil, []]
+ end
+
+ it "calls #to_a to convert an Object RHS with a single LHS" do
+ x = mock("multi-assign RHS splat")
+ x.should_receive(:to_a).and_return([1, 2])
+
+ (a = *x).should == [1, 2]
+ a.should == [1, 2]
+ end
+
+ it "does not call #to_ary to convert an Object RHS with a single LHS" do
+ x = mock("multi-assign RHS splat")
+ x.should_not_receive(:to_ary)
+
+ (a = *x).should == [x]
+ a.should == [x]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array with a single LHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { a = *x }.should raise_error(TypeError)
+ end
+
+ it "calls #to_a to convert an Object splat RHS when assigned to a simple MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_a).and_return([1, 2])
+
+ (a, b, c = *x).should == [1, 2]
+ [a, b, c].should == [1, 2, nil]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array with a simple MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { a, b, c = *x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary to convert an Object splat RHS when assigned to a simple MLHS" do
+ x = mock("multi-assign splat")
+ x.should_not_receive(:to_ary)
+
+ (a, b, c = *x).should == [x]
+ [a, b, c].should == [x, nil, nil]
+ end
+
+ it "calls #to_a to convert an Object RHS with MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_a).and_return([1, 2])
+
+ a, *b, c = *x
+ [a, b, c].should == [1, [], 2]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array with MLHS" do
+ x = mock("multi-assign splat")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { a, *b, c = *x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary to convert an Object RHS with a MLHS" do
+ x = mock("multi-assign splat")
+ x.should_not_receive(:to_ary)
+
+ a, *b = *x
+ [a, b].should == [x, []]
+ end
+
+ it "assigns a grouped LHS without splats" do
+ ((a, b), c), (d, (e,), (f, (g, h))) = *1
+ [a, b, c, d, e, f, g, h].should == [1, nil, nil, nil, nil, nil, nil, nil]
+ end
+
+ it "assigns a grouped LHS with splats" do
+ (a, *b), c, (*d, (e, *f, g)) = *1
+ [a, b, c, d, e, f, g].should == [1, [], nil, [], nil, [], nil]
+ end
+
+ it "does not mutate a RHS Array" do
+ x = [1, 2, 3, 4]
+ a, *b, c, d = *x
+ [a, b, c, d].should == [1, [2], 3, 4]
+ x.should == [1, 2, 3, 4]
+ end
+
+ it "assigns constants" do
+ module VariableSpecs
+ (*SINGLE_SPLATTED_RHS) = *1
+ SINGLE_SPLATTED_RHS.should == [1]
+ end
+ end
+ end
+
+ context "with a MRHS value" do
+ it "consumes values for an anonymous splat" do
+ (* = 1, 2, 3).should == [1, 2, 3]
+ end
+
+ it "consumes values for a grouped anonymous splat" do
+ ((*) = 1, 2, 3).should == [1, 2, 3]
+ end
+
+ it "consumes values for multiple '_' variables" do
+ a, _, b, _, c = 1, 2, 3, 4, 5
+ [a, b, c].should == [1, 3, 5]
+ end
+
+ it "does not call #to_a to convert an Object in a MRHS" do
+ x = mock("multi-assign MRHS")
+ x.should_not_receive(:to_a)
+
+ (a, b = 1, x).should == [1, x]
+ [a, b].should == [1, x]
+ end
+
+ it "does not call #to_ary to convert an Object in a MRHS" do
+ x = mock("multi-assign MRHS")
+ x.should_not_receive(:to_ary)
+
+ (a, b = 1, x).should == [1, x]
+ [a, b].should == [1, x]
+ end
+
+ it "calls #to_a to convert a splatted Object as part of a MRHS with a splat MLHS" do
+ x = mock("multi-assign splat MRHS")
+ x.should_receive(:to_a).and_return([3, 4])
+
+ (a, *b = 1, *x).should == [1, 3, 4]
+ [a, b].should == [1, [3, 4]]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array with a splat MLHS" do
+ x = mock("multi-assign splat MRHS")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { a, *b = 1, *x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary to convert a splatted Object as part of a MRHS with a splat MRHS" do
+ x = mock("multi-assign splat MRHS")
+ x.should_not_receive(:to_ary)
+
+ (a, *b = 1, *x).should == [1, x]
+ [a, b].should == [1, [x]]
+ end
+
+ it "calls #to_a to convert a splatted Object as part of a MRHS" do
+ x = mock("multi-assign splat MRHS")
+ x.should_receive(:to_a).and_return([3, 4])
+
+ (a, *b = *x, 1).should == [3, 4, 1]
+ [a, b].should == [3, [4, 1]]
+ end
+
+ it "raises a TypeError if #to_a does not return an Array with a splat MRHS" do
+ x = mock("multi-assign splat MRHS")
+ x.should_receive(:to_a).and_return(1)
+
+ -> { a, *b = *x, 1 }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary to convert a splatted Object with a splat MRHS" do
+ x = mock("multi-assign splat MRHS")
+ x.should_not_receive(:to_ary)
+
+ (a, *b = *x, 1).should == [x, 1]
+ [a, b].should == [x, [1]]
+ end
+
+ it "assigns a grouped LHS without splat from a simple Array" do
+ ((a, b), c), (d, (e,), (f, (g, h))) = 1, 2, 3, 4, 5
+ [a, b, c, d, e, f, g, h].should == [1, nil, nil, 2, nil, nil, nil, nil]
+ end
+
+ it "assigns a grouped LHS without splat from nested Arrays" do
+ ary = [[1, 2, 3], 4], [[5], [6, 7], [8, [9, 10]]]
+ ((a, b), c), (d, (e,), (f, (g, h))) = ary
+ [a, b, c, d, e, f, g, h].should == [1, 2, 4, [5], 6, 8, 9, 10]
+ end
+
+ it "assigns a single grouped LHS splat" do
+ (*a) = 1, 2, 3
+ a.should == [1, 2, 3]
+ end
+
+ it "assigns a grouped LHS with splats from nested Arrays for simple values" do
+ (a, *b), c, (*d, (e, *f, g)) = 1, 2, 3, 4
+ [a, b, c, d, e, f, g].should == [1, [], 2, [], 3, [], nil]
+ end
+
+ it "assigns a grouped LHS with splats from nested Arrays for nested arrays" do
+ (a, *b), c, (*d, (e, *f, g)) = [1, [2, 3]], [4, 5], [6, 7, 8]
+ [a, b, c, d, e, f, g].should == [1, [[2, 3]], [4, 5], [6, 7], 8, [], nil]
+ end
+
+ it "calls #to_ary to convert an Object when the position receiving the value is a multiple assignment" do
+ x = mock("multi-assign mixed RHS")
+ x.should_receive(:to_ary).and_return([1, 2])
+
+ (a, (b, c), d, e = 1, x, 3, 4).should == [1, x, 3, 4]
+ [a, b, c, d, e].should == [1, 1, 2, 3, 4]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ x = mock("multi-assign mixed RHS")
+ x.should_receive(:to_ary).and_return(x)
+
+ -> { a, (b, c), d = 1, x, 3, 4 }.should raise_error(TypeError)
+ end
+
+ it "calls #to_a to convert a splatted Object value in a MRHS" do
+ x = mock("multi-assign mixed splatted RHS")
+ x.should_receive(:to_a).and_return([4, 5])
+
+ (a, *b, (c, d) = 1, 2, 3, *x).should == [1, 2, 3, 4, 5]
+ [a, b, c, d].should == [1, [2, 3, 4], 5, nil]
+
+ end
+
+ it "calls #to_ary to convert a splatted Object when the position receiving the value is a multiple assignment" do
+ x = mock("multi-assign mixed splatted RHS")
+ x.should_receive(:to_ary).and_return([4, 5])
+
+ (a, *b, (c, d) = 1, 2, 3, *x).should == [1, 2, 3, x]
+ [a, b, c, d].should == [1, [2, 3], 4, 5]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array in a MRHS" do
+ x = mock("multi-assign mixed splatted RHS")
+ x.should_receive(:to_ary).and_return(x)
+
+ -> { a, *b, (c, d) = 1, 2, 3, *x }.should raise_error(TypeError)
+ end
+
+ it "does not call #to_ary to convert an Object when the position receiving the value is a simple variable" do
+ x = mock("multi-assign mixed RHS")
+ x.should_not_receive(:to_ary)
+
+ a, b, c, d = 1, x, 3, 4
+ [a, b, c, d].should == [1, x, 3, 4]
+ end
+
+ it "does not call #to_ary to convert an Object when the position receiving the value is a rest variable" do
+ x = mock("multi-assign mixed RHS")
+ x.should_not_receive(:to_ary)
+
+ a, *b, c, d = 1, x, 3, 4
+ [a, b, c, d].should == [1, [x], 3, 4]
+ end
+
+ it "does not call #to_ary to convert a splatted Object when the position receiving the value is a simple variable" do
+ x = mock("multi-assign mixed splatted RHS")
+ x.should_not_receive(:to_ary)
+
+ a, *b, c = 1, 2, *x
+ [a, b, c].should == [1, [2], x]
+ end
+
+ it "does not call #to_ary to convert a splatted Object when the position receiving the value is a rest variable" do
+ x = mock("multi-assign mixed splatted RHS")
+ x.should_not_receive(:to_ary)
+
+ a, b, *c = 1, 2, *x
+ [a, b, c].should == [1, 2, [x]]
+ end
+
+ it "does not mutate the assigned Array" do
+ x = ((a, *b, c, d) = 1, 2, 3, 4, 5)
+ x.should == [1, 2, 3, 4, 5]
+ end
+
+ it "can be used to swap array elements" do
+ a = [1, 2]
+ a[0], a[1] = a[1], a[0]
+ a.should == [2, 1]
+ end
+
+ it "can be used to swap range of array elements" do
+ a = [1, 2, 3, 4]
+ a[0, 2], a[2, 2] = a[2, 2], a[0, 2]
+ a.should == [3, 4, 1, 2]
+ end
+
+ it "assigns RHS values to LHS constants" do
+ module VariableSpecs
+ MRHS_VALUES_1, MRHS_VALUES_2 = 1, 2
+ MRHS_VALUES_1.should == 1
+ MRHS_VALUES_2.should == 2
+ end
+ end
+
+ it "assigns all RHS values as an array to a single LHS constant" do
+ module VariableSpecs
+ MRHS_VALUES = 1, 2, 3
+ MRHS_VALUES.should == [1, 2, 3]
+ end
+ end
+ end
+
+ context "with a RHS assignment value" do
+ it "consumes values for an anonymous splat" do
+ (* = (a = 1)).should == 1
+ a.should == 1
+ end
+
+ it "does not mutate a RHS Array" do
+ a, *b, c, d = (e = [1, 2, 3, 4])
+ [a, b, c, d].should == [1, [2], 3, 4]
+ e.should == [1, 2, 3, 4]
+ end
+ end
+end
+
+describe "A local variable assigned only within a conditional block" do
+ context "accessed from a later closure" do
+ it "is defined?" do
+ if VariablesSpecs.false
+ a = 1
+ end
+
+ 1.times do
+ defined?(a).should == "local-variable"
+ end
+ end
+
+ it "is nil" do
+ if VariablesSpecs.false
+ a = 1
+ end
+
+ 1.times do
+ a.inspect.should == "nil"
+ end
+ end
+ end
+end
+
+describe 'Local variable shadowing' do
+ it "does not warn in verbose mode" do
+ result = nil
+
+ -> do
+ eval <<-CODE
+ a = [1, 2, 3]
+ result = a.map { |a| a = 3 }
+ CODE
+ end.should_not complain(verbose: true)
+
+ result.should == [3, 3, 3]
+ end
+end
+
+describe 'Allowed characters' do
+ it 'allows non-ASCII lowercased characters at the beginning' do
+ result = nil
+
+ eval <<-CODE
+ def test
+ μ = 1
+ end
+
+ result = test
+ CODE
+
+ result.should == 1
+ end
+
+ it 'parses a non-ASCII upcased character as a constant identifier' do
+ -> do
+ eval <<-CODE
+ def test
+ á¼BB = 1
+ end
+ CODE
+ end.should raise_error(SyntaxError, /dynamic constant assignment/)
+ end
+end
+
+describe "Instance variables" do
+ context "when instance variable is uninitialized" do
+ ruby_version_is ""..."3.0" do
+ it "warns about accessing uninitialized instance variable" do
+ obj = Object.new
+ def obj.foobar; a = @a; end
+
+ -> { obj.foobar }.should complain(/warning: instance variable @a not initialized/, verbose: true)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "doesn't warn about accessing uninitialized instance variable" do
+ obj = Object.new
+ def obj.foobar; a = @a; end
+
+ -> { obj.foobar }.should_not complain(verbose: true)
+ end
+ end
+
+ it "doesn't warn at lazy initialization" do
+ obj = Object.new
+ def obj.foobar; @a ||= 42; end
+
+ -> { obj.foobar }.should_not complain(verbose: true)
+ end
+ end
+
+ describe "global variable" do
+ context "when global variable is uninitialized" do
+ it "warns about accessing uninitialized global variable in verbose mode" do
+ obj = Object.new
+ def obj.foobar; a = $specs_uninitialized_global_variable; end
+
+ -> { obj.foobar }.should complain(/warning: global variable `\$specs_uninitialized_global_variable' not initialized/, verbose: true)
+ end
+
+ it "doesn't warn at lazy initialization" do
+ obj = Object.new
+ def obj.foobar; $specs_uninitialized_global_variable_lazy ||= 42; end
+
+ -> { obj.foobar }.should_not complain(verbose: true)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/language/while_spec.rb b/spec/ruby/language/while_spec.rb
new file mode 100644
index 0000000000..e172453ca6
--- /dev/null
+++ b/spec/ruby/language/while_spec.rb
@@ -0,0 +1,344 @@
+require_relative '../spec_helper'
+
+# while bool-expr [do]
+# body
+# end
+#
+# begin
+# body
+# end while bool-expr
+#
+# expr while bool-expr
+describe "The while expression" do
+ it "runs while the expression is true" do
+ i = 0
+ while i < 3
+ i += 1
+ end
+ i.should == 3
+ end
+
+ it "optionally takes a 'do' after the expression" do
+ i = 0
+ while i < 3 do
+ i += 1
+ end
+
+ i.should == 3
+ end
+
+ it "allows body begin on the same line if do is used" do
+ i = 0
+ while i < 3 do i += 1
+ end
+
+ i.should == 3
+ end
+
+ it "executes code in containing variable scope" do
+ i = 0
+ while i != 1
+ a = 123
+ i = 1
+ end
+
+ a.should == 123
+ end
+
+ it "executes code in containing variable scope with 'do'" do
+ i = 0
+ while i != 1 do
+ a = 123
+ i = 1
+ end
+
+ a.should == 123
+ end
+
+ it "returns nil if ended when condition became false" do
+ i = 0
+ while i < 3
+ i += 1
+ end.should == nil
+ end
+
+ it "does not evaluate the body if expression is empty" do
+ a = []
+ while ()
+ a << :body_evaluated
+ end
+ a.should == []
+ end
+
+ it "stops running body if interrupted by break" do
+ i = 0
+ while i < 10
+ i += 1
+ break if i > 5
+ end
+ i.should == 6
+ end
+
+ it "stops running body if interrupted by break in a parenthesized element op-assign-or value" do
+ c = true
+ a = []
+ while c
+ a[1] ||=
+ (
+ break if c
+ c = false
+ )
+ end.should be_nil
+ end
+
+ it "stops running body if interrupted by break in a begin ... end element op-assign-or value" do
+ c = true
+ a = []
+ while c
+ a[1] ||= begin
+ break if c
+ c = false
+ end
+ end.should be_nil
+ end
+
+ it "stops running body if interrupted by break in a parenthesized element op-assign value" do
+ c = true
+ a = [1, 2]
+ while c
+ a[1] +=
+ (
+ break if c
+ c = false
+ )
+ end.should be_nil
+ a.should == [1, 2]
+ end
+
+ it "stops running body if interrupted by break in a begin ... end element op-assign value" do
+ c = true
+ a = [1, 2]
+ while c
+ a[1] += begin
+ break if c
+ c = false
+ end
+ end.should be_nil
+ a.should == [1, 2]
+ end
+
+ it "stops running body if interrupted by break with unless in a parenthesized attribute op-assign-or value" do
+ a = mock("attribute assignment break")
+ a.should_receive(:m).twice.and_return(nil)
+ a.should_receive(:m=)
+
+ c = d = true
+ while c
+ a.m ||=
+ (
+ break unless d
+ d = false
+ )
+ end.should be_nil
+ end
+
+ it "stops running body if interrupted by break with unless in a begin ... end attribute op-assign-or value" do
+ a = mock("attribute assignment break")
+ a.should_receive(:m).twice.and_return(nil)
+ a.should_receive(:m=)
+
+ c = d = true
+ while c
+ a.m ||= begin
+ break unless d
+ d = false
+ end
+ end.should be_nil
+ end
+
+ it "stops running body if interrupted by break in a parenthesized attribute op-assign-or value" do
+ a = mock("attribute assignment break")
+ a.should_receive(:m).and_return(nil)
+ a.should_not_receive(:m=)
+
+ c = true
+ while c
+ a.m +=
+ (
+ break if c
+ c = false
+ )
+ end.should be_nil
+ end
+
+ it "stops running body if interrupted by break in a begin ... end attribute op-assign-or value" do
+ a = mock("attribute assignment break")
+ a.should_receive(:m).and_return(nil)
+ a.should_not_receive(:m=)
+
+ c = true
+ while c
+ a.m += begin
+ break if c
+ c = false
+ end
+ end.should be_nil
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ while true
+ break 123
+ end.should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ while true
+ break
+ end.should == nil
+ end
+
+ it "skips to end of body with next" do
+ a = []
+ i = 0
+ while (i+=1)<5
+ next if i==3
+ a << i
+ end
+ a.should == [1, 2, 4]
+ end
+
+ it "restarts the current iteration without reevaluating condition with redo" do
+ a = []
+ i = 0
+ j = 0
+ while (i+=1)<3
+ a << i
+ j+=1
+ redo if j<3
+ end
+ a.should == [1, 1, 1, 2]
+ end
+end
+
+describe "The while modifier" do
+ it "runs preceding statement while the condition is true" do
+ i = 0
+ i += 1 while i < 3
+ i.should == 3
+ end
+
+ it "evaluates condition before statement execution" do
+ a = []
+ i = 0
+ a << i while (i+=1) < 3
+ a.should == [1, 2]
+ end
+
+ it "does not run preceding statement if the condition is false" do
+ i = 0
+ i += 1 while false
+ i.should == 0
+ end
+
+ it "does not run preceding statement if the condition is empty" do
+ i = 0
+ i += 1 while ()
+ i.should == 0
+ end
+
+ it "returns nil if ended when condition became false" do
+ i = 0
+ (i += 1 while i<10).should == nil
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ (break 123 while true).should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ (break while true).should == nil
+ end
+
+ it "skips to end of body with next" do
+ i = 0
+ j = 0
+ ((i+=1) == 3 ? next : j+=i) while i <= 10
+ j.should == 63
+ end
+
+ it "restarts the current iteration without reevaluating condition with redo" do
+ i = 0
+ j = 0
+ (i+=1) == 4 ? redo : j+=i while (i+=1) <= 10
+ j.should == 34
+ end
+end
+
+describe "The while modifier with begin .. end block" do
+ it "runs block while the expression is true" do
+ i = 0
+ begin
+ i += 1
+ end while i < 3
+
+ i.should == 3
+ end
+
+ it "stops running block if interrupted by break" do
+ i = 0
+ begin
+ i += 1
+ break if i > 5
+ end while i < 10
+
+ i.should == 6
+ end
+
+ it "returns value passed to break if interrupted by break" do
+ (begin; break 123; end while true).should == 123
+ end
+
+ it "returns nil if interrupted by break with no arguments" do
+ (begin; break; end while true).should == nil
+ end
+
+ it "runs block at least once (even if the expression is false)" do
+ i = 0
+ begin
+ i += 1
+ end while false
+
+ i.should == 1
+ end
+
+ it "evaluates condition after block execution" do
+ a = []
+ i = 0
+ begin
+ a << i
+ end while (i+=1)<5
+ a.should == [0, 1, 2, 3, 4]
+ end
+
+ it "skips to end of body with next" do
+ a = []
+ i = 0
+ begin
+ next if i==3
+ a << i
+ end while (i+=1)<5
+ a.should == [0, 1, 2, 4]
+ end
+
+ it "restarts the current iteration without reevaluating condition with redo" do
+ a = []
+ i = 0
+ j = 0
+ begin
+ a << i
+ j+=1
+ redo if j<3
+ end while (i+=1)<3
+ a.should == [0, 0, 0, 1, 2]
+ end
+end
diff --git a/spec/ruby/language/yield_spec.rb b/spec/ruby/language/yield_spec.rb
new file mode 100644
index 0000000000..05d713af70
--- /dev/null
+++ b/spec/ruby/language/yield_spec.rb
@@ -0,0 +1,225 @@
+require_relative '../spec_helper'
+require_relative 'fixtures/yield'
+
+# Note that these specs use blocks defined as { |*a| ... } to capture the
+# arguments with which the block is invoked. This is slightly confusing
+# because the outer Array is a consequence of |*a| but it is necessary to
+# clearly distinguish some behaviors.
+
+describe "The yield call" do
+ before :each do
+ @y = YieldSpecs::Yielder.new
+ end
+
+ describe "taking no arguments" do
+ it "raises a LocalJumpError when the method is not passed a block" do
+ -> { @y.z }.should raise_error(LocalJumpError)
+ end
+
+ it "ignores assignment to the explicit block argument and calls the passed block" do
+ @y.ze { 42 }.should == 42
+ end
+
+ it "does not pass a named block to the block being yielded to" do
+ @y.z() { |&block| block == nil }.should == true
+ end
+ end
+
+ describe "taking a single argument" do
+ describe "when no block is given" do
+ it "raises a LocalJumpError" do
+ -> { @y.s(1) }.should raise_error(LocalJumpError)
+ end
+ end
+
+ describe "yielding to a literal block" do
+ it "passes an empty Array when the argument is an empty Array" do
+ @y.s([]) { |*a| a }.should == [[]]
+ end
+
+ it "passes nil as a value" do
+ @y.s(nil) { |*a| a }.should == [nil]
+ end
+
+ it "passes a single value" do
+ @y.s(1) { |*a| a }.should == [1]
+ end
+
+ it "passes a single, multi-value Array" do
+ @y.s([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]]
+ end
+ end
+
+ describe "yielding to a lambda" do
+ it "passes an empty Array when the argument is an empty Array" do
+ @y.s([], &-> *a { a }).should == [[]]
+ end
+
+ it "passes nil as a value" do
+ @y.s(nil, &-> *a { a }).should == [nil]
+ end
+
+ it "passes a single value" do
+ @y.s(1, &-> *a { a }).should == [1]
+ end
+
+ it "passes a single, multi-value Array" do
+ @y.s([1, 2, 3], &-> *a { a }).should == [[1, 2, 3]]
+ end
+
+ it "raises an ArgumentError if too few arguments are passed" do
+ -> {
+ @y.s(1, &-> a, b { [a,b] })
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should not destructure an Array into multiple arguments" do
+ -> {
+ @y.s([1, 2], &-> a, b { [a,b] })
+ }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe "taking multiple arguments" do
+ it "raises a LocalJumpError when the method is not passed a block" do
+ -> { @y.m(1, 2, 3) }.should raise_error(LocalJumpError)
+ end
+
+ it "passes the arguments to the block" do
+ @y.m(1, 2, 3) { |*a| a }.should == [1, 2, 3]
+ end
+
+ it "passes only the first argument if the block takes one parameter" do
+ @y.m(1, 2, 3) { |a| a }.should == 1
+ end
+
+ it "raises an ArgumentError if too many arguments are passed to a lambda" do
+ -> {
+ @y.m(1, 2, 3, &-> a { })
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if too few arguments are passed to a lambda" do
+ -> {
+ @y.m(1, 2, 3, &-> a, b, c, d { })
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "taking a single splatted argument" do
+ it "raises a LocalJumpError when the method is not passed a block" do
+ -> { @y.r(0) }.should raise_error(LocalJumpError)
+ end
+
+ it "passes a single value" do
+ @y.r(1) { |*a| a }.should == [1]
+ end
+
+ it "passes no arguments when the argument is an empty Array" do
+ @y.r([]) { |*a| a }.should == []
+ end
+
+ it "passes the value when the argument is an Array containing a single value" do
+ @y.r([1]) { |*a| a }.should == [1]
+ end
+
+ it "passes the values of the Array as individual arguments" do
+ @y.r([1, 2, 3]) { |*a| a }.should == [1, 2, 3]
+ end
+
+ it "passes the element of a single element Array" do
+ @y.r([[1, 2]]) { |*a| a }.should == [[1, 2]]
+ @y.r([nil]) { |*a| a }.should == [nil]
+ @y.r([[]]) { |*a| a }.should == [[]]
+ end
+
+ it "passes no values when give nil as an argument" do
+ @y.r(nil) { |*a| a }.should == []
+ end
+ end
+
+ describe "taking multiple arguments with a splat" do
+ it "raises a LocalJumpError when the method is not passed a block" do
+ -> { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
+ end
+
+ it "passes the arguments to the block" do
+ @y.rs(1, 2, 3) { |*a| a }.should == [1, 2, 3]
+ end
+
+ it "does not pass an argument value if the splatted argument is an empty Array" do
+ @y.rs(1, 2, []) { |*a| a }.should == [1, 2]
+ end
+
+ it "passes the Array elements as arguments if the splatted argument is a non-empty Array" do
+ @y.rs(1, 2, [3]) { |*a| a }.should == [1, 2, 3]
+ @y.rs(1, 2, [nil]) { |*a| a }.should == [1, 2, nil]
+ @y.rs(1, 2, [[]]) { |*a| a }.should == [1, 2, []]
+ @y.rs(1, 2, [3, 4, 5]) { |*a| a }.should == [1, 2, 3, 4, 5]
+ end
+
+ it "does not pass an argument value if the splatted argument is nil" do
+ @y.rs(1, 2, nil) { |*a| a }.should == [1, 2]
+ end
+ end
+
+ describe "taking matching arguments with splats and post args" do
+ it "raises a LocalJumpError when the method is not passed a block" do
+ -> { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError)
+ end
+
+ it "passes the arguments to the block" do
+ @y.rs([1, 2], 3, 4) { |(*a, b), c, d| [a, b, c, d] }.should == [[1], 2, 3, 4]
+ end
+ end
+
+ describe "taking a splat and a keyword argument" do
+ it "passes it as an array of the values and a hash" do
+ @y.k([1, 2]) { |*a| a }.should == [1, 2, {:b=>true}]
+ end
+ end
+
+ it "uses captured block of a block used in define_method" do
+ @y.deep(2).should == 4
+ end
+end
+
+describe "Using yield in a singleton class literal" do
+ ruby_version_is ""..."3.0" do
+ it 'emits a deprecation warning' do
+ code = <<~RUBY
+ def m
+ class << Object.new
+ yield
+ end
+ end
+ m { :ok }
+ RUBY
+
+ -> { eval(code) }.should complain(/warning: `yield' in class syntax will not be supported from Ruby 3.0/)
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it 'raises a SyntaxError' do
+ code = <<~RUBY
+ class << Object.new
+ yield
+ end
+ RUBY
+
+ -> { eval(code) }.should raise_error(SyntaxError, /Invalid yield/)
+ end
+ end
+end
+
+describe "Using yield in non-lambda block" do
+ it 'raises a SyntaxError' do
+ code = <<~RUBY
+ 1.times { yield }
+ RUBY
+
+ -> { eval(code) }.should raise_error(SyntaxError, /Invalid yield/)
+ end
+end
diff --git a/spec/ruby/library/English/English_spec.rb b/spec/ruby/library/English/English_spec.rb
new file mode 100644
index 0000000000..480602d5e5
--- /dev/null
+++ b/spec/ruby/library/English/English_spec.rb
@@ -0,0 +1,171 @@
+require_relative '../../spec_helper'
+
+require 'English'
+
+describe "English" do
+ it "aliases $ERROR_INFO to $!" do
+ begin
+ raise "error"
+ rescue
+ $ERROR_INFO.should_not be_nil
+ $ERROR_INFO.should == $!
+ end
+ $ERROR_INFO.should be_nil
+ end
+
+ it "aliases $ERROR_POSITION to $@" do
+ begin
+ raise "error"
+ rescue
+ $ERROR_POSITION.should_not be_nil
+ $ERROR_POSITION.should == $@
+ end
+ $ERROR_POSITION.should be_nil
+ end
+
+ it "aliases $FS to $;" do
+ original = $;
+ suppress_warning {$; = ","}
+ $FS.should_not be_nil
+ $FS.should == $;
+ suppress_warning {$; = original}
+ end
+
+ it "aliases $FIELD_SEPARATOR to $;" do
+ original = $;
+ suppress_warning {$; = ","}
+ $FIELD_SEPARATOR.should_not be_nil
+ $FIELD_SEPARATOR.should == $;
+ suppress_warning {$; = original}
+ end
+
+ it "aliases $OFS to $," do
+ original = $,
+ suppress_warning {$, = "|"}
+ $OFS.should_not be_nil
+ $OFS.should == $,
+ suppress_warning {$, = original}
+ end
+
+ it "aliases $OUTPUT_FIELD_SEPARATOR to $," do
+ original = $,
+ suppress_warning {$, = "|"}
+ $OUTPUT_FIELD_SEPARATOR.should_not be_nil
+ $OUTPUT_FIELD_SEPARATOR.should == $,
+ suppress_warning {$, = original}
+ end
+
+ it "aliases $RS to $/" do
+ $RS.should_not be_nil
+ $RS.should == $/
+ end
+
+ it "aliases $INPUT_RECORD_SEPARATOR to $/" do
+ $INPUT_RECORD_SEPARATOR.should_not be_nil
+ $INPUT_RECORD_SEPARATOR.should == $/
+ end
+
+ it "aliases $ORS to $\\" do
+ original = $\
+ suppress_warning {$\ = "\t"}
+ $ORS.should_not be_nil
+ $ORS.should == $\
+ suppress_warning {$\ = original}
+ end
+
+ it "aliases $OUTPUT_RECORD_SEPARATOR to $\\" do
+ original = $\
+ suppress_warning {$\ = "\t"}
+ $OUTPUT_RECORD_SEPARATOR.should_not be_nil
+ $OUTPUT_RECORD_SEPARATOR.should == $\
+ suppress_warning {$\ = original}
+ end
+
+ it "aliases $INPUT_LINE_NUMBER to $." do
+ $INPUT_LINE_NUMBER.should_not be_nil
+ $INPUT_LINE_NUMBER.should == $.
+ end
+
+ it "aliases $NR to $." do
+ $NR.should_not be_nil
+ $NR.should == $.
+ end
+
+ it "aliases $LAST_READ_LINE to $_ needs to be reviewed for spec completeness"
+
+ it "aliases $DEFAULT_OUTPUT to $>" do
+ $DEFAULT_OUTPUT.should_not be_nil
+ $DEFAULT_OUTPUT.should == $>
+ end
+
+ it "aliases $DEFAULT_INPUT to $<" do
+ $DEFAULT_INPUT.should_not be_nil
+ $DEFAULT_INPUT.should == $<
+ end
+
+ it "aliases $PID to $$" do
+ $PID.should_not be_nil
+ $PID.should == $$
+ end
+
+ it "aliases $PID to $$" do
+ $PID.should_not be_nil
+ $PID.should == $$
+ end
+
+ it "aliases $PROCESS_ID to $$" do
+ $PROCESS_ID.should_not be_nil
+ $PROCESS_ID.should == $$
+ end
+
+ it "aliases $CHILD_STATUS to $?" do
+ ruby_exe('exit 0')
+ $CHILD_STATUS.should_not be_nil
+ $CHILD_STATUS.should == $?
+ end
+
+ it "aliases $LAST_MATCH_INFO to $~" do
+ /c(a)t/ =~ "cat"
+ $LAST_MATCH_INFO.should_not be_nil
+ $LAST_MATCH_INFO.should == $~
+ end
+
+ it "aliases $IGNORECASE to $=" do
+ $VERBOSE, verbose = nil, $VERBOSE
+ begin
+ $IGNORECASE.should_not be_nil
+ $IGNORECASE.should == $=
+ ensure
+ $VERBOSE = verbose
+ end
+ end
+
+ it "aliases $ARGV to $*" do
+ $ARGV.should_not be_nil
+ $ARGV.should == $*
+ end
+
+ it "aliases $MATCH to $&" do
+ /c(a)t/ =~ "cat"
+ $MATCH.should_not be_nil
+ $MATCH.should == $&
+ end
+
+ it "aliases $PREMATCH to $`" do
+ /c(a)t/ =~ "cat"
+ $PREMATCH.should_not be_nil
+ $PREMATCH.should == $`
+ end
+
+ it "aliases $POSTMATCH to $'" do
+ /c(a)t/ =~ "cat"
+ $POSTMATCH.should_not be_nil
+ $POSTMATCH.should == $'
+ end
+
+ it "aliases $LAST_PAREN_MATCH to $+" do
+ /c(a)t/ =~ "cat"
+ $LAST_PAREN_MATCH.should_not be_nil
+ $LAST_PAREN_MATCH.should == $+
+ end
+end
diff --git a/spec/ruby/library/English/alias_spec.rb b/spec/ruby/library/English/alias_spec.rb
new file mode 100644
index 0000000000..78ccfb4398
--- /dev/null
+++ b/spec/ruby/library/English/alias_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require 'English'
+
+describe "English" do
+ it "aliases $! to $ERROR_INFO and $ERROR_INFO still returns an Exception with a backtrace" do
+ exception = (1 / 0 rescue $ERROR_INFO)
+ exception.should be_kind_of(Exception)
+ exception.backtrace.should be_kind_of(Array)
+ end
+
+ it "aliases $@ to $ERROR_POSITION and $ERROR_POSITION still returns a backtrace" do
+ (1 / 0 rescue $ERROR_POSITION).should be_kind_of(Array)
+ end
+end
diff --git a/spec/ruby/library/abbrev/abbrev_spec.rb b/spec/ruby/library/abbrev/abbrev_spec.rb
new file mode 100644
index 0000000000..61b0926597
--- /dev/null
+++ b/spec/ruby/library/abbrev/abbrev_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require 'abbrev'
+
+#test both Abbrev.abbrev and Array#abbrev in
+#the same manner, as they're more or less aliases
+#of one another
+
+[["Abbrev.abbrev", -> a { Abbrev.abbrev(a)}],
+ ["Array#abbrev", -> a { a.abbrev}]
+].each do |(name, func)|
+
+ describe name do
+ it "returns a hash of all unambiguous abbreviations of the array of strings passed in" do
+ func.call(['ruby', 'rules']).should == {"rub" => "ruby",
+ "ruby" => "ruby",
+ "rul" => "rules",
+ "rule" => "rules",
+ "rules" => "rules"}
+
+ func.call(["car", "cone"]).should == {"ca" => "car",
+ "car" => "car",
+ "co" => "cone",
+ "con" => "cone",
+ "cone" => "cone"}
+ end
+
+ it "returns an empty hash when called on an empty array" do
+ func.call([]).should == {}
+ end
+ end
+end
diff --git a/spec/ruby/library/base64/decode64_spec.rb b/spec/ruby/library/base64/decode64_spec.rb
new file mode 100644
index 0000000000..6dd33dddfe
--- /dev/null
+++ b/spec/ruby/library/base64/decode64_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+require 'base64'
+
+describe "Base64#decode64" do
+ it "returns the Base64-decoded version of the given string" do
+ Base64.decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n").should == "Send reinforcements"
+ end
+
+ it "returns the Base64-decoded version of the given shared string" do
+ Base64.decode64("base64: U2VuZCByZWluZm9yY2VtZW50cw==\n".split(" ").last).should == "Send reinforcements"
+ end
+
+ it "returns the Base64-decoded version of the given string with wrong padding" do
+ Base64.decode64("XU2VuZCByZWluZm9yY2VtZW50cw===").should == "]M\x95\xB9\x90\x81\xC9\x95\xA5\xB9\x99\xBD\xC9\x8D\x95\xB5\x95\xB9\xD1\xCC".b
+ end
+
+ it "returns the Base64-decoded version of the given string that contains an invalid character" do
+ Base64.decode64("%3D").should == "\xDC".b
+ end
+
+ it "returns a binary encoded string" do
+ Base64.decode64("SEk=").encoding.should == Encoding::BINARY
+ end
+
+ it "decodes without padding suffix ==" do
+ Base64.decode64("eyJrZXkiOnsibiI6InR0dCJ9fQ").should == "{\"key\":{\"n\":\"ttt\"}}"
+ end
+end
diff --git a/spec/ruby/library/base64/encode64_spec.rb b/spec/ruby/library/base64/encode64_spec.rb
new file mode 100644
index 0000000000..64de6257bc
--- /dev/null
+++ b/spec/ruby/library/base64/encode64_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+require 'base64'
+
+describe "Base64#encode64" do
+ it "returns the Base64-encoded version of the given string" do
+ Base64.encode64("Now is the time for all good coders\nto learn Ruby").should ==
+ "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g\nUnVieQ==\n"
+ end
+
+ it "returns the Base64-encoded version of the given string" do
+ Base64.encode64('Send reinforcements').should == "U2VuZCByZWluZm9yY2VtZW50cw==\n"
+ end
+
+ it "returns the Base64-encoded version of the given shared string" do
+ Base64.encode64("Now is the time for all good coders\nto learn Ruby".split("\n").last).should ==
+ "dG8gbGVhcm4gUnVieQ==\n"
+ end
+
+ it "returns a US_ASCII encoded string" do
+ Base64.encode64("HI").encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/library/base64/strict_decode64_spec.rb b/spec/ruby/library/base64/strict_decode64_spec.rb
new file mode 100644
index 0000000000..d258223c82
--- /dev/null
+++ b/spec/ruby/library/base64/strict_decode64_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+require 'base64'
+
+describe "Base64#strict_decode64" do
+ it "returns the Base64-decoded version of the given string" do
+ Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==").should == "Send reinforcements"
+ end
+
+ it "returns the Base64-decoded version of the given shared string" do
+ Base64.strict_decode64("base64: U2VuZCByZWluZm9yY2VtZW50cw==".split(" ").last).should == "Send reinforcements"
+ end
+
+ it "raises ArgumentError when the given string contains CR" do
+ -> do
+ Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==\r")
+ end.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError when the given string contains LF" do
+ -> do
+ Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n")
+ end.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError when the given string has wrong padding" do
+ -> do
+ Base64.strict_decode64("=U2VuZCByZWluZm9yY2VtZW50cw==")
+ end.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError when the given string contains an invalid character" do
+ -> do
+ Base64.strict_decode64("%3D")
+ end.should raise_error(ArgumentError)
+ end
+
+ it "returns a binary encoded string" do
+ Base64.strict_decode64("SEk=").encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/library/base64/strict_encode64_spec.rb b/spec/ruby/library/base64/strict_encode64_spec.rb
new file mode 100644
index 0000000000..7cabcf190c
--- /dev/null
+++ b/spec/ruby/library/base64/strict_encode64_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+require 'base64'
+
+describe "Base64#strict_encode64" do
+ it "returns the Base64-encoded version of the given string" do
+ Base64.strict_encode64("Now is the time for all good coders\nto learn Ruby").should ==
+ "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4gUnVieQ=="
+ end
+
+ it "returns the Base64-encoded version of the given shared string" do
+ Base64.strict_encode64("Now is the time for all good coders\nto learn Ruby".split("\n").last).should ==
+ "dG8gbGVhcm4gUnVieQ=="
+ end
+
+ it "returns a US_ASCII encoded string" do
+ Base64.strict_encode64("HI").encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/library/base64/urlsafe_decode64_spec.rb b/spec/ruby/library/base64/urlsafe_decode64_spec.rb
new file mode 100644
index 0000000000..1b813ee1b9
--- /dev/null
+++ b/spec/ruby/library/base64/urlsafe_decode64_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+require 'base64'
+
+describe "Base64#urlsafe_decode64" do
+ it "uses '_' instead of '/'" do
+ decoded = Base64.urlsafe_decode64("V2hlcmUgYW0gST8gV2hvIGFtIEk_IEFtIEk_IEk_")
+ decoded.should == 'Where am I? Who am I? Am I? I?'
+ end
+
+ it "uses '-' instead of '+'" do
+ decoded = Base64.urlsafe_decode64('IkJlaW5nIGRpc2ludGVncmF0ZWQgbWFrZXMgbWUgdmUtcnkgYW4tZ3J5ISIgPGh1ZmYsIGh1ZmY-')
+ decoded.should == '"Being disintegrated makes me ve-ry an-gry!" <huff, huff>'
+ end
+
+ it "does not require padding" do
+ Base64.urlsafe_decode64("MQ").should == "1"
+ end
+end
diff --git a/spec/ruby/library/base64/urlsafe_encode64_spec.rb b/spec/ruby/library/base64/urlsafe_encode64_spec.rb
new file mode 100644
index 0000000000..de1f235cea
--- /dev/null
+++ b/spec/ruby/library/base64/urlsafe_encode64_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+require 'base64'
+
+describe "Base64#urlsafe_encode64" do
+ it "uses '_' instead of '/'" do
+ encoded = Base64.urlsafe_encode64('Where am I? Who am I? Am I? I?')
+ encoded.should == "V2hlcmUgYW0gST8gV2hvIGFtIEk_IEFtIEk_IEk_"
+ end
+
+ it "uses '-' instead of '+'" do
+ encoded = Base64.urlsafe_encode64('"Being disintegrated makes me ve-ry an-gry!" <huff, huff>')
+ encoded.should == 'IkJlaW5nIGRpc2ludGVncmF0ZWQgbWFrZXMgbWUgdmUtcnkgYW4tZ3J5ISIgPGh1ZmYsIGh1ZmY-'
+ end
+
+ it "makes padding optional" do
+ Base64.urlsafe_encode64("1", padding: false).should == "MQ"
+ Base64.urlsafe_encode64("1").should == "MQ=="
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/BigDecimal_spec.rb b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb
new file mode 100644
index 0000000000..43a779b420
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb
@@ -0,0 +1,269 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal" do
+ it "is not defined unless it is required" do
+ ruby_exe('puts Object.const_defined?(:BigDecimal)').should == "false\n"
+ end
+end
+
+describe "Kernel#BigDecimal" do
+
+ it "creates a new object of class BigDecimal" do
+ BigDecimal("3.14159").should be_kind_of(BigDecimal)
+ (0..9).each {|i|
+ BigDecimal("1#{i}").should == 10 + i
+ BigDecimal("-1#{i}").should == -10 - i
+ BigDecimal("1E#{i}").should == 10**i
+ BigDecimal("1000000E-#{i}").should == 10**(6-i).to_f
+ # ^ to_f to avoid Rational type
+ }
+ (1..9).each {|i|
+ BigDecimal("100.#{i}").to_s.should =~ /\A0\.100#{i}E3\z/i
+ BigDecimal("-100.#{i}").to_s.should =~ /\A-0\.100#{i}E3\z/i
+ }
+ end
+
+ it "BigDecimal(Rational) with bigger-than-double numerator" do
+ rational = 99999999999999999999/100r
+ rational.numerator.should > 2**64
+ BigDecimal(rational, 100).to_s.should == "0.99999999999999999999e18"
+ end
+
+ it "accepts significant digits >= given precision" do
+ suppress_warning do
+ BigDecimal("3.1415923", 10).precs[1].should >= 10
+ end
+ end
+
+ it "determines precision from initial value" do
+ pi_string = "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043"
+ suppress_warning {
+ BigDecimal(pi_string).precs[1]
+ }.should >= pi_string.size-1
+ end
+
+ it "ignores leading and trailing whitespace" do
+ BigDecimal(" \t\n \r1234\t\r\n ").should == BigDecimal("1234")
+ BigDecimal(" \t\n \rNaN \n").should.nan?
+ BigDecimal(" \t\n \rInfinity \n").infinite?.should == 1
+ BigDecimal(" \t\n \r-Infinity \n").infinite?.should == -1
+ end
+
+ it "coerces the value argument with #to_str" do
+ initial = mock("value")
+ initial.should_receive(:to_str).and_return("123")
+ BigDecimal(initial).should == BigDecimal("123")
+ end
+
+ it "does not ignores trailing garbage" do
+ -> { BigDecimal("123E45ruby") }.should raise_error(ArgumentError)
+ -> { BigDecimal("123x45") }.should raise_error(ArgumentError)
+ -> { BigDecimal("123.4%E5") }.should raise_error(ArgumentError)
+ -> { BigDecimal("1E2E3E4E5E") }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError for invalid strings" do
+ -> { BigDecimal("ruby") }.should raise_error(ArgumentError)
+ -> { BigDecimal(" \t\n \r-\t\t\tInfinity \n") }.should raise_error(ArgumentError)
+ end
+
+ it "allows omitting the integer part" do
+ BigDecimal(".123").should == BigDecimal("0.123")
+ end
+
+ it "process underscores as Float()" do
+ reference = BigDecimal("12345.67E89")
+
+ BigDecimal("12_345.67E89").should == reference
+ -> { BigDecimal("1_2_3_4_5_._6____7_E89") }.should raise_error(ArgumentError)
+ -> { BigDecimal("12345_.67E_8__9_") }.should raise_error(ArgumentError)
+ end
+
+ it "accepts NaN and [+-]Infinity" do
+ BigDecimal("NaN").should.nan?
+
+ pos_inf = BigDecimal("Infinity")
+ pos_inf.should_not.finite?
+ pos_inf.should > 0
+ pos_inf.should == BigDecimal("+Infinity")
+
+ neg_inf = BigDecimal("-Infinity")
+ neg_inf.should_not.finite?
+ neg_inf.should < 0
+ end
+
+ describe "with exception: false" do
+ it "returns nil for invalid strings" do
+ BigDecimal("invalid", exception: false).should be_nil
+ BigDecimal("0invalid", exception: false).should be_nil
+ BigDecimal("invalid0", exception: false).should be_nil
+ BigDecimal("0.", exception: false).should be_nil
+ end
+ end
+
+ describe "accepts NaN and [+-]Infinity as Float values" do
+ it "works without an explicit precision" do
+ BigDecimal(Float::NAN).should.nan?
+
+ pos_inf = BigDecimal(Float::INFINITY)
+ pos_inf.should_not.finite?
+ pos_inf.should > 0
+ pos_inf.should == BigDecimal("+Infinity")
+
+ neg_inf = BigDecimal(-Float::INFINITY)
+ neg_inf.should_not.finite?
+ neg_inf.should < 0
+ end
+
+ it "works with an explicit precision" do
+ BigDecimal(Float::NAN, Float::DIG).should.nan?
+
+ pos_inf = BigDecimal(Float::INFINITY, Float::DIG)
+ pos_inf.should_not.finite?
+ pos_inf.should > 0
+ pos_inf.should == BigDecimal("+Infinity")
+
+ neg_inf = BigDecimal(-Float::INFINITY, Float::DIG)
+ neg_inf.should_not.finite?
+ neg_inf.should < 0
+ end
+ end
+
+ it "allows for [eEdD] as exponent separator" do
+ reference = BigDecimal("12345.67E89")
+
+ BigDecimal("12345.67e89").should == reference
+ BigDecimal("12345.67E89").should == reference
+ BigDecimal("12345.67d89").should == reference
+ BigDecimal("12345.67D89").should == reference
+ end
+
+ it "allows for varying signs" do
+ reference = BigDecimal("123.456E1")
+
+ BigDecimal("+123.456E1").should == reference
+ BigDecimal("-123.456E1").should == -reference
+ BigDecimal("123.456E+1").should == reference
+ BigDecimal("12345.6E-1").should == reference
+ BigDecimal("+123.456E+1").should == reference
+ BigDecimal("+12345.6E-1").should == reference
+ BigDecimal("-123.456E+1").should == -reference
+ BigDecimal("-12345.6E-1").should == -reference
+ end
+
+ it "raises ArgumentError when Float is used without precision" do
+ -> { BigDecimal(1.0) }.should raise_error(ArgumentError)
+ end
+
+ it "returns appropriate BigDecimal zero for signed zero" do
+ BigDecimal(-0.0, Float::DIG).sign.should == -1
+ BigDecimal(0.0, Float::DIG).sign.should == 1
+ end
+
+ it "pre-coerces long integers" do
+ BigDecimal(3).add(1 << 50, 3).should == BigDecimal('0.113e16')
+ end
+
+ it "does not call to_s when calling inspect" do
+ value = BigDecimal('44.44')
+ value.to_s.should == '0.4444e2'
+ value.inspect.should == '0.4444e2'
+
+ ruby_exe( <<-'EOF').should == "cheese 0.4444e2"
+ require 'bigdecimal'
+ module BigDecimalOverride
+ def to_s; "cheese"; end
+ end
+ BigDecimal.prepend BigDecimalOverride
+ value = BigDecimal('44.44')
+ print "#{value.to_s} #{value.inspect}"
+ EOF
+ end
+
+ describe "when interacting with Rational" do
+ before :each do
+ @a = BigDecimal('166.666666666')
+ @b = Rational(500, 3)
+ @c = @a - @b
+ end
+
+ # Check the input is as we understand it
+
+ it "has the LHS print as expected" do
+ @a.to_s.should == "0.166666666666e3"
+ @a.to_f.to_s.should == "166.666666666"
+ Float(@a).to_s.should == "166.666666666"
+ end
+
+ it "has the RHS print as expected" do
+ @b.to_s.should == "500/3"
+ @b.to_f.to_s.should == "166.66666666666666"
+ Float(@b).to_s.should == "166.66666666666666"
+ end
+
+ it "has the expected precision on the LHS" do
+ suppress_warning { @a.precs[0] }.should == 18
+ end
+
+ it "has the expected maximum precision on the LHS" do
+ suppress_warning { @a.precs[1] }.should == 27
+ end
+
+ it "produces the expected result when done via Float" do
+ (Float(@a) - Float(@b)).to_s.should == "-6.666596163995564e-10"
+ end
+
+ it "produces the expected result when done via to_f" do
+ (@a.to_f - @b.to_f).to_s.should == "-6.666596163995564e-10"
+ end
+
+ # Check underlying methods work as we understand
+
+ it "BigDecimal precision is the number of digits rounded up to a multiple of nine" do
+ 1.upto(100) do |n|
+ b = BigDecimal('4' * n)
+ precs, _ = suppress_warning { b.precs }
+ (precs >= 9).should be_true
+ (precs >= n).should be_true
+ (precs % 9).should == 0
+ end
+ suppress_warning { BigDecimal('NaN').precs[0] }.should == 9
+ end
+
+ it "BigDecimal maximum precision is nine more than precision except for abnormals" do
+ 1.upto(100) do |n|
+ b = BigDecimal('4' * n)
+ precs, max = suppress_warning { b.precs }
+ max.should == precs + 9
+ end
+ suppress_warning { BigDecimal('NaN').precs[1] }.should == 9
+ end
+
+ it "BigDecimal(Rational, 18) produces the result we expect" do
+ BigDecimal(@b, 18).to_s.should == "0.166666666666666667e3"
+ end
+
+ it "BigDecimal(Rational, BigDecimal.precs[0]) produces the result we expect" do
+ BigDecimal(@b, suppress_warning { @a.precs[0] }).to_s.should == "0.166666666666666667e3"
+ end
+
+ # Check the top-level expression works as we expect
+
+ it "produces a BigDecimal" do
+ @c.class.should == BigDecimal
+ end
+
+ it "produces the expected result" do
+ @c.should == BigDecimal("-0.666667e-9")
+ @c.to_s.should == "-0.666667e-9"
+ end
+
+ it "produces the correct class for other arithmetic operators" do
+ (@a + @b).class.should == BigDecimal
+ (@a * @b).class.should == BigDecimal
+ (@a / @b).class.should == BigDecimal
+ (@a % @b).class.should == BigDecimal
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/abs_spec.rb b/spec/ruby/library/bigdecimal/abs_spec.rb
new file mode 100644
index 0000000000..95dc45a905
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/abs_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#abs" do
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @two = BigDecimal("2")
+ @three = BigDecimal("3")
+ @mixed = BigDecimal("1.23456789")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ end
+
+ it "returns the absolute value" do
+ pos_int = BigDecimal("2E5555")
+ neg_int = BigDecimal("-2E5555")
+ pos_frac = BigDecimal("2E-9999")
+ neg_frac = BigDecimal("-2E-9999")
+
+ pos_int.abs.should == pos_int
+ neg_int.abs.should == pos_int
+ pos_frac.abs.should == pos_frac
+ neg_frac.abs.should == pos_frac
+ @one.abs.should == 1
+ @two.abs.should == 2
+ @three.abs.should == 3
+ @mixed.abs.should == @mixed
+ @one_minus.abs.should == @one
+ end
+
+ it "properly handles special values" do
+ @infinity.abs.should == @infinity
+ @infinity_minus.abs.should == @infinity
+ @nan.abs.should.nan? # have to do it this way, since == doesn't work on NaN
+ @zero.abs.should == 0
+ @zero.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ @zero_pos.abs.should == 0
+ @zero_pos.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ @zero_neg.abs.should == 0
+ @zero_neg.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/add_spec.rb b/spec/ruby/library/bigdecimal/add_spec.rb
new file mode 100644
index 0000000000..bea8b8f764
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/add_spec.rb
@@ -0,0 +1,193 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+require 'bigdecimal'
+
+describe "BigDecimal#add" do
+
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @two = BigDecimal("2")
+ @three = BigDecimal("3")
+ @ten = BigDecimal("10")
+ @eleven = BigDecimal("11")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ @frac_3 = BigDecimal("12345E10")
+ @frac_4 = BigDecimal("98765E10")
+ @dot_ones = BigDecimal("0.1111111111")
+ end
+
+ it "returns a + b with given precision" do
+ # documentation states, that precision ist optional, but it ain't,
+ @two.add(@one, 1).should == @three
+ @one .add(@two, 1).should == @three
+ @one.add(@one_minus, 1).should == @zero
+ @ten.add(@one, 2).should == @eleven
+ @zero.add(@one, 1).should == @one
+ @frac_2.add(@frac_1, 10000).should == BigDecimal("1.9E-99999")
+ @frac_1.add(@frac_1, 10000).should == BigDecimal("2E-99999")
+ @frac_3.add(@frac_4, 0).should == BigDecimal("0.11111E16")
+ @frac_3.add(@frac_4, 1).should == BigDecimal("0.1E16")
+ @frac_3.add(@frac_4, 2).should == BigDecimal("0.11E16")
+ @frac_3.add(@frac_4, 3).should == BigDecimal("0.111E16")
+ @frac_3.add(@frac_4, 4).should == BigDecimal("0.1111E16")
+ @frac_3.add(@frac_4, 5).should == BigDecimal("0.11111E16")
+ @frac_3.add(@frac_4, 6).should == BigDecimal("0.11111E16")
+ end
+
+ it "returns a + [Fixnum value] with given precision" do
+ (1..10).each {|precision|
+ @dot_ones.add(0, precision).should == BigDecimal("0." + "1" * precision)
+ }
+ BigDecimal("0.88").add(0, 1).should == BigDecimal("0.9")
+ end
+
+ it "returns a + [Bignum value] with given precision" do
+ bignum = 10000000000000000000
+ (1..20).each {|precision|
+ @dot_ones.add(bignum, precision).should == BigDecimal("0.1E20")
+ }
+ (21..30).each {|precision|
+ @dot_ones.add(bignum, precision).should == BigDecimal(
+ "0.10000000000000000000" + "1" * (precision - 20) + "E20")
+ }
+ end
+
+# TODO:
+# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/17374
+#
+# This doesn't work on MRI and looks like a bug to me:
+# one can use BigDecimal + Float, but not Bigdecimal.add(Float)
+#
+# it "returns a + [Float value] with given precision" do
+# (1..10).each {|precision|
+# @dot_ones.add(0.0, precision).should == BigDecimal("0." + "1" * precision)
+# }
+#
+# BigDecimal("0.88").add(0.0, 1).should == BigDecimal("0.9")
+# end
+
+ describe "with Object" do
+ it "tries to coerce the other operand to self" do
+ object = mock("Object")
+ object.should_receive(:coerce).with(@frac_3).and_return([@frac_3, @frac_4])
+ @frac_3.add(object, 1).should == BigDecimal("0.1E16")
+ end
+ end
+
+ describe "with Rational" do
+ it "produces a BigDecimal" do
+ (@three + Rational(500, 2)).should == BigDecimal("0.253e3")
+ end
+ end
+
+ it "favors the precision specified in the second argument over the global limit" do
+ BigDecimalSpecs.with_limit(1) do
+ BigDecimal('0.888').add(@zero, 3).should == BigDecimal('0.888')
+ end
+
+ BigDecimalSpecs.with_limit(2) do
+ BigDecimal('0.888').add(@zero, 1).should == BigDecimal('0.9')
+ end
+ end
+
+ it "uses the current rounding mode if rounding is needed" do
+ BigDecimalSpecs.with_rounding(BigDecimal::ROUND_UP) do
+ BigDecimal('0.111').add(@zero, 1).should == BigDecimal('0.2')
+ BigDecimal('-0.111').add(@zero, 1).should == BigDecimal('-0.2')
+ end
+ BigDecimalSpecs.with_rounding(BigDecimal::ROUND_DOWN) do
+ BigDecimal('0.999').add(@zero, 1).should == BigDecimal('0.9')
+ BigDecimal('-0.999').add(@zero, 1).should == BigDecimal('-0.9')
+ end
+ BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_UP) do
+ BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9')
+ BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9')
+ end
+ BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_DOWN) do
+ BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8')
+ BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8')
+ end
+ BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_EVEN) do
+ BigDecimal('0.75').add(@zero, 1).should == BigDecimal('0.8')
+ BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8')
+ BigDecimal('-0.75').add(@zero, 1).should == BigDecimal('-0.8')
+ BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8')
+ end
+ BigDecimalSpecs.with_rounding(BigDecimal::ROUND_CEILING) do
+ BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9')
+ BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8')
+ end
+ BigDecimalSpecs.with_rounding(BigDecimal::ROUND_FLOOR) do
+ BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8')
+ BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9')
+ end
+ end
+
+ it "uses the default ROUND_HALF_UP rounding if it wasn't explicitly changed" do
+ BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9')
+ BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9')
+ end
+
+ it "returns NaN if NaN is involved" do
+ @one.add(@nan, 10000).should.nan?
+ @nan.add(@one, 1).should.nan?
+ end
+
+ it "returns Infinity or -Infinity if these are involved" do
+ @zero.add(@infinity, 1).should == @infinity
+ @frac_2.add(@infinity, 1).should == @infinity
+ @one_minus.add(@infinity, 1).should == @infinity
+ @two.add(@infinity, 1).should == @infinity
+
+ @zero.add(@infinity_minus, 1).should == @infinity_minus
+ @frac_2.add(@infinity_minus, 1).should == @infinity_minus
+ @one_minus.add(@infinity_minus, 1).should == @infinity_minus
+ @two.add(@infinity_minus, 1).should == @infinity_minus
+
+ @infinity.add(@zero, 1).should == @infinity
+ @infinity.add(@frac_2, 1).should == @infinity
+ @infinity.add(@one_minus, 1).should == @infinity
+ @infinity.add(@two, 1).should == @infinity
+
+ @infinity_minus.add(@zero, 1).should == @infinity_minus
+ @infinity_minus.add(@frac_2, 1).should == @infinity_minus
+ @infinity_minus.add(@one_minus, 1).should == @infinity_minus
+ @infinity_minus.add(@two, 1).should == @infinity_minus
+
+ @infinity.add(@infinity, 10000).should == @infinity
+ @infinity_minus.add(@infinity_minus, 10000).should == @infinity_minus
+ end
+
+ it "returns NaN if Infinity + (- Infinity)" do
+ @infinity.add(@infinity_minus, 10000).should.nan?
+ @infinity_minus.add(@infinity, 10000).should.nan?
+ end
+
+ it "raises TypeError when adds nil" do
+ -> {
+ @one.add(nil, 10)
+ }.should raise_error(TypeError)
+ -> {
+ @one.add(nil, 0)
+ }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError when precision parameter is nil" do
+ -> {
+ @one.add(@one, nil)
+ }.should raise_error(TypeError)
+ end
+
+ it "raises ArgumentError when precision parameter is negative" do
+ -> {
+ @one.add(@one, -10)
+ }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/case_compare_spec.rb b/spec/ruby/library/bigdecimal/case_compare_spec.rb
new file mode 100644
index 0000000000..fac6714356
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/case_compare_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eql'
+
+
+describe "BigDecimal#===" do
+ it_behaves_like :bigdecimal_eql, :===
+end
diff --git a/spec/ruby/library/bigdecimal/ceil_spec.rb b/spec/ruby/library/bigdecimal/ceil_spec.rb
new file mode 100644
index 0000000000..60e71b12fb
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/ceil_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#ceil" do
+ before :each do
+ @zero = BigDecimal("0")
+ @one = BigDecimal("1")
+ @three = BigDecimal("3")
+ @four = BigDecimal("4")
+ @mixed = BigDecimal("1.23456789")
+ @mixed_big = BigDecimal("1.23456789E100")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+ @nan = BigDecimal("NaN")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ end
+
+ it "returns an Integer, if n is unspecified" do
+ @mixed.ceil.kind_of?(Integer).should == true
+ end
+
+ it "returns a BigDecimal, if n is specified" do
+ @pos_int.ceil(2).kind_of?(BigDecimal).should == true
+ end
+
+ it "returns the smallest integer greater or equal to self, if n is unspecified" do
+ @pos_int.ceil.should == @pos_int
+ @neg_int.ceil.should == @neg_int
+ @pos_frac.ceil.should == BigDecimal("1")
+ @neg_frac.ceil.should == @zero
+ @zero.ceil.should == 0
+ @zero_pos.ceil.should == @zero_pos
+ @zero_neg.ceil.should == @zero_neg
+
+
+ BigDecimal('2.3').ceil.should == 3
+ BigDecimal('2.5').ceil.should == 3
+ BigDecimal('2.9999').ceil.should == 3
+ BigDecimal('-2.3').ceil.should == -2
+ BigDecimal('-2.5').ceil.should == -2
+ BigDecimal('-2.9999').ceil.should == -2
+ end
+
+ it "raise exception, if self is special value" do
+ -> { @infinity.ceil }.should raise_error(FloatDomainError)
+ -> { @infinity_neg.ceil }.should raise_error(FloatDomainError)
+ -> { @nan.ceil }.should raise_error(FloatDomainError)
+ end
+
+ it "returns n digits right of the decimal point if given n > 0" do
+ @mixed.ceil(1).should == BigDecimal("1.3")
+ @mixed.ceil(5).should == BigDecimal("1.23457")
+
+ BigDecimal("-0.03").ceil(1).should == BigDecimal("0")
+ BigDecimal("0.03").ceil(1).should == BigDecimal("0.1")
+
+ BigDecimal("23.45").ceil(0).should == BigDecimal('24')
+ BigDecimal("23.45").ceil(1).should == BigDecimal('23.5')
+ BigDecimal("23.45").ceil(2).should == BigDecimal('23.45')
+
+ BigDecimal("-23.45").ceil(0).should == BigDecimal('-23')
+ BigDecimal("-23.45").ceil(1).should == BigDecimal('-23.4')
+ BigDecimal("-23.45").ceil(2).should == BigDecimal('-23.45')
+
+ BigDecimal("2E-10").ceil(0).should == @one
+ BigDecimal("2E-10").ceil(9).should == BigDecimal('1E-9')
+ BigDecimal("2E-10").ceil(10).should == BigDecimal('2E-10')
+ BigDecimal("2E-10").ceil(11).should == BigDecimal('2E-10')
+
+ (1..10).each do |n|
+ # 0.4, 0.34, 0.334, etc.
+ (@one.div(@three,20)).ceil(n).should == BigDecimal("0.#{'3'*(n-1)}4")
+ # 1.4, 1.34, 1.334, etc.
+ (@four.div(@three,20)).ceil(n).should == BigDecimal("1.#{'3'*(n-1)}4")
+ (BigDecimal('31').div(@three,20)).ceil(n).should == BigDecimal("10.#{'3'*(n-1)}4")
+ end
+ (1..10).each do |n|
+ # -0.4, -0.34, -0.334, etc.
+ (-@one.div(@three,20)).ceil(n).should == BigDecimal("-0.#{'3'* n}")
+ end
+ (1..10).each do |n|
+ (@three.div(@one,20)).ceil(n).should == @three
+ end
+ (1..10).each do |n|
+ (-@three.div(@one,20)).ceil(n).should == -@three
+ end
+ end
+
+ it "sets n digits left of the decimal point to 0, if given n < 0" do
+ BigDecimal("13345.234").ceil(-2).should == BigDecimal("13400.0")
+ @mixed_big.ceil(-99).should == BigDecimal("0.13E101")
+ @mixed_big.ceil(-100).should == BigDecimal("0.2E101")
+ @mixed_big.ceil(-95).should == BigDecimal("0.123457E101")
+ BigDecimal("1E10").ceil(-30).should == BigDecimal('1E30')
+ BigDecimal("-1E10").ceil(-30).should == @zero
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/clone_spec.rb b/spec/ruby/library/bigdecimal/clone_spec.rb
new file mode 100644
index 0000000000..b3a1c61d6a
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/clone_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/clone'
+
+describe "BigDecimal#dup" do
+ it_behaves_like :bigdecimal_clone, :clone
+end
diff --git a/spec/ruby/library/bigdecimal/coerce_spec.rb b/spec/ruby/library/bigdecimal/coerce_spec.rb
new file mode 100644
index 0000000000..1e5c73f969
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/coerce_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#coerce" do
+
+ it "returns [other, self] both as BigDecimal" do
+ one = BigDecimal("1.0")
+ five_point_28 = BigDecimal("5.28")
+ zero_minus = BigDecimal("-0.0")
+ some_value = 32434234234234234234
+
+ BigDecimal("1.2").coerce(1).should == [one, BigDecimal("1.2")]
+ five_point_28.coerce(1.0).should == [one, BigDecimal("5.28")]
+ one.coerce(one).should == [one, one]
+ one.coerce(2.5).should == [2.5, one]
+ BigDecimal("1").coerce(3.14).should == [3.14, one]
+ a, b = zero_minus.coerce(some_value)
+ a.should == BigDecimal(some_value.to_s)
+ b.should == zero_minus
+ a, b = one.coerce(some_value)
+ a.should == BigDecimal(some_value.to_s)
+ b.to_f.should be_close(1.0, TOLERANCE) # can we take out the to_f once BigDecimal#- is implemented?
+ b.should == one
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/comparison_spec.rb b/spec/ruby/library/bigdecimal/comparison_spec.rb
new file mode 100644
index 0000000000..c53187b727
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/comparison_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#<=>" do
+ before :each do
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @mixed = BigDecimal("1.23456789")
+ @mixed_big = BigDecimal("1.23456789E100")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+
+ @int_mock = mock('123')
+ class << @int_mock
+ def coerce(other)
+ return [other, BigDecimal('123')]
+ end
+ def >=(other)
+ BigDecimal('123') >= other
+ end
+ end
+
+ @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac,
+ -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1,
+ @zero , 1, 2, 10, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg]
+
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+ @nan = BigDecimal("NaN")
+ end
+
+
+ it "returns 0 if a == b" do
+ (@pos_int <=> @pos_int).should == 0
+ (@neg_int <=> @neg_int).should == 0
+ (@pos_frac <=> @pos_frac).should == 0
+ (@neg_frac <=> @neg_frac).should == 0
+ (@zero <=> @zero).should == 0
+ (@infinity <=> @infinity).should == 0
+ (@infinity_neg <=> @infinity_neg).should == 0
+ end
+
+ it "returns 1 if a > b" do
+ (@pos_int <=> @neg_int).should == 1
+ (@pos_frac <=> @neg_frac).should == 1
+ (@pos_frac <=> @zero).should == 1
+ @values.each { |val|
+ (@infinity <=> val).should == 1
+ }
+ end
+
+ it "returns -1 if a < b" do
+ (@zero <=> @pos_frac).should == -1
+ (@neg_int <=> @pos_frac).should == -1
+ (@pos_frac <=> @pos_int).should == -1
+ @values.each { |val|
+ (@infinity_neg <=> val).should == -1
+ }
+ end
+
+ it "returns nil if NaN is involved" do
+ @values += [@infinity, @infinity_neg, @nan]
+ @values << nil
+ @values << Object.new
+ @values.each { |val|
+ (@nan <=> val).should == nil
+ }
+ end
+
+ it "returns nil if the argument is nil" do
+ (@zero <=> nil).should == nil
+ (@infinity <=> nil).should == nil
+ (@infinity_neg <=> nil).should == nil
+ (@mixed <=> nil).should == nil
+ (@pos_int <=> nil).should == nil
+ (@neg_frac <=> nil).should == nil
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/constants_spec.rb b/spec/ruby/library/bigdecimal/constants_spec.rb
new file mode 100644
index 0000000000..8d879c036a
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/constants_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal constants" do
+ it "defines a VERSION value" do
+ BigDecimal.const_defined?(:VERSION).should be_true
+ end
+
+ it "has a BASE value" do
+ # The actual one is decided based on HAVE_INT64_T in MRI,
+ # which is hard to check here.
+ [10000, 1000000000].should include(BigDecimal::BASE)
+ end
+
+ it "has a NaN value" do
+ BigDecimal::NAN.nan?.should be_true
+ end
+
+ it "has an INFINITY value" do
+ BigDecimal::INFINITY.infinite?.should == 1
+ end
+
+ describe "exception-related constants" do
+ [
+ [:EXCEPTION_ALL, 0xff],
+ [:EXCEPTION_INFINITY, 0x01],
+ [:EXCEPTION_NaN, 0x02],
+ [:EXCEPTION_UNDERFLOW, 0x04],
+ [:EXCEPTION_OVERFLOW, 0x01],
+ [:EXCEPTION_ZERODIVIDE, 0x10]
+ ].each do |const, value|
+ it "has a #{const} value" do
+ BigDecimal.const_get(const).should == value
+ end
+ end
+ end
+
+ describe "rounding-related constants" do
+ [
+ [:ROUND_MODE, 0x100],
+ [:ROUND_UP, 1],
+ [:ROUND_DOWN, 2],
+ [:ROUND_HALF_UP, 3],
+ [:ROUND_HALF_DOWN, 4],
+ [:ROUND_CEILING, 5],
+ [:ROUND_FLOOR, 6],
+ [:ROUND_HALF_EVEN, 7]
+ ].each do |const, value|
+ it "has a #{const} value" do
+ BigDecimal.const_get(const).should == value
+ end
+ end
+ end
+
+ describe "sign-related constants" do
+ [
+ [:SIGN_NaN, 0],
+ [:SIGN_POSITIVE_ZERO, 1],
+ [:SIGN_NEGATIVE_ZERO, -1],
+ [:SIGN_POSITIVE_FINITE, 2],
+ [:SIGN_NEGATIVE_FINITE, -2],
+ [:SIGN_POSITIVE_INFINITE, 3],
+ [:SIGN_NEGATIVE_INFINITE, -3]
+ ].each do |const, value|
+ it "has a #{const} value" do
+ BigDecimal.const_get(const).should == value
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/div_spec.rb b/spec/ruby/library/bigdecimal/div_spec.rb
new file mode 100644
index 0000000000..53ad6d0418
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/div_spec.rb
@@ -0,0 +1,110 @@
+require_relative '../../spec_helper'
+require_relative 'shared/quo'
+require 'bigdecimal'
+
+describe "BigDecimal#div with precision set to 0" do
+ # TODO: figure out if there is a better way to do these
+ # shared specs rather than sending [0]. See other specs
+ # that share :bigdecimal_quo.
+ it_behaves_like :bigdecimal_quo, :div, [0]
+end
+
+describe "BigDecimal#div" do
+
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @zero_plus = BigDecimal("+0")
+ @zero_minus = BigDecimal("-0")
+ @two = BigDecimal("2")
+ @three = BigDecimal("3")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ end
+
+ it "returns a / b with optional precision" do
+ @two.div(@one).should == @two
+ @one.div(@two).should == @zero
+ # ^^ is this really intended for a class with arbitrary precision?
+ @one.div(@two, 1).should == BigDecimal("0.5")
+ @one.div(@one_minus).should == @one_minus
+ @one_minus.div(@one_minus).should == @one
+ @frac_2.div(@frac_1, 1).should == BigDecimal("0.9")
+ @frac_1.div(@frac_1).should == @one
+
+ res = "0." + "3" * 1000
+ (1..100).each { |idx|
+ @one.div(@three, idx).to_s("F").should == "0." + res[2, idx]
+ }
+ end
+
+ describe "with Object" do
+ it "tries to coerce the other operand to self" do
+ object = mock("Object")
+ object.should_receive(:coerce).with(@one).and_return([@one, @two])
+ @one.div(object).should == @zero
+ end
+ end
+
+ it "raises FloatDomainError if NaN is involved" do
+ -> { @one.div(@nan) }.should raise_error(FloatDomainError)
+ -> { @nan.div(@one) }.should raise_error(FloatDomainError)
+ -> { @nan.div(@nan) }.should raise_error(FloatDomainError)
+ end
+
+ it "returns 0 if divided by Infinity and no precision given" do
+ @zero.div(@infinity).should == 0
+ @frac_2.div(@infinity).should == 0
+ end
+
+ it "returns 0 if divided by Infinity with given precision" do
+ @zero.div(@infinity, 0).should == 0
+ @frac_2.div(@infinity, 1).should == 0
+ @zero.div(@infinity, 100000).should == 0
+ @frac_2.div(@infinity, 100000).should == 0
+ end
+
+ it "raises ZeroDivisionError if divided by zero and no precision given" do
+ -> { @one.div(@zero) }.should raise_error(ZeroDivisionError)
+ -> { @one.div(@zero_plus) }.should raise_error(ZeroDivisionError)
+ -> { @one.div(@zero_minus) }.should raise_error(ZeroDivisionError)
+
+ -> { @zero.div(@zero) }.should raise_error(ZeroDivisionError)
+ -> { @zero_minus.div(@zero_plus) }.should raise_error(ZeroDivisionError)
+ -> { @zero_minus.div(@zero_minus) }.should raise_error(ZeroDivisionError)
+ -> { @zero_plus.div(@zero_minus) }.should raise_error(ZeroDivisionError)
+ end
+
+ it "returns NaN if zero is divided by zero" do
+ @zero.div(@zero, 0).should.nan?
+ @zero_minus.div(@zero_plus, 0).should.nan?
+ @zero_plus.div(@zero_minus, 0).should.nan?
+
+ @zero.div(@zero, 10).should.nan?
+ @zero_minus.div(@zero_plus, 10).should.nan?
+ @zero_plus.div(@zero_minus, 10).should.nan?
+ end
+
+ it "raises FloatDomainError if (+|-) Infinity divided by 1 and no precision given" do
+ -> { @infinity_minus.div(@one) }.should raise_error(FloatDomainError)
+ -> { @infinity.div(@one) }.should raise_error(FloatDomainError)
+ -> { @infinity_minus.div(@one_minus) }.should raise_error(FloatDomainError)
+ end
+
+ it "returns (+|-)Infinity if (+|-)Infinity by 1 and precision given" do
+ @infinity_minus.div(@one, 0).should == @infinity_minus
+ @infinity.div(@one, 0).should == @infinity
+ @infinity_minus.div(@one_minus, 0).should == @infinity
+ end
+
+ it "returns NaN if Infinity / ((+|-) Infinity)" do
+ @infinity.div(@infinity_minus, 100000).should.nan?
+ @infinity_minus.div(@infinity, 1).should.nan?
+ end
+
+
+end
diff --git a/spec/ruby/library/bigdecimal/divide_spec.rb b/spec/ruby/library/bigdecimal/divide_spec.rb
new file mode 100644
index 0000000000..c62b23557d
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/divide_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require_relative 'shared/quo'
+require 'bigdecimal'
+
+describe "BigDecimal#/" do
+ it_behaves_like :bigdecimal_quo, :/, []
+
+ before :each do
+ @three = BigDecimal("3")
+ end
+
+ describe "with Rational" do
+ it "produces a BigDecimal" do
+ (@three / Rational(500, 2)).should == BigDecimal("0.12e-1")
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/divmod_spec.rb b/spec/ruby/library/bigdecimal/divmod_spec.rb
new file mode 100644
index 0000000000..294f01cba0
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/divmod_spec.rb
@@ -0,0 +1,180 @@
+require_relative '../../spec_helper'
+require_relative 'shared/modulo'
+require 'bigdecimal'
+
+module DivmodSpecs
+ def self.check_both_nan(array)
+ array.length.should == 2
+ array[0].should.nan?
+ array[1].should.nan?
+ end
+ def self.check_both_bigdecimal(array)
+ array.length.should == 2
+ array[0].kind_of?(BigDecimal).should == true
+ array[1].kind_of?(BigDecimal).should == true
+ end
+end
+
+# TODO: figure out a way to do the shared specs with helpers instead
+# of spec'ing a method that does not really exist
+describe "BigDecimal#mod_part_of_divmod" do
+ # BigDecimal#divmod[1] behaves exactly like #modulo
+ before :all do
+ class BigDecimal
+ def mod_part_of_divmod(arg)
+ divmod(arg)[1]
+ end
+ end
+ end
+
+ after :all do
+ class BigDecimal
+ undef mod_part_of_divmod
+ end
+ end
+
+ it_behaves_like :bigdecimal_modulo, :mod_part_of_divmod
+
+ it "raises ZeroDivisionError if other is zero" do
+ bd5667 = BigDecimal("5667.19")
+
+ -> { bd5667.mod_part_of_divmod(0) }.should raise_error(ZeroDivisionError)
+ -> { bd5667.mod_part_of_divmod(BigDecimal("0")) }.should raise_error(ZeroDivisionError)
+ -> { @zero.mod_part_of_divmod(@zero) }.should raise_error(ZeroDivisionError)
+ end
+end
+
+describe "BigDecimal#divmod" do
+
+ before :each do
+ @a = BigDecimal("42.00000000000000000001")
+
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+
+ @one = BigDecimal("1")
+ @mixed = BigDecimal("1.23456789")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+
+ @special_vals = [@infinity, @infinity_minus, @nan]
+ @regular_vals = [
+ @one, @mixed, @pos_int, @neg_int, @pos_frac,
+ @neg_frac, @one_minus, @frac_1, @frac_2]
+ @zeroes = [@zero, @zero_pos, @zero_neg]
+ end
+
+ it "divides value, returns an array" do
+ res = @a.divmod(5)
+ res.kind_of?(Array).should == true
+ end
+
+ it "array contains quotient and modulus as BigDecimal" do
+ res = @a.divmod(5)
+ DivmodSpecs.check_both_bigdecimal(res)
+ res[0].should == BigDecimal('0.8E1')
+ res[1].should == BigDecimal('2.00000000000000000001')
+
+ BigDecimal('1').divmod(BigDecimal('2')).should == [0, 1]
+ BigDecimal('2').divmod(BigDecimal('1')).should == [2, 0]
+
+ BigDecimal('1').divmod(BigDecimal('-2')).should == [-1, -1]
+ BigDecimal('2').divmod(BigDecimal('-1')).should == [-2, 0]
+
+ BigDecimal('-1').divmod(BigDecimal('2')).should == [-1, 1]
+ BigDecimal('-2').divmod(BigDecimal('1')).should == [-2, 0]
+ end
+
+ it "can be reversed with * and +" do
+ # Example taken from BigDecimal documentation
+ a = BigDecimal("42")
+ b = BigDecimal("9")
+ q, m = a.divmod(b)
+ c = q * b + m
+ a.should == c
+
+ values = [@one, @one_minus, BigDecimal('2'), BigDecimal('-2'),
+ BigDecimal('5'), BigDecimal('-5'), BigDecimal('10'), BigDecimal('-10'),
+ BigDecimal('20'), BigDecimal('-20'), BigDecimal('100'), BigDecimal('-100'),
+ BigDecimal('1.23456789E10'), BigDecimal('-1.23456789E10')
+ ]
+
+ # TODO: file MRI bug:
+ # BigDecimal('1').divmod(BigDecimal('3E-9'))[0] #=> 0.3E9,
+ # but really should be 0.333333333E9
+ values << BigDecimal('1E-10')
+ values << BigDecimal('-1E-10')
+ values << BigDecimal('2E55')
+ values << BigDecimal('-2E55')
+ values << BigDecimal('2E-5555')
+ values << BigDecimal('-2E-5555')
+
+
+ values_and_zeroes = values + @zeroes
+ values_and_zeroes.each do |val1|
+ values.each do |val2|
+ res = val1.divmod(val2)
+ DivmodSpecs.check_both_bigdecimal(res)
+ res[0].should == ((val1/val2).floor)
+ res[1].should == (val1 - res[0] * val2)
+ end
+ end
+ end
+
+ it "returns an array of two NaNs if NaN is involved" do
+ (@special_vals + @regular_vals + @zeroes).each do |val|
+ DivmodSpecs.check_both_nan(val.divmod(@nan))
+ DivmodSpecs.check_both_nan(@nan.divmod(val))
+ end
+ end
+
+ it "raises ZeroDivisionError if the divisor is zero" do
+ (@special_vals + @regular_vals + @zeroes - [@nan]).each do |val|
+ @zeroes.each do |zero|
+ -> { val.divmod(zero) }.should raise_error(ZeroDivisionError)
+ end
+ end
+ end
+
+ it "returns an array of Infinity and NaN if the dividend is Infinity" do
+ @regular_vals.each do |val|
+ array = @infinity.divmod(val)
+ array.length.should == 2
+ array[0].infinite?.should == (val > 0 ? 1 : -1)
+ array[1].should.nan?
+ end
+ end
+
+ it "returns an array of zero and the dividend if the divisor is Infinity" do
+ @regular_vals.each do |val|
+ array = val.divmod(@infinity)
+ array.length.should == 2
+ array[0].should == @zero
+ array[1].should == val
+ end
+ end
+
+ it "returns an array of two zero if the dividend is zero" do
+ @zeroes.each do |zero|
+ @regular_vals.each do |val|
+ zero.divmod(val).should == [@zero, @zero]
+ end
+ end
+ end
+
+ it "raises TypeError if the argument cannot be coerced to BigDecimal" do
+ -> {
+ @one.divmod('1')
+ }.should raise_error(TypeError)
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/double_fig_spec.rb b/spec/ruby/library/bigdecimal/double_fig_spec.rb
new file mode 100644
index 0000000000..f742d68f87
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/double_fig_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal.double_fig" do
+ # The result depends on the CPU and OS
+ it "returns the number of digits a Float number is allowed to have" do
+ BigDecimal.double_fig.should_not == nil
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/dup_spec.rb b/spec/ruby/library/bigdecimal/dup_spec.rb
new file mode 100644
index 0000000000..bfabaf6e8b
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/dup_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/clone'
+
+describe "BigDecimal#dup" do
+ it_behaves_like :bigdecimal_clone, :dup
+end
diff --git a/spec/ruby/library/bigdecimal/eql_spec.rb b/spec/ruby/library/bigdecimal/eql_spec.rb
new file mode 100644
index 0000000000..1be5862751
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/eql_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eql'
+
+describe "BigDecimal#eql?" do
+ it_behaves_like :bigdecimal_eql, :eql?
+end
diff --git a/spec/ruby/library/bigdecimal/equal_value_spec.rb b/spec/ruby/library/bigdecimal/equal_value_spec.rb
new file mode 100644
index 0000000000..933060eada
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/equal_value_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eql'
+
+
+describe "BigDecimal#==" do
+ it_behaves_like :bigdecimal_eql, :==
+end
diff --git a/spec/ruby/library/bigdecimal/exponent_spec.rb b/spec/ruby/library/bigdecimal/exponent_spec.rb
new file mode 100644
index 0000000000..8877147955
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/exponent_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'shared/power'
+require 'bigdecimal'
+
+describe "BigDecimal#**" do
+ it_behaves_like :bigdecimal_power, :**
+end
+
+describe "BigDecimal#exponent" do
+
+ it "returns an Integer" do
+ BigDecimal("2E100000000").exponent.kind_of?(Integer).should == true
+ BigDecimal("2E-999").exponent.kind_of?(Integer).should == true
+ end
+
+ it "is n if number can be represented as 0.xxx*10**n" do
+ BigDecimal("2E1000").exponent.should == 1001
+ BigDecimal("1234567E10").exponent.should == 17
+ end
+
+ it "returns 0 if self is 0" do
+ BigDecimal("0").exponent.should == 0
+ BigDecimal("+0").exponent.should == 0
+ BigDecimal("-0").exponent.should == 0
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/finite_spec.rb b/spec/ruby/library/bigdecimal/finite_spec.rb
new file mode 100644
index 0000000000..8fc06029bb
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/finite_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#finite?" do
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @two = BigDecimal("2")
+ @three = BigDecimal("3")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ @big = BigDecimal("2E40001")
+ @finite_vals = [@one, @zero, @zero_pos, @zero_neg, @two,
+ @three, @frac_1, @frac_2, @big, @one_minus]
+ end
+
+ it "is false if Infinity or NaN" do
+ @infinity.should_not.finite?
+ @infinity_minus.should_not.finite?
+ @nan.should_not.finite?
+ end
+
+ it "returns true for finite values" do
+ @finite_vals.each do |val|
+ val.should.finite?
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/fix_spec.rb b/spec/ruby/library/bigdecimal/fix_spec.rb
new file mode 100644
index 0000000000..231c9a587e
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/fix_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#fix" do
+ before :each do
+ @zero = BigDecimal("0")
+ @mixed = BigDecimal("1.23456789")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+ @nan = BigDecimal("NaN")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ end
+
+ it "returns a BigDecimal" do
+ BigDecimal("2E100000000").fix.kind_of?(BigDecimal).should == true
+ BigDecimal("2E-999").kind_of?(BigDecimal).should == true
+ end
+
+ it "returns the integer part of the absolute value" do
+ a = BigDecimal("2E1000")
+ a.fix.should == a
+ b = BigDecimal("-2E1000")
+ b.fix.should == b
+ BigDecimal("0.123456789E5").fix.should == BigDecimal("0.12345E5")
+ BigDecimal("-0.123456789E5").fix.should == BigDecimal("-0.12345E5")
+ end
+
+ it "correctly handles special values" do
+ @infinity.fix.should == @infinity
+ @infinity_neg.fix.should == @infinity_neg
+ @nan.fix.should.nan?
+ end
+
+ it "returns 0 if the absolute value is < 1" do
+ BigDecimal("0.99999").fix.should == 0
+ BigDecimal("-0.99999").fix.should == 0
+ BigDecimal("0.000000001").fix.should == 0
+ BigDecimal("-0.00000001").fix.should == 0
+ BigDecimal("-1000000").fix.should_not == 0
+ @zero.fix.should == 0
+ @zero_pos.fix.should == @zero_pos
+ @zero_neg.fix.should == @zero_neg
+ end
+
+ it "does not allow any arguments" do
+ -> {
+ @mixed.fix(10)
+ }.should raise_error(ArgumentError)
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/fixtures/classes.rb b/spec/ruby/library/bigdecimal/fixtures/classes.rb
new file mode 100644
index 0000000000..06e4474cf0
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/fixtures/classes.rb
@@ -0,0 +1,17 @@
+module BigDecimalSpecs
+ # helper method to sure that the global limit is reset back
+ def self.with_limit(l)
+ old = BigDecimal.limit(l)
+ yield
+ ensure
+ BigDecimal.limit(old)
+ end
+
+ def self.with_rounding(r)
+ old = BigDecimal.mode(BigDecimal::ROUND_MODE)
+ BigDecimal.mode(BigDecimal::ROUND_MODE, r)
+ yield
+ ensure
+ BigDecimal.mode(BigDecimal::ROUND_MODE, old)
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/floor_spec.rb b/spec/ruby/library/bigdecimal/floor_spec.rb
new file mode 100644
index 0000000000..a7dfec2c9a
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/floor_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#floor" do
+ before :each do
+ @one = BigDecimal("1")
+ @three = BigDecimal("3")
+ @four = BigDecimal("4")
+ @zero = BigDecimal("0")
+ @mixed = BigDecimal("1.23456789")
+ @mixed_big = BigDecimal("1.23456789E100")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+ @nan = BigDecimal("NaN")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ end
+
+ it "returns the greatest integer smaller or equal to self" do
+ @pos_int.floor.should == @pos_int
+ @neg_int.floor.should == @neg_int
+ @pos_frac.floor.should == @zero
+ @neg_frac.floor.should == BigDecimal("-1")
+ @zero.floor.should == 0
+ @zero_pos.floor.should == @zero_pos
+ @zero_neg.floor.should == @zero_neg
+
+ BigDecimal('2.3').floor.should == 2
+ BigDecimal('2.5').floor.should == 2
+ BigDecimal('2.9999').floor.should == 2
+ BigDecimal('-2.3').floor.should == -3
+ BigDecimal('-2.5').floor.should == -3
+ BigDecimal('-2.9999').floor.should == -3
+ BigDecimal('0.8').floor.should == 0
+ BigDecimal('-0.8').floor.should == -1
+ end
+
+ it "raise exception, if self is special value" do
+ -> { @infinity.floor }.should raise_error(FloatDomainError)
+ -> { @infinity_neg.floor }.should raise_error(FloatDomainError)
+ -> { @nan.floor }.should raise_error(FloatDomainError)
+ end
+
+ it "returns n digits right of the decimal point if given n > 0" do
+ @mixed.floor(1).should == BigDecimal("1.2")
+ @mixed.floor(5).should == BigDecimal("1.23456")
+
+ BigDecimal("-0.03").floor(1).should == BigDecimal("-0.1")
+ BigDecimal("0.03").floor(1).should == BigDecimal("0")
+
+ BigDecimal("23.45").floor(0).should == BigDecimal('23')
+ BigDecimal("23.45").floor(1).should == BigDecimal('23.4')
+ BigDecimal("23.45").floor(2).should == BigDecimal('23.45')
+
+ BigDecimal("-23.45").floor(0).should == BigDecimal('-24')
+ BigDecimal("-23.45").floor(1).should == BigDecimal('-23.5')
+ BigDecimal("-23.45").floor(2).should == BigDecimal('-23.45')
+
+ BigDecimal("2E-10").floor(0).should == @zero
+ BigDecimal("2E-10").floor(9).should == @zero
+ BigDecimal("2E-10").floor(10).should == BigDecimal('2E-10')
+ BigDecimal("2E-10").floor(11).should == BigDecimal('2E-10')
+
+ (1..10).each do |n|
+ # 0.3, 0.33, 0.333, etc.
+ (@one.div(@three,20)).floor(n).should == BigDecimal("0.#{'3'*n}")
+ # 1.3, 1.33, 1.333, etc.
+ (@four.div(@three,20)).floor(n).should == BigDecimal("1.#{'3'*n}")
+ (BigDecimal('31').div(@three,20)).floor(n).should == BigDecimal("10.#{'3'*n}")
+ end
+ (1..10).each do |n|
+ # -0.4, -0.34, -0.334, etc.
+ (-@one.div(@three,20)).floor(n).should == BigDecimal("-0.#{'3'*(n-1)}4")
+ end
+ (1..10).each do |n|
+ (@three.div(@one,20)).floor(n).should == @three
+ end
+ (1..10).each do |n|
+ (-@three.div(@one,20)).floor(n).should == -@three
+ end
+ end
+
+ it "sets n digits left of the decimal point to 0, if given n < 0" do
+ BigDecimal("13345.234").floor(-2).should == BigDecimal("13300.0")
+ @mixed_big.floor(-99).should == BigDecimal("0.12E101")
+ @mixed_big.floor(-100).should == BigDecimal("0.1E101")
+ @mixed_big.floor(-95).should == BigDecimal("0.123456E101")
+ (1..10).each do |n|
+ BigDecimal('1.8').floor(-n).should == @zero
+ end
+ BigDecimal("1E10").floor(-30).should == @zero
+ BigDecimal("-1E10").floor(-30).should == BigDecimal('-1E30')
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/frac_spec.rb b/spec/ruby/library/bigdecimal/frac_spec.rb
new file mode 100644
index 0000000000..11ccf03c2f
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/frac_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#frac" do
+ before :each do
+ @zero = BigDecimal("0")
+ @mixed = BigDecimal("1.23456789")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+ @nan = BigDecimal("NaN")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ end
+
+ it "returns a BigDecimal" do
+ @pos_int.frac.kind_of?(BigDecimal).should == true
+ @neg_int.frac.kind_of?(BigDecimal).should == true
+ @pos_frac.kind_of?(BigDecimal).should == true
+ @neg_frac.kind_of?(BigDecimal).should == true
+ end
+
+ it "returns the fractional part of the absolute value" do
+ @mixed.frac.should == BigDecimal("0.23456789")
+ @pos_frac.frac.should == @pos_frac
+ @neg_frac.frac.should == @neg_frac
+ end
+
+ it "returns 0 if the value is 0" do
+ @zero.frac.should == @zero
+ end
+
+ it "returns 0 if the value is an integer" do
+ @pos_int.frac.should == @zero
+ @neg_int.frac.should == @zero
+ end
+
+ it "correctly handles special values" do
+ @infinity.frac.should == @infinity
+ @infinity_neg.frac.should == @infinity_neg
+ @nan.frac.should.nan?
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/gt_spec.rb b/spec/ruby/library/bigdecimal/gt_spec.rb
new file mode 100644
index 0000000000..78547fb85f
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/gt_spec.rb
@@ -0,0 +1,96 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#>" do
+ before :each do
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @mixed = BigDecimal("1.23456789")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+
+ @int_mock = mock('123')
+ class << @int_mock
+ def coerce(other)
+ return [other, BigDecimal('123')]
+ end
+ def >(other)
+ BigDecimal('123') > other
+ end
+ end
+
+ @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac,
+ -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1,
+ @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg]
+
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+
+ @float_infinity = Float::INFINITY
+ @float_infinity_neg = -Float::INFINITY
+
+ @nan = BigDecimal("NaN")
+ end
+
+ it "returns true if a > b" do
+ one = BigDecimal("1")
+ two = BigDecimal("2")
+
+ frac_1 = BigDecimal("1E-99999")
+ frac_2 = BigDecimal("0.9E-99999")
+ (@zero > one).should == false
+ (two > @zero).should == true
+ (frac_2 > frac_1).should == false
+
+ (@neg_int > @pos_int).should == false
+ (@pos_int > @neg_int).should == true
+ (@neg_int > @pos_frac).should == false
+ (@pos_frac > @neg_int).should == true
+ (@zero > @zero_pos).should == false
+ (@zero > @zero_neg).should == false
+ (@zero_neg > @zero_pos).should == false
+ (@zero_pos > @zero_neg).should == false
+ end
+
+ it "properly handles infinity values" do
+ @values.each { |val|
+ (val > @infinity).should == false
+ (@infinity > val).should == true
+ (val > @infinity_neg).should == true
+ (@infinity_neg > val).should == false
+ }
+ (@infinity > @infinity).should == false
+ (@infinity_neg > @infinity_neg).should == false
+ (@infinity > @infinity_neg).should == true
+ (@infinity_neg > @infinity).should == false
+ end
+
+ it "properly handles Float infinity values" do
+ @values.each { |val|
+ (val > @float_infinity).should == false
+ (@float_infinity > val).should == true
+ (val > @float_infinity_neg).should == true
+ (@float_infinity_neg > val).should == false
+ }
+ end
+
+ it "properly handles NaN values" do
+ @values += [@infinity, @infinity_neg, @nan]
+ @values.each { |val|
+ (@nan > val).should == false
+ (val > @nan).should == false
+ }
+ end
+
+ it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do
+ -> {@zero > nil }.should raise_error(ArgumentError)
+ -> {@infinity > nil }.should raise_error(ArgumentError)
+ -> {@infinity_neg > nil }.should raise_error(ArgumentError)
+ -> {@mixed > nil }.should raise_error(ArgumentError)
+ -> {@pos_int > nil }.should raise_error(ArgumentError)
+ -> {@neg_frac > nil }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/gte_spec.rb b/spec/ruby/library/bigdecimal/gte_spec.rb
new file mode 100644
index 0000000000..2a5cc025ba
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/gte_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#>=" do
+ before :each do
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @mixed = BigDecimal("1.23456789")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+
+ @int_mock = mock('123')
+ class << @int_mock
+ def coerce(other)
+ return [other, BigDecimal('123')]
+ end
+ def >=(other)
+ BigDecimal('123') >= other
+ end
+ end
+
+ @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac,
+ -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1,
+ @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg]
+
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+
+ @float_infinity = Float::INFINITY
+ @float_infinity_neg = -Float::INFINITY
+
+ @nan = BigDecimal("NaN")
+ end
+
+ it "returns true if a >= b" do
+ one = BigDecimal("1")
+ two = BigDecimal("2")
+
+ frac_1 = BigDecimal("1E-99999")
+ frac_2 = BigDecimal("0.9E-99999")
+
+ (@zero >= one).should == false
+ (two >= @zero).should == true
+
+ (frac_2 >= frac_1).should == false
+ (two >= two).should == true
+ (frac_1 >= frac_1).should == true
+
+ (@neg_int >= @pos_int).should == false
+ (@pos_int >= @neg_int).should == true
+ (@neg_int >= @pos_frac).should == false
+ (@pos_frac >= @neg_int).should == true
+ (@zero >= @zero_pos).should == true
+ (@zero >= @zero_neg).should == true
+ (@zero_neg >= @zero_pos).should == true
+ (@zero_pos >= @zero_neg).should == true
+ end
+
+ it "properly handles infinity values" do
+ @values.each { |val|
+ (val >= @infinity).should == false
+ (@infinity >= val).should == true
+ (val >= @infinity_neg).should == true
+ (@infinity_neg >= val).should == false
+ }
+ (@infinity >= @infinity).should == true
+ (@infinity_neg >= @infinity_neg).should == true
+ (@infinity >= @infinity_neg).should == true
+ (@infinity_neg >= @infinity).should == false
+ end
+
+ it "properly handles Float infinity values" do
+ @values.each { |val|
+ (val >= @float_infinity).should == false
+ (@float_infinity >= val).should == true
+ (val >= @float_infinity_neg).should == true
+ (@float_infinity_neg >= val).should == false
+ }
+ end
+
+ it "properly handles NaN values" do
+ @values += [@infinity, @infinity_neg, @nan]
+ @values.each { |val|
+ (@nan >= val).should == false
+ (val >= @nan).should == false
+ }
+ end
+
+ it "returns nil if the argument is nil" do
+ -> {@zero >= nil }.should raise_error(ArgumentError)
+ -> {@infinity >= nil }.should raise_error(ArgumentError)
+ -> {@infinity_neg >= nil }.should raise_error(ArgumentError)
+ -> {@mixed >= nil }.should raise_error(ArgumentError)
+ -> {@pos_int >= nil }.should raise_error(ArgumentError)
+ -> {@neg_frac >= nil }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/hash_spec.rb b/spec/ruby/library/bigdecimal/hash_spec.rb
new file mode 100644
index 0000000000..7581c90f68
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/hash_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BidDecimal#hash" do
+ describe "two BigDecimal objects with the same value" do
+ it "should have the same hash for ordinary values" do
+ BigDecimal('1.2920').hash.should == BigDecimal('1.2920').hash
+ end
+
+ it "should have the same hash for infinite values" do
+ BigDecimal("+Infinity").hash.should == BigDecimal("+Infinity").hash
+ BigDecimal("-Infinity").hash.should == BigDecimal("-Infinity").hash
+ end
+
+ it "should have the same hash for NaNs" do
+ BigDecimal("NaN").hash.should == BigDecimal("NaN").hash
+ end
+
+ it "should have the same hash for zero values" do
+ BigDecimal("+0").hash.should == BigDecimal("+0").hash
+ BigDecimal("-0").hash.should == BigDecimal("-0").hash
+ end
+ end
+
+ describe "two BigDecimal objects with numerically equal values" do
+ it "should have the same hash value" do
+ BigDecimal("1.2920").hash.should == BigDecimal("1.2920000").hash
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/infinite_spec.rb b/spec/ruby/library/bigdecimal/infinite_spec.rb
new file mode 100644
index 0000000000..025386107f
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/infinite_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#infinite?" do
+
+ it "returns 1 if self is Infinity" do
+ BigDecimal("Infinity").infinite?.should == 1
+ end
+
+ it "returns -1 if self is -Infinity" do
+ BigDecimal("-Infinity").infinite?.should == -1
+ end
+
+ it "returns not true otherwise" do
+ e2_plus = BigDecimal("2E40001")
+ e3_minus = BigDecimal("3E-20001")
+ really_small_zero = BigDecimal("0E-200000000")
+ really_big_zero = BigDecimal("0E200000000000")
+ e3_minus.infinite?.should == nil
+ e2_plus.infinite?.should == nil
+ really_small_zero.infinite?.should == nil
+ really_big_zero.infinite?.should == nil
+ BigDecimal("0.000000000000000000000000").infinite?.should == nil
+ end
+
+ it "returns not true if self is NaN" do
+ # NaN is a special value which is neither finite nor infinite.
+ nan = BigDecimal("NaN")
+ nan.infinite?.should == nil
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/inspect_spec.rb b/spec/ruby/library/bigdecimal/inspect_spec.rb
new file mode 100644
index 0000000000..7ce47142b2
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/inspect_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#inspect" do
+
+ before :each do
+ @bigdec = BigDecimal("1234.5678")
+ end
+
+ it "returns String" do
+ @bigdec.inspect.kind_of?(String).should == true
+ end
+
+ it "looks like this" do
+ @bigdec.inspect.should == "0.12345678e4"
+ end
+
+ it "does not add an exponent for zero values" do
+ BigDecimal("0").inspect.should == "0.0"
+ BigDecimal("+0").inspect.should == "0.0"
+ BigDecimal("-0").inspect.should == "-0.0"
+ end
+
+ it "properly cases non-finite values" do
+ BigDecimal("NaN").inspect.should == "NaN"
+ BigDecimal("Infinity").inspect.should == "Infinity"
+ BigDecimal("+Infinity").inspect.should == "Infinity"
+ BigDecimal("-Infinity").inspect.should == "-Infinity"
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/limit_spec.rb b/spec/ruby/library/bigdecimal/limit_spec.rb
new file mode 100644
index 0000000000..75cbc8b55c
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/limit_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require 'bigdecimal'
+
+describe "BigDecimal.limit" do
+ it "returns the value before set if the passed argument is nil or is not specified" do
+ old = BigDecimal.limit
+ BigDecimal.limit.should == 0
+ BigDecimal.limit(10).should == 0
+ BigDecimal.limit.should == 10
+ BigDecimal.limit(old)
+ end
+
+ it "uses the global limit if no precision is specified" do
+ BigDecimalSpecs.with_limit(0) do
+ (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.888')
+ (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.888')
+ (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('2.664')
+ (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.296')
+ end
+
+ BigDecimalSpecs.with_limit(1) do
+ (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.9')
+ (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.9')
+ (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('3')
+ (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.3')
+ end
+
+ BigDecimalSpecs.with_limit(2) do
+ (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.89')
+ (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.89')
+ (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('2.7')
+ (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.30')
+ end
+ end
+
+ it "picks the specified precision over global limit" do
+ BigDecimalSpecs.with_limit(3) do
+ BigDecimal('0.888').add(BigDecimal('0'), 2).should == BigDecimal('0.89')
+ BigDecimal('0.888').sub(BigDecimal('0'), 2).should == BigDecimal('0.89')
+ BigDecimal('0.888').mult(BigDecimal('3'), 2).should == BigDecimal('2.7')
+ BigDecimal('0.888').div(BigDecimal('3'), 2).should == BigDecimal('0.30')
+ end
+ end
+
+ it "picks the global precision when limit 0 specified" do
+ BigDecimalSpecs.with_limit(3) do
+ BigDecimal('0.8888').add(BigDecimal('0'), 0).should == BigDecimal('0.889')
+ BigDecimal('0.8888').sub(BigDecimal('0'), 0).should == BigDecimal('0.889')
+ BigDecimal('0.888').mult(BigDecimal('3'), 0).should == BigDecimal('2.66')
+ BigDecimal('0.8888').div(BigDecimal('3'), 0).should == BigDecimal('0.296')
+ end
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/lt_spec.rb b/spec/ruby/library/bigdecimal/lt_spec.rb
new file mode 100644
index 0000000000..02390e76e3
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/lt_spec.rb
@@ -0,0 +1,94 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#<" do
+ before :each do
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @mixed = BigDecimal("1.23456789")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+
+ @int_mock = mock('123')
+ class << @int_mock
+ def coerce(other)
+ return [other, BigDecimal('123')]
+ end
+ def <(other)
+ BigDecimal('123') < other
+ end
+ end
+
+ @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac,
+ -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1,
+ @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg]
+
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+
+ @float_infinity = Float::INFINITY
+ @float_infinity_neg = -Float::INFINITY
+
+ @nan = BigDecimal("NaN")
+ end
+
+ it "returns true if a < b" do
+ one = BigDecimal("1")
+ two = BigDecimal("2")
+ frac_1 = BigDecimal("1E-99999")
+ frac_2 = BigDecimal("0.9E-99999")
+ (@zero < one).should == true
+ (two < @zero).should == false
+ (frac_2 < frac_1).should == true
+ (@neg_int < @pos_int).should == true
+ (@pos_int < @neg_int).should == false
+ (@neg_int < @pos_frac).should == true
+ (@pos_frac < @neg_int).should == false
+ (@zero < @zero_pos).should == false
+ (@zero < @zero_neg).should == false
+ (@zero_neg < @zero_pos).should == false
+ (@zero_pos < @zero_neg).should == false
+ end
+
+ it "properly handles infinity values" do
+ @values.each { |val|
+ (val < @infinity).should == true
+ (@infinity < val).should == false
+ (val < @infinity_neg).should == false
+ (@infinity_neg < val).should == true
+ }
+ (@infinity < @infinity).should == false
+ (@infinity_neg < @infinity_neg).should == false
+ (@infinity < @infinity_neg).should == false
+ (@infinity_neg < @infinity).should == true
+ end
+
+ it "properly handles Float infinity values" do
+ @values.each { |val|
+ (val < @float_infinity).should == true
+ (@float_infinity < val).should == false
+ (val < @float_infinity_neg).should == false
+ (@float_infinity_neg < val).should == true
+ }
+ end
+
+ it "properly handles NaN values" do
+ @values += [@infinity, @infinity_neg, @nan]
+ @values.each { |val|
+ (@nan < val).should == false
+ (val < @nan).should == false
+ }
+ end
+
+ it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do
+ -> {@zero < nil }.should raise_error(ArgumentError)
+ -> {@infinity < nil }.should raise_error(ArgumentError)
+ -> {@infinity_neg < nil }.should raise_error(ArgumentError)
+ -> {@mixed < nil }.should raise_error(ArgumentError)
+ -> {@pos_int < nil }.should raise_error(ArgumentError)
+ -> {@neg_frac < nil }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/lte_spec.rb b/spec/ruby/library/bigdecimal/lte_spec.rb
new file mode 100644
index 0000000000..b92be04123
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/lte_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#<=" do
+ before :each do
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @mixed = BigDecimal("1.23456789")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+
+ @int_mock = mock('123')
+ class << @int_mock
+ def coerce(other)
+ return [other, BigDecimal('123')]
+ end
+ def <=(other)
+ BigDecimal('123') <= other
+ end
+ end
+
+ @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac,
+ -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1,
+ @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg]
+
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+
+ @float_infinity = Float::INFINITY
+ @float_infinity_neg = -Float::INFINITY
+
+ @nan = BigDecimal("NaN")
+ end
+
+ it "returns true if a <= b" do
+ one = BigDecimal("1")
+ two = BigDecimal("2")
+
+ frac_1 = BigDecimal("1E-99999")
+ frac_2 = BigDecimal("0.9E-99999")
+
+ (@zero <= one).should == true
+ (two <= @zero).should == false
+
+ (frac_2 <= frac_1).should == true
+ (two <= two).should == true
+ (frac_1 <= frac_1).should == true
+
+ (@neg_int <= @pos_int).should == true
+ (@pos_int <= @neg_int).should == false
+ (@neg_int <= @pos_frac).should == true
+ (@pos_frac <= @neg_int).should == false
+ (@zero <= @zero_pos).should == true
+ (@zero <= @zero_neg).should == true
+ (@zero_neg <= @zero_pos).should == true
+ (@zero_pos <= @zero_neg).should == true
+ end
+
+ it "properly handles infinity values" do
+ @values.each { |val|
+ (val <= @infinity).should == true
+ (@infinity <= val).should == false
+ (val <= @infinity_neg).should == false
+ (@infinity_neg <= val).should == true
+ }
+ (@infinity <= @infinity).should == true
+ (@infinity_neg <= @infinity_neg).should == true
+ (@infinity <= @infinity_neg).should == false
+ (@infinity_neg <= @infinity).should == true
+ end
+
+ it "properly handles Float infinity values" do
+ @values.each { |val|
+ (val <= @float_infinity).should == true
+ (@float_infinity <= val).should == false
+ (val <= @float_infinity_neg).should == false
+ (@float_infinity_neg <= val).should == true
+ }
+ end
+
+ it "properly handles NaN values" do
+ @values += [@infinity, @infinity_neg, @nan]
+ @values.each { |val|
+ (@nan <= val).should == false
+ (val <= @nan).should == false
+ }
+ end
+
+ it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do
+ -> {@zero <= nil }.should raise_error(ArgumentError)
+ -> {@infinity <= nil }.should raise_error(ArgumentError)
+ -> {@infinity_neg <= nil }.should raise_error(ArgumentError)
+ -> {@mixed <= nil }.should raise_error(ArgumentError)
+ -> {@pos_int <= nil }.should raise_error(ArgumentError)
+ -> {@neg_frac <= nil }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/minus_spec.rb b/spec/ruby/library/bigdecimal/minus_spec.rb
new file mode 100644
index 0000000000..bd3c19584b
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/minus_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#-" do
+
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @two = BigDecimal("2")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ end
+
+ it "returns a - b" do
+ (@two - @one).should == @one
+ (@one - @two).should == @one_minus
+ (@one - @one_minus).should == @two
+ (@frac_2 - @frac_1).should == BigDecimal("-0.1E-99999")
+ (@two - @two).should == @zero
+ (@frac_1 - @frac_1).should == @zero
+ (BigDecimal('1.23456789') - BigDecimal('1.2')).should == BigDecimal("0.03456789")
+ end
+
+ it "returns NaN if NaN is involved" do
+ (@one - @nan).should.nan?
+ (@nan - @one).should.nan?
+ (@nan - @nan).should.nan?
+ (@nan - @infinity).should.nan?
+ (@nan - @infinity_minus).should.nan?
+ (@infinity - @nan).should.nan?
+ (@infinity_minus - @nan).should.nan?
+ end
+
+ it "returns NaN both operands are infinite with the same sign" do
+ (@infinity - @infinity).should.nan?
+ (@infinity_minus - @infinity_minus).should.nan?
+ end
+
+ it "returns Infinity or -Infinity if these are involved" do
+ (@infinity - @infinity_minus).should == @infinity
+ (@infinity_minus - @infinity).should == @infinity_minus
+
+ (@infinity - @zero).should == @infinity
+ (@infinity - @frac_2).should == @infinity
+ (@infinity - @two).should == @infinity
+ (@infinity - @one_minus).should == @infinity
+
+ (@zero - @infinity).should == @infinity_minus
+ (@frac_2 - @infinity).should == @infinity_minus
+ (@two - @infinity).should == @infinity_minus
+ (@one_minus - @infinity).should == @infinity_minus
+ end
+
+ describe "with Object" do
+ it "tries to coerce the other operand to self" do
+ object = mock("Object")
+ object.should_receive(:coerce).with(@one).and_return([@one, BigDecimal("42")])
+ (@one - object).should == BigDecimal("-41")
+ end
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/mode_spec.rb b/spec/ruby/library/bigdecimal/mode_spec.rb
new file mode 100644
index 0000000000..73fa1a118e
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/mode_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal.mode" do
+ #the default value of BigDecimal exception constants is false
+ after :each do
+ BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false)
+ BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false)
+ BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false)
+ BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false)
+ BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, false)
+ end
+
+ it "returns the appropriate value and continue the computation if the flag is false" do
+ BigDecimal("NaN").add(BigDecimal("1"),0).should.nan?
+ BigDecimal("0").add(BigDecimal("Infinity"),0).should == BigDecimal("Infinity")
+ BigDecimal("1").quo(BigDecimal("0")).should == BigDecimal("Infinity")
+ end
+
+ it "returns Infinity when too big" do
+ BigDecimal("1E11111111111111111111").should == BigDecimal("Infinity")
+ (BigDecimal("1E1000000000000000000")**10).should == BigDecimal("Infinity")
+ end
+
+ it "raise an exception if the flag is true" do
+ BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true)
+ -> { BigDecimal("NaN").add(BigDecimal("1"),0) }.should raise_error(FloatDomainError)
+ BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true)
+ -> { BigDecimal("0").add(BigDecimal("Infinity"),0) }.should raise_error(FloatDomainError)
+ BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, true)
+ -> { BigDecimal("1").quo(BigDecimal("0")) }.should raise_error(FloatDomainError)
+ BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, true)
+ -> { BigDecimal("1E11111111111111111111") }.should raise_error(FloatDomainError)
+ -> { (BigDecimal("1E1000000000000000000")**10) }.should raise_error(FloatDomainError)
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/modulo_spec.rb b/spec/ruby/library/bigdecimal/modulo_spec.rb
new file mode 100644
index 0000000000..035d31bd98
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/modulo_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'shared/modulo'
+
+describe "BigDecimal#%" do
+ it_behaves_like :bigdecimal_modulo, :%
+ it_behaves_like :bigdecimal_modulo_zerodivisionerror, :%
+end
+
+describe "BigDecimal#modulo" do
+ it_behaves_like :bigdecimal_modulo, :modulo
+ it_behaves_like :bigdecimal_modulo_zerodivisionerror, :modulo
+end
diff --git a/spec/ruby/library/bigdecimal/mult_spec.rb b/spec/ruby/library/bigdecimal/mult_spec.rb
new file mode 100644
index 0000000000..b7f8044b0b
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/mult_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'shared/mult'
+require 'bigdecimal'
+
+describe "BigDecimal#mult" do
+ it_behaves_like :bigdecimal_mult, :mult, [10]
+end
+
+describe "BigDecimal#mult" do
+ before :each do
+ @one = BigDecimal "1"
+ @e3_minus = BigDecimal("3E-20001")
+ @e3_plus = BigDecimal("3E20001")
+ @e = BigDecimal "1.00000000000000000000123456789"
+ @tolerance = @e.sub @one, 1000
+ @tolerance2 = BigDecimal "30001E-20005"
+
+ end
+
+ it "multiply self with other with (optional) precision" do
+ @e.mult(@one, 1).should be_close(@one, @tolerance)
+ @e3_minus.mult(@one, 1).should be_close(0, @tolerance2)
+ end
+
+ describe "with Object" do
+ it "tries to coerce the other operand to self" do
+ object = mock("Object")
+ object.should_receive(:coerce).with(@e3_minus).and_return([@e3_minus, @e3_plus])
+ @e3_minus.mult(object, 1).should == BigDecimal("9")
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/multiply_spec.rb b/spec/ruby/library/bigdecimal/multiply_spec.rb
new file mode 100644
index 0000000000..a8ce1da32e
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/multiply_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'shared/mult'
+require 'bigdecimal'
+
+describe "BigDecimal#*" do
+ it_behaves_like :bigdecimal_mult, :*, []
+end
+
+describe "BigDecimal#*" do
+ before :each do
+ @three = BigDecimal("3")
+ @e3_minus = BigDecimal("3E-20001")
+ @e3_plus = BigDecimal("3E20001")
+ @e = BigDecimal("1.00000000000000000000123456789")
+ @one = BigDecimal("1")
+ end
+
+ it "multiply self with other" do
+ (@one * @one).should == @one
+ (@e3_minus * @e3_plus).should == BigDecimal("9")
+ # Can't do this till we implement **
+ # (@e3_minus * @e3_minus).should == @e3_minus ** 2
+ # So let's rewrite it as:
+ (@e3_minus * @e3_minus).should == BigDecimal("9E-40002")
+ (@e * @one).should == @e
+ end
+
+ describe "with Object" do
+ it "tries to coerce the other operand to self" do
+ object = mock("Object")
+ object.should_receive(:coerce).with(@e3_minus).and_return([@e3_minus, @e3_plus])
+ (@e3_minus * object).should == BigDecimal("9")
+ end
+ end
+
+ describe "with Rational" do
+ it "produces a BigDecimal" do
+ (@three * Rational(500, 2)).should == BigDecimal("0.75e3")
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/nan_spec.rb b/spec/ruby/library/bigdecimal/nan_spec.rb
new file mode 100644
index 0000000000..9eaf69b610
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/nan_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#nan?" do
+
+ it "returns true if self is not a number" do
+ BigDecimal("NaN").should.nan?
+ end
+
+ it "returns false if self is not a NaN" do
+ BigDecimal("Infinity").should_not.nan?
+ BigDecimal("-Infinity").should_not.nan?
+ BigDecimal("0").should_not.nan?
+ BigDecimal("+0").should_not.nan?
+ BigDecimal("-0").should_not.nan?
+ BigDecimal("2E40001").should_not.nan?
+ BigDecimal("3E-20001").should_not.nan?
+ BigDecimal("0E-200000000").should_not.nan?
+ BigDecimal("0E200000000000").should_not.nan?
+ BigDecimal("0.000000000000000000000000").should_not.nan?
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/nonzero_spec.rb b/spec/ruby/library/bigdecimal/nonzero_spec.rb
new file mode 100644
index 0000000000..f43c4393cd
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/nonzero_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#nonzero?" do
+
+ it "returns self if self doesn't equal zero" do
+ # documentation says, it returns true. (04/10/08)
+ e2_plus = BigDecimal("2E40001")
+ e3_minus = BigDecimal("3E-20001")
+ infinity = BigDecimal("Infinity")
+ infinity_minus = BigDecimal("-Infinity")
+ nan = BigDecimal("NaN")
+ infinity.nonzero?.should equal(infinity)
+ infinity_minus.nonzero?.should equal(infinity_minus)
+ nan.nonzero?.should equal(nan)
+ e3_minus.nonzero?.should equal(e3_minus)
+ e2_plus.nonzero?.should equal(e2_plus)
+ end
+
+ it "returns nil otherwise" do
+ # documentation states, it should return false. (04/10/08)
+ really_small_zero = BigDecimal("0E-200000000")
+ really_big_zero = BigDecimal("0E200000000000")
+ really_small_zero.nonzero?.should == nil
+ really_big_zero.nonzero?.should == nil
+ BigDecimal("0.000000000000000000000000").nonzero?.should == nil
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/plus_spec.rb b/spec/ruby/library/bigdecimal/plus_spec.rb
new file mode 100644
index 0000000000..d1934841c8
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/plus_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#+" do
+
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @two = BigDecimal("2")
+ @three = BigDecimal("3")
+ @ten = BigDecimal("10")
+ @eleven = BigDecimal("11")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ end
+
+ it "returns a + b" do
+ (@two + @one).should == @three
+ (@one + @two).should == @three
+ (@one + @one_minus).should == @zero
+ (@zero + @one).should == @one
+ (@ten + @one).should == @eleven
+ (@frac_1 + @frac_2).should == BigDecimal("1.9E-99999")
+ (@frac_2 + @frac_1).should == BigDecimal("1.9E-99999")
+ (@frac_1 + @frac_1).should == BigDecimal("2E-99999")
+ end
+
+ it "returns NaN if NaN is involved" do
+ (@one + @nan).should.nan?
+ (@nan + @one).should.nan?
+ end
+
+ it "returns Infinity or -Infinity if these are involved" do
+ (@zero + @infinity).should == @infinity
+ (@frac_2 + @infinity).should == @infinity
+ (@two + @infinity_minus).should == @infinity_minus
+ end
+
+ it "returns NaN if Infinity + (- Infinity)" do
+ (@infinity + @infinity_minus).should.nan?
+ end
+
+ describe "with Object" do
+ it "tries to coerce the other operand to self" do
+ object = mock("Object")
+ object.should_receive(:coerce).with(@one).and_return([@one, BigDecimal("42")])
+ (@one + object).should == BigDecimal("43")
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/power_spec.rb b/spec/ruby/library/bigdecimal/power_spec.rb
new file mode 100644
index 0000000000..63a45a1887
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/power_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/power'
+
+describe "BigDecimal#power" do
+ it_behaves_like :bigdecimal_power, :power
+end
diff --git a/spec/ruby/library/bigdecimal/precs_spec.rb b/spec/ruby/library/bigdecimal/precs_spec.rb
new file mode 100644
index 0000000000..5fda8d3087
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/precs_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#precs" do
+ before :each do
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+ @nan = BigDecimal("NaN")
+ @zero = BigDecimal("0")
+ @zero_neg = BigDecimal("-0")
+
+ @arr = [BigDecimal("2E40001"), BigDecimal("3E-20001"),\
+ @infinity, @infinity_neg, @nan, @zero, @zero_neg]
+ @precision = BigDecimal::BASE.to_s.length - 1
+ end
+
+ it "returns array of two values" do
+ suppress_warning do
+ @arr.each do |x|
+ x.precs.kind_of?(Array).should == true
+ x.precs.size.should == 2
+ end
+ end
+ end
+
+ it "returns Integers as array values" do
+ suppress_warning do
+ @arr.each do |x|
+ x.precs[0].kind_of?(Integer).should == true
+ x.precs[1].kind_of?(Integer).should == true
+ end
+ end
+ end
+
+ it "returns the current value of significant digits as the first value" do
+ suppress_warning do
+ BigDecimal("3.14159").precs[0].should >= 6
+ BigDecimal('1').precs[0].should == BigDecimal('1' + '0' * 100).precs[0]
+ [@infinity, @infinity_neg, @nan, @zero, @zero_neg].each do |value|
+ value.precs[0].should <= @precision
+ end
+ end
+ end
+
+ it "returns the maximum number of significant digits as the second value" do
+ suppress_warning do
+ BigDecimal("3.14159").precs[1].should >= 6
+ BigDecimal('1').precs[1].should >= 1
+ BigDecimal('1' + '0' * 100).precs[1].should >= 101
+ [@infinity, @infinity_neg, @nan, @zero, @zero_neg].each do |value|
+ value.precs[1].should >= 1
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/quo_spec.rb b/spec/ruby/library/bigdecimal/quo_spec.rb
new file mode 100644
index 0000000000..65a4330303
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/quo_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'shared/quo'
+require 'bigdecimal'
+
+describe "BigDecimal#quo" do
+ it_behaves_like :bigdecimal_quo, :quo, []
+
+ it "returns NaN if NaN is involved" do
+ BigDecimal("1").quo(BigDecimal("NaN")).should.nan?
+ BigDecimal("NaN").quo(BigDecimal("1")).should.nan?
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/remainder_spec.rb b/spec/ruby/library/bigdecimal/remainder_spec.rb
new file mode 100644
index 0000000000..1866f665cd
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/remainder_spec.rb
@@ -0,0 +1,92 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#remainder" do
+
+ before :each do
+ @zero = BigDecimal("0")
+ @one = BigDecimal("1")
+ @three = BigDecimal("3")
+ @mixed = BigDecimal("1.23456789")
+ @pos_int = BigDecimal("2E5555")
+ @neg_int = BigDecimal("-2E5555")
+ @pos_frac = BigDecimal("2E-9999")
+ @neg_frac = BigDecimal("-2E-9999")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ end
+
+ it "it equals modulo, if both values are of same sign" do
+ BigDecimal('1234567890123456789012345679').remainder(BigDecimal('1')).should == @zero
+ BigDecimal('123456789').remainder(BigDecimal('333333333333333333333333333E-50')).should == BigDecimal('0.12233333333333333333345679E-24')
+
+ @mixed.remainder(@pos_frac).should == @mixed % @pos_frac
+ @pos_int.remainder(@pos_frac).should == @pos_int % @pos_frac
+ @neg_frac.remainder(@neg_int).should == @neg_frac % @neg_int
+ @neg_int.remainder(@neg_frac).should == @neg_int % @neg_frac
+ end
+
+ it "means self-arg*(self/arg).truncate" do
+ @mixed.remainder(@neg_frac).should == @mixed - @neg_frac * (@mixed / @neg_frac).truncate
+ @pos_int.remainder(@neg_frac).should == @pos_int - @neg_frac * (@pos_int / @neg_frac).truncate
+ @neg_frac.remainder(@pos_int).should == @neg_frac - @pos_int * (@neg_frac / @pos_int).truncate
+ @neg_int.remainder(@pos_frac).should == @neg_int - @pos_frac * (@neg_int / @pos_frac).truncate
+ end
+
+ it "returns NaN used with zero" do
+ @mixed.remainder(@zero).should.nan?
+ @zero.remainder(@zero).should.nan?
+ end
+
+ it "returns zero if used on zero" do
+ @zero.remainder(@mixed).should == @zero
+ end
+
+ it "returns NaN if NaN is involved" do
+ @nan.remainder(@nan).should.nan?
+ @nan.remainder(@one).should.nan?
+ @one.remainder(@nan).should.nan?
+ @infinity.remainder(@nan).should.nan?
+ @nan.remainder(@infinity).should.nan?
+ end
+
+ it "returns NaN if Infinity is involved" do
+ @infinity.remainder(@infinity).should.nan?
+ @infinity.remainder(@one).should.nan?
+ @infinity.remainder(@mixed).should.nan?
+ @infinity.remainder(@one_minus).should.nan?
+ @infinity.remainder(@frac_1).should.nan?
+ @one.remainder(@infinity).should.nan?
+
+ @infinity_minus.remainder(@infinity_minus).should.nan?
+ @infinity_minus.remainder(@one).should.nan?
+ @one.remainder(@infinity_minus).should.nan?
+ @frac_2.remainder(@infinity_minus).should.nan?
+
+ @infinity.remainder(@infinity_minus).should.nan?
+ @infinity_minus.remainder(@infinity).should.nan?
+ end
+
+ it "coerces arguments to BigDecimal if possible" do
+ @three.remainder(2).should == @one
+ end
+
+ describe "with Object" do
+ it "tries to coerce the other operand to self" do
+ object = mock("Object")
+ object.should_receive(:coerce).with(@three).and_return([@three, 2])
+ @three.remainder(object).should == @one
+ end
+ end
+
+ it "raises TypeError if the argument cannot be coerced to BigDecimal" do
+ -> {
+ @one.remainder('2')
+ }.should raise_error(TypeError)
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/round_spec.rb b/spec/ruby/library/bigdecimal/round_spec.rb
new file mode 100644
index 0000000000..bfc6dbc763
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/round_spec.rb
@@ -0,0 +1,242 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#round" do
+ before :each do
+ @one = BigDecimal("1")
+ @two = BigDecimal("2")
+ @three = BigDecimal("3")
+
+ @neg_one = BigDecimal("-1")
+ @neg_two = BigDecimal("-2")
+ @neg_three = BigDecimal("-3")
+
+ @p1_50 = BigDecimal("1.50")
+ @p1_51 = BigDecimal("1.51")
+ @p1_49 = BigDecimal("1.49")
+ @n1_50 = BigDecimal("-1.50")
+ @n1_51 = BigDecimal("-1.51")
+ @n1_49 = BigDecimal("-1.49")
+
+ @p2_50 = BigDecimal("2.50")
+ @p2_51 = BigDecimal("2.51")
+ @p2_49 = BigDecimal("2.49")
+ @n2_50 = BigDecimal("-2.50")
+ @n2_51 = BigDecimal("-2.51")
+ @n2_49 = BigDecimal("-2.49")
+ end
+
+ after :each do
+ BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP)
+ end
+
+ it "uses default rounding method unless given" do
+ @p1_50.round(0).should == @two
+ @p1_51.round(0).should == @two
+ @p1_49.round(0).should == @one
+ @n1_50.round(0).should == @neg_two
+ @n1_51.round(0).should == @neg_two
+ @n1_49.round(0).should == @neg_one
+
+ @p2_50.round(0).should == @three
+ @p2_51.round(0).should == @three
+ @p2_49.round(0).should == @two
+ @n2_50.round(0).should == @neg_three
+ @n2_51.round(0).should == @neg_three
+ @n2_49.round(0).should == @neg_two
+
+ BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_DOWN)
+
+ @p1_50.round(0).should == @one
+ @p1_51.round(0).should == @one
+ @p1_49.round(0).should == @one
+ @n1_50.round(0).should == @neg_one
+ @n1_51.round(0).should == @neg_one
+ @n1_49.round(0).should == @neg_one
+
+ @p2_50.round(0).should == @two
+ @p2_51.round(0).should == @two
+ @p2_49.round(0).should == @two
+ @n2_50.round(0).should == @neg_two
+ @n2_51.round(0).should == @neg_two
+ @n2_49.round(0).should == @neg_two
+ end
+
+ ["BigDecimal::ROUND_UP", ":up"].each do |way|
+ describe way do
+ it "rounds values away from zero" do
+ mode = eval(way)
+
+ @p1_50.round(0, mode).should == @two
+ @p1_51.round(0, mode).should == @two
+ @p1_49.round(0, mode).should == @two
+ @n1_50.round(0, mode).should == @neg_two
+ @n1_51.round(0, mode).should == @neg_two
+ @n1_49.round(0, mode).should == @neg_two
+
+ @p2_50.round(0, mode).should == @three
+ @p2_51.round(0, mode).should == @three
+ @p2_49.round(0, mode).should == @three
+ @n2_50.round(0, mode).should == @neg_three
+ @n2_51.round(0, mode).should == @neg_three
+ @n2_49.round(0, mode).should == @neg_three
+ end
+ end
+ end
+
+ ["BigDecimal::ROUND_DOWN", ":down", ":truncate"].each do |way|
+ describe way do
+ it "rounds values towards zero" do
+ mode = eval(way)
+
+ @p1_50.round(0, mode).should == @one
+ @p1_51.round(0, mode).should == @one
+ @p1_49.round(0, mode).should == @one
+ @n1_50.round(0, mode).should == @neg_one
+ @n1_51.round(0, mode).should == @neg_one
+ @n1_49.round(0, mode).should == @neg_one
+
+ @p2_50.round(0, mode).should == @two
+ @p2_51.round(0, mode).should == @two
+ @p2_49.round(0, mode).should == @two
+ @n2_50.round(0, mode).should == @neg_two
+ @n2_51.round(0, mode).should == @neg_two
+ @n2_49.round(0, mode).should == @neg_two
+ end
+ end
+ end
+
+ ["BigDecimal::ROUND_HALF_UP", ":half_up", ":default"].each do |way|
+ describe way do
+ it "rounds values >= 5 up, otherwise down" do
+ mode = eval(way)
+
+ @p1_50.round(0, mode).should == @two
+ @p1_51.round(0, mode).should == @two
+ @p1_49.round(0, mode).should == @one
+ @n1_50.round(0, mode).should == @neg_two
+ @n1_51.round(0, mode).should == @neg_two
+ @n1_49.round(0, mode).should == @neg_one
+
+ @p2_50.round(0, mode).should == @three
+ @p2_51.round(0, mode).should == @three
+ @p2_49.round(0, mode).should == @two
+ @n2_50.round(0, mode).should == @neg_three
+ @n2_51.round(0, mode).should == @neg_three
+ @n2_49.round(0, mode).should == @neg_two
+ end
+ end
+ end
+
+ ["BigDecimal::ROUND_HALF_DOWN", ":half_down"].each do |way|
+ describe way do
+ it "rounds values > 5 up, otherwise down" do
+ mode = eval(way)
+
+ @p1_50.round(0, mode).should == @one
+ @p1_51.round(0, mode).should == @two
+ @p1_49.round(0, mode).should == @one
+ @n1_50.round(0, mode).should == @neg_one
+ @n1_51.round(0, mode).should == @neg_two
+ @n1_49.round(0, mode).should == @neg_one
+
+ @p2_50.round(0, mode).should == @two
+ @p2_51.round(0, mode).should == @three
+ @p2_49.round(0, mode).should == @two
+ @n2_50.round(0, mode).should == @neg_two
+ @n2_51.round(0, mode).should == @neg_three
+ @n2_49.round(0, mode).should == @neg_two
+ end
+ end
+ end
+
+ ["BigDecimal::ROUND_CEILING", ":ceiling", ":ceil"].each do |way|
+ describe way do
+ it "rounds values towards +infinity" do
+ mode = eval(way)
+
+ @p1_50.round(0, mode).should == @two
+ @p1_51.round(0, mode).should == @two
+ @p1_49.round(0, mode).should == @two
+ @n1_50.round(0, mode).should == @neg_one
+ @n1_51.round(0, mode).should == @neg_one
+ @n1_49.round(0, mode).should == @neg_one
+
+ @p2_50.round(0, mode).should == @three
+ @p2_51.round(0, mode).should == @three
+ @p2_49.round(0, mode).should == @three
+ @n2_50.round(0, mode).should == @neg_two
+ @n2_51.round(0, mode).should == @neg_two
+ @n2_49.round(0, mode).should == @neg_two
+ end
+ end
+ end
+
+ ["BigDecimal::ROUND_FLOOR", ":floor"].each do |way|
+ describe way do
+ it "rounds values towards -infinity" do
+ mode = eval(way)
+
+ @p1_50.round(0, mode).should == @one
+ @p1_51.round(0, mode).should == @one
+ @p1_49.round(0, mode).should == @one
+ @n1_50.round(0, mode).should == @neg_two
+ @n1_51.round(0, mode).should == @neg_two
+ @n1_49.round(0, mode).should == @neg_two
+
+ @p2_50.round(0, mode).should == @two
+ @p2_51.round(0, mode).should == @two
+ @p2_49.round(0, mode).should == @two
+ @n2_50.round(0, mode).should == @neg_three
+ @n2_51.round(0, mode).should == @neg_three
+ @n2_49.round(0, mode).should == @neg_three
+ end
+ end
+ end
+
+ ["BigDecimal::ROUND_HALF_EVEN", ":half_even", ":banker"].each do |way|
+ describe way do
+ it "rounds values > 5 up, < 5 down and == 5 towards even neighbor" do
+ mode = eval(way)
+
+ @p1_50.round(0, mode).should == @two
+ @p1_51.round(0, mode).should == @two
+ @p1_49.round(0, mode).should == @one
+ @n1_50.round(0, mode).should == @neg_two
+ @n1_51.round(0, mode).should == @neg_two
+ @n1_49.round(0, mode).should == @neg_one
+
+ @p2_50.round(0, mode).should == @two
+ @p2_51.round(0, mode).should == @three
+ @p2_49.round(0, mode).should == @two
+ @n2_50.round(0, mode).should == @neg_two
+ @n2_51.round(0, mode).should == @neg_three
+ @n2_49.round(0, mode).should == @neg_two
+ end
+ end
+ end
+
+ it 'raise exception, if self is special value' do
+ -> { BigDecimal('NaN').round }.should raise_error(FloatDomainError)
+ -> { BigDecimal('Infinity').round }.should raise_error(FloatDomainError)
+ -> { BigDecimal('-Infinity').round }.should raise_error(FloatDomainError)
+ end
+
+ it 'do not raise exception, if self is special value and precision is given' do
+ -> { BigDecimal('NaN').round(2) }.should_not raise_error(FloatDomainError)
+ -> { BigDecimal('Infinity').round(2) }.should_not raise_error(FloatDomainError)
+ -> { BigDecimal('-Infinity').round(2) }.should_not raise_error(FloatDomainError)
+ end
+
+ ruby_version_is ''...'3.2' do
+ it 'raise for a non-existent round mode' do
+ -> { @p1_50.round(0, :nonsense) }.should raise_error(ArgumentError, "invalid rounding mode")
+ end
+ end
+
+ ruby_version_is '3.2' do
+ it 'raise for a non-existent round mode' do
+ -> { @p1_50.round(0, :nonsense) }.should raise_error(ArgumentError, "invalid rounding mode (nonsense)")
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/shared/clone.rb b/spec/ruby/library/bigdecimal/shared/clone.rb
new file mode 100644
index 0000000000..935ef76e7e
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/shared/clone.rb
@@ -0,0 +1,13 @@
+require 'bigdecimal'
+
+describe :bigdecimal_clone, shared: true do
+ before :each do
+ @obj = BigDecimal("1.2345")
+ end
+
+ it "returns self" do
+ copy = @obj.public_send(@method)
+
+ copy.should equal(@obj)
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/shared/eql.rb b/spec/ruby/library/bigdecimal/shared/eql.rb
new file mode 100644
index 0000000000..8e3e388bab
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/shared/eql.rb
@@ -0,0 +1,61 @@
+require 'bigdecimal'
+
+describe :bigdecimal_eql, shared: true do
+ before :each do
+ @bg6543_21 = BigDecimal("6543.21")
+ @bg5667_19 = BigDecimal("5667.19")
+ @a = BigDecimal("1.0000000000000000000000000000000000000000005")
+ @b = BigDecimal("1.00000000000000000000000000000000000000000005")
+ @bigint = BigDecimal("1000.0")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ end
+
+ it "tests for equality" do
+ @bg6543_21.send(@method, @bg6543_21).should == true
+ @a.send(@method, @a).should == true
+ @a.send(@method, @b).should == false
+ @bg6543_21.send(@method, @a).should == false
+ @bigint.send(@method, 1000).should == true
+ end
+
+ it "returns false for NaN as it is never equal to any number" do
+ @nan.send(@method, @nan).should == false
+ @a.send(@method, @nan).should == false
+ @nan.send(@method, @a).should == false
+ @nan.send(@method, @infinity).should == false
+ @nan.send(@method, @infinity_minus).should == false
+ @infinity.send(@method, @nan).should == false
+ @infinity_minus.send(@method, @nan).should == false
+ end
+
+ it "returns true for infinity values with the same sign" do
+ @infinity.send(@method, @infinity).should == true
+ @infinity.send(@method, BigDecimal("Infinity")).should == true
+ BigDecimal("Infinity").send(@method, @infinity).should == true
+
+ @infinity_minus.send(@method, @infinity_minus).should == true
+ @infinity_minus.send(@method, BigDecimal("-Infinity")).should == true
+ BigDecimal("-Infinity").send(@method, @infinity_minus).should == true
+ end
+
+ it "returns false for infinity values with different signs" do
+ @infinity.send(@method, @infinity_minus).should == false
+ @infinity_minus.send(@method, @infinity).should == false
+ end
+
+ it "returns false when infinite value compared to finite one" do
+ @infinity.send(@method, @a).should == false
+ @infinity_minus.send(@method, @a).should == false
+
+ @a.send(@method, @infinity).should == false
+ @a.send(@method, @infinity_minus).should == false
+ end
+
+ it "returns false when compared objects that can not be coerced into BigDecimal" do
+ @infinity.send(@method, nil).should == false
+ @bigint.send(@method, nil).should == false
+ @nan.send(@method, nil).should == false
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/shared/modulo.rb b/spec/ruby/library/bigdecimal/shared/modulo.rb
new file mode 100644
index 0000000000..aa5c5a640b
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/shared/modulo.rb
@@ -0,0 +1,125 @@
+require 'bigdecimal'
+
+describe :bigdecimal_modulo, shared: true do
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @two = BigDecimal("2")
+ @three = BigDecimal("3")
+ @mixed = BigDecimal("1.23456789")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-9999")
+ @frac_2 = BigDecimal("0.9E-9999")
+ end
+
+ it "returns self modulo other" do
+ bd6543 = BigDecimal("6543.21")
+ bd5667 = BigDecimal("5667.19")
+ a = BigDecimal("1.0000000000000000000000000000000000000000005")
+ b = BigDecimal("1.00000000000000000000000000000000000000000005")
+
+ bd6543.send(@method, 137).should == BigDecimal("104.21")
+ bd5667.send(@method, bignum_value).should == 5667.19
+ bd6543.send(@method, BigDecimal("137.24")).should == BigDecimal("92.93")
+ bd6543.send(@method, 137).should be_close(6543.21.%(137), TOLERANCE)
+ bd6543.send(@method, 137).should == bd6543 % 137
+ bd5667.send(@method, bignum_value).should be_close(5667.19.%(0xffffffff), TOLERANCE)
+ bd5667.send(@method, bignum_value).should == bd5667.%(0xffffffff)
+ bd6543.send(@method, 137.24).should be_close(6543.21.%(137.24), TOLERANCE)
+ a.send(@method, b).should == BigDecimal("0.45E-42")
+ @zero.send(@method, @one).should == @zero
+ @zero.send(@method, @one_minus).should == @zero
+ @two.send(@method, @one).should == @zero
+ @one.send(@method, @two).should == @one
+ @frac_1.send(@method, @one).should == @frac_1
+ @frac_2.send(@method, @one).should == @frac_2
+ @one_minus.send(@method, @one_minus).should == @zero
+ @one_minus.send(@method, @one).should == @zero
+ @one_minus.send(@method, @two).should == @one
+ @one.send(@method, -@two).should == -@one
+
+ @one_minus.modulo(BigDecimal('0.9')).should == BigDecimal('0.8')
+ @one.modulo(BigDecimal('-0.9')).should == BigDecimal('-0.8')
+
+ @one_minus.modulo(BigDecimal('0.8')).should == BigDecimal('0.6')
+ @one.modulo(BigDecimal('-0.8')).should == BigDecimal('-0.6')
+
+ @one_minus.modulo(BigDecimal('0.6')).should == BigDecimal('0.2')
+ @one.modulo(BigDecimal('-0.6')).should == BigDecimal('-0.2')
+
+ @one_minus.modulo(BigDecimal('0.5')).should == @zero
+ @one.modulo(BigDecimal('-0.5')).should == @zero
+ @one_minus.modulo(BigDecimal('-0.5')).should == @zero
+
+ @one_minus.modulo(BigDecimal('0.4')).should == BigDecimal('0.2')
+ @one.modulo(BigDecimal('-0.4')).should == BigDecimal('-0.2')
+
+ @one_minus.modulo(BigDecimal('0.3')).should == BigDecimal('0.2')
+ @one_minus.modulo(BigDecimal('0.2')).should == @zero
+ end
+
+ it "returns a [Float value] when the argument is Float" do
+ @two.send(@method, 2.0).should == 0.0
+ @one.send(@method, 2.0).should == 1.0
+ res = @two.send(@method, 5.0)
+ res.kind_of?(BigDecimal).should == true
+ end
+
+ describe "with Object" do
+ it "tries to coerce the other operand to self" do
+ bd6543 = BigDecimal("6543.21")
+ object = mock("Object")
+ object.should_receive(:coerce).with(bd6543).and_return([bd6543, 137])
+ bd6543.send(@method, object, *@object).should == BigDecimal("104.21")
+ end
+ end
+
+ it "returns NaN if NaN is involved" do
+ @nan.send(@method, @nan).should.nan?
+ @nan.send(@method, @one).should.nan?
+ @one.send(@method, @nan).should.nan?
+ @infinity.send(@method, @nan).should.nan?
+ @nan.send(@method, @infinity).should.nan?
+ end
+
+ it "returns NaN if the dividend is Infinity" do
+ @infinity.send(@method, @infinity).should.nan?
+ @infinity.send(@method, @one).should.nan?
+ @infinity.send(@method, @mixed).should.nan?
+ @infinity.send(@method, @one_minus).should.nan?
+ @infinity.send(@method, @frac_1).should.nan?
+
+ @infinity_minus.send(@method, @infinity_minus).should.nan?
+ @infinity_minus.send(@method, @one).should.nan?
+
+ @infinity.send(@method, @infinity_minus).should.nan?
+ @infinity_minus.send(@method, @infinity).should.nan?
+ end
+
+ it "returns the dividend if the divisor is Infinity" do
+ @one.send(@method, @infinity).should == @one
+ @one.send(@method, @infinity_minus).should == @one
+ @frac_2.send(@method, @infinity_minus).should == @frac_2
+ end
+
+ it "raises TypeError if the argument cannot be coerced to BigDecimal" do
+ -> {
+ @one.send(@method, '2')
+ }.should raise_error(TypeError)
+ end
+end
+
+describe :bigdecimal_modulo_zerodivisionerror, shared: true do
+ it "raises ZeroDivisionError if other is zero" do
+ bd5667 = BigDecimal("5667.19")
+
+ -> { bd5667.send(@method, 0) }.should raise_error(ZeroDivisionError)
+ -> { bd5667.send(@method, BigDecimal("0")) }.should raise_error(ZeroDivisionError)
+ -> { @zero.send(@method, @zero) }.should raise_error(ZeroDivisionError)
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/shared/mult.rb b/spec/ruby/library/bigdecimal/shared/mult.rb
new file mode 100644
index 0000000000..c613df04c4
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/shared/mult.rb
@@ -0,0 +1,97 @@
+require 'bigdecimal'
+
+describe :bigdecimal_mult, shared: true do
+ before :each do
+ @zero = BigDecimal "0"
+ @zero_pos = BigDecimal "+0"
+ @zero_neg = BigDecimal "-0"
+
+ @one = BigDecimal "1"
+ @mixed = BigDecimal "1.23456789"
+ @pos_int = BigDecimal "2E5555"
+ @neg_int = BigDecimal "-2E5555"
+ @pos_frac = BigDecimal "2E-9999"
+ @neg_frac = BigDecimal "-2E-9999"
+ @nan = BigDecimal "NaN"
+ @infinity = BigDecimal "Infinity"
+ @infinity_minus = BigDecimal "-Infinity"
+ @one_minus = BigDecimal "-1"
+ @frac_1 = BigDecimal "1E-99999"
+ @frac_2 = BigDecimal "0.9E-99999"
+
+ @e3_minus = BigDecimal "3E-20001"
+ @e = BigDecimal "1.00000000000000000000123456789"
+ @tolerance = @e.sub @one, 1000
+ @tolerance2 = BigDecimal "30001E-20005"
+
+ @special_vals = [@infinity, @infinity_minus, @nan]
+ @regular_vals = [ @one, @mixed, @pos_int, @neg_int,
+ @pos_frac, @neg_frac, @one_minus,
+ @frac_1, @frac_2
+ ]
+ @zeroes = [@zero, @zero_pos, @zero_neg]
+ end
+
+ it "returns zero of appropriate sign if self or argument is zero" do
+ @zero.send(@method, @zero, *@object).sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ @zero_neg.send(@method, @zero_neg, *@object).sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ @zero.send(@method, @zero_neg, *@object).sign.should == BigDecimal::SIGN_NEGATIVE_ZERO
+ @zero_neg.send(@method, @zero, *@object).sign.should == BigDecimal::SIGN_NEGATIVE_ZERO
+
+ @one.send(@method, @zero, *@object).sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ @one.send(@method, @zero_neg, *@object).sign.should == BigDecimal::SIGN_NEGATIVE_ZERO
+
+ @zero.send(@method, @one, *@object).sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ @zero.send(@method, @one_minus, *@object).sign.should == BigDecimal::SIGN_NEGATIVE_ZERO
+ @zero_neg.send(@method, @one_minus, *@object).sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ @zero_neg.send(@method, @one, *@object).sign.should == BigDecimal::SIGN_NEGATIVE_ZERO
+ end
+
+ it "returns NaN if NaN is involved" do
+ values = @regular_vals + @zeroes
+
+ values.each do |val|
+ @nan.send(@method, val, *@object).should.nan?
+ val.send(@method, @nan, *@object).should.nan?
+ end
+ end
+
+ it "returns zero if self or argument is zero" do
+ values = @regular_vals + @zeroes
+
+ values.each do |val|
+ @zeroes.each do |zero|
+ zero.send(@method, val, *@object).should == 0
+ zero.send(@method, val, *@object).should.zero?
+ val.send(@method, zero, *@object).should == 0
+ val.send(@method, zero, *@object).should.zero?
+ end
+ end
+ end
+
+ it "returns infinite value if self or argument is infinite" do
+ values = @regular_vals
+ infs = [@infinity, @infinity_minus]
+
+ values.each do |val|
+ infs.each do |inf|
+ inf.send(@method, val, *@object).should_not.finite?
+ val.send(@method, inf, *@object).should_not.finite?
+ end
+ end
+
+ @infinity.send(@method, @infinity, *@object).infinite?.should == 1
+ @infinity_minus.send(@method, @infinity_minus, *@object).infinite?.should == 1
+ @infinity.send(@method, @infinity_minus, *@object).infinite?.should == -1
+ @infinity_minus.send(@method, @infinity, *@object).infinite?.should == -1
+ @infinity.send(@method, @one, *@object).infinite?.should == 1
+ @infinity_minus.send(@method, @one, *@object).infinite?.should == -1
+ end
+
+ it "returns NaN if the result is undefined" do
+ @zero.send(@method, @infinity, *@object).should.nan?
+ @zero.send(@method, @infinity_minus, *@object).should.nan?
+ @infinity.send(@method, @zero, *@object).should.nan?
+ @infinity_minus.send(@method, @zero, *@object).should.nan?
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/shared/power.rb b/spec/ruby/library/bigdecimal/shared/power.rb
new file mode 100644
index 0000000000..568a08589b
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/shared/power.rb
@@ -0,0 +1,72 @@
+require 'bigdecimal'
+
+describe :bigdecimal_power, shared: true do
+ it "powers of self" do
+ e3_minus = BigDecimal("3E-20001")
+ e3_minus_power_2 = BigDecimal("9E-40002")
+ e3_plus = BigDecimal("3E20001")
+ e2_plus = BigDecimal("2E40001")
+ e5_minus = BigDecimal("5E-40002")
+ e = BigDecimal("1.00000000000000000000123456789")
+ one = BigDecimal("1")
+ ten = BigDecimal("10")
+ # The tolerance is dependent upon the size of BASE_FIG
+ tolerance = BigDecimal("1E-70")
+ ten_powers = BigDecimal("1E10000")
+ pi = BigDecimal("3.14159265358979")
+ e3_minus.send(@method, 2).should == e3_minus_power_2
+ e3_plus.send(@method, 0).should == 1
+ e3_minus.send(@method, 1).should == e3_minus
+ e2_plus.send(@method, -1).should == e5_minus
+ e2_plus.send(@method, -1).should == e5_minus.power(1)
+ (e2_plus.send(@method, -1) * e5_minus.send(@method, -1)).should == 1
+ e.send(@method, 2).should == e * e
+ e.send(@method, -1).should be_close(one.div(e, 120), tolerance)
+ ten.send(@method, 10000).should == ten_powers
+ pi.send(@method, 10).should be_close(Math::PI ** 10, TOLERANCE)
+ end
+
+ it "powers of 1 equal 1" do
+ one = BigDecimal("1")
+ one.send(@method, 0).should == 1
+ one.send(@method, 1).should == 1
+ one.send(@method, 10).should == 1
+ one.send(@method, -10).should == 1
+ end
+
+ it "0 to power of 0 is 1" do
+ zero = BigDecimal("0")
+ zero.send(@method, 0).should == 1
+ end
+
+ it "0 to powers < 0 is Infinity" do
+ zero = BigDecimal("0")
+ infinity = BigDecimal("Infinity")
+ zero.send(@method, -10).should == infinity
+ zero.send(@method, -1).should == infinity
+ end
+
+ it "other powers of 0 are 0" do
+ zero = BigDecimal("0")
+ zero.send(@method, 1).should == 0
+ zero.send(@method, 10).should == 0
+ end
+
+ it "returns NaN if self is NaN" do
+ BigDecimal("NaN").send(@method, -5).should.nan?
+ BigDecimal("NaN").send(@method, 5).should.nan?
+ end
+
+ it "returns 0.0 if self is infinite and argument is negative" do
+ BigDecimal("Infinity").send(@method, -5).should == 0
+ BigDecimal("-Infinity").send(@method, -5).should == 0
+ end
+
+ it "returns infinite if self is infinite and argument is positive" do
+ infinity = BigDecimal("Infinity")
+ BigDecimal("Infinity").send(@method, 4).should == infinity
+ BigDecimal("-Infinity").send(@method, 4).should == infinity
+ BigDecimal("Infinity").send(@method, 5).should == infinity
+ BigDecimal("-Infinity").send(@method, 5).should == -infinity
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/shared/quo.rb b/spec/ruby/library/bigdecimal/shared/quo.rb
new file mode 100644
index 0000000000..46e6d62bf4
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/shared/quo.rb
@@ -0,0 +1,67 @@
+require 'bigdecimal'
+
+describe :bigdecimal_quo, shared: true do
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @zero_plus = BigDecimal("+0")
+ @zero_minus = BigDecimal("-0")
+ @two = BigDecimal("2")
+ @three = BigDecimal("3")
+ @eleven = BigDecimal("11")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ end
+
+ it "returns a / b" do
+ @two.send(@method, @one, *@object).should == @two
+ @one.send(@method, @two, *@object).should == BigDecimal("0.5")
+ @eleven.send(@method, @three, *@object).should be_close(@three + (@two / @three), TOLERANCE)
+ @one.send(@method, @one_minus, *@object).should == @one_minus
+ @one_minus.send(@method, @one_minus, *@object).should == @one
+ @frac_2.send(@method, @frac_1, *@object).should == BigDecimal("0.9")
+ @frac_1.send(@method, @frac_1, *@object).should == @one
+ @one.send(@method, BigDecimal('-2E5555'), *@object).should == BigDecimal('-0.5E-5555')
+ @one.send(@method, BigDecimal('2E-5555'), *@object).should == BigDecimal('0.5E5555')
+ end
+
+ describe "with Object" do
+ it "tries to coerce the other operand to self" do
+ object = mock("Object")
+ object.should_receive(:coerce).with(@one).and_return([@one, @two])
+ @one.send(@method, object, *@object).should == BigDecimal("0.5")
+ end
+ end
+
+ it "returns 0 if divided by Infinity" do
+ @zero.send(@method, @infinity, *@object).should == 0
+ @frac_2.send(@method, @infinity, *@object).should == 0
+ end
+
+ it "returns (+|-) Infinity if (+|-) Infinity divided by one" do
+ @infinity_minus.send(@method, @one, *@object).should == @infinity_minus
+ @infinity.send(@method, @one, *@object).should == @infinity
+ @infinity_minus.send(@method, @one_minus, *@object).should == @infinity
+ end
+
+ it "returns NaN if Infinity / ((+|-) Infinity)" do
+ @infinity.send(@method, @infinity_minus, *@object).should.nan?
+ @infinity_minus.send(@method, @infinity, *@object).should.nan?
+ end
+
+ it "returns (+|-) Infinity if divided by zero" do
+ @one.send(@method, @zero, *@object).should == @infinity
+ @one.send(@method, @zero_plus, *@object).should == @infinity
+ @one.send(@method, @zero_minus, *@object).should == @infinity_minus
+ end
+
+ it "returns NaN if zero is divided by zero" do
+ @zero.send(@method, @zero, *@object).should.nan?
+ @zero_minus.send(@method, @zero_plus, *@object).should.nan?
+ @zero_plus.send(@method, @zero_minus, *@object).should.nan?
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/shared/to_int.rb b/spec/ruby/library/bigdecimal/shared/to_int.rb
new file mode 100644
index 0000000000..0f16251612
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/shared/to_int.rb
@@ -0,0 +1,16 @@
+require 'bigdecimal'
+
+describe :bigdecimal_to_int , shared: true do
+ it "raises FloatDomainError if BigDecimal is infinity or NaN" do
+ -> { BigDecimal("Infinity").send(@method) }.should raise_error(FloatDomainError)
+ -> { BigDecimal("NaN").send(@method) }.should raise_error(FloatDomainError)
+ end
+
+ it "returns Integer otherwise" do
+ BigDecimal("3E-20001").send(@method).should == 0
+ BigDecimal("2E4000").send(@method).should == 2 * 10 ** 4000
+ BigDecimal("2").send(@method).should == 2
+ BigDecimal("2E10").send(@method).should == 20000000000
+ BigDecimal("3.14159").send(@method).should == 3
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/sign_spec.rb b/spec/ruby/library/bigdecimal/sign_spec.rb
new file mode 100644
index 0000000000..ae2c28e9fd
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/sign_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#sign" do
+
+ it "defines several constants for signs" do
+ # are these really correct?
+ BigDecimal::SIGN_POSITIVE_INFINITE.should == 3
+ BigDecimal::SIGN_NEGATIVE_INFINITE.should == -3
+ BigDecimal::SIGN_POSITIVE_ZERO.should == 1
+ BigDecimal::SIGN_NEGATIVE_ZERO.should == -1
+ BigDecimal::SIGN_POSITIVE_FINITE.should == 2
+ BigDecimal::SIGN_NEGATIVE_FINITE.should == -2
+ end
+
+ it "returns positive value if BigDecimal greater than 0" do
+ BigDecimal("1").sign.should == BigDecimal::SIGN_POSITIVE_FINITE
+ BigDecimal("1E-20000000").sign.should == BigDecimal::SIGN_POSITIVE_FINITE
+ BigDecimal("1E200000000").sign.should == BigDecimal::SIGN_POSITIVE_FINITE
+ BigDecimal("Infinity").sign.should == BigDecimal::SIGN_POSITIVE_INFINITE
+ end
+
+ it "returns negative value if BigDecimal less than 0" do
+ BigDecimal("-1").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE
+ BigDecimal("-1E-9990000").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE
+ BigDecimal("-1E20000000").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE
+ BigDecimal("-Infinity").sign.should == BigDecimal::SIGN_NEGATIVE_INFINITE
+ end
+
+ it "returns positive zero if BigDecimal equals positive zero" do
+ BigDecimal("0").sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ BigDecimal("0E-200000000").sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ BigDecimal("0E200000000").sign.should == BigDecimal::SIGN_POSITIVE_ZERO
+ end
+
+ it "returns negative zero if BigDecimal equals negative zero" do
+ BigDecimal("-0").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO
+ BigDecimal("-0E-200000000").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO
+ BigDecimal("-0E200000000").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO
+ end
+
+ it "returns BigDecimal::SIGN_NaN if BigDecimal is NaN" do
+ BigDecimal("NaN").sign.should == BigDecimal::SIGN_NaN
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/split_spec.rb b/spec/ruby/library/bigdecimal/split_spec.rb
new file mode 100644
index 0000000000..f9b4bab5f7
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/split_spec.rb
@@ -0,0 +1,86 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#split" do
+
+ before :each do
+ @arr = BigDecimal("0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1").split
+ @arr_neg = BigDecimal("-0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1").split
+ @digits = "922337203685477580810101333333333333333333333333333"
+ @arr_big = BigDecimal("00#{@digits}000").split
+ @arr_big_neg = BigDecimal("-00#{@digits}000").split
+ @huge = BigDecimal('100000000000000000000000000000000000000000001E90000000').split
+
+ @infinity = BigDecimal("Infinity")
+ @infinity_neg = BigDecimal("-Infinity")
+ @nan = BigDecimal("NaN")
+ @zero = BigDecimal("0")
+ @zero_neg = BigDecimal("-0")
+ end
+
+ it "splits BigDecimal in an array with four values" do
+ @arr.size.should == 4
+ end
+
+ it "first value: 1 for numbers > 0" do
+ @arr[0].should == 1
+ @arr_big[0].should == 1
+ @zero.split[0].should == 1
+ @huge[0].should == 1
+ BigDecimal("+0").split[0].should == 1
+ BigDecimal("1E400").split[0].should == 1
+ @infinity.split[0].should == 1
+ end
+
+ it "first value: -1 for numbers < 0" do
+ @arr_neg[0].should == -1
+ @arr_big_neg[0].should == -1
+ @zero_neg.split[0].should == -1
+ BigDecimal("-1E400").split[0].should == -1
+ @infinity_neg.split[0].should == -1
+ end
+
+ it "first value: 0 if BigDecimal is NaN" do
+ BigDecimal("NaN").split[0].should == 0
+ end
+
+ it "second value: a string with the significant digits" do
+ string = "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043"
+ @arr[1].should == string
+ @arr_big[1].should == @digits
+ @arr_big_neg[1].should == @digits
+ @huge[1].should == "100000000000000000000000000000000000000000001"
+ @infinity.split[1].should == @infinity.to_s
+ @nan.split[1].should == @nan.to_s
+ @infinity_neg.split[1].should == @infinity.to_s
+ @zero.split[1].should == "0"
+ BigDecimal("-0").split[1].should == "0"
+ end
+
+ it "third value: the base (currently always ten)" do
+ @arr[2].should == 10
+ @arr_neg[2].should == 10
+ @arr_big[2].should == 10
+ @arr_big_neg[2].should == 10
+ @huge[2].should == 10
+ @infinity.split[2].should == 10
+ @nan.split[2].should == 10
+ @infinity_neg.split[2].should == 10
+ @zero.split[2].should == 10
+ @zero_neg.split[2].should == 10
+ end
+
+ it "fourth value: the exponent" do
+ @arr[3].should == 1
+ @arr_neg[3].should == 1
+ @arr_big[3].should == 54
+ @arr_big_neg[3].should == 54
+ @huge[3].should == 90000045
+ @infinity.split[3].should == 0
+ @nan.split[3].should == 0
+ @infinity_neg.split[3].should == 0
+ @zero.split[3].should == 0
+ @zero_neg.split[3].should == 0
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/sqrt_spec.rb b/spec/ruby/library/bigdecimal/sqrt_spec.rb
new file mode 100644
index 0000000000..d149003b9f
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/sqrt_spec.rb
@@ -0,0 +1,112 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require 'bigdecimal'
+
+describe "BigDecimal#sqrt" do
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @two = BigDecimal("2.0")
+ @three = BigDecimal("3.0")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ end
+
+ it "returns square root of 2 with desired precision" do
+ string = "1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157"
+ (1..99).each { |idx|
+ @two.sqrt(idx).should be_close(BigDecimal(string), BigDecimal("1E-#{idx-1}"))
+ }
+ end
+
+ it "returns square root of 3 with desired precision" do
+ sqrt_3 = "1.732050807568877293527446341505872366942805253810380628055806979451933016908800037081146186757248575"
+ (1..99).each { |idx|
+ @three.sqrt(idx).should be_close(BigDecimal(sqrt_3), BigDecimal("1E-#{idx-1}"))
+ }
+ end
+
+ it "returns square root of 121 with desired precision" do
+ BigDecimal('121').sqrt(5).should be_close(11, 0.00001)
+ end
+
+ it "returns square root of 0.9E-99999 with desired precision" do
+ @frac_2.sqrt(1).to_s.should =~ /\A0\.3E-49999\z/i
+ end
+
+ it "raises ArgumentError when no argument is given" do
+ -> {
+ @one.sqrt
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if a negative number is given" do
+ -> {
+ @one.sqrt(-1)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if 2 arguments are given" do
+ -> {
+ @one.sqrt(1, 1)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises TypeError if nil is given" do
+ -> {
+ @one.sqrt(nil)
+ }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError if a string is given" do
+ -> {
+ @one.sqrt("stuff")
+ }.should raise_error(TypeError)
+ end
+
+ it "raises TypeError if a plain Object is given" do
+ -> {
+ @one.sqrt(Object.new)
+ }.should raise_error(TypeError)
+ end
+
+ it "returns 1 if precision is 0 or 1" do
+ @one.sqrt(1).should == 1
+ @one.sqrt(0).should == 1
+ end
+
+ it "raises FloatDomainError on negative values" do
+ -> {
+ BigDecimal('-1').sqrt(10)
+ }.should raise_error(FloatDomainError)
+ end
+
+ it "returns positive infinity for infinity" do
+ @infinity.sqrt(1).should == @infinity
+ end
+
+ it "raises FloatDomainError for negative infinity" do
+ -> {
+ @infinity_minus.sqrt(1)
+ }.should raise_error(FloatDomainError)
+ end
+
+ it "raises FloatDomainError for NaN" do
+ -> {
+ @nan.sqrt(1)
+ }.should raise_error(FloatDomainError)
+ end
+
+ it "returns 0 for 0, +0.0 and -0.0" do
+ @zero.sqrt(1).should == 0
+ @zero_pos.sqrt(1).should == 0
+ @zero_neg.sqrt(1).should == 0
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/sub_spec.rb b/spec/ruby/library/bigdecimal/sub_spec.rb
new file mode 100644
index 0000000000..bddfec2186
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/sub_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#sub" do
+
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @two = BigDecimal("2")
+ @three = BigDecimal("3")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ @frac_3 = BigDecimal("12345E10")
+ @frac_4 = BigDecimal("98765E10")
+ end
+
+ it "returns a - b with given precision" do
+ # documentation states, that precision is optional
+ # but implementation raises ArgumentError if not given.
+
+ @two.sub(@one, 1).should == @one
+ @one.sub(@two, 1).should == @one_minus
+ @one.sub(@one_minus, 1).should == @two
+ @frac_2.sub(@frac_1, 1000000).should == BigDecimal("-0.1E-99999")
+ @frac_2.sub(@frac_1, 1).should == BigDecimal("-0.1E-99999")
+ # the above two examples puzzle me.
+ in_arow_one = BigDecimal("1.23456789")
+ in_arow_two = BigDecimal("1.2345678")
+ in_arow_one.sub(in_arow_two, 10).should == BigDecimal("0.9E-7")
+ @two.sub(@two,1).should == @zero
+ @frac_1.sub(@frac_1, 1000000).should == @zero
+ end
+
+ describe "with Object" do
+ it "tries to coerce the other operand to self" do
+ object = mock("Object")
+ object.should_receive(:coerce).with(@frac_3).and_return([@frac_3, @frac_4])
+ @frac_3.sub(object, 1).should == BigDecimal("-0.9E15")
+ end
+ end
+
+ describe "with Rational" do
+ it "produces a BigDecimal" do
+ (@three - Rational(500, 2)).should == BigDecimal('-0.247e3')
+ end
+ end
+
+ it "returns NaN if NaN is involved" do
+ @one.sub(@nan, 1).should.nan?
+ @nan.sub(@one, 1).should.nan?
+ end
+
+ it "returns NaN if both values are infinite with the same signs" do
+ @infinity.sub(@infinity, 1).should.nan?
+ @infinity_minus.sub(@infinity_minus, 1).should.nan?
+ end
+
+ it "returns Infinity or -Infinity if these are involved" do
+ @infinity.sub(@infinity_minus, 1).should == @infinity
+ @infinity_minus.sub(@infinity, 1).should == @infinity_minus
+ @zero.sub(@infinity, 1).should == @infinity_minus
+ @frac_2.sub( @infinity, 1).should == @infinity_minus
+ @two.sub(@infinity, 1).should == @infinity_minus
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/to_d_spec.rb b/spec/ruby/library/bigdecimal/to_d_spec.rb
new file mode 100644
index 0000000000..50aea99bf7
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/to_d_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+require 'bigdecimal/util'
+
+describe "Float#to_d" do
+ it "returns appropriate BigDecimal zero for signed zero" do
+ -0.0.to_d.sign.should == -1
+ 0.0.to_d.sign.should == 1
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/to_f_spec.rb b/spec/ruby/library/bigdecimal/to_f_spec.rb
new file mode 100644
index 0000000000..84d4d49de2
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/to_f_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#to_f" do
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @two = BigDecimal("2")
+ @three = BigDecimal("3")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ @vals = [@one, @zero, @two, @three, @frac_1, @frac_2]
+ @spec_vals = [@zero_pos, @zero_neg, @nan, @infinity, @infinity_minus]
+ end
+
+ it "returns number of type float" do
+ BigDecimal("3.14159").to_f.should be_kind_of(Float)
+ @vals.each { |val| val.to_f.should be_kind_of(Float) }
+ @spec_vals.each { |val| val.to_f.should be_kind_of(Float) }
+ end
+
+ it "rounds correctly to Float precision" do
+ bigdec = BigDecimal("3.141592653589793238462643383279502884197169399375")
+ bigdec.to_f.should be_close(3.14159265358979, TOLERANCE)
+ @one.to_f.should == 1.0
+ @two.to_f.should == 2.0
+ @three.to_f.should be_close(3.0, TOLERANCE)
+ @one_minus.to_f.should == -1.0
+
+ # regression test for [ruby-talk:338957]
+ BigDecimal("10.03").to_f.should == 10.03
+ end
+
+ it "properly handles special values" do
+ @zero.to_f.should == 0
+ @zero.to_f.to_s.should == "0.0"
+
+ @nan.to_f.should.nan?
+
+ @infinity.to_f.infinite?.should == 1
+ @infinity_minus.to_f.infinite?.should == -1
+ end
+
+ it "remembers negative zero when converted to float" do
+ @zero_neg.to_f.should == 0
+ @zero_neg.to_f.to_s.should == "-0.0"
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/to_i_spec.rb b/spec/ruby/library/bigdecimal/to_i_spec.rb
new file mode 100644
index 0000000000..09481fce15
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/to_i_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_int'
+require 'bigdecimal'
+
+describe "BigDecimal#to_i" do
+ it_behaves_like :bigdecimal_to_int, :to_i
+end
diff --git a/spec/ruby/library/bigdecimal/to_int_spec.rb b/spec/ruby/library/bigdecimal/to_int_spec.rb
new file mode 100644
index 0000000000..4df6749845
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/to_int_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_int'
+require 'bigdecimal'
+
+
+describe "BigDecimal#to_int" do
+ it_behaves_like :bigdecimal_to_int, :to_int
+end
diff --git a/spec/ruby/library/bigdecimal/to_r_spec.rb b/spec/ruby/library/bigdecimal/to_r_spec.rb
new file mode 100644
index 0000000000..c350beff08
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/to_r_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#to_r" do
+
+ it "returns a Rational" do
+ BigDecimal("3.14159").to_r.should be_kind_of(Rational)
+ end
+
+ it "returns a Rational with bignum values" do
+ r = BigDecimal("3.141592653589793238462643").to_r
+ r.numerator.should eql(3141592653589793238462643)
+ r.denominator.should eql(1000000000000000000000000)
+ end
+
+ it "returns a Rational from a BigDecimal with an exponent" do
+ r = BigDecimal("1E2").to_r
+ r.numerator.should eql(100)
+ r.denominator.should eql(1)
+ end
+
+ it "returns a Rational from a negative BigDecimal with an exponent" do
+ r = BigDecimal("-1E2").to_r
+ r.numerator.should eql(-100)
+ r.denominator.should eql(1)
+ end
+
+end
diff --git a/spec/ruby/library/bigdecimal/to_s_spec.rb b/spec/ruby/library/bigdecimal/to_s_spec.rb
new file mode 100644
index 0000000000..4f1054d38e
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/to_s_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#to_s" do
+
+ before :each do
+ @bigdec_str = "3.14159265358979323846264338327950288419716939937"
+ @bigneg_str = "-3.1415926535897932384626433832795028841971693993"
+ @bigdec = BigDecimal(@bigdec_str)
+ @bigneg = BigDecimal(@bigneg_str)
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "return type is of class String" do
+ @bigdec.to_s.kind_of?(String).should == true
+ @bigneg.to_s.kind_of?(String).should == true
+ end
+
+ it "the default format looks like 0.xxxxenn" do
+ @bigdec.to_s.should =~ /^0\.[0-9]*e[0-9]*$/
+ end
+
+ it "does not add an exponent for zero values" do
+ BigDecimal("0").to_s.should == "0.0"
+ BigDecimal("+0").to_s.should == "0.0"
+ BigDecimal("-0").to_s.should == "-0.0"
+ end
+
+ it "takes an optional argument" do
+ -> {@bigdec.to_s("F")}.should_not raise_error()
+ end
+
+ it "starts with + if + is supplied and value is positive" do
+ @bigdec.to_s("+").should =~ /^\+.*/
+ @bigneg.to_s("+").should_not =~ /^\+.*/
+ end
+
+ it "inserts a space every n chars, if integer n is supplied" do
+ re =\
+ /\A0\.314 159 265 358 979 323 846 264 338 327 950 288 419 716 939 937E1\z/i
+ @bigdec.to_s(3).should =~ re
+
+ str1 = '-123.45678 90123 45678 9'
+ BigDecimal("-123.45678901234567890").to_s('5F').should == str1
+ BigDecimal('1000010').to_s('5F').should == "10000 10.0"
+ # trailing zeroes removed
+ BigDecimal("1.00000000000").to_s('1F').should == "1.0"
+ # 0 is treated as no spaces
+ BigDecimal("1.2345").to_s('0F').should == "1.2345"
+ end
+
+ it "can return a leading space for values > 0" do
+ @bigdec.to_s(" F").should =~ /\ .*/
+ @bigneg.to_s(" F").should_not =~ /\ .*/
+ end
+
+ it "removes trailing spaces in floating point notation" do
+ BigDecimal('-123.45678901234567890').to_s('F').should == "-123.4567890123456789"
+ BigDecimal('1.2500').to_s('F').should == "1.25"
+ BigDecimal('0000.00000').to_s('F').should == "0.0"
+ BigDecimal('-00.000010000').to_s('F').should == "-0.00001"
+ BigDecimal("5.00000E-2").to_s("F").should == "0.05"
+
+ BigDecimal("500000").to_s("F").should == "500000.0"
+ BigDecimal("5E2").to_s("F").should == "500.0"
+ BigDecimal("-5E100").to_s("F").should == "-5" + "0" * 100 + ".0"
+ end
+
+ it "can use engineering notation" do
+ @bigdec.to_s("E").should =~ /^0\.[0-9]*E[0-9]*$/i
+ end
+
+ it "can use conventional floating point notation" do
+ %w[f F].each do |format_char|
+ @bigdec.to_s(format_char).should == @bigdec_str
+ @bigneg.to_s(format_char).should == @bigneg_str
+ str2 = "+123.45678901 23456789"
+ BigDecimal('123.45678901234567890').to_s("+8#{format_char}").should == str2
+ end
+ end
+
+ ruby_version_is "3.0" do
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ BigDecimal('1.23').to_s.encoding.should equal(Encoding::US_ASCII)
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do
+ Encoding.default_internal = Encoding::IBM437
+ BigDecimal('1.23').to_s.encoding.should equal(Encoding::US_ASCII)
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/truncate_spec.rb b/spec/ruby/library/bigdecimal/truncate_spec.rb
new file mode 100644
index 0000000000..4ad9eb92d1
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/truncate_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#truncate" do
+
+ before :each do
+ @arr = ['3.14159', '8.7', "0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1"]
+ @big = BigDecimal("123456.789")
+ @nan = BigDecimal('NaN')
+ @infinity = BigDecimal('Infinity')
+ @infinity_negative = BigDecimal('-Infinity')
+ end
+
+ it "returns value of type Integer." do
+ @arr.each do |x|
+ BigDecimal(x).truncate.kind_of?(Integer).should == true
+ end
+ end
+
+ it "returns the integer part as a BigDecimal if no precision given" do
+ BigDecimal(@arr[0]).truncate.should == 3
+ BigDecimal(@arr[1]).truncate.should == 8
+ BigDecimal(@arr[2]).truncate.should == 3
+ BigDecimal('0').truncate.should == 0
+ BigDecimal('0.1').truncate.should == 0
+ BigDecimal('-0.1').truncate.should == 0
+ BigDecimal('1.5').truncate.should == 1
+ BigDecimal('-1.5').truncate.should == -1
+ BigDecimal('1E10').truncate.should == BigDecimal('1E10')
+ BigDecimal('-1E10').truncate.should == BigDecimal('-1E10')
+ BigDecimal('1.8888E10').truncate.should == BigDecimal('1.8888E10')
+ BigDecimal('-1E-1').truncate.should == 0
+ end
+
+ it "returns value of given precision otherwise" do
+ BigDecimal('-1.55').truncate(1).should == BigDecimal('-1.5')
+ BigDecimal('1.55').truncate(1).should == BigDecimal('1.5')
+ BigDecimal(@arr[0]).truncate(2).should == BigDecimal("3.14")
+ BigDecimal('123.456').truncate(2).should == BigDecimal("123.45")
+ BigDecimal('123.456789').truncate(4).should == BigDecimal("123.4567")
+ BigDecimal('0.456789').truncate(10).should == BigDecimal("0.456789")
+ BigDecimal('-1E-1').truncate(1).should == BigDecimal('-0.1')
+ BigDecimal('-1E-1').truncate(2).should == BigDecimal('-0.1E0')
+ BigDecimal('-1E-1').truncate.should == BigDecimal('0')
+ BigDecimal('-1E-1').truncate(0).should == BigDecimal('0')
+ BigDecimal('-1E-1').truncate(-1).should == BigDecimal('0')
+ BigDecimal('-1E-1').truncate(-2).should == BigDecimal('0')
+
+ BigDecimal(@arr[1]).truncate(1).should == BigDecimal("8.7")
+ BigDecimal(@arr[2]).truncate(100).should == BigDecimal(\
+ "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679")
+ end
+
+ it "sets n digits left of the decimal point to 0, if given n < 0" do
+ @big.truncate(-1).should == BigDecimal("123450.0")
+ @big.truncate(-2).should == BigDecimal("123400.0")
+ BigDecimal(@arr[2]).truncate(-1).should == 0
+ end
+
+ it "returns NaN if self is NaN" do
+ @nan.truncate(-1).should.nan?
+ @nan.truncate(+1).should.nan?
+ @nan.truncate(0).should.nan?
+ end
+
+ it "returns Infinity if self is infinite" do
+ @infinity.truncate(-1).should == @infinity
+ @infinity.truncate(+1).should == @infinity
+ @infinity.truncate(0).should == @infinity
+
+ @infinity_negative.truncate(-1).should == @infinity_negative
+ @infinity_negative.truncate(+1).should == @infinity_negative
+ @infinity_negative.truncate(0).should == @infinity_negative
+ end
+
+ it "returns the same value if self is special value" do
+ -> { @nan.truncate }.should raise_error(FloatDomainError)
+ -> { @infinity.truncate }.should raise_error(FloatDomainError)
+ -> { @infinity_negative.truncate }.should raise_error(FloatDomainError)
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/uminus_spec.rb b/spec/ruby/library/bigdecimal/uminus_spec.rb
new file mode 100644
index 0000000000..c780cdfac5
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/uminus_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#-@" do
+ before :each do
+ @one = BigDecimal("1")
+ @zero = BigDecimal("0")
+ @zero_pos = BigDecimal("+0")
+ @zero_neg = BigDecimal("-0")
+ @nan = BigDecimal("NaN")
+ @infinity = BigDecimal("Infinity")
+ @infinity_minus = BigDecimal("-Infinity")
+ @one_minus = BigDecimal("-1")
+ @frac_1 = BigDecimal("1E-99999")
+ @frac_2 = BigDecimal("0.9E-99999")
+ @big = BigDecimal("333E99999")
+ @big_neg = BigDecimal("-333E99999")
+ @values = [@one, @zero, @zero_pos, @zero_neg, @infinity,
+ @infinity_minus, @one_minus, @frac_1, @frac_2, @big, @big_neg]
+ end
+
+ it "negates self" do
+ @one.send(:-@).should == @one_minus
+ @one_minus.send(:-@).should == @one
+ @frac_1.send(:-@).should == BigDecimal("-1E-99999")
+ @frac_2.send(:-@).should == BigDecimal("-0.9E-99999")
+ @big.send(:-@).should == @big_neg
+ @big_neg.send(:-@).should == @big
+ BigDecimal("2.221").send(:-@).should == BigDecimal("-2.221")
+ BigDecimal("2E10000").send(:-@).should == BigDecimal("-2E10000")
+ some_number = BigDecimal("2455999221.5512")
+ some_number_neg = BigDecimal("-2455999221.5512")
+ some_number.send(:-@).should == some_number_neg
+ (-BigDecimal("-5.5")).should == BigDecimal("5.5")
+ another_number = BigDecimal("-8.551551551551551551")
+ another_number_pos = BigDecimal("8.551551551551551551")
+ another_number.send(:-@).should == another_number_pos
+ @values.each do |val|
+ (val.send(:-@).send(:-@)).should == val
+ end
+ end
+
+ it "properly handles special values" do
+ @infinity.send(:-@).should == @infinity_minus
+ @infinity_minus.send(:-@).should == @infinity
+ @infinity.send(:-@).infinite?.should == -1
+ @infinity_minus.send(:-@).infinite?.should == 1
+
+ @zero.send(:-@).should == @zero
+ @zero.send(:-@).sign.should == -1
+ @zero_pos.send(:-@).should == @zero
+ @zero_pos.send(:-@).sign.should == -1
+ @zero_neg.send(:-@).should == @zero
+ @zero_neg.send(:-@).sign.should == 1
+
+ @nan.send(:-@).should.nan?
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/uplus_spec.rb b/spec/ruby/library/bigdecimal/uplus_spec.rb
new file mode 100644
index 0000000000..77483046b7
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/uplus_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#+@" do
+ it "returns the same value with same sign (twos complement)" do
+ first = BigDecimal("34.56")
+ first.send(:+@).should == first
+ second = BigDecimal("-34.56")
+ second.send(:+@).should == second
+ third = BigDecimal("0.0")
+ third.send(:+@).should == third
+ fourth = BigDecimal("2E1000000")
+ fourth.send(:+@).should == fourth
+ fifth = BigDecimal("123456789E-1000000")
+ fifth.send(:+@).should == fifth
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/util_spec.rb b/spec/ruby/library/bigdecimal/util_spec.rb
new file mode 100644
index 0000000000..fc67fcf200
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/util_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+require 'bigdecimal/util'
+
+describe "BigDecimal's util method definitions" do
+ describe "#to_d" do
+ it "should define #to_d on Integer" do
+ 42.to_d.should == BigDecimal(42)
+ end
+
+ it "should define #to_d on Float" do
+ 0.5.to_d.should == BigDecimal(0.5, Float::DIG)
+ 1.234.to_d(2).should == BigDecimal(1.234, 2)
+ end
+
+ it "should define #to_d on String" do
+ "0.5".to_d.should == BigDecimal(0.5, Float::DIG)
+ "45.67 degrees".to_d.should == BigDecimal(45.67, Float::DIG)
+ end
+
+ it "should define #to_d on BigDecimal" do
+ bd = BigDecimal("3.14")
+ bd.to_d.should equal(bd)
+ end
+
+ it "should define #to_d on Rational" do
+ Rational(22, 7).to_d(3).should == BigDecimal(3.14, 3)
+ end
+
+ it "should define #to_d on nil" do
+ nil.to_d.should == BigDecimal(0)
+ end
+ end
+
+ describe "#to_digits" do
+ it "should define #to_digits on BigDecimal" do
+ BigDecimal("3.14").to_digits.should == "3.14"
+ end
+ end
+end
diff --git a/spec/ruby/library/bigdecimal/zero_spec.rb b/spec/ruby/library/bigdecimal/zero_spec.rb
new file mode 100644
index 0000000000..2563210939
--- /dev/null
+++ b/spec/ruby/library/bigdecimal/zero_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#zero?" do
+
+ it "returns true if self does equal zero" do
+ really_small_zero = BigDecimal("0E-200000000")
+ really_big_zero = BigDecimal("0E200000000000")
+ really_small_zero.should.zero?
+ really_big_zero.should.zero?
+ BigDecimal("0.000000000000000000000000").should.zero?
+ BigDecimal("0").should.zero?
+ BigDecimal("0E0").should.zero?
+ BigDecimal("+0").should.zero?
+ BigDecimal("-0").should.zero?
+ end
+
+ it "returns false otherwise" do
+ BigDecimal("0000000001").should_not.zero?
+ BigDecimal("2E40001").should_not.zero?
+ BigDecimal("3E-20001").should_not.zero?
+ BigDecimal("Infinity").should_not.zero?
+ BigDecimal("-Infinity").should_not.zero?
+ BigDecimal("NaN").should_not.zero?
+ end
+
+end
diff --git a/spec/ruby/library/bigmath/log_spec.rb b/spec/ruby/library/bigmath/log_spec.rb
new file mode 100644
index 0000000000..2ffbf8b6b9
--- /dev/null
+++ b/spec/ruby/library/bigmath/log_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'bigdecimal'
+
+describe "BigDecimal#log" do
+ it "handles high-precision Rational arguments" do
+ result = BigDecimal('0.22314354220170971436137296411949880462556361100856391620766259404746040597133837784E0')
+ r = Rational(1_234_567_890, 987_654_321)
+ BigMath.log(r, 50).should == result
+ end
+end
diff --git a/spec/ruby/library/cgi/cookie/domain_spec.rb b/spec/ruby/library/cgi/cookie/domain_spec.rb
new file mode 100644
index 0000000000..962609ebaf
--- /dev/null
+++ b/spec/ruby/library/cgi/cookie/domain_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::Cookie#domain" do
+ it "returns self's domain" do
+ cookie = CGI::Cookie.new("test-cookie")
+ cookie.domain.should be_nil
+
+ cookie = CGI::Cookie.new("name" => "test-cookie", "domain" => "example.com")
+ cookie.domain.should == "example.com"
+ end
+end
+
+describe "CGI::Cookie#domain=" do
+ it "sets self's domain" do
+ cookie = CGI::Cookie.new("test-cookie")
+ cookie.domain = "test.com"
+ cookie.domain.should == "test.com"
+
+ cookie.domain = "example.com"
+ cookie.domain.should == "example.com"
+ end
+end
diff --git a/spec/ruby/library/cgi/cookie/expires_spec.rb b/spec/ruby/library/cgi/cookie/expires_spec.rb
new file mode 100644
index 0000000000..c8f26d01cd
--- /dev/null
+++ b/spec/ruby/library/cgi/cookie/expires_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::Cookie#expires" do
+ it "returns self's expiration date" do
+ cookie = CGI::Cookie.new("test-cookie")
+ cookie.expires.should be_nil
+
+ cookie = CGI::Cookie.new("name" => "test-cookie", "expires" => Time.at(1196524602))
+ cookie.expires.should == Time.at(1196524602)
+ end
+end
+
+describe "CGI::Cookie#expires=" do
+ it "sets self's expiration date" do
+ cookie = CGI::Cookie.new("test-cookie")
+ cookie.expires = Time.at(1196524602)
+ cookie.expires.should == Time.at(1196524602)
+
+ cookie.expires = Time.at(1196525000)
+ cookie.expires.should == Time.at(1196525000)
+ end
+end
diff --git a/spec/ruby/library/cgi/cookie/initialize_spec.rb b/spec/ruby/library/cgi/cookie/initialize_spec.rb
new file mode 100644
index 0000000000..4b6e104b10
--- /dev/null
+++ b/spec/ruby/library/cgi/cookie/initialize_spec.rb
@@ -0,0 +1,147 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::Cookie#initialize when passed String" do
+ before :each do
+ @cookie = CGI::Cookie.allocate
+ end
+
+ it "sets the self's name to the passed String" do
+ @cookie.send(:initialize, "test-cookie")
+ @cookie.name.should == "test-cookie"
+ end
+
+ it "sets the self's value to an empty Array" do
+ @cookie.send(:initialize, "test-cookie")
+ @cookie.value.should == []
+ end
+
+ it "sets self to a non-secure cookie" do
+ @cookie.send(:initialize, "test")
+ @cookie.secure.should be_false
+ end
+
+ it "does set self's path to an empty String when ENV[\"SCRIPT_NAME\"] is not set" do
+ @cookie.send(:initialize, "test-cookie")
+ @cookie.path.should == ""
+ end
+
+ it "does set self's path based on ENV[\"SCRIPT_NAME\"] when ENV[\"SCRIPT_NAME\"] is set" do
+ old_script_name = ENV["SCRIPT_NAME"]
+
+ begin
+ ENV["SCRIPT_NAME"] = "some/path/script.rb"
+ @cookie.send(:initialize, "test-cookie")
+ @cookie.path.should == "some/path/"
+
+ ENV["SCRIPT_NAME"] = "script.rb"
+ @cookie.send(:initialize, "test-cookie")
+ @cookie.path.should == ""
+
+ ENV["SCRIPT_NAME"] = nil
+ @cookie.send(:initialize, "test-cookie")
+ @cookie.path.should == ""
+ ensure
+ ENV["SCRIPT_NAME"] = old_script_name
+ end
+ end
+
+ it "does not set self's expiration date" do
+ @cookie.expires.should be_nil
+ end
+
+ it "does not set self's domain" do
+ @cookie.domain.should be_nil
+ end
+end
+
+describe "CGI::Cookie#initialize when passed Hash" do
+ before :each do
+ @cookie = CGI::Cookie.allocate
+ end
+
+ it "sets self's contents based on the passed Hash" do
+ @cookie.send(:initialize,
+ 'name' => 'test-cookie',
+ 'value' => ["one", "two", "three"],
+ 'path' => 'some/path/',
+ 'domain' => 'example.com',
+ 'expires' => Time.at(1196524602),
+ 'secure' => true)
+
+ @cookie.name.should == "test-cookie"
+ @cookie.value.should == ["one", "two", "three"]
+ @cookie.path.should == "some/path/"
+ @cookie.domain.should == "example.com"
+ @cookie.expires.should == Time.at(1196524602)
+ @cookie.secure.should be_true
+ end
+
+ it "does set self's path based on ENV[\"SCRIPT_NAME\"] when the Hash has no 'path' entry" do
+ old_script_name = ENV["SCRIPT_NAME"]
+
+ begin
+ ENV["SCRIPT_NAME"] = "some/path/script.rb"
+ @cookie.send(:initialize, 'name' => 'test-cookie')
+ @cookie.path.should == "some/path/"
+
+ ENV["SCRIPT_NAME"] = "script.rb"
+ @cookie.send(:initialize, 'name' => 'test-cookie')
+ @cookie.path.should == ""
+
+ ENV["SCRIPT_NAME"] = nil
+ @cookie.send(:initialize, 'name' => 'test-cookie')
+ @cookie.path.should == ""
+ ensure
+ ENV["SCRIPT_NAME"] = old_script_name
+ end
+ end
+
+ it "tries to convert the Hash's 'value' to an Array using #Array" do
+ obj = mock("Converted To Array")
+ obj.should_receive(:to_ary).and_return(["1", "2", "3"])
+ @cookie.send(:initialize,
+ 'name' => 'test-cookie',
+ 'value' => obj)
+ @cookie.value.should == [ "1", "2", "3" ]
+
+ obj = mock("Converted To Array")
+ obj.should_receive(:to_a).and_return(["one", "two", "three"])
+ @cookie.send(:initialize,
+ 'name' => 'test-cookie',
+ 'value' => obj)
+ @cookie.value.should == [ "one", "two", "three" ]
+
+ obj = mock("Put into an Array")
+ @cookie.send(:initialize,
+ 'name' => 'test-cookie',
+ 'value' => obj)
+ @cookie.value.should == [ obj ]
+ end
+
+ it "raises a ArgumentError when the passed Hash has no 'name' entry" do
+ -> { @cookie.send(:initialize, {}) }.should raise_error(ArgumentError)
+ -> { @cookie.send(:initialize, "value" => "test") }.should raise_error(ArgumentError)
+ end
+end
+
+describe "CGI::Cookie#initialize when passed String, values ..." do
+ before :each do
+ @cookie = CGI::Cookie.allocate
+ end
+
+ it "sets the self's name to the passed String" do
+ @cookie.send(:initialize, "test-cookie", "one", "two", "three")
+ @cookie.name.should == "test-cookie"
+ end
+
+ it "sets the self's value to an Array containing all passed values" do
+ @cookie.send(:initialize, "test-cookie", "one", "two", "three")
+ @cookie.value.should == ["one", "two", "three"]
+ end
+
+ it "sets self to a non-secure cookie" do
+ @cookie.send(:initialize, "test", "one", "two", "three")
+ @cookie.secure.should be_false
+ end
+end
diff --git a/spec/ruby/library/cgi/cookie/name_spec.rb b/spec/ruby/library/cgi/cookie/name_spec.rb
new file mode 100644
index 0000000000..326a43ade3
--- /dev/null
+++ b/spec/ruby/library/cgi/cookie/name_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::Cookie#name" do
+ it "returns self's name" do
+ cookie = CGI::Cookie.new("test-cookie")
+ cookie.name.should == "test-cookie"
+
+ cookie = CGI::Cookie.new("name" => "another-cookie")
+ cookie.name.should == "another-cookie"
+ end
+end
+
+describe "CGI::Cookie#name=" do
+ it "sets self's expiration date" do
+ cookie = CGI::Cookie.new("test-cookie")
+ cookie.name = "another-name"
+ cookie.name.should == "another-name"
+
+ cookie.name = "and-one-more"
+ cookie.name.should == "and-one-more"
+ end
+end
diff --git a/spec/ruby/library/cgi/cookie/parse_spec.rb b/spec/ruby/library/cgi/cookie/parse_spec.rb
new file mode 100644
index 0000000000..d484c7bad9
--- /dev/null
+++ b/spec/ruby/library/cgi/cookie/parse_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::Cookie.parse" do
+ it "parses a raw cookie string into a hash of Cookies" do
+ expected = { "test-cookie" => ["one", "two", "three"] }
+ CGI::Cookie.parse("test-cookie=one&two&three").should == expected
+
+ expected = { "second-cookie" => ["three", "four"], "first-cookie" => ["one", "two"] }
+ CGI::Cookie.parse("first-cookie=one&two;second-cookie=three&four").should == expected
+ end
+
+ it "does not use , for cookie separators" do
+ expected = {
+ "first-cookie" => ["one", "two"],
+ "second-cookie" => ["three", "four,third_cookie=five", "six"]
+ }
+ CGI::Cookie.parse("first-cookie=one&two;second-cookie=three&four,third_cookie=five&six").should == expected
+ end
+
+ it "unescapes the Cookie values" do
+ cookie = "test-cookie=+%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E"
+ expected = { "test-cookie" => [ " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" ] }
+ CGI::Cookie.parse(cookie).should == expected
+ end
+end
diff --git a/spec/ruby/library/cgi/cookie/path_spec.rb b/spec/ruby/library/cgi/cookie/path_spec.rb
new file mode 100644
index 0000000000..8a2f05aa50
--- /dev/null
+++ b/spec/ruby/library/cgi/cookie/path_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::Cookie#path" do
+ it "returns self's path" do
+ cookie = CGI::Cookie.new("test-cookie")
+ cookie.path.should == ""
+
+ cookie = CGI::Cookie.new("name" => "test-cookie", "path" => "/some/path/")
+ cookie.path.should == "/some/path/"
+ end
+end
+
+describe "CGI::Cookie#path=" do
+ it "sets self's path" do
+ cookie = CGI::Cookie.new("test-cookie")
+ cookie.path = "/some/path/"
+ cookie.path.should == "/some/path/"
+
+ cookie.path = "/another/path/"
+ cookie.path.should == "/another/path/"
+ end
+end
diff --git a/spec/ruby/library/cgi/cookie/secure_spec.rb b/spec/ruby/library/cgi/cookie/secure_spec.rb
new file mode 100644
index 0000000000..694bc2eeed
--- /dev/null
+++ b/spec/ruby/library/cgi/cookie/secure_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::Cookie#secure" do
+ before :each do
+ @cookie = CGI::Cookie.new("test-cookie")
+ end
+
+ it "returns whether self is a secure cookie or not" do
+ @cookie.secure = true
+ @cookie.secure.should be_true
+
+ @cookie.secure = false
+ @cookie.secure.should be_false
+ end
+end
+
+describe "CGI::Cookie#secure= when passed true" do
+ before :each do
+ @cookie = CGI::Cookie.new("test-cookie")
+ end
+
+ it "returns true" do
+ (@cookie.secure = true).should be_true
+ end
+
+ it "sets self to a secure cookie" do
+ @cookie.secure = true
+ @cookie.secure.should be_true
+ end
+end
+
+describe "CGI::Cookie#secure= when passed false" do
+ before :each do
+ @cookie = CGI::Cookie.new("test-cookie")
+ end
+
+ it "returns false" do
+ (@cookie.secure = false).should be_false
+ end
+
+ it "sets self to a non-secure cookie" do
+ @cookie.secure = false
+ @cookie.secure.should be_false
+ end
+end
+
+describe "CGI::Cookie#secure= when passed Object" do
+ before :each do
+ @cookie = CGI::Cookie.new("test-cookie")
+ end
+
+ it "does not change self's secure value" do
+ @cookie.secure = false
+
+ @cookie.secure = Object.new
+ @cookie.secure.should be_false
+
+ @cookie.secure = "Test"
+ @cookie.secure.should be_false
+
+ @cookie.secure = true
+
+ @cookie.secure = Object.new
+ @cookie.secure.should be_true
+
+ @cookie.secure = "Test"
+ @cookie.secure.should be_true
+ end
+end
diff --git a/spec/ruby/library/cgi/cookie/to_s_spec.rb b/spec/ruby/library/cgi/cookie/to_s_spec.rb
new file mode 100644
index 0000000000..da15e6ed2a
--- /dev/null
+++ b/spec/ruby/library/cgi/cookie/to_s_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::Cookie#to_s" do
+ it "returns a String representation of self" do
+ cookie = CGI::Cookie.new("test-cookie")
+ cookie.to_s.should == "test-cookie=; path="
+
+ cookie = CGI::Cookie.new("test-cookie", "value")
+ cookie.to_s.should == "test-cookie=value; path="
+
+ cookie = CGI::Cookie.new("test-cookie", "one", "two", "three")
+ cookie.to_s.should == "test-cookie=one&two&three; path="
+
+ cookie = CGI::Cookie.new(
+ 'name' => 'test-cookie',
+ 'value' => ["one", "two", "three"],
+ 'path' => 'some/path/',
+ 'domain' => 'example.com',
+ 'expires' => Time.at(1196524602),
+ 'secure' => true)
+ cookie.to_s.should == "test-cookie=one&two&three; domain=example.com; path=some/path/; expires=Sat, 01 Dec 2007 15:56:42 GMT; secure"
+ end
+
+ it "escapes the self's values" do
+ cookie = CGI::Cookie.new("test-cookie", " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}")
+ cookie.to_s.should == "test-cookie=+%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D; path="
+ end
+
+ it "does not escape tilde" do
+ cookie = CGI::Cookie.new("test-cookie", "~").to_s.should == "test-cookie=~; path="
+ end
+end
diff --git a/spec/ruby/library/cgi/cookie/value_spec.rb b/spec/ruby/library/cgi/cookie/value_spec.rb
new file mode 100644
index 0000000000..1d5da3a3ac
--- /dev/null
+++ b/spec/ruby/library/cgi/cookie/value_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::Cookie#value" do
+ it "returns self's value" do
+ cookie = CGI::Cookie.new("test-cookie")
+ cookie.value.should == []
+
+ cookie = CGI::Cookie.new("test-cookie", "one")
+ cookie.value.should == ["one"]
+
+ cookie = CGI::Cookie.new("test-cookie", "one", "two", "three")
+ cookie.value.should == ["one", "two", "three"]
+
+ cookie = CGI::Cookie.new("name" => "test-cookie", "value" => ["one", "two", "three"])
+ cookie.value.should == ["one", "two", "three"]
+ end
+
+ it "is in synch with self" do
+ fail = []
+ [
+ :pop,
+ :shift,
+ [:<<, "Hello"],
+ [:push, "Hello"],
+ [:unshift, "World"],
+ [:replace, ["A", "B"]],
+ [:[]=, 1, "Set"],
+ [:delete, "first"],
+ [:delete_at, 0],
+ ].each do |method, *args|
+ cookie1 = CGI::Cookie.new("test-cookie", "first", "second")
+ cookie2 = CGI::Cookie.new("test-cookie", "first", "second")
+ cookie1.send(method, *args)
+ cookie2.value.send(method, *args)
+ fail << method unless cookie1.value == cookie2.value
+ end
+ fail.should be_empty
+ end
+end
+
+describe "CGI::Cookie#value=" do
+ before :each do
+ @cookie = CGI::Cookie.new("test-cookie")
+ end
+
+ it "sets self's value" do
+ @cookie.value = ["one"]
+ @cookie.value.should == ["one"]
+
+ @cookie.value = ["one", "two", "three"]
+ @cookie.value.should == ["one", "two", "three"]
+ end
+
+ it "automatically converts the passed Object to an Array using #Array" do
+ @cookie.value = "test"
+ @cookie.value.should == ["test"]
+
+ obj = mock("to_a")
+ obj.should_receive(:to_a).and_return(["1", "2"])
+ @cookie.value = obj
+ @cookie.value.should == ["1", "2"]
+
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_return(["1", "2"])
+ @cookie.value = obj
+ @cookie.value.should == ["1", "2"]
+ end
+
+ it "does keep self and the values in sync" do
+ @cookie.value = ["one", "two", "three"]
+ @cookie[0].should == "one"
+ @cookie[1].should == "two"
+ @cookie[2].should == "three"
+ end
+end
diff --git a/spec/ruby/library/cgi/escapeElement_spec.rb b/spec/ruby/library/cgi/escapeElement_spec.rb
new file mode 100644
index 0000000000..148926c453
--- /dev/null
+++ b/spec/ruby/library/cgi/escapeElement_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI.escapeElement when passed String, elements, ..." do
+ it "escapes only the tags of the passed elements in the passed String" do
+ res = CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG")
+ res.should == "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;"
+
+ res = CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"])
+ res.should == "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;"
+ end
+
+ it "is case-insensitive" do
+ res = CGI.escapeElement('<BR><A HREF="url"></A>', "a", "img")
+ res.should == '<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;'
+
+ res = CGI.escapeElement('<br><a href="url"></a>', "A", "IMG")
+ res.should == '<br>&lt;a href=&quot;url&quot;&gt;&lt;/a&gt;'
+ end
+end
diff --git a/spec/ruby/library/cgi/escapeHTML_spec.rb b/spec/ruby/library/cgi/escapeHTML_spec.rb
new file mode 100644
index 0000000000..421aac5d4a
--- /dev/null
+++ b/spec/ruby/library/cgi/escapeHTML_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI.escapeHTML" do
+ it "escapes special HTML characters (&\"<>') in the passed argument" do
+ CGI.escapeHTML(%[& < > " ']).should == '&amp; &lt; &gt; &quot; &#39;'
+ end
+
+ it "escapes invalid encoding" do
+ CGI.escapeHTML(%[<\xA4??>]).should == "&lt;\xA4??&gt;"
+ end
+
+ it "does not escape any other characters" do
+ chars = " !\#$%()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
+ CGI.escapeHTML(chars).should == chars
+ end
+end
diff --git a/spec/ruby/library/cgi/escape_spec.rb b/spec/ruby/library/cgi/escape_spec.rb
new file mode 100644
index 0000000000..c599a73cf0
--- /dev/null
+++ b/spec/ruby/library/cgi/escape_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI.escape" do
+ it "url-encodes the passed argument" do
+ input = " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}"
+ expected = "+%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D"
+ CGI.escape(input).should == expected
+
+ input = "https://ja.wikipedia.org/wiki/\343\203\255\343\203\240\343\202\271\343\202\253\343\203\273\343\203\221\343\203\255\343\203\273\343\202\246\343\203\253\343\203\273\343\203\251\343\203\224\343\203\245\343\202\277"
+ expected = 'https%3A%2F%2Fja.wikipedia.org%2Fwiki%2F%E3%83%AD%E3%83%A0%E3%82%B9%E3%82%AB%E3%83%BB%E3%83%91%E3%83%AD%E3%83%BB%E3%82%A6%E3%83%AB%E3%83%BB%E3%83%A9%E3%83%94%E3%83%A5%E3%82%BF'
+ CGI.escape(input).should == expected
+ end
+
+ it "does not escape tilde" do
+ CGI.escape("~").should == "~"
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/a_spec.rb b/spec/ruby/library/cgi/htmlextension/a_spec.rb
new file mode 100644
index 0000000000..05b4c2d14c
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/a_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#a" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed a String" do
+ it "returns an 'a'-element, using the passed String as the 'href'-attribute" do
+ output = @html.a("http://www.example.com")
+ output.should equal_element("A", "HREF" => "http://www.example.com")
+ end
+
+ it "includes the passed block's return value when passed a block" do
+ output = @html.a("http://www.example.com") { "Example" }
+ output.should equal_element("A", { "HREF" => "http://www.example.com" }, "Example")
+ end
+ end
+
+ describe "when passed a Hash" do
+ it "returns an 'a'-element, using the passed Hash for attributes" do
+ attributes = {"HREF" => "http://www.example.com", "TARGET" => "_top"}
+ @html.a(attributes).should equal_element("A", attributes)
+ end
+
+ it "includes the passed block's return value when passed a block" do
+ attributes = {"HREF" => "http://www.example.com", "TARGET" => "_top"}
+ @html.a(attributes) { "Example" }.should equal_element("A", attributes, "Example")
+ end
+ end
+
+ describe "when each HTML generation" do
+ it "returns the doctype declaration for HTML3" do
+ CGISpecs.cgi_new("html3").a.should == %(<A HREF=""></A>)
+ CGISpecs.cgi_new("html3").a { "link text" }.should == %(<A HREF="">link text</A>)
+ end
+
+ it "returns the doctype declaration for HTML4" do
+ CGISpecs.cgi_new("html4").a.should == %(<A HREF=""></A>)
+ CGISpecs.cgi_new("html4").a { "link text" }.should == %(<A HREF="">link text</A>)
+ end
+ it "returns the doctype declaration for the Transitional version of HTML4" do
+ CGISpecs.cgi_new("html4Tr").a.should == %(<A HREF=""></A>)
+ CGISpecs.cgi_new("html4Tr").a { "link text" }.should == %(<A HREF="">link text</A>)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/base_spec.rb b/spec/ruby/library/cgi/htmlextension/base_spec.rb
new file mode 100644
index 0000000000..877ac321cd
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/base_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#base" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when bassed a String" do
+ it "returns a 'base'-element, using the passed String as the 'href'-attribute" do
+ output = @html.base("http://www.example.com")
+ output.should equal_element("BASE", {"HREF" => "http://www.example.com"}, nil, not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.base("http://www.example.com") { "Example" }
+ output.should equal_element("BASE", {"HREF" => "http://www.example.com"}, nil, not_closed: true)
+ end
+ end
+
+ describe "when passed a Hash" do
+ it "returns a 'base'-element, using the passed Hash for attributes" do
+ output = @html.base("HREF" => "http://www.example.com", "ID" => "test")
+ output.should equal_element("BASE", {"HREF" => "http://www.example.com", "ID" => "test"}, nil, not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.base("HREF" => "http://www.example.com", "ID" => "test") { "Example" }
+ output.should equal_element("BASE", {"HREF" => "http://www.example.com", "ID" => "test"}, nil, not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/blockquote_spec.rb b/spec/ruby/library/cgi/htmlextension/blockquote_spec.rb
new file mode 100644
index 0000000000..a7b833b1c5
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/blockquote_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#blockquote" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed a String" do
+ it "returns a 'blockquote'-element, using the passed String for the 'cite'-attribute" do
+ output = @html.blockquote("http://www.example.com/quotes/foo.html")
+ output.should equal_element("BLOCKQUOTE", "CITE" => "http://www.example.com/quotes/foo.html")
+ end
+
+ it "includes the passed block's return value when passed a block" do
+ output = @html.blockquote("http://www.example.com/quotes/foo.html") { "Foo!" }
+ output.should equal_element("BLOCKQUOTE", { "CITE" => "http://www.example.com/quotes/foo.html" }, "Foo!")
+ end
+ end
+
+ describe "when passed a Hash" do
+ it "returns a 'blockquote'-element, using the passed Hash for attributes" do
+ output = @html.blockquote("CITE" => "http://www.example.com/quotes/foo.html", "ID" => "test")
+ output.should equal_element("BLOCKQUOTE", "CITE" => "http://www.example.com/quotes/foo.html", "ID" => "test")
+ end
+
+ it "includes the passed block's return value when passed a block" do
+ output = @html.blockquote("CITE" => "http://www.example.com/quotes/foo.html", "ID" => "test") { "Foo!" }
+ output.should equal_element("BLOCKQUOTE", {"CITE" => "http://www.example.com/quotes/foo.html", "ID" => "test"}, "Foo!")
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/br_spec.rb b/spec/ruby/library/cgi/htmlextension/br_spec.rb
new file mode 100644
index 0000000000..dfca121884
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/br_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#br" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when each HTML generation" do
+ it "returns the doctype declaration for HTML3" do
+ CGISpecs.cgi_new("html3").br.should == "<BR>"
+ end
+
+ it "returns the doctype declaration for HTML4" do
+ CGISpecs.cgi_new("html4").br.should == "<BR>"
+ end
+ it "returns the doctype declaration for the Transitional version of HTML4" do
+ CGISpecs.cgi_new("html4Tr").br.should == "<BR>"
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/caption_spec.rb b/spec/ruby/library/cgi/htmlextension/caption_spec.rb
new file mode 100644
index 0000000000..16615028b8
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/caption_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#caption" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed a String" do
+ it "returns a 'caption'-element, using the passed String for the 'align'-attribute" do
+ output = @html.caption("left")
+ output.should equal_element("CAPTION", "ALIGN" => "left")
+ end
+
+ it "includes the passed block's return value when passed a block" do
+ output = @html.caption("left") { "Capital Cities" }
+ output.should equal_element("CAPTION", {"ALIGN" => "left"}, "Capital Cities")
+ end
+ end
+
+ describe "when passed a Hash" do
+ it "returns a 'caption'-element, using the passed Hash for attributes" do
+ output = @html.caption("ALIGN" => "left", "ID" => "test")
+ output.should equal_element("CAPTION", "ALIGN" => "left", "ID" => "test")
+ end
+
+ it "includes the passed block's return value when passed a block" do
+ output = @html.caption("ALIGN" => "left", "ID" => "test") { "Capital Cities" }
+ output.should equal_element("CAPTION", {"ALIGN" => "left", "ID" => "test"}, "Capital Cities")
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/checkbox_group_spec.rb b/spec/ruby/library/cgi/htmlextension/checkbox_group_spec.rb
new file mode 100644
index 0000000000..64f852cc52
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/checkbox_group_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#checkbox_group" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed name, values ..." do
+ it "returns a sequence of 'checkbox'-elements with the passed name and the passed values" do
+ output = CGISpecs.split(@html.checkbox_group("test", "foo", "bar", "baz"))
+ output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true)
+ end
+
+ it "allows passing a value inside an Array" do
+ output = CGISpecs.split(@html.checkbox_group("test", ["foo"], "bar", ["baz"]))
+ output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true)
+ end
+
+ it "allows passing a value as an Array containing the value and the checked state or a label" do
+ output = CGISpecs.split(@html.checkbox_group("test", ["foo"], ["bar", true], ["baz", "label for baz"]))
+ output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "baz"}, "label for baz", not_closed: true)
+ end
+
+ it "allows passing a value as an Array containing the value, a label and the checked state" do
+ output = CGISpecs.split(@html.checkbox_group("test", ["foo", "label for foo", true], ["bar", "label for bar", false], ["baz", "label for baz", true]))
+ output[0].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "checkbox", "VALUE" => "foo"}, "label for foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "bar"}, "label for bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "checkbox", "VALUE" => "baz"}, "label for baz", not_closed: true)
+ end
+
+ it "returns an empty String when passed no values" do
+ @html.checkbox_group("test").should == ""
+ end
+
+ it "ignores a passed block" do
+ output = CGISpecs.split(@html.checkbox_group("test", "foo", "bar", "baz") { "test" })
+ output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "uses the passed Hash to generate the checkbox sequence" do
+ output = CGISpecs.split(@html.checkbox_group("NAME" => "name", "VALUES" => ["foo", "bar", "baz"]))
+ output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true)
+
+ output = CGISpecs.split(@html.checkbox_group("NAME" => "name", "VALUES" => [["foo"], ["bar", true], "baz"]))
+ output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "name", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true)
+
+ output = CGISpecs.split(@html.checkbox_group("NAME" => "name", "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]))
+ output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "1"}, "Foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "name", "TYPE" => "checkbox", "VALUE" => "2"}, "Bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "Baz"}, "Baz", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = CGISpecs.split(@html.checkbox_group("NAME" => "name", "VALUES" => ["foo", "bar", "baz"]) { "test" })
+ output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/checkbox_spec.rb b/spec/ruby/library/cgi/htmlextension/checkbox_spec.rb
new file mode 100644
index 0000000000..af76fa1da9
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/checkbox_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#checkbox" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns a checkbox-'input'-element without a name" do
+ output = @html.checkbox
+ output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "checkbox"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.checkbox { "test" }
+ output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "checkbox"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name" do
+ it "returns a checkbox-'input'-element with the passed name" do
+ output = @html.checkbox("test")
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.checkbox("test") { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox"}, "", not_closed: true)
+ end
+ end
+
+ describe "CGI::HtmlExtension#checkbox when passed name, value" do
+ it "returns a checkbox-'input'-element with the passed name and value" do
+ output = @html.checkbox("test", "test-value")
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.checkbox("test", "test-value") { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, value, checked" do
+ it "returns a checked checkbox-'input'-element with the passed name and value when checked is true" do
+ output = @html.checkbox("test", "test-value", true)
+ output.should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true)
+
+ output = @html.checkbox("test", "test-value", false)
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true)
+
+ output = @html.checkbox("test", "test-value", nil)
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.checkbox("test", "test-value", nil) { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "returns a checkbox-'input'-element using the passed Hash for attributes" do
+ attributes = {"NAME" => "test", "VALUE" => "test-value", "CHECKED" => true}
+ output = @html.checkbox(attributes)
+ output.should equal_element("INPUT", attributes, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ attributes = {"NAME" => "test", "VALUE" => "test-value", "CHECKED" => true}
+ output = @html.checkbox(attributes) { "test" }
+ output.should equal_element("INPUT", attributes, "", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/doctype_spec.rb b/spec/ruby/library/cgi/htmlextension/doctype_spec.rb
new file mode 100644
index 0000000000..9a28a8883b
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/doctype_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#doctype" do
+ describe "when each HTML generation" do
+ it "returns the doctype declaration for HTML3" do
+ expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'
+ CGISpecs.cgi_new("html3").doctype.should == expect
+ end
+
+ it "returns the doctype declaration for HTML4" do
+ expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
+ CGISpecs.cgi_new("html4").doctype.should == expect
+ end
+
+ it "returns the doctype declaration for the Frameset version of HTML4" do
+ expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
+ CGISpecs.cgi_new("html4Fr").doctype.should == expect
+ end
+
+ it "returns the doctype declaration for the Transitional version of HTML4" do
+ expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
+ CGISpecs.cgi_new("html4Tr").doctype.should == expect
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/file_field_spec.rb b/spec/ruby/library/cgi/htmlextension/file_field_spec.rb
new file mode 100644
index 0000000000..2a0632fd58
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/file_field_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#file_field" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns a file-'input'-element without a name and a size of 20" do
+ output = @html.file_field
+ output.should equal_element("INPUT", {"SIZE" => 20, "NAME" => "", "TYPE" => "file"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.file_field { "test" }
+ output.should equal_element("INPUT", {"SIZE" => 20, "NAME" => "", "TYPE" => "file"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name" do
+ it "returns a checkbox-'input'-element with the passed name" do
+ output = @html.file_field("Example")
+ output.should equal_element("INPUT", {"SIZE" => 20, "NAME" => "Example", "TYPE" => "file"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.file_field("Example") { "test" }
+ output.should equal_element("INPUT", {"SIZE" => 20, "NAME" => "Example", "TYPE" => "file"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, size" do
+ it "returns a checkbox-'input'-element with the passed name and size" do
+ output = @html.file_field("Example", 40)
+ output.should equal_element("INPUT", {"SIZE" => 40, "NAME" => "Example", "TYPE" => "file"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.file_field("Example", 40) { "test" }
+ output.should equal_element("INPUT", {"SIZE" => 40, "NAME" => "Example", "TYPE" => "file"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, size, maxlength" do
+ it "returns a checkbox-'input'-element with the passed name, size and maxlength" do
+ output = @html.file_field("Example", 40, 100)
+ output.should equal_element("INPUT", {"SIZE" => 40, "NAME" => "Example", "TYPE" => "file", "MAXLENGTH" => 100}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.file_field("Example", 40, 100) { "test" }
+ output.should equal_element("INPUT", {"SIZE" => 40, "NAME" => "Example", "TYPE" => "file", "MAXLENGTH" => 100}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed a Hash" do
+ it "returns a file-'input'-element using the passed Hash for attributes" do
+ output = @html.file_field("NAME" => "test", "SIZE" => 40)
+ output.should equal_element("INPUT", {"NAME" => "test", "SIZE" => 40}, "", not_closed: true)
+
+ output = @html.file_field("NAME" => "test", "MAXLENGTH" => 100)
+ output.should equal_element("INPUT", {"NAME" => "test", "MAXLENGTH" => 100}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.file_field("NAME" => "test", "SIZE" => 40) { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "SIZE" => 40}, "", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/fixtures/common.rb b/spec/ruby/library/cgi/htmlextension/fixtures/common.rb
new file mode 100644
index 0000000000..4f951f6389
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/fixtures/common.rb
@@ -0,0 +1,15 @@
+module CGISpecs
+ def self.cgi_new(html = "html4")
+ old_request_method = ENV['REQUEST_METHOD']
+ ENV['REQUEST_METHOD'] = "GET"
+ begin
+ CGI.new(tag_maker: html)
+ ensure
+ ENV['REQUEST_METHOD'] = old_request_method
+ end
+ end
+
+ def self.split(string)
+ string.split("<").reject { |x| x.empty? }.map { |x| "<#{x}" }
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/form_spec.rb b/spec/ruby/library/cgi/htmlextension/form_spec.rb
new file mode 100644
index 0000000000..8c0ac97735
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/form_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#form" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ @html.stub!(:script_name).and_return("/path/to/some/script")
+ end
+
+ describe "when passed no arguments" do
+ it "returns a 'form'-element" do
+ output = @html.form
+ output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "post", "ACTION" => "/path/to/some/script"}, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.form { "test" }
+ output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "post", "ACTION" => "/path/to/some/script"}, "test")
+ end
+ end
+
+ describe "when passed method" do
+ it "returns a 'form'-element with the passed method" do
+ output = @html.form("get")
+ output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ACTION" => "/path/to/some/script"}, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.form("get") { "test" }
+ output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ACTION" => "/path/to/some/script"}, "test")
+ end
+ end
+
+ describe "when passed method, action" do
+ it "returns a 'form'-element with the passed method and the passed action" do
+ output = @html.form("get", "/some/other/script")
+ output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ACTION" => "/some/other/script"}, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.form("get", "/some/other/script") { "test" }
+ output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ACTION" => "/some/other/script"}, "test")
+ end
+ end
+
+ describe "when passed method, action, enctype" do
+ it "returns a 'form'-element with the passed method, action and enctype" do
+ output = @html.form("get", "/some/other/script", "multipart/form-data")
+ output.should equal_element("FORM", {"ENCTYPE" => "multipart/form-data", "METHOD" => "get", "ACTION" => "/some/other/script"}, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.form("get", "/some/other/script", "multipart/form-data") { "test" }
+ output.should equal_element("FORM", {"ENCTYPE" => "multipart/form-data", "METHOD" => "get", "ACTION" => "/some/other/script"}, "test")
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/frame_spec.rb b/spec/ruby/library/cgi/htmlextension/frame_spec.rb
new file mode 100644
index 0000000000..2ddd4e1ef0
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/frame_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'fixtures/common'
+require 'cgi'
+
+describe "CGI::HtmlExtension#frame" do
+ before :each do
+ @html = CGISpecs.cgi_new("html4Fr")
+ end
+
+ it "initializes the HTML Generation methods for the Frameset version of HTML4" do
+ @html.frameset.should == "<FRAMESET></FRAMESET>"
+ @html.frameset { "link text" }.should == "<FRAMESET>link text</FRAMESET>"
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/frameset_spec.rb b/spec/ruby/library/cgi/htmlextension/frameset_spec.rb
new file mode 100644
index 0000000000..baeb446593
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/frameset_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'fixtures/common'
+require 'cgi'
+
+describe "CGI::HtmlExtension#frameset" do
+ before :each do
+ @html = CGISpecs.cgi_new("html4Fr")
+ end
+
+ it "initializes the HTML Generation methods for the Frameset version of HTML4" do
+ @html.frameset.should == "<FRAMESET></FRAMESET>"
+ @html.frameset { "link text" }.should == "<FRAMESET>link text</FRAMESET>"
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/hidden_spec.rb b/spec/ruby/library/cgi/htmlextension/hidden_spec.rb
new file mode 100644
index 0000000000..52ebd8c261
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/hidden_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#hidden" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns an hidden-'input'-element without a name" do
+ output = @html.hidden
+ output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "hidden"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.hidden { "test" }
+ output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "hidden"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name" do
+ it "returns an hidden-'input'-element with the passed name" do
+ output = @html.hidden("test")
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "hidden"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.hidden("test") { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "hidden"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, value" do
+ it "returns an hidden-'input'-element with the passed name and value" do
+ output = @html.hidden("test", "some value")
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "hidden", "VALUE" => "some value"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.hidden("test", "some value") { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "hidden", "VALUE" => "some value"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "returns a checkbox-'input'-element using the passed Hash for attributes" do
+ attributes = { "NAME" => "test", "VALUE" => "some value" }
+ output = @html.hidden("test", "some value")
+ output.should equal_element("INPUT", attributes, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ attributes = { "NAME" => "test", "VALUE" => "some value" }
+ output = @html.hidden("test", "some value") { "test" }
+ output.should equal_element("INPUT", attributes, "", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/html_spec.rb b/spec/ruby/library/cgi/htmlextension/html_spec.rb
new file mode 100644
index 0000000000..5d89c82086
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/html_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#html" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ @html.stub!(:doctype).and_return("<!DOCTYPE SUPA-FUNKAY-RUBYSPEC-DOCTYPE>")
+ end
+
+ describe "when passed no arguments" do
+ it "returns a self's doctype and an 'html'-element" do
+ expected = '<!DOCTYPE SUPA-FUNKAY-RUBYSPEC-DOCTYPE><HTML>'
+ @html.html.should == expected
+ end
+
+ it "includes the passed block when passed a block" do
+ expected = '<!DOCTYPE SUPA-FUNKAY-RUBYSPEC-DOCTYPE><HTML>test</HTML>'
+ @html.html { "test" }.should == expected
+ end
+ end
+
+ describe "when passed 'PRETTY'" do
+ it "returns pretty output when the passed String is 'PRETTY" do
+ expected = "<!DOCTYPE SUPA-FUNKAY-RUBYSPEC-DOCTYPE>\n<HTML>\n"
+ @html.html("PRETTY").should == expected
+ end
+
+ it "includes the passed block when passed a block" do
+ expected = "<!DOCTYPE SUPA-FUNKAY-RUBYSPEC-DOCTYPE>\n<HTML>\n test\n</HTML>\n"
+ @html.html("PRETTY") { "test" }.should == expected
+ end
+ end
+
+ describe "when passed a Hash" do
+ it "returns an 'html'-element using the passed Hash for attributes" do
+ expected = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><HTML BLA="TEST">'
+ @html.html("DOCTYPE" => '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">', "BLA" => "TEST").should == expected
+ end
+
+ it "omits the doctype when the Hash contains a 'DOCTYPE' entry that's false or nil" do
+ @html.html("DOCTYPE" => false).should == "<HTML>"
+ @html.html("DOCTYPE" => nil).should == "<HTML>"
+ end
+ end
+
+ describe "when each HTML generation" do
+ it "returns the doctype declaration for HTML3" do
+ expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'
+ CGISpecs.cgi_new("html3").html.should == expect + "<HTML>"
+ CGISpecs.cgi_new("html3").html { "html body" }.should == expect + "<HTML>html body</HTML>"
+ end
+
+ it "returns the doctype declaration for HTML4" do
+ expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
+ CGISpecs.cgi_new("html4").html.should == expect + "<HTML>"
+ CGISpecs.cgi_new("html4").html { "html body" }.should == expect + "<HTML>html body</HTML>"
+ end
+
+ it "returns the doctype declaration for the Transitional version of HTML4" do
+ expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
+ CGISpecs.cgi_new("html4Tr").html.should == expect + "<HTML>"
+ CGISpecs.cgi_new("html4Tr").html { "html body" }.should == expect + "<HTML>html body</HTML>"
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/image_button_spec.rb b/spec/ruby/library/cgi/htmlextension/image_button_spec.rb
new file mode 100644
index 0000000000..d14bec9ca3
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/image_button_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#image_button" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns an image-'input'-element without a source image" do
+ output = @html.image_button
+ output.should equal_element("INPUT", {"SRC" => "", "TYPE" => "image"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.image_button { "test" }
+ output.should equal_element("INPUT", {"SRC" => "", "TYPE" => "image"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed src" do
+ it "returns an image-'input'-element with the passed src" do
+ output = @html.image_button("/path/to/image.png")
+ output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.image_button("/path/to/image.png") { "test" }
+ output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed src, name" do
+ it "returns an image-'input'-element with the passed src and name" do
+ output = @html.image_button("/path/to/image.png", "test")
+ output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image", "NAME" => "test"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.image_button("/path/to/image.png", "test") { "test" }
+ output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image", "NAME" => "test"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed src, name, alt" do
+ it "returns an image-'input'-element with the passed src, name and alt" do
+ output = @html.image_button("/path/to/image.png", "test", "alternative")
+ output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image", "NAME" => "test", "ALT" => "alternative"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.image_button("/path/to/image.png", "test", "alternative") { "test" }
+ output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image", "NAME" => "test", "ALT" => "alternative"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "returns a image-'input'-element using the passed Hash for attributes" do
+ output = @html.image_button("NAME" => "test", "VALUE" => "test-value")
+ output.should equal_element("INPUT", {"SRC" => "", "TYPE" => "image", "NAME" => "test", "VALUE" => "test-value"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.image_button("NAME" => "test", "VALUE" => "test-value") { "test" }
+ output.should equal_element("INPUT", {"SRC" => "", "TYPE" => "image", "NAME" => "test", "VALUE" => "test-value"}, "", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/img_spec.rb b/spec/ruby/library/cgi/htmlextension/img_spec.rb
new file mode 100644
index 0000000000..994ae7fedf
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/img_spec.rb
@@ -0,0 +1,83 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#img" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns an 'img'-element without an src-url or alt-text" do
+ output = @html.img
+ output.should equal_element("IMG", { "SRC" => "", "ALT" => "" }, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.img { "test" }
+ output.should equal_element("IMG", { "SRC" => "", "ALT" => "" }, "", not_closed: true)
+ end
+ end
+
+ describe "when passed src" do
+ it "returns an 'img'-element with the passed src-url" do
+ output = @html.img("/path/to/some/image.png")
+ output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "" }, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.img("/path/to/some/image.png")
+ output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "" }, "", not_closed: true)
+ end
+ end
+
+ describe "when passed src, alt" do
+ it "returns an 'img'-element with the passed src-url and the passed alt-text" do
+ output = @html.img("/path/to/some/image.png", "Alternative")
+ output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "Alternative" }, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.img("/path/to/some/image.png", "Alternative") { "test" }
+ output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "Alternative" }, "", not_closed: true)
+ end
+ end
+
+ describe "when passed src, alt, width" do
+ it "returns an 'img'-element with the passed src-url, the passed alt-text and the passed width" do
+ output = @html.img("/path/to/some/image.png", "Alternative", 40)
+ output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "Alternative", "WIDTH" => "40" }, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.img("/path/to/some/image.png", "Alternative", 40) { "test" }
+ output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "Alternative", "WIDTH" => "40" }, "", not_closed: true)
+ end
+ end
+
+ describe "when passed src, alt, width, height" do
+ it "returns an 'img'-element with the passed src-url, the passed alt-text, the passed width and the passed height" do
+ output = @html.img("/path/to/some/image.png", "Alternative", 40, 60)
+ output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "Alternative", "WIDTH" => "40", "HEIGHT" => "60" }, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.img { "test" }
+ output.should equal_element("IMG", { "SRC" => "", "ALT" => "" }, "", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "returns an 'img'-element with the passed Hash as attributes" do
+ attributes = { "SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50 }
+ output = @html.img(attributes)
+ output.should equal_element("IMG", attributes, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ attributes = { "SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50 }
+ output = @html.img(attributes) { "test" }
+ output.should equal_element("IMG", attributes, "", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb b/spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb
new file mode 100644
index 0000000000..0bf2042a33
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#multipart_form" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ @html.stub!(:script_name).and_return("/path/to/some/script.rb")
+ end
+
+ describe "when passed no arguments" do
+ it "returns a 'form'-element with it's enctype set to multipart" do
+ output = @html.multipart_form
+ output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post" }, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.multipart_form { "test" }
+ output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post" }, "test")
+ end
+ end
+
+ describe "when passed action" do
+ it "returns a 'form'-element with the passed action" do
+ output = @html.multipart_form("/some/other/script.rb")
+ output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post", "ACTION" => "/some/other/script.rb" }, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.multipart_form("/some/other/script.rb") { "test" }
+ output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post", "ACTION" => "/some/other/script.rb" }, "test")
+ end
+ end
+
+ describe "when passed action, enctype" do
+ it "returns a 'form'-element with the passed action and enctype" do
+ output = @html.multipart_form("/some/other/script.rb", "application/x-www-form-urlencoded")
+ output.should equal_element("FORM", { "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "post", "ACTION" => "/some/other/script.rb" }, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.multipart_form("/some/other/script.rb", "application/x-www-form-urlencoded") { "test" }
+ output.should equal_element("FORM", { "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "post", "ACTION" => "/some/other/script.rb" }, "test")
+ end
+ end
+
+ describe "when passed Hash" do
+ it "returns a 'form'-element with the passed Hash as attributes" do
+ output = @html.multipart_form("ID" => "test")
+ output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post", "ID" => "test" }, "")
+
+ output = @html.multipart_form("ID" => "test", "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get")
+ output.should equal_element("FORM", { "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ID" => "test" }, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.multipart_form("ID" => "test") { "test" }
+ output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post", "ID" => "test" }, "test")
+
+ output = @html.multipart_form("ID" => "test", "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get") { "test" }
+ output.should equal_element("FORM", { "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ID" => "test" }, "test")
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/password_field_spec.rb b/spec/ruby/library/cgi/htmlextension/password_field_spec.rb
new file mode 100644
index 0000000000..683bc428ba
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/password_field_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#password_field" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns an password-'input'-element without a name" do
+ output = @html.password_field
+ output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "password", "SIZE" => "40"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.password_field { "test" }
+ output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "password", "SIZE" => "40"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name" do
+ it "returns an password-'input'-element with the passed name" do
+ output = @html.password_field("test")
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "SIZE" => "40"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.password_field("test") { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "SIZE" => "40"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, value" do
+ it "returns an password-'input'-element with the passed name and value" do
+ output = @html.password_field("test", "some value")
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "40"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.password_field("test", "some value") { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "40"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, value, size" do
+ it "returns an password-'input'-element with the passed name, value and size" do
+ output = @html.password_field("test", "some value", 60)
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "60"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.password_field("test", "some value", 60) { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "60"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, value, size, maxlength" do
+ it "returns an password-'input'-element with the passed name, value, size and maxlength" do
+ output = @html.password_field("test", "some value", 60, 12)
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "60", "MAXLENGTH" => 12}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.password_field("test", "some value", 60, 12) { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "60", "MAXLENGTH" => 12}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "returns a checkbox-'input'-element using the passed Hash for attributes" do
+ output = @html.password_field("NAME" => "test", "VALUE" => "some value")
+ output.should equal_element("INPUT", { "NAME" => "test", "VALUE" => "some value", "TYPE" => "password" }, "", not_closed: true)
+
+ output = @html.password_field("TYPE" => "hidden")
+ output.should equal_element("INPUT", {"TYPE" => "password"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.password_field("NAME" => "test", "VALUE" => "some value") { "test" }
+ output.should equal_element("INPUT", { "NAME" => "test", "VALUE" => "some value", "TYPE" => "password" }, "", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/popup_menu_spec.rb b/spec/ruby/library/cgi/htmlextension/popup_menu_spec.rb
new file mode 100644
index 0000000000..3462be09b0
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/popup_menu_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+require_relative 'shared/popup_menu'
+
+describe "CGI::HtmlExtension#popup_menu" do
+ it_behaves_like :cgi_htmlextension_popup_menu, :popup_menu
+end
diff --git a/spec/ruby/library/cgi/htmlextension/radio_button_spec.rb b/spec/ruby/library/cgi/htmlextension/radio_button_spec.rb
new file mode 100644
index 0000000000..3dc3c879b5
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/radio_button_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#radio_button" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns a radio-'input'-element without a name" do
+ output = @html.radio_button
+ output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "radio"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.radio_button { "test" }
+ output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "radio"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name" do
+ it "returns a radio-'input'-element with the passed name" do
+ output = @html.radio_button("test")
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.radio_button("test") { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio"}, "", not_closed: true)
+ end
+ end
+
+ describe "CGI::HtmlExtension#checkbox when passed name, value" do
+ it "returns a radio-'input'-element with the passed name and value" do
+ output = @html.radio_button("test", "test-value")
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.radio_button("test", "test-value") { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, value, checked" do
+ it "returns a checked radio-'input'-element with the passed name and value when checked is true" do
+ output = @html.radio_button("test", "test-value", true)
+ output.should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true)
+
+ output = @html.radio_button("test", "test-value", false)
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true)
+
+ output = @html.radio_button("test", "test-value", nil)
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.radio_button("test", "test-value", nil) { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "returns a radio-'input'-element using the passed Hash for attributes" do
+ attributes = {"NAME" => "test", "VALUE" => "test-value", "CHECKED" => true}
+ output = @html.radio_button(attributes)
+ output.should equal_element("INPUT", attributes, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ attributes = {"NAME" => "test", "VALUE" => "test-value", "CHECKED" => true}
+ output = @html.radio_button(attributes) { "test" }
+ output.should equal_element("INPUT", attributes, "", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/radio_group_spec.rb b/spec/ruby/library/cgi/htmlextension/radio_group_spec.rb
new file mode 100644
index 0000000000..1bfd43449d
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/radio_group_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#radio_group" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed name, values ..." do
+ it "returns a sequence of 'radio'-elements with the passed name and the passed values" do
+ output = CGISpecs.split(@html.radio_group("test", "foo", "bar", "baz"))
+ output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true)
+ end
+
+ it "allows passing a value inside an Array" do
+ output = CGISpecs.split(@html.radio_group("test", ["foo"], "bar", ["baz"]))
+ output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true)
+ end
+
+ it "allows passing a value as an Array containing the value and the checked state or a label" do
+ output = CGISpecs.split(@html.radio_group("test", ["foo"], ["bar", true], ["baz", "label for baz"]))
+ output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "baz"}, "label for baz", not_closed: true)
+ end
+
+ # TODO: CGI does not like passing false instead of true.
+ it "allows passing a value as an Array containing the value, a label and the checked state" do
+ output = CGISpecs.split(@html.radio_group("test", ["foo", "label for foo", true], ["bar", "label for bar", false], ["baz", "label for baz", true]))
+ output[0].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "radio", "VALUE" => "foo"}, "label for foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "bar"}, "label for bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "radio", "VALUE" => "baz"}, "label for baz", not_closed: true)
+ end
+
+ it "returns an empty String when passed no values" do
+ @html.radio_group("test").should == ""
+ end
+
+ it "ignores a passed block" do
+ output = CGISpecs.split(@html.radio_group("test", "foo", "bar", "baz") { "test" })
+ output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "uses the passed Hash to generate the radio sequence" do
+ output = CGISpecs.split(@html.radio_group("NAME" => "name", "VALUES" => ["foo", "bar", "baz"]))
+ output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true)
+
+ output = CGISpecs.split(@html.radio_group("NAME" => "name", "VALUES" => [["foo"], ["bar", true], "baz"]))
+ output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "name", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true)
+
+ output = CGISpecs.split(@html.radio_group("NAME" => "name", "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]))
+ output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "1"}, "Foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "name", "TYPE" => "radio", "VALUE" => "2"}, "Bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "Baz"}, "Baz", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = CGISpecs.split(@html.radio_group("NAME" => "name", "VALUES" => ["foo", "bar", "baz"]) { "test" })
+ output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true)
+ output[1].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true)
+ output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/reset_spec.rb b/spec/ruby/library/cgi/htmlextension/reset_spec.rb
new file mode 100644
index 0000000000..86fa25fc8a
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/reset_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#reset" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns a reset-'input'-element" do
+ output = @html.reset
+ output.should equal_element("INPUT", {"TYPE" => "reset"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.reset { "test" }
+ output.should equal_element("INPUT", {"TYPE" => "reset"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed value" do
+ it "returns a reset-'input'-element with the passed value" do
+ output = @html.reset("Example")
+ output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.reset("Example") { "test" }
+ output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed value, name" do
+ it "returns a reset-'input'-element with the passed value and the passed name" do
+ output = @html.reset("Example", "test-name")
+ output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example", "NAME" => "test-name"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.reset("Example", "test-name") { "test" }
+ output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example", "NAME" => "test-name"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "returns a reset-'input'-element with the passed value" do
+ output = @html.reset("Example")
+ output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.reset("Example") { "test" }
+ output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example"}, "", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/scrolling_list_spec.rb b/spec/ruby/library/cgi/htmlextension/scrolling_list_spec.rb
new file mode 100644
index 0000000000..4eb0c86c1a
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/scrolling_list_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../spec_helper'
+require_relative 'fixtures/common'
+require 'cgi'
+require_relative 'shared/popup_menu'
+
+describe "CGI::HtmlExtension#scrolling_list" do
+ it_behaves_like :cgi_htmlextension_popup_menu, :scrolling_list
+end
diff --git a/spec/ruby/library/cgi/htmlextension/shared/popup_menu.rb b/spec/ruby/library/cgi/htmlextension/shared/popup_menu.rb
new file mode 100644
index 0000000000..4787fd3f8e
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/shared/popup_menu.rb
@@ -0,0 +1,94 @@
+describe :cgi_htmlextension_popup_menu, shared: true do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns an empty 'select'-element without a name" do
+ output = @html.send(@method)
+ output.should equal_element("SELECT", {"NAME" => ""}, "")
+ end
+
+ it "ignores a passed block" do
+ output = @html.send(@method) { "test" }
+ output.should equal_element("SELECT", {"NAME" => ""}, "")
+ end
+ end
+
+ describe "when passed name, values ..." do
+ it "returns a 'select'-element with the passed name containing 'option'-elements based on the passed values" do
+ content = @html.option("VALUE" => "foo") { "foo" }
+ content << @html.option("VALUE" => "bar") { "bar" }
+ content << @html.option("VALUE" => "baz") { "baz" }
+
+ output = @html.send(@method, "test", "foo", "bar", "baz")
+ output.should equal_element("SELECT", {"NAME" => "test"}, content)
+ end
+
+ it "allows passing values inside of arrays" do
+ content = @html.option("VALUE" => "foo") { "foo" }
+ content << @html.option("VALUE" => "bar") { "bar" }
+ content << @html.option("VALUE" => "baz") { "baz" }
+
+ output = @html.send(@method, "test", ["foo"], ["bar"], ["baz"])
+ output.should equal_element("SELECT", {"NAME" => "test"}, content)
+ end
+
+ it "allows passing a value as an Array containing the value and the select state or a label" do
+ content = @html.option("VALUE" => "foo") { "foo" }
+ content << @html.option("VALUE" => "bar", "SELECTED" => true) { "bar" }
+ content << @html.option("VALUE" => "baz") { "baz" }
+
+ output = @html.send(@method, "test", ["foo"], ["bar", true], "baz")
+ output.should equal_element("SELECT", {"NAME" => "test"}, content)
+ end
+
+ it "allows passing a value as an Array containing the value, a label and the select state" do
+ content = @html.option("VALUE" => "1") { "Foo" }
+ content << @html.option("VALUE" => "2", "SELECTED" => true) { "Bar" }
+ content << @html.option("VALUE" => "Baz") { "Baz" }
+
+ output = @html.send(@method, "test", ["1", "Foo"], ["2", "Bar", true], "Baz")
+ output.should equal_element("SELECT", {"NAME" => "test"}, content)
+ end
+
+ it "ignores a passed block" do
+ content = @html.option("VALUE" => "foo") { "foo" }
+ content << @html.option("VALUE" => "bar") { "bar" }
+ content << @html.option("VALUE" => "baz") { "baz" }
+
+ output = @html.send(@method, "test", "foo", "bar", "baz") { "woot" }
+ output.should equal_element("SELECT", {"NAME" => "test"}, content)
+ end
+ end
+
+ describe "when passed a Hash" do
+ it "uses the passed Hash to generate the 'select'-element and the 'option'-elements" do
+ attributes = {
+ "NAME" => "test", "SIZE" => 2, "MULTIPLE" => true,
+ "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]
+ }
+
+ content = @html.option("VALUE" => "1") { "Foo" }
+ content << @html.option("VALUE" => "2", "SELECTED" => true) { "Bar" }
+ content << @html.option("VALUE" => "Baz") { "Baz" }
+
+ output = @html.send(@method, attributes)
+ output.should equal_element("SELECT", {"NAME" => "test", "SIZE" => 2, "MULTIPLE" => true}, content)
+ end
+
+ it "ignores a passed block" do
+ attributes = {
+ "NAME" => "test", "SIZE" => 2, "MULTIPLE" => true,
+ "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]
+ }
+
+ content = @html.option("VALUE" => "1") { "Foo" }
+ content << @html.option("VALUE" => "2", "SELECTED" => true) { "Bar" }
+ content << @html.option("VALUE" => "Baz") { "Baz" }
+
+ output = @html.send(@method, attributes) { "testing" }
+ output.should equal_element("SELECT", {"NAME" => "test", "SIZE" => 2, "MULTIPLE" => true}, content)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/submit_spec.rb b/spec/ruby/library/cgi/htmlextension/submit_spec.rb
new file mode 100644
index 0000000000..063891b959
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/submit_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#submit" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns a submit-'input'-element" do
+ output = @html.submit
+ output.should equal_element("INPUT", {"TYPE" => "submit"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.submit { "test" }
+ output.should equal_element("INPUT", {"TYPE" => "submit"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed value" do
+ it "returns a submit-'input'-element with the passed value" do
+ output = @html.submit("Example")
+ output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.submit("Example") { "test" }
+ output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed value, name" do
+ it "returns a submit-'input'-element with the passed value and the passed name" do
+ output = @html.submit("Example", "test-name")
+ output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example", "NAME" => "test-name"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.submit("Example", "test-name") { "test" }
+ output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example", "NAME" => "test-name"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "returns a submit-'input'-element with the passed value" do
+ output = @html.submit("Example")
+ output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.submit("Example") { "test" }
+ output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example"}, "", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/text_field_spec.rb b/spec/ruby/library/cgi/htmlextension/text_field_spec.rb
new file mode 100644
index 0000000000..44b5a5e69f
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/text_field_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#text_field" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns an text-'input'-element without a name" do
+ output = @html.text_field
+ output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "text", "SIZE" => "40"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.text_field { "test" }
+ output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "text", "SIZE" => "40"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name" do
+ it "returns an text-'input'-element with the passed name" do
+ output = @html.text_field("test")
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "SIZE" => "40"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.text_field("test") { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "SIZE" => "40"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, value" do
+ it "returns an text-'input'-element with the passed name and value" do
+ output = @html.text_field("test", "some value")
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "40"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.text_field("test", "some value") { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "40"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, value, size" do
+ it "returns an text-'input'-element with the passed name, value and size" do
+ output = @html.text_field("test", "some value", 60)
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "60"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.text_field("test", "some value", 60) { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "60"}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed name, value, size, maxlength" do
+ it "returns an text-'input'-element with the passed name, value, size and maxlength" do
+ output = @html.text_field("test", "some value", 60, 12)
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "60", "MAXLENGTH" => 12}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.text_field("test", "some value", 60, 12) { "test" }
+ output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "60", "MAXLENGTH" => 12}, "", not_closed: true)
+ end
+ end
+
+ describe "when passed Hash" do
+ it "returns a checkbox-'input'-element using the passed Hash for attributes" do
+ output = @html.text_field("NAME" => "test", "VALUE" => "some value")
+ output.should equal_element("INPUT", { "NAME" => "test", "VALUE" => "some value", "TYPE" => "text" }, "", not_closed: true)
+
+ output = @html.text_field("TYPE" => "hidden")
+ output.should equal_element("INPUT", {"TYPE" => "text"}, "", not_closed: true)
+ end
+
+ it "ignores a passed block" do
+ output = @html.text_field("NAME" => "test", "VALUE" => "some value") { "test" }
+ output.should equal_element("INPUT", { "NAME" => "test", "VALUE" => "some value", "TYPE" => "text" }, "", not_closed: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/htmlextension/textarea_spec.rb b/spec/ruby/library/cgi/htmlextension/textarea_spec.rb
new file mode 100644
index 0000000000..db84a973d2
--- /dev/null
+++ b/spec/ruby/library/cgi/htmlextension/textarea_spec.rb
@@ -0,0 +1,73 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'fixtures/common'
+
+describe "CGI::HtmlExtension#textarea" do
+ before :each do
+ @html = CGISpecs.cgi_new
+ end
+
+ describe "when passed no arguments" do
+ it "returns an 'textarea'-element without a name" do
+ output = @html.textarea
+ output.should equal_element("TEXTAREA", {"NAME" => "", "COLS" => "70", "ROWS" => "10"}, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.textarea { "Example" }
+ output.should equal_element("TEXTAREA", {"NAME" => "", "COLS" => "70", "ROWS" => "10"}, "Example")
+ end
+ end
+
+ describe "when passed name" do
+ it "returns an 'textarea'-element with the passed name" do
+ output = @html.textarea("test")
+ output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "70", "ROWS" => "10"}, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.textarea("test") { "Example" }
+ output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "70", "ROWS" => "10"}, "Example")
+ end
+ end
+
+ describe "when passed name, cols" do
+ it "returns an 'textarea'-element with the passed name and the passed amount of columns" do
+ output = @html.textarea("test", 40)
+ output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "40", "ROWS" => "10"}, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.textarea("test", 40) { "Example" }
+ output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "40", "ROWS" => "10"}, "Example")
+ end
+ end
+
+ describe "when passed name, cols, rows" do
+ it "returns an 'textarea'-element with the passed name, the passed amount of columns and the passed number of rows" do
+ output = @html.textarea("test", 40, 5)
+ output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "40", "ROWS" => "5"}, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ output = @html.textarea("test", 40, 5) { "Example" }
+ output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "40", "ROWS" => "5"}, "Example")
+ end
+ end
+
+ describe "when passed Hash" do
+ it "uses the passed Hash as attributes" do
+ @html.textarea("ID" => "test").should == '<TEXTAREA ID="test"></TEXTAREA>'
+
+ attributes = {"ID" => "test-id", "NAME" => "test-name"}
+ output = @html.textarea(attributes)
+ output.should equal_element("TEXTAREA", attributes, "")
+ end
+
+ it "includes the return value of the passed block when passed a block" do
+ attributes = {"ID" => "test-id", "NAME" => "test-name"}
+ output = @html.textarea(attributes) { "test" }
+ output.should equal_element("TEXTAREA", attributes, "test")
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/http_header_spec.rb b/spec/ruby/library/cgi/http_header_spec.rb
new file mode 100644
index 0000000000..4094bebed3
--- /dev/null
+++ b/spec/ruby/library/cgi/http_header_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+require_relative 'shared/http_header'
+
+describe "CGI#http_header" do
+ it_behaves_like :cgi_http_header, :http_header
+end
diff --git a/spec/ruby/library/cgi/initialize_spec.rb b/spec/ruby/library/cgi/initialize_spec.rb
new file mode 100644
index 0000000000..f794f157f0
--- /dev/null
+++ b/spec/ruby/library/cgi/initialize_spec.rb
@@ -0,0 +1,133 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI#initialize" do
+ it "is private" do
+ CGI.should have_private_instance_method(:initialize)
+ end
+end
+
+describe "CGI#initialize when passed no arguments" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.allocate
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "extends self with CGI::QueryExtension" do
+ @cgi.send(:initialize)
+ @cgi.should be_kind_of(CGI::QueryExtension)
+ end
+
+ it "does not extend self with CGI::HtmlExtension" do
+ @cgi.send(:initialize)
+ @cgi.should_not be_kind_of(CGI::HtmlExtension)
+ end
+
+ it "does not extend self with any of the other HTML modules" do
+ @cgi.send(:initialize)
+ @cgi.should_not be_kind_of(CGI::Html3)
+ @cgi.should_not be_kind_of(CGI::HtmlExtension)
+ @cgi.should_not be_kind_of(CGI::Html4)
+ @cgi.should_not be_kind_of(CGI::Html4Tr)
+ @cgi.should_not be_kind_of(CGI::Html4Fr)
+ end
+
+ it "sets #cookies based on ENV['HTTP_COOKIE']" do
+ begin
+ old_env, ENV["HTTP_COOKIE"] = ENV["HTTP_COOKIE"], "test=test yay"
+ @cgi.send(:initialize)
+ @cgi.cookies.should == { "test"=>[ "test yay" ] }
+ ensure
+ ENV["HTTP_COOKIE"] = old_env
+ end
+ end
+
+ it "sets #params based on ENV['QUERY_STRING'] when ENV['REQUEST_METHOD'] is GET" do
+ begin
+ old_env_query, ENV["QUERY_STRING"] = ENV["QUERY_STRING"], "?test=a&test2=b"
+ old_env_method, ENV["REQUEST_METHOD"] = ENV["REQUEST_METHOD"], "GET"
+ @cgi.send(:initialize)
+ @cgi.params.should == { "test2" => ["b"], "?test" => ["a"] }
+ ensure
+ ENV["QUERY_STRING"] = old_env_query
+ ENV["REQUEST_METHOD"] = old_env_method
+ end
+ end
+
+ it "sets #params based on ENV['QUERY_STRING'] when ENV['REQUEST_METHOD'] is HEAD" do
+ begin
+ old_env_query, ENV["QUERY_STRING"] = ENV["QUERY_STRING"], "?test=a&test2=b"
+ old_env_method, ENV["REQUEST_METHOD"] = ENV["REQUEST_METHOD"], "HEAD"
+ @cgi.send(:initialize)
+ @cgi.params.should == { "test2" => ["b"], "?test" => ["a"] }
+ ensure
+ ENV["QUERY_STRING"] = old_env_query
+ ENV["REQUEST_METHOD"] = old_env_method
+ end
+ end
+end
+
+describe "CGI#initialize when passed type" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.allocate
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+
+ it "extends self with CGI::QueryExtension" do
+ @cgi.send(:initialize, "test")
+ @cgi.should be_kind_of(CGI::QueryExtension)
+ end
+
+ it "extends self with CGI::QueryExtension, CGI::Html3 and CGI::HtmlExtension when the passed type is 'html3'" do
+ @cgi.send(:initialize, "html3")
+ @cgi.should be_kind_of(CGI::Html3)
+ @cgi.should be_kind_of(CGI::HtmlExtension)
+ @cgi.should be_kind_of(CGI::QueryExtension)
+
+ @cgi.should_not be_kind_of(CGI::Html4)
+ @cgi.should_not be_kind_of(CGI::Html4Tr)
+ @cgi.should_not be_kind_of(CGI::Html4Fr)
+ end
+
+ it "extends self with CGI::QueryExtension, CGI::Html4 and CGI::HtmlExtension when the passed type is 'html4'" do
+ @cgi.send(:initialize, "html4")
+ @cgi.should be_kind_of(CGI::Html4)
+ @cgi.should be_kind_of(CGI::HtmlExtension)
+ @cgi.should be_kind_of(CGI::QueryExtension)
+
+ @cgi.should_not be_kind_of(CGI::Html3)
+ @cgi.should_not be_kind_of(CGI::Html4Tr)
+ @cgi.should_not be_kind_of(CGI::Html4Fr)
+ end
+
+ it "extends self with CGI::QueryExtension, CGI::Html4Tr and CGI::HtmlExtension when the passed type is 'html4Tr'" do
+ @cgi.send(:initialize, "html4Tr")
+ @cgi.should be_kind_of(CGI::Html4Tr)
+ @cgi.should be_kind_of(CGI::HtmlExtension)
+ @cgi.should be_kind_of(CGI::QueryExtension)
+
+ @cgi.should_not be_kind_of(CGI::Html3)
+ @cgi.should_not be_kind_of(CGI::Html4)
+ @cgi.should_not be_kind_of(CGI::Html4Fr)
+ end
+
+ it "extends self with CGI::QueryExtension, CGI::Html4Tr, CGI::Html4Fr and CGI::HtmlExtension when the passed type is 'html4Fr'" do
+ @cgi.send(:initialize, "html4Fr")
+ @cgi.should be_kind_of(CGI::Html4Tr)
+ @cgi.should be_kind_of(CGI::Html4Fr)
+ @cgi.should be_kind_of(CGI::HtmlExtension)
+ @cgi.should be_kind_of(CGI::QueryExtension)
+
+ @cgi.should_not be_kind_of(CGI::Html3)
+ @cgi.should_not be_kind_of(CGI::Html4)
+ end
+end
diff --git a/spec/ruby/library/cgi/out_spec.rb b/spec/ruby/library/cgi/out_spec.rb
new file mode 100644
index 0000000000..bc09f5bcbb
--- /dev/null
+++ b/spec/ruby/library/cgi/out_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI#out" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ $stdout, @old_stdout = IOStub.new, $stdout
+ end
+
+ after :each do
+ $stdout = @old_stdout
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "it writes a HTMl header based on the passed argument to $stdout" do
+ @cgi.out { "" }
+ $stdout.should == "Content-Type: text/html\r\nContent-Length: 0\r\n\r\n"
+ end
+
+ it "appends the block's return value to the HTML header" do
+ @cgi.out { "test!" }
+ $stdout.should == "Content-Type: text/html\r\nContent-Length: 5\r\n\r\ntest!"
+ end
+
+ it "automatically sets the Content-Length Header based on the block's return value" do
+ @cgi.out { "0123456789" }
+ $stdout.should == "Content-Type: text/html\r\nContent-Length: 10\r\n\r\n0123456789"
+ end
+
+ it "includes Cookies in the @output_cookies field" do
+ @cgi.instance_variable_set(:@output_cookies, ["multiple", "cookies"])
+ @cgi.out { "" }
+ $stdout.should == "Content-Type: text/html\r\nContent-Length: 0\r\nSet-Cookie: multiple\r\nSet-Cookie: cookies\r\n\r\n"
+ end
+end
+
+describe "CGI#out when passed no block" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "raises a LocalJumpError" do
+ -> { @cgi.out }.should raise_error(LocalJumpError)
+ end
+end
diff --git a/spec/ruby/library/cgi/parse_spec.rb b/spec/ruby/library/cgi/parse_spec.rb
new file mode 100644
index 0000000000..04539b1226
--- /dev/null
+++ b/spec/ruby/library/cgi/parse_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI.parse when passed String" do
+ it "parses a HTTP Query String into a Hash" do
+ CGI.parse("test=test&a=b").should == { "a" => ["b"], "test" => ["test"] }
+ CGI.parse("test=1,2,3").should == { "test" => ["1,2,3"] }
+ CGI.parse("test=a&a=&b=").should == { "test" => ["a"], "a" => [""], "b" => [""] }
+ end
+
+ it "parses query strings with semicolons in place of ampersands" do
+ CGI.parse("test=test;a=b").should == { "a" => ["b"], "test" => ["test"] }
+ CGI.parse("test=a;a=;b=").should == { "test" => ["a"], "a" => [""], "b" => [""] }
+ end
+
+ it "allows passing multiple values for one key" do
+ CGI.parse("test=1&test=2&test=3").should == { "test" => ["1", "2", "3"] }
+ CGI.parse("test[]=1&test[]=2&test[]=3").should == { "test[]" => [ "1", "2", "3" ] }
+ end
+
+ it "unescapes keys and values" do
+ CGI.parse("hello%3F=hello%21").should == { "hello?" => ["hello!"] }
+ end
+end
diff --git a/spec/ruby/library/cgi/pretty_spec.rb b/spec/ruby/library/cgi/pretty_spec.rb
new file mode 100644
index 0000000000..a7b7505c15
--- /dev/null
+++ b/spec/ruby/library/cgi/pretty_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI.pretty when passed html" do
+ it "indents the passed html String with two spaces" do
+ CGI.pretty("<HTML><BODY></BODY></HTML>").should == <<-EOS
+<HTML>
+ <BODY>
+ </BODY>
+</HTML>
+EOS
+ end
+end
+
+describe "CGI.pretty when passed html, indentation_unit" do
+ it "indents the passed html String with the passed indentation_unit" do
+ CGI.pretty("<HTML><BODY></BODY></HTML>", "\t").should == <<-EOS
+<HTML>
+\t<BODY>
+\t</BODY>
+</HTML>
+EOS
+ end
+end
diff --git a/spec/ruby/library/cgi/print_spec.rb b/spec/ruby/library/cgi/print_spec.rb
new file mode 100644
index 0000000000..18ab8d673b
--- /dev/null
+++ b/spec/ruby/library/cgi/print_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI#print" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "passes all arguments to $stdout.print" do
+ $stdout.should_receive(:print).with("test")
+ @cgi.print("test")
+
+ $stdout.should_receive(:print).with("one", "two", "three", ["four", "five"])
+ @cgi.print("one", "two", "three", ["four", "five"])
+ end
+
+ it "returns the result of calling $stdout.print" do
+ $stdout.should_receive(:print).with("test").and_return(:expected)
+ @cgi.print("test").should == :expected
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/accept_charset_spec.rb b/spec/ruby/library/cgi/queryextension/accept_charset_spec.rb
new file mode 100644
index 0000000000..0487569b9c
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/accept_charset_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#accept_charset" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_ACCEPT_CHARSET']" do
+ old_value, ENV['HTTP_ACCEPT_CHARSET'] = ENV['HTTP_ACCEPT_CHARSET'], "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
+ begin
+ @cgi.accept_charset.should == "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
+ ensure
+ ENV['HTTP_ACCEPT_CHARSET'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/accept_encoding_spec.rb b/spec/ruby/library/cgi/queryextension/accept_encoding_spec.rb
new file mode 100644
index 0000000000..35ff3c2b30
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/accept_encoding_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#accept_encoding" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_ACCEPT_ENCODING']" do
+ old_value, ENV['HTTP_ACCEPT_ENCODING'] = ENV['HTTP_ACCEPT_ENCODING'], "gzip,deflate"
+ begin
+ @cgi.accept_encoding.should == "gzip,deflate"
+ ensure
+ ENV['HTTP_ACCEPT_ENCODING'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/accept_language_spec.rb b/spec/ruby/library/cgi/queryextension/accept_language_spec.rb
new file mode 100644
index 0000000000..4a15d58914
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/accept_language_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#accept_language" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_ACCEPT_LANGUAGE']" do
+ old_value, ENV['HTTP_ACCEPT_LANGUAGE'] = ENV['HTTP_ACCEPT_LANGUAGE'], "en-us,en;q=0.5"
+ begin
+ @cgi.accept_language.should == "en-us,en;q=0.5"
+ ensure
+ ENV['HTTP_ACCEPT_LANGUAGE'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/accept_spec.rb b/spec/ruby/library/cgi/queryextension/accept_spec.rb
new file mode 100644
index 0000000000..af5209ffbe
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/accept_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#accept" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_ACCEPT']" do
+ old_value, ENV['HTTP_ACCEPT'] = ENV['HTTP_ACCEPT'], "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
+ begin
+ @cgi.accept.should == "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
+ ensure
+ ENV['HTTP_ACCEPT'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/auth_type_spec.rb b/spec/ruby/library/cgi/queryextension/auth_type_spec.rb
new file mode 100644
index 0000000000..25318269b1
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/auth_type_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#auth_type" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['AUTH_TYPE']" do
+ old_value, ENV['AUTH_TYPE'] = ENV['AUTH_TYPE'], "Basic"
+ begin
+ @cgi.auth_type.should == "Basic"
+ ensure
+ ENV['AUTH_TYPE'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/cache_control_spec.rb b/spec/ruby/library/cgi/queryextension/cache_control_spec.rb
new file mode 100644
index 0000000000..0471307c22
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/cache_control_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#cache_control" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_CACHE_CONTROL']" do
+ old_value, ENV['HTTP_CACHE_CONTROL'] = ENV['HTTP_CACHE_CONTROL'], "no-cache"
+ begin
+ @cgi.cache_control.should == "no-cache"
+ ensure
+ ENV['HTTP_CACHE_CONTROL'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/content_length_spec.rb b/spec/ruby/library/cgi/queryextension/content_length_spec.rb
new file mode 100644
index 0000000000..de823f7119
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/content_length_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#content_length" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['CONTENT_LENGTH'] as Integer" do
+ old_value = ENV['CONTENT_LENGTH']
+ begin
+ ENV['CONTENT_LENGTH'] = nil
+ @cgi.content_length.should be_nil
+
+ ENV['CONTENT_LENGTH'] = "100"
+ @cgi.content_length.should eql(100)
+ ensure
+ ENV['CONTENT_LENGTH'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/content_type_spec.rb b/spec/ruby/library/cgi/queryextension/content_type_spec.rb
new file mode 100644
index 0000000000..49b8389c87
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/content_type_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#content_type" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['CONTENT_TYPE']" do
+ old_value, ENV['CONTENT_TYPE'] = ENV['CONTENT_TYPE'], "text/html"
+ begin
+ @cgi.content_type.should == "text/html"
+ ensure
+ ENV['CONTENT_TYPE'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/cookies_spec.rb b/spec/ruby/library/cgi/queryextension/cookies_spec.rb
new file mode 100644
index 0000000000..4befd61ab7
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/cookies_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#cookies" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "CGI::QueryExtension#cookies=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/cgi/queryextension/element_reference_spec.rb b/spec/ruby/library/cgi/queryextension/element_reference_spec.rb
new file mode 100644
index 0000000000..6ac5b46407
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/element_reference_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#[]" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ ENV['QUERY_STRING'], @old_query_string = "one=a&two=b&two=c", ENV['QUERY_STRING']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ ENV['QUERY_STRING'] = @old_query_string
+ end
+
+ it "it returns the value for the parameter with the given key" do
+ @cgi["one"].should == "a"
+ end
+
+ it "only returns the first value for parameters with multiple values" do
+ @cgi["two"].should == "b"
+ end
+
+ it "returns a String" do
+ @cgi["one"].should be_kind_of(String)
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/from_spec.rb b/spec/ruby/library/cgi/queryextension/from_spec.rb
new file mode 100644
index 0000000000..04a992cfc7
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/from_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#from" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_FROM']" do
+ old_value, ENV['HTTP_FROM'] = ENV['HTTP_FROM'], "googlebot(at)googlebot.com"
+ begin
+ @cgi.from.should == "googlebot(at)googlebot.com"
+ ensure
+ ENV['HTTP_FROM'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/gateway_interface_spec.rb b/spec/ruby/library/cgi/queryextension/gateway_interface_spec.rb
new file mode 100644
index 0000000000..3ead5bd8bf
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/gateway_interface_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#gateway_interface" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['GATEWAY_INTERFACE']" do
+ old_value, ENV['GATEWAY_INTERFACE'] = ENV['GATEWAY_INTERFACE'], "CGI/1.1"
+ begin
+ @cgi.gateway_interface.should == "CGI/1.1"
+ ensure
+ ENV['GATEWAY_INTERFACE'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/has_key_spec.rb b/spec/ruby/library/cgi/queryextension/has_key_spec.rb
new file mode 100644
index 0000000000..525b45b507
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/has_key_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'shared/has_key'
+
+describe "CGI::QueryExtension#has_key?" do
+ it_behaves_like :cgi_query_extension_has_key_p, :has_key?
+end
diff --git a/spec/ruby/library/cgi/queryextension/host_spec.rb b/spec/ruby/library/cgi/queryextension/host_spec.rb
new file mode 100644
index 0000000000..e820e0afc3
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/host_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#host" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_HOST']" do
+ old_value, ENV['HTTP_HOST'] = ENV['HTTP_HOST'], "localhost"
+ begin
+ @cgi.host.should == "localhost"
+ ensure
+ ENV['HTTP_HOST'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/include_spec.rb b/spec/ruby/library/cgi/queryextension/include_spec.rb
new file mode 100644
index 0000000000..12365ea284
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/include_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'shared/has_key'
+
+describe "CGI::QueryExtension#include?" do
+ it_behaves_like :cgi_query_extension_has_key_p, :include?
+end
diff --git a/spec/ruby/library/cgi/queryextension/key_spec.rb b/spec/ruby/library/cgi/queryextension/key_spec.rb
new file mode 100644
index 0000000000..d0f1e4cee2
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/key_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require_relative 'shared/has_key'
+
+describe "CGI::QueryExtension#key?" do
+ it_behaves_like :cgi_query_extension_has_key_p, :key?
+end
diff --git a/spec/ruby/library/cgi/queryextension/keys_spec.rb b/spec/ruby/library/cgi/queryextension/keys_spec.rb
new file mode 100644
index 0000000000..14d77180fa
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/keys_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#keys" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ ENV['QUERY_STRING'], @old_query_string = "one=a&two=b", ENV['QUERY_STRING']
+
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ ENV['QUERY_STRING'] = @old_query_string
+ end
+
+ it "returns all parameter keys as an Array" do
+ @cgi.keys.sort.should == ["one", "two"]
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/multipart_spec.rb b/spec/ruby/library/cgi/queryextension/multipart_spec.rb
new file mode 100644
index 0000000000..ced4cb05a1
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/multipart_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+require "stringio"
+
+describe "CGI::QueryExtension#multipart?" do
+ before :each do
+ @old_stdin = $stdin
+
+ @old_request_method = ENV['REQUEST_METHOD']
+ @old_content_type = ENV['CONTENT_TYPE']
+ @old_content_length = ENV['CONTENT_LENGTH']
+
+ ENV['REQUEST_METHOD'] = "POST"
+ ENV["CONTENT_TYPE"] = "multipart/form-data; boundary=---------------------------1137522503144128232716531729"
+ ENV["CONTENT_LENGTH"] = "222"
+
+ $stdin = StringIO.new <<-EOS
+-----------------------------1137522503144128232716531729\r
+Content-Disposition: form-data; name="file"; filename=""\r
+Content-Type: application/octet-stream\r
+\r
+\r
+-----------------------------1137522503144128232716531729--\r
+EOS
+
+ @cgi = CGI.new
+ end
+
+ after :each do
+ $stdin = @old_stdin
+
+ ENV['REQUEST_METHOD'] = @old_request_method
+ ENV['CONTENT_TYPE'] = @old_content_type
+ ENV['CONTENT_LENGTH'] = @old_content_length
+ end
+
+ it "returns true if the current Request is a multipart request" do
+ @cgi.multipart?.should be_true
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/negotiate_spec.rb b/spec/ruby/library/cgi/queryextension/negotiate_spec.rb
new file mode 100644
index 0000000000..b6fe036042
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/negotiate_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#negotiate" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_NEGOTIATE']" do
+ old_value, ENV['HTTP_NEGOTIATE'] = ENV['HTTP_NEGOTIATE'], "trans"
+ begin
+ @cgi.negotiate.should == "trans"
+ ensure
+ ENV['HTTP_NEGOTIATE'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/params_spec.rb b/spec/ruby/library/cgi/queryextension/params_spec.rb
new file mode 100644
index 0000000000..f4449e3c8a
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/params_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#params" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ ENV['QUERY_STRING'], @old_query_string = "one=a&two=b&two=c&three", ENV['QUERY_STRING']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['QUERY_STRING'] = @old_query_string
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns the parsed HTTP Query Params" do
+ @cgi.params.should == {"three"=>[], "two"=>["b", "c"], "one"=>["a"]}
+ end
+end
+
+describe "CGI::QueryExtension#params=" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "sets the HTTP Query Params to the passed argument" do
+ @cgi.params.should == {}
+
+ @cgi.params = {"one"=>["a"], "two"=>["b", "c"]}
+ @cgi.params.should == {"one"=>["a"], "two"=>["b", "c"]}
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/path_info_spec.rb b/spec/ruby/library/cgi/queryextension/path_info_spec.rb
new file mode 100644
index 0000000000..9785df85f1
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/path_info_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#path_info" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['PATH_INFO']" do
+ old_value, ENV['PATH_INFO'] = ENV['PATH_INFO'], "/test/path"
+ begin
+ @cgi.path_info.should == "/test/path"
+ ensure
+ ENV['PATH_INFO'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/path_translated_spec.rb b/spec/ruby/library/cgi/queryextension/path_translated_spec.rb
new file mode 100644
index 0000000000..417a749341
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/path_translated_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#path_translated" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['PATH_TRANSLATED']" do
+ old_value, ENV['PATH_TRANSLATED'] = ENV['PATH_TRANSLATED'], "/full/path/to/dir"
+ begin
+ @cgi.path_translated.should == "/full/path/to/dir"
+ ensure
+ ENV['PATH_TRANSLATED'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/pragma_spec.rb b/spec/ruby/library/cgi/queryextension/pragma_spec.rb
new file mode 100644
index 0000000000..02d5c91221
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/pragma_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#pragma" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_PRAGMA']" do
+ old_value, ENV['HTTP_PRAGMA'] = ENV['HTTP_PRAGMA'], "no-cache"
+ begin
+ @cgi.pragma.should == "no-cache"
+ ensure
+ ENV['HTTP_PRAGMA'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/query_string_spec.rb b/spec/ruby/library/cgi/queryextension/query_string_spec.rb
new file mode 100644
index 0000000000..a6b454a7eb
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/query_string_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#query_string" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['QUERY_STRING']" do
+ old_value, ENV['QUERY_STRING'] = ENV['QUERY_STRING'], "one=a&two=b"
+ begin
+ @cgi.query_string.should == "one=a&two=b"
+ ensure
+ ENV['QUERY_STRING'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/raw_cookie2_spec.rb b/spec/ruby/library/cgi/queryextension/raw_cookie2_spec.rb
new file mode 100644
index 0000000000..3d7072e346
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/raw_cookie2_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#raw_cookie2" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_COOKIE2']" do
+ old_value, ENV['HTTP_COOKIE2'] = ENV['HTTP_COOKIE2'], "some_cookie=data"
+ begin
+ @cgi.raw_cookie2.should == "some_cookie=data"
+ ensure
+ ENV['HTTP_COOKIE2'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/raw_cookie_spec.rb b/spec/ruby/library/cgi/queryextension/raw_cookie_spec.rb
new file mode 100644
index 0000000000..ede7b9ff87
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/raw_cookie_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#raw_cookie" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_COOKIE']" do
+ old_value, ENV['HTTP_COOKIE'] = ENV['HTTP_COOKIE'], "some_cookie=data"
+ begin
+ @cgi.raw_cookie.should == "some_cookie=data"
+ ensure
+ ENV['HTTP_COOKIE'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/referer_spec.rb b/spec/ruby/library/cgi/queryextension/referer_spec.rb
new file mode 100644
index 0000000000..6cd5dc96f8
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/referer_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#referer" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_REFERER']" do
+ old_value, ENV['HTTP_REFERER'] = ENV['HTTP_REFERER'], "example.com"
+ begin
+ @cgi.referer.should == "example.com"
+ ensure
+ ENV['HTTP_REFERER'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/remote_addr_spec.rb b/spec/ruby/library/cgi/queryextension/remote_addr_spec.rb
new file mode 100644
index 0000000000..0259402b23
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/remote_addr_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#remote_addr" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['REMOTE_ADDR']" do
+ old_value, ENV['REMOTE_ADDR'] = ENV['REMOTE_ADDR'], "127.0.0.1"
+ begin
+ @cgi.remote_addr.should == "127.0.0.1"
+ ensure
+ ENV['REMOTE_ADDR'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/remote_host_spec.rb b/spec/ruby/library/cgi/queryextension/remote_host_spec.rb
new file mode 100644
index 0000000000..cf77e0031b
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/remote_host_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#remote_host" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['REMOTE_HOST']" do
+ old_value, ENV['REMOTE_HOST'] = ENV['REMOTE_HOST'], "test.host"
+ begin
+ @cgi.remote_host.should == "test.host"
+ ensure
+ ENV['REMOTE_HOST'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/remote_ident_spec.rb b/spec/ruby/library/cgi/queryextension/remote_ident_spec.rb
new file mode 100644
index 0000000000..5b51d6b8ee
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/remote_ident_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#remote_ident" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['REMOTE_IDENT']" do
+ old_value, ENV['REMOTE_IDENT'] = ENV['REMOTE_IDENT'], "no-idea-what-this-is-for"
+ begin
+ @cgi.remote_ident.should == "no-idea-what-this-is-for"
+ ensure
+ ENV['REMOTE_IDENT'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/remote_user_spec.rb b/spec/ruby/library/cgi/queryextension/remote_user_spec.rb
new file mode 100644
index 0000000000..1a93bb6561
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/remote_user_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#remote_user" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['REMOTE_USER']" do
+ old_value, ENV['REMOTE_USER'] = ENV['REMOTE_USER'], "username"
+ begin
+ @cgi.remote_user.should == "username"
+ ensure
+ ENV['REMOTE_USER'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/request_method_spec.rb b/spec/ruby/library/cgi/queryextension/request_method_spec.rb
new file mode 100644
index 0000000000..eabdd6998b
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/request_method_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#request_method" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['REQUEST_METHOD']" do
+ old_value, ENV['REQUEST_METHOD'] = ENV['REQUEST_METHOD'], "GET"
+ begin
+ @cgi.request_method.should == "GET"
+ ensure
+ ENV['REQUEST_METHOD'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/script_name_spec.rb b/spec/ruby/library/cgi/queryextension/script_name_spec.rb
new file mode 100644
index 0000000000..341b7b6fea
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/script_name_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#script_name" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['SCRIPT_NAME']" do
+ old_value, ENV['SCRIPT_NAME'] = ENV['SCRIPT_NAME'], "/path/to/script.rb"
+ begin
+ @cgi.script_name.should == "/path/to/script.rb"
+ ensure
+ ENV['SCRIPT_NAME'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/server_name_spec.rb b/spec/ruby/library/cgi/queryextension/server_name_spec.rb
new file mode 100644
index 0000000000..b12aaf8c5c
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/server_name_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#server_name" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['SERVER_NAME']" do
+ old_value, ENV['SERVER_NAME'] = ENV['SERVER_NAME'], "localhost"
+ begin
+ @cgi.server_name.should == "localhost"
+ ensure
+ ENV['SERVER_NAME'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/server_port_spec.rb b/spec/ruby/library/cgi/queryextension/server_port_spec.rb
new file mode 100644
index 0000000000..0e688a99bf
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/server_port_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#server_port" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['SERVER_PORT'] as Integer" do
+ old_value = ENV['SERVER_PORT']
+ begin
+ ENV['SERVER_PORT'] = nil
+ @cgi.server_port.should be_nil
+
+ ENV['SERVER_PORT'] = "3000"
+ @cgi.server_port.should eql(3000)
+ ensure
+ ENV['SERVER_PORT'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/server_protocol_spec.rb b/spec/ruby/library/cgi/queryextension/server_protocol_spec.rb
new file mode 100644
index 0000000000..f9dcf3c5b8
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/server_protocol_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#server_protocol" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['SERVER_PROTOCOL']" do
+ old_value, ENV['SERVER_PROTOCOL'] = ENV['SERVER_PROTOCOL'], "HTTP/1.1"
+ begin
+ @cgi.server_protocol.should == "HTTP/1.1"
+ ensure
+ ENV['SERVER_PROTOCOL'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/server_software_spec.rb b/spec/ruby/library/cgi/queryextension/server_software_spec.rb
new file mode 100644
index 0000000000..df349cdf2e
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/server_software_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#server_software" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['SERVER_SOFTWARE']" do
+ old_value, ENV['SERVER_SOFTWARE'] = ENV['SERVER_SOFTWARE'], "Server/1.0.0"
+ begin
+ @cgi.server_software.should == "Server/1.0.0"
+ ensure
+ ENV['SERVER_SOFTWARE'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/shared/has_key.rb b/spec/ruby/library/cgi/queryextension/shared/has_key.rb
new file mode 100644
index 0000000000..cfac5865fa
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/shared/has_key.rb
@@ -0,0 +1,19 @@
+describe :cgi_query_extension_has_key_p, shared: true do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ ENV['QUERY_STRING'], @old_query_string = "one=a&two=b", ENV['QUERY_STRING']
+
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ ENV['QUERY_STRING'] = @old_query_string
+ end
+
+ it "returns true when the passed key exists in the HTTP Query" do
+ @cgi.send(@method, "one").should be_true
+ @cgi.send(@method, "two").should be_true
+ @cgi.send(@method, "three").should be_false
+ end
+end
diff --git a/spec/ruby/library/cgi/queryextension/user_agent_spec.rb b/spec/ruby/library/cgi/queryextension/user_agent_spec.rb
new file mode 100644
index 0000000000..ec5ef187dd
--- /dev/null
+++ b/spec/ruby/library/cgi/queryextension/user_agent_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe "CGI::QueryExtension#user_agent" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+ it "returns ENV['HTTP_USER_AGENT']" do
+ old_value, ENV['HTTP_USER_AGENT'] = ENV['HTTP_USER_AGENT'], "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_2; de-de) AppleWebKit/527+ (KHTML, like Gecko) Version/3.1 Safari/525.13"
+ begin
+ @cgi.user_agent.should == "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_2; de-de) AppleWebKit/527+ (KHTML, like Gecko) Version/3.1 Safari/525.13"
+ ensure
+ ENV['HTTP_USER_AGENT'] = old_value
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/rfc1123_date_spec.rb b/spec/ruby/library/cgi/rfc1123_date_spec.rb
new file mode 100644
index 0000000000..6904eeabaa
--- /dev/null
+++ b/spec/ruby/library/cgi/rfc1123_date_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI.rfc1123_date when passed Time" do
+ it "returns the passed Time formatted in RFC1123 ('Sat, 01 Dec 2007 15:56:42 GMT')" do
+ input = Time.at(1196524602)
+ expected = 'Sat, 01 Dec 2007 15:56:42 GMT'
+ CGI.rfc1123_date(input).should == expected
+ end
+end
diff --git a/spec/ruby/library/cgi/shared/http_header.rb b/spec/ruby/library/cgi/shared/http_header.rb
new file mode 100644
index 0000000000..b225b5925e
--- /dev/null
+++ b/spec/ruby/library/cgi/shared/http_header.rb
@@ -0,0 +1,112 @@
+require_relative '../../../spec_helper'
+require 'cgi'
+
+describe :cgi_http_header, shared: true do
+ describe "CGI#http_header when passed no arguments" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+
+ it "returns a HTTP header specifying the Content-Type as text/html" do
+ @cgi.send(@method).should == "Content-Type: text/html\r\n\r\n"
+ end
+
+ it "includes Cookies in the @output_cookies field" do
+ @cgi.instance_variable_set(:@output_cookies, ["multiple", "cookies"])
+ @cgi.send(@method).should == "Content-Type: text/html\r\nSet-Cookie: multiple\r\nSet-Cookie: cookies\r\n\r\n"
+ end
+ end
+
+ describe "CGI#http_header when passed String" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+
+ it "returns a HTTP header specifying the Content-Type as the passed String's content" do
+ @cgi.send(@method, "text/plain").should == "Content-Type: text/plain\r\n\r\n"
+ end
+
+ it "includes Cookies in the @output_cookies field" do
+ @cgi.instance_variable_set(:@output_cookies, ["multiple", "cookies"])
+ @cgi.send(@method, "text/plain").should == "Content-Type: text/plain\r\nSet-Cookie: multiple\r\nSet-Cookie: cookies\r\n\r\n"
+ end
+ end
+
+ describe "CGI#http_header when passed Hash" do
+ before :each do
+ ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD']
+ @cgi = CGI.new
+ end
+
+ after :each do
+ ENV['REQUEST_METHOD'] = @old_request_method
+ end
+
+
+ it "returns a HTTP header based on the Hash's key/value pairs" do
+ header = @cgi.send(@method, "type" => "text/plain")
+ header.should == "Content-Type: text/plain\r\n\r\n"
+
+ header = @cgi.send(@method, "type" => "text/plain", "charset" => "UTF-8")
+ header.should == "Content-Type: text/plain; charset=UTF-8\r\n\r\n"
+
+ header = @cgi.send(@method, "nph" => true)
+ header.should include("HTTP/1.0 200 OK\r\n")
+ header.should include("Date: ")
+ header.should include("Server: ")
+ header.should include("Connection: close\r\n")
+ header.should include("Content-Type: text/html\r\n")
+
+ header = @cgi.send(@method, "status" => "OK")
+ header.should == "Status: 200 OK\r\nContent-Type: text/html\r\n\r\n"
+
+ header = @cgi.send(@method, "status" => "PARTIAL_CONTENT")
+ header.should == "Status: 206 Partial Content\r\nContent-Type: text/html\r\n\r\n"
+
+ header = @cgi.send(@method, "status" => "MULTIPLE_CHOICES")
+ header.should == "Status: 300 Multiple Choices\r\nContent-Type: text/html\r\n\r\n"
+
+ header = @cgi.send(@method, "server" => "Server Software used")
+ header.should == "Server: Server Software used\r\nContent-Type: text/html\r\n\r\n"
+
+ header = @cgi.send(@method, "connection" => "connection type")
+ header.should == "Connection: connection type\r\nContent-Type: text/html\r\n\r\n"
+
+ header = @cgi.send(@method, "length" => 103)
+ header.should == "Content-Type: text/html\r\nContent-Length: 103\r\n\r\n"
+
+ header = @cgi.send(@method, "language" => "ja")
+ header.should == "Content-Type: text/html\r\nContent-Language: ja\r\n\r\n"
+
+ header = @cgi.send(@method, "expires" => Time.at(0))
+ header.should == "Content-Type: text/html\r\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\r\n\r\n"
+
+ header = @cgi.send(@method, "cookie" => "my cookie's content")
+ header.should == "Content-Type: text/html\r\nSet-Cookie: my cookie's content\r\n\r\n"
+
+ header = @cgi.send(@method, "cookie" => ["multiple", "cookies"])
+ header.should == "Content-Type: text/html\r\nSet-Cookie: multiple\r\nSet-Cookie: cookies\r\n\r\n"
+ end
+
+ it "includes Cookies in the @output_cookies field" do
+ @cgi.instance_variable_set(:@output_cookies, ["multiple", "cookies"])
+ @cgi.send(@method, {}).should == "Content-Type: text/html\r\nSet-Cookie: multiple\r\nSet-Cookie: cookies\r\n\r\n"
+ end
+
+ it "returns a HTTP header specifying the Content-Type as text/html when passed an empty Hash" do
+ @cgi.send(@method, {}).should == "Content-Type: text/html\r\n\r\n"
+ end
+ end
+end
diff --git a/spec/ruby/library/cgi/unescapeElement_spec.rb b/spec/ruby/library/cgi/unescapeElement_spec.rb
new file mode 100644
index 0000000000..ae4d50b076
--- /dev/null
+++ b/spec/ruby/library/cgi/unescapeElement_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI.unescapeElement when passed String, elements, ..." do
+ it "unescapes only the tags of the passed elements in the passed String" do
+ res = CGI.unescapeElement("&lt;BR&gt;&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", "A", "IMG")
+ res.should == '&lt;BR&gt;<A HREF="url"></A>'
+
+ res = CGI.unescapeElement('&lt;BR&gt;&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;', ["A", "IMG"])
+ res.should == '&lt;BR&gt;<A HREF="url"></A>'
+ end
+
+ it "is case-insensitive" do
+ res = CGI.unescapeElement("&lt;BR&gt;&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", "a", "img")
+ res.should == '&lt;BR&gt;<A HREF="url"></A>'
+
+ res = CGI.unescapeElement("&lt;br&gt;&lt;a href=&quot;url&quot;&gt;&lt;/a&gt;", "A", "IMG")
+ res.should == '&lt;br&gt;<a href="url"></a>'
+ end
+end
diff --git a/spec/ruby/library/cgi/unescapeHTML_spec.rb b/spec/ruby/library/cgi/unescapeHTML_spec.rb
new file mode 100644
index 0000000000..84b30c6aa6
--- /dev/null
+++ b/spec/ruby/library/cgi/unescapeHTML_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI.unescapeHTML" do
+ it "unescapes '&amp; &lt; &gt; &quot;' to '& < > \"'" do
+ input = '&amp; &lt; &gt; &quot;'
+ expected = '& < > "'
+ CGI.unescapeHTML(input).should == expected
+ end
+
+ it "doesn't unescape other html entities such as '&copy;' or '&heart'" do
+ input = '&copy;&heart;'
+ expected = input
+ CGI.unescapeHTML(input).should == expected
+ end
+
+ it "unescapes '&#99' format entities" do
+ input = '&#34;&#38;&#39;&#60;&#62;'
+ expected = '"&\'<>'
+ CGI.unescapeHTML(input).should == expected
+ end
+
+ it "unescapes '&#x9999' format entities" do
+ input = '&#x0022;&#x0026;&#x0027;&#x003c;&#x003E;'
+ expected = '"&\'<>'
+ CGI.unescapeHTML(input).should == expected
+ end
+
+ it "leaves invalid formatted strings" do
+ input = '&&lt;&amp&gt;&quot&abcdefghijklmn'
+ expected = '&<&amp>&quot&abcdefghijklmn'
+ CGI.unescapeHTML(input).should == expected
+ end
+
+ it "leaves partial invalid &# at end of string" do
+ input = "fooooooo&#"
+ CGI.unescapeHTML(input).should == input
+ end
+
+ it "unescapes invalid encoding" do
+ input = "\xFF&"
+ CGI.unescapeHTML(input).should == input
+ end
+end
diff --git a/spec/ruby/library/cgi/unescape_spec.rb b/spec/ruby/library/cgi/unescape_spec.rb
new file mode 100644
index 0000000000..c593e24b4a
--- /dev/null
+++ b/spec/ruby/library/cgi/unescape_spec.rb
@@ -0,0 +1,15 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require 'cgi'
+
+describe "CGI.unescape" do
+ it "url-decodes the passed argument" do
+ input = "+%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E"
+ expected = " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
+ CGI.unescape(input).should == expected
+
+ input = 'https%3A%2F%2Fja.wikipedia.org%2Fwiki%2F%E3%83%AD%E3%83%A0%E3%82%B9%E3%82%AB%E3%83%BB%E3%83%91%E3%83%AD%E3%83%BB%E3%82%A6%E3%83%AB%E3%83%BB%E3%83%A9%E3%83%94%E3%83%A5%E3%82%BF'
+ expected = "https://ja.wikipedia.org/wiki/\343\203\255\343\203\240\343\202\271\343\202\253\343\203\273\343\203\221\343\203\255\343\203\273\343\202\246\343\203\253\343\203\273\343\203\251\343\203\224\343\203\245\343\202\277"
+ CGI.unescape(input).should == expected
+ end
+end
diff --git a/spec/ruby/library/coverage/fixtures/eval_code.rb b/spec/ruby/library/coverage/fixtures/eval_code.rb
new file mode 100644
index 0000000000..8ab82218f3
--- /dev/null
+++ b/spec/ruby/library/coverage/fixtures/eval_code.rb
@@ -0,0 +1,11 @@
+5 + 5
+
+module CoverageSpecs
+
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ attr_reader :ok
+ RUBY
+
+end
+
+4 + 4
diff --git a/spec/ruby/library/coverage/fixtures/second_class.rb b/spec/ruby/library/coverage/fixtures/second_class.rb
new file mode 100644
index 0000000000..0318ac26ff
--- /dev/null
+++ b/spec/ruby/library/coverage/fixtures/second_class.rb
@@ -0,0 +1,5 @@
+class SecondClass
+ def some_method
+ 42
+ end
+end
diff --git a/spec/ruby/library/coverage/fixtures/some_class.rb b/spec/ruby/library/coverage/fixtures/some_class.rb
new file mode 100644
index 0000000000..52629f0332
--- /dev/null
+++ b/spec/ruby/library/coverage/fixtures/some_class.rb
@@ -0,0 +1,16 @@
+
+#Class comment
+class SomeClass
+
+ # Method comment
+ def some_method
+
+ # Inline method comment
+ actual_code = true
+
+ end
+
+end
+
+# Trailing comment and extra blank line
+
diff --git a/spec/ruby/library/coverage/fixtures/start_coverage.rb b/spec/ruby/library/coverage/fixtures/start_coverage.rb
new file mode 100644
index 0000000000..8a0c56c50a
--- /dev/null
+++ b/spec/ruby/library/coverage/fixtures/start_coverage.rb
@@ -0,0 +1,3 @@
+2 + 2
+Coverage.start
+1 + 1
diff --git a/spec/ruby/library/coverage/peek_result_spec.rb b/spec/ruby/library/coverage/peek_result_spec.rb
new file mode 100644
index 0000000000..9d7c890faa
--- /dev/null
+++ b/spec/ruby/library/coverage/peek_result_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require 'coverage'
+
+describe 'Coverage.peek_result' do
+ before :all do
+ @class_file = fixture __FILE__, 'some_class.rb'
+ @second_class_file = fixture __FILE__, 'second_class.rb'
+ end
+
+ after :each do
+ $LOADED_FEATURES.delete(@class_file)
+ $LOADED_FEATURES.delete(@second_class_file)
+ end
+
+ it 'returns the result so far' do
+ Coverage.start
+ require @class_file.chomp('.rb')
+ result = Coverage.peek_result
+ Coverage.result
+
+ result.should == {
+ @class_file => [
+ nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil
+ ]
+ }
+ end
+
+ it 'immediate second call returns same result' do
+ Coverage.start
+ require @class_file.chomp('.rb')
+ result1 = Coverage.peek_result
+ result2 = Coverage.peek_result
+ Coverage.result
+
+ result2.should == result1
+ end
+
+ it 'second call after require returns accumulated result' do
+ Coverage.start
+ require @class_file.chomp('.rb')
+ Coverage.peek_result
+ require @second_class_file.chomp('.rb')
+ result = Coverage.peek_result
+ Coverage.result
+
+ result.should == {
+ @class_file => [
+ nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil
+ ],
+ @second_class_file => [
+ 1, 1, 0, nil, nil
+ ]
+ }
+ end
+
+ it 'call right before Coverage.result should give equal result' do
+ Coverage.start
+ require @class_file.chomp('.rb')
+ result1 = Coverage.peek_result
+ result2 = Coverage.result
+
+ result1.should == result2
+ end
+end
diff --git a/spec/ruby/library/coverage/result_spec.rb b/spec/ruby/library/coverage/result_spec.rb
new file mode 100644
index 0000000000..4bcce00cd7
--- /dev/null
+++ b/spec/ruby/library/coverage/result_spec.rb
@@ -0,0 +1,141 @@
+require_relative '../../spec_helper'
+require 'coverage'
+
+describe 'Coverage.result' do
+ before :all do
+ @class_file = fixture __FILE__, 'some_class.rb'
+ @config_file = fixture __FILE__, 'start_coverage.rb'
+ @eval_code_file = fixture __FILE__, 'eval_code.rb'
+ end
+
+ after :each do
+ $LOADED_FEATURES.delete(@class_file)
+ $LOADED_FEATURES.delete(@config_file)
+ $LOADED_FEATURES.delete(@eval_code_file)
+ end
+
+ it 'gives the covered files as a hash with arrays of count or nil' do
+ Coverage.start
+ require @class_file.chomp('.rb')
+ result = Coverage.result
+
+ result.should == {
+ @class_file => [
+ nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil
+ ]
+ }
+ end
+
+ it 'no requires/loads should give empty hash' do
+ Coverage.start
+ result = Coverage.result
+
+ result.should == {}
+ end
+
+ it 'second call should give exception' do
+ Coverage.start
+ require @class_file.chomp('.rb')
+ Coverage.result
+ -> {
+ Coverage.result
+ }.should raise_error(RuntimeError, 'coverage measurement is not enabled')
+ end
+
+ it 'second run should give same result' do
+ Coverage.start
+ load @class_file
+ result1 = Coverage.result
+
+ Coverage.start
+ load @class_file
+ result2 = Coverage.result
+
+ result2.should == result1
+ end
+
+ it 'second run without load/require should give empty hash' do
+ Coverage.start
+ require @class_file.chomp('.rb')
+ Coverage.result
+
+ Coverage.start
+ result = Coverage.result
+
+ result.should == {}
+ end
+
+ ruby_version_is ''...'3.1' do
+ it 'second Coverage.start does nothing' do
+ Coverage.start
+ require @config_file.chomp('.rb')
+ result = Coverage.result
+
+ result.should == { @config_file => [1, 1, 1] }
+ end
+ end
+
+ ruby_version_is '3.1' do
+ it 'second Coverage.start give exception' do
+ Coverage.start
+ -> {
+ require @config_file.chomp('.rb')
+ }.should raise_error(RuntimeError, 'coverage measurement is already setup')
+ ensure
+ Coverage.result
+ end
+ end
+
+ it 'does not include the file starting coverage since it is not tracked' do
+ require @config_file.chomp('.rb')
+ Coverage.result.should_not include(@config_file)
+ end
+
+ ruby_version_is '3.1'...'3.2' do
+ it 'returns the correct results when eval is used' do
+ Coverage.start
+ require @eval_code_file.chomp('.rb')
+ result = Coverage.result
+
+ result.should == {
+ @eval_code_file => [
+ 1, nil, 1, nil, 1, nil, nil, nil, nil, nil, 1
+ ]
+ }
+ end
+ end
+
+ ruby_version_is '3.2' do
+ it 'indicates support for different features' do
+ Coverage.supported?(:lines).should == true
+ end
+
+ it 'returns the correct results when eval coverage is enabled' do
+ Coverage.supported?(:eval).should == true
+
+ Coverage.start(lines: true, eval: true)
+ require @eval_code_file.chomp('.rb')
+ result = Coverage.result
+
+ result.should == {
+ @eval_code_file => {
+ lines: [1, nil, 1, nil, 1, 1, nil, nil, nil, nil, 1]
+ }
+ }
+ end
+
+ it 'returns the correct results when eval coverage is enabled' do
+ Coverage.supported?(:eval).should == true
+
+ Coverage.start(lines: true, eval: false)
+ require @eval_code_file.chomp('.rb')
+ result = Coverage.result
+
+ result.should == {
+ @eval_code_file => {
+ lines: [1, nil, 1, nil, 1, nil, nil, nil, nil, nil, 1]
+ }
+ }
+ end
+ end
+end
diff --git a/spec/ruby/library/coverage/running_spec.rb b/spec/ruby/library/coverage/running_spec.rb
new file mode 100644
index 0000000000..745270164f
--- /dev/null
+++ b/spec/ruby/library/coverage/running_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require 'coverage'
+
+describe 'Coverage.running?' do
+ it "returns false if coverage is not started" do
+ Coverage.running?.should == false
+ end
+
+ it "returns true if coverage is started" do
+ Coverage.start
+ Coverage.running?.should == true
+ Coverage.result
+ end
+
+ it "returns false if coverage was started and stopped" do
+ Coverage.start
+ Coverage.result
+ Coverage.running?.should == false
+ end
+end \ No newline at end of file
diff --git a/spec/ruby/library/coverage/start_spec.rb b/spec/ruby/library/coverage/start_spec.rb
new file mode 100644
index 0000000000..a993abbf4e
--- /dev/null
+++ b/spec/ruby/library/coverage/start_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require 'coverage'
+
+describe 'Coverage.start' do
+ ruby_version_is '3.2' do
+ it "can measure coverage within eval" do
+ Coverage.start(lines: true, eval: true)
+ eval("Object.new\n"*3, binding, "test.rb", 1)
+ Coverage.result["test.rb"].should == {lines: [1, 1, 1]}
+ end
+ end
+end
diff --git a/spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb b/spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb
new file mode 100644
index 0000000000..599e640624
--- /dev/null
+++ b/spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::BasicWriter#close_on_terminate" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/basicwriter/initialize_spec.rb b/spec/ruby/library/csv/basicwriter/initialize_spec.rb
new file mode 100644
index 0000000000..2c13c93f2e
--- /dev/null
+++ b/spec/ruby/library/csv/basicwriter/initialize_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::BasicWriter#initialize" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/basicwriter/terminate_spec.rb b/spec/ruby/library/csv/basicwriter/terminate_spec.rb
new file mode 100644
index 0000000000..8c53db3f0b
--- /dev/null
+++ b/spec/ruby/library/csv/basicwriter/terminate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::BasicWriter#terminate" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/cell/data_spec.rb b/spec/ruby/library/csv/cell/data_spec.rb
new file mode 100644
index 0000000000..aa034b0b62
--- /dev/null
+++ b/spec/ruby/library/csv/cell/data_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::Cell#data" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/cell/initialize_spec.rb b/spec/ruby/library/csv/cell/initialize_spec.rb
new file mode 100644
index 0000000000..c9e506676c
--- /dev/null
+++ b/spec/ruby/library/csv/cell/initialize_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::Cell#initialize" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/fixtures/one_line.csv b/spec/ruby/library/csv/fixtures/one_line.csv
new file mode 100644
index 0000000000..d72f2010a8
--- /dev/null
+++ b/spec/ruby/library/csv/fixtures/one_line.csv
@@ -0,0 +1 @@
+1,2
diff --git a/spec/ruby/library/csv/foreach_spec.rb b/spec/ruby/library/csv/foreach_spec.rb
new file mode 100644
index 0000000000..36b3cd152f
--- /dev/null
+++ b/spec/ruby/library/csv/foreach_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'csv'
+
+describe "CSV.foreach" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/generate_line_spec.rb b/spec/ruby/library/csv/generate_line_spec.rb
new file mode 100644
index 0000000000..656365b109
--- /dev/null
+++ b/spec/ruby/library/csv/generate_line_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require 'csv'
+
+describe "CSV.generate_line" do
+
+ it "generates an empty string" do
+ result = CSV.generate_line([])
+ result.should == "\n"
+ end
+
+ it "generates the string 'foo,bar'" do
+ result = CSV.generate_line(["foo", "bar"])
+ result.should == "foo,bar\n"
+ end
+
+ it "generates the string 'foo;bar'" do
+ result = CSV.generate_line(["foo", "bar"], col_sep: ?;)
+ result.should == "foo;bar\n"
+ end
+
+ it "generates the string 'foo,,bar'" do
+ result = CSV.generate_line(["foo", nil, "bar"])
+ result.should == "foo,,bar\n"
+ end
+
+ it "generates the string 'foo;;bar'" do
+ result = CSV.generate_line(["foo", nil, "bar"], col_sep: ?;)
+ result.should == "foo;;bar\n"
+ end
+end
diff --git a/spec/ruby/library/csv/generate_row_spec.rb b/spec/ruby/library/csv/generate_row_spec.rb
new file mode 100644
index 0000000000..79dfc34000
--- /dev/null
+++ b/spec/ruby/library/csv/generate_row_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'csv'
+
+describe "CSV.generate_row" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/generate_spec.rb b/spec/ruby/library/csv/generate_spec.rb
new file mode 100644
index 0000000000..0a1e3d9604
--- /dev/null
+++ b/spec/ruby/library/csv/generate_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require 'csv'
+require 'tempfile'
+
+describe "CSV.generate" do
+
+ it "returns CSV string" do
+ csv_str = CSV.generate do |csv|
+ csv.add_row [1, 2, 3]
+ csv << [4, 5, 6]
+ end
+ csv_str.should == "1,2,3\n4,5,6\n"
+ end
+
+ it "accepts a col separator" do
+ csv_str = CSV.generate(col_sep: ";") do |csv|
+ csv.add_row [1, 2, 3]
+ csv << [4, 5, 6]
+ end
+ csv_str.should == "1;2;3\n4;5;6\n"
+ end
+
+ it "appends and returns the argument itself" do
+ str = ""
+ csv_str = CSV.generate(str) do |csv|
+ csv.add_row [1, 2, 3]
+ csv << [4, 5, 6]
+ end
+ csv_str.should equal str
+ str.should == "1,2,3\n4,5,6\n"
+ end
+end
diff --git a/spec/ruby/library/csv/iobuf/close_spec.rb b/spec/ruby/library/csv/iobuf/close_spec.rb
new file mode 100644
index 0000000000..e2fda86080
--- /dev/null
+++ b/spec/ruby/library/csv/iobuf/close_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::IOBuf#close" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/iobuf/initialize_spec.rb b/spec/ruby/library/csv/iobuf/initialize_spec.rb
new file mode 100644
index 0000000000..f08e82548a
--- /dev/null
+++ b/spec/ruby/library/csv/iobuf/initialize_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::IOBuf#initialize" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/iobuf/read_spec.rb b/spec/ruby/library/csv/iobuf/read_spec.rb
new file mode 100644
index 0000000000..b45334ee2a
--- /dev/null
+++ b/spec/ruby/library/csv/iobuf/read_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::IOBuf#read" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/iobuf/terminate_spec.rb b/spec/ruby/library/csv/iobuf/terminate_spec.rb
new file mode 100644
index 0000000000..69289e960c
--- /dev/null
+++ b/spec/ruby/library/csv/iobuf/terminate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::IOBuf#terminate" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb b/spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb
new file mode 100644
index 0000000000..4887ade1a1
--- /dev/null
+++ b/spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::IOReader#close_on_terminate" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/ioreader/get_row_spec.rb b/spec/ruby/library/csv/ioreader/get_row_spec.rb
new file mode 100644
index 0000000000..5fd2178b1b
--- /dev/null
+++ b/spec/ruby/library/csv/ioreader/get_row_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::IOReader#get_row" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/ioreader/initialize_spec.rb b/spec/ruby/library/csv/ioreader/initialize_spec.rb
new file mode 100644
index 0000000000..4e8c0964df
--- /dev/null
+++ b/spec/ruby/library/csv/ioreader/initialize_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::IOReader#initialize" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/ioreader/terminate_spec.rb b/spec/ruby/library/csv/ioreader/terminate_spec.rb
new file mode 100644
index 0000000000..676cd03a0f
--- /dev/null
+++ b/spec/ruby/library/csv/ioreader/terminate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::IOReader#terminate" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/liberal_parsing_spec.rb b/spec/ruby/library/csv/liberal_parsing_spec.rb
new file mode 100644
index 0000000000..9878658027
--- /dev/null
+++ b/spec/ruby/library/csv/liberal_parsing_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require 'csv'
+
+describe "CSV#liberal_parsing?" do
+ it "returns true if illegal input is handled" do
+ csv = CSV.new("", liberal_parsing: true)
+ csv.should.liberal_parsing?
+ end
+
+ it "returns false if illegal input is not handled" do
+ csv = CSV.new("", liberal_parsing: false)
+ csv.should_not.liberal_parsing?
+ end
+
+ it "returns false by default" do
+ csv = CSV.new("")
+ csv.should_not.liberal_parsing?
+ end
+end
diff --git a/spec/ruby/library/csv/open_spec.rb b/spec/ruby/library/csv/open_spec.rb
new file mode 100644
index 0000000000..2d310cda3e
--- /dev/null
+++ b/spec/ruby/library/csv/open_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'csv'
+
+describe "CSV.open" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/parse_spec.rb b/spec/ruby/library/csv/parse_spec.rb
new file mode 100644
index 0000000000..ef5d4ea3ca
--- /dev/null
+++ b/spec/ruby/library/csv/parse_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../../spec_helper'
+require 'csv'
+
+describe "CSV.parse" do
+
+ it "parses '' into []" do
+ result = CSV.parse ''
+ result.should be_kind_of(Array)
+ result.should == []
+ end
+
+ it "parses '\n' into [[]]" do
+ result = CSV.parse "\n"
+ result.should == [[]]
+ end
+
+ it "parses 'foo' into [['foo']]" do
+ result = CSV.parse 'foo'
+ result.should == [['foo']]
+ end
+
+ it "parses 'foo,bar,baz' into [['foo','bar','baz']]" do
+ result = CSV.parse 'foo,bar,baz'
+ result.should == [['foo','bar','baz']]
+ end
+
+ it "parses 'foo,baz' into [[foo,nil,baz]]" do
+ result = CSV.parse 'foo,,baz'
+ result.should == [['foo',nil,'baz']]
+ end
+
+ it "parses '\nfoo' into [[],['foo']]" do
+ result = CSV.parse "\nfoo"
+ result.should == [[],['foo']]
+ end
+
+ it "parses 'foo\n' into [['foo']]" do
+ result = CSV.parse "foo\n"
+ result.should == [['foo']]
+ end
+
+ it "parses 'foo\nbar' into [['foo'],['bar']]" do
+ result = CSV.parse "foo\nbar"
+ result.should == [['foo'],['bar']]
+ end
+
+ it "parses 'foo,bar\nbaz,quz' into [['foo','bar'],['baz','quz']]" do
+ result = CSV.parse "foo,bar\nbaz,quz"
+ result.should == [['foo','bar'],['baz','quz']]
+ end
+
+ it "parses 'foo,bar'\nbaz' into [['foo','bar'],['baz']]" do
+ result = CSV.parse "foo,bar\nbaz"
+ result.should == [['foo','bar'],['baz']]
+ end
+
+ it "parses 'foo\nbar,baz' into [['foo'],['bar','baz']]" do
+ result = CSV.parse "foo\nbar,baz"
+ result.should == [['foo'],['bar','baz']]
+ end
+
+ it "parses '\n\nbar' into [[],[],'bar']]" do
+ result = CSV.parse "\n\nbar"
+ result.should == [[],[],['bar']]
+ end
+
+ it "parses 'foo' into [['foo']] with a separator of ;" do
+ result = CSV.parse "foo", col_sep: ?;
+ result.should == [['foo']]
+ end
+
+ it "parses 'foo;bar' into [['foo','bar']] with a separator of ;" do
+ result = CSV.parse "foo;bar", col_sep: ?;
+ result.should == [['foo','bar']]
+ end
+
+ it "parses 'foo;bar\nbaz;quz' into [['foo','bar'],['baz','quz']] with a separator of ;" do
+ result = CSV.parse "foo;bar\nbaz;quz", col_sep: ?;
+ result.should == [['foo','bar'],['baz','quz']]
+ end
+
+ it "raises CSV::MalformedCSVError exception if input is illegal" do
+ -> {
+ CSV.parse('"quoted" field')
+ }.should raise_error(CSV::MalformedCSVError)
+ end
+
+ it "handles illegal input with the liberal_parsing option" do
+ illegal_input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson'
+ result = CSV.parse(illegal_input, liberal_parsing: true)
+ result.should == [["Johnson, Dwayne", 'Dwayne "The Rock" Johnson']]
+ end
+end
diff --git a/spec/ruby/library/csv/read_spec.rb b/spec/ruby/library/csv/read_spec.rb
new file mode 100644
index 0000000000..2e6bb65d56
--- /dev/null
+++ b/spec/ruby/library/csv/read_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'csv'
+
+describe "CSV.read" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/readlines_spec.rb b/spec/ruby/library/csv/readlines_spec.rb
new file mode 100644
index 0000000000..14dea34381
--- /dev/null
+++ b/spec/ruby/library/csv/readlines_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require 'csv'
+
+describe "CSV.readlines" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "CSV#readlines" do
+ it "returns an Array of Array containing each element in a one-line CSV file" do
+ file = CSV.new "a, b, c"
+ file.readlines.should == [["a", " b", " c"]]
+ end
+
+ it "returns an Array of Arrays containing each element in a multi-line CSV file" do
+ file = CSV.new "a, b, c\nd, e, f"
+ file.readlines.should == [["a", " b", " c"], ["d", " e", " f"]]
+ end
+
+ it "returns nil for a missing value" do
+ file = CSV.new "a,, b, c"
+ file.readlines.should == [["a", nil, " b", " c"]]
+ end
+
+ it "raises CSV::MalformedCSVError exception if input is illegal" do
+ csv = CSV.new('"quoted" field')
+ -> { csv.readlines }.should raise_error(CSV::MalformedCSVError)
+ end
+
+ it "handles illegal input with the liberal_parsing option" do
+ illegal_input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson'
+ csv = CSV.new(illegal_input, liberal_parsing: true)
+ result = csv.readlines
+ result.should == [["Johnson, Dwayne", 'Dwayne "The Rock" Johnson']]
+ end
+end
diff --git a/spec/ruby/library/csv/streambuf/add_buf_spec.rb b/spec/ruby/library/csv/streambuf/add_buf_spec.rb
new file mode 100644
index 0000000000..58c530c500
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/add_buf_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#add_buf" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/streambuf/buf_size_spec.rb b/spec/ruby/library/csv/streambuf/buf_size_spec.rb
new file mode 100644
index 0000000000..1793c8b65e
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/buf_size_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#buf_size" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/streambuf/drop_spec.rb b/spec/ruby/library/csv/streambuf/drop_spec.rb
new file mode 100644
index 0000000000..448f0a2196
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/drop_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#drop" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/streambuf/element_reference_spec.rb b/spec/ruby/library/csv/streambuf/element_reference_spec.rb
new file mode 100644
index 0000000000..5a75901830
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/element_reference_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#[]" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/streambuf/get_spec.rb b/spec/ruby/library/csv/streambuf/get_spec.rb
new file mode 100644
index 0000000000..2255e55e91
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/get_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#get" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb b/spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb
new file mode 100644
index 0000000000..563b8b2d4a
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#idx_is_eos?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/streambuf/initialize_spec.rb b/spec/ruby/library/csv/streambuf/initialize_spec.rb
new file mode 100644
index 0000000000..1273c98094
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/initialize_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#initialize" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/streambuf/is_eos_spec.rb b/spec/ruby/library/csv/streambuf/is_eos_spec.rb
new file mode 100644
index 0000000000..a0a3c1e0b0
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/is_eos_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#is_eos?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/streambuf/read_spec.rb b/spec/ruby/library/csv/streambuf/read_spec.rb
new file mode 100644
index 0000000000..cf98c53409
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/read_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#read" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/streambuf/rel_buf_spec.rb b/spec/ruby/library/csv/streambuf/rel_buf_spec.rb
new file mode 100644
index 0000000000..548e347200
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/rel_buf_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#rel_buf" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/streambuf/terminate_spec.rb b/spec/ruby/library/csv/streambuf/terminate_spec.rb
new file mode 100644
index 0000000000..247b33184a
--- /dev/null
+++ b/spec/ruby/library/csv/streambuf/terminate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StreamBuf#terminate" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/stringreader/get_row_spec.rb b/spec/ruby/library/csv/stringreader/get_row_spec.rb
new file mode 100644
index 0000000000..5cc3447061
--- /dev/null
+++ b/spec/ruby/library/csv/stringreader/get_row_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StringReader#get_row" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/stringreader/initialize_spec.rb b/spec/ruby/library/csv/stringreader/initialize_spec.rb
new file mode 100644
index 0000000000..4e3634847e
--- /dev/null
+++ b/spec/ruby/library/csv/stringreader/initialize_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::StringReader#initialize" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/writer/add_row_spec.rb b/spec/ruby/library/csv/writer/add_row_spec.rb
new file mode 100644
index 0000000000..2f074b45db
--- /dev/null
+++ b/spec/ruby/library/csv/writer/add_row_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::Writer#add_row" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/writer/append_spec.rb b/spec/ruby/library/csv/writer/append_spec.rb
new file mode 100644
index 0000000000..4e1f6728c2
--- /dev/null
+++ b/spec/ruby/library/csv/writer/append_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::Writer#<<" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/writer/close_spec.rb b/spec/ruby/library/csv/writer/close_spec.rb
new file mode 100644
index 0000000000..1a87094bb7
--- /dev/null
+++ b/spec/ruby/library/csv/writer/close_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::Writer#close" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/writer/create_spec.rb b/spec/ruby/library/csv/writer/create_spec.rb
new file mode 100644
index 0000000000..a4514d5578
--- /dev/null
+++ b/spec/ruby/library/csv/writer/create_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::Writer.create" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/writer/generate_spec.rb b/spec/ruby/library/csv/writer/generate_spec.rb
new file mode 100644
index 0000000000..6ea9161777
--- /dev/null
+++ b/spec/ruby/library/csv/writer/generate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::Writer.generate" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/writer/initialize_spec.rb b/spec/ruby/library/csv/writer/initialize_spec.rb
new file mode 100644
index 0000000000..6bba8f8d0a
--- /dev/null
+++ b/spec/ruby/library/csv/writer/initialize_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::Writer#initialize" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/csv/writer/terminate_spec.rb b/spec/ruby/library/csv/writer/terminate_spec.rb
new file mode 100644
index 0000000000..77136dd018
--- /dev/null
+++ b/spec/ruby/library/csv/writer/terminate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'csv'
+
+describe "CSV::Writer#terminate" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/accessor_spec.rb b/spec/ruby/library/date/accessor_spec.rb
new file mode 100644
index 0000000000..68a2d9f3de
--- /dev/null
+++ b/spec/ruby/library/date/accessor_spec.rb
@@ -0,0 +1,91 @@
+require 'date'
+require_relative '../../spec_helper'
+
+describe "Date#ajd" do
+ it "determines the Astronomical Julian day" do
+ Date.civil(2007, 1, 17).ajd.should == 4908235.to_r / 2
+ end
+end
+
+describe "Date#amjd" do
+ it "determines the Astronomical Modified Julian day" do
+ Date.civil(2007, 1, 17).amjd.should == 54117
+ end
+end
+
+describe "Date#day_fraction" do
+ it "determines the day fraction" do
+ Date.civil(2007, 1, 17).day_fraction.should == 0
+ end
+end
+
+describe "Date#mjd" do
+ it "determines the Modified Julian day" do
+ Date.civil(2007, 1, 17).mjd.should == 54117
+ end
+end
+
+describe "Date#ld" do
+ it "determines the Modified Julian day" do
+ Date.civil(2007, 1, 17).ld.should == 154958
+ end
+end
+
+describe "Date#year" do
+ it "determines the year" do
+ Date.civil(2007, 1, 17).year.should == 2007
+ end
+end
+
+describe "Date#yday" do
+ it "determines the year" do
+ Date.civil(2007, 1, 17).yday.should == 17
+ Date.civil(2008, 10, 28).yday.should == 302
+ end
+end
+
+describe "Date#mon" do
+ it "determines the month" do
+ Date.civil(2007, 1, 17).mon.should == 1
+ Date.civil(2008, 10, 28).mon.should == 10
+ end
+end
+
+describe "Date#mday" do
+ it "determines the day of the month" do
+ Date.civil(2007, 1, 17).mday.should == 17
+ Date.civil(2008, 10, 28).mday.should == 28
+ end
+end
+
+describe "Date#wday" do
+ it "determines the week day" do
+ Date.civil(2007, 1, 17).wday.should == 3
+ Date.civil(2008, 10, 26).wday.should == 0
+ end
+end
+
+describe "Date#cwyear" do
+ it "determines the commercial year" do
+ Date.civil(2007, 1, 17).cwyear.should == 2007
+ Date.civil(2008, 10, 28).cwyear.should == 2008
+ Date.civil(2007, 12, 31).cwyear.should == 2008
+ Date.civil(2010, 1, 1).cwyear.should == 2009
+ end
+end
+
+describe "Date#cweek" do
+ it "determines the commercial week" do
+ Date.civil(2007, 1, 17).cweek.should == 3
+ Date.civil(2008, 10, 28).cweek.should == 44
+ Date.civil(2007, 12, 31).cweek.should == 1
+ Date.civil(2010, 1, 1).cweek.should == 53
+ end
+end
+
+describe "Date#cwday" do
+ it "determines the commercial week day" do
+ Date.civil(2007, 1, 17).cwday.should == 3
+ Date.civil(2008, 10, 26).cwday.should == 7
+ end
+end
diff --git a/spec/ruby/library/date/add_month_spec.rb b/spec/ruby/library/date/add_month_spec.rb
new file mode 100644
index 0000000000..40833f6487
--- /dev/null
+++ b/spec/ruby/library/date/add_month_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#>>" do
+ it "adds the number of months to a Date" do
+ d = Date.civil(2007,2,27) >> 10
+ d.should == Date.civil(2007, 12, 27)
+ end
+
+ it "sets the day to the last day of a month if the day doesn't exist" do
+ d = Date.civil(2008,3,31) >> 1
+ d.should == Date.civil(2008, 4, 30)
+ end
+
+ it "returns the day of the reform if date falls within calendar reform" do
+ calendar_reform_italy = Date.new(1582, 10, 4)
+ d1 = Date.new(1582, 9, 9) >> 1
+ d2 = Date.new(1582, 9, 10) >> 1
+ d1.should == calendar_reform_italy
+ d2.should == calendar_reform_italy
+ end
+
+ it "raise a TypeError when passed a Symbol" do
+ -> { Date.civil(2007,2,27) >> :hello }.should raise_error(TypeError)
+ end
+
+ it "raise a TypeError when passed a String" do
+ -> { Date.civil(2007,2,27) >> "hello" }.should raise_error(TypeError)
+ end
+
+ it "raise a TypeError when passed a Date" do
+ -> { Date.civil(2007,2,27) >> Date.new }.should raise_error(TypeError)
+ end
+
+ it "raise a TypeError when passed an Object" do
+ -> { Date.civil(2007,2,27) >> Object.new }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/date/add_spec.rb b/spec/ruby/library/date/add_spec.rb
new file mode 100644
index 0000000000..2b9cc62023
--- /dev/null
+++ b/spec/ruby/library/date/add_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#+" do
+ it "adds the number of days to a Date" do
+ d = Date.civil(2007,2,27) + 10
+ d.should == Date.civil(2007, 3, 9)
+ end
+
+ it "adds a negative number of days to a Date" do
+ d = Date.civil(2007,2,27).+(-10)
+ d.should == Date.civil(2007, 2, 17)
+ end
+
+ it "raises a TypeError when passed a Symbol" do
+ -> { Date.civil(2007,2,27) + :hello }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { Date.civil(2007,2,27) + "hello" }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed a Date" do
+ -> { Date.civil(2007,2,27) + Date.new }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed an Object" do
+ -> { Date.civil(2007,2,27) + Object.new }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/date/ajd_spec.rb b/spec/ruby/library/date/ajd_spec.rb
new file mode 100644
index 0000000000..10f1302354
--- /dev/null
+++ b/spec/ruby/library/date/ajd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#ajd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/ajd_to_amjd_spec.rb b/spec/ruby/library/date/ajd_to_amjd_spec.rb
new file mode 100644
index 0000000000..948f4c2236
--- /dev/null
+++ b/spec/ruby/library/date/ajd_to_amjd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.ajd_to_amjd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/ajd_to_jd_spec.rb b/spec/ruby/library/date/ajd_to_jd_spec.rb
new file mode 100644
index 0000000000..e55ce9f4f2
--- /dev/null
+++ b/spec/ruby/library/date/ajd_to_jd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.ajd_to_jd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/amjd_spec.rb b/spec/ruby/library/date/amjd_spec.rb
new file mode 100644
index 0000000000..ad7bc14965
--- /dev/null
+++ b/spec/ruby/library/date/amjd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#amjd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/amjd_to_ajd_spec.rb b/spec/ruby/library/date/amjd_to_ajd_spec.rb
new file mode 100644
index 0000000000..66c26a16a8
--- /dev/null
+++ b/spec/ruby/library/date/amjd_to_ajd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.amjd_to_ajd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/append_spec.rb b/spec/ruby/library/date/append_spec.rb
new file mode 100644
index 0000000000..4305a00321
--- /dev/null
+++ b/spec/ruby/library/date/append_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#<<" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/asctime_spec.rb b/spec/ruby/library/date/asctime_spec.rb
new file mode 100644
index 0000000000..67d158cc01
--- /dev/null
+++ b/spec/ruby/library/date/asctime_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#asctime" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/boat_spec.rb b/spec/ruby/library/date/boat_spec.rb
new file mode 100644
index 0000000000..e4f1b797fc
--- /dev/null
+++ b/spec/ruby/library/date/boat_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#<=>" do
+ it "returns 0 when two dates are equal" do
+ (Date.civil(2000, 04, 06) <=> Date.civil(2000, 04, 06)).should == 0
+ end
+
+ it "returns -1 when self is less than another date" do
+ (Date.civil(2000, 04, 05) <=> Date.civil(2000, 04, 06)).should == -1
+ end
+
+ it "returns -1 when self is less than a Numeric" do
+ (Date.civil(2000, 04, 05) <=> Date.civil(2000, 04, 06).jd).should == -1
+ end
+
+ it "returns 1 when self is greater than another date" do
+ (Date.civil(2001, 04, 05) <=> Date.civil(2000, 04, 06)).should == 1
+ end
+
+ it "returns 1 when self is greater than a Numeric" do
+ (Date.civil(2001, 04, 05) <=> Date.civil(2000, 04, 06).jd).should == 1
+ end
+end
diff --git a/spec/ruby/library/date/case_compare_spec.rb b/spec/ruby/library/date/case_compare_spec.rb
new file mode 100644
index 0000000000..87d522ee6a
--- /dev/null
+++ b/spec/ruby/library/date/case_compare_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#===" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/civil_spec.rb b/spec/ruby/library/date/civil_spec.rb
new file mode 100644
index 0000000000..1c780fce56
--- /dev/null
+++ b/spec/ruby/library/date/civil_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/civil'
+require 'date'
+
+describe "Date.civil" do
+ it_behaves_like :date_civil, :civil
+end
diff --git a/spec/ruby/library/date/commercial_spec.rb b/spec/ruby/library/date/commercial_spec.rb
new file mode 100644
index 0000000000..d7fc34d74a
--- /dev/null
+++ b/spec/ruby/library/date/commercial_spec.rb
@@ -0,0 +1,17 @@
+require 'date'
+require_relative '../../spec_helper'
+require_relative 'shared/commercial'
+
+describe "Date#commercial" do
+
+ it_behaves_like :date_commercial, :commercial
+
+end
+
+# reference:
+# October 1582 (the Gregorian calendar, Civil Date)
+# S M Tu W Th F S
+# 1 2 3 4 15 16
+# 17 18 19 20 21 22 23
+# 24 25 26 27 28 29 30
+# 31
diff --git a/spec/ruby/library/date/commercial_to_jd_spec.rb b/spec/ruby/library/date/commercial_to_jd_spec.rb
new file mode 100644
index 0000000000..9b77f1229f
--- /dev/null
+++ b/spec/ruby/library/date/commercial_to_jd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.commercial_to_jd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/comparison_spec.rb b/spec/ruby/library/date/comparison_spec.rb
new file mode 100644
index 0000000000..1a94b9dcd2
--- /dev/null
+++ b/spec/ruby/library/date/comparison_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#<=>" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/constants_spec.rb b/spec/ruby/library/date/constants_spec.rb
new file mode 100644
index 0000000000..1d18dd1b0c
--- /dev/null
+++ b/spec/ruby/library/date/constants_spec.rb
@@ -0,0 +1,48 @@
+require 'date'
+require_relative '../../spec_helper'
+
+describe "Date constants" do
+
+ it "defines JULIAN" do
+ (Date::JULIAN <=> Date::Infinity.new).should == 0
+ end
+
+ it "defines GREGORIAN" do
+ (Date::GREGORIAN <=> -Date::Infinity.new).should == 0
+ end
+
+ it "defines ITALY" do
+ Date::ITALY.should == 2299161 # 1582-10-15
+ end
+
+ it "defines ENGLAND" do
+ Date::ENGLAND.should == 2361222 # 1752-09-14
+ end
+
+ it "defines MONTHNAMES" do
+ Date::MONTHNAMES.should == [nil] + %w(January February March April May June July
+ August September October November December)
+ end
+
+ it "defines DAYNAMES" do
+ Date::DAYNAMES.should == %w(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
+ end
+
+ it "defines ABBR_MONTHNAMES" do
+ Date::ABBR_DAYNAMES.should == %w(Sun Mon Tue Wed Thu Fri Sat)
+ end
+
+ it "freezes MONTHNAMES, DAYNAMES, ABBR_MONTHNAMES, ABBR_DAYSNAMES" do
+ [Date::MONTHNAMES, Date::DAYNAMES, Date::ABBR_MONTHNAMES, Date::ABBR_DAYNAMES].each do |ary|
+ -> {
+ ary << "Unknown"
+ }.should raise_error(FrozenError, /frozen/)
+ ary.compact.each do |name|
+ -> {
+ name << "modified"
+ }.should raise_error(FrozenError, /frozen/)
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/library/date/conversions_spec.rb b/spec/ruby/library/date/conversions_spec.rb
new file mode 100644
index 0000000000..a9a320b0fc
--- /dev/null
+++ b/spec/ruby/library/date/conversions_spec.rb
@@ -0,0 +1,43 @@
+require 'date'
+require_relative '../../spec_helper'
+
+
+describe "Date#new_start" do
+ it "converts a date object into another with a new calendar reform" do
+ Date.civil(1582, 10, 14, Date::ENGLAND).new_start.should == Date.civil(1582, 10, 24)
+ Date.civil(1582, 10, 4, Date::ENGLAND).new_start.should == Date.civil(1582, 10, 4)
+ Date.civil(1582, 10, 15).new_start(Date::ENGLAND).should == Date.civil(1582, 10, 5, Date::ENGLAND)
+ Date.civil(1752, 9, 14).new_start(Date::ENGLAND).should == Date.civil(1752, 9, 14, Date::ENGLAND)
+ Date.civil(1752, 9, 13).new_start(Date::ENGLAND).should == Date.civil(1752, 9, 2, Date::ENGLAND)
+ end
+end
+
+describe "Date#italy" do
+ it "converts a date object into another with the Italian calendar reform" do
+ Date.civil(1582, 10, 14, Date::ENGLAND).italy.should == Date.civil(1582, 10, 24)
+ Date.civil(1582, 10, 4, Date::ENGLAND).italy.should == Date.civil(1582, 10, 4)
+ end
+end
+
+describe "Date#england" do
+ it "converts a date object into another with the English calendar reform" do
+ Date.civil(1582, 10, 15).england.should == Date.civil(1582, 10, 5, Date::ENGLAND)
+ Date.civil(1752, 9, 14).england.should == Date.civil(1752, 9, 14, Date::ENGLAND)
+ Date.civil(1752, 9, 13).england.should == Date.civil(1752, 9, 2, Date::ENGLAND)
+ end
+end
+
+describe "Date#julian" do
+ it "converts a date object into another with the Julian calendar" do
+ Date.civil(1582, 10, 15).julian.should == Date.civil(1582, 10, 5, Date::JULIAN)
+ Date.civil(1752, 9, 14).julian.should == Date.civil(1752, 9, 3, Date::JULIAN)
+ Date.civil(1752, 9, 13).julian.should == Date.civil(1752, 9, 2, Date::JULIAN)
+ end
+end
+
+describe "Date#gregorian" do
+ it "converts a date object into another with the Gregorian calendar" do
+ Date.civil(1582, 10, 4).gregorian.should == Date.civil(1582, 10, 14, Date::GREGORIAN)
+ Date.civil(1752, 9, 14).gregorian.should == Date.civil(1752, 9, 14, Date::GREGORIAN)
+ end
+end
diff --git a/spec/ruby/library/date/ctime_spec.rb b/spec/ruby/library/date/ctime_spec.rb
new file mode 100644
index 0000000000..3faa7c6380
--- /dev/null
+++ b/spec/ruby/library/date/ctime_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#ctime" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/cwday_spec.rb b/spec/ruby/library/date/cwday_spec.rb
new file mode 100644
index 0000000000..c5a39f277f
--- /dev/null
+++ b/spec/ruby/library/date/cwday_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#cwday" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/cweek_spec.rb b/spec/ruby/library/date/cweek_spec.rb
new file mode 100644
index 0000000000..6f7aab3922
--- /dev/null
+++ b/spec/ruby/library/date/cweek_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#cweek" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/cwyear_spec.rb b/spec/ruby/library/date/cwyear_spec.rb
new file mode 100644
index 0000000000..a85ee29920
--- /dev/null
+++ b/spec/ruby/library/date/cwyear_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#cwyear" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/day_fraction_spec.rb b/spec/ruby/library/date/day_fraction_spec.rb
new file mode 100644
index 0000000000..12b873773f
--- /dev/null
+++ b/spec/ruby/library/date/day_fraction_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#day_fraction" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/day_fraction_to_time_spec.rb b/spec/ruby/library/date/day_fraction_to_time_spec.rb
new file mode 100644
index 0000000000..d4741d65ec
--- /dev/null
+++ b/spec/ruby/library/date/day_fraction_to_time_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.day_fraction_to_time" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/day_spec.rb b/spec/ruby/library/date/day_spec.rb
new file mode 100644
index 0000000000..bc727c4717
--- /dev/null
+++ b/spec/ruby/library/date/day_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#day" do
+ it "returns the day" do
+ d = Date.new(2000, 7, 1).day
+ d.should == 1
+ end
+end
diff --git a/spec/ruby/library/date/downto_spec.rb b/spec/ruby/library/date/downto_spec.rb
new file mode 100644
index 0000000000..84c641ee14
--- /dev/null
+++ b/spec/ruby/library/date/downto_spec.rb
@@ -0,0 +1,18 @@
+require 'date'
+require_relative '../../spec_helper'
+
+describe "Date#downto" do
+
+ it "creates earlier dates when passed a negative step" do
+ ds = Date.civil(2000, 4, 14)
+ de = Date.civil(2000, 3, 29)
+ count = 0
+ ds.step(de, -1) do |d|
+ d.should <= ds
+ d.should >= de
+ count += 1
+ end
+ count.should == 17
+ end
+
+end
diff --git a/spec/ruby/library/date/england_spec.rb b/spec/ruby/library/date/england_spec.rb
new file mode 100644
index 0000000000..2e30530c5a
--- /dev/null
+++ b/spec/ruby/library/date/england_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#england" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/eql_spec.rb b/spec/ruby/library/date/eql_spec.rb
new file mode 100644
index 0000000000..a1819cae3a
--- /dev/null
+++ b/spec/ruby/library/date/eql_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#eql?" do
+ it "returns true if self is equal to another date" do
+ Date.civil(2007, 10, 11).eql?(Date.civil(2007, 10, 11)).should be_true
+ end
+
+ it "returns false if self is not equal to another date" do
+ Date.civil(2007, 10, 11).eql?(Date.civil(2007, 10, 12)).should be_false
+ end
+end
diff --git a/spec/ruby/library/date/format/bag/method_missing_spec.rb b/spec/ruby/library/date/format/bag/method_missing_spec.rb
new file mode 100644
index 0000000000..03e4fbcd30
--- /dev/null
+++ b/spec/ruby/library/date/format/bag/method_missing_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../../spec_helper'
+require 'date'
+
+describe "Date::Format::Bag#method_missing" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/format/bag/to_hash_spec.rb b/spec/ruby/library/date/format/bag/to_hash_spec.rb
new file mode 100644
index 0000000000..76734624b9
--- /dev/null
+++ b/spec/ruby/library/date/format/bag/to_hash_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../../spec_helper'
+require 'date'
+
+describe "Date::Format::Bag#to_hash" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/friday_spec.rb b/spec/ruby/library/date/friday_spec.rb
new file mode 100644
index 0000000000..3dc040fabe
--- /dev/null
+++ b/spec/ruby/library/date/friday_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#friday?" do
+ it "should be friday" do
+ Date.new(2000, 1, 7).friday?.should be_true
+ end
+
+ it "should not be friday" do
+ Date.new(2000, 1, 8).friday?.should be_false
+ end
+end
diff --git a/spec/ruby/library/date/gregorian_leap_spec.rb b/spec/ruby/library/date/gregorian_leap_spec.rb
new file mode 100644
index 0000000000..c3d25cf90f
--- /dev/null
+++ b/spec/ruby/library/date/gregorian_leap_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#gregorian_leap?" do
+ it "returns true if a year is a leap year in the Gregorian calendar" do
+ Date.gregorian_leap?(2000).should be_true
+ Date.gregorian_leap?(2004).should be_true
+ end
+
+ it "returns false if a year is not a leap year in the Gregorian calendar" do
+ Date.gregorian_leap?(1900).should be_false
+ Date.gregorian_leap?(1999).should be_false
+ Date.gregorian_leap?(2002).should be_false
+ end
+end
diff --git a/spec/ruby/library/date/gregorian_spec.rb b/spec/ruby/library/date/gregorian_spec.rb
new file mode 100644
index 0000000000..ea7ece2ade
--- /dev/null
+++ b/spec/ruby/library/date/gregorian_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#gregorian?" do
+
+ it "marks a day before the calendar reform as Julian" do
+ Date.civil(1007, 2, 27).gregorian?.should be_false
+ Date.civil(1907, 2, 27, Date.civil(1930, 1, 1).jd).gregorian?.should be_false
+ end
+
+ it "marks a day after the calendar reform as Julian" do
+ Date.civil(2007, 2, 27).should.gregorian?
+ Date.civil(1607, 2, 27, Date.civil(1582, 1, 1).jd).gregorian?.should be_true
+ end
+
+end
diff --git a/spec/ruby/library/date/hash_spec.rb b/spec/ruby/library/date/hash_spec.rb
new file mode 100644
index 0000000000..4fb452d486
--- /dev/null
+++ b/spec/ruby/library/date/hash_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#hash" do
+ it "returns the same value for equal dates" do
+ Date.civil(2004, 7, 12).hash.should == Date.civil(2004, 7, 12).hash
+ end
+end
diff --git a/spec/ruby/library/date/infinity/abs_spec.rb b/spec/ruby/library/date/infinity/abs_spec.rb
new file mode 100644
index 0000000000..c08189155d
--- /dev/null
+++ b/spec/ruby/library/date/infinity/abs_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'date'
+
+describe "Date::Infinity#abs" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/infinity/coerce_spec.rb b/spec/ruby/library/date/infinity/coerce_spec.rb
new file mode 100644
index 0000000000..75e5ebeab7
--- /dev/null
+++ b/spec/ruby/library/date/infinity/coerce_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'date'
+
+describe "Date::Infinity#coerce" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/infinity/comparison_spec.rb b/spec/ruby/library/date/infinity/comparison_spec.rb
new file mode 100644
index 0000000000..a9b9d124fd
--- /dev/null
+++ b/spec/ruby/library/date/infinity/comparison_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'date'
+
+describe "Date::Infinity#<=>" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/infinity/d_spec.rb b/spec/ruby/library/date/infinity/d_spec.rb
new file mode 100644
index 0000000000..a5bd2427c7
--- /dev/null
+++ b/spec/ruby/library/date/infinity/d_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'date'
+
+describe "Date::Infinity#d" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/infinity/finite_spec.rb b/spec/ruby/library/date/infinity/finite_spec.rb
new file mode 100644
index 0000000000..8971c2213e
--- /dev/null
+++ b/spec/ruby/library/date/infinity/finite_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'date'
+
+describe "Date::Infinity#finite?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/infinity/infinite_spec.rb b/spec/ruby/library/date/infinity/infinite_spec.rb
new file mode 100644
index 0000000000..848f538672
--- /dev/null
+++ b/spec/ruby/library/date/infinity/infinite_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'date'
+
+describe "Date::Infinity#infinite?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/infinity/nan_spec.rb b/spec/ruby/library/date/infinity/nan_spec.rb
new file mode 100644
index 0000000000..b0f5d8ac7a
--- /dev/null
+++ b/spec/ruby/library/date/infinity/nan_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'date'
+
+describe "Date::Infinity#nan?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/infinity/uminus_spec.rb b/spec/ruby/library/date/infinity/uminus_spec.rb
new file mode 100644
index 0000000000..1b1f568103
--- /dev/null
+++ b/spec/ruby/library/date/infinity/uminus_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'date'
+
+describe "Date::Infinity#-@" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/infinity/uplus_spec.rb b/spec/ruby/library/date/infinity/uplus_spec.rb
new file mode 100644
index 0000000000..6a3b2d8442
--- /dev/null
+++ b/spec/ruby/library/date/infinity/uplus_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'date'
+
+describe "Date::Infinity#+@" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/infinity/zero_spec.rb b/spec/ruby/library/date/infinity/zero_spec.rb
new file mode 100644
index 0000000000..7df5518785
--- /dev/null
+++ b/spec/ruby/library/date/infinity/zero_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'date'
+
+describe "Date::Infinity#zero?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/infinity_spec.rb b/spec/ruby/library/date/infinity_spec.rb
new file mode 100644
index 0000000000..721fd76066
--- /dev/null
+++ b/spec/ruby/library/date/infinity_spec.rb
@@ -0,0 +1,67 @@
+require 'date'
+require_relative '../../spec_helper'
+
+describe "Date::Infinity" do
+
+ it "should be able to check whether Infinity is zero" do
+ i = Date::Infinity.new
+ i.should_not.zero?
+ end
+
+ it "should be able to check whether Infinity is finite" do
+ i1 = Date::Infinity.new
+ i1.should_not.finite?
+ i2 = Date::Infinity.new(-1)
+ i2.should_not.finite?
+ i3 = Date::Infinity.new(0)
+ i3.should_not.finite?
+ end
+
+ it "should be able to check whether Infinity is infinite" do
+ i1 = Date::Infinity.new
+ i1.infinite?.should == 1
+ i2 = Date::Infinity.new(-1)
+ i2.infinite?.should == -1
+ i3 = Date::Infinity.new(0)
+ i3.infinite?.should == nil
+ end
+
+ it "should be able to check whether Infinity is not a number" do
+ i1 = Date::Infinity.new
+ i1.should_not.nan?
+ i2 = Date::Infinity.new(-1)
+ i2.should_not.nan?
+ i3 = Date::Infinity.new(0)
+ i3.should.nan?
+ end
+
+ it "should be able to compare Infinity objects" do
+ i1 = Date::Infinity.new
+ i2 = Date::Infinity.new(-1)
+ i3 = Date::Infinity.new(0)
+ i4 = Date::Infinity.new
+ (i4 <=> i1).should == 0
+ (i3 <=> i1).should == -1
+ (i2 <=> i1).should == -1
+ (i3 <=> i2).should == 1
+ end
+
+ it "should be able to return plus Infinity for abs" do
+ i1 = Date::Infinity.new
+ i2 = Date::Infinity.new(-1)
+ i3 = Date::Infinity.new(0)
+ (i2.abs <=> i1).should == 0
+ (i3.abs <=> i1).should == 0
+ end
+
+ it "should be able to use -@ and +@ for Date::Infinity" do
+ (Date::Infinity.new <=> +Date::Infinity.new).should == 0
+ (Date::Infinity.new(-1) <=> -Date::Infinity.new).should == 0
+ end
+
+ it "should be able to coerce a Date::Infinity object" do
+ Date::Infinity.new.coerce(1).should == [-1, 1]
+ Date::Infinity.new(0).coerce(2).should == [0, 0]
+ Date::Infinity.new(-1).coerce(1.5).should == [1, -1]
+ end
+end
diff --git a/spec/ruby/library/date/inspect_spec.rb b/spec/ruby/library/date/inspect_spec.rb
new file mode 100644
index 0000000000..81c2cc8003
--- /dev/null
+++ b/spec/ruby/library/date/inspect_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#inspect" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/iso8601_spec.rb b/spec/ruby/library/date/iso8601_spec.rb
new file mode 100644
index 0000000000..a29652014e
--- /dev/null
+++ b/spec/ruby/library/date/iso8601_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.iso8601" do
+ it "parses YYYY-MM-DD into a Date object" do
+ d = Date.iso8601("2018-01-01")
+ d.should == Date.civil(2018, 1, 1)
+ end
+
+ it "parses YYYYMMDD into a Date object" do
+ d = Date.iso8601("20180715")
+ d.should == Date.civil(2018, 7, 15)
+ end
+
+ it "parses a negative Date" do
+ d = Date.iso8601("-4712-01-01")
+ d.should == Date.civil(-4712, 1, 1)
+ end
+
+ it "parses a StringSubclass into a Date object" do
+ d = Date.iso8601(Class.new(String).new("-4712-01-01"))
+ d.should == Date.civil(-4712, 1, 1)
+ end
+
+ it "raises a TypeError when passed an Object" do
+ -> { Date.iso8601(Object.new) }.should raise_error(TypeError)
+ end
+end
+
+describe "Date._iso8601" do
+ it "returns an empty hash if the argument is a invalid Date" do
+ h = Date._iso8601('invalid')
+ h.should == {}
+ end
+end
diff --git a/spec/ruby/library/date/italy_spec.rb b/spec/ruby/library/date/italy_spec.rb
new file mode 100644
index 0000000000..9369b05180
--- /dev/null
+++ b/spec/ruby/library/date/italy_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#italy" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/jd_spec.rb b/spec/ruby/library/date/jd_spec.rb
new file mode 100644
index 0000000000..336b783e8d
--- /dev/null
+++ b/spec/ruby/library/date/jd_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'shared/jd'
+require 'date'
+
+describe "Date#jd" do
+
+ it "determines the Julian day for a Date object" do
+ Date.civil(2008, 1, 16).jd.should == 2454482
+ end
+
+end
+
+describe "Date.jd" do
+ it_behaves_like :date_jd, :jd
+end
diff --git a/spec/ruby/library/date/jd_to_ajd_spec.rb b/spec/ruby/library/date/jd_to_ajd_spec.rb
new file mode 100644
index 0000000000..f946c46b8a
--- /dev/null
+++ b/spec/ruby/library/date/jd_to_ajd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.jd_to_ajd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/jd_to_civil_spec.rb b/spec/ruby/library/date/jd_to_civil_spec.rb
new file mode 100644
index 0000000000..13b6e47ee2
--- /dev/null
+++ b/spec/ruby/library/date/jd_to_civil_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.jd_to_civil" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/jd_to_commercial_spec.rb b/spec/ruby/library/date/jd_to_commercial_spec.rb
new file mode 100644
index 0000000000..2256b74f2a
--- /dev/null
+++ b/spec/ruby/library/date/jd_to_commercial_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.jd_to_commercial" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/jd_to_ld_spec.rb b/spec/ruby/library/date/jd_to_ld_spec.rb
new file mode 100644
index 0000000000..5954014f85
--- /dev/null
+++ b/spec/ruby/library/date/jd_to_ld_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.jd_to_ld" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/jd_to_mjd_spec.rb b/spec/ruby/library/date/jd_to_mjd_spec.rb
new file mode 100644
index 0000000000..24eb84e171
--- /dev/null
+++ b/spec/ruby/library/date/jd_to_mjd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.jd_to_mjd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/jd_to_ordinal_spec.rb b/spec/ruby/library/date/jd_to_ordinal_spec.rb
new file mode 100644
index 0000000000..c7c1704948
--- /dev/null
+++ b/spec/ruby/library/date/jd_to_ordinal_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.jd_to_ordinal" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/jd_to_wday_spec.rb b/spec/ruby/library/date/jd_to_wday_spec.rb
new file mode 100644
index 0000000000..27e00b2044
--- /dev/null
+++ b/spec/ruby/library/date/jd_to_wday_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.jd_to_wday" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/julian_leap_spec.rb b/spec/ruby/library/date/julian_leap_spec.rb
new file mode 100644
index 0000000000..2ef2d65d81
--- /dev/null
+++ b/spec/ruby/library/date/julian_leap_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.julian_leap?" do
+ it "determines whether a year is a leap year in the Julian calendar" do
+ Date.julian_leap?(1900).should be_true
+ Date.julian_leap?(2000).should be_true
+ Date.julian_leap?(2004).should be_true
+ end
+
+ it "determines whether a year is not a leap year in the Julian calendar" do
+ Date.julian_leap?(1999).should be_false
+ Date.julian_leap?(2002).should be_false
+ end
+end
diff --git a/spec/ruby/library/date/julian_spec.rb b/spec/ruby/library/date/julian_spec.rb
new file mode 100644
index 0000000000..db2629d1e7
--- /dev/null
+++ b/spec/ruby/library/date/julian_spec.rb
@@ -0,0 +1,16 @@
+require 'date'
+require_relative '../../spec_helper'
+
+describe "Date#julian?" do
+
+ it "marks a day before the calendar reform as Julian" do
+ Date.civil(1007, 2, 27).should.julian?
+ Date.civil(1907, 2, 27, Date.civil(1930, 1, 1).jd).julian?.should be_true
+ end
+
+ it "marks a day after the calendar reform as Julian" do
+ Date.civil(2007, 2, 27).should_not.julian?
+ Date.civil(1607, 2, 27, Date.civil(1582, 1, 1).jd).julian?.should be_false
+ end
+
+end
diff --git a/spec/ruby/library/date/ld_spec.rb b/spec/ruby/library/date/ld_spec.rb
new file mode 100644
index 0000000000..73a47d2382
--- /dev/null
+++ b/spec/ruby/library/date/ld_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#ld" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/ld_to_jd_spec.rb b/spec/ruby/library/date/ld_to_jd_spec.rb
new file mode 100644
index 0000000000..37abe01449
--- /dev/null
+++ b/spec/ruby/library/date/ld_to_jd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.ld_to_jd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/leap_spec.rb b/spec/ruby/library/date/leap_spec.rb
new file mode 100644
index 0000000000..674b191c9f
--- /dev/null
+++ b/spec/ruby/library/date/leap_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#leap?" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "Date.leap?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/mday_spec.rb b/spec/ruby/library/date/mday_spec.rb
new file mode 100644
index 0000000000..53f6f98169
--- /dev/null
+++ b/spec/ruby/library/date/mday_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#mday" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/minus_month_spec.rb b/spec/ruby/library/date/minus_month_spec.rb
new file mode 100644
index 0000000000..470c4d8a76
--- /dev/null
+++ b/spec/ruby/library/date/minus_month_spec.rb
@@ -0,0 +1,23 @@
+require 'date'
+require_relative '../../spec_helper'
+
+describe "Date#<<" do
+
+ it "subtracts a number of months from a date" do
+ d = Date.civil(2007,2,27) << 10
+ d.should == Date.civil(2006, 4, 27)
+ end
+
+ it "returns the last day of a month if the day doesn't exist" do
+ d = Date.civil(2008,3,31) << 1
+ d.should == Date.civil(2008, 2, 29)
+ end
+
+ it "raises an error on non numeric parameters" do
+ -> { Date.civil(2007,2,27) << :hello }.should raise_error(TypeError)
+ -> { Date.civil(2007,2,27) << "hello" }.should raise_error(TypeError)
+ -> { Date.civil(2007,2,27) << Date.new }.should raise_error(TypeError)
+ -> { Date.civil(2007,2,27) << Object.new }.should raise_error(TypeError)
+ end
+
+end
diff --git a/spec/ruby/library/date/minus_spec.rb b/spec/ruby/library/date/minus_spec.rb
new file mode 100644
index 0000000000..5a2a29e04a
--- /dev/null
+++ b/spec/ruby/library/date/minus_spec.rb
@@ -0,0 +1,30 @@
+require 'date'
+require_relative '../../spec_helper'
+
+describe "Date#-" do
+
+ it "subtracts a number of days from a Date" do
+ d = Date.civil(2007, 5 ,2) - 13
+ d.should == Date.civil(2007, 4, 19)
+ end
+
+ it "subtracts a negative number of days from a Date" do
+ d = Date.civil(2007, 4, 19).-(-13)
+ d.should == Date.civil(2007, 5 ,2)
+ end
+
+ it "computes the difference between two dates" do
+ (Date.civil(2007,2,27) - Date.civil(2007,2,27)).should == 0
+ (Date.civil(2007,2,27) - Date.civil(2007,2,26)).should == 1
+ (Date.civil(2006,2,27) - Date.civil(2007,2,27)).should == -365
+ (Date.civil(2008,2,27) - Date.civil(2007,2,27)).should == 365
+
+ end
+
+ it "raises an error for non Numeric arguments" do
+ -> { Date.civil(2007,2,27) - :hello }.should raise_error(TypeError)
+ -> { Date.civil(2007,2,27) - "hello" }.should raise_error(TypeError)
+ -> { Date.civil(2007,2,27) - Object.new }.should raise_error(TypeError)
+ end
+
+end
diff --git a/spec/ruby/library/date/mjd_spec.rb b/spec/ruby/library/date/mjd_spec.rb
new file mode 100644
index 0000000000..6f03af346b
--- /dev/null
+++ b/spec/ruby/library/date/mjd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#mjd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/mjd_to_jd_spec.rb b/spec/ruby/library/date/mjd_to_jd_spec.rb
new file mode 100644
index 0000000000..2009261103
--- /dev/null
+++ b/spec/ruby/library/date/mjd_to_jd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.mjd_to_jd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/mon_spec.rb b/spec/ruby/library/date/mon_spec.rb
new file mode 100644
index 0000000000..724e7d6564
--- /dev/null
+++ b/spec/ruby/library/date/mon_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#mon" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/monday_spec.rb b/spec/ruby/library/date/monday_spec.rb
new file mode 100644
index 0000000000..14a117b73c
--- /dev/null
+++ b/spec/ruby/library/date/monday_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#monday?" do
+ it "should be monday" do
+ Date.new(2000, 1, 3).monday?.should be_true
+ end
+end
diff --git a/spec/ruby/library/date/month_spec.rb b/spec/ruby/library/date/month_spec.rb
new file mode 100644
index 0000000000..e040f9a94c
--- /dev/null
+++ b/spec/ruby/library/date/month_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#month" do
+ it "returns the month" do
+ m = Date.new(2000, 7, 1).month
+ m.should == 7
+ end
+end
diff --git a/spec/ruby/library/date/new_spec.rb b/spec/ruby/library/date/new_spec.rb
new file mode 100644
index 0000000000..18120118c0
--- /dev/null
+++ b/spec/ruby/library/date/new_spec.rb
@@ -0,0 +1,8 @@
+require 'date'
+require_relative '../../spec_helper'
+require_relative 'shared/civil'
+require_relative 'shared/new_bang'
+
+describe "Date.new" do
+ it_behaves_like :date_civil, :new
+end
diff --git a/spec/ruby/library/date/new_start_spec.rb b/spec/ruby/library/date/new_start_spec.rb
new file mode 100644
index 0000000000..aef78f2320
--- /dev/null
+++ b/spec/ruby/library/date/new_start_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#new_start" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/next_day_spec.rb b/spec/ruby/library/date/next_day_spec.rb
new file mode 100644
index 0000000000..3b066630e7
--- /dev/null
+++ b/spec/ruby/library/date/next_day_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#next_day" do
+ it "returns the next day" do
+ d = Date.new(2000, 1, 4).next_day
+ d.should == Date.new(2000, 1, 5)
+ end
+
+ it "returns three days later across months" do
+ d = Date.new(2000, 1, 30).next_day(3)
+ d.should == Date.new(2000, 2, 2)
+ end
+end
diff --git a/spec/ruby/library/date/next_month_spec.rb b/spec/ruby/library/date/next_month_spec.rb
new file mode 100644
index 0000000000..6ee664433f
--- /dev/null
+++ b/spec/ruby/library/date/next_month_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#next_month" do
+ it "returns the next month" do
+ d = Date.new(2000, 7, 1).next_month
+ d.should == Date.new(2000, 8, 1)
+ end
+
+ it "returns three months later" do
+ d = Date.new(2000, 7, 1).next_month(3)
+ d.should == Date.new(2000, 10, 1)
+ end
+
+ it "returns three months later across years" do
+ d = Date.new(2000, 12, 1).next_month(3)
+ d.should == Date.new(2001, 3, 1)
+ end
+
+ it "returns last day of month two months later" do
+ d = Date.new(2000, 1, 31).next_month(2)
+ d.should == Date.new(2000, 3, 31)
+ end
+
+ it "returns last day of next month when same day does not exist" do
+ d = Date.new(2001, 1, 30).next_month
+ d.should == Date.new(2001, 2, 28)
+ end
+end
diff --git a/spec/ruby/library/date/next_spec.rb b/spec/ruby/library/date/next_spec.rb
new file mode 100644
index 0000000000..8063d6a2e4
--- /dev/null
+++ b/spec/ruby/library/date/next_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#next" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/next_year_spec.rb b/spec/ruby/library/date/next_year_spec.rb
new file mode 100644
index 0000000000..dda9a44008
--- /dev/null
+++ b/spec/ruby/library/date/next_year_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#next_year" do
+ it "returns the day of the reform if date falls within calendar reform" do
+ calendar_reform_italy = Date.new(1582, 10, 4)
+ d1 = Date.new(1581, 10, 9).next_year
+ d2 = Date.new(1581, 10, 10).next_year
+ d1.should == calendar_reform_italy
+ d2.should == calendar_reform_italy
+ end
+end
diff --git a/spec/ruby/library/date/ordinal_spec.rb b/spec/ruby/library/date/ordinal_spec.rb
new file mode 100644
index 0000000000..ec490fd49c
--- /dev/null
+++ b/spec/ruby/library/date/ordinal_spec.rb
@@ -0,0 +1,7 @@
+require 'date'
+require_relative '../../spec_helper'
+require_relative 'shared/ordinal'
+
+describe "Date.ordinal" do
+ it_behaves_like :date_ordinal, :ordinal
+end
diff --git a/spec/ruby/library/date/ordinal_to_jd_spec.rb b/spec/ruby/library/date/ordinal_to_jd_spec.rb
new file mode 100644
index 0000000000..44f4b3321e
--- /dev/null
+++ b/spec/ruby/library/date/ordinal_to_jd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.ordinal_to_jd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/parse_spec.rb b/spec/ruby/library/date/parse_spec.rb
new file mode 100644
index 0000000000..bfbe86fbac
--- /dev/null
+++ b/spec/ruby/library/date/parse_spec.rb
@@ -0,0 +1,159 @@
+require_relative '../../spec_helper'
+require_relative 'shared/parse'
+require_relative 'shared/parse_us'
+require_relative 'shared/parse_eu'
+require 'date'
+
+describe "Date#parse" do
+ # The space separator is also different, doesn't work for only numbers
+ it "parses a day name into a Date object" do
+ d = Date.parse("friday")
+ d.should == Date.commercial(d.cwyear, d.cweek, 5)
+ end
+
+ it "parses a month name into a Date object" do
+ d = Date.parse("october")
+ d.should == Date.civil(Date.today.year, 10)
+ end
+
+ it "parses a month day into a Date object" do
+ d = Date.parse("5th")
+ d.should == Date.civil(Date.today.year, Date.today.month, 5)
+ end
+
+ # Specs using numbers
+ it "throws an argument error for a single digit" do
+ ->{ Date.parse("1") }.should raise_error(ArgumentError)
+ end
+
+ it "parses DD as month day number" do
+ d = Date.parse("10")
+ d.should == Date.civil(Date.today.year, Date.today.month, 10)
+ end
+
+ it "parses DDD as year day number" do
+ d = Date.parse("100")
+ if Date.gregorian_leap?(Date.today.year)
+ d.should == Date.civil(Date.today.year, 4, 9)
+ else
+ d.should == Date.civil(Date.today.year, 4, 10)
+ end
+ end
+
+ it "parses MMDD as month and day" do
+ d = Date.parse("1108")
+ d.should == Date.civil(Date.today.year, 11, 8)
+ end
+
+ it "parses YYDDD as year and day number in 1969--2068" do
+ d = Date.parse("10100")
+ d.should == Date.civil(2010, 4, 10)
+ end
+
+ it "parses YYMMDD as year, month and day in 1969--2068" do
+ d = Date.parse("201023")
+ d.should == Date.civil(2020, 10, 23)
+ end
+
+ it "parses YYYYDDD as year and day number" do
+ d = Date.parse("1910100")
+ d.should == Date.civil(1910, 4, 10)
+ end
+
+ it "parses YYYYMMDD as year, month and day number" do
+ d = Date.parse("19101101")
+ d.should == Date.civil(1910, 11, 1)
+ end
+
+ it "raises a TypeError trying to parse non-String-like object" do
+ -> { Date.parse(1) }.should raise_error(TypeError)
+ -> { Date.parse([]) }.should raise_error(TypeError)
+ end
+
+ it "coerces using to_str" do
+ c = Class.new do
+ attr_accessor :string
+ def to_str
+ @string
+ end
+ end
+ o = c.new
+ o.string = "19101101"
+
+ d = Date.parse(o)
+ d.should == Date.civil(1910, 11, 1)
+
+ # parse should not modify string value
+ o.to_str.should == "19101101"
+ end
+end
+
+describe "Date#parse with '.' separator" do
+ before :all do
+ @sep = '.'
+ end
+
+ it_should_behave_like "date_parse"
+end
+
+describe "Date#parse with '/' separator" do
+ before :all do
+ @sep = '/'
+ end
+
+ it_should_behave_like "date_parse"
+end
+
+describe "Date#parse with ' ' separator" do
+ before :all do
+ @sep = ' '
+ end
+
+ it_should_behave_like "date_parse"
+end
+
+describe "Date#parse with '/' separator US-style" do
+ before :all do
+ @sep = '/'
+ end
+
+ it_should_behave_like "date_parse_us"
+end
+
+describe "Date#parse with '-' separator EU-style" do
+ before :all do
+ @sep = '-'
+ end
+
+ it_should_behave_like "date_parse_eu"
+end
+
+describe "Date#parse(.)" do
+ it "parses YYYY.MM.DD into a Date object" do
+ d = Date.parse("2007.10.01")
+ d.year.should == 2007
+ d.month.should == 10
+ d.day.should == 1
+ end
+
+ it "parses DD.MM.YYYY into a Date object" do
+ d = Date.parse("10.01.2007")
+ d.year.should == 2007
+ d.month.should == 1
+ d.day.should == 10
+ end
+
+ it "parses YY.MM.DD into a Date object using the year 20YY" do
+ d = Date.parse("10.01.07")
+ d.year.should == 2010
+ d.month.should == 1
+ d.day.should == 7
+ end
+
+ it "parses YY.MM.DD using the year digits as 20YY when given true as additional argument" do
+ d = Date.parse("10.01.07", true)
+ d.year.should == 2010
+ d.month.should == 1
+ d.day.should == 7
+ end
+end
diff --git a/spec/ruby/library/date/plus_spec.rb b/spec/ruby/library/date/plus_spec.rb
new file mode 100644
index 0000000000..0cb99fd4ca
--- /dev/null
+++ b/spec/ruby/library/date/plus_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#+" do
+ before :all do
+ @date = Date.civil(2000, 1, 1)
+ end
+
+ it "returns a new Date object that is n days later than the current one" do
+ (@date + 31).should == Date.civil(2000, 2, 1)
+ end
+
+ it "accepts a negative argument and returns a new Date that is earlier than the current one" do
+ (@date + -1).should == Date.civil(1999, 12, 31)
+ end
+
+ it "raises TypeError if argument is not Numeric" do
+ -> { Date.today + Date.today }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/date/prev_day_spec.rb b/spec/ruby/library/date/prev_day_spec.rb
new file mode 100644
index 0000000000..cce24da875
--- /dev/null
+++ b/spec/ruby/library/date/prev_day_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#prev_day" do
+ it "returns previous day" do
+ d = Date.new(2000, 7, 2).prev_day
+ d.should == Date.new(2000, 7, 1)
+ end
+
+ it "returns three days ago across months" do
+ d = Date.new(2000, 7, 2).prev_day(3)
+ d.should == Date.new(2000, 6, 29)
+ end
+end
diff --git a/spec/ruby/library/date/prev_month_spec.rb b/spec/ruby/library/date/prev_month_spec.rb
new file mode 100644
index 0000000000..3d0d1d437d
--- /dev/null
+++ b/spec/ruby/library/date/prev_month_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#prev_month" do
+ it "returns previous month" do
+ d = Date.new(2000, 9, 1).prev_month
+ d.should == Date.new(2000, 8, 1)
+ end
+
+ it "returns three months ago" do
+ d = Date.new(2000, 10, 1).prev_month(3)
+ d.should == Date.new(2000, 7, 1)
+ end
+
+ it "returns three months ago across years" do
+ d = Date.new(2000, 1, 1).prev_month(3)
+ d.should == Date.new(1999, 10, 1)
+ end
+
+ it "returns last day of month two months ago" do
+ d = Date.new(2000, 3, 31).prev_month(2)
+ d.should == Date.new(2000, 1, 31)
+ end
+
+ it "returns last day of previous month when same day does not exist" do
+ d = Date.new(2001, 3, 30).prev_month
+ d.should == Date.new(2001, 2, 28)
+ end
+end
diff --git a/spec/ruby/library/date/prev_year_spec.rb b/spec/ruby/library/date/prev_year_spec.rb
new file mode 100644
index 0000000000..ba06dd198b
--- /dev/null
+++ b/spec/ruby/library/date/prev_year_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#prev_year" do
+ it "returns the day of the reform if date falls within calendar reform" do
+ calendar_reform_italy = Date.new(1582, 10, 4)
+ d1 = Date.new(1583, 10, 9).prev_year
+ d2 = Date.new(1583, 10, 10).prev_year
+ d1.should == calendar_reform_italy
+ d2.should == calendar_reform_italy
+ end
+end
diff --git a/spec/ruby/library/date/relationship_spec.rb b/spec/ruby/library/date/relationship_spec.rb
new file mode 100644
index 0000000000..979516e164
--- /dev/null
+++ b/spec/ruby/library/date/relationship_spec.rb
@@ -0,0 +1,20 @@
+require 'date'
+require_relative '../../spec_helper'
+
+describe "Date#===" do
+
+ it "returns 0 when comparing two equal dates" do
+ (Date.civil(2000, 04, 06) <=> Date.civil(2000, 04, 06)).should == 0
+ end
+
+ it "computes the difference between two dates" do
+ (Date.civil(2000, 04, 05) <=> Date.civil(2000, 04, 06)).should == -1
+ (Date.civil(2001, 04, 05) <=> Date.civil(2000, 04, 06)).should == 1
+ end
+
+ it "compares to another numeric" do
+ (Date.civil(2000, 04, 05) <=> Date.civil(2000, 04, 06).jd).should == -1
+ (Date.civil(2001, 04, 05) <=> Date.civil(2000, 04, 06).jd).should == 1
+ end
+
+end
diff --git a/spec/ruby/library/date/rfc3339_spec.rb b/spec/ruby/library/date/rfc3339_spec.rb
new file mode 100644
index 0000000000..a8711d47b2
--- /dev/null
+++ b/spec/ruby/library/date/rfc3339_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.rfc3339" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "Date._rfc3339" do
+ it "returns an empty hash if the argument is a invalid Date" do
+ h = Date._rfc3339('invalid')
+ h.should == {}
+ end
+end
diff --git a/spec/ruby/library/date/right_shift_spec.rb b/spec/ruby/library/date/right_shift_spec.rb
new file mode 100644
index 0000000000..bd7de0e3d5
--- /dev/null
+++ b/spec/ruby/library/date/right_shift_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#>>" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/saturday_spec.rb b/spec/ruby/library/date/saturday_spec.rb
new file mode 100644
index 0000000000..1527b71d00
--- /dev/null
+++ b/spec/ruby/library/date/saturday_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#saturday?" do
+ it "should be saturday" do
+ Date.new(2000, 1, 1).saturday?.should be_true
+ end
+end
diff --git a/spec/ruby/library/date/shared/civil.rb b/spec/ruby/library/date/shared/civil.rb
new file mode 100644
index 0000000000..bbed4a8866
--- /dev/null
+++ b/spec/ruby/library/date/shared/civil.rb
@@ -0,0 +1,57 @@
+describe :date_civil, shared: true do
+ it "creates a Date for -4712 by default" do
+ # the #chomp calls are necessary because of RSpec
+ d = Date.send(@method)
+ d.year.should == -4712
+ d.month.should == 1
+ d.day.should == 1
+ d.should.julian?
+ d.jd.should == 0
+ end
+
+ it "creates a date with arguments" do
+ d = Date.send(@method, 2000, 3, 5)
+ d.year.should == 2000
+ d.month.should == 3
+ d.day.should == 5
+ d.should_not.julian?
+ d.jd.should == 2451609
+
+ # Should also work with years far in the past and future
+
+ d = Date.send(@method, -9000, 7, 5)
+ d.year.should == -9000
+ d.month.should == 7
+ d.day.should == 5
+ d.should.julian?
+ d.jd.should == -1566006
+
+ d = Date.send(@method, 9000, 10, 14)
+ d.year.should == 9000
+ d.month.should == 10
+ d.day.should == 14
+ d.should_not.julian?
+ d.jd.should == 5008529
+
+ end
+
+ it "doesn't create dates for invalid arguments" do
+ -> { Date.send(@method, 2000, 13, 31) }.should raise_error(ArgumentError)
+ -> { Date.send(@method, 2000, 12, 32) }.should raise_error(ArgumentError)
+ -> { Date.send(@method, 2000, 2, 30) }.should raise_error(ArgumentError)
+ -> { Date.send(@method, 1900, 2, 29) }.should raise_error(ArgumentError)
+ -> { Date.send(@method, 2000, 2, 29) }.should_not raise_error(ArgumentError)
+
+ -> { Date.send(@method, 1582, 10, 14) }.should raise_error(ArgumentError)
+ -> { Date.send(@method, 1582, 10, 15) }.should_not raise_error(ArgumentError)
+
+ end
+
+ it "creates a Date for different calendar reform dates" do
+ d1 = Date.send(@method, 1582, 10, 4)
+ d1.succ.day.should == 15
+
+ d2 = Date.send(@method, 1582, 10, 4, Date::ENGLAND)
+ d2.succ.day.should == 5
+ end
+end
diff --git a/spec/ruby/library/date/shared/commercial.rb b/spec/ruby/library/date/shared/commercial.rb
new file mode 100644
index 0000000000..39c9af47b6
--- /dev/null
+++ b/spec/ruby/library/date/shared/commercial.rb
@@ -0,0 +1,39 @@
+describe :date_commercial, shared: true do
+ it "creates a Date for Julian Day Number day 0 by default" do
+ d = Date.send(@method)
+ d.year.should == -4712
+ d.month.should == 1
+ d.day.should == 1
+ end
+
+ it "creates a Date for the monday in the year and week given" do
+ d = Date.send(@method, 2000, 1)
+ d.year.should == 2000
+ d.month.should == 1
+ d.day.should == 3
+ d.cwday.should == 1
+ end
+
+ it "creates a Date for the correct day given the year, week and day number" do
+ d = Date.send(@method, 2004, 1, 1)
+ d.year.should == 2003
+ d.month.should == 12
+ d.day.should == 29
+ d.cwday.should == 1
+ d.cweek.should == 1
+ d.cwyear.should == 2004
+ end
+
+ it "creates only Date objects for valid weeks" do
+ -> { Date.send(@method, 2004, 53, 1) }.should_not raise_error(ArgumentError)
+ -> { Date.send(@method, 2004, 53, 0) }.should raise_error(ArgumentError)
+ -> { Date.send(@method, 2004, 53, 8) }.should raise_error(ArgumentError)
+ -> { Date.send(@method, 2004, 54, 1) }.should raise_error(ArgumentError)
+ -> { Date.send(@method, 2004, 0, 1) }.should raise_error(ArgumentError)
+
+ -> { Date.send(@method, 2003, 52, 1) }.should_not raise_error(ArgumentError)
+ -> { Date.send(@method, 2003, 53, 1) }.should raise_error(ArgumentError)
+ -> { Date.send(@method, 2003, 52, 0) }.should raise_error(ArgumentError)
+ -> { Date.send(@method, 2003, 52, 8) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/date/shared/jd.rb b/spec/ruby/library/date/shared/jd.rb
new file mode 100644
index 0000000000..511557b4f7
--- /dev/null
+++ b/spec/ruby/library/date/shared/jd.rb
@@ -0,0 +1,14 @@
+describe :date_jd, shared: true do
+ it "constructs a Date object if passed a Julian day" do
+ Date.send(@method, 2454482).should == Date.civil(2008, 1, 16)
+ end
+
+ it "returns a Date object representing Julian day 0 (-4712-01-01) if no arguments passed" do
+ Date.send(@method).should == Date.civil(-4712, 1, 1)
+ end
+
+ it "constructs a Date object if passed a negative number" do
+ Date.send(@method, -1).should == Date.civil(-4713, 12, 31)
+ end
+
+end
diff --git a/spec/ruby/library/date/shared/new_bang.rb b/spec/ruby/library/date/shared/new_bang.rb
new file mode 100644
index 0000000000..90f1b432f0
--- /dev/null
+++ b/spec/ruby/library/date/shared/new_bang.rb
@@ -0,0 +1,14 @@
+describe :date_new_bang, shared: true do
+
+ it "returns a new Date object set to Astronomical Julian Day 0 if no arguments passed" do
+ d = Date.send(@method)
+ d.ajd.should == 0
+ end
+
+ it "accepts astronomical julian day number, offset as a fraction of a day and returns a new Date object" do
+ d = Date.send(@method, 10, 0.5)
+ d.ajd.should == 10
+ d.jd.should == 11
+ end
+
+end
diff --git a/spec/ruby/library/date/shared/ordinal.rb b/spec/ruby/library/date/shared/ordinal.rb
new file mode 100644
index 0000000000..4b182d5a25
--- /dev/null
+++ b/spec/ruby/library/date/shared/ordinal.rb
@@ -0,0 +1,22 @@
+# reference:
+# October 1582 (the Gregorian calendar, Civil Date)
+# S M Tu W Th F S
+# 1 2 3 4 15 16
+# 17 18 19 20 21 22 23
+# 24 25 26 27 28 29 30
+# 31
+
+describe :date_ordinal, shared: true do
+ it "constructs a Date object from an ordinal date" do
+ # October 1582 (the Gregorian calendar, Ordinal Date)
+ # S M Tu W Th F S
+ # 274 275 276 277 278 279
+ # 280 281 282 283 284 285 286
+ # 287 288 289 290 291 292 293
+ # 294
+ Date.send(@method, 1582, 274).should == Date.civil(1582, 10, 1)
+ Date.send(@method, 1582, 277).should == Date.civil(1582, 10, 4)
+ Date.send(@method, 1582, 278).should == Date.civil(1582, 10, 15)
+ Date.send(@method, 1582, 287, Date::ENGLAND).should == Date.civil(1582, 10, 14, Date::ENGLAND)
+ end
+end
diff --git a/spec/ruby/library/date/shared/parse.rb b/spec/ruby/library/date/shared/parse.rb
new file mode 100644
index 0000000000..1015285e04
--- /dev/null
+++ b/spec/ruby/library/date/shared/parse.rb
@@ -0,0 +1,54 @@
+describe :date_parse, shared: true do
+ it "can parse a mmm-YYYY string into a Date object" do
+ d = Date.parse("feb#{@sep}2008")
+ d.year.should == 2008
+ d.month.should == 2
+ d.day.should == 1
+ end
+
+ it "can parse a 'DD mmm YYYY' string into a Date object" do
+ d = Date.parse("23#{@sep}feb#{@sep}2008")
+ d.year.should == 2008
+ d.month.should == 2
+ d.day.should == 23
+ end
+
+ it "can parse a 'mmm DD YYYY' string into a Date object" do
+ d = Date.parse("23#{@sep}feb#{@sep}2008")
+ d.year.should == 2008
+ d.month.should == 2
+ d.day.should == 23
+ end
+
+ it "can parse a 'YYYY mmm DD' string into a Date object" do
+ d = Date.parse("2008#{@sep}feb#{@sep}23")
+ d.year.should == 2008
+ d.month.should == 2
+ d.day.should == 23
+ end
+
+ it "can parse a month name and day into a Date object" do
+ d = Date.parse("november#{@sep}5th")
+ d.should == Date.civil(Date.today.year, 11, 5)
+ end
+
+ it "can parse a month name, day and year into a Date object" do
+ d = Date.parse("november#{@sep}5th#{@sep}2005")
+ d.should == Date.civil(2005, 11, 5)
+ end
+
+ it "can parse a year, month name and day into a Date object" do
+ d = Date.parse("2005#{@sep}november#{@sep}5th")
+ d.should == Date.civil(2005, 11, 5)
+ end
+
+ it "can parse a year, day and month name into a Date object" do
+ d = Date.parse("5th#{@sep}november#{@sep}2005")
+ d.should == Date.civil(2005, 11, 5)
+ end
+
+ it "can handle negative year numbers" do
+ d = Date.parse("5th#{@sep}november#{@sep}-2005")
+ d.should == Date.civil(-2005, 11, 5)
+ end
+end
diff --git a/spec/ruby/library/date/shared/parse_eu.rb b/spec/ruby/library/date/shared/parse_eu.rb
new file mode 100644
index 0000000000..ecb15e3c0e
--- /dev/null
+++ b/spec/ruby/library/date/shared/parse_eu.rb
@@ -0,0 +1,37 @@
+describe :date_parse_eu, shared: true do
+ # The - separator let's it work like European format, so it as a different spec
+ it "can parse a YYYY-MM-DD string into a Date object" do
+ d = Date.parse("2007#{@sep}10#{@sep}01")
+ d.year.should == 2007
+ d.month.should == 10
+ d.day.should == 1
+ end
+
+ it "can parse a MM-DD-YYYY string into a Date object" do
+ d = Date.parse("10#{@sep}01#{@sep}2007")
+ d.year.should == 2007
+ d.month.should == 1
+ d.day.should == 10
+ end
+
+ it "can parse a MM-DD-YY string into a Date object" do
+ d = Date.parse("10#{@sep}01#{@sep}07")
+ d.year.should == 2010
+ d.month.should == 1
+ d.day.should == 7
+ end
+
+ it "can parse a MM-DD-YY string into a Date object NOT using the year digits as 20XX" do
+ d = Date.parse("10#{@sep}01#{@sep}07", false)
+ d.year.should == 10
+ d.month.should == 1
+ d.day.should == 7
+ end
+
+ it "can parse a MM-DD-YY string into a Date object using the year digits as 20XX" do
+ d = Date.parse("10#{@sep}01#{@sep}07", true)
+ d.year.should == 2010
+ d.month.should == 1
+ d.day.should == 7
+ end
+end
diff --git a/spec/ruby/library/date/shared/parse_us.rb b/spec/ruby/library/date/shared/parse_us.rb
new file mode 100644
index 0000000000..7be62b1af1
--- /dev/null
+++ b/spec/ruby/library/date/shared/parse_us.rb
@@ -0,0 +1,36 @@
+describe :date_parse_us, shared: true do
+ it "parses a YYYY#{@sep}MM#{@sep}DD string into a Date object" do
+ d = Date.parse("2007#{@sep}10#{@sep}01")
+ d.year.should == 2007
+ d.month.should == 10
+ d.day.should == 1
+ end
+
+ it "parses a MM#{@sep}DD#{@sep}YYYY string into a Date object" do
+ d = Date.parse("10#{@sep}01#{@sep}2007")
+ d.year.should == 2007
+ d.month.should == 1
+ d.day.should == 10
+ end
+
+ it "parses a MM#{@sep}DD#{@sep}YY string into a Date object" do
+ d = Date.parse("10#{@sep}01#{@sep}07")
+ d.year.should == 2010
+ d.month.should == 1
+ d.day.should == 7
+ end
+
+ it "parses a MM#{@sep}DD#{@sep}YY string into a Date object NOT using the year digits as 20XX" do
+ d = Date.parse("10#{@sep}01#{@sep}07", false)
+ d.year.should == 10
+ d.month.should == 1
+ d.day.should == 7
+ end
+
+ it "parses a MM#{@sep}DD#{@sep}YY string into a Date object using the year digits as 20XX" do
+ d = Date.parse("10#{@sep}01#{@sep}07", true)
+ d.year.should == 2010
+ d.month.should == 1
+ d.day.should == 7
+ end
+end
diff --git a/spec/ruby/library/date/shared/valid_civil.rb b/spec/ruby/library/date/shared/valid_civil.rb
new file mode 100644
index 0000000000..545c207bbe
--- /dev/null
+++ b/spec/ruby/library/date/shared/valid_civil.rb
@@ -0,0 +1,36 @@
+describe :date_valid_civil?, shared: true do
+
+ # reference:
+ # October 1582 (the Gregorian calendar, Civil Date)
+ # S M Tu W Th F S
+ # 1 2 3 4 15 16
+ # 17 18 19 20 21 22 23
+ # 24 25 26 27 28 29 30
+ # 31
+
+ it "returns true if it is a valid civil date" do
+ Date.send(@method, 1582, 10, 15).should be_true
+ Date.send(@method, 1582, 10, 14, Date::ENGLAND).should be_true
+ end
+
+ it "returns false if it is not a valid civil date" do
+ Date.send(@method, 1582, 10, 14).should == false
+ end
+
+ it "handles negative months and days" do
+ # October 1582 (the Gregorian calendar, Civil Date)
+ # S M Tu W Th F S
+ # -21 -20 -19 -18 -17 -16
+ # -15 -14 -13 -12 -11 -10 -9
+ # -8 -7 -6 -5 -4 -3 -2
+ # -1
+ Date.send(@method, 1582, -3, -22).should be_false
+ Date.send(@method, 1582, -3, -21).should be_true
+ Date.send(@method, 1582, -3, -18).should be_true
+ Date.send(@method, 1582, -3, -17).should be_true
+
+ Date.send(@method, 2007, -11, -10).should be_true
+ Date.send(@method, 2008, -11, -10).should be_true
+ end
+
+end
diff --git a/spec/ruby/library/date/shared/valid_commercial.rb b/spec/ruby/library/date/shared/valid_commercial.rb
new file mode 100644
index 0000000000..117dfe1d3d
--- /dev/null
+++ b/spec/ruby/library/date/shared/valid_commercial.rb
@@ -0,0 +1,34 @@
+describe :date_valid_commercial?, shared: true do
+
+ it "returns true if it is a valid commercial date" do
+ # October 1582 (the Gregorian calendar, Commercial Date)
+ # M Tu W Th F Sa Su
+ # 39: 1 2 3 4 5 6 7
+ # 40: 1 2 3 4 5 6 7
+ # 41: 1 2 3 4 5 6 7
+ Date.send(@method, 1582, 39, 4).should be_true
+ Date.send(@method, 1582, 39, 5).should be_true
+ Date.send(@method, 1582, 41, 4).should be_true
+ Date.send(@method, 1582, 41, 5).should be_true
+ Date.send(@method, 1582, 41, 4, Date::ENGLAND).should be_true
+ Date.send(@method, 1752, 37, 4, Date::ENGLAND).should be_true
+ end
+
+ it "returns false it is not a valid commercial date" do
+ Date.send(@method, 1999, 53, 1).should be_false
+ end
+
+ it "handles negative week and day numbers" do
+ # October 1582 (the Gregorian calendar, Commercial Date)
+ # M Tu W Th F Sa Su
+ # -12: -7 -6 -5 -4 -3 -2 -1
+ # -11: -7 -6 -5 -4 -3 -2 -1
+ # -10: -7 -6 -5 -4 -3 -2 -1
+ Date.send(@method, 1582, -12, -4).should be_true
+ Date.send(@method, 1582, -12, -3).should be_true
+ Date.send(@method, 2007, -44, -2).should be_true
+ Date.send(@method, 2008, -44, -2).should be_true
+ Date.send(@method, 1999, -53, -1).should be_false
+ end
+
+end
diff --git a/spec/ruby/library/date/shared/valid_jd.rb b/spec/ruby/library/date/shared/valid_jd.rb
new file mode 100644
index 0000000000..e474dfb450
--- /dev/null
+++ b/spec/ruby/library/date/shared/valid_jd.rb
@@ -0,0 +1,20 @@
+describe :date_valid_jd?, shared: true do
+ it "returns true if passed a number value" do
+ Date.send(@method, -100).should be_true
+ Date.send(@method, 100.0).should be_true
+ Date.send(@method, 2**100).should be_true
+ Date.send(@method, Rational(1,2)).should be_true
+ end
+
+ it "returns false if passed nil" do
+ Date.send(@method, nil).should be_false
+ end
+
+ it "returns false if passed symbol" do
+ Date.send(@method, :number).should be_false
+ end
+
+ it "returns false if passed false" do
+ Date.send(@method, false).should be_false
+ end
+end
diff --git a/spec/ruby/library/date/shared/valid_ordinal.rb b/spec/ruby/library/date/shared/valid_ordinal.rb
new file mode 100644
index 0000000000..1ed961be23
--- /dev/null
+++ b/spec/ruby/library/date/shared/valid_ordinal.rb
@@ -0,0 +1,26 @@
+describe :date_valid_ordinal?, shared: true do
+ it "determines if the date is a valid ordinal date" do
+ # October 1582 (the Gregorian calendar, Ordinal Date)
+ # S M Tu W Th F S
+ # 274 275 276 277 278 279
+ # 280 281 282 283 284 285 286
+ # 287 288 289 290 291 292 293
+ # 294
+ Date.send(@method, 1582, 277).should == true
+ Date.send(@method, 1582, 278).should == true
+ Date.send(@method, 1582, 287).should == true
+ Date.send(@method, 1582, 288).should == true
+ end
+
+ it "handles negative day numbers" do
+ # October 1582 (the Gregorian calendar, Ordinal Date)
+ # S M Tu W Th F S
+ # -82 -81 -80 -79 -78 -77
+ # -76 -75 -74 -73 -72 -71 -70
+ # -69 -68 -67 -66 -65 -64 -63
+ # -62
+ Date.send(@method, 1582, -79).should == true
+ Date.send(@method, 1582, -78).should == true
+ Date.send(@method, 2007, -100).should == true
+ end
+end
diff --git a/spec/ruby/library/date/start_spec.rb b/spec/ruby/library/date/start_spec.rb
new file mode 100644
index 0000000000..8ba272a7f3
--- /dev/null
+++ b/spec/ruby/library/date/start_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#start" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/step_spec.rb b/spec/ruby/library/date/step_spec.rb
new file mode 100644
index 0000000000..6bbd671840
--- /dev/null
+++ b/spec/ruby/library/date/step_spec.rb
@@ -0,0 +1,56 @@
+require 'date'
+require_relative '../../spec_helper'
+
+describe "Date#step" do
+
+ it "steps forward in time" do
+ ds = Date.civil(2008, 10, 11)
+ de = Date.civil(2008, 9, 29)
+ count = 0
+ de.step(ds) do |d|
+ d.should <= ds
+ d.should >= de
+ count += 1
+ end
+ count.should == 13
+
+ count = 0
+ de.step(ds, 5) do |d|
+ d.should <= ds
+ d.should >= de
+ count += 1
+ end
+ count.should == 3
+
+ count = 0
+ ds.step(de) do |d|; count += 1; end
+ count.should == 0
+
+ end
+
+ it "steps backward in time" do
+ ds = Date.civil(2000, 4, 14)
+ de = Date.civil(2000, 3, 29)
+ count = 0
+ ds.step(de, -1) do |d|
+ d.should <= ds
+ d.should >= de
+ count += 1
+ end
+ count.should == 17
+
+ count = 0
+ ds.step(de, -5) do |d|
+ d.should <= ds
+ d.should >= de
+ count += 1
+ end
+ count.should == 4
+
+ count = 0
+ de.step(ds, -1) do |d|; count += 1; end
+ count.should == 0
+
+ end
+
+end
diff --git a/spec/ruby/library/date/strftime_spec.rb b/spec/ruby/library/date/strftime_spec.rb
new file mode 100644
index 0000000000..8b1ebe061c
--- /dev/null
+++ b/spec/ruby/library/date/strftime_spec.rb
@@ -0,0 +1,49 @@
+require 'date'
+require_relative '../../shared/time/strftime_for_date'
+
+describe "Date#strftime" do
+ before :all do
+ @new_date = -> y, m, d { Date.civil(y,m,d) }
+
+ @date = Date.civil(2000, 4, 9)
+ end
+
+ it_behaves_like :strftime_date, :strftime
+
+ # Differences with Time
+ it "should be able to print the date with no argument" do
+ @date.strftime.should == "2000-04-09"
+ @date.strftime.should == @date.to_s
+ end
+
+ # %Z is %:z for Date/DateTime
+ it "should be able to show the timezone with a : separator" do
+ @date.strftime("%Z").should == "+00:00"
+ end
+
+ # %v is %e-%b-%Y for Date/DateTime
+ ruby_version_is ""..."3.1" do
+ it "should be able to show the commercial week" do
+ @date.strftime("%v").should == " 9-Apr-2000"
+ @date.strftime("%v").should == @date.strftime('%e-%b-%Y')
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "should be able to show the commercial week" do
+ @date.strftime("%v").should == " 9-APR-2000"
+ @date.strftime("%v").should != @date.strftime('%e-%b-%Y')
+ end
+ end
+
+ # additional conversion specifiers only in Date/DateTime
+ it 'shows the number of milliseconds since epoch' do
+ DateTime.new(1970, 1, 1).strftime('%Q').should == "0"
+ @date.strftime("%Q").should == "955238400000"
+ end
+
+ it "should be able to show a full notation" do
+ @date.strftime("%+").should == "Sun Apr 9 00:00:00 +00:00 2000"
+ @date.strftime("%+").should == @date.strftime('%a %b %e %H:%M:%S %Z %Y')
+ end
+end
diff --git a/spec/ruby/library/date/strptime_spec.rb b/spec/ruby/library/date/strptime_spec.rb
new file mode 100644
index 0000000000..c90721751e
--- /dev/null
+++ b/spec/ruby/library/date/strptime_spec.rb
@@ -0,0 +1,149 @@
+require 'date'
+require_relative '../../spec_helper'
+
+describe "Date#strptime" do
+
+ it "returns January 1, 4713 BCE when given no arguments" do
+ Date.strptime.should == Date.civil(-4712, 1, 1)
+ end
+
+ it "uses the default format when not given a date format" do
+ Date.strptime("2000-04-06").should == Date.civil(2000, 4, 6)
+ Date.civil(2000, 4, 6).strftime.should == Date.civil(2000, 4, 6).to_s
+ end
+
+ it "parses a full day name" do
+ d = Date.today
+ expected_date = Date.commercial(d.cwyear, d.cweek, 4)
+ # strptime assumed week that start on sunday, not monday
+ expected_date += 7 if d.cwday == 7
+ Date.strptime("Thursday", "%A").should == expected_date
+ end
+
+ it "parses a short day name" do
+ d = Date.today
+ expected_date = Date.commercial(d.cwyear, d.cweek, 4)
+ # strptime assumed week that start on sunday, not monday
+ expected_date += 7 if d.cwday == 7
+ Date.strptime("Thu", "%a").should == expected_date
+ end
+
+ it "parses a full month name" do
+ d = Date.today
+ Date.strptime("April", "%B").should == Date.civil(d.year, 4, 1)
+ end
+
+ it "parses a short month name" do
+ d = Date.today
+ Date.strptime("Apr", "%b").should == Date.civil(d.year, 4, 1)
+ Date.strptime("Apr", "%h").should == Date.civil(d.year, 4, 1)
+ end
+
+ it "parses a century" do
+ Date.strptime("06 20", "%y %C").should == Date.civil(2006, 1, 1)
+ end
+
+ it "parses a month day with leading zeroes" do
+ d = Date.today
+ Date.strptime("06", "%d").should == Date.civil(d.year, d.month, 6)
+ end
+
+ it "parses a month day with leading spaces" do
+ d = Date.today
+ Date.strptime(" 6", "%e").should == Date.civil(d.year, d.month, 6)
+ end
+
+ it "parses a commercial year with leading zeroes" do
+ Date.strptime("2000", "%G").should == Date.civil(2000, 1, 3)
+ Date.strptime("2002", "%G").should == Date.civil(2001, 12, 31)
+ end
+
+ it "parses a commercial year with only two digits" do
+ Date.strptime("68", "%g").should == Date.civil(2068, 1, 2)
+ Date.strptime("69", "%g").should == Date.civil(1968, 12, 30)
+ end
+
+ it "parses a year day with leading zeroes" do
+ d = Date.today
+ if Date.gregorian_leap?(Date.today.year)
+ Date.strptime("097", "%j").should == Date.civil(d.year, 4, 6)
+ else
+ Date.strptime("097", "%j").should == Date.civil(d.year, 4, 7)
+ end
+ end
+
+ it "parses a month with leading zeroes" do
+ d = Date.today
+ Date.strptime("04", "%m").should == Date.civil(d.year, 4, 1)
+ end
+
+ it "parses a week number for a week starting on Sunday" do
+ Date.strptime("2010/1", "%Y/%U").should == Date.civil(2010, 1, 3)
+ end
+
+ # See http://redmine.ruby-lang.org/repositories/diff/ruby-19?rev=24500
+ it "parses a week number for a week starting on Monday" do
+ Date.strptime("2010/1", "%Y/%W").should == Date.civil(2010, 1, 4)
+ end
+
+ it "parses a commercial week day" do
+ Date.strptime("2008 1", "%G %u").should == Date.civil(2007, 12, 31)
+ end
+
+ it "parses a commercial week" do
+ d = Date.commercial(Date.today.cwyear,1,1)
+ Date.strptime("1", "%V").should == d
+ Date.strptime("15", "%V").should == Date.commercial(d.cwyear, 15, 1)
+ end
+
+ it "parses a week day" do
+ Date.strptime("2007 4", "%Y %w").should == Date.civil(2007, 1, 4)
+ end
+
+ it "parses a year in YYYY format" do
+ Date.strptime("2007", "%Y").should == Date.civil(2007, 1, 1)
+ end
+
+ it "parses a year in YY format" do
+ Date.strptime("00", "%y").should == Date.civil(2000, 1, 1)
+ end
+
+ ############################
+ # Specs that combine stuff #
+ ############################
+
+ it "parses a full date" do
+ Date.strptime("Thu Apr 6 00:00:00 2000", "%c").should == Date.civil(2000, 4, 6)
+ Date.strptime("Thu Apr 6 00:00:00 2000", "%a %b %e %H:%M:%S %Y").should == Date.civil(2000, 4, 6)
+ end
+
+ it "parses a date with slashes" do
+ Date.strptime("04/06/00", "%D").should == Date.civil(2000, 4, 6)
+ Date.strptime("04/06/00", "%m/%d/%y").should == Date.civil(2000, 4, 6)
+ end
+
+ it "parses a date given as YYYY-MM-DD" do
+ Date.strptime("2000-04-06", "%F").should == Date.civil(2000, 4, 6)
+ Date.strptime("2000-04-06", "%Y-%m-%d").should == Date.civil(2000, 4, 6)
+ end
+
+ it "parses a commercial week" do
+ Date.strptime(" 9-Apr-2000", "%v").should == Date.civil(2000, 4, 9)
+ Date.strptime(" 9-Apr-2000", "%e-%b-%Y").should == Date.civil(2000, 4, 9)
+ end
+
+ it "parses a date given MM/DD/YY" do
+ Date.strptime("04/06/00", "%x").should == Date.civil(2000, 4, 6)
+ Date.strptime("04/06/00", "%m/%d/%y").should == Date.civil(2000, 4, 6)
+ end
+
+ it "parses a date given in full notation" do
+ Date.strptime("Sun Apr 9 00:00:00 +00:00 2000", "%+").should == Date.civil(2000, 4, 9)
+ Date.strptime("Sun Apr 9 00:00:00 +00:00 2000", "%a %b %e %H:%M:%S %Z %Y").should == Date.civil(2000, 4, 9)
+ end
+
+end
+
+describe "Date.strptime" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/succ_spec.rb b/spec/ruby/library/date/succ_spec.rb
new file mode 100644
index 0000000000..c4a902aa63
--- /dev/null
+++ b/spec/ruby/library/date/succ_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#succ" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/sunday_spec.rb b/spec/ruby/library/date/sunday_spec.rb
new file mode 100644
index 0000000000..c3a817fa86
--- /dev/null
+++ b/spec/ruby/library/date/sunday_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#sunday?" do
+ it "should be sunday" do
+ Date.new(2000, 1, 2).sunday?.should be_true
+ end
+end
diff --git a/spec/ruby/library/date/thursday_spec.rb b/spec/ruby/library/date/thursday_spec.rb
new file mode 100644
index 0000000000..74b5f40365
--- /dev/null
+++ b/spec/ruby/library/date/thursday_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#thursday?" do
+ it "should be thursday" do
+ Date.new(2000, 1, 6).thursday?.should be_true
+ end
+end
diff --git a/spec/ruby/library/date/time_to_day_fraction_spec.rb b/spec/ruby/library/date/time_to_day_fraction_spec.rb
new file mode 100644
index 0000000000..e59980e036
--- /dev/null
+++ b/spec/ruby/library/date/time_to_day_fraction_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.time_to_day_fraction" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/to_s_spec.rb b/spec/ruby/library/date/to_s_spec.rb
new file mode 100644
index 0000000000..fe7cb19a46
--- /dev/null
+++ b/spec/ruby/library/date/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#to_s" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/today_spec.rb b/spec/ruby/library/date/today_spec.rb
new file mode 100644
index 0000000000..7c6ebc9cb4
--- /dev/null
+++ b/spec/ruby/library/date/today_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.today" do
+ it "returns a Date object" do
+ Date.today.should be_kind_of Date
+ end
+
+ it "sets Date object to the current date" do
+ today = Date.today
+ now = Time.now
+ (now - today.to_time).should be_close(0.0, 24 * 60 * 60)
+ end
+end
diff --git a/spec/ruby/library/date/tuesday_spec.rb b/spec/ruby/library/date/tuesday_spec.rb
new file mode 100644
index 0000000000..052837b54e
--- /dev/null
+++ b/spec/ruby/library/date/tuesday_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#tuesday?" do
+ it "should be tuesday" do
+ Date.new(2000, 1, 4).tuesday?.should be_true
+ end
+end
diff --git a/spec/ruby/library/date/upto_spec.rb b/spec/ruby/library/date/upto_spec.rb
new file mode 100644
index 0000000000..8745be85b3
--- /dev/null
+++ b/spec/ruby/library/date/upto_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#upto" do
+ it "returns future dates for the default step value" do
+ ds = Date.civil(2008, 10, 11)
+ de = Date.civil(2008, 9, 29)
+ count = 0
+ de.upto(ds) do |d|
+ d.should <= ds
+ d.should >= de
+ count += 1
+ end
+ count.should == 13
+ end
+end
diff --git a/spec/ruby/library/date/valid_civil_spec.rb b/spec/ruby/library/date/valid_civil_spec.rb
new file mode 100644
index 0000000000..00f2c57205
--- /dev/null
+++ b/spec/ruby/library/date/valid_civil_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'shared/valid_civil'
+require 'date'
+
+describe "Date#valid_civil?" do
+
+ it_behaves_like :date_valid_civil?, :valid_civil?
+
+end
diff --git a/spec/ruby/library/date/valid_commercial_spec.rb b/spec/ruby/library/date/valid_commercial_spec.rb
new file mode 100644
index 0000000000..7e96782b6b
--- /dev/null
+++ b/spec/ruby/library/date/valid_commercial_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'shared/valid_commercial'
+require 'date'
+
+describe "Date#valid_commercial?" do
+
+ it_behaves_like :date_valid_commercial?, :valid_commercial?
+end
diff --git a/spec/ruby/library/date/valid_date_spec.rb b/spec/ruby/library/date/valid_date_spec.rb
new file mode 100644
index 0000000000..f12a71d966
--- /dev/null
+++ b/spec/ruby/library/date/valid_date_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/valid_civil'
+require 'date'
+
+describe "Date#valid_date?" do
+ it_behaves_like :date_valid_civil?, :valid_date?
+end
diff --git a/spec/ruby/library/date/valid_jd_spec.rb b/spec/ruby/library/date/valid_jd_spec.rb
new file mode 100644
index 0000000000..aecaaabcf4
--- /dev/null
+++ b/spec/ruby/library/date/valid_jd_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'shared/valid_jd'
+require 'date'
+
+describe "Date.valid_jd?" do
+
+ it_behaves_like :date_valid_jd?, :valid_jd?
+
+end
diff --git a/spec/ruby/library/date/valid_ordinal_spec.rb b/spec/ruby/library/date/valid_ordinal_spec.rb
new file mode 100644
index 0000000000..58d548c704
--- /dev/null
+++ b/spec/ruby/library/date/valid_ordinal_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'shared/valid_ordinal'
+require 'date'
+
+describe "Date.valid_ordinal?" do
+
+ it_behaves_like :date_valid_ordinal?, :valid_ordinal?
+
+end
diff --git a/spec/ruby/library/date/valid_time_spec.rb b/spec/ruby/library/date/valid_time_spec.rb
new file mode 100644
index 0000000000..87c239bedb
--- /dev/null
+++ b/spec/ruby/library/date/valid_time_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.valid_time?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/wday_spec.rb b/spec/ruby/library/date/wday_spec.rb
new file mode 100644
index 0000000000..303905ed35
--- /dev/null
+++ b/spec/ruby/library/date/wday_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#wday" do
+ it "returns the week day as a number starting with Sunday as 0" do
+ w = Date.new(2000, 1, 1).wday
+ w.should == 6
+ end
+end
diff --git a/spec/ruby/library/date/wednesday_spec.rb b/spec/ruby/library/date/wednesday_spec.rb
new file mode 100644
index 0000000000..e80ec23dd2
--- /dev/null
+++ b/spec/ruby/library/date/wednesday_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#wednesday?" do
+ it "should be wednesday" do
+ Date.new(2000, 1, 5).wednesday?.should be_true
+ end
+end
diff --git a/spec/ruby/library/date/yday_spec.rb b/spec/ruby/library/date/yday_spec.rb
new file mode 100644
index 0000000000..cfb174a4c2
--- /dev/null
+++ b/spec/ruby/library/date/yday_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#yday" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/date/year_spec.rb b/spec/ruby/library/date/year_spec.rb
new file mode 100644
index 0000000000..90d14e5a39
--- /dev/null
+++ b/spec/ruby/library/date/year_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date#year" do
+ it "returns the year" do
+ y = Date.new(2000, 7, 1).year
+ y.should == 2000
+ end
+end
diff --git a/spec/ruby/library/date/zone_to_diff_spec.rb b/spec/ruby/library/date/zone_to_diff_spec.rb
new file mode 100644
index 0000000000..354daaaee4
--- /dev/null
+++ b/spec/ruby/library/date/zone_to_diff_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "Date.zone_to_diff" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/_strptime_spec.rb b/spec/ruby/library/datetime/_strptime_spec.rb
new file mode 100644
index 0000000000..abec26ff9f
--- /dev/null
+++ b/spec/ruby/library/datetime/_strptime_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime._strptime" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/add_spec.rb b/spec/ruby/library/datetime/add_spec.rb
new file mode 100644
index 0000000000..20288e2105
--- /dev/null
+++ b/spec/ruby/library/datetime/add_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#+" do
+ it "is able to add sub-millisecond precision values" do
+ datetime = DateTime.new(2017)
+ (datetime + 0.00001001).to_time.usec.should == 864864
+ end
+end
diff --git a/spec/ruby/library/datetime/civil_spec.rb b/spec/ruby/library/datetime/civil_spec.rb
new file mode 100644
index 0000000000..fb6f67f16d
--- /dev/null
+++ b/spec/ruby/library/datetime/civil_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.civil" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/commercial_spec.rb b/spec/ruby/library/datetime/commercial_spec.rb
new file mode 100644
index 0000000000..ad97b2a80e
--- /dev/null
+++ b/spec/ruby/library/datetime/commercial_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.commercial" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/hour_spec.rb b/spec/ruby/library/datetime/hour_spec.rb
new file mode 100644
index 0000000000..8efd5f92f0
--- /dev/null
+++ b/spec/ruby/library/datetime/hour_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#hour" do
+ it "returns 0 if no argument is passed" do
+ DateTime.new.hour.should == 0
+ end
+
+ it "returns the hour given as argument" do
+ new_datetime(hour: 5).hour.should == 5
+ end
+
+ it "adds 24 to negative hours" do
+ new_datetime(hour: -10).hour.should == 14
+ end
+
+ it "raises an error for Rational" do
+ -> { new_datetime(hour: 1 + Rational(1,2)) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error for Float" do
+ -> { new_datetime(hour: 1.5).hour }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error for Rational" do
+ -> { new_datetime(day: 1 + Rational(1,2)) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error, when the hour is smaller than -24" do
+ -> { new_datetime(hour: -25) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error, when the hour is larger than 24" do
+ -> { new_datetime(hour: 25) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error for hour fractions smaller than -24" do
+ -> { new_datetime(hour: -24 - Rational(1,2)) }.should(
+ raise_error(ArgumentError))
+ end
+
+ it "adds 1 to day, when 24 hours given" do
+ d = new_datetime day: 1, hour: 24
+ d.hour.should == 0
+ d.day.should == 2
+ end
+end
diff --git a/spec/ruby/library/datetime/httpdate_spec.rb b/spec/ruby/library/datetime/httpdate_spec.rb
new file mode 100644
index 0000000000..68e45cbff0
--- /dev/null
+++ b/spec/ruby/library/datetime/httpdate_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.httpdate" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/iso8601_spec.rb b/spec/ruby/library/datetime/iso8601_spec.rb
new file mode 100644
index 0000000000..457881277a
--- /dev/null
+++ b/spec/ruby/library/datetime/iso8601_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.iso8601" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "DateTime#iso8601" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/jd_spec.rb b/spec/ruby/library/datetime/jd_spec.rb
new file mode 100644
index 0000000000..1e783f5af4
--- /dev/null
+++ b/spec/ruby/library/datetime/jd_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.jd" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/jisx0301_spec.rb b/spec/ruby/library/datetime/jisx0301_spec.rb
new file mode 100644
index 0000000000..ab26aa2d73
--- /dev/null
+++ b/spec/ruby/library/datetime/jisx0301_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.jisx0301" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "DateTime#jisx0301" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/min_spec.rb b/spec/ruby/library/datetime/min_spec.rb
new file mode 100644
index 0000000000..a1eaa214cb
--- /dev/null
+++ b/spec/ruby/library/datetime/min_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/min'
+
+describe "DateTime.min" do
+ it_behaves_like :datetime_min, :min
+end
diff --git a/spec/ruby/library/datetime/minute_spec.rb b/spec/ruby/library/datetime/minute_spec.rb
new file mode 100644
index 0000000000..acdfeda345
--- /dev/null
+++ b/spec/ruby/library/datetime/minute_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/min'
+
+describe "DateTime.minute" do
+ it_behaves_like :datetime_min, :minute
+end
diff --git a/spec/ruby/library/datetime/new_offset_spec.rb b/spec/ruby/library/datetime/new_offset_spec.rb
new file mode 100644
index 0000000000..bc0988f32d
--- /dev/null
+++ b/spec/ruby/library/datetime/new_offset_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#new_offset" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/new_spec.rb b/spec/ruby/library/datetime/new_spec.rb
new file mode 100644
index 0000000000..6a4dced384
--- /dev/null
+++ b/spec/ruby/library/datetime/new_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.new" do
+ it "sets all values to default if passed no arguments" do
+ d = DateTime.new
+ d.year.should == -4712
+ d.month.should == 1
+ d.day.should == 1
+ d.hour.should == 0
+ d.min.should == 0
+ d.sec.should == 0
+ d.sec_fraction.should == 0
+ d.offset.should == 0
+ end
+
+ it "takes the first argument as year" do
+ DateTime.new(2011).year.should == 2011
+ end
+
+ it "takes the second argument as month" do
+ DateTime.new(2011, 2).month.should == 2
+ end
+
+ it "takes the third argument as day" do
+ DateTime.new(2011, 2, 3).day.should == 3
+ end
+
+ it "takes the forth argument as hour" do
+ DateTime.new(2011, 2, 3, 4).hour.should == 4
+ end
+
+ it "takes the fifth argument as minute" do
+ DateTime.new(1, 2, 3, 4, 5).min.should == 5
+ end
+
+ it "takes the sixth argument as second" do
+ DateTime.new(1, 2, 3, 4, 5, 6).sec.should == 6
+ end
+
+ it "takes the seventh argument as an offset" do
+ DateTime.new(1, 2, 3, 4, 5, 6, 0.7).offset.should == 0.7
+ end
+
+ it "takes the eighth argument as the date of calendar reform" do
+ DateTime.new(1, 2, 3, 4, 5, 6, 0.7, Date::ITALY).start().should == Date::ITALY
+ end
+
+ it "raises an error on invalid arguments" do
+ -> { new_datetime(minute: 999) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/datetime/now_spec.rb b/spec/ruby/library/datetime/now_spec.rb
new file mode 100644
index 0000000000..9f22153c15
--- /dev/null
+++ b/spec/ruby/library/datetime/now_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.now" do
+ it "creates an instance of DateTime" do
+ DateTime.now.should be_an_instance_of(DateTime)
+ end
+
+ it "sets the current date" do
+ (DateTime.now - Date.today).to_f.should be_close(0.0, TIME_TOLERANCE)
+ end
+
+ it "sets the current time" do
+ dt = DateTime.now
+ now = Time.now
+ (dt.to_time - now).should be_close(0.0, TIME_TOLERANCE)
+ end
+
+ it "grabs the local timezone" do
+ with_timezone("PDT", -8) do
+ dt = DateTime.now
+ dt.zone.should == "-08:00"
+ end
+ end
+end
diff --git a/spec/ruby/library/datetime/offset_spec.rb b/spec/ruby/library/datetime/offset_spec.rb
new file mode 100644
index 0000000000..e25e7a0f35
--- /dev/null
+++ b/spec/ruby/library/datetime/offset_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#offset" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/ordinal_spec.rb b/spec/ruby/library/datetime/ordinal_spec.rb
new file mode 100644
index 0000000000..64b154ee9b
--- /dev/null
+++ b/spec/ruby/library/datetime/ordinal_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.ordinal" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/parse_spec.rb b/spec/ruby/library/datetime/parse_spec.rb
new file mode 100644
index 0000000000..e9bf4e2ed1
--- /dev/null
+++ b/spec/ruby/library/datetime/parse_spec.rb
@@ -0,0 +1,127 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.parse" do
+
+ it "parses a day name into a DateTime object" do
+ d = DateTime.parse("friday")
+ d.should == DateTime.commercial(d.cwyear, d.cweek, 5)
+ end
+
+ it "parses a month name into a DateTime object" do
+ d = DateTime.parse("october")
+ d.should == DateTime.civil(Date.today.year, 10)
+ end
+
+ it "parses a month day into a DateTime object" do
+ d = DateTime.parse("5th")
+ d.should == DateTime.civil(Date.today.year, Date.today.month, 5)
+ end
+
+ # Specs using numbers
+ it "throws an argument error for a single digit" do
+ ->{ DateTime.parse("1") }.should raise_error(ArgumentError)
+ end
+
+ it "parses DD as month day number" do
+ d = DateTime.parse("10")
+ d.should == DateTime.civil(Date.today.year, Date.today.month, 10)
+ end
+
+ it "parses DDD as year day number" do
+ d = DateTime.parse("100")
+ if DateTime.gregorian_leap?(Date.today.year)
+ d.should == DateTime.civil(Date.today.year, 4, 9)
+ else
+ d.should == DateTime.civil(Date.today.year, 4, 10)
+ end
+ end
+
+ it "parses MMDD as month and day" do
+ d = DateTime.parse("1108")
+ d.should == DateTime.civil(Date.today.year, 11, 8)
+ end
+
+ it "parses YYYYMMDD as year, month and day" do
+ d = DateTime.parse("20121108")
+ d.should == DateTime.civil(2012, 11, 8)
+ end
+
+ describe "YYYY-MM-DDTHH:MM:SS format" do
+ it "parses YYYY-MM-DDTHH:MM:SS into a DateTime object" do
+ d = DateTime.parse("2012-11-08T15:43:59")
+ d.should == DateTime.civil(2012, 11, 8, 15, 43, 59)
+ end
+
+ it "throws an argument error for invalid month values" do
+ ->{DateTime.parse("2012-13-08T15:43:59")}.should raise_error(ArgumentError)
+ end
+
+ it "throws an argument error for invalid day values" do
+ ->{DateTime.parse("2012-12-32T15:43:59")}.should raise_error(ArgumentError)
+ end
+
+ it "throws an argument error for invalid hour values" do
+ ->{DateTime.parse("2012-12-31T25:43:59")}.should raise_error(ArgumentError)
+ end
+
+ it "throws an argument error for invalid minute values" do
+ ->{DateTime.parse("2012-12-31T25:43:59")}.should raise_error(ArgumentError)
+ end
+
+ it "throws an argument error for invalid second values" do
+ ->{DateTime.parse("2012-11-08T15:43:61")}.should raise_error(ArgumentError)
+ end
+
+ end
+
+ it "parses YYDDD as year and day number in 1969--2068" do
+ d = DateTime.parse("10100")
+ d.should == DateTime.civil(2010, 4, 10)
+ end
+
+ it "parses YYMMDD as year, month and day in 1969--2068" do
+ d = DateTime.parse("201023")
+ d.should == DateTime.civil(2020, 10, 23)
+ end
+
+ it "parses YYYYDDD as year and day number" do
+ d = DateTime.parse("1910100")
+ d.should == DateTime.civil(1910, 4, 10)
+ end
+
+ it "parses YYYYMMDD as year, month and day number" do
+ d = DateTime.parse("19101101")
+ d.should == DateTime.civil(1910, 11, 1)
+ end
+end
+
+describe "DateTime.parse(.)" do
+ it "parses YYYY.MM.DD into a DateTime object" do
+ d = DateTime.parse("2007.10.01")
+ d.year.should == 2007
+ d.month.should == 10
+ d.day.should == 1
+ end
+
+ it "parses DD.MM.YYYY into a DateTime object" do
+ d = DateTime.parse("10.01.2007")
+ d.year.should == 2007
+ d.month.should == 1
+ d.day.should == 10
+ end
+
+ it "parses YY.MM.DD into a DateTime object using the year 20YY" do
+ d = DateTime.parse("10.01.07")
+ d.year.should == 2010
+ d.month.should == 1
+ d.day.should == 7
+ end
+
+ it "parses YY.MM.DD using the year digits as 20YY when given true as additional argument" do
+ d = DateTime.parse("10.01.07", true)
+ d.year.should == 2010
+ d.month.should == 1
+ d.day.should == 7
+ end
+end
diff --git a/spec/ruby/library/datetime/rfc2822_spec.rb b/spec/ruby/library/datetime/rfc2822_spec.rb
new file mode 100644
index 0000000000..70bfca60b4
--- /dev/null
+++ b/spec/ruby/library/datetime/rfc2822_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.rfc2822" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/rfc3339_spec.rb b/spec/ruby/library/datetime/rfc3339_spec.rb
new file mode 100644
index 0000000000..f870a5f63b
--- /dev/null
+++ b/spec/ruby/library/datetime/rfc3339_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.rfc3339" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "DateTime#rfc3339" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/rfc822_spec.rb b/spec/ruby/library/datetime/rfc822_spec.rb
new file mode 100644
index 0000000000..0cd0aacc19
--- /dev/null
+++ b/spec/ruby/library/datetime/rfc822_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.rfc822" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/sec_fraction_spec.rb b/spec/ruby/library/datetime/sec_fraction_spec.rb
new file mode 100644
index 0000000000..40383a8ca4
--- /dev/null
+++ b/spec/ruby/library/datetime/sec_fraction_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#sec_fraction" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/sec_spec.rb b/spec/ruby/library/datetime/sec_spec.rb
new file mode 100644
index 0000000000..f681283c8e
--- /dev/null
+++ b/spec/ruby/library/datetime/sec_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/sec'
+
+describe "DateTime.sec" do
+ it_behaves_like :datetime_sec, :sec
+end
diff --git a/spec/ruby/library/datetime/second_fraction_spec.rb b/spec/ruby/library/datetime/second_fraction_spec.rb
new file mode 100644
index 0000000000..d5393149ba
--- /dev/null
+++ b/spec/ruby/library/datetime/second_fraction_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#second_fraction" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/second_spec.rb b/spec/ruby/library/datetime/second_spec.rb
new file mode 100644
index 0000000000..545c3f9109
--- /dev/null
+++ b/spec/ruby/library/datetime/second_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/sec'
+
+describe "DateTime#second" do
+ it_behaves_like :datetime_sec, :second
+end
diff --git a/spec/ruby/library/datetime/shared/min.rb b/spec/ruby/library/datetime/shared/min.rb
new file mode 100644
index 0000000000..a35b839281
--- /dev/null
+++ b/spec/ruby/library/datetime/shared/min.rb
@@ -0,0 +1,40 @@
+require 'date'
+
+describe :datetime_min, shared: true do
+ it "returns 0 if no argument is passed" do
+ DateTime.new.send(@method).should == 0
+ end
+
+ it "returns the minute passed as argument" do
+ new_datetime(minute: 5).send(@method).should == 5
+ end
+
+ it "adds 60 to negative minutes" do
+ new_datetime(minute: -20).send(@method).should == 40
+ end
+
+ it "raises an error for Rational" do
+ -> { new_datetime minute: 5 + Rational(1,2) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error for Float" do
+ -> { new_datetime minute: 5.5 }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error for Rational" do
+ -> { new_datetime(hour: 2 + Rational(1,2)) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error, when the minute is smaller than -60" do
+ -> { new_datetime(minute: -61) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error, when the minute is greater or equal than 60" do
+ -> { new_datetime(minute: 60) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error for minute fractions smaller than -60" do
+ -> { new_datetime(minute: -60 - Rational(1,2))}.should(
+ raise_error(ArgumentError))
+ end
+end
diff --git a/spec/ruby/library/datetime/shared/sec.rb b/spec/ruby/library/datetime/shared/sec.rb
new file mode 100644
index 0000000000..60009213aa
--- /dev/null
+++ b/spec/ruby/library/datetime/shared/sec.rb
@@ -0,0 +1,45 @@
+require 'date'
+
+describe :datetime_sec, shared: true do
+ it "returns 0 seconds if passed no arguments" do
+ d = DateTime.new
+ d.send(@method).should == 0
+ end
+
+ it "returns the seconds passed in the arguments" do
+ new_datetime(second: 5).send(@method).should == 5
+ end
+
+ it "adds 60 to negative values" do
+ new_datetime(second: -20).send(@method).should == 40
+ end
+
+ it "returns the absolute value of a Rational" do
+ new_datetime(second: 5 + Rational(1,2)).send(@method).should == 5
+ end
+
+ it "returns the absolute value of a float" do
+ new_datetime(second: 5.5).send(@method).should == 5
+ end
+
+ it "raises an error when minute is given as a rational" do
+ -> { new_datetime(minute: 5 + Rational(1,2)) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error, when the second is smaller than -60" do
+ -> { new_datetime(second: -61) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error, when the second is greater or equal than 60" do
+ -> { new_datetime(second: 60) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error for second fractions smaller than -60" do
+ -> { new_datetime(second: -60 - Rational(1,2))}.should(
+ raise_error(ArgumentError))
+ end
+
+ it "takes a second fraction near 60" do
+ new_datetime(second: 59 + Rational(1,2)).send(@method).should == 59
+ end
+end
diff --git a/spec/ruby/library/datetime/strftime_spec.rb b/spec/ruby/library/datetime/strftime_spec.rb
new file mode 100644
index 0000000000..725bcafb0d
--- /dev/null
+++ b/spec/ruby/library/datetime/strftime_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require 'date'
+require_relative '../../shared/time/strftime_for_date'
+require_relative '../../shared/time/strftime_for_time'
+
+describe "DateTime#strftime" do
+ before :all do
+ @new_date = -> y, m, d { DateTime.civil(y,m,d) }
+ @new_time = -> *args { DateTime.civil(*args) }
+ @new_time_in_zone = -> zone, offset, *args {
+ y, m, d, h, min, s = args
+ DateTime.new(y, m||1, d||1, h||0, min||0, s||0, Rational(offset, 24))
+ }
+ @new_time_with_offset = -> y, m, d, h, min, s, offset {
+ DateTime.new(y,m,d,h,min,s, Rational(offset, 86_400))
+ }
+
+ @time = DateTime.civil(2001, 2, 3, 4, 5, 6)
+ end
+
+ it_behaves_like :strftime_date, :strftime
+ it_behaves_like :strftime_time, :strftime
+
+ # Differences with Time
+ it "should be able to print the datetime with no argument" do
+ @time.strftime.should == "2001-02-03T04:05:06+00:00"
+ @time.strftime.should == @time.to_s
+ end
+
+ # %Z is %:z for Date/DateTime
+ it "should be able to show the timezone with a : separator" do
+ @time.strftime("%Z").should == "+00:00"
+ end
+
+ # %v is %e-%b-%Y for Date/DateTime
+ ruby_version_is ""..."3.1" do
+ it "should be able to show the commercial week" do
+ @time.strftime("%v").should == " 3-Feb-2001"
+ @time.strftime("%v").should == @time.strftime('%e-%b-%Y')
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "should be able to show the commercial week" do
+ @time.strftime("%v").should == " 3-FEB-2001"
+ @time.strftime("%v").should != @time.strftime('%e-%b-%Y')
+ end
+ end
+
+ # additional conversion specifiers only in Date/DateTime
+ it 'shows the number of milliseconds since epoch' do
+ DateTime.new(1970, 1, 1, 0, 0, 0).strftime("%Q").should == "0"
+ @time.strftime("%Q").should == "981173106000"
+ DateTime.civil(2001, 2, 3, 4, 5, Rational(6123, 1000)).strftime("%Q").should == "981173106123"
+ end
+
+ it "should be able to show a full notation" do
+ @time.strftime("%+").should == "Sat Feb 3 04:05:06 +00:00 2001"
+ @time.strftime("%+").should == @time.strftime('%a %b %e %H:%M:%S %Z %Y')
+ end
+end
diff --git a/spec/ruby/library/datetime/strptime_spec.rb b/spec/ruby/library/datetime/strptime_spec.rb
new file mode 100644
index 0000000000..d1e83550e4
--- /dev/null
+++ b/spec/ruby/library/datetime/strptime_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.strptime" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/subtract_spec.rb b/spec/ruby/library/datetime/subtract_spec.rb
new file mode 100644
index 0000000000..ba01f4eff6
--- /dev/null
+++ b/spec/ruby/library/datetime/subtract_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#-" do
+ it "is able to subtract sub-millisecond precision values" do
+ date = DateTime.new(2017)
+ diff = Rational(123456789, 24*60*60*1000*1000)
+ ((date + diff) - date).should == diff
+ (date - (date + diff)).should == -diff
+ (date - (date - diff)).should == diff
+ ((date - diff) - date).should == -diff
+ end
+
+ it "correctly calculates sub-millisecond time differences" do #5493
+ dt1 = DateTime.new(2018, 1, 1, 0, 0, 30)
+ dt2 = DateTime.new(2018, 1, 1, 0, 1, 29.000001)
+ ((dt2 - dt1) * 24 * 60 * 60).should == 59.000001
+ end
+end
diff --git a/spec/ruby/library/datetime/to_date_spec.rb b/spec/ruby/library/datetime/to_date_spec.rb
new file mode 100644
index 0000000000..48c05e7fed
--- /dev/null
+++ b/spec/ruby/library/datetime/to_date_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#to_date" do
+ it "returns an instance of Date" do
+ dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00')
+ dt.to_date.should be_kind_of(Date)
+ end
+
+ it "maintains the same year" do
+ dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00')
+ dt.to_date.year.should == dt.year
+ end
+
+ it "maintains the same month" do
+ dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00')
+ dt.to_date.mon.should == dt.mon
+ end
+
+ it "maintains the same day" do
+ dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00')
+ dt.to_date.day.should == dt.day
+ end
+
+ it "maintains the same mday" do
+ dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00')
+ dt.to_date.mday.should == dt.mday
+ end
+
+ it "maintains the same julian day regardless of local time or zone" do
+ dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00')
+
+ with_timezone("Pacific/Pago_Pago", -11) do
+ dt.to_date.jd.should == dt.jd
+ end
+ end
+end
diff --git a/spec/ruby/library/datetime/to_datetime_spec.rb b/spec/ruby/library/datetime/to_datetime_spec.rb
new file mode 100644
index 0000000000..95ee29268f
--- /dev/null
+++ b/spec/ruby/library/datetime/to_datetime_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#to_datetime" do
+ it "returns itself" do
+ dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00')
+ dt.to_datetime.should == dt
+ end
+end
diff --git a/spec/ruby/library/datetime/to_s_spec.rb b/spec/ruby/library/datetime/to_s_spec.rb
new file mode 100644
index 0000000000..175fb807f4
--- /dev/null
+++ b/spec/ruby/library/datetime/to_s_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#to_s" do
+ it "returns a new String object" do
+ dt = DateTime.new(2012, 12, 24, 1, 2, 3, "+03:00")
+ dt.to_s.should be_kind_of(String)
+ end
+
+ it "maintains timezone regardless of local time" do
+ dt = DateTime.new(2012, 12, 24, 1, 2, 3, "+03:00")
+
+ with_timezone("Pacific/Pago_Pago", -11) do
+ dt.to_s.should == "2012-12-24T01:02:03+03:00"
+ end
+ end
+end
diff --git a/spec/ruby/library/datetime/to_time_spec.rb b/spec/ruby/library/datetime/to_time_spec.rb
new file mode 100644
index 0000000000..95eca864da
--- /dev/null
+++ b/spec/ruby/library/datetime/to_time_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#to_time" do
+ it "yields a new Time object" do
+ DateTime.now.to_time.should be_kind_of(Time)
+ end
+
+ it "returns a Time representing the same instant" do
+ datetime = DateTime.civil(2012, 12, 31, 23, 58, 59)
+ time = datetime.to_time.utc
+
+ time.year.should == 2012
+ time.month.should == 12
+ time.day.should == 31
+ time.hour.should == 23
+ time.min.should == 58
+ time.sec.should == 59
+ end
+
+ date_version = defined?(Date::VERSION) ? Date::VERSION : '0.0.0'
+ version_is(date_version, '3.2.3') do
+ it "returns a Time representing the same instant before Gregorian" do
+ datetime = DateTime.civil(1582, 10, 4, 23, 58, 59)
+ time = datetime.to_time.utc
+ time.year.should == 1582
+ time.month.should == 10
+ time.day.should == 14
+ time.hour.should == 23
+ time.min.should == 58
+ time.sec.should == 59
+ end
+ end
+
+ it "preserves the same time regardless of local time or zone" do
+ date = DateTime.new(2012, 12, 24, 12, 23, 00, '+03:00')
+
+ with_timezone("Pacific/Pago_Pago", -11) do
+ time = date.to_time
+
+ time.utc_offset.should == 3 * 3600
+ time.year.should == date.year
+ time.mon.should == date.mon
+ time.day.should == date.day
+ time.hour.should == date.hour
+ time.min.should == date.min
+ time.sec.should == date.sec
+ end
+ end
+end
diff --git a/spec/ruby/library/datetime/xmlschema_spec.rb b/spec/ruby/library/datetime/xmlschema_spec.rb
new file mode 100644
index 0000000000..42832631ed
--- /dev/null
+++ b/spec/ruby/library/datetime/xmlschema_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime.xmlschema" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "DateTime#xmlschema" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/datetime/zone_spec.rb b/spec/ruby/library/datetime/zone_spec.rb
new file mode 100644
index 0000000000..b2c10b4b3b
--- /dev/null
+++ b/spec/ruby/library/datetime/zone_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'date'
+
+describe "DateTime#zone" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/delegate/delegate_class/instance_method_spec.rb b/spec/ruby/library/delegate/delegate_class/instance_method_spec.rb
new file mode 100644
index 0000000000..16bf8d734c
--- /dev/null
+++ b/spec/ruby/library/delegate/delegate_class/instance_method_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "DelegateClass.instance_method" do
+ before :all do
+ @klass = DelegateSpecs::DelegateClass
+ @obj = @klass.new(DelegateSpecs::Simple.new)
+ end
+
+ it "returns a method object for public instance methods of the delegated class" do
+ m = @klass.instance_method(:pub)
+ m.should be_an_instance_of(UnboundMethod)
+ m.bind(@obj).call.should == :foo
+ end
+
+ it "returns a method object for protected instance methods of the delegated class" do
+ m = @klass.instance_method(:prot)
+ m.should be_an_instance_of(UnboundMethod)
+ m.bind(@obj).call.should == :protected
+ end
+
+ it "raises a NameError for a private instance methods of the delegated class" do
+ -> {
+ @klass.instance_method(:priv)
+ }.should raise_error(NameError)
+ end
+
+ it "returns a method object for public instance methods of the DelegateClass class" do
+ m = @klass.instance_method(:extra)
+ m.should be_an_instance_of(UnboundMethod)
+ m.bind(@obj).call.should == :cheese
+ end
+
+ it "returns a method object for protected instance methods of the DelegateClass class" do
+ m = @klass.instance_method(:extra_protected)
+ m.should be_an_instance_of(UnboundMethod)
+ m.bind(@obj).call.should == :baz
+ end
+
+ it "returns a method object for private instance methods of the DelegateClass class" do
+ m = @klass.instance_method(:extra_private)
+ m.should be_an_instance_of(UnboundMethod)
+ m.bind(@obj).call.should == :bar
+ end
+
+ it "raises a NameError for an invalid method name" do
+ -> {
+ @klass.instance_method(:invalid_and_silly_method_name)
+ }.should raise_error(NameError)
+ end
+
+end
diff --git a/spec/ruby/library/delegate/delegate_class/instance_methods_spec.rb b/spec/ruby/library/delegate/delegate_class/instance_methods_spec.rb
new file mode 100644
index 0000000000..6012ff72de
--- /dev/null
+++ b/spec/ruby/library/delegate/delegate_class/instance_methods_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "DelegateClass.instance_methods" do
+ before :all do
+ @methods = DelegateSpecs::DelegateClass.instance_methods
+ end
+
+ it "includes all public methods of the delegated class" do
+ @methods.should include :pub
+ end
+
+ it "includes all protected methods of the delegated class" do
+ @methods.should include :prot
+ end
+
+ it "includes instance methods of the DelegateClass class" do
+ @methods.should include :extra
+ @methods.should include :extra_protected
+ end
+
+ it "does not include private methods" do
+ @methods.should_not include :priv
+ @methods.should_not include :extra_private
+ end
+end
diff --git a/spec/ruby/library/delegate/delegate_class/private_instance_methods_spec.rb b/spec/ruby/library/delegate/delegate_class/private_instance_methods_spec.rb
new file mode 100644
index 0000000000..06b2115cc5
--- /dev/null
+++ b/spec/ruby/library/delegate/delegate_class/private_instance_methods_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "DelegateClass.private_instance_methods" do
+ before :all do
+ @methods = DelegateSpecs::DelegateClass.private_instance_methods
+ end
+
+ it "does not include any instance methods of the delegated class" do
+ @methods.should_not include :pub
+ @methods.should_not include :prot
+ @methods.should_not include :priv # since these are not forwarded...
+ end
+
+ it "includes private instance methods of the DelegateClass class" do
+ @methods.should include :extra_private
+ end
+
+ it "does not include public or protected instance methods of the DelegateClass class" do
+ @methods.should_not include :extra
+ @methods.should_not include :extra_protected
+ end
+end
diff --git a/spec/ruby/library/delegate/delegate_class/protected_instance_methods_spec.rb b/spec/ruby/library/delegate/delegate_class/protected_instance_methods_spec.rb
new file mode 100644
index 0000000000..ac6659ec1e
--- /dev/null
+++ b/spec/ruby/library/delegate/delegate_class/protected_instance_methods_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "DelegateClass.protected_instance_methods" do
+ before :all do
+ @methods = DelegateSpecs::DelegateClass.protected_instance_methods
+ end
+
+ it "does not include public methods of the delegated class" do
+ @methods.should_not include :pub
+ end
+
+ it "includes the protected methods of the delegated class" do
+ @methods.should include :prot
+ end
+
+ it "includes protected instance methods of the DelegateClass class" do
+ @methods.should include :extra_protected
+ end
+
+ it "does not include public instance methods of the DelegateClass class" do
+ @methods.should_not include :extra
+ end
+
+ it "does not include private methods" do
+ @methods.should_not include :priv
+ @methods.should_not include :extra_private
+ end
+end
diff --git a/spec/ruby/library/delegate/delegate_class/public_instance_methods_spec.rb b/spec/ruby/library/delegate/delegate_class/public_instance_methods_spec.rb
new file mode 100644
index 0000000000..6c0d9bcab1
--- /dev/null
+++ b/spec/ruby/library/delegate/delegate_class/public_instance_methods_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "DelegateClass.public_instance_methods" do
+ before :all do
+ @methods = DelegateSpecs::DelegateClass.public_instance_methods
+ end
+
+ it "includes all public methods of the delegated class" do
+ @methods.should include :pub
+ end
+
+ it "does not include the protected methods of the delegated class" do
+ @methods.should_not include :prot
+ end
+
+ it "includes public instance methods of the DelegateClass class" do
+ @methods.should include :extra
+ end
+
+ it "does not include private methods" do
+ @methods.should_not include :priv
+ @methods.should_not include :extra_private
+ end
+end
diff --git a/spec/ruby/library/delegate/delegate_class/respond_to_missing_spec.rb b/spec/ruby/library/delegate/delegate_class/respond_to_missing_spec.rb
new file mode 100644
index 0000000000..729cfc96c6
--- /dev/null
+++ b/spec/ruby/library/delegate/delegate_class/respond_to_missing_spec.rb
@@ -0,0 +1,23 @@
+require 'delegate'
+
+describe "DelegateClass#respond_to_missing?" do
+ it "is used for respond_to? behavior of late-bound delegated methods" do
+ # From jruby/jruby#3151:
+ # DelegateClass subtracts Delegate's public API from the target class's instance_methods
+ # to determine which methods to eagerly delegate. If respond_to_missing? shows up in
+ # instance_methods, it will get delegated and skip the delegate-aware implementation
+ # in Delegate. Any methods that must be delegated through method_missing, like methods
+ # defined after the DelegateClass is created, will fail to dispatch properly.
+
+ cls = Class.new
+ dcls = DelegateClass(cls)
+ cdcls = Class.new(dcls)
+ cdcls_obj = cdcls.new(cls.new)
+
+ cdcls_obj.respond_to?(:foo).should == false
+
+ cls.class_eval { def foo; end }
+
+ cdcls_obj.respond_to?(:foo).should == true
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/case_compare_spec.rb b/spec/ruby/library/delegate/delegator/case_compare_spec.rb
new file mode 100644
index 0000000000..b62397cecf
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/case_compare_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#===" do
+ it "is delegated" do
+ base = mock('base')
+ delegator = DelegateSpecs::Delegator.new(base)
+ base.should_receive(:===).with(42).and_return(:foo)
+ (delegator === 42).should == :foo
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/compare_spec.rb b/spec/ruby/library/delegate/delegator/compare_spec.rb
new file mode 100644
index 0000000000..7dee5c2fb5
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/compare_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#<=>" do
+ it "is delegated" do
+ base = mock('base')
+ delegator = DelegateSpecs::Delegator.new(base)
+ base.should_receive(:<=>).with(42).and_return(1)
+ (delegator <=> 42).should == 1
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/complement_spec.rb b/spec/ruby/library/delegate/delegator/complement_spec.rb
new file mode 100644
index 0000000000..10cb37c7ea
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/complement_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#~" do
+ it "is delegated" do
+ base = mock('base')
+ delegator = DelegateSpecs::Delegator.new(base)
+ base.should_receive(:~).and_return(:foo)
+ (~delegator).should == :foo
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/eql_spec.rb b/spec/ruby/library/delegate/delegator/eql_spec.rb
new file mode 100644
index 0000000000..34f56f44c9
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/eql_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#eql?" do
+ it "returns true when compared with same delegator" do
+ base = mock('base')
+ delegator = DelegateSpecs::Delegator.new(base)
+
+ delegator.eql?(delegator).should be_true
+ end
+
+ it "returns true when compared with the inner object" do
+ base = mock('base')
+ delegator = DelegateSpecs::Delegator.new(base)
+
+ delegator.eql?(base).should be_true
+ end
+
+ it "returns false when compared with the delegator with other object" do
+ base = mock('base')
+ other = mock('other')
+ delegator0 = DelegateSpecs::Delegator.new(base)
+ delegator1 = DelegateSpecs::Delegator.new(other)
+
+ delegator0.eql?(delegator1).should be_false
+ end
+
+ it "returns false when compared with the other object" do
+ base = mock('base')
+ other = mock('other')
+ delegator = DelegateSpecs::Delegator.new(base)
+
+ delegator.eql?(other).should be_false
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/equal_spec.rb b/spec/ruby/library/delegate/delegator/equal_spec.rb
new file mode 100644
index 0000000000..c8711c74b5
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/equal_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#equal?" do
+ it "returns true only when compared with the delegator" do
+ obj = mock('base')
+ delegator = DelegateSpecs::Delegator.new(obj)
+ obj.should_not_receive(:equal?)
+ delegator.equal?(obj).should be_false
+ delegator.equal?(nil).should be_false
+ delegator.equal?(delegator).should be_true
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/equal_value_spec.rb b/spec/ruby/library/delegate/delegator/equal_value_spec.rb
new file mode 100644
index 0000000000..0c967d5f94
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/equal_value_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#==" do
+ before :all do
+ @base = mock('base')
+ @delegator = DelegateSpecs::Delegator.new(@base)
+ end
+
+ it "is not delegated when passed self" do
+ @base.should_not_receive(:==)
+ (@delegator == @delegator).should be_true
+ end
+
+ it "is delegated when passed the delegated object" do
+ @base.should_receive(:==).and_return(false)
+ (@delegator == @base).should be_false
+ end
+
+ it "is delegated in general" do
+ @base.should_receive(:==).and_return(true)
+ (@delegator == 42).should be_true
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/frozen_spec.rb b/spec/ruby/library/delegate/delegator/frozen_spec.rb
new file mode 100644
index 0000000000..b3145c54b1
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/frozen_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator when frozen" do
+ before :all do
+ @array = [42, :hello]
+ @delegate = DelegateSpecs::Delegator.new(@array)
+ @delegate.freeze
+ end
+
+ it "is still readable" do
+ @delegate.should == [42, :hello]
+ @delegate.include?("bar").should be_false
+ end
+
+ it "is frozen" do
+ @delegate.frozen?.should be_true
+ end
+
+ it "is not writable" do
+ ->{ @delegate[0] += 2 }.should raise_error( RuntimeError )
+ end
+
+ it "creates a frozen clone" do
+ @delegate.clone.frozen?.should be_true
+ end
+
+ it "creates an unfrozen dup" do
+ @delegate.dup.frozen?.should be_false
+ end
+
+ it "causes mutative calls to raise RuntimeError" do
+ ->{ @delegate.__setobj__("hola!") }.should raise_error( RuntimeError )
+ end
+
+ it "returns false if only the delegated object is frozen" do
+ DelegateSpecs::Delegator.new([1,2,3].freeze).frozen?.should be_false
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/hash_spec.rb b/spec/ruby/library/delegate/delegator/hash_spec.rb
new file mode 100644
index 0000000000..132cb91ccc
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/hash_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#hash" do
+ it "is delegated" do
+ base = mock('base')
+ delegator = DelegateSpecs::Delegator.new(base)
+ base.should_receive(:hash).and_return(42)
+ delegator.hash.should == 42
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/marshal_spec.rb b/spec/ruby/library/delegate/delegator/marshal_spec.rb
new file mode 100644
index 0000000000..6c75c8f573
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/marshal_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+require 'delegate'
+
+describe "SimpleDelegator" do
+ before :all do
+ @obj = "hello"
+ @delegate = SimpleDelegator.new(@obj)
+ end
+
+ it "can be marshalled" do
+ m = Marshal.load(Marshal.dump(@delegate))
+ m.class.should == SimpleDelegator
+ (m == @obj).should be_true
+ end
+
+ it "can be marshalled with its instance variables intact" do
+ @delegate.instance_variable_set(:@foo, "bar")
+ m = Marshal.load(Marshal.dump(@delegate))
+ m.instance_variable_get(:@foo).should == "bar"
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/method_spec.rb b/spec/ruby/library/delegate/delegator/method_spec.rb
new file mode 100644
index 0000000000..81c8eea710
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/method_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#method" do
+ before :each do
+ @simple = DelegateSpecs::Simple.new
+ @delegate = DelegateSpecs::Delegator.new(@simple)
+ end
+
+ it "returns a method object for public methods of the delegate object" do
+ m = @delegate.method(:pub)
+ m.should be_an_instance_of(Method)
+ m.call.should == :foo
+ end
+
+ it "raises a NameError for protected methods of the delegate object" do
+ -> {
+ -> {
+ @delegate.method(:prot)
+ }.should complain(/delegator does not forward private method #prot/)
+ }.should raise_error(NameError)
+ end
+
+ it "raises a NameError for a private methods of the delegate object" do
+ -> {
+ -> {
+ @delegate.method(:priv)
+ }.should complain(/delegator does not forward private method #priv/)
+ }.should raise_error(NameError)
+ end
+
+ it "returns a method object for public methods of the Delegator class" do
+ m = @delegate.method(:extra)
+ m.should be_an_instance_of(Method)
+ m.call.should == :cheese
+ end
+
+ it "returns a method object for protected methods of the Delegator class" do
+ m = @delegate.method(:extra_protected)
+ m.should be_an_instance_of(Method)
+ m.call.should == :baz
+ end
+
+ it "returns a method object for private methods of the Delegator class" do
+ m = @delegate.method(:extra_private)
+ m.should be_an_instance_of(Method)
+ m.call.should == :bar
+ end
+
+ it "raises a NameError for an invalid method name" do
+ -> {
+ @delegate.method(:invalid_and_silly_method_name)
+ }.should raise_error(NameError)
+ end
+
+ it "returns a method that respond_to_missing?" do
+ m = @delegate.method(:pub_too)
+ m.should be_an_instance_of(Method)
+ m.call.should == :pub_too
+ end
+
+ it "raises a NameError if method is no longer valid because object has changed" do
+ m = @delegate.method(:pub)
+ @delegate.__setobj__([1,2,3])
+ -> {
+ m.call
+ }.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/methods_spec.rb b/spec/ruby/library/delegate/delegator/methods_spec.rb
new file mode 100644
index 0000000000..b9942bd230
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/methods_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#methods" do
+ before :all do
+ @simple = DelegateSpecs::Simple.new
+ class << @simple
+ def singleton_method
+ end
+ end
+
+ @delegate = DelegateSpecs::Delegator.new(@simple)
+ @methods = @delegate.methods
+ end
+
+ it "returns singleton methods when passed false" do
+ @delegate.methods(false).should include(:singleton_method)
+ end
+
+ it "includes all public methods of the delegate object" do
+ @methods.should include :pub
+ end
+
+ it "includes all protected methods of the delegate object" do
+ @methods.should include :prot
+ end
+
+ it "includes instance methods of the Delegator class" do
+ @methods.should include :extra
+ @methods.should include :extra_protected
+ end
+
+ it "does not include private methods" do
+ @methods.should_not include :priv
+ @methods.should_not include :extra_private
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/not_equal_spec.rb b/spec/ruby/library/delegate/delegator/not_equal_spec.rb
new file mode 100644
index 0000000000..6f2df21715
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/not_equal_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#!=" do
+ before :all do
+ @base = mock('base')
+ @delegator = DelegateSpecs::Delegator.new(@base)
+ end
+
+ it "is not delegated when passed self" do
+ @base.should_not_receive(:"!=")
+ (@delegator != @delegator).should be_false
+ end
+
+ it "is delegated when passed the delegated object" do
+ @base.should_receive(:"!=").and_return(true)
+ (@delegator != @base).should be_true
+ end
+
+ it "is delegated in general" do
+ @base.should_receive(:"!=").and_return(false)
+ (@delegator != 42).should be_false
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/not_spec.rb b/spec/ruby/library/delegate/delegator/not_spec.rb
new file mode 100644
index 0000000000..50105181c3
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/not_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#!" do
+ it "is delegated" do
+ base = mock('base')
+ delegator = DelegateSpecs::Delegator.new(base)
+ base.should_receive(:"!").and_return(:foo)
+ (!delegator).should == :foo
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/private_methods_spec.rb b/spec/ruby/library/delegate/delegator/private_methods_spec.rb
new file mode 100644
index 0000000000..7724b8d413
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/private_methods_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#private_methods" do
+ before :all do
+ @simple = DelegateSpecs::Simple.new
+ @delegate = DelegateSpecs::Delegator.new(@simple)
+ @methods = @delegate.private_methods
+ end
+
+ it "does not include any method of the delegate object" do # since delegates does not forward private calls
+ @methods.should_not include :priv
+ @methods.should_not include :prot
+ @methods.should_not include :pub
+ end
+
+ it "includes all private instance methods of the Delegate class" do
+ @methods.should include :extra_private
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/protected_methods_spec.rb b/spec/ruby/library/delegate/delegator/protected_methods_spec.rb
new file mode 100644
index 0000000000..fd7874fb21
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/protected_methods_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#protected_methods" do
+ before :all do
+ @simple = DelegateSpecs::Simple.new
+ @delegate = DelegateSpecs::Delegator.new(@simple)
+ @methods = @delegate.protected_methods
+ end
+
+ it "includes protected methods of the delegate object" do
+ @methods.should include :prot
+ end
+
+ it "includes protected instance methods of the Delegator class" do
+ @methods.should include :extra_protected
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/public_methods_spec.rb b/spec/ruby/library/delegate/delegator/public_methods_spec.rb
new file mode 100644
index 0000000000..18da16a613
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/public_methods_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#public_methods" do
+ before :all do
+ @simple = DelegateSpecs::Simple.new
+ @delegate = DelegateSpecs::Delegator.new(@simple)
+ @methods = @delegate.public_methods
+ end
+
+ it "includes public methods of the delegate object" do
+ @methods.should include :pub
+ end
+
+ it "includes public instance methods of the Delegator class" do
+ @methods.should include :extra
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/send_spec.rb b/spec/ruby/library/delegate/delegator/send_spec.rb
new file mode 100644
index 0000000000..3022c2ce91
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/send_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "SimpleDelegator.new" do
+ before :all do
+ @simple = DelegateSpecs::Simple.new
+ @delegate = SimpleDelegator.new(@simple)
+ end
+
+ it "forwards public method calls" do
+ @delegate.pub.should == :foo
+ end
+
+ it "forwards protected method calls" do
+ ->{ @delegate.prot }.should raise_error( NoMethodError )
+ end
+
+ it "doesn't forward private method calls" do
+ ->{ @delegate.priv }.should raise_error( NoMethodError )
+ end
+
+ it "doesn't forward private method calls even via send or __send__" do
+ ->{ @delegate.send(:priv, 42) }.should raise_error( NoMethodError )
+ ->{ @delegate.__send__(:priv, 42) }.should raise_error( NoMethodError )
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/taint_spec.rb b/spec/ruby/library/delegate/delegator/taint_spec.rb
new file mode 100644
index 0000000000..6bf13bb73d
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/taint_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#taint" do
+ before :each do
+ @delegate = DelegateSpecs::Delegator.new("")
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/tap_spec.rb b/spec/ruby/library/delegate/delegator/tap_spec.rb
new file mode 100644
index 0000000000..34a88fa1d5
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/tap_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#tap" do
+ it "yield the delegator object" do
+ obj = mock('base')
+ delegator = DelegateSpecs::Delegator.new(obj)
+ obj.should_not_receive(:tap)
+ yielded = []
+ delegator.tap do |x|
+ yielded << x
+ end
+ yielded.size.should == 1
+ yielded[0].equal?(delegator).should be_true
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/trust_spec.rb b/spec/ruby/library/delegate/delegator/trust_spec.rb
new file mode 100644
index 0000000000..f1b81814c5
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/trust_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#trust" do
+ before :each do
+ @delegate = DelegateSpecs::Delegator.new([])
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/untaint_spec.rb b/spec/ruby/library/delegate/delegator/untaint_spec.rb
new file mode 100644
index 0000000000..4051fd2629
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/untaint_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#untaint" do
+ before :each do
+ @delegate = -> { DelegateSpecs::Delegator.new("") }.call
+ end
+end
diff --git a/spec/ruby/library/delegate/delegator/untrust_spec.rb b/spec/ruby/library/delegate/delegator/untrust_spec.rb
new file mode 100644
index 0000000000..4f7fa1e582
--- /dev/null
+++ b/spec/ruby/library/delegate/delegator/untrust_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Delegator#untrust" do
+ before :each do
+ @delegate = DelegateSpecs::Delegator.new("")
+ end
+end
diff --git a/spec/ruby/library/delegate/fixtures/classes.rb b/spec/ruby/library/delegate/fixtures/classes.rb
new file mode 100644
index 0000000000..3cb43eb8b1
--- /dev/null
+++ b/spec/ruby/library/delegate/fixtures/classes.rb
@@ -0,0 +1,60 @@
+require 'delegate'
+module DelegateSpecs
+ class Simple
+ def pub
+ :foo
+ end
+
+ def respond_to_missing?(method, priv=false)
+ method == :pub_too ||
+ (priv && method == :priv_too)
+ end
+
+ def method_missing(method, *args)
+ super unless respond_to_missing?(method, true)
+ method
+ end
+
+ def priv(arg=nil)
+ yield arg if block_given?
+ [:priv, arg]
+ end
+ private :priv
+
+ def prot
+ :protected
+ end
+ protected :prot
+ end
+
+ module Extra
+ def extra
+ :cheese
+ end
+
+ def extra_private
+ :bar
+ end
+ private :extra_private
+
+ def extra_protected
+ :baz
+ end
+ protected :extra_protected
+ end
+
+ class Delegator < ::Delegator
+ attr_accessor :data
+
+ attr_reader :__getobj__
+ def __setobj__(o)
+ @__getobj__ = o
+ end
+
+ include Extra
+ end
+
+ class DelegateClass < DelegateClass(Simple)
+ include Extra
+ end
+end
diff --git a/spec/ruby/library/digest/bubblebabble_spec.rb b/spec/ruby/library/digest/bubblebabble_spec.rb
new file mode 100644
index 0000000000..bbc11ceec5
--- /dev/null
+++ b/spec/ruby/library/digest/bubblebabble_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require 'digest/bubblebabble'
+
+describe "Digest.bubblebabble" do
+ it "returns a String" do
+ Digest.bubblebabble('').should be_an_instance_of(String)
+ end
+
+ it "returns a String in the Bubble Babble Binary Data Encoding format" do
+ Digest.bubblebabble('').should == 'xexax'
+ Digest.bubblebabble('foo').should == 'xinik-zorox'
+ Digest.bubblebabble('bar').should == 'ximik-cosex'
+ Digest.bubblebabble('1234567890').should == 'xesef-disof-gytuf-katof-movif-baxux'
+ end
+
+ it "calls #to_str on an object and returns the bubble babble value of the result" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return('foo')
+ Digest.bubblebabble(obj).should == 'xinik-zorox'
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { Digest.bubblebabble(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed an Integer" do
+ -> { Digest.bubblebabble(9001) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/digest/hexencode_spec.rb b/spec/ruby/library/digest/hexencode_spec.rb
new file mode 100644
index 0000000000..4b6db6eaff
--- /dev/null
+++ b/spec/ruby/library/digest/hexencode_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require 'digest'
+
+describe "Digest.hexencode" do
+ before :each do
+ @string = 'sample string'
+ @encoded = "73616d706c6520737472696e67"
+ end
+
+ it "returns '' when passed an empty String" do
+ Digest.hexencode('').should == ''
+ end
+
+ it "returns the hex-encoded value of a non-empty String" do
+ Digest.hexencode(@string).should == @encoded
+ end
+
+ it "calls #to_str on an object and returns the hex-encoded value of the result" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(@string)
+ Digest.hexencode(obj).should == @encoded
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { Digest.hexencode(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed an Integer" do
+ -> { Digest.hexencode(9001) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/digest/instance/append_spec.rb b/spec/ruby/library/digest/instance/append_spec.rb
new file mode 100644
index 0000000000..2499579298
--- /dev/null
+++ b/spec/ruby/library/digest/instance/append_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require 'digest'
+require_relative 'shared/update'
+
+describe "Digest::Instance#<<" do
+ it_behaves_like :digest_instance_update, :<<
+end
diff --git a/spec/ruby/library/digest/instance/new_spec.rb b/spec/ruby/library/digest/instance/new_spec.rb
new file mode 100644
index 0000000000..3f7939844b
--- /dev/null
+++ b/spec/ruby/library/digest/instance/new_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+require 'digest'
+require_relative '../md5/shared/constants'
+
+describe "Digest::Instance#new" do
+ it "returns a copy of the digest instance" do
+ digest = Digest::MD5.new
+ copy = digest.new
+ copy.should_not.equal?(digest)
+ end
+
+ it "calls reset" do
+ digest = Digest::MD5.new
+ digest << "test"
+ digest.hexdigest.should != MD5Constants::BlankHexdigest
+ copy = digest.new
+ copy.hexdigest.should == MD5Constants::BlankHexdigest
+ end
+end
diff --git a/spec/ruby/library/digest/instance/shared/update.rb b/spec/ruby/library/digest/instance/shared/update.rb
new file mode 100644
index 0000000000..dccc8f80df
--- /dev/null
+++ b/spec/ruby/library/digest/instance/shared/update.rb
@@ -0,0 +1,8 @@
+describe :digest_instance_update, shared: true do
+ it "raises a RuntimeError if called" do
+ c = Class.new do
+ include Digest::Instance
+ end
+ -> { c.new.update("test") }.should raise_error(RuntimeError)
+ end
+end
diff --git a/spec/ruby/library/digest/instance/update_spec.rb b/spec/ruby/library/digest/instance/update_spec.rb
new file mode 100644
index 0000000000..3bb4dd7f1b
--- /dev/null
+++ b/spec/ruby/library/digest/instance/update_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require 'digest'
+require_relative 'shared/update'
+
+describe "Digest::Instance#update" do
+ it_behaves_like :digest_instance_update, :update
+end
diff --git a/spec/ruby/library/digest/md5/append_spec.rb b/spec/ruby/library/digest/md5/append_spec.rb
new file mode 100644
index 0000000000..a7f841c883
--- /dev/null
+++ b/spec/ruby/library/digest/md5/append_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/update'
+
+describe "Digest::MD5#<<" do
+ it_behaves_like :md5_update, :<<
+end
diff --git a/spec/ruby/library/digest/md5/block_length_spec.rb b/spec/ruby/library/digest/md5/block_length_spec.rb
new file mode 100644
index 0000000000..14fb050abd
--- /dev/null
+++ b/spec/ruby/library/digest/md5/block_length_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::MD5#block_length" do
+
+ it "returns the length of digest block" do
+ cur_digest = Digest::MD5.new
+ cur_digest.block_length.should == MD5Constants::BlockLength
+ end
+
+end
diff --git a/spec/ruby/library/digest/md5/digest_bang_spec.rb b/spec/ruby/library/digest/md5/digest_bang_spec.rb
new file mode 100644
index 0000000000..7b884a16d9
--- /dev/null
+++ b/spec/ruby/library/digest/md5/digest_bang_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::MD5#digest!" do
+
+ it "returns a digest and can digest!" do
+ cur_digest = Digest::MD5.new
+ cur_digest << MD5Constants::Contents
+ cur_digest.digest!().should == MD5Constants::Digest
+ cur_digest.digest().should == MD5Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/md5/digest_length_spec.rb b/spec/ruby/library/digest/md5/digest_length_spec.rb
new file mode 100644
index 0000000000..47e071e329
--- /dev/null
+++ b/spec/ruby/library/digest/md5/digest_length_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::MD5#digest_length" do
+
+ it "returns the length of computed digests" do
+ cur_digest = Digest::MD5.new
+ cur_digest.digest_length.should == MD5Constants::DigestLength
+ end
+
+end
diff --git a/spec/ruby/library/digest/md5/digest_spec.rb b/spec/ruby/library/digest/md5/digest_spec.rb
new file mode 100644
index 0000000000..d9bbc45ee2
--- /dev/null
+++ b/spec/ruby/library/digest/md5/digest_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::MD5#digest" do
+
+ it "returns a digest" do
+ cur_digest = Digest::MD5.new
+ cur_digest.digest().should == MD5Constants::BlankDigest
+
+ # add something to check that the state is reset later
+ cur_digest << "test"
+
+ cur_digest.digest(MD5Constants::Contents).should == MD5Constants::Digest
+ # second invocation is intentional, to make sure there are no side-effects
+ cur_digest.digest(MD5Constants::Contents).should == MD5Constants::Digest
+
+ # after all is done, verify that the digest is in the original, blank state
+ cur_digest.digest.should == MD5Constants::BlankDigest
+ end
+
+end
+
+describe "Digest::MD5.digest" do
+
+ it "returns a digest" do
+ Digest::MD5.digest(MD5Constants::Contents).should == MD5Constants::Digest
+ # second invocation is intentional, to make sure there are no side-effects
+ Digest::MD5.digest(MD5Constants::Contents).should == MD5Constants::Digest
+ Digest::MD5.digest("").should == MD5Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/md5/equal_spec.rb b/spec/ruby/library/digest/md5/equal_spec.rb
new file mode 100644
index 0000000000..b0e36564cd
--- /dev/null
+++ b/spec/ruby/library/digest/md5/equal_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::MD5#==" do
+
+ it "equals itself" do
+ cur_digest = Digest::MD5.new
+ cur_digest.should == cur_digest
+ end
+
+ it "equals the string representing its hexdigest" do
+ cur_digest = Digest::MD5.new
+ cur_digest.should == MD5Constants::BlankHexdigest
+ end
+
+ it "equals the appropriate object that responds to to_str" do
+ # blank digest
+ cur_digest = Digest::MD5.new
+ obj = mock(MD5Constants::BlankHexdigest)
+ obj.should_receive(:to_str).and_return(MD5Constants::BlankHexdigest)
+ cur_digest.should == obj
+
+ # non-blank digest
+ cur_digest = Digest::MD5.new
+ cur_digest << "test"
+ d_value = cur_digest.hexdigest
+ (obj = mock(d_value)).should_receive(:to_str).and_return(d_value)
+ cur_digest.should == obj
+ end
+
+ it "equals the same digest for a different object" do
+ cur_digest = Digest::MD5.new
+ cur_digest2 = Digest::MD5.new
+ cur_digest.should == cur_digest2
+ end
+
+end
diff --git a/spec/ruby/library/digest/md5/file_spec.rb b/spec/ruby/library/digest/md5/file_spec.rb
new file mode 100644
index 0000000000..0c8d12cbc9
--- /dev/null
+++ b/spec/ruby/library/digest/md5/file_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative '../../../core/file/shared/read'
+
+describe "Digest::MD5.file" do
+
+ describe "when passed a path to a file that exists" do
+ before :each do
+ @file = tmp("md5_temp")
+ touch(@file, 'wb') {|f| f.write MD5Constants::Contents }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns a Digest::MD5 object" do
+ Digest::MD5.file(@file).should be_kind_of(Digest::MD5)
+ end
+
+ it "returns a Digest::MD5 object with the correct digest" do
+ Digest::MD5.file(@file).digest.should == MD5Constants::Digest
+ end
+
+ it "calls #to_str on an object and returns the Digest::MD5 with the result" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(@file)
+ result = Digest::MD5.file(obj)
+ result.should be_kind_of(Digest::MD5)
+ result.digest.should == MD5Constants::Digest
+ end
+ end
+
+ it_behaves_like :file_read_directory, :file, Digest::MD5
+
+ it "raises a Errno::ENOENT when passed a path that does not exist" do
+ -> { Digest::MD5.file("") }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { Digest::MD5.file(nil) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/digest/md5/hexdigest_bang_spec.rb b/spec/ruby/library/digest/md5/hexdigest_bang_spec.rb
new file mode 100644
index 0000000000..a953eb3b4c
--- /dev/null
+++ b/spec/ruby/library/digest/md5/hexdigest_bang_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::MD5#hexdigest!" do
+
+ it "returns a hexdigest and resets the state" do
+ cur_digest = Digest::MD5.new
+
+ cur_digest << MD5Constants::Contents
+ cur_digest.hexdigest!.should == MD5Constants::Hexdigest
+ cur_digest.hexdigest.should == MD5Constants::BlankHexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/md5/hexdigest_spec.rb b/spec/ruby/library/digest/md5/hexdigest_spec.rb
new file mode 100644
index 0000000000..03ead68b82
--- /dev/null
+++ b/spec/ruby/library/digest/md5/hexdigest_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::MD5#hexdigest" do
+
+ it "returns a hexdigest" do
+ cur_digest = Digest::MD5.new
+ cur_digest.hexdigest.should == MD5Constants::BlankHexdigest
+
+ # add something to check that the state is reset later
+ cur_digest << "test"
+
+ cur_digest.hexdigest(MD5Constants::Contents).should == MD5Constants::Hexdigest
+ # second invocation is intentional, to make sure there are no side-effects
+ cur_digest.hexdigest(MD5Constants::Contents).should == MD5Constants::Hexdigest
+
+ # after all is done, verify that the digest is in the original, blank state
+ cur_digest.hexdigest.should == MD5Constants::BlankHexdigest
+ end
+
+end
+
+describe "Digest::MD5.hexdigest" do
+
+ it "returns a hexdigest" do
+ Digest::MD5.hexdigest(MD5Constants::Contents).should == MD5Constants::Hexdigest
+ # second invocation is intentional, to make sure there are no side-effects
+ Digest::MD5.hexdigest(MD5Constants::Contents).should == MD5Constants::Hexdigest
+ Digest::MD5.hexdigest("").should == MD5Constants::BlankHexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/md5/inspect_spec.rb b/spec/ruby/library/digest/md5/inspect_spec.rb
new file mode 100644
index 0000000000..decc86fba5
--- /dev/null
+++ b/spec/ruby/library/digest/md5/inspect_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::MD5#inspect" do
+
+ it "returns a Ruby object representation" do
+ cur_digest = Digest::MD5.new
+ cur_digest.inspect.should == "#<#{MD5Constants::Klass}: #{cur_digest.hexdigest()}>"
+ end
+
+end
diff --git a/spec/ruby/library/digest/md5/length_spec.rb b/spec/ruby/library/digest/md5/length_spec.rb
new file mode 100644
index 0000000000..b05b2a20fd
--- /dev/null
+++ b/spec/ruby/library/digest/md5/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/length'
+
+describe "Digest::MD5#length" do
+ it_behaves_like :md5_length, :length
+end
diff --git a/spec/ruby/library/digest/md5/reset_spec.rb b/spec/ruby/library/digest/md5/reset_spec.rb
new file mode 100644
index 0000000000..c937844f38
--- /dev/null
+++ b/spec/ruby/library/digest/md5/reset_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::MD5#reset" do
+
+ it "returns digest state to initial conditions" do
+ cur_digest = Digest::MD5.new
+ cur_digest.update MD5Constants::Contents
+ cur_digest.digest().should_not == MD5Constants::BlankDigest
+ cur_digest.reset
+ cur_digest.digest().should == MD5Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/md5/shared/constants.rb b/spec/ruby/library/digest/md5/shared/constants.rb
new file mode 100644
index 0000000000..e807b96f9f
--- /dev/null
+++ b/spec/ruby/library/digest/md5/shared/constants.rb
@@ -0,0 +1,17 @@
+# -*- encoding: binary -*-
+require 'digest/md5'
+
+module MD5Constants
+
+ Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
+
+ Klass = ::Digest::MD5
+ BlockLength = 64
+ DigestLength = 16
+ BlankDigest = "\324\035\214\331\217\000\262\004\351\200\t\230\354\370B~"
+ Digest = "\2473\267qw\276\364\343\345\320\304\350\313\314\217n"
+ BlankHexdigest = "d41d8cd98f00b204e9800998ecf8427e"
+ Hexdigest = "a733b77177bef4e3e5d0c4e8cbcc8f6e"
+ Base64digest = "pzO3cXe+9OPl0MToy8yPbg=="
+
+end
diff --git a/spec/ruby/library/digest/md5/shared/length.rb b/spec/ruby/library/digest/md5/shared/length.rb
new file mode 100644
index 0000000000..c5b2b97b58
--- /dev/null
+++ b/spec/ruby/library/digest/md5/shared/length.rb
@@ -0,0 +1,8 @@
+describe :md5_length, shared: true do
+ it "returns the length of the digest" do
+ cur_digest = Digest::MD5.new
+ cur_digest.send(@method).should == MD5Constants::BlankDigest.size
+ cur_digest << MD5Constants::Contents
+ cur_digest.send(@method).should == MD5Constants::Digest.size
+ end
+end
diff --git a/spec/ruby/library/digest/md5/shared/sample.rb b/spec/ruby/library/digest/md5/shared/sample.rb
new file mode 100644
index 0000000000..2bb4f658b1
--- /dev/null
+++ b/spec/ruby/library/digest/md5/shared/sample.rb
@@ -0,0 +1,17 @@
+# -*- encoding: binary -*-
+
+require 'digest/md5'
+
+module MD5Constants
+
+ Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
+
+ Klass = ::Digest::MD5
+ BlockLength = 64
+ DigestLength = 16
+ BlankDigest = "\324\035\214\331\217\000\262\004\351\200\t\230\354\370B~"
+ Digest = "\2473\267qw\276\364\343\345\320\304\350\313\314\217n"
+ BlankHexdigest = "d41d8cd98f00b204e9800998ecf8427e"
+ Hexdigest = "a733b77177bef4e3e5d0c4e8cbcc8f6e"
+
+end
diff --git a/spec/ruby/library/digest/md5/shared/update.rb b/spec/ruby/library/digest/md5/shared/update.rb
new file mode 100644
index 0000000000..be8622aed5
--- /dev/null
+++ b/spec/ruby/library/digest/md5/shared/update.rb
@@ -0,0 +1,7 @@
+describe :md5_update, shared: true do
+ it "can update" do
+ cur_digest = Digest::MD5.new
+ cur_digest.send @method, MD5Constants::Contents
+ cur_digest.digest.should == MD5Constants::Digest
+ end
+end
diff --git a/spec/ruby/library/digest/md5/size_spec.rb b/spec/ruby/library/digest/md5/size_spec.rb
new file mode 100644
index 0000000000..22e3272d36
--- /dev/null
+++ b/spec/ruby/library/digest/md5/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/length'
+
+describe "Digest::MD5#size" do
+ it_behaves_like :md5_length, :size
+end
diff --git a/spec/ruby/library/digest/md5/to_s_spec.rb b/spec/ruby/library/digest/md5/to_s_spec.rb
new file mode 100644
index 0000000000..78d53d6967
--- /dev/null
+++ b/spec/ruby/library/digest/md5/to_s_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+require 'digest/md5'
+
+require_relative 'shared/constants'
+
+describe "Digest::MD5#to_s" do
+
+ it "returns a hexdigest" do
+ cur_digest = Digest::MD5.new
+ cur_digest.to_s.should == MD5Constants::BlankHexdigest
+ end
+
+ it "does not change the internal state" do
+ cur_digest = Digest::MD5.new
+ cur_digest.to_s.should == MD5Constants::BlankHexdigest
+ cur_digest.to_s.should == MD5Constants::BlankHexdigest
+
+ cur_digest << MD5Constants::Contents
+ cur_digest.to_s.should == MD5Constants::Hexdigest
+ cur_digest.to_s.should == MD5Constants::Hexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/md5/update_spec.rb b/spec/ruby/library/digest/md5/update_spec.rb
new file mode 100644
index 0000000000..4773db308c
--- /dev/null
+++ b/spec/ruby/library/digest/md5/update_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/update'
+
+describe "Digest::MD5#update" do
+ it_behaves_like :md5_update, :update
+end
diff --git a/spec/ruby/library/digest/sha1/digest_spec.rb b/spec/ruby/library/digest/sha1/digest_spec.rb
new file mode 100644
index 0000000000..03f805336f
--- /dev/null
+++ b/spec/ruby/library/digest/sha1/digest_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA1#digest" do
+
+ it "returns a digest" do
+ cur_digest = Digest::SHA1.new
+ cur_digest.digest().should == SHA1Constants::BlankDigest
+ cur_digest.digest(SHA1Constants::Contents).should == SHA1Constants::Digest
+ end
+
+end
+
+describe "Digest::SHA1.digest" do
+
+ it "returns a digest" do
+ Digest::SHA1.digest(SHA1Constants::Contents).should == SHA1Constants::Digest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha1/file_spec.rb b/spec/ruby/library/digest/sha1/file_spec.rb
new file mode 100644
index 0000000000..9c15f5b02f
--- /dev/null
+++ b/spec/ruby/library/digest/sha1/file_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative '../../../core/file/shared/read'
+
+describe "Digest::SHA1.file" do
+
+ describe "when passed a path to a file that exists" do
+ before :each do
+ @file = tmp("md5_temp")
+ touch(@file, 'wb') {|f| f.write SHA1Constants::Contents }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns a Digest::SHA1 object" do
+ Digest::SHA1.file(@file).should be_kind_of(Digest::SHA1)
+ end
+
+ it "returns a Digest::SHA1 object with the correct digest" do
+ Digest::SHA1.file(@file).digest.should == SHA1Constants::Digest
+ end
+
+ it "calls #to_str on an object and returns the Digest::SHA1 with the result" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(@file)
+ result = Digest::SHA1.file(obj)
+ result.should be_kind_of(Digest::SHA1)
+ result.digest.should == SHA1Constants::Digest
+ end
+ end
+
+ it_behaves_like :file_read_directory, :file, Digest::SHA1
+
+ it "raises a Errno::ENOENT when passed a path that does not exist" do
+ -> { Digest::SHA1.file("") }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { Digest::SHA1.file(nil) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/digest/sha1/shared/constants.rb b/spec/ruby/library/digest/sha1/shared/constants.rb
new file mode 100644
index 0000000000..169438747f
--- /dev/null
+++ b/spec/ruby/library/digest/sha1/shared/constants.rb
@@ -0,0 +1,18 @@
+# -*- encoding: binary -*-
+
+require 'digest/sha1'
+
+module SHA1Constants
+
+ Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
+
+ Klass = ::Digest::SHA1
+ BlockLength = 64
+ DigestLength = 20
+ BlankDigest = "\3329\243\356^kK\r2U\277\357\225`\030\220\257\330\a\t"
+ Digest = "X!\255b\323\035\352\314a|q\344+\376\317\361V9\324\343"
+ BlankHexdigest = "da39a3ee5e6b4b0d3255bfef95601890afd80709"
+ Hexdigest = "5821ad62d31deacc617c71e42bfecff15639d4e3"
+ Base64digest = "WCGtYtMd6sxhfHHkK/7P8VY51OM="
+
+end
diff --git a/spec/ruby/library/digest/sha2/hexdigest_spec.rb b/spec/ruby/library/digest/sha2/hexdigest_spec.rb
new file mode 100644
index 0000000000..79beca5788
--- /dev/null
+++ b/spec/ruby/library/digest/sha2/hexdigest_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative '../sha256/shared/constants'
+
+describe "Digest::SHA2#hexdigest" do
+
+ it "returns a SHA256 hexdigest by default" do
+ cur_digest = Digest::SHA2.new
+ cur_digest.hexdigest.should == SHA256Constants::BlankHexdigest
+
+ # add something to check that the state is reset later
+ cur_digest << "test"
+
+ cur_digest.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest
+ # second invocation is intentional, to make sure there are no side-effects
+ cur_digest.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest
+
+ # after all is done, verify that the digest is in the original, blank state
+ cur_digest.hexdigest.should == SHA256Constants::BlankHexdigest
+ end
+
+end
+
+describe "Digest::SHA2.hexdigest" do
+
+ it "returns a SHA256 hexdigest by default" do
+ Digest::SHA2.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest
+ # second invocation is intentional, to make sure there are no side-effects
+ Digest::SHA2.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest
+ Digest::SHA2.hexdigest("").should == SHA256Constants::BlankHexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/append_spec.rb b/spec/ruby/library/digest/sha256/append_spec.rb
new file mode 100644
index 0000000000..9ca3496afc
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/append_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/update'
+
+describe "Digest::SHA256#<<" do
+ it_behaves_like :sha256_update, :<<
+end
diff --git a/spec/ruby/library/digest/sha256/block_length_spec.rb b/spec/ruby/library/digest/sha256/block_length_spec.rb
new file mode 100644
index 0000000000..1e29e832cf
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/block_length_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA256#block_length" do
+
+ it "returns the length of digest block" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.block_length.should == SHA256Constants::BlockLength
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/digest_bang_spec.rb b/spec/ruby/library/digest/sha256/digest_bang_spec.rb
new file mode 100644
index 0000000000..690442221a
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/digest_bang_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA256#digest!" do
+
+ it "returns a digest and can digest!" do
+ cur_digest = Digest::SHA256.new
+ cur_digest << SHA256Constants::Contents
+ cur_digest.digest!().should == SHA256Constants::Digest
+ cur_digest.digest().should == SHA256Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/digest_length_spec.rb b/spec/ruby/library/digest/sha256/digest_length_spec.rb
new file mode 100644
index 0000000000..b5c8958e84
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/digest_length_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA256#digest_length" do
+
+ it "returns the length of computed digests" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.digest_length.should == SHA256Constants::DigestLength
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/digest_spec.rb b/spec/ruby/library/digest/sha256/digest_spec.rb
new file mode 100644
index 0000000000..1e79f25627
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/digest_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA256#digest" do
+
+ it "returns a digest" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.digest().should == SHA256Constants::BlankDigest
+
+ # add something to check that the state is reset later
+ cur_digest << "test"
+
+ cur_digest.digest(SHA256Constants::Contents).should == SHA256Constants::Digest
+ # second invocation is intentional, to make sure there are no side-effects
+ cur_digest.digest(SHA256Constants::Contents).should == SHA256Constants::Digest
+
+ # after all is done, verify that the digest is in the original, blank state
+ cur_digest.digest.should == SHA256Constants::BlankDigest
+ end
+
+end
+
+describe "Digest::SHA256.digest" do
+
+ it "returns a digest" do
+ Digest::SHA256.digest(SHA256Constants::Contents).should == SHA256Constants::Digest
+ # second invocation is intentional, to make sure there are no side-effects
+ Digest::SHA256.digest(SHA256Constants::Contents).should == SHA256Constants::Digest
+ Digest::SHA256.digest("").should == SHA256Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/equal_spec.rb b/spec/ruby/library/digest/sha256/equal_spec.rb
new file mode 100644
index 0000000000..84662aa068
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/equal_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA256#==" do
+
+ it "equals itself" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.should == cur_digest
+ end
+
+ it "equals the string representing its hexdigest" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.should == SHA256Constants::BlankHexdigest
+ end
+
+ it "equals the appropriate object that responds to to_str" do
+ # blank digest
+ cur_digest = Digest::SHA256.new
+ (obj = mock(SHA256Constants::BlankHexdigest)).should_receive(:to_str).and_return(SHA256Constants::BlankHexdigest)
+ cur_digest.should == obj
+
+ # non-blank digest
+ cur_digest = Digest::SHA256.new
+ cur_digest << "test"
+ d_value = cur_digest.hexdigest
+ (obj = mock(d_value)).should_receive(:to_str).and_return(d_value)
+ cur_digest.should == obj
+ end
+
+ it "equals the same digest for a different object" do
+ cur_digest = Digest::SHA256.new
+ cur_digest2 = Digest::SHA256.new
+ cur_digest.should == cur_digest2
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/file_spec.rb b/spec/ruby/library/digest/sha256/file_spec.rb
new file mode 100644
index 0000000000..8cbc5a2755
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/file_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative '../../../core/file/shared/read'
+
+describe "Digest::SHA256.file" do
+
+ describe "when passed a path to a file that exists" do
+ before :each do
+ @file = tmp("md5_temp")
+ touch(@file, 'wb') {|f| f.write SHA256Constants::Contents }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns a Digest::SHA256 object" do
+ Digest::SHA256.file(@file).should be_kind_of(Digest::SHA256)
+ end
+
+ it "returns a Digest::SHA256 object with the correct digest" do
+ Digest::SHA256.file(@file).digest.should == SHA256Constants::Digest
+ end
+
+ it "can be used with frozen-string-literal" do
+ ruby_exe("require 'digest'; puts Digest::SHA256.file(#{@file.inspect}).digest.inspect", options: "--enable=frozen-string-literal").chomp.should == SHA256Constants::Digest.inspect
+ end
+
+ it "calls #to_str on an object and returns the Digest::SHA256 with the result" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(@file)
+ result = Digest::SHA256.file(obj)
+ result.should be_kind_of(Digest::SHA256)
+ result.digest.should == SHA256Constants::Digest
+ end
+ end
+
+ it_behaves_like :file_read_directory, :file, Digest::SHA256
+
+ it "raises a Errno::ENOENT when passed a path that does not exist" do
+ -> { Digest::SHA256.file("") }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { Digest::SHA256.file(nil) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/digest/sha256/hexdigest_bang_spec.rb b/spec/ruby/library/digest/sha256/hexdigest_bang_spec.rb
new file mode 100644
index 0000000000..1acd8043b3
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/hexdigest_bang_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA256#hexdigest!" do
+
+ it "returns a hexdigest and resets the state" do
+ cur_digest = Digest::SHA256.new
+
+ cur_digest << SHA256Constants::Contents
+ cur_digest.hexdigest!.should == SHA256Constants::Hexdigest
+ cur_digest.hexdigest.should == SHA256Constants::BlankHexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/hexdigest_spec.rb b/spec/ruby/library/digest/sha256/hexdigest_spec.rb
new file mode 100644
index 0000000000..4f748b33b4
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/hexdigest_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA256#hexdigest" do
+
+ it "returns a hexdigest" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.hexdigest.should == SHA256Constants::BlankHexdigest
+
+ # add something to check that the state is reset later
+ cur_digest << "test"
+
+ cur_digest.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest
+ # second invocation is intentional, to make sure there are no side-effects
+ cur_digest.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest
+
+ # after all is done, verify that the digest is in the original, blank state
+ cur_digest.hexdigest.should == SHA256Constants::BlankHexdigest
+ end
+
+end
+
+describe "Digest::SHA256.hexdigest" do
+
+ it "returns a hexdigest" do
+ Digest::SHA256.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest
+ # second invocation is intentional, to make sure there are no side-effects
+ Digest::SHA256.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest
+ Digest::SHA256.hexdigest("").should == SHA256Constants::BlankHexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/inspect_spec.rb b/spec/ruby/library/digest/sha256/inspect_spec.rb
new file mode 100644
index 0000000000..ed606e4517
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/inspect_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA256#inspect" do
+
+ it "returns a Ruby object representation" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.inspect.should == "#<#{SHA256Constants::Klass}: #{cur_digest.hexdigest()}>"
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/length_spec.rb b/spec/ruby/library/digest/sha256/length_spec.rb
new file mode 100644
index 0000000000..181ac564ad
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/length'
+
+describe "Digest::SHA256#length" do
+ it_behaves_like :sha256_length, :length
+end
diff --git a/spec/ruby/library/digest/sha256/reset_spec.rb b/spec/ruby/library/digest/sha256/reset_spec.rb
new file mode 100644
index 0000000000..f0eb4faea6
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/reset_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA256#reset" do
+
+ it "returns digest state to initial conditions" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.update SHA256Constants::Contents
+ cur_digest.digest().should_not == SHA256Constants::BlankDigest
+ cur_digest.reset
+ cur_digest.digest().should == SHA256Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/shared/constants.rb b/spec/ruby/library/digest/sha256/shared/constants.rb
new file mode 100644
index 0000000000..351679f344
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/shared/constants.rb
@@ -0,0 +1,18 @@
+# -*- encoding: binary -*-
+
+require 'digest/sha2'
+
+module SHA256Constants
+
+ Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
+
+ Klass = ::Digest::SHA256
+ BlockLength = 64
+ DigestLength = 32
+ BlankDigest = "\343\260\304B\230\374\034\024\232\373\364\310\231o\271$'\256A\344d\233\223L\244\225\231\exR\270U"
+ Digest = "\230b\265\344_\337\357\337\242\004\314\311A\211jb\350\373\254\370\365M\230B\002\372\020j\as\270\376"
+ BlankHexdigest = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ Hexdigest = "9862b5e45fdfefdfa204ccc941896a62e8fbacf8f54d984202fa106a0773b8fe"
+ Base64digest = "mGK15F/f79+iBMzJQYlqYuj7rPj1TZhCAvoQagdzuP4="
+
+end
diff --git a/spec/ruby/library/digest/sha256/shared/length.rb b/spec/ruby/library/digest/sha256/shared/length.rb
new file mode 100644
index 0000000000..996673a5bd
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/shared/length.rb
@@ -0,0 +1,8 @@
+describe :sha256_length, shared: true do
+ it "returns the length of the digest" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.send(@method).should == SHA256Constants::BlankDigest.size
+ cur_digest << SHA256Constants::Contents
+ cur_digest.send(@method).should == SHA256Constants::Digest.size
+ end
+end
diff --git a/spec/ruby/library/digest/sha256/shared/update.rb b/spec/ruby/library/digest/sha256/shared/update.rb
new file mode 100644
index 0000000000..0edc07935b
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/shared/update.rb
@@ -0,0 +1,7 @@
+describe :sha256_update, shared: true do
+ it "can update" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.send @method, SHA256Constants::Contents
+ cur_digest.digest.should == SHA256Constants::Digest
+ end
+end
diff --git a/spec/ruby/library/digest/sha256/size_spec.rb b/spec/ruby/library/digest/sha256/size_spec.rb
new file mode 100644
index 0000000000..1028263342
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/length'
+
+describe "Digest::SHA256#size" do
+ it_behaves_like :sha256_length, :size
+end
diff --git a/spec/ruby/library/digest/sha256/to_s_spec.rb b/spec/ruby/library/digest/sha256/to_s_spec.rb
new file mode 100644
index 0000000000..8bedee3f98
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/to_s_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA256#to_s" do
+
+ it "returns a hexdigest" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.to_s.should == SHA256Constants::BlankHexdigest
+ end
+
+ it "does not change the internal state" do
+ cur_digest = Digest::SHA256.new
+ cur_digest.to_s.should == SHA256Constants::BlankHexdigest
+ cur_digest.to_s.should == SHA256Constants::BlankHexdigest
+
+ cur_digest << SHA256Constants::Contents
+ cur_digest.to_s.should == SHA256Constants::Hexdigest
+ cur_digest.to_s.should == SHA256Constants::Hexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha256/update_spec.rb b/spec/ruby/library/digest/sha256/update_spec.rb
new file mode 100644
index 0000000000..92316eb752
--- /dev/null
+++ b/spec/ruby/library/digest/sha256/update_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/update'
+
+describe "Digest::SHA256#update" do
+ it_behaves_like :sha256_update, :update
+end
diff --git a/spec/ruby/library/digest/sha384/append_spec.rb b/spec/ruby/library/digest/sha384/append_spec.rb
new file mode 100644
index 0000000000..2bc0c5b90b
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/append_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/update'
+
+describe "Digest::SHA384#<<" do
+ it_behaves_like :sha384_update, :<<
+end
diff --git a/spec/ruby/library/digest/sha384/block_length_spec.rb b/spec/ruby/library/digest/sha384/block_length_spec.rb
new file mode 100644
index 0000000000..dff645ffb9
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/block_length_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA384#block_length" do
+
+ it "returns the length of digest block" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.block_length.should == SHA384Constants::BlockLength
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha384/digest_bang_spec.rb b/spec/ruby/library/digest/sha384/digest_bang_spec.rb
new file mode 100644
index 0000000000..8711913deb
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/digest_bang_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA384#digest!" do
+
+ it "returns a digest and can digest!" do
+ cur_digest = Digest::SHA384.new
+ cur_digest << SHA384Constants::Contents
+ cur_digest.digest!().should == SHA384Constants::Digest
+ cur_digest.digest().should == SHA384Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha384/digest_length_spec.rb b/spec/ruby/library/digest/sha384/digest_length_spec.rb
new file mode 100644
index 0000000000..4067dd34af
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/digest_length_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA384#digest_length" do
+
+ it "returns the length of computed digests" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.digest_length.should == SHA384Constants::DigestLength
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha384/digest_spec.rb b/spec/ruby/library/digest/sha384/digest_spec.rb
new file mode 100644
index 0000000000..d0e2825934
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/digest_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA384#digest" do
+
+ it "returns a digest" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.digest().should == SHA384Constants::BlankDigest
+
+ # add something to check that the state is reset later
+ cur_digest << "test"
+
+ cur_digest.digest(SHA384Constants::Contents).should == SHA384Constants::Digest
+ # second invocation is intentional, to make sure there are no side-effects
+ cur_digest.digest(SHA384Constants::Contents).should == SHA384Constants::Digest
+
+ # after all is done, verify that the digest is in the original, blank state
+ cur_digest.digest.should == SHA384Constants::BlankDigest
+ end
+
+end
+
+describe "Digest::SHA384.digest" do
+
+ it "returns a digest" do
+ Digest::SHA384.digest(SHA384Constants::Contents).should == SHA384Constants::Digest
+ # second invocation is intentional, to make sure there are no side-effects
+ Digest::SHA384.digest(SHA384Constants::Contents).should == SHA384Constants::Digest
+ Digest::SHA384.digest("").should == SHA384Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha384/equal_spec.rb b/spec/ruby/library/digest/sha384/equal_spec.rb
new file mode 100644
index 0000000000..5d3483d79a
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/equal_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA384#==" do
+
+ it "equals itself" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.should == cur_digest
+ end
+
+ it "equals the string representing its hexdigest" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.should == SHA384Constants::BlankHexdigest
+ end
+
+ it "equals the appropriate object that responds to to_str" do
+ # blank digest
+ cur_digest = Digest::SHA384.new
+ (obj = mock(SHA384Constants::BlankHexdigest)).should_receive(:to_str).and_return(SHA384Constants::BlankHexdigest)
+ cur_digest.should == obj
+
+ # non-blank digest
+ cur_digest = Digest::SHA384.new
+ cur_digest << "test"
+ d_value = cur_digest.hexdigest
+ (obj = mock(d_value)).should_receive(:to_str).and_return(d_value)
+ cur_digest.should == obj
+ end
+
+ it "equals the same digest for a different object" do
+ cur_digest = Digest::SHA384.new
+ cur_digest2 = Digest::SHA384.new
+ cur_digest.should == cur_digest2
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha384/file_spec.rb b/spec/ruby/library/digest/sha384/file_spec.rb
new file mode 100644
index 0000000000..8556f10175
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/file_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative '../../../core/file/shared/read'
+
+describe "Digest::SHA384.file" do
+
+ describe "when passed a path to a file that exists" do
+ before :each do
+ @file = tmp("md5_temp")
+ touch(@file, 'wb') {|f| f.write SHA384Constants::Contents }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns a Digest::SHA384 object" do
+ Digest::SHA384.file(@file).should be_kind_of(Digest::SHA384)
+ end
+
+ it "returns a Digest::SHA384 object with the correct digest" do
+ Digest::SHA384.file(@file).digest.should == SHA384Constants::Digest
+ end
+
+ it "calls #to_str on an object and returns the Digest::SHA384 with the result" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(@file)
+ result = Digest::SHA384.file(obj)
+ result.should be_kind_of(Digest::SHA384)
+ result.digest.should == SHA384Constants::Digest
+ end
+ end
+
+ it_behaves_like :file_read_directory, :file, Digest::SHA384
+
+ it "raises a Errno::ENOENT when passed a path that does not exist" do
+ -> { Digest::SHA384.file("") }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { Digest::SHA384.file(nil) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/digest/sha384/hexdigest_bang_spec.rb b/spec/ruby/library/digest/sha384/hexdigest_bang_spec.rb
new file mode 100644
index 0000000000..8efceec3eb
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/hexdigest_bang_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA384#hexdigest!" do
+
+ it "returns a hexdigest and resets the state" do
+ cur_digest = Digest::SHA384.new
+
+ cur_digest << SHA384Constants::Contents
+ cur_digest.hexdigest!.should == SHA384Constants::Hexdigest
+ cur_digest.hexdigest.should == SHA384Constants::BlankHexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha384/hexdigest_spec.rb b/spec/ruby/library/digest/sha384/hexdigest_spec.rb
new file mode 100644
index 0000000000..07ea05c541
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/hexdigest_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA384#hexdigest" do
+
+ it "returns a hexdigest" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.hexdigest.should == SHA384Constants::BlankHexdigest
+
+ # add something to check that the state is reset later
+ cur_digest << "test"
+
+ cur_digest.hexdigest(SHA384Constants::Contents).should == SHA384Constants::Hexdigest
+ # second invocation is intentional, to make sure there are no side-effects
+ cur_digest.hexdigest(SHA384Constants::Contents).should == SHA384Constants::Hexdigest
+
+ # after all is done, verify that the digest is in the original, blank state
+ cur_digest.hexdigest.should == SHA384Constants::BlankHexdigest
+ end
+
+end
+
+describe "Digest::SHA384.hexdigest" do
+
+ it "returns a hexdigest" do
+ Digest::SHA384.hexdigest(SHA384Constants::Contents).should == SHA384Constants::Hexdigest
+ # second invocation is intentional, to make sure there are no side-effects
+ Digest::SHA384.hexdigest(SHA384Constants::Contents).should == SHA384Constants::Hexdigest
+ Digest::SHA384.hexdigest("").should == SHA384Constants::BlankHexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha384/inspect_spec.rb b/spec/ruby/library/digest/sha384/inspect_spec.rb
new file mode 100644
index 0000000000..8f9f946cc5
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/inspect_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA384#inspect" do
+
+ it "returns a Ruby object representation" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.inspect.should == "#<#{SHA384Constants::Klass}: #{cur_digest.hexdigest()}>"
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha384/length_spec.rb b/spec/ruby/library/digest/sha384/length_spec.rb
new file mode 100644
index 0000000000..33fed492ef
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/length'
+
+describe "Digest::SHA384#length" do
+ it_behaves_like :sha384_length, :length
+end
diff --git a/spec/ruby/library/digest/sha384/reset_spec.rb b/spec/ruby/library/digest/sha384/reset_spec.rb
new file mode 100644
index 0000000000..991b90903d
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/reset_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA384#reset" do
+
+ it "returns digest state to initial conditions" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.update SHA384Constants::Contents
+ cur_digest.digest().should_not == SHA384Constants::BlankDigest
+ cur_digest.reset
+ cur_digest.digest().should == SHA384Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha384/shared/constants.rb b/spec/ruby/library/digest/sha384/shared/constants.rb
new file mode 100644
index 0000000000..2050f03f2b
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/shared/constants.rb
@@ -0,0 +1,19 @@
+# -*- encoding: binary -*-
+
+require 'digest/sha2'
+
+module SHA384Constants
+
+ Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
+
+
+ Klass = ::Digest::SHA384
+ BlockLength = 128
+ DigestLength = 48
+ BlankDigest = "8\260`\247Q\254\2268L\3312~\261\261\343j!\375\267\021\024\276\aCL\f\307\277c\366\341\332'N\336\277\347oe\373\325\032\322\361H\230\271["
+ Digest = "B&\266:\314\216z\361!TD\001{`\355\323\320MW%\270\272\0034n\034\026g\a\217\"\333s\202\275\002Y*\217]\207u\f\034\244\231\266f"
+ BlankHexdigest = "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
+ Hexdigest = "4226b63acc8e7af1215444017b60edd3d04d5725b8ba03346e1c1667078f22db7382bd02592a8f5d87750c1ca499b666"
+ Base64digest = "Qia2OsyOevEhVEQBe2Dt09BNVyW4ugM0bhwWZwePIttzgr0CWSqPXYd1DBykmbZm"
+
+end
diff --git a/spec/ruby/library/digest/sha384/shared/length.rb b/spec/ruby/library/digest/sha384/shared/length.rb
new file mode 100644
index 0000000000..0c88288bcf
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/shared/length.rb
@@ -0,0 +1,8 @@
+describe :sha384_length, shared: true do
+ it "returns the length of the digest" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.send(@method).should == SHA384Constants::BlankDigest.size
+ cur_digest << SHA384Constants::Contents
+ cur_digest.send(@method).should == SHA384Constants::Digest.size
+ end
+end
diff --git a/spec/ruby/library/digest/sha384/shared/update.rb b/spec/ruby/library/digest/sha384/shared/update.rb
new file mode 100644
index 0000000000..1c6e31cf6a
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/shared/update.rb
@@ -0,0 +1,7 @@
+describe :sha384_update, shared: true do
+ it "can update" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.send @method, SHA384Constants::Contents
+ cur_digest.digest.should == SHA384Constants::Digest
+ end
+end
diff --git a/spec/ruby/library/digest/sha384/size_spec.rb b/spec/ruby/library/digest/sha384/size_spec.rb
new file mode 100644
index 0000000000..4c3b14f7a0
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/length'
+
+describe "Digest::SHA384#size" do
+ it_behaves_like :sha384_length, :size
+end
diff --git a/spec/ruby/library/digest/sha384/to_s_spec.rb b/spec/ruby/library/digest/sha384/to_s_spec.rb
new file mode 100644
index 0000000000..68ea9c013f
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/to_s_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA384#to_s" do
+
+ it "returns a hexdigest" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.to_s.should == SHA384Constants::BlankHexdigest
+ end
+
+ it "does not change the internal state" do
+ cur_digest = Digest::SHA384.new
+ cur_digest.to_s.should == SHA384Constants::BlankHexdigest
+ cur_digest.to_s.should == SHA384Constants::BlankHexdigest
+
+ cur_digest << SHA384Constants::Contents
+ cur_digest.to_s.should == SHA384Constants::Hexdigest
+ cur_digest.to_s.should == SHA384Constants::Hexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha384/update_spec.rb b/spec/ruby/library/digest/sha384/update_spec.rb
new file mode 100644
index 0000000000..a1d0dd6068
--- /dev/null
+++ b/spec/ruby/library/digest/sha384/update_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/update'
+
+describe "Digest::SHA384#update" do
+ it_behaves_like :sha384_update, :update
+end
diff --git a/spec/ruby/library/digest/sha512/append_spec.rb b/spec/ruby/library/digest/sha512/append_spec.rb
new file mode 100644
index 0000000000..e5f84b56f4
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/append_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/update'
+
+describe "Digest::SHA512#<<" do
+ it_behaves_like :sha512_update, :<<
+end
diff --git a/spec/ruby/library/digest/sha512/block_length_spec.rb b/spec/ruby/library/digest/sha512/block_length_spec.rb
new file mode 100644
index 0000000000..947af841dd
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/block_length_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA512#block_length" do
+
+ it "returns the length of digest block" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.block_length.should == SHA512Constants::BlockLength
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha512/digest_bang_spec.rb b/spec/ruby/library/digest/sha512/digest_bang_spec.rb
new file mode 100644
index 0000000000..981570b907
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/digest_bang_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA512#digest!" do
+
+ it "returns a digest and can digest!" do
+ cur_digest = Digest::SHA512.new
+ cur_digest << SHA512Constants::Contents
+ cur_digest.digest!().should == SHA512Constants::Digest
+ cur_digest.digest().should == SHA512Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha512/digest_length_spec.rb b/spec/ruby/library/digest/sha512/digest_length_spec.rb
new file mode 100644
index 0000000000..ff5956dd75
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/digest_length_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA512#digest_length" do
+
+ it "returns the length of computed digests" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.digest_length.should == SHA512Constants::DigestLength
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha512/digest_spec.rb b/spec/ruby/library/digest/sha512/digest_spec.rb
new file mode 100644
index 0000000000..092efccc62
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/digest_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA512#digest" do
+
+ it "returns a digest" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.digest().should == SHA512Constants::BlankDigest
+
+ # add something to check that the state is reset later
+ cur_digest << "test"
+
+ cur_digest.digest(SHA512Constants::Contents).should == SHA512Constants::Digest
+ # second invocation is intentional, to make sure there are no side-effects
+ cur_digest.digest(SHA512Constants::Contents).should == SHA512Constants::Digest
+
+ # after all is done, verify that the digest is in the original, blank state
+ cur_digest.digest.should == SHA512Constants::BlankDigest
+ end
+
+end
+
+describe "Digest::SHA512.digest" do
+
+ it "returns a digest" do
+ Digest::SHA512.digest(SHA512Constants::Contents).should == SHA512Constants::Digest
+ # second invocation is intentional, to make sure there are no side-effects
+ Digest::SHA512.digest(SHA512Constants::Contents).should == SHA512Constants::Digest
+ Digest::SHA512.digest("").should == SHA512Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha512/equal_spec.rb b/spec/ruby/library/digest/sha512/equal_spec.rb
new file mode 100644
index 0000000000..5100ced6e8
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/equal_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA512#==" do
+
+ it "equals itself" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.should == cur_digest
+ end
+
+ it "equals the string representing its hexdigest" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.should == SHA512Constants::BlankHexdigest
+ end
+
+ it "equals the appropriate object that responds to to_str" do
+ # blank digest
+ cur_digest = Digest::SHA512.new
+ (obj = mock(SHA512Constants::BlankHexdigest)).should_receive(:to_str).and_return(SHA512Constants::BlankHexdigest)
+ cur_digest.should == obj
+
+ # non-blank digest
+ cur_digest = Digest::SHA512.new
+ cur_digest << "test"
+ d_value = cur_digest.hexdigest
+ (obj = mock(d_value)).should_receive(:to_str).and_return(d_value)
+ cur_digest.should == obj
+ end
+
+ it "equals the same digest for a different object" do
+ cur_digest = Digest::SHA512.new
+ cur_digest2 = Digest::SHA512.new
+ cur_digest.should == cur_digest2
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha512/file_spec.rb b/spec/ruby/library/digest/sha512/file_spec.rb
new file mode 100644
index 0000000000..781ec781e5
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/file_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative '../../../core/file/shared/read'
+
+describe "Digest::SHA512.file" do
+
+ describe "when passed a path to a file that exists" do
+ before :each do
+ @file = tmp("md5_temp")
+ touch(@file, 'wb') {|f| f.write SHA512Constants::Contents }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns a Digest::SHA512 object" do
+ Digest::SHA512.file(@file).should be_kind_of(Digest::SHA512)
+ end
+
+ it "returns a Digest::SHA512 object with the correct digest" do
+ Digest::SHA512.file(@file).digest.should == SHA512Constants::Digest
+ end
+
+ it "calls #to_str on an object and returns the Digest::SHA512 with the result" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(@file)
+ result = Digest::SHA512.file(obj)
+ result.should be_kind_of(Digest::SHA512)
+ result.digest.should == SHA512Constants::Digest
+ end
+ end
+
+ it_behaves_like :file_read_directory, :file, Digest::SHA512
+
+ it "raises a Errno::ENOENT when passed a path that does not exist" do
+ -> { Digest::SHA512.file("") }.should raise_error(Errno::ENOENT)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { Digest::SHA512.file(nil) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/digest/sha512/hexdigest_bang_spec.rb b/spec/ruby/library/digest/sha512/hexdigest_bang_spec.rb
new file mode 100644
index 0000000000..e9b0da6191
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/hexdigest_bang_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA512#hexdigest!" do
+
+ it "returns a hexdigest and resets the state" do
+ cur_digest = Digest::SHA512.new
+
+ cur_digest << SHA512Constants::Contents
+ cur_digest.hexdigest!.should == SHA512Constants::Hexdigest
+ cur_digest.hexdigest.should == SHA512Constants::BlankHexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha512/hexdigest_spec.rb b/spec/ruby/library/digest/sha512/hexdigest_spec.rb
new file mode 100644
index 0000000000..6e1dc3c642
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/hexdigest_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA512#hexdigest" do
+
+ it "returns a hexdigest" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.hexdigest.should == SHA512Constants::BlankHexdigest
+
+ # add something to check that the state is reset later
+ cur_digest << "test"
+
+ cur_digest.hexdigest(SHA512Constants::Contents).should == SHA512Constants::Hexdigest
+ # second invocation is intentional, to make sure there are no side-effects
+ cur_digest.hexdigest(SHA512Constants::Contents).should == SHA512Constants::Hexdigest
+
+ # after all is done, verify that the digest is in the original, blank state
+ cur_digest.hexdigest.should == SHA512Constants::BlankHexdigest
+ end
+
+end
+
+describe "Digest::SHA512.hexdigest" do
+
+ it "returns a hexdigest" do
+ Digest::SHA512.hexdigest(SHA512Constants::Contents).should == SHA512Constants::Hexdigest
+ # second invocation is intentional, to make sure there are no side-effects
+ Digest::SHA512.hexdigest(SHA512Constants::Contents).should == SHA512Constants::Hexdigest
+ Digest::SHA512.hexdigest("").should == SHA512Constants::BlankHexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha512/inspect_spec.rb b/spec/ruby/library/digest/sha512/inspect_spec.rb
new file mode 100644
index 0000000000..54a466043a
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/inspect_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA512#inspect" do
+
+ it "returns a Ruby object representation" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.inspect.should == "#<#{SHA512Constants::Klass}: #{cur_digest.hexdigest()}>"
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha512/length_spec.rb b/spec/ruby/library/digest/sha512/length_spec.rb
new file mode 100644
index 0000000000..e9fde90577
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/length'
+
+describe "Digest::SHA512#length" do
+ it_behaves_like :sha512_length, :length
+end
diff --git a/spec/ruby/library/digest/sha512/reset_spec.rb b/spec/ruby/library/digest/sha512/reset_spec.rb
new file mode 100644
index 0000000000..24a936d4ba
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/reset_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA512#reset" do
+
+ it "returns digest state to initial conditions" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.update SHA512Constants::Contents
+ cur_digest.digest().should_not == SHA512Constants::BlankDigest
+ cur_digest.reset
+ cur_digest.digest().should == SHA512Constants::BlankDigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha512/shared/constants.rb b/spec/ruby/library/digest/sha512/shared/constants.rb
new file mode 100644
index 0000000000..2765a1ec16
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/shared/constants.rb
@@ -0,0 +1,18 @@
+# -*- encoding: binary -*-
+
+require 'digest/sha2'
+
+module SHA512Constants
+
+ Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
+
+ Klass = ::Digest::SHA512
+ BlockLength = 128
+ DigestLength = 64
+ BlankDigest = "\317\203\3415~\357\270\275\361T(P\326m\200\a\326 \344\005\vW\025\334\203\364\251!\323l\351\316G\320\321<]\205\362\260\377\203\030\322\207~\354/c\2711\275GAz\201\24582z\371'\332>"
+ Digest = "\241\231\232\365\002z\241\331\242\310=\367F\272\004\326\331g\315n\251Q\222\250\374E\257\254=\325\225\003SM\350\244\234\220\233=\031\230A;\000\203\233\340\323t\333\271\222w\266\307\2678\344\255j\003\216\300"
+ BlankHexdigest = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
+ Hexdigest = "a1999af5027aa1d9a2c83df746ba04d6d967cd6ea95192a8fc45afac3dd59503534de8a49c909b3d1998413b00839be0d374dbb99277b6c7b738e4ad6a038ec0"
+ Base64digest = "oZma9QJ6odmiyD33RroE1tlnzW6pUZKo/EWvrD3VlQNTTeiknJCbPRmYQTsAg5vg03TbuZJ3tse3OOStagOOwA=="
+
+end
diff --git a/spec/ruby/library/digest/sha512/shared/length.rb b/spec/ruby/library/digest/sha512/shared/length.rb
new file mode 100644
index 0000000000..c0609d5386
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/shared/length.rb
@@ -0,0 +1,8 @@
+describe :sha512_length, shared: true do
+ it "returns the length of the digest" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.send(@method).should == SHA512Constants::BlankDigest.size
+ cur_digest << SHA512Constants::Contents
+ cur_digest.send(@method).should == SHA512Constants::Digest.size
+ end
+end
diff --git a/spec/ruby/library/digest/sha512/shared/update.rb b/spec/ruby/library/digest/sha512/shared/update.rb
new file mode 100644
index 0000000000..ca74dbf4df
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/shared/update.rb
@@ -0,0 +1,7 @@
+describe :sha512_update, shared: true do
+ it "can update" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.send @method, SHA512Constants::Contents
+ cur_digest.digest.should == SHA512Constants::Digest
+ end
+end
diff --git a/spec/ruby/library/digest/sha512/size_spec.rb b/spec/ruby/library/digest/sha512/size_spec.rb
new file mode 100644
index 0000000000..6d0acdabdb
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/length'
+
+describe "Digest::SHA512#size" do
+ it_behaves_like :sha512_length, :size
+end
diff --git a/spec/ruby/library/digest/sha512/to_s_spec.rb b/spec/ruby/library/digest/sha512/to_s_spec.rb
new file mode 100644
index 0000000000..68d86241c9
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/to_s_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+
+describe "Digest::SHA512#to_s" do
+
+ it "returns a hexdigest" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.to_s.should == SHA512Constants::BlankHexdigest
+ end
+
+ it "does not change the internal state" do
+ cur_digest = Digest::SHA512.new
+ cur_digest.to_s.should == SHA512Constants::BlankHexdigest
+ cur_digest.to_s.should == SHA512Constants::BlankHexdigest
+
+ cur_digest << SHA512Constants::Contents
+ cur_digest.to_s.should == SHA512Constants::Hexdigest
+ cur_digest.to_s.should == SHA512Constants::Hexdigest
+ end
+
+end
diff --git a/spec/ruby/library/digest/sha512/update_spec.rb b/spec/ruby/library/digest/sha512/update_spec.rb
new file mode 100644
index 0000000000..682d3a19bb
--- /dev/null
+++ b/spec/ruby/library/digest/sha512/update_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/constants'
+require_relative 'shared/update'
+
+describe "Digest::SHA512#update" do
+ it_behaves_like :sha512_update, :update
+end
diff --git a/spec/ruby/library/drb/fixtures/test_server.rb b/spec/ruby/library/drb/fixtures/test_server.rb
new file mode 100644
index 0000000000..9d412f4ac7
--- /dev/null
+++ b/spec/ruby/library/drb/fixtures/test_server.rb
@@ -0,0 +1,8 @@
+class TestServer
+ def add(*args)
+ args.inject {|n,v| n+v}
+ end
+ def add_yield(x)
+ return (yield x)+1
+ end
+end
diff --git a/spec/ruby/library/drb/start_service_spec.rb b/spec/ruby/library/drb/start_service_spec.rb
new file mode 100644
index 0000000000..016c8b2cff
--- /dev/null
+++ b/spec/ruby/library/drb/start_service_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/test_server'
+require 'drb'
+
+describe "DRb.start_service" do
+ before :each do
+ @server = DRb.start_service("druby://localhost:0", TestServer.new)
+ end
+
+ after :each do
+ DRb.stop_service if @server
+ end
+
+ it "runs a basic remote call" do
+ DRb.current_server.should == @server
+ obj = DRbObject.new(nil, @server.uri)
+ obj.add(1,2,3).should == 6
+ end
+
+ it "runs a basic remote call passing a block" do
+ DRb.current_server.should == @server
+ obj = DRbObject.new(nil, @server.uri)
+ obj.add_yield(2) do |i|
+ i.should == 2
+ i+1
+ end.should == 4
+ end
+end
diff --git a/spec/ruby/library/erb/def_class_spec.rb b/spec/ruby/library/erb/def_class_spec.rb
new file mode 100644
index 0000000000..88bd385f4c
--- /dev/null
+++ b/spec/ruby/library/erb/def_class_spec.rb
@@ -0,0 +1,29 @@
+require 'erb'
+require_relative '../../spec_helper'
+
+describe "ERB#def_class" do
+
+ it "return an unnamed class which has instance method to render eRuby script" do
+ input = <<'END'
+@arg1=<%=@arg1.inspect%>
+@arg2=<%=@arg2.inspect%>
+END
+ expected = <<'END'
+@arg1="foo"
+@arg2=123
+END
+ class MyClass1ForErb_
+ def initialize(arg1, arg2)
+ @arg1 = arg1; @arg2 = arg2
+ end
+ end
+ filename = 'example.rhtml'
+ #erb = ERB.new(File.read(filename))
+ erb = ERB.new(input)
+ erb.filename = filename
+ MyClass1ForErb = erb.def_class(MyClass1ForErb_, 'render()')
+ MyClass1ForErb.method_defined?(:render).should == true
+ MyClass1ForErb.new('foo', 123).render().should == expected
+ end
+
+end
diff --git a/spec/ruby/library/erb/def_method_spec.rb b/spec/ruby/library/erb/def_method_spec.rb
new file mode 100644
index 0000000000..188789a693
--- /dev/null
+++ b/spec/ruby/library/erb/def_method_spec.rb
@@ -0,0 +1,26 @@
+require 'erb'
+require_relative '../../spec_helper'
+
+describe "ERB#def_method" do
+
+ it "define module's instance method to render eRuby file" do
+ input = <<'END'
+arg1=<%= arg1.inspect %>
+arg2=<%= arg2.inspect %>
+END
+ expected = <<'END'
+arg1="foo"
+arg2=123
+END
+ #
+ filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml
+ #erb = ERB.new(File.read(filename))
+ erb = ERB.new(input)
+ class MyClass0ForErb
+ end
+ erb.def_method(MyClass0ForErb, 'render(arg1, arg2)', filename)
+ MyClass0ForErb.method_defined?(:render)
+ MyClass0ForErb.new.render('foo', 123).should == expected
+ end
+
+end
diff --git a/spec/ruby/library/erb/def_module_spec.rb b/spec/ruby/library/erb/def_module_spec.rb
new file mode 100644
index 0000000000..806e564ef0
--- /dev/null
+++ b/spec/ruby/library/erb/def_module_spec.rb
@@ -0,0 +1,27 @@
+require 'erb'
+require_relative '../../spec_helper'
+
+describe "ERB#def_module" do
+
+ it "return unnamed module which has instance method to render eRuby" do
+ input = <<'END'
+arg1=<%= arg1.inspect %>
+arg2=<%= arg2.inspect %>
+END
+ expected = <<'END'
+arg1="foo"
+arg2=123
+END
+ filename = 'example.rhtml'
+ #erb = ERB.new(File.read(filename))
+ erb = ERB.new(input)
+ erb.filename = filename
+ MyModule2ForErb = erb.def_module('render(arg1, arg2)')
+ MyModule2ForErb.method_defined?(':render')
+ class MyClass2ForErb
+ include MyModule2ForErb
+ end
+ MyClass2ForErb.new.render('foo', 123).should == expected
+ end
+
+end
diff --git a/spec/ruby/library/erb/defmethod/def_erb_method_spec.rb b/spec/ruby/library/erb/defmethod/def_erb_method_spec.rb
new file mode 100644
index 0000000000..dc1e044d9c
--- /dev/null
+++ b/spec/ruby/library/erb/defmethod/def_erb_method_spec.rb
@@ -0,0 +1,64 @@
+require 'erb'
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "ERB::DefMethod.def_erb_method" do
+
+
+ input = <<'END'
+<% for item in @items %>
+<b><%= item %></b>
+<% end %>
+END
+
+
+ it "define method to render eRuby file as an instance method of current module" do
+ expected = <<'END'
+
+<b>10</b>
+
+<b>20</b>
+
+<b>30</b>
+
+END
+ #
+ begin
+ file = tmp('_example.rhtml')
+ File.open(file, 'w') {|f| f.write(input) }
+ klass = Class.new do
+ extend ERB::DefMethod
+ def_erb_method('render()', file)
+ def initialize(items)
+ @items = items
+ end
+ end
+ klass.new([10,20,30]).render().should == expected
+ ensure
+ rm_r file
+ end
+
+ end
+
+
+ it "define method to render eRuby object as an instance method of current module" do
+ expected = <<'END'
+<b>10</b>
+<b>20</b>
+<b>30</b>
+END
+ #
+ MY_INPUT4_FOR_ERB = input
+ class MyClass4ForErb
+ extend ERB::DefMethod
+ erb = ERBSpecs.new_erb(MY_INPUT4_FOR_ERB, trim_mode: '<>')
+ def_erb_method('render()', erb)
+ def initialize(items)
+ @items = items
+ end
+ end
+ MyClass4ForErb.new([10,20,30]).render().should == expected
+ end
+
+
+end
diff --git a/spec/ruby/library/erb/filename_spec.rb b/spec/ruby/library/erb/filename_spec.rb
new file mode 100644
index 0000000000..8ecaed7343
--- /dev/null
+++ b/spec/ruby/library/erb/filename_spec.rb
@@ -0,0 +1,40 @@
+require 'erb'
+require_relative '../../spec_helper'
+
+describe "ERB#filename" do
+ it "raises an exception if there are errors processing content" do
+ filename = 'foobar.rhtml'
+ erb = ERB.new('<% if true %>') # will raise SyntaxError
+ erb.filename = filename
+ -> {
+ begin
+ erb.result(binding)
+ rescue Exception => e
+ @ex = e
+ raise e
+ end
+ }.should raise_error(SyntaxError)
+ expected = filename
+
+ @ex.message =~ /^(.*?):(\d+): /
+ $1.should == expected
+ $2.to_i.should == 1
+ end
+
+ it "uses '(erb)' as filename when filename is not set" do
+ erb = ERB.new('<% if true %>') # will raise SyntaxError
+ -> {
+ begin
+ erb.result(binding)
+ rescue Exception => e
+ @ex = e
+ raise e
+ end
+ }.should raise_error(SyntaxError)
+ expected = '(erb)'
+
+ @ex.message =~ /^(.*?):(\d+): /
+ $1.should == expected
+ $2.to_i.should == 1
+ end
+end
diff --git a/spec/ruby/library/erb/fixtures/classes.rb b/spec/ruby/library/erb/fixtures/classes.rb
new file mode 100644
index 0000000000..e07a6ed68d
--- /dev/null
+++ b/spec/ruby/library/erb/fixtures/classes.rb
@@ -0,0 +1,5 @@
+module ERBSpecs
+ def self.new_erb(input, trim_mode: nil)
+ ERB.new(input, trim_mode: trim_mode)
+ end
+end
diff --git a/spec/ruby/library/erb/new_spec.rb b/spec/ruby/library/erb/new_spec.rb
new file mode 100644
index 0000000000..4d7f7bf36a
--- /dev/null
+++ b/spec/ruby/library/erb/new_spec.rb
@@ -0,0 +1,157 @@
+require 'erb'
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "ERB.new" do
+ before :all do
+ @eruby_str = <<'END'
+<ul>
+<% list = [1,2,3] %>
+<% for item in list %>
+<% if item %>
+<li><%= item %></li>
+<% end %>
+<% end %>
+</ul>
+END
+
+ @eruby_str2 = <<'END'
+<ul>
+% list = [1,2,3]
+%for item in list
+% if item
+ <li><%= item %>
+ <% end %>
+<% end %>
+</ul>
+%%%
+END
+
+ end
+
+ it "compiles eRuby script into ruby code when trim mode is 0 or not specified" do
+ expected = "<ul>\n\n\n\n<li>1</li>\n\n\n\n<li>2</li>\n\n\n\n<li>3</li>\n\n\n</ul>\n"
+ [0, nil].each do |trim_mode|
+ ERBSpecs.new_erb(@eruby_str, trim_mode: trim_mode).result.should == expected
+ end
+ end
+
+ it "warns invalid trim_mode" do
+ -> do
+ ERBSpecs.new_erb(@eruby_str, trim_mode: '')
+ end.should complain(/Invalid ERB trim mode/)
+ end
+
+ it "removes '\n' when trim_mode is 1 or '>'" do
+ expected = "<ul>\n<li>1</li>\n<li>2</li>\n<li>3</li>\n</ul>\n"
+ [1, '>'].each do |trim_mode|
+ ERBSpecs.new_erb(@eruby_str, trim_mode: trim_mode).result.should == expected
+ end
+ end
+
+ it "removes spaces at beginning of line and '\n' when trim_mode is 2 or '<>'" do
+ expected = "<ul>\n<li>1</li>\n<li>2</li>\n<li>3</li>\n</ul>\n"
+ [2, '<>'].each do |trim_mode|
+ ERBSpecs.new_erb(@eruby_str, trim_mode: trim_mode).result.should == expected
+ end
+ end
+
+ it "removes spaces around '<%- -%>' when trim_mode is '-'" do
+ expected = "<ul>\n <li>1 <li>2 <li>3</ul>\n"
+ input = <<'END'
+<ul>
+<%- for item in [1,2,3] -%>
+ <%- if item -%>
+ <li><%= item -%>
+ <%- end -%>
+<%- end -%>
+</ul>
+END
+
+ ERBSpecs.new_erb(input, trim_mode: '-').result.should == expected
+ end
+
+
+ it "does not support '<%-= expr %> even when trim_mode is '-'" do
+
+ input = <<'END'
+<p>
+ <%= expr -%>
+ <%-= expr -%>
+</p>
+END
+
+ -> {
+ ERBSpecs.new_erb(input, trim_mode: '-').result
+ }.should raise_error(SyntaxError)
+ end
+
+ it "regards lines starting with '%' as '<% ... %>' when trim_mode is '%'" do
+ expected = "<ul>\n <li>1\n \n <li>2\n \n <li>3\n \n\n</ul>\n%%\n"
+ ERBSpecs.new_erb(@eruby_str2, trim_mode: "%").result.should == expected
+ end
+ it "regards lines starting with '%' as '<% ... %>' and remove \"\\n\" when trim_mode is '%>'" do
+ expected = "<ul>\n <li>1 <li>2 <li>3 </ul>\n%%\n"
+ ERBSpecs.new_erb(@eruby_str2, trim_mode: '%>').result.should == expected
+ end
+
+
+ it "regard lines starting with '%' as '<% ... %>' and remove \"\\n\" when trim_mode is '%<>'" do
+ expected = "<ul>\n <li>1\n \n <li>2\n \n <li>3\n \n</ul>\n%%\n"
+ ERBSpecs.new_erb(@eruby_str2, trim_mode: '%<>').result.should == expected
+ end
+
+
+ it "regard lines starting with '%' as '<% ... %>' and spaces around '<%- -%>' when trim_mode is '%-'" do
+ expected = "<ul>\n<li>1</li>\n<li>2</li>\n</ul>\n%%\n"
+ input = <<'END'
+<ul>
+%list = [1,2]
+%for item in list
+<li><%= item %></li>
+<% end %></ul>
+%%%
+END
+
+ ERBSpecs.new_erb(input, trim_mode: '%-').result.should == expected
+ end
+
+ it "changes '_erbout' variable name in the produced source" do
+ input = @eruby_str
+ match_erbout = ERB.new(input, trim_mode: nil).src
+ match_buf = ERB.new(input, trim_mode: nil, eoutvar: 'buf').src
+ match_erbout.gsub("_erbout", "buf").should == match_buf
+ end
+
+
+ it "ignores '<%# ... %>'" do
+ input = <<'END'
+<%# for item in list %>
+<b><%#= item %></b>
+<%# end %>
+END
+ ERBSpecs.new_erb(input).result.should == "\n<b></b>\n\n"
+ ERBSpecs.new_erb(input, trim_mode: '<>').result.should == "<b></b>\n"
+ end
+
+ it "forget local variables defined previous one" do
+ ERB.new(@eruby_str).result
+ ->{ ERB.new("<%= list %>").result }.should raise_error(NameError)
+ end
+
+ describe "warning about arguments" do
+ ruby_version_is "3.1" do
+ it "warns when passed safe_level and later arguments" do
+ -> {
+ ERB.new(@eruby_str, nil, '%')
+ }.should complain(/warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments./)
+ end
+
+ it "does not warn when passed arguments as keyword argument" do
+ -> {
+ ERB.new(@eruby_str, trim_mode: '%')
+ }.should_not complain(/warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments./)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/erb/result_spec.rb b/spec/ruby/library/erb/result_spec.rb
new file mode 100644
index 0000000000..a29c1ccedb
--- /dev/null
+++ b/spec/ruby/library/erb/result_spec.rb
@@ -0,0 +1,86 @@
+require 'erb'
+require_relative '../../spec_helper'
+
+describe "ERB#result" do
+
+
+ it "return the result of compiled ruby code" do
+ input = <<'END'
+<ul>
+<% for item in list %>
+ <li><%= item %>
+<% end %>
+</ul>
+END
+ expected = <<'END'
+<ul>
+
+ <li>AAA
+
+ <li>BBB
+
+ <li>CCC
+
+</ul>
+END
+ erb = ERB.new(input)
+ list = %w[AAA BBB CCC]
+ actual = erb.result(binding)
+ actual.should == expected
+ end
+
+
+ it "share local variables" do
+ input = "<% var = 456 %>"
+ expected = 456
+ var = 123
+ ERB.new(input).result(binding)
+ var.should == expected
+ end
+
+
+ it "is not able to h() or u() unless including ERB::Util" do
+ input = "<%=h '<>' %>"
+ -> {
+ ERB.new(input).result()
+ }.should raise_error(NameError)
+ end
+
+
+ it "is able to h() or u() if ERB::Util is included" do
+ myerb1 = Class.new do
+ include ERB::Util
+ def main
+ input = "<%=h '<>' %>"
+ return ERB.new(input).result(binding)
+ end
+ end
+ expected = '&lt;&gt;'
+ actual = myerb1.new.main()
+ actual.should == expected
+ end
+
+
+ it "use TOPLEVEL_BINDING if binding is not passed" do
+ myerb2 = Class.new do
+ include ERB::Util
+ def main1
+ #input = "<%= binding.to_s %>"
+ input = "<%= _xxx_var_ %>"
+ return ERB.new(input).result()
+ end
+ def main2
+ input = "<%=h '<>' %>"
+ return ERB.new(input).result()
+ end
+ end
+
+ eval '_xxx_var_ = 123', TOPLEVEL_BINDING
+ expected = '123'
+ myerb2.new.main1().should == expected
+
+ -> {
+ myerb2.new.main2()
+ }.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/library/erb/run_spec.rb b/spec/ruby/library/erb/run_spec.rb
new file mode 100644
index 0000000000..8c07442d8f
--- /dev/null
+++ b/spec/ruby/library/erb/run_spec.rb
@@ -0,0 +1,96 @@
+require 'erb'
+require_relative '../../spec_helper'
+
+describe "ERB#run" do
+ # TODO: what is this? why does it not use
+ # lambda { ... }.should output
+ def _steal_stdout
+ orig = $stdout
+ s = ''
+ def s.write(arg); self << arg.to_s; end
+ $stdout = s
+ begin
+ yield
+ ensure
+ $stdout = orig
+ end
+ return s
+ end
+
+ it "print the result of compiled ruby code" do
+ input = <<END
+<ul>
+<% for item in list %>
+ <li><%= item %>
+<% end %>
+</ul>
+END
+ expected = <<END
+<ul>
+
+ <li>AAA
+
+ <li>BBB
+
+ <li>CCC
+
+</ul>
+END
+ erb = ERB.new(input)
+ list = %w[AAA BBB CCC]
+ actual = _steal_stdout { erb.run(binding) }
+ actual.should == expected
+ end
+
+ it "share local variables" do
+ input = "<% var = 456 %>"
+ expected = 456
+ var = 123
+ _steal_stdout { ERB.new(input).run(binding) }
+ var.should == expected
+ end
+
+ it "is not able to h() or u() unless including ERB::Util" do
+ input = "<%=h '<>' %>"
+ -> {
+ _steal_stdout { ERB.new(input).run() }
+ }.should raise_error(NameError)
+ end
+
+ it "is able to h() or u() if ERB::Util is included" do
+ myerb1 = Class.new do
+ include ERB::Util
+ def main
+ input = "<%=h '<>' %>"
+ ERB.new(input).run(binding)
+ end
+ end
+ expected = '&lt;&gt;'
+ actual = _steal_stdout { myerb1.new.main() }
+ actual.should == expected
+ end
+
+ it "use TOPLEVEL_BINDING if binding is not passed" do
+ myerb2 = Class.new do
+ include ERB::Util
+ def main1
+ #input = "<%= binding.to_s %>"
+ input = "<%= _xxx_var_ %>"
+ return ERB.new(input).run()
+ end
+ def main2
+ input = "<%=h '<>' %>"
+ return ERB.new(input).run()
+ end
+ end
+
+ eval '_xxx_var_ = 123', TOPLEVEL_BINDING
+ expected = '123'
+ actual = _steal_stdout { myerb2.new.main1() }
+ actual.should == expected
+
+ -> {
+ _steal_stdout { myerb2.new.main2() }
+ }.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/library/erb/src_spec.rb b/spec/ruby/library/erb/src_spec.rb
new file mode 100644
index 0000000000..fc11b7e4b7
--- /dev/null
+++ b/spec/ruby/library/erb/src_spec.rb
@@ -0,0 +1,33 @@
+require 'erb'
+require_relative '../../spec_helper'
+
+describe "ERB#src" do
+
+ it "returns the compiled ruby code evaluated to a String" do
+ # note that what concrete code is emitted is not guaranteed.
+
+ input = <<'END'
+<ul>
+<% for item in list %>
+ <li><%= item %>
+<% end %>
+</ul>
+END
+
+ expected = <<'END'
+<ul>
+
+ <li>AAA
+
+ <li>BBB
+
+ <li>CCC
+
+</ul>
+END
+
+ list = %w[AAA BBB CCC]
+ eval(ERB.new(input).src).should == expected
+ end
+
+end
diff --git a/spec/ruby/library/erb/util/h_spec.rb b/spec/ruby/library/erb/util/h_spec.rb
new file mode 100644
index 0000000000..6de79cfd92
--- /dev/null
+++ b/spec/ruby/library/erb/util/h_spec.rb
@@ -0,0 +1,7 @@
+require 'erb'
+require_relative '../../../spec_helper'
+require_relative 'shared/html_escape'
+
+describe "ERB::Util.h" do
+ it_behaves_like :erb_util_html_escape, :h
+end
diff --git a/spec/ruby/library/erb/util/html_escape_spec.rb b/spec/ruby/library/erb/util/html_escape_spec.rb
new file mode 100644
index 0000000000..1c15fb8791
--- /dev/null
+++ b/spec/ruby/library/erb/util/html_escape_spec.rb
@@ -0,0 +1,7 @@
+require 'erb'
+require_relative '../../../spec_helper'
+require_relative 'shared/html_escape'
+
+describe "ERB::Util.html_escape" do
+ it_behaves_like :erb_util_html_escape, :html_escape
+end
diff --git a/spec/ruby/library/erb/util/shared/html_escape.rb b/spec/ruby/library/erb/util/shared/html_escape.rb
new file mode 100644
index 0000000000..71b378755e
--- /dev/null
+++ b/spec/ruby/library/erb/util/shared/html_escape.rb
@@ -0,0 +1,42 @@
+describe :erb_util_html_escape, shared: true do
+ it "escape (& < > \" ') to (&amp; &lt; &gt; &quot; &#39;)" do
+ input = '& < > " \''
+ expected = '&amp; &lt; &gt; &quot; &#39;'
+ ERB::Util.__send__(@method, input).should == expected
+ end
+
+ it "not escape characters except (& < > \" ')" do
+ input = (0x20..0x7E).to_a.collect {|ch| ch.chr}.join('')
+ expected = input.
+ gsub(/&/,'&amp;').
+ gsub(/</,'&lt;').
+ gsub(/>/,'&gt;').
+ gsub(/'/,'&#39;').
+ gsub(/"/,'&quot;')
+ ERB::Util.__send__(@method, input).should == expected
+ end
+
+ it "return empty string when argument is nil" do
+ input = nil
+ expected = ''
+ ERB::Util.__send__(@method, input).should == expected
+ end
+
+ it "returns string when argument is number" do
+ input = 123
+ expected = '123'
+ ERB::Util.__send__(@method, input).should == expected
+ input = 3.14159
+ expected = '3.14159'
+ ERB::Util.__send__(@method, input).should == expected
+ end
+
+ it "returns string when argument is boolean" do
+ input = true
+ expected = 'true'
+ ERB::Util.__send__(@method, input).should == expected
+ input = false
+ expected = 'false'
+ ERB::Util.__send__(@method, input).should == expected
+ end
+end
diff --git a/spec/ruby/library/erb/util/shared/url_encode.rb b/spec/ruby/library/erb/util/shared/url_encode.rb
new file mode 100644
index 0000000000..34009c4903
--- /dev/null
+++ b/spec/ruby/library/erb/util/shared/url_encode.rb
@@ -0,0 +1,42 @@
+describe :erb_util_url_encode, shared: true do
+ it "encode characters" do
+ #input = (0x20..0x7E).to_a.collect{|ch| ch.chr}.join
+ input = " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}"
+ expected = "%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D"
+ ERB::Util.__send__(@method, input).should == expected
+ end
+
+ it "does not escape tilde" do
+ ERB::Util.__send__(@method, "~").should == "~"
+ end
+
+ it "encode unicode string" do
+ input = "https://ja.wikipedia.org/wiki/\343\203\255\343\203\240\343\202\271\343\202\253\343\203\273\343\203\221\343\203\255\343\203\273\343\202\246\343\203\253\343\203\273\343\203\251\343\203\224\343\203\245\343\202\277"
+ expected = 'https%3A%2F%2Fja.wikipedia.org%2Fwiki%2F%E3%83%AD%E3%83%A0%E3%82%B9%E3%82%AB%E3%83%BB%E3%83%91%E3%83%AD%E3%83%BB%E3%82%A6%E3%83%AB%E3%83%BB%E3%83%A9%E3%83%94%E3%83%A5%E3%82%BF'
+ ERB::Util.__send__(@method, input).should == expected
+ end
+
+ it "returns empty string when argument is nil" do
+ input = nil
+ expected = ''
+ ERB::Util.__send__(@method, input).should == expected
+ end
+
+ it "returns string when argument is number" do
+ input = 123
+ expected = '123'
+ ERB::Util.__send__(@method, input).should == expected
+ input = 3.14159
+ expected = '3.14159'
+ ERB::Util.__send__(@method, input).should == expected
+ end
+
+ it "returns string when argument is boolean" do
+ input = true
+ expected = 'true'
+ ERB::Util.__send__(@method, input).should == expected
+ input = false
+ expected = 'false'
+ ERB::Util.__send__(@method, input).should == expected
+ end
+end
diff --git a/spec/ruby/library/erb/util/u_spec.rb b/spec/ruby/library/erb/util/u_spec.rb
new file mode 100644
index 0000000000..2a08451031
--- /dev/null
+++ b/spec/ruby/library/erb/util/u_spec.rb
@@ -0,0 +1,7 @@
+require 'erb'
+require_relative '../../../spec_helper'
+require_relative 'shared/url_encode'
+
+describe "ERB::Util.u" do
+ it_behaves_like :erb_util_url_encode, :u
+end
diff --git a/spec/ruby/library/erb/util/url_encode_spec.rb b/spec/ruby/library/erb/util/url_encode_spec.rb
new file mode 100644
index 0000000000..5569a1cc64
--- /dev/null
+++ b/spec/ruby/library/erb/util/url_encode_spec.rb
@@ -0,0 +1,7 @@
+require 'erb'
+require_relative '../../../spec_helper'
+require_relative 'shared/url_encode'
+
+describe "ERB::Util.url_encode" do
+ it_behaves_like :erb_util_url_encode, :url_encode
+end
diff --git a/spec/ruby/library/etc/confstr_spec.rb b/spec/ruby/library/etc/confstr_spec.rb
new file mode 100644
index 0000000000..41a970a918
--- /dev/null
+++ b/spec/ruby/library/etc/confstr_spec.rb
@@ -0,0 +1,14 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require 'etc'
+
+platform_is_not :windows, :android do
+ describe "Etc.confstr" do
+ it "returns a String for Etc::CS_PATH" do
+ Etc.confstr(Etc::CS_PATH).should be_an_instance_of(String)
+ end
+
+ it "raises Errno::EINVAL for unknown configuration variables" do
+ -> { Etc.confstr(-1) }.should raise_error(Errno::EINVAL)
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/endgrent_spec.rb b/spec/ruby/library/etc/endgrent_spec.rb
new file mode 100644
index 0000000000..88de231d87
--- /dev/null
+++ b/spec/ruby/library/etc/endgrent_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/windows'
+require 'etc'
+
+describe "Etc.endgrent" do
+ it_behaves_like :etc_on_windows, :endgrent
+end
diff --git a/spec/ruby/library/etc/endpwent_spec.rb b/spec/ruby/library/etc/endpwent_spec.rb
new file mode 100644
index 0000000000..e4e564d251
--- /dev/null
+++ b/spec/ruby/library/etc/endpwent_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/windows'
+require 'etc'
+
+describe "Etc.endpwent" do
+ it_behaves_like :etc_on_windows, :endpwent
+end
diff --git a/spec/ruby/library/etc/getgrent_spec.rb b/spec/ruby/library/etc/getgrent_spec.rb
new file mode 100644
index 0000000000..45a4442262
--- /dev/null
+++ b/spec/ruby/library/etc/getgrent_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/windows'
+require 'etc'
+
+describe "Etc.getgrent" do
+ it_behaves_like :etc_on_windows, :getgrent
+end
diff --git a/spec/ruby/library/etc/getgrgid_spec.rb b/spec/ruby/library/etc/getgrgid_spec.rb
new file mode 100644
index 0000000000..14da5e041d
--- /dev/null
+++ b/spec/ruby/library/etc/getgrgid_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+require 'etc'
+
+platform_is :windows do
+ describe "Etc.getgrgid" do
+ it "returns nil" do
+ Etc.getgrgid(1).should == nil
+ Etc.getgrgid(nil).should == nil
+ Etc.getgrgid('nil').should == nil
+ end
+ end
+end
+
+# TODO: verify these on non-windows, non-darwin OS
+platform_is_not :windows do
+ grpname = nil
+ guard -> {
+ grpname = IO.popen(%w'id -gn', err: IO::NULL, &:read).chomp
+ $?.success?
+ } do
+ describe "Etc.getgrgid" do
+ before :all do
+ @gid = `id -g`.strip.to_i
+ @name = grpname
+ end
+
+ it "returns a Etc::Group struct instance for the given user" do
+ gr = Etc.getgrgid(@gid)
+
+ gr.is_a?(Etc::Group).should == true
+ gr.gid.should == @gid
+ gr.name.should == @name
+ end
+
+ it "returns the Etc::Group for a given gid if it exists" do
+ grp = Etc.getgrgid(@gid)
+ grp.should be_kind_of(Etc::Group)
+ grp.gid.should == @gid
+ grp.name.should == @name
+ end
+
+ it "uses Process.gid as the default value for the argument" do
+ gr = Etc.getgrgid
+
+ gr.gid.should == @gid
+ gr.name.should == @name
+ end
+
+ it "raises if the group does not exist" do
+ -> { Etc.getgrgid(9876)}.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed an Integer" do
+ -> { Etc.getgrgid("foo") }.should raise_error(TypeError)
+ -> { Etc.getgrgid(nil) }.should raise_error(TypeError)
+ end
+
+ it "can be called safely by multiple threads" do
+ 20.times.map do
+ Thread.new do
+ 100.times do
+ Etc.getgrgid(@gid).gid.should == @gid
+ end
+ end
+ end.each(&:join)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/getgrnam_spec.rb b/spec/ruby/library/etc/getgrnam_spec.rb
new file mode 100644
index 0000000000..fa49f15349
--- /dev/null
+++ b/spec/ruby/library/etc/getgrnam_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require 'etc'
+
+platform_is :windows do
+ describe "Etc.getgrnam" do
+ it "returns nil" do
+ Etc.getgrnam(1).should == nil
+ Etc.getgrnam(nil).should == nil
+ Etc.getgrnam('nil').should == nil
+ end
+ end
+end
+
+platform_is_not :windows, :android do
+ describe "Etc.getgrnam" do
+ it "returns a Etc::Group struct instance for the given group" do
+ gr_name = Etc.getgrent.name
+ Etc.endgrent
+ gr = Etc.getgrnam(gr_name)
+ gr.is_a?(Etc::Group).should == true
+ end
+
+ it "only accepts strings as argument" do
+ -> {
+ Etc.getgrnam(123)
+ Etc.getgrnam(nil)
+ }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/getlogin_spec.rb b/spec/ruby/library/etc/getlogin_spec.rb
new file mode 100644
index 0000000000..7a4fd79ae2
--- /dev/null
+++ b/spec/ruby/library/etc/getlogin_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require 'etc'
+
+describe "Etc.getlogin" do
+ it "returns the name associated with the current login activity" do
+ getlogin_null = false
+
+ # POSIX logname(1) shows getlogin(2)'s result
+ # NOTE: Etc.getlogin returns ENV['USER'] if getlogin(2) returns NULL
+ begin
+ # make Etc.getlogin to return nil if getlogin(3) returns NULL
+ envuser, ENV['USER'] = ENV['USER'], nil
+ if Etc.getlogin
+ if ENV['TRAVIS'] and platform_is(:darwin)
+ # See https://travis-ci.org/ruby/spec/jobs/285967744
+ # and https://travis-ci.org/ruby/spec/jobs/285999602
+ Etc.getlogin.should be_an_instance_of(String)
+ else
+ # Etc.getlogin returns the same result of logname(2)
+ # if it returns non NULL
+ if system("which logname", out: File::NULL, err: File::NULL)
+ Etc.getlogin.should == `logname`.chomp
+ else
+ # fallback to `id` command since `logname` is not available
+ Etc.getlogin.should == `id -un`.chomp
+ end
+ end
+ else
+ # Etc.getlogin may return nil if the login name is not set
+ # because of chroot or sudo or something.
+ Etc.getlogin.should be_nil
+ getlogin_null = true
+ end
+ ensure
+ ENV['USER'] = envuser
+ end
+
+ # if getlogin(2) returns NULL, Etc.getlogin returns ENV['USER']
+ if getlogin_null
+ Etc.getlogin.should == ENV['USER']
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/getpwent_spec.rb b/spec/ruby/library/etc/getpwent_spec.rb
new file mode 100644
index 0000000000..9a911aed8f
--- /dev/null
+++ b/spec/ruby/library/etc/getpwent_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/windows'
+require 'etc'
+
+describe "Etc.getpwent" do
+ it_behaves_like :etc_on_windows, :getpwent
+end
diff --git a/spec/ruby/library/etc/getpwnam_spec.rb b/spec/ruby/library/etc/getpwnam_spec.rb
new file mode 100644
index 0000000000..3f4416aa9d
--- /dev/null
+++ b/spec/ruby/library/etc/getpwnam_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require 'etc'
+
+platform_is :windows do
+ describe "Etc.getpwnam" do
+ it "returns nil" do
+ Etc.getpwnam(1).should == nil
+ Etc.getpwnam(nil).should == nil
+ Etc.getpwnam('nil').should == nil
+ end
+ end
+end
+
+platform_is_not :windows do
+ describe "Etc.getpwnam" do
+ it "returns a Etc::Passwd struct instance for the given user" do
+ pw = Etc.getpwnam(`whoami`.strip)
+ pw.is_a?(Etc::Passwd).should == true
+ end
+
+ it "only accepts strings as argument" do
+ -> {
+ Etc.getpwnam(123)
+ Etc.getpwnam(nil)
+ }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/getpwuid_spec.rb b/spec/ruby/library/etc/getpwuid_spec.rb
new file mode 100644
index 0000000000..5b98f0f8d9
--- /dev/null
+++ b/spec/ruby/library/etc/getpwuid_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require 'etc'
+
+platform_is :windows do
+ describe "Etc.getpwuid" do
+ it "returns nil" do
+ Etc.getpwuid(1).should == nil
+ Etc.getpwuid(nil).should == nil
+ Etc.getpwuid('nil').should == nil
+ end
+ end
+end
+
+platform_is_not :windows do
+ describe "Etc.getpwuid" do
+ before :all do
+ @pw = Etc.getpwuid(`id -u`.strip.to_i)
+ end
+
+ it "returns a Etc::Passwd struct instance for the given user" do
+ @pw.is_a?(Etc::Passwd).should == true
+ end
+
+ it "uses Process.uid as the default value for the argument" do
+ pw = Etc.getpwuid
+ pw.should == @pw
+ end
+
+ it "only accepts integers as argument" do
+ -> {
+ Etc.getpwuid("foo")
+ Etc.getpwuid(nil)
+ }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/group_spec.rb b/spec/ruby/library/etc/group_spec.rb
new file mode 100644
index 0000000000..fda808eec9
--- /dev/null
+++ b/spec/ruby/library/etc/group_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'shared/windows'
+require 'etc'
+
+describe "Etc.group" do
+ it_behaves_like :etc_on_windows, :group
+
+ platform_is_not :windows, :android do
+ it "returns a Etc::Group struct" do
+ group = Etc.group
+ begin
+ group.should be_an_instance_of(Etc::Group)
+ ensure
+ Etc.endgrent
+ end
+ end
+
+ it "raises a RuntimeError for parallel iteration" do
+ proc {
+ Etc.group do | group |
+ Etc.group do | group2 |
+ end
+ end
+ }.should raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/nprocessors_spec.rb b/spec/ruby/library/etc/nprocessors_spec.rb
new file mode 100644
index 0000000000..ec7ffc81da
--- /dev/null
+++ b/spec/ruby/library/etc/nprocessors_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require 'etc'
+
+describe "Etc.nprocessors" do
+ it "returns the number of online processors" do
+ Etc.nprocessors.should be_kind_of(Integer)
+ Etc.nprocessors.should >= 1
+ end
+end
diff --git a/spec/ruby/library/etc/passwd_spec.rb b/spec/ruby/library/etc/passwd_spec.rb
new file mode 100644
index 0000000000..d61dada451
--- /dev/null
+++ b/spec/ruby/library/etc/passwd_spec.rb
@@ -0,0 +1,15 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require 'etc'
+
+platform_is_not :windows do
+ describe "Etc.passwd" do
+ it "returns a Etc::Passwd struct" do
+ passwd = Etc.passwd
+ begin
+ passwd.should be_an_instance_of(Etc::Passwd)
+ ensure
+ Etc.endpwent
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/shared/windows.rb b/spec/ruby/library/etc/shared/windows.rb
new file mode 100644
index 0000000000..8bae235199
--- /dev/null
+++ b/spec/ruby/library/etc/shared/windows.rb
@@ -0,0 +1,7 @@
+describe :etc_on_windows, shared: true do
+ platform_is :windows do
+ it "returns nil" do
+ Etc.send(@method).should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/struct_group_spec.rb b/spec/ruby/library/etc/struct_group_spec.rb
new file mode 100644
index 0000000000..b2147e306d
--- /dev/null
+++ b/spec/ruby/library/etc/struct_group_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require 'etc'
+
+describe "Etc::Group" do
+ platform_is_not :windows do
+ grpname = IO.popen(%w'id -gn', err: IO::NULL, &:read)
+ next unless $?.success?
+ grpname.chomp!
+
+ before :all do
+ @g = Etc.getgrgid(`id -g`.strip.to_i)
+ end
+
+ it "returns group name" do
+ @g.name.should == grpname
+ end
+
+ it "returns group password" do
+ @g.passwd.is_a?(String).should == true
+ end
+
+ it "returns group id" do
+ @g.gid.should == `id -g`.strip.to_i
+ end
+
+ it "returns an array of users belonging to the group" do
+ @g.mem.is_a?(Array).should == true
+ end
+
+ it "can be compared to another object" do
+ (@g == nil).should == false
+ (@g == Object.new).should == false
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/struct_passwd_spec.rb b/spec/ruby/library/etc/struct_passwd_spec.rb
new file mode 100644
index 0000000000..dc37c17e7d
--- /dev/null
+++ b/spec/ruby/library/etc/struct_passwd_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require 'etc'
+
+describe "Etc::Passwd" do
+ platform_is_not :windows do
+ before :all do
+ @pw = Etc.getpwuid(`id -u`.strip.to_i)
+ end
+
+ it "returns user name" do
+ @pw.name.should == `id -un`.strip
+ end
+
+ it "returns user password" do
+ @pw.passwd.is_a?(String).should == true
+ end
+
+ it "returns user id" do
+ @pw.uid.should == `id -u`.strip.to_i
+ end
+
+ it "returns user group id" do
+ @pw.gid.should == `id -g`.strip.to_i
+ end
+
+ it "returns user personal information (gecos field)" do
+ @pw.gecos.is_a?(String).should == true
+ end
+
+ it "returns user home directory" do
+ @pw.dir.is_a?(String).should == true
+ end
+
+ it "returns user shell" do
+ @pw.shell.is_a?(String).should == true
+ end
+
+ it "can be compared to another object" do
+ (@pw == nil).should == false
+ (@pw == Object.new).should == false
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/sysconf_spec.rb b/spec/ruby/library/etc/sysconf_spec.rb
new file mode 100644
index 0000000000..e7d59d1b22
--- /dev/null
+++ b/spec/ruby/library/etc/sysconf_spec.rb
@@ -0,0 +1,22 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require 'etc'
+
+platform_is_not :windows do
+ describe "Etc.sysconf" do
+ %w[
+ SC_ARG_MAX SC_CHILD_MAX SC_HOST_NAME_MAX SC_LOGIN_NAME_MAX SC_NGROUPS_MAX
+ SC_CLK_TCK SC_OPEN_MAX SC_PAGESIZE SC_RE_DUP_MAX SC_STREAM_MAX
+ SC_SYMLOOP_MAX SC_TTY_NAME_MAX SC_TZNAME_MAX SC_VERSION
+ ].each do |const|
+ it "returns the value of POSIX.1 system configuration variable #{const}" do
+ var = Etc.const_get(const)
+ value = Etc.sysconf(var)
+ if value.nil?
+ value.should == nil
+ else
+ value.should be_kind_of(Integer)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/etc/sysconfdir_spec.rb b/spec/ruby/library/etc/sysconfdir_spec.rb
new file mode 100644
index 0000000000..d54299c513
--- /dev/null
+++ b/spec/ruby/library/etc/sysconfdir_spec.rb
@@ -0,0 +1,8 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require 'etc'
+
+describe "Etc.sysconfdir" do
+ it "returns a String" do
+ Etc.sysconfdir.should be_an_instance_of(String)
+ end
+end
diff --git a/spec/ruby/library/etc/systmpdir_spec.rb b/spec/ruby/library/etc/systmpdir_spec.rb
new file mode 100644
index 0000000000..99c82903f8
--- /dev/null
+++ b/spec/ruby/library/etc/systmpdir_spec.rb
@@ -0,0 +1,8 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require 'etc'
+
+describe "Etc.systmpdir" do
+ it "returns a String" do
+ Etc.systmpdir.should be_an_instance_of(String)
+ end
+end
diff --git a/spec/ruby/library/expect/expect_spec.rb b/spec/ruby/library/expect/expect_spec.rb
new file mode 100644
index 0000000000..a7041d42ee
--- /dev/null
+++ b/spec/ruby/library/expect/expect_spec.rb
@@ -0,0 +1,62 @@
+platform_is_not :windows do
+ require_relative '../../spec_helper'
+ require 'expect'
+
+ describe "IO#expect" do
+ before :each do
+ @read, @write = IO.pipe
+ end
+
+ after :each do
+ @read.close unless @read.closed?
+ @write.close unless @write.closed?
+ end
+
+ it "matches data against a Regexp" do
+ @write << "prompt> hello"
+
+ result = @read.expect(/[pf]rompt>/)
+ result.should == ["prompt>"]
+ end
+
+ it "matches data against a String" do
+ @write << "prompt> hello"
+
+ result = @read.expect("prompt>")
+ result.should == ["prompt>"]
+ end
+
+ it "returns any captures of the Regexp" do
+ @write << "prompt> hello"
+
+ result = @read.expect(/(pro)mpt(>)/)
+ result.should == ["prompt>", "pro", ">"]
+ end
+
+ it "returns raises IOError if the IO is closed" do
+ @write << "prompt> hello"
+ @read.close
+
+ -> {
+ @read.expect("hello")
+ }.should raise_error(IOError)
+ end
+
+ it "returns nil if eof is hit" do
+ @write << "pro"
+ @write.close
+
+ @read.expect("prompt").should be_nil
+ end
+
+ it "yields the result if a block is given" do
+ @write << "prompt> hello"
+
+ res = nil
+
+ @read.expect("prompt>") { |x| res = x }
+
+ res.should == ["prompt>"]
+ end
+ end
+end
diff --git a/spec/ruby/library/fiber/alive_spec.rb b/spec/ruby/library/fiber/alive_spec.rb
new file mode 100644
index 0000000000..47149d5279
--- /dev/null
+++ b/spec/ruby/library/fiber/alive_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+
+require 'fiber'
+
+describe "Fiber#alive?" do
+ it "returns true for a Fiber that hasn't had #resume called" do
+ fiber = Fiber.new { true }
+ fiber.alive?.should be_true
+ end
+
+ # FIXME: Better description?
+ it "returns true for a Fiber that's yielded to the caller" do
+ fiber = Fiber.new { Fiber.yield }
+ fiber.resume
+ fiber.alive?.should be_true
+ end
+
+ it "returns true when called from its Fiber" do
+ fiber = Fiber.new { fiber.alive?.should be_true }
+ fiber.resume
+ end
+
+ it "doesn't invoke the block associated with the Fiber" do
+ offthehook = mock('do not call')
+ offthehook.should_not_receive(:ring)
+ fiber = Fiber.new { offthehook.ring }
+ fiber.alive?
+ end
+
+ it "returns false for a Fiber that's dead" do
+ fiber = Fiber.new { true }
+ fiber.resume
+ -> { fiber.resume }.should raise_error(FiberError)
+ fiber.alive?.should be_false
+ end
+
+ it "always returns false for a dead Fiber" do
+ fiber = Fiber.new { true }
+ fiber.resume
+ -> { fiber.resume }.should raise_error(FiberError)
+ fiber.alive?.should be_false
+ -> { fiber.resume }.should raise_error(FiberError)
+ fiber.alive?.should be_false
+ fiber.alive?.should be_false
+ end
+end
diff --git a/spec/ruby/library/fiber/current_spec.rb b/spec/ruby/library/fiber/current_spec.rb
new file mode 100644
index 0000000000..e67d7d050a
--- /dev/null
+++ b/spec/ruby/library/fiber/current_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+
+require 'fiber'
+
+describe "Fiber.current" do
+ it "returns the root Fiber when called outside of a Fiber" do
+ root = Fiber.current
+ root.should be_an_instance_of(Fiber)
+ # We can always transfer to the root Fiber; it will never die
+ 5.times do
+ root.transfer.should be_nil
+ root.alive?.should be_true
+ end
+ end
+
+ it "returns the current Fiber when called from a Fiber" do
+ fiber = Fiber.new do
+ this = Fiber.current
+ this.should be_an_instance_of(Fiber)
+ this.should == fiber
+ this.alive?.should be_true
+ end
+ fiber.resume
+ end
+
+ it "returns the current Fiber when called from a Fiber that transferred to another" do
+ states = []
+ fiber = Fiber.new do
+ states << :fiber
+ this = Fiber.current
+ this.should be_an_instance_of(Fiber)
+ this.should == fiber
+ this.alive?.should be_true
+ end
+
+ fiber2 = Fiber.new do
+ states << :fiber2
+ fiber.transfer
+ flunk
+ end
+
+ fiber3 = Fiber.new do
+ states << :fiber3
+ fiber2.transfer
+ ruby_version_is '3.0' do
+ states << :fiber3_terminated
+ end
+ ruby_version_is '' ... '3.0' do
+ flunk
+ end
+ end
+
+ fiber3.resume
+
+ ruby_version_is "" ... "3.0" do
+ states.should == [:fiber3, :fiber2, :fiber]
+ end
+
+ ruby_version_is "3.0" do
+ states.should == [:fiber3, :fiber2, :fiber, :fiber3_terminated]
+ end
+ end
+end
diff --git a/spec/ruby/library/fiber/resume_spec.rb b/spec/ruby/library/fiber/resume_spec.rb
new file mode 100644
index 0000000000..8b7c104a6f
--- /dev/null
+++ b/spec/ruby/library/fiber/resume_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+require 'fiber'
+
+describe "Fiber#resume" do
+ ruby_version_is '' ... '3.0' do
+ it "raises a FiberError if the Fiber has transferred control to another Fiber" do
+ fiber1 = Fiber.new { true }
+ fiber2 = Fiber.new { fiber1.transfer; Fiber.yield }
+ fiber2.resume
+ -> { fiber2.resume }.should raise_error(FiberError)
+ end
+
+ it "raises a FiberError if the Fiber attempts to resume a resuming fiber" do
+ root_fiber = Fiber.current
+ fiber1 = Fiber.new { root_fiber.resume }
+ -> { fiber1.resume }.should raise_error(FiberError, /double resume/)
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "can work with Fiber#transfer" do
+ fiber1 = Fiber.new { true }
+ fiber2 = Fiber.new { fiber1.transfer; Fiber.yield 10 ; Fiber.yield 20; raise }
+ fiber2.resume.should == 10
+ fiber2.resume.should == 20
+ end
+
+ it "raises a FiberError if the Fiber attempts to resume a resuming fiber" do
+ root_fiber = Fiber.current
+ fiber1 = Fiber.new { root_fiber.resume }
+ -> { fiber1.resume }.should raise_error(FiberError, /attempt to resume a resuming fiber/)
+ end
+ end
+end
diff --git a/spec/ruby/library/fiber/transfer_spec.rb b/spec/ruby/library/fiber/transfer_spec.rb
new file mode 100644
index 0000000000..7af548da1a
--- /dev/null
+++ b/spec/ruby/library/fiber/transfer_spec.rb
@@ -0,0 +1,128 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/fiber/resume'
+
+require 'fiber'
+
+describe "Fiber#transfer" do
+ it_behaves_like :fiber_resume, :transfer
+end
+
+describe "Fiber#transfer" do
+ it "transfers control from one Fiber to another when called from a Fiber" do
+ fiber1 = Fiber.new { :fiber1 }
+ fiber2 = Fiber.new { fiber1.transfer; :fiber2 }
+
+ ruby_version_is '' ... '3.0' do
+ fiber2.resume.should == :fiber1
+ end
+ ruby_version_is '3.0' do
+ fiber2.resume.should == :fiber2
+ end
+ end
+
+ it "returns to the root Fiber when finished" do
+ f1 = Fiber.new { :fiber_1 }
+ f2 = Fiber.new { f1.transfer; :fiber_2 }
+
+ f2.transfer.should == :fiber_1
+ f2.transfer.should == :fiber_2
+ end
+
+ it "can be invoked from the same Fiber it transfers control to" do
+ states = []
+ fiber = Fiber.new { states << :start; fiber.transfer; states << :end }
+ fiber.transfer
+ states.should == [:start, :end]
+
+ states = []
+ fiber = Fiber.new { states << :start; fiber.transfer; states << :end }
+ fiber.resume
+ states.should == [:start, :end]
+ end
+
+ ruby_version_is '' ... '3.0' do
+ it "can transfer control to a Fiber that has transferred to another Fiber" do
+ states = []
+ fiber1 = Fiber.new { states << :fiber1 }
+ fiber2 = Fiber.new { states << :fiber2_start; fiber1.transfer; states << :fiber2_end}
+ fiber2.resume.should == [:fiber2_start, :fiber1]
+ fiber2.transfer.should == [:fiber2_start, :fiber1, :fiber2_end]
+ end
+ end
+
+ ruby_version_is '3.0' do
+ it "can not transfer control to a Fiber that has suspended by Fiber.yield" do
+ states = []
+ fiber1 = Fiber.new { states << :fiber1 }
+ fiber2 = Fiber.new { states << :fiber2_start; Fiber.yield fiber1.transfer; states << :fiber2_end}
+ fiber2.resume.should == [:fiber2_start, :fiber1]
+ -> { fiber2.transfer }.should raise_error(FiberError)
+ end
+ end
+
+ it "raises a FiberError when transferring to a Fiber which resumes itself" do
+ fiber = Fiber.new { fiber.resume }
+ -> { fiber.transfer }.should raise_error(FiberError)
+ end
+
+ it "works if Fibers in different Threads each transfer to a Fiber in the same Thread" do
+ # This catches a bug where Fibers are running on a thread-pool
+ # and Fibers from a different Ruby Thread reuse the same native thread.
+ # Caching the Ruby Thread based on the native thread is not correct in that case,
+ # and the check for "fiber called across threads" in Fiber#transfer
+ # might be incorrect based on that.
+ 2.times do
+ Thread.new do
+ io_fiber = Fiber.new do |calling_fiber|
+ calling_fiber.transfer
+ end
+ io_fiber.transfer(Fiber.current)
+ value = Object.new
+ io_fiber.transfer(value).should equal value
+ end.join
+ end
+ end
+
+ it "transfers control between a non-main thread's root fiber to a child fiber and back again" do
+ states = []
+ thread = Thread.new do
+ f1 = Fiber.new do |f0|
+ states << 0
+ value2 = f0.transfer(1)
+ states << value2
+ 3
+ end
+
+ value1 = f1.transfer(Fiber.current)
+ states << value1
+ value3 = f1.transfer(2)
+ states << value3
+ end
+ thread.join
+ states.should == [0, 1, 2, 3]
+ end
+
+ ruby_version_is "" ... "3.0" do
+ it "runs until Fiber.yield" do
+ obj = mock('obj')
+ obj.should_not_receive(:do)
+ fiber = Fiber.new { 1 + 2; Fiber.yield; obj.do }
+ fiber.transfer
+ end
+
+ it "resumes from the last call to Fiber.yield on subsequent invocations" do
+ fiber = Fiber.new { Fiber.yield :first; :second }
+ fiber.transfer.should == :first
+ fiber.transfer.should == :second
+ end
+
+ it "sets the block parameters to its arguments on the first invocation" do
+ first = mock('first')
+ first.should_receive(:arg).with(:first).twice
+
+ fiber = Fiber.new { |arg| first.arg arg; Fiber.yield; first.arg arg; }
+ fiber.transfer :first
+ fiber.transfer :second
+ end
+ end
+end
diff --git a/spec/ruby/library/fiddle/handle/initialize_spec.rb b/spec/ruby/library/fiddle/handle/initialize_spec.rb
new file mode 100644
index 0000000000..51c2470efd
--- /dev/null
+++ b/spec/ruby/library/fiddle/handle/initialize_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'fiddle'
+
+describe "Fiddle::Handle#initialize" do
+ it "raises Fiddle::DLError if the library cannot be found" do
+ -> {
+ Fiddle::Handle.new("doesnotexist.doesnotexist")
+ }.should raise_error(Fiddle::DLError)
+ end
+end
diff --git a/spec/ruby/library/find/find_spec.rb b/spec/ruby/library/find/find_spec.rb
new file mode 100644
index 0000000000..7cd76fa01b
--- /dev/null
+++ b/spec/ruby/library/find/find_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require 'find'
+
+describe "Find.find" do
+ before :each do
+ FindDirSpecs.create_mock_dirs
+ end
+
+ after :each do
+ FindDirSpecs.delete_mock_dirs
+ end
+
+ describe "when called without a block" do
+ it "returns an Enumerator" do
+ Find.find(FindDirSpecs.mock_dir).should be_an_instance_of(Enumerator)
+ Find.find(FindDirSpecs.mock_dir).to_a.sort.should == FindDirSpecs.expected_paths
+ end
+ end
+
+ it "should recursively yield every file in the directory" do
+ a = []
+
+ Find.find(FindDirSpecs.mock_dir) do |file|
+ a << file
+ end
+
+ a.sort.should == FindDirSpecs.expected_paths
+ end
+end
diff --git a/spec/ruby/library/find/fixtures/common.rb b/spec/ruby/library/find/fixtures/common.rb
new file mode 100644
index 0000000000..14a7edb09a
--- /dev/null
+++ b/spec/ruby/library/find/fixtures/common.rb
@@ -0,0 +1,174 @@
+module FindDirSpecs
+ def self.mock_dir(dirs = ['find_specs_mock'])
+ @mock_dir ||= tmp("")
+ File.join @mock_dir, dirs
+ end
+
+ # The names of the fixture directories and files used by
+ # various Find specs.
+ def self.mock_dir_files
+ unless @mock_dir_files
+ @mock_dir_files = %w[
+ .dotfile
+ .dotsubdir/.dotfile
+ .dotsubdir/nondotfile
+
+ deeply/.dotfile
+ deeply/nested/.dotfile.ext
+ deeply/nested/directory/structure/.ext
+ deeply/nested/directory/structure/bar
+ deeply/nested/directory/structure/baz
+ deeply/nested/directory/structure/file_one
+ deeply/nested/directory/structure/file_one.ext
+ deeply/nested/directory/structure/foo
+ deeply/nondotfile
+
+ file_one.ext
+ file_two.ext
+
+ dir_filename_ordering
+ dir/filename_ordering
+
+ nondotfile
+
+ subdir_one/.dotfile
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ subdir_two/nondotfile.ext
+
+ brace/a
+ brace/a.js
+ brace/a.erb
+ brace/a.js.rjs
+ brace/a.html.erb
+
+ special/+
+
+ special/^
+ special/$
+
+ special/(
+ special/)
+ special/[
+ special/]
+ special/{
+ special/}
+
+ special/test{1}/file[1]
+ ]
+
+ platform_is_not :windows do
+ @mock_dir_files += %w[
+ special/*
+ special/?
+
+ special/|
+ ]
+ end
+ end
+
+ @mock_dir_files
+ end
+
+ def self.create_mock_dirs
+ umask = File.umask 0
+ mock_dir_files.each do |name|
+ file = File.join mock_dir, name
+ mkdir_p File.dirname(file)
+ touch file
+ end
+ File.umask umask
+ end
+
+ def self.delete_mock_dirs
+ rm_r mock_dir
+ end
+
+ def self.expected_paths
+ unless @expected_paths
+ @expected_paths = %w[
+ .dotfile
+
+ .dotsubdir
+ .dotsubdir/.dotfile
+ .dotsubdir/nondotfile
+
+ deeply
+ deeply/.dotfile
+
+ deeply/nested
+ deeply/nested/.dotfile.ext
+
+ deeply/nested/directory
+
+ deeply/nested/directory/structure
+ deeply/nested/directory/structure/.ext
+ deeply/nested/directory/structure/bar
+ deeply/nested/directory/structure/baz
+ deeply/nested/directory/structure/file_one
+ deeply/nested/directory/structure/file_one.ext
+ deeply/nested/directory/structure/foo
+ deeply/nondotfile
+
+ file_one.ext
+ file_two.ext
+
+ dir_filename_ordering
+
+ dir
+ dir/filename_ordering
+
+ nondotfile
+
+ subdir_one
+ subdir_one/.dotfile
+ subdir_one/nondotfile
+
+ subdir_two
+ subdir_two/nondotfile
+ subdir_two/nondotfile.ext
+
+ brace
+ brace/a
+ brace/a.js
+ brace/a.erb
+ brace/a.js.rjs
+ brace/a.html.erb
+
+ special
+ special/+
+
+ special/^
+ special/$
+
+ special/(
+ special/)
+ special/[
+ special/]
+ special/{
+ special/}
+
+ special/test{1}
+ special/test{1}/file[1]
+ ]
+
+ platform_is_not :windows do
+ @expected_paths += %w[
+ special/*
+ special/?
+
+ special/|
+ ]
+ end
+
+ @expected_paths.map! do |file|
+ File.join(mock_dir, file)
+ end
+
+ @expected_paths << mock_dir
+ @expected_paths.sort!
+ end
+
+ @expected_paths
+ end
+end
diff --git a/spec/ruby/library/find/prune_spec.rb b/spec/ruby/library/find/prune_spec.rb
new file mode 100644
index 0000000000..25dc2cbf3e
--- /dev/null
+++ b/spec/ruby/library/find/prune_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require 'find'
+
+describe "Find.prune" do
+ it "should throw :prune" do
+ msg = catch(:prune) do
+ Find.prune
+ end
+
+ msg.should == nil
+ end
+end
diff --git a/spec/ruby/library/getoptlong/each_option_spec.rb b/spec/ruby/library/getoptlong/each_option_spec.rb
new file mode 100644
index 0000000000..c6d82af86d
--- /dev/null
+++ b/spec/ruby/library/getoptlong/each_option_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require 'getoptlong'
+require_relative 'shared/each'
+
+describe "GetoptLong#each_option" do
+ it_behaves_like :getoptlong_each, :each_option
+end
diff --git a/spec/ruby/library/getoptlong/each_spec.rb b/spec/ruby/library/getoptlong/each_spec.rb
new file mode 100644
index 0000000000..d9022f02af
--- /dev/null
+++ b/spec/ruby/library/getoptlong/each_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require 'getoptlong'
+require_relative 'shared/each'
+
+describe "GetoptLong#each" do
+ it_behaves_like :getoptlong_each, :each
+end
diff --git a/spec/ruby/library/getoptlong/error_message_spec.rb b/spec/ruby/library/getoptlong/error_message_spec.rb
new file mode 100644
index 0000000000..1ed9419f6c
--- /dev/null
+++ b/spec/ruby/library/getoptlong/error_message_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require 'getoptlong'
+
+describe "GetoptLong#error_message" do
+ it "returns nil if no error occurred" do
+ opts = GetoptLong.new
+ opts.error_message.should == nil
+ end
+
+ it "returns the error message of the last error that occurred" do
+ argv [] do
+ opts = GetoptLong.new
+ opts.quiet = true
+ opts.get
+ -> {
+ opts.ordering = GetoptLong::PERMUTE
+ }.should raise_error(ArgumentError) { |e|
+ e.message.should == "argument error"
+ opts.error_message.should == "argument error"
+ }
+ end
+ end
+end
diff --git a/spec/ruby/library/getoptlong/get_option_spec.rb b/spec/ruby/library/getoptlong/get_option_spec.rb
new file mode 100644
index 0000000000..3cb2044379
--- /dev/null
+++ b/spec/ruby/library/getoptlong/get_option_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require 'getoptlong'
+require_relative 'shared/get'
+
+describe "GetoptLong#get_option" do
+ it_behaves_like :getoptlong_get, :get_option
+end
diff --git a/spec/ruby/library/getoptlong/get_spec.rb b/spec/ruby/library/getoptlong/get_spec.rb
new file mode 100644
index 0000000000..a8ec586fc9
--- /dev/null
+++ b/spec/ruby/library/getoptlong/get_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require 'getoptlong'
+require_relative 'shared/get'
+
+describe "GetoptLong#get" do
+ it_behaves_like :getoptlong_get, :get
+end
diff --git a/spec/ruby/library/getoptlong/initialize_spec.rb b/spec/ruby/library/getoptlong/initialize_spec.rb
new file mode 100644
index 0000000000..782edbd981
--- /dev/null
+++ b/spec/ruby/library/getoptlong/initialize_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require 'getoptlong'
+
+describe "GetoptLong#initialize" do
+ it "sets ordering to REQUIRE_ORDER if ENV['POSIXLY_CORRECT'] is set" do
+ begin
+ old_env_value = ENV["POSIXLY_CORRECT"]
+ ENV["POSIXLY_CORRECT"] = ""
+
+ opt = GetoptLong.new
+ opt.ordering.should == GetoptLong::REQUIRE_ORDER
+ ensure
+ ENV["POSIXLY_CORRECT"] = old_env_value
+ end
+ end
+
+ it "sets ordering to PERMUTE if ENV['POSIXLY_CORRECT'] is not set" do
+ begin
+ old_env_value = ENV["POSIXLY_CORRECT"]
+ ENV["POSIXLY_CORRECT"] = nil
+
+ opt = GetoptLong.new
+ opt.ordering.should == GetoptLong::PERMUTE
+ ensure
+ ENV["POSIXLY_CORRECT"] = old_env_value
+ end
+ end
+end
diff --git a/spec/ruby/library/getoptlong/ordering_spec.rb b/spec/ruby/library/getoptlong/ordering_spec.rb
new file mode 100644
index 0000000000..695d1cafa7
--- /dev/null
+++ b/spec/ruby/library/getoptlong/ordering_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require 'getoptlong'
+
+describe "GetoptLong#ordering=" do
+ it "raises an ArgumentError if called after processing has started" do
+ argv [ "--size", "10k", "--verbose" ] do
+ opts = GetoptLong.new([ '--size', GetoptLong::REQUIRED_ARGUMENT ],
+ [ '--verbose', GetoptLong::NO_ARGUMENT ])
+ opts.quiet = true
+ opts.get
+
+ -> {
+ opts.ordering = GetoptLong::PERMUTE
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises an ArgumentError if given an invalid value" do
+ opts = GetoptLong.new
+
+ -> {
+ opts.ordering = 12345
+ }.should raise_error(ArgumentError)
+ end
+
+ it "does not allow changing ordering to PERMUTE if ENV['POSIXLY_CORRECT'] is set" do
+ begin
+ old_env_value = ENV['POSIXLY_CORRECT']
+ ENV['POSIXLY_CORRECT'] = ""
+
+ opts = GetoptLong.new
+ opts.ordering = GetoptLong::PERMUTE
+ opts.ordering.should == GetoptLong::REQUIRE_ORDER
+ ensure
+ ENV['POSIXLY_CORRECT'] = old_env_value
+ end
+ end
+end
diff --git a/spec/ruby/library/getoptlong/set_options_spec.rb b/spec/ruby/library/getoptlong/set_options_spec.rb
new file mode 100644
index 0000000000..36b9c579c4
--- /dev/null
+++ b/spec/ruby/library/getoptlong/set_options_spec.rb
@@ -0,0 +1,98 @@
+require_relative '../../spec_helper'
+require 'getoptlong'
+
+describe "GetoptLong#set_options" do
+ before :each do
+ @opts = GetoptLong.new
+ end
+
+ it "allows setting command line options" do
+ argv ["--size", "10k", "-v", "arg1", "arg2"] do
+ @opts.set_options(
+ ["--size", GetoptLong::REQUIRED_ARGUMENT],
+ ["--verbose", "-v", GetoptLong::NO_ARGUMENT]
+ )
+
+ @opts.get.should == ["--size", "10k"]
+ @opts.get.should == ["--verbose", ""]
+ @opts.get.should == nil
+ end
+ end
+
+ it "discards previously defined command line options" do
+ argv ["--size", "10k", "-v", "arg1", "arg2"] do
+ @opts.set_options(
+ ["--size", GetoptLong::REQUIRED_ARGUMENT],
+ ["--verbose", "-v", GetoptLong::NO_ARGUMENT]
+ )
+
+ @opts.set_options(
+ ["-s", "--size", GetoptLong::REQUIRED_ARGUMENT],
+ ["-v", GetoptLong::NO_ARGUMENT]
+ )
+
+ @opts.get.should == ["-s", "10k"]
+ @opts.get.should == ["-v", ""]
+ @opts.get.should == nil
+ end
+ end
+
+ it "raises an ArgumentError if too many argument flags where given" do
+ argv [] do
+ -> {
+ @opts.set_options(["--size", GetoptLong::NO_ARGUMENT, GetoptLong::REQUIRED_ARGUMENT])
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises a RuntimeError if processing has already started" do
+ argv [] do
+ @opts.get
+ -> {
+ @opts.set_options()
+ }.should raise_error(RuntimeError)
+ end
+ end
+
+ it "raises an ArgumentError if no argument flag was given" do
+ argv [] do
+ -> {
+ @opts.set_options(["--size"])
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises an ArgumentError if one of the given arguments is not an Array" do
+ argv [] do
+ -> {
+ @opts.set_options(
+ ["--size", GetoptLong::REQUIRED_ARGUMENT],
+ "test")
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises an ArgumentError if the same option is given twice" do
+ argv [] do
+ -> {
+ @opts.set_options(
+ ["--size", GetoptLong::NO_ARGUMENT],
+ ["--size", GetoptLong::OPTIONAL_ARGUMENT])
+ }.should raise_error(ArgumentError)
+
+ -> {
+ @opts.set_options(
+ ["--size", GetoptLong::NO_ARGUMENT],
+ ["-s", "--size", GetoptLong::OPTIONAL_ARGUMENT])
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "raises an ArgumentError if the given option is invalid" do
+ argv [] do
+ -> {
+ @opts.set_options(["-size", GetoptLong::NO_ARGUMENT])
+ }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/getoptlong/shared/each.rb b/spec/ruby/library/getoptlong/shared/each.rb
new file mode 100644
index 0000000000..b534e24c0f
--- /dev/null
+++ b/spec/ruby/library/getoptlong/shared/each.rb
@@ -0,0 +1,18 @@
+describe :getoptlong_each, shared: true do
+ before :each do
+ @opts = GetoptLong.new(
+ [ '--size', '-s', GetoptLong::REQUIRED_ARGUMENT ],
+ [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
+ [ '--query', '-q', GetoptLong::NO_ARGUMENT ],
+ [ '--check', '--valid', '-c', GetoptLong::NO_ARGUMENT ]
+ )
+ end
+
+ it "passes each argument/value pair to the block" do
+ argv [ "--size", "10k", "-v", "-q", "a.txt", "b.txt" ] do
+ pairs = []
+ @opts.send(@method) { |arg, val| pairs << [ arg, val ] }
+ pairs.should == [ [ "--size", "10k" ], [ "--verbose", "" ], [ "--query", ""] ]
+ end
+ end
+end
diff --git a/spec/ruby/library/getoptlong/shared/get.rb b/spec/ruby/library/getoptlong/shared/get.rb
new file mode 100644
index 0000000000..f44cf583d2
--- /dev/null
+++ b/spec/ruby/library/getoptlong/shared/get.rb
@@ -0,0 +1,62 @@
+describe :getoptlong_get, shared: true do
+ before :each do
+ @opts = GetoptLong.new(
+ [ '--size', '-s', GetoptLong::REQUIRED_ARGUMENT ],
+ [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
+ [ '--query', '-q', GetoptLong::NO_ARGUMENT ],
+ [ '--check', '--valid', '-c', GetoptLong::NO_ARGUMENT ]
+ )
+ @opts.quiet = true # silence using $deferr
+ end
+
+ it "returns the next option name and its argument as an Array" do
+ argv [ "--size", "10k", "-v", "-q", "a.txt", "b.txt" ] do
+ @opts.send(@method).should == [ "--size", "10k" ]
+ @opts.send(@method).should == [ "--verbose", "" ]
+ @opts.send(@method).should == [ "--query", ""]
+ @opts.send(@method).should == nil
+ end
+ end
+
+ it "shifts ARGV on each call" do
+ argv [ "--size", "10k", "-v", "-q", "a.txt", "b.txt" ] do
+ @opts.send(@method)
+ ARGV.should == [ "-v", "-q", "a.txt", "b.txt" ]
+
+ @opts.send(@method)
+ ARGV.should == [ "-q", "a.txt", "b.txt" ]
+
+ @opts.send(@method)
+ ARGV.should == [ "a.txt", "b.txt" ]
+
+ @opts.send(@method)
+ ARGV.should == [ "a.txt", "b.txt" ]
+ end
+ end
+
+ it "terminates processing when encountering '--'" do
+ argv [ "--size", "10k", "--", "-v", "-q", "a.txt", "b.txt" ] do
+ @opts.send(@method)
+ ARGV.should == ["--", "-v", "-q", "a.txt", "b.txt"]
+
+ @opts.send(@method)
+ ARGV.should == ["-v", "-q", "a.txt", "b.txt"]
+
+ @opts.send(@method)
+ ARGV.should == ["-v", "-q", "a.txt", "b.txt"]
+ end
+ end
+
+ it "raises a if an argument was required, but none given" do
+ argv [ "--size" ] do
+ -> { @opts.send(@method) }.should raise_error(GetoptLong::MissingArgument)
+ end
+ end
+
+ # https://bugs.ruby-lang.org/issues/13858
+ it "returns multiline argument" do
+ argv [ "--size=\n10k\n" ] do
+ @opts.send(@method).should == [ "--size", "\n10k\n" ]
+ end
+ end
+end
diff --git a/spec/ruby/library/getoptlong/terminate_spec.rb b/spec/ruby/library/getoptlong/terminate_spec.rb
new file mode 100644
index 0000000000..a12d1df2ef
--- /dev/null
+++ b/spec/ruby/library/getoptlong/terminate_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require 'getoptlong'
+
+describe "GetoptLong#terminate" do
+ before :each do
+ @opts = GetoptLong.new(
+ [ '--size', '-s', GetoptLong::REQUIRED_ARGUMENT ],
+ [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
+ [ '--query', '-q', GetoptLong::NO_ARGUMENT ],
+ [ '--check', '--valid', '-c', GetoptLong::NO_ARGUMENT ]
+ )
+ end
+
+ it "terminates option processing" do
+ argv [ "--size", "10k", "-v", "-q", "a.txt", "b.txt" ] do
+ @opts.get.should == [ "--size", "10k" ]
+ @opts.terminate
+ @opts.get.should == nil
+ end
+ end
+
+ it "returns self when option processing is terminated" do
+ @opts.terminate.should == @opts
+ end
+
+ it "returns nil when option processing was already terminated" do
+ @opts.terminate
+ @opts.terminate.should == nil
+ end
+end
diff --git a/spec/ruby/library/getoptlong/terminated_spec.rb b/spec/ruby/library/getoptlong/terminated_spec.rb
new file mode 100644
index 0000000000..6108a7f6e9
--- /dev/null
+++ b/spec/ruby/library/getoptlong/terminated_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require 'getoptlong'
+
+describe "GetoptLong#terminated?" do
+ it "returns true if option processing has terminated" do
+ argv [ "--size", "10k" ] do
+ opts = GetoptLong.new(["--size", GetoptLong::REQUIRED_ARGUMENT])
+ opts.should_not.terminated?
+
+ opts.get.should == ["--size", "10k"]
+ opts.should_not.terminated?
+
+ opts.get.should == nil
+ opts.should.terminated?
+ end
+ end
+end
diff --git a/spec/ruby/library/io-wait/wait_readable_spec.rb b/spec/ruby/library/io-wait/wait_readable_spec.rb
new file mode 100644
index 0000000000..06ffbda5c8
--- /dev/null
+++ b/spec/ruby/library/io-wait/wait_readable_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ''...'3.2' do
+ require 'io/wait'
+end
+
+describe "IO#wait_readable" do
+ before :each do
+ @io = File.new(__FILE__ )
+ end
+
+ after :each do
+ @io.close
+ end
+
+ it "waits for the IO to become readable with no timeout" do
+ @io.wait_readable.should == @io
+ end
+
+ it "waits for the IO to become readable with the given timeout" do
+ @io.wait_readable(1).should == @io
+ end
+
+ it "waits for the IO to become readable with the given large timeout" do
+ @io.wait_readable(365 * 24 * 60 * 60).should == @io
+ end
+end
diff --git a/spec/ruby/library/io-wait/wait_writable_spec.rb b/spec/ruby/library/io-wait/wait_writable_spec.rb
new file mode 100644
index 0000000000..8c44780d39
--- /dev/null
+++ b/spec/ruby/library/io-wait/wait_writable_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ''...'3.2' do
+ require 'io/wait'
+end
+
+describe "IO#wait_writable" do
+ it "waits for the IO to become writable with no timeout" do
+ STDOUT.wait_writable.should == STDOUT
+ end
+
+ it "waits for the IO to become writable with the given timeout" do
+ STDOUT.wait_writable(1).should == STDOUT
+ end
+
+ it "waits for the IO to become writable with the given large timeout" do
+ # Represents one year and is larger than a 32-bit int
+ STDOUT.wait_writable(365 * 24 * 60 * 60).should == STDOUT
+ end
+end
diff --git a/spec/ruby/library/ipaddr/hton_spec.rb b/spec/ruby/library/ipaddr/hton_spec.rb
new file mode 100644
index 0000000000..9c0b821abf
--- /dev/null
+++ b/spec/ruby/library/ipaddr/hton_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require 'ipaddr'
+
+describe "IPAddr#hton" do
+
+ it "converts IPAddr to network byte order" do
+ addr = ''
+ IPAddr.new("1234:5678:9abc:def0:1234:5678:9abc:def0").hton.each_byte do |c|
+ addr += sprintf("%02x", c)
+ end
+ addr.should == "123456789abcdef0123456789abcdef0"
+ addr = ''
+ IPAddr.new("123.45.67.89").hton.each_byte do |c|
+ addr += sprintf("%02x", c)
+ end
+ addr.should == sprintf("%02x%02x%02x%02x", 123, 45, 67, 89)
+ end
+
+end
+
+describe "IPAddr#new_ntoh" do
+
+ it "creates a new IPAddr using hton notation" do
+ a = IPAddr.new("3ffe:505:2::")
+ IPAddr.new_ntoh(a.hton).to_s.should == "3ffe:505:2::"
+ a = IPAddr.new("192.168.2.1")
+ IPAddr.new_ntoh(a.hton).to_s.should == "192.168.2.1"
+ end
+
+end
diff --git a/spec/ruby/library/ipaddr/ipv4_conversion_spec.rb b/spec/ruby/library/ipaddr/ipv4_conversion_spec.rb
new file mode 100644
index 0000000000..1128c16dd2
--- /dev/null
+++ b/spec/ruby/library/ipaddr/ipv4_conversion_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require 'ipaddr'
+
+describe "IPAddr#ipv4_compat" do
+
+ it "should ipv4_compat?" do
+ a = IPAddr.new("::192.168.1.2")
+ a.to_s.should == "::192.168.1.2"
+ a.to_string.should == "0000:0000:0000:0000:0000:0000:c0a8:0102"
+ a.family.should == Socket::AF_INET6
+ a.should.ipv4_compat?
+ b = a.native
+ b.to_s.should == "192.168.1.2"
+ b.family.should == Socket::AF_INET
+ b.should_not.ipv4_compat?
+
+ a = IPAddr.new("192.168.1.2")
+ b = a.ipv4_compat
+ b.to_s.should == "::192.168.1.2"
+ b.family.should == Socket::AF_INET6
+ end
+
+end
+
+describe "IPAddr#ipv4_mapped" do
+
+ it "should ipv4_mapped" do
+ a = IPAddr.new("::ffff:192.168.1.2")
+ a.to_s.should == "::ffff:192.168.1.2"
+ a.to_string.should == "0000:0000:0000:0000:0000:ffff:c0a8:0102"
+ a.family.should == Socket::AF_INET6
+ a.should.ipv4_mapped?
+ b = a.native
+ b.to_s.should == "192.168.1.2"
+ b.family.should == Socket::AF_INET
+ b.should_not.ipv4_mapped?
+
+ a = IPAddr.new("192.168.1.2")
+ b = a.ipv4_mapped
+ b.to_s.should == "::ffff:192.168.1.2"
+ b.family.should == Socket::AF_INET6
+ end
+
+end
diff --git a/spec/ruby/library/ipaddr/new_spec.rb b/spec/ruby/library/ipaddr/new_spec.rb
new file mode 100644
index 0000000000..053928c3cf
--- /dev/null
+++ b/spec/ruby/library/ipaddr/new_spec.rb
@@ -0,0 +1,110 @@
+require_relative '../../spec_helper'
+require 'ipaddr'
+
+describe "IPAddr#new" do
+ it "initializes IPAddr" do
+ ->{ IPAddr.new("3FFE:505:ffff::/48") }.should_not raise_error
+ ->{ IPAddr.new("0:0:0:1::") }.should_not raise_error
+ ->{ IPAddr.new("2001:200:300::/48") }.should_not raise_error
+ end
+
+ it "initializes IPAddr ipv6 address with short notation" do
+ a = IPAddr.new
+ a.to_s.should == "::"
+ a.to_string.should == "0000:0000:0000:0000:0000:0000:0000:0000"
+ a.family.should == Socket::AF_INET6
+ end
+
+ it "initializes IPAddr ipv6 address with long notation" do
+ a = IPAddr.new("0123:4567:89ab:cdef:0ABC:DEF0:1234:5678")
+ a.to_s.should == "123:4567:89ab:cdef:abc:def0:1234:5678"
+ a.to_string.should == "0123:4567:89ab:cdef:0abc:def0:1234:5678"
+ a.family.should == Socket::AF_INET6
+ end
+
+ it "initializes IPAddr ipv6 address with / subnet notation" do
+ a = IPAddr.new("3ffe:505:2::/48")
+ a.to_s.should == "3ffe:505:2::"
+ a.to_string.should == "3ffe:0505:0002:0000:0000:0000:0000:0000"
+ a.family.should == Socket::AF_INET6
+ a.should_not.ipv4?
+ a.should.ipv6?
+ a.inspect.should == "#<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0000/ffff:ffff:ffff:0000:0000:0000:0000:0000>"
+ end
+
+ it "initializes IPAddr ipv6 address with mask subnet notation" do
+ a = IPAddr.new("3ffe:505:2::/ffff:ffff:ffff::")
+ a.to_s.should == "3ffe:505:2::"
+ a.to_string.should == "3ffe:0505:0002:0000:0000:0000:0000:0000"
+ a.family.should == Socket::AF_INET6
+ end
+
+ it "initializes IPAddr ipv4 address with all zeroes" do
+ a = IPAddr.new("0.0.0.0")
+ a.to_s.should == "0.0.0.0"
+ a.to_string.should == "0.0.0.0"
+ a.family.should == Socket::AF_INET
+ end
+
+ it "initializes IPAddr ipv4 address" do
+ a = IPAddr.new("192.168.1.2")
+ a.to_s.should == "192.168.1.2"
+ a.to_string.should == "192.168.1.2"
+ a.family.should == Socket::AF_INET
+ a.should.ipv4?
+ a.should_not.ipv6?
+ end
+
+ it "initializes IPAddr ipv4 address with / subnet notation" do
+ a = IPAddr.new("192.168.1.2/24")
+ a.to_s.should == "192.168.1.0"
+ a.to_string.should == "192.168.1.0"
+ a.family.should == Socket::AF_INET
+ a.inspect.should == "#<IPAddr: IPv4:192.168.1.0/255.255.255.0>"
+ end
+
+ it "initializes IPAddr ipv4 address with subnet mask" do
+ a = IPAddr.new("192.168.1.2/255.255.255.0")
+ a.to_s.should == "192.168.1.0"
+ a.to_string.should == "192.168.1.0"
+ a.family.should == Socket::AF_INET
+ end
+
+ it "initializes IPAddr ipv4 mapped address with subnet mask" do
+ a = IPAddr.new("::1:192.168.1.2/120")
+ a.to_s.should == "::1:c0a8:100"
+ a.to_string.should == "0000:0000:0000:0000:0000:0001:c0a8:0100"
+ a.family.should == Socket::AF_INET6
+ end
+
+ ruby_version_is ""..."3.1" do
+ it "raises on incorrect IPAddr strings" do
+ [
+ ["fe80::1%fxp0"],
+ ["::1/255.255.255.0"],
+ [IPAddr.new("::1").to_i],
+ ["::ffff:192.168.1.2/120", Socket::AF_INET],
+ ["[192.168.1.2]/120"],
+ ].each { |args|
+ ->{
+ IPAddr.new(*args)
+ }.should raise_error(ArgumentError)
+ }
+ end
+ end
+
+ ruby_version_is "3.1" do
+ it "raises on incorrect IPAddr strings" do
+ [
+ ["::1/255.255.255.0"],
+ [IPAddr.new("::1").to_i],
+ ["::ffff:192.168.1.2/120", Socket::AF_INET],
+ ["[192.168.1.2]/120"],
+ ].each { |args|
+ ->{
+ IPAddr.new(*args)
+ }.should raise_error(ArgumentError)
+ }
+ end
+ end
+end
diff --git a/spec/ruby/library/ipaddr/operator_spec.rb b/spec/ruby/library/ipaddr/operator_spec.rb
new file mode 100644
index 0000000000..f90c56009c
--- /dev/null
+++ b/spec/ruby/library/ipaddr/operator_spec.rb
@@ -0,0 +1,82 @@
+require_relative '../../spec_helper'
+require 'ipaddr'
+
+describe "IPAddr Operator" do
+ before do
+ @in6_addr_any = IPAddr.new()
+ @a = IPAddr.new("3ffe:505:2::/48")
+ @b = IPAddr.new("0:0:0:1::")
+ @c = IPAddr.new("ffff:ffff::")
+ end
+
+ it "bitwises or" do
+ (@a | @b).to_s.should == "3ffe:505:2:1::"
+ a = @a
+ a |= @b
+ a.to_s.should == "3ffe:505:2:1::"
+ @a.to_s.should == "3ffe:505:2::"
+ (@a | 0x00000000000000010000000000000000).to_s.should == "3ffe:505:2:1::"
+ end
+
+ it "bitwises and" do
+ (@a & @c).to_s.should == "3ffe:505::"
+ a = @a
+ a &= @c
+ a.to_s.should == "3ffe:505::"
+ @a.to_s.should == "3ffe:505:2::"
+ (@a & 0xffffffff000000000000000000000000).to_s.should == "3ffe:505::"
+ end
+
+ it "bitshifts right" do
+ (@a >> 16).to_s.should == "0:3ffe:505:2::"
+ a = @a
+ a >>= 16
+ a.to_s.should == "0:3ffe:505:2::"
+ @a.to_s.should == "3ffe:505:2::"
+ end
+
+ it "bitshifts left" do
+ (@a << 16).to_s.should == "505:2::"
+ a = @a
+ a <<= 16
+ a.to_s.should == "505:2::"
+ @a.to_s.should == "3ffe:505:2::"
+ end
+
+ it "inverts" do
+ a = ~@in6_addr_any
+ a.to_s.should == "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+ @in6_addr_any.to_s.should == "::"
+ end
+
+ it "tests for equality" do
+ @a.should == IPAddr.new("3ffe:505:2::")
+ @a.should_not == IPAddr.new("3ffe:505:3::")
+ end
+
+ # https://bugs.ruby-lang.org/issues/12799
+ it "tests for equality correctly if object cannot be converted to IPAddr" do
+ IPAddr.new("1.1.1.1").should_not == "sometext"
+ end
+
+ it "sets a mask" do
+ a = @a.mask(32)
+ a.to_s.should == "3ffe:505::"
+ @a.to_s.should == "3ffe:505:2::"
+ end
+
+ it "checks whether an address is included in a range" do
+ @a.should include(IPAddr.new("3ffe:505:2::"))
+ @a.should include(IPAddr.new("3ffe:505:2::1"))
+ @a.should_not include(IPAddr.new("3ffe:505:3::"))
+ net1 = IPAddr.new("192.168.2.0/24")
+ net1.should include(IPAddr.new("192.168.2.0"))
+ net1.should include(IPAddr.new("192.168.2.255"))
+ net1.should_not include(IPAddr.new("192.168.3.0"))
+ # test with integer parameter
+ int = (192 << 24) + (168 << 16) + (2 << 8) + 13
+
+ net1.should include(int)
+ net1.should_not include(int+255)
+ end
+end
diff --git a/spec/ruby/library/ipaddr/reverse_spec.rb b/spec/ruby/library/ipaddr/reverse_spec.rb
new file mode 100644
index 0000000000..6ebb343269
--- /dev/null
+++ b/spec/ruby/library/ipaddr/reverse_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require 'ipaddr'
+
+describe "IPAddr#reverse" do
+ it "generates the reverse DNS lookup entry" do
+ IPAddr.new("3ffe:505:2::f").reverse.should == "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa"
+ IPAddr.new("192.168.2.1").reverse.should == "1.2.168.192.in-addr.arpa"
+ end
+end
+
+describe "IPAddr#ip6_arpa" do
+ it "converts an IPv6 address into the reverse DNS lookup representation according to RFC3172" do
+ IPAddr.new("3ffe:505:2::f").ip6_arpa.should == "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa"
+ ->{
+ IPAddr.new("192.168.2.1").ip6_arpa
+ }.should raise_error(ArgumentError)
+ end
+end
+
+describe "IPAddr#ip6_int" do
+ it "converts an IPv6 address into the reverse DNS lookup representation according to RFC1886" do
+ IPAddr.new("3ffe:505:2::f").ip6_int.should == "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.int"
+ ->{
+ IPAddr.new("192.168.2.1").ip6_int
+ }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/ipaddr/to_s_spec.rb b/spec/ruby/library/ipaddr/to_s_spec.rb
new file mode 100644
index 0000000000..2a9a027909
--- /dev/null
+++ b/spec/ruby/library/ipaddr/to_s_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require 'ipaddr'
+
+describe "IPAddr#to_s" do
+
+ it "displays IPAddr using short notation" do
+ IPAddr.new("0:0:0:1::").to_s.should == "0:0:0:1::"
+ IPAddr.new("2001:200:300::/48").to_s.should == "2001:200:300::"
+ IPAddr.new("[2001:200:300::]/48").to_s.should == "2001:200:300::"
+ IPAddr.new("3ffe:505:2::1").to_s.should == "3ffe:505:2::1"
+ end
+
+end
+
+describe "IPAddr#to_string" do
+ it "displays an IPAddr using full notation" do
+ IPAddr.new("3ffe:505:2::1").to_string.should == "3ffe:0505:0002:0000:0000:0000:0000:0001"
+ end
+
+end
diff --git a/spec/ruby/library/logger/device/close_spec.rb b/spec/ruby/library/logger/device/close_spec.rb
new file mode 100644
index 0000000000..7c5e118d56
--- /dev/null
+++ b/spec/ruby/library/logger/device/close_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger::LogDevice#close" do
+ before :each do
+ @file_path = tmp("test_log.log")
+ @log_file = File.open(@file_path, "w+")
+
+ # Avoid testing this with STDERR, we don't want to be closing that.
+ @device = Logger::LogDevice.new(@log_file)
+ end
+
+ after :each do
+ @log_file.close unless @log_file.closed?
+ rm_r @file_path
+ end
+
+ version_is Logger::VERSION, ""..."1.4.0" do
+ it "closes the LogDevice's stream" do
+ @device.close
+ -> { @device.write("Test") }.should complain(/\Alog writing failed\./)
+ end
+ end
+
+ version_is Logger::VERSION, "1.4.0" do
+ it "closes the LogDevice's stream" do
+ @device.close
+ -> { @device.write("Test") }.should complain(/\Alog shifting failed\./)
+ end
+ end
+end
diff --git a/spec/ruby/library/logger/device/new_spec.rb b/spec/ruby/library/logger/device/new_spec.rb
new file mode 100644
index 0000000000..26a38c2b8c
--- /dev/null
+++ b/spec/ruby/library/logger/device/new_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger::LogDevice#new" do
+ before :each do
+ @file_path = tmp("test_log.log")
+ @log_file = File.open(@file_path, "w+")
+ end
+
+ after :each do
+ @log_file.close unless @log_file.closed?
+ rm_r @file_path
+ end
+
+ it "creates a new log device" do
+ l = Logger::LogDevice.new(@log_file)
+ l.dev.should be_kind_of(File)
+ end
+
+ it "receives an IO object to log there as first argument" do
+ @log_file.should be_kind_of(IO)
+ l = Logger::LogDevice.new(@log_file)
+ l.write("foo")
+ @log_file.rewind
+ @log_file.readlines.first.should == "foo"
+ end
+
+ it "creates a File if the IO object does not exist" do
+ path = tmp("test_logger_file")
+ l = Logger::LogDevice.new(path)
+ l.write("Test message")
+ l.close
+
+ File.should.exist?(path)
+ File.open(path) do |f|
+ f.readlines.should_not be_empty
+ end
+
+ rm_r path
+ end
+
+ it "receives options via a hash as second argument" do
+ -> {
+ Logger::LogDevice.new(STDERR, shift_age: 8, shift_size: 10)
+ }.should_not raise_error
+ end
+end
diff --git a/spec/ruby/library/logger/device/write_spec.rb b/spec/ruby/library/logger/device/write_spec.rb
new file mode 100644
index 0000000000..cd2d7e27a9
--- /dev/null
+++ b/spec/ruby/library/logger/device/write_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger::LogDevice#write" do
+ before :each do
+ @file_path = tmp("test_log.log")
+ @log_file = File.open(@file_path, "w+")
+ # Avoid testing this with STDERR, we don't want to be closing that.
+ @device = Logger::LogDevice.new(@log_file)
+ end
+
+ after :each do
+ @log_file.close unless @log_file.closed?
+ rm_r @file_path
+ end
+
+ it "writes a message to the device" do
+ @device.write "This is a test message"
+ @log_file.rewind
+ @log_file.readlines.first.should == "This is a test message"
+ end
+
+ it "can create a file and writes empty message" do
+ path = tmp("you_should_not_see_me")
+ logdevice = Logger::LogDevice.new(path)
+ logdevice.write("")
+ logdevice.close
+
+ File.open(path) do |f|
+ messages = f.readlines
+ messages.size.should == 1
+ messages.first.should =~ /#.*/ # only a comment
+ end
+
+ rm_r path
+ end
+
+ version_is Logger::VERSION, ""..."1.4.0" do
+ it "fails if the device is already closed" do
+ @device.close
+ -> { @device.write "foo" }.should complain(/\Alog writing failed\./)
+ end
+ end
+
+ version_is Logger::VERSION, "1.4.0" do
+ it "fails if the device is already closed" do
+ @device.close
+ -> { @device.write "foo" }.should complain(/\Alog shifting failed\./)
+ end
+ end
+end
diff --git a/spec/ruby/library/logger/fixtures/common.rb b/spec/ruby/library/logger/fixtures/common.rb
new file mode 100644
index 0000000000..d369c64a24
--- /dev/null
+++ b/spec/ruby/library/logger/fixtures/common.rb
@@ -0,0 +1,9 @@
+require 'logger'
+
+module LoggerSpecs
+
+ def self.strip_date(str)
+ str.gsub(/[A-Z].*\[.*\]/, "").lstrip
+ end
+
+end
diff --git a/spec/ruby/library/logger/logger/add_spec.rb b/spec/ruby/library/logger/logger/add_spec.rb
new file mode 100644
index 0000000000..3f709e18ba
--- /dev/null
+++ b/spec/ruby/library/logger/logger/add_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger#add" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "writes a new message to the logger" do
+ @logger.add(Logger::WARN, "Test")
+ @log_file.rewind
+ message = @log_file.readlines.last
+ LoggerSpecs.strip_date(message).should == "WARN -- : Test\n"
+ end
+
+ it "receives a severity" do
+ @logger.log(Logger::INFO, "Info message")
+ @logger.log(Logger::DEBUG, "Debug message")
+ @logger.log(Logger::WARN, "Warn message")
+ @logger.log(Logger::ERROR, "Error message")
+ @logger.log(Logger::FATAL, "Fatal message")
+
+ @log_file.rewind
+
+ info, debug, warn, error, fatal = @log_file.readlines
+
+ LoggerSpecs.strip_date(info).should == "INFO -- : Info message\n"
+ LoggerSpecs.strip_date(debug).should == "DEBUG -- : Debug message\n"
+ LoggerSpecs.strip_date(warn).should == "WARN -- : Warn message\n"
+ LoggerSpecs.strip_date(error).should == "ERROR -- : Error message\n"
+ LoggerSpecs.strip_date(fatal).should == "FATAL -- : Fatal message\n"
+ end
+
+ it "receives a message" do
+ @logger.log(nil, "test")
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readline).should == "ANY -- : test\n"
+ end
+
+ it "receives a program name" do
+ @logger.log(nil, "test", "TestApp")
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readline).should == "ANY -- TestApp: test\n"
+ end
+
+ it "receives a block" do
+ -> {
+ @logger.log(nil, "test", "TestApp") do
+ 1+1
+ end
+ }.should_not raise_error
+ end
+
+ it "calls the block if message is nil" do
+ temp = 0
+ -> {
+ @logger.log(nil, nil, "TestApp") do
+ temp = 1+1
+ end
+ }.should_not raise_error
+ temp.should == 2
+ end
+
+ it "ignores the block if the message is not nil" do
+ temp = 0
+ -> {
+ @logger.log(nil, "not nil", "TestApp") do
+ temp = 1+1
+ end
+ }.should_not raise_error
+ temp.should == 0
+ end
+end
diff --git a/spec/ruby/library/logger/logger/close_spec.rb b/spec/ruby/library/logger/logger/close_spec.rb
new file mode 100644
index 0000000000..81aac2a6cd
--- /dev/null
+++ b/spec/ruby/library/logger/logger/close_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger#close" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "closes the logging device" do
+ @logger.close
+ -> { @logger.add(nil, "Foo") }.should complain(/\Alog writing failed\./)
+ end
+end
diff --git a/spec/ruby/library/logger/logger/datetime_format_spec.rb b/spec/ruby/library/logger/logger/datetime_format_spec.rb
new file mode 100644
index 0000000000..582b34bfda
--- /dev/null
+++ b/spec/ruby/library/logger/logger/datetime_format_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger#datetime_format" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "returns the date format used for the logs" do
+ format = "%Y-%d"
+ @logger.datetime_format = format
+ @logger.datetime_format.should == format
+ end
+
+ it "returns nil logger is using the default date format" do
+ @logger.datetime_format.should == nil
+ end
+end
+
+describe "Logger#datetime_format=" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "sets the date format for the logs" do
+ @logger.datetime_format = "%Y"
+ @logger.datetime_format.should == "%Y"
+ @logger.add(Logger::WARN, "Test message")
+ @log_file.rewind
+
+ regex = /2[0-9]{3}.*Test message/
+ @log_file.readlines.first.should =~ regex
+ end
+
+ it "follows the Time#strftime format" do
+ -> { @logger.datetime_format = "%Y-%m" }.should_not raise_error
+
+ regex = /\d{4}-\d{2}-\d{2}oo-\w+ar/
+ @logger.datetime_format = "%Foo-%Bar"
+ @logger.add(nil, "Test message")
+ @log_file.rewind
+ @log_file.readlines.first.should =~ regex
+ end
+end
diff --git a/spec/ruby/library/logger/logger/debug_spec.rb b/spec/ruby/library/logger/logger/debug_spec.rb
new file mode 100644
index 0000000000..9375ab0cc6
--- /dev/null
+++ b/spec/ruby/library/logger/logger/debug_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger#debug?" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "returns true if severity level allows debug messages" do
+ @logger.level = Logger::DEBUG
+ @logger.should.debug?
+ end
+
+ it "returns false if severity level does not allow debug messages" do
+ @logger.level = Logger::WARN
+ @logger.should_not.debug?
+ end
+end
+
+describe "Logger#debug" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "logs a DEBUG message" do
+ @logger.debug("test")
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "DEBUG -- : test\n"
+ end
+
+ it "accepts an application name with a block" do
+ @logger.debug("MyApp") { "Test message" }
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "DEBUG -- MyApp: Test message\n"
+ end
+end
diff --git a/spec/ruby/library/logger/logger/error_spec.rb b/spec/ruby/library/logger/logger/error_spec.rb
new file mode 100644
index 0000000000..42f1dbd883
--- /dev/null
+++ b/spec/ruby/library/logger/logger/error_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger#error?" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "returns true if severity level allows printing errors" do
+ @logger.level = Logger::INFO
+ @logger.should.error?
+ end
+
+ it "returns false if severity level does not allow errors" do
+ @logger.level = Logger::FATAL
+ @logger.should_not.error?
+ end
+end
+
+describe "Logger#error" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "logs a ERROR message" do
+ @logger.error("test")
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "ERROR -- : test\n"
+ end
+
+ it "accepts an application name with a block" do
+ @logger.error("MyApp") { "Test message" }
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "ERROR -- MyApp: Test message\n"
+ end
+
+end
diff --git a/spec/ruby/library/logger/logger/fatal_spec.rb b/spec/ruby/library/logger/logger/fatal_spec.rb
new file mode 100644
index 0000000000..f12fa3a89f
--- /dev/null
+++ b/spec/ruby/library/logger/logger/fatal_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger#fatal?" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "returns true if severity level allows fatal messages" do
+ @logger.level = Logger::FATAL
+ @logger.should.fatal?
+ end
+
+ it "returns false if severity level does not allow fatal messages" do
+ @logger.level = Logger::UNKNOWN
+ @logger.should_not.fatal?
+ end
+end
+
+describe "Logger#fatal" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "logs a FATAL message" do
+ @logger.fatal("test")
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "FATAL -- : test\n"
+ end
+
+ it "accepts an application name with a block" do
+ @logger.fatal("MyApp") { "Test message" }
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "FATAL -- MyApp: Test message\n"
+ end
+
+end
diff --git a/spec/ruby/library/logger/logger/info_spec.rb b/spec/ruby/library/logger/logger/info_spec.rb
new file mode 100644
index 0000000000..eb5dca48dd
--- /dev/null
+++ b/spec/ruby/library/logger/logger/info_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger#info?" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "returns true if severity level allows info messages" do
+ @logger.level = Logger::INFO
+ @logger.should.info?
+ end
+
+ it "returns false if severity level does not allow info messages" do
+ @logger.level = Logger::FATAL
+ @logger.should_not.info?
+ end
+end
+
+describe "Logger#info" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "logs a INFO message" do
+ @logger.info("test")
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "INFO -- : test\n"
+ end
+
+ it "accepts an application name with a block" do
+ @logger.info("MyApp") { "Test message" }
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "INFO -- MyApp: Test message\n"
+ end
+
+end
diff --git a/spec/ruby/library/logger/logger/new_spec.rb b/spec/ruby/library/logger/logger/new_spec.rb
new file mode 100644
index 0000000000..d3100ee2d1
--- /dev/null
+++ b/spec/ruby/library/logger/logger/new_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger#new" do
+
+ before :each do
+ @file_path = tmp("test_log.log")
+ @log_file = File.open(@file_path, "w+")
+ end
+
+ after :each do
+ @log_file.close unless @log_file.closed?
+ rm_r @file_path
+ end
+
+ it "creates a new logger object" do
+ l = Logger.new(STDERR)
+ -> { l.add(Logger::WARN, "Foo") }.should output_to_fd(/Foo/, STDERR)
+ end
+
+ it "receives a logging device as first argument" do
+ l = Logger.new(@log_file)
+ l.add(Logger::WARN, "Test message")
+
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readline).should == "WARN -- : Test message\n"
+ l.close
+ end
+
+ it "receives a frequency rotation as second argument" do
+ -> { Logger.new(@log_file, "daily") }.should_not raise_error
+ -> { Logger.new(@log_file, "weekly") }.should_not raise_error
+ -> { Logger.new(@log_file, "monthly") }.should_not raise_error
+ end
+
+ it "also receives a number of log files to keep as second argument" do
+ -> { Logger.new(@log_file, 1).close }.should_not raise_error
+ end
+
+ it "receives a maximum logfile size as third argument" do
+ # This should create 2 small log files, logfile_test and logfile_test.0
+ # in /tmp, each one with a different message.
+ path = tmp("logfile_test.log")
+
+ l = Logger.new(path, 2, 5)
+ l.add Logger::WARN, "foo"
+ l.add Logger::WARN, "bar"
+
+ File.should.exist?(path)
+ File.should.exist?(path + ".0")
+
+ # first line will be a comment so we'll have to skip it.
+ f = File.open(path)
+ f1 = File.open("#{path}.0")
+ LoggerSpecs.strip_date(f1.readlines.last).should == "WARN -- : foo\n"
+ LoggerSpecs.strip_date(f.readlines.last).should == "WARN -- : bar\n"
+
+ l.close
+ f.close
+ f1.close
+ rm_r path, "#{path}.0"
+ end
+
+ it "receives level symbol as keyword argument" do
+ logger = Logger.new(STDERR, level: :info)
+ logger.level.should == Logger::INFO
+ end
+
+ it "receives level as keyword argument" do
+ logger = Logger.new(STDERR, level: Logger::INFO)
+ logger.level.should == Logger::INFO
+ end
+
+ it "receives progname as keyword argument" do
+ progname = "progname"
+
+ logger = Logger.new(STDERR, progname: progname)
+ logger.progname.should == progname
+ end
+
+ it "receives datetime_format as keyword argument" do
+ datetime_format = "%H:%M:%S"
+
+ logger = Logger.new(STDERR, datetime_format: datetime_format)
+ logger.datetime_format.should == datetime_format
+ end
+
+ it "receives formatter as keyword argument" do
+ formatter = Class.new do
+ def call(_severity, _time, _progname, _msg); end
+ end.new
+
+ logger = Logger.new(STDERR, formatter: formatter)
+ logger.formatter.should == formatter
+ end
+
+ it "receives shift_period_suffix " do
+ shift_period_suffix = "%Y-%m-%d"
+ path = tmp("shift_period_suffix_test.log")
+ now = Time.now
+ tomorrow = Time.at(now.to_i + 60 * 60 * 24)
+ logger = Logger.new(path, 'daily', shift_period_suffix: shift_period_suffix)
+
+ logger.add Logger::INFO, 'message'
+
+ Time.stub!(:now).and_return(tomorrow)
+ logger.add Logger::INFO, 'second message'
+
+ shifted_path = "#{path}.#{now.strftime(shift_period_suffix)}"
+
+ File.should.exist?(shifted_path)
+
+ logger.close
+
+ rm_r path, shifted_path
+ end
+
+end
diff --git a/spec/ruby/library/logger/logger/unknown_spec.rb b/spec/ruby/library/logger/logger/unknown_spec.rb
new file mode 100644
index 0000000000..b174b8b2c9
--- /dev/null
+++ b/spec/ruby/library/logger/logger/unknown_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger#unknown" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "logs a message with unknown severity" do
+ @logger.unknown "Test"
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "ANY -- : Test\n"
+ end
+
+ it "defaults the priority value to 5 and text value to ANY" do
+ @logger.unknown "Test"
+ @log_file.rewind
+ message = LoggerSpecs.strip_date(@log_file.readlines.first)[0..2]
+ message.should == "ANY"
+ Logger::UNKNOWN.should == 5
+ end
+
+ it "receives empty messages" do
+ -> { @logger.unknown("") }.should_not raise_error
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "ANY -- : \n"
+ end
+end
diff --git a/spec/ruby/library/logger/logger/warn_spec.rb b/spec/ruby/library/logger/logger/warn_spec.rb
new file mode 100644
index 0000000000..0bca34824a
--- /dev/null
+++ b/spec/ruby/library/logger/logger/warn_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/common'
+
+describe "Logger#warn?" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "returns true if severity level allows printing warn messages" do
+ @logger.level = Logger::WARN
+ @logger.should.warn?
+ end
+
+ it "returns false if severity level does not allow printing warn messages" do
+ @logger.level = Logger::FATAL
+ @logger.should_not.warn?
+ end
+end
+
+describe "Logger#warn" do
+ before :each do
+ @path = tmp("test_log.log")
+ @log_file = File.open(@path, "w+")
+ @logger = Logger.new(@path)
+ end
+
+ after :each do
+ @logger.close
+ @log_file.close unless @log_file.closed?
+ rm_r @path
+ end
+
+ it "logs a WARN message" do
+ @logger.warn("test")
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "WARN -- : test\n"
+ end
+
+ it "accepts an application name with a block" do
+ @logger.warn("MyApp") { "Test message" }
+ @log_file.rewind
+ LoggerSpecs.strip_date(@log_file.readlines.first).should == "WARN -- MyApp: Test message\n"
+ end
+
+end
diff --git a/spec/ruby/library/logger/severity_spec.rb b/spec/ruby/library/logger/severity_spec.rb
new file mode 100644
index 0000000000..e9bc850c33
--- /dev/null
+++ b/spec/ruby/library/logger/severity_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require 'logger'
+
+describe "Logger::Severity" do
+ it "defines Logger severity constants" do
+ Logger::DEBUG.should == 0
+ Logger::INFO.should == 1
+ Logger::WARN.should == 2
+ Logger::ERROR.should == 3
+ Logger::FATAL.should == 4
+ Logger::UNKNOWN.should == 5
+ end
+end
diff --git a/spec/ruby/library/matrix/I_spec.rb b/spec/ruby/library/matrix/I_spec.rb
new file mode 100644
index 0000000000..aa064ed54b
--- /dev/null
+++ b/spec/ruby/library/matrix/I_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/identity'
+
+ describe "Matrix.I" do
+ it_behaves_like :matrix_identity, :I
+ end
+end
diff --git a/spec/ruby/library/matrix/antisymmetric_spec.rb b/spec/ruby/library/matrix/antisymmetric_spec.rb
new file mode 100644
index 0000000000..dcc3c30f3e
--- /dev/null
+++ b/spec/ruby/library/matrix/antisymmetric_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#antisymmetric?" do
+ it "returns true for an antisymmetric Matrix" do
+ Matrix[[0, -2, Complex(1, 3)], [2, 0, 5], [-Complex(1, 3), -5, 0]].antisymmetric?.should be_true
+ end
+
+ it "returns true for a 0x0 empty matrix" do
+ Matrix.empty.antisymmetric?.should be_true
+ end
+
+ it "returns false for non-antisymmetric matrices" do
+ [
+ Matrix[[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ Matrix[[1, -2, 3], [2, 0, 6], [-3, -6, 0]], # wrong diagonal element
+ Matrix[[0, 2, -3], [2, 0, 6], [-3, 6, 0]] # only signs wrong
+ ].each do |matrix|
+ matrix.antisymmetric?.should be_false
+ end
+ end
+
+ it "raises an error for rectangular matrices" do
+ [
+ Matrix[[0], [0]],
+ Matrix[[0, 0]],
+ Matrix.empty(0, 2),
+ Matrix.empty(2, 0),
+ ].each do |rectangular_matrix|
+ -> {
+ rectangular_matrix.antisymmetric?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/build_spec.rb b/spec/ruby/library/matrix/build_spec.rb
new file mode 100644
index 0000000000..d3055a1aa7
--- /dev/null
+++ b/spec/ruby/library/matrix/build_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix.build" do
+
+ it "returns a Matrix object of the given size" do
+ m = Matrix.build(3, 4){1}
+ m.should be_an_instance_of(Matrix)
+ m.row_size.should == 3
+ m.column_size.should == 4
+ end
+
+ it "builds the Matrix using the given block" do
+ Matrix.build(2, 3){|col, row| 10*col - row}.should ==
+ Matrix[[0, -1, -2], [10, 9, 8]]
+ end
+
+ it "iterates through the first row, then the second, ..." do
+ acc = []
+ Matrix.build(2, 3){|*args| acc << args}
+ acc.should == [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]]
+ end
+
+ it "returns an Enumerator is no block is given" do
+ enum = Matrix.build(2, 1)
+ enum.should be_an_instance_of(Enumerator)
+ enum.each{1}.should == Matrix[[1], [1]]
+ end
+
+ it "requires integers as parameters" do
+ -> { Matrix.build("1", "2"){1} }.should raise_error(TypeError)
+ -> { Matrix.build(nil, nil){1} }.should raise_error(TypeError)
+ -> { Matrix.build(1..2){1} }.should raise_error(TypeError)
+ end
+
+ it "requires non-negative integers" do
+ -> { Matrix.build(-1, 1){1} }.should raise_error(ArgumentError)
+ -> { Matrix.build(+1,-1){1} }.should raise_error(ArgumentError)
+ end
+
+ it "returns empty Matrix if one argument is zero" do
+ m = Matrix.build(0, 3){
+ raise "Should not yield"
+ }
+ m.should be_empty
+ m.column_size.should == 3
+
+ m = Matrix.build(3, 0){
+ raise "Should not yield"
+ }
+ m.should be_empty
+ m.row_size.should == 3
+ end
+
+ it "tries to calls :to_int on arguments" do
+ int = mock('int')
+ int.should_receive(:to_int).twice.and_return(2)
+ Matrix.build(int, int){ 1 }.should == Matrix[ [1,1], [1,1] ]
+ end
+
+ it "builds an nxn Matrix when given only one argument" do
+ m = Matrix.build(3){1}
+ m.row_size.should == 3
+ m.column_size.should == 3
+ end
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.build(3){1}.should be_an_instance_of(MatrixSub)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/clone_spec.rb b/spec/ruby/library/matrix/clone_spec.rb
new file mode 100644
index 0000000000..bde119988f
--- /dev/null
+++ b/spec/ruby/library/matrix/clone_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#clone" do
+ before :each do
+ @a = Matrix[[1, 2], [3, 4], [5, 6]]
+ end
+
+ it "returns a shallow copy of the matrix" do
+ b = @a.clone
+ @a.should_not equal(b)
+ b.should be_kind_of(Matrix)
+ b.should == @a
+ 0.upto(@a.row_size - 1) do |i|
+ @a.row(i).should_not equal(b.row(i))
+ end
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.ins.clone.should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/coerce_spec.rb b/spec/ruby/library/matrix/coerce_spec.rb
new file mode 100644
index 0000000000..aa3a32765a
--- /dev/null
+++ b/spec/ruby/library/matrix/coerce_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#coerce" do
+ it "allows the division of integer by a Matrix " do
+ (1/Matrix[[0,1],[-1,0]]).should == Matrix[[0,-1],[1,0]]
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/collect_spec.rb b/spec/ruby/library/matrix/collect_spec.rb
new file mode 100644
index 0000000000..66ec3486c8
--- /dev/null
+++ b/spec/ruby/library/matrix/collect_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/collect'
+
+ describe "Matrix#collect" do
+ it_behaves_like :collect, :collect
+ end
+end
diff --git a/spec/ruby/library/matrix/column_size_spec.rb b/spec/ruby/library/matrix/column_size_spec.rb
new file mode 100644
index 0000000000..e7b42b101e
--- /dev/null
+++ b/spec/ruby/library/matrix/column_size_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#column_size" do
+ it "returns the number of columns" do
+ Matrix[ [1,2], [3,4] ].column_size.should == 2
+ end
+
+ it "returns 0 for empty matrices" do
+ Matrix[ [], [] ].column_size.should == 0
+ Matrix[ ].column_size.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/column_spec.rb b/spec/ruby/library/matrix/column_spec.rb
new file mode 100644
index 0000000000..98a767ad08
--- /dev/null
+++ b/spec/ruby/library/matrix/column_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#column" do
+ before :all do
+ @m = Matrix[[1,2,3], [2,3,4]]
+ end
+
+ it "returns a Vector when called without a block" do
+ @m.column(1).should == Vector[2,3]
+ end
+
+ it "yields each element in the column to the block" do
+ a = []
+ @m.column(1) {|n| a << n }
+ a.should == [2,3]
+ end
+
+ it "counts backwards for negative argument" do
+ @m.column(-1).should == Vector[3, 4]
+ end
+
+ it "returns self when called with a block" do
+ @m.column(0) { |x| x }.should equal(@m)
+ end
+
+ it "returns nil when out of bounds" do
+ @m.column(3).should == nil
+ end
+
+ it "never yields when out of bounds" do
+ -> { @m.column(3){ raise } }.should_not raise_error
+ -> { @m.column(-4){ raise } }.should_not raise_error
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/column_vector_spec.rb b/spec/ruby/library/matrix/column_vector_spec.rb
new file mode 100644
index 0000000000..afdeaced47
--- /dev/null
+++ b/spec/ruby/library/matrix/column_vector_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix.column_vector" do
+
+ it "returns a single column Matrix when called with an Array" do
+ m = Matrix.column_vector([4,5,6])
+ m.should be_an_instance_of(Matrix)
+ m.should == Matrix[ [4],[5],[6] ]
+ end
+
+ it "returns an empty Matrix when called with an empty Array" do
+ m = Matrix.column_vector([])
+ m.should be_an_instance_of(Matrix)
+ m.row_size.should == 0
+ m.column_size.should == 1
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.column_vector([4,5,6]).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/column_vectors_spec.rb b/spec/ruby/library/matrix/column_vectors_spec.rb
new file mode 100644
index 0000000000..7bec095b9a
--- /dev/null
+++ b/spec/ruby/library/matrix/column_vectors_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#column_vectors" do
+
+ before :each do
+ @vectors = Matrix[ [1,2], [3,4] ].column_vectors
+ end
+
+ it "returns an Array" do
+ Matrix[ [1,2], [3,4] ].column_vectors.should be_an_instance_of(Array)
+ end
+
+ it "returns an Array of Vectors" do
+ @vectors.all? {|v| v.should be_an_instance_of(Vector)}
+ end
+
+ it "returns each column as a Vector" do
+ @vectors.should == [Vector[1,3], Vector[2,4]]
+ end
+
+ it "returns an empty Array for empty matrices" do
+ Matrix[ [] ].column_vectors.should == []
+ end
+
+ end
+end
diff --git a/spec/ruby/library/matrix/columns_spec.rb b/spec/ruby/library/matrix/columns_spec.rb
new file mode 100644
index 0000000000..757086c14b
--- /dev/null
+++ b/spec/ruby/library/matrix/columns_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix.columns" do
+ before :each do
+ @a = [1, 2]
+ @b = [3, 4]
+ @m = Matrix.columns([@a, @b])
+ end
+
+ it "creates a Matrix from argument columns" do
+ @m.should be_an_instance_of(Matrix)
+ @m.column(0).to_a.should == @a
+ @m.column(1).to_a.should == @b
+ end
+
+ it "accepts Vectors as argument columns" do
+ m = Matrix.columns([Vector[*@a], Vector[*@b]])
+ m.should == @m
+ m.column(0).to_a.should == @a
+ m.column(1).to_a.should == @b
+ end
+
+ it "handles empty matrices" do
+ e = Matrix.columns([])
+ e.row_size.should == 0
+ e.column_size.should == 0
+ e.should == Matrix[]
+
+ v = Matrix.columns([[],[],[]])
+ v.row_size.should == 0
+ v.column_size.should == 3
+ v.should == Matrix[[], [], []].transpose
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.columns([[1]]).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/conj_spec.rb b/spec/ruby/library/matrix/conj_spec.rb
new file mode 100644
index 0000000000..a922580399
--- /dev/null
+++ b/spec/ruby/library/matrix/conj_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/conjugate'
+
+ describe "Matrix#conj" do
+ it_behaves_like :matrix_conjugate, :conj
+ end
+end
diff --git a/spec/ruby/library/matrix/conjugate_spec.rb b/spec/ruby/library/matrix/conjugate_spec.rb
new file mode 100644
index 0000000000..b99792a24b
--- /dev/null
+++ b/spec/ruby/library/matrix/conjugate_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/conjugate'
+
+ describe "Matrix#conjugate" do
+ it_behaves_like :matrix_conjugate, :conjugate
+ end
+end
diff --git a/spec/ruby/library/matrix/constructor_spec.rb b/spec/ruby/library/matrix/constructor_spec.rb
new file mode 100644
index 0000000000..d8224b4430
--- /dev/null
+++ b/spec/ruby/library/matrix/constructor_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix.[]" do
+
+ it "requires arrays as parameters" do
+ -> { Matrix[5] }.should raise_error(TypeError)
+ -> { Matrix[nil] }.should raise_error(TypeError)
+ -> { Matrix[1..2] }.should raise_error(TypeError)
+ -> { Matrix[[1, 2], 3] }.should raise_error(TypeError)
+ end
+
+ it "creates an empty Matrix with no arguments" do
+ m = Matrix[]
+ m.column_size.should == 0
+ m.row_size.should == 0
+ end
+
+ it "raises for non-rectangular matrices" do
+ ->{ Matrix[ [0], [0,1] ] }.should \
+ raise_error(Matrix::ErrDimensionMismatch)
+ ->{ Matrix[ [0,1], [0,1,2], [0,1] ]}.should \
+ raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+ it "accepts vector arguments" do
+ a = Matrix[Vector[1, 2], Vector[3, 4]]
+ a.should be_an_instance_of(Matrix)
+ a.should == Matrix[ [1, 2], [3, 4] ]
+ end
+
+ it "tries to calls :to_ary on arguments" do
+ array = mock('ary')
+ array.should_receive(:to_ary).and_return([1,2])
+ Matrix[array, [3,4] ].should == Matrix[ [1,2], [3,4] ]
+ end
+
+
+ it "returns a Matrix object" do
+ Matrix[ [1] ].should be_an_instance_of(Matrix)
+ end
+
+ it "can create an nxn Matrix" do
+ m = Matrix[ [20,30], [40.5, 9] ]
+ m.row_size.should == 2
+ m.column_size.should == 2
+ m.column(0).should == Vector[20, 40.5]
+ m.column(1).should == Vector[30, 9]
+ m.row(0).should == Vector[20, 30]
+ m.row(1).should == Vector[40.5, 9]
+ end
+
+ it "can create a 0xn Matrix" do
+ m = Matrix[ [], [], [] ]
+ m.row_size.should == 3
+ m.column_size.should == 0
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub[ [20,30], [40.5, 9] ].should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/det_spec.rb b/spec/ruby/library/matrix/det_spec.rb
new file mode 100644
index 0000000000..7d3d547735
--- /dev/null
+++ b/spec/ruby/library/matrix/det_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/determinant'
+ require 'matrix'
+
+ describe "Matrix#det" do
+ it_behaves_like :determinant, :det
+ end
+end
diff --git a/spec/ruby/library/matrix/determinant_spec.rb b/spec/ruby/library/matrix/determinant_spec.rb
new file mode 100644
index 0000000000..bfd91fcf68
--- /dev/null
+++ b/spec/ruby/library/matrix/determinant_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/determinant'
+ require 'matrix'
+
+ describe "Matrix#determinant" do
+ it_behaves_like :determinant, :determinant
+ end
+end
diff --git a/spec/ruby/library/matrix/diagonal_spec.rb b/spec/ruby/library/matrix/diagonal_spec.rb
new file mode 100644
index 0000000000..8c82433fde
--- /dev/null
+++ b/spec/ruby/library/matrix/diagonal_spec.rb
@@ -0,0 +1,75 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix.diagonal" do
+ before :each do
+ @m = Matrix.diagonal(10, 11, 12, 13, 14)
+ end
+
+ it "returns an object of type Matrix" do
+ @m.should be_kind_of(Matrix)
+ end
+
+ it "returns a square Matrix of the right size" do
+ @m.column_size.should == 5
+ @m.row_size.should == 5
+ end
+
+ it "sets the diagonal to the arguments" do
+ (0..4).each do |i|
+ @m[i, i].should == i + 10
+ end
+ end
+
+ it "fills all non-diagonal cells with 0" do
+ (0..4).each do |i|
+ (0..4).each do |j|
+ if i != j
+ @m[i, j].should == 0
+ end
+ end
+ end
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.diagonal(1).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+
+ describe "Matrix.diagonal?" do
+ it "returns true for a diagonal Matrix" do
+ Matrix.diagonal([1, 2, 3]).diagonal?.should be_true
+ end
+
+ it "returns true for a zero square Matrix" do
+ Matrix.zero(3).diagonal?.should be_true
+ end
+
+ it "returns false for a non diagonal square Matrix" do
+ Matrix[[0, 1], [0, 0]].diagonal?.should be_false
+ Matrix[[1, 2, 3], [1, 2, 3], [1, 2, 3]].diagonal?.should be_false
+ end
+
+ it "returns true for an empty 0x0 matrix" do
+ Matrix.empty(0,0).diagonal?.should be_true
+ end
+
+ it "raises an error for rectangular matrices" do
+ [
+ Matrix[[0], [0]],
+ Matrix[[0, 0]],
+ Matrix.empty(0, 2),
+ Matrix.empty(2, 0),
+ ].each do |rectangular_matrix|
+ -> {
+ rectangular_matrix.diagonal?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/divide_spec.rb b/spec/ruby/library/matrix/divide_spec.rb
new file mode 100644
index 0000000000..68e6f1fde5
--- /dev/null
+++ b/spec/ruby/library/matrix/divide_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#/" do
+ before :each do
+ @a = Matrix[ [1, 2], [3, 4] ]
+ @b = Matrix[ [4, 5], [6, 7] ]
+ @c = Matrix[ [1.2, 2.4], [3.6, 4.8] ]
+ end
+
+ it "returns the result of dividing self by another Matrix" do
+ (@a / @b).should be_close_to_matrix([[2.5, -1.5], [1.5, -0.5]])
+ end
+
+ # Guard against the Mathn library
+ guard -> { !defined?(Math.rsqrt) } do
+ it "returns the result of dividing self by a Fixnum" do
+ (@a / 2).should == Matrix[ [0, 1], [1, 2] ]
+ end
+
+ it "returns the result of dividing self by a Bignum" do
+ (@a / bignum_value).should == Matrix[ [0, 0], [0, 0] ]
+ end
+ end
+
+ it "returns the result of dividing self by a Float" do
+ (@c / 1.2).should == Matrix[ [1, 2], [3, 4] ]
+ end
+
+ it "raises a Matrix::ErrDimensionMismatch if the matrices are different sizes" do
+ -> { @a / Matrix[ [1] ] }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+ it "returns an instance of Matrix" do
+ (@a / @b).should be_kind_of(Matrix)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ m = MatrixSub.ins
+ (m/m).should be_an_instance_of(MatrixSub)
+ (m/1).should be_an_instance_of(MatrixSub)
+ end
+ end
+
+ it "raises a TypeError if other is of wrong type" do
+ -> { @a / nil }.should raise_error(TypeError)
+ -> { @a / "a" }.should raise_error(TypeError)
+ -> { @a / [ [1, 2] ] }.should raise_error(TypeError)
+ -> { @a / Object.new }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/each_spec.rb b/spec/ruby/library/matrix/each_spec.rb
new file mode 100644
index 0000000000..d2b13c80b6
--- /dev/null
+++ b/spec/ruby/library/matrix/each_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#each" do
+ before :all do
+ @m = Matrix[ [1, 2, 3], [4, 5, 6] ]
+ @result = (1..6).to_a
+ end
+
+ it "returns an Enumerator when called without a block" do
+ enum = @m.each
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == @result
+ end
+
+ it "returns self" do
+ @m.each{}.should equal(@m)
+ end
+
+ it "yields the elements starting with the those of the first row" do
+ a = []
+ @m.each {|x| a << x}
+ a.should == @result
+ end
+ end
+
+ describe "Matrix#each with an argument" do
+ before :all do
+ @m = Matrix[ [1, 2, 3, 4], [5, 6, 7, 8] ]
+ @t = Matrix[ [1, 2], [3, 4], [5, 6], [7, 8] ]
+ end
+
+ it "raises an ArgumentError for unrecognized argument" do
+ -> {
+ @m.each("all"){}
+ }.should raise_error(ArgumentError)
+ -> {
+ @m.each(nil){}
+ }.should raise_error(ArgumentError)
+ -> {
+ @m.each(:left){}
+ }.should raise_error(ArgumentError)
+ end
+
+ it "yields the rights elements when passed :diagonal" do
+ @m.each(:diagonal).to_a.should == [1, 6]
+ @t.each(:diagonal).to_a.should == [1, 4]
+ end
+
+ it "yields the rights elements when passed :off_diagonal" do
+ @m.each(:off_diagonal).to_a.should == [2, 3, 4, 5, 7, 8]
+ @t.each(:off_diagonal).to_a.should == [2, 3, 5, 6, 7, 8]
+ end
+
+ it "yields the rights elements when passed :lower" do
+ @m.each(:lower).to_a.should == [1, 5, 6]
+ @t.each(:lower).to_a.should == [1, 3, 4, 5, 6, 7, 8]
+ end
+
+ it "yields the rights elements when passed :strict_lower" do
+ @m.each(:strict_lower).to_a.should == [5]
+ @t.each(:strict_lower).to_a.should == [3, 5, 6, 7, 8]
+ end
+
+ it "yields the rights elements when passed :strict_upper" do
+ @m.each(:strict_upper).to_a.should == [2, 3, 4, 7, 8]
+ @t.each(:strict_upper).to_a.should == [2]
+ end
+
+ it "yields the rights elements when passed :upper" do
+ @m.each(:upper).to_a.should == [1, 2, 3, 4, 6, 7, 8]
+ @t.each(:upper).to_a.should == [1, 2, 4]
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/each_with_index_spec.rb b/spec/ruby/library/matrix/each_with_index_spec.rb
new file mode 100644
index 0000000000..59549f77b7
--- /dev/null
+++ b/spec/ruby/library/matrix/each_with_index_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#each_with_index" do
+ before :all do
+ @m = Matrix[ [1, 2, 3], [4, 5, 6] ]
+ @result = [
+ [1, 0, 0],
+ [2, 0, 1],
+ [3, 0, 2],
+ [4, 1, 0],
+ [5, 1, 1],
+ [6, 1, 2]
+ ]
+ end
+
+ it "returns an Enumerator when called without a block" do
+ enum = @m.each_with_index
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == @result
+ end
+
+ it "returns self" do
+ @m.each_with_index{}.should equal(@m)
+ end
+
+ it "yields the elements starting with the those of the first row" do
+ a = []
+ @m.each_with_index {|x, r, c| a << [x, r, c]}
+ a.should == @result
+ end
+ end
+
+ describe "Matrix#each_with_index with an argument" do
+ before :all do
+ @m = Matrix[ [1, 2, 3, 4], [5, 6, 7, 8] ]
+ @t = Matrix[ [1, 2], [3, 4], [5, 6], [7, 8] ]
+ end
+
+ it "raises an ArgumentError for unrecognized argument" do
+ -> {
+ @m.each_with_index("all"){}
+ }.should raise_error(ArgumentError)
+ -> {
+ @m.each_with_index(nil){}
+ }.should raise_error(ArgumentError)
+ -> {
+ @m.each_with_index(:left){}
+ }.should raise_error(ArgumentError)
+ end
+
+ it "yields the rights elements when passed :diagonal" do
+ @m.each_with_index(:diagonal).to_a.should == [[1, 0, 0], [6, 1, 1]]
+ @t.each_with_index(:diagonal).to_a.should == [[1, 0, 0], [4, 1, 1]]
+ end
+
+ it "yields the rights elements when passed :off_diagonal" do
+ @m.each_with_index(:off_diagonal).to_a.should == [[2, 0, 1], [3, 0, 2], [4, 0, 3], [5, 1, 0], [7, 1, 2], [8, 1, 3]]
+ @t.each_with_index(:off_diagonal).to_a.should == [[2, 0, 1], [3, 1, 0], [5, 2, 0], [6, 2, 1], [7, 3, 0], [8, 3, 1]]
+ end
+
+ it "yields the rights elements when passed :lower" do
+ @m.each_with_index(:lower).to_a.should == [[1, 0, 0], [5, 1, 0], [6, 1, 1]]
+ @t.each_with_index(:lower).to_a.should == [[1, 0, 0], [3, 1, 0], [4, 1, 1], [5, 2, 0], [6, 2, 1], [7, 3, 0], [8, 3, 1]]
+ end
+
+ it "yields the rights elements when passed :strict_lower" do
+ @m.each_with_index(:strict_lower).to_a.should == [[5, 1, 0]]
+ @t.each_with_index(:strict_lower).to_a.should == [[3, 1, 0], [5, 2, 0], [6, 2, 1], [7, 3, 0], [8, 3, 1]]
+ end
+
+ it "yields the rights elements when passed :strict_upper" do
+ @m.each_with_index(:strict_upper).to_a.should == [[2, 0, 1], [3, 0, 2], [4, 0, 3], [7, 1, 2], [8, 1, 3]]
+ @t.each_with_index(:strict_upper).to_a.should == [[2, 0, 1]]
+ end
+
+ it "yields the rights elements when passed :upper" do
+ @m.each_with_index(:upper).to_a.should == [[1, 0, 0], [2, 0, 1], [3, 0, 2], [4, 0, 3], [6, 1, 1], [7, 1, 2], [8, 1, 3]]
+ @t.each_with_index(:upper).to_a.should == [[1, 0, 0], [2, 0, 1], [4, 1, 1]]
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalue_matrix_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalue_matrix_spec.rb
new file mode 100644
index 0000000000..f9ffca0123
--- /dev/null
+++ b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalue_matrix_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::EigenvalueDecomposition#eigenvalue_matrix" do
+ it "returns a diagonal matrix with the eigenvalues on the diagonal" do
+ Matrix[[14, 16], [-6, -6]].eigensystem.eigenvalue_matrix.should == Matrix[[6, 0],[0, 2]]
+ Matrix[[1, 1], [-1, 1]].eigensystem.eigenvalue_matrix.should == Matrix[[Complex(1,1), 0],[0, Complex(1,-1)]]
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalues_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalues_spec.rb
new file mode 100644
index 0000000000..650d43d90f
--- /dev/null
+++ b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalues_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::EigenvalueDecomposition#eigenvalues" do
+ it "returns an array of complex eigenvalues for a rotation matrix" do
+ Matrix[[ 1, 1],
+ [-1, 1]].eigensystem.eigenvalues.sort_by{|v| v.imag}.should ==
+ [ Complex(1, -1), Complex(1, 1)]
+ end
+
+ it "returns an array of real eigenvalues for a symmetric matrix" do
+ Matrix[[1, 2],
+ [2, 1]].eigensystem.eigenvalues.sort.map!{|x| x.round(10)}.should ==
+ [ -1, 3 ]
+ end
+
+ it "returns an array of real eigenvalues for a matrix" do
+ Matrix[[14, 16],
+ [-6, -6]].eigensystem.eigenvalues.sort.map!{|x| x.round(10)}.should ==
+ [ 2, 6 ]
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvector_matrix_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvector_matrix_spec.rb
new file mode 100644
index 0000000000..57299ce69e
--- /dev/null
+++ b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvector_matrix_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::EigenvalueDecomposition#eigenvector_matrix" do
+ it "returns a complex eigenvector matrix given a rotation matrix" do
+ # Fix me: should test for linearity, not for equality
+ Matrix[[ 1, 1],
+ [-1, 1]].eigensystem.eigenvector_matrix.should ==
+ Matrix[[1, 1],
+ [Complex(0, 1), Complex(0, -1)]]
+ end
+
+ it "returns an real eigenvector matrix for a symmetric matrix" do
+ # Fix me: should test for linearity, not for equality
+ Matrix[[1, 2],
+ [2, 1]].eigensystem.eigenvector_matrix.should ==
+ Matrix[[0.7071067811865475, 0.7071067811865475],
+ [-0.7071067811865475, 0.7071067811865475]]
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvectors_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvectors_spec.rb
new file mode 100644
index 0000000000..54e6857bf3
--- /dev/null
+++ b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvectors_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::EigenvalueDecomposition#eigenvectors" do
+ it "returns an array of complex eigenvectors for a rotation matrix" do
+ # Fix me: should test for linearity, not for equality
+ Matrix[[ 1, 1],
+ [-1, 1]].eigensystem.eigenvectors.should ==
+ [ Vector[1, Complex(0, 1)],
+ Vector[1, Complex(0, -1)]
+ ]
+ end
+
+ it "returns an array of real eigenvectors for a symmetric matrix" do
+ # Fix me: should test for linearity, not for equality
+ Matrix[[1, 2],
+ [2, 1]].eigensystem.eigenvectors.should ==
+ [ Vector[0.7071067811865475, -0.7071067811865475],
+ Vector[0.7071067811865475, 0.7071067811865475]
+ ]
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/initialize_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/initialize_spec.rb
new file mode 100644
index 0000000000..02aad65c4b
--- /dev/null
+++ b/spec/ruby/library/matrix/eigenvalue_decomposition/initialize_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::EigenvalueDecomposition#initialize" do
+ it "raises an error if argument is not a matrix" do
+ -> {
+ Matrix::EigenvalueDecomposition.new([[]])
+ }.should raise_error(TypeError)
+ -> {
+ Matrix::EigenvalueDecomposition.new(42)
+ }.should raise_error(TypeError)
+ end
+
+ it "raises an error if matrix is not square" do
+ -> {
+ Matrix::EigenvalueDecomposition.new(Matrix[[1, 2]])
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+ it "never hangs" do
+ m = Matrix[ [0,0,0,0,0], [0,0,0,0,1], [0,0,0,1,0], [1,1,0,0,1], [1,0,1,0,1] ]
+ Matrix::EigenvalueDecomposition.new(m).should_not == "infinite loop"
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/to_a_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/to_a_spec.rb
new file mode 100644
index 0000000000..352ae274b4
--- /dev/null
+++ b/spec/ruby/library/matrix/eigenvalue_decomposition/to_a_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::EigenvalueDecomposition#to_a" do
+ before :each do
+ @a = Matrix[[14, 16], [-6, -6]]
+ @e = Matrix::EigenvalueDecomposition.new(@a)
+ end
+
+ it "returns an array of with [V, D, V.inv]" do
+ @e.to_a.should == [@e.v, @e.d, @e.v_inv]
+ end
+
+ it "returns a factorization" do
+ v, d, v_inv = @e.to_a
+ (v * d * v_inv).map{|e| e.round(10)}.should == @a
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/element_reference_spec.rb b/spec/ruby/library/matrix/element_reference_spec.rb
new file mode 100644
index 0000000000..286ab851bd
--- /dev/null
+++ b/spec/ruby/library/matrix/element_reference_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#[]" do
+
+ before :all do
+ @m = Matrix[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
+ end
+
+ it "returns element at (i, j)" do
+ (0..3).each do |i|
+ (0..2).each do |j|
+ @m[i, j].should == (i * 3) + j
+ end
+ end
+ end
+
+ it "returns nil for an invalid index pair" do
+ @m[8,1].should be_nil
+ @m[1,8].should be_nil
+ end
+
+ end
+end
diff --git a/spec/ruby/library/matrix/empty_spec.rb b/spec/ruby/library/matrix/empty_spec.rb
new file mode 100644
index 0000000000..0b5d5ffe0f
--- /dev/null
+++ b/spec/ruby/library/matrix/empty_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#empty?" do
+ it "returns true when the Matrix is empty" do
+ Matrix[ ].empty?.should be_true
+ Matrix[ [], [], [] ].empty?.should be_true
+ Matrix[ [], [], [] ].transpose.empty?.should be_true
+ end
+
+ it "returns false when the Matrix has elements" do
+ Matrix[ [1, 2] ].empty?.should be_false
+ Matrix[ [1], [2] ].empty?.should be_false
+ end
+
+ it "doesn't accept any parameter" do
+ ->{
+ Matrix[ [1, 2] ].empty?(42)
+ }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "Matrix.empty" do
+ it "returns an empty matrix of the requested size" do
+ m = Matrix.empty(3, 0)
+ m.row_size.should == 3
+ m.column_size.should == 0
+
+ m = Matrix.empty(0, 3)
+ m.row_size.should == 0
+ m.column_size.should == 3
+ end
+
+ it "has arguments defaulting to 0" do
+ Matrix.empty.should == Matrix.empty(0, 0)
+ Matrix.empty(42).should == Matrix.empty(42, 0)
+ end
+
+ it "does not accept more than two parameters" do
+ ->{
+ Matrix.empty(1, 2, 3)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error if both dimensions are > 0" do
+ ->{
+ Matrix.empty(1, 2)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error if any dimension is < 0" do
+ ->{
+ Matrix.empty(-2, 0)
+ }.should raise_error(ArgumentError)
+
+ ->{
+ Matrix.empty(0, -2)
+ }.should raise_error(ArgumentError)
+ end
+
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.empty(0, 1).should be_an_instance_of(MatrixSub)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/eql_spec.rb b/spec/ruby/library/matrix/eql_spec.rb
new file mode 100644
index 0000000000..d3f03dd6f3
--- /dev/null
+++ b/spec/ruby/library/matrix/eql_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/equal_value'
+ require 'matrix'
+
+ describe "Matrix#eql?" do
+ it_behaves_like :equal, :eql?
+
+ it "returns false if some elements are == but not eql?" do
+ Matrix[[1, 2],[3, 4]].eql?(Matrix[[1, 2],[3, 4.0]]).should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/equal_value_spec.rb b/spec/ruby/library/matrix/equal_value_spec.rb
new file mode 100644
index 0000000000..0408a8ef3e
--- /dev/null
+++ b/spec/ruby/library/matrix/equal_value_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/equal_value'
+ require 'matrix'
+
+ describe "Matrix#==" do
+ it_behaves_like :equal, :==
+
+ it "returns true if some elements are == but not eql?" do
+ Matrix[[1, 2],[3, 4]].should == Matrix[[1, 2],[3, 4.0]]
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/exponent_spec.rb b/spec/ruby/library/matrix/exponent_spec.rb
new file mode 100644
index 0000000000..5262627fd5
--- /dev/null
+++ b/spec/ruby/library/matrix/exponent_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#**" do
+
+ describe "given an integer _n_" do
+ it "multiples the Matrix by itself _n_ times" do
+ m = Matrix[ [7,6], [3,9] ]
+ (m ** 1).should == m
+ (m ** 2).should == Matrix[ [67, 96], [48,99] ]
+ (m ** 2).should == m * m
+ (m ** 3).should == m * m * m
+ (m ** 4).should == m * m * m * m
+ (m ** 5).should == m * m * m * m * m
+ end
+
+ it "raises a ErrDimensionMismatch for non square matrices" do
+ m = Matrix[ [1, 1], [1, 2], [2, 3]]
+ -> { m ** 3 }.should raise_error(Matrix::ErrDimensionMismatch)
+ -> { m ** 0 }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+ describe "that is < 0" do
+ it "returns the inverse of **(-n)" do
+ m = Matrix[ [1, 1], [1, 2] ]
+ (m ** -2).should == Matrix[ [5, -3], [-3, 2]]
+ (m ** -4).should == (m.inverse ** 4)
+ end
+
+ it "raises a ErrNotRegular for irregular matrices" do
+ m = Matrix[ [1, 1], [1, 1] ]
+ -> { m ** -2 }.should raise_error(Matrix::ErrNotRegular)
+ end
+ end
+
+ ruby_version_is '3.1.0' do # https://bugs.ruby-lang.org/issues/17521
+ describe "that is 0" do
+ it "returns the identity for square matrices" do
+ m = Matrix[ [1, 1], [1, 1] ]
+ (m ** 0).should == Matrix.identity(2)
+ end
+
+ it "raises an ErrDimensionMismatch for non-square matrices" do
+ m = Matrix[ [1, 1] ]
+ -> { m ** 0 }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+ end
+ end
+
+ it "returns the power for non integer powers" do
+ a = Matrix[[5, 4], [4, 5]]
+ ((a ** 0.5) ** 2).round(8).should == a
+ a = Matrix[[7, 10], [15, 22]]
+ ((a ** 0.25) ** 4).round(8).should == a
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ (MatrixSub.ins ** 1).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/find_index_spec.rb b/spec/ruby/library/matrix/find_index_spec.rb
new file mode 100644
index 0000000000..a0e3679aef
--- /dev/null
+++ b/spec/ruby/library/matrix/find_index_spec.rb
@@ -0,0 +1,149 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#find_index without any argument" do
+ before :all do
+ @m = Matrix[ [1, 2, 3, 4], [5, 6, 7, 8] ]
+ end
+
+ it "returns an Enumerator when called without a block" do
+ enum = @m.find_index
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == [1, 2, 3, 4, 5, 6, 7, 8]
+ end
+
+ it "returns nil if the block is always false" do
+ @m.find_index{false}.should be_nil
+ end
+
+ it "returns the first index for which the block is true" do
+ @m.find_index{|x| x >= 3}.should == [0, 2]
+ end
+ end
+
+ describe "Matrix#find_index with a subselection argument" do
+ before :all do
+ @tests = [
+ [ Matrix[ [1, 2, 3, 4], [5, 6, 7, 8] ], {
+ diagonal: [1, 6] ,
+ off_diagonal: [2, 3, 4, 5, 7, 8],
+ lower: [1, 5, 6] ,
+ strict_lower: [5] ,
+ strict_upper: [2, 3, 4, 7, 8] ,
+ upper: [1, 2, 3, 4, 6, 7, 8] ,
+ }
+ ],
+ [ Matrix[ [1, 2], [3, 4], [5, 6], [7, 8] ], {
+ diagonal: [1, 4] ,
+ off_diagonal: [2, 3, 5, 6, 7, 8],
+ lower: [1, 3, 4, 5, 6, 7, 8] ,
+ strict_lower: [3, 5, 6, 7, 8] ,
+ strict_upper: [2] ,
+ upper: [1, 2, 4] ,
+ }
+ ]]
+ end
+
+ describe "and no generic argument" do
+ it "returns an Enumerator when called without a block" do
+ @tests.each do |matrix, h|
+ h.each do |selector, result|
+ matrix.find_index(selector).should be_an_instance_of(Enumerator)
+ end
+ end
+ end
+
+ it "yields the rights elements" do
+ @tests.each do |matrix, h|
+ h.each do |selector, result|
+ matrix.find_index(selector).to_a.should == result
+ end
+ end
+ end
+
+ it "returns the first index for which the block returns true" do
+ @tests.each do |matrix, h|
+ h.each do |selector, result|
+ cnt = result.size.div 2
+ which = result[cnt]
+ idx = matrix.find_index(selector){|x| cnt -= 1; x == which}
+ matrix[*idx].should == which
+ cnt.should == -1
+ end
+ end
+ end
+
+ it "returns nil if the block is always false" do
+ @tests.each do |matrix, h|
+ h.each do |selector, result|
+ matrix.find_index(selector){ nil }.should == nil
+ end
+ end
+ end
+
+ end
+
+ describe "and a generic argument" do
+ it "ignores a block" do
+ @m.find_index(42, :diagonal){raise "oups"}.should == nil
+ end
+
+ it "returns the index of the requested value" do
+ @tests.each do |matrix, h|
+ h.each do |selector, result|
+ cnt = result.size / 2
+ which = result[cnt]
+ idx = matrix.find_index(which, selector)
+ matrix[*idx].should == which
+ end
+ end
+ end
+
+ it "returns nil if the requested value is not found" do
+ @tests.each do |matrix, h|
+ h.each do |selector, result|
+ matrix.find_index(42, selector).should == nil
+ end
+ end
+ end
+ end
+
+ end
+
+ describe "Matrix#find_index with only a generic argument" do
+ before :all do
+ @m = Matrix[ [1, 2, 3, 4], [1, 2, 3, 4] ]
+ end
+
+ it "returns nil if the value is not found" do
+ @m.find_index(42).should be_nil
+ end
+
+ it "returns the first index for of the requested value" do
+ @m.find_index(3).should == [0, 2]
+ end
+
+ it "ignores a block" do
+ @m.find_index(4){raise "oups"}.should == [0, 3]
+ end
+ end
+
+ describe "Matrix#find_index with two arguments" do
+ it "raises an ArgumentError for an unrecognized last argument" do
+ -> {
+ @m.find_index(1, "all"){}
+ }.should raise_error(ArgumentError)
+ -> {
+ @m.find_index(1, nil){}
+ }.should raise_error(ArgumentError)
+ -> {
+ @m.find_index(1, :left){}
+ }.should raise_error(ArgumentError)
+ -> {
+ @m.find_index(:diagonal, 1){}
+ }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/fixtures/classes.rb b/spec/ruby/library/matrix/fixtures/classes.rb
new file mode 100644
index 0000000000..394377135f
--- /dev/null
+++ b/spec/ruby/library/matrix/fixtures/classes.rb
@@ -0,0 +1,7 @@
+require 'matrix'
+
+class MatrixSub < Matrix
+ def self.ins
+ rows([[1, 0], [0, 1]])
+ end
+end
diff --git a/spec/ruby/library/matrix/hash_spec.rb b/spec/ruby/library/matrix/hash_spec.rb
new file mode 100644
index 0000000000..7c1970511b
--- /dev/null
+++ b/spec/ruby/library/matrix/hash_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#hash" do
+
+ it "returns an Integer" do
+ Matrix[ [1,2] ].hash.should be_an_instance_of(Integer)
+ end
+
+ it "returns the same value for the same matrix" do
+ data = [ [40,5], [2,7] ]
+ Matrix[ *data ].hash.should == Matrix[ *data ].hash
+ end
+
+ end
+end
diff --git a/spec/ruby/library/matrix/hermitian_spec.rb b/spec/ruby/library/matrix/hermitian_spec.rb
new file mode 100644
index 0000000000..4038ee3fa9
--- /dev/null
+++ b/spec/ruby/library/matrix/hermitian_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix.hermitian?" do
+ it "returns true for a hermitian Matrix" do
+ Matrix[[1, 2, Complex(0, 3)], [2, 4, 5], [Complex(0, -3), 5, 6]].hermitian?.should be_true
+ end
+
+ it "returns true for a 0x0 empty matrix" do
+ Matrix.empty.hermitian?.should be_true
+ end
+
+ it "returns false for an asymmetric Matrix" do
+ Matrix[[1, 2],[-2, 1]].hermitian?.should be_false
+ end
+
+ it "raises an error for rectangular matrices" do
+ [
+ Matrix[[0], [0]],
+ Matrix[[0, 0]],
+ Matrix.empty(0, 2),
+ Matrix.empty(2, 0),
+ ].each do |rectangular_matrix|
+ -> {
+ rectangular_matrix.hermitian?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+
+ it "returns false for a matrix with complex values on the diagonal" do
+ Matrix[[Complex(1,1)]].hermitian?.should be_false
+ Matrix[[Complex(1,0)]].hermitian?.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/identity_spec.rb b/spec/ruby/library/matrix/identity_spec.rb
new file mode 100644
index 0000000000..14f51c402e
--- /dev/null
+++ b/spec/ruby/library/matrix/identity_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/identity'
+
+ describe "Matrix.identity" do
+ it_behaves_like :matrix_identity, :identity
+ end
+end
diff --git a/spec/ruby/library/matrix/imag_spec.rb b/spec/ruby/library/matrix/imag_spec.rb
new file mode 100644
index 0000000000..a89186ec02
--- /dev/null
+++ b/spec/ruby/library/matrix/imag_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/imaginary'
+
+ describe "Matrix#imag" do
+ it_behaves_like :matrix_imaginary, :imag
+ end
+end
diff --git a/spec/ruby/library/matrix/imaginary_spec.rb b/spec/ruby/library/matrix/imaginary_spec.rb
new file mode 100644
index 0000000000..e7f0e49d31
--- /dev/null
+++ b/spec/ruby/library/matrix/imaginary_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/imaginary'
+
+ describe "Matrix#imaginary" do
+ it_behaves_like :matrix_imaginary, :imaginary
+ end
+end
diff --git a/spec/ruby/library/matrix/inspect_spec.rb b/spec/ruby/library/matrix/inspect_spec.rb
new file mode 100644
index 0000000000..d754c0fe7f
--- /dev/null
+++ b/spec/ruby/library/matrix/inspect_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#inspect" do
+
+ it "returns a stringified representation of the Matrix" do
+ Matrix[ [1,2], [2,1] ].inspect.should == "Matrix[[1, 2], [2, 1]]"
+ end
+
+ it "returns 'Matrix.empty(...)' for empty matrices" do
+ Matrix[ [], [], [] ].inspect.should == "Matrix.empty(3, 0)"
+ Matrix.columns([ [], [], [] ]).inspect.should == "Matrix.empty(0, 3)"
+ end
+
+ it "calls inspect on its contents" do
+ obj = mock("some_value")
+ obj.should_receive(:inspect).and_return("some_value")
+ Matrix[ [1, 2], [3, obj] ].inspect.should == "Matrix[[1, 2], [3, some_value]]"
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns a string using the subclass' name" do
+ MatrixSub.ins.inspect.should == "MatrixSub[[1, 0], [0, 1]]"
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/inv_spec.rb b/spec/ruby/library/matrix/inv_spec.rb
new file mode 100644
index 0000000000..0ad817a253
--- /dev/null
+++ b/spec/ruby/library/matrix/inv_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'shared/inverse'
+
+ describe "Matrix#inv" do
+ it_behaves_like :inverse, :inv
+ end
+end
diff --git a/spec/ruby/library/matrix/inverse_from_spec.rb b/spec/ruby/library/matrix/inverse_from_spec.rb
new file mode 100644
index 0000000000..bca40542f6
--- /dev/null
+++ b/spec/ruby/library/matrix/inverse_from_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#inverse_from" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/inverse_spec.rb b/spec/ruby/library/matrix/inverse_spec.rb
new file mode 100644
index 0000000000..dd9099bec5
--- /dev/null
+++ b/spec/ruby/library/matrix/inverse_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'shared/inverse'
+
+ describe "Matrix#inverse" do
+ it_behaves_like :inverse, :inverse
+ end
+end
diff --git a/spec/ruby/library/matrix/lower_triangular_spec.rb b/spec/ruby/library/matrix/lower_triangular_spec.rb
new file mode 100644
index 0000000000..0223b8b619
--- /dev/null
+++ b/spec/ruby/library/matrix/lower_triangular_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix.lower_triangular?" do
+ it "returns true for a square lower triangular Matrix" do
+ Matrix[[1, 0, 0], [1, 2, 0], [1, 2, 3]].lower_triangular?.should be_true
+ Matrix.diagonal([1, 2, 3]).lower_triangular?.should be_true
+ Matrix[[1, 0], [1, 2], [1, 2], [1, 2]].lower_triangular?.should be_true
+ Matrix[[1, 0, 0, 0], [1, 2, 0, 0]].lower_triangular?.should be_true
+ end
+
+ it "returns true for an empty Matrix" do
+ Matrix.empty(3, 0).lower_triangular?.should be_true
+ Matrix.empty(0, 3).lower_triangular?.should be_true
+ Matrix.empty(0, 0).lower_triangular?.should be_true
+ end
+
+ it "returns false for a non lower triangular square Matrix" do
+ Matrix[[0, 1], [0, 0]].lower_triangular?.should be_false
+ Matrix[[1, 2, 3], [1, 2, 3], [1, 2, 3]].lower_triangular?.should be_false
+ Matrix[[0, 1], [0, 0], [0, 0], [0, 0]].lower_triangular?.should be_false
+ Matrix[[0, 0, 0, 1], [0, 0, 0, 0]].lower_triangular?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/lup_decomposition/determinant_spec.rb b/spec/ruby/library/matrix/lup_decomposition/determinant_spec.rb
new file mode 100644
index 0000000000..1ac4bc971e
--- /dev/null
+++ b/spec/ruby/library/matrix/lup_decomposition/determinant_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::LUPDecomposition#determinant" do
+ it "returns the determinant when the matrix is square" do
+ a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]]
+ a.lup.determinant.should == 15120 # == a.determinant
+ end
+
+ it "raises an error for rectangular matrices" do
+ [
+ Matrix[[7, 8, 9], [14, 46, 51]],
+ Matrix[[7, 8], [14, 46], [28, 82]],
+ ].each do |m|
+ lup = m.lup
+ -> {
+ lup.determinant
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/lup_decomposition/initialize_spec.rb b/spec/ruby/library/matrix/lup_decomposition/initialize_spec.rb
new file mode 100644
index 0000000000..42f78c7540
--- /dev/null
+++ b/spec/ruby/library/matrix/lup_decomposition/initialize_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::LUPDecomposition#initialize" do
+ it "raises an error if argument is not a matrix" do
+ -> {
+ Matrix::LUPDecomposition.new([[]])
+ }.should raise_error(TypeError)
+ -> {
+ Matrix::LUPDecomposition.new(42)
+ }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/lup_decomposition/l_spec.rb b/spec/ruby/library/matrix/lup_decomposition/l_spec.rb
new file mode 100644
index 0000000000..6c751f12b7
--- /dev/null
+++ b/spec/ruby/library/matrix/lup_decomposition/l_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::LUPDecomposition#l" do
+ before :each do
+ @a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]]
+ @lu = Matrix::LUPDecomposition.new(@a)
+ @l = @lu.l
+ end
+
+ it "returns the first element of to_a" do
+ @l.should == @lu.to_a[0]
+ end
+
+ it "returns a lower triangular matrix" do
+ @l.lower_triangular?.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/lup_decomposition/p_spec.rb b/spec/ruby/library/matrix/lup_decomposition/p_spec.rb
new file mode 100644
index 0000000000..481f8a17d5
--- /dev/null
+++ b/spec/ruby/library/matrix/lup_decomposition/p_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::LUPDecomposition#p" do
+ before :each do
+ @a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]]
+ @lu = Matrix::LUPDecomposition.new(@a)
+ @p = @lu.p
+ end
+
+ it "returns the third element of to_a" do
+ @p.should == @lu.to_a[2]
+ end
+
+ it "returns a permutation matrix" do
+ @p.permutation?.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/lup_decomposition/solve_spec.rb b/spec/ruby/library/matrix/lup_decomposition/solve_spec.rb
new file mode 100644
index 0000000000..773fcb3e65
--- /dev/null
+++ b/spec/ruby/library/matrix/lup_decomposition/solve_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::LUPDecomposition#solve" do
+ describe "for rectangular matrices" do
+ it "raises an error for singular matrices" do
+ a = Matrix[[1, 2, 3], [1, 3, 5], [2, 5, 8]]
+ lu = Matrix::LUPDecomposition.new(a)
+ -> {
+ lu.solve(a)
+ }.should raise_error(Matrix::ErrNotRegular)
+ end
+
+ describe "for non singular matrices" do
+ before :each do
+ @a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]]
+ @lu = Matrix::LUPDecomposition.new(@a)
+ end
+
+ it "returns the appropriate empty matrix when given an empty matrix" do
+ @lu.solve(Matrix.empty(3,0)).should == Matrix.empty(3,0)
+ empty = Matrix::LUPDecomposition.new(Matrix.empty(0, 0))
+ empty.solve(Matrix.empty(0,3)).should == Matrix.empty(0,3)
+ end
+
+ it "returns the right matrix when given a matrix of the appropriate size" do
+ solution = Matrix[[1, 2, 3, 4], [0, 1, 2, 3], [-1, -2, -3, -4]]
+ values = Matrix[[-2, 4, 10, 16], [-37, -28, -19, -10], [-135, -188, -241, -294]] # == @a * solution
+ @lu.solve(values).should == solution
+ end
+
+ it "raises an error when given a matrix of the wrong size" do
+ values = Matrix[[1, 2, 3, 4], [0, 1, 2, 3]]
+ -> {
+ @lu.solve(values)
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+ it "returns the right vector when given a vector of the appropriate size" do
+ solution = Vector[1, 2, -1]
+ values = Vector[14, 55, 29] # == @a * solution
+ @lu.solve(values).should == solution
+ end
+
+ it "raises an error when given a vector of the wrong size" do
+ values = Vector[14, 55]
+ -> {
+ @lu.solve(values)
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/lup_decomposition/to_a_spec.rb b/spec/ruby/library/matrix/lup_decomposition/to_a_spec.rb
new file mode 100644
index 0000000000..8702292865
--- /dev/null
+++ b/spec/ruby/library/matrix/lup_decomposition/to_a_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::LUPDecomposition#to_a" do
+ before :each do
+ @a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]]
+ @lu = Matrix::LUPDecomposition.new(@a)
+ @to_a = @lu.to_a
+ @l, @u, @p = @to_a
+ end
+
+ it "returns an array of three matrices" do
+ @to_a.should be_kind_of(Array)
+ @to_a.length.should == 3
+ @to_a.each{|m| m.should be_kind_of(Matrix)}
+ end
+
+ it "returns [l, u, p] such that l*u == a*p" do
+ (@l * @u).should == (@p * @a)
+ end
+
+ it "returns the right values for rectangular matrices" do
+ [
+ Matrix[[7, 8, 9], [14, 46, 51]],
+ Matrix[[4, 11], [5, 8], [3, 4]],
+ ].each do |a|
+ l, u, p = Matrix::LUPDecomposition.new(a).to_a
+ (l * u).should == (p * a)
+ end
+ end
+
+ it "has other properties implied by the specs of #l, #u and #p"
+ end
+end
diff --git a/spec/ruby/library/matrix/lup_decomposition/u_spec.rb b/spec/ruby/library/matrix/lup_decomposition/u_spec.rb
new file mode 100644
index 0000000000..cd884b88ec
--- /dev/null
+++ b/spec/ruby/library/matrix/lup_decomposition/u_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::LUPDecomposition#u" do
+ before :each do
+ @a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]]
+ @lu = Matrix::LUPDecomposition.new(@a)
+ @u = @lu.u
+ end
+
+ it "returns the second element of to_a" do
+ @u.should == @lu.to_a[1]
+ end
+
+ it "returns an upper triangular matrix" do
+ @u.upper_triangular?.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/map_spec.rb b/spec/ruby/library/matrix/map_spec.rb
new file mode 100644
index 0000000000..cde0df5441
--- /dev/null
+++ b/spec/ruby/library/matrix/map_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/collect'
+
+ describe "Matrix#map" do
+ it_behaves_like :collect, :map
+ end
+end
diff --git a/spec/ruby/library/matrix/minor_spec.rb b/spec/ruby/library/matrix/minor_spec.rb
new file mode 100644
index 0000000000..0a6b0823c8
--- /dev/null
+++ b/spec/ruby/library/matrix/minor_spec.rb
@@ -0,0 +1,88 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#minor" do
+ before :each do
+ @matrix = Matrix[ [1,2], [3,4], [5,6] ]
+ end
+
+ describe "with start_row, nrows, start_col, ncols" do
+ it "returns the given portion of the Matrix" do
+ @matrix.minor(0,1,0,2).should == Matrix[ [1, 2] ]
+ @matrix.minor(1,2,1,1).should == Matrix[ [4], [6] ]
+ end
+
+ it "returns an empty Matrix if nrows or ncols is 0" do
+ @matrix.minor(0,0,0,0).should == Matrix[]
+ @matrix.minor(1,0,1,0).should == Matrix[]
+ @matrix.minor(1,0,1,1).should == Matrix.columns([[]])
+ @matrix.minor(1,1,1,0).should == Matrix[[]]
+ end
+
+ it "returns nil for out-of-bounds start_row/col" do
+ r = @matrix.row_size + 1
+ c = @matrix.column_size + 1
+ @matrix.minor(r,0,0,10).should == nil
+ @matrix.minor(0,10,c,9).should == nil
+ @matrix.minor(-r,0,0,10).should == nil
+ @matrix.minor(0,10,-c,9).should == nil
+ end
+
+ it "returns nil for negative nrows or ncols" do
+ @matrix.minor(0,1,0,-1).should == nil
+ @matrix.minor(0,-1,0,1).should == nil
+ end
+
+ it "start counting backwards for start_row or start_col below zero" do
+ @matrix.minor(0, 1, -1, 1).should == @matrix.minor(0, 1, 1, 1)
+ @matrix.minor(-1, 1, 0, 1).should == @matrix.minor(2, 1, 0, 1)
+ end
+
+ it "returns empty matrices for extreme start_row/col" do
+ @matrix.minor(3,10,1,10).should == Matrix.columns([[]])
+ @matrix.minor(1,10,2,10).should == Matrix[[], []]
+ @matrix.minor(3,0,0,10).should == Matrix.columns([[], []])
+ end
+
+ it "ignores big nrows or ncols" do
+ @matrix.minor(0,1,0,20).should == Matrix[ [1, 2] ]
+ @matrix.minor(1,20,1,1).should == Matrix[ [4], [6] ]
+ end
+ end
+
+ describe "with col_range, row_range" do
+ it "returns the given portion of the Matrix" do
+ @matrix.minor(0..0, 0..1).should == Matrix[ [1, 2] ]
+ @matrix.minor(1..2, 1..2).should == Matrix[ [4], [6] ]
+ @matrix.minor(1...3, 1...3).should == Matrix[ [4], [6] ]
+ end
+
+ it "returns nil if col_range or row_range is out of range" do
+ r = @matrix.row_size + 1
+ c = @matrix.column_size + 1
+ @matrix.minor(r..6, c..6).should == nil
+ @matrix.minor(0..1, c..6).should == nil
+ @matrix.minor(r..6, 0..1).should == nil
+ @matrix.minor(-r..6, -c..6).should == nil
+ @matrix.minor(0..1, -c..6).should == nil
+ @matrix.minor(-r..6, 0..1).should == nil
+ end
+
+ it "start counting backwards for col_range or row_range below zero" do
+ @matrix.minor(0..1, -2..-1).should == @matrix.minor(0..1, 0..1)
+ @matrix.minor(0..1, -2..1).should == @matrix.minor(0..1, 0..1)
+ @matrix.minor(-2..-1, 0..1).should == @matrix.minor(1..2, 0..1)
+ @matrix.minor(-2..2, 0..1).should == @matrix.minor(1..2, 0..1)
+ end
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.ins.minor(0, 1, 0, 1).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/minus_spec.rb b/spec/ruby/library/matrix/minus_spec.rb
new file mode 100644
index 0000000000..27dfbeaea5
--- /dev/null
+++ b/spec/ruby/library/matrix/minus_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#-" do
+ before :each do
+ @a = Matrix[ [1, 2], [3, 4] ]
+ @b = Matrix[ [4, 5], [6, 7] ]
+ end
+
+ it "returns the result of subtracting the corresponding elements of other from self" do
+ (@a - @b).should == Matrix[ [-3,-3], [-3,-3] ]
+ end
+
+ it "returns an instance of Matrix" do
+ (@a - @b).should be_kind_of(Matrix)
+ end
+
+ it "raises a Matrix::ErrDimensionMismatch if the matrices are different sizes" do
+ -> { @a - Matrix[ [1] ] }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+ it "raises a ExceptionForMatrix::ErrOperationNotDefined if other is a Numeric Type" do
+ -> { @a - 2 }.should raise_error(Matrix::ErrOperationNotDefined)
+ -> { @a - 1.2 }.should raise_error(Matrix::ErrOperationNotDefined)
+ -> { @a - bignum_value }.should raise_error(Matrix::ErrOperationNotDefined)
+ end
+
+ it "raises a TypeError if other is of wrong type" do
+ -> { @a - nil }.should raise_error(TypeError)
+ -> { @a - "a" }.should raise_error(TypeError)
+ -> { @a - [ [1, 2] ] }.should raise_error(TypeError)
+ -> { @a - Object.new }.should raise_error(TypeError)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ m = MatrixSub.ins
+ (m-m).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/multiply_spec.rb b/spec/ruby/library/matrix/multiply_spec.rb
new file mode 100644
index 0000000000..a63fcf4020
--- /dev/null
+++ b/spec/ruby/library/matrix/multiply_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#*" do
+ before :each do
+ @a = Matrix[ [1, 2], [3, 4] ]
+ @b = Matrix[ [4, 5], [6, 7] ]
+ end
+
+ it "returns the result of multiplying the corresponding elements of self and a Matrix" do
+ (@a * @b).should == Matrix[ [16,19], [36,43] ]
+ end
+
+ it "returns the result of multiplying the corresponding elements of self and a Vector" do
+ (@a * Vector[1,2]).should == Vector[5, 11]
+ end
+
+ it "returns the result of multiplying the elements of self and a Fixnum" do
+ (@a * 2).should == Matrix[ [2, 4], [6, 8] ]
+ end
+
+ it "returns the result of multiplying the elements of self and a Bignum" do
+ (@a * bignum_value).should == Matrix[
+ [18446744073709551616, 36893488147419103232],
+ [55340232221128654848, 73786976294838206464]
+ ]
+ end
+
+ it "returns the result of multiplying the elements of self and a Float" do
+ (@a * 2.0).should == Matrix[ [2.0, 4.0], [6.0, 8.0] ]
+ end
+
+ it "raises a Matrix::ErrDimensionMismatch if the matrices are different sizes" do
+ -> { @a * Matrix[ [1] ] }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+ it "returns a zero matrix if (nx0) * (0xn)" do
+ (Matrix[[],[],[]] * Matrix.columns([[],[],[]])).should == Matrix.zero(3)
+ end
+
+ it "returns an empty matrix if (0xn) * (nx0)" do
+ (Matrix.columns([[],[],[]]) * Matrix[[],[],[]]).should == Matrix[]
+ end
+
+ it "returns a mx0 matrix if (mxn) * (nx0)" do
+ (Matrix[[1,2],[3,4],[5,6]] * Matrix[[],[]]).should == Matrix[[],[],[]]
+ end
+
+ it "returns a 0xm matrix if (0xm) * (mxn)" do
+ (Matrix.columns([[], [], []]) * Matrix[[1,2],[3,4],[5,6]]).should == Matrix.columns([[],[]])
+ end
+
+ it "raises a TypeError if other is of wrong type" do
+ -> { @a * nil }.should raise_error(TypeError)
+ -> { @a * "a" }.should raise_error(TypeError)
+ -> { @a * [ [1, 2] ] }.should raise_error(TypeError)
+ -> { @a * Object.new }.should raise_error(TypeError)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ m = MatrixSub.ins
+ (m*m).should be_an_instance_of(MatrixSub)
+ (m*1).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/new_spec.rb b/spec/ruby/library/matrix/new_spec.rb
new file mode 100644
index 0000000000..4be2e17116
--- /dev/null
+++ b/spec/ruby/library/matrix/new_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix.new" do
+ it "is private" do
+ Matrix.should have_private_method(:new)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/normal_spec.rb b/spec/ruby/library/matrix/normal_spec.rb
new file mode 100644
index 0000000000..2f2e138c1b
--- /dev/null
+++ b/spec/ruby/library/matrix/normal_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix.normal?" do
+ # it "returns false for non normal matrices" do
+ # Matrix[[0, 1], [1, 2]].should_not.normal?
+ # end
+
+ it "returns true for normal matrices" do
+ Matrix[[1, 1, 0], [0, 1, 1], [1, 0, 1]].should.normal?
+ Matrix[[0, Complex(0, 2)], [Complex(0, -2), 0]].should.normal?
+ end
+
+ it "raises an error for rectangular matrices" do
+ [
+ Matrix[[0], [0]],
+ Matrix[[0, 0]],
+ Matrix.empty(0, 2),
+ Matrix.empty(2, 0),
+ ].each do |rectangular_matrix|
+ -> {
+ rectangular_matrix.normal?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/orthogonal_spec.rb b/spec/ruby/library/matrix/orthogonal_spec.rb
new file mode 100644
index 0000000000..eb06305040
--- /dev/null
+++ b/spec/ruby/library/matrix/orthogonal_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix.orthogonal?" do
+ it "returns false for non orthogonal matrices" do
+ Matrix[[0, 1], [1, 2]].should_not.orthogonal?
+ Matrix[[1, 1, 0], [0, 1, 1], [1, 0, 1]].should_not.orthogonal?
+ end
+
+ it "returns true for orthogonal matrices" do
+ Matrix[[0, 1], [1, 0]].should.orthogonal?
+ end
+
+ it "raises an error for rectangular matrices" do
+ [
+ Matrix[[0], [0]],
+ Matrix[[0, 0]],
+ Matrix.empty(0, 2),
+ Matrix.empty(2, 0),
+ ].each do |rectangular_matrix|
+ -> {
+ rectangular_matrix.orthogonal?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/permutation_spec.rb b/spec/ruby/library/matrix/permutation_spec.rb
new file mode 100644
index 0000000000..ed8edad755
--- /dev/null
+++ b/spec/ruby/library/matrix/permutation_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#permutation?" do
+ it "returns true for a permutation Matrix" do
+ Matrix[[0, 1, 0], [0, 0, 1], [1, 0, 0]].permutation?.should be_true
+ end
+
+ it "returns false for a non permutation square Matrix" do
+ Matrix[[0, 1], [0, 0]].permutation?.should be_false
+ Matrix[[-1, 0], [0, -1]].permutation?.should be_false
+ Matrix[[1, 0], [1, 0]].permutation?.should be_false
+ Matrix[[1, 0], [1, 1]].permutation?.should be_false
+ end
+
+ it "returns true for an empty 0x0 matrix" do
+ Matrix.empty(0,0).permutation?.should be_true
+ end
+
+ it "raises an error for rectangular matrices" do
+ [
+ Matrix[[0], [0]],
+ Matrix[[0, 0]],
+ Matrix.empty(0, 2),
+ Matrix.empty(2, 0),
+ ].each do |rectangular_matrix|
+ -> {
+ rectangular_matrix.permutation?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/plus_spec.rb b/spec/ruby/library/matrix/plus_spec.rb
new file mode 100644
index 0000000000..99e6615778
--- /dev/null
+++ b/spec/ruby/library/matrix/plus_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#+" do
+ before :each do
+ @a = Matrix[ [1,2], [3,4] ]
+ @b = Matrix[ [4,5], [6,7] ]
+ end
+
+ it "returns the result of adding the corresponding elements of self and other" do
+ (@a + @b).should == Matrix[ [5,7], [9,11] ]
+ end
+
+ it "returns an instance of Matrix" do
+ (@a + @b).should be_kind_of(Matrix)
+ end
+
+ it "raises a Matrix::ErrDimensionMismatch if the matrices are different sizes" do
+ -> { @a + Matrix[ [1] ] }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+ it "raises a ExceptionForMatrix::ErrOperationNotDefined if other is a Numeric Type" do
+ -> { @a + 2 }.should raise_error(ExceptionForMatrix::ErrOperationNotDefined)
+ -> { @a + 1.2 }.should raise_error(ExceptionForMatrix::ErrOperationNotDefined)
+ -> { @a + bignum_value }.should raise_error(ExceptionForMatrix::ErrOperationNotDefined)
+ end
+
+ it "raises a TypeError if other is of wrong type" do
+ -> { @a + nil }.should raise_error(TypeError)
+ -> { @a + "a" }.should raise_error(TypeError)
+ -> { @a + [ [1, 2] ] }.should raise_error(TypeError)
+ -> { @a + Object.new }.should raise_error(TypeError)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ m = MatrixSub.ins
+ (m+m).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/rank_spec.rb b/spec/ruby/library/matrix/rank_spec.rb
new file mode 100644
index 0000000000..fc795b58c1
--- /dev/null
+++ b/spec/ruby/library/matrix/rank_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#rank" do
+ it "returns the rank of the Matrix" do
+ Matrix[ [7,6], [3,9] ].rank.should == 2
+ end
+
+ it "doesn't loop forever" do
+ Matrix[ [1,2,3], [4,5,6], [7,8,9] ].rank.should == 2
+ Matrix[ [1, 2, 0, 3], [1, -2, 3, 0], [0, 0, 4, 8], [2, 4, 0, 6] ].rank.
+ should == 3
+ end
+
+ it "works for some easy rectangular matrices" do
+ Matrix[[0,0],[0,0],[1,0]].rank.should == 1
+ Matrix[[0,1],[0,0],[1,0]].rank.should == 2
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/real_spec.rb b/spec/ruby/library/matrix/real_spec.rb
new file mode 100644
index 0000000000..63a6bde923
--- /dev/null
+++ b/spec/ruby/library/matrix/real_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#real?" do
+ it "returns true for matrices with all real entries" do
+ Matrix[ [1, 2], [3, 4] ].real?.should be_true
+ Matrix[ [1.9, 2], [3, 4] ].real?.should be_true
+ end
+
+ it "returns true for empty matrices" do
+ Matrix.empty.real?.should be_true
+ end
+
+ it "returns false if one element is a Complex" do
+ Matrix[ [Complex(1,1), 2], [3, 4] ].real?.should be_false
+ end
+
+ # Guard against the Mathn library
+ guard -> { !defined?(Math.rsqrt) } do
+ it "returns false if one element is a Complex whose imaginary part is 0" do
+ Matrix[ [Complex(1,0), 2], [3, 4] ].real?.should be_false
+ end
+ end
+ end
+
+ describe "Matrix#real" do
+ it "returns a matrix with the real part of the elements of the receiver" do
+ Matrix[ [1, 2], [3, 4] ].real.should == Matrix[ [1, 2], [3, 4] ]
+ Matrix[ [1.9, Complex(1,1)], [Complex(-0.42, 0), 4] ].real.should == Matrix[ [1.9, 1], [-0.42, 4] ]
+ end
+
+ it "returns empty matrices on the same size if empty" do
+ Matrix.empty(0, 3).real.should == Matrix.empty(0, 3)
+ Matrix.empty(3, 0).real.should == Matrix.empty(3, 0)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.ins.real.should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/rect_spec.rb b/spec/ruby/library/matrix/rect_spec.rb
new file mode 100644
index 0000000000..b3b7ac05c9
--- /dev/null
+++ b/spec/ruby/library/matrix/rect_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/rectangular'
+
+ describe "Matrix#rect" do
+ it_behaves_like :matrix_rectangular, :rect
+ end
+end
diff --git a/spec/ruby/library/matrix/rectangular_spec.rb b/spec/ruby/library/matrix/rectangular_spec.rb
new file mode 100644
index 0000000000..1fc2e0e60d
--- /dev/null
+++ b/spec/ruby/library/matrix/rectangular_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/rectangular'
+
+ describe "Matrix#rectangular" do
+ it_behaves_like :matrix_rectangular, :rectangular
+ end
+end
diff --git a/spec/ruby/library/matrix/regular_spec.rb b/spec/ruby/library/matrix/regular_spec.rb
new file mode 100644
index 0000000000..f6688bba30
--- /dev/null
+++ b/spec/ruby/library/matrix/regular_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#regular?" do
+
+ it "returns false for singular matrices" do
+ m = Matrix[ [1,2,3], [3,4,3], [0,0,0] ]
+ m.regular?.should be_false
+
+ m = Matrix[ [1,2,9], [3,4,9], [1,2,9] ]
+ m.regular?.should be_false
+ end
+
+ it "returns true if the Matrix is regular" do
+ Matrix[ [0,1], [1,0] ].regular?.should be_true
+ end
+
+ it "returns true for an empty 0x0 matrix" do
+ Matrix.empty(0,0).regular?.should be_true
+ end
+
+ it "raises an error for rectangular matrices" do
+ -> {
+ Matrix[[1], [2], [3]].regular?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+
+ -> {
+ Matrix.empty(3,0).regular?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/round_spec.rb b/spec/ruby/library/matrix/round_spec.rb
new file mode 100644
index 0000000000..db33268414
--- /dev/null
+++ b/spec/ruby/library/matrix/round_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix#round" do
+ it "returns a matrix with all entries rounded" do
+ Matrix[ [1, 2.34], [5.67, 8] ].round.should == Matrix[ [1, 2], [6, 8] ]
+ Matrix[ [1, 2.34], [5.67, 8] ].round(1).should == Matrix[ [1, 2.3], [5.7, 8] ]
+ end
+
+ it "returns empty matrices on the same size if empty" do
+ Matrix.empty(0, 3).round.should == Matrix.empty(0, 3)
+ Matrix.empty(3, 0).round(42).should == Matrix.empty(3, 0)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.ins.round.should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/row_size_spec.rb b/spec/ruby/library/matrix/row_size_spec.rb
new file mode 100644
index 0000000000..fca5a846ab
--- /dev/null
+++ b/spec/ruby/library/matrix/row_size_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#row_size" do
+ it "returns the number rows" do
+ Matrix[ [1,2], [3, 4], [5, 6] ].row_size.should == 3
+ end
+
+ it "returns the number rows even for some empty matrices" do
+ Matrix[ [], [], [] ].row_size.should == 3
+ end
+
+ end
+end
diff --git a/spec/ruby/library/matrix/row_spec.rb b/spec/ruby/library/matrix/row_spec.rb
new file mode 100644
index 0000000000..eb05cd9273
--- /dev/null
+++ b/spec/ruby/library/matrix/row_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#row" do
+ before :all do
+ @m = Matrix[ [1, 2], [2, 3], [3, 4] ]
+ end
+
+ it "returns a Vector when called without a block" do
+ @m.row(0).should == Vector[1,2]
+ end
+
+ it "yields the elements of the row when called with a block" do
+ a = []
+ @m.row(0) {|x| a << x}
+ a.should == [1,2]
+ end
+
+ it "counts backwards for negative argument" do
+ @m.row(-1).should == Vector[3, 4]
+ end
+
+ it "returns self when called with a block" do
+ @m.row(0) { |x| x }.should equal(@m)
+ end
+
+ it "returns nil when out of bounds" do
+ @m.row(3).should == nil
+ @m.row(-4).should == nil
+ end
+
+ it "never yields when out of bounds" do
+ -> { @m.row(3){ raise } }.should_not raise_error
+ -> { @m.row(-4){ raise } }.should_not raise_error
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/row_vector_spec.rb b/spec/ruby/library/matrix/row_vector_spec.rb
new file mode 100644
index 0000000000..4c97d6013a
--- /dev/null
+++ b/spec/ruby/library/matrix/row_vector_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix.row_vector" do
+
+ it "returns a Matrix" do
+ Matrix.row_vector([]).should be_an_instance_of(Matrix)
+ end
+
+ it "returns a single-row Matrix with the specified values" do
+ Matrix.row_vector([1,2]).should == Matrix[ [1,2] ]
+ end
+
+ it "returns a 1x0 matrix when called with an empty Array" do
+ Matrix.row_vector([]).should == Matrix[ [] ]
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.row_vector([1]).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/row_vectors_spec.rb b/spec/ruby/library/matrix/row_vectors_spec.rb
new file mode 100644
index 0000000000..d01a4ca10d
--- /dev/null
+++ b/spec/ruby/library/matrix/row_vectors_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#row_vectors" do
+
+ before :each do
+ @vectors = Matrix[ [1,2], [3,4] ].row_vectors
+ end
+
+ it "returns an Array" do
+ Matrix[ [1,2], [3,4] ].row_vectors.should be_an_instance_of(Array)
+ end
+
+ it "returns an Array of Vectors" do
+ @vectors.all? {|v| v.should be_an_instance_of(Vector)}
+ end
+
+ it "returns each row as a Vector" do
+ @vectors.should == [Vector[1,2], Vector[3,4]]
+ end
+
+ it "returns an empty Array for empty matrices" do
+ Matrix[].row_vectors.should == []
+ Matrix[ [] ].row_vectors.should == [ Vector[] ]
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/rows_spec.rb b/spec/ruby/library/matrix/rows_spec.rb
new file mode 100644
index 0000000000..5f0515a37b
--- /dev/null
+++ b/spec/ruby/library/matrix/rows_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix.rows" do
+ before :each do
+ @a = [1, 2]
+ @b = [3, 4]
+ @m = Matrix.rows([@a, @b])
+ end
+
+ it "returns a Matrix" do
+ @m.should be_kind_of(Matrix)
+ end
+
+ it "creates a matrix from argument rows" do
+ @m.row(0).to_a.should == @a
+ @m.row(1).to_a.should == @b
+ end
+
+ it "copies the original rows by default" do
+ @a << 3
+ @b << 6
+ @m.row(0).should_not equal(@a)
+ @m.row(1).should_not equal(@b)
+ end
+
+ it "references the original rows if copy is false" do
+ @m_ref = Matrix.rows([@a, @b], false)
+ @a << 3
+ @b << 6
+ @m_ref.row(0).to_a.should == @a
+ @m_ref.row(1).to_a.should == @b
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.rows([[0, 1], [0, 1]]).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/scalar/Fail_spec.rb b/spec/ruby/library/matrix/scalar/Fail_spec.rb
new file mode 100644
index 0000000000..4f774eda28
--- /dev/null
+++ b/spec/ruby/library/matrix/scalar/Fail_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::Scalar#Fail" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/scalar/Raise_spec.rb b/spec/ruby/library/matrix/scalar/Raise_spec.rb
new file mode 100644
index 0000000000..b405b6d6c5
--- /dev/null
+++ b/spec/ruby/library/matrix/scalar/Raise_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::Scalar#Raise" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/scalar/divide_spec.rb b/spec/ruby/library/matrix/scalar/divide_spec.rb
new file mode 100644
index 0000000000..0ef2206bf6
--- /dev/null
+++ b/spec/ruby/library/matrix/scalar/divide_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::Scalar#/" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/scalar/exponent_spec.rb b/spec/ruby/library/matrix/scalar/exponent_spec.rb
new file mode 100644
index 0000000000..87eea283d1
--- /dev/null
+++ b/spec/ruby/library/matrix/scalar/exponent_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::Scalar#**" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/scalar/included_spec.rb b/spec/ruby/library/matrix/scalar/included_spec.rb
new file mode 100644
index 0000000000..bab80e5120
--- /dev/null
+++ b/spec/ruby/library/matrix/scalar/included_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::Scalar.included" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/scalar/initialize_spec.rb b/spec/ruby/library/matrix/scalar/initialize_spec.rb
new file mode 100644
index 0000000000..af51562b43
--- /dev/null
+++ b/spec/ruby/library/matrix/scalar/initialize_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::Scalar#initialize" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/scalar/minus_spec.rb b/spec/ruby/library/matrix/scalar/minus_spec.rb
new file mode 100644
index 0000000000..966db1d75b
--- /dev/null
+++ b/spec/ruby/library/matrix/scalar/minus_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::Scalar#-" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/scalar/multiply_spec.rb b/spec/ruby/library/matrix/scalar/multiply_spec.rb
new file mode 100644
index 0000000000..21caac478b
--- /dev/null
+++ b/spec/ruby/library/matrix/scalar/multiply_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::Scalar#*" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/scalar/plus_spec.rb b/spec/ruby/library/matrix/scalar/plus_spec.rb
new file mode 100644
index 0000000000..78cdf9367f
--- /dev/null
+++ b/spec/ruby/library/matrix/scalar/plus_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix::Scalar#+" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/scalar_spec.rb b/spec/ruby/library/matrix/scalar_spec.rb
new file mode 100644
index 0000000000..1ec64d2d78
--- /dev/null
+++ b/spec/ruby/library/matrix/scalar_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix.scalar" do
+
+ before :each do
+ @side = 3
+ @value = 8
+ @a = Matrix.scalar(@side, @value)
+ end
+
+ it "returns a Matrix" do
+ @a.should be_kind_of(Matrix)
+ end
+
+ it "returns a n x n matrix" do
+ @a.row_size.should == @side
+ @a.column_size.should == @side
+ end
+
+ it "initializes diagonal to value" do
+ (0...@a.row_size).each do |i|
+ @a[i, i].should == @value
+ end
+ end
+
+ it "initializes all non-diagonal values to 0" do
+ (0...@a.row_size).each do |i|
+ (0...@a.column_size).each do |j|
+ if i != j
+ @a[i, j].should == 0
+ end
+ end
+ end
+ end
+
+ before :each do
+ @side = 3
+ @value = 8
+ @a = Matrix.scalar(@side, @value)
+ end
+
+ it "returns a Matrix" do
+ @a.should be_kind_of(Matrix)
+ end
+
+ it "returns a square matrix, where the first argument specifies the side of the square" do
+ @a.row_size.should == @side
+ @a.column_size.should == @side
+ end
+
+ it "puts the second argument in all diagonal values" do
+ (0...@a.row_size).each do |i|
+ @a[i, i].should == @value
+ end
+ end
+
+ it "fills all values not on the main diagonal with 0" do
+ (0...@a.row_size).each do |i|
+ (0...@a.column_size).each do |j|
+ if i != j
+ @a[i, j].should == 0
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/shared/collect.rb b/spec/ruby/library/matrix/shared/collect.rb
new file mode 100644
index 0000000000..852f7fd6cf
--- /dev/null
+++ b/spec/ruby/library/matrix/shared/collect.rb
@@ -0,0 +1,26 @@
+require_relative '../fixtures/classes'
+require 'matrix'
+
+describe :collect, shared: true do
+ before :all do
+ @m = Matrix[ [1, 2], [1, 2] ]
+ end
+
+ it "returns an instance of Matrix" do
+ @m.send(@method){|n| n * 2 }.should be_kind_of(Matrix)
+ end
+
+ it "returns a Matrix where each element is the result of the block" do
+ @m.send(@method) { |n| n * 2 }.should == Matrix[ [2, 4], [2, 4] ]
+ end
+
+ it "returns an enumerator if no block is given" do
+ @m.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.ins.send(@method){1}.should be_an_instance_of(MatrixSub)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/shared/conjugate.rb b/spec/ruby/library/matrix/shared/conjugate.rb
new file mode 100644
index 0000000000..d87658f855
--- /dev/null
+++ b/spec/ruby/library/matrix/shared/conjugate.rb
@@ -0,0 +1,20 @@
+require_relative '../fixtures/classes'
+require 'matrix'
+
+describe :matrix_conjugate, shared: true do
+ it "returns a matrix with all entries 'conjugated'" do
+ Matrix[ [1, 2], [3, 4] ].send(@method).should == Matrix[ [1, 2], [3, 4] ]
+ Matrix[ [1.9, Complex(1,1)], [3, 4] ].send(@method).should == Matrix[ [1.9, Complex(1,-1)], [3, 4] ]
+ end
+
+ it "returns empty matrices on the same size if empty" do
+ Matrix.empty(0, 3).send(@method).should == Matrix.empty(0, 3)
+ Matrix.empty(3, 0).send(@method).should == Matrix.empty(3, 0)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.ins.send(@method).should be_an_instance_of(MatrixSub)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/shared/determinant.rb b/spec/ruby/library/matrix/shared/determinant.rb
new file mode 100644
index 0000000000..9e0528c24b
--- /dev/null
+++ b/spec/ruby/library/matrix/shared/determinant.rb
@@ -0,0 +1,38 @@
+require 'matrix'
+
+describe :determinant, shared: true do
+ it "returns the determinant of a square Matrix" do
+ m = Matrix[ [7,6], [3,9] ]
+ m.send(@method).should == 45
+
+ m = Matrix[ [9, 8], [6,5] ]
+ m.send(@method).should == -3
+
+ m = Matrix[ [9,8,3], [4,20,5], [1,1,1] ]
+ m.send(@method).should == 95
+ end
+
+ it "returns the determinant of a single-element Matrix" do
+ m = Matrix[ [2] ]
+ m.send(@method).should == 2
+ end
+
+ it "returns 1 for an empty Matrix" do
+ m = Matrix[ ]
+ m.send(@method).should == 1
+ end
+
+ it "returns the determinant even for Matrices containing 0 as first entry" do
+ Matrix[[0,1],[1,0]].send(@method).should == -1
+ end
+
+ it "raises an error for rectangular matrices" do
+ -> {
+ Matrix[[1], [2], [3]].send(@method)
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+
+ -> {
+ Matrix.empty(3,0).send(@method)
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+end
diff --git a/spec/ruby/library/matrix/shared/equal_value.rb b/spec/ruby/library/matrix/shared/equal_value.rb
new file mode 100644
index 0000000000..2b2311d49e
--- /dev/null
+++ b/spec/ruby/library/matrix/shared/equal_value.rb
@@ -0,0 +1,33 @@
+require_relative '../fixtures/classes'
+require 'matrix'
+
+describe :equal, shared: true do
+ before do
+ @matrix = Matrix[ [1, 2, 3, 4, 5], [2, 3, 4, 5, 6] ]
+ end
+
+ it "returns true for self" do
+ @matrix.send(@method, @matrix).should be_true
+ end
+
+ it "returns true for equal matrices" do
+ @matrix.send(@method, Matrix[ [1, 2, 3, 4, 5], [2, 3, 4, 5, 6] ]).should be_true
+ end
+
+ it "returns false for different matrices" do
+ @matrix.send(@method, Matrix[ [42, 2, 3, 4, 5], [2, 3, 4, 5, 6] ]).should be_false
+ @matrix.send(@method, Matrix[ [1, 2, 3, 4, 5, 6], [2, 3, 4, 5, 6, 7] ]).should be_false
+ @matrix.send(@method, Matrix[ [1, 2, 3], [2, 3, 4] ]).should be_false
+ end
+
+ it "returns false for different empty matrices" do
+ Matrix.empty(42, 0).send(@method, Matrix.empty(6, 0)).should be_false
+ Matrix.empty(0, 42).send(@method, Matrix.empty(0, 6)).should be_false
+ Matrix.empty(0, 0).send(@method, Matrix.empty(6, 0)).should be_false
+ Matrix.empty(0, 0).send(@method, Matrix.empty(0, 6)).should be_false
+ end
+
+ it "doesn't distinguish on subclasses" do
+ MatrixSub.ins.send(@method, Matrix.I(2)).should be_true
+ end
+end
diff --git a/spec/ruby/library/matrix/shared/identity.rb b/spec/ruby/library/matrix/shared/identity.rb
new file mode 100644
index 0000000000..114f86e7b0
--- /dev/null
+++ b/spec/ruby/library/matrix/shared/identity.rb
@@ -0,0 +1,19 @@
+require_relative '../fixtures/classes'
+require 'matrix'
+
+describe :matrix_identity, shared: true do
+ it "returns a Matrix" do
+ Matrix.send(@method, 2).should be_kind_of(Matrix)
+ end
+
+ it "returns a n x n identity matrix" do
+ Matrix.send(@method, 3).should == Matrix.scalar(3, 1)
+ Matrix.send(@method, 100).should == Matrix.scalar(100, 1)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.send(@method, 2).should be_an_instance_of(MatrixSub)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/shared/imaginary.rb b/spec/ruby/library/matrix/shared/imaginary.rb
new file mode 100644
index 0000000000..d28ecc69f1
--- /dev/null
+++ b/spec/ruby/library/matrix/shared/imaginary.rb
@@ -0,0 +1,20 @@
+require_relative '../fixtures/classes'
+require 'matrix'
+
+describe :matrix_imaginary, shared: true do
+ it "returns a matrix with the imaginary part of the elements of the receiver" do
+ Matrix[ [1, 2], [3, 4] ].send(@method).should == Matrix[ [0, 0], [0, 0] ]
+ Matrix[ [1.9, Complex(1,1)], [Complex(-2,0.42), 4] ].send(@method).should == Matrix[ [0, 1], [0.42, 0] ]
+ end
+
+ it "returns empty matrices on the same size if empty" do
+ Matrix.empty(0, 3).send(@method).should == Matrix.empty(0, 3)
+ Matrix.empty(3, 0).send(@method).should == Matrix.empty(3, 0)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.ins.send(@method).should be_an_instance_of(MatrixSub)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/shared/inverse.rb b/spec/ruby/library/matrix/shared/inverse.rb
new file mode 100644
index 0000000000..c8a6b90da5
--- /dev/null
+++ b/spec/ruby/library/matrix/shared/inverse.rb
@@ -0,0 +1,38 @@
+require_relative '../fixtures/classes'
+require 'matrix'
+
+describe :inverse, shared: true do
+
+ it "returns a Matrix" do
+ Matrix[ [1,2], [2,1] ].send(@method).should be_an_instance_of(Matrix)
+ end
+
+ it "returns the inverse of the Matrix" do
+ Matrix[
+ [1, 3, 3], [1, 4, 3], [1, 3, 4]
+ ].send(@method).should ==
+ Matrix[
+ [7, -3, -3], [-1, 1, 0], [-1, 0, 1]
+ ]
+ end
+
+ it "returns the inverse of the Matrix (other case)" do
+ Matrix[
+ [1, 2, 3], [0, 1, 4], [5, 6, 0]
+ ].send(@method).should be_close_to_matrix([
+ [-24, 18, 5], [20, -15, -4], [-5, 4, 1]
+ ])
+ end
+
+ it "raises a ErrDimensionMismatch if the Matrix is not square" do
+ ->{
+ Matrix[ [1,2,3], [1,2,3] ].send(@method)
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.ins.send(@method).should be_an_instance_of(MatrixSub)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/shared/rectangular.rb b/spec/ruby/library/matrix/shared/rectangular.rb
new file mode 100644
index 0000000000..3d9a0dfe8a
--- /dev/null
+++ b/spec/ruby/library/matrix/shared/rectangular.rb
@@ -0,0 +1,18 @@
+require_relative '../fixtures/classes'
+require 'matrix'
+
+describe :matrix_rectangular, shared: true do
+ it "returns [receiver.real, receiver.imag]" do
+ m = Matrix[ [1.2, Complex(1,2)], [Complex(-2,0.42), 4] ]
+ m.send(@method).should == [m.real, m.imag]
+
+ m = Matrix.empty(3, 0)
+ m.send(@method).should == [m.real, m.imag]
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns instances of that subclass" do
+ MatrixSub.ins.send(@method).each{|m| m.should be_an_instance_of(MatrixSub) }
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/shared/trace.rb b/spec/ruby/library/matrix/shared/trace.rb
new file mode 100644
index 0000000000..57b89863f8
--- /dev/null
+++ b/spec/ruby/library/matrix/shared/trace.rb
@@ -0,0 +1,12 @@
+require 'matrix'
+
+describe :trace, shared: true do
+ it "returns the sum of diagonal elements in a square Matrix" do
+ Matrix[[7,6], [3,9]].trace.should == 16
+ end
+
+ it "returns the sum of diagonal elements in a rectangular Matrix" do
+ ->{ Matrix[[1,2,3], [4,5,6]].trace}.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+end
diff --git a/spec/ruby/library/matrix/shared/transpose.rb b/spec/ruby/library/matrix/shared/transpose.rb
new file mode 100644
index 0000000000..89b1d025be
--- /dev/null
+++ b/spec/ruby/library/matrix/shared/transpose.rb
@@ -0,0 +1,19 @@
+require_relative '../fixtures/classes'
+require 'matrix'
+
+describe :matrix_transpose, shared: true do
+ it "returns a transposed matrix" do
+ Matrix[[1, 2], [3, 4], [5, 6]].send(@method).should == Matrix[[1, 3, 5], [2, 4, 6]]
+ end
+
+ it "can transpose empty matrices" do
+ m = Matrix[[], [], []]
+ m.send(@method).send(@method).should == m
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.ins.send(@method).should be_an_instance_of(MatrixSub)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/singular_spec.rb b/spec/ruby/library/matrix/singular_spec.rb
new file mode 100644
index 0000000000..341c2675a8
--- /dev/null
+++ b/spec/ruby/library/matrix/singular_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#singular?" do
+ it "returns true for singular matrices" do
+ m = Matrix[ [1,2,3], [3,4,3], [0,0,0] ]
+ m.singular?.should be_true
+
+ m = Matrix[ [1,2,9], [3,4,9], [1,2,9] ]
+ m.singular?.should be_true
+ end
+
+ it "returns false if the Matrix is regular" do
+ Matrix[ [0,1], [1,0] ].singular?.should be_false
+ end
+
+ it "returns false for an empty 0x0 matrix" do
+ Matrix.empty(0,0).singular?.should be_false
+ end
+
+ it "raises an error for rectangular matrices" do
+ -> {
+ Matrix[[1], [2], [3]].singular?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+
+ -> {
+ Matrix.empty(3,0).singular?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+
+ end
+end
diff --git a/spec/ruby/library/matrix/spec_helper.rb b/spec/ruby/library/matrix/spec_helper.rb
new file mode 100644
index 0000000000..d44612981a
--- /dev/null
+++ b/spec/ruby/library/matrix/spec_helper.rb
@@ -0,0 +1,35 @@
+class BeCloseToMatrixMatcher
+ def initialize(expected, tolerance = TOLERANCE)
+ SpecExpectation.matcher! rescue "Used with the balance_should_and_match branch of mspec"
+ @expected = Matrix[*expected]
+ @tolerance = tolerance
+ end
+
+ def matches?(actual)
+ @actual = actual
+ return false unless @actual.is_a? Matrix
+ return false unless @actual.row_size == @expected.row_size
+ @actual.row_size.times do |i|
+ a, e = @actual.row(i), @expected.row(i)
+ return false unless a.size == e.size
+ a.size.times do |j|
+ return false unless (a[j] - e[j]).abs < @tolerance
+ end
+ end
+ true
+ end
+
+ def failure_message
+ ["Expected #{@expected}", "to be within +/- #{@tolerance} of #{@actual}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@expected}", "not to be within +/- #{@tolerance} of #{@actual}"]
+ end
+end
+
+class Object
+ def be_close_to_matrix(expected, tolerance = TOLERANCE)
+ BeCloseToMatrixMatcher.new(expected, tolerance)
+ end
+end
diff --git a/spec/ruby/library/matrix/square_spec.rb b/spec/ruby/library/matrix/square_spec.rb
new file mode 100644
index 0000000000..e678f1c702
--- /dev/null
+++ b/spec/ruby/library/matrix/square_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#square?" do
+
+ it "returns true when the Matrix is square" do
+ Matrix[ [1,2], [2,4] ].square?.should be_true
+ Matrix[ [100,3,5], [9.5, 4.9, 8], [2,0,77] ].square?.should be_true
+ end
+
+ it "returns true when the Matrix has only one element" do
+ Matrix[ [9] ].square?.should be_true
+ end
+
+ it "returns false when the Matrix is rectangular" do
+ Matrix[ [1, 2] ].square?.should be_false
+ end
+
+ it "returns false when the Matrix is rectangular" do
+ Matrix[ [1], [2] ].square?.should be_false
+ end
+
+ it "returns handles empty matrices" do
+ Matrix[].square?.should be_true
+ Matrix[[]].square?.should be_false
+ Matrix.columns([[]]).square?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/symmetric_spec.rb b/spec/ruby/library/matrix/symmetric_spec.rb
new file mode 100644
index 0000000000..9eed28ac0d
--- /dev/null
+++ b/spec/ruby/library/matrix/symmetric_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix.symmetric?" do
+ it "returns true for a symmetric Matrix" do
+ Matrix[[1, 2, Complex(0, 3)], [2, 4, 5], [Complex(0, 3), 5, 6]].symmetric?.should be_true
+ end
+
+ it "returns true for a 0x0 empty matrix" do
+ Matrix.empty.symmetric?.should be_true
+ end
+
+ it "returns false for an asymmetric Matrix" do
+ Matrix[[1, 2],[-2, 1]].symmetric?.should be_false
+ end
+
+ it "raises an error for rectangular matrices" do
+ [
+ Matrix[[0], [0]],
+ Matrix[[0, 0]],
+ Matrix.empty(0, 2),
+ Matrix.empty(2, 0),
+ ].each do |rectangular_matrix|
+ -> {
+ rectangular_matrix.symmetric?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/t_spec.rb b/spec/ruby/library/matrix/t_spec.rb
new file mode 100644
index 0000000000..6eb54371ee
--- /dev/null
+++ b/spec/ruby/library/matrix/t_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/transpose'
+
+ describe "Matrix#transpose" do
+ it_behaves_like :matrix_transpose, :t
+ end
+end
diff --git a/spec/ruby/library/matrix/to_a_spec.rb b/spec/ruby/library/matrix/to_a_spec.rb
new file mode 100644
index 0000000000..0222a663ac
--- /dev/null
+++ b/spec/ruby/library/matrix/to_a_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#to_a" do
+ it "returns the array of arrays that describe the rows of the matrix" do
+ Matrix[].to_a.should == []
+ Matrix[[]].to_a.should == [[]]
+ Matrix[[1]].to_a.should == [[1]]
+ Matrix[[1, 2], [3, 4]].to_a.should == [[1, 2],[3, 4]]
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/to_s_spec.rb b/spec/ruby/library/matrix/to_s_spec.rb
new file mode 100644
index 0000000000..7d38655e0d
--- /dev/null
+++ b/spec/ruby/library/matrix/to_s_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix#to_s" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/matrix/tr_spec.rb b/spec/ruby/library/matrix/tr_spec.rb
new file mode 100644
index 0000000000..bbf274bb5e
--- /dev/null
+++ b/spec/ruby/library/matrix/tr_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/trace'
+ require 'matrix'
+
+ describe "Matrix#tr" do
+ it_behaves_like :trace, :tr
+ end
+end
diff --git a/spec/ruby/library/matrix/trace_spec.rb b/spec/ruby/library/matrix/trace_spec.rb
new file mode 100644
index 0000000000..ac6ce7927d
--- /dev/null
+++ b/spec/ruby/library/matrix/trace_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/trace'
+ require 'matrix'
+
+ describe "Matrix#trace" do
+ it_behaves_like :trace, :trace
+ end
+end
diff --git a/spec/ruby/library/matrix/transpose_spec.rb b/spec/ruby/library/matrix/transpose_spec.rb
new file mode 100644
index 0000000000..d7f495b946
--- /dev/null
+++ b/spec/ruby/library/matrix/transpose_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/transpose'
+
+ describe "Matrix#transpose" do
+ it_behaves_like :matrix_transpose, :transpose
+ end
+end
diff --git a/spec/ruby/library/matrix/unit_spec.rb b/spec/ruby/library/matrix/unit_spec.rb
new file mode 100644
index 0000000000..439e0d616b
--- /dev/null
+++ b/spec/ruby/library/matrix/unit_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/identity'
+
+ describe "Matrix.unit" do
+ it_behaves_like :matrix_identity, :unit
+ end
+end
diff --git a/spec/ruby/library/matrix/unitary_spec.rb b/spec/ruby/library/matrix/unitary_spec.rb
new file mode 100644
index 0000000000..b579cb244d
--- /dev/null
+++ b/spec/ruby/library/matrix/unitary_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix.unitary?" do
+ it "returns false for non unitary matrices" do
+ Matrix[[0, 1], [1, 2]].should_not.unitary?
+ Matrix[[0, Complex(0, 2)], [Complex(0, 2), 0]].should_not.unitary?
+ Matrix[[1, 1, 0], [0, 1, 1], [1, 0, 1]].should_not.unitary?
+ end
+
+ it "returns true for unitary matrices" do
+ Matrix[[0, Complex(0, 1)], [Complex(0, 1), 0]].should.unitary?
+ end
+
+ version_is((Matrix::const_defined?(:VERSION) ? Matrix::VERSION : "0.1.0"), "0.3.0") do
+ it "returns true for unitary matrices with a Complex and a negative #imag" do
+ Matrix[[0, Complex(0, 1)], [Complex(0, -1), 0]].should.unitary?
+ end
+ end
+
+ it "raises an error for rectangular matrices" do
+ [
+ Matrix[[0], [0]],
+ Matrix[[0, 0]],
+ Matrix.empty(0, 2),
+ Matrix.empty(2, 0),
+ ].each do |rectangular_matrix|
+ -> {
+ rectangular_matrix.unitary?
+ }.should raise_error(Matrix::ErrDimensionMismatch)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/upper_triangular_spec.rb b/spec/ruby/library/matrix/upper_triangular_spec.rb
new file mode 100644
index 0000000000..cc2aeb7233
--- /dev/null
+++ b/spec/ruby/library/matrix/upper_triangular_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Matrix.upper_triangular?" do
+ it "returns true for an upper triangular Matrix" do
+ Matrix[[1, 2, 3], [0, 2, 3], [0, 0, 3]].upper_triangular?.should be_true
+ Matrix.diagonal([1, 2, 3]).upper_triangular?.should be_true
+ Matrix[[1, 2], [0, 2], [0, 0], [0, 0]].upper_triangular?.should be_true
+ Matrix[[1, 2, 3, 4], [0, 2, 3, 4]].upper_triangular?.should be_true
+ end
+
+ it "returns false for a non upper triangular square Matrix" do
+ Matrix[[0, 0], [1, 0]].upper_triangular?.should be_false
+ Matrix[[1, 2, 3], [1, 2, 3], [1, 2, 3]].upper_triangular?.should be_false
+ Matrix[[0, 0], [0, 0], [0, 0], [0, 1]].upper_triangular?.should be_false
+ Matrix[[0, 0, 0, 0], [1, 0, 0, 0]].upper_triangular?.should be_false
+ end
+
+ it "returns true for an empty matrix" do
+ Matrix.empty(3,0).upper_triangular?.should be_true
+ Matrix.empty(0,3).upper_triangular?.should be_true
+ Matrix.empty(0,0).upper_triangular?.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/vector/cross_product_spec.rb b/spec/ruby/library/matrix/vector/cross_product_spec.rb
new file mode 100644
index 0000000000..88523824cd
--- /dev/null
+++ b/spec/ruby/library/matrix/vector/cross_product_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Vector#cross_product" do
+ it "returns the cross product of a vector" do
+ Vector[1, 2, 3].cross_product(Vector[0, -4, 5]).should == Vector[22, -5, -4]
+ end
+
+ it "raises an error unless both vectors have dimension 3" do
+ -> {
+ Vector[1, 2, 3].cross_product(Vector[0, -4])
+ }.should raise_error(Vector::ErrDimensionMismatch)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/vector/each2_spec.rb b/spec/ruby/library/matrix/vector/each2_spec.rb
new file mode 100644
index 0000000000..bdd6966472
--- /dev/null
+++ b/spec/ruby/library/matrix/vector/each2_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Vector.each2" do
+ before :all do
+ @v = Vector[1, 2, 3]
+ @v2 = Vector[4, 5, 6]
+ end
+
+ it "requires one argument" do
+ -> { @v.each2(@v2, @v2){} }.should raise_error(ArgumentError)
+ -> { @v.each2(){} }.should raise_error(ArgumentError)
+ end
+
+ describe "given one argument" do
+ it "accepts an Array argument" do
+ a = []
+ @v.each2([7, 8, 9]){|x, y| a << x << y}
+ a.should == [1, 7, 2, 8, 3, 9]
+ end
+
+ it "raises a DimensionMismatch error if the Vector size is different" do
+ -> { @v.each2(Vector[1,2]){} }.should raise_error(Vector::ErrDimensionMismatch)
+ -> { @v.each2(Vector[1,2,3,4]){} }.should raise_error(Vector::ErrDimensionMismatch)
+ end
+
+ it "yields arguments in sequence" do
+ a = []
+ @v.each2(@v2){|first, second| a << [first, second]}
+ a.should == [[1, 4], [2, 5], [3, 6]]
+ end
+
+ it "yield arguments in pairs" do
+ a = []
+ @v.each2(@v2){|*pair| a << pair}
+ a.should == [[1, 4], [2, 5], [3, 6]]
+ end
+
+ it "returns self when given a block" do
+ @v.each2(@v2){}.should equal(@v)
+ end
+
+ it "returns an enumerator if no block given" do
+ enum = @v.each2(@v2)
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == [[1, 4], [2, 5], [3, 6]]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/vector/eql_spec.rb b/spec/ruby/library/matrix/vector/eql_spec.rb
new file mode 100644
index 0000000000..fa086bd33a
--- /dev/null
+++ b/spec/ruby/library/matrix/vector/eql_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Vector#eql?" do
+ before do
+ @vector = Vector[1, 2, 3, 4, 5]
+ end
+
+ it "returns true for self" do
+ @vector.eql?(@vector).should be_true
+ end
+
+ it "returns false when there are a pair corresponding elements which are not equal in the sense of Kernel#eql?" do
+ @vector.eql?(Vector[1, 2, 3, 4, 5.0]).should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/vector/inner_product_spec.rb b/spec/ruby/library/matrix/vector/inner_product_spec.rb
new file mode 100644
index 0000000000..95dc4806b5
--- /dev/null
+++ b/spec/ruby/library/matrix/vector/inner_product_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Vector#inner_product" do
+ it "returns the inner product of a vector" do
+ Vector[1, 2, 3].inner_product(Vector[0, -4, 5]).should == 7
+ end
+
+ it "returns 0 for empty vectors" do
+ Vector[].inner_product(Vector[]).should == 0
+ end
+
+ it "raises an error for mismatched vectors" do
+ -> {
+ Vector[1, 2, 3].inner_product(Vector[0, -4])
+ }.should raise_error(Vector::ErrDimensionMismatch)
+ end
+
+ it "uses the conjugate of its argument" do
+ Vector[Complex(1,2)].inner_product(Vector[Complex(3,4)]).should == Complex(11, 2)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/vector/normalize_spec.rb b/spec/ruby/library/matrix/vector/normalize_spec.rb
new file mode 100644
index 0000000000..7345e11fa4
--- /dev/null
+++ b/spec/ruby/library/matrix/vector/normalize_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'matrix'
+
+ describe "Vector#normalize" do
+ it "returns a normalized copy of the vector" do
+ x = 0.2672612419124244
+ Vector[1, 2, 3].normalize.should == Vector[x, x * 2, x * 3]
+ end
+
+ it "raises an error for zero vectors" do
+ -> {
+ Vector[].normalize
+ }.should raise_error(Vector::ZeroVectorError)
+ -> {
+ Vector[0, 0, 0].normalize
+ }.should raise_error(Vector::ZeroVectorError)
+ end
+ end
+end
diff --git a/spec/ruby/library/matrix/zero_spec.rb b/spec/ruby/library/matrix/zero_spec.rb
new file mode 100644
index 0000000000..80162a03d0
--- /dev/null
+++ b/spec/ruby/library/matrix/zero_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'fixtures/classes'
+ require 'matrix'
+
+ describe "Matrix.zero" do
+ it "returns an object of type Matrix" do
+ Matrix.zero(3).should be_kind_of(Matrix)
+ end
+
+ it "creates a n x n matrix" do
+ m3 = Matrix.zero(3)
+ m3.row_size.should == 3
+ m3.column_size.should == 3
+
+ m8 = Matrix.zero(8)
+ m8.row_size.should == 8
+ m8.column_size.should == 8
+ end
+
+ it "initializes all cells to 0" do
+ size = 10
+ m = Matrix.zero(size)
+
+ (0...size).each do |i|
+ (0...size).each do |j|
+ m[i, j].should == 0
+ end
+ end
+ end
+
+ describe "for a subclass of Matrix" do
+ it "returns an instance of that subclass" do
+ MatrixSub.zero(3).should be_an_instance_of(MatrixSub)
+ end
+ end
+ end
+
+ describe "Matrix.zero?" do
+ it "returns true for empty matrices" do
+ Matrix.empty.should.zero?
+ Matrix.empty(3,0).should.zero?
+ Matrix.empty(0,3).should.zero?
+ end
+
+ it "returns true for matrices with zero entries" do
+ Matrix.zero(2,3).should.zero?
+ end
+
+ it "returns false for matrices with non zero entries" do
+ Matrix[[1]].should_not.zero?
+ end
+ end
+end
diff --git a/spec/ruby/library/mkmf/mkmf_spec.rb b/spec/ruby/library/mkmf/mkmf_spec.rb
new file mode 100644
index 0000000000..18b090e703
--- /dev/null
+++ b/spec/ruby/library/mkmf/mkmf_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe 'mkmf' do
+ it 'can be required with --enable-frozen-string-literal' do
+ ruby_exe('p MakeMakefile', options: '-rmkmf --enable-frozen-string-literal').should == "MakeMakefile\n"
+ end
+end
diff --git a/spec/ruby/library/monitor/enter_spec.rb b/spec/ruby/library/monitor/enter_spec.rb
new file mode 100644
index 0000000000..f523c42087
--- /dev/null
+++ b/spec/ruby/library/monitor/enter_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require 'monitor'
+
+describe "Monitor#enter" do
+ it "acquires the monitor" do
+ monitor = Monitor.new
+ 10.times do
+ wait_q = Queue.new
+ continue_q = Queue.new
+
+ thread = Thread.new do
+ begin
+ monitor.enter
+ wait_q << true
+ continue_q.pop
+ ensure
+ monitor.exit
+ end
+ end
+
+ wait_q.pop
+ monitor.mon_locked?.should == true
+ continue_q << true
+ thread.join
+ monitor.mon_locked?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/library/monitor/mon_initialize_spec.rb b/spec/ruby/library/monitor/mon_initialize_spec.rb
new file mode 100644
index 0000000000..e0fe6c2d97
--- /dev/null
+++ b/spec/ruby/library/monitor/mon_initialize_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require 'monitor'
+
+describe "MonitorMixin#mon_initialize" do
+ it "can be called in initialize_copy to get a new Mutex and used with synchronize" do
+ cls = Class.new do
+ include MonitorMixin
+
+ def initialize(*array)
+ mon_initialize
+ @array = array
+ end
+
+ def to_a
+ synchronize { @array.dup }
+ end
+
+ def initialize_copy(other)
+ mon_initialize
+
+ synchronize do
+ @array = other.to_a
+ end
+ end
+ end
+
+ instance = cls.new(1, 2, 3)
+ copy = instance.dup
+ copy.should_not equal(instance)
+ end
+end
diff --git a/spec/ruby/library/monitor/new_cond_spec.rb b/spec/ruby/library/monitor/new_cond_spec.rb
new file mode 100644
index 0000000000..ec25d3f8a2
--- /dev/null
+++ b/spec/ruby/library/monitor/new_cond_spec.rb
@@ -0,0 +1,88 @@
+require_relative '../../spec_helper'
+require 'monitor'
+
+describe "Monitor#new_cond" do
+ it "creates a MonitorMixin::ConditionVariable" do
+ m = Monitor.new
+ c = m.new_cond
+ c.class.should == MonitorMixin::ConditionVariable
+ end
+
+ it 'returns a condition variable which can be waited on by a thread holding the monitor' do
+ m = Monitor.new
+ c = m.new_cond
+
+ 10.times do
+
+ wait_q = Queue.new
+ thread = Thread.new do
+ m.synchronize do
+ wait_q << true
+ c.wait
+ end
+ :done
+ end
+
+ wait_q.pop
+
+ # Synchronize can't happen until the other thread is waiting.
+ m.synchronize { c.signal }
+
+ thread.join
+ thread.value.should == :done
+ end
+ end
+
+ it 'returns a condition variable which can be waited on by a thread holding the monitor inside multiple synchronize blocks' do
+ m = Monitor.new
+ c = m.new_cond
+
+ 10.times do
+
+ wait_q = Queue.new
+ thread = Thread.new do
+ m.synchronize do
+ m.synchronize do
+ wait_q << true
+ c.wait
+ end
+ end
+ :done
+ end
+
+ wait_q.pop
+
+ #No need to wait here as we cannot synchronize until the other thread is waiting.
+ m.synchronize { c.signal }
+
+ thread.join
+ thread.value.should == :done
+ end
+ end
+
+ it 'returns a condition variable which can be signalled by a thread holding the monitor inside multiple synchronize blocks' do
+ m = Monitor.new
+ c = m.new_cond
+
+ 10.times do
+
+ wait_q = Queue.new
+ thread = Thread.new do
+ m.synchronize do
+ wait_q << true
+ c.wait
+ end
+ :done
+ end
+
+ wait_q.pop
+
+ # Synchronize can't happen until the other thread is waiting.
+ m.synchronize { m.synchronize { c.signal } }
+
+ thread.join
+ thread.value.should == :done
+ end
+ end
+
+end
diff --git a/spec/ruby/library/monitor/synchronize_spec.rb b/spec/ruby/library/monitor/synchronize_spec.rb
new file mode 100644
index 0000000000..d78393eb3a
--- /dev/null
+++ b/spec/ruby/library/monitor/synchronize_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require 'monitor'
+
+describe "Monitor#synchronize" do
+ it "unlocks after return, even if it was interrupted by Thread#raise" do
+ exc_class = Class.new(RuntimeError)
+
+ monitor = Monitor.new
+ 10.times do
+ wait_q = Queue.new
+ continue_q = Queue.new
+
+ thread = Thread.new do
+ begin
+ monitor.synchronize do
+ wait_q << true
+ # Do not wait here, we are trying to interrupt the ensure part of #synchronize
+ end
+ continue_q.pop
+ rescue exc_class
+ monitor.should_not.mon_locked?
+ :ok
+ end
+ end
+
+ wait_q.pop
+ thread.raise exc_class, "interrupt"
+ continue_q << true
+ thread.value.should == :ok
+ end
+ end
+
+ it "raises a LocalJumpError if not passed a block" do
+ -> { Monitor.new.synchronize }.should raise_error(LocalJumpError)
+ end
+
+ it "raises a thread error if the monitor is not owned on exiting the block" do
+ monitor = Monitor.new
+ -> { monitor.synchronize { monitor.exit } }.should raise_error(ThreadError)
+ end
+end
diff --git a/spec/ruby/library/monitor/try_enter_spec.rb b/spec/ruby/library/monitor/try_enter_spec.rb
new file mode 100644
index 0000000000..04b878f720
--- /dev/null
+++ b/spec/ruby/library/monitor/try_enter_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require 'monitor'
+
+describe "Monitor#try_enter" do
+ it "will acquire a monitor not held by another thread" do
+ monitor = Monitor.new
+ 10.times do
+
+ thread = Thread.new do
+ val = monitor.try_enter
+ monitor.exit if val
+ val
+ end
+
+ thread.join
+ thread.value.should == true
+ end
+ end
+
+ it "will not acquire a monitor already held by another thread" do
+ monitor = Monitor.new
+ 10.times do
+ monitor.enter
+ begin
+ thread = Thread.new do
+ val = monitor.try_enter
+ monitor.exit if val
+ val
+ end
+
+ thread.join
+ thread.value.should == false
+ ensure
+ monitor.exit
+ end
+ monitor.mon_locked?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/library/net/FTPError_spec.rb b/spec/ruby/library/net/FTPError_spec.rb
new file mode 100644
index 0000000000..84128511db
--- /dev/null
+++ b/spec/ruby/library/net/FTPError_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'net/ftp'
+
+ describe "Net::FTPError" do
+ it "is an Exception" do
+ Net::FTPError.should < Exception
+ end
+ end
+end
diff --git a/spec/ruby/library/net/FTPPermError_spec.rb b/spec/ruby/library/net/FTPPermError_spec.rb
new file mode 100644
index 0000000000..0da35e7d82
--- /dev/null
+++ b/spec/ruby/library/net/FTPPermError_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'net/ftp'
+
+ describe "Net::FTPPermError" do
+ it "is an Exception" do
+ Net::FTPPermError.should < Exception
+ end
+
+ it "is a subclass of Net::FTPError" do
+ Net::FTPPermError.should < Net::FTPError
+ end
+ end
+end
diff --git a/spec/ruby/library/net/FTPProtoError_spec.rb b/spec/ruby/library/net/FTPProtoError_spec.rb
new file mode 100644
index 0000000000..20e30dd2dd
--- /dev/null
+++ b/spec/ruby/library/net/FTPProtoError_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'net/ftp'
+
+ describe "Net::FTPProtoError" do
+ it "is an Exception" do
+ Net::FTPProtoError.should < Exception
+ end
+
+ it "is a subclass of Net::FTPError" do
+ Net::FTPPermError.should < Net::FTPError
+ end
+ end
+end
diff --git a/spec/ruby/library/net/FTPReplyError_spec.rb b/spec/ruby/library/net/FTPReplyError_spec.rb
new file mode 100644
index 0000000000..cc774aabe5
--- /dev/null
+++ b/spec/ruby/library/net/FTPReplyError_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'net/ftp'
+
+ describe "Net::FTPReplyError" do
+ it "is an Exception" do
+ Net::FTPReplyError.should < Exception
+ end
+
+ it "is a subclass of Net::FTPError" do
+ Net::FTPPermError.should < Net::FTPError
+ end
+ end
+end
diff --git a/spec/ruby/library/net/FTPTempError_spec.rb b/spec/ruby/library/net/FTPTempError_spec.rb
new file mode 100644
index 0000000000..e2fbdfd842
--- /dev/null
+++ b/spec/ruby/library/net/FTPTempError_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'net/ftp'
+
+ describe "Net::FTPTempError" do
+ it "is an Exception" do
+ Net::FTPTempError.should < Exception
+ end
+
+ it "is a subclass of Net::FTPError" do
+ Net::FTPPermError.should < Net::FTPError
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/abort_spec.rb b/spec/ruby/library/net/ftp/abort_spec.rb
new file mode 100644
index 0000000000..ebdfed4b16
--- /dev/null
+++ b/spec/ruby/library/net/ftp/abort_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#abort" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the ABOR command to the server" do
+ -> { @ftp.abort }.should_not raise_error
+ end
+
+ it "ignores the response" do
+ @ftp.abort
+ @ftp.last_response.should == "220 Dummy FTP Server ready!\n"
+ end
+
+ it "returns the full response" do
+ @ftp.abort.should == "226 Closing data connection. (ABOR)\n"
+ end
+
+ it "does not raise any error when the response code is 225" do
+ @server.should_receive(:abor).and_respond("225 Data connection open; no transfer in progress.")
+ -> { @ftp.abort }.should_not raise_error
+ end
+
+ it "does not raise any error when the response code is 226" do
+ @server.should_receive(:abor).and_respond("226 Closing data connection.")
+ -> { @ftp.abort }.should_not raise_error
+ end
+
+ it "raises a Net::FTPProtoError when the response code is 500" do
+ @server.should_receive(:abor).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.abort }.should raise_error(Net::FTPProtoError)
+ end
+
+ it "raises a Net::FTPProtoError when the response code is 501" do
+ @server.should_receive(:abor).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.abort }.should raise_error(Net::FTPProtoError)
+ end
+
+ it "raises a Net::FTPProtoError when the response code is 502" do
+ @server.should_receive(:abor).and_respond("502 Command not implemented.")
+ -> { @ftp.abort }.should raise_error(Net::FTPProtoError)
+ end
+
+ it "raises a Net::FTPProtoError when the response code is 421" do
+ @server.should_receive(:abor).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.abort }.should raise_error(Net::FTPProtoError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/acct_spec.rb b/spec/ruby/library/net/ftp/acct_spec.rb
new file mode 100644
index 0000000000..a960ae20a4
--- /dev/null
+++ b/spec/ruby/library/net/ftp/acct_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#acct" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "writes the ACCT command to the server" do
+ @ftp.acct("my_account")
+ @ftp.last_response.should == "230 User 'my_account' logged in, proceed. (ACCT)\n"
+ end
+
+ it "returns nil" do
+ @ftp.acct("my_account").should == nil
+ end
+
+ it "does not raise any error when the response code is 230" do
+ @server.should_receive(:acct).and_respond("230 User logged in, proceed.")
+ -> { @ftp.acct("my_account") }.should_not raise_error
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:acct).and_respond("530 Not logged in.")
+ -> { @ftp.acct("my_account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:acct).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.acct("my_account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:acct).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.acct("my_account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 503" do
+ @server.should_receive(:acct).and_respond("503 Bad sequence of commands.")
+ -> { @ftp.acct("my_account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:acct).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.acct("my_account") }.should raise_error(Net::FTPTempError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/binary_spec.rb b/spec/ruby/library/net/ftp/binary_spec.rb
new file mode 100644
index 0000000000..da7e2d6c14
--- /dev/null
+++ b/spec/ruby/library/net/ftp/binary_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP#binary" do
+ it "returns true when self is in binary mode" do
+ ftp = Net::FTP.new
+ ftp.binary.should be_true
+
+ ftp.binary = false
+ ftp.binary.should be_false
+ end
+ end
+
+ describe "Net::FTP#binary=" do
+ it "sets self to binary mode when passed true" do
+ ftp = Net::FTP.new
+
+ ftp.binary = true
+ ftp.binary.should be_true
+
+ ftp.binary = false
+ ftp.binary.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/chdir_spec.rb b/spec/ruby/library/net/ftp/chdir_spec.rb
new file mode 100644
index 0000000000..741c3c845e
--- /dev/null
+++ b/spec/ruby/library/net/ftp/chdir_spec.rb
@@ -0,0 +1,102 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#chdir" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ describe "when switching to the parent directory" do
+ it "sends the 'CDUP' command to the server" do
+ @ftp.chdir("..")
+ @ftp.last_response.should == "200 Command okay. (CDUP)\n"
+ end
+
+ it "returns nil" do
+ @ftp.chdir("..").should be_nil
+ end
+
+ it "does not raise a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:cdup).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.chdir("..") }.should_not raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:cdup).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.chdir("..") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:cdup).and_respond("502 Command not implemented.")
+ -> { @ftp.chdir("..") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:cdup).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.chdir("..") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:cdup).and_respond("530 Not logged in.")
+ -> { @ftp.chdir("..") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 550" do
+ @server.should_receive(:cdup).and_respond("550 Requested action not taken.")
+ -> { @ftp.chdir("..") }.should raise_error(Net::FTPPermError)
+ end
+ end
+
+ it "writes the 'CWD' command with the passed directory to the socket" do
+ @ftp.chdir("test")
+ @ftp.last_response.should == "200 Command okay. (CWD test)\n"
+ end
+
+ it "returns nil" do
+ @ftp.chdir("test").should be_nil
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:cwd).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.chdir("test") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:cwd).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.chdir("test") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:cwd).and_respond("502 Command not implemented.")
+ -> { @ftp.chdir("test") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:cwd).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.chdir("test") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:cwd).and_respond("530 Not logged in.")
+ -> { @ftp.chdir("test") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 550" do
+ @server.should_receive(:cwd).and_respond("550 Requested action not taken.")
+ -> { @ftp.chdir("test") }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/close_spec.rb b/spec/ruby/library/net/ftp/close_spec.rb
new file mode 100644
index 0000000000..49cdf4dea7
--- /dev/null
+++ b/spec/ruby/library/net/ftp/close_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP#close" do
+ before :each do
+ @socket = mock("Socket")
+ @socket.stub!(:closed?).and_return(false)
+ @socket.stub!(:read_timeout).and_return(60)
+ @socket.stub!(:read_timeout=).and_return(3)
+
+ @ftp = Net::FTP.new
+ @ftp.instance_variable_set(:@sock, @socket)
+ end
+
+ it "closes the socket" do
+ @socket.should_receive(:close)
+ @ftp.close.should be_nil
+ end
+
+ it "does not try to close the socket if it has already been closed" do
+ @socket.should_receive(:closed?).and_return(true)
+ @socket.should_not_receive(:close)
+ @ftp.close.should be_nil
+ end
+
+ it "does not try to close the socket if it is nil" do
+ @ftp.instance_variable_set(:@sock, nil)
+ @ftp.close.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/closed_spec.rb b/spec/ruby/library/net/ftp/closed_spec.rb
new file mode 100644
index 0000000000..a81917090a
--- /dev/null
+++ b/spec/ruby/library/net/ftp/closed_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP#closed?" do
+ before :each do
+ @socket = mock("Socket")
+
+ @ftp = Net::FTP.new
+ @ftp.instance_variable_set(:@sock, @socket)
+ end
+
+ it "returns true when the socket is closed" do
+ @socket.should_receive(:closed?).and_return(true)
+ @ftp.closed?.should be_true
+ end
+
+ it "returns true when the socket is nil" do
+ @ftp.instance_variable_set(:@sock, nil)
+ @ftp.closed?.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/connect_spec.rb b/spec/ruby/library/net/ftp/connect_spec.rb
new file mode 100644
index 0000000000..b45e46c530
--- /dev/null
+++ b/spec/ruby/library/net/ftp/connect_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ # TODO: Add specs for using the SOCKSSocket
+ describe "Net::FTP#connect" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ end
+
+ after :each do
+ @server.connect_message = nil
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "tries to connect to the FTP Server on the given host and port" do
+ -> { @ftp.connect(@server.hostname, @server.server_port) }.should_not raise_error
+ end
+
+ it "returns nil" do
+ @ftp.connect(@server.hostname, @server.server_port).should be_nil
+ end
+
+ it "prints a small debug line when in debug mode" do
+ @ftp.debug_mode = true
+ -> { @ftp.connect(@server.hostname, @server.server_port) }.should output(/#{"connect: "}#{@server.hostname}#{", "}#{@server.server_port}#{"\\nget: 220 Dummy FTP Server ready!"}/)
+ @ftp.debug_mode = false
+ end
+
+ it "does not raise any error when the response code is 220" do
+ @server.connect_message = "220 Dummy FTP Server ready!"
+ -> { @ftp.connect(@server.hostname, @server.server_port) }.should_not raise_error
+ end
+
+ it "raises a Net::FTPReplyError when the response code is 120" do
+ @server.connect_message = "120 Service ready in nnn minutes."
+ -> { @ftp.connect(@server.hostname, @server.server_port) }.should raise_error(Net::FTPReplyError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.connect_message = "421 Service not available, closing control connection."
+ -> { @ftp.connect(@server.hostname, @server.server_port) }.should raise_error(Net::FTPTempError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/debug_mode_spec.rb b/spec/ruby/library/net/ftp/debug_mode_spec.rb
new file mode 100644
index 0000000000..46d207bbea
--- /dev/null
+++ b/spec/ruby/library/net/ftp/debug_mode_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP#debug_mode" do
+ it "returns true when self is in debug mode" do
+ ftp = Net::FTP.new
+ ftp.debug_mode.should be_false
+
+ ftp.debug_mode = true
+ ftp.debug_mode.should be_true
+ end
+ end
+
+ describe "Net::FTP#debug_mode=" do
+ it "sets self into debug mode when passed true" do
+ ftp = Net::FTP.new
+ ftp.debug_mode = true
+ ftp.debug_mode.should be_true
+
+ ftp.debug_mode = false
+ ftp.debug_mode.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/default_passive_spec.rb b/spec/ruby/library/net/ftp/default_passive_spec.rb
new file mode 100644
index 0000000000..9348d3294d
--- /dev/null
+++ b/spec/ruby/library/net/ftp/default_passive_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP#default_passive" do
+ it "is true by default" do
+ ruby_exe(fixture(__FILE__, "default_passive.rb")).should == "true\ntrue\n"
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/delete_spec.rb b/spec/ruby/library/net/ftp/delete_spec.rb
new file mode 100644
index 0000000000..43bfcc1541
--- /dev/null
+++ b/spec/ruby/library/net/ftp/delete_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#delete" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the DELE command with the passed filename to the server" do
+ @ftp.delete("test.file")
+ @ftp.last_response.should == "250 Requested file action okay, completed. (DELE test.file)\n"
+ end
+
+ it "raises a Net::FTPTempError when the response code is 450" do
+ @server.should_receive(:dele).and_respond("450 Requested file action not taken.")
+ -> { @ftp.delete("test.file") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 550" do
+ @server.should_receive(:dele).and_respond("550 Requested action not taken.")
+ -> { @ftp.delete("test.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:dele).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.delete("test.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:dele).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.delete("test.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:dele).and_respond("502 Command not implemented.")
+ -> { @ftp.delete("test.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:dele).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.delete("test.file") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:dele).and_respond("530 Not logged in.")
+ -> { @ftp.delete("test.file") }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/dir_spec.rb b/spec/ruby/library/net/ftp/dir_spec.rb
new file mode 100644
index 0000000000..dce50a5ac5
--- /dev/null
+++ b/spec/ruby/library/net/ftp/dir_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+ require_relative 'shared/list'
+
+ describe "Net::FTP#dir" do
+ it_behaves_like :net_ftp_list, :dir
+ end
+end
diff --git a/spec/ruby/library/net/ftp/fixtures/default_passive.rb b/spec/ruby/library/net/ftp/fixtures/default_passive.rb
new file mode 100644
index 0000000000..b6995d6f34
--- /dev/null
+++ b/spec/ruby/library/net/ftp/fixtures/default_passive.rb
@@ -0,0 +1,3 @@
+require "net/ftp"
+puts Net::FTP.default_passive
+puts Net::FTP.new.passive
diff --git a/spec/ruby/library/net/ftp/fixtures/passive.rb b/spec/ruby/library/net/ftp/fixtures/passive.rb
new file mode 100644
index 0000000000..6b5cde82df
--- /dev/null
+++ b/spec/ruby/library/net/ftp/fixtures/passive.rb
@@ -0,0 +1,2 @@
+require "net/ftp"
+print Net::FTP.new.passive
diff --git a/spec/ruby/library/net/ftp/fixtures/putbinaryfile b/spec/ruby/library/net/ftp/fixtures/putbinaryfile
new file mode 100644
index 0000000000..f3130c6e43
--- /dev/null
+++ b/spec/ruby/library/net/ftp/fixtures/putbinaryfile
@@ -0,0 +1,3 @@
+This is an example file
+which is going to be transmitted
+using #putbinaryfile.
diff --git a/spec/ruby/library/net/ftp/fixtures/puttextfile b/spec/ruby/library/net/ftp/fixtures/puttextfile
new file mode 100644
index 0000000000..b4f3b2b62d
--- /dev/null
+++ b/spec/ruby/library/net/ftp/fixtures/puttextfile
@@ -0,0 +1,3 @@
+This is an example file
+which is going to be transmitted
+using #puttextfile.
diff --git a/spec/ruby/library/net/ftp/fixtures/server.rb b/spec/ruby/library/net/ftp/fixtures/server.rb
new file mode 100644
index 0000000000..ecbed591d5
--- /dev/null
+++ b/spec/ruby/library/net/ftp/fixtures/server.rb
@@ -0,0 +1,277 @@
+module NetFTPSpecs
+ class DummyFTP
+ attr_accessor :connect_message
+ attr_reader :login_user, :login_pass, :login_acct
+
+ # hostname or IP address
+ attr_reader :hostname
+ # port number
+ attr_reader :server_port
+
+ def initialize
+ @hostname = "localhost"
+ @server = TCPServer.new(@hostname, 0)
+ @server_port = @server.addr[1]
+
+ @handlers = {}
+ @commands = []
+ @connect_message = nil
+ end
+
+ def serve_once
+ @thread = Thread.new do
+ @socket = @server.accept
+ @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 1)
+ begin
+ handle_request
+ ensure
+ @socket.close
+ end
+ end
+ end
+
+ def handle_request
+ # Send out the welcome message.
+ response @connect_message || "220 Dummy FTP Server ready!"
+
+ begin
+ while command = @socket.gets
+ command, argument = command.chomp.split(" ", 2)
+
+ if command == "QUIT"
+ self.response("221 OK, bye")
+ break
+ elsif proc_handler = @handlers[command.downcase.to_sym]
+ if argument.nil?
+ proc_handler.call(self)
+ else
+ proc_handler.call(self, argument)
+ end
+ else
+ if argument.nil?
+ self.send(command.downcase.to_sym)
+ else
+ self.send(command.downcase.to_sym, argument)
+ end
+ end
+ end
+ rescue => e
+ self.error_response("Exception: #{e} #{e.backtrace.inspect}")
+ end
+ end
+
+ def error_response(text)
+ self.response("451 #{text}")
+ end
+
+ def response(text)
+ @socket.puts(text) unless @socket.closed?
+ end
+
+ def stop
+ @datasocket.close unless @datasocket.nil? || @datasocket.closed?
+ @server.close
+ @thread.join
+ end
+
+
+ ##
+ def handle(sym, &block)
+ @handlers[sym] = block
+ end
+
+ def should_receive(method)
+ @handler_for = method
+ self
+ end
+
+ def and_respond(text)
+ @handlers[@handler_for] = -> s, *args { s.response(text) }
+ end
+
+ ##
+ # FTP methods
+ ##
+
+ def abor
+ self.response("226 Closing data connection. (ABOR)")
+ end
+
+ def acct(account)
+ @login_acct = account
+ self.response("230 User '#{account}' logged in, proceed. (ACCT)")
+ end
+
+ def cdup
+ self.response("200 Command okay. (CDUP)")
+ end
+
+ def cwd(dir)
+ self.response("200 Command okay. (CWD #{dir})")
+ end
+
+ def dele(file)
+ self.response("250 Requested file action okay, completed. (DELE #{file})")
+ end
+
+ def eprt(arg)
+ _, _, host, port = arg.split("|")
+
+ @datasocket = TCPSocket.new(host, port)
+ self.response("200 port opened")
+ end
+
+ def help(param = :default)
+ if param == :default
+ self.response("211 System status, or system help reply. (HELP)")
+ else
+ self.response("211 System status, or system help reply. (HELP #{param})")
+ end
+ end
+
+ def list(folder)
+ self.response("150 opening ASCII connection for file list")
+ @datasocket.puts("-rw-r--r-- 1 spec staff 507 17 Jul 18:41 last_response_code.rb")
+ @datasocket.puts("-rw-r--r-- 1 spec staff 50 17 Jul 18:41 list.rb")
+ @datasocket.puts("-rw-r--r-- 1 spec staff 48 17 Jul 18:41 pwd.rb")
+ @datasocket.close()
+ self.response("226 transfer complete (LIST #{folder})")
+ end
+
+ def mdtm(filename)
+ self.response("213 19980705132316")
+ end
+
+ def mkd(foldername)
+ self.response(%Q{257 "#{foldername.gsub('"', '""')}" created.})
+ end
+
+ def nlst(folder = nil)
+ self.response("150 opening ASCII connection for file list")
+ @datasocket.puts("last_response_code.rb")
+ @datasocket.puts("list.rb")
+ @datasocket.puts("pwd.rb")
+ @datasocket.close()
+ self.response("226 transfer complete (NLST#{folder ? " #{folder}" : ""})")
+ end
+
+ def noop
+ self.response("200 Command okay. (NOOP)")
+ end
+
+ def pass(password)
+ @login_pass = password
+ self.response("230 User logged in, proceed. (PASS #{password})")
+ end
+
+ def port(arg)
+ nums = arg.split(",")
+
+ if nums[0] == "::1"
+ # IPv6
+ port = nums[1].to_i * 256 + nums[2].to_i
+ host = nums[0]
+ else
+ # IPv4
+ port = nums[4].to_i * 256 + nums[5].to_i
+ host = nums[0..3].join(".")
+ end
+
+ @datasocket = TCPSocket.new(host, port)
+ self.response("200 port opened")
+ end
+
+ def pwd
+ self.response('257 "/some/dir/" - current directory')
+ end
+
+ def retr(file)
+ self.response("125 Data transfer starting")
+ if @restart_at && @restart_at == 20
+ @datasocket.puts("of the file named '#{file}'.")
+ @restart_at = nil
+ else
+ @datasocket.puts("This is the content")
+ @datasocket.puts("of the file named '#{file}'.")
+ end
+ @datasocket.close()
+ self.response("226 Closing data connection. (RETR #{file})")
+ end
+
+ def rest(at_bytes)
+ @restart_at = at_bytes.to_i
+ self.response("350 Requested file action pending further information. (REST)")
+ end
+
+ def rmd(folder)
+ self.response("250 Requested file action okay, completed. (RMD #{folder})")
+ end
+
+ def rnfr(from)
+ @rename_from = from
+ self.response("350 Requested file action pending further information.")
+ end
+
+ def rnto(to)
+ self.response("250 Requested file action okay, completed. (Renamed #{@rename_from} to #{to})")
+ @rename_from = nil
+ end
+
+ def site(param)
+ self.response("200 Command okay. (SITE #{param})")
+ end
+
+ def size(filename)
+ if filename == "binary"
+ self.response("213 24")
+ else
+ self.response("213 1024")
+ end
+ end
+
+ def stat(param = :default)
+ if param == :default
+ self.response("211 System status, or system help reply. (STAT)")
+ else
+ self.response("211 System status, or system help reply. (STAT #{param})")
+ end
+ end
+
+ def stor(file)
+ tmp_file = tmp("#{file}file", false)
+
+ self.response("125 Data transfer starting.")
+
+ mode = @restart_at ? "a" : "w"
+
+ File.open(tmp_file, mode + "b") do |f|
+ loop do
+ data = @datasocket.recv(1024)
+ break if !data || data.empty?
+ f << data
+ end
+ end
+
+ @datasocket.close()
+ self.response("200 OK, Data received. (STOR #{file})")
+ end
+
+ def appe(file)
+ @restart_at = true
+ stor(file)
+ end
+
+ def syst
+ self.response("215 FTP Dummy Server (SYST)")
+ end
+
+ def type(type)
+ self.response("200 TYPE switched to #{type}")
+ end
+
+ def user(name)
+ @login_user = name
+ self.response("230 User logged in, proceed. (USER #{name})")
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/get_spec.rb b/spec/ruby/library/net/ftp/get_spec.rb
new file mode 100644
index 0000000000..892b30061c
--- /dev/null
+++ b/spec/ruby/library/net/ftp/get_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+ require_relative 'shared/gettextfile'
+ require_relative 'shared/getbinaryfile'
+
+ describe "Net::FTP#get (binary mode)" do
+ before :each do
+ @binary_mode = true
+ end
+
+ it_behaves_like :net_ftp_getbinaryfile, :get
+ end
+
+ describe "Net::FTP#get (text mode)" do
+ before :each do
+ @binary_mode = false
+ end
+
+ it_behaves_like :net_ftp_gettextfile, :get
+ end
+end
diff --git a/spec/ruby/library/net/ftp/getbinaryfile_spec.rb b/spec/ruby/library/net/ftp/getbinaryfile_spec.rb
new file mode 100644
index 0000000000..c5abdd67e7
--- /dev/null
+++ b/spec/ruby/library/net/ftp/getbinaryfile_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+ require_relative 'shared/getbinaryfile'
+
+ describe "Net::FTP#getbinaryfile" do
+ it_behaves_like :net_ftp_getbinaryfile, :getbinaryfile
+ end
+end
diff --git a/spec/ruby/library/net/ftp/getdir_spec.rb b/spec/ruby/library/net/ftp/getdir_spec.rb
new file mode 100644
index 0000000000..8f6fca5bfb
--- /dev/null
+++ b/spec/ruby/library/net/ftp/getdir_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'shared/pwd'
+
+ describe "Net::FTP#getdir" do
+ it_behaves_like :net_ftp_pwd, :getdir
+ end
+end
diff --git a/spec/ruby/library/net/ftp/gettextfile_spec.rb b/spec/ruby/library/net/ftp/gettextfile_spec.rb
new file mode 100644
index 0000000000..e272ae73b1
--- /dev/null
+++ b/spec/ruby/library/net/ftp/gettextfile_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+ require_relative 'shared/gettextfile'
+
+ describe "Net::FTP#gettextfile" do
+ it_behaves_like :net_ftp_gettextfile, :gettextfile
+ end
+end
diff --git a/spec/ruby/library/net/ftp/help_spec.rb b/spec/ruby/library/net/ftp/help_spec.rb
new file mode 100644
index 0000000000..9b15f42ede
--- /dev/null
+++ b/spec/ruby/library/net/ftp/help_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#help" do
+ def with_connection
+ yield
+ end
+
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "writes the HELP command to the server" do
+ @ftp.help
+ @ftp.last_response.should == "211 System status, or system help reply. (HELP)\n"
+ end
+
+ it "returns the server's response" do
+ @ftp.help.should == "211 System status, or system help reply. (HELP)\n"
+ end
+
+ it "writes the HELP command with an optional parameter to the socket" do
+ @ftp.help("some parameter").should == "211 System status, or system help reply. (HELP some parameter)\n"
+ end
+
+ it "does not raise any error when the response code is 211" do
+ @server.should_receive(:help).and_respond("211 System status, or system help reply.")
+ -> { @ftp.help }.should_not raise_error
+ end
+
+ it "does not raise any error when the response code is 214" do
+ @server.should_receive(:help).and_respond("214 Help message.")
+ -> { @ftp.help }.should_not raise_error
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:help).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.help }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:help).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.help }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:help).and_respond("502 Command not implemented.")
+ -> { @ftp.help }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:help).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.help }.should raise_error(Net::FTPTempError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/initialize_spec.rb b/spec/ruby/library/net/ftp/initialize_spec.rb
new file mode 100644
index 0000000000..80f71a9161
--- /dev/null
+++ b/spec/ruby/library/net/ftp/initialize_spec.rb
@@ -0,0 +1,408 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP#initialize" do
+ before :each do
+ @ftp = Net::FTP.allocate
+ @ftp.stub!(:connect)
+ @port_args = []
+ @port_args << 21
+ end
+
+ it "is private" do
+ Net::FTP.should have_private_instance_method(:initialize)
+ end
+
+ it "sets self into binary mode" do
+ @ftp.binary.should be_nil
+ @ftp.send(:initialize)
+ @ftp.binary.should be_true
+ end
+
+ it "sets self into active mode" do
+ @ftp.passive.should be_nil
+ @ftp.send(:initialize)
+ @ftp.passive.should be_false
+ end
+
+ it "sets self into non-debug mode" do
+ @ftp.debug_mode.should be_nil
+ @ftp.send(:initialize)
+ @ftp.debug_mode.should be_false
+ end
+
+ it "sets self to not resume file uploads/downloads" do
+ @ftp.resume.should be_nil
+ @ftp.send(:initialize)
+ @ftp.resume.should be_false
+ end
+
+ describe "when passed no arguments" do
+ it "does not try to connect" do
+ @ftp.should_not_receive(:connect)
+ @ftp.send(:initialize)
+ end
+ end
+
+ describe "when passed host" do
+ it "tries to connect to the passed host" do
+ @ftp.should_receive(:connect).with("localhost", *@port_args)
+ @ftp.send(:initialize, "localhost")
+ end
+ end
+
+ describe "when passed host, user" do
+ it "tries to connect to the passed host" do
+ @ftp.should_receive(:connect).with("localhost", *@port_args)
+ @ftp.send(:initialize, "localhost")
+ end
+
+ it "tries to login with the passed username" do
+ @ftp.should_receive(:login).with("rubyspec", nil, nil)
+ @ftp.send(:initialize, "localhost", "rubyspec")
+ end
+ end
+
+ describe "when passed host, user, password" do
+ it "tries to connect to the passed host" do
+ @ftp.should_receive(:connect).with("localhost", *@port_args)
+ @ftp.send(:initialize, "localhost")
+ end
+
+ it "tries to login with the passed username and password" do
+ @ftp.should_receive(:login).with("rubyspec", "rocks", nil)
+ @ftp.send(:initialize, "localhost", "rubyspec", "rocks")
+ end
+ end
+
+ describe "when passed host, user" do
+ it "tries to connect to the passed host" do
+ @ftp.should_receive(:connect).with("localhost", *@port_args)
+ @ftp.send(:initialize, "localhost")
+ end
+
+ it "tries to login with the passed username, password and account" do
+ @ftp.should_receive(:login).with("rubyspec", "rocks", "account")
+ @ftp.send(:initialize, "localhost", "rubyspec", "rocks", "account")
+ end
+ end
+
+ before :each do
+ @ftp.stub!(:login)
+ end
+
+ describe 'when the host' do
+ describe 'is set' do
+ describe 'and port option' do
+ describe 'is set' do
+ it 'tries to connect to the host on the specified port' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ port: 8080 })
+ @ftp.should_receive(:connect).with('localhost', 8080)
+
+ @ftp.send(:initialize, 'localhost', options)
+ end
+ end
+
+ describe 'is not set' do
+ it 'tries to connect to the host without a port' do
+ @ftp.should_receive(:connect).with("localhost", *@port_args)
+
+ @ftp.send(:initialize, 'localhost')
+ end
+ end
+ end
+
+ describe 'when the username option' do
+ describe 'is set' do
+ describe 'and the password option' do
+ describe 'is set' do
+ describe 'and the account option' do
+ describe 'is set' do
+ it 'tries to log in with the supplied parameters' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ username: 'a', password: 'topsecret', account: 'b' })
+ @ftp.should_receive(:login).with('a', 'topsecret', 'b')
+
+ @ftp.send(:initialize, 'localhost', options)
+ end
+ end
+
+ describe 'is unset' do
+ it 'tries to log in with the supplied parameters' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ username: 'a', password: 'topsecret' })
+ @ftp.should_receive(:login).with('a', 'topsecret', nil)
+
+ @ftp.send(:initialize, 'localhost', options)
+ end
+ end
+ end
+ end
+
+ describe 'is unset' do
+ describe 'and the account option' do
+ describe 'is set' do
+ it 'tries to log in with the supplied parameters' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ username: 'a', account: 'b' })
+ @ftp.should_receive(:login).with('a', nil, 'b')
+
+ @ftp.send(:initialize, 'localhost', options)
+ end
+ end
+
+ describe 'is unset' do
+ it 'tries to log in with the supplied parameters' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ username: 'a'})
+ @ftp.should_receive(:login).with('a', nil, nil)
+
+ @ftp.send(:initialize, 'localhost', options)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe 'is not set' do
+ it 'does not try to log in' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({})
+ @ftp.should_not_receive(:login)
+
+ @ftp.send(:initialize, 'localhost', options)
+ end
+ end
+ end
+ end
+
+ describe 'is unset' do
+ it 'does not try to connect' do
+ @ftp.should_not_receive(:connect)
+
+ @ftp.send(:initialize)
+ end
+
+ it 'does not try to log in' do
+ @ftp.should_not_receive(:login)
+
+ @ftp.send(:initialize)
+ end
+ end
+ end
+
+ describe 'when the passive option' do
+ describe 'is set' do
+ describe 'to true' do
+ it 'sets passive to true' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ passive: true })
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.passive.should == true
+ end
+ end
+
+ describe 'to false' do
+ it 'sets passive to false' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ passive: false })
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.passive.should == false
+ end
+ end
+ end
+
+ describe 'is unset' do
+ it 'sets passive to false' do
+ @ftp.send(:initialize)
+ @ftp.passive.should == false
+ end
+ end
+ end
+
+ describe 'when the debug_mode option' do
+ describe 'is set' do
+ describe 'to true' do
+ it 'sets debug_mode to true' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ debug_mode: true })
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.debug_mode.should == true
+ end
+ end
+
+ describe 'to false' do
+ it 'sets debug_mode to false' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ debug_mode: false })
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.debug_mode.should == false
+ end
+ end
+ end
+
+ describe 'is unset' do
+ it 'sets debug_mode to false' do
+ @ftp.send(:initialize)
+ @ftp.debug_mode.should == false
+ end
+ end
+ end
+
+ describe 'when the open_timeout option' do
+ describe 'is set' do
+ it 'sets open_timeout to the specified value' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ open_timeout: 42 })
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.open_timeout.should == 42
+ end
+ end
+
+ describe 'is not set' do
+ it 'sets open_timeout to nil' do
+ @ftp.send(:initialize)
+ @ftp.open_timeout.should == nil
+ end
+ end
+ end
+
+ describe 'when the read_timeout option' do
+ describe 'is set' do
+ it 'sets read_timeout to the specified value' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ read_timeout: 100 })
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.read_timeout.should == 100
+ end
+ end
+
+ describe 'is not set' do
+ it 'sets read_timeout to the default value' do
+ @ftp.send(:initialize)
+ @ftp.read_timeout.should == 60
+ end
+ end
+ end
+
+ describe 'when the ssl_handshake_timeout option' do
+ describe 'is set' do
+ it 'sets ssl_handshake_timeout to the specified value' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ ssl_handshake_timeout: 23 })
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.ssl_handshake_timeout.should == 23
+ end
+ end
+
+ describe 'is not set' do
+ it 'sets ssl_handshake_timeout to nil' do
+ @ftp.send(:initialize)
+ @ftp.ssl_handshake_timeout.should == nil
+ end
+ end
+ end
+
+ describe 'when the ssl option' do
+ describe 'is set' do
+ describe "and the ssl option's value is true" do
+ it 'initializes ssl_context to a blank SSLContext object' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ ssl: true })
+
+ ssl_context = OpenSSL::SSL::SSLContext.allocate
+ ssl_context.stub!(:set_params)
+
+ OpenSSL::SSL::SSLContext.should_receive(:new).and_return(ssl_context)
+ ssl_context.should_receive(:set_params).with({})
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.instance_variable_get(:@ssl_context).should == ssl_context
+ end
+ end
+
+ describe "and the ssl option's value is a hash" do
+ it 'initializes ssl_context to a configured SSLContext object' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ ssl: {key: 'value'} })
+
+ ssl_context = OpenSSL::SSL::SSLContext.allocate
+ ssl_context.stub!(:set_params)
+
+ OpenSSL::SSL::SSLContext.should_receive(:new).and_return(ssl_context)
+ ssl_context.should_receive(:set_params).with({key: 'value'})
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.instance_variable_get(:@ssl_context).should == ssl_context
+ end
+ end
+
+ describe 'and private_data_connection' do
+ describe 'is set' do
+ it 'sets private_data_connection to that value' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ ssl: true, private_data_connection: 'true' })
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.instance_variable_get(:@private_data_connection).should == 'true'
+ end
+ end
+
+ describe 'is not set' do
+ it 'sets private_data_connection to nil' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ ssl: true })
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.instance_variable_get(:@private_data_connection).should == true
+ end
+ end
+ end
+ end
+
+ describe 'is not set' do
+ it 'sets ssl_context to nil' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({})
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.instance_variable_get(:@ssl_context).should == nil
+ end
+
+ describe 'private_data_connection' do
+ describe 'is set' do
+ it 'raises an ArgumentError' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({ private_data_connection: true })
+
+ -> {
+ @ftp.send(:initialize, nil, options)
+ }.should raise_error(ArgumentError, /private_data_connection can be set to true only when ssl is enabled/)
+ end
+ end
+
+ describe 'is not set' do
+ it 'sets private_data_connection to false' do
+ options = mock('ftp initialize options')
+ options.should_receive(:to_hash).and_return({})
+
+ @ftp.send(:initialize, nil, options)
+ @ftp.instance_variable_get(:@private_data_connection).should == false
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/last_response_code_spec.rb b/spec/ruby/library/net/ftp/last_response_code_spec.rb
new file mode 100644
index 0000000000..86f2b9a695
--- /dev/null
+++ b/spec/ruby/library/net/ftp/last_response_code_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'shared/last_response_code'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#last_response_code" do
+ it_behaves_like :net_ftp_last_response_code, :last_response_code
+ end
+end
diff --git a/spec/ruby/library/net/ftp/last_response_spec.rb b/spec/ruby/library/net/ftp/last_response_spec.rb
new file mode 100644
index 0000000000..1d29b9b73f
--- /dev/null
+++ b/spec/ruby/library/net/ftp/last_response_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#last_response" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "returns the last response" do
+ @ftp.last_response.should == "220 Dummy FTP Server ready!\n"
+ @ftp.help
+ @ftp.last_response.should == "211 System status, or system help reply. (HELP)\n"
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/lastresp_spec.rb b/spec/ruby/library/net/ftp/lastresp_spec.rb
new file mode 100644
index 0000000000..9d26efb8f8
--- /dev/null
+++ b/spec/ruby/library/net/ftp/lastresp_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'shared/last_response_code'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#lastresp" do
+ it_behaves_like :net_ftp_last_response_code, :lastresp
+ end
+end
diff --git a/spec/ruby/library/net/ftp/list_spec.rb b/spec/ruby/library/net/ftp/list_spec.rb
new file mode 100644
index 0000000000..6cffafeb4f
--- /dev/null
+++ b/spec/ruby/library/net/ftp/list_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+ require_relative 'shared/list'
+
+ describe "Net::FTP#list" do
+ it_behaves_like :net_ftp_list, :list
+ end
+end
diff --git a/spec/ruby/library/net/ftp/login_spec.rb b/spec/ruby/library/net/ftp/login_spec.rb
new file mode 100644
index 0000000000..981b439082
--- /dev/null
+++ b/spec/ruby/library/net/ftp/login_spec.rb
@@ -0,0 +1,198 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#login" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ describe "when passed no arguments" do
+ it "sends the USER command with 'anonymous' as name to the server" do
+ @ftp.login
+ @server.login_user.should == "anonymous"
+ end
+
+ it "sends 'anonymous@' as a password when required" do
+ @server.should_receive(:user).and_respond("331 User name okay, need password.")
+ @ftp.login
+ @server.login_pass.should == "anonymous@"
+ end
+
+ it "raises a Net::FTPReplyError when the server requests an account" do
+ @server.should_receive(:user).and_respond("331 User name okay, need password.")
+ @server.should_receive(:pass).and_respond("332 Need account for login.")
+ -> { @ftp.login }.should raise_error(Net::FTPReplyError)
+ end
+ end
+
+ describe "when passed name" do
+ it "sends the USER command with the passed name to the server" do
+ @ftp.login("rubyspec")
+ @server.login_user.should == "rubyspec"
+ end
+
+ it "raises a Net::FTPReplyError when the server requests a password, but none was given" do
+ @server.should_receive(:user).and_respond("331 User name okay, need password.")
+ -> { @ftp.login("rubyspec") }.should raise_error(Net::FTPReplyError)
+ end
+
+ it "raises a Net::FTPReplyError when the server requests an account, but none was given" do
+ @server.should_receive(:user).and_respond("331 User name okay, need password.")
+ @server.should_receive(:pass).and_respond("332 Need account for login.")
+ -> { @ftp.login("rubyspec") }.should raise_error(Net::FTPReplyError)
+ end
+ end
+
+ describe "when passed name, password" do
+ it "sends the USER command with the passed name to the server" do
+ @ftp.login("rubyspec", "rocks")
+ @server.login_user.should == "rubyspec"
+ end
+
+ it "sends the passed password when required" do
+ @server.should_receive(:user).and_respond("331 User name okay, need password.")
+ @ftp.login("rubyspec", "rocks")
+ @server.login_pass.should == "rocks"
+ end
+
+ it "raises a Net::FTPReplyError when the server requests an account" do
+ @server.should_receive(:user).and_respond("331 User name okay, need password.")
+ @server.should_receive(:pass).and_respond("332 Need account for login.")
+ -> { @ftp.login("rubyspec", "rocks") }.should raise_error(Net::FTPReplyError)
+ end
+ end
+
+ describe "when passed name, password, account" do
+ it "sends the USER command with the passed name to the server" do
+ @ftp.login("rubyspec", "rocks", "account")
+ @server.login_user.should == "rubyspec"
+ end
+
+ it "sends the passed password when required" do
+ @server.should_receive(:user).and_respond("331 User name okay, need password.")
+ @ftp.login("rubyspec", "rocks", "account")
+ @server.login_pass.should == "rocks"
+ end
+
+ it "sends the passed account when required" do
+ @server.should_receive(:user).and_respond("331 User name okay, need password.")
+ @server.should_receive(:pass).and_respond("332 Need account for login.")
+ @ftp.login("rubyspec", "rocks", "account")
+ @server.login_acct.should == "account"
+ end
+ end
+
+ describe "when the USER command fails" do
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:user).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:user).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:user).and_respond("502 Command not implemented.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:user).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:user).and_respond("530 Not logged in.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+ end
+
+ describe "when the PASS command fails" do
+ before :each do
+ @server.should_receive(:user).and_respond("331 User name okay, need password.")
+ end
+
+ it "does not raise an Error when the response code is 202" do
+ @server.should_receive(:pass).and_respond("202 Command not implemented, superfluous at this site.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should_not raise_error
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:pass).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:pass).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:pass).and_respond("502 Command not implemented.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:pass).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:pass).and_respond("530 Not logged in.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+ end
+
+ describe "when the ACCT command fails" do
+ before :each do
+ @server.should_receive(:user).and_respond("331 User name okay, need password.")
+ @server.should_receive(:pass).and_respond("332 Need account for login.")
+ end
+
+ it "does not raise an Error when the response code is 202" do
+ @server.should_receive(:acct).and_respond("202 Command not implemented, superfluous at this site.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should_not raise_error
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:acct).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:acct).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:acct).and_respond("502 Command not implemented.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:acct).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:acct).and_respond("530 Not logged in.")
+ -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/ls_spec.rb b/spec/ruby/library/net/ftp/ls_spec.rb
new file mode 100644
index 0000000000..f262515865
--- /dev/null
+++ b/spec/ruby/library/net/ftp/ls_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+ require_relative 'shared/list'
+
+ describe "Net::FTP#ls" do
+ it_behaves_like :net_ftp_list, :ls
+ end
+end
diff --git a/spec/ruby/library/net/ftp/mdtm_spec.rb b/spec/ruby/library/net/ftp/mdtm_spec.rb
new file mode 100644
index 0000000000..ea55533c43
--- /dev/null
+++ b/spec/ruby/library/net/ftp/mdtm_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#mdtm" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the MDTM with the passed filename command to the server" do
+ @ftp.mdtm("test.file")
+ @ftp.last_response.should == "213 19980705132316\n"
+ end
+
+ it "returns the last modification time of the passed file" do
+ @ftp.mdtm("test.file").should == "19980705132316"
+ end
+
+ it "raises a Net::FTPPermError when the response code is 550" do
+ @server.should_receive(:mdtm).and_respond("550 Requested action not taken.")
+ -> { @ftp.mdtm("test.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:mdtm).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.mdtm("test.file") }.should raise_error(Net::FTPTempError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/mkdir_spec.rb b/spec/ruby/library/net/ftp/mkdir_spec.rb
new file mode 100644
index 0000000000..2cb437a076
--- /dev/null
+++ b/spec/ruby/library/net/ftp/mkdir_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#mkdir" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the MKD command with the passed pathname to the server" do
+ @ftp.mkdir("test.folder")
+ @ftp.last_response.should == %{257 "test.folder" created.\n}
+ end
+
+ it "returns the path to the newly created directory" do
+ @ftp.mkdir("test.folder").should == "test.folder"
+ @ftp.mkdir("/absolute/path/to/test.folder").should == "/absolute/path/to/test.folder"
+ @ftp.mkdir("relative/path/to/test.folder").should == "relative/path/to/test.folder"
+ @ftp.mkdir('/usr/dm/foo"bar').should == '/usr/dm/foo"bar'
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:mkd).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:mkd).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:mkd).and_respond("502 Command not implemented.")
+ -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:mkd).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:mkd).and_respond("530 Not logged in.")
+ -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 550" do
+ @server.should_receive(:mkd).and_respond("550 Requested action not taken.")
+ -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/mtime_spec.rb b/spec/ruby/library/net/ftp/mtime_spec.rb
new file mode 100644
index 0000000000..7265667a52
--- /dev/null
+++ b/spec/ruby/library/net/ftp/mtime_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#mtime" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the MDTM with the passed filename command to the server" do
+ @ftp.mtime("test.file")
+ @ftp.last_response.should == "213 19980705132316\n"
+ end
+
+ describe "when passed filename" do
+ it "returns the last modification time of the passed file as a Time object in the local time" do
+ @ftp.mtime("test.file").should == Time.gm("1998", "07", "05", "13", "23", "16")
+ end
+ end
+
+ describe "when passed filename, local_time" do
+ it "returns the last modification time as a Time object in UTC when local_time is true" do
+ @ftp.mtime("test.file", true).should == Time.local("1998", "07", "05", "13", "23", "16")
+ end
+
+ it "returns the last modification time as a Time object in the local time when local_time is false" do
+ @ftp.mtime("test.file", false).should == Time.gm("1998", "07", "05", "13", "23", "16")
+ end
+ end
+
+ it "raises a Net::FTPPermError when the response code is 550" do
+ @server.should_receive(:mdtm).and_respond("550 Requested action not taken.")
+ -> { @ftp.mtime("test.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:mdtm).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.mtime("test.file") }.should raise_error(Net::FTPTempError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/nlst_spec.rb b/spec/ruby/library/net/ftp/nlst_spec.rb
new file mode 100644
index 0000000000..0de84b3a76
--- /dev/null
+++ b/spec/ruby/library/net/ftp/nlst_spec.rb
@@ -0,0 +1,95 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#nlst" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.passive = false
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ describe "when passed no arguments" do
+ it "returns an Array containing a list of files in the current dir" do
+ @ftp.nlst.should == ["last_response_code.rb", "list.rb", "pwd.rb"]
+ @ftp.last_response.should == "226 transfer complete (NLST)\n"
+ end
+ end
+
+ describe "when passed dir" do
+ it "returns an Array containing a list of files in the passed dir" do
+ @ftp.nlst("test.folder").should == ["last_response_code.rb", "list.rb", "pwd.rb"]
+ @ftp.last_response.should == "226 transfer complete (NLST test.folder)\n"
+ end
+ end
+
+ describe "when the NLST command fails" do
+ it "raises a Net::FTPTempError when the response code is 450" do
+ @server.should_receive(:nlst).and_respond("450 Requested file action not taken..")
+ -> { @ftp.nlst }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:nlst).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.nlst }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:nlst).and_respond("501 Syntax error, command unrecognized.")
+ -> { @ftp.nlst }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:nlst).and_respond("502 Command not implemented.")
+ -> { @ftp.nlst }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:nlst).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.nlst }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:nlst).and_respond("530 Not logged in.")
+ -> { @ftp.nlst }.should raise_error(Net::FTPPermError)
+ end
+ end
+
+ describe "when opening the data port fails" do
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.")
+ @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.nlst }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.")
+ @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.nlst }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.")
+ @server.should_receive(:port).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.nlst }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:eprt).and_respond("530 Not logged in.")
+ @server.should_receive(:port).and_respond("530 Not logged in.")
+ -> { @ftp.nlst }.should raise_error(Net::FTPPermError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/noop_spec.rb b/spec/ruby/library/net/ftp/noop_spec.rb
new file mode 100644
index 0000000000..71011d4af7
--- /dev/null
+++ b/spec/ruby/library/net/ftp/noop_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#noop" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the NOOP command to the server" do
+ @ftp.noop
+ @ftp.last_response.should == "200 Command okay. (NOOP)\n"
+ end
+
+ it "returns nil" do
+ @ftp.noop.should be_nil
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:noop).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.noop }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:noop).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.noop }.should raise_error(Net::FTPTempError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/open_spec.rb b/spec/ruby/library/net/ftp/open_spec.rb
new file mode 100644
index 0000000000..89187b9802
--- /dev/null
+++ b/spec/ruby/library/net/ftp/open_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP.open" do
+ before :each do
+ @ftp = mock("Net::FTP instance")
+ Net::FTP.stub!(:new).and_return(@ftp)
+ end
+
+ describe "when passed no block" do
+ it "returns a new Net::FTP instance" do
+ Net::FTP.open("localhost").should equal(@ftp)
+ end
+
+ it "passes the passed arguments down to Net::FTP.new" do
+ Net::FTP.should_receive(:new).with("localhost", "user", "password", "account")
+ Net::FTP.open("localhost", "user", "password", "account")
+ end
+ end
+
+ describe "when passed a block" do
+ before :each do
+ @ftp.stub!(:close)
+ end
+
+ it "yields a new Net::FTP instance to the passed block" do
+ yielded = false
+ Net::FTP.open("localhost") do |ftp|
+ yielded = true
+ ftp.should equal(@ftp)
+ end
+ yielded.should be_true
+ end
+
+ it "closes the Net::FTP instance after yielding" do
+ Net::FTP.open("localhost") do |ftp|
+ ftp.should_receive(:close)
+ end
+ end
+
+ it "closes the Net::FTP instance even if an exception is raised while yielding" do
+ begin
+ Net::FTP.open("localhost") do |ftp|
+ ftp.should_receive(:close)
+ raise ArgumentError, "some exception"
+ end
+ rescue ArgumentError
+ end
+ end
+
+ it "returns the block's return value" do
+ Net::FTP.open("localhost") { :test }.should == :test
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/passive_spec.rb b/spec/ruby/library/net/ftp/passive_spec.rb
new file mode 100644
index 0000000000..f9c34efb7d
--- /dev/null
+++ b/spec/ruby/library/net/ftp/passive_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP#passive" do
+ it "returns true when self is in passive mode" do
+ ftp = Net::FTP.new
+ ftp.passive.should be_false
+
+ ftp.passive = true
+ ftp.passive.should be_true
+ end
+
+ it "is the value of Net::FTP.default_value by default" do
+ ruby_exe(fixture(__FILE__, "passive.rb")).should == "true"
+ end
+ end
+
+ describe "Net::FTP#passive=" do
+ it "sets self to passive mode when passed true" do
+ ftp = Net::FTP.new
+
+ ftp.passive = true
+ ftp.passive.should be_true
+
+ ftp.passive = false
+ ftp.passive.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/put_spec.rb b/spec/ruby/library/net/ftp/put_spec.rb
new file mode 100644
index 0000000000..36ba6c1963
--- /dev/null
+++ b/spec/ruby/library/net/ftp/put_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+ require_relative 'shared/puttextfile'
+ require_relative 'shared/putbinaryfile'
+
+ describe "Net::FTP#put (binary mode)" do
+ before :each do
+ @binary_mode = true
+ end
+
+ it_behaves_like :net_ftp_putbinaryfile, :put
+ end
+
+ describe "Net::FTP#put (text mode)" do
+ before :each do
+ @binary_mode = false
+ end
+
+ it_behaves_like :net_ftp_puttextfile, :put
+ end
+end
diff --git a/spec/ruby/library/net/ftp/putbinaryfile_spec.rb b/spec/ruby/library/net/ftp/putbinaryfile_spec.rb
new file mode 100644
index 0000000000..6ced5246fe
--- /dev/null
+++ b/spec/ruby/library/net/ftp/putbinaryfile_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+ require_relative 'shared/putbinaryfile'
+
+ describe "Net::FTP#putbinaryfile" do
+ it_behaves_like :net_ftp_putbinaryfile, :putbinaryfile
+ end
+end
diff --git a/spec/ruby/library/net/ftp/puttextfile_spec.rb b/spec/ruby/library/net/ftp/puttextfile_spec.rb
new file mode 100644
index 0000000000..0cab6bd3c3
--- /dev/null
+++ b/spec/ruby/library/net/ftp/puttextfile_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+ require_relative 'shared/puttextfile'
+
+ describe "Net::FTP#puttextfile" do
+ it_behaves_like :net_ftp_puttextfile, :puttextfile
+ end
+end
diff --git a/spec/ruby/library/net/ftp/pwd_spec.rb b/spec/ruby/library/net/ftp/pwd_spec.rb
new file mode 100644
index 0000000000..856ff5ff9b
--- /dev/null
+++ b/spec/ruby/library/net/ftp/pwd_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#pwd" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the PWD command to the server" do
+ @ftp.pwd
+ @ftp.last_response.should == "257 \"/some/dir/\" - current directory\n"
+ end
+
+ it "returns the current directory" do
+ @ftp.pwd.should == "/some/dir/"
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:pwd).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.pwd }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:pwd).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.pwd }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:pwd).and_respond("502 Command not implemented.")
+ -> { @ftp.pwd }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:pwd).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.pwd }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 550" do
+ @server.should_receive(:pwd).and_respond("550 Requested action not taken.")
+ -> { @ftp.pwd }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/quit_spec.rb b/spec/ruby/library/net/ftp/quit_spec.rb
new file mode 100644
index 0000000000..12b9fd3cee
--- /dev/null
+++ b/spec/ruby/library/net/ftp/quit_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#quit" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the QUIT command to the server" do
+ @ftp.quit
+ @ftp.last_response.should == "221 OK, bye\n"
+ end
+
+ it "does not close the socket automatically" do
+ @ftp.quit
+ @ftp.closed?.should be_false
+ end
+
+ it "returns nil" do
+ @ftp.quit.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/rename_spec.rb b/spec/ruby/library/net/ftp/rename_spec.rb
new file mode 100644
index 0000000000..aa7c1360b5
--- /dev/null
+++ b/spec/ruby/library/net/ftp/rename_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#rename" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ describe "when passed from_name, to_name" do
+ it "sends the RNFR command with the passed from_name and the RNTO command with the passed to_name to the server" do
+ @ftp.rename("from.file", "to.file")
+ @ftp.last_response.should == "250 Requested file action okay, completed. (Renamed from.file to to.file)\n"
+ end
+
+ it "returns something" do
+ @ftp.rename("from.file", "to.file").should be_nil
+ end
+ end
+
+ describe "when the RNFR command fails" do
+ it "raises a Net::FTPTempError when the response code is 450" do
+ @server.should_receive(:rnfr).and_respond("450 Requested file action not taken.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 550" do
+ @server.should_receive(:rnfr).and_respond("550 Requested action not taken.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:rnfr).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:rnfr).and_respond("502 Command not implemented.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:rnfr).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:rnfr).and_respond("530 Not logged in.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError)
+ end
+ end
+
+ describe "when the RNTO command fails" do
+ it "raises a Net::FTPPermError when the response code is 532" do
+ @server.should_receive(:rnfr).and_respond("532 Need account for storing files.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 553" do
+ @server.should_receive(:rnto).and_respond("553 Requested action not taken.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:rnto).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:rnto).and_respond("502 Command not implemented.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:rnto).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:rnto).and_respond("530 Not logged in.")
+ -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/resume_spec.rb b/spec/ruby/library/net/ftp/resume_spec.rb
new file mode 100644
index 0000000000..1b575c29f1
--- /dev/null
+++ b/spec/ruby/library/net/ftp/resume_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP#resume" do
+ it "returns true when self is set to resume uploads/downloads" do
+ ftp = Net::FTP.new
+ ftp.resume.should be_false
+
+ ftp.resume = true
+ ftp.resume.should be_true
+ end
+ end
+
+ describe "Net::FTP#resume=" do
+ it "sets self to resume uploads/downloads when set to true" do
+ ftp = Net::FTP.new
+ ftp.resume = true
+ ftp.resume.should be_true
+
+ ftp.resume = false
+ ftp.resume.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/retrbinary_spec.rb b/spec/ruby/library/net/ftp/retrbinary_spec.rb
new file mode 100644
index 0000000000..1f89f0d454
--- /dev/null
+++ b/spec/ruby/library/net/ftp/retrbinary_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#retrbinary" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the passed command to the server" do
+ @ftp.retrbinary("RETR test", 4096) {}
+ @ftp.last_response.should == "226 Closing data connection. (RETR test)\n"
+ end
+
+ it "yields the received content as binary blocks of the passed size" do
+ res = []
+ @ftp.retrbinary("RETR test", 10) { |bin| res << bin }
+ res.should == [ "This is th", "e content\n", "of the fil", "e named 't", "est'.\n" ]
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/retrlines_spec.rb b/spec/ruby/library/net/ftp/retrlines_spec.rb
new file mode 100644
index 0000000000..f26b008680
--- /dev/null
+++ b/spec/ruby/library/net/ftp/retrlines_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#retrlines" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the passed command over the socket" do
+ @ftp.retrlines("LIST test.dir") {}
+ @ftp.last_response.should == "226 transfer complete (LIST test.dir)\n"
+ end
+
+ it "yields each received line to the passed block" do
+ res = []
+ @ftp.retrlines("LIST test.dir") { |x| res << x }
+ res.should == [
+ "-rw-r--r-- 1 spec staff 507 17 Jul 18:41 last_response_code.rb",
+ "-rw-r--r-- 1 spec staff 50 17 Jul 18:41 list.rb",
+ "-rw-r--r-- 1 spec staff 48 17 Jul 18:41 pwd.rb"
+ ]
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/return_code_spec.rb b/spec/ruby/library/net/ftp/return_code_spec.rb
new file mode 100644
index 0000000000..67fc9d3b19
--- /dev/null
+++ b/spec/ruby/library/net/ftp/return_code_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP#return_code" do
+ before :each do
+ @ftp = Net::FTP.new
+ end
+
+ it "outputs a warning and returns a newline" do
+ -> do
+ @ftp.return_code.should == "\n"
+ end.should complain(/warning: Net::FTP#return_code is obsolete and do nothing/)
+ end
+ end
+
+ describe "Net::FTP#return_code=" do
+ before :each do
+ @ftp = Net::FTP.new
+ end
+
+ it "outputs a warning" do
+ -> { @ftp.return_code = 123 }.should complain(/warning: Net::FTP#return_code= is obsolete and do nothing/)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/rmdir_spec.rb b/spec/ruby/library/net/ftp/rmdir_spec.rb
new file mode 100644
index 0000000000..5b9586c6f0
--- /dev/null
+++ b/spec/ruby/library/net/ftp/rmdir_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#rmdir" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the RMD command with the passed pathname to the server" do
+ @ftp.rmdir("test.folder")
+ @ftp.last_response.should == "250 Requested file action okay, completed. (RMD test.folder)\n"
+ end
+
+ it "returns nil" do
+ @ftp.rmdir("test.folder").should be_nil
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:rmd).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:rmd).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:rmd).and_respond("502 Command not implemented.")
+ -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:rmd).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:rmd).and_respond("530 Not logged in.")
+ -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 550" do
+ @server.should_receive(:rmd).and_respond("550 Requested action not taken.")
+ -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/sendcmd_spec.rb b/spec/ruby/library/net/ftp/sendcmd_spec.rb
new file mode 100644
index 0000000000..fefb89ae0b
--- /dev/null
+++ b/spec/ruby/library/net/ftp/sendcmd_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#sendcmd" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the passed command to the server" do
+ @ftp.sendcmd("HELP")
+ @ftp.last_response.should == "211 System status, or system help reply. (HELP)\n"
+ end
+
+ it "returns the server's response" do
+ @ftp.sendcmd("HELP").should == "211 System status, or system help reply. (HELP)\n"
+ end
+
+ it "raises no error when the response code is 1xx, 2xx or 3xx" do
+ @server.should_receive(:help).and_respond("120 Service ready in nnn minutes.")
+ -> { @ftp.sendcmd("HELP") }.should_not raise_error
+
+ @server.should_receive(:help).and_respond("200 Command okay.")
+ -> { @ftp.sendcmd("HELP") }.should_not raise_error
+
+ @server.should_receive(:help).and_respond("350 Requested file action pending further information.")
+ -> { @ftp.sendcmd("HELP") }.should_not raise_error
+ end
+
+ it "raises a Net::FTPTempError when the response code is 4xx" do
+ @server.should_receive(:help).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.sendcmd("HELP") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 5xx" do
+ @server.should_receive(:help).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.sendcmd("HELP") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPProtoError when the response code is not between 1xx-5xx" do
+ @server.should_receive(:help).and_respond("999 Invalid response.")
+ -> { @ftp.sendcmd("HELP") }.should raise_error(Net::FTPProtoError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/set_socket_spec.rb b/spec/ruby/library/net/ftp/set_socket_spec.rb
new file mode 100644
index 0000000000..6c8b58fb79
--- /dev/null
+++ b/spec/ruby/library/net/ftp/set_socket_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+
+ describe "Net::FTP#set_socket" do
+ # TODO: I won't spec this method, as it is not used
+ # anywhere and it should be private anyway.
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/net/ftp/shared/getbinaryfile.rb b/spec/ruby/library/net/ftp/shared/getbinaryfile.rb
new file mode 100644
index 0000000000..f324f5b85d
--- /dev/null
+++ b/spec/ruby/library/net/ftp/shared/getbinaryfile.rb
@@ -0,0 +1,150 @@
+describe :net_ftp_getbinaryfile, shared: :true do
+ before :each do
+ @fixture_file = File.dirname(__FILE__) + "/../fixtures/getbinaryfile"
+ @tmp_file = tmp("getbinaryfile")
+
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ @ftp.binary = @binary_mode
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+
+ rm_r @tmp_file
+ end
+
+ it "sends the RETR command to the server" do
+ @ftp.send(@method, "test", @tmp_file)
+ @ftp.last_response.should == "226 Closing data connection. (RETR test)\n"
+ end
+
+ it "returns nil" do
+ @ftp.send(@method, "test", @tmp_file).should be_nil
+ end
+
+ it "saves the contents of the passed remote file to the passed local file" do
+ @ftp.send(@method, "test", @tmp_file)
+ File.read(@tmp_file).should == "This is the content\nof the file named 'test'.\n"
+ end
+
+ describe "when passed a block" do
+ it "yields the received content as binary blocks of the passed size" do
+ res = []
+ @ftp.send(@method, "test", @tmp_file, 10) { |bin| res << bin }
+ res.should == [ "This is th", "e content\n", "of the fil", "e named 't", "est'.\n" ]
+ end
+ end
+
+ describe "when resuming an existing file" do
+ before :each do
+ @tmp_file = tmp("getbinaryfile_resume")
+
+ File.open(@tmp_file, "wb") do |f|
+ f << "This is the content\n"
+ end
+
+ @ftp.resume = true
+ end
+
+ it "saves the remaining content of the passed remote file to the passed local file" do
+ @ftp.send(@method, "test", @tmp_file)
+ File.read(@tmp_file).should == "This is the content\nof the file named 'test'.\n"
+ end
+
+ describe "and the REST command fails" do
+ it "raises a Net::FTPProtoError when the response code is 550" do
+ @server.should_receive(:rest).and_respond("Requested action not taken.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPProtoError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:rest).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:rest).and_respond("501 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:rest).and_respond("502 Command not implemented.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:rest).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:rest).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+ end
+ end
+
+ describe "when the RETR command fails" do
+ it "raises a Net::FTPTempError when the response code is 450" do
+ @server.should_receive(:retr).and_respond("450 Requested file action not taken.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPProtoError when the response code is 550" do
+ @server.should_receive(:retr).and_respond("Requested action not taken.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPProtoError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:retr).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:retr).and_respond("501 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:retr).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:retr).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+ end
+
+ describe "when opening the data port fails" do
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.")
+ @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.")
+ @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.")
+ @server.should_receive(:port).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:eprt).and_respond("530 Not logged in.")
+ @server.should_receive(:port).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/shared/gettextfile.rb b/spec/ruby/library/net/ftp/shared/gettextfile.rb
new file mode 100644
index 0000000000..82bec2a4a7
--- /dev/null
+++ b/spec/ruby/library/net/ftp/shared/gettextfile.rb
@@ -0,0 +1,100 @@
+describe :net_ftp_gettextfile, shared: :true do
+ before :each do
+ @tmp_file = tmp("gettextfile")
+
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ @ftp.binary = @binary_mode
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+
+ rm_r @tmp_file
+ end
+
+ it "sends the RETR command to the server" do
+ @ftp.send(@method, "test", @tmp_file)
+ @ftp.last_response.should == "226 Closing data connection. (RETR test)\n"
+ end
+
+ it "returns nil" do
+ @ftp.send(@method, "test", @tmp_file).should be_nil
+ end
+
+ it "saves the contents of the passed remote file to the passed local file" do
+ @ftp.send(@method, "test", @tmp_file)
+ File.read(@tmp_file).should == "This is the content\nof the file named 'test'.\n"
+ end
+
+ describe "when passed a block" do
+ it "yields each line of the retrieved file to the passed block" do
+ res = []
+ @ftp.send(@method, "test", @tmp_file) { |line| res << line }
+ res.should == [ "This is the content", "of the file named 'test'."]
+ end
+ end
+
+ describe "when the RETR command fails" do
+ it "raises a Net::FTPTempError when the response code is 450" do
+ @server.should_receive(:retr).and_respond("450 Requested file action not taken.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPProtoError when the response code is 550" do
+ @server.should_receive(:retr).and_respond("Requested action not taken.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPProtoError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:retr).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:retr).and_respond("501 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:retr).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:retr).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+ end
+
+ describe "when opening the data port fails" do
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.")
+ @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.")
+ @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.")
+ @server.should_receive(:port).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:eprt).and_respond("530 Not logged in.")
+ @server.should_receive(:port).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/shared/last_response_code.rb b/spec/ruby/library/net/ftp/shared/last_response_code.rb
new file mode 100644
index 0000000000..4fe53677db
--- /dev/null
+++ b/spec/ruby/library/net/ftp/shared/last_response_code.rb
@@ -0,0 +1,25 @@
+describe :net_ftp_last_response_code, shared: true do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "returns the response code for the last response" do
+ @server.should_receive(:help).and_respond("200 Command okay.")
+ @ftp.help
+ @ftp.send(@method).should == "200"
+
+ @server.should_receive(:help).and_respond("212 Directory status.")
+ @ftp.help
+ @ftp.send(@method).should == "212"
+ end
+end
diff --git a/spec/ruby/library/net/ftp/shared/list.rb b/spec/ruby/library/net/ftp/shared/list.rb
new file mode 100644
index 0000000000..adc3fa59c1
--- /dev/null
+++ b/spec/ruby/library/net/ftp/shared/list.rb
@@ -0,0 +1,104 @@
+describe :net_ftp_list, shared: true do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.passive = false
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ describe "when passed a block" do
+ it "yields each file in the list of files in the passed dir" do
+ expected = [
+ "-rw-r--r-- 1 spec staff 507 17 Jul 18:41 last_response_code.rb",
+ "-rw-r--r-- 1 spec staff 50 17 Jul 18:41 list.rb",
+ "-rw-r--r-- 1 spec staff 48 17 Jul 18:41 pwd.rb"
+ ]
+
+ res = []
+ @ftp.send(@method, "test.folder") { |line| res << line}
+ res.should == expected
+
+ @ftp.last_response.should == "226 transfer complete (LIST test.folder)\n"
+ end
+ end
+
+ describe "when passed no block" do
+ it "returns an Array containing a list of files in the passed dir" do
+ expected = [
+ "-rw-r--r-- 1 spec staff 507 17 Jul 18:41 last_response_code.rb",
+ "-rw-r--r-- 1 spec staff 50 17 Jul 18:41 list.rb",
+ "-rw-r--r-- 1 spec staff 48 17 Jul 18:41 pwd.rb"
+ ]
+
+ @ftp.send(@method, "test.folder").should == expected
+
+ @ftp.last_response.should == "226 transfer complete (LIST test.folder)\n"
+ end
+ end
+
+ describe "when the LIST command fails" do
+ it "raises a Net::FTPTempError when the response code is 450" do
+ @server.should_receive(:list).and_respond("450 Requested file action not taken..")
+ -> { @ftp.send(@method) }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:list).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:list).and_respond("501 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:list).and_respond("502 Command not implemented.")
+ -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:list).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method) }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:list).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError)
+ end
+ end
+
+ describe "when opening the data port fails" do
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.")
+ @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.")
+ @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.")
+ @server.should_receive(:port).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method) }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:eprt).and_respond("530 Not logged in.")
+ @server.should_receive(:port).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/shared/putbinaryfile.rb b/spec/ruby/library/net/ftp/shared/putbinaryfile.rb
new file mode 100644
index 0000000000..1163a1cfea
--- /dev/null
+++ b/spec/ruby/library/net/ftp/shared/putbinaryfile.rb
@@ -0,0 +1,167 @@
+describe :net_ftp_putbinaryfile, shared: :true do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @local_fixture_file = File.dirname(__FILE__) + "/../fixtures/putbinaryfile"
+ @remote_tmp_file = tmp("binaryfile", false)
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ @ftp.binary = @binary_mode
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+
+ rm_r @remote_tmp_file
+ end
+
+ it "sends the STOR command to the server" do
+ @ftp.send(@method, @local_fixture_file, "binary")
+ @ftp.last_response.should == "200 OK, Data received. (STOR binary)\n"
+ end
+
+ it "sends the contents of the passed local_file, without modifications" do
+ @ftp.send(@method, @local_fixture_file, "binary")
+
+ remote_lines = File.readlines(@remote_tmp_file)
+ local_lines = File.readlines(@local_fixture_file)
+
+ remote_lines.should == local_lines
+ end
+
+ it "returns nil" do
+ @ftp.send(@method, @local_fixture_file, "binary").should be_nil
+ end
+
+ describe "when passed a block" do
+ it "yields the transmitted content as binary blocks of the passed size" do
+ res = []
+ @ftp.send(@method, @local_fixture_file, "binary", 10) { |x| res << x }
+ res.should == [
+ "This is an", " example f",
+ "ile\nwhich ", "is going t",
+ "o be trans", "mitted\nusi",
+ "ng #putbin", "aryfile.\n"
+ ]
+ end
+ end
+
+ describe "when resuming an existing file" do
+ before :each do
+ File.open(@remote_tmp_file, "w") do |f|
+ f << "This is an example file\n"
+ end
+
+ @ftp.resume = true
+ end
+
+ it "sends the remaining content of the passed local_file to the passed remote_file" do
+ @ftp.send(@method, @local_fixture_file, "binary")
+ File.read(@remote_tmp_file).should == File.read(@local_fixture_file)
+ end
+
+ describe "and the APPE command fails" do
+ it "raises a Net::FTPProtoError when the response code is 550" do
+ @server.should_receive(:appe).and_respond("Requested action not taken.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPProtoError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:appe).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:appe).and_respond("501 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:appe).and_respond("502 Command not implemented.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:appe).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:appe).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+ end
+ end
+
+ describe "when the STOR command fails" do
+ it "raises a Net::FTPPermError when the response code is 532" do
+ @server.should_receive(:stor).and_respond("532 Need account for storing files.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 450" do
+ @server.should_receive(:stor).and_respond("450 Requested file action not taken.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 452" do
+ @server.should_receive(:stor).and_respond("452 Requested action not taken.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 553" do
+ @server.should_receive(:stor).and_respond("553 Requested action not taken.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:stor).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:stor).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:stor).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:stor).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+ end
+
+ describe "when opening the data port fails" do
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.")
+ @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.")
+ @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.")
+ @server.should_receive(:port).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:eprt).and_respond("530 Not logged in.")
+ @server.should_receive(:port).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/shared/puttextfile.rb b/spec/ruby/library/net/ftp/shared/puttextfile.rb
new file mode 100644
index 0000000000..50e8de28e6
--- /dev/null
+++ b/spec/ruby/library/net/ftp/shared/puttextfile.rb
@@ -0,0 +1,120 @@
+describe :net_ftp_puttextfile, shared: true do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @local_fixture_file = File.dirname(__FILE__) + "/../fixtures/puttextfile"
+ @remote_tmp_file = tmp("textfile", false)
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ @ftp.binary = @binary_mode
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+
+ rm_r @remote_tmp_file
+ end
+
+ it "sends the STOR command to the server" do
+ @ftp.send(@method, @local_fixture_file, "text")
+ @ftp.last_response.should == "200 OK, Data received. (STOR text)\n"
+ end
+
+ it "sends the contents of the passed local_file, using \\r\\n as the newline separator" do
+ @ftp.send(@method, @local_fixture_file, "text")
+
+ remote_lines = open(@remote_tmp_file, "rb") {|f| f.read }
+ local_lines = open(@local_fixture_file, "rb") {|f| f.read }
+
+ remote_lines.should_not == local_lines
+ remote_lines.should == local_lines.gsub("\n", "\r\n")
+ end
+
+ it "returns nil" do
+ @ftp.send(@method, @local_fixture_file, "text").should be_nil
+ end
+
+ describe "when passed a block" do
+ it "yields each transmitted line" do
+ res = []
+ @ftp.send(@method, @local_fixture_file, "text") { |x| res << x }
+ res.should == [
+ "This is an example file\r\n",
+ "which is going to be transmitted\r\n",
+ "using #puttextfile.\r\n"
+ ]
+ end
+ end
+
+ describe "when the STOR command fails" do
+ it "raises a Net::FTPPermError when the response code is 532" do
+ @server.should_receive(:stor).and_respond("532 Need account for storing files.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 450" do
+ @server.should_receive(:stor).and_respond("450 Requested file action not taken.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 452" do
+ @server.should_receive(:stor).and_respond("452 Requested action not taken.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 553" do
+ @server.should_receive(:stor).and_respond("553 Requested action not taken.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:stor).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:stor).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:stor).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:stor).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError)
+ end
+ end
+
+ describe "when opening the data port fails" do
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.")
+ @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.")
+ @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.")
+ @server.should_receive(:port).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:eprt).and_respond("530 Not logged in.")
+ @server.should_receive(:port).and_respond("530 Not logged in.")
+ -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/shared/pwd.rb b/spec/ruby/library/net/ftp/shared/pwd.rb
new file mode 100644
index 0000000000..951d020f2d
--- /dev/null
+++ b/spec/ruby/library/net/ftp/shared/pwd.rb
@@ -0,0 +1,3 @@
+describe :net_ftp_pwd, shared: true do
+
+end
diff --git a/spec/ruby/library/net/ftp/site_spec.rb b/spec/ruby/library/net/ftp/site_spec.rb
new file mode 100644
index 0000000000..ca4f499112
--- /dev/null
+++ b/spec/ruby/library/net/ftp/site_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#site" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the SITE command with the passed argument to the server" do
+ @ftp.site("param")
+ @ftp.last_response.should == "200 Command okay. (SITE param)\n"
+ end
+
+ it "returns nil" do
+ @ftp.site("param").should be_nil
+ end
+
+ it "does not raise an error when the response code is 202" do
+ @server.should_receive(:site).and_respond("202 Command not implemented, superfluous at this site.")
+ -> { @ftp.site("param") }.should_not raise_error
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:site).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.site("param") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:site).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.site("param") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:site).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.site("param") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:site).and_respond("530 Requested action not taken.")
+ -> { @ftp.site("param") }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/size_spec.rb b/spec/ruby/library/net/ftp/size_spec.rb
new file mode 100644
index 0000000000..0c20b10549
--- /dev/null
+++ b/spec/ruby/library/net/ftp/size_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#size" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the SIZE command to the server" do
+ @ftp.size("test.file")
+ @ftp.last_response.should == "213 1024\n"
+ end
+
+ it "returns the size of the passed file as Integer" do
+ @ftp.size("test.file").should eql(1024)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:size).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.size("test.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:size).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.size("test.file") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:size).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.size("test.file") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 550" do
+ @server.should_receive(:size).and_respond("550 Requested action not taken.")
+ -> { @ftp.size("test.file") }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/spec_helper.rb b/spec/ruby/library/net/ftp/spec_helper.rb
new file mode 100644
index 0000000000..c87d16218b
--- /dev/null
+++ b/spec/ruby/library/net/ftp/spec_helper.rb
@@ -0,0 +1,5 @@
+require "net/ftp"
+
+if defined?(Net::FTP.default_passive)
+ Net::FTP.default_passive = false
+end
diff --git a/spec/ruby/library/net/ftp/status_spec.rb b/spec/ruby/library/net/ftp/status_spec.rb
new file mode 100644
index 0000000000..03bc5d6e05
--- /dev/null
+++ b/spec/ruby/library/net/ftp/status_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#status" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the STAT command to the server" do
+ @ftp.status
+ @ftp.last_response.should == "211 System status, or system help reply. (STAT)\n"
+ end
+
+ it "sends the STAT command with an optional parameter to the server" do
+ @ftp.status("/pub").should == "211 System status, or system help reply. (STAT /pub)\n"
+ end
+
+ it "returns the received information" do
+ @ftp.status.should == "211 System status, or system help reply. (STAT)\n"
+ end
+
+ it "does not raise an error when the response code is 212" do
+ @server.should_receive(:stat).and_respond("212 Directory status.")
+ -> { @ftp.status }.should_not raise_error
+ end
+
+ it "does not raise an error when the response code is 213" do
+ @server.should_receive(:stat).and_respond("213 File status.")
+ -> { @ftp.status }.should_not raise_error
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:stat).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.status }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:stat).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.status }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:stat).and_respond("502 Command not implemented.")
+ -> { @ftp.status }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:stat).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.status }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 530" do
+ @server.should_receive(:stat).and_respond("530 Requested action not taken.")
+ -> { @ftp.status }.should raise_error(Net::FTPPermError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/storbinary_spec.rb b/spec/ruby/library/net/ftp/storbinary_spec.rb
new file mode 100644
index 0000000000..64c9090760
--- /dev/null
+++ b/spec/ruby/library/net/ftp/storbinary_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#storbinary" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @local_fixture_file = File.dirname(__FILE__) + "/fixtures/putbinaryfile"
+ @tmp_file = tmp("binaryfile", false)
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+
+ rm_r @tmp_file
+ end
+
+ it "sends the passed command and the passed File object's content to the server" do
+ File.open(@local_fixture_file) do |f|
+ f.binmode
+
+ @ftp.storbinary("STOR binary", f, 4096) {}
+ @ftp.last_response.should == "200 OK, Data received. (STOR binary)\n"
+ end
+ end
+
+ it "yields the transmitted content as binary blocks of the passed size" do
+ File.open(@local_fixture_file) do |f|
+ f.binmode
+
+ res = []
+ @ftp.storbinary("STOR binary", f, 10) { |x| res << x }
+ res.should == [
+ "This is an", " example f",
+ "ile\nwhich ", "is going t",
+ "o be trans", "mitted\nusi",
+ "ng #putbin", "aryfile.\n"
+ ]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/storlines_spec.rb b/spec/ruby/library/net/ftp/storlines_spec.rb
new file mode 100644
index 0000000000..a4bb7af799
--- /dev/null
+++ b/spec/ruby/library/net/ftp/storlines_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#storlines" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @local_fixture_file = File.dirname(__FILE__) + "/fixtures/puttextfile"
+ @tmp_file = tmp("textfile", false)
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+
+ rm_r @tmp_file
+ end
+
+ it "sends the passed command and the passed File object's content to the server" do
+ File.open(@local_fixture_file) do |f|
+ @ftp.storlines("STOR text", f) {}
+ @ftp.last_response.should == "200 OK, Data received. (STOR text)\n"
+ end
+ end
+
+ it "yields each line of the transmitted content" do
+ File.open(@local_fixture_file) do |f|
+ res = []
+ @ftp.storlines("STOR text", f) { |x| res << x }
+ res.should == [
+ "This is an example file\r\n",
+ "which is going to be transmitted\r\n",
+ "using #puttextfile.\r\n"
+ ]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/system_spec.rb b/spec/ruby/library/net/ftp/system_spec.rb
new file mode 100644
index 0000000000..0630bbe1f6
--- /dev/null
+++ b/spec/ruby/library/net/ftp/system_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#system" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the SYST command to the server" do
+ @ftp.system
+ @ftp.last_response.should =~ /\A215 FTP Dummy Server \(SYST\)\Z/
+ end
+
+ it "returns the received information" do
+ @ftp.system.should =~ /\AFTP Dummy Server \(SYST\)\Z/
+ end
+
+ it "raises a Net::FTPPermError when the response code is 500" do
+ @server.should_receive(:syst).and_respond("500 Syntax error, command unrecognized.")
+ -> { @ftp.system }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 501" do
+ @server.should_receive(:syst).and_respond("501 Syntax error in parameters or arguments.")
+ -> { @ftp.system }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 502" do
+ @server.should_receive(:syst).and_respond("502 Command not implemented.")
+ -> { @ftp.system }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 421" do
+ @server.should_receive(:syst).and_respond("421 Service not available, closing control connection.")
+ -> { @ftp.system }.should raise_error(Net::FTPTempError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/voidcmd_spec.rb b/spec/ruby/library/net/ftp/voidcmd_spec.rb
new file mode 100644
index 0000000000..ee74455d63
--- /dev/null
+++ b/spec/ruby/library/net/ftp/voidcmd_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#voidcmd" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "sends the passed command to the server" do
+ @server.should_receive(:help).and_respond("2xx Does not raise.")
+ -> { @ftp.voidcmd("HELP") }.should_not raise_error
+ end
+
+ it "returns nil" do
+ @server.should_receive(:help).and_respond("2xx Does not raise.")
+ @ftp.voidcmd("HELP").should be_nil
+ end
+
+ it "raises a Net::FTPReplyError when the response code is 1xx" do
+ @server.should_receive(:help).and_respond("1xx Does raise a Net::FTPReplyError.")
+ -> { @ftp.voidcmd("HELP") }.should raise_error(Net::FTPReplyError)
+ end
+
+ it "raises a Net::FTPReplyError when the response code is 3xx" do
+ @server.should_receive(:help).and_respond("3xx Does raise a Net::FTPReplyError.")
+ -> { @ftp.voidcmd("HELP") }.should raise_error(Net::FTPReplyError)
+ end
+
+ it "raises a Net::FTPTempError when the response code is 4xx" do
+ @server.should_receive(:help).and_respond("4xx Does raise a Net::FTPTempError.")
+ -> { @ftp.voidcmd("HELP") }.should raise_error(Net::FTPTempError)
+ end
+
+ it "raises a Net::FTPPermError when the response code is 5xx" do
+ @server.should_receive(:help).and_respond("5xx Does raise a Net::FTPPermError.")
+ -> { @ftp.voidcmd("HELP") }.should raise_error(Net::FTPPermError)
+ end
+
+ it "raises a Net::FTPProtoError when the response code is not valid" do
+ @server.should_receive(:help).and_respond("999 Does raise a Net::FTPProtoError.")
+ -> { @ftp.voidcmd("HELP") }.should raise_error(Net::FTPProtoError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/ftp/welcome_spec.rb b/spec/ruby/library/net/ftp/welcome_spec.rb
new file mode 100644
index 0000000000..e5414ef607
--- /dev/null
+++ b/spec/ruby/library/net/ftp/welcome_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'spec_helper'
+ require_relative 'fixtures/server'
+
+ describe "Net::FTP#welcome" do
+ before :each do
+ @server = NetFTPSpecs::DummyFTP.new
+ @server.serve_once
+
+ @ftp = Net::FTP.new
+ @ftp.connect(@server.hostname, @server.server_port)
+ end
+
+ after :each do
+ @ftp.quit rescue nil
+ @ftp.close
+ @server.stop
+ end
+
+ it "returns the server's welcome message" do
+ @ftp.welcome.should be_nil
+ @ftp.login
+ @ftp.welcome.should == "230 User logged in, proceed. (USER anonymous)\n"
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/HTTPBadResponse_spec.rb b/spec/ruby/library/net/http/HTTPBadResponse_spec.rb
new file mode 100644
index 0000000000..be644968f5
--- /dev/null
+++ b/spec/ruby/library/net/http/HTTPBadResponse_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPBadResponse" do
+ it "is a subclass of StandardError" do
+ Net::HTTPBadResponse.should < StandardError
+ end
+end
diff --git a/spec/ruby/library/net/http/HTTPClientExcepton_spec.rb b/spec/ruby/library/net/http/HTTPClientExcepton_spec.rb
new file mode 100644
index 0000000000..d576349a57
--- /dev/null
+++ b/spec/ruby/library/net/http/HTTPClientExcepton_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPClientException" do
+ it "is a subclass of Net::ProtoServerError" do
+ Net::HTTPClientException.should < Net::ProtoServerError
+ end
+
+ it "includes the Net::HTTPExceptions module" do
+ Net::HTTPClientException.should < Net::HTTPExceptions
+ end
+end
diff --git a/spec/ruby/library/net/http/HTTPError_spec.rb b/spec/ruby/library/net/http/HTTPError_spec.rb
new file mode 100644
index 0000000000..ab5f491bb7
--- /dev/null
+++ b/spec/ruby/library/net/http/HTTPError_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPError" do
+ it "is a subclass of Net::ProtocolError" do
+ Net::HTTPError.should < Net::ProtocolError
+ end
+
+ it "includes the Net::HTTPExceptions module" do
+ Net::HTTPError.should < Net::HTTPExceptions
+ end
+end
diff --git a/spec/ruby/library/net/http/HTTPFatalError_spec.rb b/spec/ruby/library/net/http/HTTPFatalError_spec.rb
new file mode 100644
index 0000000000..6ab36bff6c
--- /dev/null
+++ b/spec/ruby/library/net/http/HTTPFatalError_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPFatalError" do
+ it "is a subclass of Net::ProtoFatalError" do
+ Net::HTTPFatalError.should < Net::ProtoFatalError
+ end
+
+ it "includes the Net::HTTPExceptions module" do
+ Net::HTTPFatalError.should < Net::HTTPExceptions
+ end
+end
diff --git a/spec/ruby/library/net/http/HTTPHeaderSyntaxError_spec.rb b/spec/ruby/library/net/http/HTTPHeaderSyntaxError_spec.rb
new file mode 100644
index 0000000000..38e9454f99
--- /dev/null
+++ b/spec/ruby/library/net/http/HTTPHeaderSyntaxError_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPHeaderSyntaxError" do
+ it "is a subclass of StandardError" do
+ Net::HTTPHeaderSyntaxError.should < StandardError
+ end
+end
diff --git a/spec/ruby/library/net/http/HTTPRetriableError_spec.rb b/spec/ruby/library/net/http/HTTPRetriableError_spec.rb
new file mode 100644
index 0000000000..3a4bb9146c
--- /dev/null
+++ b/spec/ruby/library/net/http/HTTPRetriableError_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPRetriableError" do
+ it "is a subclass of Net::ProtoRetriableError" do
+ Net::HTTPRetriableError.should < Net::ProtoRetriableError
+ end
+
+ it "includes the Net::HTTPExceptions module" do
+ Net::HTTPRetriableError.should < Net::HTTPExceptions
+ end
+end
diff --git a/spec/ruby/library/net/http/HTTPServerException_spec.rb b/spec/ruby/library/net/http/HTTPServerException_spec.rb
new file mode 100644
index 0000000000..23b0657364
--- /dev/null
+++ b/spec/ruby/library/net/http/HTTPServerException_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPServerException" do
+ it "is a subclass of Net::ProtoServerError and is warned as deprecated" do
+ -> { Net::HTTPServerException.should < Net::ProtoServerError }.should complain(/warning: constant Net::HTTPServerException is deprecated/)
+ end
+
+ it "includes the Net::HTTPExceptions module and is warned as deprecated" do
+ -> { Net::HTTPServerException.should < Net::HTTPExceptions }.should complain(/warning: constant Net::HTTPServerException is deprecated/)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/Proxy_spec.rb b/spec/ruby/library/net/http/http/Proxy_spec.rb
new file mode 100644
index 0000000000..f85ccc0ee9
--- /dev/null
+++ b/spec/ruby/library/net/http/http/Proxy_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.Proxy" do
+ it "returns a new subclass of Net::HTTP" do
+ Net::HTTP.Proxy("localhost").should < Net::HTTP
+ end
+
+ it "returns Net::HTTP when the passed address is nil" do
+ Net::HTTP.Proxy(nil).should == Net::HTTP
+ end
+
+ it "sets the returned subclasses' proxy options based on the passed arguments" do
+ http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks")
+ http_with_proxy.proxy_address.should == "localhost"
+ http_with_proxy.proxy_port.should eql(1234)
+ http_with_proxy.proxy_user.should == "rspec"
+ http_with_proxy.proxy_pass.should == "rocks"
+ end
+end
+
+describe "Net::HTTP#proxy?" do
+ describe "when self is no proxy class instance" do
+ it "returns false" do
+ Net::HTTP.new("localhost", 3333).proxy?.should be_false
+ end
+ end
+
+ describe "when self is a proxy class instance" do
+ it "returns false" do
+ http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks")
+ http_with_proxy.new("localhost", 3333).proxy?.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/active_spec.rb b/spec/ruby/library/net/http/http/active_spec.rb
new file mode 100644
index 0000000000..ef657243ba
--- /dev/null
+++ b/spec/ruby/library/net/http/http/active_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+require_relative 'shared/started'
+
+describe "Net::HTTP#active?" do
+ it_behaves_like :net_http_started_p, :active?
+end
diff --git a/spec/ruby/library/net/http/http/address_spec.rb b/spec/ruby/library/net/http/http/address_spec.rb
new file mode 100644
index 0000000000..5fce76d767
--- /dev/null
+++ b/spec/ruby/library/net/http/http/address_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP#address" do
+ it "returns the current host name" do
+ net = Net::HTTP.new("localhost")
+ net.address.should == "localhost"
+ end
+end
diff --git a/spec/ruby/library/net/http/http/close_on_empty_response_spec.rb b/spec/ruby/library/net/http/http/close_on_empty_response_spec.rb
new file mode 100644
index 0000000000..a97b7b6c43
--- /dev/null
+++ b/spec/ruby/library/net/http/http/close_on_empty_response_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP#close_on_empty_response" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "Net::HTTP#close_on_empty_response=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/net/http/http/copy_spec.rb b/spec/ruby/library/net/http/http/copy_spec.rb
new file mode 100644
index 0000000000..5ebfdc334e
--- /dev/null
+++ b/spec/ruby/library/net/http/http/copy_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#copy" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends a COPY request to the passed path and returns the response" do
+ response = @http.copy("/request")
+ response.should be_kind_of(Net::HTTPResponse)
+ response.body.should == "Request type: COPY"
+ end
+end
diff --git a/spec/ruby/library/net/http/http/default_port_spec.rb b/spec/ruby/library/net/http/http/default_port_spec.rb
new file mode 100644
index 0000000000..30db18efae
--- /dev/null
+++ b/spec/ruby/library/net/http/http/default_port_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.default_port" do
+ it "returns 80" do
+ Net::HTTP.http_default_port.should eql(80)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/delete_spec.rb b/spec/ruby/library/net/http/http/delete_spec.rb
new file mode 100644
index 0000000000..160c653115
--- /dev/null
+++ b/spec/ruby/library/net/http/http/delete_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#delete" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends a DELETE request to the passed path and returns the response" do
+ response = @http.delete("/request")
+ response.should be_kind_of(Net::HTTPResponse)
+ response.body.should == "Request type: DELETE"
+ end
+end
diff --git a/spec/ruby/library/net/http/http/finish_spec.rb b/spec/ruby/library/net/http/http/finish_spec.rb
new file mode 100644
index 0000000000..f98bc7be13
--- /dev/null
+++ b/spec/ruby/library/net/http/http/finish_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#finish" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.new("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ describe "when self has been started" do
+ it "closes the tcp connection" do
+ @http.start
+ @http.finish
+ @http.started?.should be_false
+ end
+ end
+
+ describe "when self has not been started yet" do
+ it "raises an IOError" do
+ -> { @http.finish }.should raise_error(IOError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/fixtures/http_server.rb b/spec/ruby/library/net/http/http/fixtures/http_server.rb
new file mode 100644
index 0000000000..c1cedbfa76
--- /dev/null
+++ b/spec/ruby/library/net/http/http/fixtures/http_server.rb
@@ -0,0 +1,123 @@
+require 'socket'
+
+module NetHTTPSpecs
+ class NullWriter
+ def <<(s) end
+ def puts(*args) end
+ def print(*args) end
+ def printf(*args) end
+ end
+
+ class SmallHTTPServer
+ def initialize(bind_address)
+ @server = TCPServer.new(bind_address, 0)
+ @thread = Thread.new {
+ Thread.current.abort_on_exception = true
+ listen
+ }
+ end
+
+ def ip
+ @server.addr[3]
+ end
+
+ def port
+ @server.addr[1]
+ end
+
+ def listen
+ until @server.closed?
+ client = @server.accept
+ handle_client(client)
+ end
+ end
+
+ def handle_client(client)
+ begin
+ until client.closed?
+ request = client.gets("\r\n\r\n")
+ break unless request
+ if request == "CLOSE"
+ @server.close
+ break
+ end
+ handle_request(client, request)
+ end
+ ensure
+ client.close
+ end
+ end
+
+ def parse_request(request)
+ request, *headers = request.chomp.lines.map { |line| line.chomp }
+ request_method, request_uri, _http_version = request.split
+ headers = headers.map { |line| line.split(': ', 2) }.to_h
+ [request_method, request_uri, headers]
+ end
+
+ def handle_request(client, request)
+ request_method, request_uri, headers = parse_request(request)
+
+ if headers.include? 'Content-Length'
+ request_body_size = Integer(headers['Content-Length'])
+ request_body = client.read(request_body_size)
+ end
+
+ case request_uri
+ when '/'
+ raise request_method unless request_method == 'GET'
+ reply(client, "This is the index page.", request_method)
+ when '/request'
+ reply(client, "Request type: #{request_method}", request_method)
+ when '/request/body'
+ reply(client, request_body, request_method)
+ when '/request/header'
+ reply(client, headers.inspect, request_method)
+ when '/request/basic_auth'
+ reply(client, "username: \npassword: ", request_method)
+ else
+ raise request_uri
+ end
+ end
+
+ def reply(client, body, request_method)
+ client.print "HTTP/1.1 200 OK\r\n"
+ if request_method == 'HEAD'
+ client.close
+ else
+ client.print "Content-Type: text/plain\r\n"
+ client.print "Content-Length: #{body.bytesize}\r\n"
+ client.print "\r\n"
+ client.print body
+ end
+ end
+
+ def close
+ TCPSocket.open(ip, port) do |socket|
+ socket.write "CLOSE"
+ end
+ @thread.join
+ end
+ end
+
+ @server = nil
+
+ class << self
+ def port
+ raise "server not started" unless @server
+ @server.port
+ end
+
+ def start_server
+ bind_address = platform_is(:windows) ? "localhost" : "127.0.0.1"
+ @server = SmallHTTPServer.new(bind_address)
+ end
+
+ def stop_server
+ if @server
+ @server.close
+ @server = nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/get2_spec.rb b/spec/ruby/library/net/http/http/get2_spec.rb
new file mode 100644
index 0000000000..519e4c2599
--- /dev/null
+++ b/spec/ruby/library/net/http/http/get2_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+require_relative 'shared/request_get'
+
+describe "Net::HTTP#get2" do
+ it_behaves_like :net_http_request_get, :get2
+end
diff --git a/spec/ruby/library/net/http/http/get_print_spec.rb b/spec/ruby/library/net/http/http/get_print_spec.rb
new file mode 100644
index 0000000000..f59dd68c81
--- /dev/null
+++ b/spec/ruby/library/net/http/http/get_print_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP.get_print" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @port = NetHTTPSpecs.port
+ end
+
+ after :each do
+ NetHTTPSpecs.stop_server
+ end
+
+ describe "when passed URI" do
+ it "it prints the body of the specified uri to $stdout" do
+ -> do
+ Net::HTTP.get_print URI.parse("http://localhost:#{@port}/")
+ end.should output(/This is the index page\./)
+ end
+ end
+
+ describe "when passed host, path, port" do
+ it "it prints the body of the specified uri to $stdout" do
+ -> do
+ Net::HTTP.get_print 'localhost', "/", @port
+ end.should output(/This is the index page\./)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/get_response_spec.rb b/spec/ruby/library/net/http/http/get_response_spec.rb
new file mode 100644
index 0000000000..941b35e773
--- /dev/null
+++ b/spec/ruby/library/net/http/http/get_response_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP.get_response" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @port = NetHTTPSpecs.port
+ end
+
+ after :each do
+ NetHTTPSpecs.stop_server
+ end
+
+ describe "when passed URI" do
+ it "returns the response for the specified uri" do
+ res = Net::HTTP.get_response(URI.parse("http://localhost:#{@port}/"))
+ res.content_type.should == "text/plain"
+ res.body.should == "This is the index page."
+ end
+ end
+
+ describe "when passed host, path, port" do
+ it "returns the response for the specified host-path-combination" do
+ res = Net::HTTP.get_response('localhost', "/", @port)
+ res.content_type.should == "text/plain"
+ res.body.should == "This is the index page."
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/get_spec.rb b/spec/ruby/library/net/http/http/get_spec.rb
new file mode 100644
index 0000000000..9f6c45f26b
--- /dev/null
+++ b/spec/ruby/library/net/http/http/get_spec.rb
@@ -0,0 +1,96 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP.get" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @port = NetHTTPSpecs.port
+ end
+
+ after :each do
+ NetHTTPSpecs.stop_server
+ end
+
+ describe "when passed URI" do
+ it "returns the body of the specified uri" do
+ Net::HTTP.get(URI.parse("http://localhost:#{@port}/")).should == "This is the index page."
+ end
+ end
+
+ describe "when passed host, path, port" do
+ it "returns the body of the specified host-path-combination" do
+ Net::HTTP.get('localhost', "/", @port).should == "This is the index page."
+ end
+ end
+end
+
+quarantine! do # These specs fail frequently with CHECK_LEAKS=true
+describe "Net::HTTP.get" do
+ describe "when reading gzipped contents" do
+ def start_threads
+ require 'zlib'
+ require 'stringio'
+
+ server = nil
+ server_thread = Thread.new do
+ server = TCPServer.new("127.0.0.1", 0)
+ begin
+ c = server.accept
+ ensure
+ server.close
+ end
+ c.print "HTTP/1.1 200\r\n"
+ c.print "Content-Type: text/plain\r\n"
+ c.print "Content-Encoding: gzip\r\n"
+ s = StringIO.new
+ z = Zlib::GzipWriter.new(s)
+ begin
+ z.write 'Hello World!'
+ ensure
+ z.close
+ end
+ c.print "Content-Length: #{s.length}\r\n\r\n"
+ # Write partial gzip content
+ c.write s.string.byteslice(0..-2)
+ c.flush
+ c
+ end
+ Thread.pass until server && server_thread.stop?
+
+ client_thread = Thread.new do
+ Thread.current.report_on_exception = false
+ Net::HTTP.get("127.0.0.1", '/', server.connect_address.ip_port)
+ end
+
+ socket = server_thread.value
+ Thread.pass until client_thread.stop?
+
+ [socket, client_thread]
+ end
+
+ it "propagates exceptions interrupting the thread and does not replace it with Zlib::BufError" do
+ my_exception = Class.new(RuntimeError)
+ socket, client_thread = start_threads
+ begin
+ client_thread.raise my_exception, "my exception"
+ -> { client_thread.value }.should raise_error(my_exception)
+ ensure
+ socket.close
+ end
+ end
+
+ ruby_version_is "3.0" do # https://bugs.ruby-lang.org/issues/13882#note-6
+ it "lets the kill Thread exception goes through and does not replace it with Zlib::BufError" do
+ socket, client_thread = start_threads
+ begin
+ client_thread.kill
+ client_thread.value.should == nil
+ ensure
+ socket.close
+ end
+ end
+ end
+ end
+end
+end
diff --git a/spec/ruby/library/net/http/http/head2_spec.rb b/spec/ruby/library/net/http/http/head2_spec.rb
new file mode 100644
index 0000000000..6958204ee1
--- /dev/null
+++ b/spec/ruby/library/net/http/http/head2_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+require_relative 'shared/request_head'
+
+describe "Net::HTTP#head2" do
+ it_behaves_like :net_http_request_head, :head2
+end
diff --git a/spec/ruby/library/net/http/http/head_spec.rb b/spec/ruby/library/net/http/http/head_spec.rb
new file mode 100644
index 0000000000..925a8e6043
--- /dev/null
+++ b/spec/ruby/library/net/http/http/head_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#head" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends a HEAD request to the passed path and returns the response" do
+ response = @http.head("/request")
+ # HEAD requests have no responses
+ response.body.should be_nil
+ end
+
+ it "returns a Net::HTTPResponse" do
+ @http.head("/request").should be_kind_of(Net::HTTPResponse)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/http_default_port_spec.rb b/spec/ruby/library/net/http/http/http_default_port_spec.rb
new file mode 100644
index 0000000000..cf7f73e630
--- /dev/null
+++ b/spec/ruby/library/net/http/http/http_default_port_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.http_default_port" do
+ it "returns 80" do
+ Net::HTTP.http_default_port.should eql(80)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/https_default_port_spec.rb b/spec/ruby/library/net/http/http/https_default_port_spec.rb
new file mode 100644
index 0000000000..fbf0bd1abc
--- /dev/null
+++ b/spec/ruby/library/net/http/http/https_default_port_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.https_default_port" do
+ it "returns 443" do
+ Net::HTTP.https_default_port.should eql(443)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/initialize_spec.rb b/spec/ruby/library/net/http/http/initialize_spec.rb
new file mode 100644
index 0000000000..7683713a0e
--- /dev/null
+++ b/spec/ruby/library/net/http/http/initialize_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP#initialize" do
+ it "is private" do
+ Net::HTTP.should have_private_instance_method(:initialize)
+ end
+
+ describe "when passed address" do
+ before :each do
+ @net = Net::HTTP.allocate
+ @net.send(:initialize, "localhost")
+ end
+
+ it "sets the new Net::HTTP instance's address to the passed address" do
+ @net.address.should == "localhost"
+ end
+
+ it "sets the new Net::HTTP instance's port to the default HTTP port" do
+ @net.port.should eql(Net::HTTP.default_port)
+ end
+
+ it "does not start the new Net::HTTP instance" do
+ @net.started?.should be_false
+ end
+ end
+
+ describe "when passed address, port" do
+ before :each do
+ @net = Net::HTTP.allocate
+ @net.send(:initialize, "localhost", 3333)
+ end
+
+ it "sets the new Net::HTTP instance's address to the passed address" do
+ @net.address.should == "localhost"
+ end
+
+ it "sets the new Net::HTTP instance's port to the passed port" do
+ @net.port.should eql(3333)
+ end
+
+ it "does not start the new Net::HTTP instance" do
+ @net.started?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/inspect_spec.rb b/spec/ruby/library/net/http/http/inspect_spec.rb
new file mode 100644
index 0000000000..b1e799ca34
--- /dev/null
+++ b/spec/ruby/library/net/http/http/inspect_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#inspect" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @port = NetHTTPSpecs.port
+ @http = Net::HTTP.new("localhost", @port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "returns a String representation of self" do
+ @http.inspect.should be_kind_of(String)
+ @http.inspect.should == "#<Net::HTTP localhost:#{@port} open=false>"
+
+ @http.start
+ @http.inspect.should == "#<Net::HTTP localhost:#{@port} open=true>"
+ end
+end
diff --git a/spec/ruby/library/net/http/http/is_version_1_1_spec.rb b/spec/ruby/library/net/http/http/is_version_1_1_spec.rb
new file mode 100644
index 0000000000..f37695b777
--- /dev/null
+++ b/spec/ruby/library/net/http/http/is_version_1_1_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'shared/version_1_1'
+
+describe "Net::HTTP.is_version_1_1?" do
+ it_behaves_like :net_http_version_1_1_p, :is_version_1_1?
+end
diff --git a/spec/ruby/library/net/http/http/is_version_1_2_spec.rb b/spec/ruby/library/net/http/http/is_version_1_2_spec.rb
new file mode 100644
index 0000000000..82dbdc87aa
--- /dev/null
+++ b/spec/ruby/library/net/http/http/is_version_1_2_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'shared/version_1_2'
+
+describe "Net::HTTP.is_version_1_2?" do
+ it_behaves_like :net_http_version_1_2_p, :is_version_1_2?
+end
diff --git a/spec/ruby/library/net/http/http/lock_spec.rb b/spec/ruby/library/net/http/http/lock_spec.rb
new file mode 100644
index 0000000000..bb76607a8b
--- /dev/null
+++ b/spec/ruby/library/net/http/http/lock_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#lock" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends a LOCK request to the passed path and returns the response" do
+ response = @http.lock("/request", "test=test")
+ response.should be_kind_of(Net::HTTPResponse)
+ response.body.should == "Request type: LOCK"
+ end
+end
diff --git a/spec/ruby/library/net/http/http/mkcol_spec.rb b/spec/ruby/library/net/http/http/mkcol_spec.rb
new file mode 100644
index 0000000000..33017625e2
--- /dev/null
+++ b/spec/ruby/library/net/http/http/mkcol_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#mkcol" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends a MKCOL request to the passed path and returns the response" do
+ response = @http.mkcol("/request")
+ response.should be_kind_of(Net::HTTPResponse)
+ response.body.should == "Request type: MKCOL"
+ end
+end
diff --git a/spec/ruby/library/net/http/http/move_spec.rb b/spec/ruby/library/net/http/http/move_spec.rb
new file mode 100644
index 0000000000..4d6b828150
--- /dev/null
+++ b/spec/ruby/library/net/http/http/move_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#head" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends a MOVE request to the passed path and returns the response" do
+ response = @http.move("/request")
+ # HEAD requests have no responses
+ response.body.should == "Request type: MOVE"
+ end
+
+ it "returns a Net::HTTPResponse" do
+ @http.move("/request").should be_kind_of(Net::HTTPResponse)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/new_spec.rb b/spec/ruby/library/net/http/http/new_spec.rb
new file mode 100644
index 0000000000..491d1d01fd
--- /dev/null
+++ b/spec/ruby/library/net/http/http/new_spec.rb
@@ -0,0 +1,86 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.new" do
+ describe "when passed address" do
+ before :each do
+ @http = Net::HTTP.new("localhost")
+ end
+
+ it "returns a Net::HTTP instance" do
+ @http.proxy?.should be_false
+ @http.instance_of?(Net::HTTP).should be_true
+ end
+
+ it "sets the new Net::HTTP instance's address to the passed address" do
+ @http.address.should == "localhost"
+ end
+
+ it "sets the new Net::HTTP instance's port to the default HTTP port" do
+ @http.port.should eql(Net::HTTP.default_port)
+ end
+
+ it "does not start the new Net::HTTP instance" do
+ @http.started?.should be_false
+ end
+ end
+
+ describe "when passed address, port" do
+ before :each do
+ @http = Net::HTTP.new("localhost", 3333)
+ end
+
+ it "returns a Net::HTTP instance" do
+ @http.proxy?.should be_false
+ @http.instance_of?(Net::HTTP).should be_true
+ end
+
+ it "sets the new Net::HTTP instance's address to the passed address" do
+ @http.address.should == "localhost"
+ end
+
+ it "sets the new Net::HTTP instance's port to the passed port" do
+ @http.port.should eql(3333)
+ end
+
+ it "does not start the new Net::HTTP instance" do
+ @http.started?.should be_false
+ end
+ end
+
+ describe "when passed address, port, *proxy_options" do
+ it "returns a Net::HTTP instance" do
+ http = Net::HTTP.new("localhost", 3333, "localhost")
+ http.proxy?.should be_true
+ http.instance_of?(Net::HTTP).should be_true
+ http.should be_kind_of(Net::HTTP)
+ end
+
+ it "correctly sets the passed Proxy options" do
+ http = Net::HTTP.new("localhost", 3333, "localhost")
+ http.proxy_address.should == "localhost"
+ http.proxy_port.should eql(80)
+ http.proxy_user.should be_nil
+ http.proxy_pass.should be_nil
+
+ http = Net::HTTP.new("localhost", 3333, "localhost", 1234)
+ http.proxy_address.should == "localhost"
+ http.proxy_port.should eql(1234)
+ http.proxy_user.should be_nil
+ http.proxy_pass.should be_nil
+
+ http = Net::HTTP.new("localhost", 3333, "localhost", 1234, "rubyspec")
+ http.proxy_address.should == "localhost"
+ http.proxy_port.should eql(1234)
+ http.proxy_user.should == "rubyspec"
+ http.proxy_pass.should be_nil
+
+ http = Net::HTTP.new("localhost", 3333, "localhost", 1234, "rubyspec", "rocks")
+ http.proxy_address.should == "localhost"
+ http.proxy_port.should eql(1234)
+ http.proxy_user.should == "rubyspec"
+ http.proxy_pass.should == "rocks"
+ end
+ end
+
+end
diff --git a/spec/ruby/library/net/http/http/newobj_spec.rb b/spec/ruby/library/net/http/http/newobj_spec.rb
new file mode 100644
index 0000000000..b261dcc5db
--- /dev/null
+++ b/spec/ruby/library/net/http/http/newobj_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.newobj" do
+ before :each do
+ @net = Net::HTTP.newobj("localhost")
+ end
+
+ describe "when passed address" do
+ it "returns a new Net::HTTP instance" do
+ @net.should be_kind_of(Net::HTTP)
+ end
+
+ it "sets the new Net::HTTP instance's address to the passed address" do
+ @net.address.should == "localhost"
+ end
+
+ it "sets the new Net::HTTP instance's port to the default HTTP port" do
+ @net.port.should eql(Net::HTTP.default_port)
+ end
+
+ it "does not start the new Net::HTTP instance" do
+ @net.started?.should be_false
+ end
+ end
+
+ describe "when passed address, port" do
+ before :each do
+ @net = Net::HTTP.newobj("localhost", 3333)
+ end
+
+ it "returns a new Net::HTTP instance" do
+ @net.should be_kind_of(Net::HTTP)
+ end
+
+ it "sets the new Net::HTTP instance's address to the passed address" do
+ @net.address.should == "localhost"
+ end
+
+ it "sets the new Net::HTTP instance's port to the passed port" do
+ @net.port.should eql(3333)
+ end
+
+ it "does not start the new Net::HTTP instance" do
+ @net.started?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/open_timeout_spec.rb b/spec/ruby/library/net/http/http/open_timeout_spec.rb
new file mode 100644
index 0000000000..44b33615e6
--- /dev/null
+++ b/spec/ruby/library/net/http/http/open_timeout_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP#open_timeout" do
+ it "returns the seconds to wait till the connection is open" do
+ net = Net::HTTP.new("localhost")
+ net.open_timeout.should eql(60)
+ net.open_timeout = 10
+ net.open_timeout.should eql(10)
+ end
+end
+
+describe "Net::HTTP#open_timeout=" do
+ it "sets the seconds to wait till the connection is open" do
+ net = Net::HTTP.new("localhost")
+ net.open_timeout = 10
+ net.open_timeout.should eql(10)
+ end
+
+ it "returns the newly set value" do
+ net = Net::HTTP.new("localhost")
+ (net.open_timeout = 10).should eql(10)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/options_spec.rb b/spec/ruby/library/net/http/http/options_spec.rb
new file mode 100644
index 0000000000..d798e69197
--- /dev/null
+++ b/spec/ruby/library/net/http/http/options_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#options" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends an options request to the passed path and returns the response" do
+ response = @http.options("/request")
+
+ response.body.should == "Request type: OPTIONS"
+ end
+
+ it "returns a Net::HTTPResponse" do
+ @http.options("/request").should be_kind_of(Net::HTTPResponse)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/port_spec.rb b/spec/ruby/library/net/http/http/port_spec.rb
new file mode 100644
index 0000000000..7de295ca75
--- /dev/null
+++ b/spec/ruby/library/net/http/http/port_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP#port" do
+ it "returns the current port number" do
+ net = Net::HTTP.new("localhost", 3333)
+ net.port.should eql(3333)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/post2_spec.rb b/spec/ruby/library/net/http/http/post2_spec.rb
new file mode 100644
index 0000000000..ccc95068fb
--- /dev/null
+++ b/spec/ruby/library/net/http/http/post2_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+require_relative 'shared/request_post'
+
+describe "Net::HTTP#post2" do
+ it_behaves_like :net_http_request_post, :post2
+end
diff --git a/spec/ruby/library/net/http/http/post_form_spec.rb b/spec/ruby/library/net/http/http/post_form_spec.rb
new file mode 100644
index 0000000000..891e05e7af
--- /dev/null
+++ b/spec/ruby/library/net/http/http/post_form_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP.post_form when passed URI" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @port = NetHTTPSpecs.port
+ end
+
+ after :each do
+ NetHTTPSpecs.stop_server
+ end
+
+ it "POSTs the passed form data to the given uri" do
+ uri = URI.parse("http://localhost:#{@port}/request/body")
+ data = { test: :data }
+
+ res = Net::HTTP.post_form(uri, data)
+ res.body.should == "test=data"
+ end
+end
diff --git a/spec/ruby/library/net/http/http/post_spec.rb b/spec/ruby/library/net/http/http/post_spec.rb
new file mode 100644
index 0000000000..891e20aaca
--- /dev/null
+++ b/spec/ruby/library/net/http/http/post_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require 'uri'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP.post" do
+ before :each do
+ NetHTTPSpecs.start_server
+ end
+
+ after :each do
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends post request to the specified URI and returns response" do
+ response = Net::HTTP.post(
+ URI("http://localhost:#{NetHTTPSpecs.port}/request"),
+ '{ "q": "ruby", "max": "50" }',
+ "Content-Type" => "application/json")
+ response.body.should == "Request type: POST"
+ end
+
+ it "returns a Net::HTTPResponse" do
+ response = Net::HTTP.post(URI("http://localhost:#{NetHTTPSpecs.port}/request"), "test=test")
+ response.should be_kind_of(Net::HTTPResponse)
+ end
+
+ it "sends Content-Type: application/x-www-form-urlencoded by default" do
+ response = Net::HTTP.post(URI("http://localhost:#{NetHTTPSpecs.port}/request/header"), "test=test")
+ response.body.should include('"Content-Type"=>"application/x-www-form-urlencoded"')
+ end
+
+ it "does not support HTTP Basic Auth" do
+ response = Net::HTTP.post(
+ URI("http://john:qwerty@localhost:#{NetHTTPSpecs.port}/request/basic_auth"),
+ "test=test")
+ response.body.should == "username: \npassword: "
+ end
+end
+
+describe "Net::HTTP#post" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends an post request to the passed path and returns the response" do
+ response = @http.post("/request", "test=test")
+ response.body.should == "Request type: POST"
+ end
+
+ it "returns a Net::HTTPResponse" do
+ @http.post("/request", "test=test").should be_kind_of(Net::HTTPResponse)
+ end
+
+ describe "when passed a block" do
+ it "yields fragments of the response body to the passed block" do
+ str = ""
+ @http.post("/request", "test=test") do |res|
+ str << res
+ end
+ str.should == "Request type: POST"
+ end
+
+ it "returns a Net::HTTPResponse" do
+ @http.post("/request", "test=test") {}.should be_kind_of(Net::HTTPResponse)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/propfind_spec.rb b/spec/ruby/library/net/http/http/propfind_spec.rb
new file mode 100644
index 0000000000..5240171618
--- /dev/null
+++ b/spec/ruby/library/net/http/http/propfind_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#propfind" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends an propfind request to the passed path and returns the response" do
+ response = @http.propfind("/request", "test=test")
+ response.body.should == "Request type: PROPFIND"
+ end
+
+ it "returns a Net::HTTPResponse" do
+ @http.propfind("/request", "test=test").should be_kind_of(Net::HTTPResponse)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/proppatch_spec.rb b/spec/ruby/library/net/http/http/proppatch_spec.rb
new file mode 100644
index 0000000000..7a761a9f23
--- /dev/null
+++ b/spec/ruby/library/net/http/http/proppatch_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#proppatch" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends an proppatch request to the passed path and returns the response" do
+ response = @http.proppatch("/request", "test=test")
+ response.body.should == "Request type: PROPPATCH"
+ end
+
+ it "returns a Net::HTTPResponse" do
+ @http.proppatch("/request", "test=test").should be_kind_of(Net::HTTPResponse)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/proxy_address_spec.rb b/spec/ruby/library/net/http/http/proxy_address_spec.rb
new file mode 100644
index 0000000000..8d152b8d44
--- /dev/null
+++ b/spec/ruby/library/net/http/http/proxy_address_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.proxy_address" do
+ describe "when self is no proxy class" do
+ it "returns nil" do
+ Net::HTTP.proxy_address.should be_nil
+ end
+ end
+
+ describe "when self is a proxy class" do
+ it "returns the address for self's proxy connection" do
+ Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks").proxy_address.should == "localhost"
+ end
+ end
+end
+
+describe "Net::HTTP#proxy_address" do
+ describe "when self is no proxy class instance" do
+ it "returns nil" do
+ Net::HTTP.new("localhost", 3333).proxy_address.should be_nil
+ end
+ end
+
+ describe "when self is a proxy class instance" do
+ it "returns the password for self's proxy connection" do
+ http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks")
+ http_with_proxy.new("localhost", 3333).proxy_address.should == "localhost"
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/proxy_class_spec.rb b/spec/ruby/library/net/http/http/proxy_class_spec.rb
new file mode 100644
index 0000000000..2d32a39f01
--- /dev/null
+++ b/spec/ruby/library/net/http/http/proxy_class_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.proxy_class?" do
+ it "returns true if self is a class created with Net::HTTP.Proxy" do
+ Net::HTTP.proxy_class?.should be_false
+ Net::HTTP.Proxy("localhost").proxy_class?.should be_true
+ end
+end
diff --git a/spec/ruby/library/net/http/http/proxy_pass_spec.rb b/spec/ruby/library/net/http/http/proxy_pass_spec.rb
new file mode 100644
index 0000000000..94a0034544
--- /dev/null
+++ b/spec/ruby/library/net/http/http/proxy_pass_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.proxy_pass" do
+ describe "when self is no proxy class" do
+ it "returns nil" do
+ Net::HTTP.proxy_pass.should be_nil
+ end
+ end
+
+ describe "when self is a proxy class" do
+ it "returns nil if no password was set for self's proxy connection" do
+ Net::HTTP.Proxy("localhost").proxy_pass.should be_nil
+ end
+
+ it "returns the password for self's proxy connection" do
+ Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks").proxy_pass.should == "rocks"
+ end
+ end
+end
+
+describe "Net::HTTP#proxy_pass" do
+ describe "when self is no proxy class instance" do
+ it "returns nil" do
+ Net::HTTP.new("localhost", 3333).proxy_pass.should be_nil
+ end
+ end
+
+ describe "when self is a proxy class instance" do
+ it "returns nil if no password was set for self's proxy connection" do
+ Net::HTTP.Proxy("localhost").new("localhost", 3333).proxy_pass.should be_nil
+ end
+
+ it "returns the password for self's proxy connection" do
+ http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks")
+ http_with_proxy.new("localhost", 3333).proxy_pass.should == "rocks"
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/proxy_port_spec.rb b/spec/ruby/library/net/http/http/proxy_port_spec.rb
new file mode 100644
index 0000000000..339f7ee850
--- /dev/null
+++ b/spec/ruby/library/net/http/http/proxy_port_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.proxy_port" do
+ describe "when self is no proxy class" do
+ it "returns nil" do
+ Net::HTTP.proxy_port.should be_nil
+ end
+ end
+
+ describe "when self is a proxy class" do
+ it "returns 80 if no port was set for self's proxy connection" do
+ Net::HTTP.Proxy("localhost").proxy_port.should eql(80)
+ end
+
+ it "returns the port for self's proxy connection" do
+ Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks").proxy_port.should eql(1234)
+ end
+ end
+end
+
+describe "Net::HTTP#proxy_port" do
+ describe "when self is no proxy class instance" do
+ it "returns nil" do
+ Net::HTTP.new("localhost", 3333).proxy_port.should be_nil
+ end
+ end
+
+ describe "when self is a proxy class instance" do
+ it "returns 80 if no port was set for self's proxy connection" do
+ Net::HTTP.Proxy("localhost").new("localhost", 3333).proxy_port.should eql(80)
+ end
+
+ it "returns the port for self's proxy connection" do
+ http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks")
+ http_with_proxy.new("localhost", 3333).proxy_port.should eql(1234)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/proxy_user_spec.rb b/spec/ruby/library/net/http/http/proxy_user_spec.rb
new file mode 100644
index 0000000000..01fda400e9
--- /dev/null
+++ b/spec/ruby/library/net/http/http/proxy_user_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.proxy_user" do
+ describe "when self is no proxy class" do
+ it "returns nil" do
+ Net::HTTP.proxy_user.should be_nil
+ end
+ end
+
+ describe "when self is a proxy class" do
+ it "returns nil if no username was set for self's proxy connection" do
+ Net::HTTP.Proxy("localhost").proxy_user.should be_nil
+ end
+
+ it "returns the username for self's proxy connection" do
+ Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks").proxy_user.should == "rspec"
+ end
+ end
+end
+
+describe "Net::HTTP#proxy_user" do
+ describe "when self is no proxy class instance" do
+ it "returns nil" do
+ Net::HTTP.new("localhost", 3333).proxy_user.should be_nil
+ end
+ end
+
+ describe "when self is a proxy class instance" do
+ it "returns nil if no username was set for self's proxy connection" do
+ Net::HTTP.Proxy("localhost").new("localhost", 3333).proxy_user.should be_nil
+ end
+
+ it "returns the username for self's proxy connection" do
+ http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks")
+ http_with_proxy.new("localhost", 3333).proxy_user.should == "rspec"
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/put2_spec.rb b/spec/ruby/library/net/http/http/put2_spec.rb
new file mode 100644
index 0000000000..99329a5fd9
--- /dev/null
+++ b/spec/ruby/library/net/http/http/put2_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+require_relative 'shared/request_put'
+
+describe "Net::HTTP#put2" do
+ it_behaves_like :net_http_request_put, :put2
+end
diff --git a/spec/ruby/library/net/http/http/put_spec.rb b/spec/ruby/library/net/http/http/put_spec.rb
new file mode 100644
index 0000000000..3ca0d0963e
--- /dev/null
+++ b/spec/ruby/library/net/http/http/put_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#put" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends an put request to the passed path and returns the response" do
+ response = @http.put("/request", "test=test")
+ response.body.should == "Request type: PUT"
+ end
+
+ it "returns a Net::HTTPResponse" do
+ @http.put("/request", "test=test").should be_kind_of(Net::HTTPResponse)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/read_timeout_spec.rb b/spec/ruby/library/net/http/http/read_timeout_spec.rb
new file mode 100644
index 0000000000..e23ee76025
--- /dev/null
+++ b/spec/ruby/library/net/http/http/read_timeout_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP#read_timeout" do
+ it "returns the seconds to wait until reading one block" do
+ net = Net::HTTP.new("localhost")
+ net.read_timeout.should eql(60)
+ net.read_timeout = 10
+ net.read_timeout.should eql(10)
+ end
+end
+
+describe "Net::HTTP#read_timeout=" do
+ it "sets the seconds to wait till the connection is open" do
+ net = Net::HTTP.new("localhost")
+ net.read_timeout = 10
+ net.read_timeout.should eql(10)
+ end
+
+ it "returns the newly set value" do
+ net = Net::HTTP.new("localhost")
+ (net.read_timeout = 10).should eql(10)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/request_get_spec.rb b/spec/ruby/library/net/http/http/request_get_spec.rb
new file mode 100644
index 0000000000..9932ef0beb
--- /dev/null
+++ b/spec/ruby/library/net/http/http/request_get_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+require_relative 'shared/request_get'
+
+describe "Net::HTTP#request_get" do
+ it_behaves_like :net_http_request_get, :get2
+end
diff --git a/spec/ruby/library/net/http/http/request_head_spec.rb b/spec/ruby/library/net/http/http/request_head_spec.rb
new file mode 100644
index 0000000000..788101c951
--- /dev/null
+++ b/spec/ruby/library/net/http/http/request_head_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+require_relative 'shared/request_head'
+
+describe "Net::HTTP#request_head" do
+ it_behaves_like :net_http_request_head, :request_head
+end
diff --git a/spec/ruby/library/net/http/http/request_post_spec.rb b/spec/ruby/library/net/http/http/request_post_spec.rb
new file mode 100644
index 0000000000..7ac67cf95d
--- /dev/null
+++ b/spec/ruby/library/net/http/http/request_post_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+require_relative 'shared/request_post'
+
+describe "Net::HTTP#request_post" do
+ it_behaves_like :net_http_request_post, :request_post
+end
diff --git a/spec/ruby/library/net/http/http/request_put_spec.rb b/spec/ruby/library/net/http/http/request_put_spec.rb
new file mode 100644
index 0000000000..110ac43ca6
--- /dev/null
+++ b/spec/ruby/library/net/http/http/request_put_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+require_relative 'shared/request_put'
+
+describe "Net::HTTP#request_put" do
+ it_behaves_like :net_http_request_put, :request_put
+end
diff --git a/spec/ruby/library/net/http/http/request_spec.rb b/spec/ruby/library/net/http/http/request_spec.rb
new file mode 100644
index 0000000000..e63dde9c8d
--- /dev/null
+++ b/spec/ruby/library/net/http/http/request_spec.rb
@@ -0,0 +1,109 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#request" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ describe "when passed request_object" do
+ it "makes a HTTP Request based on the passed request_object" do
+ response = @http.request(Net::HTTP::Get.new("/request"), "test=test")
+ response.body.should == "Request type: GET"
+
+ response = @http.request(Net::HTTP::Head.new("/request"), "test=test")
+ response.body.should be_nil
+
+ response = @http.request(Net::HTTP::Post.new("/request"), "test=test")
+ response.body.should == "Request type: POST"
+
+ response = @http.request(Net::HTTP::Put.new("/request"), "test=test")
+ response.body.should == "Request type: PUT"
+
+ response = @http.request(Net::HTTP::Proppatch.new("/request"), "test=test")
+ response.body.should == "Request type: PROPPATCH"
+
+ response = @http.request(Net::HTTP::Lock.new("/request"), "test=test")
+ response.body.should == "Request type: LOCK"
+
+ response = @http.request(Net::HTTP::Unlock.new("/request"), "test=test")
+ response.body.should == "Request type: UNLOCK"
+
+ # TODO: Does not work?
+ #response = @http.request(Net::HTTP::Options.new("/request"), "test=test")
+ #response.body.should be_nil
+
+ response = @http.request(Net::HTTP::Propfind.new("/request"), "test=test")
+ response.body.should == "Request type: PROPFIND"
+
+ response = @http.request(Net::HTTP::Delete.new("/request"), "test=test")
+ response.body.should == "Request type: DELETE"
+
+ response = @http.request(Net::HTTP::Move.new("/request"), "test=test")
+ response.body.should == "Request type: MOVE"
+
+ response = @http.request(Net::HTTP::Copy.new("/request"), "test=test")
+ response.body.should == "Request type: COPY"
+
+ response = @http.request(Net::HTTP::Mkcol.new("/request"), "test=test")
+ response.body.should == "Request type: MKCOL"
+
+ response = @http.request(Net::HTTP::Trace.new("/request"), "test=test")
+ response.body.should == "Request type: TRACE"
+ end
+ end
+
+ describe "when passed request_object and request_body" do
+ it "sends the passed request_body when making the HTTP Request" do
+ response = @http.request(Net::HTTP::Get.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ response = @http.request(Net::HTTP::Head.new("/request/body"), "test=test")
+ response.body.should be_nil
+
+ response = @http.request(Net::HTTP::Post.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ response = @http.request(Net::HTTP::Put.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ response = @http.request(Net::HTTP::Proppatch.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ response = @http.request(Net::HTTP::Lock.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ response = @http.request(Net::HTTP::Unlock.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ # TODO: Does not work?
+ #response = @http.request(Net::HTTP::Options.new("/request/body"), "test=test")
+ #response.body.should be_nil
+
+ response = @http.request(Net::HTTP::Propfind.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ response = @http.request(Net::HTTP::Delete.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ response = @http.request(Net::HTTP::Move.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ response = @http.request(Net::HTTP::Copy.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ response = @http.request(Net::HTTP::Mkcol.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+
+ response = @http.request(Net::HTTP::Trace.new("/request/body"), "test=test")
+ response.body.should == "test=test"
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/request_types_spec.rb b/spec/ruby/library/net/http/http/request_types_spec.rb
new file mode 100644
index 0000000000..99d754d3d1
--- /dev/null
+++ b/spec/ruby/library/net/http/http/request_types_spec.rb
@@ -0,0 +1,254 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP::Get" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Get.should < Net::HTTPRequest
+ end
+
+ it "represents the 'GET'-Request-Method" do
+ Net::HTTP::Get::METHOD.should == "GET"
+ end
+
+ it "has no Request Body" do
+ Net::HTTP::Get::REQUEST_HAS_BODY.should be_false
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Get::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Head" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Head.should < Net::HTTPRequest
+ end
+
+ it "represents the 'HEAD'-Request-Method" do
+ Net::HTTP::Head::METHOD.should == "HEAD"
+ end
+
+ it "has no Request Body" do
+ Net::HTTP::Head::REQUEST_HAS_BODY.should be_false
+ end
+
+ it "has no Response Body" do
+ Net::HTTP::Head::RESPONSE_HAS_BODY.should be_false
+ end
+end
+
+describe "Net::HTTP::Post" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Post.should < Net::HTTPRequest
+ end
+
+ it "represents the 'POST'-Request-Method" do
+ Net::HTTP::Post::METHOD.should == "POST"
+ end
+
+ it "has a Request Body" do
+ Net::HTTP::Post::REQUEST_HAS_BODY.should be_true
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Post::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Put" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Put.should < Net::HTTPRequest
+ end
+
+ it "represents the 'PUT'-Request-Method" do
+ Net::HTTP::Put::METHOD.should == "PUT"
+ end
+
+ it "has a Request Body" do
+ Net::HTTP::Put::REQUEST_HAS_BODY.should be_true
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Put::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Delete" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Delete.should < Net::HTTPRequest
+ end
+
+ it "represents the 'DELETE'-Request-Method" do
+ Net::HTTP::Delete::METHOD.should == "DELETE"
+ end
+
+ it "has no Request Body" do
+ Net::HTTP::Delete::REQUEST_HAS_BODY.should be_false
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Delete::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Options" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Options.should < Net::HTTPRequest
+ end
+
+ it "represents the 'OPTIONS'-Request-Method" do
+ Net::HTTP::Options::METHOD.should == "OPTIONS"
+ end
+
+ it "has no Request Body" do
+ Net::HTTP::Options::REQUEST_HAS_BODY.should be_false
+ end
+
+ it "has no Response Body" do
+ Net::HTTP::Options::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Trace" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Trace.should < Net::HTTPRequest
+ end
+
+ it "represents the 'TRACE'-Request-Method" do
+ Net::HTTP::Trace::METHOD.should == "TRACE"
+ end
+
+ it "has no Request Body" do
+ Net::HTTP::Trace::REQUEST_HAS_BODY.should be_false
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Trace::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Propfind" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Propfind.should < Net::HTTPRequest
+ end
+
+ it "represents the 'PROPFIND'-Request-Method" do
+ Net::HTTP::Propfind::METHOD.should == "PROPFIND"
+ end
+
+ it "has a Request Body" do
+ Net::HTTP::Propfind::REQUEST_HAS_BODY.should be_true
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Propfind::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Proppatch" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Proppatch.should < Net::HTTPRequest
+ end
+
+ it "represents the 'PROPPATCH'-Request-Method" do
+ Net::HTTP::Proppatch::METHOD.should == "PROPPATCH"
+ end
+
+ it "has a Request Body" do
+ Net::HTTP::Proppatch::REQUEST_HAS_BODY.should be_true
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Proppatch::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Mkcol" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Mkcol.should < Net::HTTPRequest
+ end
+
+ it "represents the 'MKCOL'-Request-Method" do
+ Net::HTTP::Mkcol::METHOD.should == "MKCOL"
+ end
+
+ it "has a Request Body" do
+ Net::HTTP::Mkcol::REQUEST_HAS_BODY.should be_true
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Mkcol::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Copy" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Copy.should < Net::HTTPRequest
+ end
+
+ it "represents the 'COPY'-Request-Method" do
+ Net::HTTP::Copy::METHOD.should == "COPY"
+ end
+
+ it "has no Request Body" do
+ Net::HTTP::Copy::REQUEST_HAS_BODY.should be_false
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Copy::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Move" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Move.should < Net::HTTPRequest
+ end
+
+ it "represents the 'MOVE'-Request-Method" do
+ Net::HTTP::Move::METHOD.should == "MOVE"
+ end
+
+ it "has no Request Body" do
+ Net::HTTP::Move::REQUEST_HAS_BODY.should be_false
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Move::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Lock" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Lock.should < Net::HTTPRequest
+ end
+
+ it "represents the 'LOCK'-Request-Method" do
+ Net::HTTP::Lock::METHOD.should == "LOCK"
+ end
+
+ it "has a Request Body" do
+ Net::HTTP::Lock::REQUEST_HAS_BODY.should be_true
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Lock::RESPONSE_HAS_BODY.should be_true
+ end
+end
+
+describe "Net::HTTP::Unlock" do
+ it "is a subclass of Net::HTTPRequest" do
+ Net::HTTP::Unlock.should < Net::HTTPRequest
+ end
+
+ it "represents the 'UNLOCK'-Request-Method" do
+ Net::HTTP::Unlock::METHOD.should == "UNLOCK"
+ end
+
+ it "has a Request Body" do
+ Net::HTTP::Unlock::REQUEST_HAS_BODY.should be_true
+ end
+
+ it "has a Response Body" do
+ Net::HTTP::Unlock::RESPONSE_HAS_BODY.should be_true
+ end
+end
diff --git a/spec/ruby/library/net/http/http/send_request_spec.rb b/spec/ruby/library/net/http/http/send_request_spec.rb
new file mode 100644
index 0000000000..83e9448b8b
--- /dev/null
+++ b/spec/ruby/library/net/http/http/send_request_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#send_request" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+
+ # HEAD is special so handled separately
+ @methods = %w[
+ GET POST PUT DELETE
+ OPTIONS
+ PROPFIND PROPPATCH LOCK UNLOCK
+ ]
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ # TODO: Does only work with GET and POST requests
+ describe "when passed type, path" do
+ it "sends a HTTP Request of the passed type to the passed path" do
+ response = @http.send_request("HEAD", "/request")
+ response.body.should be_nil
+
+ (@methods - %w[POST PUT]).each do |method|
+ response = @http.send_request(method, "/request")
+ response.body.should == "Request type: #{method}"
+ end
+ end
+ end
+
+ describe "when passed type, path, body" do
+ it "sends a HTTP Request with the passed body" do
+ response = @http.send_request("HEAD", "/request/body", "test=test")
+ response.body.should be_nil
+
+ @methods.each do |method|
+ response = @http.send_request(method, "/request/body", "test=test")
+ response.body.should == "test=test"
+ end
+ end
+ end
+
+ describe "when passed type, path, body, headers" do
+ it "sends a HTTP Request with the passed headers" do
+ referer = 'https://www.ruby-lang.org/'.freeze
+
+ response = @http.send_request("HEAD", "/request/header", "test=test", "referer" => referer)
+ response.body.should be_nil
+
+ @methods.each do |method|
+ response = @http.send_request(method, "/request/header", "test=test", "referer" => referer)
+ response.body.should include('"Referer"=>"' + referer + '"')
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/set_debug_output_spec.rb b/spec/ruby/library/net/http/http/set_debug_output_spec.rb
new file mode 100644
index 0000000000..94b6f4499d
--- /dev/null
+++ b/spec/ruby/library/net/http/http/set_debug_output_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require "stringio"
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#set_debug_output when passed io" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.new("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sets the passed io as output stream for debugging" do
+ io = StringIO.new
+
+ @http.set_debug_output(io)
+ @http.start
+ io.string.should_not be_empty
+ size = io.string.size
+
+ @http.get("/")
+ io.string.size.should > size
+ end
+
+ it "outputs a warning when the connection has already been started" do
+ @http.start
+ -> { @http.set_debug_output(StringIO.new) }.should complain(/Net::HTTP#set_debug_output called after HTTP started/)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/shared/request_get.rb b/spec/ruby/library/net/http/http/shared/request_get.rb
new file mode 100644
index 0000000000..d25f32049b
--- /dev/null
+++ b/spec/ruby/library/net/http/http/shared/request_get.rb
@@ -0,0 +1,41 @@
+describe :net_http_request_get, shared: true do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ describe "when passed no block" do
+ it "sends a GET request to the passed path and returns the response" do
+ response = @http.send(@method, "/request")
+ response.body.should == "Request type: GET"
+ end
+
+ it "returns a Net::HTTPResponse object" do
+ response = @http.send(@method, "/request")
+ response.should be_kind_of(Net::HTTPResponse)
+ end
+ end
+
+ describe "when passed a block" do
+ it "sends a GET request to the passed path and returns the response" do
+ response = @http.send(@method, "/request") {}
+ response.body.should == "Request type: GET"
+ end
+
+ it "yields the response to the passed block" do
+ @http.send(@method, "/request") do |response|
+ response.body.should == "Request type: GET"
+ end
+ end
+
+ it "returns a Net::HTTPResponse object" do
+ response = @http.send(@method, "/request") {}
+ response.should be_kind_of(Net::HTTPResponse)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/shared/request_head.rb b/spec/ruby/library/net/http/http/shared/request_head.rb
new file mode 100644
index 0000000000..78b555884b
--- /dev/null
+++ b/spec/ruby/library/net/http/http/shared/request_head.rb
@@ -0,0 +1,41 @@
+describe :net_http_request_head, shared: true do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ describe "when passed no block" do
+ it "sends a head request to the passed path and returns the response" do
+ response = @http.send(@method, "/request")
+ response.body.should be_nil
+ end
+
+ it "returns a Net::HTTPResponse object" do
+ response = @http.send(@method, "/request")
+ response.should be_kind_of(Net::HTTPResponse)
+ end
+ end
+
+ describe "when passed a block" do
+ it "sends a head request to the passed path and returns the response" do
+ response = @http.send(@method, "/request") {}
+ response.body.should be_nil
+ end
+
+ it "yields the response to the passed block" do
+ @http.send(@method, "/request") do |response|
+ response.body.should be_nil
+ end
+ end
+
+ it "returns a Net::HTTPResponse object" do
+ response = @http.send(@method, "/request") {}
+ response.should be_kind_of(Net::HTTPResponse)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/shared/request_post.rb b/spec/ruby/library/net/http/http/shared/request_post.rb
new file mode 100644
index 0000000000..e832411c48
--- /dev/null
+++ b/spec/ruby/library/net/http/http/shared/request_post.rb
@@ -0,0 +1,41 @@
+describe :net_http_request_post, shared: true do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ describe "when passed no block" do
+ it "sends a post request to the passed path and returns the response" do
+ response = @http.send(@method, "/request", "test=test")
+ response.body.should == "Request type: POST"
+ end
+
+ it "returns a Net::HTTPResponse object" do
+ response = @http.send(@method, "/request", "test=test")
+ response.should be_kind_of(Net::HTTPResponse)
+ end
+ end
+
+ describe "when passed a block" do
+ it "sends a post request to the passed path and returns the response" do
+ response = @http.send(@method, "/request", "test=test") {}
+ response.body.should == "Request type: POST"
+ end
+
+ it "yields the response to the passed block" do
+ @http.send(@method, "/request", "test=test") do |response|
+ response.body.should == "Request type: POST"
+ end
+ end
+
+ it "returns a Net::HTTPResponse object" do
+ response = @http.send(@method, "/request", "test=test") {}
+ response.should be_kind_of(Net::HTTPResponse)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/shared/request_put.rb b/spec/ruby/library/net/http/http/shared/request_put.rb
new file mode 100644
index 0000000000..3b902f4957
--- /dev/null
+++ b/spec/ruby/library/net/http/http/shared/request_put.rb
@@ -0,0 +1,41 @@
+describe :net_http_request_put, shared: true do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ describe "when passed no block" do
+ it "sends a put request to the passed path and returns the response" do
+ response = @http.send(@method, "/request", "test=test")
+ response.body.should == "Request type: PUT"
+ end
+
+ it "returns a Net::HTTPResponse object" do
+ response = @http.send(@method, "/request", "test=test")
+ response.should be_kind_of(Net::HTTPResponse)
+ end
+ end
+
+ describe "when passed a block" do
+ it "sends a put request to the passed path and returns the response" do
+ response = @http.send(@method, "/request", "test=test") {}
+ response.body.should == "Request type: PUT"
+ end
+
+ it "yields the response to the passed block" do
+ @http.send(@method, "/request", "test=test") do |response|
+ response.body.should == "Request type: PUT"
+ end
+ end
+
+ it "returns a Net::HTTPResponse object" do
+ response = @http.send(@method, "/request", "test=test") {}
+ response.should be_kind_of(Net::HTTPResponse)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/shared/started.rb b/spec/ruby/library/net/http/http/shared/started.rb
new file mode 100644
index 0000000000..9ff6272c31
--- /dev/null
+++ b/spec/ruby/library/net/http/http/shared/started.rb
@@ -0,0 +1,26 @@
+describe :net_http_started_p, shared: true do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.new("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "returns true when self has been started" do
+ @http.start
+ @http.send(@method).should be_true
+ end
+
+ it "returns false when self has not been started yet" do
+ @http.send(@method).should be_false
+ end
+
+ it "returns false when self has been stopped again" do
+ @http.start
+ @http.finish
+ @http.send(@method).should be_false
+ end
+end
diff --git a/spec/ruby/library/net/http/http/shared/version_1_1.rb b/spec/ruby/library/net/http/http/shared/version_1_1.rb
new file mode 100644
index 0000000000..db3d6a986d
--- /dev/null
+++ b/spec/ruby/library/net/http/http/shared/version_1_1.rb
@@ -0,0 +1,6 @@
+describe :net_http_version_1_1_p, shared: true do
+ it "returns the state of net/http 1.1 features" do
+ Net::HTTP.version_1_2
+ Net::HTTP.send(@method).should be_false
+ end
+end
diff --git a/spec/ruby/library/net/http/http/shared/version_1_2.rb b/spec/ruby/library/net/http/http/shared/version_1_2.rb
new file mode 100644
index 0000000000..b044182c60
--- /dev/null
+++ b/spec/ruby/library/net/http/http/shared/version_1_2.rb
@@ -0,0 +1,6 @@
+describe :net_http_version_1_2_p, shared: true do
+ it "returns the state of net/http 1.2 features" do
+ Net::HTTP.version_1_2
+ Net::HTTP.send(@method).should be_true
+ end
+end
diff --git a/spec/ruby/library/net/http/http/socket_type_spec.rb b/spec/ruby/library/net/http/http/socket_type_spec.rb
new file mode 100644
index 0000000000..5c844ddf94
--- /dev/null
+++ b/spec/ruby/library/net/http/http/socket_type_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP.socket_type" do
+ it "returns BufferedIO" do
+ Net::HTTP.socket_type.should == Net::BufferedIO
+ end
+end
diff --git a/spec/ruby/library/net/http/http/start_spec.rb b/spec/ruby/library/net/http/http/start_spec.rb
new file mode 100644
index 0000000000..a2768eed18
--- /dev/null
+++ b/spec/ruby/library/net/http/http/start_spec.rb
@@ -0,0 +1,111 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP.start" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @port = NetHTTPSpecs.port
+ end
+
+ after :each do
+ NetHTTPSpecs.stop_server
+ end
+
+ describe "when not passed a block" do
+ before :each do
+ @http = Net::HTTP.start("localhost", @port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ end
+
+ it "returns a new Net::HTTP object for the passed address and port" do
+ @http.should be_kind_of(Net::HTTP)
+ @http.address.should == "localhost"
+ @http.port.should == @port
+ end
+
+ it "opens the tcp connection" do
+ @http.started?.should be_true
+ end
+ end
+
+ describe "when passed a block" do
+ it "returns the blocks return value" do
+ Net::HTTP.start("localhost", @port) { :test }.should == :test
+ end
+
+ it "yields the new Net::HTTP object to the block" do
+ yielded = false
+ Net::HTTP.start("localhost", @port) do |net|
+ yielded = true
+ net.should be_kind_of(Net::HTTP)
+ end
+ yielded.should be_true
+ end
+
+ it "opens the tcp connection before yielding" do
+ Net::HTTP.start("localhost", @port) { |http| http.started?.should be_true }
+ end
+
+ it "closes the tcp connection after yielding" do
+ net = nil
+ Net::HTTP.start("localhost", @port) { |x| net = x }
+ net.started?.should be_false
+ end
+ end
+end
+
+describe "Net::HTTP#start" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.new("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "returns self" do
+ @http.start.should equal(@http)
+ end
+
+ it "opens the tcp connection" do
+ @http.start
+ @http.started?.should be_true
+ end
+
+ describe "when self has already been started" do
+ it "raises an IOError" do
+ @http.start
+ -> { @http.start }.should raise_error(IOError)
+ end
+ end
+
+ describe "when passed a block" do
+ it "returns the blocks return value" do
+ @http.start { :test }.should == :test
+ end
+
+ it "yields the new Net::HTTP object to the block" do
+ yielded = false
+ @http.start do |http|
+ yielded = true
+ http.should equal(@http)
+ end
+ yielded.should be_true
+ end
+
+ it "opens the tcp connection before yielding" do
+ @http.start { |http| http.started?.should be_true }
+ end
+
+ it "closes the tcp connection after yielding" do
+ @http.start { }
+ @http.started?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/http/started_spec.rb b/spec/ruby/library/net/http/http/started_spec.rb
new file mode 100644
index 0000000000..ea441ed16a
--- /dev/null
+++ b/spec/ruby/library/net/http/http/started_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+require_relative 'shared/started'
+
+describe "Net::HTTP#started?" do
+ it_behaves_like :net_http_started_p, :started?
+end
diff --git a/spec/ruby/library/net/http/http/trace_spec.rb b/spec/ruby/library/net/http/http/trace_spec.rb
new file mode 100644
index 0000000000..94a1bf6655
--- /dev/null
+++ b/spec/ruby/library/net/http/http/trace_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#trace" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends a TRACE request to the passed path and returns the response" do
+ response = @http.trace("/request")
+ response.body.should == "Request type: TRACE"
+ end
+
+ it "returns a Net::HTTPResponse" do
+ @http.trace("/request").should be_kind_of(Net::HTTPResponse)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/unlock_spec.rb b/spec/ruby/library/net/http/http/unlock_spec.rb
new file mode 100644
index 0000000000..a4f1b7a1d1
--- /dev/null
+++ b/spec/ruby/library/net/http/http/unlock_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/http_server'
+
+describe "Net::HTTP#unlock" do
+ before :each do
+ NetHTTPSpecs.start_server
+ @http = Net::HTTP.start("localhost", NetHTTPSpecs.port)
+ end
+
+ after :each do
+ @http.finish if @http.started?
+ NetHTTPSpecs.stop_server
+ end
+
+ it "sends an UNLOCK request to the passed path and returns the response" do
+ response = @http.unlock("/request", "test=test")
+ response.body.should == "Request type: UNLOCK"
+ end
+
+ it "returns a Net::HTTPResponse" do
+ @http.unlock("/request", "test=test").should be_kind_of(Net::HTTPResponse)
+ end
+end
diff --git a/spec/ruby/library/net/http/http/use_ssl_spec.rb b/spec/ruby/library/net/http/http/use_ssl_spec.rb
new file mode 100644
index 0000000000..be1ec7fa25
--- /dev/null
+++ b/spec/ruby/library/net/http/http/use_ssl_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTP#use_ssl?" do
+ it "returns false" do
+ http = Net::HTTP.new("localhost")
+ http.use_ssl?.should be_false
+ end
+end
diff --git a/spec/ruby/library/net/http/http/version_1_1_spec.rb b/spec/ruby/library/net/http/http/version_1_1_spec.rb
new file mode 100644
index 0000000000..1c069e9ea6
--- /dev/null
+++ b/spec/ruby/library/net/http/http/version_1_1_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'shared/version_1_1'
+
+describe "Net::HTTP.version_1_1?" do
+ it_behaves_like :net_http_version_1_1_p, :version_1_1?
+end
diff --git a/spec/ruby/library/net/http/http/version_1_2_spec.rb b/spec/ruby/library/net/http/http/version_1_2_spec.rb
new file mode 100644
index 0000000000..4e601462c9
--- /dev/null
+++ b/spec/ruby/library/net/http/http/version_1_2_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'shared/version_1_2'
+
+describe "Net::HTTP.version_1_2" do
+ it "turns on net/http 1.2 features" do
+ Net::HTTP.version_1_2
+
+ Net::HTTP.version_1_2?.should be_true
+ Net::HTTP.version_1_1?.should be_false
+ end
+
+ it "returns true" do
+ Net::HTTP.version_1_2.should be_true
+ end
+end
+
+describe "Net::HTTP.version_1_2?" do
+ it_behaves_like :net_http_version_1_2_p, :version_1_2?
+end
diff --git a/spec/ruby/library/net/http/httpexceptions/fixtures/classes.rb b/spec/ruby/library/net/http/httpexceptions/fixtures/classes.rb
new file mode 100644
index 0000000000..abe8855eff
--- /dev/null
+++ b/spec/ruby/library/net/http/httpexceptions/fixtures/classes.rb
@@ -0,0 +1,5 @@
+module NetHTTPExceptionsSpecs
+ class Simple < StandardError
+ include Net::HTTPExceptions
+ end
+end
diff --git a/spec/ruby/library/net/http/httpexceptions/initialize_spec.rb b/spec/ruby/library/net/http/httpexceptions/initialize_spec.rb
new file mode 100644
index 0000000000..8e3fd8cc02
--- /dev/null
+++ b/spec/ruby/library/net/http/httpexceptions/initialize_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPExceptions#initialize when passed message, response" do
+ before :each do
+ @exception = NetHTTPExceptionsSpecs::Simple.new("error message", "a http response")
+ end
+
+ it "calls super with the passed message" do
+ @exception.message.should == "error message"
+ end
+
+ it "sets self's response to the passed response" do
+ @exception.response.should == "a http response"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpexceptions/response_spec.rb b/spec/ruby/library/net/http/httpexceptions/response_spec.rb
new file mode 100644
index 0000000000..205b2bc212
--- /dev/null
+++ b/spec/ruby/library/net/http/httpexceptions/response_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPExceptions#response" do
+ it "returns self's response" do
+ exception = NetHTTPExceptionsSpecs::Simple.new("error message", "a http response")
+ exception.response.should == "a http response"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpgenericrequest/body_exist_spec.rb b/spec/ruby/library/net/http/httpgenericrequest/body_exist_spec.rb
new file mode 100644
index 0000000000..7d081939b8
--- /dev/null
+++ b/spec/ruby/library/net/http/httpgenericrequest/body_exist_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPGenericRequest#body_exist?" do
+ it "returns true when the response is expected to have a body" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ request.body_exist?.should be_true
+
+ request = Net::HTTPGenericRequest.new("POST", true, false, "/some/path")
+ request.body_exist?.should be_false
+ end
+
+ describe "when $VERBOSE is true" do
+ it "emits a warning" do
+ request = Net::HTTPGenericRequest.new("POST", true, false, "/some/path")
+ -> {
+ request.body_exist?
+ }.should complain(/body_exist\? is obsolete/, verbose: true)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpgenericrequest/body_spec.rb b/spec/ruby/library/net/http/httpgenericrequest/body_spec.rb
new file mode 100644
index 0000000000..a215895b57
--- /dev/null
+++ b/spec/ruby/library/net/http/httpgenericrequest/body_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require "stringio"
+
+describe "Net::HTTPGenericRequest#body" do
+ it "returns self's request body" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ request.body.should be_nil
+
+ request.body = "Some Content"
+ request.body.should == "Some Content"
+ end
+end
+
+describe "Net::HTTPGenericRequest#body=" do
+ before :each do
+ @request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ end
+
+ it "sets self's body content to the passed String" do
+ @request.body = "Some Content"
+ @request.body.should == "Some Content"
+ end
+
+ it "sets self's body stream to nil" do
+ @request.body_stream = StringIO.new("")
+ @request.body = "Some Content"
+ @request.body_stream.should be_nil
+ end
+end
diff --git a/spec/ruby/library/net/http/httpgenericrequest/body_stream_spec.rb b/spec/ruby/library/net/http/httpgenericrequest/body_stream_spec.rb
new file mode 100644
index 0000000000..c2a60e6836
--- /dev/null
+++ b/spec/ruby/library/net/http/httpgenericrequest/body_stream_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require "stringio"
+
+describe "Net::HTTPGenericRequest#body_stream" do
+ it "returns self's body stream Object" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ request.body_stream.should be_nil
+
+ stream = StringIO.new("test")
+ request.body_stream = stream
+ request.body_stream.should equal(stream)
+ end
+end
+
+describe "Net::HTTPGenericRequest#body_stream=" do
+ before :each do
+ @request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ @stream = StringIO.new("test")
+ end
+
+ it "sets self's body stream to the passed Object" do
+ @request.body_stream = @stream
+ @request.body_stream.should equal(@stream)
+ end
+
+ it "sets self's body to nil" do
+ @request.body = "Some Content"
+ @request.body_stream = @stream
+ @request.body.should be_nil
+ end
+end
diff --git a/spec/ruby/library/net/http/httpgenericrequest/exec_spec.rb b/spec/ruby/library/net/http/httpgenericrequest/exec_spec.rb
new file mode 100644
index 0000000000..da5b1a63be
--- /dev/null
+++ b/spec/ruby/library/net/http/httpgenericrequest/exec_spec.rb
@@ -0,0 +1,131 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require "stringio"
+
+describe "Net::HTTPGenericRequest#exec when passed socket, version, path" do
+ before :each do
+ @socket = StringIO.new("")
+ @buffered_socket = Net::BufferedIO.new(@socket)
+ end
+
+ it "executes the request over the socket to the path using the HTTP version" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+
+ request.exec(@buffered_socket, "1.1", "/some/path")
+ str = @socket.string
+
+ str.should =~ %r[POST /some/path HTTP/1.1\r\n]
+ str.should =~ %r[Accept: \*/\*\r\n]
+ str[-4..-1].should == "\r\n\r\n"
+
+ request = Net::HTTPGenericRequest.new("GET", true, true, "/some/path",
+ "Content-Type" => "text/html")
+
+ request.exec(@buffered_socket, "1.0", "/some/other/path")
+ str = @socket.string
+
+ str.should =~ %r[GET /some/other/path HTTP/1.0\r\n]
+ str.should =~ %r[Accept: \*/\*\r\n]
+ str.should =~ %r[Content-Type: text/html\r\n]
+ str[-4..-1].should == "\r\n\r\n"
+ end
+
+ describe "when a request body is set" do
+ it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ request.body = "Some Content"
+
+ request.exec(@buffered_socket, "1.1", "/some/other/path")
+ str = @socket.string
+
+ str.should =~ %r[POST /some/other/path HTTP/1.1\r\n]
+ str.should =~ %r[Accept: \*/\*\r\n]
+ str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n]
+ str.should =~ %r[Content-Length: 12\r\n]
+ str[-16..-1].should == "\r\n\r\nSome Content"
+ end
+
+ it "correctly sets the 'Content-Length' header and includes the body" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path",
+ "Content-Type" => "text/html")
+ request.body = "Some Content"
+
+ request.exec(@buffered_socket, "1.1", "/some/other/path")
+ str = @socket.string
+
+ str.should =~ %r[POST /some/other/path HTTP/1.1\r\n]
+ str.should =~ %r[Accept: \*/\*\r\n]
+ str.should =~ %r[Content-Type: text/html\r\n]
+ str.should =~ %r[Content-Length: 12\r\n]
+ str[-16..-1].should == "\r\n\r\nSome Content"
+ end
+ end
+
+ describe "when a body stream is set" do
+ it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path",
+ "Content-Length" => "10")
+ request.body_stream = StringIO.new("a" * 20)
+
+ request.exec(@buffered_socket, "1.1", "/some/other/path")
+ str = @socket.string
+
+ str.should =~ %r[POST /some/other/path HTTP/1.1\r\n]
+ str.should =~ %r[Accept: \*/\*\r\n]
+ str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n]
+ str.should =~ %r[Content-Length: 10\r\n]
+ str[-24..-1].should == "\r\n\r\naaaaaaaaaaaaaaaaaaaa"
+ end
+
+ it "sends the whole stream, regardless of the 'Content-Length' header" do
+ request = Net::HTTPGenericRequest.new("POST", true, true,"/some/path",
+ "Content-Type" => "text/html",
+ "Content-Length" => "10")
+ request.body_stream = StringIO.new("a" * 20)
+
+ request.exec(@buffered_socket, "1.1", "/some/other/path")
+ str = @socket.string
+
+ str.should =~ %r[POST /some/other/path HTTP/1.1\r\n]
+ str.should =~ %r[Accept: \*/\*\r\n]
+ str.should =~ %r[Content-Type: text/html\r\n]
+ str.should =~ %r[Content-Length: 10\r\n]
+ str[-24..-1].should == "\r\n\r\naaaaaaaaaaaaaaaaaaaa"
+ end
+
+ it "sends the request in chunks when 'Transfer-Encoding' is set to 'chunked'" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path",
+ "Content-Type" => "text/html",
+ "Transfer-Encoding" => "chunked")
+ datasize = 1024 * 10
+ request.body_stream = StringIO.new("a" * datasize)
+
+ request.exec(@buffered_socket, "1.1", "/some/other/path")
+ str = @socket.string
+
+ str.should =~ %r[POST /some/other/path HTTP/1.1\r\n]
+ str.should =~ %r[Accept: \*/\*\r\n]
+ str.should =~ %r[Content-Type: text/html\r\n]
+ str.should =~ %r[Transfer-Encoding: chunked\r\n]
+ str =~ %r[\r\n\r\n]
+ str = $'
+ while datasize > 0
+ chunk_size_line, str = str.split(/\r\n/, 2)
+ chunk_size = chunk_size_line[/\A[0-9A-Fa-f]+/].to_i(16)
+ str.slice!(0, chunk_size).should == 'a' * chunk_size
+ datasize -= chunk_size
+ str.slice!(0, 2).should == "\r\n"
+ end
+ datasize.should == 0
+ str.should == %"0\r\n\r\n"
+ end
+
+ it "raises an ArgumentError when the 'Content-Length' is not set or 'Transfer-Encoding' is not set to 'chunked'" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path",
+ "Content-Type" => "text/html")
+ request.body_stream = StringIO.new("Some Content")
+
+ -> { request.exec(@buffered_socket, "1.1", "/some/other/path") }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpgenericrequest/inspect_spec.rb b/spec/ruby/library/net/http/httpgenericrequest/inspect_spec.rb
new file mode 100644
index 0000000000..36240949c3
--- /dev/null
+++ b/spec/ruby/library/net/http/httpgenericrequest/inspect_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPGenericRequest#inspect" do
+ it "returns a String representation of self" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ request.inspect.should == "#<Net::HTTPGenericRequest POST>"
+
+ request = Net::HTTPGenericRequest.new("GET", false, true, "/some/path")
+ request.inspect.should == "#<Net::HTTPGenericRequest GET>"
+
+ request = Net::HTTPGenericRequest.new("BLA", true, true, "/some/path")
+ request.inspect.should == "#<Net::HTTPGenericRequest BLA>"
+
+ # Subclasses
+ request = Net::HTTP::Get.new("/some/path")
+ request.inspect.should == "#<Net::HTTP::Get GET>"
+
+ request = Net::HTTP::Post.new("/some/path")
+ request.inspect.should == "#<Net::HTTP::Post POST>"
+
+ request = Net::HTTP::Trace.new("/some/path")
+ request.inspect.should == "#<Net::HTTP::Trace TRACE>"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpgenericrequest/method_spec.rb b/spec/ruby/library/net/http/httpgenericrequest/method_spec.rb
new file mode 100644
index 0000000000..3f7c2cbf2b
--- /dev/null
+++ b/spec/ruby/library/net/http/httpgenericrequest/method_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPGenericRequest#method" do
+ it "returns self's request method" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ request.method.should == "POST"
+
+ request = Net::HTTPGenericRequest.new("GET", false, true, "/some/path")
+ request.method.should == "GET"
+
+ request = Net::HTTPGenericRequest.new("BLA", true, true, "/some/path")
+ request.method.should == "BLA"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpgenericrequest/path_spec.rb b/spec/ruby/library/net/http/httpgenericrequest/path_spec.rb
new file mode 100644
index 0000000000..fc4cf9af53
--- /dev/null
+++ b/spec/ruby/library/net/http/httpgenericrequest/path_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPGenericRequest#path" do
+ it "returns self's request path" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ request.path.should == "/some/path"
+
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/other/path")
+ request.path.should == "/some/other/path"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpgenericrequest/request_body_permitted_spec.rb b/spec/ruby/library/net/http/httpgenericrequest/request_body_permitted_spec.rb
new file mode 100644
index 0000000000..50cd1ff637
--- /dev/null
+++ b/spec/ruby/library/net/http/httpgenericrequest/request_body_permitted_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPGenericRequest#request_body_permitted?" do
+ it "returns true when the request is expected to have a body" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ request.request_body_permitted?.should be_true
+
+ request = Net::HTTPGenericRequest.new("POST", false, true, "/some/path")
+ request.request_body_permitted?.should be_false
+ end
+end
diff --git a/spec/ruby/library/net/http/httpgenericrequest/response_body_permitted_spec.rb b/spec/ruby/library/net/http/httpgenericrequest/response_body_permitted_spec.rb
new file mode 100644
index 0000000000..0c4165d0ab
--- /dev/null
+++ b/spec/ruby/library/net/http/httpgenericrequest/response_body_permitted_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPGenericRequest#response_body_permitted?" do
+ it "returns true when the response is expected to have a body" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ request.response_body_permitted?.should be_true
+
+ request = Net::HTTPGenericRequest.new("POST", true, false, "/some/path")
+ request.response_body_permitted?.should be_false
+ end
+end
diff --git a/spec/ruby/library/net/http/httpgenericrequest/set_body_internal_spec.rb b/spec/ruby/library/net/http/httpgenericrequest/set_body_internal_spec.rb
new file mode 100644
index 0000000000..7494c69173
--- /dev/null
+++ b/spec/ruby/library/net/http/httpgenericrequest/set_body_internal_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPGenericRequest#set_body_internal when passed string" do
+ before :each do
+ @request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ end
+
+ it "sets self's body to the passed string" do
+ @request.set_body_internal("Some Content")
+ @request.body.should == "Some Content"
+ end
+
+ it "raises an ArgumentError when the body or body_stream of self have already been set" do
+ @request.body = "Some Content"
+ -> { @request.set_body_internal("Some other Content") }.should raise_error(ArgumentError)
+
+ @request.body_stream = "Some Content"
+ -> { @request.set_body_internal("Some other Content") }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/add_field_spec.rb b/spec/ruby/library/net/http/httpheader/add_field_spec.rb
new file mode 100644
index 0000000000..882d5ac5bb
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/add_field_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#add_field when passed key, value" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "adds the passed value to the header entry with the passed key" do
+ @headers.add_field("My-Header", "a")
+ @headers.get_fields("My-Header").should == ["a"]
+
+ @headers.add_field("My-Header", "b")
+ @headers.get_fields("My-Header").should == ["a", "b"]
+
+ @headers.add_field("My-Header", "c")
+ @headers.get_fields("My-Header").should == ["a", "b", "c"]
+ end
+
+ it "is case-insensitive" do
+ @headers.add_field("My-Header", "a")
+ @headers.get_fields("My-Header").should == ["a"]
+
+ @headers.add_field("my-header", "b")
+ @headers.get_fields("My-Header").should == ["a", "b"]
+
+ @headers.add_field("MY-HEADER", "c")
+ @headers.get_fields("My-Header").should == ["a", "b", "c"]
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/basic_auth_spec.rb b/spec/ruby/library/net/http/httpheader/basic_auth_spec.rb
new file mode 100644
index 0000000000..fa2a710fe1
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/basic_auth_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#basic_auth when passed account, password" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "sets the 'Authorization' Header entry for basic authorization" do
+ @headers.basic_auth("rubyspec", "rocks")
+ @headers["Authorization"].should == "Basic cnVieXNwZWM6cm9ja3M="
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/canonical_each_spec.rb b/spec/ruby/library/net/http/httpheader/canonical_each_spec.rb
new file mode 100644
index 0000000000..0dddd3049b
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/canonical_each_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/each_capitalized'
+
+describe "Net::HTTPHeader#canonical_each" do
+ it_behaves_like :net_httpheader_each_capitalized, :canonical_each
+end
diff --git a/spec/ruby/library/net/http/httpheader/chunked_spec.rb b/spec/ruby/library/net/http/httpheader/chunked_spec.rb
new file mode 100644
index 0000000000..96b758981b
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/chunked_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#chunked?" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns true if the 'Transfer-Encoding' header entry is set to chunked" do
+ @headers.chunked?.should be_false
+
+ @headers["Transfer-Encoding"] = "bla"
+ @headers.chunked?.should be_false
+
+ @headers["Transfer-Encoding"] = "blachunkedbla"
+ @headers.chunked?.should be_false
+
+ @headers["Transfer-Encoding"] = "chunked"
+ @headers.chunked?.should be_true
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/content_length_spec.rb b/spec/ruby/library/net/http/httpheader/content_length_spec.rb
new file mode 100644
index 0000000000..e344817e82
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/content_length_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#content_length" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns nil if no 'Content-Length' header entry is set" do
+ @headers.content_length.should be_nil
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when the 'Content-Length' header entry has an invalid format" do
+ @headers["Content-Length"] = "invalid"
+ -> { @headers.content_length }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+
+ it "returns the value of the 'Content-Length' header entry as an Integer" do
+ @headers["Content-Length"] = "123"
+ @headers.content_length.should eql(123)
+
+ @headers["Content-Length"] = "123valid"
+ @headers.content_length.should eql(123)
+
+ @headers["Content-Length"] = "valid123"
+ @headers.content_length.should eql(123)
+ end
+end
+
+describe "Net::HTTPHeader#content_length=" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "removes the 'Content-Length' entry if passed false or nil" do
+ @headers["Content-Length"] = "123"
+ @headers.content_length = nil
+ @headers["Content-Length"].should be_nil
+ end
+
+ it "sets the 'Content-Length' entry to the passed value" do
+ @headers.content_length = "123"
+ @headers["Content-Length"].should == "123"
+
+ @headers.content_length = "123valid"
+ @headers["Content-Length"].should == "123"
+ end
+
+ it "sets the 'Content-Length' entry to 0 if the passed value is not valid" do
+ @headers.content_length = "invalid123"
+ @headers["Content-Length"].should == "0"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/content_range_spec.rb b/spec/ruby/library/net/http/httpheader/content_range_spec.rb
new file mode 100644
index 0000000000..ba75f9a9a1
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/content_range_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#content_range" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns a Range object that represents the 'Content-Range' header entry" do
+ @headers["Content-Range"] = "bytes 0-499/1234"
+ @headers.content_range.should == (0..499)
+
+ @headers["Content-Range"] = "bytes 500-1233/1234"
+ @headers.content_range.should == (500..1233)
+ end
+
+ it "returns nil when there is no 'Content-Range' header entry" do
+ @headers.content_range.should be_nil
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when the 'Content-Range' has an invalid format" do
+ @headers["Content-Range"] = "invalid"
+ -> { @headers.content_range }.should raise_error(Net::HTTPHeaderSyntaxError)
+
+ @headers["Content-Range"] = "bytes 123-abc"
+ -> { @headers.content_range }.should raise_error(Net::HTTPHeaderSyntaxError)
+
+ @headers["Content-Range"] = "bytes abc-123"
+ -> { @headers.content_range }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/content_type_spec.rb b/spec/ruby/library/net/http/httpheader/content_type_spec.rb
new file mode 100644
index 0000000000..1f8b4ba326
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/content_type_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/set_content_type'
+
+describe "Net::HTTPHeader#content_type" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns the content type string, as per 'Content-Type' header entry" do
+ @headers["Content-Type"] = "text/html"
+ @headers.content_type.should == "text/html"
+
+ @headers["Content-Type"] = "text/html;charset=utf-8"
+ @headers.content_type.should == "text/html"
+ end
+
+ it "returns nil if the 'Content-Type' header entry does not exist" do
+ @headers.content_type.should be_nil
+ end
+end
+
+describe "Net::HTTPHeader#content_type=" do
+ it_behaves_like :net_httpheader_set_content_type, :content_type=
+end
diff --git a/spec/ruby/library/net/http/httpheader/delete_spec.rb b/spec/ruby/library/net/http/httpheader/delete_spec.rb
new file mode 100644
index 0000000000..603ae198de
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/delete_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#delete when passed key" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "removes the header entry with the passed key" do
+ @headers["My-Header"] = "test"
+ @headers.delete("My-Header")
+
+ @headers["My-Header"].should be_nil
+ @headers.size.should eql(0)
+ end
+
+ it "returns the removed values" do
+ @headers["My-Header"] = "test"
+ @headers.delete("My-Header").should == ["test"]
+ end
+
+ it "is case-insensitive" do
+ @headers["My-Header"] = "test"
+ @headers.delete("my-header")
+
+ @headers["My-Header"].should be_nil
+ @headers.size.should eql(0)
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/each_capitalized_name_spec.rb b/spec/ruby/library/net/http/httpheader/each_capitalized_name_spec.rb
new file mode 100644
index 0000000000..1af2c6939c
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/each_capitalized_name_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#each_capitalized_name" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ @headers["My-Header"] = "test"
+ @headers.add_field("My-Other-Header", "a")
+ @headers.add_field("My-Other-Header", "b")
+ end
+
+ describe "when passed a block" do
+ it "yields each header key to the passed block (keys capitalized)" do
+ res = []
+ @headers.each_capitalized_name do |key|
+ res << key
+ end
+ res.sort.should == ["My-Header", "My-Other-Header"]
+ end
+ end
+
+ describe "when passed no block" do
+ it "returns an Enumerator" do
+ enumerator = @headers.each_capitalized_name
+ enumerator.should be_an_instance_of(Enumerator)
+
+ res = []
+ enumerator.each do |key|
+ res << key
+ end
+ res.sort.should == ["My-Header", "My-Other-Header"]
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/each_capitalized_spec.rb b/spec/ruby/library/net/http/httpheader/each_capitalized_spec.rb
new file mode 100644
index 0000000000..961a2d051f
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/each_capitalized_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/each_capitalized'
+
+describe "Net::HTTPHeader#each_capitalized" do
+ it_behaves_like :net_httpheader_each_capitalized, :each_capitalized
+end
diff --git a/spec/ruby/library/net/http/httpheader/each_header_spec.rb b/spec/ruby/library/net/http/httpheader/each_header_spec.rb
new file mode 100644
index 0000000000..19634a001b
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/each_header_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/each_header'
+
+describe "Net::HTTPHeader#each_header" do
+ it_behaves_like :net_httpheader_each_header, :each_header
+end
diff --git a/spec/ruby/library/net/http/httpheader/each_key_spec.rb b/spec/ruby/library/net/http/httpheader/each_key_spec.rb
new file mode 100644
index 0000000000..ebb17d2eac
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/each_key_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/each_name'
+
+describe "Net::HTTPHeader#each_key" do
+ it_behaves_like :net_httpheader_each_name, :each_key
+end
diff --git a/spec/ruby/library/net/http/httpheader/each_name_spec.rb b/spec/ruby/library/net/http/httpheader/each_name_spec.rb
new file mode 100644
index 0000000000..f4533ebcfb
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/each_name_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/each_name'
+
+describe "Net::HTTPHeader#each_name" do
+ it_behaves_like :net_httpheader_each_name, :each_name
+end
diff --git a/spec/ruby/library/net/http/httpheader/each_spec.rb b/spec/ruby/library/net/http/httpheader/each_spec.rb
new file mode 100644
index 0000000000..7ba8434f75
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/each_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/each_header'
+
+describe "Net::HTTPHeader#each" do
+ it_behaves_like :net_httpheader_each_header, :each
+end
diff --git a/spec/ruby/library/net/http/httpheader/each_value_spec.rb b/spec/ruby/library/net/http/httpheader/each_value_spec.rb
new file mode 100644
index 0000000000..3224de7703
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/each_value_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#each_value" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ @headers["My-Header"] = "test"
+ @headers.add_field("My-Other-Header", "a")
+ @headers.add_field("My-Other-Header", "b")
+ end
+
+ describe "when passed a block" do
+ it "yields each header entry's joined values" do
+ res = []
+ @headers.each_value do |value|
+ res << value
+ end
+ res.sort.should == ["a, b", "test"]
+ end
+ end
+
+ describe "when passed no block" do
+ it "returns an Enumerator" do
+ enumerator = @headers.each_value
+ enumerator.should be_an_instance_of(Enumerator)
+
+ res = []
+ enumerator.each do |key|
+ res << key
+ end
+ res.sort.should == ["a, b", "test"]
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/element_reference_spec.rb b/spec/ruby/library/net/http/httpheader/element_reference_spec.rb
new file mode 100644
index 0000000000..4a35e20d20
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/element_reference_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#[] when passed key" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns the value of the header entry with the passed key" do
+ @headers["My-Header"] = "test"
+ @headers["My-Header"].should == "test"
+ @headers["My-Other-Header"] = "another test"
+ @headers["My-Other-Header"].should == "another test"
+ end
+
+ it "is case-insensitive" do
+ @headers["My-Header"] = "test"
+
+ @headers['My-Header'].should == "test"
+ @headers['my-Header'].should == "test"
+ @headers['My-header'].should == "test"
+ @headers['my-header'].should == "test"
+ @headers['MY-HEADER'].should == "test"
+ end
+
+ it "returns multi-element values joined together" do
+ @headers["My-Header"] = "test"
+ @headers.add_field("My-Header", "another test")
+ @headers.add_field("My-Header", "and one more")
+
+ @headers["My-Header"].should == "test, another test, and one more"
+ end
+
+ it "returns nil for non-existing entries" do
+ @headers["My-Header"].should be_nil
+ @headers["My-Other-Header"].should be_nil
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/element_set_spec.rb b/spec/ruby/library/net/http/httpheader/element_set_spec.rb
new file mode 100644
index 0000000000..e9ad64fafc
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/element_set_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#[]= when passed key, value" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "sets the header entry with the passed key to the passed value" do
+ @headers["My-Header"] = "test"
+ @headers["My-Header"].should == "test"
+
+ @headers["My-Header"] = "overwritten"
+ @headers["My-Header"].should == "overwritten"
+
+ @headers["My-Other-Header"] = "another test"
+ @headers["My-Other-Header"].should == "another test"
+ end
+
+ it "is case-insensitive" do
+ @headers['My-Header'] = "test"
+ @headers['my-Header'] = "another test"
+ @headers['My-header'] = "and one more test"
+ @headers['my-header'] = "and another one"
+ @headers['MY-HEADER'] = "last one"
+
+ @headers["My-Header"].should == "last one"
+ @headers.size.should eql(1)
+ end
+
+ it "removes the header entry with the passed key when the value is false or nil" do
+ @headers['My-Header'] = "test"
+ @headers['My-Header'] = nil
+ @headers['My-Header'].should be_nil
+
+ @headers['My-Header'] = "test"
+ @headers['My-Header'] = false
+ @headers['My-Header'].should be_nil
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/fetch_spec.rb b/spec/ruby/library/net/http/httpheader/fetch_spec.rb
new file mode 100644
index 0000000000..ea15679acb
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/fetch_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#fetch" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ describe "when passed key" do
+ it "returns the header entry for the passed key" do
+ @headers["My-Header"] = "test"
+ @headers.fetch("My-Header").should == "test"
+
+ @headers.add_field("My-Other-Header", "a")
+ @headers.add_field("My-Other-Header", "b")
+ @headers.add_field("My-Other-Header", "c")
+ @headers.fetch("My-Other-Header").should == "a, b, c"
+ end
+
+ it "is case-insensitive" do
+ @headers["My-Header"] = "test"
+ @headers.fetch("my-header").should == "test"
+ @headers.fetch("MY-HEADER").should == "test"
+ end
+
+ it "returns nil when there is no entry for the passed key" do
+ -> { @headers.fetch("my-header") }.should raise_error(IndexError)
+ end
+ end
+
+ describe "when passed key, default" do
+ it "returns the header entry for the passed key" do
+ @headers["My-Header"] = "test"
+ @headers.fetch("My-Header", "bla").should == "test"
+
+ @headers.add_field("My-Other-Header", "a")
+ @headers.add_field("My-Other-Header", "b")
+ @headers.add_field("My-Other-Header", "c")
+ @headers.fetch("My-Other-Header", "bla").should == "a, b, c"
+ end
+
+ # TODO: This raises a NoMethodError: undefined method `join' for "bla":String
+ it "returns the default value when there is no entry for the passed key" do
+ @headers.fetch("My-Header", "bla").should == "bla"
+ end
+ end
+
+ describe "when passed key and block" do
+ it "returns the header entry for the passed key" do
+ @headers["My-Header"] = "test"
+ @headers.fetch("My-Header") {}.should == "test"
+
+ @headers.add_field("My-Other-Header", "a")
+ @headers.add_field("My-Other-Header", "b")
+ @headers.add_field("My-Other-Header", "c")
+ -> {
+ @result = @headers.fetch("My-Other-Header", "bla") {}
+ }.should complain(/block supersedes default value argument/)
+ @result.should == "a, b, c"
+ end
+
+ # TODO: This raises a NoMethodError: undefined method `join' for "redaeh-ym":String
+ it "yieldsand returns the block's return value when there is no entry for the passed key" do
+ @headers.fetch("My-Header") { |key| key.reverse }.should == "redaeh-ym"
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/fixtures/classes.rb b/spec/ruby/library/net/http/httpheader/fixtures/classes.rb
new file mode 100644
index 0000000000..b5ec6abd75
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/fixtures/classes.rb
@@ -0,0 +1,11 @@
+module NetHTTPHeaderSpecs
+ class Example
+ include Net::HTTPHeader
+
+ attr_accessor :body
+
+ def initialize
+ initialize_http_header({})
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/form_data_spec.rb b/spec/ruby/library/net/http/httpheader/form_data_spec.rb
new file mode 100644
index 0000000000..9b29a03159
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/form_data_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/set_form_data'
+
+describe "Net::HTTPHeader#form_data=" do
+ it_behaves_like :net_httpheader_set_form_data, :form_data=
+end
diff --git a/spec/ruby/library/net/http/httpheader/get_fields_spec.rb b/spec/ruby/library/net/http/httpheader/get_fields_spec.rb
new file mode 100644
index 0000000000..1b623bf2a3
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/get_fields_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#get_fields when passed key" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns an Array containing the values of the header entry with the passed key" do
+ @headers["My-Header"] = "a"
+ @headers.get_fields("My-Header").should == ["a"]
+
+ @headers.add_field("My-Header", "b")
+ @headers.get_fields("My-Header").should == ["a", "b"]
+ end
+
+ it "returns a copy of the header entry values" do
+ @headers["My-Header"] = "a"
+
+ @headers.get_fields("My-Header").clear
+ @headers.get_fields("My-Header").should == ["a"]
+
+ @headers.get_fields("My-Header") << "b"
+ @headers.get_fields("My-Header").should == ["a"]
+ end
+
+ it "returns nil for non-existing header entries" do
+ @headers.get_fields("My-Header").should be_nil
+ @headers.get_fields("My-Other-header").should be_nil
+ end
+
+ it "is case-insensitive" do
+ @headers["My-Header"] = "test"
+ @headers.get_fields("My-Header").should == ["test"]
+ @headers.get_fields("my-header").should == ["test"]
+ @headers.get_fields("MY-HEADER").should == ["test"]
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/initialize_http_header_spec.rb b/spec/ruby/library/net/http/httpheader/initialize_http_header_spec.rb
new file mode 100644
index 0000000000..efc5e7d0b2
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/initialize_http_header_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#initialize_http_header when passed Hash" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.allocate
+ end
+
+ it "initializes the HTTP Header using the passed Hash" do
+ @headers.initialize_http_header("My-Header" => "test", "My-Other-Header" => "another test")
+ @headers["My-Header"].should == "test"
+ @headers["My-Other-Header"].should == "another test"
+ end
+
+ it "complains about duplicate keys when in verbose mode" do
+ -> do
+ @headers.initialize_http_header("My-Header" => "test", "my-header" => "another test")
+ end.should complain(/duplicated HTTP header/, verbose: true)
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/key_spec.rb b/spec/ruby/library/net/http/httpheader/key_spec.rb
new file mode 100644
index 0000000000..9099024229
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/key_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#key? when passed key" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns true if the header entry with the passed key exists" do
+ @headers.key?("My-Header").should be_false
+ @headers["My-Header"] = "test"
+ @headers.key?("My-Header").should be_true
+ end
+
+ it "is case-insensitive" do
+ @headers["My-Header"] = "test"
+ @headers.key?("my-header").should be_true
+ @headers.key?("MY-HEADER").should be_true
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/length_spec.rb b/spec/ruby/library/net/http/httpheader/length_spec.rb
new file mode 100644
index 0000000000..0d1da149f4
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/length_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/size'
+
+describe "Net::HTTPHeader#length" do
+ it_behaves_like :net_httpheader_size, :length
+end
diff --git a/spec/ruby/library/net/http/httpheader/main_type_spec.rb b/spec/ruby/library/net/http/httpheader/main_type_spec.rb
new file mode 100644
index 0000000000..3e18de6b5b
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/main_type_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#main_type" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns the 'main-content-type', as per 'Content-Type' header entry" do
+ @headers["Content-Type"] = "text/html"
+ @headers.main_type.should == "text"
+
+ @headers["Content-Type"] = "application/pdf"
+ @headers.main_type.should == "application"
+
+ @headers["Content-Type"] = "text/html;charset=utf-8"
+ @headers.main_type.should == "text"
+ end
+
+ it "returns nil if the 'Content-Type' header entry does not exist" do
+ @headers.main_type.should be_nil
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/proxy_basic_auth_spec.rb b/spec/ruby/library/net/http/httpheader/proxy_basic_auth_spec.rb
new file mode 100644
index 0000000000..8b576ee164
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/proxy_basic_auth_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#proxy_basic_auth when passed account, password" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "sets the 'Proxy-Authorization' Header entry for basic authorization" do
+ @headers.proxy_basic_auth("rubyspec", "rocks")
+ @headers["Proxy-Authorization"].should == "Basic cnVieXNwZWM6cm9ja3M="
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/range_length_spec.rb b/spec/ruby/library/net/http/httpheader/range_length_spec.rb
new file mode 100644
index 0000000000..b8fce4f690
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/range_length_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#range_length" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns the length of the Range represented by the 'Content-Range' header entry" do
+ @headers["Content-Range"] = "bytes 0-499/1234"
+ @headers.range_length.should eql(500)
+
+ @headers["Content-Range"] = "bytes 500-1233/1234"
+ @headers.range_length.should eql(734)
+ end
+
+ it "returns nil when there is no 'Content-Range' header entry" do
+ @headers.range_length.should be_nil
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when the 'Content-Range' has an invalid format" do
+ @headers["Content-Range"] = "invalid"
+ -> { @headers.range_length }.should raise_error(Net::HTTPHeaderSyntaxError)
+
+ @headers["Content-Range"] = "bytes 123-abc"
+ -> { @headers.range_length }.should raise_error(Net::HTTPHeaderSyntaxError)
+
+ @headers["Content-Range"] = "bytes abc-123"
+ -> { @headers.range_length }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/range_spec.rb b/spec/ruby/library/net/http/httpheader/range_spec.rb
new file mode 100644
index 0000000000..005177f6be
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/range_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/set_range'
+
+describe "Net::HTTPHeader#range" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns a Range object that represents the 'Range' header entry" do
+ @headers["Range"] = "bytes=0-499"
+ @headers.range.should == [0..499]
+
+ @headers["Range"] = "bytes=500-1233"
+ @headers.range.should == [500..1233]
+
+ @headers["Range"] = "bytes=10-"
+ @headers.range.should == [10..-1]
+
+ @headers["Range"] = "bytes=-10"
+ @headers.range.should == [-10..-1]
+ end
+
+ it "returns nil when there is no 'Range' header entry" do
+ @headers.range.should be_nil
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when the 'Range' has an invalid format" do
+ @headers["Range"] = "invalid"
+ -> { @headers.range }.should raise_error(Net::HTTPHeaderSyntaxError)
+
+ @headers["Range"] = "bytes 123-abc"
+ -> { @headers.range }.should raise_error(Net::HTTPHeaderSyntaxError)
+
+ @headers["Range"] = "bytes abc-123"
+ -> { @headers.range }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when the 'Range' was not specified" do
+ @headers["Range"] = "bytes=-"
+ -> { @headers.range }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+end
+
+describe "Net::HTTPHeader#range=" do
+ it_behaves_like :net_httpheader_set_range, :range=
+end
diff --git a/spec/ruby/library/net/http/httpheader/set_content_type_spec.rb b/spec/ruby/library/net/http/httpheader/set_content_type_spec.rb
new file mode 100644
index 0000000000..125f7a7e0d
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/set_content_type_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/set_content_type'
+
+describe "Net::HTTPHeader#set_content_type" do
+ it_behaves_like :net_httpheader_set_content_type, :set_content_type
+end
diff --git a/spec/ruby/library/net/http/httpheader/set_form_data_spec.rb b/spec/ruby/library/net/http/httpheader/set_form_data_spec.rb
new file mode 100644
index 0000000000..14c66ae01c
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/set_form_data_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/set_form_data'
+
+describe "Net::HTTPHeader#set_form_data" do
+ it_behaves_like :net_httpheader_set_form_data, :set_form_data
+end
diff --git a/spec/ruby/library/net/http/httpheader/set_range_spec.rb b/spec/ruby/library/net/http/httpheader/set_range_spec.rb
new file mode 100644
index 0000000000..85b9c50422
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/set_range_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/set_range'
+
+describe "Net::HTTPHeader#set_range" do
+ it_behaves_like :net_httpheader_set_range, :set_range
+end
diff --git a/spec/ruby/library/net/http/httpheader/shared/each_capitalized.rb b/spec/ruby/library/net/http/httpheader/shared/each_capitalized.rb
new file mode 100644
index 0000000000..3bac409876
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/shared/each_capitalized.rb
@@ -0,0 +1,31 @@
+describe :net_httpheader_each_capitalized, shared: true do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ @headers["my-header"] = "test"
+ @headers.add_field("my-Other-Header", "a")
+ @headers.add_field("My-Other-header", "b")
+ end
+
+ describe "when passed a block" do
+ it "yields each header entry to the passed block (capitalized keys, values joined)" do
+ res = []
+ @headers.send(@method) do |key, value|
+ res << [key, value]
+ end
+ res.sort.should == [["My-Header", "test"], ["My-Other-Header", "a, b"]]
+ end
+ end
+
+ describe "when passed no block" do
+ it "returns an Enumerator" do
+ enumerator = @headers.send(@method)
+ enumerator.should be_an_instance_of(Enumerator)
+
+ res = []
+ enumerator.each do |*key|
+ res << key
+ end
+ res.sort.should == [["My-Header", "test"], ["My-Other-Header", "a, b"]]
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/shared/each_header.rb b/spec/ruby/library/net/http/httpheader/shared/each_header.rb
new file mode 100644
index 0000000000..6bf3a6ddfe
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/shared/each_header.rb
@@ -0,0 +1,31 @@
+describe :net_httpheader_each_header, shared: true do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ @headers["My-Header"] = "test"
+ @headers.add_field("My-Other-Header", "a")
+ @headers.add_field("My-Other-Header", "b")
+ end
+
+ describe "when passed a block" do
+ it "yields each header entry to the passed block (keys in lower case, values joined)" do
+ res = []
+ @headers.send(@method) do |key, value|
+ res << [key, value]
+ end
+ res.sort.should == [["my-header", "test"], ["my-other-header", "a, b"]]
+ end
+ end
+
+ describe "when passed no block" do
+ it "returns an Enumerator" do
+ enumerator = @headers.send(@method)
+ enumerator.should be_an_instance_of(Enumerator)
+
+ res = []
+ enumerator.each do |*key|
+ res << key
+ end
+ res.sort.should == [["my-header", "test"], ["my-other-header", "a, b"]]
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/shared/each_name.rb b/spec/ruby/library/net/http/httpheader/shared/each_name.rb
new file mode 100644
index 0000000000..efc6a09dfd
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/shared/each_name.rb
@@ -0,0 +1,31 @@
+describe :net_httpheader_each_name, shared: true do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ @headers["My-Header"] = "test"
+ @headers.add_field("My-Other-Header", "a")
+ @headers.add_field("My-Other-Header", "b")
+ end
+
+ describe "when passed a block" do
+ it "yields each header key to the passed block (keys in lower case)" do
+ res = []
+ @headers.send(@method) do |key|
+ res << key
+ end
+ res.sort.should == ["my-header", "my-other-header"]
+ end
+ end
+
+ describe "when passed no block" do
+ it "returns an Enumerator" do
+ enumerator = @headers.send(@method)
+ enumerator.should be_an_instance_of(Enumerator)
+
+ res = []
+ enumerator.each do |key|
+ res << key
+ end
+ res.sort.should == ["my-header", "my-other-header"]
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/shared/set_content_type.rb b/spec/ruby/library/net/http/httpheader/shared/set_content_type.rb
new file mode 100644
index 0000000000..b7359bdca6
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/shared/set_content_type.rb
@@ -0,0 +1,18 @@
+describe :net_httpheader_set_content_type, shared: true do
+ describe "when passed type, params" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "sets the 'Content-Type' header entry based on the passed type and params" do
+ @headers.send(@method, "text/html")
+ @headers["Content-Type"].should == "text/html"
+
+ @headers.send(@method, "text/html", "charset" => "utf-8")
+ @headers["Content-Type"].should == "text/html; charset=utf-8"
+
+ @headers.send(@method, "text/html", "charset" => "utf-8", "rubyspec" => "rocks")
+ @headers["Content-Type"].split(/; /).sort.should == %w[charset=utf-8 rubyspec=rocks text/html]
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/shared/set_form_data.rb b/spec/ruby/library/net/http/httpheader/shared/set_form_data.rb
new file mode 100644
index 0000000000..db20b18803
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/shared/set_form_data.rb
@@ -0,0 +1,27 @@
+describe :net_httpheader_set_form_data, shared: true do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ describe "when passed params" do
+ it "automatically set the 'Content-Type' to 'application/x-www-form-urlencoded'" do
+ @headers.send(@method, "cmd" => "search", "q" => "ruby", "max" => "50")
+ @headers["Content-Type"].should == "application/x-www-form-urlencoded"
+ end
+
+ it "sets self's body based on the passed form parameters" do
+ @headers.send(@method, "cmd" => "search", "q" => "ruby", "max" => "50")
+ @headers.body.split("&").sort.should == ["cmd=search", "max=50", "q=ruby"]
+ end
+ end
+
+ describe "when passed params, separator" do
+ it "sets self's body based on the passed form parameters and the passed separator" do
+ @headers.send(@method, {"cmd" => "search", "q" => "ruby", "max" => "50"}, "&")
+ @headers.body.split("&").sort.should == ["cmd=search", "max=50", "q=ruby"]
+
+ @headers.send(@method, {"cmd" => "search", "q" => "ruby", "max" => "50"}, ";")
+ @headers.body.split(";").sort.should == ["cmd=search", "max=50", "q=ruby"]
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/shared/set_range.rb b/spec/ruby/library/net/http/httpheader/shared/set_range.rb
new file mode 100644
index 0000000000..87f51d46f3
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/shared/set_range.rb
@@ -0,0 +1,89 @@
+describe :net_httpheader_set_range, shared: true do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ describe "when passed nil" do
+ it "returns nil" do
+ @headers.send(@method, nil).should be_nil
+ end
+
+ it "deletes the 'Range' header entry" do
+ @headers["Range"] = "bytes 0-499/1234"
+ @headers.send(@method, nil)
+ @headers["Range"].should be_nil
+ end
+ end
+
+ describe "when passed Numeric" do
+ it "sets the 'Range' header entry based on the passed Numeric" do
+ @headers.send(@method, 10)
+ @headers["Range"].should == "bytes=0-9"
+
+ @headers.send(@method, -10)
+ @headers["Range"].should == "bytes=-10"
+
+ @headers.send(@method, 10.9)
+ @headers["Range"].should == "bytes=0-9"
+ end
+ end
+
+ describe "when passed Range" do
+ it "sets the 'Range' header entry based on the passed Range" do
+ @headers.send(@method, 10..200)
+ @headers["Range"].should == "bytes=10-200"
+
+ @headers.send(@method, 1..5)
+ @headers["Range"].should == "bytes=1-5"
+
+ @headers.send(@method, 1...5)
+ @headers["Range"].should == "bytes=1-4"
+
+ @headers.send(@method, 234..567)
+ @headers["Range"].should == "bytes=234-567"
+
+ @headers.send(@method, -5..-1)
+ @headers["Range"].should == "bytes=-5"
+
+ @headers.send(@method, 1..-1)
+ @headers["Range"].should == "bytes=1-"
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when the first Range element is negative" do
+ -> { @headers.send(@method, -10..5) }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when the last Range element is negative" do
+ -> { @headers.send(@method, 10..-5) }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when the last Range element is smaller than the first" do
+ -> { @headers.send(@method, 10..5) }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+ end
+
+ describe "when passed start, end" do
+ it "sets the 'Range' header entry based on the passed start and length values" do
+ @headers.send(@method, 10, 200)
+ @headers["Range"].should == "bytes=10-209"
+
+ @headers.send(@method, 1, 5)
+ @headers["Range"].should == "bytes=1-5"
+
+ @headers.send(@method, 234, 567)
+ @headers["Range"].should == "bytes=234-800"
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when start is negative" do
+ -> { @headers.send(@method, -10, 5) }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when start + length is negative" do
+ -> { @headers.send(@method, 10, -15) }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+
+ it "raises a Net::HTTPHeaderSyntaxError when length is negative" do
+ -> { @headers.send(@method, 10, -4) }.should raise_error(Net::HTTPHeaderSyntaxError)
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/shared/size.rb b/spec/ruby/library/net/http/httpheader/shared/size.rb
new file mode 100644
index 0000000000..e2b1e4c22b
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/shared/size.rb
@@ -0,0 +1,18 @@
+describe :net_httpheader_size, shared: true do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns the number of header entries in self" do
+ @headers.send(@method).should eql(0)
+
+ @headers["a"] = "b"
+ @headers.send(@method).should eql(1)
+
+ @headers["b"] = "b"
+ @headers.send(@method).should eql(2)
+
+ @headers["c"] = "c"
+ @headers.send(@method).should eql(3)
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/size_spec.rb b/spec/ruby/library/net/http/httpheader/size_spec.rb
new file mode 100644
index 0000000000..a7d78f96e0
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/size_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+require_relative 'shared/size'
+
+describe "Net::HTTPHeader#size" do
+ it_behaves_like :net_httpheader_size, :size
+end
diff --git a/spec/ruby/library/net/http/httpheader/sub_type_spec.rb b/spec/ruby/library/net/http/httpheader/sub_type_spec.rb
new file mode 100644
index 0000000000..3c73ca0027
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/sub_type_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#sub_type" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns the 'sub-content-type', as per 'Content-Type' header entry" do
+ @headers["Content-Type"] = "text/html"
+ @headers.sub_type.should == "html"
+
+ @headers["Content-Type"] = "application/pdf"
+ @headers.sub_type.should == "pdf"
+
+ @headers["Content-Type"] = "text/html;charset=utf-8"
+ @headers.sub_type.should == "html"
+ end
+
+ it "returns nil if no 'sub-content-type' is set" do
+ @headers["Content-Type"] = "text"
+ @headers.sub_type.should be_nil
+
+ @headers["Content-Type"] = "text;charset=utf-8"
+ @headers.sub_type.should be_nil
+ end
+
+ it "returns nil if the 'Content-Type' header entry does not exist" do
+ @headers.sub_type.should be_nil
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/to_hash_spec.rb b/spec/ruby/library/net/http/httpheader/to_hash_spec.rb
new file mode 100644
index 0000000000..8c1d36c30a
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/to_hash_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#to_hash" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns a Hash representing all Header entries (keys in lower case, values as arrays)" do
+ @headers.to_hash.should == {}
+
+ @headers["My-Header"] = "test"
+ @headers.to_hash.should == { "my-header" => ["test"] }
+
+ @headers.add_field("My-Header", "another test")
+ @headers.to_hash.should == { "my-header" => ["test", "another test"] }
+ end
+
+ it "does not allow modifying the headers from the returned hash" do
+ @headers.to_hash["my-header"] = ["test"]
+ @headers.to_hash.should == {}
+ @headers.key?("my-header").should be_false
+ end
+end
diff --git a/spec/ruby/library/net/http/httpheader/type_params_spec.rb b/spec/ruby/library/net/http/httpheader/type_params_spec.rb
new file mode 100644
index 0000000000..e77be7ea85
--- /dev/null
+++ b/spec/ruby/library/net/http/httpheader/type_params_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'fixtures/classes'
+
+describe "Net::HTTPHeader#type_params" do
+ before :each do
+ @headers = NetHTTPHeaderSpecs::Example.new
+ end
+
+ it "returns additional 'Content-Type' information as a Hash" do
+ @headers["Content-Type"] = "text/html;charset=utf-8"
+ @headers.type_params.should == {"charset" => "utf-8"}
+
+ @headers["Content-Type"] = "text/html; charset=utf-8; rubyspec=rocks"
+ @headers.type_params.should == {"charset" => "utf-8", "rubyspec" => "rocks"}
+ end
+
+ it "returns an empty Hash when no additional 'Content-Type' information is set" do
+ @headers.type_params.should == {}
+
+ @headers["Content-Type"] = "text/html"
+ @headers.type_params.should == {}
+ end
+end
diff --git a/spec/ruby/library/net/http/httprequest/initialize_spec.rb b/spec/ruby/library/net/http/httprequest/initialize_spec.rb
new file mode 100644
index 0000000000..88e9fb1c77
--- /dev/null
+++ b/spec/ruby/library/net/http/httprequest/initialize_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+module NetHTTPRequestSpecs
+ class TestRequest < Net::HTTPRequest
+ METHOD = "TEST"
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+ end
+end
+
+describe "Net::HTTPRequest#initialize" do
+ before :each do
+ @req = NetHTTPRequestSpecs::TestRequest.allocate
+ end
+
+ it "uses the METHOD constants to set the request method" do
+ @req.send(:initialize, "/some/path")
+ @req.method.should == "TEST"
+ end
+
+ it "uses the REQUEST_HAS_BODY to set whether the Request has a body or not" do
+ @req.send(:initialize, "/some/path")
+ @req.request_body_permitted?.should be_false
+ end
+
+ it "uses the RESPONSE_HAS_BODY to set whether the Response can have a body or not" do
+ @req.send(:initialize, "/some/path")
+ @req.response_body_permitted?.should be_true
+ end
+
+ describe "when passed path" do
+ it "sets self's path to the passed path" do
+ @req.send(:initialize, "/some/path")
+ @req.path.should == "/some/path"
+ end
+ end
+
+ describe "when passed path, headers" do
+ it "uses the passed headers Hash to initialize self's header entries" do
+ @req.send(:initialize, "/some/path", "Content-Type" => "text/html")
+ @req["Content-Type"].should == "text/html"
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/body_permitted_spec.rb b/spec/ruby/library/net/http/httpresponse/body_permitted_spec.rb
new file mode 100644
index 0000000000..8ade46689f
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/body_permitted_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse.body_permitted?" do
+ it "returns true if this response type can have a response body" do
+ Net::HTTPUnknownResponse.should.body_permitted?
+ Net::HTTPInformation.should_not.body_permitted?
+ Net::HTTPSuccess.should.body_permitted?
+ Net::HTTPRedirection.should.body_permitted?
+ Net::HTTPClientError.should.body_permitted?
+ Net::HTTPServerError.should.body_permitted?
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/body_spec.rb b/spec/ruby/library/net/http/httpresponse/body_spec.rb
new file mode 100644
index 0000000000..79569441f1
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/body_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'shared/body'
+
+describe "Net::HTTPResponse#body" do
+ it_behaves_like :net_httpresponse_body, :body
+end
diff --git a/spec/ruby/library/net/http/httpresponse/code_spec.rb b/spec/ruby/library/net/http/httpresponse/code_spec.rb
new file mode 100644
index 0000000000..114277cb43
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/code_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#code" do
+ it "returns the result code string" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.code.should == "???"
+
+ res = Net::HTTPInformation.new("1.0", "1xx", "test response")
+ res.code.should == "1xx"
+
+ res = Net::HTTPSuccess.new("1.0", "2xx", "test response")
+ res.code.should == "2xx"
+
+ res = Net::HTTPRedirection.new("1.0", "3xx", "test response")
+ res.code.should == "3xx"
+
+ res = Net::HTTPClientError.new("1.0", "4xx", "test response")
+ res.code.should == "4xx"
+
+ res = Net::HTTPServerError.new("1.0", "5xx", "test response")
+ res.code.should == "5xx"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/code_type_spec.rb b/spec/ruby/library/net/http/httpresponse/code_type_spec.rb
new file mode 100644
index 0000000000..fa2d572e9a
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/code_type_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#code_type" do
+ it "returns self's class" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.code_type.should == Net::HTTPUnknownResponse
+
+ res = Net::HTTPInformation.new("1.0", "1xx", "test response")
+ res.code_type.should == Net::HTTPInformation
+
+ res = Net::HTTPSuccess.new("1.0", "2xx", "test response")
+ res.code_type.should == Net::HTTPSuccess
+
+ res = Net::HTTPRedirection.new("1.0", "3xx", "test response")
+ res.code_type.should == Net::HTTPRedirection
+
+ res = Net::HTTPClientError.new("1.0", "4xx", "test response")
+ res.code_type.should == Net::HTTPClientError
+
+ res = Net::HTTPServerError.new("1.0", "5xx", "test response")
+ res.code_type.should == Net::HTTPServerError
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/entity_spec.rb b/spec/ruby/library/net/http/httpresponse/entity_spec.rb
new file mode 100644
index 0000000000..f1639042c1
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/entity_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require_relative 'shared/body'
+
+describe "Net::HTTPResponse#entity" do
+ it_behaves_like :net_httpresponse_body, :entity
+end
diff --git a/spec/ruby/library/net/http/httpresponse/error_spec.rb b/spec/ruby/library/net/http/httpresponse/error_spec.rb
new file mode 100644
index 0000000000..89f4a47f60
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/error_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#error!" do
+ it "raises self's class 'EXCEPTION_TYPE' Exception" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ -> { res.error! }.should raise_error(Net::HTTPError)
+
+ res = Net::HTTPInformation.new("1.0", "1xx", "test response")
+ -> { res.error! }.should raise_error(Net::HTTPError)
+
+ res = Net::HTTPSuccess.new("1.0", "2xx", "test response")
+ -> { res.error! }.should raise_error(Net::HTTPError)
+
+ res = Net::HTTPRedirection.new("1.0", "3xx", "test response")
+ -> { res.error! }.should raise_error(Net::HTTPRetriableError)
+
+ res = Net::HTTPClientError.new("1.0", "4xx", "test response")
+ -> { res.error! }.should raise_error(Net::HTTPClientException)
+
+ res = Net::HTTPServerError.new("1.0", "5xx", "test response")
+ -> { res.error! }.should raise_error(Net::HTTPFatalError)
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/error_type_spec.rb b/spec/ruby/library/net/http/httpresponse/error_type_spec.rb
new file mode 100644
index 0000000000..8885b7706b
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/error_type_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#error_type" do
+ it "returns self's class 'EXCEPTION_TYPE' constant" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.error_type.should == Net::HTTPError
+
+ res = Net::HTTPInformation.new("1.0", "1xx", "test response")
+ res.error_type.should == Net::HTTPError
+
+ res = Net::HTTPSuccess.new("1.0", "2xx", "test response")
+ res.error_type.should == Net::HTTPError
+
+ res = Net::HTTPRedirection.new("1.0", "3xx", "test response")
+ res.error_type.should == Net::HTTPRetriableError
+
+ res = Net::HTTPClientError.new("1.0", "4xx", "test response")
+ res.error_type.should == Net::HTTPClientException
+
+ res = Net::HTTPServerError.new("1.0", "5xx", "test response")
+ res.error_type.should == Net::HTTPFatalError
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/exception_type_spec.rb b/spec/ruby/library/net/http/httpresponse/exception_type_spec.rb
new file mode 100644
index 0000000000..0c9c11291f
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/exception_type_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse.exception_type" do
+ it "returns self's 'EXCEPTION_TYPE' constant" do
+ Net::HTTPUnknownResponse.exception_type.should == Net::HTTPError
+ Net::HTTPInformation.exception_type.should == Net::HTTPError
+ Net::HTTPSuccess.exception_type.should == Net::HTTPError
+ Net::HTTPRedirection.exception_type.should == Net::HTTPRetriableError
+ Net::HTTPClientError.exception_type.should == Net::HTTPClientException
+ Net::HTTPServerError.exception_type.should == Net::HTTPFatalError
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/header_spec.rb b/spec/ruby/library/net/http/httpresponse/header_spec.rb
new file mode 100644
index 0000000000..a9615feda8
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/header_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#header" do
+ it "returns self" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.response.should equal(res)
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/http_version_spec.rb b/spec/ruby/library/net/http/httpresponse/http_version_spec.rb
new file mode 100644
index 0000000000..db85696d77
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/http_version_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#http_version" do
+ it "returns self's http version" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.http_version.should == "1.0"
+
+ res = Net::HTTPUnknownResponse.new("1.1", "???", "test response")
+ res.http_version.should == "1.1"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/initialize_spec.rb b/spec/ruby/library/net/http/httpresponse/initialize_spec.rb
new file mode 100644
index 0000000000..eb77e2e277
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/initialize_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#initialize when passed http_version, response_code, response_message" do
+ it "sets self http_version, response_code and response_message to the passed values" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.http_version.should == "1.0"
+ res.code.should == "???"
+ res.message.should == "test response"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/inspect_spec.rb b/spec/ruby/library/net/http/httpresponse/inspect_spec.rb
new file mode 100644
index 0000000000..1e1a2c7cc7
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/inspect_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require "stringio"
+
+describe "Net::HTTPResponse#inspect" do
+ it "returns a String representation of self" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.inspect.should == "#<Net::HTTPUnknownResponse ??? test response readbody=false>"
+
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ socket = Net::BufferedIO.new(StringIO.new("test body"))
+ res.reading_body(socket, true) {}
+ res.inspect.should == "#<Net::HTTPUnknownResponse ??? test response readbody=true>"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/message_spec.rb b/spec/ruby/library/net/http/httpresponse/message_spec.rb
new file mode 100644
index 0000000000..ae8e3678a1
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/message_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#message" do
+ it "returns self's response message" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.message.should == "test response"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/msg_spec.rb b/spec/ruby/library/net/http/httpresponse/msg_spec.rb
new file mode 100644
index 0000000000..0e5e7eb4aa
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/msg_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#msg" do
+ it "returns self's response message" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.message.should == "test response"
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/read_body_spec.rb b/spec/ruby/library/net/http/httpresponse/read_body_spec.rb
new file mode 100644
index 0000000000..ec9b42f919
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/read_body_spec.rb
@@ -0,0 +1,86 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require 'stringio'
+
+describe "Net::HTTPResponse#read_body" do
+ before :each do
+ @res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ @socket = Net::BufferedIO.new(StringIO.new("test body"))
+ end
+
+ describe "when passed no arguments" do
+ it "returns the read body" do
+ @res.reading_body(@socket, true) do
+ @res.read_body.should == "test body"
+ end
+ end
+
+ it "returns the previously read body if called a second time" do
+ @res.reading_body(@socket, true) do
+ @res.read_body.should equal(@res.read_body)
+ end
+ end
+ end
+
+ describe "when passed a buffer" do
+ it "reads the body to the passed buffer" do
+ @res.reading_body(@socket, true) do
+ buffer = ""
+ @res.read_body(buffer)
+ buffer.should == "test body"
+ end
+ end
+
+ it "returns the passed buffer" do
+ @res.reading_body(@socket, true) do
+ buffer = ""
+ @res.read_body(buffer).should equal(buffer)
+ end
+ end
+
+ it "raises an IOError if called a second time" do
+ @res.reading_body(@socket, true) do
+ @res.read_body("")
+ -> { @res.read_body("") }.should raise_error(IOError)
+ end
+ end
+ end
+
+ describe "when passed a block" do
+ it "reads the body and yields it to the passed block (in chunks)" do
+ @res.reading_body(@socket, true) do
+ yielded = false
+
+ buffer = ""
+ @res.read_body do |body|
+ yielded = true
+ buffer << body
+ end
+
+ yielded.should be_true
+ buffer.should == "test body"
+ end
+ end
+
+ it "returns the ReadAdapter" do
+ @res.reading_body(@socket, true) do
+ @res.read_body { nil }.should be_kind_of(Net::ReadAdapter)
+ end
+ end
+
+ it "raises an IOError if called a second time" do
+ @res.reading_body(@socket, true) do
+ @res.read_body {}
+ -> { @res.read_body {} }.should raise_error(IOError)
+ end
+ end
+ end
+
+ describe "when passed buffer and block" do
+ it "raises an ArgumentError" do
+ @res.reading_body(@socket, true) do
+ -> { @res.read_body("") {} }.should raise_error(ArgumentError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/read_header_spec.rb b/spec/ruby/library/net/http/httpresponse/read_header_spec.rb
new file mode 100644
index 0000000000..6af8c6bd6a
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/read_header_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#read_header" do
+ it "returns self" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.response.should equal(res)
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/read_new_spec.rb b/spec/ruby/library/net/http/httpresponse/read_new_spec.rb
new file mode 100644
index 0000000000..dc2cdc9621
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/read_new_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require 'stringio'
+
+describe "Net::HTTPResponse.read_new" do
+ it "creates a HTTPResponse object based on the response read from the passed socket" do
+ socket = Net::BufferedIO.new(StringIO.new(<<EOS))
+HTTP/1.1 200 OK
+Content-Type: text/html; charset=utf-8
+
+test-body
+EOS
+ response = Net::HTTPResponse.read_new(socket)
+
+ response.should be_kind_of(Net::HTTPOK)
+ response.code.should == "200"
+ response["Content-Type"].should == "text/html; charset=utf-8"
+
+ response.reading_body(socket, true) do
+ response.body.should == "test-body\n"
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/reading_body_spec.rb b/spec/ruby/library/net/http/httpresponse/reading_body_spec.rb
new file mode 100644
index 0000000000..ebdab891e1
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/reading_body_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+require "stringio"
+
+describe "Net::HTTPResponse#reading_body" do
+ before :each do
+ @res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ @socket = Net::BufferedIO.new(StringIO.new("test body"))
+ end
+
+ describe "when body_allowed is true" do
+ it "reads and returns the response body for self from the passed socket" do
+ @res.reading_body(@socket, true) {}.should == "test body"
+ @res.body.should == "test body"
+ end
+
+ it "yields the passed block before reading the body" do
+ yielded = false
+
+ @res.reading_body(@socket, true) do
+ @res.inspect.should == "#<Net::HTTPUnknownResponse ??? test response readbody=false>"
+ yielded = true
+ end
+
+ yielded.should be_true
+ end
+
+ describe "but the response type is not allowed to have a body" do
+ before :each do
+ @res = Net::HTTPInformation.new("1.0", "???", "test response")
+ end
+
+ it "returns nil" do
+ @res.reading_body(@socket, false) {}.should be_nil
+ @res.body.should be_nil
+ end
+
+ it "yields the passed block" do
+ yielded = false
+ @res.reading_body(@socket, true) { yielded = true }
+ yielded.should be_true
+ end
+ end
+ end
+
+ describe "when body_allowed is false" do
+ it "returns nil" do
+ @res.reading_body(@socket, false) {}.should be_nil
+ @res.body.should be_nil
+ end
+
+ it "yields the passed block" do
+ yielded = false
+ @res.reading_body(@socket, true) { yielded = true }
+ yielded.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/response_spec.rb b/spec/ruby/library/net/http/httpresponse/response_spec.rb
new file mode 100644
index 0000000000..f6035f3695
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/response_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#response" do
+ it "returns self" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ res.response.should equal(res)
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/shared/body.rb b/spec/ruby/library/net/http/httpresponse/shared/body.rb
new file mode 100644
index 0000000000..618e3936fb
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/shared/body.rb
@@ -0,0 +1,20 @@
+require 'stringio'
+
+describe :net_httpresponse_body, shared: true do
+ before :each do
+ @res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ @socket = Net::BufferedIO.new(StringIO.new("test body"))
+ end
+
+ it "returns the read body" do
+ @res.reading_body(@socket, true) do
+ @res.send(@method).should == "test body"
+ end
+ end
+
+ it "returns the previously read body if called a second time" do
+ @res.reading_body(@socket, true) do
+ @res.send(@method).should equal(@res.send(@method))
+ end
+ end
+end
diff --git a/spec/ruby/library/net/http/httpresponse/value_spec.rb b/spec/ruby/library/net/http/httpresponse/value_spec.rb
new file mode 100644
index 0000000000..5cd58316ef
--- /dev/null
+++ b/spec/ruby/library/net/http/httpresponse/value_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../../spec_helper'
+require 'net/http'
+
+describe "Net::HTTPResponse#value" do
+ it "raises an HTTP error for non 2xx HTTP Responses" do
+ res = Net::HTTPUnknownResponse.new("1.0", "???", "test response")
+ -> { res.value }.should raise_error(Net::HTTPError)
+
+ res = Net::HTTPInformation.new("1.0", "1xx", "test response")
+ -> { res.value }.should raise_error(Net::HTTPError)
+
+ res = Net::HTTPSuccess.new("1.0", "2xx", "test response")
+ -> { res.value }.should_not raise_error(Net::HTTPError)
+
+ res = Net::HTTPRedirection.new("1.0", "3xx", "test response")
+ -> { res.value }.should raise_error(Net::HTTPRetriableError)
+
+ res = Net::HTTPClientError.new("1.0", "4xx", "test response")
+ -> { res.value }.should raise_error(Net::HTTPClientException)
+
+ res = Net::HTTPServerError.new("1.0", "5xx", "test response")
+ -> { res.value }.should raise_error(Net::HTTPFatalError)
+ end
+end
diff --git a/spec/ruby/library/objectspace/fixtures/trace.rb b/spec/ruby/library/objectspace/fixtures/trace.rb
new file mode 100644
index 0000000000..fd4524b0ba
--- /dev/null
+++ b/spec/ruby/library/objectspace/fixtures/trace.rb
@@ -0,0 +1,5 @@
+require "objspace/trace"
+a = "foo"
+b = "b" + "a" + "r"
+c = 42
+p a, b, c
diff --git a/spec/ruby/library/objectspace/memsize_of_all_spec.rb b/spec/ruby/library/objectspace/memsize_of_all_spec.rb
new file mode 100644
index 0000000000..c5a48165ce
--- /dev/null
+++ b/spec/ruby/library/objectspace/memsize_of_all_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require 'objspace'
+
+describe "ObjectSpace.memsize_of_all" do
+ it "returns a non-zero Integer for all objects" do
+ ObjectSpace.memsize_of_all.should be_kind_of(Integer)
+ ObjectSpace.memsize_of_all.should > 0
+ end
+
+ it "returns a non-zero Integer for Class" do
+ ObjectSpace.memsize_of_all(Class).should be_kind_of(Integer)
+ ObjectSpace.memsize_of_all(Class).should > 0
+ end
+
+ it "increases when a new object is allocated" do
+ c = Class.new
+ before = ObjectSpace.memsize_of_all(c)
+ o = c.new
+ after = ObjectSpace.memsize_of_all(c)
+ after.should > before
+ end
+end
diff --git a/spec/ruby/library/objectspace/memsize_of_spec.rb b/spec/ruby/library/objectspace/memsize_of_spec.rb
new file mode 100644
index 0000000000..eefafbb334
--- /dev/null
+++ b/spec/ruby/library/objectspace/memsize_of_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require 'objspace'
+
+describe "ObjectSpace.memsize_of" do
+ it "returns 0 for true, false and nil" do
+ ObjectSpace.memsize_of(true).should == 0
+ ObjectSpace.memsize_of(false).should == 0
+ ObjectSpace.memsize_of(nil).should == 0
+ end
+
+ it "returns 0 for small Integers" do
+ ObjectSpace.memsize_of(42).should == 0
+ end
+
+ it "returns 0 for literal Symbols" do
+ ObjectSpace.memsize_of(:abc).should == 0
+ end
+
+ it "returns a positive Integer for an Object" do
+ obj = Object.new
+ ObjectSpace.memsize_of(obj).should be_kind_of(Integer)
+ ObjectSpace.memsize_of(obj).should > 0
+ end
+
+ it "is larger if the Object has more instance variables" do
+ obj = Object.new
+ before = ObjectSpace.memsize_of(obj)
+ 100.times do |i|
+ obj.instance_variable_set(:"@foo#{i}", nil)
+ end
+ after = ObjectSpace.memsize_of(obj)
+ after.should > before
+ end
+end
diff --git a/spec/ruby/library/objectspace/reachable_objects_from_spec.rb b/spec/ruby/library/objectspace/reachable_objects_from_spec.rb
new file mode 100644
index 0000000000..7e70bc8569
--- /dev/null
+++ b/spec/ruby/library/objectspace/reachable_objects_from_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require 'objspace'
+
+describe "ObjectSpace.reachable_objects_from" do
+ it "returns nil for true and false" do
+ ObjectSpace.reachable_objects_from(true).should == nil
+ ObjectSpace.reachable_objects_from(false).should == nil
+ end
+
+ it "returns nil for nil" do
+ ObjectSpace.reachable_objects_from(nil).should == nil
+ end
+
+ it "returns nil for small Integers" do
+ ObjectSpace.reachable_objects_from(42).should == nil
+ end
+
+ it "enumerates objects directly reachable from a given object" do
+ ObjectSpace.reachable_objects_from(['a', 'b', 'c']).should include(Array, 'a', 'b', 'c')
+ ObjectSpace.reachable_objects_from(Object.new).should == [Object]
+ end
+
+ it "finds an object stored in an Array" do
+ obj = Object.new
+ ary = [obj]
+ reachable = ObjectSpace.reachable_objects_from(ary)
+ reachable.should include(obj)
+ end
+
+ it "finds an object stored in a copy-on-write Array" do
+ removed = Object.new
+ obj = Object.new
+ ary = [removed, obj]
+ ary.shift
+ reachable = ObjectSpace.reachable_objects_from(ary)
+ reachable.should include(obj)
+ reachable.should_not include(removed)
+ end
+
+ it "finds an object stored in a Queue" do
+ require 'thread'
+ o = Object.new
+ q = Queue.new
+ q << o
+
+ reachable = ObjectSpace.reachable_objects_from(q)
+ reachable = reachable + reachable.flat_map { |r| ObjectSpace.reachable_objects_from(r) }
+ reachable.should include(o)
+ end
+
+ it "finds an object stored in a SizedQueue" do
+ require 'thread'
+ o = Object.new
+ q = SizedQueue.new(3)
+ q << o
+
+ reachable = ObjectSpace.reachable_objects_from(q)
+ reachable = reachable + reachable.flat_map { |r| ObjectSpace.reachable_objects_from(r) }
+ reachable.should include(o)
+ end
+end
diff --git a/spec/ruby/library/objectspace/trace_object_allocations_spec.rb b/spec/ruby/library/objectspace/trace_object_allocations_spec.rb
new file mode 100644
index 0000000000..612430e067
--- /dev/null
+++ b/spec/ruby/library/objectspace/trace_object_allocations_spec.rb
@@ -0,0 +1,149 @@
+require_relative '../../spec_helper'
+require 'objspace'
+
+describe "ObjectSpace.trace_object_allocations" do
+ it "runs a block" do
+ ScratchPad.clear
+ ObjectSpace.trace_object_allocations do
+ ScratchPad.record :a
+ end
+ ScratchPad.recorded.should == :a
+ end
+
+ it "records info for allocation_class_path" do
+ ObjectSpace.trace_object_allocations do
+ o = Object.new
+ ObjectSpace.allocation_class_path(o).should == "Class"
+ a = [1, 2, 3]
+ ObjectSpace.allocation_class_path(a).should == nil
+ end
+ end
+
+ it "records info for allocation_generation" do
+ ObjectSpace.trace_object_allocations do
+ o = Object.new
+ ObjectSpace.allocation_generation(o).should.kind_of?(Integer)
+ a = [1, 2, 3]
+ ObjectSpace.allocation_generation(a).should.kind_of?(Integer)
+ end
+ end
+
+ it "records info for allocation_method_id" do
+ ObjectSpace.trace_object_allocations do
+ o = Object.new
+ ObjectSpace.allocation_method_id(o).should == :new
+ a = [1, 2, 3]
+ ObjectSpace.allocation_method_id(a).should == nil
+ end
+ end
+
+ it "records info for allocation_sourcefile" do
+ ObjectSpace.trace_object_allocations do
+ o = Object.new
+ ObjectSpace.allocation_sourcefile(o).should == __FILE__
+ a = [1, 2, 3]
+ ObjectSpace.allocation_sourcefile(a).should == __FILE__
+ end
+ end
+
+ it "records info for allocation_sourceline" do
+ ObjectSpace.trace_object_allocations do
+ o = Object.new
+ ObjectSpace.allocation_sourceline(o).should == __LINE__ - 1
+ a = [1, 2, 3]
+ ObjectSpace.allocation_sourceline(a).should == __LINE__ - 1
+ end
+ end
+
+ it "can be cleared using trace_object_allocations_clear" do
+ ObjectSpace.trace_object_allocations do
+ o = Object.new
+ ObjectSpace.allocation_class_path(o).should == "Class"
+ ObjectSpace.trace_object_allocations_clear
+ ObjectSpace.allocation_class_path(o).should be_nil
+ end
+ end
+
+ it "does not clears allocation data after returning" do
+ o = nil
+ ObjectSpace.trace_object_allocations do
+ o = Object.new
+ end
+ ObjectSpace.allocation_class_path(o).should == "Class"
+ end
+
+ it "can be used without a block using trace_object_allocations_start and _stop" do
+ ObjectSpace.trace_object_allocations_start
+ begin
+ o = Object.new
+ ObjectSpace.allocation_class_path(o).should == "Class"
+ a = [1, 2, 3]
+ ObjectSpace.allocation_class_path(a).should == nil
+ ensure
+ ObjectSpace.trace_object_allocations_stop
+ end
+ end
+
+ it "does not clears allocation data after trace_object_allocations_stop" do
+ ObjectSpace.trace_object_allocations_start
+ begin
+ o = Object.new
+ ensure
+ ObjectSpace.trace_object_allocations_stop
+ end
+ ObjectSpace.allocation_class_path(o).should == "Class"
+ end
+
+ it "can be nested" do
+ ObjectSpace.trace_object_allocations do
+ ObjectSpace.trace_object_allocations do
+ o = Object.new
+ ObjectSpace.allocation_class_path(o).should == "Class"
+ end
+ end
+ end
+
+ it "can be nested without a block using trace_object_allocations_start and _stop" do
+ ObjectSpace.trace_object_allocations_start
+ begin
+ ObjectSpace.trace_object_allocations_start
+ begin
+ o = Object.new
+ ObjectSpace.allocation_class_path(o).should == "Class"
+ ensure
+ ObjectSpace.trace_object_allocations_stop
+ end
+ ensure
+ ObjectSpace.trace_object_allocations_stop
+ end
+ end
+
+ it "can be nested with more _stop than _start" do
+ ObjectSpace.trace_object_allocations_start
+ begin
+ o = Object.new
+ ObjectSpace.allocation_class_path(o).should == "Class"
+ ObjectSpace.trace_object_allocations_stop
+ ensure
+ ObjectSpace.trace_object_allocations_stop
+ end
+ end
+
+ it "returns nil for class_path, generation, method_id, sourcefile, and sourceline for immutable objects" do
+ ObjectSpace.trace_object_allocations_start
+ begin
+ one = nil
+ two = 42
+ three = :foo
+ [one, two, three].each do |i|
+ ObjectSpace.allocation_class_path(i).should == nil
+ ObjectSpace.allocation_generation(i).should == nil
+ ObjectSpace.allocation_method_id(i).should == nil
+ ObjectSpace.allocation_sourcefile(i).should == nil
+ ObjectSpace.allocation_sourceline(i).should == nil
+ end
+ ensure
+ ObjectSpace.trace_object_allocations_stop
+ end
+ end
+end
diff --git a/spec/ruby/library/objectspace/trace_spec.rb b/spec/ruby/library/objectspace/trace_spec.rb
new file mode 100644
index 0000000000..59952a006c
--- /dev/null
+++ b/spec/ruby/library/objectspace/trace_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.1" do
+ describe 'require "objspace/trace"' do
+ it "shows object allocation sites" do
+ file = fixture(__FILE__ , "trace.rb")
+ ruby_exe(file, args: "2>&1").lines(chomp: true).should == [
+ "objspace/trace is enabled",
+ "\"foo\" @ #{file}:2",
+ "\"bar\" @ #{file}:3",
+ "42"
+ ]
+ end
+ end
+end
diff --git a/spec/ruby/library/observer/add_observer_spec.rb b/spec/ruby/library/observer/add_observer_spec.rb
new file mode 100644
index 0000000000..5217ae6dc4
--- /dev/null
+++ b/spec/ruby/library/observer/add_observer_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Observer#add_observer" do
+
+ before :each do
+ @observable = ObservableSpecs.new
+ @observer = ObserverCallbackSpecs.new
+ end
+
+ it "adds the observer" do
+ @observer.value.should == nil
+ @observable.changed
+ @observable.notify_observers("test")
+ @observer.value.should == nil
+
+ @observable.add_observer(@observer)
+ @observable.changed
+ @observable.notify_observers("test2")
+ @observer.value.should == "test2"
+ end
+
+end
diff --git a/spec/ruby/library/observer/count_observers_spec.rb b/spec/ruby/library/observer/count_observers_spec.rb
new file mode 100644
index 0000000000..c93674196d
--- /dev/null
+++ b/spec/ruby/library/observer/count_observers_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Observer#count_observers" do
+ before :each do
+ @observable = ObservableSpecs.new
+ @observer = ObserverCallbackSpecs.new
+ @observer2 = ObserverCallbackSpecs.new
+ end
+
+ it "returns the number of observers" do
+ @observable.count_observers.should == 0
+ @observable.add_observer(@observer)
+ @observable.count_observers.should == 1
+ @observable.add_observer(@observer2)
+ @observable.count_observers.should == 2
+ end
+
+ it "returns the number of unique observers" do
+ 2.times { @observable.add_observer(@observer) }
+ @observable.count_observers.should == 1
+ end
+end
diff --git a/spec/ruby/library/observer/delete_observer_spec.rb b/spec/ruby/library/observer/delete_observer_spec.rb
new file mode 100644
index 0000000000..52be1a6cba
--- /dev/null
+++ b/spec/ruby/library/observer/delete_observer_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Observer#delete_observer" do
+ before :each do
+ @observable = ObservableSpecs.new
+ @observer = ObserverCallbackSpecs.new
+ end
+
+ it "deletes the observer" do
+ @observable.add_observer(@observer)
+ @observable.delete_observer(@observer)
+
+ @observable.changed
+ @observable.notify_observers("test")
+ @observer.value.should == nil
+ end
+
+end
diff --git a/spec/ruby/library/observer/delete_observers_spec.rb b/spec/ruby/library/observer/delete_observers_spec.rb
new file mode 100644
index 0000000000..186e93a013
--- /dev/null
+++ b/spec/ruby/library/observer/delete_observers_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Observer#delete_observers" do
+ before :each do
+ @observable = ObservableSpecs.new
+ @observer = ObserverCallbackSpecs.new
+ end
+
+ it "deletes the observers" do
+ @observable.add_observer(@observer)
+ @observable.delete_observers
+
+ @observable.changed
+ @observable.notify_observers("test")
+ @observer.value.should == nil
+ end
+
+end
diff --git a/spec/ruby/library/observer/fixtures/classes.rb b/spec/ruby/library/observer/fixtures/classes.rb
new file mode 100644
index 0000000000..70cd1b1be2
--- /dev/null
+++ b/spec/ruby/library/observer/fixtures/classes.rb
@@ -0,0 +1,17 @@
+require 'observer'
+
+class ObserverCallbackSpecs
+ attr_reader :value
+
+ def initialize
+ @value = nil
+ end
+
+ def update(value)
+ @value = value
+ end
+end
+
+class ObservableSpecs
+ include Observable
+end
diff --git a/spec/ruby/library/observer/notify_observers_spec.rb b/spec/ruby/library/observer/notify_observers_spec.rb
new file mode 100644
index 0000000000..31f82e9266
--- /dev/null
+++ b/spec/ruby/library/observer/notify_observers_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Observer#notify_observers" do
+
+ before :each do
+ @observable = ObservableSpecs.new
+ @observer = ObserverCallbackSpecs.new
+ @observable.add_observer(@observer)
+ end
+
+ it "must call changed before notifying observers" do
+ @observer.value.should == nil
+ @observable.notify_observers("test")
+ @observer.value.should == nil
+ end
+
+ it "verifies observer responds to update" do
+ -> {
+ @observable.add_observer(@observable)
+ }.should raise_error(NoMethodError)
+ end
+
+ it "receives the callback" do
+ @observer.value.should == nil
+ @observable.changed
+ @observable.notify_observers("test")
+ @observer.value.should == "test"
+ end
+
+end
diff --git a/spec/ruby/library/open3/capture2_spec.rb b/spec/ruby/library/open3/capture2_spec.rb
new file mode 100644
index 0000000000..f707281d7f
--- /dev/null
+++ b/spec/ruby/library/open3/capture2_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.capture2" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/open3/capture2e_spec.rb b/spec/ruby/library/open3/capture2e_spec.rb
new file mode 100644
index 0000000000..7dd42f3491
--- /dev/null
+++ b/spec/ruby/library/open3/capture2e_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.capture2e" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/open3/capture3_spec.rb b/spec/ruby/library/open3/capture3_spec.rb
new file mode 100644
index 0000000000..55c858c03f
--- /dev/null
+++ b/spec/ruby/library/open3/capture3_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.capture3" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/open3/pipeline_r_spec.rb b/spec/ruby/library/open3/pipeline_r_spec.rb
new file mode 100644
index 0000000000..e1b476f856
--- /dev/null
+++ b/spec/ruby/library/open3/pipeline_r_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.pipeline_r" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/open3/pipeline_rw_spec.rb b/spec/ruby/library/open3/pipeline_rw_spec.rb
new file mode 100644
index 0000000000..8d889a200a
--- /dev/null
+++ b/spec/ruby/library/open3/pipeline_rw_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.pipeline_rw" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/open3/pipeline_spec.rb b/spec/ruby/library/open3/pipeline_spec.rb
new file mode 100644
index 0000000000..5dc628dcaf
--- /dev/null
+++ b/spec/ruby/library/open3/pipeline_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.pipeline" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/open3/pipeline_start_spec.rb b/spec/ruby/library/open3/pipeline_start_spec.rb
new file mode 100644
index 0000000000..af426807fe
--- /dev/null
+++ b/spec/ruby/library/open3/pipeline_start_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.pipeline_start" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/open3/pipeline_w_spec.rb b/spec/ruby/library/open3/pipeline_w_spec.rb
new file mode 100644
index 0000000000..0c2a3ca4e7
--- /dev/null
+++ b/spec/ruby/library/open3/pipeline_w_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.pipeline_w" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/open3/popen2_spec.rb b/spec/ruby/library/open3/popen2_spec.rb
new file mode 100644
index 0000000000..a1a251660a
--- /dev/null
+++ b/spec/ruby/library/open3/popen2_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.popen2" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/open3/popen2e_spec.rb b/spec/ruby/library/open3/popen2e_spec.rb
new file mode 100644
index 0000000000..e65607160c
--- /dev/null
+++ b/spec/ruby/library/open3/popen2e_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.popen2e" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/open3/popen3_spec.rb b/spec/ruby/library/open3/popen3_spec.rb
new file mode 100644
index 0000000000..d3103ad3cb
--- /dev/null
+++ b/spec/ruby/library/open3/popen3_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require 'open3'
+
+describe "Open3.popen3" do
+ it "returns in, out, err and a thread waiting the process" do
+ stdin, out, err, waiter = Open3.popen3(ruby_cmd("print :foo"))
+ begin
+ stdin.should be_kind_of IO
+ out.should be_kind_of IO
+ err.should be_kind_of IO
+ waiter.should be_kind_of Thread
+
+ out.read.should == "foo"
+ ensure
+ stdin.close
+ out.close
+ err.close
+ waiter.join
+ end
+ end
+
+ it "executes a process with a pipe to read stdout" do
+ Open3.popen3(ruby_cmd("print :foo")) do |stdin, out, err|
+ out.read.should == "foo"
+ end
+ end
+
+ it "executes a process with a pipe to read stderr" do
+ Open3.popen3(ruby_cmd("STDERR.print :foo")) do |stdin, out, err|
+ err.read.should == "foo"
+ end
+ end
+
+ it "executes a process with a pipe to write stdin" do
+ Open3.popen3(ruby_cmd("print STDIN.read")) do |stdin, out, err|
+ stdin.write("foo")
+ stdin.close
+ out.read.should == "foo"
+ end
+ end
+end
diff --git a/spec/ruby/library/openssl/cipher_spec.rb b/spec/ruby/library/openssl/cipher_spec.rb
new file mode 100644
index 0000000000..f66f25f9c6
--- /dev/null
+++ b/spec/ruby/library/openssl/cipher_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'shared/constants'
+require 'openssl'
+
+describe "OpenSSL::Cipher's CipherError" do
+ it "exists under OpenSSL::Cipher namespace" do
+ OpenSSL::Cipher.should have_constant :CipherError
+ end
+end
diff --git a/spec/ruby/library/openssl/config/freeze_spec.rb b/spec/ruby/library/openssl/config/freeze_spec.rb
new file mode 100644
index 0000000000..c814341b86
--- /dev/null
+++ b/spec/ruby/library/openssl/config/freeze_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require_relative '../shared/constants'
+
+require 'openssl'
+
+version_is(OpenSSL::VERSION, ""..."2.2") do
+ describe "OpenSSL::Config#freeze" do
+ it "needs to be reviewed for completeness"
+
+ it "freezes" do
+ c = OpenSSL::Config.new
+ -> {
+ c['foo'] = [ ['key', 'value'] ]
+ }.should_not raise_error
+ c.freeze
+ c.frozen?.should be_true
+ -> {
+ c['foo'] = [ ['key', 'value'] ]
+ }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/openssl/digest_spec.rb b/spec/ruby/library/openssl/digest_spec.rb
new file mode 100644
index 0000000000..b8e82d073f
--- /dev/null
+++ b/spec/ruby/library/openssl/digest_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+require_relative '../../library/digest/sha1/shared/constants'
+require_relative '../../library/digest/sha256/shared/constants'
+require_relative '../../library/digest/sha384/shared/constants'
+require_relative '../../library/digest/sha512/shared/constants'
+require 'openssl'
+
+describe "OpenSSL::Digest" do
+
+ describe ".digest" do
+ it "returns a SHA1 digest" do
+ OpenSSL::Digest.digest('sha1', SHA1Constants::Contents).should == SHA1Constants::Digest
+ end
+
+ it "returns a SHA256 digest" do
+ OpenSSL::Digest.digest('sha256', SHA256Constants::Contents).should == SHA256Constants::Digest
+ end
+
+ it "returns a SHA384 digest" do
+ OpenSSL::Digest.digest('sha384', SHA384Constants::Contents).should == SHA384Constants::Digest
+ end
+
+ it "returns a SHA512 digest" do
+ OpenSSL::Digest.digest('sha512', SHA512Constants::Contents).should == SHA512Constants::Digest
+ end
+ end
+
+ describe ".hexdigest" do
+ it "returns a SHA1 hexdigest" do
+ OpenSSL::Digest.hexdigest('sha1', SHA1Constants::Contents).should == SHA1Constants::Hexdigest
+ end
+
+ it "returns a SHA256 hexdigest" do
+ OpenSSL::Digest.hexdigest('sha256', SHA256Constants::Contents).should == SHA256Constants::Hexdigest
+ end
+
+ it "returns a SHA384 hexdigest" do
+ OpenSSL::Digest.hexdigest('sha384', SHA384Constants::Contents).should == SHA384Constants::Hexdigest
+ end
+
+ it "returns a SHA512 hexdigest" do
+ OpenSSL::Digest.hexdigest('sha512', SHA512Constants::Contents).should == SHA512Constants::Hexdigest
+ end
+ end
+
+ describe ".base64digest" do
+ it "returns a SHA1 base64digest" do
+ OpenSSL::Digest.base64digest('sha1', SHA1Constants::Contents).should == SHA1Constants::Base64digest
+ end
+
+ it "returns a SHA256 base64digest" do
+ OpenSSL::Digest.base64digest('sha256', SHA256Constants::Contents).should == SHA256Constants::Base64digest
+ end
+
+ it "returns a SHA384 base64digest" do
+ OpenSSL::Digest.base64digest('sha384', SHA384Constants::Contents).should == SHA384Constants::Base64digest
+ end
+
+ it "returns a SHA512 base64digest" do
+ OpenSSL::Digest.base64digest('sha512', SHA512Constants::Contents).should == SHA512Constants::Base64digest
+ end
+ end
+end
diff --git a/spec/ruby/library/openssl/hmac/digest_spec.rb b/spec/ruby/library/openssl/hmac/digest_spec.rb
new file mode 100644
index 0000000000..03ed136e64
--- /dev/null
+++ b/spec/ruby/library/openssl/hmac/digest_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+require_relative '../shared/constants'
+require 'openssl'
+
+describe "OpenSSL::HMAC.digest" do
+ it "returns an SHA1 digest" do
+ cur_digest = OpenSSL::Digest.new("SHA1")
+ cur_digest.digest.should == HMACConstants::BlankSHA1Digest
+ digest = OpenSSL::HMAC.digest(cur_digest,
+ HMACConstants::Key,
+ HMACConstants::Contents)
+ digest.should == HMACConstants::SHA1Digest
+ end
+end
+
+# Should add in similar specs for MD5, RIPEMD160, and SHA256
diff --git a/spec/ruby/library/openssl/hmac/hexdigest_spec.rb b/spec/ruby/library/openssl/hmac/hexdigest_spec.rb
new file mode 100644
index 0000000000..3508c1bbd7
--- /dev/null
+++ b/spec/ruby/library/openssl/hmac/hexdigest_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+require_relative '../shared/constants'
+require 'openssl'
+
+describe "OpenSSL::HMAC.hexdigest" do
+ it "returns an SHA1 hex digest" do
+ cur_digest = OpenSSL::Digest.new("SHA1")
+ cur_digest.hexdigest.should == HMACConstants::BlankSHA1HexDigest
+ hexdigest = OpenSSL::HMAC.hexdigest(cur_digest,
+ HMACConstants::Key,
+ HMACConstants::Contents)
+ hexdigest.should == HMACConstants::SHA1Hexdigest
+ end
+end
+
+# Should add in similar specs for MD5, RIPEMD160, and SHA256
diff --git a/spec/ruby/library/openssl/random/pseudo_bytes_spec.rb b/spec/ruby/library/openssl/random/pseudo_bytes_spec.rb
new file mode 100644
index 0000000000..c5e450ce3d
--- /dev/null
+++ b/spec/ruby/library/openssl/random/pseudo_bytes_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/random_bytes'
+
+if defined?(OpenSSL::Random.pseudo_bytes)
+ describe "OpenSSL::Random.pseudo_bytes" do
+ it_behaves_like :openssl_random_bytes, :pseudo_bytes
+ end
+end
diff --git a/spec/ruby/library/openssl/random/random_bytes_spec.rb b/spec/ruby/library/openssl/random/random_bytes_spec.rb
new file mode 100644
index 0000000000..2678885da4
--- /dev/null
+++ b/spec/ruby/library/openssl/random/random_bytes_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/random_bytes'
+
+describe "OpenSSL::Random.random_bytes" do
+ it_behaves_like :openssl_random_bytes, :random_bytes
+end
diff --git a/spec/ruby/library/openssl/random/shared/random_bytes.rb b/spec/ruby/library/openssl/random/shared/random_bytes.rb
new file mode 100644
index 0000000000..037f10d409
--- /dev/null
+++ b/spec/ruby/library/openssl/random/shared/random_bytes.rb
@@ -0,0 +1,29 @@
+require_relative '../../../../spec_helper'
+require 'openssl'
+
+describe :openssl_random_bytes, shared: true do |cmd|
+ it "generates a random binary string of specified length" do
+ (1..64).each do |idx|
+ bytes = OpenSSL::Random.send(@method, idx)
+ bytes.should be_kind_of(String)
+ bytes.length.should == idx
+ end
+ end
+
+ it "generates different binary strings with subsequent invocations" do
+ # quick and dirty check, but good enough
+ values = []
+ 256.times do
+ val = OpenSSL::Random.send(@method, 16)
+ # make sure the random bytes are not repeating
+ values.include?(val).should == false
+ values << val
+ end
+ end
+
+ it "raises ArgumentError on negative arguments" do
+ -> {
+ OpenSSL::Random.send(@method, -1)
+ }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/openssl/shared/constants.rb b/spec/ruby/library/openssl/shared/constants.rb
new file mode 100644
index 0000000000..0bed4156a1
--- /dev/null
+++ b/spec/ruby/library/openssl/shared/constants.rb
@@ -0,0 +1,11 @@
+# -*- encoding: binary -*-
+module HMACConstants
+
+ Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
+ Key = 'sekrit'
+
+ BlankSHA1Digest = "\3329\243\356^kK\r2U\277\357\225`\030\220\257\330\a\t"
+ SHA1Digest = "\236\022\323\341\037\236\262n\344\t\372:\004J\242\330\257\270\363\264"
+ BlankSHA1HexDigest = "da39a3ee5e6b4b0d3255bfef95601890afd80709"
+ SHA1Hexdigest = "9e12d3e11f9eb26ee409fa3a044aa2d8afb8f3b4"
+end
diff --git a/spec/ruby/library/openssl/x509/name/parse_spec.rb b/spec/ruby/library/openssl/x509/name/parse_spec.rb
new file mode 100644
index 0000000000..6624161d83
--- /dev/null
+++ b/spec/ruby/library/openssl/x509/name/parse_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../../../spec_helper'
+require 'openssl'
+
+describe "OpenSSL::X509::Name.parse" do
+ it "parses a /-delimited string of key-value pairs into a Name" do
+ dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org"
+ name = OpenSSL::X509::Name.parse(dn)
+
+ name.to_s.should == dn
+
+ ary = name.to_a
+
+ ary[0][0].should == "DC"
+ ary[1][0].should == "DC"
+ ary[2][0].should == "CN"
+ ary[0][1].should == "org"
+ ary[1][1].should == "ruby-lang"
+ ary[2][1].should == "www.ruby-lang.org"
+ ary[0][2].should == OpenSSL::ASN1::IA5STRING
+ ary[1][2].should == OpenSSL::ASN1::IA5STRING
+ ary[2][2].should == OpenSSL::ASN1::UTF8STRING
+ end
+
+ it "parses a comma-delimited string of key-value pairs into a name" do
+ dn = "DC=org, DC=ruby-lang, CN=www.ruby-lang.org"
+ name = OpenSSL::X509::Name.parse(dn)
+
+ name.to_s.should == "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org"
+
+ ary = name.to_a
+
+ ary[0][1].should == "org"
+ ary[1][1].should == "ruby-lang"
+ ary[2][1].should == "www.ruby-lang.org"
+ end
+
+ it "raises TypeError if the given string contains no key/value pairs" do
+ -> do
+ OpenSSL::X509::Name.parse("hello")
+ end.should raise_error(TypeError)
+ end
+
+ it "raises OpenSSL::X509::NameError if the given string contains invalid keys" do
+ -> do
+ OpenSSL::X509::Name.parse("hello=goodbye")
+ end.should raise_error(OpenSSL::X509::NameError)
+ end
+end
diff --git a/spec/ruby/library/openssl/x509/name/verify_spec.rb b/spec/ruby/library/openssl/x509/name/verify_spec.rb
new file mode 100644
index 0000000000..6dcfc99466
--- /dev/null
+++ b/spec/ruby/library/openssl/x509/name/verify_spec.rb
@@ -0,0 +1,78 @@
+require_relative '../../../../spec_helper'
+require 'openssl'
+
+describe "OpenSSL::X509::Name.verify" do
+ it "returns true for valid certificate" do
+ key = OpenSSL::PKey::RSA.new 2048
+ cert = OpenSSL::X509::Certificate.new
+ cert.version = 2
+ cert.serial = 1
+ cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby CA"
+ cert.issuer = cert.subject
+ cert.public_key = key.public_key
+ cert.not_before = Time.now - 10
+ cert.not_after = cert.not_before + 365 * 24 * 60 * 60
+ cert.sign key, OpenSSL::Digest.new('SHA256')
+ store = OpenSSL::X509::Store.new
+ store.add_cert(cert)
+ [store.verify(cert), store.error, store.error_string].should == [true, 0, "ok"]
+ end
+
+ it "returns false for an expired certificate" do
+ key = OpenSSL::PKey::RSA.new 2048
+ cert = OpenSSL::X509::Certificate.new
+ cert.version = 2
+ cert.serial = 1
+ cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby CA"
+ cert.issuer = cert.subject
+ cert.public_key = key.public_key
+ cert.not_before = Time.now - 10
+ cert.not_after = Time.now - 5
+ cert.sign key, OpenSSL::Digest.new('SHA256')
+ store = OpenSSL::X509::Store.new
+ store.add_cert(cert)
+ store.verify(cert).should == false
+ end
+
+ it "returns false for an expired root certificate" do
+ root_key = OpenSSL::PKey::RSA.new 2048
+ root_cert = OpenSSL::X509::Certificate.new
+ root_cert.version = 2
+ root_cert.serial = 1
+ root_cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby CA"
+ root_cert.issuer = root_cert.subject
+ root_cert.public_key = root_key.public_key
+ root_cert.not_before = Time.now - 10
+ root_cert.not_after = Time.now - 5
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.subject_certificate = root_cert
+ ef.issuer_certificate = root_cert
+ root_cert.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true))
+ root_cert.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true))
+ root_cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
+ root_cert.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false))
+ root_cert.sign(root_key, OpenSSL::Digest.new('SHA256'))
+
+
+ key = OpenSSL::PKey::RSA.new 2048
+ cert = OpenSSL::X509::Certificate.new
+ cert.version = 2
+ cert.serial = 2
+ cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby certificate"
+ cert.issuer = root_cert.subject
+ cert.public_key = key.public_key
+ cert.not_before = Time.now
+ cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.subject_certificate = cert
+ ef.issuer_certificate = root_cert
+ cert.add_extension(ef.create_extension("keyUsage","digitalSignature", true))
+ cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
+ cert.sign(root_key, OpenSSL::Digest.new('SHA256'))
+
+ store = OpenSSL::X509::Store.new
+ store.add_cert(root_cert)
+ store.add_cert(cert)
+ store.verify(cert).should == false
+ end
+end
diff --git a/spec/ruby/library/openstruct/delete_field_spec.rb b/spec/ruby/library/openstruct/delete_field_spec.rb
new file mode 100644
index 0000000000..9ac80196cc
--- /dev/null
+++ b/spec/ruby/library/openstruct/delete_field_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require 'ostruct'
+
+describe "OpenStruct#delete_field" do
+ before :each do
+ @os = OpenStruct.new(name: "John Smith", age: 70, pension: 300)
+ end
+
+ it "removes the named field from self's method/value table" do
+ @os.delete_field(:name)
+ @os[:name].should be_nil
+ end
+
+ it "does remove the accessor methods" do
+ @os.delete_field(:name)
+ @os.respond_to?(:name).should be_false
+ @os.respond_to?(:name=).should be_false
+ end
+end
diff --git a/spec/ruby/library/openstruct/element_reference_spec.rb b/spec/ruby/library/openstruct/element_reference_spec.rb
new file mode 100644
index 0000000000..c425991b0f
--- /dev/null
+++ b/spec/ruby/library/openstruct/element_reference_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require "ostruct"
+
+describe "OpenStruct#[]" do
+ before :each do
+ @os = OpenStruct.new
+ end
+
+ it "returns the associated value" do
+ @os.foo = 42
+ @os[:foo].should == 42
+ end
+end
diff --git a/spec/ruby/library/openstruct/element_set_spec.rb b/spec/ruby/library/openstruct/element_set_spec.rb
new file mode 100644
index 0000000000..eeb5a8b318
--- /dev/null
+++ b/spec/ruby/library/openstruct/element_set_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require "ostruct"
+
+describe "OpenStruct#[]=" do
+ before :each do
+ @os = OpenStruct.new
+ end
+
+ it "sets the associated value" do
+ @os[:foo] = 42
+ @os.foo.should == 42
+ end
+end
diff --git a/spec/ruby/library/openstruct/equal_value_spec.rb b/spec/ruby/library/openstruct/equal_value_spec.rb
new file mode 100644
index 0000000000..103ac13588
--- /dev/null
+++ b/spec/ruby/library/openstruct/equal_value_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require "ostruct"
+require_relative 'fixtures/classes'
+
+describe "OpenStruct#==" do
+ before :each do
+ @os = OpenStruct.new(name: "John")
+ end
+
+ it "returns false when the passed argument is no OpenStruct" do
+ (@os == Object.new).should be_false
+ (@os == "Test").should be_false
+ (@os == 10).should be_false
+ (@os == :sym).should be_false
+ end
+
+ it "returns true when self and other are equal method/value wise" do
+ (@os == @os).should be_true
+ (@os == OpenStruct.new(name: "John")).should be_true
+ (@os == OpenStructSpecs::OpenStructSub.new(name: "John")).should be_true
+
+ (@os == OpenStruct.new(name: "Jonny")).should be_false
+ (@os == OpenStructSpecs::OpenStructSub.new(name: "Jonny")).should be_false
+
+ (@os == OpenStruct.new(name: "John", age: 20)).should be_false
+ (@os == OpenStructSpecs::OpenStructSub.new(name: "John", age: 20)).should be_false
+ end
+end
diff --git a/spec/ruby/library/openstruct/fixtures/classes.rb b/spec/ruby/library/openstruct/fixtures/classes.rb
new file mode 100644
index 0000000000..da42e8511d
--- /dev/null
+++ b/spec/ruby/library/openstruct/fixtures/classes.rb
@@ -0,0 +1,4 @@
+module OpenStructSpecs
+ class OpenStructSub < OpenStruct
+ end
+end
diff --git a/spec/ruby/library/openstruct/frozen_spec.rb b/spec/ruby/library/openstruct/frozen_spec.rb
new file mode 100644
index 0000000000..c14a4bac55
--- /dev/null
+++ b/spec/ruby/library/openstruct/frozen_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require 'ostruct'
+
+describe "OpenStruct.new when frozen" do
+ before :each do
+ @os = OpenStruct.new(name: "John Smith", age: 70, pension: 300).freeze
+ end
+ #
+ # method_missing case handled in method_missing_spec.rb
+ #
+ it "is still readable" do
+ @os.age.should eql(70)
+ @os.pension.should eql(300)
+ @os.name.should == "John Smith"
+ end
+
+ it "is not writable" do
+ ->{ @os.age = 42 }.should raise_error( RuntimeError )
+ end
+
+ it "cannot create new fields" do
+ ->{ @os.state = :new }.should raise_error( RuntimeError )
+ end
+
+ it "creates a frozen clone" do
+ f = @os.clone
+ f.frozen?.should == true
+ f.age.should == 70
+ ->{ f.age = 0 }.should raise_error( RuntimeError )
+ ->{ f.state = :newer }.should raise_error( RuntimeError )
+ end
+
+ it "creates an unfrozen dup" do
+ d = @os.dup
+ d.frozen?.should == false
+ d.age.should == 70
+ d.age = 42
+ d.age.should == 42
+ end
+end
diff --git a/spec/ruby/library/openstruct/initialize_spec.rb b/spec/ruby/library/openstruct/initialize_spec.rb
new file mode 100644
index 0000000000..dee5de48c6
--- /dev/null
+++ b/spec/ruby/library/openstruct/initialize_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'ostruct'
+
+describe "OpenStruct#initialize" do
+ it "is private" do
+ OpenStruct.should have_private_instance_method(:initialize)
+ end
+end
diff --git a/spec/ruby/library/openstruct/inspect_spec.rb b/spec/ruby/library/openstruct/inspect_spec.rb
new file mode 100644
index 0000000000..e2fed41528
--- /dev/null
+++ b/spec/ruby/library/openstruct/inspect_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'ostruct'
+require_relative 'fixtures/classes'
+require_relative 'shared/inspect'
+
+describe "OpenStruct#inspect" do
+ it_behaves_like :ostruct_inspect, :inspect
+end
diff --git a/spec/ruby/library/openstruct/marshal_dump_spec.rb b/spec/ruby/library/openstruct/marshal_dump_spec.rb
new file mode 100644
index 0000000000..5c38fd959e
--- /dev/null
+++ b/spec/ruby/library/openstruct/marshal_dump_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require "ostruct"
+
+describe "OpenStruct#marshal_dump" do
+ it "returns the method/value table" do
+ os = OpenStruct.new("age" => 20, "name" => "John")
+ os.marshal_dump.should == { age: 20, name: "John" }
+ end
+end
diff --git a/spec/ruby/library/openstruct/marshal_load_spec.rb b/spec/ruby/library/openstruct/marshal_load_spec.rb
new file mode 100644
index 0000000000..342e5e68cd
--- /dev/null
+++ b/spec/ruby/library/openstruct/marshal_load_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require "ostruct"
+
+describe "OpenStruct#marshal_load when passed [Hash]" do
+ it "defines methods based on the passed Hash" do
+ os = OpenStruct.new
+ os.send :marshal_load, age: 20, name: "John"
+
+ os.age.should eql(20)
+ os.name.should == "John"
+ end
+end
diff --git a/spec/ruby/library/openstruct/method_missing_spec.rb b/spec/ruby/library/openstruct/method_missing_spec.rb
new file mode 100644
index 0000000000..89f83d07b3
--- /dev/null
+++ b/spec/ruby/library/openstruct/method_missing_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require "ostruct"
+
+describe "OpenStruct#method_missing when called with a method name ending in '='" do
+ before :each do
+ @os = OpenStruct.new
+ end
+
+ it "raises an ArgumentError when not passed any additional arguments" do
+ -> { @os.send(:test=) }.should raise_error(ArgumentError)
+ end
+end
+
+describe "OpenStruct#method_missing when passed additional arguments" do
+ it "raises a NoMethodError when the key does not exist" do
+ os = OpenStruct.new
+ -> { os.test(1, 2, 3) }.should raise_error(NoMethodError)
+ end
+
+ it "raises an ArgumentError when the key exists" do
+ os = OpenStruct.new(test: 20)
+ -> { os.test(1, 2, 3) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/openstruct/new_spec.rb b/spec/ruby/library/openstruct/new_spec.rb
new file mode 100644
index 0000000000..5d2cacea40
--- /dev/null
+++ b/spec/ruby/library/openstruct/new_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require 'ostruct'
+
+describe "OpenStruct.new when passed [Hash]" do
+ before :each do
+ @os = OpenStruct.new(name: "John Smith", age: 70, pension: 300)
+ end
+
+ it "creates an attribute for each key of the passed Hash" do
+ @os.age.should eql(70)
+ @os.pension.should eql(300)
+ @os.name.should == "John Smith"
+ end
+end
+
+describe "OpenStruct.new when passed no arguments" do
+ it "returns a new OpenStruct Object without any attributes" do
+ OpenStruct.new.to_s.should == "#<OpenStruct>"
+ end
+end
diff --git a/spec/ruby/library/openstruct/shared/inspect.rb b/spec/ruby/library/openstruct/shared/inspect.rb
new file mode 100644
index 0000000000..ffcd690e1f
--- /dev/null
+++ b/spec/ruby/library/openstruct/shared/inspect.rb
@@ -0,0 +1,20 @@
+describe :ostruct_inspect, shared: true do
+ it "returns a String representation of self" do
+ os = OpenStruct.new(name: "John Smith")
+ os.send(@method).should == "#<OpenStruct name=\"John Smith\">"
+
+ os = OpenStruct.new(age: 20, name: "John Smith")
+ os.send(@method).should be_kind_of(String)
+ end
+
+ it "correctly handles self-referential OpenStructs" do
+ os = OpenStruct.new
+ os.self = os
+ os.send(@method).should == "#<OpenStruct self=#<OpenStruct ...>>"
+ end
+
+ it "correctly handles OpenStruct subclasses" do
+ os = OpenStructSpecs::OpenStructSub.new(name: "John Smith")
+ os.send(@method).should == "#<OpenStructSpecs::OpenStructSub name=\"John Smith\">"
+ end
+end
diff --git a/spec/ruby/library/openstruct/to_h_spec.rb b/spec/ruby/library/openstruct/to_h_spec.rb
new file mode 100644
index 0000000000..6c272bcc71
--- /dev/null
+++ b/spec/ruby/library/openstruct/to_h_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+require 'ostruct'
+
+describe "OpenStruct#to_h" do
+ before :each do
+ @h = {name: "John Smith", age: 70, pension: 300}
+ @os = OpenStruct.new(@h)
+ @to_h = @os.to_h
+ end
+
+ it "returns a Hash with members as keys" do
+ @to_h.should == @h
+ end
+
+ it "returns a Hash with keys as symbols" do
+ os = OpenStruct.new("name" => "John Smith", "age" => 70)
+ os.pension = 300
+ os.to_h.should == @h
+ end
+
+ it "does not return the hash used as initializer" do
+ @to_h.should_not equal(@h)
+ end
+
+ it "returns a Hash that is independent from the struct" do
+ @to_h[:age] = 71
+ @os.age.should == 70
+ end
+
+ context "with block" do
+ it "converts [key, value] pairs returned by the block to a hash" do
+ h = @os.to_h { |k, v| [k.to_s, v*2] }
+ h.should == { "name" => "John SmithJohn Smith", "age" => 140, "pension" => 600 }
+ end
+
+ it "raises ArgumentError if block returns longer or shorter array" do
+ -> do
+ @os.to_h { |k, v| [k.to_s, v*2, 1] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+
+ -> do
+ @os.to_h { |k, v| [k] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ @os.to_h { |k, v| "not-array" }
+ end.should raise_error(TypeError, /wrong element type String/)
+ end
+
+ it "coerces returned pair to Array with #to_ary" do
+ x = mock('x')
+ x.stub!(:to_ary).and_return([:b, 'b'])
+
+ @os.to_h { |k| x }.should == { :b => 'b' }
+ end
+
+ it "does not coerce returned pair to Array with #to_a" do
+ x = mock('x')
+ x.stub!(:to_a).and_return([:b, 'b'])
+
+ -> do
+ @os.to_h { |k| x }
+ end.should raise_error(TypeError, /wrong element type MockObject/)
+ end
+ end
+end
diff --git a/spec/ruby/library/openstruct/to_s_spec.rb b/spec/ruby/library/openstruct/to_s_spec.rb
new file mode 100644
index 0000000000..73d91bf981
--- /dev/null
+++ b/spec/ruby/library/openstruct/to_s_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'ostruct'
+require_relative 'fixtures/classes'
+require_relative 'shared/inspect'
+
+describe "OpenStruct#to_s" do
+ it_behaves_like :ostruct_inspect, :to_s
+end
diff --git a/spec/ruby/library/optionparser/order_spec.rb b/spec/ruby/library/optionparser/order_spec.rb
new file mode 100644
index 0000000000..e49bd25554
--- /dev/null
+++ b/spec/ruby/library/optionparser/order_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require 'optparse'
+
+describe "OptionParser#order" do
+ it "accepts `into` keyword argument and stores result in it" do
+ options = {}
+ parser = OptionParser.new do |opts|
+ opts.on("-v", "--[no-]verbose", "Run verbosely")
+ opts.on("-r", "--require LIBRARY", "Require the LIBRARY before executing your script")
+ end
+ parser.order %w[--verbose --require optparse], into: options
+
+ options.should == { verbose: true, require: "optparse" }
+ end
+end
+
+describe "OptionParser#order!" do
+ it "accepts `into` keyword argument and stores result in it" do
+ options = {}
+ parser = OptionParser.new do |opts|
+ opts.on("-v", "--[no-]verbose", "Run verbosely")
+ opts.on("-r", "--require LIBRARY", "Require the LIBRARY before executing your script")
+ end
+ parser.order! %w[--verbose --require optparse], into: options
+
+ options.should == { verbose: true, require: "optparse" }
+ end
+end
diff --git a/spec/ruby/library/optionparser/parse_spec.rb b/spec/ruby/library/optionparser/parse_spec.rb
new file mode 100644
index 0000000000..9511acb1db
--- /dev/null
+++ b/spec/ruby/library/optionparser/parse_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require 'optparse'
+
+describe "OptionParser#parse" do
+ it "accepts `into` keyword argument and stores result in it" do
+ options = {}
+ parser = OptionParser.new do |opts|
+ opts.on("-v", "--[no-]verbose", "Run verbosely")
+ opts.on("-r", "--require LIBRARY", "Require the LIBRARY before executing your script")
+ end
+ parser.parse %w[--verbose --require optparse], into: options
+
+ options.should == { verbose: true, require: "optparse" }
+ end
+end
+
+describe "OptionParser#parse!" do
+ it "accepts `into` keyword argument and stores result in it" do
+ options = {}
+ parser = OptionParser.new do |opts|
+ opts.on("-v", "--[no-]verbose", "Run verbosely")
+ opts.on("-r", "--require LIBRARY", "Require the LIBRARY before executing your script")
+ end
+ parser.parse! %w[--verbose --require optparse], into: options
+
+ options.should == { verbose: true, require: "optparse" }
+ end
+end
diff --git a/spec/ruby/library/pathname/absolute_spec.rb b/spec/ruby/library/pathname/absolute_spec.rb
new file mode 100644
index 0000000000..109abb8ee9
--- /dev/null
+++ b/spec/ruby/library/pathname/absolute_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#absolute?" do
+
+ it "returns true for the root directory" do
+ Pathname.new('/').should.absolute?
+ end
+
+ it "returns true for a dir starting with a slash" do
+ Pathname.new('/usr/local/bin').should.absolute?
+ end
+
+ it "returns false for a dir not starting with a slash" do
+ Pathname.new('fish').should_not.absolute?
+ end
+
+ it "returns false for a dir not starting with a slash" do
+ Pathname.new('fish/dog/cow').should_not.absolute?
+ end
+
+end
diff --git a/spec/ruby/library/pathname/birthtime_spec.rb b/spec/ruby/library/pathname/birthtime_spec.rb
new file mode 100644
index 0000000000..109c112303
--- /dev/null
+++ b/spec/ruby/library/pathname/birthtime_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#birthtime" do
+ platform_is :windows, :darwin, :freebsd, :netbsd do
+ it "returns the birth time for self" do
+ Pathname.new(__FILE__).birthtime.should be_kind_of(Time)
+ end
+ end
+
+ platform_is :openbsd do
+ it "raises an NotImplementedError" do
+ -> { Pathname.new(__FILE__).birthtime }.should raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/ruby/library/pathname/divide_spec.rb b/spec/ruby/library/pathname/divide_spec.rb
new file mode 100644
index 0000000000..8af79d0c8f
--- /dev/null
+++ b/spec/ruby/library/pathname/divide_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/plus'
+
+describe "Pathname#/" do
+ it_behaves_like :pathname_plus, :/
+end
diff --git a/spec/ruby/library/pathname/empty_spec.rb b/spec/ruby/library/pathname/empty_spec.rb
new file mode 100644
index 0000000000..4deade5b64
--- /dev/null
+++ b/spec/ruby/library/pathname/empty_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe 'Pathname#empty?' do
+ before :all do
+ @file = tmp 'new_file_path_name.txt'
+ touch @file
+ @dir = tmp 'new_directory_path_name'
+ Dir.mkdir @dir
+ end
+
+ after :all do
+ rm_r @file
+ rm_r @dir
+ end
+
+ it 'returns true when file is not empty' do
+ Pathname.new(__FILE__).empty?.should be_false
+ end
+
+ it 'returns false when the directory is not empty' do
+ Pathname.new(__dir__).empty?.should be_false
+ end
+
+ it 'return true when file is empty' do
+ Pathname.new(@file).empty?.should be_true
+ end
+
+ it 'returns true when directory is empty' do
+ Pathname.new(@dir).empty?.should be_true
+ end
+end
diff --git a/spec/ruby/library/pathname/equal_value_spec.rb b/spec/ruby/library/pathname/equal_value_spec.rb
new file mode 100644
index 0000000000..92d4767e76
--- /dev/null
+++ b/spec/ruby/library/pathname/equal_value_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#==" do
+
+ it "returns true when identical paths are used" do
+ (Pathname.new('') == Pathname.new('')).should == true
+ end
+
+ it "returns true when identical paths are used" do
+ (Pathname.new('') == Pathname.new('/usr/local/bin')).should == false
+ end
+
+end
diff --git a/spec/ruby/library/pathname/glob_spec.rb b/spec/ruby/library/pathname/glob_spec.rb
new file mode 100644
index 0000000000..ced810fa90
--- /dev/null
+++ b/spec/ruby/library/pathname/glob_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe 'Pathname.glob' do
+ before :all do
+ @dir = tmp('pathname_glob') + '/'
+ @file_1 = @dir + 'lib/ipaddr.rb'
+ @file_2 = @dir + 'lib/irb.rb'
+ @file_3 = @dir + 'lib/.hidden.rb'
+
+ touch @file_1
+ touch @file_2
+ touch @file_3
+ end
+
+ after :all do
+ rm_r @dir[0...-1]
+ end
+
+ it 'returns [] for no match' do
+ Pathname.glob(@dir + 'lib/*.js').should == []
+ end
+
+ it 'returns matching file paths' do
+ Pathname.glob(@dir + 'lib/*i*.rb').sort.should == [Pathname.new(@file_1), Pathname.new(@file_2)].sort
+ end
+
+ it 'returns matching file paths when a flag is provided' do
+ expected = [Pathname.new(@file_1), Pathname.new(@file_2), Pathname.new(@file_3)].sort
+ Pathname.glob(@dir + 'lib/*i*.rb', File::FNM_DOTMATCH).sort.should == expected
+ end
+
+ it 'returns matching file paths when supplied :base keyword argument' do
+ Pathname.glob('*i*.rb', base: @dir + 'lib').sort.should == [Pathname.new('ipaddr.rb'), Pathname.new('irb.rb')].sort
+ end
+
+ it "raises an ArgumentError when supplied a keyword argument other than :base" do
+ -> {
+ Pathname.glob('*i*.rb', foo: @dir + 'lib')
+ }.should raise_error(ArgumentError, /unknown keyword: :?foo/)
+ end
+
+ it "does not raise an ArgumentError when supplied a flag and :base keyword argument" do
+ expected = [Pathname.new('ipaddr.rb'), Pathname.new('irb.rb'), Pathname.new('.hidden.rb')].sort
+ Pathname.glob('*i*.rb', File::FNM_DOTMATCH, base: @dir + 'lib').sort.should == expected
+ end
+end
+
+
+describe 'Pathname#glob' do
+ before :all do
+ @dir = tmp('pathname_glob') + '/'
+ @file_1 = @dir + 'lib/ipaddr.rb'
+ @file_2 = @dir + 'lib/irb.rb'
+ @file_3 = @dir + 'lib/.hidden.rb'
+
+ touch @file_1
+ touch @file_2
+ touch @file_3
+ end
+
+ after :all do
+ rm_r @dir[0...-1]
+ end
+
+ it 'returns [] for no match' do
+ Pathname.new(@dir).glob('lib/*.js').should == []
+ end
+
+ it 'returns matching file paths' do
+ Pathname.new(@dir).glob('lib/*i*.rb').sort.should == [Pathname.new(@file_1), Pathname.new(@file_2)].sort
+ end
+
+ it 'yields matching file paths to block' do
+ ary = []
+ Pathname.new(@dir).glob('lib/*i*.rb') { |p| ary << p }.should be_nil
+ ary.sort.should == [Pathname.new(@file_1), Pathname.new(@file_2)].sort
+ end
+
+ it 'returns matching file paths when a flag is provided' do
+ expected = [Pathname.new(@file_1), Pathname.new(@file_2), Pathname.new(@file_3)].sort
+ Pathname.new(@dir).glob('lib/*i*.rb', File::FNM_DOTMATCH).sort.should == expected
+ end
+end
diff --git a/spec/ruby/library/pathname/hash_spec.rb b/spec/ruby/library/pathname/hash_spec.rb
new file mode 100644
index 0000000000..da1b8f4f76
--- /dev/null
+++ b/spec/ruby/library/pathname/hash_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#hash" do
+
+ it "is equal to the hash of the pathname" do
+ Pathname.new('/usr/local/bin/').hash.should == '/usr/local/bin/'.hash
+ end
+
+ it "is not equal the hash of a different pathname" do
+ Pathname.new('/usr/local/bin/').hash.should_not == '/usr/bin/'.hash
+ end
+
+end
diff --git a/spec/ruby/library/pathname/inspect_spec.rb b/spec/ruby/library/pathname/inspect_spec.rb
new file mode 100644
index 0000000000..304746fbe5
--- /dev/null
+++ b/spec/ruby/library/pathname/inspect_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#inspect" do
+ it "returns a consistent String" do
+ result = Pathname.new('/tmp').inspect
+ result.should be_an_instance_of(String)
+ result.should == "#<Pathname:/tmp>"
+ end
+end
diff --git a/spec/ruby/library/pathname/join_spec.rb b/spec/ruby/library/pathname/join_spec.rb
new file mode 100644
index 0000000000..a0877777fc
--- /dev/null
+++ b/spec/ruby/library/pathname/join_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#join" do
+ it "without separators" do
+ Pathname.new('/usr').join(Pathname.new('foo')).should == Pathname.new('/usr/foo')
+ end
+
+ it "with separators" do
+ Pathname.new('/usr').join(Pathname.new('/foo')).should == Pathname.new('/foo')
+ end
+
+ it "with a string" do
+ Pathname.new('/usr').join('foo').should == Pathname.new('/usr/foo')
+ end
+
+ it "with root" do
+ Pathname.new('/usr').join(Pathname.new('/')).should == Pathname.new('/')
+ end
+
+ it "with a relative path" do
+ Pathname.new('/usr').join(Pathname.new('../foo')).should == Pathname.new('/foo')
+ end
+
+ it "a relative path with current" do
+ Pathname.new('.').join(Pathname.new('foo')).should == Pathname.new('foo')
+ end
+
+ it "an absolute path with current" do
+ Pathname.new('.').join(Pathname.new('/foo')).should == Pathname.new('/foo')
+ end
+
+ it "a prefixed relative path with current" do
+ Pathname.new('.').join(Pathname.new('./foo')).should == Pathname.new('foo')
+ end
+
+ it "multiple paths" do
+ Pathname.new('.').join(Pathname.new('./foo'), 'bar').should == Pathname.new('foo/bar')
+ end
+end
diff --git a/spec/ruby/library/pathname/new_spec.rb b/spec/ruby/library/pathname/new_spec.rb
new file mode 100644
index 0000000000..36226ed515
--- /dev/null
+++ b/spec/ruby/library/pathname/new_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname.new" do
+ it "returns a new Pathname Object with 1 argument" do
+ Pathname.new('').should be_kind_of(Pathname)
+ end
+
+ it "raises an ArgumentError when called with \0" do
+ -> { Pathname.new("\0")}.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { Pathname.new(nil) }.should raise_error(TypeError)
+ -> { Pathname.new(0) }.should raise_error(TypeError)
+ -> { Pathname.new(true) }.should raise_error(TypeError)
+ -> { Pathname.new(false) }.should raise_error(TypeError)
+ end
+
+ it "initializes with an object with to_path" do
+ Pathname.new(mock_to_path('foo')).should == Pathname.new('foo')
+ end
+end
diff --git a/spec/ruby/library/pathname/parent_spec.rb b/spec/ruby/library/pathname/parent_spec.rb
new file mode 100644
index 0000000000..3843bb22ed
--- /dev/null
+++ b/spec/ruby/library/pathname/parent_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#parent" do
+
+ it "has parent of root as root" do
+ Pathname.new('/').parent.to_s.should == '/'
+ end
+
+ it "has parent of /usr/ as root" do
+ Pathname.new('/usr/').parent.to_s.should == '/'
+ end
+
+ it "has parent of /usr/local as root" do
+ Pathname.new('/usr/local').parent.to_s.should == '/usr'
+ end
+
+end
diff --git a/spec/ruby/library/pathname/pathname_spec.rb b/spec/ruby/library/pathname/pathname_spec.rb
new file mode 100644
index 0000000000..0fb2881468
--- /dev/null
+++ b/spec/ruby/library/pathname/pathname_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Kernel#Pathname" do
+ it "is a private instance method" do
+ Kernel.should have_private_instance_method(:Pathname)
+ end
+
+ it "is also a public method" do
+ Kernel.should have_method(:Pathname)
+ end
+
+ it "returns same argument when called with a pathname argument" do
+ path = Pathname('foo')
+ new_path = Pathname(path)
+
+ path.should.equal?(new_path)
+ end
+end
diff --git a/spec/ruby/library/pathname/plus_spec.rb b/spec/ruby/library/pathname/plus_spec.rb
new file mode 100644
index 0000000000..57e472c266
--- /dev/null
+++ b/spec/ruby/library/pathname/plus_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/plus'
+
+describe "Pathname#+" do
+ it_behaves_like :pathname_plus, :+
+end
diff --git a/spec/ruby/library/pathname/realdirpath_spec.rb b/spec/ruby/library/pathname/realdirpath_spec.rb
new file mode 100644
index 0000000000..a9e44e354e
--- /dev/null
+++ b/spec/ruby/library/pathname/realdirpath_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#realdirpath" do
+
+ it "returns a Pathname" do
+ Pathname.pwd.realdirpath.should be_an_instance_of(Pathname)
+ end
+
+end
diff --git a/spec/ruby/library/pathname/realpath_spec.rb b/spec/ruby/library/pathname/realpath_spec.rb
new file mode 100644
index 0000000000..f2c654308e
--- /dev/null
+++ b/spec/ruby/library/pathname/realpath_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#realpath" do
+
+ it "returns a Pathname" do
+ Pathname.pwd.realpath.should be_an_instance_of(Pathname)
+ end
+
+end
diff --git a/spec/ruby/library/pathname/relative_path_from_spec.rb b/spec/ruby/library/pathname/relative_path_from_spec.rb
new file mode 100644
index 0000000000..abe9c80a45
--- /dev/null
+++ b/spec/ruby/library/pathname/relative_path_from_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#relative_path_from" do
+ def relative_path_str(dest, base)
+ Pathname.new(dest).relative_path_from(Pathname.new(base)).to_s
+ end
+
+ it "raises an error when the two paths do not share a common prefix" do
+ -> { relative_path_str('/usr', 'foo') }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error when the base directory has .." do
+ -> { relative_path_str('a', '..') }.should raise_error(ArgumentError)
+ end
+
+ it "returns a path relative from root" do
+ relative_path_str('/usr', '/').should == 'usr'
+ end
+
+ it 'returns 1 level up when both paths are relative' do
+ relative_path_str('a', 'b').should == '../a'
+ relative_path_str('a', 'b/').should == '../a'
+ end
+
+ it 'returns a relative path when both are absolute' do
+ relative_path_str('/a', '/b').should == '../a'
+ end
+
+ it "returns a path relative to the current directory" do
+ relative_path_str('/usr/bin/ls', '/usr').should == 'bin/ls'
+ end
+
+ it 'returns a . when base and dest are the same' do
+ relative_path_str('/usr', '/usr').should == '.'
+ end
+
+ it 'returns the same directory with a non clean base that matches the current dir' do
+ relative_path_str('/usr', '/stuff/..').should == 'usr'
+ end
+
+ it 'returns a relative path with a non clean base that matches a different dir' do
+ relative_path_str('/usr', '/stuff/../foo').should == '../usr'
+ end
+
+ it 'returns current and pattern when only those patterns are used' do
+ relative_path_str('.', '.').should == '.'
+ relative_path_str('..', '..').should == '.'
+ relative_path_str('..', '.').should == '..'
+ end
+end
diff --git a/spec/ruby/library/pathname/relative_spec.rb b/spec/ruby/library/pathname/relative_spec.rb
new file mode 100644
index 0000000000..0fab9a7b9f
--- /dev/null
+++ b/spec/ruby/library/pathname/relative_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#relative?" do
+
+ it "returns false for the root directory" do
+ Pathname.new('/').should_not.relative?
+ end
+
+ it "returns false for a dir starting with a slash" do
+ Pathname.new('/usr/local/bin').should_not.relative?
+ end
+
+ it "returns true for a dir not starting with a slash" do
+ Pathname.new('fish').should.relative?
+ end
+
+ it "returns true for a dir not starting with a slash" do
+ Pathname.new('fish/dog/cow').should.relative?
+ end
+
+end
diff --git a/spec/ruby/library/pathname/root_spec.rb b/spec/ruby/library/pathname/root_spec.rb
new file mode 100644
index 0000000000..cd2be24516
--- /dev/null
+++ b/spec/ruby/library/pathname/root_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#root?" do
+
+ it "returns true for root directories" do
+ Pathname.new('/').should.root?
+ end
+
+ it "returns false for empty string" do
+ Pathname.new('').should_not.root?
+ end
+
+ it "returns false for a top level directory" do
+ Pathname.new('/usr').should_not.root?
+ end
+
+ it "returns false for a top level with .. appended directory" do
+ Pathname.new('/usr/..').should_not.root?
+ end
+
+ it "returns false for a directory below top level" do
+ Pathname.new('/usr/local/bin/').should_not.root?
+ end
+
+end
diff --git a/spec/ruby/library/pathname/shared/plus.rb b/spec/ruby/library/pathname/shared/plus.rb
new file mode 100644
index 0000000000..b3b896ea43
--- /dev/null
+++ b/spec/ruby/library/pathname/shared/plus.rb
@@ -0,0 +1,8 @@
+require 'pathname'
+
+describe :pathname_plus, shared: true do
+ it "appends a pathname to self" do
+ p = Pathname.new("/usr")
+ p.send(@method, "bin/ruby").should == Pathname.new("/usr/bin/ruby")
+ end
+end
diff --git a/spec/ruby/library/pathname/sub_spec.rb b/spec/ruby/library/pathname/sub_spec.rb
new file mode 100644
index 0000000000..ad2900f62b
--- /dev/null
+++ b/spec/ruby/library/pathname/sub_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require 'pathname'
+
+describe "Pathname#sub" do
+
+ it "replaces the pattern with rest" do
+ Pathname.new('/usr/local/bin/').sub(/local/, 'fish').to_s.should == '/usr/fish/bin/'
+ end
+
+ it "returns a new object" do
+ p = Pathname.new('/usr/local/bin/')
+ p.sub(/local/, 'fish').should_not == p
+ end
+
+end
diff --git a/spec/ruby/library/pp/pp_spec.rb b/spec/ruby/library/pp/pp_spec.rb
new file mode 100644
index 0000000000..243478efd9
--- /dev/null
+++ b/spec/ruby/library/pp/pp_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require 'pp'
+
+describe "PP.pp" do
+ it 'works with default arguments' do
+ array = [1, 2, 3]
+
+ -> {
+ PP.pp array
+ }.should output "[1, 2, 3]\n"
+ end
+
+ it 'allows specifying out explicitly' do
+ array = [1, 2, 3]
+ other_out = IOStub.new
+
+ -> {
+ PP.pp array, other_out
+ }.should output "" # no output on stdout
+
+ other_out.to_s.should == "[1, 2, 3]\n"
+ end
+
+ it 'correctly prints a Hash' do
+ hash = { 'key' => 42 }
+ -> {
+ PP.pp hash
+ }.should output('{"key"=>42}' + "\n")
+ end
+end
diff --git a/spec/ruby/library/prime/each_spec.rb b/spec/ruby/library/prime/each_spec.rb
new file mode 100644
index 0000000000..c89e871582
--- /dev/null
+++ b/spec/ruby/library/prime/each_spec.rb
@@ -0,0 +1,170 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'prime'
+
+ describe :prime_each, shared: true do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "enumerates primes" do
+ primes = Prime.instance
+ result = []
+
+ primes.each { |p|
+ result << p
+ break if p > 10
+ }
+
+ result.should == [2, 3, 5, 7, 11]
+ end
+
+ it "yields ascending primes to the block" do
+ previous = 1
+ @object.each do |prime|
+ break if prime > 1000
+ ScratchPad << prime
+ prime.should > previous
+ previous = prime
+ end
+
+ all_prime = true
+ ScratchPad.recorded.all? do |prime|
+ all_prime &&= (2..Math.sqrt(prime)).all? { |d| prime % d != 0 }
+ end
+
+ all_prime.should be_true
+ end
+
+ it "returns the last evaluated expression in the passed block" do
+ @object.each { break :value }.should equal(:value)
+ end
+
+ describe "when not passed a block" do
+ before :each do
+ @prime_enum = @object.each
+ end
+
+ it "returns an object that is Enumerable" do
+ @prime_enum.each.should be_kind_of(Enumerable)
+ end
+
+ it "returns an object that responds to #with_index" do
+ @prime_enum.should respond_to(:with_index)
+ end
+
+ it "returns an object that responds to #with_object" do
+ @prime_enum.should respond_to(:with_object)
+ end
+
+ it "returns an object that responds to #next" do
+ @prime_enum.should respond_to(:next)
+ end
+
+ it "returns an object that responds to #rewind" do
+ @prime_enum.should respond_to(:rewind)
+ end
+
+ it "yields primes starting at 2 independent of prior enumerators" do
+ @prime_enum.next.should == 2
+ @prime_enum.next.should == 3
+
+ @object.each { |prime| break prime }.should == 2
+ end
+
+ it "returns an enumerator that yields previous primes when #rewind is called" do
+ @prime_enum.next.should == 2
+ @prime_enum.next.should == 3
+ @prime_enum.rewind
+ @prime_enum.next.should == 2
+ end
+
+ it "returns independent enumerators" do
+ enum = @object.each
+ enum.next.should == 2
+ enum.next.should == 3
+
+ @prime_enum.next.should == 2
+
+ enum.next.should == 5
+ end
+ end
+ end
+
+ describe :prime_each_with_arguments, shared: true do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "yields ascending primes less than or equal to the argument" do
+ bound = 1000
+ previous = 1
+ @object.each(bound) do |prime|
+ ScratchPad << prime
+ prime.should > previous
+ previous = prime
+ end
+
+ ScratchPad.recorded.all? do |prime|
+ (2..Math.sqrt(prime)).all? { |d| prime % d != 0 }
+ end.should be_true
+
+ ScratchPad.recorded.all? { |prime| prime <= bound }.should be_true
+ end
+
+ it "returns nil when no prime is generated" do
+ @object.each(1) { :value }.should be_nil
+ end
+
+ it "yields primes starting at 2 independent of prior enumeration" do
+ @object.each(10) { |prime| prime }.should == 7
+ @object.each(10) { |prime| break prime }.should == 2
+ end
+
+ it "accepts a pseudo-prime generator as the second argument" do
+ generator = mock('very bad pseudo-prime generator')
+ generator.should_receive(:upper_bound=).with(100)
+ generator.should_receive(:each).and_yield(2).and_yield(3).and_yield(4)
+
+ @object.each(100, generator) { |prime| ScratchPad << prime }
+ ScratchPad.recorded.should == [2, 3, 4]
+ end
+
+ describe "when not passed a block" do
+ it "returns an object that returns primes less than or equal to the bound" do
+ bound = 100
+ @object.each(bound).all? { |prime| prime <= bound }.should be_true
+ end
+ end
+ end
+
+ describe "Prime.each" do
+ it_behaves_like :prime_each, :each, Prime
+ end
+
+ describe "Prime.each" do
+ it_behaves_like :prime_each_with_arguments, :each, Prime
+ end
+
+ describe "Prime#each with Prime.instance" do
+ it_behaves_like :prime_each, :each, Prime.instance
+ end
+
+ describe "Prime#each with Prime.instance" do
+ it_behaves_like :prime_each_with_arguments, :each, Prime.instance
+ end
+
+ describe "Prime#each with Prime.instance" do
+ before :each do
+ @object = Prime.instance
+ end
+
+ it_behaves_like :prime_each, :each
+
+ it "resets the enumerator with each call" do
+ @object.each { |prime| break if prime > 10 }
+ @object.each { |prime| break prime }.should == 2
+ end
+ end
+end
diff --git a/spec/ruby/library/prime/instance_spec.rb b/spec/ruby/library/prime/instance_spec.rb
new file mode 100644
index 0000000000..82f21913b7
--- /dev/null
+++ b/spec/ruby/library/prime/instance_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'prime'
+
+ describe "Prime.instance" do
+ it "returns a object representing the set of prime numbers" do
+ Prime.instance.should be_kind_of(Prime)
+ end
+
+ it "returns a object with no obsolete features" do
+ Prime.instance.should_not respond_to(:succ)
+ Prime.instance.should_not respond_to(:next)
+ end
+
+ it "does not complain anything" do
+ -> { Prime.instance }.should_not complain
+ end
+
+ it "raises a ArgumentError when is called with some arguments" do
+ -> { Prime.instance(1) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/prime/int_from_prime_division_spec.rb b/spec/ruby/library/prime/int_from_prime_division_spec.rb
new file mode 100644
index 0000000000..5c881aefa1
--- /dev/null
+++ b/spec/ruby/library/prime/int_from_prime_division_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'prime'
+
+ describe "Prime.int_from_prime_division" do
+ it "returns the product of the given factorization" do
+ Prime.int_from_prime_division([[2,3], [3,3], [5,3], [7,3], [11,3], [13,3], [17,3]]).
+ should == 2**3 * 3**3 * 5**3 * 7**3 * 11**3 * 13**3 * 17**3
+ end
+
+ it "returns 1 for an empty factorization" do
+ Prime.int_from_prime_division([]).should == 1
+ end
+ end
+end
diff --git a/spec/ruby/library/prime/integer/each_prime_spec.rb b/spec/ruby/library/prime/integer/each_prime_spec.rb
new file mode 100644
index 0000000000..6034802e73
--- /dev/null
+++ b/spec/ruby/library/prime/integer/each_prime_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'prime'
+
+ describe "Integer.each_prime" do
+ it "is transferred to Prime.each" do
+ Prime.should_receive(:each).with(100).and_yield(2).and_yield(3).and_yield(5)
+ yielded = []
+ Integer.each_prime(100) do |prime|
+ yielded << prime
+ end
+ yielded.should == [2,3,5]
+ end
+ end
+end
diff --git a/spec/ruby/library/prime/integer/from_prime_division_spec.rb b/spec/ruby/library/prime/integer/from_prime_division_spec.rb
new file mode 100644
index 0000000000..5422bc651c
--- /dev/null
+++ b/spec/ruby/library/prime/integer/from_prime_division_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'prime'
+
+ describe "Integer.from_prime_division" do
+ it "returns the product of the given factorization" do
+ Integer.from_prime_division([[2,3], [3,3], [5,3], [7,3], [11,3], [13,3], [17,3]]).
+ should == 2**3 * 3**3 * 5**3 * 7**3 * 11**3 * 13**3 * 17**3
+ end
+
+ it "returns 1 for an empty factorization" do
+ Integer.from_prime_division([]).should == 1
+ end
+ end
+end
diff --git a/spec/ruby/library/prime/integer/prime_division_spec.rb b/spec/ruby/library/prime/integer/prime_division_spec.rb
new file mode 100644
index 0000000000..03be0be27b
--- /dev/null
+++ b/spec/ruby/library/prime/integer/prime_division_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'prime'
+
+ describe "Integer#prime_division" do
+ it "returns an array of a prime factor and a corresponding exponent" do
+ (2*3*5*7*11*13*17).prime_division.should ==
+ [[2,1], [3,1], [5,1], [7,1], [11,1], [13,1], [17,1]]
+ end
+
+ it "returns an empty array for 1" do
+ 1.prime_division.should == []
+ end
+ it "returns an empty array for -1" do
+ -1.prime_division.should == [[-1, 1]]
+ end
+ it "raises ZeroDivisionError for 0" do
+ -> { 0.prime_division }.should raise_error(ZeroDivisionError)
+ end
+ end
+end
diff --git a/spec/ruby/library/prime/integer/prime_spec.rb b/spec/ruby/library/prime/integer/prime_spec.rb
new file mode 100644
index 0000000000..65b779e319
--- /dev/null
+++ b/spec/ruby/library/prime/integer/prime_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'prime'
+
+ describe "Integer#prime?" do
+ it "returns a true value for prime numbers" do
+ 2.prime?.should be_true
+ 3.prime?.should be_true
+ (2**31-1).prime?.should be_true # 8th Mersenne prime (M8)
+ end
+
+ it "returns a false value for composite numbers" do
+ 4.prime?.should be_false
+ 15.prime?.should be_false
+ (2**32-1).prime?.should be_false
+ ( (2**17-1)*(2**19-1) ).prime?.should be_false # M6*M7
+ end
+ end
+end
diff --git a/spec/ruby/library/prime/next_spec.rb b/spec/ruby/library/prime/next_spec.rb
new file mode 100644
index 0000000000..8e805ed044
--- /dev/null
+++ b/spec/ruby/library/prime/next_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/next'
+ require 'prime'
+
+ describe "Prime#next" do
+ it_behaves_like :prime_next, :next
+ end
+end
diff --git a/spec/ruby/library/prime/prime_division_spec.rb b/spec/ruby/library/prime/prime_division_spec.rb
new file mode 100644
index 0000000000..4c93d5936a
--- /dev/null
+++ b/spec/ruby/library/prime/prime_division_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'prime'
+
+ describe "Prime.prime_division" do
+ it "returns an array of a prime factor and a corresponding exponent" do
+ Prime.prime_division(2*3*5*7*11*13*17).should ==
+ [[2,1], [3,1], [5,1], [7,1], [11,1], [13,1], [17,1]]
+ end
+
+ it "returns an empty array for 1" do
+ Prime.prime_division(1).should == []
+ end
+
+ it "returns [[-1, 1]] for -1" do
+ Prime.prime_division(-1).should == [[-1, 1]]
+ end
+
+ it "includes [[-1, 1]] in the divisors of a negative number" do
+ Prime.prime_division(-10).should include([-1, 1])
+ end
+
+ it "raises ZeroDivisionError for 0" do
+ -> { Prime.prime_division(0) }.should raise_error(ZeroDivisionError)
+ end
+ end
+end
diff --git a/spec/ruby/library/prime/prime_spec.rb b/spec/ruby/library/prime/prime_spec.rb
new file mode 100644
index 0000000000..e2afddd5b2
--- /dev/null
+++ b/spec/ruby/library/prime/prime_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require 'prime'
+
+ describe "Prime#prime?" do
+ it "returns a true value for prime numbers" do
+ Prime.prime?(2).should be_true
+ Prime.prime?(3).should be_true
+ Prime.prime?(2**31-1).should be_true # 8th Mersenne prime (M8)
+ end
+
+ it "returns a false value for composite numbers" do
+ Prime.prime?(4).should be_false
+ Prime.prime?(15).should be_false
+ Prime.prime?(2**32-1).should be_false
+ Prime.prime?( (2**17-1)*(2**19-1) ).should be_false # M6*M7
+ end
+ end
+end
diff --git a/spec/ruby/library/prime/shared/next.rb b/spec/ruby/library/prime/shared/next.rb
new file mode 100644
index 0000000000..f79b2c051e
--- /dev/null
+++ b/spec/ruby/library/prime/shared/next.rb
@@ -0,0 +1,8 @@
+describe :prime_next, shared: true do
+ it "returns the element at the current position and moves forward" do
+ p = Prime.instance.each
+ p.next.should == 2
+ p.next.should == 3
+ p.next.next.should == 6
+ end
+end
diff --git a/spec/ruby/library/prime/succ_spec.rb b/spec/ruby/library/prime/succ_spec.rb
new file mode 100644
index 0000000000..9843dae25d
--- /dev/null
+++ b/spec/ruby/library/prime/succ_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+ruby_version_is ""..."3.1" do
+ require_relative 'shared/next'
+ require 'prime'
+
+ describe "Prime#succ" do
+ it_behaves_like :prime_next, :succ
+ end
+end
diff --git a/spec/ruby/library/rbconfig/rbconfig_spec.rb b/spec/ruby/library/rbconfig/rbconfig_spec.rb
new file mode 100644
index 0000000000..b90cc90970
--- /dev/null
+++ b/spec/ruby/library/rbconfig/rbconfig_spec.rb
@@ -0,0 +1,101 @@
+require_relative '../../spec_helper'
+require 'rbconfig'
+
+describe 'RbConfig::CONFIG' do
+ it 'values are all strings' do
+ RbConfig::CONFIG.each do |k, v|
+ k.should be_kind_of String
+ v.should be_kind_of String
+ end
+ end
+
+ # These directories have no meanings before the installation.
+ guard -> { RbConfig::TOPDIR } do
+ it "['rubylibdir'] returns the directory containing Ruby standard libraries" do
+ rubylibdir = RbConfig::CONFIG['rubylibdir']
+ File.directory?(rubylibdir).should == true
+ File.should.exist?("#{rubylibdir}/fileutils.rb")
+ end
+
+ it "['archdir'] returns the directory containing standard libraries C extensions" do
+ archdir = RbConfig::CONFIG['archdir']
+ File.directory?(archdir).should == true
+ File.should.exist?("#{archdir}/etc.#{RbConfig::CONFIG['DLEXT']}")
+ end
+
+ it "['sitelibdir'] is set and is part of $LOAD_PATH" do
+ sitelibdir = RbConfig::CONFIG['sitelibdir']
+ sitelibdir.should be_kind_of String
+ $LOAD_PATH.map{|path| File.realpath(path) rescue path }.should.include? sitelibdir
+ end
+ end
+
+ it "contains no frozen strings even with --enable-frozen-string-literal" do
+ ruby_exe(<<-RUBY, options: '--enable-frozen-string-literal').should == "Done\n"
+ require 'rbconfig'
+ RbConfig::CONFIG.each do |k, v|
+ if v.frozen?
+ puts "\#{k} Failure"
+ end
+ end
+ puts 'Done'
+ RUBY
+ end
+
+ platform_is_not :windows do
+ it "['LIBRUBY'] is the same as LIBRUBY_SO if and only if ENABLE_SHARED" do
+ case RbConfig::CONFIG['ENABLE_SHARED']
+ when 'yes'
+ RbConfig::CONFIG['LIBRUBY'].should == RbConfig::CONFIG['LIBRUBY_SO']
+ when 'no'
+ RbConfig::CONFIG['LIBRUBY'].should_not == RbConfig::CONFIG['LIBRUBY_SO']
+ end
+ end
+ end
+
+ guard -> { RbConfig::TOPDIR } do
+ it "libdir/LIBRUBY_SO is the path to libruby and it exists if and only if ENABLE_SHARED" do
+ libdirname = RbConfig::CONFIG['LIBPATHENV'] == 'PATH' ? 'bindir' :
+ RbConfig::CONFIG['libdirname']
+ libdir = RbConfig::CONFIG[libdirname]
+ libruby_so = "#{libdir}/#{RbConfig::CONFIG['LIBRUBY_SO']}"
+ case RbConfig::CONFIG['ENABLE_SHARED']
+ when 'yes'
+ File.should.exist?(libruby_so)
+ when 'no'
+ File.should_not.exist?(libruby_so)
+ end
+ end
+ end
+
+ platform_is :linux do
+ it "['AR'] exists and can be executed" do
+ ar = RbConfig::CONFIG.fetch('AR')
+ out = `#{ar} --version`
+ $?.should.success?
+ out.should_not be_empty
+ end
+
+ it "['STRIP'] exists and can be executed" do
+ strip = RbConfig::CONFIG.fetch('STRIP')
+ copy = tmp("sh")
+ cp '/bin/sh', copy
+ begin
+ out = `#{strip} #{copy}`
+ $?.should.success?
+ ensure
+ rm_r copy
+ end
+ end
+ end
+end
+
+describe "RbConfig::TOPDIR" do
+ it "either returns nil (if not installed) or the prefix" do
+ if RbConfig::TOPDIR
+ RbConfig::TOPDIR.should == RbConfig::CONFIG["prefix"]
+ else
+ RbConfig::TOPDIR.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rbconfig/sizeof/limits_spec.rb b/spec/ruby/library/rbconfig/sizeof/limits_spec.rb
new file mode 100644
index 0000000000..776099da27
--- /dev/null
+++ b/spec/ruby/library/rbconfig/sizeof/limits_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../../spec_helper'
+require 'rbconfig/sizeof'
+
+describe "RbConfig::LIMITS" do
+ it "is a Hash" do
+ RbConfig::LIMITS.should be_kind_of(Hash)
+ end
+
+ it "has string keys and numeric values" do
+ RbConfig::LIMITS.each do |key, value|
+ key.should be_kind_of String
+ value.should be_kind_of Numeric
+ end
+ end
+
+ it "contains FIXNUM_MIN and FIXNUM_MAX" do
+ RbConfig::LIMITS["FIXNUM_MIN"].should < 0
+ RbConfig::LIMITS["FIXNUM_MAX"].should > 0
+ end
+
+ it "contains CHAR_MIN and CHAR_MAX" do
+ RbConfig::LIMITS["CHAR_MIN"].should <= 0
+ RbConfig::LIMITS["CHAR_MAX"].should > 0
+ end
+
+ it "contains SHRT_MIN and SHRT_MAX" do
+ RbConfig::LIMITS["SHRT_MIN"].should == -32768
+ RbConfig::LIMITS["SHRT_MAX"].should == 32767
+ end
+
+ it "contains INT_MIN and INT_MAX" do
+ RbConfig::LIMITS["INT_MIN"].should < 0
+ RbConfig::LIMITS["INT_MAX"].should > 0
+ end
+
+ it "contains LONG_MIN and LONG_MAX" do
+ RbConfig::LIMITS["LONG_MIN"].should < 0
+ RbConfig::LIMITS["LONG_MAX"].should > 0
+ end
+end
diff --git a/spec/ruby/library/rbconfig/sizeof/sizeof_spec.rb b/spec/ruby/library/rbconfig/sizeof/sizeof_spec.rb
new file mode 100644
index 0000000000..f2582dc4fd
--- /dev/null
+++ b/spec/ruby/library/rbconfig/sizeof/sizeof_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../spec_helper'
+require 'rbconfig/sizeof'
+
+describe "RbConfig::SIZEOF" do
+ it "is a Hash" do
+ RbConfig::SIZEOF.should be_kind_of(Hash)
+ end
+
+ it "has string keys and integer values" do
+ RbConfig::SIZEOF.each do |key, value|
+ key.should be_kind_of String
+ value.should be_kind_of Integer
+ end
+ end
+
+ it "contains the sizeof(void*)" do
+ (RbConfig::SIZEOF["void*"] * 8).should == PlatformGuard::POINTER_SIZE
+ end
+
+ it "contains the sizeof(float) and sizeof(double)" do
+ RbConfig::SIZEOF["float"].should == 4
+ RbConfig::SIZEOF["double"].should == 8
+ end
+
+ it "contains the size of short, int and long" do
+ RbConfig::SIZEOF["short"].should > 0
+ RbConfig::SIZEOF["int"].should > 0
+ RbConfig::SIZEOF["long"].should > 0
+ end
+end
diff --git a/spec/ruby/library/rbconfig/unicode_emoji_version_spec.rb b/spec/ruby/library/rbconfig/unicode_emoji_version_spec.rb
new file mode 100644
index 0000000000..3dc9900127
--- /dev/null
+++ b/spec/ruby/library/rbconfig/unicode_emoji_version_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require 'rbconfig'
+
+describe "RbConfig::CONFIG['UNICODE_EMOJI_VERSION']" do
+ ruby_version_is ""..."3.1" do
+ it "is 12.1" do
+ RbConfig::CONFIG['UNICODE_EMOJI_VERSION'].should == "12.1"
+ end
+ end
+
+ ruby_version_is "3.1"..."3.2" do
+ it "is 13.1" do
+ RbConfig::CONFIG['UNICODE_EMOJI_VERSION'].should == "13.1"
+ end
+ end
+
+ # Caution: ruby_version_is means is_or_later
+ ruby_version_is "3.2" do
+ it "is 15.0" do
+ RbConfig::CONFIG['UNICODE_EMOJI_VERSION'].should == "15.0"
+ end
+ end
+end
diff --git a/spec/ruby/library/rbconfig/unicode_version_spec.rb b/spec/ruby/library/rbconfig/unicode_version_spec.rb
new file mode 100644
index 0000000000..458f13bf03
--- /dev/null
+++ b/spec/ruby/library/rbconfig/unicode_version_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require 'rbconfig'
+
+describe "RbConfig::CONFIG['UNICODE_VERSION']" do
+ ruby_version_is ""..."3.1" do
+ it "is 12.1.0" do
+ RbConfig::CONFIG['UNICODE_VERSION'].should == "12.1.0"
+ end
+ end
+
+ ruby_version_is "3.1"..."3.2" do
+ it "is 13.0.0" do
+ RbConfig::CONFIG['UNICODE_VERSION'].should == "13.0.0"
+ end
+ end
+
+ # Caution: ruby_version_is means is_or_later
+ ruby_version_is "3.2" do
+ it "is 15.0.0" do
+ RbConfig::CONFIG['UNICODE_VERSION'].should == "15.0.0"
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/basic_quote_characters_spec.rb b/spec/ruby/library/readline/basic_quote_characters_spec.rb
new file mode 100644
index 0000000000..216899d875
--- /dev/null
+++ b/spec/ruby/library/readline/basic_quote_characters_spec.rb
@@ -0,0 +1,18 @@
+require_relative 'spec_helper'
+
+platform_is_not :darwin do
+ with_feature :readline do
+ describe "Readline.basic_quote_characters" do
+ it "returns not nil" do
+ Readline.basic_quote_characters.should_not be_nil
+ end
+ end
+
+ describe "Readline.basic_quote_characters=" do
+ it "returns the passed string" do
+ Readline.basic_quote_characters = "test"
+ Readline.basic_quote_characters.should == "test"
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/basic_word_break_characters_spec.rb b/spec/ruby/library/readline/basic_word_break_characters_spec.rb
new file mode 100644
index 0000000000..daa0e1cb76
--- /dev/null
+++ b/spec/ruby/library/readline/basic_word_break_characters_spec.rb
@@ -0,0 +1,16 @@
+require_relative 'spec_helper'
+
+with_feature :readline do
+ describe "Readline.basic_word_break_characters" do
+ it "returns not nil" do
+ Readline.basic_word_break_characters.should_not be_nil
+ end
+ end
+
+ describe "Readline.basic_word_break_characters=" do
+ it "returns the passed string" do
+ Readline.basic_word_break_characters = "test"
+ Readline.basic_word_break_characters.should == "test"
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/completer_quote_characters_spec.rb b/spec/ruby/library/readline/completer_quote_characters_spec.rb
new file mode 100644
index 0000000000..86c58f3cf6
--- /dev/null
+++ b/spec/ruby/library/readline/completer_quote_characters_spec.rb
@@ -0,0 +1,16 @@
+require_relative 'spec_helper'
+
+with_feature :readline do
+ describe "Readline.completer_quote_characters" do
+ it "returns nil" do
+ Readline.completer_quote_characters.should be_nil
+ end
+ end
+
+ describe "Readline.completer_quote_characters=" do
+ it "returns the passed string" do
+ Readline.completer_quote_characters = "test"
+ Readline.completer_quote_characters.should == "test"
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/completer_word_break_characters_spec.rb b/spec/ruby/library/readline/completer_word_break_characters_spec.rb
new file mode 100644
index 0000000000..c72f1135c4
--- /dev/null
+++ b/spec/ruby/library/readline/completer_word_break_characters_spec.rb
@@ -0,0 +1,16 @@
+require_relative 'spec_helper'
+
+with_feature :readline do
+ describe "Readline.completer_word_break_characters" do
+ it "returns nil" do
+ Readline.completer_word_break_characters.should be_nil
+ end
+ end
+
+ describe "Readline.completer_word_break_characters=" do
+ it "returns the passed string" do
+ Readline.completer_word_break_characters = "test"
+ Readline.completer_word_break_characters.should == "test"
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/completion_append_character_spec.rb b/spec/ruby/library/readline/completion_append_character_spec.rb
new file mode 100644
index 0000000000..615b523f4e
--- /dev/null
+++ b/spec/ruby/library/readline/completion_append_character_spec.rb
@@ -0,0 +1,16 @@
+require_relative 'spec_helper'
+
+with_feature :readline do
+ describe "Readline.completion_append_character" do
+ it "returns not nil" do
+ Readline.completion_append_character.should_not be_nil
+ end
+ end
+
+ describe "Readline.completion_append_character=" do
+ it "returns the first character of the passed string" do
+ Readline.completion_append_character = "test"
+ Readline.completion_append_character.should == "t"
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/completion_case_fold_spec.rb b/spec/ruby/library/readline/completion_case_fold_spec.rb
new file mode 100644
index 0000000000..966f5d6c79
--- /dev/null
+++ b/spec/ruby/library/readline/completion_case_fold_spec.rb
@@ -0,0 +1,18 @@
+require_relative 'spec_helper'
+
+with_feature :readline do
+ describe "Readline.completion_case_fold" do
+ it "returns nil" do
+ Readline.completion_case_fold.should be_nil
+ end
+ end
+
+ describe "Readline.completion_case_fold=" do
+ it "returns the passed boolean" do
+ Readline.completion_case_fold = true
+ Readline.completion_case_fold.should == true
+ Readline.completion_case_fold = false
+ Readline.completion_case_fold.should == false
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/completion_proc_spec.rb b/spec/ruby/library/readline/completion_proc_spec.rb
new file mode 100644
index 0000000000..2d7a353ec5
--- /dev/null
+++ b/spec/ruby/library/readline/completion_proc_spec.rb
@@ -0,0 +1,22 @@
+require_relative 'spec_helper'
+
+with_feature :readline do
+ describe "Readline.completion_proc" do
+ it "returns nil" do
+ Readline.completion_proc.should be_nil
+ end
+ end
+
+ describe "Readline.completion_proc=" do
+ it "returns the passed Proc" do
+ proc = Proc.new do |e|
+ end
+ Readline.completion_proc = proc
+ Readline.completion_proc.should == proc
+ end
+
+ it "returns an ArgumentError if not given an Proc or #call" do
+ -> { Readline.completion_proc = "test" }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/constants_spec.rb b/spec/ruby/library/readline/constants_spec.rb
new file mode 100644
index 0000000000..8fee274866
--- /dev/null
+++ b/spec/ruby/library/readline/constants_spec.rb
@@ -0,0 +1,18 @@
+require_relative 'spec_helper'
+
+with_feature :readline do
+ # Note: additional specs for HISTORY are in 'history' subdir.
+ describe "Readline::HISTORY" do
+ it "is defined" do
+ Readline.const_defined?(:HISTORY).should == true
+ end
+ end
+
+ describe "Readline::VERSION" do
+ it "is defined and is a non-empty String" do
+ Readline.const_defined?(:VERSION).should == true
+ Readline::VERSION.should be_kind_of(String)
+ Readline::VERSION.should_not be_empty
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/emacs_editing_mode_spec.rb b/spec/ruby/library/readline/emacs_editing_mode_spec.rb
new file mode 100644
index 0000000000..f7e8eda982
--- /dev/null
+++ b/spec/ruby/library/readline/emacs_editing_mode_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'spec_helper'
+
+platform_is_not :darwin do
+ with_feature :readline do
+ describe "Readline.emacs_editing_mode" do
+ it "returns nil" do
+ Readline.emacs_editing_mode.should be_nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/filename_quote_characters_spec.rb b/spec/ruby/library/readline/filename_quote_characters_spec.rb
new file mode 100644
index 0000000000..de8ce700a8
--- /dev/null
+++ b/spec/ruby/library/readline/filename_quote_characters_spec.rb
@@ -0,0 +1,18 @@
+require_relative 'spec_helper'
+
+platform_is_not :darwin do
+ with_feature :readline do
+ describe "Readline.filename_quote_characters" do
+ it "returns nil" do
+ Readline.filename_quote_characters.should be_nil
+ end
+ end
+
+ describe "Readline.filename_quote_characters=" do
+ it "returns the passed string" do
+ Readline.filename_quote_characters = "test"
+ Readline.filename_quote_characters.should == "test"
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/append_spec.rb b/spec/ruby/library/readline/history/append_spec.rb
new file mode 100644
index 0000000000..5383271374
--- /dev/null
+++ b/spec/ruby/library/readline/history/append_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY.<<" do
+ it "appends the given Object to the history" do
+ Readline::HISTORY << "1"
+ Readline::HISTORY.size.should == 1
+
+ Readline::HISTORY << "2"
+ Readline::HISTORY.size.should == 2
+
+ Readline::HISTORY.pop.should == "2"
+ Readline::HISTORY.pop.should == "1"
+ end
+
+ it "tries to convert the passed Object to a String using #to_str" do
+ obj = mock("Converted to String")
+ obj.should_receive(:to_str).and_return("converted")
+
+ Readline::HISTORY << obj
+ Readline::HISTORY.pop.should == "converted"
+ end
+
+ it "raises a TypeError when the passed Object can't be converted to a String" do
+ -> { Readline::HISTORY << mock("Object") }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/delete_at_spec.rb b/spec/ruby/library/readline/history/delete_at_spec.rb
new file mode 100644
index 0000000000..3bd577e75c
--- /dev/null
+++ b/spec/ruby/library/readline/history/delete_at_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY.delete_at" do
+ it "deletes and returns the history entry at the specified index" do
+ Readline::HISTORY.push("1", "2", "3")
+
+ Readline::HISTORY.delete_at(1).should == "2"
+ Readline::HISTORY.size.should == 2
+
+ Readline::HISTORY.delete_at(1).should == "3"
+ Readline::HISTORY.size.should == 1
+
+ Readline::HISTORY.delete_at(0).should == "1"
+ Readline::HISTORY.size.should == 0
+
+
+ Readline::HISTORY.push("1", "2", "3", "4")
+
+ Readline::HISTORY.delete_at(-2).should == "3"
+ Readline::HISTORY.size.should == 3
+
+ Readline::HISTORY.delete_at(-2).should == "2"
+ Readline::HISTORY.size.should == 2
+
+ Readline::HISTORY.delete_at(0).should == "1"
+ Readline::HISTORY.size.should == 1
+
+ Readline::HISTORY.delete_at(0).should == "4"
+ Readline::HISTORY.size.should == 0
+ end
+
+ it "raises an IndexError when the given index is greater than the history size" do
+ -> { Readline::HISTORY.delete_at(10) }.should raise_error(IndexError)
+ -> { Readline::HISTORY.delete_at(-10) }.should raise_error(IndexError)
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/each_spec.rb b/spec/ruby/library/readline/history/each_spec.rb
new file mode 100644
index 0000000000..aa48dd46df
--- /dev/null
+++ b/spec/ruby/library/readline/history/each_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY.each" do
+ before :each do
+ Readline::HISTORY.push("1", "2", "3")
+ end
+
+ after :each do
+ Readline::HISTORY.pop
+ Readline::HISTORY.pop
+ Readline::HISTORY.pop
+ end
+
+ it "yields each item in the history" do
+ result = []
+ Readline::HISTORY.each do |x|
+ result << x
+ end
+ result.should == ["1", "2", "3"]
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/element_reference_spec.rb b/spec/ruby/library/readline/history/element_reference_spec.rb
new file mode 100644
index 0000000000..0a74f3d62d
--- /dev/null
+++ b/spec/ruby/library/readline/history/element_reference_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY.[]" do
+ before :each do
+ Readline::HISTORY.push("1", "2", "3")
+ end
+
+ after :each do
+ Readline::HISTORY.pop
+ Readline::HISTORY.pop
+ Readline::HISTORY.pop
+ end
+
+ it "returns the history item at the passed index" do
+ Readline::HISTORY[0].should == "1"
+ Readline::HISTORY[1].should == "2"
+ Readline::HISTORY[2].should == "3"
+
+ Readline::HISTORY[-1].should == "3"
+ Readline::HISTORY[-2].should == "2"
+ Readline::HISTORY[-3].should == "1"
+ end
+
+ it "raises an IndexError when there is no item at the passed index" do
+ -> { Readline::HISTORY[-10] }.should raise_error(IndexError)
+ -> { Readline::HISTORY[-9] }.should raise_error(IndexError)
+ -> { Readline::HISTORY[-8] }.should raise_error(IndexError)
+
+ -> { Readline::HISTORY[8] }.should raise_error(IndexError)
+ -> { Readline::HISTORY[9] }.should raise_error(IndexError)
+ -> { Readline::HISTORY[10] }.should raise_error(IndexError)
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/element_set_spec.rb b/spec/ruby/library/readline/history/element_set_spec.rb
new file mode 100644
index 0000000000..776adaacd1
--- /dev/null
+++ b/spec/ruby/library/readline/history/element_set_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY.[]=" do
+ before :each do
+ Readline::HISTORY.push("1", "2", "3")
+ end
+
+ after :each do
+ Readline::HISTORY.pop
+ Readline::HISTORY.pop
+ Readline::HISTORY.pop
+ end
+
+ it "returns the new value for the passed index" do
+ (Readline::HISTORY[1] = "second test").should == "second test"
+ end
+
+ it "raises an IndexError when there is no item at the passed positive index" do
+ -> { Readline::HISTORY[10] = "test" }.should raise_error(IndexError)
+ end
+
+ it "sets the item at the given index" do
+ Readline::HISTORY[0] = "test"
+ Readline::HISTORY[0].should == "test"
+
+ Readline::HISTORY[1] = "second test"
+ Readline::HISTORY[1].should == "second test"
+ end
+
+ it "raises an IndexError when there is no item at the passed negative index" do
+ -> { Readline::HISTORY[10] = "test" }.should raise_error(IndexError)
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/empty_spec.rb b/spec/ruby/library/readline/history/empty_spec.rb
new file mode 100644
index 0000000000..31d01d9601
--- /dev/null
+++ b/spec/ruby/library/readline/history/empty_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY.empty?" do
+ it "returns true when the history is empty" do
+ Readline::HISTORY.should be_empty
+ Readline::HISTORY.push("test")
+ Readline::HISTORY.should_not be_empty
+ Readline::HISTORY.pop
+ Readline::HISTORY.should be_empty
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/history_spec.rb b/spec/ruby/library/readline/history/history_spec.rb
new file mode 100644
index 0000000000..927dd52ebf
--- /dev/null
+++ b/spec/ruby/library/readline/history/history_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY" do
+ it "is extended with the Enumerable module" do
+ Readline::HISTORY.should be_kind_of(Enumerable)
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/length_spec.rb b/spec/ruby/library/readline/history/length_spec.rb
new file mode 100644
index 0000000000..9427d10a00
--- /dev/null
+++ b/spec/ruby/library/readline/history/length_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ require_relative 'shared/size'
+
+ describe "Readline::HISTORY.length" do
+ it_behaves_like :readline_history_size, :length
+ end
+end
diff --git a/spec/ruby/library/readline/history/pop_spec.rb b/spec/ruby/library/readline/history/pop_spec.rb
new file mode 100644
index 0000000000..156a8a06f8
--- /dev/null
+++ b/spec/ruby/library/readline/history/pop_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY.pop" do
+ it "returns nil when the history is empty" do
+ Readline::HISTORY.pop.should be_nil
+ end
+
+ it "returns and removes the last item from the history" do
+ Readline::HISTORY.push("1", "2", "3")
+ Readline::HISTORY.size.should == 3
+
+ Readline::HISTORY.pop.should == "3"
+ Readline::HISTORY.size.should == 2
+
+ Readline::HISTORY.pop.should == "2"
+ Readline::HISTORY.size.should == 1
+
+ Readline::HISTORY.pop.should == "1"
+ Readline::HISTORY.size.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/push_spec.rb b/spec/ruby/library/readline/history/push_spec.rb
new file mode 100644
index 0000000000..53505ccba6
--- /dev/null
+++ b/spec/ruby/library/readline/history/push_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY.push" do
+ it "pushes all passed Objects into the history" do
+ Readline::HISTORY.push("1", "2", "3")
+ Readline::HISTORY.size.should == 3
+
+ Readline::HISTORY.pop.should == "3"
+ Readline::HISTORY.pop.should == "2"
+ Readline::HISTORY.pop.should == "1"
+ end
+
+ it "tries to convert the passed Object to a String using #to_str" do
+ obj = mock("Converted to String")
+ obj.should_receive(:to_str).and_return("converted")
+
+ Readline::HISTORY.push(obj)
+ Readline::HISTORY.pop.should == "converted"
+ end
+
+ it "raises a TypeError when the passed Object can't be converted to a String" do
+ -> { Readline::HISTORY.push(mock("Object")) }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/shared/size.rb b/spec/ruby/library/readline/history/shared/size.rb
new file mode 100644
index 0000000000..1d6df86f78
--- /dev/null
+++ b/spec/ruby/library/readline/history/shared/size.rb
@@ -0,0 +1,14 @@
+describe :readline_history_size, shared: true do
+ it "returns the size of the history" do
+ Readline::HISTORY.send(@method).should == 0
+ Readline::HISTORY.push("1", "2", "")
+ Readline::HISTORY.send(@method).should == 3
+
+ Readline::HISTORY.pop
+ Readline::HISTORY.send(@method).should == 2
+
+ Readline::HISTORY.pop
+ Readline::HISTORY.pop
+ Readline::HISTORY.send(@method).should == 0
+ end
+end
diff --git a/spec/ruby/library/readline/history/shift_spec.rb b/spec/ruby/library/readline/history/shift_spec.rb
new file mode 100644
index 0000000000..9aad7d5399
--- /dev/null
+++ b/spec/ruby/library/readline/history/shift_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY.shift" do
+ it "returns nil when the history is empty" do
+ Readline::HISTORY.shift.should be_nil
+ end
+
+ it "returns and removes the first item from the history" do
+ Readline::HISTORY.push("1", "2", "3")
+ Readline::HISTORY.size.should == 3
+
+ Readline::HISTORY.shift.should == "1"
+ Readline::HISTORY.size.should == 2
+
+ Readline::HISTORY.shift.should == "2"
+ Readline::HISTORY.size.should == 1
+
+ Readline::HISTORY.shift.should == "3"
+ Readline::HISTORY.size.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/history/size_spec.rb b/spec/ruby/library/readline/history/size_spec.rb
new file mode 100644
index 0000000000..c55253ccea
--- /dev/null
+++ b/spec/ruby/library/readline/history/size_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ require_relative 'shared/size'
+
+ describe "Readline::HISTORY.size" do
+ it_behaves_like :readline_history_size, :size
+ end
+end
diff --git a/spec/ruby/library/readline/history/to_s_spec.rb b/spec/ruby/library/readline/history/to_s_spec.rb
new file mode 100644
index 0000000000..ee338f2ab4
--- /dev/null
+++ b/spec/ruby/library/readline/history/to_s_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../spec_helper'
+
+with_feature :readline do
+ describe "Readline::HISTORY.to_s" do
+ it "returns 'HISTORY'" do
+ Readline::HISTORY.to_s.should == "HISTORY"
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/readline_spec.rb b/spec/ruby/library/readline/readline_spec.rb
new file mode 100644
index 0000000000..6e349ad543
--- /dev/null
+++ b/spec/ruby/library/readline/readline_spec.rb
@@ -0,0 +1,26 @@
+require_relative 'spec_helper'
+
+with_feature :readline do
+ describe "Readline.readline" do
+ before :each do
+ @file = tmp('readline')
+ @out = tmp('out.txt')
+ touch(@file) { |f|
+ f.puts "test"
+ }
+ @options = { options: "-rreadline", args: [@out, "< #{@file}"] }
+ end
+
+ after :each do
+ rm_r @file, @out
+ end
+
+ # Somehow those specs block on Windows
+ platform_is_not :windows do
+ it "returns the input string" do
+ ruby_exe('File.write ARGV[0], Readline.readline', @options)
+ File.read(@out).should == "test"
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/readline/spec_helper.rb b/spec/ruby/library/readline/spec_helper.rb
new file mode 100644
index 0000000000..32d820f7ac
--- /dev/null
+++ b/spec/ruby/library/readline/spec_helper.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+begin
+ require 'readline'
+rescue LoadError
+else
+ # rb-readline and reline behave quite differently
+ unless defined?(RbReadline) or defined?(Reline)
+ MSpec.enable_feature :readline
+ end
+end
diff --git a/spec/ruby/library/readline/vi_editing_mode_spec.rb b/spec/ruby/library/readline/vi_editing_mode_spec.rb
new file mode 100644
index 0000000000..6622962ceb
--- /dev/null
+++ b/spec/ruby/library/readline/vi_editing_mode_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'spec_helper'
+
+platform_is_not :darwin do
+ with_feature :readline do
+ describe "Readline.vi_editing_mode" do
+ it "returns nil" do
+ Readline.vi_editing_mode.should be_nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/resolv/fixtures/hosts b/spec/ruby/library/resolv/fixtures/hosts
new file mode 100644
index 0000000000..a50f3d6a69
--- /dev/null
+++ b/spec/ruby/library/resolv/fixtures/hosts
@@ -0,0 +1 @@
+127.0.0.1 localhost localhost4
diff --git a/spec/ruby/library/resolv/get_address_spec.rb b/spec/ruby/library/resolv/get_address_spec.rb
new file mode 100644
index 0000000000..ecc2cdf7de
--- /dev/null
+++ b/spec/ruby/library/resolv/get_address_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require 'resolv'
+
+describe "Resolv#getaddress" do
+ it "resolves localhost" do
+ hosts = Resolv::Hosts.new(fixture(__FILE__ , "hosts"))
+ res = Resolv.new([hosts])
+
+ res.getaddress("localhost").should == "127.0.0.1"
+ res.getaddress("localhost4").should == "127.0.0.1"
+ end
+
+ it "raises ResolvError if the name can not be looked up" do
+ res = Resolv.new([])
+ -> {
+ res.getaddress("should.raise.error.")
+ }.should raise_error(Resolv::ResolvError)
+ end
+end
diff --git a/spec/ruby/library/resolv/get_addresses_spec.rb b/spec/ruby/library/resolv/get_addresses_spec.rb
new file mode 100644
index 0000000000..b84f29b7da
--- /dev/null
+++ b/spec/ruby/library/resolv/get_addresses_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require 'resolv'
+
+describe "Resolv#getaddresses" do
+ it "resolves localhost" do
+ hosts = Resolv::Hosts.new(fixture(__FILE__ , "hosts"))
+ res = Resolv.new([hosts])
+
+ res.getaddresses("localhost").should == ["127.0.0.1"]
+ res.getaddresses("localhost4").should == ["127.0.0.1"]
+ end
+end
diff --git a/spec/ruby/library/resolv/get_name_spec.rb b/spec/ruby/library/resolv/get_name_spec.rb
new file mode 100644
index 0000000000..3ef97a2cea
--- /dev/null
+++ b/spec/ruby/library/resolv/get_name_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require 'resolv'
+
+describe "Resolv#getname" do
+ it "resolves 127.0.0.1" do
+ hosts = Resolv::Hosts.new(fixture(__FILE__ , "hosts"))
+ res = Resolv.new([hosts])
+
+ res.getname("127.0.0.1").should == "localhost"
+ end
+
+ it "raises ResolvError when there is no result" do
+ res = Resolv.new([])
+ -> {
+ res.getname("should.raise.error")
+ }.should raise_error(Resolv::ResolvError)
+ end
+end
diff --git a/spec/ruby/library/resolv/get_names_spec.rb b/spec/ruby/library/resolv/get_names_spec.rb
new file mode 100644
index 0000000000..c405360615
--- /dev/null
+++ b/spec/ruby/library/resolv/get_names_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require 'resolv'
+
+describe "Resolv#getnames" do
+ it "resolves 127.0.0.1" do
+ hosts = Resolv::Hosts.new(fixture(__FILE__ , "hosts"))
+ res = Resolv.new([hosts])
+
+ names = res.getnames("127.0.0.1").should == ["localhost", "localhost4"]
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/clone_spec.rb b/spec/ruby/library/rexml/attribute/clone_spec.rb
new file mode 100644
index 0000000000..5c86468d45
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/clone_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#clone" do
+ it "returns a copy of this Attribute" do
+ orig = REXML::Attribute.new("name", "value&&")
+ orig.should == orig.clone
+ orig.clone.to_s.should == orig.to_s
+ orig.clone.to_string.should == orig.to_string
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/element_spec.rb b/spec/ruby/library/rexml/attribute/element_spec.rb
new file mode 100644
index 0000000000..0e4ce46a4f
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/element_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#element" do
+ it "returns the parent element" do
+ e = REXML::Element.new "root"
+
+ REXML::Attribute.new("name", "value", e).element.should == e
+ REXML::Attribute.new("name", "default_constructor").element.should == nil
+ end
+ end
+
+ describe "REXML::Attribute#element=" do
+ it "sets the parent element" do
+ e = REXML::Element.new "root"
+ f = REXML::Element.new "temp"
+ a = REXML::Attribute.new("name", "value", e)
+ a.element.should == e
+
+ a.element = f
+ a.element.should == f
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/equal_value_spec.rb b/spec/ruby/library/rexml/attribute/equal_value_spec.rb
new file mode 100644
index 0000000000..1498bae624
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/equal_value_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#==" do
+ it "returns true if other has equal name and value" do
+ a1 = REXML::Attribute.new("foo", "bar")
+ a1.should == a1.clone
+
+ a2 = REXML::Attribute.new("foo", "bar")
+ a1.should == a2
+
+ a3 = REXML::Attribute.new("foo", "bla")
+ a1.should_not == a3
+
+ a4 = REXML::Attribute.new("baz", "bar")
+ a1.should_not == a4
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/hash_spec.rb b/spec/ruby/library/rexml/attribute/hash_spec.rb
new file mode 100644
index 0000000000..7e0cbcc1ea
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/hash_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#hash" do
+ # These are not really complete, any idea on how to make them more
+ # "testable" will be appreciated.
+ it "returns a hashcode made of the name and value of self" do
+ a = REXML::Attribute.new("name", "value")
+ a.hash.should be_kind_of(Numeric)
+ b = REXML::Attribute.new(a)
+ a.hash.should == b.hash
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/initialize_spec.rb b/spec/ruby/library/rexml/attribute/initialize_spec.rb
new file mode 100644
index 0000000000..35b87b0733
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/initialize_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#initialize" do
+ before :each do
+ @e = REXML::Element.new "root"
+ @name = REXML::Attribute.new("name", "Nicko")
+ @e.add_attribute @name
+ end
+
+ it "receives two strings for name and value" do
+ @e.attributes["name"].should == "Nicko"
+ @e.add_attribute REXML::Attribute.new("last_name", nil)
+ @e.attributes["last_name"].should == ""
+ end
+
+ it "receives an Attribute and clones it" do
+ copy = REXML::Attribute.new(@name)
+ copy.should == @name
+ end
+
+ it "receives a parent node" do
+ last_name = REXML::Attribute.new("last_name", "McBrain", @e)
+ last_name.element.should == @e
+
+ last_name = REXML::Attribute.new(@name, @e)
+ last_name.element.should == @e
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/inspect_spec.rb b/spec/ruby/library/rexml/attribute/inspect_spec.rb
new file mode 100644
index 0000000000..ee5236b98e
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/inspect_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#inspect" do
+ it "returns the name and value as a string" do
+ a = REXML::Attribute.new("my_name", "my_value")
+ a.inspect.should == "my_name='my_value'"
+ end
+
+ it "accepts attributes with no value" do
+ a = REXML::Attribute.new("my_name")
+ a.inspect.should == "my_name=''"
+ end
+
+ it "does not escape text" do
+ a = REXML::Attribute.new("name", "<>")
+ a.inspect.should == "name='<>'"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/namespace_spec.rb b/spec/ruby/library/rexml/attribute/namespace_spec.rb
new file mode 100644
index 0000000000..645b3cd1b1
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/namespace_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#namespace" do
+ it "returns the namespace url" do
+ e = REXML::Element.new("root")
+ e.add_attribute REXML::Attribute.new("xmlns:ns", "http://some_uri")
+ e.namespace("ns").should == "http://some_uri"
+ end
+
+ it "returns nil if namespace is not defined" do
+ e = REXML::Element.new("root")
+ e.add_attribute REXML::Attribute.new("test", "value")
+ e.namespace("test").should == nil
+ e.namespace("ns").should == nil
+ end
+
+ it "defaults arg to nil" do
+ e = REXML::Element.new("root")
+ e.add_attribute REXML::Attribute.new("xmlns:ns", "http://some_uri")
+ e.namespace.should == ""
+ e.namespace("ns").should == "http://some_uri"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/node_type_spec.rb b/spec/ruby/library/rexml/attribute/node_type_spec.rb
new file mode 100644
index 0000000000..da055ae8f0
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/node_type_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#node_type" do
+ it "always returns :attribute" do
+ attr = REXML::Attribute.new("foo", "bar")
+ attr.node_type.should == :attribute
+ REXML::Attribute.new(attr).node_type.should == :attribute
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/prefix_spec.rb b/spec/ruby/library/rexml/attribute/prefix_spec.rb
new file mode 100644
index 0000000000..87bff4822b
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/prefix_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#prefix" do
+ it "returns the namespace of the Attribute" do
+ ans = REXML::Attribute.new("ns:someattr", "some_value")
+ out = REXML::Attribute.new("out:something", "some_other_value")
+
+ ans.prefix.should == "ns"
+ out.prefix.should == "out"
+ end
+
+ it "returns an empty string for Attributes with no prefixes" do
+ attr = REXML::Attribute.new("foo", "bar")
+
+ attr.prefix.should == ""
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/remove_spec.rb b/spec/ruby/library/rexml/attribute/remove_spec.rb
new file mode 100644
index 0000000000..5f928b1286
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/remove_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#remove" do
+ before :each do
+ @e = REXML::Element.new "Root"
+ @attr = REXML::Attribute.new("foo", "bar")
+ end
+
+ it "deletes this Attribute from parent" do
+ @e.add_attribute(@attr)
+ @e.attributes["foo"].should_not == nil
+ @attr.remove
+ @e.attributes["foo"].should == nil
+ end
+
+ it "does not anything if element has no parent" do
+ -> {@attr.remove}.should_not raise_error(Exception)
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/to_s_spec.rb b/spec/ruby/library/rexml/attribute/to_s_spec.rb
new file mode 100644
index 0000000000..e362cee8f1
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/to_s_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#to_s" do
+ it "returns the value of the Attribute" do
+ REXML::Attribute.new("name", "some_value").to_s.should == "some_value"
+ end
+
+ it "returns the escaped value if it was created from Attribute" do
+ orig = REXML::Attribute.new("name", "<&>")
+ copy = REXML::Attribute.new(orig)
+ copy.to_s.should == "&lt;&amp;&gt;"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/to_string_spec.rb b/spec/ruby/library/rexml/attribute/to_string_spec.rb
new file mode 100644
index 0000000000..a9d249f5bb
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/to_string_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#to_string" do
+ it "returns the attribute as XML" do
+ attr = REXML::Attribute.new("name", "value")
+ attr_empty = REXML::Attribute.new("name")
+ attr_ns = REXML::Attribute.new("xmlns:ns", "http://uri")
+
+ attr.to_string.should == "name='value'"
+ attr_empty.to_string.should == "name=''"
+ attr_ns.to_string.should == "xmlns:ns='http://uri'"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/value_spec.rb b/spec/ruby/library/rexml/attribute/value_spec.rb
new file mode 100644
index 0000000000..77071f6f70
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/value_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#value" do
+ it "returns the value of the Attribute unnormalized" do
+ attr = REXML::Attribute.new("name", "value")
+ attr_ents = REXML::Attribute.new("name", "<&>")
+ attr_empty = REXML::Attribute.new("name")
+
+ attr.value.should == "value"
+ attr_ents.value.should == "<&>"
+ attr_empty.value.should == ""
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/write_spec.rb b/spec/ruby/library/rexml/attribute/write_spec.rb
new file mode 100644
index 0000000000..0012b3cc77
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/write_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#write" do
+ before :each do
+ @attr = REXML::Attribute.new("name", "Charlotte")
+ @s = ""
+ end
+
+ it "writes the name and value to output" do
+ @attr.write(@s)
+ @s.should == "name='Charlotte'"
+ end
+
+ it "currently ignores the second argument" do
+ @attr.write(@s, 3)
+ @s.should == "name='Charlotte'"
+
+ @s = ""
+ @attr.write(@s, "foo")
+ @s.should == "name='Charlotte'"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attribute/xpath_spec.rb b/spec/ruby/library/rexml/attribute/xpath_spec.rb
new file mode 100644
index 0000000000..0a09046b01
--- /dev/null
+++ b/spec/ruby/library/rexml/attribute/xpath_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attribute#xpath" do
+
+ before :each do
+ @e = REXML::Element.new "root"
+ @attr = REXML::Attribute.new("year", "1989")
+ end
+
+ it "returns the path for Attribute" do
+ @e.add_attribute @attr
+ @attr.xpath.should == "root/@year"
+ end
+
+ it "raises an error if attribute has no parent" do
+ -> { @attr.xpath }.should raise_error(Exception)
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/add_spec.rb b/spec/ruby/library/rexml/attributes/add_spec.rb
new file mode 100644
index 0000000000..e24e9fabbc
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/add_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require_relative 'shared/add'
+ require 'rexml/document'
+
+ describe "REXML::Attributes#add" do
+ it_behaves_like :rexml_attribute_add, :add
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/append_spec.rb b/spec/ruby/library/rexml/attributes/append_spec.rb
new file mode 100644
index 0000000000..f96a727f47
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/append_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require_relative 'shared/add'
+ require 'rexml/document'
+
+ describe "REXML::Attributes#<<" do
+ it_behaves_like :rexml_attribute_add, :<<
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/delete_all_spec.rb b/spec/ruby/library/rexml/attributes/delete_all_spec.rb
new file mode 100644
index 0000000000..707baa235b
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/delete_all_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#delete_all" do
+ before :each do
+ @e = REXML::Element.new("root")
+ end
+
+ it "deletes all attributes that match name" do
+ uri = REXML::Attribute.new("uri", "http://something")
+ @e.attributes << uri
+ @e.attributes.delete_all("uri")
+ @e.attributes.should be_empty
+ @e.attributes["uri"].should == nil
+ end
+
+ it "deletes all attributes that match name with a namespace" do
+ ns_uri = REXML::Attribute.new("xmlns:uri", "http://something_here_too")
+ @e.attributes << ns_uri
+ @e.attributes.delete_all("xmlns:uri")
+ @e.attributes.should be_empty
+ @e.attributes["xmlns:uri"].should == nil
+ end
+
+ it "returns the removed attribute" do
+ uri = REXML::Attribute.new("uri", "http://something_here_too")
+ @e.attributes << uri
+ attrs = @e.attributes.delete_all("uri")
+ attrs.first.should == uri
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/delete_spec.rb b/spec/ruby/library/rexml/attributes/delete_spec.rb
new file mode 100644
index 0000000000..723fa70751
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/delete_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#delete" do
+ before :each do
+ @e = REXML::Element.new("root")
+ @name = REXML::Attribute.new("name", "Pepe")
+ end
+
+ it "takes an attribute name and deletes the attribute" do
+ @e.attributes.delete("name")
+ @e.attributes["name"].should be_nil
+ @e.attributes.should be_empty
+ end
+
+ it "takes an Attribute and deletes it" do
+ @e.attributes.delete(@name)
+ @e.attributes["name"].should be_nil
+ @e.attributes.should be_empty
+ end
+
+ it "returns the element with the attribute removed" do
+ ret_val = @e.attributes.delete(@name)
+ ret_val.should == @e
+ ret_val.attributes.should be_empty
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/each_attribute_spec.rb b/spec/ruby/library/rexml/attributes/each_attribute_spec.rb
new file mode 100644
index 0000000000..692cf4f943
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/each_attribute_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#each_attribute" do
+ it "iterates over the attributes yielding actual Attribute objects" do
+ e = REXML::Element.new("root")
+ name = REXML::Attribute.new("name", "Joe")
+ ns_uri = REXML::Attribute.new("xmlns:ns", "http://some_uri")
+ e.add_attribute name
+ e.add_attribute ns_uri
+
+ attributes = []
+
+ e.attributes.each_attribute do |attr|
+ attributes << attr
+ end
+
+ attributes = attributes.sort_by {|a| a.name }
+ attributes.first.should == name
+ attributes.last.should == ns_uri
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/each_spec.rb b/spec/ruby/library/rexml/attributes/each_spec.rb
new file mode 100644
index 0000000000..49add3b77b
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/each_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#each" do
+ before :each do
+ @e = REXML::Element.new("root")
+ @name = REXML::Attribute.new("name", "Joe")
+ @ns_uri = REXML::Attribute.new("xmlns:ns", "http://some_uri")
+ @e.add_attribute @name
+ @e.add_attribute @ns_uri
+ end
+
+ it "iterates over the attributes yielding expanded-name/value" do
+ attributes = []
+ @e.attributes.each do |attr|
+ attr.should be_kind_of(Array)
+ attributes << attr
+ end
+ attributes = attributes.sort_by {|a| a.first }
+ attributes.first.should == ["name", "Joe"]
+ attributes.last.should == ["xmlns:ns", "http://some_uri"]
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/element_reference_spec.rb b/spec/ruby/library/rexml/attributes/element_reference_spec.rb
new file mode 100644
index 0000000000..0d089eaab2
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/element_reference_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#[]" do
+ before :each do
+ @e = REXML::Element.new("root")
+ @lang = REXML::Attribute.new("language", "english")
+ @e.attributes << @lang
+ end
+
+ it "returns the value of an attribute" do
+ @e.attributes["language"].should == "english"
+ end
+
+ it "returns nil if the attribute does not exist" do
+ @e.attributes["chunky bacon"].should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/element_set_spec.rb b/spec/ruby/library/rexml/attributes/element_set_spec.rb
new file mode 100644
index 0000000000..834ad682a6
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/element_set_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#[]=" do
+ before :each do
+ @e = REXML::Element.new("song")
+ @name = REXML::Attribute.new("name", "Holy Smoke!")
+ @e.attributes << @name
+ end
+
+ it "sets an attribute" do
+ @e.attributes["author"] = "_why's foxes"
+ @e.attributes["author"].should == "_why's foxes"
+ end
+
+ it "overwrites an existing attribute" do
+ @e.attributes["name"] = "Chunky Bacon"
+ @e.attributes["name"].should == "Chunky Bacon"
+ end
+
+ it "deletes an attribute is value is nil" do
+ @e.attributes["name"] = nil
+ @e.attributes.length.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/get_attribute_ns_spec.rb b/spec/ruby/library/rexml/attributes/get_attribute_ns_spec.rb
new file mode 100644
index 0000000000..1109ff519c
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/get_attribute_ns_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#get_attribute_ns" do
+ it "returns an attribute by name and namespace" do
+ e = REXML::Element.new("root")
+ attr = REXML::Attribute.new("xmlns:ns", "http://some_url")
+ e.attributes << attr
+ attr.prefix.should == "xmlns"
+ # This might be a bug in Attribute, commenting until those specs
+ # are ready
+ # e.attributes.get_attribute_ns(attr.prefix, "name").should == "http://some_url"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/get_attribute_spec.rb b/spec/ruby/library/rexml/attributes/get_attribute_spec.rb
new file mode 100644
index 0000000000..cc94191729
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/get_attribute_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#get_attribute" do
+ before :each do
+ @e = REXML::Element.new("root")
+ @name = REXML::Attribute.new("name", "Dave")
+ @e.attributes << @name
+ end
+
+ it "fetches an attributes" do
+ @e.attributes.get_attribute("name").should == @name
+ end
+
+ it "fetches an namespaced attribute" do
+ ns_name = REXML::Attribute.new("im:name", "Murray")
+ @e.attributes << ns_name
+ @e.attributes.get_attribute("name").should == @name
+ @e.attributes.get_attribute("im:name").should == ns_name
+ end
+
+ it "returns an Attribute" do
+ @e.attributes.get_attribute("name").should be_kind_of(REXML::Attribute)
+ end
+
+ it "returns nil if it attribute does not exist" do
+ @e.attributes.get_attribute("fake").should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/initialize_spec.rb b/spec/ruby/library/rexml/attributes/initialize_spec.rb
new file mode 100644
index 0000000000..42ec742e60
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/initialize_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#initialize" do
+ it "is auto initialized by Element" do
+ e = REXML::Element.new "root"
+ e.attributes.should be_kind_of(REXML::Attributes)
+
+ e.attributes << REXML::Attribute.new("name", "Paul")
+ e.attributes["name"].should == "Paul"
+ end
+
+ it "receives a parent node" do
+ e = REXML::Element.new "root"
+ e.attributes << REXML::Attribute.new("name", "Vic")
+ e.attributes["name"].should == "Vic"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/length_spec.rb b/spec/ruby/library/rexml/attributes/length_spec.rb
new file mode 100644
index 0000000000..81733b4a96
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/length_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require_relative 'shared/length'
+ require 'rexml/document'
+
+ describe "REXML::Attributes#length" do
+ it_behaves_like :rexml_attribute_length, :length
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/namespaces_spec.rb b/spec/ruby/library/rexml/attributes/namespaces_spec.rb
new file mode 100644
index 0000000000..b88346854f
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/namespaces_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#namespaces" do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/prefixes_spec.rb b/spec/ruby/library/rexml/attributes/prefixes_spec.rb
new file mode 100644
index 0000000000..574b7ffbaf
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/prefixes_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#prefixes" do
+ before :each do
+ @e = REXML::Element.new("root")
+ a1 = REXML::Attribute.new("xmlns:a", "bar")
+ a2 = REXML::Attribute.new("xmlns:b", "bla")
+ a3 = REXML::Attribute.new("xmlns:c", "baz")
+ @e.attributes << a1
+ @e.attributes << a2
+ @e.attributes << a3
+
+ @e.attributes << REXML::Attribute.new("xmlns", "foo")
+ end
+
+ it "returns an array with the prefixes of each attribute" do
+ @e.attributes.prefixes.sort.should == ["a", "b", "c"]
+ end
+
+ it "does not include the default namespace" do
+ @e.attributes.prefixes.include?("xmlns").should == false
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/shared/add.rb b/spec/ruby/library/rexml/attributes/shared/add.rb
new file mode 100644
index 0000000000..872f149f45
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/shared/add.rb
@@ -0,0 +1,17 @@
+describe :rexml_attribute_add, shared: true do
+ before :each do
+ @e = REXML::Element.new("root")
+ @attr = REXML::Attributes.new(@e)
+ @name = REXML::Attribute.new("name", "Joe")
+ end
+
+ it "adds an attribute" do
+ @attr.send(@method, @name)
+ @attr["name"].should == "Joe"
+ end
+
+ it "replaces an existing attribute" do
+ @attr.send(@method, REXML::Attribute.new("name", "Bruce"))
+ @attr["name"].should == "Bruce"
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/shared/length.rb b/spec/ruby/library/rexml/attributes/shared/length.rb
new file mode 100644
index 0000000000..7848f9bf33
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/shared/length.rb
@@ -0,0 +1,13 @@
+require_relative '../../../../spec_helper'
+require 'rexml/document'
+
+describe :rexml_attribute_length, shared: true do
+ it "returns the number of attributes" do
+ e = REXML::Element.new("root")
+ e.attributes.send(@method).should == 0
+
+ e.attributes << REXML::Attribute.new("name", "John")
+ e.attributes << REXML::Attribute.new("another_name", "Leo")
+ e.attributes.send(@method).should == 2
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/size_spec.rb b/spec/ruby/library/rexml/attributes/size_spec.rb
new file mode 100644
index 0000000000..13ef08f644
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/size_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require_relative 'shared/length'
+ require 'rexml/document'
+
+ describe "REXML::Attributes#size" do
+ it_behaves_like :rexml_attribute_length, :size
+ end
+end
diff --git a/spec/ruby/library/rexml/attributes/to_a_spec.rb b/spec/ruby/library/rexml/attributes/to_a_spec.rb
new file mode 100644
index 0000000000..902cd86a29
--- /dev/null
+++ b/spec/ruby/library/rexml/attributes/to_a_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Attributes#to_a" do
+ it "returns an array with the attributes" do
+ e = REXML::Element.new("root")
+ name = REXML::Attribute.new("name", "Dave")
+ last = REXML::Attribute.new("last_name", "Murray")
+
+ e.attributes << name
+ e.attributes << last
+
+ e.attributes.to_a.sort{|a,b|a.to_s<=>b.to_s}.should == [name, last]
+ end
+
+ it "returns an empty array if it has no attributes" do
+ REXML::Element.new("root").attributes.to_a.should == []
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/cdata/clone_spec.rb b/spec/ruby/library/rexml/cdata/clone_spec.rb
new file mode 100644
index 0000000000..abe1a0b062
--- /dev/null
+++ b/spec/ruby/library/rexml/cdata/clone_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::CData#clone" do
+ it "makes a copy of itself" do
+ c = REXML::CData.new("some text")
+ c.clone.to_s.should == c.to_s
+ c.clone.should == c
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/cdata/initialize_spec.rb b/spec/ruby/library/rexml/cdata/initialize_spec.rb
new file mode 100644
index 0000000000..1393d97f4a
--- /dev/null
+++ b/spec/ruby/library/rexml/cdata/initialize_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::CData#initialize" do
+ it "creates a new CData object" do
+ c = REXML::CData.new("some text")
+ c.should be_kind_of(REXML::CData)
+ c.should be_kind_of(REXML::Text)
+ end
+
+ it "respects whitespace if whitespace is true" do
+ c = REXML::CData.new("whitespace test", true)
+ c1 = REXML::CData.new("whitespace test", false)
+
+ c.to_s.should == "whitespace test"
+ c1.to_s.should == "whitespace test"
+ end
+
+ it "receives parent as third argument" do
+ e = REXML::Element.new("root")
+ REXML::CData.new("test", true, e)
+ e.to_s.should == "<root><![CDATA[test]]></root>"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/cdata/shared/to_s.rb b/spec/ruby/library/rexml/cdata/shared/to_s.rb
new file mode 100644
index 0000000000..f8c4951c95
--- /dev/null
+++ b/spec/ruby/library/rexml/cdata/shared/to_s.rb
@@ -0,0 +1,11 @@
+describe :rexml_cdata_to_s, shared: true do
+ it "returns the contents of the CData" do
+ c = REXML::CData.new("some text")
+ c.send(@method).should == "some text"
+ end
+
+ it "does not escape text" do
+ c1 = REXML::CData.new("some& text\n")
+ c1.send(@method).should == "some& text\n"
+ end
+end
diff --git a/spec/ruby/library/rexml/cdata/to_s_spec.rb b/spec/ruby/library/rexml/cdata/to_s_spec.rb
new file mode 100644
index 0000000000..a5c061f116
--- /dev/null
+++ b/spec/ruby/library/rexml/cdata/to_s_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require_relative 'shared/to_s'
+ require 'rexml/document'
+
+ describe "REXML::CData#to_s" do
+ it_behaves_like :rexml_cdata_to_s, :to_s
+ end
+end
diff --git a/spec/ruby/library/rexml/cdata/value_spec.rb b/spec/ruby/library/rexml/cdata/value_spec.rb
new file mode 100644
index 0000000000..9f36226976
--- /dev/null
+++ b/spec/ruby/library/rexml/cdata/value_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require_relative 'shared/to_s'
+ require 'rexml/document'
+
+ describe "REXML::CData#value" do
+ it_behaves_like :rexml_cdata_to_s, :value
+ end
+end
diff --git a/spec/ruby/library/rexml/document/add_element_spec.rb b/spec/ruby/library/rexml/document/add_element_spec.rb
new file mode 100644
index 0000000000..29dec0b24e
--- /dev/null
+++ b/spec/ruby/library/rexml/document/add_element_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Document#add_element" do
+ it "adds arg1 with attributes arg2 as root node" do
+ d = REXML::Document.new
+ e = REXML::Element.new("root")
+ d.add_element e
+ d.root.should == e
+ end
+
+ it "sets arg2 as arg1's attributes" do
+ d = REXML::Document.new
+ e = REXML::Element.new("root")
+ attr = {"foo" => "bar"}
+ d.add_element(e,attr)
+ d.root.attributes["foo"].should == attr["foo"]
+ end
+
+ it "accepts a node name as arg1 and adds it as root" do
+ d = REXML::Document.new
+ d.add_element "foo"
+ d.root.name.should == "foo"
+ end
+
+ it "sets arg1's context to the root's context" do
+ d = REXML::Document.new("", {"foo" => "bar"})
+ d.add_element "foo"
+ d.root.context.should == d.context
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/document/add_spec.rb b/spec/ruby/library/rexml/document/add_spec.rb
new file mode 100644
index 0000000000..8666d3dbf9
--- /dev/null
+++ b/spec/ruby/library/rexml/document/add_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ # This spec defines Document#add and Document#<<
+
+ describe :rexml_document_add, shared: true do
+ before :each do
+ @doc = REXML::Document.new("<root/>")
+ @decl = REXML::XMLDecl.new("1.0")
+ end
+
+ it "sets document's XML declaration" do
+ @doc.send(@method, @decl)
+ @doc.xml_decl.should == @decl
+ end
+
+ it "inserts XML declaration as first node" do
+ @doc.send(@method, @decl)
+ @doc.children[0].version.should == "1.0"
+ end
+
+ it "overwrites existing XML declaration" do
+ @doc.send(@method, @decl)
+ @doc.send(@method, REXML::XMLDecl.new("2.0"))
+ @doc.xml_decl.version.should == "2.0"
+ end
+
+ it "sets document DocType" do
+ @doc.send(@method, REXML::DocType.new("transitional"))
+ @doc.doctype.name.should == "transitional"
+ end
+
+ it "overwrites existing DocType" do
+ @doc.send(@method, REXML::DocType.new("transitional"))
+ @doc.send(@method, REXML::DocType.new("strict"))
+ @doc.doctype.name.should == "strict"
+ end
+
+ it "adds root node unless it exists" do
+ d = REXML::Document.new("")
+ elem = REXML::Element.new "root"
+ d.send(@method, elem)
+ d.root.should == elem
+ end
+
+ it "refuses to add second root" do
+ -> { @doc.send(@method, REXML::Element.new("foo")) }.should raise_error(RuntimeError)
+ end
+ end
+
+ describe "REXML::Document#add" do
+ it_behaves_like :rexml_document_add, :add
+ end
+
+ describe "REXML::Document#<<" do
+ it_behaves_like :rexml_document_add, :<<
+ end
+end
diff --git a/spec/ruby/library/rexml/document/clone_spec.rb b/spec/ruby/library/rexml/document/clone_spec.rb
new file mode 100644
index 0000000000..137fe8a073
--- /dev/null
+++ b/spec/ruby/library/rexml/document/clone_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ # According to the MRI documentation (http://www.ruby-doc.org/stdlib/libdoc/rexml/rdoc/index.html),
+ # clone's behavior "should be obvious". Apparently "obvious" means cloning
+ # only the attributes and the context of the document, not its children.
+ describe "REXML::Document#clone" do
+ it "clones document attributes" do
+ d = REXML::Document.new("foo")
+ d.attributes["foo"] = "bar"
+ e = d.clone
+ e.attributes.should == d.attributes
+ end
+
+ it "clones document context" do
+ d = REXML::Document.new("foo", {"foo" => "bar"})
+ e = d.clone
+ e.context.should == d.context
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/document/doctype_spec.rb b/spec/ruby/library/rexml/document/doctype_spec.rb
new file mode 100644
index 0000000000..e1b7ba4916
--- /dev/null
+++ b/spec/ruby/library/rexml/document/doctype_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Document#doctype" do
+ it "returns the doctype" do
+ d = REXML::Document.new
+ dt = REXML::DocType.new("foo")
+ d.add dt
+ d.doctype.should == dt
+ end
+
+ it "returns nil if there's no doctype" do
+ REXML::Document.new.doctype.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/document/encoding_spec.rb b/spec/ruby/library/rexml/document/encoding_spec.rb
new file mode 100644
index 0000000000..2cc947f06a
--- /dev/null
+++ b/spec/ruby/library/rexml/document/encoding_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Document#encoding" do
+ before :each do
+ @doc = REXML::Document.new
+ end
+
+ it "returns encoding from XML declaration" do
+ @doc.add REXML::XMLDecl.new(nil, "UTF-16", nil)
+ @doc.encoding.should == "UTF-16"
+ end
+
+ it "returns encoding from XML declaration (for UTF-16 as well)" do
+ @doc.add REXML::XMLDecl.new("1.0", "UTF-8", nil)
+ @doc.encoding.should == "UTF-8"
+ end
+
+ it "uses UTF-8 as default encoding" do
+ @doc.encoding.should == "UTF-8"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/document/expanded_name_spec.rb b/spec/ruby/library/rexml/document/expanded_name_spec.rb
new file mode 100644
index 0000000000..9d1025b5e0
--- /dev/null
+++ b/spec/ruby/library/rexml/document/expanded_name_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe :document_expanded_name, shared: true do
+ it "returns an empty string for root" do # root nodes have no expanded name
+ REXML::Document.new.send(@method).should == ""
+ end
+ end
+
+ describe "REXML::Document#expanded_name" do
+ it_behaves_like :document_expanded_name, :expanded_name
+ end
+
+ describe "REXML::Document#name" do
+ it_behaves_like :document_expanded_name, :name
+ end
+end
diff --git a/spec/ruby/library/rexml/document/new_spec.rb b/spec/ruby/library/rexml/document/new_spec.rb
new file mode 100644
index 0000000000..4e24b6f5a1
--- /dev/null
+++ b/spec/ruby/library/rexml/document/new_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Document#new" do
+
+ it "initializes context of {} unless specified" do
+ d = REXML::Document.new("<foo />")
+ d.context.should == {}
+ end
+
+ it "has empty attributes if source is nil" do
+ d = REXML::Document.new(nil)
+ d.elements.should be_empty
+ end
+
+ it "can use other document context" do
+ s = REXML::Document.new("")
+ d = REXML::Document.new(s)
+ d.context.should == s.context
+ end
+
+ it "clones source attributes" do
+ s = REXML::Document.new("<root />")
+ s.attributes["some_attr"] = "some_val"
+ d = REXML::Document.new(s)
+ d.attributes.should == s.attributes
+ end
+
+ it "raises an error if source is not a Document, String or IO" do
+ -> {REXML::Document.new(3)}.should raise_error(RuntimeError)
+ end
+
+ it "does not perform XML validation" do
+ REXML::Document.new("Invalid document").should be_kind_of(REXML::Document)
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/document/node_type_spec.rb b/spec/ruby/library/rexml/document/node_type_spec.rb
new file mode 100644
index 0000000000..b6d7e7a7da
--- /dev/null
+++ b/spec/ruby/library/rexml/document/node_type_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Document#node_type" do
+ it "returns :document" do
+ REXML::Document.new.node_type.should == :document
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/document/root_spec.rb b/spec/ruby/library/rexml/document/root_spec.rb
new file mode 100644
index 0000000000..1a584a720b
--- /dev/null
+++ b/spec/ruby/library/rexml/document/root_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Document#root" do
+ it "returns document root tag name" do
+ REXML::Document.new("<foo/>").root.name.should == "foo"
+ end
+
+ it "returns nil if there is not root" do
+ REXML::Document.new.root.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/document/stand_alone_spec.rb b/spec/ruby/library/rexml/document/stand_alone_spec.rb
new file mode 100644
index 0000000000..e1c721e782
--- /dev/null
+++ b/spec/ruby/library/rexml/document/stand_alone_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Document#stand_alone?" do
+ it "returns the XMLDecl standalone value" do
+ d = REXML::Document.new
+ decl = REXML::XMLDecl.new("1.0", "UTF-8", "yes")
+ d.add decl
+ d.stand_alone?.should == "yes"
+ end
+
+ # According to the docs this should return the default XMLDecl but that
+ # will carry some more problems when printing the document. Currently, it
+ # returns nil. See http://www.ruby-forum.com/topic/146812#650061
+ it "returns the default value when no XML declaration present" do
+ REXML::Document.new.stand_alone?.should == nil
+ end
+
+ end
+end
diff --git a/spec/ruby/library/rexml/document/version_spec.rb b/spec/ruby/library/rexml/document/version_spec.rb
new file mode 100644
index 0000000000..4f6b40551b
--- /dev/null
+++ b/spec/ruby/library/rexml/document/version_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Document#version" do
+ it "returns XML version from declaration" do
+ d = REXML::Document.new
+ d.add REXML::XMLDecl.new("1.1")
+ d.version.should == "1.1"
+ end
+
+ it "returns the default version when declaration is not present" do
+ REXML::Document.new.version.should == REXML::XMLDecl::DEFAULT_VERSION
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/document/write_spec.rb b/spec/ruby/library/rexml/document/write_spec.rb
new file mode 100644
index 0000000000..00c22141b3
--- /dev/null
+++ b/spec/ruby/library/rexml/document/write_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+ require 'rexml/formatters/transitive'
+
+ # Maybe this can be cleaned
+ describe "REXML::Document#write" do
+ before :each do
+ @d = REXML::Document.new
+ city = REXML::Element.new "Springfield"
+ street = REXML::Element.new "EvergreenTerrace"
+ address = REXML::Element.new "House742"
+ @d << city << street << address
+ @str = ""
+ end
+
+ it "returns document source as string" do
+ @d.write(@str)
+ @str.should == "<Springfield><EvergreenTerrace><House742/></EvergreenTerrace></Springfield>"
+ end
+
+ it "returns document indented" do
+ @d.write(@str, 2)
+ @str.should =~ /\s*<Springfield>\s*<EvergreenTerrace>\s*<House742\/>\s*<\/EvergreenTerrace>\s*<\/Springfield>/
+ end
+
+ it "returns document with transitive support" do
+ @d.write(@str, 2, true)
+ @str.should =~ /\s*<Springfield\s*><EvergreenTerrace\s*><House742\s*\/><\/EvergreenTerrace\s*><\/Springfield\s*>/
+ end
+
+ it "returns document with support for IE" do
+ @d.write(@str, -1, false, true)
+ @str.should == "<Springfield><EvergreenTerrace><House742 /></EvergreenTerrace></Springfield>"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/document/xml_decl_spec.rb b/spec/ruby/library/rexml/document/xml_decl_spec.rb
new file mode 100644
index 0000000000..8ac47510b0
--- /dev/null
+++ b/spec/ruby/library/rexml/document/xml_decl_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Document#xml_decl" do
+ it "returns XML declaration of the document" do
+ d = REXML::Document.new
+ decl = REXML::XMLDecl.new("1.0", "UTF-16", "yes")
+ d.add decl
+ d.xml_decl.should == decl
+ end
+
+ it "returns default XML declaration unless present" do
+ REXML::Document.new.xml_decl.should == REXML::XMLDecl.new
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/add_attribute_spec.rb b/spec/ruby/library/rexml/element/add_attribute_spec.rb
new file mode 100644
index 0000000000..64f2ec84a3
--- /dev/null
+++ b/spec/ruby/library/rexml/element/add_attribute_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#add_attribute" do
+ before :each do
+ @person = REXML::Element.new "person"
+ @person.attributes["name"] = "Bill"
+ end
+
+ it "adds a new attribute" do
+ @person.add_attribute("age", "17")
+ @person.attributes["age"].should == "17"
+ end
+
+ it "overwrites an existing attribute" do
+ @person.add_attribute("name", "Bill")
+ @person.attributes["name"].should == "Bill"
+ end
+
+ it "accepts a pair of strings" do
+ @person.add_attribute("male", "true")
+ @person.attributes["male"].should == "true"
+ end
+
+ it "accepts an Attribute for key" do
+ attr = REXML::Attribute.new("male", "true")
+ @person.add_attribute attr
+ @person.attributes["male"].should == "true"
+ end
+
+ it "ignores value if key is an Attribute" do
+ attr = REXML::Attribute.new("male", "true")
+ @person.add_attribute(attr, "false")
+ @person.attributes["male"].should == "true"
+ end
+
+ it "returns the attribute added" do
+ attr = REXML::Attribute.new("name", "Tony")
+ @person.add_attribute(attr).should == attr
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/add_attributes_spec.rb b/spec/ruby/library/rexml/element/add_attributes_spec.rb
new file mode 100644
index 0000000000..f331803dd8
--- /dev/null
+++ b/spec/ruby/library/rexml/element/add_attributes_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#add_attributes" do
+ before :each do
+ @person = REXML::Element.new "person"
+ @person.attributes["name"] = "Bill"
+ end
+
+ it "adds multiple attributes from a hash" do
+ @person.add_attributes({"name" => "Joe", "age" => "27"})
+ @person.attributes["name"].should == "Joe"
+ @person.attributes["age"].should == "27"
+ end
+
+ it "adds multiple attributes from an array" do
+ attrs = { "name" => "Joe", "age" => "27"}
+ @person.add_attributes attrs.to_a
+ @person.attributes["name"].should == "Joe"
+ @person.attributes["age"].should == "27"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/add_element_spec.rb b/spec/ruby/library/rexml/element/add_element_spec.rb
new file mode 100644
index 0000000000..8ba023f2c7
--- /dev/null
+++ b/spec/ruby/library/rexml/element/add_element_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#add_element" do
+ before :each do
+ @root = REXML::Element.new("root")
+ end
+
+ it "adds a child without attributes" do
+ name = REXML::Element.new("name")
+ @root.add_element name
+ @root.elements["name"].name.should == name.name
+ @root.elements["name"].attributes.should == name.attributes
+ @root.elements["name"].context.should == name.context
+ end
+
+ it "adds a child with attributes" do
+ person = REXML::Element.new("person")
+ @root.add_element(person, {"name" => "Madonna"})
+ @root.elements["person"].name.should == person.name
+ @root.elements["person"].attributes.should == person.attributes
+ @root.elements["person"].context.should == person.context
+ end
+
+ it "adds a child with name" do
+ @root.add_element "name"
+ @root.elements["name"].name.should == "name"
+ @root.elements["name"].attributes.should == {}
+ @root.elements["name"].context.should == nil
+ end
+
+ it "returns the added child" do
+ name = @root.add_element "name"
+ @root.elements["name"].name.should == name.name
+ @root.elements["name"].attributes.should == name.attributes
+ @root.elements["name"].context.should == name.context
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/add_namespace_spec.rb b/spec/ruby/library/rexml/element/add_namespace_spec.rb
new file mode 100644
index 0000000000..44b074bac7
--- /dev/null
+++ b/spec/ruby/library/rexml/element/add_namespace_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#add_namespace" do
+ before :each do
+ @elem = REXML::Element.new("person")
+ end
+
+ it "adds a namespace to element" do
+ @elem.add_namespace("foo", "bar")
+ @elem.namespace("foo").should == "bar"
+ end
+
+ it "accepts a prefix string as prefix" do
+ @elem.add_namespace("xmlns:foo", "bar")
+ @elem.namespace("foo").should == "bar"
+ end
+
+ it "uses prefix as URI if uri is nil" do
+ @elem.add_namespace("some_uri", nil)
+ @elem.namespace.should == "some_uri"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/add_text_spec.rb b/spec/ruby/library/rexml/element/add_text_spec.rb
new file mode 100644
index 0000000000..3a0531ad42
--- /dev/null
+++ b/spec/ruby/library/rexml/element/add_text_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#add_text" do
+ before :each do
+ @name = REXML::Element.new "Name"
+ end
+
+ it "adds text to an element" do
+ @name.add_text "Ringo"
+ @name.to_s.should == "<Name>Ringo</Name>"
+ end
+
+ it "accepts a Text" do
+ @name.add_text(REXML::Text.new("Ringo"))
+ @name.to_s.should == "<Name>Ringo</Name>"
+ end
+
+ it "joins the new text with the old one" do
+ @name.add_text "Ringo"
+ @name.add_text " Starr"
+ @name.to_s.should == "<Name>Ringo Starr</Name>"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/attribute_spec.rb b/spec/ruby/library/rexml/element/attribute_spec.rb
new file mode 100644
index 0000000000..b223d3440c
--- /dev/null
+++ b/spec/ruby/library/rexml/element/attribute_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#attribute" do
+ it "returns an attribute by name" do
+ person = REXML::Element.new "Person"
+ attribute = REXML::Attribute.new("drink", "coffee")
+ person.add_attribute(attribute)
+ person.attribute("drink").should == attribute
+ end
+
+ it "supports attributes inside namespaces" do
+ e = REXML::Element.new("element")
+ e.add_attributes({"xmlns:ns" => "http://some_uri"})
+ e.attribute("ns", "ns").to_s.should == "http://some_uri"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/attributes_spec.rb b/spec/ruby/library/rexml/element/attributes_spec.rb
new file mode 100644
index 0000000000..92bcecc40a
--- /dev/null
+++ b/spec/ruby/library/rexml/element/attributes_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#attributes" do
+ it "returns element's Attributes" do
+ p = REXML::Element.new "Person"
+
+ name = REXML::Attribute.new("name", "John")
+ attrs = REXML::Attributes.new(p)
+ attrs.add name
+
+ p.add_attribute name
+ p.attributes.should == attrs
+ end
+
+ it "returns an empty hash if element has no attributes" do
+ REXML::Element.new("Person").attributes.should == {}
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/cdatas_spec.rb b/spec/ruby/library/rexml/element/cdatas_spec.rb
new file mode 100644
index 0000000000..988b2cb422
--- /dev/null
+++ b/spec/ruby/library/rexml/element/cdatas_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#cdatas" do
+ before :each do
+ @e = REXML::Element.new("Root")
+ end
+
+ it "returns the array of children cdatas" do
+ c = REXML::CData.new("Primary")
+ d = REXML::CData.new("Secondary")
+ @e << c
+ @e << d
+ @e.cdatas.should == [c, d]
+ end
+
+ it "freezes the returned array" do
+ @e.cdatas.should.frozen?
+ end
+
+ it "returns an empty array if element has no cdata" do
+ @e.cdatas.should == []
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/clone_spec.rb b/spec/ruby/library/rexml/element/clone_spec.rb
new file mode 100644
index 0000000000..490e43181f
--- /dev/null
+++ b/spec/ruby/library/rexml/element/clone_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#clone" do
+ before :each do
+ @e = REXML::Element.new "a"
+ end
+ it "creates a copy of element" do
+ @e.clone.to_s.should == @e.to_s
+ end
+
+ it "copies the attributes" do
+ @e.add_attribute("foo", "bar")
+ @e.clone.to_s.should == @e.to_s
+ end
+
+ it "does not copy the text" do
+ @e.add_text "some text..."
+ @e.clone.to_s.should_not == @e
+ @e.clone.to_s.should == "<a/>"
+ end
+
+ it "does not copy the child elements" do
+ b = REXML::Element.new "b"
+ @e << b
+ @e.clone.should_not == @e
+ @e.clone.to_s.should == "<a/>"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/comments_spec.rb b/spec/ruby/library/rexml/element/comments_spec.rb
new file mode 100644
index 0000000000..84ab9a7469
--- /dev/null
+++ b/spec/ruby/library/rexml/element/comments_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#comments" do
+ before :each do
+ @e = REXML::Element.new "root"
+ @c1 = REXML::Comment.new "this is a comment"
+ @c2 = REXML::Comment.new "this is another comment"
+ @e << @c1
+ @e << @c2
+ end
+
+ it "returns the array of comments" do
+ @e.comments.should == [@c1, @c2]
+ end
+
+ it "returns a frozen object" do
+ @e.comments.should.frozen?
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/delete_attribute_spec.rb b/spec/ruby/library/rexml/element/delete_attribute_spec.rb
new file mode 100644
index 0000000000..e2ba81eb0d
--- /dev/null
+++ b/spec/ruby/library/rexml/element/delete_attribute_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#delete_attribute" do
+ before :each do
+ @e = REXML::Element.new("Person")
+ @attr = REXML::Attribute.new("name", "Sean")
+ @e.add_attribute(@attr)
+ end
+
+ it "deletes an attribute from the element" do
+ @e.delete_attribute("name")
+ @e.attributes["name"].should be_nil
+ end
+
+ # Bug was filled with a patch in Ruby's tracker #20298
+ quarantine! do
+ it "receives an Attribute" do
+ @e.add_attribute(@attr)
+ @e.delete_attribute(@attr)
+ @e.attributes["name"].should be_nil
+ end
+ end
+
+ # Docs say that it returns the removed attribute but then examples
+ # show it returns the element with the attribute removed.
+ # Also fixed in #20298
+ it "returns the element with the attribute removed" do
+ elem = @e.delete_attribute("name")
+ elem.attributes.should be_empty
+ elem.to_s.should eql("<Person/>")
+ end
+
+ it "returns nil if the attribute does not exist" do
+ @e.delete_attribute("name")
+ at = @e.delete_attribute("name")
+ at.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/delete_element_spec.rb b/spec/ruby/library/rexml/element/delete_element_spec.rb
new file mode 100644
index 0000000000..c0b486a6f7
--- /dev/null
+++ b/spec/ruby/library/rexml/element/delete_element_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#delete_element" do
+ before :each do
+ @root = REXML::Element.new("root")
+ end
+
+ it "deletes the child element" do
+ node = REXML::Element.new("some_node")
+ @root.add_element node
+ @root.delete_element node
+ @root.elements.size.should == 0
+ end
+
+ it "deletes a child via XPath" do
+ @root.add_element "some_node"
+ @root.delete_element "some_node"
+ @root.elements.size.should == 0
+ end
+
+ it "deletes the child at index" do
+ @root.add_element "some_node"
+ @root.delete_element 1
+ @root.elements.size.should == 0
+ end
+
+ # According to the docs this should return the deleted element
+ # but it won't if it's an Element.
+ it "deletes Element and returns it" do
+ node = REXML::Element.new("some_node")
+ @root.add_element node
+ del_node = @root.delete_element node
+ del_node.should == node
+ end
+
+ # Note how passing the string will return the removed element
+ # but passing the Element as above won't.
+ it "deletes an element and returns it" do
+ node = REXML::Element.new("some_node")
+ @root.add_element node
+ del_node = @root.delete_element "some_node"
+ del_node.should == node
+ end
+
+ it "returns nil unless element exists" do
+ @root.delete_element("something").should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/delete_namespace_spec.rb b/spec/ruby/library/rexml/element/delete_namespace_spec.rb
new file mode 100644
index 0000000000..a7763d51e8
--- /dev/null
+++ b/spec/ruby/library/rexml/element/delete_namespace_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#delete_namespace" do
+
+ before :each do
+ @doc = REXML::Document.new "<a xmlns:foo='bar' xmlns='twiddle'/>"
+ end
+
+ it "deletes a namespace from the element" do
+ @doc.root.delete_namespace 'foo'
+ @doc.root.namespace("foo").should be_nil
+ @doc.root.to_s.should == "<a xmlns='twiddle'/>"
+ end
+
+ it "deletes default namespace when called with no args" do
+ @doc.root.delete_namespace
+ @doc.root.namespace.should be_empty
+ @doc.root.to_s.should == "<a xmlns:foo='bar'/>"
+ end
+
+ it "returns the element" do
+ @doc.root.delete_namespace.should == @doc.root
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/document_spec.rb b/spec/ruby/library/rexml/element/document_spec.rb
new file mode 100644
index 0000000000..754f27d8a0
--- /dev/null
+++ b/spec/ruby/library/rexml/element/document_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#document" do
+
+ it "returns the element's document" do
+ d = REXML::Document.new("<root><elem/></root>")
+ d << REXML::XMLDecl.new
+ d.root.document.should == d
+ d.root.document.to_s.should == d.to_s
+ end
+
+ it "returns nil if it belongs to no document" do
+ REXML::Element.new("standalone").document.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/each_element_with_attribute_spec.rb b/spec/ruby/library/rexml/element/each_element_with_attribute_spec.rb
new file mode 100644
index 0000000000..dcc6dbbf17
--- /dev/null
+++ b/spec/ruby/library/rexml/element/each_element_with_attribute_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#each_element_with_attributes" do
+ before :each do
+ @document = REXML::Element.new("people")
+ @father = REXML::Element.new("Person")
+ @father.attributes["name"] = "Joe"
+ @son = REXML::Element.new("Child")
+ @son.attributes["name"] = "Fred"
+ @document.root << @father
+ @document.root << @son
+ @children = []
+ end
+
+ it "returns children with attribute" do
+ @document.each_element_with_attribute("name") { |elem| @children << elem }
+ @children[0].should == @father
+ @children[1].should == @son
+ end
+
+ it "takes attribute value as second argument" do
+ @document.each_element_with_attribute("name", "Fred"){ |elem| elem.should == @son }
+ end
+
+ it "takes max number of children as third argument" do
+ @document.each_element_with_attribute("name", nil, 1) { |elem| @children << elem }
+ @children.size.should == 1
+ @children[0].should == @father
+ end
+
+ it "takes XPath filter as fourth argument" do
+ @document.each_element_with_attribute("name", nil, 0, "Child"){ |elem| elem.should == @son}
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/each_element_with_text_spec.rb b/spec/ruby/library/rexml/element/each_element_with_text_spec.rb
new file mode 100644
index 0000000000..a4a200d237
--- /dev/null
+++ b/spec/ruby/library/rexml/element/each_element_with_text_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#each_element_with_text" do
+ before :each do
+ @document = REXML::Element.new("people")
+
+ @joe = REXML::Element.new("Person")
+ @joe.text = "Joe"
+ @fred = REXML::Element.new("Person")
+ @fred.text = "Fred"
+ @another = REXML::Element.new("AnotherPerson")
+ @another.text = "Fred"
+ @document.root << @joe
+ @document.root << @fred
+ @document.root << @another
+ @children = []
+ end
+
+ it "returns children with text" do
+ @document.each_element_with_text("Joe"){|c| c.should == @joe}
+ end
+
+ it "takes max as second argument" do
+ @document.each_element_with_text("Fred", 1){ |c| c.should == @fred}
+ end
+
+ it "takes XPath filter as third argument" do
+ @document.each_element_with_text("Fred", 0, "Person"){ |c| c.should == @fred}
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/element_reference_spec.rb b/spec/ruby/library/rexml/element/element_reference_spec.rb
new file mode 100644
index 0000000000..9e5d371ce4
--- /dev/null
+++ b/spec/ruby/library/rexml/element/element_reference_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#[]" do
+
+ before :each do
+ @doc = REXML::Document.new("<root foo='bar'></root>")
+ @child = REXML::Element.new("child")
+ @doc.root.add_element @child
+ end
+
+ it "return attribute value if argument is string or symbol" do
+ @doc.root[:foo].should == 'bar'
+ @doc.root['foo'].should == 'bar'
+ end
+
+ it "return nth element if argument is int" do
+ @doc.root[0].should == @child
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/get_text_spec.rb b/spec/ruby/library/rexml/element/get_text_spec.rb
new file mode 100644
index 0000000000..0fa8d7cb3f
--- /dev/null
+++ b/spec/ruby/library/rexml/element/get_text_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#get_text" do
+ before :each do
+ @doc = REXML::Document.new "<p>some text<b>this is bold!</b> more text</p>"
+ end
+
+ it "returns the first text child node" do
+ @doc.root.get_text.value.should == "some text"
+ @doc.root.get_text.should be_kind_of(REXML::Text)
+ end
+
+ it "returns text from an element matching path" do
+ @doc.root.get_text("b").value.should == "this is bold!"
+ @doc.root.get_text("b").should be_kind_of(REXML::Text)
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/has_attributes_spec.rb b/spec/ruby/library/rexml/element/has_attributes_spec.rb
new file mode 100644
index 0000000000..af3ce8ce1b
--- /dev/null
+++ b/spec/ruby/library/rexml/element/has_attributes_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#has_attributes?" do
+ before :each do
+ @e = REXML::Element.new("test_elem")
+ end
+
+ it "returns true when element has any attributes" do
+ @e.add_attribute("name", "Joe")
+ @e.has_attributes?.should be_true
+ end
+
+ it "returns false if element has no attributes" do
+ @e.has_attributes?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/has_elements_spec.rb b/spec/ruby/library/rexml/element/has_elements_spec.rb
new file mode 100644
index 0000000000..04c7fe01a5
--- /dev/null
+++ b/spec/ruby/library/rexml/element/has_elements_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#has_elements?" do
+ before :each do
+ @e = REXML::Element.new("root")
+ end
+
+ it "returns true if element has child elements" do
+ child = REXML::Element.new("child")
+ @e << child
+ @e.has_elements?.should be_true
+ end
+
+ it "returns false if element doesn't have child elements" do
+ @e.has_elements?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/has_text_spec.rb b/spec/ruby/library/rexml/element/has_text_spec.rb
new file mode 100644
index 0000000000..206c167ae6
--- /dev/null
+++ b/spec/ruby/library/rexml/element/has_text_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#has_text?" do
+
+ it "returns true if element has a Text child" do
+ e = REXML::Element.new("Person")
+ e.text = "My text"
+ e.has_text?.should be_true
+ end
+
+ it "returns false if it has no Text children" do
+ e = REXML::Element.new("Person")
+ e.has_text?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/inspect_spec.rb b/spec/ruby/library/rexml/element/inspect_spec.rb
new file mode 100644
index 0000000000..ec16c136ee
--- /dev/null
+++ b/spec/ruby/library/rexml/element/inspect_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#inspect" do
+
+ before :each do
+ @name = REXML::Element.new "name"
+ end
+
+ it "returns the node as a string" do
+ @name.inspect.should == "<name/>"
+ end
+
+ it "inserts '...' if the node has children" do
+ e = REXML::Element.new "last_name"
+ @name << e
+ @name.inspect.should == "<name> ... </>"
+ # This might make more sense but differs from MRI's default behavior
+ # @name.inspect.should == "<name> ... </name>"
+ end
+
+ it "inserts the attributes in the string" do
+ @name.add_attribute "language"
+ @name.attributes["language"] = "english"
+ @name.inspect.should == "<name language='english'/>"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/instructions_spec.rb b/spec/ruby/library/rexml/element/instructions_spec.rb
new file mode 100644
index 0000000000..11f1396df0
--- /dev/null
+++ b/spec/ruby/library/rexml/element/instructions_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#instructions" do
+ before :each do
+ @elem = REXML::Element.new("root")
+ end
+ it "returns the Instruction children nodes" do
+ inst = REXML::Instruction.new("xml-stylesheet", "href='headlines.css'")
+ @elem << inst
+ @elem.instructions.first.should == inst
+ end
+
+ it "returns an empty array if it has no Instruction children" do
+ @elem.instructions.should be_empty
+ end
+
+ it "freezes the returned array" do
+ @elem.instructions.frozen?.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/namespace_spec.rb b/spec/ruby/library/rexml/element/namespace_spec.rb
new file mode 100644
index 0000000000..28966289c5
--- /dev/null
+++ b/spec/ruby/library/rexml/element/namespace_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#namespace" do
+ before :each do
+ @doc = REXML::Document.new("<a xmlns='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
+ @elem = @doc.elements["//b"]
+ end
+
+ it "returns the default namespace" do
+ @elem.namespace.should == "1"
+ end
+
+ it "accepts a namespace prefix" do
+ @elem.namespace("y").should == "2"
+ @doc.elements["//c"].namespace("z").should == "3"
+ end
+
+ it "returns an empty String if default namespace is not defined" do
+ e = REXML::Document.new("<a/>")
+ e.root.namespace.should be_empty
+ end
+
+ it "returns nil if namespace is not defined" do
+ @elem.namespace("z").should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/namespaces_spec.rb b/spec/ruby/library/rexml/element/namespaces_spec.rb
new file mode 100644
index 0000000000..4544540173
--- /dev/null
+++ b/spec/ruby/library/rexml/element/namespaces_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#namespaces" do
+ before :each do
+ doc = REXML::Document.new("<a xmlns='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
+ @elem = doc.elements["//c"]
+ end
+
+ it "returns a hash of the namespaces" do
+ ns = {"y"=>"2", "z"=>"3", "xmlns"=>"1"}
+ @elem.namespaces.keys.sort.should == ns.keys.sort
+ @elem.namespaces.values.sort.should == ns.values.sort
+ end
+
+ it "returns an empty hash if no namespaces exist" do
+ e = REXML::Element.new "element"
+ e.namespaces.kind_of?(Hash).should == true
+ e.namespaces.should be_empty
+ end
+
+ it "uses namespace prefixes as keys" do
+ prefixes = ["y", "z", "xmlns"]
+ @elem.namespaces.keys.sort.should == prefixes.sort
+ end
+
+ it "uses namespace values as the hash values" do
+ values = ["2", "3", "1"]
+ @elem.namespaces.values.sort.should == values.sort
+ end
+
+ end
+end
diff --git a/spec/ruby/library/rexml/element/new_spec.rb b/spec/ruby/library/rexml/element/new_spec.rb
new file mode 100644
index 0000000000..c6ab289476
--- /dev/null
+++ b/spec/ruby/library/rexml/element/new_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#new" do
+
+ it "creates element from tag name" do
+ REXML::Element.new("foo").name.should == "foo"
+ end
+
+ it "creates element with default attributes" do
+ e = REXML::Element.new
+ e.name.should == REXML::Element::UNDEFINED
+ e.context.should == nil
+ e.parent.should == nil
+ end
+
+ it "creates element from another element" do
+ e = REXML::Element.new "foo"
+ f = REXML::Element.new e
+ e.name.should == f.name
+ e.context.should == f.context
+ e.parent.should == f.parent
+ end
+
+ it "takes parent as second argument" do
+ parent = REXML::Element.new "foo"
+ child = REXML::Element.new "bar", parent
+ child.parent.should == parent
+ end
+
+ it "takes context as third argument" do
+ context = {"some_key" => "some_value"}
+ REXML::Element.new("foo", nil, context).context.should == context
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/next_element_spec.rb b/spec/ruby/library/rexml/element/next_element_spec.rb
new file mode 100644
index 0000000000..46d8f74760
--- /dev/null
+++ b/spec/ruby/library/rexml/element/next_element_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#next_element" do
+ before :each do
+ @a = REXML::Element.new "a"
+ @b = REXML::Element.new "b"
+ @c = REXML::Element.new "c"
+ @a.root << @b
+ @a.root << @c
+ end
+ it "returns next existing element" do
+ @a.elements["b"].next_element.should == @c
+ end
+
+ it "returns nil on last element" do
+ @a.elements["c"].next_element.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/node_type_spec.rb b/spec/ruby/library/rexml/element/node_type_spec.rb
new file mode 100644
index 0000000000..a39c2deca5
--- /dev/null
+++ b/spec/ruby/library/rexml/element/node_type_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#node_type" do
+ it "returns :element" do
+ REXML::Element.new("MyElem").node_type.should == :element
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/prefixes_spec.rb b/spec/ruby/library/rexml/element/prefixes_spec.rb
new file mode 100644
index 0000000000..ea4caab4bc
--- /dev/null
+++ b/spec/ruby/library/rexml/element/prefixes_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#prefixes" do
+ before :each do
+ doc = REXML::Document.new("<a xmlns='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
+ @elem = doc.elements["//c"]
+ end
+
+ it "returns an array of the prefixes of the namespaces" do
+ @elem.prefixes.should == ["y", "z"]
+ end
+
+ it "does not include the default namespace" do
+ @elem.prefixes.include?("xmlns").should == false
+ end
+
+ it "returns an empty array if no namespace was defined" do
+ doc = REXML::Document.new "<root><something/></root>"
+ root = doc.elements["//root"]
+ root.prefixes.should == []
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/previous_element_spec.rb b/spec/ruby/library/rexml/element/previous_element_spec.rb
new file mode 100644
index 0000000000..a43b1ddd10
--- /dev/null
+++ b/spec/ruby/library/rexml/element/previous_element_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#previous_element" do
+ before :each do
+ @a = REXML::Element.new "a"
+ @b = REXML::Element.new "b"
+ @c = REXML::Element.new "c"
+ @a.root << @b
+ @a.root << @c
+ end
+
+ it "returns previous element" do
+ @a.elements["c"].previous_element.should == @b
+ end
+
+ it "returns nil on first element" do
+ @a.elements["b"].previous_element.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/raw_spec.rb b/spec/ruby/library/rexml/element/raw_spec.rb
new file mode 100644
index 0000000000..200a99d194
--- /dev/null
+++ b/spec/ruby/library/rexml/element/raw_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#raw" do
+ it "returns true if raw mode is set to all" do
+ REXML::Element.new("MyElem", nil, {raw: :all}).raw.should == true
+ end
+
+ it "returns true if raw mode is set to expanded_name" do
+ REXML::Element.new("MyElem", nil, {raw: "MyElem"}).raw.should == true
+ end
+
+ it "returns false if raw mode is not set" do
+ REXML::Element.new("MyElem", nil, {raw: ""}).raw.should == false
+ end
+
+ it "returns false if raw is not :all or expanded_name" do
+ REXML::Element.new("MyElem", nil, {raw: "Something"}).raw.should == false
+ end
+
+ it "returns nil if context is not set" do
+ REXML::Element.new("MyElem").raw.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/root_spec.rb b/spec/ruby/library/rexml/element/root_spec.rb
new file mode 100644
index 0000000000..52aa4571b9
--- /dev/null
+++ b/spec/ruby/library/rexml/element/root_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#root" do
+ before :each do
+ @doc = REXML::Document.new
+ @root = REXML::Element.new "root"
+ @node = REXML::Element.new "node"
+ @doc << @root << @node
+ end
+
+ it "returns first child on documents" do
+ @doc.root.should == @root
+ end
+
+ it "returns self on root nodes" do
+ @root.root.should == @root
+ end
+
+ it "returns parent's root on child nodes" do
+ @node.root.should == @root
+ end
+
+ it "returns self on standalone nodes" do
+ e = REXML::Element.new "Elem" # Note that it doesn't have a parent node
+ e.root.should == e
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/text_spec.rb b/spec/ruby/library/rexml/element/text_spec.rb
new file mode 100644
index 0000000000..3234bba153
--- /dev/null
+++ b/spec/ruby/library/rexml/element/text_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#text" do
+ before :each do
+ @e = REXML::Element.new "name"
+ @e.text = "John"
+ end
+
+ it "returns the text node of element" do
+ @e.text.should == "John"
+ end
+
+ it "returns the text node value" do
+ t = REXML::Text.new "Joe"
+ @e.text = t
+ @e.text.should == "Joe"
+ @e.text.should_not == t
+ end
+
+ it "returns nil if no text is attached" do
+ elem = REXML::Element.new "name"
+ elem.text.should == nil
+ end
+ end
+
+ describe "REXML::Element#text=" do
+ before :each do
+ @e = REXML::Element.new "name"
+ @e.text = "John"
+ end
+
+ it "sets the text node" do
+ @e.to_s.should == "<name>John</name>"
+ end
+
+ it "replaces existing text" do
+ @e.text = "Joe"
+ @e.to_s.should == "<name>Joe</name>"
+ end
+
+ it "receives nil as an argument" do
+ @e.text = nil
+ @e.to_s.should == "<name/>"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/texts_spec.rb b/spec/ruby/library/rexml/element/texts_spec.rb
new file mode 100644
index 0000000000..2d374d5e66
--- /dev/null
+++ b/spec/ruby/library/rexml/element/texts_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#texts" do
+
+ it "returns an array of the Text children" do
+ e = REXML::Element.new("root")
+ e.add_text "First"
+ e.add_text "Second"
+ e.texts.should == ["FirstSecond"]
+ end
+
+ it "returns an empty array if it has no Text children" do
+ REXML::Element.new("root").texts.should == []
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/element/whitespace_spec.rb b/spec/ruby/library/rexml/element/whitespace_spec.rb
new file mode 100644
index 0000000000..f455067922
--- /dev/null
+++ b/spec/ruby/library/rexml/element/whitespace_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Element#whitespace" do
+ it "returns true if whitespace is respected in the element" do
+ e = REXML::Element.new("root")
+ e.whitespace.should be_true
+
+ e = REXML::Element.new("root", nil, respect_whitespace: :all)
+ e.whitespace.should be_true
+
+ e = REXML::Element.new("root", nil, respect_whitespace: ["root"])
+ e.whitespace.should be_true
+ end
+
+ it "returns false if whitespace is ignored inside element" do
+ e = REXML::Element.new("root", nil, compress_whitespace: :all)
+ e.whitespace.should be_false
+
+ e = REXML::Element.new("root", nil, compress_whitespace: ["root"])
+ e.whitespace.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/node/each_recursive_spec.rb b/spec/ruby/library/rexml/node/each_recursive_spec.rb
new file mode 100644
index 0000000000..da347b1389
--- /dev/null
+++ b/spec/ruby/library/rexml/node/each_recursive_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Node#each_recursive" do
+ before :each do
+ @doc = REXML::Document.new
+ @doc << REXML::XMLDecl.new
+ @root = REXML::Element.new "root"
+ @child1 = REXML::Element.new "child1"
+ @child2 = REXML::Element.new "child2"
+ @root << @child1
+ @root << @child2
+ @doc << @root
+ end
+
+ it "visits all subnodes of self" do
+ nodes = []
+ @doc.each_recursive { |node| nodes << node}
+ nodes.should == [@root, @child1, @child2]
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/node/find_first_recursive_spec.rb b/spec/ruby/library/rexml/node/find_first_recursive_spec.rb
new file mode 100644
index 0000000000..2a4f1097ae
--- /dev/null
+++ b/spec/ruby/library/rexml/node/find_first_recursive_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Node#find_first_recursive" do
+ before :each do
+ @e = REXML::Element.new("root")
+ @node1 = REXML::Element.new("node")
+ @node2 = REXML::Element.new("another node")
+ @subnode = REXML::Element.new("another node")
+ @node1 << @subnode
+ @e << @node1
+ @e << @node2
+ end
+
+ it "finds the first element that matches block" do
+ found = @e.find_first_recursive { |n| n.to_s == "<node><another node/></node>"}
+ found.should == @node1
+ end
+
+ it "visits the nodes in preorder" do
+ found = @e.find_first_recursive { |n| n.to_s == "<another node/>"}
+ found.should == @subnode
+ found.should_not == @node2
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/node/index_in_parent_spec.rb b/spec/ruby/library/rexml/node/index_in_parent_spec.rb
new file mode 100644
index 0000000000..55909f86d6
--- /dev/null
+++ b/spec/ruby/library/rexml/node/index_in_parent_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Node#index_in_parent" do
+ it "returns the index (starting from 1) of self in parent" do
+ e = REXML::Element.new("root")
+ node1 = REXML::Element.new("node")
+ node2 = REXML::Element.new("another node")
+ e << node1
+ e << node2
+
+ node1.index_in_parent.should == 1
+ node2.index_in_parent.should == 2
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/node/next_sibling_node_spec.rb b/spec/ruby/library/rexml/node/next_sibling_node_spec.rb
new file mode 100644
index 0000000000..7aae861d75
--- /dev/null
+++ b/spec/ruby/library/rexml/node/next_sibling_node_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Node#next_sibling_node" do
+ before :each do
+ @e = REXML::Element.new("root")
+ @node1 = REXML::Element.new("node")
+ @node2 = REXML::Element.new("another node")
+ @e << @node1
+ @e << @node2
+ end
+
+ it "returns the next child node in parent" do
+ @node1.next_sibling_node.should == @node2
+ end
+
+ it "returns nil if there are no more child nodes next" do
+ @node2.next_sibling_node.should == nil
+ @e.next_sibling_node.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/node/parent_spec.rb b/spec/ruby/library/rexml/node/parent_spec.rb
new file mode 100644
index 0000000000..43c3a747e0
--- /dev/null
+++ b/spec/ruby/library/rexml/node/parent_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Node#parent?" do
+ it "returns true for Elements" do
+ e = REXML::Element.new("foo")
+ e.should.parent?
+ end
+
+ it "returns true for Documents" do
+ e = REXML::Document.new
+ e.should.parent?
+ end
+
+ # This includes attributes, CData and declarations.
+ it "returns false for Texts" do
+ e = REXML::Text.new("foo")
+ e.should_not.parent?
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/node/previous_sibling_node_spec.rb b/spec/ruby/library/rexml/node/previous_sibling_node_spec.rb
new file mode 100644
index 0000000000..11263968a7
--- /dev/null
+++ b/spec/ruby/library/rexml/node/previous_sibling_node_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Node#previous_sibling_node" do
+ before :each do
+ @e = REXML::Element.new("root")
+ @node1 = REXML::Element.new("node")
+ @node2 = REXML::Element.new("another node")
+ @e << @node1
+ @e << @node2
+ end
+
+ it "returns the previous child node in parent" do
+ @node2.previous_sibling_node.should == @node1
+ end
+
+ it "returns nil if there are no more child nodes before" do
+ @node1.previous_sibling_node.should == nil
+ @e.previous_sibling_node.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/shared/each_element.rb b/spec/ruby/library/rexml/shared/each_element.rb
new file mode 100644
index 0000000000..2e0871161d
--- /dev/null
+++ b/spec/ruby/library/rexml/shared/each_element.rb
@@ -0,0 +1,36 @@
+require 'rexml/document'
+require_relative '../../../spec_helper'
+
+describe :rexml_each_element, shared: true do
+ before :each do
+ @e = REXML::Element.new "root"
+ s1 = REXML::Element.new "node1"
+ s2 = REXML::Element.new "node2"
+ s3 = REXML::Element.new "node3"
+ s4 = REXML::Element.new "sub_node"
+ @e << s1
+ @e << s2
+ @e << s3
+ @e << s4
+ end
+
+ it "iterates through element" do
+ str = ""
+ @e.send(@method) { |elem| str << elem.name << " " }
+ str.should == "node1 node2 node3 sub_node "
+ end
+
+ it "iterates through element filtering with XPath" do
+ str = ""
+ @e.send(@method, "/*"){ |e| str << e.name << " "}
+ str.should == "node1 node2 node3 sub_node "
+ end
+end
+
+describe "REXML::Element#each_element" do
+ it_behaves_like :rexml_each_element, :each_element
+end
+
+describe "REXML::Elements#each" do
+ it_behaves_like :rexml_each_element, :each
+end
diff --git a/spec/ruby/library/rexml/shared/elements_to_a.rb b/spec/ruby/library/rexml/shared/elements_to_a.rb
new file mode 100644
index 0000000000..b7169f0b2e
--- /dev/null
+++ b/spec/ruby/library/rexml/shared/elements_to_a.rb
@@ -0,0 +1,34 @@
+require 'rexml/document'
+require_relative '../../../spec_helper'
+
+describe :rexml_elements_to_a, shared: true do
+ before :each do
+ @e = REXML::Element.new "root"
+ @first = REXML::Element.new("FirstChild")
+ @second = REXML::Element.new("SecondChild")
+ @e << @first
+ @e << @second
+ end
+
+ it "returns elements that match xpath" do
+ @e.elements.send(@method, "FirstChild").first.should == @first
+ end
+
+ # According to the docs REXML::Element#get_elements is an alias for
+ # REXML::Elements.to_a. Implementation wise there's a difference, get_elements
+ # always needs the first param (even if it's nil).
+ # A patch was submitted:
+ # http://rubyforge.org/tracker/index.php?func=detail&aid=19354&group_id=426&atid=1698
+ it "returns all children if xpath is nil" do
+ @e.elements.send(@method).should == [@first, @second]
+ end
+
+end
+
+describe "REXML::REXML::Elements#to_a" do
+ it_behaves_like :rexml_elements_to_a, :to_a
+end
+
+describe "REXML::REXML::Element#get_elements" do
+ it_behaves_like :rexml_elements_to_a, :get_elements
+end
diff --git a/spec/ruby/library/rexml/text/append_spec.rb b/spec/ruby/library/rexml/text/append_spec.rb
new file mode 100644
index 0000000000..5e7a5bae7c
--- /dev/null
+++ b/spec/ruby/library/rexml/text/append_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#<<" do
+ it "appends a string to this text node" do
+ text = REXML::Text.new("foo")
+ text << "bar"
+ text.should == "foobar"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/clone_spec.rb b/spec/ruby/library/rexml/text/clone_spec.rb
new file mode 100644
index 0000000000..7801782ff5
--- /dev/null
+++ b/spec/ruby/library/rexml/text/clone_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#clone" do
+ it "creates a copy of this node" do
+ text = REXML::Text.new("foo")
+ text.clone.should == "foo"
+ text.clone.should == text
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/comparison_spec.rb b/spec/ruby/library/rexml/text/comparison_spec.rb
new file mode 100644
index 0000000000..119dd050a6
--- /dev/null
+++ b/spec/ruby/library/rexml/text/comparison_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#<=>" do
+ before :each do
+ @first = REXML::Text.new("abc")
+ @last = REXML::Text.new("def")
+ end
+
+ it "returns -1 if lvalue is less than rvalue" do
+ val = @first <=> @last
+ val.should == -1
+ end
+
+ it "returns -1 if lvalue is greater than rvalue" do
+ val = @last <=> @first
+ val.should == 1
+ end
+
+ it "returns 0 if both values are equal" do
+ tmp = REXML::Text.new("tmp")
+ val = tmp <=> tmp
+ val.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/empty_spec.rb b/spec/ruby/library/rexml/text/empty_spec.rb
new file mode 100644
index 0000000000..4c9c899bcb
--- /dev/null
+++ b/spec/ruby/library/rexml/text/empty_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#empty?" do
+ it "returns true if the text is empty" do
+ REXML::Text.new("").should.empty?
+ end
+
+ it "returns false if the text is not empty" do
+ REXML::Text.new("some_text").should_not.empty?
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/indent_text_spec.rb b/spec/ruby/library/rexml/text/indent_text_spec.rb
new file mode 100644
index 0000000000..73065c37da
--- /dev/null
+++ b/spec/ruby/library/rexml/text/indent_text_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#indent_text" do
+ before :each do
+ @t = REXML::Text.new("")
+ end
+ it "indents a string with default parameters" do
+ @t.indent_text("foo").should == "\tfoo"
+ end
+
+ it "accepts a custom indentation level as second argument" do
+ @t.indent_text("foo", 2, "\t", true).should == "\t\tfoo"
+ end
+
+ it "accepts a custom separator as third argument" do
+ @t.indent_text("foo", 1, "\n", true).should == "\nfoo"
+ end
+
+ it "accepts a fourth parameter to skip the first line" do
+ @t.indent_text("foo", 1, "\t", false).should == "foo"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/inspect_spec.rb b/spec/ruby/library/rexml/text/inspect_spec.rb
new file mode 100644
index 0000000000..af389890ee
--- /dev/null
+++ b/spec/ruby/library/rexml/text/inspect_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#inspect" do
+ it "inspects the string attribute as a string" do
+ REXML::Text.new("a text").inspect.should == "a text".inspect
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/new_spec.rb b/spec/ruby/library/rexml/text/new_spec.rb
new file mode 100644
index 0000000000..8b33da9294
--- /dev/null
+++ b/spec/ruby/library/rexml/text/new_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text.new" do
+
+ it "creates a Text child node with no parent" do
+ t = REXML::Text.new("test")
+ t.should be_kind_of(REXML::Child)
+ t.should == "test"
+ t.parent.should == nil
+ end
+
+ it "respects whitespace if second argument is true" do
+ t = REXML::Text.new("testing whitespace", true)
+ t.should == "testing whitespace"
+ t = REXML::Text.new(" ", true)
+ t.should == " "
+ end
+
+ it "receives a parent as third argument" do
+ e = REXML::Element.new("root")
+ t = REXML::Text.new("test", false, e)
+ t.parent.should == e
+ e.to_s.should == "<root>test</root>"
+ end
+
+ it "expects escaped text if raw is true" do
+ t = REXML::Text.new("&lt;&amp;&gt;", false, nil, true)
+ t.should == "&lt;&amp;&gt;"
+
+ ->{ REXML::Text.new("<&>", false, nil, true)}.should raise_error(Exception)
+ end
+
+ it "uses raw value of the parent if raw is nil" do
+ e1 = REXML::Element.new("root", nil, { raw: :all})
+ -> {REXML::Text.new("<&>", false, e1)}.should raise_error(Exception)
+
+ e2 = REXML::Element.new("root", nil, { raw: []})
+ e2.raw.should be_false
+ t1 = REXML::Text.new("<&>", false, e2)
+ t1.should == "&lt;&amp;&gt;"
+ end
+
+ it "escapes the values if raw is false" do
+ t = REXML::Text.new("<&>", false, nil, false)
+ t.should == "&lt;&amp;&gt;"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/node_type_spec.rb b/spec/ruby/library/rexml/text/node_type_spec.rb
new file mode 100644
index 0000000000..f44a1ede3e
--- /dev/null
+++ b/spec/ruby/library/rexml/text/node_type_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#node_type" do
+ it "returns :text" do
+ REXML::Text.new("test").node_type.should == :text
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/normalize_spec.rb b/spec/ruby/library/rexml/text/normalize_spec.rb
new file mode 100644
index 0000000000..cde11ec3c9
--- /dev/null
+++ b/spec/ruby/library/rexml/text/normalize_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text.normalize" do
+ it "escapes a string with <, >, &, ' and \" " do
+ REXML::Text.normalize("< > & \" '").should == "&lt; &gt; &amp; &quot; &apos;"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/read_with_substitution_spec.rb b/spec/ruby/library/rexml/text/read_with_substitution_spec.rb
new file mode 100644
index 0000000000..7ff26f4d53
--- /dev/null
+++ b/spec/ruby/library/rexml/text/read_with_substitution_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text.read_with_substitution" do
+ it "reads a text and escapes entities" do
+ REXML::Text.read_with_substitution("&lt; &gt; &amp; &quot; &apos;").should == "< > & \" '"
+ end
+
+ it "accepts an regex for invalid expressions and raises an error if text matches" do
+ -> {REXML::Text.read_with_substitution("this is illegal", /illegal/)}.should raise_error(Exception)
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/to_s_spec.rb b/spec/ruby/library/rexml/text/to_s_spec.rb
new file mode 100644
index 0000000000..e67632c9a1
--- /dev/null
+++ b/spec/ruby/library/rexml/text/to_s_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#to_s" do
+ it "returns the string of this Text node" do
+ u = REXML::Text.new("sean russell", false, nil, true)
+ u.to_s.should == "sean russell"
+
+ t = REXML::Text.new("some test text")
+ t.to_s.should == "some test text"
+ end
+
+ it "escapes the text" do
+ t = REXML::Text.new("& < >")
+ t.to_s.should == "&amp; &lt; &gt;"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/unnormalize_spec.rb b/spec/ruby/library/rexml/text/unnormalize_spec.rb
new file mode 100644
index 0000000000..7b507194d0
--- /dev/null
+++ b/spec/ruby/library/rexml/text/unnormalize_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text.unnormalize" do
+ it "unescapes a string with the values defined in SETUTITSBUS" do
+ REXML::Text.unnormalize("&lt; &gt; &amp; &quot; &apos;").should == "< > & \" '"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/value_spec.rb b/spec/ruby/library/rexml/text/value_spec.rb
new file mode 100644
index 0000000000..53d40c765f
--- /dev/null
+++ b/spec/ruby/library/rexml/text/value_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#value" do
+ it "returns the text value of this node" do
+ REXML::Text.new("test").value.should == "test"
+ end
+
+ it "does not escape entities" do
+ REXML::Text.new("& \"").value.should == "& \""
+ end
+
+ it "follows the respect_whitespace attribute" do
+ REXML::Text.new("test bar", false).value.should == "test bar"
+ REXML::Text.new("test bar", true).value.should == "test bar"
+ end
+
+ it "ignores the raw attribute" do
+ REXML::Text.new("sean russell", false, nil, true).value.should == "sean russell"
+ end
+ end
+
+ describe "REXML::Text#value=" do
+ before :each do
+ @t = REXML::Text.new("new")
+ end
+
+ it "sets the text of the node" do
+ @t.value = "another text"
+ @t.to_s.should == "another text"
+ end
+
+ it "escapes entities" do
+ @t.value = "<a>"
+ @t.to_s.should == "&lt;a&gt;"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/wrap_spec.rb b/spec/ruby/library/rexml/text/wrap_spec.rb
new file mode 100644
index 0000000000..331a8439e2
--- /dev/null
+++ b/spec/ruby/library/rexml/text/wrap_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#wrap" do
+ before :each do
+ @t = REXML::Text.new("abc def")
+ end
+
+ it "wraps the text at width" do
+ @t.wrap("abc def", 3, false).should == "abc\ndef"
+ end
+
+ it "returns the string if width is greater than the size of the string" do
+ @t.wrap("abc def", 10, false).should == "abc def"
+ end
+
+ it "takes a newline at the beginning option as the third parameter" do
+ @t.wrap("abc def", 3, true).should == "\nabc\ndef"
+ end
+ end
+end
diff --git a/spec/ruby/library/rexml/text/write_with_substitution_spec.rb b/spec/ruby/library/rexml/text/write_with_substitution_spec.rb
new file mode 100644
index 0000000000..840f141e3d
--- /dev/null
+++ b/spec/ruby/library/rexml/text/write_with_substitution_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ''...'3.0' do
+ require 'rexml/document'
+
+ describe "REXML::Text#write_with_substitution" do
+ before :each do
+ @t = REXML::Text.new("test")
+ @f = tmp("rexml_spec")
+ @file = File.open(@f, "w+")
+ end
+
+ after :each do
+ @file.close
+ rm_r @f
+ end
+
+ it "writes out the input to a String" do
+ s = ""
+ @t.write_with_substitution(s, "some text")
+ s.should == "some text"
+ end
+
+ it "writes out the input to an IO" do
+ @t.write_with_substitution(@file, "some text")
+ @file.rewind
+ @file.gets.should == "some text"
+ end
+
+ it "escapes characters" do
+ @t.write_with_substitution(@file, "& < >")
+ @file.rewind
+ @file.gets.should == "&amp; &lt; &gt;"
+ end
+ end
+end
diff --git a/spec/ruby/library/ripper/lex_spec.rb b/spec/ruby/library/ripper/lex_spec.rb
new file mode 100644
index 0000000000..97cfb06904
--- /dev/null
+++ b/spec/ruby/library/ripper/lex_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require 'ripper'
+
+describe "Ripper.lex" do
+ it "lexes a simple method declaration" do
+ expected = [
+ [[1, 0], :on_kw, "def", 'FNAME'],
+ [[1, 3], :on_sp, " ", 'FNAME'],
+ [[1, 4], :on_ident, "m", 'ENDFN'],
+ [[1, 5], :on_lparen, "(", 'BEG|LABEL'],
+ [[1, 6], :on_ident, "a", 'ARG'],
+ [[1, 7], :on_rparen, ")", 'ENDFN'],
+ [[1, 8], :on_sp, " ", 'BEG'],
+ [[1, 9], :on_kw, "nil", 'END'],
+ [[1, 12], :on_sp, " ", 'END'],
+ [[1, 13], :on_kw, "end", 'END']
+ ]
+ lexed = Ripper.lex("def m(a) nil end")
+ lexed.map { |e|
+ e[0...-1] + [e[-1].to_s.split('|').map { |s| s.sub(/^EXPR_/, '') }.join('|')]
+ }.should == expected
+ end
+end
diff --git a/spec/ruby/library/ripper/sexp_spec.rb b/spec/ruby/library/ripper/sexp_spec.rb
new file mode 100644
index 0000000000..6c69624c65
--- /dev/null
+++ b/spec/ruby/library/ripper/sexp_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require 'ripper'
+
+describe "Ripper.sexp" do
+ it "returns an s-expression for a method declaration" do
+ expected = [:program,
+ [[:def,
+ [:@ident, "hello", [1, 4]],
+ [:params, nil, nil, nil, nil, nil, nil, nil],
+ [:bodystmt, [[:@int, "42", [1, 11]]], nil, nil, nil]]]]
+ Ripper.sexp("def hello; 42; end").should == expected
+ end
+end
diff --git a/spec/ruby/library/rubygems/gem/bin_path_spec.rb b/spec/ruby/library/rubygems/gem/bin_path_spec.rb
new file mode 100644
index 0000000000..67b3e042c2
--- /dev/null
+++ b/spec/ruby/library/rubygems/gem/bin_path_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../../spec_helper'
+require 'rubygems'
+
+describe "Gem.bin_path" do
+ before :each do
+ @bundle_gemfile = ENV['BUNDLE_GEMFILE']
+ ENV['BUNDLE_GEMFILE'] = tmp("no-gemfile")
+ end
+
+ after :each do
+ ENV['BUNDLE_GEMFILE'] = @bundle_gemfile
+ end
+
+ platform_is_not :windows do
+ it "finds executables of default gems, which are the only files shipped for default gems" do
+ # For instance, Gem.bin_path("bundler", "bundle") is used by rails new
+
+ if Gem.respond_to? :default_specifications_dir
+ default_specifications_dir = Gem.default_specifications_dir
+ else
+ default_specifications_dir = Gem::Specification.default_specifications_dir
+ end
+
+ skip "Could not find the default gemspecs" unless Dir.exist?(default_specifications_dir)
+
+ Gem::Specification.each_spec([default_specifications_dir]) do |spec|
+ spec.executables.each do |exe|
+ path = Gem.bin_path(spec.name, exe)
+ File.should.exist?(path)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/rubygems/gem/load_path_insert_index_spec.rb b/spec/ruby/library/rubygems/gem/load_path_insert_index_spec.rb
new file mode 100644
index 0000000000..58913cddab
--- /dev/null
+++ b/spec/ruby/library/rubygems/gem/load_path_insert_index_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'rubygems'
+
+describe "Gem.load_path_insert_index" do
+ guard -> { RbConfig::TOPDIR } do
+ it "is set for an installed an installed Ruby" do
+ Gem.load_path_insert_index.should be_kind_of Integer
+ end
+ end
+end
diff --git a/spec/ruby/library/securerandom/base64_spec.rb b/spec/ruby/library/securerandom/base64_spec.rb
new file mode 100644
index 0000000000..34cd419ce2
--- /dev/null
+++ b/spec/ruby/library/securerandom/base64_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+
+require 'securerandom'
+
+describe "SecureRandom.base64" do
+ it "generates a random base64 string out of specified number of random bytes" do
+ (16..128).each do |idx|
+ base64 = SecureRandom.base64(idx)
+ base64.should be_kind_of(String)
+ base64.length.should < 2 * idx
+ base64.should =~ /^[A-Za-z0-9\+\/]+={0,2}$/
+ end
+
+ base64 = SecureRandom.base64(16.5)
+ base64.should be_kind_of(String)
+ base64.length.should < 2 * 16
+ end
+
+ it "returns an empty string when argument is 0" do
+ SecureRandom.base64(0).should == ""
+ end
+
+ it "generates different base64 strings with subsequent invocations" do
+ # quick and dirty check, but good enough
+ values = []
+ 256.times do
+ base64 = SecureRandom.base64
+ # make sure the random values are not repeating
+ values.include?(base64).should == false
+ values << base64
+ end
+ end
+
+ it "generates a random base64 string out of 32 random bytes" do
+ SecureRandom.base64.should be_kind_of(String)
+ SecureRandom.base64.length.should < 32 * 2
+ end
+
+ it "treats nil argument as default one and generates a random base64 string" do
+ SecureRandom.base64(nil).should be_kind_of(String)
+ SecureRandom.base64(nil).length.should < 32 * 2
+ end
+
+ it "raises ArgumentError on negative arguments" do
+ -> {
+ SecureRandom.base64(-1)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(5)
+ SecureRandom.base64(obj).size.should < 10
+ end
+end
diff --git a/spec/ruby/library/securerandom/bytes_spec.rb b/spec/ruby/library/securerandom/bytes_spec.rb
new file mode 100644
index 0000000000..a1ab836d16
--- /dev/null
+++ b/spec/ruby/library/securerandom/bytes_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative '../../core/random/shared/bytes'
+
+require 'securerandom'
+
+describe "SecureRandom.bytes" do
+ it_behaves_like :random_bytes, :bytes, SecureRandom
+end
diff --git a/spec/ruby/library/securerandom/hex_spec.rb b/spec/ruby/library/securerandom/hex_spec.rb
new file mode 100644
index 0000000000..bdb920b217
--- /dev/null
+++ b/spec/ruby/library/securerandom/hex_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+
+require 'securerandom'
+
+describe "SecureRandom.hex" do
+ it "generates a random hex string of length twice the specified argument" do
+ (1..64).each do |idx|
+ hex = SecureRandom.hex(idx)
+ hex.should be_kind_of(String)
+ hex.length.should == 2 * idx
+ end
+
+ base64 = SecureRandom.hex(5.5)
+ base64.should be_kind_of(String)
+ base64.length.should eql(10)
+ end
+
+ it "returns an empty string when argument is 0" do
+ SecureRandom.hex(0).should == ""
+ end
+
+ it "generates different hex strings with subsequent invocations" do
+ # quick and dirty check, but good enough
+ values = []
+ 256.times do
+ hex = SecureRandom.hex
+ # make sure the random values are not repeating
+ values.include?(hex).should == false
+ values << hex
+ end
+ end
+
+ it "generates a random hex string of length 32 if no argument is provided" do
+ SecureRandom.hex.should be_kind_of(String)
+ SecureRandom.hex.length.should == 32
+ end
+
+ it "treats nil argument as default one and generates a random hex string of length 32" do
+ SecureRandom.hex(nil).should be_kind_of(String)
+ SecureRandom.hex(nil).length.should == 32
+ end
+
+ it "raises ArgumentError on negative arguments" do
+ -> {
+ SecureRandom.hex(-1)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(5)
+ SecureRandom.hex(obj).size.should eql(10)
+ end
+end
diff --git a/spec/ruby/library/securerandom/random_bytes_spec.rb b/spec/ruby/library/securerandom/random_bytes_spec.rb
new file mode 100644
index 0000000000..ed3a02255c
--- /dev/null
+++ b/spec/ruby/library/securerandom/random_bytes_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative '../../core/random/shared/bytes'
+
+require 'securerandom'
+
+describe "SecureRandom.random_bytes" do
+ it_behaves_like :random_bytes, :random_bytes, SecureRandom
+
+ it "generates a random binary string of length 16 if no argument is provided" do
+ bytes = SecureRandom.random_bytes
+ bytes.should be_kind_of(String)
+ bytes.length.should == 16
+ end
+
+ it "generates a random binary string of length 16 if argument is nil" do
+ bytes = SecureRandom.random_bytes(nil)
+ bytes.should be_kind_of(String)
+ bytes.length.should == 16
+ end
+
+ it "generates a random binary string of specified length" do
+ (1..64).each do |idx|
+ bytes = SecureRandom.random_bytes(idx)
+ bytes.should be_kind_of(String)
+ bytes.length.should == idx
+ end
+
+ SecureRandom.random_bytes(2.2).length.should eql(2)
+ end
+
+ it "generates different binary strings with subsequent invocations" do
+ # quick and dirty check, but good enough
+ values = []
+ 256.times do
+ val = SecureRandom.random_bytes
+ # make sure the random bytes are not repeating
+ values.include?(val).should == false
+ values << val
+ end
+ end
+
+ it "raises ArgumentError on negative arguments" do
+ -> {
+ SecureRandom.random_bytes(-1)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(5)
+ SecureRandom.random_bytes(obj).size.should eql(5)
+ end
+end
diff --git a/spec/ruby/library/securerandom/random_number_spec.rb b/spec/ruby/library/securerandom/random_number_spec.rb
new file mode 100644
index 0000000000..03781f4901
--- /dev/null
+++ b/spec/ruby/library/securerandom/random_number_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+require_relative '../../core/random/shared/rand'
+
+require 'securerandom'
+
+describe "SecureRandom.random_number" do
+ it_behaves_like :random_number, :rand, SecureRandom
+ it_behaves_like :random_number, :random_number, SecureRandom
+
+ it "generates a random positive number smaller then the positive integer argument" do
+ (1..64).each do |idx|
+ num = SecureRandom.random_number(idx)
+ num.should be_kind_of(Integer)
+ (0 <= num).should == true
+ (num < idx).should == true
+ end
+ end
+
+ it "generates a random (potentially bignum) integer value for bignum argument" do
+ max = 12345678901234567890
+ 11.times do
+ num = SecureRandom.random_number max
+ num.should be_kind_of(Integer)
+ (0 <= num).should == true
+ (num < max).should == true
+ end
+ end
+
+ it "generates a random float number between 0.0 and 1.0 if no argument provided" do
+ 64.times do
+ num = SecureRandom.random_number
+ num.should be_kind_of(Float)
+ (0.0 <= num).should == true
+ (num < 1.0).should == true
+ end
+ end
+
+ it "generates a random value in given (integer) range limits" do
+ 64.times do
+ num = SecureRandom.random_number 11...13
+ num.should be_kind_of(Integer)
+ (11 <= num).should == true
+ (num < 13).should == true
+ end
+ end
+
+ it "generates a random value in given big (integer) range limits" do
+ lower = 12345678901234567890
+ upper = 12345678901234567890 + 5
+ 32.times do
+ num = SecureRandom.random_number lower..upper
+ num.should be_kind_of(Integer)
+ (lower <= num).should == true
+ (num <= upper).should == true
+ end
+ end
+
+ it "generates a random value in given (float) range limits" do
+ 64.times do
+ num = SecureRandom.random_number 0.6..0.9
+ num.should be_kind_of(Float)
+ (0.6 <= num).should == true
+ (num <= 0.9).should == true
+ end
+ end
+
+ it "generates a random float number between 0.0 and 1.0 if argument is negative" do
+ num = SecureRandom.random_number(-10)
+ num.should be_kind_of(Float)
+ (0.0 <= num).should == true
+ (num < 1.0).should == true
+ end
+
+ it "generates a random float number between 0.0 and 1.0 if argument is negative float" do
+ num = SecureRandom.random_number(-11.1)
+ num.should be_kind_of(Float)
+ (0.0 <= num).should == true
+ (num < 1.0).should == true
+ end
+
+ it "generates different float numbers with subsequent invocations" do
+ # quick and dirty check, but good enough
+ values = []
+ 256.times do
+ val = SecureRandom.random_number
+ # make sure the random values are not repeating
+ values.include?(val).should == false
+ values << val
+ end
+ end
+
+ it "raises ArgumentError if the argument is non-numeric" do
+ -> {
+ SecureRandom.random_number(Object.new)
+ }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/set/add_spec.rb b/spec/ruby/library/set/add_spec.rb
new file mode 100644
index 0000000000..68356cc111
--- /dev/null
+++ b/spec/ruby/library/set/add_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require 'set'
+require_relative 'shared/add'
+
+describe "Set#add" do
+ it_behaves_like :set_add, :add
+end
+
+describe "Set#add?" do
+ before :each do
+ @set = Set.new
+ end
+
+ it "adds the passed Object to self" do
+ @set.add?("cat")
+ @set.should include("cat")
+ end
+
+ it "returns self when the Object has not yet been added to self" do
+ @set.add?("cat").should equal(@set)
+ end
+
+ it "returns nil when the Object has already been added to self" do
+ @set.add?("cat")
+ @set.add?("cat").should be_nil
+ end
+end
diff --git a/spec/ruby/library/set/append_spec.rb b/spec/ruby/library/set/append_spec.rb
new file mode 100644
index 0000000000..8b3498b779
--- /dev/null
+++ b/spec/ruby/library/set/append_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require 'set'
+require_relative 'shared/add'
+
+describe "Set#<<" do
+ it_behaves_like :set_add, :<<
+end
diff --git a/spec/ruby/library/set/case_compare_spec.rb b/spec/ruby/library/set/case_compare_spec.rb
new file mode 100644
index 0000000000..70d392a27d
--- /dev/null
+++ b/spec/ruby/library/set/case_compare_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+require 'set'
+
+describe "Set#===" do
+ it_behaves_like :set_include, :===
+
+ it "is an alias for include?" do
+ set = Set.new
+ set.method(:===).should == set.method(:include?)
+ end
+end
diff --git a/spec/ruby/library/set/case_equality_spec.rb b/spec/ruby/library/set/case_equality_spec.rb
new file mode 100644
index 0000000000..10cbfd380a
--- /dev/null
+++ b/spec/ruby/library/set/case_equality_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+require 'set'
+
+describe "Set#===" do
+ it_behaves_like :set_include, :===
+end
diff --git a/spec/ruby/library/set/classify_spec.rb b/spec/ruby/library/set/classify_spec.rb
new file mode 100644
index 0000000000..ec600c91d6
--- /dev/null
+++ b/spec/ruby/library/set/classify_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#classify" do
+ before :each do
+ @set = Set["one", "two", "three", "four"]
+ end
+
+ it "yields each Object in self" do
+ res = []
+ @set.classify { |x| res << x }
+ res.sort.should == ["one", "two", "three", "four"].sort
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @set.classify
+ enum.should be_an_instance_of(Enumerator)
+
+ classified = enum.each { |x| x.length }
+ classified.should == { 3 => Set["one", "two"], 4 => Set["four"], 5 => Set["three"] }
+ end
+
+ it "classifies the Objects in self based on the block's return value" do
+ classified = @set.classify { |x| x.length }
+ classified.should == { 3 => Set["one", "two"], 4 => Set["four"], 5 => Set["three"] }
+ end
+end
diff --git a/spec/ruby/library/set/clear_spec.rb b/spec/ruby/library/set/clear_spec.rb
new file mode 100644
index 0000000000..2b1c9c5b5a
--- /dev/null
+++ b/spec/ruby/library/set/clear_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#clear" do
+ before :each do
+ @set = Set["one", "two", "three", "four"]
+ end
+
+ it "removes all elements from self" do
+ @set.clear
+ @set.should be_empty
+ end
+
+ it "returns self" do
+ @set.clear.should equal(@set)
+ end
+end
diff --git a/spec/ruby/library/set/collect_spec.rb b/spec/ruby/library/set/collect_spec.rb
new file mode 100644
index 0000000000..f8813a9331
--- /dev/null
+++ b/spec/ruby/library/set/collect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require 'set'
+require_relative 'shared/collect'
+
+describe "Set#collect!" do
+ it_behaves_like :set_collect_bang, :collect!
+end
diff --git a/spec/ruby/library/set/compare_by_identity_spec.rb b/spec/ruby/library/set/compare_by_identity_spec.rb
new file mode 100644
index 0000000000..9ed1602189
--- /dev/null
+++ b/spec/ruby/library/set/compare_by_identity_spec.rb
@@ -0,0 +1,143 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#compare_by_identity" do
+ it "compares its members by identity" do
+ a = "a"
+ b1 = "b"
+ b2 = "b"
+
+ set = Set.new
+ set.compare_by_identity
+ set.merge([a, a, b1, b2])
+ set.to_a.sort.should == [a, b1, b2].sort
+ end
+
+ it "causes future comparisons on the receiver to be made by identity" do
+ elt = [1]
+ set = Set.new
+ set << elt
+ set.member?(elt.dup).should be_true
+ set.compare_by_identity
+ set.member?(elt.dup).should be_false
+ end
+
+ it "rehashes internally so that old members can be looked up" do
+ set = Set.new
+ (1..10).each { |k| set << k }
+ o = Object.new
+ def o.hash; 123; end
+ set << o
+ set.compare_by_identity
+ set.member?(o).should be_true
+ end
+
+ it "returns self" do
+ set = Set.new
+ result = set.compare_by_identity
+ result.should equal(set)
+ end
+
+ it "is idempotent and has no effect on an already compare_by_identity set" do
+ set = Set.new.compare_by_identity
+ set << :foo
+ set.compare_by_identity.should equal(set)
+ set.should.compare_by_identity?
+ set.to_a.should == [:foo]
+ end
+
+ it "uses the semantics of BasicObject#equal? to determine members identity" do
+ :a.equal?(:a).should == true
+ Set.new.compare_by_identity.merge([:a, :a]).to_a.should == [:a]
+
+ ary1 = [1]
+ ary2 = [1]
+ ary1.equal?(ary2).should == false
+ Set.new.compare_by_identity.merge([ary1, ary2]).to_a.sort.should == [ary1, ary2].sort
+ end
+
+ it "uses #equal? semantics, but doesn't actually call #equal? to determine identity" do
+ set = Set.new.compare_by_identity
+ obj = mock("equal")
+ obj.should_not_receive(:equal?)
+ set << :foo
+ set << obj
+ set.to_a.should == [:foo, obj]
+ end
+
+ it "does not call #hash on members" do
+ elt = mock("element")
+ elt.should_not_receive(:hash)
+ set = Set.new.compare_by_identity
+ set << elt
+ set.member?(elt).should be_true
+ end
+
+ it "regards #dup'd objects as having different identities" do
+ a1 = "a"
+ a2 = a1.dup
+
+ set = Set.new.compare_by_identity
+ set.merge([a1, a2])
+ set.to_a.sort.should == [a1, a2].sort
+ end
+
+ it "regards #clone'd objects as having different identities" do
+ a1 = "a"
+ a2 = a1.clone
+
+ set = Set.new.compare_by_identity
+ set.merge([a1, a2])
+ set.to_a.sort.should == [a1, a2].sort
+ end
+
+ it "raises a FrozenError on frozen sets" do
+ set = Set.new.freeze
+ -> {
+ set.compare_by_identity
+ }.should raise_error(FrozenError, /frozen Hash/)
+ end
+
+ it "persists over #dups" do
+ set = Set.new.compare_by_identity
+ set << :a
+ set_dup = set.dup
+ set_dup.should == set
+ set_dup << :a
+ set_dup.to_a.should == [:a]
+ end
+
+ it "persists over #clones" do
+ set = Set.new.compare_by_identity
+ set << :a
+ set_clone = set.clone
+ set_clone.should == set
+ set_clone << :a
+ set_clone.to_a.should == [:a]
+ end
+
+ it "is not equal to set what does not compare by identity" do
+ Set.new([1, 2]).should == Set.new([1, 2])
+ Set.new([1, 2]).should_not == Set.new([1, 2]).compare_by_identity
+ end
+end
+
+describe "Set#compare_by_identity?" do
+ it "returns false by default" do
+ Set.new.should_not.compare_by_identity?
+ end
+
+ it "returns true once #compare_by_identity has been invoked on self" do
+ set = Set.new
+ set.compare_by_identity
+ set.should.compare_by_identity?
+ end
+
+ it "returns true when called multiple times on the same set" do
+ set = Set.new
+ set.compare_by_identity
+ set.should.compare_by_identity?
+ set.should.compare_by_identity?
+ set.should.compare_by_identity?
+ end
+end
diff --git a/spec/ruby/library/set/comparison_spec.rb b/spec/ruby/library/set/comparison_spec.rb
new file mode 100644
index 0000000000..b851ea3d57
--- /dev/null
+++ b/spec/ruby/library/set/comparison_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require 'set'
+
+ruby_version_is "3.0" do
+ describe "Set#<=>" do
+ it "returns 0 if the sets are equal" do
+ (Set[] <=> Set[]).should == 0
+ (Set[:a, :b, :c] <=> Set[:a, :b, :c]).should == 0
+ end
+
+ it "returns -1 if the set is a proper subset of the other set" do
+ (Set[] <=> Set[1]).should == -1
+ (Set[1, 2] <=> Set[1, 2, 3]).should == -1
+ end
+
+ it "returns +1 if the set is a proper superset of other set" do
+ (Set[1] <=> Set[]).should == +1
+ (Set[1, 2, 3] <=> Set[1, 2]).should == +1
+ end
+
+ it "returns nil if the set has unique elements" do
+ (Set[1, 2, 3] <=> Set[:a, :b, :c]).should be_nil
+ end
+
+ it "returns nil when the argument is not set-like" do
+ (Set[] <=> false).should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/set/constructor_spec.rb b/spec/ruby/library/set/constructor_spec.rb
new file mode 100644
index 0000000000..bb84861514
--- /dev/null
+++ b/spec/ruby/library/set/constructor_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set[]" do
+ it "returns a new Set populated with the passed Objects" do
+ set = Set[1, 2, 3]
+
+ set.instance_of?(Set).should be_true
+ set.size.should eql(3)
+
+ set.should include(1)
+ set.should include(2)
+ set.should include(3)
+ end
+end
diff --git a/spec/ruby/library/set/delete_if_spec.rb b/spec/ruby/library/set/delete_if_spec.rb
new file mode 100644
index 0000000000..33caeeaab7
--- /dev/null
+++ b/spec/ruby/library/set/delete_if_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#delete_if" do
+ before :each do
+ @set = Set["one", "two", "three"]
+ end
+
+ it "yields every element of self" do
+ ret = []
+ @set.delete_if { |x| ret << x }
+ ret.sort.should == ["one", "two", "three"].sort
+ end
+
+ it "deletes every element from self for which the passed block returns true" do
+ @set.delete_if { |x| x.size == 3 }
+ @set.size.should eql(1)
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+
+ it "returns self" do
+ @set.delete_if { |x| x }.should equal(@set)
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @set.delete_if
+ enum.should be_an_instance_of(Enumerator)
+
+ enum.each { |x| x.size == 3 }
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+end
diff --git a/spec/ruby/library/set/delete_spec.rb b/spec/ruby/library/set/delete_spec.rb
new file mode 100644
index 0000000000..b12524384a
--- /dev/null
+++ b/spec/ruby/library/set/delete_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#delete" do
+ before :each do
+ @set = Set["a", "b", "c"]
+ end
+
+ it "deletes the passed Object from self" do
+ @set.delete("a")
+ @set.should_not include("a")
+ end
+
+ it "returns self" do
+ @set.delete("a").should equal(@set)
+ @set.delete("x").should equal(@set)
+ end
+end
+
+describe "Set#delete?" do
+ before :each do
+ @set = Set["a", "b", "c"]
+ end
+
+ it "deletes the passed Object from self" do
+ @set.delete?("a")
+ @set.should_not include("a")
+ end
+
+ it "returns self when the passed Object is in self" do
+ @set.delete?("a").should equal(@set)
+ end
+
+ it "returns nil when the passed Object is not in self" do
+ @set.delete?("x").should be_nil
+ end
+end
diff --git a/spec/ruby/library/set/difference_spec.rb b/spec/ruby/library/set/difference_spec.rb
new file mode 100644
index 0000000000..422f2ed3c7
--- /dev/null
+++ b/spec/ruby/library/set/difference_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require 'set'
+require_relative 'shared/difference'
+
+describe "Set#difference" do
+ it_behaves_like :set_difference, :difference
+end
diff --git a/spec/ruby/library/set/disjoint_spec.rb b/spec/ruby/library/set/disjoint_spec.rb
new file mode 100644
index 0000000000..ea3b141455
--- /dev/null
+++ b/spec/ruby/library/set/disjoint_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/set_like'
+require 'set'
+
+describe "Set#disjoint?" do
+ it "returns false when two Sets have at least one element in common" do
+ Set[1, 2].disjoint?(Set[2, 3]).should == false
+ end
+
+ it "returns true when two Sets have no element in common" do
+ Set[1, 2].disjoint?(Set[3, 4]).should == true
+ end
+
+ context "when comparing to a Set-like object" do
+ it "returns false when a Set has at least one element in common with a Set-like object" do
+ Set[1, 2].disjoint?(SetSpecs::SetLike.new([2, 3])).should be_false
+ end
+
+ it "returns true when a Set has no element in common with a Set-like object" do
+ Set[1, 2].disjoint?(SetSpecs::SetLike.new([3, 4])).should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/set/divide_spec.rb b/spec/ruby/library/set/divide_spec.rb
new file mode 100644
index 0000000000..fdd8cd9622
--- /dev/null
+++ b/spec/ruby/library/set/divide_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#divide" do
+ it "divides self into a set of subsets based on the blocks return values" do
+ set = Set["one", "two", "three", "four", "five"].divide { |x| x.length }
+ set.map { |x| x.to_a.sort }.sort.should == [["five", "four"], ["one", "two"], ["three"]]
+ end
+
+ it "yields each Object to the block" do
+ ret = []
+ Set["one", "two", "three", "four", "five"].divide { |x| ret << x }
+ ret.sort.should == ["five", "four", "one", "three", "two"]
+ end
+
+ # BUG: Does not raise a LocalJumpError, but a NoMethodError
+ #
+ # it "raises a LocalJumpError when not passed a block" do
+ # lambda { Set[1].divide }.should raise_error(LocalJumpError)
+ # end
+end
+
+describe "Set#divide when passed a block with an arity of 2" do
+ it "divides self into a set of subsets based on the blocks return values" do
+ set = Set[1, 3, 4, 6, 9, 10, 11].divide { |x, y| (x - y).abs == 1 }
+ set.map{ |x| x.to_a.sort }.sort.should == [[1], [3, 4], [6], [9, 10, 11]]
+ end
+
+ it "yields each two Object to the block" do
+ ret = []
+ Set[1, 2].divide { |x, y| ret << [x, y] }
+ ret.sort.should == [[1, 1], [1, 2], [2, 1], [2, 2]]
+ end
+end
diff --git a/spec/ruby/library/set/each_spec.rb b/spec/ruby/library/set/each_spec.rb
new file mode 100644
index 0000000000..9bb5ead03a
--- /dev/null
+++ b/spec/ruby/library/set/each_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#each" do
+ before :each do
+ @set = Set[1, 2, 3]
+ end
+
+ it "yields each Object in self" do
+ ret = []
+ @set.each { |x| ret << x }
+ ret.sort.should == [1, 2, 3]
+ end
+
+ it "returns self" do
+ @set.each { |x| x }.should equal(@set)
+ end
+
+ it "returns an Enumerator when not passed a block" do
+ enum = @set.each
+
+ ret = []
+ enum.each { |x| ret << x }
+ ret.sort.should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/library/set/empty_spec.rb b/spec/ruby/library/set/empty_spec.rb
new file mode 100644
index 0000000000..1789a664c7
--- /dev/null
+++ b/spec/ruby/library/set/empty_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#empty?" do
+ it "returns true if self is empty" do
+ Set[].empty?.should be_true
+ Set[1].empty?.should be_false
+ Set[1,2,3].empty?.should be_false
+ end
+end
diff --git a/spec/ruby/library/set/enumerable/to_set_spec.rb b/spec/ruby/library/set/enumerable/to_set_spec.rb
new file mode 100644
index 0000000000..3790d8deee
--- /dev/null
+++ b/spec/ruby/library/set/enumerable/to_set_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+require 'set'
+
+describe "Enumerable#to_set" do
+ it "returns a new Set created from self" do
+ [1, 2, 3].to_set.should == Set[1, 2, 3]
+ {a: 1, b: 2}.to_set.should == Set[[:b, 2], [:a, 1]]
+ end
+
+ ruby_version_is ''...'3.0' do
+ it "allows passing an alternate class for Set" do
+ sorted_set = [1, 2, 3].to_set(SortedSet)
+ sorted_set.should == SortedSet[1, 2, 3]
+ sorted_set.instance_of?(SortedSet).should == true
+ end
+ end
+
+ it "passes down passed blocks" do
+ [1, 2, 3].to_set { |x| x * x }.should == Set[1, 4, 9]
+ end
+end
diff --git a/spec/ruby/library/set/eql_spec.rb b/spec/ruby/library/set/eql_spec.rb
new file mode 100644
index 0000000000..dd8e633775
--- /dev/null
+++ b/spec/ruby/library/set/eql_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#eql?" do
+ it "returns true when the passed argument is a Set and contains the same elements" do
+ Set[].should eql(Set[])
+ Set[1, 2, 3].should eql(Set[1, 2, 3])
+ Set[1, 2, 3].should eql(Set[3, 2, 1])
+ Set["a", :b, ?c].should eql(Set[?c, :b, "a"])
+
+ Set[1, 2, 3].should_not eql(Set[1.0, 2, 3])
+ Set[1, 2, 3].should_not eql(Set[2, 3])
+ Set[1, 2, 3].should_not eql(Set[])
+ end
+end
diff --git a/spec/ruby/library/set/equal_value_spec.rb b/spec/ruby/library/set/equal_value_spec.rb
new file mode 100644
index 0000000000..f5b5f790c0
--- /dev/null
+++ b/spec/ruby/library/set/equal_value_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/set_like'
+require 'set'
+
+describe "Set#==" do
+ it "returns true when the passed Object is a Set and self and the Object contain the same elements" do
+ Set[].should == Set[]
+ Set[1, 2, 3].should == Set[1, 2, 3]
+ Set["1", "2", "3"].should == Set["1", "2", "3"]
+
+ Set[1, 2, 3].should_not == Set[1.0, 2, 3]
+ Set[1, 2, 3].should_not == [1, 2, 3]
+ end
+
+ it "does not depend on the order of the elements" do
+ Set[1, 2, 3].should == Set[3, 2, 1]
+ Set[:a, "b", ?c].should == Set[?c, "b", :a]
+ end
+
+ it "does not depend on the order of nested Sets" do
+ Set[Set[1], Set[2], Set[3]].should == Set[Set[3], Set[2], Set[1]]
+
+ set1 = Set[Set["a", "b"], Set["c", "d"], Set["e", "f"]]
+ set2 = Set[Set["c", "d"], Set["a", "b"], Set["e", "f"]]
+ set1.should == set2
+ end
+
+ context "when comparing to a Set-like object" do
+ it "returns true when a Set and a Set-like object contain the same elements" do
+ Set[1, 2, 3].should == SetSpecs::SetLike.new([1, 2, 3])
+ end
+ end
+end
diff --git a/spec/ruby/library/set/exclusion_spec.rb b/spec/ruby/library/set/exclusion_spec.rb
new file mode 100644
index 0000000000..5bc4b5a2bf
--- /dev/null
+++ b/spec/ruby/library/set/exclusion_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#^" do
+ before :each do
+ @set = Set[1, 2, 3, 4]
+ end
+
+ it "returns a new Set containing elements that are not in both self and the passed Enumerable" do
+ (@set ^ Set[3, 4, 5]).should == Set[1, 2, 5]
+ (@set ^ [3, 4, 5]).should == Set[1, 2, 5]
+ end
+
+ it "raises an ArgumentError when passed a non-Enumerable" do
+ -> { @set ^ 3 }.should raise_error(ArgumentError)
+ -> { @set ^ Object.new }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/set/filter_spec.rb b/spec/ruby/library/set/filter_spec.rb
new file mode 100644
index 0000000000..779254ad68
--- /dev/null
+++ b/spec/ruby/library/set/filter_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/select'
+
+describe "Set#filter!" do
+ it_behaves_like :set_select_bang, :filter!
+end
diff --git a/spec/ruby/library/set/fixtures/set_like.rb b/spec/ruby/library/set/fixtures/set_like.rb
new file mode 100644
index 0000000000..46f61a451e
--- /dev/null
+++ b/spec/ruby/library/set/fixtures/set_like.rb
@@ -0,0 +1,31 @@
+require 'set'
+
+module SetSpecs
+ # This class is used to test the interaction of "Set-like" objects with real Sets
+ #
+ # These "Set-like" objects reply to is_a?(Set) with true and thus real Set objects are able to transparently
+ # interoperate with them in a duck-typing manner.
+ class SetLike
+ include Enumerable
+
+ def is_a?(klass)
+ super || klass == ::Set
+ end
+
+ def initialize(entries)
+ @entries = entries
+ end
+
+ def each(&block)
+ @entries.each(&block)
+ end
+
+ def inspect
+ "#<#{self.class}: {#{map(&:inspect).join(", ")}}>"
+ end
+
+ def size
+ @entries.size
+ end
+ end
+end
diff --git a/spec/ruby/library/set/flatten_merge_spec.rb b/spec/ruby/library/set/flatten_merge_spec.rb
new file mode 100644
index 0000000000..f2c99a9481
--- /dev/null
+++ b/spec/ruby/library/set/flatten_merge_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#flatten_merge" do
+ it "is protected" do
+ Set.should have_protected_instance_method("flatten_merge")
+ end
+
+ it "flattens the passed Set and merges it into self" do
+ set1 = Set[1, 2]
+ set2 = Set[3, 4, Set[5, 6]]
+
+ set1.send(:flatten_merge, set2).should == Set[1, 2, 3, 4, 5, 6]
+ end
+
+ it "raises an ArgumentError when trying to flatten a recursive Set" do
+ set1 = Set[1, 2, 3]
+ set2 = Set[5, 6, 7]
+ set2 << set2
+
+ -> { set1.send(:flatten_merge, set2) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/set/flatten_spec.rb b/spec/ruby/library/set/flatten_spec.rb
new file mode 100644
index 0000000000..4ac83ea825
--- /dev/null
+++ b/spec/ruby/library/set/flatten_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/set_like'
+require 'set'
+
+describe "Set#flatten" do
+ it "returns a copy of self with each included Set flattened" do
+ set = Set[1, 2, Set[3, 4, Set[5, 6, Set[7, 8]]], 9, 10]
+ flattened_set = set.flatten
+
+ flattened_set.should_not equal(set)
+ flattened_set.should == Set[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ end
+
+ it "raises an ArgumentError when self is recursive" do
+ (set = Set[]) << set
+ -> { set.flatten }.should raise_error(ArgumentError)
+ end
+
+ context "when Set contains a Set-like object" do
+ it "returns a copy of self with each included Set-like object flattened" do
+ Set[SetSpecs::SetLike.new([1])].flatten.should == Set[1]
+ end
+ end
+end
+
+describe "Set#flatten!" do
+ it "flattens self" do
+ set = Set[1, 2, Set[3, 4, Set[5, 6, Set[7, 8]]], 9, 10]
+ set.flatten!
+ set.should == Set[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ end
+
+ it "returns self when self was modified" do
+ set = Set[1, 2, Set[3, 4]]
+ set.flatten!.should equal(set)
+ end
+
+ it "returns nil when self was not modified" do
+ set = Set[1, 2, 3, 4]
+ set.flatten!.should be_nil
+ end
+
+ it "raises an ArgumentError when self is recursive" do
+ (set = Set[]) << set
+ -> { set.flatten! }.should raise_error(ArgumentError)
+ end
+
+ context "when Set contains a Set-like object" do
+ it "flattens self, including Set-like objects" do
+ Set[SetSpecs::SetLike.new([1])].flatten!.should == Set[1]
+ end
+ end
+end
diff --git a/spec/ruby/library/set/hash_spec.rb b/spec/ruby/library/set/hash_spec.rb
new file mode 100644
index 0000000000..47c43c05f1
--- /dev/null
+++ b/spec/ruby/library/set/hash_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#hash" do
+ it "is static" do
+ Set[].hash.should == Set[].hash
+ Set[1, 2, 3].hash.should == Set[1, 2, 3].hash
+ Set[:a, "b", ?c].hash.should == Set[?c, "b", :a].hash
+
+ Set[].hash.should_not == Set[1, 2, 3].hash
+ Set[1, 2, 3].hash.should_not == Set[:a, "b", ?c].hash
+ end
+end
diff --git a/spec/ruby/library/set/include_spec.rb b/spec/ruby/library/set/include_spec.rb
new file mode 100644
index 0000000000..68532d9a04
--- /dev/null
+++ b/spec/ruby/library/set/include_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+require 'set'
+
+describe "Set#include?" do
+ it_behaves_like :set_include, :include?
+end
diff --git a/spec/ruby/library/set/initialize_clone_spec.rb b/spec/ruby/library/set/initialize_clone_spec.rb
new file mode 100644
index 0000000000..62985987fa
--- /dev/null
+++ b/spec/ruby/library/set/initialize_clone_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#initialize_clone" do
+ ruby_version_is "3.0" do
+ # See https://bugs.ruby-lang.org/issues/14266
+ it "does not freeze the new Set when called from clone(freeze: false)" do
+ set1 = Set[1, 2]
+ set1.freeze
+ set2 = set1.clone(freeze: false)
+ set1.frozen?.should == true
+ set2.frozen?.should == false
+ set2.add 3
+ set1.should == Set[1, 2]
+ set2.should == Set[1, 2, 3]
+ end
+ end
+end
diff --git a/spec/ruby/library/set/initialize_spec.rb b/spec/ruby/library/set/initialize_spec.rb
new file mode 100644
index 0000000000..76ebc0a20a
--- /dev/null
+++ b/spec/ruby/library/set/initialize_spec.rb
@@ -0,0 +1,73 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#initialize" do
+ it "is private" do
+ Set.should have_private_instance_method(:initialize)
+ end
+
+ it "adds all elements of the passed Enumerable to self" do
+ s = Set.new([1, 2, 3])
+ s.size.should eql(3)
+ s.should include(1)
+ s.should include(2)
+ s.should include(3)
+ end
+
+ it "uses #each_entry on the provided Enumerable" do
+ enumerable = MockObject.new('mock-enumerable')
+ enumerable.should_receive(:each_entry).and_yield(1).and_yield(2).and_yield(3)
+ s = Set.new(enumerable)
+ s.size.should eql(3)
+ s.should include(1)
+ s.should include(2)
+ s.should include(3)
+ end
+
+ it "uses #each on the provided Enumerable if it does not respond to #each_entry" do
+ enumerable = MockObject.new('mock-enumerable')
+ enumerable.should_receive(:each).and_yield(1).and_yield(2).and_yield(3)
+ s = Set.new(enumerable)
+ s.size.should eql(3)
+ s.should include(1)
+ s.should include(2)
+ s.should include(3)
+ end
+
+ it "raises if the provided Enumerable does not respond to #each_entry or #each" do
+ enumerable = MockObject.new('mock-enumerable')
+ -> { Set.new(enumerable) }.should raise_error(ArgumentError, "value must be enumerable")
+ end
+
+ it "should initialize with empty array and set" do
+ s = Set.new([])
+ s.size.should eql(0)
+
+ s = Set.new({})
+ s.size.should eql(0)
+ end
+
+ it "preprocesses all elements by a passed block before adding to self" do
+ s = Set.new([1, 2, 3]) { |x| x * x }
+ s.size.should eql(3)
+ s.should include(1)
+ s.should include(4)
+ s.should include(9)
+ end
+
+ it "should initialize with empty array and block" do
+ s = Set.new([]) { |x| x * x }
+ s.size.should eql(0)
+ end
+
+ it "should initialize with empty set and block" do
+ s = Set.new(Set.new) { |x| x * x }
+ s.size.should eql(0)
+ end
+
+ it "should initialize with just block" do
+ s = Set.new { |x| x * x }
+ s.size.should eql(0)
+ s.should eql(Set.new)
+ end
+end
diff --git a/spec/ruby/library/set/inspect_spec.rb b/spec/ruby/library/set/inspect_spec.rb
new file mode 100644
index 0000000000..4060c63b95
--- /dev/null
+++ b/spec/ruby/library/set/inspect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/inspect'
+require 'set'
+
+describe "Set#inspect" do
+ it_behaves_like :set_inspect, :inspect
+end
diff --git a/spec/ruby/library/set/intersect_spec.rb b/spec/ruby/library/set/intersect_spec.rb
new file mode 100644
index 0000000000..e60f06db94
--- /dev/null
+++ b/spec/ruby/library/set/intersect_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/set_like'
+require 'set'
+
+describe "Set#intersect?" do
+ it "returns true when two Sets have at least one element in common" do
+ Set[1, 2].intersect?(Set[2, 3]).should == true
+ end
+
+ it "returns false when two Sets have no element in common" do
+ Set[1, 2].intersect?(Set[3, 4]).should == false
+ end
+
+ context "when comparing to a Set-like object" do
+ it "returns true when a Set has at least one element in common with a Set-like object" do
+ Set[1, 2].intersect?(SetSpecs::SetLike.new([2, 3])).should be_true
+ end
+
+ it "returns false when a Set has no element in common with a Set-like object" do
+ Set[1, 2].intersect?(SetSpecs::SetLike.new([3, 4])).should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/set/intersection_spec.rb b/spec/ruby/library/set/intersection_spec.rb
new file mode 100644
index 0000000000..792c2d8f07
--- /dev/null
+++ b/spec/ruby/library/set/intersection_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/intersection'
+require 'set'
+
+describe "Set#intersection" do
+ it_behaves_like :set_intersection, :intersection
+end
+
+describe "Set#&" do
+ it_behaves_like :set_intersection, :&
+end
diff --git a/spec/ruby/library/set/join_spec.rb b/spec/ruby/library/set/join_spec.rb
new file mode 100644
index 0000000000..7498a91d98
--- /dev/null
+++ b/spec/ruby/library/set/join_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require 'set'
+
+ruby_version_is "3.0" do
+ describe "Set#join" do
+ it "returns an empty string if the Set is empty" do
+ Set[].join.should == ''
+ end
+
+ it "returns a new string formed by joining elements after conversion" do
+ set = Set[:a, :b, :c]
+ set.join.should == "abc"
+ end
+
+ it "does not separate elements when the passed separator is nil" do
+ set = Set[:a, :b, :c]
+ set.join(nil).should == "abc"
+ end
+
+ it "returns a string formed by concatenating each element separated by the separator" do
+ set = Set[:a, :b, :c]
+ set.join(' | ').should == "a | b | c"
+ end
+
+ it "calls #to_a to convert the Set in to an Array" do
+ set = Set[:a, :b, :c]
+ set.should_receive(:to_a).and_return([:a, :b, :c])
+ set.join.should == "abc"
+ end
+ end
+end
diff --git a/spec/ruby/library/set/keep_if_spec.rb b/spec/ruby/library/set/keep_if_spec.rb
new file mode 100644
index 0000000000..7edc80769f
--- /dev/null
+++ b/spec/ruby/library/set/keep_if_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#keep_if" do
+ before :each do
+ @set = Set["one", "two", "three"]
+ end
+
+ it "yields every element of self" do
+ ret = []
+ @set.keep_if { |x| ret << x }
+ ret.sort.should == ["one", "two", "three"].sort
+ end
+
+ it "keeps every element from self for which the passed block returns true" do
+ @set.keep_if { |x| x.size != 3 }
+ @set.size.should eql(1)
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+
+ it "returns self" do
+ @set.keep_if {}.should equal(@set)
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @set.keep_if
+ enum.should be_an_instance_of(Enumerator)
+
+ enum.each { |x| x.size != 3 }
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+end
diff --git a/spec/ruby/library/set/length_spec.rb b/spec/ruby/library/set/length_spec.rb
new file mode 100644
index 0000000000..fef63d25a7
--- /dev/null
+++ b/spec/ruby/library/set/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+require 'set'
+
+describe "Set#length" do
+ it_behaves_like :set_length, :length
+end
diff --git a/spec/ruby/library/set/map_spec.rb b/spec/ruby/library/set/map_spec.rb
new file mode 100644
index 0000000000..e60e98b179
--- /dev/null
+++ b/spec/ruby/library/set/map_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require 'set'
+require_relative 'shared/collect'
+
+describe "Set#map!" do
+ it_behaves_like :set_collect_bang, :map!
+end
diff --git a/spec/ruby/library/set/member_spec.rb b/spec/ruby/library/set/member_spec.rb
new file mode 100644
index 0000000000..5b56a38ab9
--- /dev/null
+++ b/spec/ruby/library/set/member_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+require 'set'
+
+describe "Set#member?" do
+ it_behaves_like :set_include, :member?
+end
diff --git a/spec/ruby/library/set/merge_spec.rb b/spec/ruby/library/set/merge_spec.rb
new file mode 100644
index 0000000000..a8e3ffc870
--- /dev/null
+++ b/spec/ruby/library/set/merge_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#merge" do
+ it "adds the elements of the passed Enumerable to self" do
+ Set[:a, :b].merge(Set[:b, :c, :d]).should == Set[:a, :b, :c, :d]
+ Set[1, 2].merge([3, 4]).should == Set[1, 2, 3, 4]
+ end
+
+ it "returns self" do
+ set = Set[1, 2]
+ set.merge([3, 4]).should equal(set)
+ end
+
+ it "raises an ArgumentError when passed a non-Enumerable" do
+ -> { Set[1, 2].merge(1) }.should raise_error(ArgumentError)
+ -> { Set[1, 2].merge(Object.new) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/set/minus_spec.rb b/spec/ruby/library/set/minus_spec.rb
new file mode 100644
index 0000000000..3fe0b6a2cc
--- /dev/null
+++ b/spec/ruby/library/set/minus_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require 'set'
+require_relative 'shared/difference'
+
+describe "Set#-" do
+ it_behaves_like :set_difference, :-
+end
diff --git a/spec/ruby/library/set/plus_spec.rb b/spec/ruby/library/set/plus_spec.rb
new file mode 100644
index 0000000000..3e70d3269d
--- /dev/null
+++ b/spec/ruby/library/set/plus_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/union'
+require 'set'
+
+describe "Set#+" do
+ it_behaves_like :set_union, :+
+end
diff --git a/spec/ruby/library/set/pretty_print_cycle_spec.rb b/spec/ruby/library/set/pretty_print_cycle_spec.rb
new file mode 100644
index 0000000000..4f440353e5
--- /dev/null
+++ b/spec/ruby/library/set/pretty_print_cycle_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#pretty_print_cycle" do
+ it "passes the 'pretty print' representation of a self-referencing Set to the pretty print writer" do
+ pp = mock("PrettyPrint")
+ pp.should_receive(:text).with("#<Set: {...}>")
+ Set[1, 2, 3].pretty_print_cycle(pp)
+ end
+end
diff --git a/spec/ruby/library/set/pretty_print_spec.rb b/spec/ruby/library/set/pretty_print_spec.rb
new file mode 100644
index 0000000000..ea9ead0df8
--- /dev/null
+++ b/spec/ruby/library/set/pretty_print_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require 'set'
+
+ruby_version_is ""..."3.1" do
+ describe "Set#pretty_print" do
+ it "passes the 'pretty print' representation of self to the pretty print writer" do
+ pp = mock("PrettyPrint")
+ set = Set[1, 2, 3]
+
+ pp.should_receive(:text).with("#<Set: {")
+ pp.should_receive(:text).with("}>")
+
+ pp.should_receive(:nest).with(1).and_yield
+ pp.should_receive(:seplist).with(set)
+
+ set.pretty_print(pp)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/proper_subset_spec.rb b/spec/ruby/library/set/proper_subset_spec.rb
new file mode 100644
index 0000000000..1f496a6199
--- /dev/null
+++ b/spec/ruby/library/set/proper_subset_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/set_like'
+require 'set'
+
+describe "Set#proper_subset?" do
+ before :each do
+ @set = Set[1, 2, 3, 4]
+ end
+
+ it "returns true if passed a Set that self is a proper subset of" do
+ Set[].proper_subset?(@set).should be_true
+ Set[].proper_subset?(Set[1, 2, 3]).should be_true
+ Set[].proper_subset?(Set["a", :b, ?c]).should be_true
+
+ Set[1, 2, 3].proper_subset?(@set).should be_true
+ Set[1, 3].proper_subset?(@set).should be_true
+ Set[1, 2].proper_subset?(@set).should be_true
+ Set[1].proper_subset?(@set).should be_true
+
+ Set[5].proper_subset?(@set).should be_false
+ Set[1, 5].proper_subset?(@set).should be_false
+ Set[nil].proper_subset?(@set).should be_false
+ Set["test"].proper_subset?(@set).should be_false
+
+ @set.proper_subset?(@set).should be_false
+ Set[].proper_subset?(Set[]).should be_false
+ end
+
+ it "raises an ArgumentError when passed a non-Set" do
+ -> { Set[].proper_subset?([]) }.should raise_error(ArgumentError)
+ -> { Set[].proper_subset?(1) }.should raise_error(ArgumentError)
+ -> { Set[].proper_subset?("test") }.should raise_error(ArgumentError)
+ -> { Set[].proper_subset?(Object.new) }.should raise_error(ArgumentError)
+ end
+
+ context "when comparing to a Set-like object" do
+ it "returns true if passed a Set-like object that self is a proper subset of" do
+ Set[1, 2, 3].proper_subset?(SetSpecs::SetLike.new([1, 2, 3, 4])).should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/set/proper_superset_spec.rb b/spec/ruby/library/set/proper_superset_spec.rb
new file mode 100644
index 0000000000..a386c8c097
--- /dev/null
+++ b/spec/ruby/library/set/proper_superset_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/set_like'
+require 'set'
+
+describe "Set#proper_superset?" do
+ before :each do
+ @set = Set[1, 2, 3, 4]
+ end
+
+ it "returns true if passed a Set that self is a proper superset of" do
+ @set.proper_superset?(Set[]).should be_true
+ Set[1, 2, 3].proper_superset?(Set[]).should be_true
+ Set["a", :b, ?c].proper_superset?(Set[]).should be_true
+
+ @set.proper_superset?(Set[1, 2, 3]).should be_true
+ @set.proper_superset?(Set[1, 3]).should be_true
+ @set.proper_superset?(Set[1, 2]).should be_true
+ @set.proper_superset?(Set[1]).should be_true
+
+ @set.proper_superset?(Set[5]).should be_false
+ @set.proper_superset?(Set[1, 5]).should be_false
+ @set.proper_superset?(Set[nil]).should be_false
+ @set.proper_superset?(Set["test"]).should be_false
+
+ @set.proper_superset?(@set).should be_false
+ Set[].proper_superset?(Set[]).should be_false
+ end
+
+ it "raises an ArgumentError when passed a non-Set" do
+ -> { Set[].proper_superset?([]) }.should raise_error(ArgumentError)
+ -> { Set[].proper_superset?(1) }.should raise_error(ArgumentError)
+ -> { Set[].proper_superset?("test") }.should raise_error(ArgumentError)
+ -> { Set[].proper_superset?(Object.new) }.should raise_error(ArgumentError)
+ end
+
+ context "when comparing to a Set-like object" do
+ it "returns true if passed a Set-like object that self is a proper superset of" do
+ Set[1, 2, 3, 4].proper_superset?(SetSpecs::SetLike.new([1, 2, 3])).should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/set/reject_spec.rb b/spec/ruby/library/set/reject_spec.rb
new file mode 100644
index 0000000000..9131f960ad
--- /dev/null
+++ b/spec/ruby/library/set/reject_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#reject!" do
+ before :each do
+ @set = Set["one", "two", "three"]
+ end
+
+ it "yields every element of self" do
+ ret = []
+ @set.reject! { |x| ret << x }
+ ret.sort.should == ["one", "two", "three"].sort
+ end
+
+ it "deletes every element from self for which the passed block returns true" do
+ @set.reject! { |x| x.size == 3 }
+ @set.size.should eql(1)
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+
+ it "returns self when self was modified" do
+ @set.reject! { |x| true }.should equal(@set)
+ end
+
+ it "returns nil when self was not modified" do
+ @set.reject! { |x| false }.should be_nil
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @set.reject!
+ enum.should be_an_instance_of(Enumerator)
+
+ enum.each { |x| x.size == 3 }
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+end
diff --git a/spec/ruby/library/set/replace_spec.rb b/spec/ruby/library/set/replace_spec.rb
new file mode 100644
index 0000000000..7511066c9c
--- /dev/null
+++ b/spec/ruby/library/set/replace_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#replace" do
+ before :each do
+ @set = Set[:a, :b, :c]
+ end
+
+ it "replaces the contents with other and returns self" do
+ @set.replace(Set[1, 2, 3]).should == @set
+ @set.should == Set[1, 2, 3]
+ end
+
+ it "accepts any enumerable as other" do
+ @set.replace([1, 2, 3]).should == Set[1, 2, 3]
+ end
+end
diff --git a/spec/ruby/library/set/select_spec.rb b/spec/ruby/library/set/select_spec.rb
new file mode 100644
index 0000000000..b458ffacaa
--- /dev/null
+++ b/spec/ruby/library/set/select_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/select'
+
+describe "Set#select!" do
+ it_behaves_like :set_select_bang, :select!
+end
diff --git a/spec/ruby/library/set/shared/add.rb b/spec/ruby/library/set/shared/add.rb
new file mode 100644
index 0000000000..9e797f5df9
--- /dev/null
+++ b/spec/ruby/library/set/shared/add.rb
@@ -0,0 +1,14 @@
+describe :set_add, shared: true do
+ before :each do
+ @set = Set.new
+ end
+
+ it "adds the passed Object to self" do
+ @set.send(@method, "dog")
+ @set.should include("dog")
+ end
+
+ it "returns self" do
+ @set.send(@method, "dog").should equal(@set)
+ end
+end
diff --git a/spec/ruby/library/set/shared/collect.rb b/spec/ruby/library/set/shared/collect.rb
new file mode 100644
index 0000000000..bc58c231be
--- /dev/null
+++ b/spec/ruby/library/set/shared/collect.rb
@@ -0,0 +1,20 @@
+describe :set_collect_bang, shared: true do
+ before :each do
+ @set = Set[1, 2, 3, 4, 5]
+ end
+
+ it "yields each Object in self" do
+ res = []
+ @set.send(@method) { |x| res << x }
+ res.sort.should == [1, 2, 3, 4, 5].sort
+ end
+
+ it "returns self" do
+ @set.send(@method) { |x| x }.should equal(@set)
+ end
+
+ it "replaces self with the return values of the block" do
+ @set.send(@method) { |x| x * 2 }
+ @set.should == Set[2, 4, 6, 8, 10]
+ end
+end
diff --git a/spec/ruby/library/set/shared/difference.rb b/spec/ruby/library/set/shared/difference.rb
new file mode 100644
index 0000000000..f88987ed2a
--- /dev/null
+++ b/spec/ruby/library/set/shared/difference.rb
@@ -0,0 +1,15 @@
+describe :set_difference, shared: true do
+ before :each do
+ @set = Set[:a, :b, :c]
+ end
+
+ it "returns a new Set containing self's elements excluding the elements in the passed Enumerable" do
+ @set.send(@method, Set[:a, :b]).should == Set[:c]
+ @set.send(@method, [:b, :c]).should == Set[:a]
+ end
+
+ it "raises an ArgumentError when passed a non-Enumerable" do
+ -> { @set.send(@method, 1) }.should raise_error(ArgumentError)
+ -> { @set.send(@method, Object.new) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/set/shared/include.rb b/spec/ruby/library/set/shared/include.rb
new file mode 100644
index 0000000000..b4d95cde24
--- /dev/null
+++ b/spec/ruby/library/set/shared/include.rb
@@ -0,0 +1,29 @@
+describe :set_include, shared: true do
+ it "returns true when self contains the passed Object" do
+ set = Set[:a, :b, :c]
+ set.send(@method, :a).should be_true
+ set.send(@method, :e).should be_false
+ end
+
+ describe "member equality" do
+ it "is checked using both #hash and #eql?" do
+ obj = Object.new
+ obj_another = Object.new
+
+ def obj.hash; 42 end
+ def obj_another.hash; 42 end
+ def obj_another.eql?(o) hash == o.hash end
+
+ set = Set["a", "b", "c", obj]
+ set.send(@method, obj_another).should == true
+ end
+
+ it "is not checked using #==" do
+ obj = Object.new
+ set = Set["a", "b", "c"]
+
+ obj.should_not_receive(:==)
+ set.send(@method, obj)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/shared/inspect.rb b/spec/ruby/library/set/shared/inspect.rb
new file mode 100644
index 0000000000..69fbdd12f6
--- /dev/null
+++ b/spec/ruby/library/set/shared/inspect.rb
@@ -0,0 +1,15 @@
+describe "set_inspect", shared: true do
+ it "returns a String representation of self" do
+ Set[].send(@method).should be_kind_of(String)
+ Set[nil, false, true].send(@method).should be_kind_of(String)
+ Set[1, 2, 3].send(@method).should be_kind_of(String)
+ Set["1", "2", "3"].send(@method).should be_kind_of(String)
+ Set[:a, "b", Set[?c]].send(@method).should be_kind_of(String)
+ end
+
+ it "correctly handles self-references" do
+ (set = Set[]) << set
+ set.send(@method).should be_kind_of(String)
+ set.send(@method).should include("#<Set: {...}>")
+ end
+end
diff --git a/spec/ruby/library/set/shared/intersection.rb b/spec/ruby/library/set/shared/intersection.rb
new file mode 100644
index 0000000000..5ae4199c94
--- /dev/null
+++ b/spec/ruby/library/set/shared/intersection.rb
@@ -0,0 +1,15 @@
+describe :set_intersection, shared: true do
+ before :each do
+ @set = Set[:a, :b, :c]
+ end
+
+ it "returns a new Set containing only elements shared by self and the passed Enumerable" do
+ @set.send(@method, Set[:b, :c, :d, :e]).should == Set[:b, :c]
+ @set.send(@method, [:b, :c, :d]).should == Set[:b, :c]
+ end
+
+ it "raises an ArgumentError when passed a non-Enumerable" do
+ -> { @set.send(@method, 1) }.should raise_error(ArgumentError)
+ -> { @set.send(@method, Object.new) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/set/shared/length.rb b/spec/ruby/library/set/shared/length.rb
new file mode 100644
index 0000000000..a8fcee9f39
--- /dev/null
+++ b/spec/ruby/library/set/shared/length.rb
@@ -0,0 +1,6 @@
+describe :set_length, shared: true do
+ it "returns the number of elements in the set" do
+ set = Set[:a, :b, :c]
+ set.send(@method).should == 3
+ end
+end
diff --git a/spec/ruby/library/set/shared/select.rb b/spec/ruby/library/set/shared/select.rb
new file mode 100644
index 0000000000..2108d398b4
--- /dev/null
+++ b/spec/ruby/library/set/shared/select.rb
@@ -0,0 +1,42 @@
+require_relative '../../../spec_helper'
+require 'set'
+
+describe :set_select_bang, shared: true do
+ before :each do
+ @set = Set["one", "two", "three"]
+ end
+
+ it "yields every element of self" do
+ ret = []
+ @set.send(@method) { |x| ret << x }
+ ret.sort.should == ["one", "two", "three"].sort
+ end
+
+ it "keeps every element from self for which the passed block returns true" do
+ @set.send(@method) { |x| x.size != 3 }
+ @set.size.should eql(1)
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+
+ it "returns self when self was modified" do
+ @set.send(@method) { false }.should equal(@set)
+ end
+
+ it "returns nil when self was not modified" do
+ @set.send(@method) { true }.should be_nil
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @set.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+
+ enum.each { |x| x.size != 3 }
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+end
diff --git a/spec/ruby/library/set/shared/union.rb b/spec/ruby/library/set/shared/union.rb
new file mode 100644
index 0000000000..314f0e852d
--- /dev/null
+++ b/spec/ruby/library/set/shared/union.rb
@@ -0,0 +1,15 @@
+describe :set_union, shared: true do
+ before :each do
+ @set = Set[:a, :b, :c]
+ end
+
+ it "returns a new Set containing all elements of self and the passed Enumerable" do
+ @set.send(@method, Set[:b, :d, :e]).should == Set[:a, :b, :c, :d, :e]
+ @set.send(@method, [:b, :e]).should == Set[:a, :b, :c, :e]
+ end
+
+ it "raises an ArgumentError when passed a non-Enumerable" do
+ -> { @set.send(@method, 1) }.should raise_error(ArgumentError)
+ -> { @set.send(@method, Object.new) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/set/size_spec.rb b/spec/ruby/library/set/size_spec.rb
new file mode 100644
index 0000000000..3c8cb38517
--- /dev/null
+++ b/spec/ruby/library/set/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+require 'set'
+
+describe "Set#size" do
+ it_behaves_like :set_length, :size
+end
diff --git a/spec/ruby/library/set/sortedset/add_spec.rb b/spec/ruby/library/set/sortedset/add_spec.rb
new file mode 100644
index 0000000000..4f3bb252e1
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/add_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+ require_relative 'shared/add'
+
+ describe "SortedSet#add" do
+ it_behaves_like :sorted_set_add, :add
+
+ it "takes only values which responds <=>" do
+ obj = mock('no_comparison_operator')
+ obj.stub!(:respond_to?).with(:<=>).and_return(false)
+ -> { SortedSet["hello"].add(obj) }.should raise_error(ArgumentError)
+ end
+
+ it "raises on incompatible <=> comparison" do
+ # Use #to_a here as elements are sorted only when needed.
+ # Therefore the <=> incompatibility is only noticed on sorting.
+ -> { SortedSet['1', '2'].add(3).to_a }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "SortedSet#add?" do
+ before :each do
+ @set = SortedSet.new
+ end
+
+ it "adds the passed Object to self" do
+ @set.add?("cat")
+ @set.should include("cat")
+ end
+
+ it "returns self when the Object has not yet been added to self" do
+ @set.add?("cat").should equal(@set)
+ end
+
+ it "returns nil when the Object has already been added to self" do
+ @set.add?("cat")
+ @set.add?("cat").should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/append_spec.rb b/spec/ruby/library/set/sortedset/append_spec.rb
new file mode 100644
index 0000000000..d72d70b21f
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/append_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+ require_relative 'shared/add'
+
+ describe "SortedSet#<<" do
+ it_behaves_like :sorted_set_add, :<<
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/case_equality_spec.rb b/spec/ruby/library/set/sortedset/case_equality_spec.rb
new file mode 100644
index 0000000000..d7c296b626
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/case_equality_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require_relative 'shared/include'
+ require 'set'
+
+ describe "SortedSet#===" do
+ it_behaves_like :sorted_set_include, :===
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/classify_spec.rb b/spec/ruby/library/set/sortedset/classify_spec.rb
new file mode 100644
index 0000000000..4011e58b82
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/classify_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#classify" do
+ before :each do
+ @set = SortedSet["one", "two", "three", "four"]
+ end
+
+ it "yields each Object in self in sorted order" do
+ res = []
+ @set.classify { |x| res << x }
+ res.should == ["one", "two", "three", "four"].sort
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @set.classify
+ enum.should be_an_instance_of(Enumerator)
+
+ classified = enum.each { |x| x.length }
+ classified.should == { 3 => SortedSet["one", "two"], 4 => SortedSet["four"], 5 => SortedSet["three"] }
+ end
+
+ it "classifies the Objects in self based on the block's return value" do
+ classified = @set.classify { |x| x.length }
+ classified.should == { 3 => SortedSet["one", "two"], 4 => SortedSet["four"], 5 => SortedSet["three"] }
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/clear_spec.rb b/spec/ruby/library/set/sortedset/clear_spec.rb
new file mode 100644
index 0000000000..879aa824d8
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/clear_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#clear" do
+ before :each do
+ @set = SortedSet["one", "two", "three", "four"]
+ end
+
+ it "removes all elements from self" do
+ @set.clear
+ @set.should be_empty
+ end
+
+ it "returns self" do
+ @set.clear.should equal(@set)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/collect_spec.rb b/spec/ruby/library/set/sortedset/collect_spec.rb
new file mode 100644
index 0000000000..0674f0d130
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/collect_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+ require_relative 'shared/collect'
+
+ describe "SortedSet#collect!" do
+ it_behaves_like :sorted_set_collect_bang, :collect!
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/constructor_spec.rb b/spec/ruby/library/set/sortedset/constructor_spec.rb
new file mode 100644
index 0000000000..31f30fd892
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/constructor_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet[]" do
+ it "returns a new SortedSet populated with the passed Objects" do
+ set = SortedSet[1, 2, 3]
+
+ set.instance_of?(SortedSet).should be_true
+ set.size.should eql(3)
+
+ set.should include(1)
+ set.should include(2)
+ set.should include(3)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/delete_if_spec.rb b/spec/ruby/library/set/sortedset/delete_if_spec.rb
new file mode 100644
index 0000000000..787639ae12
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/delete_if_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#delete_if" do
+ before :each do
+ @set = SortedSet["one", "two", "three"]
+ end
+
+ it "yields each Object in self in sorted order" do
+ ret = []
+ @set.delete_if { |x| ret << x }
+ ret.should == ["one", "two", "three"].sort
+ end
+
+ it "deletes every element from self for which the passed block returns true" do
+ @set.delete_if { |x| x.size == 3 }
+ @set.size.should eql(1)
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+
+ it "returns self" do
+ @set.delete_if { |x| x }.should equal(@set)
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @set.delete_if
+ enum.should be_an_instance_of(Enumerator)
+
+ enum.each { |x| x.size == 3 }
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/delete_spec.rb b/spec/ruby/library/set/sortedset/delete_spec.rb
new file mode 100644
index 0000000000..0e2a6accf3
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/delete_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#delete" do
+ before :each do
+ @set = SortedSet["a", "b", "c"]
+ end
+
+ it "deletes the passed Object from self" do
+ @set.delete("a")
+ @set.should_not include("a")
+ end
+
+ it "returns self" do
+ @set.delete("a").should equal(@set)
+ @set.delete("x").should equal(@set)
+ end
+ end
+
+ describe "SortedSet#delete?" do
+ before :each do
+ @set = SortedSet["a", "b", "c"]
+ end
+
+ it "deletes the passed Object from self" do
+ @set.delete?("a")
+ @set.should_not include("a")
+ end
+
+ it "returns self when the passed Object is in self" do
+ @set.delete?("a").should equal(@set)
+ end
+
+ it "returns nil when the passed Object is not in self" do
+ @set.delete?("x").should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/difference_spec.rb b/spec/ruby/library/set/sortedset/difference_spec.rb
new file mode 100644
index 0000000000..fb064bdff9
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/difference_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+ require_relative 'shared/difference'
+
+ describe "SortedSet#difference" do
+ it_behaves_like :sorted_set_difference, :difference
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/divide_spec.rb b/spec/ruby/library/set/sortedset/divide_spec.rb
new file mode 100644
index 0000000000..31ab6037e4
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/divide_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#divide" do
+ it "divides self into a set of subsets based on the blocks return values" do
+ set = SortedSet["one", "two", "three", "four", "five"].divide { |x| x.length }
+ set.map { |x| x.to_a }.to_a.sort.should == [["five", "four"], ["one", "two"], ["three"]]
+ end
+
+ it "yields each Object in self in sorted order" do
+ ret = []
+ SortedSet["one", "two", "three", "four", "five"].divide { |x| ret << x }
+ ret.should == ["one", "two", "three", "four", "five"].sort
+ end
+
+ # BUG: Does not raise a LocalJumpError, but a NoMethodError
+ #
+ # it "raises a LocalJumpError when not passed a block" do
+ # lambda { SortedSet[1].divide }.should raise_error(LocalJumpError)
+ # end
+ end
+
+ describe "SortedSet#divide when passed a block with an arity of 2" do
+ it "divides self into a set of subsets based on the blocks return values" do
+ set = SortedSet[1, 3, 4, 6, 9, 10, 11].divide { |x, y| (x - y).abs == 1 }
+ set.map { |x| x.to_a }.to_a.sort.should == [[1], [3, 4], [6], [9, 10, 11]]
+ end
+
+ it "yields each two Objects to the block" do
+ ret = []
+ SortedSet[1, 2].divide { |x, y| ret << [x, y] }
+ ret.should == [[1, 1], [1, 2], [2, 1], [2, 2]]
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/each_spec.rb b/spec/ruby/library/set/sortedset/each_spec.rb
new file mode 100644
index 0000000000..79d8aee223
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/each_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#each" do
+ before :each do
+ @set = SortedSet[1, 2, 3]
+ end
+
+ it "yields each Object in self in sorted order" do
+ ret = []
+ SortedSet["one", "two", "three"].each { |x| ret << x }
+ ret.should == ["one", "two", "three"].sort
+ end
+
+ it "returns self" do
+ @set.each { |x| x }.should equal(@set)
+ end
+
+ it "returns an Enumerator when not passed a block" do
+ enum = @set.each
+
+ ret = []
+ enum.each { |x| ret << x }
+ ret.sort.should == [1, 2, 3]
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/empty_spec.rb b/spec/ruby/library/set/sortedset/empty_spec.rb
new file mode 100644
index 0000000000..2e52c3e81a
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/empty_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#empty?" do
+ it "returns true if self is empty" do
+ SortedSet[].empty?.should be_true
+ SortedSet[1].empty?.should be_false
+ SortedSet[1,2,3].empty?.should be_false
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/eql_spec.rb b/spec/ruby/library/set/sortedset/eql_spec.rb
new file mode 100644
index 0000000000..050464994b
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/eql_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#eql?" do
+ it "returns true when the passed argument is a SortedSet and contains the same elements" do
+ SortedSet[].should eql(SortedSet[])
+ SortedSet[1, 2, 3].should eql(SortedSet[1, 2, 3])
+ SortedSet[1, 2, 3].should eql(SortedSet[3, 2, 1])
+
+ # SortedSet["a", :b, ?c].should eql(SortedSet[?c, :b, "a"])
+
+ SortedSet[1, 2, 3].should_not eql(SortedSet[1.0, 2, 3])
+ SortedSet[1, 2, 3].should_not eql(SortedSet[2, 3])
+ SortedSet[1, 2, 3].should_not eql(SortedSet[])
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/equal_value_spec.rb b/spec/ruby/library/set/sortedset/equal_value_spec.rb
new file mode 100644
index 0000000000..30422f5b95
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/equal_value_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#==" do
+ it "returns true when the passed Object is a SortedSet and self and the Object contain the same elements" do
+ SortedSet[].should == SortedSet[]
+ SortedSet[1, 2, 3].should == SortedSet[1, 2, 3]
+ SortedSet["1", "2", "3"].should == SortedSet["1", "2", "3"]
+
+ SortedSet[1, 2, 3].should_not == SortedSet[1.0, 2, 3]
+ SortedSet[1, 2, 3].should_not == [1, 2, 3]
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/exclusion_spec.rb b/spec/ruby/library/set/sortedset/exclusion_spec.rb
new file mode 100644
index 0000000000..1967dfbfa6
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/exclusion_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#^" do
+ before :each do
+ @set = SortedSet[1, 2, 3, 4]
+ end
+
+ it "returns a new SortedSet containing elements that are not in both self and the passed Enumerable" do
+ (@set ^ SortedSet[3, 4, 5]).should == SortedSet[1, 2, 5]
+ (@set ^ [3, 4, 5]).should == SortedSet[1, 2, 5]
+ end
+
+ it "raises an ArgumentError when passed a non-Enumerable" do
+ -> { @set ^ 3 }.should raise_error(ArgumentError)
+ -> { @set ^ Object.new }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/filter_spec.rb b/spec/ruby/library/set/sortedset/filter_spec.rb
new file mode 100644
index 0000000000..3b9dcb63c9
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/filter_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require_relative 'shared/select'
+ require 'set'
+
+ describe "SortedSet#filter!" do
+ it_behaves_like :sorted_set_select_bang, :filter!
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/flatten_merge_spec.rb b/spec/ruby/library/set/sortedset/flatten_merge_spec.rb
new file mode 100644
index 0000000000..0d67cb331e
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/flatten_merge_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#flatten_merge" do
+ it "is protected" do
+ SortedSet.should have_protected_instance_method("flatten_merge")
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/flatten_spec.rb b/spec/ruby/library/set/sortedset/flatten_spec.rb
new file mode 100644
index 0000000000..e83ad1044a
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/flatten_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ # Note: Flatten make little sens on sorted sets, because SortedSets are not (by default)
+ # comparable. For a SortedSet to be both valid and nested, we need to define a comparison operator:
+ module SortedSet_FlattenSpecs
+ class ComparableSortedSet < SortedSet
+ def <=>(other)
+ return puts "#{other} vs #{self}" unless other.is_a?(ComparableSortedSet)
+ to_a <=> other.to_a
+ end
+ end
+ end
+
+ describe "SortedSet#flatten" do
+ it "returns a copy of self with each included SortedSet flattened" do
+ klass = SortedSet_FlattenSpecs::ComparableSortedSet
+ set = klass[klass[1,2], klass[3,4], klass[5,6,7], klass[8]]
+ flattened_set = set.flatten
+
+ flattened_set.should_not equal(set)
+ flattened_set.should == klass[1, 2, 3, 4, 5, 6, 7, 8]
+ end
+ end
+
+ describe "SortedSet#flatten!" do
+ it "flattens self" do
+ klass = SortedSet_FlattenSpecs::ComparableSortedSet
+ set = klass[klass[1,2], klass[3,4], klass[5,6,7], klass[8]]
+ set.flatten!
+ set.should == klass[1, 2, 3, 4, 5, 6, 7, 8]
+ end
+
+ it "returns self when self was modified" do
+ klass = SortedSet_FlattenSpecs::ComparableSortedSet
+ set = klass[klass[1,2], klass[3,4]]
+ set.flatten!.should equal(set)
+ end
+
+ it "returns nil when self was not modified" do
+ set = SortedSet[1, 2, 3, 4]
+ set.flatten!.should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/hash_spec.rb b/spec/ruby/library/set/sortedset/hash_spec.rb
new file mode 100644
index 0000000000..40676de7fc
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/hash_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#hash" do
+ it "is static" do
+ SortedSet[].hash.should == SortedSet[].hash
+ SortedSet[1, 2, 3].hash.should == SortedSet[1, 2, 3].hash
+ SortedSet["a", "b", "c"].hash.should == SortedSet["c", "b", "a"].hash
+
+ SortedSet[].hash.should_not == SortedSet[1, 2, 3].hash
+ SortedSet[1, 2, 3].hash.should_not == SortedSet["a", "b", "c"].hash
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/include_spec.rb b/spec/ruby/library/set/sortedset/include_spec.rb
new file mode 100644
index 0000000000..ec2ad987d5
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/include_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require_relative 'shared/include'
+ require 'set'
+
+ describe "SortedSet#include?" do
+ it_behaves_like :sorted_set_include, :include?
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/initialize_spec.rb b/spec/ruby/library/set/sortedset/initialize_spec.rb
new file mode 100644
index 0000000000..4d1707b72a
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/initialize_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#initialize" do
+ it "is private" do
+ SortedSet.should have_private_instance_method("initialize")
+ end
+
+ it "adds all elements of the passed Enumerable to self" do
+ s = SortedSet.new([1, 2, 3])
+ s.size.should eql(3)
+ s.should include(1)
+ s.should include(2)
+ s.should include(3)
+ end
+
+ it "preprocesses all elements by a passed block before adding to self" do
+ s = SortedSet.new([1, 2, 3]) { |x| x * x }
+ s.size.should eql(3)
+ s.should include(1)
+ s.should include(4)
+ s.should include(9)
+ end
+
+ it "raises on incompatible <=> comparison" do
+ # Use #to_a here as elements are sorted only when needed.
+ # Therefore the <=> incompatibility is only noticed on sorting.
+ -> { SortedSet.new(['00', nil]).to_a }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/inspect_spec.rb b/spec/ruby/library/set/sortedset/inspect_spec.rb
new file mode 100644
index 0000000000..1c4dd9e6e2
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/inspect_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#inspect" do
+ it "returns a String representation of self" do
+ SortedSet[].inspect.should be_kind_of(String)
+ SortedSet[1, 2, 3].inspect.should be_kind_of(String)
+ SortedSet["1", "2", "3"].inspect.should be_kind_of(String)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/intersection_spec.rb b/spec/ruby/library/set/sortedset/intersection_spec.rb
new file mode 100644
index 0000000000..6daa271b73
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/intersection_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require_relative 'shared/intersection'
+ require 'set'
+
+ describe "SortedSet#intersection" do
+ it_behaves_like :sorted_set_intersection, :intersection
+ end
+
+ describe "SortedSet#&" do
+ it_behaves_like :sorted_set_intersection, :&
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/keep_if_spec.rb b/spec/ruby/library/set/sortedset/keep_if_spec.rb
new file mode 100644
index 0000000000..3e5f3bbc47
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/keep_if_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#keep_if" do
+ before :each do
+ @set = SortedSet["one", "two", "three"]
+ end
+
+ it "yields each Object in self in sorted order" do
+ ret = []
+ @set.keep_if { |x| ret << x }
+ ret.should == ["one", "two", "three"].sort
+ end
+
+ it "keeps every element from self for which the passed block returns true" do
+ @set.keep_if { |x| x.size != 3 }
+ @set.to_a.should == ["three"]
+ end
+
+ it "returns self" do
+ @set.keep_if {}.should equal(@set)
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @set.keep_if
+ enum.should be_an_instance_of(Enumerator)
+
+ enum.each { |x| x.size != 3 }
+ @set.to_a.should == ["three"]
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/length_spec.rb b/spec/ruby/library/set/sortedset/length_spec.rb
new file mode 100644
index 0000000000..de6791f6bb
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/length_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require_relative 'shared/length'
+ require 'set'
+
+ describe "SortedSet#length" do
+ it_behaves_like :sorted_set_length, :length
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/map_spec.rb b/spec/ruby/library/set/sortedset/map_spec.rb
new file mode 100644
index 0000000000..4971b9529b
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/map_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+ require_relative 'shared/collect'
+
+ describe "SortedSet#map!" do
+ it_behaves_like :sorted_set_collect_bang, :map!
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/member_spec.rb b/spec/ruby/library/set/sortedset/member_spec.rb
new file mode 100644
index 0000000000..142b09b651
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/member_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require_relative 'shared/include'
+ require 'set'
+
+ describe "SortedSet#member?" do
+ it_behaves_like :sorted_set_include, :member?
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/merge_spec.rb b/spec/ruby/library/set/sortedset/merge_spec.rb
new file mode 100644
index 0000000000..c4cbc6d2b4
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/merge_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#merge" do
+ it "adds the elements of the passed Enumerable to self" do
+ SortedSet["a", "b"].merge(SortedSet["b", "c", "d"]).should == SortedSet["a", "b", "c", "d"]
+ SortedSet[1, 2].merge([3, 4]).should == SortedSet[1, 2, 3, 4]
+ end
+
+ it "returns self" do
+ set = SortedSet[1, 2]
+ set.merge([3, 4]).should equal(set)
+ end
+
+ it "raises an ArgumentError when passed a non-Enumerable" do
+ -> { SortedSet[1, 2].merge(1) }.should raise_error(ArgumentError)
+ -> { SortedSet[1, 2].merge(Object.new) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/minus_spec.rb b/spec/ruby/library/set/sortedset/minus_spec.rb
new file mode 100644
index 0000000000..d6abc5e204
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/minus_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+ require_relative 'shared/difference'
+
+ describe "SortedSet#-" do
+ it_behaves_like :sorted_set_difference, :-
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/plus_spec.rb b/spec/ruby/library/set/sortedset/plus_spec.rb
new file mode 100644
index 0000000000..13fc873ad1
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/plus_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require_relative 'shared/union'
+ require 'set'
+
+ describe "SortedSet#+" do
+ it_behaves_like :sorted_set_union, :+
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/pretty_print_cycle_spec.rb b/spec/ruby/library/set/sortedset/pretty_print_cycle_spec.rb
new file mode 100644
index 0000000000..e97f509406
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/pretty_print_cycle_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#pretty_print_cycle" do
+ it "passes the 'pretty print' representation of a self-referencing SortedSet to the pretty print writer" do
+ pp = mock("PrettyPrint")
+ pp.should_receive(:text).with("#<SortedSet: {...}>")
+ SortedSet[1, 2, 3].pretty_print_cycle(pp)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/pretty_print_spec.rb b/spec/ruby/library/set/sortedset/pretty_print_spec.rb
new file mode 100644
index 0000000000..a8088bf797
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/pretty_print_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#pretty_print" do
+ it "passes the 'pretty print' representation of self to the pretty print writer" do
+ pp = mock("PrettyPrint")
+ set = SortedSet[1, 2, 3]
+
+ pp.should_receive(:text).with("#<SortedSet: {")
+ pp.should_receive(:text).with("}>")
+
+ pp.should_receive(:nest).with(1).and_yield
+ pp.should_receive(:seplist).with(set)
+
+ set.pretty_print(pp)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/proper_subset_spec.rb b/spec/ruby/library/set/sortedset/proper_subset_spec.rb
new file mode 100644
index 0000000000..34fb89d13d
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/proper_subset_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#proper_subset?" do
+ before :each do
+ @set = SortedSet[1, 2, 3, 4]
+ end
+
+ it "returns true if passed a SortedSet that self is a proper subset of" do
+ SortedSet[].proper_subset?(@set).should be_true
+ SortedSet[].proper_subset?(SortedSet[1, 2, 3]).should be_true
+ SortedSet[].proper_subset?(SortedSet["a", "b", "c"]).should be_true
+
+ SortedSet[1, 2, 3].proper_subset?(@set).should be_true
+ SortedSet[1, 3].proper_subset?(@set).should be_true
+ SortedSet[1, 2].proper_subset?(@set).should be_true
+ SortedSet[1].proper_subset?(@set).should be_true
+
+ SortedSet[5].proper_subset?(@set).should be_false
+ SortedSet[1, 5].proper_subset?(@set).should be_false
+ SortedSet["test"].proper_subset?(@set).should be_false
+
+ @set.proper_subset?(@set).should be_false
+ SortedSet[].proper_subset?(SortedSet[]).should be_false
+ end
+
+ it "raises an ArgumentError when passed a non-SortedSet" do
+ -> { SortedSet[].proper_subset?([]) }.should raise_error(ArgumentError)
+ -> { SortedSet[].proper_subset?(1) }.should raise_error(ArgumentError)
+ -> { SortedSet[].proper_subset?("test") }.should raise_error(ArgumentError)
+ -> { SortedSet[].proper_subset?(Object.new) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/proper_superset_spec.rb b/spec/ruby/library/set/sortedset/proper_superset_spec.rb
new file mode 100644
index 0000000000..8b92444f72
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/proper_superset_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#proper_superset?" do
+ before :each do
+ @set = SortedSet[1, 2, 3, 4]
+ end
+
+ it "returns true if passed a SortedSet that self is a proper superset of" do
+ @set.proper_superset?(SortedSet[]).should be_true
+ SortedSet[1, 2, 3].proper_superset?(SortedSet[]).should be_true
+ SortedSet["a", "b", "c"].proper_superset?(SortedSet[]).should be_true
+
+ @set.proper_superset?(SortedSet[1, 2, 3]).should be_true
+ @set.proper_superset?(SortedSet[1, 3]).should be_true
+ @set.proper_superset?(SortedSet[1, 2]).should be_true
+ @set.proper_superset?(SortedSet[1]).should be_true
+
+ @set.proper_superset?(SortedSet[5]).should be_false
+ @set.proper_superset?(SortedSet[1, 5]).should be_false
+ @set.proper_superset?(SortedSet["test"]).should be_false
+
+ @set.proper_superset?(@set).should be_false
+ SortedSet[].proper_superset?(SortedSet[]).should be_false
+ end
+
+ it "raises an ArgumentError when passed a non-SortedSet" do
+ -> { SortedSet[].proper_superset?([]) }.should raise_error(ArgumentError)
+ -> { SortedSet[].proper_superset?(1) }.should raise_error(ArgumentError)
+ -> { SortedSet[].proper_superset?("test") }.should raise_error(ArgumentError)
+ -> { SortedSet[].proper_superset?(Object.new) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/reject_spec.rb b/spec/ruby/library/set/sortedset/reject_spec.rb
new file mode 100644
index 0000000000..396b864cc5
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/reject_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#reject!" do
+ before :each do
+ @set = SortedSet["one", "two", "three"]
+ end
+
+ it "yields each Object in self in sorted order" do
+ res = []
+ @set.reject! { |x| res << x }
+ res.should == ["one", "two", "three"].sort
+ end
+
+ it "deletes every element from self for which the passed block returns true" do
+ @set.reject! { |x| x.size == 3 }
+ @set.size.should eql(1)
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+
+ it "returns self when self was modified" do
+ @set.reject! { |x| true }.should equal(@set)
+ end
+
+ it "returns nil when self was not modified" do
+ @set.reject! { |x| false }.should be_nil
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @set.reject!
+ enum.should be_an_instance_of(Enumerator)
+
+ enum.each { |x| x.size == 3 }
+
+ @set.should_not include("one")
+ @set.should_not include("two")
+ @set.should include("three")
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/replace_spec.rb b/spec/ruby/library/set/sortedset/replace_spec.rb
new file mode 100644
index 0000000000..2900221c01
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/replace_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#replace" do
+ before :each do
+ @set = SortedSet["a", "b", "c"]
+ end
+
+ it "replaces the contents with other and returns self" do
+ @set.replace(SortedSet[1, 2, 3]).should == @set
+ @set.should == SortedSet[1, 2, 3]
+ end
+
+ it "accepts any enumerable as other" do
+ @set.replace([1, 2, 3]).should == SortedSet[1, 2, 3]
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/select_spec.rb b/spec/ruby/library/set/sortedset/select_spec.rb
new file mode 100644
index 0000000000..fc4c15ee4d
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/select_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require_relative 'shared/select'
+ require 'set'
+
+ describe "SortedSet#select!" do
+ it_behaves_like :sorted_set_select_bang, :select!
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/shared/add.rb b/spec/ruby/library/set/sortedset/shared/add.rb
new file mode 100644
index 0000000000..95ef1b090e
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/shared/add.rb
@@ -0,0 +1,14 @@
+describe :sorted_set_add, shared: true do
+ before :each do
+ @set = SortedSet.new
+ end
+
+ it "adds the passed Object to self" do
+ @set.send(@method, "dog")
+ @set.should include("dog")
+ end
+
+ it "returns self" do
+ @set.send(@method, "dog").should equal(@set)
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/shared/collect.rb b/spec/ruby/library/set/sortedset/shared/collect.rb
new file mode 100644
index 0000000000..e53304d427
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/shared/collect.rb
@@ -0,0 +1,20 @@
+describe :sorted_set_collect_bang, shared: true do
+ before :each do
+ @set = SortedSet[1, 2, 3, 4, 5]
+ end
+
+ it "yields each Object in self in sorted order" do
+ res = []
+ SortedSet["one", "two", "three"].send(@method) { |x| res << x; x }
+ res.should == ["one", "two", "three"].sort
+ end
+
+ it "returns self" do
+ @set.send(@method) { |x| x }.should equal(@set)
+ end
+
+ it "replaces self with the return values of the block" do
+ @set.send(@method) { |x| x * 2 }
+ @set.should == SortedSet[2, 4, 6, 8, 10]
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/shared/difference.rb b/spec/ruby/library/set/sortedset/shared/difference.rb
new file mode 100644
index 0000000000..688e23a7a7
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/shared/difference.rb
@@ -0,0 +1,15 @@
+describe :sorted_set_difference, shared: true do
+ before :each do
+ @set = SortedSet["a", "b", "c"]
+ end
+
+ it "returns a new SortedSet containing self's elements excluding the elements in the passed Enumerable" do
+ @set.send(@method, SortedSet["a", "b"]).should == SortedSet["c"]
+ @set.send(@method, ["b", "c"]).should == SortedSet["a"]
+ end
+
+ it "raises an ArgumentError when passed a non-Enumerable" do
+ -> { @set.send(@method, 1) }.should raise_error(ArgumentError)
+ -> { @set.send(@method, Object.new) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/shared/include.rb b/spec/ruby/library/set/sortedset/shared/include.rb
new file mode 100644
index 0000000000..cd1758819d
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/shared/include.rb
@@ -0,0 +1,7 @@
+describe :sorted_set_include, shared: true do
+ it "returns true when self contains the passed Object" do
+ set = SortedSet["a", "b", "c"]
+ set.send(@method, "a").should be_true
+ set.send(@method, "e").should be_false
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/shared/intersection.rb b/spec/ruby/library/set/sortedset/shared/intersection.rb
new file mode 100644
index 0000000000..045716ad05
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/shared/intersection.rb
@@ -0,0 +1,15 @@
+describe :sorted_set_intersection, shared: true do
+ before :each do
+ @set = SortedSet["a", "b", "c"]
+ end
+
+ it "returns a new SortedSet containing only elements shared by self and the passed Enumerable" do
+ @set.send(@method, SortedSet["b", "c", "d", "e"]).should == SortedSet["b", "c"]
+ @set.send(@method, ["b", "c", "d"]).should == SortedSet["b", "c"]
+ end
+
+ it "raises an ArgumentError when passed a non-Enumerable" do
+ -> { @set.send(@method, 1) }.should raise_error(ArgumentError)
+ -> { @set.send(@method, Object.new) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/shared/length.rb b/spec/ruby/library/set/sortedset/shared/length.rb
new file mode 100644
index 0000000000..d1dfee1cff
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/shared/length.rb
@@ -0,0 +1,6 @@
+describe :sorted_set_length, shared: true do
+ it "returns the number of elements in the set" do
+ set = SortedSet["a", "b", "c"]
+ set.send(@method).should == 3
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/shared/select.rb b/spec/ruby/library/set/sortedset/shared/select.rb
new file mode 100644
index 0000000000..e13311eda5
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/shared/select.rb
@@ -0,0 +1,35 @@
+require_relative '../../../../spec_helper'
+require 'set'
+
+describe :sorted_set_select_bang, shared: true do
+ before :each do
+ @set = SortedSet["one", "two", "three"]
+ end
+
+ it "yields each Object in self in sorted order" do
+ res = []
+ @set.send(@method) { |x| res << x }
+ res.should == ["one", "two", "three"].sort
+ end
+
+ it "keeps every element from self for which the passed block returns true" do
+ @set.send(@method) { |x| x.size != 3 }
+ @set.to_a.should == ["three"]
+ end
+
+ it "returns self when self was modified" do
+ @set.send(@method) { false }.should equal(@set)
+ end
+
+ it "returns nil when self was not modified" do
+ @set.send(@method) { true }.should be_nil
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @set.send(@method)
+ enum.should be_an_instance_of(Enumerator)
+
+ enum.each { |x| x.size != 3 }
+ @set.to_a.should == ["three"]
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/shared/union.rb b/spec/ruby/library/set/sortedset/shared/union.rb
new file mode 100644
index 0000000000..9015bdc8e3
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/shared/union.rb
@@ -0,0 +1,15 @@
+describe :sorted_set_union, shared: true do
+ before :each do
+ @set = SortedSet["a", "b", "c"]
+ end
+
+ it "returns a new SortedSet containing all elements of self and the passed Enumerable" do
+ @set.send(@method, SortedSet["b", "d", "e"]).should == SortedSet["a", "b", "c", "d", "e"]
+ @set.send(@method, ["b", "e"]).should == SortedSet["a", "b", "c", "e"]
+ end
+
+ it "raises an ArgumentError when passed a non-Enumerable" do
+ -> { @set.send(@method, 1) }.should raise_error(ArgumentError)
+ -> { @set.send(@method, Object.new) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/size_spec.rb b/spec/ruby/library/set/sortedset/size_spec.rb
new file mode 100644
index 0000000000..d908b33b53
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/size_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require_relative 'shared/length'
+ require 'set'
+
+ describe "SortedSet#size" do
+ it_behaves_like :sorted_set_length, :size
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/sortedset_spec.rb b/spec/ruby/library/set/sortedset/sortedset_spec.rb
new file mode 100644
index 0000000000..3ead5495fc
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/sortedset_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'set'
+
+ruby_version_is "3.0" do
+ describe "SortedSet" do
+ it "raises error including message that it has been extracted from the set stdlib" do
+ -> {
+ SortedSet
+ }.should raise_error(RuntimeError) { |e|
+ e.message.should.include?("The `SortedSet` class has been extracted from the `set` library")
+ }
+ end
+ end
+end
+
+ruby_version_is ""..."3.0" do
+ describe "SortedSet" do
+ it "is part of the set stdlib" do
+ SortedSet.superclass.should == Set
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/subset_spec.rb b/spec/ruby/library/set/sortedset/subset_spec.rb
new file mode 100644
index 0000000000..272e3f985e
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/subset_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#subset?" do
+ before :each do
+ @set = SortedSet[1, 2, 3, 4]
+ end
+
+ it "returns true if passed a SortedSet that is equal to self or self is a subset of" do
+ @set.subset?(@set).should be_true
+ SortedSet[].subset?(SortedSet[]).should be_true
+
+ SortedSet[].subset?(@set).should be_true
+ SortedSet[].subset?(SortedSet[1, 2, 3]).should be_true
+ SortedSet[].subset?(SortedSet["a", "b", "c"]).should be_true
+
+ SortedSet[1, 2, 3].subset?(@set).should be_true
+ SortedSet[1, 3].subset?(@set).should be_true
+ SortedSet[1, 2].subset?(@set).should be_true
+ SortedSet[1].subset?(@set).should be_true
+
+ SortedSet[5].subset?(@set).should be_false
+ SortedSet[1, 5].subset?(@set).should be_false
+ SortedSet["test"].subset?(@set).should be_false
+ end
+
+ it "raises an ArgumentError when passed a non-SortedSet" do
+ -> { SortedSet[].subset?([]) }.should raise_error(ArgumentError)
+ -> { SortedSet[].subset?(1) }.should raise_error(ArgumentError)
+ -> { SortedSet[].subset?("test") }.should raise_error(ArgumentError)
+ -> { SortedSet[].subset?(Object.new) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/subtract_spec.rb b/spec/ruby/library/set/sortedset/subtract_spec.rb
new file mode 100644
index 0000000000..b2af127f89
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/subtract_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#subtract" do
+ before :each do
+ @set = SortedSet["a", "b", "c"]
+ end
+
+ it "deletes any elements contained in other and returns self" do
+ @set.subtract(SortedSet["b", "c"]).should == @set
+ @set.should == SortedSet["a"]
+ end
+
+ it "accepts any enumerable as other" do
+ @set.subtract(["c"]).should == SortedSet["a", "b"]
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/superset_spec.rb b/spec/ruby/library/set/sortedset/superset_spec.rb
new file mode 100644
index 0000000000..a1bbacb966
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/superset_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#superset?" do
+ before :each do
+ @set = SortedSet[1, 2, 3, 4]
+ end
+
+ it "returns true if passed a SortedSet that equals self or self is a proper superset of" do
+ @set.superset?(@set).should be_true
+ SortedSet[].superset?(SortedSet[]).should be_true
+
+ @set.superset?(SortedSet[]).should be_true
+ SortedSet[1, 2, 3].superset?(SortedSet[]).should be_true
+ SortedSet["a", "b", "c"].superset?(SortedSet[]).should be_true
+
+ @set.superset?(SortedSet[1, 2, 3]).should be_true
+ @set.superset?(SortedSet[1, 3]).should be_true
+ @set.superset?(SortedSet[1, 2]).should be_true
+ @set.superset?(SortedSet[1]).should be_true
+
+ @set.superset?(SortedSet[5]).should be_false
+ @set.superset?(SortedSet[1, 5]).should be_false
+ @set.superset?(SortedSet["test"]).should be_false
+ end
+
+ it "raises an ArgumentError when passed a non-SortedSet" do
+ -> { SortedSet[].superset?([]) }.should raise_error(ArgumentError)
+ -> { SortedSet[].superset?(1) }.should raise_error(ArgumentError)
+ -> { SortedSet[].superset?("test") }.should raise_error(ArgumentError)
+ -> { SortedSet[].superset?(Object.new) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/to_a_spec.rb b/spec/ruby/library/set/sortedset/to_a_spec.rb
new file mode 100644
index 0000000000..bb54cd7cdb
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/to_a_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require 'set'
+
+ describe "SortedSet#to_a" do
+ it "returns an array containing elements" do
+ set = SortedSet.new [1, 2, 3]
+ set.to_a.should == [1, 2, 3]
+ end
+
+ it "returns a sorted array containing elements" do
+ set = SortedSet[2, 3, 1]
+ set.to_a.should == [1, 2, 3]
+
+ set = SortedSet.new [5, 6, 4, 4]
+ set.to_a.should == [4, 5, 6]
+ end
+ end
+end
diff --git a/spec/ruby/library/set/sortedset/union_spec.rb b/spec/ruby/library/set/sortedset/union_spec.rb
new file mode 100644
index 0000000000..c942f20d3e
--- /dev/null
+++ b/spec/ruby/library/set/sortedset/union_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is ""..."3.0" do
+ require_relative 'shared/union'
+ require 'set'
+
+ describe "SortedSet#union" do
+ it_behaves_like :sorted_set_union, :union
+ end
+
+ describe "SortedSet#|" do
+ it_behaves_like :sorted_set_union, :|
+ end
+end
diff --git a/spec/ruby/library/set/subset_spec.rb b/spec/ruby/library/set/subset_spec.rb
new file mode 100644
index 0000000000..f375efa6df
--- /dev/null
+++ b/spec/ruby/library/set/subset_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/set_like'
+require 'set'
+
+describe "Set#subset?" do
+ before :each do
+ @set = Set[1, 2, 3, 4]
+ end
+
+ it "returns true if passed a Set that is equal to self or self is a subset of" do
+ @set.subset?(@set).should be_true
+ Set[].subset?(Set[]).should be_true
+
+ Set[].subset?(@set).should be_true
+ Set[].subset?(Set[1, 2, 3]).should be_true
+ Set[].subset?(Set["a", :b, ?c]).should be_true
+
+ Set[1, 2, 3].subset?(@set).should be_true
+ Set[1, 3].subset?(@set).should be_true
+ Set[1, 2].subset?(@set).should be_true
+ Set[1].subset?(@set).should be_true
+
+ Set[5].subset?(@set).should be_false
+ Set[1, 5].subset?(@set).should be_false
+ Set[nil].subset?(@set).should be_false
+ Set["test"].subset?(@set).should be_false
+ end
+
+ it "raises an ArgumentError when passed a non-Set" do
+ -> { Set[].subset?([]) }.should raise_error(ArgumentError)
+ -> { Set[].subset?(1) }.should raise_error(ArgumentError)
+ -> { Set[].subset?("test") }.should raise_error(ArgumentError)
+ -> { Set[].subset?(Object.new) }.should raise_error(ArgumentError)
+ end
+
+ context "when comparing to a Set-like object" do
+ it "returns true if passed a Set-like object that self is a subset of" do
+ Set[1, 2, 3].subset?(SetSpecs::SetLike.new([1, 2, 3, 4])).should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/set/subtract_spec.rb b/spec/ruby/library/set/subtract_spec.rb
new file mode 100644
index 0000000000..56713de8b3
--- /dev/null
+++ b/spec/ruby/library/set/subtract_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#subtract" do
+ before :each do
+ @set = Set[:a, :b, :c]
+ end
+
+ it "deletes any elements contained in other and returns self" do
+ @set.subtract(Set[:b, :c]).should == @set
+ @set.should == Set[:a]
+ end
+
+ it "accepts any enumerable as other" do
+ @set.subtract([:c]).should == Set[:a, :b]
+ end
+end
diff --git a/spec/ruby/library/set/superset_spec.rb b/spec/ruby/library/set/superset_spec.rb
new file mode 100644
index 0000000000..bd9d2f3eee
--- /dev/null
+++ b/spec/ruby/library/set/superset_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/set_like'
+require 'set'
+
+describe "Set#superset?" do
+ before :each do
+ @set = Set[1, 2, 3, 4]
+ end
+
+ it "returns true if passed a Set that equals self or self is a proper superset of" do
+ @set.superset?(@set).should be_true
+ Set[].superset?(Set[]).should be_true
+
+ @set.superset?(Set[]).should be_true
+ Set[1, 2, 3].superset?(Set[]).should be_true
+ Set["a", :b, ?c].superset?(Set[]).should be_true
+
+ @set.superset?(Set[1, 2, 3]).should be_true
+ @set.superset?(Set[1, 3]).should be_true
+ @set.superset?(Set[1, 2]).should be_true
+ @set.superset?(Set[1]).should be_true
+
+ @set.superset?(Set[5]).should be_false
+ @set.superset?(Set[1, 5]).should be_false
+ @set.superset?(Set[nil]).should be_false
+ @set.superset?(Set["test"]).should be_false
+ end
+
+ it "raises an ArgumentError when passed a non-Set" do
+ -> { Set[].superset?([]) }.should raise_error(ArgumentError)
+ -> { Set[].superset?(1) }.should raise_error(ArgumentError)
+ -> { Set[].superset?("test") }.should raise_error(ArgumentError)
+ -> { Set[].superset?(Object.new) }.should raise_error(ArgumentError)
+ end
+
+ context "when comparing to a Set-like object" do
+ it "returns true if passed a Set-like object that self is a superset of" do
+ Set[1, 2, 3, 4].superset?(SetSpecs::SetLike.new([1, 2, 3])).should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/set/to_a_spec.rb b/spec/ruby/library/set/to_a_spec.rb
new file mode 100644
index 0000000000..689e44f38a
--- /dev/null
+++ b/spec/ruby/library/set/to_a_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'set'
+
+describe "Set#to_a" do
+ it "returns an array containing elements of self" do
+ Set[1, 2, 3].to_a.sort.should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/library/set/to_s_spec.rb b/spec/ruby/library/set/to_s_spec.rb
new file mode 100644
index 0000000000..7b9f7b6603
--- /dev/null
+++ b/spec/ruby/library/set/to_s_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'shared/inspect'
+require 'set'
+
+describe "Set#to_s" do
+ it_behaves_like :set_inspect, :to_s
+
+ it "is an alias of inspect" do
+ set = Set.new
+ set.method(:to_s).should == set.method(:inspect)
+ end
+end
diff --git a/spec/ruby/library/set/union_spec.rb b/spec/ruby/library/set/union_spec.rb
new file mode 100644
index 0000000000..20fe0ddca3
--- /dev/null
+++ b/spec/ruby/library/set/union_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/union'
+require 'set'
+
+describe "Set#union" do
+ it_behaves_like :set_union, :union
+end
+
+describe "Set#|" do
+ it_behaves_like :set_union, :|
+end
diff --git a/spec/ruby/library/shellwords/shellwords_spec.rb b/spec/ruby/library/shellwords/shellwords_spec.rb
new file mode 100644
index 0000000000..2975fd9974
--- /dev/null
+++ b/spec/ruby/library/shellwords/shellwords_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require 'shellwords'
+include Shellwords
+
+describe "Shellwords#shellwords" do
+ it "honors quoted strings" do
+ shellwords('a "b b" a').should == ['a', 'b b', 'a']
+ end
+
+ it "honors escaped double quotes" do
+ shellwords('a "\"b\" c" d').should == ['a', '"b" c', 'd']
+ end
+
+ it "honors escaped single quotes" do
+ shellwords("a \"'b' c\" d").should == ['a', "'b' c", 'd']
+ end
+
+ it "honors escaped spaces" do
+ shellwords('a b\ c d').should == ['a', 'b c', 'd']
+ end
+
+ it "raises ArgumentError when double quoted strings are misquoted" do
+ -> { shellwords('a "b c d e') }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError when single quoted strings are misquoted" do
+ -> { shellwords("a 'b c d e") }.should raise_error(ArgumentError)
+ end
+
+ # https://bugs.ruby-lang.org/issues/10055
+ it "matches POSIX sh behavior for backslashes within double quoted strings" do
+ shellsplit('printf "%s\n"').should == ['printf', '%s\n']
+ end
+end
diff --git a/spec/ruby/library/singleton/allocate_spec.rb b/spec/ruby/library/singleton/allocate_spec.rb
new file mode 100644
index 0000000000..6a1512d53b
--- /dev/null
+++ b/spec/ruby/library/singleton/allocate_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Singleton.allocate" do
+ it "is a private method" do
+ -> { SingletonSpecs::MyClass.allocate }.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/library/singleton/clone_spec.rb b/spec/ruby/library/singleton/clone_spec.rb
new file mode 100644
index 0000000000..3635bcd594
--- /dev/null
+++ b/spec/ruby/library/singleton/clone_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Singleton#clone" do
+ it "is prevented" do
+ -> { SingletonSpecs::MyClass.instance.clone }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/singleton/dump_spec.rb b/spec/ruby/library/singleton/dump_spec.rb
new file mode 100644
index 0000000000..333e3bc4b0
--- /dev/null
+++ b/spec/ruby/library/singleton/dump_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Singleton#_dump" do
+
+ it "returns an empty string" do
+ SingletonSpecs::MyClass.instance.send(:_dump).should == ""
+ end
+
+ it "returns an empty string from a singleton subclass" do
+ SingletonSpecs::MyClassChild.instance.send(:_dump).should == ""
+ end
+
+end
diff --git a/spec/ruby/library/singleton/dup_spec.rb b/spec/ruby/library/singleton/dup_spec.rb
new file mode 100644
index 0000000000..13d5a213e9
--- /dev/null
+++ b/spec/ruby/library/singleton/dup_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Singleton#dup" do
+ it "is prevented" do
+ -> { SingletonSpecs::MyClass.instance.dup }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/singleton/fixtures/classes.rb b/spec/ruby/library/singleton/fixtures/classes.rb
new file mode 100644
index 0000000000..c718ebaec8
--- /dev/null
+++ b/spec/ruby/library/singleton/fixtures/classes.rb
@@ -0,0 +1,18 @@
+require 'singleton'
+
+module SingletonSpecs
+ class MyClass
+ attr_accessor :data
+ include Singleton
+ end
+
+ class NewSpec
+ include Singleton
+ end
+
+ class MyClassChild < MyClass
+ end
+
+ class NotInstantiated < MyClass
+ end
+end
diff --git a/spec/ruby/library/singleton/instance_spec.rb b/spec/ruby/library/singleton/instance_spec.rb
new file mode 100644
index 0000000000..1679728d4c
--- /dev/null
+++ b/spec/ruby/library/singleton/instance_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Singleton.instance" do
+ it "returns an instance of the singleton class" do
+ SingletonSpecs::MyClass.instance.should be_kind_of(SingletonSpecs::MyClass)
+ end
+
+ it "returns the same instance for multiple calls to instance" do
+ SingletonSpecs::MyClass.instance.should equal(SingletonSpecs::MyClass.instance)
+ end
+
+ it "returns an instance of the singleton's subclasses" do
+ SingletonSpecs::MyClassChild.instance.should be_kind_of(SingletonSpecs::MyClassChild)
+ end
+
+ it "returns the same instance for multiple class to instance on subclasses" do
+ SingletonSpecs::MyClassChild.instance.should equal(SingletonSpecs::MyClassChild.instance)
+ end
+
+ it "returns an instance of the singleton's clone" do
+ klone = SingletonSpecs::MyClassChild.clone
+ klone.instance.should be_kind_of(klone)
+ end
+
+ it "returns the same instance for multiple class to instance on clones" do
+ klone = SingletonSpecs::MyClassChild.clone
+ klone.instance.should equal(klone.instance)
+ end
+end
diff --git a/spec/ruby/library/singleton/load_spec.rb b/spec/ruby/library/singleton/load_spec.rb
new file mode 100644
index 0000000000..4c753f9e7a
--- /dev/null
+++ b/spec/ruby/library/singleton/load_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# TODO: change to a.should be_equal(b)
+# TODO: write spec for cloning classes and calling private methods
+# TODO: write spec for private_methods not showing up via extended
+describe "Singleton._load" do
+ it "returns the singleton instance for anything passed in" do
+ klass = SingletonSpecs::MyClass
+ klass._load("").should equal(klass.instance)
+ klass._load("42").should equal(klass.instance)
+ klass._load(42).should equal(klass.instance)
+ end
+
+ it "returns the singleton instance for anything passed in to subclass" do
+ subklass = SingletonSpecs::MyClassChild
+ subklass._load("").should equal(subklass.instance)
+ subklass._load("42").should equal(subklass.instance)
+ subklass._load(42).should equal(subklass.instance)
+ end
+end
diff --git a/spec/ruby/library/singleton/new_spec.rb b/spec/ruby/library/singleton/new_spec.rb
new file mode 100644
index 0000000000..2f45db819c
--- /dev/null
+++ b/spec/ruby/library/singleton/new_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Singleton.new" do
+ it "is a private method" do
+ -> { SingletonSpecs::NewSpec.new }.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/afamily_spec.rb b/spec/ruby/library/socket/addrinfo/afamily_spec.rb
new file mode 100644
index 0000000000..7229dab9de
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/afamily_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#afamily" do
+ describe "for an ipv4 socket" do
+
+ before :each do
+ @addrinfo = Addrinfo.tcp("127.0.0.1", 80)
+ end
+
+ it "returns Socket::AF_INET" do
+ @addrinfo.afamily.should == Socket::AF_INET
+ end
+
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "returns Socket::AF_INET6" do
+ @addrinfo.afamily.should == Socket::AF_INET6
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns Socket::AF_UNIX" do
+ @addrinfo.afamily.should == Socket::AF_UNIX
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/bind_spec.rb b/spec/ruby/library/socket/addrinfo/bind_spec.rb
new file mode 100644
index 0000000000..6f78890a4d
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/bind_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Addrinfo#bind" do
+
+ before :each do
+ @addrinfo = Addrinfo.tcp("127.0.0.1", 0)
+ end
+
+ after :each do
+ @socket.close unless @socket.closed?
+ end
+
+ it "returns a bound socket when no block is given" do
+ @socket = @addrinfo.bind
+ @socket.should be_kind_of(Socket)
+ @socket.closed?.should be_false
+ end
+
+ it "yields the socket if a block is given" do
+ @addrinfo.bind do |sock|
+ @socket = sock
+ sock.should be_kind_of(Socket)
+ end
+ @socket.closed?.should be_true
+ end
+
+end
diff --git a/spec/ruby/library/socket/addrinfo/canonname_spec.rb b/spec/ruby/library/socket/addrinfo/canonname_spec.rb
new file mode 100644
index 0000000000..a1cc8b3980
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/canonname_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Addrinfo#canonname" do
+
+ before :each do
+ @addrinfos = Addrinfo.getaddrinfo("localhost", 80, :INET, :STREAM, nil, Socket::AI_CANONNAME)
+ end
+
+ it "returns the canonical name for a host" do
+ canonname = @addrinfos.map { |a| a.canonname }.find { |name| name and name.include?("localhost") }
+ if canonname
+ canonname.should include("localhost")
+ else
+ canonname.should == nil
+ end
+ end
+
+ describe 'when the canonical name is not available' do
+ it 'returns nil' do
+ addr = Addrinfo.new(Socket.sockaddr_in(0, '127.0.0.1'))
+
+ addr.canonname.should be_nil
+ end
+ end
+
+end
diff --git a/spec/ruby/library/socket/addrinfo/connect_from_spec.rb b/spec/ruby/library/socket/addrinfo/connect_from_spec.rb
new file mode 100644
index 0000000000..55fce2e159
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/connect_from_spec.rb
@@ -0,0 +1,75 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Addrinfo#connect_from' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ @port = @server.connect_address.ip_port
+ @addr = Addrinfo.tcp(ip_address, @port)
+ end
+
+ after do
+ @socket.close if @socket
+ @server.close
+ end
+
+ describe 'using separate arguments' do
+ it 'returns a Socket when no block is given' do
+ @socket = @addr.connect_from(ip_address, 0)
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'yields the Socket when a block is given' do
+ @addr.connect_from(ip_address, 0) do |socket|
+ socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'treats the last argument as a set of options if it is a Hash' do
+ @socket = @addr.connect_from(ip_address, 0, timeout: 2)
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'binds the socket to the local address' do
+ @socket = @addr.connect_from(ip_address, 0)
+
+ @socket.local_address.ip_address.should == ip_address
+
+ @socket.local_address.ip_port.should > 0
+ @socket.local_address.ip_port.should_not == @port
+ end
+ end
+
+ describe 'using an Addrinfo as the 1st argument' do
+ before do
+ @from_addr = Addrinfo.tcp(ip_address, 0)
+ end
+
+ it 'returns a Socket when no block is given' do
+ @socket = @addr.connect_from(@from_addr)
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'yields the Socket when a block is given' do
+ @addr.connect_from(@from_addr) do |socket|
+ socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'treats the last argument as a set of options if it is a Hash' do
+ @socket = @addr.connect_from(@from_addr, timeout: 2)
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'binds the socket to the local address' do
+ @socket = @addr.connect_from(@from_addr)
+
+ @socket.local_address.ip_address.should == ip_address
+
+ @socket.local_address.ip_port.should > 0
+ @socket.local_address.ip_port.should_not == @port
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/connect_spec.rb b/spec/ruby/library/socket/addrinfo/connect_spec.rb
new file mode 100644
index 0000000000..1c2dc609ca
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/connect_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Addrinfo#connect' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ @port = @server.connect_address.ip_port
+ end
+
+ after do
+ @socket.close if @socket
+ @server.close
+ end
+
+ it 'returns a Socket when no block is given' do
+ addr = Addrinfo.tcp(ip_address, @port)
+ @socket = addr.connect
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'yields a Socket when a block is given' do
+ addr = Addrinfo.tcp(ip_address, @port)
+ addr.connect do |socket|
+ socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'accepts a Hash of options' do
+ addr = Addrinfo.tcp(ip_address, @port)
+ @socket = addr.connect(timeout: 2)
+ @socket.should be_an_instance_of(Socket)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/connect_to_spec.rb b/spec/ruby/library/socket/addrinfo/connect_to_spec.rb
new file mode 100644
index 0000000000..69666da19b
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/connect_to_spec.rb
@@ -0,0 +1,75 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Addrinfo#connect_to' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ @port = @server.connect_address.ip_port
+ @addr = Addrinfo.tcp(ip_address, 0)
+ end
+
+ after do
+ @socket.close if @socket
+ @server.close
+ end
+
+ describe 'using separate arguments' do
+ it 'returns a Socket when no block is given' do
+ @socket = @addr.connect_to(ip_address, @port)
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'yields the Socket when a block is given' do
+ @addr.connect_to(ip_address, @port) do |socket|
+ socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'treats the last argument as a set of options if it is a Hash' do
+ @socket = @addr.connect_to(ip_address, @port, timeout: 2)
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'binds the Addrinfo to the local address' do
+ @socket = @addr.connect_to(ip_address, @port)
+
+ @socket.local_address.ip_address.should == ip_address
+
+ @socket.local_address.ip_port.should > 0
+ @socket.local_address.ip_port.should_not == @port
+ end
+ end
+
+ describe 'using an Addrinfo as the 1st argument' do
+ before do
+ @to_addr = Addrinfo.tcp(ip_address, @port)
+ end
+
+ it 'returns a Socket when no block is given' do
+ @socket = @addr.connect_to(@to_addr)
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'yields the Socket when a block is given' do
+ @addr.connect_to(@to_addr) do |socket|
+ socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'treats the last argument as a set of options if it is a Hash' do
+ @socket = @addr.connect_to(@to_addr, timeout: 2)
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'binds the socket to the local address' do
+ @socket = @addr.connect_to(@to_addr)
+
+ @socket.local_address.ip_address.should == ip_address
+
+ @socket.local_address.ip_port.should > 0
+ @socket.local_address.ip_port.should_not == @port
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb b/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb
new file mode 100644
index 0000000000..2bc3b6a2e3
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb
@@ -0,0 +1,115 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#family_addrinfo' do
+ it 'raises ArgumentError if no arguments are given' do
+ addr = Addrinfo.tcp('127.0.0.1', 0)
+
+ -> { addr.family_addrinfo }.should raise_error(ArgumentError)
+ end
+
+ describe 'using multiple arguments' do
+ describe 'with an IP Addrinfo' do
+ before do
+ @source = Addrinfo.tcp('127.0.0.1', 0)
+ end
+
+ it 'raises ArgumentError if only 1 argument is given' do
+ -> { @source.family_addrinfo('127.0.0.1') }.should raise_error(ArgumentError)
+ end
+
+ it 'raises ArgumentError if more than 2 arguments are given' do
+ -> { @source.family_addrinfo('127.0.0.1', 0, 666) }.should raise_error(ArgumentError)
+ end
+
+ it 'returns an Addrinfo when a host and port are given' do
+ addr = @source.family_addrinfo('127.0.0.1', 0)
+
+ addr.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ before do
+ @addr = @source.family_addrinfo('127.0.0.1', 0)
+ end
+
+ it 'uses the same address family as the source Addrinfo' do
+ @addr.afamily.should == @source.afamily
+ end
+
+ it 'uses the same protocol family as the source Addrinfo' do
+ @addr.pfamily.should == @source.pfamily
+ end
+
+ it 'uses the same socket type as the source Addrinfo' do
+ @addr.socktype.should == @source.socktype
+ end
+
+ it 'uses the same protocol as the source Addrinfo' do
+ @addr.protocol.should == @source.protocol
+ end
+ end
+ end
+
+ with_feature :unix_socket do
+ describe 'with a UNIX Addrinfo' do
+ before do
+ @source = Addrinfo.unix('cats')
+ end
+
+ it 'raises ArgumentError if more than 1 argument is given' do
+ -> { @source.family_addrinfo('foo', 'bar') }.should raise_error(ArgumentError)
+ end
+
+ it 'returns an Addrinfo when a UNIX socket path is given' do
+ addr = @source.family_addrinfo('dogs')
+
+ addr.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ before do
+ @addr = @source.family_addrinfo('dogs')
+ end
+
+ it 'uses AF_UNIX as the address family' do
+ @addr.afamily.should == Socket::AF_UNIX
+ end
+
+ it 'uses PF_UNIX as the protocol family' do
+ @addr.pfamily.should == Socket::PF_UNIX
+ end
+
+ it 'uses the given socket path' do
+ @addr.unix_path.should == 'dogs'
+ end
+ end
+ end
+ end
+ end
+
+ describe 'using an Addrinfo as the 1st argument' do
+ before do
+ @source = Addrinfo.tcp('127.0.0.1', 0)
+ end
+
+ it 'returns the input Addrinfo' do
+ input = Addrinfo.tcp('127.0.0.2', 0)
+ @source.family_addrinfo(input).should == input
+ end
+
+ it 'raises ArgumentError if more than 1 argument is given' do
+ input = Addrinfo.tcp('127.0.0.2', 0)
+ -> { @source.family_addrinfo(input, 666) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the protocol families don't match" do
+ input = Addrinfo.tcp('::1', 0)
+ -> { @source.family_addrinfo(input) }.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if the socket types don't match" do
+ input = Addrinfo.udp('127.0.0.1', 0)
+ -> { @source.family_addrinfo(input) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/foreach_spec.rb b/spec/ruby/library/socket/addrinfo/foreach_spec.rb
new file mode 100644
index 0000000000..6ec8fab905
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/foreach_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo.foreach' do
+ it 'yields Addrinfo instances to the supplied block' do
+ Addrinfo.foreach('127.0.0.1', 80) do |addr|
+ addr.should be_an_instance_of(Addrinfo)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb b/spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb
new file mode 100644
index 0000000000..67fad73815
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Addrinfo.getaddrinfo' do
+ it 'returns an Array of Addrinfo instances' do
+ array = Addrinfo.getaddrinfo('127.0.0.1', 80)
+
+ array.should be_an_instance_of(Array)
+ array[0].should be_an_instance_of(Addrinfo)
+ end
+
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ it 'sets the IP address of the Addrinfo instances' do
+ array = Addrinfo.getaddrinfo(ip_address, 80)
+
+ array[0].ip_address.should == ip_address
+ end
+
+ it 'sets the port of the Addrinfo instances' do
+ array = Addrinfo.getaddrinfo(ip_address, 80)
+
+ array[0].ip_port.should == 80
+ end
+
+ it 'sets the address family of the Addrinfo instances' do
+ array = Addrinfo.getaddrinfo(ip_address, 80)
+
+ array[0].afamily.should == family
+ end
+
+ it 'sets the protocol family of the Addrinfo instances' do
+ array = Addrinfo.getaddrinfo(ip_address, 80)
+
+ array[0].pfamily.should == family
+ end
+ end
+
+ guard -> { SocketSpecs.ipv6_available? } do
+ it 'sets a custom protocol family of the Addrinfo instances' do
+ array = Addrinfo.getaddrinfo('::1', 80, Socket::PF_INET6)
+
+ array[0].pfamily.should == Socket::PF_INET6
+ end
+
+ it 'sets a corresponding address family based on a custom protocol family' do
+ array = Addrinfo.getaddrinfo('::1', 80, Socket::PF_INET6)
+
+ array[0].afamily.should == Socket::AF_INET6
+ end
+ end
+
+ platform_is_not :windows do
+ it 'sets the default socket type of the Addrinfo instances' do
+ array = Addrinfo.getaddrinfo('127.0.0.1', 80)
+ possible = [Socket::SOCK_STREAM, Socket::SOCK_DGRAM]
+
+ possible.should include(array[0].socktype)
+ end
+ end
+
+ it 'sets a custom socket type of the Addrinfo instances' do
+ array = Addrinfo.getaddrinfo('127.0.0.1', 80, nil, Socket::SOCK_DGRAM)
+
+ array[0].socktype.should == Socket::SOCK_DGRAM
+ end
+
+ platform_is_not :windows do
+ it 'sets the default socket protocol of the Addrinfo instances' do
+ array = Addrinfo.getaddrinfo('127.0.0.1', 80)
+ possible = [Socket::IPPROTO_TCP, Socket::IPPROTO_UDP]
+
+ possible.should include(array[0].protocol)
+ end
+ end
+
+ platform_is_not :'solaris2.10' do # i386-solaris
+ it 'sets a custom socket protocol of the Addrinfo instances' do
+ array = Addrinfo.getaddrinfo('127.0.0.1', 80, nil, nil, Socket::IPPROTO_UDP)
+
+ array[0].protocol.should == Socket::IPPROTO_UDP
+ end
+ end
+
+ platform_is_not :solaris do
+ it 'sets the canonical name when AI_CANONNAME is given as a flag' do
+ array = Addrinfo.getaddrinfo('localhost', 80, nil, nil, nil, Socket::AI_CANONNAME)
+
+ array[0].canonname.should be_an_instance_of(String)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb b/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb
new file mode 100644
index 0000000000..76579de74c
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Addrinfo#getnameinfo' do
+ describe 'using an IP Addrinfo' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @addr = Addrinfo.tcp(ip_address, 21)
+ end
+
+ it 'returns the node and service names' do
+ host, service = @addr.getnameinfo
+ service.should == 'ftp'
+ end
+
+ it 'accepts flags as an Integer as the first argument' do
+ host, service = @addr.getnameinfo(Socket::NI_NUMERICSERV)
+ service.should == '21'
+ end
+ end
+ end
+
+ platform_is :linux do
+ platform_is_not :android do
+ with_feature :unix_socket do
+ describe 'using a UNIX Addrinfo' do
+ before do
+ @addr = Addrinfo.unix('cats')
+ @host = Socket.gethostname
+ end
+
+ it 'returns the hostname and UNIX socket path' do
+ host, path = @addr.getnameinfo
+
+ host.should == @host
+ path.should == 'cats'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/initialize_spec.rb b/spec/ruby/library/socket/addrinfo/initialize_spec.rb
new file mode 100644
index 0000000000..83b204b575
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/initialize_spec.rb
@@ -0,0 +1,591 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#initialize" do
+
+ describe "with a sockaddr string" do
+
+ describe "without a family" do
+ before :each do
+ @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1"))
+ end
+
+ it "stores the ip address from the sockaddr" do
+ @addrinfo.ip_address.should == "2001:db8::1"
+ end
+
+ it "stores the port number from the sockaddr" do
+ @addrinfo.ip_port.should == 25
+ end
+
+ it "returns the Socket::UNSPEC pfamily" do
+ @addrinfo.pfamily.should == Socket::PF_UNSPEC
+ end
+
+ it 'returns AF_INET as the default address family' do
+ addr = Addrinfo.new(Socket.sockaddr_in(80, '127.0.0.1'))
+
+ addr.afamily.should == Socket::AF_INET
+ end
+
+ it "returns the INET6 afamily" do
+ @addrinfo.afamily.should == Socket::AF_INET6
+ end
+
+ it "returns the 0 socket type" do
+ @addrinfo.socktype.should == 0
+ end
+
+ it "returns the 0 protocol" do
+ @addrinfo.protocol.should == 0
+ end
+ end
+
+ describe "with a family given" do
+ before :each do
+ @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1"), Socket::PF_INET6)
+ end
+
+ it "stores the ip address from the sockaddr" do
+ @addrinfo.ip_address.should == "2001:db8::1"
+ end
+
+ it "stores the port number from the sockaddr" do
+ @addrinfo.ip_port.should == 25
+ end
+
+ it "returns the Socket::UNSPEC pfamily" do
+ @addrinfo.pfamily.should == Socket::PF_INET6
+ end
+
+ it "returns the INET6 afamily" do
+ @addrinfo.afamily.should == Socket::AF_INET6
+ end
+
+ it "returns the 0 socket type" do
+ @addrinfo.socktype.should == 0
+ end
+
+ it "returns the 0 protocol" do
+ @addrinfo.protocol.should == 0
+ end
+ end
+
+ describe "with a family and socket type" do
+ before :each do
+ @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1"), Socket::PF_INET6, Socket::SOCK_STREAM)
+ end
+
+ it "stores the ip address from the sockaddr" do
+ @addrinfo.ip_address.should == "2001:db8::1"
+ end
+
+ it "stores the port number from the sockaddr" do
+ @addrinfo.ip_port.should == 25
+ end
+
+ it "returns the Socket::UNSPEC pfamily" do
+ @addrinfo.pfamily.should == Socket::PF_INET6
+ end
+
+ it "returns the INET6 afamily" do
+ @addrinfo.afamily.should == Socket::AF_INET6
+ end
+
+ it "returns the specified socket type" do
+ @addrinfo.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it "returns the 0 protocol" do
+ @addrinfo.protocol.should == 0
+ end
+ end
+
+ describe "with a family, socket type and protocol" do
+ before :each do
+ @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1"), Socket::PF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP)
+ end
+
+ it "stores the ip address from the sockaddr" do
+ @addrinfo.ip_address.should == "2001:db8::1"
+ end
+
+ it "stores the port number from the sockaddr" do
+ @addrinfo.ip_port.should == 25
+ end
+
+ it "returns the Socket::UNSPEC pfamily" do
+ @addrinfo.pfamily.should == Socket::PF_INET6
+ end
+
+ it "returns the INET6 afamily" do
+ @addrinfo.afamily.should == Socket::AF_INET6
+ end
+
+ it "returns the specified socket type" do
+ @addrinfo.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it "returns the specified protocol" do
+ @addrinfo.protocol.should == Socket::IPPROTO_TCP
+ end
+ end
+
+ end
+
+ describe "with a sockaddr array" do
+
+ describe "without a family" do
+ before :each do
+ @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"])
+ end
+
+ it "stores the ip address from the sockaddr" do
+ @addrinfo.ip_address.should == "127.0.0.1"
+ end
+
+ it "stores the port number from the sockaddr" do
+ @addrinfo.ip_port.should == 46102
+ end
+
+ it "returns the Socket::PF_INET pfamily" do
+ @addrinfo.pfamily.should == Socket::PF_INET
+ end
+
+ it "returns the INET6 afamily" do
+ @addrinfo.afamily.should == Socket::AF_INET
+ end
+
+ it "returns the 0 socket type" do
+ @addrinfo.socktype.should == 0
+ end
+
+ it "returns the 0 protocol" do
+ @addrinfo.protocol.should == 0
+ end
+ end
+
+ describe 'with a valid IP address' do
+ # Uses AF_INET6 since AF_INET is the default, making it a better test
+ # that Addrinfo actually sets the family correctly.
+ before do
+ @sockaddr = ['AF_INET6', 80, 'hostname', '::1']
+ end
+
+ it 'returns an Addrinfo with the correct IP' do
+ addr = Addrinfo.new(@sockaddr)
+
+ addr.ip_address.should == '::1'
+ end
+
+ it 'returns an Addrinfo with the correct address family' do
+ addr = Addrinfo.new(@sockaddr)
+
+ addr.afamily.should == Socket::AF_INET6
+ end
+
+ it 'returns an Addrinfo with the correct protocol family' do
+ addr = Addrinfo.new(@sockaddr)
+
+ addr.pfamily.should == Socket::PF_INET6
+ end
+
+ it 'returns an Addrinfo with the correct port' do
+ addr = Addrinfo.new(@sockaddr)
+
+ addr.ip_port.should == 80
+ end
+ end
+
+ describe 'with an invalid IP address' do
+ it 'raises SocketError' do
+ block = -> { Addrinfo.new(['AF_INET6', 80, 'hostname', '127.0.0.1']) }
+
+ block.should raise_error(SocketError)
+ end
+ end
+
+ describe "with a family given" do
+ before :each do
+ @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"], Socket::PF_INET)
+ end
+
+ it "stores the ip address from the sockaddr" do
+ @addrinfo.ip_address.should == "127.0.0.1"
+ end
+
+ it "stores the port number from the sockaddr" do
+ @addrinfo.ip_port.should == 46102
+ end
+
+ it "returns the Socket::UNSPEC pfamily" do
+ @addrinfo.pfamily.should == Socket::PF_INET
+ end
+
+ it "returns the INET6 afamily" do
+ @addrinfo.afamily.should == Socket::AF_INET
+ end
+
+ it "returns the 0 socket type" do
+ @addrinfo.socktype.should == 0
+ end
+
+ it "returns the 0 protocol" do
+ @addrinfo.protocol.should == 0
+ end
+ end
+
+ describe "with a family and socket type" do
+ before :each do
+ @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"], Socket::PF_INET, Socket::SOCK_STREAM)
+ end
+
+ it "stores the ip address from the sockaddr" do
+ @addrinfo.ip_address.should == "127.0.0.1"
+ end
+
+ it "stores the port number from the sockaddr" do
+ @addrinfo.ip_port.should == 46102
+ end
+
+ it "returns the Socket::UNSPEC pfamily" do
+ @addrinfo.pfamily.should == Socket::PF_INET
+ end
+
+ it "returns the INET6 afamily" do
+ @addrinfo.afamily.should == Socket::AF_INET
+ end
+
+ it "returns the 0 socket type" do
+ @addrinfo.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it "returns the 0 protocol" do
+ @addrinfo.protocol.should == 0
+ end
+
+ [:SOCK_STREAM, :SOCK_DGRAM, :SOCK_RAW].each do |type|
+ it "overwrites the socket type #{type}" do
+ sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1']
+
+ value = Socket.const_get(type)
+ addr = Addrinfo.new(sockaddr, nil, value)
+
+ addr.socktype.should == value
+ end
+ end
+
+ platform_is_not :android do
+ with_feature :sock_packet do
+ [:SOCK_SEQPACKET].each do |type|
+ it "overwrites the socket type #{type}" do
+ sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1']
+
+ value = Socket.const_get(type)
+ addr = Addrinfo.new(sockaddr, nil, value)
+
+ addr.socktype.should == value
+ end
+ end
+ end
+ end
+
+ it "raises SocketError when using SOCK_RDM" do
+ sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1']
+ value = Socket::SOCK_RDM
+ block = -> { Addrinfo.new(sockaddr, nil, value) }
+
+ block.should raise_error(SocketError)
+ end
+ end
+
+ describe "with a family, socket type and protocol" do
+ before :each do
+ @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"], Socket::PF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP)
+ end
+
+ it "stores the ip address from the sockaddr" do
+ @addrinfo.ip_address.should == "127.0.0.1"
+ end
+
+ it "stores the port number from the sockaddr" do
+ @addrinfo.ip_port.should == 46102
+ end
+
+ it "returns the Socket::UNSPEC pfamily" do
+ @addrinfo.pfamily.should == Socket::PF_INET
+ end
+
+ it "returns the INET6 afamily" do
+ @addrinfo.afamily.should == Socket::AF_INET
+ end
+
+ it "returns the 0 socket type" do
+ @addrinfo.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it "returns the specified protocol" do
+ @addrinfo.protocol.should == Socket::IPPROTO_TCP
+ end
+ end
+ end
+
+ describe 'using an Array with extra arguments' do
+ describe 'with the AF_INET6 address family and an explicit protocol family' do
+ before do
+ @sockaddr = ['AF_INET6', 80, 'hostname', '127.0.0.1']
+ end
+
+ it "raises SocketError when using any Socket constant except except AF_INET(6)/PF_INET(6)" do
+ Socket.constants.grep(/(^AF_|^PF_)(?!INET)/).each do |constant|
+ value = Socket.const_get(constant)
+ -> {
+ Addrinfo.new(@sockaddr, value)
+ }.should raise_error(SocketError)
+ end
+ end
+ end
+
+ describe 'with the AF_INET address family and an explicit socket protocol' do
+ before do
+ @sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1']
+ end
+
+ describe 'and no socket type is given' do
+ valid = [:IPPROTO_IP, :IPPROTO_UDP, :IPPROTO_HOPOPTS]
+
+ valid.each do |type|
+ it "overwrites the protocol when using #{type}" do
+ value = Socket.const_get(type)
+ addr = Addrinfo.new(@sockaddr, nil, nil, value)
+
+ addr.protocol.should == value
+ end
+ end
+
+ platform_is_not :windows, :aix, :solaris do
+ (Socket.constants.grep(/^IPPROTO/) - valid).each do |type|
+ it "raises SocketError when using #{type}" do
+ value = Socket.const_get(type)
+ block = -> { Addrinfo.new(@sockaddr, nil, nil, value) }
+
+ block.should raise_error(SocketError)
+ end
+ end
+ end
+ end
+
+ describe 'and the socket type is set to SOCK_DGRAM' do
+ before do
+ @socktype = Socket::SOCK_DGRAM
+ end
+
+ valid = [:IPPROTO_IP, :IPPROTO_UDP, :IPPROTO_HOPOPTS]
+
+ valid.each do |type|
+ it "overwrites the protocol when using #{type}" do
+ value = Socket.const_get(type)
+ addr = Addrinfo.new(@sockaddr, nil, @socktype, value)
+
+ addr.protocol.should == value
+ end
+ end
+
+ platform_is_not :windows, :aix, :solaris do
+ (Socket.constants.grep(/^IPPROTO/) - valid).each do |type|
+ it "raises SocketError when using #{type}" do
+ value = Socket.const_get(type)
+ block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) }
+
+ block.should raise_error(SocketError)
+ end
+ end
+ end
+ end
+
+ with_feature :sock_packet do
+ describe 'and the socket type is set to SOCK_PACKET' do
+ before do
+ @socktype = Socket::SOCK_PACKET
+ end
+
+ Socket.constants.grep(/^IPPROTO/).each do |type|
+ it "raises SocketError when using #{type}" do
+ value = Socket.const_get(type)
+ block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) }
+
+ block.should raise_error(SocketError)
+ end
+ end
+ end
+ end
+
+ describe 'and the socket type is set to SOCK_RAW' do
+ before do
+ @socktype = Socket::SOCK_RAW
+ end
+
+ Socket.constants.grep(/^IPPROTO/).each do |type|
+ it "overwrites the protocol when using #{type}" do
+ value = Socket.const_get(type)
+ addr = Addrinfo.new(@sockaddr, nil, @socktype, value)
+
+ addr.protocol.should == value
+ end
+ end
+ end
+
+ describe 'and the socket type is set to SOCK_RDM' do
+ before do
+ @socktype = Socket::SOCK_RDM
+ end
+
+ Socket.constants.grep(/^IPPROTO/).each do |type|
+ it "raises SocketError when using #{type}" do
+ value = Socket.const_get(type)
+ block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) }
+
+ block.should raise_error(SocketError)
+ end
+ end
+ end
+
+ platform_is :linux do
+ platform_is_not :android do
+ describe 'and the socket type is set to SOCK_SEQPACKET' do
+ before do
+ @socktype = Socket::SOCK_SEQPACKET
+ end
+
+ valid = [:IPPROTO_IP, :IPPROTO_HOPOPTS]
+
+ valid.each do |type|
+ it "overwrites the protocol when using #{type}" do
+ value = Socket.const_get(type)
+ addr = Addrinfo.new(@sockaddr, nil, @socktype, value)
+
+ addr.protocol.should == value
+ end
+ end
+
+ (Socket.constants.grep(/^IPPROTO/) - valid).each do |type|
+ it "raises SocketError when using #{type}" do
+ value = Socket.const_get(type)
+ block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) }
+
+ block.should raise_error(SocketError)
+ end
+ end
+ end
+ end
+ end
+
+ describe 'and the socket type is set to SOCK_STREAM' do
+ before do
+ @socktype = Socket::SOCK_STREAM
+ end
+
+ valid = [:IPPROTO_IP, :IPPROTO_TCP, :IPPROTO_HOPOPTS]
+
+ valid.each do |type|
+ it "overwrites the protocol when using #{type}" do
+ value = Socket.const_get(type)
+ addr = Addrinfo.new(@sockaddr, nil, @socktype, value)
+
+ addr.protocol.should == value
+ end
+ end
+
+ platform_is_not :windows, :aix, :solaris do
+ (Socket.constants.grep(/^IPPROTO/) - valid).each do |type|
+ it "raises SocketError when using #{type}" do
+ value = Socket.const_get(type)
+ block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) }
+
+ block.should raise_error(SocketError)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe 'with Symbols' do
+ before do
+ @sockaddr = Socket.sockaddr_in(80, '127.0.0.1')
+ end
+
+ it 'returns an Addrinfo with :PF_INET family' do
+ addr = Addrinfo.new(@sockaddr, :PF_INET)
+
+ addr.pfamily.should == Socket::PF_INET
+ end
+
+ it 'returns an Addrinfo with :INET family' do
+ addr = Addrinfo.new(@sockaddr, :INET)
+
+ addr.pfamily.should == Socket::PF_INET
+ end
+
+ it 'returns an Addrinfo with :SOCK_STREAM as the socket type' do
+ addr = Addrinfo.new(@sockaddr, nil, :SOCK_STREAM)
+
+ addr.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'returns an Addrinfo with :STREAM as the socket type' do
+ addr = Addrinfo.new(@sockaddr, nil, :STREAM)
+
+ addr.socktype.should == Socket::SOCK_STREAM
+ end
+ end
+
+ describe 'with Strings' do
+ before do
+ @sockaddr = Socket.sockaddr_in(80, '127.0.0.1')
+ end
+
+ it 'returns an Addrinfo with "PF_INET" family' do
+ addr = Addrinfo.new(@sockaddr, 'PF_INET')
+
+ addr.pfamily.should == Socket::PF_INET
+ end
+
+ it 'returns an Addrinfo with "INET" family' do
+ addr = Addrinfo.new(@sockaddr, 'INET')
+
+ addr.pfamily.should == Socket::PF_INET
+ end
+
+ it 'returns an Addrinfo with "SOCK_STREAM" as the socket type' do
+ addr = Addrinfo.new(@sockaddr, nil, 'SOCK_STREAM')
+
+ addr.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'returns an Addrinfo with "STREAM" as the socket type' do
+ addr = Addrinfo.new(@sockaddr, nil, 'STREAM')
+
+ addr.socktype.should == Socket::SOCK_STREAM
+ end
+ end
+
+ with_feature :unix_socket do
+ describe 'using separate arguments for a Unix socket' do
+ before do
+ @sockaddr = Socket.pack_sockaddr_un('socket')
+ end
+
+ it 'returns an Addrinfo with the correct unix path' do
+ Addrinfo.new(@sockaddr).unix_path.should == 'socket'
+ end
+
+ it 'returns an Addrinfo with the correct protocol family' do
+ Addrinfo.new(@sockaddr).pfamily.should == Socket::PF_UNSPEC
+ end
+
+ it 'returns an Addrinfo with the correct address family' do
+ Addrinfo.new(@sockaddr).afamily.should == Socket::AF_UNIX
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb b/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb
new file mode 100644
index 0000000000..70ca4dd4d7
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../spec_helper'
+
+
+describe 'Addrinfo#inspect_sockaddr' do
+ describe 'using an IPv4 address' do
+ it 'returns a String containing the IP address and port number' do
+ addr = Addrinfo.tcp('127.0.0.1', 80)
+
+ addr.inspect_sockaddr.should == '127.0.0.1:80'
+ end
+
+ it 'returns a String containing just the IP address when no port is given' do
+ addr = Addrinfo.tcp('127.0.0.1', 0)
+
+ addr.inspect_sockaddr.should == '127.0.0.1'
+ end
+ end
+
+ describe 'using an IPv6 address' do
+ before :each do
+ @ip = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ end
+
+ it 'returns a String containing the IP address and port number' do
+ Addrinfo.tcp('::1', 80).inspect_sockaddr.should == '[::1]:80'
+ Addrinfo.tcp(@ip, 80).inspect_sockaddr.should == '[2001:db8:85a3::8a2e:370:7334]:80'
+ end
+
+ it 'returns a String containing just the IP address when no port is given' do
+ Addrinfo.tcp('::1', 0).inspect_sockaddr.should == '::1'
+ Addrinfo.tcp(@ip, 0).inspect_sockaddr.should == '2001:db8:85a3::8a2e:370:7334'
+ end
+ end
+
+ with_feature :unix_socket do
+ describe 'using a UNIX path' do
+ it 'returns a String containing the UNIX path' do
+ addr = Addrinfo.unix('/foo/bar')
+
+ addr.inspect_sockaddr.should == '/foo/bar'
+ end
+
+ it 'returns a String containing the UNIX path when using a relative path' do
+ addr = Addrinfo.unix('foo')
+
+ addr.inspect_sockaddr.should == 'UNIX foo'
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/inspect_spec.rb b/spec/ruby/library/socket/addrinfo/inspect_spec.rb
new file mode 100644
index 0000000000..98e1e83ffa
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/inspect_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#inspect' do
+ describe 'using an IPv4 Addrinfo' do
+ it 'returns a String when using a TCP Addrinfo' do
+ addr = Addrinfo.tcp('127.0.0.1', 80)
+
+ addr.inspect.should == '#<Addrinfo: 127.0.0.1:80 TCP>'
+ end
+
+ it 'returns a String when using an UDP Addrinfo' do
+ addr = Addrinfo.udp('127.0.0.1', 80)
+
+ addr.inspect.should == '#<Addrinfo: 127.0.0.1:80 UDP>'
+ end
+
+ it 'returns a String when using an Addrinfo without a port' do
+ addr = Addrinfo.ip('127.0.0.1')
+
+ addr.inspect.should == '#<Addrinfo: 127.0.0.1>'
+ end
+ end
+
+ describe 'using an IPv6 Addrinfo' do
+ it 'returns a String when using a TCP Addrinfo' do
+ addr = Addrinfo.tcp('::1', 80)
+
+ addr.inspect.should == '#<Addrinfo: [::1]:80 TCP>'
+ end
+
+ it 'returns a String when using an UDP Addrinfo' do
+ addr = Addrinfo.udp('::1', 80)
+
+ addr.inspect.should == '#<Addrinfo: [::1]:80 UDP>'
+ end
+
+ it 'returns a String when using an Addrinfo without a port' do
+ addr = Addrinfo.ip('::1')
+
+ addr.inspect.should == '#<Addrinfo: ::1>'
+ end
+ end
+
+ with_feature :unix_socket do
+ describe 'using a UNIX Addrinfo' do
+ it 'returns a String' do
+ addr = Addrinfo.unix('/foo')
+
+ addr.inspect.should == '#<Addrinfo: /foo SOCK_STREAM>'
+ end
+
+ it 'returns a String when using a relative UNIX path' do
+ addr = Addrinfo.unix('foo')
+
+ addr.inspect.should == '#<Addrinfo: UNIX foo SOCK_STREAM>'
+ end
+
+ it 'returns a String when using a DGRAM socket' do
+ addr = Addrinfo.unix('/foo', Socket::SOCK_DGRAM)
+
+ addr.inspect.should == '#<Addrinfo: /foo SOCK_DGRAM>'
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ip_address_spec.rb b/spec/ruby/library/socket/addrinfo/ip_address_spec.rb
new file mode 100644
index 0000000000..4522cf5cfd
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ip_address_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#ip_address" do
+ describe "for an ipv4 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("127.0.0.1", 80)
+ end
+
+ it "returns the ip address" do
+ @addrinfo.ip_address.should == "127.0.0.1"
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "returns the ip address" do
+ @addrinfo.ip_address.should == "::1"
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "raises an exception" do
+ -> { @addrinfo.ip_address }.should raise_error(SocketError)
+ end
+ end
+ end
+
+ describe 'with an Array as the socket address' do
+ it 'returns the IP as a String' do
+ sockaddr = ['AF_INET', 80, 'localhost', '127.0.0.1']
+ addr = Addrinfo.new(sockaddr)
+
+ addr.ip_address.should == '127.0.0.1'
+ end
+ end
+
+ describe 'without an IP address' do
+ before do
+ @ips = ['127.0.0.1', '0.0.0.0', '::1']
+ end
+
+ # Both these cases seem to return different values at times on MRI. Since
+ # this is network dependent we can't rely on an exact IP being returned.
+ it 'returns the local IP address when using an empty String as the IP' do
+ sockaddr = Socket.sockaddr_in(80, '')
+ addr = Addrinfo.new(sockaddr)
+
+ @ips.include?(addr.ip_address).should == true
+ end
+
+ it 'returns the local IP address when using nil as the IP' do
+ sockaddr = Socket.sockaddr_in(80, nil)
+ addr = Addrinfo.new(sockaddr)
+
+ @ips.include?(addr.ip_address).should == true
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ip_port_spec.rb b/spec/ruby/library/socket/addrinfo/ip_port_spec.rb
new file mode 100644
index 0000000000..4118607db0
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ip_port_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#ip_port" do
+ describe "for an ipv4 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("127.0.0.1", 80)
+ end
+
+ it "returns the port" do
+ @addrinfo.ip_port.should == 80
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "returns the port" do
+ @addrinfo.ip_port.should == 80
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "raises an exception" do
+ -> { @addrinfo.ip_port }.should raise_error(SocketError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ip_spec.rb b/spec/ruby/library/socket/addrinfo/ip_spec.rb
new file mode 100644
index 0000000000..80e7a62df7
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ip_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Addrinfo#ip?" do
+ describe "for an ipv4 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("127.0.0.1", 80)
+ end
+
+ it "returns true" do
+ @addrinfo.ip?.should be_true
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "returns true" do
+ @addrinfo.ip?.should be_true
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns false" do
+ @addrinfo.ip?.should be_false
+ end
+ end
+ end
+end
+
+describe 'Addrinfo.ip' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ it 'returns an Addrinfo instance' do
+ Addrinfo.ip(ip_address).should be_an_instance_of(Addrinfo)
+ end
+
+ it 'sets the IP address' do
+ Addrinfo.ip(ip_address).ip_address.should == ip_address
+ end
+
+ it 'sets the port to 0' do
+ Addrinfo.ip(ip_address).ip_port.should == 0
+ end
+
+ it 'sets the address family' do
+ Addrinfo.ip(ip_address).afamily.should == family
+ end
+
+ it 'sets the protocol family' do
+ Addrinfo.ip(ip_address).pfamily.should == family
+ end
+
+ it 'sets the socket type to 0' do
+ Addrinfo.ip(ip_address).socktype.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb b/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb
new file mode 100644
index 0000000000..6c81c48d1c
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#ip_unpack" do
+ describe "for an ipv4 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("127.0.0.1", 80)
+ end
+
+ it "returns the ip address and port pair" do
+ @addrinfo.ip_unpack.should == ["127.0.0.1", 80]
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "returns the ip address and port pair" do
+ @addrinfo.ip_unpack.should == ["::1", 80]
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "raises an exception" do
+ -> { @addrinfo.ip_unpack }.should raise_error(SocketError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb
new file mode 100644
index 0000000000..10ad084fc9
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#ipv4_loopback?" do
+ describe "for an ipv4 socket" do
+ it "returns true for the loopback address" do
+ Addrinfo.ip('127.0.0.1').should.ipv4_loopback?
+ Addrinfo.ip('127.0.0.2').should.ipv4_loopback?
+ Addrinfo.ip('127.255.0.1').should.ipv4_loopback?
+ Addrinfo.ip('127.255.255.255').should.ipv4_loopback?
+ end
+
+ it "returns false for another address" do
+ Addrinfo.ip('255.255.255.0').ipv4_loopback?.should be_false
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @loopback = Addrinfo.tcp("::1", 80)
+ @other = Addrinfo.tcp("::", 80)
+ end
+
+ it "returns false for the loopback address" do
+ @loopback.ipv4_loopback?.should be_false
+ end
+
+ it "returns false for another address" do
+ @other.ipv4_loopback?.should be_false
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns false" do
+ @addrinfo.ipv4_loopback?.should be_false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb
new file mode 100644
index 0000000000..f7fead8640
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#ipv4_multicast?" do
+ it 'returns true for a multicast address' do
+ Addrinfo.ip('224.0.0.0').should.ipv4_multicast?
+ Addrinfo.ip('224.0.0.9').should.ipv4_multicast?
+ Addrinfo.ip('239.255.255.250').should.ipv4_multicast?
+ end
+
+ it 'returns false for a regular address' do
+ Addrinfo.ip('8.8.8.8').should_not.ipv4_multicast?
+ end
+
+ it 'returns false for an IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv4_multicast?
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns false" do
+ @addrinfo.ipv4_multicast?.should be_false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb
new file mode 100644
index 0000000000..e5a33b4953
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#ipv4_private?" do
+ describe "for an ipv4 socket" do
+ before :each do
+ @private = Addrinfo.tcp("10.0.0.1", 80)
+ @other = Addrinfo.tcp("0.0.0.0", 80)
+ end
+
+ it "returns true for a private address" do
+ Addrinfo.ip('10.0.0.0').should.ipv4_private?
+ Addrinfo.ip('10.0.0.5').should.ipv4_private?
+
+ Addrinfo.ip('172.16.0.0').should.ipv4_private?
+ Addrinfo.ip('172.16.0.5').should.ipv4_private?
+
+ Addrinfo.ip('192.168.0.0').should.ipv4_private?
+ Addrinfo.ip('192.168.0.5').should.ipv4_private?
+ end
+
+ it "returns false for a public address" do
+ @other.ipv4_private?.should be_false
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @other = Addrinfo.tcp("::", 80)
+ end
+
+ it "returns false" do
+ @other.ipv4_private?.should be_false
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns false" do
+ @addrinfo.ipv4_private?.should be_false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv4_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_spec.rb
new file mode 100644
index 0000000000..7cba8209b6
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv4_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#ipv4?" do
+ describe "for an ipv4 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("10.0.0.1", 80)
+ end
+
+ it "returns true" do
+ @addrinfo.ipv4?.should be_true
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "returns false" do
+ @addrinfo.ipv4?.should be_false
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns false" do
+ @addrinfo.ipv4?.should be_false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb
new file mode 100644
index 0000000000..bfef396381
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+guard -> { SocketSpecs.ipv6_available? } do
+ describe 'Addrinfo#ipv6_linklocal?' do
+ platform_is_not :aix do
+ it 'returns true for a link-local address' do
+ Addrinfo.ip('fe80::').should.ipv6_linklocal?
+ Addrinfo.ip('fe81::').should.ipv6_linklocal?
+ Addrinfo.ip('fe8f::').should.ipv6_linklocal?
+ Addrinfo.ip('fe80::1').should.ipv6_linklocal?
+ end
+ end
+
+ it 'returns false for a regular address' do
+ Addrinfo.ip('::1').should_not.ipv6_linklocal?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_linklocal?
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb
new file mode 100644
index 0000000000..9ff8f107bf
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#ipv6_loopback?" do
+ describe "for an ipv4 socket" do
+ before :each do
+ @loopback = Addrinfo.tcp("127.0.0.1", 80)
+ @other = Addrinfo.tcp("0.0.0.0", 80)
+ end
+
+ it "returns false for the loopback address" do
+ @loopback.ipv6_loopback?.should be_false
+ end
+
+ it "returns false for another address" do
+ @other.ipv6_loopback?.should be_false
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @loopback = Addrinfo.tcp("::1", 80)
+ @other = Addrinfo.tcp("::", 80)
+ end
+
+ it "returns true for the loopback address" do
+ @loopback.ipv6_loopback?.should be_true
+ end
+
+ it "returns false for another address" do
+ @other.ipv6_loopback?.should be_false
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns false" do
+ @addrinfo.ipv6_loopback?.should be_false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb
new file mode 100644
index 0000000000..01fa0992ba
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#ipv6_mc_global?' do
+ it 'returns true for a multi-cast address in the global scope' do
+ Addrinfo.ip('ff1e::').should.ipv6_mc_global?
+ Addrinfo.ip('fffe::').should.ipv6_mc_global?
+ Addrinfo.ip('ff0e::').should.ipv6_mc_global?
+ Addrinfo.ip('ff1e::1').should.ipv6_mc_global?
+ end
+
+ it 'returns false for a regular IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv6_mc_global?
+ Addrinfo.ip('ff1a::').should_not.ipv6_mc_global?
+ Addrinfo.ip('ff1f::1').should_not.ipv6_mc_global?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_global?
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb
new file mode 100644
index 0000000000..a1298919eb
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#ipv6_mc_linklocal?' do
+ it 'returns true for a multi-cast link-local address' do
+ Addrinfo.ip('ff12::').should.ipv6_mc_linklocal?
+ Addrinfo.ip('ff02::').should.ipv6_mc_linklocal?
+ Addrinfo.ip('fff2::').should.ipv6_mc_linklocal?
+ Addrinfo.ip('ff12::1').should.ipv6_mc_linklocal?
+ end
+
+ it 'returns false for a regular IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv6_mc_linklocal?
+ Addrinfo.ip('fff1::').should_not.ipv6_mc_linklocal?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_linklocal?
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb
new file mode 100644
index 0000000000..0aee952d88
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#ipv6_mc_nodelocal?' do
+ it 'returns true for a multi-cast node-local address' do
+ Addrinfo.ip('ff11::').should.ipv6_mc_nodelocal?
+ Addrinfo.ip('ff01::').should.ipv6_mc_nodelocal?
+ Addrinfo.ip('fff1::').should.ipv6_mc_nodelocal?
+ Addrinfo.ip('ff11::1').should.ipv6_mc_nodelocal?
+ end
+
+ it 'returns false for a regular IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv6_mc_nodelocal?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_nodelocal?
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb
new file mode 100644
index 0000000000..2977a98d30
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#ipv6_mc_orglocal?' do
+ it 'returns true for a multi-cast org-local address' do
+ Addrinfo.ip('ff18::').should.ipv6_mc_orglocal?
+ Addrinfo.ip('ff08::').should.ipv6_mc_orglocal?
+ Addrinfo.ip('fff8::').should.ipv6_mc_orglocal?
+ Addrinfo.ip('ff18::1').should.ipv6_mc_orglocal?
+ end
+
+ it 'returns false for a regular IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv6_mc_orglocal?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_orglocal?
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb
new file mode 100644
index 0000000000..58e5976a40
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#ipv6_mc_sitelocal?' do
+ it 'returns true for a multi-cast site-local address' do
+ Addrinfo.ip('ff15::').should.ipv6_mc_sitelocal?
+ Addrinfo.ip('ff05::').should.ipv6_mc_sitelocal?
+ Addrinfo.ip('fff5::').should.ipv6_mc_sitelocal?
+ Addrinfo.ip('ff15::1').should.ipv6_mc_sitelocal?
+ end
+
+ it 'returns false for a regular IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv6_mc_sitelocal?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_sitelocal?
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb
new file mode 100644
index 0000000000..2c987b5921
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#ipv6_multicast?" do
+ describe "for an ipv4 socket" do
+ before :each do
+ @multicast = Addrinfo.tcp("224.0.0.1", 80)
+ @other = Addrinfo.tcp("0.0.0.0", 80)
+ end
+
+ it "returns true for a multicast address" do
+ @multicast.ipv6_multicast?.should be_false
+ end
+
+ it "returns false for another address" do
+ @other.ipv6_multicast?.should be_false
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ it "returns true for a multicast address" do
+ Addrinfo.ip('ff00::').should.ipv6_multicast?
+ Addrinfo.ip('ff00::1').should.ipv6_multicast?
+ Addrinfo.ip('ff08::1').should.ipv6_multicast?
+ Addrinfo.ip('fff8::1').should.ipv6_multicast?
+
+ Addrinfo.ip('ff02::').should.ipv6_multicast?
+ Addrinfo.ip('ff02::1').should.ipv6_multicast?
+ Addrinfo.ip('ff0f::').should.ipv6_multicast?
+ end
+
+ it "returns false for another address" do
+ Addrinfo.ip('::1').should_not.ipv6_multicast?
+ Addrinfo.ip('fe80::').should_not.ipv6_multicast?
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns false" do
+ @addrinfo.ipv6_multicast?.should be_false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb
new file mode 100644
index 0000000000..9158eb5809
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+guard -> { SocketSpecs.ipv6_available? } do
+ describe 'Addrinfo#ipv6_sitelocal?' do
+ platform_is_not :aix do
+ it 'returns true for a site-local address' do
+ Addrinfo.ip('feef::').should.ipv6_sitelocal?
+ Addrinfo.ip('fee0::').should.ipv6_sitelocal?
+ Addrinfo.ip('fee2::').should.ipv6_sitelocal?
+ Addrinfo.ip('feef::1').should.ipv6_sitelocal?
+ end
+ end
+
+ it 'returns false for a regular IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv6_sitelocal?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_sitelocal?
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_spec.rb
new file mode 100644
index 0000000000..131e38849c
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#ipv6?" do
+ describe "for an ipv4 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("10.0.0.1", 80)
+ end
+
+ it "returns true" do
+ @addrinfo.ipv6?.should be_false
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "returns false" do
+ @addrinfo.ipv6?.should be_true
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns false" do
+ @addrinfo.ipv6?.should be_false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb
new file mode 100644
index 0000000000..6dfaf531ae
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+guard -> { SocketSpecs.ipv6_available? } do
+ describe 'Addrinfo#ipv6_to_ipv4' do
+ it 'returns an Addrinfo for ::192.168.1.1' do
+ addr = Addrinfo.ip('::192.168.1.1').ipv6_to_ipv4
+
+ addr.should be_an_instance_of(Addrinfo)
+
+ addr.afamily.should == Socket::AF_INET
+ addr.ip_address.should == '192.168.1.1'
+ end
+
+ platform_is_not :aix do
+ it 'returns an Addrinfo for ::0.0.1.1' do
+ addr = Addrinfo.ip('::0.0.1.1').ipv6_to_ipv4
+
+ addr.should be_an_instance_of(Addrinfo)
+
+ addr.afamily.should == Socket::AF_INET
+ addr.ip_address.should == '0.0.1.1'
+ end
+
+ it 'returns an Addrinfo for ::0.0.1.0' do
+ addr = Addrinfo.ip('::0.0.1.0').ipv6_to_ipv4
+
+ addr.should be_an_instance_of(Addrinfo)
+
+ addr.afamily.should == Socket::AF_INET
+ addr.ip_address.should == '0.0.1.0'
+ end
+
+ it 'returns an Addrinfo for ::0.1.0.0' do
+ addr = Addrinfo.ip('::0.1.0.0').ipv6_to_ipv4
+
+ addr.should be_an_instance_of(Addrinfo)
+
+ addr.afamily.should == Socket::AF_INET
+ addr.ip_address.should == '0.1.0.0'
+ end
+ end
+
+ it 'returns an Addrinfo for ::ffff:192.168.1.1' do
+ addr = Addrinfo.ip('::ffff:192.168.1.1').ipv6_to_ipv4
+
+ addr.should be_an_instance_of(Addrinfo)
+
+ addr.afamily.should == Socket::AF_INET
+ addr.ip_address.should == '192.168.1.1'
+ end
+
+ it 'returns nil for ::0.0.0.1' do
+ Addrinfo.ip('::0.0.0.1').ipv6_to_ipv4.should be_nil
+ end
+
+ it 'returns nil for a pure IPv6 Addrinfo' do
+ Addrinfo.ip('::1').ipv6_to_ipv4.should be_nil
+ end
+
+ it 'returns nil for an IPv4 Addrinfo' do
+ Addrinfo.ip('192.168.1.1').ipv6_to_ipv4.should be_nil
+ end
+
+ with_feature :unix_socket do
+ it 'returns nil for a UNIX Addrinfo' do
+ Addrinfo.unix('foo').ipv6_to_ipv4.should be_nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb
new file mode 100644
index 0000000000..22f0fa3b75
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#ipv6_unique_local?' do
+ it 'returns true for an unique local IPv6 address' do
+ Addrinfo.ip('fc00::').should.ipv6_unique_local?
+ Addrinfo.ip('fd00::').should.ipv6_unique_local?
+ Addrinfo.ip('fcff::').should.ipv6_unique_local?
+ end
+
+ it 'returns false for a regular IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv6_unique_local?
+ Addrinfo.ip('fe00::').should_not.ipv6_unique_local?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_unique_local?
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb
new file mode 100644
index 0000000000..d63979ceda
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#ipv6_unspecified?' do
+ it 'returns true for an unspecified IPv6 address' do
+ Addrinfo.ip('::').should.ipv6_unspecified?
+ end
+
+ it 'returns false for a regular IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv6_unspecified?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_unspecified?
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb
new file mode 100644
index 0000000000..21ca85af99
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#ipv6_v4compat?' do
+ it 'returns true for an IPv4 compatible address' do
+ Addrinfo.ip('::127.0.0.1').should.ipv6_v4compat?
+ Addrinfo.ip('::192.168.1.1').should.ipv6_v4compat?
+ end
+
+ it 'returns false for an IPv4 mapped address' do
+ Addrinfo.ip('::ffff:192.168.1.1').should_not.ipv6_v4compat?
+ end
+
+ it 'returns false for a regular IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv6_v4compat?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_v4compat?
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb
new file mode 100644
index 0000000000..7dac0e75db
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#ipv6_v4mapped?' do
+ it 'returns true for an IPv4 compatible address' do
+ Addrinfo.ip('::ffff:192.168.1.1').should.ipv6_v4mapped?
+ end
+
+ it 'returns false for an IPv4 compatible address' do
+ Addrinfo.ip('::192.168.1.1').should_not.ipv6_v4mapped?
+ Addrinfo.ip('::127.0.0.1').should_not.ipv6_v4mapped?
+ end
+
+ it 'returns false for a regular IPv6 address' do
+ Addrinfo.ip('::1').should_not.ipv6_v4mapped?
+ end
+
+ it 'returns false for an IPv4 address' do
+ Addrinfo.ip('127.0.0.1').should_not.ipv6_v4mapped?
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/listen_spec.rb b/spec/ruby/library/socket/addrinfo/listen_spec.rb
new file mode 100644
index 0000000000..931093f732
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/listen_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#listen' do
+ before do
+ @addr = Addrinfo.tcp('127.0.0.1', 0)
+ @socket = nil
+ end
+
+ after do
+ @socket.close if @socket
+ end
+
+ it 'returns a Socket when no block is given' do
+ @socket = @addr.listen
+
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'yields the Socket if a block is given' do
+ @addr.listen do |socket|
+ socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'closes the socket if a block is given' do
+ socket = nil
+
+ @addr.listen do |sock|
+ socket = sock
+ end
+
+ socket.should.closed?
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb b/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb
new file mode 100644
index 0000000000..c4220a6f3e
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#marshal_dump' do
+ describe 'using an IP Addrinfo' do
+ before do
+ @addr = Addrinfo.getaddrinfo('localhost', 80, :INET, :STREAM,
+ Socket::IPPROTO_TCP, Socket::AI_CANONNAME)[0]
+ end
+
+ it 'returns an Array' do
+ @addr.marshal_dump.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = @addr.marshal_dump
+ end
+
+ it 'includes the address family as the 1st value' do
+ @array[0].should == 'AF_INET'
+ end
+
+ it 'includes the IP address as the 2nd value' do
+ @array[1].should == [@addr.ip_address, @addr.ip_port.to_s]
+ end
+
+ it 'includes the protocol family as the 3rd value' do
+ @array[2].should == 'PF_INET'
+ end
+
+ it 'includes the socket type as the 4th value' do
+ @array[3].should == 'SOCK_STREAM'
+ end
+
+ platform_is_not :'solaris2.10' do # i386-solaris
+ it 'includes the protocol as the 5th value' do
+ @array[4].should == 'IPPROTO_TCP'
+ end
+ end
+
+ it 'includes the canonical name as the 6th value' do
+ @array[5].should == @addr.canonname
+ end
+ end
+ end
+
+ with_feature :unix_socket do
+ describe 'using a UNIX Addrinfo' do
+ before do
+ @addr = Addrinfo.unix('foo')
+ end
+
+ it 'returns an Array' do
+ @addr.marshal_dump.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = @addr.marshal_dump
+ end
+
+ it 'includes the address family as the 1st value' do
+ @array[0].should == 'AF_UNIX'
+ end
+
+ it 'includes the UNIX path as the 2nd value' do
+ @array[1].should == @addr.unix_path
+ end
+
+ it 'includes the protocol family as the 3rd value' do
+ @array[2].should == 'PF_UNIX'
+ end
+
+ it 'includes the socket type as the 4th value' do
+ @array[3].should == 'SOCK_STREAM'
+ end
+
+ it 'includes the protocol as the 5th value' do
+ @array[4].should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb b/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb
new file mode 100644
index 0000000000..aa20865224
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+
+describe 'Addrinfo#marshal_load' do
+ describe 'using an IP address' do
+ it 'returns a new Addrinfo' do
+ source = Addrinfo.getaddrinfo('localhost', 80, :INET, :STREAM,
+ Socket::IPPROTO_TCP, Socket::AI_CANONNAME)[0]
+
+ addr = Marshal.load(Marshal.dump(source))
+
+ addr.afamily.should == source.afamily
+ addr.pfamily.should == source.pfamily
+ addr.socktype.should == source.socktype
+ addr.protocol.should == source.protocol
+ addr.ip_address.should == source.ip_address
+ addr.ip_port.should == source.ip_port
+ addr.canonname.should == source.canonname
+ end
+ end
+
+ with_feature :unix_socket do
+ describe 'using a UNIX socket' do
+ it 'returns a new Addrinfo' do
+ source = Addrinfo.unix('foo')
+ addr = Marshal.load(Marshal.dump(source))
+
+ addr.afamily.should == source.afamily
+ addr.pfamily.should == source.pfamily
+ addr.socktype.should == source.socktype
+ addr.protocol.should == source.protocol
+ addr.unix_path.should == source.unix_path
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/pfamily_spec.rb b/spec/ruby/library/socket/addrinfo/pfamily_spec.rb
new file mode 100644
index 0000000000..984744a964
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/pfamily_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#pfamily" do
+ it 'returns PF_UNSPEC as the default socket family' do
+ sockaddr = Socket.pack_sockaddr_in(80, 'localhost')
+
+ Addrinfo.new(sockaddr).pfamily.should == Socket::PF_UNSPEC
+ end
+
+ describe "for an ipv4 socket" do
+
+ before :each do
+ @addrinfo = Addrinfo.tcp("127.0.0.1", 80)
+ end
+
+ it "returns Socket::PF_INET" do
+ @addrinfo.pfamily.should == Socket::PF_INET
+ end
+
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "returns Socket::PF_INET6" do
+ @addrinfo.pfamily.should == Socket::PF_INET6
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns Socket::PF_UNIX" do
+ @addrinfo.pfamily.should == Socket::PF_UNIX
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/protocol_spec.rb b/spec/ruby/library/socket/addrinfo/protocol_spec.rb
new file mode 100644
index 0000000000..ea143fc4a8
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/protocol_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#protocol" do
+ it 'returns 0 by default' do
+ Addrinfo.ip('127.0.0.1').protocol.should == 0
+ end
+
+ it 'returns a custom protocol when given' do
+ Addrinfo.tcp('127.0.0.1', 80).protocol.should == Socket::IPPROTO_TCP
+ Addrinfo.tcp('::1', 80).protocol.should == Socket::IPPROTO_TCP
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns 0" do
+ @addrinfo.protocol.should == 0
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb b/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb
new file mode 100644
index 0000000000..c32da5986d
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb
@@ -0,0 +1,51 @@
+describe :socket_addrinfo_to_sockaddr, :shared => true do
+
+ describe "for an ipv4 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("127.0.0.1", 80)
+ end
+
+ it "returns a sockaddr packed structure" do
+ @addrinfo.send(@method).should == Socket.sockaddr_in(80, '127.0.0.1')
+ end
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "returns a sockaddr packed structure" do
+ @addrinfo.send(@method).should == Socket.sockaddr_in(80, '::1')
+ end
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns a sockaddr packed structure" do
+ @addrinfo.send(@method).should == Socket.sockaddr_un('/tmp/sock')
+ end
+ end
+ end
+
+ describe 'using a Addrinfo with just an IP address' do
+ it 'returns a String' do
+ addr = Addrinfo.ip('127.0.0.1')
+
+ addr.send(@method).should == Socket.sockaddr_in(0, '127.0.0.1')
+ end
+ end
+
+ describe 'using a Addrinfo without an IP and port' do
+ it 'returns a String' do
+ addr = Addrinfo.new(['AF_INET', 0, '', ''])
+
+ addr.send(@method).should == Socket.sockaddr_in(0, '')
+ end
+ end
+
+end
diff --git a/spec/ruby/library/socket/addrinfo/socktype_spec.rb b/spec/ruby/library/socket/addrinfo/socktype_spec.rb
new file mode 100644
index 0000000000..b994bea140
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/socktype_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../spec_helper'
+
+describe "Addrinfo#socktype" do
+ it 'returns 0 by default' do
+ Addrinfo.ip('127.0.0.1').socktype.should == 0
+ end
+
+ it 'returns the socket type when given' do
+ Addrinfo.tcp('127.0.0.1', 80).socktype.should == Socket::SOCK_STREAM
+ end
+
+ with_feature :unix_socket do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns Socket::SOCK_STREAM" do
+ @addrinfo.socktype.should == Socket::SOCK_STREAM
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/tcp_spec.rb b/spec/ruby/library/socket/addrinfo/tcp_spec.rb
new file mode 100644
index 0000000000..c74c9c21c2
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/tcp_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Addrinfo.tcp' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ it 'returns an Addrinfo instance' do
+ Addrinfo.tcp(ip_address, 80).should be_an_instance_of(Addrinfo)
+ end
+
+ it 'sets the IP address' do
+ Addrinfo.tcp(ip_address, 80).ip_address.should == ip_address
+ end
+
+ it 'sets the port' do
+ Addrinfo.tcp(ip_address, 80).ip_port.should == 80
+ end
+
+ it 'sets the address family' do
+ Addrinfo.tcp(ip_address, 80).afamily.should == family
+ end
+
+ it 'sets the protocol family' do
+ Addrinfo.tcp(ip_address, 80).pfamily.should == family
+ end
+
+ it 'sets the socket type' do
+ Addrinfo.tcp(ip_address, 80).socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'sets the socket protocol' do
+ Addrinfo.tcp(ip_address, 80).protocol.should == Socket::IPPROTO_TCP
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/to_s_spec.rb b/spec/ruby/library/socket/addrinfo/to_s_spec.rb
new file mode 100644
index 0000000000..ddf994e051
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../spec_helper'
+require_relative 'shared/to_sockaddr'
+
+describe "Addrinfo#to_s" do
+ it_behaves_like :socket_addrinfo_to_sockaddr, :to_s
+end
diff --git a/spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb b/spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb
new file mode 100644
index 0000000000..b9f75454bd
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../spec_helper'
+require_relative 'shared/to_sockaddr'
+
+describe "Addrinfo#to_sockaddr" do
+ it_behaves_like :socket_addrinfo_to_sockaddr, :to_sockaddr
+end
diff --git a/spec/ruby/library/socket/addrinfo/udp_spec.rb b/spec/ruby/library/socket/addrinfo/udp_spec.rb
new file mode 100644
index 0000000000..b05cbf9b0b
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/udp_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Addrinfo.udp' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ it 'returns an Addrinfo instance' do
+ Addrinfo.udp(ip_address, 80).should be_an_instance_of(Addrinfo)
+ end
+
+ it 'sets the IP address' do
+ Addrinfo.udp(ip_address, 80).ip_address.should == ip_address
+ end
+
+ it 'sets the port' do
+ Addrinfo.udp(ip_address, 80).ip_port.should == 80
+ end
+
+ it 'sets the address family' do
+ Addrinfo.udp(ip_address, 80).afamily.should == family
+ end
+
+ it 'sets the protocol family' do
+ Addrinfo.udp(ip_address, 80).pfamily.should == family
+ end
+
+ it 'sets the socket type' do
+ Addrinfo.udp(ip_address, 80).socktype.should == Socket::SOCK_DGRAM
+ end
+
+ platform_is_not :solaris do
+ it 'sets the socket protocol' do
+ Addrinfo.udp(ip_address, 80).protocol.should == Socket::IPPROTO_UDP
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/unix_path_spec.rb b/spec/ruby/library/socket/addrinfo/unix_path_spec.rb
new file mode 100644
index 0000000000..6bfb56a4ac
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/unix_path_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../spec_helper'
+
+with_feature :unix_socket do
+ describe "Addrinfo#unix_path" do
+ describe "for an ipv4 socket" do
+
+ before :each do
+ @addrinfo = Addrinfo.tcp("127.0.0.1", 80)
+ end
+
+ it "raises an exception" do
+ -> { @addrinfo.unix_path }.should raise_error(SocketError)
+ end
+
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "raises an exception" do
+ -> { @addrinfo.unix_path }.should raise_error(SocketError)
+ end
+ end
+
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns the socket path" do
+ @addrinfo.unix_path.should == "/tmp/sock"
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/addrinfo/unix_spec.rb b/spec/ruby/library/socket/addrinfo/unix_spec.rb
new file mode 100644
index 0000000000..4596ece17e
--- /dev/null
+++ b/spec/ruby/library/socket/addrinfo/unix_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../spec_helper'
+
+with_feature :unix_socket do
+ describe 'Addrinfo.unix' do
+ it 'returns an Addrinfo instance' do
+ Addrinfo.unix('socket').should be_an_instance_of(Addrinfo)
+ end
+
+ it 'sets the IP address' do
+ Addrinfo.unix('socket').unix_path.should == 'socket'
+ end
+
+ it 'sets the address family' do
+ Addrinfo.unix('socket').afamily.should == Socket::AF_UNIX
+ end
+
+ it 'sets the protocol family' do
+ Addrinfo.unix('socket').pfamily.should == Socket::PF_UNIX
+ end
+
+ it 'sets the socket type' do
+ Addrinfo.unix('socket').socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'sets a custom socket type' do
+ addr = Addrinfo.unix('socket', Socket::SOCK_DGRAM)
+
+ addr.socktype.should == Socket::SOCK_DGRAM
+ end
+
+ it 'sets the socket protocol to 0' do
+ Addrinfo.unix('socket').protocol.should == 0
+ end
+ end
+end
+
+describe "Addrinfo#unix?" do
+ describe "for an ipv4 socket" do
+
+ before :each do
+ @addrinfo = Addrinfo.tcp("127.0.0.1", 80)
+ end
+
+ it "returns false" do
+ @addrinfo.unix?.should be_false
+ end
+
+ end
+
+ describe "for an ipv6 socket" do
+ before :each do
+ @addrinfo = Addrinfo.tcp("::1", 80)
+ end
+
+ it "returns false" do
+ @addrinfo.unix?.should be_false
+ end
+ end
+
+ platform_is_not :windows do
+ describe "for a unix socket" do
+ before :each do
+ @addrinfo = Addrinfo.unix("/tmp/sock")
+ end
+
+ it "returns true" do
+ @addrinfo.unix?.should be_true
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb b/spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb
new file mode 100644
index 0000000000..c54ee29825
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data do
+ describe 'Socket::AncillaryData#cmsg_is?' do
+ describe 'using :INET, :IP, :TTL as the family, level, and type' do
+ before do
+ @data = Socket::AncillaryData.new(:INET, :IP, :TTL, '')
+ end
+
+ it 'returns true when comparing with IPPROTO_IP and IP_TTL' do
+ @data.cmsg_is?(Socket::IPPROTO_IP, Socket::IP_TTL).should == true
+ end
+
+ it 'returns true when comparing with :IP and :TTL' do
+ @data.cmsg_is?(:IP, :TTL).should == true
+ end
+
+ with_feature :pktinfo do
+ it 'returns false when comparing with :IP and :PKTINFO' do
+ @data.cmsg_is?(:IP, :PKTINFO).should == false
+ end
+ end
+
+ it 'returns false when comparing with :SOCKET and :RIGHTS' do
+ @data.cmsg_is?(:SOCKET, :RIGHTS).should == false
+ end
+
+ it 'raises SocketError when comparing with :IPV6 and :RIGHTS' do
+ -> { @data.cmsg_is?(:IPV6, :RIGHTS) }.should raise_error(SocketError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/data_spec.rb b/spec/ruby/library/socket/ancillarydata/data_spec.rb
new file mode 100644
index 0000000000..5a1a446dd5
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/data_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data do
+ describe 'Socket::AncillaryData#data' do
+ it 'returns the data as a String' do
+ Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, 'ugh').data.should == 'ugh'
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/family_spec.rb b/spec/ruby/library/socket/ancillarydata/family_spec.rb
new file mode 100644
index 0000000000..975f0d2538
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/family_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data do
+ describe 'Socket::AncillaryData#family' do
+ it 'returns the family as an Integer' do
+ Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').family.should == Socket::AF_INET
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/initialize_spec.rb b/spec/ruby/library/socket/ancillarydata/initialize_spec.rb
new file mode 100644
index 0000000000..344d1485c5
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/initialize_spec.rb
@@ -0,0 +1,284 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data do
+ describe 'Socket::AncillaryData#initialize' do
+ describe 'using Integers for the family, level, and type' do
+ before do
+ @data = Socket::AncillaryData
+ .new(Socket::AF_INET, Socket::IPPROTO_IP, Socket::IP_RECVTTL, 'ugh')
+ end
+
+ it 'sets the address family' do
+ @data.family.should == Socket::AF_INET
+ end
+
+ it 'sets the message level' do
+ @data.level.should == Socket::IPPROTO_IP
+ end
+
+ it 'sets the message type' do
+ @data.type.should == Socket::IP_RECVTTL
+ end
+
+ it 'sets the data' do
+ @data.data.should == 'ugh'
+ end
+ end
+
+ describe 'using Symbols for the family, level, and type' do
+ before do
+ @data = Socket::AncillaryData.new(:INET, :IPPROTO_IP, :RECVTTL, 'ugh')
+ end
+
+ it 'sets the address family' do
+ @data.family.should == Socket::AF_INET
+ end
+
+ it 'sets the message level' do
+ @data.level.should == Socket::IPPROTO_IP
+ end
+
+ it 'sets the message type' do
+ @data.type.should == Socket::IP_RECVTTL
+ end
+
+ it 'sets the data' do
+ @data.data.should == 'ugh'
+ end
+ end
+
+ describe 'using Strings for the family, level, and type' do
+ before do
+ @data = Socket::AncillaryData.new('INET', 'IPPROTO_IP', 'RECVTTL', 'ugh')
+ end
+
+ it 'sets the address family' do
+ @data.family.should == Socket::AF_INET
+ end
+
+ it 'sets the message level' do
+ @data.level.should == Socket::IPPROTO_IP
+ end
+
+ it 'sets the message type' do
+ @data.type.should == Socket::IP_RECVTTL
+ end
+
+ it 'sets the data' do
+ @data.data.should == 'ugh'
+ end
+ end
+
+ describe 'using custom objects with a to_str method for the family, level, and type' do
+ before do
+ fmock = mock(:family)
+ lmock = mock(:level)
+ tmock = mock(:type)
+ dmock = mock(:data)
+
+ fmock.stub!(:to_str).and_return('INET')
+ lmock.stub!(:to_str).and_return('IP')
+ tmock.stub!(:to_str).and_return('RECVTTL')
+ dmock.stub!(:to_str).and_return('ugh')
+
+ @data = Socket::AncillaryData.new(fmock, lmock, tmock, dmock)
+ end
+
+ it 'sets the address family' do
+ @data.family.should == Socket::AF_INET
+ end
+
+ it 'sets the message level' do
+ @data.level.should == Socket::IPPROTO_IP
+ end
+
+ it 'sets the message type' do
+ @data.type.should == Socket::IP_RECVTTL
+ end
+
+ it 'sets the data' do
+ @data.data.should == 'ugh'
+ end
+ end
+
+ describe 'using :AF_INET as the family and :SOCKET as the level' do
+ it 'sets the type to SCM_RIGHTS when using :RIGHTS as the type argument' do
+ Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS
+ end
+
+ platform_is_not :"solaris2.10", :aix do
+ it 'sets the type to SCM_TIMESTAMP when using :TIMESTAMP as the type argument' do
+ Socket::AncillaryData.new(:INET, :SOCKET, :TIMESTAMP, '').type.should == Socket::SCM_TIMESTAMP
+ end
+ end
+
+ it 'raises TypeError when using a numeric string as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :IGMP, Socket::SCM_RIGHTS.to_s, '')
+ }.should raise_error(TypeError)
+ end
+
+ it 'raises SocketError when using :RECVTTL as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :SOCKET, :RECVTTL, '')
+ }.should raise_error(SocketError)
+ end
+
+ it 'raises SocketError when using :MOO as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :SOCKET, :MOO, '')
+ }.should raise_error(SocketError)
+ end
+
+ it 'raises SocketError when using :IP_RECVTTL as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :SOCKET, :IP_RECVTTL, '')
+ }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using :AF_INET as the family and :SOCKET as the level' do
+ it 'sets the type to SCM_RIGHTS when using :RIGHTS as the type argument' do
+ Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS
+ end
+ end
+
+ describe 'using :AF_INET as the family and :IP as the level' do
+ it 'sets the type to IP_RECVTTL when using :RECVTTL as the type argument' do
+ Socket::AncillaryData.new(:INET, :IP, :RECVTTL, '').type.should == Socket::IP_RECVTTL
+ end
+
+ with_feature :ip_mtu do
+ it 'sets the type to IP_MTU when using :MTU as the type argument' do
+ Socket::AncillaryData.new(:INET, :IP, :MTU, '').type.should == Socket::IP_MTU
+ end
+ end
+
+ it 'raises SocketError when using :RIGHTS as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :IP, :RIGHTS, '')
+ }.should raise_error(SocketError)
+ end
+
+ it 'raises SocketError when using :MOO as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :IP, :MOO, '')
+ }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using :AF_INET as the family and :IPV6 as the level' do
+ it 'sets the type to IPV6_CHECKSUM when using :CHECKSUM as the type argument' do
+ Socket::AncillaryData.new(:INET, :IPV6, :CHECKSUM, '').type.should == Socket::IPV6_CHECKSUM
+ end
+
+ with_feature :ipv6_nexthop do
+ it 'sets the type to IPV6_NEXTHOP when using :NEXTHOP as the type argument' do
+ Socket::AncillaryData.new(:INET, :IPV6, :NEXTHOP, '').type.should == Socket::IPV6_NEXTHOP
+ end
+ end
+
+ it 'raises SocketError when using :RIGHTS as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :IPV6, :RIGHTS, '')
+ }.should raise_error(SocketError)
+ end
+
+ it 'raises SocketError when using :MOO as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :IPV6, :MOO, '')
+ }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using :AF_INET as the family and :TCP as the level' do
+ with_feature :tcp_cork do
+ it 'sets the type to TCP_CORK when using :CORK as the type argument' do
+ Socket::AncillaryData.new(:INET, :TCP, :CORK, '').type.should == Socket::TCP_CORK
+ end
+ end
+
+ with_feature :tcp_info do
+ it 'sets the type to TCP_INFO when using :INFO as the type argument' do
+ Socket::AncillaryData.new(:INET, :TCP, :INFO, '').type.should == Socket::TCP_INFO
+ end
+ end
+
+ it 'raises SocketError when using :RIGHTS as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :TCP, :RIGHTS, '')
+ }.should raise_error(SocketError)
+ end
+
+ it 'raises SocketError when using :MOO as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :TCP, :MOO, '')
+ }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using :AF_INET as the family and :UDP as the level' do
+ with_feature :udp_cork do
+ it 'sets the type to UDP_CORK when using :CORK as the type argument' do
+ Socket::AncillaryData.new(:INET, :UDP, :CORK, '').type.should == Socket::UDP_CORK
+ end
+ end
+
+ it 'raises SocketError when using :RIGHTS as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :UDP, :RIGHTS, '')
+ }.should raise_error(SocketError)
+ end
+
+ it 'raises SocketError when using :MOO as the type argument' do
+ -> {
+ Socket::AncillaryData.new(:INET, :UDP, :MOO, '')
+ }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using :AF_UNIX as the family and :SOCKET as the level' do
+ it 'sets the type to SCM_RIGHTS when using :RIGHTS as the type argument' do
+ Socket::AncillaryData.new(:UNIX, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS
+ end
+
+ it 'raises SocketError when using :CORK sa the type argument' do
+ -> {
+ Socket::AncillaryData.new(:UNIX, :SOCKET, :CORK, '')
+ }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using :AF_UNIX as the family and :IP as the level' do
+ it 'raises SocketError' do
+ -> {
+ Socket::AncillaryData.new(:UNIX, :IP, :RECVTTL, '')
+ }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using :AF_UNIX as the family and :IPV6 as the level' do
+ it 'raises SocketError' do
+ -> {
+ Socket::AncillaryData.new(:UNIX, :IPV6, :NEXTHOP, '')
+ }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using :AF_UNIX as the family and :TCP as the level' do
+ it 'raises SocketError' do
+ -> {
+ Socket::AncillaryData.new(:UNIX, :TCP, :CORK, '')
+ }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using :AF_UNIX as the family and :UDP as the level' do
+ it 'raises SocketError' do
+ -> {
+ Socket::AncillaryData.new(:UNIX, :UDP, :CORK, '')
+ }.should raise_error(SocketError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/int_spec.rb b/spec/ruby/library/socket/ancillarydata/int_spec.rb
new file mode 100644
index 0000000000..fe41a30a1a
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/int_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data do
+ describe 'Socket::AncillaryData.int' do
+ before do
+ @data = Socket::AncillaryData.int(:INET, :SOCKET, :RIGHTS, 4)
+ end
+
+ it 'returns a Socket::AncillaryData' do
+ @data.should be_an_instance_of(Socket::AncillaryData)
+ end
+
+ it 'sets the family to AF_INET' do
+ @data.family.should == Socket::AF_INET
+ end
+
+ it 'sets the level SOL_SOCKET' do
+ @data.level.should == Socket::SOL_SOCKET
+ end
+
+ it 'sets the type SCM_RIGHTS' do
+ @data.type.should == Socket::SCM_RIGHTS
+ end
+
+ it 'sets the data to a packed String' do
+ @data.data.should == [4].pack('I')
+ end
+ end
+
+ describe 'Socket::AncillaryData#int' do
+ it 'returns the data as an Integer' do
+ data = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, 4)
+
+ data.int.should == 4
+ end
+
+ it 'raises when the data is not an Integer' do
+ data = Socket::AncillaryData.new(:UNIX, :SOCKET, :RIGHTS, 'ugh')
+
+ -> { data.int }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb b/spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb
new file mode 100644
index 0000000000..84910a038a
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb
@@ -0,0 +1,145 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data, :pktinfo do
+ describe 'Socket::AncillaryData.ip_pktinfo' do
+ describe 'with a source address and index' do
+ before do
+ @data = Socket::AncillaryData.ip_pktinfo(Addrinfo.ip('127.0.0.1'), 4)
+ end
+
+ it 'returns a Socket::AncillaryData' do
+ @data.should be_an_instance_of(Socket::AncillaryData)
+ end
+
+ it 'sets the family to AF_INET' do
+ @data.family.should == Socket::AF_INET
+ end
+
+ it 'sets the level to IPPROTO_IP' do
+ @data.level.should == Socket::IPPROTO_IP
+ end
+
+ it 'sets the type to IP_PKTINFO' do
+ @data.type.should == Socket::IP_PKTINFO
+ end
+ end
+
+ describe 'with a source address, index, and destination address' do
+ before do
+ source = Addrinfo.ip('127.0.0.1')
+ dest = Addrinfo.ip('127.0.0.5')
+ @data = Socket::AncillaryData.ip_pktinfo(source, 4, dest)
+ end
+
+ it 'returns a Socket::AncillaryData' do
+ @data.should be_an_instance_of(Socket::AncillaryData)
+ end
+
+ it 'sets the family to AF_INET' do
+ @data.family.should == Socket::AF_INET
+ end
+
+ it 'sets the level to IPPROTO_IP' do
+ @data.level.should == Socket::IPPROTO_IP
+ end
+
+ it 'sets the type to IP_PKTINFO' do
+ @data.type.should == Socket::IP_PKTINFO
+ end
+ end
+ end
+
+ describe 'Socket::AncillaryData#ip_pktinfo' do
+ describe 'using an Addrinfo without a port number' do
+ before do
+ @source = Addrinfo.ip('127.0.0.1')
+ @dest = Addrinfo.ip('127.0.0.5')
+ @data = Socket::AncillaryData.ip_pktinfo(@source, 4, @dest)
+ end
+
+ it 'returns an Array' do
+ @data.ip_pktinfo.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ before do
+ @info = @data.ip_pktinfo
+ end
+
+ it 'stores an Addrinfo at index 0' do
+ @info[0].should be_an_instance_of(Addrinfo)
+ end
+
+ it 'stores the ifindex at index 1' do
+ @info[1].should be_kind_of(Integer)
+ end
+
+ it 'stores an Addrinfo at index 2' do
+ @info[2].should be_an_instance_of(Addrinfo)
+ end
+ end
+
+ describe 'the source Addrinfo' do
+ before do
+ @addr = @data.ip_pktinfo[0]
+ end
+
+ it 'uses the correct IP address' do
+ @addr.ip_address.should == '127.0.0.1'
+ end
+
+ it 'is not the same object as the input Addrinfo' do
+ @addr.should_not equal @source
+ end
+ end
+
+ describe 'the ifindex' do
+ it 'is an Integer' do
+ @data.ip_pktinfo[1].should == 4
+ end
+ end
+
+ describe 'the destination Addrinfo' do
+ before do
+ @addr = @data.ip_pktinfo[2]
+ end
+
+ it 'uses the correct IP address' do
+ @addr.ip_address.should == '127.0.0.5'
+ end
+
+ it 'is not the same object as the input Addrinfo' do
+ @addr.should_not equal @dest
+ end
+ end
+ end
+
+ describe 'using an Addrinfo with a port number' do
+ before do
+ @source = Addrinfo.tcp('127.0.0.1', 80)
+ @dest = Addrinfo.tcp('127.0.0.5', 85)
+ @data = Socket::AncillaryData.ip_pktinfo(@source, 4, @dest)
+ end
+
+ describe 'the source Addrinfo' do
+ before do
+ @addr = @data.ip_pktinfo[0]
+ end
+
+ it 'does not contain a port number' do
+ @addr.ip_port.should == 0
+ end
+ end
+
+ describe 'the destination Addrinfo' do
+ before do
+ @addr = @data.ip_pktinfo[2]
+ end
+
+ it 'does not contain a port number' do
+ @addr.ip_port.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb
new file mode 100644
index 0000000000..f70fe27d6a
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data, :ipv6_pktinfo do
+ describe 'Socket::AncillaryData#ipv6_pktinfo_addr' do
+ it 'returns an Addrinfo' do
+ data = Socket::AncillaryData.ipv6_pktinfo(Addrinfo.ip('::1'), 4)
+
+ data.ipv6_pktinfo_addr.should be_an_instance_of(Addrinfo)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb
new file mode 100644
index 0000000000..bda37eec98
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data, :ipv6_pktinfo do
+ describe 'Socket::AncillaryData#ipv6_pktinfo_ifindex' do
+ it 'returns an Addrinfo' do
+ data = Socket::AncillaryData.ipv6_pktinfo(Addrinfo.ip('::1'), 4)
+
+ data.ipv6_pktinfo_ifindex.should == 4
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb
new file mode 100644
index 0000000000..0fffc720dc
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb
@@ -0,0 +1,89 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data, :ipv6_pktinfo do
+ describe 'Socket::AncillaryData.ipv6_pktinfo' do
+ before do
+ @data = Socket::AncillaryData.ipv6_pktinfo(Addrinfo.ip('::1'), 4)
+ end
+
+ it 'returns a Socket::AncillaryData' do
+ @data.should be_an_instance_of(Socket::AncillaryData)
+ end
+
+ it 'sets the family to AF_INET' do
+ @data.family.should == Socket::AF_INET6
+ end
+
+ it 'sets the level to IPPROTO_IP' do
+ @data.level.should == Socket::IPPROTO_IPV6
+ end
+
+ it 'sets the type to IP_PKTINFO' do
+ @data.type.should == Socket::IPV6_PKTINFO
+ end
+ end
+
+ describe 'Socket::AncillaryData#ipv6_pktinfo' do
+ describe 'using an Addrinfo without a port number' do
+ before do
+ @source = Addrinfo.ip('::1')
+ @data = Socket::AncillaryData.ipv6_pktinfo(@source, 4)
+ end
+
+ it 'returns an Array' do
+ @data.ipv6_pktinfo.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ before do
+ @info = @data.ipv6_pktinfo
+ end
+
+ it 'stores an Addrinfo at index 0' do
+ @info[0].should be_an_instance_of(Addrinfo)
+ end
+
+ it 'stores the ifindex at index 1' do
+ @info[1].should be_kind_of(Integer)
+ end
+ end
+
+ describe 'the source Addrinfo' do
+ before do
+ @addr = @data.ipv6_pktinfo[0]
+ end
+
+ it 'uses the correct IP address' do
+ @addr.ip_address.should == '::1'
+ end
+
+ it 'is not the same object as the input Addrinfo' do
+ @addr.should_not equal @source
+ end
+ end
+
+ describe 'the ifindex' do
+ it 'is an Integer' do
+ @data.ipv6_pktinfo[1].should == 4
+ end
+ end
+ end
+
+ describe 'using an Addrinfo with a port number' do
+ before do
+ @source = Addrinfo.tcp('::1', 80)
+ @data = Socket::AncillaryData.ipv6_pktinfo(@source, 4)
+ end
+
+ describe 'the source Addrinfo' do
+ before do
+ @addr = @data.ipv6_pktinfo[0]
+ end
+
+ it 'does not contain a port number' do
+ @addr.ip_port.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/level_spec.rb b/spec/ruby/library/socket/ancillarydata/level_spec.rb
new file mode 100644
index 0000000000..a2ff216f9d
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/level_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data do
+ describe 'Socket::AncillaryData#level' do
+ it 'returns the level as an Integer' do
+ Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').level.should == Socket::SOL_SOCKET
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/type_spec.rb b/spec/ruby/library/socket/ancillarydata/type_spec.rb
new file mode 100644
index 0000000000..972beeeca0
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/type_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data do
+ describe 'Socket::AncillaryData#type' do
+ it 'returns the type as an Integer' do
+ Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb b/spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb
new file mode 100644
index 0000000000..65ffcb01af
--- /dev/null
+++ b/spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../spec_helper'
+
+with_feature :ancillary_data do
+ describe 'Socket::AncillaryData.unix_rights' do
+ describe 'using a list of IO objects' do
+ before do
+ @data = Socket::AncillaryData.unix_rights(STDOUT, STDERR)
+ end
+
+ it 'sets the family to AF_UNIX' do
+ @data.family.should == Socket::AF_UNIX
+ end
+
+ it 'sets the level to SOL_SOCKET' do
+ @data.level.should == Socket::SOL_SOCKET
+ end
+
+ it 'sets the type to SCM_RIGHTS' do
+ @data.type.should == Socket::SCM_RIGHTS
+ end
+
+ it 'sets the data to a String containing the file descriptors' do
+ @data.data.unpack('I*').should == [STDOUT.fileno, STDERR.fileno]
+ end
+ end
+
+ describe 'using non IO objects' do
+ it 'raises TypeError' do
+ -> { Socket::AncillaryData.unix_rights(10) }.should raise_error(TypeError)
+ end
+ end
+ end
+
+ describe 'Socket::AncillaryData#unix_rights' do
+ it 'returns the data as an Array of IO objects' do
+ data = Socket::AncillaryData.unix_rights(STDOUT, STDERR)
+
+ data.unix_rights.should == [STDOUT, STDERR]
+ end
+
+ it 'returns nil when the data is not a list of file descriptors' do
+ data = Socket::AncillaryData.new(:UNIX, :SOCKET, :RIGHTS, '')
+
+ data.unix_rights.should be_nil
+ end
+
+ it 'raises TypeError when the level is not SOL_SOCKET' do
+ data = Socket::AncillaryData.new(:INET, :IP, :RECVTTL, '')
+
+ -> { data.unix_rights }.should raise_error(TypeError)
+ end
+
+ platform_is_not :"solaris2.10", :aix do
+ it 'raises TypeError when the type is not SCM_RIGHTS' do
+ data = Socket::AncillaryData.new(:INET, :SOCKET, :TIMESTAMP, '')
+
+ -> { data.unix_rights }.should raise_error(TypeError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/close_read_spec.rb b/spec/ruby/library/socket/basicsocket/close_read_spec.rb
new file mode 100644
index 0000000000..f317b34955
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/close_read_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::BasicSocket#close_read" do
+ before :each do
+ @server = TCPServer.new(0)
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ end
+
+ it "closes the reading end of the socket" do
+ @server.close_read
+ -> { @server.read }.should raise_error(IOError)
+ end
+
+ it 'does not raise when called on a socket already closed for reading' do
+ @server.close_read
+ @server.close_read
+ -> { @server.read }.should raise_error(IOError)
+ end
+
+ it 'does not fully close the socket' do
+ @server.close_read
+ @server.closed?.should be_false
+ end
+
+ it "fully closes the socket if it was already closed for writing" do
+ @server.close_write
+ @server.close_read
+ @server.closed?.should be_true
+ end
+
+ it 'raises IOError when called on a fully closed socket' do
+ @server.close
+ -> { @server.close_read }.should raise_error(IOError)
+ end
+
+ it "returns nil" do
+ @server.close_read.should be_nil
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/close_write_spec.rb b/spec/ruby/library/socket/basicsocket/close_write_spec.rb
new file mode 100644
index 0000000000..232cfbb7c6
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/close_write_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::BasicSocket#close_write" do
+ before :each do
+ @server = TCPServer.new(0)
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ end
+
+ it "closes the writing end of the socket" do
+ @server.close_write
+ -> { @server.write("foo") }.should raise_error(IOError)
+ end
+
+ it 'does not raise when called on a socket already closed for writing' do
+ @server.close_write
+ @server.close_write
+ -> { @server.write("foo") }.should raise_error(IOError)
+ end
+
+ it 'does not fully close the socket' do
+ @server.close_write
+ @server.closed?.should be_false
+ end
+
+ it "does not prevent reading" do
+ @server.close_write
+ @server.read(0).should == ""
+ end
+
+ it "fully closes the socket if it was already closed for reading" do
+ @server.close_read
+ @server.close_write
+ @server.closed?.should be_true
+ end
+
+ it 'raises IOError when called on a fully closed socket' do
+ @server.close
+ -> { @server.close_write }.should raise_error(IOError)
+ end
+
+ it "returns nil" do
+ @server.close_write.should be_nil
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/connect_address_spec.rb b/spec/ruby/library/socket/basicsocket/connect_address_spec.rb
new file mode 100644
index 0000000000..1a1c9982d9
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/connect_address_spec.rb
@@ -0,0 +1,154 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Socket#connect_address' do
+ describe 'using an unbound socket' do
+ after do
+ @sock.close
+ end
+
+ it 'raises SocketError' do
+ @sock = Socket.new(:INET, :STREAM)
+
+ -> { @sock.connect_address }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using a socket bound to 0.0.0.0' do
+ before do
+ @sock = Socket.new(:INET, :STREAM)
+ @sock.bind(Socket.sockaddr_in(0, '0.0.0.0'))
+ end
+
+ after do
+ @sock.close
+ end
+
+ it 'returns an Addrinfo' do
+ @sock.connect_address.should be_an_instance_of(Addrinfo)
+ end
+
+ it 'uses 127.0.0.1 as the IP address' do
+ @sock.connect_address.ip_address.should == '127.0.0.1'
+ end
+
+ it 'uses the correct port number' do
+ @sock.connect_address.ip_port.should > 0
+ end
+
+ it 'uses AF_INET as the address family' do
+ @sock.connect_address.afamily.should == Socket::AF_INET
+ end
+
+ it 'uses PF_INET as the address family' do
+ @sock.connect_address.pfamily.should == Socket::PF_INET
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @sock.connect_address.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses 0 as the protocol' do
+ @sock.connect_address.protocol.should == 0
+ end
+ end
+
+ guard -> { SocketSpecs.ipv6_available? } do
+ describe 'using a socket bound to ::' do
+ before do
+ @sock = Socket.new(:INET6, :STREAM)
+ @sock.bind(Socket.sockaddr_in(0, '::'))
+ end
+
+ after do
+ @sock.close
+ end
+
+ it 'returns an Addrinfo' do
+ @sock.connect_address.should be_an_instance_of(Addrinfo)
+ end
+
+ it 'uses ::1 as the IP address' do
+ @sock.connect_address.ip_address.should == '::1'
+ end
+
+ it 'uses the correct port number' do
+ @sock.connect_address.ip_port.should > 0
+ end
+
+ it 'uses AF_INET6 as the address family' do
+ @sock.connect_address.afamily.should == Socket::AF_INET6
+ end
+
+ it 'uses PF_INET6 as the address family' do
+ @sock.connect_address.pfamily.should == Socket::PF_INET6
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @sock.connect_address.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses 0 as the protocol' do
+ @sock.connect_address.protocol.should == 0
+ end
+ end
+ end
+
+ with_feature :unix_socket do
+ platform_is_not :aix do
+ describe 'using an unbound UNIX socket' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ @client = UNIXSocket.new(@path)
+ end
+
+ after do
+ @client.close
+ @server.close
+ rm_r(@path)
+ end
+
+ it 'raises SocketError' do
+ -> { @client.connect_address }.should raise_error(SocketError)
+ end
+ end
+ end
+
+ describe 'using a bound UNIX socket' do
+ before do
+ @path = SocketSpecs.socket_path
+ @sock = UNIXServer.new(@path)
+ end
+
+ after do
+ @sock.close
+ rm_r(@path)
+ end
+
+ it 'returns an Addrinfo' do
+ @sock.connect_address.should be_an_instance_of(Addrinfo)
+ end
+
+ it 'uses the correct socket path' do
+ @sock.connect_address.unix_path.should == @path
+ end
+
+ it 'uses AF_UNIX as the address family' do
+ @sock.connect_address.afamily.should == Socket::AF_UNIX
+ end
+
+ it 'uses PF_UNIX as the protocol family' do
+ @sock.connect_address.pfamily.should == Socket::PF_UNIX
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @sock.connect_address.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses 0 as the protocol' do
+ @sock.connect_address.protocol.should == 0
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb b/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb
new file mode 100644
index 0000000000..a8800a8493
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb
@@ -0,0 +1,103 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "BasicSocket.do_not_reverse_lookup" do
+ before :each do
+ @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup
+ @server = TCPServer.new('127.0.0.1', 0)
+ @port = @server.addr[1]
+ @socket = TCPSocket.new('127.0.0.1', @port)
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ @socket.close unless @socket.closed?
+ BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup
+ end
+
+ it "defaults to true" do
+ BasicSocket.do_not_reverse_lookup.should be_true
+ end
+
+ it "causes 'peeraddr' to avoid name lookups" do
+ @socket.do_not_reverse_lookup = true
+ BasicSocket.do_not_reverse_lookup = true
+ @socket.peeraddr.should == ["AF_INET", @port, "127.0.0.1", "127.0.0.1"]
+ end
+
+ it "looks for hostnames when set to false" do
+ @socket.do_not_reverse_lookup = false
+ BasicSocket.do_not_reverse_lookup = false
+ @socket.peeraddr[2].should == SocketSpecs.hostname
+ end
+
+ it "looks for numeric addresses when set to true" do
+ @socket.do_not_reverse_lookup = true
+ BasicSocket.do_not_reverse_lookup = true
+ @socket.peeraddr[2].should == "127.0.0.1"
+ end
+end
+
+describe :socket_do_not_reverse_lookup, shared: true do
+ it "inherits from BasicSocket.do_not_reverse_lookup when the socket is created" do
+ @socket = @method.call
+ reverse = BasicSocket.do_not_reverse_lookup
+ @socket.do_not_reverse_lookup.should == reverse
+
+ BasicSocket.do_not_reverse_lookup = !reverse
+ @socket.do_not_reverse_lookup.should == reverse
+ end
+
+ it "is true when BasicSocket.do_not_reverse_lookup is true" do
+ BasicSocket.do_not_reverse_lookup = true
+ @socket = @method.call
+ @socket.do_not_reverse_lookup.should == true
+ end
+
+ it "is false when BasicSocket.do_not_reverse_lookup is false" do
+ BasicSocket.do_not_reverse_lookup = false
+ @socket = @method.call
+ @socket.do_not_reverse_lookup.should == false
+ end
+
+ it "can be changed with #do_not_reverse_lookup=" do
+ @socket = @method.call
+ reverse = @socket.do_not_reverse_lookup
+ @socket.do_not_reverse_lookup = !reverse
+ @socket.do_not_reverse_lookup.should == !reverse
+ end
+end
+
+describe "BasicSocket#do_not_reverse_lookup" do
+ before :each do
+ @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup
+ @server = TCPServer.new('127.0.0.1', 0)
+ @port = @server.addr[1]
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ @socket.close if @socket && !@socket.closed?
+ BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup
+ end
+
+ describe "for an TCPSocket.new socket" do
+ it_behaves_like :socket_do_not_reverse_lookup, -> {
+ TCPSocket.new('127.0.0.1', @port)
+ }
+ end
+
+ describe "for an TCPServer#accept socket" do
+ before :each do
+ @client = TCPSocket.new('127.0.0.1', @port)
+ end
+
+ after :each do
+ @client.close if @client && !@client.closed?
+ end
+
+ it_behaves_like :socket_do_not_reverse_lookup, -> {
+ @server.accept
+ }
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/for_fd_spec.rb b/spec/ruby/library/socket/basicsocket/for_fd_spec.rb
new file mode 100644
index 0000000000..9c9e6a8b55
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/for_fd_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "BasicSocket.for_fd" do
+ before :each do
+ @server = TCPServer.new(0)
+ @s2 = nil
+ end
+
+ after :each do
+ @socket1.close if @socket1
+ @server.close if @server
+ end
+
+ it "return a Socket instance wrapped around the descriptor" do
+ @s2 = TCPServer.for_fd(@server.fileno)
+ @s2.autoclose = false
+ @s2.should be_kind_of(TCPServer)
+ @s2.fileno.should == @server.fileno
+ end
+
+ it 'returns a new socket for a file descriptor' do
+ @socket1 = Socket.new(:INET, :DGRAM)
+ socket2 = Socket.for_fd(@socket1.fileno)
+ socket2.autoclose = false
+
+ socket2.should be_an_instance_of(Socket)
+ socket2.fileno.should == @socket1.fileno
+ end
+
+ it 'sets the socket into binary mode' do
+ @socket1 = Socket.new(:INET, :DGRAM)
+ socket2 = Socket.for_fd(@socket1.fileno)
+ socket2.autoclose = false
+
+ socket2.binmode?.should be_true
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb b/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb
new file mode 100644
index 0000000000..6179211d96
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'BasicSocket#getpeereid' do
+ with_feature :unix_socket do
+ describe 'using a UNIXSocket' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ @client = UNIXSocket.new(@path)
+ end
+
+ after do
+ @client.close
+ @server.close
+
+ rm_r(@path)
+ end
+
+ it 'returns an Array with the user and group ID' do
+ @client.getpeereid.should == [Process.euid, Process.egid]
+ end
+ end
+ end
+
+ describe 'using an IPSocket' do
+ after do
+ @sock.close
+ end
+
+ it 'raises NoMethodError' do
+ @sock = TCPServer.new('127.0.0.1', 0)
+ -> { @sock.getpeereid }.should raise_error(NoMethodError)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/getpeername_spec.rb b/spec/ruby/library/socket/basicsocket/getpeername_spec.rb
new file mode 100644
index 0000000000..0b93f02eef
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/getpeername_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::BasicSocket#getpeername" do
+
+ before :each do
+ @server = TCPServer.new("127.0.0.1", 0)
+ @port = @server.addr[1]
+ @client = TCPSocket.new("127.0.0.1", @port)
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ @client.close unless @client.closed?
+ end
+
+ it "returns the sockaddr of the other end of the connection" do
+ server_sockaddr = Socket.pack_sockaddr_in(@port, "127.0.0.1")
+ @client.getpeername.should == server_sockaddr
+ end
+
+ it 'raises Errno::ENOTCONN for a disconnected socket' do
+ -> { @server.getpeername }.should raise_error(Errno::ENOTCONN)
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/getsockname_spec.rb b/spec/ruby/library/socket/basicsocket/getsockname_spec.rb
new file mode 100644
index 0000000000..b33db088b6
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/getsockname_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::BasicSocket#getsockname" do
+ after :each do
+ @socket.closed?.should be_false
+ @socket.close
+ end
+
+ it "returns the sockaddr associated with the socket" do
+ @socket = TCPServer.new("127.0.0.1", 0)
+ sockaddr = Socket.unpack_sockaddr_in(@socket.getsockname)
+ sockaddr.should == [@socket.addr[1], "127.0.0.1"]
+ end
+
+ it "works on sockets listening in ipaddr_any" do
+ @socket = TCPServer.new(0)
+ sockaddr = Socket.unpack_sockaddr_in(@socket.getsockname)
+ ["::", "0.0.0.0", "::ffff:0.0.0.0"].include?(sockaddr[1]).should be_true
+ sockaddr[0].should == @socket.addr[1]
+ end
+
+ it 'returns a default socket address for a disconnected socket' do
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
+ sockaddr = Socket.unpack_sockaddr_in(@socket.getsockname)
+ sockaddr.should == [0, "0.0.0.0"]
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb b/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb
new file mode 100644
index 0000000000..ce65d6c92b
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb
@@ -0,0 +1,188 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "BasicSocket#getsockopt" do
+ before :each do
+ @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
+ end
+
+ after :each do
+ @sock.closed?.should be_false
+ @sock.close
+ end
+
+ platform_is_not :aix do
+ # A known bug in AIX. getsockopt(2) does not properly set
+ # the fifth argument for SO_TYPE, SO_OOBINLINE, SO_BROADCAST, etc.
+
+ it "gets a socket option Socket::SO_TYPE" do
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE).to_s
+ n.should == [Socket::SOCK_STREAM].pack("i")
+ end
+
+ it "gets a socket option Socket::SO_OOBINLINE" do
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should == [0].pack("i")
+ end
+ end
+
+ it "gets a socket option Socket::SO_LINGER" do
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s
+ if (n.size == 8) # linger struct on some platforms, not just a value
+ n.should == [0, 0].pack("ii")
+ else
+ n.should == [0].pack("i")
+ end
+ end
+
+ it "gets a socket option Socket::SO_SNDBUF" do
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s
+ n.unpack('i')[0].should > 0
+ end
+
+ it "raises a SystemCallError with an invalid socket option" do
+ -> { @sock.getsockopt Socket::SOL_SOCKET, -1 }.should raise_error(Errno::ENOPROTOOPT)
+ end
+
+ it 'returns a Socket::Option using a constant' do
+ opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE)
+
+ opt.should be_an_instance_of(Socket::Option)
+ end
+
+ it 'returns a Socket::Option for a boolean option' do
+ opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR)
+
+ opt.bool.should == false
+ end
+
+ it 'returns a Socket::Option for a numeric option' do
+ opt = @sock.getsockopt(Socket::IPPROTO_IP, Socket::IP_TTL)
+
+ opt.int.should be_kind_of(Integer)
+ end
+
+ it 'returns a Socket::Option for a struct option' do
+ opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER)
+
+ opt.linger.should == [false, 0]
+ end
+
+ it 'raises Errno::ENOPROTOOPT when requesting an invalid option' do
+ -> { @sock.getsockopt(Socket::SOL_SOCKET, -1) }.should raise_error(Errno::ENOPROTOOPT)
+ end
+
+ describe 'using Symbols as arguments' do
+ it 'returns a Socket::Option for arguments :SOCKET and :TYPE' do
+ opt = @sock.getsockopt(:SOCKET, :TYPE)
+
+ opt.level.should == Socket::SOL_SOCKET
+ opt.optname.should == Socket::SO_TYPE
+ end
+
+ it 'returns a Socket::Option for arguments :IP and :TTL' do
+ opt = @sock.getsockopt(:IP, :TTL)
+
+ opt.level.should == Socket::IPPROTO_IP
+ opt.optname.should == Socket::IP_TTL
+ end
+
+ it 'returns a Socket::Option for arguments :SOCKET and :REUSEADDR' do
+ opt = @sock.getsockopt(:SOCKET, :REUSEADDR)
+
+ opt.level.should == Socket::SOL_SOCKET
+ opt.optname.should == Socket::SO_REUSEADDR
+ end
+
+ it 'returns a Socket::Option for arguments :SOCKET and :LINGER' do
+ opt = @sock.getsockopt(:SOCKET, :LINGER)
+
+ opt.level.should == Socket::SOL_SOCKET
+ opt.optname.should == Socket::SO_LINGER
+ end
+
+ with_feature :udp_cork do
+ it 'returns a Socket::Option for arguments :UDP and :CORK' do
+ sock = Socket.new(:INET, :DGRAM)
+ begin
+ opt = sock.getsockopt(:UDP, :CORK)
+
+ opt.level.should == Socket::IPPROTO_UDP
+ opt.optname.should == Socket::UDP_CORK
+ ensure
+ sock.close
+ end
+ end
+ end
+ end
+
+ describe 'using Strings as arguments' do
+ it 'returns a Socket::Option for arguments "SOCKET" and "TYPE"' do
+ opt = @sock.getsockopt("SOCKET", "TYPE")
+
+ opt.level.should == Socket::SOL_SOCKET
+ opt.optname.should == Socket::SO_TYPE
+ end
+
+ it 'returns a Socket::Option for arguments "IP" and "TTL"' do
+ opt = @sock.getsockopt("IP", "TTL")
+
+ opt.level.should == Socket::IPPROTO_IP
+ opt.optname.should == Socket::IP_TTL
+ end
+
+ it 'returns a Socket::Option for arguments "SOCKET" and "REUSEADDR"' do
+ opt = @sock.getsockopt("SOCKET", "REUSEADDR")
+
+ opt.level.should == Socket::SOL_SOCKET
+ opt.optname.should == Socket::SO_REUSEADDR
+ end
+
+ it 'returns a Socket::Option for arguments "SOCKET" and "LINGER"' do
+ opt = @sock.getsockopt("SOCKET", "LINGER")
+
+ opt.level.should == Socket::SOL_SOCKET
+ opt.optname.should == Socket::SO_LINGER
+ end
+
+ with_feature :udp_cork do
+ it 'returns a Socket::Option for arguments "UDP" and "CORK"' do
+ sock = Socket.new("INET", "DGRAM")
+ begin
+ opt = sock.getsockopt("UDP", "CORK")
+
+ opt.level.should == Socket::IPPROTO_UDP
+ opt.optname.should == Socket::UDP_CORK
+ ensure
+ sock.close
+ end
+ end
+ end
+ end
+
+ describe 'using a String based option' do
+ it 'allows unpacking of a boolean option' do
+ opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR).to_s
+
+ opt.unpack('i').should == [0]
+ end
+
+ it 'allows unpacking of a numeric option' do
+ opt = @sock.getsockopt(Socket::IPPROTO_IP, Socket::IP_TTL).to_s
+ array = opt.unpack('i')
+
+ array[0].should be_kind_of(Integer)
+ array[0].should > 0
+ end
+
+ it 'allows unpacking of a struct option' do
+ opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s
+
+ if opt.bytesize == 8
+ opt.unpack('ii').should == [0, 0]
+ else
+ opt.unpack('i').should == [0]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/ioctl_spec.rb b/spec/ruby/library/socket/basicsocket/ioctl_spec.rb
new file mode 100644
index 0000000000..615d92bea8
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/ioctl_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../spec_helper'
+
+describe "Socket::BasicSocket#ioctl" do
+ platform_is :linux do
+ it "passes data from and to a String correctly" do
+ s = Socket.new Socket::AF_INET, Socket::SOCK_DGRAM, 0
+ # /usr/include/net/if.h, structure ifreq
+ # The structure is 32 bytes on x86, 40 bytes on x86_64
+ if_name = ['lo'].pack('a16')
+ buffer = if_name + 'z' * 24
+ # SIOCGIFADDR in /usr/include/bits/ioctls.h
+ s.ioctl 0x8915, buffer
+ s.close
+
+ # Interface name should remain unchanged.
+ buffer[0, 16].should == if_name
+ # lo should have an IPv4 address of 127.0.0.1
+ buffer[16, 2].unpack('S!').first.should == Socket::AF_INET
+ buffer[20, 4].should == "\x7f\0\0\x01"
+ end
+ end
+
+ platform_is :freebsd do
+ it "passes data from and to a String correctly" do
+ s = Socket.new Socket::AF_INET, Socket::SOCK_DGRAM, 0
+ # /usr/include/net/if.h, structure ifreq
+ # The structure is 32 bytes on x86, 40 bytes on x86_64
+ if_name = ['lo0'].pack('a16')
+ buffer = if_name + 'z' * 24
+ # SIOCGIFADDR in /usr/include/bits/ioctls.h
+ s.ioctl 0xc0206921, buffer
+ s.close
+
+ # Interface name should remain unchanged.
+ buffer[0, 16].should == if_name
+ # lo should have an IPv4 address of 127.0.0.1
+ buffer[16, 1].unpack('C').first.should == 16
+ buffer[17, 1].unpack('C').first.should == Socket::AF_INET
+ buffer[20, 4].should == "\x7f\0\0\x01"
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/local_address_spec.rb b/spec/ruby/library/socket/basicsocket/local_address_spec.rb
new file mode 100644
index 0000000000..0bd60a44cd
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/local_address_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../spec_helper'
+require_relative '../shared/address'
+
+describe 'BasicSocket#local_address' do
+ it_behaves_like :socket_local_remote_address, :local_address, -> socket {
+ a2 = BasicSocket.for_fd(socket.fileno)
+ a2.autoclose = false
+ a2.local_address
+ }
+end
diff --git a/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb
new file mode 100644
index 0000000000..df44a50afa
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "BasicSocket#read_nonblock" do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before :each do
+ @r = Socket.new(family, :DGRAM)
+ @w = Socket.new(family, :DGRAM)
+
+ @r.bind(Socket.pack_sockaddr_in(0, ip_address))
+ @w.send("aaa", 0, @r.getsockname)
+ end
+
+ after :each do
+ @r.close unless @r.closed?
+ @w.close unless @w.closed?
+ end
+
+ it "receives data after it's ready" do
+ IO.select([@r], nil, nil, 2)
+ @r.recv_nonblock(5).should == "aaa"
+ end
+
+ platform_is :linux do
+ it 'does not set the IO in nonblock mode' do
+ require 'io/nonblock'
+ @r.nonblock = false
+ IO.select([@r], nil, nil, 2)
+ @r.read_nonblock(3).should == "aaa"
+ @r.should_not.nonblock?
+ end
+ end
+
+ platform_is_not :linux, :windows do
+ it 'sets the IO in nonblock mode' do
+ require 'io/nonblock'
+ @r.nonblock = false
+ IO.select([@r], nil, nil, 2)
+ @r.read_nonblock(3).should == "aaa"
+ @r.should.nonblock?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb
new file mode 100644
index 0000000000..b6ab8a9cea
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::BasicSocket#recv_nonblock" do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before :each do
+ @s1 = Socket.new(family, :DGRAM)
+ @s2 = Socket.new(family, :DGRAM)
+ end
+
+ after :each do
+ @s1.close unless @s1.closed?
+ @s2.close unless @s2.closed?
+ end
+
+ platform_is_not :windows do
+ describe 'using an unbound socket' do
+ it 'raises an exception extending IO::WaitReadable' do
+ -> { @s1.recv_nonblock(1) }.should raise_error(IO::WaitReadable)
+ end
+ end
+ end
+
+ it "raises an exception extending IO::WaitReadable if there's no data available" do
+ @s1.bind(Socket.pack_sockaddr_in(0, ip_address))
+ -> {
+ @s1.recv_nonblock(5)
+ }.should raise_error(IO::WaitReadable) { |e|
+ platform_is_not :windows do
+ e.should be_kind_of(Errno::EAGAIN)
+ end
+ platform_is :windows do
+ e.should be_kind_of(Errno::EWOULDBLOCK)
+ end
+ }
+ end
+
+ it "returns :wait_readable with exception: false" do
+ @s1.bind(Socket.pack_sockaddr_in(0, ip_address))
+ @s1.recv_nonblock(5, exception: false).should == :wait_readable
+ end
+
+ it "receives data after it's ready" do
+ @s1.bind(Socket.pack_sockaddr_in(0, ip_address))
+ @s2.send("aaa", 0, @s1.getsockname)
+ IO.select([@s1], nil, nil, 2)
+ @s1.recv_nonblock(5).should == "aaa"
+ end
+
+ it "allows an output buffer as third argument" do
+ @s1.bind(Socket.pack_sockaddr_in(0, ip_address))
+ @s2.send("data", 0, @s1.getsockname)
+ IO.select([@s1], nil, nil, 2)
+
+ buf = "foo"
+ @s1.recv_nonblock(5, 0, buf)
+ buf.should == "data"
+ end
+
+ it "does not block if there's no data available" do
+ @s1.bind(Socket.pack_sockaddr_in(0, ip_address))
+ @s2.send("a", 0, @s1.getsockname)
+ IO.select([@s1], nil, nil, 2)
+ @s1.recv_nonblock(1).should == "a"
+ -> {
+ @s1.recv_nonblock(5)
+ }.should raise_error(IO::WaitReadable)
+ end
+ end
+
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'using a connected but not bound socket' do
+ before do
+ @server = Socket.new(family, :STREAM)
+ end
+
+ after do
+ @server.close
+ end
+
+ it "raises Errno::ENOTCONN" do
+ -> { @server.recv_nonblock(1) }.should raise_error { |e|
+ [Errno::ENOTCONN, Errno::EINVAL].should.include?(e.class)
+ }
+ -> { @server.recv_nonblock(1, exception: false) }.should raise_error { |e|
+ [Errno::ENOTCONN, Errno::EINVAL].should.include?(e.class)
+ }
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/recv_spec.rb b/spec/ruby/library/socket/basicsocket/recv_spec.rb
new file mode 100644
index 0000000000..b6ccda5d00
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/recv_spec.rb
@@ -0,0 +1,159 @@
+# -*- encoding: binary -*-
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "BasicSocket#recv" do
+
+ before :each do
+ @server = TCPServer.new('127.0.0.1', 0)
+ @port = @server.addr[1]
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ ScratchPad.clear
+ end
+
+ it "receives a specified number of bytes of a message from another socket" do
+ t = Thread.new do
+ client = @server.accept
+ ScratchPad.record client.recv(10)
+ client.recv(1) # this recv is important
+ client.close
+ end
+ Thread.pass while t.status and t.status != "sleep"
+ t.status.should_not be_nil
+
+ socket = TCPSocket.new('127.0.0.1', @port)
+ socket.send('hello', 0)
+ socket.close
+
+ t.join
+ ScratchPad.recorded.should == 'hello'
+ end
+
+ platform_is_not :solaris do
+ it "accepts flags to specify unusual receiving behaviour" do
+ t = Thread.new do
+ client = @server.accept
+
+ # in-band data (TCP), doesn't receive the flag.
+ ScratchPad.record client.recv(10)
+
+ # this recv is important (TODO: explain)
+ client.recv(10)
+ client.close
+ end
+ Thread.pass while t.status and t.status != "sleep"
+ t.status.should_not be_nil
+
+ socket = TCPSocket.new('127.0.0.1', @port)
+ socket.send('helloU', Socket::MSG_OOB)
+ socket.shutdown(1)
+ t.join
+ socket.close
+ ScratchPad.recorded.should == 'hello'
+ end
+ end
+
+ it "gets lines delimited with a custom separator" do
+ t = Thread.new do
+ client = @server.accept
+ ScratchPad.record client.gets("\377")
+
+ # this call is important (TODO: explain)
+ client.gets(nil)
+ client.close
+ end
+ Thread.pass while t.status and t.status != "sleep"
+ t.status.should_not be_nil
+
+ socket = TCPSocket.new('127.0.0.1', @port)
+ socket.write("firstline\377secondline\377")
+ socket.close
+
+ t.join
+ ScratchPad.recorded.should == "firstline\377"
+ end
+
+ it "allows an output buffer as third argument" do
+ socket = TCPSocket.new('127.0.0.1', @port)
+ socket.write("data")
+
+ client = @server.accept
+ buf = "foo"
+ begin
+ client.recv(4, 0, buf)
+ ensure
+ client.close
+ end
+ buf.should == "data"
+
+ socket.close
+ end
+end
+
+describe 'BasicSocket#recv' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :DGRAM)
+ @client = Socket.new(family, :DGRAM)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'using an unbound socket' do
+ it 'blocks the caller' do
+ -> { @server.recv(4) }.should block_caller
+ end
+ end
+
+ describe 'using a bound socket' do
+ before do
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ describe 'without any data available' do
+ it 'blocks the caller' do
+ -> { @server.recv(4) }.should block_caller
+ end
+ end
+
+ describe 'with data available' do
+ before do
+ @client.connect(@server.getsockname)
+ end
+
+ it 'reads the given amount of bytes' do
+ @client.write('hello')
+
+ @server.recv(2).should == 'he'
+ end
+
+ it 'reads the given amount of bytes when it exceeds the data size' do
+ @client.write('he')
+
+ @server.recv(6).should == 'he'
+ end
+
+ it 'blocks the caller when called twice without new data being available' do
+ @client.write('hello')
+
+ @server.recv(2).should == 'he'
+
+ -> { @server.recv(4) }.should block_caller
+ end
+
+ it 'takes a peek at the data when using the MSG_PEEK flag' do
+ @client.write('hello')
+
+ @server.recv(2, Socket::MSG_PEEK).should == 'he'
+ @server.recv(2).should == 'he'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb
new file mode 100644
index 0000000000..cc4275c417
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb
@@ -0,0 +1,224 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'BasicSocket#recvmsg_nonblock' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'using a disconnected socket' do
+ before do
+ @client = Socket.new(family, :DGRAM)
+ @server = Socket.new(family, :DGRAM)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ platform_is_not :windows do
+ describe 'using an unbound socket' do
+ it 'raises an exception extending IO::WaitReadable' do
+ -> { @server.recvmsg_nonblock }.should raise_error(IO::WaitReadable)
+ end
+ end
+ end
+
+ describe 'using a bound socket' do
+ before do
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ describe 'without any data available' do
+ it 'raises an exception extending IO::WaitReadable' do
+ -> { @server.recvmsg_nonblock }.should raise_error(IO::WaitReadable)
+ end
+
+ it 'returns :wait_readable with exception: false' do
+ @server.recvmsg_nonblock(exception: false).should == :wait_readable
+ end
+ end
+
+ describe 'with data available' do
+ before do
+ @client.connect(@server.getsockname)
+
+ @client.write('hello')
+
+ IO.select([@server], nil, nil, 5)
+ end
+
+ it 'returns an Array containing the data, an Addrinfo and the flags' do
+ @server.recvmsg_nonblock.should be_an_instance_of(Array)
+ end
+
+ describe 'without a maximum message length' do
+ it 'reads all the available data' do
+ @server.recvmsg_nonblock[0].should == 'hello'
+ end
+ end
+
+ describe 'with a maximum message length' do
+ platform_is_not :windows do
+ it 'reads up to the maximum amount of bytes' do
+ @server.recvmsg_nonblock(2)[0].should == 'he'
+ end
+ end
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = @server.recvmsg_nonblock
+ end
+
+ it 'stores the message at index 0' do
+ @array[0].should == 'hello'
+ end
+
+ it 'stores an Addrinfo at index 1' do
+ @array[1].should be_an_instance_of(Addrinfo)
+ end
+
+ platform_is_not :windows do
+ it 'stores the flags at index 2' do
+ @array[2].should be_kind_of(Integer)
+ end
+ end
+
+ describe 'the returned Addrinfo' do
+ before do
+ @addr = @array[1]
+ end
+
+ it 'uses the IP address of the client' do
+ @addr.ip_address.should == @client.local_address.ip_address
+ end
+
+ it 'uses the correct address family' do
+ @addr.afamily.should == family
+ end
+
+ it 'uses the correct protocol family' do
+ @addr.pfamily.should == family
+ end
+
+ it 'uses the correct socket type' do
+ @addr.socktype.should == Socket::SOCK_DGRAM
+ end
+
+ it 'uses the port number of the client' do
+ @addr.ip_port.should == @client.local_address.ip_port
+ end
+ end
+ end
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ describe 'using a connected but not bound socket' do
+ before do
+ @server = Socket.new(family, :STREAM)
+ end
+
+ after do
+ @server.close
+ end
+
+ it "raises Errno::ENOTCONN" do
+ -> { @server.recvmsg_nonblock }.should raise_error(Errno::ENOTCONN)
+ -> { @server.recvmsg_nonblock(exception: false) }.should raise_error(Errno::ENOTCONN)
+ end
+ end
+
+ describe 'using a connected socket' do
+ before do
+ @client = Socket.new(family, :STREAM)
+ @server = Socket.new(family, :STREAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ @server.listen(1)
+
+ @client.connect(@server.getsockname)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'without any data available' do
+ it 'raises IO::WaitReadable' do
+ -> {
+ socket, _ = @server.accept
+ begin
+ socket.recvmsg_nonblock
+ ensure
+ socket.close
+ end
+ }.should raise_error(IO::WaitReadable)
+ end
+ end
+
+ describe 'with data available' do
+ before do
+ @client.write('hello')
+
+ @socket, _ = @server.accept
+ IO.select([@socket])
+ end
+
+ after do
+ @socket.close
+ end
+
+ it 'returns an Array containing the data, an Addrinfo and the flags' do
+ @socket.recvmsg_nonblock.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = @socket.recvmsg_nonblock
+ end
+
+ it 'stores the message at index 0' do
+ @array[0].should == 'hello'
+ end
+
+ it 'stores an Addrinfo at index 1' do
+ @array[1].should be_an_instance_of(Addrinfo)
+ end
+
+ it 'stores the flags at index 2' do
+ @array[2].should be_kind_of(Integer)
+ end
+
+ describe 'the returned Addrinfo' do
+ before do
+ @addr = @array[1]
+ end
+
+ it 'raises when receiving the ip_address message' do
+ -> { @addr.ip_address }.should raise_error(SocketError)
+ end
+
+ it 'uses the correct address family' do
+ @addr.afamily.should == Socket::AF_UNSPEC
+ end
+
+ it 'uses 0 for the protocol family' do
+ @addr.pfamily.should == 0
+ end
+
+ it 'uses the correct socket type' do
+ @addr.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'raises when receiving the ip_port message' do
+ -> { @addr.ip_port }.should raise_error(SocketError)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb b/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb
new file mode 100644
index 0000000000..8063723701
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb
@@ -0,0 +1,197 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'BasicSocket#recvmsg' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'using a disconnected socket' do
+ before do
+ @client = Socket.new(family, :DGRAM)
+ @server = Socket.new(family, :DGRAM)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ platform_is_not :windows do
+ describe 'using an unbound socket' do
+ it 'blocks the caller' do
+ -> { @server.recvmsg }.should block_caller
+ end
+ end
+ end
+
+ describe 'using a bound socket' do
+ before do
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ describe 'without any data available' do
+ it 'blocks the caller' do
+ -> { @server.recvmsg }.should block_caller
+ end
+ end
+
+ describe 'with data available' do
+ before do
+ @client.connect(@server.getsockname)
+
+ @client.write('hello')
+ end
+
+ it 'returns an Array containing the data, an Addrinfo and the flags' do
+ @server.recvmsg.should be_an_instance_of(Array)
+ end
+
+ describe 'without a maximum message length' do
+ it 'reads all the available data' do
+ @server.recvmsg[0].should == 'hello'
+ end
+ end
+
+ describe 'with a maximum message length' do
+ it 'reads up to the maximum amount of bytes' do
+ @server.recvmsg(2)[0].should == 'he'
+ end
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = @server.recvmsg
+ end
+
+ it 'stores the message at index 0' do
+ @array[0].should == 'hello'
+ end
+
+ it 'stores an Addrinfo at index 1' do
+ @array[1].should be_an_instance_of(Addrinfo)
+ end
+
+ platform_is_not :windows do
+ it 'stores the flags at index 2' do
+ @array[2].should be_kind_of(Integer)
+ end
+ end
+
+ describe 'the returned Addrinfo' do
+ before do
+ @addr = @array[1]
+ end
+
+ it 'uses the IP address of the client' do
+ @addr.ip_address.should == @client.local_address.ip_address
+ end
+
+ it 'uses the correct address family' do
+ @addr.afamily.should == family
+ end
+
+ it 'uses the correct protocol family' do
+ @addr.pfamily.should == family
+ end
+
+ it 'uses the correct socket type' do
+ @addr.socktype.should == Socket::SOCK_DGRAM
+ end
+
+ it 'uses the port number of the client' do
+ @addr.ip_port.should == @client.local_address.ip_port
+ end
+ end
+ end
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ describe 'using a connected socket' do
+ before do
+ @client = Socket.new(family, :STREAM)
+ @server = Socket.new(family, :STREAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ @server.listen(1)
+
+ @client.connect(@server.getsockname)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'without any data available' do
+ it 'blocks the caller' do
+ socket, _ = @server.accept
+ begin
+ -> { socket.recvmsg }.should block_caller
+ ensure
+ socket.close
+ end
+ end
+ end
+
+ describe 'with data available' do
+ before do
+ @client.write('hello')
+ @socket, _ = @server.accept
+ end
+
+ after do
+ @socket.close
+ end
+
+ it 'returns an Array containing the data, an Addrinfo and the flags' do
+ @socket.recvmsg.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = @socket.recvmsg
+ end
+
+ it 'stores the message at index 0' do
+ @array[0].should == 'hello'
+ end
+
+ it 'stores an Addrinfo at index 1' do
+ @array[1].should be_an_instance_of(Addrinfo)
+ end
+
+ it 'stores the flags at index 2' do
+ @array[2].should be_kind_of(Integer)
+ end
+
+ describe 'the returned Addrinfo' do
+ before do
+ @addr = @array[1]
+ end
+
+ it 'raises when receiving the ip_address message' do
+ -> { @addr.ip_address }.should raise_error(SocketError)
+ end
+
+ it 'uses the correct address family' do
+ @addr.afamily.should == Socket::AF_UNSPEC
+ end
+
+ it 'returns 0 for the protocol family' do
+ @addr.pfamily.should == 0
+ end
+
+ it 'uses the correct socket type' do
+ @addr.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'raises when receiving the ip_port message' do
+ -> { @addr.ip_port }.should raise_error(SocketError)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/remote_address_spec.rb b/spec/ruby/library/socket/basicsocket/remote_address_spec.rb
new file mode 100644
index 0000000000..439bf31592
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/remote_address_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../spec_helper'
+require_relative '../shared/address'
+
+describe 'BasicSocket#remote_address' do
+ it_behaves_like :socket_local_remote_address, :remote_address, -> socket {
+ a2 = BasicSocket.for_fd(socket.fileno)
+ a2.autoclose = false
+ a2.remote_address
+ }
+end
diff --git a/spec/ruby/library/socket/basicsocket/send_spec.rb b/spec/ruby/library/socket/basicsocket/send_spec.rb
new file mode 100644
index 0000000000..868801df30
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/send_spec.rb
@@ -0,0 +1,220 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "BasicSocket#send" do
+ before :each do
+ @server = TCPServer.new('127.0.0.1', 0)
+ @port = @server.addr[1]
+ @socket = TCPSocket.new('127.0.0.1', @port)
+ end
+
+ after :each do
+ @server.closed?.should be_false
+ @socket.closed?.should be_false
+
+ @server.close
+ @socket.close
+ end
+
+ it "sends a message to another socket and returns the number of bytes sent" do
+ data = ""
+ t = Thread.new do
+ client = @server.accept
+ loop do
+ got = client.recv(5)
+ break if got.empty?
+ data << got
+ end
+ client.close
+ end
+ Thread.pass while t.status and t.status != "sleep"
+ t.status.should_not be_nil
+
+ @socket.send('hello', 0).should == 5
+ @socket.shutdown(1) # indicate, that we are done sending
+ @socket.recv(10)
+
+ t.join
+ data.should == 'hello'
+ end
+
+ platform_is_not :solaris, :windows do
+ it "accepts flags to specify unusual sending behaviour" do
+ data = nil
+ peek_data = nil
+ t = Thread.new do
+ client = @server.accept
+ peek_data = client.recv(6, Socket::MSG_PEEK)
+ data = client.recv(6)
+ client.recv(10) # this recv is important
+ client.close
+ end
+ Thread.pass while t.status and t.status != "sleep"
+ t.status.should_not be_nil
+
+ @socket.send('helloU', Socket::MSG_PEEK | Socket::MSG_OOB).should == 6
+ @socket.shutdown # indicate, that we are done sending
+
+ t.join
+ peek_data.should == "hello"
+ data.should == 'hello'
+ end
+ end
+
+ it "accepts a sockaddr as recipient address" do
+ data = ""
+ t = Thread.new do
+ client = @server.accept
+ loop do
+ got = client.recv(5)
+ break if got.empty?
+ data << got
+ end
+ client.close
+ end
+ Thread.pass while t.status and t.status != "sleep"
+ t.status.should_not be_nil
+
+ sockaddr = Socket.pack_sockaddr_in(@port, "127.0.0.1")
+ @socket.send('hello', 0, sockaddr).should == 5
+ @socket.shutdown # indicate, that we are done sending
+
+ t.join
+ data.should == 'hello'
+ end
+end
+
+describe 'BasicSocket#send' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'using a disconnected socket' do
+ before do
+ @client = Socket.new(family, :DGRAM)
+ @server = Socket.new(family, :DGRAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'with an object implementing #to_str' do
+ it 'returns the amount of sent bytes' do
+ data = mock('message')
+ data.should_receive(:to_str).and_return('hello')
+ @client.send(data, 0, @server.getsockname).should == 5
+ end
+ end
+
+ describe 'without a destination address' do
+ it "raises #{SocketSpecs.dest_addr_req_error}" do
+ -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error)
+ end
+ end
+
+ describe 'with a destination address as a String' do
+ it 'returns the amount of sent bytes' do
+ @client.send('hello', 0, @server.getsockname).should == 5
+ end
+
+ it 'does not persist the connection after writing to the socket' do
+ @client.send('hello', 0, @server.getsockname)
+
+ -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error)
+ end
+ end
+
+ describe 'with a destination address as an Addrinfo' do
+ it 'returns the amount of sent bytes' do
+ @client.send('hello', 0, @server.connect_address).should == 5
+ end
+ end
+ end
+
+ describe 'using a connected UDP socket' do
+ before do
+ @client = Socket.new(family, :DGRAM)
+ @server = Socket.new(family, :DGRAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'without a destination address argument' do
+ before do
+ @client.connect(@server.getsockname)
+ end
+
+ it 'returns the amount of bytes written' do
+ @client.send('hello', 0).should == 5
+ end
+ end
+
+ describe 'with a destination address argument' do
+ before do
+ @alt_server = Socket.new(family, :DGRAM)
+
+ @alt_server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @alt_server.close
+ end
+
+ it 'sends the message to the given address instead' do
+ @client.send('hello', 0, @alt_server.getsockname).should == 5
+
+ -> { @server.recv(5) }.should block_caller
+
+ @alt_server.recv(5).should == 'hello'
+ end
+
+ it 'does not persist the alternative connection after writing to the socket' do
+ @client.send('hello', 0, @alt_server.getsockname)
+
+ @client.connect(@server.getsockname)
+ @client.send('world', 0)
+
+ @server.recv(5).should == 'world'
+ end
+ end
+ end
+
+ platform_is_not :darwin, :windows do
+ describe 'using a connected TCP socket' do
+ before do
+ @client = Socket.new(family, :STREAM)
+ @server = Socket.new(family, :STREAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ @server.listen(1)
+
+ @client.connect(@server.getsockname)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'using the MSG_OOB flag' do
+ it 'sends an out-of-band message' do
+ socket, _ = @server.accept
+ socket.setsockopt(:SOCKET, :OOBINLINE, true)
+ @client.send('a', Socket::MSG_OOB).should == 1
+ begin
+ socket.recv(10).should == 'a'
+ ensure
+ socket.close
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb
new file mode 100644
index 0000000000..7acfc659bd
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'BasicSocket#sendmsg_nonblock' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'using a disconnected socket' do
+ before do
+ @client = Socket.new(family, :DGRAM)
+ @server = Socket.new(family, :DGRAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'without a destination address' do
+ it "raises #{SocketSpecs.dest_addr_req_error}" do
+ -> {
+ @client.sendmsg_nonblock('hello')
+ }.should raise_error(SocketSpecs.dest_addr_req_error)
+ -> {
+ @client.sendmsg_nonblock('hello', exception: false)
+ }.should raise_error(SocketSpecs.dest_addr_req_error)
+ end
+ end
+
+ describe 'with a destination address as a String' do
+ it 'returns the amount of sent bytes' do
+ @client.sendmsg_nonblock('hello', 0, @server.getsockname).should == 5
+ end
+ end
+
+ describe 'with a destination address as an Addrinfo' do
+ it 'returns the amount of sent bytes' do
+ @client.sendmsg_nonblock('hello', 0, @server.connect_address).should == 5
+ end
+ end
+ end
+
+ describe 'using a connected UDP socket' do
+ before do
+ @client = Socket.new(family, :DGRAM)
+ @server = Socket.new(family, :DGRAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'without a destination address argument' do
+ before do
+ @client.connect(@server.getsockname)
+ end
+
+ it 'returns the amount of bytes written' do
+ @client.sendmsg_nonblock('hello').should == 5
+ end
+ end
+
+ describe 'with a destination address argument' do
+ before do
+ @alt_server = Socket.new(family, :DGRAM)
+ @alt_server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @alt_server.close
+ end
+
+ it 'sends the message to the given address instead' do
+ @client.sendmsg_nonblock('hello', 0, @alt_server.getsockname).should == 5
+ -> { @server.recv(5) }.should block_caller
+ @alt_server.recv(5).should == 'hello'
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ describe 'using a connected TCP socket' do
+ before do
+ @client = Socket.new(family, :STREAM)
+ @server = Socket.new(family, :STREAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ @server.listen(1)
+
+ @client.connect(@server.getsockname)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'raises IO::WaitWritable when the underlying buffer is full' do
+ -> {
+ 10.times { @client.sendmsg_nonblock('hello' * 1_000_000) }
+ }.should raise_error(IO::WaitWritable)
+ end
+
+ it 'returns :wait_writable when the underlying buffer is full with exception: false' do
+ ret = nil
+ 10.times {
+ ret = @client.sendmsg_nonblock('hello' * 1_000_000, exception: false)
+ break unless ret.is_a?(Integer)
+ }
+ ret.should == :wait_writable
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/sendmsg_spec.rb b/spec/ruby/library/socket/basicsocket/sendmsg_spec.rb
new file mode 100644
index 0000000000..7ff336c0b7
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/sendmsg_spec.rb
@@ -0,0 +1,111 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'BasicSocket#sendmsg' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'using a disconnected socket' do
+ before do
+ @client = Socket.new(family, :DGRAM)
+ @server = Socket.new(family, :DGRAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ platform_is_not :windows do
+ describe 'without a destination address' do
+ it "raises #{SocketSpecs.dest_addr_req_error}" do
+ -> { @client.sendmsg('hello') }.should raise_error(SocketSpecs.dest_addr_req_error)
+ end
+ end
+ end
+
+ describe 'with a destination address as a String' do
+ it 'returns the amount of sent bytes' do
+ @client.sendmsg('hello', 0, @server.getsockname).should == 5
+ end
+ end
+
+ describe 'with a destination address as an Addrinfo' do
+ it 'returns the amount of sent bytes' do
+ @client.sendmsg('hello', 0, @server.connect_address).should == 5
+ end
+ end
+ end
+
+ describe 'using a connected UDP socket' do
+ before do
+ @client = Socket.new(family, :DGRAM)
+ @server = Socket.new(family, :DGRAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'without a destination address argument' do
+ before do
+ @client.connect(@server.getsockname)
+ end
+
+ it 'returns the amount of bytes written' do
+ @client.sendmsg('hello').should == 5
+ end
+ end
+
+ describe 'with a destination address argument' do
+ before do
+ @alt_server = Socket.new(family, :DGRAM)
+
+ @alt_server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @alt_server.close
+ end
+
+ it 'sends the message to the given address instead' do
+ @client.sendmsg('hello', 0, @alt_server.getsockname).should == 5
+
+ -> { @server.recv(5) }.should block_caller
+
+ @alt_server.recv(5).should == 'hello'
+ end
+ end
+ end
+
+ platform_is_not :windows do # spurious
+ describe 'using a connected TCP socket' do
+ before do
+ @client = Socket.new(family, :STREAM)
+ @server = Socket.new(family, :STREAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ @server.listen(1)
+
+ @client.connect(@server.getsockname)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'blocks when the underlying buffer is full' do
+ # Buffer sizes may differ per platform, so sadly this is the only
+ # reliable way of testing blocking behaviour.
+ -> do
+ 10.times { @client.sendmsg('hello' * 1_000_000) }
+ end.should block_caller
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb b/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb
new file mode 100644
index 0000000000..1e8d84e1c9
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb
@@ -0,0 +1,336 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "BasicSocket#setsockopt" do
+
+ before :each do
+ @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
+ end
+
+ after :each do
+ @sock.close unless @sock.closed?
+ end
+
+ it "sets the socket linger to 0" do
+ linger = [0, 0].pack("ii")
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, linger).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s
+
+ if (n.size == 8) # linger struct on some platforms, not just a value
+ n.should == [0, 0].pack("ii")
+ else
+ n.should == [0].pack("i")
+ end
+ end
+
+ it "sets the socket linger to some positive value" do
+ linger = [64, 64].pack("ii")
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, linger).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s
+ if (n.size == 8) # linger struct on some platforms, not just a value
+ a = n.unpack('ii')
+ a[0].should_not == 0
+ a[1].should == 64
+ else
+ n.should == [64].pack("i")
+ end
+ end
+
+ platform_is_not :windows do
+ it "raises EINVAL if passed wrong linger value" do
+ -> do
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, 0)
+ end.should raise_error(Errno::EINVAL)
+ end
+ end
+
+ platform_is_not :aix do
+ # A known bug in AIX. getsockopt(2) does not properly set
+ # the fifth argument for SO_TYPE, SO_OOBINLINE, SO_BROADCAST, etc.
+
+ it "sets the socket option Socket::SO_OOBINLINE" do
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, true).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should_not == [0].pack("i")
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, false).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should == [0].pack("i")
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 1).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should_not == [0].pack("i")
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 0).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should == [0].pack("i")
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 2).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should_not == [0].pack("i")
+
+ platform_is_not :windows do
+ -> {
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "")
+ }.should raise_error(SystemCallError)
+ end
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "blah").should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should_not == [0].pack("i")
+
+ platform_is_not :windows do
+ -> {
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "0")
+ }.should raise_error(SystemCallError)
+ end
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "\x00\x00\x00\x00").should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should == [0].pack("i")
+
+ platform_is_not :windows do
+ -> {
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "1")
+ }.should raise_error(SystemCallError)
+ end
+
+ platform_is_not :windows do
+ -> {
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "\x00\x00\x00")
+ }.should raise_error(SystemCallError)
+ end
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, [1].pack('i')).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should_not == [0].pack("i")
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, [0].pack('i')).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should == [0].pack("i")
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, [1000].pack('i')).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s
+ n.should_not == [0].pack("i")
+ end
+ end
+
+ it "sets the socket option Socket::SO_SNDBUF" do
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 4000).should == 0
+ sndbuf = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s
+ # might not always be possible to set to exact size
+ sndbuf.unpack('i')[0].should >= 4000
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, true).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s
+ n.unpack('i')[0].should >= 1
+
+ -> {
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, nil).should == 0
+ }.should raise_error(TypeError)
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 1).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s
+ n.unpack('i')[0].should >= 1
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 2).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s
+ n.unpack('i')[0].should >= 2
+
+ -> {
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "")
+ }.should raise_error(SystemCallError)
+
+ -> {
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "bla")
+ }.should raise_error(SystemCallError)
+
+ -> {
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "0")
+ }.should raise_error(SystemCallError)
+
+ -> {
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "1")
+ }.should raise_error(SystemCallError)
+
+ -> {
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "\x00\x00\x00")
+ }.should raise_error(SystemCallError)
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "\x00\x00\x01\x00").should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s
+ n.unpack('i')[0].should >= "\x00\x00\x01\x00".unpack('i')[0]
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, [4000].pack('i')).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s
+ n.unpack('i')[0].should >= 4000
+
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, [1000].pack('i')).should == 0
+ n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s
+ n.unpack('i')[0].should >= 1000
+ end
+
+ platform_is_not :aix do
+ describe 'accepts Socket::Option as argument' do
+ it 'boolean' do
+ option = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true)
+ @sock.setsockopt(option).should == 0
+ @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).bool.should == true
+ end
+
+ it 'int' do
+ option = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1)
+ @sock.setsockopt(option).should == 0
+ @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).bool.should == true
+ end
+ end
+ end
+
+ platform_is :aix do
+ describe 'accepts Socket::Option as argument' do
+ it 'boolean' do
+ option = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true)
+ @sock.setsockopt(option).should == 0
+ end
+
+ it 'int' do
+ option = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1)
+ @sock.setsockopt(option).should == 0
+ end
+ end
+ end
+
+ describe 'accepts Socket::Option as argument' do
+ it 'linger' do
+ option = Socket::Option.linger(true, 10)
+ @sock.setsockopt(option).should == 0
+ onoff, seconds = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).linger
+ seconds.should == 10
+ # Both results can be produced depending on the OS and value of Socket::SO_LINGER
+ [true, Socket::SO_LINGER].should include(onoff)
+ end
+ end
+end
+
+describe 'BasicSocket#setsockopt' do
+ describe 'using a STREAM socket' do
+ before do
+ @socket = Socket.new(:INET, :STREAM)
+ end
+
+ after do
+ @socket.close
+ end
+
+ describe 'using separate arguments with Symbols' do
+ it 'raises TypeError when the first argument is nil' do
+ -> { @socket.setsockopt(nil, :REUSEADDR, true) }.should raise_error(TypeError)
+ end
+
+ it 'sets a boolean option' do
+ @socket.setsockopt(:SOCKET, :REUSEADDR, true).should == 0
+ @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true
+ end
+
+ it 'sets an integer option' do
+ @socket.setsockopt(:IP, :TTL, 255).should == 0
+ @socket.getsockopt(:IP, :TTL).int.should == 255
+ end
+
+ guard -> { SocketSpecs.ipv6_available? } do
+ it 'sets an IPv6 boolean option' do
+ socket = Socket.new(:INET6, :STREAM)
+ begin
+ socket.setsockopt(:IPV6, :V6ONLY, true).should == 0
+ socket.getsockopt(:IPV6, :V6ONLY).bool.should == true
+ ensure
+ socket.close
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ it 'raises Errno::EINVAL when setting an invalid option value' do
+ -> { @socket.setsockopt(:SOCKET, :OOBINLINE, 'bla') }.should raise_error(Errno::EINVAL)
+ end
+ end
+ end
+
+ describe 'using separate arguments with Symbols' do
+ it 'sets a boolean option' do
+ @socket.setsockopt('SOCKET', 'REUSEADDR', true).should == 0
+ @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true
+ end
+
+ it 'sets an integer option' do
+ @socket.setsockopt('IP', 'TTL', 255).should == 0
+ @socket.getsockopt(:IP, :TTL).int.should == 255
+ end
+ end
+
+ describe 'using separate arguments with constants' do
+ it 'sets a boolean option' do
+ @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true).should == 0
+ @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true
+ end
+
+ it 'sets an integer option' do
+ @socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, 255).should == 0
+ @socket.getsockopt(:IP, :TTL).int.should == 255
+ end
+ end
+
+ describe 'using separate arguments with custom objects' do
+ it 'sets a boolean option' do
+ level = mock(:level)
+ name = mock(:name)
+
+ level.stub!(:to_str).and_return('SOCKET')
+ name.stub!(:to_str).and_return('REUSEADDR')
+
+ @socket.setsockopt(level, name, true).should == 0
+ end
+ end
+
+ describe 'using a Socket::Option as the first argument' do
+ it 'sets a boolean option' do
+ @socket.setsockopt(Socket::Option.bool(:INET, :SOCKET, :REUSEADDR, true)).should == 0
+ @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true
+ end
+
+ it 'sets an integer option' do
+ @socket.setsockopt(Socket::Option.int(:INET, :IP, :TTL, 255)).should == 0
+ @socket.getsockopt(:IP, :TTL).int.should == 255
+ end
+
+ it 'raises ArgumentError when passing 2 arguments' do
+ option = Socket::Option.bool(:INET, :SOCKET, :REUSEADDR, true)
+ -> { @socket.setsockopt(option, :REUSEADDR) }.should raise_error(ArgumentError)
+ end
+
+ it 'raises TypeError when passing 3 arguments' do
+ option = Socket::Option.bool(:INET, :SOCKET, :REUSEADDR, true)
+ -> { @socket.setsockopt(option, :REUSEADDR, true) }.should raise_error(TypeError)
+ end
+ end
+ end
+
+ with_feature :unix_socket do
+ describe 'using a UNIX socket' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ end
+
+ after do
+ @server.close
+ rm_r @path
+ end
+
+ it 'sets a boolean option' do
+ @server.setsockopt(:SOCKET, :REUSEADDR, true)
+ @server.getsockopt(:SOCKET, :REUSEADDR).bool.should == true
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/shutdown_spec.rb b/spec/ruby/library/socket/basicsocket/shutdown_spec.rb
new file mode 100644
index 0000000000..41d9581bde
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/shutdown_spec.rb
@@ -0,0 +1,155 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+platform_is_not :windows do # hangs
+ describe "Socket::BasicSocket#shutdown" do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :STREAM)
+ @client = Socket.new(family, :STREAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ @server.listen(1)
+
+ @client.connect(@server.getsockname)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'using an Integer' do
+ it 'shuts down a socket for reading' do
+ @client.shutdown(Socket::SHUT_RD)
+
+ @client.recv(1).should be_empty
+ end
+
+ it 'shuts down a socket for writing' do
+ @client.shutdown(Socket::SHUT_WR)
+
+ -> { @client.write('hello') }.should raise_error(Errno::EPIPE)
+ end
+
+ it 'shuts down a socket for reading and writing' do
+ @client.shutdown(Socket::SHUT_RDWR)
+
+ @client.recv(1).should be_empty
+
+ -> { @client.write('hello') }.should raise_error(Errno::EPIPE)
+ end
+
+ it 'raises ArgumentError when using an invalid option' do
+ -> { @server.shutdown(666) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe 'using a Symbol' do
+ it 'shuts down a socket for reading using :RD' do
+ @client.shutdown(:RD)
+
+ @client.recv(1).should be_empty
+ end
+
+ it 'shuts down a socket for reading using :SHUT_RD' do
+ @client.shutdown(:SHUT_RD)
+
+ @client.recv(1).should be_empty
+ end
+
+ it 'shuts down a socket for writing using :WR' do
+ @client.shutdown(:WR)
+
+ -> { @client.write('hello') }.should raise_error(Errno::EPIPE)
+ end
+
+ it 'shuts down a socket for writing using :SHUT_WR' do
+ @client.shutdown(:SHUT_WR)
+
+ -> { @client.write('hello') }.should raise_error(Errno::EPIPE)
+ end
+
+ it 'shuts down a socket for reading and writing' do
+ @client.shutdown(:RDWR)
+
+ @client.recv(1).should be_empty
+
+ -> { @client.write('hello') }.should raise_error(Errno::EPIPE)
+ end
+
+ it 'raises ArgumentError when using an invalid option' do
+ -> { @server.shutdown(:Nope) }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using a String' do
+ it 'shuts down a socket for reading using "RD"' do
+ @client.shutdown('RD')
+
+ @client.recv(1).should be_empty
+ end
+
+ it 'shuts down a socket for reading using "SHUT_RD"' do
+ @client.shutdown('SHUT_RD')
+
+ @client.recv(1).should be_empty
+ end
+
+ it 'shuts down a socket for writing using "WR"' do
+ @client.shutdown('WR')
+
+ -> { @client.write('hello') }.should raise_error(Errno::EPIPE)
+ end
+
+ it 'shuts down a socket for writing using "SHUT_WR"' do
+ @client.shutdown('SHUT_WR')
+
+ -> { @client.write('hello') }.should raise_error(Errno::EPIPE)
+ end
+
+ it 'raises ArgumentError when using an invalid option' do
+ -> { @server.shutdown('Nope') }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using an object that responds to #to_str' do
+ before do
+ @dummy = mock(:dummy)
+ end
+
+ it 'shuts down a socket for reading using "RD"' do
+ @dummy.stub!(:to_str).and_return('RD')
+
+ @client.shutdown(@dummy)
+
+ @client.recv(1).should be_empty
+ end
+
+ it 'shuts down a socket for reading using "SHUT_RD"' do
+ @dummy.stub!(:to_str).and_return('SHUT_RD')
+
+ @client.shutdown(@dummy)
+
+ @client.recv(1).should be_empty
+ end
+
+ it 'shuts down a socket for reading and writing' do
+ @dummy.stub!(:to_str).and_return('RDWR')
+
+ @client.shutdown(@dummy)
+
+ @client.recv(1).should be_empty
+
+ -> { @client.write('hello') }.should raise_error(Errno::EPIPE)
+ end
+ end
+
+ describe 'using an object that does not respond to #to_str' do
+ it 'raises TypeError' do
+ -> { @server.shutdown(mock(:dummy)) }.should raise_error(TypeError)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb
new file mode 100644
index 0000000000..523e732959
--- /dev/null
+++ b/spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "BasicSocket#write_nonblock" do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before :each do
+ @r = Socket.new(family, :DGRAM)
+ @w = Socket.new(family, :DGRAM)
+
+ @r.bind(Socket.pack_sockaddr_in(0, ip_address))
+ @w.connect(@r.getsockname)
+ end
+
+ after :each do
+ @r.close unless @r.closed?
+ @w.close unless @w.closed?
+ end
+
+ it "sends data" do
+ @w.write_nonblock("aaa").should == 3
+ IO.select([@r], nil, nil, 2)
+ @r.recv_nonblock(5).should == "aaa"
+ end
+
+ platform_is :linux do
+ it 'does not set the IO in nonblock mode' do
+ require 'io/nonblock'
+ @w.nonblock = false
+ @w.write_nonblock("aaa").should == 3
+ @w.should_not.nonblock?
+ end
+ end
+
+ platform_is_not :linux, :windows do
+ it 'sets the IO in nonblock mode' do
+ require 'io/nonblock'
+ @w.nonblock = false
+ @w.write_nonblock("aaa").should == 3
+ @w.should.nonblock?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/constants/constants_spec.rb b/spec/ruby/library/socket/constants/constants_spec.rb
new file mode 100644
index 0000000000..637bc6740a
--- /dev/null
+++ b/spec/ruby/library/socket/constants/constants_spec.rb
@@ -0,0 +1,108 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::Constants" do
+ it "defines socket types" do
+ consts = ["SOCK_DGRAM", "SOCK_RAW", "SOCK_RDM", "SOCK_SEQPACKET", "SOCK_STREAM"]
+ consts.each do |c|
+ Socket::Constants.should have_constant(c)
+ end
+ end
+
+ it "defines protocol families" do
+ consts = ["PF_INET6", "PF_INET", "PF_UNIX", "PF_UNSPEC"]
+ consts.each do |c|
+ Socket::Constants.should have_constant(c)
+ end
+ end
+
+ platform_is_not :aix do
+ it "defines PF_IPX protocol" do
+ Socket::Constants.should have_constant("PF_IPX")
+ end
+ end
+
+ it "defines address families" do
+ consts = ["AF_INET6", "AF_INET", "AF_UNIX", "AF_UNSPEC"]
+ consts.each do |c|
+ Socket::Constants.should have_constant(c)
+ end
+ end
+
+ platform_is_not :aix do
+ it "defines AF_IPX address" do
+ Socket::Constants.should have_constant("AF_IPX")
+ end
+ end
+
+ it "defines send/receive options" do
+ consts = ["MSG_DONTROUTE", "MSG_OOB", "MSG_PEEK"]
+ consts.each do |c|
+ Socket::Constants.should have_constant(c)
+ end
+ end
+
+ it "defines socket level options" do
+ consts = ["SOL_SOCKET"]
+ consts.each do |c|
+ Socket::Constants.should have_constant(c)
+ end
+ end
+
+ it "defines socket options" do
+ consts = ["SO_BROADCAST", "SO_DEBUG", "SO_DONTROUTE", "SO_ERROR", "SO_KEEPALIVE", "SO_LINGER",
+ "SO_OOBINLINE", "SO_RCVBUF", "SO_REUSEADDR", "SO_SNDBUF", "SO_TYPE"]
+ consts.each do |c|
+ Socket::Constants.should have_constant(c)
+ end
+ end
+
+ it "defines multicast options" do
+ consts = ["IP_ADD_MEMBERSHIP",
+ "IP_MULTICAST_LOOP", "IP_MULTICAST_TTL"]
+ platform_is_not :windows do
+ consts += ["IP_DEFAULT_MULTICAST_LOOP", "IP_DEFAULT_MULTICAST_TTL"]
+ end
+ consts.each do |c|
+ Socket::Constants.should have_constant(c)
+ end
+ end
+
+ platform_is_not :solaris, :windows, :aix, :android do
+ it "defines multicast options" do
+ consts = ["IP_MAX_MEMBERSHIPS"]
+ consts.each do |c|
+ Socket::Constants.should have_constant(c)
+ end
+ end
+ end
+
+ it "defines TCP options" do
+ consts = ["TCP_NODELAY"]
+ platform_is_not :windows do
+ consts << "TCP_MAXSEG"
+ end
+ consts.each do |c|
+ Socket::Constants.should have_constant(c)
+ end
+ end
+
+ platform_is_not :windows do
+ it 'defines SCM options' do
+ Socket::Constants.should have_constant('SCM_RIGHTS')
+ end
+
+ it 'defines error options' do
+ consts = ["EAI_ADDRFAMILY", "EAI_NODATA"]
+
+ # FreeBSD (11.1, at least) obsoletes EAI_ADDRFAMILY and EAI_NODATA
+ platform_is :freebsd do
+ consts = %w(EAI_MEMORY)
+ end
+
+ consts.each do |c|
+ Socket::Constants.should have_constant(c)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/fixtures/classes.rb b/spec/ruby/library/socket/fixtures/classes.rb
new file mode 100644
index 0000000000..4a590502ca
--- /dev/null
+++ b/spec/ruby/library/socket/fixtures/classes.rb
@@ -0,0 +1,164 @@
+require 'socket'
+
+module SocketSpecs
+ # helper to get the hostname associated to 127.0.0.1 or the given ip
+ def self.hostname(ip = "127.0.0.1")
+ # Calculate each time, without caching, since the result might
+ # depend on things like do_not_reverse_lookup mode, which is
+ # changing from test to test
+ Socket.getaddrinfo(ip, nil)[0][2]
+ end
+
+ def self.hostname_reverse_lookup(ip = "127.0.0.1")
+ Socket.getaddrinfo(ip, nil, 0, 0, 0, 0, true)[0][2]
+ end
+
+ def self.addr(which=:ipv4)
+ case which
+ when :ipv4
+ host = "127.0.0.1"
+ when :ipv6
+ host = "::1"
+ end
+ Socket.getaddrinfo(host, nil)[0][3]
+ end
+
+ def self.reserved_unused_port
+ # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
+ 0
+ end
+
+ def self.sockaddr_in(port, host)
+ Socket::SockAddr_In.new(Socket.sockaddr_in(port, host))
+ end
+
+ def self.socket_path
+ path = tmp("unix.sock", false)
+ # Check for too long unix socket path (max 104 bytes on macOS)
+ # Note that Linux accepts not null-terminated paths but the man page advises against it.
+ if path.bytesize > 104
+ path = "/tmp/unix_server_spec.socket"
+ end
+ rm_socket(path)
+ path
+ end
+
+ def self.rm_socket(path)
+ File.delete(path) if File.exist?(path)
+ end
+
+ def self.ipv6_available?
+ @ipv6_available ||= begin
+ server = TCPServer.new('::1', 0)
+ rescue Errno::EAFNOSUPPORT, Errno::EADDRNOTAVAIL, SocketError
+ :no
+ else
+ server.close
+ :yes
+ end
+ @ipv6_available == :yes
+ end
+
+ def self.each_ip_protocol
+ describe 'using IPv4' do
+ yield Socket::AF_INET, '127.0.0.1', 'AF_INET'
+ end
+
+ guard -> { SocketSpecs.ipv6_available? } do
+ describe 'using IPv6' do
+ yield Socket::AF_INET6, '::1', 'AF_INET6'
+ end
+ end
+ end
+
+ def self.loop_with_timeout(timeout = TIME_TOLERANCE)
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+
+ while yield == :retry
+ if Process.clock_gettime(Process::CLOCK_MONOTONIC) - start >= timeout
+ raise RuntimeError, "Did not succeed within #{timeout} seconds"
+ end
+ end
+ end
+
+ def self.dest_addr_req_error
+ error = Errno::EDESTADDRREQ
+ platform_is :windows do
+ error = Errno::ENOTCONN
+ end
+ error
+ end
+
+ # TCPServer echo server accepting one connection
+ class SpecTCPServer
+ attr_reader :hostname, :port
+
+ def initialize
+ @hostname = SocketSpecs.hostname
+ @server = TCPServer.new @hostname, 0
+ @port = @server.addr[1]
+
+ log "SpecTCPServer starting on #{@hostname}:#{@port}"
+
+ @thread = Thread.new do
+ socket = @server.accept
+ log "SpecTCPServer accepted connection: #{socket}"
+ service socket
+ end
+ end
+
+ def service(socket)
+ begin
+ data = socket.recv(1024)
+
+ return if data.empty?
+ log "SpecTCPServer received: #{data.inspect}"
+
+ return if data == "QUIT"
+
+ socket.send data, 0
+ ensure
+ socket.close
+ end
+ end
+
+ def shutdown
+ log "SpecTCPServer shutting down"
+ @thread.join
+ @server.close
+ end
+
+ def log(message)
+ @logger.puts message if @logger
+ end
+ end
+
+ # We need to find a free port for Socket.tcp_server_loop and Socket.udp_server_loop,
+ # and the only reliable way to do that is to pass 0 as the port, but then we need to
+ # find out which one was chosen and the API doesn't let us find what it is. So we
+ # intercept one of the public API methods called by these methods.
+ class ServerLoopPortFinder < Socket
+ def self.tcp_server_sockets(*args)
+ super(*args) { |sockets|
+ @port = sockets.first.local_address.ip_port
+ yield(sockets)
+ }
+ end
+
+ def self.udp_server_sockets(*args, &block)
+ super(*args) { |sockets|
+ @port = sockets.first.local_address.ip_port
+ yield(sockets)
+ }
+ end
+
+ def self.cleanup
+ @port = nil
+ end
+
+ def self.port
+ sleep 0.001 until @port
+ @port
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/fixtures/send_io.txt b/spec/ruby/library/socket/fixtures/send_io.txt
new file mode 100644
index 0000000000..eaaa1eb3ec
--- /dev/null
+++ b/spec/ruby/library/socket/fixtures/send_io.txt
@@ -0,0 +1 @@
+This data is magic.
diff --git a/spec/ruby/library/socket/ipsocket/addr_spec.rb b/spec/ruby/library/socket/ipsocket/addr_spec.rb
new file mode 100644
index 0000000000..199eb85ab7
--- /dev/null
+++ b/spec/ruby/library/socket/ipsocket/addr_spec.rb
@@ -0,0 +1,105 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::IPSocket#addr" do
+ before :each do
+ @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup
+ @socket = TCPServer.new("127.0.0.1", 0)
+ end
+
+ after :each do
+ @socket.close unless @socket.closed?
+ BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup
+ end
+
+ it "returns an array with the socket's information" do
+ @socket.do_not_reverse_lookup = false
+ BasicSocket.do_not_reverse_lookup = false
+ addrinfo = @socket.addr
+ addrinfo[0].should == "AF_INET"
+ addrinfo[1].should be_kind_of(Integer)
+ addrinfo[2].should == SocketSpecs.hostname
+ addrinfo[3].should == "127.0.0.1"
+ end
+
+ it "returns an address in the array if do_not_reverse_lookup is true" do
+ @socket.do_not_reverse_lookup = true
+ BasicSocket.do_not_reverse_lookup = true
+ addrinfo = @socket.addr
+ addrinfo[0].should == "AF_INET"
+ addrinfo[1].should be_kind_of(Integer)
+ addrinfo[2].should == "127.0.0.1"
+ addrinfo[3].should == "127.0.0.1"
+ end
+
+ it "returns an address in the array if passed false" do
+ addrinfo = @socket.addr(false)
+ addrinfo[0].should == "AF_INET"
+ addrinfo[1].should be_kind_of(Integer)
+ addrinfo[2].should == "127.0.0.1"
+ addrinfo[3].should == "127.0.0.1"
+ end
+end
+
+describe 'Socket::IPSocket#addr' do
+ SocketSpecs.each_ip_protocol do |family, ip_address, family_name|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ @port = @server.connect_address.ip_port
+ end
+
+ after do
+ @server.close
+ end
+
+ describe 'without reverse lookups' do
+ before do
+ @hostname = Socket.getaddrinfo(ip_address, nil)[0][2]
+ end
+
+ it 'returns an Array containing address information' do
+ @server.addr.should == [family_name, @port, @hostname, ip_address]
+ end
+ end
+
+ describe 'with reverse lookups' do
+ before do
+ @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2]
+ end
+
+ describe 'using true as the argument' do
+ it 'returns an Array containing address information' do
+ @server.addr(true).should == [family_name, @port, @hostname, ip_address]
+ end
+ end
+
+ describe 'using :hostname as the argument' do
+ it 'returns an Array containing address information' do
+ @server.addr(:hostname).should == [family_name, @port, @hostname, ip_address]
+ end
+ end
+
+ describe 'using :cats as the argument' do
+ it 'raises ArgumentError' do
+ -> { @server.addr(:cats) }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe 'with do_not_reverse_lookup disabled on socket level' do
+ before do
+ @server.do_not_reverse_lookup = false
+
+ @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2]
+ end
+
+ after do
+ @server.do_not_reverse_lookup = true
+ end
+
+ it 'returns an Array containing address information' do
+ @server.addr.should == [family_name, @port, @hostname, ip_address]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ipsocket/getaddress_spec.rb b/spec/ruby/library/socket/ipsocket/getaddress_spec.rb
new file mode 100644
index 0000000000..746d2ab86b
--- /dev/null
+++ b/spec/ruby/library/socket/ipsocket/getaddress_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::IPSocket#getaddress" do
+
+ it "returns the IP address of hostname" do
+ addr_local = IPSocket.getaddress(SocketSpecs.hostname)
+ ["127.0.0.1", "::1"].include?(addr_local).should == true
+ end
+
+ it "returns the IP address when passed an IP" do
+ IPSocket.getaddress("127.0.0.1").should == "127.0.0.1"
+ IPSocket.getaddress("0.0.0.0").should == "0.0.0.0"
+ IPSocket.getaddress('::1').should == '::1'
+ end
+
+ # There is no way to make this fail-proof on all machines, because
+ # DNS servers like opendns return A records for ANY host, including
+ # traditionally invalidly named ones.
+ it "raises an error on unknown hostnames" do
+ -> {
+ IPSocket.getaddress("rubyspecdoesntexist.fallingsnow.net")
+ }.should raise_error(SocketError)
+ end
+end
diff --git a/spec/ruby/library/socket/ipsocket/peeraddr_spec.rb b/spec/ruby/library/socket/ipsocket/peeraddr_spec.rb
new file mode 100644
index 0000000000..702650940b
--- /dev/null
+++ b/spec/ruby/library/socket/ipsocket/peeraddr_spec.rb
@@ -0,0 +1,117 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::IPSocket#peeraddr" do
+ before :each do
+ @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup
+ @server = TCPServer.new("127.0.0.1", 0)
+ @port = @server.addr[1]
+ @client = TCPSocket.new("127.0.0.1", @port)
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ @client.close unless @client.closed?
+ BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup
+ end
+
+ it "raises error if socket is not connected" do
+ -> {
+ @server.peeraddr
+ }.should raise_error(Errno::ENOTCONN)
+ end
+
+ it "returns an array of information on the peer" do
+ @client.do_not_reverse_lookup = false
+ BasicSocket.do_not_reverse_lookup = false
+ addrinfo = @client.peeraddr
+ addrinfo[0].should == "AF_INET"
+ addrinfo[1].should == @port
+ addrinfo[2].should == SocketSpecs.hostname
+ addrinfo[3].should == "127.0.0.1"
+ end
+
+ it "returns an IP instead of hostname if do_not_reverse_lookup is true" do
+ @client.do_not_reverse_lookup = true
+ BasicSocket.do_not_reverse_lookup = true
+ addrinfo = @client.peeraddr
+ addrinfo[0].should == "AF_INET"
+ addrinfo[1].should == @port
+ addrinfo[2].should == "127.0.0.1"
+ addrinfo[3].should == "127.0.0.1"
+ end
+
+ it "returns an IP instead of hostname if passed false" do
+ addrinfo = @client.peeraddr(false)
+ addrinfo[0].should == "AF_INET"
+ addrinfo[1].should == @port
+ addrinfo[2].should == "127.0.0.1"
+ addrinfo[3].should == "127.0.0.1"
+ end
+end
+
+describe 'Socket::IPSocket#peeraddr' do
+ SocketSpecs.each_ip_protocol do |family, ip_address, family_name|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ @port = @server.connect_address.ip_port
+ @client = TCPSocket.new(ip_address, @port)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'without reverse lookups' do
+ before do
+ @hostname = Socket.getaddrinfo(ip_address, nil)[0][2]
+ end
+
+ it 'returns an Array containing address information' do
+ @client.peeraddr.should == [family_name, @port, @hostname, ip_address]
+ end
+ end
+
+ describe 'with reverse lookups' do
+ before do
+ @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2]
+ end
+
+ describe 'using true as the argument' do
+ it 'returns an Array containing address information' do
+ @client.peeraddr(true).should == [family_name, @port, @hostname, ip_address]
+ end
+ end
+
+ describe 'using :hostname as the argument' do
+ it 'returns an Array containing address information' do
+ @client.peeraddr(:hostname).should == [family_name, @port, @hostname, ip_address]
+ end
+ end
+
+ describe 'using :cats as the argument' do
+ it 'raises ArgumentError' do
+ -> { @client.peeraddr(:cats) }.should raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe 'with do_not_reverse_lookup disabled on socket level' do
+ before do
+ @client.do_not_reverse_lookup = false
+
+ @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2]
+ @port = @client.local_address.ip_port
+ end
+
+ after do
+ @client.do_not_reverse_lookup = true
+ end
+
+ it 'returns an Array containing address information' do
+ @client.addr.should == [family_name, @port, @hostname, ip_address]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb b/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb
new file mode 100644
index 0000000000..2af86ea70d
--- /dev/null
+++ b/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb
@@ -0,0 +1,123 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::IPSocket#recvfrom" do
+ before :each do
+ @server = TCPServer.new("127.0.0.1", 0)
+ @port = @server.addr[1]
+ @client = TCPSocket.new("127.0.0.1", @port)
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ @client.close unless @client.closed?
+ end
+
+ it "reads data from the connection" do
+ data = nil
+ t = Thread.new do
+ client = @server.accept
+ begin
+ data = client.recvfrom(6)
+ ensure
+ client.close
+ end
+ end
+
+ @client.send('hello', 0)
+ @client.shutdown rescue nil
+ # shutdown may raise Errno::ENOTCONN when sent data is pending.
+ t.join
+
+ data.first.should == 'hello'
+ end
+
+ it "reads up to len bytes" do
+ data = nil
+ t = Thread.new do
+ client = @server.accept
+ begin
+ data = client.recvfrom(3)
+ ensure
+ client.close
+ end
+ end
+
+ @client.send('hello', 0)
+ @client.shutdown rescue nil
+ t.join
+
+ data.first.should == 'hel'
+ end
+
+ it "returns an array with the data and connection info" do
+ data = nil
+ t = Thread.new do
+ client = @server.accept
+ data = client.recvfrom(3)
+ client.close
+ end
+
+ @client.send('hello', 0)
+ @client.shutdown rescue nil
+ t.join
+
+ data.size.should == 2
+ data.first.should == "hel"
+ # This does not apply to every platform, dependent on recvfrom(2)
+ # data.last.should == nil
+ end
+end
+
+describe 'Socket::IPSocket#recvfrom' do
+ SocketSpecs.each_ip_protocol do |family, ip_address, family_name|
+ before do
+ @server = UDPSocket.new(family)
+ @client = UDPSocket.new(family)
+
+ @server.bind(ip_address, 0)
+ @client.connect(ip_address, @server.connect_address.ip_port)
+
+ @hostname = Socket.getaddrinfo(ip_address, nil)[0][2]
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'returns an Array containing up to N bytes and address information' do
+ @client.write('hello')
+
+ port = @client.local_address.ip_port
+ ret = @server.recvfrom(2)
+
+ ret.should == ['he', [family_name, port, @hostname, ip_address]]
+ end
+
+ it 'allows specifying of flags when receiving data' do
+ @client.write('hello')
+
+ @server.recvfrom(2, Socket::MSG_PEEK)[0].should == 'he'
+
+ @server.recvfrom(2)[0].should == 'he'
+ end
+
+ describe 'using reverse lookups' do
+ before do
+ @server.do_not_reverse_lookup = false
+
+ @hostname = Socket.getaddrinfo(ip_address, nil, 0, 0, 0, 0, true)[0][2]
+ end
+
+ it 'includes the hostname in the address Array' do
+ @client.write('hello')
+
+ port = @client.local_address.ip_port
+ ret = @server.recvfrom(2)
+
+ ret.should == ['he', [family_name, port, @hostname, ip_address]]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/option/bool_spec.rb b/spec/ruby/library/socket/option/bool_spec.rb
new file mode 100644
index 0000000000..144a78043d
--- /dev/null
+++ b/spec/ruby/library/socket/option/bool_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::Option.bool" do
+ it "creates a new Socket::Option" do
+ so = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true)
+ so.should be_an_instance_of(Socket::Option)
+ so.family.should == Socket::AF_INET
+ so.level.should == Socket::SOL_SOCKET
+ so.optname.should == Socket::SO_KEEPALIVE
+ so.data.should == [1].pack('i')
+ end
+end
+
+describe "Socket::Option#bool" do
+ it "returns boolean value" do
+ Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true).bool.should == true
+ Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, false).bool.should == false
+ end
+
+ platform_is_not :windows do
+ it 'raises TypeError when called on a non boolean option' do
+ opt = Socket::Option.linger(1, 4)
+ -> { opt.bool }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/option/initialize_spec.rb b/spec/ruby/library/socket/option/initialize_spec.rb
new file mode 100644
index 0000000000..8071ad7ef0
--- /dev/null
+++ b/spec/ruby/library/socket/option/initialize_spec.rb
@@ -0,0 +1,83 @@
+require_relative '../spec_helper'
+
+describe 'Socket::Option#initialize' do
+ before do
+ @bool = [0].pack('i')
+ end
+
+ describe 'using Integers' do
+ it 'returns a Socket::Option' do
+ opt = Socket::Option
+ .new(Socket::AF_INET, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, @bool)
+
+ opt.should be_an_instance_of(Socket::Option)
+
+ opt.family.should == Socket::AF_INET
+ opt.level.should == Socket::SOL_SOCKET
+ opt.optname.should == Socket::SO_KEEPALIVE
+ opt.data.should == @bool
+ end
+ end
+
+ describe 'using Symbols' do
+ it 'returns a Socket::Option' do
+ opt = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, @bool)
+
+ opt.should be_an_instance_of(Socket::Option)
+
+ opt.family.should == Socket::AF_INET
+ opt.level.should == Socket::SOL_SOCKET
+ opt.optname.should == Socket::SO_KEEPALIVE
+ opt.data.should == @bool
+ end
+
+ it 'raises when using an invalid address family' do
+ -> {
+ Socket::Option.new(:INET2, :SOCKET, :KEEPALIVE, @bool)
+ }.should raise_error(SocketError)
+ end
+
+ it 'raises when using an invalid level' do
+ -> {
+ Socket::Option.new(:INET, :CATS, :KEEPALIVE, @bool)
+ }.should raise_error(SocketError)
+ end
+
+ it 'raises when using an invalid option name' do
+ -> {
+ Socket::Option.new(:INET, :SOCKET, :CATS, @bool)
+ }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using Strings' do
+ it 'returns a Socket::Option' do
+ opt = Socket::Option.new('INET', 'SOCKET', 'KEEPALIVE', @bool)
+
+ opt.should be_an_instance_of(Socket::Option)
+
+ opt.family.should == Socket::AF_INET
+ opt.level.should == Socket::SOL_SOCKET
+ opt.optname.should == Socket::SO_KEEPALIVE
+ opt.data.should == @bool
+ end
+
+ it 'raises when using an invalid address family' do
+ -> {
+ Socket::Option.new('INET2', 'SOCKET', 'KEEPALIVE', @bool)
+ }.should raise_error(SocketError)
+ end
+
+ it 'raises when using an invalid level' do
+ -> {
+ Socket::Option.new('INET', 'CATS', 'KEEPALIVE', @bool)
+ }.should raise_error(SocketError)
+ end
+
+ it 'raises when using an invalid option name' do
+ -> {
+ Socket::Option.new('INET', 'SOCKET', 'CATS', @bool)
+ }.should raise_error(SocketError)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/option/inspect_spec.rb b/spec/ruby/library/socket/option/inspect_spec.rb
new file mode 100644
index 0000000000..ebea940d2f
--- /dev/null
+++ b/spec/ruby/library/socket/option/inspect_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+
+describe 'Socket::Option#inspect' do
+ it 'correctly returns SO_LINGER value' do
+ value = Socket::Option.linger(nil, 0).inspect
+ value.should == '#<Socket::Option: UNSPEC SOCKET LINGER off 0sec>'
+
+ value = Socket::Option.linger(false, 30).inspect
+ value.should == '#<Socket::Option: UNSPEC SOCKET LINGER off 30sec>'
+
+ value = Socket::Option.linger(true, 0).inspect
+ value.should == '#<Socket::Option: UNSPEC SOCKET LINGER on 0sec>'
+
+ value = Socket::Option.linger(true, 30).inspect
+ value.should == '#<Socket::Option: UNSPEC SOCKET LINGER on 30sec>'
+ end
+end
diff --git a/spec/ruby/library/socket/option/int_spec.rb b/spec/ruby/library/socket/option/int_spec.rb
new file mode 100644
index 0000000000..8c69ef6cbd
--- /dev/null
+++ b/spec/ruby/library/socket/option/int_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::Option.int" do
+ it "creates a new Socket::Option" do
+ so = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 5)
+ so.should be_an_instance_of(Socket::Option)
+ so.family.should == Socket::Constants::AF_INET
+ so.level.should == Socket::Constants::SOL_SOCKET
+ so.optname.should == Socket::Constants::SO_KEEPALIVE
+ so.data.should == [5].pack('i')
+ end
+
+ it 'returns a Socket::Option' do
+ opt = Socket::Option.int(:INET, :IP, :TTL, 4)
+
+ opt.should be_an_instance_of(Socket::Option)
+
+ opt.family.should == Socket::AF_INET
+ opt.level.should == Socket::IPPROTO_IP
+ opt.optname.should == Socket::IP_TTL
+ opt.data.should == [4].pack('i')
+ end
+end
+
+describe "Socket::Option#int" do
+ it "returns int value" do
+ so = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 17)
+ so.int.should == 17
+
+ so = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 32765)
+ so.int.should == 32765
+
+ Socket::Option.int(:INET, :IP, :TTL, 4).int.should == 4
+ end
+
+ platform_is_not :windows do
+ it 'raises TypeError when called on a non integer option' do
+ opt = Socket::Option.linger(1, 4)
+ -> { opt.int }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/option/linger_spec.rb b/spec/ruby/library/socket/option/linger_spec.rb
new file mode 100644
index 0000000000..ee987db85b
--- /dev/null
+++ b/spec/ruby/library/socket/option/linger_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+option_pack = 'i*'
+platform_is :windows do
+ option_pack = 's*'
+end
+
+describe "Socket::Option.linger" do
+ it "creates a new Socket::Option for SO_LINGER" do
+ so = Socket::Option.linger(1, 10)
+ so.should be_an_instance_of(Socket::Option)
+
+ so.family.should == Socket::Constants::AF_UNSPEC
+ so.level.should == Socket::Constants::SOL_SOCKET
+ so.optname.should == Socket::Constants::SO_LINGER
+
+ so.data.should == [1, 10].pack(option_pack)
+ end
+
+ it "accepts boolean as onoff argument" do
+ so = Socket::Option.linger(false, 0)
+ so.data.should == [0, 0].pack(option_pack)
+
+ so = Socket::Option.linger(true, 1)
+ so.data.should == [1, 1].pack(option_pack)
+ end
+end
+
+describe "Socket::Option#linger" do
+ it "returns linger option" do
+ so = Socket::Option.linger(0, 5)
+ ary = so.linger
+ ary[0].should be_false
+ ary[1].should == 5
+
+ so = Socket::Option.linger(false, 4)
+ ary = so.linger
+ ary[0].should be_false
+ ary[1].should == 4
+
+ so = Socket::Option.linger(1, 10)
+ ary = so.linger
+ ary[0].should be_true
+ ary[1].should == 10
+
+ so = Socket::Option.linger(true, 9)
+ ary = so.linger
+ ary[0].should be_true
+ ary[1].should == 9
+ end
+
+ it "raises TypeError if not a SO_LINGER" do
+ so = Socket::Option.int(:AF_UNSPEC, :SOL_SOCKET, :KEEPALIVE, 1)
+ -> { so.linger }.should raise_error(TypeError)
+ end
+
+ it 'raises TypeError when called on a non SOL_SOCKET/SO_LINGER option' do
+ opt = Socket::Option.int(:INET, :IP, :TTL, 4)
+
+ -> { opt.linger }.should raise_error(TypeError)
+ end
+
+ platform_is_not :windows do
+ it "raises TypeError if option has not good size" do
+ so = Socket::Option.int(:AF_UNSPEC, :SOL_SOCKET, :LINGER, 1)
+ -> { so.linger }.should raise_error(TypeError)
+ end
+ end
+
+ it 'raises TypeError when called on a non linger option' do
+ opt = Socket::Option.new(:INET, :SOCKET, :LINGER, '')
+
+ -> { opt.linger }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/socket/option/new_spec.rb b/spec/ruby/library/socket/option/new_spec.rb
new file mode 100644
index 0000000000..a9e6f09097
--- /dev/null
+++ b/spec/ruby/library/socket/option/new_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::Option.new" do
+ it "should accept integers" do
+ so = Socket::Option.new(Socket::AF_INET, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, [0].pack('i'))
+ so.family.should == Socket::AF_INET
+ so.level.should == Socket::SOL_SOCKET
+ so.optname.should == Socket::SO_KEEPALIVE
+ end
+
+ it "should accept symbols" do
+ so = Socket::Option.new(:AF_INET, :SOL_SOCKET, :SO_KEEPALIVE, [0].pack('i'))
+ so.family.should == Socket::AF_INET
+ so.level.should == Socket::SOL_SOCKET
+ so.optname.should == Socket::SO_KEEPALIVE
+
+ so = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [0].pack('i'))
+ so.family.should == Socket::AF_INET
+ so.level.should == Socket::SOL_SOCKET
+ so.optname.should == Socket::SO_KEEPALIVE
+ end
+
+ it "should raise error on unknown family" do
+ -> { Socket::Option.new(:INET4, :SOCKET, :KEEPALIVE, [0].pack('i')) }.should raise_error(SocketError)
+ end
+
+ it "should raise error on unknown level" do
+ -> { Socket::Option.new(:INET, :ROCKET, :KEEPALIVE, [0].pack('i')) }.should raise_error(SocketError)
+ end
+
+ it "should raise error on unknown option name" do
+ -> { Socket::Option.new(:INET, :SOCKET, :ALIVE, [0].pack('i')) }.should raise_error(SocketError)
+ end
+end
diff --git a/spec/ruby/library/socket/shared/address.rb b/spec/ruby/library/socket/shared/address.rb
new file mode 100644
index 0000000000..f3be9cfb99
--- /dev/null
+++ b/spec/ruby/library/socket/shared/address.rb
@@ -0,0 +1,249 @@
+require_relative '../fixtures/classes'
+
+describe :socket_local_remote_address, shared: true do
+ describe 'using TCPSocket' do
+ before :each do
+ @s = TCPServer.new('127.0.0.1', 0)
+ @a = TCPSocket.new('127.0.0.1', @s.addr[1])
+ @b = @s.accept
+ @addr = @object.call(@a)
+ end
+
+ after :each do
+ [@b, @a, @s].each(&:close)
+ end
+
+ it 'uses AF_INET as the address family' do
+ @addr.afamily.should == Socket::AF_INET
+ end
+
+ it 'uses PF_INET as the protocol family' do
+ @addr.pfamily.should == Socket::PF_INET
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @addr.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses the correct IP address' do
+ @addr.ip_address.should == '127.0.0.1'
+ end
+
+ it 'uses the correct port' do
+ if @method == :local_address
+ @addr.ip_port.should != @s.addr[1]
+ else
+ @addr.ip_port.should == @s.addr[1]
+ end
+ end
+
+ it 'equals address of peer socket' do
+ if @method == :local_address
+ @addr.to_s.should == @b.remote_address.to_s
+ else
+ @addr.to_s.should == @b.local_address.to_s
+ end
+ end
+
+ it 'returns an Addrinfo' do
+ @addr.should be_an_instance_of(Addrinfo)
+ end
+
+ it 'uses 0 as the protocol' do
+ @addr.protocol.should == 0
+ end
+
+ it 'can be used to connect to the server' do
+ skip if @method == :local_address
+ b = @addr.connect
+ begin
+ b.remote_address.to_s.should == @addr.to_s
+ ensure
+ b.close
+ end
+ end
+ end
+
+ guard -> { SocketSpecs.ipv6_available? } do
+ describe 'using IPv6' do
+ before :each do
+ @s = TCPServer.new('::1', 0)
+ @a = TCPSocket.new('::1', @s.addr[1])
+ @b = @s.accept
+ @addr = @object.call(@a)
+ end
+
+ after :each do
+ [@b, @a, @s].each(&:close)
+ end
+
+ it 'uses AF_INET6 as the address family' do
+ @addr.afamily.should == Socket::AF_INET6
+ end
+
+ it 'uses PF_INET6 as the protocol family' do
+ @addr.pfamily.should == Socket::PF_INET6
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @addr.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses the correct IP address' do
+ @addr.ip_address.should == '::1'
+ end
+
+ it 'uses the correct port' do
+ if @method == :local_address
+ @addr.ip_port.should != @s.addr[1]
+ else
+ @addr.ip_port.should == @s.addr[1]
+ end
+ end
+
+ it 'equals address of peer socket' do
+ if @method == :local_address
+ @addr.to_s.should == @b.remote_address.to_s
+ else
+ @addr.to_s.should == @b.local_address.to_s
+ end
+ end
+
+ it 'returns an Addrinfo' do
+ @addr.should be_an_instance_of(Addrinfo)
+ end
+
+ it 'uses 0 as the protocol' do
+ @addr.protocol.should == 0
+ end
+
+ it 'can be used to connect to the server' do
+ skip if @method == :local_address
+ b = @addr.connect
+ begin
+ b.remote_address.to_s.should == @addr.to_s
+ ensure
+ b.close
+ end
+ end
+ end
+ end
+
+ with_feature :unix_socket do
+ describe 'using UNIXSocket' do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @s = UNIXServer.new(@path)
+ @a = UNIXSocket.new(@path)
+ @b = @s.accept
+ @addr = @object.call(@a)
+ end
+
+ after :each do
+ [@b, @a, @s].each(&:close)
+ rm_r(@path)
+ end
+
+ it 'uses AF_UNIX as the address family' do
+ @addr.afamily.should == Socket::AF_UNIX
+ end
+
+ it 'uses PF_UNIX as the protocol family' do
+ @addr.pfamily.should == Socket::PF_UNIX
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @addr.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses the correct socket path' do
+ if @method == :local_address
+ @addr.unix_path.should == ""
+ else
+ @addr.unix_path.should == @path
+ end
+ end
+
+ it 'equals address of peer socket' do
+ if @method == :local_address
+ @addr.to_s.should == @b.remote_address.to_s
+ else
+ @addr.to_s.should == @b.local_address.to_s
+ end
+ end
+
+ it 'returns an Addrinfo' do
+ @addr.should be_an_instance_of(Addrinfo)
+ end
+
+ it 'uses 0 as the protocol' do
+ @addr.protocol.should == 0
+ end
+
+ it 'can be used to connect to the server' do
+ skip if @method == :local_address
+ b = @addr.connect
+ begin
+ b.remote_address.to_s.should == @addr.to_s
+ ensure
+ b.close
+ end
+ end
+ end
+ end
+
+ describe 'using UDPSocket' do
+ before :each do
+ @s = UDPSocket.new
+ @s.bind("127.0.0.1", 0)
+ @a = UDPSocket.new
+ @a.connect("127.0.0.1", @s.addr[1])
+ @addr = @object.call(@a)
+ end
+
+ after :each do
+ [@a, @s].each(&:close)
+ end
+
+ it 'uses the correct address family' do
+ @addr.afamily.should == Socket::AF_INET
+ end
+
+ it 'uses the correct protocol family' do
+ @addr.pfamily.should == Socket::PF_INET
+ end
+
+ it 'uses SOCK_DGRAM as the socket type' do
+ @addr.socktype.should == Socket::SOCK_DGRAM
+ end
+
+ it 'uses the correct IP address' do
+ @addr.ip_address.should == '127.0.0.1'
+ end
+
+ it 'uses the correct port' do
+ if @method == :local_address
+ @addr.ip_port.should != @s.addr[1]
+ else
+ @addr.ip_port.should == @s.addr[1]
+ end
+ end
+
+ it 'returns an Addrinfo' do
+ @addr.should be_an_instance_of(Addrinfo)
+ end
+
+ it 'uses 0 as the protocol' do
+ @addr.protocol.should == 0
+ end
+
+ it 'can be used to connect to the peer' do
+ b = @addr.connect
+ begin
+ b.remote_address.to_s.should == @addr.to_s
+ ensure
+ b.close
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/shared/pack_sockaddr.rb b/spec/ruby/library/socket/shared/pack_sockaddr.rb
new file mode 100644
index 0000000000..9f6238e7bc
--- /dev/null
+++ b/spec/ruby/library/socket/shared/pack_sockaddr.rb
@@ -0,0 +1,106 @@
+# coding: utf-8
+describe :socket_pack_sockaddr_in, shared: true do
+ it "packs and unpacks" do
+ sockaddr_in = Socket.public_send(@method, 0, nil)
+ port, addr = Socket.unpack_sockaddr_in(sockaddr_in)
+ ["127.0.0.1", "::1"].include?(addr).should == true
+ port.should == 0
+
+ sockaddr_in = Socket.public_send(@method, 0, '')
+ Socket.unpack_sockaddr_in(sockaddr_in).should == [0, '0.0.0.0']
+
+ sockaddr_in = Socket.public_send(@method, 80, '127.0.0.1')
+ Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '127.0.0.1']
+
+ sockaddr_in = Socket.public_send(@method, '80', '127.0.0.1')
+ Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '127.0.0.1']
+
+ sockaddr_in = Socket.public_send(@method, nil, '127.0.0.1')
+ Socket.unpack_sockaddr_in(sockaddr_in).should == [0, '127.0.0.1']
+ end
+
+ platform_is_not :solaris do
+ it 'resolves the service name to a port' do
+ sockaddr_in = Socket.public_send(@method, 'http', '127.0.0.1')
+ Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '127.0.0.1']
+ end
+ end
+
+ describe 'using an IPv4 address' do
+ it 'returns a String of 16 bytes' do
+ str = Socket.public_send(@method, 80, '127.0.0.1')
+
+ str.should be_an_instance_of(String)
+ str.bytesize.should == 16
+ end
+ end
+
+ platform_is_not :solaris do
+ describe 'using an IPv6 address' do
+ it 'returns a String of 28 bytes' do
+ str = Socket.public_send(@method, 80, '::1')
+
+ str.should be_an_instance_of(String)
+ str.bytesize.should == 28
+ end
+ end
+ end
+
+ platform_is :solaris do
+ describe 'using an IPv6 address' do
+ it 'returns a String of 32 bytes' do
+ str = Socket.public_send(@method, 80, '::1')
+
+ str.should be_an_instance_of(String)
+ str.bytesize.should == 32
+ end
+ end
+ end
+end
+
+describe :socket_pack_sockaddr_un, shared: true do
+ with_feature :unix_socket do
+ it 'should be idempotent' do
+ bytes = Socket.public_send(@method, '/tmp/foo').bytes
+ bytes[2..9].should == [47, 116, 109, 112, 47, 102, 111, 111]
+ bytes[10..-1].all?(&:zero?).should == true
+ end
+
+ it "packs and unpacks" do
+ sockaddr_un = Socket.public_send(@method, '/tmp/s')
+ Socket.unpack_sockaddr_un(sockaddr_un).should == '/tmp/s'
+ end
+
+ it "handles correctly paths with multibyte chars" do
+ sockaddr_un = Socket.public_send(@method, '/home/ваÑÑ/sock')
+ path = Socket.unpack_sockaddr_un(sockaddr_un).encode('UTF-8', 'UTF-8')
+ path.should == '/home/ваÑÑ/sock'
+ end
+ end
+
+ platform_is :linux do
+ it 'returns a String of 110 bytes' do
+ str = Socket.public_send(@method, '/tmp/test.sock')
+
+ str.should be_an_instance_of(String)
+ str.bytesize.should == 110
+ end
+ end
+
+ platform_is :bsd do
+ it 'returns a String of 106 bytes' do
+ str = Socket.public_send(@method, '/tmp/test.sock')
+
+ str.should be_an_instance_of(String)
+ str.bytesize.should == 106
+ end
+ end
+
+ platform_is_not :windows, :aix do
+ it "raises ArgumentError for paths that are too long" do
+ # AIX doesn't raise error
+ long_path = 'a' * 110
+ -> { Socket.public_send(@method, long_path) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/shared/partially_closable_sockets.rb b/spec/ruby/library/socket/shared/partially_closable_sockets.rb
new file mode 100644
index 0000000000..1bdff08bf6
--- /dev/null
+++ b/spec/ruby/library/socket/shared/partially_closable_sockets.rb
@@ -0,0 +1,13 @@
+describe "partially closable sockets", shared: true do
+ it "if the write end is closed then the other side can read past EOF without blocking" do
+ @s1.write("foo")
+ @s1.close_write
+ @s2.read("foo".size + 1).should == "foo"
+ end
+
+ it "closing the write end ensures that the other side can read until EOF" do
+ @s1.write("hello world")
+ @s1.close_write
+ @s2.read.should == "hello world"
+ end
+end
diff --git a/spec/ruby/library/socket/shared/socketpair.rb b/spec/ruby/library/socket/shared/socketpair.rb
new file mode 100644
index 0000000000..25146cfff6
--- /dev/null
+++ b/spec/ruby/library/socket/shared/socketpair.rb
@@ -0,0 +1,138 @@
+describe :socket_socketpair, shared: true do
+ platform_is_not :windows do
+ it "ensures the returned sockets are connected" do
+ s1, s2 = Socket.public_send(@method, Socket::AF_UNIX, 1, 0)
+ s1.puts("test")
+ s2.gets.should == "test\n"
+ s1.close
+ s2.close
+ end
+
+ it "responses with array of two sockets" do
+ begin
+ s1, s2 = Socket.public_send(@method, :UNIX, :STREAM)
+
+ s1.should be_an_instance_of(Socket)
+ s2.should be_an_instance_of(Socket)
+ ensure
+ s1.close
+ s2.close
+ end
+ end
+
+ describe 'using an Integer as the 1st and 2nd argument' do
+ it 'returns two Socket objects' do
+ s1, s2 = Socket.public_send(@method, Socket::AF_UNIX, Socket::SOCK_STREAM)
+
+ s1.should be_an_instance_of(Socket)
+ s2.should be_an_instance_of(Socket)
+ s1.close
+ s2.close
+ end
+ end
+
+ describe 'using a Symbol as the 1st and 2nd argument' do
+ it 'returns two Socket objects' do
+ s1, s2 = Socket.public_send(@method, :UNIX, :STREAM)
+
+ s1.should be_an_instance_of(Socket)
+ s2.should be_an_instance_of(Socket)
+ s1.close
+ s2.close
+ end
+
+ it 'raises SocketError for an unknown address family' do
+ -> { Socket.public_send(@method, :CATS, :STREAM) }.should raise_error(SocketError)
+ end
+
+ it 'raises SocketError for an unknown socket type' do
+ -> { Socket.public_send(@method, :UNIX, :CATS) }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using a String as the 1st and 2nd argument' do
+ it 'returns two Socket objects' do
+ s1, s2 = Socket.public_send(@method, 'UNIX', 'STREAM')
+
+ s1.should be_an_instance_of(Socket)
+ s2.should be_an_instance_of(Socket)
+ s1.close
+ s2.close
+ end
+
+ it 'raises SocketError for an unknown address family' do
+ -> { Socket.public_send(@method, 'CATS', 'STREAM') }.should raise_error(SocketError)
+ end
+
+ it 'raises SocketError for an unknown socket type' do
+ -> { Socket.public_send(@method, 'UNIX', 'CATS') }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'using an object that responds to #to_str as the 1st and 2nd argument' do
+ it 'returns two Socket objects' do
+ family = mock(:family)
+ type = mock(:type)
+
+ family.stub!(:to_str).and_return('UNIX')
+ type.stub!(:to_str).and_return('STREAM')
+
+ s1, s2 = Socket.public_send(@method, family, type)
+
+ s1.should be_an_instance_of(Socket)
+ s2.should be_an_instance_of(Socket)
+ s1.close
+ s2.close
+ end
+
+ it 'raises TypeError when #to_str does not return a String' do
+ family = mock(:family)
+ type = mock(:type)
+
+ family.stub!(:to_str).and_return(Socket::AF_UNIX)
+ type.stub!(:to_str).and_return(Socket::SOCK_STREAM)
+
+ -> { Socket.public_send(@method, family, type) }.should raise_error(TypeError)
+ end
+
+ it 'raises SocketError for an unknown address family' do
+ family = mock(:family)
+ type = mock(:type)
+
+ family.stub!(:to_str).and_return('CATS')
+ type.stub!(:to_str).and_return('STREAM')
+
+ -> { Socket.public_send(@method, family, type) }.should raise_error(SocketError)
+ end
+
+ it 'raises SocketError for an unknown socket type' do
+ family = mock(:family)
+ type = mock(:type)
+
+ family.stub!(:to_str).and_return('UNIX')
+ type.stub!(:to_str).and_return('CATS')
+
+ -> { Socket.public_send(@method, family, type) }.should raise_error(SocketError)
+ end
+ end
+
+ it 'accepts a custom protocol as an Integer as the 3rd argument' do
+ s1, s2 = Socket.public_send(@method, :UNIX, :STREAM, Socket::IPPROTO_IP)
+ s1.should be_an_instance_of(Socket)
+ s2.should be_an_instance_of(Socket)
+ s1.close
+ s2.close
+ end
+
+ it 'connects the returned Socket objects' do
+ s1, s2 = Socket.public_send(@method, :UNIX, :STREAM)
+ begin
+ s1.write('hello')
+ s2.recv(5).should == 'hello'
+ ensure
+ s1.close
+ s2.close
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/accept_loop_spec.rb b/spec/ruby/library/socket/socket/accept_loop_spec.rb
new file mode 100644
index 0000000000..78e8c3fa4a
--- /dev/null
+++ b/spec/ruby/library/socket/socket/accept_loop_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../spec_helper'
+
+describe 'Socket.accept_loop' do
+ before do
+ @server = Socket.new(:INET, :STREAM)
+ @client = Socket.new(:INET, :STREAM)
+
+ @server.bind(Socket.sockaddr_in(0, '127.0.0.1'))
+ @server.listen(1)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'using an Array of Sockets' do
+ describe 'without any available connections' do
+ # FIXME windows randomly hangs here forever
+ # https://ci.appveyor.com/project/ruby/ruby/builds/20817932/job/dor2ipny7ru4erpa
+ platform_is_not :windows do
+ it 'blocks the caller' do
+ -> { Socket.accept_loop([@server]) }.should block_caller
+ end
+ end
+ end
+
+ describe 'with available connections' do
+ before do
+ @client.connect(@server.getsockname)
+ end
+
+ it 'yields a Socket and an Addrinfo' do
+ conn = nil
+ addr = nil
+
+ Socket.accept_loop([@server]) do |connection, address|
+ conn = connection
+ addr = address
+ break
+ end
+
+ begin
+ conn.should be_an_instance_of(Socket)
+ addr.should be_an_instance_of(Addrinfo)
+ ensure
+ conn.close
+ end
+ end
+ end
+ end
+
+ describe 'using separate Socket arguments' do
+ describe 'without any available connections' do
+ it 'blocks the caller' do
+ -> { Socket.accept_loop(@server) }.should block_caller
+ end
+ end
+
+ describe 'with available connections' do
+ before do
+ @client.connect(@server.getsockname)
+ end
+
+ it 'yields a Socket and an Addrinfo' do
+ conn = nil
+ addr = nil
+
+ Socket.accept_loop(@server) do |connection, address|
+ conn = connection
+ addr = address
+ break
+ end
+
+ begin
+ conn.should be_an_instance_of(Socket)
+ addr.should be_an_instance_of(Addrinfo)
+ ensure
+ conn.close
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/accept_nonblock_spec.rb b/spec/ruby/library/socket/socket/accept_nonblock_spec.rb
new file mode 100644
index 0000000000..011622988c
--- /dev/null
+++ b/spec/ruby/library/socket/socket/accept_nonblock_spec.rb
@@ -0,0 +1,141 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket#accept_nonblock" do
+ before :each do
+ @hostname = "127.0.0.1"
+ @addr = Socket.sockaddr_in(0, @hostname)
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
+ @socket.bind(@addr)
+ @socket.listen(1)
+ end
+
+ after :each do
+ @socket.close
+ end
+
+ it "raises IO::WaitReadable if the connection is not accepted yet" do
+ -> {
+ @socket.accept_nonblock
+ }.should raise_error(IO::WaitReadable) { |e|
+ platform_is_not :windows do
+ e.should be_kind_of(Errno::EAGAIN)
+ end
+ platform_is :windows do
+ e.should be_kind_of(Errno::EWOULDBLOCK)
+ end
+ }
+ end
+
+ it 'returns :wait_readable in exceptionless mode' do
+ @socket.accept_nonblock(exception: false).should == :wait_readable
+ end
+end
+
+describe 'Socket#accept_nonblock' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :STREAM, 0)
+ @sockaddr = Socket.sockaddr_in(0, ip_address)
+ end
+
+ after do
+ @server.close unless @server.closed?
+ end
+
+ describe 'using an unbound socket' do
+ it 'raises Errno::EINVAL' do
+ -> { @server.accept_nonblock }.should raise_error(Errno::EINVAL)
+ -> { @server.accept_nonblock(exception: false) }.should raise_error(Errno::EINVAL)
+ end
+ end
+
+ describe "using a bound socket that's not listening" do
+ before do
+ @server.bind(@sockaddr)
+ end
+
+ it 'raises Errno::EINVAL' do
+ -> { @server.accept_nonblock }.should raise_error(Errno::EINVAL)
+ -> { @server.accept_nonblock(exception: false) }.should raise_error(Errno::EINVAL)
+ end
+ end
+
+ describe 'using a closed socket' do
+ it 'raises IOError' do
+ @server.close
+
+ -> { @server.accept_nonblock }.should raise_error(IOError)
+ -> { @server.accept_nonblock(exception: false) }.should raise_error(IOError)
+ end
+ end
+
+ describe "using a bound socket that's listening" do
+ before do
+ @server.bind(@sockaddr)
+ @server.listen(1)
+ end
+
+ describe 'without a connected client' do
+ it 'raises IO::WaitReadable' do
+ -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable)
+ end
+ end
+
+ platform_is_not :windows do
+ describe 'with a connected client' do
+ before do
+ addr = Socket.sockaddr_in(@server.local_address.ip_port, ip_address)
+ @client = Socket.new(family, :STREAM, 0)
+
+ @client.connect(addr)
+ end
+
+ after do
+ @socket.close if @socket
+ @client.close
+ end
+
+ it 'returns an Array containing a Socket and an Addrinfo' do
+ IO.select([@server])
+ @socket, addrinfo = @server.accept_nonblock
+
+ @socket.should be_an_instance_of(Socket)
+ addrinfo.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ before do
+ IO.select([@server])
+ @socket, @addr = @server.accept_nonblock
+ end
+
+ it 'uses AF_INET as the address family' do
+ @addr.afamily.should == family
+ end
+
+ it 'uses PF_INET as the protocol family' do
+ @addr.pfamily.should == family
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @addr.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses 0 as the protocol' do
+ @addr.protocol.should == 0
+ end
+
+ it 'uses the same IP address as the client Socket' do
+ @addr.ip_address.should == @client.local_address.ip_address
+ end
+
+ it 'uses the same port as the client Socket' do
+ @addr.ip_port.should == @client.local_address.ip_port
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/accept_spec.rb b/spec/ruby/library/socket/socket/accept_spec.rb
new file mode 100644
index 0000000000..417f996c55
--- /dev/null
+++ b/spec/ruby/library/socket/socket/accept_spec.rb
@@ -0,0 +1,121 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Socket#accept' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :STREAM, 0)
+ @sockaddr = Socket.sockaddr_in(0, ip_address)
+ end
+
+ after do
+ @server.close unless @server.closed?
+ end
+
+ platform_is :linux do # hangs on other platforms
+ describe 'using an unbound socket' do
+ it 'raises Errno::EINVAL' do
+ -> { @server.accept }.should raise_error(Errno::EINVAL)
+ end
+ end
+
+ describe "using a bound socket that's not listening" do
+ before do
+ @server.bind(@sockaddr)
+ end
+
+ it 'raises Errno::EINVAL' do
+ -> { @server.accept }.should raise_error(Errno::EINVAL)
+ end
+ end
+ end
+
+ describe 'using a closed socket' do
+ it 'raises IOError' do
+ @server.close
+
+ -> { @server.accept }.should raise_error(IOError)
+ end
+ end
+
+ describe "using a bound socket that's listening" do
+ before do
+ @server.bind(@sockaddr)
+ @server.listen(1)
+
+ server_ip = @server.local_address.ip_port
+ @server_addr = Socket.sockaddr_in(server_ip, ip_address)
+ end
+
+ describe 'without a connected client' do
+ it 'blocks the caller until a connection is available' do
+ client = Socket.new(family, :STREAM, 0)
+ thread = Thread.new do
+ @server.accept
+ end
+
+ client.connect(@server_addr)
+
+ value = thread.value
+ begin
+ value.should be_an_instance_of(Array)
+ ensure
+ client.close
+ value[0].close
+ end
+ end
+ end
+
+ describe 'with a connected client' do
+ before do
+ addr = Socket.sockaddr_in(@server.local_address.ip_port, ip_address)
+ @client = Socket.new(family, :STREAM, 0)
+
+ @client.connect(addr)
+ end
+
+ after do
+ @socket.close if @socket
+ @client.close
+ end
+
+ it 'returns an Array containing a Socket and an Addrinfo' do
+ @socket, addrinfo = @server.accept
+
+ @socket.should be_an_instance_of(Socket)
+ addrinfo.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ before do
+ @socket, @addr = @server.accept
+ end
+
+ it 'uses AF_INET as the address family' do
+ @addr.afamily.should == family
+ end
+
+ it 'uses PF_INET as the protocol family' do
+ @addr.pfamily.should == family
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @addr.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses 0 as the protocol' do
+ @addr.protocol.should == 0
+ end
+
+ it 'uses the same IP address as the client Socket' do
+ @addr.ip_address.should == @client.local_address.ip_address
+ end
+
+ it 'uses the same port as the client Socket' do
+ @addr.ip_port.should == @client.local_address.ip_port
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/bind_spec.rb b/spec/ruby/library/socket/socket/bind_spec.rb
new file mode 100644
index 0000000000..4465a3dafa
--- /dev/null
+++ b/spec/ruby/library/socket/socket/bind_spec.rb
@@ -0,0 +1,150 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket#bind on SOCK_DGRAM socket" do
+ before :each do
+ @sock = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0)
+ @sockaddr = Socket.pack_sockaddr_in(0, "127.0.0.1")
+ end
+
+ after :each do
+ @sock.closed?.should be_false
+ @sock.close
+ end
+
+ it "binds to a port" do
+ -> { @sock.bind(@sockaddr) }.should_not raise_error
+ end
+
+ it "returns 0 if successful" do
+ @sock.bind(@sockaddr).should == 0
+ end
+
+ it "raises Errno::EINVAL when already bound" do
+ @sock.bind(@sockaddr)
+
+ -> { @sock.bind(@sockaddr) }.should raise_error(Errno::EINVAL)
+ end
+
+ it "raises Errno::EADDRNOTAVAIL when the specified sockaddr is not available from the local machine" do
+ sockaddr1 = Socket.pack_sockaddr_in(0, "4.3.2.1")
+ -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EADDRNOTAVAIL)
+ end
+
+ platform_is_not :windows, :cygwin do
+ as_user do
+ break if File.read('/proc/sys/net/ipv4/ip_unprivileged_port_start').to_i <= 1 rescue nil
+ it "raises Errno::EACCES when the current user does not have permission to bind" do
+ sockaddr1 = Socket.pack_sockaddr_in(1, "127.0.0.1")
+ -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EACCES)
+ end
+ end
+ end
+end
+
+describe "Socket#bind on SOCK_STREAM socket" do
+ before :each do
+ @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
+ @sockaddr = Socket.pack_sockaddr_in(0, "127.0.0.1")
+ end
+
+ after :each do
+ @sock.closed?.should be_false
+ @sock.close
+ end
+
+ it "binds to a port" do
+ -> { @sock.bind(@sockaddr) }.should_not raise_error
+ end
+
+ it "returns 0 if successful" do
+ @sock.bind(@sockaddr).should == 0
+ end
+
+ it "raises Errno::EINVAL when already bound" do
+ @sock.bind(@sockaddr)
+
+ -> { @sock.bind(@sockaddr) }.should raise_error(Errno::EINVAL)
+ end
+
+ it "raises Errno::EADDRNOTAVAIL when the specified sockaddr is not available from the local machine" do
+ sockaddr1 = Socket.pack_sockaddr_in(0, "4.3.2.1")
+ -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EADDRNOTAVAIL)
+ end
+
+ platform_is_not :windows, :cygwin do
+ as_user do
+ break if File.read('/proc/sys/net/ipv4/ip_unprivileged_port_start').to_i <= 1 rescue nil
+ it "raises Errno::EACCES when the current user does not have permission to bind" do
+ sockaddr1 = Socket.pack_sockaddr_in(1, "127.0.0.1")
+ -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EACCES)
+ end
+ end
+ end
+end
+
+describe 'Socket#bind' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'using a packed socket address' do
+ before do
+ @socket = Socket.new(family, :DGRAM)
+ @sockaddr = Socket.sockaddr_in(0, ip_address)
+ end
+
+ after do
+ @socket.close
+ end
+
+ it 'returns 0 when successfully bound' do
+ @socket.bind(@sockaddr).should == 0
+ end
+
+ it 'raises Errno::EINVAL when binding to an already bound port' do
+ @socket.bind(@sockaddr)
+
+ -> { @socket.bind(@sockaddr) }.should raise_error(Errno::EINVAL)
+ end
+
+ it 'raises Errno::EADDRNOTAVAIL when the specified sockaddr is not available' do
+ ip = family == Socket::AF_INET ? '4.3.2.1' : '::2'
+ sockaddr1 = Socket.sockaddr_in(0, ip)
+
+ -> { @socket.bind(sockaddr1) }.should raise_error(Errno::EADDRNOTAVAIL)
+ end
+
+ platform_is_not :windows do
+ as_user do
+ break if File.read('/proc/sys/net/ipv4/ip_unprivileged_port_start').to_i <= 1 rescue nil
+
+ it 'raises Errno::EACCES when the user is not allowed to bind to the port' do
+ sockaddr1 = Socket.pack_sockaddr_in(1, ip_address)
+
+ -> { @socket.bind(sockaddr1) }.should raise_error(Errno::EACCES)
+ end
+ end
+ end
+ end
+
+ describe 'using an Addrinfo' do
+ before do
+ @addr = Addrinfo.udp(ip_address, 0)
+ @socket = Socket.new(@addr.afamily, @addr.socktype)
+ end
+
+ after do
+ @socket.close
+ end
+
+ it 'binds to an Addrinfo' do
+ @socket.bind(@addr).should == 0
+ @socket.local_address.should be_an_instance_of(Addrinfo)
+ end
+
+ it 'uses a new Addrinfo for the local address' do
+ @socket.bind(@addr)
+ @socket.local_address.should_not == @addr
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/connect_nonblock_spec.rb b/spec/ruby/library/socket/socket/connect_nonblock_spec.rb
new file mode 100644
index 0000000000..3cf667fc4a
--- /dev/null
+++ b/spec/ruby/library/socket/socket/connect_nonblock_spec.rb
@@ -0,0 +1,149 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket#connect_nonblock" do
+ before :each do
+ @hostname = "127.0.0.1"
+ @server = TCPServer.new(@hostname, 0) # started, but no accept
+ @addr = Socket.sockaddr_in(@server.addr[1], @hostname)
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
+ @thread = nil
+ end
+
+ after :each do
+ @socket.close
+ @server.close
+ @thread.join if @thread
+ end
+
+ platform_is_not :solaris do
+ it "connects the socket to the remote side" do
+ port = nil
+ accept = false
+ @thread = Thread.new do
+ server = TCPServer.new(@hostname, 0)
+ port = server.addr[1]
+ Thread.pass until accept
+ conn = server.accept
+ conn << "hello!"
+ conn.close
+ server.close
+ end
+
+ Thread.pass until port
+
+ addr = Socket.sockaddr_in(port, @hostname)
+ begin
+ @socket.connect_nonblock(addr)
+ rescue Errno::EINPROGRESS
+ end
+
+ accept = true
+ IO.select nil, [@socket]
+
+ begin
+ @socket.connect_nonblock(addr)
+ rescue Errno::EISCONN
+ # Not all OS's use this errno, so we trap and ignore it
+ end
+
+ @socket.read(6).should == "hello!"
+ end
+ end
+
+ platform_is_not :freebsd, :solaris, :aix do
+ it "raises Errno::EINPROGRESS when the connect would block" do
+ -> do
+ @socket.connect_nonblock(@addr)
+ end.should raise_error(Errno::EINPROGRESS)
+ end
+
+ it "raises Errno::EINPROGRESS with IO::WaitWritable mixed in when the connect would block" do
+ -> do
+ @socket.connect_nonblock(@addr)
+ end.should raise_error(IO::WaitWritable)
+ end
+
+ it "returns :wait_writable in exceptionless mode when the connect would block" do
+ @socket.connect_nonblock(@addr, exception: false).should == :wait_writable
+ end
+ end
+end
+
+describe 'Socket#connect_nonblock' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'using a DGRAM socket' do
+ before do
+ @server = Socket.new(family, :DGRAM)
+ @client = Socket.new(family, :DGRAM)
+ @sockaddr = Socket.sockaddr_in(0, ip_address)
+
+ @server.bind(@sockaddr)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'returns 0 when successfully connected using a String' do
+ @client.connect_nonblock(@server.getsockname).should == 0
+ end
+
+ it 'returns 0 when successfully connected using an Addrinfo' do
+ @client.connect_nonblock(@server.connect_address).should == 0
+ end
+
+ it 'raises TypeError when passed an Integer' do
+ -> { @client.connect_nonblock(666) }.should raise_error(TypeError)
+ end
+ end
+
+ describe 'using a STREAM socket' do
+ before do
+ @server = Socket.new(family, :STREAM)
+ @client = Socket.new(family, :STREAM)
+ @sockaddr = Socket.sockaddr_in(0, ip_address)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ platform_is_not :windows do
+ it 'raises Errno::EISCONN when already connected' do
+ @server.listen(1)
+ @client.connect(@server.connect_address).should == 0
+
+ -> {
+ @client.connect_nonblock(@server.connect_address)
+
+ # A second call needed if non-blocking sockets become default
+ # XXX honestly I don't expect any real code to care about this spec
+ # as it's too implementation-dependent and checking for connect()
+ # errors is futile anyways because of TOCTOU
+ @client.connect_nonblock(@server.connect_address)
+ }.should raise_error(Errno::EISCONN)
+ end
+
+ it 'returns 0 when already connected in exceptionless mode' do
+ @server.listen(1)
+ @client.connect(@server.connect_address).should == 0
+
+ @client.connect_nonblock(@server.connect_address, exception: false).should == 0
+ end
+ end
+
+ platform_is_not :freebsd, :solaris do
+ it 'raises IO:EINPROGRESSWaitWritable when the connection would block' do
+ @server.bind(@sockaddr)
+
+ -> {
+ @client.connect_nonblock(@server.connect_address)
+ }.should raise_error(IO::EINPROGRESSWaitWritable)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/connect_spec.rb b/spec/ruby/library/socket/socket/connect_spec.rb
new file mode 100644
index 0000000000..8653fba552
--- /dev/null
+++ b/spec/ruby/library/socket/socket/connect_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Socket#connect' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :STREAM)
+ @client = Socket.new(family, :STREAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'returns 0 when connected successfully using a String' do
+ @server.listen(1)
+
+ @client.connect(@server.getsockname).should == 0
+ end
+
+ it 'returns 0 when connected successfully using an Addrinfo' do
+ @server.listen(1)
+
+ @client.connect(@server.connect_address).should == 0
+ end
+
+ it 'raises Errno::EISCONN when already connected' do
+ @server.listen(1)
+
+ @client.connect(@server.getsockname).should == 0
+
+ -> {
+ @client.connect(@server.getsockname)
+
+ # A second call needed if non-blocking sockets become default
+ # XXX honestly I don't expect any real code to care about this spec
+ # as it's too implementation-dependent and checking for connect()
+ # errors is futile anyways because of TOCTOU
+ @client.connect(@server.getsockname)
+ }.should raise_error(Errno::EISCONN)
+ end
+
+ platform_is_not :darwin do
+ it 'raises Errno::ECONNREFUSED or Errno::ETIMEDOUT when the connection failed' do
+ begin
+ @client.connect(@server.getsockname)
+ rescue => e
+ [Errno::ECONNREFUSED, Errno::ETIMEDOUT].include?(e.class).should == true
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/for_fd_spec.rb b/spec/ruby/library/socket/socket/for_fd_spec.rb
new file mode 100644
index 0000000000..e89228d436
--- /dev/null
+++ b/spec/ruby/library/socket/socket/for_fd_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket.for_fd" do
+ before :each do
+ @server = TCPServer.new("127.0.0.1", 0)
+ @port = @server.addr[1]
+ @client = TCPSocket.open("127.0.0.1", @port)
+ end
+
+ after :each do
+ @socket.close
+ @client.close
+ @host.close
+ @server.close
+ end
+
+ it "creates a new Socket that aliases the existing Socket's file descriptor" do
+ @socket = Socket.for_fd(@client.fileno)
+ @socket.autoclose = false
+ @socket.fileno.should == @client.fileno
+
+ @socket.send("foo", 0)
+ @client.send("bar", 0)
+
+ @host = @server.accept
+ @host.read(3).should == "foo"
+ @host.read(3).should == "bar"
+ end
+end
diff --git a/spec/ruby/library/socket/socket/getaddrinfo_spec.rb b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb
new file mode 100644
index 0000000000..e0eff3cef4
--- /dev/null
+++ b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb
@@ -0,0 +1,373 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket.getaddrinfo" do
+ before :each do
+ @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup
+ BasicSocket.do_not_reverse_lookup = true
+ end
+
+ after :each do
+ BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup
+ end
+
+ platform_is_not :solaris, :windows do
+ it "gets the address information" do
+ expected = []
+ # The check for AP_INET6's class is needed because ipaddr.rb adds
+ # fake AP_INET6 even in case when IPv6 is not really supported.
+ # Without such check, this test might fail when ipaddr was required
+ # by some other specs.
+ if (Socket.constants.include? 'AF_INET6') &&
+ (Socket::AF_INET6.class != Object) then
+ expected.concat [
+ ['AF_INET6', 9, SocketSpecs.hostname, '::1', Socket::AF_INET6,
+ Socket::SOCK_DGRAM, Socket::IPPROTO_UDP],
+ ['AF_INET6', 9, SocketSpecs.hostname, '::1', Socket::AF_INET6,
+ Socket::SOCK_STREAM, Socket::IPPROTO_TCP],
+ ['AF_INET6', 9, SocketSpecs.hostname, 'fe80::1%lo0', Socket::AF_INET6,
+ Socket::SOCK_DGRAM, Socket::IPPROTO_UDP],
+ ['AF_INET6', 9, SocketSpecs.hostname, 'fe80::1%lo0', Socket::AF_INET6,
+ Socket::SOCK_STREAM, Socket::IPPROTO_TCP],
+ ]
+ end
+
+ expected.concat [
+ ['AF_INET', 9, SocketSpecs.hostname, '127.0.0.1', Socket::AF_INET,
+ Socket::SOCK_DGRAM, Socket::IPPROTO_UDP],
+ ['AF_INET', 9, SocketSpecs.hostname, '127.0.0.1', Socket::AF_INET,
+ Socket::SOCK_STREAM, Socket::IPPROTO_TCP],
+ ]
+
+ addrinfo = Socket.getaddrinfo SocketSpecs.hostname, 'discard'
+ addrinfo.each do |a|
+ case a.last
+ when Socket::IPPROTO_UDP, Socket::IPPROTO_TCP
+ expected.should include(a)
+ else
+ # don't check this. It's some weird protocol we don't know about
+ # so we can't spec it.
+ end
+ end
+ end
+
+ # #getaddrinfo will return a INADDR_ANY address (0.0.0.0 or "::")
+ # if it's a passive socket. In the case of non-passive
+ # sockets (AI_PASSIVE not set) it should return the loopback
+ # address (127.0.0.1 or "::1").
+
+ it "accepts empty addresses for IPv4 passive sockets" do
+ res = Socket.getaddrinfo(nil, "discard",
+ Socket::AF_INET,
+ Socket::SOCK_STREAM,
+ Socket::IPPROTO_TCP,
+ Socket::AI_PASSIVE)
+
+ expected = [["AF_INET", 9, "0.0.0.0", "0.0.0.0", Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP]]
+ res.should == expected
+ end
+
+ it "accepts empty addresses for IPv4 non-passive sockets" do
+ res = Socket.getaddrinfo(nil, "discard",
+ Socket::AF_INET,
+ Socket::SOCK_STREAM,
+ Socket::IPPROTO_TCP,
+ 0)
+
+ expected = [["AF_INET", 9, "127.0.0.1", "127.0.0.1", Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP]]
+ res.should == expected
+ end
+
+
+ it "accepts empty addresses for IPv6 passive sockets" do
+ res = Socket.getaddrinfo(nil, "discard",
+ Socket::AF_INET6,
+ Socket::SOCK_STREAM,
+ Socket::IPPROTO_TCP,
+ Socket::AI_PASSIVE)
+
+ expected = [
+ ["AF_INET6", 9, "::", "::", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP],
+ ["AF_INET6", 9, "0:0:0:0:0:0:0:0", "0:0:0:0:0:0:0:0", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP]
+ ]
+ res.each { |a| expected.should include(a) }
+ end
+
+ it "accepts empty addresses for IPv6 non-passive sockets" do
+ res = Socket.getaddrinfo(nil, "discard",
+ Socket::AF_INET6,
+ Socket::SOCK_STREAM,
+ Socket::IPPROTO_TCP,
+ 0)
+
+ expected = [
+ ["AF_INET6", 9, "::1", "::1", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP],
+ ["AF_INET6", 9, "0:0:0:0:0:0:0:1", "0:0:0:0:0:0:0:1", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP]
+ ]
+ res.each { |a| expected.should include(a) }
+ end
+ end
+end
+
+describe 'Socket.getaddrinfo' do
+ describe 'without global reverse lookups' do
+ it 'returns an Array' do
+ Socket.getaddrinfo(nil, 'ftp').should be_an_instance_of(Array)
+ end
+
+ it 'accepts an Integer as the address family' do
+ array = Socket.getaddrinfo(nil, 'ftp', Socket::AF_INET)[0]
+
+ array[0].should == 'AF_INET'
+ array[1].should == 21
+ array[2].should == '127.0.0.1'
+ array[3].should == '127.0.0.1'
+ array[4].should == Socket::AF_INET
+ array[5].should be_kind_of(Integer)
+ array[6].should be_kind_of(Integer)
+ end
+
+ it 'accepts an Integer as the address family using IPv6' do
+ array = Socket.getaddrinfo(nil, 'ftp', Socket::AF_INET6)[0]
+
+ array[0].should == 'AF_INET6'
+ array[1].should == 21
+ array[2].should == '::1'
+ array[3].should == '::1'
+ array[4].should == Socket::AF_INET6
+ array[5].should be_kind_of(Integer)
+ array[6].should be_kind_of(Integer)
+ end
+
+ it 'accepts a Symbol as the address family' do
+ array = Socket.getaddrinfo(nil, 'ftp', :INET)[0]
+
+ array[0].should == 'AF_INET'
+ array[1].should == 21
+ array[2].should == '127.0.0.1'
+ array[3].should == '127.0.0.1'
+ array[4].should == Socket::AF_INET
+ array[5].should be_kind_of(Integer)
+ array[6].should be_kind_of(Integer)
+ end
+
+ it 'accepts a Symbol as the address family using IPv6' do
+ array = Socket.getaddrinfo(nil, 'ftp', :INET6)[0]
+
+ array[0].should == 'AF_INET6'
+ array[1].should == 21
+ array[2].should == '::1'
+ array[3].should == '::1'
+ array[4].should == Socket::AF_INET6
+ array[5].should be_kind_of(Integer)
+ array[6].should be_kind_of(Integer)
+ end
+
+ it 'accepts a String as the address family' do
+ array = Socket.getaddrinfo(nil, 'ftp', 'INET')[0]
+
+ array[0].should == 'AF_INET'
+ array[1].should == 21
+ array[2].should == '127.0.0.1'
+ array[3].should == '127.0.0.1'
+ array[4].should == Socket::AF_INET
+ array[5].should be_kind_of(Integer)
+ array[6].should be_kind_of(Integer)
+ end
+
+ it 'accepts a String as the address family using IPv6' do
+ array = Socket.getaddrinfo(nil, 'ftp', 'INET6')[0]
+
+ array[0].should == 'AF_INET6'
+ array[1].should == 21
+ array[2].should == '::1'
+ array[3].should == '::1'
+ array[4].should == Socket::AF_INET6
+ array[5].should be_kind_of(Integer)
+ array[6].should be_kind_of(Integer)
+ end
+
+ it 'accepts an object responding to #to_str as the host' do
+ dummy = mock(:dummy)
+
+ dummy.stub!(:to_str).and_return('127.0.0.1')
+
+ array = Socket.getaddrinfo(dummy, 'ftp')[0]
+
+ array[0].should == 'AF_INET'
+ array[1].should == 21
+ array[2].should == '127.0.0.1'
+ array[3].should == '127.0.0.1'
+ array[4].should == Socket::AF_INET
+ array[5].should be_kind_of(Integer)
+ array[6].should be_kind_of(Integer)
+ end
+
+ it 'accepts an object responding to #to_str as the address family' do
+ dummy = mock(:dummy)
+
+ dummy.stub!(:to_str).and_return('INET')
+
+ array = Socket.getaddrinfo(nil, 'ftp', dummy)[0]
+
+ array[0].should == 'AF_INET'
+ array[1].should == 21
+ array[2].should == '127.0.0.1'
+ array[3].should == '127.0.0.1'
+ array[4].should == Socket::AF_INET
+ array[5].should be_kind_of(Integer)
+ array[6].should be_kind_of(Integer)
+ end
+
+ it 'accepts an Integer as the socket type' do
+ *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, Socket::SOCK_STREAM)[0]
+ array.should == [
+ 'AF_INET',
+ 21,
+ '127.0.0.1',
+ '127.0.0.1',
+ Socket::AF_INET,
+ Socket::SOCK_STREAM,
+ ]
+ [0, Socket::IPPROTO_TCP].should include(proto)
+ end
+
+ it 'accepts a Symbol as the socket type' do
+ *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM)[0]
+ array.should == [
+ 'AF_INET',
+ 21,
+ '127.0.0.1',
+ '127.0.0.1',
+ Socket::AF_INET,
+ Socket::SOCK_STREAM,
+ ]
+ [0, Socket::IPPROTO_TCP].should include(proto)
+ end
+
+ it 'accepts a String as the socket type' do
+ *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, 'STREAM')[0]
+ array.should == [
+ 'AF_INET',
+ 21,
+ '127.0.0.1',
+ '127.0.0.1',
+ Socket::AF_INET,
+ Socket::SOCK_STREAM,
+ ]
+ [0, Socket::IPPROTO_TCP].should include(proto)
+ end
+
+ it 'accepts an object responding to #to_str as the socket type' do
+ dummy = mock(:dummy)
+
+ dummy.stub!(:to_str).and_return('STREAM')
+
+ *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, dummy)[0]
+ array.should == [
+ 'AF_INET',
+ 21,
+ '127.0.0.1',
+ '127.0.0.1',
+ Socket::AF_INET,
+ Socket::SOCK_STREAM,
+ ]
+ [0, Socket::IPPROTO_TCP].should include(proto)
+ end
+
+ platform_is_not :windows do
+ it 'accepts an Integer as the protocol family' do
+ *array, proto = Socket.getaddrinfo(nil, 'discard', :INET, :DGRAM, Socket::IPPROTO_UDP)[0]
+ array.should == [
+ 'AF_INET',
+ 9,
+ '127.0.0.1',
+ '127.0.0.1',
+ Socket::AF_INET,
+ Socket::SOCK_DGRAM,
+ ]
+ [0, Socket::IPPROTO_UDP].should include(proto)
+ end
+ end
+
+ it 'accepts an Integer as the flags' do
+ *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM,
+ Socket::IPPROTO_TCP, Socket::AI_PASSIVE)[0]
+ array.should == [
+ 'AF_INET',
+ 21,
+ '0.0.0.0',
+ '0.0.0.0',
+ Socket::AF_INET,
+ Socket::SOCK_STREAM,
+ ]
+ [0, Socket::IPPROTO_TCP].should include(proto)
+ end
+
+ it 'performs a reverse lookup when the reverse_lookup argument is true' do
+ addr = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM,
+ Socket::IPPROTO_TCP, 0, true)[0]
+
+ addr[0].should == 'AF_INET'
+ addr[1].should == 21
+
+ addr[2].should be_an_instance_of(String)
+ addr[2].should_not == addr[3]
+
+ addr[3].should == '127.0.0.1'
+ end
+
+ it 'performs a reverse lookup when the reverse_lookup argument is :hostname' do
+ addr = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM,
+ Socket::IPPROTO_TCP, 0, :hostname)[0]
+
+ addr[0].should == 'AF_INET'
+ addr[1].should == 21
+
+ addr[2].should be_an_instance_of(String)
+ addr[2].should_not == addr[3]
+
+ addr[3].should == '127.0.0.1'
+ end
+
+ it 'performs a reverse lookup when the reverse_lookup argument is :numeric' do
+ *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM,
+ Socket::IPPROTO_TCP, 0, :numeric)[0]
+ array.should == [
+ 'AF_INET',
+ 21,
+ '127.0.0.1',
+ '127.0.0.1',
+ Socket::AF_INET,
+ Socket::SOCK_STREAM,
+ ]
+ [0, Socket::IPPROTO_TCP].should include(proto)
+ end
+ end
+
+ describe 'with global reverse lookups' do
+ before do
+ @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup
+ BasicSocket.do_not_reverse_lookup = false
+ end
+
+ after do
+ BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup
+ end
+
+ it 'returns an address honoring the global lookup option' do
+ addr = Socket.getaddrinfo(nil, 'ftp', :INET)[0]
+
+ addr[0].should == 'AF_INET'
+ addr[1].should == 21
+
+ # We don't have control over this value and there's no way to test this
+ # without relying on Socket.getaddrinfo()'s own behaviour (meaning this
+ # test would faily any way of the method was not implemented correctly).
+ addr[2].should be_an_instance_of(String)
+ addr[2].should_not == addr[3]
+
+ addr[3].should == '127.0.0.1'
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/gethostbyaddr_spec.rb b/spec/ruby/library/socket/socket/gethostbyaddr_spec.rb
new file mode 100644
index 0000000000..a4c8355520
--- /dev/null
+++ b/spec/ruby/library/socket/socket/gethostbyaddr_spec.rb
@@ -0,0 +1,124 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require 'ipaddr'
+
+describe 'Socket.gethostbyaddr' do
+ describe 'using an IPv4 address' do
+ before do
+ @addr = IPAddr.new('127.0.0.1').hton
+ end
+
+ describe 'without an explicit address family' do
+ it 'returns an Array' do
+ suppress_warning { Socket.gethostbyaddr(@addr) }.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = suppress_warning { Socket.gethostbyaddr(@addr) }
+ end
+
+ # RubyCI Solaris 11x defines 127.0.0.1 as unstable11x
+ platform_is_not :"solaris2.11" do
+ it 'includes the hostname as the first value' do
+ @array[0].should == SocketSpecs.hostname_reverse_lookup
+ end
+ end
+
+ it 'includes the aliases as the 2nd value' do
+ @array[1].should be_an_instance_of(Array)
+
+ @array[1].each do |val|
+ val.should be_an_instance_of(String)
+ end
+ end
+
+ it 'includes the address type as the 3rd value' do
+ @array[2].should == Socket::AF_INET
+ end
+
+ it 'includes all address strings as the remaining values' do
+ @array[3].should == @addr
+
+ @array[4..-1].each do |val|
+ val.should be_an_instance_of(String)
+ end
+ end
+ end
+ end
+
+ describe 'with an explicit address family' do
+ it 'returns an Array when using an Integer as the address family' do
+ suppress_warning { Socket.gethostbyaddr(@addr, Socket::AF_INET) }.should be_an_instance_of(Array)
+ end
+
+ it 'returns an Array when using a Symbol as the address family' do
+ suppress_warning { Socket.gethostbyaddr(@addr, :INET) }.should be_an_instance_of(Array)
+ end
+
+ it 'raises SocketError when the address is not supported by the family' do
+ -> { suppress_warning { Socket.gethostbyaddr(@addr, :INET6) } }.should raise_error(SocketError)
+ end
+ end
+ end
+
+ guard -> { SocketSpecs.ipv6_available? && platform_is_not(:aix) } do
+ describe 'using an IPv6 address' do
+ before do
+ @addr = IPAddr.new('::1').hton
+ end
+
+ describe 'without an explicit address family' do
+ it 'returns an Array' do
+ suppress_warning { Socket.gethostbyaddr(@addr) }.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = suppress_warning { Socket.gethostbyaddr(@addr) }
+ end
+
+ it 'includes the hostname as the first value' do
+ @array[0].should == SocketSpecs.hostname_reverse_lookup("::1")
+ end
+
+ it 'includes the aliases as the 2nd value' do
+ @array[1].should be_an_instance_of(Array)
+
+ @array[1].each do |val|
+ val.should be_an_instance_of(String)
+ end
+ end
+
+ it 'includes the address type as the 3rd value' do
+ @array[2].should == Socket::AF_INET6
+ end
+
+ it 'includes all address strings as the remaining values' do
+ @array[3].should be_an_instance_of(String)
+
+ @array[4..-1].each do |val|
+ val.should be_an_instance_of(String)
+ end
+ end
+ end
+ end
+
+ describe 'with an explicit address family' do
+ it 'returns an Array when using an Integer as the address family' do
+ suppress_warning { Socket.gethostbyaddr(@addr, Socket::AF_INET6) }.should be_an_instance_of(Array)
+ end
+
+ it 'returns an Array when using a Symbol as the address family' do
+ suppress_warning { Socket.gethostbyaddr(@addr, :INET6) }.should be_an_instance_of(Array)
+ end
+
+ platform_is_not :windows, :wsl do
+ it 'raises SocketError when the address is not supported by the family' do
+ -> { suppress_warning { Socket.gethostbyaddr(@addr, :INET) } }.should raise_error(SocketError)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/gethostbyname_spec.rb b/spec/ruby/library/socket/socket/gethostbyname_spec.rb
new file mode 100644
index 0000000000..0858e255e4
--- /dev/null
+++ b/spec/ruby/library/socket/socket/gethostbyname_spec.rb
@@ -0,0 +1,135 @@
+# -*- encoding: binary -*-
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket.gethostbyname" do
+ it "returns broadcast address info for '<broadcast>'" do
+ addr = suppress_warning { Socket.gethostbyname('<broadcast>') }
+ addr.should == ["255.255.255.255", [], 2, "\xFF\xFF\xFF\xFF"]
+ end
+
+ it "returns broadcast address info for '<any>'" do
+ addr = suppress_warning { Socket.gethostbyname('<any>') }
+ addr.should == ["0.0.0.0", [], 2, "\x00\x00\x00\x00"]
+ end
+end
+
+describe 'Socket.gethostbyname' do
+ it 'returns an Array' do
+ suppress_warning { Socket.gethostbyname('127.0.0.1') }.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = suppress_warning { Socket.gethostbyname('127.0.0.1') }
+ end
+
+ it 'includes the hostname as the first value' do
+ @array[0].should == '127.0.0.1'
+ end
+
+ it 'includes the aliases as the 2nd value' do
+ @array[1].should be_an_instance_of(Array)
+
+ @array[1].each do |val|
+ val.should be_an_instance_of(String)
+ end
+ end
+
+ it 'includes the address type as the 3rd value' do
+ possible = [Socket::AF_INET, Socket::AF_INET6]
+
+ possible.include?(@array[2]).should == true
+ end
+
+ it 'includes the address strings as the remaining values' do
+ @array[3].should be_an_instance_of(String)
+
+ @array[4..-1].each do |val|
+ val.should be_an_instance_of(String)
+ end
+ end
+ end
+
+ describe 'using <broadcast> as the input address' do
+ describe 'the returned Array' do
+ before do
+ @addr = suppress_warning { Socket.gethostbyname('<broadcast>') }
+ end
+
+ it 'includes the broadcast address as the first value' do
+ @addr[0].should == '255.255.255.255'
+ end
+
+ it 'includes the address type as the 3rd value' do
+ @addr[2].should == Socket::AF_INET
+ end
+
+ it 'includes the address string as the 4th value' do
+ @addr[3].should == [255, 255, 255, 255].pack('C4')
+ end
+ end
+ end
+
+ describe 'using <any> as the input address' do
+ describe 'the returned Array' do
+ before do
+ @addr = suppress_warning { Socket.gethostbyname('<any>') }
+ end
+
+ it 'includes the wildcard address as the first value' do
+ @addr[0].should == '0.0.0.0'
+ end
+
+ it 'includes the address type as the 3rd value' do
+ @addr[2].should == Socket::AF_INET
+ end
+
+ it 'includes the address string as the 4th value' do
+ @addr[3].should == [0, 0, 0, 0].pack('C4')
+ end
+ end
+ end
+
+ describe 'using an IPv4 address' do
+ describe 'the returned Array' do
+ before do
+ @addr = suppress_warning { Socket.gethostbyname('127.0.0.1') }
+ end
+
+ it 'includes the IP address as the first value' do
+ @addr[0].should == '127.0.0.1'
+ end
+
+ it 'includes the address type as the 3rd value' do
+ @addr[2].should == Socket::AF_INET
+ end
+
+ it 'includes the address string as the 4th value' do
+ @addr[3].should == [127, 0, 0, 1].pack('C4')
+ end
+ end
+ end
+
+ guard -> { SocketSpecs.ipv6_available? } do
+ describe 'using an IPv6 address' do
+ describe 'the returned Array' do
+ before do
+ @addr = suppress_warning { Socket.gethostbyname('::1') }
+ end
+
+ it 'includes the IP address as the first value' do
+ @addr[0].should == '::1'
+ end
+
+ it 'includes the address type as the 3rd value' do
+ @addr[2].should == Socket::AF_INET6
+ end
+
+ it 'includes the address string as the 4th value' do
+ @addr[3].should == [0, 0, 0, 0, 0, 0, 0, 1].pack('n8')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/gethostname_spec.rb b/spec/ruby/library/socket/socket/gethostname_spec.rb
new file mode 100644
index 0000000000..4b79747b27
--- /dev/null
+++ b/spec/ruby/library/socket/socket/gethostname_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket.gethostname" do
+ it "returns the host name" do
+ Socket.gethostname.should == `hostname`.strip
+ end
+end
diff --git a/spec/ruby/library/socket/socket/getifaddrs_spec.rb b/spec/ruby/library/socket/socket/getifaddrs_spec.rb
new file mode 100644
index 0000000000..7df542abe6
--- /dev/null
+++ b/spec/ruby/library/socket/socket/getifaddrs_spec.rb
@@ -0,0 +1,117 @@
+require_relative '../spec_helper'
+
+platform_is_not :aix, :"solaris2.10" do
+describe 'Socket.getifaddrs' do
+ before do
+ @ifaddrs = Socket.getifaddrs
+ end
+
+ it 'returns an Array' do
+ @ifaddrs.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ it 'should not be empty' do
+ @ifaddrs.should_not be_empty
+ end
+
+ it 'contains instances of Socket::Ifaddr' do
+ @ifaddrs.each do |ifaddr|
+ ifaddr.should be_an_instance_of(Socket::Ifaddr)
+ end
+ end
+ end
+
+ describe 'each returned Socket::Ifaddr' do
+ it 'has an interface index' do
+ @ifaddrs.each do |ifaddr|
+ ifaddr.ifindex.should be_kind_of(Integer)
+ end
+ end
+
+ it 'has an interface name' do
+ @ifaddrs.each do |ifaddr|
+ ifaddr.name.should be_an_instance_of(String)
+ end
+ end
+
+ it 'has a set of flags' do
+ @ifaddrs.each do |ifaddr|
+ ifaddr.flags.should be_kind_of(Integer)
+ end
+ end
+ end
+
+ describe 'the Socket::Ifaddr address' do
+ before do
+ @addrs = @ifaddrs.map(&:addr).compact
+ end
+
+ it 'is an Addrinfo' do
+ @addrs.all? do |addr|
+ addr.should be_an_instance_of(Addrinfo)
+ true
+ end.should be_true
+ end
+
+ it 'has an address family' do
+ @addrs.all? do |addr|
+ addr.afamily.should be_kind_of(Integer)
+ addr.afamily.should_not == Socket::AF_UNSPEC
+ true
+ end.should be_true
+ end
+ end
+
+ platform_is_not :windows do
+ describe 'the Socket::Ifaddr broadcast address' do
+ before do
+ @addrs = @ifaddrs.map(&:broadaddr).compact
+ end
+
+ it 'is an Addrinfo' do
+ @addrs.all? do |addr|
+ addr.should be_an_instance_of(Addrinfo)
+ true
+ end.should be_true
+ end
+
+ it 'has an address family' do
+ @addrs.all? do |addr|
+ addr.afamily.should be_kind_of(Integer)
+ addr.afamily.should_not == Socket::AF_UNSPEC
+ true
+ end.should be_true
+ end
+ end
+
+ describe 'the Socket::Ifaddr netmask address' do
+ before do
+ @addrs = @ifaddrs.map(&:netmask).compact.select(&:ip?)
+ end
+
+ it 'is an Addrinfo' do
+ @addrs.all? do |addr|
+ addr.should be_an_instance_of(Addrinfo)
+ true
+ end.should be_true
+ end
+
+ it 'has an address family' do
+ @addrs.all? do |addr|
+ addr.afamily.should be_kind_of(Integer)
+ addr.afamily.should_not == Socket::AF_UNSPEC
+ true
+ end.should be_true
+ end
+
+ it 'has an IP address' do
+ @addrs.all? do |addr|
+ addr.ip_address.should be_an_instance_of(String)
+ true
+ end.should be_true
+ end
+ end
+ end
+end
+end
diff --git a/spec/ruby/library/socket/socket/getnameinfo_spec.rb b/spec/ruby/library/socket/socket/getnameinfo_spec.rb
new file mode 100644
index 0000000000..b406348aa8
--- /dev/null
+++ b/spec/ruby/library/socket/socket/getnameinfo_spec.rb
@@ -0,0 +1,147 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket.getnameinfo" do
+ before :each do
+ @reverse_lookup = BasicSocket.do_not_reverse_lookup
+ BasicSocket.do_not_reverse_lookup = true
+ end
+
+ after :each do
+ BasicSocket.do_not_reverse_lookup = @reverse_lookup
+ end
+
+ it "gets the name information and don't resolve it" do
+ sockaddr = Socket.sockaddr_in 3333, '127.0.0.1'
+ name_info = Socket.getnameinfo(sockaddr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV)
+ name_info.should == ['127.0.0.1', "3333"]
+ end
+
+ def should_be_valid_dns_name(name)
+ # http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address
+ # ftp://ftp.rfc-editor.org/in-notes/rfc3696.txt
+ # http://domainkeys.sourceforge.net/underscore.html
+ valid_dns = /^(([a-zA-Z0-9_]|[a-zA-Z0-9_][a-zA-Z0-9\-_]*[a-zA-Z0-9_])\.)*([A-Za-z_]|[A-Za-z_][A-Za-z0-9\-_]*[A-Za-z0-9_])\.?$/
+ name.should =~ valid_dns
+ end
+
+ it "gets the name information and resolve the host" do
+ sockaddr = Socket.sockaddr_in 3333, '127.0.0.1'
+ name_info = Socket.getnameinfo(sockaddr, Socket::NI_NUMERICSERV)
+ should_be_valid_dns_name(name_info[0])
+ name_info[1].should == 3333.to_s
+ end
+
+ it "gets the name information and resolves the service" do
+ sockaddr = Socket.sockaddr_in 9, '127.0.0.1'
+ name_info = Socket.getnameinfo(sockaddr)
+ name_info.size.should == 2
+ should_be_valid_dns_name(name_info[0])
+ # see http://www.iana.org/assignments/port-numbers
+ name_info[1].should == 'discard'
+ end
+
+ it "gets a 3-element array and doesn't resolve hostname" do
+ name_info = Socket.getnameinfo(["AF_INET", 3333, '127.0.0.1'], Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV)
+ name_info.should == ['127.0.0.1', "3333"]
+ end
+
+ it "gets a 3-element array and resolves the service" do
+ name_info = Socket.getnameinfo ["AF_INET", 9, '127.0.0.1']
+ name_info[1].should == 'discard'
+ end
+
+ it "gets a 4-element array and doesn't resolve hostname" do
+ name_info = Socket.getnameinfo(["AF_INET", 3333, 'foo', '127.0.0.1'], Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV)
+ name_info.should == ['127.0.0.1', "3333"]
+ end
+
+ it "gets a 4-element array and resolves the service" do
+ name_info = Socket.getnameinfo ["AF_INET", 9, 'foo', '127.0.0.1']
+ name_info[1].should == 'discard'
+ end
+end
+
+describe 'Socket.getnameinfo' do
+ describe 'using a String as the first argument' do
+ before do
+ @addr = Socket.sockaddr_in(21, '127.0.0.1')
+ end
+
+ it 'raises SocketError or TypeError when using an invalid String' do
+ -> { Socket.getnameinfo('cats') }.should raise_error(Exception) { |e|
+ [SocketError, TypeError].should include(e.class)
+ }
+ end
+
+ describe 'without custom flags' do
+ it 'returns an Array containing the hostname and service name' do
+ Socket.getnameinfo(@addr).should == [SocketSpecs.hostname_reverse_lookup, 'ftp']
+ end
+ end
+
+ describe 'using NI_NUMERICHOST as the flag' do
+ it 'returns an Array containing the numeric hostname and service name' do
+ array = Socket.getnameinfo(@addr, Socket::NI_NUMERICHOST)
+
+ %w{127.0.0.1 ::1}.include?(array[0]).should == true
+
+ array[1].should == 'ftp'
+ end
+ end
+ end
+
+ SocketSpecs.each_ip_protocol do |family, ip_address, family_name|
+ before do
+ @hostname = SocketSpecs.hostname_reverse_lookup(ip_address)
+ end
+
+ describe 'using a 3 element Array as the first argument' do
+ before do
+ @addr = [family_name, 21, @hostname]
+ end
+
+ it 'raises ArgumentError when using an invalid Array' do
+ -> { Socket.getnameinfo([family_name]) }.should raise_error(ArgumentError)
+ end
+
+ platform_is_not :windows do
+ describe 'using NI_NUMERICHOST as the flag' do
+ it 'returns an Array containing the numeric hostname and service name' do
+ Socket.getnameinfo(@addr, Socket::NI_NUMERICHOST).should == [ip_address, 'ftp']
+ end
+ end
+ end
+ end
+
+ describe 'using a 4 element Array as the first argument' do
+ before do
+ @addr = [family_name, 21, ip_address, ip_address]
+ end
+
+ describe 'without custom flags' do
+ it 'returns an Array containing the hostname and service name' do
+ array = Socket.getnameinfo(@addr)
+ array.should be_an_instance_of(Array)
+ array[0].should == @hostname
+ array[1].should == 'ftp'
+ end
+
+ it 'uses the 3rd value as the hostname if the 4th is not present' do
+ addr = [family_name, 21, ip_address, nil]
+
+ array = Socket.getnameinfo(addr)
+ array.should be_an_instance_of(Array)
+ array[0].should == @hostname
+ array[1].should == 'ftp'
+ end
+ end
+
+ describe 'using NI_NUMERICHOST as the flag' do
+ it 'returns an Array containing the numeric hostname and service name' do
+ Socket.getnameinfo(@addr, Socket::NI_NUMERICHOST).should == [ip_address, 'ftp']
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/getservbyname_spec.rb b/spec/ruby/library/socket/socket/getservbyname_spec.rb
new file mode 100644
index 0000000000..d361e619f2
--- /dev/null
+++ b/spec/ruby/library/socket/socket/getservbyname_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket#getservbyname" do
+ it "returns the port for service 'discard'" do
+ Socket.getservbyname('discard').should == 9
+ end
+
+ it "returns the port for service 'discard' with protocol 'tcp'" do
+ Socket.getservbyname('discard', 'tcp').should == 9
+ end
+
+ it 'returns the port for service "ftp"' do
+ Socket.getservbyname('ftp').should == 21
+ end
+
+ it 'returns the port for service "ftp" with protocol "tcp"' do
+ Socket.getservbyname('ftp', 'tcp').should == 21
+ end
+
+ it "returns the port for service 'domain' with protocol 'udp'" do
+ Socket.getservbyname('domain', 'udp').should == 53
+ end
+
+ it "returns the port for service 'daytime'" do
+ Socket.getservbyname('daytime').should == 13
+ end
+
+ it "raises a SocketError when the service or port is invalid" do
+ -> { Socket.getservbyname('invalid') }.should raise_error(SocketError)
+ end
+end
diff --git a/spec/ruby/library/socket/socket/getservbyport_spec.rb b/spec/ruby/library/socket/socket/getservbyport_spec.rb
new file mode 100644
index 0000000000..563c592b54
--- /dev/null
+++ b/spec/ruby/library/socket/socket/getservbyport_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../spec_helper'
+
+describe 'Socket.getservbyport' do
+ platform_is_not :windows do
+ it 'returns the service name as a String' do
+ Socket.getservbyport(514).should == 'shell'
+ end
+ end
+
+ platform_is :windows do
+ it 'returns the service name as a String' do
+ Socket.getservbyport(514).should == 'cmd'
+ end
+ end
+
+ it 'returns the service name when using a custom protocol name' do
+ Socket.getservbyport(514, 'udp').should == 'syslog'
+ end
+
+ it 'raises SocketError for an unknown port number' do
+ -> { Socket.getservbyport(0) }.should raise_error(SocketError)
+ end
+end
diff --git a/spec/ruby/library/socket/socket/initialize_spec.rb b/spec/ruby/library/socket/socket/initialize_spec.rb
new file mode 100644
index 0000000000..f8337bcaa5
--- /dev/null
+++ b/spec/ruby/library/socket/socket/initialize_spec.rb
@@ -0,0 +1,87 @@
+require_relative '../spec_helper'
+
+describe 'Socket#initialize' do
+ before do
+ @socket = nil
+ end
+
+ after do
+ @socket.close if @socket
+ end
+
+ describe 'using an Integer as the 1st and 2nd arguments' do
+ it 'returns a Socket' do
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
+
+ @socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ describe 'using Symbols as the 1st and 2nd arguments' do
+ it 'returns a Socket' do
+ @socket = Socket.new(:INET, :STREAM)
+
+ @socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ describe 'using Strings as the 1st and 2nd arguments' do
+ it 'returns a Socket' do
+ @socket = Socket.new('INET', 'STREAM')
+
+ @socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ describe 'using objects that respond to #to_str' do
+ it 'returns a Socket' do
+ family = mock(:family)
+ type = mock(:type)
+
+ family.stub!(:to_str).and_return('AF_INET')
+ type.stub!(:to_str).and_return('STREAM')
+
+ @socket = Socket.new(family, type)
+
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'raises TypeError when the #to_str method does not return a String' do
+ family = mock(:family)
+ type = mock(:type)
+
+ family.stub!(:to_str).and_return(Socket::AF_INET)
+ type.stub!(:to_str).and_return(Socket::SOCK_STREAM)
+
+ -> { Socket.new(family, type) }.should raise_error(TypeError)
+ end
+ end
+
+ describe 'using a custom protocol' do
+ it 'returns a Socket when using an Integer' do
+ @socket = Socket.new(:INET, :STREAM, Socket::IPPROTO_TCP)
+
+ @socket.should be_an_instance_of(Socket)
+ end
+
+ it 'raises TypeError when using a Symbol' do
+ -> { Socket.new(:INET, :STREAM, :TCP) }.should raise_error(TypeError)
+ end
+ end
+
+ it 'sets the do_not_reverse_lookup option' do
+ @socket = Socket.new(:INET, :STREAM)
+
+ @socket.do_not_reverse_lookup.should == Socket.do_not_reverse_lookup
+ end
+
+ it "sets basic IO accessors" do
+ @socket = Socket.new(:INET, :STREAM)
+ @socket.lineno.should == 0
+ end
+
+ it "sets the socket to binary mode" do
+ @socket = Socket.new(:INET, :STREAM)
+ @socket.binmode?.should be_true
+ end
+end
diff --git a/spec/ruby/library/socket/socket/ip_address_list_spec.rb b/spec/ruby/library/socket/socket/ip_address_list_spec.rb
new file mode 100644
index 0000000000..f97c2d7f85
--- /dev/null
+++ b/spec/ruby/library/socket/socket/ip_address_list_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../spec_helper'
+
+describe 'Socket.ip_address_list' do
+ it 'returns an Array' do
+ Socket.ip_address_list.should be_an_instance_of(Array)
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = Socket.ip_address_list
+ end
+
+ it 'is not empty' do
+ @array.should_not be_empty
+ end
+
+ it 'contains Addrinfo objects' do
+ @array.each do |klass|
+ klass.should be_an_instance_of(Addrinfo)
+ end
+ end
+ end
+
+ describe 'each returned Addrinfo' do
+ before do
+ @array = Socket.ip_address_list
+ end
+
+ it 'has a non-empty IP address' do
+ @array.each do |addr|
+ addr.ip_address.should be_an_instance_of(String)
+ addr.ip_address.should_not be_empty
+ end
+ end
+
+ it 'has an address family' do
+ families = [Socket::AF_INET, Socket::AF_INET6]
+
+ @array.each do |addr|
+ families.include?(addr.afamily).should == true
+ end
+ end
+
+ it 'uses 0 as the port number' do
+ @array.each do |addr|
+ addr.ip_port.should == 0
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/ipv6only_bang_spec.rb b/spec/ruby/library/socket/socket/ipv6only_bang_spec.rb
new file mode 100644
index 0000000000..4f429c089e
--- /dev/null
+++ b/spec/ruby/library/socket/socket/ipv6only_bang_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+guard -> { SocketSpecs.ipv6_available? } do
+ describe 'Socket#ipv6only!' do
+ before do
+ @socket = Socket.new(:INET6, :DGRAM)
+ end
+
+ after do
+ @socket.close
+ end
+
+ it 'enables IPv6 only mode' do
+ @socket.ipv6only!
+
+ @socket.getsockopt(:IPV6, :V6ONLY).bool.should == true
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/listen_spec.rb b/spec/ruby/library/socket/socket/listen_spec.rb
new file mode 100644
index 0000000000..4d2aedab19
--- /dev/null
+++ b/spec/ruby/library/socket/socket/listen_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket#listen" do
+ before :each do
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
+ end
+
+ after :each do
+ @socket.closed?.should be_false
+ @socket.close
+ end
+
+ it "verifies we can listen for incoming connections" do
+ sockaddr = Socket.pack_sockaddr_in(0, "127.0.0.1")
+ @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
+ @socket.bind(sockaddr)
+ @socket.listen(1).should == 0
+ end
+end
+
+describe 'Socket#listen' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'using a DGRAM socket' do
+ before do
+ @server = Socket.new(family, :DGRAM)
+ @client = Socket.new(family, :DGRAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'raises Errno::EOPNOTSUPP or Errno::EACCES' do
+ -> { @server.listen(1) }.should raise_error { |e|
+ [Errno::EOPNOTSUPP, Errno::EACCES].should.include?(e.class)
+ }
+ end
+ end
+
+ describe 'using a STREAM socket' do
+ before do
+ @server = Socket.new(family, :STREAM)
+ @client = Socket.new(family, :STREAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'returns 0' do
+ @server.listen(1).should == 0
+ end
+
+ it "raises when the given argument can't be coerced to an Integer" do
+ -> { @server.listen('cats') }.should raise_error(TypeError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/local_address_spec.rb b/spec/ruby/library/socket/socket/local_address_spec.rb
new file mode 100644
index 0000000000..3687f93a0c
--- /dev/null
+++ b/spec/ruby/library/socket/socket/local_address_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../spec_helper'
+
+describe 'Socket#local_address' do
+ before do
+ @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP)
+ end
+
+ after do
+ @sock.close
+ end
+
+ it 'returns an Addrinfo' do
+ @sock.local_address.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses AF_INET as the address family' do
+ @sock.local_address.afamily.should == Socket::AF_INET
+ end
+
+ it 'uses PF_INET as the protocol family' do
+ @sock.local_address.pfamily.should == Socket::PF_INET
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @sock.local_address.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses 0.0.0.0 as the IP address' do
+ @sock.local_address.ip_address.should == '0.0.0.0'
+ end
+
+ platform_is_not :windows do
+ it 'uses 0 as the port' do
+ @sock.local_address.ip_port.should == 0
+ end
+ end
+
+ it 'uses 0 as the protocol' do
+ @sock.local_address.protocol.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/new_spec.rb b/spec/ruby/library/socket/socket/new_spec.rb
new file mode 100644
index 0000000000..b2ec607f6a
--- /dev/null
+++ b/spec/ruby/library/socket/socket/new_spec.rb
@@ -0,0 +1,2 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
diff --git a/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb
new file mode 100644
index 0000000000..63d4724453
--- /dev/null
+++ b/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/pack_sockaddr'
+
+describe "Socket#pack_sockaddr_in" do
+ it_behaves_like :socket_pack_sockaddr_in, :pack_sockaddr_in
+end
diff --git a/spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb b/spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb
new file mode 100644
index 0000000000..1ee0bc6157
--- /dev/null
+++ b/spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/pack_sockaddr'
+
+describe "Socket#pack_sockaddr_un" do
+ it_behaves_like :socket_pack_sockaddr_un, :pack_sockaddr_un
+end
diff --git a/spec/ruby/library/socket/socket/pair_spec.rb b/spec/ruby/library/socket/socket/pair_spec.rb
new file mode 100644
index 0000000000..292eacd38d
--- /dev/null
+++ b/spec/ruby/library/socket/socket/pair_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/socketpair'
+
+describe "Socket#pair" do
+ it_behaves_like :socket_socketpair, :pair
+end
diff --git a/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb b/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb
new file mode 100644
index 0000000000..94f58ac49f
--- /dev/null
+++ b/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Socket#recvfrom_nonblock' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :DGRAM)
+ @client = Socket.new(family, :DGRAM)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ platform_is_not :windows do
+ describe 'using an unbound socket' do
+ it 'raises IO::WaitReadable' do
+ -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable)
+ end
+ end
+ end
+
+ describe 'using a bound socket' do
+ before do
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ @client.connect(@server.getsockname)
+ end
+
+ describe 'without any data available' do
+ it 'raises IO::WaitReadable' do
+ -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable)
+ end
+
+ it 'returns :wait_readable with exception: false' do
+ @server.recvfrom_nonblock(1, exception: false).should == :wait_readable
+ end
+ end
+
+ describe 'with data available' do
+ before do
+ @client.write('hello')
+ end
+
+ platform_is_not :windows do
+ it 'returns an Array containing the data and an Addrinfo' do
+ IO.select([@server])
+ ret = @server.recvfrom_nonblock(1)
+
+ ret.should be_an_instance_of(Array)
+ ret.length.should == 2
+ end
+ end
+
+ describe 'the returned data' do
+ it 'is the same as the sent data' do
+ 5.times do
+ @client.write('hello')
+
+ IO.select([@server])
+ msg, _ = @server.recvfrom_nonblock(5)
+
+ msg.should == 'hello'
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ describe 'the returned Array' do
+ before do
+ IO.select([@server])
+ @array = @server.recvfrom_nonblock(1)
+ end
+
+ it 'contains the data at index 0' do
+ @array[0].should == 'h'
+ end
+
+ it 'contains an Addrinfo at index 1' do
+ @array[1].should be_an_instance_of(Addrinfo)
+ end
+ end
+
+ describe 'the returned Addrinfo' do
+ before do
+ IO.select([@server])
+ @addr = @server.recvfrom_nonblock(1)[1]
+ end
+
+ it 'uses AF_INET as the address family' do
+ @addr.afamily.should == family
+ end
+
+ it 'uses SOCK_DGRAM as the socket type' do
+ @addr.socktype.should == Socket::SOCK_DGRAM
+ end
+
+ it 'uses PF_INET as the protocol family' do
+ @addr.pfamily.should == family
+ end
+
+ it 'uses 0 as the protocol' do
+ @addr.protocol.should == 0
+ end
+
+ it 'uses the IP address of the client' do
+ @addr.ip_address.should == ip_address
+ end
+
+ it 'uses the port of the client' do
+ @addr.ip_port.should == @client.local_address.ip_port
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/recvfrom_spec.rb b/spec/ruby/library/socket/socket/recvfrom_spec.rb
new file mode 100644
index 0000000000..faf161e4a5
--- /dev/null
+++ b/spec/ruby/library/socket/socket/recvfrom_spec.rb
@@ -0,0 +1,92 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Socket#recvfrom' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :DGRAM)
+ @client = Socket.new(family, :DGRAM)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ describe 'using an unbound socket' do
+ it 'blocks the caller' do
+ -> { @server.recvfrom(1) }.should block_caller
+ end
+ end
+
+ describe 'using a bound socket' do
+ before do
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ @client.connect(@server.getsockname)
+ end
+
+ describe 'without any data available' do
+ it 'blocks the caller' do
+ -> { @server.recvfrom(1) }.should block_caller
+ end
+ end
+
+ describe 'with data available' do
+ before do
+ @client.write('hello')
+ end
+
+ it 'returns an Array containing the data and an Addrinfo' do
+ ret = @server.recvfrom(1)
+
+ ret.should be_an_instance_of(Array)
+ ret.length.should == 2
+ end
+
+ describe 'the returned Array' do
+ before do
+ @array = @server.recvfrom(1)
+ end
+
+ it 'contains the data at index 0' do
+ @array[0].should == 'h'
+ end
+
+ it 'contains an Addrinfo at index 1' do
+ @array[1].should be_an_instance_of(Addrinfo)
+ end
+ end
+
+ describe 'the returned Addrinfo' do
+ before do
+ @addr = @server.recvfrom(1)[1]
+ end
+
+ it 'uses AF_INET as the address family' do
+ @addr.afamily.should == family
+ end
+
+ it 'uses SOCK_DGRAM as the socket type' do
+ @addr.socktype.should == Socket::SOCK_DGRAM
+ end
+
+ it 'uses PF_INET as the protocol family' do
+ @addr.pfamily.should == family
+ end
+
+ it 'uses 0 as the protocol' do
+ @addr.protocol.should == 0
+ end
+
+ it 'uses the IP address of the client' do
+ @addr.ip_address.should == ip_address
+ end
+
+ it 'uses the port of the client' do
+ @addr.ip_port.should == @client.local_address.ip_port
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/remote_address_spec.rb b/spec/ruby/library/socket/socket/remote_address_spec.rb
new file mode 100644
index 0000000000..24d60d7f58
--- /dev/null
+++ b/spec/ruby/library/socket/socket/remote_address_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Socket#remote_address' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :STREAM)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+ @server.listen(1)
+
+ @host = @server.local_address.ip_address
+ @port = @server.local_address.ip_port
+ @client = Socket.new(family, :STREAM, Socket::IPPROTO_TCP)
+
+ @client.connect(Socket.sockaddr_in(@port, @host))
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'returns an Addrinfo' do
+ @client.remote_address.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses AF_INET as the address family' do
+ @client.remote_address.afamily.should == family
+ end
+
+ it 'uses PF_INET as the protocol family' do
+ @client.remote_address.pfamily.should == family
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @client.remote_address.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses the correct IP address' do
+ @client.remote_address.ip_address.should == @host
+ end
+
+ it 'uses the correct port' do
+ @client.remote_address.ip_port.should == @port
+ end
+
+ it 'uses 0 as the protocol' do
+ @client.remote_address.protocol.should == 0
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/sockaddr_in_spec.rb
new file mode 100644
index 0000000000..8ee956ac26
--- /dev/null
+++ b/spec/ruby/library/socket/socket/sockaddr_in_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/pack_sockaddr'
+
+describe "Socket#sockaddr_in" do
+ it_behaves_like :socket_pack_sockaddr_in, :sockaddr_in
+end
diff --git a/spec/ruby/library/socket/socket/sockaddr_un_spec.rb b/spec/ruby/library/socket/socket/sockaddr_un_spec.rb
new file mode 100644
index 0000000000..8922ff4d6d
--- /dev/null
+++ b/spec/ruby/library/socket/socket/sockaddr_un_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/pack_sockaddr'
+
+describe "Socket#sockaddr_un" do
+ it_behaves_like :socket_pack_sockaddr_un, :sockaddr_un
+end
diff --git a/spec/ruby/library/socket/socket/socket_spec.rb b/spec/ruby/library/socket/socket/socket_spec.rb
new file mode 100644
index 0000000000..5a3d6733e0
--- /dev/null
+++ b/spec/ruby/library/socket/socket/socket_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket" do
+ it "inherits from BasicSocket and IO" do
+ Socket.superclass.should == BasicSocket
+ BasicSocket.superclass.should == IO
+ end
+end
+
+describe "The socket class hierarchy" do
+ it "has an IPSocket in parallel to Socket" do
+ Socket.ancestors.include?(IPSocket).should == false
+ IPSocket.ancestors.include?(Socket).should == false
+ IPSocket.superclass.should == BasicSocket
+ end
+
+ it "has TCPSocket and UDPSocket subclasses of IPSocket" do
+ TCPSocket.superclass.should == IPSocket
+ UDPSocket.superclass.should == IPSocket
+ end
+
+ platform_is_not :windows do
+ it "has a UNIXSocket in parallel to Socket" do
+ Socket.ancestors.include?(UNIXSocket).should == false
+ UNIXSocket.ancestors.include?(Socket).should == false
+ UNIXSocket.superclass.should == BasicSocket
+ end
+ end
+end
+
+platform_is_not :windows do
+ describe "Server class hierarchy" do
+ it "contains UNIXServer" do
+ UNIXServer.superclass.should == UNIXSocket
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/socketpair_spec.rb b/spec/ruby/library/socket/socket/socketpair_spec.rb
new file mode 100644
index 0000000000..5b8311124e
--- /dev/null
+++ b/spec/ruby/library/socket/socket/socketpair_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/socketpair'
+
+describe "Socket#socketpair" do
+ it_behaves_like :socket_socketpair, :socketpair
+end
diff --git a/spec/ruby/library/socket/socket/sysaccept_spec.rb b/spec/ruby/library/socket/socket/sysaccept_spec.rb
new file mode 100644
index 0000000000..92ac21124e
--- /dev/null
+++ b/spec/ruby/library/socket/socket/sysaccept_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Socket#sysaccept' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :STREAM)
+ @sockaddr = Socket.sockaddr_in(0, ip_address)
+ end
+
+ after do
+ @server.close
+ end
+
+ platform_is :linux do # hangs on other platforms
+ describe 'using an unbound socket' do
+ it 'raises Errno::EINVAL' do
+ -> { @server.sysaccept }.should raise_error(Errno::EINVAL)
+ end
+ end
+
+ describe "using a bound socket that's not listening" do
+ before do
+ @server.bind(@sockaddr)
+ end
+
+ it 'raises Errno::EINVAL' do
+ -> { @server.sysaccept }.should raise_error(Errno::EINVAL)
+ end
+ end
+ end
+
+ describe "using a bound socket that's listening" do
+ before do
+ @server.bind(@sockaddr)
+ @server.listen(1)
+
+ server_ip = @server.local_address.ip_port
+ @server_addr = Socket.sockaddr_in(server_ip, ip_address)
+ end
+
+ after do
+ Socket.for_fd(@fd).close if @fd
+ end
+
+ describe 'without a connected client' do
+ before do
+ @client = Socket.new(family, :STREAM)
+ end
+
+ after do
+ @client.close
+ end
+
+ it 'blocks the caller until a connection is available' do
+ thread = Thread.new do
+ @fd, _ = @server.sysaccept
+ end
+
+ @client.connect(@server_addr)
+
+ thread.value.should be_an_instance_of(Array)
+ end
+ end
+
+ describe 'with a connected client' do
+ before do
+ @client = Socket.new(family, :STREAM)
+ @client.connect(@server.getsockname)
+ end
+
+ after do
+ @client.close
+ end
+
+ it 'returns an Array containing an Integer and an Addrinfo' do
+ @fd, addrinfo = @server.sysaccept
+
+ @fd.should be_kind_of(Integer)
+ addrinfo.should be_an_instance_of(Addrinfo)
+ end
+
+ it 'returns a new file descriptor' do
+ @fd, _ = @server.sysaccept
+
+ @fd.should_not == @client.fileno
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/tcp_server_loop_spec.rb b/spec/ruby/library/socket/socket/tcp_server_loop_spec.rb
new file mode 100644
index 0000000000..a46c6df5c6
--- /dev/null
+++ b/spec/ruby/library/socket/socket/tcp_server_loop_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Socket.tcp_server_loop' do
+ describe 'when no connections are available' do
+ it 'blocks the caller' do
+ -> { Socket.tcp_server_loop('127.0.0.1', 0) }.should block_caller
+ end
+ end
+
+ describe 'when a connection is available' do
+ before do
+ @client = Socket.new(:INET, :STREAM)
+ SocketSpecs::ServerLoopPortFinder.cleanup
+ end
+
+ after do
+ @sock.close if @sock
+ @client.close
+ end
+
+ it 'yields a Socket and an Addrinfo' do
+ @sock, addr = nil
+
+ thread = Thread.new do
+ SocketSpecs::ServerLoopPortFinder.tcp_server_loop('127.0.0.1', 0) do |socket, addrinfo|
+ @sock = socket
+ addr = addrinfo
+
+ break
+ end
+ end
+
+ port = SocketSpecs::ServerLoopPortFinder.port
+
+ SocketSpecs.loop_with_timeout do
+ begin
+ @client.connect(Socket.sockaddr_in(port, '127.0.0.1'))
+ rescue SystemCallError
+ sleep 0.01
+ :retry
+ end
+ end
+
+ # At this point the connection has been set up but the thread may not yet
+ # have returned, thus we'll need to wait a little longer for it to
+ # complete.
+ thread.join
+
+ @sock.should be_an_instance_of(Socket)
+ addr.should be_an_instance_of(Addrinfo)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb b/spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb
new file mode 100644
index 0000000000..bd496d3015
--- /dev/null
+++ b/spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../spec_helper'
+
+describe 'Socket.tcp_server_sockets' do
+ describe 'without a block' do
+ before do
+ @sockets = nil
+ end
+
+ after do
+ @sockets.each(&:close)
+ end
+
+ it 'returns an Array of Socket objects' do
+ @sockets = Socket.tcp_server_sockets(0)
+
+ @sockets.should be_an_instance_of(Array)
+ @sockets[0].should be_an_instance_of(Socket)
+ end
+ end
+
+ describe 'with a block' do
+ it 'yields the sockets to the supplied block' do
+ Socket.tcp_server_sockets(0) do |sockets|
+ sockets.should be_an_instance_of(Array)
+ sockets[0].should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'closes all sockets after the block returns' do
+ sockets = nil
+
+ Socket.tcp_server_sockets(0) { |socks| sockets = socks }
+
+ sockets.each do |socket|
+ socket.should.closed?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/tcp_spec.rb b/spec/ruby/library/socket/socket/tcp_spec.rb
new file mode 100644
index 0000000000..faf020b1ea
--- /dev/null
+++ b/spec/ruby/library/socket/socket/tcp_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../spec_helper'
+
+describe 'Socket.tcp' do
+ before do
+ @server = Socket.new(:INET, :STREAM)
+ @client = nil
+
+ @server.bind(Socket.sockaddr_in(0, '127.0.0.1'))
+ @server.listen(1)
+
+ @host = @server.connect_address.ip_address
+ @port = @server.connect_address.ip_port
+ end
+
+ after do
+ @client.close if @client && !@client.closed?
+ @client = nil
+
+ @server.close
+ end
+
+ it 'returns a Socket when no block is given' do
+ @client = Socket.tcp(@host, @port)
+
+ @client.should be_an_instance_of(Socket)
+ end
+
+ it 'yields the Socket when a block is given' do
+ Socket.tcp(@host, @port) do |socket|
+ socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'closes the Socket automatically when a block is given' do
+ Socket.tcp(@host, @port) do |socket|
+ @socket = socket
+ end
+
+ @socket.should.closed?
+ end
+
+ it 'binds to a local address and port when specified' do
+ @client = Socket.tcp(@host, @port, @host, 0)
+
+ @client.local_address.ip_address.should == @host
+
+ @client.local_address.ip_port.should > 0
+ @client.local_address.ip_port.should_not == @port
+ end
+
+ it 'raises ArgumentError when 6 arguments are provided' do
+ -> {
+ Socket.tcp(@host, @port, @host, 0, {:connect_timeout => 1}, 10)
+ }.should raise_error(ArgumentError)
+ end
+
+ it 'connects to the server' do
+ @client = Socket.tcp(@host, @port)
+
+ @client.write('hello')
+
+ connection, _ = @server.accept
+
+ begin
+ connection.recv(5).should == 'hello'
+ ensure
+ connection.close
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb b/spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb
new file mode 100644
index 0000000000..cb8c5c5587
--- /dev/null
+++ b/spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../spec_helper'
+
+describe 'Socket.udp_server_loop_on' do
+ before do
+ @server = Socket.new(:INET, :DGRAM)
+
+ @server.bind(Socket.sockaddr_in(0, '127.0.0.1'))
+ end
+
+ after do
+ @server.close
+ end
+
+ describe 'when no connections are available' do
+ it 'blocks the caller' do
+ -> { Socket.udp_server_loop_on([@server]) }.should block_caller
+ end
+ end
+
+ describe 'when a connection is available' do
+ before do
+ @client = Socket.new(:INET, :DGRAM)
+ end
+
+ after do
+ @client.close
+ end
+
+ it 'yields the message and a Socket::UDPSource' do
+ msg = nil
+ src = nil
+
+ @client.connect(@server.getsockname)
+ @client.write('hello')
+
+ Socket.udp_server_loop_on([@server]) do |message, source|
+ msg = message
+ src = source
+
+ break
+ end
+
+ msg.should == 'hello'
+ src.should be_an_instance_of(Socket::UDPSource)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/udp_server_loop_spec.rb b/spec/ruby/library/socket/socket/udp_server_loop_spec.rb
new file mode 100644
index 0000000000..fc030e75b9
--- /dev/null
+++ b/spec/ruby/library/socket/socket/udp_server_loop_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'Socket.udp_server_loop' do
+ describe 'when no connections are available' do
+ it 'blocks the caller' do
+ -> { Socket.udp_server_loop('127.0.0.1', 0) }.should block_caller
+ end
+ end
+
+ describe 'when a connection is available' do
+ before do
+ @client = Socket.new(:INET, :DGRAM)
+ SocketSpecs::ServerLoopPortFinder.cleanup
+ end
+
+ after do
+ @client.close
+ end
+
+ it 'yields the message and a Socket::UDPSource' do
+ msg, src = nil
+
+ thread = Thread.new do
+ SocketSpecs::ServerLoopPortFinder.udp_server_loop('127.0.0.1', 0) do |message, source|
+ msg = message
+ src = source
+
+ break
+ end
+ end
+
+ port = SocketSpecs::ServerLoopPortFinder.port
+
+ # Because this will return even if the server is up and running (it's UDP
+ # after all) we'll have to write and wait until "msg" is set.
+ @client.connect(Socket.sockaddr_in(port, '127.0.0.1'))
+
+ SocketSpecs.loop_with_timeout do
+ begin
+ @client.write('hello')
+ rescue SystemCallError
+ sleep 0.01
+ :retry
+ else
+ unless msg
+ sleep 0.001
+ :retry
+ end
+ end
+ end
+
+ msg.should == 'hello'
+ src.should be_an_instance_of(Socket::UDPSource)
+
+ thread.join
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/udp_server_recv_spec.rb b/spec/ruby/library/socket/socket/udp_server_recv_spec.rb
new file mode 100644
index 0000000000..47ed74bc03
--- /dev/null
+++ b/spec/ruby/library/socket/socket/udp_server_recv_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+
+describe 'Socket.udp_server_recv' do
+ before do
+ @server = Socket.new(:INET, :DGRAM)
+ @client = Socket.new(:INET, :DGRAM)
+
+ @server.bind(Socket.sockaddr_in(0, '127.0.0.1'))
+ @client.connect(@server.getsockname)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'yields the message and a Socket::UDPSource' do
+ msg = :unset
+ src = :unset
+
+ @client.write('hello')
+
+ readable, _, _ = IO.select([@server])
+ readable.size.should == 1
+
+ Socket.udp_server_recv(readable) do |message, source|
+ msg = message
+ src = source
+ break
+ end
+
+ msg.should == 'hello'
+ src.should be_an_instance_of(Socket::UDPSource)
+ end
+end
diff --git a/spec/ruby/library/socket/socket/udp_server_sockets_spec.rb b/spec/ruby/library/socket/socket/udp_server_sockets_spec.rb
new file mode 100644
index 0000000000..f8be672612
--- /dev/null
+++ b/spec/ruby/library/socket/socket/udp_server_sockets_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../spec_helper'
+
+describe 'Socket.udp_server_sockets' do
+ describe 'without a block' do
+ before do
+ @sockets = nil
+ end
+
+ after do
+ @sockets.each(&:close)
+ end
+
+ it 'returns an Array of Socket objects' do
+ @sockets = Socket.udp_server_sockets(0)
+
+ @sockets.should be_an_instance_of(Array)
+ @sockets[0].should be_an_instance_of(Socket)
+ end
+ end
+
+ describe 'with a block' do
+ it 'yields the sockets to the supplied block' do
+ Socket.udp_server_sockets(0) do |sockets|
+ sockets.should be_an_instance_of(Array)
+ sockets[0].should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'closes all sockets after the block returns' do
+ sockets = nil
+
+ Socket.udp_server_sockets(0) { |socks| sockets = socks }
+
+ sockets.each do |socket|
+ socket.should.closed?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/unix_server_loop_spec.rb b/spec/ruby/library/socket/socket/unix_server_loop_spec.rb
new file mode 100644
index 0000000000..0f34d4a50b
--- /dev/null
+++ b/spec/ruby/library/socket/socket/unix_server_loop_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+with_feature :unix_socket do
+ describe 'Socket.unix_server_loop' do
+ before do
+ @path = SocketSpecs.socket_path
+ end
+
+ after do
+ rm_r(@path) if File.file?(@path)
+ end
+
+ describe 'when no connections are available' do
+ it 'blocks the caller' do
+ -> { Socket.unix_server_loop(@path) }.should block_caller
+ end
+ end
+
+ describe 'when a connection is available' do
+ before do
+ @client = nil
+ end
+
+ after do
+ @sock.close if @sock
+ @client.close if @client
+ end
+
+ it 'yields a Socket and an Addrinfo' do
+ @sock, addr = nil
+
+ thread = Thread.new do
+ Socket.unix_server_loop(@path) do |socket, addrinfo|
+ @sock = socket
+ addr = addrinfo
+
+ break
+ end
+ end
+
+ SocketSpecs.loop_with_timeout do
+ begin
+ @client = Socket.unix(@path)
+ rescue SystemCallError
+ sleep 0.01
+ :retry
+ end
+ end
+
+ thread.join
+
+ @sock.should be_an_instance_of(Socket)
+ addr.should be_an_instance_of(Addrinfo)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/unix_server_socket_spec.rb b/spec/ruby/library/socket/socket/unix_server_socket_spec.rb
new file mode 100644
index 0000000000..fc357740fa
--- /dev/null
+++ b/spec/ruby/library/socket/socket/unix_server_socket_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+with_feature :unix_socket do
+ describe 'Socket.unix_server_socket' do
+ before do
+ @path = SocketSpecs.socket_path
+ end
+
+ after do
+ rm_r(@path)
+ end
+
+ describe 'when no block is given' do
+ before do
+ @socket = nil
+ end
+
+ after do
+ @socket.close
+ end
+
+ it 'returns a Socket' do
+ @socket = Socket.unix_server_socket(@path)
+
+ @socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ describe 'when a block is given' do
+ it 'yields a Socket' do
+ Socket.unix_server_socket(@path) do |sock|
+ sock.should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'closes the Socket when the block returns' do
+ socket = nil
+
+ Socket.unix_server_socket(@path) do |sock|
+ socket = sock
+ end
+
+ socket.should be_an_instance_of(Socket)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/unix_spec.rb b/spec/ruby/library/socket/socket/unix_spec.rb
new file mode 100644
index 0000000000..4bff59bd4b
--- /dev/null
+++ b/spec/ruby/library/socket/socket/unix_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+with_feature :unix_socket do
+ describe 'Socket.unix' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ @socket = nil
+ end
+
+ after do
+ @server.close
+ @socket.close if @socket
+
+ rm_r(@path)
+ end
+
+ describe 'when no block is given' do
+ it 'returns a Socket' do
+ @socket = Socket.unix(@path)
+
+ @socket.should be_an_instance_of(Socket)
+ end
+ end
+
+ describe 'when a block is given' do
+ it 'yields a Socket' do
+ Socket.unix(@path) do |sock|
+ sock.should be_an_instance_of(Socket)
+ end
+ end
+
+ it 'closes the Socket when the block returns' do
+ socket = nil
+
+ Socket.unix(@path) do |sock|
+ socket = sock
+ end
+
+ socket.should.closed?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb
new file mode 100644
index 0000000000..79ec68cd18
--- /dev/null
+++ b/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket.unpack_sockaddr_in" do
+ it "decodes the host name and port number of a packed sockaddr_in" do
+ sockaddr = Socket.sockaddr_in 3333, '127.0.0.1'
+ Socket.unpack_sockaddr_in(sockaddr).should == [3333, '127.0.0.1']
+ end
+
+ it "gets the hostname and port number from a passed Addrinfo" do
+ addrinfo = Addrinfo.tcp('127.0.0.1', 3333)
+ Socket.unpack_sockaddr_in(addrinfo).should == [3333, '127.0.0.1']
+ end
+
+ describe 'using an IPv4 address' do
+ it 'returns an Array containing the port and IP address' do
+ port = 80
+ ip = '127.0.0.1'
+ addr = Socket.pack_sockaddr_in(port, ip)
+
+ Socket.unpack_sockaddr_in(addr).should == [port, ip]
+ end
+ end
+
+ describe 'using an IPv6 address' do
+ it 'returns an Array containing the port and IP address' do
+ port = 80
+ ip = '::1'
+ addr = Socket.pack_sockaddr_in(port, ip)
+
+ Socket.unpack_sockaddr_in(addr).should == [port, ip]
+ end
+ end
+
+ with_feature :unix_socket do
+ it "raises an ArgumentError when the sin_family is not AF_INET" do
+ sockaddr = Socket.sockaddr_un '/tmp/x'
+ -> { Socket.unpack_sockaddr_in sockaddr }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed addrinfo is not AF_INET/AF_INET6" do
+ addrinfo = Addrinfo.unix('/tmp/sock')
+ -> { Socket.unpack_sockaddr_in(addrinfo) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb b/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb
new file mode 100644
index 0000000000..12f970f89b
--- /dev/null
+++ b/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+with_feature :unix_socket do
+ describe 'Socket.unpack_sockaddr_un' do
+ it 'decodes sockaddr to unix path' do
+ sockaddr = Socket.sockaddr_un('/tmp/sock')
+ Socket.unpack_sockaddr_un(sockaddr).should == '/tmp/sock'
+ end
+
+ it 'returns unix path from a passed Addrinfo' do
+ addrinfo = Addrinfo.unix('/tmp/sock')
+ Socket.unpack_sockaddr_un(addrinfo).should == '/tmp/sock'
+ end
+
+ it 'raises an ArgumentError when the sa_family is not AF_UNIX' do
+ sockaddr = Socket.sockaddr_in(0, '127.0.0.1')
+ -> { Socket.unpack_sockaddr_un(sockaddr) }.should raise_error(ArgumentError)
+ end
+
+ it 'raises an ArgumentError when passed addrinfo is not AF_UNIX' do
+ addrinfo = Addrinfo.tcp('127.0.0.1', 0)
+ -> { Socket.unpack_sockaddr_un(addrinfo) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/spec_helper.rb b/spec/ruby/library/socket/spec_helper.rb
new file mode 100644
index 0000000000..1121542dd5
--- /dev/null
+++ b/spec/ruby/library/socket/spec_helper.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require 'socket'
+
+MSpec.enable_feature :sock_packet if Socket.const_defined?(:SOCK_PACKET)
+MSpec.enable_feature :unix_socket unless PlatformGuard.windows?
+MSpec.enable_feature :udp_cork if Socket.const_defined?(:UDP_CORK)
+MSpec.enable_feature :tcp_cork if Socket.const_defined?(:TCP_CORK)
+MSpec.enable_feature :pktinfo if Socket.const_defined?(:IP_PKTINFO)
+MSpec.enable_feature :ipv6_pktinfo if Socket.const_defined?(:IPV6_PKTINFO)
+MSpec.enable_feature :ip_mtu if Socket.const_defined?(:IP_MTU)
+MSpec.enable_feature :ipv6_nexthop if Socket.const_defined?(:IPV6_NEXTHOP)
+MSpec.enable_feature :tcp_info if Socket.const_defined?(:TCP_INFO)
+MSpec.enable_feature :ancillary_data if Socket.const_defined?(:AncillaryData)
diff --git a/spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb b/spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb
new file mode 100644
index 0000000000..91f6a327f0
--- /dev/null
+++ b/spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb
@@ -0,0 +1,85 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Socket::TCPServer.accept_nonblock" do
+ before :each do
+ @server = TCPServer.new("127.0.0.1", 0)
+ @port = @server.addr[1]
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ end
+
+ it "accepts non blocking connections" do
+ @server.listen(5)
+ -> {
+ @server.accept_nonblock
+ }.should raise_error(IO::WaitReadable)
+
+ c = TCPSocket.new("127.0.0.1", @port)
+ IO.select([@server])
+ s = @server.accept_nonblock
+
+ port, address = Socket.unpack_sockaddr_in(s.getsockname)
+
+ port.should == @port
+ address.should == "127.0.0.1"
+ s.should be_kind_of(TCPSocket)
+
+ c.close
+ s.close
+ end
+
+ it "raises an IOError if the socket is closed" do
+ @server.close
+ -> { @server.accept }.should raise_error(IOError)
+ end
+
+ describe 'without a connected client' do
+ it 'raises error' do
+ -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable)
+ end
+
+ it 'returns :wait_readable in exceptionless mode' do
+ @server.accept_nonblock(exception: false).should == :wait_readable
+ end
+ end
+end
+
+describe 'TCPServer#accept_nonblock' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ end
+
+ after do
+ @server.close
+ end
+
+ describe 'without a connected client' do
+ it 'raises IO::WaitReadable' do
+ -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable)
+ end
+ end
+
+ platform_is_not :windows do # spurious
+ describe 'with a connected client' do
+ before do
+ @client = TCPSocket.new(ip_address, @server.connect_address.ip_port)
+ end
+
+ after do
+ @socket.close if @socket
+ @client.close
+ end
+
+ it 'returns a TCPSocket' do
+ IO.select([@server])
+ @socket = @server.accept_nonblock
+ @socket.should be_an_instance_of(TCPSocket)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpserver/accept_spec.rb b/spec/ruby/library/socket/tcpserver/accept_spec.rb
new file mode 100644
index 0000000000..d38d95e0e1
--- /dev/null
+++ b/spec/ruby/library/socket/tcpserver/accept_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "TCPServer#accept" do
+ before :each do
+ @server = TCPServer.new("127.0.0.1", 0)
+ @port = @server.addr[1]
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ end
+
+ it "accepts a connection and returns a TCPSocket" do
+ data = nil
+ t = Thread.new do
+ client = @server.accept
+ client.should be_kind_of(TCPSocket)
+ data = client.read(5)
+ client << "goodbye"
+ client.close
+ end
+ Thread.pass while t.status and t.status != "sleep"
+
+ socket = TCPSocket.new('127.0.0.1', @port)
+ socket.write('hello')
+ socket.shutdown(1) # we are done with sending
+ socket.read.should == 'goodbye'
+ t.join
+ data.should == 'hello'
+ socket.close
+ end
+
+ it "can be interrupted by Thread#kill" do
+ t = Thread.new { @server.accept }
+
+ Thread.pass while t.status and t.status != "sleep"
+
+ # kill thread, ensure it dies in a reasonable amount of time
+ t.kill
+ a = 0
+ while t.alive? and a < 5000
+ sleep 0.001
+ a += 1
+ end
+ a.should < 5000
+ end
+
+ it "can be interrupted by Thread#raise" do
+ t = Thread.new {
+ -> {
+ @server.accept
+ }.should raise_error(Exception, "interrupted")
+ }
+
+ Thread.pass while t.status and t.status != "sleep"
+ t.raise Exception, "interrupted"
+ t.join
+ end
+
+ it "is automatically retried when interrupted by SIGVTALRM" do
+ t = Thread.new do
+ client = @server.accept
+ value = client.read(2)
+ client.close
+ value
+ end
+
+ Thread.pass while t.status and t.status != "sleep"
+ # Thread#backtrace uses SIGVTALRM on TruffleRuby and potentially other implementations.
+ # Sending a signal to a thread is not possible with Ruby APIs.
+ t.backtrace.join("\n").should.include?("in `accept'")
+
+ socket = TCPSocket.new('127.0.0.1', @port)
+ socket.write("OK")
+ socket.close
+
+ t.value.should == "OK"
+ end
+
+ it "raises an IOError if the socket is closed" do
+ @server.close
+ -> { @server.accept }.should raise_error(IOError)
+ end
+end
+
+describe 'TCPServer#accept' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ end
+
+ after do
+ @server.close
+ end
+
+ describe 'without a connected client' do
+ it 'blocks the caller' do
+ -> { @server.accept }.should block_caller
+ end
+ end
+
+ describe 'with a connected client' do
+ before do
+ @client = TCPSocket.new(ip_address, @server.connect_address.ip_port)
+ end
+
+ after do
+ @socket.close if @socket
+ @client.close
+ end
+
+ it 'returns a TCPSocket' do
+ @socket = @server.accept
+ @socket.should be_an_instance_of(TCPSocket)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpserver/gets_spec.rb b/spec/ruby/library/socket/tcpserver/gets_spec.rb
new file mode 100644
index 0000000000..417976d737
--- /dev/null
+++ b/spec/ruby/library/socket/tcpserver/gets_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "TCPServer#gets" do
+ before :each do
+ @server = TCPServer.new(SocketSpecs.hostname, 0)
+ end
+
+ after :each do
+ @server.close
+ end
+
+ it "raises Errno::ENOTCONN on gets" do
+ -> { @server.gets }.should raise_error(Errno::ENOTCONN)
+ end
+end
diff --git a/spec/ruby/library/socket/tcpserver/initialize_spec.rb b/spec/ruby/library/socket/tcpserver/initialize_spec.rb
new file mode 100644
index 0000000000..4ddd1f465f
--- /dev/null
+++ b/spec/ruby/library/socket/tcpserver/initialize_spec.rb
@@ -0,0 +1,101 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'TCPServer#initialize' do
+ describe 'with a single Integer argument' do
+ before do
+ @server = TCPServer.new(0)
+ end
+
+ after do
+ @server.close
+ end
+
+ it 'sets the port to the given argument' do
+ @server.local_address.ip_port.should be_kind_of(Integer)
+ @server.local_address.ip_port.should > 0
+ end
+
+ platform_is_not :windows do
+ it 'sets the hostname to 0.0.0.0 or ::' do
+ a = @server.local_address
+ a.ip_address.should == (a.ipv6? ? '::' : '0.0.0.0')
+ end
+ end
+
+ it "sets the socket to binmode" do
+ @server.binmode?.should be_true
+ end
+ end
+
+ describe 'with a single String argument containing a numeric value' do
+ before do
+ @server = TCPServer.new('0')
+ end
+
+ after do
+ @server.close
+ end
+
+ it 'sets the port to the given argument' do
+ @server.local_address.ip_port.should be_kind_of(Integer)
+ @server.local_address.ip_port.should > 0
+ end
+
+ platform_is_not :windows do
+ it 'sets the hostname to 0.0.0.0 or ::' do
+ a = @server.local_address
+ a.ip_address.should == (a.ipv6? ? '::' : '0.0.0.0')
+ end
+ end
+ end
+
+ describe 'with a single String argument containing a non numeric value' do
+ it 'raises SocketError' do
+ -> { TCPServer.new('cats') }.should raise_error(SocketError)
+ end
+ end
+
+ describe 'with a String and an Integer' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ end
+
+ after do
+ @server.close
+ end
+
+ it 'sets the port to the given port argument' do
+ @server.local_address.ip_port.should be_kind_of(Integer)
+ @server.local_address.ip_port.should > 0
+ end
+
+ it 'sets the hostname to the given host argument' do
+ @server.local_address.ip_address.should == ip_address
+ end
+ end
+ end
+
+ describe 'with a String and a custom object' do
+ before do
+ dummy = mock(:dummy)
+ dummy.stub!(:to_str).and_return('0')
+
+ @server = TCPServer.new('127.0.0.1', dummy)
+ end
+
+ after do
+ @server.close
+ end
+
+ it 'sets the port to the given port argument' do
+ @server.local_address.ip_port.should be_kind_of(Integer)
+ @server.local_address.ip_port.should > 0
+ end
+
+ it 'sets the hostname to the given host argument' do
+ @server.local_address.ip_address.should == '127.0.0.1'
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpserver/listen_spec.rb b/spec/ruby/library/socket/tcpserver/listen_spec.rb
new file mode 100644
index 0000000000..c877fdced6
--- /dev/null
+++ b/spec/ruby/library/socket/tcpserver/listen_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'TCPServer#listen' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ end
+
+ after do
+ @server.close
+ end
+
+ it 'returns 0' do
+ @server.listen(1).should == 0
+ end
+
+ it "raises when the given argument can't be coerced to an Integer" do
+ -> { @server.listen('cats') }.should raise_error(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpserver/new_spec.rb b/spec/ruby/library/socket/tcpserver/new_spec.rb
new file mode 100644
index 0000000000..8d9696c9d8
--- /dev/null
+++ b/spec/ruby/library/socket/tcpserver/new_spec.rb
@@ -0,0 +1,131 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "TCPServer.new" do
+ after :each do
+ @server.close if @server && !@server.closed?
+ end
+
+ it "binds to a host and a port" do
+ @server = TCPServer.new('127.0.0.1', 0)
+ addr = @server.addr
+ addr[0].should == 'AF_INET'
+ addr[1].should be_kind_of(Integer)
+ # on some platforms (Mac), MRI
+ # returns comma at the end.
+ addr[2].should =~ /^#{SocketSpecs.hostname}\b/
+ addr[3].should == '127.0.0.1'
+ end
+
+ it "binds to localhost and a port with either IPv4 or IPv6" do
+ @server = TCPServer.new(SocketSpecs.hostname, 0)
+ addr = @server.addr
+ addr[1].should be_kind_of(Integer)
+ if addr[0] == 'AF_INET'
+ addr[2].should =~ /^#{SocketSpecs.hostname}\b/
+ addr[3].should == '127.0.0.1'
+ else
+ addr[2].should =~ /^#{SocketSpecs.hostname('::1')}\b/
+ addr[3].should == '::1'
+ end
+ end
+
+ it "binds to INADDR_ANY if the hostname is empty" do
+ @server = TCPServer.new('', 0)
+ addr = @server.addr
+ addr[0].should == 'AF_INET'
+ addr[1].should be_kind_of(Integer)
+ addr[2].should == '0.0.0.0'
+ addr[3].should == '0.0.0.0'
+ end
+
+ it "binds to INADDR_ANY if the hostname is empty and the port is a string" do
+ @server = TCPServer.new('', '0')
+ addr = @server.addr
+ addr[0].should == 'AF_INET'
+ addr[1].should be_kind_of(Integer)
+ addr[2].should == '0.0.0.0'
+ addr[3].should == '0.0.0.0'
+ end
+
+ it "binds to a port if the port is explicitly nil" do
+ @server = TCPServer.new('', nil)
+ addr = @server.addr
+ addr[0].should == 'AF_INET'
+ addr[1].should be_kind_of(Integer)
+ addr[2].should == '0.0.0.0'
+ addr[3].should == '0.0.0.0'
+ end
+
+ it "binds to a port if the port is an empty string" do
+ @server = TCPServer.new('', '')
+ addr = @server.addr
+ addr[0].should == 'AF_INET'
+ addr[1].should be_kind_of(Integer)
+ addr[2].should == '0.0.0.0'
+ addr[3].should == '0.0.0.0'
+ end
+
+ it "coerces port to string, then determines port from that number or service name" do
+ -> { TCPServer.new(SocketSpecs.hostname, Object.new) }.should raise_error(TypeError)
+
+ port = Object.new
+ port.should_receive(:to_str).and_return("0")
+
+ @server = TCPServer.new(SocketSpecs.hostname, port)
+ addr = @server.addr
+ addr[1].should be_kind_of(Integer)
+
+ # TODO: This should also accept strings like 'https', but I don't know how to
+ # pick such a service port that will be able to reliably bind...
+ end
+
+ it "has a single argument form and treats it as a port number" do
+ @server = TCPServer.new(0)
+ addr = @server.addr
+ addr[1].should be_kind_of(Integer)
+ end
+
+ it "coerces port to a string when it is the only argument" do
+ -> { TCPServer.new(Object.new) }.should raise_error(TypeError)
+
+ port = Object.new
+ port.should_receive(:to_str).and_return("0")
+
+ @server = TCPServer.new(port)
+ addr = @server.addr
+ addr[1].should be_kind_of(Integer)
+ end
+
+ it "raises Errno::EADDRNOTAVAIL when the address is unknown" do
+ -> { TCPServer.new("1.2.3.4", 0) }.should raise_error(Errno::EADDRNOTAVAIL)
+ end
+
+ # There is no way to make this fail-proof on all machines, because
+ # DNS servers like opendns return A records for ANY host, including
+ # traditionally invalidly named ones.
+ quarantine! do
+ it "raises a SocketError when the host is unknown" do
+ -> {
+ TCPServer.new("--notavalidname", 0)
+ }.should raise_error(SocketError)
+ end
+ end
+
+ it "raises Errno::EADDRINUSE when address is already in use" do
+ @server = TCPServer.new('127.0.0.1', 0)
+ -> {
+ @server = TCPServer.new('127.0.0.1', @server.addr[1])
+ }.should raise_error(Errno::EADDRINUSE)
+ end
+
+ platform_is_not :windows, :aix do
+ # A known bug in AIX. getsockopt(2) does not properly set
+ # the fifth argument for SO_REUSEADDR.
+ it "sets SO_REUSEADDR on the resulting server" do
+ @server = TCPServer.new('127.0.0.1', 0)
+ @server.getsockopt(:SOCKET, :REUSEADDR).data.should_not == "\x00\x00\x00\x00"
+ @server.getsockopt(:SOCKET, :REUSEADDR).int.should_not == 0
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpserver/sysaccept_spec.rb b/spec/ruby/library/socket/tcpserver/sysaccept_spec.rb
new file mode 100644
index 0000000000..bd7d33faf4
--- /dev/null
+++ b/spec/ruby/library/socket/tcpserver/sysaccept_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "TCPServer#sysaccept" do
+ before :each do
+ @server = TCPServer.new(SocketSpecs.hostname, 0)
+ @port = @server.addr[1]
+ end
+
+ after :each do
+ @server.close unless @server.closed?
+ end
+
+ it 'blocks if no connections' do
+ -> { @server.sysaccept }.should block_caller
+ end
+
+ it 'returns file descriptor of an accepted connection' do
+ begin
+ sock = TCPSocket.new(SocketSpecs.hostname, @port)
+
+ fd = @server.sysaccept
+
+ fd.should be_kind_of(Integer)
+ ensure
+ sock.close if sock && !sock.closed?
+ IO.for_fd(fd).close if fd
+ end
+ end
+end
+
+describe 'TCPServer#sysaccept' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ end
+
+ after do
+ @server.close
+ end
+
+ describe 'without a connected client' do
+ it 'blocks the caller' do
+ -> { @server.sysaccept }.should block_caller
+ end
+ end
+
+ describe 'with a connected client' do
+ before do
+ @client = TCPSocket.new(ip_address, @server.connect_address.ip_port)
+ end
+
+ after do
+ Socket.for_fd(@fd).close if @fd
+ @client.close
+ end
+
+ it 'returns a new file descriptor as an Integer' do
+ @fd = @server.sysaccept
+
+ @fd.should be_kind_of(Integer)
+ @fd.should_not == @client.fileno
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb b/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb
new file mode 100644
index 0000000000..f0e98778f5
--- /dev/null
+++ b/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+# TODO: verify these for windows
+describe "TCPSocket#gethostbyname" do
+ before :each do
+ suppress_warning do
+ @host_info = TCPSocket.gethostbyname(SocketSpecs.hostname)
+ end
+ end
+
+ it "returns an array elements of information on the hostname" do
+ @host_info.should be_kind_of(Array)
+ end
+
+ platform_is_not :windows do
+ it "returns the canonical name as first value" do
+ @host_info[0].should == SocketSpecs.hostname
+ end
+
+ it "returns the address type as the third value" do
+ address_type = @host_info[2]
+ [Socket::AF_INET, Socket::AF_INET6].include?(address_type).should be_true
+ end
+
+ it "returns the IP address as the fourth value" do
+ ip = @host_info[3]
+ ["127.0.0.1", "::1"].include?(ip).should be_true
+ end
+ end
+
+ platform_is :windows do
+ quarantine! do # name lookup seems not working on Windows CI
+ it "returns the canonical name as first value" do
+ host = "#{ENV['COMPUTERNAME'].downcase}"
+ host << ".#{ENV['USERDNSDOMAIN'].downcase}" if ENV['USERDNSDOMAIN']
+ @host_info[0].should == host
+ end
+ end
+
+ it "returns the address type as the third value" do
+ @host_info[2].should == Socket::AF_INET
+ end
+
+ it "returns the IP address as the fourth value" do
+ @host_info[3].should == "127.0.0.1"
+ end
+ end
+
+ it "returns any aliases to the address as second value" do
+ @host_info[1].should be_kind_of(Array)
+ end
+end
+
+describe 'TCPSocket#gethostbyname' do
+ it 'returns an Array' do
+ suppress_warning do
+ TCPSocket.gethostbyname('127.0.0.1').should be_an_instance_of(Array)
+ end
+ end
+
+ describe 'using a hostname' do
+ describe 'the returned Array' do
+ before do
+ suppress_warning do
+ @array = TCPSocket.gethostbyname('127.0.0.1')
+ end
+ end
+
+ it 'includes the canonical name as the 1st value' do
+ @array[0].should == '127.0.0.1'
+ end
+
+ it 'includes an array of alternative hostnames as the 2nd value' do
+ @array[1].should be_an_instance_of(Array)
+ end
+
+ it 'includes the address family as the 3rd value' do
+ @array[2].should be_kind_of(Integer)
+ end
+
+ it 'includes the IP addresses as all the remaining values' do
+ ips = %w{::1 127.0.0.1}
+
+ ips.include?(@array[3]).should == true
+
+ # Not all machines might have both IPv4 and IPv6 set up, so this value is
+ # optional.
+ ips.include?(@array[4]).should == true if @array[4]
+ end
+ end
+ end
+
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'the returned Array' do
+ before do
+ suppress_warning do
+ @array = TCPSocket.gethostbyname(ip_address)
+ end
+ end
+
+ it 'includes the IP address as the 1st value' do
+ @array[0].should == ip_address
+ end
+
+ it 'includes an empty list of aliases as the 2nd value' do
+ @array[1].should == []
+ end
+
+ it 'includes the address family as the 3rd value' do
+ @array[2].should == family
+ end
+
+ it 'includes the IP address as the 4th value' do
+ @array[3].should == ip_address
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpsocket/initialize_spec.rb b/spec/ruby/library/socket/tcpsocket/initialize_spec.rb
new file mode 100644
index 0000000000..065c8f4190
--- /dev/null
+++ b/spec/ruby/library/socket/tcpsocket/initialize_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/new'
+
+describe 'TCPSocket#initialize' do
+ it_behaves_like :tcpsocket_new, :new
+end
+
+describe 'TCPSocket#initialize' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ describe 'when no server is listening on the given address' do
+ it 'raises Errno::ECONNREFUSED' do
+ -> { TCPSocket.new(ip_address, 666) }.should raise_error(Errno::ECONNREFUSED)
+ end
+ end
+
+ describe 'when a server is listening on the given address' do
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ @port = @server.connect_address.ip_port
+ end
+
+ after do
+ @client.close if @client
+ @server.close
+ end
+
+ it 'returns a TCPSocket when using an Integer as the port' do
+ @client = TCPSocket.new(ip_address, @port)
+ @client.should be_an_instance_of(TCPSocket)
+ end
+
+ it 'returns a TCPSocket when using a String as the port' do
+ @client = TCPSocket.new(ip_address, @port.to_s)
+ @client.should be_an_instance_of(TCPSocket)
+ end
+
+ it 'raises SocketError when the port number is a non numeric String' do
+ -> { TCPSocket.new(ip_address, 'cats') }.should raise_error(SocketError)
+ end
+
+ it 'set the socket to binmode' do
+ @client = TCPSocket.new(ip_address, @port)
+ @client.binmode?.should be_true
+ end
+
+ it 'connects to the right address' do
+ @client = TCPSocket.new(ip_address, @port)
+
+ @client.remote_address.ip_address.should == @server.local_address.ip_address
+ @client.remote_address.ip_port.should == @server.local_address.ip_port
+ end
+
+ describe 'using a local address and service' do
+ it 'binds the client socket to the local address and service' do
+ @client = TCPSocket.new(ip_address, @port, ip_address, 0)
+
+ @client.local_address.ip_address.should == ip_address
+
+ @client.local_address.ip_port.should > 0
+ @client.local_address.ip_port.should_not == @port
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpsocket/local_address_spec.rb b/spec/ruby/library/socket/tcpsocket/local_address_spec.rb
new file mode 100644
index 0000000000..ce66d5ff8f
--- /dev/null
+++ b/spec/ruby/library/socket/tcpsocket/local_address_spec.rb
@@ -0,0 +1,73 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'TCPSocket#local_address' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ @host = @server.connect_address.ip_address
+ @port = @server.connect_address.ip_port
+ end
+
+ after do
+ @server.close
+ end
+
+ describe 'using an explicit hostname' do
+ before do
+ @sock = TCPSocket.new(@host, @port)
+ end
+
+ after do
+ @sock.close
+ end
+
+ it 'returns an Addrinfo' do
+ @sock.local_address.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses AF_INET as the address family' do
+ @sock.local_address.afamily.should == family
+ end
+
+ it 'uses PF_INET as the protocol family' do
+ @sock.local_address.pfamily.should == family
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @sock.local_address.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses the correct IP address' do
+ @sock.local_address.ip_address.should == @host
+ end
+
+ it 'uses a randomly assigned local port' do
+ @sock.local_address.ip_port.should > 0
+ @sock.local_address.ip_port.should_not == @port
+ end
+
+ it 'uses 0 as the protocol' do
+ @sock.local_address.protocol.should == 0
+ end
+ end
+ end
+
+ describe 'using an implicit hostname' do
+ before do
+ @sock = TCPSocket.new(nil, @port)
+ end
+
+ after do
+ @sock.close
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses the correct IP address' do
+ @sock.local_address.ip_address.should == @host
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpsocket/open_spec.rb b/spec/ruby/library/socket/tcpsocket/open_spec.rb
new file mode 100644
index 0000000000..31b630a23b
--- /dev/null
+++ b/spec/ruby/library/socket/tcpsocket/open_spec.rb
@@ -0,0 +1,5 @@
+require_relative 'shared/new'
+
+describe "TCPSocket.open" do
+ it_behaves_like :tcpsocket_new, :open
+end
diff --git a/spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb b/spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb
new file mode 100644
index 0000000000..a381627a39
--- /dev/null
+++ b/spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/partially_closable_sockets'
+
+describe "TCPSocket partial closability" do
+
+ before :each do
+ @server = TCPServer.new("127.0.0.1", 0)
+ @s1 = TCPSocket.new("127.0.0.1", @server.addr[1])
+ @s2 = @server.accept
+ end
+
+ after :each do
+ @server.close
+ @s1.close
+ @s2.close
+ end
+
+ it_should_behave_like "partially closable sockets"
+
+end
diff --git a/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb b/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb
new file mode 100644
index 0000000000..6ce5a41b58
--- /dev/null
+++ b/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "TCPSocket#recv_nonblock" do
+ before :each do
+ @server = SocketSpecs::SpecTCPServer.new
+ @hostname = @server.hostname
+ end
+
+ after :each do
+ if @socket
+ @socket.write "QUIT"
+ @socket.close
+ end
+ @server.shutdown
+ end
+
+ it "returns a String read from the socket" do
+ @socket = TCPSocket.new @hostname, @server.port
+ @socket.write "TCPSocket#recv_nonblock"
+
+ # Wait for the server to echo. This spec is testing the return
+ # value, not the non-blocking behavior.
+ #
+ # TODO: Figure out a good way to test non-blocking.
+ IO.select([@socket])
+ @socket.recv_nonblock(50).should == "TCPSocket#recv_nonblock"
+ end
+
+ it 'writes the read to a buffer from the socket' do
+ @socket = TCPSocket.new @hostname, @server.port
+ @socket.write "TCPSocket#recv_nonblock"
+
+ # Wait for the server to echo. This spec is testing the return
+ # value, not the non-blocking behavior.
+ #
+ # TODO: Figure out a good way to test non-blocking.
+ IO.select([@socket])
+ buffer = "".b
+ @socket.recv_nonblock(50, 0, buffer)
+ buffer.should == 'TCPSocket#recv_nonblock'
+ end
+
+ it 'returns :wait_readable in exceptionless mode' do
+ @socket = TCPSocket.new @hostname, @server.port
+ @socket.recv_nonblock(50, exception: false).should == :wait_readable
+ end
+end
diff --git a/spec/ruby/library/socket/tcpsocket/recv_spec.rb b/spec/ruby/library/socket/tcpsocket/recv_spec.rb
new file mode 100644
index 0000000000..f380db670d
--- /dev/null
+++ b/spec/ruby/library/socket/tcpsocket/recv_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'TCPSocket#recv' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ @client = TCPSocket.new(ip_address, @server.connect_address.ip_port)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'returns the message data' do
+ @client.write('hello')
+
+ socket = @server.accept
+
+ begin
+ socket.recv(5).should == 'hello'
+ ensure
+ socket.close
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpsocket/remote_address_spec.rb b/spec/ruby/library/socket/tcpsocket/remote_address_spec.rb
new file mode 100644
index 0000000000..eb9dabc075
--- /dev/null
+++ b/spec/ruby/library/socket/tcpsocket/remote_address_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'TCPSocket#remote_address' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = TCPServer.new(ip_address, 0)
+ @host = @server.connect_address.ip_address
+ @port = @server.connect_address.ip_port
+ end
+
+ after do
+ @server.close
+ end
+
+ describe 'using an explicit hostname' do
+ before do
+ @sock = TCPSocket.new(@host, @port)
+ end
+
+ after do
+ @sock.close
+ end
+
+ it 'returns an Addrinfo' do
+ @sock.remote_address.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses AF_INET as the address family' do
+ @sock.remote_address.afamily.should == family
+ end
+
+ it 'uses PF_INET as the protocol family' do
+ @sock.remote_address.pfamily.should == family
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @sock.remote_address.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses the correct IP address' do
+ @sock.remote_address.ip_address.should == @host
+ end
+
+ it 'uses the correct port' do
+ @sock.remote_address.ip_port.should == @port
+ end
+
+ it 'uses 0 as the protocol' do
+ @sock.remote_address.protocol.should == 0
+ end
+ end
+ end
+
+ describe 'using an implicit hostname' do
+ before do
+ @sock = TCPSocket.new(nil, @port)
+ end
+
+ after do
+ @sock.close
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses the correct IP address' do
+ @sock.remote_address.ip_address.should == @host
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb b/spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb
new file mode 100644
index 0000000000..8b728b7522
--- /dev/null
+++ b/spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "TCPSocket#setsockopt" do
+ before :each do
+ @server = SocketSpecs::SpecTCPServer.new
+ @hostname = @server.hostname
+ @sock = TCPSocket.new @hostname, @server.port
+ end
+
+ after :each do
+ @sock.close unless @sock.closed?
+ @server.shutdown
+ end
+
+ describe "using constants" do
+ it "sets the TCP nodelay to 1" do
+ @sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1).should == 0
+ end
+ end
+
+ describe "using symbols" do
+ it "sets the TCP nodelay to 1" do
+ @sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, 1).should == 0
+ end
+
+ context "without prefix" do
+ it "sets the TCP nodelay to 1" do
+ @sock.setsockopt(:TCP, :NODELAY, 1).should == 0
+ end
+ end
+ end
+
+ describe "using strings" do
+ it "sets the TCP nodelay to 1" do
+ @sock.setsockopt('IPPROTO_TCP', 'TCP_NODELAY', 1).should == 0
+ end
+
+ context "without prefix" do
+ it "sets the TCP nodelay to 1" do
+ @sock.setsockopt('TCP', 'NODELAY', 1).should == 0
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/tcpsocket/shared/new.rb b/spec/ruby/library/socket/tcpsocket/shared/new.rb
new file mode 100644
index 0000000000..e7eb2f3c13
--- /dev/null
+++ b/spec/ruby/library/socket/tcpsocket/shared/new.rb
@@ -0,0 +1,102 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/classes'
+
+describe :tcpsocket_new, shared: true do
+ it "requires a hostname and a port as arguments" do
+ -> { TCPSocket.send(@method) }.should raise_error(ArgumentError)
+ end
+
+ it "refuses the connection when there is no server to connect to" do
+ -> do
+ TCPSocket.send(@method, SocketSpecs.hostname, SocketSpecs.reserved_unused_port)
+ end.should raise_error(SystemCallError) {|e|
+ [Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL].should include(e.class)
+ }
+ end
+
+ ruby_version_is "3.0"..."3.1" do
+ it 'raises Errno::ETIMEDOUT with :connect_timeout when no server is listening on the given address' do
+ -> {
+ TCPSocket.send(@method, "192.0.2.1", 80, connect_timeout: 0)
+ }.should raise_error(Errno::ETIMEDOUT)
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it 'raises IO::TimeoutError with :connect_timeout when no server is listening on the given address' do
+ -> {
+ TCPSocket.send(@method, "192.0.2.1", 80, connect_timeout: 0)
+ }.should raise_error(IO::TimeoutError)
+ end
+ end
+
+ describe "with a running server" do
+ before :each do
+ @server = SocketSpecs::SpecTCPServer.new
+ @hostname = @server.hostname
+ end
+
+ after :each do
+ if @socket
+ @socket.write "QUIT"
+ @socket.close
+ end
+ @server.shutdown
+ end
+
+ it "silently ignores 'nil' as the third parameter" do
+ @socket = TCPSocket.send(@method, @hostname, @server.port, nil)
+ @socket.should be_an_instance_of(TCPSocket)
+ end
+
+ it "connects to a listening server with host and port" do
+ @socket = TCPSocket.send(@method, @hostname, @server.port)
+ @socket.should be_an_instance_of(TCPSocket)
+ end
+
+ it "connects to a server when passed local_host argument" do
+ @socket = TCPSocket.send(@method, @hostname, @server.port, @hostname)
+ @socket.should be_an_instance_of(TCPSocket)
+ end
+
+ it "connects to a server when passed local_host and local_port arguments" do
+ server = TCPServer.new(SocketSpecs.hostname, 0)
+ begin
+ available_port = server.addr[1]
+ ensure
+ server.close
+ end
+ @socket = TCPSocket.send(@method, @hostname, @server.port,
+ @hostname, available_port)
+ @socket.should be_an_instance_of(TCPSocket)
+ end
+
+ it "has an address once it has connected to a listening server" do
+ @socket = TCPSocket.send(@method, @hostname, @server.port)
+ @socket.should be_an_instance_of(TCPSocket)
+
+ # TODO: Figure out how to abstract this. You can get AF_INET
+ # from 'Socket.getaddrinfo(hostname, nil)[0][3]' but socket.addr
+ # will return AF_INET6. At least this check will weed out clearly
+ # erroneous values.
+ @socket.addr[0].should =~ /^AF_INET6?/
+
+ case @socket.addr[0]
+ when 'AF_INET'
+ @socket.addr[3].should == SocketSpecs.addr(:ipv4)
+ when 'AF_INET6'
+ @socket.addr[3].should == SocketSpecs.addr(:ipv6)
+ end
+
+ @socket.addr[1].should be_kind_of(Integer)
+ @socket.addr[2].should =~ /^#{@hostname}/
+ end
+
+ ruby_version_is "3.0" do
+ it "connects to a server when passed connect_timeout argument" do
+ @socket = TCPSocket.send(@method, @hostname, @server.port, connect_timeout: 1)
+ @socket.should be_an_instance_of(TCPSocket)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/bind_spec.rb b/spec/ruby/library/socket/udpsocket/bind_spec.rb
new file mode 100644
index 0000000000..08b386e941
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/bind_spec.rb
@@ -0,0 +1,83 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UDPSocket#bind" do
+ before :each do
+ @socket = UDPSocket.new
+ end
+
+ after :each do
+ @socket.close unless @socket.closed?
+ end
+
+ it "binds the socket to a port" do
+ @socket.bind(SocketSpecs.hostname, 0)
+ @socket.addr[1].should be_kind_of(Integer)
+ end
+
+ it "raises Errno::EINVAL when already bound" do
+ @socket.bind(SocketSpecs.hostname, 0)
+
+ -> {
+ @socket.bind(SocketSpecs.hostname, @socket.addr[1])
+ }.should raise_error(Errno::EINVAL)
+ end
+
+ it "receives a hostname and a port" do
+ @socket.bind(SocketSpecs.hostname, 0)
+
+ port, host = Socket.unpack_sockaddr_in(@socket.getsockname)
+
+ host.should == "127.0.0.1"
+ port.should == @socket.addr[1]
+ end
+
+ it "binds to INADDR_ANY if the hostname is empty" do
+ @socket.bind("", 0).should == 0
+ port, host = Socket.unpack_sockaddr_in(@socket.getsockname)
+ host.should == "0.0.0.0"
+ port.should == @socket.addr[1]
+ end
+end
+
+describe 'UDPSocket#bind' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @socket = UDPSocket.new(family)
+ end
+
+ after do
+ @socket.close
+ end
+
+ it 'binds to an address and port' do
+ @socket.bind(ip_address, 0).should == 0
+
+ @socket.local_address.ip_address.should == ip_address
+ @socket.local_address.ip_port.should > 0
+ end
+
+ it 'binds to an address and port using String arguments' do
+ @socket.bind(ip_address, '0').should == 0
+
+ @socket.local_address.ip_address.should == ip_address
+ @socket.local_address.ip_port.should > 0
+ end
+
+ it 'can receive data after being bound to an address' do
+ @socket.bind(ip_address, 0)
+
+ addr = @socket.connect_address
+ client = UDPSocket.new(family)
+
+ client.connect(addr.ip_address, addr.ip_port)
+ client.write('hello')
+
+ begin
+ @socket.recv(6).should == 'hello'
+ ensure
+ client.close
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/connect_spec.rb b/spec/ruby/library/socket/udpsocket/connect_spec.rb
new file mode 100644
index 0000000000..d92bdeb981
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/connect_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'UDPSocket#connect' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @socket = UDPSocket.new(family)
+ end
+
+ after do
+ @socket.close
+ end
+
+ it 'connects to an address even when it is not used' do
+ @socket.connect(ip_address, 9996).should == 0
+ end
+
+ it 'can send data after connecting' do
+ receiver = UDPSocket.new(family)
+
+ receiver.bind(ip_address, 0)
+
+ addr = receiver.connect_address
+
+ @socket.connect(addr.ip_address, addr.ip_port)
+ @socket.write('hello')
+
+ begin
+ receiver.recv(6).should == 'hello'
+ ensure
+ receiver.close
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/initialize_spec.rb b/spec/ruby/library/socket/udpsocket/initialize_spec.rb
new file mode 100644
index 0000000000..1d635149f7
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/initialize_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../spec_helper'
+
+describe 'UDPSocket#initialize' do
+ after do
+ @socket.close if @socket
+ end
+
+ it 'initializes a new UDPSocket' do
+ @socket = UDPSocket.new
+ @socket.should be_an_instance_of(UDPSocket)
+ end
+
+ it 'initializes a new UDPSocket using an Integer' do
+ @socket = UDPSocket.new(Socket::AF_INET)
+ @socket.should be_an_instance_of(UDPSocket)
+ end
+
+ it 'initializes a new UDPSocket using a Symbol' do
+ @socket = UDPSocket.new(:INET)
+ @socket.should be_an_instance_of(UDPSocket)
+ end
+
+ it 'initializes a new UDPSocket using a String' do
+ @socket = UDPSocket.new('INET')
+ @socket.should be_an_instance_of(UDPSocket)
+ end
+
+ it 'sets the socket to binmode' do
+ @socket = UDPSocket.new(:INET)
+ @socket.binmode?.should be_true
+ end
+
+ it 'raises Errno::EAFNOSUPPORT or Errno::EPROTONOSUPPORT when given an invalid address family' do
+ -> {
+ UDPSocket.new(666)
+ }.should raise_error(SystemCallError) { |e|
+ [Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT].should include(e.class)
+ }
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/inspect_spec.rb b/spec/ruby/library/socket/udpsocket/inspect_spec.rb
new file mode 100644
index 0000000000..e212120b14
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/inspect_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../spec_helper'
+
+describe 'UDPSocket#inspect' do
+ before do
+ @socket = UDPSocket.new
+ @socket.bind('127.0.0.1', 0)
+ end
+
+ after do
+ @socket.close
+ end
+
+ it 'returns a String with the fd, family, address and port' do
+ port = @socket.addr[1]
+ @socket.inspect.should == "#<UDPSocket:fd #{@socket.fileno}, AF_INET, 127.0.0.1, #{port}>"
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/local_address_spec.rb b/spec/ruby/library/socket/udpsocket/local_address_spec.rb
new file mode 100644
index 0000000000..92e4cc10c7
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/local_address_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'UDPSocket#local_address' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :DGRAM, Socket::IPPROTO_UDP)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+
+ @host = @server.connect_address.ip_address
+ @port = @server.connect_address.ip_port
+ end
+
+ after do
+ @server.close
+ end
+
+ describe 'using an explicit hostname' do
+ before do
+ @sock = UDPSocket.new(family)
+
+ @sock.connect(@host, @port)
+ end
+
+ after do
+ @sock.close
+ end
+
+ it 'returns an Addrinfo' do
+ @sock.local_address.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses the correct address family' do
+ @sock.local_address.afamily.should == family
+ end
+
+ it 'uses the correct protocol family' do
+ @sock.local_address.pfamily.should == family
+ end
+
+ it 'uses SOCK_DGRAM as the socket type' do
+ @sock.local_address.socktype.should == Socket::SOCK_DGRAM
+ end
+
+ it 'uses the correct IP address' do
+ @sock.local_address.ip_address.should == @host
+ end
+
+ it 'uses a randomly assigned local port' do
+ @sock.local_address.ip_port.should > 0
+ @sock.local_address.ip_port.should_not == @port
+ end
+
+ it 'uses 0 as the protocol' do
+ @sock.local_address.protocol.should == 0
+ end
+ end
+ end
+
+ describe 'using an implicit hostname' do
+ before do
+ @sock = UDPSocket.new(family)
+
+ @sock.connect(nil, @port)
+ end
+
+ after do
+ @sock.close
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses the correct IP address' do
+ @sock.local_address.ip_address.should == @host
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/new_spec.rb b/spec/ruby/library/socket/udpsocket/new_spec.rb
new file mode 100644
index 0000000000..6cc0cadbcb
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/new_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'UDPSocket.new' do
+ after :each do
+ @socket.close if @socket && !@socket.closed?
+ end
+
+ it 'without arguments' do
+ @socket = UDPSocket.new
+ @socket.should be_an_instance_of(UDPSocket)
+ end
+
+ it 'using Integer argument' do
+ @socket = UDPSocket.new(Socket::AF_INET)
+ @socket.should be_an_instance_of(UDPSocket)
+ end
+
+ it 'using Symbol argument' do
+ @socket = UDPSocket.new(:INET)
+ @socket.should be_an_instance_of(UDPSocket)
+ end
+
+ it 'using String argument' do
+ @socket = UDPSocket.new('INET')
+ @socket.should be_an_instance_of(UDPSocket)
+ end
+
+ it 'raises Errno::EAFNOSUPPORT or Errno::EPROTONOSUPPORT if unsupported family passed' do
+ -> { UDPSocket.new(-1) }.should raise_error(SystemCallError) { |e|
+ [Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT].should include(e.class)
+ }
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/open_spec.rb b/spec/ruby/library/socket/udpsocket/open_spec.rb
new file mode 100644
index 0000000000..e4dbb2ee2a
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/open_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UDPSocket.open" do
+ after :each do
+ @socket.close if @socket && !@socket.closed?
+ end
+
+ it "allows calls to open without arguments" do
+ @socket = UDPSocket.open
+ @socket.should be_kind_of(UDPSocket)
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb b/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb
new file mode 100644
index 0000000000..650a061221
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb
@@ -0,0 +1,102 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'UDPSocket#recvfrom_nonblock' do
+ SocketSpecs.each_ip_protocol do |family, ip_address, family_name|
+ before do
+ @server = UDPSocket.new(family)
+ @client = UDPSocket.new(family)
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ platform_is_not :windows do
+ describe 'using an unbound socket' do
+ it 'raises IO::WaitReadable' do
+ -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable)
+ end
+ end
+ end
+
+ describe 'using a bound socket' do
+ before do
+ @server.bind(ip_address, 0)
+
+ addr = @server.connect_address
+
+ @client.connect(addr.ip_address, addr.ip_port)
+ end
+
+ describe 'without any data available' do
+ it 'raises IO::WaitReadable' do
+ -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable)
+ end
+
+ it 'returns :wait_readable with exception: false' do
+ @server.recvfrom_nonblock(1, exception: false).should == :wait_readable
+ end
+ end
+
+ platform_is_not :windows do
+ describe 'with data available' do
+ before do
+ @client.write('hello')
+ end
+
+ it 'returns an Array containing the data and an Array' do
+ IO.select([@server])
+ @server.recvfrom_nonblock(1).should be_an_instance_of(Array)
+ end
+
+ it 'writes the data to the buffer when one is present' do
+ buffer = "".b
+ IO.select([@server])
+ @server.recvfrom_nonblock(1, 0, buffer)
+ buffer.should == 'h'
+ end
+
+ describe 'the returned Array' do
+ before do
+ IO.select([@server])
+ @array = @server.recvfrom_nonblock(1)
+ end
+
+ it 'contains the data at index 0' do
+ @array[0].should == 'h'
+ end
+
+ it 'contains an Array at index 1' do
+ @array[1].should be_an_instance_of(Array)
+ end
+ end
+
+ describe 'the returned address Array' do
+ before do
+ IO.select([@server])
+ @addr = @server.recvfrom_nonblock(1)[1]
+ end
+
+ it 'uses the correct address family' do
+ @addr[0].should == family_name
+ end
+
+ it 'uses the port of the client' do
+ @addr[1].should == @client.local_address.ip_port
+ end
+
+ it 'uses the hostname of the client' do
+ @addr[2].should == ip_address
+ end
+
+ it 'uses the IP address of the client' do
+ @addr[3].should == ip_address
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/remote_address_spec.rb b/spec/ruby/library/socket/udpsocket/remote_address_spec.rb
new file mode 100644
index 0000000000..94889ce560
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/remote_address_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe 'UDPSocket#remote_address' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = Socket.new(family, :DGRAM, Socket::IPPROTO_UDP)
+
+ @server.bind(Socket.sockaddr_in(0, ip_address))
+
+ @host = @server.connect_address.ip_address
+ @port = @server.connect_address.ip_port
+ end
+
+ after do
+ @server.close
+ end
+
+ describe 'using an explicit hostname' do
+ before do
+ @sock = UDPSocket.new(family)
+
+ @sock.connect(@host, @port)
+ end
+
+ after do
+ @sock.close
+ end
+
+ it 'returns an Addrinfo' do
+ @sock.remote_address.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses the correct address family' do
+ @sock.remote_address.afamily.should == family
+ end
+
+ it 'uses the correct protocol family' do
+ @sock.remote_address.pfamily.should == family
+ end
+
+ it 'uses SOCK_DGRAM as the socket type' do
+ @sock.remote_address.socktype.should == Socket::SOCK_DGRAM
+ end
+
+ it 'uses the correct IP address' do
+ @sock.remote_address.ip_address.should == @host
+ end
+
+ it 'uses the correct port' do
+ @sock.remote_address.ip_port.should == @port
+ end
+
+ it 'uses 0 as the protocol' do
+ @sock.remote_address.protocol.should == 0
+ end
+ end
+ end
+
+ describe 'using an implicit hostname' do
+ before do
+ @sock = UDPSocket.new(family)
+
+ @sock.connect(nil, @port)
+ end
+
+ after do
+ @sock.close
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses the correct IP address' do
+ @sock.remote_address.ip_address.should == @host
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/send_spec.rb b/spec/ruby/library/socket/udpsocket/send_spec.rb
new file mode 100644
index 0000000000..5d5de684af
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/send_spec.rb
@@ -0,0 +1,154 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UDPSocket#send" do
+ before :each do
+ @port = nil
+ @server_thread = Thread.new do
+ @server = UDPSocket.open
+ begin
+ @server.bind(nil, 0)
+ @port = @server.addr[1]
+ begin
+ @msg = @server.recvfrom_nonblock(64)
+ rescue IO::WaitReadable
+ IO.select([@server])
+ retry
+ end
+ ensure
+ @server.close if !@server.closed?
+ end
+ end
+ Thread.pass while @server_thread.status and !@port
+ end
+
+ after :each do
+ @server_thread.join
+ end
+
+ it "sends data in ad hoc mode" do
+ @socket = UDPSocket.open
+ @socket.send("ad hoc", 0, SocketSpecs.hostname, @port)
+ @socket.close
+ @server_thread.join
+
+ @msg[0].should == "ad hoc"
+ @msg[1][0].should == "AF_INET"
+ @msg[1][1].should be_kind_of(Integer)
+ @msg[1][3].should == "127.0.0.1"
+ end
+
+ it "sends data in ad hoc mode (with port given as a String)" do
+ @socket = UDPSocket.open
+ @socket.send("ad hoc", 0, SocketSpecs.hostname, @port.to_s)
+ @socket.close
+ @server_thread.join
+
+ @msg[0].should == "ad hoc"
+ @msg[1][0].should == "AF_INET"
+ @msg[1][1].should be_kind_of(Integer)
+ @msg[1][3].should == "127.0.0.1"
+ end
+
+ it "sends data in connection mode" do
+ @socket = UDPSocket.open
+ @socket.connect(SocketSpecs.hostname, @port)
+ @socket.send("connection-based", 0)
+ @socket.close
+ @server_thread.join
+
+ @msg[0].should == "connection-based"
+ @msg[1][0].should == "AF_INET"
+ @msg[1][1].should be_kind_of(Integer)
+ @msg[1][3].should == "127.0.0.1"
+ end
+
+ it "raises EMSGSIZE if data is too too big" do
+ @socket = UDPSocket.open
+ begin
+ -> do
+ @socket.send('1' * 100_000, 0, SocketSpecs.hostname, @port.to_s)
+ end.should raise_error(Errno::EMSGSIZE)
+ ensure
+ @socket.send("ad hoc", 0, SocketSpecs.hostname, @port)
+ @socket.close
+ @server_thread.join
+ end
+ end
+end
+
+describe 'UDPSocket#send' do
+ SocketSpecs.each_ip_protocol do |family, ip_address|
+ before do
+ @server = UDPSocket.new(family)
+ @client = UDPSocket.new(family)
+
+ @server.bind(ip_address, 0)
+
+ @addr = @server.connect_address
+ end
+
+ after do
+ @server.close
+ @client.close
+ end
+
+ describe 'using a disconnected socket' do
+ describe 'without a destination address' do
+ it "raises #{SocketSpecs.dest_addr_req_error}" do
+ -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error)
+ end
+ end
+
+ describe 'with a destination address as separate arguments' do
+ it 'returns the amount of sent bytes' do
+ @client.send('hello', 0, @addr.ip_address, @addr.ip_port).should == 5
+ end
+
+ it 'does not persist the connection after sending data' do
+ @client.send('hello', 0, @addr.ip_address, @addr.ip_port)
+
+ -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error)
+ end
+ end
+
+ describe 'with a destination address as a single String argument' do
+ it 'returns the amount of sent bytes' do
+ @client.send('hello', 0, @server.getsockname).should == 5
+ end
+ end
+ end
+
+ describe 'using a connected socket' do
+ describe 'without an explicit destination address' do
+ before do
+ @client.connect(@addr.ip_address, @addr.ip_port)
+ end
+
+ it 'returns the amount of bytes written' do
+ @client.send('hello', 0).should == 5
+ end
+ end
+
+ describe 'with an explicit destination address' do
+ before do
+ @alt_server = UDPSocket.new(family)
+
+ @alt_server.bind(ip_address, 0)
+ end
+
+ after do
+ @alt_server.close
+ end
+
+ it 'sends the data to the given address instead' do
+ @client.send('hello', 0, @alt_server.getsockname).should == 5
+
+ -> { @server.recv(5) }.should block_caller
+
+ @alt_server.recv(5).should == 'hello'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/udpsocket/write_spec.rb b/spec/ruby/library/socket/udpsocket/write_spec.rb
new file mode 100644
index 0000000000..c971f29b62
--- /dev/null
+++ b/spec/ruby/library/socket/udpsocket/write_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UDPSocket#write" do
+ it "raises EMSGSIZE if msg is too long" do
+ begin
+ host = SocketSpecs.hostname
+ s1 = UDPSocket.new
+ s1.bind(host, 0)
+ s2 = UDPSocket.new
+ s2.connect(host, s1.addr[1])
+
+ -> do
+ s2.write('1' * 100_000)
+ end.should raise_error(Errno::EMSGSIZE)
+ ensure
+ s1.close if s1 && !s1.closed?
+ s2.close if s2 && !s2.closed?
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb b/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb
new file mode 100644
index 0000000000..30688b46b6
--- /dev/null
+++ b/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb
@@ -0,0 +1,92 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UNIXServer#accept_nonblock" do
+
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ @client = UNIXSocket.open(@path)
+
+ @socket = @server.accept_nonblock
+ @client.send("foobar", 0)
+ end
+
+ after :each do
+ @socket.close
+ @client.close
+ @server.close
+ SocketSpecs.rm_socket @path
+ end
+
+ it "accepts a connection in a non-blocking way" do
+ data = @socket.recvfrom(6).first
+ data.should == "foobar"
+ end
+
+ it "returns a UNIXSocket" do
+ @socket.should be_kind_of(UNIXSocket)
+ end
+
+ it 'returns :wait_readable in exceptionless mode' do
+ @server.accept_nonblock(exception: false).should == :wait_readable
+ end
+ end
+end
+
+with_feature :unix_socket do
+ describe 'UNIXServer#accept_nonblock' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ end
+
+ after do
+ @server.close
+ rm_r(@path)
+ end
+
+ describe 'without a client' do
+ it 'raises IO::WaitReadable' do
+ -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable)
+ end
+ end
+
+ describe 'with a client' do
+ before do
+ @client = UNIXSocket.new(@path)
+ end
+
+ after do
+ @client.close
+ @socket.close if @socket
+ end
+
+ describe 'without any data' do
+ it 'returns a UNIXSocket' do
+ @socket = @server.accept_nonblock
+ @socket.should be_an_instance_of(UNIXSocket)
+ end
+ end
+
+ describe 'with data available' do
+ before do
+ @client.write('hello')
+ end
+
+ it 'returns a UNIXSocket' do
+ @socket = @server.accept_nonblock
+ @socket.should be_an_instance_of(UNIXSocket)
+ end
+
+ describe 'the returned UNIXSocket' do
+ it 'can read the data written' do
+ @socket = @server.accept_nonblock
+ @socket.recv(5).should == 'hello'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixserver/accept_spec.rb b/spec/ruby/library/socket/unixserver/accept_spec.rb
new file mode 100644
index 0000000000..c05fbe7f22
--- /dev/null
+++ b/spec/ruby/library/socket/unixserver/accept_spec.rb
@@ -0,0 +1,117 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+platform_is_not :windows do
+ describe "UNIXServer#accept" do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ end
+
+ after :each do
+ @server.close if @server
+ SocketSpecs.rm_socket @path
+ end
+
+ it "accepts what is written by the client" do
+ client = UNIXSocket.open(@path)
+
+ client.send('hello', 0)
+
+ sock = @server.accept
+ begin
+ data, info = sock.recvfrom(5)
+
+ data.should == 'hello'
+ info.should_not be_empty
+ ensure
+ sock.close
+ client.close
+ end
+ end
+
+ it "can be interrupted by Thread#kill" do
+ t = Thread.new {
+ @server.accept
+ }
+ Thread.pass while t.status and t.status != "sleep"
+
+ # kill thread, ensure it dies in a reasonable amount of time
+ t.kill
+ a = 0
+ while t.alive? and a < 5000
+ sleep 0.001
+ a += 1
+ end
+ a.should < 5000
+ end
+
+ it "can be interrupted by Thread#raise" do
+ t = Thread.new {
+ -> {
+ @server.accept
+ }.should raise_error(Exception, "interrupted")
+ }
+
+ Thread.pass while t.status and t.status != "sleep"
+ t.raise Exception, "interrupted"
+ t.join
+ end
+ end
+end
+
+with_feature :unix_socket do
+ describe 'UNIXServer#accept' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ end
+
+ after do
+ @server.close
+ rm_r(@path)
+ end
+
+ describe 'without a client' do
+ it 'blocks the calling thread' do
+ -> { @server.accept }.should block_caller
+ end
+ end
+
+ describe 'with a client' do
+ before do
+ @client = UNIXSocket.new(@path)
+ end
+
+ after do
+ @client.close
+ @socket.close if @socket
+ end
+
+ describe 'without any data' do
+ it 'returns a UNIXSocket' do
+ @socket = @server.accept
+ @socket.should be_an_instance_of(UNIXSocket)
+ end
+ end
+
+ describe 'with data available' do
+ before do
+ @client.write('hello')
+ end
+
+ it 'returns a UNIXSocket' do
+ @socket = @server.accept
+ @socket.should be_an_instance_of(UNIXSocket)
+ end
+
+ describe 'the returned UNIXSocket' do
+ it 'can read the data written' do
+ @socket = @server.accept
+ @socket.recv(5).should == 'hello'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixserver/for_fd_spec.rb b/spec/ruby/library/socket/unixserver/for_fd_spec.rb
new file mode 100644
index 0000000000..4f3816ad37
--- /dev/null
+++ b/spec/ruby/library/socket/unixserver/for_fd_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+platform_is_not :windows do
+ describe "UNIXServer#for_fd" do
+ before :each do
+ @unix_path = SocketSpecs.socket_path
+ @unix = UNIXServer.new(@unix_path)
+ end
+
+ after :each do
+ @unix.close if @unix
+ SocketSpecs.rm_socket @unix_path
+ end
+
+ it "can calculate the path" do
+ b = UNIXServer.for_fd(@unix.fileno)
+ b.autoclose = false
+
+ b.path.should == @unix_path
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixserver/initialize_spec.rb b/spec/ruby/library/socket/unixserver/initialize_spec.rb
new file mode 100644
index 0000000000..0cc49ef1eb
--- /dev/null
+++ b/spec/ruby/library/socket/unixserver/initialize_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+with_feature :unix_socket do
+ describe 'UNIXServer#initialize' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ end
+
+ after do
+ @server.close if @server
+ rm_r @path
+ end
+
+ it 'returns a new UNIXServer' do
+ @server.should be_an_instance_of(UNIXServer)
+ end
+
+ it 'sets the socket to binmode' do
+ @server.binmode?.should be_true
+ end
+
+ it 'raises Errno::EADDRINUSE when the socket is already in use' do
+ -> { UNIXServer.new(@path) }.should raise_error(Errno::EADDRINUSE)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixserver/listen_spec.rb b/spec/ruby/library/socket/unixserver/listen_spec.rb
new file mode 100644
index 0000000000..b90b3bbb09
--- /dev/null
+++ b/spec/ruby/library/socket/unixserver/listen_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+with_feature :unix_socket do
+ describe 'UNIXServer#listen' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ end
+
+ after do
+ @server.close
+
+ rm_r(@path)
+ end
+
+ it 'returns 0' do
+ @server.listen(1).should == 0
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixserver/new_spec.rb b/spec/ruby/library/socket/unixserver/new_spec.rb
new file mode 100644
index 0000000000..f831f40bc6
--- /dev/null
+++ b/spec/ruby/library/socket/unixserver/new_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../spec_helper'
+require_relative 'shared/new'
+
+describe "UNIXServer.new" do
+ it_behaves_like :unixserver_new, :new
+end
diff --git a/spec/ruby/library/socket/unixserver/open_spec.rb b/spec/ruby/library/socket/unixserver/open_spec.rb
new file mode 100644
index 0000000000..f2506d9f6f
--- /dev/null
+++ b/spec/ruby/library/socket/unixserver/open_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/new'
+
+describe "UNIXServer.open" do
+ it_behaves_like :unixserver_new, :open
+
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ end
+
+ after :each do
+ @server.close if @server
+ @server = nil
+ SocketSpecs.rm_socket @path
+ end
+
+ it "yields the new UNIXServer object to the block, if given" do
+ UNIXServer.open(@path) do |unix|
+ unix.path.should == @path
+ unix.addr.should == ["AF_UNIX", @path]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixserver/shared/new.rb b/spec/ruby/library/socket/unixserver/shared/new.rb
new file mode 100644
index 0000000000..35395826c9
--- /dev/null
+++ b/spec/ruby/library/socket/unixserver/shared/new.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/classes'
+
+describe :unixserver_new, shared: true do
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ end
+
+ after :each do
+ @server.close if @server
+ @server = nil
+ SocketSpecs.rm_socket @path
+ end
+
+ it "creates a new UNIXServer" do
+ @server = UNIXServer.send(@method, @path)
+ @server.path.should == @path
+ @server.addr.should == ["AF_UNIX", @path]
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixserver/sysaccept_spec.rb b/spec/ruby/library/socket/unixserver/sysaccept_spec.rb
new file mode 100644
index 0000000000..e59731878a
--- /dev/null
+++ b/spec/ruby/library/socket/unixserver/sysaccept_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+with_feature :unix_socket do
+ describe 'UNIXServer#sysaccept' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ end
+
+ after do
+ @server.close
+
+ rm_r(@path)
+ end
+
+ describe 'without a client' do
+ it 'blocks the calling thread' do
+ -> { @server.sysaccept }.should block_caller
+ end
+ end
+
+ describe 'with a client' do
+ before do
+ @client = UNIXSocket.new(@path)
+ end
+
+ after do
+ Socket.for_fd(@fd).close if @fd
+ @client.close
+ end
+
+ describe 'without any data' do
+ it 'returns an Integer' do
+ @fd = @server.sysaccept
+ @fd.should be_kind_of(Integer)
+ end
+ end
+
+ describe 'with data available' do
+ before do
+ @client.write('hello')
+ end
+
+ it 'returns an Integer' do
+ @fd = @server.sysaccept
+ @fd.should be_kind_of(Integer)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/addr_spec.rb b/spec/ruby/library/socket/unixsocket/addr_spec.rb
new file mode 100644
index 0000000000..e8431bea16
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/addr_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UNIXSocket#addr" do
+
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ @client = UNIXSocket.open(@path)
+ end
+
+ after :each do
+ @client.close
+ @server.close
+ SocketSpecs.rm_socket @path
+ end
+
+ it "returns an array" do
+ @client.addr.should be_kind_of(Array)
+ end
+
+ it "returns the address family of this socket in an array" do
+ @client.addr[0].should == "AF_UNIX"
+ @server.addr[0].should == "AF_UNIX"
+ end
+
+ it "returns the path of the socket in an array if it's a server" do
+ @server.addr[1].should == @path
+ end
+
+ it "returns an empty string for path if it's a client" do
+ @client.addr[1].should == ""
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/initialize_spec.rb b/spec/ruby/library/socket/unixsocket/initialize_spec.rb
new file mode 100644
index 0000000000..13b6972f03
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/initialize_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+with_feature :unix_socket do
+ describe 'UNIXSocket#initialize' do
+ describe 'using a non existing path' do
+ it 'raises Errno::ENOENT' do
+ -> { UNIXSocket.new(SocketSpecs.socket_path) }.should raise_error(Errno::ENOENT)
+ end
+ end
+
+ describe 'using an existing socket path' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ @socket = UNIXSocket.new(@path)
+ end
+
+ after do
+ @socket.close
+ @server.close
+ rm_r(@path)
+ end
+
+ it 'returns a new UNIXSocket' do
+ @socket.should be_an_instance_of(UNIXSocket)
+ end
+
+ it 'sets the socket path to an empty String' do
+ @socket.path.should == ''
+ end
+
+ it 'sets the socket to binmode' do
+ @socket.binmode?.should be_true
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/inspect_spec.rb b/spec/ruby/library/socket/unixsocket/inspect_spec.rb
new file mode 100644
index 0000000000..d2e3cabbd3
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/inspect_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UNIXSocket#inspect" do
+ platform_is_not :windows do
+ it "returns sockets fd for unnamed sockets" do
+ begin
+ s1, s2 = UNIXSocket.socketpair
+ s1.inspect.should == "#<UNIXSocket:fd #{s1.fileno}>"
+ s2.inspect.should == "#<UNIXSocket:fd #{s2.fileno}>"
+ ensure
+ s1.close
+ s2.close
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/local_address_spec.rb b/spec/ruby/library/socket/unixsocket/local_address_spec.rb
new file mode 100644
index 0000000000..cbf315f9f4
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/local_address_spec.rb
@@ -0,0 +1,96 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+with_feature :unix_socket do
+ describe 'UNIXSocket#local_address' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ @client = UNIXSocket.new(@path)
+ end
+
+ after do
+ @client.close
+ @server.close
+
+ rm_r(@path)
+ end
+
+ it 'returns an Addrinfo' do
+ @client.local_address.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ platform_is_not :aix do
+ it 'uses AF_UNIX as the address family' do
+ @client.local_address.afamily.should == Socket::AF_UNIX
+ end
+
+ it 'uses PF_UNIX as the protocol family' do
+ @client.local_address.pfamily.should == Socket::PF_UNIX
+ end
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @client.local_address.socktype.should == Socket::SOCK_STREAM
+ end
+
+ platform_is_not :aix do
+ it 'uses an empty socket path' do
+ @client.local_address.unix_path.should == ''
+ end
+ end
+
+ it 'uses 0 as the protocol' do
+ @client.local_address.protocol.should == 0
+ end
+ end
+ end
+end
+
+with_feature :unix_socket do
+ describe 'UNIXSocket#local_address with a UNIX socket pair' do
+ before :each do
+ @sock, @sock2 = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM)
+ end
+
+ after :each do
+ @sock.close
+ @sock2.close
+ end
+
+ it 'returns an Addrinfo' do
+ @sock.local_address.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses AF_UNIX as the address family' do
+ @sock.local_address.afamily.should == Socket::AF_UNIX
+ end
+
+ it 'uses PF_UNIX as the protocol family' do
+ @sock.local_address.pfamily.should == Socket::PF_UNIX
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @sock.local_address.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'raises SocketError for #ip_address' do
+ -> {
+ @sock.local_address.ip_address
+ }.should raise_error(SocketError, "need IPv4 or IPv6 address")
+ end
+
+ it 'raises SocketError for #ip_port' do
+ -> {
+ @sock.local_address.ip_port
+ }.should raise_error(SocketError, "need IPv4 or IPv6 address")
+ end
+
+ it 'uses 0 as the protocol' do
+ @sock.local_address.protocol.should == 0
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/new_spec.rb b/spec/ruby/library/socket/unixsocket/new_spec.rb
new file mode 100644
index 0000000000..05a6b3eda2
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/new_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../spec_helper'
+require_relative 'shared/new'
+
+describe "UNIXSocket.new" do
+ it_behaves_like :unixsocket_new, :new
+end
diff --git a/spec/ruby/library/socket/unixsocket/open_spec.rb b/spec/ruby/library/socket/unixsocket/open_spec.rb
new file mode 100644
index 0000000000..99ad151bb8
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/open_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/new'
+
+describe "UNIXSocket.open" do
+ it_behaves_like :unixsocket_new, :open
+end
+
+describe "UNIXSocket.open" do
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ end
+
+ after :each do
+ @server.close
+ SocketSpecs.rm_socket @path
+ end
+
+ it "opens a unix socket on the specified file and yields it to the block" do
+ UNIXSocket.open(@path) do |client|
+ client.addr[0].should == "AF_UNIX"
+ client.should_not.closed?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/pair_spec.rb b/spec/ruby/library/socket/unixsocket/pair_spec.rb
new file mode 100644
index 0000000000..845ff76ecc
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/pair_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/partially_closable_sockets'
+
+describe "UNIXSocket#pair" do
+ platform_is_not :windows do
+
+ it_should_behave_like "partially closable sockets"
+
+ before :each do
+ @s1, @s2 = UNIXSocket.pair
+ end
+
+ after :each do
+ @s1.close
+ @s2.close
+ end
+
+ it "returns a pair of connected sockets" do
+ @s1.puts "foo"
+ @s2.gets.should == "foo\n"
+ end
+
+ it "returns sockets with no name" do
+ @s1.path.should == @s2.path
+ @s1.path.should == ""
+ end
+
+ it "returns sockets with no address" do
+ @s1.addr.should == ["AF_UNIX", ""]
+ @s2.addr.should == ["AF_UNIX", ""]
+ end
+
+ it "returns sockets with no peeraddr" do
+ @s1.peeraddr.should == ["AF_UNIX", ""]
+ @s2.peeraddr.should == ["AF_UNIX", ""]
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb b/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb
new file mode 100644
index 0000000000..78a64fe6be
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/partially_closable_sockets'
+
+platform_is_not :windows do
+ describe "UNIXSocket partial closability" do
+
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ @s1 = UNIXSocket.new(@path)
+ @s2 = @server.accept
+ end
+
+ after :each do
+ @server.close
+ @s1.close
+ @s2.close
+ SocketSpecs.rm_socket @path
+ end
+
+ it_should_behave_like "partially closable sockets"
+
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/path_spec.rb b/spec/ruby/library/socket/unixsocket/path_spec.rb
new file mode 100644
index 0000000000..317ffc0975
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/path_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UNIXSocket#path" do
+
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ @client = UNIXSocket.open(@path)
+ end
+
+ after :each do
+ @client.close
+ @server.close
+ SocketSpecs.rm_socket @path
+ end
+
+ it "returns the path of the socket if it's a server" do
+ @server.path.should == @path
+ end
+
+ it "returns an empty string for path if it's a client" do
+ @client.path.should == ""
+ end
+ end
+
+end
diff --git a/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb b/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb
new file mode 100644
index 0000000000..0b6b1ccf04
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UNIXSocket#peeraddr" do
+
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ @client = UNIXSocket.open(@path)
+ end
+
+ after :each do
+ @client.close
+ @server.close
+ SocketSpecs.rm_socket @path
+ end
+
+ it "returns the address family and path of the server end of the connection" do
+ @client.peeraddr.should == ["AF_UNIX", @path]
+ end
+
+ it "raises an error in server sockets" do
+ -> {
+ @server.peeraddr
+ }.should raise_error(Errno::ENOTCONN)
+ end
+ end
+
+end
diff --git a/spec/ruby/library/socket/unixsocket/recv_io_spec.rb b/spec/ruby/library/socket/unixsocket/recv_io_spec.rb
new file mode 100644
index 0000000000..533f02a0fa
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/recv_io_spec.rb
@@ -0,0 +1,87 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UNIXSocket#recv_io" do
+
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ @client = UNIXSocket.open(@path)
+
+ @send_io_path = File.expand_path('../../fixtures/send_io.txt', __FILE__)
+ @file = File.open(@send_io_path)
+ end
+
+ after :each do
+ @io.close if @io
+ @socket.close if @socket
+
+ @file.close
+ @client.close
+ @server.close
+ SocketSpecs.rm_socket @path
+ end
+
+ it "reads an IO object across the socket" do
+ @client.send_io(@file)
+
+ @socket = @server.accept
+ @io = @socket.recv_io
+
+ @io.read.should == File.read(@send_io_path)
+ end
+
+ it "takes an optional class to use" do
+ @client.send_io(@file)
+
+ @socket = @server.accept
+ @io = @socket.recv_io(File)
+
+ @io.should be_an_instance_of(File)
+ end
+ end
+end
+
+with_feature :unix_socket do
+ describe 'UNIXSocket#recv_io' do
+ before do
+ @file = File.open('/dev/null', 'w')
+ @client, @server = UNIXSocket.socketpair
+ end
+
+ after do
+ @client.close
+ @server.close
+ @io.close if @io
+ @file.close
+ end
+
+ describe 'without a custom class' do
+ it 'returns an IO' do
+ @client.send_io(@file)
+
+ @io = @server.recv_io
+ @io.should be_an_instance_of(IO)
+ end
+ end
+
+ describe 'with a custom class' do
+ it 'returns an instance of the custom class' do
+ @client.send_io(@file)
+
+ @io = @server.recv_io(File)
+ @io.should be_an_instance_of(File)
+ end
+ end
+
+ describe 'with a custom mode' do
+ it 'opens the IO using the given mode' do
+ @client.send_io(@file)
+
+ @io = @server.recv_io(File, File::WRONLY)
+ @io.should be_an_instance_of(File)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb
new file mode 100644
index 0000000000..c0e1cf670b
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb
@@ -0,0 +1,98 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UNIXSocket#recvfrom" do
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ @client = UNIXSocket.open(@path)
+ end
+
+ after :each do
+ @client.close
+ @server.close
+ SocketSpecs.rm_socket @path
+ end
+
+ it "receives len bytes from sock" do
+ @client.send("foobar", 0)
+ sock = @server.accept
+ sock.recvfrom(6).first.should == "foobar"
+ sock.close
+ end
+
+ it "returns an array with data and information on the sender" do
+ @client.send("foobar", 0)
+ sock = @server.accept
+ data = sock.recvfrom(6)
+ data.first.should == "foobar"
+ data.last.should == ["AF_UNIX", ""]
+ sock.close
+ end
+
+ it "uses different message options" do
+ @client.send("foobar", Socket::MSG_PEEK)
+ sock = @server.accept
+ peek_data = sock.recvfrom(6, Socket::MSG_PEEK) # Does not retrieve the message
+ real_data = sock.recvfrom(6)
+
+ real_data.should == peek_data
+ peek_data.should == ["foobar", ["AF_UNIX", ""]]
+ sock.close
+ end
+ end
+end
+
+
+with_feature :unix_socket do
+ describe 'UNIXSocket#recvfrom' do
+ describe 'using a socket pair' do
+ before do
+ @client, @server = UNIXSocket.socketpair
+ @client.write('hello')
+ end
+
+ after do
+ @client.close
+ @server.close
+ end
+
+ it 'returns an Array containing the data and address information' do
+ @server.recvfrom(5).should == ['hello', ['AF_UNIX', '']]
+ end
+ end
+
+ # These specs are taken from the rdoc examples on UNIXSocket#recvfrom.
+ describe 'using a UNIX socket constructed using UNIXSocket.for_fd' do
+ before do
+ @path1 = SocketSpecs.socket_path
+ @path2 = SocketSpecs.socket_path.chop + '2'
+ rm_r(@path2)
+
+ @client_raw = Socket.new(:UNIX, :DGRAM)
+ @client_raw.bind(Socket.sockaddr_un(@path1))
+
+ @server_raw = Socket.new(:UNIX, :DGRAM)
+ @server_raw.bind(Socket.sockaddr_un(@path2))
+
+ @socket = UNIXSocket.for_fd(@server_raw.fileno)
+ @socket.autoclose = false
+ end
+
+ after do
+ @client_raw.close
+ @server_raw.close # also closes @socket
+
+ rm_r @path1
+ rm_r @path2
+ end
+
+ it 'returns an Array containing the data and address information' do
+ @client_raw.send('hello', 0, Socket.sockaddr_un(@path2))
+
+ @socket.recvfrom(5).should == ['hello', ['AF_UNIX', @path1]]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/remote_address_spec.rb b/spec/ruby/library/socket/unixsocket/remote_address_spec.rb
new file mode 100644
index 0000000000..0b416254d0
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/remote_address_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+with_feature :unix_socket do
+ describe 'UNIXSocket#remote_address' do
+ before do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.new(@path)
+ @client = UNIXSocket.new(@path)
+ end
+
+ after do
+ @client.close
+ @server.close
+
+ rm_r(@path)
+ end
+
+ it 'returns an Addrinfo' do
+ @client.remote_address.should be_an_instance_of(Addrinfo)
+ end
+
+ describe 'the returned Addrinfo' do
+ it 'uses AF_UNIX as the address family' do
+ @client.remote_address.afamily.should == Socket::AF_UNIX
+ end
+
+ it 'uses PF_UNIX as the protocol family' do
+ @client.remote_address.pfamily.should == Socket::PF_UNIX
+ end
+
+ it 'uses SOCK_STREAM as the socket type' do
+ @client.remote_address.socktype.should == Socket::SOCK_STREAM
+ end
+
+ it 'uses the correct socket path' do
+ @client.remote_address.unix_path.should == @path
+ end
+
+ it 'uses 0 as the protocol' do
+ @client.remote_address.protocol.should == 0
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/send_io_spec.rb b/spec/ruby/library/socket/unixsocket/send_io_spec.rb
new file mode 100644
index 0000000000..a2a7d26539
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/send_io_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "UNIXSocket#send_io" do
+
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ @client = UNIXSocket.open(@path)
+
+ @send_io_path = File.expand_path('../../fixtures/send_io.txt', __FILE__)
+ @file = File.open(@send_io_path)
+ end
+
+ after :each do
+ @io.close if @io
+ @socket.close if @socket
+
+ @file.close
+ @client.close
+ @server.close
+ SocketSpecs.rm_socket @path
+ end
+
+ it "sends the fd for an IO object across the socket" do
+ @client.send_io(@file)
+
+ @socket = @server.accept
+ @io = @socket.recv_io
+
+ @io.read.should == File.read(@send_io_path)
+ end
+ end
+end
+
+with_feature :unix_socket do
+ describe 'UNIXSocket#send_io' do
+ before do
+ @file = File.open('/dev/null', 'w')
+ @client, @server = UNIXSocket.socketpair
+ end
+
+ after do
+ @client.close
+ @server.close
+ @io.close if @io
+ @file.close
+ end
+
+ it 'sends an IO object' do
+ @client.send_io(@file)
+
+ @io = @server.recv_io
+ @io.should be_an_instance_of(IO)
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/shared/new.rb b/spec/ruby/library/socket/unixsocket/shared/new.rb
new file mode 100644
index 0000000000..bfb7ed3886
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/shared/new.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/classes'
+
+describe :unixsocket_new, shared: true do
+ platform_is_not :windows do
+ before :each do
+ @path = SocketSpecs.socket_path
+ @server = UNIXServer.open(@path)
+ end
+
+ after :each do
+ @client.close if @client
+ @server.close
+ SocketSpecs.rm_socket @path
+ end
+
+ it "opens a unix socket on the specified file" do
+ @client = UNIXSocket.send(@method, @path)
+
+ @client.addr[0].should == "AF_UNIX"
+ @client.should_not.closed?
+ end
+ end
+end
diff --git a/spec/ruby/library/socket/unixsocket/socketpair_spec.rb b/spec/ruby/library/socket/unixsocket/socketpair_spec.rb
new file mode 100644
index 0000000000..3e9646f76b
--- /dev/null
+++ b/spec/ruby/library/socket/unixsocket/socketpair_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../spec_helper'
+
+with_feature :unix_socket do
+ describe 'UNIXSocket.socketpair' do
+ before do
+ @s1, @s2 = UNIXSocket.socketpair
+ end
+
+ after do
+ @s1.close
+ @s2.close
+ end
+
+ it 'returns two UNIXSockets' do
+ @s1.should be_an_instance_of(UNIXSocket)
+ @s2.should be_an_instance_of(UNIXSocket)
+ end
+
+ it 'connects the sockets to each other' do
+ @s1.write('hello')
+
+ @s2.recv(5).should == 'hello'
+ end
+
+ it 'sets the socket paths to empty Strings' do
+ @s1.path.should == ''
+ @s2.path.should == ''
+ end
+
+ it 'sets the socket addresses to empty Strings' do
+ @s1.addr.should == ['AF_UNIX', '']
+ @s2.addr.should == ['AF_UNIX', '']
+ end
+
+ it 'sets the socket peer addresses to empty Strings' do
+ @s1.peeraddr.should == ['AF_UNIX', '']
+ @s2.peeraddr.should == ['AF_UNIX', '']
+ end
+ end
+end
diff --git a/spec/ruby/library/stringio/append_spec.rb b/spec/ruby/library/stringio/append_spec.rb
new file mode 100644
index 0000000000..981229fc10
--- /dev/null
+++ b/spec/ruby/library/stringio/append_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#<< when passed [Object]" do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "returns self" do
+ (@io << "just testing").should equal(@io)
+ end
+
+ it "writes the passed argument onto self" do
+ (@io << "just testing")
+ @io.string.should == "just testing"
+ (@io << " and more testing")
+ @io.string.should == "just testing and more testing"
+ end
+
+ it "writes the passed argument at the current position" do
+ @io.pos = 5
+ @io << "<test>"
+ @io.string.should == "examp<test>"
+ end
+
+ it "pads self with \\000 when the current position is after the end" do
+ @io.pos = 15
+ @io << "just testing"
+ @io.string.should == "example\000\000\000\000\000\000\000\000just testing"
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "does not taint self when the passed argument is tainted" do
+ (@io << "test".taint)
+ @io.tainted?.should be_false
+ end
+ end
+
+ it "updates self's position" do
+ @io << "test"
+ @io.pos.should eql(4)
+ end
+
+ it "tries to convert the passed argument to a String using #to_s" do
+ obj = mock("to_s")
+ obj.should_receive(:to_s).and_return("Test")
+
+ (@io << obj).string.should == "Testple"
+ end
+end
+
+describe "StringIO#<< when self is not writable" do
+ it "raises an IOError" do
+ io = StringIO.new("test", "r")
+ -> { io << "test" }.should raise_error(IOError)
+
+ io = StringIO.new("test")
+ io.close_write
+ -> { io << "test" }.should raise_error(IOError)
+ end
+end
+
+describe "StringIO#<< when in append mode" do
+ before :each do
+ @io = StringIO.new("example", "a")
+ end
+
+ it "appends the passed argument to the end of self, ignoring current position" do
+ (@io << ", just testing")
+ @io.string.should == "example, just testing"
+
+ @io.pos = 3
+ (@io << " and more testing")
+ @io.string.should == "example, just testing and more testing"
+ end
+
+ it "correctly updates self's position" do
+ @io << ", testing"
+ @io.pos.should eql(16)
+ end
+end
diff --git a/spec/ruby/library/stringio/binmode_spec.rb b/spec/ruby/library/stringio/binmode_spec.rb
new file mode 100644
index 0000000000..853d9c9bd6
--- /dev/null
+++ b/spec/ruby/library/stringio/binmode_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#binmode" do
+ it "returns self" do
+ io = StringIO.new("example")
+ io.binmode.should equal(io)
+ end
+
+ it "changes external encoding to BINARY" do
+ io = StringIO.new
+ io.external_encoding.should == Encoding.find('external')
+ io.binmode
+ io.external_encoding.should == Encoding::BINARY
+ end
+
+ it "does not set internal encoding" do
+ io = StringIO.new
+ io.internal_encoding.should == nil
+ io.binmode
+ io.internal_encoding.should == nil
+ end
+end
diff --git a/spec/ruby/library/stringio/bytes_spec.rb b/spec/ruby/library/stringio/bytes_spec.rb
new file mode 100644
index 0000000000..4ef7a490a5
--- /dev/null
+++ b/spec/ruby/library/stringio/bytes_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require 'stringio'
+require_relative 'shared/each_byte'
+
+ruby_version_is ''...'3.0' do
+ describe "StringIO#bytes" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :stringio_each_byte, :bytes
+ end
+
+ describe "StringIO#bytes when self is not readable" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :stringio_each_byte_not_readable, :bytes
+ end
+end
diff --git a/spec/ruby/library/stringio/chars_spec.rb b/spec/ruby/library/stringio/chars_spec.rb
new file mode 100644
index 0000000000..58cba77634
--- /dev/null
+++ b/spec/ruby/library/stringio/chars_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require 'stringio'
+require_relative 'shared/each_char'
+
+ruby_version_is ''...'3.0' do
+ describe "StringIO#chars" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :stringio_each_char, :chars
+ end
+
+ describe "StringIO#chars when self is not readable" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :stringio_each_char_not_readable, :chars
+ end
+end
diff --git a/spec/ruby/library/stringio/close_read_spec.rb b/spec/ruby/library/stringio/close_read_spec.rb
new file mode 100644
index 0000000000..80bd547e85
--- /dev/null
+++ b/spec/ruby/library/stringio/close_read_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#close_read" do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "returns nil" do
+ @io.close_read.should be_nil
+ end
+
+ it "prevents further reading" do
+ @io.close_read
+ -> { @io.read(1) }.should raise_error(IOError)
+ end
+
+ it "allows further writing" do
+ @io.close_read
+ @io.write("x").should == 1
+ end
+
+ it "raises an IOError when in write-only mode" do
+ io = StringIO.new("example", "w")
+ -> { io.close_read }.should raise_error(IOError)
+
+ io = StringIO.new("example")
+ io.close_read
+ io.close_read.should == nil
+ end
+end
diff --git a/spec/ruby/library/stringio/close_spec.rb b/spec/ruby/library/stringio/close_spec.rb
new file mode 100644
index 0000000000..520a8de782
--- /dev/null
+++ b/spec/ruby/library/stringio/close_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#close" do
+ before :each do
+ @io = StringIOSpecs.build
+ end
+
+ it "returns nil" do
+ @io.close.should be_nil
+ end
+
+ it "prevents further reading and/or writing" do
+ @io.close
+ -> { @io.read(1) }.should raise_error(IOError)
+ -> { @io.write('x') }.should raise_error(IOError)
+ end
+
+ it "does not raise anything when self was already closed" do
+ @io.close
+ -> { @io.close }.should_not raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/close_write_spec.rb b/spec/ruby/library/stringio/close_write_spec.rb
new file mode 100644
index 0000000000..1a4cfa113e
--- /dev/null
+++ b/spec/ruby/library/stringio/close_write_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#close_write" do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "returns nil" do
+ @io.close_write.should be_nil
+ end
+
+ it "prevents further writing" do
+ @io.close_write
+ -> { @io.write('x') }.should raise_error(IOError)
+ end
+
+ it "allows further reading" do
+ @io.close_write
+ @io.read(1).should == 'e'
+ end
+
+ it "raises an IOError when in read-only mode" do
+ io = StringIO.new("example", "r")
+ -> { io.close_write }.should raise_error(IOError)
+
+ io = StringIO.new("example")
+ io.close_write
+ io.close_write.should == nil
+ end
+end
diff --git a/spec/ruby/library/stringio/closed_read_spec.rb b/spec/ruby/library/stringio/closed_read_spec.rb
new file mode 100644
index 0000000000..cb4267ac98
--- /dev/null
+++ b/spec/ruby/library/stringio/closed_read_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#closed_read?" do
+ it "returns true if self is not readable" do
+ io = StringIO.new("example", "r+")
+ io.close_write
+ io.closed_read?.should be_false
+ io.close_read
+ io.closed_read?.should be_true
+ end
+end
diff --git a/spec/ruby/library/stringio/closed_spec.rb b/spec/ruby/library/stringio/closed_spec.rb
new file mode 100644
index 0000000000..ca8a2232a8
--- /dev/null
+++ b/spec/ruby/library/stringio/closed_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#closed?" do
+ it "returns true if self is completely closed" do
+ io = StringIO.new("example", "r+")
+ io.close_read
+ io.closed?.should be_false
+ io.close_write
+ io.closed?.should be_true
+
+ io = StringIO.new("example", "r+")
+ io.close
+ io.closed?.should be_true
+ end
+end
diff --git a/spec/ruby/library/stringio/closed_write_spec.rb b/spec/ruby/library/stringio/closed_write_spec.rb
new file mode 100644
index 0000000000..5c111affd8
--- /dev/null
+++ b/spec/ruby/library/stringio/closed_write_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#closed_write?" do
+ it "returns true if self is not writable" do
+ io = StringIO.new("example", "r+")
+ io.close_read
+ io.closed_write?.should be_false
+ io.close_write
+ io.closed_write?.should be_true
+ end
+end
diff --git a/spec/ruby/library/stringio/codepoints_spec.rb b/spec/ruby/library/stringio/codepoints_spec.rb
new file mode 100644
index 0000000000..ceaadefc32
--- /dev/null
+++ b/spec/ruby/library/stringio/codepoints_spec.rb
@@ -0,0 +1,19 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/codepoints'
+
+ruby_version_is ''...'3.0' do
+ # See redmine #1667
+ describe "StringIO#codepoints" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :stringio_codepoints, :codepoints
+ end
+end
diff --git a/spec/ruby/library/stringio/each_byte_spec.rb b/spec/ruby/library/stringio/each_byte_spec.rb
new file mode 100644
index 0000000000..6f82a32441
--- /dev/null
+++ b/spec/ruby/library/stringio/each_byte_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require 'stringio'
+require_relative 'shared/each_byte'
+
+describe "StringIO#each_byte" do
+ it_behaves_like :stringio_each_byte, :each_byte
+end
+
+describe "StringIO#each_byte when self is not readable" do
+ it_behaves_like :stringio_each_byte_not_readable, :each_byte
+end
diff --git a/spec/ruby/library/stringio/each_char_spec.rb b/spec/ruby/library/stringio/each_char_spec.rb
new file mode 100644
index 0000000000..14b2f09a17
--- /dev/null
+++ b/spec/ruby/library/stringio/each_char_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require 'stringio'
+require_relative 'shared/each_char'
+
+describe "StringIO#each_char" do
+ it_behaves_like :stringio_each_char, :each_char
+end
+
+describe "StringIO#each_char when self is not readable" do
+ it_behaves_like :stringio_each_char_not_readable, :each_char
+end
diff --git a/spec/ruby/library/stringio/each_codepoint_spec.rb b/spec/ruby/library/stringio/each_codepoint_spec.rb
new file mode 100644
index 0000000000..f18de22aad
--- /dev/null
+++ b/spec/ruby/library/stringio/each_codepoint_spec.rb
@@ -0,0 +1,9 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/codepoints'
+
+# See redmine #1667
+describe "StringIO#each_codepoint" do
+ it_behaves_like :stringio_codepoints, :each_codepoint
+end
diff --git a/spec/ruby/library/stringio/each_line_spec.rb b/spec/ruby/library/stringio/each_line_spec.rb
new file mode 100644
index 0000000000..c68f7dae82
--- /dev/null
+++ b/spec/ruby/library/stringio/each_line_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/each'
+
+describe "StringIO#each_line when passed a separator" do
+ it_behaves_like :stringio_each_separator, :each_line
+end
+
+describe "StringIO#each_line when passed no arguments" do
+ it_behaves_like :stringio_each_no_arguments, :each_line
+end
+
+describe "StringIO#each_line when self is not readable" do
+ it_behaves_like :stringio_each_not_readable, :each_line
+end
+
+describe "StringIO#each_line when passed chomp" do
+ it_behaves_like :stringio_each_chomp, :each_line
+end
+
+describe "StringIO#each_line when passed limit" do
+ it_behaves_like :stringio_each_limit, :each_line
+end
diff --git a/spec/ruby/library/stringio/each_spec.rb b/spec/ruby/library/stringio/each_spec.rb
new file mode 100644
index 0000000000..2c30ed5cda
--- /dev/null
+++ b/spec/ruby/library/stringio/each_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/each'
+
+describe "StringIO#each when passed a separator" do
+ it_behaves_like :stringio_each_separator, :each
+end
+
+describe "StringIO#each when passed no arguments" do
+ it_behaves_like :stringio_each_no_arguments, :each
+end
+
+describe "StringIO#each when self is not readable" do
+ it_behaves_like :stringio_each_not_readable, :each
+end
+
+describe "StringIO#each when passed chomp" do
+ it_behaves_like :stringio_each_chomp, :each
+end
+
+describe "StringIO#each when passed chomp" do
+ it_behaves_like :stringio_each_separator_and_chomp, :each
+end
+
+describe "StringIO#each when passed limit" do
+ it_behaves_like :stringio_each_limit, :each
+end
diff --git a/spec/ruby/library/stringio/eof_spec.rb b/spec/ruby/library/stringio/eof_spec.rb
new file mode 100644
index 0000000000..af0170977c
--- /dev/null
+++ b/spec/ruby/library/stringio/eof_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/eof'
+
+describe "StringIO#eof?" do
+ it_behaves_like :stringio_eof, :eof?
+end
+
+describe "StringIO#eof" do
+ it_behaves_like :stringio_eof, :eof
+end
diff --git a/spec/ruby/library/stringio/external_encoding_spec.rb b/spec/ruby/library/stringio/external_encoding_spec.rb
new file mode 100644
index 0000000000..6c5edb1713
--- /dev/null
+++ b/spec/ruby/library/stringio/external_encoding_spec.rb
@@ -0,0 +1,25 @@
+require 'stringio'
+require_relative '../../spec_helper'
+
+describe "StringIO#external_encoding" do
+ it "gets the encoding of the underlying String" do
+ io = StringIO.new
+ io.set_encoding Encoding::EUC_JP
+ io.external_encoding.should == Encoding::EUC_JP
+ end
+
+ it "changes to match string if string's encoding is changed" do
+ io = StringIO.new
+ io.string.force_encoding(Encoding::EUC_JP)
+ io.external_encoding.should == Encoding::EUC_JP
+ end
+
+ it "does not set the encoding of its buffer string if the string is frozen" do
+ str = "foo".freeze
+ enc = str.encoding
+ io = StringIO.new(str)
+ io.set_encoding Encoding::EUC_JP
+ io.external_encoding.should == Encoding::EUC_JP
+ str.encoding.should == enc
+ end
+end
diff --git a/spec/ruby/library/stringio/fcntl_spec.rb b/spec/ruby/library/stringio/fcntl_spec.rb
new file mode 100644
index 0000000000..a78004d868
--- /dev/null
+++ b/spec/ruby/library/stringio/fcntl_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#fcntl" do
+ it "raises a NotImplementedError" do
+ -> { StringIO.new("boom").fcntl }.should raise_error(NotImplementedError)
+ end
+end
diff --git a/spec/ruby/library/stringio/fileno_spec.rb b/spec/ruby/library/stringio/fileno_spec.rb
new file mode 100644
index 0000000000..eea03a5af3
--- /dev/null
+++ b/spec/ruby/library/stringio/fileno_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/each'
+
+describe "StringIO#fileno" do
+ it "returns nil" do
+ StringIO.new("nuffin").fileno.should be_nil
+ end
+end
diff --git a/spec/ruby/library/stringio/fixtures/classes.rb b/spec/ruby/library/stringio/fixtures/classes.rb
new file mode 100644
index 0000000000..bb8dc354cc
--- /dev/null
+++ b/spec/ruby/library/stringio/fixtures/classes.rb
@@ -0,0 +1,15 @@
+require 'stringio'
+
+class StringSubclass < String; end
+
+module StringIOSpecs
+ def self.build
+ str = <<-EOS
+ each
+ peach
+ pear
+ plum
+ EOS
+ StringIO.new(str)
+ end
+end
diff --git a/spec/ruby/library/stringio/flush_spec.rb b/spec/ruby/library/stringio/flush_spec.rb
new file mode 100644
index 0000000000..17a16dfdd5
--- /dev/null
+++ b/spec/ruby/library/stringio/flush_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#flush" do
+ it "returns self" do
+ io = StringIO.new("flush")
+ io.flush.should equal(io)
+ end
+end
diff --git a/spec/ruby/library/stringio/fsync_spec.rb b/spec/ruby/library/stringio/fsync_spec.rb
new file mode 100644
index 0000000000..8fb2b59a24
--- /dev/null
+++ b/spec/ruby/library/stringio/fsync_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#fsync" do
+ it "returns zero" do
+ io = StringIO.new("fsync")
+ io.fsync.should eql(0)
+ end
+end
diff --git a/spec/ruby/library/stringio/getbyte_spec.rb b/spec/ruby/library/stringio/getbyte_spec.rb
new file mode 100644
index 0000000000..3daa3d8e02
--- /dev/null
+++ b/spec/ruby/library/stringio/getbyte_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require 'stringio'
+require_relative 'shared/getc'
+
+describe "StringIO#getbyte" do
+ it_behaves_like :stringio_getc, :getbyte
+
+ it "returns the 8-bit byte at the current position" do
+ io = StringIO.new("example")
+
+ io.getbyte.should == 101
+ io.getbyte.should == 120
+ io.getbyte.should == 97
+ end
+end
+
+describe "StringIO#getbyte when self is not readable" do
+ it_behaves_like :stringio_getc_not_readable, :getbyte
+end
diff --git a/spec/ruby/library/stringio/getc_spec.rb b/spec/ruby/library/stringio/getc_spec.rb
new file mode 100644
index 0000000000..263d418316
--- /dev/null
+++ b/spec/ruby/library/stringio/getc_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require 'stringio'
+require_relative 'shared/getc'
+
+describe "StringIO#getc" do
+ it_behaves_like :stringio_getc, :getc
+
+ it "returns the character at the current position" do
+ io = StringIO.new("example")
+
+ io.getc.should == ?e
+ io.getc.should == ?x
+ io.getc.should == ?a
+ end
+end
+
+describe "StringIO#getc when self is not readable" do
+ it_behaves_like :stringio_getc_not_readable, :getc
+end
diff --git a/spec/ruby/library/stringio/getch_spec.rb b/spec/ruby/library/stringio/getch_spec.rb
new file mode 100644
index 0000000000..113b4971bf
--- /dev/null
+++ b/spec/ruby/library/stringio/getch_spec.rb
@@ -0,0 +1,44 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require 'stringio'
+require_relative 'shared/getc'
+
+# This method is added by io/console on require.
+describe "StringIO#getch" do
+ require 'io/console'
+
+ it_behaves_like :stringio_getc, :getch
+
+ it "returns the character at the current position" do
+ io = StringIO.new("example")
+
+ io.getch.should == ?e
+ io.getch.should == ?x
+ io.getch.should == ?a
+ end
+
+ it "increments #pos by the byte size of the character in multibyte strings" do
+ io = StringIO.new("föóbar")
+
+ io.getch; io.pos.should == 1 # "f" has byte size 1
+ io.getch; io.pos.should == 3 # "ö" has byte size 2
+ io.getch; io.pos.should == 5 # "ó" has byte size 2
+ io.getch; io.pos.should == 6 # "b" has byte size 1
+ end
+
+ it "returns nil at the end of the string" do
+ # empty string case
+ io = StringIO.new("")
+ io.getch.should == nil
+ io.getch.should == nil
+
+ # non-empty string case
+ io = StringIO.new("a")
+ io.getch # skip one
+ io.getch.should == nil
+ end
+
+ describe "StringIO#getch when self is not readable" do
+ it_behaves_like :stringio_getc_not_readable, :getch
+ end
+end
diff --git a/spec/ruby/library/stringio/getpass_spec.rb b/spec/ruby/library/stringio/getpass_spec.rb
new file mode 100644
index 0000000000..60fc64f0c5
--- /dev/null
+++ b/spec/ruby/library/stringio/getpass_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require 'stringio'
+
+# This method is added by io/console on require.
+describe "StringIO#getpass" do
+ require 'io/console'
+
+ it "is defined by io/console" do
+ StringIO.new("example").should.respond_to?(:getpass)
+ end
+end
diff --git a/spec/ruby/library/stringio/gets_spec.rb b/spec/ruby/library/stringio/gets_spec.rb
new file mode 100644
index 0000000000..d597ec0e45
--- /dev/null
+++ b/spec/ruby/library/stringio/gets_spec.rb
@@ -0,0 +1,250 @@
+require_relative '../../spec_helper'
+require "stringio"
+
+describe "StringIO#gets when passed [separator]" do
+ before :each do
+ @io = StringIO.new("this>is>an>example")
+ end
+
+ it "returns the data read till the next occurrence of the passed separator" do
+ @io.gets(">").should == "this>"
+ @io.gets(">").should == "is>"
+ @io.gets(">").should == "an>"
+ @io.gets(">").should == "example"
+ end
+
+ it "sets $_ to the read content" do
+ @io.gets(">")
+ $_.should == "this>"
+ @io.gets(">")
+ $_.should == "is>"
+ @io.gets(">")
+ $_.should == "an>"
+ @io.gets(">")
+ $_.should == "example"
+ @io.gets(">")
+ $_.should be_nil
+ end
+
+ it "accepts string as separator" do
+ @io.gets("is>")
+ $_.should == "this>"
+ @io.gets("an>")
+ $_.should == "is>an>"
+ @io.gets("example")
+ $_.should == "example"
+ @io.gets("ple")
+ $_.should be_nil
+ end
+
+ it "updates self's lineno by one" do
+ @io.gets(">")
+ @io.lineno.should eql(1)
+
+ @io.gets(">")
+ @io.lineno.should eql(2)
+
+ @io.gets(">")
+ @io.lineno.should eql(3)
+ end
+
+ it "returns the next paragraph when the passed separator is an empty String" do
+ io = StringIO.new("this is\n\nan example")
+ io.gets("").should == "this is\n\n"
+ io.gets("").should == "an example"
+ end
+
+ it "returns the remaining content starting at the current position when passed nil" do
+ io = StringIO.new("this is\n\nan example")
+ io.pos = 5
+ io.gets(nil).should == "is\n\nan example"
+ end
+
+ it "tries to convert the passed separator to a String using #to_str" do
+ obj = mock('to_str')
+ obj.should_receive(:to_str).and_return(">")
+ @io.gets(obj).should == "this>"
+ end
+end
+
+describe "StringIO#gets when passed no argument" do
+ before :each do
+ @io = StringIO.new("this is\nan example\nfor StringIO#gets")
+ end
+
+ it "returns the data read till the next occurrence of $/ or till eof" do
+ @io.gets.should == "this is\n"
+
+ begin
+ old_sep = $/
+ suppress_warning {$/ = " "}
+ @io.gets.should == "an "
+ @io.gets.should == "example\nfor "
+ @io.gets.should == "StringIO#gets"
+ ensure
+ suppress_warning {$/ = old_sep}
+ end
+ end
+
+ it "sets $_ to the read content" do
+ @io.gets
+ $_.should == "this is\n"
+ @io.gets
+ $_.should == "an example\n"
+ @io.gets
+ $_.should == "for StringIO#gets"
+ @io.gets
+ $_.should be_nil
+ end
+
+ it "updates self's position" do
+ @io.gets
+ @io.pos.should eql(8)
+
+ @io.gets
+ @io.pos.should eql(19)
+
+ @io.gets
+ @io.pos.should eql(36)
+ end
+
+ it "updates self's lineno" do
+ @io.gets
+ @io.lineno.should eql(1)
+
+ @io.gets
+ @io.lineno.should eql(2)
+
+ @io.gets
+ @io.lineno.should eql(3)
+ end
+
+ it "returns nil if self is at the end" do
+ @io.pos = 36
+ @io.gets.should be_nil
+ @io.gets.should be_nil
+ end
+end
+
+describe "StringIO#gets when passed [limit]" do
+ before :each do
+ @io = StringIO.new("this>is>an>example")
+ end
+
+ it "returns the data read until the limit is met" do
+ @io.gets(4).should == "this"
+ @io.gets(3).should == ">is"
+ @io.gets(5).should == ">an>e"
+ @io.gets(6).should == "xample"
+ end
+
+ it "sets $_ to the read content" do
+ @io.gets(4)
+ $_.should == "this"
+ @io.gets(3)
+ $_.should == ">is"
+ @io.gets(5)
+ $_.should == ">an>e"
+ @io.gets(6)
+ $_.should == "xample"
+ @io.gets(3)
+ $_.should be_nil
+ end
+
+ it "updates self's lineno by one" do
+ @io.gets(3)
+ @io.lineno.should eql(1)
+
+ @io.gets(3)
+ @io.lineno.should eql(2)
+
+ @io.gets(3)
+ @io.lineno.should eql(3)
+ end
+
+ it "tries to convert the passed limit to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(4)
+ @io.gets(obj).should == "this"
+ end
+
+ it "returns a blank string when passed a limit of 0" do
+ @io.gets(0).should == ""
+ end
+
+ it "ignores it when passed a negative limit" do
+ @io.gets(-4).should == "this>is>an>example"
+ end
+end
+
+describe "StringIO#gets when passed [separator] and [limit]" do
+ before :each do
+ @io = StringIO.new("this>is>an>example")
+ end
+
+ it "returns the data read until the limit is consumed or the separator is met" do
+ @io.gets('>', 8).should == "this>"
+ @io.gets('>', 2).should == "is"
+ @io.gets('>', 10).should == ">"
+ @io.gets('>', 6).should == "an>"
+ @io.gets('>', 5).should == "examp"
+ end
+
+ it "sets $_ to the read content" do
+ @io.gets('>', 8)
+ $_.should == "this>"
+ @io.gets('>', 2)
+ $_.should == "is"
+ @io.gets('>', 10)
+ $_.should == ">"
+ @io.gets('>', 6)
+ $_.should == "an>"
+ @io.gets('>', 5)
+ $_.should == "examp"
+ end
+
+ it "updates self's lineno by one" do
+ @io.gets('>', 3)
+ @io.lineno.should eql(1)
+
+ @io.gets('>', 3)
+ @io.lineno.should eql(2)
+
+ @io.gets('>', 3)
+ @io.lineno.should eql(3)
+ end
+
+ it "tries to convert the passed separator to a String using #to_str" do
+ obj = mock('to_str')
+ obj.should_receive(:to_str).and_return('>')
+ @io.gets(obj, 5).should == "this>"
+ end
+
+ it "does not raise TypeError if passed separator is nil" do
+ @io.gets(nil, 5).should == "this>"
+ end
+
+ it "tries to convert the passed limit to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(5)
+ @io.gets('>', obj).should == "this>"
+ end
+end
+
+describe "StringIO#gets when in write-only mode" do
+ it "raises an IOError" do
+ io = StringIO.new("xyz", "w")
+ -> { io.gets }.should raise_error(IOError)
+
+ io = StringIO.new("xyz")
+ io.close_read
+ -> { io.gets }.should raise_error(IOError)
+ end
+end
+
+describe "StringIO#gets when passed [chomp]" do
+ it "returns the data read without a trailing newline character" do
+ io = StringIO.new("this>is>an>example\n")
+ io.gets(chomp: true).should == "this>is>an>example"
+ end
+end
diff --git a/spec/ruby/library/stringio/initialize_spec.rb b/spec/ruby/library/stringio/initialize_spec.rb
new file mode 100644
index 0000000000..c597e328d3
--- /dev/null
+++ b/spec/ruby/library/stringio/initialize_spec.rb
@@ -0,0 +1,307 @@
+require_relative '../../spec_helper'
+require 'stringio'
+
+describe "StringIO#initialize when passed [Object, mode]" do
+ before :each do
+ @io = StringIO.allocate
+ end
+
+ it "uses the passed Object as the StringIO backend" do
+ @io.send(:initialize, str = "example", "r")
+ @io.string.should equal(str)
+ end
+
+ it "sets the mode based on the passed mode" do
+ io = StringIO.allocate
+ io.send(:initialize, "example", "r")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_true
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "rb")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_true
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "r+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "rb+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "w")
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "wb")
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "w+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "wb+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "a")
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "ab")
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "a+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", "ab+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+ end
+
+ it "allows passing the mode as an Integer" do
+ io = StringIO.allocate
+ io.send(:initialize, "example", IO::RDONLY)
+ io.closed_read?.should be_false
+ io.closed_write?.should be_true
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", IO::RDWR)
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", IO::WRONLY)
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", IO::WRONLY | IO::TRUNC)
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", IO::RDWR | IO::TRUNC)
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", IO::WRONLY | IO::APPEND)
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.allocate
+ io.send(:initialize, "example", IO::RDWR | IO::APPEND)
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+ end
+
+ it "raises a FrozenError when passed a frozen String in truncate mode as StringIO backend" do
+ io = StringIO.allocate
+ -> { io.send(:initialize, "example".freeze, IO::TRUNC) }.should raise_error(FrozenError)
+ end
+
+ it "tries to convert the passed mode to a String using #to_str" do
+ obj = mock('to_str')
+ obj.should_receive(:to_str).and_return("r")
+ @io.send(:initialize, "example", obj)
+
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_true
+ end
+
+ it "raises an Errno::EACCES error when passed a frozen string with a write-mode" do
+ (str = "example").freeze
+ -> { @io.send(:initialize, str, "r+") }.should raise_error(Errno::EACCES)
+ -> { @io.send(:initialize, str, "w") }.should raise_error(Errno::EACCES)
+ -> { @io.send(:initialize, str, "a") }.should raise_error(Errno::EACCES)
+ end
+end
+
+describe "StringIO#initialize when passed [Object]" do
+ before :each do
+ @io = StringIO.allocate
+ end
+
+ it "uses the passed Object as the StringIO backend" do
+ @io.send(:initialize, str = "example")
+ @io.string.should equal(str)
+ end
+
+ it "sets the mode to read-write" do
+ @io.send(:initialize, "example")
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_false
+ end
+
+ it "tries to convert the passed Object to a String using #to_str" do
+ obj = mock('to_str')
+ obj.should_receive(:to_str).and_return("example")
+ @io.send(:initialize, obj)
+ @io.string.should == "example"
+ end
+
+ it "automatically sets the mode to read-only when passed a frozen string" do
+ (str = "example").freeze
+ @io.send(:initialize, str)
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_true
+ end
+end
+
+# NOTE: Synchronise with core/io/new_spec.rb (core/io/shared/new.rb)
+describe "StringIO#initialize when passed keyword arguments" do
+ it "sets the mode based on the passed :mode option" do
+ io = StringIO.new("example", "r")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_true
+ end
+
+ it "accepts a mode argument set to nil with a valid :mode option" do
+ @io = StringIO.new('', nil, mode: "w")
+ @io.write("foo").should == 3
+ end
+
+ it "accepts a mode argument with a :mode option set to nil" do
+ @io = StringIO.new('', "w", mode: nil)
+ @io.write("foo").should == 3
+ end
+
+ it "sets binmode from :binmode option" do
+ @io = StringIO.new('', 'w', binmode: true)
+ @io.external_encoding.to_s.should == "ASCII-8BIT" # #binmode? isn't implemented in StringIO
+ end
+
+ it "does not set binmode from false :binmode" do
+ @io = StringIO.new('', 'w', binmode: false)
+ @io.external_encoding.to_s.should == "UTF-8" # #binmode? isn't implemented in StringIO
+ end
+end
+
+# NOTE: Synchronise with core/io/new_spec.rb (core/io/shared/new.rb)
+describe "StringIO#initialize when passed keyword arguments and error happens" do
+ it "raises an error if passed encodings two ways" do
+ -> {
+ @io = StringIO.new('', 'w:ISO-8859-1', encoding: 'ISO-8859-1')
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = StringIO.new('', 'w:ISO-8859-1', external_encoding: 'ISO-8859-1')
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = StringIO.new('', 'w:ISO-8859-1:UTF-8', internal_encoding: 'ISO-8859-1')
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error if passed matching binary/text mode two ways" do
+ -> {
+ @io = StringIO.new('', "wb", binmode: true)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = StringIO.new('', "wt", textmode: true)
+ }.should raise_error(ArgumentError)
+
+ -> {
+ @io = StringIO.new('', "wb", textmode: false)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = StringIO.new('', "wt", binmode: false)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error if passed conflicting binary/text mode two ways" do
+ -> {
+ @io = StringIO.new('', "wb", binmode: false)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = StringIO.new('', "wt", textmode: false)
+ }.should raise_error(ArgumentError)
+
+ -> {
+ @io = StringIO.new('', "wb", textmode: true)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = StringIO.new('', "wt", binmode: true)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "raises an error when trying to set both binmode and textmode" do
+ -> {
+ @io = StringIO.new('', "w", textmode: true, binmode: true)
+ }.should raise_error(ArgumentError)
+ -> {
+ @io = StringIO.new('', File::Constants::WRONLY, textmode: true, binmode: true)
+ }.should raise_error(ArgumentError)
+ end
+end
+
+describe "StringIO#initialize when passed no arguments" do
+ before :each do
+ @io = StringIO.allocate
+ end
+
+ it "is private" do
+ StringIO.should have_private_instance_method(:initialize)
+ end
+
+ it "sets the mode to read-write" do
+ @io.send(:initialize, "example")
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_false
+ end
+
+ it "uses an empty String as the StringIO backend" do
+ @io.send(:initialize)
+ @io.string.should == ""
+ end
+end
+
+describe "StringIO#initialize sets" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+ Encoding.default_external = Encoding::ISO_8859_2
+ Encoding.default_internal = Encoding::ISO_8859_2
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "the encoding to Encoding.default_external when passed no arguments" do
+ io = StringIO.new
+ io.external_encoding.should == Encoding::ISO_8859_2
+ io.string.encoding.should == Encoding::ISO_8859_2
+ end
+
+ it "the encoding to the encoding of the String when passed a String" do
+ s = ''.force_encoding(Encoding::EUC_JP)
+ io = StringIO.new(s)
+ io.string.encoding.should == Encoding::EUC_JP
+ end
+
+ guard_not -> { # [Bug #16497]
+ stringio_version = StringIO.const_defined?(:VERSION) ? StringIO::VERSION : "0.0.2"
+ version_is(stringio_version, "0.0.3"..."0.1.1")
+ } do
+ it "the #external_encoding to the encoding of the String when passed a String" do
+ s = ''.force_encoding(Encoding::EUC_JP)
+ io = StringIO.new(s)
+ io.external_encoding.should == Encoding::EUC_JP
+ end
+ end
+end
diff --git a/spec/ruby/library/stringio/inspect_spec.rb b/spec/ruby/library/stringio/inspect_spec.rb
new file mode 100644
index 0000000000..7c02f8d360
--- /dev/null
+++ b/spec/ruby/library/stringio/inspect_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require "stringio"
+
+describe "StringIO#inspect" do
+ it "returns the same as #to_s" do
+ io = StringIO.new("example")
+ io.inspect.should == io.to_s
+ end
+
+ it "does not include the contents" do
+ io = StringIO.new("contents")
+ io.inspect.should_not include("contents")
+ end
+
+ it "uses the regular Object#inspect without any instance variable" do
+ io = StringIO.new("example")
+ io.inspect.should =~ /\A#<StringIO:0x\h+>\z/
+ end
+end
diff --git a/spec/ruby/library/stringio/internal_encoding_spec.rb b/spec/ruby/library/stringio/internal_encoding_spec.rb
new file mode 100644
index 0000000000..2035cf25a9
--- /dev/null
+++ b/spec/ruby/library/stringio/internal_encoding_spec.rb
@@ -0,0 +1,10 @@
+require 'stringio'
+require_relative '../../spec_helper'
+
+describe "StringIO#internal_encoding" do
+ it "returns nil" do
+ io = StringIO.new
+ io.set_encoding Encoding::UTF_8
+ io.internal_encoding.should == nil
+ end
+end
diff --git a/spec/ruby/library/stringio/isatty_spec.rb b/spec/ruby/library/stringio/isatty_spec.rb
new file mode 100644
index 0000000000..1ef33978b5
--- /dev/null
+++ b/spec/ruby/library/stringio/isatty_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/isatty'
+
+describe "StringIO#isatty" do
+ it_behaves_like :stringio_isatty, :isatty
+end
diff --git a/spec/ruby/library/stringio/length_spec.rb b/spec/ruby/library/stringio/length_spec.rb
new file mode 100644
index 0000000000..d3070f50a7
--- /dev/null
+++ b/spec/ruby/library/stringio/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/length'
+
+describe "StringIO#length" do
+ it_behaves_like :stringio_length, :length
+end
diff --git a/spec/ruby/library/stringio/lineno_spec.rb b/spec/ruby/library/stringio/lineno_spec.rb
new file mode 100644
index 0000000000..c620a1a686
--- /dev/null
+++ b/spec/ruby/library/stringio/lineno_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require "stringio"
+
+describe "StringIO#lineno" do
+ before :each do
+ @io = StringIO.new("this\nis\nan\nexample")
+ end
+
+ it "returns the number of lines read" do
+ @io.gets
+ @io.gets
+ @io.gets
+ @io.lineno.should eql(3)
+ end
+end
+
+describe "StringIO#lineno=" do
+ before :each do
+ @io = StringIO.new("this\nis\nan\nexample")
+ end
+
+ it "sets the current line number, but has no impact on the position" do
+ @io.lineno = 3
+ @io.pos.should eql(0)
+
+ @io.gets.should == "this\n"
+ @io.lineno.should eql(4)
+ @io.pos.should eql(5)
+ end
+end
diff --git a/spec/ruby/library/stringio/lines_spec.rb b/spec/ruby/library/stringio/lines_spec.rb
new file mode 100644
index 0000000000..42d11772ae
--- /dev/null
+++ b/spec/ruby/library/stringio/lines_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require 'stringio'
+require_relative 'shared/each'
+
+ruby_version_is ''...'3.0' do
+ describe "StringIO#lines when passed a separator" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :stringio_each_separator, :lines
+ end
+
+ describe "StringIO#lines when passed no arguments" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :stringio_each_no_arguments, :lines
+ end
+
+ describe "StringIO#lines when self is not readable" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :stringio_each_not_readable, :lines
+ end
+
+ describe "StringIO#lines when passed chomp" do
+ before :each do
+ @verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ after :each do
+ $VERBOSE = @verbose
+ end
+
+ it_behaves_like :stringio_each_chomp, :lines
+ end
+end
diff --git a/spec/ruby/library/stringio/new_spec.rb b/spec/ruby/library/stringio/new_spec.rb
new file mode 100644
index 0000000000..e36d210caa
--- /dev/null
+++ b/spec/ruby/library/stringio/new_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'stringio'
+
+describe "StringIO.new" do
+ it "warns when called with a block" do
+ -> { eval("StringIO.new {}") }.should complain(/StringIO::new\(\) does not take block; use StringIO::open\(\) instead/)
+ end
+end \ No newline at end of file
diff --git a/spec/ruby/library/stringio/open_spec.rb b/spec/ruby/library/stringio/open_spec.rb
new file mode 100644
index 0000000000..3068e19435
--- /dev/null
+++ b/spec/ruby/library/stringio/open_spec.rb
@@ -0,0 +1,215 @@
+require_relative '../../spec_helper'
+require 'stringio'
+
+describe "StringIO.open when passed [Object, mode]" do
+ it "uses the passed Object as the StringIO backend" do
+ io = StringIO.open(str = "example", "r")
+ io.string.should equal(str)
+ end
+
+ it "returns the blocks return value when yielding" do
+ ret = StringIO.open("example", "r") { :test }
+ ret.should equal(:test)
+ end
+
+ it "yields self to the passed block" do
+ io = nil
+ StringIO.open("example", "r") { |strio| io = strio }
+ io.should be_kind_of(StringIO)
+ end
+
+ it "closes self after yielding" do
+ io = nil
+ StringIO.open("example", "r") { |strio| io = strio }
+ io.closed?.should be_true
+ end
+
+ it "even closes self when an exception is raised while yielding" do
+ io = nil
+ begin
+ StringIO.open("example", "r") do |strio|
+ io = strio
+ raise "Error"
+ end
+ rescue
+ end
+ io.closed?.should be_true
+ end
+
+ it "sets self's string to nil after yielding" do
+ io = nil
+ StringIO.open("example", "r") { |strio| io = strio }
+ io.string.should be_nil
+ end
+
+ it "even sets self's string to nil when an exception is raised while yielding" do
+ io = nil
+ begin
+ StringIO.open("example", "r") do |strio|
+ io = strio
+ raise "Error"
+ end
+ rescue
+ end
+ io.string.should be_nil
+ end
+
+ it "sets the mode based on the passed mode" do
+ io = StringIO.open("example", "r")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_true
+
+ io = StringIO.open("example", "rb")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_true
+
+ io = StringIO.open("example", "r+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", "rb+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", "w")
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", "wb")
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", "w+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", "wb+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", "a")
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", "ab")
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", "a+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", "ab+")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+ end
+
+ it "allows passing the mode as an Integer" do
+ io = StringIO.open("example", IO::RDONLY)
+ io.closed_read?.should be_false
+ io.closed_write?.should be_true
+
+ io = StringIO.open("example", IO::RDWR)
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", IO::WRONLY)
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", IO::WRONLY | IO::TRUNC)
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", IO::RDWR | IO::TRUNC)
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", IO::WRONLY | IO::APPEND)
+ io.closed_read?.should be_true
+ io.closed_write?.should be_false
+
+ io = StringIO.open("example", IO::RDWR | IO::APPEND)
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+ end
+
+ it "raises a FrozenError when passed a frozen String in truncate mode as StringIO backend" do
+ -> { StringIO.open("example".freeze, IO::TRUNC) }.should raise_error(FrozenError)
+ end
+
+ it "tries to convert the passed mode to a String using #to_str" do
+ obj = mock('to_str')
+ obj.should_receive(:to_str).and_return("r")
+ io = StringIO.open("example", obj)
+
+ io.closed_read?.should be_false
+ io.closed_write?.should be_true
+ end
+
+ it "raises an Errno::EACCES error when passed a frozen string with a write-mode" do
+ (str = "example").freeze
+ -> { StringIO.open(str, "r+") }.should raise_error(Errno::EACCES)
+ -> { StringIO.open(str, "w") }.should raise_error(Errno::EACCES)
+ -> { StringIO.open(str, "a") }.should raise_error(Errno::EACCES)
+ end
+end
+
+describe "StringIO.open when passed [Object]" do
+ it "uses the passed Object as the StringIO backend" do
+ io = StringIO.open(str = "example")
+ io.string.should equal(str)
+ end
+
+ it "yields self to the passed block" do
+ io = nil
+ ret = StringIO.open("example") { |strio| io = strio }
+ io.should equal(ret)
+ end
+
+ it "sets the mode to read-write (r+)" do
+ io = StringIO.open("example")
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.new("example")
+ io.printf("%d", 123)
+ io.string.should == "123mple"
+ end
+
+ it "tries to convert the passed Object to a String using #to_str" do
+ obj = mock('to_str')
+ obj.should_receive(:to_str).and_return("example")
+ io = StringIO.open(obj)
+ io.string.should == "example"
+ end
+
+ it "automatically sets the mode to read-only when passed a frozen string" do
+ (str = "example").freeze
+ io = StringIO.open(str)
+ io.closed_read?.should be_false
+ io.closed_write?.should be_true
+ end
+end
+
+describe "StringIO.open when passed no arguments" do
+ it "yields self to the passed block" do
+ io = nil
+ ret = StringIO.open { |strio| io = strio }
+ io.should equal(ret)
+ end
+
+ it "sets the mode to read-write (r+)" do
+ io = StringIO.open
+ io.closed_read?.should be_false
+ io.closed_write?.should be_false
+
+ io = StringIO.new("example")
+ io.printf("%d", 123)
+ io.string.should == "123mple"
+ end
+
+ it "uses an empty String as the StringIO backend" do
+ StringIO.open.string.should == ""
+ end
+end
diff --git a/spec/ruby/library/stringio/path_spec.rb b/spec/ruby/library/stringio/path_spec.rb
new file mode 100644
index 0000000000..1184ca523f
--- /dev/null
+++ b/spec/ruby/library/stringio/path_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#path" do
+ it "is not defined" do
+ -> { StringIO.new("path").path }.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/library/stringio/pid_spec.rb b/spec/ruby/library/stringio/pid_spec.rb
new file mode 100644
index 0000000000..08f2d7ab1a
--- /dev/null
+++ b/spec/ruby/library/stringio/pid_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#pid" do
+ it "returns nil" do
+ StringIO.new("pid").pid.should be_nil
+ end
+end
diff --git a/spec/ruby/library/stringio/pos_spec.rb b/spec/ruby/library/stringio/pos_spec.rb
new file mode 100644
index 0000000000..81be5f01a5
--- /dev/null
+++ b/spec/ruby/library/stringio/pos_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/tell'
+
+describe "StringIO#pos" do
+ it_behaves_like :stringio_tell, :pos
+end
+
+describe "StringIO#pos=" do
+ before :each do
+ @io = StringIOSpecs.build
+ end
+
+ it "updates the current byte offset" do
+ @io.pos = 26
+ @io.read(1).should == "r"
+ end
+
+ it "raises an EINVAL if given a negative argument" do
+ -> { @io.pos = -10 }.should raise_error(Errno::EINVAL)
+ end
+
+ it "updates the current byte offset after reaching EOF" do
+ @io.read
+ @io.pos = 26
+ @io.read(1).should == "r"
+ end
+end
diff --git a/spec/ruby/library/stringio/print_spec.rb b/spec/ruby/library/stringio/print_spec.rb
new file mode 100644
index 0000000000..6ac6430900
--- /dev/null
+++ b/spec/ruby/library/stringio/print_spec.rb
@@ -0,0 +1,102 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#print" do
+ before :each do
+ @io = StringIO.new('example')
+ end
+
+ it "prints $_ when passed no arguments" do
+ $_ = nil
+ @io.print
+ @io.string.should == "example"
+
+ $_ = "blah"
+ @io.print
+ @io.string.should == "blahple"
+ end
+
+ it "prints the passed arguments to self" do
+ @io.print(5, 6, 7, 8)
+ @io.string.should == "5678ple"
+ end
+
+ it "tries to convert the passed Object to a String using #to_s" do
+ obj = mock("to_s")
+ obj.should_receive(:to_s).and_return("to_s")
+ @io.print(obj)
+ @io.string.should == "to_sple"
+ end
+
+ it "returns nil" do
+ @io.print(1, 2, 3).should be_nil
+ end
+
+ it "pads self with \\000 when the current position is after the end" do
+ @io.pos = 10
+ @io.print(1, 2, 3)
+ @io.string.should == "example\000\000\000123"
+ end
+
+ it "honors the output record separator global" do
+ old_rs = $\
+ suppress_warning {$\ = 'x'}
+
+ begin
+ @io.print(5, 6, 7, 8)
+ @io.string.should == '5678xle'
+ ensure
+ suppress_warning {$\ = old_rs}
+ end
+ end
+
+ it "updates the current position" do
+ @io.print(1, 2, 3)
+ @io.pos.should eql(3)
+
+ @io.print(1, 2, 3)
+ @io.pos.should eql(6)
+ end
+
+ it "correctly updates the current position when honoring the output record separator global" do
+ old_rs = $\
+ suppress_warning {$\ = 'x'}
+
+ begin
+ @io.print(5, 6, 7, 8)
+ @io.pos.should eql(5)
+ ensure
+ suppress_warning {$\ = old_rs}
+ end
+ end
+end
+
+describe "StringIO#print when in append mode" do
+ before :each do
+ @io = StringIO.new("example", "a")
+ end
+
+ it "appends the passed argument to the end of self" do
+ @io.print(", just testing")
+ @io.string.should == "example, just testing"
+
+ @io.print(" and more testing")
+ @io.string.should == "example, just testing and more testing"
+ end
+
+ it "correctly updates self's position" do
+ @io.print(", testing")
+ @io.pos.should eql(16)
+ end
+end
+
+describe "StringIO#print when self is not writable" do
+ it "raises an IOError" do
+ io = StringIO.new("test", "r")
+ -> { io.print("test") }.should raise_error(IOError)
+
+ io = StringIO.new("test")
+ io.close_write
+ -> { io.print("test") }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/printf_spec.rb b/spec/ruby/library/stringio/printf_spec.rb
new file mode 100644
index 0000000000..f3f669a185
--- /dev/null
+++ b/spec/ruby/library/stringio/printf_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../core/kernel/shared/sprintf'
+
+describe "StringIO#printf" do
+ before :each do
+ @io = StringIO.new()
+ end
+
+ it "returns nil" do
+ @io.printf("%d %04x", 123, 123).should be_nil
+ end
+
+ it "pads self with \\000 when the current position is after the end" do
+ @io.pos = 3
+ @io.printf("%d", 123)
+ @io.string.should == "\000\000\000123"
+ end
+
+ it "performs format conversion" do
+ @io.printf("%d %04x", 123, 123)
+ @io.string.should == "123 007b"
+ end
+
+ it "updates the current position" do
+ @io.printf("%d %04x", 123, 123)
+ @io.pos.should eql(8)
+
+ @io.printf("%d %04x", 123, 123)
+ @io.pos.should eql(16)
+ end
+
+ describe "formatting" do
+ it_behaves_like :kernel_sprintf, -> format, *args {
+ io = StringIO.new(+"")
+ io.printf(format, *args)
+ io.string
+ }
+ end
+end
+
+describe "StringIO#printf when in read-write mode" do
+ before :each do
+ @io = StringIO.new("example", "r+")
+ end
+
+ it "starts from the beginning" do
+ @io.printf("%s", "abcdefghijk")
+ @io.string.should == "abcdefghijk"
+ end
+
+ it "does not truncate existing string" do
+ @io.printf("%s", "abc")
+ @io.string.should == "abcmple"
+ end
+
+ it "correctly updates self's position" do
+ @io.printf("%s", "abc")
+ @io.pos.should eql(3)
+ end
+end
+
+describe "StringIO#printf when in append mode" do
+ before :each do
+ @io = StringIO.new("example", "a")
+ end
+
+ it "appends the passed argument to the end of self" do
+ @io.printf("%d %04x", 123, 123)
+ @io.string.should == "example123 007b"
+
+ @io.printf("%d %04x", 123, 123)
+ @io.string.should == "example123 007b123 007b"
+ end
+
+ it "correctly updates self's position" do
+ @io.printf("%d %04x", 123, 123)
+ @io.pos.should eql(15)
+ end
+end
+
+describe "StringIO#printf when self is not writable" do
+ it "raises an IOError" do
+ io = StringIO.new("test", "r")
+ -> { io.printf("test") }.should raise_error(IOError)
+
+ io = StringIO.new("test")
+ io.close_write
+ -> { io.printf("test") }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/putc_spec.rb b/spec/ruby/library/stringio/putc_spec.rb
new file mode 100644
index 0000000000..1ce53b7ef2
--- /dev/null
+++ b/spec/ruby/library/stringio/putc_spec.rb
@@ -0,0 +1,103 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#putc when passed [String]" do
+ before :each do
+ @io = StringIO.new('example')
+ end
+
+ it "overwrites the character at the current position" do
+ @io.putc("t")
+ @io.string.should == "txample"
+
+ @io.pos = 3
+ @io.putc("t")
+ @io.string.should == "txatple"
+ end
+
+ it "only writes the first character from the passed String" do
+ @io.putc("test")
+ @io.string.should == "txample"
+ end
+
+ it "returns the passed String" do
+ str = "test"
+ @io.putc(str).should equal(str)
+ end
+
+ it "correctly updates the current position" do
+ @io.putc("t")
+ @io.pos.should == 1
+
+ @io.putc("test")
+ @io.pos.should == 2
+
+ @io.putc("t")
+ @io.pos.should == 3
+ end
+
+ it "handles concurrent writes correctly" do
+ @io = StringIO.new
+ n = 8
+ go = false
+ threads = n.times.map { |i|
+ Thread.new {
+ Thread.pass until go
+ @io.putc i.to_s
+ }
+ }
+ go = true
+ threads.each(&:join)
+ @io.string.size.should == n
+ end
+end
+
+describe "StringIO#putc when passed [Object]" do
+ before :each do
+ @io = StringIO.new('example')
+ end
+
+ it "it writes the passed Integer % 256 to self" do
+ @io.putc(333) # 333 % 256 == ?M
+ @io.string.should == "Mxample"
+
+ @io.putc(-450) # -450 % 256 == ?>
+ @io.string.should == "M>ample"
+ end
+
+ it "pads self with \\000 when the current position is after the end" do
+ @io.pos = 10
+ @io.putc(?A)
+ @io.string.should == "example\000\000\000A"
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(116)
+ @io.putc(obj)
+ @io.string.should == "txample"
+ end
+
+ it "raises a TypeError when the passed argument can't be coerced to Integer" do
+ -> { @io.putc(Object.new) }.should raise_error(TypeError)
+ end
+end
+
+describe "StringIO#putc when in append mode" do
+ it "appends to the end of self" do
+ io = StringIO.new("test", "a")
+ io.putc(?t)
+ io.string.should == "testt"
+ end
+end
+
+describe "StringIO#putc when self is not writable" do
+ it "raises an IOError" do
+ io = StringIO.new("test", "r")
+ -> { io.putc(?a) }.should raise_error(IOError)
+
+ io = StringIO.new("test")
+ io.close_write
+ -> { io.putc("t") }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/puts_spec.rb b/spec/ruby/library/stringio/puts_spec.rb
new file mode 100644
index 0000000000..9c890262dd
--- /dev/null
+++ b/spec/ruby/library/stringio/puts_spec.rb
@@ -0,0 +1,184 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#puts when passed an Array" do
+ before :each do
+ @io = StringIO.new
+ end
+
+ it "writes each element of the passed Array to self, separated by a newline" do
+ @io.puts([1, 2, 3, 4])
+ @io.string.should == "1\n2\n3\n4\n"
+
+ @io.puts([1, 2], [3, 4])
+ @io.string.should == "1\n2\n3\n4\n1\n2\n3\n4\n"
+ end
+
+ it "flattens nested Arrays" do
+ @io.puts([1, [2, [3, [4]]]])
+ @io.string.should == "1\n2\n3\n4\n"
+ end
+
+ it "handles self-recursive arrays correctly" do
+ (ary = [5])
+ ary << ary
+ @io.puts(ary)
+ @io.string.should == "5\n[...]\n"
+ end
+
+ it "does not honor the global output record separator $\\" do
+ begin
+ old_rs = $\
+ suppress_warning {$\ = "test"}
+ @io.puts([1, 2, 3, 4])
+ @io.string.should == "1\n2\n3\n4\n"
+ ensure
+ suppress_warning {$\ = old_rs}
+ end
+ end
+
+ it "first tries to convert each Array element to an Array using #to_ary" do
+ obj = mock("Object")
+ obj.should_receive(:to_ary).and_return(["to_ary"])
+ @io.puts([obj])
+ @io.string.should == "to_ary\n"
+ end
+
+ it "then tries to convert each Array element to a String using #to_s" do
+ obj = mock("Object")
+ obj.should_receive(:to_s).and_return("to_s")
+ @io.puts([obj])
+ @io.string.should == "to_s\n"
+ end
+
+ it "returns general object info if :to_s does not return a string" do
+ object = mock('hola')
+ object.should_receive(:to_s).and_return(false)
+
+ @io.puts(object).should == nil
+ @io.string.should == object.inspect.split(" ")[0] + ">\n"
+ end
+end
+
+describe "StringIO#puts when passed 1 or more objects" do
+ before :each do
+ @io = StringIO.new
+ end
+
+ it "does not honor the global output record separator $\\" do
+ begin
+ old_rs = $\
+ suppress_warning {$\ = "test"}
+ @io.puts(1, 2, 3, 4)
+ @io.string.should == "1\n2\n3\n4\n"
+ ensure
+ suppress_warning {$\ = old_rs}
+ end
+ end
+
+ it "does not put a \\n after each Objects that end in a newline" do
+ @io.puts("1\n", "2\n", "3\n")
+ @io.string.should == "1\n2\n3\n"
+ end
+
+ it "first tries to convert each Object to an Array using #to_ary" do
+ obj = mock("Object")
+ obj.should_receive(:to_ary).and_return(["to_ary"])
+ @io.puts(obj)
+ @io.string.should == "to_ary\n"
+ end
+
+ it "then tries to convert each Object to a String using #to_s" do
+ obj = mock("Object")
+ obj.should_receive(:to_s).and_return("to_s")
+ @io.puts(obj)
+ @io.string.should == "to_s\n"
+ end
+
+ it "prints a newline when passed an empty string" do
+ @io.puts ''
+ @io.string.should == "\n"
+ end
+
+ it "handles concurrent writes correctly" do
+ n = 8
+ go = false
+ threads = n.times.map { |i|
+ Thread.new {
+ Thread.pass until go
+ @io.puts i
+ }
+ }
+ go = true
+ threads.each(&:join)
+ @io.string.size.should == n.times.map { |i| "#{i}\n" }.join.size
+ end
+end
+
+describe "StringIO#puts when passed no arguments" do
+ before :each do
+ @io = StringIO.new
+ end
+
+ it "returns nil" do
+ @io.puts.should be_nil
+ end
+
+ it "prints a newline" do
+ @io.puts
+ @io.string.should == "\n"
+ end
+
+ it "does not honor the global output record separator $\\" do
+ begin
+ old_rs = $\
+ suppress_warning {$\ = "test"}
+ @io.puts
+ @io.string.should == "\n"
+ ensure
+ suppress_warning {$\ = old_rs}
+ end
+ end
+end
+
+describe "StringIO#puts when in append mode" do
+ before :each do
+ @io = StringIO.new("example", "a")
+ end
+
+ it "appends the passed argument to the end of self" do
+ @io.puts(", just testing")
+ @io.string.should == "example, just testing\n"
+
+ @io.puts(" and more testing")
+ @io.string.should == "example, just testing\n and more testing\n"
+ end
+
+ it "correctly updates self's position" do
+ @io.puts(", testing")
+ @io.pos.should eql(17)
+ end
+end
+
+describe "StringIO#puts when self is not writable" do
+ it "raises an IOError" do
+ io = StringIO.new("test", "r")
+ -> { io.puts }.should raise_error(IOError)
+
+ io = StringIO.new("test")
+ io.close_write
+ -> { io.puts }.should raise_error(IOError)
+ end
+end
+
+describe "StringIO#puts when passed an encoded string" do
+ it "stores the bytes unmodified" do
+ io = StringIO.new("")
+ io.puts "\x00\x01\x02"
+ io.puts "æåø"
+
+ io.string.should == "\x00\x01\x02\næåø\n"
+ end
+end
diff --git a/spec/ruby/library/stringio/read_nonblock_spec.rb b/spec/ruby/library/stringio/read_nonblock_spec.rb
new file mode 100644
index 0000000000..d4ec56d9aa
--- /dev/null
+++ b/spec/ruby/library/stringio/read_nonblock_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require "stringio"
+require_relative 'shared/read'
+require_relative 'shared/sysread'
+
+describe "StringIO#read_nonblock when passed length, buffer" do
+ it_behaves_like :stringio_read, :read_nonblock
+
+ it "accepts :exception option" do
+ io = StringIO.new("example")
+ io.read_nonblock(3, buffer = "", exception: true)
+ buffer.should == "exa"
+ end
+end
+
+describe "StringIO#read_nonblock when passed length" do
+ it_behaves_like :stringio_read_length, :read_nonblock
+
+ it "accepts :exception option" do
+ io = StringIO.new("example")
+ io.read_nonblock(3, exception: true).should == "exa"
+ end
+end
+
+describe "StringIO#read_nonblock when passed nil" do
+ it_behaves_like :stringio_read_nil, :read_nonblock
+end
+
+describe "StringIO#read_nonblock when passed length" do
+ it_behaves_like :stringio_sysread_length, :read_nonblock
+end
+
+describe "StringIO#read_nonblock" do
+
+ it "accepts an exception option" do
+ stringio = StringIO.new('foo')
+ stringio.read_nonblock(3, exception: false).should == 'foo'
+ end
+
+ context "when exception option is set to false" do
+ context "when the end is reached" do
+ it "returns nil" do
+ stringio = StringIO.new('')
+ stringio << "hello"
+ stringio.rewind
+
+ stringio.read_nonblock(5).should == "hello"
+ stringio.read_nonblock(5, exception: false).should be_nil
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/library/stringio/read_spec.rb b/spec/ruby/library/stringio/read_spec.rb
new file mode 100644
index 0000000000..52ab3dcf47
--- /dev/null
+++ b/spec/ruby/library/stringio/read_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+require "stringio"
+require_relative 'shared/read'
+
+describe "StringIO#read when passed length, buffer" do
+ it_behaves_like :stringio_read, :read
+end
+
+describe "StringIO#read when passed [length]" do
+ it_behaves_like :stringio_read_length, :read
+end
+
+describe "StringIO#read when passed no arguments" do
+ it_behaves_like :stringio_read_no_arguments, :read
+
+ it "returns an empty string if at EOF" do
+ @io.read.should == "example"
+ @io.read.should == ""
+ end
+end
+
+describe "StringIO#read when passed nil" do
+ it_behaves_like :stringio_read_nil, :read
+
+ it "returns an empty string if at EOF" do
+ @io.read(nil).should == "example"
+ @io.read(nil).should == ""
+ end
+end
+
+describe "StringIO#read when self is not readable" do
+ it_behaves_like :stringio_read_not_readable, :read
+end
+
+describe "StringIO#read when passed [length]" do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "returns nil when self's position is at the end" do
+ @io.pos = 7
+ @io.read(10).should be_nil
+ end
+
+ it "returns an empty String when length is 0" do
+ @io.read(0).should == ""
+ end
+end
+
+describe "StringIO#read when passed length and a buffer" do
+ before :each do
+ @io = StringIO.new("abcdefghijklmnopqrstuvwxyz")
+ end
+
+ it "reads [length] characters into the buffer" do
+ buf = "foo"
+ result = @io.read(10, buf)
+
+ buf.should == "abcdefghij"
+ result.should equal(buf)
+ end
+end
diff --git a/spec/ruby/library/stringio/readbyte_spec.rb b/spec/ruby/library/stringio/readbyte_spec.rb
new file mode 100644
index 0000000000..41a0911293
--- /dev/null
+++ b/spec/ruby/library/stringio/readbyte_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require 'stringio'
+require_relative 'shared/readchar'
+
+describe "StringIO#readbyte" do
+ it_behaves_like :stringio_readchar, :readbyte
+
+ it "reads the next 8-bit byte from self's current position" do
+ io = StringIO.new("example")
+
+ io.readbyte.should == 101
+
+ io.pos = 4
+ io.readbyte.should == 112
+ end
+end
+
+describe "StringIO#readbyte when self is not readable" do
+ it_behaves_like :stringio_readchar_not_readable, :readbyte
+end
diff --git a/spec/ruby/library/stringio/readchar_spec.rb b/spec/ruby/library/stringio/readchar_spec.rb
new file mode 100644
index 0000000000..38944819a2
--- /dev/null
+++ b/spec/ruby/library/stringio/readchar_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require 'stringio'
+require_relative 'shared/readchar'
+
+describe "StringIO#readchar" do
+ it_behaves_like :stringio_readchar, :readchar
+
+ it "reads the next 8-bit byte from self's current position" do
+ io = StringIO.new("example")
+
+ io.readchar.should == ?e
+
+ io.pos = 4
+ io.readchar.should == ?p
+ end
+end
+
+describe "StringIO#readchar when self is not readable" do
+ it_behaves_like :stringio_readchar_not_readable, :readchar
+end
diff --git a/spec/ruby/library/stringio/readline_spec.rb b/spec/ruby/library/stringio/readline_spec.rb
new file mode 100644
index 0000000000..b794e5fade
--- /dev/null
+++ b/spec/ruby/library/stringio/readline_spec.rb
@@ -0,0 +1,150 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+
+describe "StringIO#readline when passed [separator]" do
+ before :each do
+ @io = StringIO.new("this>is>an>example")
+ end
+
+ it "returns the data read till the next occurrence of the passed separator" do
+ @io.readline(">").should == "this>"
+ @io.readline(">").should == "is>"
+ @io.readline(">").should == "an>"
+ @io.readline(">").should == "example"
+ end
+
+ it "sets $_ to the read content" do
+ @io.readline(">")
+ $_.should == "this>"
+ @io.readline(">")
+ $_.should == "is>"
+ @io.readline(">")
+ $_.should == "an>"
+ @io.readline(">")
+ $_.should == "example"
+ end
+
+ it "updates self's lineno by one" do
+ @io.readline(">")
+ @io.lineno.should eql(1)
+
+ @io.readline(">")
+ @io.lineno.should eql(2)
+
+ @io.readline(">")
+ @io.lineno.should eql(3)
+ end
+
+ it "returns the next paragraph when the passed separator is an empty String" do
+ io = StringIO.new("this is\n\nan example")
+ io.readline("").should == "this is\n\n"
+ io.readline("").should == "an example"
+ end
+
+ it "returns the remaining content starting at the current position when passed nil" do
+ io = StringIO.new("this is\n\nan example")
+ io.pos = 5
+ io.readline(nil).should == "is\n\nan example"
+ end
+
+ it "tries to convert the passed separator to a String using #to_str" do
+ obj = mock('to_str')
+ obj.should_receive(:to_str).and_return(">")
+ @io.readline(obj).should == "this>"
+ end
+end
+
+describe "StringIO#readline when passed no argument" do
+ before :each do
+ @io = StringIO.new("this is\nan example\nfor StringIO#readline")
+ end
+
+ it "returns the data read till the next occurrence of $/ or till eof" do
+ @io.readline.should == "this is\n"
+
+ begin
+ old_sep = $/
+ suppress_warning {$/ = " "}
+ @io.readline.should == "an "
+ @io.readline.should == "example\nfor "
+ @io.readline.should == "StringIO#readline"
+ ensure
+ suppress_warning {$/ = old_sep}
+ end
+ end
+
+ it "sets $_ to the read content" do
+ @io.readline
+ $_.should == "this is\n"
+ @io.readline
+ $_.should == "an example\n"
+ @io.readline
+ $_.should == "for StringIO#readline"
+ end
+
+ it "updates self's position" do
+ @io.readline
+ @io.pos.should eql(8)
+
+ @io.readline
+ @io.pos.should eql(19)
+
+ @io.readline
+ @io.pos.should eql(40)
+ end
+
+ it "updates self's lineno" do
+ @io.readline
+ @io.lineno.should eql(1)
+
+ @io.readline
+ @io.lineno.should eql(2)
+
+ @io.readline
+ @io.lineno.should eql(3)
+ end
+
+ it "raises an IOError if self is at the end" do
+ @io.pos = 40
+ -> { @io.readline }.should raise_error(IOError)
+ end
+end
+
+describe "StringIO#readline when in write-only mode" do
+ it "raises an IOError" do
+ io = StringIO.new("xyz", "w")
+ -> { io.readline }.should raise_error(IOError)
+
+ io = StringIO.new("xyz")
+ io.close_read
+ -> { io.readline }.should raise_error(IOError)
+ end
+end
+
+describe "StringIO#readline when passed [chomp]" do
+ it "returns the data read without a trailing newline character" do
+ io = StringIO.new("this>is>an>example\n")
+ io.readline(chomp: true).should == "this>is>an>example"
+ end
+end
+
+describe "StringIO#readline when passed [limit]" do
+ before :each do
+ @io = StringIO.new("this>is>an>example")
+ end
+
+ it "returns the data read until the limit is met" do
+ io = StringIO.new("this>is>an>example\n")
+ io.readline(3).should == "thi"
+ end
+
+ it "returns a blank string when passed a limit of 0" do
+ @io.readline(0).should == ""
+ end
+
+ it "ignores it when the limit is negative" do
+ seen = []
+ @io.readline(-4).should == "this>is>an>example"
+ end
+end
diff --git a/spec/ruby/library/stringio/readlines_spec.rb b/spec/ruby/library/stringio/readlines_spec.rb
new file mode 100644
index 0000000000..c471d0fd73
--- /dev/null
+++ b/spec/ruby/library/stringio/readlines_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#readlines when passed [separator]" do
+ before :each do
+ @io = StringIO.new("this>is>an>example")
+ end
+
+ it "returns an Array containing lines based on the passed separator" do
+ @io.readlines(">").should == ["this>", "is>", "an>", "example"]
+ end
+
+ it "updates self's position based on the number of read bytes" do
+ @io.readlines(">")
+ @io.pos.should eql(18)
+ end
+
+ it "updates self's lineno based on the number of read lines" do
+ @io.readlines(">")
+ @io.lineno.should eql(4)
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.readlines(">")
+ $_.should == "test"
+ end
+
+ it "returns an Array containing all paragraphs when the passed separator is an empty String" do
+ io = StringIO.new("this is\n\nan example")
+ io.readlines("").should == ["this is\n\n", "an example"]
+ end
+
+ it "returns the remaining content as one line starting at the current position when passed nil" do
+ io = StringIO.new("this is\n\nan example")
+ io.pos = 5
+ io.readlines(nil).should == ["is\n\nan example"]
+ end
+
+ it "tries to convert the passed separator to a String using #to_str" do
+ obj = mock('to_str')
+ obj.stub!(:to_str).and_return(">")
+ @io.readlines(obj).should == ["this>", "is>", "an>", "example"]
+ end
+end
+
+describe "StringIO#readlines when passed no argument" do
+ before :each do
+ @io = StringIO.new("this is\nan example\nfor StringIO#readlines")
+ end
+
+ it "returns an Array containing lines based on $/" do
+ begin
+ old_sep = $/;
+ suppress_warning {$/ = " "}
+ @io.readlines.should == ["this ", "is\nan ", "example\nfor ", "StringIO#readlines"]
+ ensure
+ suppress_warning {$/ = old_sep}
+ end
+ end
+
+ it "updates self's position based on the number of read bytes" do
+ @io.readlines
+ @io.pos.should eql(41)
+ end
+
+ it "updates self's lineno based on the number of read lines" do
+ @io.readlines
+ @io.lineno.should eql(3)
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.readlines(">")
+ $_.should == "test"
+ end
+
+ it "returns an empty Array when self is at the end" do
+ @io.pos = 41
+ @io.readlines.should == []
+ end
+end
+
+describe "StringIO#readlines when in write-only mode" do
+ it "raises an IOError" do
+ io = StringIO.new("xyz", "w")
+ -> { io.readlines }.should raise_error(IOError)
+
+ io = StringIO.new("xyz")
+ io.close_read
+ -> { io.readlines }.should raise_error(IOError)
+ end
+end
+
+describe "StringIO#readlines when passed [chomp]" do
+ it "returns the data read without a trailing newline character" do
+ io = StringIO.new("this>is\nan>example\r\n")
+ io.readlines(chomp: true).should == ["this>is", "an>example"]
+ end
+end
+
+describe "StringIO#readlines when passed [limit]" do
+ before :each do
+ @io = StringIO.new("a b c d e\n1 2 3 4 5")
+ end
+
+ it "returns the data read until the limit is met" do
+ @io.readlines(4).should == ["a b ", "c d ", "e\n", "1 2 ", "3 4 ", "5"]
+ end
+
+ it "raises ArgumentError when limit is 0" do
+ -> { @io.readlines(0) }.should raise_error(ArgumentError)
+ end
+
+ it "ignores it when the limit is negative" do
+ @io.readlines(-4).should == ["a b c d e\n", "1 2 3 4 5"]
+ end
+end
diff --git a/spec/ruby/library/stringio/readpartial_spec.rb b/spec/ruby/library/stringio/readpartial_spec.rb
new file mode 100644
index 0000000000..2601fe8c42
--- /dev/null
+++ b/spec/ruby/library/stringio/readpartial_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#readpartial" do
+ before :each do
+ @string = StringIO.new('Stop, look, listen')
+ end
+
+ after :each do
+ @string.close unless @string.closed?
+ end
+
+ it "raises IOError on closed stream" do
+ @string.close
+ -> { @string.readpartial(10) }.should raise_error(IOError)
+ end
+
+ it "reads at most the specified number of bytes" do
+
+ # buffered read
+ @string.read(1).should == 'S'
+ # return only specified number, not the whole buffer
+ @string.readpartial(1).should == "t"
+ end
+
+ it "reads after ungetc with data in the buffer" do
+ c = @string.getc
+ @string.ungetc(c)
+ @string.readpartial(4).should == "Stop"
+ @string.readpartial(3).should == ", l"
+ end
+
+ it "reads after ungetc without data in the buffer" do
+ @string = StringIO.new
+ @string.write("f").should == 1
+ @string.rewind
+ c = @string.getc
+ c.should == 'f'
+ @string.ungetc(c).should == nil
+
+ @string.readpartial(2).should == "f"
+ @string.rewind
+ # now, also check that the ungot char is cleared and
+ # not returned again
+ @string.write("b").should == 1
+ @string.rewind
+ @string.readpartial(2).should == "b"
+ end
+
+ it "discards the existing buffer content upon successful read" do
+ buffer = "existing"
+ @string.readpartial(11, buffer)
+ buffer.should == "Stop, look,"
+ end
+
+ it "raises EOFError on EOF" do
+ @string.readpartial(18).should == 'Stop, look, listen'
+ -> { @string.readpartial(10) }.should raise_error(EOFError)
+ end
+
+ it "discards the existing buffer content upon error" do
+ buffer = 'hello'
+ @string.readpartial(100)
+ -> { @string.readpartial(1, buffer) }.should raise_error(EOFError)
+ buffer.should be_empty
+ end
+
+ it "raises IOError if the stream is closed" do
+ @string.close
+ -> { @string.readpartial(1) }.should raise_error(IOError)
+ end
+
+ it "raises ArgumentError if the negative argument is provided" do
+ -> { @string.readpartial(-1) }.should raise_error(ArgumentError)
+ end
+
+ it "immediately returns an empty string if the length argument is 0" do
+ @string.readpartial(0).should == ""
+ end
+end
diff --git a/spec/ruby/library/stringio/reopen_spec.rb b/spec/ruby/library/stringio/reopen_spec.rb
new file mode 100644
index 0000000000..4863a5332b
--- /dev/null
+++ b/spec/ruby/library/stringio/reopen_spec.rb
@@ -0,0 +1,281 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#reopen when passed [Object, Integer]" do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "reopens self with the passed Object in the passed mode" do
+ @io.reopen("reopened", IO::RDONLY)
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_true
+ @io.string.should == "reopened"
+
+ @io.reopen("reopened, twice", IO::WRONLY)
+ @io.closed_read?.should be_true
+ @io.closed_write?.should be_false
+ @io.string.should == "reopened, twice"
+
+ @io.reopen("reopened, another time", IO::RDWR)
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_false
+ @io.string.should == "reopened, another time"
+ end
+
+ ruby_version_is ""..."3.0" do
+ # NOTE: WEIRD!
+ it "does not taint self when the passed Object was tainted" do
+ @io.reopen("reopened".taint, IO::RDONLY)
+ @io.tainted?.should be_false
+
+ @io.reopen("reopened".taint, IO::WRONLY)
+ @io.tainted?.should be_false
+ end
+ end
+
+ it "tries to convert the passed Object to a String using #to_str" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return("to_str")
+ @io.reopen(obj, IO::RDWR)
+ @io.string.should == "to_str"
+ end
+
+ it "raises a TypeError when the passed Object can't be converted to a String" do
+ -> { @io.reopen(Object.new, IO::RDWR) }.should raise_error(TypeError)
+ end
+
+ it "raises an Errno::EACCES when trying to reopen self with a frozen String in write-mode" do
+ -> { @io.reopen("burn".freeze, IO::WRONLY) }.should raise_error(Errno::EACCES)
+ -> { @io.reopen("burn".freeze, IO::WRONLY | IO::APPEND) }.should raise_error(Errno::EACCES)
+ end
+
+ it "raises a FrozenError when trying to reopen self with a frozen String in truncate-mode" do
+ -> { @io.reopen("burn".freeze, IO::RDONLY | IO::TRUNC) }.should raise_error(FrozenError)
+ end
+
+ it "does not raise IOError when passed a frozen String in read-mode" do
+ @io.reopen("burn".freeze, IO::RDONLY)
+ @io.string.should == "burn"
+ end
+end
+
+describe "StringIO#reopen when passed [Object, Object]" do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "reopens self with the passed Object in the passed mode" do
+ @io.reopen("reopened", "r")
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_true
+ @io.string.should == "reopened"
+
+ @io.reopen("reopened, twice", "r+")
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_false
+ @io.string.should == "reopened, twice"
+
+ @io.reopen("reopened, another", "w+")
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_false
+ @io.string.should == ""
+
+ @io.reopen("reopened, another time", "r+")
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_false
+ @io.string.should == "reopened, another time"
+ end
+
+ it "truncates the passed String when opened in truncate mode" do
+ @io.reopen(str = "reopened", "w")
+ str.should == ""
+ end
+
+ ruby_version_is ""..."3.0" do
+ # NOTE: WEIRD!
+ it "does not taint self when the passed Object was tainted" do
+ @io.reopen("reopened".taint, "r")
+ @io.tainted?.should be_false
+
+ @io.reopen("reopened".taint, "w")
+ @io.tainted?.should be_false
+ end
+ end
+
+ it "tries to convert the passed Object to a String using #to_str" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return("to_str")
+ @io.reopen(obj, "r")
+ @io.string.should == "to_str"
+ end
+
+ it "raises a TypeError when the passed Object can't be converted to a String using #to_str" do
+ -> { @io.reopen(Object.new, "r") }.should raise_error(TypeError)
+ end
+
+ it "resets self's position to 0" do
+ @io.read(5)
+ @io.reopen("reopened")
+ @io.pos.should eql(0)
+ end
+
+ it "resets self's line number to 0" do
+ @io.gets
+ @io.reopen("reopened")
+ @io.lineno.should eql(0)
+ end
+
+ it "tries to convert the passed mode Object to an Integer using #to_str" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return("r")
+ @io.reopen("reopened", obj)
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_true
+ @io.string.should == "reopened"
+ end
+
+ it "raises an Errno::EACCES error when trying to reopen self with a frozen String in write-mode" do
+ -> { @io.reopen("burn".freeze, 'w') }.should raise_error(Errno::EACCES)
+ -> { @io.reopen("burn".freeze, 'w+') }.should raise_error(Errno::EACCES)
+ -> { @io.reopen("burn".freeze, 'a') }.should raise_error(Errno::EACCES)
+ -> { @io.reopen("burn".freeze, "r+") }.should raise_error(Errno::EACCES)
+ end
+
+ it "does not raise IOError if a frozen string is passed in read mode" do
+ @io.reopen("burn".freeze, "r")
+ @io.string.should == "burn"
+ end
+end
+
+describe "StringIO#reopen when passed [String]" do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "reopens self with the passed String in read-write mode" do
+ @io.close
+
+ @io.reopen("reopened")
+
+ @io.closed_write?.should be_false
+ @io.closed_read?.should be_false
+
+ @io.string.should == "reopened"
+ end
+
+ ruby_version_is ""..."3.0" do
+ # NOTE: WEIRD!
+ it "does not taint self when the passed Object was tainted" do
+ @io.reopen("reopened".taint)
+ @io.tainted?.should be_false
+ end
+ end
+
+ it "resets self's position to 0" do
+ @io.read(5)
+ @io.reopen("reopened")
+ @io.pos.should eql(0)
+ end
+
+ it "resets self's line number to 0" do
+ @io.gets
+ @io.reopen("reopened")
+ @io.lineno.should eql(0)
+ end
+end
+
+describe "StringIO#reopen when passed [Object]" do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "raises a TypeError when passed an Object that can't be converted to a StringIO" do
+ -> { @io.reopen(Object.new) }.should raise_error(TypeError)
+ end
+
+ it "does not try to convert the passed Object to a String using #to_str" do
+ obj = mock("not to_str")
+ obj.should_not_receive(:to_str)
+ -> { @io.reopen(obj) }.should raise_error(TypeError)
+ end
+
+ it "tries to convert the passed Object to a StringIO using #to_strio" do
+ obj = mock("to_strio")
+ obj.should_receive(:to_strio).and_return(StringIO.new("to_strio"))
+ @io.reopen(obj)
+ @io.string.should == "to_strio"
+ end
+end
+
+describe "StringIO#reopen when passed no arguments" do
+ before :each do
+ @io = StringIO.new("example\nsecond line")
+ end
+
+ it "resets self's mode to read-write" do
+ @io.close
+ @io.reopen
+ @io.closed_read?.should be_false
+ @io.closed_write?.should be_false
+ end
+
+ it "resets self's position to 0" do
+ @io.read(5)
+ @io.reopen
+ @io.pos.should eql(0)
+ end
+
+ it "resets self's line number to 0" do
+ @io.gets
+ @io.reopen
+ @io.lineno.should eql(0)
+ end
+end
+
+# NOTE: Some reopen specs disabled due to MRI bugs. See:
+# http://rubyforge.org/tracker/index.php?func=detail&aid=13919&group_id=426&atid=1698
+# for details.
+describe "StringIO#reopen" do
+ before :each do
+ @io = StringIO.new('hello','a')
+ end
+
+ # TODO: find out if this is really a bug
+ it "reopens a stream when given a String argument" do
+ @io.reopen('goodbye').should == @io
+ @io.string.should == 'goodbye'
+ @io << 'x'
+ @io.string.should == 'xoodbye'
+ end
+
+ it "reopens a stream in append mode when flagged as such" do
+ @io.reopen('goodbye', 'a').should == @io
+ @io.string.should == 'goodbye'
+ @io << 'x'
+ @io.string.should == 'goodbyex'
+ end
+
+ it "reopens and truncate when reopened in write mode" do
+ @io.reopen('goodbye', 'wb').should == @io
+ @io.string.should == ''
+ @io << 'x'
+ @io.string.should == 'x'
+ end
+
+ it "truncates the given string, not a copy" do
+ str = 'goodbye'
+ @io.reopen(str, 'w')
+ @io.string.should == ''
+ str.should == ''
+ end
+
+ it "does not truncate the content even when the StringIO argument is in the truncate mode" do
+ orig_io = StringIO.new("Original StringIO", IO::RDWR|IO::TRUNC)
+ orig_io.write("BLAH") # make sure the content is not empty
+
+ @io.reopen(orig_io)
+ @io.string.should == "BLAH"
+ end
+
+end
diff --git a/spec/ruby/library/stringio/rewind_spec.rb b/spec/ruby/library/stringio/rewind_spec.rb
new file mode 100644
index 0000000000..5f885ecf81
--- /dev/null
+++ b/spec/ruby/library/stringio/rewind_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#rewind" do
+ before :each do
+ @io = StringIO.new("hello\nworld")
+ @io.pos = 3
+ @io.lineno = 1
+ end
+
+ it "returns 0" do
+ @io.rewind.should eql(0)
+ end
+
+ it "resets the position" do
+ @io.rewind
+ @io.pos.should == 0
+ end
+
+ it "resets the line number" do
+ @io.rewind
+ @io.lineno.should == 0
+ end
+end
diff --git a/spec/ruby/library/stringio/seek_spec.rb b/spec/ruby/library/stringio/seek_spec.rb
new file mode 100644
index 0000000000..253b5027a9
--- /dev/null
+++ b/spec/ruby/library/stringio/seek_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#seek" do
+ before :each do
+ @io = StringIO.new("12345678")
+ end
+
+ it "seeks from the current position when whence is IO::SEEK_CUR" do
+ @io.pos = 1
+ @io.seek(1, IO::SEEK_CUR)
+ @io.pos.should eql(2)
+
+ @io.seek(-1, IO::SEEK_CUR)
+ @io.pos.should eql(1)
+ end
+
+ it "seeks from the end of self when whence is IO::SEEK_END" do
+ @io.seek(3, IO::SEEK_END)
+ @io.pos.should eql(11) # Outside of the StringIO's content
+
+ @io.seek(-2, IO::SEEK_END)
+ @io.pos.should eql(6)
+ end
+
+ it "seeks to an absolute position when whence is IO::SEEK_SET" do
+ @io.seek(5, IO::SEEK_SET)
+ @io.pos.should == 5
+
+ @io.pos = 3
+ @io.seek(5, IO::SEEK_SET)
+ @io.pos.should == 5
+ end
+
+ it "raises an Errno::EINVAL error on negative amounts when whence is IO::SEEK_SET" do
+ -> { @io.seek(-5, IO::SEEK_SET) }.should raise_error(Errno::EINVAL)
+ end
+
+ it "raises an Errno::EINVAL error on incorrect whence argument" do
+ -> { @io.seek(0, 3) }.should raise_error(Errno::EINVAL)
+ -> { @io.seek(0, -1) }.should raise_error(Errno::EINVAL)
+ -> { @io.seek(0, 2**16) }.should raise_error(Errno::EINVAL)
+ -> { @io.seek(0, -2**16) }.should raise_error(Errno::EINVAL)
+ end
+
+ it "tries to convert the passed Object to a String using #to_int" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(2)
+ @io.seek(obj)
+ @io.pos.should eql(2)
+ end
+
+ it "raises a TypeError when the passed Object can't be converted to an Integer" do
+ -> { @io.seek(Object.new) }.should raise_error(TypeError)
+ end
+end
+
+describe "StringIO#seek when self is closed" do
+ before :each do
+ @io = StringIO.new("example")
+ @io.close
+ end
+
+ it "raises an IOError" do
+ -> { @io.seek(5) }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/set_encoding_spec.rb b/spec/ruby/library/stringio/set_encoding_spec.rb
new file mode 100644
index 0000000000..21d45750f3
--- /dev/null
+++ b/spec/ruby/library/stringio/set_encoding_spec.rb
@@ -0,0 +1,20 @@
+require 'stringio'
+require_relative '../../spec_helper'
+
+describe "StringIO#set_encoding" do
+ it "sets the encoding of the underlying String if the String is not frozen" do
+ str = "".encode(Encoding::US_ASCII)
+
+ io = StringIO.new(str)
+ io.set_encoding Encoding::UTF_8
+ io.string.encoding.should == Encoding::UTF_8
+ end
+
+ it "does not set the encoding of the underlying String if the String is frozen" do
+ str = "".encode(Encoding::US_ASCII).freeze
+
+ io = StringIO.new(str)
+ io.set_encoding Encoding::UTF_8
+ io.string.encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/codepoints.rb b/spec/ruby/library/stringio/shared/codepoints.rb
new file mode 100644
index 0000000000..9d84aa4919
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/codepoints.rb
@@ -0,0 +1,45 @@
+# -*- encoding: utf-8 -*-
+describe :stringio_codepoints, shared: true do
+ before :each do
+ @io = StringIO.new("∂φ/∂x = gaîté")
+ @enum = @io.send(@method)
+ end
+
+ it "returns an Enumerator" do
+ @enum.should be_an_instance_of(Enumerator)
+ end
+
+ it "yields each codepoint code in turn" do
+ @enum.to_a.should == [8706, 966, 47, 8706, 120, 32, 61, 32, 103, 97, 238, 116, 233]
+ end
+
+ it "yields each codepoint starting from the current position" do
+ @io.pos = 15
+ @enum.to_a.should == [238, 116, 233]
+ end
+
+ it "raises an error if reading invalid sequence" do
+ @io.pos = 1 # inside of a multibyte sequence
+ -> { @enum.first }.should raise_error(ArgumentError)
+ end
+
+ it "raises an IOError if not readable" do
+ @io.close_read
+ -> { @enum.to_a }.should raise_error(IOError)
+
+ io = StringIO.new("xyz", "w")
+ -> { io.send(@method).to_a }.should raise_error(IOError)
+ end
+
+
+ it "calls the given block" do
+ r = []
+ @io.send(@method){|c| r << c }
+ r.should == [8706, 966, 47, 8706, 120, 32, 61, 32, 103, 97, 238, 116, 233]
+ end
+
+ it "returns self" do
+ @io.send(@method) {|l| l }.should equal(@io)
+ end
+
+end
diff --git a/spec/ruby/library/stringio/shared/each.rb b/spec/ruby/library/stringio/shared/each.rb
new file mode 100644
index 0000000000..bf3265ee46
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/each.rb
@@ -0,0 +1,163 @@
+describe :stringio_each_separator, shared: true do
+ before :each do
+ @io = StringIO.new("a b c d e\n1 2 3 4 5")
+ end
+
+ it "uses the passed argument as the line separator" do
+ seen = []
+ @io.send(@method, " ") {|s| seen << s}
+ seen.should == ["a ", "b ", "c ", "d ", "e\n1 ", "2 ", "3 ", "4 ", "5"]
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.send(@method, " ") { |s| s}
+ $_.should == "test"
+ end
+
+ it "returns self" do
+ @io.send(@method) {|l| l }.should equal(@io)
+ end
+
+ it "tries to convert the passed separator to a String using #to_str" do
+ obj = mock("to_str")
+ obj.stub!(:to_str).and_return(" ")
+
+ seen = []
+ @io.send(@method, obj) { |l| seen << l }
+ seen.should == ["a ", "b ", "c ", "d ", "e\n1 ", "2 ", "3 ", "4 ", "5"]
+ end
+
+ it "yields self's content starting from the current position when the passed separator is nil" do
+ seen = []
+ io = StringIO.new("1 2 1 2 1 2")
+ io.pos = 2
+ io.send(@method, nil) {|s| seen << s}
+ seen.should == ["2 1 2 1 2"]
+ end
+
+ ruby_version_is ''..."3.2" do
+ it "yields each paragraph with two separation characters when passed an empty String as separator" do
+ seen = []
+ io = StringIO.new("para1\n\npara2\n\n\npara3")
+ io.send(@method, "") {|s| seen << s}
+ seen.should == ["para1\n\n", "para2\n\n", "para3"]
+ end
+ end
+
+ ruby_version_is "3.2" do
+ it "yields each paragraph with all separation characters when passed an empty String as separator" do
+ seen = []
+ io = StringIO.new("para1\n\npara2\n\n\npara3")
+ io.send(@method, "") {|s| seen << s}
+ seen.should == ["para1\n\n", "para2\n\n\n", "para3"]
+ end
+ end
+end
+
+describe :stringio_each_no_arguments, shared: true do
+ before :each do
+ @io = StringIO.new("a b c d e\n1 2 3 4 5")
+ end
+
+ it "yields each line to the passed block" do
+ seen = []
+ @io.send(@method) {|s| seen << s }
+ seen.should == ["a b c d e\n", "1 2 3 4 5"]
+ end
+
+ it "yields each line starting from the current position" do
+ seen = []
+ @io.pos = 4
+ @io.send(@method) {|s| seen << s }
+ seen.should == ["c d e\n", "1 2 3 4 5"]
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.send(@method) { |s| s}
+ $_.should == "test"
+ end
+
+ it "uses $/ as the default line separator" do
+ seen = []
+ begin
+ old_rs = $/
+ suppress_warning {$/ = " "}
+ @io.send(@method) {|s| seen << s }
+ seen.should eql(["a ", "b ", "c ", "d ", "e\n1 ", "2 ", "3 ", "4 ", "5"])
+ ensure
+ suppress_warning {$/ = old_rs}
+ end
+ end
+
+ it "returns self" do
+ @io.send(@method) {|l| l }.should equal(@io)
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @io.send(@method)
+ enum.instance_of?(Enumerator).should be_true
+
+ seen = []
+ enum.each { |b| seen << b }
+ seen.should == ["a b c d e\n", "1 2 3 4 5"]
+ end
+end
+
+describe :stringio_each_not_readable, shared: true do
+ it "raises an IOError" do
+ io = StringIO.new("a b c d e", "w")
+ -> { io.send(@method) { |b| b } }.should raise_error(IOError)
+
+ io = StringIO.new("a b c d e")
+ io.close_read
+ -> { io.send(@method) { |b| b } }.should raise_error(IOError)
+ end
+end
+
+describe :stringio_each_chomp, shared: true do
+ it "yields each line with removed newline characters to the passed block" do
+ seen = []
+ io = StringIO.new("a b \rc d e\n1 2 3 4 5\r\nthe end")
+ io.send(@method, chomp: true) {|s| seen << s }
+ seen.should == ["a b \rc d e", "1 2 3 4 5", "the end"]
+ end
+
+ it "returns each line with removed newline characters when called without block" do
+ seen = []
+ io = StringIO.new("a b \rc d e\n1 2 3 4 5\r\nthe end")
+ enum = io.send(@method, chomp: true)
+ enum.each {|s| seen << s }
+ seen.should == ["a b \rc d e", "1 2 3 4 5", "the end"]
+ end
+end
+
+describe :stringio_each_separator_and_chomp, shared: true do
+ it "yields each line with removed separator to the passed block" do
+ seen = []
+ io = StringIO.new("a b \nc d e|1 2 3 4 5\n|the end")
+ io.send(@method, "|", chomp: true) {|s| seen << s }
+ seen.should == ["a b \nc d e", "1 2 3 4 5\n", "the end"]
+ end
+
+ it "returns each line with removed separator when called without block" do
+ seen = []
+ io = StringIO.new("a b \nc d e|1 2 3 4 5\n|the end")
+ enum = io.send(@method, "|", chomp: true)
+ enum.each {|s| seen << s }
+ seen.should == ["a b \nc d e", "1 2 3 4 5\n", "the end"]
+ end
+end
+
+describe :stringio_each_limit, shared: true do
+ before :each do
+ @io = StringIO.new("a b c d e\n1 2 3 4 5")
+ end
+
+ it "returns the data read until the limit is met" do
+ seen = []
+ @io.send(@method, 4) { |s| seen << s }
+ seen.should == ["a b ", "c d ", "e\n", "1 2 ", "3 4 ", "5"]
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/each_byte.rb b/spec/ruby/library/stringio/shared/each_byte.rb
new file mode 100644
index 0000000000..56734ff99d
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/each_byte.rb
@@ -0,0 +1,48 @@
+describe :stringio_each_byte, shared: true do
+ before :each do
+ @io = StringIO.new("xyz")
+ end
+
+ it "yields each character code in turn" do
+ seen = []
+ @io.send(@method) { |b| seen << b }
+ seen.should == [120, 121, 122]
+ end
+
+ it "updates the position before each yield" do
+ seen = []
+ @io.send(@method) { |b| seen << @io.pos }
+ seen.should == [1, 2, 3]
+ end
+
+ it "does not yield if the current position is out of bounds" do
+ @io.pos = 1000
+ seen = nil
+ @io.send(@method) { |b| seen = b }
+ seen.should be_nil
+ end
+
+ it "returns self" do
+ @io.send(@method) {}.should equal(@io)
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @io.send(@method)
+ enum.instance_of?(Enumerator).should be_true
+
+ seen = []
+ enum.each { |b| seen << b }
+ seen.should == [120, 121, 122]
+ end
+end
+
+describe :stringio_each_byte_not_readable, shared: true do
+ it "raises an IOError" do
+ io = StringIO.new("xyz", "w")
+ -> { io.send(@method) { |b| b } }.should raise_error(IOError)
+
+ io = StringIO.new("xyz")
+ io.close_read
+ -> { io.send(@method) { |b| b } }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/each_char.rb b/spec/ruby/library/stringio/shared/each_char.rb
new file mode 100644
index 0000000000..bcdac53282
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/each_char.rb
@@ -0,0 +1,36 @@
+# -*- encoding: utf-8 -*-
+describe :stringio_each_char, shared: true do
+ before :each do
+ @io = StringIO.new("xyz äöü")
+ end
+
+ it "yields each character code in turn" do
+ seen = []
+ @io.send(@method) { |c| seen << c }
+ seen.should == ["x", "y", "z", " ", "ä", "ö", "ü"]
+ end
+
+ it "returns self" do
+ @io.send(@method) {}.should equal(@io)
+ end
+
+ it "returns an Enumerator when passed no block" do
+ enum = @io.send(@method)
+ enum.instance_of?(Enumerator).should be_true
+
+ seen = []
+ enum.each { |c| seen << c }
+ seen.should == ["x", "y", "z", " ", "ä", "ö", "ü"]
+ end
+end
+
+describe :stringio_each_char_not_readable, shared: true do
+ it "raises an IOError" do
+ io = StringIO.new("xyz", "w")
+ -> { io.send(@method) { |b| b } }.should raise_error(IOError)
+
+ io = StringIO.new("xyz")
+ io.close_read
+ -> { io.send(@method) { |b| b } }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/eof.rb b/spec/ruby/library/stringio/shared/eof.rb
new file mode 100644
index 0000000000..e0368a2892
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/eof.rb
@@ -0,0 +1,24 @@
+describe :stringio_eof, shared: true do
+ before :each do
+ @io = StringIO.new("eof")
+ end
+
+ it "returns true when self's position is greater than or equal to self's size" do
+ @io.pos = 3
+ @io.send(@method).should be_true
+
+ @io.pos = 6
+ @io.send(@method).should be_true
+ end
+
+ it "returns false when self's position is less than self's size" do
+ @io.pos = 0
+ @io.send(@method).should be_false
+
+ @io.pos = 1
+ @io.send(@method).should be_false
+
+ @io.pos = 2
+ @io.send(@method).should be_false
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/getc.rb b/spec/ruby/library/stringio/shared/getc.rb
new file mode 100644
index 0000000000..6318bcc30f
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/getc.rb
@@ -0,0 +1,43 @@
+describe :stringio_getc, shared: true do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "increases self's position by one" do
+ @io.send(@method)
+ @io.pos.should eql(1)
+
+ @io.send(@method)
+ @io.pos.should eql(2)
+
+ @io.send(@method)
+ @io.pos.should eql(3)
+ end
+
+ it "returns nil when called at the end of self" do
+ @io.pos = 7
+ @io.send(@method).should be_nil
+ @io.send(@method).should be_nil
+ @io.send(@method).should be_nil
+ end
+
+ it "does not increase self's position when called at the end of file" do
+ @io.pos = 7
+ @io.send(@method)
+ @io.pos.should eql(7)
+
+ @io.send(@method)
+ @io.pos.should eql(7)
+ end
+end
+
+describe :stringio_getc_not_readable, shared: true do
+ it "raises an IOError" do
+ io = StringIO.new("xyz", "w")
+ -> { io.send(@method) }.should raise_error(IOError)
+
+ io = StringIO.new("xyz")
+ io.close_read
+ -> { io.send(@method) }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/isatty.rb b/spec/ruby/library/stringio/shared/isatty.rb
new file mode 100644
index 0000000000..3da5999953
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/isatty.rb
@@ -0,0 +1,5 @@
+describe :stringio_isatty, shared: true do
+ it "returns false" do
+ StringIO.new('tty').send(@method).should be_false
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/length.rb b/spec/ruby/library/stringio/shared/length.rb
new file mode 100644
index 0000000000..60a4eb1bdd
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/length.rb
@@ -0,0 +1,5 @@
+describe :stringio_length, shared: true do
+ it "returns the length of the wrapped string" do
+ StringIO.new("example").send(@method).should == 7
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/read.rb b/spec/ruby/library/stringio/shared/read.rb
new file mode 100644
index 0000000000..252a85d89d
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/read.rb
@@ -0,0 +1,127 @@
+describe :stringio_read, shared: true do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "returns the passed buffer String" do
+ # Note: Rubinius bug:
+ # @io.send(@method, 7, buffer = "").should equal(buffer)
+ ret = @io.send(@method, 7, buffer = "")
+ ret.should equal(buffer)
+ end
+
+ it "reads length bytes and writes them to the buffer String" do
+ @io.send(@method, 7, buffer = "")
+ buffer.should == "example"
+ end
+
+ it "tries to convert the passed buffer Object to a String using #to_str" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(buffer = "")
+
+ @io.send(@method, 7, obj)
+ buffer.should == "example"
+ end
+
+ it "raises a TypeError when the passed buffer Object can't be converted to a String" do
+ -> { @io.send(@method, 7, Object.new) }.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError error when passed a frozen String as buffer" do
+ -> { @io.send(@method, 7, "".freeze) }.should raise_error(FrozenError)
+ end
+end
+
+describe :stringio_read_length, shared: true do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "reads length bytes from the current position and returns them" do
+ @io.pos = 3
+ @io.send(@method, 4).should == "mple"
+ end
+
+ it "reads at most the whole content" do
+ @io.send(@method, 999).should == "example"
+ end
+
+ it "correctly updates the position" do
+ @io.send(@method, 3)
+ @io.pos.should eql(3)
+
+ @io.send(@method, 999)
+ @io.pos.should eql(7)
+ end
+
+ it "tries to convert the passed length to an Integer using #to_int" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(7)
+ @io.send(@method, obj).should == "example"
+ end
+
+ it "raises a TypeError when the passed length can't be converted to an Integer" do
+ -> { @io.send(@method, Object.new) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the passed length is negative" do
+ -> { @io.send(@method, -2) }.should raise_error(ArgumentError)
+ end
+
+ it "returns a binary String" do
+ @io.send(@method, 4).encoding.should == Encoding::BINARY
+ end
+end
+
+describe :stringio_read_no_arguments, shared: true do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "reads the whole content starting from the current position" do
+ @io.send(@method).should == "example"
+
+ @io.pos = 3
+ @io.send(@method).should == "mple"
+ end
+
+ it "correctly updates the current position" do
+ @io.send(@method)
+ @io.pos.should eql(7)
+ end
+
+ it "correctly update the current position in bytes when multi-byte characters are used" do
+ @io.print("example\u03A3") # Overwrite the original string with 8 characters containing 9 bytes.
+ @io.send(@method)
+ @io.pos.should eql(9)
+ end
+end
+
+describe :stringio_read_nil, shared: true do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "returns the remaining content from the current position" do
+ @io.send(@method, nil).should == "example"
+
+ @io.pos = 4
+ @io.send(@method, nil).should == "ple"
+ end
+
+ it "updates the current position" do
+ @io.send(@method, nil)
+ @io.pos.should eql(7)
+ end
+end
+
+describe :stringio_read_not_readable, shared: true do
+ it "raises an IOError" do
+ io = StringIO.new("test", "w")
+ -> { io.send(@method) }.should raise_error(IOError)
+
+ io = StringIO.new("test")
+ io.close_read
+ -> { io.send(@method) }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/readchar.rb b/spec/ruby/library/stringio/shared/readchar.rb
new file mode 100644
index 0000000000..4248e75420
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/readchar.rb
@@ -0,0 +1,29 @@
+describe :stringio_readchar, shared: true do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "correctly updates the current position" do
+ @io.send(@method)
+ @io.pos.should == 1
+
+ @io.send(@method)
+ @io.pos.should == 2
+ end
+
+ it "raises an EOFError when self is at the end" do
+ @io.pos = 7
+ -> { @io.send(@method) }.should raise_error(EOFError)
+ end
+end
+
+describe :stringio_readchar_not_readable, shared: true do
+ it "raises an IOError" do
+ io = StringIO.new("a b c d e", "w")
+ -> { io.send(@method) }.should raise_error(IOError)
+
+ io = StringIO.new("a b c d e")
+ io.close_read
+ -> { io.send(@method) }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/sysread.rb b/spec/ruby/library/stringio/shared/sysread.rb
new file mode 100644
index 0000000000..3376bd9907
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/sysread.rb
@@ -0,0 +1,15 @@
+describe :stringio_sysread_length, :shared => true do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "returns an empty String when passed 0 and no data remains" do
+ @io.send(@method, 8).should == "example"
+ @io.send(@method, 0).should == ""
+ end
+
+ it "raises an EOFError when passed length > 0 and no data remains" do
+ @io.read.should == "example"
+ -> { @io.sysread(1) }.should raise_error(EOFError)
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/tell.rb b/spec/ruby/library/stringio/shared/tell.rb
new file mode 100644
index 0000000000..852c51c192
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/tell.rb
@@ -0,0 +1,12 @@
+describe :stringio_tell, shared: true do
+ before :each do
+ @io = StringIOSpecs.build
+ end
+
+ it "returns the current byte offset" do
+ @io.getc
+ @io.send(@method).should == 1
+ @io.read(7)
+ @io.send(@method).should == 8
+ end
+end
diff --git a/spec/ruby/library/stringio/shared/write.rb b/spec/ruby/library/stringio/shared/write.rb
new file mode 100644
index 0000000000..d9c21028e0
--- /dev/null
+++ b/spec/ruby/library/stringio/shared/write.rb
@@ -0,0 +1,121 @@
+describe :stringio_write, shared: true do
+ before :each do
+ @io = StringIO.new('12345')
+ end
+
+ it "tries to convert the passed Object to a String using #to_s" do
+ obj = mock("to_s")
+ obj.should_receive(:to_s).and_return("to_s")
+ @io.send(@method, obj)
+ @io.string.should == "to_s5"
+ end
+end
+
+describe :stringio_write_string, shared: true do
+ before :each do
+ @io = StringIO.new('12345')
+ end
+
+ # TODO: RDoc says that #write appends at the current position.
+ it "writes the passed String at the current buffer position" do
+ @io.pos = 2
+ @io.send(@method, 'x').should == 1
+ @io.string.should == '12x45'
+ @io.send(@method, 7).should == 1
+ @io.string.should == '12x75'
+ end
+
+ it "pads self with \\000 when the current position is after the end" do
+ @io.pos = 8
+ @io.send(@method, 'x')
+ @io.string.should == "12345\000\000\000x"
+ @io.send(@method, 9)
+ @io.string.should == "12345\000\000\000x9"
+ end
+
+ it "returns the number of bytes written" do
+ @io.send(@method, '').should == 0
+ @io.send(@method, nil).should == 0
+ str = "1" * 100
+ @io.send(@method, str).should == 100
+ end
+
+ it "updates self's position" do
+ @io.send(@method, 'test')
+ @io.pos.should eql(4)
+ end
+
+ it "handles concurrent writes correctly" do
+ @io = StringIO.new
+ n = 8
+ go = false
+ threads = n.times.map { |i|
+ Thread.new {
+ Thread.pass until go
+ @io.write i.to_s
+ }
+ }
+ go = true
+ threads.each(&:join)
+ @io.string.size.should == n.times.map(&:to_s).join.size
+ end
+
+ ruby_version_is ""..."3.0" do
+ it "does not taint self when the passed argument is tainted" do
+ @io.send(@method, "test".taint)
+ @io.tainted?.should be_false
+ end
+ end
+
+ it "handles writing non-ASCII UTF-8 after seek" do
+ @io.binmode
+ @io << "\x80"
+ @io.pos = 0
+ @io << "\x81"
+ @io.string.should == "\x812345".b
+ end
+
+ it "handles writing with position < buffer size" do
+ @io.pos = 2
+ @io.write "abc"
+ @io.string.should == "12abc"
+
+ @io.pos = 2
+ @io.write "de"
+ @io.string.should == "12dec"
+
+ @io.pos = 2
+ @io.write "fghi"
+ @io.string.should == "12fghi"
+ end
+end
+
+describe :stringio_write_not_writable, shared: true do
+ it "raises an IOError" do
+ io = StringIO.new("test", "r")
+ -> { io.send(@method, "test") }.should raise_error(IOError)
+
+ io = StringIO.new("test")
+ io.close_write
+ -> { io.send(@method, "test") }.should raise_error(IOError)
+ end
+end
+
+describe :stringio_write_append, shared: true do
+ before :each do
+ @io = StringIO.new("example", "a")
+ end
+
+ it "appends the passed argument to the end of self" do
+ @io.send(@method, ", just testing")
+ @io.string.should == "example, just testing"
+
+ @io.send(@method, " and more testing")
+ @io.string.should == "example, just testing and more testing"
+ end
+
+ it "correctly updates self's position" do
+ @io.send(@method, ", testing")
+ @io.pos.should eql(16)
+ end
+end
diff --git a/spec/ruby/library/stringio/size_spec.rb b/spec/ruby/library/stringio/size_spec.rb
new file mode 100644
index 0000000000..f674d22db9
--- /dev/null
+++ b/spec/ruby/library/stringio/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/length'
+
+describe "StringIO#size" do
+ it_behaves_like :stringio_length, :size
+end
diff --git a/spec/ruby/library/stringio/string_spec.rb b/spec/ruby/library/stringio/string_spec.rb
new file mode 100644
index 0000000000..1ed5233ba6
--- /dev/null
+++ b/spec/ruby/library/stringio/string_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#string" do
+ it "returns the underlying string" do
+ io = StringIO.new(str = "hello")
+ io.string.should equal(str)
+ end
+end
+
+describe "StringIO#string=" do
+ before :each do
+ @io = StringIO.new("example\nstring")
+ end
+
+ it "returns the passed String" do
+ str = "test"
+ (@io.string = str).should equal(str)
+ end
+
+ it "changes the underlying string" do
+ str = "hello"
+ @io.string = str
+ @io.string.should equal(str)
+ end
+
+ it "resets the position" do
+ @io.pos = 1
+ @io.string = "other"
+ @io.pos.should eql(0)
+ end
+
+ it "resets the line number" do
+ @io.lineno = 1
+ @io.string = "other"
+ @io.lineno.should eql(0)
+ end
+
+ it "tries to convert the passed Object to a String using #to_str" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return("to_str")
+
+ @io.string = obj
+ @io.string.should == "to_str"
+ end
+
+ it "raises a TypeError when the passed Object can't be converted to an Integer" do
+ -> { @io.seek(Object.new) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/stringio/stringio_spec.rb b/spec/ruby/library/stringio/stringio_spec.rb
new file mode 100644
index 0000000000..5ef42b3390
--- /dev/null
+++ b/spec/ruby/library/stringio/stringio_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require "stringio"
+
+describe "StringIO" do
+ it "includes the Enumerable module" do
+ StringIO.should include(Enumerable)
+ end
+end
diff --git a/spec/ruby/library/stringio/sync_spec.rb b/spec/ruby/library/stringio/sync_spec.rb
new file mode 100644
index 0000000000..e717a5697b
--- /dev/null
+++ b/spec/ruby/library/stringio/sync_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#sync" do
+ it "returns true" do
+ StringIO.new('').sync.should be_true
+ end
+end
+
+describe "StringIO#sync=" do
+ before :each do
+ @io = StringIO.new('')
+ end
+
+ it "does not change 'sync' status" do
+ @io.sync = false
+ @io.sync.should be_true
+ end
+end
diff --git a/spec/ruby/library/stringio/sysread_spec.rb b/spec/ruby/library/stringio/sysread_spec.rb
new file mode 100644
index 0000000000..8f78073f42
--- /dev/null
+++ b/spec/ruby/library/stringio/sysread_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require "stringio"
+require_relative 'shared/read'
+
+describe "StringIO#sysread when passed length, buffer" do
+ it_behaves_like :stringio_read, :sysread
+end
+
+describe "StringIO#sysread when passed [length]" do
+ it_behaves_like :stringio_read_length, :sysread
+end
+
+describe "StringIO#sysread when passed no arguments" do
+ it_behaves_like :stringio_read_no_arguments, :sysread
+
+ it "returns an empty String if at EOF" do
+ @io.sysread.should == "example"
+ @io.sysread.should == ""
+ end
+end
+
+describe "StringIO#sysread when self is not readable" do
+ it_behaves_like :stringio_read_not_readable, :sysread
+end
+
+describe "StringIO#sysread when passed nil" do
+ it_behaves_like :stringio_read_nil, :sysread
+
+ it "returns an empty String if at EOF" do
+ @io.sysread(nil).should == "example"
+ @io.sysread(nil).should == ""
+ end
+end
+
+describe "StringIO#sysread when passed [length]" do
+ before :each do
+ @io = StringIO.new("example")
+ end
+
+ it "raises an EOFError when self's position is at the end" do
+ @io.pos = 7
+ -> { @io.sysread(10) }.should raise_error(EOFError)
+ end
+
+ it "returns an empty String when length is 0" do
+ @io.sysread(0).should == ""
+ end
+end
diff --git a/spec/ruby/library/stringio/syswrite_spec.rb b/spec/ruby/library/stringio/syswrite_spec.rb
new file mode 100644
index 0000000000..c4891e669b
--- /dev/null
+++ b/spec/ruby/library/stringio/syswrite_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/write'
+
+describe "StringIO#syswrite when passed [Object]" do
+ it_behaves_like :stringio_write, :syswrite
+end
+
+describe "StringIO#syswrite when passed [String]" do
+ it_behaves_like :stringio_write_string, :syswrite
+end
+
+describe "StringIO#syswrite when self is not writable" do
+ it_behaves_like :stringio_write_not_writable, :syswrite
+end
+
+describe "StringIO#syswrite when in append mode" do
+ it_behaves_like :stringio_write_append, :syswrite
+end
diff --git a/spec/ruby/library/stringio/tell_spec.rb b/spec/ruby/library/stringio/tell_spec.rb
new file mode 100644
index 0000000000..8350ee6f4d
--- /dev/null
+++ b/spec/ruby/library/stringio/tell_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/tell'
+
+describe "StringIO#tell" do
+ it_behaves_like :stringio_tell, :tell
+end
diff --git a/spec/ruby/library/stringio/truncate_spec.rb b/spec/ruby/library/stringio/truncate_spec.rb
new file mode 100644
index 0000000000..e8d7f1a15d
--- /dev/null
+++ b/spec/ruby/library/stringio/truncate_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+require "stringio"
+
+describe "StringIO#truncate when passed [length]" do
+ before :each do
+ @io = StringIO.new('123456789')
+ end
+
+ it "returns an Integer" do
+ @io.truncate(4).should be_kind_of(Integer)
+ end
+
+ it "truncated the underlying string down to the passed length" do
+ @io.truncate(4)
+ @io.string.should == "1234"
+ end
+
+ it "does not create a copy of the underlying string" do
+ io = StringIO.new(str = "123456789")
+ io.truncate(4)
+ io.string.should equal(str)
+ end
+
+ it "does not change the position" do
+ @io.pos = 7
+ @io.truncate(4)
+ @io.pos.should eql(7)
+ end
+
+ it "can grow a string to a larger size, padding it with \\000" do
+ @io.truncate(12)
+ @io.string.should == "123456789\000\000\000"
+ end
+
+ it "raises an Errno::EINVAL when the passed length is negative" do
+ -> { @io.truncate(-1) }.should raise_error(Errno::EINVAL)
+ -> { @io.truncate(-10) }.should raise_error(Errno::EINVAL)
+ end
+
+ it "tries to convert the passed length to an Integer using #to_int" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(4)
+
+ @io.truncate(obj)
+ @io.string.should == "1234"
+ end
+
+ it "raises a TypeError when the passed length can't be converted to an Integer" do
+ -> { @io.truncate(Object.new) }.should raise_error(TypeError)
+ end
+end
+
+describe "StringIO#truncate when self is not writable" do
+ it "raises an IOError" do
+ io = StringIO.new("test", "r")
+ -> { io.truncate(2) }.should raise_error(IOError)
+
+ io = StringIO.new("test")
+ io.close_write
+ -> { io.truncate(2) }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/stringio/tty_spec.rb b/spec/ruby/library/stringio/tty_spec.rb
new file mode 100644
index 0000000000..c6293dcbd7
--- /dev/null
+++ b/spec/ruby/library/stringio/tty_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/isatty'
+
+describe "StringIO#tty?" do
+ it_behaves_like :stringio_isatty, :tty?
+end
diff --git a/spec/ruby/library/stringio/ungetbyte_spec.rb b/spec/ruby/library/stringio/ungetbyte_spec.rb
new file mode 100644
index 0000000000..87b27b837e
--- /dev/null
+++ b/spec/ruby/library/stringio/ungetbyte_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: false
+require_relative '../../spec_helper'
+require 'stringio'
+
+describe "StringIO#ungetbyte" do
+ it "ungets a single byte from a string starting with a single byte character" do
+ str = 'This is a simple string.'
+ io = StringIO.new("#{str}")
+ c = io.getc
+ c.should == 'T'
+ io.ungetbyte(83)
+ io.string.should == 'Shis is a simple string.'
+ end
+
+ it "ungets a single byte from a string in the middle of a multibyte character" do
+ str = "\u01a9"
+ io = StringIO.new(str)
+ b = io.getbyte
+ b.should == 0xc6 # First byte of UTF-8 encoding of \u01a9
+ io.ungetbyte(0xce) # First byte of UTF-8 encoding of \u03a9
+ io.string.should == "\u03a9"
+ end
+
+ it "constrains the value of a numeric argument to a single byte" do
+ str = 'This is a simple string.'
+ io = StringIO.new("#{str}")
+ c = io.getc
+ c.should == 'T'
+ io.ungetbyte(83 | 0xff00)
+ io.string.should == 'Shis is a simple string.'
+ end
+
+ it "ungets the bytes of a string if given a string as an argument" do
+ str = "\u01a9"
+ io = StringIO.new(str)
+ b = io.getbyte
+ b.should == 0xc6 # First byte of UTF-8 encoding of \u01a9
+ io.ungetbyte("\u01a9")
+ io.string.bytes.should == [198, 169, 169]
+ end
+
+end
diff --git a/spec/ruby/library/stringio/ungetc_spec.rb b/spec/ruby/library/stringio/ungetc_spec.rb
new file mode 100644
index 0000000000..91ef2100a1
--- /dev/null
+++ b/spec/ruby/library/stringio/ungetc_spec.rb
@@ -0,0 +1,72 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "StringIO#ungetc when passed [char]" do
+ before :each do
+ @io = StringIO.new('1234')
+ end
+
+ it "writes the passed char before the current position" do
+ @io.pos = 1
+ @io.ungetc(?A)
+ @io.string.should == 'A234'
+ end
+
+ it "returns nil" do
+ @io.pos = 1
+ @io.ungetc(?A).should be_nil
+ end
+
+ it "decreases the current position by one" do
+ @io.pos = 2
+ @io.ungetc(?A)
+ @io.pos.should eql(1)
+ end
+
+ it "pads with \\000 when the current position is after the end" do
+ @io.pos = 15
+ @io.ungetc(?A)
+ @io.string.should == "1234\000\000\000\000\000\000\000\000\000\000A"
+ end
+
+ it "tries to convert the passed argument to an String using #to_str" do
+ obj = mock("to_str")
+ obj.should_receive(:to_str).and_return(?A)
+
+ @io.pos = 1
+ @io.ungetc(obj)
+ @io.string.should == "A234"
+ end
+
+ it "raises a TypeError when the passed length can't be converted to an Integer or String" do
+ -> { @io.ungetc(Object.new) }.should raise_error(TypeError)
+ end
+end
+
+describe "StringIO#ungetc when self is not readable" do
+ it "raises an IOError" do
+ io = StringIO.new("test", "w")
+ io.pos = 1
+ -> { io.ungetc(?A) }.should raise_error(IOError)
+
+ io = StringIO.new("test")
+ io.pos = 1
+ io.close_read
+ -> { io.ungetc(?A) }.should raise_error(IOError)
+ end
+end
+
+# Note: This is incorrect.
+#
+# describe "StringIO#ungetc when self is not writable" do
+# it "raises an IOError" do
+# io = StringIO.new("test", "r")
+# io.pos = 1
+# lambda { io.ungetc(?A) }.should raise_error(IOError)
+#
+# io = StringIO.new("test")
+# io.pos = 1
+# io.close_write
+# lambda { io.ungetc(?A) }.should raise_error(IOError)
+# end
+# end
diff --git a/spec/ruby/library/stringio/write_nonblock_spec.rb b/spec/ruby/library/stringio/write_nonblock_spec.rb
new file mode 100644
index 0000000000..a457b97667
--- /dev/null
+++ b/spec/ruby/library/stringio/write_nonblock_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/write'
+
+describe "StringIO#write_nonblock when passed [Object]" do
+ it_behaves_like :stringio_write, :write_nonblock
+end
+
+describe "StringIO#write_nonblock when passed [String]" do
+ it_behaves_like :stringio_write_string, :write_nonblock
+
+ it "accepts :exception option" do
+ io = StringIO.new("12345", "a")
+ io.write_nonblock("67890", exception: true)
+ io.string.should == "1234567890"
+ end
+end
+
+describe "StringIO#write_nonblock when self is not writable" do
+ it_behaves_like :stringio_write_not_writable, :write_nonblock
+end
+
+describe "StringIO#write_nonblock when in append mode" do
+ it_behaves_like :stringio_write_append, :write_nonblock
+end
diff --git a/spec/ruby/library/stringio/write_spec.rb b/spec/ruby/library/stringio/write_spec.rb
new file mode 100644
index 0000000000..3f755c32b4
--- /dev/null
+++ b/spec/ruby/library/stringio/write_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/write'
+
+describe "StringIO#write when passed [Object]" do
+ it_behaves_like :stringio_write, :write
+end
+
+describe "StringIO#write when passed [String]" do
+ it_behaves_like :stringio_write_string, :write
+end
+
+describe "StringIO#write when self is not writable" do
+ it_behaves_like :stringio_write_not_writable, :write
+end
+
+describe "StringIO#write when in append mode" do
+ it_behaves_like :stringio_write_append, :write
+end
diff --git a/spec/ruby/library/stringscanner/append_spec.rb b/spec/ruby/library/stringscanner/append_spec.rb
new file mode 100644
index 0000000000..fef5dcf2bd
--- /dev/null
+++ b/spec/ruby/library/stringscanner/append_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/concat'
+require 'strscan'
+
+describe "StringScanner#<<" do
+ it_behaves_like :strscan_concat, :<<
+end
+
+describe "StringScanner#<< when passed an Integer" do
+ it_behaves_like :strscan_concat_fixnum, :<<
+end
diff --git a/spec/ruby/library/stringscanner/beginning_of_line_spec.rb b/spec/ruby/library/stringscanner/beginning_of_line_spec.rb
new file mode 100644
index 0000000000..3f6f0da75f
--- /dev/null
+++ b/spec/ruby/library/stringscanner/beginning_of_line_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/bol'
+require 'strscan'
+
+describe "StringScanner#beginning_of_line?" do
+ it_behaves_like :strscan_bol, :beginning_of_line?
+end
diff --git a/spec/ruby/library/stringscanner/bol_spec.rb b/spec/ruby/library/stringscanner/bol_spec.rb
new file mode 100644
index 0000000000..d31766e0e2
--- /dev/null
+++ b/spec/ruby/library/stringscanner/bol_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/bol'
+require 'strscan'
+
+describe "StringScanner#bol?" do
+ it_behaves_like :strscan_bol, :bol?
+end
diff --git a/spec/ruby/library/stringscanner/check_spec.rb b/spec/ruby/library/stringscanner/check_spec.rb
new file mode 100644
index 0000000000..a97c26af83
--- /dev/null
+++ b/spec/ruby/library/stringscanner/check_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#check" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the value that scan would return, without advancing the scan pointer" do
+ @s.check(/This/).should == "This"
+ @s.matched.should == "This"
+ @s.pos.should == 0
+ @s.check(/is/).should == nil
+ @s.matched.should == nil
+ end
+
+ it "treats String as the pattern itself" do
+ @s.check("This").should == "This"
+ @s.matched.should == "This"
+ @s.pos.should == 0
+ @s.check(/is/).should == nil
+ @s.matched.should == nil
+ end
+
+end
diff --git a/spec/ruby/library/stringscanner/check_until_spec.rb b/spec/ruby/library/stringscanner/check_until_spec.rb
new file mode 100644
index 0000000000..ad222fd76b
--- /dev/null
+++ b/spec/ruby/library/stringscanner/check_until_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#check_until" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the same value of scan_until, but don't advances the scan pointer" do
+ @s.check_until(/a/).should == "This is a"
+ @s.pos.should == 0
+ @s.matched.should == "a"
+ @s.check_until(/test/).should == "This is a test"
+ end
+
+ it "raises TypeError if given a String" do
+ -> {
+ @s.check_until('T')
+ }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)')
+ end
+end
diff --git a/spec/ruby/library/stringscanner/clear_spec.rb b/spec/ruby/library/stringscanner/clear_spec.rb
new file mode 100644
index 0000000000..7ae089704a
--- /dev/null
+++ b/spec/ruby/library/stringscanner/clear_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'shared/terminate'
+require 'strscan'
+
+describe "StringScanner#clear" do
+ it_behaves_like :strscan_terminate, :clear
+
+ it "warns in verbose mode that the method is obsolete" do
+ s = StringScanner.new("abc")
+ -> {
+ s.clear
+ }.should complain(/clear.*obsolete.*terminate/, verbose: true)
+
+ -> {
+ s.clear
+ }.should_not complain(verbose: false)
+ end
+end
diff --git a/spec/ruby/library/stringscanner/concat_spec.rb b/spec/ruby/library/stringscanner/concat_spec.rb
new file mode 100644
index 0000000000..4f790e2505
--- /dev/null
+++ b/spec/ruby/library/stringscanner/concat_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/concat'
+require 'strscan'
+
+describe "StringScanner#concat" do
+ it_behaves_like :strscan_concat, :concat
+end
+
+describe "StringScanner#concat when passed an Integer" do
+ it_behaves_like :strscan_concat_fixnum, :concat
+end
diff --git a/spec/ruby/library/stringscanner/dup_spec.rb b/spec/ruby/library/stringscanner/dup_spec.rb
new file mode 100644
index 0000000000..0fc52a1477
--- /dev/null
+++ b/spec/ruby/library/stringscanner/dup_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#dup" do
+ before :each do
+ @string = "this is a test"
+ @orig_s = StringScanner.new(@string)
+ end
+
+ it "copies the passed StringScanner's content to self" do
+ s = @orig_s.dup
+ s.string.should == @string
+ end
+
+ it "copies the passed StringScanner's position to self" do
+ @orig_s.pos = 5
+ s = @orig_s.dup
+ s.pos.should eql(5)
+ end
+
+ it "copies previous match state" do
+ @orig_s.scan(/\w+/)
+ @orig_s.scan(/\s/)
+
+ @orig_s.pre_match.should == "this"
+
+ s = @orig_s.dup
+ s.pre_match.should == "this"
+
+ s.unscan
+ s.scan(/\s/).should == " "
+ end
+
+ it "copies the passed StringScanner scan pointer to self" do
+ @orig_s.terminate
+ s = @orig_s.dup
+ s.eos?.should be_true
+ end
+end
diff --git a/spec/ruby/library/stringscanner/element_reference_spec.rb b/spec/ruby/library/stringscanner/element_reference_spec.rb
new file mode 100644
index 0000000000..60fe15d807
--- /dev/null
+++ b/spec/ruby/library/stringscanner/element_reference_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#[]" do
+ before :each do
+ @s = StringScanner.new("Fri Jun 13 2008 22:43")
+ end
+
+ it "returns nil if there is no current match" do
+ @s[0].should be_nil
+ end
+
+ it "returns the n-th subgroup in the most recent match" do
+ @s.scan(/(\w+) (\w+) (\d+) /)
+ @s[0].should == "Fri Jun 13 "
+ @s[1].should == "Fri"
+ @s[2].should == "Jun"
+ @s[3].should == "13"
+ @s[-3].should == "Fri"
+ @s[-2].should == "Jun"
+ @s[-1].should == "13"
+ end
+
+ it "returns nil if index is outside of self" do
+ @s.scan(/(\w+) (\w+) (\d+) /)
+ @s[5].should == nil
+ @s[-5].should == nil
+ end
+
+ it "calls to_int on the given index" do
+ @s.scan(/(\w+) (\w+) (\d+) /)
+ @s[0.5].should == "Fri Jun 13 "
+ end
+
+ it "raises a TypeError if the given index is nil" do
+ @s.scan(/(\w+) (\w+) (\d+) /)
+ -> { @s[nil]}.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when a Range is as argument" do
+ @s.scan(/(\w+) (\w+) (\d+) /)
+ -> { @s[0..2]}.should raise_error(TypeError)
+ end
+
+ it "raises a IndexError when there's no named capture" do
+ @s.scan(/(\w+) (\w+) (\d+) /)
+ -> { @s["wday"]}.should raise_error(IndexError)
+ -> { @s[:wday]}.should raise_error(IndexError)
+ end
+
+ it "returns named capture" do
+ @s.scan(/(?<wday>\w+) (?<month>\w+) (?<day>\d+) /)
+ @s["wday"].should == "Fri"
+ @s["month"].should == "Jun"
+ @s["day"].should == "13"
+ @s[:wday].should == "Fri"
+ @s[:month].should == "Jun"
+ @s[:day].should == "13"
+ end
+end
diff --git a/spec/ruby/library/stringscanner/empty_spec.rb b/spec/ruby/library/stringscanner/empty_spec.rb
new file mode 100644
index 0000000000..d9449bea6e
--- /dev/null
+++ b/spec/ruby/library/stringscanner/empty_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eos'
+require 'strscan'
+
+describe "StringScanner#empty?" do
+ it_behaves_like :strscan_eos, :empty?
+
+ it "warns in verbose mode that the method is obsolete" do
+ s = StringScanner.new("abc")
+ -> {
+ s.empty?
+ }.should complain(/empty?.*obsolete.*eos?/, verbose: true)
+
+ -> {
+ s.empty?
+ }.should_not complain(verbose: false)
+ end
+end
diff --git a/spec/ruby/library/stringscanner/eos_spec.rb b/spec/ruby/library/stringscanner/eos_spec.rb
new file mode 100644
index 0000000000..b58ee1e473
--- /dev/null
+++ b/spec/ruby/library/stringscanner/eos_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/eos'
+require 'strscan'
+
+describe "StringScanner#eos?" do
+ it_behaves_like :strscan_eos, :eos?
+end
diff --git a/spec/ruby/library/stringscanner/exist_spec.rb b/spec/ruby/library/stringscanner/exist_spec.rb
new file mode 100644
index 0000000000..ff860a0d3e
--- /dev/null
+++ b/spec/ruby/library/stringscanner/exist_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#exist?" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the index of the first occurrence of the given pattern" do
+ @s.exist?(/s/).should == 4
+ @s.scan(/This is/)
+ @s.exist?(/s/).should == 6
+ end
+
+ it "returns 0 if the pattern is empty" do
+ @s.exist?(//).should == 0
+ end
+
+ it "returns nil if the pattern isn't found in the string" do
+ @s.exist?(/S/).should == nil
+ @s.scan(/This is/)
+ @s.exist?(/i/).should == nil
+ end
+
+ it "raises TypeError if given a String" do
+ -> {
+ @s.exist?('T')
+ }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)')
+ end
+end
diff --git a/spec/ruby/library/stringscanner/get_byte_spec.rb b/spec/ruby/library/stringscanner/get_byte_spec.rb
new file mode 100644
index 0000000000..29e2f557de
--- /dev/null
+++ b/spec/ruby/library/stringscanner/get_byte_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/get_byte'
+require 'strscan'
+
+describe "StringScanner#get_byte" do
+ it_behaves_like :strscan_get_byte, :get_byte
+end
diff --git a/spec/ruby/library/stringscanner/getbyte_spec.rb b/spec/ruby/library/stringscanner/getbyte_spec.rb
new file mode 100644
index 0000000000..e0659a5829
--- /dev/null
+++ b/spec/ruby/library/stringscanner/getbyte_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'shared/get_byte'
+require_relative 'shared/extract_range'
+require 'strscan'
+
+describe "StringScanner#getbyte" do
+ it_behaves_like :strscan_get_byte, :getbyte
+
+ it "warns in verbose mode that the method is obsolete" do
+ s = StringScanner.new("abc")
+ -> {
+ s.getbyte
+ }.should complain(/getbyte.*obsolete.*get_byte/, verbose: true)
+
+ -> {
+ s.getbyte
+ }.should_not complain(verbose: false)
+ end
+
+ it_behaves_like :extract_range, :getbyte
+end
diff --git a/spec/ruby/library/stringscanner/getch_spec.rb b/spec/ruby/library/stringscanner/getch_spec.rb
new file mode 100644
index 0000000000..a6be0d4221
--- /dev/null
+++ b/spec/ruby/library/stringscanner/getch_spec.rb
@@ -0,0 +1,35 @@
+# -*- encoding: binary -*-
+require_relative '../../spec_helper'
+require_relative 'shared/extract_range'
+require 'strscan'
+
+describe "StringScanner#getch" do
+ it "scans one character and returns it" do
+ s = StringScanner.new('abc')
+ s.getch.should == "a"
+ s.getch.should == "b"
+ s.getch.should == "c"
+ end
+
+ it "is multi-byte character sensitive" do
+ # Japanese hiragana "A" in EUC-JP
+ src = "\244\242".force_encoding("euc-jp")
+
+ s = StringScanner.new(src)
+ s.getch.should == src
+ end
+
+ it "returns nil at the end of the string" do
+ # empty string case
+ s = StringScanner.new('')
+ s.getch.should == nil
+ s.getch.should == nil
+
+ # non-empty string case
+ s = StringScanner.new('a')
+ s.getch # skip one
+ s.getch.should == nil
+ end
+
+ it_behaves_like :extract_range, :getch
+end
diff --git a/spec/ruby/library/stringscanner/initialize_spec.rb b/spec/ruby/library/stringscanner/initialize_spec.rb
new file mode 100644
index 0000000000..047d9d058b
--- /dev/null
+++ b/spec/ruby/library/stringscanner/initialize_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#initialize" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "is a private method" do
+ StringScanner.should have_private_instance_method(:initialize)
+ end
+
+ it "returns an instance of StringScanner" do
+ @s.should be_kind_of(StringScanner)
+ @s.eos?.should be_false
+ end
+
+ it "converts the argument into a string using #to_str" do
+ m = mock(:str)
+
+ s = "test"
+ m.should_receive(:to_str).and_return(s)
+
+ scan = StringScanner.new(m)
+ scan.string.should == s
+ end
+end
diff --git a/spec/ruby/library/stringscanner/inspect_spec.rb b/spec/ruby/library/stringscanner/inspect_spec.rb
new file mode 100644
index 0000000000..ff6b97eb91
--- /dev/null
+++ b/spec/ruby/library/stringscanner/inspect_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#inspect" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns a String object" do
+ @s.inspect.should be_kind_of(String)
+ end
+
+ it "returns a string that represents the StringScanner object" do
+ @s.inspect.should == "#<StringScanner 0/14 @ \"This ...\">"
+ @s.scan_until(/is/)
+ @s.inspect.should == "#<StringScanner 4/14 \"This\" @ \" is a...\">"
+ @s.terminate
+ @s.inspect.should == "#<StringScanner fin>"
+ end
+end
diff --git a/spec/ruby/library/stringscanner/match_spec.rb b/spec/ruby/library/stringscanner/match_spec.rb
new file mode 100644
index 0000000000..ec59680914
--- /dev/null
+++ b/spec/ruby/library/stringscanner/match_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#match?" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the length of the match and the scan pointer is not advanced" do
+ @s.match?(/\w+/).should == 4
+ @s.match?(/\w+/).should == 4
+ @s.pos.should == 0
+ end
+
+ it "returns nil if there's no match" do
+ @s.match?(/\d+/).should == nil
+ @s.match?(/\s+/).should == nil
+ end
+
+ it "effects pre_match" do
+ @s.scan(/\w+/)
+ @s.scan(/\s/)
+
+ @s.pre_match.should == "This"
+ @s.match?(/\w+/)
+ @s.pre_match.should == "This "
+ end
+end
diff --git a/spec/ruby/library/stringscanner/matched_size_spec.rb b/spec/ruby/library/stringscanner/matched_size_spec.rb
new file mode 100644
index 0000000000..d9c338a07e
--- /dev/null
+++ b/spec/ruby/library/stringscanner/matched_size_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#matched_size" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the size of the most recent match" do
+ @s.check(/This/)
+ @s.matched_size.should == 4
+ @s.matched_size.should == 4
+ @s.scan(//)
+ @s.matched_size.should == 0
+ end
+
+ it "returns nil if there was no recent match" do
+ @s.matched_size.should == nil
+ @s.check(/\d+/)
+ @s.matched_size.should == nil
+ @s.terminate
+ @s.matched_size.should == nil
+ end
+end
diff --git a/spec/ruby/library/stringscanner/matched_spec.rb b/spec/ruby/library/stringscanner/matched_spec.rb
new file mode 100644
index 0000000000..c020bd3eae
--- /dev/null
+++ b/spec/ruby/library/stringscanner/matched_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'shared/extract_range_matched'
+require 'strscan'
+
+describe "StringScanner#matched" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the last matched string" do
+ @s.match?(/\w+/)
+ @s.matched.should == "This"
+ @s.getch
+ @s.matched.should == "T"
+ @s.get_byte
+ @s.matched.should == "h"
+ end
+
+ it "returns nil if there's no match" do
+ @s.match?(/\d+/)
+ @s.matched.should == nil
+ end
+
+ it_behaves_like :extract_range_matched, :matched
+end
+
+describe "StringScanner#matched?" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns true if the last match was successful" do
+ @s.match?(/\w+/)
+ @s.matched?.should be_true
+ end
+
+ it "returns false if there's no match" do
+ @s.match?(/\d+/)
+ @s.matched?.should be_false
+ end
+end
diff --git a/spec/ruby/library/stringscanner/must_C_version_spec.rb b/spec/ruby/library/stringscanner/must_C_version_spec.rb
new file mode 100644
index 0000000000..fcc5b596f6
--- /dev/null
+++ b/spec/ruby/library/stringscanner/must_C_version_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner.must_C_version" do
+ it "returns self" do
+ StringScanner.must_C_version.should == StringScanner
+ end
+end
diff --git a/spec/ruby/library/stringscanner/peek_spec.rb b/spec/ruby/library/stringscanner/peek_spec.rb
new file mode 100644
index 0000000000..cbb5630ff9
--- /dev/null
+++ b/spec/ruby/library/stringscanner/peek_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/peek'
+require 'strscan'
+
+describe "StringScanner#peek" do
+ it_behaves_like :strscan_peek, :peek
+end
diff --git a/spec/ruby/library/stringscanner/peep_spec.rb b/spec/ruby/library/stringscanner/peep_spec.rb
new file mode 100644
index 0000000000..bf6d579325
--- /dev/null
+++ b/spec/ruby/library/stringscanner/peep_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'shared/peek'
+require 'strscan'
+
+describe "StringScanner#peep" do
+ it_behaves_like :strscan_peek, :peep
+
+ it "warns in verbose mode that the method is obsolete" do
+ s = StringScanner.new("abc")
+ -> {
+ s.peep(1)
+ }.should complain(/peep.*obsolete.*peek/, verbose: true)
+
+ -> {
+ s.peep(1)
+ }.should_not complain(verbose: false)
+ end
+end
diff --git a/spec/ruby/library/stringscanner/pointer_spec.rb b/spec/ruby/library/stringscanner/pointer_spec.rb
new file mode 100644
index 0000000000..bc0c0c50b7
--- /dev/null
+++ b/spec/ruby/library/stringscanner/pointer_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/pos'
+require 'strscan'
+
+describe "StringScanner#pointer" do
+ it_behaves_like :strscan_pos, :pointer
+end
+
+describe "StringScanner#pointer=" do
+ it_behaves_like :strscan_pos_set, :pointer=
+end
diff --git a/spec/ruby/library/stringscanner/pos_spec.rb b/spec/ruby/library/stringscanner/pos_spec.rb
new file mode 100644
index 0000000000..275fecf0f3
--- /dev/null
+++ b/spec/ruby/library/stringscanner/pos_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/pos'
+require 'strscan'
+
+describe "StringScanner#pos" do
+ it_behaves_like :strscan_pos, :pos
+end
+
+describe "StringScanner#pos=" do
+ it_behaves_like :strscan_pos_set, :pos=
+end
diff --git a/spec/ruby/library/stringscanner/post_match_spec.rb b/spec/ruby/library/stringscanner/post_match_spec.rb
new file mode 100644
index 0000000000..720dc3530d
--- /dev/null
+++ b/spec/ruby/library/stringscanner/post_match_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative 'shared/extract_range_matched'
+require 'strscan'
+
+describe "StringScanner#post_match" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the post-match (in the regular expression sense) of the last scan" do
+ @s.post_match.should == nil
+ @s.scan(/\w+\s/)
+ @s.post_match.should == "is a test"
+ @s.getch
+ @s.post_match.should == "s a test"
+ @s.get_byte
+ @s.post_match.should == " a test"
+ @s.get_byte
+ @s.post_match.should == "a test"
+ end
+
+ it "returns nil if there's no match" do
+ @s.scan(/\s+/)
+ @s.post_match.should == nil
+ end
+
+ it_behaves_like :extract_range_matched, :post_match
+end
diff --git a/spec/ruby/library/stringscanner/pre_match_spec.rb b/spec/ruby/library/stringscanner/pre_match_spec.rb
new file mode 100644
index 0000000000..1c2c511d5c
--- /dev/null
+++ b/spec/ruby/library/stringscanner/pre_match_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'shared/extract_range_matched'
+require 'strscan'
+
+describe "StringScanner#pre_match" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the pre-match (in the regular expression sense) of the last scan" do
+ @s.pre_match.should == nil
+ @s.scan(/\w+\s/)
+ @s.pre_match.should == ""
+ @s.getch
+ @s.pre_match.should == "This "
+ @s.get_byte
+ @s.pre_match.should == "This i"
+ @s.get_byte
+ @s.pre_match.should == "This is"
+ end
+
+ it "returns nil if there's no match" do
+ @s.scan(/\s+/)
+ @s.pre_match.should == nil
+ end
+
+ it "is more than just the data from the last match" do
+ @s.scan(/\w+/)
+ @s.scan_until(/a te/)
+ @s.pre_match.should == "This is "
+ end
+
+ it "is not changed when the scanner's position changes" do
+ @s.scan_until(/\s+/)
+ @s.pre_match.should == "This"
+ @s.pos -= 1
+ @s.pre_match.should == "This"
+ end
+
+ it_behaves_like :extract_range_matched, :pre_match
+end
diff --git a/spec/ruby/library/stringscanner/reset_spec.rb b/spec/ruby/library/stringscanner/reset_spec.rb
new file mode 100644
index 0000000000..a10ca2d838
--- /dev/null
+++ b/spec/ruby/library/stringscanner/reset_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#reset" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "reset the scan pointer and clear matching data" do
+ @s.scan(/This/)
+ @s.reset
+ @s.pos.should == 0
+ @s.matched.should == nil
+ end
+end
diff --git a/spec/ruby/library/stringscanner/rest_size_spec.rb b/spec/ruby/library/stringscanner/rest_size_spec.rb
new file mode 100644
index 0000000000..e62e3a8f8c
--- /dev/null
+++ b/spec/ruby/library/stringscanner/rest_size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/rest_size'
+require 'strscan'
+
+describe "StringScanner#rest_size" do
+ it_behaves_like :strscan_rest_size, :rest_size
+end
diff --git a/spec/ruby/library/stringscanner/rest_spec.rb b/spec/ruby/library/stringscanner/rest_spec.rb
new file mode 100644
index 0000000000..67072f880d
--- /dev/null
+++ b/spec/ruby/library/stringscanner/rest_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require_relative 'shared/extract_range_matched'
+require 'strscan'
+
+describe "StringScanner#rest" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the rest of the string" do
+ @s.scan(/This\s+/)
+ @s.rest.should == "is a test"
+ end
+
+ it "returns self in the reset position" do
+ @s.reset
+ @s.rest.should == @s.string
+ end
+
+ it "returns an empty string in the terminate position" do
+ @s.terminate
+ @s.rest.should == ""
+ end
+
+ it_behaves_like :extract_range_matched, :rest
+
+end
+
+describe "StringScanner#rest?" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns true if there is more data in the string" do
+ @s.rest?.should be_true
+ @s.scan(/This/)
+ @s.rest?.should be_true
+ end
+
+ it "returns false if there is no more data in the string" do
+ @s.terminate
+ @s.rest?.should be_false
+ end
+
+ it "is the opposite of eos?" do
+ @s.rest?.should_not == @s.eos?
+ end
+end
diff --git a/spec/ruby/library/stringscanner/restsize_spec.rb b/spec/ruby/library/stringscanner/restsize_spec.rb
new file mode 100644
index 0000000000..710520afae
--- /dev/null
+++ b/spec/ruby/library/stringscanner/restsize_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'shared/rest_size'
+require 'strscan'
+
+describe "StringScanner#restsize" do
+ it_behaves_like :strscan_rest_size, :restsize
+
+ it "warns in verbose mode that the method is obsolete" do
+ s = StringScanner.new("abc")
+ -> {
+ s.restsize
+ }.should complain(/restsize.*obsolete.*rest_size/, verbose: true)
+
+ -> {
+ s.restsize
+ }.should_not complain(verbose: false)
+ end
+end
diff --git a/spec/ruby/library/stringscanner/scan_full_spec.rb b/spec/ruby/library/stringscanner/scan_full_spec.rb
new file mode 100644
index 0000000000..ed34d7d3f6
--- /dev/null
+++ b/spec/ruby/library/stringscanner/scan_full_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#scan_full" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the number of bytes advanced" do
+ orig_pos = @s.pos
+ @s.scan_full(/This/, false, false).should == 4
+ @s.pos.should == orig_pos
+ end
+
+ it "returns the number of bytes advanced and advances the scan pointer if the second argument is true" do
+ @s.scan_full(/This/, true, false).should == 4
+ @s.pos.should == 4
+ end
+
+ it "returns the matched string if the third argument is true" do
+ orig_pos = @s.pos
+ @s.scan_full(/This/, false, true).should == "This"
+ @s.pos.should == orig_pos
+ end
+
+ it "returns the matched string if the third argument is true and advances the scan pointer if the second argument is true" do
+ @s.scan_full(/This/, true, true).should == "This"
+ @s.pos.should == 4
+ end
+end
diff --git a/spec/ruby/library/stringscanner/scan_spec.rb b/spec/ruby/library/stringscanner/scan_spec.rb
new file mode 100644
index 0000000000..ea711767b9
--- /dev/null
+++ b/spec/ruby/library/stringscanner/scan_spec.rb
@@ -0,0 +1,87 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#scan" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the matched string" do
+ @s.scan(/\w+/).should == "This"
+ @s.scan(/.../).should == " is"
+ @s.scan(//).should == ""
+ @s.scan(/\s+/).should == " "
+ end
+
+ it "treats ^ as matching from the beginning of the current position" do
+ @s.scan(/\w+/).should == "This"
+ @s.scan(/^\d/).should be_nil
+ @s.scan(/^\s/).should == " "
+ end
+
+ it "treats ^ as matching from the beginning of the current position when it's not the first character in the regexp" do
+ @s.scan(/\w+/).should == "This"
+ @s.scan(/( is not|^ is a)/).should == " is a"
+ end
+
+ it "treats \\A as matching from the beginning of the current position" do
+ @s.scan(/\w+/).should == "This"
+ @s.scan(/\A\d/).should be_nil
+ @s.scan(/\A\s/).should == " "
+ end
+
+ it "treats \\A as matching from the beginning of the current position when it's not the first character in the regexp" do
+ @s.scan(/\w+/).should == "This"
+ @s.scan(/( is not|\A is a)/).should == " is a"
+ end
+
+ it "returns nil if there's no match" do
+ @s.scan(/\d/).should == nil
+ end
+
+ it "returns nil when there is no more to scan" do
+ @s.scan(/[\w\s]+/).should == "This is a test"
+ @s.scan(/\w+/).should be_nil
+ end
+
+ it "returns an empty string when the pattern matches empty" do
+ @s.scan(/.*/).should == "This is a test"
+ @s.scan(/.*/).should == ""
+ @s.scan(/./).should be_nil
+ end
+
+ it "treats String as the pattern itself" do
+ @s.scan("this").should be_nil
+ @s.scan("This").should == "This"
+ end
+
+ it "raises a TypeError if pattern isn't a Regexp nor String" do
+ -> { @s.scan(5) }.should raise_error(TypeError)
+ -> { @s.scan(:test) }.should raise_error(TypeError)
+ -> { @s.scan(mock('x')) }.should raise_error(TypeError)
+ end
+end
+
+describe "StringScanner#scan with fixed_anchor: true" do
+ before :each do
+ @s = StringScanner.new("This\nis\na\ntest", fixed_anchor: true)
+ end
+
+ it "returns the matched string" do
+ @s.scan(/\w+/).should == "This"
+ @s.scan(/.../m).should == "\nis"
+ @s.scan(//).should == ""
+ @s.scan(/\s+/).should == "\n"
+ end
+
+ it "treats ^ as matching from the beginning of line" do
+ @s.scan(/\w+\n/).should == "This\n"
+ @s.scan(/^\w/).should == "i"
+ @s.scan(/^\w/).should be_nil
+ end
+
+ it "treats \\A as matching from the beginning of string" do
+ @s.scan(/\A\w/).should == "T"
+ @s.scan(/\A\w/).should be_nil
+ end
+end
diff --git a/spec/ruby/library/stringscanner/scan_until_spec.rb b/spec/ruby/library/stringscanner/scan_until_spec.rb
new file mode 100644
index 0000000000..6b7782572d
--- /dev/null
+++ b/spec/ruby/library/stringscanner/scan_until_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#scan_until" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the substring up to and including the end of the match" do
+ @s.scan_until(/a/).should == "This is a"
+ @s.pre_match.should == "This is "
+ @s.post_match.should == " test"
+ end
+
+ it "returns nil if there's no match" do
+ @s.scan_until(/\d/).should == nil
+ end
+
+ it "can match anchors properly" do
+ @s.scan(/T/)
+ @s.scan_until(/^h/).should == "h"
+ end
+
+ it "raises TypeError if given a String" do
+ -> {
+ @s.scan_until('T')
+ }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)')
+ end
+end
diff --git a/spec/ruby/library/stringscanner/search_full_spec.rb b/spec/ruby/library/stringscanner/search_full_spec.rb
new file mode 100644
index 0000000000..7d2a714fa5
--- /dev/null
+++ b/spec/ruby/library/stringscanner/search_full_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#search_full" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the number of bytes advanced" do
+ orig_pos = @s.pos
+ @s.search_full(/This/, false, false).should == 4
+ @s.pos.should == orig_pos
+ end
+
+ it "returns the number of bytes advanced and advances the scan pointer if the second argument is true" do
+ @s.search_full(/This/, true, false).should == 4
+ @s.pos.should == 4
+ end
+
+ it "returns the matched string if the third argument is true" do
+ orig_pos = @s.pos
+ @s.search_full(/This/, false, true).should == "This"
+ @s.pos.should == orig_pos
+ end
+
+ it "returns the matched string if the third argument is true and advances the scan pointer if the second argument is true" do
+ @s.search_full(/This/, true, true).should == "This"
+ @s.pos.should == 4
+ end
+
+ it "raises TypeError if given a String" do
+ -> {
+ @s.search_full('T', true, true)
+ }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)')
+ end
+end
diff --git a/spec/ruby/library/stringscanner/shared/bol.rb b/spec/ruby/library/stringscanner/shared/bol.rb
new file mode 100644
index 0000000000..ebcdd7938f
--- /dev/null
+++ b/spec/ruby/library/stringscanner/shared/bol.rb
@@ -0,0 +1,25 @@
+describe :strscan_bol, shared: true do
+ it "returns true if the scan pointer is at the beginning of the line, false otherwise" do
+ s = StringScanner.new("This is a test")
+ s.send(@method).should be_true
+ s.scan(/This/)
+ s.send(@method).should be_false
+ s.terminate
+ s.send(@method).should be_false
+
+ s = StringScanner.new("hello\nworld")
+ s.bol?.should be_true
+ s.scan(/\w+/)
+ s.bol?.should be_false
+ s.scan(/\n/)
+ s.bol?.should be_true
+ s.unscan
+ s.bol?.should be_false
+ end
+
+ it "returns true if the scan pointer is at the end of the line of an empty string." do
+ s = StringScanner.new('')
+ s.terminate
+ s.send(@method).should be_true
+ end
+end
diff --git a/spec/ruby/library/stringscanner/shared/concat.rb b/spec/ruby/library/stringscanner/shared/concat.rb
new file mode 100644
index 0000000000..cb884a5c01
--- /dev/null
+++ b/spec/ruby/library/stringscanner/shared/concat.rb
@@ -0,0 +1,30 @@
+describe :strscan_concat, shared: true do
+ it "concatenates the given argument to self and returns self" do
+ s = StringScanner.new("hello ")
+ s.send(@method, 'world').should == s
+ s.string.should == "hello world"
+ s.eos?.should be_false
+ end
+
+ it "raises a TypeError if the given argument can't be converted to a String" do
+ -> { StringScanner.new('hello').send(@method, :world) }.should raise_error(TypeError)
+ -> { StringScanner.new('hello').send(@method, mock('x')) }.should raise_error(TypeError)
+ end
+end
+
+describe :strscan_concat_fixnum, shared: true do
+ it "raises a TypeError" do
+ a = StringScanner.new("hello world")
+ -> { a.send(@method, 333) }.should raise_error(TypeError)
+ b = StringScanner.new("")
+ -> { b.send(@method, (256 * 3 + 64)) }.should raise_error(TypeError)
+ -> { b.send(@method, -200) }.should raise_error(TypeError)
+ end
+
+ it "doesn't call to_int on the argument" do
+ x = mock('x')
+ x.should_not_receive(:to_int)
+
+ -> { StringScanner.new("").send(@method, x) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/library/stringscanner/shared/eos.rb b/spec/ruby/library/stringscanner/shared/eos.rb
new file mode 100644
index 0000000000..ea04c764a2
--- /dev/null
+++ b/spec/ruby/library/stringscanner/shared/eos.rb
@@ -0,0 +1,17 @@
+describe :strscan_eos, shared: true do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns true if the scan pointer is at the end of the string" do
+ @s.terminate
+ @s.send(@method).should be_true
+
+ s = StringScanner.new('')
+ s.send(@method).should be_true
+ end
+
+ it "returns false if the scan pointer is not at the end of the string" do
+ @s.send(@method).should be_false
+ end
+end
diff --git a/spec/ruby/library/stringscanner/shared/extract_range.rb b/spec/ruby/library/stringscanner/shared/extract_range.rb
new file mode 100644
index 0000000000..e7404fd0cb
--- /dev/null
+++ b/spec/ruby/library/stringscanner/shared/extract_range.rb
@@ -0,0 +1,11 @@
+describe :extract_range, shared: true do
+ it "returns an instance of String when passed a String subclass" do
+ cls = Class.new(String)
+ sub = cls.new("abc")
+
+ s = StringScanner.new(sub)
+ ch = s.send(@method)
+ ch.should_not be_kind_of(cls)
+ ch.should be_an_instance_of(String)
+ end
+end
diff --git a/spec/ruby/library/stringscanner/shared/extract_range_matched.rb b/spec/ruby/library/stringscanner/shared/extract_range_matched.rb
new file mode 100644
index 0000000000..070a132812
--- /dev/null
+++ b/spec/ruby/library/stringscanner/shared/extract_range_matched.rb
@@ -0,0 +1,13 @@
+describe :extract_range_matched, shared: true do
+ it "returns an instance of String when passed a String subclass" do
+ cls = Class.new(String)
+ sub = cls.new("abc")
+
+ s = StringScanner.new(sub)
+ s.scan(/\w{1}/)
+
+ ch = s.send(@method)
+ ch.should_not be_kind_of(cls)
+ ch.should be_an_instance_of(String)
+ end
+end
diff --git a/spec/ruby/library/stringscanner/shared/get_byte.rb b/spec/ruby/library/stringscanner/shared/get_byte.rb
new file mode 100644
index 0000000000..763ab6f4a4
--- /dev/null
+++ b/spec/ruby/library/stringscanner/shared/get_byte.rb
@@ -0,0 +1,29 @@
+# -*- encoding: binary -*-
+describe :strscan_get_byte, shared: true do
+ it "scans one byte and returns it" do
+ s = StringScanner.new('abc5.')
+ s.send(@method).should == 'a'
+ s.send(@method).should == 'b'
+ s.send(@method).should == 'c'
+ s.send(@method).should == '5'
+ s.send(@method).should == '.'
+ end
+
+ it "is not multi-byte character sensitive" do
+ s = StringScanner.new("\244\242")
+ s.send(@method).should == "\244"
+ s.send(@method).should == "\242"
+ end
+
+ it "returns nil at the end of the string" do
+ # empty string case
+ s = StringScanner.new('')
+ s.send(@method).should == nil
+ s.send(@method).should == nil
+
+ # non-empty string case
+ s = StringScanner.new('a')
+ s.send(@method) # skip one
+ s.send(@method).should == nil
+ end
+end
diff --git a/spec/ruby/library/stringscanner/shared/peek.rb b/spec/ruby/library/stringscanner/shared/peek.rb
new file mode 100644
index 0000000000..4c757866c1
--- /dev/null
+++ b/spec/ruby/library/stringscanner/shared/peek.rb
@@ -0,0 +1,39 @@
+describe :strscan_peek, shared: true do
+ before :each do
+ @s = StringScanner.new('This is a test')
+ end
+
+ it "returns at most the specified number of bytes from the current position" do
+ @s.send(@method, 4).should == "This"
+ @s.pos.should == 0
+ @s.pos = 5
+ @s.send(@method, 2).should == "is"
+ @s.send(@method, 1000).should == "is a test"
+
+ s = StringScanner.new("été")
+ s.send(@method, 2).should == "é"
+ end
+
+ it "returns an empty string when the passed argument is zero" do
+ @s.send(@method, 0).should == ""
+ end
+
+ it "raises a ArgumentError when the passed argument is negative" do
+ -> { @s.send(@method, -2) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a RangeError when the passed argument is a Bignum" do
+ -> { @s.send(@method, bignum_value) }.should raise_error(RangeError)
+ end
+
+ it "returns an instance of String when passed a String subclass" do
+ cls = Class.new(String)
+ sub = cls.new("abc")
+
+ s = StringScanner.new(sub)
+
+ ch = s.send(@method, 1)
+ ch.should_not be_kind_of(cls)
+ ch.should be_an_instance_of(String)
+ end
+end
diff --git a/spec/ruby/library/stringscanner/shared/pos.rb b/spec/ruby/library/stringscanner/shared/pos.rb
new file mode 100644
index 0000000000..6d540881f2
--- /dev/null
+++ b/spec/ruby/library/stringscanner/shared/pos.rb
@@ -0,0 +1,52 @@
+describe :strscan_pos, shared: true do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the position of the scan pointer" do
+ @s.send(@method).should == 0
+ @s.scan_until(/This is/)
+ @s.send(@method).should == 7
+ @s.get_byte
+ @s.send(@method).should == 8
+ @s.terminate
+ @s.send(@method).should == 14
+ end
+
+ it "returns 0 in the reset position" do
+ @s.reset
+ @s.send(@method).should == 0
+ end
+
+ it "returns the length of the string in the terminate position" do
+ @s.terminate
+ @s.send(@method).should == @s.string.length
+ end
+end
+
+describe :strscan_pos_set, shared: true do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "modify the scan pointer" do
+ @s.send(@method, 5)
+ @s.rest.should == "is a test"
+ end
+
+ it "positions from the end if the argument is negative" do
+ @s.send(@method, -2)
+ @s.rest.should == "st"
+ @s.pos.should == 12
+ end
+
+ it "raises a RangeError if position too far backward" do
+ -> {
+ @s.send(@method, -20)
+ }.should raise_error(RangeError)
+ end
+
+ it "raises a RangeError when the passed argument is out of range" do
+ -> { @s.send(@method, 20) }.should raise_error(RangeError)
+ end
+end
diff --git a/spec/ruby/library/stringscanner/shared/rest_size.rb b/spec/ruby/library/stringscanner/shared/rest_size.rb
new file mode 100644
index 0000000000..4c4f49e45c
--- /dev/null
+++ b/spec/ruby/library/stringscanner/shared/rest_size.rb
@@ -0,0 +1,18 @@
+describe :strscan_rest_size, shared: true do
+ before :each do
+ @s = StringScanner.new('This is a test')
+ end
+
+ it "returns the length of the rest of the string" do
+ @s.send(@method).should == 14
+ @s.scan(/This/)
+ @s.send(@method).should == 10
+ @s.terminate
+ @s.send(@method).should == 0
+ end
+
+ it "is equivalent to rest.size" do
+ @s.scan(/This/)
+ @s.send(@method).should == @s.rest.size
+ end
+end
diff --git a/spec/ruby/library/stringscanner/shared/terminate.rb b/spec/ruby/library/stringscanner/shared/terminate.rb
new file mode 100644
index 0000000000..bf41d097e2
--- /dev/null
+++ b/spec/ruby/library/stringscanner/shared/terminate.rb
@@ -0,0 +1,8 @@
+describe :strscan_terminate, shared: true do
+ it "set the scan pointer to the end of the string and clear matching data." do
+ s = StringScanner.new('This is a test')
+ s.send(@method)
+ s.bol?.should be_false
+ s.eos?.should be_true
+ end
+end
diff --git a/spec/ruby/library/stringscanner/size_spec.rb b/spec/ruby/library/stringscanner/size_spec.rb
new file mode 100644
index 0000000000..3e475489e3
--- /dev/null
+++ b/spec/ruby/library/stringscanner/size_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#size" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the number of captures groups of the last match" do
+ @s.scan(/(.)(.)(.)/)
+ @s.size.should == 4
+ end
+
+ it "returns nil if there is no last match" do
+ @s.size.should == nil
+ end
+end
diff --git a/spec/ruby/library/stringscanner/skip_spec.rb b/spec/ruby/library/stringscanner/skip_spec.rb
new file mode 100644
index 0000000000..473361782c
--- /dev/null
+++ b/spec/ruby/library/stringscanner/skip_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#skip" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns length of the match" do
+ @s.skip(/\w+/).should == 4
+ @s.skip(/\s+\w+/).should == 3
+ end
+
+ it "returns nil if there's no match" do
+ @s.skip(/\s+/).should == nil
+ @s.skip(/\d+/).should == nil
+ end
+end
diff --git a/spec/ruby/library/stringscanner/skip_until_spec.rb b/spec/ruby/library/stringscanner/skip_until_spec.rb
new file mode 100644
index 0000000000..7b56f13e4f
--- /dev/null
+++ b/spec/ruby/library/stringscanner/skip_until_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#skip_until" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "returns the number of bytes advanced and advances the scan pointer until pattern is matched and consumed" do
+ @s.skip_until(/a/).should == 9
+ @s.pos.should == 9
+ @s.matched.should == "a"
+ end
+
+ it "returns nil if no match was found" do
+ @s.skip_until(/d+/).should == nil
+ end
+
+ it "raises TypeError if given a String" do
+ -> {
+ @s.skip_until('T')
+ }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)')
+ end
+end
diff --git a/spec/ruby/library/stringscanner/string_spec.rb b/spec/ruby/library/stringscanner/string_spec.rb
new file mode 100644
index 0000000000..28e2f0ed37
--- /dev/null
+++ b/spec/ruby/library/stringscanner/string_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#string" do
+ before :each do
+ @string = "This is a test"
+ @s = StringScanner.new(@string)
+ end
+
+ it "returns the string being scanned" do
+ @s.string.should == "This is a test"
+ @s << " case"
+ @s.string.should == "This is a test case"
+ end
+
+ it "returns the identical object passed in" do
+ @s.string.equal?(@string).should be_true
+ end
+end
+
+describe "StringScanner#string=" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "changes the string being scanned to the argument and resets the scanner" do
+ @s.string = "Hello world"
+ @s.string.should == "Hello world"
+ end
+
+ it "converts the argument into a string using #to_str" do
+ m = mock(:str)
+
+ s = "test"
+ m.should_receive(:to_str).and_return(s)
+
+ @s.string = m
+ @s.string.should == s
+ end
+end
diff --git a/spec/ruby/library/stringscanner/terminate_spec.rb b/spec/ruby/library/stringscanner/terminate_spec.rb
new file mode 100644
index 0000000000..249023f1ab
--- /dev/null
+++ b/spec/ruby/library/stringscanner/terminate_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/terminate'
+require 'strscan'
+
+describe "StringScanner#terminate" do
+ it_behaves_like :strscan_terminate, :terminate
+end
diff --git a/spec/ruby/library/stringscanner/unscan_spec.rb b/spec/ruby/library/stringscanner/unscan_spec.rb
new file mode 100644
index 0000000000..df0ea43367
--- /dev/null
+++ b/spec/ruby/library/stringscanner/unscan_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require 'strscan'
+
+describe "StringScanner#unscan" do
+ before :each do
+ @s = StringScanner.new("This is a test")
+ end
+
+ it "set the scan pointer to the previous position" do
+ @s.scan(/This/)
+ @s.unscan
+ @s.matched.should == nil
+ @s.pos.should == 0
+ end
+
+ it "remember only one previous position" do
+ @s.scan(/This/)
+ pos = @s.pos
+ @s.scan(/ is/)
+ @s.unscan
+ @s.pos.should == pos
+ end
+
+ it "raises a ScanError when the previous match had failed" do
+ -> { @s.unscan }.should raise_error(ScanError)
+ -> { @s.scan(/\d/); @s.unscan }.should raise_error(ScanError)
+ end
+end
diff --git a/spec/ruby/library/syslog/alert_spec.rb b/spec/ruby/library/syslog/alert_spec.rb
new file mode 100644
index 0000000000..edff789dc9
--- /dev/null
+++ b/spec/ruby/library/syslog/alert_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require_relative 'shared/log'
+ require 'syslog'
+
+ describe "Syslog.alert" do
+ it_behaves_like :syslog_log, :alert
+ end
+end
diff --git a/spec/ruby/library/syslog/close_spec.rb b/spec/ruby/library/syslog/close_spec.rb
new file mode 100644
index 0000000000..8c3b67c05b
--- /dev/null
+++ b/spec/ruby/library/syslog/close_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require 'syslog'
+
+ describe "Syslog.close" do
+ platform_is_not :windows do
+
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ end
+
+ it "closes the log" do
+ Syslog.opened?.should be_false
+ Syslog.open
+ Syslog.opened?.should be_true
+ Syslog.close
+ Syslog.opened?.should be_false
+ end
+
+ it "raises a RuntimeError if the log's already closed" do
+ -> { Syslog.close }.should raise_error(RuntimeError)
+ end
+
+ it "it does not work inside blocks" do
+ -> {
+ Syslog.open { |s| s.close }
+ }.should raise_error(RuntimeError)
+ Syslog.should_not.opened?
+ end
+
+ it "sets the identity to nil" do
+ Syslog.open("rubyspec")
+ Syslog.ident.should == "rubyspec"
+ Syslog.close
+ Syslog.ident.should be_nil
+ end
+
+ it "sets the options to nil" do
+ Syslog.open("rubyspec", Syslog::LOG_PID)
+ Syslog.options.should == Syslog::LOG_PID
+ Syslog.close
+ Syslog.options.should == nil
+ end
+
+ it "sets the facility to nil" do
+ Syslog.open
+ Syslog.facility.should == 8
+ Syslog.close
+ Syslog.facility.should == nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/constants_spec.rb b/spec/ruby/library/syslog/constants_spec.rb
new file mode 100644
index 0000000000..2b9524c53d
--- /dev/null
+++ b/spec/ruby/library/syslog/constants_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require 'syslog'
+
+ describe "Syslog::Constants" do
+ platform_is_not :windows, :solaris, :aix do
+ before :all do
+ @constants = %w(LOG_AUTHPRIV LOG_USER LOG_LOCAL2 LOG_NOTICE LOG_NDELAY
+ LOG_SYSLOG LOG_ALERT LOG_FTP LOG_LOCAL5 LOG_ERR LOG_AUTH
+ LOG_LOCAL1 LOG_ODELAY LOG_NEWS LOG_DAEMON LOG_LOCAL4
+ LOG_CRIT LOG_INFO LOG_PERROR LOG_LOCAL0 LOG_CONS LOG_LPR
+ LOG_LOCAL7 LOG_WARNING LOG_CRON LOG_LOCAL3 LOG_EMERG
+ LOG_NOWAIT LOG_UUCP LOG_PID LOG_KERN LOG_MAIL LOG_LOCAL6
+ LOG_DEBUG)
+ end
+
+ it "includes the Syslog constants" do
+ @constants.each do |c|
+ Syslog::Constants.should have_constant(c)
+ end
+ end
+ end
+
+ # The masks are defined in <syslog.h>
+
+ describe "Syslog::Constants.LOG_MASK" do
+ it "returns the mask value for a priority" do
+ Syslog::Constants.LOG_MASK(Syslog::LOG_DEBUG).should == 128
+ Syslog::Constants.LOG_MASK(Syslog::LOG_WARNING).should == 16
+ end
+ end
+
+ describe "Syslog::Constants.LOG_UPTO" do
+ it "returns a mask for the priorities up to a given argument" do
+ Syslog::Constants.LOG_UPTO(Syslog::LOG_ALERT).should == 3
+ Syslog::Constants.LOG_UPTO(Syslog::LOG_DEBUG).should == 255
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/crit_spec.rb b/spec/ruby/library/syslog/crit_spec.rb
new file mode 100644
index 0000000000..5d3904f719
--- /dev/null
+++ b/spec/ruby/library/syslog/crit_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require_relative 'shared/log'
+ require 'syslog'
+
+ describe "Syslog.crit" do
+ it_behaves_like :syslog_log, :crit
+ end
+end
diff --git a/spec/ruby/library/syslog/debug_spec.rb b/spec/ruby/library/syslog/debug_spec.rb
new file mode 100644
index 0000000000..d03e8a88c9
--- /dev/null
+++ b/spec/ruby/library/syslog/debug_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require_relative 'shared/log'
+ require 'syslog'
+
+ describe "Syslog.debug" do
+ it_behaves_like :syslog_log, :debug
+ end
+end
diff --git a/spec/ruby/library/syslog/emerg_spec.rb b/spec/ruby/library/syslog/emerg_spec.rb
new file mode 100644
index 0000000000..2ab4d60291
--- /dev/null
+++ b/spec/ruby/library/syslog/emerg_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require_relative 'shared/log'
+ require 'syslog'
+
+ describe "Syslog.emerg" do
+ # Some way needs do be found to prevent this spec
+ # from causing output on all open terminals. If this
+ # is not possible, this spec may need a special guard
+ # that only runs when requested.
+ quarantine! do
+ it_behaves_like :syslog_log, :emerg
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/err_spec.rb b/spec/ruby/library/syslog/err_spec.rb
new file mode 100644
index 0000000000..43e876ed37
--- /dev/null
+++ b/spec/ruby/library/syslog/err_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require_relative 'shared/log'
+ require 'syslog'
+
+ describe "Syslog.err" do
+ it_behaves_like :syslog_log, :err
+ end
+end
diff --git a/spec/ruby/library/syslog/facility_spec.rb b/spec/ruby/library/syslog/facility_spec.rb
new file mode 100644
index 0000000000..550ca70b11
--- /dev/null
+++ b/spec/ruby/library/syslog/facility_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require 'syslog'
+
+ describe "Syslog.facility" do
+ platform_is_not :windows do
+
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ end
+
+ it "returns the logging facility" do
+ Syslog.open("rubyspec", 3, Syslog::LOG_MAIL)
+ Syslog.facility.should == Syslog::LOG_MAIL
+ Syslog.close
+ end
+
+ it "returns nil if the log is closed" do
+ Syslog.opened?.should be_false
+ Syslog.facility.should == nil
+ end
+
+ it "defaults to LOG_USER" do
+ Syslog.open
+ Syslog.facility.should == Syslog::LOG_USER
+ Syslog.close
+ end
+
+ it "resets after each open call" do
+ Syslog.open
+ Syslog.facility.should == Syslog::LOG_USER
+
+ Syslog.open!("rubyspec", 3, Syslog::LOG_MAIL)
+ Syslog.facility.should == Syslog::LOG_MAIL
+ Syslog.close
+
+ Syslog.open
+ Syslog.facility.should == Syslog::LOG_USER
+ Syslog.close
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/ident_spec.rb b/spec/ruby/library/syslog/ident_spec.rb
new file mode 100644
index 0000000000..3b08327140
--- /dev/null
+++ b/spec/ruby/library/syslog/ident_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require 'syslog'
+
+ describe "Syslog.ident" do
+ platform_is_not :windows do
+
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ end
+
+ it "returns the logging identity" do
+ Syslog.open("rubyspec")
+ Syslog.ident.should == "rubyspec"
+ Syslog.close
+ end
+
+ it "returns nil if the log is closed" do
+ Syslog.should_not.opened?
+ Syslog.ident.should == nil
+ end
+
+ it "defaults to $0" do
+ Syslog.open
+ Syslog.ident.should == $0
+ Syslog.close
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/info_spec.rb b/spec/ruby/library/syslog/info_spec.rb
new file mode 100644
index 0000000000..f2d535299c
--- /dev/null
+++ b/spec/ruby/library/syslog/info_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require_relative 'shared/log'
+ require 'syslog'
+
+ describe "Syslog.info" do
+ it_behaves_like :syslog_log, :info
+ end
+end
diff --git a/spec/ruby/library/syslog/inspect_spec.rb b/spec/ruby/library/syslog/inspect_spec.rb
new file mode 100644
index 0000000000..f45231f8e3
--- /dev/null
+++ b/spec/ruby/library/syslog/inspect_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require 'syslog'
+
+ describe "Syslog.inspect" do
+ platform_is_not :windows do
+
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ end
+
+ it "returns a string a closed log" do
+ Syslog.inspect.should =~ /opened=false/
+ end
+
+ it "returns a string for an opened log" do
+ Syslog.open
+ Syslog.inspect.should =~ /opened=true.*/
+ Syslog.close
+ end
+
+ it "includes the ident, options, facility and mask" do
+ Syslog.open("rubyspec", Syslog::LOG_PID, Syslog::LOG_USER)
+ inspect_str = Syslog.inspect.split ", "
+ inspect_str[0].should =~ /opened=true/
+ inspect_str[1].should == "ident=\"rubyspec\""
+ inspect_str[2].should == "options=#{Syslog::LOG_PID}"
+ inspect_str[3].should == "facility=#{Syslog::LOG_USER}"
+ inspect_str[4].should == "mask=255>"
+ Syslog.close
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/instance_spec.rb b/spec/ruby/library/syslog/instance_spec.rb
new file mode 100644
index 0000000000..891296c52d
--- /dev/null
+++ b/spec/ruby/library/syslog/instance_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require 'syslog'
+
+ describe "Syslog.instance" do
+ platform_is_not :windows do
+ it "returns the module" do
+ Syslog.instance.should == Syslog
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/log_spec.rb b/spec/ruby/library/syslog/log_spec.rb
new file mode 100644
index 0000000000..8589fb1f73
--- /dev/null
+++ b/spec/ruby/library/syslog/log_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require 'syslog'
+
+ describe "Syslog.log" do
+ platform_is_not :windows, :darwin, :solaris, :aix, :android do
+
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ end
+
+ it "receives a priority as first argument" do
+ -> {
+ Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s|
+ s.log(Syslog::LOG_ALERT, "Hello")
+ s.log(Syslog::LOG_CRIT, "World")
+ end
+ }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello\nrubyspec(?::| \d+ - -) World\n\z/, $stderr)
+ end
+
+ it "accepts undefined priorities" do
+ -> {
+ Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s|
+ s.log(1337, "Hello")
+ end
+ # use a regex since it'll output unknown facility/priority messages
+ }.should output_to_fd(/rubyspec(?::| \d+ - -) Hello\n\z/, $stderr)
+ end
+
+ it "fails with TypeError on nil log messages" do
+ Syslog.open do |s|
+ -> { s.log(1, nil) }.should raise_error(TypeError)
+ end
+ end
+
+ it "fails if the log is closed" do
+ -> {
+ Syslog.log(Syslog::LOG_ALERT, "test")
+ }.should raise_error(RuntimeError)
+ end
+
+ it "accepts printf parameters" do
+ -> {
+ Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s|
+ s.log(Syslog::LOG_ALERT, "%s x %d", "chunky bacon", 2)
+ end
+ }.should output_to_fd(/rubyspec(?::| \d+ - -) chunky bacon x 2\n\z/, $stderr)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/mask_spec.rb b/spec/ruby/library/syslog/mask_spec.rb
new file mode 100644
index 0000000000..b3f1250b24
--- /dev/null
+++ b/spec/ruby/library/syslog/mask_spec.rb
@@ -0,0 +1,113 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require 'syslog'
+
+ describe "Syslog.mask" do
+ platform_is_not :windows do
+
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ # make sure we return the mask to the default value
+ Syslog.open { |s| s.mask = 255 }
+ end
+
+ it "returns the log priority mask" do
+ Syslog.open("rubyspec") do
+ Syslog.mask.should == 255
+ Syslog.mask = 3
+ Syslog.mask.should == 3
+ Syslog.mask = 255
+ end
+ end
+
+ it "defaults to 255" do
+ Syslog.open do |s|
+ s.mask.should == 255
+ end
+ end
+
+ it "returns nil if the log is closed" do
+ Syslog.should_not.opened?
+ Syslog.mask.should == nil
+ end
+
+ platform_is :darwin do
+ it "resets if the log is reopened" do
+ Syslog.open
+ Syslog.mask.should == 255
+ Syslog.mask = 64
+
+ Syslog.reopen("rubyspec") do
+ Syslog.mask.should == 255
+ end
+
+ Syslog.open do
+ Syslog.mask.should == 255
+ end
+ end
+ end
+
+ platform_is_not :darwin do
+ it "persists if the log is reopened" do
+ Syslog.open
+ Syslog.mask.should == 255
+ Syslog.mask = 64
+
+ Syslog.reopen("rubyspec") do
+ Syslog.mask.should == 64
+ end
+
+ Syslog.open do
+ Syslog.mask.should == 64
+ end
+ end
+ end
+ end
+ end
+
+ describe "Syslog.mask=" do
+ platform_is_not :windows do
+
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ # make sure we return the mask to the default value
+ Syslog.open { |s| s.mask = 255 }
+ end
+
+ it "sets the log priority mask" do
+ Syslog.open do
+ Syslog.mask = 64
+ Syslog.mask.should == 64
+ end
+ end
+
+ it "raises an error if the log is closed" do
+ -> { Syslog.mask = 1337 }.should raise_error(RuntimeError)
+ end
+
+ it "only accepts numbers" do
+ Syslog.open do
+
+ Syslog.mask = 1337
+ Syslog.mask.should == 1337
+
+ Syslog.mask = 3.1416
+ Syslog.mask.should == 3
+
+ -> { Syslog.mask = "oh hai" }.should raise_error(TypeError)
+ -> { Syslog.mask = "43" }.should raise_error(TypeError)
+
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/notice_spec.rb b/spec/ruby/library/syslog/notice_spec.rb
new file mode 100644
index 0000000000..a2134e0140
--- /dev/null
+++ b/spec/ruby/library/syslog/notice_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require_relative 'shared/log'
+ require 'syslog'
+
+ describe "Syslog.notice" do
+ it_behaves_like :syslog_log, :notice
+ end
+end
diff --git a/spec/ruby/library/syslog/open_spec.rb b/spec/ruby/library/syslog/open_spec.rb
new file mode 100644
index 0000000000..543f5d418b
--- /dev/null
+++ b/spec/ruby/library/syslog/open_spec.rb
@@ -0,0 +1,92 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require_relative 'shared/reopen'
+ require 'syslog'
+
+ describe "Syslog.open" do
+ platform_is_not :windows do
+
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ end
+
+ it "returns the module" do
+ Syslog.open.should == Syslog
+ Syslog.close
+ Syslog.open("Test", 5, 9).should == Syslog
+ Syslog.close
+ end
+
+ it "receives an identity as first argument" do
+ Syslog.open("rubyspec")
+ Syslog.ident.should == "rubyspec"
+ Syslog.close
+ end
+
+ it "defaults the identity to $0" do
+ Syslog.open
+ Syslog.ident.should == $0
+ Syslog.close
+ end
+
+ it "receives the logging options as second argument" do
+ Syslog.open("rubyspec", Syslog::LOG_PID)
+ Syslog.options.should == Syslog::LOG_PID
+ Syslog.close
+ end
+
+ it "defaults the logging options to LOG_PID | LOG_CONS" do
+ Syslog.open
+ Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS
+ Syslog.close
+ end
+
+ it "receives a facility as third argument" do
+ Syslog.open("rubyspec", Syslog::LOG_PID, 0)
+ Syslog.facility.should == 0
+ Syslog.close
+ end
+
+ it "defaults the facility to LOG_USER" do
+ Syslog.open
+ Syslog.facility.should == Syslog::LOG_USER
+ Syslog.close
+ end
+
+ it "receives a block and calls it with the module" do
+ Syslog.open("rubyspec", 3, 8) do |s|
+ s.should == Syslog
+ s.ident.should == "rubyspec"
+ s.options.should == 3
+ s.facility.should == Syslog::LOG_USER
+ end
+ end
+
+ it "closes the log if after it receives a block" do
+ Syslog.open{ }
+ Syslog.opened?.should be_false
+ end
+
+ it "raises an error if the log is opened" do
+ Syslog.open
+ -> {
+ Syslog.open
+ }.should raise_error(RuntimeError, /syslog already open/)
+ -> {
+ Syslog.close
+ Syslog.open
+ }.should_not raise_error
+ Syslog.close
+ end
+ end
+ end
+
+ describe "Syslog.open!" do
+ it_behaves_like :syslog_reopen, :open!
+ end
+end
diff --git a/spec/ruby/library/syslog/opened_spec.rb b/spec/ruby/library/syslog/opened_spec.rb
new file mode 100644
index 0000000000..94432e65a4
--- /dev/null
+++ b/spec/ruby/library/syslog/opened_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require 'syslog'
+
+ describe "Syslog.opened?" do
+ platform_is_not :windows do
+
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ end
+
+ it "returns true if the log is opened" do
+ Syslog.open
+ Syslog.opened?.should be_true
+ Syslog.close
+ end
+
+ it "returns false otherwise" do
+ Syslog.opened?.should be_false
+ Syslog.open
+ Syslog.close
+ Syslog.opened?.should be_false
+ end
+
+ it "works inside a block" do
+ Syslog.open do |s|
+ s.opened?.should be_true
+ Syslog.opened?.should be_true
+ end
+ Syslog.opened?.should be_false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/options_spec.rb b/spec/ruby/library/syslog/options_spec.rb
new file mode 100644
index 0000000000..83ba43503e
--- /dev/null
+++ b/spec/ruby/library/syslog/options_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require 'syslog'
+
+ describe "Syslog.options" do
+ platform_is_not :windows do
+
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ end
+
+ it "returns the logging options" do
+ Syslog.open("rubyspec", Syslog::LOG_PID)
+ Syslog.options.should == Syslog::LOG_PID
+ Syslog.close
+ end
+
+ it "returns nil when the log is closed" do
+ Syslog.opened?.should be_false
+ Syslog.options.should == nil
+ end
+
+ it "defaults to LOG_PID | LOG_CONS" do
+ Syslog.open
+ Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS
+ Syslog.close
+ end
+
+ it "resets after each open call" do
+ Syslog.open
+ Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS
+
+ Syslog.open!("rubyspec", Syslog::LOG_PID)
+ Syslog.options.should == Syslog::LOG_PID
+ Syslog.close
+
+ Syslog.open
+ Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS
+ Syslog.close
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/reopen_spec.rb b/spec/ruby/library/syslog/reopen_spec.rb
new file mode 100644
index 0000000000..a78529fa1f
--- /dev/null
+++ b/spec/ruby/library/syslog/reopen_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require_relative 'shared/reopen'
+ require 'syslog'
+
+ describe "Syslog.reopen" do
+ it_behaves_like :syslog_reopen, :reopen
+ end
+end
diff --git a/spec/ruby/library/syslog/shared/log.rb b/spec/ruby/library/syslog/shared/log.rb
new file mode 100644
index 0000000000..12e4ea8366
--- /dev/null
+++ b/spec/ruby/library/syslog/shared/log.rb
@@ -0,0 +1,39 @@
+describe :syslog_log, shared: true do
+ platform_is_not :windows, :darwin, :solaris, :aix, :android do
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ end
+
+ it "logs a message" do
+ -> {
+ Syslog.open("rubyspec", Syslog::LOG_PERROR) do
+ Syslog.send(@method, "Hello")
+ end
+ }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello\n\z/, $stderr)
+ end
+
+ it "accepts sprintf arguments" do
+ -> {
+ Syslog.open("rubyspec", Syslog::LOG_PERROR) do
+ Syslog.send(@method, "Hello %s", "world")
+ Syslog.send(@method, "%d dogs", 2)
+ end
+ }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello world\nrubyspec(?::| \d+ - -) 2 dogs\n\z/, $stderr)
+ end
+
+ it "works as an alias for Syslog.log" do
+ level = Syslog.const_get "LOG_#{@method.to_s.upcase}"
+ -> {
+ Syslog.open("rubyspec", Syslog::LOG_PERROR) do
+ Syslog.send(@method, "Hello")
+ Syslog.log(level, "Hello")
+ end
+ # make sure the same thing is written to $stderr.
+ }.should output_to_fd(/\A(?:rubyspec(?::| \d+ - -) Hello\n){2}\z/, $stderr)
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/shared/reopen.rb b/spec/ruby/library/syslog/shared/reopen.rb
new file mode 100644
index 0000000000..621437a01d
--- /dev/null
+++ b/spec/ruby/library/syslog/shared/reopen.rb
@@ -0,0 +1,40 @@
+describe :syslog_reopen, shared: true do
+ platform_is_not :windows do
+ before :each do
+ Syslog.opened?.should be_false
+ end
+
+ after :each do
+ Syslog.opened?.should be_false
+ end
+
+ it "reopens the log" do
+ Syslog.open
+ -> { Syslog.send(@method)}.should_not raise_error
+ Syslog.opened?.should be_true
+ Syslog.close
+ end
+
+ it "fails with RuntimeError if the log is closed" do
+ -> { Syslog.send(@method)}.should raise_error(RuntimeError)
+ end
+
+ it "receives the same parameters as Syslog.open" do
+ Syslog.open
+ Syslog.send(@method, "rubyspec", 3, 8) do |s|
+ s.should == Syslog
+ s.ident.should == "rubyspec"
+ s.options.should == 3
+ s.facility.should == Syslog::LOG_USER
+ s.opened?.should be_true
+ end
+ Syslog.opened?.should be_false
+ end
+
+ it "returns the module" do
+ Syslog.open
+ Syslog.send(@method).should == Syslog
+ Syslog.close
+ end
+ end
+end
diff --git a/spec/ruby/library/syslog/warning_spec.rb b/spec/ruby/library/syslog/warning_spec.rb
new file mode 100644
index 0000000000..eeca603136
--- /dev/null
+++ b/spec/ruby/library/syslog/warning_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ require_relative 'shared/log'
+ require 'syslog'
+
+ describe "Syslog.warning" do
+ it_behaves_like :syslog_log, :warning
+ end
+end
diff --git a/spec/ruby/library/tempfile/_close_spec.rb b/spec/ruby/library/tempfile/_close_spec.rb
new file mode 100644
index 0000000000..c08f425b6f
--- /dev/null
+++ b/spec/ruby/library/tempfile/_close_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require 'tempfile'
+
+describe "Tempfile#_close" do
+ before :each do
+ @tempfile = Tempfile.new("specs")
+ end
+
+ after :each do
+ @tempfile.close!
+ end
+
+ it "is protected" do
+ Tempfile.should have_protected_instance_method(:_close)
+ end
+
+ it "closes self" do
+ @tempfile.send(:_close)
+ @tempfile.closed?.should be_true
+ end
+end
diff --git a/spec/ruby/library/tempfile/callback_spec.rb b/spec/ruby/library/tempfile/callback_spec.rb
new file mode 100644
index 0000000000..c0b1518326
--- /dev/null
+++ b/spec/ruby/library/tempfile/callback_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'tempfile'
+
+describe "Tempfile.callback" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/tempfile/close_spec.rb b/spec/ruby/library/tempfile/close_spec.rb
new file mode 100644
index 0000000000..db0eae3fa5
--- /dev/null
+++ b/spec/ruby/library/tempfile/close_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require 'tempfile'
+
+describe "Tempfile#close when passed no argument or [false]" do
+ before :each do
+ @tempfile = Tempfile.new("specs", tmp(""))
+ end
+
+ after :each do
+ @tempfile.close!
+ end
+
+ it "closes self" do
+ @tempfile.close
+ @tempfile.closed?.should be_true
+ end
+
+ it "does not unlink self" do
+ path = @tempfile.path
+ @tempfile.close
+ File.should.exist?(path)
+ end
+end
+
+describe "Tempfile#close when passed [true]" do
+ before :each do
+ @tempfile = Tempfile.new("specs", tmp(""))
+ end
+
+ it "closes self" do
+ @tempfile.close(true)
+ @tempfile.closed?.should be_true
+ end
+
+ it "unlinks self" do
+ path = @tempfile.path
+ @tempfile.close(true)
+ File.should_not.exist?(path)
+ end
+end
+
+describe "Tempfile#close!" do
+ before :each do
+ @tempfile = Tempfile.new("specs", tmp(""))
+ end
+
+ it "closes self" do
+ @tempfile.close!
+ @tempfile.closed?.should be_true
+ end
+
+ it "unlinks self" do
+ path = @tempfile.path
+ @tempfile.close!
+ File.should_not.exist?(path)
+ end
+end
diff --git a/spec/ruby/library/tempfile/delete_spec.rb b/spec/ruby/library/tempfile/delete_spec.rb
new file mode 100644
index 0000000000..0332b44dde
--- /dev/null
+++ b/spec/ruby/library/tempfile/delete_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/unlink'
+require 'tempfile'
+
+describe "Tempfile#delete" do
+ it_behaves_like :tempfile_unlink, :delete
+end
diff --git a/spec/ruby/library/tempfile/initialize_spec.rb b/spec/ruby/library/tempfile/initialize_spec.rb
new file mode 100644
index 0000000000..f2e786d7d8
--- /dev/null
+++ b/spec/ruby/library/tempfile/initialize_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require 'tempfile'
+
+describe "Tempfile#initialize" do
+ before :each do
+ @tempfile = Tempfile.allocate
+ end
+
+ after :each do
+ @tempfile.close!
+ end
+
+ it "opens a new tempfile with the passed name in the passed directory" do
+ @tempfile.send(:initialize, "basename", tmp(""))
+ File.should.exist?(@tempfile.path)
+
+ tmpdir = tmp("")
+ path = @tempfile.path
+
+ platform_is :windows do
+ # on Windows, both types of slashes are OK,
+ # but the tmp helper always uses '/'
+ path.gsub!('\\', '/')
+ end
+
+ path[0, tmpdir.length].should == tmpdir
+ path.should include("basename")
+ end
+
+ platform_is_not :windows do
+ it "sets the permissions on the tempfile to 0600" do
+ @tempfile.send(:initialize, "basename", tmp(""))
+ File.stat(@tempfile.path).mode.should == 0100600
+ end
+ end
+
+ it "accepts encoding options" do
+ @tempfile.send(:initialize, ['shiftjis', 'yml'], encoding: 'SHIFT_JIS')
+ @tempfile.external_encoding.should == Encoding::Shift_JIS
+ end
+
+ it "does not try to modify the arguments" do
+ @tempfile.send(:initialize, ['frozen'.freeze, 'txt'.freeze], encoding: Encoding::IBM437)
+ @tempfile.external_encoding.should == Encoding::IBM437
+ end
+end
diff --git a/spec/ruby/library/tempfile/length_spec.rb b/spec/ruby/library/tempfile/length_spec.rb
new file mode 100644
index 0000000000..bc622b9a70
--- /dev/null
+++ b/spec/ruby/library/tempfile/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+require 'tempfile'
+
+describe "Tempfile#length" do
+ it_behaves_like :tempfile_length, :length
+end
diff --git a/spec/ruby/library/tempfile/open_spec.rb b/spec/ruby/library/tempfile/open_spec.rb
new file mode 100644
index 0000000000..ef2c95376f
--- /dev/null
+++ b/spec/ruby/library/tempfile/open_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+require 'tempfile'
+
+describe "Tempfile#open" do
+ before :each do
+ @tempfile = Tempfile.new("specs")
+ @tempfile.puts("Test!")
+ end
+
+ after :each do
+ @tempfile.close!
+ end
+
+ it "reopens self" do
+ @tempfile.close
+ @tempfile.open
+ @tempfile.closed?.should be_false
+ end
+
+ it "reopens self in read and write mode and does not truncate" do
+ @tempfile.open
+ @tempfile.puts("Another Test!")
+
+ @tempfile.open
+ @tempfile.readline.should == "Another Test!\n"
+ end
+end
+
+describe "Tempfile.open" do
+ after :each do
+ @tempfile.close! if @tempfile
+ end
+
+ it "returns a new, open Tempfile instance" do
+ @tempfile = Tempfile.open("specs")
+ # Delegation messes up .should be_an_instance_of(Tempfile)
+ @tempfile.instance_of?(Tempfile).should be_true
+ end
+
+ it "is passed an array [base, suffix] as first argument" do
+ Tempfile.open(["specs", ".tt"]) { |tempfile| @tempfile = tempfile }
+ @tempfile.path.should =~ /specs.*\.tt$/
+ end
+
+ it "passes the third argument (options) to open" do
+ Tempfile.open("specs", Dir.tmpdir, encoding: "IBM037:IBM037", binmode: true) do |tempfile|
+ @tempfile = tempfile
+ tempfile.external_encoding.should == Encoding.find("IBM037")
+ tempfile.binmode?.should be_true
+ end
+ end
+
+ it "uses a blank string for basename when passed no arguments" do
+ Tempfile.open() do |tempfile|
+ @tempfile = tempfile
+ tempfile.closed?.should be_false
+ end
+ @tempfile.should_not == nil
+ end
+end
+
+describe "Tempfile.open when passed a block" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ after :each do
+ # Tempfile.open with block does not unlink
+ @tempfile.close! if @tempfile
+ end
+
+ it "yields a new, open Tempfile instance to the block" do
+ Tempfile.open("specs") do |tempfile|
+ @tempfile = tempfile
+ ScratchPad.record :yielded
+
+ # Delegation messes up .should be_an_instance_of(Tempfile)
+ tempfile.instance_of?(Tempfile).should be_true
+ tempfile.closed?.should be_false
+ end
+
+ ScratchPad.recorded.should == :yielded
+ end
+
+ it "returns the value of the block" do
+ value = Tempfile.open("specs") do |tempfile|
+ @tempfile = tempfile
+ "return"
+ end
+ value.should == "return"
+ end
+
+ it "closes the yielded Tempfile after the block" do
+ Tempfile.open("specs") { |tempfile| @tempfile = tempfile }
+ @tempfile.closed?.should be_true
+ end
+end
diff --git a/spec/ruby/library/tempfile/path_spec.rb b/spec/ruby/library/tempfile/path_spec.rb
new file mode 100644
index 0000000000..07f75b3e10
--- /dev/null
+++ b/spec/ruby/library/tempfile/path_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require 'tempfile'
+
+describe "Tempfile#path" do
+ before :each do
+ @tempfile = Tempfile.new("specs", tmp(""))
+ end
+
+ after :each do
+ @tempfile.close!
+ end
+
+ it "returns the path to the tempfile" do
+ tmpdir = tmp("")
+ path = @tempfile.path
+
+ platform_is :windows do
+ # on Windows, both types of slashes are OK,
+ # but the tmp helper always uses '/'
+ path.gsub!('\\', '/')
+ end
+
+ path[0, tmpdir.length].should == tmpdir
+ path.should include("specs")
+ end
+end
diff --git a/spec/ruby/library/tempfile/shared/length.rb b/spec/ruby/library/tempfile/shared/length.rb
new file mode 100644
index 0000000000..4d18d1f385
--- /dev/null
+++ b/spec/ruby/library/tempfile/shared/length.rb
@@ -0,0 +1,21 @@
+describe :tempfile_length, shared: true do
+ before :each do
+ @tempfile = Tempfile.new("specs")
+ end
+
+ after :each do
+ @tempfile.close!
+ end
+
+ it "returns the size of self" do
+ @tempfile.send(@method).should eql(0)
+ @tempfile.print("Test!")
+ @tempfile.send(@method).should eql(5)
+ end
+
+ it "returns the size of self even if self is closed" do
+ @tempfile.print("Test!")
+ @tempfile.close
+ @tempfile.send(@method).should eql(5)
+ end
+end
diff --git a/spec/ruby/library/tempfile/shared/unlink.rb b/spec/ruby/library/tempfile/shared/unlink.rb
new file mode 100644
index 0000000000..e821228d70
--- /dev/null
+++ b/spec/ruby/library/tempfile/shared/unlink.rb
@@ -0,0 +1,12 @@
+describe :tempfile_unlink, shared: true do
+ before :each do
+ @tempfile = Tempfile.new("specs")
+ end
+
+ it "unlinks self" do
+ @tempfile.close
+ path = @tempfile.path
+ @tempfile.send(@method)
+ File.should_not.exist?(path)
+ end
+end
diff --git a/spec/ruby/library/tempfile/size_spec.rb b/spec/ruby/library/tempfile/size_spec.rb
new file mode 100644
index 0000000000..f4824601c7
--- /dev/null
+++ b/spec/ruby/library/tempfile/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+require 'tempfile'
+
+describe "Tempfile#size" do
+ it_behaves_like :tempfile_length, :size
+end
diff --git a/spec/ruby/library/tempfile/unlink_spec.rb b/spec/ruby/library/tempfile/unlink_spec.rb
new file mode 100644
index 0000000000..eac7df8472
--- /dev/null
+++ b/spec/ruby/library/tempfile/unlink_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/unlink'
+require 'tempfile'
+
+describe "Tempfile#unlink" do
+ it_behaves_like :tempfile_unlink, :unlink
+end
diff --git a/spec/ruby/library/thread/queue_spec.rb b/spec/ruby/library/thread/queue_spec.rb
new file mode 100644
index 0000000000..c7e2bb1b50
--- /dev/null
+++ b/spec/ruby/library/thread/queue_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "Thread::Queue" do
+ it "is the same class as ::Queue" do
+ Thread.should have_constant(:Queue)
+ Thread::Queue.should equal ::Queue
+ end
+end
diff --git a/spec/ruby/library/thread/sizedqueue_spec.rb b/spec/ruby/library/thread/sizedqueue_spec.rb
new file mode 100644
index 0000000000..6151ff437c
--- /dev/null
+++ b/spec/ruby/library/thread/sizedqueue_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "Thread::SizedQueue" do
+ it "is the same class as ::SizedQueue" do
+ Thread.should have_constant(:SizedQueue)
+ Thread::SizedQueue.should equal ::SizedQueue
+ end
+end
diff --git a/spec/ruby/library/time/httpdate_spec.rb b/spec/ruby/library/time/httpdate_spec.rb
new file mode 100644
index 0000000000..90953a9307
--- /dev/null
+++ b/spec/ruby/library/time/httpdate_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require 'time'
+
+describe "Time.httpdate" do
+ it "parses RFC-2616 strings" do
+ t = Time.utc(1994, 11, 6, 8, 49, 37)
+ t.should == Time.httpdate("Sun, 06 Nov 1994 08:49:37 GMT")
+
+ # relies on Time.parse (not yet implemented)
+ # t.should == Time.httpdate("Sunday, 06-Nov-94 08:49:37 GMT")
+
+ t.should == Time.httpdate("Sun Nov 6 08:49:37 1994")
+ Time.utc(1995, 11, 15, 6, 25, 24).should == Time.httpdate("Wed, 15 Nov 1995 06:25:24 GMT")
+ Time.utc(1995, 11, 15, 4, 58, 8).should == Time.httpdate("Wed, 15 Nov 1995 04:58:08 GMT")
+ Time.utc(1994, 11, 15, 8, 12, 31).should == Time.httpdate("Tue, 15 Nov 1994 08:12:31 GMT")
+ Time.utc(1994, 12, 1, 16, 0, 0).should == Time.httpdate("Thu, 01 Dec 1994 16:00:00 GMT")
+ Time.utc(1994, 10, 29, 19, 43, 31).should == Time.httpdate("Sat, 29 Oct 1994 19:43:31 GMT")
+ Time.utc(1994, 11, 15, 12, 45, 26).should == Time.httpdate("Tue, 15 Nov 1994 12:45:26 GMT")
+ Time.utc(1999, 12, 31, 23, 59, 59).should == Time.httpdate("Fri, 31 Dec 1999 23:59:59 GMT")
+ end
+end
diff --git a/spec/ruby/library/time/iso8601_spec.rb b/spec/ruby/library/time/iso8601_spec.rb
new file mode 100644
index 0000000000..4a9eb45613
--- /dev/null
+++ b/spec/ruby/library/time/iso8601_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/xmlschema'
+require 'time'
+
+describe "Time.xmlschema" do
+ it_behaves_like :time_xmlschema, :iso8601
+end
diff --git a/spec/ruby/library/time/rfc2822_spec.rb b/spec/ruby/library/time/rfc2822_spec.rb
new file mode 100644
index 0000000000..7fc5e9a64b
--- /dev/null
+++ b/spec/ruby/library/time/rfc2822_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/rfc2822'
+require 'time'
+
+describe "Time.rfc2822" do
+ it_behaves_like :time_rfc2822, :rfc2822
+end
diff --git a/spec/ruby/library/time/rfc822_spec.rb b/spec/ruby/library/time/rfc822_spec.rb
new file mode 100644
index 0000000000..da77e6ee77
--- /dev/null
+++ b/spec/ruby/library/time/rfc822_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/rfc2822'
+require 'time'
+
+describe "Time.rfc822" do
+ it_behaves_like :time_rfc2822, :rfc822
+end
diff --git a/spec/ruby/library/time/shared/rfc2822.rb b/spec/ruby/library/time/shared/rfc2822.rb
new file mode 100644
index 0000000000..d99f1f76de
--- /dev/null
+++ b/spec/ruby/library/time/shared/rfc2822.rb
@@ -0,0 +1,65 @@
+describe :time_rfc2822, shared: true do
+ it "parses RFC-822 strings" do
+ t1 = (Time.utc(1976, 8, 26, 14, 30) + 4 * 3600)
+ t2 = Time.rfc2822("26 Aug 76 14:30 EDT")
+ t1.should == t2
+
+ t3 = Time.utc(1976, 8, 27, 9, 32) + 7 * 3600
+ t4 = Time.rfc2822("27 Aug 76 09:32 PDT")
+ t3.should == t4
+ end
+
+ it "parses RFC-2822 strings" do
+ t1 = Time.utc(1997, 11, 21, 9, 55, 6) + 6 * 3600
+ t2 = Time.rfc2822("Fri, 21 Nov 1997 09:55:06 -0600")
+ t1.should == t2
+
+ t3 = Time.utc(2003, 7, 1, 10, 52, 37) - 2 * 3600
+ t4 = Time.rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")
+ t3.should == t4
+
+ t5 = Time.utc(1997, 11, 21, 10, 1, 10) + 6 * 3600
+ t6 = Time.rfc2822("Fri, 21 Nov 1997 10:01:10 -0600")
+ t5.should == t6
+
+ t7 = Time.utc(1997, 11, 21, 11, 0, 0) + 6 * 3600
+ t8 = Time.rfc2822("Fri, 21 Nov 1997 11:00:00 -0600")
+ t7.should == t8
+
+ t9 = Time.utc(1997, 11, 24, 14, 22, 1) + 8 * 3600
+ t10 = Time.rfc2822("Mon, 24 Nov 1997 14:22:01 -0800")
+ t9.should == t10
+
+ begin
+ Time.at(-1)
+ rescue ArgumentError
+ # ignore
+ else
+ t11 = Time.utc(1969, 2, 13, 23, 32, 54) + 3 * 3600 + 30 * 60
+ t12 = Time.rfc2822("Thu, 13 Feb 1969 23:32:54 -0330")
+ t11.should == t12
+
+ t13 = Time.utc(1969, 2, 13, 23, 32, 0) + 3 * 3600 + 30 * 60
+ t14 = Time.rfc2822(" Thu,
+ 13
+ Feb
+ 1969
+ 23:32
+ -0330 (Newfoundland Time)")
+ t13.should == t14
+ end
+
+ t15 = Time.utc(1997, 11, 21, 9, 55, 6)
+ t16 = Time.rfc2822("21 Nov 97 09:55:06 GMT")
+ t15.should == t16
+
+ t17 = Time.utc(1997, 11, 21, 9, 55, 6) + 6 * 3600
+ t18 = Time.rfc2822("Fri, 21 Nov 1997 09 : 55 : 06 -0600")
+ t17.should == t18
+
+ -> {
+ # inner comment is not supported.
+ Time.rfc2822("Fri, 21 Nov 1997 09(comment): 55 : 06 -0600")
+ }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/time/shared/xmlschema.rb b/spec/ruby/library/time/shared/xmlschema.rb
new file mode 100644
index 0000000000..44d33cda7e
--- /dev/null
+++ b/spec/ruby/library/time/shared/xmlschema.rb
@@ -0,0 +1,53 @@
+describe :time_xmlschema, shared: true do
+ it "parses ISO-8601 strings" do
+ t = Time.utc(1985, 4, 12, 23, 20, 50, 520000)
+ s = "1985-04-12T23:20:50.52Z"
+ t.should == Time.xmlschema(s)
+ #s.should == t.xmlschema(2)
+
+ t = Time.utc(1996, 12, 20, 0, 39, 57)
+ s = "1996-12-19T16:39:57-08:00"
+ t.should == Time.xmlschema(s)
+ # There is no way to generate time string with arbitrary timezone.
+ s = "1996-12-20T00:39:57Z"
+ t.should == Time.xmlschema(s)
+ #assert_equal(s, t.xmlschema)
+
+ t = Time.utc(1990, 12, 31, 23, 59, 60)
+ s = "1990-12-31T23:59:60Z"
+ t.should == Time.xmlschema(s)
+ # leap second is representable only if timezone file has it.
+ s = "1990-12-31T15:59:60-08:00"
+ t.should == Time.xmlschema(s)
+
+ begin
+ Time.at(-1)
+ rescue ArgumentError
+ # ignore
+ else
+ t = Time.utc(1937, 1, 1, 11, 40, 27, 870000)
+ s = "1937-01-01T12:00:27.87+00:20"
+ t.should == Time.xmlschema(s)
+ end
+
+ # more
+
+ # (Time.utc(1999, 5, 31, 13, 20, 0) + 5 * 3600).should == Time.xmlschema("1999-05-31T13:20:00-05:00")
+ # (Time.local(2000, 1, 20, 12, 0, 0)).should == Time.xmlschema("2000-01-20T12:00:00")
+ # (Time.utc(2000, 1, 20, 12, 0, 0)).should == Time.xmlschema("2000-01-20T12:00:00Z")
+ # (Time.utc(2000, 1, 20, 12, 0, 0) - 12 * 3600).should == Time.xmlschema("2000-01-20T12:00:00+12:00")
+ # (Time.utc(2000, 1, 20, 12, 0, 0) + 13 * 3600).should == Time.xmlschema("2000-01-20T12:00:00-13:00")
+ # (Time.utc(2000, 3, 4, 23, 0, 0) - 3 * 3600).should == Time.xmlschema("2000-03-04T23:00:00+03:00")
+ # (Time.utc(2000, 3, 4, 20, 0, 0)).should == Time.xmlschema("2000-03-04T20:00:00Z")
+ # (Time.local(2000, 1, 15, 0, 0, 0)).should == Time.xmlschema("2000-01-15T00:00:00")
+ # (Time.local(2000, 2, 15, 0, 0, 0)).should == Time.xmlschema("2000-02-15T00:00:00")
+ # (Time.local(2000, 1, 15, 12, 0, 0)).should == Time.xmlschema("2000-01-15T12:00:00")
+ # (Time.utc(2000, 1, 16, 12, 0, 0)).should == Time.xmlschema("2000-01-16T12:00:00Z")
+ # (Time.local(2000, 1, 1, 12, 0, 0)).should == Time.xmlschema("2000-01-01T12:00:00")
+ # (Time.utc(1999, 12, 31, 23, 0, 0)).should == Time.xmlschema("1999-12-31T23:00:00Z")
+ # (Time.local(2000, 1, 16, 12, 0, 0)).should == Time.xmlschema("2000-01-16T12:00:00")
+ # (Time.local(2000, 1, 16, 0, 0, 0)).should == Time.xmlschema("2000-01-16T00:00:00")
+ # (Time.utc(2000, 1, 12, 12, 13, 14)).should == Time.xmlschema("2000-01-12T12:13:14Z")
+ # (Time.utc(2001, 4, 17, 19, 23, 17, 300000)).should == Time.xmlschema("2001-04-17T19:23:17.3Z")
+ end
+end
diff --git a/spec/ruby/library/time/to_date_spec.rb b/spec/ruby/library/time/to_date_spec.rb
new file mode 100644
index 0000000000..baeafe0847
--- /dev/null
+++ b/spec/ruby/library/time/to_date_spec.rb
@@ -0,0 +1,42 @@
+
+require_relative '../../spec_helper'
+require 'time'
+
+describe "Time#to_date" do
+ it "yields accurate julian date for ambiguous pre-Gregorian reform value" do
+ Time.utc(1582, 10, 4).to_date.jd.should == Date::ITALY - 11 # 2299150j
+ end
+
+ it "yields accurate julian date for Julian-Gregorian gap value" do
+ Time.utc(1582, 10, 14).to_date.jd.should == Date::ITALY - 1 # 2299160j
+ end
+
+ it "yields accurate julian date for post-Gregorian reform value" do
+ Time.utc(1582, 10, 15).to_date.jd.should == Date::ITALY # 2299161j
+ end
+
+ it "yields same julian day regardless of UTC time value" do
+ Time.utc(1582, 10, 15, 00, 00, 00).to_date.jd.should == Date::ITALY
+ Time.utc(1582, 10, 15, 23, 59, 59).to_date.jd.should == Date::ITALY
+ end
+
+ it "yields same julian day regardless of local time or zone" do
+
+ with_timezone("Pacific/Pago_Pago", -11) do
+ Time.local(1582, 10, 15, 00, 00, 00).to_date.jd.should == Date::ITALY
+ Time.local(1582, 10, 15, 23, 59, 59).to_date.jd.should == Date::ITALY
+ end
+
+ with_timezone("Asia/Kamchatka", +12) do
+ Time.local(1582, 10, 15, 00, 00, 00).to_date.jd.should == Date::ITALY
+ Time.local(1582, 10, 15, 23, 59, 59).to_date.jd.should == Date::ITALY
+ end
+
+ end
+
+ it "yields date with default Calendar reform day" do
+ Time.utc(1582, 10, 4).to_date.start.should == Date::ITALY
+ Time.utc(1582, 10, 14).to_date.start.should == Date::ITALY
+ Time.utc(1582, 10, 15).to_date.start.should == Date::ITALY
+ end
+end
diff --git a/spec/ruby/library/time/to_datetime_spec.rb b/spec/ruby/library/time/to_datetime_spec.rb
new file mode 100644
index 0000000000..6025950b59
--- /dev/null
+++ b/spec/ruby/library/time/to_datetime_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require 'time'
+
+describe "Time#to_datetime" do
+ it "returns a DateTime representing the same instant" do
+ time = Time.utc(2012, 12, 31, 23, 58, 59)
+ datetime = time.to_datetime
+ datetime.year.should == 2012
+ datetime.month.should == 12
+ datetime.day.should == 31
+ datetime.hour.should == 23
+ datetime.min.should == 58
+ datetime.sec.should == 59
+ end
+
+ date_version = defined?(Date::VERSION) ? Date::VERSION : '0.0.0'
+ version_is(date_version, '3.2.3') do
+ it "returns a DateTime representing the same instant before Gregorian" do
+ time = Time.utc(1582, 10, 14, 23, 58, 59)
+ datetime = time.to_datetime
+ datetime.year.should == 1582
+ datetime.month.should == 10
+ datetime.day.should == 4
+ datetime.hour.should == 23
+ datetime.min.should == 58
+ datetime.sec.should == 59
+ end
+ end
+
+ it "roundtrips" do
+ time = Time.utc(3, 12, 31, 23, 58, 59)
+ datetime = time.to_datetime
+ datetime.to_time.utc.should == time
+ end
+
+ it "yields a DateTime with the default Calendar reform day" do
+ Time.utc(1582, 10, 4, 1, 2, 3).to_datetime.start.should == Date::ITALY
+ Time.utc(1582, 10, 14, 1, 2, 3).to_datetime.start.should == Date::ITALY
+ Time.utc(1582, 10, 15, 1, 2, 3).to_datetime.start.should == Date::ITALY
+ end
+end
diff --git a/spec/ruby/library/time/to_time_spec.rb b/spec/ruby/library/time/to_time_spec.rb
new file mode 100644
index 0000000000..7e6c75a003
--- /dev/null
+++ b/spec/ruby/library/time/to_time_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require 'time'
+
+describe "Time#to_time" do
+ it "returns itself in the same timezone" do
+ time = Time.new(2012, 2, 21, 10, 11, 12)
+
+ with_timezone("America/Regina") do
+ time.to_time.should equal time
+ end
+
+ time2 = Time.utc(2012, 2, 21, 10, 11, 12)
+ time2.to_time.should equal time2
+ end
+end
diff --git a/spec/ruby/library/time/xmlschema_spec.rb b/spec/ruby/library/time/xmlschema_spec.rb
new file mode 100644
index 0000000000..4279311199
--- /dev/null
+++ b/spec/ruby/library/time/xmlschema_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'shared/xmlschema'
+require 'time'
+
+describe "Time.xmlschema" do
+ it_behaves_like :time_xmlschema, :xmlschema
+end
diff --git a/spec/ruby/library/timeout/error_spec.rb b/spec/ruby/library/timeout/error_spec.rb
new file mode 100644
index 0000000000..6c236e5128
--- /dev/null
+++ b/spec/ruby/library/timeout/error_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'timeout'
+
+describe "Timeout::Error" do
+ it "is a subclass of RuntimeError" do
+ RuntimeError.should be_ancestor_of(Timeout::Error)
+ end
+end
diff --git a/spec/ruby/library/timeout/timeout_spec.rb b/spec/ruby/library/timeout/timeout_spec.rb
new file mode 100644
index 0000000000..584b38d8ec
--- /dev/null
+++ b/spec/ruby/library/timeout/timeout_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require 'timeout'
+
+describe "Timeout.timeout" do
+ it "raises Timeout::Error when it times out with no specified error type" do
+ -> {
+ Timeout.timeout(1) do
+ sleep
+ end
+ }.should raise_error(Timeout::Error)
+ end
+
+ it "raises specified error type when it times out" do
+ -> do
+ Timeout.timeout(1, StandardError) do
+ sleep
+ end
+ end.should raise_error(StandardError)
+ end
+
+ it "raises specified error type with specified message when it times out" do
+ -> do
+ Timeout.timeout(1, StandardError, "foobar") do
+ sleep
+ end
+ end.should raise_error(StandardError, "foobar")
+ end
+
+ it "raises specified error type with a default message when it times out if message is nil" do
+ -> do
+ Timeout.timeout(1, StandardError, nil) do
+ sleep
+ end
+ end.should raise_error(StandardError, "execution expired")
+ end
+
+ it "returns back the last value in the block" do
+ Timeout.timeout(1) do
+ 42
+ end.should == 42
+ end
+end
diff --git a/spec/ruby/library/tmpdir/dir/mktmpdir_spec.rb b/spec/ruby/library/tmpdir/dir/mktmpdir_spec.rb
new file mode 100644
index 0000000000..8165c2d8a8
--- /dev/null
+++ b/spec/ruby/library/tmpdir/dir/mktmpdir_spec.rb
@@ -0,0 +1,117 @@
+require_relative '../../../spec_helper'
+require "tmpdir"
+
+describe "Dir.mktmpdir when passed no arguments" do
+ after :each do
+ Dir.rmdir @tmpdir if File.directory? @tmpdir
+ end
+
+ it "returns the path to the created tmp-dir" do
+ Dir.stub!(:mkdir)
+ Dir.should_receive(:tmpdir).and_return("/tmp")
+ @tmpdir = Dir.mktmpdir
+ @tmpdir.should =~ /^\/tmp\//
+ end
+
+ it "creates a new writable directory in the path provided by Dir.tmpdir" do
+ Dir.should_receive(:tmpdir).and_return(tmp(""))
+ @tmpdir = Dir.mktmpdir
+ File.directory?(@tmpdir).should be_true
+ File.writable?(@tmpdir).should be_true
+ end
+end
+
+describe "Dir.mktmpdir when passed a block" do
+ before :each do
+ @real_tmp_root = tmp('')
+ Dir.stub!(:tmpdir).and_return(@real_tmp_root)
+ FileUtils.stub!(:remove_entry)
+ FileUtils.stub!(:remove_entry_secure)
+ end
+
+ after :each do
+ Dir.rmdir @tmpdir if File.directory? @tmpdir
+ end
+
+ it "yields the path to the passed block" do
+ Dir.stub!(:mkdir)
+ called = nil
+ Dir.mktmpdir do |path|
+ @tmpdir = path
+ called = true
+ path.should.start_with?(@real_tmp_root)
+ end
+ called.should be_true
+ end
+
+ it "creates the tmp-dir before yielding" do
+ Dir.should_receive(:tmpdir).and_return(tmp(""))
+ Dir.mktmpdir do |path|
+ @tmpdir = path
+ File.directory?(path).should be_true
+ File.writable?(path).should be_true
+ end
+ end
+
+ it "removes the tmp-dir after executing the block" do
+ Dir.stub!(:mkdir)
+ Dir.mktmpdir do |path|
+ @tmpdir = path
+ FileUtils.should_receive(:remove_entry).with(path)
+ end
+ end
+
+ it "returns the blocks return value" do
+ Dir.stub!(:mkdir)
+ result = Dir.mktmpdir do |path|
+ @tmpdir = path
+ :test
+ end
+ result.should equal(:test)
+ end
+end
+
+describe "Dir.mktmpdir when passed [String]" do
+ before :each do
+ Dir.stub!(:mkdir)
+ Dir.stub!(:tmpdir).and_return("/tmp")
+ end
+
+ after :each do
+ Dir.rmdir @tmpdir if File.directory? @tmpdir
+ end
+
+ it "uses the passed String as a prefix to the tmp-directory" do
+ prefix = "before"
+ @tmpdir = Dir.mktmpdir(prefix)
+ @tmpdir.should =~ /^\/tmp\/#{prefix}/
+ end
+end
+
+describe "Dir.mktmpdir when passed [Array]" do
+ before :each do
+ Dir.stub!(:mkdir)
+ Dir.stub!(:tmpdir).and_return("/tmp")
+ FileUtils.stub!(:remove_entry_secure)
+ end
+
+ after :each do
+ Dir.rmdir @tmpdir if File.directory? @tmpdir
+ end
+
+ it "uses the first element of the passed Array as a prefix and the second element as a suffix to the tmp-directory" do
+ prefix = "before"
+ suffix = "after"
+
+ @tmpdir = Dir.mktmpdir([prefix, suffix])
+ @tmpdir.should =~ /#{suffix}$/
+ end
+end
+
+describe "Dir.mktmpdir when passed [Object]" do
+ it "raises an ArgumentError" do
+ -> { Dir.mktmpdir(Object.new) }.should raise_error(ArgumentError)
+ -> { Dir.mktmpdir(:symbol) }.should raise_error(ArgumentError)
+ -> { Dir.mktmpdir(10) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/tmpdir/dir/tmpdir_spec.rb b/spec/ruby/library/tmpdir/dir/tmpdir_spec.rb
new file mode 100644
index 0000000000..f4ab5e40b8
--- /dev/null
+++ b/spec/ruby/library/tmpdir/dir/tmpdir_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require "tmpdir"
+
+describe "Dir.tmpdir" do
+ it "returns the path to a writable and readable directory" do
+ dir = Dir.tmpdir
+ File.directory?(dir).should be_true
+ File.writable?(dir).should be_true
+ end
+end
diff --git a/spec/ruby/library/uri/decode_www_form_component_spec.rb b/spec/ruby/library/uri/decode_www_form_component_spec.rb
new file mode 100644
index 0000000000..075cec1087
--- /dev/null
+++ b/spec/ruby/library/uri/decode_www_form_component_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI.decode_www_form_component" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/decode_www_form_spec.rb b/spec/ruby/library/uri/decode_www_form_spec.rb
new file mode 100644
index 0000000000..8dd37e514f
--- /dev/null
+++ b/spec/ruby/library/uri/decode_www_form_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI.decode_www_form" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/encode_www_form_component_spec.rb b/spec/ruby/library/uri/encode_www_form_component_spec.rb
new file mode 100644
index 0000000000..a0508b207c
--- /dev/null
+++ b/spec/ruby/library/uri/encode_www_form_component_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI.encode_www_form_component" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/encode_www_form_spec.rb b/spec/ruby/library/uri/encode_www_form_spec.rb
new file mode 100644
index 0000000000..7f4aecf89b
--- /dev/null
+++ b/spec/ruby/library/uri/encode_www_form_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI.encode_www_form" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/eql_spec.rb b/spec/ruby/library/uri/eql_spec.rb
new file mode 100644
index 0000000000..2bbf8fd40c
--- /dev/null
+++ b/spec/ruby/library/uri/eql_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/normalization'
+require_relative 'shared/eql'
+require 'uri'
+
+describe "URI#eql?" do
+ it_behaves_like :uri_eql, :eql?
+
+ it_behaves_like :uri_eql_against_other_types, :eql?
+end
diff --git a/spec/ruby/library/uri/equality_spec.rb b/spec/ruby/library/uri/equality_spec.rb
new file mode 100644
index 0000000000..1c247ce291
--- /dev/null
+++ b/spec/ruby/library/uri/equality_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/normalization'
+require_relative 'shared/eql'
+require 'uri'
+
+describe "URI#==" do
+ it "ignores capitalization of host names" do
+ URI("http://exAMPLE.cOm").should == URI("http://example.com")
+ end
+
+ it "ignores capitalization of scheme" do
+ URI("hTTp://example.com").should == URI("http://example.com")
+ end
+
+ it "treats a blank path and a path of '/' as the same" do
+ URI("http://example.com").should == URI("http://example.com/")
+ end
+
+ it "is case sensitive in all components of the URI but the host and scheme" do
+ URI("http://example.com/paTH").should_not == URI("http://example.com/path")
+ URI("http://uSer@example.com").should_not == URI("http://user@example.com")
+ URI("http://example.com/path?quERy").should_not == URI("http://example.com/path?query")
+ URI("http://example.com/#fragMENT").should_not == URI("http://example.com/#fragment")
+ end
+
+ it "differentiates based on port number" do
+ URI("http://example.com:8080").should_not == URI("http://example.com")
+ end
+
+ # Note: The previous tests will be included in following ones
+
+ it_behaves_like :uri_eql, :==
+
+ it_behaves_like :uri_eql_against_other_types, :==
+
+ quarantine! do # Quarantined until redmine:2542 is accepted
+ it "returns true only if the normalized forms are equivalent" do
+ URISpec::NORMALIZED_FORMS.each do |form|
+ normal_uri = URI(form[:normalized])
+ form[:equivalent].each do |same|
+ URI(same).should == normal_uri
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/uri/escape/decode_spec.rb b/spec/ruby/library/uri/escape/decode_spec.rb
new file mode 100644
index 0000000000..b4ef799411
--- /dev/null
+++ b/spec/ruby/library/uri/escape/decode_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Escape#decode" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/escape/encode_spec.rb b/spec/ruby/library/uri/escape/encode_spec.rb
new file mode 100644
index 0000000000..2b61b7c152
--- /dev/null
+++ b/spec/ruby/library/uri/escape/encode_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Escape#encode" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/escape/escape_spec.rb b/spec/ruby/library/uri/escape/escape_spec.rb
new file mode 100644
index 0000000000..dddbc60707
--- /dev/null
+++ b/spec/ruby/library/uri/escape/escape_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Escape#escape" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/escape/unescape_spec.rb b/spec/ruby/library/uri/escape/unescape_spec.rb
new file mode 100644
index 0000000000..7d574d13c1
--- /dev/null
+++ b/spec/ruby/library/uri/escape/unescape_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Escape#unescape" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/extract_spec.rb b/spec/ruby/library/uri/extract_spec.rb
new file mode 100644
index 0000000000..1294a480f1
--- /dev/null
+++ b/spec/ruby/library/uri/extract_spec.rb
@@ -0,0 +1,86 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI.extract" do
+ it "behaves according to its documentation" do
+ URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.").should == ["http://foo.example.org/bla", "mailto:test@example.com"]
+ end
+
+ it "treats contiguous URIs as a single URI" do
+ URI.extract('http://example.jphttp://example.jp').should == ['http://example.jphttp://example.jp']
+ end
+
+ it "treats pretty much anything with a colon as a URI" do
+ URI.extract('From: XXX [mailto:xxx@xxx.xxx.xxx]').should == ['From:', 'mailto:xxx@xxx.xxx.xxx]']
+ end
+
+ it "wraps a URI string in an array" do
+ URI.extract("http://github.com/brixen/rubyspec/tree/master").should == ["http://github.com/brixen/rubyspec/tree/master"]
+ end
+
+ it "pulls a variety of protocol URIs from a string" do
+ URI.extract("this is a string, it has http://rubini.us/ in it").should == ["http://rubini.us/"]
+ URI.extract("mailto:spambait@example.com").should == ["mailto:spambait@example.com"]
+ URI.extract("ftp://ruby-lang.org/").should == ["ftp://ruby-lang.org/"]
+ URI.extract("https://mail.google.com").should == ["https://mail.google.com"]
+ URI.extract("anything://example.com/").should == ["anything://example.com/"]
+ end
+
+ it "pulls all URIs within a string in order into an array when a block is not given" do
+ URI.extract("1.3. Example URI
+
+ The following examples illustrate URI that are in common use.
+
+ ftp://ftp.is.co.za/rfc/rfc1808.txt
+ -- ftp scheme for File Transfer Protocol services
+
+ gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles
+ -- gopher scheme for Gopher and Gopher+ Protocol services
+
+ http://www.math.uio.no/faq/compression-faq/part1.html
+ -- http scheme for Hypertext Transfer Protocol services
+
+ mailto:mduerst@ifi.unizh.ch
+ -- mailto scheme for electronic mail addresses
+
+ news:comp.infosystems.www.servers.unix
+ -- news scheme for USENET news groups and articles
+
+ telnet://melvyl.ucop.edu/
+ -- telnet scheme for interactive services via the TELNET Protocol
+ ").should == ["ftp://ftp.is.co.za/rfc/rfc1808.txt","gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles","http://www.math.uio.no/faq/compression-faq/part1.html","mailto:mduerst@ifi.unizh.ch","news:comp.infosystems.www.servers.unix","telnet://melvyl.ucop.edu/"]
+ end
+
+ it "yields each URI in the given string in order to a block, if given, and returns nil" do
+ results = ["http://foo.example.org/bla", "mailto:test@example.com"]
+ URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.") {|uri|
+ uri.should == results.shift
+ }.should == nil
+ results.should == []
+ end
+
+ it "allows the user to specify a list of acceptable protocols of URIs to scan for" do
+ URI.extract("1.3. Example URI
+
+ The following examples illustrate URI that are in common use.
+
+ ftp://ftp.is.co.za/rfc/rfc1808.txt
+ -- ftp scheme for File Transfer Protocol services
+
+ gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles
+ -- gopher scheme for Gopher and Gopher+ Protocol services
+
+ http://www.math.uio.no/faq/compression-faq/part1.html
+ -- http scheme for Hypertext Transfer Protocol services
+
+ mailto:mduerst@ifi.unizh.ch
+ -- mailto scheme for electronic mail addresses
+
+ news:comp.infosystems.www.servers.unix
+ -- news scheme for USENET news groups and articles
+
+ telnet://melvyl.ucop.edu/
+ -- telnet scheme for interactive services via the TELNET Protocol
+ ", ["http","ftp","mailto"]).should == ["ftp://ftp.is.co.za/rfc/rfc1808.txt","http://www.math.uio.no/faq/compression-faq/part1.html","mailto:mduerst@ifi.unizh.ch"]
+ end
+end
diff --git a/spec/ruby/library/uri/fixtures/classes.rb b/spec/ruby/library/uri/fixtures/classes.rb
new file mode 100644
index 0000000000..e1179307cc
--- /dev/null
+++ b/spec/ruby/library/uri/fixtures/classes.rb
@@ -0,0 +1,11 @@
+require 'uri'
+
+module URISpec
+ def self.components(uri)
+ result = {}
+ uri.component.each do |component|
+ result[component] = uri.send(component)
+ end
+ result
+ end
+end
diff --git a/spec/ruby/library/uri/fixtures/normalization.rb b/spec/ruby/library/uri/fixtures/normalization.rb
new file mode 100644
index 0000000000..cbc26c9b48
--- /dev/null
+++ b/spec/ruby/library/uri/fixtures/normalization.rb
@@ -0,0 +1,54 @@
+module URISpec
+ # Not an exhaustive list. Refer to rfc3986
+ NORMALIZED_FORMS = [
+ { normalized: "http://example.com/",
+ equivalent: %w{ hTTp://example.com/
+ http://exaMple.com/
+ http://exa%4dple.com/
+ http://exa%4Dple.com/
+ http://exa%6dple.com/
+ http://exa%6Dple.com/
+ http://@example.com/
+ http://example.com:/
+ http://example.com:80/
+ http://example.com
+ },
+ different: %w{ http://example.com/#
+ http://example.com/?
+ http://example.com:8888/
+ http:///example.com
+ http:example.com
+ https://example.com/
+ },
+ },
+ { normalized: "http://example.com/index.html",
+ equivalent: %w{ http://example.com/index.ht%6dl
+ http://example.com/index.ht%6Dl
+ },
+ different: %w{ http://example.com/index.hTMl
+ http://example.com/index.ht%4dl
+ http://example.com/index
+ http://example.com/
+ http://example.com/
+ },
+ },
+ { normalized: "http://example.com/x?y#z",
+ equivalent: %w{ http://example.com/x?y#%7a
+ http://example.com/x?y#%7A
+ http://example.com/x?%79#z
+ },
+ different: %w{ http://example.com/x?Y#z
+ http://example.com/x?y#Z
+ http://example.com/x?y=#z
+ http://example.com/x?y
+ http://example.com/x#z
+ },
+ },
+ { normalized: "http://example.com/x?q=a%20b",
+ equivalent: %w{
+ },
+ different: %w{ http://example.com/x?q=a+b
+ },
+ },
+ ]
+end
diff --git a/spec/ruby/library/uri/ftp/build_spec.rb b/spec/ruby/library/uri/ftp/build_spec.rb
new file mode 100644
index 0000000000..9e0fb44cf1
--- /dev/null
+++ b/spec/ruby/library/uri/ftp/build_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::FTP.build" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ftp/merge_spec.rb b/spec/ruby/library/uri/ftp/merge_spec.rb
new file mode 100644
index 0000000000..7a9997bbac
--- /dev/null
+++ b/spec/ruby/library/uri/ftp/merge_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::FTP#merge" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ftp/new2_spec.rb b/spec/ruby/library/uri/ftp/new2_spec.rb
new file mode 100644
index 0000000000..eb1b149c81
--- /dev/null
+++ b/spec/ruby/library/uri/ftp/new2_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::FTP.new2" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ftp/path_spec.rb b/spec/ruby/library/uri/ftp/path_spec.rb
new file mode 100644
index 0000000000..5fec7f11b6
--- /dev/null
+++ b/spec/ruby/library/uri/ftp/path_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::FTP#path=" do
+ before :each do
+ @url = URI.parse('ftp://example.com')
+ end
+
+ it "does not require a leading /" do
+ @url.path = 'foo'
+ @url.path.should == 'foo'
+ end
+
+ it "does not strip the leading /" do
+ @url.path = '/foo'
+ @url.path.should == '/foo'
+ end
+end
+
+describe "URI::FTP#path" do
+ it "unescapes the leading /" do
+ url = URI.parse('ftp://example.com/%2Ffoo')
+
+ url.path.should == '/foo'
+ end
+end
diff --git a/spec/ruby/library/uri/ftp/set_typecode_spec.rb b/spec/ruby/library/uri/ftp/set_typecode_spec.rb
new file mode 100644
index 0000000000..31067930c0
--- /dev/null
+++ b/spec/ruby/library/uri/ftp/set_typecode_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::FTP#set_typecode" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ftp/to_s_spec.rb b/spec/ruby/library/uri/ftp/to_s_spec.rb
new file mode 100644
index 0000000000..3b4ff2d906
--- /dev/null
+++ b/spec/ruby/library/uri/ftp/to_s_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+
+describe "URI::FTP#to_s" do
+ before :each do
+ @url = URI.parse('ftp://example.com')
+ end
+
+ it "escapes the leading /" do
+ @url.path = '/foo'
+
+ @url.to_s.should == 'ftp://example.com/%2Ffoo'
+ end
+end
diff --git a/spec/ruby/library/uri/ftp/typecode_spec.rb b/spec/ruby/library/uri/ftp/typecode_spec.rb
new file mode 100644
index 0000000000..1f2bb02252
--- /dev/null
+++ b/spec/ruby/library/uri/ftp/typecode_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::FTP#typecode" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::FTP#typecode=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/absolute_spec.rb b/spec/ruby/library/uri/generic/absolute_spec.rb
new file mode 100644
index 0000000000..fe4b48d067
--- /dev/null
+++ b/spec/ruby/library/uri/generic/absolute_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#absolute" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#absolute?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/build2_spec.rb b/spec/ruby/library/uri/generic/build2_spec.rb
new file mode 100644
index 0000000000..9abd1d80ef
--- /dev/null
+++ b/spec/ruby/library/uri/generic/build2_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic.build2" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/build_spec.rb b/spec/ruby/library/uri/generic/build_spec.rb
new file mode 100644
index 0000000000..50c27674ce
--- /dev/null
+++ b/spec/ruby/library/uri/generic/build_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic.build" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/coerce_spec.rb b/spec/ruby/library/uri/generic/coerce_spec.rb
new file mode 100644
index 0000000000..f695e560ac
--- /dev/null
+++ b/spec/ruby/library/uri/generic/coerce_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#coerce" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/component_ary_spec.rb b/spec/ruby/library/uri/generic/component_ary_spec.rb
new file mode 100644
index 0000000000..b39752f8d9
--- /dev/null
+++ b/spec/ruby/library/uri/generic/component_ary_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#component_ary" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/component_spec.rb b/spec/ruby/library/uri/generic/component_spec.rb
new file mode 100644
index 0000000000..f92409a0b0
--- /dev/null
+++ b/spec/ruby/library/uri/generic/component_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#component" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic.component" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/default_port_spec.rb b/spec/ruby/library/uri/generic/default_port_spec.rb
new file mode 100644
index 0000000000..4e10e34c9d
--- /dev/null
+++ b/spec/ruby/library/uri/generic/default_port_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#default_port" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic.default_port" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/eql_spec.rb b/spec/ruby/library/uri/generic/eql_spec.rb
new file mode 100644
index 0000000000..df9987b524
--- /dev/null
+++ b/spec/ruby/library/uri/generic/eql_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#eql?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/equal_value_spec.rb b/spec/ruby/library/uri/generic/equal_value_spec.rb
new file mode 100644
index 0000000000..bd2feb86d4
--- /dev/null
+++ b/spec/ruby/library/uri/generic/equal_value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#==" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/fragment_spec.rb b/spec/ruby/library/uri/generic/fragment_spec.rb
new file mode 100644
index 0000000000..20126b207a
--- /dev/null
+++ b/spec/ruby/library/uri/generic/fragment_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#fragment" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#fragment=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/hash_spec.rb b/spec/ruby/library/uri/generic/hash_spec.rb
new file mode 100644
index 0000000000..286c1ab38d
--- /dev/null
+++ b/spec/ruby/library/uri/generic/hash_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#hash" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/hierarchical_spec.rb b/spec/ruby/library/uri/generic/hierarchical_spec.rb
new file mode 100644
index 0000000000..df9bbae202
--- /dev/null
+++ b/spec/ruby/library/uri/generic/hierarchical_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#hierarchical?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/host_spec.rb b/spec/ruby/library/uri/generic/host_spec.rb
new file mode 100644
index 0000000000..f2076d2bc1
--- /dev/null
+++ b/spec/ruby/library/uri/generic/host_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#host" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#host=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/inspect_spec.rb b/spec/ruby/library/uri/generic/inspect_spec.rb
new file mode 100644
index 0000000000..4ff81eef82
--- /dev/null
+++ b/spec/ruby/library/uri/generic/inspect_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#inspect" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/merge_spec.rb b/spec/ruby/library/uri/generic/merge_spec.rb
new file mode 100644
index 0000000000..017873cc90
--- /dev/null
+++ b/spec/ruby/library/uri/generic/merge_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#merge" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#merge!" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/minus_spec.rb b/spec/ruby/library/uri/generic/minus_spec.rb
new file mode 100644
index 0000000000..ad8f816839
--- /dev/null
+++ b/spec/ruby/library/uri/generic/minus_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#-" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/normalize_spec.rb b/spec/ruby/library/uri/generic/normalize_spec.rb
new file mode 100644
index 0000000000..d70a77c044
--- /dev/null
+++ b/spec/ruby/library/uri/generic/normalize_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#normalize" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#normalize!" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/opaque_spec.rb b/spec/ruby/library/uri/generic/opaque_spec.rb
new file mode 100644
index 0000000000..e6d40da52b
--- /dev/null
+++ b/spec/ruby/library/uri/generic/opaque_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#opaque" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#opaque=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/password_spec.rb b/spec/ruby/library/uri/generic/password_spec.rb
new file mode 100644
index 0000000000..18db503883
--- /dev/null
+++ b/spec/ruby/library/uri/generic/password_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#password" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#password=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/path_spec.rb b/spec/ruby/library/uri/generic/path_spec.rb
new file mode 100644
index 0000000000..d84975c579
--- /dev/null
+++ b/spec/ruby/library/uri/generic/path_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#path" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#path=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/plus_spec.rb b/spec/ruby/library/uri/generic/plus_spec.rb
new file mode 100644
index 0000000000..e6d2222dac
--- /dev/null
+++ b/spec/ruby/library/uri/generic/plus_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#+" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/port_spec.rb b/spec/ruby/library/uri/generic/port_spec.rb
new file mode 100644
index 0000000000..6e5ef01493
--- /dev/null
+++ b/spec/ruby/library/uri/generic/port_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#port" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#port=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/query_spec.rb b/spec/ruby/library/uri/generic/query_spec.rb
new file mode 100644
index 0000000000..528cc3be02
--- /dev/null
+++ b/spec/ruby/library/uri/generic/query_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#query" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#query=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/registry_spec.rb b/spec/ruby/library/uri/generic/registry_spec.rb
new file mode 100644
index 0000000000..aece265a07
--- /dev/null
+++ b/spec/ruby/library/uri/generic/registry_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#registry" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#registry=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/relative_spec.rb b/spec/ruby/library/uri/generic/relative_spec.rb
new file mode 100644
index 0000000000..a7de1f306a
--- /dev/null
+++ b/spec/ruby/library/uri/generic/relative_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#relative?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/route_from_spec.rb b/spec/ruby/library/uri/generic/route_from_spec.rb
new file mode 100644
index 0000000000..fd69816edf
--- /dev/null
+++ b/spec/ruby/library/uri/generic/route_from_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#route_from" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/route_to_spec.rb b/spec/ruby/library/uri/generic/route_to_spec.rb
new file mode 100644
index 0000000000..7ab9aff2e8
--- /dev/null
+++ b/spec/ruby/library/uri/generic/route_to_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#route_to" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/scheme_spec.rb b/spec/ruby/library/uri/generic/scheme_spec.rb
new file mode 100644
index 0000000000..7922a8e977
--- /dev/null
+++ b/spec/ruby/library/uri/generic/scheme_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#scheme" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#scheme=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/select_spec.rb b/spec/ruby/library/uri/generic/select_spec.rb
new file mode 100644
index 0000000000..99aef83f99
--- /dev/null
+++ b/spec/ruby/library/uri/generic/select_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#select" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_fragment_spec.rb b/spec/ruby/library/uri/generic/set_fragment_spec.rb
new file mode 100644
index 0000000000..2476315f08
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_fragment_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_fragment" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_host_spec.rb b/spec/ruby/library/uri/generic/set_host_spec.rb
new file mode 100644
index 0000000000..c7f5c6884e
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_host_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_host" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_opaque_spec.rb b/spec/ruby/library/uri/generic/set_opaque_spec.rb
new file mode 100644
index 0000000000..8a494a7ee2
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_opaque_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_opaque" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_password_spec.rb b/spec/ruby/library/uri/generic/set_password_spec.rb
new file mode 100644
index 0000000000..93b05fe911
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_password_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_password" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_path_spec.rb b/spec/ruby/library/uri/generic/set_path_spec.rb
new file mode 100644
index 0000000000..6d9f59d1a5
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_path_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_path" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_port_spec.rb b/spec/ruby/library/uri/generic/set_port_spec.rb
new file mode 100644
index 0000000000..2c8a4edd22
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_port_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_port" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_query_spec.rb b/spec/ruby/library/uri/generic/set_query_spec.rb
new file mode 100644
index 0000000000..3f3453ba8e
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_query_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_query" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_registry_spec.rb b/spec/ruby/library/uri/generic/set_registry_spec.rb
new file mode 100644
index 0000000000..44afe246d1
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_registry_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_registry" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_scheme_spec.rb b/spec/ruby/library/uri/generic/set_scheme_spec.rb
new file mode 100644
index 0000000000..ffa29da446
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_scheme_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_scheme" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_user_spec.rb b/spec/ruby/library/uri/generic/set_user_spec.rb
new file mode 100644
index 0000000000..9a39e1f4c3
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_user_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_user" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/set_userinfo_spec.rb b/spec/ruby/library/uri/generic/set_userinfo_spec.rb
new file mode 100644
index 0000000000..76878204d2
--- /dev/null
+++ b/spec/ruby/library/uri/generic/set_userinfo_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#set_userinfo" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/to_s_spec.rb b/spec/ruby/library/uri/generic/to_s_spec.rb
new file mode 100644
index 0000000000..8c90d7645b
--- /dev/null
+++ b/spec/ruby/library/uri/generic/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#to_s" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/use_registry_spec.rb b/spec/ruby/library/uri/generic/use_registry_spec.rb
new file mode 100644
index 0000000000..bdfe27c048
--- /dev/null
+++ b/spec/ruby/library/uri/generic/use_registry_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic.use_registry" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/user_spec.rb b/spec/ruby/library/uri/generic/user_spec.rb
new file mode 100644
index 0000000000..345412ca29
--- /dev/null
+++ b/spec/ruby/library/uri/generic/user_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#user" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#user=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/generic/userinfo_spec.rb b/spec/ruby/library/uri/generic/userinfo_spec.rb
new file mode 100644
index 0000000000..4bf111079c
--- /dev/null
+++ b/spec/ruby/library/uri/generic/userinfo_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Generic#userinfo" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::Generic#userinfo=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/http/build_spec.rb b/spec/ruby/library/uri/http/build_spec.rb
new file mode 100644
index 0000000000..d34cf83ecf
--- /dev/null
+++ b/spec/ruby/library/uri/http/build_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::HTTP.build" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/http/request_uri_spec.rb b/spec/ruby/library/uri/http/request_uri_spec.rb
new file mode 100644
index 0000000000..7b05147d36
--- /dev/null
+++ b/spec/ruby/library/uri/http/request_uri_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::HTTP.request_uri" do
+ it "returns a string of the path + query" do
+ URI("http://reddit.com/r/ruby/").request_uri.should == "/r/ruby/"
+ URI("http://reddit.com/r/ruby/search?q=rubinius").request_uri.should == "/r/ruby/search?q=rubinius"
+ end
+
+ it "returns '/' if the path of the URI is blank" do
+ URI("http://ruby.reddit.com").request_uri.should == "/"
+ end
+end
+describe "URI::HTTP#request_uri" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/join_spec.rb b/spec/ruby/library/uri/join_spec.rb
new file mode 100644
index 0000000000..796f74134f
--- /dev/null
+++ b/spec/ruby/library/uri/join_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI.join" do
+ it "returns a URI object of the concatenation of a protocol and domain, and a path" do
+ URI.join("http://localhost/","main.rbx").should == URI.parse("http://localhost/main.rbx")
+ end
+
+ it "accepts URI objects" do
+ URI.join(URI("http://localhost/"),"main.rbx").should == URI.parse("http://localhost/main.rbx")
+ URI.join("http://localhost/",URI("main.rbx")).should == URI.parse("http://localhost/main.rbx")
+ URI.join(URI("http://localhost/"),URI("main.rbx")).should == URI.parse("http://localhost/main.rbx")
+ end
+
+ it "accepts string-like arguments with to_str" do
+ str = mock('string-like')
+ str.should_receive(:to_str).and_return("http://ruby-lang.org")
+ str2 = mock('string-like also')
+ str2.should_receive(:to_str).and_return("foo/bar")
+ URI.join(str, str2).should == URI.parse("http://ruby-lang.org/foo/bar")
+ end
+
+ it "raises an error if given no argument" do
+ -> {
+ URI.join
+ }.should raise_error(ArgumentError)
+ end
+
+ it "doesn't create redundant '/'s" do
+ URI.join("http://localhost/", "/main.rbx").should == URI.parse("http://localhost/main.rbx")
+ end
+
+ it "discards arguments given before an absolute uri" do
+ URI.join("http://localhost/a/b/c/d", "http://ruby-lang.com/foo", "bar").should == URI.parse("http://ruby-lang.com/bar")
+ end
+
+ it "resolves .. in paths" do
+ URI.join("http://localhost/a/b/c/d", "../../e/f", "g/h/../i").to_s.should == "http://localhost/a/e/g/i"
+ end
+end
+
+
+# assert_equal(URI.parse('http://foo/bar'), URI.join('http://foo/bar'))
+# assert_equal(URI.parse('http://foo/bar'), URI.join('http://foo', 'bar'))
+# assert_equal(URI.parse('http://foo/bar/'), URI.join('http://foo', 'bar/'))
+#
+# assert_equal(URI.parse('http://foo/baz'), URI.join('http://foo', 'bar', 'baz'))
+# assert_equal(URI.parse('http://foo/baz'), URI.join('http://foo', 'bar', '/baz'))
+# assert_equal(URI.parse('http://foo/baz/'), URI.join('http://foo', 'bar', '/baz/'))
+# assert_equal(URI.parse('http://foo/bar/baz'), URI.join('http://foo', 'bar/', 'baz'))
+# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar', 'baz', 'hoge'))
+#
+# assert_equal(URI.parse('http://foo/bar/baz'), URI.join('http://foo', 'bar/baz'))
+# assert_equal(URI.parse('http://foo/bar/hoge'), URI.join('http://foo', 'bar/baz', 'hoge'))
+# assert_equal(URI.parse('http://foo/bar/baz/hoge'), URI.join('http://foo', 'bar/baz/', 'hoge'))
+# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar/baz', '/hoge'))
+# assert_equal(URI.parse('http://foo/bar/hoge'), URI.join('http://foo', 'bar/baz', 'hoge'))
+# assert_equal(URI.parse('http://foo/bar/baz/hoge'), URI.join('http://foo', 'bar/baz/', 'hoge'))
+# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar/baz', '/hoge'))
diff --git a/spec/ruby/library/uri/ldap/attributes_spec.rb b/spec/ruby/library/uri/ldap/attributes_spec.rb
new file mode 100644
index 0000000000..88e3328bad
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/attributes_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#attributes" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::LDAP#attributes=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/build_spec.rb b/spec/ruby/library/uri/ldap/build_spec.rb
new file mode 100644
index 0000000000..8d0e312d1a
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/build_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP.build" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/dn_spec.rb b/spec/ruby/library/uri/ldap/dn_spec.rb
new file mode 100644
index 0000000000..a5ac02e891
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/dn_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#dn" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::LDAP#dn=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/extensions_spec.rb b/spec/ruby/library/uri/ldap/extensions_spec.rb
new file mode 100644
index 0000000000..473222eb7a
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/extensions_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#extensions" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::LDAP#extensions=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/filter_spec.rb b/spec/ruby/library/uri/ldap/filter_spec.rb
new file mode 100644
index 0000000000..d0b7fcc384
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/filter_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#filter" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::LDAP#filter=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/hierarchical_spec.rb b/spec/ruby/library/uri/ldap/hierarchical_spec.rb
new file mode 100644
index 0000000000..5471c53d76
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/hierarchical_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#hierarchical?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/scope_spec.rb b/spec/ruby/library/uri/ldap/scope_spec.rb
new file mode 100644
index 0000000000..5ea5581671
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/scope_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#scope" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::LDAP#scope=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/set_attributes_spec.rb b/spec/ruby/library/uri/ldap/set_attributes_spec.rb
new file mode 100644
index 0000000000..fdaaa8344a
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/set_attributes_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#set_attributes" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/set_dn_spec.rb b/spec/ruby/library/uri/ldap/set_dn_spec.rb
new file mode 100644
index 0000000000..c50ee6a98d
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/set_dn_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#set_dn" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/set_extensions_spec.rb b/spec/ruby/library/uri/ldap/set_extensions_spec.rb
new file mode 100644
index 0000000000..5a39da4607
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/set_extensions_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#set_extensions" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/set_filter_spec.rb b/spec/ruby/library/uri/ldap/set_filter_spec.rb
new file mode 100644
index 0000000000..c3ede20bb4
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/set_filter_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#set_filter" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/ldap/set_scope_spec.rb b/spec/ruby/library/uri/ldap/set_scope_spec.rb
new file mode 100644
index 0000000000..43f3f68f86
--- /dev/null
+++ b/spec/ruby/library/uri/ldap/set_scope_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::LDAP#set_scope" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/mailto/build_spec.rb b/spec/ruby/library/uri/mailto/build_spec.rb
new file mode 100644
index 0000000000..2c011626ab
--- /dev/null
+++ b/spec/ruby/library/uri/mailto/build_spec.rb
@@ -0,0 +1,92 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Mailto.build" do
+ it "conforms to the MatzRuby tests" do
+ ok = []
+ bad = []
+
+ # RFC2368, 6. Examples
+ # mailto:chris@example.com
+ ok << ["mailto:chris@example.com"]
+ ok[-1] << ["chris@example.com", nil]
+ ok[-1] << {to: "chris@example.com"}
+
+ # mailto:infobot@example.com?subject=current-issue
+ ok << ["mailto:infobot@example.com?subject=current-issue"]
+ ok[-1] << ["infobot@example.com", ["subject=current-issue"]]
+ ok[-1] << {to: "infobot@example.com",
+ headers: ["subject=current-issue"]}
+
+ # mailto:infobot@example.com?body=send%20current-issue
+ ok << ["mailto:infobot@example.com?body=send%20current-issue"]
+ ok[-1] << ["infobot@example.com", ["body=send%20current-issue"]]
+ ok[-1] << {to: "infobot@example.com",
+ headers: ["body=send%20current-issue"]}
+
+ # mailto:infobot@example.com?body=send%20current-issue%0D%0Asend%20index
+ ok << ["mailto:infobot@example.com?body=send%20current-issue%0D%0Asend%20index"]
+ ok[-1] << ["infobot@example.com",
+ ["body=send%20current-issue%0D%0Asend%20index"]]
+ ok[-1] << {to: "infobot@example.com",
+ headers: ["body=send%20current-issue%0D%0Asend%20index"]}
+
+ # mailto:foobar@example.com?In-Reply-To=%3c3469A91.D10AF4C@example.com
+ ok << ["mailto:foobar@example.com?In-Reply-To=%3c3469A91.D10AF4C@example.com"]
+ ok[-1] << ["foobar@example.com",
+ ["In-Reply-To=%3c3469A91.D10AF4C@example.com"]]
+ ok[-1] << {to: "foobar@example.com",
+ headers: ["In-Reply-To=%3c3469A91.D10AF4C@example.com"]}
+
+ # mailto:majordomo@example.com?body=subscribe%20bamboo-l
+ ok << ["mailto:majordomo@example.com?body=subscribe%20bamboo-l"]
+ ok[-1] << ["majordomo@example.com", ["body=subscribe%20bamboo-l"]]
+ ok[-1] << {to: "majordomo@example.com",
+ headers: ["body=subscribe%20bamboo-l"]}
+
+ # mailto:joe@example.com?cc=bob@example.com&body=hello
+ ok << ["mailto:joe@example.com?cc=bob@example.com&body=hello"]
+ ok[-1] << ["joe@example.com", ["cc=bob@example.com", "body=hello"]]
+ ok[-1] << {to: "joe@example.com",
+ headers: ["cc=bob@example.com", "body=hello"]}
+
+ # mailto:?to=joe@example.com&cc=bob@example.com&body=hello
+ ok << ["mailto:?to=joe@example.com&cc=bob@example.com&body=hello"]
+ ok[-1] << [nil,
+ ["to=joe@example.com", "cc=bob@example.com", "body=hello"]]
+ ok[-1] << {headers: ["to=joe@example.com", "cc=bob@example.com", "body=hello"]}
+
+ # mailto:gorby%25kremvax@example.com
+ ok << ["mailto:gorby%25kremvax@example.com"]
+ ok[-1] << ["gorby%25kremvax@example.com", nil]
+ ok[-1] << {to: "gorby%25kremvax@example.com"}
+
+ # mailto:unlikely%3Faddress@example.com?blat=foop
+ ok << ["mailto:unlikely%3Faddress@example.com?blat=foop"]
+ ok[-1] << ["unlikely%3Faddress@example.com", ["blat=foop"]]
+ ok[-1] << {to: "unlikely%3Faddress@example.com",
+ headers: ["blat=foop"]}
+
+ ok_all = ok.flatten.join("\0")
+
+ # mailto:joe@example.com?cc=bob@example.com?body=hello ; WRONG!
+ bad << ["joe@example.com", ["cc=bob@example.com?body=hello"]]
+
+ # mailto:javascript:alert()
+ bad << ["javascript:alert()", []]
+
+ # '=' which is in hname or hvalue is wrong.
+ bad << ["foo@example.jp?subject=1+1=2", []]
+
+ ok.each do |x|
+ URI::MailTo.build(x[1]).to_s.should == x[0]
+ URI::MailTo.build(x[2]).to_s.should == x[0]
+ end
+
+ bad.each do |x|
+ -> { URI::MailTo.build(x) }.should raise_error(URI::InvalidComponentError)
+ end
+
+ ok.flatten.join("\0").should == ok_all
+ end
+end
diff --git a/spec/ruby/library/uri/mailto/headers_spec.rb b/spec/ruby/library/uri/mailto/headers_spec.rb
new file mode 100644
index 0000000000..8aefec0e75
--- /dev/null
+++ b/spec/ruby/library/uri/mailto/headers_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::MailTo#headers" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::MailTo#headers=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/mailto/set_headers_spec.rb b/spec/ruby/library/uri/mailto/set_headers_spec.rb
new file mode 100644
index 0000000000..b6ce1a694b
--- /dev/null
+++ b/spec/ruby/library/uri/mailto/set_headers_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::MailTo#set_headers" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/mailto/set_to_spec.rb b/spec/ruby/library/uri/mailto/set_to_spec.rb
new file mode 100644
index 0000000000..eabc47f9a8
--- /dev/null
+++ b/spec/ruby/library/uri/mailto/set_to_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::MailTo#set_to" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/mailto/to_mailtext_spec.rb b/spec/ruby/library/uri/mailto/to_mailtext_spec.rb
new file mode 100644
index 0000000000..3763a2d402
--- /dev/null
+++ b/spec/ruby/library/uri/mailto/to_mailtext_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::MailTo#to_mailtext" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/mailto/to_rfc822text_spec.rb b/spec/ruby/library/uri/mailto/to_rfc822text_spec.rb
new file mode 100644
index 0000000000..2843b46848
--- /dev/null
+++ b/spec/ruby/library/uri/mailto/to_rfc822text_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::MailTo#to_rfc822text" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/mailto/to_s_spec.rb b/spec/ruby/library/uri/mailto/to_s_spec.rb
new file mode 100644
index 0000000000..746e8356eb
--- /dev/null
+++ b/spec/ruby/library/uri/mailto/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::MailTo#to_s" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/mailto/to_spec.rb b/spec/ruby/library/uri/mailto/to_spec.rb
new file mode 100644
index 0000000000..68dfadd359
--- /dev/null
+++ b/spec/ruby/library/uri/mailto/to_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::MailTo#to" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "URI::MailTo#to=" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/merge_spec.rb b/spec/ruby/library/uri/merge_spec.rb
new file mode 100644
index 0000000000..e9644a7fd0
--- /dev/null
+++ b/spec/ruby/library/uri/merge_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI#merge" do
+ it "returns the receiver and the argument, joined as per URI.join" do
+ URI("http://localhost/").merge("main.rbx").should == URI.parse("http://localhost/main.rbx")
+ URI("http://localhost/a/b/c/d").merge("http://ruby-lang.com/foo").should == URI.parse("http://ruby-lang.com/foo")
+ URI("http://localhost/a/b/c/d").merge("../../e/f").to_s.should == "http://localhost/a/e/f"
+ end
+
+ it "accepts URI objects as argument" do
+ URI("http://localhost/").merge(URI("main.rbx")).should == URI.parse("http://localhost/main.rbx")
+ end
+
+ it "accepts a string-like argument" do
+ str = mock('string-like')
+ str.should_receive(:to_str).and_return("foo/bar")
+ URI("http://localhost/").merge(str).should == URI.parse("http://localhost/foo/bar")
+ end
+end
diff --git a/spec/ruby/library/uri/normalize_spec.rb b/spec/ruby/library/uri/normalize_spec.rb
new file mode 100644
index 0000000000..3d4451990a
--- /dev/null
+++ b/spec/ruby/library/uri/normalize_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/normalization'
+require 'uri'
+
+describe "URI#normalize" do
+ it "adds a / onto the end of the URI if the path is blank" do
+ no_path = URI("http://example.com")
+ no_path.to_s.should_not == "http://example.com/"
+ no_path.normalize.to_s.should == "http://example.com/"
+ end
+
+ it "downcases the host of the URI" do
+ uri = URI("http://exAMPLE.cOm/")
+ uri.to_s.should_not == "http://example.com/"
+ uri.normalize.to_s.should == "http://example.com/"
+ end
+
+ # The previous tests are included by the one below
+
+ quarantine! do # Quarantined until redmine:2542 is accepted
+ it "respects RFC 3986" do
+ URISpec::NORMALIZED_FORMS.each do |form|
+ normal_uri = URI(form[:normalized])
+ normalized = normal_uri.normalize.to_s
+ normal_uri.to_s.should == normalized
+ form[:equivalent].each do |same|
+ URI(same).normalize.to_s.should == normalized
+ end
+ form[:different].each do |other|
+ URI(other).normalize.to_s.should_not == normalized
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/uri/parse_spec.rb b/spec/ruby/library/uri/parse_spec.rb
new file mode 100644
index 0000000000..e9ec59b490
--- /dev/null
+++ b/spec/ruby/library/uri/parse_spec.rb
@@ -0,0 +1,203 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "URI.parse" do
+
+ it "returns a URI::HTTP object when parsing an HTTP URI" do
+ URI.parse("http://www.example.com/").should be_kind_of(URI::HTTP)
+ end
+
+ it "populates the components of a parsed URI::HTTP, setting the port to 80 by default" do
+ # general case
+ URISpec.components(URI.parse("http://user:pass@example.com/path/?query=val&q2=val2#fragment")).should == {
+ scheme: "http",
+ userinfo: "user:pass",
+ host: "example.com",
+ port: 80,
+ path: "/path/",
+ query: "query=val&q2=val2",
+ fragment: "fragment"
+ }
+
+ # multiple paths
+ URISpec.components(URI.parse("http://a/b/c/d;p?q")).should == {
+ scheme: "http",
+ userinfo: nil,
+ host: "a",
+ port: 80,
+ path: "/b/c/d;p",
+ query: "q",
+ fragment: nil
+ }
+
+ # multi-level domain
+ URISpec.components(URI.parse('http://www.math.uio.no/faq/compression-faq/part1.html')).should == {
+ scheme: "http",
+ userinfo: nil,
+ host: "www.math.uio.no",
+ port: 80,
+ path: "/faq/compression-faq/part1.html",
+ query: nil,
+ fragment: nil
+ }
+ end
+
+ it "parses out the port number of a URI, when given" do
+ URI.parse("http://example.com:8080/").port.should == 8080
+ end
+
+ it "returns a URI::HTTPS object when parsing an HTTPS URI" do
+ URI.parse("https://important-intern-net.net").should be_kind_of(URI::HTTPS)
+ end
+
+ it "sets the port of a parsed https URI to 443 by default" do
+ URI.parse("https://example.com/").port.should == 443
+ end
+
+ it "populates the components of a parsed URI::FTP object" do
+ # generic, empty password.
+ url = URI.parse("ftp://anonymous@ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.bz2;type=i")
+ url.should be_kind_of(URI::FTP)
+ URISpec.components(url).should == {
+ scheme: "ftp",
+ userinfo: "anonymous",
+ host: "ruby-lang.org",
+ port: 21,
+ path: "pub/ruby/1.8/ruby-1.8.6.tar.bz2",
+ typecode: "i"
+ }
+
+ # multidomain, no user or password
+ url = URI.parse('ftp://ftp.is.co.za/rfc/rfc1808.txt')
+ url.should be_kind_of(URI::FTP)
+ URISpec.components(url).should == {
+ scheme: "ftp",
+ userinfo: nil,
+ host: "ftp.is.co.za",
+ port: 21,
+ path: "rfc/rfc1808.txt",
+ typecode: nil
+ }
+
+ # empty user
+ url = URI.parse('ftp://:pass@localhost/')
+ url.should be_kind_of(URI::FTP)
+ URISpec.components(url).should == {
+ scheme: "ftp",
+ userinfo: ":pass",
+ host: "localhost",
+ port: 21,
+ path: "",
+ typecode: nil
+ }
+ url.password.should == "pass"
+ end
+
+ it "returns a URI::LDAP object when parsing an LDAP URI" do
+ #taken from http://www.faqs.org/rfcs/rfc2255.html 'cause I don't really know what an LDAP url looks like
+ ldap_uris = %w{ ldap:///o=University%20of%20Michigan,c=US ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress ldap://host.com:6666/o=University%20of%20Michigan,c=US??sub?(cn=Babs%20Jensen) ldap://ldap.itd.umich.edu/c=GB?objectClass?one ldap://ldap.question.com/o=Question%3f,c=US?mail ldap://ldap.netscape.com/o=Babsco,c=US??(int=%5c00%5c00%5c00%5c04) ldap:///??sub??bindname=cn=Manager%2co=Foo ldap:///??sub??!bindname=cn=Manager%2co=Foo }
+ ldap_uris.each do |ldap_uri|
+ URI.parse(ldap_uri).should be_kind_of(URI::LDAP)
+ end
+ end
+
+ it "populates the components of a parsed URI::LDAP object" do
+ URISpec.components(URI.parse("ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress?scope?filter?extensions")).should == {
+ scheme: "ldap",
+ host: "ldap.itd.umich.edu",
+ port: 389,
+ dn: "o=University%20of%20Michigan,c=US",
+ attributes: "postalAddress",
+ scope: "scope",
+ filter: "filter",
+ extensions: "extensions"
+ }
+ end
+
+ it "returns a URI::MailTo object when passed a mailto URI" do
+ URI.parse("mailto:spam@mailinator.com").should be_kind_of(URI::MailTo)
+ end
+
+ it "populates the components of a parsed URI::MailTo object" do
+ URISpec.components(URI.parse("mailto:spam@mailinator.com?subject=Discounts%20On%20Imported%20methods!!!&body=Exciting%20offer")).should == {
+ scheme: "mailto",
+ to: "spam@mailinator.com",
+ headers: [["subject","Discounts%20On%20Imported%20methods!!!"],
+ ["body", "Exciting%20offer"]]
+ }
+ end
+
+ # TODO
+ # Test registry
+ it "does its best to extract components from URI::Generic objects" do
+ # generic
+ URISpec.components(URI("scheme://userinfo@host/path?query#fragment")).should == {
+ scheme: "scheme",
+ userinfo: "userinfo",
+ host: "host",
+ port: nil,
+ path: "/path",
+ query: "query",
+ fragment: "fragment",
+ registry: nil,
+ opaque: nil
+ }
+
+ # gopher
+ gopher = URI.parse('gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles')
+ gopher.should be_kind_of(URI::Generic)
+
+ URISpec.components(gopher).should == {
+ scheme: "gopher",
+ userinfo: nil,
+ host: "spinaltap.micro.umn.edu",
+ port: nil,
+ path: "/00/Weather/California/Los%20Angeles",
+ query: nil,
+ fragment: nil,
+ registry: nil,
+ opaque: nil
+ }
+
+ # news
+ news = URI.parse('news:comp.infosystems.www.servers.unix')
+ news.should be_kind_of(URI::Generic)
+ URISpec.components(news).should == {
+ scheme: "news",
+ userinfo: nil,
+ host: nil,
+ port: nil,
+ path: nil,
+ query: nil,
+ fragment: nil,
+ registry: nil,
+ opaque: "comp.infosystems.www.servers.unix"
+ }
+
+ # telnet
+ telnet = URI.parse('telnet://melvyl.ucop.edu/')
+ telnet.should be_kind_of(URI::Generic)
+ URISpec.components(telnet).should == {
+ scheme: "telnet",
+ userinfo: nil,
+ host: "melvyl.ucop.edu",
+ port: nil,
+ path: "/",
+ query: nil,
+ fragment: nil,
+ registry: nil,
+ opaque: nil
+ }
+
+ # files
+ file_l = URI.parse('file:///foo/bar.txt')
+ file_l.should be_kind_of(URI::Generic)
+ file = URI.parse('file:/foo/bar.txt')
+ file.should be_kind_of(URI::Generic)
+ end
+
+ it "doesn't raise errors on URIs which has underscore in reg_name" do
+ URI.parse('http://a_b:80/').host.should == "a_b"
+ URI.parse('http://a_b/').host.should == "a_b"
+ end
+end
diff --git a/spec/ruby/library/uri/parser/escape_spec.rb b/spec/ruby/library/uri/parser/escape_spec.rb
new file mode 100644
index 0000000000..66853d9fcb
--- /dev/null
+++ b/spec/ruby/library/uri/parser/escape_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Parser#escape" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/parser/extract_spec.rb b/spec/ruby/library/uri/parser/extract_spec.rb
new file mode 100644
index 0000000000..20d4565b08
--- /dev/null
+++ b/spec/ruby/library/uri/parser/extract_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../shared/extract'
+require 'uri'
+
+describe "URI::Parser#extract" do
+ it_behaves_like :uri_extract, :extract, URI::Parser.new
+end
diff --git a/spec/ruby/library/uri/parser/inspect_spec.rb b/spec/ruby/library/uri/parser/inspect_spec.rb
new file mode 100644
index 0000000000..44fbd4077c
--- /dev/null
+++ b/spec/ruby/library/uri/parser/inspect_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Parser#split" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/parser/join_spec.rb b/spec/ruby/library/uri/parser/join_spec.rb
new file mode 100644
index 0000000000..0c9230be76
--- /dev/null
+++ b/spec/ruby/library/uri/parser/join_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../shared/join'
+require 'uri'
+
+describe "URI::Parser#join" do
+ it_behaves_like :uri_join, :join, URI::Parser.new
+end
diff --git a/spec/ruby/library/uri/parser/make_regexp_spec.rb b/spec/ruby/library/uri/parser/make_regexp_spec.rb
new file mode 100644
index 0000000000..0631d13ee6
--- /dev/null
+++ b/spec/ruby/library/uri/parser/make_regexp_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Parser#make_regexp" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/parser/parse_spec.rb b/spec/ruby/library/uri/parser/parse_spec.rb
new file mode 100644
index 0000000000..df126eab6d
--- /dev/null
+++ b/spec/ruby/library/uri/parser/parse_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative '../shared/parse'
+
+describe "URI::Parser#parse" do
+ it_behaves_like :uri_parse, :parse, URI::Parser.new
+end
diff --git a/spec/ruby/library/uri/parser/split_spec.rb b/spec/ruby/library/uri/parser/split_spec.rb
new file mode 100644
index 0000000000..44fbd4077c
--- /dev/null
+++ b/spec/ruby/library/uri/parser/split_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Parser#split" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/parser/unescape_spec.rb b/spec/ruby/library/uri/parser/unescape_spec.rb
new file mode 100644
index 0000000000..e18d2eb9d3
--- /dev/null
+++ b/spec/ruby/library/uri/parser/unescape_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Parser#unescape" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/plus_spec.rb b/spec/ruby/library/uri/plus_spec.rb
new file mode 100644
index 0000000000..b84b0767c1
--- /dev/null
+++ b/spec/ruby/library/uri/plus_spec.rb
@@ -0,0 +1,459 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+#an alias of URI#merge
+describe "URI#+" do
+ it "replaces the end of the path of the URI when added to a string that looks like a relative path" do
+ (URI('http://foo') + 'bar').should == URI("http://foo/bar")
+ (URI('http://foo/baz') + 'bar').should == URI("http://foo/bar")
+ (URI('http://foo/baz/') + 'bar').should == URI("http://foo/baz/bar")
+ (URI('mailto:foo@example.com') + "#bar").should == URI("mailto:foo@example.com#bar")
+ end
+
+ it "replaces the entire path of the URI when added to a string that begins with a /" do
+ (URI('http://foo/baz/') + '/bar').should == URI("http://foo/bar")
+ end
+
+ it "replaces the entire url when added to a string that looks like a full url" do
+ (URI.parse('http://a/b') + 'http://x/y').should == URI("http://x/y")
+ (URI.parse('telnet:example.com') + 'http://x/y').should == URI("http://x/y")
+ end
+
+ it "canonicalizes the URI's path, removing ../'s" do
+ (URI.parse('http://a/b/c/../') + "./").should == URI("http://a/b/")
+ (URI.parse('http://a/b/c/../') + ".").should == URI("http://a/b/")
+ (URI.parse('http://a/b/c/') + "../").should == URI("http://a/b/")
+ (URI.parse('http://a/b/c/../../') + "./").should == URI("http://a/")
+ (URI.parse('http://a/b/c/') + "../e/").should == URI("http://a/b/e/")
+ (URI.parse('http://a/b/c/') + "../e/../").should == URI("http://a/b/")
+ (URI.parse('http://a/b/../c/') + ".").should == URI("http://a/c/")
+
+ (URI.parse('http://a/b/c/../../../') + ".").should == URI("http://a/")
+ end
+
+ it "doesn't canonicalize the path when adding to the empty string" do
+ (URI.parse('http://a/b/c/../') + "").should == URI("http://a/b/c/../")
+ end
+
+ it "raises a URI::BadURIError when adding two relative URIs" do
+ -> {URI.parse('a/b/c') + "d"}.should raise_error(URI::BadURIError)
+ end
+
+ #Todo: make more BDD?
+ it "conforms to the merge specifications from rfc 2396" do
+ @url = 'http://a/b/c/d;p?q'
+ @base_url = URI.parse(@url)
+
+# http://a/b/c/d;p?q
+# g:h = g:h
+ url = @base_url.merge('g:h')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g:h'
+ url = @base_url.route_to('g:h')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g:h'
+
+# http://a/b/c/d;p?q
+# g = http://a/b/c/g
+ url = @base_url.merge('g')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g'
+ url = @base_url.route_to('http://a/b/c/g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g'
+
+# http://a/b/c/d;p?q
+# ./g = http://a/b/c/g
+ url = @base_url.merge('./g')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g'
+ url = @base_url.route_to('http://a/b/c/g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == './g' # ok
+ url.to_s.should == 'g'
+
+# http://a/b/c/d;p?q
+# g/ = http://a/b/c/g/
+ url = @base_url.merge('g/')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g/'
+ url = @base_url.route_to('http://a/b/c/g/')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g/'
+
+# http://a/b/c/d;p?q
+# /g = http://a/g
+ url = @base_url.merge('/g')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/g'
+ url = @base_url.route_to('http://a/g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == '/g' # ok
+ url.to_s.should == '../../g'
+
+# http://a/b/c/d;p?q
+# //g = http://g
+ url = @base_url.merge('//g')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://g'
+ url = @base_url.route_to('http://g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '//g'
+
+# http://a/b/c/d;p?q
+# ?y = http://a/b/c/?y
+ url = @base_url.merge('?y')
+ url.should be_kind_of(URI::HTTP)
+
+ url.to_s.should == 'http://a/b/c/d;p?y'
+
+ url = @base_url.route_to('http://a/b/c/?y')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '?y'
+
+# http://a/b/c/d;p?q
+# g?y = http://a/b/c/g?y
+ url = @base_url.merge('g?y')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g?y'
+ url = @base_url.route_to('http://a/b/c/g?y')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g?y'
+
+# http://a/b/c/d;p?q
+# #s = (current document)#s
+ url = @base_url.merge('#s')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == @base_url.to_s + '#s'
+ url = @base_url.route_to(@base_url.to_s + '#s')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '#s'
+
+# http://a/b/c/d;p?q
+# g#s = http://a/b/c/g#s
+ url = @base_url.merge('g#s')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g#s'
+ url = @base_url.route_to('http://a/b/c/g#s')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g#s'
+
+# http://a/b/c/d;p?q
+# g?y#s = http://a/b/c/g?y#s
+ url = @base_url.merge('g?y#s')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g?y#s'
+ url = @base_url.route_to('http://a/b/c/g?y#s')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g?y#s'
+
+# http://a/b/c/d;p?q
+# ;x = http://a/b/c/;x
+ url = @base_url.merge(';x')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/;x'
+ url = @base_url.route_to('http://a/b/c/;x')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == ';x'
+
+# http://a/b/c/d;p?q
+# g;x = http://a/b/c/g;x
+ url = @base_url.merge('g;x')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g;x'
+ url = @base_url.route_to('http://a/b/c/g;x')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g;x'
+
+# http://a/b/c/d;p?q
+# g;x?y#s = http://a/b/c/g;x?y#s
+ url = @base_url.merge('g;x?y#s')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g;x?y#s'
+ url = @base_url.route_to('http://a/b/c/g;x?y#s')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g;x?y#s'
+
+# http://a/b/c/d;p?q
+# . = http://a/b/c/
+ url = @base_url.merge('.')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/'
+ url = @base_url.route_to('http://a/b/c/')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == '.' # ok
+ url.to_s.should == './'
+
+# http://a/b/c/d;p?q
+# ./ = http://a/b/c/
+ url = @base_url.merge('./')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/'
+ url = @base_url.route_to('http://a/b/c/')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == './'
+
+# http://a/b/c/d;p?q
+# .. = http://a/b/
+ url = @base_url.merge('..')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/'
+ url = @base_url.route_to('http://a/b/')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == '..' # ok
+ url.to_s.should == '../'
+
+# http://a/b/c/d;p?q
+# ../ = http://a/b/
+ url = @base_url.merge('../')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/'
+ url = @base_url.route_to('http://a/b/')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '../'
+
+# http://a/b/c/d;p?q
+# ../g = http://a/b/g
+ url = @base_url.merge('../g')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/g'
+ url = @base_url.route_to('http://a/b/g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '../g'
+
+# http://a/b/c/d;p?q
+# ../.. = http://a/
+ url = @base_url.merge('../..')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/'
+ url = @base_url.route_to('http://a/')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == '../..' # ok
+ url.to_s.should == '../../'
+
+# http://a/b/c/d;p?q
+# ../../ = http://a/
+ url = @base_url.merge('../../')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/'
+ url = @base_url.route_to('http://a/')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '../../'
+
+# http://a/b/c/d;p?q
+# ../../g = http://a/g
+ url = @base_url.merge('../../g')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/g'
+ url = @base_url.route_to('http://a/g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '../../g'
+
+# http://a/b/c/d;p?q
+# <> = (current document)
+ url = @base_url.merge('')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/d;p?q'
+ url = @base_url.route_to('http://a/b/c/d;p?q')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == ''
+
+# http://a/b/c/d;p?q
+# /./g = http://a/./g
+ url = @base_url.merge('/./g')
+ url.should be_kind_of(URI::HTTP)
+
+ url.to_s.should == 'http://a/g'
+
+ url = @base_url.route_to('http://a/./g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '/./g'
+
+# http://a/b/c/d;p?q
+# /../g = http://a/../g
+ url = @base_url.merge('/../g')
+ url.should be_kind_of(URI::HTTP)
+
+ url.to_s.should == 'http://a/g'
+
+ url = @base_url.route_to('http://a/../g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '/../g'
+
+# http://a/b/c/d;p?q
+# g. = http://a/b/c/g.
+ url = @base_url.merge('g.')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g.'
+ url = @base_url.route_to('http://a/b/c/g.')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g.'
+
+# http://a/b/c/d;p?q
+# .g = http://a/b/c/.g
+ url = @base_url.merge('.g')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/.g'
+ url = @base_url.route_to('http://a/b/c/.g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '.g'
+
+# http://a/b/c/d;p?q
+# g.. = http://a/b/c/g..
+ url = @base_url.merge('g..')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g..'
+ url = @base_url.route_to('http://a/b/c/g..')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g..'
+
+# http://a/b/c/d;p?q
+# ..g = http://a/b/c/..g
+ url = @base_url.merge('..g')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/..g'
+ url = @base_url.route_to('http://a/b/c/..g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == '..g'
+
+# http://a/b/c/d;p?q
+# ../../../g = http://a/../g
+ url = @base_url.merge('../../../g')
+ url.should be_kind_of(URI::HTTP)
+
+ url.to_s.should == 'http://a/g'
+
+ url = @base_url.route_to('http://a/../g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == '../../../g' # ok? yes, it confuses you
+ url.to_s.should == '/../g' # and it is clearly
+
+# http://a/b/c/d;p?q
+# ../../../../g = http://a/../../g
+ url = @base_url.merge('../../../../g')
+ url.should be_kind_of(URI::HTTP)
+
+ url.to_s.should == 'http://a/g'
+
+ url = @base_url.route_to('http://a/../../g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == '../../../../g' # ok? yes, it confuses you
+ url.to_s.should == '/../../g' # and it is clearly
+
+# http://a/b/c/d;p?q
+# ./../g = http://a/b/g
+ url = @base_url.merge('./../g')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/g'
+ url = @base_url.route_to('http://a/b/g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == './../g' # ok
+ url.to_s.should == '../g'
+
+# http://a/b/c/d;p?q
+# ./g/. = http://a/b/c/g/
+ url = @base_url.merge('./g/.')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g/'
+ url = @base_url.route_to('http://a/b/c/g/')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == './g/.' # ok
+ url.to_s.should == 'g/'
+
+# http://a/b/c/d;p?q
+# g/./h = http://a/b/c/g/h
+ url = @base_url.merge('g/./h')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g/h'
+ url = @base_url.route_to('http://a/b/c/g/h')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == 'g/./h' # ok
+ url.to_s.should == 'g/h'
+
+# http://a/b/c/d;p?q
+# g/../h = http://a/b/c/h
+ url = @base_url.merge('g/../h')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/h'
+ url = @base_url.route_to('http://a/b/c/h')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == 'g/../h' # ok
+ url.to_s.should == 'h'
+
+# http://a/b/c/d;p?q
+# g;x=1/./y = http://a/b/c/g;x=1/y
+ url = @base_url.merge('g;x=1/./y')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g;x=1/y'
+ url = @base_url.route_to('http://a/b/c/g;x=1/y')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == 'g;x=1/./y' # ok
+ url.to_s.should == 'g;x=1/y'
+
+# http://a/b/c/d;p?q
+# g;x=1/../y = http://a/b/c/y
+ url = @base_url.merge('g;x=1/../y')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/y'
+ url = @base_url.route_to('http://a/b/c/y')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should_not == 'g;x=1/../y' # ok
+ url.to_s.should == 'y'
+
+# http://a/b/c/d;p?q
+# g?y/./x = http://a/b/c/g?y/./x
+ url = @base_url.merge('g?y/./x')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g?y/./x'
+ url = @base_url.route_to('http://a/b/c/g?y/./x')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g?y/./x'
+
+# http://a/b/c/d;p?q
+# g?y/../x = http://a/b/c/g?y/../x
+ url = @base_url.merge('g?y/../x')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g?y/../x'
+ url = @base_url.route_to('http://a/b/c/g?y/../x')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g?y/../x'
+
+# http://a/b/c/d;p?q
+# g#s/./x = http://a/b/c/g#s/./x
+ url = @base_url.merge('g#s/./x')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g#s/./x'
+ url = @base_url.route_to('http://a/b/c/g#s/./x')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g#s/./x'
+
+# http://a/b/c/d;p?q
+# g#s/../x = http://a/b/c/g#s/../x
+ url = @base_url.merge('g#s/../x')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http://a/b/c/g#s/../x'
+ url = @base_url.route_to('http://a/b/c/g#s/../x')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'g#s/../x'
+
+# http://a/b/c/d;p?q
+# http:g = http:g ; for validating parsers
+# | http://a/b/c/g ; for backwards compatibility
+ url = @base_url.merge('http:g')
+ url.should be_kind_of(URI::HTTP)
+ url.to_s.should == 'http:g'
+ url = @base_url.route_to('http:g')
+ url.should be_kind_of(URI::Generic)
+ url.to_s.should == 'http:g'
+ end
+end
+
+#TODO: incorporate these tests:
+#
+# u = URI.parse('http://foo/bar/baz')
+# assert_equal(nil, u.merge!(""))
+# assert_equal(nil, u.merge!(u))
+# assert(nil != u.merge!("."))
+# assert_equal('http://foo/bar/', u.to_s)
+# assert(nil != u.merge!("../baz"))
+# assert_equal('http://foo/baz', u.to_s)
diff --git a/spec/ruby/library/uri/regexp_spec.rb b/spec/ruby/library/uri/regexp_spec.rb
new file mode 100644
index 0000000000..6e8b3df4d0
--- /dev/null
+++ b/spec/ruby/library/uri/regexp_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+#I'm more or less ok with these limited tests, as the more extensive extract tests
+#use URI.regexp
+describe "URI.regexp" do
+ it "behaves according to the MatzRuby tests" do
+ URI.regexp.should == URI.regexp
+ 'x http:// x'.slice(URI.regexp).should == 'http://'
+ 'x http:// x'.slice(URI.regexp(['http'])).should == 'http://'
+ 'x http:// x ftp://'.slice(URI.regexp(['http'])).should == 'http://'
+ 'http://'.slice(URI.regexp([])).should == nil
+ ''.slice(URI.regexp).should == nil
+ 'xxxx'.slice(URI.regexp).should == nil
+ ':'.slice(URI.regexp).should == nil
+ 'From:'.slice(URI.regexp).should == 'From:'
+ end
+end
diff --git a/spec/ruby/library/uri/route_from_spec.rb b/spec/ruby/library/uri/route_from_spec.rb
new file mode 100644
index 0000000000..501f455775
--- /dev/null
+++ b/spec/ruby/library/uri/route_from_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI#route_from" do
+
+ #this could be split out a good bit better
+ it "gives the minimal difference between the current URI and the target" do
+ URI("http://example.com/a.html").route_from('http://example.com/a.html').to_s.should == ""
+ URI("http://example.com/a.html").route_from('http://example.com/b.html').to_s.should == "a.html"
+ URI("http://example.com/a/").route_from('http://example.com/b/').to_s.should == "../a/"
+ URI("http://example.com/b/").route_from('http://example.com/a/c').to_s.should == "../b/"
+ URI("http://example.com/b/").route_from('http://example.com/a/b/').to_s.should == "../../b/"
+ URI("http://example.com/b/").route_from('http://EXAMPLE.cOm/a/b/').to_s.should == "../../b/"
+ URI("http://example.net/b/").route_from('http://example.com/a/b/').to_s.should == "//example.net/b/"
+ URI("mailto:foo@example.com#bar").route_from('mailto:foo@example.com').to_s.should == "#bar"
+ end
+
+ it "accepts a string-like argument" do
+ str = mock('string-like')
+ str.should_receive(:to_str).and_return("http://example.com/b.html")
+ URI("http://example.com/a.html").route_from(str).to_s.should == "a.html"
+ end
+end
diff --git a/spec/ruby/library/uri/route_to_spec.rb b/spec/ruby/library/uri/route_to_spec.rb
new file mode 100644
index 0000000000..ae9d38d23d
--- /dev/null
+++ b/spec/ruby/library/uri/route_to_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI#route_to" do
+
+ #this could be split out a good bit better
+ it "gives the minimal difference between the current URI and the target" do
+ URI("http://example.com/a.html").route_to('http://example.com/a.html').to_s.should == ""
+ URI("http://example.com/a.html").route_to('http://example.com/b.html').to_s.should == "b.html"
+ URI("http://example.com/a/").route_to('http://example.com/b/').to_s.should == "../b/"
+ URI("http://example.com/a/c").route_to('http://example.com/b/').to_s.should == "../b/"
+ URI("http://example.com/a/b/").route_to('http://example.com/b/').to_s.should == "../../b/"
+ URI("http://example.com/a/b/").route_to('http://EXAMPLE.cOm/b/').to_s.should == "../../b/"
+ URI("http://example.com/a/b/").route_to('http://example.net/b/').to_s.should == "//example.net/b/"
+ URI("mailto:foo@example.com").route_to('mailto:foo@example.com#bar').to_s.should == "#bar"
+
+ #this was a little surprising to me
+ URI("mailto:foo@example.com#bar").route_to('mailto:foo@example.com').to_s.should == ""
+ end
+
+ it "accepts a string-like argument" do
+ str = mock('string-like')
+ str.should_receive(:to_str).and_return("http://example.com/b.html")
+ URI("http://example.com/a.html").route_to(str).to_s.should == "b.html"
+ end
+end
diff --git a/spec/ruby/library/uri/select_spec.rb b/spec/ruby/library/uri/select_spec.rb
new file mode 100644
index 0000000000..839b68b3a1
--- /dev/null
+++ b/spec/ruby/library/uri/select_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI#select" do
+ it "takes any number of component names as symbols, and returns an array of those components" do
+ URI("http://host:8080/path/").select.should == []
+ URI("http://host:8080/path/").select(:scheme,:host,:port,:path).should == [
+ "http","host",8080,"/path/"]
+ end
+
+ it "returns nil for any valid component that isn't set and doesn't have a default" do
+ uri = URI("http://host")
+ uri.select(:userinfo, :query, :fragment).should == [nil] * 3
+ uri.select(:port, :path).should == [80, '']
+ end
+
+ it "raises an ArgumentError if a component is requested that isn't valid under the given scheme" do
+ -> { URI("mailto:spam@mailinator.com").select(:path) }.should raise_error(ArgumentError)
+ -> { URI("http://blog.blag.web").select(:typecode) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if given strings rather than symbols" do
+ -> {
+ URI("http://host:8080/path/").select("scheme","host","port",'path')
+ }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/library/uri/set_component_spec.rb b/spec/ruby/library/uri/set_component_spec.rb
new file mode 100644
index 0000000000..642a5d6fcf
--- /dev/null
+++ b/spec/ruby/library/uri/set_component_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+#TODO: make this more BDD
+describe "URI#select" do
+ it "conforms to the MatzRuby tests" do
+ uri = URI.parse('http://foo:bar@baz')
+ (uri.user = 'oof').should == 'oof'
+ uri.to_s.should == 'http://oof:bar@baz'
+ (uri.password = 'rab').should == 'rab'
+ uri.to_s.should == 'http://oof:rab@baz'
+ (uri.userinfo = 'foo').should == 'foo'
+ uri.to_s.should == 'http://foo:rab@baz'
+ (uri.userinfo = ['foo', 'bar']).should == ['foo', 'bar']
+ uri.to_s.should == 'http://foo:bar@baz'
+ (uri.userinfo = ['foo']).should == ['foo']
+ uri.to_s.should == 'http://foo:bar@baz'
+ (uri.host = 'zab').should == 'zab'
+ uri.to_s.should == 'http://foo:bar@zab'
+ (uri.port = 8080).should == 8080
+ uri.to_s.should == 'http://foo:bar@zab:8080'
+ (uri.path = '/').should == '/'
+ uri.to_s.should == 'http://foo:bar@zab:8080/'
+ (uri.query = 'a=1').should == 'a=1'
+ uri.to_s.should == 'http://foo:bar@zab:8080/?a=1'
+ (uri.fragment = 'b123').should == 'b123'
+ uri.to_s.should == 'http://foo:bar@zab:8080/?a=1#b123'
+
+ uri = URI.parse('http://example.com')
+ -> { uri.password = 'bar' }.should raise_error(URI::InvalidURIError)
+ uri.userinfo = 'foo:bar'
+ uri.to_s.should == 'http://foo:bar@example.com'
+ -> { uri.registry = 'bar' }.should raise_error(URI::InvalidURIError)
+ -> { uri.opaque = 'bar' }.should raise_error(URI::InvalidURIError)
+
+ uri = URI.parse('mailto:foo@example.com')
+ -> { uri.user = 'bar' }.should raise_error(URI::InvalidURIError)
+ -> { uri.password = 'bar' }.should raise_error(URI::InvalidURIError)
+ -> { uri.userinfo = ['bar', 'baz'] }.should raise_error(URI::InvalidURIError)
+ -> { uri.host = 'bar' }.should raise_error(URI::InvalidURIError)
+ -> { uri.port = 'bar' }.should raise_error(URI::InvalidURIError)
+ -> { uri.path = 'bar' }.should raise_error(URI::InvalidURIError)
+ -> { uri.query = 'bar' }.should raise_error(URI::InvalidURIError)
+ end
+end
diff --git a/spec/ruby/library/uri/shared/eql.rb b/spec/ruby/library/uri/shared/eql.rb
new file mode 100644
index 0000000000..2cc960d39a
--- /dev/null
+++ b/spec/ruby/library/uri/shared/eql.rb
@@ -0,0 +1,17 @@
+describe :uri_eql, shared: true do
+ it "returns false if the normalized forms are different" do
+ URISpec::NORMALIZED_FORMS.each do |form|
+ normal_uri = URI(form[:normalized])
+ form[:different].each do |other|
+ URI(other).send(@method, normal_uri).should be_false
+ end
+ end
+ end
+end
+
+describe :uri_eql_against_other_types, shared: true do
+ it "returns false for when compared to non-uri objects" do
+ URI("http://example.com/").send(@method, "http://example.com/").should be_false
+ URI("http://example.com/").send(@method, nil).should be_false
+ end
+end
diff --git a/spec/ruby/library/uri/shared/extract.rb b/spec/ruby/library/uri/shared/extract.rb
new file mode 100644
index 0000000000..efe60ae4b9
--- /dev/null
+++ b/spec/ruby/library/uri/shared/extract.rb
@@ -0,0 +1,83 @@
+describe :uri_extract, shared: true do
+ it "behaves according to its documentation" do
+ @object.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.").should == ["http://foo.example.org/bla", "mailto:test@example.com"]
+ end
+
+ it "treats contiguous URIs as a single URI" do
+ @object.extract('http://example.jphttp://example.jp').should == ['http://example.jphttp://example.jp']
+ end
+
+ it "treats pretty much anything with a colon as a URI" do
+ @object.extract('From: XXX [mailto:xxx@xxx.xxx.xxx]').should == ['From:', 'mailto:xxx@xxx.xxx.xxx]']
+ end
+
+ it "wraps a URI string in an array" do
+ @object.extract("http://github.com/brixen/rubyspec/tree/master").should == ["http://github.com/brixen/rubyspec/tree/master"]
+ end
+
+ it "pulls a variety of protocol URIs from a string" do
+ @object.extract("this is a string, it has http://rubini.us/ in it").should == ["http://rubini.us/"]
+ @object.extract("mailto:spambait@example.com").should == ["mailto:spambait@example.com"]
+ @object.extract("ftp://ruby-lang.org/").should == ["ftp://ruby-lang.org/"]
+ @object.extract("https://mail.google.com").should == ["https://mail.google.com"]
+ @object.extract("anything://example.com/").should == ["anything://example.com/"]
+ end
+
+ it "pulls all URIs within a string in order into an array when a block is not given" do
+ @object.extract("1.3. Example URI
+
+ The following examples illustrate URI that are in common use.
+
+ ftp://ftp.is.co.za/rfc/rfc1808.txt
+ -- ftp scheme for File Transfer Protocol services
+
+ gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles
+ -- gopher scheme for Gopher and Gopher+ Protocol services
+
+ http://www.math.uio.no/faq/compression-faq/part1.html
+ -- http scheme for Hypertext Transfer Protocol services
+
+ mailto:mduerst@ifi.unizh.ch
+ -- mailto scheme for electronic mail addresses
+
+ news:comp.infosystems.www.servers.unix
+ -- news scheme for USENET news groups and articles
+
+ telnet://melvyl.ucop.edu/
+ -- telnet scheme for interactive services via the TELNET Protocol
+ ").should == ["ftp://ftp.is.co.za/rfc/rfc1808.txt","gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles","http://www.math.uio.no/faq/compression-faq/part1.html","mailto:mduerst@ifi.unizh.ch","news:comp.infosystems.www.servers.unix","telnet://melvyl.ucop.edu/"]
+ end
+
+ it "yields each URI in the given string in order to a block, if given, and returns nil" do
+ results = ["http://foo.example.org/bla", "mailto:test@example.com"]
+ @object.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.") {|uri|
+ uri.should == results.shift
+ }.should == nil
+ results.should == []
+ end
+
+ it "allows the user to specify a list of acceptable protocols of URIs to scan for" do
+ @object.extract("1.3. Example URI
+
+ The following examples illustrate URI that are in common use.
+
+ ftp://ftp.is.co.za/rfc/rfc1808.txt
+ -- ftp scheme for File Transfer Protocol services
+
+ gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles
+ -- gopher scheme for Gopher and Gopher+ Protocol services
+
+ http://www.math.uio.no/faq/compression-faq/part1.html
+ -- http scheme for Hypertext Transfer Protocol services
+
+ mailto:mduerst@ifi.unizh.ch
+ -- mailto scheme for electronic mail addresses
+
+ news:comp.infosystems.www.servers.unix
+ -- news scheme for USENET news groups and articles
+
+ telnet://melvyl.ucop.edu/
+ -- telnet scheme for interactive services via the TELNET Protocol
+ ", ["http","ftp","mailto"]).should == ["ftp://ftp.is.co.za/rfc/rfc1808.txt","http://www.math.uio.no/faq/compression-faq/part1.html","mailto:mduerst@ifi.unizh.ch"]
+ end
+end
diff --git a/spec/ruby/library/uri/shared/join.rb b/spec/ruby/library/uri/shared/join.rb
new file mode 100644
index 0000000000..4df0782b37
--- /dev/null
+++ b/spec/ruby/library/uri/shared/join.rb
@@ -0,0 +1,56 @@
+describe :uri_join, shared: true do
+ it "returns a URI object of the concatenation of a protocol and domain, and a path" do
+ @object.join("http://localhost/","main.rbx").should == URI.parse("http://localhost/main.rbx")
+ end
+
+ it "accepts URI objects" do
+ @object.join(URI("http://localhost/"),"main.rbx").should == URI.parse("http://localhost/main.rbx")
+ @object.join("http://localhost/",URI("main.rbx")).should == URI.parse("http://localhost/main.rbx")
+ @object.join(URI("http://localhost/"),URI("main.rbx")).should == URI.parse("http://localhost/main.rbx")
+ end
+
+ it "accepts string-like arguments with to_str" do
+ str = mock('string-like')
+ str.should_receive(:to_str).and_return("http://ruby-lang.org")
+ str2 = mock('string-like also')
+ str2.should_receive(:to_str).and_return("foo/bar")
+ @object.join(str, str2).should == URI.parse("http://ruby-lang.org/foo/bar")
+ end
+
+ it "raises an error if given no argument" do
+ -> {
+ @object.join
+ }.should raise_error(ArgumentError)
+ end
+
+ it "doesn't create redundant '/'s" do
+ @object.join("http://localhost/", "/main.rbx").should == URI.parse("http://localhost/main.rbx")
+ end
+
+ it "discards arguments given before an absolute uri" do
+ @object.join("http://localhost/a/b/c/d", "http://ruby-lang.com/foo", "bar").should == URI.parse("http://ruby-lang.com/bar")
+ end
+
+ it "resolves .. in paths" do
+ @object.join("http://localhost/a/b/c/d", "../../e/f", "g/h/../i").to_s.should == "http://localhost/a/e/g/i"
+ end
+end
+
+
+# assert_equal(URI.parse('http://foo/bar'), URI.join('http://foo/bar'))
+# assert_equal(URI.parse('http://foo/bar'), URI.join('http://foo', 'bar'))
+# assert_equal(URI.parse('http://foo/bar/'), URI.join('http://foo', 'bar/'))
+#
+# assert_equal(URI.parse('http://foo/baz'), URI.join('http://foo', 'bar', 'baz'))
+# assert_equal(URI.parse('http://foo/baz'), URI.join('http://foo', 'bar', '/baz'))
+# assert_equal(URI.parse('http://foo/baz/'), URI.join('http://foo', 'bar', '/baz/'))
+# assert_equal(URI.parse('http://foo/bar/baz'), URI.join('http://foo', 'bar/', 'baz'))
+# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar', 'baz', 'hoge'))
+#
+# assert_equal(URI.parse('http://foo/bar/baz'), URI.join('http://foo', 'bar/baz'))
+# assert_equal(URI.parse('http://foo/bar/hoge'), URI.join('http://foo', 'bar/baz', 'hoge'))
+# assert_equal(URI.parse('http://foo/bar/baz/hoge'), URI.join('http://foo', 'bar/baz/', 'hoge'))
+# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar/baz', '/hoge'))
+# assert_equal(URI.parse('http://foo/bar/hoge'), URI.join('http://foo', 'bar/baz', 'hoge'))
+# assert_equal(URI.parse('http://foo/bar/baz/hoge'), URI.join('http://foo', 'bar/baz/', 'hoge'))
+# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar/baz', '/hoge'))
diff --git a/spec/ruby/library/uri/shared/parse.rb b/spec/ruby/library/uri/shared/parse.rb
new file mode 100644
index 0000000000..87e1ee933e
--- /dev/null
+++ b/spec/ruby/library/uri/shared/parse.rb
@@ -0,0 +1,199 @@
+describe :uri_parse, shared: true do
+ it "returns a URI::HTTP object when parsing an HTTP URI" do
+ @object.parse("http://www.example.com/").should be_kind_of(URI::HTTP)
+ end
+
+ it "populates the components of a parsed URI::HTTP, setting the port to 80 by default" do
+ # general case
+ URISpec.components(@object.parse("http://user:pass@example.com/path/?query=val&q2=val2#fragment")).should == {
+ scheme: "http",
+ userinfo: "user:pass",
+ host: "example.com",
+ port: 80,
+ path: "/path/",
+ query: "query=val&q2=val2",
+ fragment: "fragment"
+ }
+
+ # multiple paths
+ URISpec.components(@object.parse("http://a/b/c/d;p?q")).should == {
+ scheme: "http",
+ userinfo: nil,
+ host: "a",
+ port: 80,
+ path: "/b/c/d;p",
+ query: "q",
+ fragment: nil
+ }
+
+ # multi-level domain
+ URISpec.components(@object.parse('http://www.math.uio.no/faq/compression-faq/part1.html')).should == {
+ scheme: "http",
+ userinfo: nil,
+ host: "www.math.uio.no",
+ port: 80,
+ path: "/faq/compression-faq/part1.html",
+ query: nil,
+ fragment: nil
+ }
+ end
+
+ it "parses out the port number of a URI, when given" do
+ @object.parse("http://example.com:8080/").port.should == 8080
+ end
+
+ it "returns a URI::HTTPS object when parsing an HTTPS URI" do
+ @object.parse("https://important-intern-net.net").should be_kind_of(URI::HTTPS)
+ end
+
+ it "sets the port of a parsed https URI to 443 by default" do
+ @object.parse("https://example.com/").port.should == 443
+ end
+
+ it "populates the components of a parsed URI::FTP object" do
+ # generic, empty password.
+ url = @object.parse("ftp://anonymous@ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.bz2;type=i")
+ url.should be_kind_of(URI::FTP)
+ URISpec.components(url).should == {
+ scheme: "ftp",
+ userinfo: "anonymous",
+ host: "ruby-lang.org",
+ port: 21,
+ path: "pub/ruby/1.8/ruby-1.8.6.tar.bz2",
+ typecode: "i"
+ }
+
+ # multidomain, no user or password
+ url = @object.parse('ftp://ftp.is.co.za/rfc/rfc1808.txt')
+ url.should be_kind_of(URI::FTP)
+ URISpec.components(url).should == {
+ scheme: "ftp",
+ userinfo: nil,
+ host: "ftp.is.co.za",
+ port: 21,
+ path: "rfc/rfc1808.txt",
+ typecode: nil
+ }
+
+ # empty user
+ url = @object.parse('ftp://:pass@localhost/')
+ url.should be_kind_of(URI::FTP)
+ URISpec.components(url).should == {
+ scheme: "ftp",
+ userinfo: ":pass",
+ host: "localhost",
+ port: 21,
+ path: "",
+ typecode: nil
+ }
+ url.password.should == "pass"
+ end
+
+ it "returns a URI::LDAP object when parsing an LDAP URI" do
+ #taken from http://www.faqs.org/rfcs/rfc2255.html 'cause I don't really know what an LDAP url looks like
+ ldap_uris = %w{ ldap:///o=University%20of%20Michigan,c=US ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress ldap://host.com:6666/o=University%20of%20Michigan,c=US??sub?(cn=Babs%20Jensen) ldap://ldap.itd.umich.edu/c=GB?objectClass?one ldap://ldap.question.com/o=Question%3f,c=US?mail ldap://ldap.netscape.com/o=Babsco,c=US??(int=%5c00%5c00%5c00%5c04) ldap:///??sub??bindname=cn=Manager%2co=Foo ldap:///??sub??!bindname=cn=Manager%2co=Foo }
+ ldap_uris.each do |ldap_uri|
+ @object.parse(ldap_uri).should be_kind_of(URI::LDAP)
+ end
+ end
+
+ it "populates the components of a parsed URI::LDAP object" do
+ URISpec.components(@object.parse("ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress?scope?filter?extensions")).should == {
+ scheme: "ldap",
+ host: "ldap.itd.umich.edu",
+ port: 389,
+ dn: "o=University%20of%20Michigan,c=US",
+ attributes: "postalAddress",
+ scope: "scope",
+ filter: "filter",
+ extensions: "extensions"
+ }
+ end
+
+ it "returns a URI::MailTo object when passed a mailto URI" do
+ @object.parse("mailto:spam@mailinator.com").should be_kind_of(URI::MailTo)
+ end
+
+ it "populates the components of a parsed URI::MailTo object" do
+ URISpec.components(@object.parse("mailto:spam@mailinator.com?subject=Discounts%20On%20Imported%20methods!!!&body=Exciting%20offer")).should == {
+ scheme: "mailto",
+ to: "spam@mailinator.com",
+ headers: [["subject","Discounts%20On%20Imported%20methods!!!"],
+ ["body", "Exciting%20offer"]]
+ }
+ end
+
+ # TODO
+ # Test registry
+ it "does its best to extract components from URI::Generic objects" do
+ # generic
+ URISpec.components(URI("scheme://userinfo@host/path?query#fragment")).should == {
+ scheme: "scheme",
+ userinfo: "userinfo",
+ host: "host",
+ port: nil,
+ path: "/path",
+ query: "query",
+ fragment: "fragment",
+ registry: nil,
+ opaque: nil
+ }
+
+ # gopher
+ gopher = @object.parse('gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles')
+ gopher.should be_kind_of(URI::Generic)
+
+ URISpec.components(gopher).should == {
+ scheme: "gopher",
+ userinfo: nil,
+ host: "spinaltap.micro.umn.edu",
+ port: nil,
+ path: "/00/Weather/California/Los%20Angeles",
+ query: nil,
+ fragment: nil,
+ registry: nil,
+ opaque: nil
+ }
+
+ # news
+ news = @object.parse('news:comp.infosystems.www.servers.unix')
+ news.should be_kind_of(URI::Generic)
+ URISpec.components(news).should == {
+ scheme: "news",
+ userinfo: nil,
+ host: nil,
+ port: nil,
+ path: nil,
+ query: nil,
+ fragment: nil,
+ registry: nil,
+ opaque: "comp.infosystems.www.servers.unix"
+ }
+
+ # telnet
+ telnet = @object.parse('telnet://melvyl.ucop.edu/')
+ telnet.should be_kind_of(URI::Generic)
+ URISpec.components(telnet).should == {
+ scheme: "telnet",
+ userinfo: nil,
+ host: "melvyl.ucop.edu",
+ port: nil,
+ path: "/",
+ query: nil,
+ fragment: nil,
+ registry: nil,
+ opaque: nil
+ }
+
+ # files
+ file_l = @object.parse('file:///foo/bar.txt')
+ file_l.should be_kind_of(URI::Generic)
+ file = @object.parse('file:/foo/bar.txt')
+ file.should be_kind_of(URI::Generic)
+ end
+
+ it "raises errors on malformed URIs" do
+ -> { @object.parse('http://a_b:80/') }.should raise_error(URI::InvalidURIError)
+ -> { @object.parse('http://a_b/') }.should raise_error(URI::InvalidURIError)
+ end
+end
diff --git a/spec/ruby/library/uri/split_spec.rb b/spec/ruby/library/uri/split_spec.rb
new file mode 100644
index 0000000000..9ad37e3b1f
--- /dev/null
+++ b/spec/ruby/library/uri/split_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+describe "URI.split" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/uri/uri_spec.rb b/spec/ruby/library/uri/uri_spec.rb
new file mode 100644
index 0000000000..45a7502052
--- /dev/null
+++ b/spec/ruby/library/uri/uri_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require 'uri'
+
+#the testing is light here as this is an alias for URI.parse
+
+#we're just testing that the method ends up in the right place
+describe "the URI method" do
+ it "parses a given URI, returning a URI object" do
+ result = URI.parse("http://ruby-lang.org")
+ URI("http://ruby-lang.org").should == result
+ Kernel::URI("http://ruby-lang.org").should == result
+ end
+
+ it "converts its argument with to_str" do
+ str = mock('string-like')
+ str.should_receive(:to_str).and_return("http://ruby-lang.org")
+ URI(str).should == URI.parse("http://ruby-lang.org")
+ end
+
+ it "returns the argument if it is a URI object" do
+ result = URI.parse("http://ruby-lang.org")
+ URI(result).should equal(result)
+ end
+
+ #apparently this was a concern? imported from MRI tests
+ it "does not add a URI method to Object instances" do
+ -> {Object.new.URI("http://ruby-lang.org/")}.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/library/uri/util/make_components_hash_spec.rb b/spec/ruby/library/uri/util/make_components_hash_spec.rb
new file mode 100644
index 0000000000..6d26b81130
--- /dev/null
+++ b/spec/ruby/library/uri/util/make_components_hash_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require 'uri'
+
+describe "URI::Util.make_components_hash" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/library/weakref/__getobj___spec.rb b/spec/ruby/library/weakref/__getobj___spec.rb
new file mode 100644
index 0000000000..79b06f5c96
--- /dev/null
+++ b/spec/ruby/library/weakref/__getobj___spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "WeakRef#__getobj__" do
+ it "returns the object if it is reachable" do
+ obj = Object.new
+ ref = WeakRef.new(obj)
+ ref.__getobj__.should equal(obj)
+ end
+
+ it "raises WeakRef::RefError if the object is no longer reachable" do
+ ref = WeakRefSpec.make_dead_weakref
+ -> {
+ ref.__getobj__
+ }.should raise_error(WeakRef::RefError)
+ end
+end
diff --git a/spec/ruby/library/weakref/allocate_spec.rb b/spec/ruby/library/weakref/allocate_spec.rb
new file mode 100644
index 0000000000..e734cfd23d
--- /dev/null
+++ b/spec/ruby/library/weakref/allocate_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'weakref'
+
+describe "WeakRef#allocate" do
+ it "assigns nil as the reference" do
+ -> { WeakRef.allocate.__getobj__ }.should raise_error(WeakRef::RefError)
+ end
+end
diff --git a/spec/ruby/library/weakref/fixtures/classes.rb b/spec/ruby/library/weakref/fixtures/classes.rb
new file mode 100644
index 0000000000..041afab14d
--- /dev/null
+++ b/spec/ruby/library/weakref/fixtures/classes.rb
@@ -0,0 +1,26 @@
+require 'weakref'
+
+# From MRI test_weakref.rb
+class WeakRefSpec
+ def self.make_weakref(level = 10)
+ if level > 0
+ make_weakref(level - 1)
+ else
+ WeakRef.new(Object.new)
+ end
+ end
+
+ def self.make_dead_weakref
+ weaks = []
+ weak = nil
+ 1000.times do
+ weaks << make_weakref
+ end
+
+ 1000.times do
+ GC.start
+ break if weak = weaks.find { |w| !w.weakref_alive? }
+ end
+ weak
+ end
+end
diff --git a/spec/ruby/library/weakref/new_spec.rb b/spec/ruby/library/weakref/new_spec.rb
new file mode 100644
index 0000000000..6290e61fe3
--- /dev/null
+++ b/spec/ruby/library/weakref/new_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require 'weakref'
+
+describe "WeakRef#new" do
+ it "creates a subclass correctly" do
+ wr2 = Class.new(WeakRef) {
+ def __getobj__
+ :dummy
+ end
+ }
+ wr2.new(Object.new).__getobj__.should == :dummy
+ end
+end
diff --git a/spec/ruby/library/weakref/send_spec.rb b/spec/ruby/library/weakref/send_spec.rb
new file mode 100644
index 0000000000..9591657e01
--- /dev/null
+++ b/spec/ruby/library/weakref/send_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require 'weakref'
+
+describe "WeakRef#__send__" do
+ module WeakRefSpecs
+ class << self
+ def delegated_method
+ :result
+ end
+
+ def protected_method
+ :result
+ end
+ protected :protected_method
+
+ def private_method
+ :result
+ end
+ private :private_method
+ end
+ end
+
+ it "delegates to public methods of the weakly-referenced object" do
+ wr = WeakRef.new(WeakRefSpecs)
+ wr.delegated_method.should == :result
+ end
+
+ it "delegates to protected methods of the weakly-referenced object" do
+ wr = WeakRef.new(WeakRefSpecs)
+ -> { wr.protected_method }.should raise_error(NameError)
+ end
+
+ it "does not delegate to private methods of the weakly-referenced object" do
+ wr = WeakRef.new(WeakRefSpecs)
+ -> { wr.private_method }.should raise_error(NameError)
+ end
+end
diff --git a/spec/ruby/library/weakref/weakref_alive_spec.rb b/spec/ruby/library/weakref/weakref_alive_spec.rb
new file mode 100644
index 0000000000..1ebf9c1ee3
--- /dev/null
+++ b/spec/ruby/library/weakref/weakref_alive_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "WeakRef#weakref_alive?" do
+ it "returns true if the object is reachable" do
+ obj = Object.new
+ ref = WeakRef.new(obj)
+ ref.weakref_alive?.should be_true
+ end
+
+ it "returns a falsy value if the object is no longer reachable" do
+ ref = WeakRefSpec.make_dead_weakref
+ [false, nil].should include(ref.weakref_alive?)
+ end
+end
diff --git a/spec/ruby/library/win32ole/fixtures/classes.rb b/spec/ruby/library/win32ole/fixtures/classes.rb
new file mode 100644
index 0000000000..f61cf6ba69
--- /dev/null
+++ b/spec/ruby/library/win32ole/fixtures/classes.rb
@@ -0,0 +1,22 @@
+require 'win32ole'
+
+module WIN32OLESpecs
+ MSXML_AVAILABLE = WIN32OLE_TYPELIB.typelibs.any? { |t| t.name.start_with?('Microsoft XML') }
+ SYSTEM_MONITOR_CONTROL_AVAILABLE = WIN32OLE_TYPELIB.typelibs.any? { |t| t.name.start_with?('System Monitor Control') }
+
+ def self.new_ole(name)
+ tries = 0
+ begin
+ WIN32OLE.new(name)
+ rescue WIN32OLERuntimeError => e
+ if tries < 3
+ tries += 1
+ $stderr.puts "WIN32OLESpecs#new_ole retry (#{tries}): #{e.class}: #{e.message}"
+ sleep(2 ** tries)
+ retry
+ else
+ raise
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/fixtures/event.xml b/spec/ruby/library/win32ole/fixtures/event.xml
new file mode 100644
index 0000000000..23f3d2b126
--- /dev/null
+++ b/spec/ruby/library/win32ole/fixtures/event.xml
@@ -0,0 +1,4 @@
+<program>
+ <name>Ruby</name>
+ <version>trunk</version>
+</program>
diff --git a/spec/ruby/library/win32ole/win32ole/_getproperty_spec.rb b/spec/ruby/library/win32ole/win32ole/_getproperty_spec.rb
new file mode 100644
index 0000000000..940eebfb91
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/_getproperty_spec.rb
@@ -0,0 +1,14 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE#_getproperty" do
+ before :each do
+ @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary')
+ end
+
+ it "gets value" do
+ @dict.add('key', 'value')
+ @dict._getproperty(0, ['key'], [WIN32OLE::VARIANT::VT_BSTR]).should == 'value'
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole/_invoke_spec.rb b/spec/ruby/library/win32ole/win32ole/_invoke_spec.rb
new file mode 100644
index 0000000000..91f5091d24
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/_invoke_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE#_invoke" do
+ before :each do
+ @shell = WIN32OLESpecs.new_ole 'Shell.application'
+ end
+
+ it "raises ArgumentError if insufficient number of arguments are given" do
+ -> { @shell._invoke() }.should raise_error ArgumentError
+ -> { @shell._invoke(0) }.should raise_error ArgumentError
+ -> { @shell._invoke(0, []) }.should raise_error ArgumentError
+ end
+
+ it "dispatches the method bound to a specific ID" do
+ @shell._invoke(0x60020002, [37], [WIN32OLE::VARIANT::VT_VARIANT]).title.should =~ /System32/i
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole/codepage_spec.rb b/spec/ruby/library/win32ole/win32ole/codepage_spec.rb
new file mode 100644
index 0000000000..4e0cf5ca55
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/codepage_spec.rb
@@ -0,0 +1,13 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE.codepage=" do
+ it "sets codepage" do
+ cp = WIN32OLE.codepage
+ WIN32OLE.codepage = WIN32OLE::CP_UTF8
+ WIN32OLE.codepage.should == WIN32OLE::CP_UTF8
+ WIN32OLE.codepage = cp
+ end
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole/connect_spec.rb b/spec/ruby/library/win32ole/win32ole/connect_spec.rb
new file mode 100644
index 0000000000..72dceb1572
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/connect_spec.rb
@@ -0,0 +1,15 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE.connect" do
+ it "creates WIN32OLE object given valid argument" do
+ obj = WIN32OLE.connect("winmgmts:")
+ obj.should be_kind_of WIN32OLE
+ end
+
+ it "raises TypeError when given invalid argument" do
+ -> { WIN32OLE.connect 1 }.should raise_error TypeError
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole/const_load_spec.rb b/spec/ruby/library/win32ole/win32ole/const_load_spec.rb
new file mode 100644
index 0000000000..cacc7a2b22
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/const_load_spec.rb
@@ -0,0 +1,32 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE.const_load when passed Shell.Application OLE object" do
+ before :each do
+ @win32ole = WIN32OLESpecs.new_ole 'Shell.Application'
+ end
+
+ it "loads constant SsfWINDOWS into WIN32OLE namespace" do
+ WIN32OLE.const_defined?(:SsfWINDOWS).should be_false
+ WIN32OLE.const_load @win32ole
+ WIN32OLE.const_defined?(:SsfWINDOWS).should be_true
+ end
+ end
+
+ describe "WIN32OLE.const_load when namespace is specified" do
+ before :each do
+ module WIN32OLE_RUBYSPEC; end
+ @win32ole = WIN32OLESpecs.new_ole 'Shell.Application'
+ end
+
+ it "loads constants into given namespace" do
+ module WIN32OLE_RUBYSPEC; end
+
+ WIN32OLE_RUBYSPEC.const_defined?(:SsfWINDOWS).should be_false
+ WIN32OLE.const_load @win32ole, WIN32OLE_RUBYSPEC
+ WIN32OLE_RUBYSPEC.const_defined?(:SsfWINDOWS).should be_true
+
+ end
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole/constants_spec.rb b/spec/ruby/library/win32ole/win32ole/constants_spec.rb
new file mode 100644
index 0000000000..978b7ade92
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/constants_spec.rb
@@ -0,0 +1,42 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE class" do
+ it "defines constant CP_ACP" do
+ WIN32OLE::CP_ACP.should == 0
+ end
+
+ it "defines constant CP_OEMCP" do
+ WIN32OLE::CP_OEMCP.should == 1
+ end
+
+ it "defines constant CP_MACCP" do
+ WIN32OLE::CP_MACCP.should == 2
+ end
+
+ it "defines constant CP_THREAD_ACP" do
+ WIN32OLE::CP_THREAD_ACP.should == 3
+ end
+
+ it "defines constant CP_SYMBOL" do
+ WIN32OLE::CP_SYMBOL.should == 42
+ end
+
+ it "defines constant CP_UTF7" do
+ WIN32OLE::CP_UTF7.should == 65000
+ end
+
+ it "defines constant CP_UTF8" do
+ WIN32OLE::CP_UTF8.should == 65001
+ end
+
+ it "defines constant LOCALE_SYSTEM_DEFAULT" do
+ WIN32OLE::LOCALE_SYSTEM_DEFAULT.should == 0x0800
+ end
+
+ it "defines constant LOCALE_USER_DEFAULT" do
+ WIN32OLE::LOCALE_USER_DEFAULT.should == 0x0400
+ end
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole/create_guid_spec.rb b/spec/ruby/library/win32ole/win32ole/create_guid_spec.rb
new file mode 100644
index 0000000000..2e18b6ab11
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/create_guid_spec.rb
@@ -0,0 +1,9 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE.create_guid" do
+ it "generates guid with valid format" do
+ WIN32OLE.create_guid.should =~ /^\{[A-Z0-9]{8}\-[A-Z0-9]{4}\-[A-Z0-9]{4}\-[A-Z0-9]{4}\-[A-Z0-9]{12}/
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole/invoke_spec.rb b/spec/ruby/library/win32ole/win32ole/invoke_spec.rb
new file mode 100644
index 0000000000..08a5156e05
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/invoke_spec.rb
@@ -0,0 +1,14 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE#invoke" do
+ before :each do
+ @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary')
+ end
+
+ it "get value by invoking 'Item' OLE method" do
+ @dict.add('key', 'value')
+ @dict.invoke('Item', 'key').should == 'value'
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole/locale_spec.rb b/spec/ruby/library/win32ole/win32ole/locale_spec.rb
new file mode 100644
index 0000000000..75a82ddd7f
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/locale_spec.rb
@@ -0,0 +1,29 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE.locale" do
+ it "gets locale" do
+ WIN32OLE.locale.should == WIN32OLE::LOCALE_SYSTEM_DEFAULT
+ end
+ end
+
+ describe "WIN32OLE.locale=" do
+ it "sets locale to Japanese, if available" do
+ begin
+ begin
+ WIN32OLE.locale = 1041
+ rescue WIN32OLERuntimeError
+ STDERR.puts("\n#{__FILE__}:#{__LINE__}:#{self.class.name}.test_s_locale_set is skipped(Japanese locale is not installed)")
+ return
+ end
+
+ WIN32OLE.locale.should == 1041
+ WIN32OLE.locale = WIN32OLE::LOCALE_SYSTEM_DEFAULT
+ -> { WIN32OLE.locale = 111 }.should raise_error WIN32OLERuntimeError
+ WIN32OLE.locale.should == WIN32OLE::LOCALE_SYSTEM_DEFAULT
+ ensure
+ WIN32OLE.locale.should == WIN32OLE::LOCALE_SYSTEM_DEFAULT
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole/new_spec.rb b/spec/ruby/library/win32ole/win32ole/new_spec.rb
new file mode 100644
index 0000000000..6b717195f1
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/new_spec.rb
@@ -0,0 +1,25 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLESpecs.new_ole" do
+ it "creates a WIN32OLE object from OLE server name" do
+ shell = WIN32OLESpecs.new_ole 'Shell.Application'
+ shell.should be_kind_of WIN32OLE
+ end
+
+ it "creates a WIN32OLE object from valid CLSID" do
+ shell = WIN32OLESpecs.new_ole("{13709620-C279-11CE-A49E-444553540000}")
+ shell.should be_kind_of WIN32OLE
+ end
+
+ it "raises TypeError if argument cannot be converted to String" do
+ -> { WIN32OLESpecs.new_ole(42) }.should raise_error( TypeError )
+ end
+
+ it "raises WIN32OLERuntimeError if invalid string is given" do
+ -> { WIN32OLE.new('foo') }.should raise_error( WIN32OLERuntimeError )
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb
new file mode 100644
index 0000000000..75748182fe
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE#ole_func_methods" do
+ before :each do
+ @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary')
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @dict.ole_func_methods(1) }.should raise_error ArgumentError
+ end
+
+ it "returns an array of WIN32OLE_METHODs" do
+ @dict.ole_func_methods.all? { |m| m.kind_of? WIN32OLE_METHOD }.should be_true
+ end
+
+ it "contains a 'AddRef' method for Scripting Dictionary" do
+ @dict.ole_func_methods.map { |m| m.name }.include?('AddRef').should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb
new file mode 100644
index 0000000000..a991624a23
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb
@@ -0,0 +1,16 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE#ole_get_methods" do
+
+ before :each do
+ @win32ole = WIN32OLESpecs.new_ole('Shell.Application')
+ end
+
+ it "returns an array of WIN32OLE_METHOD objects" do
+ @win32ole.ole_get_methods.all? {|m| m.kind_of? WIN32OLE_METHOD}.should be_true
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole/ole_method_help_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_method_help_spec.rb
new file mode 100644
index 0000000000..8a26d79a20
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/ole_method_help_spec.rb
@@ -0,0 +1,10 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+ require_relative 'shared/ole_method'
+
+ describe "WIN32OLE#ole_method_help" do
+ it_behaves_like :win32ole_ole_method, :ole_method_help
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole/ole_method_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_method_spec.rb
new file mode 100644
index 0000000000..f82a212f5d
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/ole_method_spec.rb
@@ -0,0 +1,10 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+ require_relative 'shared/ole_method'
+
+ describe "WIN32OLE#ole_method" do
+ it_behaves_like :win32ole_ole_method, :ole_method
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb
new file mode 100644
index 0000000000..5ac9ae9cfa
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE#ole_methods" do
+ before :each do
+ @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary')
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @dict.ole_methods(1) }.should raise_error ArgumentError
+ end
+
+ it "returns an array of WIN32OLE_METHODs" do
+ @dict.ole_methods.all? { |m| m.kind_of? WIN32OLE_METHOD }.should be_true
+ end
+
+ it "contains a 'AddRef' method for Scripting Dictionary" do
+ @dict.ole_methods.map { |m| m.name }.include?('AddRef').should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb
new file mode 100644
index 0000000000..ef8944ee39
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb
@@ -0,0 +1,18 @@
+
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE#ole_obj_help" do
+ before :each do
+ @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary')
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @dict.ole_obj_help(1) }.should raise_error ArgumentError
+ end
+
+ it "returns an instance of WIN32OLE_TYPE" do
+ @dict.ole_obj_help.kind_of?(WIN32OLE_TYPE).should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb
new file mode 100644
index 0000000000..727291e9f0
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ describe "WIN32OLE#ole_put_methods" do
+ before :each do
+ @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary')
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @dict.ole_put_methods(1) }.should raise_error ArgumentError
+ end
+
+ it "returns an array of WIN32OLE_METHODs" do
+ @dict.ole_put_methods.all? { |m| m.kind_of? WIN32OLE_METHOD }.should be_true
+ end
+
+ it "contains a 'Key' method for Scripting Dictionary" do
+ @dict.ole_put_methods.map { |m| m.name }.include?('Key').should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole/setproperty_spec.rb b/spec/ruby/library/win32ole/win32ole/setproperty_spec.rb
new file mode 100644
index 0000000000..7409823f20
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/setproperty_spec.rb
@@ -0,0 +1,10 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+ require_relative 'shared/setproperty'
+
+ describe "WIN32OLE#setproperty" do
+ it_behaves_like :win32ole_setproperty, :setproperty
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole/shared/ole_method.rb b/spec/ruby/library/win32ole/win32ole/shared/ole_method.rb
new file mode 100644
index 0000000000..f1fd8713a4
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/shared/ole_method.rb
@@ -0,0 +1,19 @@
+platform_is :windows do
+ require_relative '../../fixtures/classes'
+
+ describe :win32ole_ole_method, shared: true do
+ before :each do
+ @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary')
+ end
+
+ it "raises ArgumentError if no argument is given" do
+ -> { @dict.send(@method) }.should raise_error ArgumentError
+ end
+
+ it "returns the WIN32OLE_METHOD 'Add' if given 'Add'" do
+ result = @dict.send(@method, "Add")
+ result.kind_of?(WIN32OLE_METHOD).should be_true
+ result.name.should == 'Add'
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole/shared/setproperty.rb b/spec/ruby/library/win32ole/win32ole/shared/setproperty.rb
new file mode 100644
index 0000000000..b9267aef71
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole/shared/setproperty.rb
@@ -0,0 +1,23 @@
+platform_is :windows do
+ require_relative '../../fixtures/classes'
+
+ describe :win32ole_setproperty, shared: true do
+ before :each do
+ @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary')
+ end
+
+ it "raises ArgumentError if no argument is given" do
+ -> { @dict.send(@method) }.should raise_error ArgumentError
+ end
+
+ it "sets key to newkey and returns nil" do
+ oldkey = 'oldkey'
+ newkey = 'newkey'
+ @dict.add(oldkey, 'value')
+ result = @dict.send(@method, 'Key', oldkey, newkey)
+ result.should == nil
+ @dict[oldkey].should == nil
+ @dict[newkey].should == 'value'
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_event/new_spec.rb b/spec/ruby/library/win32ole/win32ole_event/new_spec.rb
new file mode 100644
index 0000000000..a1a1612393
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_event/new_spec.rb
@@ -0,0 +1,33 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+
+ guard -> { WIN32OLESpecs::MSXML_AVAILABLE } do
+ describe "WIN32OLE_EVENT.new" do
+ before :all do
+ @xml_dom = WIN32OLESpecs.new_ole('MSXML.DOMDocument')
+ end
+
+ after :all do
+ @xml_dom = nil
+ end
+
+ it "raises TypeError given invalid argument" do
+ -> { WIN32OLE_EVENT.new "A" }.should raise_error TypeError
+ end
+
+ it "raises RuntimeError if event does not exist" do
+ -> { WIN32OLE_EVENT.new(@xml_dom, 'A') }.should raise_error RuntimeError
+ end
+
+ it "raises RuntimeError if OLE object has no events" do
+ dict = WIN32OLESpecs.new_ole('Scripting.Dictionary')
+ -> { WIN32OLE_EVENT.new(dict) }.should raise_error RuntimeError
+ end
+
+ it "creates WIN32OLE_EVENT object" do
+ ev = WIN32OLE_EVENT.new(@xml_dom)
+ ev.should be_kind_of WIN32OLE_EVENT
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb b/spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb
new file mode 100644
index 0000000000..feb26b0637
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb
@@ -0,0 +1,70 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+ guard -> { WIN32OLESpecs::MSXML_AVAILABLE } do
+
+ def handler_global(event, *args)
+ @event_global += event
+ end
+
+ def handler_specific(*args)
+ @event_specific = "specific"
+ end
+
+ def handler_spec_alt(*args)
+ @event_spec_alt = "spec_alt"
+ end
+
+ describe "WIN32OLE_EVENT#on_event" do
+ before :all do
+ @fn_xml = File.absolute_path "../fixtures/event.xml", __dir__
+ end
+
+ before :each do
+ @xml_dom = WIN32OLESpecs.new_ole 'MSXML.DOMDocument'
+ @xml_dom.async = true
+ @ev = WIN32OLE_EVENT.new @xml_dom
+ @event_global = ''
+ @event_specific = ''
+ @event_spec_alt = ''
+ end
+
+ after :each do
+ @xml_dom = nil
+ @ev = nil
+ end
+
+ it "sets global event handler properly, and the handler is invoked by event loop" do
+ @ev.on_event { |*args| handler_global(*args) }
+ @xml_dom.loadXML "<program><name>Ruby</name><version>trunk</version></program>"
+ WIN32OLE_EVENT.message_loop
+ @event_global.should =~ /onreadystatechange/
+ end
+
+ it "accepts a String argument and the handler is invoked by event loop" do
+ @ev.on_event("onreadystatechange") { |*args| @event = 'foo' }
+ @xml_dom.loadXML "<program><name>Ruby</name><version>trunk</version></program>"
+ WIN32OLE_EVENT.message_loop
+ @event.should =~ /foo/
+ end
+
+ it "accepts a Symbol argument and the handler is invoked by event loop" do
+ @ev.on_event(:onreadystatechange) { |*args| @event = 'bar' }
+ @xml_dom.loadXML "<program><name>Ruby</name><version>trunk</version></program>"
+ WIN32OLE_EVENT.message_loop
+ @event.should =~ /bar/
+ end
+
+ it "accepts a specific event handler and overrides a global event handler" do
+ @ev.on_event { |*args| handler_global(*args) }
+ @ev.on_event("onreadystatechange") { |*args| handler_specific(*args) }
+ @ev.on_event("onreadystatechange") { |*args| handler_spec_alt(*args) }
+ @xml_dom.load @fn_xml
+ WIN32OLE_EVENT.message_loop
+ @event_global.should == 'ondataavailable'
+ @event_global.should_not =~ /onreadystatechange/
+ @event_specific.should == ''
+ @event_spec_alt.should == "spec_alt"
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb b/spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb
new file mode 100644
index 0000000000..69068683b7
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#dispid" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ @m = WIN32OLE_METHOD.new(ole_type, "namespace")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m.dispid(0) }.should raise_error ArgumentError
+ end
+
+ it "returns expected dispatch ID for Shell's 'namespace' method" do
+ @m.dispid.should == 1610743810 # value found in MRI's test
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb b/spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb
new file mode 100644
index 0000000000..70c8b30cca
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb
@@ -0,0 +1,28 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+ guard -> { WIN32OLESpecs::SYSTEM_MONITOR_CONTROL_AVAILABLE } do
+
+ describe "WIN32OLE_METHOD#event_interface" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("System Monitor Control", "SystemMonitor")
+ @on_dbl_click_method = WIN32OLE_METHOD.new(ole_type, "OnDblClick")
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ @namespace_method = WIN32OLE_METHOD.new(ole_type, "namespace")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @on_dbl_click_method.event_interface(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected string for System Monitor Control's 'OnDblClick' method" do
+ @on_dbl_click_method.event_interface.should == "DISystemMonitorEvents"
+ end
+
+ it "returns nil if method has no event interface" do
+ @namespace_method.event_interface.should be_nil
+ end
+
+ end
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/event_spec.rb b/spec/ruby/library/win32ole/win32ole_method/event_spec.rb
new file mode 100644
index 0000000000..c41f8fe99d
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/event_spec.rb
@@ -0,0 +1,22 @@
+platform_is :windows do
+ require_relative '../fixtures/classes'
+ guard -> { WIN32OLESpecs::SYSTEM_MONITOR_CONTROL_AVAILABLE } do
+
+ describe "WIN32OLE_METHOD#event?" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("System Monitor Control", "SystemMonitor")
+ @on_dbl_click_method = WIN32OLE_METHOD.new(ole_type, "OnDblClick")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @on_dbl_click_method.event?(1) }.should raise_error ArgumentError
+ end
+
+ it "returns true for System Monitor Control's 'OnDblClick' method" do
+ @on_dbl_click_method.event?.should be_true
+ end
+
+ end
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb b/spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb
new file mode 100644
index 0000000000..21b3ae8bde
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb
@@ -0,0 +1,26 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#helpcontext" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject")
+ @get_file_version = WIN32OLE_METHOD.new(ole_type, "GetFileVersion")
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File")
+ @m_file_name = WIN32OLE_METHOD.new(ole_type, "name")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @get_file_version.helpcontext(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for FileSystemObject's 'GetFileVersion' method" do
+ @get_file_version.helpcontext.should == 0
+ end
+
+ it "returns expected value for Scripting Runtime's 'name' method" do
+ @m_file_name.helpcontext.should == 2181996 # value indicated in MRI's test
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb b/spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb
new file mode 100644
index 0000000000..b6d0a19a37
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#helpfile" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File")
+ @m_file_name = WIN32OLE_METHOD.new(ole_type, "name")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_file_name.helpfile(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Scripting Runtime's 'File' method" do
+ @m_file_name.helpfile.should =~ /VBENLR.*\.CHM$/i
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb b/spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb
new file mode 100644
index 0000000000..9f940fd4a0
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#helpstring" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File")
+ @m_file_name = WIN32OLE_METHOD.new(ole_type, "name")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_file_name.helpstring(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Scripting Runtime's 'File' method" do
+ @m_file_name.helpstring.should == "Get name of file" # value indicated in MRI's test
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb b/spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb
new file mode 100644
index 0000000000..7fff479daf
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#invkind" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File")
+ @m_file_name = WIN32OLE_METHOD.new(ole_type, "name")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_file_name.invkind(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Scripting Runtime's 'name' method" do
+ @m_file_name.invkind.should == 2
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb b/spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb
new file mode 100644
index 0000000000..e8638abd91
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#invoke_kind" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File")
+ @m_file_name = WIN32OLE_METHOD.new(ole_type, "name")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_file_name.invoke_kind(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Scripting Runtime's 'name' method" do
+ @m_file_name.invoke_kind.should =~ /^(UNKNOWN|PROPERTY|PROPERTYGET|PROPERTYPUT|PROPERTYPUTREF|FUNC)$/
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/name_spec.rb b/spec/ruby/library/win32ole/win32ole_method/name_spec.rb
new file mode 100644
index 0000000000..cd5404fc54
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/name_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'shared/name'
+
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#name" do
+ it_behaves_like :win32ole_method_name, :name
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/new_spec.rb b/spec/ruby/library/win32ole/win32ole_method/new_spec.rb
new file mode 100644
index 0000000000..8ebf93b992
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/new_spec.rb
@@ -0,0 +1,33 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD.new" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ it "raises TypeError when given non-strings" do
+ -> { WIN32OLE_METHOD.new(1, 2) }.should raise_error TypeError
+ end
+
+ it "raises ArgumentError if only 1 argument is given" do
+ -> { WIN32OLE_METHOD.new("hello") }.should raise_error ArgumentError
+ -> { WIN32OLE_METHOD.new(@ole_type) }.should raise_error ArgumentError
+ end
+
+ it "returns a valid WIN32OLE_METHOD object" do
+ WIN32OLE_METHOD.new(@ole_type, "Open").should be_kind_of WIN32OLE_METHOD
+ WIN32OLE_METHOD.new(@ole_type, "open").should be_kind_of WIN32OLE_METHOD
+ end
+
+ it "raises WIN32OLERuntimeError if the method does not exist" do
+ -> { WIN32OLE_METHOD.new(@ole_type, "NonexistentMethod") }.should raise_error WIN32OLERuntimeError
+ end
+
+ it "raises TypeError if second argument is not a String" do
+ -> { WIN32OLE_METHOD.new(@ole_type, 5) }.should raise_error TypeError
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb b/spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb
new file mode 100644
index 0000000000..8e50c39787
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#offset_vtbl" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File")
+ @m_file_name = WIN32OLE_METHOD.new(ole_type, "name")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_file_name.offset_vtbl(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Scripting Runtime's 'name' method" do
+ pointer_size = PlatformGuard::POINTER_SIZE
+ @m_file_name.offset_vtbl.should == pointer_size
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/params_spec.rb b/spec/ruby/library/win32ole/win32ole_method/params_spec.rb
new file mode 100644
index 0000000000..2f8da3d45b
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/params_spec.rb
@@ -0,0 +1,28 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#params" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File")
+ @m_file_name = WIN32OLE_METHOD.new(ole_type, "name")
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_file_name.params(1) }.should raise_error ArgumentError
+ end
+
+ it "returns empty array for Scripting Runtime's 'name' method" do
+ @m_file_name.params.should be_kind_of Array
+ @m_file_name.params.should be_empty
+ end
+
+ it "returns 4-element array of WIN32OLE_PARAM for Shell's 'BrowseForFolder' method" do
+ @m_browse_for_folder.params.all? { |p| p.kind_of? WIN32OLE_PARAM }.should be_true
+ @m_browse_for_folder.params.size == 4
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb b/spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb
new file mode 100644
index 0000000000..f8ce3e1b3a
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#return_type_detail" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_browse_for_folder.return_type_detail(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Shell Control's 'BrowseForFolder' method" do
+ @m_browse_for_folder.return_type_detail.should be_kind_of Array
+ @m_browse_for_folder.return_type_detail.should == ['PTR', 'USERDEFINED', 'Folder']
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb b/spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb
new file mode 100644
index 0000000000..58e26df77b
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#return_type" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File")
+ @m_file_name = WIN32OLE_METHOD.new(ole_type, "name")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_file_name.return_type(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Scripting Runtime's 'name' method" do
+ @m_file_name.return_type.should == 'BSTR'
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb b/spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb
new file mode 100644
index 0000000000..dc159dd09e
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#return_vtype" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_browse_for_folder.return_vtype(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Shell Control's 'BrowseForFolder' method" do
+ @m_browse_for_folder.return_vtype.should == 26
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/shared/name.rb b/spec/ruby/library/win32ole/win32ole_method/shared/name.rb
new file mode 100644
index 0000000000..ddaff4011b
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/shared/name.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe :win32ole_method_name, shared: true do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File")
+ @m_file_name = WIN32OLE_METHOD.new(ole_type, "name")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_file_name.send(@method, 1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Scripting Runtime's 'name' method" do
+ @m_file_name.send(@method).should == 'Name' # note the capitalization
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb b/spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb
new file mode 100644
index 0000000000..a38fe5c681
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#size_opt_params" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_browse_for_folder.size_opt_params(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Shell Control's 'BrowseForFolder' method" do
+ @m_browse_for_folder.size_opt_params.should == 1
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb b/spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb
new file mode 100644
index 0000000000..0c5a94c338
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#size_params" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_browse_for_folder.size_params(1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Shell Control's 'BrowseForFolder' method" do
+ @m_browse_for_folder.size_params.should == 4
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb
new file mode 100644
index 0000000000..ecb3c08038
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'shared/name'
+
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#name" do
+ it_behaves_like :win32ole_method_name, :to_s
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_method/visible_spec.rb b/spec/ruby/library/win32ole/win32ole_method/visible_spec.rb
new file mode 100644
index 0000000000..918b6ef782
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_method/visible_spec.rb
@@ -0,0 +1,20 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_METHOD#visible?" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @m_browse_for_folder.visible?(1) }.should raise_error ArgumentError
+ end
+
+ it "returns true for Shell Control's 'BrowseForFolder' method" do
+ @m_browse_for_folder.visible?.should be_true
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_param/default_spec.rb b/spec/ruby/library/win32ole/win32ole_param/default_spec.rb
new file mode 100644
index 0000000000..af08c84782
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_param/default_spec.rb
@@ -0,0 +1,31 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_PARAM#default" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder")
+ @params = m_browse_for_folder.params
+
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject")
+ m_copyfile = WIN32OLE_METHOD.new(ole_type, "CopyFile")
+ @param_overwritefiles = m_copyfile.params[2]
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @params[0].default(1) }.should raise_error ArgumentError
+ end
+
+ it "returns nil for each of WIN32OLE_PARAM for Shell's 'BrowseForFolder' method" do
+ @params.each do |p|
+ p.default.should be_nil
+ end
+ end
+
+ it "returns true for 3rd parameter of FileSystemObject's 'CopyFile' method" do
+ @param_overwritefiles.default.should == true # not be_true
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_param/input_spec.rb b/spec/ruby/library/win32ole/win32ole_param/input_spec.rb
new file mode 100644
index 0000000000..e2a90daa56
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_param/input_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_PARAM#input?" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject")
+ m_copyfile = WIN32OLE_METHOD.new(ole_type, "CopyFile")
+ @param_overwritefiles = m_copyfile.params[2]
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @param_overwritefiles.input?(1) }.should raise_error ArgumentError
+ end
+
+ it "returns true for 3rd parameter of FileSystemObject's 'CopyFile' method" do
+ @param_overwritefiles.should.input?
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_param/name_spec.rb b/spec/ruby/library/win32ole/win32ole_param/name_spec.rb
new file mode 100644
index 0000000000..0c20c24720
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_param/name_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'shared/name'
+
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_PARAM#name" do
+ it_behaves_like :win32ole_param_name, :name
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb b/spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb
new file mode 100644
index 0000000000..e683d1c16e
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_PARAM#ole_type_detail" do
+ before :each do
+ ole_type_detail = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject")
+ m_copyfile = WIN32OLE_METHOD.new(ole_type_detail, "CopyFile")
+ @param_overwritefiles = m_copyfile.params[2]
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @param_overwritefiles.ole_type_detail(1) }.should raise_error ArgumentError
+ end
+
+ it "returns ['BOOL'] for 3rd parameter of FileSystemObject's 'CopyFile' method" do
+ @param_overwritefiles.ole_type_detail.should == ['BOOL']
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb b/spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb
new file mode 100644
index 0000000000..b9a3639c3e
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_PARAM#ole_type" do
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject")
+ m_copyfile = WIN32OLE_METHOD.new(ole_type, "CopyFile")
+ @param_overwritefiles = m_copyfile.params[2]
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @param_overwritefiles.ole_type(1) }.should raise_error ArgumentError
+ end
+
+ it "returns 'BOOL' for 3rd parameter of FileSystemObject's 'CopyFile' method" do
+ @param_overwritefiles.ole_type.should == 'BOOL'
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_param/optional_spec.rb b/spec/ruby/library/win32ole/win32ole_param/optional_spec.rb
new file mode 100644
index 0000000000..3fb9dc1867
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_param/optional_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_PARAM#optional?" do
+ before :each do
+ ole_type_detail = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject")
+ m_copyfile = WIN32OLE_METHOD.new(ole_type_detail, "CopyFile")
+ @param_overwritefiles = m_copyfile.params[2]
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @param_overwritefiles.optional?(1) }.should raise_error ArgumentError
+ end
+
+ it "returns true for 3rd parameter of FileSystemObject's 'CopyFile' method" do
+ @param_overwritefiles.optional?.should be_true
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_param/retval_spec.rb b/spec/ruby/library/win32ole/win32ole_param/retval_spec.rb
new file mode 100644
index 0000000000..f5546e79e5
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_param/retval_spec.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_PARAM#retval?" do
+ before :each do
+ ole_type_detail = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject")
+ m_copyfile = WIN32OLE_METHOD.new(ole_type_detail, "CopyFile")
+ @param_overwritefiles = m_copyfile.params[2]
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @param_overwritefiles.retval?(1) }.should raise_error ArgumentError
+ end
+
+ it "returns false for 3rd parameter of FileSystemObject's 'CopyFile' method" do
+ @param_overwritefiles.retval?.should be_false
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_param/shared/name.rb b/spec/ruby/library/win32ole/win32ole_param/shared/name.rb
new file mode 100644
index 0000000000..043bc32856
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_param/shared/name.rb
@@ -0,0 +1,21 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe :win32ole_param_name, shared: true do
+ before :each do
+ ole_type_detail = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject")
+ m_copyfile = WIN32OLE_METHOD.new(ole_type_detail, "CopyFile")
+ @param_overwritefiles = m_copyfile.params[2]
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @param_overwritefiles.send(@method, 1) }.should raise_error ArgumentError
+ end
+
+ it "returns expected value for Scripting Runtime's 'name' method" do
+ @param_overwritefiles.send(@method).should == 'OverWriteFiles' # note the capitalization
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb
new file mode 100644
index 0000000000..5b4b4c1c80
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'shared/name'
+
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_PARAM#to_s" do
+ it_behaves_like :win32ole_param_name, :to_s
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/guid_spec.rb b/spec/ruby/library/win32ole/win32ole_type/guid_spec.rb
new file mode 100644
index 0000000000..25907c8e32
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/guid_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#guid for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns String with expected format" do
+ @ole_type.guid.should =~ /\A\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}\z/
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb b/spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb
new file mode 100644
index 0000000000..d436835188
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#helpcontext for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns an Integer" do
+ @ole_type.helpcontext.should be_kind_of Integer
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb b/spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb
new file mode 100644
index 0000000000..01e6945138
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#helpfile for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns an empty string" do
+ @ole_type.helpfile.should be_empty
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb b/spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb
new file mode 100644
index 0000000000..3bd2cbe5dd
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#helpstring for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns expected string" do
+ @ole_type.helpstring.should == "Shell Object Type Information"
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb b/spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb
new file mode 100644
index 0000000000..7dae16617d
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#major_version for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns an Integer" do
+ @ole_type.major_version.should be_kind_of Integer
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb b/spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb
new file mode 100644
index 0000000000..ff412dd100
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#minor_version for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns an Integer" do
+ @ole_type.minor_version.should be_kind_of Integer
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/name_spec.rb b/spec/ruby/library/win32ole/win32ole_type/name_spec.rb
new file mode 100644
index 0000000000..b7a28c553a
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/name_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'shared/name'
+
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#name" do
+ it_behaves_like :win32ole_type_name, :name
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/new_spec.rb b/spec/ruby/library/win32ole/win32ole_type/new_spec.rb
new file mode 100644
index 0000000000..3c3aa1c390
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/new_spec.rb
@@ -0,0 +1,40 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE.new" do
+ it "raises ArgumentError with no argument" do
+ -> { WIN32OLE_TYPE.new }.should raise_error ArgumentError
+ end
+
+ it "raises ArgumentError with invalid string" do
+ -> { WIN32OLE_TYPE.new("foo") }.should raise_error ArgumentError
+ end
+
+ it "raises TypeError if second argument is not a String" do
+ -> { WIN32OLE_TYPE.new(1,2) }.should raise_error TypeError
+ -> {
+ WIN32OLE_TYPE.new('Microsoft Shell Controls And Automation',2)
+ }.should raise_error TypeError
+ end
+
+ it "raise WIN32OLERuntimeError if OLE object specified is not found" do
+ -> {
+ WIN32OLE_TYPE.new('Microsoft Shell Controls And Automation','foo')
+ }.should raise_error WIN32OLERuntimeError
+ -> {
+ WIN32OLE_TYPE.new('Microsoft Shell Controls And Automation','Application')
+ }.should raise_error WIN32OLERuntimeError
+ end
+
+ it "creates WIN32OLE_TYPE object from name and valid type" do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ ole_type.should be_kind_of WIN32OLE_TYPE
+ end
+
+ it "creates WIN32OLE_TYPE object from CLSID and valid type" do
+ ole_type2 = WIN32OLE_TYPE.new("{13709620-C279-11CE-A49E-444553540000}", "Shell")
+ ole_type2.should be_kind_of WIN32OLE_TYPE
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb b/spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb
new file mode 100644
index 0000000000..0ce0fc98a4
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE.ole_classes for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns array of WIN32OLE_TYPEs" do
+ WIN32OLE_TYPE.ole_classes("Microsoft Shell Controls And Automation").all? {|e| e.kind_of? WIN32OLE_TYPE }.should be_true
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb b/spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb
new file mode 100644
index 0000000000..9265549d20
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#ole_methods for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns an Integer" do
+ @ole_type.ole_methods.all? { |m| m.kind_of? WIN32OLE_METHOD }.should be_true
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb b/spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb
new file mode 100644
index 0000000000..2bc19aa85e
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#ole_type for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns string 'Class'" do
+ @ole_type.ole_type.should == "Class"
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/progid_spec.rb b/spec/ruby/library/win32ole/win32ole_type/progid_spec.rb
new file mode 100644
index 0000000000..f0d80ba39e
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/progid_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#progid for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns expected string" do
+ @ole_type.progid.should == "Shell.Application.1"
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/progids_spec.rb b/spec/ruby/library/win32ole/win32ole_type/progids_spec.rb
new file mode 100644
index 0000000000..19d8bf56e0
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/progids_spec.rb
@@ -0,0 +1,14 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE.progids" do
+ it "raises ArgumentError if an argument is given" do
+ -> { WIN32OLE_TYPE.progids(1) }.should raise_error ArgumentError
+ end
+
+ it "returns an array containing 'Shell.Explorer'" do
+ WIN32OLE_TYPE.progids().include?('Shell.Explorer').should be_true
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/shared/name.rb b/spec/ruby/library/win32ole/win32ole_type/shared/name.rb
new file mode 100644
index 0000000000..6f37446b23
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/shared/name.rb
@@ -0,0 +1,19 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe :win32ole_type_name, shared: true do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants")
+ end
+
+ it "raises ArgumentError if argument is given" do
+ -> { @ole_type.send(@method, 1) }.should raise_error ArgumentError
+ end
+
+ it "returns a String" do
+ @ole_type.send(@method).should == 'ShellSpecialFolderConstants'
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb b/spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb
new file mode 100644
index 0000000000..71e304d80a
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#src_type for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns nil" do
+ @ole_type.src_type.should be_nil
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb
new file mode 100644
index 0000000000..b713990ed2
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'shared/name'
+
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#to_s" do
+ it_behaves_like :win32ole_type_name, :to_s
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb b/spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb
new file mode 100644
index 0000000000..35f3562721
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#typekind for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns an Integer" do
+ @ole_type.typekind.should be_kind_of Integer
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb b/spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb
new file mode 100644
index 0000000000..369e0274f3
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb
@@ -0,0 +1,22 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE.typelibs for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "raises ArgumentError if any argument is give" do
+ -> { WIN32OLE_TYPE.typelibs(1) }.should raise_error ArgumentError
+ end
+
+ it "returns array of type libraries" do
+ WIN32OLE_TYPE.typelibs().include?("Microsoft Shell Controls And Automation").should be_true
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/variables_spec.rb b/spec/ruby/library/win32ole/win32ole_type/variables_spec.rb
new file mode 100644
index 0000000000..fbf3dd0341
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/variables_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#variables for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns an empty array" do
+ @ole_type.variables.should == []
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_type/visible_spec.rb b/spec/ruby/library/win32ole/win32ole_type/visible_spec.rb
new file mode 100644
index 0000000000..403b2b843b
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_type/visible_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_TYPE#visible? for Shell Controls" do
+ before :each do
+ @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell")
+ end
+
+ after :each do
+ @ole_type = nil
+ end
+
+ it "returns true" do
+ @ole_type.visible?.should be_true
+ end
+
+ end
+end
diff --git a/spec/ruby/library/win32ole/win32ole_variable/name_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/name_spec.rb
new file mode 100644
index 0000000000..8bac1a9891
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_variable/name_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'shared/name'
+
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_VARIABLE#name" do
+ it_behaves_like :win32ole_variable_new, :name
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb
new file mode 100644
index 0000000000..dab4edabaa
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb
@@ -0,0 +1,19 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_VARIABLE#ole_type_detail" do
+ # not sure how WIN32OLE_VARIABLE objects are supposed to be generated
+ # WIN32OLE_VARIABLE.new even seg faults in some cases
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants")
+ @var = ole_type.variables[0]
+ end
+
+ it "returns a nonempty Array" do
+ @var.ole_type_detail.should be_kind_of Array
+ @var.ole_type_detail.should_not be_empty
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb
new file mode 100644
index 0000000000..d08acc9bde
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_VARIABLE#ole_type" do
+ # not sure how WIN32OLE_VARIABLE objects are supposed to be generated
+ # WIN32OLE_VARIABLE.new even seg faults in some cases
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants")
+ @var = ole_type.variables[0]
+ end
+
+ it "returns a String" do
+ @var.ole_type.should be_kind_of String
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_variable/shared/name.rb b/spec/ruby/library/win32ole/win32ole_variable/shared/name.rb
new file mode 100644
index 0000000000..033e830fac
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_variable/shared/name.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe :win32ole_variable_new, shared: true do
+ # not sure how WIN32OLE_VARIABLE objects are supposed to be generated
+ # WIN32OLE_VARIABLE.new even seg faults in some cases
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants")
+ @var = ole_type.variables[0]
+ end
+
+ it "returns a String" do
+ @var.send(@method).should be_kind_of String
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_variable/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/to_s_spec.rb
new file mode 100644
index 0000000000..000ac14d7e
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_variable/to_s_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'shared/name'
+
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_VARIABLE#to_s" do
+ it_behaves_like :win32ole_variable_new, :to_s
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_variable/value_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/value_spec.rb
new file mode 100644
index 0000000000..4f240b561c
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_variable/value_spec.rb
@@ -0,0 +1,19 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_VARIABLE#value" do
+ # not sure how WIN32OLE_VARIABLE objects are supposed to be generated
+ # WIN32OLE_VARIABLE.new even seg faults in some cases
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants")
+ @var = ole_type.variables[0]
+ end
+
+ it "returns an Integer" do
+ # according to doc, this could return nil
+ @var.value.should be_kind_of Integer
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb
new file mode 100644
index 0000000000..4cca7f8874
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb
@@ -0,0 +1,19 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_VARIABLE#variable_kind" do
+ # not sure how WIN32OLE_VARIABLE objects are supposed to be generated
+ # WIN32OLE_VARIABLE.new even seg faults in some cases
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants")
+ @var = ole_type.variables[0]
+ end
+
+ it "returns a String" do
+ @var.variable_kind.should be_kind_of String
+ @var.variable_kind.should == 'CONSTANT'
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb
new file mode 100644
index 0000000000..56cd1c337a
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb
@@ -0,0 +1,19 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_VARIABLE#varkind" do
+ # TODO review
+ # not sure how WIN32OLE_VARIABLE objects are supposed to be generated
+ # WIN32OLE_VARIABLE.new even seg faults in some cases
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants")
+ @var = ole_type.variables[0]
+ end
+
+ it "returns an Integer" do
+ @var.varkind.should be_kind_of Integer
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb
new file mode 100644
index 0000000000..7f7a557b57
--- /dev/null
+++ b/spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb
@@ -0,0 +1,18 @@
+platform_is :windows do
+ require 'win32ole'
+
+ describe "WIN32OLE_VARIABLE#visible?" do
+ # not sure how WIN32OLE_VARIABLE objects are supposed to be generated
+ # WIN32OLE_VARIABLE.new even seg faults in some cases
+ before :each do
+ ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants")
+ @var = ole_type.variables[0]
+ end
+
+ it "returns a String" do
+ @var.visible?.should be_true
+ end
+
+ end
+
+end
diff --git a/spec/ruby/library/yaml/dump_spec.rb b/spec/ruby/library/yaml/dump_spec.rb
new file mode 100644
index 0000000000..3107a8f51d
--- /dev/null
+++ b/spec/ruby/library/yaml/dump_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+# TODO: WTF is this using a global?
+describe "YAML.dump" do
+ after :each do
+ rm_r $test_file
+ end
+
+ it "converts an object to YAML and write result to io when io provided" do
+ File.open($test_file, 'w' ) do |io|
+ YAML.dump( ['badger', 'elephant', 'tiger'], io )
+ end
+ YAML.load_file($test_file).should == ['badger', 'elephant', 'tiger']
+ end
+
+ it "returns a string containing dumped YAML when no io provided" do
+ YAML.dump( :locked ).should match_yaml("--- :locked\n")
+ end
+
+ it "returns the same string that #to_yaml on objects" do
+ ["a", "b", "c"].to_yaml.should == YAML.dump(["a", "b", "c"])
+ end
+
+ it "dumps strings into YAML strings" do
+ YAML.dump("str").should match_yaml("--- str\n")
+ end
+
+ it "dumps hashes into YAML key-values" do
+ YAML.dump({ "a" => "b" }).should match_yaml("--- \na: b\n")
+ end
+
+ it "dumps Arrays into YAML collection" do
+ YAML.dump(["a", "b", "c"]).should match_yaml("--- \n- a\n- b\n- c\n")
+ end
+
+ it "dumps an OpenStruct" do
+ require "ostruct"
+ os = OpenStruct.new("age" => 20, "name" => "John")
+ yaml_dump = YAML.dump(os)
+
+ [
+ "--- !ruby/object:OpenStruct\nage: 20\nname: John\n",
+ "--- !ruby/object:OpenStruct\ntable:\n :age: 20\n :name: John\n",
+ ].should.include?(yaml_dump)
+ end
+
+ it "dumps a File without any state" do
+ file = File.new(__FILE__)
+ begin
+ YAML.dump(file).should match_yaml("--- !ruby/object:File {}\n")
+ ensure
+ file.close
+ end
+ end
+end
diff --git a/spec/ruby/library/yaml/dump_stream_spec.rb b/spec/ruby/library/yaml/dump_stream_spec.rb
new file mode 100644
index 0000000000..9d30fef819
--- /dev/null
+++ b/spec/ruby/library/yaml/dump_stream_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "YAML.dump_stream" do
+ it "returns a YAML stream containing the objects passed" do
+ YAML.dump_stream('foo', 20, [], {}).should match_yaml("--- foo\n--- 20\n--- []\n\n--- {}\n\n")
+ end
+end
diff --git a/spec/ruby/library/yaml/fixtures/common.rb b/spec/ruby/library/yaml/fixtures/common.rb
new file mode 100644
index 0000000000..f7fb4037e7
--- /dev/null
+++ b/spec/ruby/library/yaml/fixtures/common.rb
@@ -0,0 +1,4 @@
+require 'yaml'
+
+$test_file = tmp("yaml_test_file")
+$test_parse_file = File.dirname(__FILE__) + "/test_yaml.yml"
diff --git a/spec/ruby/library/yaml/fixtures/example_class.rb b/spec/ruby/library/yaml/fixtures/example_class.rb
new file mode 100644
index 0000000000..8259870799
--- /dev/null
+++ b/spec/ruby/library/yaml/fixtures/example_class.rb
@@ -0,0 +1,7 @@
+module YAMLSpecs
+ class Example
+ def initialize(name)
+ @name = name
+ end
+ end
+end
diff --git a/spec/ruby/library/yaml/fixtures/strings.rb b/spec/ruby/library/yaml/fixtures/strings.rb
new file mode 100644
index 0000000000..6f66dc3659
--- /dev/null
+++ b/spec/ruby/library/yaml/fixtures/strings.rb
@@ -0,0 +1,36 @@
+$complex_key_1 = <<EOY
+ ? # PLAY SCHEDULE
+ - Detroit Tigers
+ - Chicago Cubs
+ :
+ - 2001-07-23
+
+ ? [ New York Yankees,
+ Atlanta Braves ]
+ : [ 2001-07-02, 2001-08-12,
+ 2001-08-14 ]
+EOY
+
+$to_yaml_hash =
+<<EOY
+-
+ avg: 0.278
+ hr: 65
+ name: Mark McGwire
+-
+ avg: 0.288
+ hr: 63
+ name: Sammy Sosa
+EOY
+
+$multidocument = <<EOY
+---
+- Mark McGwire
+- Sammy Sosa
+- Ken Griffey
+
+# Team ranking
+---
+- Chicago Cubs
+- St Louis Cardinals
+EOY
diff --git a/spec/ruby/library/yaml/fixtures/test_yaml.yml b/spec/ruby/library/yaml/fixtures/test_yaml.yml
new file mode 100644
index 0000000000..efe3b5cc1a
--- /dev/null
+++ b/spec/ruby/library/yaml/fixtures/test_yaml.yml
@@ -0,0 +1,2 @@
+project:
+ name: RubySpec
diff --git a/spec/ruby/library/yaml/load_file_spec.rb b/spec/ruby/library/yaml/load_file_spec.rb
new file mode 100644
index 0000000000..2363c08120
--- /dev/null
+++ b/spec/ruby/library/yaml/load_file_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "YAML.load_file" do
+ after :each do
+ rm_r $test_file
+ end
+
+ it "returns a hash" do
+ File.open($test_file,'w' ){|io| YAML.dump( {"bar"=>2, "car"=>1}, io ) }
+ YAML.load_file($test_file).should == {"bar"=>2, "car"=>1}
+ end
+end
diff --git a/spec/ruby/library/yaml/load_spec.rb b/spec/ruby/library/yaml/load_spec.rb
new file mode 100644
index 0000000000..56700a85f9
--- /dev/null
+++ b/spec/ruby/library/yaml/load_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/load'
+
+describe "YAML.load" do
+ it_behaves_like :yaml_load_safe, :load
+
+ guard -> { Psych::VERSION < "4.0.0" } do
+ it_behaves_like :yaml_load_unsafe, :load
+ end
+end
diff --git a/spec/ruby/library/yaml/load_stream_spec.rb b/spec/ruby/library/yaml/load_stream_spec.rb
new file mode 100644
index 0000000000..689653c8cd
--- /dev/null
+++ b/spec/ruby/library/yaml/load_stream_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'fixtures/strings'
+require_relative 'shared/each_document'
+
+describe "YAML.load_stream" do
+ it_behaves_like :yaml_each_document, :load_stream
+end
diff --git a/spec/ruby/library/yaml/parse_file_spec.rb b/spec/ruby/library/yaml/parse_file_spec.rb
new file mode 100644
index 0000000000..8c59a2d7ef
--- /dev/null
+++ b/spec/ruby/library/yaml/parse_file_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "YAML#parse_file" do
+ it "returns a YAML::Syck::Map object after parsing a YAML file" do
+ YAML.parse_file($test_parse_file).should be_kind_of(Psych::Nodes::Document)
+ end
+end
diff --git a/spec/ruby/library/yaml/parse_spec.rb b/spec/ruby/library/yaml/parse_spec.rb
new file mode 100644
index 0000000000..d5dbfdcee2
--- /dev/null
+++ b/spec/ruby/library/yaml/parse_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "YAML#parse with an empty string" do
+ it "returns false" do
+ YAML.parse('').should be_false
+ end
+end
+
+describe "YAML#parse" do
+ before :each do
+ @string_yaml = "foo".to_yaml
+ end
+
+ it "returns the value from the object" do
+ if YAML.to_s == "Psych"
+ YAML.parse(@string_yaml).to_ruby.should == "foo"
+ else
+ YAML.parse(@string_yaml).value.should == "foo"
+ end
+ end
+end
diff --git a/spec/ruby/library/yaml/shared/each_document.rb b/spec/ruby/library/yaml/shared/each_document.rb
new file mode 100644
index 0000000000..999123dc2a
--- /dev/null
+++ b/spec/ruby/library/yaml/shared/each_document.rb
@@ -0,0 +1,18 @@
+describe :yaml_each_document, shared: true do
+ it "calls the block on each successive document" do
+ documents = []
+ YAML.send(@method, $multidocument) do |doc|
+ documents << doc
+ end
+ documents.should == [["Mark McGwire", "Sammy Sosa", "Ken Griffey"],
+ ["Chicago Cubs", "St Louis Cardinals"]]
+ end
+
+ it "works on files" do
+ File.open($test_parse_file, "r") do |file|
+ YAML.send(@method, file) do |doc|
+ doc.should == {"project"=>{"name"=>"RubySpec"}}
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/yaml/shared/load.rb b/spec/ruby/library/yaml/shared/load.rb
new file mode 100644
index 0000000000..185a5a60cd
--- /dev/null
+++ b/spec/ruby/library/yaml/shared/load.rb
@@ -0,0 +1,136 @@
+require_relative '../fixtures/common'
+require_relative '../fixtures/strings'
+
+describe :yaml_load_safe, shared: true do
+ it "returns a document from current io stream when io provided" do
+ File.open($test_file, 'w') do |io|
+ YAML.dump( ['badger', 'elephant', 'tiger'], io )
+ end
+ File.open($test_file) { |yf| YAML.send(@method, yf ) }.should == ['badger', 'elephant', 'tiger']
+ ensure
+ rm_r $test_file
+ end
+
+ it "loads strings" do
+ strings = ["str",
+ " str",
+ "'str'",
+ "str",
+ " str",
+ "'str'",
+ "\"str\"",
+ "\n str",
+ "--- str",
+ "---\nstr",
+ "--- \nstr",
+ "--- \n str",
+ "--- 'str'"
+ ]
+ strings.each do |str|
+ YAML.send(@method, str).should == "str"
+ end
+ end
+
+ it "loads strings with chars from non-base Unicode plane" do
+ # We add these strings as bytes and force the encoding for safety
+ # as bugs in parsing unicode characters can obscure bugs in this
+ # area.
+
+ yaml_and_strings = {
+ # "--- 🌵" => "🌵"
+ [45, 45, 45, 32, 240, 159, 140, 181] =>
+ [240, 159, 140, 181],
+ # "--- 🌵 and some text" => "🌵 and some text"
+ [45, 45, 45, 32, 240, 159, 140, 181, 32, 97, 110, 100, 32, 115, 111, 109, 101, 32, 116, 101, 120, 116] =>
+ [240, 159, 140, 181, 32, 97, 110, 100, 32, 115, 111, 109, 101, 32, 116, 101, 120, 116],
+ # "--- Some text 🌵 and some text" => "Some text 🌵 and some text"
+ [45, 45, 45, 32, 83, 111, 109, 101, 32, 116, 101, 120, 116, 32, 240, 159, 140, 181, 32, 97, 110, 100, 32, 115, 111, 109, 101, 32, 116, 101, 120, 116] =>
+ [83, 111, 109, 101, 32, 116, 101, 120, 116, 32, 240, 159, 140, 181, 32, 97, 110, 100, 32, 115, 111, 109, 101, 32, 116, 101, 120, 116]
+ }
+ yaml_and_strings.each do |yaml, str|
+ YAML.send(@method, yaml.pack("C*").force_encoding("UTF-8")).should == str.pack("C*").force_encoding("UTF-8")
+ end
+ end
+
+ it "fails on invalid keys" do
+ if YAML.to_s == "Psych"
+ error = Psych::SyntaxError
+ else
+ error = ArgumentError
+ end
+ -> { YAML.send(@method, "key1: value\ninvalid_key") }.should raise_error(error)
+ end
+
+ it "accepts symbols" do
+ YAML.send(@method, "--- :locked" ).should == :locked
+ end
+
+ it "accepts numbers" do
+ YAML.send(@method, "47").should == 47
+ YAML.send(@method, "-1").should == -1
+ end
+
+ it "accepts collections" do
+ expected = ["a", "b", "c"]
+ YAML.send(@method, "--- \n- a\n- b\n- c\n").should == expected
+ YAML.send(@method, "--- [a, b, c]").should == expected
+ YAML.send(@method, "[a, b, c]").should == expected
+ end
+
+ it "parses start markers" do
+ YAML.send(@method, "---\n").should == nil
+ YAML.send(@method, "--- ---\n").should == "---"
+ YAML.send(@method, "--- abc").should == "abc"
+ end
+
+ it "works with block sequence shortcuts" do
+ block_seq = "- - - one\n - two\n - three"
+ YAML.send(@method, block_seq).should == [[["one", "two", "three"]]]
+ end
+
+ it "loads a symbol key that contains spaces" do
+ string = ":user name: This is the user name."
+ expected = { :"user name" => "This is the user name."}
+ YAML.send(@method, string).should == expected
+ end
+end
+
+describe :yaml_load_unsafe, shared: true do
+ it "works on complex keys" do
+ require 'date'
+ expected = {
+ [ 'Detroit Tigers', 'Chicago Cubs' ] => [ Date.new( 2001, 7, 23 ) ],
+ [ 'New York Yankees', 'Atlanta Braves' ] => [ Date.new( 2001, 7, 2 ),
+ Date.new( 2001, 8, 12 ),
+ Date.new( 2001, 8, 14 ) ]
+ }
+ YAML.send(@method, $complex_key_1).should == expected
+ end
+
+ describe "with iso8601 timestamp" do
+ it "computes the microseconds" do
+ [ [YAML.send(@method, "2011-03-22t23:32:11.2233+01:00"), 223300],
+ [YAML.send(@method, "2011-03-22t23:32:11.0099+01:00"), 9900],
+ [YAML.send(@method, "2011-03-22t23:32:11.000076+01:00"), 76]
+ ].should be_computed_by(:usec)
+ end
+
+ it "rounds values smaller than 1 usec to 0 " do
+ YAML.send(@method, "2011-03-22t23:32:11.000000342222+01:00").usec.should == 0
+ end
+ end
+
+ it "loads an OpenStruct" do
+ require "ostruct"
+ os = OpenStruct.new("age" => 20, "name" => "John")
+ loaded = YAML.send(@method, "--- !ruby/object:OpenStruct\ntable:\n :age: 20\n :name: John\n")
+ loaded.should == os
+ end
+
+ it "loads a File but raise an error when used as it is uninitialized" do
+ loaded = YAML.send(@method, "--- !ruby/object:File {}\n")
+ -> {
+ loaded.read(1)
+ }.should raise_error(IOError)
+ end
+end
diff --git a/spec/ruby/library/yaml/to_yaml_spec.rb b/spec/ruby/library/yaml/to_yaml_spec.rb
new file mode 100644
index 0000000000..8e80b02cb4
--- /dev/null
+++ b/spec/ruby/library/yaml/to_yaml_spec.rb
@@ -0,0 +1,98 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'fixtures/example_class'
+
+describe "Object#to_yaml" do
+
+ it "returns the YAML representation of an Array object" do
+ %w( 30 ruby maz irb 99 ).to_yaml.gsub("'", '"').should match_yaml("--- \n- \"30\"\n- ruby\n- maz\n- irb\n- \"99\"\n")
+ end
+
+ it "returns the YAML representation of a Hash object" do
+ { "a" => "b"}.to_yaml.should match_yaml("--- \na: b\n")
+ end
+
+ it "returns the YAML representation of a Class object" do
+ YAMLSpecs::Example.new("baz").to_yaml.should match_yaml("--- !ruby/object:YAMLSpecs::Example\nname: baz\n")
+ end
+
+ it "returns the YAML representation of a Date object" do
+ require 'date'
+ Date.parse('1997/12/30').to_yaml.should match_yaml("--- 1997-12-30\n")
+ end
+
+ it "returns the YAML representation of a FalseClass" do
+ false_klass = false
+ false_klass.should be_kind_of(FalseClass)
+ false_klass.to_yaml.should match_yaml("--- false\n")
+ end
+
+ it "returns the YAML representation of a Float object" do
+ float = 1.2
+ float.should be_kind_of(Float)
+ float.to_yaml.should match_yaml("--- 1.2\n")
+ end
+
+ it "returns the YAML representation of an Integer object" do
+ int = 20
+ int.should be_kind_of(Integer)
+ int.to_yaml.should match_yaml("--- 20\n")
+ end
+
+ it "returns the YAML representation of a NilClass object" do
+ nil_klass = nil
+ nil_klass.should be_kind_of(NilClass)
+ nil_klass.to_yaml.should match_yaml("--- \n")
+ end
+
+ it "returns the YAML representation of a RegExp object" do
+ Regexp.new('^a-z+:\\s+\w+').to_yaml.should match_yaml("--- !ruby/regexp /^a-z+:\\s+\\w+/\n")
+ end
+
+ it "returns the YAML representation of a String object" do
+ "I love Ruby".to_yaml.should match_yaml("--- I love Ruby\n")
+ end
+
+ it "returns the YAML representation of a Struct object" do
+ Person = Struct.new(:name, :gender)
+ Person.new("Jane", "female").to_yaml.should match_yaml("--- !ruby/struct:Person\nname: Jane\ngender: female\n")
+ end
+
+ it "returns the YAML representation of a Symbol object" do
+ :symbol.to_yaml.should match_yaml("--- :symbol\n")
+ end
+
+ it "returns the YAML representation of a Time object" do
+ Time.utc(2000,"jan",1,20,15,1).to_yaml.sub(/\.0+/, "").should match_yaml("--- 2000-01-01 20:15:01 Z\n")
+ end
+
+ it "returns the YAML representation of a TrueClass" do
+ true_klass = true
+ true_klass.should be_kind_of(TrueClass)
+ true_klass.to_yaml.should match_yaml("--- true\n")
+ end
+
+ it "returns the YAML representation of a Error object" do
+ StandardError.new("foobar").to_yaml.should match_yaml("--- !ruby/exception:StandardError\nmessage: foobar\nbacktrace: \n")
+ end
+
+ it "returns the YAML representation for Range objects" do
+ yaml = Range.new(1,3).to_yaml
+ yaml.include?("!ruby/range").should be_true
+ yaml.include?("begin: 1").should be_true
+ yaml.include?("end: 3").should be_true
+ yaml.include?("excl: false").should be_true
+ end
+
+ it "returns the YAML representation of numeric constants" do
+ nan_value.to_yaml.downcase.should match_yaml("--- .nan\n")
+ infinity_value.to_yaml.downcase.should match_yaml("--- .inf\n")
+ (-infinity_value).to_yaml.downcase.should match_yaml("--- -.inf\n")
+ (0.0).to_yaml.should match_yaml("--- 0.0\n")
+ end
+
+ it "returns the YAML representation of an array of hashes" do
+ players = [{"a" => "b"}, {"b" => "c"}]
+ players.to_yaml.should match_yaml("--- \n- a: b\n- b: c\n")
+ end
+end
diff --git a/spec/ruby/library/yaml/unsafe_load_spec.rb b/spec/ruby/library/yaml/unsafe_load_spec.rb
new file mode 100644
index 0000000000..385cd2a6e2
--- /dev/null
+++ b/spec/ruby/library/yaml/unsafe_load_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'shared/load'
+
+guard -> { Psych::VERSION >= "4.0.0" } do
+ describe "YAML.unsafe_load" do
+ it_behaves_like :yaml_load_safe, :unsafe_load
+ it_behaves_like :yaml_load_unsafe, :unsafe_load
+ end
+end
diff --git a/spec/ruby/library/zlib/adler32_spec.rb b/spec/ruby/library/zlib/adler32_spec.rb
new file mode 100644
index 0000000000..226aa18522
--- /dev/null
+++ b/spec/ruby/library/zlib/adler32_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require 'zlib'
+
+describe "Zlib.adler32" do
+ it "calculates Adler checksum for string" do
+ Zlib.adler32("").should == 1
+ Zlib.adler32(" ").should == 2162721
+ Zlib.adler32("123456789").should == 152961502
+ Zlib.adler32("!@#\{$\}%^&**()").should == 365495023
+ Zlib.adler32("to be or not to be" * 22).should == 3979904837
+ Zlib.adler32("0").should == 3211313
+ Zlib.adler32((2**32).to_s).should == 193331739
+ Zlib.adler32((2**64).to_s).should == 723452953
+ end
+
+ it "calculates Adler checksum for string and initial Adler value" do
+ test_string = "This is a test string! How exciting!%?"
+ Zlib.adler32(test_string, 0).should == 63900955
+ Zlib.adler32(test_string, 1).should == 66391324
+ Zlib.adler32(test_string, 2**8).should == 701435419
+ Zlib.adler32(test_string, 2**16).should == 63966491
+ -> { Zlib.adler32(test_string, 2**128) }.should raise_error(RangeError)
+ end
+
+ it "calculates the Adler checksum for string and initial Adler value for Integers" do
+ test_string = "This is a test string! How exciting!%?"
+ Zlib.adler32(test_string, 2**30).should == 1137642779
+ end
+
+ it "assumes that the initial value is given to adler, if adler is omitted" do
+ orig_crc = Zlib.adler32
+ Zlib.adler32("").should == Zlib.adler32("", orig_crc)
+ Zlib.adler32(" ").should == Zlib.adler32(" ", orig_crc)
+ Zlib.adler32("123456789").should == Zlib.adler32("123456789", orig_crc)
+ Zlib.adler32("!@#\{$\}%^&**()").should == Zlib.adler32("!@#\{$\}%^&**()", orig_crc)
+ Zlib.adler32("to be or not to be" * 22).should == Zlib.adler32("to be or not to be" * 22, orig_crc)
+ Zlib.adler32("0").should == Zlib.adler32("0", orig_crc)
+ Zlib.adler32((2**32).to_s).should == Zlib.adler32((2**32).to_s, orig_crc)
+ Zlib.adler32((2**64).to_s).should == Zlib.adler32((2**64).to_s, orig_crc)
+ end
+
+ it "it returns the CRC initial value, if string is omitted" do
+ Zlib.adler32.should == 1
+ end
+
+end
diff --git a/spec/ruby/library/zlib/crc32_spec.rb b/spec/ruby/library/zlib/crc32_spec.rb
new file mode 100644
index 0000000000..d5f5c199cc
--- /dev/null
+++ b/spec/ruby/library/zlib/crc32_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require 'zlib'
+
+describe "Zlib.crc32" do
+ it "calculates CRC checksum for string" do
+ Zlib.crc32("").should == 0
+ Zlib.crc32(" ").should == 3916222277
+ Zlib.crc32("123456789").should == 3421780262
+ Zlib.crc32("!@#\{$\}%^&**()").should == 2824518887
+ Zlib.crc32("to be or not to be" * 22).should == 1832379978
+ Zlib.crc32("0").should == 4108050209
+ Zlib.crc32((2**32).to_s).should == 3267533297
+ Zlib.crc32((2**64).to_s).should == 653721760
+ end
+
+ it "calculates CRC checksum for string and initial CRC value" do
+ test_string = "This is a test string! How exciting!%?"
+ # Zlib.crc32(test_string, -2**28).should == 3230195786
+ # Zlib.crc32(test_string, -2**20).should == 2770207303
+ # Zlib.crc32(test_string, -2**16).should == 2299432960
+ # Zlib.crc32(test_string, -2**8).should == 861809849
+ # Zlib.crc32(test_string, -1).should == 2170124077
+ Zlib.crc32(test_string, 0).should == 3864990561
+ Zlib.crc32(test_string, 1).should == 1809313411
+ Zlib.crc32(test_string, 2**8).should == 1722745982
+ Zlib.crc32(test_string, 2**16).should == 1932511220
+ Zlib.crc32("p", ~305419896).should == 4046865307
+ Zlib.crc32("p", -305419897).should == 4046865307
+ -> { Zlib.crc32(test_string, 2**128) }.should raise_error(RangeError)
+ end
+
+ it "calculates the CRC checksum for string and initial CRC value for Integers" do
+ test_string = "This is a test string! How exciting!%?"
+ # Zlib.crc32(test_string, -2**30).should == 277228695
+ Zlib.crc32(test_string, 2**30).should == 46597132
+ end
+
+ it "assumes that the initial value is given to crc, if crc is omitted" do
+ orig_crc = Zlib.crc32
+ Zlib.crc32("").should == Zlib.crc32("", orig_crc)
+ Zlib.crc32(" ").should == Zlib.crc32(" ", orig_crc)
+ Zlib.crc32("123456789").should == Zlib.crc32("123456789", orig_crc)
+ Zlib.crc32("!@#\{$\}%^&**()").should == Zlib.crc32("!@#\{$\}%^&**()", orig_crc)
+ Zlib.crc32("to be or not to be" * 22).should == Zlib.crc32("to be or not to be" * 22, orig_crc)
+ Zlib.crc32("0").should == Zlib.crc32("0", orig_crc)
+ Zlib.crc32((2**32).to_s).should == Zlib.crc32((2**32).to_s, orig_crc)
+ Zlib.crc32((2**64).to_s).should == Zlib.crc32((2**64).to_s, orig_crc)
+ end
+
+ it "it returns the CRC initial value, if string is omitted" do
+ Zlib.crc32.should == 0
+ end
+
+end
diff --git a/spec/ruby/library/zlib/crc_table_spec.rb b/spec/ruby/library/zlib/crc_table_spec.rb
new file mode 100644
index 0000000000..de8876086b
--- /dev/null
+++ b/spec/ruby/library/zlib/crc_table_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../../spec_helper'
+require "zlib"
+
+describe "Zlib.crc_table" do
+ # This spec fails when zlib.h and libz.so are not from the same version.
+ # In older zlib (< 1.2.7 it seems), get_crc_table() is stored as u64[],
+ # but in newer zlib, get_crc_table() is stored as u32[].
+ # Technically, there is ABI breakage between those zlib versions,
+ # but get_crc_table() is an "undocumented function" according to zlib.h.
+ guard -> { ENV["RUBY_SPEC_TEST_ZLIB_CRC_TABLE"] != "false" } do
+ it "returns the same value as zlib's get_crc_table()" do
+ Zlib.crc_table.should == [
+ 0, 1996959894, 3993919788, 2567524794,
+ 124634137, 1886057615, 3915621685, 2657392035,
+ 249268274, 2044508324, 3772115230, 2547177864,
+ 162941995, 2125561021, 3887607047, 2428444049,
+ 498536548, 1789927666, 4089016648, 2227061214,
+ 450548861, 1843258603, 4107580753, 2211677639,
+ 325883990, 1684777152, 4251122042, 2321926636,
+ 335633487, 1661365465, 4195302755, 2366115317,
+ 997073096, 1281953886, 3579855332, 2724688242,
+ 1006888145, 1258607687, 3524101629, 2768942443,
+ 901097722, 1119000684, 3686517206, 2898065728,
+ 853044451, 1172266101, 3705015759, 2882616665,
+ 651767980, 1373503546, 3369554304, 3218104598,
+ 565507253, 1454621731, 3485111705, 3099436303,
+ 671266974, 1594198024, 3322730930, 2970347812,
+ 795835527, 1483230225, 3244367275, 3060149565,
+ 1994146192, 31158534, 2563907772, 4023717930,
+ 1907459465, 112637215, 2680153253, 3904427059,
+ 2013776290, 251722036, 2517215374, 3775830040,
+ 2137656763, 141376813, 2439277719, 3865271297,
+ 1802195444, 476864866, 2238001368, 4066508878,
+ 1812370925, 453092731, 2181625025, 4111451223,
+ 1706088902, 314042704, 2344532202, 4240017532,
+ 1658658271, 366619977, 2362670323, 4224994405,
+ 1303535960, 984961486, 2747007092, 3569037538,
+ 1256170817, 1037604311, 2765210733, 3554079995,
+ 1131014506, 879679996, 2909243462, 3663771856,
+ 1141124467, 855842277, 2852801631, 3708648649,
+ 1342533948, 654459306, 3188396048, 3373015174,
+ 1466479909, 544179635, 3110523913, 3462522015,
+ 1591671054, 702138776, 2966460450, 3352799412,
+ 1504918807, 783551873, 3082640443, 3233442989,
+ 3988292384, 2596254646, 62317068, 1957810842,
+ 3939845945, 2647816111, 81470997, 1943803523,
+ 3814918930, 2489596804, 225274430, 2053790376,
+ 3826175755, 2466906013, 167816743, 2097651377,
+ 4027552580, 2265490386, 503444072, 1762050814,
+ 4150417245, 2154129355, 426522225, 1852507879,
+ 4275313526, 2312317920, 282753626, 1742555852,
+ 4189708143, 2394877945, 397917763, 1622183637,
+ 3604390888, 2714866558, 953729732, 1340076626,
+ 3518719985, 2797360999, 1068828381, 1219638859,
+ 3624741850, 2936675148, 906185462, 1090812512,
+ 3747672003, 2825379669, 829329135, 1181335161,
+ 3412177804, 3160834842, 628085408, 1382605366,
+ 3423369109, 3138078467, 570562233, 1426400815,
+ 3317316542, 2998733608, 733239954, 1555261956,
+ 3268935591, 3050360625, 752459403, 1541320221,
+ 2607071920, 3965973030, 1969922972, 40735498,
+ 2617837225, 3943577151, 1913087877, 83908371,
+ 2512341634, 3803740692, 2075208622, 213261112,
+ 2463272603, 3855990285, 2094854071, 198958881,
+ 2262029012, 4057260610, 1759359992, 534414190,
+ 2176718541, 4139329115, 1873836001, 414664567,
+ 2282248934, 4279200368, 1711684554, 285281116,
+ 2405801727, 4167216745, 1634467795, 376229701,
+ 2685067896, 3608007406, 1308918612, 956543938,
+ 2808555105, 3495958263, 1231636301, 1047427035,
+ 2932959818, 3654703836, 1088359270, 936918000,
+ 2847714899, 3736837829, 1202900863, 817233897,
+ 3183342108, 3401237130, 1404277552, 615818150,
+ 3134207493, 3453421203, 1423857449, 601450431,
+ 3009837614, 3294710456, 1567103746, 711928724,
+ 3020668471, 3272380065, 1510334235, 755167117,
+ ]
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/deflate/deflate_spec.rb b/spec/ruby/library/zlib/deflate/deflate_spec.rb
new file mode 100644
index 0000000000..50a563ef6f
--- /dev/null
+++ b/spec/ruby/library/zlib/deflate/deflate_spec.rb
@@ -0,0 +1,133 @@
+require 'zlib'
+require_relative '../../../spec_helper'
+
+describe "Zlib::Deflate.deflate" do
+ it "deflates some data" do
+ data = Array.new(10,0).pack('C*')
+
+ zipped = Zlib::Deflate.deflate data
+
+ zipped.should == [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1].pack('C*')
+ end
+
+ it "deflates lots of data" do
+ data = "\000" * 32 * 1024
+
+ zipped = Zlib::Deflate.deflate data
+
+ zipped.should == ([120, 156, 237, 193, 1, 1, 0, 0] +
+ [0, 128, 144, 254, 175, 238, 8, 10] +
+ Array.new(31, 0) +
+ [24, 128, 0, 0, 1]).pack('C*')
+ end
+
+ it "deflates chunked data" do
+ random_generator = Random.new(0)
+ deflated = ''
+
+ Zlib::Deflate.deflate(random_generator.bytes(20000)) do |chunk|
+ deflated << chunk
+ end
+
+ deflated.length.should == 20016
+ end
+end
+
+describe "Zlib::Deflate#deflate" do
+ before :each do
+ @deflator = Zlib::Deflate.new
+ end
+
+ it "deflates some data" do
+ data = "\000" * 10
+
+ zipped = @deflator.deflate data, Zlib::FINISH
+ @deflator.finish
+
+ zipped.should == [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1].pack('C*')
+ end
+
+ it "deflates lots of data" do
+ data = "\000" * 32 * 1024
+
+ zipped = @deflator.deflate data, Zlib::FINISH
+ @deflator.finish
+
+ zipped.should == ([120, 156, 237, 193, 1, 1, 0, 0] +
+ [0, 128, 144, 254, 175, 238, 8, 10] +
+ Array.new(31, 0) +
+ [24, 128, 0, 0, 1]).pack('C*')
+ end
+
+ it "has a binary encoding" do
+ @deflator.deflate("").encoding.should == Encoding::BINARY
+ @deflator.finish.encoding.should == Encoding::BINARY
+ end
+end
+
+describe "Zlib::Deflate#deflate" do
+
+ before :each do
+ @deflator = Zlib::Deflate.new
+ @random_generator = Random.new(0)
+ @original = ''
+ @chunks = []
+ end
+
+ describe "without break" do
+
+ before do
+ 2.times do
+ @input = @random_generator.bytes(20000)
+ @original << @input
+ @deflator.deflate(@input) do |chunk|
+ @chunks << chunk
+ end
+ end
+ end
+
+ it "deflates chunked data" do
+ @deflator.finish
+ @chunks.map { |chunk| chunk.length }.should == [16384, 16384]
+ end
+
+ it "deflates chunked data with final chunk" do
+ final = @deflator.finish
+ final.length.should == 7253
+ end
+
+ it "deflates chunked data without errors" do
+ final = @deflator.finish
+ @chunks << final
+ @original.should == Zlib.inflate(@chunks.join)
+ end
+
+ end
+
+ describe "with break" do
+ before :each do
+ @input = @random_generator.bytes(20000)
+ @deflator.deflate(@input) do |chunk|
+ @chunks << chunk
+ break
+ end
+ end
+
+ it "deflates only first chunk" do
+ @deflator.finish
+ @chunks.map { |chunk| chunk.length }.should == [16384]
+ end
+
+ it "deflates chunked data with final chunk" do
+ final = @deflator.finish
+ final.length.should == 3632
+ end
+
+ it "deflates chunked data without errors" do
+ final = @deflator.finish
+ @chunks << final
+ @input.should == Zlib.inflate(@chunks.join)
+ end
+
+ end
+end
diff --git a/spec/ruby/library/zlib/deflate/new_spec.rb b/spec/ruby/library/zlib/deflate/new_spec.rb
new file mode 100644
index 0000000000..e15f14f95f
--- /dev/null
+++ b/spec/ruby/library/zlib/deflate/new_spec.rb
@@ -0,0 +1 @@
+require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/zlib/deflate/params_spec.rb b/spec/ruby/library/zlib/deflate/params_spec.rb
new file mode 100644
index 0000000000..0b1cca8c8a
--- /dev/null
+++ b/spec/ruby/library/zlib/deflate/params_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+require 'zlib'
+
+describe "Zlib::Deflate#params" do
+ it "changes the deflate parameters" do
+ data = 'abcdefghijklm'
+
+ d = Zlib::Deflate.new Zlib::NO_COMPRESSION, Zlib::MAX_WBITS,
+ Zlib::DEF_MEM_LEVEL, Zlib::DEFAULT_STRATEGY
+
+ d << data.slice!(0..10)
+ d.params Zlib::BEST_COMPRESSION, Zlib::DEFAULT_STRATEGY
+ d << data
+
+ Zlib::Inflate.inflate(d.finish).should == 'abcdefghijklm'
+ end
+end
diff --git a/spec/ruby/library/zlib/deflate/set_dictionary_spec.rb b/spec/ruby/library/zlib/deflate/set_dictionary_spec.rb
new file mode 100644
index 0000000000..0e461229c7
--- /dev/null
+++ b/spec/ruby/library/zlib/deflate/set_dictionary_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require 'zlib'
+
+describe "Zlib::Deflate#set_dictionary" do
+ it "sets the dictionary" do
+ d = Zlib::Deflate.new
+ d.set_dictionary 'aaaaaaaaaa'
+ d << 'abcdefghij'
+
+ d.finish.should == [120, 187, 20, 225, 3, 203, 75, 76,
+ 74, 78, 73, 77, 75, 207, 200, 204,
+ 2, 0, 21, 134, 3, 248].pack('C*')
+ end
+end
diff --git a/spec/ruby/library/zlib/deflate_spec.rb b/spec/ruby/library/zlib/deflate_spec.rb
new file mode 100644
index 0000000000..6eeaa164c5
--- /dev/null
+++ b/spec/ruby/library/zlib/deflate_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require "zlib"
+
+describe "Zlib.deflate" do
+ it "deflates some data" do
+ Zlib.deflate("1" * 10).should == [120, 156, 51, 52, 132, 1, 0, 10, 145, 1, 235].pack('C*')
+ end
+end
diff --git a/spec/ruby/library/zlib/gunzip_spec.rb b/spec/ruby/library/zlib/gunzip_spec.rb
new file mode 100644
index 0000000000..2417fed57c
--- /dev/null
+++ b/spec/ruby/library/zlib/gunzip_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require 'zlib'
+
+describe "Zlib.gunzip" do
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+ end
+
+ it "decodes the given gzipped string" do
+ Zlib.gunzip(@zip).should == @data
+ end
+end
diff --git a/spec/ruby/library/zlib/gzip_spec.rb b/spec/ruby/library/zlib/gzip_spec.rb
new file mode 100644
index 0000000000..35694264f0
--- /dev/null
+++ b/spec/ruby/library/zlib/gzip_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require 'zlib'
+
+describe "Zlib.gzip" do
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+ end
+
+ it "gzips the given string" do
+ # skip gzip header for now
+ Zlib.gzip(@data)[10..-1].should == @zip[10..-1]
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipfile/close_spec.rb b/spec/ruby/library/zlib/gzipfile/close_spec.rb
new file mode 100644
index 0000000000..964b5ffb4d
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipfile/close_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipFile#close" do
+ it "finishes the stream and closes the io" do
+ io = StringIO.new "".b
+ Zlib::GzipWriter.wrap io do |gzio|
+ gzio.close
+
+ gzio.should.closed?
+
+ -> { gzio.orig_name }.should \
+ raise_error(Zlib::GzipFile::Error, 'closed gzip stream')
+ -> { gzio.comment }.should \
+ raise_error(Zlib::GzipFile::Error, 'closed gzip stream')
+ end
+
+ io.string[10..-1].should == ([3] + Array.new(9,0)).pack('C*')
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipfile/closed_spec.rb b/spec/ruby/library/zlib/gzipfile/closed_spec.rb
new file mode 100644
index 0000000000..726f391b41
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipfile/closed_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipFile#closed?" do
+ it "returns the closed status" do
+ io = StringIO.new
+ Zlib::GzipWriter.wrap io do |gzio|
+ gzio.should_not.closed?
+
+ gzio.close
+
+ gzio.should.closed?
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipfile/comment_spec.rb b/spec/ruby/library/zlib/gzipfile/comment_spec.rb
new file mode 100644
index 0000000000..70d97ecaf6
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipfile/comment_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipFile#comment" do
+ before :each do
+ @io = StringIO.new
+ end
+
+ it "returns the name" do
+ Zlib::GzipWriter.wrap @io do |gzio|
+ gzio.comment = 'name'
+
+ gzio.comment.should == 'name'
+ end
+ end
+
+ it "raises an error on a closed stream" do
+ Zlib::GzipWriter.wrap @io do |gzio|
+ gzio.close
+
+ -> { gzio.comment }.should \
+ raise_error(Zlib::GzipFile::Error, 'closed gzip stream')
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipfile/orig_name_spec.rb b/spec/ruby/library/zlib/gzipfile/orig_name_spec.rb
new file mode 100644
index 0000000000..ebfd3692af
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipfile/orig_name_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipFile#orig_name" do
+ before :each do
+ @io = StringIO.new
+ end
+
+ it "returns the name" do
+ Zlib::GzipWriter.wrap @io do |gzio|
+ gzio.orig_name = 'name'
+
+ gzio.orig_name.should == 'name'
+ end
+ end
+
+ it "raises an error on a closed stream" do
+ Zlib::GzipWriter.wrap @io do |gzio|
+ gzio.close
+
+ -> { gzio.orig_name }.should \
+ raise_error(Zlib::GzipFile::Error, 'closed gzip stream')
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipreader/each_byte_spec.rb b/spec/ruby/library/zlib/gzipreader/each_byte_spec.rb
new file mode 100644
index 0000000000..48821dc833
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/each_byte_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipReader#each_byte" do
+
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+
+ @io = StringIO.new @zip
+ ScratchPad.clear
+ end
+
+ it "calls the given block for each byte in the stream, passing the byte as an argument" do
+ gz = Zlib::GzipReader.new @io
+
+ ScratchPad.record []
+ gz.each_byte { |b| ScratchPad << b }
+
+ ScratchPad.recorded.should == [49, 50, 51, 52, 53, 97, 98, 99, 100, 101]
+ end
+
+ it "returns an enumerator, which yields each byte in the stream, when no block is passed" do
+ gz = Zlib::GzipReader.new @io
+ enum = gz.each_byte
+
+ ScratchPad.record []
+ while true
+ begin
+ ScratchPad << enum.next
+ rescue StopIteration
+ break
+ end
+ end
+
+ ScratchPad.recorded.should == [49, 50, 51, 52, 53, 97, 98, 99, 100, 101]
+ end
+
+ it "increments position before calling the block" do
+ gz = Zlib::GzipReader.new @io
+
+ i = 1
+ gz.each_byte do |ignore|
+ gz.pos.should == i
+ i += 1
+ end
+ end
+
+end
diff --git a/spec/ruby/library/zlib/gzipreader/each_line_spec.rb b/spec/ruby/library/zlib/gzipreader/each_line_spec.rb
new file mode 100644
index 0000000000..efaf27d6bb
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/each_line_spec.rb
@@ -0,0 +1,5 @@
+require_relative 'shared/each'
+
+describe "Zlib::GzipReader#each_line" do
+ it_behaves_like :gzipreader_each, :each_line
+end
diff --git a/spec/ruby/library/zlib/gzipreader/each_spec.rb b/spec/ruby/library/zlib/gzipreader/each_spec.rb
new file mode 100644
index 0000000000..59aa63e52c
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/each_spec.rb
@@ -0,0 +1,5 @@
+require_relative 'shared/each'
+
+describe "Zlib::GzipReader#each" do
+ it_behaves_like :gzipreader_each, :each
+end
diff --git a/spec/ruby/library/zlib/gzipreader/eof_spec.rb b/spec/ruby/library/zlib/gzipreader/eof_spec.rb
new file mode 100644
index 0000000000..673220fdfd
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/eof_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipReader#eof?" do
+ before :each do
+ @data = '{"a":1234}'
+ @zip = [31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 171, 86, 74, 84, 178, 50,
+ 52, 50, 54, 169, 5, 0, 196, 20, 118, 213, 10, 0, 0, 0].pack('C*')
+ @io = StringIO.new @zip
+ end
+
+ it "returns true when at EOF" do
+ gz = Zlib::GzipReader.new @io
+ gz.eof?.should be_false
+ gz.read
+ gz.eof?.should be_true
+ end
+
+ it "returns true when at EOF with the exact length of uncompressed data" do
+ gz = Zlib::GzipReader.new @io
+ gz.eof?.should be_false
+ gz.read(10)
+ gz.eof?.should be_true
+ end
+
+ it "returns true when at EOF with a length greater than the size of uncompressed data" do
+ gz = Zlib::GzipReader.new @io
+ gz.eof?.should be_false
+ gz.read(11)
+ gz.eof?.should be_true
+ end
+
+ it "returns false when at EOF when there's data left in the buffer to read" do
+ gz = Zlib::GzipReader.new @io
+ gz.read(9)
+ gz.eof?.should be_false
+ gz.read
+ gz.eof?.should be_true
+ end
+
+ # This is especially important for JRuby, since eof? there
+ # is more than just a simple accessor.
+ it "does not affect the reading data" do
+ gz = Zlib::GzipReader.new @io
+ 0.upto(9) do |i|
+ gz.eof?.should be_false
+ gz.read(1).should == @data[i, 1]
+ end
+ gz.eof?.should be_true
+ gz.read.should == ""
+ gz.eof?.should be_true
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipreader/getc_spec.rb b/spec/ruby/library/zlib/gzipreader/getc_spec.rb
new file mode 100644
index 0000000000..e567231940
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/getc_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipReader#getc" do
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+ @io = StringIO.new @zip
+ end
+
+ it "returns the next character from the stream" do
+ gz = Zlib::GzipReader.new @io
+ gz.pos.should == 0
+
+ gz.getc.should == '1'
+ gz.getc.should == '2'
+ gz.getc.should == '3'
+ gz.getc.should == '4'
+ gz.getc.should == '5'
+ end
+
+ it "increments position" do
+ gz = Zlib::GzipReader.new @io
+ (0..@data.size).each do |i|
+ gz.pos.should == i
+ gz.getc
+ end
+ end
+
+ it "returns nil at the end of the stream" do
+ gz = Zlib::GzipReader.new @io
+ gz.read
+ pos = gz.pos
+ gz.getc.should be_nil
+ gz.pos.should == pos
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipreader/gets_spec.rb b/spec/ruby/library/zlib/gzipreader/gets_spec.rb
new file mode 100644
index 0000000000..d3a2e7d263
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/gets_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require 'zlib'
+require 'stringio'
+
+describe 'Zlib::GzipReader#gets' do
+ describe 'with "" separator' do
+ it 'reads paragraphs skipping newlines' do
+ # gz contains "\n\n\n\n\n123\n45\n\n\n\n\nabc\nde\n\n\n\n\n"
+ gz = Zlib::GzipReader.new(
+ StringIO.new(
+ [31, 139, 8, 0, 223, 152, 48, 89, 0, 3, 227, 226, 2, 2, 67, 35,
+ 99, 46, 19, 83, 16, 139, 43, 49, 41, 153, 43, 37, 21, 204, 4, 0,
+ 32, 119, 45, 184, 27, 0, 0, 0].pack('C*')
+ )
+ )
+
+ gz.gets('').should == "123\n45\n\n"
+ gz.gets('').should == "abc\nde\n\n"
+ gz.eof?.should be_true
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipreader/new_spec.rb b/spec/ruby/library/zlib/gzipreader/new_spec.rb
new file mode 100644
index 0000000000..e15f14f95f
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/new_spec.rb
@@ -0,0 +1 @@
+require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/zlib/gzipreader/pos_spec.rb b/spec/ruby/library/zlib/gzipreader/pos_spec.rb
new file mode 100644
index 0000000000..8586faec92
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/pos_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipReader#pos" do
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+ @io = StringIO.new @zip
+ end
+
+ it "returns the position" do
+ gz = Zlib::GzipReader.new @io
+
+ gz.pos.should == 0
+
+ gz.read 5
+ gz.pos.should == 5
+
+ gz.read
+ gz.pos.should == @data.length
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipreader/read_spec.rb b/spec/ruby/library/zlib/gzipreader/read_spec.rb
new file mode 100644
index 0000000000..b81954b5ce
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/read_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipReader#read" do
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+ @io = StringIO.new @zip
+ end
+
+ it "with no arguments reads the entire content of a gzip file" do
+ gz = Zlib::GzipReader.new @io
+ gz.read.should == @data
+ end
+
+ it "with nil length argument reads the entire content of a gzip file" do
+ gz = Zlib::GzipReader.new @io
+ gz.read(nil).should == @data
+ end
+
+ it "reads the contents up to a certain size" do
+ gz = Zlib::GzipReader.new @io
+ gz.read(5).should == @data[0...5]
+ gz.read(5).should == @data[5...10]
+ end
+
+ it "does not accept a negative length to read" do
+ gz = Zlib::GzipReader.new @io
+ -> {
+ gz.read(-1)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "returns an empty string if a 0 length is given" do
+ gz = Zlib::GzipReader.new @io
+ gz.read(0).should == ""
+ end
+
+ it "respects :external_encoding option" do
+ gz = Zlib::GzipReader.new(@io, external_encoding: 'UTF-8')
+ gz.read.encoding.should == Encoding::UTF_8
+
+ @io.rewind
+ gz = Zlib::GzipReader.new(@io, external_encoding: 'UTF-16LE')
+ gz.read.encoding.should == Encoding::UTF_16LE
+ end
+
+ describe "at the end of data" do
+ it "returns empty string if length parameter is not specified or 0" do
+ gz = Zlib::GzipReader.new @io
+ gz.read # read till the end
+ gz.read(0).should == ""
+ gz.read().should == ""
+ gz.read(nil).should == ""
+ end
+
+ it "returns nil if length parameter is positive" do
+ gz = Zlib::GzipReader.new @io
+ gz.read # read till the end
+ gz.read(1).should be_nil
+ gz.read(2**16).should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipreader/readpartial_spec.rb b/spec/ruby/library/zlib/gzipreader/readpartial_spec.rb
new file mode 100644
index 0000000000..559ce9f841
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/readpartial_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipReader#readpartial" do
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+ @io = StringIO.new(@zip)
+ end
+
+ it 'accepts nil buffer' do
+ gz = Zlib::GzipReader.new(@io)
+ gz.readpartial(5, nil).should == '12345'
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipreader/rewind_spec.rb b/spec/ruby/library/zlib/gzipreader/rewind_spec.rb
new file mode 100644
index 0000000000..b31abb6abf
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/rewind_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipReader#rewind" do
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+ @io = StringIO.new @zip
+ ScratchPad.clear
+ end
+
+ it "resets the position of the stream pointer" do
+ gz = Zlib::GzipReader.new @io
+ gz.read
+ gz.pos.should == @data.length
+
+ gz.rewind
+ gz.pos.should == 0
+ gz.lineno.should == 0
+ end
+
+ it "resets the position of the stream pointer to data previously read" do
+ gz = Zlib::GzipReader.new @io
+ first_read = gz.read
+ gz.rewind
+ first_read.should == gz.read
+ end
+
+ it "invokes seek method on the associated IO object" do
+ # first, prepare the mock object:
+ (obj = mock("io")).should_receive(:get_io).any_number_of_times.and_return(@io)
+ def obj.read(args); get_io.read(args); end
+ def obj.seek(pos, whence = 0)
+ ScratchPad.record :seek
+ get_io.seek(pos, whence)
+ end
+
+ gz = Zlib::GzipReader.new(obj)
+ gz.rewind()
+
+ ScratchPad.recorded.should == :seek
+ gz.pos.should == 0
+ gz.read.should == "12345abcde"
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipreader/shared/each.rb b/spec/ruby/library/zlib/gzipreader/shared/each.rb
new file mode 100644
index 0000000000..71608e04ab
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/shared/each.rb
@@ -0,0 +1,49 @@
+require_relative '../../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe :gzipreader_each, shared: true do
+ before :each do
+ @data = "firstline\nsecondline\n\nforthline"
+ @zip = [31, 139, 8, 0, 244, 125, 128, 88, 2, 255, 75, 203, 44, 42, 46, 201,
+ 201, 204, 75, 229, 42, 78, 77, 206, 207, 75, 1, 51, 185, 210,242,
+ 139, 74, 50, 64, 76, 0, 180, 54, 61, 111, 31, 0, 0, 0].pack('C*')
+
+ @io = StringIO.new @zip
+ @gzreader = Zlib::GzipReader.new @io
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "calls the given block for each line in the stream, passing the line as an argument" do
+ ScratchPad.record []
+ @gzreader.send(@method) { |b| ScratchPad << b }
+
+ ScratchPad.recorded.should == ["firstline\n", "secondline\n", "\n", "forthline"]
+ end
+
+ it "returns an enumerator, which yields each byte in the stream, when no block is passed" do
+ enum = @gzreader.send(@method)
+
+ ScratchPad.record []
+ while true
+ begin
+ ScratchPad << enum.next
+ rescue StopIteration
+ break
+ end
+ end
+
+ ScratchPad.recorded.should == ["firstline\n", "secondline\n", "\n", "forthline"]
+ end
+
+ it "increments position before calling the block" do
+ i = 0
+ @gzreader.send(@method) do |line|
+ i += line.length
+ @gzreader.pos.should == i
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipreader/ungetbyte_spec.rb b/spec/ruby/library/zlib/gzipreader/ungetbyte_spec.rb
new file mode 100644
index 0000000000..7fa0608f9f
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/ungetbyte_spec.rb
@@ -0,0 +1,120 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipReader#ungetbyte" do
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+ @io = StringIO.new @zip
+ end
+
+ describe 'at the start of the stream' do
+ before :each do
+ @gz = Zlib::GzipReader.new(@io)
+ end
+
+ describe 'with an integer' do
+ it 'prepends the byte to the stream' do
+ @gz.ungetbyte 0x21
+ @gz.read.should == '!12345abcde'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetbyte 0x21
+ @gz.pos.should == -1
+ end
+ end
+
+ quarantine! do # https://bugs.ruby-lang.org/issues/13675
+ describe 'with nil' do
+ it 'does not prepend anything to the stream' do
+ @gz.ungetbyte nil
+ @gz.read.should == '12345abcde'
+ end
+
+ it 'does not decrement pos' do
+ @gz.ungetbyte nil
+ @gz.pos.should == 0
+ end
+ end
+ end
+ end
+
+ describe 'in the middle of the stream' do
+ before :each do
+ @gz = Zlib::GzipReader.new(@io)
+ @gz.read 5
+ end
+
+ describe 'with an integer' do
+ it 'inserts the corresponding character into the stream' do
+ @gz.ungetbyte 0x21
+ @gz.read.should == '!abcde'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetbyte 0x21
+ @gz.pos.should == 4
+ end
+ end
+
+ quarantine! do # https://bugs.ruby-lang.org/issues/13675
+ describe 'with nil' do
+ it 'does not insert anything into the stream' do
+ @gz.ungetbyte nil
+ @gz.read.should == 'abcde'
+ end
+
+ it 'does not decrement pos' do
+ @gz.ungetbyte nil
+ @gz.pos.should == 5
+ end
+ end
+ end
+ end
+
+ describe 'at the end of the stream' do
+ before :each do
+ @gz = Zlib::GzipReader.new(@io)
+ @gz.read
+ end
+
+ describe 'with an integer' do
+ it 'appends the corresponding character to the stream' do
+ @gz.ungetbyte 0x21
+ @gz.read.should == '!'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetbyte 0x21
+ @gz.pos.should == 9
+ end
+
+ it 'makes eof? false' do
+ @gz.ungetbyte 0x21
+ @gz.eof?.should be_false
+ end
+ end
+
+ quarantine! do # https://bugs.ruby-lang.org/issues/13675
+ describe 'with nil' do
+ it 'does not append anything to the stream' do
+ @gz.ungetbyte nil
+ @gz.read.should == ''
+ end
+
+ it 'does not decrement pos' do
+ @gz.ungetbyte nil
+ @gz.pos.should == 10
+ end
+
+ it 'does not make eof? false' do
+ @gz.ungetbyte nil
+ @gz.eof?.should be_true
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipreader/ungetc_spec.rb b/spec/ruby/library/zlib/gzipreader/ungetc_spec.rb
new file mode 100644
index 0000000000..34f2a1a2ca
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipreader/ungetc_spec.rb
@@ -0,0 +1,284 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipReader#ungetc" do
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+ @io = StringIO.new @zip
+ end
+
+ describe 'at the start of the stream' do
+ before :each do
+ @gz = Zlib::GzipReader.new(@io, external_encoding: Encoding::UTF_8)
+ end
+
+ describe 'with a single-byte character' do
+ it 'prepends the character to the stream' do
+ @gz.ungetc 'x'
+ @gz.read.should == 'x12345abcde'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 'x'
+ @gz.pos.should == -1
+ end
+ end
+
+ describe 'with a multi-byte character' do
+ it 'prepends the character to the stream' do
+ @gz.ungetc 'Å·'
+ @gz.read.should == 'Å·12345abcde'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 'Å·'
+ @gz.pos.should == -2
+ end
+ end
+
+ describe 'with a multi-character string' do
+ it 'prepends the characters to the stream' do
+ @gz.ungetc 'xŷž'
+ @gz.read.should == 'xŷž12345abcde'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 'xŷž'
+ @gz.pos.should == -5
+ end
+ end
+
+ describe 'with an integer' do
+ it 'prepends the corresponding character to the stream' do
+ @gz.ungetc 0x21
+ @gz.read.should == '!12345abcde'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 0x21
+ @gz.pos.should == -1
+ end
+ end
+
+ describe 'with an empty string' do
+ it 'does not prepend anything to the stream' do
+ @gz.ungetc ''
+ @gz.read.should == '12345abcde'
+ end
+
+ it 'does not decrement pos' do
+ @gz.ungetc ''
+ @gz.pos.should == 0
+ end
+ end
+
+ quarantine! do # https://bugs.ruby-lang.org/issues/13675
+ describe 'with nil' do
+ it 'does not prepend anything to the stream' do
+ @gz.ungetc nil
+ @gz.read.should == '12345abcde'
+ end
+
+ it 'does not decrement pos' do
+ @gz.ungetc nil
+ @gz.pos.should == 0
+ end
+ end
+ end
+ end
+
+ describe 'in the middle of the stream' do
+ before :each do
+ @gz = Zlib::GzipReader.new(@io, external_encoding: Encoding::UTF_8)
+ @gz.read 5
+ end
+
+ describe 'with a single-byte character' do
+ it 'inserts the character into the stream' do
+ @gz.ungetc 'x'
+ @gz.read.should == 'xabcde'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 'x'
+ @gz.pos.should == 4
+ end
+ end
+
+ describe 'with a multi-byte character' do
+ it 'inserts the character into the stream' do
+ @gz.ungetc 'Å·'
+ @gz.read.should == 'Å·abcde'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 'Å·'
+ @gz.pos.should == 3
+ end
+ end
+
+ describe 'with a multi-character string' do
+ it 'inserts the characters into the stream' do
+ @gz.ungetc 'xŷž'
+ @gz.read.should == 'xŷžabcde'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 'xŷž'
+ @gz.pos.should == 0
+ end
+ end
+
+ describe 'with an integer' do
+ it 'inserts the corresponding character into the stream' do
+ @gz.ungetc 0x21
+ @gz.read.should == '!abcde'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 0x21
+ @gz.pos.should == 4
+ end
+ end
+
+ describe 'with an empty string' do
+ it 'does not insert anything into the stream' do
+ @gz.ungetc ''
+ @gz.read.should == 'abcde'
+ end
+
+ it 'does not decrement pos' do
+ @gz.ungetc ''
+ @gz.pos.should == 5
+ end
+ end
+
+ quarantine! do # https://bugs.ruby-lang.org/issues/13675
+ describe 'with nil' do
+ it 'does not insert anything into the stream' do
+ @gz.ungetc nil
+ @gz.read.should == 'abcde'
+ end
+
+ it 'does not decrement pos' do
+ @gz.ungetc nil
+ @gz.pos.should == 5
+ end
+ end
+ end
+ end
+
+ describe 'at the end of the stream' do
+ before :each do
+ @gz = Zlib::GzipReader.new(@io, external_encoding: Encoding::UTF_8)
+ @gz.read
+ end
+
+ describe 'with a single-byte character' do
+ it 'appends the character to the stream' do
+ @gz.ungetc 'x'
+ @gz.read.should == 'x'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 'x'
+ @gz.pos.should == 9
+ end
+
+ it 'makes eof? false' do
+ @gz.ungetc 'x'
+ @gz.eof?.should be_false
+ end
+ end
+
+ describe 'with a multi-byte character' do
+ it 'appends the character to the stream' do
+ @gz.ungetc 'Å·'
+ @gz.read.should == 'Å·'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 'Å·'
+ @gz.pos.should == 8
+ end
+
+ it 'makes eof? false' do
+ @gz.ungetc 'Å·'
+ @gz.eof?.should be_false
+ end
+ end
+
+ describe 'with a multi-character string' do
+ it 'appends the characters to the stream' do
+ @gz.ungetc 'xŷž'
+ @gz.read.should == 'xŷž'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 'xŷž'
+ @gz.pos.should == 5
+ end
+
+ it 'makes eof? false' do
+ @gz.ungetc 'xŷž'
+ @gz.eof?.should be_false
+ end
+ end
+
+ describe 'with an integer' do
+ it 'appends the corresponding character to the stream' do
+ @gz.ungetc 0x21
+ @gz.read.should == '!'
+ end
+
+ it 'decrements pos' do
+ @gz.ungetc 0x21
+ @gz.pos.should == 9
+ end
+
+ it 'makes eof? false' do
+ @gz.ungetc 0x21
+ @gz.eof?.should be_false
+ end
+ end
+
+ describe 'with an empty string' do
+ it 'does not append anything to the stream' do
+ @gz.ungetc ''
+ @gz.read.should == ''
+ end
+
+ it 'does not decrement pos' do
+ @gz.ungetc ''
+ @gz.pos.should == 10
+ end
+
+ it 'does not make eof? false' do
+ @gz.ungetc ''
+ @gz.eof?.should be_true
+ end
+ end
+
+ quarantine! do # https://bugs.ruby-lang.org/issues/13675
+ describe 'with nil' do
+ it 'does not append anything to the stream' do
+ @gz.ungetc nil
+ @gz.read.should == ''
+ end
+
+ it 'does not decrement pos' do
+ @gz.ungetc nil
+ @gz.pos.should == 10
+ end
+
+ it 'does not make eof? false' do
+ @gz.ungetc nil
+ @gz.eof?.should be_true
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipwriter/append_spec.rb b/spec/ruby/library/zlib/gzipwriter/append_spec.rb
new file mode 100644
index 0000000000..6aa2824180
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipwriter/append_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipWriter#<<" do
+ before :each do
+ @io = StringIO.new
+ end
+
+ it "returns self" do
+ Zlib::GzipWriter.wrap @io do |gzio|
+ (gzio << "test").should equal(gzio)
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipwriter/mtime_spec.rb b/spec/ruby/library/zlib/gzipwriter/mtime_spec.rb
new file mode 100644
index 0000000000..621b602dc7
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipwriter/mtime_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipWriter#mtime=" do
+ before :each do
+ @io = StringIO.new
+ end
+
+ it "sets mtime using Integer" do
+ Zlib::GzipWriter.wrap @io do |gzio|
+ gzio.mtime = 1
+
+ gzio.mtime.should == Time.at(1)
+ end
+
+ @io.string[4, 4].should == [1,0,0,0].pack('C*')
+ end
+
+ it "sets mtime using Time" do
+ Zlib::GzipWriter.wrap @io do |gzio|
+ gzio.mtime = Time.at 1
+
+ gzio.mtime.should == Time.at(1)
+ end
+
+ @io.string[4, 4].should == [1,0,0,0].pack('C*')
+ end
+
+ it "raises if the header was written" do
+ Zlib::GzipWriter.wrap @io do |gzio|
+ gzio.write ''
+
+ -> { gzio.mtime = nil }.should \
+ raise_error(Zlib::GzipFile::Error, 'header is already written')
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/gzipwriter/write_spec.rb b/spec/ruby/library/zlib/gzipwriter/write_spec.rb
new file mode 100644
index 0000000000..522ae7f2aa
--- /dev/null
+++ b/spec/ruby/library/zlib/gzipwriter/write_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+require 'stringio'
+require 'zlib'
+
+describe "Zlib::GzipWriter#write" do
+ before :each do
+ @data = '12345abcde'
+ @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77,
+ 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*')
+ @io = StringIO.new "".b
+ end
+
+ it "writes some compressed data" do
+ Zlib::GzipWriter.wrap @io do |gzio|
+ gzio.write @data
+ end
+
+ # skip gzip header for now
+ @io.string.unpack('C*')[10..-1].should == @zip.unpack('C*')[10..-1]
+ end
+
+ it "returns the number of bytes in the input" do
+ Zlib::GzipWriter.wrap @io do |gzio|
+ gzio.write(@data).should == @data.size
+ end
+ end
+
+ it "handles inputs of 2^23 bytes" do
+ input = '.'.b * (2 ** 23)
+
+ Zlib::GzipWriter.wrap @io do |gzio|
+ gzio.write input
+ end
+ @io.string.size.should == 8176
+ end
+end
diff --git a/spec/ruby/library/zlib/inflate/append_spec.rb b/spec/ruby/library/zlib/inflate/append_spec.rb
new file mode 100644
index 0000000000..f121e66566
--- /dev/null
+++ b/spec/ruby/library/zlib/inflate/append_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../../spec_helper'
+require 'zlib'
+
+describe "Zlib::Inflate#<<" do
+ before :all do
+ @foo_deflated = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*')
+ end
+
+ before :each do
+ @z = Zlib::Inflate.new
+ end
+
+ after :each do
+ @z.close unless @z.closed?
+ end
+
+ it "appends data to the input stream" do
+ @z << @foo_deflated
+ @z.finish.should == 'foo'
+ end
+
+ it "treats nil argument as the end of compressed data" do
+ @z = Zlib::Inflate.new
+ @z << @foo_deflated << nil
+ @z.finish.should == 'foo'
+ end
+
+ it "just passes through the data after nil argument" do
+ @z = Zlib::Inflate.new
+ @z << @foo_deflated << nil
+ @z << "-after_nil_data"
+ @z.finish.should == 'foo-after_nil_data'
+ end
+
+ it "properly handles data in chunks" do
+ # add bytes, one by one
+ @foo_deflated.each_byte { |d| @z << d.chr}
+ @z.finish.should == "foo"
+ end
+
+ it "properly handles incomplete data" do
+ # add bytes, one by one
+ @foo_deflated[0, 5].each_byte { |d| @z << d.chr}
+ -> { @z.finish }.should raise_error(Zlib::BufError)
+ end
+
+ it "properly handles excessive data, byte-by-byte" do
+ # add bytes, one by one
+ data = @foo_deflated * 2
+ data.each_byte { |d| @z << d.chr}
+ @z.finish.should == "foo" + @foo_deflated
+ end
+
+ it "properly handles excessive data, in one go" do
+ # add bytes, one by one
+ data = @foo_deflated * 2
+ @z << data
+ @z.finish.should == "foo" + @foo_deflated
+ end
+end
diff --git a/spec/ruby/library/zlib/inflate/finish_spec.rb b/spec/ruby/library/zlib/inflate/finish_spec.rb
new file mode 100644
index 0000000000..f6e592fb6b
--- /dev/null
+++ b/spec/ruby/library/zlib/inflate/finish_spec.rb
@@ -0,0 +1,28 @@
+require 'zlib'
+
+describe "Zlib::Inflate#finish" do
+
+ before do
+ @zeros = Zlib::Deflate.deflate("0" * 100_000)
+ @inflator = Zlib::Inflate.new
+ @chunks = []
+
+ @inflator.inflate(@zeros) do |chunk|
+ @chunks << chunk
+ break
+ end
+
+ @inflator.finish do |chunk|
+ @chunks << chunk
+ end
+ end
+
+ it "inflates chunked data" do
+ @chunks.map { |chunk| chunk.length }.should == [16384, 16384, 16384, 16384, 16384, 16384, 1696]
+ end
+
+ it "each chunk should have the same prefix" do
+ @chunks.all? { |chunk| chunk =~ /\A0+\z/ }.should be_true
+ end
+
+end
diff --git a/spec/ruby/library/zlib/inflate/inflate_spec.rb b/spec/ruby/library/zlib/inflate/inflate_spec.rb
new file mode 100644
index 0000000000..79b72bf91c
--- /dev/null
+++ b/spec/ruby/library/zlib/inflate/inflate_spec.rb
@@ -0,0 +1,159 @@
+require 'zlib'
+require_relative '../../../spec_helper'
+
+describe "Zlib::Inflate#inflate" do
+
+ before :each do
+ @inflator = Zlib::Inflate.new
+ end
+ it "inflates some data" do
+ data = [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1].pack('C*')
+ unzipped = @inflator.inflate data
+ @inflator.finish
+
+ unzipped.should == "\000" * 10
+ end
+
+ it "inflates lots of data" do
+ data = [120, 156, 237, 193, 1, 1, 0, 0] +
+ [0, 128, 144, 254, 175, 238, 8, 10] +
+ Array.new(31, 0) +
+ [24, 128, 0, 0, 1]
+
+ unzipped = @inflator.inflate data.pack('C*')
+ @inflator.finish
+
+ unzipped.should == "\000" * 32 * 1024
+ end
+
+ it "works in pass-through mode, once finished" do
+ data = [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1]
+ @inflator.inflate data.pack('C*')
+ @inflator.finish # this is a precondition
+
+ out = @inflator.inflate('uncompressed_data')
+ out << @inflator.finish
+ out.should == 'uncompressed_data'
+
+ @inflator << ('uncompressed_data') << nil
+ @inflator.finish.should == 'uncompressed_data'
+ end
+
+ it "has a binary encoding" do
+ data = [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1].pack('C*')
+ unzipped = @inflator.inflate data
+ @inflator.finish.encoding.should == Encoding::BINARY
+ unzipped.encoding.should == Encoding::BINARY
+ end
+
+end
+
+describe "Zlib::Inflate.inflate" do
+
+ it "inflates some data" do
+ data = [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1]
+ unzipped = Zlib::Inflate.inflate data.pack('C*')
+
+ unzipped.should == "\000" * 10
+ end
+
+ it "inflates lots of data" do
+ data = [120, 156, 237, 193, 1, 1, 0, 0] +
+ [0, 128, 144, 254, 175, 238, 8, 10] +
+ Array.new(31,0) +
+ [24, 128, 0, 0, 1]
+
+ zipped = Zlib::Inflate.inflate data.pack('C*')
+
+ zipped.should == "\000" * 32 * 1024
+ end
+
+ it "properly handles data in chunks" do
+ data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*')
+ z = Zlib::Inflate.new
+ # add bytes, one by one
+ result = ""
+ data.each_byte { |d| result << z.inflate(d.chr)}
+ result << z.finish
+ result.should == "foo"
+ end
+
+ it "properly handles incomplete data" do
+ data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*')[0,5]
+ z = Zlib::Inflate.new
+ # add bytes, one by one, but not all
+ result = ""
+ data.each_byte { |d| result << z.inflate(d.chr)}
+ -> { result << z.finish }.should raise_error(Zlib::BufError)
+ end
+
+ it "properly handles excessive data, byte-by-byte" do
+ main_data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*')
+ data = main_data * 2
+ result = ""
+
+ z = Zlib::Inflate.new
+ # add bytes, one by one
+ data.each_byte { |d| result << z.inflate(d.chr)}
+ result << z.finish
+
+ # the first chunk is inflated to its completion,
+ # the second chunk is just passed through.
+ result.should == "foo" + main_data
+ end
+
+ it "properly handles excessive data, in one go" do
+ main_data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*')
+ data = main_data * 2
+ result = ""
+
+ z = Zlib::Inflate.new
+ result << z.inflate(data)
+ result << z.finish
+
+ # the first chunk is inflated to its completion,
+ # the second chunk is just passed through.
+ result.should == "foo" + main_data
+ end
+end
+
+describe "Zlib::Inflate#inflate" do
+
+ before do
+ @zeros = Zlib::Deflate.deflate("0" * 100_000)
+ @inflator = Zlib::Inflate.new
+ @chunks = []
+ end
+
+ describe "without break" do
+
+ before do
+ @inflator.inflate(@zeros) do |chunk|
+ @chunks << chunk
+ end
+ end
+
+ it "inflates chunked data" do
+ @chunks.map { |chunk| chunk.size }.should == [16384, 16384, 16384, 16384, 16384, 16384, 1696]
+ end
+
+ it "properly handles chunked data" do
+ @chunks.all? { |chunk| chunk =~ /\A0+\z/ }.should be_true
+ end
+ end
+
+ describe "with break" do
+
+ before do
+ @inflator.inflate(@zeros) do |chunk|
+ @chunks << chunk
+ break
+ end
+ end
+
+ it "inflates chunked break" do
+ output = @inflator.inflate nil
+ (100_000 - @chunks.first.length).should == output.length
+ end
+ end
+end
diff --git a/spec/ruby/library/zlib/inflate/new_spec.rb b/spec/ruby/library/zlib/inflate/new_spec.rb
new file mode 100644
index 0000000000..e15f14f95f
--- /dev/null
+++ b/spec/ruby/library/zlib/inflate/new_spec.rb
@@ -0,0 +1 @@
+require_relative '../../../spec_helper'
diff --git a/spec/ruby/library/zlib/inflate/set_dictionary_spec.rb b/spec/ruby/library/zlib/inflate/set_dictionary_spec.rb
new file mode 100644
index 0000000000..251cec44bb
--- /dev/null
+++ b/spec/ruby/library/zlib/inflate/set_dictionary_spec.rb
@@ -0,0 +1,20 @@
+# -*- encoding: binary -*-
+require_relative '../../../spec_helper'
+require 'zlib'
+
+describe "Zlib::Inflate#set_dictionary" do
+ it "sets the inflate dictionary" do
+ deflated = "x\273\024\341\003\313KLJNIMK\317\310\314\002\000\025\206\003\370"
+
+ i = Zlib::Inflate.new
+
+ begin
+ i << deflated
+ flunk 'Zlib::NeedDict not raised'
+ rescue Zlib::NeedDict
+ i.set_dictionary 'aaaaaaaaaa'
+ end
+
+ i.finish.should == 'abcdefghij'
+ end
+end
diff --git a/spec/ruby/library/zlib/inflate_spec.rb b/spec/ruby/library/zlib/inflate_spec.rb
new file mode 100644
index 0000000000..42c8dc5fbe
--- /dev/null
+++ b/spec/ruby/library/zlib/inflate_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require "zlib"
+
+describe "Zlib.inflate" do
+ it "inflates some data" do
+ Zlib.inflate([120, 156, 51, 52, 132, 1, 0, 10, 145, 1, 235].pack('C*')).should == "1" * 10
+ end
+end
diff --git a/spec/ruby/library/zlib/zlib_version_spec.rb b/spec/ruby/library/zlib/zlib_version_spec.rb
new file mode 100644
index 0000000000..f83dfae66d
--- /dev/null
+++ b/spec/ruby/library/zlib/zlib_version_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require 'zlib'
+
+describe "Zlib.zlib_version" do
+ it "returns the version of the libz library" do
+ Zlib.zlib_version.should be_an_instance_of(String)
+ end
+end
diff --git a/spec/ruby/library/zlib/zstream/adler_spec.rb b/spec/ruby/library/zlib/zstream/adler_spec.rb
new file mode 100644
index 0000000000..55ac8ae79e
--- /dev/null
+++ b/spec/ruby/library/zlib/zstream/adler_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require 'zlib'
+
+describe "Zlib::ZStream#adler" do
+ it "generates hash" do
+ z = Zlib::Deflate.new
+ z << "foo"
+ z.finish
+ z.adler.should == 0x02820145
+ end
+end
diff --git a/spec/ruby/library/zlib/zstream/avail_in_spec.rb b/spec/ruby/library/zlib/zstream/avail_in_spec.rb
new file mode 100644
index 0000000000..eddae15830
--- /dev/null
+++ b/spec/ruby/library/zlib/zstream/avail_in_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+require 'zlib'
+
+describe "Zlib::ZStream#avail_in" do
+ it "returns bytes in the input buffer" do
+ z = Zlib::Deflate.new
+ z.avail_in.should == 0
+ end
+end
diff --git a/spec/ruby/library/zlib/zstream/avail_out_spec.rb b/spec/ruby/library/zlib/zstream/avail_out_spec.rb
new file mode 100644
index 0000000000..2e5a394ec0
--- /dev/null
+++ b/spec/ruby/library/zlib/zstream/avail_out_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+require 'zlib'
+
+describe "Zlib::ZStream#avail_out" do
+ it "returns bytes in the output buffer" do
+ z = Zlib::Deflate.new
+ z.avail_out.should == 0
+ end
+end
diff --git a/spec/ruby/library/zlib/zstream/data_type_spec.rb b/spec/ruby/library/zlib/zstream/data_type_spec.rb
new file mode 100644
index 0000000000..8be96adf7c
--- /dev/null
+++ b/spec/ruby/library/zlib/zstream/data_type_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+require 'zlib'
+
+describe "Zlib::ZStream#data_type" do
+ it "returns the type of the data in the stream" do
+ z = Zlib::Deflate.new
+ [Zlib::ASCII, Zlib::BINARY, Zlib::UNKNOWN].include?(z.data_type).should == true
+ end
+end
diff --git a/spec/ruby/library/zlib/zstream/flush_next_out_spec.rb b/spec/ruby/library/zlib/zstream/flush_next_out_spec.rb
new file mode 100644
index 0000000000..63676a8203
--- /dev/null
+++ b/spec/ruby/library/zlib/zstream/flush_next_out_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require 'zlib'
+
+describe "Zlib::ZStream#flush_next_out" do
+
+ it "flushes the stream and flushes the output buffer" do
+ zs = Zlib::Inflate.new
+ zs << [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*')
+
+ zs.flush_next_out.should == 'foo'
+ zs.should.finished?
+ zs.flush_next_out.should == ''
+ end
+end
diff --git a/spec/ruby/optional/capi/README b/spec/ruby/optional/capi/README
new file mode 100644
index 0000000000..069ca3c106
--- /dev/null
+++ b/spec/ruby/optional/capi/README
@@ -0,0 +1,13 @@
+C-API Specs
+
+These specs test the C-API from Ruby. The following are conventions for the
+specs:
+
+1. Put specs for functions related to a Ruby class in a file named according
+ to the class. For example, for rb_ary_new function, put the specs in
+ optional/capi/array_spec.rb
+2. Put the C file containing the C functions for array_spec.rb in
+ optional/capi/ext/array_spec.c
+3. Name the C extension class 'CApiArraySpecs'.
+4. Name the C functions 'array_spec_rb_ary_new'.
+5. Attach the C function to the class using the name 'rb_ary_new'
diff --git a/spec/ruby/optional/capi/array_spec.rb b/spec/ruby/optional/capi/array_spec.rb
new file mode 100644
index 0000000000..8e90980c6a
--- /dev/null
+++ b/spec/ruby/optional/capi/array_spec.rb
@@ -0,0 +1,497 @@
+require_relative 'spec_helper'
+
+load_extension("array")
+
+describe :rb_ary_new2, shared: true do
+ it "returns an empty array" do
+ @s.send(@method, 5).should == []
+ end
+
+ it "raises an ArgumentError when the given argument is negative" do
+ -> { @s.send(@method, -1) }.should raise_error(ArgumentError)
+ end
+end
+
+describe "C-API Array function" do
+ before :each do
+ @s = CApiArraySpecs.new
+ end
+
+ describe "rb_Array" do
+ it "returns obj if it is an array" do
+ arr = @s.rb_Array([1,2])
+ arr.should == [1, 2]
+ end
+
+ it "tries to convert obj to an array" do
+ arr = @s.rb_Array({"bar" => "foo"})
+ arr.should == [["bar", "foo"]]
+ end
+
+ it "returns obj wrapped in an array if it cannot be converted to an array" do
+ arr = @s.rb_Array("a")
+ arr.should == ["a"]
+ end
+ end
+
+ describe "rb_ary_new" do
+ it "returns an empty array" do
+ @s.rb_ary_new.should == []
+ end
+ end
+
+ describe "rb_ary_new2" do
+ it_behaves_like :rb_ary_new2, :rb_ary_new2
+ end
+
+ describe "rb_ary_new_capa" do
+ it_behaves_like :rb_ary_new2, :rb_ary_new_capa
+ end
+
+ describe "rb_ary_new3" do
+ it "returns an array with the passed cardinality and varargs" do
+ @s.rb_ary_new3(1,2,3).should == [1,2,3]
+ end
+ end
+
+ describe "rb_ary_new_from_args" do
+ it "returns an array with the passed cardinality and varargs" do
+ @s.rb_ary_new_from_args(1,2,3).should == [1,2,3]
+ end
+ end
+
+ describe "rb_ary_new4" do
+ it "returns an array with the passed values" do
+ @s.rb_ary_new4(1,2,3).should == [1,2,3]
+ end
+ end
+
+ describe "rb_ary_new_from_values" do
+ it "returns an array with the passed values" do
+ @s.rb_ary_new_from_values(1,2,3).should == [1,2,3]
+ end
+ end
+
+ describe "rb_ary_push" do
+ it "adds an element to the array" do
+ @s.rb_ary_push([], 4).should == [4]
+ end
+ end
+
+ describe "rb_ary_cat" do
+ it "pushes the given objects onto the end of the array" do
+ @s.rb_ary_cat([1, 2], 3, 4).should == [1, 2, 3, 4]
+ end
+
+ it "raises a FrozenError if the array is frozen" do
+ -> { @s.rb_ary_cat([].freeze, 1) }.should raise_error(FrozenError)
+ end
+ end
+
+ describe "rb_ary_pop" do
+ it "removes and returns the last element in the array" do
+ a = [1,2,3]
+ @s.rb_ary_pop(a).should == 3
+ a.should == [1,2]
+ end
+ end
+
+ describe "rb_ary_join" do
+ it "joins elements of an array with a string" do
+ a = [1,2,3]
+ b = ","
+ @s.rb_ary_join(a,b).should == "1,2,3"
+ end
+ end
+
+ describe "rb_ary_to_s" do
+ it "creates an Array literal representation as a String" do
+ @s.rb_ary_to_s([1,2,3]).should == "[1, 2, 3]"
+ @s.rb_ary_to_s([]).should == "[]"
+ end
+ end
+
+ describe "rb_ary_reverse" do
+ it "reverses the order of elements in the array" do
+ a = [1,2,3]
+ @s.rb_ary_reverse(a)
+ a.should == [3,2,1]
+ end
+
+ it "returns the original array" do
+ a = [1,2,3]
+ @s.rb_ary_reverse(a).should equal(a)
+ end
+ end
+
+ describe "rb_ary_rotate" do
+ it "rotates the array so that the element at the specified position comes first" do
+ @s.rb_ary_rotate([1, 2, 3, 4], 2).should == [3, 4, 1, 2]
+ @s.rb_ary_rotate([1, 2, 3, 4], -3).should == [2, 3, 4, 1]
+ end
+
+ it "raises a FrozenError if the array is frozen" do
+ -> { @s.rb_ary_rotate([].freeze, 1) }.should raise_error(FrozenError)
+ end
+ end
+
+ describe "rb_ary_entry" do
+ it "returns nil when passed an empty array" do
+ @s.rb_ary_entry([], 0).should == nil
+ end
+
+ it "returns elements from the end when passed a negative index" do
+ @s.rb_ary_entry([1, 2, 3], -1).should == 3
+ @s.rb_ary_entry([1, 2, 3], -2).should == 2
+ end
+
+ it "returns nil if the index is out of range" do
+ @s.rb_ary_entry([1, 2, 3], 3).should == nil
+ @s.rb_ary_entry([1, 2, 3], -10).should == nil
+ end
+ end
+
+ describe "rb_ary_clear" do
+ it "removes all elements from the array" do
+ @s.rb_ary_clear([]).should == []
+ @s.rb_ary_clear([1, 2, 3]).should == []
+ end
+ end
+
+ describe "rb_ary_dup" do
+ it "duplicates the array" do
+ @s.rb_ary_dup([]).should == []
+
+ a = [1, 2, 3]
+ b = @s.rb_ary_dup(a)
+
+ b.should == a
+ b.should_not equal(a)
+ end
+ end
+
+ describe "rb_ary_unshift" do
+ it "prepends the element to the array" do
+ a = [1, 2, 3]
+ @s.rb_ary_unshift(a, "a").should == ["a", 1, 2, 3]
+ a.should == ['a', 1, 2, 3]
+ end
+ end
+
+ describe "rb_ary_shift" do
+ it "removes and returns the first element" do
+ a = [5, 1, 1, 5, 4]
+ @s.rb_ary_shift(a).should == 5
+ a.should == [1, 1, 5, 4]
+ end
+
+ it "returns nil when the array is empty" do
+ @s.rb_ary_shift([]).should == nil
+ end
+ end
+
+ describe "rb_ary_sort" do
+ it "returns a new sorted array" do
+ a = [2, 1, 3]
+ @s.rb_ary_sort(a).should == [1, 2, 3]
+ a.should == [2, 1, 3]
+ end
+ end
+
+ describe "rb_ary_sort_bang" do
+ it "sorts the given array" do
+ a = [2, 1, 3]
+ @s.rb_ary_sort_bang(a).should == [1, 2, 3]
+ a.should == [1, 2, 3]
+ end
+ end
+
+ describe "rb_ary_store" do
+ it "overwrites the element at the given position" do
+ a = [1, 2, 3]
+ @s.rb_ary_store(a, 1, 5)
+ a.should == [1, 5, 3]
+ end
+
+ it "writes to elements offset from the end if passed a negative index" do
+ a = [1, 2, 3]
+ @s.rb_ary_store(a, -1, 5)
+ a.should == [1, 2, 5]
+ end
+
+ it "raises an IndexError if the negative index is greater than the length" do
+ a = [1, 2, 3]
+ -> { @s.rb_ary_store(a, -10, 5) }.should raise_error(IndexError)
+ end
+
+ it "enlarges the array as needed" do
+ a = []
+ @s.rb_ary_store(a, 2, 7)
+ a.should == [nil, nil, 7]
+ end
+
+ it "raises a FrozenError if the array is frozen" do
+ a = [1, 2, 3].freeze
+ -> { @s.rb_ary_store(a, 1, 5) }.should raise_error(FrozenError)
+ end
+ end
+
+ describe "rb_ary_concat" do
+ it "concats two arrays" do
+ a = [5, 1, 1, 5, 4]
+ b = [2, 3]
+ @s.rb_ary_concat(a, b).should == [5, 1, 1, 5, 4, 2, 3]
+ end
+ end
+
+ describe "rb_ary_plus" do
+ it "adds two arrays together" do
+ @s.rb_ary_plus([10], [20]).should == [10, 20]
+ end
+ end
+
+ describe "RARRAY_PTR" do
+ it "returns a pointer to a C array of the array's elements" do
+ a = [1, 2, 3]
+ b = []
+ @s.RARRAY_PTR_iterate(a) do |e|
+ b << e
+ end
+ a.should == b
+ end
+
+ it "allows assigning to the elements of the C array" do
+ a = [1, 2, 3]
+ @s.RARRAY_PTR_assign(a, :set)
+ a.should == [:set, :set, :set]
+ end
+
+ it "allows memcpying between arrays" do
+ a = [1, 2, 3]
+ b = [0, 0, 0]
+ @s.RARRAY_PTR_memcpy(a, b)
+ b.should == [1, 2, 3]
+ a.should == [1, 2, 3] # check a was not modified
+ end
+ end
+
+ describe "RARRAY_LEN" do
+ it "returns the size of the array" do
+ @s.RARRAY_LEN([1, 2, 3]).should == 3
+ end
+ end
+
+ describe "RARRAY_AREF" do
+ # This macro does NOT do any bounds checking!
+ it "returns an element from the array" do
+ @s.RARRAY_AREF([1, 2, 3], 1).should == 2
+ end
+ end
+
+ describe "RARRAY_ASET" do
+ # This macro does NOT do any bounds checking!
+ it "writes an element in the array" do
+ ary = [1, 2, 3]
+ @s.RARRAY_ASET(ary, 0, 0)
+ @s.RARRAY_ASET(ary, 2, 42)
+ ary.should == [0, 2, 42]
+ end
+ end
+
+ describe "rb_assoc_new" do
+ it "returns an array containing the two elements" do
+ @s.rb_assoc_new(1, 2).should == [1, 2]
+ @s.rb_assoc_new(:h, [:a, :b]).should == [:h, [:a, :b]]
+ end
+ end
+
+ describe "rb_ary_includes" do
+ it "returns true if the array includes the element" do
+ @s.rb_ary_includes([1, 2, 3], 2).should be_true
+ end
+
+ it "returns false if the array does not include the element" do
+ @s.rb_ary_includes([1, 2, 3], 4).should be_false
+ end
+ end
+
+ describe "rb_ary_aref" do
+ it "returns the element at the given index" do
+ @s.rb_ary_aref([:me, :you], 0).should == :me
+ @s.rb_ary_aref([:me, :you], 1).should == :you
+ end
+
+ it "returns nil for an out of range index" do
+ @s.rb_ary_aref([1, 2, 3], 6).should be_nil
+ end
+
+ it "returns a new array where the first argument is the index and the second is the length" do
+ @s.rb_ary_aref([1, 2, 3, 4], 0, 2).should == [1, 2]
+ @s.rb_ary_aref([1, 2, 3, 4], -4, 3).should == [1, 2, 3]
+ end
+
+ it "accepts a range" do
+ @s.rb_ary_aref([1, 2, 3, 4], 0..-1).should == [1, 2, 3, 4]
+ end
+
+ it "returns nil when the start of a range is out of bounds" do
+ @s.rb_ary_aref([1, 2, 3, 4], 6..10).should be_nil
+ end
+
+ it "returns an empty array when the start of a range equals the last element" do
+ @s.rb_ary_aref([1, 2, 3, 4], 4..10).should == []
+ end
+ end
+
+ describe "rb_block_call" do
+ it "calls an callback function as a block passed to an method" do
+ s = [1,2,3,4]
+ s2 = @s.rb_block_call(s)
+
+ s2.should == s
+
+ # Make sure they're different objects
+ s2.equal?(s).should be_false
+ end
+
+ it "calls a function with the other function available as a block" do
+ h = {a: 1, b: 2}
+
+ @s.rb_block_call_each_pair(h).sort.should == [1,2]
+ end
+
+ it "calls a function which can yield into the original block" do
+ s2 = []
+
+ o = Object.new
+ def o.each
+ yield 1
+ yield 2
+ yield 3
+ yield 4
+ end
+
+ @s.rb_block_call_then_yield(o) { |x| s2 << x }
+
+ s2.should == [1,2,3,4]
+ end
+ end
+
+ describe "rb_ary_delete" do
+ it "removes an element from an array and returns it" do
+ ary = [1, 2, 3, 4]
+ @s.rb_ary_delete(ary, 3).should == 3
+ ary.should == [1, 2, 4]
+ end
+
+ it "returns nil if the element is not in the array" do
+ ary = [1, 2, 3, 4]
+ @s.rb_ary_delete(ary, 5).should be_nil
+ ary.should == [1, 2, 3, 4]
+ end
+ end
+
+ describe "rb_mem_clear" do
+ it "sets elements of a C array to nil" do
+ @s.rb_mem_clear(1).should == nil
+ end
+ end
+
+ describe "rb_ary_freeze" do
+ it "freezes the object exactly like Kernel#freeze" do
+ ary = [1,2]
+ @s.rb_ary_freeze(ary)
+ ary.frozen?.should be_true
+ end
+ end
+
+ describe "rb_ary_delete_at" do
+ before :each do
+ @array = [1, 2, 3, 4]
+ end
+
+ it "removes an element from an array at a positive index" do
+ @s.rb_ary_delete_at(@array, 2).should == 3
+ @array.should == [1, 2, 4]
+ end
+
+ it "removes an element from an array at a negative index" do
+ @s.rb_ary_delete_at(@array, -3).should == 2
+ @array.should == [1, 3, 4]
+ end
+
+ it "returns nil if the index is out of bounds" do
+ @s.rb_ary_delete_at(@array, 4).should be_nil
+ @array.should == [1, 2, 3, 4]
+ end
+
+ it "returns nil if the negative index is out of bounds" do
+ @s.rb_ary_delete_at(@array, -5).should be_nil
+ @array.should == [1, 2, 3, 4]
+ end
+ end
+
+ describe "rb_ary_to_ary" do
+
+ describe "with an array" do
+
+ it "returns the given array" do
+ array = [1, 2, 3]
+ @s.rb_ary_to_ary(array).should equal(array)
+ end
+
+ end
+
+ describe "with an object that responds to to_ary" do
+
+ it "calls to_ary on the object" do
+ obj = mock('to_ary')
+ obj.stub!(:to_ary).and_return([1, 2, 3])
+ @s.rb_ary_to_ary(obj).should == [1, 2, 3]
+ end
+
+ end
+
+ describe "with an object that responds to to_a" do
+
+ it "returns the original object in an array" do
+ obj = mock('to_a')
+ obj.stub!(:to_a).and_return([1, 2, 3])
+ @s.rb_ary_to_ary(obj).should == [obj]
+ end
+
+ end
+
+ describe "with an object that doesn't respond to to_ary" do
+
+ it "returns the original object in an array" do
+ obj = mock('no_to_ary')
+ @s.rb_ary_to_ary(obj).should == [obj]
+ end
+
+ end
+
+ end
+
+ describe "rb_ary_subseq" do
+ it "returns a subsequence of the given array" do
+ @s.rb_ary_subseq([1, 2, 3, 4, 5], 1, 3).should == [2, 3, 4]
+ end
+
+ it "returns an empty array for a subsequence of 0 elements" do
+ @s.rb_ary_subseq([1, 2, 3, 4, 5], 1, 0).should == []
+ end
+
+ it "returns nil if the begin index is out of bound" do
+ @s.rb_ary_subseq([1, 2, 3, 4, 5], 6, 3).should be_nil
+ end
+
+ it "returns the existing subsequence of the length is out of bounds" do
+ @s.rb_ary_subseq([1, 2, 3, 4, 5], 4, 3).should == [5]
+ end
+
+ it "returns nil if the size is negative" do
+ @s.rb_ary_subseq([1, 2, 3, 4, 5], 1, -1).should be_nil
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/basic_object_spec.rb b/spec/ruby/optional/capi/basic_object_spec.rb
new file mode 100644
index 0000000000..2922a421da
--- /dev/null
+++ b/spec/ruby/optional/capi/basic_object_spec.rb
@@ -0,0 +1,24 @@
+require_relative 'spec_helper'
+
+load_extension("basic_object")
+
+describe "C-API basic object" do
+ before :each do
+ @s = CApiBasicObjectSpecs.new
+ end
+
+ describe "RBASIC_CLASS" do
+ it "returns the class of an object" do
+ c = Class.new
+ o = c.new
+ @s.RBASIC_CLASS(o).should == c
+ end
+
+ it "returns the singleton class" do
+ o = Object.new
+ @s.RBASIC_CLASS(o).should == Object
+ singleton_class = o.singleton_class
+ @s.RBASIC_CLASS(o).should == singleton_class
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/bignum_spec.rb b/spec/ruby/optional/capi/bignum_spec.rb
new file mode 100644
index 0000000000..cde929af28
--- /dev/null
+++ b/spec/ruby/optional/capi/bignum_spec.rb
@@ -0,0 +1,224 @@
+require_relative 'spec_helper'
+
+load_extension("bignum")
+
+def ensure_bignum(n)
+ raise "Bignum#coerce returned Fixnum" if fixnum_min <= n && n <= fixnum_max
+ n
+end
+
+full_range_longs = (fixnum_max == 2**(0.size * 8 - 1) - 1)
+
+describe "CApiBignumSpecs" do
+ before :each do
+ @s = CApiBignumSpecs.new
+
+ if full_range_longs
+ @max_long = 2**(0.size * 8 - 1) - 1
+ @min_long = -@max_long - 1
+ @max_ulong = ensure_bignum(2**(0.size * 8) - 1)
+ else
+ @max_long = ensure_bignum(2**(0.size * 8 - 1) - 1)
+ @min_long = ensure_bignum(-@max_long - 1)
+ @max_ulong = ensure_bignum(2**(0.size * 8) - 1)
+ end
+ end
+
+ describe "rb_big2long" do
+ unless full_range_longs
+ it "converts a Bignum" do
+ @s.rb_big2long(@max_long).should == @max_long
+ @s.rb_big2long(@min_long).should == @min_long
+ end
+ end
+
+ it "raises RangeError if passed Bignum overflow long" do
+ -> { @s.rb_big2long(ensure_bignum(@max_long + 1)) }.should raise_error(RangeError)
+ -> { @s.rb_big2long(ensure_bignum(@min_long - 1)) }.should raise_error(RangeError)
+ end
+ end
+
+ describe "rb_big2ll" do
+ unless full_range_longs
+ it "converts a Bignum" do
+ @s.rb_big2ll(@max_long).should == @max_long
+ @s.rb_big2ll(@min_long).should == @min_long
+ end
+ end
+
+ it "raises RangeError if passed Bignum overflow long" do
+ -> { @s.rb_big2ll(ensure_bignum(@max_long << 40)) }.should raise_error(RangeError)
+ -> { @s.rb_big2ll(ensure_bignum(@min_long << 40)) }.should raise_error(RangeError)
+ end
+ end
+
+ describe "rb_big2ulong" do
+ it "converts a Bignum" do
+ @s.rb_big2ulong(@max_ulong).should == @max_ulong
+ end
+
+ unless full_range_longs
+ it "wraps around if passed a negative bignum" do
+ @s.rb_big2ulong(ensure_bignum(@min_long + 1)).should == -(@min_long - 1)
+ @s.rb_big2ulong(ensure_bignum(@min_long)).should == -(@min_long)
+ end
+ end
+
+ it "raises RangeError if passed Bignum overflow long" do
+ -> { @s.rb_big2ulong(ensure_bignum(@max_ulong + 1)) }.should raise_error(RangeError)
+ -> { @s.rb_big2ulong(ensure_bignum(@min_long - 1)) }.should raise_error(RangeError)
+ end
+ end
+
+ describe "rb_big2dbl" do
+ it "converts a Bignum to a double value" do
+ @s.rb_big2dbl(ensure_bignum(Float::MAX.to_i)).eql?(Float::MAX).should == true
+ end
+
+ it "returns Infinity if the number is too big for a double" do
+ huge_bignum = ensure_bignum(Float::MAX.to_i * 2)
+ @s.rb_big2dbl(huge_bignum).should == infinity_value
+ end
+
+ it "returns -Infinity if the number is negative and too big for a double" do
+ huge_bignum = -ensure_bignum(Float::MAX.to_i * 2)
+ @s.rb_big2dbl(huge_bignum).should == -infinity_value
+ end
+ end
+
+ describe "rb_big2str" do
+
+ it "converts a Bignum to a string with base 10" do
+ @s.rb_big2str(ensure_bignum(2**70), 10).eql?("1180591620717411303424").should == true
+ end
+
+ it "converts a Bignum to a string with a different base" do
+ @s.rb_big2str(ensure_bignum(2**70), 16).eql?("400000000000000000").should == true
+ end
+ end
+
+ describe "RBIGNUM_SIGN" do
+ it "returns 1 for a positive Bignum" do
+ @s.RBIGNUM_SIGN(bignum_value(1)).should == 1
+ end
+
+ it "returns 0 for a negative Bignum" do
+ @s.RBIGNUM_SIGN(-bignum_value(1)).should == 0
+ end
+ end
+
+ describe "rb_big_cmp" do
+ it "compares a Bignum with a Bignum" do
+ @s.rb_big_cmp(bignum_value, bignum_value(1)).should == -1
+ end
+
+ it "compares a Bignum with a Fixnum" do
+ @s.rb_big_cmp(bignum_value, 5).should == 1
+ end
+ end
+
+ describe "rb_big_pack" do
+ it "packs a Bignum into an unsigned long" do
+ val = @s.rb_big_pack(@max_ulong)
+ val.should == @max_ulong
+ end
+
+ platform_is wordsize: 64 do
+ it "packs max_ulong into 2 ulongs to allow sign bit" do
+ val = @s.rb_big_pack_length(@max_ulong)
+ val.should == 2
+ val = @s.rb_big_pack_array(@max_ulong, 2)
+ val[0].should == @max_ulong
+ val[1].should == 0
+ end
+
+ it "packs a 72-bit positive Bignum into 2 unsigned longs" do
+ num = 2 ** 71
+ val = @s.rb_big_pack_length(num)
+ val.should == 2
+ end
+
+ it "packs a 72-bit positive Bignum into correct 2 longs" do
+ num = 2 ** 71 + 1
+ val = @s.rb_big_pack_array(num, 2)
+ val[0].should == 1;
+ val[1].should == 0x80;
+ end
+
+ it "packs a 72-bit negative Bignum into correct 2 longs" do
+ num = -(2 ** 71 + 1)
+ val = @s.rb_big_pack_array(num, @s.rb_big_pack_length(num))
+ val[0].should == @max_ulong;
+ val[1].should == @max_ulong - 0x80;
+ end
+
+ it "packs lower order bytes into least significant bytes of longs for positive bignum" do
+ num = 0
+ 32.times { |i| num += i << (i * 8) }
+ val = @s.rb_big_pack_array(num, @s.rb_big_pack_length(num))
+ val.size.should == 4
+ 32.times do |i|
+ a_long = val[i/8]
+ a_byte = (a_long >> ((i % 8) * 8)) & 0xff
+ a_byte.should == i
+ end
+ end
+
+ it "packs lower order bytes into least significant bytes of longs for negative bignum" do
+ num = 0
+ 32.times { |i| num += i << (i * 8) }
+ num = -num
+ val = @s.rb_big_pack_array(num, @s.rb_big_pack_length(num))
+ val.size.should == 4
+ expected_bytes = [0x00, 0xff, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
+ 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0,
+ 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8,
+ 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0 ]
+ 32.times do |i|
+ a_long = val[i/8]
+ a_byte = (a_long >> ((i % 8) * 8)) & 0xff
+ a_byte.should == expected_bytes[i]
+ end
+ end
+ end
+ end
+
+ describe "rb_dbl2big" do
+ it "returns a Fixnum for a Fixnum input value" do
+ val = @s.rb_dbl2big(2)
+
+ val.kind_of?(Integer).should == true
+ val.should == 2
+ end
+
+ it "returns a Fixnum for a Float input value" do
+ val = @s.rb_dbl2big(2.5)
+
+ val.kind_of?(Integer).should == true
+ val.should == 2
+ end
+
+ it "returns a Bignum for a large enough Float input value" do
+ input = 219238102380912830988.5 # chosen by fair dice roll
+ val = @s.rb_dbl2big(input)
+
+ val.kind_of?(Integer).should == true
+
+ # This value is based on the output of a simple C extension that uses
+ # rb_dbl2big() to convert the above input value to a Bignum.
+ val.should == 219238102380912836608
+ end
+
+ it "raises FloatDomainError for Infinity values" do
+ inf = 1.0 / 0
+
+ -> { @s.rb_dbl2big(inf) }.should raise_error(FloatDomainError)
+ end
+
+ it "raises FloatDomainError for NaN values" do
+ nan = 0.0 / 0
+
+ -> { @s.rb_dbl2big(nan) }.should raise_error(FloatDomainError)
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/binding_spec.rb b/spec/ruby/optional/capi/binding_spec.rb
new file mode 100644
index 0000000000..2165705457
--- /dev/null
+++ b/spec/ruby/optional/capi/binding_spec.rb
@@ -0,0 +1,28 @@
+require_relative 'spec_helper'
+
+load_extension("binding")
+
+describe "CApiBindingSpecs" do
+ before :each do
+ @b = CApiBindingSpecs.new
+ end
+
+ describe "Kernel#binding" do
+ ruby_version_is '3.2' do
+ it "raises when called from C" do
+ foo = 14
+ -> { @b.get_binding }.should raise_error(RuntimeError)
+ end
+ end
+
+ ruby_version_is ''...'3.2' do
+ it "gives the top-most Ruby binding when called from C" do
+ foo = 14
+ b = @b.get_binding
+ b.local_variable_get(:foo).should == 14
+ b.local_variable_set :foo, 12
+ foo.should == 12
+ end
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/boolean_spec.rb b/spec/ruby/optional/capi/boolean_spec.rb
new file mode 100644
index 0000000000..351419cbec
--- /dev/null
+++ b/spec/ruby/optional/capi/boolean_spec.rb
@@ -0,0 +1,33 @@
+require_relative 'spec_helper'
+
+load_extension("boolean")
+
+describe "CApiBooleanSpecs" do
+ before :each do
+ @b = CApiBooleanSpecs.new
+ end
+
+ describe "a true value from Ruby" do
+ it "is truthy in C" do
+ @b.is_true(true).should == 1
+ end
+ end
+
+ describe "a true value from Qtrue" do
+ it "is truthy in C" do
+ @b.is_true(@b.q_true).should == 1
+ end
+ end
+
+ describe "a false value from Ruby" do
+ it "is falsey in C" do
+ @b.is_true(false).should == 2
+ end
+ end
+
+ describe "a false value from Qfalse" do
+ it "is falsey in C" do
+ @b.is_true(@b.q_false).should == 2
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/class_spec.rb b/spec/ruby/optional/capi/class_spec.rb
new file mode 100644
index 0000000000..66af381243
--- /dev/null
+++ b/spec/ruby/optional/capi/class_spec.rb
@@ -0,0 +1,492 @@
+require_relative 'spec_helper'
+require_relative 'fixtures/class'
+require_relative '../../core/module/fixtures/classes'
+
+load_extension("class")
+compile_extension("class_under_autoload")
+compile_extension("class_id_under_autoload")
+
+autoload :ClassUnderAutoload, "#{object_path}/class_under_autoload_spec"
+autoload :ClassIdUnderAutoload, "#{object_path}/class_id_under_autoload_spec"
+
+describe :rb_path_to_class, shared: true do
+ it "returns a class or module from a scoped String" do
+ @s.send(@method, "CApiClassSpecs::A::B").should equal(CApiClassSpecs::A::B)
+ @s.send(@method, "CApiClassSpecs::A::M").should equal(CApiClassSpecs::A::M)
+ end
+
+ it "resolves autoload constants" do
+ @s.send(@method, "CApiClassSpecs::A::D").name.should == "CApiClassSpecs::A::D"
+ end
+
+ it "raises an ArgumentError if a constant in the path does not exist" do
+ -> { @s.send(@method, "CApiClassSpecs::NotDefined::B") }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the final constant does not exist" do
+ -> { @s.send(@method, "CApiClassSpecs::NotDefined") }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if the constant is not a class or module" do
+ -> {
+ @s.send(@method, "CApiClassSpecs::A::C")
+ }.should raise_error(TypeError, 'CApiClassSpecs::A::C does not refer to class/module')
+ end
+
+ it "raises an ArgumentError even if a constant in the path exists on toplevel" do
+ -> { @s.send(@method, "CApiClassSpecs::Object") }.should raise_error(ArgumentError)
+ end
+end
+
+describe "C-API Class function" do
+ before :each do
+ @s = CApiClassSpecs.new
+ end
+
+ describe "rb_class_instance_methods" do
+ it "returns the public and protected methods of self and its ancestors" do
+ methods = @s.rb_class_instance_methods(ModuleSpecs::Basic)
+ methods.should include(:protected_module, :public_module)
+
+ methods = @s.rb_class_instance_methods(ModuleSpecs::Basic, true)
+ methods.should include(:protected_module, :public_module)
+ end
+
+ it "when passed false as a parameter, returns the instance methods of the class" do
+ methods = @s.rb_class_instance_methods(ModuleSpecs::Child, false)
+ methods.should include(:protected_child, :public_child)
+ end
+ end
+
+ describe "rb_class_public_instance_methods" do
+ it "returns a list of public methods in module and its ancestors" do
+ methods = @s.rb_class_public_instance_methods(ModuleSpecs::CountsChild)
+ methods.should include(:public_3)
+ methods.should include(:public_2)
+ methods.should include(:public_1)
+
+ methods = @s.rb_class_public_instance_methods(ModuleSpecs::CountsChild, true)
+ methods.should include(:public_3)
+ methods.should include(:public_2)
+ methods.should include(:public_1)
+ end
+
+ it "when passed false as a parameter, should return only methods defined in that module" do
+ @s.rb_class_public_instance_methods(ModuleSpecs::CountsChild, false).should == [:public_1]
+ end
+ end
+
+ describe "rb_class_protected_instance_methods" do
+ it "returns a list of protected methods in module and its ancestors" do
+ methods = @s.rb_class_protected_instance_methods(ModuleSpecs::CountsChild)
+ methods.should include(:protected_3)
+ methods.should include(:protected_2)
+ methods.should include(:protected_1)
+
+ methods = @s.rb_class_protected_instance_methods(ModuleSpecs::CountsChild, true)
+ methods.should include(:protected_3)
+ methods.should include(:protected_2)
+ methods.should include(:protected_1)
+ end
+
+ it "when passed false as a parameter, should return only methods defined in that module" do
+ @s.rb_class_public_instance_methods(ModuleSpecs::CountsChild, false).should == [:public_1]
+ end
+ end
+
+ describe "rb_class_private_instance_methods" do
+ it "returns a list of private methods in module and its ancestors" do
+ @s.rb_class_private_instance_methods(ModuleSpecs::CountsChild).should == ModuleSpecs::CountsChild.private_instance_methods
+ @s.rb_class_private_instance_methods(ModuleSpecs::CountsChild, true).should == ModuleSpecs::CountsChild.private_instance_methods
+ end
+
+ it "when passed false as a parameter, should return only methods defined in that module" do
+ methods = @s.rb_class_private_instance_methods(ModuleSpecs::CountsChild, false)
+ methods.should == [:private_1]
+ end
+ end
+
+ describe "rb_class_new_instance" do
+ it "allocates and initializes a new object" do
+ o = @s.rb_class_new_instance([], CApiClassSpecs::Alloc)
+ o.class.should == CApiClassSpecs::Alloc
+ o.initialized.should be_true
+ end
+
+ it "passes arguments to the #initialize method" do
+ o = @s.rb_class_new_instance([:one, :two], CApiClassSpecs::Alloc)
+ o.arguments.should == [:one, :two]
+ end
+ end
+
+ ruby_version_is "3.0" do
+ describe "rb_class_new_instance_kw" do
+ it "passes arguments and keywords to the #initialize method" do
+ obj = @s.rb_class_new_instance_kw([{pos: 1}, {kw: 2}], CApiClassSpecs::KeywordAlloc)
+ obj.args.should == [{pos: 1}]
+ obj.kwargs.should == {kw: 2}
+
+ obj = @s.rb_class_new_instance_kw([{}], CApiClassSpecs::KeywordAlloc)
+ obj.args.should == []
+ obj.kwargs.should == {}
+ end
+
+ it "raises TypeError if the last argument is not a Hash" do
+ -> {
+ @s.rb_class_new_instance_kw([42], CApiClassSpecs::KeywordAlloc)
+ }.should raise_error(TypeError, 'no implicit conversion of Integer into Hash')
+ end
+ end
+ end
+
+ describe "rb_include_module" do
+ it "includes a module into a class" do
+ c = Class.new
+ o = c.new
+ -> { o.included? }.should raise_error(NameError)
+ @s.rb_include_module(c, CApiClassSpecs::M)
+ o.included?.should be_true
+ end
+ end
+
+ describe "rb_define_attr" do
+ before :each do
+ @a = CApiClassSpecs::Attr.new
+ end
+
+ it "defines an attr_reader when passed true, false" do
+ @s.rb_define_attr(CApiClassSpecs::Attr, :foo, true, false)
+ @a.foo.should == 1
+ -> { @a.foo = 5 }.should raise_error(NameError)
+ end
+
+ it "defines an attr_writer when passed false, true" do
+ @s.rb_define_attr(CApiClassSpecs::Attr, :bar, false, true)
+ -> { @a.bar }.should raise_error(NameError)
+ @a.bar = 5
+ @a.instance_variable_get(:@bar).should == 5
+ end
+
+ it "defines an attr_accessor when passed true, true" do
+ @s.rb_define_attr(CApiClassSpecs::Attr, :baz, true, true)
+ @a.baz.should == 3
+ @a.baz = 6
+ @a.baz.should == 6
+ end
+ end
+
+ describe "rb_call_super" do
+ it "calls the method in the superclass" do
+ @s.define_call_super_method CApiClassSpecs::Sub, "call_super_method"
+ obj = CApiClassSpecs::Sub.new
+ obj.call_super_method.should == :super_method
+ end
+
+ it "calls the method in the superclass with the correct self" do
+ @s.define_call_super_method CApiClassSpecs::SubSelf, "call_super_method"
+ obj = CApiClassSpecs::SubSelf.new
+ obj.call_super_method.should equal obj
+ end
+
+ it "calls the method in the superclass through two native levels" do
+ @s.define_call_super_method CApiClassSpecs::Sub, "call_super_method"
+ @s.define_call_super_method CApiClassSpecs::SubSub, "call_super_method"
+ obj = CApiClassSpecs::SubSub.new
+ obj.call_super_method.should == :super_method
+ end
+ end
+
+ describe "rb_class2name" do
+ it "returns the class name" do
+ @s.rb_class2name(CApiClassSpecs).should == "CApiClassSpecs"
+ end
+
+ it "returns a string for an anonymous class" do
+ @s.rb_class2name(Class.new).should be_kind_of(String)
+ end
+
+ it "returns a string beginning with # for an anonymous class" do
+ @s.rb_class2name(Struct.new(:x, :y).new(1, 2).class).should.start_with?('#')
+ end
+ end
+
+ describe "rb_class_path" do
+ it "returns a String of a class path with no scope modifiers" do
+ @s.rb_class_path(Array).should == "Array"
+ end
+
+ it "returns a String of a class path with scope modifiers" do
+ @s.rb_class_path(File::Stat).should == "File::Stat"
+ end
+ end
+
+ describe "rb_class_name" do
+ it "returns the class name" do
+ @s.rb_class_name(CApiClassSpecs).should == "CApiClassSpecs"
+ end
+
+ it "returns a string for an anonymous class" do
+ @s.rb_class_name(Class.new).should be_kind_of(String)
+ end
+ end
+
+ describe "rb_path2class" do
+ it_behaves_like :rb_path_to_class, :rb_path2class
+ end
+
+ describe "rb_path_to_class" do
+ it_behaves_like :rb_path_to_class, :rb_path_to_class
+ end
+
+ describe "rb_cvar_defined" do
+ it "returns false when the class variable is not defined" do
+ @s.rb_cvar_defined(CApiClassSpecs::CVars, "@@nocvar").should be_false
+ end
+
+ it "returns true when the class variable is defined" do
+ @s.rb_cvar_defined(CApiClassSpecs::CVars, "@@cvar").should be_true
+ end
+
+ it "returns true if the class instance variable is defined" do
+ @s.rb_cvar_defined(CApiClassSpecs::CVars, "@c_ivar").should be_true
+ end
+ end
+
+ describe "rb_cv_set" do
+ it "sets a class variable" do
+ o = CApiClassSpecs::CVars.new
+ o.new_cv.should be_nil
+ @s.rb_cv_set(CApiClassSpecs::CVars, "@@new_cv", 1)
+ o.new_cv.should == 1
+ CApiClassSpecs::CVars.remove_class_variable :@@new_cv
+ end
+ end
+
+ describe "rb_cv_get" do
+ it "returns the value of the class variable" do
+ @s.rb_cvar_get(CApiClassSpecs::CVars, "@@cvar").should == :cvar
+ end
+
+ it "raises a NameError if the class variable is not defined" do
+ -> {
+ @s.rb_cv_get(CApiClassSpecs::CVars, "@@no_cvar")
+ }.should raise_error(NameError, /class variable @@no_cvar/)
+ end
+ end
+
+ describe "rb_cvar_set" do
+ it "sets a class variable" do
+ o = CApiClassSpecs::CVars.new
+ o.new_cvar.should be_nil
+ @s.rb_cvar_set(CApiClassSpecs::CVars, "@@new_cvar", 1)
+ o.new_cvar.should == 1
+ CApiClassSpecs::CVars.remove_class_variable :@@new_cvar
+ end
+
+ end
+
+ describe "rb_define_class" do
+ before :each do
+ @cls = @s.rb_define_class("ClassSpecDefineClass", CApiClassSpecs::Super)
+ end
+
+ it "creates a subclass of the superclass" do
+ @cls.should be_kind_of(Class)
+ ClassSpecDefineClass.should equal(@cls)
+ @cls.superclass.should == CApiClassSpecs::Super
+ end
+
+ it "sets the class name" do
+ @cls.name.should == "ClassSpecDefineClass"
+ end
+
+ it "calls #inherited on the superclass" do
+ CApiClassSpecs::Super.should_receive(:inherited)
+ @s.rb_define_class("ClassSpecDefineClass2", CApiClassSpecs::Super)
+ Object.send(:remove_const, :ClassSpecDefineClass2)
+ end
+
+ it "raises a TypeError when given a non class object to superclass" do
+ -> {
+ @s.rb_define_class("ClassSpecDefineClass3", Module.new)
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when given a mismatched class to superclass" do
+ -> {
+ @s.rb_define_class("ClassSpecDefineClass", Object)
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a ArgumentError when given NULL as superclass" do
+ -> {
+ @s.rb_define_class("ClassSpecDefineClass4", nil)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "allows arbitrary names, including constant names not valid in Ruby" do
+ cls = @s.rb_define_class("_INVALID_CLASS", CApiClassSpecs::Super)
+ cls.name.should == "_INVALID_CLASS"
+
+ -> {
+ Object.const_get(cls.name)
+ }.should raise_error(NameError, /wrong constant name/)
+ end
+ end
+
+ describe "rb_define_class_under" do
+ it "creates a subclass of the superclass contained in a module" do
+ cls = @s.rb_define_class_under(CApiClassSpecs,
+ "ClassUnder1",
+ CApiClassSpecs::Super)
+ cls.should be_kind_of(Class)
+ CApiClassSpecs::Super.should be_ancestor_of(CApiClassSpecs::ClassUnder1)
+ end
+
+ it "sets the class name" do
+ cls = @s.rb_define_class_under(CApiClassSpecs, "ClassUnder3", Object)
+ cls.name.should == "CApiClassSpecs::ClassUnder3"
+ end
+
+ it "calls #inherited on the superclass" do
+ CApiClassSpecs::Super.should_receive(:inherited)
+ @s.rb_define_class_under(CApiClassSpecs, "ClassUnder4", CApiClassSpecs::Super)
+ CApiClassSpecs.send(:remove_const, :ClassUnder4)
+ end
+
+ it "raises a TypeError when given a non class object to superclass" do
+ -> { @s.rb_define_class_under(CApiClassSpecs,
+ "ClassUnder5",
+ Module.new)
+ }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when given a mismatched class to superclass" do
+ CApiClassSpecs::ClassUnder6 = Class.new(CApiClassSpecs::Super)
+ -> { @s.rb_define_class_under(CApiClassSpecs,
+ "ClassUnder6",
+ Class.new)
+ }.should raise_error(TypeError)
+ end
+
+ it "defines a class for an existing Autoload" do
+ ClassUnderAutoload.name.should == "ClassUnderAutoload"
+ end
+
+ it "raises a TypeError if class is defined and its superclass mismatches the given one" do
+ -> { @s.rb_define_class_under(CApiClassSpecs, "Sub", Object) }.should raise_error(TypeError)
+ end
+
+ it "allows arbitrary names, including constant names not valid in Ruby" do
+ cls = @s.rb_define_class_under(CApiClassSpecs, "_INVALID_CLASS", CApiClassSpecs::Super)
+ cls.name.should == "CApiClassSpecs::_INVALID_CLASS"
+
+ -> {
+ CApiClassSpecs.const_get(cls.name)
+ }.should raise_error(NameError, /wrong constant name/)
+ end
+ end
+
+ describe "rb_define_class_id_under" do
+ it "creates a subclass of the superclass contained in a module" do
+ cls = @s.rb_define_class_id_under(CApiClassSpecs, :ClassIdUnder1, CApiClassSpecs::Super)
+ cls.should be_kind_of(Class)
+ CApiClassSpecs::Super.should be_ancestor_of(CApiClassSpecs::ClassIdUnder1)
+ end
+
+ it "sets the class name" do
+ cls = @s.rb_define_class_id_under(CApiClassSpecs, :ClassIdUnder3, Object)
+ cls.name.should == "CApiClassSpecs::ClassIdUnder3"
+ end
+
+ it "calls #inherited on the superclass" do
+ CApiClassSpecs::Super.should_receive(:inherited)
+ @s.rb_define_class_id_under(CApiClassSpecs, :ClassIdUnder4, CApiClassSpecs::Super)
+ CApiClassSpecs.send(:remove_const, :ClassIdUnder4)
+ end
+
+ it "defines a class for an existing Autoload" do
+ ClassIdUnderAutoload.name.should == "ClassIdUnderAutoload"
+ end
+
+ it "raises a TypeError if class is defined and its superclass mismatches the given one" do
+ -> { @s.rb_define_class_id_under(CApiClassSpecs, :Sub, Object) }.should raise_error(TypeError)
+ end
+
+ it "allows arbitrary names, including constant names not valid in Ruby" do
+ cls = @s.rb_define_class_id_under(CApiClassSpecs, :_INVALID_CLASS2, CApiClassSpecs::Super)
+ cls.name.should == "CApiClassSpecs::_INVALID_CLASS2"
+
+ -> {
+ CApiClassSpecs.const_get(cls.name)
+ }.should raise_error(NameError, /wrong constant name/)
+ end
+ end
+
+ describe "rb_define_class_variable" do
+ it "sets a class variable" do
+ o = CApiClassSpecs::CVars.new
+ o.rbdcv_cvar.should be_nil
+ @s.rb_define_class_variable(CApiClassSpecs::CVars, "@@rbdcv_cvar", 1)
+ o.rbdcv_cvar.should == 1
+ CApiClassSpecs::CVars.remove_class_variable :@@rbdcv_cvar
+ end
+ end
+
+ describe "rb_cvar_get" do
+ it "returns the value of the class variable" do
+ @s.rb_cvar_get(CApiClassSpecs::CVars, "@@cvar").should == :cvar
+ end
+
+ it "raises a NameError if the class variable is not defined" do
+ -> {
+ @s.rb_cvar_get(CApiClassSpecs::CVars, "@@no_cvar")
+ }.should raise_error(NameError, /class variable @@no_cvar/)
+ end
+ end
+
+ describe "rb_class_new" do
+ it "returns a new subclass of the superclass" do
+ subclass = @s.rb_class_new(CApiClassSpecs::NewClass)
+ CApiClassSpecs::NewClass.should be_ancestor_of(subclass)
+ end
+
+ it "raises a TypeError if passed Class as the superclass" do
+ -> { @s.rb_class_new(Class) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed a singleton class as the superclass" do
+ metaclass = Object.new.singleton_class
+ -> { @s.rb_class_new(metaclass) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "rb_class_superclass" do
+ it "returns the superclass of a class" do
+ cls = @s.rb_class_superclass(CApiClassSpecs::Sub)
+ cls.should == CApiClassSpecs::Super
+ end
+
+ it "returns nil if the class has no superclass" do
+ @s.rb_class_superclass(BasicObject).should be_nil
+ end
+ end
+
+ describe "rb_class_real" do
+ it "returns the class of an object ignoring the singleton class" do
+ obj = CApiClassSpecs::Sub.new
+ def obj.some_method() end
+
+ @s.rb_class_real(obj).should == CApiClassSpecs::Sub
+ end
+
+ it "returns the class of an object ignoring included modules" do
+ obj = CApiClassSpecs::SubM.new
+ @s.rb_class_real(obj).should == CApiClassSpecs::SubM
+ end
+
+ it "returns 0 if passed 0" do
+ @s.rb_class_real(0).should == 0
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/complex_spec.rb b/spec/ruby/optional/capi/complex_spec.rb
new file mode 100644
index 0000000000..3d8142d172
--- /dev/null
+++ b/spec/ruby/optional/capi/complex_spec.rb
@@ -0,0 +1,45 @@
+require_relative 'spec_helper'
+
+load_extension("complex")
+
+describe :rb_Complex, shared: true do
+ it "creates a new Complex with numerator and denominator" do
+ @r.send(@method, 1, 2).should == Complex(1, 2)
+ end
+end
+
+describe :rb_complex_new, shared: true do
+ it "creates a normalized Complex" do
+ r = @r.send(@method, 10, 4)
+ r.real.should == 10
+ r.imag.should == 4
+ end
+end
+
+describe "CApiComplexSpecs" do
+ before :each do
+ @r = CApiComplexSpecs.new
+ end
+
+ describe "rb_Complex" do
+ it_behaves_like :rb_Complex, :rb_Complex
+ end
+
+ describe "rb_Complex2" do
+ it_behaves_like :rb_Complex, :rb_Complex2
+ end
+
+ describe "rb_Complex1" do
+ it "creates a new Complex with real and imaginary of 0" do
+ @r.rb_Complex1(5).should == Complex(5, 0)
+ end
+ end
+
+ describe "rb_complex_new" do
+ it_behaves_like :rb_complex_new, :rb_complex_new
+ end
+
+ describe "rb_complex_new2" do
+ it_behaves_like :rb_complex_new, :rb_complex_new2
+ end
+end
diff --git a/spec/ruby/optional/capi/constants_spec.rb b/spec/ruby/optional/capi/constants_spec.rb
new file mode 100644
index 0000000000..172d10a788
--- /dev/null
+++ b/spec/ruby/optional/capi/constants_spec.rb
@@ -0,0 +1,325 @@
+require_relative 'spec_helper'
+
+load_extension("constants")
+
+describe "C-API constant" do
+ before :each do
+ @s = CApiConstantsSpecs.new
+ end
+
+ specify "rb_cArray references the Array class" do
+ @s.rb_cArray.should == Array
+ end
+
+ specify "rb_cBasicObject references the BasicObject class" do
+ @s.rb_cBasicObject.should == BasicObject
+ end
+
+ specify "rb_cBinding references the Binding class" do
+ @s.rb_cBinding.should == Binding
+ end
+
+ specify "rb_cClass references the Class class" do
+ @s.rb_cClass.should == Class
+ end
+
+ specify "rb_cComplex references the Complex class" do
+ @s.rb_cComplex.should == Complex
+ end
+
+ specify "rb_mComparable references the Comparable module" do
+ @s.rb_mComparable.should == Comparable
+ end
+
+ specify "rb_cDir references the Dir class" do
+ @s.rb_cDir.should == Dir
+ end
+
+ specify "rb_cEncoding references the Encoding class" do
+ @s.rb_cEncoding.should == Encoding
+ end
+
+ specify "rb_mEnumerable references the Enumerable module" do
+ @s.rb_mEnumerable.should == Enumerable
+ end
+
+ specify "rb_cEnumerator references the Enumerator class" do
+ @s.rb_cEnumerator.should == Enumerator
+ end
+
+ specify "rb_cFalseClass references the FalseClass class" do
+ @s.rb_cFalseClass.should == FalseClass
+ end
+
+ specify "rb_cFile references the File class" do
+ @s.rb_cFile.should == File
+ end
+
+ specify "rb_mFileTest references the FileTest module" do
+ @s.rb_mFileTest.should == FileTest
+ end
+
+ specify "rb_cFloat references the Float class" do
+ @s.rb_cFloat.should == Float
+ end
+
+ specify "rb_mGC references the GC module" do
+ @s.rb_mGC.should == GC
+ end
+
+ specify "rb_cHash references the Hash class" do
+ @s.rb_cHash.should == Hash
+ end
+
+ specify "rb_cInteger references the Integer class" do
+ @s.rb_cInteger.should == Integer
+ end
+
+ specify "rb_cIO references the IO class" do
+ @s.rb_cIO.should == IO
+ end
+
+ specify "rb_mKernel references the Kernel module" do
+ @s.rb_mKernel.should == Kernel
+ end
+
+ specify "rb_mMath references the Math module" do
+ @s.rb_mMath.should == Math
+ end
+
+ specify "rb_cMatch references the MatchData class" do
+ @s.rb_cMatch.should == MatchData
+ end
+
+ specify "rb_cMethod references the Method class" do
+ @s.rb_cMethod.should == Method
+ end
+
+ specify "rb_cModule references the Module class" do
+ @s.rb_cModule.should == Module
+ end
+
+ specify "rb_cNilClass references the NilClass class" do
+ @s.rb_cNilClass.should == NilClass
+ end
+
+ specify "rb_cNumeric references the Numeric class" do
+ @s.rb_cNumeric.should == Numeric
+ end
+
+ specify "rb_cObject references the Object class" do
+ @s.rb_cObject.should == Object
+ end
+
+ specify "rb_cProc references the Proc class" do
+ @s.rb_cProc.should == Proc
+ end
+
+ specify "rb_mProcess references the Process module" do
+ @s.rb_mProcess.should == Process
+ end
+
+ specify "rb_cRandom references the Random class" do
+ @s.rb_cRandom.should == Random
+ end
+
+ specify "rb_cRange references the Range class" do
+ @s.rb_cRange.should == Range
+ end
+
+ specify "rb_cRational references the Rational class" do
+ @s.rb_cRational.should == Rational
+ end
+
+ specify "rb_cRegexp references the Regexp class" do
+ @s.rb_cRegexp.should == Regexp
+ end
+
+ specify "rb_cStat references the File::Stat class" do
+ @s.rb_cStat.should == File::Stat
+ end
+
+ specify "rb_cString references the String class" do
+ @s.rb_cString.should == String
+ end
+
+ specify "rb_cStruct references the Struct class" do
+ @s.rb_cStruct.should == Struct
+ end
+
+ specify "rb_cSymbol references the Symbol class" do
+ @s.rb_cSymbol.should == Symbol
+ end
+
+ specify "rb_cTime references the Time class" do
+ @s.rb_cTime.should == Time
+ end
+
+ specify "rb_cThread references the Thread class" do
+ @s.rb_cThread.should == Thread
+ end
+
+ specify "rb_cTrueClass references the TrueClass class" do
+ @s.rb_cTrueClass.should == TrueClass
+ end
+
+ specify "rb_cUnboundMethod references the UnboundMethod class" do
+ @s.rb_cUnboundMethod.should == UnboundMethod
+ end
+end
+
+describe "C-API exception constant" do
+ before :each do
+ @s = CApiConstantsSpecs.new
+ end
+
+ specify "rb_eArgError references the ArgumentError class" do
+ @s.rb_eArgError.should == ArgumentError
+ end
+
+ specify "rb_eEncodingError references the EncodingError class" do
+ @s.rb_eEncodingError.should == EncodingError
+ end
+
+ specify "rb_eEncCompatError references the Encoding::CompatibilityError" do
+ @s.rb_eEncCompatError.should == Encoding::CompatibilityError
+ end
+
+ specify "rb_eEOFError references the EOFError class" do
+ @s.rb_eEOFError.should == EOFError
+ end
+
+ specify "rb_eErrno references the Errno module" do
+ @s.rb_mErrno.should == Errno
+ end
+
+ specify "rb_eException references the Exception class" do
+ @s.rb_eException.should == Exception
+ end
+
+ specify "rb_eFatal references the fatal class" do
+ fatal = @s.rb_eFatal
+ fatal.should be_kind_of(Class)
+ fatal.should < Exception
+ end
+
+ specify "rb_eFloatDomainError references the FloatDomainError class" do
+ @s.rb_eFloatDomainError.should == FloatDomainError
+ end
+
+ specify "rb_eFrozenError references the FrozenError class" do
+ @s.rb_eFrozenError.should == FrozenError
+ end
+
+ specify "rb_eIndexError references the IndexError class" do
+ @s.rb_eIndexError.should == IndexError
+ end
+
+ specify "rb_eInterrupt references the Interrupt class" do
+ @s.rb_eInterrupt.should == Interrupt
+ end
+
+ specify "rb_eIOError references the IOError class" do
+ @s.rb_eIOError.should == IOError
+ end
+
+ specify "rb_eKeyError references the KeyError class" do
+ @s.rb_eKeyError.should == KeyError
+ end
+
+ specify "rb_eLoadError references the LoadError class" do
+ @s.rb_eLoadError.should == LoadError
+ end
+
+ specify "rb_eLocalJumpError references the LocalJumpError class" do
+ @s.rb_eLocalJumpError.should == LocalJumpError
+ end
+
+ specify "rb_eMathDomainError references the Math::DomainError class" do
+ @s.rb_eMathDomainError.should == Math::DomainError
+ end
+
+ specify "rb_eNameError references the NameError class" do
+ @s.rb_eNameError.should == NameError
+ end
+
+ specify "rb_eNoMemError references the NoMemoryError class" do
+ @s.rb_eNoMemError.should == NoMemoryError
+ end
+
+ specify "rb_eNoMethodError references the NoMethodError class" do
+ @s.rb_eNoMethodError.should == NoMethodError
+ end
+
+ specify "rb_eNotImpError references the NotImplementedError class" do
+ @s.rb_eNotImpError.should == NotImplementedError
+ end
+
+ specify "rb_eRangeError references the RangeError class" do
+ @s.rb_eRangeError.should == RangeError
+ end
+
+ specify "rb_eRegexpError references the RegexpError class" do
+ @s.rb_eRegexpError.should == RegexpError
+ end
+
+ specify "rb_eRuntimeError references the RuntimeError class" do
+ @s.rb_eRuntimeError.should == RuntimeError
+ end
+
+ specify "rb_eScriptError references the ScriptError class" do
+ @s.rb_eScriptError.should == ScriptError
+ end
+
+ specify "rb_eSecurityError references the SecurityError class" do
+ @s.rb_eSecurityError.should == SecurityError
+ end
+
+ specify "rb_eSignal references the SignalException class" do
+ @s.rb_eSignal.should == SignalException
+ end
+
+ specify "rb_eStandardError references the StandardError class" do
+ @s.rb_eStandardError.should == StandardError
+ end
+
+ specify "rb_eStopIteration references the StopIteration class" do
+ @s.rb_eStopIteration.should == StopIteration
+ end
+
+ specify "rb_eSyntaxError references the SyntaxError class" do
+ @s.rb_eSyntaxError.should == SyntaxError
+ end
+
+ specify "rb_eSystemCallError references the SystemCallError class" do
+ @s.rb_eSystemCallError.should == SystemCallError
+ end
+
+ specify "rb_eSystemExit references the SystemExit class" do
+ @s.rb_eSystemExit.should == SystemExit
+ end
+
+ specify "rb_eSysStackError references the SystemStackError class" do
+ @s.rb_eSysStackError.should == SystemStackError
+ end
+
+ specify "rb_eTypeError references the TypeError class" do
+ @s.rb_eTypeError.should == TypeError
+ end
+
+ specify "rb_eThreadError references the ThreadError class" do
+ @s.rb_eThreadError.should == ThreadError
+ end
+
+ specify "rb_mWaitReadable references the IO::WaitReadable module" do
+ @s.rb_mWaitReadable.should == IO::WaitReadable
+ end
+
+ specify "rb_mWaitWritable references the IO::WaitWritable module" do
+ @s.rb_mWaitWritable.should == IO::WaitWritable
+ end
+
+ specify "rb_eZeroDivError references the ZeroDivisionError class" do
+ @s.rb_eZeroDivError.should == ZeroDivisionError
+ end
+end
diff --git a/spec/ruby/optional/capi/data_spec.rb b/spec/ruby/optional/capi/data_spec.rb
new file mode 100644
index 0000000000..18c769332e
--- /dev/null
+++ b/spec/ruby/optional/capi/data_spec.rb
@@ -0,0 +1,52 @@
+require_relative 'spec_helper'
+
+load_extension("data")
+
+describe "CApiAllocSpecs (a class with an alloc func defined)" do
+ it "calls the alloc func" do
+ @s = CApiAllocSpecs.new
+ @s.wrapped_data.should == 42 # not defined in initialize
+ end
+end
+
+describe "CApiWrappedStruct" do
+ before :each do
+ @s = CApiWrappedStructSpecs.new
+ end
+
+ it "wraps with Data_Wrap_Struct and Data_Get_Struct returns data" do
+ a = @s.wrap_struct(1024)
+ @s.get_struct(a).should == 1024
+ end
+
+ describe "RDATA()" do
+ it "returns the struct data" do
+ a = @s.wrap_struct(1024)
+ @s.get_struct_rdata(a).should == 1024
+ end
+
+ it "allows changing the wrapped struct" do
+ a = @s.wrap_struct(1024)
+ @s.change_struct(a, 100)
+ @s.get_struct(a).should == 100
+ end
+
+ it "raises a TypeError if the object does not wrap a struct" do
+ -> { @s.get_struct(Object.new) }.should raise_error(TypeError)
+ end
+ end
+
+ describe "rb_check_type" do
+ it "does not raise an exception when checking data objects" do
+ a = @s.wrap_struct(1024)
+ @s.rb_check_type(a, a).should == true
+ end
+ end
+
+ describe "DATA_PTR" do
+ it "returns the struct data" do
+ a = @s.wrap_struct(1024)
+ @s.get_struct_data_ptr(a).should == 1024
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/debug_spec.rb b/spec/ruby/optional/capi/debug_spec.rb
new file mode 100644
index 0000000000..c8c91417d1
--- /dev/null
+++ b/spec/ruby/optional/capi/debug_spec.rb
@@ -0,0 +1,66 @@
+require_relative 'spec_helper'
+
+load_extension('debug')
+
+describe "C-API Debug function" do
+ before :each do
+ @o = CApiDebugSpecs.new
+ end
+
+ describe "rb_debug_inspector_open" do
+ it "creates a debug context and calls the given callback" do
+ @o.rb_debug_inspector_open(42).should be_kind_of(Array)
+ @o.debug_spec_callback_data.should == 42
+ end
+ end
+
+ describe "rb_debug_inspector_frame_self_get" do
+ it "returns self" do
+ @o.rb_debug_inspector_frame_self_get(0).should == @o
+ end
+ end
+
+ describe "rb_debug_inspector_frame_class_get" do
+ it "returns the frame class" do
+ @o.rb_debug_inspector_frame_class_get(0).should == CApiDebugSpecs
+ end
+ end
+
+ describe "rb_debug_inspector_frame_binding_get" do
+ it "returns the current binding" do
+ a = "test"
+ b = @o.rb_debug_inspector_frame_binding_get(1)
+ b.should be_an_instance_of(Binding)
+ b.local_variable_get(:a).should == "test"
+ end
+
+ it "matches the locations in rb_debug_inspector_backtrace_locations" do
+ frames = @o.rb_debug_inspector_open(42);
+ frames.each do |_s, _klass, binding, _iseq, backtrace_location|
+ if binding
+ "#{backtrace_location.path}:#{backtrace_location.lineno}".should == "#{binding.source_location[0]}:#{binding.source_location[1]}"
+ end
+ end
+ end
+ end
+
+ describe "rb_debug_inspector_frame_iseq_get" do
+ it "returns an InstructionSequence" do
+ if defined?(RubyVM::InstructionSequence)
+ @o.rb_debug_inspector_frame_iseq_get(1).should be_an_instance_of(RubyVM::InstructionSequence)
+ else
+ @o.rb_debug_inspector_frame_iseq_get(1).should == nil
+ end
+ end
+ end
+
+ describe "rb_debug_inspector_backtrace_locations" do
+ it "returns an array of Thread::Backtrace::Location" do
+ bts = @o.rb_debug_inspector_backtrace_locations
+ bts.should_not.empty?
+ bts.each { |bt| bt.should be_kind_of(Thread::Backtrace::Location) }
+ location = "#{__FILE__}:#{__LINE__ - 3}"
+ bts[1].to_s.should include(location)
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb
new file mode 100644
index 0000000000..aa632b963b
--- /dev/null
+++ b/spec/ruby/optional/capi/encoding_spec.rb
@@ -0,0 +1,694 @@
+# -*- encoding: utf-8 -*-
+require_relative 'spec_helper'
+require_relative 'fixtures/encoding'
+
+load_extension('encoding')
+
+describe :rb_enc_get_index, shared: true do
+ it "returns the index of the encoding of a String" do
+ @s.send(@method, "string").should >= 0
+ end
+
+ it "returns the index of the encoding of a Regexp" do
+ @s.send(@method, /regexp/).should >= 0
+ end
+end
+
+describe :rb_enc_set_index, shared: true do
+ it "sets the object's encoding to the Encoding specified by the index" do
+ obj = "abc"
+ result = @s.send(@method, obj, 2)
+
+ # This is used because indexes should be considered implementation
+ # dependent. So a pair is returned:
+ # [rb_enc_find_index() -> name, rb_enc_get(obj) -> name]
+ result.first.should == result.last
+ end
+
+ it "associates an encoding with a subclass of String" do
+ str = CApiEncodingSpecs::S.new "abc"
+ result = @s.send(@method, str, 1)
+ result.first.should == result.last
+ end
+
+ it "raises an ArgumentError for a non-encoding capable object" do
+ obj = Object.new
+ -> {
+ result = @s.send(@method, obj, 1)
+ }.should raise_error(ArgumentError, "cannot set encoding on non-encoding capable object")
+ end
+end
+
+describe "C-API Encoding function" do
+ @n = 0
+
+ before :each do
+ @s = CApiEncodingSpecs.new
+ end
+
+ describe "rb_enc_alias" do
+ it "creates an alias for an existing Encoding" do
+ name = "ZOMGWTFBBQ#{@n += 1}"
+ @s.rb_enc_alias(name, "UTF-8").should >= 0
+ Encoding.find(name).name.should == "UTF-8"
+ end
+ end
+
+ describe "rb_enc_codelen" do
+ it "returns the correct length for the given codepoint" do
+ @s.rb_enc_codelen(0x24, Encoding::UTF_8).should == 1
+ @s.rb_enc_codelen(0xA2, Encoding::UTF_8).should == 2
+ @s.rb_enc_codelen(0x20AC, Encoding::UTF_8).should == 3
+ @s.rb_enc_codelen(0x24B62, Encoding::UTF_8).should == 4
+ end
+ end
+
+ describe "rb_enc_strlen" do
+ before :each do
+ @str = 'ã“ã«ã¡ã‚' # Each codepoint in this string is 3 bytes in UTF-8
+ end
+
+ it "returns the correct string length for the encoding" do
+ @s.rb_enc_strlen(@str, @str.bytesize, Encoding::UTF_8).should == 4
+ @s.rb_enc_strlen(@str, @str.bytesize, Encoding::BINARY).should == 12
+ end
+
+ it "returns the string length based on a fixed-width encoding's character length, even if the encoding is incompatible" do
+ @s.rb_enc_strlen(@str, @str.bytesize, Encoding::UTF_16BE).should == 6
+ @s.rb_enc_strlen(@str, @str.bytesize, Encoding::UTF_16LE).should == 6
+ @s.rb_enc_strlen(@str, @str.bytesize, Encoding::UTF_32BE).should == 3
+ @s.rb_enc_strlen(@str, @str.bytesize, Encoding::UTF_32LE).should == 3
+ end
+
+ it "does not consider strings to be NUL-terminated" do
+ s = "abc\0def"
+ @s.rb_enc_strlen(s, s.bytesize, Encoding::US_ASCII).should == 7
+ @s.rb_enc_strlen(s, s.bytesize, Encoding::UTF_8).should == 7
+ end
+
+ describe "handles broken strings" do
+ it "combines valid character and invalid character counts in UTF-8" do
+ # The result is 3 because `rb_enc_strlen` counts the first valid character and then adds
+ # the byte count for the invalid character that follows for 1 + 2.
+ @s.rb_enc_strlen(@str, 5, Encoding::UTF_8).should == 3
+ end
+
+ it "combines valid character and invalid character counts in UTF-16" do
+ @s.rb_enc_strlen(@str, 5, Encoding::UTF_16BE).should == 3
+ end
+
+ it "rounds up for fixed-width encodings" do
+ @s.rb_enc_strlen(@str, 7, Encoding::UTF_32BE).should == 2
+ @s.rb_enc_strlen(@str, 7, Encoding::UTF_32LE).should == 2
+ @s.rb_enc_strlen(@str, 5, Encoding::BINARY).should == 5
+ end
+ end
+ end
+
+ describe "rb_enc_find" do
+ it "returns the encoding of an Encoding" do
+ @s.rb_enc_find("UTF-8").should == "UTF-8"
+ end
+
+ it "returns the encoding of an Encoding specified with lower case" do
+ @s.rb_enc_find("utf-8").should == "UTF-8"
+ end
+ end
+
+ describe "rb_enc_find_index" do
+ it "returns the index of an Encoding" do
+ @s.rb_enc_find_index("UTF-8").should >= 0
+ end
+
+ it "returns the index of an Encoding specified with lower case" do
+ @s.rb_enc_find_index("utf-8").should >= 0
+ end
+
+ it "returns -1 for an non existing encoding" do
+ @s.rb_enc_find_index("non-existent-encoding").should == -1
+ end
+ end
+
+ describe "rb_enc_isalnum" do
+ it "returns non-zero for alpha-numeric characters" do
+ @s.rb_enc_isalnum("a".ord, Encoding::US_ASCII).should == true
+ @s.rb_enc_isalnum("2".ord, Encoding::US_ASCII).should == true
+ @s.rb_enc_isalnum("a".ord, Encoding::UTF_8).should == true
+ @s.rb_enc_isalnum("2".ord, Encoding::UTF_8).should == true
+ @s.rb_enc_isalnum("é".encode(Encoding::ISO_8859_1).ord, Encoding::ISO_8859_1).should == true
+ end
+
+ it "returns zero for non alpha-numeric characters" do
+ @s.rb_enc_isalnum("-".ord, Encoding::US_ASCII).should == false
+ @s.rb_enc_isalnum(" ".ord, Encoding::US_ASCII).should == false
+ @s.rb_enc_isalnum("-".ord, Encoding::UTF_8).should == false
+ @s.rb_enc_isalnum(" ".ord, Encoding::UTF_8).should == false
+ end
+ end
+
+ describe "rb_enc_isspace" do
+ it "returns non-zero for space characters" do
+ @s.rb_enc_isspace(" ".ord, Encoding::US_ASCII).should == true
+ @s.rb_enc_isspace(" ".ord, Encoding::UTF_8).should == true
+ end
+
+ it "returns zero for non space characters" do
+ @s.rb_enc_isspace("-".ord, Encoding::US_ASCII).should == false
+ @s.rb_enc_isspace("A".ord, Encoding::US_ASCII).should == false
+ @s.rb_enc_isspace("3".ord, Encoding::US_ASCII).should == false
+ @s.rb_enc_isspace("-".ord, Encoding::UTF_8).should == false
+ @s.rb_enc_isspace("A".ord, Encoding::UTF_8).should == false
+ @s.rb_enc_isspace("3".ord, Encoding::UTF_8).should == false
+ end
+ end
+
+ describe "rb_enc_from_index" do
+ it "returns an Encoding" do
+ @s.rb_enc_from_index(0).should be_an_instance_of(String)
+ end
+ end
+
+ describe "rb_enc_mbc_to_codepoint" do
+ it "returns the correct codepoint for the given character and size" do
+ @s.rb_enc_mbc_to_codepoint("é").should == 0xE9
+ end
+
+ it "returns 0 if p == e" do
+ @s.rb_enc_mbc_to_codepoint("").should == 0
+ end
+
+ it "returns the raw byte if incomplete character in UTF-8" do
+ @s.rb_enc_mbc_to_codepoint("\xC3").should == 0xC3
+ @s.rb_enc_mbc_to_codepoint("\x80").should == 0x80
+ end
+ end
+
+ describe "rb_enc_mbcput" do
+ it "writes the correct bytes to the buffer" do
+ @s.rb_enc_mbcput(0x24, Encoding::UTF_8).should == "$"
+ @s.rb_enc_mbcput(0xA2, Encoding::UTF_8).should == "¢"
+ @s.rb_enc_mbcput(0x20AC, Encoding::UTF_8).should == "€"
+ @s.rb_enc_mbcput(0x24B62, Encoding::UTF_8).should == "𤭢"
+
+ @s.rb_enc_mbcput(0x24, Encoding::UTF_16BE).bytes.should == [0, 0x24]
+ @s.rb_enc_mbcput(0x24B62, Encoding::UTF_16LE).bytes.should == [82, 216, 98, 223]
+ end
+ end
+
+ describe "rb_usascii_encoding" do
+ it "returns the encoding for Encoding::US_ASCII" do
+ @s.rb_usascii_encoding.should == "US-ASCII"
+ end
+ end
+
+ describe "rb_ascii8bit_encoding" do
+ it "returns the encoding for Encoding::BINARY" do
+ @s.rb_ascii8bit_encoding.should == "ASCII-8BIT"
+ end
+ end
+
+ describe "rb_utf8_encoding" do
+ it "returns the encoding for Encoding::UTF_8" do
+ @s.rb_utf8_encoding.should == "UTF-8"
+ end
+ end
+
+ describe "rb_enc_from_encoding" do
+ it "returns an Encoding instance from an encoding data structure" do
+ @s.rb_enc_from_encoding("UTF-8").should == Encoding::UTF_8
+ end
+ end
+
+ describe "rb_locale_encoding" do
+ it "returns the encoding for the current locale" do
+ @s.rb_locale_encoding.should == Encoding.find('locale').name
+ end
+ end
+
+ describe "rb_filesystem_encoding" do
+ it "returns the encoding for the current filesystem" do
+ @s.rb_filesystem_encoding.should == Encoding.find('filesystem').name
+ end
+ end
+
+ describe "rb_enc_get" do
+ it "returns the encoding associated with an object" do
+ str = "abc".encode Encoding::BINARY
+ @s.rb_enc_get(str).should == "ASCII-8BIT"
+ end
+ end
+
+ describe "rb_enc_precise_mbclen" do
+ it "returns the correct length for single byte characters" do
+ @s.rb_enc_precise_mbclen("hello", 7).should == 1
+ @s.rb_enc_precise_mbclen("hello", 5).should == 1
+ @s.rb_enc_precise_mbclen("hello", 1).should == 1
+ @s.rb_enc_precise_mbclen("hello", 0).should == -2
+ @s.rb_enc_precise_mbclen("hello", -1).should == -2
+ @s.rb_enc_precise_mbclen("hello", -5).should == -2
+ end
+
+ it "returns the correct length for multi-byte characters" do
+ @s.rb_enc_precise_mbclen("ésumé", 2).should == 2
+ @s.rb_enc_precise_mbclen("ésumé", 3).should == 2
+ @s.rb_enc_precise_mbclen("ésumé", 0).should == -2
+ @s.rb_enc_precise_mbclen("ésumé", 1).should == -2
+ @s.rb_enc_precise_mbclen("ã‚", 20).should == 3
+ @s.rb_enc_precise_mbclen("ã‚", 3).should == 3
+ @s.rb_enc_precise_mbclen("ã‚", 2).should == -2
+ @s.rb_enc_precise_mbclen("ã‚", 0).should == -2
+ @s.rb_enc_precise_mbclen("ã‚", -2).should == -2
+ end
+ end
+
+ describe "rb_obj_encoding" do
+ it "returns the encoding associated with an object" do
+ str = "abc".encode Encoding::BINARY
+ @s.rb_obj_encoding(str).should == Encoding::BINARY
+ end
+ end
+
+ describe "rb_enc_get_index" do
+ it_behaves_like :rb_enc_get_index, :rb_enc_get_index
+
+ it "returns the index of the encoding of a Symbol" do
+ @s.rb_enc_get_index(:symbol).should >= 0
+ end
+
+ it "returns -1 as the index of nil" do
+ @s.rb_enc_get_index(nil).should == -1
+ end
+
+ it "returns -1 as the index for immediates" do
+ @s.rb_enc_get_index(1).should == -1
+ end
+
+ it "returns -1 for an object without an encoding" do
+ obj = Object.new
+ @s.rb_enc_get_index(obj).should == -1
+ end
+ end
+
+ describe "rb_enc_set_index" do
+ it_behaves_like :rb_enc_set_index, :rb_enc_set_index
+ end
+
+ describe "rb_enc_str_new" do
+ it "returns a String in US-ASCII encoding when high bits are set" do
+ xEE = [0xEE].pack('C').force_encoding('utf-8')
+ result = @s.rb_enc_str_new(xEE, 1, Encoding::US_ASCII)
+ result.encoding.should equal(Encoding::US_ASCII)
+ end
+ end
+
+ describe "rb_enc_str_new_cstr" do
+ it "creates a new ruby string from a c string literal" do
+ result = @s.rb_enc_str_new_cstr_constant(Encoding::US_ASCII)
+ result.should == "test string literal"
+ result.encoding.should == Encoding::US_ASCII
+ end
+
+ it "creates a new ruby string from a c string variable" do
+ result = @s.rb_enc_str_new_cstr("test string", Encoding::US_ASCII)
+ result.should == "test string"
+ result.encoding.should == Encoding::US_ASCII
+ end
+
+ it "when null encoding is given with a c string literal, it creates a new ruby string with ASCII_8BIT encoding" do
+ result = @s.rb_enc_str_new_cstr_constant(nil)
+ result.should == "test string literal"
+ result.encoding.should == Encoding::ASCII_8BIT
+ end
+ end
+
+ describe "rb_enc_str_coderange" do
+ describe "when the encoding is BINARY" do
+ it "returns ENC_CODERANGE_7BIT if there are no high bits set" do
+ result = @s.rb_enc_str_coderange("abc".force_encoding("binary"))
+ result.should == :coderange_7bit
+ end
+
+ it "returns ENC_CODERANGE_VALID if there are high bits set" do
+ xEE = [0xEE].pack('C').force_encoding('utf-8')
+ result = @s.rb_enc_str_coderange(xEE.force_encoding("binary"))
+ result.should == :coderange_valid
+ end
+ end
+
+ describe "when the encoding is UTF-8" do
+ it "returns ENC_CODERANGE_7BIT if there are no high bits set" do
+ result = @s.rb_enc_str_coderange("abc".force_encoding("utf-8"))
+ result.should == :coderange_7bit
+ end
+
+ it "returns ENC_CODERANGE_VALID if there are high bits set in a valid string" do
+ result = @s.rb_enc_str_coderange("\xE3\x81\x82".force_encoding("utf-8"))
+ result.should == :coderange_valid
+ end
+
+ it "returns ENC_CODERANGE_BROKEN if there are high bits set in an invalid string" do
+ result = @s.rb_enc_str_coderange([0xEE].pack('C').force_encoding("utf-8"))
+ result.should == :coderange_broken
+ end
+ end
+
+ describe "when the encoding is US-ASCII" do
+ it "returns ENC_CODERANGE_7BIT if there are no high bits set" do
+ result = @s.rb_enc_str_coderange("abc".force_encoding("us-ascii"))
+ result.should == :coderange_7bit
+ end
+
+ it "returns ENC_CODERANGE_BROKEN if there are high bits set" do
+ result = @s.rb_enc_str_coderange([0xEE].pack('C').force_encoding("us-ascii"))
+ result.should == :coderange_broken
+ end
+ end
+ end
+
+ describe "MBCLEN_CHARFOUND_P" do
+ it "returns non-zero for valid character" do
+ @s.MBCLEN_CHARFOUND_P("a".ord).should == 1
+ end
+
+ it "returns zero for invalid characters" do
+ @s.MBCLEN_CHARFOUND_P(0).should == 0
+ @s.MBCLEN_CHARFOUND_P(-1).should == 0
+ end
+ end
+
+ describe "ENCODING_GET" do
+ it_behaves_like :rb_enc_get_index, :ENCODING_GET
+ end
+
+ describe "ENCODING_SET" do
+ it_behaves_like :rb_enc_set_index, :ENCODING_SET
+ end
+
+ describe "ENC_CODERANGE_ASCIIONLY" do
+ it "returns true if the object encoding is only ASCII" do
+ str = "abc".force_encoding("us-ascii")
+ str.valid_encoding? # make sure to set the coderange
+ @s.ENC_CODERANGE_ASCIIONLY(str).should be_true
+ end
+
+ it "returns false if the object encoding is not ASCII only" do
+ str = "ã‚りãŒã¨ã†".force_encoding("utf-8")
+ @s.ENC_CODERANGE_ASCIIONLY(str).should be_false
+ end
+ end
+
+ describe "rb_to_encoding" do
+ it "returns the encoding for the Encoding instance passed" do
+ @s.rb_to_encoding(Encoding::BINARY).should == "ASCII-8BIT"
+ end
+
+ it "returns the correct encoding for a replicated encoding" do
+ @s.rb_to_encoding(Encoding::IBM857).should == "IBM857"
+ end
+
+ it "returns the encoding when passed a String" do
+ @s.rb_to_encoding("ASCII").should == "US-ASCII"
+ end
+
+ it "calls #to_str to convert the argument to a String" do
+ obj = mock("rb_to_encoding Encoding name")
+ obj.should_receive(:to_str).and_return("utf-8")
+
+ @s.rb_to_encoding(obj).should == "UTF-8"
+ end
+
+ describe "when the rb_encoding struct is stored in native memory" do
+ it "can still read the name of the encoding" do
+ address = @s.rb_to_encoding_native_store(Encoding::UTF_8)
+ address.should be_kind_of(Integer)
+ @s.rb_to_encoding_native_name(address).should == "UTF-8"
+ end
+ end
+ end
+
+ describe "rb_to_encoding_index" do
+ it "returns the index of the encoding for the Encoding instance passed" do
+ @s.rb_to_encoding_index(Encoding::BINARY).should >= 0
+ end
+
+ it "returns the index of the encoding when passed a String" do
+ @s.rb_to_encoding_index("ASCII").should >= 0
+ end
+
+ it "returns the index of the dummy encoding of an Object" do
+ index = Encoding.list.index(Encoding::UTF_16)
+ @s.rb_to_encoding_index(Encoding::UTF_16.name).should == index
+ end
+
+ it "calls #to_str to convert the argument to a String" do
+ obj = mock("rb_to_encoding Encoding name")
+ obj.should_receive(:to_str).and_return("utf-8")
+
+ @s.rb_to_encoding_index(obj).should >= 0
+ end
+ end
+
+ describe "rb_enc_compatible" do
+ it "returns 0 if the encodings of the Strings are not compatible" do
+ a = [0xff].pack('C').force_encoding "binary"
+ b = "\u3042".encode("utf-8")
+ @s.rb_enc_compatible(a, b).should == 0
+ end
+
+ # The coverage of this sucks, but there is not a simple way (yet?) to
+ # easily share the specs between rb_enc_compatible and
+ # Encoding.compatible?
+ it "returns the same value as Encoding.compatible? if the Strings have a compatible encoding" do
+ a = "abc".force_encoding("us-ascii")
+ b = "\u3042".encode("utf-8")
+ @s.rb_enc_compatible(a, b).should == Encoding.compatible?(a, b)
+ end
+ end
+
+ describe "rb_enc_copy" do
+ before :each do
+ @obj = "rb_enc_copy".encode(Encoding::US_ASCII)
+ end
+
+ it "sets the encoding of a String to that of the second argument" do
+ @s.rb_enc_copy("string", @obj).encoding.should == Encoding::US_ASCII
+ end
+
+ it "raises a RuntimeError if the second argument is a Symbol" do
+ -> { @s.rb_enc_copy(:symbol, @obj) }.should raise_error(RuntimeError)
+ end
+
+ it "sets the encoding of a Regexp to that of the second argument" do
+ @s.rb_enc_copy(/regexp/.dup, @obj).encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ describe "rb_default_internal_encoding" do
+ before :each do
+ @default = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @default
+ end
+
+ it "returns 0 if Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ @s.rb_default_internal_encoding.should be_nil
+ end
+
+ it "returns the encoding for Encoding.default_internal" do
+ Encoding.default_internal = "US-ASCII"
+ @s.rb_default_internal_encoding.should == "US-ASCII"
+ Encoding.default_internal = "UTF-8"
+ @s.rb_default_internal_encoding.should == "UTF-8"
+ end
+ end
+
+ describe "rb_default_external_encoding" do
+ before :each do
+ @default = Encoding.default_external
+ end
+
+ after :each do
+ Encoding.default_external = @default
+ end
+
+ it "returns the encoding for Encoding.default_external" do
+ Encoding.default_external = "ASCII-8BIT"
+ @s.rb_default_external_encoding.should == "ASCII-8BIT"
+ end
+ end
+
+ describe "rb_enc_associate" do
+ it "sets the encoding of a String to the encoding" do
+ @s.rb_enc_associate("string", "BINARY").encoding.should == Encoding::BINARY
+ end
+
+ it "raises a RuntimeError if the argument is Symbol" do
+ -> { @s.rb_enc_associate(:symbol, "US-ASCII") }.should raise_error(RuntimeError)
+ end
+
+ it "sets the encoding of a Regexp to the encoding" do
+ @s.rb_enc_associate(/regexp/.dup, "BINARY").encoding.should == Encoding::BINARY
+ end
+
+ it "sets the encoding of a String to a default when the encoding is NULL" do
+ @s.rb_enc_associate("string", nil).encoding.should == Encoding::BINARY
+ end
+ end
+
+ describe "rb_enc_associate_index" do
+ it "sets the encoding of a String to the encoding" do
+ index = @s.rb_enc_find_index("BINARY")
+ enc = @s.rb_enc_associate_index("string", index).encoding
+ enc.should == Encoding::BINARY
+ end
+
+ it "sets the encoding of a Regexp to the encoding" do
+ index = @s.rb_enc_find_index("UTF-8")
+ enc = @s.rb_enc_associate_index(/regexp/.dup, index).encoding
+ enc.should == Encoding::UTF_8
+ end
+
+ it "sets the encoding of a Symbol to the encoding" do
+ index = @s.rb_enc_find_index("UTF-8")
+ -> { @s.rb_enc_associate_index(:symbol, index) }.should raise_error(RuntimeError)
+ end
+ end
+
+ describe "rb_ascii8bit_encindex" do
+ it "returns an index for the ASCII-8BIT encoding" do
+ @s.rb_ascii8bit_encindex().should >= 0
+ end
+ end
+
+ describe "rb_utf8_encindex" do
+ it "returns an index for the UTF-8 encoding" do
+ @s.rb_utf8_encindex().should >= 0
+ end
+ end
+
+ describe "rb_usascii_encindex" do
+ it "returns an index for the US-ASCII encoding" do
+ @s.rb_usascii_encindex().should >= 0
+ end
+ end
+
+ describe "rb_locale_encindex" do
+ it "returns an index for the locale encoding" do
+ @s.rb_locale_encindex().should >= 0
+ end
+ end
+
+ describe "rb_filesystem_encindex" do
+ it "returns an index for the filesystem encoding" do
+ @s.rb_filesystem_encindex().should >= 0
+ end
+ end
+
+ describe "rb_enc_to_index" do
+ it "returns an index for the encoding" do
+ @s.rb_enc_to_index("UTF-8").should >= 0
+ end
+
+ it "returns a non-negative int if the encoding is not defined" do
+ # Encoding indexes are an implementation detail and not guaranteed
+ # across implementations.
+ @s.rb_enc_to_index("FTU-81").should >= 0
+ end
+ end
+
+ describe "rb_enc_nth" do
+ it "returns the byte index of the given character index" do
+ @s.rb_enc_nth("hüllo", 3).should == 4
+ end
+ end
+
+ describe "rb_enc_codepoint_len" do
+ it "raises ArgumentError if an empty string is given" do
+ -> do
+ @s.rb_enc_codepoint_len("")
+ end.should raise_error(ArgumentError)
+ end
+
+ it "raises ArgumentError if an invalid byte sequence is given" do
+ -> do
+ @s.rb_enc_codepoint_len([0xa0, 0xa1].pack('CC').force_encoding('utf-8')) # Invalid sequence identifier
+ end.should raise_error(ArgumentError)
+ end
+
+ it "returns codepoint 0x24 and length 1 for character '$'" do
+ codepoint, length = @s.rb_enc_codepoint_len("$")
+
+ codepoint.should == 0x24
+ length.should == 1
+ end
+
+ it "returns codepoint 0xA2 and length 2 for character '¢'" do
+ codepoint, length = @s.rb_enc_codepoint_len("¢")
+
+ codepoint.should == 0xA2
+ length.should == 2
+ end
+
+ it "returns codepoint 0x20AC and length 3 for character '€'" do
+ codepoint, length = @s.rb_enc_codepoint_len("€")
+
+ codepoint.should == 0x20AC
+ length.should == 3
+ end
+
+ it "returns codepoint 0x24B62 and length 4 for character '𤭢'" do
+ codepoint, length = @s.rb_enc_codepoint_len("𤭢")
+
+ codepoint.should == 0x24B62
+ length.should == 4
+ end
+ end
+
+ describe "rb_enc_str_asciionly_p" do
+ it "returns true for an ASCII string" do
+ @s.rb_enc_str_asciionly_p("hello").should be_true
+ end
+
+ it "returns false for a non-ASCII string" do
+ @s.rb_enc_str_asciionly_p("hüllo").should be_false
+ end
+ end
+
+ describe "rb_uv_to_utf8" do
+ it 'converts a Unicode codepoint to a UTF-8 C string' do
+ str = ' ' * 6
+ {
+ 1 => "\x01",
+ 0x80 => "\xC2\x80",
+ 0x800 => "\xE0\xA0\x80",
+ 0x10000 => "\xF0\x90\x80\x80",
+ 0x200000 => "\xF8\x88\x80\x80\x80",
+ 0x4000000 => "\xFC\x84\x80\x80\x80\x80",
+ }.each do |num, result|
+ len = @s.rb_uv_to_utf8(str, num)
+ str.byteslice(0, len).should == result
+ end
+ end
+ end
+
+ describe "ONIGENC_MBC_CASE_FOLD" do
+ it "returns the correct case fold for the given string" do
+ @s.ONIGENC_MBC_CASE_FOLD("lower").should == ["l", 1]
+ @s.ONIGENC_MBC_CASE_FOLD("Upper").should == ["u", 1]
+ @s.ONIGENC_MBC_CASE_FOLD("ABC"[1..-1]).should == ["b", 1]
+ end
+
+ it "works with other encodings" do
+ @s.ONIGENC_MBC_CASE_FOLD("lower".force_encoding("binary")).should == ["l", 1]
+ @s.ONIGENC_MBC_CASE_FOLD("Upper".force_encoding("binary")).should == ["u", 1]
+ @s.ONIGENC_MBC_CASE_FOLD("É").should == ["é", 2]
+
+ str, length = @s.ONIGENC_MBC_CASE_FOLD('$'.encode(Encoding::UTF_16BE))
+ length.should == 2
+ str.bytes.should == [0, 0x24]
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/enumerator_spec.rb b/spec/ruby/optional/capi/enumerator_spec.rb
new file mode 100644
index 0000000000..9ed68c9063
--- /dev/null
+++ b/spec/ruby/optional/capi/enumerator_spec.rb
@@ -0,0 +1,66 @@
+require_relative 'spec_helper'
+
+load_extension("enumerator")
+
+describe "C-API Enumerator function" do
+ before :each do
+ @s = CApiEnumeratorSpecs.new
+ end
+
+ describe "rb_enumeratorize" do
+ before do
+ @enumerable = [1, 2, 3]
+ end
+
+ it "constructs a new Enumerator for the given object, method and arguments" do
+ enumerator = @s.rb_enumeratorize(@enumerable, :each, :arg1, :arg2)
+ enumerator.class.should == Enumerator
+ end
+
+ it "enumerates the given object" do
+ enumerator = @s.rb_enumeratorize(@enumerable, :each)
+ enumerated = []
+ enumerator.each { |i| enumerated << i }
+ enumerated.should == @enumerable
+ end
+
+ it "uses the given method for enumeration" do
+ enumerator = @s.rb_enumeratorize(@enumerable, :awesome_each)
+ @enumerable.should_receive(:awesome_each)
+ enumerator.each {}
+ end
+
+ it "passes the given arguments to the enumeration method" do
+ enumerator = @s.rb_enumeratorize(@enumerable, :each, :arg1, :arg2)
+ @enumerable.should_receive(:each).with(:arg1, :arg2)
+ enumerator.each {}
+ end
+ end
+
+ describe "rb_enumeratorize_with_size" do
+
+ it "enumerates the given object" do
+ enumerator = @s.rb_enumeratorize_with_size(@enumerable, :each)
+ enumerated = []
+ enumerator.each { |i| enumerated << i }
+ enumerated.should == @enumerable
+ end
+
+ it "uses the given method for enumeration" do
+ enumerator = @s.rb_enumeratorize_with_size(@enumerable, :awesome_each)
+ @enumerable.should_receive(:awesome_each)
+ enumerator.each {}
+ end
+
+ it "passes the given arguments to the enumeration method" do
+ enumerator = @s.rb_enumeratorize_with_size(@enumerable, :each, :arg1, :arg2)
+ @enumerable.should_receive(:each).with(:arg1, :arg2)
+ enumerator.each {}
+ end
+
+ it "uses the size function to report the size" do
+ enumerator = @s.rb_enumeratorize_with_size(@enumerable, :each, :arg1, :arg2)
+ enumerator.size.should == 7
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/exception_spec.rb b/spec/ruby/optional/capi/exception_spec.rb
new file mode 100644
index 0000000000..b0a8a2860e
--- /dev/null
+++ b/spec/ruby/optional/capi/exception_spec.rb
@@ -0,0 +1,147 @@
+require_relative 'spec_helper'
+
+load_extension("exception")
+
+describe "C-API Exception function" do
+ before :each do
+ @s = CApiExceptionSpecs.new
+ end
+
+ describe "rb_exc_new" do
+ it "creates an exception from a C string and length" do
+ @s.rb_exc_new('foo').to_s.should == 'foo'
+ end
+ end
+
+ describe "rb_exc_new2" do
+ it "creates an exception from a C string" do
+ @s.rb_exc_new2('foo').to_s.should == 'foo'
+ end
+ end
+
+ describe "rb_exc_new3" do
+ it "creates an exception from a Ruby string" do
+ @s.rb_exc_new3('foo').to_s.should == 'foo'
+ end
+ end
+
+ describe "rb_exc_raise" do
+ it "raises passed exception" do
+ runtime_error = RuntimeError.new '42'
+ -> { @s.rb_exc_raise(runtime_error) }.should raise_error(RuntimeError, '42')
+ end
+
+ it "raises an exception with an empty backtrace" do
+ runtime_error = RuntimeError.new '42'
+ runtime_error.set_backtrace []
+ -> { @s.rb_exc_raise(runtime_error) }.should raise_error(RuntimeError, '42')
+ end
+
+ it "sets $! to the raised exception when not rescuing from an another exception" do
+ runtime_error = RuntimeError.new '42'
+ runtime_error.set_backtrace []
+ begin
+ @s.rb_exc_raise(runtime_error)
+ rescue
+ $!.should == runtime_error
+ end
+ end
+
+ it "sets $! to the raised exception when $! when rescuing from an another exception" do
+ runtime_error = RuntimeError.new '42'
+ runtime_error.set_backtrace []
+ begin
+ begin
+ raise StandardError
+ rescue
+ @s.rb_exc_raise(runtime_error)
+ end
+ rescue
+ $!.should == runtime_error
+ end
+ end
+ end
+
+ describe "rb_errinfo" do
+ it "is cleared when entering a C method" do
+ begin
+ raise StandardError
+ rescue
+ $!.class.should == StandardError
+ @s.rb_errinfo().should == nil
+ end
+ end
+
+ it "does not clear $! in the calling method" do
+ begin
+ raise StandardError
+ rescue
+ @s.rb_errinfo()
+ $!.class.should == StandardError
+ end
+ end
+ end
+
+ describe "rb_set_errinfo" do
+ after :each do
+ @s.rb_set_errinfo(nil)
+ end
+
+ it "accepts nil" do
+ @s.rb_set_errinfo(nil).should be_nil
+ end
+
+ it "accepts an Exception instance" do
+ @s.rb_set_errinfo(Exception.new).should be_nil
+ end
+
+ it "raises a TypeError if the object is not nil or an Exception instance" do
+ -> { @s.rb_set_errinfo("error") }.should raise_error(TypeError)
+ end
+ end
+
+ describe "rb_make_exception" do
+ it "returns a RuntimeError when given a String argument" do
+ e = @s.rb_make_exception(["Message"])
+ e.class.should == RuntimeError
+ e.message.should == "Message"
+ end
+
+ it "returns the exception when given an Exception argument" do
+ exc = Exception.new
+ e = @s.rb_make_exception([exc])
+ e.should == exc
+ end
+
+ it "returns the exception with the given class and message" do
+ e = @s.rb_make_exception([Exception, "Message"])
+ e.class.should == Exception
+ e.message.should == "Message"
+ end
+
+ it "returns the exception with the given class, message, and backtrace" do
+ e = @s.rb_make_exception([Exception, "Message", ["backtrace 1"]])
+ e.class.should == Exception
+ e.message.should == "Message"
+ e.backtrace.should == ["backtrace 1"]
+ end
+
+ it "raises a TypeError for incorrect types" do
+ -> { @s.rb_make_exception([nil]) }.should raise_error(TypeError)
+ -> { @s.rb_make_exception([Object.new]) }.should raise_error(TypeError)
+ obj = Object.new
+ def obj.exception
+ "not exception type"
+ end
+ -> { @s.rb_make_exception([obj]) }.should raise_error(TypeError)
+ end
+
+ it "raises an ArgumentError for too many arguments" do
+ -> { @s.rb_make_exception([Exception, "Message", ["backtrace 1"], "extra"]) }.should raise_error(ArgumentError)
+ end
+
+ it "returns nil for empty arguments" do
+ @s.rb_make_exception([]).should == nil
+ end
+ end
+end
diff --git a/spec/ruby/optional/capi/ext/.gitignore b/spec/ruby/optional/capi/ext/.gitignore
new file mode 100644
index 0000000000..577d117bb1
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/.gitignore
@@ -0,0 +1,9 @@
+# signature of implementation that
+# last compiled an extension
+*.sig
+
+# build artifacts
+*.o
+*.so
+*.bundle
+*.dll
diff --git a/spec/ruby/optional/capi/ext/array_spec.c b/spec/ruby/optional/capi/ext/array_spec.c
new file mode 100644
index 0000000000..9386239813
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/array_spec.c
@@ -0,0 +1,297 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE array_spec_rb_Array(VALUE self, VALUE object) {
+ return rb_Array(object);
+}
+
+static VALUE array_spec_RARRAY_PTR_iterate(VALUE self, VALUE array) {
+ int i;
+ VALUE* ptr;
+
+ ptr = RARRAY_PTR(array);
+ for(i = 0; i < RARRAY_LEN(array); i++) {
+ rb_yield(ptr[i]);
+ }
+ return Qnil;
+}
+
+static VALUE array_spec_RARRAY_PTR_assign(VALUE self, VALUE array, VALUE value) {
+ int i;
+ VALUE* ptr;
+
+ ptr = RARRAY_PTR(array);
+ for(i = 0; i < RARRAY_LEN(array); i++) {
+ ptr[i] = value;
+ }
+ return Qnil;
+}
+
+
+static VALUE array_spec_RARRAY_PTR_memcpy(VALUE self, VALUE array1, VALUE array2) {
+ VALUE *ptr1, *ptr2;
+ long size;
+ size = RARRAY_LEN(array1);
+ ptr1 = RARRAY_PTR(array1);
+ ptr2 = RARRAY_PTR(array2);
+ if (ptr1 != NULL && ptr2 != NULL) {
+ memcpy(ptr2, ptr1, size * sizeof(VALUE));
+ }
+ return Qnil;
+}
+
+static VALUE array_spec_RARRAY_LEN(VALUE self, VALUE array) {
+ return INT2FIX(RARRAY_LEN(array));
+}
+
+static VALUE array_spec_RARRAY_AREF(VALUE self, VALUE array, VALUE index) {
+ return RARRAY_AREF(array, FIX2INT(index));
+}
+
+static VALUE array_spec_RARRAY_ASET(VALUE self, VALUE array, VALUE index, VALUE value) {
+ RARRAY_ASET(array, FIX2INT(index), value);
+ return value;
+}
+
+static VALUE array_spec_rb_ary_aref(int argc, VALUE *argv, VALUE self) {
+ VALUE ary, args;
+ rb_scan_args(argc, argv, "1*", &ary, &args);
+ return rb_ary_aref((int)RARRAY_LEN(args), RARRAY_PTR(args), ary);
+}
+
+static VALUE array_spec_rb_ary_clear(VALUE self, VALUE array) {
+ return rb_ary_clear(array);
+}
+
+static VALUE array_spec_rb_ary_delete(VALUE self, VALUE array, VALUE item) {
+ return rb_ary_delete(array, item);
+}
+
+static VALUE array_spec_rb_ary_delete_at(VALUE self, VALUE array, VALUE index) {
+ return rb_ary_delete_at(array, NUM2LONG(index));
+}
+
+static VALUE array_spec_rb_ary_dup(VALUE self, VALUE array) {
+ return rb_ary_dup(array);
+}
+
+static VALUE array_spec_rb_ary_entry(VALUE self, VALUE array, VALUE offset) {
+ return rb_ary_entry(array, FIX2INT(offset));
+}
+
+static VALUE array_spec_rb_ary_includes(VALUE self, VALUE ary, VALUE item) {
+ return rb_ary_includes(ary, item);
+}
+
+static VALUE array_spec_rb_ary_join(VALUE self, VALUE array1, VALUE array2) {
+ return rb_ary_join(array1, array2);
+}
+
+static VALUE array_spec_rb_ary_to_s(VALUE self, VALUE array) {
+ return rb_ary_to_s(array);
+}
+
+static VALUE array_spec_rb_ary_new(VALUE self) {
+ VALUE ret;
+ ret = rb_ary_new();
+ return ret;
+}
+
+static VALUE array_spec_rb_ary_new2(VALUE self, VALUE length) {
+ return rb_ary_new2(NUM2LONG(length));
+}
+
+static VALUE array_spec_rb_ary_new_capa(VALUE self, VALUE length) {
+ return rb_ary_new_capa(NUM2LONG(length));
+}
+
+static VALUE array_spec_rb_ary_new3(VALUE self, VALUE first, VALUE second, VALUE third) {
+ return rb_ary_new3(3, first, second, third);
+}
+
+static VALUE array_spec_rb_ary_new_from_args(VALUE self, VALUE first, VALUE second, VALUE third) {
+ return rb_ary_new_from_args(3, first, second, third);
+}
+
+static VALUE array_spec_rb_ary_new4(VALUE self, VALUE first, VALUE second, VALUE third) {
+ VALUE values[3];
+ values[0] = first;
+ values[1] = second;
+ values[2] = third;
+ return rb_ary_new4(3, values);
+}
+
+static VALUE array_spec_rb_ary_new_from_values(VALUE self, VALUE first, VALUE second, VALUE third) {
+ VALUE values[3];
+ values[0] = first;
+ values[1] = second;
+ values[2] = third;
+ return rb_ary_new_from_values(3, values);
+}
+
+static VALUE array_spec_rb_ary_pop(VALUE self, VALUE array) {
+ return rb_ary_pop(array);
+}
+
+static VALUE array_spec_rb_ary_push(VALUE self, VALUE array, VALUE item) {
+ rb_ary_push(array, item);
+ return array;
+}
+
+static VALUE array_spec_rb_ary_cat(int argc, VALUE *argv, VALUE self) {
+ VALUE ary, args;
+ rb_scan_args(argc, argv, "1*", &ary, &args);
+ return rb_ary_cat(ary, RARRAY_PTR(args), RARRAY_LEN(args));
+}
+
+static VALUE array_spec_rb_ary_reverse(VALUE self, VALUE array) {
+ return rb_ary_reverse(array);
+}
+
+static VALUE array_spec_rb_ary_rotate(VALUE self, VALUE array, VALUE count) {
+ return rb_ary_rotate(array, NUM2LONG(count));
+}
+
+static VALUE array_spec_rb_ary_shift(VALUE self, VALUE array) {
+ return rb_ary_shift(array);
+}
+
+static VALUE array_spec_rb_ary_sort(VALUE self, VALUE array) {
+ return rb_ary_sort(array);
+}
+
+static VALUE array_spec_rb_ary_sort_bang(VALUE self, VALUE array) {
+ return rb_ary_sort_bang(array);
+}
+
+static VALUE array_spec_rb_ary_store(VALUE self, VALUE array, VALUE offset, VALUE value) {
+ rb_ary_store(array, FIX2INT(offset), value);
+
+ return Qnil;
+}
+
+static VALUE array_spec_rb_ary_concat(VALUE self, VALUE array1, VALUE array2) {
+ return rb_ary_concat(array1, array2);
+}
+
+static VALUE array_spec_rb_ary_plus(VALUE self, VALUE array1, VALUE array2) {
+ return rb_ary_plus(array1, array2);
+}
+
+static VALUE array_spec_rb_ary_unshift(VALUE self, VALUE array, VALUE val) {
+ return rb_ary_unshift(array, val);
+}
+
+static VALUE array_spec_rb_assoc_new(VALUE self, VALUE first, VALUE second) {
+ return rb_assoc_new(first, second);
+}
+
+static VALUE copy_ary(RB_BLOCK_CALL_FUNC_ARGLIST(el, new_ary)) {
+ return rb_ary_push(new_ary, el);
+}
+
+static VALUE array_spec_rb_block_call(VALUE self, VALUE ary) {
+ VALUE new_ary = rb_ary_new();
+
+ rb_block_call(ary, rb_intern("each"), 0, 0, copy_ary, new_ary);
+
+ return new_ary;
+}
+
+static VALUE sub_pair(RB_BLOCK_CALL_FUNC_ARGLIST(el, holder)) {
+ return rb_ary_push(holder, rb_ary_entry(el, 1));
+}
+
+static VALUE array_spec_rb_block_call_each_pair(VALUE self, VALUE obj) {
+ VALUE new_ary = rb_ary_new();
+
+ rb_block_call(obj, rb_intern("each_pair"), 0, 0, sub_pair, new_ary);
+
+ return new_ary;
+}
+
+static VALUE iter_yield(RB_BLOCK_CALL_FUNC_ARGLIST(el, ary)) {
+ rb_yield(el);
+ return Qnil;
+}
+
+static VALUE array_spec_rb_block_call_then_yield(VALUE self, VALUE obj) {
+ rb_block_call(obj, rb_intern("each"), 0, 0, iter_yield, obj);
+ return Qnil;
+}
+
+static VALUE array_spec_rb_mem_clear(VALUE self, VALUE obj) {
+ VALUE ary[1];
+ ary[0] = obj;
+ rb_mem_clear(ary, 1);
+ return ary[0];
+}
+
+static VALUE array_spec_rb_ary_freeze(VALUE self, VALUE ary) {
+ return rb_ary_freeze(ary);
+}
+
+static VALUE array_spec_rb_ary_to_ary(VALUE self, VALUE ary) {
+ return rb_ary_to_ary(ary);
+}
+
+static VALUE array_spec_rb_ary_subseq(VALUE self, VALUE ary, VALUE begin, VALUE len) {
+ return rb_ary_subseq(ary, FIX2LONG(begin), FIX2LONG(len));
+}
+
+void Init_array_spec(void) {
+ VALUE cls = rb_define_class("CApiArraySpecs", rb_cObject);
+ rb_define_method(cls, "rb_Array", array_spec_rb_Array, 1);
+ rb_define_method(cls, "RARRAY_LEN", array_spec_RARRAY_LEN, 1);
+ rb_define_method(cls, "RARRAY_PTR_iterate", array_spec_RARRAY_PTR_iterate, 1);
+ rb_define_method(cls, "RARRAY_PTR_assign", array_spec_RARRAY_PTR_assign, 2);
+ rb_define_method(cls, "RARRAY_PTR_memcpy", array_spec_RARRAY_PTR_memcpy, 2);
+ rb_define_method(cls, "RARRAY_AREF", array_spec_RARRAY_AREF, 2);
+ rb_define_method(cls, "RARRAY_ASET", array_spec_RARRAY_ASET, 3);
+ rb_define_method(cls, "rb_ary_aref", array_spec_rb_ary_aref, -1);
+ rb_define_method(cls, "rb_ary_clear", array_spec_rb_ary_clear, 1);
+ rb_define_method(cls, "rb_ary_delete", array_spec_rb_ary_delete, 2);
+ rb_define_method(cls, "rb_ary_delete_at", array_spec_rb_ary_delete_at, 2);
+ rb_define_method(cls, "rb_ary_dup", array_spec_rb_ary_dup, 1);
+ rb_define_method(cls, "rb_ary_entry", array_spec_rb_ary_entry, 2);
+ rb_define_method(cls, "rb_ary_includes", array_spec_rb_ary_includes, 2);
+ rb_define_method(cls, "rb_ary_join", array_spec_rb_ary_join, 2);
+ rb_define_method(cls, "rb_ary_to_s", array_spec_rb_ary_to_s, 1);
+ rb_define_method(cls, "rb_ary_new", array_spec_rb_ary_new, 0);
+ rb_define_method(cls, "rb_ary_new2", array_spec_rb_ary_new2, 1);
+ rb_define_method(cls, "rb_ary_new_capa", array_spec_rb_ary_new_capa, 1);
+ rb_define_method(cls, "rb_ary_new3", array_spec_rb_ary_new3, 3);
+ rb_define_method(cls, "rb_ary_new_from_args", array_spec_rb_ary_new_from_args, 3);
+ rb_define_method(cls, "rb_ary_new4", array_spec_rb_ary_new4, 3);
+ rb_define_method(cls, "rb_ary_new_from_values", array_spec_rb_ary_new_from_values, 3);
+ rb_define_method(cls, "rb_ary_pop", array_spec_rb_ary_pop, 1);
+ rb_define_method(cls, "rb_ary_push", array_spec_rb_ary_push, 2);
+ rb_define_method(cls, "rb_ary_cat", array_spec_rb_ary_cat, -1);
+ rb_define_method(cls, "rb_ary_reverse", array_spec_rb_ary_reverse, 1);
+ rb_define_method(cls, "rb_ary_rotate", array_spec_rb_ary_rotate, 2);
+ rb_define_method(cls, "rb_ary_shift", array_spec_rb_ary_shift, 1);
+ rb_define_method(cls, "rb_ary_sort", array_spec_rb_ary_sort, 1);
+ rb_define_method(cls, "rb_ary_sort_bang", array_spec_rb_ary_sort_bang, 1);
+ rb_define_method(cls, "rb_ary_store", array_spec_rb_ary_store, 3);
+ rb_define_method(cls, "rb_ary_concat", array_spec_rb_ary_concat, 2);
+ rb_define_method(cls, "rb_ary_plus", array_spec_rb_ary_plus, 2);
+ rb_define_method(cls, "rb_ary_unshift", array_spec_rb_ary_unshift, 2);
+ rb_define_method(cls, "rb_assoc_new", array_spec_rb_assoc_new, 2);
+ rb_define_method(cls, "rb_block_call", array_spec_rb_block_call, 1);
+ rb_define_method(cls, "rb_block_call_each_pair", array_spec_rb_block_call_each_pair, 1);
+ rb_define_method(cls, "rb_block_call_then_yield", array_spec_rb_block_call_then_yield, 1);
+ rb_define_method(cls, "rb_mem_clear", array_spec_rb_mem_clear, 1);
+ rb_define_method(cls, "rb_ary_freeze", array_spec_rb_ary_freeze, 1);
+ rb_define_method(cls, "rb_ary_to_ary", array_spec_rb_ary_to_ary, 1);
+ rb_define_method(cls, "rb_ary_subseq", array_spec_rb_ary_subseq, 3);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/basic_object_spec.c b/spec/ruby/optional/capi/ext/basic_object_spec.c
new file mode 100644
index 0000000000..1618670ceb
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/basic_object_spec.c
@@ -0,0 +1,19 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE basic_object_spec_RBASIC_CLASS(VALUE self, VALUE obj) {
+ return RBASIC_CLASS(obj);
+}
+
+void Init_basic_object_spec(void) {
+ VALUE cls = rb_define_class("CApiBasicObjectSpecs", rb_cObject);
+ rb_define_method(cls, "RBASIC_CLASS", basic_object_spec_RBASIC_CLASS, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/bignum_spec.c b/spec/ruby/optional/capi/ext/bignum_spec.c
new file mode 100644
index 0000000000..a950d8b16f
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/bignum_spec.c
@@ -0,0 +1,106 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE bignum_spec_rb_big2dbl(VALUE self, VALUE num) {
+ return rb_float_new(rb_big2dbl(num));
+}
+
+static VALUE bignum_spec_rb_dbl2big(VALUE self, VALUE num) {
+ double dnum = NUM2DBL(num);
+
+ return rb_dbl2big(dnum);
+}
+
+static VALUE bignum_spec_rb_big2ll(VALUE self, VALUE num) {
+ return rb_ll2inum(rb_big2ll(num));
+}
+
+static VALUE bignum_spec_rb_big2long(VALUE self, VALUE num) {
+ return LONG2NUM(rb_big2long(num));
+}
+
+static VALUE bignum_spec_rb_big2str(VALUE self, VALUE num, VALUE base) {
+ return rb_big2str(num, FIX2INT(base));
+}
+
+static VALUE bignum_spec_rb_big2ulong(VALUE self, VALUE num) {
+ return ULONG2NUM(rb_big2ulong(num));
+}
+
+static VALUE bignum_spec_RBIGNUM_SIGN(VALUE self, VALUE val) {
+ return INT2FIX(RBIGNUM_SIGN(val));
+}
+
+static VALUE bignum_spec_rb_big_cmp(VALUE self, VALUE x, VALUE y) {
+ return rb_big_cmp(x, y);
+}
+
+static VALUE bignum_spec_rb_big_pack(VALUE self, VALUE val) {
+ unsigned long buff;
+
+ rb_big_pack(val, &buff, 1);
+
+ return ULONG2NUM(buff);
+}
+
+static VALUE bignum_spec_rb_big_pack_length(VALUE self, VALUE val) {
+ long long_len;
+ int leading_bits = 0;
+ int divisor = SIZEOF_LONG;
+ size_t len = rb_absint_size(val, &leading_bits);
+ if (leading_bits == 0) {
+ len += 1;
+ }
+
+ long_len = len / divisor + ((len % divisor == 0) ? 0 : 1);
+ return LONG2NUM(long_len);
+}
+
+static VALUE bignum_spec_rb_big_pack_array(VALUE self, VALUE val, VALUE len) {
+ int i;
+ long long_len = NUM2LONG(len);
+
+ VALUE ary = rb_ary_new_capa(long_len);
+ unsigned long *buf = (unsigned long*) malloc(long_len * SIZEOF_LONG);
+
+ /* The array should be filled with recognisable junk so we can check
+ it is all cleared properly. */
+
+ for (i = 0; i < long_len; i++) {
+#if SIZEOF_LONG == 8
+ buf[i] = 0xfedcba9876543210L;
+#else
+ buf[i] = 0xfedcba98L;
+#endif
+ }
+
+ rb_big_pack(val, buf, long_len);
+ for (i = 0; i < long_len; i++) {
+ rb_ary_store(ary, i, ULONG2NUM(buf[i]));
+ }
+ free(buf);
+ return ary;
+}
+
+void Init_bignum_spec(void) {
+ VALUE cls = rb_define_class("CApiBignumSpecs", rb_cObject);
+ rb_define_method(cls, "rb_big2dbl", bignum_spec_rb_big2dbl, 1);
+ rb_define_method(cls, "rb_dbl2big", bignum_spec_rb_dbl2big, 1);
+ rb_define_method(cls, "rb_big2ll", bignum_spec_rb_big2ll, 1);
+ rb_define_method(cls, "rb_big2long", bignum_spec_rb_big2long, 1);
+ rb_define_method(cls, "rb_big2str", bignum_spec_rb_big2str, 2);
+ rb_define_method(cls, "rb_big2ulong", bignum_spec_rb_big2ulong, 1);
+ rb_define_method(cls, "RBIGNUM_SIGN", bignum_spec_RBIGNUM_SIGN, 1);
+ rb_define_method(cls, "rb_big_cmp", bignum_spec_rb_big_cmp, 2);
+ rb_define_method(cls, "rb_big_pack", bignum_spec_rb_big_pack, 1);
+ rb_define_method(cls, "rb_big_pack_array", bignum_spec_rb_big_pack_array, 2);
+ rb_define_method(cls, "rb_big_pack_length", bignum_spec_rb_big_pack_length, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/binding_spec.c b/spec/ruby/optional/capi/ext/binding_spec.c
new file mode 100644
index 0000000000..b2e3c88b6d
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/binding_spec.c
@@ -0,0 +1,19 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE binding_spec_get_binding(VALUE self) {
+ return rb_funcall(self, rb_intern("binding"), 0);
+}
+
+void Init_binding_spec(void) {
+ VALUE cls = rb_define_class("CApiBindingSpecs", rb_cObject);
+ rb_define_method(cls, "get_binding", binding_spec_get_binding, 0);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/boolean_spec.c b/spec/ruby/optional/capi/ext/boolean_spec.c
new file mode 100644
index 0000000000..081cffa103
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/boolean_spec.c
@@ -0,0 +1,33 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE boolean_spec_is_true(VALUE self, VALUE boolean) {
+ if (boolean) {
+ return INT2NUM(1);
+ } else {
+ return INT2NUM(2);
+ }
+}
+
+static VALUE boolean_spec_q_true(VALUE self) {
+ return Qtrue;
+}
+
+static VALUE boolean_spec_q_false(VALUE self) {
+ return Qfalse;
+}
+
+void Init_boolean_spec(void) {
+ VALUE cls = rb_define_class("CApiBooleanSpecs", rb_cObject);
+ rb_define_method(cls, "is_true", boolean_spec_is_true, 1);
+ rb_define_method(cls, "q_true", boolean_spec_q_true, 0);
+ rb_define_method(cls, "q_false", boolean_spec_q_false, 0);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/class_id_under_autoload_spec.c b/spec/ruby/optional/capi/ext/class_id_under_autoload_spec.c
new file mode 100644
index 0000000000..cc5550f041
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/class_id_under_autoload_spec.c
@@ -0,0 +1,13 @@
+#include "ruby.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void Init_class_id_under_autoload_spec(void) {
+ rb_define_class_id_under(rb_cObject, rb_intern("ClassIdUnderAutoload"), rb_cObject);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/class_spec.c b/spec/ruby/optional/capi/ext/class_spec.c
new file mode 100644
index 0000000000..589025f677
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/class_spec.c
@@ -0,0 +1,179 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE class_spec_call_super_method(VALUE self) {
+ return rb_call_super(0, 0);
+}
+
+static VALUE class_spec_define_call_super_method(VALUE self, VALUE obj, VALUE str_name) {
+ rb_define_method(obj, RSTRING_PTR(str_name), class_spec_call_super_method, 0);
+ return Qnil;
+}
+
+static VALUE class_spec_rb_class_path(VALUE self, VALUE klass) {
+ return rb_class_path(klass);
+}
+
+static VALUE class_spec_rb_class_name(VALUE self, VALUE klass) {
+ return rb_class_name(klass);
+}
+
+static VALUE class_spec_rb_class2name(VALUE self, VALUE klass) {
+ return rb_str_new2( rb_class2name(klass) );
+}
+
+static VALUE class_spec_rb_path2class(VALUE self, VALUE path) {
+ return rb_path2class(RSTRING_PTR(path));
+}
+
+static VALUE class_spec_rb_path_to_class(VALUE self, VALUE path) {
+ return rb_path_to_class(path);
+}
+
+static VALUE class_spec_rb_class_instance_methods(int argc, VALUE* argv, VALUE self) {
+ VALUE mod = argv[0];
+ return rb_class_instance_methods(--argc, ++argv, mod);
+}
+
+static VALUE class_spec_rb_class_public_instance_methods(int argc, VALUE* argv, VALUE self) {
+ VALUE mod = argv[0];
+ return rb_class_public_instance_methods(--argc, ++argv, mod);
+}
+
+static VALUE class_spec_rb_class_protected_instance_methods(int argc, VALUE* argv, VALUE self) {
+ VALUE mod = argv[0];
+ return rb_class_protected_instance_methods(--argc, ++argv, mod);
+}
+
+static VALUE class_spec_rb_class_private_instance_methods(int argc, VALUE* argv, VALUE self) {
+ VALUE mod = argv[0];
+ return rb_class_private_instance_methods(--argc, ++argv, mod);
+}
+
+static VALUE class_spec_rb_class_new(VALUE self, VALUE super) {
+ return rb_class_new(super);
+}
+
+static VALUE class_spec_rb_class_new_instance(VALUE self, VALUE args, VALUE klass) {
+ return rb_class_new_instance(RARRAY_LENINT(args), RARRAY_PTR(args), klass);
+}
+
+#ifdef RUBY_VERSION_IS_3_0
+static VALUE class_spec_rb_class_new_instance_kw(VALUE self, VALUE args, VALUE klass) {
+ return rb_class_new_instance_kw(RARRAY_LENINT(args), RARRAY_PTR(args), klass, RB_PASS_KEYWORDS);
+}
+#endif
+
+static VALUE class_spec_rb_class_real(VALUE self, VALUE object) {
+ if(rb_type_p(object, T_FIXNUM)) {
+ return INT2FIX(rb_class_real(FIX2INT(object)));
+ } else {
+ return rb_class_real(CLASS_OF(object));
+ }
+}
+
+static VALUE class_spec_rb_class_superclass(VALUE self, VALUE klass) {
+ return rb_class_superclass(klass);
+}
+
+static VALUE class_spec_cvar_defined(VALUE self, VALUE klass, VALUE id) {
+ ID as_id = rb_intern(StringValuePtr(id));
+ return rb_cvar_defined(klass, as_id);
+}
+
+static VALUE class_spec_cvar_get(VALUE self, VALUE klass, VALUE name) {
+ return rb_cvar_get(klass, rb_intern(StringValuePtr(name)));
+}
+
+static VALUE class_spec_cvar_set(VALUE self, VALUE klass, VALUE name, VALUE val) {
+ rb_cvar_set(klass, rb_intern(StringValuePtr(name)), val);
+ return Qnil;
+}
+
+static VALUE class_spec_cv_get(VALUE self, VALUE klass, VALUE name) {
+ return rb_cv_get(klass, StringValuePtr(name));
+}
+
+static VALUE class_spec_cv_set(VALUE self, VALUE klass, VALUE name, VALUE val) {
+ rb_cv_set(klass, StringValuePtr(name), val);
+
+ return Qnil;
+}
+
+VALUE class_spec_define_attr(VALUE self, VALUE klass, VALUE sym, VALUE read, VALUE write) {
+ int int_read, int_write;
+ int_read = read == Qtrue ? 1 : 0;
+ int_write = write == Qtrue ? 1 : 0;
+ rb_define_attr(klass, rb_id2name(SYM2ID(sym)), int_read, int_write);
+ return Qnil;
+}
+
+static VALUE class_spec_rb_define_class(VALUE self, VALUE name, VALUE super) {
+ if(NIL_P(super)) super = 0;
+ return rb_define_class(RSTRING_PTR(name), super);
+}
+
+static VALUE class_spec_rb_define_class_under(VALUE self, VALUE outer,
+ VALUE name, VALUE super) {
+ if(NIL_P(super)) super = 0;
+ return rb_define_class_under(outer, RSTRING_PTR(name), super);
+}
+
+static VALUE class_spec_rb_define_class_id_under(VALUE self, VALUE outer,
+ VALUE name, VALUE super) {
+ if(NIL_P(super)) super = 0;
+ return rb_define_class_id_under(outer, SYM2ID(name), super);
+}
+
+static VALUE class_spec_define_class_variable(VALUE self, VALUE klass, VALUE name, VALUE val) {
+ rb_define_class_variable(klass, StringValuePtr(name), val);
+ return Qnil;
+}
+
+static VALUE class_spec_include_module(VALUE self, VALUE klass, VALUE module) {
+ rb_include_module(klass, module);
+ return klass;
+}
+
+void Init_class_spec(void) {
+ VALUE cls = rb_define_class("CApiClassSpecs", rb_cObject);
+ rb_define_method(cls, "define_call_super_method", class_spec_define_call_super_method, 2);
+ rb_define_method(cls, "rb_class_path", class_spec_rb_class_path, 1);
+ rb_define_method(cls, "rb_class_name", class_spec_rb_class_name, 1);
+ rb_define_method(cls, "rb_class2name", class_spec_rb_class2name, 1);
+ rb_define_method(cls, "rb_path2class", class_spec_rb_path2class, 1);
+ rb_define_method(cls, "rb_path_to_class", class_spec_rb_path_to_class, 1);
+ rb_define_method(cls, "rb_class_instance_methods", class_spec_rb_class_instance_methods, -1);
+ rb_define_method(cls, "rb_class_public_instance_methods", class_spec_rb_class_public_instance_methods, -1);
+ rb_define_method(cls, "rb_class_protected_instance_methods", class_spec_rb_class_protected_instance_methods, -1);
+ rb_define_method(cls, "rb_class_private_instance_methods", class_spec_rb_class_private_instance_methods, -1);
+ rb_define_method(cls, "rb_class_new", class_spec_rb_class_new, 1);
+ rb_define_method(cls, "rb_class_new_instance", class_spec_rb_class_new_instance, 2);
+#ifdef RUBY_VERSION_IS_3_0
+ rb_define_method(cls, "rb_class_new_instance_kw", class_spec_rb_class_new_instance_kw, 2);
+#endif
+ rb_define_method(cls, "rb_class_real", class_spec_rb_class_real, 1);
+ rb_define_method(cls, "rb_class_superclass", class_spec_rb_class_superclass, 1);
+ rb_define_method(cls, "rb_cvar_defined", class_spec_cvar_defined, 2);
+ rb_define_method(cls, "rb_cvar_get", class_spec_cvar_get, 2);
+ rb_define_method(cls, "rb_cvar_set", class_spec_cvar_set, 3);
+ rb_define_method(cls, "rb_cv_get", class_spec_cv_get, 2);
+ rb_define_method(cls, "rb_cv_set", class_spec_cv_set, 3);
+ rb_define_method(cls, "rb_define_attr", class_spec_define_attr, 4);
+ rb_define_method(cls, "rb_define_class", class_spec_rb_define_class, 2);
+ rb_define_method(cls, "rb_define_class_under", class_spec_rb_define_class_under, 3);
+ rb_define_method(cls, "rb_define_class_id_under", class_spec_rb_define_class_id_under, 3);
+ rb_define_method(cls, "rb_define_class_variable", class_spec_define_class_variable, 3);
+ rb_define_method(cls, "rb_include_module", class_spec_include_module, 2);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/class_under_autoload_spec.c b/spec/ruby/optional/capi/ext/class_under_autoload_spec.c
new file mode 100644
index 0000000000..e0b1f249c0
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/class_under_autoload_spec.c
@@ -0,0 +1,13 @@
+#include "ruby.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void Init_class_under_autoload_spec(void) {
+ rb_define_class_under(rb_cObject, "ClassUnderAutoload", rb_cObject);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/complex_spec.c b/spec/ruby/optional/capi/ext/complex_spec.c
new file mode 100644
index 0000000000..dfccd7a037
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/complex_spec.c
@@ -0,0 +1,45 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE complex_spec_rb_Complex(VALUE self, VALUE num, VALUE den) {
+ return rb_Complex(num, den);
+}
+
+static VALUE complex_spec_rb_Complex1(VALUE self, VALUE num) {
+ return rb_Complex1(num);
+}
+
+static VALUE complex_spec_rb_Complex2(VALUE self, VALUE num, VALUE den) {
+ return rb_Complex2(num, den);
+}
+
+static VALUE complex_spec_rb_complex_new(VALUE self, VALUE num, VALUE den) {
+ return rb_complex_new(num, den);
+}
+
+static VALUE complex_spec_rb_complex_new1(VALUE self, VALUE num) {
+ return rb_complex_new1(num);
+}
+
+static VALUE complex_spec_rb_complex_new2(VALUE self, VALUE num, VALUE den) {
+ return rb_complex_new2(num, den);
+}
+
+void Init_complex_spec(void) {
+ VALUE cls = rb_define_class("CApiComplexSpecs", rb_cObject);
+ rb_define_method(cls, "rb_Complex", complex_spec_rb_Complex, 2);
+ rb_define_method(cls, "rb_Complex1", complex_spec_rb_Complex1, 1);
+ rb_define_method(cls, "rb_Complex2", complex_spec_rb_Complex2, 2);
+ rb_define_method(cls, "rb_complex_new", complex_spec_rb_complex_new, 2);
+ rb_define_method(cls, "rb_complex_new1", complex_spec_rb_complex_new1, 1);
+ rb_define_method(cls, "rb_complex_new2", complex_spec_rb_complex_new2, 2);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/spec/ruby/optional/capi/ext/constants_spec.c b/spec/ruby/optional/capi/ext/constants_spec.c
new file mode 100644
index 0000000000..9aee8db37f
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/constants_spec.c
@@ -0,0 +1,178 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define defconstfunc(name) \
+static VALUE constants_spec_##name(VALUE self) { return name; }
+
+defconstfunc(rb_cArray)
+defconstfunc(rb_cBasicObject)
+defconstfunc(rb_cBinding)
+defconstfunc(rb_cClass)
+defconstfunc(rb_cComplex)
+defconstfunc(rb_mComparable)
+#ifndef RUBY_VERSION_IS_3_0
+defconstfunc(rb_cData)
+#endif
+defconstfunc(rb_cDir)
+defconstfunc(rb_cEncoding)
+defconstfunc(rb_mEnumerable)
+defconstfunc(rb_cEnumerator)
+defconstfunc(rb_cFalseClass)
+defconstfunc(rb_cFile)
+defconstfunc(rb_mFileTest)
+defconstfunc(rb_cFloat)
+defconstfunc(rb_mGC)
+defconstfunc(rb_cHash)
+defconstfunc(rb_cInteger)
+defconstfunc(rb_cIO)
+defconstfunc(rb_mKernel)
+defconstfunc(rb_mMath)
+defconstfunc(rb_cMatch)
+defconstfunc(rb_cMethod)
+defconstfunc(rb_cModule)
+defconstfunc(rb_cNilClass)
+defconstfunc(rb_cNumeric)
+defconstfunc(rb_cObject)
+defconstfunc(rb_cProc)
+defconstfunc(rb_mProcess)
+defconstfunc(rb_cRandom)
+defconstfunc(rb_cRange)
+defconstfunc(rb_cRational)
+defconstfunc(rb_cRegexp)
+defconstfunc(rb_cStat)
+defconstfunc(rb_cString)
+defconstfunc(rb_cStruct)
+defconstfunc(rb_cSymbol)
+defconstfunc(rb_cTime)
+defconstfunc(rb_cThread)
+defconstfunc(rb_cTrueClass)
+defconstfunc(rb_cUnboundMethod)
+defconstfunc(rb_eArgError)
+defconstfunc(rb_eEncodingError)
+defconstfunc(rb_eEncCompatError)
+defconstfunc(rb_eEOFError)
+defconstfunc(rb_mErrno)
+defconstfunc(rb_eException)
+defconstfunc(rb_eFatal)
+defconstfunc(rb_eFloatDomainError)
+defconstfunc(rb_eFrozenError)
+defconstfunc(rb_eIndexError)
+defconstfunc(rb_eInterrupt)
+defconstfunc(rb_eIOError)
+defconstfunc(rb_eKeyError)
+defconstfunc(rb_eLoadError)
+defconstfunc(rb_eLocalJumpError)
+defconstfunc(rb_eMathDomainError)
+defconstfunc(rb_eNameError)
+defconstfunc(rb_eNoMemError)
+defconstfunc(rb_eNoMethodError)
+defconstfunc(rb_eNotImpError)
+defconstfunc(rb_eRangeError)
+defconstfunc(rb_eRegexpError)
+defconstfunc(rb_eRuntimeError)
+defconstfunc(rb_eScriptError)
+defconstfunc(rb_eSecurityError)
+defconstfunc(rb_eSignal)
+defconstfunc(rb_eStandardError)
+defconstfunc(rb_eStopIteration)
+defconstfunc(rb_eSyntaxError)
+defconstfunc(rb_eSystemCallError)
+defconstfunc(rb_eSystemExit)
+defconstfunc(rb_eSysStackError)
+defconstfunc(rb_eTypeError)
+defconstfunc(rb_eThreadError)
+defconstfunc(rb_mWaitReadable)
+defconstfunc(rb_mWaitWritable)
+defconstfunc(rb_eZeroDivError)
+
+void Init_constants_spec(void) {
+ VALUE cls = rb_define_class("CApiConstantsSpecs", rb_cObject);
+ rb_define_method(cls, "rb_cArray", constants_spec_rb_cArray, 0);
+ rb_define_method(cls, "rb_cBasicObject", constants_spec_rb_cBasicObject, 0);
+ rb_define_method(cls, "rb_cBinding", constants_spec_rb_cBinding, 0);
+ rb_define_method(cls, "rb_cClass", constants_spec_rb_cClass, 0);
+ rb_define_method(cls, "rb_cComplex", constants_spec_rb_cComplex, 0);
+ rb_define_method(cls, "rb_mComparable", constants_spec_rb_mComparable, 0);
+ #ifndef RUBY_VERSION_IS_3_0
+ rb_define_method(cls, "rb_cData", constants_spec_rb_cData, 0);
+ #endif
+ rb_define_method(cls, "rb_cDir", constants_spec_rb_cDir, 0);
+ rb_define_method(cls, "rb_cEncoding", constants_spec_rb_cEncoding, 0);
+ rb_define_method(cls, "rb_mEnumerable", constants_spec_rb_mEnumerable, 0);
+ rb_define_method(cls, "rb_cEnumerator", constants_spec_rb_cEnumerator, 0);
+ rb_define_method(cls, "rb_cFalseClass", constants_spec_rb_cFalseClass, 0);
+ rb_define_method(cls, "rb_cFile", constants_spec_rb_cFile, 0);
+ rb_define_method(cls, "rb_mFileTest", constants_spec_rb_mFileTest, 0);
+ rb_define_method(cls, "rb_cFloat", constants_spec_rb_cFloat, 0);
+ rb_define_method(cls, "rb_mGC", constants_spec_rb_mGC, 0);
+ rb_define_method(cls, "rb_cHash", constants_spec_rb_cHash, 0);
+ rb_define_method(cls, "rb_cInteger", constants_spec_rb_cInteger, 0);
+ rb_define_method(cls, "rb_cIO", constants_spec_rb_cIO, 0);
+ rb_define_method(cls, "rb_mKernel", constants_spec_rb_mKernel, 0);
+ rb_define_method(cls, "rb_mMath", constants_spec_rb_mMath, 0);
+ rb_define_method(cls, "rb_cMatch", constants_spec_rb_cMatch, 0);
+ rb_define_method(cls, "rb_cMethod", constants_spec_rb_cMethod, 0);
+ rb_define_method(cls, "rb_cModule", constants_spec_rb_cModule, 0);
+ rb_define_method(cls, "rb_cNilClass", constants_spec_rb_cNilClass, 0);
+ rb_define_method(cls, "rb_cNumeric", constants_spec_rb_cNumeric, 0);
+ rb_define_method(cls, "rb_cObject", constants_spec_rb_cObject, 0);
+ rb_define_method(cls, "rb_cProc", constants_spec_rb_cProc, 0);
+ rb_define_method(cls, "rb_mProcess", constants_spec_rb_mProcess, 0);
+ rb_define_method(cls, "rb_cRandom", constants_spec_rb_cRandom, 0);
+ rb_define_method(cls, "rb_cRange", constants_spec_rb_cRange, 0);
+ rb_define_method(cls, "rb_cRational", constants_spec_rb_cRational, 0);
+ rb_define_method(cls, "rb_cRegexp", constants_spec_rb_cRegexp, 0);
+ rb_define_method(cls, "rb_cStat", constants_spec_rb_cStat, 0);
+ rb_define_method(cls, "rb_cString", constants_spec_rb_cString, 0);
+ rb_define_method(cls, "rb_cStruct", constants_spec_rb_cStruct, 0);
+ rb_define_method(cls, "rb_cSymbol", constants_spec_rb_cSymbol, 0);
+ rb_define_method(cls, "rb_cTime", constants_spec_rb_cTime, 0);
+ rb_define_method(cls, "rb_cThread", constants_spec_rb_cThread, 0);
+ rb_define_method(cls, "rb_cTrueClass", constants_spec_rb_cTrueClass, 0);
+ rb_define_method(cls, "rb_cUnboundMethod", constants_spec_rb_cUnboundMethod, 0);
+ rb_define_method(cls, "rb_eArgError", constants_spec_rb_eArgError, 0);
+ rb_define_method(cls, "rb_eEncodingError", constants_spec_rb_eEncodingError, 0);
+ rb_define_method(cls, "rb_eEncCompatError", constants_spec_rb_eEncCompatError, 0);
+ rb_define_method(cls, "rb_eEOFError", constants_spec_rb_eEOFError, 0);
+ rb_define_method(cls, "rb_mErrno", constants_spec_rb_mErrno, 0);
+ rb_define_method(cls, "rb_eException", constants_spec_rb_eException, 0);
+ rb_define_method(cls, "rb_eFatal", constants_spec_rb_eFatal, 0);
+ rb_define_method(cls, "rb_eFloatDomainError", constants_spec_rb_eFloatDomainError, 0);
+ rb_define_method(cls, "rb_eFrozenError", constants_spec_rb_eFrozenError, 0);
+ rb_define_method(cls, "rb_eIndexError", constants_spec_rb_eIndexError, 0);
+ rb_define_method(cls, "rb_eInterrupt", constants_spec_rb_eInterrupt, 0);
+ rb_define_method(cls, "rb_eIOError", constants_spec_rb_eIOError, 0);
+ rb_define_method(cls, "rb_eKeyError", constants_spec_rb_eKeyError, 0);
+ rb_define_method(cls, "rb_eLoadError", constants_spec_rb_eLoadError, 0);
+ rb_define_method(cls, "rb_eLocalJumpError", constants_spec_rb_eLocalJumpError, 0);
+ rb_define_method(cls, "rb_eMathDomainError", constants_spec_rb_eMathDomainError, 0);
+ rb_define_method(cls, "rb_eNameError", constants_spec_rb_eNameError, 0);
+ rb_define_method(cls, "rb_eNoMemError", constants_spec_rb_eNoMemError, 0);
+ rb_define_method(cls, "rb_eNoMethodError", constants_spec_rb_eNoMethodError, 0);
+ rb_define_method(cls, "rb_eNotImpError", constants_spec_rb_eNotImpError, 0);
+ rb_define_method(cls, "rb_eRangeError", constants_spec_rb_eRangeError, 0);
+ rb_define_method(cls, "rb_eRegexpError", constants_spec_rb_eRegexpError, 0);
+ rb_define_method(cls, "rb_eRuntimeError", constants_spec_rb_eRuntimeError, 0);
+ rb_define_method(cls, "rb_eScriptError", constants_spec_rb_eScriptError, 0);
+ rb_define_method(cls, "rb_eSecurityError", constants_spec_rb_eSecurityError, 0);
+ rb_define_method(cls, "rb_eSignal", constants_spec_rb_eSignal, 0);
+ rb_define_method(cls, "rb_eStandardError", constants_spec_rb_eStandardError, 0);
+ rb_define_method(cls, "rb_eStopIteration", constants_spec_rb_eStopIteration, 0);
+ rb_define_method(cls, "rb_eSyntaxError", constants_spec_rb_eSyntaxError, 0);
+ rb_define_method(cls, "rb_eSystemCallError", constants_spec_rb_eSystemCallError, 0);
+ rb_define_method(cls, "rb_eSystemExit", constants_spec_rb_eSystemExit, 0);
+ rb_define_method(cls, "rb_eSysStackError", constants_spec_rb_eSysStackError, 0);
+ rb_define_method(cls, "rb_eTypeError", constants_spec_rb_eTypeError, 0);
+ rb_define_method(cls, "rb_eThreadError", constants_spec_rb_eThreadError, 0);
+ rb_define_method(cls, "rb_mWaitReadable", constants_spec_rb_mWaitReadable, 0);
+ rb_define_method(cls, "rb_mWaitWritable", constants_spec_rb_mWaitWritable, 0);
+ rb_define_method(cls, "rb_eZeroDivError", constants_spec_rb_eZeroDivError, 0);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/data_spec.c b/spec/ruby/optional/capi/ext/data_spec.c
new file mode 100644
index 0000000000..ef069ef0ba
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/data_spec.c
@@ -0,0 +1,89 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct sample_wrapped_struct {
+ int foo;
+};
+
+void sample_wrapped_struct_free(void* st) {
+ free(st);
+}
+
+void sample_wrapped_struct_mark(void* st) {
+}
+
+VALUE sdaf_alloc_func(VALUE klass) {
+ struct sample_wrapped_struct* bar = (struct sample_wrapped_struct*) malloc(sizeof(struct sample_wrapped_struct));
+ bar->foo = 42;
+ return Data_Wrap_Struct(klass, &sample_wrapped_struct_mark, &sample_wrapped_struct_free, bar);
+}
+
+VALUE sdaf_get_struct(VALUE self) {
+ struct sample_wrapped_struct* bar;
+ Data_Get_Struct(self, struct sample_wrapped_struct, bar);
+
+ return INT2FIX((*bar).foo);
+}
+
+VALUE sws_wrap_struct(VALUE self, VALUE val) {
+ struct sample_wrapped_struct* bar = (struct sample_wrapped_struct*) malloc(sizeof(struct sample_wrapped_struct));
+ bar->foo = FIX2INT(val);
+ return Data_Wrap_Struct(rb_cObject, &sample_wrapped_struct_mark, &sample_wrapped_struct_free, bar);
+}
+
+VALUE sws_get_struct(VALUE self, VALUE obj) {
+ struct sample_wrapped_struct* bar;
+ Data_Get_Struct(obj, struct sample_wrapped_struct, bar);
+
+ return INT2FIX((*bar).foo);
+}
+
+VALUE sws_get_struct_rdata(VALUE self, VALUE obj) {
+ struct sample_wrapped_struct* bar;
+ bar = (struct sample_wrapped_struct*) RDATA(obj)->data;
+ return INT2FIX(bar->foo);
+}
+
+VALUE sws_get_struct_data_ptr(VALUE self, VALUE obj) {
+ struct sample_wrapped_struct* bar;
+ bar = (struct sample_wrapped_struct*) DATA_PTR(obj);
+ return INT2FIX(bar->foo);
+}
+
+VALUE sws_change_struct(VALUE self, VALUE obj, VALUE new_val) {
+ struct sample_wrapped_struct *old_struct, *new_struct;
+ new_struct = (struct sample_wrapped_struct*) malloc(sizeof(struct sample_wrapped_struct));
+ new_struct->foo = FIX2INT(new_val);
+ old_struct = (struct sample_wrapped_struct*) RDATA(obj)->data;
+ free(old_struct);
+ RDATA(obj)->data = new_struct;
+ return Qnil;
+}
+
+VALUE sws_rb_check_type(VALUE self, VALUE obj, VALUE other) {
+ rb_check_type(obj, TYPE(other));
+ return Qtrue;
+}
+
+void Init_data_spec(void) {
+ VALUE cls = rb_define_class("CApiAllocSpecs", rb_cObject);
+ rb_define_alloc_func(cls, sdaf_alloc_func);
+ rb_define_method(cls, "wrapped_data", sdaf_get_struct, 0);
+ cls = rb_define_class("CApiWrappedStructSpecs", rb_cObject);
+ rb_define_method(cls, "wrap_struct", sws_wrap_struct, 1);
+ rb_define_method(cls, "get_struct", sws_get_struct, 1);
+ rb_define_method(cls, "get_struct_rdata", sws_get_struct_rdata, 1);
+ rb_define_method(cls, "get_struct_data_ptr", sws_get_struct_data_ptr, 1);
+ rb_define_method(cls, "change_struct", sws_change_struct, 2);
+ rb_define_method(cls, "rb_check_type", sws_rb_check_type, 2);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/debug_spec.c b/spec/ruby/optional/capi/ext/debug_spec.c
new file mode 100644
index 0000000000..344dfc33fa
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/debug_spec.c
@@ -0,0 +1,93 @@
+#include "ruby.h"
+#include "rubyspec.h"
+#include "ruby/debug.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE callback_data = Qfalse;
+
+static VALUE rb_debug_inspector_open_callback(const rb_debug_inspector_t *dc, void *ptr) {
+ if (!dc) {
+ rb_raise(rb_eRuntimeError, "rb_debug_inspector_t should not be NULL");
+ }
+
+ VALUE locations = rb_debug_inspector_backtrace_locations(dc);
+ int len = RARRAY_LENINT(locations);
+ VALUE results = rb_ary_new2(len);
+ for (int i = 0; i < len; i++) {
+ VALUE ary = rb_ary_new2(5); // [self, klass, binding, iseq, backtrace_location]
+ rb_ary_store(ary, 0, rb_debug_inspector_frame_self_get(dc, i));
+ rb_ary_store(ary, 1, rb_debug_inspector_frame_class_get(dc, i));
+ rb_ary_store(ary, 2, rb_debug_inspector_frame_binding_get(dc, i));
+ rb_ary_store(ary, 3, rb_debug_inspector_frame_iseq_get(dc, i));
+ rb_ary_store(ary, 4, rb_ary_entry(locations, i));
+ rb_ary_push(results, ary);
+ }
+ callback_data = (VALUE)ptr;
+ return results;
+}
+
+static VALUE rb_debug_inspector_frame_self_get_callback(const rb_debug_inspector_t *dc, void *ptr) {
+ return rb_debug_inspector_frame_self_get(dc, NUM2LONG((VALUE) ptr));
+}
+
+static VALUE rb_debug_inspector_frame_class_get_callback(const rb_debug_inspector_t *dc, void *ptr) {
+ return rb_debug_inspector_frame_class_get(dc, NUM2LONG((VALUE) ptr));
+}
+
+static VALUE rb_debug_inspector_frame_binding_get_callback(const rb_debug_inspector_t *dc, void *ptr) {
+ return rb_debug_inspector_frame_binding_get(dc, NUM2LONG((VALUE) ptr));
+}
+
+static VALUE rb_debug_inspector_frame_iseq_get_callback(const rb_debug_inspector_t *dc, void *ptr) {
+ return rb_debug_inspector_frame_iseq_get(dc, NUM2LONG((VALUE) ptr));
+}
+
+static VALUE debug_spec_callback_data(VALUE self){
+ return callback_data;
+}
+
+VALUE debug_spec_rb_debug_inspector_open(VALUE self, VALUE index) {
+ return rb_debug_inspector_open(rb_debug_inspector_open_callback, (void *)index);
+}
+
+VALUE debug_spec_rb_debug_inspector_frame_self_get(VALUE self, VALUE index) {
+ return rb_debug_inspector_open(rb_debug_inspector_frame_self_get_callback, (void *)index);
+}
+
+VALUE debug_spec_rb_debug_inspector_frame_class_get(VALUE self, VALUE index) {
+ return rb_debug_inspector_open(rb_debug_inspector_frame_class_get_callback, (void *)index);
+}
+
+VALUE debug_spec_rb_debug_inspector_frame_binding_get(VALUE self, VALUE index) {
+ return rb_debug_inspector_open(rb_debug_inspector_frame_binding_get_callback, (void *)index);
+}
+
+VALUE debug_spec_rb_debug_inspector_frame_iseq_get(VALUE self, VALUE index) {
+ return rb_debug_inspector_open(rb_debug_inspector_frame_iseq_get_callback, (void *)index);
+}
+
+static VALUE rb_debug_inspector_backtrace_locations_func(const rb_debug_inspector_t *dc, void *ptr) {
+ return rb_debug_inspector_backtrace_locations(dc);
+}
+
+VALUE debug_spec_rb_debug_inspector_backtrace_locations(VALUE self) {
+ return rb_debug_inspector_open(rb_debug_inspector_backtrace_locations_func, (void *)self);
+}
+
+void Init_debug_spec(void) {
+ VALUE cls = rb_define_class("CApiDebugSpecs", rb_cObject);
+ rb_define_method(cls, "rb_debug_inspector_open", debug_spec_rb_debug_inspector_open, 1);
+ rb_define_method(cls, "rb_debug_inspector_frame_self_get", debug_spec_rb_debug_inspector_frame_self_get, 1);
+ rb_define_method(cls, "rb_debug_inspector_frame_class_get", debug_spec_rb_debug_inspector_frame_class_get, 1);
+ rb_define_method(cls, "rb_debug_inspector_frame_binding_get", debug_spec_rb_debug_inspector_frame_binding_get, 1);
+ rb_define_method(cls, "rb_debug_inspector_frame_iseq_get", debug_spec_rb_debug_inspector_frame_iseq_get, 1);
+ rb_define_method(cls, "rb_debug_inspector_backtrace_locations", debug_spec_rb_debug_inspector_backtrace_locations, 0);
+ rb_define_method(cls, "debug_spec_callback_data", debug_spec_callback_data, 0);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/encoding_spec.c b/spec/ruby/optional/capi/ext/encoding_spec.c
new file mode 100644
index 0000000000..a0136530f2
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/encoding_spec.c
@@ -0,0 +1,371 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include "ruby/encoding.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE encoding_spec_MBCLEN_CHARFOUND_P(VALUE self, VALUE obj) {
+ return INT2FIX(MBCLEN_CHARFOUND_P(FIX2INT(obj)));
+}
+
+static VALUE encoding_spec_ENC_CODERANGE_ASCIIONLY(VALUE self, VALUE obj) {
+ if(ENC_CODERANGE_ASCIIONLY(obj)) {
+ return Qtrue;
+ } else {
+ return Qfalse;
+ }
+}
+
+static VALUE encoding_spec_rb_usascii_encoding(VALUE self) {
+ return rb_str_new2(rb_usascii_encoding()->name);
+}
+
+static VALUE encoding_spec_rb_usascii_encindex(VALUE self) {
+ return INT2NUM(rb_usascii_encindex());
+}
+
+static VALUE encoding_spec_rb_ascii8bit_encoding(VALUE self) {
+ return rb_str_new2(rb_ascii8bit_encoding()->name);
+}
+
+static VALUE encoding_spec_rb_ascii8bit_encindex(VALUE self) {
+ return INT2NUM(rb_ascii8bit_encindex());
+}
+
+static VALUE encoding_spec_rb_utf8_encoding(VALUE self) {
+ return rb_str_new2(rb_utf8_encoding()->name);
+}
+
+static VALUE encoding_spec_rb_utf8_encindex(VALUE self) {
+ return INT2NUM(rb_utf8_encindex());
+}
+
+static VALUE encoding_spec_rb_locale_encoding(VALUE self) {
+ return rb_str_new2(rb_locale_encoding()->name);
+}
+
+static VALUE encoding_spec_rb_locale_encindex(VALUE self) {
+ return INT2NUM(rb_locale_encindex());
+}
+
+static VALUE encoding_spec_rb_filesystem_encoding(VALUE self) {
+ return rb_str_new2(rb_filesystem_encoding()->name);
+}
+
+static VALUE encoding_spec_rb_filesystem_encindex(VALUE self) {
+ return INT2NUM(rb_filesystem_encindex());
+}
+
+static VALUE encoding_spec_rb_default_internal_encoding(VALUE self) {
+ rb_encoding* enc = rb_default_internal_encoding();
+ if(enc == 0) return Qnil;
+ return rb_str_new2(enc->name);
+}
+
+static VALUE encoding_spec_rb_default_external_encoding(VALUE self) {
+ rb_encoding* enc = rb_default_external_encoding();
+ if(enc == 0) return Qnil;
+ return rb_str_new2(enc->name);
+}
+
+static VALUE encoding_spec_rb_enc_alias(VALUE self, VALUE alias, VALUE orig) {
+ return INT2NUM(rb_enc_alias(RSTRING_PTR(alias), RSTRING_PTR(orig)));
+}
+
+static VALUE encoding_spec_rb_enc_associate(VALUE self, VALUE obj, VALUE enc) {
+ return rb_enc_associate(obj, NIL_P(enc) ? NULL : rb_enc_find(RSTRING_PTR(enc)));
+}
+
+static VALUE encoding_spec_rb_enc_associate_index(VALUE self, VALUE obj, VALUE index) {
+ return rb_enc_associate_index(obj, FIX2INT(index));
+}
+
+static VALUE encoding_spec_rb_enc_compatible(VALUE self, VALUE a, VALUE b) {
+ rb_encoding* enc = rb_enc_compatible(a, b);
+
+ if(!enc) return INT2FIX(0);
+
+ return rb_enc_from_encoding(enc);
+}
+
+static VALUE encoding_spec_rb_enc_copy(VALUE self, VALUE dest, VALUE src) {
+ rb_enc_copy(dest, src);
+ return dest;
+}
+
+static VALUE encoding_spec_rb_enc_find(VALUE self, VALUE name) {
+ return rb_str_new2(rb_enc_find(RSTRING_PTR(name))->name);
+}
+
+static VALUE encoding_spec_rb_enc_find_index(VALUE self, VALUE name) {
+ return INT2NUM(rb_enc_find_index(RSTRING_PTR(name)));
+}
+
+static VALUE encoding_spec_rb_enc_isalnum(VALUE self, VALUE chr, VALUE encoding) {
+ rb_encoding *e = rb_to_encoding(encoding);
+ return rb_enc_isalnum(FIX2INT(chr), e) ? Qtrue : Qfalse;
+}
+
+static VALUE encoding_spec_rb_enc_isspace(VALUE self, VALUE chr, VALUE encoding) {
+ rb_encoding *e = rb_to_encoding(encoding);
+ return rb_enc_isspace(FIX2INT(chr), e) ? Qtrue : Qfalse;
+}
+
+static VALUE encoding_spec_rb_enc_from_index(VALUE self, VALUE index) {
+ return rb_str_new2(rb_enc_from_index(NUM2INT(index))->name);
+}
+
+static VALUE encoding_spec_rb_enc_mbc_to_codepoint(VALUE self, VALUE str) {
+ char *p = RSTRING_PTR(str);
+ char *e = RSTRING_END(str);
+ return INT2FIX(rb_enc_mbc_to_codepoint(p, e, rb_enc_get(str)));
+}
+
+static VALUE encoding_spec_rb_enc_mbcput(VALUE self, VALUE code, VALUE encoding) {
+ unsigned int c = FIX2UINT(code);
+ rb_encoding *enc = rb_to_encoding(encoding);
+ char buf[ONIGENC_CODE_TO_MBC_MAXLEN];
+ memset(buf, '\1', sizeof(buf));
+ int len = rb_enc_mbcput(c, buf, enc);
+ if (buf[len] != '\1') {
+ rb_raise(rb_eRuntimeError, "should not change bytes after len");
+ }
+ return rb_enc_str_new(buf, len, enc);
+}
+
+static VALUE encoding_spec_rb_enc_from_encoding(VALUE self, VALUE name) {
+ return rb_enc_from_encoding(rb_enc_find(RSTRING_PTR(name)));
+}
+
+static VALUE encoding_spec_rb_enc_get(VALUE self, VALUE obj) {
+ return rb_str_new2(rb_enc_get(obj)->name);
+}
+
+static VALUE encoding_spec_rb_enc_precise_mbclen(VALUE self, VALUE str, VALUE offset) {
+ int o = FIX2INT(offset);
+ char *p = RSTRING_PTR(str);
+ char *e = p + o;
+ return INT2FIX(rb_enc_precise_mbclen(p, e, rb_enc_get(str)));
+}
+
+static VALUE encoding_spec_rb_obj_encoding(VALUE self, VALUE obj) {
+ return rb_obj_encoding(obj);
+}
+
+static VALUE encoding_spec_rb_enc_get_index(VALUE self, VALUE obj) {
+ return INT2NUM(rb_enc_get_index(obj));
+}
+
+static VALUE encoding_spec_rb_enc_set_index(VALUE self, VALUE obj, VALUE index) {
+ int i = NUM2INT(index);
+
+ rb_encoding* enc = rb_enc_from_index(i);
+ rb_enc_set_index(obj, i);
+
+ return rb_ary_new3(2, rb_str_new2(rb_enc_name(enc)),
+ rb_str_new2(rb_enc_name(rb_enc_get(obj))));
+}
+
+static VALUE encoding_spec_rb_enc_str_coderange(VALUE self, VALUE str) {
+ int coderange = rb_enc_str_coderange(str);
+
+ switch(coderange) {
+ case ENC_CODERANGE_UNKNOWN:
+ return ID2SYM(rb_intern("coderange_unknown"));
+ case ENC_CODERANGE_7BIT:
+ return ID2SYM(rb_intern("coderange_7bit"));
+ case ENC_CODERANGE_VALID:
+ return ID2SYM(rb_intern("coderange_valid"));
+ case ENC_CODERANGE_BROKEN:
+ return ID2SYM(rb_intern("coderange_broken"));
+ default:
+ return ID2SYM(rb_intern("coderange_unrecognized"));
+ }
+}
+
+static VALUE encoding_spec_rb_enc_str_new_cstr(VALUE self, VALUE str, VALUE enc) {
+ rb_encoding *e = rb_to_encoding(enc);
+ return rb_enc_str_new_cstr(StringValueCStr(str), e);
+}
+
+static VALUE encoding_spec_rb_enc_str_new_cstr_constant(VALUE self, VALUE enc) {
+ if (NIL_P(enc)) {
+ rb_encoding *e = NULL;
+ return rb_enc_str_new_static("test string literal", strlen("test string literal"), e);
+ } else {
+ rb_encoding *e = rb_to_encoding(enc);
+ return rb_enc_str_new_cstr("test string literal", e);
+ }
+}
+
+static VALUE encoding_spec_rb_enc_str_new(VALUE self, VALUE str, VALUE len, VALUE enc) {
+ return rb_enc_str_new(RSTRING_PTR(str), FIX2INT(len), rb_to_encoding(enc));
+}
+
+static VALUE encoding_spec_ENCODING_GET(VALUE self, VALUE obj) {
+ return INT2NUM(ENCODING_GET(obj));
+}
+
+static VALUE encoding_spec_ENCODING_SET(VALUE self, VALUE obj, VALUE index) {
+ int i = NUM2INT(index);
+
+ rb_encoding* enc = rb_enc_from_index(i);
+ ENCODING_SET(obj, i);
+
+ return rb_ary_new3(2, rb_str_new2(rb_enc_name(enc)),
+ rb_str_new2(rb_enc_name(rb_enc_get(obj))));
+}
+
+static VALUE encoding_spec_rb_enc_to_index(VALUE self, VALUE name) {
+ return INT2NUM(rb_enc_to_index(NIL_P(name) ? NULL : rb_enc_find(RSTRING_PTR(name))));
+}
+
+static VALUE encoding_spec_rb_to_encoding(VALUE self, VALUE obj) {
+ return rb_str_new2(rb_to_encoding(obj)->name);
+}
+
+static rb_encoding** native_rb_encoding_pointer;
+
+static VALUE encoding_spec_rb_to_encoding_native_store(VALUE self, VALUE obj) {
+ rb_encoding* enc = rb_to_encoding(obj);
+ VALUE address = SIZET2NUM((size_t) native_rb_encoding_pointer);
+ *native_rb_encoding_pointer = enc;
+ return address;
+}
+
+static VALUE encoding_spec_rb_to_encoding_native_name(VALUE self, VALUE address) {
+ rb_encoding** ptr = (rb_encoding**) NUM2SIZET(address);
+ rb_encoding* enc = *ptr;
+ return rb_str_new2(enc->name);
+}
+
+static VALUE encoding_spec_rb_to_encoding_index(VALUE self, VALUE obj) {
+ return INT2NUM(rb_to_encoding_index(obj));
+}
+
+static VALUE encoding_spec_rb_enc_nth(VALUE self, VALUE str, VALUE index) {
+ char* start = RSTRING_PTR(str);
+ char* end = start + RSTRING_LEN(str);
+ char* ptr = rb_enc_nth(start, end, FIX2LONG(index), rb_enc_get(str));
+ return LONG2NUM(ptr - start);
+}
+
+static VALUE encoding_spec_rb_enc_codepoint_len(VALUE self, VALUE str) {
+ char* start = RSTRING_PTR(str);
+ char* end = start + RSTRING_LEN(str);
+
+ int len;
+ unsigned int codepoint = rb_enc_codepoint_len(start, end, &len, rb_enc_get(str));
+
+ return rb_ary_new3(2, LONG2NUM(codepoint), LONG2NUM(len));
+}
+
+static VALUE encoding_spec_rb_enc_str_asciionly_p(VALUE self, VALUE str) {
+ if (rb_enc_str_asciionly_p(str)) {
+ return Qtrue;
+ } else {
+ return Qfalse;
+ }
+}
+
+static VALUE encoding_spec_rb_uv_to_utf8(VALUE self, VALUE buf, VALUE num) {
+ int len = rb_uv_to_utf8(RSTRING_PTR(buf), NUM2INT(num));
+ RB_ENC_CODERANGE_CLEAR(buf);
+ return INT2NUM(len);
+}
+
+static VALUE encoding_spec_ONIGENC_MBC_CASE_FOLD(VALUE self, VALUE str) {
+ char *beg = RSTRING_PTR(str);
+ char *beg_initial = beg;
+ char *end = beg + 2;
+ OnigUChar fold[ONIGENC_GET_CASE_FOLD_CODES_MAX_NUM];
+ memset(fold, '\1', sizeof(fold));
+ rb_encoding *enc = rb_enc_get(str);
+ int r = ONIGENC_MBC_CASE_FOLD(enc, ONIGENC_CASE_FOLD, &beg, (const OnigUChar *)end, fold);
+ if (r > 0 && fold[r] != '\1') {
+ rb_raise(rb_eRuntimeError, "should not change bytes after len");
+ }
+ VALUE str_result = r <= 0 ? Qnil : rb_enc_str_new((char *)fold, r, enc);
+ long bytes_used = beg - beg_initial;
+ return rb_ary_new3(2, str_result, INT2FIX(bytes_used));
+}
+
+static VALUE encoding_spec_rb_enc_codelen(VALUE self, VALUE code, VALUE encoding) {
+ unsigned int c = FIX2UINT(code);
+ rb_encoding *enc = rb_to_encoding(encoding);
+ return INT2FIX(rb_enc_codelen(c, enc));
+}
+
+static VALUE encoding_spec_rb_enc_strlen(VALUE self, VALUE str, VALUE length, VALUE encoding) {
+ int l = FIX2INT(length);
+ char *p = RSTRING_PTR(str);
+ char *e = p + l;
+
+ return LONG2FIX(rb_enc_strlen(p, e, rb_to_encoding(encoding)));
+}
+
+void Init_encoding_spec(void) {
+ VALUE cls;
+ native_rb_encoding_pointer = (rb_encoding**) malloc(sizeof(rb_encoding*));
+
+ cls = rb_define_class("CApiEncodingSpecs", rb_cObject);
+ rb_define_method(cls, "ENC_CODERANGE_ASCIIONLY",
+ encoding_spec_ENC_CODERANGE_ASCIIONLY, 1);
+
+ rb_define_method(cls, "rb_usascii_encoding", encoding_spec_rb_usascii_encoding, 0);
+ rb_define_method(cls, "rb_usascii_encindex", encoding_spec_rb_usascii_encindex, 0);
+ rb_define_method(cls, "rb_ascii8bit_encoding", encoding_spec_rb_ascii8bit_encoding, 0);
+ rb_define_method(cls, "rb_ascii8bit_encindex", encoding_spec_rb_ascii8bit_encindex, 0);
+ rb_define_method(cls, "rb_utf8_encoding", encoding_spec_rb_utf8_encoding, 0);
+ rb_define_method(cls, "rb_utf8_encindex", encoding_spec_rb_utf8_encindex, 0);
+ rb_define_method(cls, "rb_locale_encoding", encoding_spec_rb_locale_encoding, 0);
+ rb_define_method(cls, "rb_locale_encindex", encoding_spec_rb_locale_encindex, 0);
+ rb_define_method(cls, "rb_filesystem_encoding", encoding_spec_rb_filesystem_encoding, 0);
+ rb_define_method(cls, "rb_filesystem_encindex", encoding_spec_rb_filesystem_encindex, 0);
+ rb_define_method(cls, "rb_default_internal_encoding", encoding_spec_rb_default_internal_encoding, 0);
+ rb_define_method(cls, "rb_default_external_encoding", encoding_spec_rb_default_external_encoding, 0);
+ rb_define_method(cls, "rb_enc_alias", encoding_spec_rb_enc_alias, 2);
+ rb_define_method(cls, "MBCLEN_CHARFOUND_P", encoding_spec_MBCLEN_CHARFOUND_P, 1);
+ rb_define_method(cls, "rb_enc_associate", encoding_spec_rb_enc_associate, 2);
+ rb_define_method(cls, "rb_enc_associate_index", encoding_spec_rb_enc_associate_index, 2);
+ rb_define_method(cls, "rb_enc_compatible", encoding_spec_rb_enc_compatible, 2);
+ rb_define_method(cls, "rb_enc_copy", encoding_spec_rb_enc_copy, 2);
+ rb_define_method(cls, "rb_enc_codelen", encoding_spec_rb_enc_codelen, 2);
+ rb_define_method(cls, "rb_enc_strlen", encoding_spec_rb_enc_strlen, 3);
+ rb_define_method(cls, "rb_enc_find", encoding_spec_rb_enc_find, 1);
+ rb_define_method(cls, "rb_enc_find_index", encoding_spec_rb_enc_find_index, 1);
+ rb_define_method(cls, "rb_enc_isalnum", encoding_spec_rb_enc_isalnum, 2);
+ rb_define_method(cls, "rb_enc_isspace", encoding_spec_rb_enc_isspace, 2);
+ rb_define_method(cls, "rb_enc_from_index", encoding_spec_rb_enc_from_index, 1);
+ rb_define_method(cls, "rb_enc_mbc_to_codepoint", encoding_spec_rb_enc_mbc_to_codepoint, 1);
+ rb_define_method(cls, "rb_enc_mbcput", encoding_spec_rb_enc_mbcput, 2);
+ rb_define_method(cls, "rb_enc_from_encoding", encoding_spec_rb_enc_from_encoding, 1);
+ rb_define_method(cls, "rb_enc_get", encoding_spec_rb_enc_get, 1);
+ rb_define_method(cls, "rb_enc_precise_mbclen", encoding_spec_rb_enc_precise_mbclen, 2);
+ rb_define_method(cls, "rb_obj_encoding", encoding_spec_rb_obj_encoding, 1);
+ rb_define_method(cls, "rb_enc_get_index", encoding_spec_rb_enc_get_index, 1);
+ rb_define_method(cls, "rb_enc_set_index", encoding_spec_rb_enc_set_index, 2);
+ rb_define_method(cls, "rb_enc_str_coderange", encoding_spec_rb_enc_str_coderange, 1);
+ rb_define_method(cls, "rb_enc_str_new_cstr", encoding_spec_rb_enc_str_new_cstr, 2);
+ rb_define_method(cls, "rb_enc_str_new_cstr_constant", encoding_spec_rb_enc_str_new_cstr_constant, 1);
+ rb_define_method(cls, "rb_enc_str_new", encoding_spec_rb_enc_str_new, 3);
+ rb_define_method(cls, "ENCODING_GET", encoding_spec_ENCODING_GET, 1);
+ rb_define_method(cls, "ENCODING_SET", encoding_spec_ENCODING_SET, 2);
+ rb_define_method(cls, "rb_enc_to_index", encoding_spec_rb_enc_to_index, 1);
+ rb_define_method(cls, "rb_to_encoding", encoding_spec_rb_to_encoding, 1);
+ rb_define_method(cls, "rb_to_encoding_native_store", encoding_spec_rb_to_encoding_native_store, 1);
+ rb_define_method(cls, "rb_to_encoding_native_name", encoding_spec_rb_to_encoding_native_name, 1);
+ rb_define_method(cls, "rb_to_encoding_index", encoding_spec_rb_to_encoding_index, 1);
+ rb_define_method(cls, "rb_enc_nth", encoding_spec_rb_enc_nth, 2);
+ rb_define_method(cls, "rb_enc_codepoint_len", encoding_spec_rb_enc_codepoint_len, 1);
+ rb_define_method(cls, "rb_enc_str_asciionly_p", encoding_spec_rb_enc_str_asciionly_p, 1);
+ rb_define_method(cls, "rb_uv_to_utf8", encoding_spec_rb_uv_to_utf8, 2);
+ rb_define_method(cls, "ONIGENC_MBC_CASE_FOLD", encoding_spec_ONIGENC_MBC_CASE_FOLD, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/enumerator_spec.c b/spec/ruby/optional/capi/ext/enumerator_spec.c
new file mode 100644
index 0000000000..917621c003
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/enumerator_spec.c
@@ -0,0 +1,32 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE enumerator_spec_rb_enumeratorize(int argc, VALUE *argv, VALUE self) {
+ VALUE obj, meth, args;
+ rb_scan_args(argc, argv, "2*", &obj, &meth, &args);
+ return rb_enumeratorize(obj, meth, (int)RARRAY_LEN(args), RARRAY_PTR(args));
+}
+
+VALUE enumerator_spec_size_fn(VALUE obj, VALUE args, VALUE anEnum) {
+ return INT2NUM(7);
+}
+
+VALUE enumerator_spec_rb_enumeratorize_with_size(int argc, VALUE *argv, VALUE self) {
+ VALUE obj, meth, args;
+ rb_scan_args(argc, argv, "2*", &obj, &meth, &args);
+ return rb_enumeratorize_with_size(obj, meth, (int)RARRAY_LEN(args), RARRAY_PTR(args), enumerator_spec_size_fn);
+}
+
+void Init_enumerator_spec(void) {
+ VALUE cls = rb_define_class("CApiEnumeratorSpecs", rb_cObject);
+ rb_define_method(cls, "rb_enumeratorize", enumerator_spec_rb_enumeratorize, -1);
+ rb_define_method(cls, "rb_enumeratorize_with_size", enumerator_spec_rb_enumeratorize_with_size, -1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/exception_spec.c b/spec/ruby/optional/capi/ext/exception_spec.c
new file mode 100644
index 0000000000..e1114aabb8
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/exception_spec.c
@@ -0,0 +1,59 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE exception_spec_rb_errinfo(VALUE self) {
+ return rb_errinfo();
+}
+
+VALUE exception_spec_rb_exc_new(VALUE self, VALUE str) {
+ char *cstr = StringValuePtr(str);
+ return rb_exc_new(rb_eException, cstr, strlen(cstr));
+}
+
+VALUE exception_spec_rb_exc_new2(VALUE self, VALUE str) {
+ char *cstr = StringValuePtr(str);
+ return rb_exc_new2(rb_eException, cstr);
+}
+
+VALUE exception_spec_rb_exc_new3(VALUE self, VALUE str) {
+ return rb_exc_new3(rb_eException, str);
+}
+
+VALUE exception_spec_rb_exc_raise(VALUE self, VALUE exc) {
+ if (self != Qundef) rb_exc_raise(exc);
+ return Qnil;
+}
+
+VALUE exception_spec_rb_set_errinfo(VALUE self, VALUE exc) {
+ rb_set_errinfo(exc);
+ return Qnil;
+}
+
+
+VALUE exception_spec_rb_make_exception(VALUE self, VALUE ary) {
+ int argc = RARRAY_LENINT(ary);
+ VALUE *argv = RARRAY_PTR(ary);
+ return rb_make_exception(argc, argv);
+}
+
+void Init_exception_spec(void) {
+ VALUE cls = rb_define_class("CApiExceptionSpecs", rb_cObject);
+ rb_define_method(cls, "rb_errinfo", exception_spec_rb_errinfo, 0);
+ rb_define_method(cls, "rb_exc_new", exception_spec_rb_exc_new, 1);
+ rb_define_method(cls, "rb_exc_new2", exception_spec_rb_exc_new2, 1);
+ rb_define_method(cls, "rb_exc_new3", exception_spec_rb_exc_new3, 1);
+ rb_define_method(cls, "rb_exc_raise", exception_spec_rb_exc_raise, 1);
+ rb_define_method(cls, "rb_set_errinfo", exception_spec_rb_set_errinfo, 1);
+ rb_define_method(cls, "rb_make_exception", exception_spec_rb_make_exception, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/fiber_spec.c b/spec/ruby/optional/capi/ext/fiber_spec.c
new file mode 100644
index 0000000000..f06a54494e
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/fiber_spec.c
@@ -0,0 +1,69 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE fiber_spec_rb_fiber_current(VALUE self) {
+ return rb_fiber_current();
+}
+
+VALUE fiber_spec_rb_fiber_alive_p(VALUE self, VALUE fiber) {
+ return rb_fiber_alive_p(fiber);
+}
+
+VALUE fiber_spec_rb_fiber_resume(VALUE self, VALUE fiber, VALUE ary) {
+ long argc = RARRAY_LEN(ary);
+ VALUE *argv = (VALUE*) alloca(sizeof(VALUE) * argc);
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ argv[i] = rb_ary_entry(ary, i);
+ }
+
+ return rb_fiber_resume(fiber, (int)argc, argv);
+}
+
+VALUE fiber_spec_rb_fiber_yield(VALUE self, VALUE ary) {
+ long argc = RARRAY_LEN(ary);
+ VALUE *argv = (VALUE*) alloca(sizeof(VALUE) * argc);
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ argv[i] = rb_ary_entry(ary, i);
+ }
+ return rb_fiber_yield((int)argc, argv);
+}
+
+VALUE fiber_spec_rb_fiber_new_function(RB_BLOCK_CALL_FUNC_ARGLIST(args, dummy)) {
+ return rb_funcall(args, rb_intern("inspect"), 0);
+}
+
+VALUE fiber_spec_rb_fiber_new(VALUE self) {
+ return rb_fiber_new(fiber_spec_rb_fiber_new_function, Qnil);
+}
+
+#ifdef RUBY_VERSION_IS_3_1
+VALUE fiber_spec_rb_fiber_raise(int argc, VALUE *argv, VALUE self) {
+ VALUE fiber = argv[0];
+ return rb_fiber_raise(fiber, argc-1, argv+1);
+}
+#endif
+
+void Init_fiber_spec(void) {
+ VALUE cls = rb_define_class("CApiFiberSpecs", rb_cObject);
+ rb_define_method(cls, "rb_fiber_current", fiber_spec_rb_fiber_current, 0);
+ rb_define_method(cls, "rb_fiber_alive_p", fiber_spec_rb_fiber_alive_p, 1);
+ rb_define_method(cls, "rb_fiber_resume", fiber_spec_rb_fiber_resume, 2);
+ rb_define_method(cls, "rb_fiber_yield", fiber_spec_rb_fiber_yield, 1);
+ rb_define_method(cls, "rb_fiber_new", fiber_spec_rb_fiber_new, 0);
+
+#ifdef RUBY_VERSION_IS_3_1
+ rb_define_method(cls, "rb_fiber_raise", fiber_spec_rb_fiber_raise, -1);
+#endif
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/file_spec.c b/spec/ruby/optional/capi/ext/file_spec.c
new file mode 100644
index 0000000000..cd4a653765
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/file_spec.c
@@ -0,0 +1,29 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE file_spec_rb_file_open(VALUE self, VALUE name, VALUE mode) {
+ return rb_file_open(RSTRING_PTR(name), RSTRING_PTR(mode));
+}
+
+VALUE file_spec_rb_file_open_str(VALUE self, VALUE name, VALUE mode) {
+ return rb_file_open_str(name, RSTRING_PTR(mode));
+}
+
+VALUE file_spec_FilePathValue(VALUE self, VALUE obj) {
+ return FilePathValue(obj);
+}
+
+void Init_file_spec(void) {
+ VALUE cls = rb_define_class("CApiFileSpecs", rb_cObject);
+ rb_define_method(cls, "rb_file_open", file_spec_rb_file_open, 2);
+ rb_define_method(cls, "rb_file_open_str", file_spec_rb_file_open_str, 2);
+ rb_define_method(cls, "FilePathValue", file_spec_FilePathValue, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/fixnum_spec.c b/spec/ruby/optional/capi/ext/fixnum_spec.c
new file mode 100644
index 0000000000..7048ce3f13
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/fixnum_spec.c
@@ -0,0 +1,26 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE fixnum_spec_FIX2INT(VALUE self, VALUE value) {
+ int i = FIX2INT(value);
+ return INT2NUM(i);
+}
+
+static VALUE fixnum_spec_FIX2UINT(VALUE self, VALUE value) {
+ unsigned int i = FIX2UINT(value);
+ return UINT2NUM(i);
+}
+
+void Init_fixnum_spec(void) {
+ VALUE cls = rb_define_class("CApiFixnumSpecs", rb_cObject);
+ rb_define_method(cls, "FIX2INT", fixnum_spec_FIX2INT, 1);
+ rb_define_method(cls, "FIX2UINT", fixnum_spec_FIX2UINT, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/float_spec.c b/spec/ruby/optional/capi/ext/float_spec.c
new file mode 100644
index 0000000000..3db05cef8c
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/float_spec.c
@@ -0,0 +1,47 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include <math.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE float_spec_new_zero(VALUE self) {
+ double flt = 0;
+ return rb_float_new(flt);
+}
+
+static VALUE float_spec_new_point_five(VALUE self) {
+ double flt = 0.555;
+ return rb_float_new(flt);
+}
+
+static VALUE float_spec_rb_Float(VALUE self, VALUE float_str) {
+ return rb_Float(float_str);
+}
+
+static VALUE float_spec_RFLOAT_VALUE(VALUE self, VALUE float_h) {
+ return rb_float_new(RFLOAT_VALUE(float_h));
+}
+
+static VALUE float_spec_RB_FLOAT_TYPE_P(VALUE self, VALUE val) {
+ if (RB_FLOAT_TYPE_P(val)) {
+ return Qtrue;
+ } else {
+ return Qfalse;
+ }
+}
+
+void Init_float_spec(void) {
+ VALUE cls = rb_define_class("CApiFloatSpecs", rb_cObject);
+ rb_define_method(cls, "new_zero", float_spec_new_zero, 0);
+ rb_define_method(cls, "new_point_five", float_spec_new_point_five, 0);
+ rb_define_method(cls, "rb_Float", float_spec_rb_Float, 1);
+ rb_define_method(cls, "RFLOAT_VALUE", float_spec_RFLOAT_VALUE, 1);
+ rb_define_method(cls, "RB_FLOAT_TYPE_P", float_spec_RB_FLOAT_TYPE_P, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/gc_spec.c b/spec/ruby/optional/capi/ext/gc_spec.c
new file mode 100644
index 0000000000..b323c2456d
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/gc_spec.c
@@ -0,0 +1,97 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE registered_tagged_value;
+VALUE registered_reference_value;
+VALUE registered_before_rb_gc_register_address;
+VALUE registered_before_rb_global_variable;
+VALUE rb_gc_register_address_outside_init;
+
+static VALUE registered_tagged_address(VALUE self) {
+ return registered_tagged_value;
+}
+
+static VALUE registered_reference_address(VALUE self) {
+ return registered_reference_value;
+}
+
+static VALUE get_registered_before_rb_gc_register_address(VALUE self) {
+ return registered_before_rb_gc_register_address;
+}
+
+static VALUE get_registered_before_rb_global_variable(VALUE self) {
+ return registered_before_rb_global_variable;
+}
+
+static VALUE gc_spec_rb_gc_register_address(VALUE self) {
+ rb_gc_register_address(&rb_gc_register_address_outside_init);
+ rb_gc_register_address_outside_init = rb_str_new_cstr("rb_gc_register_address() outside Init_");
+ return rb_gc_register_address_outside_init;
+}
+
+static VALUE gc_spec_rb_gc_unregister_address(VALUE self) {
+ rb_gc_unregister_address(&rb_gc_register_address_outside_init);
+ return Qnil;
+}
+
+static VALUE gc_spec_rb_gc_enable(VALUE self) {
+ return rb_gc_enable();
+}
+
+static VALUE gc_spec_rb_gc_disable(VALUE self) {
+ return rb_gc_disable();
+}
+
+static VALUE gc_spec_rb_gc(VALUE self) {
+ rb_gc();
+ return Qnil;
+}
+
+static VALUE gc_spec_rb_gc_latest_gc_info(VALUE self, VALUE hash_or_key){
+ return rb_gc_latest_gc_info(hash_or_key);
+}
+
+static VALUE gc_spec_rb_gc_adjust_memory_usage(VALUE self, VALUE diff) {
+ rb_gc_adjust_memory_usage(NUM2SSIZET(diff));
+ return Qnil;
+}
+
+static VALUE gc_spec_rb_gc_register_mark_object(VALUE self, VALUE obj) {
+ rb_gc_register_mark_object(obj);
+ return Qnil;
+}
+
+void Init_gc_spec(void) {
+ VALUE cls = rb_define_class("CApiGCSpecs", rb_cObject);
+
+ rb_gc_register_address(&registered_tagged_value);
+ rb_gc_register_address(&registered_reference_value);
+ rb_gc_register_address(&registered_before_rb_gc_register_address);
+ rb_global_variable(&registered_before_rb_global_variable);
+
+ registered_tagged_value = INT2NUM(10);
+ registered_reference_value = rb_str_new2("Globally registered data");
+ registered_before_rb_gc_register_address = rb_str_new_cstr("registered before rb_gc_register_address()");
+ registered_before_rb_global_variable = rb_str_new_cstr("registered before rb_global_variable()");
+
+ rb_define_method(cls, "registered_tagged_address", registered_tagged_address, 0);
+ rb_define_method(cls, "registered_reference_address", registered_reference_address, 0);
+ rb_define_method(cls, "registered_before_rb_gc_register_address", get_registered_before_rb_gc_register_address, 0);
+ rb_define_method(cls, "registered_before_rb_global_variable", get_registered_before_rb_global_variable, 0);
+ rb_define_method(cls, "rb_gc_register_address", gc_spec_rb_gc_register_address, 0);
+ rb_define_method(cls, "rb_gc_unregister_address", gc_spec_rb_gc_unregister_address, 0);
+ rb_define_method(cls, "rb_gc_enable", gc_spec_rb_gc_enable, 0);
+ rb_define_method(cls, "rb_gc_disable", gc_spec_rb_gc_disable, 0);
+ rb_define_method(cls, "rb_gc", gc_spec_rb_gc, 0);
+ rb_define_method(cls, "rb_gc_adjust_memory_usage", gc_spec_rb_gc_adjust_memory_usage, 1);
+ rb_define_method(cls, "rb_gc_register_mark_object", gc_spec_rb_gc_register_mark_object, 1);
+ rb_define_method(cls, "rb_gc_latest_gc_info", gc_spec_rb_gc_latest_gc_info, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/globals_spec.c b/spec/ruby/optional/capi/ext/globals_spec.c
new file mode 100644
index 0000000000..20dea1a05a
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/globals_spec.c
@@ -0,0 +1,161 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE g_hooked_var;
+
+VALUE var_2x_getter(ID id, VALUE *data) {
+ return *data;
+}
+
+void var_2x_setter(VALUE val, ID id, VALUE *var) {
+ *var = INT2NUM(NUM2INT(val) * 2);
+}
+
+static VALUE sb_define_hooked_variable(VALUE self, VALUE var_name) {
+ rb_define_hooked_variable(StringValuePtr(var_name), &g_hooked_var, var_2x_getter, var_2x_setter);
+ return Qnil;
+}
+
+static VALUE sb_define_hooked_variable_default_accessors(VALUE self, VALUE var_name) {
+ rb_define_hooked_variable(StringValuePtr(var_name), &g_hooked_var, (rb_gvar_getter_t*) NULL, (rb_gvar_setter_t*) NULL);
+ return Qnil;
+}
+
+static VALUE sb_define_hooked_variable_null_var(VALUE self, VALUE var_name) {
+ rb_define_hooked_variable(StringValuePtr(var_name), NULL, (rb_gvar_getter_t*) NULL, (rb_gvar_setter_t*) NULL);
+ return Qnil;
+}
+
+VALUE g_ro_var;
+
+static VALUE sb_define_readonly_variable(VALUE self, VALUE var_name, VALUE val) {
+ g_ro_var = val;
+ rb_define_readonly_variable(StringValuePtr(var_name), &g_ro_var);
+ return Qnil;
+}
+
+VALUE g_var;
+
+static VALUE sb_get_global_value(VALUE self) {
+ return g_var;
+}
+
+static VALUE sb_define_variable(VALUE self, VALUE var_name, VALUE val) {
+ g_var = val;
+ rb_define_variable(StringValuePtr(var_name), &g_var);
+ return Qnil;
+}
+
+long virtual_var_storage;
+
+VALUE incrementing_getter(ID id, VALUE *data) {
+ return LONG2FIX(virtual_var_storage++);
+}
+
+void incrementing_setter(VALUE val, ID id, VALUE *data) {
+ virtual_var_storage = FIX2LONG(val);
+}
+
+static VALUE sb_define_virtual_variable_default_accessors(VALUE self, VALUE name) {
+ rb_define_virtual_variable(StringValuePtr(name), (rb_gvar_getter_t*) NULL, (rb_gvar_setter_t*) NULL);
+ return Qnil;
+}
+
+static VALUE sb_define_virtual_variable_incrementing_accessors(VALUE self, VALUE name) {
+ rb_define_virtual_variable(StringValuePtr(name), incrementing_getter, incrementing_setter);
+ return Qnil;
+}
+
+static VALUE sb_f_global_variables(VALUE self) {
+ return rb_f_global_variables();
+}
+
+static VALUE sb_gv_get(VALUE self, VALUE var) {
+ return rb_gv_get(StringValuePtr(var));
+}
+
+static VALUE sb_gv_set(VALUE self, VALUE var, VALUE val) {
+ return rb_gv_set(StringValuePtr(var), val);
+}
+
+static VALUE global_spec_rb_stdin(VALUE self) {
+ return rb_stdin;
+}
+
+static VALUE global_spec_rb_stdout(VALUE self) {
+ return rb_stdout;
+}
+
+static VALUE global_spec_rb_stderr(VALUE self) {
+ return rb_stderr;
+}
+
+static VALUE global_spec_rb_defout(VALUE self) {
+ return rb_defout;
+}
+
+static VALUE global_spec_rb_fs(VALUE self) {
+ return rb_fs;
+}
+
+static VALUE global_spec_rb_rs(VALUE self) {
+ return rb_rs;
+}
+
+static VALUE global_spec_rb_default_rs(VALUE self) {
+ return rb_default_rs;
+}
+
+static VALUE global_spec_rb_output_rs(VALUE self) {
+ return rb_output_rs;
+}
+
+static VALUE global_spec_rb_output_fs(VALUE self) {
+ return rb_output_fs;
+}
+
+static VALUE global_spec_rb_lastline_set(VALUE self, VALUE line) {
+ rb_lastline_set(line);
+ return Qnil;
+}
+
+static VALUE global_spec_rb_lastline_get(VALUE self) {
+ return rb_lastline_get();
+}
+
+void Init_globals_spec(void) {
+ VALUE cls = rb_define_class("CApiGlobalSpecs", rb_cObject);
+ g_hooked_var = Qnil;
+ rb_define_method(cls, "rb_define_hooked_variable_2x", sb_define_hooked_variable, 1);
+ rb_define_method(cls, "rb_define_hooked_variable_default_accessors", sb_define_hooked_variable_default_accessors, 1);
+ rb_define_method(cls, "rb_define_hooked_variable_null_var", sb_define_hooked_variable_null_var, 1);
+ g_ro_var = Qnil;
+ rb_define_method(cls, "rb_define_readonly_variable", sb_define_readonly_variable, 2);
+ g_var = Qnil;
+ rb_define_method(cls, "rb_define_variable", sb_define_variable, 2);
+ rb_define_method(cls, "rb_define_virtual_variable_default_accessors", sb_define_virtual_variable_default_accessors, 1);
+ rb_define_method(cls, "rb_define_virtual_variable_incrementing_accessors", sb_define_virtual_variable_incrementing_accessors, 1);
+ rb_define_method(cls, "sb_get_global_value", sb_get_global_value, 0);
+ rb_define_method(cls, "rb_f_global_variables", sb_f_global_variables, 0);
+ rb_define_method(cls, "sb_gv_get", sb_gv_get, 1);
+ rb_define_method(cls, "sb_gv_set", sb_gv_set, 2);
+ rb_define_method(cls, "rb_stdin", global_spec_rb_stdin, 0);
+ rb_define_method(cls, "rb_stdout", global_spec_rb_stdout, 0);
+ rb_define_method(cls, "rb_stderr", global_spec_rb_stderr, 0);
+ rb_define_method(cls, "rb_defout", global_spec_rb_defout, 0);
+ rb_define_method(cls, "rb_fs", global_spec_rb_fs, 0);
+ rb_define_method(cls, "rb_rs", global_spec_rb_rs, 0);
+ rb_define_method(cls, "rb_default_rs", global_spec_rb_default_rs, 0);
+ rb_define_method(cls, "rb_output_rs", global_spec_rb_output_rs, 0);
+ rb_define_method(cls, "rb_output_fs", global_spec_rb_output_fs, 0);
+ rb_define_method(cls, "rb_lastline_set", global_spec_rb_lastline_set, 1);
+ rb_define_method(cls, "rb_lastline_get", global_spec_rb_lastline_get, 0);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/hash_spec.c b/spec/ruby/optional/capi/ext/hash_spec.c
new file mode 100644
index 0000000000..7f38708915
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/hash_spec.c
@@ -0,0 +1,160 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE hash_spec_rb_hash(VALUE self, VALUE hash) {
+ return rb_hash(hash);
+}
+
+VALUE hash_spec_rb_Hash(VALUE self, VALUE val) {
+ return rb_Hash(val);
+}
+
+VALUE hash_spec_rb_hash_dup(VALUE self, VALUE hash) {
+ return rb_hash_dup(hash);
+}
+
+VALUE hash_spec_rb_hash_fetch(VALUE self, VALUE hash, VALUE key) {
+ return rb_hash_fetch(hash, key);
+}
+
+VALUE hash_spec_rb_hash_freeze(VALUE self, VALUE hash) {
+ return rb_hash_freeze(hash);
+}
+
+VALUE hash_spec_rb_hash_aref(VALUE self, VALUE hash, VALUE key) {
+ return rb_hash_aref(hash, key);
+}
+
+VALUE hash_spec_rb_hash_aref_nil(VALUE self, VALUE hash, VALUE key) {
+ VALUE ret = rb_hash_aref(hash, key);
+ return NIL_P(ret) ? Qtrue : Qfalse;
+}
+
+VALUE hash_spec_rb_hash_aset(VALUE self, VALUE hash, VALUE key, VALUE val) {
+ return rb_hash_aset(hash, key, val);
+}
+
+VALUE hash_spec_rb_hash_clear(VALUE self, VALUE hash) {
+ return rb_hash_clear(hash);
+}
+
+VALUE hash_spec_rb_hash_delete(VALUE self, VALUE hash, VALUE key) {
+ return rb_hash_delete(hash, key);
+}
+
+VALUE hash_spec_rb_hash_delete_if(VALUE self, VALUE hash) {
+ return rb_hash_delete_if(hash);
+}
+
+static int foreach_i(VALUE key, VALUE val, VALUE other) {
+ rb_hash_aset(other, key, val);
+ return 0; /* ST_CONTINUE; */
+}
+
+static int foreach_stop_i(VALUE key, VALUE val, VALUE other) {
+ rb_hash_aset(other, key, val);
+ return 1; /* ST_STOP; */
+}
+
+static int foreach_delete_i(VALUE key, VALUE val, VALUE other) {
+ rb_hash_aset(other, key, val);
+ return 2; /* ST_DELETE; */
+}
+
+VALUE hash_spec_rb_hash_foreach(VALUE self, VALUE hsh) {
+ VALUE other = rb_hash_new();
+ rb_hash_foreach(hsh, foreach_i, other);
+ return other;
+}
+
+VALUE hash_spec_rb_hash_foreach_stop(VALUE self, VALUE hsh) {
+ VALUE other = rb_hash_new();
+ rb_hash_foreach(hsh, foreach_stop_i, other);
+ return other;
+}
+
+VALUE hash_spec_rb_hash_foreach_delete(VALUE self, VALUE hsh) {
+ VALUE other = rb_hash_new();
+ rb_hash_foreach(hsh, foreach_delete_i, other);
+ return other;
+}
+
+VALUE hash_spec_rb_hash_lookup(VALUE self, VALUE hash, VALUE key) {
+ return rb_hash_lookup(hash, key);
+}
+
+VALUE hash_spec_rb_hash_lookup_nil(VALUE self, VALUE hash, VALUE key) {
+ VALUE ret = rb_hash_lookup(hash, key);
+ return ret == Qnil ? Qtrue : Qfalse;
+}
+
+VALUE hash_spec_rb_hash_lookup2(VALUE self, VALUE hash, VALUE key, VALUE def) {
+ return rb_hash_lookup2(hash, key, def);
+}
+
+VALUE hash_spec_rb_hash_lookup2_default_undef(VALUE self, VALUE hash, VALUE key) {
+ VALUE ret = rb_hash_lookup2(hash, key, Qundef);
+ return ret == Qundef ? Qtrue : Qfalse;
+}
+
+VALUE hash_spec_rb_hash_new(VALUE self) {
+ return rb_hash_new();
+}
+
+VALUE rb_ident_hash_new(void); /* internal.h, used in ripper */
+
+VALUE hash_spec_rb_ident_hash_new(VALUE self) {
+ return rb_ident_hash_new();
+}
+
+VALUE hash_spec_rb_hash_size(VALUE self, VALUE hash) {
+ return rb_hash_size(hash);
+}
+
+VALUE hash_spec_rb_hash_set_ifnone(VALUE self, VALUE hash, VALUE def) {
+ return rb_hash_set_ifnone(hash, def);
+}
+
+VALUE hash_spec_compute_a_hash_code(VALUE self, VALUE seed) {
+ int int_seed = FIX2INT(seed);
+ st_index_t h = rb_hash_start(int_seed);
+ h = rb_hash_uint32(h, 540u);
+ h = rb_hash_uint32(h, 340u);
+ h = rb_hash_end(h);
+ return ULONG2NUM(h);
+}
+
+void Init_hash_spec(void) {
+ VALUE cls = rb_define_class("CApiHashSpecs", rb_cObject);
+ rb_define_method(cls, "rb_hash", hash_spec_rb_hash, 1);
+ rb_define_method(cls, "rb_Hash", hash_spec_rb_Hash, 1);
+ rb_define_method(cls, "rb_hash_dup", hash_spec_rb_hash_dup, 1);
+ rb_define_method(cls, "rb_hash_freeze", hash_spec_rb_hash_freeze, 1);
+ rb_define_method(cls, "rb_hash_aref", hash_spec_rb_hash_aref, 2);
+ rb_define_method(cls, "rb_hash_aref_nil", hash_spec_rb_hash_aref_nil, 2);
+ rb_define_method(cls, "rb_hash_aset", hash_spec_rb_hash_aset, 3);
+ rb_define_method(cls, "rb_hash_clear", hash_spec_rb_hash_clear, 1);
+ rb_define_method(cls, "rb_hash_delete", hash_spec_rb_hash_delete, 2);
+ rb_define_method(cls, "rb_hash_delete_if", hash_spec_rb_hash_delete_if, 1);
+ rb_define_method(cls, "rb_hash_fetch", hash_spec_rb_hash_fetch, 2);
+ rb_define_method(cls, "rb_hash_foreach", hash_spec_rb_hash_foreach, 1);
+ rb_define_method(cls, "rb_hash_foreach_stop", hash_spec_rb_hash_foreach_stop, 1);
+ rb_define_method(cls, "rb_hash_foreach_delete", hash_spec_rb_hash_foreach_delete, 1);
+ rb_define_method(cls, "rb_hash_lookup_nil", hash_spec_rb_hash_lookup_nil, 2);
+ rb_define_method(cls, "rb_hash_lookup", hash_spec_rb_hash_lookup, 2);
+ rb_define_method(cls, "rb_hash_lookup2", hash_spec_rb_hash_lookup2, 3);
+ rb_define_method(cls, "rb_hash_lookup2_default_undef", hash_spec_rb_hash_lookup2_default_undef, 2);
+ rb_define_method(cls, "rb_hash_new", hash_spec_rb_hash_new, 0);
+ rb_define_method(cls, "rb_ident_hash_new", hash_spec_rb_ident_hash_new, 0);
+ rb_define_method(cls, "rb_hash_size", hash_spec_rb_hash_size, 1);
+ rb_define_method(cls, "rb_hash_set_ifnone", hash_spec_rb_hash_set_ifnone, 2);
+ rb_define_method(cls, "compute_a_hash_code", hash_spec_compute_a_hash_code, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/integer_spec.c b/spec/ruby/optional/capi/ext/integer_spec.c
new file mode 100644
index 0000000000..16cd95f111
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/integer_spec.c
@@ -0,0 +1,41 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE integer_spec_rb_integer_pack(VALUE self, VALUE value,
+ VALUE words, VALUE numwords, VALUE wordsize, VALUE nails, VALUE flags)
+{
+ int result = rb_integer_pack(value, (void*)RSTRING_PTR(words), FIX2INT(numwords),
+ FIX2INT(wordsize), FIX2INT(nails), FIX2INT(flags));
+ return INT2FIX(result);
+}
+
+RUBY_EXTERN VALUE rb_int_positive_pow(long x, unsigned long y); /* internal.h, used in ripper */
+
+static VALUE integer_spec_rb_int_positive_pow(VALUE self, VALUE a, VALUE b){
+ return rb_int_positive_pow(FIX2INT(a), FIX2INT(b));
+}
+
+void Init_integer_spec(void) {
+ VALUE cls = rb_define_class("CApiIntegerSpecs", rb_cObject);
+ rb_define_const(cls, "MSWORD", INT2NUM(INTEGER_PACK_MSWORD_FIRST));
+ rb_define_const(cls, "LSWORD", INT2NUM(INTEGER_PACK_LSWORD_FIRST));
+ rb_define_const(cls, "MSBYTE", INT2NUM(INTEGER_PACK_MSBYTE_FIRST));
+ rb_define_const(cls, "LSBYTE", INT2NUM(INTEGER_PACK_LSBYTE_FIRST));
+ rb_define_const(cls, "NATIVE", INT2NUM(INTEGER_PACK_NATIVE_BYTE_ORDER));
+ rb_define_const(cls, "PACK_2COMP", INT2NUM(INTEGER_PACK_2COMP));
+ rb_define_const(cls, "LITTLE_ENDIAN", INT2NUM(INTEGER_PACK_LITTLE_ENDIAN));
+ rb_define_const(cls, "BIG_ENDIAN", INT2NUM(INTEGER_PACK_BIG_ENDIAN));
+ rb_define_const(cls, "FORCE_BIGNUM", INT2NUM(INTEGER_PACK_FORCE_BIGNUM));
+ rb_define_const(cls, "NEGATIVE", INT2NUM(INTEGER_PACK_NEGATIVE));
+
+ rb_define_method(cls, "rb_integer_pack", integer_spec_rb_integer_pack, 6);
+ rb_define_method(cls, "rb_int_positive_pow", integer_spec_rb_int_positive_pow, 2);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/io_spec.c b/spec/ruby/optional/capi/ext/io_spec.c
new file mode 100644
index 0000000000..f257cef554
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/io_spec.c
@@ -0,0 +1,312 @@
+#include "ruby.h"
+#include "rubyspec.h"
+#include "ruby/io.h"
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static int set_non_blocking(int fd) {
+#if defined(O_NONBLOCK) && defined(F_GETFL)
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (flags == -1)
+ flags = 0;
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+#elif defined(FIOBIO)
+ int flags = 1;
+ return ioctl(fd, FIOBIO, &flags);
+#else
+#define SET_NON_BLOCKING_FAILS_ALWAYS 1
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+static int io_spec_get_fd(VALUE io) {
+ rb_io_t* fp;
+ GetOpenFile(io, fp);
+ return fp->fd;
+}
+
+VALUE io_spec_GetOpenFile_fd(VALUE self, VALUE io) {
+ return INT2NUM(io_spec_get_fd(io));
+}
+
+VALUE io_spec_rb_io_addstr(VALUE self, VALUE io, VALUE str) {
+ return rb_io_addstr(io, str);
+}
+
+VALUE io_spec_rb_io_printf(VALUE self, VALUE io, VALUE ary) {
+ long argc = RARRAY_LEN(ary);
+ VALUE *argv = (VALUE*) alloca(sizeof(VALUE) * argc);
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ argv[i] = rb_ary_entry(ary, i);
+ }
+
+ return rb_io_printf((int)argc, argv, io);
+}
+
+VALUE io_spec_rb_io_print(VALUE self, VALUE io, VALUE ary) {
+ long argc = RARRAY_LEN(ary);
+ VALUE *argv = (VALUE*) alloca(sizeof(VALUE) * argc);
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ argv[i] = rb_ary_entry(ary, i);
+ }
+
+ return rb_io_print((int)argc, argv, io);
+}
+
+VALUE io_spec_rb_io_puts(VALUE self, VALUE io, VALUE ary) {
+ long argc = RARRAY_LEN(ary);
+ VALUE *argv = (VALUE*) alloca(sizeof(VALUE) * argc);
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ argv[i] = rb_ary_entry(ary, i);
+ }
+
+ return rb_io_puts((int)argc, argv, io);
+}
+
+VALUE io_spec_rb_io_write(VALUE self, VALUE io, VALUE str) {
+ return rb_io_write(io, str);
+}
+
+VALUE io_spec_rb_io_check_io(VALUE self, VALUE io) {
+ return rb_io_check_io(io);
+}
+
+VALUE io_spec_rb_io_check_readable(VALUE self, VALUE io) {
+ rb_io_t* fp;
+ GetOpenFile(io, fp);
+ rb_io_check_readable(fp);
+ return Qnil;
+}
+
+VALUE io_spec_rb_io_check_writable(VALUE self, VALUE io) {
+ rb_io_t* fp;
+ GetOpenFile(io, fp);
+ rb_io_check_writable(fp);
+ return Qnil;
+}
+
+VALUE io_spec_rb_io_check_closed(VALUE self, VALUE io) {
+ rb_io_t* fp;
+ GetOpenFile(io, fp);
+ rb_io_check_closed(fp);
+ return Qnil;
+}
+
+VALUE io_spec_rb_io_taint_check(VALUE self, VALUE io) {
+ /*rb_io_t* fp;
+ GetOpenFile(io, fp);*/
+ rb_io_taint_check(io);
+ return io;
+}
+
+#define RB_IO_WAIT_READABLE_BUF 13
+
+#ifdef SET_NON_BLOCKING_FAILS_ALWAYS
+NORETURN(VALUE io_spec_rb_io_wait_readable(VALUE self, VALUE io, VALUE read_p));
+#endif
+
+VALUE io_spec_rb_io_wait_readable(VALUE self, VALUE io, VALUE read_p) {
+ int fd = io_spec_get_fd(io);
+#ifndef SET_NON_BLOCKING_FAILS_ALWAYS
+ char buf[RB_IO_WAIT_READABLE_BUF];
+ int ret, saved_errno;
+#endif
+
+ if (set_non_blocking(fd) == -1)
+ rb_sys_fail("set_non_blocking failed");
+
+#ifndef SET_NON_BLOCKING_FAILS_ALWAYS
+ if(RTEST(read_p)) {
+ if (read(fd, buf, RB_IO_WAIT_READABLE_BUF) != -1) {
+ return Qnil;
+ }
+ saved_errno = errno;
+ rb_ivar_set(self, rb_intern("@write_data"), Qtrue);
+ errno = saved_errno;
+ }
+
+ ret = rb_io_wait_readable(fd);
+
+ if(RTEST(read_p)) {
+ ssize_t r = read(fd, buf, RB_IO_WAIT_READABLE_BUF);
+ if (r != RB_IO_WAIT_READABLE_BUF) {
+ perror("read");
+ return SSIZET2NUM(r);
+ }
+ rb_ivar_set(self, rb_intern("@read_data"),
+ rb_str_new(buf, RB_IO_WAIT_READABLE_BUF));
+ }
+
+ return ret ? Qtrue : Qfalse;
+#else
+ UNREACHABLE;
+#endif
+}
+
+VALUE io_spec_rb_io_wait_writable(VALUE self, VALUE io) {
+ int ret = rb_io_wait_writable(io_spec_get_fd(io));
+ return ret ? Qtrue : Qfalse;
+}
+
+VALUE io_spec_rb_thread_wait_fd(VALUE self, VALUE io) {
+ rb_thread_wait_fd(io_spec_get_fd(io));
+ return Qnil;
+}
+
+VALUE io_spec_rb_wait_for_single_fd(VALUE self, VALUE io, VALUE events, VALUE secs, VALUE usecs) {
+ int fd = io_spec_get_fd(io);
+ struct timeval tv;
+ if (!NIL_P(secs)) {
+ tv.tv_sec = FIX2INT(secs);
+ tv.tv_usec = FIX2INT(usecs);
+ }
+ return INT2FIX(rb_wait_for_single_fd(fd, FIX2INT(events), NIL_P(secs) ? NULL : &tv));
+}
+
+VALUE io_spec_rb_thread_fd_writable(VALUE self, VALUE io) {
+ rb_thread_fd_writable(io_spec_get_fd(io));
+ return Qnil;
+}
+
+VALUE io_spec_rb_thread_fd_select_read(VALUE self, VALUE io) {
+ int fd = io_spec_get_fd(io);
+
+ rb_fdset_t fds;
+ rb_fd_init(&fds);
+ rb_fd_set(fd, &fds);
+
+ int r = rb_thread_fd_select(fd + 1, &fds, NULL, NULL, NULL);
+ rb_fd_term(&fds);
+ return INT2FIX(r);
+}
+
+VALUE io_spec_rb_thread_fd_select_write(VALUE self, VALUE io) {
+ int fd = io_spec_get_fd(io);
+
+ rb_fdset_t fds;
+ rb_fd_init(&fds);
+ rb_fd_set(fd, &fds);
+
+ int r = rb_thread_fd_select(fd + 1, NULL, &fds, NULL, NULL);
+ rb_fd_term(&fds);
+ return INT2FIX(r);
+}
+
+VALUE io_spec_rb_thread_fd_select_timeout(VALUE self, VALUE io) {
+ int fd = io_spec_get_fd(io);
+
+ struct timeval timeout;
+ timeout.tv_sec = 10;
+ timeout.tv_usec = 20;
+
+ rb_fdset_t fds;
+ rb_fd_init(&fds);
+ rb_fd_set(fd, &fds);
+
+ int r = rb_thread_fd_select(fd + 1, NULL, &fds, NULL, &timeout);
+ rb_fd_term(&fds);
+ return INT2FIX(r);
+}
+
+VALUE io_spec_rb_io_binmode(VALUE self, VALUE io) {
+ return rb_io_binmode(io);
+}
+
+VALUE io_spec_rb_fd_fix_cloexec(VALUE self, VALUE io) {
+ rb_fd_fix_cloexec(io_spec_get_fd(io));
+ return Qnil;
+}
+
+VALUE io_spec_rb_cloexec_open(VALUE self, VALUE path, VALUE flags, VALUE mode) {
+ const char *pathname = StringValuePtr(path);
+ int fd = rb_cloexec_open(pathname, FIX2INT(flags), FIX2INT(mode));
+ return rb_funcall(rb_cIO, rb_intern("for_fd"), 1, INT2FIX(fd));
+}
+
+VALUE io_spec_rb_io_close(VALUE self, VALUE io) {
+ return rb_io_close(io);
+}
+
+VALUE io_spec_rb_io_set_nonblock(VALUE self, VALUE io) {
+ rb_io_t* fp;
+#ifdef F_GETFL
+ int flags;
+#endif
+ GetOpenFile(io, fp);
+ rb_io_set_nonblock(fp);
+#ifdef F_GETFL
+ flags = fcntl(fp->fd, F_GETFL, 0);
+ return flags & O_NONBLOCK ? Qtrue : Qfalse;
+#else
+ return Qfalse;
+#endif
+}
+
+/*
+ * this is needed to ensure rb_io_wait_*able functions behave
+ * predictably because errno may be set to unexpected values
+ * otherwise.
+ */
+static VALUE io_spec_errno_set(VALUE self, VALUE val) {
+ int e = NUM2INT(val);
+ errno = e;
+ return val;
+}
+
+VALUE io_spec_mode_sync_flag(VALUE self, VALUE io) {
+ rb_io_t *fp;
+ GetOpenFile(io, fp);
+ if (fp->mode & FMODE_SYNC) {
+ return Qtrue;
+ } else {
+ return Qfalse;
+ }
+}
+
+void Init_io_spec(void) {
+ VALUE cls = rb_define_class("CApiIOSpecs", rb_cObject);
+ rb_define_method(cls, "GetOpenFile_fd", io_spec_GetOpenFile_fd, 1);
+ rb_define_method(cls, "rb_io_addstr", io_spec_rb_io_addstr, 2);
+ rb_define_method(cls, "rb_io_printf", io_spec_rb_io_printf, 2);
+ rb_define_method(cls, "rb_io_print", io_spec_rb_io_print, 2);
+ rb_define_method(cls, "rb_io_puts", io_spec_rb_io_puts, 2);
+ rb_define_method(cls, "rb_io_write", io_spec_rb_io_write, 2);
+ rb_define_method(cls, "rb_io_close", io_spec_rb_io_close, 1);
+ rb_define_method(cls, "rb_io_check_io", io_spec_rb_io_check_io, 1);
+ rb_define_method(cls, "rb_io_check_readable", io_spec_rb_io_check_readable, 1);
+ rb_define_method(cls, "rb_io_check_writable", io_spec_rb_io_check_writable, 1);
+ rb_define_method(cls, "rb_io_check_closed", io_spec_rb_io_check_closed, 1);
+ rb_define_method(cls, "rb_io_set_nonblock", io_spec_rb_io_set_nonblock, 1);
+ rb_define_method(cls, "rb_io_taint_check", io_spec_rb_io_taint_check, 1);
+ rb_define_method(cls, "rb_io_wait_readable", io_spec_rb_io_wait_readable, 2);
+ rb_define_method(cls, "rb_io_wait_writable", io_spec_rb_io_wait_writable, 1);
+ rb_define_method(cls, "rb_thread_wait_fd", io_spec_rb_thread_wait_fd, 1);
+ rb_define_method(cls, "rb_thread_fd_writable", io_spec_rb_thread_fd_writable, 1);
+ rb_define_method(cls, "rb_thread_fd_select_read", io_spec_rb_thread_fd_select_read, 1);
+ rb_define_method(cls, "rb_thread_fd_select_write", io_spec_rb_thread_fd_select_write, 1);
+ rb_define_method(cls, "rb_thread_fd_select_timeout", io_spec_rb_thread_fd_select_timeout, 1);
+ rb_define_method(cls, "rb_wait_for_single_fd", io_spec_rb_wait_for_single_fd, 4);
+ rb_define_method(cls, "rb_io_binmode", io_spec_rb_io_binmode, 1);
+ rb_define_method(cls, "rb_fd_fix_cloexec", io_spec_rb_fd_fix_cloexec, 1);
+ rb_define_method(cls, "rb_cloexec_open", io_spec_rb_cloexec_open, 3);
+ rb_define_method(cls, "errno=", io_spec_errno_set, 1);
+ rb_define_method(cls, "rb_io_mode_sync_flag", io_spec_mode_sync_flag, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/kernel_spec.c b/spec/ruby/optional/capi/ext/kernel_spec.c
new file mode 100644
index 0000000000..e194ba8fde
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/kernel_spec.c
@@ -0,0 +1,405 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include <errno.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE kernel_spec_call_proc(VALUE arg_array) {
+ VALUE arg = rb_ary_pop(arg_array);
+ VALUE proc = rb_ary_pop(arg_array);
+ return rb_funcall(proc, rb_intern("call"), 1, arg);
+}
+
+VALUE kernel_spec_call_proc_raise(VALUE arg_array, VALUE raised_exc) {
+ return kernel_spec_call_proc(arg_array);
+}
+
+static VALUE kernel_spec_rb_block_given_p(VALUE self) {
+ return rb_block_given_p() ? Qtrue : Qfalse;
+}
+
+VALUE kernel_spec_rb_need_block(VALUE self) {
+ rb_need_block();
+ return Qnil;
+}
+
+VALUE kernel_spec_rb_block_proc(VALUE self) {
+ return rb_block_proc();
+}
+
+VALUE kernel_spec_rb_block_lambda(VALUE self) {
+ return rb_block_lambda();
+}
+
+VALUE block_call_inject(RB_BLOCK_CALL_FUNC_ARGLIST(yield_value, data2)) {
+ /* yield_value yields the first block argument */
+ VALUE elem = yield_value;
+ VALUE elem_incr = INT2FIX(FIX2INT(elem) + 1);
+ return elem_incr;
+}
+
+VALUE kernel_spec_rb_block_call(VALUE self, VALUE ary) {
+ return rb_block_call(ary, rb_intern("map"), 0, NULL, block_call_inject, Qnil);
+}
+
+VALUE block_call_inject_multi_arg(RB_BLOCK_CALL_FUNC_ARGLIST(yield_value, data2)) {
+ /* yield_value yields the first block argument */
+ VALUE sum = yield_value;
+ VALUE elem = argv[1];
+
+ return INT2FIX(FIX2INT(sum) + FIX2INT(elem));
+}
+
+VALUE kernel_spec_rb_block_call_multi_arg(VALUE self, VALUE ary) {
+ VALUE method_args[1];
+ method_args[0] = INT2FIX(0);
+ return rb_block_call(ary, rb_intern("inject"), 1, method_args, block_call_inject_multi_arg, Qnil);
+}
+
+static VALUE return_extra_data(RB_BLOCK_CALL_FUNC_ARGLIST(yield_value, extra_data)) {
+ return extra_data;
+}
+
+VALUE rb_block_call_extra_data(VALUE self, VALUE object) {
+ return rb_block_call(object, rb_intern("instance_exec"), 0, NULL, return_extra_data, object);
+}
+
+VALUE kernel_spec_rb_block_call_no_func(VALUE self, VALUE ary) {
+ return rb_block_call(ary, rb_intern("map"), 0, NULL, (rb_block_call_func_t)NULL, Qnil);
+}
+
+
+VALUE kernel_spec_rb_frame_this_func(VALUE self) {
+ return ID2SYM(rb_frame_this_func());
+}
+
+VALUE kernel_spec_rb_ensure(VALUE self, VALUE main_proc, VALUE arg,
+ VALUE ensure_proc, VALUE arg2) {
+ VALUE main_array, ensure_array;
+
+ main_array = rb_ary_new();
+ rb_ary_push(main_array, main_proc);
+ rb_ary_push(main_array, arg);
+
+ ensure_array = rb_ary_new();
+ rb_ary_push(ensure_array, ensure_proc);
+ rb_ary_push(ensure_array, arg2);
+
+ return rb_ensure(kernel_spec_call_proc, main_array,
+ kernel_spec_call_proc, ensure_array);
+}
+
+VALUE kernel_spec_call_proc_with_catch(RB_BLOCK_CALL_FUNC_ARGLIST(arg, data)) {
+ return rb_funcall(data, rb_intern("call"), 0);
+}
+
+VALUE kernel_spec_rb_catch(VALUE self, VALUE sym, VALUE main_proc) {
+ return rb_catch(StringValuePtr(sym), kernel_spec_call_proc_with_catch, main_proc);
+}
+
+VALUE kernel_spec_call_proc_with_catch_obj(RB_BLOCK_CALL_FUNC_ARGLIST(arg, data)) {
+ return rb_funcall(data, rb_intern("call"), 0);
+}
+
+VALUE kernel_spec_rb_catch_obj(VALUE self, VALUE obj, VALUE main_proc) {
+ return rb_catch_obj(obj, kernel_spec_call_proc_with_catch_obj, main_proc);
+}
+
+VALUE kernel_spec_rb_eval_string(VALUE self, VALUE str) {
+ return rb_eval_string(RSTRING_PTR(str));
+}
+
+VALUE kernel_spec_rb_eval_cmd_kw(VALUE self, VALUE cmd, VALUE args, VALUE kw_splat) {
+ return rb_eval_cmd_kw(cmd, args, NUM2INT(kw_splat));
+}
+
+VALUE kernel_spec_rb_raise(VALUE self, VALUE hash) {
+ rb_hash_aset(hash, ID2SYM(rb_intern("stage")), ID2SYM(rb_intern("before")));
+ if (self != Qundef)
+ rb_raise(rb_eTypeError, "Wrong argument type %s (expected %s)", "Integer", "String");
+ rb_hash_aset(hash, ID2SYM(rb_intern("stage")), ID2SYM(rb_intern("after")));
+ return Qnil;
+}
+
+VALUE kernel_spec_rb_throw(VALUE self, VALUE result) {
+ if (self != Qundef) rb_throw("foo", result);
+ return ID2SYM(rb_intern("rb_throw_failed"));
+}
+
+VALUE kernel_spec_rb_throw_obj(VALUE self, VALUE obj, VALUE result) {
+ if (self != Qundef) rb_throw_obj(obj, result);
+ return ID2SYM(rb_intern("rb_throw_failed"));
+}
+
+VALUE kernel_spec_call_proc_with_raised_exc(VALUE arg_array, VALUE raised_exc) {
+ VALUE argv[2];
+ int argc;
+
+ VALUE arg = rb_ary_pop(arg_array);
+ VALUE proc = rb_ary_pop(arg_array);
+
+ argv[0] = arg;
+ argv[1] = raised_exc;
+
+ argc = 2;
+
+ return rb_funcallv(proc, rb_intern("call"), argc, argv);
+}
+
+VALUE kernel_spec_rb_rescue(VALUE self, VALUE main_proc, VALUE arg,
+ VALUE raise_proc, VALUE arg2) {
+ VALUE main_array, raise_array;
+
+ main_array = rb_ary_new();
+ rb_ary_push(main_array, main_proc);
+ rb_ary_push(main_array, arg);
+
+ if (raise_proc == Qnil) {
+ return rb_rescue(kernel_spec_call_proc, main_array, NULL, arg2);
+ }
+
+ raise_array = rb_ary_new();
+ rb_ary_push(raise_array, raise_proc);
+ rb_ary_push(raise_array, arg2);
+
+ return rb_rescue(kernel_spec_call_proc, main_array,
+ kernel_spec_call_proc_with_raised_exc, raise_array);
+}
+
+VALUE kernel_spec_rb_rescue2(int argc, VALUE *args, VALUE self) {
+ VALUE main_array, raise_array;
+
+ main_array = rb_ary_new();
+ rb_ary_push(main_array, args[0]);
+ rb_ary_push(main_array, args[1]);
+
+ raise_array = rb_ary_new();
+ rb_ary_push(raise_array, args[2]);
+ rb_ary_push(raise_array, args[3]);
+
+ return rb_rescue2(kernel_spec_call_proc, main_array,
+ kernel_spec_call_proc_raise, raise_array, args[4], args[5], (VALUE)0);
+}
+
+static VALUE kernel_spec_rb_protect_yield(VALUE self, VALUE obj, VALUE ary) {
+ int status = 0;
+ VALUE res = rb_protect(rb_yield, obj, &status);
+ rb_ary_store(ary, 0, INT2NUM(23));
+ rb_ary_store(ary, 1, res);
+ if (status) {
+ rb_jump_tag(status);
+ }
+ return res;
+}
+
+static VALUE kernel_spec_rb_protect_errinfo(VALUE self, VALUE obj, VALUE ary) {
+ int status = 0;
+ VALUE res = rb_protect(rb_yield, obj, &status);
+ rb_ary_store(ary, 0, INT2NUM(23));
+ rb_ary_store(ary, 1, res);
+ return rb_errinfo();
+}
+
+static VALUE kernel_spec_rb_protect_null_status(VALUE self, VALUE obj) {
+ return rb_protect(rb_yield, obj, NULL);
+}
+
+static VALUE kernel_spec_rb_eval_string_protect(VALUE self, VALUE str, VALUE ary) {
+ int status = 0;
+ VALUE res = rb_eval_string_protect(RSTRING_PTR(str), &status);
+ rb_ary_store(ary, 0, INT2NUM(23));
+ rb_ary_store(ary, 1, res);
+ if (status) {
+ rb_jump_tag(status);
+ }
+ return res;
+}
+
+VALUE kernel_spec_rb_sys_fail(VALUE self, VALUE msg) {
+ errno = 1;
+ if(msg == Qnil) {
+ rb_sys_fail(0);
+ } else if (self != Qundef) {
+ rb_sys_fail(StringValuePtr(msg));
+ }
+ return Qnil;
+}
+
+VALUE kernel_spec_rb_syserr_fail(VALUE self, VALUE err, VALUE msg) {
+ if(msg == Qnil) {
+ rb_syserr_fail(NUM2INT(err), NULL);
+ } else if (self != Qundef) {
+ rb_syserr_fail(NUM2INT(err), StringValuePtr(msg));
+ }
+ return Qnil;
+}
+
+VALUE kernel_spec_rb_warn(VALUE self, VALUE msg) {
+ rb_warn("%s", StringValuePtr(msg));
+ return Qnil;
+}
+
+static VALUE kernel_spec_rb_yield(VALUE self, VALUE obj) {
+ return rb_yield(obj);
+}
+
+static VALUE kernel_spec_rb_yield_each(int argc, VALUE *args, VALUE self) {
+ int i;
+ for(i = 0; i < 4; i++) {
+ rb_yield(INT2FIX(i));
+ }
+ return INT2FIX(4);
+}
+
+static VALUE kernel_spec_rb_yield_define_each(VALUE self, VALUE cls) {
+ rb_define_method(cls, "each", kernel_spec_rb_yield_each, -1);
+ return Qnil;
+}
+
+static int kernel_cb(const void *a, const void *b) {
+ rb_yield(Qtrue);
+ return 0;
+}
+
+static VALUE kernel_indirected(int (*compar)(const void *, const void *)) {
+ int bob[] = { 1, 1, 2, 3, 5, 8, 13 };
+ qsort(bob, 7, sizeof(int), compar);
+ return Qfalse;
+}
+
+static VALUE kernel_spec_rb_yield_indirected(VALUE self, VALUE obj) {
+ return kernel_indirected(kernel_cb);
+}
+
+static VALUE kernel_spec_rb_yield_splat(VALUE self, VALUE ary) {
+ return rb_yield_splat(ary);
+}
+
+static VALUE kernel_spec_rb_yield_values(VALUE self, VALUE obj1, VALUE obj2) {
+ return rb_yield_values(2, obj1, obj2);
+}
+
+static VALUE kernel_spec_rb_yield_values2(VALUE self, VALUE ary) {
+ long len = RARRAY_LEN(ary);
+ VALUE *args = (VALUE*)alloca(sizeof(VALUE) * len);
+ for (int i = 0; i < len; i++) {
+ args[i] = rb_ary_entry(ary, i);
+ }
+ return rb_yield_values2((int)len, args);
+}
+
+static VALUE do_rec(VALUE obj, VALUE arg, int is_rec) {
+ if(is_rec) {
+ return obj;
+ } else if(arg == Qtrue) {
+ return rb_exec_recursive(do_rec, obj, Qnil);
+ } else {
+ return Qnil;
+ }
+}
+
+static VALUE kernel_spec_rb_exec_recursive(VALUE self, VALUE obj) {
+ return rb_exec_recursive(do_rec, obj, Qtrue);
+}
+
+static void write_io(VALUE io) {
+ rb_funcall(io, rb_intern("write"), 1, rb_str_new2("in write_io"));
+}
+
+static VALUE kernel_spec_rb_set_end_proc(VALUE self, VALUE io) {
+ rb_set_end_proc(write_io, io);
+ return Qnil;
+}
+
+static VALUE kernel_spec_rb_f_sprintf(VALUE self, VALUE ary) {
+ return rb_f_sprintf((int)RARRAY_LEN(ary), RARRAY_PTR(ary));
+}
+
+static VALUE kernel_spec_rb_make_backtrace(VALUE self) {
+ return rb_make_backtrace();
+}
+
+static VALUE kernel_spec_rb_funcallv(VALUE self, VALUE obj, VALUE method, VALUE args) {
+ return rb_funcallv(obj, SYM2ID(method), RARRAY_LENINT(args), RARRAY_PTR(args));
+}
+
+#ifdef RUBY_VERSION_IS_3_0
+static VALUE kernel_spec_rb_funcallv_kw(VALUE self, VALUE obj, VALUE method, VALUE args) {
+ return rb_funcallv_kw(obj, SYM2ID(method), RARRAY_LENINT(args), RARRAY_PTR(args), RB_PASS_KEYWORDS);
+}
+
+static VALUE kernel_spec_rb_keyword_given_p(int argc, VALUE *args, VALUE self) {
+ return rb_keyword_given_p() ? Qtrue : Qfalse;
+}
+#endif
+
+static VALUE kernel_spec_rb_funcallv_public(VALUE self, VALUE obj, VALUE method) {
+ return rb_funcallv_public(obj, SYM2ID(method), 0, NULL);
+}
+
+static VALUE kernel_spec_rb_funcall_with_block(VALUE self, VALUE obj, VALUE method, VALUE block) {
+ return rb_funcall_with_block(obj, SYM2ID(method), 0, NULL, block);
+}
+
+static VALUE kernel_spec_rb_funcall_many_args(VALUE self, VALUE obj, VALUE method) {
+ return rb_funcall(obj, SYM2ID(method), 15,
+ INT2FIX(15), INT2FIX(14), INT2FIX(13), INT2FIX(12), INT2FIX(11),
+ INT2FIX(10), INT2FIX(9), INT2FIX(8), INT2FIX(7), INT2FIX(6),
+ INT2FIX(5), INT2FIX(4), INT2FIX(3), INT2FIX(2), INT2FIX(1));
+}
+
+void Init_kernel_spec(void) {
+ VALUE cls = rb_define_class("CApiKernelSpecs", rb_cObject);
+ rb_define_method(cls, "rb_block_given_p", kernel_spec_rb_block_given_p, 0);
+ rb_define_method(cls, "rb_need_block", kernel_spec_rb_need_block, 0);
+ rb_define_method(cls, "rb_block_call", kernel_spec_rb_block_call, 1);
+ rb_define_method(cls, "rb_block_call_multi_arg", kernel_spec_rb_block_call_multi_arg, 1);
+ rb_define_method(cls, "rb_block_call_no_func", kernel_spec_rb_block_call_no_func, 1);
+ rb_define_method(cls, "rb_block_call_extra_data", rb_block_call_extra_data, 1);
+ rb_define_method(cls, "rb_block_proc", kernel_spec_rb_block_proc, 0);
+ rb_define_method(cls, "rb_block_lambda", kernel_spec_rb_block_lambda, 0);
+ rb_define_method(cls, "rb_frame_this_func_test", kernel_spec_rb_frame_this_func, 0);
+ rb_define_method(cls, "rb_frame_this_func_test_again", kernel_spec_rb_frame_this_func, 0);
+ rb_define_method(cls, "rb_ensure", kernel_spec_rb_ensure, 4);
+ rb_define_method(cls, "rb_eval_string", kernel_spec_rb_eval_string, 1);
+ rb_define_method(cls, "rb_eval_cmd_kw", kernel_spec_rb_eval_cmd_kw, 3);
+ rb_define_method(cls, "rb_raise", kernel_spec_rb_raise, 1);
+ rb_define_method(cls, "rb_throw", kernel_spec_rb_throw, 1);
+ rb_define_method(cls, "rb_throw_obj", kernel_spec_rb_throw_obj, 2);
+ rb_define_method(cls, "rb_rescue", kernel_spec_rb_rescue, 4);
+ rb_define_method(cls, "rb_rescue2", kernel_spec_rb_rescue2, -1);
+ rb_define_method(cls, "rb_protect_yield", kernel_spec_rb_protect_yield, 2);
+ rb_define_method(cls, "rb_protect_errinfo", kernel_spec_rb_protect_errinfo, 2);
+ rb_define_method(cls, "rb_protect_null_status", kernel_spec_rb_protect_null_status, 1);
+ rb_define_method(cls, "rb_eval_string_protect", kernel_spec_rb_eval_string_protect, 2);
+ rb_define_method(cls, "rb_catch", kernel_spec_rb_catch, 2);
+ rb_define_method(cls, "rb_catch_obj", kernel_spec_rb_catch_obj, 2);
+ rb_define_method(cls, "rb_sys_fail", kernel_spec_rb_sys_fail, 1);
+ rb_define_method(cls, "rb_syserr_fail", kernel_spec_rb_syserr_fail, 2);
+ rb_define_method(cls, "rb_warn", kernel_spec_rb_warn, 1);
+ rb_define_method(cls, "rb_yield", kernel_spec_rb_yield, 1);
+ rb_define_method(cls, "rb_yield_indirected", kernel_spec_rb_yield_indirected, 1);
+ rb_define_method(cls, "rb_yield_define_each", kernel_spec_rb_yield_define_each, 1);
+ rb_define_method(cls, "rb_yield_values", kernel_spec_rb_yield_values, 2);
+ rb_define_method(cls, "rb_yield_values2", kernel_spec_rb_yield_values2, 1);
+ rb_define_method(cls, "rb_yield_splat", kernel_spec_rb_yield_splat, 1);
+ rb_define_method(cls, "rb_exec_recursive", kernel_spec_rb_exec_recursive, 1);
+ rb_define_method(cls, "rb_set_end_proc", kernel_spec_rb_set_end_proc, 1);
+ rb_define_method(cls, "rb_f_sprintf", kernel_spec_rb_f_sprintf, 1);
+ rb_define_method(cls, "rb_make_backtrace", kernel_spec_rb_make_backtrace, 0);
+ rb_define_method(cls, "rb_funcallv", kernel_spec_rb_funcallv, 3);
+#ifdef RUBY_VERSION_IS_3_0
+ rb_define_method(cls, "rb_funcallv_kw", kernel_spec_rb_funcallv_kw, 3);
+ rb_define_method(cls, "rb_keyword_given_p", kernel_spec_rb_keyword_given_p, -1);
+#endif
+ rb_define_method(cls, "rb_funcallv_public", kernel_spec_rb_funcallv_public, 2);
+ rb_define_method(cls, "rb_funcall_many_args", kernel_spec_rb_funcall_many_args, 2);
+ rb_define_method(cls, "rb_funcall_with_block", kernel_spec_rb_funcall_with_block, 3);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/language_spec.c b/spec/ruby/optional/capi/ext/language_spec.c
new file mode 100644
index 0000000000..749c188956
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/language_spec.c
@@ -0,0 +1,42 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE language_spec_switch(VALUE self, VALUE value) {
+ if (value == ID2SYM(rb_intern("undef"))) {
+ value = Qundef;
+ }
+
+ switch (value) {
+ case Qtrue:
+ return ID2SYM(rb_intern("true"));
+ case Qfalse:
+ return ID2SYM(rb_intern("false"));
+ case Qnil:
+ return ID2SYM(rb_intern("nil"));
+ case Qundef:
+ return ID2SYM(rb_intern("undef"));
+ default:
+ return ID2SYM(rb_intern("default"));
+ }
+}
+
+/* Defining a local variable rb_mProcess which already exists as a global variable
+ * For instance eventmachine does this in Init_rubyeventmachine() */
+static VALUE language_spec_global_local_var(VALUE self) {
+ VALUE rb_mProcess = rb_const_get(rb_cObject, rb_intern("Process"));
+ return rb_mProcess;
+}
+
+void Init_language_spec(void) {
+ VALUE cls = rb_define_class("CApiLanguageSpecs", rb_cObject);
+ rb_define_method(cls, "switch", language_spec_switch, 1);
+ rb_define_method(cls, "global_local_var", language_spec_global_local_var, 0);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/marshal_spec.c b/spec/ruby/optional/capi/ext/marshal_spec.c
new file mode 100644
index 0000000000..ea8e3d5a07
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/marshal_spec.c
@@ -0,0 +1,24 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE marshal_spec_rb_marshal_dump(VALUE self, VALUE obj, VALUE port) {
+ return rb_marshal_dump(obj, port);
+}
+
+VALUE marshal_spec_rb_marshal_load(VALUE self, VALUE data) {
+ return rb_marshal_load(data);
+}
+
+void Init_marshal_spec(void) {
+ VALUE cls = rb_define_class("CApiMarshalSpecs", rb_cObject);
+ rb_define_method(cls, "rb_marshal_dump", marshal_spec_rb_marshal_dump, 2);
+ rb_define_method(cls, "rb_marshal_load", marshal_spec_rb_marshal_load, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/module_spec.c b/spec/ruby/optional/capi/ext/module_spec.c
new file mode 100644
index 0000000000..12bcf99983
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/module_spec.c
@@ -0,0 +1,176 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE module_specs_test_method(VALUE self) {
+ return ID2SYM(rb_intern("test_method"));
+}
+
+static VALUE module_specs_const_defined(VALUE self, VALUE klass, VALUE id) {
+ return rb_const_defined(klass, SYM2ID(id)) ? Qtrue : Qfalse;
+}
+
+static VALUE module_specs_const_defined_at(VALUE self, VALUE klass, VALUE id) {
+ return rb_const_defined_at(klass, SYM2ID(id)) ? Qtrue : Qfalse;
+}
+
+static VALUE module_specs_const_get(VALUE self, VALUE klass, VALUE val) {
+ return rb_const_get(klass, SYM2ID(val));
+}
+
+static VALUE module_specs_const_get_at(VALUE self, VALUE klass, VALUE val) {
+ return rb_const_get_at(klass, SYM2ID(val));
+}
+
+static VALUE module_specs_const_get_from(VALUE self, VALUE klass, VALUE val) {
+ return rb_const_get_from(klass, SYM2ID(val));
+}
+
+static VALUE module_specs_const_set(VALUE self, VALUE klass, VALUE name, VALUE val) {
+ rb_const_set(klass, SYM2ID(name), val);
+ return Qnil;
+}
+
+static VALUE module_specs_rb_define_alias(VALUE self, VALUE obj,
+ VALUE new_name, VALUE old_name) {
+
+ rb_define_alias(obj, RSTRING_PTR(new_name), RSTRING_PTR(old_name));
+ return Qnil;
+}
+
+static VALUE module_specs_rb_alias(VALUE self, VALUE obj,
+ VALUE new_name, VALUE old_name) {
+
+ rb_alias(obj, SYM2ID(new_name), SYM2ID(old_name));
+ return Qnil;
+}
+
+static VALUE module_specs_rb_define_module(VALUE self, VALUE name) {
+ return rb_define_module(RSTRING_PTR(name));
+}
+
+static VALUE module_specs_rb_define_module_under(VALUE self, VALUE outer, VALUE name) {
+ return rb_define_module_under(outer, RSTRING_PTR(name));
+}
+
+static VALUE module_specs_define_const(VALUE self, VALUE klass, VALUE str_name, VALUE val) {
+ rb_define_const(klass, RSTRING_PTR(str_name), val);
+ return Qnil;
+}
+
+static VALUE module_specs_define_global_const(VALUE self, VALUE str_name, VALUE obj) {
+ rb_define_global_const(RSTRING_PTR(str_name), obj);
+ return Qnil;
+}
+
+static VALUE module_specs_rb_define_global_function(VALUE self, VALUE str_name) {
+ rb_define_global_function(RSTRING_PTR(str_name), module_specs_test_method, 0);
+ return Qnil;
+}
+
+static VALUE module_specs_rb_define_method(VALUE self, VALUE cls, VALUE str_name) {
+ rb_define_method(cls, RSTRING_PTR(str_name), module_specs_test_method, 0);
+ return Qnil;
+}
+
+static VALUE module_specs_method_var_args_1(int argc, VALUE *argv, VALUE self) {
+ VALUE ary = rb_ary_new();
+ int i;
+ for (i = 0; i < argc; i++) {
+ rb_ary_push(ary, argv[i]);
+ }
+ return ary;
+}
+
+static VALUE module_specs_method_var_args_2(VALUE self, VALUE argv) {
+ return argv;
+}
+
+static VALUE module_specs_rb_define_method_1required(VALUE self, VALUE arg1) {
+ return arg1;
+}
+
+static VALUE module_specs_rb_define_method_2required(VALUE self, VALUE arg1, VALUE arg2) {
+ return arg2;
+}
+
+static VALUE module_specs_rb_define_module_function(VALUE self, VALUE cls, VALUE str_name) {
+ rb_define_module_function(cls, RSTRING_PTR(str_name), module_specs_test_method, 0);
+ return Qnil;
+}
+
+static VALUE module_specs_rb_define_private_method(VALUE self, VALUE cls, VALUE str_name) {
+ rb_define_private_method(cls, RSTRING_PTR(str_name), module_specs_test_method, 0);
+ return Qnil;
+}
+
+static VALUE module_specs_rb_define_protected_method(VALUE self, VALUE cls, VALUE str_name) {
+ rb_define_protected_method(cls, RSTRING_PTR(str_name), module_specs_test_method, 0);
+ return Qnil;
+}
+
+static VALUE module_specs_rb_define_singleton_method(VALUE self, VALUE cls, VALUE str_name) {
+ rb_define_singleton_method(cls, RSTRING_PTR(str_name), module_specs_test_method, 0);
+ return Qnil;
+}
+
+static VALUE module_specs_rb_undef_method(VALUE self, VALUE cls, VALUE str_name) {
+ rb_undef_method(cls, RSTRING_PTR(str_name));
+ return Qnil;
+}
+
+static VALUE module_specs_rb_undef(VALUE self, VALUE cls, VALUE symbol_name) {
+ rb_undef(cls, SYM2ID(symbol_name));
+ return Qnil;
+}
+
+static VALUE module_specs_rbclass2name(VALUE self, VALUE klass) {
+ return rb_str_new2(rb_class2name(klass));
+}
+
+static VALUE module_specs_rb_mod_ancestors(VALUE self, VALUE klass) {
+ return rb_mod_ancestors(klass);
+}
+
+void Init_module_spec(void) {
+ VALUE cls = rb_define_class("CApiModuleSpecs", rb_cObject);
+ rb_define_method(cls, "rb_const_defined", module_specs_const_defined, 2);
+ rb_define_method(cls, "rb_const_defined_at", module_specs_const_defined_at, 2);
+ rb_define_method(cls, "rb_const_get", module_specs_const_get, 2);
+ rb_define_method(cls, "rb_const_get_at", module_specs_const_get_at, 2);
+ rb_define_method(cls, "rb_const_get_from", module_specs_const_get_from, 2);
+ rb_define_method(cls, "rb_const_set", module_specs_const_set, 3);
+ rb_define_method(cls, "rb_define_alias", module_specs_rb_define_alias, 3);
+ rb_define_method(cls, "rb_alias", module_specs_rb_alias, 3);
+ rb_define_method(cls, "rb_define_module", module_specs_rb_define_module, 1);
+ rb_define_method(cls, "rb_define_module_under", module_specs_rb_define_module_under, 2);
+ rb_define_method(cls, "rb_define_const", module_specs_define_const, 3);
+ rb_define_method(cls, "rb_define_global_const", module_specs_define_global_const, 2);
+ rb_define_method(cls, "rb_define_global_function", module_specs_rb_define_global_function, 1);
+
+ rb_define_method(cls, "rb_define_method", module_specs_rb_define_method, 2);
+ rb_define_method(cls, "rb_define_method_varargs_1", module_specs_method_var_args_1, -1);
+ rb_define_method(cls, "rb_define_method_varargs_2", module_specs_method_var_args_2, -2);
+ rb_define_method(cls, "rb_define_method_1required", module_specs_rb_define_method_1required, 1);
+ rb_define_method(cls, "rb_define_method_2required", module_specs_rb_define_method_2required, 2);
+
+ rb_define_method(cls, "rb_define_module_function", module_specs_rb_define_module_function, 2);
+
+ rb_define_method(cls, "rb_define_private_method", module_specs_rb_define_private_method, 2);
+
+ rb_define_method(cls, "rb_define_protected_method", module_specs_rb_define_protected_method, 2);
+
+ rb_define_method(cls, "rb_define_singleton_method", module_specs_rb_define_singleton_method, 2);
+
+ rb_define_method(cls, "rb_undef_method", module_specs_rb_undef_method, 2);
+ rb_define_method(cls, "rb_undef", module_specs_rb_undef, 2);
+ rb_define_method(cls, "rb_class2name", module_specs_rbclass2name, 1);
+ rb_define_method(cls, "rb_mod_ancestors", module_specs_rb_mod_ancestors, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/module_under_autoload_spec.c b/spec/ruby/optional/capi/ext/module_under_autoload_spec.c
new file mode 100644
index 0000000000..b19466e555
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/module_under_autoload_spec.c
@@ -0,0 +1,15 @@
+#include "ruby.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void Init_module_under_autoload_spec(void) {
+ VALUE specs = rb_const_get(rb_cObject, rb_intern("CApiModuleSpecs"));
+ rb_define_module_under(specs, "ModuleUnderAutoload");
+ rb_define_module_under(specs, "RubyUnderAutoload");
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/mutex_spec.c b/spec/ruby/optional/capi/ext/mutex_spec.c
new file mode 100644
index 0000000000..c2fdf917ac
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/mutex_spec.c
@@ -0,0 +1,55 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE mutex_spec_rb_mutex_new(VALUE self) {
+ return rb_mutex_new();
+}
+
+VALUE mutex_spec_rb_mutex_locked_p(VALUE self, VALUE mutex) {
+ return rb_mutex_locked_p(mutex);
+}
+
+VALUE mutex_spec_rb_mutex_trylock(VALUE self, VALUE mutex) {
+ return rb_mutex_trylock(mutex);
+}
+
+VALUE mutex_spec_rb_mutex_lock(VALUE self, VALUE mutex) {
+ return rb_mutex_lock(mutex);
+}
+
+VALUE mutex_spec_rb_mutex_unlock(VALUE self, VALUE mutex) {
+ return rb_mutex_unlock(mutex);
+}
+
+VALUE mutex_spec_rb_mutex_sleep(VALUE self, VALUE mutex, VALUE timeout) {
+ return rb_mutex_sleep(mutex, timeout);
+}
+
+
+VALUE mutex_spec_rb_mutex_callback(VALUE arg) {
+ return rb_funcall(arg, rb_intern("call"), 0);
+}
+
+VALUE mutex_spec_rb_mutex_synchronize(VALUE self, VALUE mutex, VALUE value) {
+ return rb_mutex_synchronize(mutex, mutex_spec_rb_mutex_callback, value);
+}
+
+void Init_mutex_spec(void) {
+ VALUE cls = rb_define_class("CApiMutexSpecs", rb_cObject);
+ rb_define_method(cls, "rb_mutex_new", mutex_spec_rb_mutex_new, 0);
+ rb_define_method(cls, "rb_mutex_locked_p", mutex_spec_rb_mutex_locked_p, 1);
+ rb_define_method(cls, "rb_mutex_trylock", mutex_spec_rb_mutex_trylock, 1);
+ rb_define_method(cls, "rb_mutex_lock", mutex_spec_rb_mutex_lock, 1);
+ rb_define_method(cls, "rb_mutex_unlock", mutex_spec_rb_mutex_unlock, 1);
+ rb_define_method(cls, "rb_mutex_sleep", mutex_spec_rb_mutex_sleep, 2);
+ rb_define_method(cls, "rb_mutex_synchronize", mutex_spec_rb_mutex_synchronize, 2);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/spec/ruby/optional/capi/ext/numeric_spec.c b/spec/ruby/optional/capi/ext/numeric_spec.c
new file mode 100644
index 0000000000..d3639580a8
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/numeric_spec.c
@@ -0,0 +1,130 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE numeric_spec_size_of_VALUE(VALUE self) {
+ return INT2FIX(sizeof(VALUE));
+}
+
+static VALUE numeric_spec_size_of_long_long(VALUE self) {
+ return INT2FIX(sizeof(LONG_LONG));
+}
+
+static VALUE numeric_spec_NUM2CHR(VALUE self, VALUE value) {
+ return INT2FIX(NUM2CHR(value));
+}
+
+static VALUE numeric_spec_rb_int2inum_14(VALUE self) {
+ return rb_int2inum(14);
+}
+
+static VALUE numeric_spec_rb_uint2inum_14(VALUE self) {
+ return rb_uint2inum(14);
+}
+
+static VALUE numeric_spec_rb_uint2inum_n14(VALUE self) {
+ return rb_uint2inum(-14);
+}
+
+static VALUE numeric_spec_rb_Integer(VALUE self, VALUE str) {
+ return rb_Integer(str);
+}
+
+static VALUE numeric_spec_rb_ll2inum_14(VALUE self) {
+ return rb_ll2inum(14);
+}
+
+static VALUE numeric_spec_rb_ull2inum_14(VALUE self) {
+ return rb_ull2inum(14);
+}
+
+static VALUE numeric_spec_rb_ull2inum_n14(VALUE self) {
+ return rb_ull2inum(-14);
+}
+
+static VALUE numeric_spec_NUM2DBL(VALUE self, VALUE num) {
+ return rb_float_new(NUM2DBL(num));
+}
+
+static VALUE numeric_spec_NUM2INT(VALUE self, VALUE num) {
+ return LONG2NUM(NUM2INT(num));
+}
+
+static VALUE numeric_spec_INT2NUM(VALUE self, VALUE num) {
+ return INT2NUM(NUM2INT(num));
+}
+
+static VALUE numeric_spec_NUM2LONG(VALUE self, VALUE num) {
+ return LONG2NUM(NUM2LONG(num));
+}
+
+static VALUE numeric_spec_NUM2SHORT(VALUE self, VALUE num) {
+ return LONG2NUM(NUM2SHORT(num));
+}
+
+static VALUE numeric_spec_NUM2UINT(VALUE self, VALUE num) {
+ return ULONG2NUM(NUM2UINT(num));
+}
+
+static VALUE numeric_spec_NUM2ULONG(VALUE self, VALUE num) {
+ return ULONG2NUM(NUM2ULONG(num));
+}
+
+static VALUE numeric_spec_rb_num_zerodiv(VALUE self) {
+ rb_num_zerodiv();
+ return Qnil;
+}
+
+static VALUE numeric_spec_rb_cmpint(VALUE self, VALUE val, VALUE b) {
+ return INT2FIX(rb_cmpint(val, val, b));
+}
+
+static VALUE numeric_spec_rb_num_coerce_bin(VALUE self, VALUE x, VALUE y, VALUE op) {
+ return rb_num_coerce_bin(x, y, SYM2ID(op));
+}
+
+static VALUE numeric_spec_rb_num_coerce_cmp(VALUE self, VALUE x, VALUE y, VALUE op) {
+ return rb_num_coerce_cmp(x, y, SYM2ID(op));
+}
+
+static VALUE numeric_spec_rb_num_coerce_relop(VALUE self, VALUE x, VALUE y, VALUE op) {
+ return rb_num_coerce_relop(x, y, SYM2ID(op));
+}
+
+static VALUE numeric_spec_rb_absint_singlebit_p(VALUE self, VALUE num) {
+ return INT2FIX(rb_absint_singlebit_p(num));
+}
+
+void Init_numeric_spec(void) {
+ VALUE cls = rb_define_class("CApiNumericSpecs", rb_cObject);
+ rb_define_method(cls, "size_of_VALUE", numeric_spec_size_of_VALUE, 0);
+ rb_define_method(cls, "size_of_long_long", numeric_spec_size_of_long_long, 0);
+ rb_define_method(cls, "NUM2CHR", numeric_spec_NUM2CHR, 1);
+ rb_define_method(cls, "rb_int2inum_14", numeric_spec_rb_int2inum_14, 0);
+ rb_define_method(cls, "rb_uint2inum_14", numeric_spec_rb_uint2inum_14, 0);
+ rb_define_method(cls, "rb_uint2inum_n14", numeric_spec_rb_uint2inum_n14, 0);
+ rb_define_method(cls, "rb_Integer", numeric_spec_rb_Integer, 1);
+ rb_define_method(cls, "rb_ll2inum_14", numeric_spec_rb_ll2inum_14, 0);
+ rb_define_method(cls, "rb_ull2inum_14", numeric_spec_rb_ull2inum_14, 0);
+ rb_define_method(cls, "rb_ull2inum_n14", numeric_spec_rb_ull2inum_n14, 0);
+ rb_define_method(cls, "NUM2DBL", numeric_spec_NUM2DBL, 1);
+ rb_define_method(cls, "NUM2INT", numeric_spec_NUM2INT, 1);
+ rb_define_method(cls, "NUM2LONG", numeric_spec_NUM2LONG, 1);
+ rb_define_method(cls, "NUM2SHORT", numeric_spec_NUM2SHORT, 1);
+ rb_define_method(cls, "INT2NUM", numeric_spec_INT2NUM, 1);
+ rb_define_method(cls, "NUM2UINT", numeric_spec_NUM2UINT, 1);
+ rb_define_method(cls, "NUM2ULONG", numeric_spec_NUM2ULONG, 1);
+ rb_define_method(cls, "rb_num_zerodiv", numeric_spec_rb_num_zerodiv, 0);
+ rb_define_method(cls, "rb_cmpint", numeric_spec_rb_cmpint, 2);
+ rb_define_method(cls, "rb_num_coerce_bin", numeric_spec_rb_num_coerce_bin, 3);
+ rb_define_method(cls, "rb_num_coerce_cmp", numeric_spec_rb_num_coerce_cmp, 3);
+ rb_define_method(cls, "rb_num_coerce_relop", numeric_spec_rb_num_coerce_relop, 3);
+rb_define_method(cls, "rb_absint_singlebit_p", numeric_spec_rb_absint_singlebit_p, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/object_spec.c b/spec/ruby/optional/capi/ext/object_spec.c
new file mode 100644
index 0000000000..30ac44cf1f
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/object_spec.c
@@ -0,0 +1,528 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE object_spec_FL_ABLE(VALUE self, VALUE obj) {
+ if (FL_ABLE(obj)) {
+ return Qtrue;
+ } else {
+ return Qfalse;
+ }
+}
+
+static int object_spec_FL_TEST_flag(VALUE flag_string) {
+ char *flag_cstr = StringValueCStr(flag_string);
+#ifndef RUBY_VERSION_IS_3_1
+ if (strcmp(flag_cstr, "FL_TAINT") == 0) {
+ return FL_TAINT;
+ }
+#endif
+ if (strcmp(flag_cstr, "FL_FREEZE") == 0) {
+ return FL_FREEZE;
+ }
+ return 0;
+}
+
+static VALUE object_spec_FL_TEST(VALUE self, VALUE obj, VALUE flag) {
+ return INT2FIX(FL_TEST(obj, object_spec_FL_TEST_flag(flag)));
+}
+
+#ifndef RUBY_VERSION_IS_3_1
+static VALUE object_spec_OBJ_TAINT(VALUE self, VALUE obj) {
+ OBJ_TAINT(obj);
+ return Qnil;
+}
+
+static VALUE object_spec_OBJ_TAINTED(VALUE self, VALUE obj) {
+ return OBJ_TAINTED(obj) ? Qtrue : Qfalse;
+}
+
+static VALUE object_spec_OBJ_INFECT(VALUE self, VALUE host, VALUE source) {
+ OBJ_INFECT(host, source);
+ return Qnil;
+}
+#endif
+
+static VALUE object_spec_rb_any_to_s(VALUE self, VALUE obj) {
+ return rb_any_to_s(obj);
+}
+
+static VALUE so_attr_get(VALUE self, VALUE obj, VALUE attr) {
+ return rb_attr_get(obj, SYM2ID(attr));
+}
+
+static VALUE object_spec_rb_obj_instance_variables(VALUE self, VALUE obj) {
+ return rb_obj_instance_variables(obj);
+}
+
+static VALUE so_check_array_type(VALUE self, VALUE ary) {
+ return rb_check_array_type(ary);
+}
+
+static VALUE so_check_convert_type(VALUE self, VALUE obj, VALUE klass, VALUE method) {
+ return rb_check_convert_type(obj, T_ARRAY, RSTRING_PTR(klass), RSTRING_PTR(method));
+}
+
+static VALUE so_check_to_integer(VALUE self, VALUE obj, VALUE method) {
+ return rb_check_to_integer(obj, RSTRING_PTR(method));
+}
+
+static VALUE object_spec_rb_check_frozen(VALUE self, VALUE obj) {
+ rb_check_frozen(obj);
+ return Qnil;
+}
+
+static VALUE so_check_string_type(VALUE self, VALUE str) {
+ return rb_check_string_type(str);
+}
+
+static VALUE so_rbclassof(VALUE self, VALUE obj) {
+ return rb_class_of(obj);
+}
+
+static VALUE so_convert_type(VALUE self, VALUE obj, VALUE klass, VALUE method) {
+ return rb_convert_type(obj, T_ARRAY, RSTRING_PTR(klass), RSTRING_PTR(method));
+}
+
+static VALUE object_spec_rb_extend_object(VALUE self, VALUE obj, VALUE mod) {
+ rb_extend_object(obj, mod);
+ return obj;
+}
+
+static VALUE so_inspect(VALUE self, VALUE obj) {
+ return rb_inspect(obj);
+}
+
+static VALUE so_rb_obj_alloc(VALUE self, VALUE klass) {
+ return rb_obj_alloc(klass);
+}
+
+static VALUE so_rb_obj_dup(VALUE self, VALUE klass) {
+ return rb_obj_dup(klass);
+}
+
+static VALUE so_rb_obj_call_init(VALUE self, VALUE object,
+ VALUE nargs, VALUE args) {
+ int c_nargs = FIX2INT(nargs);
+ VALUE *c_args = (VALUE*) alloca(sizeof(VALUE) * c_nargs);
+ int i;
+
+ for (i = 0; i < c_nargs; i++)
+ c_args[i] = rb_ary_entry(args, i);
+
+ rb_obj_call_init(object, c_nargs, c_args);
+
+ return Qnil;
+}
+
+static VALUE so_rb_obj_class(VALUE self, VALUE obj) {
+ return rb_obj_class(obj);
+}
+
+static VALUE so_rbobjclassname(VALUE self, VALUE obj) {
+ return rb_str_new2(rb_obj_classname(obj));
+}
+
+static VALUE object_spec_rb_obj_freeze(VALUE self, VALUE obj) {
+ return rb_obj_freeze(obj);
+}
+
+static VALUE object_spec_rb_obj_frozen_p(VALUE self, VALUE obj) {
+ return rb_obj_frozen_p(obj);
+}
+
+static VALUE object_spec_rb_obj_id(VALUE self, VALUE obj) {
+ return rb_obj_id(obj);
+}
+
+static VALUE so_instance_of(VALUE self, VALUE obj, VALUE klass) {
+ return rb_obj_is_instance_of(obj, klass);
+}
+
+static VALUE so_kind_of(VALUE self, VALUE obj, VALUE klass) {
+ return rb_obj_is_kind_of(obj, klass);
+}
+
+static VALUE object_specs_rb_obj_method_arity(VALUE self, VALUE obj, VALUE mid) {
+ return INT2FIX(rb_obj_method_arity(obj, SYM2ID(mid)));
+}
+
+static VALUE object_specs_rb_obj_method(VALUE self, VALUE obj, VALUE method) {
+ return rb_obj_method(obj, method);
+}
+
+#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(__clang__) && defined(__has_warning)
+# if __has_warning("-Wdeprecated-declarations")
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdeprecated-declarations"
+# endif
+#endif
+
+#ifndef RUBY_VERSION_IS_3_2
+static VALUE object_spec_rb_obj_taint(VALUE self, VALUE obj) {
+ return rb_obj_taint(obj);
+}
+#endif
+
+#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+# pragma GCC diagnostic pop
+#elif defined(__clang__) && defined(__has_warning)
+# if __has_warning("-Wdeprecated-declarations")
+# pragma clang diagnostic pop
+# endif
+#endif
+
+static VALUE so_require(VALUE self) {
+ rb_require("fixtures/foo");
+ return Qnil;
+}
+
+static VALUE so_respond_to(VALUE self, VALUE obj, VALUE sym) {
+ return rb_respond_to(obj, SYM2ID(sym)) ? Qtrue : Qfalse;
+}
+
+static VALUE so_obj_respond_to(VALUE self, VALUE obj, VALUE sym, VALUE priv) {
+ return rb_obj_respond_to(obj, SYM2ID(sym), priv == Qtrue ? 1 : 0) ? Qtrue : Qfalse;
+}
+
+static VALUE object_spec_rb_method_boundp(VALUE self, VALUE obj, VALUE method, VALUE exclude_private) {
+ ID id = SYM2ID(method);
+ return rb_method_boundp(obj, id, exclude_private == Qtrue ? 1 : 0) ? Qtrue : Qfalse;
+}
+
+static VALUE object_spec_rb_special_const_p(VALUE self, VALUE value) {
+ if (rb_special_const_p(value)) {
+ return Qtrue;
+ } else {
+ return Qfalse;
+ }
+}
+
+static VALUE so_to_id(VALUE self, VALUE obj) {
+ return ID2SYM(rb_to_id(obj));
+}
+
+static VALUE object_spec_RTEST(VALUE self, VALUE value) {
+ return RTEST(value) ? Qtrue : Qfalse;
+}
+
+static VALUE so_check_type(VALUE self, VALUE obj, VALUE other) {
+ rb_check_type(obj, TYPE(other));
+ return Qtrue;
+}
+
+static VALUE so_is_type_nil(VALUE self, VALUE obj) {
+ if(TYPE(obj) == T_NIL) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_type_object(VALUE self, VALUE obj) {
+ if(TYPE(obj) == T_OBJECT) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_type_array(VALUE self, VALUE obj) {
+ if(TYPE(obj) == T_ARRAY) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_type_module(VALUE self, VALUE obj) {
+ if(TYPE(obj) == T_MODULE) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_type_class(VALUE self, VALUE obj) {
+ if(TYPE(obj) == T_CLASS) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_type_data(VALUE self, VALUE obj) {
+ if(TYPE(obj) == T_DATA) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_rb_type_p_nil(VALUE self, VALUE obj) {
+ if(rb_type_p(obj, T_NIL)) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_rb_type_p_object(VALUE self, VALUE obj) {
+ if(rb_type_p(obj, T_OBJECT)) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_rb_type_p_array(VALUE self, VALUE obj) {
+ if(rb_type_p(obj, T_ARRAY)) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_rb_type_p_module(VALUE self, VALUE obj) {
+ if(rb_type_p(obj, T_MODULE)) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_rb_type_p_class(VALUE self, VALUE obj) {
+ if(rb_type_p(obj, T_CLASS)) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_rb_type_p_data(VALUE self, VALUE obj) {
+ if(rb_type_p(obj, T_DATA)) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_rb_type_p_file(VALUE self, VALUE obj) {
+ if(rb_type_p(obj, T_FILE)) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_builtin_type_object(VALUE self, VALUE obj) {
+ if(BUILTIN_TYPE(obj) == T_OBJECT) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_builtin_type_array(VALUE self, VALUE obj) {
+ if(BUILTIN_TYPE(obj) == T_ARRAY) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_builtin_type_module(VALUE self, VALUE obj) {
+ if(BUILTIN_TYPE(obj) == T_MODULE) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_builtin_type_class(VALUE self, VALUE obj) {
+ if(BUILTIN_TYPE(obj) == T_CLASS) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE so_is_builtin_type_data(VALUE self, VALUE obj) {
+ if(BUILTIN_TYPE(obj) == T_DATA) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static VALUE object_spec_rb_to_int(VALUE self, VALUE obj) {
+ return rb_to_int(obj);
+}
+
+static VALUE object_spec_rb_obj_instance_eval(VALUE self, VALUE obj) {
+ return rb_obj_instance_eval(0, NULL, obj);
+}
+
+static VALUE object_spec_rb_iv_get(VALUE self, VALUE obj, VALUE name) {
+ return rb_iv_get(obj, RSTRING_PTR(name));
+}
+
+static VALUE object_spec_rb_iv_set(VALUE self, VALUE obj, VALUE name, VALUE value) {
+ return rb_iv_set(obj, RSTRING_PTR(name), value);
+}
+
+static VALUE object_spec_rb_ivar_count(VALUE self, VALUE obj) {
+ return ULONG2NUM(rb_ivar_count(obj));
+}
+
+static VALUE object_spec_rb_ivar_get(VALUE self, VALUE obj, VALUE sym_name) {
+ return rb_ivar_get(obj, SYM2ID(sym_name));
+}
+
+static VALUE object_spec_rb_ivar_set(VALUE self, VALUE obj, VALUE sym_name, VALUE value) {
+ return rb_ivar_set(obj, SYM2ID(sym_name), value);
+}
+
+static VALUE object_spec_rb_ivar_defined(VALUE self, VALUE obj, VALUE sym_name) {
+ return rb_ivar_defined(obj, SYM2ID(sym_name));
+}
+
+static VALUE object_spec_rb_copy_generic_ivar(VALUE self, VALUE clone, VALUE obj) {
+ rb_copy_generic_ivar(clone, obj);
+ return self;
+}
+
+static VALUE object_spec_rb_free_generic_ivar(VALUE self, VALUE obj) {
+ rb_free_generic_ivar(obj);
+ return self;
+}
+
+static VALUE object_spec_rb_equal(VALUE self, VALUE a, VALUE b) {
+ return rb_equal(a, b);
+}
+
+static VALUE object_spec_rb_class_inherited_p(VALUE self, VALUE mod, VALUE arg) {
+ return rb_class_inherited_p(mod, arg);
+}
+
+static int foreach_f(ID key, VALUE val, VALUE ary) {
+ rb_ary_push(ary, ID2SYM(key));
+ rb_ary_push(ary, val);
+ return ST_CONTINUE;
+}
+
+static VALUE object_spec_rb_ivar_foreach(VALUE self, VALUE obj) {
+ VALUE ary = rb_ary_new();
+ rb_ivar_foreach(obj, foreach_f, ary);
+ return ary;
+}
+
+static VALUE speced_allocator(VALUE klass) {
+ VALUE flags = 0;
+ VALUE instance;
+ if (RTEST(rb_class_inherited_p(klass, rb_cString))) {
+ flags = T_STRING;
+ } else if (RTEST(rb_class_inherited_p(klass, rb_cArray))) {
+ flags = T_ARRAY;
+ } else {
+ flags = T_OBJECT;
+ }
+ instance = rb_newobj_of(klass, flags);
+ rb_iv_set(instance, "@from_custom_allocator", Qtrue);
+ return instance;
+}
+
+static VALUE define_alloc_func(VALUE self, VALUE klass) {
+ rb_define_alloc_func(klass, speced_allocator);
+ return Qnil;
+}
+
+static VALUE undef_alloc_func(VALUE self, VALUE klass) {
+ rb_undef_alloc_func(klass);
+ return Qnil;
+}
+
+static VALUE speced_allocator_p(VALUE self, VALUE klass) {
+ rb_alloc_func_t allocator = rb_get_alloc_func(klass);
+ return (allocator == speced_allocator) ? Qtrue : Qfalse;
+}
+
+static VALUE custom_alloc_func_p(VALUE self, VALUE klass) {
+ rb_alloc_func_t allocator = rb_get_alloc_func(klass);
+ return allocator ? Qtrue : Qfalse;
+}
+
+void Init_object_spec(void) {
+ VALUE cls = rb_define_class("CApiObjectSpecs", rb_cObject);
+ rb_define_method(cls, "FL_ABLE", object_spec_FL_ABLE, 1);
+ rb_define_method(cls, "FL_TEST", object_spec_FL_TEST, 2);
+#ifndef RUBY_VERSION_IS_3_1
+ rb_define_method(cls, "OBJ_TAINT", object_spec_OBJ_TAINT, 1);
+ rb_define_method(cls, "OBJ_TAINTED", object_spec_OBJ_TAINTED, 1);
+ rb_define_method(cls, "OBJ_INFECT", object_spec_OBJ_INFECT, 2);
+#endif
+ rb_define_method(cls, "rb_any_to_s", object_spec_rb_any_to_s, 1);
+ rb_define_method(cls, "rb_attr_get", so_attr_get, 2);
+ rb_define_method(cls, "rb_obj_instance_variables", object_spec_rb_obj_instance_variables, 1);
+ rb_define_method(cls, "rb_check_array_type", so_check_array_type, 1);
+ rb_define_method(cls, "rb_check_convert_type", so_check_convert_type, 3);
+ rb_define_method(cls, "rb_check_to_integer", so_check_to_integer, 2);
+ rb_define_method(cls, "rb_check_frozen", object_spec_rb_check_frozen, 1);
+ rb_define_method(cls, "rb_check_string_type", so_check_string_type, 1);
+ rb_define_method(cls, "rb_class_of", so_rbclassof, 1);
+ rb_define_method(cls, "rb_convert_type", so_convert_type, 3);
+ rb_define_method(cls, "rb_extend_object", object_spec_rb_extend_object, 2);
+ rb_define_method(cls, "rb_inspect", so_inspect, 1);
+ rb_define_method(cls, "rb_obj_alloc", so_rb_obj_alloc, 1);
+ rb_define_method(cls, "rb_obj_dup", so_rb_obj_dup, 1);
+ rb_define_method(cls, "rb_obj_call_init", so_rb_obj_call_init, 3);
+ rb_define_method(cls, "rb_obj_class", so_rb_obj_class, 1);
+ rb_define_method(cls, "rb_obj_classname", so_rbobjclassname, 1);
+ rb_define_method(cls, "rb_obj_freeze", object_spec_rb_obj_freeze, 1);
+ rb_define_method(cls, "rb_obj_frozen_p", object_spec_rb_obj_frozen_p, 1);
+ rb_define_method(cls, "rb_obj_id", object_spec_rb_obj_id, 1);
+ rb_define_method(cls, "rb_obj_is_instance_of", so_instance_of, 2);
+ rb_define_method(cls, "rb_obj_is_kind_of", so_kind_of, 2);
+ rb_define_method(cls, "rb_obj_method_arity", object_specs_rb_obj_method_arity, 2);
+ rb_define_method(cls, "rb_obj_method", object_specs_rb_obj_method, 2);
+#ifndef RUBY_VERSION_IS_3_2
+ rb_define_method(cls, "rb_obj_taint", object_spec_rb_obj_taint, 1);
+#endif
+ rb_define_method(cls, "rb_require", so_require, 0);
+ rb_define_method(cls, "rb_respond_to", so_respond_to, 2);
+ rb_define_method(cls, "rb_method_boundp", object_spec_rb_method_boundp, 3);
+ rb_define_method(cls, "rb_obj_respond_to", so_obj_respond_to, 3);
+ rb_define_method(cls, "rb_special_const_p", object_spec_rb_special_const_p, 1);
+
+ rb_define_method(cls, "rb_to_id", so_to_id, 1);
+ rb_define_method(cls, "RTEST", object_spec_RTEST, 1);
+ rb_define_method(cls, "rb_check_type", so_check_type, 2);
+ rb_define_method(cls, "rb_is_type_nil", so_is_type_nil, 1);
+ rb_define_method(cls, "rb_is_type_object", so_is_type_object, 1);
+ rb_define_method(cls, "rb_is_type_array", so_is_type_array, 1);
+ rb_define_method(cls, "rb_is_type_module", so_is_type_module, 1);
+ rb_define_method(cls, "rb_is_type_class", so_is_type_class, 1);
+ rb_define_method(cls, "rb_is_type_data", so_is_type_data, 1);
+ rb_define_method(cls, "rb_is_rb_type_p_nil", so_is_rb_type_p_nil, 1);
+ rb_define_method(cls, "rb_is_rb_type_p_object", so_is_rb_type_p_object, 1);
+ rb_define_method(cls, "rb_is_rb_type_p_array", so_is_rb_type_p_array, 1);
+ rb_define_method(cls, "rb_is_rb_type_p_module", so_is_rb_type_p_module, 1);
+ rb_define_method(cls, "rb_is_rb_type_p_class", so_is_rb_type_p_class, 1);
+ rb_define_method(cls, "rb_is_rb_type_p_data", so_is_rb_type_p_data, 1);
+ rb_define_method(cls, "rb_is_rb_type_p_file", so_is_rb_type_p_file, 1);
+ rb_define_method(cls, "rb_is_builtin_type_object", so_is_builtin_type_object, 1);
+ rb_define_method(cls, "rb_is_builtin_type_array", so_is_builtin_type_array, 1);
+ rb_define_method(cls, "rb_is_builtin_type_module", so_is_builtin_type_module, 1);
+ rb_define_method(cls, "rb_is_builtin_type_class", so_is_builtin_type_class, 1);
+ rb_define_method(cls, "rb_is_builtin_type_data", so_is_builtin_type_data, 1);
+ rb_define_method(cls, "rb_to_int", object_spec_rb_to_int, 1);
+ rb_define_method(cls, "rb_equal", object_spec_rb_equal, 2);
+ rb_define_method(cls, "rb_class_inherited_p", object_spec_rb_class_inherited_p, 2);
+ rb_define_method(cls, "rb_obj_instance_eval", object_spec_rb_obj_instance_eval, 1);
+ rb_define_method(cls, "rb_iv_get", object_spec_rb_iv_get, 2);
+ rb_define_method(cls, "rb_iv_set", object_spec_rb_iv_set, 3);
+ rb_define_method(cls, "rb_ivar_count", object_spec_rb_ivar_count, 1);
+ rb_define_method(cls, "rb_ivar_get", object_spec_rb_ivar_get, 2);
+ rb_define_method(cls, "rb_ivar_set", object_spec_rb_ivar_set, 3);
+ rb_define_method(cls, "rb_ivar_defined", object_spec_rb_ivar_defined, 2);
+ rb_define_method(cls, "rb_copy_generic_ivar", object_spec_rb_copy_generic_ivar, 2);
+ rb_define_method(cls, "rb_free_generic_ivar", object_spec_rb_free_generic_ivar, 1);
+ rb_define_method(cls, "rb_define_alloc_func", define_alloc_func, 1);
+ rb_define_method(cls, "rb_undef_alloc_func", undef_alloc_func, 1);
+ rb_define_method(cls, "speced_allocator?", speced_allocator_p, 1);
+ rb_define_method(cls, "custom_alloc_func?", custom_alloc_func_p, 1);
+ rb_define_method(cls, "not_implemented_method", rb_f_notimplement, -1);
+ rb_define_method(cls, "rb_ivar_foreach", object_spec_rb_ivar_foreach, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/proc_spec.c b/spec/ruby/optional/capi/ext/proc_spec.c
new file mode 100644
index 0000000000..1137f4156b
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/proc_spec.c
@@ -0,0 +1,132 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE proc_spec_rb_proc_new_function(RB_BLOCK_CALL_FUNC_ARGLIST(args, dummy)) {
+ return rb_funcall(args, rb_intern("inspect"), 0);
+}
+
+VALUE proc_spec_rb_proc_new_function_arg(VALUE arg, VALUE callback_arg, int argc, const VALUE *argv, VALUE blockarg) {
+ return arg;
+}
+
+VALUE proc_spec_rb_proc_new_function_argc(VALUE arg, VALUE callback_arg, int argc, const VALUE *argv, VALUE blockarg) {
+ return INT2FIX(argc);
+}
+
+VALUE proc_spec_rb_proc_new_function_argv_n(VALUE arg, VALUE callback_arg, int argc, const VALUE *argv, VALUE blockarg) {
+ int n = FIX2INT(arg);
+ if (n < argc) {
+ return argv[n];
+ } else {
+ rb_exc_raise(rb_exc_new2(rb_eArgError, "Arg index out of bounds."));
+ }
+}
+
+VALUE proc_spec_rb_proc_new_function_callback_arg(VALUE arg, VALUE callback_arg, int argc, const VALUE *argv, VALUE blockarg) {
+ return callback_arg;
+}
+
+VALUE proc_spec_rb_proc_new_function_blockarg(VALUE arg, VALUE callback_arg, int argc, const VALUE *argv, VALUE blockarg) {
+ return blockarg;
+}
+
+VALUE proc_spec_rb_proc_new_function_block_given_p(VALUE arg, VALUE callback_arg, int argc, const VALUE *argv, VALUE blockarg) {
+ return rb_block_given_p() ? Qtrue : Qfalse;
+}
+
+VALUE proc_spec_rb_proc_new(VALUE self) {
+ return rb_proc_new(proc_spec_rb_proc_new_function, Qnil);
+}
+
+VALUE proc_spec_rb_proc_new_arg(VALUE self) {
+ return rb_proc_new(proc_spec_rb_proc_new_function_arg, Qnil);
+}
+
+VALUE proc_spec_rb_proc_new_argc(VALUE self) {
+ return rb_proc_new(proc_spec_rb_proc_new_function_argc, Qnil);
+}
+
+VALUE proc_spec_rb_proc_new_argv_n(VALUE self) {
+ return rb_proc_new(proc_spec_rb_proc_new_function_argv_n, Qnil);
+}
+
+VALUE proc_spec_rb_proc_new_callback_arg(VALUE self, VALUE arg) {
+ return rb_proc_new(proc_spec_rb_proc_new_function_callback_arg, arg);
+}
+
+VALUE proc_spec_rb_proc_new_blockarg(VALUE self) {
+ return rb_proc_new(proc_spec_rb_proc_new_function_blockarg, Qnil);
+}
+
+VALUE proc_spec_rb_proc_new_block_given_p(VALUE self) {
+ return rb_proc_new(proc_spec_rb_proc_new_function_block_given_p, Qnil);
+}
+
+VALUE proc_spec_rb_proc_arity(VALUE self, VALUE prc) {
+ return INT2FIX(rb_proc_arity(prc));
+}
+
+VALUE proc_spec_rb_proc_call(VALUE self, VALUE prc, VALUE args) {
+ return rb_proc_call(prc, args);
+}
+
+VALUE proc_spec_rb_obj_is_proc(VALUE self, VALUE prc) {
+ return rb_obj_is_proc(prc);
+}
+
+/* This helper is not strictly necessary but reflects the code in wxRuby that
+ * originally exposed issues with this Proc.new behavior.
+ */
+VALUE proc_spec_rb_Proc_new_helper(void) {
+ return rb_funcall(rb_cProc, rb_intern("new"), 0);
+}
+
+VALUE proc_spec_rb_Proc_new(VALUE self, VALUE scenario) {
+ switch(FIX2INT(scenario)) {
+ case 0:
+ return proc_spec_rb_Proc_new_helper();
+ case 1:
+ rb_funcall(self, rb_intern("call_nothing"), 0);
+ return proc_spec_rb_Proc_new_helper();
+ case 2:
+ return rb_funcall(self, rb_intern("call_Proc_new"), 0);
+ case 3:
+ return rb_funcall(self, rb_intern("call_rb_Proc_new"), 0);
+ case 4:
+ return rb_funcall(self, rb_intern("call_rb_Proc_new_with_block"), 0);
+ case 5:
+ rb_funcall(self, rb_intern("call_rb_Proc_new_with_block"), 0);
+ return proc_spec_rb_Proc_new_helper();
+ case 6:
+ return rb_funcall(self, rb_intern("call_block_given?"), 0);
+ default:
+ rb_raise(rb_eException, "invalid scenario");
+ }
+
+ return Qnil;
+}
+
+void Init_proc_spec(void) {
+ VALUE cls = rb_define_class("CApiProcSpecs", rb_cObject);
+ rb_define_method(cls, "rb_proc_new", proc_spec_rb_proc_new, 0);
+ rb_define_method(cls, "rb_proc_new_arg", proc_spec_rb_proc_new_arg, 0);
+ rb_define_method(cls, "rb_proc_new_argc", proc_spec_rb_proc_new_argc, 0);
+ rb_define_method(cls, "rb_proc_new_argv_n", proc_spec_rb_proc_new_argv_n, 0);
+ rb_define_method(cls, "rb_proc_new_callback_arg", proc_spec_rb_proc_new_callback_arg, 1);
+ rb_define_method(cls, "rb_proc_new_blockarg", proc_spec_rb_proc_new_blockarg, 0);
+ rb_define_method(cls, "rb_proc_new_block_given_p", proc_spec_rb_proc_new_block_given_p, 0);
+ rb_define_method(cls, "rb_proc_arity", proc_spec_rb_proc_arity, 1);
+ rb_define_method(cls, "rb_proc_call", proc_spec_rb_proc_call, 2);
+ rb_define_method(cls, "rb_Proc_new", proc_spec_rb_Proc_new, 1);
+ rb_define_method(cls, "rb_obj_is_proc", proc_spec_rb_obj_is_proc, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/range_spec.c b/spec/ruby/optional/capi/ext/range_spec.c
new file mode 100644
index 0000000000..7a475ec695
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/range_spec.c
@@ -0,0 +1,50 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE range_spec_rb_range_new(int argc, VALUE* argv, VALUE self) {
+ int exclude_end = 0;
+ if(argc == 3) {
+ exclude_end = RTEST(argv[2]);
+ }
+ return rb_range_new(argv[0], argv[1], exclude_end);
+}
+
+VALUE range_spec_rb_range_values(VALUE self, VALUE range) {
+ VALUE beg;
+ VALUE end;
+ int excl;
+ VALUE ary = rb_ary_new();
+ rb_range_values(range, &beg, &end, &excl);
+ rb_ary_store(ary, 0, beg);
+ rb_ary_store(ary, 1, end);
+ rb_ary_store(ary, 2, excl ? Qtrue : Qfalse);
+ return ary;
+}
+
+VALUE range_spec_rb_range_beg_len(VALUE self, VALUE range, VALUE begpv, VALUE lenpv, VALUE lenv, VALUE errv) {
+ long begp = FIX2LONG(begpv);
+ long lenp = FIX2LONG(lenpv);
+ long len = FIX2LONG(lenv);
+ int err = FIX2INT(errv);
+ VALUE ary = rb_ary_new();
+ VALUE res = rb_range_beg_len(range, &begp, &lenp, len, err);
+ rb_ary_store(ary, 0, LONG2FIX(begp));
+ rb_ary_store(ary, 1, LONG2FIX(lenp));
+ rb_ary_store(ary, 2, res);
+ return ary;
+}
+
+void Init_range_spec(void) {
+ VALUE cls = rb_define_class("CApiRangeSpecs", rb_cObject);
+ rb_define_method(cls, "rb_range_new", range_spec_rb_range_new, -1);
+ rb_define_method(cls, "rb_range_values", range_spec_rb_range_values, 1);
+ rb_define_method(cls, "rb_range_beg_len", range_spec_rb_range_beg_len, 5);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/rational_spec.c b/spec/ruby/optional/capi/ext/rational_spec.c
new file mode 100644
index 0000000000..6273af68c5
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/rational_spec.c
@@ -0,0 +1,54 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE rational_spec_rb_Rational(VALUE self, VALUE num, VALUE den) {
+ return rb_Rational(num, den);
+}
+
+static VALUE rational_spec_rb_Rational1(VALUE self, VALUE num) {
+ return rb_Rational1(num);
+}
+
+static VALUE rational_spec_rb_Rational2(VALUE self, VALUE num, VALUE den) {
+ return rb_Rational2(num, den);
+}
+
+static VALUE rational_spec_rb_rational_new(VALUE self, VALUE num, VALUE den) {
+ return rb_rational_new(num, den);
+}
+
+static VALUE rational_spec_rb_rational_new1(VALUE self, VALUE num) {
+ return rb_rational_new1(num);
+}
+
+static VALUE rational_spec_rb_rational_new2(VALUE self, VALUE num, VALUE den) {
+ return rb_rational_new2(num, den);
+}
+
+static VALUE rational_spec_rb_rational_num(VALUE self, VALUE rational) {
+ return rb_rational_num(rational);
+}
+
+static VALUE rational_spec_rb_rational_den(VALUE self, VALUE rational) {
+ return rb_rational_den(rational);
+}
+
+void Init_rational_spec(void) {
+ VALUE cls = rb_define_class("CApiRationalSpecs", rb_cObject);
+ rb_define_method(cls, "rb_Rational", rational_spec_rb_Rational, 2);
+ rb_define_method(cls, "rb_Rational1", rational_spec_rb_Rational1, 1);
+ rb_define_method(cls, "rb_Rational2", rational_spec_rb_Rational2, 2);
+ rb_define_method(cls, "rb_rational_new", rational_spec_rb_rational_new, 2);
+ rb_define_method(cls, "rb_rational_new1", rational_spec_rb_rational_new1, 1);
+ rb_define_method(cls, "rb_rational_new2", rational_spec_rb_rational_new2, 2);
+ rb_define_method(cls, "rb_rational_num", rational_spec_rb_rational_num, 1);
+ rb_define_method(cls, "rb_rational_den", rational_spec_rb_rational_den, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/rbasic_spec.c b/spec/ruby/optional/capi/ext/rbasic_spec.c
new file mode 100644
index 0000000000..9178e5f639
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/rbasic_spec.c
@@ -0,0 +1,100 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef FL_SHAREABLE
+static const VALUE VISIBLE_BITS = FL_TAINT | FL_FREEZE;
+static const VALUE DATA_VISIBLE_BITS = FL_TAINT | FL_FREEZE | ~(FL_USER0 - 1);
+#else
+static const VALUE VISIBLE_BITS = FL_FREEZE;
+static const VALUE DATA_VISIBLE_BITS = FL_FREEZE | ~(FL_USER0 - 1);
+#endif
+
+#if SIZEOF_VALUE == SIZEOF_LONG
+#define VALUE2NUM(v) ULONG2NUM(v)
+#define NUM2VALUE(n) NUM2ULONG(n)
+#elif SIZEOF_VALUE == SIZEOF_LONG_LONG
+#define VALUE2NUM(v) ULL2NUM(v)
+#define NUM2VALUE(n) NUM2ULL(n)
+#else
+#error "unsupported"
+#endif
+
+
+#ifndef RUBY_VERSION_IS_3_1
+VALUE rbasic_spec_taint_flag(VALUE self) {
+ return VALUE2NUM(RUBY_FL_TAINT);
+}
+#endif
+
+VALUE rbasic_spec_freeze_flag(VALUE self) {
+ return VALUE2NUM(RUBY_FL_FREEZE);
+}
+
+ static VALUE spec_get_flags(const struct RBasic *b, VALUE visible_bits) {
+ VALUE flags = b->flags & visible_bits;
+ return VALUE2NUM(flags);
+}
+
+static VALUE spec_set_flags(struct RBasic *b, VALUE flags, VALUE visible_bits) {
+ flags &= visible_bits;
+ b->flags = (b->flags & ~visible_bits) | flags;
+ return VALUE2NUM(flags);
+}
+
+VALUE rbasic_spec_get_flags(VALUE self, VALUE val) {
+ return spec_get_flags(RBASIC(val), VISIBLE_BITS);
+}
+
+VALUE rbasic_spec_set_flags(VALUE self, VALUE val, VALUE flags) {
+ return spec_set_flags(RBASIC(val), NUM2VALUE(flags), VISIBLE_BITS);
+}
+
+VALUE rbasic_spec_copy_flags(VALUE self, VALUE to, VALUE from) {
+ return spec_set_flags(RBASIC(to), RBASIC(from)->flags, VISIBLE_BITS);
+}
+
+VALUE rbasic_spec_get_klass(VALUE self, VALUE val) {
+ return RBASIC(val)->klass;
+}
+
+VALUE rbasic_rdata_spec_get_flags(VALUE self, VALUE structure) {
+ return spec_get_flags(&RDATA(structure)->basic, DATA_VISIBLE_BITS);
+}
+
+VALUE rbasic_rdata_spec_set_flags(VALUE self, VALUE structure, VALUE flags) {
+ return spec_set_flags(&RDATA(structure)->basic, NUM2VALUE(flags), DATA_VISIBLE_BITS);
+}
+
+VALUE rbasic_rdata_spec_copy_flags(VALUE self, VALUE to, VALUE from) {
+ return spec_set_flags(&RDATA(to)->basic, RDATA(from)->basic.flags, DATA_VISIBLE_BITS);
+}
+
+VALUE rbasic_rdata_spec_get_klass(VALUE self, VALUE structure) {
+ return RDATA(structure)->basic.klass;
+}
+
+void Init_rbasic_spec(void) {
+ VALUE cls = rb_define_class("CApiRBasicSpecs", rb_cObject);
+#ifndef RUBY_VERSION_IS_3_1
+ rb_define_method(cls, "taint_flag", rbasic_spec_taint_flag, 0);
+#endif
+ rb_define_method(cls, "freeze_flag", rbasic_spec_freeze_flag, 0);
+ rb_define_method(cls, "get_flags", rbasic_spec_get_flags, 1);
+ rb_define_method(cls, "set_flags", rbasic_spec_set_flags, 2);
+ rb_define_method(cls, "copy_flags", rbasic_spec_copy_flags, 2);
+ rb_define_method(cls, "get_klass", rbasic_spec_get_klass, 1);
+
+ cls = rb_define_class("CApiRBasicRDataSpecs", rb_cObject);
+ rb_define_method(cls, "get_flags", rbasic_rdata_spec_get_flags, 1);
+ rb_define_method(cls, "set_flags", rbasic_rdata_spec_set_flags, 2);
+ rb_define_method(cls, "copy_flags", rbasic_rdata_spec_copy_flags, 2);
+ rb_define_method(cls, "get_klass", rbasic_rdata_spec_get_klass, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/regexp_spec.c b/spec/ruby/optional/capi/ext/regexp_spec.c
new file mode 100644
index 0000000000..9de7982b50
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/regexp_spec.c
@@ -0,0 +1,74 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include "ruby/re.h"
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VALUE regexp_spec_re(VALUE self, VALUE str, VALUE options) {
+ char *cstr = StringValueCStr(str);
+ int opts = FIX2INT(options);
+ return rb_reg_new(cstr, strlen(cstr), opts);
+}
+
+VALUE regexp_spec_reg_1st_match(VALUE self, VALUE md) {
+ return rb_reg_nth_match(1, md);
+}
+
+VALUE regexp_spec_rb_reg_options(VALUE self, VALUE regexp) {
+ return INT2FIX(rb_reg_options(regexp));
+}
+
+VALUE regexp_spec_rb_reg_regcomp(VALUE self, VALUE str) {
+ return rb_reg_regcomp(str);
+}
+
+VALUE regexp_spec_reg_match(VALUE self, VALUE re, VALUE str) {
+ return rb_reg_match(re, str);
+}
+
+VALUE regexp_spec_backref_get(VALUE self) {
+ return rb_backref_get();
+}
+
+static VALUE regexp_spec_backref_set(VALUE self, VALUE backref) {
+ rb_backref_set(backref);
+ return Qnil;
+}
+
+VALUE regexp_spec_reg_match_backref_get(VALUE self, VALUE re, VALUE str) {
+ rb_reg_match(re, str);
+ return rb_backref_get();
+}
+
+VALUE regexp_spec_match(VALUE self, VALUE regexp, VALUE str) {
+ return rb_funcall(regexp, rb_intern("match"), 1, str);
+}
+
+VALUE regexp_spec_memcicmp(VALUE self, VALUE str1, VALUE str2) {
+ long l1 = RSTRING_LEN(str1);
+ long l2 = RSTRING_LEN(str2);
+ return INT2FIX(rb_memcicmp(RSTRING_PTR(str1), RSTRING_PTR(str2), l1 < l2 ? l1 : l2));
+}
+
+void Init_regexp_spec(void) {
+ VALUE cls = rb_define_class("CApiRegexpSpecs", rb_cObject);
+ rb_define_method(cls, "match", regexp_spec_match, 2);
+ rb_define_method(cls, "a_re", regexp_spec_re, 2);
+ rb_define_method(cls, "a_re_1st_match", regexp_spec_reg_1st_match, 1);
+ rb_define_method(cls, "rb_reg_match", regexp_spec_reg_match, 2);
+ rb_define_method(cls, "rb_backref_get", regexp_spec_backref_get, 0);
+ rb_define_method(cls, "rb_backref_set", regexp_spec_backref_set, 1);
+ rb_define_method(cls, "rb_reg_match_backref_get", regexp_spec_reg_match_backref_get, 2);
+ rb_define_method(cls, "rb_reg_options", regexp_spec_rb_reg_options, 1);
+ rb_define_method(cls, "rb_reg_regcomp", regexp_spec_rb_reg_regcomp, 1);
+ rb_define_method(cls, "rb_memcicmp", regexp_spec_memcicmp, 2);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/rubyspec.h b/spec/ruby/optional/capi/ext/rubyspec.h
new file mode 100644
index 0000000000..245669d200
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/rubyspec.h
@@ -0,0 +1,37 @@
+#ifndef RUBYSPEC_H
+#define RUBYSPEC_H
+
+/* Define convenience macros similar to the mspec
+ * guards to assist with version incompatibilities. */
+
+#include <ruby.h>
+#ifdef HAVE_RUBY_VERSION_H
+# include <ruby/version.h>
+#else
+# include <version.h>
+#endif
+
+#ifndef RUBY_VERSION_MAJOR
+#define RUBY_VERSION_MAJOR RUBY_API_VERSION_MAJOR
+#define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
+#define RUBY_VERSION_TEENY RUBY_API_VERSION_TEENY
+#endif
+
+#define RUBY_VERSION_BEFORE(major,minor,teeny) \
+ ((RUBY_VERSION_MAJOR < (major)) || \
+ (RUBY_VERSION_MAJOR == (major) && RUBY_VERSION_MINOR < (minor)) || \
+ (RUBY_VERSION_MAJOR == (major) && RUBY_VERSION_MINOR == (minor) && RUBY_VERSION_TEENY < (teeny)))
+
+#if RUBY_VERSION_MAJOR > 3 || (RUBY_VERSION_MAJOR == 3 && RUBY_VERSION_MINOR >= 2)
+#define RUBY_VERSION_IS_3_2
+#endif
+
+#if RUBY_VERSION_MAJOR > 3 || (RUBY_VERSION_MAJOR == 3 && RUBY_VERSION_MINOR >= 1)
+#define RUBY_VERSION_IS_3_1
+#endif
+
+#if RUBY_VERSION_MAJOR > 3 || (RUBY_VERSION_MAJOR == 3 && RUBY_VERSION_MINOR >= 0)
+#define RUBY_VERSION_IS_3_0
+#endif
+
+#endif
diff --git a/spec/ruby/optional/capi/ext/st_spec.c b/spec/ruby/optional/capi/ext/st_spec.c
new file mode 100644
index 0000000000..0fb5b5dc2d
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/st_spec.c
@@ -0,0 +1,83 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include <string.h>
+#include <stdarg.h>
+
+#include <ruby/st.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#if SIZEOF_LONG == SIZEOF_VOIDP
+# define ST2NUM(x) ULONG2NUM(x)
+#else
+# define ST2NUM(x) ULL2NUM(x)
+#endif
+
+VALUE st_spec_st_init_numtable(VALUE self) {
+ st_table *tbl = st_init_numtable();
+ st_index_t entries = tbl->num_entries;
+ st_free_table(tbl);
+ return ST2NUM(entries);
+}
+
+VALUE st_spec_st_init_numtable_with_size(VALUE self) {
+ st_table *tbl = st_init_numtable_with_size(128);
+ st_index_t entries = tbl->num_entries;
+ st_free_table(tbl);
+ return ST2NUM(entries);
+}
+
+VALUE st_spec_st_insert(VALUE self) {
+ st_index_t entries;
+ st_table *tbl = st_init_numtable_with_size(128);
+ st_insert(tbl, 1, 1);
+ entries = tbl->num_entries;
+ st_free_table(tbl);
+ return ST2NUM(entries);
+}
+
+static int sum(st_data_t key, st_data_t value, st_data_t arg) {
+ *(int*)arg += (int)value;
+ return ST_CONTINUE;
+}
+
+VALUE st_spec_st_foreach(VALUE self) {
+ int total = 0;
+ st_table *tbl = st_init_numtable_with_size(128);
+ st_insert(tbl, 1, 3);
+ st_insert(tbl, 2, 4);
+ st_foreach(tbl, sum, (st_data_t)&total);
+ st_free_table(tbl);
+ return INT2FIX(total);
+}
+
+VALUE st_spec_st_lookup(VALUE self) {
+ st_data_t result = (st_data_t)0;
+ st_table *tbl = st_init_numtable_with_size(128);
+ st_insert(tbl, 7, 42);
+ st_insert(tbl, 2, 4);
+ st_lookup(tbl, (st_data_t)7, &result);
+ st_free_table(tbl);
+#if SIZEOF_LONG == SIZEOF_VOIDP
+ return ULONG2NUM(result);
+#else
+ return ULL2NUM(result);
+#endif
+}
+
+void Init_st_spec(void) {
+ VALUE cls = rb_define_class("CApiStSpecs", rb_cObject);
+ rb_define_method(cls, "st_init_numtable", st_spec_st_init_numtable, 0);
+ rb_define_method(cls, "st_init_numtable_with_size", st_spec_st_init_numtable_with_size, 0);
+ rb_define_method(cls, "st_insert", st_spec_st_insert, 0);
+ rb_define_method(cls, "st_foreach", st_spec_st_foreach, 0);
+ rb_define_method(cls, "st_lookup", st_spec_st_lookup, 0);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/string_spec.c b/spec/ruby/optional/capi/ext/string_spec.c
new file mode 100644
index 0000000000..a858936243
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/string_spec.c
@@ -0,0 +1,719 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include <fcntl.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include "ruby/encoding.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Make sure the RSTRING_PTR and the bytes are in native memory.
+ * On TruffleRuby RSTRING_PTR and the bytes remain in managed memory
+ * until they must be written to native memory.
+ * In some specs we want to test using the native memory. */
+#ifndef NATIVE_RSTRING_PTR
+#define NATIVE_RSTRING_PTR(str) RSTRING_PTR(str)
+#endif
+
+VALUE string_spec_rb_cstr2inum(VALUE self, VALUE str, VALUE inum) {
+ int num = FIX2INT(inum);
+ return rb_cstr2inum(RSTRING_PTR(str), num);
+}
+
+static VALUE string_spec_rb_cstr_to_inum(VALUE self, VALUE str, VALUE inum, VALUE badcheck) {
+ int num = FIX2INT(inum);
+ return rb_cstr_to_inum(RSTRING_PTR(str), num, RTEST(badcheck));
+}
+
+VALUE string_spec_rb_str2inum(VALUE self, VALUE str, VALUE inum) {
+ int num = FIX2INT(inum);
+ return rb_str2inum(str, num);
+}
+
+VALUE string_spec_rb_str_append(VALUE self, VALUE str, VALUE str2) {
+ return rb_str_append(str, str2);
+}
+
+VALUE string_spec_rb_str_set_len(VALUE self, VALUE str, VALUE len) {
+ rb_str_set_len(str, NUM2LONG(len));
+
+ return str;
+}
+
+VALUE string_spec_rb_str_set_len_RSTRING_LEN(VALUE self, VALUE str, VALUE len) {
+ rb_str_set_len(str, NUM2LONG(len));
+
+ return INT2FIX(RSTRING_LEN(str));
+}
+
+VALUE rb_fstring(VALUE str); /* internal.h, used in ripper */
+
+VALUE string_spec_rb_str_fstring(VALUE self, VALUE str) {
+ return rb_fstring(str);
+}
+
+VALUE string_spec_rb_str_buf_new(VALUE self, VALUE len, VALUE str) {
+ VALUE buf;
+
+ buf = rb_str_buf_new(NUM2LONG(len));
+
+ if(RTEST(str)) {
+ snprintf(RSTRING_PTR(buf), NUM2LONG(len), "%s", RSTRING_PTR(str));
+ }
+
+ return buf;
+}
+
+VALUE string_spec_rb_str_capacity(VALUE self, VALUE str) {
+ return SIZET2NUM(rb_str_capacity(str));
+}
+
+VALUE string_spec_rb_str_buf_new2(VALUE self) {
+ return rb_str_buf_new2("hello\0invisible");
+}
+
+VALUE string_spec_rb_str_tmp_new(VALUE self, VALUE len) {
+ VALUE str = rb_str_tmp_new(NUM2LONG(len));
+ rb_obj_reveal(str, rb_cString);
+ return str;
+}
+
+VALUE string_spec_rb_str_tmp_new_klass(VALUE self, VALUE len) {
+ return RBASIC_CLASS(rb_str_tmp_new(NUM2LONG(len)));
+}
+
+VALUE string_spec_rb_str_buf_append(VALUE self, VALUE str, VALUE two) {
+ return rb_str_buf_append(str, two);
+}
+
+VALUE string_spec_rb_str_buf_cat(VALUE self, VALUE str) {
+ const char *question_mark = "?";
+ rb_str_buf_cat(str, question_mark, strlen(question_mark));
+ return str;
+}
+
+VALUE string_spec_rb_enc_str_buf_cat(VALUE self, VALUE str, VALUE other, VALUE encoding) {
+ char *cstr = StringValueCStr(other);
+ rb_encoding* enc = rb_to_encoding(encoding);
+ return rb_enc_str_buf_cat(str, cstr, strlen(cstr), enc);
+}
+
+VALUE string_spec_rb_str_cat(VALUE self, VALUE str) {
+ return rb_str_cat(str, "?", 1);
+}
+
+VALUE string_spec_rb_str_cat2(VALUE self, VALUE str) {
+ return rb_str_cat2(str, "?");
+}
+
+VALUE string_spec_rb_str_cat_cstr(VALUE self, VALUE str, VALUE other) {
+ return rb_str_cat_cstr(str, StringValueCStr(other));
+}
+
+VALUE string_spec_rb_str_cat_cstr_constant(VALUE self, VALUE str) {
+ return rb_str_cat_cstr(str, "?");
+}
+
+VALUE string_spec_rb_str_cmp(VALUE self, VALUE str1, VALUE str2) {
+ return INT2NUM(rb_str_cmp(str1, str2));
+}
+
+VALUE string_spec_rb_str_conv_enc(VALUE self, VALUE str, VALUE from, VALUE to) {
+ rb_encoding* from_enc;
+ rb_encoding* to_enc;
+
+ from_enc = rb_to_encoding(from);
+
+ if(NIL_P(to)) {
+ to_enc = 0;
+ } else {
+ to_enc = rb_to_encoding(to);
+ }
+
+ return rb_str_conv_enc(str, from_enc, to_enc);
+}
+
+VALUE string_spec_rb_str_conv_enc_opts(VALUE self, VALUE str, VALUE from, VALUE to,
+ VALUE ecflags, VALUE ecopts)
+{
+ rb_encoding* from_enc;
+ rb_encoding* to_enc;
+
+ from_enc = rb_to_encoding(from);
+
+ if(NIL_P(to)) {
+ to_enc = 0;
+ } else {
+ to_enc = rb_to_encoding(to);
+ }
+
+ return rb_str_conv_enc_opts(str, from_enc, to_enc, FIX2INT(ecflags), ecopts);
+}
+
+VALUE string_spec_rb_str_drop_bytes(VALUE self, VALUE str, VALUE len) {
+ return rb_str_drop_bytes(str, NUM2LONG(len));
+}
+
+VALUE string_spec_rb_str_export(VALUE self, VALUE str) {
+ return rb_str_export(str);
+}
+
+VALUE string_spec_rb_str_export_locale(VALUE self, VALUE str) {
+ return rb_str_export_locale(str);
+}
+
+VALUE string_spec_rb_str_dup(VALUE self, VALUE str) {
+ return rb_str_dup(str);
+}
+
+VALUE string_spec_rb_str_freeze(VALUE self, VALUE str) {
+ return rb_str_freeze(str);
+}
+
+VALUE string_spec_rb_str_inspect(VALUE self, VALUE str) {
+ return rb_str_inspect(str);
+}
+
+VALUE string_spec_rb_str_intern(VALUE self, VALUE str) {
+ return rb_str_intern(str);
+}
+
+VALUE string_spec_rb_str_length(VALUE self, VALUE str) {
+ return rb_str_length(str);
+}
+
+VALUE string_spec_rb_str_new(VALUE self, VALUE str, VALUE len) {
+ return rb_str_new(RSTRING_PTR(str), FIX2INT(len));
+}
+
+VALUE string_spec_rb_str_new_native(VALUE self, VALUE str, VALUE len) {
+ return rb_str_new(NATIVE_RSTRING_PTR(str), FIX2INT(len));
+}
+
+VALUE string_spec_rb_str_new_offset(VALUE self, VALUE str, VALUE offset, VALUE len) {
+ return rb_str_new(RSTRING_PTR(str) + FIX2INT(offset), FIX2INT(len));
+}
+
+VALUE string_spec_rb_str_new2(VALUE self, VALUE str) {
+ if(NIL_P(str)) {
+ return rb_str_new2("");
+ } else {
+ return rb_str_new2(RSTRING_PTR(str));
+ }
+}
+
+VALUE string_spec_rb_str_encode(VALUE self, VALUE str, VALUE enc, VALUE flags, VALUE opts) {
+ return rb_str_encode(str, enc, FIX2INT(flags), opts);
+}
+
+VALUE string_spec_rb_str_export_to_enc(VALUE self, VALUE str, VALUE enc) {
+ return rb_str_export_to_enc(str, rb_to_encoding(enc));
+}
+
+VALUE string_spec_rb_str_new_cstr(VALUE self, VALUE str) {
+ if(NIL_P(str)) {
+ return rb_str_new_cstr("");
+ } else {
+ return rb_str_new_cstr(RSTRING_PTR(str));
+ }
+}
+
+VALUE string_spec_rb_external_str_new(VALUE self, VALUE str) {
+ return rb_external_str_new(RSTRING_PTR(str), RSTRING_LEN(str));
+}
+
+VALUE string_spec_rb_external_str_new_cstr(VALUE self, VALUE str) {
+ return rb_external_str_new_cstr(RSTRING_PTR(str));
+}
+
+VALUE string_spec_rb_external_str_new_with_enc(VALUE self, VALUE str, VALUE len, VALUE encoding) {
+ return rb_external_str_new_with_enc(RSTRING_PTR(str), FIX2LONG(len), rb_to_encoding(encoding));
+}
+
+VALUE string_spec_rb_locale_str_new(VALUE self, VALUE str, VALUE len) {
+ return rb_locale_str_new(RSTRING_PTR(str), FIX2INT(len));
+}
+
+VALUE string_spec_rb_locale_str_new_cstr(VALUE self, VALUE str) {
+ return rb_locale_str_new_cstr(RSTRING_PTR(str));
+}
+
+VALUE string_spec_rb_str_new3(VALUE self, VALUE str) {
+ return rb_str_new3(str);
+}
+
+VALUE string_spec_rb_str_new4(VALUE self, VALUE str) {
+ return rb_str_new4(str);
+}
+
+VALUE string_spec_rb_str_new5(VALUE self, VALUE str, VALUE ptr, VALUE len) {
+ return rb_str_new5(str, RSTRING_PTR(ptr), FIX2INT(len));
+}
+
+#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(__clang__) && defined(__has_warning)
+# if __has_warning("-Wdeprecated-declarations")
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdeprecated-declarations"
+# endif
+#endif
+
+#ifndef RUBY_VERSION_IS_3_2
+VALUE string_spec_rb_tainted_str_new(VALUE self, VALUE str, VALUE len) {
+ return rb_tainted_str_new(RSTRING_PTR(str), FIX2INT(len));
+}
+
+VALUE string_spec_rb_tainted_str_new2(VALUE self, VALUE str) {
+ return rb_tainted_str_new2(RSTRING_PTR(str));
+}
+#endif
+
+#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+# pragma GCC diagnostic pop
+#elif defined(__clang__) && defined(__has_warning)
+# if __has_warning("-Wdeprecated-declarations")
+# pragma clang diagnostic pop
+# endif
+#endif
+
+VALUE string_spec_rb_str_plus(VALUE self, VALUE str1, VALUE str2) {
+ return rb_str_plus(str1, str2);
+}
+
+VALUE string_spec_rb_str_times(VALUE self, VALUE str, VALUE times) {
+ return rb_str_times(str, times);
+}
+
+VALUE string_spec_rb_str_modify_expand(VALUE self, VALUE str, VALUE size) {
+ rb_str_modify_expand(str, FIX2LONG(size));
+ return str;
+}
+
+VALUE string_spec_rb_str_resize(VALUE self, VALUE str, VALUE size) {
+ return rb_str_resize(str, FIX2INT(size));
+}
+
+VALUE string_spec_rb_str_resize_RSTRING_LEN(VALUE self, VALUE str, VALUE size) {
+ VALUE modified = rb_str_resize(str, FIX2INT(size));
+ return INT2FIX(RSTRING_LEN(modified));
+}
+
+VALUE string_spec_rb_str_resize_copy(VALUE self, VALUE str) {
+ rb_str_modify_expand(str, 5);
+ char *buffer = RSTRING_PTR(str);
+ buffer[1] = 'e';
+ buffer[2] = 's';
+ buffer[3] = 't';
+ rb_str_resize(str, 4);
+ return str;
+}
+
+VALUE string_spec_rb_str_split(VALUE self, VALUE str) {
+ return rb_str_split(str, ",");
+}
+
+VALUE string_spec_rb_str_subseq(VALUE self, VALUE str, VALUE beg, VALUE len) {
+ return rb_str_subseq(str, FIX2INT(beg), FIX2INT(len));
+}
+
+VALUE string_spec_rb_str_substr(VALUE self, VALUE str, VALUE beg, VALUE len) {
+ return rb_str_substr(str, FIX2INT(beg), FIX2INT(len));
+}
+
+VALUE string_spec_rb_str_to_str(VALUE self, VALUE arg) {
+ return rb_str_to_str(arg);
+}
+
+VALUE string_spec_RSTRING_LEN(VALUE self, VALUE str) {
+ return INT2FIX(RSTRING_LEN(str));
+}
+
+VALUE string_spec_RSTRING_LENINT(VALUE self, VALUE str) {
+ return INT2FIX(RSTRING_LENINT(str));
+}
+
+VALUE string_spec_RSTRING_PTR_iterate(VALUE self, VALUE str) {
+ int i;
+ char* ptr;
+
+ ptr = RSTRING_PTR(str);
+ for(i = 0; i < RSTRING_LEN(str); i++) {
+ rb_yield(CHR2FIX(ptr[i]));
+ }
+ return Qnil;
+}
+
+VALUE string_spec_RSTRING_PTR_iterate_uint32(VALUE self, VALUE str) {
+ uint32_t* ptr;
+ long i, l = RSTRING_LEN(str) / sizeof(uint32_t);
+
+ ptr = (uint32_t *)RSTRING_PTR(str);
+ for(i = 0; i < l; i++) {
+ rb_yield(UINT2NUM(ptr[i]));
+ }
+ return Qnil;
+}
+
+VALUE string_spec_RSTRING_PTR_short_memcpy(VALUE self, VALUE str) {
+ /* Short memcpy operations may be optimised by the compiler to a single write. */
+ if (RSTRING_LEN(str) >= 8) {
+ memcpy(RSTRING_PTR(str), "Infinity", 8);
+ }
+ return str;
+}
+
+VALUE string_spec_RSTRING_PTR_assign(VALUE self, VALUE str, VALUE chr) {
+ int i;
+ char c;
+ char* ptr;
+
+ ptr = RSTRING_PTR(str);
+ c = FIX2INT(chr);
+
+ for(i = 0; i < RSTRING_LEN(str); i++) {
+ ptr[i] = c;
+ }
+ return Qnil;
+}
+
+VALUE string_spec_RSTRING_PTR_set(VALUE self, VALUE str, VALUE i, VALUE chr) {
+ RSTRING_PTR(str)[FIX2INT(i)] = (char) FIX2INT(chr);
+ return str;
+}
+
+VALUE string_spec_RSTRING_PTR_after_funcall(VALUE self, VALUE str, VALUE cb) {
+ /* Silence gcc 4.3.2 warning about computed value not used */
+ if(RSTRING_PTR(str)) { /* force it out */
+ rb_funcall(cb, rb_intern("call"), 1, str);
+ }
+
+ return rb_str_new2(RSTRING_PTR(str));
+}
+
+VALUE string_spec_RSTRING_PTR_after_yield(VALUE self, VALUE str) {
+ char* ptr = NATIVE_RSTRING_PTR(str);
+ long len = RSTRING_LEN(str);
+ VALUE from_rstring_ptr;
+
+ ptr[0] = '1';
+ rb_yield(str);
+ ptr[2] = '2';
+
+ from_rstring_ptr = rb_str_new(ptr, len);
+ return from_rstring_ptr;
+}
+
+VALUE string_spec_RSTRING_PTR_read(VALUE self, VALUE str, VALUE path) {
+ char *cpath = StringValueCStr(path);
+ int fd = open(cpath, O_RDONLY);
+ VALUE capacities = rb_ary_new();
+ if (fd < 0) {
+ rb_syserr_fail(errno, "open");
+ }
+
+ rb_str_modify_expand(str, 30);
+ rb_ary_push(capacities, SIZET2NUM(rb_str_capacity(str)));
+ char *buffer = RSTRING_PTR(str);
+ if (read(fd, buffer, 30) < 0) {
+ rb_syserr_fail(errno, "read");
+ }
+
+ rb_str_modify_expand(str, 53);
+ rb_ary_push(capacities, SIZET2NUM(rb_str_capacity(str)));
+ char *buffer2 = RSTRING_PTR(str);
+ if (read(fd, buffer2 + 30, 53 - 30) < 0) {
+ rb_syserr_fail(errno, "read");
+ }
+
+ rb_str_set_len(str, 53);
+ close(fd);
+ return capacities;
+}
+
+VALUE string_spec_RSTRING_PTR_null_terminate(VALUE self, VALUE str, VALUE min_length) {
+ char* ptr = RSTRING_PTR(str);
+ char* end = ptr + RSTRING_LEN(str);
+ return rb_str_new(end, FIX2LONG(min_length));
+}
+
+VALUE string_spec_StringValue(VALUE self, VALUE str) {
+ return StringValue(str);
+}
+
+static VALUE string_spec_SafeStringValue(VALUE self, VALUE str) {
+ SafeStringValue(str);
+ return str;
+}
+
+static VALUE string_spec_rb_str_hash(VALUE self, VALUE str) {
+ st_index_t val = rb_str_hash(str);
+
+ return ST2FIX(val);
+}
+
+static VALUE string_spec_rb_str_update(VALUE self, VALUE str, VALUE beg, VALUE end, VALUE replacement) {
+ rb_str_update(str, FIX2LONG(beg), FIX2LONG(end), replacement);
+ return str;
+}
+
+static VALUE string_spec_rb_str_free(VALUE self, VALUE str) {
+ rb_str_free(str);
+ return Qnil;
+}
+
+static VALUE string_spec_rb_sprintf1(VALUE self, VALUE str, VALUE repl) {
+ return rb_sprintf(RSTRING_PTR(str), RSTRING_PTR(repl));
+}
+static VALUE string_spec_rb_sprintf2(VALUE self, VALUE str, VALUE repl1, VALUE repl2) {
+ return rb_sprintf(RSTRING_PTR(str), RSTRING_PTR(repl1), RSTRING_PTR(repl2));
+}
+
+static VALUE string_spec_rb_sprintf3(VALUE self, VALUE str) {
+ return rb_sprintf("Result: %" PRIsVALUE ".", str);
+}
+
+static VALUE string_spec_rb_sprintf4(VALUE self, VALUE str) {
+ return rb_sprintf("Result: %+" PRIsVALUE ".", str);
+}
+
+static VALUE string_spec_rb_sprintf5(VALUE self, VALUE width, VALUE precision, VALUE str) {
+ return rb_sprintf("Result: %*.*s.", FIX2INT(width), FIX2INT(precision), RSTRING_PTR(str));
+}
+
+static VALUE string_spec_rb_sprintf6(VALUE self, VALUE width, VALUE precision, VALUE str) {
+ return rb_sprintf("Result: %*.*" PRIsVALUE ".", FIX2INT(width), FIX2INT(precision), str);
+}
+
+static VALUE string_spec_rb_sprintf7(VALUE self, VALUE str, VALUE obj) {
+ VALUE results = rb_ary_new();
+ rb_ary_push(results, rb_sprintf(RSTRING_PTR(str), obj));
+ char cstr[256];
+ int len = snprintf(cstr, 256, RSTRING_PTR(str), obj);
+ rb_ary_push(results, rb_str_new(cstr, len));
+ return results;
+}
+
+static VALUE string_spec_rb_sprintf8(VALUE self, VALUE str, VALUE num) {
+ VALUE results = rb_ary_new();
+ rb_ary_push(results, rb_sprintf(RSTRING_PTR(str), FIX2LONG(num)));
+ char cstr[256];
+ int len = snprintf(cstr, 256, RSTRING_PTR(str), FIX2LONG(num));
+ rb_ary_push(results, rb_str_new(cstr, len));
+ return results;
+}
+
+PRINTF_ARGS(static VALUE string_spec_rb_vsprintf_worker(char* fmt, ...), 1, 2);
+static VALUE string_spec_rb_vsprintf_worker(char* fmt, ...) {
+ va_list varargs;
+ VALUE str;
+
+ va_start(varargs, fmt);
+ str = rb_vsprintf(fmt, varargs);
+ va_end(varargs);
+
+ return str;
+}
+
+static VALUE string_spec_rb_vsprintf(VALUE self, VALUE fmt, VALUE str, VALUE i, VALUE f) {
+ return string_spec_rb_vsprintf_worker(RSTRING_PTR(fmt), RSTRING_PTR(str),
+ FIX2INT(i), RFLOAT_VALUE(f));
+}
+
+VALUE string_spec_rb_str_equal(VALUE self, VALUE str1, VALUE str2) {
+ return rb_str_equal(str1, str2);
+}
+
+static VALUE string_spec_rb_usascii_str_new(VALUE self, VALUE str, VALUE len) {
+ return rb_usascii_str_new(RSTRING_PTR(str), NUM2INT(len));
+}
+
+static VALUE string_spec_rb_usascii_str_new_lit(VALUE self) {
+ return rb_usascii_str_new_lit("nokogiri");
+}
+
+static VALUE string_spec_rb_usascii_str_new_lit_non_ascii(VALUE self) {
+ return rb_usascii_str_new_lit("r\xc3\xa9sum\xc3\xa9");
+}
+
+static VALUE string_spec_rb_usascii_str_new_cstr(VALUE self, VALUE str) {
+ return rb_usascii_str_new_cstr(RSTRING_PTR(str));
+}
+
+static VALUE string_spec_rb_String(VALUE self, VALUE val) {
+ return rb_String(val);
+}
+
+static VALUE string_spec_rb_string_value_cstr(VALUE self, VALUE str) {
+ char *c_str = rb_string_value_cstr(&str);
+ return c_str ? Qtrue : Qfalse;
+}
+
+static VALUE string_spec_rb_str_modify(VALUE self, VALUE str) {
+ rb_str_modify(str);
+ return str;
+}
+
+static VALUE string_spec_rb_utf8_str_new_static(VALUE self) {
+ return rb_utf8_str_new_static("nokogiri", 8);
+}
+
+static VALUE string_spec_rb_utf8_str_new(VALUE self) {
+ return rb_utf8_str_new("nokogiri", 8);
+}
+
+static VALUE string_spec_rb_utf8_str_new_cstr(VALUE self) {
+ return rb_utf8_str_new_cstr("nokogiri");
+}
+
+PRINTF_ARGS(static VALUE call_rb_str_vcatf(VALUE mesg, const char *fmt, ...), 2, 3);
+static VALUE call_rb_str_vcatf(VALUE mesg, const char *fmt, ...){
+ va_list ap;
+ va_start(ap, fmt);
+ VALUE result = rb_str_vcatf(mesg, fmt, ap);
+ va_end(ap);
+ return result;
+}
+
+static VALUE string_spec_rb_str_vcatf(VALUE self, VALUE mesg) {
+ return call_rb_str_vcatf(mesg, "fmt %d %d number", 42, 7);
+}
+
+static VALUE string_spec_rb_str_catf(VALUE self, VALUE mesg) {
+ return rb_str_catf(mesg, "fmt %d %d number", 41, 6);
+}
+
+static VALUE string_spec_rb_str_locktmp(VALUE self, VALUE str) {
+ return rb_str_locktmp(str);
+}
+
+static VALUE string_spec_rb_str_unlocktmp(VALUE self, VALUE str) {
+ return rb_str_unlocktmp(str);
+}
+
+VALUE rb_str_to_interned_str(VALUE str);
+VALUE rb_enc_interned_str_cstr(const char *ptr, rb_encoding *enc);
+
+static VALUE string_spec_rb_enc_interned_str_cstr(VALUE self, VALUE str, VALUE enc) {
+ rb_encoding *e = NIL_P(enc) ? 0 : rb_to_encoding(enc);
+ return rb_enc_interned_str_cstr(RSTRING_PTR(str), e);
+}
+
+static VALUE string_spec_rb_str_to_interned_str(VALUE self, VALUE str) {
+ return rb_str_to_interned_str(str);
+}
+
+void Init_string_spec(void) {
+ VALUE cls = rb_define_class("CApiStringSpecs", rb_cObject);
+ rb_define_method(cls, "rb_cstr2inum", string_spec_rb_cstr2inum, 2);
+ rb_define_method(cls, "rb_cstr_to_inum", string_spec_rb_cstr_to_inum, 3);
+ rb_define_method(cls, "rb_fstring", string_spec_rb_str_fstring, 1);
+ rb_define_method(cls, "rb_str2inum", string_spec_rb_str2inum, 2);
+ rb_define_method(cls, "rb_str_append", string_spec_rb_str_append, 2);
+ rb_define_method(cls, "rb_str_buf_new", string_spec_rb_str_buf_new, 2);
+ rb_define_method(cls, "rb_str_capacity", string_spec_rb_str_capacity, 1);
+ rb_define_method(cls, "rb_str_buf_new2", string_spec_rb_str_buf_new2, 0);
+ rb_define_method(cls, "rb_str_tmp_new", string_spec_rb_str_tmp_new, 1);
+ rb_define_method(cls, "rb_str_tmp_new_klass", string_spec_rb_str_tmp_new_klass, 1);
+ rb_define_method(cls, "rb_str_buf_append", string_spec_rb_str_buf_append, 2);
+ rb_define_method(cls, "rb_str_buf_cat", string_spec_rb_str_buf_cat, 1);
+ rb_define_method(cls, "rb_enc_str_buf_cat", string_spec_rb_enc_str_buf_cat, 3);
+ rb_define_method(cls, "rb_str_cat", string_spec_rb_str_cat, 1);
+ rb_define_method(cls, "rb_str_cat2", string_spec_rb_str_cat2, 1);
+ rb_define_method(cls, "rb_str_cat_cstr", string_spec_rb_str_cat_cstr, 2);
+ rb_define_method(cls, "rb_str_cat_cstr_constant", string_spec_rb_str_cat_cstr_constant, 1);
+ rb_define_method(cls, "rb_str_cmp", string_spec_rb_str_cmp, 2);
+ rb_define_method(cls, "rb_str_conv_enc", string_spec_rb_str_conv_enc, 3);
+ rb_define_method(cls, "rb_str_conv_enc_opts", string_spec_rb_str_conv_enc_opts, 5);
+ rb_define_method(cls, "rb_str_drop_bytes", string_spec_rb_str_drop_bytes, 2);
+ rb_define_method(cls, "rb_str_export", string_spec_rb_str_export, 1);
+ rb_define_method(cls, "rb_str_export_locale", string_spec_rb_str_export_locale, 1);
+ rb_define_method(cls, "rb_str_dup", string_spec_rb_str_dup, 1);
+ rb_define_method(cls, "rb_str_freeze", string_spec_rb_str_freeze, 1);
+ rb_define_method(cls, "rb_str_inspect", string_spec_rb_str_inspect, 1);
+ rb_define_method(cls, "rb_str_intern", string_spec_rb_str_intern, 1);
+ rb_define_method(cls, "rb_str_length", string_spec_rb_str_length, 1);
+ rb_define_method(cls, "rb_str_new", string_spec_rb_str_new, 2);
+ rb_define_method(cls, "rb_str_new_native", string_spec_rb_str_new_native, 2);
+ rb_define_method(cls, "rb_str_new_offset", string_spec_rb_str_new_offset, 3);
+ rb_define_method(cls, "rb_str_new2", string_spec_rb_str_new2, 1);
+ rb_define_method(cls, "rb_str_encode", string_spec_rb_str_encode, 4);
+ rb_define_method(cls, "rb_str_export_to_enc", string_spec_rb_str_export_to_enc, 2);
+ rb_define_method(cls, "rb_str_new_cstr", string_spec_rb_str_new_cstr, 1);
+ rb_define_method(cls, "rb_external_str_new", string_spec_rb_external_str_new, 1);
+ rb_define_method(cls, "rb_external_str_new_cstr", string_spec_rb_external_str_new_cstr, 1);
+ rb_define_method(cls, "rb_external_str_new_with_enc", string_spec_rb_external_str_new_with_enc, 3);
+ rb_define_method(cls, "rb_locale_str_new", string_spec_rb_locale_str_new, 2);
+ rb_define_method(cls, "rb_locale_str_new_cstr", string_spec_rb_locale_str_new_cstr, 1);
+ rb_define_method(cls, "rb_str_new3", string_spec_rb_str_new3, 1);
+ rb_define_method(cls, "rb_str_new4", string_spec_rb_str_new4, 1);
+ rb_define_method(cls, "rb_str_new5", string_spec_rb_str_new5, 3);
+#ifndef RUBY_VERSION_IS_3_2
+ rb_define_method(cls, "rb_tainted_str_new", string_spec_rb_tainted_str_new, 2);
+ rb_define_method(cls, "rb_tainted_str_new2", string_spec_rb_tainted_str_new2, 1);
+#endif
+ rb_define_method(cls, "rb_str_plus", string_spec_rb_str_plus, 2);
+ rb_define_method(cls, "rb_str_times", string_spec_rb_str_times, 2);
+ rb_define_method(cls, "rb_str_modify_expand", string_spec_rb_str_modify_expand, 2);
+ rb_define_method(cls, "rb_str_resize", string_spec_rb_str_resize, 2);
+ rb_define_method(cls, "rb_str_resize_RSTRING_LEN", string_spec_rb_str_resize_RSTRING_LEN, 2);
+ rb_define_method(cls, "rb_str_resize_copy", string_spec_rb_str_resize_copy, 1);
+ rb_define_method(cls, "rb_str_set_len", string_spec_rb_str_set_len, 2);
+ rb_define_method(cls, "rb_str_set_len_RSTRING_LEN", string_spec_rb_str_set_len_RSTRING_LEN, 2);
+ rb_define_method(cls, "rb_str_split", string_spec_rb_str_split, 1);
+ rb_define_method(cls, "rb_str_subseq", string_spec_rb_str_subseq, 3);
+ rb_define_method(cls, "rb_str_substr", string_spec_rb_str_substr, 3);
+ rb_define_method(cls, "rb_str_to_str", string_spec_rb_str_to_str, 1);
+ rb_define_method(cls, "RSTRING_LEN", string_spec_RSTRING_LEN, 1);
+ rb_define_method(cls, "RSTRING_LENINT", string_spec_RSTRING_LENINT, 1);
+ rb_define_method(cls, "RSTRING_PTR_iterate", string_spec_RSTRING_PTR_iterate, 1);
+ rb_define_method(cls, "RSTRING_PTR_iterate_uint32", string_spec_RSTRING_PTR_iterate_uint32, 1);
+ rb_define_method(cls, "RSTRING_PTR_short_memcpy", string_spec_RSTRING_PTR_short_memcpy, 1);
+ rb_define_method(cls, "RSTRING_PTR_assign", string_spec_RSTRING_PTR_assign, 2);
+ rb_define_method(cls, "RSTRING_PTR_set", string_spec_RSTRING_PTR_set, 3);
+ rb_define_method(cls, "RSTRING_PTR_after_funcall", string_spec_RSTRING_PTR_after_funcall, 2);
+ rb_define_method(cls, "RSTRING_PTR_after_yield", string_spec_RSTRING_PTR_after_yield, 1);
+ rb_define_method(cls, "RSTRING_PTR_read", string_spec_RSTRING_PTR_read, 2);
+ rb_define_method(cls, "RSTRING_PTR_null_terminate", string_spec_RSTRING_PTR_null_terminate, 2);
+ rb_define_method(cls, "StringValue", string_spec_StringValue, 1);
+ rb_define_method(cls, "SafeStringValue", string_spec_SafeStringValue, 1);
+ rb_define_method(cls, "rb_str_hash", string_spec_rb_str_hash, 1);
+ rb_define_method(cls, "rb_str_update", string_spec_rb_str_update, 4);
+ rb_define_method(cls, "rb_str_free", string_spec_rb_str_free, 1);
+ rb_define_method(cls, "rb_sprintf1", string_spec_rb_sprintf1, 2);
+ rb_define_method(cls, "rb_sprintf2", string_spec_rb_sprintf2, 3);
+ rb_define_method(cls, "rb_sprintf3", string_spec_rb_sprintf3, 1);
+ rb_define_method(cls, "rb_sprintf4", string_spec_rb_sprintf4, 1);
+ rb_define_method(cls, "rb_sprintf5", string_spec_rb_sprintf5, 3);
+ rb_define_method(cls, "rb_sprintf6", string_spec_rb_sprintf6, 3);
+ rb_define_method(cls, "rb_sprintf7", string_spec_rb_sprintf7, 2);
+ rb_define_method(cls, "rb_sprintf8", string_spec_rb_sprintf8, 2);
+ rb_define_method(cls, "rb_vsprintf", string_spec_rb_vsprintf, 4);
+ rb_define_method(cls, "rb_str_equal", string_spec_rb_str_equal, 2);
+ rb_define_method(cls, "rb_usascii_str_new", string_spec_rb_usascii_str_new, 2);
+ rb_define_method(cls, "rb_usascii_str_new_lit", string_spec_rb_usascii_str_new_lit, 0);
+ rb_define_method(cls, "rb_usascii_str_new_lit_non_ascii", string_spec_rb_usascii_str_new_lit_non_ascii, 0);
+ rb_define_method(cls, "rb_usascii_str_new_cstr", string_spec_rb_usascii_str_new_cstr, 1);
+ rb_define_method(cls, "rb_String", string_spec_rb_String, 1);
+ rb_define_method(cls, "rb_string_value_cstr", string_spec_rb_string_value_cstr, 1);
+ rb_define_method(cls, "rb_str_modify", string_spec_rb_str_modify, 1);
+ rb_define_method(cls, "rb_utf8_str_new_static", string_spec_rb_utf8_str_new_static, 0);
+ rb_define_method(cls, "rb_utf8_str_new", string_spec_rb_utf8_str_new, 0);
+ rb_define_method(cls, "rb_utf8_str_new_cstr", string_spec_rb_utf8_str_new_cstr, 0);
+ rb_define_method(cls, "rb_str_vcatf", string_spec_rb_str_vcatf, 1);
+ rb_define_method(cls, "rb_str_catf", string_spec_rb_str_catf, 1);
+ rb_define_method(cls, "rb_str_locktmp", string_spec_rb_str_locktmp, 1);
+ rb_define_method(cls, "rb_str_unlocktmp", string_spec_rb_str_unlocktmp, 1);
+ rb_define_method(cls, "rb_enc_interned_str_cstr", string_spec_rb_enc_interned_str_cstr, 2);
+ rb_define_method(cls, "rb_str_to_interned_str", string_spec_rb_str_to_interned_str, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/struct_spec.c b/spec/ruby/optional/capi/ext/struct_spec.c
new file mode 100644
index 0000000000..0393d6937d
--- /dev/null
+++ b/spec/ruby/optional/capi/ext/struct_spec.c
@@ -0,0 +1,85 @@
+#include "ruby.h"
+#include "rubyspec.h"
+
+#include "ruby/intern.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static VALUE struct_spec_rb_struct_aref(VALUE self, VALUE st, VALUE key) {
+ return rb_struct_aref(st, key);
+}
+
+static VALUE struct_spec_rb_struct_getmember(VALUE self, VALUE st, VALUE key) {
+ return rb_struct_getmember(st, SYM2ID(key));
+}
+
+static VALUE struct_spec_rb_struct_s_members(VALUE self, VALUE klass)
+{
+ return rb_ary_dup(rb_struct_s_members(klass));
+}
+
+static VALUE struct_spec_rb_struct_members(VALUE self, VALUE st)
+{
+ return rb_ary_dup(rb_struct_members(st));
+}
+
+static VALUE struct_spec_rb_struct_aset(VALUE self, VALUE st, VALUE key, VALUE value) {
+ return rb_struct_aset(st, key, value);
+}
+
+/* Only allow setting three attributes, should be sufficient for testing. */
+static VALUE struct_spec_struct_define(VALUE self, VALUE name,
+ VALUE attr1, VALUE attr2, VALUE attr3) {
+
+ const char *a1 = StringValuePtr(attr1);
+ const char *a2 = StringValuePtr(attr2);
+ const char *a3 = StringValuePtr(attr3);
+ char *nm = NULL;
+
+ if (name != Qnil) nm = StringValuePtr(name);
+
+ return rb_struct_define(nm, a1, a2, a3, NULL);
+}
+
+/* Only allow setting three attributes, should be sufficient for testing. */
+static VALUE struct_spec_struct_define_under(VALUE self, VALUE outer,
+ VALUE name, VALUE attr1, VALUE attr2, VALUE attr3) {
+
+ const char *nm = StringValuePtr(name);
+ const char *a1 = StringValuePtr(attr1);
+ const char *a2 = StringValuePtr(attr2);
+ const char *a3 = StringValuePtr(attr3);
+
+ return rb_struct_define_under(outer, nm, a1, a2, a3, NULL);
+}
+
+static VALUE struct_spec_rb_struct_new(VALUE self, VALUE klass,
+ VALUE a, VALUE b, VALUE c)
+{
+
+ return rb_struct_new(klass, a, b, c);
+}
+
+static VALUE struct_spec_rb_struct_size(VALUE self, VALUE st)
+{
+ return rb_struct_size(st);
+}
+
+void Init_struct_spec(void) {
+ VALUE cls = rb_define_class("CApiStructSpecs", rb_cObject);
+ rb_define_method(cls, "rb_struct_aref", struct_spec_rb_struct_aref, 2);
+ rb_define_method(cls, "rb_struct_getmember", struct_spec_rb_struct_getmember, 2);
+ rb_define_method(cls, "rb_struct_s_members", struct_spec_rb_struct_s_members, 1);
+ rb_define_method(cls, "rb_struct_members", struct_spec_rb_struct_members, 1);
+ rb_define_method(cls, "rb_struct_aset", struct_spec_rb_struct_aset, 3);
+ rb_define_method(cls, "rb_struct_define", struct_spec_struct_define, 4);
+ rb_define_method(cls, "rb_struct_define_under", struct_spec_struct_define_under, 5);
+ rb_define_method(cls, "rb_struct_new", struct_spec_rb_struct_new, 4);
+ rb_define_method(cls, "rb_struct_size", struct_spec_rb_struct_size, 1);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/spec/ruby/optional/capi/ext/symbol_spec.c b/spec/ruby/optional/capi/ext/symbol_spec.c
new file mode 100644